pytorch入门(三):用例子学pytorch

自动求导

pytorch:张量和自动求导

pytorch自动求导原理:
使用autograd包,网络正向传播定义计算图;图中的节点作为张量,图中的边作为从输入张量产生输出向量的函数。
通过图反向传播,计算梯度。

利用 x.requires_grad=True: 来确定反向传播是否计算梯度。

loss.backward() : 反向传播更新参数。
loss.backward()及注意事项

x.mm(w) :tensor x与w相乘

torch.clamp(input, min, max, out=None) : 将每个张量限定在指定范围内
参考链接

# -*- coding: utf-8 -*-
import torch

dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # Uncomment this to run on GPU

# N是批尺寸大小;D_in是输入维度;
# H是隐藏层维度;D_out是输出维度 
N, D_in, H, D_out = 64, 1000, 100, 10

# 产生随机输入和输出数据,将requires_grad置为False,意味着我们不需要在反向传播时候计算这些值的梯度
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 产生随机权重tensor,将requires_grad设置为True,意味着我们希望在反向传播时候计算这些值的梯度
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    # 前向传播:使用tensor的操作计算预测值y。
    # 由于w1和w2有requires_grad=True,涉及这些张量的操作将让PyTorch构建计算图,从而允许自动计算梯度。
    # 由于我们不再手工实现反向传播,所以不需要保留中间值的引用。
    y_pred = x.mm(w1).clamp(min=0).mm(w2)
    
    # 计算并输出loss
    # loss是一个形状为(1,)的张量
    # loss.item()是这个张量对应的python数值
    loss = (y_pred - y).pow(2).sum()  #均方误差
    if t % 100 == 99:
        print(t, loss.item())

    # 使用autograd计算反向传播,这个调用将计算loss对所有requires_grad=True的tensor的梯度。
    # 这次调用后,w1.grad和w2.grad将分别是loss对w1和w2的梯度张量。
    loss.backward()

    # 使用梯度下降更新权重。对于这一步,我们只想对w1和w2的值进行原地改变;不想为更新阶段构建计算图,
    # 所以我们使用torch.no_grad()上下文管理器防止PyTorch为更新构建计算图
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # 反向传播之后手动将梯度置零
        w1.grad.zero_()
        w2.grad.zero_()

注意

手动清零的原因

上述代码中需要在反向传播后手动将梯度清零,理由如下:

1.根据pytorch中的backward()函数的计算,当网络参量进行反馈时,梯度是被积累的而不是被替换掉;但是在每一个batch时毫无疑问并不需要将两个batch的梯度混合起来累积,因此这里就需要每个batch设置一遍zero_grad 了。

2.如果不是每一个batch就清除掉原有的梯度,而是比如说两个batch再清除掉梯度,这是一种变相提高batch_size的方法,对于计算机硬件不行,但是batch_size可能需要设高的领域比较适合,比如目标检测模型的训练。

参考链接

.clamp(min=0)和F.relu的区别

虽然从定义表达上来看二者是相同的,都是连续函数。但是在自变量等于零的时候,ReLu不可导,x=0时,clamp选择导数为1,而ReLu选择导数为0,把训练部分代码替换观察结果。

# -*- coding: utf-8 -*-
import torch
import torch.nn as nn

dtype = torch.float
# device = torch.device("cpu")
device = torch.device("cuda:0") # Uncomment this to run on GPU

# N是批尺寸大小;D_in是输入维度;
# H是隐藏层维度;D_out是输出维度 
N, D_in, H, D_out = 64, 1000, 100, 10

# 产生随机输入和输出数据,将requires_grad置为False,意味着我们不需要在反向传播时候计算这些值的梯度
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 产生随机权重tensor,将requires_grad设置为True,意味着我们希望在反向传播时候计算这些值的梯度
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
m = nn.ReLU(inplace = True)
for t in range(500):
    y_pred = m(x.mm(w1)).mm(w2)
    
    loss = (y_pred - y).pow(2).sum()  
    if t % 100 == 99:
        print(t, loss.item())
    loss.backward()

    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad
        w1.grad.zero_()
        w2.grad.zero_()

pytorch:定义新的自动求导函数

自动求导的本质:forward函数计算从输入Tensors获得的输出Tensors。而backward函数接收输出Tensors对于某个标量值的梯度,并且计算输入Tensors相对于该相同标量值的梯度(有点疑惑)。

定义 torch.autograd.Function 的子类并实现 forward 和 backward 函数

import torch

class MyReLU(torch.autograd.Function):
    def forward(ctx, input):
        """
        在前向传播中,我们收到包含输入和返回的张量包含输出的张量。 
        ctx是可以使用的上下文对象存储信息以进行向后计算。 
        您可以使用ctx.save_for_backward方法缓存任意对象,以便反向传播使用。
        """
        ctx.save_for_backward(input)
        return input.clamp(min=0)
    
    def backward(ctx, grad_output):  #????????
        input, = ctx.saved_tensors
        grad_input = grad_output.clone()  #?????
        grad_input[input < 0] = 0
        return grad_input
    
dtype = torch.float
device = torch.device("cuda:0")
                      
N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    relu = MyReLU()
    #relu = MyReLU.apply
    y_pred = relu(x.mm(w1)).mm(w2)
    #y_pred = x.mm(w1).clamp(min=0).mm(w2)
    loss = (y_pred - y).pow(2).sum()
    if t % 100 == 99:
        print(t, loss.item())
    
    loss.backward()
    with torch.no_grad():
        w1 -= learning_rate*w1.grad
        w2 -= learning_rate*w2.grad
        w1.grad.zero_()
        w2.grad.zero_()

注意

在上述代码中,如果按照官方文档调用自定义类 MyReLU relu = MyReLU.apply 会报错:NotImplementedError

解决方法:直接声明类 relu = MyReLU()

nn模块

引入nn和optim

nn:定义一组大致等价于层的模块。一个模块接受输入的tesnor,计算输出的tensor,而且还保存了一些内部状态比如需要学习的tensor的参数等。nn包中也定义了一组损失函数(loss functions),用来训练神经网络。
optim:调用较为复杂的优化器。
nn.Sequential:是包含其他模块的模块,并按顺序应用这些模块来产生其输出。
.to() :方法将输出移动到所需的设备。
nn.MSELoss(reduction = ‘sum’) :计算平方误差之和,而不是平均值,这与先前手工计算损失函数一致。

注意:在实践中,通过设置reduction='elementwise_mean 来使用均方误差作为损失更为常见。

import torch

device = torch.device("cuda:0" if torch.cuda.is_available() else 'cpu')
N, D_in, H, D_out = 64, 1000, 100, 1000

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),
    ).to(device)
loss_fn = torch.nn.MSELoss('sum')
loss_fn.cuda(device)

learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

for t in range(500):
    y_pred = model(x.cuda(device))
    y = y.cuda(device)
    loss = loss_fn(y_pred, y)
    print(t, loss.item())
    
    #反向传播前请零梯度
    optimizer.zero_grad()  
    loss.backward()
    optimizer.step() #利用优化器更新权重

注意

使用cuda device = torch.device(“cuda:0” if torch.cuda.is_available() else ‘cpu’) 报错

解决方法
对输入x,输出y,model,loss函数包裹上 .cuda(device)

pytorch:自定义nn模块

# 可运行代码见本文件夹中的 two_layer_net_module.py
import torch

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

class TwoLayerNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        在构造函数中,我们实例化了两个nn.Linear模块,并将它们作为成员变量。
        """
        super(TwoLayerNet, self).__init__()
        self.linear1 = torch.nn.Linear(D_in, H)
        self.linear2 = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        在前向传播的函数中,我们接收一个输入的张量,也必须返回一个输出张量。
        我们可以使用构造函数中定义的模块以及张量上的任意的(可微分的)操作。
        """
        h_relu = self.linear1(x).clamp(min=0)
        y_pred = self.linear2(h_relu)
        return y_pred

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = TwoLayerNet(D_in, H, D_out).cuda(device)

loss_fn = torch.nn.MSELoss(reduction='sum').cuda(device)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):

    y_pred = model(x.cuda(device))
    y = y.cuda(device)

    loss = loss_fn(y_pred, y)
    print(t, loss.item())

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

pytorch:控制流和权重共享

import random
import torch

class DynamicNet(torch.nn.Module):
    def __init__(self, D_in, D_out):
        super(DynamicNet, self).__init__()
        self.input_linear = torch.nn.Linear(D_in, H)
        self.hidden_linear = torch.nn.Linear(H, H)
        self.output_linear = torch.nn.Linear(H, D_out)
    
    def forward(self, x):
        h_relu = self.input_linear(x).clamp(min=0)
        for _ in range(random.randint(0, 3)):
            h_relu = self.hidden_linear(h_relu).clamp(min=0)
        y_pred = self.output_linear(h_relu)
        return y_pred
    
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = DynamicNet(D_in, D_out)
loss_fn = torch.nn.MSELoss(reduction="sum")
optimizer = torch.optim.SGD(model.parameters(), lr = 1e-4, momentum=0.9)

for t in range(1000):
    y_pred = model(x)
    loss = loss_fn(y_pred, y)
    print(t, loss.item())
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值