9 从0开始学PyTorch | 过拟合欠拟合、训练集验证集、关闭自动求导

news/2025/1/17 4:10:48/

这一小节在开始搞神经网络之前,我们先熟悉几个概念,主要还是把模型训练的流程打通。

过拟合和欠拟合

我们在日常的工作中,训练好的模型往往是要去评价它的准确率的,通过此来判断我们的模型是否符合我的要求。
几个可能的方案是,对我们训练使用的数据再输入到训练好的模型中,查看输出的结果是否跟预期的结果是一致的,当然这个在我们的线性模型上跟训练过程没有区别。另外一个比较靠谱的方案是把一部分在训练的时候没有用过的数据放进模型里,看预测结果是否和预期结果一致。

过拟合(overfitting):对于上述两个方案获得的结果,一种情况是在训练用的数据上表现良好,但是对于新数据预测的结果比较差,这时候就是过拟合了,模型学到了训练数据上太多的细节,导致模型的泛化能力变差。
欠拟合(underfitting):另外一个可能的情况是,不光在新数据上表现不好,就在训练数据上表现也不好,这种情况就是欠拟合,连训练数据的特点都没学好。
如下图中画的,左边的模型算是比较好的,中间的模型就是欠拟合,只学到了上半部分数据的特征,而右边那副图就是过拟合。对于处理过拟合和欠拟合问题,有很多解决方案,比如说增加数据,增加迭代轮次,调整参数,增加噪声,随机丢弃等等,这里我们先不纠缠这个问题。

image.png

训练集和验证集

关于上面提到的两份数据,我们就可以称为训练集和验证集,当然有些时候还有一个叫测试集,有时候认为测试集介于训练集和验证集之间,也就是拿训练集去训练模型,使用测试集测试并进行调整,最后用验证集确定最终的效果。在这本书上只写了训练集和验证集,所以我们这里也先按照这个思路来介绍。

image.png

正如上图绘制的那样,在原始数据到来的时候,把它分成两份,一份是训练集,一份是验证集。训练集用来训练模型,当模型迭代到一定程度的时候,我们使用验证集输入到训练好的模型里,评估模型的表现。

torch.randperm方法:将0~n-1(包括0和n-1)随机打乱后获得的数字序列,函数名是random permutation缩写
下面用代码来实现一下

n_samples = t_u.shape[0] #获取样本数量
n_val = int(0.2 * n_samples) #验证集的数量,取全集的20%,这里是2个shuffled_indices = torch.randperm(n_samples) #打乱顺序train_indices = shuffled_indices[:-n_val] #训练集位置信息
val_indices = shuffled_indices[-n_val:] #验证集位置信息train_indices, val_indices  
outs:(tensor([2, 5, 9, 8, 6, 1, 4, 3, 7]), tensor([10,  0]))

紧接着是获取训练数据和验证数据

train_t_u = t_u[train_indices]
train_t_c = t_c[train_indices]val_t_u = t_u[val_indices]
val_t_c = t_c[val_indices]train_t_un = 0.1 * train_t_u
val_t_un = 0.1 * val_t_u

定义训练方法,这些跟之前的都差不多

def training_loop(n_epochs, optimizer, params, train_t_u, val_t_u,train_t_c, val_t_c):for epoch in range(1, n_epochs + 1):train_t_p = model(train_t_u, *params) # <1>train_loss = loss_fn(train_t_p, train_t_c)val_t_p = model(val_t_u, *params) # <1>val_loss = loss_fn(val_t_p, val_t_c)optimizer.zero_grad()train_loss.backward() optimizer.step()if epoch <= 3 or epoch % 500 == 0:print(f"Epoch {epoch}, Training loss {train_loss.item():.4f},"f" Validation loss {val_loss.item():.4f}")return paramsparams = torch.tensor([1.0, 0.0], requires_grad=True)
learning_rate = 1e-2
optimizer = optim.SGD([params], lr=learning_rate)training_loop(n_epochs = 3000, optimizer = optimizer,params = params,train_t_u = train_t_un, # <1> val_t_u = val_t_un, # <1> train_t_c = train_t_c,val_t_c = val_t_c)
out:
Epoch 1, Training loss 91.7660, Validation loss 29.0568
Epoch 2, Training loss 43.7766, Validation loss 2.3025
Epoch 3, Training loss 36.0900, Validation loss 3.5195
Epoch 500, Training loss 7.0920, Validation loss 4.6118
Epoch 1000, Training loss 3.4116, Validation loss 4.0901
Epoch 1500, Training loss 2.9273, Validation loss 3.9970
Epoch 2000, Training loss 2.8636, Validation loss 3.9759
Epoch 2500, Training loss 2.8552, Validation loss 3.9700
Epoch 3000, Training loss 2.8541, Validation loss 3.9680
tensor([  5.4240, -17.2490], requires_grad=True)

从上面的结果可以看到,训练集损失持续下降,验证集损失前期波动比较大,这可能是因为我们的验证集数量太少导致的,不过在500代以后训练损失和验证损失都趋于稳定。
这里作者给出了几个对比训练损失和验证损失的图片,很有意思。其中蓝色实线是训练损失,红色虚线是验证损失。对于图A,训练损失和验证损失随着训练轮次的增长都没啥变化,表明数据并没有提供什么有价值的信息;图B中,随着训练轮次增加,训练损失逐步下降,而验证损失逐步上升,这说明出现了过拟合现象;C图中验证损失和训练损失同步下降,是一种比较理想化的模型效果;D图中验证损失和训练损失也是同步下降,但是训练损失下降幅度更大一些,这种情况显示存在一定的过拟合,但是仍在可以接受的范围内。

image.png

关闭自动求导

在上面的过程中,我们涉及到一个问题,就是对于验证损失计算完以后,我们并没有调用backward(),那是因为我们只想用验证集数据来检查模型效果,而不希望验证集数据影响我们的模型训练,不然的话就相当于验证集数据也加入了训练,那就很难判断模型是否存在过拟合了。就像下图所写的,使用模型预测和计算损失的步骤是一样的,但是只对train_loss进行反向传播。

image.png

因此在验证过程中,我们实际不需要进行自动求导,但是如果我们前面都设置了自动求导怎么办呢,这会带来大量不必要的运算开销。于是PyTorch提供了关闭自动求导的方法,就是使用torch.no_grad()。

def training_loop(n_epochs, optimizer, params, train_t_u, val_t_u,train_t_c, val_t_c):for epoch in range(1, n_epochs + 1):train_t_p = model(train_t_u, *params)train_loss = loss_fn(train_t_p, train_t_c)with torch.no_grad():  # 上下文管理器,关闭自动求导val_t_p = model(val_t_u, *params)val_loss = loss_fn(val_t_p, val_t_c)assert val_loss.requires_grad == Falseoptimizer.zero_grad()train_loss.backward()optimizer.step()

这里还有另外一个方式,就是使用set_grad_enabled(),这个方法接收一个bool类型的参数,来设置是否自动求导。

def calc_forward(t_u, t_c, is_train): with torch.set_grad_enabled(is_train): #这里传入了是否是训练这样一个bool类型来显示当前的前向传播是训练还是验证t_p = model(t_u, *params)loss = loss_fn(t_p, t_c)return loss

今天写的比较短,感觉轻松多了。


http://www.ppmy.cn/news/607516.html

相关文章

LogisticRegressionCV 参数使用以及含义 笔记

第一次接触LogisticRegressionCV &#xff0c;记录一下。 Logistic回归是分类算法&#xff0c;不能应用于回归中(传入模型的y值,不能是float类型&#xff0c;必须是int类型) 正则化选择参数 &#xff1a;penalty ——> L1 / L2 默认是…

芯片产品介绍

芯片产品介绍 云端训练芯片 7纳米GPGPU高端自研云端训练芯片 基于全自研通用计算GPGPU芯片&#xff0c;天数智芯的硬件产品聚焦于云端训练及推理&#xff0c;通过丰富全面的自研指令集释放强大的可编程性与应用通用性&#xff0c;提供业界领先的AI算力密度与能效比。 天数智芯…

Python第三方库jieba(中文分词)入门与进阶(官方文档)

jieba “结巴”中文分词&#xff1a;做最好的 Python 中文分词组件 github:https://github.com/fxsjy/jieba 特点 支持三种分词模式&#xff1a; 精确模式&#xff0c;试图将句子最精确地切开&#xff0c;适合文本分析&#xff1b; 全模式&#xff0c;把句子中所有的可以成词的…

js ajax 递归,javascript ajax循环请求/ 长轮询终极解决办法——递归

ajax循环请求&#xff0c;不能使用for循环&#xff0c;原因如下&#xff1a;1. 因为ajax是异步执行&#xff0c;在还没有拿到服务器响应内容&#xff0c;就进入下一个for循环中解决办法&#xff1a;递归currentIndex 0;function ajax(){if(currentIndex>20){return;}var ur…

[JS] 什么是浮动,clear:both的使用

test.html里输入以下代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"wi…

HBM显存与GPU

HBM显存与GPU 彻底改变显存技术 低功耗存储芯片&#xff0c;具有超宽通信数据通路和革命性的创新堆叠方案。 信息图&#xff1a;推出高带宽显存 HBM采用垂直堆叠方式和高速信息传输&#xff0c;以创新的小尺寸为用户带来了真正让人振奋的性能。这种内存在显卡中的应用只是个开始…

数据湖(十六):Structured Streaming实时写入Iceberg

文章目录 Structured Streaming实时写入Iceberg 一、创建Kafka topic 二、编写向Kafka生产数据代码

使用R绘制气泡图、带有显著性标记的热力图、渐变曲线图

大家好&#xff0c;我是带我去滑雪&#xff01; 一幅精美的科研绘图会有诸多益处&#xff0c;精美的图像可以更好地传达研究结果和数据分析的重要信息。通过使用清晰、直观和易于理解的图像&#xff0c;可以更好地向读者展示研究的发现&#xff0c;有助于读者理解和解释数据。还…