人工智能-作业2:例题程序复现
目录
- 前向传播
- 反向传播
-
- 求
∂
e
r
r
o
r
∂
o
u
t
_
o
1
frac{partial error}{partial out_o_1}
- 求
∂
o
u
t
_
o
1
∂
i
n
_
o
1
frac{partial out_o_1}{partial in_o_1}
- 求
∂
i
n
_
o
1
∂
w
5
frac{partial in_o_1}{partial w_5}
- 代入
∂
e
r
r
o
r
∂
w
5
=
∂
e
r
r
o
r
∂
o
u
t
_
o
1
⋅
∂
o
u
t
_
o
1
∂
i
n
_
o
1
⋅
∂
i
n
_
o
1
∂
w
5
frac{partial error}{partial w_5}= frac{partial error}{partial out_o_1}cdot frac{partial out_o_1}{partial in_o_1}cdot frac{partial in_o_1}{partial w_5}
- 求
- 更新权重
- 全部代码及运行结果
如图,
输入值: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(w1~w8)
前向传播
每一个h或o还要具体分为in和out:
普通的相乘求和后得到in_h或in_o,将in用sigmoid函数处理后得到out_h或out_o,
其他要说的都在注释里了。
# 前向传播:
# in = sum(w*x)
# out = sigmoid(in)
# 一个隐藏层
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)
print("正向计算:o1 ,o2") # 输出本轮进入损失函数之前的数值out1、out2
print(round(out_o1, 5), round(out_o2, 5)) # round()舍入化整,round(x,y),y表保留小数后几位,此处保留5位小数
# 损失函数MSE 均方误差:1/n * sum((y^-y)**2)
# 此处只有2个y,所以n=2
error = (1 / 2) * (out_o1 - y1) ** 2 + (1 / 2) * (out_o2 - y2) ** 2
print("损失函数:均方误差") # 输出本轮损失函数
print(round(error, 5))
return out_o1, out_o2, out_h1, out_h2 # 返回了两层out,用于反向传播
反向传播
已知计算导数是由拆分成多个导数相乘计算。
以
∂
e
r
r
o
r
∂
w
5
frac{partial error}{partial w_5}
∂w5∂error的计算为例:
∂
e
r
r
o
r
∂
w
5
=
∂
e
r
r
o
r
∂
o
u
t
_
o
1
⋅
∂
o
u
t
_
o
1
∂
i
n
_
o
1
⋅
∂
i
n
_
o
1
∂
w
5
frac{partial error}{partial w_5}= frac{partial error}{partial out_o_1}cdot frac{partial out_o_1}{partial in_o_1}cdot frac{partial in_o_1}{partial w_5}
∂w5∂error=∂out_o1∂error⋅∂in_o1∂out_o1⋅∂w5∂in_o1
拆分计算,倒着看,先看
∂
e
r
r
o
r
∂
o
u
t
_
o
1
frac{partial error}{partial out_o_1}
∂out_o1∂error
求
∂
e
r
r
o
r
∂
o
u
t
_
o
1
frac{partial error}{partial out_o_1}
∂out_o1∂error
out→error用了MSE
M
S
E
=
1
m
∑
i
=
1
m
(
y
i
−
y
^
i
)
²
MSE=frac{1}{m}sum_{i=1}^{m}(y_i-widehat{y}_i) ²
MSE=m1i=1∑m(yi−y
i)²
反过来对这个过程求导,把y看做常数,最后得:
∂
e
r
r
o
r
∂
o
u
t
_
o
1
=
o
u
t
_
o
1
−
y
1
frac{partial error}{partial out_o_1}=out_o_1 - y_1
∂out_o1∂error=out_o1−y1
d_o1 = out_o1 - y1
求
∂
o
u
t
_
o
1
∂
i
n
_
o
1
frac{partial out_o_1}{partial in_o_1}
∂in_o1∂out_o1
用的激活函数是sigmoid,先看sigmoid函数求导:
∂
o
u
t
_
o
1
∂
i
n
_
o
1
=
o
u
t
_
o
1
∗
(
1
−
o
u
t
_
o
1
)
frac{partial out_o_1}{partial in_o_1}=out_o_1 * (1-out_o_1)
∂in_o1∂out_o1=out_o1∗(1−out_o1)
求
∂
i
n
_
o
1
∂
w
5
frac{partial in_o_1}{partial w_5}
∂w5∂in_o1
这个更简单了,单纯的相乘累加,out_h当做常量。
in_o1 = w5 * out_h1 + w7 * out_h2
求导得:
∂
i
n
_
o
1
∂
w
5
=
o
u
t
_
h
1
frac{partial in_o_1}{partial w_5}=out_h_1
∂w5∂in_o1=out_h1
代入
∂
e
r
r
o
r
∂
w
5
=
∂
e
r
r
o
r
∂
o
u
t
_
o
1
⋅
∂
o
u
t
_
o
1
∂
i
n
_
o
1
⋅
∂
i
n
_
o
1
∂
w
5
frac{partial error}{partial w_5}= frac{partial error}{partial out_o_1}cdot frac{partial out_o_1}{partial in_o_1}cdot frac{partial in_o_1}{partial w_5}
∂w5∂error=∂out_o1∂error⋅∂in_o1∂out_o1⋅∂w5∂in_o1
把刚刚求得的值代进去:
d
_
w
5
=
∂
e
r
r
o
r
∂
w
5
=
(
o
u
t
_
o
1
−
y
1
)
∗
o
u
t
_
o
1
∗
(
1
−
o
u
t
_
o
1
)
∗
o
u
t
_
h
1
d_w5 = frac{partial error}{partial w_5} = (out_o_1 - y_1) * out_o_1 * (1-out_o_1) * out_h_1
d_w5=∂w5∂error=(out_o1−y1)∗out_o1∗(1−out_o1)∗out_h1
以上代表了w5~w8的计算过程,同理可推w1:
因为w1的计算中同时穿过了w5和w6到达out_o1和out_o2
换句话说,w5和w5都是由h1出发的,在返回时也应该都返回到h1,
所以需要拆开计算:
∂
e
r
r
o
r
∂
w
1
=
∂
e
r
r
o
r
∂
o
u
t
_
o
1
⋅
∂
o
u
t
_
o
1
∂
w
1
+
∂
e
r
r
o
r
∂
o
u
t
_
o
2
⋅
∂
o
u
t
_
o
2
∂
w
1
frac{partial error}{partial w_1}= frac{partial error}{partial out_o_1}cdot frac{partial out_o_1}{partial w_1}+frac{partial error}{partial out_o_2}cdot frac{partial out_o_2}{partial w_1}
∂w1∂error=∂out_o1∂error⋅∂w1∂out_o1+∂out_o2∂error⋅∂w1∂out_o2
得:
d_w1 = (d_w5 + d_w6) * out_h1 * (1 - out_h1) * x1
(写的有点草率,建议看mooc,详细解释了下图的推导)
更新权重
将算出的误差传递回去,更新w
w
′
=
w
+
η
∗
∂
e
r
r
o
r
∂
w
w' = w + η * frac{partial error}{partial w}
w′=w+η∗∂w∂error
η为学习率,也称作步长
def update_w(w1, w2, w3, w4, w5, w6, w7, w8):
# 步长
step = 5
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
好啦,这样就基本完成了一轮的计算,重复计算n轮就是修正n轮w的值,把这三个环节拼凑在一起,再加一个输入x、y和初始权重的主函数运行这三个函数就可以了。
全部代码及运行结果
import numpy as np
def sigmoid(z):
a = 1 / (1 + np.exp(-z))
return a
# 前向传播:
# in = sum(w*x)
# out = sigmoid(in)
# 两层传播
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)
print("正向计算:o1 ,o2") # 输出本轮进入损失函数之前的数值out1、out2
print(round(out_o1, 5), round(out_o2, 5)) # round()舍入化整,round(x,y),y表保留小数后几位,此处保留5位小数
# 损失函数MSE 均方误差:1/n * sum((y^-y)**2)
# 此处只有2个y,所以n=2
error = (1 / 2) * (out_o1 - y1) ** 2 + (1 / 2) * (out_o2 - y2) ** 2
print("损失函数:均方误差") # 输出本轮损失函数
print(round(error, 5))
return out_o1, out_o2, out_h1, out_h2 # 返回了两层out,用于反向传播
def back_propagate(out_o1, out_o2, out_h1, out_h2):
# 反向传播
d_o1 = out_o1 - y1
d_o2 = out_o2 - y2
# print(round(d_o1, 2), round(d_o2, 2))
d_w5 = d_o1 * out_o1 * (1 - out_o1) * out_h1
d_w7 = d_o1 * out_o1 * (1 - out_o1) * out_h2
# print(round(d_w5, 2), round(d_w7, 2))
d_w6 = d_o2 * out_o2 * (1 - out_o2) * out_h1
d_w8 = d_o2 * out_o2 * (1 - out_o2) * out_h2
# print(round(d_w6, 2), round(d_w8, 2))
d_w1 = (d_w5 + d_w6) * out_h1 * (1 - out_h1) * x1
d_w3 = (d_w5 + d_w6) * out_h1 * (1 - out_h1) * x2
# print(round(d_w1, 2), round(d_w3, 2))
d_w2 = (d_w7 + d_w8) * out_h2 * (1 - out_h2) * x1
d_w4 = (d_w7 + d_w8) * out_h2 * (1 - out_h2) * x2
# print(round(d_w2, 2), round(d_w4, 2))
print("反向传播:误差传给每个权值")
print(round(d_w1, 5), round(d_w2, 5), round(d_w3, 5), round(d_w4, 5), round(d_w5, 5), round(d_w6, 5),
round(d_w7, 5), round(d_w8, 5))
return d_w1, d_w2, d_w3, d_w4, d_w5, d_w6, d_w7, d_w8
def update_w(w1, w2, w3, w4, w5, w6, w7, w8):
# 步长
step = 5
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
x1, x2 = 0.5, 0.3
y1, y2 = 0.23, -0.07
print("=====输入值:x1, x2;真实输出值:y1, y2=====")
print(x1, x2, y1, y2)
print("=====更新前的权值=====")
print(round(w1, 2), round(w2, 2), round(w3, 2), round(w4, 2), round(w5, 2), round(w6, 2), round(w7, 2),
round(w8, 2))
for i in range(1000):
print("=====第" + str(i) + "轮=====")
out_o1, out_o2, out_h1, out_h2 = forward_propagate(x1, x2, y1, y2, w1, w2, w3, w4, w5, w6, w7, w8)
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(w1, w2, w3, w4, w5, w6, w7, w8)
print("更新后的权值")
print(round(w1, 2), round(w2, 2), round(w3, 2), round(w4, 2), round(w5, 2), round(w6, 2), round(w7, 2),
round(w8, 2))
运行结果
=输入值:x1, x2;真实输出值:y1, y2=
0.5 0.3 0.23 -0.07
=更新前的权值=
0.2 -0.4 0.5 0.6 0.1 -0.5 -0.3 0.8
=第0轮=
正向计算:o1 ,o2
0.47695 0.5287
损失函数:均方误差
0.20971
反向传播:误差传给每个权值
0.01458 0.01304 0.00875 0.00782 0.03463 0.08387 0.03049 0.07384
=第1轮=
正向计算:o1 ,o2
0.43556 0.42626
损失函数:均方误差
0.14427
反向传播:误差传给每个权值
0.0117 0.01039 0.00702 0.00623 0.02779 0.06674 0.02446 0.05873
………………
…………
……
=第998轮=
正向计算:o1 ,o2
0.23038 0.00955
损失函数:均方误差
0.00316
反向传播:误差传给每个权值
4e-05 3e-05 2e-05 2e-05 3e-05 0.00029 2e-05 0.00026
=第999轮=
正向计算:o1 ,o2
0.23038 0.00954
损失函数:均方误差
0.00316
反向传播:误差传给每个权值
4e-05 3e-05 2e-05 2e-05 3e-05 0.00029 2e-05 0.00026
更新后的权值
-0.84 -1.3 -0.13 0.06 -1.55 -7.31 -1.75 -5.23
——————————————————
参考:
【基础算法】损失函数——MSE与交叉熵 - 知乎
【人工智能导论:模型与算法】MOOC 8.3 误差后向传播(BP) 例题 编程验证 - HBU_DAVID - 博客园
【浙江大学】人工智能:模型与算法 8.3 误差后向传播(BP)
另外推荐一个,关于sigmoid在这里作为激活函数的局限性可以一看:
常见激活函数及其特点 - real-zhouyc - 博客园
用Markdown编辑器写公式,可以用富文本编辑器写好公式后左右两侧加$符号加入Markdown编辑器中,也可以直接敲公式如下:
Markdown数学公式语法 - 简书