K近邻:KNN-(k-nearest neighbor classification)
是有监督学习、属于判别模型 、支持多分类以及回归、非线性、有预测函数、无优化目标、无优化求解算法。(算法地图)
对应每个训练数据xi有对应的标签yi--监督学习;
优缺点:
优点:精度高、对异常值不敏感、无数据输入假定。
缺点:计算复杂度高、空间复杂度高。
适用数据范围:数值型和标称型。
简单描述预测过程:
假定其中的K值已经选定好,也即是模型已经确定
对于给定的训练数据点和输入数据点,确定输入点的k个最近邻训练点,利用k个训练点的类的多数预测输入点的类
1.算法的抽象解释如下:
以上描述的三个要素:
距离度量、K值选择、分类决策规则
1、距离度量
距离度量方式有:欧氏距离、曼哈顿距离等
2、K值选择
K值的选择一般采取交叉验证方法,也即是需要调参的地方【4】
k值过小(近似误差小,估计误差大)容易发生过拟合,k值增大(近似误差大,估计误差小)则整体模型变简单
3、分类决策规则
多数表决
2.明确的概念:
最近邻 、 K -最近邻 、 近似误差和估计误差 、交叉验证
(最近邻)相当于k=1,在所有的训练数据集中找出距离目标B最近距离的一个值A,并将它B归类为这个最近距离的值A的类别。
(K-近邻)是找到距离目标B最近的某一个范围内,例如k的范围内;判断k范围内的类别数,选择个数最多的某一个类别A作为目标B的类。
近似误差:理解为对现有训练集的训练误差; 估计误差:理解为对测试集的测试误差
交叉验证:简单交叉验证、k折交叉验证、留一交叉验证
3.算法实现:
1)创建简单的数据集:
import numpy as np
from collections import Counter
from draw import draw
class KNN:
def __init__(self,X_train,y_train,k=3):
# 所需参数初始化
self.k=k #所取k值
self.X_train=X_train
self.y_train=y_train
def predict(self,X_new):
# 计算欧氏距离
dist_list=[(np.linalg.norm(X_new-self.X_train[i],ord=2),self.y_train[i])
for i in range(self.X_train.shape[0])]
#[(d0,-1),(d1,1)...]
# 对所有距离进行排序
dist_list.sort(key=lambda x: x[0])#以第0个元素升序排序
# 取前k个最小距离对应的类别(也就是y值)
y_list=[dist_list[i][-1] for i in range(self.k)]
# [-1,1,1,-1...]
# 对上述k个点的分类进行统计
y_count=Counter(y_list).most_common()#提取每个元素出现的次数
# [(-1, 3), (1, 2)]
return y_count[0][0]#返回最大的次数
def main():
# 训练数据
X_train=np.array([[5,4],
[9,6],
[4,7],
[2,3],
[8,1],
[7,2]])
y_train=np.array([1,1,1,-1,-1,-1])
# 测试数据
X_new = np.array([[5, 3]]) #目标值
# 绘图
draw(X_train, y_train, X_new)
# 不同的k(取奇数)对分类结果的影响
for k in range(1,6,2):#步长为2 也即是:1,3,5
#构建KNN实例
clf=KNN(X_train,y_train,k=k)
#对测试数据进行分类预测
y_predict=clf.predict(X_new)
print("k={},被分类为:{}".format(k,y_predict))
if __name__=="__main__":
main()
SKlearn实现:
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
def main():
# 训练数据
X_train=np.array([[5,4],
[9,6],
[4,7],
[2,3],
[8,1],
[7,2]])
y_train=np.array([1,1,1,-1,-1,-1])
# 待预测数据
X_new = np.array([[5, 3]])
# 不同k值对结果的影响
for k in range(1,6,2):
# 构建实例
clf = KNeighborsClassifier(n_neighbors=k,n_jobs=-1)
# 选择合适算法
clf.fit(X_train, y_train)
# print(clf.kneighbors(X_new))
# 预测
y_predict=clf.predict(X_new)
#print(clf.predict_proba(X_new))
print("预测正确率:{:.0%}".format(clf.score([[5,3]],[[1]])))
print("k={},被分类为:{}".format(k,y_predict))
if __name__=="__main__":
main()
2).使用 k近邻算法改进网站的配对效果-代码理解
见github:https://github.com/codehgq/ML-note/blob/master/Date_KNN.ipynb
分为以下步骤:
1)文件到矩阵转换(准备数据)
#导入矩阵运算模块
import numpy as np
import operator
#数据集文件datingTestSet2.txt保存在当前目录下,也即是把txt拷贝到本地的用户文件夹下(我的) 输入:文件 输出:数据集的特征矩阵returnMat和标签向量classLabelVector
def file2matrix(filename):
love_dictionary = {'largeDoses':3, 'smallDoses':2, 'didntLike':1} # 三个类别
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') # 按'\t'对字符串进行分割,得到列表
returnMat[index, :] = listFromLine[0:3] # listFromLine的0,1,2元素是特征,赋值给returnMat的当前行
if(listFromLine[-1].isdigit()): # 如果listFromLine最后一个元素是数字
classLabelVector.append(int(listFromLine[-1])) # append 的作用是在列表的末尾添加元素,直接赋值给classLabelVector
else: # 如果listFromLine最后一个元素不是数字,而是字符串
classLabelVector.append(love_dictionary.get(listFromLine[-1])) # 根据字典love_dictionary转化为数字
index += 1
return returnMat, classLabelVector # 返回的类别标签classLabelVector是1,2,3
2)数据归一化
#输入:数据矩阵 输出:归一化的数据矩阵normDataSet 范围 ranges 有3列 最小值minVals 3列
def autoNorm(dataSet):
minVals = dataSet.min(0)#从列中选择最小值
maxVals = dataSet.max(0)#从列中选择最大值
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))#shape返回矩阵中维度 此句相当于创建一个同等规模的0矩阵
m = dataSet.shape[0]#取矩阵的行数
normDataSet = dataSet - np.tile(minVals, (m, 1))#函数title用于扩充minVals成m行1列
normDataSet = normDataSet/np.tile(ranges, (m, 1)) # normDataSet值被限定在[0,1]之间
return normDataSet, ranges, minVals
3)对数据分析
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import operator
import logging
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
fig=plt.figure()
ax=fig.add_subplot(131)#1*3的第一个
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])#第二列和第三列数据
#plt.show()
ax=fig.add_subplot(132)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],10.0*np.array(datingLabels),10.0*np.array(datingLabels))
ax=fig.add_subplot(133)
#datingLabels的值为1、2、3 每个数据点的大小不一样
ax.scatter(datingDataMat[:,0],datingDataMat[:,1],10.0*np.array(datingLabels),10.0*np.array(datingLabels))
fig=plt.figure()
ax=fig.add_subplot(111)
#datingLabels=array(datingLabels)
idx_1=np.where(np.array(datingLabels)==1)
p1=ax.scatter(datingDataMat[idx_1,0],datingDataMat[idx_1,1],marker = '*',color ='xkcd:purple',label='1',s=20)
idx_2=np.where(np.array(datingLabels)==2)
p2=ax.scatter(datingDataMat[idx_2,0],datingDataMat[idx_2,1],marker = 'o',color ='xkcd:teal',label='2',s=10)
idx_3=np.where(np.array(datingLabels)==3)
p3=ax.scatter(datingDataMat[idx_3,0],datingDataMat[idx_3,1],marker = '+',color ='y',label='3',s=30)
plt.legend(loc = 'upper right')
plt.show()#最后显示即可
#scatter函数参考 https://www.cnblogs.com/shanlizi/p/6850318.html
#matplot 官方教程 https://matplotlib.org/tutorials/colors/colors.html#sphx-glr-tutorials-colors-colors-py
#最后一张图参考 http://blog.sina.com.cn/s/blog_8d249b140102wf3m.html 并修改其中的错误
4)KNN分类器
#输入:测试集inX,训练集dataSet,训练样本标签lebels,取的最近邻个数k 输出:返回K近邻中所属类别最多的一类 参考:https://www.cnblogs.com/vrfighters/articles/4715527.html 写的不错
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]#得到行数
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet#title 相当于将inX扩充为dataSeetSize行目的是为了和后面的训练集求距离
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)#求和axis=1表示对所在行的全部列求和
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort()#从小到大排序并找到索引值可参考:https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html和https://www.cnblogs.com/yyxf1413/p/6253995.html
classCount = {}#创建字典,用于存储各标签出现的次数
for i in range(k):#从上述的k个排序好的点,统计类别次数
voteIlabel = labels[sortedDistIndicies[i]]#解释 距离最小的数据样本的标签参考
#:https://blog.csdn.net/zengxyuyu/article/details/54382182
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1#获取键值voteIlabel对应的值(次数),若不存在则为0
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#将字典分解为元组列表,并按照第二个元素(次数)排序 true 为降序排列 默认升序
#参考:http://www.runoob.com/python/python-func-sorted.html
return sortedClassCount[0][0]#得到k近邻中所属类别最多的类
5)测试算法
输入:样本,输出:分类结果和实际类别 ,错误率,错误个数
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)使用算法(构建完整可用系统)
#依据用户输入3个参数,输出:判断结果类别 详细注释;参考:https://blog.csdn.net/quincuntial/article/details/50471423
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']#定义分类结果类别
#读取输入的数据 percentTats ffMiles iceCream
percentTats = float(input(\
"percentage of time spent playing video games?"))
ffMiles = float(input("frequent flier miles earned per year?"))
iceCream = float(input("liters of ice cream consumed per year?"))
#从文件datingTestSet2.txt中读取已有数据
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("You will probably like this person: %s" % resultList[classifierResult - 1])
3)手写识别系统 -代码的理解
见github:https://github.com/codehgq/ML-note/tree/master/KNN
欢迎
1)加载数据 2)图像转换为向量 3)分类
#图像转化为向量
def img2vector(filename):
returnVect = np.zeros((1, 1024)) # 存储图片像素的向量维度是1x1024
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVect[0, 32*i+j] = int(lineStr[j]) # 图片尺寸是32x32,将其依次放入向量returnVect中
return returnVect
4、如何对训练数据进行快速K近邻搜索
分为两过程:构造KD树(二叉树)和搜索KD树
KD树表示为对K维空间的划分,其每个结点对应于k维空间划分中的一个超矩形区域
优势:省去大部分数据点搜索,减少搜索计算量
其中的KD树适用于训练实例数远大于空间维数的K近邻搜索
参考资料
【1】机器学习之深入理解K-means、与KNN算法区别及其代码实现https://blog.csdn.net/sinat_35512245/article/details/55051306
【2】https://blog.csdn.net/gzj_1101/article/details/77774524
【3】近似误差和估计误差 https://blog.csdn.net/qq_35664774/article/details/79448076
【4】 一文搞懂k近邻(k-NN)算法
【5】最近邻法和k-近邻法 KD树https://blog.csdn.net/u012422446/article/details/56486342
【6】李航《统计学习方法》k近邻法
【7】KNN算法理解 https://blog.csdn.net/jmydream/article/details/8644004
【8】https://blog.csdn.net/qq_35082030/article/details/60965320 有问题
【9】kmeans和knn的区别 机器学习之深入理解K-means、与KNN算法区别及其代码实现
https://blog.csdn.net/sinat_35512245/article/details/55051306
【10】一文搞懂k近邻(k-NN)算法二 https://mp.weixin.qq.com/s/qfHBn7YydSOOnM43Be8aTg
【11】 机器学习实战