文章目录
0. pytorch 介绍
PyTorch 是一个用于深度学习的开源机器学习库,提供了灵活的张量计算和动态计算图、模块化设计、自动求导以及丰富的生态和支持多平台部署使得深度学习的实验和原型开发变得更加直观和灵活。
先导入所需要的库:
import torch
import torchvision
import torch.nn as nn # 网络层结构
import numpy as np
import torchvision.transforms as transforms
from torch.utils.data import DataLoader, Dataset
1. 基础自动梯度例子 1
tensor(张量)
:(类似于numpy
的 ndarry
)可以理解为里面有梯度信息的数组(一般多维),方便自动计算梯度,也方便放到GPU中进行加速。所以使用 pytorch
深度学习首先将数据转换成tensor
首先一个简单的求梯度的例子:
# 创建 tensors.
x = torch.tensor(1., requires_grad=True)
w = torch.tensor(2., requires_grad=True)
b = torch.tensor(3., requires_grad=True)
# 构建计算图
y = w * x + b # y = 2 * x + 3
# 计算梯度
y.backward()
print(x.grad) # tensor(2.)
print(w.grad) # tensor(1.)
print(b.grad) # tensor(1.)
总结:使用tensor.backward()
自动求梯度,使用tensor.grad
查看梯度。
2. 基础自动梯度例子 2
接下来是一个简单的线性回归模型中的单步梯度下降的例子:
# 创建 tensor 数据
x = torch.randn((10,3))
y = torch.randn((10,2))
# 构建一个全联接层
linear = nn.Linear(3, 2)
# 输出权重和偏置
print('w:', linear.weight)
print('b:', linear.bias)
"""输出
w: Parameter containing:
tensor([[ 0.5164, 0.5530, -0.3100],
[ 0.0614, 0.2425, -0.4463]], requires_grad=True)
b: Parameter containing:
tensor([-0.4081, -0.3630], requires_grad=True)
"""
上面:
- 我们先定义了特征
x
和标签y
(torch.tensor
)类型。 - 然后定义了一个最简单的模型: 单个线性层(全连接层)
- 查看了全连接层的初始
w
和b
(torch.tensor
)类型。
接下来就是创建损失函数和优化器,然后进行梯度下降:
# 创建损失函数和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(linear.parameters(), lr=0.01)
# 优化器梯度清零
optimizer.zero_grad()
# 前向传播
pred = linear(x)
# 计算损失
loss = criterion(pred, y)
print(f'loss: {loss.item()}')
# 反向传播
loss.backward()
# 输出梯度
print(f'dL/dw: {linear.weight.grad}')
print(f'dL/db: {linear.bias.grad}')
# 进行一步梯度下降
optimizer.step()
"""
loss: 1.9817368984222412
dL/dw: tensor([[ 0.5195, 0.8919, 1.3152],
[-0.5160, -0.2444, -0.5712]])
dL/db: tensor([-1.2288, -0.0866])
"""
然后:
- 我们定义了一个损失函数:
MSEloss
- 以及一个优化器:随机梯度下降
SGD
- 然后使用损失函数得到了损失
loss
- 然后进行反向传播求出梯度
- 然后进行梯度下降
optimizer.step()
经过以上步骤我们对参数进行了一次更新:
我们可以通过这个更新可以看到loss由初始降低了,也就是更预测值接近真实值了。
这里还有一个知识点:
optimizer.zero_grad()
: 这是因为在 pytorch
中梯度默认是累加的,不会自动清零。我们需要在每个batch
进行一个手动清零。
那为什么不直接设计成梯度下降完自动清零呢?
因为这样做有下面几个好处:
1. 有助于使用显存小的机器也可以 batch 训练(累计几次的梯度再进行梯度下降)
2. 多任务时训练时,可以不用每个计算图都同时加载到内存,而使用梯度累加不同任务的损失,然后一起进行优化。
3. 从 numpy 导入数据
上面我们的数据都是通过 torch
构建的,下面学习如何从numpy-ndarry
得到 tensor
.
# 构建一个 numpy 数组
x = np.array([[1, 2], [3, 4]])
print(f'numpy:\n{x}\n')
# [[1 2]
# [3 4]]
# 将 numpy 数组覆盖成 torch tensor (张量)
x_tensor1 = torch.from_numpy(x) # from_numpy()
print(x_tensor1)
x_tensor2 = torch.tensor(x) # tensor()
print(x_tensor2)
# tensor([[1, 2],
# [3, 4]])
# 将 tensor 变成 numpy 数组
x_numpy1 = x_tensor1.numpy()
print(f'numpy:\n{x_numpy1}\n')
x_numpy2 = np.array(x_tensor1)
print(f'numpy:\n{x_numpy2}\n')
# [[1 2]
# [3 4]]
4. Input pipline (流水线)
接下来就是一个输入的常用 pipline
# 下载 CIFAR10 数据集(十分类的图像数据集)
train_dataset = torchvision.datasets.CIFAR10(root = './data/', # 数据保存地址
train = True, # 是否是训练集
transform = transforms.ToTensor(), # 把图片数据转换成 tensor
download = True) # 是否下载
# 打印一组数据(数据 & 标签)
img, label = train_dataset[0]
print(img.size())
print(label)
"""
Using downloaded and verified file: ./data/cifar-10-python.tar.gz
Extracting ./data/cifar-10-python.tar.gz to ./data/
torch.Size([3, 32, 32])
6
"""
图像被 transforms.ToTensor()
转换成了是一个 [3, 32, 32]
的张量。
这一步得到了一个可生成标准的(特征-标签
)格式的 Dataset
.
接下来就是要得到一个把这些数据一批一批的输入给模型的 Dataloader
:
# dataloader (非常简洁的方式提供数据队列和线程)
train_loader = DataLoader(dataset = train_dataset, # 数据(dataset)
batch_size = 64, # 批大小
shuffle = True) # 是否打乱
# 使用 dataloader
for images, labels in train_loader:
# 训练过程
pass
以上就是一个训练的流程。
5. 自定义数据集 Input pipline
上面展示的是一个使用 pytorch
自带的数据集的例子,如果是我们自己的数据,那么需要去重写 Dataset
类中的一些方法:
# 构建自定义数据集 (重写 Dataset 类)
class MyDataset(Dataset):
def __init__(self):
super().__init__()
# TODO
# 1. 初始化文件地址、数据的列表
pass
def __getitem__(self, index):
# TODO
# 1. 读取一对数据
# 2. 数据处理
# 2. 返回一对数据(例如:数据、标签)
pass
def __len__(self):
# TODO
# 1. 返回数据大小
# return len(data)
pass
6. 保存、载入模型
下面是三种保存方式:
后两种保存的是参数,因此载入前需要先构造模型结构。
resnet = torchvision.models.resnet18(weights = True) # 直接使用了个预训练模型
# 1. 保存、载入整个模型 (训练完成)
torch.save(resnet, './model/resnet18.pt')
model = torch.load('./model/resnet18.pt')
# 这种方法会保存整个模型相关的数据,模型结构、参数之类的全部信息
# 但是弊端是:
# 1. 占用空间大
# 2. 不利于后期调整,调整模型结构,文件目录等都可能会报错,复用性不高
# 3. 不同版本的 pytorch 中,有可能会因依赖问题报错
# (因此推荐保存参数)⬇
# 2. 保存、载入参数 (训练完成)
torch.save(model.state_dict(), './model/resnet18_state_dict.pt')
model = torchvision.models.resnet18() # 定义模型架构
model.load_state_dict(torch.load('./model/resnet18_state_dict.pt'))
# 3. 保存、载入参数 (没训练完,需要继续训练)
checkpoint = {
# 'epoch': epoch
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss
}
torch.save(checkpoint, './model/resnet_check_point.tar')
checkpoint = torch.load('./model/resnet_check_point.tar')
model = torchvision.models.resnet18() # 定义模型架构
model.load_state_dict(checkpoint['model_state_dict']) # 载入模型参数
epoch = checkpoint['epoch'] # 载入 epoch
optimizer = torch.optim.SGD() # 定义优化器结构
optimizer.load_state_dict(checkpoint['optimizer_state_dict']) # 载入优化器参数