全部文章

11.神经网络案例-mnist手写数字识别

 

使用手写数字的MNIST数据集如上图所示,该数据集包含60,000个用于训练的样本和10,000个用于测试的样本,图像是固定大小(28x28像素),其值为0到255。

📊 MNIST 数据集基本信息

数据集 样本数 图像大小 数据类型
训练集 60,000 28×28 像素 uint8 (0-255)
测试集 10,000 28×28 像素 uint8 (0-255)
标签范围 0-9 (手写数字)    
整个案例的实现流程是:
  1. 数据加载
  2. 数据处理
  3. 模型构建
  4. 模型训练
  5. 模型测试
  6. 模型保存

首先要导入所需的工具包:

import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize']=(7,7)
# 数据集
from tensorflow.keras.datasets import mnist
# 构建序列模型
from tensorflow.keras import Sequential
# 导入需要的层
from tensorflow.keras.layers import Dense,Input,Activation,Dropout,BatchNormalization
# 导入辅助工具包
from tensorflow.keras import utils
# 正则化
from tensorflow.keras import regularizers

数据加载与查看

# 数据加载
# 首先加载手写数字图像
nb_class=10
# 加载数据集
# 正确写法:获取两个元组并解包
(x_train, y_train), (x_test, y_test)=mnist.load_data()#注意:mnist.load_data() 函数返回的是一个包含 ​​两个元组​​ 的结构:(x_train, y_train), (x_test, y_test)
x_train.shape,y_train.shape,x_test.shape, y_test.shape#((60000, 28, 28), (60000,), (10000, 28, 28), (10000,))

 数据处理

 神经网络中的每个训练样本是一个向量,因此需要对输入进行重塑,使每个28x28的图像成为一个784维的向量。另外,将输入数据进行归一化处理,从0-255调整到0-1。

 可以对比参照:https://www.cnblogs.com/luzhanshi/articles/18924883中的数据集格式

 

特征值处理:

# 调整数据维度,每一个数字转化为一个向量
x_train=x_train.reshape(60000,784)
x_test=x_test.reshape(10000,784)
# 格式转换
x_train=x_train.astype(np.float32)
x_test=x_test.astype(np.float32)
# 归一化
x_train / = 255
x_test / = 255
# 查看处理后数据
x_train.shape,x_test.shape#((60000, 784), (10000, 784))

 另外对于目标值我们也需要进行处理,将其转换为热编码的形式:

代码实现:

y_train=utils.to_categorical(y_train,nb_class)
y_test=utils.to_categorical(y_test,nb_class)
y_train.shape,y_test.shape#((60000, 10), (10000, 10))
y_train[0:5]
array([[0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])

 模型构建

这里我们只构建有3层全连接的网络来进行处理

 构建方法如下所示:

#利用模型序列来构建模型
model=Sequential()
# 输入层,输入维度大小为784
model.add(Input(shape=(784,)))
# 全连接层1,共512个神经元,
model.add(Dense(512))
# 添加激活函数
model.add(Activation('relu'))
# 使用正则化方法Dropout,随机失活
model.add(Dropout(0.2))
# 全连接层2,共512个神经元,并加入l2正则化
model.add(Dense(512,kernel_regularizer=regularizers.l2(0.02)))
# BN层
model.add(BatchNormalization())
# 激活函数
model.add(Activation('relu'))
# 使用正则化方法Dropout,随机失活
model.add(Dropout(0.2))
# 输出层,共10个神经元(对应10种分类结果)
model.add(Dense(10))
# softmax将神经网络输出的score转化为概率值
model.add(Activation('softmax'))
model.summary()
Model: "sequential_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense_1 (Dense)                      │ (None, 512)                 │         401,920 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ activation_1 (Activation)            │ (None, 512)                 │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_1 (Dropout)                  │ (None, 512)                 │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense)                      │ (None, 512)                 │         262,656 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ batch_normalization                  │ (None, 512)                 │           2,048 │
│ (BatchNormalization)                 │                             │                 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ activation_2 (Activation)            │ (None, 512)                 │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_2 (Dropout)                  │ (None, 512)                 │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_3 (Dense)                      │ (None, 10)                  │           5,130 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ activation_3 (Activation)            │ (None, 10)                  │               0 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 671,754 (2.56 MB)
 Trainable params: 670,730 (2.56 MB)
 Non-trainable params: 1,024 (4.00 KB)

模型编译

设置模型训练使用的损失函数交叉熵损失和优化方法adam,损失函数用来衡量预测值与真实值之间的差异,优化器用来使用损失函数达到最优:

# 模型编译:指定损失函数、优化器、评估方法
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
# 模型编译:等价写法
model.compile(loss=tf.keras.losses.categorical_crossentropy,optimizer=tf.keras.optimizers.Adam(),metrics=tf.keras.metrics.Accuracy())

模型训练

# batch_size:每次送入模型的参数,epochs:所有样本的迭代次数,validation_data:指定验证数据集
history=model.fit(x_train,y_train,batch_size=128,epochs=4,verbose=1,validation_data=(x_test,y_test))

训练过程如下:

Epoch 1/4
469/469 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.8736 - loss: 3.1170 - val_accuracy: 0.9580 - val_loss: 0.3342
Epoch 2/4
469/469 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9551 - loss: 0.2863 - val_accuracy: 0.9679 - val_loss: 0.2355
Epoch 3/4
469/469 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.9641 - loss: 0.2422 - val_accuracy: 0.9652 - val_loss: 0.2489
Epoch 4/4
469/469 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9683 - loss: 0.2208 - val_accuracy: 0.9723 - val_loss: 0.2199

打印history数据:

 history.history
{'accuracy': [0.9212333559989929,0.9562000036239624,0.9629833102226257,0.9670666456222534],
 'loss': [1.230934739112854,0.2810131907463074,0.24591435492038727,0.22761961817741394],
 'val_accuracy': [0.9580000042915344,0.9678999781608582,0.9652000069618225,0.9722999930381775],
 'val_loss': [0.33420270681381226,0.23554934561252594,0.24887068569660187,0.21987192332744598]}

将损失绘制成曲线:

#绘制损失函数变化曲线
plt.figure()
# 绘制训练集的损失
plt.plot(history.history['loss'],label='train_loss')
#绘制测试集的损失
plt.plot(history.history['val_loss'],label='val_loss')
plt.legend()
plt.grid()
plt.show()

 将训练的准确率绘制成曲线:

#绘制准确率变化曲线
plt.figure()
#训练集准确率
plt.plot(history.history['accuracy'],label='train_loss')
#测试集准确率
plt.plot(history.history['val_accuracy'],label='val_loss')
plt.legend()
plt.grid()
plt.show()

tensorboard监控训练过程

另外可通过tensorboard监控训练过程,这时我们指定回调函数:

# 添加TensorBoard观察
tensorboard=tf.keras.callbacks.TensorBoard(log_dir= r'D:\data\jupyter\Test',histogram_freq=1,write_graph=True,write_images=True)
'''注意:log_dir路径,要求全英文,否则可能会报错'''

再进行训练:

# 模型训练,并指定TensorBoard
history=model.fit(x_train,y_train,batch_size=128,epochs=4,verbose=1,validation_data=(x_test,y_test),callbacks=[tensorboard])

打开终端,进入log_dir指定的目录(D:\data\jupyter\Test),执行命令:tensorboard --logdir "D:/data/jupyter/Test/TensorBoard"

D:\data\jupyter\Test\TensorBoard>tensorboard --logdir "D:/data/jupyter/Test/TensorBoard"
2025-06-28 18:56:53.727954: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.
2025-06-28 18:56:54.648216: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.
Serving TensorBoard on localhost; to expose to the network, use a proxy or pass --bind_all
TensorBoard 2.18.0 at http://localhost:6006/ (Press CTRL+C to quit)

注意:如果遇到报错,极有可能是TensorBoard 已安装(版本 2.18.0),但系统无法找到可执行文件。这是因为 Python 用户安装目录没有添加到系统 PATH 中。

 永久解决方案:添加环境变量

  1. ​手动添加环境变量​​:

    • Win + R → sysdm.cpl → 高级 → 环境变量
    • 在"系统变量"找到 Path → 编辑
    • 新建一项:C:\Users\luzhanshi\AppData\Roaming\Python\Python312\Scripts
    • 确定保存所有更改
  2. ​通过命令提示符添加​​:

    setx PATH "%PATH%;C:\Users\luzhanshi\AppData\Roaming\Python\Python312\Scripts"
     

在浏览器中打开上面指定的网址,可以查看损失函数和准确率的变化,图结构等。

 

 模型评估

#模型测试
score=model.evaluate(x_test,y_test,verbose=1)
# 打印结果
print(score)
313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.9736 - loss: 0.2011
[0.18996557593345642, 0.9754999876022339]

 模型保存

#保存模型架构与权重到h5文件中
model.save('./mnist_model.h5')
#加载模型,包括架构与权重
model=tf.keras.models.load_model('./mnist_model.h5')
model.summary()

上述代码可以保存成功,但是会有警告:

WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. 
WARNING:absl:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.

解决模型保存为HDF5格式时出现警告的问题

 这个警告是TensorFlow/Keras在鼓励您使用新的模型保存格式(.keras)替代传统的HDF5格式(.h5)。

原因分析

  1. ​Keras格式(.keras)是新的默认格式​

    • 这是Keras 3.x的新功能,也被集成到TensorFlow 2.12+版本中
    • HDF5格式(.h5)已被标记为"legacy"(传统格式)
  2. ​推荐使用新格式的优势​​:

    • 更简洁的单一文件格式(HDF5实际是复杂的容器格式)
    • 更可靠地保存和恢复模型配置
    • 更好的向前兼容性
    • 支持保存所有自定义对象(层、损失函数等)而不需要custom_objects参数

解决方案

1. 使用新的Keras格式保存模型(推荐)

# 使用新格式保存
model.save('./mnist_model.keras')
# 加载模型
loaded_model = tf.keras.models.load_model('./mnist_model.keras')

2. 保存为TensorFlow标准格式

tf.saved_model.save(model, r"D:\data\jupyter\Test\model_save")  # ✅ 明确使用 tf.saved_model 模块
# 加载模型
loaded_model = tf.saved_model.load(r'D:\data\jupyter\Test\model_save')

此方法多次尝试,各种报错,做个了解即可

 

格式对比表

格式 扩展名 特点 适用场景
​Keras格式​ .keras 单一文件,轻量简单,推荐新项目 所有新项目
​TensorFlow SavedModel​ 无特定扩展名(目录) 包含完整资源,可部署 TensorFlow Serving, TensorFlow Lite
​HDF5 (Legacy)​ .h5 复杂容器格式,向后兼容 需要兼容旧代码的场景

迁移建议

​新项目​​:始终使用.keras格式

 ​​需要部署的场景​​:使用SavedModel格式

​共享模型给使用旧TF版本的用户​​:同时保存两种格式

# 保存新格式
model.save('latest_model.keras')

# 保存旧格式用于兼容
with keras.suppress_saved_model_warnings():
    model.save('compatible_model.h5')

何时必须使用HDF5格式

虽然不推荐,但在以下场景仍需使用HDF5:

  • 需要支持TensorFlow 2.11或更早版本
  • 需要与旧版Keras或PyTorch等框架交互
  • 需要在某些不支持新格式的库中使用(如某些可视化工具)

 

posted @ 2025-06-29 10:19  指尖下的世界  阅读(189)  评论(0)    收藏  举报