PyTorch101
Torch 101: my first lesson to torch
0. Tensor
0.1 基本介绍
tensor就是张量,是pytorch用来组织数据的数据格式,好处在于其能够用在GPU上进行加速,能用于自动微分。 同时tensor使用方式与numpy非常像,甚至会和numpy共享底层内存,即从numpy到tensor不需要复制数据。
Torch对于二维矩阵常用的写法是:[Channel_out(即输出的维度),Channel_in(进入的维度)]
0.2 常用函数与操作
0.2.1 常用的函数:
torch.from_numpy
,torch.ones
,torch.rand
,torch.device
0.2.2 GPU运算
torch默认是在CPU上进行运算,通过tensor.to()
进行迁移
# Get cpu or gpu device for training.
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
0.2.2 重排(转置见下方)(reshape, squeeze,unsqueeze)
reshape() 改为指定维度,注意要对得上数据大小
squeeze() 将维度为1的维度删除
a=torch.ones(1,3,3,1)
a.squeeze().shape#[3,3]
- unsqueeze(dim) 在指定位置添加一个1维
a=torch.ones(2,3,3)
b.unsqueeze(3).shape#[2,3,3,1]
0.2.3 扩张与拼接
- repeat 给tensor对应位置的维度重复对应的次数
a=torch.ones(2,3)
a[1:,]+=1#tensor([[1., 1., 1.],[2., 2., 2.]])
a.repeat(3,2)
out:tensor ([[1., 1., 1., 1., 1., 1.], [2., 2., 2., 2., 2., 2.], [1., 1., 1., 1., 1., 1.], [2., 2., 2., 2., 2., 2.], [1., 1., 1., 1., 1., 1.], [2., 2., 2., 2., 2., 2.]])
即在a(0)重复了2次,a(1)重复了3次
- cat
对两个tensor按指定维度进行拼接,注意其输入格式
torch.cat((tensor1, tensor2),dim)
注意,维度大小不超过两个tensor的维度最大值,且两个tensor要求相同维度
a=torch.ones(2,3)
a[1:,]-=1
# tensor([[1., 1., 1.], [0., 0., 0.]])
b=torch.ones(2,3)+1
#tensor([[2., 2., 2.], [2., 2., 2.]])
torch.cat((a,b),0)
#tensor([[1., 1., 1.],
# [0., 0., 0.],
# [2., 2., 2.],
# [2., 2., 2.]])
# torch.Size([4, 3])
torch.cat((a,b),1)
# tensor([[1., 1., 1., 2., 2., 2.],
# [0., 0., 0., 2., 2., 2.]])
# torch.Size([2, 6])
- stack 与cat类似,只是操作后会增加维度1
a=torch.ones(2,3)
a[1:,]-=1
# tensor([[1., 1., 1.], [0., 0., 0.]])
b=torch.ones(2,3)+1
#tensor([[2., 2., 2.], [2., 2., 2.]])
torch.stack((a,b),0)
# tensor([[[1., 1., 1.],
# [0., 0., 0.]],
# [[2., 2., 2.],
# [2., 2., 2.]]])
# torch.Size([2, 2, 3])
torch.stack((a,b),1)
# tensor([[[1., 1., 1.],
# [2., 2., 2.]],
# [[0., 0., 0.],
# [2., 2., 2.]]])
# torch.Size([2, 2, 3])
0.3 计算
0.3.1 四则运算
import torch
from torch import tensor
a=torch.ones(2,2)
b=torch.ones(2,2)+1
print(a+b)#the same with a.add(b)
print(a-b)#the same with a.sub(b)
print(a*b)#the same with a.mul(b)
print(a/b)#the same with a.div(b)
Out: tensor([[3., 3.],[3., 3.]]) Out: tensor([[-1., -1.],[-1., -1.]]) Out: tensor([[2., 2.],[2., 2.]]) Out: tensor([[0.5, 0.5],[0.5, 0.5]])
四则运算与numpy类似,也支持广播机制
0.3.2 矩阵乘法
- 二维矩阵
matmlt
与@
print(torch.matmul(a,b))
print(a@b)
Out: tensor([[4., 4.], [4., 4.]])
- 高维矩阵 保持前两维不变,进行后两维的运算
a = torch.rand(4,3,28,64)
b = torch.rand(4,3,64,32)
torch.matmul(a,b).shape
Out: [4,3,28,32]
0.3.3 矩阵转置
- 二维矩阵
tensor.t()
该方法只支持2维及2维以下转置
a=torch.ones(2,3)
a.t()#shape:(3,2)
- 高维矩阵
tensor.transpose(dim1.dim2)
(只支持对两个维度进行转置)
a = torch.rand(4,3,28,64)
a.transpose(2,0).shape#[28, 3, 4, 64]
#也可以调取torch的方法
torch.transpose(a,2,0).shape#[28, 3, 4, 64]
#两个方法完全相同
torch.equal(torch.transpose(a,2,0),a.transpose(2,0))
tensor.permute()
支持对所有维度进行转置,但是要输入每个维度对应的转置后的顺序,且不支持torch.permute()
a = torch.rand(4,3,28,64)
a.permute(0,1,3,2).shape#[4, 3, 64, 28]
torch.permute(a,0,1,3,2)#permute() takes 2 positional arguments but 5 were given
- 总结:
对于二维数组,直接使用
tensor.t()
,高维数组只需要对其中两个维度转置时,使用tensor.transpose()
,高维数组需要对多个维度进行转置,使用tensor.permute()
1. DataLoader
在pytorch中,提供了很多现成的数据集,有声音的、图像的、文字的。pytorch中有两个模块,torch.utils.data.DataLoader
和torch.utils.data.Dataset
,这两个模块支持使用torch内置的数据,以及用户自定义的数据。
Dataset: 存储样本和它们对应的标签,类似一个元组,使用方法类似元组,个人认为设计为元组的原因是防止数据被修改。 例如Dataset[0]就是存的数据,而Dataset[1]则是数据对应的label,而len(Dataset),则是其中储存的数据/label的条数
DataLoader:在Dataset
周围包装一个可迭代对象,以便方便地访问样本(相当于能方便的访问多个dataset)。将dataset包裹后,可以用for循环去遍历
1.1 获取torch现成的data
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
1.1.1 参数说明:
FashionMNIST是datasets
中内置的数据集,可以方便导入,其中:
root
指定路径train
是否是训练集,TRUE:训练集,Flase: 测试集download
是否需要下载transformer
训练集数据使用的transformertarget_transformer
测试集数据使用的transformer
1.1.2 数据说明:
dataset中的数据是通过tuple将数据和标签组织起来的(推测好处在于可以保证数据不会被更改,tuple特性) 以training_data为例,其实本质是(data,label),对于该例子,有60000条,而每条中的datashape是(1,28,28),label则是一个int
In[0]: len(training_data)
Out[0]: 60000 有60000条数据在training_data中
获取数据,在第一个位置
In[1]: training_data[0].shape
Out[1]: torch.Size([1, 28, 28])
1.2 客制化 Creating custom dataset
通过用户自定义类,实现dataset,包含三个函数:__init__
,__len__
,__getitem
,最终效果是dataset
可以getitem
(即可以通过index获取对应位置的数据)以及获取len
1.2.1 个人case
class FigureDataset(Dataset):
def __init__(self):
self.Data=torch.tensor([[1,1],[2,2],[3,3],[4,4]])
self.Label=torch.tensor([9,8,7,6])
def __getitem__(self, index):
image=self.Data[index]
label=self.Label[index]
return image, label
def __len__(self):
return len(self.Data)
在这个Class中,数据和label均通过直接定义的形式给出,init
函数的意义就在于初始化数据以及transformer等,随后通过__getitem__
函数重构了[index]获取数据及label的方式,而__len__
函数则是简单的获取Dataset具体长度的(数据条数),通过len(data)或者len(label)均可
1.2.2 官方case
import os
import pandas as pd
from torchvision.io import read_image
from torch.utils.data import Dataset
#是Dataset的子类
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
1.2.2.1 子函数解释
读取的数据示意: img_labels:
tshirt1.jpg, 0
tshirt2.jpg, 0
......
ankleboot999.jpg, 9
- init
__init__
函数是初始化,需要包含图像文件、标签文件、以及指定对数据和标签的两种transformer格式(即对图像文件进行预处理的方式)
- len
__len__
返回数据集中的sample数目
(后续是否有执行检查数据与标签大小是否相等的内容?)
- getitem
__getitem__
函数通过idx
从数据集中读取并返回sample,idx
主要是用于在磁盘定位数据,再使用read_image
将数据变成tensor。
可以看到img_path
是一个组合结果,img_dir
是图像位置,而img_labels.iloc[idx, 0]
则是图像具体的名称.(img_labels
如下所示)
1.3 DataLoader
如上所示,通过定义的Datase,获取了单个的tuple输出,包含了数据和标签,而使用DataLoader则可以实现批处理抽取。
具体超参数可查阅:URL
常用重要超参数:
dataset
:上节介绍
batch_size
shuffle
num_workers
:用于读取数据的进程数(大数据时需要,可以有效在大数据时加速,进行预缓存)
1.3.1 个人case
loader=DataLoader(FDataset,batch_size=2)
#这里的enumerate() 函数用于将一个可遍历的数据对象,组合为一个索引序列,同时列出数据和数据下标,可以理解为给()内的可遍历数据对象多加了一个i
for i,dataset in enumerate(loader):
data,label=dataset
print("i:",i)
print("dataset:",dataset)
print("Data:",data)
print("Label:",label)
Out: i: 0 dataset: [tensor([[1, 1],[2, 2]]), tensor([9, 8])] Data: tensor([[1, 1], [2, 2]]) Label: tensor([9, 8]) i: 1 dataset: [tensor([[3, 3],[4, 4]]), tensor([7, 6])] Data: tensor([[3, 3],[4, 4]]) Label: tensor([7, 6])
显然,DataLoader将Dataset中的数据,进行了批量(根据batch_size)抽取,而形成的loader是一个迭代器,可以通过for循环获得其中的数据。
也可以直接用for循环输出,
for a in loader:
print(a)
Out: [tensor([[1, 1],[2, 2]]), tensor([9, 8])] [tensor([[3, 3],[4, 4]]), tensor([7, 6])]
2. Transform
具体超参数可查URL
transform其实就是在学CG时候学到的对图像的处理,例如scale(缩放),reflection(对称),shear(切变),rotation(旋转)等,当然,torch中transform提供的功能更多。 而由于其实tensor也可以看做图像信息(或者说图像信息,例如灰度,本身可以用tensor表征,所以torch中,不区分区别)
补充说明:文档中提到的PIL(Python Imaging Library),推测应该只是一种图像信息的表达形式,有的torch函数专用于处理该数据类型
2.1 Normalize
2.1.1 说明
torchvision.transforms.Normalize(mean, std, inplace=False)
即对原始数据进行标准差归一化(见下方公式),将数据转换为标准的高斯分布,原始数据均值为和标准差),其是逐个channel进行的,好处在于可以加速收敛,提高模型表现
其中mean
,std
分别指定对各个channel进行标准化的均值和方差,有几个channel,就是几维的
$x^*=\frac{x-mean}{std}$
2.1.2 Case
数据准备
aa=torch.ones(2,2,3)
aa[:,:,1:2]+=1
aa[:,:,2:3]+=2
aa[1,0,:]+=1
aa[0,1,:]*=2
aa[1,1,:]+=3
# tensor([[[1., 2., 3.],
# [2., 4., 6.]],
# [[2., 3., 4.],
# [4., 5., 6.]]])
以下两种实现均可
transform1=torch.nn.Sequential(
transforms.Normalize((0, 0.5), (1, 0.5))
)
transform2 = transforms.Compose(
# [transforms.ToTensor(),
[transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
b=transform1(aa)#维度吻合,成功输出
# tensor([[[ 1., 2., 3.],
# [ 2., 4., 6.]],
# [[ 3., 5., 7.],
# [ 7., 9., 11.]]])
b=transform2(aa)#维度不吻合,报错
# The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 0
先分析数据: transform1中(0, 0.5), (1, 0.5),第一个0(mean)和第二个1(std)是一组,代表对aa[0](即[[1., 2., 3.],[2., 4., 6.]])进行处理,而由公式可知,此时$x^*=x$,故输出结果与aa[0]保持一致
transform1中(0, 0.5), (1, 0.5),第一个0.5(mean)和第二个0.5(std)是一组,代表对aa[1](即[[2., 3., 4.],[4., 5., 6.]])进行处理,而由公式可知,此时$x^*=\frac{x-0.5}{0.5}$,故输出结果如上所示
同时应当注意的是,维度的问题
3. Model defination
具体超参数可查URL
在pytorch中,每个使用的NN都是用户自定义的子类(继承于父类torch .nn
中的模块nn.Module
),而用户自定义的子类本身已是一个模块,需要有其他层等组成。
在用户自定义的Model类里,需要有至少两个函数:__init__
,forward
模型最重要的是理解各个层的使用意义,以及保证每个层的输入输出维度合理
3.1 Network defination
# Define model
class NeuralNetwork(nn.Module):
def __init__(self):
#super is a function for better inheritation of subclasses from their parent
super(NeuralNetwork, self).__init__()
#flatten is reshape(N,1)
self.flatten = nn.Flatten()
#setting the neural network
self.linear_relu_stack = nn.Sequential(
#且要注意,sequential内部各个layer之间需要,隔开
#input layer, first size = h*w(for one channel)
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
#output layer, last size =shape(y)
nn.Linear(512, 10)
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
Forward function, according to the offcial website of torchURL, it’s highly suggested not to dierctly call model. forward()
, and every subclass in nn.Module
implements the operations on input data in the forward method
打印模型
model = NeuralNetwork().to(device)
print(model)
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
4. Optimizer
4.1 Scheduler
scheduler是用来调整learning rate,通常开始时选用较大的学习率,而后期随着学习的进行,逐渐减小学习率,以找到最优解,所在位置通常是完成一次训练之后,准备在测试集上测试之前,例如
for ep in range(epochs):
model.train()
t1 = default_timer()
train_l2 = 0
for x, y in train_loader:
x, y = x.cuda(), y.cuda()
optimizer.zero_grad()
out = model(x).reshape(batch_size, s, s)
out = y_normalizer.decode(out)
y = y_normalizer.decode(y)
loss = myloss(out.view(batch_size,-1), y.view(batch_size,-1))
loss.backward()
optimizer.step()
train_l2 += loss.item()
#z之前的均为训练
scheduler.step()
#准备进行测试
model.eval()
本小节笔记来源:
4.1.1 StepLR
4.1.1.1 参考资料:
官网URL
4.1.1.2 说明
该函数等间隔调整学习率,调整倍数为gamma整数倍,调整间隔为step_size。间隔单位是step(step通常是指epoch)
torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=- 1, verbose=False)
optimizer
:指定optimizer,故通常先指定optimizer再指定scheduler
step_size(int)
: 指定经过多少epoch才改变一次learning_rate
gamma(float)
: 每次调整学习率的倍数,调整后的学习率=之前的*gamma
last_epoch(int)
: 即之前的学习率来自哪一个epoco