目录
写在前面的一些内容
本次习题与 神经网络与深度学习 pdf电子书的第104页(对应纸质版第92页)的图4.7有关,具体内容详见 [1]NNDL 作业3 。
本实验报告参考了NNDL 作业3 作者:HBU_fangerfang
和HBU-NNDL 作业3 作者:不是蒋承翰
的部分内容。
水平有限,难免有误,如有错漏之处敬请指正。
习题
实现例题中的前馈神经网络。[1]
- 使用Numpy实现(不使用深度学习框架)
- 使用Pytorch实现,并对比Numpy与Pytorch的实现效果
- 更改步长、迭代次数,观察效果
- 更改损失函数、激活函数,观察效果
具体步骤
过程推导
正向传播(前向传播)过程
下表为前馈神经网络的表示方法。
表示 | 含义 |
---|---|
L L L | 神经网络的层数 |
M l M_l Ml | 第 l l l层神经元的个数 |
f l ( ⋅ ) f_l(⋅) fl(⋅) | 第 l l l层神经元的激活函数 |
W ( l ) ∈ R M l × M l − 1 \boldsymbol{W}^{(l)} \in \mathbb{R}^{M_l \times M_{l-1}} W(l)∈RMl×Ml−1 | 第 l − 1 l-1 l−1层到第 l l l的权重矩阵 |
b ( l ) ∈ R M l \boldsymbol{b}^{(l)} \in \mathbb{R}^{M_l} b(l)∈RMl | 第 l − 1 l-1 l−1层到第 l l l的偏置 |
z ( l ) ∈ R M l \boldsymbol{z}^{(l)} \in \mathbb{R}^{M_l} z(l)∈RMl | 第 l l l层神经元的净输入(净活性值) |
a ( l ) ∈ R M l \boldsymbol{a}^{(l)} \in \mathbb{R}^{M_l} a(l)∈RMl | 第 l l l层神经元的输出(活性值) |
基本思想:从输入层的输入开始,每次将前一层的输出作为后一层的输入,如此循环至输出层输出为止。
设一共有
l
l
l层神经元。
输入层的输入:
x
\boldsymbol{x}
x输入层的输出:
a
(
0
)
=
x
\boldsymbol{a}^{(0)}=\boldsymbol{x}
a(0)=x第n个隐藏层的输入:
z
(
n
)
=
W
(
n
)
a
(
n
−
1
)
+
b
(
n
)
\boldsymbol{z}^{(n)}=\boldsymbol{W}^{(n)}\boldsymbol{a}^{(n-1)}+\boldsymbol{b}^{(n)}
z(n)=W(n)a(n−1)+b(n)第n个隐藏层的输出:
a
(
n
)
=
f
l
(
z
(
n
)
)
\boldsymbol{a}^{(n)}=f_l(\boldsymbol{z}^{(n)})
a(n)=fl(z(n))输出层的输入:
z
(
l
)
=
W
(
l
)
a
(
l
−
1
)
+
b
(
l
)
\boldsymbol{z}^{(l)}=\boldsymbol{W}^{(l)}\boldsymbol{a}^{(l-1)}+\boldsymbol{b}^{(l)}
z(l)=W(l)a(l−1)+b(l)输入层的输出:
y
=
f
l
(
z
(
l
)
)
\boldsymbol{y}=f_l(\boldsymbol{z}^{(l)})
y=fl(z(l))过程如题中所给的图4.7所示,计算时循环套用即可。
反向传播(后向传播)过程
下表为误差逆传播的表示方法。
表示 | 含义 |
---|---|
x i x_i xi | 第 i i i个输入神经元的输入 |
v i h v_{ih} vih | 输入层第 i i i个神经元与隐层第 h h h个神经元之间的连接权 |
γ h \gamma_h γh | 隐藏层第 h h h个神经元的阈值 |
α h = ∑ i = 1 d v i h x i \alpha_h=\sum_{i=1}^dv_{ih}x_i αh=∑i=1dvihxi | 第 h h h个隐层神经元的输入 |
b h b_h bh | 第 h h h个隐层神经元的输出 |
w h j w_{hj} whj | 隐层第 h h h个神经元与输出层第 j j j个神经元之间的连接权 |
θ j \theta_j θj | 输出层第 j j j个神经元的阈值 |
β j = ∑ h = 1 q w h j b h \beta_j=\sum_{h=1}^qw_{hj}b_h βj=∑h=1qwhjbh | 第 j j j个输出神经元的输入 |
y j y_j yj | 第 j j j个输出神经元的输出 |
令神经网络的输出为
y
^
=
(
y
1
k
^
,
y
2
k
^
,
.
.
.
,
y
l
k
^
)
\hat{\boldsymbol{y}}=(\hat{\boldsymbol{y}^k_1},\hat{\boldsymbol{y}^k_2},...,\hat{\boldsymbol{y}^k_l})
y^=(y1k^,y2k^,...,ylk^),即
y
^
j
k
=
f
(
β
j
−
θ
j
)
,(
1
)
\hat{y}^k_j=f(\beta_j-\theta_j),(1)
y^jk=f(βj−θj),(1)其中
β
j
\beta_j
βj时第j个神经元的输入。
该神经网络在
(
x
k
,
y
k
)
(\boldsymbol{x}_k,\boldsymbol{y}_k)
(xk,yk)上的均方误差为
E
k
=
1
2
∑
j
=
1
l
(
y
^
j
k
−
y
j
k
)
,(
2
)
E_k=\frac{1}{2}\sum_{j=1}^l(\hat{y}^k_j-y^k_j),(2)
Ek=21j=1∑l(y^jk−yjk),(2)任意参数的更新公式为
v
←
v
+
Δ
v
,(
3
)
v\leftarrow v+\Delta v,(3)
v←v+Δv,(3)BP算法基于梯度下降的策略,以目标的负梯度方向对参数进行调整。给定学习率
η
\eta
η,有
Δ
w
h
j
=
−
η
∂
E
k
∂
w
h
j
,(
4
)
\Delta w_{hj}=-\eta \frac{\partial E_k}{\partial w_{hj}},(4)
Δwhj=−η∂whj∂Ek,(4)
w
h
j
w_{hj}
whj先影响到
β
j
\beta_j
βj,再到
y
^
j
k
\hat{y}^k_j
y^jk,再到
E
k
E_k
Ek,有
∂
E
k
∂
w
h
j
=
∂
E
k
∂
y
^
j
k
⋅
∂
y
^
j
k
∂
β
j
⋅
∂
β
j
∂
w
h
j
,(
5
)
\frac{\partial E_k}{\partial w_{hj}}=\frac{\partial E_k}{\partial \hat{y}^k_j} \cdot \frac{\partial \hat{y}^k_j}{\partial \beta_j} \cdot \frac{\partial \beta_j}{\partial w_{hj}},(5)
∂whj∂Ek=∂y^jk∂Ek⋅∂βj∂y^jk⋅∂whj∂βj,(5)由
β
j
\beta_j
βj定义可知
∂
β
j
∂
w
h
j
=
b
h
,(
6
)
\frac{\partial \beta_j}{\partial w_{hj}}=b_h,(6)
∂whj∂βj=bh,(6)由Sigmoid函数的性质
f
′
(
x
)
=
f
(
x
)
(
1
−
f
(
x
)
)
,(
7
)
f'(x)=f(x)(1-f(x)),(7)
f′(x)=f(x)(1−f(x)),(7)
根据(1)式和(2)式,有
g
j
=
∂
E
k
∂
y
^
j
k
⋅
∂
y
^
j
k
∂
β
j
=
(
y
^
j
k
−
y
j
k
)
f
′
(
β
j
−
θ
j
)
=
y
^
j
k
(
1
−
y
^
j
k
)
(
y
j
k
−
y
^
j
k
)
,(
8
)
\begin{aligned} g_j &=\frac{\partial E_k}{\partial \hat{y}^k_j} \cdot \frac{\partial \hat{y}^k_j}{\partial \beta_j} \\ &=(\hat{y}^k_j-y^k_j)f'(\beta_j-\theta_j) \\ &=\hat{y}^k_j(1-\hat{y}^k_j)(y^k_j-\hat{y}^k_j),(8) \end{aligned}
gj=∂y^jk∂Ek⋅∂βj∂y^jk=(y^jk−yjk)f′(βj−θj)=y^jk(1−y^jk)(yjk−y^jk),(8)将(6)和(8)代入(5),再代入(4),可得
w
h
j
w_{hj}
whj的更新公式
Δ
w
h
j
=
η
g
j
b
h
,(
9
)
\Delta w_{hj}=\eta g_jb_h,(9)
Δwhj=ηgjbh,(9)类似可得
Δ
θ
j
=
−
η
g
j
,(
10
)
Δ
v
i
h
=
η
e
h
x
i
,(
11
)
Δ
γ
h
=
−
η
e
h
,(
12
)
\Delta \theta_j=-\eta g_j,(10)\\ \Delta v_{ih}=\eta e_hx_i,(11)\\ \Delta \gamma_h=-\eta e_h,(12)\\
Δθj=−ηgj,(10)Δvih=ηehxi,(11)Δγh=−ηeh,(12)其中
e
h
=
∂
E
k
∂
b
h
⋅
∂
b
h
∂
α
h
=
∑
j
=
1
l
∂
E
k
∂
β
j
⋅
∂
β
j
∂
b
h
f
′
(
α
h
−
γ
h
)
=
∑
j
=
1
l
w
h
j
g
j
f
′
(
α
h
−
γ
h
)
=
∑
j
=
1
l
w
h
j
g
j
⋅
b
h
(
1
−
b
h
)
\begin{aligned} e_h&=\frac{\partial E_k}{\partial b_h} \cdot \frac{\partial b_h}{\partial \alpha_h}\\ &=\sum_{j=1}^l\frac{\partial E_k}{\partial \beta_j} \cdot \frac{\partial \beta_j}{\partial b_h}f'(\alpha_h-\gamma_h)\\ &=\sum_{j=1}^lw_{hj}g_jf'(\alpha_h-\gamma_h)\\ &=\sum_{j=1}^lw_{hj}g_j \cdot b_h(1-b_h) \end{aligned}
eh=∂bh∂Ek⋅∂αh∂bh=j=1∑l∂βj∂Ek⋅∂bh∂βjf′(αh−γh)=j=1∑lwhjgjf′(αh−γh)=j=1∑lwhjgj⋅bh(1−bh)学习率
η
∈
(
0
,
1
)
\eta\in (0,1)
η∈(0,1)控制着算法每一轮迭代中的更新步长。
数值计算
输入值:x1, x2 = 0.5, 0.3
输出值:y1, y2 = 0.23, -0.07
激活函数:Sigmoid
损失函数:MSE
初始权值:0.2 -0.4 0.5 0.6 0.1 -0.5 -0.3 0.8
目标:通过反向传播优化权值
h
1
=
f
(
w
1
x
1
+
w
3
x
2
)
=
f
(
0.2
×
0.5
+
0.5
×
0.3
)
≈
0.56
h
2
=
f
(
w
2
x
1
+
w
4
x
2
)
=
f
(
−
0.4
×
0.5
+
0.6
×
0.3
)
≈
0.5
o
1
=
f
(
w
5
h
1
+
w
7
h
2
)
=
f
(
0.1
×
0.56
−
0.3
×
0.5
)
≈
0.48
o
2
=
f
(
w
6
h
1
+
w
8
h
2
)
=
f
(
−
0.5
×
0.56
+
0.8
×
0.3
)
≈
0.53
h_1=f(w_1x_1+w_3x_2)=f(0.2 \times 0.5+0.5 \times 0.3) \approx 0.56 \\ h_2=f(w_2x_1+w_4x_2)=f(-0.4 \times 0.5+0.6 \times 0.3) \approx 0.5 \\ o_1=f(w_5h_1+w_7h_2)=f(0.1 \times 0.56-0.3 \times 0.5) \approx 0.48 \\ o_2=f(w_6h_1+w_8h_2)=f(-0.5 \times 0.56+0.8 \times 0.3) \approx 0.53 \\
h1=f(w1x1+w3x2)=f(0.2×0.5+0.5×0.3)≈0.56h2=f(w2x1+w4x2)=f(−0.4×0.5+0.6×0.3)≈0.5o1=f(w5h1+w7h2)=f(0.1×0.56−0.3×0.5)≈0.48o2=f(w6h1+w8h2)=f(−0.5×0.56+0.8×0.3)≈0.53由上可得MSE损失
E
=
1
2
[
(
o
1
−
y
1
)
2
+
(
o
2
−
y
2
)
2
]
≈
1
2
[
(
0.48
−
0.23
)
2
+
(
0.53
+
0.07
)
2
]
≈
0.21
\begin{aligned} E&=\frac{1}{2}[(o_1-y_1)^2+(o_2-y_2)^2] \\ &\approx \frac{1}{2}[(0.48-0.23)^2+(0.53+0.07)^2] \\ &\approx 0.21 \end{aligned}
E=21[(o1−y1)2+(o2−y2)2]≈21[(0.48−0.23)2+(0.53+0.07)2]≈0.21取学习率
η
=
1
\eta=1
η=1,反向传播更新
w
5
,
6
,
7
,
8
w_{5,6,7,8}
w5,6,7,8
w
5
′
=
w
5
−
η
×
∂
E
w
5
=
0.1
−
∂
E
k
∂
o
1
⋅
∂
o
1
∂
I
n
o
1
⋅
∂
I
n
o
1
∂
w
5
≈
0.25
×
0.48
×
(
1
−
0.48
)
×
0.56
≈
0.03
w
6
′
=
w
5
−
η
×
∂
E
w
6
=
−
0.5
−
∂
E
k
∂
o
1
⋅
∂
o
2
∂
I
n
o
2
⋅
∂
I
n
o
2
∂
w
6
≈
0.6
×
0.53
×
(
1
−
0.53
)
×
0.56
≈
0.08
w'_5=w_5-\eta \times \frac{\partial E}{w_5}=0.1-\frac{\partial E_k}{\partial o_1} \cdot \frac{\partial o_1}{\partial Ino_1} \cdot \frac{\partial Ino_1}{\partial w_5} \approx 0.25 \times 0.48 \times (1-0.48) \times 0.56 \approx 0.03 \\ w'_6=w_5-\eta \times \frac{\partial E}{w_6}=-0.5-\frac{\partial E_k}{\partial o_1} \cdot \frac{\partial o_2}{\partial Ino_2} \cdot \frac{\partial Ino_2}{\partial w_6} \approx 0.6 \times 0.53 \times (1-0.53) \times 0.56 \approx 0.08 \\
w5′=w5−η×w5∂E=0.1−∂o1∂Ek⋅∂Ino1∂o1⋅∂w5∂Ino1≈0.25×0.48×(1−0.48)×0.56≈0.03w6′=w5−η×w6∂E=−0.5−∂o1∂Ek⋅∂Ino2∂o2⋅∂w6∂Ino2≈0.6×0.53×(1−0.53)×0.56≈0.08同理可得
w
7
′
≈
0.03
w
8
′
≈
0.07
w'_7 \approx 0.03\\ w'_8 \approx 0.07
w7′≈0.03w8′≈0.07继续反向传播
w
1
′
=
w
1
−
η
×
∂
E
w
1
=
0.2
−
∂
E
k
∂
o
1
⋅
∂
h
1
∂
I
n
h
1
⋅
∂
I
n
h
1
∂
w
1
≈
0.21
w'_1=w_1-\eta \times \frac{\partial E}{w_1}=0.2-\frac{\partial E_k}{\partial o_1} \cdot \frac{\partial h_1}{\partial Inh_1} \cdot \frac{\partial Inh_1}{\partial w_1} \approx 0.21
w1′=w1−η×w1∂E=0.2−∂o1∂Ek⋅∂Inh1∂h1⋅∂w1∂Inh1≈0.21同理
w
2
′
≈
−
0.41
w
3
′
≈
0.51
w
4
′
≈
0.59
w'_2 \approx -0.41\\ w'_3 \approx 0.51\\ w'_4 \approx 0.59\\
w2′≈−0.41w3′≈0.51w4′≈0.59
代码实现
Numpy实现
代码实现如下:
# coding=gbk
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(z):
a = 1 / (1 + np.exp(-z))
return a
def forward_propagate(x1, x2, y1, y2, w1, w2, w3, w4, w5, w6, w7, w8): # 正向传播
in_h1 = w1 * x1 + w3 * x2
out_h1 = sigmoid(in_h1)
in_h2 = w2 * x1 + w4 * x2
out_h2 = sigmoid(in_h2)
in_o1 = w5 * out_h1 + w7 * out_h2
out_o1 = sigmoid(in_o1)
in_o2 = w6 * out_h1 + w8 * out_h2
out_o2 = sigmoid(in_o2)
error = (1 / 2) * (out_o1 - y1) ** 2 + (1 / 2) * (out_o2 - y2) ** 2
return out_o1, out_o2, out_h1, out_h2, error
def back_propagate(out_o1, out_o2, out_h1, out_h2): # 反向传播
d_o1 = out_o1 - y1
d_o2 = out_o2 - y2
d_w5 = d_o1 * out_o1 * (1 - out_o1) * out_h1
d_w7 = d_o1 * out_o1 * (1 - out_o1) * out_h2
d_w6 = d_o2 * out_o2 * (1 - out_o2) * out_h1
d_w8 = d_o2 * out_o2 * (1 - out_o2) * out_h2
d_w1 = (d_w5 + d_w6) * out_h1 * (1 - out_h1) * x1
d_w3 = (d_w5 + d_w6) * out_h1 * (1 - out_h1) * x2
d_w2 = (d_w7 + d_w8) * out_h2 * (1 - out_h2) * x1
d_w4 = (d_w7 + d_w8) * out_h2 * (1 - out_h2) * x2
return d_w1, d_w2, d_w3, d_w4, d_w5, d_w6, d_w7, d_w8
def update_w(step,w1, w2, w3, w4, w5, w6, w7, w8): #梯度下降,更新权值
w1 = w1 - step * d_w1
w2 = w2 - step * d_w2
w3 = w3 - step * d_w3
w4 = w4 - step * d_w4
w5 = w5 - step * d_w5
w6 = w6 - step * d_w6
w7 = w7 - step * d_w7
w8 = w8 - step * d_w8
return w1, w2, w3, w4, w5, w6, w7, w8
if __name__ == "__main__":
w1, w2, w3, w4, w5, w6, w7, w8 = 0.2, -0.4, 0.5, 0.6, 0.1, -0.5, -0.3, 0.8 # 可以给随机值,为配合PPT,给的指定值
x1, x2 = 0.5, 0.3 # 输入值
y1, y2 = 0.23, -0.07 # 正数可以准确收敛;负数不行。why? 因为用sigmoid输出,y1, y2 在 (0,1)范围内。
N = 10 # 迭代次数
step = 10 # 步长
print("输入值 x0, x1:", x1, x2)
print("输出值 y0, y1:", y1, y2)
print("输入值:x1, x2;",x1, x2, "输出值:y1, y2:", y1, y2)
eli = []
lli = []
for i in range(N):
print("=====第" + str(i) + "轮=====")
# 正向传播
out_o1, out_o2, out_h1, out_h2, error = forward_propagate(x1, x2, y1, y2, w1, w2, w3, w4, w5, w6, w7, w8)
print("正向传播:", round(out_o1, 5), round(out_o2, 5))
print("损失函数:", round(error, 5))
# 反向传播
d_w1, d_w2, d_w3, d_w4, d_w5, d_w6, d_w7, d_w8 = back_propagate(out_o1, out_o2, out_h1, out_h2)
# 梯度下降,更新权值
w1, w2, w3, w4, w5, w6, w7, w8 = update_w(step,w1, w2, w3, w4, w5, w6, w7, w8)
eli.append(i)
lli.append(error)
plt.plot(eli, lli)
plt.ylabel('Loss')
plt.xlabel('w')
plt.show()
代码执行结果:
=====第999轮=====
正向传播: 0.23025 0.00605
损失函数: 0.00289
Pytorch实现
代码实现如下:
import torch
x1, x2 = torch.Tensor([0.5]), torch.Tensor([0.3])
y1, y2 = torch.Tensor([0.23]), torch.Tensor([-0.07])
print("=====输入值:x1, x2;真实输出值:y1, y2=====")
print(x1, x2, y1, y2)
w1, w2, w3, w4, w5, w6, w7, w8 = torch.Tensor([0.2]), torch.Tensor([-0.4]), torch.Tensor([0.5]), torch.Tensor(
[0.6]), torch.Tensor([0.1]), torch.Tensor([-0.5]), torch.Tensor([-0.3]), torch.Tensor([0.8]) # 权重初始值
w1.requires_grad = True
w2.requires_grad = True
w3.requires_grad = True
w4.requires_grad = True
w5.requires_grad = True
w6.requires_grad = True
w7.requires_grad = True
w8.requires_grad = True
def sigmoid(z):
a = 1 / (1 + torch.exp(-z))
return a
def forward_propagate(x1, x2):
in_h1 = w1 * x1 + w3 * x2
out_h1 = sigmoid(in_h1) # out_h1 = torch.sigmoid(in_h1)
in_h2 = w2 * x1 + w4 * x2
out_h2 = sigmoid(in_h2) # out_h2 = torch.sigmoid(in_h2)
in_o1 = w5 * out_h1 + w7 * out_h2
out_o1 = sigmoid(in_o1) # out_o1 = torch.sigmoid(in_o1)
in_o2 = w6 * out_h1 + w8 * out_h2
out_o2 = sigmoid(in_o2) # out_o2 = torch.sigmoid(in_o2)
print("正向计算:o1 ,o2")
print(out_o1.data, out_o2.data)
return out_o1, out_o2
def loss_fuction(x1, x2, y1, y2): # 损失函数
y1_pred, y2_pred = forward_propagate(x1, x2) # 前向传播
loss = (1 / 2) * (y1_pred - y1) ** 2 + (1 / 2) * (y2_pred - y2) ** 2 # 考虑 : t.nn.MSELoss()
print("损失函数(均方误差):", loss.item())
return loss
def update_w(w1, w2, w3, w4, w5, w6, w7, w8):
# 步长
step = 1
w1.data = w1.data - step * w1.grad.data
w2.data = w2.data - step * w2.grad.data
w3.data = w3.data - step * w3.grad.data
w4.data = w4.data - step * w4.grad.data
w5.data = w5.data - step * w5.grad.data
w6.data = w6.data - step * w6.grad.data
w7.data = w7.data - step * w7.grad.data
w8.data = w8.data - step * w8.grad.data
w1.grad.data.zero_() # 注意:将w中所有梯度清零
w2.grad.data.zero_()
w3.grad.data.zero_()
w4.grad.data.zero_()
w5.grad.data.zero_()
w6.grad.data.zero_()
w7.grad.data.zero_()
w8.grad.data.zero_()
return w1, w2, w3, w4, w5, w6, w7, w8
if __name__ == "__main__":
print("=====更新前的权值=====")
print(w1.data, w2.data, w3.data, w4.data, w5.data, w6.data, w7.data, w8.data)
for i in range(1000):
print("=====第" + str(i) + "轮=====")
L = loss_fuction(x1, x2, y1, y2) # 前向传播,求 Loss,构建计算图
L.backward() # 自动求梯度,不需要人工编程实现。反向传播,求出计算图中所有梯度存入w中
print("\tgrad W: ", round(w1.grad.item(), 2), round(w2.grad.item(), 2), round(w3.grad.item(), 2),
round(w4.grad.item(), 2), round(w5.grad.item(), 2), round(w6.grad.item(), 2), round(w7.grad.item(), 2),
round(w8.grad.item(), 2))
w1, w2, w3, w4, w5, w6, w7, w8 = update_w(w1, w2, w3, w4, w5, w6, w7, w8)
print("更新后的权值")
print(w1.data, w2.data, w3.data, w4.data, w5.data, w6.data, w7.data, w8.data)
代码执行结果:
=====第1000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.7750]) tensor([0.5920])
正向计算,预测值o1 ,o2:tensor([0.2296]) tensor([0.0098])
损失函数(均方误差): 0.003185197012498975
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([1.6515]) tensor([0.1770]) tensor([1.3709]) tensor([0.9462]) tensor([-0.7798]) tensor([-4.2741]) tensor([-1.0236]) tensor([-2.1999])
损失函数相差0.00029,可以认为损失差别不大,且极其接近最优解。
更改步长及迭代次数
步长为1 训练1000轮
=====第1000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.7750]) tensor([0.5920])
正向计算,预测值o1 ,o2:tensor([0.2296]) tensor([0.0098])
损失函数(均方误差): 0.003185197012498975
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([1.6515]) tensor([0.1770]) tensor([1.3709]) tensor([0.9462]) tensor([-0.7798]) tensor([-4.2741]) tensor([-1.0236]) tensor([-2.1999])
步长为1 训练5000轮
正向计算,隐藏层h1 ,h2:tensor([0.8237]) tensor([0.6482])
正向计算,预测值o1 ,o2:tensor([0.2299]) tensor([0.0020])
损失函数(均方误差): 0.0025912360288202763
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([2.0996]) tensor([0.5281]) tensor([1.6398]) tensor([1.1568]) tensor([-0.7065]) tensor([-5.2335]) tensor([-0.9668]) tensor([-2.9425])
步长为1 训练10000轮
=====第10000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.8410]) tensor([0.6734])
正向计算,预测值o1 ,o2:tensor([0.2300]) tensor([0.0010])
损失函数(均方误差): 0.0025182669050991535
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([2.2820]) tensor([0.6934]) tensor([1.7492]) tensor([1.2560]) tensor([-0.6798]) tensor([-5.6378]) tensor([-0.9456]) tensor([-3.2633])
步长为5 训练1000轮
=====第1000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.8225]) tensor([0.6443])
正向计算,预测值o1 ,o2:tensor([0.2299]) tensor([0.0020])
损失函数(均方误差): 0.002591301454231143
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([2.0871]) tensor([0.5032]) tensor([1.6323]) tensor([1.1419]) tensor([-0.7079]) tensor([-5.2478]) tensor([-0.9722]) tensor([-2.9524])
步长为5 训练5000轮
=====第5000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.8598]) tensor([0.7028])
正向计算,预测值o1 ,o2:tensor([0.2300]) tensor([0.0004])
损失函数(均方误差): 0.0024759655352681875
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([2.4998]) tensor([0.8953]) tensor([1.8799]) tensor([1.3772]) tensor([-0.6491]) tensor([-6.1724]) tensor([-0.9252]) tensor([-3.6919])
步长为5 训练10000轮
=====第10000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.8726]) tensor([0.7265])
正向计算,预测值o1 ,o2:tensor([0.2300]) tensor([0.0002])
损失函数(均方误差): 0.002462496282532811
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([2.6617]) tensor([1.0664]) tensor([1.9770]) tensor([1.4799]) tensor([-0.6286]) tensor([-6.5554]) tensor([-0.9082]) tensor([-4.0079])
步长为0.1 训练1000轮
=====第1000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.6871]) tensor([0.5292])
正向计算,预测值o1 ,o2:tensor([0.2379]) tensor([0.0736])
损失函数(均方误差): 0.010344177484512329
w的梯度: -0.0 -0.0 -0.0 -0.0 0.0 0.01 0.0 0.01
更新后的权值w:
tensor([0.9891]) tensor([-0.1984]) tensor([0.9735]) tensor([0.7210]) tensor([-0.8598]) tensor([-2.8373]) tensor([-1.0837]) tensor([-1.1030])
步长为0.1 训练5000轮
=====第5000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.7512]) tensor([0.5715])
正向计算,预测值o1 ,o2:tensor([0.2292]) tensor([0.0186])
损失函数(均方误差): 0.003924463409930468
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([1.4572]) tensor([0.0529]) tensor([1.2543]) tensor([0.8717]) tensor([-0.8159]) tensor([-3.8519]) tensor([-1.0503]) tensor([-1.8777])
步长为0.1 训练10000轮
=====第10000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.7754]) tensor([0.5929])
正向计算,预测值o1 ,o2:tensor([0.2296]) tensor([0.0098])
损失函数(均方误差): 0.003185402136296034
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([1.6546]) tensor([0.1826]) tensor([1.3728]) tensor([0.9496]) tensor([-0.7792]) tensor([-4.2705]) tensor([-1.0223]) tensor([-2.1968])
从运行结果来看,训练轮次越多、步长越大,损失函数越小。
更改损失函数和激活函数
Sigmoid换为PyTorch自带函数torch.sigmoid()
代码实现如下:
def forward_propagate(x): # 计算图
in_h1 = w[0] * x[0] + w[2] * x[1]
out_h1 = torch.sigmoid(in_h1)
in_h2 = w[1] * x[0] + w[3] * x[1]
out_h2 = torch.sigmoid(in_h2)
in_o1 = w[4] * out_h1 + w[6] * out_h2
out_o1 = torch.sigmoid(in_o1)
in_o2 = w[5] * out_h1 + w[7] * out_h2
out_o2 = torch.sigmoid(in_o2)
print("正向计算,隐藏层h1 ,h2:", end="")
print(out_h1.data, out_h2.data)
print("正向计算,预测值o1 ,o2:", end="")
print(out_o1.data, out_o2.data)
return out_o1, out_o2
代码执行结果:
=====第1000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.7750]) tensor([0.5920])
正向计算,预测值o1 ,o2:tensor([0.2296]) tensor([0.0098])
损失函数(均方误差): 0.003185197012498975
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w:
tensor([1.6515]) tensor([0.1770]) tensor([1.3709]) tensor([0.9462]) tensor([-0.7798]) tensor([-4.2741]) tensor([-1.0236]) tensor([-2.1999])
进程已结束,退出代码0
Sigmoid换为ReLU
代码实现如下:
def forward_propagate(x): # 计算图
in_h1 = w[0] * x[0] + w[2] * x[1]
out_h1 = torch.relu(in_h1)
in_h2 = w[1] * x[0] + w[3] * x[1]
out_h2 = torch.relu(in_h2)
in_o1 = w[4] * out_h1 + w[6] * out_h2
out_o1 = torch.relu(in_o1)
in_o2 = w[5] * out_h1 + w[7] * out_h2
out_o2 = torch.relu(in_o2)
print("正向计算,隐藏层h1 ,h2:", end="")
print(out_h1.data, out_h2.data)
print("正向计算,预测值o1 ,o2:", end="")
print(out_o1.data, out_o2.data)
return out_o1, out_o2
代码执行结果:
=====第1000轮=====
正向计算,隐藏层h1 ,h2:tensor([0.4055]) tensor([0.])
正向计算,预测值o1 ,o2:tensor([0.2300]) tensor([0.])
损失函数(均方误差): 0.0024500000290572643
w的梯度: -0.0 0.0 -0.0 0.0 -0.0 0.0 0.0 0.0
更新后的权值w:
tensor([0.4287]) tensor([-0.4000]) tensor([0.6372]) tensor([0.6000]) tensor([0.5672]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
代码向上翻一翻
=====第12轮=====
正向计算,隐藏层h1 ,h2:tensor([0.3777]) tensor([0.])
正向计算,预测值o1 ,o2:tensor([0.1916]) tensor([0.])
损失函数(均方误差): 0.0031862861942499876
w的梯度: -0.01 0.0 -0.01 0.0 -0.01 0.0 0.0 0.0
更新后的权值w:
tensor([0.3975]) tensor([-0.4000]) tensor([0.6185]) tensor([0.6000]) tensor([0.5219]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
=====第13轮=====
正向计算,隐藏层h1 ,h2:tensor([0.3843]) tensor([0.])
正向计算,预测值o1 ,o2:tensor([0.2006]) tensor([0.])
损失函数(均方误差): 0.0028835302218794823
w的梯度: -0.01 0.0 -0.0 0.0 -0.01 0.0 0.0 0.0
更新后的权值w:
tensor([0.4052]) tensor([-0.4000]) tensor([0.6231]) tensor([0.6000]) tensor([0.5332]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
很明显,损失函数的值下降很快,仅13轮时就已经比使用Sigmoid函数训练1000轮时的值低。
为什么?
第一,采用Sigmoid等函数,算激活函数是(指数运算),计算量大;反向传播求误差梯度时,求导涉及除法,计算量相对大。而采用Relu激活函数,整个过程的计算量节省很多。
第二,对于深层网络,Sigmoid函数反向传播时,很容易就会出现梯度消失的情况(在sigmoid接近饱和区时,变换太缓慢,导数趋于0),这种情况会造成信息丢失,梯度消失在网络层数多的时候尤其明显,从而无法完成深层网络的训练。
第三,ReLU会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
MSE换为PyTorch自带函数t.nn.MSELoss()
def loss_fuction(x1, x2, y1, y2): # 损失函数
y1_pred, y2_pred = forward_propagate(x1, x2) # 前向传播
mse = torch.nn.MSELoss()
loss =mse(y1_pred,y1) + mse(y2_pred,y2) # 考虑 : t.nn.MSELoss()
print("损失函数(均方误差):", loss.item())
return loss
代码执行结果:
=====第999轮=====
正向计算:o1 ,o2
tensor([0.2298]) tensor([0.0050])
损失函数(均方误差): 0.005628134589642286
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值
tensor([1.8441]) tensor([0.3147]) tensor([1.4865]) tensor([1.0288]) tensor([-0.7469]) tensor([-4.6932]) tensor([-0.9992]) tensor([-2.5217])
MSE换为交叉熵
def loss_fuction(x1, x2, y1, y2):
y1_pred, y2_pred = forward_propagate(x1, x2)
loss_func = torch.nn.CrossEntropyLoss() # 创建交叉熵损失函数
y_pred = torch.stack([y1_pred, y2_pred], dim=1)
y = torch.stack([y1, y2], dim=1)
loss = loss_func(y_pred, y) # 计算
print("损失函数(交叉熵损失):", loss.item())
return loss
代码执行结果:
=====第999轮=====
正向计算:o1 ,o2
tensor([0.9929]) tensor([0.0072])
损失函数(交叉熵损失): -0.018253758549690247
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值
tensor([2.2809]) tensor([0.6580]) tensor([1.7485]) tensor([1.2348]) tensor([3.8104]) tensor([-4.2013]) tensor([2.5933]) tensor([-2.0866])
注意:需要将输入通过Logsoftmax函数得到输出概率后再进行负对数似然损失函数计算。
权值初始值替换
替换为0
w1, w2, w3, w4, w5, w6, w7, w8 = torch.Tensor([0]), torch.Tensor([0]), torch.Tensor([0]), torch.Tensor(
[0]), torch.Tensor([0]), torch.Tensor([0]), torch.Tensor([0]), torch.Tensor([0]) # 权重初始值
代码执行结果:
=====更新前的权值=====
tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.])
=====第0轮=====
正向计算:o1 ,o2
tensor([0.5000]) tensor([0.5000])
损失函数(均方误差): 0.1988999992609024
grad W: 0.0 0.0 0.0 0.0 0.03 0.07 0.03 0.07
=====第118轮=====
正向计算:o1 ,o2
tensor([0.2336]) tensor([0.0654])
损失函数(均方误差): 0.009175034239888191
grad W: -0.0 -0.0 -0.0 -0.0 0.0 0.01 0.0 0.01
=====第119轮=====
正向计算:o1 ,o2
tensor([0.2334]) tensor([0.0649])
损失函数(均方误差): 0.009111026301980019
grad W: -0.0 -0.0 -0.0 -0.0 0.0 0.0 0.0 0.0
=====第999轮=====
正向计算:o1 ,o2
tensor([0.2296]) tensor([0.0096])
损失函数(均方误差): 0.003165625501424074
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值
tensor([1.1809]) tensor([1.1809]) tensor([0.7085]) tensor([0.7085]) tensor([-0.8765]) tensor([-3.3598]) tensor([-0.8765]) tensor([-3.3598])
替换为随机数
w1, w2, w3, w4, w5, w6, w7, w8 = torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1) # 权重初始值
代码执行结果:
=====更新前的权值=====
tensor([[0.1843]]) tensor([[1.4344]]) tensor([[-0.1303]]) tensor([[-0.1704]]) tensor([[-1.9632]]) tensor([[0.5791]]) tensor([[-0.4025]]) tensor([[-0.2650]])
=====第0轮=====
正向计算:o1 ,o2
tensor([[0.2187]]) tensor([[0.5305]])
损失函数(均方误差): 0.1803714483976364
grad W: 0.01 -0.0 0.01 -0.0 -0.0 0.08 -0.0 0.1
=====第129轮=====
正向计算:o1 ,o2
tensor([[0.2261]]) tensor([[0.0566]])
损失函数(均方误差): 0.008026348426938057
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.01
=====第130轮=====
正向计算:o1 ,o2
tensor([[0.2261]]) tensor([[0.0563]])
损失函数(均方误差): 0.007982097566127777
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
=====第999轮=====
正向计算:o1 ,o2
tensor([[0.2296]]) tensor([[0.0097]])
损失函数(均方误差): 0.0031767673790454865
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值
tensor([[0.6406]]) tensor([[2.5083]]) tensor([[0.1434]]) tensor([[0.4740]]) tensor([[-1.8034]]) tensor([[-2.2541]]) tensor([[-0.1830]]) tensor([[-4.1123]])
显然,最后的结果跟初始权重无关,初始权重只影响下降速度。
作业总结
本次主要是对前馈神经网络的部分内容进行了深入学习,包括激活函数和损失函数、权值/训练轮次/训练步长对神经网络输出结果的影响。一个合适的方法,可以大幅减少训练轮次,提升训练速度和训练质量。所以我们应当大量应用ReLU函数,因为这真的好节省时间啊(确信