一.问题提出
众所周知,电影可以按照题材分类,然而题材本身是如何定义的?怎么来判定某部电影属于哪个题材?动作片中也会存在接吻镜头,而爱情片中同样存在打斗场景,我们不能单纯依靠是否存在打斗或者亲吻来判断影片的类型。但是我们发现爱情片中的亲吻镜头更多,动作片中的打斗场景也更频繁,基于此类场景在某部电影中出现的次数,我们可以通过使用k-近邻算法构造程序,对电影进行分类。
二.K-近邻算法概述
简单来说,K-近邻算法采用不同特征值之间的距离方法进行分类。
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算量大,空间复杂度高,与决策树模型相比,KNN算法的可解释性不强
适用数据范围:数值型和标称型
三.K-近邻算法的工作原理
K-近邻算法(K-NearestNeighbor)的工作原理:对于一个新样本,K近邻算法的目的就是在已有数据中寻找与它最相似的K个数据,或者说是离它最近的K个邻居,并在这K个数据里进行统计,找到对应数据最多的类别,则该样本也属于这个类别。下面就以刚刚所提出的如何辨别动作片和爱情片为例,对KNN算法进行解释。
电影名称 | 打斗镜头 | 接吻镜头 | 电影类型 |
California Man | 3 | 104 | 爱情片 |
He‘s Not Really into Dudes | 2 | 100 | 爱情片 |
Beautiful Woman | 1 | 81 | 爱情片 |
Kevin Longblade | 101 | 10 | 动作片 |
Robo Slayer 3000 | 99 | 5 | 动作片 |
Amped ll | 98 | 2 | 动作片 |
? | 18 | 90 | 未知 |
在KNN算法中,我们采用欧式距离公式,计算出未知电影和已知电影的距离。
电影名称 | 与未知电影的距离 |
California Man | 20.5 |
He‘s Not Really into Dudes | 18.7 |
Beautiful Woman | 19.2 |
Kevin Longblade | 115.3 |
Robo Slayer 3000 | 117.4 |
Amped ll | 118.9 |
现在我们得到了样本集中所有电影与未知电影的距离,所以我们可以找到k个距离最近的电影。假设k=3,则最靠近的三部电影是California Man、He‘s Not Really into Dudes、Beautiful Woman,这三部电影的类别都是爱情片,故我们判定未知电影是爱情片。
四.K-近邻算法的一般流程
理解了KNN算法的基本原理后,还需要知道KNN算法的一般流程
(1)搜集数据:可以使用任何方法。
(2)准备数据:距离计算所需要的数值,最好是结构化的数据格式。
(3)分析数据:可以使用任何方法。
(4)训练算法:此步骤不适用于k-近邻算法。
(5)测试算法:计算错误率。
(6)使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分 别属于哪个分类,最后应用对计算出的分类执行后续的处理。
五.K-近邻算法的代码实现
1.使用python导入数据
导入4组数据,每组数据有两个我们已知的属性或者特征值。group矩阵每行包含一组不同的数据,向量label包含了每个数据点的标签信息,其中label包含的元素个数等于group矩阵行数。
from numpy import *
import operator
def createDataSet():
group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels=['A','A','B','B']
return group,labels
2.K-近邻算法实现
def classify(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()#argsort()函数得到从小到大的索引值
classCount={}
for i in range(k): #选择距离最小的k个点
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel]=classCount.get(voteIlabel,0)+1 #统计每个类别出现的次数
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #迭代对象是字典的键值对,根据value值进行降序排序
return sortedClassCount[0][0]
3.测试结果
if __name__=='__main__':
group,labels=createDataSet()
print(classify([1,1],group,labels,3))
输入点的坐标是(1,1),最后得到结果为A
六.K-近邻算法的实际运用
1.项目简介
数据集来自集美大学acm校选,数据集包括三个特征值,一个分类值。其中特征值分别为得分,用时,年级。类别分为三类,1代表入选acm校队,2代表未入选但得到综测分,3代表未取得综测分。
数据集
2.编写knn算法
#knn算法
def classify(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()#argsort()函数得到从小到大的索引值
classCount={}
for i in range(k): #选择距离最小的k个点
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel]=classCount.get(voteIlabel,0)+1 #统计每个类别出现的次数
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #迭代对象是字典的键值对,根据value值进行降序排序
return sortedClassCount[0][0]
3.导入数据
为了使数据更有代表性,我们在导入数据这一步将数据集打乱
#导入数据
def file2matrix(filename):
fr = open(filename) #打开文件
arrayOLines = fr.readlines()#读取所有行
random.shuffle(arrayOLines)#将数据打乱
numberOfLines = len(arrayOLines)
returnMat = zeros((numberOfLines,3))
classLabelVector=[]
index=0
for line in arrayOLines:
line =line.strip() #删除一行中首尾的空白符
listFromLine=line.split(' ')#字符按照空格截取
returnMat[index,:] = listFromLine[0:3] #将数据的前三列提取出来,存放在特征矩阵中
classLabelVector.append(listFromLine[-1])
index += 1
return returnMat,classLabelVector
4.数据归一化
三个特征是同等重要的,为了使三个特征等权重,采用数据归一化是一种常用的方法。
oldValue是需要归一化的数据,max是对应列的最大值,min是对应列的最小值
#数据归一化
def autoNorm(dataSet):
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
ranges=maxVals-minVals
normDataSet=zeros(shape(dataSet)) #生成与dataset同类型的零矩阵
m = dataSet.shape[0] #读取行数
normDataSet=dataSet-tile(minVals,(m,1)) #分子
normDataSet=normDataSet/tile(ranges,(m,1))
return normDataSet,ranges,minVals
5.计算错误率
在knn算法里,最为重要的是如何确定k的值。在这一步中,我将数据集(已打乱)的%20作为测试集,使用其余的%80作为特征样本。通过计算错误率,我们可以进一步确定k值得范围,从而找到最优解。
#计算错误率
def datingClassTest():
hoRatio=0.20
datingDataMat,datingLabels = file2matrix("data.txt")
normMat, ranges, minVales = autoNorm(datingDataMat)
m=normMat.shape[0]
numTestVecs = int(m*hoRatio)#取出测试行数
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3) #numTestVecs个为输入的数据,剩下的作为原数据
print ("the classifier came back with: %s, the real answer is: %s"%(classifierResult, datingLabels[i]))
if(classifierResult != datingLabels[i]):
errorCount +=1.0
print("the total error rate is",{errorCount/float(numTestVecs)})
6.绘画三维图
if __name__=='__main__':
datingClassTest()
datingDataMat,datingLabels=file2matrix("data.txt")
normMat,ranges,minVals=autoNorm(datingDataMat)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_title('3d_image_show')
ax.set_xlabel('score')
ax.set_ylabel('time')
ax.set_zlabel('grade')
index=0
color={1:'blue',2:'red',3:'green'}#分配颜色
shape={1:'o',2:'^',3:'*'}
for x,y,z in normMat:
ax.scatter(x,y,z,c=color[int(datingLabels[index])],marker=shape[int(datingLabels[index])])#获取每一个点的标签值
index=index+1
plt.show()
7.结果显示
8.穷举k
为了寻找最优的k值,我采用的是穷举的方法。
def datingClassTest():
hoRatio=0.20
datingDataMat,datingLabels = file2matrix("data.txt")
normMat, ranges, minVales = autoNorm(datingDataMat)
m=normMat.shape[0]
numTestVecs = int(m*hoRatio)#取出测试行数
numSample = int(m*(1-hoRatio))
errorCount = 0.0
for k in range(1,numSample+1):
errorCount=0.0
for i in range(numTestVecs):
classifierResult = classify(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],k) #numTestVecs个为输入的数据,剩下的作为原数据
print ("the classifier came back with: %s, the real answer is: %s"%(classifierResult, datingLabels[i]))
if(classifierResult != datingLabels[i]):
errorCount +=1.0
print("the total error rate is",{errorCount/float(numTestVecs)})
...
通过运行,我发现在这个数据集中,随着k值的增大,错误率会慢慢变大。
9.实验总结与反思
总结:通过knn算法,我们可以实现基本的分类问题。k值较大,比较稳定,但过于平滑。k值较小,分类结果容易受噪声影响,出现过拟合。
反思:由于数据较少,在计算错误率时发现,无法更加细致地算出错误率的多少,对k值的判断不够准确。
10.参考教材
机器学习实战