首先导入依赖库
# 导入必要的库和模块
import numpy as np # 用于数值计算和数组操作
import pandas as pd # 用于数据处理和分析
from keras.datasets import mnist # 加载MNIST手写数字数据集
from tensorflow.keras.utils import to_categorical # 将标签转换为分类格式
from keras import models # 神经网络模型类
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D # 神经网络层类型
from PIL import Image
然后导入mnist数据集,并对格式进行转换
(X_train_image, y_train_lable), (X_test_image, y_test_lable) = mnist.load_data()
# 数据预处理
# 将图像数据重塑为4D张量: (样本数, 高度, 宽度, 通道数)
# 添加通道维度1,表示灰度图像(如果是RGB图像则为3)
X_train = X_train_image.reshape(60000, 28, 28, 1)
X_test = X_test_image.reshape(10000, 28, 28, 1)
# 将标签转换为one-hot编码格式
# 例如: 数字3 -> [0,0,0,1,0,0,0,0,0,0]
# 数字7 -> [0,0,0,0,0,0,0,1,0,0]
y_train = to_categorical(y_train_lable, 10)
y_test = to_categorical(y_test_lable, 10)
reshape其中60000是文件样本数,28和28是像素,1是通道数,拓展1是为了更加方便理解
to_categorical是转换为独热编码,方便机器学习跟分类对应理解
然后创建一个模型
# 创建Sequential顺序模型
model = models.Sequential()
然后开始叠神经元
我理解第一个是通过3*3进行扫描,生成32个通道,其中relu是因为如果不用激活函数,会导致一直都是一维线性,会导致叠加深度无效,input_shape是输入参数
然后添加池化,为了减少数据量,提取特征
接下来再进行一次扫描,这次会让感受野增加,识别的特征更多
池化
然后随机丢弃25%神经元防止过拟合
将数据平铺后丢入全连接层
丢弃50%神经元
然后softmax进行分类
# 添加第一个卷积层
# 32个3x3的卷积核,使用ReLU激活函数
# input_shape指定输入数据的维度: 28x28像素,1个通道(灰度)
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
# 添加最大池化层
# 使用2x2池化窗口,将特征图尺寸减半(28x28 -> 14x14)
# 减少参数数量,增强特征不变性
model.add(MaxPooling2D(pool_size=(2, 2)))
# 添加第二个卷积层
# 64个3x3的卷积核,提取更复杂的特征
model.add(Conv2D(64, (3, 3), activation='relu'))
# 添加第二个最大池化层
# 进一步减少特征图尺寸(14x14 -> 7x7)
model.add(MaxPooling2D(pool_size=(2, 2)))
# 添加Dropout层防止过拟合
# 在训练过程中随机丢弃25%的神经元连接
# 强制网络学习更鲁棒的特征表示
model.add(Dropout(0.25))
# 将多维特征图展平为一维向量
# 为全连接层准备输入数据
model.add(Flatten())
# 添加全连接层(密集层)
# 128个神经元,使用ReLU激活函数
# 进行高级特征组合和分类决策
model.add(Dense(128, activation='relu'))
# 添加第二个Dropout层
# 丢弃50%的神经元连接,进一步防止过拟合
# 同时可以加速训练过程
model.add(Dropout(0.5))
# 添加输出层
# 10个神经元对应10个数字类别(0-9)
# 使用softmax激活函数输出概率分布
model.add(Dense(10, activation='softmax'))
编译模型,配置训练过程
optimizer: 使用rmsprop优化算法更新权重
loss: 使用分类交叉熵损失函数,适用于多分类问题
metrics: 监控准确率指标
开始训练模型
X_train, y_train: 训练数据和标签
validation_split=0.3: 使用30%的训练数据作为验证集
epochs=5: 整个训练集迭代5次
batch_size=128: 每次使用128个样本更新权重
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(X_train, y_train,
validation_split=0.3,
epochs=5,
batch_size=128)
print('测试集预测准确率:', score[1])
输出内容
测试集预测准确率: 0.9782999753952026
写一张图片进行测试

# 读取本地图片 pic.PNG,转换为 (1, 28, 28, 1) 格式并用训练好的 model 预测
try:
# 尝试打开同一目录下的 pic.PNG
img = Image.open('pic.PNG').convert('L') # 转为灰度
# Pillow 新旧版本兼容的重采样选项
# Image.Resampling (Pillow>=9.1.0) 使用枚举,例如 Image.Resampling.LANCZOS
# 在旧版本中可能存在 Image.LANCZOS 或 Image.ANTIALIAS;确保向后兼容
if hasattr(Image, 'Resampling'):
resample = Image.Resampling.LANCZOS
elif hasattr(Image, 'LANCZOS'):
resample = Image.LANCZOS
elif hasattr(Image, 'ANTIALIAS'):
resample = Image.ANTIALIAS
else:
# 兜底使用 BICUBIC,任何 Pillow 版本都应支持
resample = Image.BICUBIC
img = img.resize((28, 28), resample)
img_arr = np.array(img)
# 如果图片背景/前景颜色与你的训练数据(MNIST:白字黑底)不同,可能需要反转。
img_arr = 255 - img_arr # 反转颜色(如果需要)
# 形状调整为 (1,28,28,1)
img_arr = img_arr.reshape(1, 28, 28, 1)
# 保持与训练数据相近的数据类型(训练时使用的是从 mnist.load_data 返回的 uint8)
img_arr = img_arr.astype(np.float32)
# 如果在训练时进行了归一化(例如除以255),这里也要相同处理。
# 原训练脚本没有除以255,因此我们不做归一化;如果你修改过训练预处理,请在此处做相同处理。
prediction = model.predict(img_arr)
predicted_label = np.argmax(prediction, axis=1)[0]
print('\nPrediction probabilities for pic.PNG:\n', prediction)
print('Predicted label for pic.PNG:', int(predicted_label))
except FileNotFoundError:
print('\npic.PNG 未找到,已跳过图像预测。请把 pic.PNG 放到脚本同一目录下后重试。')
except Exception as e:
print('\n处理 pic.PNG 时出错:', e)
输出:
Predicted label for pic.PNG: 6
完整代码
# 导入必要的库和模块
import numpy as np # 用于数值计算和数组操作
import pandas as pd # 用于数据处理和分析
from keras.datasets import mnist # 加载MNIST手写数字数据集
from tensorflow.keras.utils import to_categorical # 将标签转换为分类格式
from keras import models # 神经网络模型类
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D # 神经网络层类型
from PIL import Image
# 加载MNIST数据集,包含训练集和测试集
# X_train_image: 60000张28x28的训练图像
# y_train_lable: 60000个对应的训练标签(0-9)
# X_test_image: 10000张28x28的测试图像
# y_test_lable: 10000个对应的测试标签
(X_train_image, y_train_lable), (X_test_image, y_test_lable) = mnist.load_data()
# 数据预处理
# 将图像数据重塑为4D张量: (样本数, 高度, 宽度, 通道数)
# 添加通道维度1,表示灰度图像(如果是RGB图像则为3)
X_train = X_train_image.reshape(60000, 28, 28, 1)
X_test = X_test_image.reshape(10000, 28, 28, 1)
# 将标签转换为one-hot编码格式
# 例如: 数字3 -> [0,0,0,1,0,0,0,0,0,0]
# 数字7 -> [0,0,0,0,0,0,0,1,0,0]
y_train = to_categorical(y_train_lable, 10)
y_test = to_categorical(y_test_lable, 10)
# 创建Sequential顺序模型
model = models.Sequential()
# 添加第一个卷积层
# 32个3x3的卷积核,使用ReLU激活函数
# input_shape指定输入数据的维度: 28x28像素,1个通道(灰度)
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
# 添加最大池化层
# 使用2x2池化窗口,将特征图尺寸减半(28x28 -> 14x14)
# 减少参数数量,增强特征不变性
model.add(MaxPooling2D(pool_size=(2, 2)))
# 添加第二个卷积层
# 64个3x3的卷积核,提取更复杂的特征
model.add(Conv2D(64, (3, 3), activation='relu'))
# 添加第二个最大池化层
# 进一步减少特征图尺寸(14x14 -> 7x7)
model.add(MaxPooling2D(pool_size=(2, 2)))
# 添加Dropout层防止过拟合
# 在训练过程中随机丢弃25%的神经元连接
# 强制网络学习更鲁棒的特征表示
model.add(Dropout(0.25))
# 将多维特征图展平为一维向量
# 为全连接层准备输入数据
model.add(Flatten())
# 添加全连接层(密集层)
# 128个神经元,使用ReLU激活函数
# 进行高级特征组合和分类决策
model.add(Dense(128, activation='relu'))
# 添加第二个Dropout层
# 丢弃50%的神经元连接,进一步防止过拟合
# 同时可以加速训练过程
model.add(Dropout(0.5))
# 添加输出层
# 10个神经元对应10个数字类别(0-9)
# 使用softmax激活函数输出概率分布
model.add(Dense(10, activation='softmax'))
# 编译模型,配置训练过程
# optimizer: 使用rmsprop优化算法更新权重
# loss: 使用分类交叉熵损失函数,适用于多分类问题
# metrics: 监控准确率指标
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
# 开始训练模型
# X_train, y_train: 训练数据和标签
# validation_split=0.3: 使用30%的训练数据作为验证集
# epochs=5: 整个训练集迭代5次
# batch_size=128: 每次使用128个样本更新权重
model.fit(X_train, y_train,
validation_split=0.3,
epochs=5,
batch_size=128)
# 在测试集上评估模型性能
# 返回损失值和准确率
score = model.evaluate(X_test, y_test)
# 打印测试集准确率
# score[0]是损失值,score[1]是准确率
print('测试集预测准确率:', score[1])
# 读取本地图片 pic.PNG,转换为 (1, 28, 28, 1) 格式并用训练好的 model 预测
try:
# 尝试打开同一目录下的 pic.PNG
img = Image.open('pic.PNG').convert('L') # 转为灰度
# Pillow 新旧版本兼容的重采样选项
# Image.Resampling (Pillow>=9.1.0) 使用枚举,例如 Image.Resampling.LANCZOS
# 在旧版本中可能存在 Image.LANCZOS 或 Image.ANTIALIAS;确保向后兼容
if hasattr(Image, 'Resampling'):
resample = Image.Resampling.LANCZOS
elif hasattr(Image, 'LANCZOS'):
resample = Image.LANCZOS
elif hasattr(Image, 'ANTIALIAS'):
resample = Image.ANTIALIAS
else:
# 兜底使用 BICUBIC,任何 Pillow 版本都应支持
resample = Image.BICUBIC
img = img.resize((28, 28), resample)
img_arr = np.array(img)
# 如果图片背景/前景颜色与你的训练数据(MNIST:白字黑底)不同,可能需要反转。
img_arr = 255 - img_arr # 反转颜色(如果需要)
# 形状调整为 (1,28,28,1)
img_arr = img_arr.reshape(1, 28, 28, 1)
# 保持与训练数据相近的数据类型(训练时使用的是从 mnist.load_data 返回的 uint8)
img_arr = img_arr.astype(np.float32)
# 如果在训练时进行了归一化(例如除以255),这里也要相同处理。
# 原训练脚本没有除以255,因此我们不做归一化;如果你修改过训练预处理,请在此处做相同处理。
prediction = model.predict(img_arr)
predicted_label = np.argmax(prediction, axis=1)[0]
print('\nPrediction probabilities for pic.PNG:\n', prediction)
print('Predicted label for pic.PNG:', int(predicted_label))
except FileNotFoundError:
print('\npic.PNG 未找到,已跳过图像预测。请把 pic.PNG 放到脚本同一目录下后重试。')
except Exception as e:
print('\n处理 pic.PNG 时出错:', e)