1.过程推导 - 了解BP原理
前馈神经网络:误差反向传播
前向传播:根据公式前向传播即可。
sigmoid函数:
反向传播:
- 计算梯度:
然后一步步计算
由sigmoid()函数性质:
同上所述:
- 计算梯度:
同上所述:
- 更新参数:
2.数值计算 - 手动计算
进行第一轮计算:定义初始参数
- 前向传播:
- 计算梯度:
- 计算梯度:
- 参数更新:假设学习率()
反向传播过程如图所示:
3.代码实现 - numpy手推实现前馈神经网络
import torch
import numpy as np
import matplotlib.pyplot as plt
# Numpy实现BP
# 初始化输入x,输出y,权重w
x1,x2=0.5,0.3
y1,y2=0.23,-0.07
w1,w2,w3,w4,w5,w6,w7,w8= 0.2,-0.4,0.5,0.6,0.1,-0.5,-0.3,0.8
print("输入值x:", x1, x2)
print("输入值y:", y1, y2)
print("初始化w:", w1, w2, w3, w4, w5, w6, w7, w8)
# 定义sigmoid()激活函数
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
in_h2=w2*x1+w4*x2
out_h1=sigmoid(in_h1)
out_h2=sigmoid(in_h2)
# 隐藏层——>输出层
in_o1=w5*out_h1+w7*out_h2
in_o2=w6*out_h1+w8*out_h2
out_o1=sigmoid(in_o1)
out_o2=sigmoid(in_o2)
#计算均方误差
E=(1/2)*((out_o1-y1)**2+(out_o2-y2)**2)
print("正向计算 隐藏层:", round(out_h1, 2), round(out_h2, 2))
print("正向计算 输出层:", round(out_o1, 2), round(out_o2, 2))
print("均方误差:", round(E, 2))
return out_o1, out_o2, out_h1, out_h2,E
# 反向传播
def back_propagate(out_o1, out_o2, out_h1, out_h2):
# 均方误差——>隐藏层输出层权重
d_w5 = out_o1*(out_o1-y1)*(1-out_o1)*out_h1
d_w6 = out_o2*(out_o2-y2)*(1-out_o2)*out_h1
d_w7 = out_o1*(out_o1-y1)*(1-out_o1)*out_h2
d_w8 = out_o2*(out_o2-y2)*(1-out_o2)*out_h2
# 均方误差——>输入层隐藏层权重
d_w1=((out_o1-y1)*out_o1*(1-out_o1)*w5+(out_o2-y2)*out_o2*(1-out_o2)*w6)*out_h1*(1-out_h1)*x1
d_w3=((out_o1-y1)*out_o1*(1-out_o1)*w5+(out_o2-y2)*out_o2*(1-out_o2)*w6)*out_h1*(1-out_h1)*x2
d_w2=((out_o1-y1)*out_o1*(1-out_o1)*w7+(out_o2-y2)*out_o2*(1-out_o2)*w8)*out_h2*(1-out_h2)*x1
d_w4=((out_o1-y1)*out_o1*(1-out_o1)*w7+(out_o2-y2)*out_o2*(1-out_o2)*w8)*out_h2*(1-out_h2)*x2
print("w的梯度:", round(d_w1, 2), round(d_w2, 2), round(d_w3, 2), round(d_w4, 2), round(d_w5, 2), round(d_w6, 2),round(d_w7, 2), round(d_w8, 2))
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=1
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
print("更新后的权值w:", round(w1, 2), round(w2, 2), round(w3, 2), round(w4, 2), round(w5, 2), round(w6, 2),round(w7, 2), round(w8, 2))
return w1, w2, w3, w4, w5, w6, w7, w8
if __name__ == "__main__":
x= []
loss = []
for i in range(100):
print("=====第" + str(i+1) + "轮=====")
out_o1, out_o2, out_h1, out_h2 ,E= 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)
x.append(i)
loss.append(E)
# 画图,将损失可视化
plt.plot(x,loss)
plt.ylabel('Loss')
plt.xlabel('N')
plt.show()
输出:
1轮计算后
100轮计算后:
将损失随着参数更新的变化可视化:
4.代码实现 - numpy手推实现前馈神经网络
# torch实现
x = [0.5, 0.3]
y = [0.23, -0.07]
w = [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])] # 权重初始值
for i in range(0, 8):
w[i].requires_grad = True
for i in range(0, 8):
print(w[i].data, end=" ")
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:",out_h1.data, out_h2.data)
print("正向计算,预测值o1 ,o2:",out_o1.data, out_o2.data)
return out_o1, out_o2
def loss(x, y): # 损失函数
y_pre = forward_propagate(x) # 前向传播
loss_mse = (1 / 2) * (y_pre[0] - y[0]) ** 2 + (1 / 2) * (y_pre[1] - y[1]) ** 2
print("均方误差:", round(loss_mse.item(),2))
return loss_mse
if __name__ == "__main__":
print("输入值 x0, x1:", x[0], x[1])
print("输出值 y0, y1:", y[0], y[1])
for k in range(100):
print("\n=====第" + str(k+1) + "轮=====")
# 前向传播,求 Loss
l = loss(x, y)
# 反向传播
l.backward()
print("w的梯度: ", end=" ")
for i in range(0, 8):
print(round(w[i].grad.item(), 2), end=" ")
# 定义学习率
step = 1
# 更新权值
for i in range(0, 8):
w[i].data = w[i].data - step * w[i].grad.data
w[i].grad.data.zero_()
print("\n更新后的权值w:")
for i in range(0, 8):
print(w[i].data, end=" ")
运行结果:
Torch实现与Numpy实现 运行结果一致
总结
1.对比【numpy】和【pytorch】程序,总结并陈述。
numpy和pytorch实现得到的结果是一样的,但PyTorch的张量(Tensor)比NumPy的数组更加方便计算,并且PyTorch,调用backward()函数支持自动求导,可以方便地计算梯度,而NumPy则需要手动计算梯度。
2.激活函数Sigmoid用PyTorch自带函数torch.sigmoid(),观察、总结并陈述。
要将Sigmoid用PyTorch自带函数torch.sigmoid()代替首先要将x,y,w初始值改为张量:
x1,x2=torch.tensor(0.5),torch.tensor(0.3)
y1,y2=torch.tensor(0.23),torch.tensor(-0.07)
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])
然后,在调用自定义sigmoid()的地方 ,改为调用torch.sigmoid()
def forward_propagate(x1, x2, y1, y2, w1, w2, w3, w4, w5, w6, w7, w8):
# 输入层——>隐藏层
in_h1=w1*x1+w3*x2
in_h2=w2*x1+w4*x2
out_h1=torch.sigmoid(in_h1)
out_h2=torch.sigmoid(in_h2)
# 隐藏层——>输出层
in_o1=w5*out_h1+w7*out_h2
in_o2=w6*out_h1+w8*out_h2
out_o1=torch.sigmoid(in_o1)
out_o2=torch.sigmoid(in_o2)
#计算均方误差
E=(1/2)*((out_o1-y1)**2+(out_o2-y2)**2)
print("正向计算 隐藏层:", out_h1, out_h2)
print("正向计算 输出层:", out_o1, out_o2)
print("均方误差:",E )
return out_o1, out_o2, out_h1, out_h2,E
并且需要注意:调用torch将导致输出值中不可使用round取整。
在PyTorch中,张量的值是以浮点数形式存储的,因此在使用round()
函数时,它返回的结果也是浮点数。不可使用round取整是为了保留更多的精度,以便在进行计算时不会丢失信息。
激活函数Sigmoid换为PyTorch自带函数torch.sigmoid(),运行出来的结果与原先一致。
3.激活函数Sigmoid改变为Relu,观察、总结并陈述。
在函数中定义Relu函数:
def relu(z):
return torch.maximum(z, torch.tensor(0.))
将代码中的sigmoid()函数改为Relu
out_h1=relu(in_h1)
out_h2=relu(in_h2)
out_o1=relu(in_o1)
out_o2=relu(in_o2)
运行结果:
学习率的情况下,Relu函数和sigmoid函数相比下降较慢,但最终都降为0.1左右。
4.损失函数MSE用PyTorch自带函数 t.nn.MSELoss()替代,观察、总结并陈述。
要完成这个修改首先需要将所有变量初始化为张量:
x = torch.tensor([0.5, 0.3])
y = torch.tensor([0.23, -0.07])
w = [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])] # 权重初始值
for i in range(8):
w[i] = torch.Tensor(w[i])
w[i].requires_grad = True
然后内置torch.nn.MSELoss()函数计算损失函数:
def loss(x, y): # 损失函数
y_pre = forward_propagate(x) # 前向传播
loss_mse =(1/2)*( torch.nn.MSELoss()(y_pre[0], y[0]) + torch.nn.MSELoss()(y_pre[1], y[1]))
print("均方误差:", round(loss_mse.item(), 2))
return loss_mse
运行程序得到的答案与手动定义均方误差函数一致。
5.损失函数MSE改变为交叉熵,观察、总结并陈述。
要将损失函数MSE改变为交叉熵,需要定义相关loss函数:
在代码中,定义的交叉熵:使用二元交叉熵作为损失函数,这在多分类问题中较为常见:
def loss(x, y, w): # 损失函数
y_pre = forward_propagate(x, w) # 前向传播
E = -y[0] * torch.log(y_pre[0]) - y[1] * torch.log(y_pre[1])
print("交叉熵损失:", round(E.item(), 2))
return E
由图:交叉熵损失函数逐渐下降,0.13~-0.31交叉熵损失函数有取负的情况。主要是因为交叉熵损失函数期望的输入是概率分布,而回归问题中的真实值通常不是概率分布。因此,将真实值直接用于交叉熵损失函数可能会导致不可预测的结果。
交叉熵损失函数用于衡量两个概率分布之间的差距,而回归问题中通常关注的是预测值与真实值之间的差距,而不是它们之间的概率分布差异。所以,要计算回归问题的损失函数还是均方误差好一点。
6.改变步长,训练次数,观察、总结并陈述。
改变步长:(训练次数为:100)
step=0.1
step=0.5
step=1
step=10
当训练次数一定时,随着步长越来越大,损失的收敛越来越快。
改变训练次数:步长为0.1
训练次数:10
训练次数:100
训练次数:200
训练次数:1000
当训练步长固定为0.1时,随着训练次数的增大,更新的损失越来越低 ,权重越来越接近于最优值。
7.权值w1-w8初始值换为随机数,对比“指定权值”的结果,观察、总结并陈述。
原先的权值:
权值w1-w8初始值为随机数:
# 初始化权重
w1, w2, w3, w4, w5, w6, w7, w8 = torch.randn(8)
w1,w2,w3,w4,w5,w6,w7,w8=torch.Tensor(w1), torch.Tensor(w2), torch.Tensor(w3), torch.Tensor(
w4), torch.Tensor(w5), torch.Tensor(w6), torch.Tensor(w7), torch.Tensor(w8)
当训练步长和次数一定时,权值w1-w8初始值换为随机数与权值w1-w8固定相比,最终算出的均方误差和w的值具有一定的随机性,当w初始随机化与最优值相差较远或较近:训练一定时,最终结果就会相差较多,但随着训练次数的增多最终都会接近于最优值。
8.权值w1-w8初始值换为0,观察、总结并陈述。
权值w1-w8初始值换为0时:
# 初始化权重
w1,w2,w3,w4,w5,w6,w7,w8=torch.Tensor([0.0]), torch.Tensor([0.0]), torch.Tensor([0.0]), torch.Tensor(
[0.0]), torch.Tensor([0.0]), torch.Tensor([0.0]), torch.Tensor([0.0]), torch.Tensor([0.0])
损失函数(均方误差):固定为0.0107,相比于w的初始值定为:0.2, -0.4, 0.5, 0.6, 0.1, -0.5, -0.3, 0.8 损失函数为0.0103相对较大,但是总的来说与上面的随机定义w 的初始值相比,还是w随机初始化比较好,以便模型能够探索不同的解空间并找到一个较好的解,更容易得到最优值。
全面总结反向传播原理和编码实现,认真写心得体会。
反向传播阶段:反向传播会从输出层开始,计算输出层损失函数对每个神经元的梯度。损失会被传递到下一层,并以此类推,直到输入层。编程一般使用梯度下降的优化方法,在每一步中,计算每个神经元的误差项,然后用这些误差项来更新权重,使用学习率来控制权重的更新步长。
在学习过程中的主要过程,主要实现总结了:
对比【numpy】和【pytorch】程序,总结并陈述。
激活函数Sigmoid用PyTorch自带函数torch.sigmoid(),观察、总结并陈述。
激活函数Sigmoid改变为Relu,观察、总结并陈述。
损失函数MSE用PyTorch自带函数 t.nn.MSELoss()替代,观察、总结并陈述。
损失函数MSE改变为交叉熵,观察、总结并陈述。
改变步长,训练次数,观察、总结并陈述。
权值w1-w8初始值换为随机数,对比“指定权值”的结果,观察、总结并陈述。
权值w1-w8初始值换为0,观察、总结并陈述。
其中,相关调用torch函数的地方进行的稍慢些,主要是张量的使用还是很不熟练。在深度学习中,要习惯性使用张量。并且在反向传播算法中,梯度的计算相对困难,计算量大。但是这部分手推过程中,能够把反向传播过程流程详详细细的搞明白。
参考范文:
https://www.cnblogs.com/hbuwyg/p/16207524.htmlhttps://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/