目录
简介
本篇文章详细教如何用numpy手动搭建一个简单的两层神经网络,对每一步的操作都有详细的解释。涉及正向传播,反向传播,链式求导。参考教材:邱锡鹏《神经网络与深度学习》
项目开始
一、确定要创建的神经网络的有关数据
该神经网络以两层网络,即包含一层隐藏层,且输入节点为4,隐藏层节点为3,输出层节点为2为例。(忽略偏置值b)
Tips:原则上隐藏层的节点数可以为任意值,但恰当的节点数有利于收敛和节省运算成本。这里以隐藏层节点数为3为例。
X :为一个4维的列向量,即[x1,x2,x3,x4].T,但是在计算时我们要将他作为一个四行一列的二维矩阵去运算,X=(4,1)
Y :真实值,二维的列向量,同理,运算时要变成一个二行一列的二维矩阵去运算,Z=(2,1)
L :层数,层数一般只考虑隐藏层和输出层,输入层也可称为第0层。
W :权重矩阵,此时w的命名方式是从后向前的,即某个权重WL_ij表示为第L层的第 i 个节点与第L-1层的第 j 个节点的连接权重值。
如:W1_32应当表示为第1层的第三个节点与第0层的第二个节点的权重值。
第L层的权重矩阵的shape应当是(ML,M(L-1)),ML代表该层的节点数。
如下图所示,W1的shape应当是 (3,4) ,W2的shape应当是 (2,3) .(为避免线太多造成混乱,仅画出部分连线)
二、搭建神经网络
2.1 创建数据
创建训练集,初始化权重矩阵W1,W2,代码如下:
import numpy as np
# 创建数据
X = np.array([[1],[2],[3],[4]])
Y = np.array([[100],[200]])
# 初始化权重矩阵W1为(3,4),W2为(2,3)
W1 = np.ones((3,4))
W2 = np.ones((2,3))
2.2 正向传播
给隐藏层命名为h,正向传播求出预测值y。图片与代码如下,图片中,权重矩阵以上标的希腊数字代表第几层,下标代表第某行某列个元素。
Tips:为简单起见,设所有的激活函数都为f(x)=x,就等于他本身,且求导简单,求导为1.
# 正向传播
h = np.dot(W1,X)
y = np.dot(W2,h)
2.3 计算误差
这里我们以均方差作为误差函数,Loss的值应当是一个标量,均方差公式为:
loss = np.sum((Z-y)**2)/2
2.4 求偏导
2.4.1 求偏导一般式
一般来讲,应该是直接用误差Loss对权重矩阵直接求导,但是因为内部的计算涉及向量对矩阵的微分,计算十分繁琐且容易混乱,所以我们利用链式法则,分别求出对每一个具体的权重的偏导,最后再“拼”回矩阵。
先看下图,先推广一般式,然后再套入本例中。(下图来自邱锡鹏《神经网络与深度学习》)
所以根据链式法则,对某个权值求偏导,公式为:
先给个新定义-->,叫做误差项,代表右边部分,即:,
对其求导计算,如下图所示:
将其拆开成两部分,左边部分是向量对标量求导,得到的结果应该是向量。
右边部分继续链式法则拆开,拆成三项:
第一项的计算结果是由激活函数的导函数构成的对角阵。
第二项的值就是L+1层的权重矩阵的转置。
第三项就是下一层的误差项。
由第三项可以发现,我们想求导就需要先得到下一层的误差项的值。
所以我们的求解方式就是先求出最后一层的误差项,然后再往前推,分别求出每层的误差项,再分别对每一层的权重矩阵求导,这就是反向传播。
2.4.2 求偏导(对本例)
根据上面思路,要解决本例,先求最后一层即第二层的误差项,,先看对y1的求导,如下所示,对y2的求导同理,故得到最后一层的误差项。
再用反向传播求出第一层的误差项。
现在我们已经求出每一层的误差项了,那么就开始对每一层的权重矩阵中的某个权重求导。
对某个权值求导,例如对W1_23求导,左半部分如下图所示
所得结果为仅第 i 个元素为 xj ,其余值都为0的向量。该例子中仅h2含有W1_23的项,其余不含该项,故其结果为0.(可回头看回图1,h的求值过程)
再结合右半部分,计算结果如下,结果为标量
然后用同样的方法求出每一个wij的偏导,如下图所示,会发现规律:对某一层的权重矩阵的偏导就等于该层的误差项与上一层的输入的乘。
求偏导代码如下:
# 求误差项
S2 = y-Z
S1 = np.dot(W2.T, S2)
# 求偏导
dW1 = np.dot(S1, X.T)
dW2 = np.dot(S2, h.T)
2.5 更新参数
# 学习率lr
lr = 0.0001
# 更新w
W1 = W1 - lr*dW1
W2 = W2 - lr*dW2
到这里就完成了一次梯度更新,进行了一次训练,要进行多次训练就要回到步骤2.2,一直重复步骤2.2到步骤2.5。将其写成一个函数,代码如下:
def Train(X,Y,lr,epochs):
W1 = np.ones((3,4))
W2 = np.ones((2,3))
# 迭代
for i in range(epochs):
# 正向传播
h = np.dot(W1,X)
y = np.dot(W2,h)
# 求loss
loss = np.sum((Z-y)**2)/2
# 求误差项
S2 = y-Z
S1 = np.dot(W2.T, S2)
# 求偏导
dW1 = np.dot(S1, X.T)
dW2 = np.dot(S2, h.T)
# 更新w
W1 = W1 - lr*dW1
W2 = W2 - lr*dW2
return W1,W2
2.6 预测
用训练好的权重矩阵去预测
# 创建数据
X = np.array([[1],[2],[3],[4]])
Y = np.array([[100],[200]])
# 训练
W1, W2 = Train(X,Z,0.0001, 100)
print(W1)
print(W2)
# 预测(其实就相当于进行一次正向传播)
h = np.dot(W1,X)
y = np.dot(W2,h)
print(y)
预测结果如下
可见我们预测的y值非常接近真实值Y,拟合成功。
三 总代码
具体总代码
import numpy as np
def Train(X,Y,lr,epochs):
# 初始化权重矩阵
W1 = np.ones((3,4))
W2 = np.ones((2,3))
for i in range(epochs):
# 正向传播
h = np.dot(W1,X) # 第一层
y = np.dot(W2,h) # 第二层
# 求loss
loss = np.sum((Y-y)**2)/2
# 求误差项
S2 = y-Y
S1 = np.dot(W2.T, S2)
# 求偏导
dW1 = np.dot(S1, X.T)
dW2 = np.dot(S2, h.T)
# 更新w
W1 = W1 - lr*dW1
W2 = W2 - lr*dW2
return W1,W2
# 创建数据
X = np.array([[1],[2],[3],[4]])
Y = np.array([[100],[200]])
# 训练
W1, W2 = Train(X,Y,0.0001, 100)
print(W1)
print(W2)
# 预测(其实就相当于进行一次正向传播)
h = np.dot(W1,X)
y = np.dot(W2,h)
print(y)