NNDL 作业四

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

2.数值计算-手动计算,掌握细节

3.代码实现-numpy手推+pytorch自动

过程推导和数值计算如下:

代码实现:

 1.对比【numpy】和【pytorch】程序,总结并陈述。

numpy:

import numpy as np
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)
print("输出:",y1,y2)
def sigmoid(x):
    return 1/(1+np.exp(-x))
def forward(x1,x2,y1,y2,w1,w2,w3,w4,w5,w6,w7,w8):
    in_h1=w1*x1+x2*w3
    h1=sigmoid(in_h1)
    in_h2=w2*x1+w4*x2
    h2=sigmoid(in_h2)
    in_o1=w5*h1+h2*w7
    o1=sigmoid(in_o1)
    in_o2=h1*w6+h2*w8
    o2=sigmoid(in_o2)
    print(round(h1,5),round(h2,5))
    print(round(o1,5),round(o2,5))
    error=(1/2)*(o1-y1)**2+(1/2)*(o2-y2)**2
    print("均方误差:",round(error,5))
    return o1,o2,h1,h2
def back_ward(o1,o2,h1,h2):
    d_o1=o1-y1
    d_o2=o2-y2
    d_w5=d_o1*o1*(1-o1)*h1
    d_w7=d_o1*o1*(1-o1)*h2
    d_w6=d_o2*o2*(1-o2)*h1
    d_w8=d_o2*o2*(1-o2)*h2
    d_w1=x1*(1-h1)*h1*(w6*(1-o2)*o2*d_o2+w5*(1-o1)*o1*d_o1)
    d_w3=x2*(1-h1)*h1*(w6*(1-o2)*o2*d_o2+w5*(1-o1)*o1*d_o1)
    d_w2=x1*(1-h2)*h2*(w7*(1-o1)*o1*d_o1+w8*(1-o2)*o2*d_o2)
    d_w4=x2*(1-h2)*h2*(w7*(1-o1)*o1*d_o1+w8*(1-o2)*o2*d_o2)
    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(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
    return w1, w2, w3, w4, w5, w6, w7, w8
if __name__ == "__main__":

    print("权值w0-w7:",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(1):
        print("=====第" + str(i+1) + "轮=====")
        o1, o2, h1, h2 = forward(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_ward(o1, o2,h1, h2)
        w1, w2, w3, w4, w5, w6, w7, w8 = update(w1, w2, w3, w4, w5, w6, w7, 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))

pytoch:

import torch
x=[0.5,0.3]
y=[0.23,-0.07]
print("输入x0,x1:",x[0],x[1])
print("输出值y0,y1:",y[0],y[1])
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
print("权值w0-w7:")
for i in range(0,8):
    print(w[i].data,end=' ')

def forward(x):
    in_h1=w[0]*x[0]+w[2]*x[1]
    h1=torch.sigmoid(in_h1)
    in_h2=w[1]*x[0]+w[3]*x[1]
    h2=torch.sigmoid(in_h2)

    in_o1=w[4]*h1+w[6]*h2
    o1=torch.sigmoid(in_o1)
    in_o2=w[5]*h1+w[7]*h2
    o2=torch.sigmoid(in_o2)
    print("隐藏层h1,h2:",end=' ')
    print(h1.data,h2.data)
    print("预测值o1,o2:",end=' ')
    print(o1.data,o2.data)
    return o1,o2
def loss(x,y):
    y_pre=forward(x)
    loss_mse=(1/2)*(y_pre[0]-y[0])**2+(1/2)*(y_pre[1]-y[1])**2
    print("损失函数:",loss_mse.item())
    return loss_mse
if __name__=="__main__":
    for k in range(1000):
        print("\n=====第"+str(k+1)+"轮=====")
        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=' ')

         pytorch是一个深度学习框架,所有函数几乎都已经包装在里边了,使用时直接调用即可,例如:求导,各种激活函数等。而numpy需要实现激活函数,手动求导。所以使用numpy写的代码量会比使用pytorch多。但使用numpy会让你对整个流程包括细节更加熟悉。

2.激活函数Sigmoid用pytorch自带函数torch.sigmoid(),观察、总结并陈述。

        使用numpy实现的sigmoid函数训练结果:

权值w0-w7: 0.2 -0.4 0.5 0.6 0.1 -0.5 -0.3 0.8
=====第1轮=====
0.56218 0.495
0.47695 0.5287
均方误差: 0.20971
w的梯度: -0.01 0.01 -0.01 0.01 0.03 0.08 0.03 0.07
=====第5轮=====
0.56913 0.48834
0.44506 0.44726
均方误差: 0.1569
w的梯度: -0.01 0.01 -0.01 0.0 0.03 0.07 0.03 0.06
=====第10轮=====
0.58087 0.48571
0.41089 0.36465
均方误差: 0.11082
w的梯度: -0.02 0.0 -0.01 0.0 0.03 0.06 0.02 0.05
更新后的权值w: 0.33 -0.45 0.58 0.57 -0.2 -1.21 -0.56 0.19
=====第1000轮=====
0.77501 0.592
0.22963 0.00981
均方误差: 0.00319
w的梯度: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值w: 1.65 0.18 1.37 0.95 -0.78 -4.27 -1.02 -2.2

自带函数torch.sigmoid训练结果:

权值w0-w7:
tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) 
=====第1轮=====
隐藏层h1,h2: tensor([0.5622]) tensor([0.4950])
预测值o1,o2: tensor([0.4769]) tensor([0.5287])
损失函数: 0.2097097933292389
w的梯度: -0.01 0.01 -0.01 0.01 0.03 0.08 0.03 0.07
=====第5轮=====
隐藏层h1,h2: tensor([0.5691]) tensor([0.4883])
预测值o1,o2: tensor([0.4451]) tensor([0.4473])
损失函数: 0.15690487623214722
w的梯度: -0.01 0.01 -0.01 0.0 0.03 0.07 0.03 0.06 
=====第10轮=====
隐藏层h1,h2: tensor([0.5809]) tensor([0.4857])
预测值o1,o2: tensor([0.4109]) tensor([0.3647])
损失函数: 0.11082295328378677
w的梯度: -0.02 0.0 -0.01 0.0 0.03 0.06 0.02 0.05 
更新后的权值w:
tensor([0.3273]) tensor([-0.4547]) tensor([0.5764]) tensor([0.5672]) tensor([-0.1985]) tensor([-1.2127]) tensor([-0.5561]) tensor([0.1883]) 
=====第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])

        从结果上来看,使用自带函数和用numpy写没有区别,所谓结果上的微小差异,是因为使用numpy写这个程序的时候使用round函数限制了小数点后边的位数,这里取两位小数,如果取4位,和pytorch无异。

        不过,torch.sigmoid()需要传入tensor格式的数据。 

3.激活函数Sigmoid改变为Relu,观察、总结并陈述。

         更换成relu:

tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) 
=====第1轮=====
隐藏层h1,h2: tensor([0.2500]) tensor([0.])
预测值o1,o2: tensor([0.0250]) tensor([0.])
损失函数: 0.023462500423192978
w的梯度: -0.01 0.0 -0.01 0.0 -0.05 0.0 -0.0 0.0 
更新后的权值w:
tensor([0.2103]) tensor([-0.4000]) tensor([0.5062]) tensor([0.6000]) tensor([0.1513]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) 
=====第5轮=====
隐藏层h1,h2: tensor([0.2924]) tensor([0.])
预测值o1,o2: tensor([0.0855]) tensor([0.])
损失函数: 0.012893404811620712
w的梯度: -0.02 0.0 -0.01 0.0 -0.04 0.0 0.0 0.0 
更新后的权值w:
tensor([0.2834]) tensor([-0.4000]) tensor([0.5501]) tensor([0.6000]) tensor([0.3346]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) 
=====第10轮=====
隐藏层h1,h2: tensor([0.3596]) tensor([0.])
预测值o1,o2: tensor([0.1679]) tensor([0.])
损失函数: 0.004378797020763159
w的梯度: -0.01 0.0 -0.01 0.0 -0.02 0.0 0.0 0.0 
更新后的权值w:
tensor([0.3757]) tensor([-0.4000]) tensor([0.6054]) tensor([0.6000]) tensor([0.4892]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])

        可以看出,更换成relu函数后,模型收敛速度变得更快。

4. 损失函数MSE用PyTorch自带函数 t.nn.MSELoss()替代,观察、总结并陈述。
        更改之后的结果:

=====第999轮=====
正向计算:o1 ,o2
tensor([0.2298]) tensor([0.0050])
损失函数(均方误差): 0.005628135986626148
	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])

        与第二问的结果对比,使用MSELoss会使得模型收敛速度变慢。

5.损失函数MSE改变为交叉熵,观察、总结并陈述。

        更换之后,训练结果:

=====第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])

         这时候,损失变成了负数。这和交叉熵函数表达式有关。如果输出的概率大于一,结果H就是负的。当概率为0-1之间,结果是正数。交叉熵损失多用于分类问题中,在这个问题中,并不是一个分类问题。

6.改变步长,训练次数,观察,总结并陈述。

        更改numpy程序的步长,训练次数:

步长1,训练次数10:

步长10,训练次数10:

步长5,训练次数10: 

 

        由图,它的步长越大,下降速度越快。

        步长取10,训练次数50:

 

步长取10,训练次数100: 

        训练次数越大,损失越小。但是到一定次数后,下降的就非常慢了,损失变化也不大,这时候没必要训练更多次了。 

7.权值w1-w8初始值换为随机数,对比“指定权值”的结果,观察、总结并陈述。

        指定:

权值w0-w7:
tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) 
=====第1轮=====
隐藏层h1,h2: tensor([0.2500]) tensor([0.])
预测值o1,o2: tensor([0.0250]) tensor([0.])
损失函数: 0.023462500423192978
w的梯度: -0.01 0.0 -0.01 0.0 -0.05 0.0 -0.0 0.0 
更新后的权值w:
tensor([0.2103]) tensor([-0.4000]) tensor([0.5062]) tensor([0.6000]) tensor([0.1513]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) 

        随机:

权值w0-w7:
tensor([[-0.6710]]) tensor([[0.1085]]) tensor([[0.6052]]) tensor([[0.2582]]) tensor([[0.7214]]) tensor([[-1.4928]]) tensor([[0.0076]]) tensor([[-0.8092]]) 
=====第1轮=====
隐藏层h1,h2: tensor([[0.]]) tensor([[0.1317]])
预测值o1,o2: tensor([[0.0010]]) tensor([[0.]])
损失函数: 0.028670920059084892
w的梯度: 0.0 -0.0 0.0 -0.0 -0.0 0.0 -0.03 0.0 
更新后的权值w:
tensor([[-0.6710]]) tensor([[0.1094]]) tensor([[0.6052]]) tensor([[0.2587]]) tensor([[0.7214]]) tensor([[-1.4928]]) tensor([[0.0377]]) tensor([[-0.8092]]) 

        初始值的改变会影响更新速度,从梯度值可以看出来。

8.权值w1-w8初始值换为0,观察、总结并陈述。

        梯度变为零了,题目没了意义。

=====第10轮=====
隐藏层h1,h2: tensor([0.]) tensor([0.])
预测值o1,o2: tensor([0.]) tensor([0.])
损失函数: 0.02890000119805336
w的梯度: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 
更新后的权值w:
tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) tensor([0.]) 

 9.总结:

        1、在推导过程中,事实上,和老师程序中所体现的细节不一样。但是结果一样。详情可见推导中,d_w1部分画框处。

        2、步长和激活函数都会影响模型收敛速度。合适的步长能减少时间开销,加快模型收敛速度。

        3、使用numpy自己写,会加深对传播过程的记忆和理解,但是会花费较长时间,使用框架虽然能节省很多时间,但不利于理解详细过程。这两种实现方法都应该掌握,在理解的基础之上,使用框架让程序更简洁。

参考博客:https://blog.csdn.net/liuzi_hang/article/details/127116020?spm=1001.2014.3001.5502

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值