KNN算法介绍
KNN(K-nearest neighbor)是机器学习里最简单的算法之一,原理也非常简单。可以用于分类或者回归。当我们要为某个新输入的实例分类的时候,我们可以抽取距离该样本最近的K个实例,如果这k个实例中的大多数属于某个类别,我们就把该实例分为该类别。
通俗一点,物以类聚,人以群分,某个人是什么水平的,看看他身边的人,可能会知道大概。某个物品是什么种类的,看看和它相似的,就能知道大概。
在scikit-learn中,已经有现成的KNN算法可以使用。但是为了加深理解,我们手动实现KNN算法。然后在尝试调用scikit-learn中的算法。
自己用python编写代码,实现KNN算法
假设:有10个点,位于坐标轴上,他们总共被分为2类(A类和B类),代码如下:
import numpy as np
// data是点的集合,target是其对应的分类。
data = np.array([
[1.9,2.1],
[2.9,3.1],
[4.3,3.4],
[5.5,3.5],
[4.1,2.5],
[4.9,5.9],
[6.8,5.9],
[6.5,7.0],
[6.8,8.0],
[7.1,9.0]
])
target = np.array(['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'])
我们可以在坐标轴上绘制其分布:
plt.scatter(data[:, 0], data[:, 1])
结果如下图:
十个点已经显示出来了,我们按照分类A,和分类B,将每个点显示为不同的颜色,代码如下:
plt.scatter(data[:, 0][target=='A'], data[:, 1][target=='A'], color='r')
plt.scatter(data[:, 0][target=='B'], data[:, 1][target=='B'], color='g')
结果如下图:
这个时候,十个点和对应的类别已经显示在图上了。这个时候,有一个新输入的实例
new_target = np.array([3.3, 4])
我们把该点添加到之前的图中,修改代码如下:
plt.scatter(data[:, 0][target=='A'], data[:, 1][target=='A'], color='r')
plt.scatter(data[:, 0][target=='B'], data[:, 1][target=='B'], color='g')
plt.scatter(new_target[0], new_target[1], color='b')
结果如下:
可以看到蓝色的点就是我们要预测的点。(PS:肉眼可见,该点和红色的是一类的,但是我们还是要通过代码实现它) 下面要进入正题了: 我们假设KNN中的K=3,也就是说,我们根据最近的3个点的类别,去判断蓝色点属于哪一类。
k = 3
不过首先,我们还是要精确计算蓝色点和其他所有点的距离。 点D1 和点D2的距离,我们采用欧拉距离,其定义如下(还可以有其他的距离,比如曼哈顿距离,本篇再不介绍,我们初高中学过的距离公式,就是欧拉距离):
distances = np.sum(np.square(data - new_target), axis = 1)
distances
我们已经求到了蓝色点和其他所有点的距离,结果如下:
array([ 5.57, 0.97, 1.36, 5.09, 2.89, 6.17, 15.86, 19.24, 28.25, 39.44])
knn的核心思想是根据距离最近的k个点,去判断最后的结果,所以我们给结果拍个序:
nearest = np.argsort(distances)
nearest
结果如下:
array([1, 2, 4, 3, 0, 5, 6, 7, 8, 9])
可以看到,距离最近的点,数组下标为1,距离最远的点,数组下标为9 距离最近的k=3个点,序号是 1,2,4 接着求距离最近的k个点
k = 3
nearest_k = [target[item] for item in nearest[:k]]
nearest_k
结果如下:
['A', 'A', 'A']
也就是说,1,2,4 都投票给了分类A
最后一步,新实例到底属于哪个分类,就由这k个点去投票了。
from collections import Counter
votes = Counter(nearest_k)
votes
结果如下:
Counter({'A': 3})
votes.most_common(1)[0][0] 结果如下:
'A'
至此,我们已经自己编写了KNN的代码。
封装自己编写的KNN算法
我们仿scikit-learn中的算法的一般规则: 1,init方法 返回一个模型的实例 2,fit方法 将数据集用于训练模型 3,predict方法 预测我们输入的实例 4,score方法 用于评判我们预测数据的准确度
封装后的代码如下:
import numpy as np
from math import sqrt
from collections import Counter
from metrics import accuracy_score
class KNNClassifier:
def __init__(self, k):
"""初始化kNN分类器"""
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):
"""根据训练数据集X_train和y_train训练kNN分类器"""
assert X_train.shape[0] == y_train.shape[0], \
"the size of X_train must be equal to the size of y_train"
assert self.k <= X_train.shape[0], \
"the size of X_train must be at least k."
self._X_train = X_train
self._y_train = y_train
return self
def predict(self, X_predict):
"""给定待预测数据集X_predict,返回表示X_predict的结果向量"""
assert self._X_train is not None and self._y_train is not None, \
"must fit before predict!"
assert X_predict.shape[1] == self._X_train.shape[1], \
"the feature number of X_predict must be equal to X_train"
y_predict = [self._predict(x) for x in X_predict]
return np.array(y_predict)
def _predict(self, x):
"""给定单个待预测数据x,返回x的预测结果值"""
assert x.shape[0] == self._X_train.shape[1], \
"the feature number of x must be equal to X_train"
distances = [sqrt(np.sum((x_train - x) ** 2))
for x_train in self._X_train]
nearest = np.argsort(distances)
topK_y = [self._y_train[i] for i in nearest[:self.k]]
votes = Counter(topK_y)
return votes.most_common(1)[0][0]
def score(self, X_test, y_test):
"""根据测试数据集 X_test 和 y_test 确定当前模型的准确度"""
y_predict = self.predict(X_test)
return accuracy_score(y_test, y_predict)
对比sklearn中的KNN
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
KNN_classfier = KNeighborsClassifier(n_neighbors=3)
KNN_classfier.fit(data, target)
new_target = np.array([[3.3, 4]])
KNN_classfier.predict(new_target)
结果如下:
array(['A'], dtype='<U1')
和我们自己编写的KNN算法的结果一直,预测的都是A。
为了方便我们理解,我们编写的算法,只是预测了单个实例。下面,我们采用正式的数据集来运行自己写的算法。
用sklearn的KNN预测鸢尾花
鸢尾花数据集是很好的用于KNN算法的数据集。我们从0开始。 该数据集,提供了花瓣的4个属性值,然后会告诉我们每个案例对应的分类。
为了方便查看更多细节,首先导入鸢尾花数据集:
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
KNN_classfier = KNeighborsClassifier(n_neighbors=3)
data = load_iris()
X = data.data
y = data.target
然后我们查看数据集的一些信息吧。
data.feature_names
4个特征值分别是(花萼长度、花萼宽度、花瓣长度、花瓣宽度),如下:
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
然后看看数据集大小:
data.data.shape, data.target.shape
结果如下:
((150, 4), (150,))
共150条数据,每条数据4个特征值,target是150x1的数组,代表了150条数据对应的分类结果。
我们先将数据集切分为训练数据和测试数据2个部分吧,我们将30%的数据用作测试集,70%的部分用作训练集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,random_state=666)
X_train.shape, y_train.shape, X_test.shape, y_test.shape
返回结果如下:
((105, 4), (105,), (45, 4), (45,))
结果显示,数据集已经按照我们的想法,切分为 训练集:测试机 = 105:45的2个部分了。
话不多书,开始训练吧
KNN_classfier_iris = KNeighborsClassifier(n_neighbors=3)
KNN_classfier_iris.fit(X_train, y_train)
在预测之前,我们看看它们本来的分类结果吧:
y_test
结果如下:
array([1, 2, 1, 2, 0, 1, 1, 2, 1, 1, 1, 0, 0, 0, 2, 1, 0, 2, 2, 2, 1, 0,
2, 0, 1, 1, 0, 1, 2, 2, 0, 0, 1, 2, 1, 1, 2, 2, 0, 1, 2, 2, 1, 1,
0])
接下来我们预测一下
y_predict = KNN_classfier_iris.predict(X_test)
y_predict
返回的是45个测试样本的预测结果,如下:
array([1, 2, 1, 2, 0, 1, 1, 2, 1, 1, 1, 0, 0, 0, 2, 1, 0, 2, 2, 2, 1, 0,
2, 0, 1, 1, 0, 1, 2, 2, 0, 0, 1, 2, 1, 1, 2, 2, 0, 1, 2, 2, 1, 1,
0])
惊讶的发现,预测结果竟然和我们的原本的分类结果完全一样。这么说本次预测的准确度应该是100%了? 为了验证这个猜想,我们调用方法查看一下准确度吧:
KNN_classfier_iris.score(X_test, y_test)
结果如下:
1.0
哦耶,本次预测的结果是1.0,也就是100%。
用自己的KNN,预测鸢尾花
除了导入模型这一步有一点差距,其他的步骤,和调用sklearn中的算法,几乎一样,我们快速实现吧。
# 导入的是自己实现的KNNClassfier
from KNN import KNNClassifier
KNN_classfier = KNNClassifier(k=3)
KNN_classfier.fit(X_train, y_train)
KNN_classfier.predict(X_test)
预测结果如下:
array([1, 2, 1, 2, 0, 1, 1, 2, 1, 1, 1, 0, 0, 0, 2, 1, 0, 2, 2, 2, 1, 0,
2, 0, 1, 1, 0, 1, 2, 2, 0, 0, 1, 2, 1, 1, 2, 2, 0, 1, 2, 2, 1, 1,
0])
看看准确度吧:
KNN_classfier.score(X_test, y_test)
结果如下:
1.0
我们自己实现的KNN算法,和sklearn的KNN算法,结果一致。
总结
KNN算法的思路简单,但是也有一些明显的优缺点:
第一点:k的取值很关键,如果k的取值过大,假设这里的k=10,那么就相当于用全部的点,去投票,最后的结果显而易见,就是5:5的结果。那么实际结果没有参考意义。如果k=1,那么用最近的一个点去投票,假如最近的这个点,刚好是一个被错误分类的实例,或者偏差很大的实例。那么其可靠性就不强。总而言之,k要适当大小。
第二点:计算量非常巨大,只适合小规模数据集,因为每次都要计算该点和所有点的距离,样本数量大的时候,耗时严重。