NNDL 作业3 反向传播BP

本文详细介绍了前馈神经网络中的BP原理,包括前向传播、sigmoid函数、反向传播过程以及手动计算和代码实现,特别比较了numpy和pytorch在实现中的异同,强调了激活函数和损失函数的选择对训练效果的影响。
摘要由CSDN通过智能技术生成

1.过程推导 - 了解BP原理

前馈神经网络:误差反向传播


前向传播:根据公式前向传播即可。

sigmoid函数: sigmoid(x)=\frac{1}{1+e^{-x}}

In_{h1}=w_{1}x_{1}+w_{3}x_{2}h_{1}=sigmoid(In_{h1})
In_{h2}=w_{2}x_{1}+w_{4}x_{2}h_{2}=sigmoid(In_{h2})
In_{o1}=w_{5}h_{1}+w_{7}h_{2}o_{1}=sigmoid(In_{o1})
In_{o2}=w_{6}h_{1}+w_{8}h_{2}o_{2}=sigmoid(In_{o2})
Error=\frac{1}{2}\sum_{i=1}^{2}(o_{i}-y_{i})^{2}

反向传播:

  • 计算梯度:w_5\sim w_8

\bigtriangleup w_{5}=\frac{\partial Error}{\partial w_{5}}=\frac{\partial Error}{\partial o_1}\cdot \frac{\partial o_1}{\partial Ino_1}\cdot \frac{\partial Ino_1}{\partial w_5}

然后一步步计算

\frac{\partial Error}{\partial o_1}=\frac{\partial \frac{1}{2}\sum_{i=1}^{2}(o_i-y_i)^{2}}{\partial o_1}=o_1-y_1

由sigmoid()函数性质:  {\sigma}'(x)=\sigma(x) (1-\sigma(x) )

    \frac{\partial o_1}{\partial Ino_1}=\frac{\partial sigmoid(Ino_1)}{\partial Ino_1}=o_1(1-o_1)        

\frac{\partial (w_{5}h_1+w_7h_2)}{\partial w_5}=h_1

同上所述:

\bigtriangleup w_5=o_1(o_1-y_1)(1-o_1)h_1
\bigtriangleup w_6=o_2(o_2-y_2)(1-o_2)h_1
\bigtriangleup w_7=o_1(o_1-y_1)(1-o_1)h_2
\bigtriangleup w_8=o_2(o_2-y_2)(1-o_2)h_2
  • 计算梯度:w_1\sim w_4

\bigtriangleup w_1=\frac{\partial Error}{\partial w_1}=\frac{\partial Error}{\partial o_1}\cdot \frac{\partial o_1}{\partial w_1}+\frac{\partial Error}{\partial o_2}\cdot \frac{\partial o_2}{\partial w_1}

=\frac{\partial Error}{\partial o_1}\cdot \frac{\partial o_1}{\partial Ino_1}\cdot \frac{\partial Ino_1}{\partial h_1}\cdot \frac{\partial h_1}{\partial Inh_1}\cdot \frac{\partial Inh_1}{\partial w_1}+\frac{\partial Error}{\partial o_2}\cdot \frac{\partial o_2}{\partial Ino_2}\cdot \frac{\partial Ino_2}{\partial h_1}\cdot \frac{\partial h_1}{\partial Inh_1}\cdot \frac{\partial Inh_1}{\partial w_1}

=(\frac{\partial Error}{\partial o_1}\cdot \frac{\partial o_1}{\partial Ino_1\cdot}\cdot \frac{\partial Ino_1}{\partial h_1}+\frac{\partial Error}{\partial o_2}\cdot \frac{\partial o_2}{\partial Ino_2\cdot}\cdot \frac{\partial Ino_2}{\partial h_1})\cdot \frac{h_1}{\partial Inh_1}\cdot \frac{\partial Inh_1}{\partial w_1}

=[(o_1-y_1)\cdot o_1\cdot (1-o_1)\cdot w_5+(o_2-y_2)\cdot o_2\cdot (1-o_2)\cdot w_6]\cdot h_1\cdot (1-h_1)\cdot x_1

同上所述:

\bigtriangleup w_1=[(o_1-y_1)\cdot o_1\cdot (1-o_1)\cdot w_5+(o_2-y_2)\cdot o_2\cdot (1-o_2)\cdot w_6]\cdot h_1\cdot (1-h_1)\cdot x_1
\bigtriangleup w_2=[(o_1-y_1)\cdot o_1\cdot (1-o_1)\cdot w_7+(o_2-y_2)\cdot o_2\cdot (1-o_2)\cdot w_8]\cdot h_2\cdot (1-h_2)\cdot x_1
\bigtriangleup w_3=[(o_1-y_1)\cdot o_1\cdot (1-o_1)\cdot w_5+(o_2-y_2)\cdot o_2\cdot (1-o_2)\cdot w_6]\cdot h_1\cdot (1-h_1)\cdot x_2
\bigtriangleup w_4=[(o_1-y_1)\cdot o_1\cdot (1-o_1)\cdot w_7+(o_2-y_2)\cdot o_2\cdot (1-o_2)\cdot w_8]\cdot h_2\cdot (1-h_2)\cdot x_2
  • 更新参数:
    w_1=w_1-\eta *\bigtriangleup w_1w_5=w_5-\eta *\bigtriangleup w_5
    w_2=w_2-\eta *\bigtriangleup w_2w_6=w_6-\eta *\bigtriangleup w_6
    w_3=w_3-\eta *\bigtriangleup w_3w_7=w_7-\eta *\bigtriangleup w_7
    w_4=w_4-\eta *\bigtriangleup w_4w_8=w_8-\eta *\bigtriangleup w_8

2.数值计算 - 手动计算

进行第一轮计算:定义初始参数

  • 前向传播
In_{h1}=w_{1}x_{1}+w_{3}x_{2}=0.25h_{1}=sigmoid(In_{h1})=0.56
In_{h2}=w_{2}x_{1}+w_{4}x_{2}=-0.02h_{2}=sigmoid(In_{h2})=0.5
In_{o1}=w_{5}h_{1}+w_{7}h_{2}=-0.09o_{1}=sigmoid(In_{o1})=0.48
In_{o2}=w_{6}h_{1}+w_{8}h_{2}=0.11

o_{2}=sigmoid(In_{o2})=0.53

Error=\frac{1}{2}\sum_{i=1}^{2}(o_{i}-y_{i})^{2}= 0.21
  • 计算梯度:w_5\sim w_8
\bigtriangleup w_5=o_1(o_1-y_1)(1-o_1)h_1=0.03
\bigtriangleup w_6=o_2(o_2-y_2)(1-o_2)h_1=0.08
\bigtriangleup w_7=o_1(o_1-y_1)(1-o_1)h_2=0.03

\bigtriangleup w_8=o_2(o_2-y_2)(1-o_2)h_2=0.07

  • 计算梯度:w_1\sim w_4 
\bigtriangleup w_1=-0.01 
\bigtriangleup w_2= 0.01
\bigtriangleup w_3= -0.01
\bigtriangleup w_4=0.01

  • 参数更新:假设学习率(\eta =1

反向传播过程如图所示:

w_1=w_1-\eta *\bigtriangleup w_1=0.21w_5=w_5-\eta *\bigtriangleup w_5=0.07
w_2=w_2-\eta *\bigtriangleup w_2=-0.41w_6=w_6-\eta *\bigtriangleup w_6=-0.58
w_3=w_3-\eta *\bigtriangleup w_3=0.51w_7=w_7-\eta *\bigtriangleup w_7=-0.33
w_4=w_4-\eta *\bigtriangleup w_4=0.59w_8=w_8-\eta *\bigtriangleup w_8=0.73

 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)

运行结果:

学习率\eta =1的情况下,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函数:

在代码中,定义的交叉熵:使用二元交叉熵作为损失函数,这在多分类问题中较为常见:L(y,f(x;\theta ))=-y^{^{T}}logf(x;\theta )=-\sum_{c=1}^{c}y_{c}logf_{c}(x;\theta )

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值