【ML】PyTorch入门

最近终于跑通了第一个模型,在这里浅记一下PyTorch的入门学习记录。
首先声明,因为我是做NLP的,但是第一个项目实践是关于图像的,所以在图像数据处理这里不做过多声明,主要谈网络怎么搭建,模型怎么跑,怎么保存和怎么用。

1 环境配置

必须要安装的:(安装视频参考:PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】)

  • anaconda
    管理python包和环境的一个工具,有了anaconda就可以实现多版本python并存、切换以及各种第三方包安装问题。
  • pytorch
    一个深度学习框架,很多东西都写好了,难以理解的公式和理论一个函数就可以解决。
  • pycharm
    写python代码的IDE

2 准备数据集(DataSet)

自己在构建数据集时,应将数据构造成(数据,标签)的形式。构建自己数据集的方法可以参考:【ML】数据集的构建。
在本次实验中,我们的数据集选用CIFAR10,准备数据集的代码如下:

import torchvision
# DataSet 准备数据集
train_data = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)     # debug此处可以查看train_data的分类

train_data_size = len(train_data)
test_data_size = len(test_data)
print("the length of train_data:{}".format(train_data_size))
print("the length of test_data:{}".format(test_data_size))

其中,transforms.ToTensor()是将数据集转换成Tensor形式。

3 加载数据集(DataLoader)

batch_size就是一次拿多少数据的意思,等于1就代表每次从数据集中取一个,具体大小自己根据情况设置。

from torch.utils.data import DataLoader
# DataLoader 加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

4 创建网络模型

不同的数据有不同的训练模型,我们选取的CIFAR10已经有了较为成熟的网络结构了,如下图。

【ML】PyTorch入门_第1张图片
在创建网络模型的时候,我们只需要将网络结构转换成对应的函数,像这个处理过程就是:
卷积层、(最大)池化层、卷积层、(最大)池化层、卷积层、(最大)池化层、Flatten、全连接层。
对应的函数是:

Conv2d()
MaxPool2d()
Flatten()
Linear()

关于函数的参数,我们需要查阅相关文档,见pytorch官网:torch.nn(只要是和pytorch有关的都可以读官方文献)。以Conv2d()为例:
【ML】PyTorch入门_第2张图片
可以看到前三个参数是要自定义的,在网络结构图中已经给出了。
标蓝色的两个参数:

  • stride
    卷积核(Convolution kernel)每次移动几个单位
  • padding
    四个边缘需要补多少行或列
    这两个参数具体应该给多少需要参考官方给出的公式:
    【ML】PyTorch入门_第3张图片

从公式可以看出,stride[0] = 1, padding[0] = 2是最符合条件的一组参数。(越小越好)
那么我们就可以写出第一个卷积层的代码:

Conv2d(3, 32, 5, padding=2)		# stride default = 1

其余同理,将网络结构图转换成下面的代码。这段代码一般都单独放在一个文件中,我的文件命名为Tudui_model

import torch
from torch import nn


class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()

        self.model1 = nn.Sequential(
            nn.Conv2d(3, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(1024, 64),		
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x

在主程序中,我们只需要以下代码就可以进行调用:

tudui = Tudui()

5 定义训练参数

# 设置训练网络的参数
total_train_step = 0    # 记录训练次数
total_test_step = 0     # 记录测试次数
# 1 epoch = see all the batches once
epoch = 10              # 训练次数 

epoch就是我们常说的轮次。注意:epochupdate。比如:N = 10000, batch_size = 10, 则 1 epoch = 1e3 updates

6 定义损失函数和优化器

# loss
loss_fn = nn.CrossEntropyLoss()

# Optimizer
learning_rate = 1e-2    # 10^(-2)
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)       # 随机梯度下降

7 开始训练

只要理解了训练的步骤就可以理解下面的代码:
dataloader里取出(数据,标签)、求损失函数loss、反向传播backward、参数调优。(这里看不懂的去补理论)
在损失函数和反向传播之间,需要加入:

optimizer.zero_grad()

进行梯度清零。(因为上一次的梯度对本次梯度更新没有用)
下面是代码:

# 训练步骤
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        optimizer.zero_grad()   # 梯度清零(上一次的梯度对本次梯度更新没有用)
        loss.backward()     # 得到每一个可调节参数的梯度
        optimizer.step()    # 对每个参数调优

		total_train_step = total_train_step + 1
		

8 进行测试

测试集并不参与训练,所以在测试的时候就不需要反向传播和参数调优了,别忘了加上:

with torch.no_grad():

下面是测试部分的代码:

    # 测试步骤
    tudui.eval()
    total_test_loss = 0
    total_accuracy = 0      # 整体正确率
    with torch.no_grad():   # 不希望测试时计算梯度,即:在该过程中不使用优化器更改参数,只使用训练好的模型
        for data in test_dataloader:
            imgs, targets = data
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)    # 局部loss
            # 整体loss
            total_test_loss = total_test_loss + loss.item()

            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的loss:{}".format(total_test_loss))
    print("整体测试集上正确率:{}".format(total_accuracy/test_data_size))
    
    total_test_step = total_test_step + 1

9 保存模型

训练集和测试集跑完一轮后,就可以保存模型了:

torch.save(tudui, "tudui_{}.pth".format(i))
print("model saved")

但是跑一轮肯定是不够的,所以在训练和测试集外面应该加上大循环:

for i in range(epoch):
    print("-----第 {} 轮训练开始-----".format(i+1))
    ```训练步骤```
    ```测试步骤```
    ```保存```

10 GPU训练

GPU训练的速度一定是会快上许多的。
GPU训练只需要改三个地方:

  • 网络模型
  • 数据(输入,标注)
  • 损失函数

使用GPU有两种方法:

  • 在每一个后面加上.cuda()
  • 在每一个后面加上.to(device),其中:
# device = torch.device("cpu")
# device = torch.device("cuda")
# device = torch.device("cuda:0")

以第二种方法为例,网络模型、数据、损失函数分别如下:

# 定义训练的设备
device = torch.device("cuda")    # cpu or cuda
tudui = Tudui()
tudui = tudui.to(device)    # 也可以省去赋值

【ML】PyTorch入门_第4张图片

# loss
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)

11 模型训练代码汇总

11.1 Tudui_model.py

import torch
from torch import nn


class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()

        self.model1 = nn.Sequential(
            nn.Conv2d(3, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(1024, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x

11.2 train_gpu_2.py

import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

from Tudui_model import *

# 哪些可以用gpu:网络模型、数据(输入,标注)、损失函数
# 怎样使用gpu:.to(device)
# device = torch.device("cpu")
# device = torch.device("cuda")
# device = torch.device("cuda:0")

# 定义训练的设备
device = torch.device("cuda")    # cpu or cuda

# DataSet 准备数据集
train_data = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)     # debug此处可以查看train_data的分类

train_data_size = len(train_data)
test_data_size = len(test_data)
print("the length of train_data:{}".format(train_data_size))
print("the length of test_data:{}".format(test_data_size))

# DataLoader 加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)


# 创建网络模型
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()

        self.model1 = nn.Sequential(
            nn.Conv2d(3, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(1024, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x


tudui = Tudui()
tudui = tudui.to(device)    # 也可以省去赋值

# loss
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)

# Optimizer
learning_rate = 1e-2    # 10^(-2)
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

# 设置训练网络的参数
total_train_step = 0    # 记录训练次数
total_test_step = 0     # 记录测试次数
# 1 epoch = see all the batches once
epoch = 10              # 训练次数 epoch ≠ update.eg:N = 1000, batch_size = 10, 则 1 epoch = 1e3 updates

# 添加tensorboard
writer = SummaryWriter("logs")      # tensorboard --logdir = logs

for i in range(epoch):
    print("-----第 {} 轮训练开始-----".format(i+1))

    # 训练步骤
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        optimizer.zero_grad()   # 梯度清零
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        # 为方便显示,每100次记录一次
        if total_train_step % 100 == 0:
            print("训练次数:{}, loss = {}".format(total_train_step, loss.item()))  # item():tensor -> int
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤
    tudui.eval()
    total_test_loss = 0
    total_accuracy = 0      # 整体正确率
    with torch.no_grad():   # 不希望测试时计算梯度,即:在该过程中不使用优化器更改参数,只使用训练好的模型
        for data in test_dataloader:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)    # 局部loss
            # 整体loss
            total_test_loss = total_test_loss + loss.item()

            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的loss:{}".format(total_test_loss))
    print("整体测试集上正确率:{}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(tudui, "tudui_{}_gpu2.pth".format(i))
    print("model saved")

writer.close()

我们训练的模型保存结果如下图:(当然是越往后训练的越好了,懒得再训练截图了hh)
【ML】PyTorch入门_第5张图片

12 调用模型

我们在保存模型后,就可以用模型进行图片的分类了。
观察网络模型图,最终给出的outputs是10,代表会输出对10种类型的预测概率,我们可以调用argmax(1),来输出概率最大的那个分类。

import torch
import torchvision
from PIL import Image
from torch import nn

# image_path = "dog.png"
image_path = "airplane.png"
image = Image.open(image_path)
# print(image)
image = image.convert('RGB')    # 适应png,jpg各种格式的图片
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)),
                                            torchvision.transforms.ToTensor()])
image = transform(image)
# print(image.shape)


class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()

        self.model1 = nn.Sequential(
            nn.Conv2d(3, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, padding=2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(1024, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x


# cpu训练
# model = torch.load("tudui_0.pth")
# print(model)

# gpu训练
model = torch.load("tudui_9_gpu.pth", map_location=torch.device('cpu'))     # gpu训练的东西在cpu上运行应该映射(map)到cpu设备

image = torch.reshape(image, (1, 3, 32, 32))
model.eval()
with torch.no_grad():
    output = model(image)
# print(output)
print(output.argmax(1))     # tensor([6])/tensor([5])

最核心的还是这段代码:

model.eval()
with torch.no_grad():
    output = model(image)
print(output.argmax(1))

最后的结果是,训练次数少,正确率低的时候给不出正确结果,反之可以。

13 总结

真正试一次才知道什么是训练模型,以及训练模型的核心到底在哪(搭建网络并转换成代码)。之前我以为NLP的特点在跑模型,后来发现我错了,NLP的特点应该是前置工作,也就是dataset的构建。这次的数据集是图像,用transforms可以解决,那以后输入如果是文本呢,怎样对文本进行处理,然后输入到网络中呢?这类问题或许是NLP的特色所在吧。(小白瞎说的hhh)

14 参考

[1] b站up主:我是土堆
[2] pytorch官网

你可能感兴趣的:(ML,pytorch,python,深度学习,机器学习,卷积神经网络)