K最近邻(kNN,k-NearestNeighbor)分类算法,所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。用的是近朱者赤近墨者黑的思想。KNN没有显式的学习过程,也就是说没有训练阶段,数据集事先已有了分类和特征值,待收到新样本后直接进行处理。
思路是:如果一个样本在特征空间中的k个最邻近的样本中的大多数属于某一个类别,则该样本也划分为这个类别。KNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。
我们要确定绿点属于哪个颜色(红色或者蓝色),要做的就是选出距离目标点距离最近的k个点,看这k个点的大多数颜色是什么颜色。当k取3的时候,我们可以看出距离最近的三个,分别是红色、红色、蓝色,因此得到目标点为红色。
KNN执行过程
1)计算测试数据与各个训练数据之间的距离;
2)按照距离的递增关系进行排序;
3)选取距离最小的K个点;
4)确定前K个点所在类别的出现频率;
5)返回前K个点中出现频率最高的类别作为测试数据的预测分类
KNN伪代码
Input: 训练数据data、待预测样本x、k
Ouput:待预测样本所属类别y
# 1. 计算待预测样本和所有训练数据的距离/相似度
D = []
idx = 0
for train_x in data.x:
# a. 使用欧式距离公式计算两个样本之间的距离
distince = calc_distince(train_x, x)
# b. 距离保存
D.append((distince, idx))
idx += 1
# 2. 从所有训练数据中,获取距离最近/相似度最大的K个邻近样本
sorted(D) # 排序,默认为升序
N = D[:k] # 获取这个距离最近的K个
# 3. 将这K个邻近样本中出现数目最多的类别作为当前待预测样本的预测值。
# a. 统计各个类别出现的次数
label_2_count = {}
for distince,idx in N:
label = data.y[idx]
label_2_count[label] = label_2_count.get(label,0) + 1
# b. 从统计结果中获取出现次数最多的那个类别
max_count = 0
y = None
for label, count in label_2_count.items():
if count > max_count:
y = label
count = max_count
return y
如上代码的缺陷:消耗空间大,需要保存所有的距离吗,是不是可以直接保存前k小的距离就可以,在保存距离的时候用插入法,从小到大排序,如果有比最后一个数小的距离,则插入到列表,删除最后最后一个数据。
KNN缺陷
- 计算开销大,分类效率低; -->解决方案:KDTree
- K值难以确认,不同近邻样本数,对分类准确程度影响较大;(K过大->欠拟合, K过小->过拟合) --> 一般情况20~50之间居多,具体的会通过GridSearchCV来选择
- 样本向量以及距离的度量公式之间的选择
KDTree构建过程
1.采用m个样本中的n个属性进行划分
2.分别计算n个属性特征的方差,进行从大到小的排序
3.选取方差最大的属性作为划分,选取样本的中位数点nkv,以此样本点为根节点,将该属性特征小于Nvk的样本的放在左子树上,大于的放在右子树上。
4.根据方差第二大的属性进行左,右子树的划分,同上述过程。
5.循环迭代,数据集划分完毕
a、KDTree是如何解决KNN的缺陷的
KDTree解决了KNN中在大数据量的情况下,求解K个近邻样本计算量过大的问题。在构建KDTree的时候,选择的是方差最大的特征属性的特征属性中值作为数据分割点,将数据分割为左右两个数据子集,然后再左右两个数据子集中继续按照这种方式进行划分;这样构建出来的KDTree在查询数据的时候可以保障尽可能少的搜索分支(因为方差大表示数据比较离散)
b、除了KDTree之外,还有没有其他方式来解决该问题?
BallTree
c、KDTree除了可以用于KNN解决KNN的问题外,KDTree还可以应用到哪些场景?
KDTree的主要功能实际上是加速数据的检索。
eg:
人脸识别(人脸打卡、人脸支付....)
聊天机器人(检索式)
python实现KNN
from collections import Counter
import numpy as np
class KnnScratch(object):
def fit(self, x_train, y_train):
self.x_train = x_train
self.y_train = y_train
def predict_once(self, x_test, k): #预测单个样本
lst_distance = []
lst_predict = []
for i in xrange(len(self.x_train)):
# euclidean distance
distance = np.linalg.norm(x_test - self.x_train[i, :])
lst_distance = sorted(lst_distance)
for i in xrange(k):
idx = lst_distance[i][1]
lst_predict.append(self.y_train[idx])
return Counter(lst_predict).most_common(1)[0][0]
def predict(self, x_test, k): # 批量预测
lst_predict = []
for i in xrange(len(x_test)):
lst_predict.append(self.predict_once(x_test[i, :], k))
return lst_predict
if __name__ == '__main__':
x_train = np.array([[1, 1, 1], [2, 2, 2], [10, 10, 10], [13, 13, 13]])
y_train = ['aa', 'aa', 'bb', 'bb']
x_test = np.array([[3, 2, 4], [9, 13, 11]])
knn = KnnScratch()
knn.fit(x_train, y_train)
print knn.predict_once(x_test[0], 2)
# aa
print knn.predict(x_test, 2)
# ['aa', 'bb']
sklearn实现KNN
#-*- coding: UTF-8 -*-
from sklearn import neighbors
from sklearn import datasets
from sklearn.model_selection import train_test_split
# 加载数据
iris = datasets.load_iris()
# 数据划分
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.5, random_state=2)
# 模型加载
knn = neighbors.KNeighborsClassifier()
knn.fit(X_train, y_train)
# 模型评估
print("训练集准确率:",knn.score(X_train,y_train))
print("测试集准确率:",knn.score(X_test,y_test))
# 预测
predicted = knn.predict(X_test)
print(predicted)