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

基于MNIST的手写多数字识别模型精度不足排查求助

多手写数字识别精度差的问题排查与优化建议

我借助ChatGPT自学机器学习,参考YouTube频道3Blue1Brown的基础案例,基于MNIST手写数字数据集训练了单数字识别模型,在MNIST数据集上训练100轮后准确率达99.2%。但拓展功能至识别白底黑字的任意多数字图片时,仅能大致判断数字个数,识别精度极差。

模型代码

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image, ImageOps
import cv2

# Load the MNIST dataset
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

# Normalize the pixel values
x_train = x_train / 255.0
x_test = x_test / 255.0

# Define the neural network architecture
model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(64, activation='sigmoid'),
    tf.keras.layers.Dense(100) # 100 neurons for double digit output
])

# Compile the model
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

# Train the model
model.fit(x_train, y_train, epochs=100)

# Load an image and convert it to grayscale
image_path = r'G:\Dr. Thrax\1MAC\PYTHON\Tinkering with python coding\Data_sources\AI_Test\0007.jpeg'
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
#gray = cv2.bitwise_not(gray)

# Apply binary thresholding to the image to separate digits from the background
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

# Find contours in the image
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Sort contours from left to right
contours = sorted(contours, key=lambda c: cv2.boundingRect(c)[0])

# Create a list to hold the digit images
digits = []

# Iterate over the contours
for contour in contours:
    # Get the bounding box of the contour
    x, y, w, h = cv2.boundingRect(contour)

    # Extract the digit image
    digit = thresh[y:y+h, x:x+w]

    # Resize the digit image to 28x28
    digit = cv2.resize(digit, (28, 28))

    # Add the digit image to the list
    digits.append(digit)

# Create a list to hold the predictions
predictions = []

# Iterate over the digit images
for digit in digits:
    # Reshape the digit image to (1, 28, 28, 1)
    digit = digit.reshape(1, 28, 28, 1)

    # Make a prediction on the digit image
    pred = model.predict(digit)

    # Get the digit prediction
    digit_pred = np.argmax(pred)

    # Add the digit prediction to the list
    predictions.append(digit_pred)

# Combine the digit predictions to get the final number
number = 0
for pred in predictions:
    number = number * 10 + pred

# Plot the image and the prediction
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title(f'Prediction: {number}')
plt.show()

已尝试的调整

  • 修改训练轮数
  • 调整网络层数
  • 在输出层交替使用sigmoid与softmax激活函数

核心问题定位

  1. 输出层设计完全错误:MNIST是单数字分类任务(0-9),但代码中输出层设为100个神经元,对应0-99的分类。而训练数据的标签是单数字(0-9),这会导致模型训练目标混乱,无法正确学习单数字特征。
  2. 预处理与MNIST数据分布不匹配:
    • MNIST数据为黑底白字,代码中注释掉了cv2.bitwise_not(gray),导致输入的白底黑字图片像素明暗与训练数据完全相反,模型无法匹配已学习的特征。
    • 裁剪单数字后直接resize到28x28,未保留MNIST中数字居中、周围留白的布局特征,导致数字的位置、占比与训练数据差异极大。
  3. 网络结构冗余:针对MNIST这种简单任务,过多的全连接层和Dropout会降低模型的学习效率,甚至引发过拟合或欠拟合。

具体优化步骤

1. 修正模型输出层与结构

将输出层改为10个神经元(对应0-9分类),同时简化冗余的网络层:

model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10, activation='softmax') # 10个神经元对应0-9分类
])

# 编译时使用softmax激活,无需from_logits=True
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(),
              metrics=['accuracy'])

2. 对齐预处理流程与MNIST数据

  • 恢复cv2.bitwise_not(gray),将白底黑字转为黑底白字,匹配训练数据的明暗分布。
  • 裁剪单数字后添加padding,让数字居中并保持正方形比例,模拟MNIST的布局:
for contour in contours:
    x, y, w, h = cv2.boundingRect(contour)
    digit = thresh[y:y+h, x:x+w]
    # 计算padding,让数字居中,转为正方形
    max_dim = max(w, h)
    padding_x = (max_dim - w) // 2
    padding_y = (max_dim - h) // 2
    digit = cv2.copyMakeBorder(digit, padding_y, padding_y, padding_x, padding_x, cv2.BORDER_CONSTANT, value=0)
    # 缩放至28x28并归一化
    digit = cv2.resize(digit, (28, 28))
    digit = digit / 255.0
    digits.append(digit)

3. 优化训练策略

  • MNIST任务无需训练100轮,20轮左右即可达到饱和,过多轮数易引发过拟合。
  • 训练时加入验证集,监控模型泛化能力:
model.fit(x_train, y_train, epochs=20, validation_split=0.1)

内容的提问来源于stack exchange,提问作者Ak. Omar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:47:24