K-近邻算法:
学习机器学习的一些所感所悟;
参考机器学习;Peter Harrington著;人民邮电出版社;
1. 工作原理:
存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
初次接触我们通俗的来讲就是:
目前已知有了一系列的数据和他们所属于的类(具有标签)(label),现在给了我们一个未知标签的数据,让我们根据这些提供的数据判断这些数据所指的是哪一类标签,我们需要计算给我们的数据与已知标签的数据求“距离”,再根据“距离”的大小我们求得前k个出现次数最多的标签,即为我们想要的。
2.算法初试:
现在我们给定一些数据:
Group | Label |
---|---|
1.0, 1.1 | A |
1.0, 1.0 | A |
0, 0 | B |
0, 0.1 | B |
我们根据这个数据在坐标轴中画出:
如果我想知道0,0.0001的属性应该是什么,那么我在图中画出我这个点的位置我们就可以很明显的看出我们这个点距离B的距离最近,我们可以快速的判断他所属于的类。
那么我们怎么用代码实现呢?
首先我们在python中创建kNN.py,第一步当然是把我们的上面的那个表放进去:
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
这样我们在进入python开发环境后输入:
>>>import kNN
>>>group,labels = kNN.createDataset()
>>>group
>>>labels
会分别得到:
array([[1.0, 1.1],
[1.0, 1.0],
[0, 0],
[0, 0.1]])
['A','A','B','B']
接下来我们就开始计算这里面最重要的距离了,那么已知我们已经学过了很多求解距离的公式,比如:
1. 欧几里得距离
2. 曼哈顿距离
3.杰卡德距离
4. 马氏距离
5.切比雪夫距离
6.海明距离
7.马哈拉诺比斯距离
8.明可夫斯基距离
等等还有很多
详细的距离算法可参考博客距离公式
我们一般使用的都是欧式距离,这也是我们在之前学习数学中无论是平面直角坐标系还是立体直角坐标系都常用到的求解距离的公式。
def classify0(inX, dataset, labels, k):
dataSetSize = dataset.shape[0] # 如果传入的是上述的group,dataset.shape[0]等于4,因为4行2列,shape 0代表行,1代表列
diffMat = tile(inX, (dataSetSize, 1)) - dataset
print(diffMat)
sqDiffMat = diffMat ** 2
sqDistance = sqDiffMat.sum(axis=1)
print(sqDistance)
distance = sqDistance ** 0.5
sortedDistIndicies = distance.argsort()
print(sortedDistIndicies)
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] # 获得按照sortedDistIndicies排序的在labels中找到labels的值
print(voteIlabel)
classCount[voteIlabel] = classCount.get(voteIlabel, 0)+1 # 就是创建一个字典{'B': x ,'A': y }x就是记录B出现的次数,y是记录x出现的次数
print(classCount) # 所以后面要+1, 前面就是用get获得
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) #在机器学习书中这里是classCount.iteritems(),这是python2中的写法
print(sortedClassCount)
return sortedClassCount[0][0]
备注:
1. tile函数返回一个数组,tile(A,reps)表示A重复出现reps次的数组
2. shape函数表示返回矩阵的参数,shape[0]输出的是行数,shape[1]输出的是列数
3. sum(axis)就是压缩也可以说成是求和,axis=0是压缩成行,将所有的列相加成为一行。axis=1是压缩成列,将所有的行相加成一列
4. argsort()是返回数组从小到大的索引值
5. get函数是返回指定键的值
6. operator.itemgetter(1) 在这里获取的并不是值,而是定义了一个函数,目的是获取对象第几域中的值,因为上面我们都压缩成一列了所以只要获取第一域的值。
这里仅仅是将在classify0函数中的一些用法写出,但其的具体用法可以搜索其函数用法。
之后我们再输入>>>kNN.classify0([0,0],group,labels,3)
这里我们获取的结果应该是 B,与我们预期在图中观察到的一样
3.对约会网站进行改进:
1.首先我们获得数据
这里我们用到的数据是datingTestSet2.txt文件中的,这个文件我们可以从书的地方获得,或者关注我的公众号回复:,获取到数据我们将文本中的数据解析出来:
## 读取文件中的数据,将数据中的group和labels分开
def file2matrix(filename,n):
fr = open(filename)
arrayOLines = fr.readlines()
numberOFLines = len(arrayOLines)
returnMat = zeros((numberOFLines, n))
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
lineFormLine = line.split('\t')
returnMat[index,:] = lineFormLine[0:n]
classLabelVector.append(int(lineFormLine[-1]))# 一定要记得在这里进行数据转换(int)
index += 1
return returnMat, classLabelVector
假设我们这里读的是表格.xlsx文件也是一样的,都是一行一行读取数据解析数据,将数据分开并返回
def file2matrix(filename,n):
data = xlrd.open_workbook(filename)
table = data.sheets()[0]
nrows = table.nrows # 获取表的行数
returnMat = zeros((nrows, n))
classLabelVector = []
index = 0
for i in range(nrows):
line = table.row_values(i)
returnMat[index, :] = (line[0:n])
classLabelVector.append(float(line[-1]))
index += 1
return returnMat, classLabelVector
执行下面的命令我们即可获取到dataset和label
2.分析处理样本数据
因为在这个列子中:
如果按照上面数据直接计算距离的话,我们会发现每年获取的飞行常客里程数对于计算结果的影响将远远大于表2-3中其他两个特征——玩视频游戏的和每周消费冰淇淋公升数——的影响。所以我们需要 归一化数值,这里面我们用到的方法是newValue = (oldValue-min)/(max-min)
我们构建函数:
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 /= tile(ranges,(m, 1))
return normDataSet, ranges, minVals
输入下面的验证是否将数值 归一化
3.因为我们在上面已经写出了计算距离的代码,那么我们就可以开始构建我们的测试代码:
def datingClassTest():
hoRatio = 0.10 # 测试集数据比列 测试集和训练集的比列为1;9
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
norMat, ranges, minVals = autoNorm(datingDataMat)
m = norMat.shape[0]
numTestVecs = int(m*hoRatio)
errcount = 0.0
for i in range(numTestVecs):
classfierResult = classify0(norMat[i,:],norMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
print("the classifier came back with: %d, the real answer is: %d" % (classfierResult, datingLabels[i]))
if classfierResult != datingLabels[i]:
errcount += 1.0
print('the total error rate is :%f' % (errcount/float(numTestVecs)))
开始测试我们的代码我们发现错误率是2.4%,那么你可以试试此时将我们的k值改变错误率又会发生什么样的变化呢?
4.将算法构建成系统:
def classifyPerson():
resultList = ['女生不太喜欢你哎','小姑娘对你有点意思', '她现在可是深陷于你了']
ffMiles=float(input("你一年坐的飞机飞多少里:"))
iceCream=float(input("你一年吃多少冰激凌?"))
gameTime=float(input("你一年打多少小时的游戏?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
norMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, gameTime, iceCream])
classResult = classify0((inArr - minVals) / ranges, norMat, datingLabels, 3)
print("估计是%s" % (resultList[classResult - 1]))
此时我们输入kNN.classifyPerson()
回答相应的问题就可以获得你是不是这位女士的心仪约会对象了
这就是算法的基本全部过程了。
KNN算法优点
- 简单易用,相比其他算法,KNN算是比较简洁明了的算法。即使没有很高的数学基础也能搞清楚它的原理。
- 模型训练时间快,上面说到KNN算法是惰性的,这里也就不再过多讲述。 预测效果好
- 对异常值不敏感
KNN算法缺点
- 对内存要求较高,因为该算法存储了所有训练数据
- 预测阶段可能很慢
- 对不相关的功能和数据规模敏感
我这里也有一些数据针对K-近邻算法算法大家可以自行练习,有需求的小伙伴或者想要书中
dataingTestSet
可以关注公众号兰月廿七回复knn数据
即可获得
小白一枚,如果以上有错误望大佬轻喷!