Python机器学习小项目实战:KNN算法预测乳腺癌
1. 引言
在之前的多篇文章中系统介绍了机器学习相关知识包括线性回归逻辑回归以及决策树等多种核心算法。在线性回归方面擅长预测连续型数值变量在逻辑回归方面则主要用于解决两类分类问题同时决策树模型能够有效建模更为复杂的非线性关系模式。然而在实际应用场景中我们经常会面临一些特定挑战这些问题使得上述传统算法难以直接应对
在图像识别领域内,我们面临着需要识别人体及其细节的问题。 传统线性模型由于无法有效建模像素间的复杂关联关系,已逐渐显露出其局限性。 此时,该算法被引入并展现出显著优势。 该算法是一种简洁而强大的分类与回归技术,其核心理念在于'物以类聚、趋若鹜',即某一样本的类别归属往往与其最相似的K个邻居之间存在密切关联
设想你正在对一种未知的新植物进行识别。观察发现其形态特征与两类标准样本——A 样本及 B 样本高度吻合。假设有五个与该未知样本最接近的标准样本中包含四个属于 A 类型,则就有理由认为该未知样本很有可能属于 A 类型
在这篇博客中, 我们将基于真实案例深入理解KNN算法的基本原理及其具体应用场景, 同时也会详细讲解其代码实现过程。 让我们一起来深入探索KNN算法的核心机制吧!
2. 理论基础
KNN 算法的原理
K近邻算法的核心概念是通过评估与所有训练样本的距离来进行分类或预测数值目标值的方法。该方法的具体操作步骤包括:首先计算与所有训练样本的距离;其次确定需要参考的邻居数量;最后根据邻居数据进行多数投票以决定结果类型或预测值数值
- 计算距离:计算待预测样本与所有训练样本之间的 .
- 选择邻居:选择与待预测样本最近的 .
- 分类/回归:
- 分类:将待预测样本归类至 中出现频率最高的类别.
- 回归:将待预测样本的估计值设定为 的均值或加权均值.
KNN 方法是一种基于懒惰学习 (Lazy Learning) 的分类技术。它不会直接学习模型参数,在执行预测任务时才完成计算过程。这种设计使得 KNN 方法高度灵活,并且能够有效地应对各种类型的数据集和问题。
距离度量
距离度量用于衡量样本之间的相似程度。 常用的距离度量方法包括:
欧氏距离 (Euclidean Distance): 适用于连续型特征。 公式如下:

曼哈顿距离 (Manhattan Distance): 适用于连续型特征。 公式如下:

闵可夫斯基距离 (Minkowski Distance): 被称为欧氏距离和曼哈顿距离的扩展。 其计算公式如下:

若闵可夫斯基距离参数取值为2,则该距离即为欧氏距离;若参数取值为1,则即为曼哈顿距离。
汉明距离 (Hamming Distance): 适用于离散型特征。 汉明距离是指两个等长字符串之间,在对应位置上不同字符的个数。
余弦相似度 (Cosine Similarity): 适用于文本数据。 公式如下:

如何选择合适的距离度量方法?
- 当所有特征均为连续型且具有相近的比例时,则通常采用欧氏距离或曼哈顿距离进行计算。
- 当各特征量纲之间存在明显差异时,则建议先对数据进行标准化或归一化处理后再采用欧氏距离或曼哈顿距离进行计算。
- 若所考察的对象是离散属性的话,则可选用汉明距离作为衡量指标。
- 在面对文本数据时,则常用余弦相似度这一指标来进行分析评估。
K 值的选择
K 值是指要选择的邻居数量。 K 值的选择对 KNN 算法的性能有重要影响。
- K 值取值偏小:该算法可能会对输入数据中的噪声较为敏感,并导致模型容易发生过拟合现象。
- K 值取值偏大:该算法可能会难以实现良好的拟合效果,并导致模型难以准确反映数据中的微小变化。
常用的 K 值选择方法包括:
- 交叉验证 (Cross-Validation): 将训练集分割为 K 个子集,并依次采用其中一个子集作为验证集、其余子集作为训练集参与模型评估与优化过程。通过交叉验证方法可挑选出在验证集中表现出最优性能的 K 值参数设置。
- 经验法则 (Rule of Thumb): 常见的经验法则是选取 K 值等于训练样本数量开平方后的整数值。
KNN 算法的分类和回归 *
KNN 算法的分类: 将被预测样本归类为 K 个邻集中出现频率最高的类别。例如,在这5位邻居中存在3位A类样本和2位B类样本,则将被预测样本归类为A类。
**KNN 算法回归:**在确定待预测样本的目标时,“采用其邻近点数据”。例如,在某情况下有5个最近邻点的数据分别为...则可以取这些数据的均值(如X),或者依据各点距离进行加权计算。
3. 项目准备
数据集介绍
该研究采用了 Scikit-learn 提供的 breast_cancer 数据集。
全部包含了共计569个样本,在每个样本中拥有30个特征用来描述乳腺肿瘤的各种形态特性和表面纹理特征。
其目标变量即用于分类标记值为良性(0)或恶性(1)。
加载数据集
1. from sklearn.datasets import load_breast_cancer
2.
3. # 加载数据集
4. cancer = load_breast_cancer()
5.
6. # 查看数据集的描述
7. print(cancer.DESCR)
运行结果:

环境配置
确保你的 Python 环境中已经安装了必要的库:NumPy, Pandas, Scikit-learn。
如果还没有安装,可以使用以下命令安装:
pip install numpy pandas scikit-learn
4. 数据探索与预处理
数据探索
1. import pandas as pd
2.
3. # 将数据集转换为 DataFrame
4. df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
5. df['target'] = cancer.target
6.
7. # 查看数据集的前几行
8. print(df.head())
9.
10. # 查看数据集的统计信息
11. print(df.describe())
12.
13. # 查看类别分布
14. print(df['target'].value_counts())
运行结果(部分):

数据可视化
1. import matplotlib.pyplot as plt
2. import seaborn as sns
3.
4. # 绘制散点图,观察特征与目标变量之间的关系
5. sns.scatterplot(x='mean radius', y='mean texture', hue='target', data=df)
6. plt.show()
7.
8. # 绘制箱线图,观察特征与目标变量之间的关系
9. sns.boxplot(x='target', y='mean radius', data=df)
10. plt.show()
运行结果:


数据预处理
1. from sklearn.preprocessing import StandardScaler
2.
3. # 特征缩放
4. scaler = StandardScaler()
5. scaled_features = scaler.fit_transform(df[cancer.feature_names])
6.
7. # 创建包含缩放后特征的新 DataFrame
8. df_scaled = pd.DataFrame(scaled_features, columns=cancer.feature_names)
9. df_scaled['target'] = df['target'] # 加上目标变量
数据分割
1. from sklearn.model_selection import train_test_split
2.
3. # 分割数据集
4. X = df_scaled[cancer.feature_names]
5. y = df_scaled['target']
6. X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
5. 模型训练与评估
模型选择与训练:
1. from sklearn.neighbors import KNeighborsClassifier
2.
3. # 创建 KNN 模型
4. knn = KNeighborsClassifier(n_neighbors=5) # 设置 K 值为 5
5.
6. # 训练模型
7. knn.fit(X_train, y_train)
模型预测:
1. # 预测
2. y_pred = knn.predict(X_test)
模型评估:
1. from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score, roc_curve, auc
2.
3. # 混淆矩阵
4. cm = confusion_matrix(y_test, y_pred)
5. print('Confusion Matrix:\n', cm)
6.
7. # 评估指标
8. accuracy = accuracy_score(y_test, y_pred)
9. precision = precision_score(y_test, y_pred)
10. recall = recall_score(y_test, y_pred)
11. f1 = f1_score(y_test, y_pred)
12.
13. print(f'Accuracy: {accuracy}')
14. print(f'Precision: {precision}')
15. print(f'Recall: {recall}')
16. print(f'F1-score: {f1}')
17.
18. # ROC 曲线和 AUC 值
19. y_prob = knn.predict_proba(X_test)[:, 1]
20. fpr, tpr, thresholds = roc_curve(y_test, y_prob)
21. roc_auc = auc(fpr, tpr)
22.
23. # 绘制 ROC 曲线
24. plt.figure(figsize=(8, 6))
25. plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (area = {roc_auc:.2f})')
26. plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
27. plt.xlim([0.0, 1.0])
28. plt.ylim([0.0, 1.05])
29. plt.xlabel('False Positive Rate')
30. plt.ylabel('True Positive Rate')
31. plt.title('Receiver Operating Characteristic')
32. plt.legend(loc="lower right")
33. plt.show()
运行结果:


6. K 值的选择与调优
使用交叉验证选择最佳 K 值:
1. from sklearn.model_selection import GridSearchCV
2.
3. # 定义参数网格
4. param_grid = {'n_neighbors': range(1, 21)} # K 值的范围
5.
6. # 创建 GridSearchCV 对象
7. grid_search = GridSearchCV(KNeighborsClassifier(),
8. param_grid,
9. cv=5, # 5 折交叉验证
10. scoring='accuracy')
11.
12. # 运行网格搜索
13. grid_search.fit(X_train, y_train)
14.
15. # 打印最佳参数
16. print('Best Parameters:', grid_search.best_params_)
17.
18. # 使用最佳参数训练模型
19. best_knn = grid_search.best_estimator_
20.
21. # 评估最佳模型
22. accuracy = best_knn.score(X_test, y_test)
23. print(f'Accuracy (Best KNN): {accuracy}')
24.
25. # 绘制 K 值与模型性能之间的关系图
26. results = pd.DataFrame(grid_search.cv_results_)
27. plt.plot(results['param_n_neighbors'], results['mean_test_score'])
28. plt.xlabel('K Value')
29. plt.ylabel('Mean Test Score')
30. plt.title('K Value vs. Performance')
31. plt.show()
运行结果:

讨论 K 值过大和过小可能导致的问题:
从交叉验证的结果来看,在不同设置下,K参数对模型性能的影响各有差异. 当K值较小时,可能使模型过于敏感于噪声数据,导致容易发生过拟合现象;当K值较大时,可能导致模型出现欠拟合问题,难以捕捉到数据中的细微变化. 因此应选取适当的K值,以平衡好模型的复杂度与泛化能力之间的关系.
7. KNN 算法的分类和回归
使用 KNN 算法进行分类:
我们已经在上面的例子中使用了 KNeighborsClassifier 类进行分类。
使用 KNN 算法进行回归:
1. from sklearn.neighbors import KNeighborsRegressor
2. import numpy as np
3.
4. # 生成一些示例数据
5. X = np.array([[1], [2], [3], [4], [5]])
6. y = np.array([2, 4, 6, 8, 10])
7.
8. # 创建 KNN 回归模型
9. knn_regressor = KNeighborsRegressor(n_neighbors=2)
10.
11. # 训练模型
12. knn_regressor.fit(X, y)
13.
14. # 预测
15. X_new = np.array([[2.5]])
16. y_pred = knn_regressor.predict(X_new)
17.
18. print(f'Predicted Value: {y_pred[0]}')
运行结果:

8. KNN 算法的优化
讨论 KNN 算法的效率问题:
尽管KNN算法易于理解,在处理大数据集时效率不高。这是因为,在预测过程中,KNN算法需要计算待测样本与所有训练样本之间的距离,并找出最接近的K个邻居。这一计算步骤的时间复杂度为O(N),其中N表示训练数据集中的样本数量。当N值极大时,该算法的速度会显著下降。
介绍 KNN 算法的优化方法:
为了解决 KNN 算法的效率问题,可以使用以下优化方法:
- KD 树 (KD-Tree):该技术提供了一种高效的高效率空间索引方法来解决最近邻查询问题。其核心思想是通过特定的空间划分策略对数据进行分类处理,并在此基础上实现快速定位目标对象的技术体系框架。
- 球树 (Ball-Tree):该技术采用了一种专门设计的数据组织方法来解决多维空间中的近邻搜索问题。其基本思路是通过构造多个层次性的球体来分类样本,并在此基础上实现快速定位目标对象的技术体系框架。
使用 Scikit-learn 的NearestNeighbors 类构建 KD 树和球树:
Scikit-learn 包含 NearestNeighbors 类别,并被用来构建 KD 树和球形树以加速 KNN 搜索过程。
1. 使用 KD 树
1. from sklearn.neighbors import NearestNeighbors
2. from sklearn.metrics import accuracy_score
3.
4. # 构建 KD 树
5. knn = NearestNeighbors(n_neighbors=5, algorithm='kd_tree') # 或者 algorithm='auto' 让 sklearn 自动选择
6. knn.fit(X_train)
7.
8. # 搜索最近邻
9. distances, indices = knn.kneighbors(X_test)
10.
11. # 使用最近邻进行分类 (这里使用最简单的投票方式)
12. y_pred = []
13. for neighbors_indices in indices:
14. # 获取邻居的类别
15. neighbor_labels = y_train.iloc[neighbors_indices]
16. # 投票,选择出现次数最多的类别
17. prediction = neighbor_labels.value_counts().idxmax()
18. y_pred.append(prediction)
19.
20. # 评估模型
21. accuracy = accuracy_score(y_test, y_pred)
22. print(f'Accuracy (KD-Tree): {accuracy}')
运行结果:

2. 使用球树
1. from sklearn.neighbors import NearestNeighbors
2. from sklearn.metrics import accuracy_score
3.
4. # 构建球树
5. knn = NearestNeighbors(n_neighbors=5, algorithm='ball_tree')
6. knn.fit(X_train)
7.
8. # 搜索最近邻
9. distances, indices = knn.kneighbors(X_test)
10.
11. # 使用最近邻进行分类 (这里使用最简单的投票方式,与 KD-Tree 相同)
12. y_pred = []
13. for neighbors_indices in indices:
14. # 获取邻居的类别
15. neighbor_labels = y_train.iloc[neighbors_indices]
16. # 投票,选择出现次数最多的类别
17. prediction = neighbor_labels.value_counts().idxmax()
18. y_pred.append(prediction)
19.
20. # 评估模型
21. accuracy = accuracy_score(y_test, y_pred)
22. print(f'Accuracy (Ball-Tree): {accuracy}')
运行结果:

代码解释: * NearestNeighbors(n_neighbors=5, algorithm='kd_tree'): 生成一个 NearestNeighbors 实例对象,并为其实现指定以下参数设置;
-
邻居数量 (
n_neighbors=5) 表示每个样本点周围要查找的最近邻居数量; -
搜索算法 (
algorithm='kd_tree') 指定用于进行最近邻搜索所使用的数据结构或方法; -
可选的其他值包括
'ball_tree'(球树)、'brute'(暴力搜索)以及'auto'(默认情况下 Scikit-learn 自动优化选择最适合该数据集的搜索算法)。 -
knn.fit(X_train): 通过训练数据构建空间分割结构。
-
knn.kneighbors(X_test): 调用kneighbors方法搜索测试集中的每个样本的最近邻关系。该方法返回两个结果数组:一个是各邻居的距离信息(以distances表示),另一个是对应邻居在训练集中的索引位置(以indices表示)。
y_train.iloc[indices[:, 0]]: 使用索引从训练集的标签中获取最近邻的类别。
比较性能:
为了比较不同搜索算法的性能,可以使用 timeit 模块来测量搜索时间。
1. import timeit
2.
3. # 测量 KD 树的搜索时间
4. kd_tree_time = timeit.timeit(lambda: NearestNeighbors(n_neighbors=5, algorithm='kd_tree').fit(X_train).kneighbors(X_test), number=10)
5. print(f'KD-Tree Search Time: {kd_tree_time}')
6.
7. # 测量球树的搜索时间
8. ball_tree_time = timeit.timeit(lambda: NearestNeighbors(n_neighbors=5, algorithm='ball_tree').fit(X_train).kneighbors(X_test), number=10)
9. print(f'Ball-Tree Search Time: {ball_tree_time}')
10.
11. # 测量暴力搜索的时间
12. brute_force_time = timeit.timeit(lambda: NearestNeighbors(n_neighbors=5, algorithm='brute').fit(X_train).kneighbors(X_test), number=10)
13. print(f'Brute-Force Search Time: {brute_force_time}')
运行结果:

根据您的数据集大小和特征维度,KD 树和球树可能会比暴力搜索更快。
在实际应用场景中
9. 总结
在本篇博客中,我们深入研究了KNN算法的基本概念、实际应用及其代码实现。基于breast_cancer数据集构建了一个乳腺癌预测模型,并深入研究了选择合适K值的方法以及优化KNN算法的策略。
KNN 算法是一种高效且易于理解的机器学习算法,在多个领域中被广泛应用。深入理解 KNN 算法后,则能够有效应对多种实际问题。
这篇博客可能会有一些启发。 机器学习的征程充满了挑战与机遇,请大家携手并进,在这条道路上持续前行。
