一、为什么手写数字识别是入门首选
手写数字识别是分类任务的「Hello World」,它具备以下特点,非常适合作为机器学习入门的第一个实战项目:
| 优势 | 说明 |
|---|---|
| 数据集内置 | load_digits 即拿即用 |
| 数据量适中 | 1797 张,训练快 |
| 维度友好 | 64 维(=8×8),可视化好 |
| 多分类问题 | 10 类(0-9) |
| 准确率天花板高 | 简单模型就能 95%+ |
一句话总结: 手写数字是分类任务的「Hello World」,KNN 在 8×8 灰度图上可达 99% 准确率,是「局部结构敏感」任务的王者基线。
二、环境准备
在开始之前,请确保你的环境满足以下版本要求:
- Python 3.14+
- scikit-learn 1.9+
- pandas 3.0+
- numpy 2.4+
- jupyter 1.1+
- notebook 7.5+
- nbconvert 7.17+
三、数据集加载
python
from sklearn.datasets import load_digits
# return_X_y=True 直接拿到 (data, target) 元组
data, target = load_digits(return_X_y=True)
data.shape # (1797, 64): 1797 张 8×8 灰度图,已展平成 64 维
target.shape # (1797,): 标签 0-9
数据结构: 每个样本是 0~16 的灰度值(16=最黑,0=最白)。
可视化单张图:
python
import matplotlib.pyplot as plt
plt.imshow(data[100].reshape(8, 8), cmap='gray_r') # 反转灰度更易读
plt.title(f"Label: {target[100]}")
plt.show()
四、训练集 / 测试集拆分
python
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(
data, target, random_state=42 # 固定种子,实验可复现
)
# 默认 75%/25% 拆分
多分类建议: 加 stratify=target 让训练/测试集类别比例一致。
五、KNN 近邻算法
核心思想: 测试样本的类别 = 训练集中与其最相似的 K 个样本的多数投票。
python
from sklearn.neighbors import KNeighborsClassifier
from sklearn import set_config
set_config(display='text') # 关闭 diagram 渲染,避免 Windows 编码报错
knn = KNeighborsClassifier()
knn.fit(x_train, y_train)
效果:
python
knn.score(x_test, y_test) # 0.9933
knn.score(x_train, y_train) # 0.9889
核心结论: 测试集 99.33% 与训练集 98.89% 几乎一致,几乎无过拟合。
原因: KNN 是「基于记忆」的学习,模型复杂度天然低。
六、逻辑回归 Logistic Regression
核心思想: 线性多分类器,通过 softmax 归一化得到每类概率。
python
from sklearn.linear_model import LogisticRegression
# max_iter=10000: 手写数字特征多,默认 100 轮不收敛
classifier = LogisticRegression(max_iter=10000)
classifier.fit(x_train, y_train)
效果:
python
classifier.score(x_test, y_test) # 0.9733
classifier.score(x_train, y_train) # 1.0000
核心结论: 训练集 100% vs 测试集 97.33%,出现轻微过拟合。
缓解方法: 调小 C 参数(正则化强度倒数,越小正则化越强)。
七、两种模型对比
| 模型 | 测试集准确率 | 训练集准确率 | 特点 |
|---|---|---|---|
| KNN (k=5) | 0.9933 | 0.9889 | 几乎无过拟合;预测时计算量大 |
| Logistic Regression | 0.9733 | 1.0000 | 训练快、可解释;轻微过拟合 |
关键洞察: 手写数字这种「局部结构强」的任务,KNN 几乎总是王者基线。
八、过拟合信号识别
经验法则:
| train_score - test_score | 判断 |
|---|---|
| < 5% | 正常 |
| 5-10% | 轻微过拟合,可加正则化 |
| > 10% | 明显过拟合,必须处理 |
处理手段:
- 减小模型复杂度(KNN 调大 k,LR 调小 C)
- 增加数据量
- 特征降维(PCA)
- 早停(早于完全收敛)
九、准确率进一步提升
四把武器:
| 方法 | 原理 | 预期提升 |
|---|---|---|
| 数据归一化 | StandardScaler 让各特征同尺度 |
1-3% |
| PCA 降维 | 保留 95% 方差,去噪声 | 0-1% |
| 模型升级 | SVM(RBF)、随机森林、MLP | 1-3% |
| 超参搜索 | GridSearchCV 找最优 K / C |
0-2% |
实战建议组合: StandardScaler + PCA + MLP,通常 99%+ 准确率。
十、关键要点
- KNN 是手写数字王者: 局部结构敏感任务几乎都是 KNN 占优
- max_iter 必调: 高维数据 LR 默认 100 轮不够,常需 10000
- 过拟合信号: train_score 远高于 test_score(差距 > 5%)要警惕
- random_state=42: 实验可复现的必备
- SVM 通常更优: 进一步提升可上 SVM(RBF 核)或轻量 MLP
十一、常见陷阱
- ❌ max_iter 太小: 抛 ConvergenceWarning,准确率异常低
- ❌ 忘记设 random_state: 每次结果不同,无法对比
- ❌ 类别不平衡未用 stratify: 测试集可能缺某些类
- ❌ 不做归一化给 LR/KNN: 距离类模型对量纲敏感
- ❌ 直接报 99% 准确率就完事: 还要看 Precision/Recall/F1
十二、LogisticRegression 关键参数
| 参数 | 含义 | 默认值 | 调参建议 |
|---|---|---|---|
C |
正则化强度倒数 | 1.0 | 越小正则化越强,过拟合时减小 |
max_iter |
最大迭代次数 | 100 | 高维数据调大到 1000-10000 |
multi_class |
多分类策略 | auto | 二分类用 ovr,多分类用 multinomial |
solver |
优化器 | lbfgs | 小数据用 liblinear,大数据用 saga |
十三、进一步提升的完整 Pipeline
python
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.svm import SVC
pipe = Pipeline([
('scaler', StandardScaler()),
('pca', PCA(n_components=0.95)),
('clf', SVC(kernel='rbf'))
])
pipe.fit(x_train, y_train)
print(pipe.score(x_test, y_test)) # 通常 99%+
效果: 标准化 + PCA + SVM(RBF) 是手写数字的「满分组合」。