我是一名深度学习的初学者。目前正在努力理解反向传播算法。我在网上找到了一段关于使用Sigmoid激活函数的简单神经网络的反向传播代码。
#Step 1 Collect Datax = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]])y = np.array([[0], [1], [1], [0]])#Step 2 build modelnum_epochs = 60000#initialize weightssyn0 = 2np.random.random((3,4)) - 1 syn1 = 2np.random.random((4,1)) - 1def nonlin(x,deriv=False): if(deriv==True): return x*(1-x) return 1/(1+np.exp(-x)) for j in xrange(num_epochs): #feed forward through layers 0,1, and 2 k0 = x k1 = nonlin(np.dot(k0, syn0)) k2 = nonlin(np.dot(k1, syn1)) #how much did we miss the target value? k2_error = y - k2 if (j% 10000) == 0: print "Error:" + str(np.mean(np.abs(k2_error))) #in what direction is the target value? k2_delta = k2_error*nonlin(k2, deriv=True) #how much did each k1 value contribute to k2 error k1_error = k2_delta.dot(syn1.T) k1_delta= k1_error * nonlin(k1,deriv=True) syn1 += k1.T.dot(k2_delta) syn0 += k0.T.dot(k1_delta)
我无法理解这行代码:k2_delta = k2_error*nonlin(k2, deriv=True)
。在计算局部梯度时,为什么使用k2_error
乘以k2的导数。我们应该使用k2_error
以外的其他东西吗?因为这个算法中的成本函数是绝对值,所以我应该使用一个[-1,1,1,-1]
的向量作为成本函数的局部梯度吗?我假设这里使用的是解析梯度。
回答:
你可以按原样使用k2_error
。我测试了你的代码(在进行格式调整后),并确认它最小化了绝对误差,这与k2_error
(你算法中梯度下降的表面但非实际目标)不同。使用k2_delta = k2_error*nonlin(k2, deriv=True)
是因为算法在最小化绝对误差而不是k2_error
。这是如何运作的:
k2_error
与k2
输入之间的关系
k2_error
相对于k2
的导数是-1。使用链式法则,k2_error
相对于k2
输入的导数是(-1)*(nonlin(k2, deriv=True))
。
因此:
k2_error
相对于k2
输入的导数总是负的。这是因为(nonlin(k2, deriv=True))
总是正的。- 因此,
k2_error
的常规梯度下降最小化将始终希望将k2
的输入向上推(使其更正)以使k2_error
更负。
最小化绝对误差
对于k2_error = y-k2
,有两种实际可能性,每种可能性都意味着不同的策略来最小化绝对误差(我们的真正目标)。(有一个不太可能的第三种可能性我们可以忽略。)
-
情况1:
y
<k2
,这意味着k2_error
<0- 为了使
y
和k2
更接近(最小化绝对误差),我们需要使误差更大/更正。我们从第一部分知道,我们可以通过将k2
的输入向下推来实现这一点(当k2
的输入减少时,k2_error
增加)。
- 为了使
-
情况2:
y
>k2
,这意味着k2_error
>0- 为了使
y
和k2
更接近(最小化绝对误差),我们需要使误差更小/更负。我们从第一部分知道,我们可以通过将k2
的输入向上推来实现这一点(当k2
的输入增加时,k2_error
减少)。
- 为了使
总结来说,如果k2_error
为负(情况1),我们通过将k2
的输入向下推来最小化绝对误差。如果k2_error
为正(情况2),我们通过将k2
的输入向上推来最小化绝对误差。
k2_delta
的解释
我们现在知道,k2_error
的梯度下降最小化将始终希望将k2
的输入向上推,但这只有在y
> k2
(上面的情况2)时才会最小化绝对误差。在情况1中,将k2
的输入向上推会增加绝对误差——所以我们通过在面对情况1时翻转其符号来修改k2
输入处的梯度,这个梯度被称为k2_delta
。情况1意味着k2_error
<0,这意味着我们可以通过将k2_delta
乘以k2_error
来翻转梯度的符号!使用这种翻转意味着当我们看到情况1时,梯度下降希望将k2
的输入向下推而不是向上推(我们强迫梯度下降放弃其默认行为)。
总结来说,使用k2_delta = k2_error*nonlin(k2, deriv=True)
仅在面对情况1时翻转常规梯度的符号,这确保我们始终在最小化绝对误差(而不是最小化k2_error
)。
重要说明
你的算法通过添加负梯度来修改权重。通常,梯度下降通过减去梯度来修改权重。添加负梯度是相同的事情,但这确实使我的回答有些复杂。例如,k2
输入处的梯度实际上是k2_delta = k2_error*(-1)*nonlin(k2, deriv=True)
,而不是k2_delta = k2_error*nonlin(k2, deriv=True)
。
你可能想知道为什么我们使用k2_error
而不是sign(k2_error)
,那是因为我们希望随着k2_error
变小,权重移动的量也变小。