k k k-NN ( k k k-nearest neighbor) 由 Cover 和 Hart 于 1968 年提出,属于机器学习算法中的监督学习算法,可以用来解决分类和回归问题。
k k k-NN 的工作原理
为了对
k
k
k-NN 算法有个直观的认识,我们看个例子:
有两类物体 A 和 B,它们在坐标轴中的分布如上图所示。现在来了一个未知类别的物体,由图中的正方形表示,我们该把它归为哪一类呢? k k k-NN 算法的工作原理是看离待分类物体最近的 k k k 个物体的类别,这 k k k 个物体的大多数属于那个类别,待分类物体也就属于那个类别。例如:当 k k k = 3 时,离待分类物体最近的 3 个物体中,有 1 个 A 类物体,2 个 B 类物体,所以待分类物体属于 B 类;当 k k k = 9 时,离待分类物体最近的 9 个物体中,有 5 个 A 类物体,4 个 B 类物体,所以待分类物体属于 A 类。由上面的例子可知,* k k k-NN 的工作流程分为三步:
- 计算待分类物体和其他物体之间的距离。
- 选取距离待分类物体最近的 k k k 个物体。
- 这 k k k 个物体的大多数属于那个分类,待分类物体也就属于那个分类。
距离的计算
特征空间中两个实例点之间的距离反映了两个实例点的相似程度。距离越大,相似度越小;距离越小,相似度越大。
k
k
k 近邻模型的特征空间一般是
n
n
n 维实数向量空间
R
n
R^n
Rn。使用的距离是欧式距离,但也可以是其他距离,如更一般的
L
p
L_p
Lp(
L
p
L_p
Lp distance) 距离或闵可夫斯基距离(Minkowski distance)。
设特征空间
χ
\chi
χ 是
n
n
n 维实数向量空间
R
n
R^n
Rn,
x
i
x_i
xi,
x
j
x_j
xj
∈
\in
∈
χ
\chi
χ,
x
i
x_i
xi = (
x
i
(
1
)
x_i^{(1)}
xi(1),
x
i
(
2
)
x_i^{(2)}
xi(2) ,…,
x
i
(
n
)
x_i^{(n)}
xi(n))
T
^T
T,
x
j
x_j
xj = (
x
j
(
1
)
x_j^{(1)}
xj(1),
x
j
(
2
)
x_j^{(2)}
xj(2) ,…,
x
j
(
n
)
x_j^{(n)}
xj(n))
T
^T
T,
x
i
x_i
xi,
x
j
x_j
xj 的
L
p
L_p
Lp 距离定义为
当 p = 2 p = 2 p=2时,称为欧式距离,即
k k k 值的选择
从上面的例子我们看到, k k k 值的选择会对结果产生重大的影响。同一个物体,如果 k k k 值选择的不同,结果可能完全不同。另外, k k k 值的选择也对模型的预测效果产生较大影响。如果 k k k 值选择的较小,只有较小邻域内的训练实例才会对预测结果起作用,这时整体模型变得复杂,容易发生过拟合;如果 k k k 值选择的较大,意味着距离输入实例较远的训练实例也会对预测结果起作用,这时整体模型变得简单,容易发生欠拟合。在应用中,一般采用交叉验证法来选取最优的 k k k 值。
决策规则
k k k 近邻法中往往采用多数表决的决策规则,也就是输入实例的 k k k 个近邻的多数类决定输入实例的类。
k d kd kd 树
在实现 k k k 近邻法时,为了找出距离输入实例最近的 k k k 个训练实例,最简单的方法便是线性扫描,这时要计算输入实例和每个训练实例的距离。当特征空间的维数以及训练集较大时,计算非常耗时。为了提高效率,减少不必要的计算,可以使用 k d kd kd 树来存储训练数据,并通过搜索 k d kd kd 树来减少计算量。
两个例子
鸢尾花的分类
鸢尾花的分类问题属于监督学习中比较经典的问题。鸢尾花有三个类别,分别为山鸢尾(Iris Setosa)、杂色鸢尾(Iris Versicolour)和维吉尼亚鸢尾(Iris Virginica)。每个类别的鸢尾花都有四个属性,分别是花萼长度(SepalLength)、花萼宽度(SepalWidth)、花瓣长度(PetalLength)以及花瓣宽度(PetalWidth)。鸢尾花的分类问题就是通过花萼长度、花萼宽度、花瓣长度以及花瓣宽度的值来对鸢尾花进行分类。下面我们通过 Scikit-learn 中的 k k k-NN 算法对鸢尾花进行分类。
- 导入库
import pandas as pd
from sklearn import metrics
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
- 读取数据
iris_data = pd.read_csv('Iris.csv')
- 将数据集分成训练数据集和测试数据集
y = iris_data['Species']
x = iris_data[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm']]
train_x, test_x, train_y, test_y = train_test_split(x, y, test_size=0.3, random_state=33)
- 使用训练数据对模型进行训练,并使用得到的模型对测试数据进行测试。
knn = KNeighborsClassifier()
knn.fit(train_x, train_y)
predict_y = knn.predict(test_x)
- 输出测试结果
print("KNN准确率: %.4lf" % accuracy_score(test_y, predict_y))
- output
KNN准确率: 0.9556
糖尿病的预测
糖尿病的预测是根据患者的一些信息来预测患者是否有糖尿病。下面我们通过 Scikit-learn 中的 k k k-NN 算法对患者是否患有糖尿病进行预测。
- 导入库
import pandas as pd
from sklearn import metrics
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
- 读取数据
diabetes_data = pd.read_csv('diabetes.csv')
- 对数据进行清洗,对于某列数据中的0值,使用这一列值的平均值进行填充。
diabetes_data.replace({
'Glucose': 0,
'BloodPressure': 0,
'SkinThickness': 0,
'BMI': 0,
'Insulin': 0
}, np.NaN, inplace=True)
glucose_mean = diabetes_data['Glucose'].mean()
blood_pressure_mean = diabetes_data['BloodPressure'].mean()
skin_thickness_mean = diabetes_data['SkinThickness'].mean()
bmi_mean = diabetes_data['BMI'].mean()
insulin_mean = diabetes_data['Insulin'].mean()
diabetes_data['Glucose'].replace(np.NaN, glucose_mean, inplace=True)
diabetes_data['BloodPressure'].replace(np.NaN, blood_pressure_mean, inplace=True)
diabetes_data['SkinThickness'].replace(np.NaN, skin_thickness_mean, inplace=True)
diabetes_data['BMI'].replace(np.NaN, bmi_mean, inplace=True)
diabetes_data['Insulin'].replace(np.NaN, insulin_mean, inplace=True)
- 将数据集分成训练数据集和测试数据集。
y = diabetes_data['Outcome']
x = diabetes_data[['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']]
train_x, test_x, train_y, test_y = train_test_split(x, y, test_size=0.2, random_state=0)
- 对数据进行规范化。
sc_x = StandardScaler()
train_x = sc_x.fit_transform(train_x)
test_x = sc_x.fit_transform(test_x)
- 使用训练数据对模型进行训练,并使用得到的模型对测试数据进行测试。
knn = KNeighborsClassifier(n_neighbors=11, metric='euclidean', p=2, weights='uniform', algorithm='auto', leaf_size=30)
knn.fit(train_x, train_y)
predict_y = knn.predict(test_x)
- 输出测试结果
print("KNN准确率: %.4lf" % accuracy_score(test_y, predict_y))
- output
KNN准确率: 0.8052
总结
本文介绍了 k k k -NN 的工作原理以及在实际问题中的应用。
数据下载
鸢尾花:https://www.kaggle.com/uciml/iris
糖尿病:https://www.kaggle.com/uciml/pima-indians-diabetes-database