首页 > 其他分享 >深度学习基础-pytorch1

深度学习基础-pytorch1

时间:2023-04-04 18:44:30浏览次数:39  
标签:tensor pytorch1 img 学习 Tensor transforms 深度 数据 torchvision


DataSet DataLoader Torchvision 数据读取

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

Dataset 类

PyTorch 中的 Dataset 类是一个抽象类,它可以用来表示数据集。

我们通过继承 Dataset 类来自定义数据集的格式、大小和其它属性,后面就可以供 DataLoader 类直接使用。其实这就表示,无论使用自定义的数据集,还是官方为我们封装好的数据集,其本质都是继承了 Dataset 类。

而在继承 Dataset 类时,至少需要重写以下几个方法:__init__():构造函数,可自定义数据读取方法以及进行数据预处理;__len__():返回数据集大小;__getitem__():索引数据集中的某一个数据。光看原理不容易理解,下面我们来编写一个简单的例子,看下如何使用 Dataset 类定义一个 Tensor 类型的数据集。

结合代码可以看到,我们定义了一个名字为 MyDataset 的数据集,在构造函数中,传入 Tensor 类型的数据与标签;在 __len__ 函数中,直接返回 Tensor 的大小;在__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]

下面,我们来看一下如何调用刚才定义的数据集。

首先随机生成一个 10*3 维的数据 Tensor,然后生成 10 维的标签 Tensor,与数据 Tensor 相对应。利用这两个 Tensor,生成一个 MyDataset 的对象。查看数据集的大小可以直接用 len() 函数,索引调用数据可以直接使用下标。

# 生成数据
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))
'''
输出:
Dataset size: 10
'''

# 使用索引调用数据
print('tensor_data[0]: ', my_dataset[0])
'''
输出:
tensor_data[0]:  (tensor([ 0.4931, -0.0697,  0.4171]), tensor(0))
'''

DataLoader 类

在实际项目中,如果数据量很大,考虑到内存有限、I/O 速度等问题,在训练过程中不可能一次性的将所有数据全部加载到内存中,也不能只用一个进程去加载,所以就需要多进程、迭代加载,而 DataLoader 就是基于这些需要被设计出来的。

DataLoader 是一个迭代器,最基本的使用方法就是传入一个 Dataset 对象,它会根据参数 batch_size 的值生成一个 batch 的数据,节省内存的同时,它还可以实现多进程、数据打乱等处理

DataLoader 类的调用方式如下:

from torch.utils.data import DataLoader
tensor_dataloader = DataLoader(dataset=my_dataset, # 传入的数据集, 必须参数
                               batch_size=2,       # 输出的batch大小
                               shuffle=True,       # 数据是否打乱
                               num_workers=0)      # 进程数, 0表示只有主进程

# 以循环形式输出
for data, target in tensor_dataloader: 
    print(data, target)
'''
输出:
tensor([[-0.1781, -1.1019, -0.1507],
        [-0.6170,  0.2366,  0.1006]]) tensor([0, 0])
tensor([[ 0.9451, -0.4923, -1.8178],
        [-0.4046, -0.5436, -1.7911]]) tensor([0, 0])
tensor([[-0.4561, -1.2480, -0.3051],
        [-0.9738,  0.9465,  0.4812]]) tensor([1, 0])
tensor([[ 0.0260,  1.5276,  0.1687],
        [ 1.3692, -0.0170, -1.6831]]) tensor([1, 0])
tensor([[ 0.0515, -0.8892, -0.1699],
        [ 0.4931, -0.0697,  0.4171]]) tensor([1, 0])
'''
 
# 输出一个batch
print('One batch tensor data: ', iter(tensor_dataloader).next())
'''
输出:
One batch tensor data:  [tensor([[ 0.9451, -0.4923, -1.8178],
        [-0.4046, -0.5436, -1.7911]]), tensor([0, 0])]
'''

结合代码,我们梳理一下 DataLoader 中的几个参数,它们分别表示:

  • dataset:Dataset 类型,输入的数据集,必须参数;
  • batch_size:int 类型,每个 batch 有多少个样本;
  • shuffle:bool 类型,在每个 epoch 开始的时候,是否对数据进行重新打乱;
  • num_workers:int 类型,加载数据的进程数,0 意味着所有的数据都会被加载进主进程,默认为 0。

Torchvision

PyTroch 官方为我们提供了一些常用的图片数据集,如果你需要读取这些数据集,那么无需自己实现,只需要利用 Torchvision 就可以搞定。

Torchvision 是一个和 PyTorch 配合使用的 Python 包。它不只提供了一些常用数据集,还提供了几个已经搭建好的经典网络模型,以及集成了一些图像数据处理方面的工具,主要供数据预处理阶段使用。简单地说,Torchvision 库就是常用数据集 + 常见网络模型 + 常用图像处理方法。

利用 Torchvision 读取数据

Torchvision 库中的torchvision.datasets包中提供了丰富的图像数据集的接口。常用的图像数据集,例如 MNIST、COCO 等,这个模块都为我们做了相应的封装。

MNIST 数据集是一个著名的手写数字数据集,因为上手简单,在深度学习领域,手写数字识别是一个很经典的学习入门样例。

数据读取

对于torchvision.datasets所支持的所有数据集,它都内置了相应的数据集接口。例如刚才介绍的 MNIST 数据集,torchvision.datasets就有一个 MNIST 的接口,接口内封装了从下载、解压缩、读取数据、解析数据等全部过程。这些接口的工作方式差不多,都是先从网络上把数据集下载到指定目录,然后再用加载器把数据集加载到内存中,最后将加载后的数据集作为对象返回给用户。

以 MNIST 为例,我们可以用如下方式调用:

# 以MNIST为例
import torchvision
mnist_dataset = torchvision.datasets.MNIST(root='./data',
                                       train=True,
                                       transform=None,
                                       target_transform=None,
                                       download=True)

torchvision.datasets.MNIST是一个类,对它进行实例化,即可返回一个 MNIST 数据集对象。

构造函数包括包含 5 个参数:

  • root:是一个字符串,用于指定你想要保存 MNIST 数据集的位置。如果 download 是 Flase,则会从目标位置读取数据集;
  • download:是布尔类型,表示是否下载数据集。如果为 True,则会自动从网上下载这个数据集,存储到 root 指定的位置。如果指定位置已经存在数据集文件,则不会重复下载;
  • train:是布尔类型,表示是否加载训练集数据。如果为 True,则只加载训练数据。如果为 False,则只加载测试数据集。这里需要注意,并不是所有的数据集都做了训练集和测试集的划分,这个参数并不一定是有效参数,具体需要参考官方接口说明文档;
  • transform:用于对图像进行预处理操作,例如数据增强、归一化、旋转或缩放等。这些操作我们会在下节课展开讲解;
  • target_transform:用于对图像标签进行预处理操作。

而对于那些没有官方接口的图像数据集,我们也可以使用以torchvision.datasets.ImageFolder接口来自行定义,在图像分类的实战篇中,就是使用 ImageFolder 进行数据读取的,你可以到那个时候再看一看。

数据预览

完成了数据读取工作,我们得到的是对应的 mnist_dataset,刚才已经讲过了,这是一个封装了的数据集。如果想要查看 mnist_dataset 中的具体内容,我们需要把它转化为列表。

(如果 IOPub data rate 超限,可以只加载测试集数据,令 train=False)

mnist_dataset_list = list(mnist_dataset)
print(mnist_dataset_list)

转换后的数据集对象变成了一个元组列表,每个元组有两个元素,第一个元素是图像数据,第二个元素是图像的标签。

Transform

图像处理工具之 torchvision.transforms

Torchvision 库中的torchvision.transforms包中提供了常用的图像操作,包括对 Tensor 及 PIL Image 对象的操作,例如随机切割、旋转、数据类型转换等等。按照torchvision.transforms 的功能,大致分为以下几类:数据类型转换、对 PIL.Image 和 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(默认值),则根据输入数据的维度进行推断:

torchvision.transforms 提供了丰富的图像变换方法,例如:改变尺寸、剪裁、翻转等。并且这些图像变换操作可以接收多种数据格式,不仅可以直接对 PIL 格式的图像进行变换,也可以对 Tensor 进行变换,无需我们再去做额外的数据类型转换。下面我们依次来看一看。

Resize

将输入的 PIL Image 或 Tensor 尺寸调整为给定的尺寸,具体定义为

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

我们依次看下相关的参数:

size:期望输出的尺寸。如果 size 是一个像 (h, w) 这样的元组,则图像输出尺寸将与之匹配。如果 size 是一个 int 类型的整数,图像较小的边将被匹配到该整数,另一条边按比例缩放。

interpolation:插值算法,int 类型,默认为 2,表示 PIL.Image.BILINEAR。

有关 Size 中是 tuple 还是 int 这一点请你一定要注意。

在 resize 之后呢,一般会接一个 crop 操作,crop 到指定的大小。对于高与宽接近的图片来说,这么做问题不大,但是高与宽的差距较大时,就会 crop 掉很多有用的信息。关于这一点,我们在后续的图像分类部分还会遇到,到时我在详细展开。


from PIL import Image
from torchvision import transforms 

# 定义一个Resize操作
resize_img_oper = transforms.Resize((200,200), interpolation=2)

# 原图
orig_img = Image.open('jk.jpg') 
display(orig_img)
# 注意调用的方法
# Resize操作后的图
img = resize_img_oper(orig_img)
display(img)

剪裁

torchvision.transforms提供了多种剪裁方法,例如中心剪裁、随机剪裁、四角和中心剪裁等。我们依次来看下它们的定义。先说中心剪裁,顾名思义,在中心裁剪指定的 PIL Image 或 Tensor,其定义如下:

其中,size 表示期望输出的剪裁尺寸。如果 size 是一个像 (h, w) 这样的元组,则剪裁后的图像尺寸将与之匹配。如果 size 是 int 类型的整数,剪裁出来的图像是 (size, size) 的正方形。

torchvision.transforms.CenterCrop(size)

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

torchvision.transforms.RandomCrop(size, padding=None)
其中,size 代表期望输出的剪裁尺寸,用法同上。而 padding 表示图像的每个边框上的可选填充。默认值是 None,即没有填充。通常来说,不会用 padding 这个参数,至少对于我来说至今没用过。

最后要说的是 FiveCrop,我们将给定的 PIL Image 或 Tensor ,分别从四角和中心进行剪裁,共剪裁成五块,定义如下:

# size 可以是 int 或 tuple,用法同上。
torchvision.transforms.FiveCrop(size)

例子:

from PIL import Image
from torchvision import transforms 
# 定义剪裁操作
center_crop_oper = transforms.CenterCrop((60,70))
random_crop_oper = transforms.RandomCrop((80,80))
five_crop_oper = transforms.FiveCrop((60,70))
# 原图
orig_img = Image.open('jk.jpg')
display(orig_img)
# 中心剪裁
img1 = center_crop_oper(orig_img)
display(img1)#
随机剪裁
img2 = random_crop_oper(orig_img)
display(img2)
# 四角和中心剪裁
imgs = five_crop_oper(orig_img)
for img in imgs: display(img)

翻转

接下来,我们来看一看翻转操作。torchvision.transforms提供了两种翻转操作,

分别是:以某一概率随机水平翻转图像和以某一概率随机垂直翻转图像。我们分别来看它们的定义。

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

定义如下:

torchvision.transforms.RandomHorizontalFlip(p=0.5)
  • 以概率 p 随机垂直翻转图像,

定义如下:

torchvision.transforms.RandomVerticalFlip(p=0.5)

其中,p 表示随机翻转的概率值,默认为 0.5。这里的随机翻转,是为数据增强提供方便。如果想要必须执行翻转操作的话,将 p 设置为 1 即可。

from PIL import Image
from torchvision import transforms 

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

# 原图
orig_img = Image.open('jk.jpg') 
display(orig_img)

# 水平翻转
img1 = h_flip_oper(orig_img)
display(img1)
# 垂直翻转
img2 = v_flip_oper(orig_img)
display(img2)

只对 Tensor 进行变换

目前版本的 Torchvision(v0.10.0)对各种图像变换操作已经基本同时支持 PIL Image 和 Tensor 类型了,因此只针对 Tensor 的变换操作很少,只有 4 个,分别是 LinearTransformation(线性变换)、Normalize(标准化)、RandomErasing(随机擦除)、ConvertImageDtype(格式转换)。

这里我们重点来看最常用的一个操作:标准化

标准化

标准化是指每一个数据点减去所在通道的平均值,再除以所在通道的标准差,

数学的计算公式如下:output=(input−mean)/std

而对图像进行标准化,就是对图像的每个通道利用均值和标准差进行正则化。这样做的目的,是为了保证数据集中所有的图像分布都相似,这样在训练的时候更容易收敛,既加快了训练速度,也提高了训练效果。让我来解释一下:首先,标准化是一个常规做法,可以理解为无脑进行标准化后再训练的效果,大概率要好于不进行标准化。

但是计算机(也就是卷积神经网络)不一定能分辨出来一个进行操作的图片和无任何操作的图片,因为卷积神经网络是通过图像的像素进行提取特征的,这两张图片像素的数值都不一样,凭什么还让神经网络认为是一张图片?

而标准化后的数据就会避免这一问题,标准化后会将数据映射到同一区间中,一个类别的图片虽说有的像素值可能有差异,但是它们分布都是类似的分布。

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

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

mean:表示各通道的均值;std:表示各通道的标准差;inplace:表示是否原地操作,默认为否。

变换的组合

其实前面介绍过的所有操作都可以用 Compose 类组合起来,进行连续操作。Compose 类是将多个变换组合到一起,它的定义如下。

torchvision.transforms.Compose(transforms)

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

from PIL import Image
from torchvision import transforms 

# 原图
orig_img = Image.open('jk.jpg') 
display(orig_img)

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

# 组合操作后的图
img = composed(orig_img)
display(img)

结合 datasets 使用

Compose 类是未来我们在实际项目中经常要使用到的类,结合torchvision.datasets包,就可以在读取数据集的时候做图像变换与数据增强操作。

在利用torchvision.datasets 读取 MNIST 数据集时,有一个参数“transform”吗?它就是用于对图像进行预处理操作的,例如数据增强、归一化、旋转或缩放等。这里的“transform”就可以接收一个torchvision.transforms操作或者由 Compose 类所定义的操作组合。

我们在读取 MNIST 数据集时,直接读取出来的图像数据是 PIL.Image.Image 类型的。但是遇到要训练手写数字识别模型这类的情况,模型接收的数据类型是 Tensor,而不是 PIL 对象。这时候,我们就可以利用“transform”参数,使数据在读取的同时做类型转换,这样读取出的数据直接就可以是 Tensor 类型了。不只是数据类型的转换,我们还可以增加归一化等数据增强的操作,只需要使用上面介绍过的 Compose 类进行组合即可。这样,在读取数据的同时,我们也就完成了数据预处理、数据增强等一系列操作。

from torchvision import transforms
from torchvision import datasets

# 定义一个transform
my_transform = transforms.Compose([transforms.ToTensor(),
                                   transforms.Normalize((0.5), (0.5))
                                  ])
# 读取MNIST数据集 同时做数据变换
mnist_dataset = datasets.MNIST(root='./data',
                               train=False,
                               transform=my_transform,
                               target_transform=None,
                               download=True)

# 查看变换后的数据类型
item = mnist_dataset.__getitem__(0)
print(type(item[0]))
'''
输出:
<class 'torch.Tensor'>
'''

数据增强的方法有很多,不过根据我的经验来看,并不是用的越多,效果越好。

make_grid

make_grid 的作用是将若干幅图像拼成在一个网格中,它的定义如下。

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

定义中对应的几个参数含义如下:

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

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

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

    make_grid 函数主要用于展示数据集或模型输出的图像结果。我们以 MNIST 数据集为例,整合之前学习过的读取数据集以及图像变换的内容,来看一看 make_grid 函数的效果。

import torchvision
from torchvision import datasets
from torchvision import transforms
from torch.utils.data import DataLoader

# 加载MNIST数据集
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_iter = iter(tensor_dataloader)
img_tensor, label_tensor = data_iter.next()
print(img_tensor.shape)
'''
输出:torch.Size([32, 1, 28, 28])
'''
# 将32张图片拼接在一个网格中
grid_tensor = torchvision.utils.make_grid(img_tensor, nrow=8, padding=2)
grid_img = transforms.ToPILImage()(grid_tensor)
display(grid_img)

结合代码我们可以看到,程序首先利用torchvision.datasets加载 MNIST 的测试集,然后利用 DataLoader 类的迭代器一次获取到 32 张图片的 Tensor,最后利用 make_grid 函数将 32 张图片拼接在了一幅图片中。

MNIST 的测试集中的 32 张图片,如下图所示,这里我要特别说明一下,因为 MNIST 的尺寸为 28x28,注意torch和numpy中关于图像的存储似乎是不同的,在PyTorch里面,对于计算机视觉任务,如果图像数据是3维张量,那么张量维数的意义是 通道数x高x宽;如果是4维张量,那么张量维数的意义是 样本数x通道数x高x宽数据存储和表示方式似乎也不同?

所以测试集里的手写数字图片像素都比较低,但这并不影响咱们动手实践。你可以参照我给到的示范,自己动手试试看。

标签:tensor,pytorch1,img,学习,Tensor,transforms,深度,数据,torchvision
From: https://www.cnblogs.com/ZZXJJ/p/17287372.html

相关文章

  • matlab学习笔记7 插值方法与求解微分方程
    插值法拉格朗日插值分段插值由于高次函数往往拟合的情况反而不好,所以用两点之间的直线代替其值进行插值三次样条插值更加光滑,节点处二阶可导代码汇总interp1(x0,y0,x,'cubic')%分段三次多项式插值,第三个参数不写则为普通分段插值interp1(x0,y0,x,'spline')%三次样条插值......
  • 系统架构设计——DDD设计框架基本学习
    摘要最近DDD设计很火,但是刚刚入门还是很懵,通过的学习DDD项目设计的结构目录来实现对DDD设计理解同时也是为大家在公司能够看懂公司的DDD项目结构目录做一个参考和学习。同时后期本人将推出更多的对DDD设计的理解。同时提供一个DDD设计的模板框架:https://github.com/2462612540/Seni......
  • 【C++学习笔记】关于const int* 、 int const * 、 int* const
    constint*、intconst*、int*const的关键点在于const与*的位置,而const与类型int的位置无关,可以随意调换,属于个人代码风格,不过建议写成constint*,方便直观。constint*与int*const的区别在于const修饰的是int*还是变量,其中constint*a修饰的是int*,表示这个......
  • 104.二叉树的最大深度
    给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明:叶子节点是指没有子节点的节点。示例:给定二叉树[3,9,20,null,null,15,7],classSolution{public:intgetdepth(TreeNode*node){if(node==NULL)return0;......
  • 系统化学习前端之JavaScript(ES6:异步编程)
    前言JavaScript异步编程这块东西比较多,涉及到宏任务和微任务,所以单开一个篇幅梳理一下。同步和异步同步和异步是一种宏观概念,具体表现在JavaScript中,是同步任务和异步任务,即同步函数和异步函数。同步同步指函数在JavaScript同步执行。同步函数执行过程:A函数进入函数调......
  • 学习-五分钟上手ts
    网址:https://www.tslang.cn/docs/handbook/typescript-in-5-minutes.html//函数重新实现解决方法在文件夹中创建tsconfig.json即可functionhello(name:string){return'hello'+name}//letuser=[0,1,2]letuser='yibo'document.body.innerHTML=hell......
  • 学习笔记——Python基础
    字符串索引str='我是一名学生'print(str[0])#输出“我”print(str[-6])#输出“我”字符串切片:把数据对象的一部分拿出来str='我是一名学生'print(str[2:4])#输出“一名”print(str[-4:-2])#输出“一名”#获取字符串长度:len()str='我是一名学生'le......
  • CMake学习
    转载来自:https://subingwen.cn/cmake/CMake-primer/1.CMake概述CMake是一个项目构建工具,并且是跨平台的。关于项目构建我们所熟知的还有Makefile(通过make命令进行项目的构建),大多是IDE软件都集成了make,比如:VS的nmake、linux下的GNUmake、Qt的qmake等,如果自己动手......
  • vickyの网络流学习小结
    前言之前一直觉得网络流很难,畏难心理作祟就一直没好好学。然后今天教练讲杂题选做的时候就遭报应了。QAQ下午本着能会就会不会也得会的心态看了一下网络流,感觉还挺简单(?草率地学了一下,在这里稍微做一下总结防止以后忘记吧。QwQ参考blog:网络流小记(EK&dinic&当前弧优化&费用......
  • vickyの搜索学习小结
    昨天模拟赛考了\(IDA^*\)然后愣是没写出来暴力,感觉自己的搜索是时候该好好练一下了啊QAQ。upd:本来只打算写一下\(ID\)、\(A^*\)、模拟退火、01bfs,双向bfs、爬山法和遗传算法的,后面想起来之前和在一个哥哥那里听过的一致代价搜索,稍微学习了一下,等有空的时候再写。迭代加......