一、KNN算法介绍
K最近邻(K-Nearest Neighbor, KNN)算法是机器学习中最简单、最直观的分类算法之一。它既可以用于分类问题,也可以用于回归问题。KNN是一种基于实例的学习(instance-based learning)或懒惰学习(lazy learning)算法,因为它不会从训练数据中学习一个明确的模型,而是直接使用训练数据本身进行预测。
1.1 KNN算法原理
KNN算法的核心思想可以概括为:"物以类聚,人以群分"。对于一个待分类的样本,算法会在训练集中找到与之最相似的K个样本(即"最近邻"),然后根据这K个样本的类别来决定待分类样本的类别。
具体步骤如下:
-
计算待分类样本与训练集中每个样本的距离(通常使用欧氏距离)
-
选取距离最近的K个训练样本
-
统计这K个样本中每个类别出现的频率
-
将频率最高的类别作为待分类样本的预测类别
1.2 KNN算法的特点
优点:
-
简单直观,易于理解和实现
-
无需训练过程,新数据可以直接加入训练集
-
对数据分布没有假设,适用于各种形状的数据分布
缺点:
-
计算复杂度高,预测时需要计算与所有训练样本的距离
-
对高维数据效果不佳(维度灾难)
-
对不平衡数据敏感
-
需要选择合适的K值和距离度量方式
二、Scikit-learn中的KNN实现
Scikit-learn提供了KNeighborsClassifier和KNeighborsRegressor两个类分别用于KNN分类和回归。下面我们重点介绍KNeighborsClassifier。
2.1 KNeighborsClassifier API详解
class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, # K值,默认5weights='uniform', # 权重函数algorithm='auto', # 计算最近邻的算法leaf_size=30, # KD树或球树的叶子节点大小p=2, # 距离度量参数(1:曼哈顿距离,2:欧氏距离)metric='minkowski', # 距离度量类型metric_params=None, # 距离度量的额外参数n_jobs=None # 并行计算数
)
主要参数说明:
-
n_neighbors (int, default=5)
-
K值,即考虑的最近邻的数量
-
较小的K值会使模型对噪声更敏感,较大的K值会使决策边界更平滑
-
通常通过交叉验证来选择最佳K值
-
-
weights ({'uniform', 'distance'} or callable, default='uniform')
-
'uniform': 所有邻居的权重相同
-
'distance': 权重与距离成反比,距离越近的邻居影响越大
-
也可以自定义权重函数
-
-
algorithm ({'auto', 'ball_tree', 'kd_tree', 'brute'}, default='auto')
-
计算最近邻的算法:
-
'brute': 暴力搜索,计算所有样本的距离
-
'kd_tree': KD树,适用于低维数据
-
'ball_tree': 球树,适用于高维数据
-
'auto': 自动选择最合适的算法
-
-
-
leaf_size (int, default=30)
-
KD树或球树的叶子节点大小
-
影响树的构建和查询速度
-
-
p (int, default=2)
-
距离度量的参数:
-
p=1: 曼哈顿距离
-
p=2: 欧氏距离
-
-
仅当metric='minkowski'时有效
-
-
metric (str or callable, default='minkowski')
-
距离度量类型,可以是:
-
'euclidean': 欧氏距离
-
'manhattan': 曼哈顿距离
-
'chebyshev': 切比雪夫距离
-
'minkowski': 闵可夫斯基距离
-
或自定义距离函数
-
-
-
n_jobs (int, default=None)
-
并行计算数
-
-1表示使用所有处理器
-
2.2 常用方法
-
fit(X, y)
: 拟合模型,只需要存储训练数据 -
predict(X)
: 预测X的类别 -
predict_proba(X)
: 返回X属于各类别的概率 -
kneighbors([X, n_neighbors, return_distance])
: 查找点的K近邻 -
score(X, y)
: 返回给定测试数据和标签的平均准确率
三、KNN分类实战示例
3.1 基础示例:鸢尾花分类
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix# 加载数据集
iris = load_iris()
X = iris.data # 特征 (150, 4)
y = iris.target # 标签 (150,)# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 创建KNN分类器
knn = KNeighborsClassifier(n_neighbors=5, # 使用5个最近邻weights='uniform', # 均匀权重algorithm='auto', # 自动选择算法p=2 # 欧氏距离
)# 训练模型(实际上只是存储数据)
knn.fit(X_train, y_train)# 预测测试集
y_pred = knn.predict(X_test)# 评估模型
print("分类报告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))
3.2 进阶示例:手写数字识别
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import matplotlib.pyplot as plt
import numpy as np# 加载手写数字数据集
digits = load_digits()
X = digits.data # (1797, 64)
y = digits.target # (1797,)# 可视化一些样本
fig, axes = plt.subplots(2, 5, figsize=(10, 5))
for i, ax in enumerate(axes.flat):ax.imshow(X[i].reshape(8, 8), cmap='gray')ax.set_title(f"Label: {y[i]}")ax.axis('off')
plt.show()# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 创建管道:先标准化数据,再应用KNN
pipe = Pipeline([('scaler', StandardScaler()), # 标准化特征('knn', KNeighborsClassifier()) # KNN分类器
])# 设置参数网格进行网格搜索
param_grid = {'knn__n_neighbors': [3, 5, 7, 9], # 不同的K值'knn__weights': ['uniform', 'distance'], # 两种权重方式'knn__p': [1, 2] # 曼哈顿距离和欧氏距离
}# 创建网格搜索对象
grid = GridSearchCV(pipe, param_grid, cv=5, # 5折交叉验证scoring='accuracy', # 评估指标n_jobs=-1 # 使用所有CPU核心
)# 执行网格搜索
grid.fit(X_train, y_train)# 输出最佳参数和得分
print(f"最佳参数: {grid.best_params_}")
print(f"最佳交叉验证准确率: {grid.best_score_:.4f}")# 在测试集上评估最佳模型
best_model = grid.best_estimator_
test_score = best_model.score(X_test, y_test)
print(f"测试集准确率: {test_score:.4f}")# 可视化一些预测结果
sample_indices = np.random.choice(len(X_test), 10, replace=False)
sample_images = X_test[sample_indices]
sample_labels = y_test[sample_indices]
predicted_labels = best_model.predict(sample_images)plt.figure(figsize=(12, 3))
for i, (image, true_label, pred_label) in enumerate(zip(sample_images, sample_labels, predicted_labels)):plt.subplot(1, 10, i+1)plt.imshow(image.reshape(8, 8), cmap='gray')plt.title(f"True: {true_label}\nPred: {pred_label}", fontsize=8)plt.axis('off')
plt.tight_layout()
plt.show()
3.3 自定义距离度量示例
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import make_classification
import numpy as np# 自定义距离函数:余弦相似度
def cosine_distance(x, y):return 1 - np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))# 创建模拟数据
X, y = make_classification(n_samples=1000, n_features=20, n_classes=3, random_state=42
)# 使用自定义距离度量的KNN
knn_custom = KNeighborsClassifier(n_neighbors=5,metric=cosine_distance, # 使用自定义距离algorithm='brute' # 自定义距离需要暴力搜索
)# 使用标准KNN作为对比
knn_standard = KNeighborsClassifier(n_neighbors=5)# 划分训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 训练并评估两个模型
knn_custom.fit(X_train, y_train)
knn_standard.fit(X_train, y_train)print(f"自定义距离KNN准确率: {knn_custom.score(X_test, y_test):.4f}")
print(f"标准KNN准确率: {knn_standard.score(X_test, y_test):.4f}")
四、KNN回归实战示例
总结:一句话区分核心差异
- 分类:回答 “这是什么” 的问题,输出离散类别(如 “是垃圾邮件”);
- 回归:回答 “这有多少” 的问题,输出连续数值(如 “房价 300 万”)。
虽然本文主要介绍分类问题,但KNN也可以用于回归。下面是一个简单的回归示例:
from sklearn.neighbors import KNeighborsRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt# 创建回归数据集
X, y = make_regression(n_samples=200, n_features=1, noise=10, random_state=42
)# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 创建KNN回归器
knn_reg = KNeighborsRegressor(n_neighbors=5,weights='distance', # 距离加权p=1 # 曼哈顿距离
)# 训练模型
knn_reg.fit(X_train, y_train)# 预测
y_pred = knn_reg.predict(X_test)# 评估
mse = mean_squared_error(y_test, y_pred)
print(f"均方误差(MSE): {mse:.2f}")# 可视化结果
plt.scatter(X_test, y_test, color='blue', label='Actual')
plt.scatter(X_test, y_pred, color='red', label='Predicted')
plt.title('KNN Regression')
plt.xlabel('Feature')
plt.ylabel('Target')
plt.legend()
plt.show()
五、KNN调优技巧
5.1 K值选择
K值的选择对KNN性能有很大影响:
-
K值太小:模型对噪声敏感,容易过拟合
-
K值太大:模型过于简单,可能欠拟合
常用方法:
-
使用交叉验证选择最佳K值
-
经验法则:K通常取3-10之间的奇数(避免平票)
5.2 数据预处理
KNN对数据尺度敏感,通常需要:
-
标准化:将特征缩放到均值为0,方差为1
-
归一化:将特征缩放到[0,1]范围
5.3 维度灾难
高维空间中,所有点都趋向于远离彼此,导致距离度量失效。解决方法:
-
特征选择:选择最相关的特征
-
降维:使用PCA等方法降低维度
5.4 距离度量选择
不同距离度量适用于不同场景:
-
欧氏距离:适用于连续变量
-
曼哈顿距离:适用于高维数据或稀疏数据
-
余弦相似度:适用于文本数据
六、总结
KNN是一种简单但强大的机器学习算法,特别适合小规模数据集和低维问题。通过Scikit-learn的KNeighborsClassifier,我们可以方便地实现KNN算法,并通过调整各种参数来优化模型性能。
关键点回顾:
-
KNN是一种懒惰学习算法,没有显式的训练过程
-
K值和距离度量的选择对模型性能至关重要
-
数据预处理(特别是标准化)对KNN非常重要
-
高维数据中KNN可能表现不佳,需要考虑降维
希望本教程能帮助你理解和应用KNN算法。在实际应用中,记得结合交叉验证和网格搜索来找到最佳参数组合。