简介:机器学习作为人工智能的核心技术,其核心原理是通过算法让计算机从数据中学习规律。在图像识别领域,卷积神经网络(CNN)因其能够自动提取局部特征的特性,成为处理视觉任务的主流架构。这项技术的价值在于能够将复杂的模式识别问题转化为可优化的数学模型,广泛应用于安防监控、自动驾驶、医疗影像分析等场景。本文以经典的MNIST手写数字识别项目为例,详细讲解如何使用Python和TensorFlow 2.x,从环境搭建、数据预处理开始,逐步构建并优化一个卷积神经网络模型。内容涵盖虚拟环境配置、数据标准化、模型训练与评估等关键步骤,并深入探讨了全连接网络与CNN的性能差异,以及通过数据增强、超参数调优等实战技巧提升模型准确率至99%以上的方法。
1. 从零到一:为什么手写数字识别是机器学习的“Hello World”
如果你刚开始接触机器学习,或者想用Python和TensorFlow做点有意思的东西,手写数字识别绝对是你绕不开的第一个实战项目。这听起来可能有点老生常谈,毕竟MNIST数据集从1998年就被用烂了。但你想过没有,为什么十年过去了,它依然是新手入门的首选?这背后其实有几个非常实际的原因。
首先,它的问题定义极其清晰。任务就是识别0到9这十个手写数字,输入是一张28x28像素的灰度图片,输出是一个0到9的整数标签。没有模棱两可的语义,没有复杂的标注规则,目标简单直接。其次,数据质量高且易于获取。MNIST数据集包含了6万张训练图片和1万张测试图片,每张图片都经过标准化处理,大小一致,背景干净,数字居中。你不需要花几天时间去爬虫、清洗、标注数据,下载下来就能直接用,这让你能立刻把精力集中在模型本身。最后,它的计算资源要求友好。用今天的普通笔记本电脑,甚至配置好环境的云服务器,训练一个基础的神经网络模型可能只需要几分钟。你可以在短时间内完成“数据加载 -> 模型构建 -> 训练 -> 评估”的完整闭环,快速获得正反馈,这对保持学习热情至关重要。
所以,别把它看作一个过时的玩具。把它当作一个完美的沙盒,在这里,你可以安全地试验各种想法:从最基础的全连接网络,到卷积神经网络(CNN),再到尝试不同的优化器、学习率、损失函数。每一次代码的修改,都能直观地反映在准确率的变化上。今天,我们就抛开那些教科书式的理论,直接上手,用Python和TensorFlow 2.x,从环境搭建开始,一步步构建、训练并优化一个能识别手写数字的模型,我会把过程中所有容易踩的坑和提升效果的小技巧都揉进去。
2. 环境搭建:避开“从入门到放弃”的第一个坑
万事开头难,而机器学习项目开头的难,十有八九卡在环境上。你可能已经搜过“tensorflow 2.18 安装”或者“python安装教程”,但照着做却可能遇到各种版本冲突、依赖报错。我们的目标是建立一个干净、可复现的Python环境,这是后续所有工作的基石。
2.1 Python版本与虚拟环境:隔离是美德
首先,忘掉系统自带的Python。直接去Python官网下载最新稳定版,比如Python 3.10或3.11。安装时务必勾选“Add Python to PATH”,这样后续在命令行里直接输入python和pip就能用。安装完成后,打开终端(Windows用CMD或PowerShell,macOS/Linux用Terminal),输入python --version确认版本。
接下来是至关重要的一步:创建虚拟环境。虚拟环境就像一个独立的沙箱,你在这个沙箱里安装的所有包(如TensorFlow、NumPy)都不会影响系统其他Python项目。这是避免包版本冲突的最佳实践。我强烈推荐使用venv,它是Python 3.3+自带的,无需额外安装。
# 在你喜欢的项目目录下,比如 D:\projects cd D:\projects # 创建一个名为 mnist_env 的虚拟环境 python -m venv mnist_env创建完成后,激活它:
- Windows (CMD):
mnist_env\Scripts\activate.bat - Windows (PowerShell):
mnist_env\Scripts\Activate.ps1(可能需要先执行Set-ExecutionPolicy RemoteSigned允许脚本运行) - macOS/Linux:
source mnist_env/bin/activate
激活后,你的命令行提示符前面应该会出现(mnist_env),表示你已经在这个虚拟环境里了。后续所有pip install操作都只在这个环境内生效。
2.2 TensorFlow与核心依赖安装:版本匹配的艺术
现在可以安装TensorFlow了。对于新手,我建议直接安装TensorFlow 2.x的CPU版本,它足够应对MNIST这样的数据集,且安装最简单。如果你的电脑有NVIDIA显卡并配置好了CUDA和cuDNN,可以安装GPU版本以加速训练,但那又是另一个需要仔细配置的“坑”了,我们初期以跑通流程为首要目标。
# 在激活的虚拟环境中,使用清华镜像源加速下载 pip install tensorflow -i https://pypi.tuna.tsinghua.edu.cn/simple安装TensorFlow时,它会自动处理大部分依赖,但有几个科学计算的核心库我们最好明确其版本,确保兼容性。
pip install numpy==1.23.5 matplotlib==3.7.1 pandas==2.0.3 -i https://pypi.tuna.tsinghua.edu.cn/simple这里为什么指定numpy==1.23.5?因为较新版本的NumPy(如1.24+)在某些API上与旧版不兼容,而TensorFlow的某些底层依赖可能还未适配,直接安装最新版容易引发难以排查的警告或错误。1.23.5是一个经过广泛验证的稳定版本。
安装完成后,写一个简单的测试脚本test_env.py来验证:
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt import pandas as pd print(f"TensorFlow 版本: {tf.__version__}") print(f"NumPy 版本: {np.__version__}") # 尝试创建一个简单的TensorFlow常量,检查GPU是否可用(如果安装了GPU版) print(f"GPU 是否可用: {tf.config.list_physical_devices('GPU')}")运行python test_env.py,如果没有报错,并输出了版本信息,那么恭喜你,最磨人的环境关已经过了。
注意:如果你在安装过程中遇到诸如“Could not find a version that satisfies the requirement”之类的错误,大概率是网络问题或Python版本与TensorFlow版本不匹配。请尝试更换pip源(如阿里云
-i https://mirrors.aliyun.com/pypi/simple/),或查阅TensorFlow官方安装指南,确认当前Python版本支持的TensorFlow版本。
3. 数据探索与预处理:理解你的“原料”
模型的好坏,一半取决于数据。在把数据喂给模型之前,我们必须先了解它、清洗它、改造它,让它变得“好吃”。MNIST数据虽然干净,但预处理步骤依然不可或缺,并且这些步骤是任何图像分类任务的通用流程。
3.1 加载与初窥MNIST数据
TensorFlow的keras.datasets模块内置了MNIST数据集,加载非常方便。
import tensorflow as tf from tensorflow import keras # 加载数据,返回的是已经划分好的训练集和测试集 (train_images, train_labels), (test_images, test_labels) = keras.datasets.mnist.load_data()加载完成后,我们首先看看数据的“长相”:
print(f"训练集图像形状: {train_images.shape}") # 应输出 (60000, 28, 28) print(f"训练集标签形状: {train_labels.shape}") # 应输出 (60000,) print(f"测试集图像形状: {test_images.shape}") # 应输出 (10000, 28, 28) print(f"测试集标签形状: {test_labels.shape}") # 应输出 (10000,) # 查看第一张训练图片和对应的标签 print(f"第一张图片的标签是: {train_labels[0]}")train_images是一个三维NumPy数组,你可以理解为60000张图片,每张图片是28行、28列的像素矩阵。每个像素值是0到255之间的整数,0代表黑色(背景),255代表白色(笔迹),但注意,MNIST图片的背景是黑色(0),数字是白色(255)。我们可以用Matplotlib把它画出来看看:
import matplotlib.pyplot as plt plt.figure() plt.imshow(train_images[0], cmap=plt.cm.binary) # cmap=plt.cm.binary 表示用灰度色图显示 plt.colorbar() # 显示颜色条,代表像素值 plt.title(f"Label: {train_labels[0]}") plt.show()3.2 数据预处理:标准化与维度调整
原始数据不能直接扔进神经网络,需要两步关键处理。
第一步:数据标准化(归一化)像素值范围是0-255,对于神经网络来说,这个范围较大且是整数。我们通常将其转换为0-1之间的浮点数,这有助于模型更快、更稳定地收敛。方法很简单,除以255.0。
# 将图像数据从整数[0, 255]缩放到浮点数[0, 1] train_images = train_images / 255.0 test_images = test_images / 255.0第二步:调整数据维度(为全连接层准备)我们即将构建的第一个模型是全连接神经网络,它要求输入是一维向量。我们的每张图片是28x28的二维矩阵,需要“展平”成28*28=784的一维向量。
# 展平图像数据,将 (60000, 28, 28) 变为 (60000, 784) train_images_flat = train_images.reshape((60000, 28 * 28)) test_images_flat = test_images.reshape((10000, 28 * 28)) print(f"展平后训练集形状: {train_images_flat.shape}")对于标签,我们目前是0到9的数字。如果使用简单的整数标签和稀疏分类交叉熵损失,可以直接用。但更常见的做法是进行“独热编码”,将每个标签转换为一个10维的向量,只有对应数字的位置是1,其余是0。这对于多分类问题在数学上更清晰。TensorFlow的损失函数可以自动处理这两种形式,但了解独热编码是有益的。
# 使用keras的to_categorical进行独热编码 train_labels_one_hot = keras.utils.to_categorical(train_labels, num_classes=10) test_labels_one_hot = keras.utils.to_categorical(test_labels, num_classes=10) print(f"独热编码后标签形状: {train_labels_one_hot.shape}") # (60000, 10) print(f"第一个标签的独热编码: {train_labels_one_hot[0]}") # 例如标签是5,则第6位(索引5)为1至此,数据已经准备好了。训练集train_images_flat和train_labels(或train_labels_one_hot)将用于教导模型,测试集test_images_flat和test_labels将用于最终评估模型在未见过的数据上的表现。
4. 构建第一个神经网络模型:全连接网络的兴衰
理解了数据,我们就可以开始搭建模型了。我们从最经典、也最容易理解的全连接网络开始。全连接层,也叫密集连接层,是神经网络最基本的构件,它的每个神经元都与上一层的所有神经元相连。
4.1 模型架构设计:堆叠层的艺术
我们打算构建一个简单的三层网络(不算输入层):
- 输入层:接收展平后的784维向量。
- 隐藏层1:128个神经元,使用ReLU激活函数。ReLU(Rectified Linear Unit)简单高效,能有效缓解梯度消失问题,是深度学习中最常用的激活函数。
- 隐藏层2:64个神经元,同样使用ReLU激活函数。增加一层可以增强模型的非线性表达能力。
- 输出层:10个神经元,对应0-9这10个数字。使用Softmax激活函数,它将10个神经元的输出转换为一个概率分布,每个值代表对应数字的概率,所有值之和为1。
使用TensorFlow Keras的Sequential API,我们可以像搭积木一样定义这个模型:
from tensorflow.keras import models, layers # 创建一个Sequential模型,它是一系列层的线性堆叠 model = models.Sequential([ # 输入层:展平后的784个特征,这一层由下面的Dense层自动处理输入形状 # 第一个隐藏层:128个神经元,激活函数为ReLU,并指定输入形状为(784,) layers.Dense(128, activation='relu', input_shape=(28*28,)), # 第二个隐藏层:64个神经元,激活函数为ReLU layers.Dense(64, activation='relu'), # 输出层:10个神经元,激活函数为Softmax layers.Dense(10, activation='softmax') ]) # 打印模型结构摘要 model.summary()运行model.summary(),你会看到每一层的输出形状和参数数量。例如,第一层dense有(784 + 1) * 128 = 100480个参数。这里的“+1”是偏置项。这个摘要能帮你快速理解模型的复杂度和规模。
4.2 编译模型:配置学习过程
模型结构定义好了,但它还不会学习。我们需要通过“编译”来配置学习过程,主要指定三个关键要素:
- 优化器:决定模型如何根据损失函数反馈来更新权重。这里我们使用
Adam,它是目前最流行、默认效果通常不错的优化器,自适应调整学习率。 - 损失函数:衡量模型预测结果与真实标签的差距。对于多分类问题,我们使用
sparse_categorical_crossentropy。注意,因为我们标签是整数形式(0,1,2...),所以用sparse_版本。如果用了独热编码标签,则使用categorical_crossentropy。 - 评估指标:在训练和测试时监控的指标。对于分类问题,我们最关心
accuracy(准确率)。
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])4.3 训练模型:让模型“看见”数据
现在,把准备好的训练数据喂给模型,开始学习。训练的核心方法是fit函数。
# 开始训练 history = model.fit(train_images_flat, train_labels, epochs=10, # 整个训练集完整遍历10次 batch_size=64, # 每次更新权重所用的小批量数据大小为64 validation_split=0.2) # 从训练集中拿出20%作为验证集,用于监控训练过程中的模型表现这里有几个关键参数:
epochs:迭代轮数。一轮代表模型看完了整个训练集一次。轮数太少,模型学不到东西(欠拟合);轮数太多,可能会过度记忆训练集细节,导致在新数据上表现变差(过拟合)。10是一个合理的起点。batch_size:批大小。由于内存限制,我们不会一次把6万张图都喂给模型,而是分成一批一批的。每次模型处理一个批次(64张图),计算一次损失,更新一次权重。较小的批次能带来更频繁的权重更新和可能更好的泛化能力,但训练速度会慢一些。validation_split:验证集比例。这非常重要!验证集不参与训练,只用于在每轮训练后评估模型在当前权重下的表现。通过观察训练集准确率和验证集准确率的差距,我们可以判断模型是否过拟合。
训练过程中,你会看到每个epoch结束后输出的损失值和准确率。训练完成后,history对象记录了训练过程中的所有指标,我们可以用它来绘制学习曲线。
import matplotlib.pyplot as plt # 绘制训练 & 验证的准确率值 plt.plot(history.history['accuracy'], label='Training Accuracy') plt.plot(history.history['val_accuracy'], label='Validation Accuracy') plt.title('Model Accuracy') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.legend(loc='lower right') plt.show() # 绘制训练 & 验证的损失值 plt.plot(history.history['loss'], label='Training Loss') plt.plot(history.history['val_loss'], label='Validation Loss') plt.title('Model Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend(loc='upper right') plt.show()观察曲线:理想情况下,训练和验证的准确率都应稳步上升并最终趋于平稳,损失稳步下降。如果训练准确率持续上升但验证准确率在达到某个点后开始下降或波动,这就是典型的过拟合信号——模型开始“死记硬背”训练数据了。
4.4 评估与预测:检验模型真功夫
训练结束后,我们用完全没参与过训练的测试集来最终评估模型。
# 在测试集上评估模型 test_loss, test_acc = model.evaluate(test_images_flat, test_labels, verbose=2) print(f'\n测试集准确率: {test_acc:.4f}')一个经过10轮训练的全连接网络,在测试集上的准确率通常能达到97%以上。这听起来很不错,但别高兴太早。全连接网络处理图像有很大的局限性:它把二维的像素空间关系完全打平成一维向量,忽略了数字的局部结构(比如“8”的上下两个圈)。这正是卷积神经网络(CNN)要解决的问题。
5. 进阶之路:引入卷积神经网络(CNN)
全连接网络达到了97%的准确率,但想突破98%甚至99%,就必须请出图像处理领域的王者——卷积神经网络。CNN通过卷积核在图像上滑动,自动学习局部特征(如边缘、角点),并通过池化层降低数据维度,保留主要特征,这种结构天生适合处理图像数据。
5.1 CNN模型架构设计
这次我们不再展平数据,而是保留图像的二维结构(28, 28, 1),最后的1表示是单通道灰度图。我们构建一个经典的简单CNN:
# 重塑数据,增加一个通道维度,从 (60000, 28, 28) 变为 (60000, 28, 28, 1) train_images_cnn = train_images.reshape((60000, 28, 28, 1)) test_images_cnn = test_images.reshape((10000, 28, 28, 1)) # 构建CNN模型 model_cnn = models.Sequential([ # 第一层卷积:使用32个3x3的卷积核,激活函数ReLU。 # input_shape指定输入图像的形状 (高度,宽度,通道数) layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)), # 第一层池化:2x2最大池化,将特征图尺寸减半 layers.MaxPooling2D((2, 2)), # 第二层卷积:使用64个3x3的卷积核 layers.Conv2D(64, (3, 3), activation='relu'), # 第二层池化 layers.MaxPooling2D((2, 2)), # 将三维特征图展平成一维,以便输入全连接层 layers.Flatten(), # 全连接层,64个神经元,引入Dropout防止过拟合 layers.Dense(64, activation='relu'), layers.Dropout(0.5), # 在训练过程中,随机丢弃50%的神经元输出,强制网络学习更鲁棒的特征 # 输出层 layers.Dense(10, activation='softmax') ]) model_cnn.summary()看model.summary(),你会发现卷积层的参数数量远少于全连接层,这是因为参数共享机制。一个3x3的卷积核在整个图像上共享参数,而不是像全连接层那样每个像素都有一个独立的权重。
5.2 训练与性能对比
用同样的方式编译和训练CNN模型:
model_cnn.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) history_cnn = model_cnn.fit(train_images_cnn, train_labels, epochs=10, batch_size=64, validation_split=0.2)训练结束后,再次评估测试集:
test_loss_cnn, test_acc_cnn = model_cnn.evaluate(test_images_cnn, test_labels, verbose=2) print(f'\nCNN模型测试集准确率: {test_acc_cnn:.4f}')你会发现,即使是这样简单的CNN结构,经过10轮训练,测试准确率也能轻松达到99%左右,显著优于全连接网络。绘制学习曲线,你可能会发现CNN的验证准确率更早地趋于稳定,过拟合的迹象也更轻微。
5.3 可视化卷积核与特征图(可选但推荐)
为了理解CNN到底学到了什么,我们可以可视化第一层卷积核以及它对某张图片的响应(特征图)。这能让你直观地看到,底层的卷积核可能学会了检测边缘、斜线等基础特征。
import numpy as np import matplotlib.pyplot as plt from tensorflow.keras import models # 获取第一层卷积层的权重(卷积核) first_layer_weights = model_cnn.layers[0].get_weights()[0] # 形状: (3, 3, 1, 32) # 获取第一层卷积层的偏置 first_layer_biases = model_cnn.layers[0].get_weights()[1] # 可视化前16个卷积核 fig, axes = plt.subplots(4, 8, figsize=(12, 6)) # 32个核,我们显示前16个 for i, ax in enumerate(axes.flat): if i < 32: kernel = first_layer_weights[:, :, 0, i] # 取第i个卷积核的权重 ax.imshow(kernel, cmap='gray') ax.axis('off') ax.set_title(f'Kernel {i}') plt.suptitle('First Conv Layer Filters') plt.tight_layout() plt.show() # 创建一个模型,输出指定层(如第一个卷积层)的激活(特征图) layer_outputs = [layer.output for layer in model_cnn.layers[:1]] # 取第一层 activation_model = models.Model(inputs=model_cnn.input, outputs=layer_outputs) # 取一张测试图片,并增加一个批次维度 (1, 28, 28, 1) img = test_images_cnn[0:1] # 获取该图片在第一层卷积后的激活值 activations = activation_model.predict(img) first_layer_activation = activations[0] # 形状: (1, 26, 26, 32) # 可视化第一层部分特征图 fig, axes = plt.subplots(4, 8, figsize=(12, 6)) for i, ax in enumerate(axes.flat): if i < 32: feature_map = first_layer_activation[0, :, :, i] ax.imshow(feature_map, cmap='viridis') ax.axis('off') ax.set_title(f'FM {i}') plt.suptitle('Feature Maps for the first test image') plt.tight_layout() plt.show()通过观察特征图,你可以看到原始图像经过卷积核过滤后,哪些区域被激活(亮色),这对应了数字的特定边缘或纹理。
6. 模型优化与实战技巧:从“能用”到“好用”
达到99%的准确率后,我们还可以从哪些方面优化?这里分享几个在实际项目中提升模型性能和稳健性的关键技巧。
6.1 数据增强:创造“新”数据
过拟合的根本原因是模型看到的训练数据太少了。数据增强通过对训练图像进行随机但合理的变换(如旋转、缩放、平移、轻微扭曲),在不改变标签的前提下,“创造”出新的训练样本,从而让模型学习到更泛化的特征,而不是记住训练集的细节。
对于MNIST,简单的旋转和缩放就很有用。我们可以使用Keras的ImageDataGenerator。
from tensorflow.keras.preprocessing.image import ImageDataGenerator # 创建数据增强生成器 datagen = ImageDataGenerator( rotation_range=10, # 随机旋转角度范围(度) zoom_range=0.1, # 随机缩放范围 width_shift_range=0.1, # 随机水平平移范围(比例) height_shift_range=0.1, # 随机垂直平移范围(比例) ) # 注意:数据增强只应用于训练集!测试集必须保持原始状态。 # 使用生成器来训练模型 model_cnn_aug = models.Sequential([...]) # 构建一个和之前一样的CNN模型 model_cnn_aug.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # fit_generator 在较新版本中已整合进fit,我们可以这样使用 train_generator = datagen.flow(train_images_cnn, train_labels, batch_size=64) history_aug = model_cnn_aug.fit(train_generator, steps_per_epoch=len(train_images_cnn) // 64, # 每个epoch的步数 epochs=15, # 可以适当增加epoch validation_data=(test_images_cnn, test_labels)) # 验证集用原始测试集使用数据增强后,你可能会发现训练速度变慢(因为每个epoch都在处理不同的增强图像),但模型的验证准确率会更稳定,过拟合现象得到缓解,在测试集上的最终表现也可能有轻微提升(例如从99.0%到99.2%),更重要的是,模型对输入图像的小变化会更鲁棒。
6.2 超参数调优:寻找更优配置
我们之前用的都是默认参数(如学习率、批大小、网络层数、神经元数量)。系统地调整这些超参数,可能找到更好的模型配置。手动调参费时费力,但有一些基本思路:
- 学习率:这是最重要的超参数之一。太大可能导致训练不稳定(损失震荡),太小则收敛缓慢。可以尝试
[0.001, 0.0005, 0.0001]。Adam优化器有自适应学习率,但初始学习率仍有影响。 - 批大小:影响梯度估计的噪声和内存使用。更小的批次(如32)可能带来更好的泛化,但训练更慢;更大的批次(如128、256)训练更快,但可能收敛到尖锐的极小值。可以尝试
[32, 64, 128]。 - 网络结构:增加卷积层或全连接层的深度和宽度(更多神经元/卷积核)可以增加模型容量,但也更容易过拟合。需要在模型复杂度和数据量之间取得平衡。对于MNIST,我们用的两层卷积+两层全连接已经足够。
- Dropout率:丢弃率是防止过拟合的有效正则化手段。可以尝试
[0.3, 0.5, 0.7]。通常在全连接层后使用。
一个简单的调参方法是使用循环进行网格搜索,但更高效的方法是使用Keras Tuner或Optuna这类自动化超参数优化工具,不过这属于进阶内容。
6.3 模型保存、加载与应用
训练一个好的模型需要时间和算力,我们必须学会保存它,以便后续使用或部署。
保存整个模型(推荐):这种方式保存了模型结构、权重和优化器状态,可以完全恢复训练或直接预测。
# 保存模型 model_cnn.save('my_mnist_cnn_model.keras') # 推荐使用 .keras 格式(TensorFlow 2.16+) # 或使用旧版H5格式: model_cnn.save('my_mnist_cnn_model.h5') # 加载模型 loaded_model = tf.keras.models.load_model('my_mnist_cnn_model.keras')保存模型权重:只保存参数,不保存结构。加载时需要先构建一模一样的模型结构。
# 保存权重 model_cnn.save_weights('my_mnist_cnn_weights.weights.h5') # 假设我们有一个结构相同的模型 `new_model` new_model = models.Sequential([...]) # 构建与model_cnn结构相同的模型 new_model.compile(...) # 加载权重 new_model.load_weights('my_mnist_cnn_weights.weights.h5')使用模型进行单张图片预测:这是最终目的。假设你有一张自己手写的数字图片my_digit.png,需要先进行与训练时相同的预处理。
from PIL import Image import numpy as np def predict_my_digit(image_path, model): # 1. 打开图片并转换为灰度 img = Image.open(image_path).convert('L') # 2. 调整大小为28x28像素(与MNIST一致) img = img.resize((28, 28)) # 3. 转换为NumPy数组并反色(如果背景是白色) img_array = np.array(img) # MNIST是黑底白字,如果你的图片是白底黑字,需要反色 # 假设你的图片是白底黑字: img_array = 255 - img_array # 4. 标准化 img_array = img_array / 255.0 # 5. 调整维度以匹配模型输入 (1, 28, 28, 1) img_array = img_array.reshape(1, 28, 28, 1) # 6. 预测 predictions = model.predict(img_array) # 7. 获取预测结果 predicted_digit = np.argmax(predictions[0]) confidence = np.max(predictions[0]) return predicted_digit, confidence # 使用加载的模型进行预测 digit, conf = predict_my_digit('my_digit.png', loaded_model) print(f'预测数字是: {digit}, 置信度: {conf:.2%}')这个过程涵盖了从环境搭建、数据处理、模型构建(全连接网络与CNN)、训练评估、可视化到优化部署的完整链路。每个步骤我都穿插了实际编码中容易遇到的问题和我的处理经验,比如环境隔离的重要性、数据预处理的细节、CNN相对于全连接网络的本质优势、以及如何通过数据增强和超参数微调来提升模型鲁棒性。
本文还有配套的精品资源,点击获取