共计 2963 个字符,预计需要花费 8 分钟才能阅读完成。
背景介绍
手写数字识别是计算机视觉和机器学习中的经典问题,广泛应用于邮政编码识别、银行支票处理、表单数据录入等场景。MNIST 数据集作为该领域的基准数据集,包含 60,000 个训练样本和 10,000 个测试样本,每个样本是 28×28 像素的灰度图像,对应 0 - 9 的手写数字。

尽管深度学习在图像识别领域取得了显著进展,但传统机器学习算法如 KNN 和逻辑回归因其简单高效、可解释性强等优势,在某些场景下仍具实用价值。本文将系统对比这两种算法在实际任务中的表现。
数据准备
首先加载并预处理 MNIST 数据集。sklearn 提供了简化版的 MNIST 数据集(原始数据的子集),适合快速实验:
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
import numpy as np
# 加载数据
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
X, y = mnist.data, mnist.target.astype(np.uint8)
# 划分训练测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 特征缩放(重要!)X_train = X_train / 255.0
X_test = X_test / 255.0
关键预处理步骤:
– 像素值归一化到 [0,1] 区间(原始像素值为 0 -255)
– 标签转换为整数类型
– 保留 20% 数据作为测试集
模型实现
KNN 分类器
K 近邻算法通过比较样本间的距离进行分类:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
# 初始化模型(默认 k =5)knn = KNeighborsClassifier()
# 训练模型
knn.fit(X_train, y_train)
# 预测测试集
y_pred_knn = knn.predict(X_test)
# 计算准确率
acc_knn = accuracy_score(y_test, y_pred_knn)
print(f"KNN 准确率: {acc_knn:.4f}")
逻辑回归分类器
逻辑回归虽然名为 ” 回归 ”,实为分类算法,通过 sigmoid 函数输出概率:
from sklearn.linear_model import LogisticRegression
# 使用多分类设置和增加最大迭代次数
log_reg = LogisticRegression(multi_class='multinomial', solver='lbfgs', max_iter=1000)
# 训练模型(可能较慢)log_reg.fit(X_train, y_train)
# 预测测试集
y_pred_log = log_reg.predict(X_test)
# 计算准确率
acc_log = accuracy_score(y_test, y_pred_log)
print(f"逻辑回归准确率: {acc_log:.4f}")
性能对比
我们通过多个维度量化比较两种模型:
- 准确率对比
- KNN: 约 96.8%
-
逻辑回归: 约 91.5%
-
训练时间
- KNN: 几乎无需训练(惰性学习)
-
逻辑回归: 在 CPU 上约 1 - 2 分钟
-
预测速度
- KNN: 较慢(需计算与所有训练样本的距离)
- 逻辑回归: 极快(只需计算权重向量点积)
可视化混淆矩阵(以 KNN 为例):
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
cm = confusion_matrix(y_test, y_pred_knn)
plt.figure(figsize=(10,8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.title('KNN 混淆矩阵')
plt.show()
参数调优
KNN 调优
关键参数是 n_neighbors(k 值)和距离度量方式:
from sklearn.model_selection import GridSearchCV
param_grid = {'n_neighbors': [3, 5, 7],
'weights': ['uniform', 'distance'],
'metric': ['euclidean', 'manhattan']
}
grid_search = GridSearchCV(KNeighborsClassifier(), param_grid, cv=3, verbose=2)
grid_search.fit(X_train[:5000], y_train[:5000]) # 为节省时间使用子集
print("最佳参数:", grid_search.best_params_)
print("最佳交叉验证准确率:", grid_search.best_score_)
逻辑回归调优
主要调整正则化强度 C 和惩罚类型:
param_grid = {'C': [0.1, 1, 10],
'penalty': ['l2'] # MNIST 数据通常 l2 优于 l1
}
grid_search = GridSearchCV(LogisticRegression(multi_class='multinomial', solver='lbfgs', max_iter=1000),
param_grid, cv=3, verbose=2
)
grid_search.fit(X_train[:5000], y_train[:5000])
print("最佳参数:", grid_search.best_params_)
print("最佳交叉验证准确率:", grid_search.best_score_)
避坑指南
- 特征缩放必要性
- KNN 对特征尺度敏感,必须归一化
-
逻辑回归虽不严格要求,但能加速收敛
-
类别不平衡处理
-
MNIST 数据较平衡,但实际应用中可考虑:
- class_weight 参数
- 过采样 / 欠采样技术
-
内存问题
- KNN 需存储全部训练数据,大数据集时内存消耗高
-
可考虑 KD 树或 Ball 树优化
-
收敛警告处理
- 逻辑回归可能出现 ” 未收敛 ” 警告
- 增加 max_iter 或调整 tol 参数
总结与展望
算法选择建议
- 选择 KNN 当:
- 需要快速原型验证
- 数据规模适中
-
解释预测结果有意义(通过近邻样本)
-
选择逻辑回归当:
- 需要部署到生产环境(预测速度快)
- 需要模型系数解释特征重要性
- 处理更高维数据(KNN 面临维度灾难)
优化方向
- 特征工程:
- 添加方向梯度直方图 (HOG) 等特征
-
使用 PCA 降维
-
模型融合:
-
结合 KNN 和逻辑回归的投票分类器
-
进阶模型:
- 随机森林
- 支持向量机
- 简单的神经网络
思考题
- 如何修改代码实现一个加权投票的 KNN 分类器(不同距离的邻居具有不同投票权重)?
- 对于逻辑回归,当某个数字类别(如 ”1″)的识别准确率明显低于其他数字时,可以采取哪些针对性的改进措施?
- 如果要在移动设备上部署手写数字识别模型,在 KNN 和逻辑回归中你会选择哪个?需要考虑哪些因素?
