反向传播中的矩阵尺寸不匹配
Posted
技术标签:
【中文标题】反向传播中的矩阵尺寸不匹配【英文标题】:Matrix dimensions not matching in back propagation 【发布时间】:2018-05-30 08:34:42 【问题描述】:在这里,我尝试实现一个具有单个隐藏层的神经网络来对两个训练示例进行分类。该网络利用了 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()
返回错误:
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())
返回错误:
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)
矩阵维度设置错误?
【问题讨论】:
我刚刚注意到您的输出有三列,其中可以设置多列。你是在尝试Multi-label classification吗? @Imran 是的,尽管有三列,但它是多标签。 【参考方案1】:您的第一个权重矩阵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
因为您正在对 3 个类进行多标签分类。如您所见,关键是将每一层的输入转换为适合该层中神经元数量的形状,或者换句话说,层的每个输入都必须馈入该层中的每个神经元。
我不会像你一样对你的层输入进行转置,我会按照描述的方式塑造权重矩阵,这样你就可以计算 np.dot(X, w1)
等。
您似乎也没有正确处理自己的偏见。当我们计算Z = np.dot(w1,X) + b1
时,b1
应该被广播,以便添加到w1
和X
乘积的每一列。如果您将b1
附加到您拥有的权重矩阵,则不会发生这种情况。相反,您应该在输入矩阵中添加一列 ones
并在权重矩阵中添加一行,因此偏置项位于权重矩阵的该行中,而输入中的 ones
确保它们被添加到任何地方。在此设置中,您不需要单独的 b1
、b2
术语。
X_train = np.c_(X_train, np.ones(m_examples))
记得在你的权重中再增加一行,所以w1
的形状应该是(n_features+1, layer_1_size)
。
反向传播更新:
反向传播的目标是计算误差函数相对于权重和偏差的梯度,并使用每个结果更新每个权重矩阵和每个偏差向量。
因此,您需要 dE/dw2
、dE/db2
、dE/dw1
和 dE/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)
您必须进一步应用链式法则才能获得dw1
和db1
,我将把它留给您,但是Neural Networks and Deep Learning Coursera 课程的第 3 周有一个很好的推导。
除了我认为您不应该在反向传播代码中进行该计算之外,关于您遇到错误的行我不能说太多,因此尺寸不匹配是有道理的。您可能会想到输出处的梯度,但我想不出任何类似的表达式,涉及 A1
用于此网络中的反向传播。
This article 在 numpy 中很好地实现了一个隐藏层神经网络。它确实在输出处使用了 softmax,但它在隐藏层中有 sigmoid 激活,否则计算上的差异很小。它应该可以帮助您计算隐藏层的dw1
和db1
。具体来说,请查看“实践中的神经网络”一节中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)
【讨论】:
谢谢,请查看更新,我已尝试采纳您的建议。 yes w2 输出大小设置为 3 : size=(2, 3) 以适应多标签分类。【参考方案2】:代码审查
我检查了您的最新版本并注意到以下错误:
(minor) 在前向传球中,从未使用过A1_sig
,可能只是一个错字。
(major) 在后向传播中,我不确定您打算将什么用作损失函数。从代码来看,它看起来像是 L2 损失:
error1 = np.dot((A2_sig - Y_trainT.T).T , A1_sig / M)
关键表达式是这样的:A2_sig - Y_trainT.T
(虽然也许我只是不明白你的想法)。
但是,您提到您正在进行多标签分类,很可能是二元分类。在这种情况下,L2 损失是一个糟糕的选择(如果您对原因感兴趣,请参阅this post)。相反,使用逻辑回归损失,也就是交叉熵。在你的情况下,它是二进制的。
(critical) 在后向传播中,您已经跳过了 sigmoid 层。下面一行将损失误差传递给线性层:
error1 = np.dot((A2_sig - Y_trainT.T).T , A1_sig / M)
...而前向传递在线性层之后通过 sigmoid 激活(这是正确的)。此时,error1
没有任何意义,它的大小也无关紧要。
解决方案
我不喜欢你的变量命名,很容易混淆。所以我改变了它并重新组织了代码。这是收敛的 NN:
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
【讨论】:
谢谢你,向前的步骤应该是迭代循环的一部分吗?我的理解是反向传播是一个单独的步骤? NN 训练是向前和一遍又一遍地向后传递。您可以尝试只做其中一个,只是为了实验 - 优化不会起作用。 要对单个示例进行分类,我应该修改取 w1 和 b1 平均值的正向传递吗?目前正向传递对整个训练集进行分类。 你的意思是训练一个例子吗?您可以将X_train
拆分为多个批次,并对每个批次执行相同操作
我的意思是一旦训练完成就对一个新的例子进行分类。例如 [1,0,0,0]以上是关于反向传播中的矩阵尺寸不匹配的主要内容,如果未能解决你的问题,请参考以下文章