矩阵维度在反向传播中不匹配

Matrix dimensions not matching in back propagation

我在这里尝试实现一个具有单个隐藏层的神经网络来对两个训练示例进行分类。该网络使用了 sigmoid 激活函数。

图层尺寸和权重如下:

X : 2X4
w1 : 2X3
l1 : 4X3
w2 : 2X4
Y : 2X3

我在反向传播中遇到矩阵维度不正确的问题。此代码:

import numpy as np

M = 2
learning_rate = 0.0001

X_train = np.asarray([[1,1,1,1] , [0,0,0,0]])
Y_train = np.asarray([[1,1,1] , [0,0,0]])

X_trainT = X_train.T
Y_trainT = Y_train.T

A2_sig = 0;
A1_sig = 0;

def sigmoid(z):
    s = 1 / (1 + np.exp(-z))  
    return s

def forwardProp() : 

    global A2_sig, A1_sig;

    w1=np.random.uniform(low=-1, high=1, size=(2, 2))
    b1=np.random.uniform(low=1, high=1, size=(2, 1))
    w1 = np.concatenate((w1 , b1) , axis=1)
    A1_dot = np.dot(X_trainT , w1)
    A1_sig = sigmoid(A1_dot).T

    w2=np.random.uniform(low=-1, high=1, size=(4, 1))
    b2=np.random.uniform(low=1, high=1, size=(4, 1))
    w2 = np.concatenate((w2 , b2) , axis=1)
    A2_dot = np.dot(A1_sig, w2)
    A2_sig = sigmoid(A2_dot)

def backProp() : 

    global A2_sig;
    global A1_sig;

    error1 = np.dot((A2_sig - Y_trainT).T, A1_sig / M)
    print(A1_sig)
    print(error1)
    error2 = A1_sig.T - error1

forwardProp()
backProp()

Returns 错误:

ValueError                                Traceback (most recent call last)
<ipython-input-605-5aa61e60051c> in <module>()
     45 
     46 forwardProp()
---> 47 backProp()
     48 
     49 # dw2 = np.dot((Y_trainT - A2_sig))

<ipython-input-605-5aa61e60051c> in backProp()
     42     print(A1_sig)
     43     print(error1)
---> 44     error2 = A1_sig.T - error1
     45 
     46 forwardProp()

ValueError: operands could not be broadcast together with shapes (4,3) (2,4) 

如何计算前一层的误差?

更新:

import numpy as np

M = 2
learning_rate = 0.0001

X_train = np.asarray([[1,1,1,1] , [0,0,0,0]])
Y_train = np.asarray([[1,1,1] , [0,0,0]])

X_trainT = X_train.T
Y_trainT = Y_train.T

A2_sig = 0;
A1_sig = 0;

def sigmoid(z):
    s = 1 / (1 + np.exp(-z))  
    return s


A1_sig = 0;
A2_sig = 0;

def forwardProp() : 

    global A2_sig, A1_sig;

    w1=np.random.uniform(low=-1, high=1, size=(4, 2))
    b1=np.random.uniform(low=1, high=1, size=(2, 1))
    A1_dot = np.dot(X_train , w1) + b1
    A1_sig = sigmoid(A1_dot).T

    w2=np.random.uniform(low=-1, high=1, size=(2, 3))
    b2=np.random.uniform(low=1, high=1, size=(2, 1))
    A2_dot = np.dot(A1_dot , w2) + b2
    A2_sig = sigmoid(A2_dot)

    return(A2_sig)

def backProp() : 
    global A2_sig;
    global A1_sig;

    error1 = np.dot((A2_sig - Y_trainT.T).T , A1_sig / M)
    error2 = error1 - A1_sig

    return(error1)

print(forwardProp())
print(backProp())

Returns 错误:

ValueError                                Traceback (most recent call last)
<ipython-input-664-25e99255981f> in <module>()
     47 
     48 print(forwardProp())
---> 49 print(backProp())

<ipython-input-664-25e99255981f> in backProp()
     42 
     43     error1 = np.dot((A2_sig - Y_trainT.T).T , A1_sig / M)
---> 44     error2 = error1.T - A1_sig
     45 
     46     return(error1)

ValueError: operands could not be broadcast together with shapes (2,3) (2,2) 

是否错误地设置了矩阵维度?

您的第一个权重矩阵 w1 的形状应为 (n_features, layer_1_size),因此当您将形状为 (m_examples, n_features)X 乘以 w1,你会得到一个 (m_examples, layer_1_size) 矩阵。这通过第 1 层的激活得到 运行,然后馈送到第 2 层,该层应该具有形状 (layer_1_size, output_size) 的权重矩阵,其中 output_size=3 因为您正在对 multi-label 进行分类3 类。如您所见,重点是将每一层的输入转换为适合该层神经元数量的形状,或者换句话说,一个层的每个输入都必须馈送到该层的每个神经元。

我不会像你那样对你的图层输入进行转置,我会按照所述调整权重矩阵,这样你就可以计算 np.dot(X, w1),等等

您似乎也没有正确处理自己的偏见。当我们计算 Z = np.dot(w1,X) + b1 时,应该广播 b1,以便将其添加到 w1X 乘积的每一列。如果您将 b1 附加到您的权重矩阵,则不会发生这种情况。相反,您应该在输入矩阵中添加一列 ones 并在权重矩阵中添加一行,因此偏差项位于权重矩阵的那一行,而输入中的 ones 确保它们得到无处不在。在此设置中,您不需要单独的 b1b2 项。

X_train = np.c_(X_train, np.ones(m_examples))

并记得在权重中再添加一行,因此 w1 的形状应为 (n_features+1, layer_1_size).

反向传播更新:

反向传播的目标是计算误差函数相对于权重和偏差的梯度,并使用每个结果更新每个权重矩阵和每个偏差向量。

因此您需要 dE/dw2dE/db2dE/dw1dE/db1,以便应用更新:

w2 <- w2 - learning_rate * dE/dw2
b2 <- b2 - learning_rate * dE/db2
w1 <- w1 - learning_rate * dE/dw1
b1 <- b1 - learning_rate * dE/db1

由于您正在进行多标签分类,因此您应该使用二元交叉熵损失:

您可以使用链式法则计算 dE/dw2

dE/dw2 = (dE/dA2) * (dA/dZ2) * (dZ2/dw2)

由于尚未应用激活,我正在使用 Z 用于您的 A2_dot,我正在使用 A2 用于您的 A2_sig

有关使用 sigmoid 激活的交叉熵损失的详细推导,请参阅 Notes on Backpropagation [pdf]。然而,这给出了逐点推导,而我们正在寻找矢量化实现,因此您将不得不做一些工作来找出矩阵的正确布局。不幸的是,也没有明确的偏差向量。

你对 error1 的表达看起来是正确的,但我会称之为 dw2,我会只使用 Y_train 而不是进行两次转置:

dw2 = (1/m) * np.dot((A2 - Y_train).T , A1)

你还需要 db2 应该是:

db2 = (1/m) * np.sum(A2 - Y_train, axis=1, keepdims=True)

你必须进一步应用链式法则才能得到 dw1db1,我会把它留给你,但是 [= 的第 3 周有一个很好的推导57=] Coursera 课程。

除了我认为您不应该在反向传播代码中进行该计算之外,关于您遇到错误的行我不能说太多,因此尺寸不匹配是有道理的。您可能会想到输出端的梯度,但我想不出任何类似的表达式涉及 A1 用于此网络中的反向传播。

This article 在 numpy 中有一个非常好的单隐藏层神经网络实现。它确实在输出端使用了 softmax,但它在隐藏层中有 sigmoid 激活,否则计算上的差异很小。它应该可以帮助您计算隐藏层的 dw1db1。具体来说,请查看标题为 "A neural network in practice".

的部分中 delta1 的表达式

将他们的计算转换为我们正在使用的符号,并在输出中使用 sigmoid 而不是 softmax,它应该如下所示:

dZ2 = A2 - Y_train
dZ1 = np.dot(dZ2, w2.T) * A1 * (1 - A1) # element-wise product

dw2 = (1/m) * np.dot(dZ2, A1.T)
db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True)

dw1 = (1/m) * np.dot(dZ1, X_train.T)
db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True)

代码审查

我检查了您的最新版本并发现了以下错误:

  • (minor) 在forward pass中,A1_sig从来没有用过,可能只是打错了。
  • (major) 在向后传递中,我不确定你打算用什么作为损失函数。从代码看,它看起来像 L2 损失:

    error1 = np.dot((A2_sig - Y_trainT.T).T , A1_sig / M)
    

    关键表达式是这样的:A2_sig - Y_trainT.T(虽然我可能只是不明白你的想法)。

    但是,您提到您正在进行多标签分类,很可能是二分类。在这种情况下,L2 损失是一个糟糕的选择(如果您对原因感兴趣,请参阅 )。相反,使用逻辑回归损失,a.k.a。交叉熵。在你的情况下,它是二进制的。

  • (critical) 在向后传递中,您跳过了 sigmoid 层。以下行采用损失误差并将其传递给线性层:

    error1 = np.dot((A2_sig - Y_trainT.T).T , A1_sig / M)
    

    ... 而前向传播在线性层之后通过 sigmoid 激活(这是正确的)。在这一点上,error1 没有任何意义,它的尺寸也无关紧要。

解决方案

我不喜欢你的变量命名,很容易混淆。所以我改变了它并稍微重新组织了代码。这是收敛的神经网络:

import numpy as np

def sigmoid(z):
  return 1 / (1 + np.exp(-z))

X_train = np.asarray([[1, 1, 1, 1], [0, 0, 0, 0]]).T
Y_train = np.asarray([[1, 1, 1], [0, 0, 0]]).T

hidden_size = 2
output_size = 3
learning_rate = 0.1

w1 = np.random.randn(hidden_size, 4) * 0.1
b1 = np.zeros((hidden_size, 1))
w2 = np.random.randn(output_size, hidden_size) * 0.1
b2 = np.zeros((output_size, 1))

for i in xrange(50):
  # forward pass

  Z1 = np.dot(w1, X_train) + b1
  A1 = sigmoid(Z1)

  Z2 = np.dot(w2, A1) + b2
  A2 = sigmoid(Z2)

  cost = -np.mean(Y_train * np.log(A2) + (1 - Y_train) * np.log(1 - A2))
  print(cost)

  # backward pass

  dA2 = (A2 - Y_train) / (A2 * (1 - A2))

  dZ2 = np.multiply(dA2, A2 * (1 - A2))
  dw2 = np.dot(dZ2, A1.T)
  db2 = np.sum(dZ2, axis=1, keepdims=True)

  dA1 = np.dot(w2.T, dZ2)
  dZ1 = np.multiply(dA1, A1 * (1 - A1))
  dw1 = np.dot(dZ1, X_train.T)
  db1 = np.sum(dZ1, axis=1, keepdims=True)

  w1 = w1 - learning_rate * dw1
  w2 = w2 - learning_rate * dw2
  b1 = b1 - learning_rate * db1
  b2 = b2 - learning_rate * db2