准备工作
现实现一个具有三层的最简单的神经网络,激活函数采用sigmod函数。整体结构如下所示:
- x是输入的样本值。设定为5*3的矩阵,即代表5个样本值,每个样本有3个特征值。
- y是标签,为5*1的矩阵代表每一个样本的标签。且取值范围为0或1。即完成一个简单的分类问题。
- w0、w1为中间的权重参数。L0、L1、L2表示每一层的计算结果。
- L2即是最终与标签比对的结果
首先是数据的初始化:
x = np.array(
[[0, 0, 1],
[0, 1, 1],
[1, 0, 1],
[1, 1, 1],
[0, 0, 1]]
)
y = np.array([
[0],
[1],
[1],
[0],
[0],
])
np.random.seed(1)
# w0 w1取值范围为 -1 到 1
w0 = 2 * np.random.random([3, 4]) - 1
w1 = 2 * np.random.random([4, 1]) - 1
因为神经网络可以大概分为两部分:前向传播与反向传播,且主要难点在反向传播。故要对反向传播进行公式的推导。
反向传播的推导
sigmod函数的定义
首先定义好sigmod函数:
def sigmod(x, deriv = False):
if(deriv == True):
return x * (1 - x)
return 1 / (1 + np.exp(-x))
该函数的deriv参数是为了方便进行反向传播的运算。我们知道sigmod函数具有一个很有意思的特征:
f
(
z
)
=
s
i
g
m
o
d
(
z
)
=
1
1
+
e
−
z
f(z) = sigmod(z) = \frac{1}{1+e^{-z}}
f(z)=sigmod(z)=1+e−z1
∂
f
∂
z
=
f
(
z
)
(
1
−
f
(
z
)
)
\frac{\partial f}{\partial z}=f(z)(1-f(z))
∂z∂f=f(z)(1−f(z))
故当进行反向传播时需要返回sigmod函数的导数,所以定义deriv参数方便返回值的调用。
损失函数
使用均方函数定义误差,如下:
L
o
s
s
=
1
2
(
l
2
−
y
)
2
Loss = \frac{1}{2}(l_2-y)^2
Loss=21(l2−y)2
分别对
w
1
w_1
w1、
w
0
w_0
w0求导:
d
L
o
s
s
d
w
1
=
(
l
2
−
y
)
d
l
2
d
w
1
=
(
l
2
−
y
)
l
2
(
1
−
l
2
)
l
1
\frac{dLoss}{dw_1} = (l_2-y)\frac{dl_2}{dw_1}=(l_2-y)l_2(1-l_2)l_1
dw1dLoss=(l2−y)dw1dl2=(l2−y)l2(1−l2)l1
d
L
o
s
s
d
w
0
=
(
l
2
−
y
)
d
l
2
d
w
0
=
(
l
2
−
y
)
d
l
2
d
l
1
d
l
1
d
w
0
=
(
l
2
−
y
)
l
2
(
1
−
l
2
)
w
1
l
0
l
1
(
1
−
l
0
)
\frac{dLoss}{dw_0} = (l_2-y)\frac{dl_2}{dw_0}=(l_2-y)\frac{dl_2}{dl_1}\frac{dl_1}{dw_0}=(l_2-y)l_2(1-l_2)w_1l_0l_1(1-l_0)
dw0dLoss=(l2−y)dw0dl2=(l2−y)dl1dl2dw0dl1=(l2−y)l2(1−l2)w1l0l1(1−l0)
其实就是简单的复合函数求导,难度较低。
在求得梯度之后就可以进行梯度下降算法更新
w
1
w_1
w1、
w
0
w_0
w0参数,进行迭代。
代码编写
设置迭代10万次,每一万次输出一下loss值
for j in range(100000):
l0 = x
l1 = sigmod(np.dot(l0, w0))
l2 = sigmod(np.dot(l1, w1))
l2_error = (y - l2) # 均方误差的导数值
l2_delta = l2_error * sigmod(l2, True) # 导数相乘。对应公式
l1_error = l2_delta.dot(w1.T)
l1_delta = l1_error * sigmod(l1, True)
w1 += l1.T.dot(l2_delta) # 每次更新梯度值
w0 += l0.T.dot(l1_delta)
if j % 10000 == 0:
print('Error:' + str(np.mean(np.abs(l2_error))))
总结
整体其实还是蛮抽象的,关键在于梯度的计算。
代码里面的每一个变量都能在公式里找到位置,因为最终更新公式太长了所以把一些变量分开写了。