目录
第一章 PyTorch介绍及安装
1.1 PyTorch简介
Facebook公司开发的PyTorch框架是端到端深度学习平台最受欢迎的框架,是一个由Python、C++和CUDA语言编写的免费开源软件库,被广泛用于计算机视觉、语音识别和自然语言处理等深度学习领域。PyTorch有两大优势:一是具有强大GPU加速的张量计算;二是包含自动求导系统的深度神经网络。
PyTorch的前身可追溯到2002年诞生于纽约大学的Torch,Facebook人工智能研究院于2017年在GitHub上开源了PyTorch,并迅速占据GitHub热度榜榜首。
1.2 PyTorch安装
1.2.1 Windows系统安装
1. CPU版PyTorch
pip install torch
2. GPU版Pytorch
步骤一
检查是否有合适的GPU,若有需要安装CUDA与CuDNN;
步骤二
进入PyTorch官网,检查目前PyTorch支持的CUDA版本;
https://pytorch.org/get-started/locally/
步骤三
进入CUDA官网,点击CUDA Toolkit 11.8.0,下载Base installer并安装;
https://developer.nvidia.com/cuda-downloads
步骤四
进入CuDNN官网,登录账号,选择最新版,点击Base installe;
步骤五
解压cudnn压缩包,并将其复制到cuda安装路径下;
步骤六
下载torch、torchvision和torchaudio
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
1.2.2 Linux系统安装
1. CPU版PyTorch
待写
2. GPU版Pytorch
待写
第二章 PyTorch 基础编程
2.1 张量定义
- 张量的定义
张量(Tensor)是PyTorch的核心数据结构。张量的维度(dimension)也称为阶。零阶张量称为标量(scalar),1阶张量称为向量(vector),2阶张量称为矩阵(matrix),3阶以上的就直接称为张量。
Tensor类的shape属性和size()函数返回张量的具体维度分量。零阶张量,一般用于超参数、参数和损失函数。一阶张量用作神经网络偏置,以及神经网络各层的输入。二阶张量一般用作神经网络的批量输入和输出,以及线性神经元的权重参数。三阶张量通常用作图片表示,一张彩色图片的RGB这三个通道占一维,宽和高各占一维。四维张量通常在卷积神经网络中表示图像。一次输入到神经网络中进行训练的样本数目称为批大小。 - Tensor与Variable的差异
在PyTorch中,Tensor和Variable有一定的关系,但在最新的PyTorch版本中,Variable已经被弃用了。在早期版本的PyTorch中,Variable是一个用于自动求导的类,它封装了Tensor并提供了自动求导的功能。 当在计算图中使用Variable时,它会跟踪计算图的操作,并允许你对变量进行梯度计算。然而,从PyTorch 0.4.0版本开始,Variable被弃用了,因为自动求导的功能已经集成到了Tensor中。在当前版本的PyTorch中,Tensor是主要的数据类型。Tensor对象支持各种数学操作和函数,可以进行高效的计算和操作。而且,Tensor对象还具有自动求导的功能,可以通过设置requires_grad属性为True来跟踪操作并计算梯度。Variable与Tensor具体的数据结构如下图所示:
图 a Variable | 图 b Tensor |
1)Variable数据类型
在pytorch 0.4.0版本之前,Variable是torch.autograd中的数据类型,主要用于封装Tensor,然后进行自动求导。Variable对象具有以下几个主要的属性:
- data:被包装的Tensor对象,存储实际的数据。
- grad:一个Tensor对象,存储data的梯度。在进行反向传播时,梯度将累积到grad中。
- grad_fn:创建该Variable的函数(或操作)的引用。它是构建计算图的关键,用于实现自动求导。
- requires_grad:一个布尔值,指示是否需要计算梯度。如果设置为True,则Variable将跟踪操作并计算梯度;如果设置为False,则不会计算梯度。
- is_leaf:一个布尔值,指示该Variable是否是计算图中的叶子节点。叶子节点是由用户直接创建的Variable,而非通过计算得到的。
2)Tensor数据类型
在PyTorch 0.4.0及以后的版本中,Tensor对象支持自动求导,并且Variable类已经被弃用。Tensor对象具有以下几个主要的属性:
- dtype:表示张量的数据类型;
- shape:表示张量的形状,以元组形式表示;
- device:表示张量所在的设备,可以是GPU或CPU。如果张量位于GPU上,则可以利用GPU的并行计算能力来加速运算。
2.2 张量操作
2.2.1 创建张量
- 创建张量
1)判断是否为PyTorch张量
torch.is_tensor()函数可判断输入参数是否为PyTorch张量。
运行结果如下:import torch arr = [1, 2, 3] print(torch.is_tensor(arr))
2)从Python列表中创建张量False
torch.tensor()函数可直接从python列表中创建PyTorch张量。
运行结果如下:import torch arr = [1, 2, 3] t1 = torch.tensor(arr) print(f"t1是否为张量:{torch.is_tensor(t1)}") print(f"t1中的数据:{t1}")
3)初始化全0或全1张量t1是否为张量:True t1中的数据:tensor([1, 2, 3])
torch.ones()函数和torch.zeros()函数分别创建全1或全0张量。
运行结果如下:import torch t = torch.ones(5) t1 = torch.zeros(5, 6) print(f"t中的数据:{t}") print(f"t1中的数据:{t1}")
4)创建随机数张量t中的数据:tensor([1., 1., 1., 1., 1.]) t1中的数据:tensor([[0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.]])
torch.randn()函数用于创建正态分布的随机张量,torch.rand()函数用于创建取值在[0, 1)范围内的均匀分布的随机张量。
运行结果如下:import torch t = torch.randn(2, 3) # 正态分布随机张量 t1 = torch.rand(2, 1) # 均匀分布随机张量 print(f"t中的数据:{t}") print(f"t1中的数据:{t1}")
注意:由于输出的是随机数,所以可能每次运行的实际输出不同于上一次运行结果。t中的数据:tensor([[-1.1226, 1.0770, 1.0084], [ 0.2833, 0.0693, -0.0252]]) t1中的数据:tensor([[0.7664], [0.6271]])
5)创建二维对角矩阵张量
torch.eye()函数用于创建二维对角矩阵张量,并不要求矩阵张量必须是方阵。
运行结果如下:import torch t = torch.eye(3) # 创建3*3的对角张量 t1 = torch.eye(2, 3) # 创建2*3的对角张量 print(f"t中的数据:{t}") print(f"t1中的数据:{t1}")
6)创建一维序列张量t中的数据:tensor([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) t1中的数据:tensor([[1., 0., 0.], [0., 1., 0.]])
torch.arange()和torch.linspace()函数创建以为序列张量。torch.arange()函数输入参数start(默认参数0),end指定结束值,step(默认参数1)。
运行结果如下:import torch t = torch.arange(0, 30) t1 = torch.linspace(0, 30, 10) print(f"t中的数据:{t}") print(f"t1中的数据:{t1}")
注意:torch.arange()函数输出的序列不包含end值;torch.linspace()函数输出的序列包含end值。t中的数据:tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) t1中的数据:tensor([ 0.0000, 3.3333, 6.6667, 10.0000, 13.3333, 16.6667, 20.0000, 23.3333, 26.6667, 30.0000])
7)创建取值为指定范围的随机置乱的张量
torch.arange()函数生成0~n-1的整数的随机排列,常用于数据采集样本的随机置乱。
运行结果如下:import torch t = torch.randperm(10) print(f"t中的数据:{t}")
8)numpy数组与张量相互转换t中的数据:tensor([3, 1, 8, 4, 7, 6, 0, 2, 9, 5])
torch.from_numpy()函数将Numpy数组转换为Tensor,tensor.numpy()函数将Tensor转化为Numpy数组。
运行结果如下:import torch import numpy as np arr = np.arange(9) t = torch.from_numpy(arr) arr1 = t.numpy() print(f"t中的数据:{t}") print(f"arr1中的数据:{arr1}")
注意:tensor.numpy()函数只能将CPU的张量转换为Numpy数组,无法将GPU的张量转换为Numpy数组。正确做法:先将GPU的张量存放到CPU中,在进行转换。t中的数据:tensor([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=torch.int32) arr1中的数据:[0 1 2 3 4 5 6 7 8]
2.2.2 张量数据类型操作
-
数据类型设定
数据类型 dtype CPU张量 GPU张量 32位浮点数 torch.float32或torch.float torch.FloatTensor() torch.cuda.FloatTensor() 64位浮点数 torch.float64或torch.double torch.DoubleTensor() torch.cuda.DoubleTensor() 8位无符号整数 torch.uint8 torch.ByteTensor() torch.cuda.ByteTensor() 8位符号整数 torch.int8 torch.CharTensor() torch.cuda.ShortTensor() 16位有符号整数 torch.int16或torch.short torch.ShortTensor() torch.cuda.ShortTensor() 32位有符号整数 torch.int32或torch.int torch.IntTensor() torch.cuda.IntTensor() 64位有符号整数 torch.int64或torch.long torch.LongTensor() torch.cuda.LongTensor() 布尔型 torch.bool torch.BoolTensor() torch.cuda.BoolTensor() 1)构建张量时,设定数据类型
用特定函数构建张量时,可以用dtype参数来设定数据类型。import torch t1 = torch.tensor([1, 2, 4], dtype=torch.float) print(t1) t2 = torch.tensor([0, 1, 1], dtype=torch.bool) print(t2)
运行结果如下:
tensor([1., 2., 4.]) tensor([False, True, True])
2)转换数据类型
使用type_as()函数转换数据类型import torch t1 = torch.tensor([1, 2, 4], dtype=torch.int) print(t1) t2 = t1.type_as(torch.FloatTensor()) print(t2)
运行结果如下:
tensor([1, 2, 4], dtype=torch.int32) tensor([1., 2., 4.])
2.2.3 张量索引、切片、拼接和形状变换
- 张量索引操作
1)索引操作
torch.index_select(input:tensor, dim:int, index:tensor)函数用于在参数dim指定维度上。
运行结果如下:import torch t = torch.tensor([1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2) t1 = torch.index_select(t, 0, torch.IntTensor([0, 2])) print(t1)
2)Numpy索引方式tensor([[1., 2.], [5., 6.]])
运行结果如下:import torch t = torch.tensor([1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2) print(t) t1 = t[0, :] t2 = t[[0, 2], :] print(t1) print(t2)
3)masked_select()索引方式tensor([[1., 2.], [3., 4.], [5., 6.]]) tensor([1., 2.]) tensor([[1., 2.], [5., 6.]])
masked_select()是一种更为灵活的索引方式,根据掩码mask中的二元索引值,取出张量中指定项。
运行结果如下:import torch t = torch.tensor([-1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2) mask = torch.BoolTensor([[1, 0], [0, 0], [0, 0]]) a = torch.masked_select(t, mask) print(a)
或tensor([-1.])
运行结果如下:import torch t = torch.tensor([-1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2) mask = t < 0 a = torch.masked_select(t, mask) print(a)
tensor([-1.])
- 切片
1)torch.chunk(input:tensor, chunks:int, dim:int)函数
torch.chunk(input:tensor, chunks:int, dim:int)函数将张量在指定维度上分割为特定数量的块chunks,默认维度为0。
运行结果如下:import torch t = torch.arange(10).reshape(5, 2) a = torch.chunk(t, 2) print(a)
2)torch.split(input:tensor, split_size_or_selections:int or List)函数(tensor([[0, 1], [2, 3], [4, 5]]), tensor([[6, 7], [8, 9]]))
torch.split(input:tensor, split_size_or_selections:int or List)函数将张量分割为指定形状的块。
运行结果如下:import torch t = torch.arange(10).reshape(5, 2) a = torch.split(t, [2, 3]) print(a)
(tensor([[0, 1], [2, 3]]), tensor([[4, 5], [6, 7], [8, 9]]))
- 拼接
1)torch.cat(tensors: Tuple[Tensor, …] | List[Tensor], dim: int = 0)函数
torch.cat(tensors: Tuple[Tensor, …] | List[Tensor], dim: int = 0)函数将张量按维度进行拼接,但不扩展张量的维度。
运行结果如下:import torch t = torch.arange(10).reshape(5, 2) t1 = torch.arange(4).reshape(2, 2) t2 = torch.cat((t, t1), 0) print(t2)
2)torch.stack(Tuple[Tensor, …] | List[Tensor],dim: int = 0)函数tensor([[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [0, 1], [2, 3]])
torch.stack(Tuple[Tensor, …] | List[Tensor],dim: int = 0)函数在指定的新维度上进行拼接,会扩展张量的维度。
运行结果如下:import torch t = torch.arange(10).reshape(5, 2) t1 = torch.stack([t, t], dim=0) print(t1.size()) print(t1)
torch.Size([2, 5, 2]) tensor([[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]], [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]])
- 拼接
1)torch.reshape(input: Tensor, shape: Sequence[int | SymInt])函数
torch.reshape(input: Tensor, shape: Sequence[int | SymInt])函数可以变化张量。
案例 1
运行结果如下:import torch t = torch.arange(10) t1 = torch.reshape(t, (2, 5)) print(t) print(t1)
案例 2tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) tensor([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
运行结果如下:import torch t = torch.arange(10) t1 = torch.reshape(t, [2, 5]) print(t) print(t1)
2)tensor.reshape(shape: Sequence[int | SymInt])函数tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) tensor([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
tensor.reshape(shape: Sequence[int | SymInt])函数可以变化张量。
运行结果如下:import torch t = torch.arange(10) t1 = t.reshape((5, 2)) print(t) print(t1)
3)torch.transpose(input: Tensor, dim0: int, dim1: int)函数tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) tensor([[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]])
torch.transpose(input: Tensor, dim0: int, dim1: int)函数可以交换张量的两个维度。
运行结果如下:import torch t = torch.rand((3, 4)) print(t) t1 = t.transpose(0, 1) print(t1) t2 = torch.transpose(t, 0, 1) print(t2)
4)torch.squeeze()函数tensor([[0.1654, 0.4490, 0.1174, 0.3273], [0.5618, 0.2000, 0.7795, 0.2445], [0.8391, 0.0548, 0.1492, 0.2705]]) tensor([[0.1654, 0.5618, 0.8391], [0.4490, 0.2000, 0.0548], [0.1174, 0.7795, 0.1492], [0.3273, 0.2445, 0.2705]]) tensor([[0.1654, 0.5618, 0.8391], [0.4490, 0.2000, 0.0548], [0.1174, 0.7795, 0.1492], [0.3273, 0.2445, 0.2705]])
torch.squeeze()函数或tensor.squeeze(input:tensor)可以压缩指定维度且长度为1的维度。如果dim取默认值None,则压缩全部长度为1的维度。
运行结果如下:import torch t = torch.rand((2, 3, 1, 2, 1)) print(t.size()) t1 = t.squeeze() print(t1.size()) t2 = torch.squeeze(t) print(t2.size())
5)torch.unsqueeze(input: Tensor, dim: int)函数torch.Size([2, 3, 1, 2, 1]) torch.Size([2, 3, 2]) torch.Size([2, 3, 2])
torch.unsqueeze(input: Tensor, dim: int)函数或tensor.unsqueeze(dim:int)函数扩展指定dim的维度且长度为1。
运行结果如下:import torch t = torch.arange(4) print(t.size()) print(t.unsqueeze(dim=0).size()) print(t.unsqueeze(dim=1).size())
torch.Size([4]) torch.Size([1, 4]) torch.Size([4, 1])
2.2.4 广播机制
- 广播机制
PyTorch中的很多操作都支持广播(broadcasting),这种广播机制与NumPy中的广播机制类似,这使得张量的形状可以根据一组规则自动调整张量的形状,使其与其他张量具有相同的维度,不需要显式地复制数据。广播机制通常用于三种类型的元素运算:标量与张量运算、相同维度不同形状的张量运算和不同维度的张量运算。
1)标量与张量运算
运行结果如下:import torch t = torch.arange(5) print(t) t = t + 1 print(t)
2)相同维度但不同形状的张量运算tensor([0, 1, 2, 3, 4]) tensor([1, 2, 3, 4, 5])
运行结果如下:import torch t = torch.arange(6).reshape(2, 3) print(t) t1 = torch.arange(3).reshape(1, 3) print(t1) t2 = t - t1 print(t2)
3)不同维度的张量运算tensor([[0, 1, 2], [3, 4, 5]]) tensor([[0, 1, 2]]) tensor([[0, 0, 0], [3, 3, 3]])
运算结果如下:import torch t = torch.arange(6).reshape(2, 3) print(t) t1 = torch.arange(3) print(t1) t2 = t - t1 print(t2)
tensor([[0, 1, 2], [3, 4, 5]]) tensor([0, 1, 2]) tensor([[0, 0, 0], [3, 3, 3]])
2.2.5 GPU加速
- GPU上使用Tensor
1)获取本机GPU配置
运行结果如下:import torch print(torch.cuda.is_available()) # GPU是否可用 print(torch.cuda.device_count()) # GPU数量 print(torch.cuda.current_device()) # 当前GPU设备 print(torch.cuda.get_device_name()) # GPU设备名称
2)指定张量的设备True 1 0 NVIDIA GeForce GTX 1650
运行结果如下:import torch t_gpu = torch.tensor([2.0, 2, 3], device='cuda') # 新建GPU张量 print(t_gpu)
注意: 新建张量时,可以使用device属性来指定期望设备,device默认值为None。tensor([2., 2., 3.], device='cuda:0')
3)CPU与GPU张量转换
方法一:
tensor.to(device=None)可以将张量放到指定的设备中。
运行结果如下:import torch t = torch.arange(5) # 新建一个cpu张量 t2 = t.to(device='cuda:0') print(t2)
方法二:tensor([0, 1, 2, 3, 4], device='cuda:0')
在创建好张量后,可以调用tensor.cuda()函数创建GPU副本;也可以调用tensor.cpu()函数创建CPU张量副本。
运行结果如下:import torch t = torch.arange(5) # 新建一个cpu张量 t2 = t.cuda() # 将cpu张量转换为gpu张量 t3 = t.cuda(0) # 指定gpu print(t2) print(t3) t4 = t3.cpu() # 将gpu张量转换为cpu张量 print(t4)
注意:不同设备之间的张量是不可以直接进行计算,必须先要将计算的张量放到同一设备中。另外,cuda表示gpu,cuda:0中的0表示GPU编号,实际指第一张GPU卡。tensor([0, 1, 2, 3, 4], device='cuda:0') tensor([0, 1, 2, 3, 4], device='cuda:0') tensor([0, 1, 2, 3, 4])
4)根据配置选择GPU加速
为了避免假设与实际配置不符,最好根据配置来选择是否使用GPU加速。
运行结果如下:import torch t = torch.arange(5, device='cuda:0' if torch.cuda.is_available() else 'cpu') print(t)
tensor([0, 1, 2, 3, 4], device='cuda:0')
2.3 自动求导
在PyTorch中,通过将某个张量的requires_grad属性设置为True,可以追踪对该张量的所有操作,并自动计算梯度。梯度的计算是通过调用backward函数实现的,计算得到的梯度会累加到张量的grad属性中。
在训练模型时,一般会将模型的可训练参数的requires_grad属性设置为True,以便跟踪梯度并进行参数更新。但在评估模型性能时,通常不需要进行梯度跟踪。为了暂时停止梯度跟踪,可以使用detach函数将张量与计算历史隔离,阻止对该张量的计算记录。另外,还可以将计算代码放在torch.no_grad()块中,这样也可以停止梯度跟踪。这些方法在评估模型性能时经常会使用。 自动求导还有一个重要的概念是梯度函数(gradient function),它对完整的计算历史进行编码,并存储在张量的grad_fn属性中。grad_fn引用了创建张量的Function对象。如果张量是由用户手动创建的,则其grad_fn属性设置为None。
如果想要计算张量的导数,可以调用张量的backward函数。如果张量本身是一个标量(scalar),则不需要为backward函数设置参数。但如果张量是一个向量、矩阵或更高维的张量,则需要指定一个gradient参数,其形状与张量相匹配,用于对梯度进行加权处理。
2.3.1 标量自动求导
案例一:
import torch
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2
y.backward()
print(x.grad)
运行结果如下:
tensor([4.])
案例二:
import torch
x = torch.tensor([2.0])
w = torch.tensor([3.0], requires_grad=True)
b = torch.randn(1)
b.requires_grad_(True)
for _ in range(5):
a = torch.mul(x, w)
output = a + b
output.backward()
print(w.grad)
运行结果如下:
tensor([2.])
tensor([4.])
tensor([6.])
tensor([8.])
tensor([10.])
结果分析:
- 当你多次运行同一个Python脚本文件时,会重新执行脚本中的代码,并创建新的张量对象。这意味着之前的张量对象及其梯度都会被丢弃。因此,每次运行脚本时,张量的梯度会被置空,梯度不会累加。
- 在一个循环中多次运行相同的操作时,使用的是同一个张量对象,而不是每次都创建新的张量对象。因此,梯度会在每次反向传播时累积,而不会被清空。
2.3.2 梯度清零
为了避免上述问题,需要在反向传播前进行梯度清零,否则会得到错误的结果。
import torch
x = torch.tensor([2.0])
w = torch.tensor([3.0], requires_grad=True)
b = torch.randn(1)
b.requires_grad_(True)
for _ in range(5):
if w.grad is not None: # w梯度清零
w.grad.zero_()
if b.grad is not None:
b.grad.zero_() # b梯度清零
a = torch.mul(x, w)
output = a + b
output.backward()
print(w.grad)
运行结果如下:
tensor([2.])
tensor([2.])
tensor([2.])
tensor([2.])
tensor([2.])
第三章 数据集
在机器学习中,模型训练通常可以划分为四个子模块:数据、模型、损失函数和优化器。数据模块可以进一步细分为数据收集、数据划分、数据读取和数据预处理。为了方便读取和预处理数据,PyTorch提供了torch.utils.data模块,其中的Dataset类、IterableDataset类和DataLoader类是最为常用的工具。
3.1 自定义数据集
在PyTorch中,通常使用Dataset类和IterableDataset类来重新定义数据集。Dataset类用于实现映射风格的数据集,而IterableDataset类则用于实现迭代风格的数据集。
1. Dataset类
Dataset类定义的数据集是一种映射风格的数据集。当创建一个继承Dataset类的子类时,需要重写__getitem__()方法和__len__()方法。通过重写__getitem__()方法,可以将索引映射到数据集上的具体样本。而__len__()方法则用于返回数据集中样本的总数,以便在训练过程中进行迭代。
代码 3.1 定义鸢尾花数据集
import pandas as pd
import numpy as np
from torch.utils.data import Dataset
class IrisDataset(Dataset):
def __init__(self):
iris_path = r"../data/iris/iris.data"
data = np.asarray(pd.read_csv(iris_path, header=None))
self.X = data[:, 0:4]
_, self.y = np.unique(data[:, 4], return_inverse=True) # 特征编码
self.len = len(data)
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
def __len__(self):
return self.len
if __name__ == "__main__":
dataset = IrisDataset()
print(len(dataset)) # 数据数目
first_data = next(iter(dataset))
print(first_data)
print("特征:", first_data[0], "标签:", first_data[1])
运行结果如下:
150
(array([5.1, 3.5, 1.4, 0.2], dtype=object), 0)
特征: [5.1 3.5 1.4 0.2] 标签: 0
注意: iter(dataset) 是将一个可迭代对象 dataset 转换为一个迭代器对象。迭代器对象可以用于遍历可迭代对象中的元素。next() 函数是一个内置函数,用于获取迭代器的下一个元素。
代码 3.2 定义Hymenoptera数据集
class Hymenoptera(Dataset):
def __init__(self, root_dir, label):
self.root_dir = root_dir
self.label = label
self.path = os.path.join(self.root_dir, self.label)
self.img_paths = os.listdir(self.path)
def __getitem__(self, idx):
img_name = self.img_paths[idx]
img_item_path = os.path.join(self.path, img_name)
img = cv2.imread(img_item_path)
return img, self.label
def __len__(self):
return len(self.img_paths)
if __name__ == "__main__":
root_path = r"../data/hymenoptera/train/"
labels = "ants"
hp = Hymenoptera(root_path, labels)
first_img, target = hp[0]
cv2.imshow(winname=target, mat=first_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果如下:
2. IterableDataset类
在 PyTorch 1.2 之前,唯一可用的数据集类是Dataset。这种数据集只需要继承Dataset 类,并重写len 和 getitem 方法,其中 getitem 方法接收一个索引,则该索引映射到数据集中的某个项。PyTorch 1.2引入了一个新的数据集类 IterableDataset,该类擅长将数据供给顺序模型,DataLoader会调用 next(iterable_dataset),直到构建完整的批次。
代码 3.2 定义Iris流数据集
from itertools import cycle
from torch.utils.data import IterableDataset, DataLoader
class IrisIterableDataset(IterableDataset):
def __init__(self, file_path):
super(IrisIterableDataset).__init__()
self.file_path = file_path
@staticmethod
def parse_file(file_path):
with open(file_path, 'r') as file_obj:
for line in file_obj:
tokens = line.strip('\n').split(',')
yield from tokens
def get_stream(self, file_path):
return cycle(self.parse_file(file_path))
def __iter__(self):
return self.get_stream(self.file_path)
if __name__ == "__main__":
path = r"../data/iris/iris.data"
iris_data = IrisIterableDataset(path)
loader = DataLoader(iris_data, batch_size=3)
for i in loader:
print(i)
break
运行结果如下:
['5.1', '3.5', '1.4']
3.2 采集、组织数据
自定义数据后,就可以返回数据样本了。但这种通过索引返回样本的方式比较原始,无法让数据集一次提供一个批次(batch)数据,也无法随机打乱和并行加速。
DataLoader类就是一个数据加载器,它将数据集和样本抽样器集成在一起,并提供给定数据集上的可迭代对象,其构造函数如下:
DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None, *, prefetch_factor=2,
persistent_workers=False)
|- dataset: 数据集对象
|- batch_size: 批处理大小
|- shuffle: 设置为True时将在每个epoch重新置乱样本顺序
|- sampler: 样本抽样器,定义从数据集中抽取样本的策略
|- num_workers: 加载数据的子进程。数值为0表示数据由主进程加载
|- drop_last: 如果数据大小不能被批处理大小整除,则将该属性设置为True
3.3 计算机视觉库
torchvision是一个用于计算机视觉任务的Python库,该库包含了一系列用于处理图像和视频数据的工具和模型。torchvision建立在PyTorch深度学习框架之上,旨在简化计算机视觉任务的开发过程。
torchvision的功能包括:
- 数据集加载:torchvision提供了用于加载常用计算机视觉数据集的函数,例如MNIST、CIFAR10、ImageNet等。这些函数使得加载和预处理数据集变得更加便捷。
- 数据转换:torchvision提供了一系列图像转换函数,用于对图像进行预处理和增强操作,例如裁剪、缩放、旋转、翻转、标准化等。这些转换函数可以应用于数据集加载之后,用于数据的预处理和数据增强。
- 模型定义:torchvision提供了一些经典的计算机视觉模型的实现,例如AlexNet、VGG、ResNet等。这些模型已经在大规模图像分类任务中被广泛使用,可以用于快速搭建和训练自己的计算机视觉模型。
- 图像工具:torchvision还提供了一些常用的图像处理工具,例如图像保存、显示、绘制边界框、计算图像直方图等。这些工具可以帮助用户对图像数据进行可视化和分析。
import os, torch
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
from torch.utils.data import Dataset, DataLoader
class CatsDogsDataset(Dataset):
def __init__(self, root):
self.root = root
imgs = os.listdir(self.root)
self.imgs = [os.path.join(self.root, img) for img in imgs]
def __getitem__(self, idx):
img_path = self.imgs[idx]
label = 1 if "Dog" in img_path.split("/")[-1] else 0
pil_img = Image.open(img_path)
img_array = np.array(pil_img)
img = torch.from_numpy(img_array)
return img, label
def __len(self):
return len(self.imgs)
if __name__ == "__main__":
path = r"../data/PetImages/Dog"
dataset = CatsDogsDataset(path)
img, label = dataset[0]
print(img.shape)
plt.imshow(img)
plt.show()
运算结果如下: