1.背景
训练时忽然发现某几项loss变成了nan。
2.nan的含义
nan值在python往往可以直接与无穷大,无穷小等价。
常见根本来源:
-
a/0
-
log(0)
-
空索引
第一个常见例子:
l
o
s
s
/
p
o
s
n
u
m
loss / posnum
loss/posnum,希望根据正例个数平分loss,却忽视pos_num可能为0,也就是图片中没有正例,常见解决
l
o
s
s
/
(
0.0001
+
n
u
m
)
loss/(0.0001 + num)
loss/(0.0001+num)或者if判断;
第二个常见例子:loss计算中带有log( p)时,p却为0,后果就是反传梯度无穷大;
第三个常见例子:mask = gt>0, 然后索引 pos_p = pred[mask],却不知此时的pos_p为空;
3.解决
原因:上面第二条
debug
当出现nan后在模型forward函数上打断点,结果发现在第一步时就出现了nan,正常的x输入后出来就有nan值。
def forward(self, x):
x = self.base_layer(x)
...
而self.base模块只是简单的卷积模块:
self.base_layer = nn.Sequential(
nn.Conv2d(3,self.channels[0], kernel_size=7, stride=1,padding=3,bias=False),
nn.BatchNorm2d(self.channels[0],momentum=0.1),
nn.ReLU(inplace=True)
)
再打印出它的学习参数:
for p in self.base_layer.parameters():
print(p)
结果发现大部分参数已经是nan了,而这里只是整个模型第一层…
显然梯度爆炸了,自然检查去loss。
重新开始,在总的loss处debug:
if(torch.isnan(loss).sum()>0):
print("here!")
当首次出现loss时在此处断点,结果发现,nan来源某一项loss,我这里是focal loss,显然是来自里面的log(p ),而p,也就是网络预测为0了,本该 p ∈ [ 0 , 1 ] p\in[0, 1] p∈[0,1],怎么会有0?
最后想起是Sigmoid函数:
z['hm'] = z['hm'].sigmoid() * out_branch
最后调整为:
z['hm'] = torch.clamp(z['hm'].sigmoid(),min=1e-4,max=1-1e-4) * out_branch