基于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激活函数
核心问题定位
- 输出层设计完全错误:MNIST是单数字分类任务(0-9),但代码中输出层设为100个神经元,对应0-99的分类。而训练数据的标签是单数字(0-9),这会导致模型训练目标混乱,无法正确学习单数字特征。
- 预处理与MNIST数据分布不匹配:
- MNIST数据为黑底白字,代码中注释掉了
cv2.bitwise_not(gray),导致输入的白底黑字图片像素明暗与训练数据完全相反,模型无法匹配已学习的特征。 - 裁剪单数字后直接resize到28x28,未保留MNIST中数字居中、周围留白的布局特征,导致数字的位置、占比与训练数据差异极大。
- MNIST数据为黑底白字,代码中注释掉了
- 网络结构冗余:针对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
相关产品推荐
相关产品推荐

