k-近邻算法也算是分类模型中比较简单的算法之一,把它的思想简单概述一下就是测量不同特征值之间的距离由此进行分类,现在有个大概的思想再来讲原理和实现。
k-近邻算法
knn的优缺点:
优点 | 精度高,对异常值不敏感…… |
---|---|
缺点 | 计算的复杂度高 |
关于距离的计算问题
我们平时是如何计算距离的呢?根据两点之间直线最短的原理,那就连一条直线去测量就好了,这就是我们最常用的度量方法,也叫欧式距离
比如求A,B之间的距离那我们就用
这个公式就可以求出任意n维空间点之间的欧氏距离。
我们计算knn就还是用欧氏距离就好,关于其他几种距离的度量可以去看看慕课的这篇文章,讲的非常清楚
https://www.imooc.com/article/67203
思想: 有了不同特征值之间的距离,那如何做到分类,这就需要用到一个很基本的思想了—距离近的总是一类的,也可以说一个圈子的是一类人。
实现的流程:
- 计算测试数据与各个训练数据之间的距离
- 对计算得到的距离进行排序
- 选取距离最小的k个点
- 确定这k个点所代表的类别
- 返回这k个点中频率最高的类别作为测试数据的类别
关于k的选择问题
因为我们很重要的一步就是选取离测试值最近的k个点,所以k的选择就尤为重要,如果k取的太小,那就有可能受到数据噪音的干扰导致测试数据分类错误;如果k取的特别大(全部训练样本数),那就只会取到样本数目最多的类别。所以我们一般选择k值的时候会利用交叉验证,评估一系列k值从而选择出最好的k值。
knn的代码实现
def knn(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # shape读取数据矩阵第一维度的长度
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet # tile重复数组inX,有dataSet行 1个dataSet列,减法计算差值
sqDiffMat = diffMat ** 2
sqDisttances = sqDiffMat.sum(axis=1) # 普通sum默认参数为axis=0为普通相加,axis=1为一行的行向量相加
distances = sqDisttances ** 0.5
sortedDistIndicies = distances.argsort() # argsort返回数值从小到大的索引值(数组索引0,1,2,3)
# 选择距离最小的k个点
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] # 根据排序结果的索引值返回靠近的前k个标签
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # 各个标签出现频率
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 排序频率
# sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list。
# reverse默认升序 key关键字排序itemgetter(1)按照第一维度排序(0,1,2,3)
return sortedClassCount[0][0] # 找出频率最高的
来个简单的数据集检验一下
import numpy as np
import operator
def createData():
group=np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels=['A','A','B','B']
return group,labels
group,labels=createData()
print("预测点的类别为:",classify0([1.0,1.2],group,labels,3))
来个被用烂透了的实战例子—约会网站数据判断是否喜欢
先来看看数据
前三列分别是飞行里数,游戏时间占比,吃冰淇淋的升数,最后一列是喜欢程度,有三类:不喜欢,一般喜欢,非常喜欢;
然后我们需要把最后一列换成1,2,3这样的数字去代替喜欢程度,因为只有数字才是计算机能够识别的。
读取数据
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 读出数据行数
returnMat = np.zeros((numberOfLines, 3)) # 创建返回矩阵
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip() # 删除空白符
listFromLine = line.split('\t') # split指定分隔符对数据切片
returnMat[index, :] = listFromLine[0:3] # 选取前3个元素(特征)存储在返回矩阵中
classLabelVector.append(int(listFromLine[-1]))
# -1索引表示最后一列元素,位label信息存储在classLabelVector
index += 1
return returnMat, classLabelVector
datamat,datalabels=file2matrix('datingTestSet2.txt')
print(datamat,'\n',datalabels)
对这些数据画个散点图可视化一下
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datamat[:,1],datamat[:,2],15.0*np.array(datalabels),15.0*np.array(datalabels))
plt.xlabel('玩游戏的时间占比')
plt.ylabel('每周消费的冰淇淋数量')
plt.show()
现在实现给数据就能判断喜欢程度
# 归一化特征值
# 归一化公式 :(当前值-最小值)/range
def autoNorm(dataSet):
minVals = dataSet.min(0) # 存放每列最小值,参数0使得可以从列中选取最小值,而不是当前行
maxVals = dataSet.max(0) # 存放每列最大值
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet)) # 初始化归一化矩阵为读取的dataSet
m = dataSet.shape[0] # m保存第一行
# 特征矩阵是3x1000,min max range是1x3 因此采用tile将变量内容复制成输入矩阵同大小
normDataSet = dataSet - np.tile(minVals, (m, 1))
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
# 测试约会网站分类结果代码
def datingClassTest():
hoRatio = 0.10 # hold out 10%
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # load data setfrom file
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("分类器返回的值: %s, 正确的值: %s" % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): errorCount += 1.0
print("总的错误率是: %f" % (errorCount / float(numTestVecs)))
print("错误的个数:%f" % errorCount)
# 完整的约会网站预测:给定一个人,判断时候适合约会
def classifyPerson():
resultList = ['不喜欢', '一般喜欢', '特别喜欢']
percentTats = float(input("玩游戏占的百分比"))
ffMiles = float(input("每年坐飞机多少公里"))
iceCream = float(input("每年吃多少公升的冰淇淋"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = np.array([ffMiles, percentTats, iceCream])
classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
print("你将有可能对这个人是:", resultList[int(classifierResult) - 1])
datingClassTest() #分类器针对约会网站的测试代码
classifyPerson() # 约会网站预测
结束
其实knn实现起来很简单,计算欧氏距离—>对各个距离排序—>取前k个距离的值所代表的类—>根据取出的类中最多的类别作为自己的类别。