1. 用torch tensor在GPU上运算来生成数据集,加速数据生成
- 如果数据集需要在线生成(即在dataloader 里面 计算生成 feature 和label), 如果数据量比较大,并且涉及到矩阵运算,可以用torch tensor来计算。把矩阵放到GPU上计算的快。
- 如果构造dataset 时 是通过cuda tensor计算得到的,那么在创造dataloader 通过多线程加载数据时,请注意加上 import multiprocessing as mp,mp.set_start_method(‘spawn’) 不然多线程加载会出现问题。
# 当用cuda 生成feature时,又用dataloader 多线程加载数据集时,需要添加 下面函数。否则pytorch 会出错。
import multiprocessing as mp
mp.set_start_method('spawn')
2. 请注意检查输入数据集是否有脏数据比如nan,这个很容易导致你训练出现nan.
-
如果数据集是 numpy 格式,可以通过 np.any(np.isnan(x)) 来判断是否有 nan,即:
assert not np.any(np.isnan(x)),'nan exists!'
-
如果是数据集是torch.tensor格式,可以通过torch.any(torch.isnan(x)) 来判断是否有nan
assert not torch.any(torch.isnan(x)),'nan exists!'
3. 训练loss 出现nan 有可能的原因
1) 学习率太高,
2) loss函数不合适,
3) 数据本身,是否存在Nan
4) target本身应该是能够被loss函数计算的
注意几点:
数据集本身计算中产生nan
- torch.acos(x)时,如果x的值接近-1或者1则会出现nan的现象,此处建议对x进行范围限制,比如 torch.clip(x, -1+1e-6, 1-1e-6)
- 计算中的除法,一定要注意 分母是否为0的情况. 分母为0,也会产生nan的现象,可以通过加极小数来解决,如 1/(x+1e-6)
- 如果计算中有涉及到求逆运算,torch.inv(x) 请确保矩阵可逆(满秩),如果担心x不是满秩 可以加一个对角阵 x+torch.eye(x.shape[-1])
- 如果遇到先求acos, 然后求倒数,再求逆,可以对x 进行如下操作: x = torch.clip(x, -1+1e-6, 1-1e-6) , torch.acos(x)
x = torch.clip(x, -1+1e-6, 1-1e-6)
y = torch.acos(x)
z = 1/(y+1e-6)
w = torch.inv(z)
target 本身输入到loss函数时, 在计算loss过程中 产生nan,也就是target本身不能被loss函数计算
- 比如手动写 loss函数,比如交叉熵损失,涉及到 -torch.log(x), 此处要保证x不能为0,所以可以加上1e-6,如 -torch.log(x+1e-6)
- 比如sigmoid激活函数的target应该大于0
4. 当计算交叉熵时,如果你的pred结果 有mask,可以通过下面进行交叉熵计算
"""
此处只限于pytorch 的使用
默认mask 里面 是 1:有效值得位置,0:无效值的地方
- check_mask_valid(mask): 检查mask里面是否全为0,如果全为0,则整个预测结果也无效,没必要计算loss
- maskNLLLoss(pred, target, mask): 带mask的交叉熵损失,如果mask 全为0,则返回带梯度的tensor(0., requres_grad=True)
- calAcc(pred, target, mask): 带mask的ACC,如果mask全为0, 则返回1。(这里不需要梯度)
"""
def check_mask_valid(mask):
# 检查 mask是否全为0, 如果全为0, 则说明整个target 都是无效的
if torch.sum(mask.float()).item() == 0:
return False
else:
return True
def maskNLLLoss(pred, target, mask):
# 返回的loss 必须是一个带梯度的tensor,因为train函数里面要做loss.backward()
if not check_mask_valid(mask):
return torch.tensor(0., requres_grad=True)# 如果mask全为0,则值无效,loss 返回为0,这里最好返回一个tensor并且带有grad,不然backward时 会出问题
target1hot = torch.nn.functional.one_hot(target.long(), pred.shape[1]).permuate(0,3,1,2).float()# B*C*H*W
crossEntropy = -torch.log((target1hot * pred).sum(dim=1) + 1e-6) * mask.float()# B*C*H*W
loss = crossEntropy.sum()/ mask.float().sum()
return loss
def calAcc(pred, target, mask):
# 返回的acc 可以是一个tensor 也可以是一个数值,因为acc不需要backward(),只是记录值
if not check_mask_valid(mask):
return 1
pred_ = torch.argmax(pred, dim=1)
equal = torch.eq(pred_.float(), target.float()).float()
masked_equal = equal * mask.float()
acc = torch.sum(masked_equal) / torch.sum(mask.float())
return acc.item()
5. 数据不均衡时,可以用focal loss 或者 对loss 进行权重调整
Focal loss
6. 梯度累积时,记得要除以累积次数 ,不然梯度会太大导致训练异常
"""
设置梯度累积参数acc_freq,在每次得到loss后,都要做一次 loss/acc_freq,不然一次性optimize.step()时会因为梯度太大而导致训练异常
if acc_freq > 0:# 梯度累积
loss = loss/acc_freq # 除以累积次数
"""
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, SubsetRandomSampler
import torch.optim as optim
val_split = 0.2
acc_freq = 4
# 数据集准备阶段
dataset = MyDataset()
dataset_size = len(dataset)
indices = list(range(dataset_size))
split = int(np.floor(val_split * dataset_size))
if shuffle_dataset:
np.random.seed(random_seed)
np.random.shuffle(indices)
train_indices, val_indices = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_indices)
valid_sampler = SubsetRandomSampler(val_indices)
train_loader = DataLoader(dataset, batch_size=4, sampler=train_sampler, num_workers=2)
# 模型准备阶段
lr = 1e-4
weight_decay = 0.2
Model = Model()
optimizer = torch.optim.Adam(model.parameters(), weight_decay=weight_decay, lr=lr)
# 训练阶段
def train():
for epoch in range(total_epoch):
for batch, (features, labels, mask) in enmuerate(train_loader):
pred = Model(features) # 预测
loss = loss_fn(pred, labels, mask) # 计算loss
acc = cal_acc(pred, labels, mask) # 计算acc
if acc_freq > 0:# 梯度累积
loss = loss/acc_freq # 除以累积次数
loss.backward()# 计算梯度
if (batch+1) % acc_freq == 0:
optimizer.step() # 反向传播
optimizer.zero_grad() # 清空梯度 非常重要(只要做optimizer.step(),就要接着zero_grad())
# update loss and acc
# save model