李宏毅机器学习课程笔记——pytorch简明教程

853 阅读6分钟

pytorch简明教程

官网文档,没有梯子的小伙伴浏览官网可能比较慢。

基本实验流程

learn-step.png

深度学习的实验流程如上图,顺序按照红色标号,其中第五步Training(训练)时会周期性的进行第六步Validation(验证),直到满足训练结束条件,到第七步Testing(测试)来测试训练出来的模型的最终效果。

而pytorch等深度学习框架主要可以帮我们做的是第1-4步,数据集管理与加载、定义神经网络(定义、保存、加载等)、定义损失函数、选择优化器(计算梯度、更新参数等)。在第五步训练中,我们可以通过定义好的网络模型、损失函数、优化器与准备好的数据,简单的使用框架计算网络结果、损失函数值、根据损失函数反向传播更新网络参数,实现模型训练。

主要组件

Tensor 张量

Tensor:张量,也就是多维向量,可以存整形和浮点型

  • shape:

    一个n维向量,(sns_{n}, sn1s_{n-1}, ... , s1s_{1}),n表示张量的维度,sis_{i}表示第i维的数量,高维(逻辑上的最高维度)在前。如一个shape为 (4, 5, 3) 的张量,从高维到低维进行说明,高(三维)有4层,每层(二维)有5条标准单位宽度的线,每条线(一维)长度为3。

    注意,这里逻辑上最高维的 dim = 0,是按照数组的下标表示的。比如下面的例子中,dim=0的维度的长度为4,dim=2的维度的长度为3。

    tensor-shape示例.png

  • pytorch创建tensor方法

    tensor创建方法.png

    • torch.randn:创建指定shape的随机值tensor
  • tensor常见运算

    1. squeeze / unsqueeze

      squeeze可消除某长度为1的维度,是一种等价变换。如 (1, 2, 3),可消除其 dim=0的维度,变为 (2, 3)。

      tensor-squeeze.png

      unsqueeze与squeeze相反,是在某处添加一个长度为1的维度,也是等价变换。

      tensor-unsqueeze.png

    2. transpose

      张量转置,类似矩阵的转置,但数据的相对位置不变。如 (1, 2, 3) -> (3, 2, 1)。

    3. cat

      按顺序拼接shape只有一个维度不同的多个tensor为一个tensor

      tensor-cat.png

调用GPU资源

  • tensor或module默认使用CPU进行计算,我们可以修改使用的硬件资源,如:x.to('cpu') 或者 x.to('cuda')

  • 检查cuda

    torch.cuda.is_available()
    
  • 多个GPU资源

    指定 cuda:0、cuda:1等

计算梯度

pytorch-计算梯度.png

处理数据集

我们需要继承pytorch的数据集处理类来实现自定义的数据集加载,基本框架如下:

from torch.utils.data import Dataset, DataLoader

class MyDataset(Dataset):
	def __init__(self, file):
        # 可做一些数据预加载、预处理的事情
        self.data = file.read()
        
    def __getitem__(self, index):
        # 根据index读取对应的一条数据
        return self.data[index]
    
    def __len__(self):
        # 返回数据总数
        return len(self.data)

自定义了数据集,我们就可以通过下面的方法使用数据集:

dataset = MyDataset(file)
# shuffle设置是否打乱数据集,一般trainning为True,Testing为False
dataloader = DataLoader(dataset, batch_size, shuffle=True)

数据集的加载过程如下图:

pytorch-数据集加载过程.png

最后,dataLoader会根据我们设置的batch_size将多条数据组成一组,进行统一运算,所以每条数据的真实结果(lable,标签)也会被组成一个向量进行统一处理。

定义网络

一个神经网络由多个不同种类的网络层组成,最基本的网络层为全连接层,也就是上面我们讲到的用激活函数多层拟合的网络,定义方法如下:

import torch.nn as nn

# Linear Layer,全连接层
nn.Linear(in_features, out_features)

全连接层示意图:

全连接层示意图2.png

上面是我们之前学到的网络状的拟合函数(神经网络)的描述,把它用神经网络单层(包含输入维度和输出维度)描述如下:

全连接层示意图1.png

接下来,我们定义一个完整的网络:

import torch.nn as nn

class MyModel(nn.Module):
    def __init(self):
        super(MyModel, self).__init__()
        # 网络结构:全连接层(10, 32) + sigmoid + 全连接层(32, 1),输入维度为10,输出维度为1
        self.net = nn.Sequential(
            nn.Linear(10, 32),
            nn.Sigmoid(),
            nn.Linear(32, 1)
        )
     
    def forward(self, x):
        return self.net(x)

激活函数

import torch.nn as nn

nn.Sigmoid()
nn.ReLU()

定义损失函数

import torch.nn as nn

// 平均方差
nn.MSELoss()
// 交叉熵,多用于分类问题
nn.CrossEntropyLoss()

优化器

torch.optim.SGD(params, lr, momentum = 0)
# params = model.parameters()
# lr,学习率

训练/验证/测试步骤

# 创建数据集对象
dataset = MyDataset(file)
# 创建数据加载对象
tr_set = DataLoader(dataset, 16, shuffle=True)
# 定义模型并声明运算使用的硬件位置
model = MyModel().to(device)
# 定义损失函数
criterion = nn.MSELoss()
# 定义优化器
optimizer = torch.optim.SGD(model.parameters(), 0.1)


# 训练模型 train
def train():
    # 训练 n_epochs 轮
    for epoch in range(n_epochs):
        # 进入训练模式,这样才能更新参数
        model.train()
        # 每次取出 batch_size 组的数据,每组数据n维,一整个batch会在n+1维被合并为一个tensor统一训练
        for x, y in tr_set:
            # 初始化,将优化器里的gradient设成0
            optimizer.zero_grad()
            # 将数据移到目标运算设备上
            x, y = x.to(device), y.to(device)
            # 前向传播,根据feature x计算网络预测的结果值pred
            pred = model(x)
            # 计算预测结果的损失函数值
            loss = criterion(pred, y)
            # 反向传播,计算损失函数的梯度
            loss.backward()
            # 使用计算的梯度更新model的参数
            optimizer.step()
   

# 验证模型 validation
def validation():
    # 将模型设置为验证模式
    model.eval()
    total_loss = 0
    # 从数据集中取出一个 batch 的数据
    for x, y in dv_set:
        # 将数据移到目标运算设备上
        x, y = x.to(device), y.to(device)
        # 设置一个scope,该scope内不计算梯度,会提升计算速度
        with torch.no_grad():
            # 前向传播,根据feature x计算网络预测的结果值pred
            pred = model(x)
            # 计算预测结果的损失函数值
            loss = criterion(pred, y)
        # 累计总loss
        total_loss += loss.cpu().item() * len(x)
    # 计算loss平均值,根据该值确定模型是否更优,是否要保存该模型
    avg_loss = total_loss / len(dv_set.dataset)


# 最终测试模型 testing
def testing():
    # 将模型设置为验证模式
    model.eval()
    preds = []
    # 从数据集中取出一个 batch 的数据
    for x in tt_set:
        # 将数据移到目标运算设备上
        x = x.to(device)
        # 设置一个scope,该scope内不计算梯度,会提升计算速度
        with torch.no_grad():
            # 前向传播,根据feature x计算网络预测的结果值pred
            pred = model(x)
            # 保存预测结果
            pred.append(pred.cpu())
        
        
# 存储模型
def save_model(path):
    torch.save(model.state_dict(), path)
    
# 加载模型
def load_model(path):
    ckpt = torch.load(path)
    model.load_state_dict(ckpt)

常见问题

  • 内存溢出 / 显卡内存移除

    如 cuda 内存溢出:

    CUDA out of memory. Tried to allocate 350.00 MiB (GPU O: 14.76 GiB total capacity; 11.94 GiB already allocated: 123.75 MiB free: 13.71 GiB reserved in total by PyTorch)
    

    在加载图片的时候,由于batch_size过大,可能会超出内存或者显卡内存的大小,导致数据无法一次性加载到内存而导致内存溢出,可以适当修改batch_size或者减小图片分辨率等方法来解决。

  • tensor的类型或者shape不匹配

    先检查这样的转换是否符合预期,若符合预期,shape不匹配可以变换tensor形状,类型不匹配可以调整某个数据的类型或者显式转换。

其他功能

  • 处理语音(speech/audio processing):torchaudio
  • 处理NLP(natural language processing):torchtext
  • 处理图像(computer vision):torchvision
  • pytorch的补充 scikit-learn:skorch