我怎样才能对一个带有 sigmoid 激活的简单神经网络进行反向传播?

how can I do a back propagation for a simple neural net with sigmoid activation?

我是深度学习的初学者。我目前正在为反向传播算法而苦苦挣扎。我在网上找到了一段带有 sigmoid 激活函数的简单神经网络反向传播的代码。

#Step 1 Collect Data
x = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]])
y = np.array([[0], [1], [1], [0]])

#Step 2 build model
num_epochs = 60000

#initialize weights
syn0 = 2np.random.random((3,4)) - 1 
syn1 = 2np.random.random((4,1)) - 1

def 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] 的向量作为成本函数的局部梯度吗?我在这里假设它使用分析梯度。

我认为这段代码取自这里:http://iamtrask.github.io/2015/07/12/basic-python-network/

每一步都有很好的解释。

此处的成本函数是实际值与预期值之间的差异:

k2_error = y - k2

因此我们将其乘以激活函数的导数。

您可以照原样使用 k2_error。我测试了您的代码(在进行格式更改后)并确认它最小化了绝对误差,这与 k2_error (算法中梯度下降的表面但不是实际目标)不同。 k2_delta = k2_error*nonlin(k2, deriv=True) 因为算法正在最小化绝对误差而不是 k2_error。工作原理如下:

k2_error与输入到k2的关系

k2_errork2 的导数是 -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 有两种实际可能性,每种可能性都意味着一种不同的策略来最小化绝对误差(我们的真正目标)。 (我们可以忽略不太可能的第三种可能性。)

  • 情况一:y<k2,即k2_error<0

    • 为了让yk2靠得更近(最小化绝对误差),我们需要让误差larger/more-positive。我们从第一部分知道,我们可以通过将 k2 的输入向下推(当 k2 的输入减少时 k2_error 增加)来做到这一点。
  • 情况2:y>k2,即k2_error>0

    • 为了使yk2靠得更近(最小化绝对误差),我们需要使误差smaller/more-negative。我们从第一部分知道,我们可以通过将 k2 的输入推高来做到这一点(当 k2 的输入增加时,k2_error 减少)。

总而言之,如果 k2_error 为负(情况 1),我们通过将 k2 的输入下推来最小化绝对误差。如果 k2_error 为正(情况 2),我们通过将 k2 的输入上推来最小化绝对误差。

k2_delta

的解释

我们现在知道 k2_error 的梯度下降最小化总是希望将 k2 的输入向上推,但这只会在 y > [= 时最小化绝对误差15=](上面的案例 2)。在情况1中,将k2的输入上推会增加绝对误差——所以我们修改k2输入处的梯度,称为k2_delta,每当我们面对案例 1 时,通过翻转它的符号。案例 1 意味着 k2_error<0,这意味着我们可以通过将 k2_delta 乘以 [=10 来翻转梯度的符号=]!使用这种翻转意味着当我们看到情况 1 时,梯度下降想要将 k2 的输入向下推而不是向上(我们强制梯度下降放弃其默认行为)。

总而言之,仅当我们面对情况 1 时,使用 k2_delta = k2_error*nonlin(k2, deriv=True) 才翻转通常梯度的符号,这确保我们始终最小化绝对误差(与最小化 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 变小,我们想移动更小的权重。