基于 KNN 算法的 MNIST 手写数字分类实战
本文将演示如何利用 NumPy 实现一个基础的 K-近邻 (KNN) 分类器,并应用于经典的 MNIST 手写数字数据集。通过从零构建算法,我们可以深入理解该分类原理的工作机制,特别是在图像识别场景下的应用。
1. 环境准备与依赖库
首先,我们需要引入科学计算库 NumPy 用于矩阵运算,以及 Matplotlib 用于数据可视化。为了方便获取数据集,这里使用 TensorFlow 的 Keras 接口。
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
# 设置绘图后端
%matplotlib inline
2. 数据集加载与可视化
加载 MNIST 数据集,并将其分为训练集和测试集。原始图像是 28x28 的灰度图,为了简化计算,我们将图像扁平化为 784 维的向量,并进行归一化处理以提升计算稳定性。
# 加载数据
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# 数据预处理:扁平化与归一化
x_train = train_images.reshape(train_images.shape[0], -1).astype('float32') / 255.0
y_train = train_labels
x_test = test_images.reshape(test_images.shape[0], -1).astype('float32') / 255.0
y_test = test_labels
print(f"训练集特征维度: {x_train.shape}")
print(f"测试集特征维度: {x_test.shape}")
下面随机选取几张训练集图像进行可视化展示,确认数据加载正确。
plt.figure(figsize=(10, 2))
for index in range(6):
plt.subplot(1, 6, index + 1)
plt.imshow(x_train[index].reshape(28, 28), cmap='gray')
plt.axis('off')
plt.show()
3. KNN 分类器核心实现
我们将定义一个 SimpleKNN 类。该算法的核心逻辑是计算测试样本与训练集中所有样本的欧氏距离,选取距离最近的 K 个样本,根据这 K 个样本的标签进行投票,得票最多的类别即为预测结果。
为了提升代码可读性,我们将距离计算和投票逻辑拆分为独立的方法。
class SimpleKNN:
def __init__(self, n_neighbors=5):
self.n_neighbors = n_neighbors
def fit(self, X, y):
"""
存储训练数据,KNN 是惰性学习,训练阶段仅需保存样本
"""
self.X_train = X
self.y_train = y
def _predict_single(self, x):
"""
对单个样本进行预测
"""
# 1. 计算欧氏距离
distances = np.linalg.norm(self.X_train - x, axis=1)
# 2. 获取距离最近的 K 个样本的索引
k_indices = np.argsort(distances)[:self.n_neighbors]
# 3. 获取对应的标签
k_nearest_labels = [self.y_train[i] for i in k_indices]
# 4. 统计出现次数最多的标签
from collections import Counter
most_common = Counter(k_nearest_labels).most_common(1)
return most_common[0][0]
def predict(self, X):
"""
批量预测
"""
predictions = [self._predict_single(x) for x in X]
return np.array(predictions)
def score(self, X, y):
"""
计算模型在给定数据上的准确率
"""
y_pred = self.predict(X)
accuracy = np.sum(y_pred == y) / len(y)
return accuracy
4. 模型训练与评估
由于 KNN 在预测时需要计算所有样本的距离,计算复杂度较高。为了演示效果,我们对测试集进行采样(例如选取前 1000 个样本)进行评估,设置 K 值为 10。
# 初始化模型,设置 K=10
knn_clf = SimpleKNN(n_neighbors=10)
# 使用训练数据进行拟合
knn_clf.fit(x_train, y_train)
# 选取部分测试数据进行预测(为了节省时间)
sample_size = 1000
x_test_sample = x_test[:sample_size]
y_test_sample = y_test[:sample_size]
# 计算准确率
acc = knn_clf.score(x_test_sample, y_test_sample)
print(f"模型在测试样本上的准确率: {acc:.4f}")
运行上述代码后,模型通常会输出接近 0.96 左右的准确率。这证明了在没有任何参数优化的情况下,KNN 算法本身在手写数字识别任务中依然具有相当的竞争力。
