手写MNIST Feedforward Neural Network自定义图片预测错误排查
问题:MNIST前馈神经网络无法识别自定义手写数字
模型参数
- 2个隐藏层,每层16个神经元
- 学习率=0.01
- 迭代次数=20000
- 隐藏层采用ReLU激活函数,输出层采用Softmax激活函数
- 数据集:共70000条样本,56000条用于训练,14000条用于测试
问题描述
该模型在测试集上的准确率为92.29%,但对自定义手写数字图片(
)的预测全部错误,尝试多个数字均无效。想请教:是自定义图片的预处理步骤有误,还是简单前馈神经网络的泛化能力不足以识别这类随机通用数据?改用CNN可以解决该问题吗?
完整实现代码
import numpy as np from sklearn.datasets import fetch_openml import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split import cv2 import time def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_derivative(x): return x * (1 - x) def relu(x): return np.maximum(0, x) def relu_derivative(x): return np.where(x > 0, 1, 0) def softmax(x): return np.exp(x) / sum(np.exp(x)) def initialize_parameters(input_layer_size, hidden_layer_size_1, hidden_layer_size_2, output_layer_size): w1 = np.random.rand(hidden_layer_size_1, input_layer_size) - 0.5 b1 = np.random.rand(hidden_layer_size_1, 1) - 0.5 w2 = np.random.rand(hidden_layer_size_2, hidden_layer_size_1) - 0.5 b2 = np.random.rand(hidden_layer_size_2, 1) - 0.5 w3 = np.random.rand(output_layer_size, hidden_layer_size_2) - 0.5 b3 = np.random.rand(output_layer_size, 1) - 0.5 return w1, b1, w2, b2, w3, b3 def forward_propagation(x, w1, b1, w2, b2, w3, b3): z1 = np.dot(w1, x.T) + b1 a1 = relu(z1) z2 = np.dot(w2, a1) + b2 a2 = relu(z2) z3 = np.dot(w3, a2) + b3 a3 = softmax(z3) return z1, a1, z2, a2, z3, a3 def loss(y, a3): m = y.shape[0] loss_value = np.sum(((y.T - a3) ** 2)) / m return loss_value def backward_propagate(x, y, z1, a1, z2, a2, a3, w2, w3): m = x.shape[0] dz3 = a3 - y.T dw3 = np.dot(dz3, a2.T) / m db3 = np.sum(dz3) / m da2 = np.dot(w3.T, dz3) dz2 = da2 * relu_derivative(z2) dw2 = np.dot(dz2, a1.T) / m db2 = np.sum(dz2) / m da1 = np.dot(w2.T, dz2) dz1 = da1 * relu_derivative(z1) dw1 = np.dot(dz1, x) / m db1 = np.sum(dz1) / m return dw1, db1, dw2, db2, dw3, db3 def update_parameters(w1, b1, w2, b2, w3, b3, dw1, db1, dw2, db2, dw3, db3, learning_rate): w1 -= learning_rate * dw1 b1 -= learning_rate * db1 w2 -= learning_rate * dw2 b2 -= learning_rate * db2 w3 -= learning_rate * dw3 b3 -= learning_rate * db3 return w1, b1, w2, b2, w3, b3 def train(x, y, input_layer_size, hidden_layer_size_1, hidden_layer_size_2, output_layer_size, learning_rate, iterations): w1, b1, w2, b2, w3, b3 = initialize_parameters(input_layer_size, hidden_layer_size_1, hidden_layer_size_2, output_layer_size) for i in range(iterations): z1, a1, z2, a2, z3, a3 = forward_propagation(x, w1, b1, w2, b2, w3, b3) loss_value = loss(y, a3) dw1, db1, dw2, db2, dw3, db3 = backward_propagate(x, y, z1, a1, z2, a2, a3, w2, w3) w1, b1, w2, b2, w3, b3 = update_parameters(w1, b1, w2, b2, w3, b3, dw1, db1, dw2, db2, dw3, db3, learning_rate) if i % 100 == 0: print(f"Iteration {i}, Loss: {loss_value}") return w1, b1, w2, b2, w3, b3 def main(): mnist = fetch_openml('mnist_784', version=1) x, y = mnist['data'], mnist['target'] x = np.array(x, dtype='float32') y = np.array(y, dtype='int32') x /= 255.0 num_classes = 10 y_onehot = np.zeros((y.shape[0], num_classes)) for i in range(len(y)): y_onehot[i, y[i]] = 1 x_train, x_test, y_train, y_test = train_test_split(x, y_onehot, test_size=0.2, random_state=42) input_layer_size = x_train.shape[1] hidden_layer_size_1 = 16 hidden_layer_size_2 = 16 output_layer_size = num_classes learning_rate = 0.01 iterations = 20000 start = time.time() w1, b1, w2, b2, w3, b3 = train(x_train, y_train, input_layer_size, hidden_layer_size_1, hidden_layer_size_2, output_layer_size, learning_rate, iterations) end = time.time() print("total time to train model", end - start, "seconds") np.savez("mnist_params.npz", w1=w1, b1=b1, w2=w2, b2=b2, w3=w3, b3=b3) _, _, _, _, _, y_predict = forward_propagation(x_test, w1, b1, w2, b2, w3, b3) accuracy = np.mean(np.argmax(y_predict.T, axis=1) == np.argmax(y_test, axis=1)) * 100 print("Accuracy = ", accuracy, " % ") img = cv2.imread("images/7.jpg", cv2.IMREAD_GRAYSCALE) plt.imshow(img, cmap='gray') plt.axis('off') plt.show() img = cv2.resize(img, (28, 28)) plt.imshow(img, cmap='gray') plt.axis('off') plt.show() img = img.flatten() img = np.array(img, dtype='float32') img /= 255.0 img = img.reshape(1, -1) params = np.load('mnist_params.npz') _, _, _, _, _, y_predict = forward_propagation(img, params['w1'], params['b1'], params['w2'], params['b2'], params['w3'], params['b3']) print(y_predict) print(np.argmax(y_predict)) main()
解答
1. 预处理步骤的核心问题
自定义图片预处理是预测错误的主要原因,存在以下关键疏漏:
- 像素反转缺失:MNIST数据集是白底黑字(背景像素值255,数字像素值0),而自定义手写图片通常是黑底白字,像素分布完全相反。需添加
img = 255 - img反转像素,匹配训练数据的特征分布。 - 缺少阈值强化:手写图片可能存在淡色笔迹或模糊,需用
cv2.threshold二值化处理,将笔迹转为纯黑、背景转为纯白,对齐MNIST的像素特征。示例代码:_, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) - 未做居中对齐:MNIST的数字集中在图片中心,若自定义数字偏移边缘,模型无法识别。可通过提取数字轮廓、计算中心坐标后平移到图片中心。
2. 前馈神经网络的泛化局限
即使预处理正确,当前前馈网络也存在先天不足:
- 模型容量不足:仅两层各16个神经元的网络,对训练数据外的风格差异(如手写粗细、倾斜、笔画变形)鲁棒性极差。MNIST测试集与训练集风格高度统一,所以能达到92%准确率,但自定义手写的风格差异超出了它的泛化能力。
- 丢失空间结构信息:前馈网络将28×28的图片拉平为784维一维向量,完全丢失数字的空间结构(如笔画位置、邻接关系),这是它处理图像任务的致命缺陷。
3. 改用CNN的效果
改用CNN可显著解决该问题:
- CNN通过卷积层天生擅长提取图像的空间特征(边缘、纹理、局部结构),对数字的位置、倾斜、粗细变化鲁棒性更强,即使预处理稍有瑕疵,也能大概率正确识别。
- 简单的CNN结构(如经典LeNet-5)在MNIST上可轻松达到99%以上的准确率,对自定义手写数字的识别能力远优于当前前馈网络。
内容的提问来源于stack exchange,提问作者Usama Maqsood
相关产品推荐
相关产品推荐

