当前位置: 首页 > news >正文

Day 37:早停策略和模型权重的保存

什么是过拟合

过拟合是指机器学习模型在训练阶段表现得过于出色,以至于它把训练数据中的 噪声(noise)和随机波动(random fluctuations) 也当作了真实的模式来学习。结果就是,这个模型对训练数据集的拟合度极高,但在面对新的、未见过的数据时,其预测或分类的准确率会大幅下降。

简单来说,模型不是在“学习”,而是在“死记硬背”。它记住了训练数据的每一个细节,却失去了对普适规律的归纳能力,导致其泛化能力(generalization ability)很差。

过拟合是如何形成的

过拟合由以下几个关联因素共同导致的:

  • 模型过于复杂
    模型过于复杂使得模型的能力远远超过了器需要解决的问题,此时模型相当于拥有极强的记忆力,能够记住数据中的所有细节,包含其中的噪声等。
  • 训练数据不足
    训练数据样本太少,无法全面的反映真实世界中所有情况,模型学到的规律仅代表训练数据中特有的巧合。
  • 数据质量不高
    训练数据质量不高,包含大量的噪声,模型难以区分噪声与真实的信号,对噪声和真实的信号同时建模混合解释。

由此,引入本节内容:早停策略


早停策略

在机器学习中,早停(Early Stopping) 是一种广泛使用的、非常有效的正则化(Regularization)技术,其主要目的是防止模型过拟合(Overfitting)。

早停的基本思想非常直观:在模型训练的过程中,持续监控其在 验证集(Validation Set) 上的性能。当模型在验证集上的性能不再提升,甚至开始变差时,就提前终止训练,即使模型在训练集上的性能仍在继续改善。

import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import time
import matplotlib.pyplot as plt
from tqdm import tqdm  # 导入tqdm库用于进度条显示
import warnings
warnings.filterwarnings("ignore")  # 忽略警告信息# 设置GPU设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")# 加载鸢尾花数据集
iris = load_iris()
X = iris.data  # 特征数据
y = iris.target  # 标签数据# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 归一化数据
scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)# 将数据转换为PyTorch张量并移至GPU
X_train = torch.FloatTensor(X_train).to(device)
y_train = torch.LongTensor(y_train).to(device)
X_test = torch.FloatTensor(X_test).to(device)
y_test = torch.LongTensor(y_test).to(device)class MLP(nn.Module):def __init__(self):super(MLP, self).__init__()self.fc1 = nn.Linear(4, 10)  # 输入层到隐藏层self.relu = nn.ReLU()self.fc2 = nn.Linear(10, 3)  # 隐藏层到输出层def forward(self, x):out = self.fc1(x)out = self.relu(out)out = self.fc2(out)return out# 实例化模型并移至GPU
model = MLP().to(device)# 分类问题使用交叉熵损失函数
criterion = nn.CrossEntropyLoss()# 使用随机梯度下降优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)# 训练模型
num_epochs = 20000  # 训练的轮数# 用于存储每100个epoch的损失值和对应的epoch数
losses = []
epochs = []start_time = time.time()  # 记录开始时间# 创建tqdm进度条
with tqdm(total=num_epochs, desc="训练进度", unit="epoch") as pbar:# 训练模型for epoch in range(num_epochs):# 前向传播outputs = model(X_train)  # 隐式调用forward函数loss = criterion(outputs, y_train)# 反向传播和优化optimizer.zero_grad()loss.backward()optimizer.step()# 记录损失值并更新进度条if (epoch + 1) % 200 == 0:losses.append(loss.item())epochs.append(epoch + 1)# 更新进度条的描述信息pbar.set_postfix({'Loss': f'{loss.item():.4f}'})# 每1000个epoch更新一次进度条if (epoch + 1) % 1000 == 0:pbar.update(1000)  # 更新进度条# 确保进度条达到100%if pbar.n < num_epochs:pbar.update(num_epochs - pbar.n)  # 计算剩余的进度并更新time_all = time.time() - start_time  # 计算训练时间
print(f'Training time: {time_all:.2f} seconds')# 可视化损失曲线
plt.figure(figsize=(10, 6))
plt.plot(epochs, losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss over Epochs')
plt.grid(True)
plt.show()# 在测试集上评估模型,此时model内部已经是训练好的参数了
# 评估模型
model.eval() # 设置模型为评估模式
with torch.no_grad(): # torch.no_grad()的作用是禁用梯度计算,可以提高模型推理速度outputs = model(X_test)  # 对测试数据进行前向传播,获得预测结果_, predicted = torch.max(outputs, 1) # torch.max(outputs, 1)返回每行的最大值和对应的索引#这个函数返回2个值,分别是最大值和对应索引,参数1是在第1维度(行)上找最大值,_ 是Python的约定,表示忽略这个返回值,所以这个写法是找到每一行最大值的下标# 此时outputs是一个tensor,p每一行是一个样本,每一行有3个值,分别是属于3个类别的概率,取最大值的下标就是预测的类别# predicted == y_test判断预测值和真实值是否相等,返回一个tensor,1表示相等,0表示不等,然后求和,再除以y_test.size(0)得到准确率# 因为这个时候数据是tensor,所以需要用item()方法将tensor转化为Python的标量# 之所以不用sklearn的accuracy_score函数,是因为这个函数是在CPU上运行的,需要将数据转移到CPU上,这样会慢一些# size(0)获取第0维的长度,即样本数量correct = (predicted == y_test).sum().item() # 计算预测正确的样本数accuracy = correct / y_test.size(0)print(f'测试集准确率: {accuracy * 100:.2f}%')

在这里插入图片描述
过拟合的判断

import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import time
import matplotlib.pyplot as plt
from tqdm import tqdm  # 导入tqdm库用于进度条显示
import warnings
warnings.filterwarnings("ignore")  # 忽略警告信息# 设置GPU设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")# 加载鸢尾花数据集
iris = load_iris()
X = iris.data  # 特征数据
y = iris.target  # 标签数据# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 归一化数据
scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)# 将数据转换为PyTorch张量并移至GPU
X_train = torch.FloatTensor(X_train).to(device)
y_train = torch.LongTensor(y_train).to(device)
X_test = torch.FloatTensor(X_test).to(device)
y_test = torch.LongTensor(y_test).to(device)class MLP(nn.Module):def __init__(self):super(MLP, self).__init__()self.fc1 = nn.Linear(4, 10)  # 输入层到隐藏层self.relu = nn.ReLU()self.fc2 = nn.Linear(10, 3)  # 隐藏层到输出层def forward(self, x):out = self.fc1(x)out = self.relu(out)out = self.fc2(out)return out# 实例化模型并移至GPU
model = MLP().to(device)# 分类问题使用交叉熵损失函数
criterion = nn.CrossEntropyLoss()# 使用随机梯度下降优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)# 训练模型
num_epochs = 20000  # 训练的轮数# 用于存储每200个epoch的损失值和对应的epoch数
train_losses = [] # 存储训练集损失
test_losses = [] # 新增:存储测试集损失
epochs = []start_time = time.time()  # 记录开始时间# 创建tqdm进度条
with tqdm(total=num_epochs, desc="训练进度", unit="epoch") as pbar:# 训练模型for epoch in range(num_epochs):# 前向传播outputs = model(X_train)  # 隐式调用forward函数train_loss = criterion(outputs, y_train)# 反向传播和优化optimizer.zero_grad()train_loss.backward()optimizer.step()# 记录损失值并更新进度条if (epoch + 1) % 200 == 0:# 计算测试集损失,新增代码model.eval()with torch.no_grad():test_outputs = model(X_test)test_loss = criterion(test_outputs, y_test)model.train()train_losses.append(train_loss.item())test_losses.append(test_loss.item())epochs.append(epoch + 1)# 更新进度条的描述信息pbar.set_postfix({'Train Loss': f'{train_loss.item():.4f}', 'Test Loss': f'{test_loss.item():.4f}'})# 每1000个epoch更新一次进度条if (epoch + 1) % 1000 == 0:pbar.update(1000)  # 更新进度条# 确保进度条达到100%if pbar.n < num_epochs:pbar.update(num_epochs - pbar.n)  # 计算剩余的进度并更新time_all = time.time() - start_time  # 计算训练时间
print(f'Training time: {time_all:.2f} seconds')# 可视化损失曲线
plt.figure(figsize=(10, 6))
plt.plot(epochs, train_losses, label='Train Loss') # 原始代码已有
plt.plot(epochs, test_losses, label='Test Loss')  # 新增:测试集损失曲线
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training and Test Loss over Epochs')
plt.legend() # 新增:显示图例
plt.grid(True)
plt.show()# 在测试集上评估模型,此时model内部已经是训练好的参数了
# 评估模型
model.eval() # 设置模型为评估模式
with torch.no_grad(): # torch.no_grad()的作用是禁用梯度计算,可以提高模型推理速度outputs = model(X_test)  # 对测试数据进行前向传播,获得预测结果_, predicted = torch.max(outputs, 1) # torch.max(outputs, 1)返回每行的最大值和对应的索引correct = (predicted == y_test).sum().item() # 计算预测正确的样本数accuracy = correct / y_test.size(0)print(f'测试集准确率: {accuracy * 100:.2f}%')    

在这里插入图片描述
理想情况下训练集和测试集的损失都下降,当验证集的损失上升,训练集的损失下降时,可以判定为过拟合。


模型保存

深度学习中模型的保存包括权重的保存,模型框架的保存和训练checkpoint的保存。

仅保存模型参数(推荐)
  • 原理:保存模型的权重参数,不保存模型结构代码。加载时需提前定义与训练时一致的模型类。
  • 优点:文件体积小(仅含参数),跨框架兼容性强(需自行定义模型结构)。
# 保存模型参数
torch.save(model.state_dict(), "model_weights.pth")
# 加载参数(需先定义模型结构)
model = MLP()  # 初始化与训练时相同的模型结构
model.load_state_dict(torch.load("model_weights.pth"))
# model.eval()  # 切换至推理模式(可选)
保存模型+权重
  • 原理:保存模型结构及参数
  • 优点:加载时无需提前定义模型类
  • 缺点:文件体积大,依赖训练时的代码环境(如自定义层可能报错)。
# 保存整个模型
torch.save(model, "full_model.pth")# 加载模型(无需提前定义类,但需确保环境一致)
model = torch.load("full_model.pth")
model.eval()  # 切换至推理模式(可选)

在这里插入图片描述

保存训练状态(断点续训)
  • 原理:保存模型参数、优化器状态(学习率、动量)、训练轮次、损失值等完整训练状态,用于中断后继续训练。
  • 适用场景:长时间训练任务(如分布式训练、算力中断)。
# # 保存训练状态
# checkpoint = {
#     "model_state_dict": model.state_dict(),
#     "optimizer_state_dict": optimizer.state_dict(),
#     "epoch": epoch,
#     "loss": best_loss,
# }
# torch.save(checkpoint, "checkpoint.pth")# # 加载并续训
# model = MLP()
# optimizer = torch.optim.Adam(model.parameters())
# checkpoint = torch.load("checkpoint.pth")# model.load_state_dict(checkpoint["model_state_dict"])
# optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
# start_epoch = checkpoint["epoch"] + 1  # 从下一轮开始训练
# best_loss = checkpoint["loss"]# # 继续训练循环
# for epoch in range(start_epoch, num_epochs):
#     train(model, optimizer, ...)

@浙大疏锦行

http://www.dtcms.com/a/325438.html

相关文章:

  • C语言指针(四):字符指针、数组指针与函数指针的实战指南
  • Unity大型场景性能优化全攻略:PC与安卓端深度实践 - 场景管理、渲染优化、资源调度 C#
  • 在Mac 上生成GitLab 的SSH 密钥并将其添加到GitLab
  • 在 .NET Core 5.0 中启用 Gzip 压缩
  • AI时代基于云原生的 CI/CD 基础设施 Tekton
  • Redis Sentinel 中 `sentinel resolve-hostnames yes` 的必要性解析
  • C#图形库SciChart与ScottPlot及LiveCharts2对比
  • Linux客户端利用MinIO对服务器数据进行同步
  • 掌握while循环:C语言编程基础
  • Unity跨平台性能优化全攻略:PC与安卓端深度优化指南 - CPU、GPU、内存优化 实战案例C#
  • 《番外:Veda的备份,在某个未联网的旧服务器中苏醒……》
  • 扩展运算符...作用介绍
  • 关系型数据库:原理、演进与生态全景——从理论基石到云原生的深度巡礼
  • 国内 Mac 开启 Apple Intelligence 教程
  • 深入浅出JVM:Java虚拟机的探秘之旅
  • 第2节 PyTorch加载数据
  • 关系操作符详解与避坑指南
  • 软件编程2-标准IO
  • Maxscript实现在物体表面均匀散布的4种主流算法
  • C# 异步编程(计时器)
  • 大模型提示词工程实践:大语言模型文本转换实践
  • 实战:用 PyTorch 复现一个 3 层全连接网络,训练 MNIST,达到 95%+ 准确率
  • 软考高级资格推荐与选择建议
  • 大语言模型(LLM)核心概念与应用技术全解析:从Prompt设计到向量检索
  • STM32蓝牙模块驱动开发
  • 什么是结构化思维?什么是结构化编程?
  • 获取MaixPy系列开发板机器码——MaixHub 模型下载机器码获取方法
  • 【Python】在rk3588开发板排查内存泄漏问题过程记录
  • 视频前处理技术全解析:从基础到前沿
  • DreaMoving:基于扩散模型的可控视频生成框架