一. KNN概述
k-近邻(kNN, k-NearestNeighbor)算法是一种基本分类与回归方法,我们这里只讨论分类问题中的 k-近邻算法。
k 近邻算法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,可以取多类。k 近邻算法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其 k 个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻算法不具有显式的学习过程。
k 近邻算法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。 k值的选择、距离度量以及分类决策规则是k近邻算法的三个基本要素。
二. KNN原理
- 假设有一个带有标签的样本数据集(训练样本集)
- 输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。
a. 计算新数据与样本数据集中每个样本的距离
b. 对求得的所有距离进行排序(从小到大,距离越近表示越相似)
c. 取前 k (k 一般小于等于 20 )个样本数据对应的标签
3.求 k 个数据中出现次数最多的分类标签作为新数据的类别
通俗理解:给定训练集,对于某个新输入的样本,计算该待分类样本距离训练集所有样本的距离,取距离最近的K个样本呢,统计他们的类别,K个样本的最多数属于哪个类,哪个类将被判定为新样本的类别
三. 项目案例
1.项目概述
海伦使用约会网站寻找约会对象。经过一段时间之后,她发现曾交往过三种类型的人:
- 不喜欢的人
- 魅力一般的人
- 极具魅力的人
现在她收集到了一些约会网站未曾记录的数据信息,这更有助于匹配对象的归类
2.收集数据
海伦把这些约会对象的数据存放在文本文件 datingTestSet2.txt 中,总共有 1000 行。海伦约会的对象主要包含以下 3 种特征:
- 每年获得的飞行常客里程数
- 玩视频游戏所耗时间百分比
- 每周消费的冰淇淋公升数
文本文件数据格式如下:
3. 思路分析
a. 导入数据,可以通过pandas或使用.readlines()
b. 可视化(非必要):可以观察数据的分布规则,查看哪些特征具有可分性,是否不存在类别不平衡问题
c. 数据归一化:归一化特征值,消除特征之间量级不同导致的影响,其次程序运行收敛更快, 归一化在-1--+1之间是统计的 坐标分布
d. 分类(计算距离所有已知类别样本的距离,距离排序,取前K个样本,统计K个样本的类别,返回频次最高的类别)
四. 代码分析
import os
import matplotlib.pyplot as plt
import operator
import math
from mpl_toolkits.mplot3d import Axes3D
1. 导入数据
filepath ='D:\code\code_test\KNN\海伦约会\datingTestSet2.txt' # data的绝对路径
pdData = pd.read_csv(filepath, header=None, names=['Fly_distance', 'Game_time','Ice_weight' , 'label'], sep='\s+')
print(pdData.head(7)) # 打印前 7 条数据
# 划分数据集
k = 50
# 划分数据(0-899) 和测试数据(900-999)
norm_train_dataSet = normData.iloc[0:900, 0:3]
#print(norm_train_dataSet)
train_dataSet_labels = pdData.iloc[0:900, 3]
#print(train_dataSet_labels)
norm_test_dataSet = normData.iloc[900:1000, 0:3]
#print(norm_test_dataSet)
test_dataSet_labels = pdData.iloc[900:1000, 3]
#print(test_dataSet_labels)
2. 可视化数据
def plot_show(pdData,model):
if model == '2D': # 二维 散点图
label_1 = pdData[pdData['label'] == 1]
label_2 = pdData[pdData['label'] == 2]
label_3 = pdData[pdData['label'] == 3]
fig, ax = plt.subplots(figsize=(10, 5)) # 画图大小
ax.scatter(label_1['Fly_distance'], label_1['Game_time'], s=30, c='b', marker='o', label='1')
ax.scatter(label_2['Fly_distance'], label_2['Game_time'], s=30, c='r', marker='o', label='2')
ax.scatter(label_3['Fly_distance'], label_3['Game_time'], s=30, c='g', marker='o', label='3')
ax.legend()
ax.set_xlabel('Fly_distance')
ax.set_ylabel('Game_time')
plt.show()
if model == '3D':
# 可视化--Matplotlib画三维
label_1 = pdData[pdData['label'] == 1]
label_2 = pdData[pdData['label'] == 2]
label_3 = pdData[pdData['label'] == 3]
x_1 = label_1.loc[:, ['Fly_distance']]
x_2 = label_2.loc[:, ['Fly_distance']]
x_3 = label_3.loc[:, ['Fly_distance']]
y_1 = label_1.loc[:, ['Game_time']]
y_2 = label_2.loc[:, ['Game_time']]
y_3 = label_3.loc[:, ['Game_time']]
z_1 = label_1.loc[:, ['Ice_weight']]
z_2 = label_2.loc[:, ['Ice_weight']]
z_3 = label_3.loc[:, ['Ice_weight']]
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x_1, y_1, z_1, c='r', label='label_1')
ax.scatter(x_2, y_2, z_2, c='b', label='label_2')
ax.scatter(x_3, y_3, z_3, c='y', label='label_3')
# 添加坐标轴(顺序是Z, Y, X)
ax.set_zlabel('Z', fontdict={'size': 15, 'color': 'red'})
ax.set_ylabel('Y', fontdict={'size': 15, 'color': 'red'})
ax.set_xlabel('X', fontdict={'size': 15, 'color': 'red'})
plt.show()
3. 归一化数据
def autoNorm(pdData,method):
dataSet = pdData.iloc[:, 0:3]
if method == 'linear':
# 计算每种属性的最大值、最小值、范围
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
# 极差
ranges = maxVals - minVals
normDataSet = np.zeros(dtype=float, shape=(dataSet.shape))
m = dataSet.shape[0]
# 生成与最小值之差组成的矩阵
normDataSet = dataSet - np.tile(minVals, (m, 1)) #####np.tile(a, (2,3)) 将a中内容重复3次,两行3*a列的数据:#a=[4,5,6] #print(np.tile(a,(4,3)))#############
# 将最小值之差除以max-min组成矩阵
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet
#if method == 'log':
'''
对数函数转换,表达式如下:
y = log10(x)
说明:以10为底的对数函数转换。
'''
# if method == 'arccot'
'''
反余切函数转换
y=arctan(x)*2/PI
'''
4. 类别预测(计算距离、距离排序、统计最近的K个样本的类别、返回出现频次最高的类别作为预测结果)
def class_predict(inx, norm_train_dataSet, labels, k):
# 距离度量 度量公式为欧氏距离
diffMat = np.tile(inx, (norm_train_dataSet.shape[0],1)) - norm_train_dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5 # 得到传进来的一个样本inx 到norm_train_dataSet 中所有点的距离
# 将距离排序:从小到大
sortedDistIndicies = distances.argsort()
# 选取前K个最短距离, 取该K个样本中类别出现次数最多的类别作为 预测 label
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
5. 测试
每次调用class_predict传入一个待分类样本,得到其预测的类别,循环test_num次后,统计得到所有测试样本中分类正确的样本个数,返回准确率
def class_test(norm_train_dataSet,train_dataSet_labels, norm_test_dataSet, test_dataSet_labels, k):
pre_right_count = 0
# print(norm_test_dataSet.shape[0])
test_num = norm_test_dataSet.shape[0]
for i in range(test_num):
inx = norm_test_dataSet.iloc[i, :]
labels = test_dataSet_labels.iloc[i]
pre_label = class_predict(inx, norm_train_dataSet, train_dataSet_labels, k)
if labels == pre_label:
pre_right_count += 1
acc = pre_right_count / test_num
return acc
五.完整代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import operator
import math
from mpl_toolkits.mplot3d import Axes3D
# 可视化函数
def plot_show(pdData,model):
if model == '2D': # 二维 散点图
label_1 = pdData[pdData['label'] == 1]
label_2 = pdData[pdData['label'] == 2]
label_3 = pdData[pdData['label'] == 3]
fig, ax = plt.subplots(figsize=(10, 5)) # 画图大小
ax.scatter(label_1['Fly_distance'], label_1['Game_time'], s=30, c='b', marker='o', label='1')
ax.scatter(label_2['Fly_distance'], label_2['Game_time'], s=30, c='r', marker='o', label='2')
ax.scatter(label_3['Fly_distance'], label_3['Game_time'], s=30, c='g', marker='o', label='3')
ax.legend()
ax.set_xlabel('Fly_distance')
ax.set_ylabel('Game_time')
plt.show()
if model == '3D':
# 可视化--Matplotlib画三维
label_1 = pdData[pdData['label'] == 1]
label_2 = pdData[pdData['label'] == 2]
label_3 = pdData[pdData['label'] == 3]
x_1 = label_1.loc[:, ['Fly_distance']]
x_2 = label_2.loc[:, ['Fly_distance']]
x_3 = label_3.loc[:, ['Fly_distance']]
y_1 = label_1.loc[:, ['Game_time']]
y_2 = label_2.loc[:, ['Game_time']]
y_3 = label_3.loc[:, ['Game_time']]
z_1 = label_1.loc[:, ['Ice_weight']]
z_2 = label_2.loc[:, ['Ice_weight']]
z_3 = label_3.loc[:, ['Ice_weight']]
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x_1, y_1, z_1, c='r', label='label_1')
ax.scatter(x_2, y_2, z_2, c='b', label='label_2')
ax.scatter(x_3, y_3, z_3, c='y', label='label_3')
# 添加坐标轴(顺序是Z, Y, X)
ax.set_zlabel('Z', fontdict={'size': 15, 'color': 'red'})
ax.set_ylabel('Y', fontdict={'size': 15, 'color': 'red'})
ax.set_xlabel('X', fontdict={'size': 15, 'color': 'red'})
plt.show()
# 归一化函数
def autoNorm(pdData, method):
"""
Desc:
归一化特征值,消除特征之间量级不同导致的影响,其次程序运行收敛更快, 归一化在-1--+1之间是统计的坐标分布。
parameter:
dataSet: 数据集
return:
归一化后的数据集 normDataSet. ranges和minVals即最小值与范围,并没有用到
归一化公式:
Y = (X-Xmin)/(Xmax-Xmin)
其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。
"""
dataSet = pdData.iloc[:, 0:3]
if method == 'linear':
# 计算每种属性的最大值、最小值、范围
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
# 极差
ranges = maxVals - minVals
normDataSet = np.zeros(dtype=float, shape=(dataSet.shape))
m = dataSet.shape[0]
# 生成与最小值之差组成的矩阵
normDataSet = dataSet - np.tile(minVals, (
m, 1)) #####np.tile(a, (2,3)) 将a中内容重复3次,两行3*a列的数据:#a=[4,5,6] #print(np.tile(a,(4,3)))#############
# 将最小值之差除以max-min组成矩阵
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet
# if method == 'log':
'''
对数函数转换,表达式如下:
y = log10(x)
说明:以10为底的对数函数转换。
'''
# if method == 'arcsin'
'''
反余切函数转换
y=arctan(x)*2/PI
'''
filepath ='D:\code\code_test\自己手动搭建\KNN\海伦约会\datingTestSet2.txt'
pdData = pd.read_csv(filepath, header=None, names=['Fly_distance', 'Game_time', 'Ice_weight' , 'label'], sep='\s+') # 正则匹配 sep='\s+',间隔读取数据
#print(pdData.head(7)) # 打印前 7 条数据
# 可视化数据
plot_show(pdData, '3D')
plot_show(pdData, '2D')
normData = autoNorm(pdData,'linear')
# KNN算法伪代码
'''
对于每一个在数据集中的数据点:
计算目标的数据点(需要分类的数据点)与该数据点的距离
将距离排序:从小到大 * 三要素之一: 距离度量
选取前K个最短距离 * 三要素之二: k的取值
选取这K个中最多的分类类别 * 三要素之三:分类决策
返回该类别来作为目标数据点的预测值
'''
k = 50
# 划分数据(0-899) 和测试数据(900-999)
norm_train_dataSet = normData.iloc[0:900, 0:3]
#print(norm_train_dataSet)
train_dataSet_labels = pdData.iloc[0:900, 3]
#print(train_dataSet_labels)
norm_test_dataSet = normData.iloc[900:1000, 0:3]
#print(norm_test_dataSet)
test_dataSet_labels = pdData.iloc[900:1000, 3]
#print(test_dataSet_labels)
######################################################
# 分类: 返回预测的 label
def class_predict(inx, norm_train_dataSet, labels, k):
# 距离度量 度量公式为欧氏距离
diffMat = np.tile(inx, (norm_train_dataSet.shape[0],1)) - norm_train_dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5 # 得到传进来的一个样本inx 到norm_train_dataSet 中所有点的距离
# 将距离排序:从小到大
sortedDistIndicies = distances.argsort()
# 选取前K个最短距离, 取该K个样本中类别出现次数最多的类别作为 预测 label
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
'''
inx = norm_test_dataSet.iloc[0,:] # 取第一个样本值inx
end=class_test(inx, norm_train_dataSet, train_dataSet_labels, 10)
print(end)
'''
# 传入测试数据,逐个送入class_predict函数进行预测,统计准确率
def class_test(norm_train_dataSet,train_dataSet_labels, norm_test_dataSet, test_dataSet_labels, k):
pre_right_count = 0
# print(norm_test_dataSet.shape[0])
test_num = norm_test_dataSet.shape[0]
for i in range(test_num):
inx = norm_test_dataSet.iloc[i, :]
labels = test_dataSet_labels.iloc[i]
pre_label = class_predict(inx, norm_train_dataSet, train_dataSet_labels, k)
if labels == pre_label:
pre_right_count += 1
acc = pre_right_count / test_num
return acc
acc = class_test(norm_train_dataSet,train_dataSet_labels, norm_test_dataSet, test_dataSet_labels, k)
print(acc)
写在最后:欢迎批评指正,共同学习进步!