2022.3.28
依据该BP神经网络与Python实现 - -Finley- - 博客园 (cnblogs.com) 博客实现的,但是在一些方面有改动
后来用pytorch又实现了一遍,见添加链接描述
与原博客中不同的:
- 我的代码中是直接设置最大迭代次数来终止训练的。(博客中说好的办法是用损失函数作为终止的依据)我使用python的numpy进行矩阵运算,故而没有了博客代码中麻烦的循环。
在原博客中学到的:
- 实际应用中我们通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正;或者为每个隐含层神经元设置一个偏置参数.λ是一个称为学习率的参数,一般在(0,0.1)区间上取值.
如何用numpy创建矩阵。我的方法是先创建出numpy的array,再用np.mat转换过去(应该还有更好的办法,请赐教)
**np.multiply() 和 * 和np.dot()有什么区别。**如下:
对于mat类型来说,*和np.dot()和np.matmul() 一样,都是矩阵乘法(左边的列数要和右边的行数相同),而np.multiply()是对应位置相乘(两矩阵形状要相同)。
对于array类型来说,np.dot()是矩阵乘法,而*和 np.multiply()是对应位置想乘。
计算sigmoid的时候是使用对应位置相乘,故而在对sigmoid求导的时候也是对应位置相乘
偏导相乘的顺序问题,见机器学习中的矩阵向量求导(四) 矩阵向量求导链式法则 - 刘建平Pinard - 博客园 (cnblogs.com) 。
z
=
f
(
y
)
,
y
=
x
w
,
∂
z
∂
x
=
∂
z
∂
y
w
T
,
∂
z
∂
w
=
x
T
∂
z
∂
y
,
(
对
左
边
求
导
,
它
的
导
数
放
在
右
边
;
对
右
边
求
导
,
它
的
导
数
放
在
左
边
)
z=f(y),y=xw, frac{partial z}{partial x}=frac{partial z}{partial y}w^T, \ frac{partial z}{partial w}=x^Tfrac{partial z}{partial y}, \(对左边求导,它的导数放在右边;对右边求导,它的导数放在左边)
z=f(y),y=xw, ∂x∂z=∂y∂zwT,∂w∂z=xT∂y∂z,(对左边求导,它的导数放在右边;对右边求导,它的导数放在左边)
损失函数,以及它的导数
l
o
s
s
=
0.5
∗
∑
i
=
1
n
(
t
a
r
g
e
t
o
i
−
o
u
t
o
i
)
2
∂
t
a
r
g
e
t
o
i
∂
o
u
t
o
i
=
2
∗
1
2
∗
(
t
a
r
g
e
t
o
i
−
o
u
t
o
i
)
=
t
a
r
g
e
t
o
i
−
o
u
t
o
i
loss=0.5*sum_{i=1}^n (target_{oi}-out_{oi})^2 \frac{partial, target_{oi} }{partial out_{oi}}=2*frac{1}{2}*(target_{oi}-out_{oi})=target_{oi}-out_{oi}
loss=0.5∗i=1∑n(targetoi−outoi)2∂outoi∂targetoi=2∗21∗(targetoi−outoi)=targetoi−outoi
- 训练集和测试集使用同一个,输入2维数据(但是代码编写时输入3维,因为通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正),训练目标是能输出正确的异或结果,如输入[1,0],要能输出0;输入[1,1],要能输出1。
设计隐层层和输出层的权值 w 1 , w 2 w_1,w_2 w1,w2 为(0,1)之间的随机值
进入前向传播环节,输入数据x0,得到输出y2,以及损失价值loss
进入后向传播环节,算出偏差loss与y2,y1的导数, 如此易得到loss与w2,w1的导数接着更新w2,w1
(这时求偏导的时候,偏导相乘要注意顺序)
import numpy as np
from icecream import ic
from matplotlib import pyplot as plt
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
def sigmoid_derivative(x):
#是对应位置相乘
return np.multiply(x,(1-x))
class BP():
def __init__(self):
self.x0=[] #输入层
self.x1=[] #隐含层
self.x2=[] #输出层
self.y1=[] #y1=x0*w1
self.y2=[] #y2=x1w1
self.w1=[] #隐层层的权值
self.w2=[] #输出层的权值
self.loss=[] #损失函数
self.label=[] #真实值
self.y2_gradient=[] #e对y2的导数
self.y1_gradient=[] #e对y2的倒数
self.loss_delta=[] #损失函数对误差值求导
def setup(self,input_n,hidden_n,ouput_n):
"""
# 对bp初始化,设置各层维度,以及初始随机权值
"""
input_n+=1 #通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正
self.x0=np.mat(np.ones([1,input_n])) #1*3
self.x1=np.mat(np.ones([1,hidden_n])) #1*5
self.x2=np.mat(np.ones([1,ouput_n])) #1*1
self.w1=np.mat(np.random.rand(input_n,hidden_n)) #3*5
self.w2=np.mat(np.random.rand(hidden_n,ouput_n)) #5*1
self.y2_gradient=np.mat(np.ones([1,ouput_n]))
self.y1_gradient=np.mat(np.ones([1,hidden_n]))
def forward(self,inputx,label):
"""
前向传播
"""
self.x0[0, 0:2] = inputx #输入层是1*3矩阵,但实际输入是1*2
self.label=np.mat(label)
self.x1=sigmoid(self.x0*self.w1) #省略了y1=x0*w1
self.x2=sigmoid(self.x1*self.w2)
#计算损失值
e=self.label-self.x2
self.loss = (0.5*sum(e**2) /e.size).getA()[0]
self.loss_delta=e
def backward(self,learn):
"""
后向传播
"""
#注意乘法顺序,求梯度
self.y2_gradient=np.multiply(sigmoid_derivative(self.x2),self.loss_delta)
self.y1_gradient=np.multiply(self.y2_gradient*self.w2.T,sigmoid_derivative(self.x1))
# 更新权值
self.w2+=self.x1.T*self.y2_gradient*learn
self.w1+=self.x0.T*self.y1_gradient*learn
def make_pic():
#绘制图像
train_epoch = 500 #迭代总次数
show_epoch = 100
def optimizer(learn):
bp.setup(input_n=2,hidden_n=5,ouput_n=1)
epoch = []
loss = []
for k in range(train_epoch):
for i in range(len(cases)):
bp.forward(inputx=cases[i], label=labels[i])
bp.backward(learn)
#每隔show_epoch个跌打数的时候,看看损失值
if k % show_epoch == 0:
l=0
for i in range(len(cases)):
bp.forward(inputx=cases[i], label=labels[i])
l+=bp.loss
epoch.append(k)
loss.append(l/4)
return epoch, loss
epoch1, loss1 = optimizer(learn=0.5)
epoch2, loss2 = optimizer(learn=0.1)
epoch3, loss3 = optimizer(learn=0.05)
plt.figure()
plt.subplot(1, 2, 1)
plt.plot(range(0, len(epoch1) * show_epoch, show_epoch), loss1, label='learn=0.5')
plt.plot(range(0, len(epoch2) * show_epoch, show_epoch), loss2, label='learn=0.1')
plt.plot(range(0, len(epoch3) * show_epoch, show_epoch), loss3, label='learn=0.05')
# 显示标签,如果不加这句,即使在plot中加了label='一些数字'的参数,最终还是不会显示标签
plt.legend(loc="upper right")
plt.show()
if __name__=='__main__':
#数据集
cases = [
[0, 0],
[0, 1],
[1, 0],
[1, 1],
]
labels = [[0], [1], [1], [0]]
bp=BP()
bp.setup(2,5,1) #初始化
#迭代训练
for _ in range(100):
for i in range(len(cases)):
bp.forward(inputx=cases[i],label=labels[i])
bp.backward(learn=0.1)
#画图,看不同学习率,迭代次数不同时的误差率
make_pic()
代码图像



