【深度学习-Day 23】框架实战:模型训练与评估核心环节详解 (MNIST实战)
Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来
Python系列文章目录
PyTorch系列文章目录
机器学习系列文章目录
深度学习系列文章目录
Java系列文章目录
JavaScript系列文章目录
深度学习系列文章目录
01-【深度学习-Day 1】为什么深度学习是未来?一探究竟AI、ML、DL关系与应用
02-【深度学习-Day 2】图解线性代数:从标量到张量,理解深度学习的数据表示与运算
03-【深度学习-Day 3】搞懂微积分关键:导数、偏导数、链式法则与梯度详解
04-【深度学习-Day 4】掌握深度学习的“概率”视角:基础概念与应用解析
05-【深度学习-Day 5】Python 快速入门:深度学习的“瑞士军刀”实战指南
06-【深度学习-Day 6】掌握 NumPy:ndarray 创建、索引、运算与性能优化指南
07-【深度学习-Day 7】精通Pandas:从Series、DataFrame入门到数据清洗实战
08-【深度学习-Day 8】让数据说话:Python 可视化双雄 Matplotlib 与 Seaborn 教程
09-【深度学习-Day 9】机器学习核心概念入门:监督、无监督与强化学习全解析
10-【深度学习-Day 10】机器学习基石:从零入门线性回归与逻辑回归
11-【深度学习-Day 11】Scikit-learn实战:手把手教你完成鸢尾花分类项目
12-【深度学习-Day 12】从零认识神经网络:感知器原理、实现与局限性深度剖析
13-【深度学习-Day 13】激活函数选型指南:一文搞懂Sigmoid、Tanh、ReLU、Softmax的核心原理与应用场景
14-【深度学习-Day 14】从零搭建你的第一个神经网络:多层感知器(MLP)详解
15-【深度学习-Day 15】告别“盲猜”:一文读懂深度学习损失函数
16-【深度学习-Day 16】梯度下降法 - 如何让模型自动变聪明?
17-【深度学习-Day 17】神经网络的心脏:反向传播算法全解析
18-【深度学习-Day 18】从SGD到Adam:深度学习优化器进阶指南与实战选择
19-【深度学习-Day 19】入门必读:全面解析 TensorFlow 与 PyTorch 的核心差异与选择指南
20-【深度学习-Day 20】PyTorch入门:核心数据结构张量(Tensor)详解与操作
21-【深度学习-Day 21】框架入门:神经网络模型构建核心指南 (Keras & PyTorch)
22-【深度学习-Day 22】框架入门:告别数据瓶颈 - 掌握PyTorch Dataset、DataLoader与TensorFlow tf.data实战
23-【深度学习-Day 23】框架实战:模型训练与评估核心环节详解 (MNIST实战)
文章目录
- Langchain系列文章目录
- Python系列文章目录
- PyTorch系列文章目录
- 机器学习系列文章目录
- 深度学习系列文章目录
- Java系列文章目录
- JavaScript系列文章目录
- 深度学习系列文章目录
- 前言
- 一、准备工作:定义损失函数与优化器
- 1.1 为何需要损失函数与优化器?
- 1.2 选择合适的损失函数
- 1.2.1 回归任务
- 1.2.2 分类任务
- 1.3 选择合适的优化器
- 二、核心引擎:详解模型训练循环
- 2.1 训练循环的五个关键步骤
- 2.2 步骤详解与代码示例
- 2.2.1 前向传播:获取模型预测
- 2.2.2 计算损失:量化预测误差
- 2.2.3 梯度清零:为新一轮计算做准备
- 2.2.4 反向传播:计算损失函数对参数的梯度
- 2.2.5 参数更新:向最优解迈进
- 2.3 迭代训练:Epoch 与 Batch
- 三、公正的裁判:模型评估
- 3.1 为何需要评估模式?
- 3.2 评估流程
- 3.3 常用评估指标
- 3.3.1 分类任务
- 3.3.2 回归任务
- 3.4 代码示例:计算准确率
- 四、实战演练:使用 PyTorch 训练 MNIST 分类模型
- 4.1 准备工作
- 4.2 加载并预处理 MNIST 数据集
- 4.3 定义一个简单的 MLP 模型
- 4.4 定义损失函数和优化器
- 4.5 编写训练循环
- 4.6 编写评估函数
- 4.7 执行训练与评估
- 五、常见问题与进一步思考
- 5.1 损失不下降怎么办?
- 5.2 如何进一步提升模型性能?
- 5.3 训练过程的可视化
- 六、总结
前言
在前面的系列文章中,我们已经逐步学习了深度学习框架的基础知识,包括张量的操作(Day 20)、如何使用框架构建神经网络模型(Day 21),以及如何高效地加载和处理数据(Day 22)。这些知识点如同建造房屋的砖瓦和工具,现在,是时候将它们整合起来,搭建并运行我们深度学习项目的“引擎”——模型训练与评估循环。
本篇文章将聚焦于使用深度学习框架(以 PyTorch 为例,但核心思想同样适用于 TensorFlow/Keras 等)进行模型训练和评估的完整流程。我们将深入探讨训练循环的每一个关键步骤,理解评估模式的重要性,并学习如何计算和解读常用的评估指标。最终,我们将通过一个经典的 MNIST 手写数字识别实战案例,将所有理论知识付诸实践。学完本篇,您将能够独立编写并执行一个完整的模型训练与评估流程,为后续更复杂的项目打下坚实的基础。
一、准备工作:定义损失函数与优化器
在正式开始训练循环之前,我们需要明确两件重要的事情:如何衡量模型的预测与真实标签之间的差异(损失函数),以及如何根据这个差异来调整模型的参数以使其表现更好(优化器)。
1.1 为何需要损失函数与优化器?
- 损失函数 (Loss Function):它是一个数学函数,用于量化模型预测结果与真实目标值之间的差距。我们的目标是在训练过程中最小化这个损失值。
- 优化器 (Optimizer):它实现了特定的算法(如梯度下降、Adam等),根据损失函数计算出的梯度来更新模型的权重和偏置,从而逐步减小损失值。
选择合适的损失函数和优化器对于模型的训练效果至关重要。
1.2 选择合适的损失函数
深度学习框架通常内置了多种常用的损失函数,我们可以根据任务类型进行选择。
1.2.1 回归任务
对于预测连续值的回归任务,常用的损失函数是:
- 均方误差损失 (Mean Squared Error Loss, MSELoss):计算预测值与真实值之差的平方的均值。
- PyTorch:
torch.nn.MSELoss()
- TensorFlow/Keras:
tf.keras.losses.MeanSquaredError()
- PyTorch:
1.2.2 分类任务
对于预测离散类别标签的分类任务,常用的损失函数是:
- 交叉熵损失 (Cross-Entropy Loss):在分类问题中非常常用。
- 对于二分类问题,通常使用二元交叉熵损失 (Binary Cross-Entropy Loss)。
- PyTorch:
torch.nn.BCELoss()
(通常需要配合 Sigmoid 输出) 或torch.nn.BCEWithLogitsLoss()
(内部集成了 Sigmoid,更稳定) - TensorFlow/Keras:
tf.keras.losses.BinaryCrossentropy()
- PyTorch:
- 对于多分类问题,通常使用分类交叉熵损失 (Categorical Cross-Entropy Loss)。
- PyTorch:
torch.nn.CrossEntropyLoss()
(内部集成了 LogSoftmax 和 NLLLoss,期望输入为原始 logits) - TensorFlow/Keras:
tf.keras.losses.CategoricalCrossentropy()
(期望one-hot编码的标签和softmax输出) 或tf.keras.losses.SparseCategoricalCrossentropy()
(期望整数编码的标签和softmax输出)
- PyTorch:
- 对于二分类问题,通常使用二元交叉熵损失 (Binary Cross-Entropy Loss)。
在我们的 MNIST 示例中,由于是多分类问题,我们将使用 torch.nn.CrossEntropyLoss()
。
# PyTorch 示例
import torch
import torch.nn as nn# 假设是多分类任务,模型输出10个类别的logits
criterion = nn.CrossEntropyLoss()# 示例:
# outputs = model(inputs) # 模型的原始输出 (logits)
# loss = criterion(outputs, labels) # labels 是真实的类别索引
1.3 选择合适的优化器
优化器的选择同样重要,它关系到模型收敛的速度和效果。
常见的优化器有:
- SGD (Stochastic Gradient Descent):随机梯度下降,可以配合动量 (momentum) 使用。
- Adam (Adaptive Moment Estimation):一种自适应学习率的优化算法,通常在各种任务中都有不错的表现,是目前比较常用的选择。
- RMSprop, Adagrad 等。
在 PyTorch 中,优化器通常在 torch.optim
模块下。在 TensorFlow/Keras 中,它们在 tf.keras.optimizers
模块下。
# PyTorch 示例
import torch.optim as optim# model 是我们定义的神经网络模型实例
# learning_rate 是学习率,一个重要的超参数
# optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = optim.Adam(model.parameters(), lr=0.001)
这里的 model.parameters()
会告诉优化器需要更新哪些参数。
二、核心引擎:详解模型训练循环
万事俱备,现在我们可以构建模型训练的核心循环了。训练循环本质上是一个迭代过程,在每个迭代(通常称为一个 epoch)中,模型会遍历整个训练数据集(或分批次遍历),并根据损失进行参数更新。
2.1 训练循环的五个关键步骤
一个典型的训练迭代(针对一个 batch 的数据)通常包含以下五个步骤:
- 前向传播 (Forward Propagation):将输入数据喂给模型,得到模型的预测输出。
- 计算损失 (Calculate Loss):使用预定义的损失函数,计算模型预测输出与真实标签之间的差异。
- 梯度清零 (Zero Gradients):在进行反向传播计算新的梯度之前,需要清除上一轮迭代中累积的梯度。
- 反向传播 (Backward Propagation):根据损失值,自动计算损失函数关于模型各个参数的梯度。
- 参数更新 (Parameter Update):优化器根据计算得到的梯度,更新模型的参数(权重和偏置)。
2.2 步骤详解与代码示例
假设我们已经有了模型 model
,损失函数 criterion
,优化器 optimizer
,以及一批输入数据 inputs
和对应的标签 labels
。
2.2.1 前向传播:获取模型预测
将输入数据传递给模型,模型会执行其内部定义的一系列运算(例如线性变换、激活函数等),最终输出预测结果。
# PyTorch 示例
# inputs 是当前批次的数据
outputs = model(inputs) # outputs 是模型对这批输入的预测结果
2.2.2 计算损失:量化预测误差
使用之前定义的损失函数,比较模型的预测 outputs
和真实的 labels
。
# PyTorch 示例
loss = criterion(outputs, labels) # loss 是一个标量,表示当前批次的平均损失
2.2.3 梯度清零:为新一轮计算做准备
在 PyTorch 中,梯度是会累积的(这在某些高级应用如 RNN 中有用)。但在大多数情况下,每次进行反向传播计算梯度之前,我们需要将模型参数的梯度清零,以避免受到之前批次梯度的影响。
# PyTorch 示例
optimizer.zero_grad()
关键点:这一步非常重要,如果忘记清零梯度,梯度会不断累积,导致训练过程出错。
2.2.4 反向传播:计算损失函数对参数的梯度
这是深度学习框架的核心魔法之一。调用损失张量的 .backward()
方法,框架会自动利用链式法则计算损失函数关于网络中所有可训练参数(requires_grad=True
的张量)的梯度。这些梯度会存储在对应参数的 .grad
属性中。
# PyTorch 示例
loss.backward() # 自动计算梯度
2.2.5 参数更新:向最优解迈进
一旦梯度计算完毕,优化器就可以使用这些梯度来更新模型的参数了。更新的规则由所选的优化器算法(如 SGD、Adam)决定。
# PyTorch 示例
optimizer.step() # 根据梯度更新模型参数
2.3 迭代训练:Epoch 与 Batch
通常,我们不会一次性将整个数据集都喂给模型进行训练,原因有二:
- 数据集可能非常大,内存可能无法一次性容纳。
- 分批次训练有助于模型跳出局部最优,并加速收敛。
- Batch (批次):将整个训练数据集划分为若干个较小的数据块,每个数据块称为一个 batch。
- Batch Size (批大小):每个 batch 中包含的样本数量。
- Iteration (迭代):处理一个 batch 数据的过程(即完成上述五个步骤)称为一次迭代。
- Epoch (轮次):当模型完整地遍历了训练数据集中所有样本一次后,称为完成了一个 epoch。
一个典型的训练流程会包含多个 epoch,每个 epoch 包含多个 iteration。
# PyTorch 伪代码结构
num_epochs = 10
for epoch in range(num_epochs):# 在每个 epoch 开始时,通常会将模型设置为训练模式model.train() # 作用是启用 Dropout 和 Batch Normalization 等层的训练行为for inputs, labels in train_loader: # train_loader 是一个数据加载器,按批次提供数据# 1. 梯度清零optimizer.zero_grad()# 2. 前向传播outputs = model(inputs)# 3. 计算损失loss = criterion(outputs, labels)# 4. 反向传播loss.backward()# 5. 参数更新optimizer.step()# 通常在一个 epoch结束后,会进行模型评估 (详见下一节)# print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}")
三、公正的裁判:模型评估
仅仅训练模型是不够的,我们还需要知道模型在未见过的数据上的表现如何,即模型的泛化能力。这就需要进行模型评估。
3.1 为何需要评估模式?
在评估模型时,我们通常需要将模型切换到评估模式。这主要是因为某些层(如 Dropout 层和 Batch Normalization 层)在训练和评估时的行为是不同的:
- Dropout 层:在训练时会随机失活一部分神经元以防止过拟合,但在评估时,我们希望使用完整的网络来进行预测,因此需要关闭 Dropout。
- Batch Normalization 层:在训练时会计算当前 batch 的均值和方差并进行归一化,同时会更新全局的均值和方差;在评估时,它会使用训练过程中学习到的全局均值和方差进行固定的归一化操作。
在 PyTorch 中,通过 model.eval()
来将模型切换到评估模式。相应地,model.train()
用于切换回训练模式。
此外,在评估阶段,我们不需要计算梯度,因为我们不进行参数更新。关闭梯度计算可以减少内存消耗并加速计算。在 PyTorch 中,这可以通过 torch.no_grad()
上下文管理器来实现。
# PyTorch 示例
model.eval() # 将模型设置为评估模式
with torch.no_grad(): # 在这个块内部,所有计算都不会追踪梯度# 进行评估...# test_outputs = model(test_inputs)# ...
model.train() # 如果后续还需要训练,记得切换回训练模式
3.2 评估流程
评估流程通常在验证集 (validation set) 或测试集 (test set) 上进行:
- 将模型设置为评估模式 (
model.eval()
)。 - 使用
torch.no_grad()
包裹评估代码。 - 遍历评估数据集的每一个批次。
- 进行前向传播,得到模型预测。
- 累积预测结果和真实标签。
- 根据累积的结果计算评估指标(如准确率、精确率、召回率等)。
3.3 常用评估指标
选择什么评估指标取决于具体的任务。
3.3.1 分类任务
- 准确率 (Accuracy):预测正确的样本数占总样本数的比例。是最常用的指标之一,但对于类别不平衡的数据集可能具有误导性。
计算公式: A c c u r a c y = f r a c T P + T N T P + T N + F P + F N Accuracy = \\frac{TP+TN}{TP+TN+FP+FN} Accuracy=fracTP+TNTP+TN+FP+FN (TP: True Positive, TN: True Negative, FP: False Positive, FN: False Negative) - 精确率 (Precision):模型预测为正类的样本中,实际也为正类的比例。关注的是“预测的有多准”。
计算公式: P r e c i s i o n = f r a c T P T P + F P Precision = \\frac{TP}{TP+FP} Precision=fracTPTP+FP - 召回率 (Recall) / 灵敏度 (Sensitivity):实际为正类的样本中,被模型成功预测为正类的比例。关注的是“有没有漏网之鱼”。
计算公式: R e c a l l = f r a c T P T P + F N Recall = \\frac{TP}{TP+FN} Recall=fracTPTP+FN - F1 分数 (F1-Score):精确率和召回率的调和平均值,综合了两者的表现。
计算公式: F 1 = 2 t i m e s f r a c P r e c i s i o n t i m e s R e c a l l P r e c i s i o n + R e c a l l F1 = 2 \\times \\frac{Precision \\times Recall}{Precision + Recall} F1=2timesfracPrecisiontimesRecallPrecision+Recall - 混淆矩阵 (Confusion Matrix):一个表格,用于可视化模型在各个类别上的预测情况。
3.3.2 回归任务
- 均方误差 (Mean Squared Error, MSE): M S E = f r a c 1 n s u m _ i = 1 n ( y _ i − h a t y _ i ) 2 MSE = \\frac{1}{n}\\sum\_{i=1}^{n}(y\_i - \\hat{y}\_i)^2 MSE=frac1nsum_i=1n(y_i−haty_i)2
- 均方根误差 (Root Mean Squared Error, RMSE): R M S E = s q r t M S E RMSE = \\sqrt{MSE} RMSE=sqrtMSE
- 平均绝对误差 (Mean Absolute Error, MAE): M A E = f r a c 1 n s u m _ i = 1 n ∣ y _ i − h a t y _ i ∣ MAE = \\frac{1}{n}\\sum\_{i=1}^{n}|y\_i - \\hat{y}\_i| MAE=frac1nsum_i=1n∣y_i−haty_i∣
3.4 代码示例:计算准确率
对于分类任务,计算准确率是一个常见的评估步骤。
# PyTorch 示例 (假设在评估循环内部)
# correct_predictions = 0
# total_samples = 0
#
# model.eval()
# with torch.no_grad():
# for inputs, labels in test_loader: # test_loader 是测试数据加载器
# outputs = model(inputs)
# _, predicted_classes = torch.max(outputs.data, 1) # 获取概率最高的类别索引
# total_samples += labels.size(0)
# correct_predictions += (predicted_classes == labels).sum().item()
#
# accuracy = 100 * correct_predictions / total_samples
# print(f'Accuracy on the test set: {accuracy:.2f}%')
四、实战演练:使用 PyTorch 训练 MNIST 分类模型
现在,我们将把前面讨论的所有概念整合起来,用 PyTorch 框架从头开始训练一个简单的多层感知器 (MLP) 来对 MNIST 手写数字数据集进行分类。
4.1 准备工作
首先,导入所有必要的库,并定义一些超参数。
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader# 定义超参数
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 优先使用GPU
input_size = 28 * 28 # MNIST 图像是 28x28 像素
hidden_size = 128 # 隐藏层大小
num_classes = 10 # 数字 0-9,共10类
num_epochs = 5 # 训练轮次
batch_size = 64 # 批大小
learning_rate = 0.001 # 学习率
4.2 加载并预处理 MNIST 数据集
PyTorch 的 torchvision
模块提供了直接加载 MNIST 数据集的功能,并可以方便地进行数据转换。
# 数据预处理:转换为张量,并进行归一化
transform = transforms.Compose([transforms.ToTensor(), # 将 PIL Image 或 numpy.ndarray 转换为 torch.Tensor,并将像素值从 [0, 255] 缩放到 [0.0, 1.0]transforms.Normalize((0.1307,), (0.3081,)) # MNIST 数据集的均值和标准差 (经验值)
])# 下载并加载训练数据集
train_dataset = torchvision.datasets.MNIST(root='./data',train=True,transform=transform,download=True)# 下载并加载测试数据集
test_dataset = torchvision.datasets.MNIST(root='./data',train=False,transform=transform)# 创建数据加载器
train_loader = DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True) # 训练时打乱数据test_loader = DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=False) # 测试时不需要打乱
4.3 定义一个简单的 MLP 模型
我们构建一个包含一个隐藏层的简单 MLP。
class MLP(nn.Module):def __init__(self, input_size, hidden_size, num_classes):super(MLP, self).__init__()self.fc1 = nn.Linear(input_size, hidden_size) # 输入层到隐藏层self.relu = nn.ReLU() # ReLU 激活函数self.fc2 = nn.Linear(hidden_size, num_classes) # 隐藏层到输出层def forward(self, x):# 将输入的 28x28 图像展平为 784 维向量x = x.reshape(-1, 28 * 28)out = self.fc1(x)out = self.relu(out)out = self.fc2(out)# 注意:对于 nn.CrossEntropyLoss(),不需要在模型末尾加 Softmax 层,它内部会处理return outmodel = MLP(input_size, hidden_size, num_classes).to(device) # 将模型移动到 GPU (如果可用)
回顾Day 21:[框架]入门(二):构建模型,我们学习了如何使用框架定义模型。
4.4 定义损失函数和优化器
# 损失函数:交叉熵损失,适用于多分类任务
criterion = nn.CrossEntropyLoss()# 优化器:Adam
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
4.5 编写训练循环
现在,我们整合第二节中学习的训练循环五个步骤。
print("开始训练...")
total_steps = len(train_loader)
for epoch in range(num_epochs):model.train() # 设置为训练模式for i, (images, labels) in enumerate(train_loader):# 将数据移动到 GPU (如果可用)images = images.to(device)labels = labels.to(device)# 1. 前向传播outputs = model(images)# 2. 计算损失loss = criterion(outputs, labels)# 3. 梯度清零optimizer.zero_grad()# 4. 反向传播loss.backward()# 5. 参数更新optimizer.step()if (i + 1) % 100 == 0: # 每 100 个 batch 打印一次信息print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{total_steps}], Loss: {loss.item():.4f}')
print("训练完成!")
4.6 编写评估函数
为了在训练后或训练过程中监控模型性能,我们编写一个评估函数。
def evaluate_model(model_to_eval, data_loader):model_to_eval.eval() # 设置为评估模式correct_predictions = 0total_samples = 0with torch.no_grad(): # 在评估阶段不计算梯度for images, labels in data_loader:images = images.to(device)labels = labels.to(device)outputs = model_to_eval(images.reshape(-1, 28 * 28)) # 确保输入形状正确_, predicted_classes = torch.max(outputs.data, 1) # 获取预测类别total_samples += labels.size(0)correct_predictions += (predicted_classes == labels).sum().item()accuracy = 100 * correct_predictions / total_samplesreturn accuracy
4.7 执行训练与评估
在训练循环结束后,调用评估函数。
# 评估模型在测试集上的表现
test_accuracy = evaluate_model(model, test_loader)
print(f'模型在测试集上的准确率: {test_accuracy:.2f}%')# 如果需要,也可以在每个 epoch 结束后评估在验证集上的表现 (此处我们直接用测试集)
# for epoch in range(num_epochs):
# # ... 训练代码 ...
# val_accuracy = evaluate_model(model, test_loader) # 或 validation_loader
# print(f'Epoch [{epoch+1}/{num_epochs}], Validation Accuracy: {val_accuracy:.2f}%')
通过以上步骤,我们就完成了一个使用 PyTorch 框架训练和评估 MNIST 分类模型的完整流程。您可以尝试调整超参数(如学习率、批大小、隐藏层大小、epoch 数)或者模型结构,观察它们对最终结果的影响。
五、常见问题与进一步思考
在实际的模型训练与评估过程中,可能会遇到各种问题,同时也存在许多可以进一步优化和探索的方向。
5.1 损失不下降怎么办?
这是初学者常遇到的问题,可能的原因有:
- 学习率设置不当:学习率过大可能导致损失震荡或发散;学习率过小可能导致收敛缓慢或陷入局部最优。可以尝试调整学习率。
- 数据问题:
- 数据未正确加载或预处理(如忘记归一化)。
- 标签错误或与输入不匹配。
- 数据量过小。
- 模型结构问题:
- 模型过于简单,无法拟合数据。
- 模型过于复杂,难以训练(可能需要更复杂的优化策略或正则化)。
- 梯度消失/爆炸:在深层网络中常见,可以尝试使用合适的激活函数(如ReLU)、权重初始化方法或 Batch Normalization。
- 代码 Bug:例如,忘记
optimizer.zero_grad()
,或者损失函数选择错误等。仔细检查代码逻辑。
5.2 如何进一步提升模型性能?
当基础训练流程跑通后,可以从以下方面尝试提升模型性能:
- 超参数调优:如学习率、批大小、网络层数、每层单元数、优化器选择等(Day 28)。
- 正则化技术:防止过拟合,如 L1/L2 正则化、Dropout (Day 25, Day 26)。
- 更复杂的模型架构:例如,对于图像任务,可以使用卷积神经网络 (CNN) (后续章节)。
- 数据增强:通过对训练数据进行变换(如旋转、裁剪、颜色抖动)来扩充数据集,提升模型泛化能力 (Day 27, Day 35)。
- 早停法 (Early Stopping):在验证集上监控性能,当性能不再提升时提前停止训练,防止过拟合 (Day 27)。
- 使用预训练模型与迁移学习:对于某些任务,可以利用在大型数据集上预训练好的模型进行微调 (Day 39)。
5.3 训练过程的可视化
仅仅打印损失值和最终的准确率可能不够直观。使用 TensorBoard (TensorFlow生态) 或类似的工具 (如 matplotlib
配合 PyTorch) 来可视化训练过程中的损失曲线、准确率曲线等指标,可以帮助我们更好地理解模型的学习动态,并进行诊断。
例如,可以绘制每个 epoch 的训练损失和验证准确率:
# 伪代码 - 使用 matplotlib 绘制曲线
# train_losses = []
# val_accuracies = []# for epoch in range(num_epochs):
# # ... 训练 ...
# # current_train_loss = ...
# # train_losses.append(current_train_loss)
#
# # ... 评估 ...
# # current_val_accuracy = ...
# # val_accuracies.append(current_val_accuracy)# import matplotlib.pyplot as plt
# plt.figure(figsize=(10, 4))
# plt.subplot(1, 2, 1)
# plt.plot(train_losses, label='Training Loss')
# plt.xlabel('Epoch')
# plt.ylabel('Loss')
# plt.legend()
#
# plt.subplot(1, 2, 2)
# plt.plot(val_accuracies, label='Validation Accuracy')
# plt.xlabel('Epoch')
# plt.ylabel('Accuracy')
# plt.legend()
# plt.show()
六、总结
本篇文章详细介绍了使用深度学习框架进行模型训练与评估的完整流程,这是深度学习实践中至关重要的一环。核心要点回顾:
- 准备阶段:正确选择并定义损失函数(如交叉熵损失)以量化模型误差,以及优化器(如Adam)来指导参数更新。
- 训练循环核心五步:
- 梯度清零 (
optimizer.zero_grad()
):清除旧梯度。 - 前向传播 (
outputs = model(inputs)
):获取模型预测。 - 计算损失 (
loss = criterion(outputs, labels)
):评估预测与真实值的差异。 - 反向传播 (
loss.backward()
):计算损失对各参数的梯度。 - 参数更新 (
optimizer.step()
):根据梯度调整模型参数。
- 梯度清零 (
- Epoch 与 Batch:理解整个数据集的一次完整遍历(Epoch)和分批处理数据(Batch)的概念及其在训练中的作用。
- 评估模式:通过
model.eval()
切换模型到评估状态,关闭 Dropout 和 Batch Normalization 的训练行为,并使用torch.no_grad()
节约计算资源。 - 评估指标:根据任务类型选择合适的指标,如分类任务中的准确率、精确率、召回率、F1分数,回归任务中的 MSE、MAE 等。
- MNIST 实战:通过一个完整的手写数字识别案例,将理论知识应用于实践,包括数据加载与预处理、模型定义、训练循环编写和模型评估。
- 持续改进:模型训练是一个迭代优化的过程,需要关注常见问题(如损失不降),并思考如何通过超参数调优、正则化、数据增强等手段进一步提升模型性能。
掌握了模型训练与评估的流程,就如同掌握了驾驶汽车的核心技能。接下来,我们将面临更多样化的道路(更复杂的模型和任务),但基本的驾驶原理是相通的。希望本篇内容能为您在深度学习的道路上提供坚实的支撑。