其他分享
首页 > 其他分享> > PyTorch实践模型训练(Torchvision)

PyTorch实践模型训练(Torchvision)

作者:互联网

模型训练的开发过程可以看作是一套完整的生产流程,这些环节包括: 数据读取、网络设计、优化方法与损失函数的选择以及一些辅助的工具等,TorchVision是一个和PyTorch配合使用的Python包,包含很多图像处理工具

PyTorch中的数据读取

模型训练开始的第一步就是数据读取,PyTorch提供了十分方便的数据读取机制,使用Dataset类与DataLoader的组合来得到数据迭代器。在训练或预测时,数据迭代器能够输出每一批次所需的数据,并且对数据进行相应的预处理与数据增强操作。

Dataset类

这是PyTorch中的一个抽象类,可以用来表示数据集,通过集成Dataset类来自定义数据集的格式、大小和其他属性,后面就可以供DataLoader类直接使用,这就表示,无论使用自定义的数据集还是官方封装好的数据集,其本质都是继承了Dataset类,而在继承Dataset类时,至少要重写以下几个方式:

__init__(): 构造函数,可自定义数据读取方法以及进行数据预处理

__len__(): 返回数据集大小

__getitem()__: 索引数据集中的某一个数据

示例:

import torch
from torch.utils.data import Dataset


class MyDataset(Dataset):
    # 构造函数
    def __init__(self, data_tensor, target_tensor):
        self.data_tensor = data_tensor
        self.target_tensor = target_tensor

    # 返回数据集大小
    def __len__(self):
        return self.data_tensor.size(0)

    # 返回索引的数据与标签
    def __getitem__(self, index):
        return self.data_tensor[index], self.target_tensor[index]

定义了一个名字为MyDataset的数据集,在构造函数中传入Tensor类型的数据与标签,在__len__函数中,直接返回Tensor大小,在__getitem__函数中返回索引的根据与标签

演示如何调用刚才定义的数据集,首先随机生成一个10×3维的数据Tensor,然后生成10维的标签Tensor,与数据Tensor相对应,利用这两个Tensor,生成一个MyDataset的对象。查看数据集大小可以直接使用len函数,索引调用数据可以直接使用下标

示例:

def main():
    # 生成数据
    data_tensor = torch.randn(10, 3)
    target_tensor = torch.randint(2, (10,))  # 标签是0或1

    # 将数据封装成Dataset
    my_dataset = MyDataset(data_tensor, target_tensor)

    # 查看数据集大小
    print("Dataset size:", len(my_dataset))
    # 使用索引调用数据
    print("tensor_data[0]: ", my_dataset[0])  # 调用了getitem


main()

输出:

Dataset size: 10
tensor_data[0]:  (tensor([ 0.1971,  1.2201, -0.3658]), tensor(0))

Dataloader类

在训练过程中可能不能一次性将所有数据全部加载到内存中,也不能只用一个进程去加载,所以就需要多进程、迭代加载,而Dataloader就是基于这些被设计出来,Dataloader是一个迭代器,最基本的使用方法就是传入一个Dataset对象,它会根据参数batch_size的值生成一个batch的数据,节省内存的同时还可实现多进程、数据打乱等处理

调用方式:

def main():
    # 生成数据
    data_tensor = torch.randn(10, 3)
    target_tensor = torch.randint(2, (10,))  # 标签是0或1

    # 将数据封装成Dataset
    my_dataset = MyDataset(data_tensor, target_tensor)
    tensor_dataloader = DataLoader(dataset=my_dataset, batch_size=2, shuffle=True, num_workers=0)
    for data, target in tensor_dataloader:
        print(data, target)
    print('One batch tensor data: ', iter(tensor_dataloader).next())


main()

输出:

tensor([[ 1.3702, -1.2140, -1.4516],
        [-0.3089, -1.0537,  0.9600]]) tensor([0, 1])
tensor([[-1.1132,  0.2558, -2.8537],
        [ 2.3735,  0.1748, -0.8713]]) tensor([0, 0])
tensor([[-1.6268,  1.5028,  0.8430],
        [ 0.7738,  0.5075,  0.4091]]) tensor([0, 1])
tensor([[ 0.3776,  1.7895,  0.0658],
        [ 0.3342,  0.1680, -0.6025]]) tensor([0, 0])
tensor([[-0.2299, -1.1589, -0.8485],
        [-1.3314, -1.5933,  2.0586]]) tensor([0, 0])
One batch tensor data:  [tensor([[ 0.3342,  0.1680, -0.6025],
        [ 1.3702, -1.2140, -1.4516]]), tensor([0, 0])]

结合代码,如下几个参数分别表示:

dataset: Dataset类型,输入的数据集,必须参数

batch_size: int类型,每个batch有多少个样本

shuffle: bool类型,在每个epoch开始的时候,是否数据进行重新打乱

num_workers: int类型,加载数据的进程数,0意味着所有的数据都会被加载进主进程

Torchvision

PyTorch如果要读取这些数据集,利用Torchvision即可,它提供了一些常用数据集以及已经搭建好的经典网络模型,并集成了一些图像数据处理方面的工具,该库就是常用数据集+常见网络模型+常用图像处理方法

读取数据

torchvision的datasets包中提供了丰富的图像数据集的接口,参考文档: https://pytorch.org/vision/stable/datasets.html,这个包本身并不包含数据集文件本身,其工作方式是先从网络上把数据集下载到用户指定目录,然后用它的加载器加载到内存中。最后将这个加载后的数据集作为对象返回给用户

介绍一下MNIST数据集,下面用这套数据来进行演示,下载地址: http://yann.lecun.com/exdb/mnist/,包含4个utype格式存储的文件

torchvision.datasets有一个MNIST的接口,封装了从下载、解压缩、读取数据和解析数据等全部过程,可以直接使用:

import torchvision

mnist_dataset = torchvision.datasets.MNIST(root='./data',
                                           train=True,
                                           transform=None,
                                           target_transform=None,
                                           download=True)

参数解释:

root是一个字符串,用于指定想要保存MNIST数据集的位置,如果download是False,则会从目标位置读取数据集

download是布尔类型,表示是否下载数据集,如果为True,就会从网上自动下载这个数据集,存储到root指定的位置,如果已经存在数据集文件,则不会重复下载

train是布尔类型,表示是否加载训练数据集,如果为True,则只加载训练数据,如果为False,则只加载测试数据集(并非所有的数据集都做了训练集和测试集的划分)

transfrom用于对图像进行预处理操作,例如数据增强、归一化、旋转或缩放

target_transform用于对图像标签进行预处理操作

运行这段代码后就会开启下载,最终在data目录中得到数据:

$ tree data
data
└── MNIST
    └── raw
        ├── t10k-images-idx3-ubyte
        ├── t10k-images-idx3-ubyte.gz
        ├── t10k-labels-idx1-ubyte
        ├── t10k-labels-idx1-ubyte.gz
        ├── train-images-idx3-ubyte
        ├── train-images-idx3-ubyte.gz
        ├── train-labels-idx1-ubyte
        └── train-labels-idx1-ubyte.gz

同时,得到的这个minist_dataset是Dataset类的派生类,已经自动写好对Datasets类的继承,完成了对数据集的封装

数据预览

如果想要查看mnist_dataset中的具体内容,要将其转化为列表

mnist_dataset_list = list(mnist_dataset)
print(mnist_dataset_list)

输出:

<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A730>, 1), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A760>, 6), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A790>, 8), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A7C0>, 9), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A7F0>, 7), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A820>, 8), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A850>, 6), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A880>, 1), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A8B0>, 0), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A8E0>, 8), (<PIL.Image.Image image mode=L size=28x28 at 0x7F7E76A8A910>, 8), (<PIL.Image.Image image mode=L size=28x28 ......

从运行结果可见,转换后的数据集对象变成了一个元组列表,每个元组有两个元素,第一个元素是图像数据,第二个元素是图像的标签

这里图像数据是PIL.Image.Image类型的,这种类型可以在Jupyter中显示出来,显示一条数据:

数据预处理

仅仅将数据集中的图片数据读取出来还不够,神经网络模型接收的数据类型是Tensor而非PIL对象,因此还要对数据进行预处理操作,torchvision.transforms包中提供了常用的图像操作,包括对Tensor及PIL Image和Tensor进行变化和变换的组合

数据类型转换

将PIL.Image或Numpy.ndarray格式的数据转化为Tensor格式,要用到Transforms.ToTensor()类,反之,将Tensor或Numpy.ndarray格式的数据转化为PIL.Image格式,则使用transforms.ToPILImage(mode=None)类,这是ToTensor的逆操作,将Tensor或Numpy的数组转换为PIL.Image对象,其中,参数mode代表PIL.Image的模式,如果mode为None (默认值),则根据输入数据的维度进行推断:输入为3通道,则mode为RGB,为4则mode为RGBA,为2则mode为LA,若为单通道,mode根据输入数据的类型确定具体模式

依然拿这个图片举例:

实现数据类型的转换:

from PIL import Image
from torchvision import transforms

image = Image.open('apple.jpg')
print(type(image))

image1 = transforms.ToTensor()(image)
print(type(image1))

image2 = transforms.ToPILImage()(image1)
print(type(image2))

输出:

<class 'PIL.JpegImagePlugin.JpegImageFile'>
<class 'torch.Tensor'>
<class 'PIL.Image.Image'>

首先读取图片,其数据类型为PIL.JpegImagePlugin.JpegImageFile,要注意的是,PIL.JpegImagePlugin.JpegImageFile是PIL.Image.Image的子类,之后用ToTensor()将PIL.Image转换为Tensor,最后再将Tensor转换为PIL.Image

对PIL.Image和Tensor进行变换

Resize 尺寸调整

将PIL.Image或者Tensor尺寸调整为给定的尺寸,具体定义为:

torchvision.transforms.Resize(size, interpolation=2)

参数size表示期望输出的尺寸,如果是一个(h,w)元组,那么h表示高,w表示宽,以此来调整尺寸,如果是一个正整数,那么图像较小的边会被匹配到该整数,另一条按比例缩放

参数interpolation表示插值算法,默认为2,表示PIL.Image.BILINEAR

示例:

from PIL import Image
from torchvision import transforms

resize_image_operation = transforms.Resize((200, 200))

# 原图
original_image = Image.open("apple.jpg")
display(original_image)

# resize
image = resize_image_operation(original_image)
display(image)

演示:

首先定义了一个Resize操作,设置变换后的尺寸为(200,200),之后对这个图像进行resize变换

裁剪

裁剪有很多方式

中心裁剪,就是在中心裁剪指定的PIL Image或者Tensor,其定义如下:

torchvision.transforms.CenterCrop(size)

其中,size表示期望输出的裁剪尺寸,如果size是一个(h,w)这样的元组,则将其裁剪为高为h,宽为w的图像,如果是一个正整数,那么裁剪为(size,size)的正方形

随机裁剪,就是在一个随机位置裁剪指定的PIL Image或者Tensor,定义如下:

torchvision.transforms.RandomCrop(size,padding=None)

其中size代表期望输出的裁剪尺寸,用法与CenterCrop雷同,padding表示图像上的每个边框上的可选填充,默认值是None,表示没有填充,这个参数很少用

四角和中心裁剪,使用FiveCrop,将给定的PIL Image或Tensor分别从四角和中心进行裁剪,共裁剪为5块,定义如下:

torchvision.transforms.FiveCrop(size)

size可以是int或者tuple,用法同上

示例代码:

from PIL import Image
from torchvision import transforms

center_crop_operation = transforms.CenterCrop((60, 70))
random_crop_operation = transforms.RandomCrop((80, 80))
five_crop_operation = transforms.FiveCrop((60, 70))

# 原图
original_image = Image.open('apple.jpg')
display(original_image)

# 中心裁剪
image1 = center_crop_operation(original_image)
display(image1)
# 随机裁剪
image2 = random_crop_operation(original_image)
display(image2)
# 四角和中心裁剪
images = five_crop_operation(original_image)
for image in images:
    display(image)

输出:

翻转

翻转有两种操作

以概率p随机水平翻转图像:

torchvision.transforms.RandomHorizontalFlip(p=0.5)

以概率p随机垂直翻转图像:

torchvision.transforms.RandomVerticalFlip(p=0.5)

其中p表示随机翻转的概率值,默认为0.5

示例代码:

from IPython.core.display_functions import display
from PIL import Image
from torchvision import transforms

# 定义翻转操作
h_flip_operation = transforms.RandomHorizontalFlip(p=1)
v_flip_operation = transforms.RandomVerticalFlip(p=1)

# 原图
original_image = Image.open('apple.jpg')
display(original_image)

# 水平翻转
image1 = h_flip_operation(original_image)
display(image1)

# 垂直翻转
image2 = v_flip_operation(original_image)
display(image2)

输出:

只对Tensor进行变换

只针对Tensor的变换一共有4个,分别是线性变换、标准化、随机擦除和格式转换

标准化

标准化是指每个数据点所在通道的平均值,再除以所在通道的标准差,数学的计算公式:
$$
output=(input-mean)/std
$$
而对图像进行标准化,就是对图像的每个通道利用均值和标准差进行正则化,目的是保证数据集中所有图像分布都相似

函数定义:

torchvision.transforms.Normalize(mean,std,inplace=False)

其中每个参数的含义如下:

mean: 各个通道的均值

std: 各通道的标准差

inplace: 表示是否原地操作,默认为否

示例代码:

from PIL import Image
from torchvision import transforms

normal_operation = transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))

# 原图
original_image = Image.open('apple.jpg')
display(original_image)

# 图像转化为Tensor
image_tensor = transforms.ToTensor()(original_image)

# 标准化
tensor_normal = normal_operation(image_tensor)

# Tensor转化为图像
image_normal = transforms.ToPILImage()(tensor_normal)
display(image_normal)

上面代码过程首先定义了均值和标准差均为(0.5,0.5,0.5)的标准化操作,然后将原图转化为Tensor,接着Tensor进行标准化,最后再将Tensor转化为图像输出

输出:

image-20220729213215206

image-20220729213418618

组合变换

使用Compose类可以将多个变换组合到一起,定义如下:

torchvision.transforms.Compose(transforms)

其中transforms是一个Transform对象的列表,表示要组合的变换列表

例如先将图片变成200×200像素大小,并且随机裁切成80像素的正方形,可以组合Resize和RandomCrop变换,具体代码如下:

from PIL import Image
from torchvision import transforms

normal_operation = transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))

# 原图
original_image = Image.open('apple.jpg')
display(original_image)

# 定义组合操作
composed = transforms.Compose([transforms.Resize((200, 200)), transforms.RandomCrop(80)])
image = composed(original_image)
display(image)

输出:

image-20220729214659504

Compose可以结合torchvision.datasets包,在读取数据集的时候做图像变换与数据增强操作

常见网络模型

Torchvision除了封装了常用的数据集,还为提供了深度学习中各种经典的网络结构以及训练好的模型,只要直接将这些经典模型的类实例化,就可以进行训练或使用。Torchvision中的各种经典网络结构以及训练好的模型,都放在了torchvision.models中

torchvision.models

该模块中包含了常见网络模型结构的定义,这些网络模型可以解决以下四大类问题: 图像分类、图像分割、物体检测和视频分类

实例化一个GooLeNet网络

直接将一个网络模型类实例化,就可以得到一个网络模型。使用随机初始化的权重,创建一个GoogleNet模型:

import torchvision.models as models

google_net = models.googlenet(pretrained=True)

实例化时,引入了一个参数pretrained,指定为True即可得到预训练好的模型,torchvision.models模块都已经封装好了,models中所有预训练好的模型,都是在ImageNet数据集上训练的,都是由PyTorch的torch.utils.model_zoo模块提供,并且可以通过参数pretrained =True来构造这些模型

模型微调

是在一个比较通用、宽泛的数据集上进行大量训练得出了一套参数,然后再使用这套预训练好的网络和参数,在自己的任务和数据集上进行训练。使用经过预训练的模型要比使用随机初始化的效果更好

import torch
import torchvision.models as models

# 加载预训练模型
google_net = models.googlenet(pretrained=True)

# 提取分类层
fc_in_features = google_net.fc.in_features
print("fc_in_features:", fc_in_features)

# 查看分类层的输出参数
fc_out_features = google_net.fc.out_features
print("fc_out_features:", fc_out_features)

# 修改预训练模型的输出分类数
google_net.fc = torch.nn.Linear(fc_in_features, 10)

首先加载预训练模型,然后提取预训练模型的分类层固定参数,最后修改预训练模型的输出分类数为10,根据输出结果,可见预训练模型的原始输出分类数是1000

Torchvision其他常用函数

torchvision还有两个常用函数: make_grid和save_img

make_grid

作用是将若干幅图像拼成在一个网格中

定义:

torchvision.utils.make_grid(tensor,nrow=8,padding=2)

参数的含义:

tensor: 类型是Tensor或列表,如果输入类型是Tensor,其形状应是(B×C×H×W),如果输入类型是列表,列表中元素应为相同大小的图片

nrow: 表示一行放入的图片数量,默认为8

padding: 子图像与子图像之间的边框宽度,默认为2像素

make_grid函数主要用于展示数据集或模型输出的图像结果,以MNIST数据集为例:

import torchvision
from IPython.core.display_functions import display
from torchvision import datasets
from torchvision import transforms
from torch.utils.data import DataLoader
import warnings
warnings.filterwarnings("ignore")

mnist_dataset = datasets.MNIST(root='./data',
                               train=False,
                               transform=transforms.ToTensor(),
                               target_transform=None,
                               download=True)

# 取32张图片的tensor
tensor_dataloader = DataLoader(dataset=mnist_dataset,
                               batch_size=32)
data_iteration = iter(tensor_dataloader)
image_tensor, label_tensor = data_iteration.next()
print(image_tensor.shape)

# 将32张图片拼接在一个网格中
grid_tensor = torchvision.utils.make_grid(image_tensor, nrow=8, padding=2)
grid_image = transforms.ToPILImage()(grid_tensor)
display(grid_image)

输出:

save_img

可以直接将tensor保存为图片,即使数据在CUDA上也会自动移动到CPU中进行保存。定义:

torchvision.utils.save_image(tensor,fp,**kwargs)

tensor参数的数据类型是Tensor或者列表,如果输入类型是Tensor,直接将Tensor保存,如果输入类型是列表,则先调用make_grid函数生成一张图片的Tensor,然后再保存

fp: 保存图片的文件名

**kwargs: make_grid函数中的参数

将32张图片的拼接图直接保存:

torchvision.utils.save_image(grid_tensor, 'grid.jpg')                      # 输入为一张图片的tensor,直接保存
torchvision.utils.save_image(image_tensor, 'grid2.jpg', nrow=5, padding=2) # 输入为list,调用grid_image函数后保存 nrow表示每行5个数字

输出:

image-20220730094142058

标签:tensor,image,torchvision,PyTorch,Tensor,transforms,数据,模型,Torchvision
来源: https://www.cnblogs.com/N3ptune/p/16534372.html