Pytorch 官方文档教程整理 (一)
对应官方的 Instuction to Pytorch 前半部分
运行的Python版本:3.9.12
所使用的库:
numpy 1.23.0 pandas 1.4.3 pip 21.2.4 tensorboard 2.9.1 torch 1.12.0+cu116 torchaudio 0.12.0+cu116 torchvision 0.13.0+cu116
注:后面涉及的链接均不再附上 都可以在官方文档找到
Chapter 1: Tensor
大多数机器学习工作涉及:数据、模型建立、模型参数优化、保存模型
本教程将根据流程的pytorch实现来进行教学
学习者应有一定的Python和深度学习基础
- 数据集:FashionMNIST
- 分类:T-shirt, Trouser, Pullover, Dress, Coat, Sandal, Shirt, Sneaker, Bag, Ankle boot
tensor
张量是一种特殊的数据结构,类似于NumPy的ndarrays,与NumPy数组通常可以共享相同的底层内存,从而消除了复制数据的需要。
张量提供了GPU或者其他硬件的加速、从而也对自动微分(automatic differentiation)进行了优化
import torch
import numpy as np
# 1.from list
data = [[1,2],[3,4]]
x_data = torch.tensor(data)
print(x_data)
# 2.from numpy array
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print('\n', x_np)
# 3.from other tensor 即由其他的tensor提供shape
x_ones = torch.ones_like(x_data)
x_rand = torch.rand_like(x_data, dtype=torch.float) # 指定数据类型
print(f"\nOnes Tensor: \n {x_ones} \n")
print(f"Random Tensor: \n {x_rand} \n")
tensor([[1, 2],
[3, 4]])
tensor([[1, 2],
[3, 4]])
Ones Tensor:
tensor([[1, 1],
[1, 1]])
Random Tensor:
tensor([[0.7730, 0.6609],
[0.8345, 0.5773]])
# 4.with random or constant values 和第三种类似
shape = (2,3,) # shape表示张量的维度 此处2为第零维的长度 3为第一维的长度 即高2宽3
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
Random Tensor:
tensor([[0.9749, 0.8934, 0.1137],
[0.1913, 0.0333, 0.6570]])
Ones Tensor:
tensor([[1., 1., 1.],
[1., 1., 1.]])
Zeros Tensor:
tensor([[0., 0., 0.],
[0., 0., 0.]])
张量的性质包括:shape(形状), datatype(数据类型), device(运行设备cpu,gpu)
tensor = torch.rand(3,4) # 初始化方式与第四种类似
print(f"Shape of tensor: {tensor.shape}") # (3, 4)
print(f"Datatype of tensor: {tensor.dtype}") # torch.float32
print(f"Device tensor is stored on: {tensor.device}") # cpu
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu
对于张量的操作有一百多种,比如:算术、线性代数、矩阵操作(转置、索引、切片)、采样等等(更多介绍见官网)
每一种操作都可以在GPU完成
默认张量的操作在CPU完成,我们需要用.to
方法将张量转移到GPU(但是需要注意的是在不同的device之间转移大型张量是很浪费时间和资源的)
# We move our tensor to the GPU if available
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Using device {device}!')
tensor = tensor.to(device) # tensor 为先前创建的张量
print(tensor.device)
Using device cuda!
cuda:0
- 首先做一些与列表类似操作
# shape = (2,3,) # shape表示张量的维度 此处2为第零维的长度 3为第一维的长度 即高2宽3
# rand_tensor = torch.rand(shape)
tensor = torch.ones(4, 4) # 初始化方式与上面的方式相同
print(tensor)
# 切片
print(f"First row: {tensor[0]}") # 第一行
print(f"First column: {tensor[:, 0]}") # 第一列
print(f"Last column: {tensor[..., -1]}") # 最后一列
tensor[:,1] = 0 # 第二列清零
print(tensor)
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
- 张量链接操作 (
torch.cat
方法将指定的张量沿着指定的维度连接起来)
t1 = torch.cat([tensor, tensor, tensor], dim=1) # 沿着row的方向链接
print(t1)
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
- 算术操作 (
@
andmatmul
矩阵乘法,*
andmul
对应元素相乘)
print(tensor)
# 矩阵的乘法 y1 y2 y3结果一样
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)
# 另一种输出形式
y3 = torch.rand_like(y1)
torch.matmul(tensor, tensor.T, out=y3)
print(y3)
# 对应元素相乘 z1 z2 z3结果一样
z1 = tensor * tensor
z2 = tensor.mul(tensor)
z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)
print(z1)
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
tensor([[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.]])
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
- 有单个元素的张量 (
.item()
方法可以将它转换成一个scalar)
计算正确率的时候会用到
agg = tensor.sum() # 全部维度求和
agg_item = agg.item() # 转化成一个常量
print(agg_item, type(agg_item))
12.0 <class 'float'>
-
In-place operations(忘记怎么翻译了)
在方法后面加上一个
_
后缀就可以改变张量自身的值
- In-place operations 节约了内存但是会导致导数丢失没办法进行方向传播之类的操作 要注意使用
print(f"{tensor} \n")
tensor.add_(5)
print(tensor)
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
tensor([[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.]])
在CPU上的张量可以和numpy数组公用一个底层内存
# Tensor to numpy array
# 使用.numpy() 方法
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
# 两者都改变
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
# numpy array to tensor
# 使用.from_array方法 同上面的初始化第二种方法
n = np.ones(5)
t = torch.from_numpy(n)
# 两者都改变
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]
Chapter 2: DATASETS & DATALOADERS
为了将数据代码和模型代码分离,获得更好的可读性和模块化pytorch提供了torch.utils.data.DataLoader
和torch.utils.data.Dataset
以便于预处理好数据、便于使用时便捷的访问数据
- Dataset 将所有的数据和其对应的标签都整合在一起
- Dataloader进一步将Dataset中的数据整理打包为一个一个batch的形式 可以迭代的使用、访问
pytorch本身提供了许多音频、图像、文本等等数据集可以直接下载成一个Dataset对象
下面以Fashion-Mnist数据集为例来演示
该数据包含60000个训练样本,10000个测试样本
每个样本由一个 28x28 的灰度图像和一个对应标签组成
下面通过以下参数来加载一个数据集
root
下载路径train
是否为训练集(测试\训练)download
是否下载 (未下载则下载 已下载则直接加载transform
andtarget_transform
对数据、标签进行transform
import torch
from torch.utils.data import Dataset
from torchvision import datasets # 用于下载官方的数据集
from torchvision.transforms import ToTensor # transform
import matplotlib.pyplot as plt # 画图
training_data = datasets.FashionMNIST(
root="data", # 在当前文件夹新建一个data文件夹存储
train=True,
download=True,
transform=ToTensor()
) # 加载训练集
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
) # 加载测试集
我们可以对dataset进行索引
然后通过matplolib进行样本可视化
labels_map = {
0: "T-Shirt",
1: "Trouser",
2: "Pullover",
3: "Dress",
4: "Coat",
5: "Sandal",
6: "Shirt",
7: "Sneaker",
8: "Bag",
9: "Ankle Boot",
} # 该数据集的内置标签
# 下面是画图
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
sample_idx = torch.randint(len(training_data), size=(1,)).item()
img, label = training_data[sample_idx] # 获取数据和标签
figure.add_subplot(rows, cols, i)
plt.title(labels_map[label])
plt.axis("off")
plt.imshow(img.squeeze(), cmap="gray")
plt.show()
创建自己的数据集
建立自己的数据集需要实现 __init__
__len__
__getitem__
方法
(假设)FashionMNIST的图像样例存储在img_dir
他们对应的标签存储在annotations_file
下面来看看这些方法内部的实现过程
import os
import pandas as pd
from torchvision.io import read_image
__ init __
标签文件格式如下
shirt1.jpg, 0
tshirt2.jpg, 0
…
ankleboot999.jpg, 9
# 通常包含图像文件、标签文件、transforms等等
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
__ len __
# 返回有多少数据样本
def __len__(self):
return len(self.img_labels)
__ getitem __
根据文件路径和参数idx
返回图片样本和标签
如果有transform那么进行transform后返回
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
完整实现如下
import os
import pandas as pd
from torchvision.io import read_image
class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
def __len__(self):
return len(self.img_labels)
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
Dataset一次只返回一个样本
我们在训练时一次update往往需要一个minibatch个样本 将全部数据都update一遍就称为一个epoch
为了避免过拟合每个epoch都通过dataloader进行reshuffle
同时也采用Python的多线程来加速数据检索
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
dataloader每次迭代都会返回minibatch个样本特征和其对应的label
因为shuffle=true所以每次遍历完所有的数据之后就会将顺序重新打乱
# 因为可迭代所以使用iter和next方法进行一次迭代 并且移动到下一个迭代的目标
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
print(f"img shape: {img.size()}")
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}") # 数字label对应的物品种类见上面的label_map
Feature batch shape: torch.Size([64, 1, 28, 28])
Labels batch shape: torch.Size([64])
img shape: torch.Size([28, 28])
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BGiNSVuX-1658890106796)(output_51_1.png)]
Label: 8
Chapter 3:Transforms
你使用的或者下载来的数据样本和其标签不一定满足你训练模型所需要的格式
我们可以使用transforms来对他们进行一些变化
所有的Torchvision的dataset都提供了transform
和 target_transforms
分别对特征和标签进行变化
torchvision.transforms
提供了一些变化方法
FashionMNIST 的特征是PIL image 标签是整数格式
为了适应训练模型,使用ToTensor
方法和Lamda
方法进行转化
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
# 沿着维度0将索引为y的位置置1 (沿着维度0是因为初始化时默认为列向量)
target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)
ToTensor 将PIL image图像或者numpy数组转化成FloatTensor型数据
并且将图片每个像素限制在[0,1]范围内
用户自定义一个转换方式
上文中将整数转换成了独热码张量
target_transform = Lambda(lambda y: torch.zeros(
10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1))