(作者:陈玓玏)
在使用tensorflow建立深度神经网络的过程中,在几次迭代之后发现所有的权重都变为了nan,导致整个网络都无法正常工作。出现这个问题我知道的可能有以下两个原因。
1) 样本未进行归一化
因为每次迭代都要计算 σ ( x ∗ ω + b ) \sigma(x*\omega+b) σ(x∗ω+b),而深度神经网络的参数众多,如果样本不进行归一化,很容易出现 x ∗ ω + b x*\omega+b x∗ω+b溢出的情况。
解决方案:
我一般都是先用sklearn中的preprocessing.MinMaxScaler()函数将每一个特征在[最小值,最大值]区间进行尺度缩放,缩放到[0,1]区间。这样处理过后再出现nan就不会特征的问题了。
2)损失函数未进行溢出处理
2.1) 为什么使用交叉熵损失函数?
当输出层使用sigmoid激活函数时,如果在神经网络中使用平方损失函数,即使用
(
y
−
σ
)
2
/
2
(y-\sigma)^2/2
(y−σ)2/2
作为损失函数,那么参数更新的公式为(仅考虑一层时,也就是不需要使用到反向传播):
ω
=
ω
−
α
(
y
−
σ
)
σ
(
1
−
σ
)
x
\omega = \omega-\alpha(y-\sigma)\sigma(1-\sigma)x
ω=ω−α(y−σ)σ(1−σ)x
其中
σ
(
1
−
σ
)
\sigma(1-\sigma)
σ(1−σ)为sigmoid激活函数导数,使用这个损失函数就因为这一项导数存在一个问题:当我们犯的错误越大时,神经网络更新越慢。考虑二分类问题,y不是0就是1,预测值与0或1差别大,即接近0或1,但与真实值为相反的方向,此时z(
σ
\sigma
σ的自变量)是离x=0轴非常远的,从sigmoid函数的图像上看也知道,此时导数几乎为0,因为参数更新就会非常慢。
因此我们得出一个结论,sigmoid激活函数不适合与平方损失函数一起用,因为我们希望我们犯错严重时,更新更快。而交叉熵损失函数可以帮助我们达到这个目的。交叉熵损失函数公式如下:
−
1
/
2
m
∗
∑
i
=
1
m
(
y
ln
σ
+
(
1
−
y
)
ln
(
1
−
σ
)
)
-1/2m*\sum_{i=1}^m(y\ln \sigma+(1-y)\ln(1-\sigma))
−1/2m∗i=1∑m(ylnσ+(1−y)ln(1−σ))
导数自己求就可以,总之就是不再有sigmoid的导数那一项,仅仅是
y
−
σ
y-\sigma
y−σ乘以系数,那么预测值和真实差距大则参数更新快,差距小则参数更新慢。
这里有一个有意思的问题,就是交叉熵公式中预测值在对数内,真实值在对数外,能不能互换位置呢?这个问题我稍微想了以下,如果交换过来,那么损失函数的导数波动范围将会非常广,因为导数会是
−
1
/
2
m
∗
∑
i
=
1
m
(
σ
(
1
−
σ
)
ln
y
−
σ
(
1
−
σ
)
ln
(
1
−
y
)
)
x
-1/2m*\sum_{i=1}^m(\sigma(1-\sigma)\ln y-\sigma(1-\sigma)\ln(1-y))x
−1/2m∗i=1∑m(σ(1−σ)lny−σ(1−σ)ln(1−y))x
化简一下:
−
1
/
2
m
∗
∑
i
=
1
m
σ
(
1
−
σ
)
ln
y
/
(
1
−
y
)
x
-1/2m*\sum_{i=1}^m\sigma(1-\sigma)\ln y/(1-y)x
−1/2m∗i=1∑mσ(1−σ)lny/(1−y)x
这样不仅之前平方损失函数的问题依然存在,还可能有一个新的问题:
y
/
(
1
−
y
)
y/(1-y)
y/(1−y)可能会出现分子或分母为0的情况,即使不出现,放到
ln
\ln
ln中也可能会出现正负无穷的情况,导数太不稳定,因此不适合交换预测值和真实值的位置。
2.2) 使用交叉熵损失函数要注意的问题
交叉熵损失函数中存在对数,如果 ln \ln ln作用在0上,则是负无穷,就会溢出,因此需要考虑到这种可能,在代码中加入一个很小的常量,比如np.pow(10,-9),也就是 1 0 − 9 10^-9 10−9,避免溢出。