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()