Pytorch实现两层神经网络

目录

Pytorch搭建模型

神经网络的基本流程可以分为两大步骤:

网络结构搭建+参数的梯度更新(前向传播+计算参数的梯度+梯度更新)

numpy实现

import numpy as np
 
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
 
# 创建输入、输出数据
x = np.random.randn(N, D_in)  #(64,1000)
y = np.random.randn(N, D_out) #(64,10)可以看成是一个10分类问题
 
# 权值初始化
w1 = np.random.randn(D_in, H)  #(1000,100),即输入层到隐藏层的权重
w2 = np.random.randn(H, D_out) #(100,10),即隐藏层到输出层的权重
 
learning_rate = 1e-6   #学习率
 
for t in range(500):
    # 第一步:数据的前向传播,计算预测值p_pred
    h = x.dot(w1)
    h_relu = np.maximum(h, 0)
    y_pred = h_relu.dot(w2)
 
    # 第二步:计算计算预测值p_pred与真实值的误差
    loss = np.square(y_pred - y).sum()
    print(t, loss)
 
    # 第三步:反向传播误差,更新两个权值矩阵
    grad_y_pred = 2.0 * (y_pred - y)     #注意:这里的导函数也是自己求的,因为这个地方是很简答的函数表达式
    grad_w2 = h_relu.T.dot(grad_y_pred)
    grad_h_relu = grad_y_pred.dot(w2.T)
    grad_h = grad_h_relu.copy()
    grad_h[h < 0] = 0
    grad_w1 = x.T.dot(grad_h)
 
    # 更新参数
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2

缺点:

  • 没办法搭建复杂的网络结构(网络的结构搭建太底层)

  • 梯度需要自己求导函数,如果函数太复杂,网络太深就很难求

  • 没有办法使用GPU加速

torch的Tensor实现

import torch
 
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # 这里使用CPU,但实际上可以使用GPU
 
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
 
# 创建输入、输出数据
x = torch.randn(N, D_in, device=device, dtype=dtype)  #(64,1000)
y = torch.randn(N, D_out, device=device, dtype=dtype) #(64,10)可以看成是一个10分类问题
 
# 权值初始化
w1 = torch.randn(D_in, H, device=device, dtype=dtype) #(1000,100),即输入层到隐藏层的权重
w2 = torch.randn(H, D_out, device=device, dtype=dtype) #(100,10),即隐藏层到输出层的权重
 
learning_rate = 1e-6
 
for t in range(500):
    
    # 第一步:数据的前向传播,计算预测值p_pred
    h = x.mm(w1)
    h_relu = h.clamp(min=0)
    y_pred = h_relu.mm(w2)
 
    # 第二步:计算计算预测值p_pred与真实值的误差
    loss = (y_pred - y).pow(2).sum().item()
    print(t, loss)
 
    # 第三步:反向传播误差,更新两个权值矩阵
    grad_y_pred = 2.0 * (y_pred - y)    #注意:这里的导函数也是自己求的,因为这个地方是很简答的函数表达式
    grad_w2 = h_relu.t().mm(grad_y_pred)
    grad_h_relu = grad_y_pred.mm(w2.t())
    grad_h = grad_h_relu.clone()
    grad_h[h < 0] = 0
    grad_w1 = x.t().mm(grad_h)
 
    # 参数更新(梯度下降法)
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2

解决的问题:

  • GPU计算

缺点:

  • 没办法搭建复杂的网络结构(网络的结构搭建太底层)

  • 梯度需要自己求导函数,如果函数太复杂,网络太深就很难求

torch的自动求导

import torch
 
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # 这里使用CPU,但实际上可以使用GPU
 
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
 
# 创建输入、输出数据
x = torch.randn(N, D_in, device=device, dtype=dtype)  #(64,1000)
y = torch.randn(N, D_out, device=device, dtype=dtype) #(64,10)可以看成是一个10分类问题
 
# 权值初始化
w1 = torch.randn(D_in, H, device=device, dtype=dtype) #(1000,100),即输入层到隐藏层的权重
w2 = torch.randn(H, D_out, device=device, dtype=dtype)#(100,10),即隐藏层到输出层的权重
 
learning_rate = 1e-6
 
for t in range(500):
  
    # 第一步:数据的前向传播,计算预测值p_pred
    y_pred = x.mm(w1).clamp(min=0).mm(w2)
 
    # 第二步:计算计算预测值p_pred与真实值的误差
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())
 
    # 第三步:反向传播误差,更新两个权值矩阵,这就是关键了,不再需要自己写出导函数,求导是自动完成的
    loss.backward()  #一步到位、自动求导
 
    # 参数梯度更新
    with torch.no_grad():
        w1 -= learning_rate * w1.grad  #grad属性获取梯度,其实这个地方就是相当于是梯度下降法,优化的过程可以自己定义,因为这里参数很少
        w2 -= learning_rate * w2.grad
 
        求完一次之后将梯度归零
        w1.grad.zero_()
        w2.grad.zero_()

解决的问题:

  • GPU计算
  • 梯度导数不用自己求

缺点:

  • 没办法搭建复杂的网络结构

pytorch.nn模块

import torch
 
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
 
# 创建输入、输出数据
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
 
# 模型搭建:这是与前面关键的区别,不再需要自己手动进行矩阵相乘,而是这种一步到位的方法
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.ReLU(),
    torch.nn.Linear(H, D_out),
)
 
# 定义损失函数
loss_fn = torch.nn.MSELoss(reduction=\'sum\')
 
learning_rate = 1e-4
 
for t in range(500):
    
    # 第一步:数据的前向传播,计算预测值p_pred
    y_pred = model(x)
 
    # 第二步:计算计算预测值p_pred与真实值的误差
    loss = loss_fn(y_pred, y)
    print(t, loss.item())
 
    # 在反向传播之前,将模型的梯度归零
    model.zero_grad()
 
    # 第三步:反向传播误差,更新两个权值矩阵,这就是关键了,不再需要自己写出导函数,求导是自动完成的
    loss.backward()
 
    # 更新参数的梯度
    with torch.no_grad():
        for param in model.parameters():
            param -= learning_rate * param.grad #这其实就是梯度下降法,优化参数,通过循环自动实现,不要再一个一个写了,相较于上面的参数更新方法简单了很多

解决的问题:

  • 解决了GPU计算问题
  • 梯度导数不用自己求
  • 可以搭建复杂的网络,不需要自己进行一个一个的矩阵相乘

思考:

  • 还需要通过一个循环来对模型的各个参数逐个进行参数的更新

torch.optim简化

import torch
 
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
 
# 创建输入、输出数据
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
 
# 模型搭建:这是与前面关键的区别,不再需要自己手动进行矩阵相乘,而是这种一步到位的方法
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.ReLU(),
    torch.nn.Linear(H, D_out),
)
 
# 定义损失函数
loss_fn = torch.nn.MSELoss(reduction=\'sum\')
 
learning_rate = 1e-4
# 构造一个optimizer对象
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
 
for t in range(500):
    
    # 第一步:数据的前向传播,计算预测值p_pred
    y_pred = model(x)
 
    # 第二步:计算计算预测值p_pred与真实值的误差
    loss = loss_fn(y_pred, y)
    print(t, loss.item())
 
    # 在反向传播之前,将模型的梯度归零
    optimizer.zero_grad()
 
    # 第三步:反向传播误差
    loss.backward()
 
    # 直接通过梯度一步到位,更新完整个网络的训练参数,一句话优化所有的参数
    optimizer.step()

进一步省略手动的参数更新

自定义nn Modules

import torch
import torch.nn as nn
 
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
 
# 创建输入、输出数据
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
 
class TwoLayerNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        super(TwoLayerNet, self).__init__()
        # define the model architecture
        self.linear1 = torch.nn.Linear(D_in, H, bias=False)
        self.linear2 = torch.nn.Linear(H, D_out, bias=False)
        
    def forward(self, x):
        y_pred = self.linear2(self.linear1(x).clamp(min=0))
        return y_pred    

# 模型搭建:这是与前面关键的区别,不再需要自己手动进行矩阵相乘,而是这种一步到位的方法
model = TwoLayerNet(D_in, H, D_out)

# 定义损失函数
loss_fn = torch.nn.MSELoss(reduction=\'sum\')
learning_rate = 1e-4
# 构造一个optimizer对象
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
 
for t in range(500):
    
    # 第一步:数据的前向传播,计算预测值p_pred
    y_pred = model(x)
 
    # 第二步:计算计算预测值p_pred与真实值的误差
    loss = loss_fn(y_pred, y)
    print(t, loss.item())
 
    # 在反向传播之前,将模型的梯度归零
    # 进来一个batch的数据,计算一次梯度,更新一次网络,所以每次不将模型的梯度归零,backward()就会累加梯度
    optimizer.zero_grad()
 
    # 第三步:反向传播误差
    loss.backward()
 
    # 直接通过梯度一步到位,更新完整个网络的训练参数,一句话优化所有的参数,是不是很牛逼
    optimizer.step()

我们可以定义一个模型,这个模型继承自nn.Module类

总结

用pytorch搭建神经网路的一般步骤

模型以及模型相关的配置:

  1. 搭建网络的结构,得到一个model

  2. 定义损失函数

    loss = torch.nn.MSELoss(reduction=\'sum\')

  3. 定义优化方式,构造一个optimizer对象

    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

训练:

  1. 计算y_pred

  2. 根据损失函数计算loss

  3. 梯度归零

    optimizer.zero_grad()

  4. 反向传播误差

    loss.backward()

  5. 更新参数,使用step()

    optimizer.step()