1.算法概述
K-近邻算法的基本原理是基于每个数据点在数据空间中的分布,当有新数据点需要分类时,通过寻找与其最相似的K个数据点,并根据这K个数据点的多数投票或者加权投票来决定新数据点的分类,举个例子,假设我们有以下数据集:
- 数据点1: (0.5, 0.8, 0)
- 数据点2: (1.0, 0.7, 0)
- 数据点3: (0.5, 0.6, 1)
- 数据点4: (1.0, 0.9, 1)
同时,我们有一个新的数据点(0.7, 0.8)
首先,对于每个数据点,计算新数据点与它们的特征之间的距离,选取距离最小的那个数据点;然后,我们选择距离最小的K个数据点,即距离新数据点最近的K个数据点。对于上面的例子,距离新数据点最近的3个数据点分别是数据点1, 数据点4, 和数据点2;最后统计这K(3)个数据点中每个类别的数量,然后根据同类别数量多少来决定新数据点的分类。在上面的例子中,我们发现有1个数据点(数据点1)的类别是0,有2个数据点(数据点2和数据点4)的类别是1,所以新数据点的类别应该是1
以下是上述例子的示意图:
2.算法代码说明
先给出K-近邻算法的代码
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize,1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort()
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
sortedClassCount = sorted(classCount.iteritems(),
key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
classify0
函数有四个参数,分别是输入向量inX
、训练样本dataSet
、标签向量labels
和选择数目k
,分别用第一部分的概念表示:新的数据点(0.7, 0.8)、 四个初始数据点集合、训练数据集中每个数据点对应的标签0/1、k=3
整体算法可以分成三个部分:距离计算、选择距离最小的k个点、排序
- 距离计算
# 获取数据集中的数据点数量
dataSetSize = dataSet.shape[0]
# 通过从数据集中的每个数据点中减去输入数据点创建一个名为diffMat的矩阵。tile函数用于将inX复制并匹配dataSet的形状
diffMat = tile(inX, (dataSetSize,1)) - dataSet
# 对diffMat中的每个元素进行平方,计算平方差值
sqDiffMat = diffMat**2
# 沿着轴1对平方差值进行求和,计算平方欧氏距离
sqDistances = sqDiffMat.sum(axis=1)
# 对每个平方距离取平方根,得到欧氏距离
distances = sqDistances**0.5
- 选择k个点
# 获取将距离按升序排序的索引
sortedDistIndicies = distances.argsort()
# 初始化一个空字典,用于计算每个类别标签的出现次数
classCount={}
# 遍历sortedDistIndicies的前k个元素
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] # 获取相应索引处的标签
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #增加对应类别标签的计数
- 排序
# 对类别计数进行排序,按照计数值降序排序
sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1), reverse=True)
# 返回排序后的类别计数中的第一个类别
return sortedClassCount[0][0]
3.用KNN算法改进约会网站配对效果
以约会网站配对效果为例,训练样本dataSet
包含三个特征:每年飞行常客里程数、玩游戏耗费百分比、每周消费的冰激凌数;标签向量labels
为不喜欢、一般、喜欢三个
基本流程
- 收集数据:提供含有数据的文件,如txt、xlsx、csv等
- 准备数据:使用Python解析数据文件
- 分析数据:使用Matplotlib画二维扩散图
- 测试算法:使用数据中的部分数据作为测试样本
测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误 - 使用算法:产生简单的命令行程序,然后可以输人一些特征数据以判斯对方是否为自己喜欢的类型
准备数据
将数据处理前需要将数据的格式进行转换,因此要定义一个file2matrix
函数将输入的文件名字符串转换为样本矩阵和类标签向量
def file2matrix(filename):
fr = open(filename) # 打开,目标文件
numberOfLines = len(fr.readlines()) # 得到文件中的数据行数
returnMat = zeros((numberOfLines,3)) # 准备零矩阵存储要返回的数据
classLabelVector = [] # 用来存储标签
fr = open(filename)
index = 0
for line in fr.readlines():
line = line.strip() # 去除行两端的空白字符
listFromLine = line.split('\t') # 将行按制表符\t进行分割
returnMat[index,:] = listFromLine[0:3] # 将列表中前三个元素赋值给矩阵的当前行
classLabelVector.append(int(listFromLine[-1]))# 将列表的最后一个标签转换为整数类型,并添加到标签向量
index += 1
return returnMat,classLabelVector # 返回结果
创建散点图
使用matplotlib
库能使数据可视化,方便查看,以下是三个特征的相关可视化代码
import matplotlib
import matplotlib.pyplot as plt
# 绘图中文输出设置
plt.rc("font",family='SimHei') # 对中文字体进行指定
plt.rc('axes',unicode_minus=False) # 指定中文字体后正常显示负号
plt.tight_layout() # 紧密布局,使标题、文本等完整显示
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(Data[:,1], Data[:,2],15.0*array(Labels),15.0*array(Labels))
plt.xlabel('玩游戏时间所占百分比')
plt.ylabel('每周消费的冰激凌公升数')
plt.show()
图上的三种不同颜色尺寸的数据点分别表示了三个样本的分布情况
归一化数据
使用当前值减去最小值,并除以取值范围
def autoNorm(dataSet):
# 得到数据列中的最小和最大值以及范围
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
# 创建新的零矩阵用来保存归一化后的数据
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m,1))
normDataSet = normDataSet/tile(ranges, (m,1)) #按元素划分
return normDataSet, ranges, minVals
通过上述的函数,可以分别得到归一化后的矩阵、取值范围和最小值
测试算法
通过定义一个计数器变量来计算错误率,
# 定义函数,读取文件并进行数据处理
def datingClassTest():
# 定义一个百分比,用于控制测试样本数量
hoRatio = 0.10 # 在测试样本中的占比,也就是10%
# 从文件中加载数据集
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
# 对数据进行归一化处理
normMat, ranges, minVals = autoNorm(datingDataMat)
# 计算数据的总行数
m = normMat.shape[0]
# 计算测试样本的数量,即实际测试样本数量
numTestVecs = int(m * hoRatio)
# 初始化错误计数器,用于统计测试错误率
errorCount = 0.0
# 遍历测试样本,计算每个样本的错误率
for i in range(numTestVecs):
# 调用分类器进行分类
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
# 打印分类结果和实际结果
print("The classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
# 如果分类结果和实际结果不一致,则增加错误计数器
if (classifierResult != datingLabels[i]): errorCount += 1.0
# 输出错误率
print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
# 输出错误计数器
print(errorCount)
经过测试,错误率只有6.6%,算是不错的结果
使用算法
创建一个综合的使用函数,通过输入三个特征量来判断是否符合约会的概率
def classifyPerson():
# 创建三个结果标签向量的列表
resultList = ['不可能', '小概率', '大概率']
percentTats = float(input(\"玩游戏耗费百分比?"))
ffMiles = float(input("每年飞行常客里程数?"))
iceCream = float(input("每年消费的冰激凌数?"))
# 使用file2matrix函数得到数据和数据标签
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
# 使用autoNorm函数得到归一化的数据、取值范围和最小值
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = np.array([ffMiles, percentTats, iceCream, ])
# 再使用classify0函数得到结果
classifierResult = classify0((inArr - \minVals)/ranges, normMat, datingLabels, 3)
print("你可能喜欢这个人的概率: %s" % resultList[classifierResult - 1])
结果如下图所示
4.总结
本章学习了KNN算法,它是一种基于实例的学习方法。这种方法在处理分类和回归问题时都表现良好。KNN的基本思想是根据待分类数据(测试数据)与已知数据的相似度(距离度量)来确定它的分类。在实际应用中,通常选择一个合适的“邻居”数量K来平衡计算复杂度和预测精度。以下是对KNN算法的总结:
- 计算距离:KNN算法依赖于距离度量来计算待分类数据与已知数据之间的相似度
- 选择邻居:确定距离待分类数据最近的K个已知数据,这些数据即为待分类数据的“邻居”
- 投票表决:对于每个邻居,根据其所属类别的数量对其进行“投票”。预测时,选择票数最多的类别作为待分类数据的分类结果
- 计算权重:为避免“少数服从多数”的情况,可以为每个邻居分配一个权重,以降低预测错误的影响
- 分类结果:根据权重加权后的邻居投票结果,计算预测类别的预测概率。预测概率最高的类别即为待分类数据的预测类别
- 预测精度:预测精度是一个评估KNN算法性能的关键指标。预测精度可以通过平均准确率、AUC-ROC曲线等方法进行评估,在本章中使用了错误率来表示