K近邻简介
K近邻算法是一种基本分类和回归方法。K近邻算法,即给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例,这K个实例的多数属于某个类,就把该输入实例分类到这个类中。
如上图所示,有两类不同的样本数据,分别用蓝色的小正方形和红色的小三角形表示,而图正中间的那个绿色的圆所标示的数据则是待分类的数据。这也就是我们的目的,来了一个新的数据点,我要得到它的类别是什么?根据k近邻的思想来给绿色圆点进行分类:
- 如果K=3,绿色圆点的最邻近的3个点是2个红色小三角形和1个蓝色小正方形,少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于红色的三角形一类。
- 如果K=5,绿色圆点的最邻近的5个邻居是2个红色三角形和3个蓝色的正方形,还是少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于蓝色的正方形一类。
K近邻模型对应于基于训练数据集对特征空间的一个划分。K近邻法中,当训练集、距离度量、K值及分类决策规则确定后,其结果唯一确定。
K近邻三要素
距离度量
设特征空间 x x x是 n n n维实数向量空间 , x i , x j ∈ X x_{i}, x_{j} \in \mathcal{X} xi,xj∈X, x i = ( x i ( 1 ) , x i ( 2 ) , ⋯ , x i ( n ) ) T x_{i}=\left(x_{i}^{(1)}, x_{i}^{(2)}, \cdots, x_{i}^{(n)}\right)^{\mathrm{T}} xi=(xi(1),xi(2),⋯,xi(n))T, x j = ( x j ( 1 ) , x j ( 2 ) , ⋯ , x j ( n ) ) T x_{j}=\left(x_{j}^{(1)}, x_{j}^{(2)}, \cdots, x_{j}^{(n)}\right)^{\mathrm{T}} xj=(xj(1),xj(2),⋯,xj(n))T ,则: x i x_i xi, x j x_j xj的 L p L_p Lp距离定义为:
L p ( x i , x j ) = ( ∑ i = 1 n ∣ x i ( i ) − x j ( l ) ∣ p ) 1 p L_{p}\left(x_{i}, x_{j}\right)=\left(\sum_{i=1}^{n}\left|x_{i}^{(i)}-x_{j}^{(l)}\right|^{p}\right)^{\frac{1}{p}} Lp(xi,xj)=(∑i=1n∣∣∣xi(i)−xj(l)∣∣∣p)p1
- p = 1 p= 1 p=1 曼哈顿距离
- p = 2 p= 2 p=2 欧氏距离
- p = ∞ p= \infty p=∞ 切比雪夫距离
K值的选择
K值选择较小时,整体模型会变得复杂,容易发生过拟合。
K值太小(极端情况K=1),模型很容易受到噪声干扰,从而让模型变得过于复杂,即过拟合。
K值选择较大时,整体模型会变得简单,使预测发生错误。
K值太大(极端情况K=N,N为训练样本数),此时无论输入实例是什么,都将简单地预测它属于在训练实例中最多的类。相当于直接拿训练数据统计了一下各个数据的类别,找最大的而已,压根没有训练模型。此时,模型过于简单粗暴,完全忽略训练数据实例中的大量有用信息。
结论:K值既不能过大,也不能过小。K值的选择反映了对近似误差与估计误差之间的权衡,通常由交叉验证选择最优的K。
分类决策规则
常用的分类决策规则是多数表决,对应于经验风险最小化。
分类算法流程
对未知类别的数据集中的每个实例依次执行以下操作:
- 计算已知类别的训练数据集众多点与当前点之间的距离
- 按照距离递增次序排序
- 选取与当前点距离最小的k个点
- 群定前k个点所在类别的出现频率
- 返回前k个点出现频率最高的类别作为当前点的预测分类
特征归一化
特征归一化的必要性:由于不同维度特征取值的量纲不同,如果不进行归一化会导致量纲大的特征会在计算中占优,从而使得模型权重学习受到干扰。而正确的做法是我们应该让每个参与训练的特征在一开始是同等重要的(公平性),这样模型训练时权重的调整才不会受到特征量纲的干扰。
K近邻代码实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
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']
df
sepal length | sepal width | petal length | petal width | label | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | 0 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | 0 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | 0 |
3 | 4.6 | 3.1 | 1.5 | 0.2 | 0 |
4 | 5.0 | 3.6 | 1.4 | 0.2 | 0 |
... | ... | ... | ... | ... | ... |
145 | 6.7 | 3.0 | 5.2 | 2.3 | 2 |
146 | 6.3 | 2.5 | 5.0 | 1.9 | 2 |
147 | 6.5 | 3.0 | 5.2 | 2.0 | 2 |
148 | 6.2 | 3.4 | 5.4 | 2.3 | 2 |
149 | 5.9 | 3.0 | 5.1 | 1.8 | 2 |
150 rows × 5 columns
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.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
<matplotlib.legend.Legend at 0x7f244cf876a0>
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)
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):
dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
knn_list.append((dist, self.y_train[i]))
for i in range(self.n, len(self.X_train)):
max_index = knn_list.index(max(knn_list, key=lambda x: x[0]))
dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
if knn_list[max_index][0] > dist:
knn_list[max_index] = (dist, self.y_train[i])
# 统计
knn = [k[-1] for k in knn_list]
count_pairs = Counter(knn)
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
n = 10
for X, y in zip(X_test, y_test):
label = self.predict(X)
if label == y:
right_count += 1
return right_count / len(X_test)
clf = KNN(X_train,y_train)
clf.score(X_test,y_test)
1.0
test_point = [6.0, 3.0]
print('Test Point: {}'.format(clf.predict(test_point)))
Test Point: 1.0
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', label='test_point')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
<matplotlib.legend.Legend at 0x7f244d11cd30>
# sklearn实例
from sklearn.neighbors import KNeighborsClassifier
clf_sk = KNeighborsClassifier()
clf_sk.fit(X_train,y_train)
KNeighborsClassifier()
clf_sk.score(X_test, y_test)
1.0