pytorch搭建一个简单的神经网络

小白一枚最近接触了pytorch框架,细读了pytorch之实战计算机视觉,唐进民一书,这里做个笔记,希望能好好学习一下这个框架。

1.手动计算梯度

  一个batch(批次)的数据从输入到输出的完整过程是:先输入100个具有1000个特征的数据x,经过隐藏层后变成了100个具有100个特征的数据,再经过输出层输出100个具有10个分类结果值的数据y,在得到结果后计算损失并反向传播,这样就完成了一次训练。然后通过epoch(循环)这个训练流程,就可以优化模型的参数。
  这里,输入层的维度是(100,1000),w1的权重为(1000,100),w2的维度为(100,10),输出层的维度为(100,10)。同理于矩阵的乘法运算。


'''需要自己算梯度'''
import  torch
batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10    #输入输出维度

x = torch.randn(batch_n,input_data)
y = torch.randn(batch_n,output_data)
# 初始化权重
w1 = torch.randn(input_data,hidden_layer)
w2 = torch.randn(hidden_layer,output_data)

epoch_n = 200
learning_rate = 1e-6    #迭代次数和学习率

#梯度下降法优化神经网络参数
for epoch in range(epoch_n):
    h1 = x.mm(w1)
    h1 = h1.clamp(min = 0) #相当于一个Relu
    y_pred = h1.mm(w2)     #正向传播

    loss = (y_pred - y).pow(2).sum() 
    print("Epoch: {}, Loss:{:.4f}".format(epoch,loss.data))   #format(),格式化函数

    grad_y_pred = 2 * (y_pred-y)
    grad_w2 = h1.t().mm(grad_y_pred)   ##.t()是转置的意思

    grad_h = grad_y_pred.clone()
    grad_h = grad_h.mm(w2.t())
    grad_h.clamp_(min = 0) 
    grad_w1 = x.t().mm(grad_h)  ##反向求梯度

    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2    #更新权重

输出结果:

Epoch: 195, Loss:25528.2734
Epoch: 196, Loss:25294.0371
Epoch: 197, Loss:25062.9824
Epoch: 198, Loss:24834.9023
Epoch: 199, Loss:24609.8242

可见损失是不断在变小的。

2.自动梯度

  上面简单的神经网络模型,我们可以通过手写代码实现前向传播和反向求导来优化参数,但对于稍微复杂的神经网络,前向传播的代码相对简单,但是反向求导的的代码很难写出来,最困难的是对模型计算逻辑的梳理。
  torch.autograd包能帮助我们完成神经网络中的反向求导,我前面的博客里有相关的笔记。

'''自动求梯度'''
import torch
from torch.autograd import Variable

batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10      #输入输出维度

x = Variable(torch.randn(batch_n,input_data),requires_grad = False)
y = Variable(torch.randn(batch_n,output_data),requires_grad = False)

w1 = Variable(torch.randn(input_data,hidden_layer),requires_grad = True)
w2 = Variable(torch.randn(hidden_layer,output_data),requires_grad = True)   #True表明在自动计算梯度中是需要保留梯度值

epoch_n = 200
learnning_rate = 1e-6

for epoch in range(epoch_n):
    y_pred = x.mm(w1).clamp(min = 0).mm(w2)
    loss = (y_pred-y).pow(2).sum()
    print("Epoch: {}, Loss:{:.4f}".format(epoch, loss.data))  # format(),格式化函数

    loss.backward()  #自动求梯度

    w1.data -= learnning_rate * w1.grad.data  #权重数据的梯度值
    w2.data -= learnning_rate * w2.grad.data

    w1.grad.data.zero_()
    w2.grad.data.zero_()  # 计算结束后将各个参数节点的梯度值置零,否则梯度会叠加,影响后续计算

这里需要注意:
1  w.grad 也是一个Variable对象,它表示的是w的梯度,想要访问梯度值的时候,需要使用w.grad.data
2. backward函数中还有retain_graph参数,使用这个参数时,再次求导的时候,会对之前的导数进行累加,所以每次反向传播前需要把梯度清零。
输出结果:

Epoch: 195, Loss:20724.7207
Epoch: 196, Loss:20540.0215
Epoch: 197, Loss:20357.6309
Epoch: 198, Loss:20177.7539
Epoch: 199, Loss:20000.4609

3.继承nn.Module

  torch.nn.Module是所有网络(net)层的父类,我们自己如果要实现层的话,需要继承该类。
不使用优化:

import torch
from torch.autograd import Variable

batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10      #输入输出维度

x = Variable(torch.randn(batch_n,input_data),requires_grad = False)
y = Variable(torch.randn(batch_n,output_data),requires_grad = False)

models = torch.nn.Sequential(
    torch.nn.Linear(input_data,hidden_layer),
    torch.nn.ReLU(),
    torch.nn.Linear(hidden_layer,output_data)
)

epoch_n = 200
learnning_rate = 1e-4
loss_fn = torch.nn.MSELoss()

for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)

    if epoch % 20 == 0:
        print("Epoch: {}, Loss:{:.4f}".format(epoch, loss.data))  # format(),格式化函数
    models.zero_grad()

    loss.backward()

    for param in models.parameters():
        param.data -= param.grad.data * learnning_rate

这里参数更新的方式发生了改变,是通过调用models.parameters()遍历所有值来完成的,然后对每个遍历的参数进行更新。
(1)torch.nn.Sequential ()类是torch.nn的一种序列容器,通过在容器中嵌套各种实现神经网络中具体功能的类,来完成对神经网络模型的搭建。另外书作者提到了用orderdict有序字典的方式搭建模型代码,需要导入:from collections import OrderedDict

models = torch.nn.Sequential(OrderedDict([
    ("Linel",torch.nn.Linear(input_data,hidden_layer)),
    ("ReLU1",torch.nn.ReLU()),
    ("Line2",torch.nn.Linear(hidden_layer,output_data))
])
)

可以方便地找到模型中不同的模块,根据不同模块的名字,打印结果如下:

Sequential(
  (Linel): Linear(in_features=1000, out_features=100, bias=True)
  (ReLU1): ReLU()
  (Line2): Linear(in_features=100, out_features=10, bias=True)
)

(2)torch.nn.MSELoss类使用均方误差函数对损失值进行计算,定义时不需要传入参数,使用时要传入两个维度一样的参数。

输出结果
Epoch: 100, Loss:0.9990
Epoch: 120, Loss:0.9974
Epoch: 140, Loss:0.9958
Epoch: 160, Loss:0.9942
Epoch: 180, Loss:0.9926

使用优化器:(自动更新参数)

#自动求梯度自动更新
import torch
from torch.autograd import Variable
batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10      #输入输出维度

x = Variable(torch.randn(batch_n,input_data),requires_grad = False)
y = Variable(torch.randn(batch_n,output_data),requires_grad = False)

models = torch.nn.Sequential(
    torch.nn.Linear(input_data,hidden_layer),   ## 输入层到隐藏层的线形变换
    torch.nn.ReLU(),                            ## 激活函数
    torch.nn.Linear(hidden_layer,output_data)   ## 隐藏层到输出层的线形变换
)
epoch_n = 200
learnning_rate = 1e-5        # # 学习率越低,需要迭代的次数会越多,loss才会变小
loss_fn = torch.nn.MSELoss() # # 定义损失函数
optimzer = torch.optim.Adam(models.parameters(),lr=learnning_rate)
for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    print("Epoch: {}, Loss:{:.4f}".format(epoch, loss.data))  # format(),格式化函数
    optimzer.zero_grad()
    loss.backward()
    optimzer.step()

(3)之前几种方法的学习速率都是固定的,所以优化的函数相对简单。对复杂的参数优化算法,代码中优化函数的部分会变得复杂。torch.nn.optim包中提供了很多的可以实现参数自动优化的类,比如SGD,Adam等,。这里传给torch.nn.Adam类的参数是parameters,初始的学习速率如果没有给定,则默认为0.001。Adam可以对梯度更新所用到的学习速率进行自适应的调节。同样,每次循环结束梯度也要置0,optimzer.step()的功能是使用计算得到的梯度值对各个节点的参数进行梯度跟新。

输出结果:
Epoch: 50, Loss:0.0016
Epoch: 51, Loss:0.0014
Epoch: 52, Loss:0.0013
Epoch: 53, Loss:0.0011
Epoch: 54, Loss:0.0010

可以看到使用Adam优化后,loss结果远低于前面所得值。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

通信仿真爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值