PyTorch使用笔记(附用例代码 + 详细注释)

  • 自定义数据集 —— Dataset的简单使用
  • TensorBoard 的使用
  • writer.add_image函数
  • writer.add_scalar函数使用
  • Torchvision.Transforms 的使用
  • ToTensor 格式转化
  • Normalize 标准化
  • Resize 调整大小
  • Compose 组合使用
  • RandomCrop 随机裁剪
  • DataLoader 的使用
  • 对于 tensor 维度通俗的理解
  • TORCH.NN的使用
  • 神经网络基本骨架 - nn.module
  • 神经网络卷积操作 - neural network convolution
  • 神经网络最大池化操作
  • 非线性激活 Non-linear Activations
  • 线性层 LINEAR
  • 组合 - Sequential
  • 损失函数 - lose function
  • 将损失函数用于神经网络 + 反向传播
  • optimizer - 优化器
  • Model 的使用
  • 模型调用及修改
  • 模型的保存
  • 模型的加载
  • 陷阱


自定义数据集 —— Dataset的简单使用

# 对 Dataset 类用例介绍
from torch.utils.data import Dataset
# utils为torch中常用的工具区,从其中的data区,导入与文件读写操作相关的Dataset
from PIL import Image  # 用于图片操作,也可以使用cv2
import os  # python 当中关于系统的一个库,用于获取所有图片的地址


class MyData(Dataset):  # 创建一个class继承Dataset列表

    def __init__(self, root_dir, label_dir):  # 根据类创建特例、实例的时候运行的函数。为整个class提供一些全局变量。
        self.root_dir = root_dir  # 不同函数之间,变量不互通,通过self相当于创造了全局变量
        self.label_dir = label_dir
        self.path = os.path.join(root_dir, label_dir)  # Windows 和 Linux 操作系统对于路径的解析是不一样的,使用join函数避免错误
        self.img_path = os.listdir(self.path)  # dir 即为文件夹,通过列表的方式读取文件夹中的文件。

    def __getitem__(self, idx):
        img_name = self.img_path[idx]  # 图片名称
        img_item_path = os.path.join(self.path, img_name)  # 图片相对路径
        img = Image.open(img_item_path)  # 获取图片文件
        label = self.label_dir
        return img, label

    def __len__(self):
        return len(self.img_path)  # 整个数据集的长度


root_dir = "dataset/train" 
ants_label_dir = "ants_image"
bees_label_dir = "bees_image"
ants_dataset = MyData(root_dir, ants_label_dir)  # 获取ants数据集
bees_dataset = MyData(root_dir, bees_label_dir)  # 获取bees数据集

# 可以在 Python控制台 直接调用 ants_dataset[0] ,看输出结果,输出即为 __getitem__ return的结果
img, label = ants_dataset[1]
img.show()  # 对ants的第2张图片进行展示

train_dataset = ants_dataset + bees_dataset  # 将两个数据集合在一起

注意事项:

  • dir 即为文件夹的意思
  • Ctrl + Shift + C 可以快速复制文件路径
  • Ctrl + Alt + Shift + C 可以快速复制文件路径
  • 直接给类的对象输入一个参数进行调用时,输出的即为__getitem__ 函数return的结果

TensorBoard 的使用

TensorBoard 是使数据(函数、图像)可视化的工具,在模型训练使起到很大作用。常用工具为:writer.add_image函数 与 writer.add_scalar函数

首先导入所需packages,创建 SummaryWriter实例对象

from torch.utils.tensorboard import SummaryWriter
# 从 torch 的 utils 工具箱中导入 tensorboard,然后导入 SummaryWriter这个类
import numpy as np
from PIL import Image

writer = SummaryWriter("logs")  # 创建一个类的实例,将事件文件存储到 logs 文件夹下

writer.add_image函数

用于向 TensorBoard 实例中添加处理好的图片

使用说明:

  1. 第一个参数为 title,第二个参数为 img,第三个参数为 step。
  2. 可以更改 img 与 step,从而在一个title下记录多个图像。
  3. 图片格式支持 numpy.array 与 torch.Tensor ,不支持PIL格式。
image_path = 'dataset/train/ants_image/0013035.jpg'  # 记录图片地址,此处为相对地址,根据自己的数据集地址做修改即可。

img_PIL = Image.open(image_path)  # 通过PIL的Image导入图片,格式为PIL
# 此处可以使用 print(type(img_PIL)) 来读取其格式

img_array = np.array(img_PIL)  
# 将PIL格式的文件转化为 numpy.array 格式,因为 add_image 函数不支持PIL格式。可以直接使用opencv导入图片,格式为 torch.Tensor 符合 add_image 的要求。
# img_array 的 shape 为 HWC,而阅读 add_image 函数说明,其默认支持的为CHW格式,故需要在调用函数时添加 dataformats="HWC" 参数
writer.add_image("test", img_array, 1, dataformats="HWC")

writer.add_scalar函数使用

用于向 TensorBoard 实例中添加函数。

使用说明:

  1. 第一个参数为label名称。
  2. 第二个参数对应y轴。
  3. 第三个参数对应x轴。
for i in range(100):  # 添加 y = x 
    writer.add_scalar("y = x", i, i)  

for i in range(100):  # 添加 y = 2x 
    writer.add_scalar("y = 2x", 2 * i, i)

最后,调用 TensorBoard 默认的close操作。

writer.close()

注意事项:

  • 使用技巧 - 巧用报错: 导入 SummaryWriter 的头文件较长,不方便记忆,可以在需要使用时,直接调用 SummaryWriter 函数,然后在 报错提示 中选择 导入 torch.utils.tensorboard.SummaryWriter 即可。
  • 打开绘制的图像: 在终端运行 tensorboard --logdir=logs 可以打开绘制的图像,logdir意思为 SummaryWriter 创建的类的实例的文件夹。
  • 更改端口号: 可以通过添加 --port=··· 来更改端口号(默认为6006)如 tensorboard --logdir=logs --port=6007
  • from PIL import Image 为 python 自带的图片处理类,可用于打开图片

Torchvision.Transforms 的使用

Transforms 用于对图像进行一些变换,常用工具为 ToTensorNormalizeResizeComposeRandomCrop

首先,导入所需packages,创建 SummaryWriter实例对象

from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

writer = SummaryWriter("logs")

ToTensor 格式转化

将图片格式转化为 tensor 格式

img = Image.open("dataset/train/bees_image/39672681_1302d204d1.jpg")
trans_toTensor = transforms.ToTensor()  # 创建 ToTensor 实例对象
img_tensor = trans_toTensor(img)
writer.add_image("img_tensor", img_tensor)

Normalize 标准化

对图片进行标准化处理

使用说明:

  1. 参数1为 mean (sequence) 即均值
  2. 参数2为 std (sequence) 即标准差
print(img_tensor[0][0][0])  # 打印标准化前的图片信息
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])  # 创建 Normalize 实例
img_norm = trans_norm(img_tensor)  # 对图片进行标准化
print(img_norm[0][0][0])  # 打印标准化后的图片信息
writer.add_image("img_norm", img_norm)

Resize 调整大小

调整图片尺寸大小

使用说明:

  1. 创建实例时,参数若同时输入WH,必须是一个序列;若是只输入一个值,则会等比例缩放(较短边=该值)。
  2. 新版本的PyTorch的Resize命令可以兼容 PIL 和 tensor 格式,输出与输入格式相同。
print(img.size)  # 打印 resize 前图片的尺寸信息
trans_resize = transforms.Resize((300, 250))  # 创建 Resize实例
img_resize = trans_resize(img_tensor)  # 对图片进行 Resize 处理
print(img_resize.size)  # 打印 resize 后图片的尺寸信息
writer.add_image("Resize", img_resize)  # writer.add_image 仅支持numpy array, torch tensor 格式图片

Compose 组合使用

将多个 transforms 函数组合使用

使用说明:

  1. Compose() 中的参数需要是一个列表,Compose([transforms参数1, transforms参数2,……])
  2. 且前一个参数输出的格式应该和后一个参数输入的格式相匹配
print(img.size)
trans_resize_2 = transforms.Resize(50)
trans_compose = transforms.Compose([trans_resize_2, trans_toTensor])  # 创建 Compose 实例,将 resize 和 toTensor 组合使用

img_resize_2 = trans_compose(img)
print(img_resize_2.size)
writer.add_image("Compose - Resize - 2", img_resize_2)

RandomCrop 随机裁剪

根据给定的尺寸参数,对所给图片进行随机裁剪

使用说明:

  1. 同时输入WH时,必须是一个序列,例:transforms.RandomCrop((500, 400))
  2. 若是只输入一个值,则会裁剪出一个正方形
trans_random = transforms.RandomCrop(250)  # 创建 RandomCrop 实例
for i in range(10):
    img_random = trans_random(img_tensor)  # 获取随机裁剪的图片
    writer.add_image("img_random", img_random, i)

最后,调用 TensorBoard 默认的close操作。

writer.close()
  • 关注的点: 输入和输出类型、官方文档、方法需要什么参数
  • 函数不会用: 按住 Ctrl 键,鼠标移至函数上时,可以快速打开对应说明文档。
  • 不知道返回值: print、print(type())、debug
  • class 中 def __call__() 函数作用,可以用类的对象直接调用,不用加 ‘.’

DataLoader 的使用

用于加载数据集操作

# 头文件
from torch.utils.data import DataLoader

# 加载数据集(在数据集导入之后)
test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=False, num_workers=0, drop_last=False)

参数说明:

  • dataset 表示数据集
  • batch_size 表示每次加载的图片数量
  • shuffle 表示加载完一次数据集后是否进行洗牌操作
  • num_workers 表示子进程数目,0 则只在主程序中运行(windows中不是0可能会出现"BrokenPipeError")
  • drop_last表示是否加载最后余下的不足batch_size的图片项。

对于 tensor 维度通俗的理解

第一个数值,表示在第一个[]中包括相应个数的子[]
第二个数值,表示在第一个[]的子[]中包括相应个数的[]
以此类推
例如一个 [4,3,2]的三维 tensor
首先,tensor需要一个括号,里面包含4个括号
torch = torch.tensor([ [ ], [ ], [ ], [ ] ])
再在四个括号里面,分别加上三个括号
torch = torch.tensor([ [],[],[] ],[ [],[],[] ],[ [],[],[] ],[ [],[],[] ])
然后三个括号内分别有两个数字
torch = torch.tensor([ [ [1,2],[1,3],[1,4] ],
[ [2,3],[3,4],[2,4] ],
[ [1,3],[2,4],[3,3] ],
[ [2,3],[2,4],[4,4] ] ])
print(torch.shape)
输出的结果即为[4,3,2]。

TORCH.NN的使用

神经网络基本骨架 - nn.module

官方文档说明:
CLASS torch.nn.Module[SOURCE]
Base class for all neural network modules.
Your models should also subclass this class.
所有的神经网络都必须继承于Module,Module相当于是其父类。

# 简单神经网络的搭建
import torch
from torch import nn  # nn -> neural network

class MyModule(nn.Module):
    # 可以通过 PyCharm 代码 -> 生成 -> 重写方法 直接添加 __init__ 函数
    def __init__(self):  # 调用父类的初始化函数,必须写
        super().__init__()

    def forward(self, input):  # 对输入进行操作的函数 input -> forward -> output
        output = input + 1
        return output


my_module = MyModule()
x = torch.tensor(1.0)
output = my_module(x)
print(output)

神经网络卷积操作 - neural network convolution

卷积操作,就是每次与池化核匹配时,将卷积核(权重矩阵),和输入矩阵对应位置做点乘,输出点乘后所有数值累加的结果。

torch.nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)

参数说明:

  • kernel_size 参数表示给定卷积核的大小
  • 不需要给定其具体数值,转换过程会自动判断
  • 实际上神经网络卷积操作也是通过图片的参数不断调整其卷积核的一个过程
import torch
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

dataset = torchvision.datasets.CIFAR10('./dataset', train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset, batch_size=64)


# 创建神经网络,在其中对输入的数据进行卷积操作
class MyModule(torch.nn.Module):
    def __init__(self):
        super().__init__()  # 调用父类的初始化函数
        # 生成 torch.nn.Conv2d 类的对象,用于在 forward 中调用
        self.conv1 = torch.nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)

    def forward(self, x):
        output = self.conv1(x)
        return output


mymodule = MyModule()
writer = SummaryWriter("conv2d_logs")
step = 0

for data in dataloader:
    imgs, targets = data
    # print(imgs.shape)  # torch.Size([64, 3, 32, 32]) 参数一表示 batch_size 也即批量的大小,参数二表示 channel 也即通道的数目,参数三、四表示HW。
    output = mymodule(imgs)  # 调用神经网络进行卷积化
    # print(output.shape)  # torch.Size([64, 6, 30, 30])

    writer.add_images("input", imgs, step)
    output = torch.reshape(output, [-1, 3, 30, 30])
    # writer.add_images 只能识别3通道数的图像,故对output做reshape处理,-1表示 batch_size 的值随其他参数而自动判定
    # print(output.shape)
    writer.add_images("output", output, step)

    step += 1

writer.close()

神经网络最大池化操作

池化操作,就是每次与池化核匹配时,仅输出其中最大的数值。
目的是保留输入的特征,同时把数据量减少,使训练得更快
对图片进行操作时,相当于是降低了清晰度(类似打了马赛克)

MaxPool2d(kernel_size=3, ceil_mode=True)

参数说明:

  • stride 默认等于 kernel_size 的值
  • ceil_mode 为 False 时,对尺寸不足池化核尺寸的部分不进行保留。
import torchvision.datasets
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

# 准备数据集
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
# 加载数据集
downloader = DataLoader(dataset, 64)


class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.maxPool = MaxPool2d(kernel_size=3, ceil_mode=True)

    def forward(self, x):
        output = self.maxPool(x)
        return output


maxPool = MyModule()

writer = SummaryWriter('maxPool_logs')
step = 0
for data in downloader:
    imgs, target = data
    writer.add_images("input", imgs, step)
    output = maxPool(imgs)
    writer.add_images("output", output, step)
    step += 1

writer.close()

非线性激活 Non-linear Activations

目的在网络中引入一些非线性特征,非线性越多,有利于训练出符合各种曲线、各种特征的模型,提高模型的泛化能力。

ReLU() 
Sigmoid()

参数说明:

  • 参数 inplace 设置为true的话,计算结果会直接替换input的值。
import torch
import torchvision.datasets
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

input = torch.tensor([[2, -1.5],
                      [-3.5, 3]])

dataset = torchvision.datasets.CIFAR10("./dataset", train=False,
                                       transform=torchvision.transforms.ToTensor, download=True)
dataloader = DataLoader(dataset, batch_size=64)


class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        # torch.nn.ReLU 的使用
        self.relu = ReLU() 
        # torch.nn.Sigmoid 的使用
        self.sigmoid = Sigmoid()

    def forward(self, x):
        output = self.sigmoid(x)
        return output


mymodule = MyModule()

writer = SummaryWriter("./non_linear_act")
step = 0
for data in dataloader:
    imgs, target = data
    writer.add_images("input", imgs, step)
    output = mymodule(imgs)
    writer.add_images("output", output, step)
    step += 1

writer.close()

线性层 LINEAR

用于更改 tensor 的线性长度

CLASS torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("dataset", train=False,
                                       transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)


class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.linear = nn.Linear(196608, 30)

    def forward(self, input):
        output = self.linear(input)
        return output


mymodule = MyModule()

for data in dataloader:
    imgs, target = data
    print(imgs.shape)
    output = torch.flatten(imgs)  # 对 torch 线性处理,使用 torch.reshape 同样可以实现
    print(output.shape)
    output = mymodule(output)
    print(output.shape)

组合 - Sequential

组合神经网络中的函数操作

Sequential()

参数说明:
在参数中按顺序写入需要操作的函数即可

import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter


class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

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


mymodule = MyModule()
print(mymodule)
input = torch.ones((64, 3, 32, 32))
output = mymodule(input)
print(output.shape)

writer = SummaryWriter("seq_logs")
writer.add_graph(mymodule, input)  # 通用 tensorboard 绘制神经网络流程图
writer.close()

损失函数 - lose function

作用:
1.计算实际输出和目标之间的差距
2.为我们更新输出提供一定的依据(反向传播)

torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
# 将所有位置数值差的绝对值求和后取平均

torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
# 将所有位置数值差的平方求和后取平均

torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction='mean', label_smoothing=0.0)

参数说明:

  • L1Loss 函数要求输入的tensor数据类型应该是浮点数,实际操作中大多已经是浮点数了。
  • 设置 reduction=‘sum’ 后将不再取平均值,直接返回求和结果,默认返回的为均值。
import torch
from torch.nn import L1Loss
from torch import nn

inputs = torch.tensor([1, 2, 3], dtype=torch.float32) 
targets = torch.tensor([1, 2, 5], dtype=torch.float32)


loss = L1Loss(reduction='sum') 
result = loss(inputs, targets)


loss_mse = nn.MSELoss() 
result_mse = loss_mse(inputs, targets)

print(result)
print(result_mse)


x = torch.tensor([0.1, 0.2, 0.3])
y = torch.tensor([1])
x = torch.reshape(x, (1, 3))
loss_cross = nn.CrossEntropyLoss()
result_cross = loss_cross(x, y)
print(result_cross)

将损失函数用于神经网络 + 反向传播

import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)

dataloader = DataLoader(dataset, batch_size=1)


class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

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


loss = nn.CrossEntropyLoss()
mymodule = MyModule()
for data in dataloader:
    imgs, targets = data
    outputs = mymodule(imgs)
    result_loss = loss(outputs, targets)
    result_loss.backward()  # 反向传播,用于对神经网络中 grad 的更新,便于后续的优化处理
    print("over")

注意:
若 transform=torchvision.transforms.ToTensor() 忘加括号,会报TypeError 如下
TypeError: init() takes 1 positional argument but 2 were given

optimizer - 优化器

TORCH.OPTIM 用于对参数的优化

import torchvision
from torch import nn, optim
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("dataset", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)

dataloader = DataLoader(dataset, batch_size=1)


class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

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


loss = nn.CrossEntropyLoss()
mymodule = MyModule()
optimizer = optim.SGD(mymodule.parameters(), lr=0.01)

for epoch in range(20):  # 扫描全部数据集,重复进行 20 次优化
    running_loss = 0.0
    for data in dataloader:
        imgs, targets = data
        outputs = mymodule(imgs)
        result_loss = loss(outputs, targets)
        optimizer.zero_grad()  # 将前一次计算的梯度清零
        result_loss.backward()  # 反向传播,对神经网络中的梯度进行更新
        optimizer.step()  # 调用优化器,对每个参数进行调优
        running_loss = running_loss + result_loss  # 对每一个 batch 的 loss 值做累加
    print(running_loss)

Model 的使用

模型调用及修改

  • 调用 Pytorch 提供的 预训练或没有预训练的模型
import torchvision
from torch import nn

vgg16 = torchvision.models.vgg16()

print(vgg16)
  • 对调用的模型做修改
vgg16.classifier.add_module('add_linear', nn.Linear(1000, 10))  # 在 vgg16 的 classifier 中添加一项操作,需要给一个名称
print(vgg16)

vgg16.classifier[6] = nn.Linear(4096, 10)  # 更改 vgg16 的 classifier 中对应项的函数。
print(vgg16)

模型的保存

  • 分为两种方式,官方推荐使用方式二,所占内存更少。
import torch
import torchvision
from torch import nn

vgg16 = torchvision.models.vgg16()

# 保存方式1,模型结构+模型参数
torch.save(vgg16, "vgg16_method1.pth")

# 保存方式2,模型参数(官方推荐)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")
  • 使用方式一保存自己的模型
class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=(3, 3))

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


myModel_save = MyModule()
torch.save(myModel_save, "myModel_save.pth")

模型的加载

import torchvision

# 方式1:加载 使用方式1保存的模型
model = torch.load("mymodule.pth")
print(model)

# 方式2:加载 使用方式2保存的模型
vgg16 = torchvision.models.vgg16()
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
model = torch.load("vgg16_method2.pth")
print(vgg16)

陷阱

  • 如果是使用方式一保存的模型,在进行load的时候,需要先导入所需的 神经网络 代码。可以直接使用 from myModel_save import * 来导入,或者直接复制粘贴其 class 代码