机器学习——K-NN算法
目录
一. KNN的原理
二. K-NN算法的注意事项
1. 如何选取K值
2. K-NN算法的优点
3. K-NN算法的缺点
三. 算法的Python实现
(1)用原理实现K-NN
(2)调用sk-learn实现K-NN
一. KNN的原理
K近邻算法(KNN)是机器学习中广泛应用于分类任务的一种基本机器学习算法。我们考虑一个分类场景:假设有两个类别A与B,在此基础之上有一个新的数据点x₁需要进行归类为A还是B的选择?这个问题可以通过下图来直观展示。

用KNN的来分类的步骤一般如下:
- step1:确定邻居数目K
- step2:计算各数据点与新样本的欧氏距离
- step3:选取与目标样本最接近的前K个邻居
- step4:统计各类别中被选中的邻居数量
- step5:将该样本归入具有最多邻居数的那个类别中
根据上面的KNN的原理,我们来对上面的问题求解:

- 首先,在模型训练过程中, 我们将邻居数量K设定为其最优值.
- 接着, 我们计算出样本点与各个样本点之间的欧式距离.

A点与B点欧式距离为

采用欧氏距离对新数据点进行计算后得出的结果表明,在这些数据样本中属于同一类别的是最邻近的前5个数据样本。

根据图示信息,在对样本进行K近邻分类统计后发现:与该数据样本距离最近的五个样本中(其中K值取5),A类共有三个样本、B类有两个样本;最终将该待分类样本归入A类类别中
二. K-NN算法的注意事项
1. 如何选取K值
K被称为一个超参数 ,通常基于经验和启发由人类设定。由于难以准确确定‘K’的最佳取值,在实现过程中我们可能会尝试不同的K值以找到最优解。在工程应用中,通常采用交叉验证方法 来选择合适的K值。
(2)极低的K值(例如K=1或K=2)可能导致模型对噪声和异常值较为敏感,并最终导致过拟合现象。
(3)选择过大的K值可能导致分类结果产生较大偏差,并且在某些情况下可能会降低分类效率。此外,在某些情况下,那些与数据点距离过远的点也可能对分类产生一定影响;这通常意味着模型出现了欠拟合现象
2. K-NN算法的优点
(1)实现起来简单。
(2)对嘈杂的训练数据具有鲁棒性,对异常值不敏感。
(3)训练数据越大越有效,精度高。
3. K-NN算法的缺点
(1)需要认为确定K值,K值不同对结果的影响也不同。
(2)计算成本高,即需要计算所有训练的数据与数据点之间的欧式距离。
三. 算法的Python实现
(1)用原理实现K-NN
第一步是导入所需的模块库,并加载了Iris数据集。随后,在处理这些数据后将其划分为训练集和测试集两部分。
# 导入相关模块
import numpy as np
from collections import Counter
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.utils import shuffle
# 导入sklearn iris数据集
iris = datasets.load_iris()
# 打乱数据后的数据与标签
X, y = shuffle(iris.data, iris.target, random_state=13)
# 数据转换为float32格式
X = X.astype(np.float32)
# 训练集与测试集的简单划分,训练-测试比例为7:3
offset = int(X.shape[0] * 0.7)
X_train, y_train = X[:offset], y[:offset]
X_test, y_test = X[offset:], y[offset:]
# 将标签转换为竖向量
y_train = y_train.reshape((-1,1))
y_test = y_test.reshape((-1,1))
# 打印训练集和测试集大小
print('X_train=', X_train.shape)
print('X_test=', X_test.shape)
print('y_train=', y_train.shape)
print('y_test=', y_test.shape)

这段代码定义了一个用于计算欧氏距离的函数,并对每个测试样本与每个训练样本之间的欧氏距离进行了计算。
### 定义欧氏距离
def compute_distances(X, X_train):
'''
输入:
X:测试样本实例矩阵
X_train:训练样本实例矩阵
输出:
dists:欧式距离
'''
# 测试实例样本量
num_test = X.shape[0]
# 训练实例样本量
num_train = X_train.shape[0]
# 基于训练和测试维度的欧氏距离初始化
dists = np.zeros((num_test, num_train))
# 测试样本与训练样本的矩阵点乘
M = np.dot(X, X_train.T)
# 测试样本矩阵平方
te = np.square(X).sum(axis=1)
# 训练样本矩阵平方
tr = np.square(X_train).sum(axis=1)
# 计算欧式距离
dists = np.sqrt(-2 * M + tr + np.matrix(te).T)
return dists
dists = compute_distances(X_test, X_train)
plt.imshow(dists, interpolation='none')
plt.show();

下面的代码定义了预测函数,并对结果进行预测:
### 定义预测函数
def predict_labels(y_train, dists, k=1):
'''
输入:
y_train:训练集标签
dists:测试集与训练集之间的欧氏距离矩阵
k:k值
输出:
y_pred:测试集预测结果
'''
# 测试样本量
num_test = dists.shape[0]
# 初始化测试集预测结果
y_pred = np.zeros(num_test)
# 遍历
for i in range(num_test):
# 初始化最近邻列表
closest_y = []
# 按欧氏距离矩阵排序后取索引,并用训练集标签按排序后的索引取值
# 最后拉平列表
# 注意np.argsort函数的用法
labels = y_train[np.argsort(dists[i, :])].flatten()
# 取最近的k个值
closest_y = labels[0:k]
# 对最近的k个值进行计数统计
# 这里注意collections模块中的计数器Counter的用法
c = Counter(closest_y)
# 取计数最多的那一个类别
y_pred[i] = c.most_common(1)[0][0]
return y_pred
# 测试集预测结果
y_test_pred = predict_labels(y_train, dists, k=1)
y_test_pred = y_test_pred.reshape((-1, 1))
# 找出预测正确的实例
num_correct = np.sum(y_test_pred == y_test)
# 计算准确率
accuracy = float(num_correct) / X_test.shape[0]
print('Got %d/%d correct=>accuracy:%f'% (num_correct, X_test.shape[0], accuracy))

最终模型预测的准确率为0.977778,45个中分类正确44个。
(2)调用sk-learn实现K-NN
这些代码是基于理论基础并采用纯编写方式完成的Python实现。在工程实践中我们也通常依赖库模块来解决分类问题。
# 导入KneighborsClassifier模块
from sklearn.neighbors import KNeighborsClassifier
# 创建k近邻实例
knn_model = KNeighborsClassifier(n_neighbors=10)
# k近邻模型拟合
knn_model.fit(X_train, y_train)
# k近邻模型预测
y_pred = knn_model.predict(X_test)
# 预测结果数组重塑
y_pred = y_pred.reshape((-1, 1))
# 统计预测正确的个数
num_correct = np.sum(y_pred == y_test)
# 计算准确率
accuracy = float(num_correct) / X_test.shape[0]
print('Got %d / %d correct => accuracy: %f' % (num_correct, X_test.shape[0], accuracy))

通过调用sk-learn的K-nearest neighbors分类器进行分类器调用所得的输出结果与我们直接基于原理计算得出的计算结果完全一致。
