简介
K-Nearest Neighbors (KNN) 算法是所有监督学习算法中最简单、直观的之一。其基本思想是通过计算新数据点到所有训练数据点的距离,找到距离最近的 K 个数据点(即 K 个邻居),然后根据这 K 个邻居的多数类别来决定新数据点的类别。
例如,给定一个红色的交叉点 X,我们只需要获取其周围最近的邻居,并根据这些邻居的多数类别来为 X 分类。
算法实现步骤
-
准备数据
- 获取训练集和测试集,将数据整理成适当的格式。
- 数据标准化,以加速算法收敛。
- 分割训练集和测试集。
-
计算点之间的距离
编写函数来计算测试数据与所有训练数据之间的成对距离。 -
找到最近的邻居
对距离进行排序,选取前 K 个最近的邻居。 -
根据多数类别进行分类
统计最近 K 个邻居的类别,并选取出现频率最高的类别作为预测结果。
实践
1. 准备数据
我们将从一个二维数据集开始,其中包含 4 个不同的类别。首先,我们将数据集分割成训练集和测试集,并对数据进行标准化处理。
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 生成数据
X, y = make_blobs(n_samples=300, centers=4, random_state=0, cluster_std=1.0)
# 分割训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# 标准化数据
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
2. 计算点之间的距离
接下来,我们将编写一个函数来计算测试集和训练集之间的成对距离。
def find_distance(X_train, X_test):
dist = X_test[:, np.newaxis, :] - X_train[np.newaxis, :, :]
sq_dist = dist ** 2
summed_dist = sq_dist.sum(axis=2)
return np.sqrt(summed_dist)
3. 找到最近的邻居
我们将利用上一步计算的距离矩阵,找到每个测试数据点的 K 个最近邻居。
def find_neighbors(X_train, X_test, k=3):
dist = find_distance(X_train, X_test)
neighbors_ix = np.argsort(dist)[:, 0:k]
return neighbors_ix
4. 根据多数类别进行分类
最后,我们统计最近 K 个邻居的类别,并选取出现频率最高的类别作为预测结果。
def get_most_common(y):
return np.bincount(y).argmax()
def predict(X_train, X_test, y_train, k=3):
neighbors_ix = find_neighbors(X_train, X_test, k)
pred = np.zeros(X_test.shape[0])
for ix, y in enumerate(y_train[neighbors_ix]):
pred[ix] = get_most_common(y)
return pred
yhat = predict(X_train, X_test, y_train, k=3)
5. 验证模型性能
我们将使用准确率、平均精度得分和分类报告来评估 KNN 模型的性能。
from sklearn.metrics import average_precision_score, classification_report
from sklearn.preprocessing import label_binarize
n_classes = len(np.unique(y_test))
print("Accuracy: ", np.sum(yhat == y_test)/len(y_test))
y_test_binarized = label_binarize(y_test, classes=[0, 1, 2, 3])
yhat_binarized = label_binarize(yhat, classes=[0, 1, 2, 3])
for i in range(n_classes):
class_score = average_precision_score(y_test_binarized[:, i], yhat_binarized[:, i])
print(f"Class {i} score: ", class_score)
print("Classification report: ")
print(classification_report(y_test, yhat))
使用Scikit-Learn实现KNN
Scikit-Learn 提供了更为简便的实现方式,通过KNeighborsClassifier
可以轻松地创建和调参 KNN 模型。
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import StratifiedShuffleSplit, GridSearchCV
model = KNeighborsClassifier()
param_grid = {"n_neighbors": np.arange(2, 10)}
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=42)
grid = GridSearchCV(model, param_grid=param_grid, cv=cv, refit=True)
grid.fit(X_train, y_train)
print(f"The best parameters are {grid.best_params_} with a score of {grid.best_score_:.2f}")
yhat = grid.predict(X_test)
print("Classification report: ")
print(classification_report(y_test, yhat))
何时使用KNN
KNN 算法的实现相对简单,在一些简单的分类问题上表现良好。然而,它的劣势也十分明显:
- 随着特征数量的增加,计算开销急剧上升。KNN 需要为每个输入点计算到所有其他点的距离,并排序,这在特征数多时代价极高。
- 无法处理类别型特征,因为难以为类别型特征制定合适的距离公式。
- 调整邻居数目(K)的过程耗时较长。
加深理解
-
比较 Naive Bayes、Logistic Regression 和 K-Nearest Neighbors 三种算法
- Naive Bayes:假设特征之间相互独立,适用于高维数据和文本分类任务,计算速度快,但假设过于简单。
- Logistic Regression:适用于线性可分数据,有明确的概率输出,适合特征数量适中的数据集。
- K-Nearest Neighbors:直观简单,适合少量特征和数据分布规则的情况,但计算成本高,特征多时效果差。
-
欧几里得距离失效的情况
当特征具有不同的量纲或尺度时,欧几里得距离可能会失效,因为它会受到大尺度特征的主导。此时需要进行特征标准化或使用其他距离度量。 -
分类平局时的处理方法
若出现平局情况,可以选择:- 随机选择一个类别
- 使用权重距离,即离得越近的点权重越大
- 减少 K 的值
-
K-Nearest Neighbors 分类计算
给定测试数据,使用 K=3 计算其属于类 0 或类 1 的可能性,并给出预测类别。
结语
K-Nearest Neighbors 是一种简单但功能强大的分类算法,特别适合初学者学习机器学习的基本概念。然而,它的计算复杂度较高,随着数据量和特征维度的增加,可能并不是最优选择。因此,在实际应用中,需要根据数据集的具体情况选择合适的算法。
如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!
欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。
谢谢大家的支持!