目录
[1. 加载 MNIST 数据集](#1. 加载 MNIST 数据集)
[2. 数据预处理(归一化)](#2. 数据预处理(归一化))
[3. 构建模型](#3. 构建模型)
[4. 编译模型](#4. 编译模型)
[5. 训练模型](#5. 训练模型)
[6. 保存模型](#6. 保存模型)
文章灵感
之前写了那么多使用第三方库,比如OpenCV、YOLO等这些已经训练好的模型来实现一些人工智能的代码,我一直在心中有一个蠢蠢欲动的想法,就是我想学会自己训练模型,即使这个想法听起来是很疯狂的吧,也很有难度,在网络上也很少有人系统的教学如何训练这么个模型,所以我的计划是慢慢的看一些模型训练框架的官方文档,慢慢理解。
我计划是使用TensorFlow,他相比与P开头的另一个好东西来说,更加的容易入门,那么话不多说进入正题吧。
快速入门
https://www.tensorflow.org/tutorials/quickstart/beginner?hl=zh-cn
上面的链接就是这篇文章下面代码的灵感来源了,当然由于是国外的网站,访问起来有点难度,我会把他的源码放在下面,并且源码也会放上详细的注释。
代码解释
其实这段代码运行完毕之后是没有任何比较直观的效果的,只有训练过程中的一些例如损失函数之类的数值 ,我们也可以讲讲他们都是干什么的。
快速入门代码
使用Keras API来加载MNIST数据集、构建一个简单的神经网络模型、训练这个模型、评估其性能,并最终将其修改为返回概率而非原始的logits。下面是对每个关键部分的详细讲解:
加载数据集
- 使用
tf.keras.datasets.mnist
加载MNIST数据集,这是一个包含手写数字(0-9)的图像集合,每张图像大小为28x28像素。 - 数据集被分为训练集和测试集,并且图像数据(
x_train
,x_test
)被归一化到0-1之间,这是通过除以255(图像的最大像素值)来实现的。归一化有助于模型的训练。
构建机器学习模型
- 使用
tf.keras.models.Sequential
构建了一个顺序模型,该模型通过堆叠层来定义。 - 第一层是
Flatten
层,它将图像的二维输入(28x28像素)转换为一维(784像素)的,以便可以被全连接层处理。 - 接着是两个
Dense
层,第一个是包含128个节点的隐藏层,使用ReLU激活函数;第二个是输出层,包含10个节点(对应于10个类别),但没有激活函数,因为我们将在计算损失时直接对这些logits应用softmax。 - 为了防止过拟合,在第一个
Dense
层后添加了一个Dropout
层,它在训练过程中随机丢弃(设置为0)一部分神经元的输出。
损失函数和编译模型
- 使用
tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
定义了损失函数,它适用于多类别分类问题,并假设标签是整数(而不是one-hot编码)。from_logits=True
表示模型输出的是logits,而不是概率。 - 使用
model.compile
编译模型,指定优化器(这里使用Adam)、损失函数和评估指标(准确率)。
训练并评估模型
- 使用
model.fit
训练模型,通过多次迭代(epochs)在训练数据上调整模型的参数。 - 使用
model.evaluate
在测试集上评估模型的性能,打印出损失值和准确率。
修改模型以返回概率
- 创建一个新的
tf.keras.Sequential
模型probability_model
,它将训练好的模型和Softmax
层封装在一起。Softmax
层将logits转换为概率分布。 - 现在,当
probability_model
对输入数据进行预测时,它将返回每个类别的概率,而不是原始的logits。
python
'''
设置 TensorFlow
'''
import tensorflow as tf
'''
加载数据集
'''
# 加载并准备 MNIST 数据集。将样本数据从整数转换为浮点数:
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
'''
构建机器学习模型
'''
# 通过堆叠层来构建 tf.keras.Sequential 模型
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)
])
# 对于每个样本,模型都会返回一个包含 logits 或 log-odds 分数的向量,每个类一个
predictions = model(x_train[:1]).numpy()
predictions
# tf.nn.softmax 函数将这些 logits 转换为每个类的概率
# 注意:注:可以将 tf.nn.softmax 烘焙到网络最后一层的激活函数中。
# 虽然这可以使模型输出更易解释,但不建议使用这种方式,因为在使用 softmax 输出时不可能为所有模型提供精确且数值稳定的损失计算。
tf.nn.softmax(predictions).numpy()
# 使用 losses.SparseCategoricalCrossentropy 为训练定义损失函数,
# 它会接受 logits 向量和 True 索引,并为每个样本返回一个标量损失
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
# 此损失等于 true 类的负对数概率:如果模型确定类正确,则损失为零。
# 这个未经训练的模型给出的概率接近随机(每个类为 1/10),因此初始损失应该接近 -tf.math.log(1/10) ~= 2.3。
loss_fn(y_train[:1], predictions).numpy()
# 在开始训练之前,使用 Keras Model.compile 配置和编译模型。
# 将 optimizer 类设置为 adam,将 loss 设置为我们之前定义的 loss_fn 函数,
# 并通过将 metrics 参数设置为 accuracy 来指定要为模型评估的指标。
model.compile(optimizer='adam',
loss=loss_fn,
metrics=['accuracy'])
'''
训练并评估模型
'''
# 使用 Model.fit 方法调整我们的模型参数并最小化损失
model.fit(x_train, y_train, epochs=5)
# Model.evaluate 方法通常在 "Validation-set" 或 "Test-set" 上检查模型性能
model.evaluate(x_test, y_test, verbose=2)
# 现在,这个照片分类器的准确度已经达到 98%。想要了解更多,请阅读 TensorFlow 教程。
# 如果您想让模型返回概率,可以封装经过训练的模型,并将 softmax 附加到该模型:
probability_model = tf.keras.Sequential([
model,
tf.keras.layers.Softmax()
])
probability_model(x_test[:5])
'''
总结
恭喜!您已经利用 Keras API 借助预构建数据集训练了一个机器学习模型。
'''
运行效果解释
正如我刚才所说的结果,这段代码运行完成之后的效果并不明显,输出了一堆数字对吧,我来解释一下。
Epoch 1/5
1875/1875 [==============================] - 3s 1ms/step - loss: 0.2943 - accuracy: 0.9153
Epoch 2/5
1875/1875 [==============================] - 2s 1ms/step - loss: 0.1408 - accuracy: 0.9583
Epoch 3/5
1875/1875 [==============================] - 2s 1ms/step - loss: 0.1051 - accuracy: 0.9678
Epoch 4/5
1875/1875 [==============================] - 2s 1ms/step - loss: 0.0860 - accuracy: 0.9734
Epoch 5/5
1875/1875 [==============================] - 2s 1ms/step - loss: 0.0745 - accuracy: 0.9764
313/313 - 0s - loss: 0.0728 - accuracy: 0.9764 - 405ms/epoch - 1ms/step
Process finished with exit code 0
这是它输出的东西。
- 我们的模型进行了5个epoch的训练。每个epoch代表整个训练数据集通过模型一次。
- 每个epoch完成后,都会输出当前epoch的loss(损失值)和accuracy(准确率)。loss值越小,表示模型预测值与实际值之间的差异越小 ;accuracy值越高,表示模型分类的正确率越高。
- 随着epoch的增加,loss值逐渐降低,accuracy值逐渐提高,这表明模型正在学习并改进其预测能力。
- 最后一个epoch结束时,模型的loss为0.0745,accuracy为0.9764。这表示模型在训练集上的性能相当不错,能够以很高的准确率进行分类。
- 随后,模型在测试集(或验证集,这里未明确区分)上的评估结果显示,loss为0.0728,accuracy为0.9764,与训练集上的表现相近,这表明模型可能没有过拟合或仅轻微过拟合。
- 每个epoch大约需要2秒完成,每个step(即每个数据点的处理)大约需要1毫秒。
- 在测试集上的评估非常快,只用了405毫秒/epoch,即几乎瞬间完成。
这些就是这些输出的参数的含义,在以后会经常的遇到。
我的修改
为了改善两个事情:
(1)模型我都训练好了,我为什么不保存下来
(2)我想识别一下我本地的文件不可以吗
所以我修改了原先的代码,我们可以首先将模型保存下来如下。
保存我们训练好的模型
代码解释
1. 加载 MNIST 数据集
代码首先通过TensorFlow的Keras API加载MNIST手写数字数据集。MNIST是一个广泛使用的数据集,包含60,000个训练样本和10,000个测试样本,每个样本都是28x28像素的灰度图像,表示一个手写数字(0到9之间)。加载的数据集被分为两部分:训练集(用于训练模型)和测试集(用于评估模型性能)。
2. 数据预处理(归一化)
接下来,代码对训练集和测试集中的图像数据进行归一化处理。归一化是指将图像的像素值从原始的[0, 255]范围缩放到[0, 1]范围。这是因为神经网络在训练过程中,对于较小的输入值通常能够更快地收敛,并且可以提高模型的性能。归一化有助于减少不同特征之间的尺度差异,使得模型能够更有效地学习。
3. 构建模型
然后,代码使用Keras的Sequential API构建了一个简单的神经网络模型。这个模型是一个多层感知机(MLP),由多个全连接层(Dense层)组成。第一层是Flatten层,它将图像的二维形状(28x28)转换为一维(784),因为全连接层需要一维输入。接下来的层是Dense层,包含一定数量的神经元和激活函数(如ReLU),用于学习数据的非线性特征。为了防止过拟合,模型中还包含了一个Dropout层,它在训练过程中随机丢弃一部分神经元的输出。最后,模型以一个没有激活函数的Dense层结束,其输出维度等于类别数(10),因为这是一个多分类问题(0到9的数字)。
4. 编译模型
在训练模型之前,需要编译模型,指定损失函数、优化器和评估指标。对于多分类问题,常用的损失函数是交叉熵损失(这里使用的是SparseCategoricalCrossentropy,因为它适用于多分类且标签是整数的情况)。优化器负责更新模型的权重以最小化损失函数。这里使用了Adam优化器,因为它能够自动调整学习率,适用于大多数情况。评估指标是准确率(accuracy),用于衡量模型在测试集上的性能。
5. 训练模型
使用训练数据对模型进行训练,通过多次迭代(epochs)来更新模型的权重。在每个epoch结束时,可以使用验证集(这里使用的是测试集,尽管在实际应用中通常会保留一个单独的验证集)来评估模型的性能。验证集的结果可以帮助我们了解模型是否过拟合或欠拟合,并相应地调整模型的结构或训练参数。
6. 保存模型
最后,将训练好的模型保存为文件,以便将来使用。这里使用了HDF5格式(文件扩展名为.h5),它是一种用于存储和组织大量数据的文件格式,能够很好地保存Keras模型的结构和权重。保存模型后,可以在需要时重新加载它,进行预测或进一步训练。
python
import tensorflow as tf
# 加载 MNIST 数据集
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 归一化,将图像像素值从 0-255 缩放到 0-1
x_train, x_test = x_train / 255.0, x_test / 255.0
# 构建模型
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)), # 将28x28图像展平为一维
tf.keras.layers.Dense(128, activation='relu'), # 全连接层,128个神经元,使用ReLU激活函数
tf.keras.layers.Dropout(0.2), # Dropout层,防止过拟合
tf.keras.layers.Dense(10) # 输出层,10个类别,对应数字0-9
])
# 定义损失函数和优化器
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
model.compile(optimizer='adam', loss=loss_fn, metrics=['accuracy'])
# 训练模型
model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))
# 保存模型为 HDF5 格式文件
model.save('models/my_mnist_model.h5')
运行完毕之后会得到这么个模型文件。
识别我们本地的图片
既然我们的模型已经训练完毕并且成功的保存下来了,那我们为什么不用我们本地的图片来测验一下呢。
代码解释
-
加载模型 :
使用
tf.keras.models.load_model
函数从指定路径('models/my_mnist_model.h5'
)加载预训练的模型。这个模型应该是一个能够识别MNIST手写数字的神经网络。 -
创建概率模型 :
通过
tf.keras.Sequential
创建一个新的模型,该模型在加载的模型之后添加了一个Softmax
层。这个Softmax
层将模型的原始输出(即对数概率)转换为概率分布,使得每个类别的预测概率都在0到1之间,并且所有类别的概率之和为1。然而,需要注意的是,在大多数情况下,如果模型的最后一层没有使用softmax
激活函数,但设置了from_logits=True
在损失函数中,那么在预测时通常不需要额外添加Softmax
层,因为predict
方法已经默认返回了概率分布(如果模型是以这种方式训练的)。不过,这里添加Softmax
层可能是为了明确展示概率转换的过程或者确保兼容性。 -
设置中文字体 :
为了在图表中显示中文字符,需要指定一个支持中文的字体文件路径。这里提供了Windows系统的示例路径(
'C:/Windows/Fonts/simhei.ttf'
),并使用了matplotlib.font_manager.FontProperties
来加载这个字体。这样,在图表中使用fontproperties=my_font
参数就可以显示中文了。 -
预测函数 :
定义了一个
predict_single_image
函数,该函数接受一个图像路径作为输入,加载该图像,将其转换为灰度图并调整大小为28x28像素(与MNIST数据集中的图像大小一致),然后将图像数据归一化到0-1之间。之后,将归一化后的图像数据扩展为四维张量(因为模型期望的输入是四维的,通常是[batch_size, height, width, channels]),并使用加载的模型进行预测。最后,使用np.argmax
获取概率最高的类别的索引,作为预测结果,并使用Matplotlib显示原始图像和预测结果。 -
使用模型进行预测 :
调用
predict_single_image
函数,传入一个包含手写数字图像的路径(在这个例子中是'./imgs/001.jpg'
),然后函数将显示该图像以及模型预测的数字。
注意:
'models/my_mnist_model.h5'
路径下的模型文件存在且已经完成训练。'C:/Windows/Fonts/simhei.ttf'
是存在的(不然显示不了中文啊┭┮﹏┭┮)'./imgs/001.jpg'
路径下的图像文件存在,而且图像中包含一个清晰的手写数字。
python
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import matplotlib.font_manager as fm # 导入字体管理器
# 从文件加载模型
loaded_model = tf.keras.models.load_model('models/my_mnist_model.h5')
# 将 softmax 包装进模型以便输出概率
probability_model = tf.keras.Sequential([
loaded_model,
tf.keras.layers.Softmax()
])
# 设置中文字体,假设你有 SimHei 字体(黑体),路径可能需要根据系统调整
# 可以将 font_path 设置为你系统中的字体文件路径
# font_path = '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc' # Linux 示例
font_path = 'C:/Windows/Fonts/simhei.ttf' # Windows 示例
# 加载中文字体
my_font = fm.FontProperties(fname=font_path)
# 处理图片并进行预测
def predict_single_image(image_path):
# 加载图片
img = Image.open(image_path).convert('L') # 将图片转换为灰度模式 ('L')
img = img.resize((28, 28)) # 调整图片大小为28x28像素
img_array = np.array(img) / 255.0 # 将图片像素值归一化到0-1之间
# 预测图片
img_array_expanded = np.expand_dims(img_array, 0) # 扩展为4D张量 (1, 28, 28)
prediction = probability_model(img_array_expanded)
predicted_label = np.argmax(prediction) # 获取预测结果
# 显示图片和预测结果
plt.figure(figsize=(3, 3))
plt.imshow(img_array, cmap='gray')
plt.title(f"预测值: {predicted_label}", fontproperties=my_font) # 使用中文字体显示预测结果
plt.axis('off') # 不显示坐标轴
plt.show()
# 使用模型识别指定路径的图片
predict_single_image('./imgs/001.jpg') # 使用本地图片进行识别
效果展示
总结
入门就是这么个如法,之后我在继续的研究,入门容易坚持难,看我是否可以成功的坚持下来吧,其实最近的事情还是挺多的,唉,加油吧