KNN 概述
k-近邻(kNN, k-NearestNeighbor)算法是一种基本分类与回归方法,我们这里只讨论分类问题中的 k-近邻算法。
k 近邻算法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。 k值的选择、距离度量以及分类决策规则是k近邻算法的三个基本要素。
KNN 场景
电影可以按照题材分类,那么如何区分 动作片
和 爱情片
呢?
- 动作片:打斗次数更多
- 爱情片:亲吻次数更多
基于电影中的亲吻、打斗出现的次数,使用 k-近邻算法构造程序,就可以自动划分电影的题材类型。
现在根据上面我们得到的样本集中所有电影与未知电影的距离,按照距离递增排序,可以找到 k 个距离最近的电影。
假定 k=3,则三个最靠近的电影依次是, He's Not Really into Dudes 、 Beautiful Woman 和 California Man。
knn 算法按照距离最近的三部电影的类型,决定未知电影的类型,而这三部电影全是爱情片,因此我们判定未知电影是爱情片。
KNN 原理
KNN 工作原理
- 假设有一个带有标签的样本数据集(训练样本集),其中包含每条数据与所属分类的对应关系。
- 输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。
- 计算新数据与样本数据集中每条数据的距离。
- 对求得的所有距离进行排序(从小到大,越小表示越相似)。
- 取前 k (k 一般小于等于 20 )个样本数据对应的分类标签。
- 求 k 个数据中出现次数最多的分类标签作为新数据的分类。
KNN 通俗理解
给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的 k 个实例,这 k 个实例的多数属于某个类,就把该输入实例分为这个类。
KNN 开发流程
收集数据:任何方法
准备数据:距离计算所需要的数值,最好是结构化的数据格式
分析数据:任何方法
训练算法:此步骤不适用于 k-近邻算法
测试算法:计算错误率
使用算法:输入样本数据和结构化的输出结果,然后运行 k-近邻算法判断输入数据分类属于哪个分类,最后对计算出的分类执行后续处理
KNN 算法特点
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型
将文本记录转换为 NumPy 的解析程序
from numpy import *
def file2matrix(filename):
"""
Desc:
导入训练数据
parameters:
filename: 数据文件路径
return:
数据矩阵 returnMat 和对应的类别 classLabelVector
"""
fr = open("datingTestSet2.txt")
arraylines = fr.readlines()
numberofLines = len(arraylines)
returnMat =zeros((numberofLines, 3))
index = 0
label = []
for line in arraylines:
print(line)
# line=line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3]
label.append(int(listFromLine[-1].strip('\n')))
index = index + 1
return returnMat,array([label])
if __name__ == '__main__':
datingDataMat,datingLabels=file2matrix("datingTestSet2.txt")
自己封装的KNNimport numpy as np from math import sqrt from collections import Counter from metrics import accuracy_score ######测试Counter 的用法 y=[0,0,0,1,1,1,3,3,3] print(np.array(y).shape[0])==print(len(y)) print(Counter(y)) #Counter({0: 3, 1: 3, 3: 3}) print(Counter(y).most_common(2)) #[(0, 3), (1, 3)] print(np.argsort([2,5,7,3,1])) #[4 0 3 1 2] class KNNClassifier: def __init__(self, k): assert k >= 1, "k must be valid" self.k = k self._X_train = None self._Y_train = None def fit(self, X_train, Y_train): assert X_train.shape[0] == Y_train.shape[0], \ "The size of X_train must be equals to the size of Y-Train" assert self.k <= X_train.shape[0], \ "the feature number of x must be equal to X_train" self._X_train = X_train self._Y_train = Y_train return self def predict(self, x_predict): return np.array([self._predict(x) for x in x_predict]) def _predict(self, x): distances = [sqrt(np.sum((x_train - x) ** 2)) for x_train in self._X_train] nearest = np.argsort(distances) votes = [i for i in self._Y_train[nearest[:self.k]]] return Counter(votes).most_common(1)[0][0] def score(self,X_test,y_test): y_predict=self.predict(X_test) return accuracy_score(y_test,y_predict) def __repr__(self): return "knn(k=%d)" % self.k
在求距离时也可用向量法
def classify(inx,dataSet,labels,k = 3): m = dataSet.shape[0] diffMat = tile(inx,(m,1)) - dataSet squareMat = diffMat ** 2 squareDistance = squareMat.sum(axis = 1) distances = squareDistance ** 0.5 nearest = distances.argsort() votes = [i for i in labels[nearest[:k]]] return Counter(votes).most_common(1)[0][0]
# 2. 选择距离最小的k个点 | |
classCount = {} | |
for i in range(k): | |
# 找到该样本的类型 | |
voteIlabel = labels[sortedDistIndicies[i]] | |
# 在字典中将该类型加一 | |
# 字典的get方法 | |
# 如:list.get(k,d) 其中 get相当于一条if...else...语句,参数k在字典中,字典将返回list[k];如果参数k不在字典中则返回参数d,如果K在字典中则返回k对应的value值 | |
# l = {5:2,3:4} | |
# print l.get(3,0)返回的值是4; | |
# Print l.get(1,0)返回值是0; | |
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 | |
# 3. 排序并返回出现最多的那个类型 | |
# 字典的 items() 方法,以列表返回可遍历的(键,值)元组数组。 | |
# 例如:dict = {'Name': 'Zara', 'Age': 7} print "Value : %s" % dict.items() Value : [('Age', 7), ('Name', 'Zara')] | |
# sorted 中的第2个参数 key=operator.itemgetter(1) 这个参数的意思是先比较第几个元素 | |
# 例如:a=[('b',2),('a',1),('c',0)] b=sorted(a,key=operator.itemgetter(1)) >>>b=[('c',0),('a',1),('b',2)] 可以看到排序是按照后边的0,1,2进行排序的,而不是a,b,c | |
# b=sorted(a,key=operator.itemgetter(0)) >>>b=[('a',1),('b',2),('c',0)] 这次比较的是前边的a,b,c而不是0,1,2 | |
# b=sorted(a,key=opertator.itemgetter(1,0)) >>>b=[('c',0),('a',1),('b',2)] 这个是先比较第2个元素,然后对第一个元素进行排序,形成多级排序。 | |
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) | |
return sortedClassCount[0][0] | |
# ------------------------------------------------------------------------------------------------------------------------------------------ | |
# 实现 classify0() 方法的第二种方式 | |
# """ | |
# 1. 计算距离 | |
# 欧氏距离: 点到点之间的距离 | |
# 第一行: 同一个点 到 dataSet的第一个点的距离。 | |
# 第二行: 同一个点 到 dataSet的第二个点的距离。 | |
# ... | |
# 第N行: 同一个点 到 dataSet的第N个点的距离。 | |
# [[1,2,3],[1,2,3]]-[[1,2,3],[1,2,0]] | |
# (A1-A2)^2+(B1-B2)^2+(c1-c2)^2 | |
# inx - dataset 使用了numpy broadcasting,见 https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html | |
# np.sum() 函数的使用见 https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.sum.html | |
# """ | |
# dist = np.sum((inx - dataset)**2, axis=1)**0.5 | |
# """ | |
# 2. k个最近的标签 | |
# 对距离排序使用numpy中的argsort函数, 见 https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.sort.html#numpy.sort | |
# 函数返回的是索引,因此取前k个索引使用[0 : k] | |
# 将这k个标签存在列表k_labels中 | |
# """ | |
# k_labels = [labels[index] for index in dist.argsort()[0 : k]] | |
# """ | |
# 3. 出现次数最多的标签即为最终类别 | |
# 使用collections.Counter可以统计各个标签的出现次数,most_common返回出现次数最多的标签tuple,例如[('lable1', 2)],因此[0][0]可以取出标签值 | |
# """ | |
# label = Counter(k_labels).most_common(1)[0][0] | |
# return label | |