k-NearestNeighbor分类算法,顾名思义,找到K个与待测数据最近的样本数据,根据K个样本类别情况来判断待测数据的类别。为什么可以这样?相近的物体往往具有一些共性,例如,在学校里一般成绩比较好的学生都喜欢坐在一起,而有些成绩较差的往往也喜欢玩到一块去。
KNN算法有三个步骤:
1.算距离:计算待测数据到每个样本数据的距离
2.找邻居:选出K个距离最近的样本数据
3.做分类:在前k个样本中选择频率最高的类别作为预测类别
K值的选取
如下图:蓝色正方形和红色三角形分别表示不同的两类样本数据,绿色圆形表示待分类的数据。当k=2时,红色三角形数量多于蓝色正方形,待分类的数据将被归于红色三角形一类;当k=5时,蓝色正方形数量多于红色三角形,待分类的数据将被归于蓝色正方形一类。由此可以看出KNN算法中的K值的取定对分类结果有很大的影响,
距离度量的方式
X、Y分别表示样本与待测数据
x1,x2和y1,y2等是两个数据在对应相应维度上的值
欧氏距离:
曼哈顿距离:
闵可夫斯基距离:
不难看出,前面两个公式是第三个公式p分别取1,2所得到的,p去不同值会得到不同的结果,比较常用的是欧氏距离。实际上使用KNN时,往往要将数据归一化,因为当一个属性的值远远大与其他的属性时,距离的结果很有可能被这一个属性所主导,其他的属性影响会小很多,这并不是我们想看到的,这时就需要进行归一化处理。
分类标准
常用的有在前k个样本中选择频率最高的类别作为预测类别;但也会存在这样一种情况,当每个类别的样本数都一样该怎么处理,如下图:
若继续采用频率来判断就不太好了,有可能是紫色,也有可能蓝色和红色。但直观上很明显,结果应该是红色,它离红色最近,自然而然地我们想到了可以加一个和距离有关地权重1/s(s表示距离),原先直接在某个类别上加1,现在则是加上1/s。
实际上KNN也可用于解决回归问题,过程大体一样,只是第三步K个最邻近的样本处理稍稍不同。分类时是想得到数量最多的那个类别;而回归往往是求每个样本label的均值(根据每个样本数据与待测数据的距离取相应的权重也许效果会更好)来预测结果。
优点:
1.思想简单,具有很不错的效果
2.几乎没有涉及什么数学知识进行公式推导
3.可以进行多分类
缺点:
1.运算量大,效率低,每预测一个值就要求到所有样本的距离
2.没有真正的模型,所以预测速度较慢
导入模块
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
算法简单实现
class Knn:
def __init__(self,k=6,weight=False):#k默认为6,weight表示是否考虑权重
self.k = k
self.x_train = None
self.y_train = None
self.weights = weight
#KNN实际上没有真正的模型
def fit(self,x_train,y_train):
self.x_train = x_train
self.y_train =y_train
def predict(self,x_test):
if self.weights==0:
pred = [self._predict1(x) for x in x_test]
else:
pred = [self._predict2(x) for x in x_test]
return np.array(pred)
def _predict1(self,x): #predict的一个私有函数
#距离
distance = [np.sqrt(((x-i)**2).sum())/len(self.x_train) for i in self.x_train]
nearest = np.argsort(distance)#得到将距离排序后原先的索引
near = self.y_train[nearest[:self.k]]
d ={}#利用字典计算频数
for i in near:
if not i in d:
d[i] = 1
else:
d[i] +=1
x_type = max(d,key=d.get)
return x_type
def _predict2(self,x): #predict的一个私有函数
#距离
distance = [np.sqrt(((x-i)**2).sum())/len(self.x_train) for i in self.x_train]
nearest = np.argsort(distance)
near = self.y_train[nearest[:self.k]]
d = {}
for i in range(self.k):
if near[i] not in d:
d[near[i]] = 1/distance[i]
else:
d[near[i]] += 1/distance[i]
x_type = max(d,key=d.get)
return x_type
def score(self,x_test,y_test):
count = sum(y_test == self.predict(x_test))
return count/len(y_test)
均值归一化
def standardization(dataSet):
meanValues = dataSet.mean(axis=0)
stdValues = dataSet.std(axis=0)
normDataSet = (dataSet-meanValues)/stdValues
return normDataSet
利用库中的鸢尾花数据集进行模型实现
iris = datasets.load_iris()
x = iris.data
y = iris.target
x = standardization(x)
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3)
model = Knn(k=15,weight=False)
model.fit(x_train,y_train)
pred_test = model.predict(x_test)
test_score = model.score(x_test,y_test)
print('test_score: ',test_score)
test_score: 0.9777777777777777