ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

PyTorch实践模型训练(Torchvision)

2022-07-30 10:03:57  阅读:167  来源: 互联网

标签:tensor image torchvision PyTorch Tensor transforms 数据 模型 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有