Scikit-learn KNeighborsClassifier 实战:Iris 数据集 5 折交叉验证调参指南
在机器学习领域,k近邻(k-Nearest Neighbors, kNN)算法以其直观性和有效性著称。本文将带您深入实战,使用Scikit-learn库对经典Iris数据集进行kNN模型调优,重点演示如何通过网格搜索和交叉验证找到最佳超参数组合。
1. 环境准备与数据加载
首先确保已安装必要的Python库。建议使用conda或pip安装scikit-learn、numpy、matplotlib和seaborn。以下是初始化代码:
import numpy as np import matplotlib.pyplot as plt from sklearn import datasets from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import GridSearchCV from sklearn.preprocessing import StandardScaler import seaborn as sns # 加载Iris数据集 iris = datasets.load_iris() X = iris.data y = iris.target feature_names = iris.feature_names target_names = iris.target_namesIris数据集包含150个样本,每个样本有4个特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度),分为3类鸢尾花。我们先进行简单的数据探索:
print(f"特征矩阵形状: {X.shape}") print(f"类别分布: {np.bincount(y)}") plt.figure(figsize=(10,6)) sns.pairplot(data=pd.DataFrame(X, columns=feature_names), diag_kind='kde', plot_kws={'alpha':0.6}) plt.suptitle('Iris数据集特征分布', y=1.02) plt.show()2. 数据预处理与标准化
kNN算法对特征尺度敏感,因此标准化是必要步骤。我们使用StandardScaler将特征缩放到均值为0、方差为1的标准正态分布:
scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 标准化前后对比 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5)) sns.boxplot(data=X, ax=ax1).set_title('原始数据') sns.boxplot(data=X_scaled, ax=ax2).set_title('标准化后数据') plt.tight_layout()标准化后的箱线图显示各特征已处于相同量纲,这将确保距离计算的公平性。
3. 超参数网格搜索设计
kNN有三个关键超参数需要优化:
- n_neighbors (k值):决定参与投票的邻居数量
- weights:距离权重策略(uniform或distance)
- p:距离度量参数(1为曼哈顿距离,2为欧氏距离)
我们使用GridSearchCV进行5折交叉验证搜索:
param_grid = { 'n_neighbors': range(1, 21), 'weights': ['uniform', 'distance'], 'p': [1, 2] } knn = KNeighborsClassifier() grid_search = GridSearchCV(knn, param_grid, cv=5, scoring='accuracy', n_jobs=-1) grid_search.fit(X_scaled, y)4. 结果分析与可视化
获取最佳参数组合和交叉验证结果:
print(f"最佳参数: {grid_search.best_params_}") print(f"最佳准确率: {grid_search.best_score_:.3f}") # 提取所有参数组合结果 cv_results = pd.DataFrame(grid_search.cv_results_) top_results = cv_results.sort_values('rank_test_score').head(10)为直观展示参数影响,我们绘制热力图:
# 筛选p=2的结果 p2_results = cv_results[cv_results['param_p']==2] heatmap_data = p2_results.pivot_table(index='param_n_neighbors', columns='param_weights', values='mean_test_score') plt.figure(figsize=(10,6)) sns.heatmap(heatmap_data, annot=True, fmt=".3f", cmap="YlGnBu") plt.title('不同k值和权重策略下的准确率热力图(p=2)') plt.xlabel('权重策略') plt.ylabel('k值') plt.show()典型的热力图会显示:
- 较小k值(1-3)时模型容易过拟合
- 中等k值(5-10)通常表现最佳
- distance权重策略在多数情况下优于uniform
5. 最佳模型验证与决策边界
使用最佳参数重新训练模型,并可视化决策边界:
best_knn = grid_search.best_estimator_ # 选择两个主要特征进行可视化 X_2d = X_scaled[:, :2] # 仅使用前两个特征 best_knn_2d = KNeighborsClassifier(**grid_search.best_params_) best_knn_2d.fit(X_2d, y) # 生成网格点 x_min, x_max = X_2d[:, 0].min() - 1, X_2d[:, 0].max() + 1 y_min, y_max = X_2d[:, 1].min() - 1, X_2d[:, 1].max() + 1 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) # 预测每个网格点 Z = best_knn_2d.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) # 绘制决策边界 plt.figure(figsize=(10,6)) plt.contourf(xx, yy, Z, alpha=0.4, cmap='viridis') sns.scatterplot(x=X_2d[:,0], y=X_2d[:,1], hue=y, palette='viridis', edgecolor='k') plt.xlabel(feature_names[0]) plt.ylabel(feature_names[1]) plt.title('kNN决策边界可视化(基于前两个特征)') plt.legend(target_names) plt.show()决策边界图清晰展示了kNN的分类逻辑——基于局部邻域进行决策。边界形状会随k值变化:
- 小k值:边界复杂,可能过拟合
- 大k值:边界平滑,可能欠拟合
6. 实际应用建议与注意事项
基于本次实验,我们总结以下工程实践要点:
k值选择黄金法则:
- 对于小型数据集(n<100),k通常取3-5
- 中型数据集(100<n<1000),k取5-10
- 大型数据集(n>1000),k可取√n
距离加权策略对比:
策略 优点 缺点 uniform 计算简单 忽略距离信息 distance 更精确 对噪声敏感 常见陷阱与解决方案:
- 维度灾难:当特征数过多时,考虑特征选择或降维
- 计算效率:使用KD树或Ball Tree加速近邻搜索
- 类别不平衡:采用加权投票或调整类别权重
# 高效近邻搜索示例 from sklearn.neighbors import KDTree kdtree = KDTree(X_scaled) distances, indices = kdtree.query(X_scaled[:1], k=5) print(f"最近5个邻居的索引: {indices}")- 扩展技巧:
- 尝试不同的距离度量(余弦相似度、马氏距离等)
- 结合特征重要性进行加权距离计算
- 对超参数搜索空间进行贝叶斯优化
# 特征加权距离计算示例 feature_weights = np.array([0.2, 0.1, 0.5, 0.2]) # 假设花瓣长度最重要 X_weighted = X_scaled * feature_weights通过本指南,您已掌握使用Scikit-learn实现kNN分类的完整流程。实际项目中,建议将调参过程封装为可复用的Pipeline,并结合业务需求持续优化模型性能。