1.KNN算法简介
1.1 介绍
邻近算法,或者说K最邻近(KNN,K-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是K个最近的邻居的意思,说的是每个样本都可以用它最接近的K个邻近值来代表。近邻算法就是将数据集合中每一个记录进行分类的方法。
1.2 核心思想
KNN算法的核心思想是,如果一个样本在特征空间中的K个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。
比如说在下图中,在一下确定的样本点中,出现一个新的样本要去识别(New example to classify)就需要计算这个新的样本点和其他样本点的距离(Distance),离新的样本点近的样本点谁更多,那新的样本点属于那个多的样本点(近朱者赤近墨者黑)。在下图当我们把Training instance 也就是K设置成1时,我们可以看到这个新的样本点就属于Class1,那我们把K设置成3,发现在范围内Class2最多,那么它就属于Class2。
大家可以看到k值的选取会直接影响到评测结果,如果k值选取过大,相当于用较大领域中的训练 实例进行预测,这样看起来是觉得数据越多可能越准确,但实际上并不然,如果要想获得较多个k 值,这样你就需要把距离进一步扩大,预测准确率自然会下降。
1.3 KNN算法中计算距离方法
特征空间中两个实例点的距离时两个实例点相似程度的反映。
KNN中,有多种常见的距离衡量方法。 如欧几里得距离、曼哈顿距离。
1.3.1欧几里得距离(Euclidean Distance)
这就是欧几里得距离(Euclidean Distance)公式
其中,x和y分别表示两个向量,n表示向量的维度(特征数),xi和yi分别表示向量x和y在第i个维度上的取值。公式的意义是计算两个向量之间的欧几里得距离。
1.3.2曼哈顿距离(Manhattan Distance)
这是曼哈顿距离(Manhattan Distance)公式
在该公式中,x 和 y 分别表示两个向量,n 表示向量的维度(特征数),xi 和 yi 分别表示向量 x 和 y 在第 i 个维度上的取值。公式的含义是计算两个向量之间的曼哈顿距离,即两个向量各个维度上差值的绝对值之和。
1.4 KNN算法的一般流程
-
收集数据:收集有标签数据(也就是包含已知分类的训练样本集);
-
准备数据:对数据进行预处理,包括数据清理、数据编码等,预处理可能包括归一化或标准化特征;
-
分析数据:对数据进行可视化、探索性分析,了解数据的特征和分布;
-
训练算法:将训练样本集输入到模型中,进行模型训练;
-
测试算法:使用测试数据集来验证模型的性能,并对模型进行优化;
-
使用算法:当模型训练完成后,可以将它应用到新的未知样本中,进行分类或回归预测。
2.KNN算法的实现(鸢尾花分类)
2.1准备数据:从txt文件中解析数据
#准备数据:从文本文件中解析数据
import numpy as np
def file2matrix(filename):
#打开文件
fr = open(filename)
#读取文件所有内容
arrayOLines = fr.readlines()
#得到文件行数
numberOfLines = len(arrayOLines)
#返回的NumPy矩阵,解析完成的数据:numberOfLines行,4列
returnMat = np.zeros((numberOfLines,4))
#返回的分类标签向量
classLabelVector = []
#行的索引值
index = 0
for line in arrayOLines:
#s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
line = line.strip()
#使用s.split(str="",num=string,cout(str))将字符串根据','分隔符进行切片。
listFromLine = line.split(',')
#将数据前四列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
returnMat[index,:] = listFromLine[0:4]
#进行分类,1代表setosa,2代表versicolor,3代表virginica
#listFromLine[-1]代表获取listFromLine列表的最后一个元素,在数据集中就是标签
if listFromLine[-1] == 'setosa':
classLabelVector.append(1)
elif listFromLine[-1] == 'versicolor':
classLabelVector.append(2)
elif listFromLine[-1] == 'virginica':
classLabelVector.append(3)
index += 1
return returnMat, classLabelVector
读取数据,显示特征矩阵,标签
filename = 'D:\KNN\iris.txt'
#将文件里的数据赋值给datingDataMat(特征矩阵),datingLabels(标签)
datingDataMat, datingLabels = file2matrix(filename)
print(datingDataMat)
print(datingLabels)
输出 ,数据太多了,没有全部输出出来
2.2分析数据:使用Matplotlib创建散点图
matplotlib是pytorch自带的模块,不需要额外安装。
#分析数据:使用Matplotlib创建散点图,导入包
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
#数据展示,以图标的形式
def showData(datingDataMat, datingLabels):
fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))
#设置样本的颜色
LabelsColors = []
for i in datingLabels:
if i == 1:
LabelsColors.append('black')
if i == 2:
LabelsColors.append('orange')
if i == 3:
LabelsColors.append('red')
# 画出位于二维矩阵[0][0]散点图,以datingDataMat矩阵的第一个和第二个,分别是萼片的长度和宽度数据画散点数据,散点大小为15,透明度为0.5,下面的依次类推,
axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
#设置标题
axs0_title_text = axs[0][0].set_title('sepal length-width')
#设置x轴
axs0_xlabel_text = axs[0][0].set_xlabel('sepal length')
#设置y轴
axs0_ylabel_text = axs[0][0].set_ylabel('sepal width')
plt.setp(axs0_title_text, size=9, weight='bold', color='red')
plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
#datingDataMat矩阵的第三个和第四个,分别是花瓣的长度和宽度数据
axs[0][1].scatter(x=datingDataMat[:, 2], y=datingDataMat[:, 3], color=LabelsColors, s=15, alpha=.5)
axs1_title_text = axs[0][1].set_title('petal length-width')
axs1_xlabel_text = axs[0][1].set_xlabel('petal length')
axs1_ylabel_text = axs[0][1].set_ylabel('petal width')
plt.setp(axs1_title_text, size=9, weight='bold', color='red')
plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')
#datingDataMat矩阵的第一个和第三个,分别是萼片和花瓣的长度
axs[1][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
axs2_title_text = axs[1][0].set_title('sepal-petal length')
axs2_xlabel_text = axs[1][0].set_xlabel('sepal length')
axs2_ylabel_text = axs[1][0].set_ylabel('petal length')
plt.setp(axs2_title_text, size=9, weight='bold', color='red')
plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
#datingDataMat矩阵的第二个和第四个,分别是萼片和花瓣的宽度
axs[1][1].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 3], color=LabelsColors, s=15, alpha=.5)
axs3_title_text = axs[1][1].set_title('sepal-petal width')
axs3_xlabel_text = axs[1][1].set_xlabel('sepal width')
axs3_ylabel_text = axs[1][1].set_ylabel('petal width')
plt.setp(axs3_title_text, size=9, weight='bold', color='red')
plt.setp(axs3_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs3_ylabel_text, size=7, weight='bold', color='black')
#绘制图例,为数据集中每个类别的散点图添加标签和颜色,方便观察和区分不同类别的数据点。
#其中,使用matplotlib库提供的Line2D函数创建一个空的线条,并指定线条的颜色、标记和标签等属性。
#对于每个类别的散点图,在循环中使用zip函数将类别的标签和颜色一一对应,然后使用Line2D函数的参数传递方法将颜色、标记、标签等属性应用到不同的类别上。
#最后,将每个类别的线条添加到对应的绘图区域中,并调用show函数显示图像。
handles = []
labels = ['setosa', 'versicolor', 'virginica']
colors = ['black', 'orange', 'red']
for c, label in zip(colors, labels):
handles.append(mlines.Line2D([], [], color=c, marker='.', markersize=6, label=label))
axs[0][0].legend(handles=handles)
axs[0][1].legend(handles=handles)
axs[1][0].legend(handles=handles)
axs[1][1].legend(handles=handles)
plt.show()
showData(datingDataMat,datingLabels)
2.3准备数据:归一化数据
def autoNorm(dataSet):
#获得数据的最小值
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
#最大值和最小值的范围
ranges = maxVals - minVals
#shape(dataSet)返回dataSet的矩阵行列数
normDataSet = np.zeros(np.shape(dataSet))
#返回dataSet的行数
m = dataSet.shape[0]
#原始值减去最小值
normDataSet = dataSet - np.tile(minVals, (m, 1))
#除以最大和最小值的差,得到归一化数据
normDataSet = normDataSet / np.tile(ranges, (m, 1))
#返回归一化数据结果,数据范围,最小值
return normDataSet, ranges, minVals
#normDataSet是将原始数据集进行归一化处理后得到的结果,使得数据集的值都落在0到1的范围内。
normDataSet, ranges, minVals = autoNorm(datingDataMat)
print(normDataSet)
print(ranges)
print(minVals)
2.4测试算法:作为完整程序的验证分类器
# 分类器
import operator
# 输入:inX - 用于分类的数据(测试集);dataSet - 训练集;labes - 分类标签;K - KNN算法参数,选择距离最小的K个点
# 输出:sortedClassCount[0][0] - 分类结果
def classify0(inX, dataSet, labels, k):
#numpy函数shape[0]返回dataSet的行数
dataSetSize = dataSet.shape[0]
#在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
#二维特征相减后平方
sqDiffMat = diffMat**2
#sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
#开方,计算出距离
distances = sqDistances**0.5
#返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
#定一个记录类别次数的字典
classCount = {}
for i in range(k):
#取出前k个元素的类别
voteIlabel = labels[sortedDistIndices[i]]
#dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
#计算类别次数
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#python3中用items()替换python2中的iteritems()
#key=operator.itemgetter(1)根据字典的值进行排序
#key=operator.itemgetter(0)根据字典的键进行排序
#reverse降序排序字典
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#返回次数最多的类别,即所要分类的类别
return sortedClassCount[0][0]
#获取归一化后的数据集(norDataSet)的行数
m = normDataSet.shape[0]
#设置测试集的为总数据集的20%
numTestVecs = int(m*0.2)
errorCount = 0.0
for i in range(numTestVecs):
#设置K是10,进行分类
classifyResult = classify0(normDataSet[i,:],normDataSet[numTestVecs:m,:],
datingLabels[numTestVecs:m],10)
print("分类结果:%d,真实类别:%d" % (classifyResult,datingLabels[i]))
if(classifyResult!=datingLabels[i]):
errorCount += 1.0
print("错误率:%f%%" %(errorCount/float(numTestVecs)*100))
2.5使用算法:构建完美系统
# 通过输入花的四维特征,进行分类输出
def classifyflower():
#输出结果
resultList = ['setosa','versicolor','virginica']
#四维特征输入
sepalength = float(input("萼片长度:"))
spealwidth = float(input("萼片宽度:"))
petallength = float(input("花瓣长度:"))
petalwidth = float(input("花瓣宽度:"))
#打开的文件名
filename = "D:\KNN\iris.txt"
#打开并处理数据
datingDataMat, datingLabels = file2matrix(filename)
#训练集归一化
normMat, ranges, minVals = autoNorm(datingDataMat)
#生成NumPy数组,测试集
inArr = np.array([sepalength,spealwidth,petallength,petalwidth ])
#测试集归一化
norminArr = (inArr - minVals) / ranges
#返回分类结果,设置k为4
classifierResult = classify0(norminArr, normMat, datingLabels, 4)
#打印结果
print("这可能是鸢尾花的%s种类" % (resultList[classifierResult-1]))
classifyflower()
输入6 3 3 1.5
3 小结
3.1遇到的问题以及解决办法
一开始不知道怎么读取数据,可以使用空格,逗号,/t来读取数据
编写代码的时候不小心弄错了缩进距离,导致代码不能运行
在Python中,缩进必须保持一致,相同级别的代码块必须使用相同数量的缩进空格,否则会导致语法错误。因此,确保所有行的缩进空格数量保持一致非常重要。
在实验中我设置几次k值结果预测函数的检测值的错误率都是0,可以不断调大k的值,错误率就会上升
3.2总结
k值过小容易导致KNN算法的过拟合
如果k值比较小,相当于我们用较小的领域内的训练样本对实例进行预测。这时,算法的近似误差(Approximate Error)会比较小,因为只有与输入实例相近的训练样本才会对预测结果起作用。但是,它也有明显的缺点:算法的估计误差比较大,预测结果会对近邻点十分敏感
k值过大容易导致KNN算法的欠拟合
如果k值选择较大的话,距离较远的训练样本也能够对实例预测结果产生影响。算法的近邻误差会偏大,距离较远的点(与预测实例不相似)也会同样对预测结果产生影响,使得预测结果产生较大偏差