一、 调皮的dropout
这个在利用torch.nn.functional.dropout
的时候,其参数为:
torch.nn.functional.dropout(input, p=0.5, training=True, inplace=False)
注意这里有个training
指明了是否是在训练阶段,是否需要对神经元输出进行随机丢弃,这个是需要自行指定的,即便是用了model.train()
或者model.eval()
都是如此,这个和torch.nn.dropout
不同,因为后者是一个 层(Layer),而前者只是一个函数,不能纪录状态。
二、pytorch中的交叉熵
pytorch的交叉熵nn.CrossEntropyLoss
在训练阶段,里面是内置了softmax
操作的,因此只需要喂入原始的数据结果即可,不需要在之前再添加softmax
层。
这个和tensorflow的tf.softmax_cross_entropy_with_logits
如出一辙. pytorch的交叉熵nn.CrossEntropyLoss
在训练阶段,里面是内置了softmax
操作的,因此只需要喂入原始的数据结果即可,不需要在之前再添加softmax
层。这个和tensorflow的tf.softmax_cross_entropy_with_logits
如出一辙.
三、验证时取消掉梯度(no_grad)
一般来说,我们在进行模型训练的过程中,因为要监控模型的性能,在跑完若干个epoch
训练之后,需要进行一次在验证集上的性能验证。
一般来说,在验证或者是测试阶段,因为只是需要跑个前向传播(forward)就足够了,因此不需要保存变量的梯度。
保存梯度是需要额外显存或者内存进行保存的,占用了空间,有时候还会在验证阶段导致OOM(OutOfMemory)错误,因此我们在验证和测试阶段,最好显式地取消掉模型变量的梯度。在pytroch 0.4
及其以后的版本中,用torch.no_grad()
这个上下文管理器就可以了
例子:
model.train()
# here train the model, just skip the codes
model.eval()
# here we start to evaluate the model
with torch.no_grad():
for each in eval_data:
data, label = each
logit = model(data)
... # here we just skip the codes
如上,我们只需要在加上上下文管理器就可以很方便的取消掉梯度。这个功能在pytorch
以前的版本中,通过设置volatile=True
生效,不过现在这个用法已经被抛弃了。
四、显式指定model.train()
和model.eval()
我们的模型中经常会有一些子模型,其在训练时候和测试时候的参数是不同的,比如dropout
中的丢弃率和Batch Normalization
中的和等,这个时候我们就需要显式地指定不同的阶段(训练或者测试),在pytorch
中我们通过model.train()
和model.eval()
进行显式指定。
具体如:
model = CNNNet(params)
# here we start the training
model.train()
for each in train_data:
data, label = each
logit = model(data)
loss = criterion(logit, label)
... # just skip
# here we start the evaluation
model.eval()
with torch.no_grad(): # we dont need grad in eval phase
for each in eval_data:
data, label = each
logit = model(data)
loss = criterion(logit, label)
... # just skip
需要注意,在模型中有BN层或者dropout层时,在训练阶段和测试阶段必须显式指定train()和eval()。
五、进行梯度累积,实现内存紧张情况下的大batch_size
训练
在上面讨论的retain_graph
参数中,还可以用于累积梯度,在GPU显存紧张的情况下使用可以等价于用更大的batch_size
进行训练。
首先我们要明白,当调用.backward()
时,其实是对损失到各个节点的梯度进行计算,计算结果将会保存在各个节点上,如果不用opt.zero_grad()
对其进行清0,那么只要你一直调用.backward()
梯度就会一直累积,相当于是在大的batch_size
下进行的训练。
我们给出几个例子阐述我们的观点。
import torch
import torch.nn as nn
import numpy as np
class net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(10,2)
self.act = nn.ReLU()
def forward(self,inputv):
return self.act(self.fc1(inputv))
n = net()
inputv = torch.tensor(np.random.normal(size=(4,10))).float()
output = n(inputv)
target = torch.tensor(np.ones((4,2))).float()
loss = nn.functional.mse_loss(output, target)
loss.backward(retain_graph=True)
opt = torch.optim.Adam(n.parameters(),lr=0.01)
for each in n.parameters():
print(each.grad)
第一次输出:
tensor([[ 0.0493, -0.0581, -0.0451, 0.0485, 0.1147, 0.1413, -0.0712, -0.1459,
0.1090, -0.0896],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000]])
tensor([-0.1192, 0.0000])
在运行一次loss.backward(retain_graph=True)
,输出为:
tensor([[ 0.0987, -0.1163, -0.0902, 0.0969, 0.2295, 0.2825, -0.1424, -0.2917,
0.2180, -0.1792],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000]])
tensor([-0.2383, 0.0000])
第三次输出:
tensor([[ 0.1480, -0.1744, -0.1353, 0.1454, 0.3442, 0.4238, -0.2136, -0.4376,
0.3271, -0.2688],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000]])
tensor([-0.3575, 0.0000])
运行一次opt.zero_grad()
,输出为:
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])
tensor([0., 0.])
现在明白为什么我们一般在求梯度时要用opt.zero_grad()
了吧,那是为什么不要这次的梯度结果被上一次给影响,但是在某些情况下这个‘影响’是可以利用的。
< 完 >
ps: 想学习人工智能的小伙伴,这里有相关的资料;
点击 加群 获取资料 备注来源
人工智能课程第一节 机器学习是什么?
尚学堂人工智能课程 第二节 线性回归 梯度下降