我需要编写一个简单的由1个输出节点、1个包含3个节点的隐藏层和1个输入层(大小可变)组成的神经网络。现在我只是尝试用xor数据进行训练,所以我们假设有3个输入节点(其中一个节点代表偏置,始终为1)。数据标记为0,1。
我推导了反向传播的方程,发现尽管非常简单,我的代码并未能使xor数据收敛到正确的结果。
设W为连接输入层和隐藏层的3×3权重矩阵,w为连接隐藏层和输出层的1×3矩阵。以下是我方法的一些辅助函数
def feed_forward_predict(x, W, w): sigmoid = lambda x: 1/(1+np.exp(-x)) z = np.array(list(map(sigmoid, np.matmul(W, x)))) L = sigmoid(np.matmul(w, z)) return [L, z, x]
这个函数只是接收一个值并使用公式sig(w*sig(W*x))进行预测。我们还有
def calculate_objective(data, labels, W, w): obj = 0 for point, label in zip(data, labels): L, z, x = feed_forward_predict(point, W, w) obj += (label - L)**2 return obj
这个函数计算一组给定数据点的均方误差。这两个函数应该都能正常工作,因为我手动检查过它们。现在问题出现在反向传播算法中
def back_prop(traindata, trainlabels): sigmoid = lambda x: 1/(1+np.exp(-x)) sigmoid_prime = lambda x: np.exp(-x)/((1+np.exp(-x))**2) W = np.random.rand(3, len(traindata[0])) w = np.random.rand(1, 3) obj = calculate_objective(traindata, trainlabels, W, w) print(obj) epochs = 10_000 eta = .01 prevobj = np.inf i=0 while(i < epochs): prevobj = obj dellw = np.zeros((1,3)) for point, label in zip(traindata, trainlabels): y, z, x = feed_forward_predict(point, W, w) dellw += 2*(y - label) * sigmoid_prime(np.dot(w, z)) * z w -= eta * dellw for point, label in zip(traindata, trainlabels): y, z, x = feed_forward_predict(point, W, w) temp = 2 * (y - label) * sigmoid_prime(np.dot(w, z)) # 注意s,u,v代表隐藏节点的权重。我的教授要求这样做 dells = temp * w[0][0] * sigmoid_prime(np.matmul(W[0,:], x)) * x dellu = temp * w[0][1] * sigmoid_prime(np.matmul(W[1,:], x)) * x dellv = temp * w[0][2] * sigmoid_prime(np.matmul(W[2,:], x)) * x dellW = np.array([dells, dellu, dellv]) W -= eta*dellW obj = calculate_objective(traindata, trainlabels, W, w) i = i + 1 print("i=", i, " Objective=",obj) return [W, w]
然而,尽管在矩阵乘法和导数计算上看似正确,这段代码并未能收敛到任何结果。实际上,误差一直在波动:先下降,然后上升,再次下降到相同的位置,然后再次上升。我认为问题出在W矩阵的梯度上,但我不知道具体是什么问题。
如果你想亲眼看看发生了什么,我使用的输入数据是
0: 0 0 10: 1 1 11: 1 0 11: 0 1 1
其中第一个数字代表标签。我还将随机种子设置为np.random.seed(0)
,这样我处理的矩阵就能保持一致。
回答:
我想我已经解决了问题,在我的代码中,我没有对隐藏节点权重的导数进行累加,而是在每次循环迭代时进行了赋值。正确的版本应该是这样的
for point, label in zip(traindata, trainlabels): y, z, x = feed_forward_predict(point, W, w) temp = 2 * (y - label) * sigmoid_prime(np.dot(w, z)) # 注意s,u,v代表隐藏节点的权重。我的教授要求这样做 dells += temp * w[0][0] * sigmoid_prime(np.matmul(W[0,:], x)) * x dellu += temp * w[0][1] * sigmoid_prime(np.matmul(W[1,:], x)) * x dellv += temp * w[0][2] * sigmoid_prime(np.matmul(W[2,:], x)) * x