AI调参经验

训练AI的调参经验总结

环境配置

2023/10/09更新

conda安装

用miniconda,没必要用anaconda。

1
2
3
4
5
# 查看当前源: 
conda config --show-sources
# 配置源的文件.condarc一般位于系统用户目录之下
# 清理环境无用包
conda clean -p

重装miniconda的时候,提前备份安装目录下的envs文件夹以保留环境。

CUDA安装

下载CUDA11.7

双击exe安装即可,安装选项选择自定义,如果已有nvidia驱动(使用nvidia-smi命令判断)可以只勾选CUDA,否则全部勾选。一直下一步。

安装完成后使用nvcc -V确定自己的CUDA版本。

输出如下:

1
2
3
4
5
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2022 NVIDIA Corporation
Built on Tue_May__3_19:00:59_Pacific_Daylight_Time_2022
Cuda compilation tools, release 11.7, V11.7.64
Build cuda_11.7.r11.7/compiler.31294372_0

cuDNN安装

下载cuDNN,选择Download cuDNN v8.5.0 (August 8th, 2022), for CUDA 11.x。

解压压缩包,将里面的 bin / include / lib 三个文件夹直接复制到CUDA安装目录下(如果安装CUDA时默认安装路径,应该是C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.7),即合并到同名文件夹下。

在include文件夹下的cudnn.h中可以看到cudnn版本。

备注

CUDA有driver api和runtime api。nvidia-smi命令显示的 CUDA Version是driver api版本,nvcc -V显示是runtime api版本。runtime api的版本要低于或者等于driver api。

driver api是显卡驱动自带的,安装CUDA一般指安装CUDA Toolkit以支持runtime api。

如果安装有pytorch,可以使用pytorch查看 ptorch、CUDA 、 cuDNN 版本。

1
2
3
4
5
6
7
import torch
print(torch.__version__)
# 2.0.0
print(torch.version.cuda)
# 11.7
print(torch.backends.cudnn.version())
# 8500

本地开发pytorch程序并不是必须在电脑上安装CUDA和cuDNN。可以在conda环境下安装pytorch时顺便安装pytorch-cuda,这样就自带了。

1
conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia

pytorch

训练

训练时使用model.train(),测试时使用model.eval()。BatchNormalization在测试时参数固定,Dropout在测试时不再生效。

测试时使用model.eval()虽然梯度不再传播,但仍然计算。with torch.no_grad():,防止计算梯度,节省资源。

学习率大时收敛快,但收敛到一定程度后不再收敛,需要降低学习率。使用torch.optim.lr_scheduler.ExponentialLR()之类的API。

标准训练流程为(代码选自《pytorch深度学习实战》笔记 - Homeworld):

1
2
3
4
5
6
7
8
t_p = model(t_u, *params) 
loss = loss_fn(t_p, t_c)
# 清空累积梯度
optimizer.zero_grad()
# 计算梯度
loss.backward()
# 查看params.grad并更新params,从中减去学习率乘梯度,就像手动求导并更新参数
optimizer.step()

optimizer.zero_grad()也可以放在训练之前,只要不出现在loss.backward()optimizer.step()之间即可。参考Where should I place .zero_grad()? - PyTorch Forums

模型存取

  1. 保存整个模型

    1
    2
    3
    4
    5
    6
    # 保存
    model = Model()
    torch.save(model, 'model_name.pth')

    # 读取
    model = torch.load('model_name.pth')
  2. 保存模型参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 保存
    model = Model()
    # 可以保存字典,用于同时保存多个模型等情况
    torch.save({'model': model.state_dict()}, 'model_name.pth')、

    # 读取
    model = Model() # 先定义
    state_dict = torch.load('model_name.pth')
    model.load_state_dict(state_dict['model'])

    第一种方法可以直接保存模型,加载模型的时候直接把读取的模型给一个参数就行。它包含四个键,分别是model,optimizer,scheduler,iteration。

    第二种方法在读取模型参数前要先定义一个模型(模型必须与原模型相同的构造),然后对这个模型导入参数。

权重初始化

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Model(torch.nn.Module):
...

def init_weight(layer):
# or if isinstance(layer, nn.Conv2d):
if type(layer)==nn.Conv2d:
nn.init.normal_(layer.weight,mean=0,std=0.5)
elif type(layer)==nn.Linear:
nn.init.uniform_(layer.weight,a=-1,b=0.1)
nn.init.constant_(layer.bias,0.1)

model=Model()
# 此方法会自下而上递归调用model中所有层,初始化其权重
model.apply(init_weight)

不同的初始化API:torch.nn.init — PyTorch 1.12 documentation

Pytorch线性层采取的默认初始化方式是kaiming_uniform_初始化。

BN

1
torch.nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)

Paper \[ y=\frac {x-E[x]} {\sqrt{Var[x]+\epsilon} } *\gamma+\beta \] 在最小批的每个维度上计算均值和标准差,\(\gamma\)\(\beta\)则是可学习参数向量,大小为输入的特征数或通道数。\(\gamma\) 默认为1,\(\beta\)默认为0。

为了将输入调整到激活函数的敏感区,BatchNorm层要加在激活函数前面。

Dropout

1
torch.nn.Dropout(p=0.5, inplace=False)

在训练过程中,按伯努利分布采样概率p随机将输入张量的元素置零。在每次调用中每个通道将被独立地置零。这已经被证明是正则化的有效手段。

注意,训练时输出会乘以\(\frac{1}{1-p}\)。测试时模块简单计算一个恒等函数。

BN层的加入可以起到抑制过拟合的作用,在训练过程对每个单个样本的forward均引入多个样本(Batch个)的统计信息,相当于自带一定噪音,起到正则效果,无需添加Dropout。BN和Dropout同时使用会使精度下降,参考论文:

Li X, Chen S, Hu X, et al. Understanding the disharmony between dropout and batch normalization by variance shift[C]//Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2019: 2682-2690.

Dropout层一般放在激活函数层之后。

tensor操作

可以认为,对于tensor a:

1
a.reshape() = a.view() + a.contiguous().view()

tensor与numpy之间的转换:

1
2
3
4
5
# 若未使用with torch.no_grad(),需要额外使用.detach()
# .cpu()与.to("cpu")等价
# .cuda()与.to("cuda:0")等价,数字0代表第一张gpu
# 示例,将torch.Tensor a转换为numpy.ndarray b
a.detach().cpu().numpy()

并行化

训练AI模型有两类并行化,数据并行化和模型并行化。数据并行化指一张GPU处理一个数据切片(如一个batch的一部分),模型并行化指一张GPU处理一个模型切片(如模型的一层)。

对于数据并行化,pytorch有两种接口,torch.nn.DataParalleltorch.nn.parallel.DistributedDataParallel,前者为单机多线程,后者为单机/多机多进程且和模型并行化兼容。

对于模型并行化,有Pipleline并行化、Tensor并行化、Sequential并行化等方法,没有直接的接口。Pipleline并行化对模型按层划分到不同GPU上加速执行(类似CPU Pipeline),实现可以参考Single-Machine Model Parallel Best Practices — PyTorch Tutorials 2.1.0+cu121 documentation

loss nan

出现loss nan可能有以下原因

  • 训练数据中含有nan

  • 计算过程中溢出

    • 上溢出,进行譬如\(exp(x)\)之类的运算。
    • 下溢出,进行譬如\(\log(0)\)之类的运算。
  • 梯度爆炸

    • 每个batch前梯度没有零,optimizer.zero_grad()(pytorch)
    • 学习率过大
    • batchsize过大
    • 可以用torch.nn.utils.clip_grad_norm_(pytorch)避免

参数配置

常用的参数配置库有argparse和configparse。

argparse从命令行读取参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import argparse

parser = argparse.ArgumentParser(description='test')
# 加--为无序可选参数,不加是顺序必选参数。
# 可用required参数设置是否可选。
# 可用nargs设置传入参数的个数,'+' 表示传入至少一个参数。
parser.add_argument('--i', type=int, default=1help='数字')
args = parser.parse_args()
print(args.i)
# 运行命令:python -u "d:\code\TEST_PYTHON\main.py" --i 5
'''
Namespace(i=5)
5
'''

如果希望将argparse读取的参数写入文件,直接从文件读取,可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
import json

# 写入
parser = argparse.ArgumentParser(description='test')
# 参数略
args = parser.parse_args()
with open('config.txt', 'w') as f:
json.dump(args.__dict__, f, indent=2)

# 读取,不再需要从命令行传参
with open('config.txt', 'r') as f:
config = json.load(f)
args = argparse.Namespace(**config)

configparse从文件读取参数。


AI调参经验
https://reddish.fun/posts/Article/AI-training/
作者
bit704
发布于
2023年3月24日
许可协议