KNN(K-Nearest Neighbor)算法是机器学习算法中最基础,最简单的算法之一。它既能用于分类,也能用于回归。KNN通过测量不同特征值的距离来进行分类。
k近邻算法简单,直观:对于一个需要预测的输入向量x,我们只需要在训练数据集中寻找k个与向量x最近的向量的集合,然后把x的类别预测为这k个样本中类别数最多的那一类。
输入:训练数据集
输出:实例x所属的类y.
(1)根据给定的距离度量,在训练集T中找出与x最临近的k个点,涵盖这k个点的x的领域记作Nk(x)
(2)在Nk(x)中根据分类决策规则(如多数表决)决定x的类别y:
I为指示函数,即当yi=ci时I为1,否则I为0
k近邻法的特殊情况是k=1的情形,称为最近邻算法,对于输入的实例点(特征向量)x,最近邻法将训练数据集中与x最邻近点的类作为x的类。
k近邻法使用的模型实际上对应于对特征空间的划分。模型主要由三个基本要素----距离度量,k值的选择和分类决策规则决定。
特征空间中两个实例点的距离是两个实例点相似程度的反应的反应。使用的是欧式距离(即坐标轴中两点的距离),但也可以是其他距离,如更一般的Lp距离(Lpdistance)或Minkowski (Minkowski distance)
这里给出numpy的实现方法,便于下文代码理解
import numpy as np
# 求范数
dist1=np.lianlg.norm(x,ord=None,axis=None,keepdims=False)
①x: 表示矩阵(也可以是一维)
②ord:范数类型
矩阵的范数:
ord=1:列和的最大值
ord=2:计算A*A的转置矩阵的最大特征值的开平方 ord=∞:行和的最大值
ord=None:默认情况下,是求整体的矩阵元素平方和,再开根号。
③axis:处理类型
axis=1表示按行向量处理,求多个行向量的范数
axis=0表示按列向量处理,求多个列向量的范数
axis=None表示矩阵范数。
④keepding:是否保持矩阵的二维特性
True表示保持矩阵的二维特性,False相反
k值的选择会对k近邻法的产生重大影响.
如果选择较小的k值,就相当于用较小的领域中的训练实例进行预测,“学习”的近似误差会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用.但缺点是“学习”的估计误差会增大。预测结果会对近邻的实例点非常敏感.如果邻近点恰巧是噪声,预测就会出错.换句话说,k值的减小就意味着整体模型变得复杂,容易发生过拟合.
如果选择较大的k值,就相当于用较大领域中的训练实例进行预测,其优点是可以减小学习的估计误差,但缺点是学习的近似误差会增大,这时与输入实例较远的(不相似的)训练实例也会对预测起作用,使预测发生错误,k值的增大就意味着整体模型变得简单.
若果k=N,那么无论输入实例是什么,都将简单地预测它属于在训练实例中最多的类,这时,模型过于简单,完全忽略训练实例中的大量有用信息,是不可取的.
在应用中,k值一般取一个较小的数值,通常采用交叉验证法来选取最优的k的值.
注: 近似误差:可以理解为对现有训练集的训练误差。 估计误差:可以理解为对测试集的测试误差。 近似误差关注训练集,如果近似误差小了会出现过拟合的现象,对现有的训练集能有很好的预测,但是对未知的测试样本将会出现较大偏差的预测。模型本身不是最接近最佳模型。 估计误差关注测试集,估计误差小了说明对未知数据的预测能力好。模型本身最接近最佳模型。
k近邻法中的分类决策规则往往是多数表决,既由输入实例的k个邻近的训练实例中的多数决定输入实例的类.
看了上面描述,是不是觉得手很痒,那就来手撸代码吧!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 本例子训练数据采用sklearn提供的自带的数据集-莺尾花数据集
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
class KNN:
def __init__(self,X_train,y_train,n_neighbors=3,p=2):
"""
parameter: n_neighbors 临近点个数
parameter: p 距离度量
"""
self.n = n_neighbors
self.p = p
self.X_train = X_train
self.y_train = y_train
def predict(self,X):
# 取出n个点
knn_list=[]
for i in range(self.n):
# 求范数,ord=2相当于欧式距离
dist1=np.linalg.norm(X-self.X_train[i],ord=self.p,keepdims=False)
knn_list.append((dist1,self.y_train[i]))
# 扫描剩余数据,如果有距离小于所选k个测试数据则进行替换
for i in range(self.n,len(self.X_train)):
# 寻找k个测试数据中距离最大的数据索引
max_index=knn_list.index(max(knn_list,key=lambda x:x[0]))
dist1=np.linalg.norm(X-self.X_train[i],ord=self.p)
# 判断是否需要进行替换
if knn_list[max_index][0]>dist1:
knn_list[max_index] = (dist1,self.y_train[i])
# 统计
knn1 = [k[-1] for k in knn_list]
# 以字典的方式统计目标值出现的次数,即y_train:次数
count_pairs = Counter(knn1)
# 将字典转换为元祖,并根据值进行排序,得到的是一个列表包含n个有序元祖,然后取排序后最后一个元祖首元素
# 即出现次数最多的y_train
max_count=sorted(count_pairs.items(),key=lambda x:x[1])[-1][0]
return max_count
# 利用测试数据进行测试并统计预测正确的次数
def score(self,X_test,y_test):
# 对预测正确的数进行统计
right_count=0
for x,y in zip(X_test,y_test):
result=self.predict(x)
if result==y:
right_count+=1
return right_count/len(X_test)
if __name__=='__main__':
# 对数据进行简单的处理,主要是运用pandas一些特性,可以参考我的另一篇博文
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
data = np.array(df.iloc[:100, [0, 1, -1]])
X, y = data[:, :-1], data[:, -1]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
knn=KNN(X_train,y_train)
# 打印测试数据的正确率
print("测试数据的正确率为:"+knn.score(X_test,y_test))
test_point=[5.1,3.5]
# 画出输入实例所在的位置
plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')
plt.plot(test_point[0], test_point[1], 'bo',color='r', label='test_point')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()
我们可以用一句成语来总结k近邻算法的思想: “近朱者赤,近墨者黑”,由你的邻居来推断出你的类别。最后提一点KNN算法没有显示的学习过程,即不用训练数据.
行业应用: 客户流失预测、欺诈侦测等(更适合于稀有事件的分类问题)
如果你觉得这篇文章有收获就给我点个赞吧!
下期预告:朴素贝叶斯算法