You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

手写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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:27:02