KNN 分类算法详解(Python 实现)
KNN(K 近邻)算法是机器学习中最简单的监督学习分类算法之一。本文将为初学者详细解释 KNN 的原理和实现过程,并通过实际代码示例帮助理解。
什么是 KNN 算法?
KNN 的核心思想非常简单:物以类聚,人以群分。当我们要对一个新样本进行分类时,只需看看它周围最近的 K 个样本属于什么类别,然后让新样本 "跟随多数派"。
想象你在一个派对上,想知道自己应该坐在哪个区域。你看看离你最近的 3 个人(K=3)坐在哪里,如果 2 个在 A 区,1 个在 B 区,你很可能也会选择 A 区。这就是 KNN 的基本思想。
实现步骤
让我们一步步实现 KNN 分类算法,从数据准备到最终评估。
1. 导入需要的库
首先,我们需要导入一些 Python 库来帮助我们处理数据和实现算法:
python
import numpy as np # 用于数值计算
import pandas as pd # 用于数据处理
from sklearn.model_selection import train_test_split # 用于划分训练集和测试集
from sklearn.preprocessing import StandardScaler # 用于数据标准化
from sklearn.neighbors import KNeighborsClassifier # KNN分类器
from sklearn.metrics import accuracy_score, classification_report # 评估指标
import matplotlib.pyplot as plt # 用于可视化
from mpl_toolkits.mplot3d import Axes3D # 用于3D可视化
2. 加载并查看数据
我们使用一个包含收入、年龄、学历和对应类别的数据集:
python
# 加载数据
df = pd.read_excel('https://labfile.oss.aliyuncs.com/courses/40611/K%E8%BF%91%E9%82%BB%28KNN%29%E5%88%86%E7%B1%BB.xlsx')
# 查看前5行数据
df.head()
数据看起来是这样的:
收入 | 年龄 | 学历 | 类别 |
---|---|---|---|
4956 | 22 | 专科 | 普通人 |
10397 | 21 | 本科 | 精英 |
5158 | 30 | 专科 | 普通人 |
4368 | 22 | 专科 | 普通人 |
12680 | 35 | 专科 | 精英 |
3. 数据预处理
计算机不理解文字,所以我们需要将中文类别转换为数字:
python
# 将学历文本转换为数字
df['学历'] = df['学历'].map({
"专科": 1,
"本科": 2,
"硕士": 3,
"博士": 4
})
# 将类别文本转换为数字
df['类别'] = df['类别'].map({
"普通人": 0,
"精英": 1,
"高质量": 2
})
# 查看转换后的数据
df.head()
为了符合国际惯例,我们将中文列名改为英文:
python
column_mapping = {
'收入': 'Income',
'年龄': 'Age',
'学历': 'Education_Level',
'类别': 'Category'
}
df.rename(columns=column_mapping, inplace=True)
4. 准备特征和标签
在机器学习中,我们通常将数据分为 "特征"(输入)和 "标签"(输出):
# 特征:收入、年龄、学历(用于预测的属性)
X = df[['Income', 'Age', 'Education_Level']]
# 标签:类别(我们要预测的结果)
y = df['Category']
5. 数据标准化
KNN 算法基于距离计算,不同特征的单位可能不同(如收入是几千,年龄是几十),这会影响距离计算。因此我们需要标准化数据:
python
# 创建标准化器
scaler = StandardScaler()
# 对特征进行标准化处理
X_scaled = scaler.fit_transform(X)
标准化后的数据均值为 0,标准差为 1,确保每个特征对距离计算的影响是均衡的。
6. 划分训练集和测试集
我们需要一部分数据来训练模型,另一部分来测试模型的效果:
# 划分数据集:80%用于训练,20%用于测试
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42
)
X_train
,y_train
:训练数据和对应的标签X_test
,y_test
:测试数据和对应的标签test_size=0.2
:测试集占 20%random_state=42
:固定随机种子,确保结果可重现
7. 实现 KNN 分类
现在我们可以创建并训练 KNN 模型了:
python
# 创建KNN分类器,K=3(选择最近的3个邻居)
knn = KNeighborsClassifier(n_neighbors=3)
# 用训练数据训练模型
knn.fit(X_train, y_train)
# 用训练好的模型预测测试集
y_pred = knn.predict(X_test)
8. 评估模型效果
我们需要看看模型预测得准不准:
python
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
# 生成详细的分类报告
report = classification_report(y_test, y_pred)
print(f'准确率: {accuracy}')
print('分类报告:\n', report)
输出结果:
python
准确率: 0.85
分类报告:
precision recall f1-score support
0 1.00 1.00 1.00 24
1 0.93 0.62 0.74 21
2 0.64 0.93 0.76 15
accuracy 0.85 60
macro avg 0.85 0.85 0.83 60
weighted avg 0.88 0.85 0.85 60
- 准确率 (accuracy):85%,表示总体预测正确的比例
- 精确率 (precision):预测为某类且实际为此类的比例
- 召回率 (recall):实际为某类且被正确预测的比例
- F1 分数:精确率和召回率的调和平均
9. 可视化分类结果
我们可以用 3D 图直观地展示分类结果:
python
# 创建3D图形
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 绘制测试数据点,用颜色表示预测的类别
scatter = ax.scatter(
X_test[:, 0], # 收入
X_test[:, 1], # 年龄
X_test[:, 2], # 学历
c=y_pred, # 用预测类别着色
cmap='viridis' # 颜色映射
)
# 设置坐标轴标签
ax.set_xlabel('Income (标准化)')
ax.set_ylabel('Age (标准化)')
ax.set_zlabel('Education Level (标准化)')
# 调整视角
ax.view_init(elev=30, azim=70)
# 添加图例
legend1 = ax.legend(*scatter.legend_elements(), title="Category")
ax.add_artist(legend1)
plt.show()
K 值的选择
K 值是 KNN 算法中最重要的参数:
- K 值太小:模型容易过拟合,对噪声敏感
- K 值太大:模型过于简单,可能忽略数据的真实模式
通常可以通过尝试不同的 K 值(如 1, 3, 5, 7...),选择性能最好的那个。
python
# 尝试不同的K值
for k in [1, 3, 5, 7, 9, 11]:
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f'K={k} 时的准确率: {accuracy:.2f}')
距离计算
KNN 中常用的距离计算方法:
- 欧氏距离:最常用,计算两点之间的直线距离
- 曼哈顿距离:计算两点在坐标轴上的绝对距离之和
- 余弦相似度:衡量两个向量方向的相似性
在 scikit-learn 中,KNeighborsClassifier
的metric
参数可以指定距离度量方法,默认为欧氏距离。
KNN 的优缺点
优点
- 简单易懂,容易实现
- 不需要训练过程,是一种 "惰性学习" 算法
- 可以处理多分类问题
- 对异常值不敏感(当 K 较大时)
缺点
- 计算量大,预测速度慢(需要计算与所有样本的距离)
- 对高维数据效果不好("维度灾难")
- 对不平衡数据敏感
- 需要存储所有训练样本,内存占用大