深度学习训练过程自查:为什么我的模型不收敛/表现不佳?

代码终于写完了,bug 处理好了,终于跑起来了。但是模型不收敛。或者收敛了,但是加 trick 也表现不良。看着这个精心编写的辣鸡模型,从内心深处生出一股恨铁不成钢的悲愤。

于是开始思考,为什么?哪里出了问题?

  • 超参数是不是有问题?(最悲伤的是调了超参数,试了几组,发现一开始直觉设置的反而是最好的……)
  • 是不是模型结构有问题?(于是开始怀疑自己,难道真的是我的模型结构有问题吗?)
  • 或者是模型代码有问题?(mask 加了吗?残差结构加了吗?……)
  • 果然还是训练代码有 bug 吧

因此就整理了这个文章,用来辅助自查模型到底哪里出了问题。(有时候是模型结构的问题,有时候真的是因为过度关注模型结构,每次写训练代码都是套模板,不往心里去,其实是训练过程有问题)

首先捋一下,深度学习模型训练过程:

  1. 定义数据集
  2. 定义模型
  3. 前向过程
  4. 反向过程计算梯度
  5. 梯度更新

那么可以从以上过程排查,是不是遗漏了什么。

最后,加一个灵魂提问:如果你用了分布式训练,你真的明白分布式训练在做什么吗?还是直接搜了一个模板代码能跑就行呢……

文章目录

  • 检查代码 bug
    • 数据预处理
    • 定义模型
    • 训练过程
      • debug和复现需要的设置
      • optimizer 和 lr
        • optimizer
        • lr_scheduler
  • 超参数设置的问题
    • 关于 batch size 和 lr
  • 分布式训练
    • DistributedDataParallel内部机制
    • 分布式训练中的各种参数
    • 分布式训练的参考资料

检查代码 bug

数据预处理

  • 数据的格式正确:如果用了预训练大模型提取特征,那么你用的数据格式要和训练的模型保持一致
    • 图片数据:三个通道是不是正确?resize是否正确?是 float32 还是 float64 还是 uint8?有没有做过归一化?
    • 文本数据:是不是用了同一个 tokenizer?(不同的 tokenizer 的单词的 id 编码不同,如果是用预训练文本特征提取器,一定要保证一致)
  • 数据真值对应:输入和输入是不是对上的
    • 特别是用了数据增广之后,还是不是对上的?
    • 有box的任务,box的数据是xyxy类型还是xywh类型,别弄错了
  • 数据处理的位置要明确
    • 有些代码把数据处理写在 __get_item__()
    • 有些写在 train() 那一长串里面,以 transforms 的形式传到 dataset 里面;
    • 有些写在 Dataloader 的 collate_fn 参数里;
    • 有些用了 transformers 等库,以 processor 等形式传到 dataset 里面;

定义模型

  • 模型结构上的小细节
    • 各种 norm
    • 残差结构
    • dropout
    • 各种 mask(padding mask、causal mask)
    • position embedding
    • attention 那个除以根号 d
    • 激活函数
  • 预训练模型,参数是不是读进去了
  • 训练的时候,哪些层梯度要更新、哪些层冻住,写好了没?
  • loss 计算:
    • 计算 loss 时传进去的数据对不对?预测结果和真值是不是可以直接一起比较,还是真值也需要经过某些预处理(归一化之类)?
    • loss 到底在哪个地方计算?有些人在 model 的 forward() 方法里面直接返回 loss,有些是前向过程结束返回预测结果,在 train() 里面再计算 loss;

训练过程

debug和复现需要的设置

  • logger 写好没?
  • model_config 和 train_config 存成本地文件了没?
  • 随机数种子,是不是 random, np.random, torch.random 都固定了

optimizer 和 lr

首先要理清这俩的关系,去掉花里胡哨的 tricks,这俩的调用方法是这样的:

# 随便选的一个 optimizer 实例化
# 这里的参数,model.parameters() 是要更新的参数,必须传
# lr 是必须传进去的
# 其它的参数是不同的 optimizer 可能不同,不关注 tricks 的话不用管
# [1] 实例化 optimizer
optimizer = torch.optim.Adadelta(model.parameters(), lr=1.0, rho=0.9, eps=1e-06, weight_decay=0.1)# 随便选的一个 lr_scheduler 实例化
# 这里 optimizer 是必须传的参数,别的参数因 lr_scheduler 不同而不同,在这不是重点
# [2] 实例化 lr_scheduler
lr_sche = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1)for epoch in range(num_epoch):for batch_data in train_dataloader# [3] 已经更新过的梯度清零,防止影响下次梯度更新optimizer.zero_grad()# [4] 前向过程计算损失loss = model(**batch_data)# [5] 反向过程计算梯度loss.backward()# [6] 利用计算的梯度,根据学习率更新梯度optimizer.step()# [7] 学习率按照计划更新lr_sche.step()
optimizer

首先解析 optimizer

optimizer 内维护了 3 个变量:

  • defaults
  • param_groups:是 List[dict] 类型的数据,这个 dict 里面存了要更新的模型参数、更新这个参数需要的超参数(比如说学习率、weight decay 之类的)
  • state: 这是个默认值为 dict 的字典(字典套娃);其中保存的是optimizer更新变量过程中计算出的最新的相关缓存变量。key是这些缓存的地址,value也是一个字典,key是缓存变量名,value是相应的tensor。

举例,模型是一个 model = torch.nn.Linear(10, 10) ,模型参数传进去,得到的 optimizer 内部的信息:
在这里插入图片描述展开看可训练的模型参数:
在这里插入图片描述
把模型看成 y = wx+b,那么其中 shape=(10, 10) 的是 w,shape=(10, ) 的是 b

每个 optimizer 子类都要实现 step() 方法,执行单步的梯度更新操作。进一步学习:torch.optim.optimizer源码阅读和灵活使用 (这里给了一个源码例子,看看 step() 方法里到底做了什么)

lr_scheduler

参考:torch.optim.lr_scheduler源码和cosine学习率策略学习

  • lr_scheduler在构造函数中主要是获取optimizer并向其添加step计数功能,然后更新一次学习率。
  • step函数主要进行lr的实时计算以及相关参数的更新,包括epoch、lr和optimizer中保存的实时lr。

超参数设置的问题

除了代码有 bug 导致你的模型表现不好,还可能是超参设置有问题。超参有问题,你往上加 trick 也无济于事呀(流泪)

超参怎么设还是经验问题,这里就放一些别人的经验。搜集到新的再慢慢更新。

关于 batch size 和 lr

观点:batch size 和 learning rate 要等比例放大

看到很多人这么说,于是去查了下原理。一般谈论 batch size 对训练过程的影响,会说:

  • 小的 batch size 计算得到的噪声多(这个很好理解,比如 batch size=1,那就是贴着这个数据的梯度方向下降,肯定会把这个数据中的噪声带进去)
  • 过大的 batch size 表现不一定好。不仅仅是说泛化性不好,val表现变差;在 train 数据集上也会变差
    • 一个解释是,因为大的 batch size 下降到鞍部就不再下降了,而小的 bs 还会探索别的梯度下降的方向。

这篇文章:如何选择模型训练的batch size和learning rate
写得非常好,给出的结论是:

  • batchsize变大 k k k 倍,学习率也要相应变大 k \sqrt{k} k 倍,本质是为了梯度的方差保持不变
  • 如果增加了学习率,那么batch size最好也跟着增加,这样收敛更稳定。
  • 尽量使用大的学习率,因为很多研究都表明更大的学习率有利于提高泛化能力。如果真的要衰减,可以尝试其他办法,比如增加batch size,学习率对模型的收敛影响真的很大,慎重调整。

分布式训练

分布式字面上很好理解,就是同时训练。搜资料最多的也是说,有把模型拆成两部分训练的、有把数据拆成两部分训练的,等等。

但是我还有有些概念搞不清楚,导致我无法把控我的训练过程中是不是出现了梯度整合的错误,导致模型训练的错误。

我的疑问是:

  1. 分布式训练是多线程还是多进程?
  2. 怎么保证分布式训练时候的数据安全性?(包括训练数据、梯度、optimizer、lr_scheduler)
  3. 常见的分布式训练的方法是什么?参数怎么设置?

先不说在pytorch上的框架(lightning 等),pytorch 提供的分布式训练有两种方法:

  • nn.DataParallel
    • 简单
    • 只能数据分布式训练
    • 单进程多线程
  • nn.DistributedDataParallel
    • 支持数据分布式、模型分布式
    • 多进程
    • 每个进程都有独立的优化器,执行自己的更新过程,但是梯度通过通信传递到每个进程,所有执行的内容是相同的;

现在一般都推荐使用 nn.DistributedDataParallel

DistributedDataParallel内部机制

(这里以数据分布式训练讲解,没有考虑模型分布式训练)

DistributedDataParallel 在多个GPUs间复制模型,每个GPU都由一个进程控制。GPU可以都在同一个节点上,也可以分布在多个节点上(这里的节点,应该说的是主机)。

不同进程拿到的是相同的模型、不同的数据,需要在正向传播时对数据的分配进行调整,所以dataloader里面多了一个sampler参数。

在多机多卡情况下分布式训练数据的读取也是一个问题,不同的卡读取到的数据应该是不同的。dataparallel的做法是直接将batch切分到不同的卡,这种方法对于多机来说不可取,因为多机之间直接进行数据传输会严重影响效率。于是有了利用sampler确保dataloader只会load到整个数据集的一个特定子集的做法。DistributedSampler就是做这件事的。它为每一个子进程划分出一部分数据集,以避免不同进程之间数据重复。

每个进程都执行相同的任务,并且每个进程都与所有其他进程通信。进程或者说GPU之间只传递梯度,这样网络通信就不再是瓶颈。

什么时候整合多张卡的计算结果?
首先看看数据流(图片来源:PyTorch分布式训练基础–DDP使用)
在这里插入图片描述所以每张卡算出来 loss 之后,要先整合大家计算的 loss 结果,得到一个梯度,然后把这个梯度告诉所有人,完成梯度更新。(也就是说,大家跑的数据是不同的,但是更新的梯度是一致的。)

还有一些需要处理的地方,比如大部分情况下,分布式的进程之间执行的都是相同的代码,但是有些只需处理一次的工作,比如ckpt的保存,就需要指定单个进程来完成。 于是需要对进程进行编号,指定特定编号的进程去完成特定的工作,这个编号是rank。

分布式训练中的各种参数

来源:PyTorch分布式训练基础–DDP使用

  • rank:用于表示进程的编号/序号(在一些结构图中rank指的是软节点,rank可以看成一个计算单位),每一个进程对应了一个rank的进程,整个分布式由许多rank完成。
    • rank与GPU之间没有必然的对应关系,一个rank可以包含多个GPU;一个GPU也可以为多个rank服务(多进程共享GPU)。
  • node:物理节点,可以是一台机器也可以是一个容器,节点内部可以有多个GPU。
  • rank与local_rank: rank是指在整个分布式任务中进程的序号;local_rank是指在一个node上进程的相对序号,local_rank在node之间相互独立。
  • nnodes、node_rank与nproc_per_node:nnodes是指物理节点数量,node_rank是物理节点的序号;nproc_per_node是指每个物理节点上面进程的数量。
    • node_rank 是和 node, nnodes 一起命名的,和 rank 没关系
  • word size : 全局(一个分布式任务)中,rank的数量。

还有一些与通信相关的参数(多机训练肯定要通信的呀)

  • backend :通信后端,可选的包括:nccl(NVIDIA推出)、gloo(Facebook推出)、mpi(OpenMPI)。从测试的效果来看,如果显卡支持nccl,建议后端选择nccl,其它硬件(非N卡)考虑用gloompi(OpenMPI)。
  • master_addr与master_port:主节点的地址以及端口,供init_method 的tcp方式使用。 因为pytorch中网络通信建立是从机去连接主机,运行ddp只需要指定主节点的IP与端口,其它节点的IP不需要填写。 这个两个参数可以通过环境变量或者init_method传入。

分布式训练中 batch size 的设置
除了分布式训练本身的参数设置,还要考虑到别的参数随着分布式训练要进行的变动。

补充资料(可看看):

  • 深度学习多机多卡batchsize和学习率的关系: 发现单机多卡和多机多卡即使全局batchsize对齐训练结果仍有较大的diff,实验结果是差了2个多点。

以前总是听到学习率和batchsize成正比例变化这样的说法,之前做reid实验的时候确实是这么回事:记得当时作者的模型是四卡DP模式训练的全局batchsize是256,相当于每张卡上的batchsize是64.作者的学习率是0.00375我复现的实验因为实验室没有卡只能用两卡实验,修改全局batchsize为128,学习率0.001875,然后还有个重要的参数,就是每个epoch遍历的iter数,因为作者的dataloader是iterloader,就是说一个epoch不是见过了所有的数据就结束,而是达到我制定的iter数目才结束,所以一个epoch可能会见到一两遍全部的数据。然后作者的iter数目设置的是400,我因为一个iter的bs只有128了,所以iter的数目设置了他的两倍就是800.修改上述三个参数实验结果和作者对齐了。reid这种任务因为要充分挖掘一个batch里面正负样本的信息,所以受到batchsize很大的影响,上述三个参数不管哪个不对都会很大的影响最后的结果。

这里让我疑惑的地方是,一个 batch 的数据送进去,计算出来的梯度是这个 batch 的数据的 sum,而不会除以 bs 的大小做平均吗?

搜到了这个问题:pytorch 如何实现梯度累积?。提问人说“因为受限于gpu资源,一次能跑起来的bath_size较小。所以想通过梯度累积的方式来解决这个问题。”并且贴了自己的代码。

我看他的代码里面在反向传播计算梯度之间,做了 loss /= batch_size

有人回答的时候贴出交叉熵损失的官方 api 接口:

torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')

这里写了个参数:reduction='mean' (我发现我居然一直都没有注意过这个参数)

这里有一个至关重要的默认参数 “reduction”,它的值必须是 none,mean,sum 中的任意一个。

所以结论是:如果实例化 loss_function 的时候,传进去的参数是 mean,那么一定要注意,mean 的维度是不是正确的(如果是 sum,也要注意 sum 的 dim);如果已经是 mean 参数了,那么自己再做 loss /= batch_size 就没意义

另一个人说:

batch size 和 learning rate 要等比例放大。如果遵从了这个原则,loss 那里就没必要除以 batch size。如果累积了 batch 但 learning rate 没变,那在 loss 那里改一下也是等效的。
但需要注意:特别大的 batch size 还需要再加上其他 trick 如 warmup 才能保证训练顺利(因为太大的初始 lr 很容易 train 出 nan)

另外有时间也可以看看:

  • 《动手学》-单机多卡、分布式,batch_size和learning rate的设置说明

分布式训练的参考资料

  • PyTorch分布式训练基础–DDP使用:写了单机多卡、多机多卡的方法(各种参数的说明、还有常用的函数),很详细,非常好
  • Pytorch多机多卡分布式训练:分享了多机多卡设置节点时候的坑。单机多卡的可以不看。
  • Pytorch 分布式训练: 整理性质的,写得也蛮细致全面的
  • 记录使用Pytorch分布式训练(torch.distributed)踩过的坑: 分享了遇到的坑:因为 Sampler 设置错误导致不同进程的loss不一致

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/257280.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【TwinCAT学习笔记 1】TwinCAT开发环境搭建

写在前面 作为技术开发人员,开启任何一项开发工作之前,首先都要搭建好开发环境,所谓磨刀不误砍材工,一定要有耐心,一次不行卸载再装。我曾遇到过一个学生,仅搭建环境就用了两周,这个过程也是一…

AI创作系统ChatGPT网站源码,AI绘画,支持GPT联网提问/即将支持TSS语音对话功能

一、AI创作系统 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI…

mybatis数据输出-map类型输出

1、建库建表 create table emp (empNo varchar(10) null,empName varchar(100) null,sal int null,deptno varchar(10) null ); 2、pom.xml <dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis<…

centOS使用docker部署ElasticSearch和Kibana

一、docker部署ElasticSearch 1、创建网桥 docker network create xybnet 2、下载镜像 docker pull elasticsearch:8.2.0 3、先运行容器 docker run -d \ --name es \ --net xybnet \ -p 9200:9200 \ -p 9300:9300 \ -p 5601:5601 \ -e "discovery.typesing…

web前端实现LED功能、液晶显示时间、数字

MENU 效果演示html部分JavaScript部分css部分 效果演示 html部分 <div id"app"><!-- 页面 --><div class"time-box"><!-- 时 --><div class"house-box"><bit-component :num"houseTem"></bit…

Qt/C++音视频开发58-逐帧播放/上一帧下一帧/切换播放进度/实时解码

一、前言 逐帧播放是近期增加的功能&#xff0c;之前也一直思考过这个功能该如何实现&#xff0c;对于mdk/qtav等内核组件&#xff0c;可以直接用该组件提供的接口实现即可&#xff0c;而对于ffmpeg&#xff0c;需要自己处理&#xff0c;如果有缓存的数据的话&#xff0c;可以…

实用篇 | 一文快速构建人工智能前端展示streamlit应用

----------------------- &#x1f388;API 相关直达 &#x1f388;-------------------------- &#x1f680;Gradio: 实用篇 | 关于Gradio快速构建人工智能模型实现界面&#xff0c;你想知道的都在这里-CSDN博客 &#x1f680;Streamlit :实用篇 | 一文快速构建人工智能前端展…

[ndss 2023]确保联邦敏感主题分类免受中毒攻击

Securing Federated Sensitive Topic Classification against Poisoning Attacks 摘要 我们提出了一种基于联邦学习 (FL) 的解决方案&#xff0c;用于构建能够检测包含敏感内容的 URL 的分布式分类器&#xff0c;即与健康、政治信仰、性取向等类别相关的内容。尽管这样的分类器…

微信小程序引入vant-weapp爬出坑

最新的微信小程序的项目结构跟之前的不一样&#xff0c;然后&#xff0c;按照vant-weapp上的官方文档&#xff0c;安装步骤失败&#xff0c;提示了各种错误。如果你的微信小程序结构跟我的一致&#xff0c;可以采用和我一样的方案。 微信小程序引入vant-weapp爬出坑 移动pack…

生成式AI赋能千行百业加速创新,2023亚马逊云科技re:Invent行业盘点

2023亚马逊云科技re:Invent全球大会已于上周圆满闭幕&#xff0c;在本次大会中&#xff0c;亚马逊云科技又为大家带来了很多功能/项目迭代更新&#xff0c;也重磅发布了很多全新的功能。今天从行业视角来盘点回顾哪些重磅发布适用于垂直行业客户&#xff0c;以及面向汽车、制造…

树莓派4B iio子系统 mpu6050

编写基于iio的mpu6050 遇到的问题&#xff0c;在读取数据时&#xff0c;读出来的数据不能直接拼接成int类型 需要先将其转换成short int&#xff0c;再转换成int 效果如图所示 注&#xff1a;驱动是使用的modprobe加载的 画的思维导图&#xff08;部分&#xff0c;上传的…

RAG应用程序的12种调优策略:使用“超参数”和策略优化来提高检索性能

本文从数据科学家的角度来研究检索增强生成(retrieve - augmented Generation, RAG)管道。讨论潜在的“超参数”&#xff0c;这些参数都可以通过实验来提高RAG管道的性能。与本文还将介绍可以应用的不同策略&#xff0c;这些策略虽然不是超参数&#xff0c;但对性能也会产生很大…