目录
PyTorch之nn模块
计算图和autograd是十分强大的工具,可以定义复杂的操作并自动求导;然而对于大规模的网络,autograd太过于底层。在构建神经网络是,我们经常考虑将计算安排成层,其中一些具有可学习的参数,他们将在隵过程中进行优化。
Tensorflow里面,有类似于Keras,TensorFlow-Slim和FLearn这些封装了底层计算图的高层抽象接口,这使得构建网络是否方便。
在PyTorch中,包nn完成了同样的功能。nn包中定义了一组大致等价于层的模块。一个模块接受输入的tensor,计算输出的tensor,而且还保存了一些内部状态比如学习的tensor的参数等等。nn包中也定义了一组损失函数loss functions,用来训练神经网络。
在下面的例子中,我们用nn包实现两层的网络:
#!/usr/bin/env torch
# -*- coding:utf-8 -*-
# @Time : 2021/2/7, 12:24
# @Author: Lee
# @File : nn_module.py
import torch
# N是批带下;D是输入维度;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)
# 使用nn包将我们的模型定义为一系列的层
# nn.sequential是包含了其他模块的模块,并按顺序应用这些模块来产生输出
# 每个线性模块使用线性函数从输入计算输出,并保存其内部的权重和偏差张量
# 在构造模型之后,我们使用.to()方法将其移动到所需的设备上
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
# nn包还包含常用的损失函数的定义:
# 在这种情况下,我们使用均方差(MSE)作为损失函数
# 设置reduction='sum',表示计算的是平方误差的和,而不是平均值
# 这是为了与前面手工计算损失的例子保持一致
# 但在实践中,通过设置reduction'elementwise_mean'来使用均方误差作为损失更为常见
loss_fn = torch.nn.MSELoss(reduction='sum')
learning_rate = 1e-4
for t in range(500):
# 前向传播:通过模型传入x计算预测的y
# 模块对象重载了__call__运算符,所以可以向函数那样调用它们
# 这么重相当于向模块传入了一个张量,然后它返回了一个输出张量
y_pred = model(x)
# 计算并打印损失
# 传递包含y的预测值和真实的张量,损失函数返回包含损失的张量
loss = loss_fn(y_pred, y)
if (t+1) % 50 == 0:
print("第", t, "次:loss.item() = ", loss.item(), " loss = ", loss)
# 反向传播之前清零梯度
model.zero_grad()
# 反向传播:计算模型的损失对所有可学习参数的导数(梯度)
# 在内部,每个模块的参数存储的requires_grad=True的张量中
# 因此这个调用将计算模型中所有可学习参数的梯度
loss.backward()
# 使用梯度下降更新权重
# 每个参数都是张量,所以我们可以像以前那样可以得到它的数值和梯度
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad
运行结果如下:
第 49 次:loss.item() = 26.66609764099121 loss = tensor(26.6661, grad_fn=<MseLossBackward>)
第 99 次:loss.item() = 1.8744418621063232 loss = tensor(1.8744, grad_fn=<MseLossBackward>)
第 149 次:loss.item() = 0.20531243085861206 loss = tensor(0.2053, grad_fn=<MseLossBackward>)
第 199 次:loss.item() = 0.026159264147281647 loss = tensor(0.0262, grad_fn=<MseLossBackward>)
第 249 次:loss.item() = 0.003675416810438037 loss = tensor(0.0037, grad_fn=<MseLossBackward>)
第 299 次:loss.item() = 0.0005623005563393235 loss = tensor(0.0006, grad_fn=<MseLossBackward>)
第 349 次:loss.item() = 9.252542804460973e-05 loss = tensor(9.2525e-05, grad_fn=<MseLossBackward>)
第 399 次:loss.item() = 1.6242187484749593e-05 loss = tensor(1.6242e-05, grad_fn=<MseLossBackward>)
第 449 次:loss.item() = 3.0295095712062903e-06 loss = tensor(3.0295e-06, grad_fn=<MseLossBackward>)
第 499 次:loss.item() = 5.997063681206782e-07 loss = tensor(5.9971e-07, grad_fn=<MseLossBackward>)
PyTorch之optim模块
到目前为止,我们已经通过手动改变可学习参数的张量来更新模型的权重。对于随机梯度下降(SGD/stochastic gradient descent)等简单的优化算法来说,这不是一个很大的负担,但在实践中,我们常用AdaGrad,RMSPro,Adam等更复杂的优化器来训练网络。
#!/usr/bin/env torch
# -*- coding:utf-8 -*-
# @Time : 2021/2/7, 12:47
# @Author: Lee
# @File : torch_optim.py
import torch
# N是批带下;D是输入维度;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)
# 使用nn包含定义模型和损失函数
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')
# 使用optim包定义优化器(Optimizer)。Opitmizer将会为我们更新模型的权重
# 这里我们使用Adam优化方法;optim还包含了很多别的优化方法
# Adam构造函数的第一个参数告诉优化其应该更新哪些张量
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
# 前向传播:通过模型传入x计算预测的y
y_pred = model(x)
# 计算并打印损失
loss = loss_fn(y_pred, y)
if (t+1) % 50 == 0:
print("第", t, "次:loss.item() = ", loss.item(), " loss = ", loss)
# 反向传播之前清零梯度
model.zero_grad()
# 反向传播:计算模型的参数loss对可学习参数的梯度
loss.backward()
# 调用Optimizer的step函数使它所有参数更新
optimizer.step()
运行结果如下:
第 49 次:loss.item() = 171.713134765625 loss = tensor(171.7131, grad_fn=<MseLossBackward>)
第 99 次:loss.item() = 35.17428970336914 loss = tensor(35.1743, grad_fn=<MseLossBackward>)
第 149 次:loss.item() = 4.378096103668213 loss = tensor(4.3781, grad_fn=<MseLossBackward>)
第 199 次:loss.item() = 0.41847413778305054 loss = tensor(0.4185, grad_fn=<MseLossBackward>)
第 249 次:loss.item() = 0.03814450651407242 loss = tensor(0.0381, grad_fn=<MseLossBackward>)
第 299 次:loss.item() = 0.00488114170730114 loss = tensor(0.0049, grad_fn=<MseLossBackward>)
第 349 次:loss.item() = 0.0008024392882362008 loss = tensor(0.0008, grad_fn=<MseLossBackward>)
第 399 次:loss.item() = 0.00012188169785076752 loss = tensor(0.0001, grad_fn=<MseLossBackward>)
第 449 次:loss.item() = 1.5356104995589703e-05 loss = tensor(1.5356e-05, grad_fn=<MseLossBackward>)
第 499 次:loss.item() = 1.5737085732325795e-06 loss = tensor(1.5737e-06, grad_fn=<MseLossBackward>)
自定义nn模块
有时候需要指定比现有模块序列更复杂的模型,对于这些情况,可以通过集成nnModule并定义forward函数,这个forward函数可以试用期他模块或者其他的自动求导运算来接收输入的tensor,产生输出tensor。
这个例子中,我们用自定义Module的子类构建两层网络:
#!/usr/bin/env torch
# -*- coding:utf-8 -*-
# @Time : 2021/2/7, 13:00
# @Author: Lee
# @File : custom_nn_module.py
import torch
class TwoLayerNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
"""
:param D_in: 输入维度
:param H: 隐藏层维度
:param 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):
"""
:param x: 输入值
:return: 预测值
在前向传播的函数中,我们接收一个输入的张量,也必须返回一个输出张量。
我们可以使用构造函数中定义的模块以及张量上的任意的可微分的操作
"""
h_relu = self.linear1(x).clamp(min=0)
y_pred = self.linear2(h_relu)
return y_pred
# N是批带下;D是输入维度;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)
# 使用nn包含定义模型和损失函数
model = TwoLayerNet(D_in, H, D_out)
# 构造损失函数和优化器
# SGD构造函数中对model.parameters()的调用
# 将包含模型的一部分,即两个nn.Linear模块的可学习参数
loss_fn = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
# 前向传播:通过模型传入x计算预测的y
y_pred = model(x)
# 计算并打印损失
loss = loss_fn(y_pred, y)
if (t+1) % 50 == 0:
print("第", t, "次:loss.item() = ", loss.item(), " loss = ", loss)
# 清零梯度, 反向传播, 更新权重
model.zero_grad()
loss.backward()
optimizer.step()
运行结果如下:
第 49 次:loss.item() = 34.36606979370117 loss = tensor(34.3661, grad_fn=<MseLossBackward>)
第 99 次:loss.item() = 2.108647346496582 loss = tensor(2.1086, grad_fn=<MseLossBackward>)
第 149 次:loss.item() = 0.22304271161556244 loss = tensor(0.2230, grad_fn=<MseLossBackward>)
第 199 次:loss.item() = 0.03183714672923088 loss = tensor(0.0318, grad_fn=<MseLossBackward>)
第 249 次:loss.item() = 0.005248690489679575 loss = tensor(0.0052, grad_fn=<MseLossBackward>)
第 299 次:loss.item() = 0.0009347241139039397 loss = tensor(0.0009, grad_fn=<MseLossBackward>)
第 349 次:loss.item() = 0.00017452010069973767 loss = tensor(0.0002, grad_fn=<MseLossBackward>)
第 399 次:loss.item() = 3.358458343427628e-05 loss = tensor(3.3585e-05, grad_fn=<MseLossBackward>)
第 449 次:loss.item() = 6.600801498279907e-06 loss = tensor(6.6008e-06, grad_fn=<MseLossBackward>)
第 499 次:loss.item() = 1.3166297776479041e-06 loss = tensor(1.3166e-06, grad_fn=<MseLossBackward>)
控制流和权重共享
作为动态图和权重共享的例子,这里有一个非常奇怪的模型:一个全连接的ReLU网络,在每一册前向传播时,它的隐藏层的层数为随机1到4之间的数,这样可以多次重复相同的权重来计算。
因为这个模型可以使用普通的Python流控制来实现循环,并且我们可以通过在定义转发时多次重用同一个模块来实现最易内层之间的权重共享。
我们利用Module的子类来实现这个模型:
#!/usr/bin/env torch
# -*- coding:utf-8 -*-
# @Time : 2021/2/7, 13:13
# @Author: Lee
# @File : share_weight.py
import random
import torch
# 自定义网络
class DynamicNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
"""
:param D_in: 输入维度
:param H: 隐藏层维度
:param D_out: 输出维度
在构造函数中,我们实例化了三个nn.Linear模块,他们将在前向传播中被使用
"""
super(DynamicNet, self).__init__()
self.input_linear = torch.nn.Linear(D_in, H)
self.middle_linear = torch.nn.Linear(H, H)
self.output_linear = torch.nn.Linear(H, D_out)
def forward(self, x):
"""
:param x: 输入值
:return: 预测值
对于模型的前向传播,我们随机选择0,1,2,3,
并重用了多次计算隐藏层的middle_linear模块
由于每次前向传播构建一个动态计算图
我们可以在定义模型的前向传播时使用常规Python控制流运算符,如循环或条件语句
在这里,我们还看到,在定义计算图形时多次重用一个模块是完全安全的
这是对Lua Torch的一大改进,因为Lua Torch中每个模块只能使用一次
"""
h_relu = self.input_linear(x).clamp(min=0)
for _ in range(random.randint(0, 3)):
h_relu = self.middle_linear(h_relu).clamp(min=0)
y_pred = self.output_linear(h_relu)
return y_pred
# N是批带下;D是输入维度;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)
# 使用nn包含定义模型和损失函数
model = DynamicNet(D_in, H, D_out)
# 构造损失函数和优化器
# 用平凡的随机梯度下降训练这个奇怪的模型是困难的,所以我们使用了momentum方法
loss_fn = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
# 前向传播:通过模型传入x计算预测的y
y_pred = model(x)
# 计算并打印损失
loss = loss_fn(y_pred, y)
if (t+1) % 50 == 0:
print("第", t, "次:loss.item() = ", loss.item(), " loss = ", loss)
# 清零梯度, 反向传播, 更新权重
model.zero_grad()
loss.backward()
optimizer.step()
运行结果如下:
第 49 次:loss.item() = 54.70349884033203 loss = tensor(54.7035, grad_fn=<MseLossBackward>)
第 99 次:loss.item() = 30.16559600830078 loss = tensor(30.1656, grad_fn=<MseLossBackward>)
第 149 次:loss.item() = 7.247811317443848 loss = tensor(7.2478, grad_fn=<MseLossBackward>)
第 199 次:loss.item() = 30.601959228515625 loss = tensor(30.6020, grad_fn=<MseLossBackward>)
第 249 次:loss.item() = 3.3245818614959717 loss = tensor(3.3246, grad_fn=<MseLossBackward>)
第 299 次:loss.item() = 1.3237444162368774 loss = tensor(1.3237, grad_fn=<MseLossBackward>)
第 349 次:loss.item() = 3.976801872253418 loss = tensor(3.9768, grad_fn=<MseLossBackward>)
第 399 次:loss.item() = 0.22191806137561798 loss = tensor(0.2219, grad_fn=<MseLossBackward>)
第 449 次:loss.item() = 0.40976208448410034 loss = tensor(0.4098, grad_fn=<MseLossBackward>)
第 499 次:loss.item() = 0.07099712640047073 loss = tensor(0.0710, grad_fn=<MseLossBackward>)