玩命加载中 . . .

常用技巧-pytorch包


pytorch

pytorch是深度学习的常用开源工具库,基于pytorch的python代码可读性较强,在目前主流的深度学习论文中逐渐占据主导地位。pytorch中最核心的概念是张量(tensor),它是一种高效的数据结构,可以简单理解为高维数组,在深度学习中应用非常广泛;事实上,pytorch的诸多方法的处理对象,大多都是张量。

本文结合笔者目前较为浅薄的编程、实验经历,对一些实用的、经常接触的以及比较经典的库方法进行记录与总结。

python
import torch
import numpy as np

张量的创建

最朴素的创建方法:torch.tensor()

  • torch.tensor(data: Any, dtype: _dtype | None = None, device: Device = None, requires_grad: _bool = False)
  • torch.LongTensor(*args: List)

这个方法的功能和用法非常显然,我之前也经常使用,可以将指定的列表数据转换为指定的张量类型。

python
a = torch.tensor([1, 5, 3], dtype=torch.float32)
print(a)
b = torch.LongTensor([2, 9, 1])
print(b)
'''
输出:
tensor([1., 5., 3.])
tensor([2, 9, 1])
'''

最常用的创建方法:torch.rand()

  • torch.rand(size: Tuple)
  • torch.rand(*size: int)
  • torch.rand_like(input: Tensor)

之所以常用,是因为这个方法是随机创建,接收的参数是一个元组,当然也可直接传入多个整型数字作为要创建的张量维度信息。

python
a = torch.rand(2, 3)
print(a)
b = torch.rand(size=(2, 3))
print(b)
c = torch.rand_like(a)
print(c)
'''
输出:
tensor([[0.4019, 0.6014, 0.1614],
        [0.5914, 0.9730, 0.2322]])
tensor([[0.9385, 0.0407, 0.6528],
        [0.8274, 0.8461, 0.6536]])
tensor([[0.2846, 0.3948, 0.3780],
        [0.1335, 0.9324, 0.5800]])
'''

最初始的创建方法:torch.zeros()/torch.ones()

  • torch.zeros(size: Tuple) 或 torch.zeros(*size: int)
  • torch.ones(size: Tuple) 或 torch.ones(*size: int)

这两个方法分别用于创建全零张量和全一张量,当然也是接收多个整形数据参数,作为要创建的张量维度信息。这种方法多用于后续初始化或者创建掩码矩阵。

python
a = torch.zeros(2, 3)
print(a)
b = torch.ones(size=(3, 2))
print(b)
'''
输出:
tensor([[0., 0., 0.],
        [0., 0., 0.]])
tensor([[1., 1.],
        [1., 1.],
        [1., 1.]])
'''

最单位化的创建方法:torch.eye()

  • torch.eye(n: int)

这个方法可以用于创建单位矩阵。其中参数n即为方阵维度。常用于创建独热向量。

python
a = torch.eye(5)
print(a)
'''
输出:
tensor([[1., 0., 0., 0., 0.],
        [0., 1., 0., 0., 0.],
        [0., 0., 1., 0., 0.],
        [0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 1.]])
'''

基于ndarray创建tensor:torch.from_numpy()

  • torch.from_numpy(ndarray: np.ndarray)

接收np.ndarray类型的数据,将其转换为tensor并返回。

python
a = np.random.rand(2, 3)
print(a)
b = torch.from_numpy(a)
print(b)
'''
输出:
[[0.34550967 0.61193832 0.97132549]
 [0.33592651 0.08636327 0.62067825]]
tensor([[0.3455, 0.6119, 0.9713],
        [0.3359, 0.0864, 0.6207]], dtype=torch.float64)
'''

张量的变形

在深度学习中,张量的形变非常普遍,常用的方法如下。

最朴素的变形方法:reshape()

  • torch.reshape(input: Tensor, shape: Sequence)
  • tensor.reshape(shape: Sequence) 或 tensor.reshape(*size: int)

顾名思义,这个方法将指定的张量进行变形;值得注意的是,参数中各个维度允许出现-1,表示该维度长度将由其他维度推导得来。

python
a = torch.rand(2, 4)
b = torch.reshape(a, shape=(4, 2))
print(b.shape)
c = a.reshape(1, -1) # 第二维度长度可以由2*4/1=8计算得来
print(c.shape)
'''
输出:
torch.Size([4, 2])
torch.Size([1, 8])
'''

展平方法:flatten()

  • torch.flatten(input: Tensor, start_dim: int=0, end_dim: int=-1)
  • tensor.flatten(start_dim: int=0, end_dim: int=-1)

将张量的指定两个维度之间的形状展平。换言之,如果张量形状为$(d_0, d_1, \dots,d_{N-1})$,调用方法flatten(n, m)后,张量形状将变为$(d_0,d_1,\dots,d_{n-1},d_n\times d_{n+1}\times \cdots\times d_{N-m},d_{N-m+1},\dots, d_{N-1})$。

python
a = torch.rand(2, 2, 4, 3)
b = a.flatten(start_dim=1, end_dim=2)
print(b.shape)
c = a.flatten()
print(c.shape)
'''
输出:
torch.Size([2, 8, 3])
torch.Size([48])
'''

删除维度:squeeze()

  • torch.squeeze(input: Tensor, dim: int)
  • tensor.squeeze(dim: int)

将张量指定维度删除;该维度长度必须为1,否则会报错。

python
a = torch.rand(2, 1, 4)
b = a.squeeze(1)
print(b.shape)
'''
输出:
torch.Size([2, 4])
'''

增加维度:unsqueeze()

  • torch.unsqueeze(input: Tensor, dim: int)
  • tensor.unsqueeze(dim: int)

在张量指定位置新增一个维度,且该新增的维度长度为1。

python
a = torch.rand(2, 3)
b = a.unsqueeze(2)
print(b.shape)
'''
输出:
torch.Size([2, 3, 1])
'''

另一种变形方法:view()

  • tensor.view(*size: int)

这个方法同样可以对张量进行变形,它的用法和reshape几乎一致。

张量的翻转:permute()

  • torch.permute(input: Tensor, dims: _size)
  • tensor.permute(dims: _size) 或 tensor.permute(*dim: int)

对张量的各个维度进行调整。假设张量$\mathbf{Z}$形状为$(d_0,d_1,\dots,d_{N-1})$,调用permute($c_0,c_1,\dots,c_{N-1}$),则调整后的张量维度为$(d_{c_0},d_{c_1},\dots,d_{c_{N-1}})$,其中$c_0,c_1,\dots,c_{N-1}$为$0,1,\dots,N-1$的一个排列。

python
a = torch.rand(2, 4, 8, 6)
b = a.permute(0, 2, 3, 1)
print(b.shape)
c = torch.permute(a, (2, 3, 1, 0))
print(c.shape)
'''
输出:
torch.Size([2, 8, 6, 4])
torch.Size([8, 6, 4, 2])
'''

张量的转置:transpose()

  • torch.transpose(input: Tensor, dim0: int, dim1: int)
  • tensor.transpose(dim0: int, dim1: int)

将张量的两个指定维度进行转置。

python
a = torch.rand(2, 4, 8)
b = a.transpose(1, 2)
print(b.shape)
'''
torch.Size([2, 8, 4])
'''

张量的拼接

最通用的拼接方法:torch.concat()

  • torch.concat(tensors: Tuple[Tensor, …] | List[Tensor], dim: _int = 0)
  • torch.concatenate(tensors: Tuple[Tensor, …] | List[Tensor], dim: _int = 0)
python
tensor_list = []
for i in range(5):
    tensor_list.append(torch.rand(2, 3))
# 列表中包含5个形状为(2, 3)的张量
a = torch.concat(tensor_list, dim=0)
print(a.shape)
'''
输出:
torch.Size([10, 3])
'''

特殊的拼接方法:torch.vstack()/torch.hstack()

  • torch.vstack(tensors: Sequence[Tensor, …])
  • torch.hstack(tensors: Sequence[Tensor, …])

这两个方法是torch.concat()的特殊情况:前者等价于参数dim=0,后者等价于参数dim=1。

python
tensor_list = []
for i in range(5):
    tensor_list.append(torch.rand(2, 1, 3))
# 列表中包含5个形状为(2, 1, 3)的张量
a = torch.vstack(tensor_list) # 等价于torch.concat(tensor_list, dim=0)
print(a.shape)
b = torch.hstack(tensor_list) # 等价于torch.concat(tensor_list, dim=1)
print(b.shape)
'''
输出:
torch.Size([10, 1, 3])
torch.Size([2, 5, 3])
'''

张量的梯度去除与转换

张量从计算图脱离:tensor.detach()

  • tensor.detach()

这个方法将张量从计算图中脱离开来,在此后的计算中无需梯度回传。

张量的位置变换:tensor.to()/tensor.cpu()

  • tensor.to(device)
  • tensor.cpu()

将张量转移到指定位置(cpu内存或者cuda显存),而tensor.cpu()直接将张量移动到内存。

张量转换为ndarray:tensor.numpy()

  • tensor.numpy()

将张量转换为np.ndarray类型数组。由于numpy与matplotlib等库有非常方便的接口,因此该方法常用于后续的文件存储、图表绘制等等。不过,要注意该张量必须脱离计算图、位于内存,因此常用以下语句:

python
# tensor_data为张量
ndarray_data = tensor_data.detach().cpu().numpy()

张量的条件语句

条件选取:torch.where()

  • torch.where(condition: Tensor, input: Tensor, other: Tensor) -> Tensor
  • torch.where(condition: Tensor) -> List[Tensor]

这个函数类似C语言中的条件语句xxx ? xxx : xxx:对于满足condition的部分,取input中的对应元素,否则取other中的对应元素。

举个实际例子,比如要编程实现下式

而往往在深度学习中以一个批量(张量)而不是单标量作为输入,记为$\mathbf{X}\in \mathbb{R}^{B}$,故下面的代码可以实现批量运算。

python
part = torch.rand(8) - 0.5
print(part)
result = torch.where(0 > part, torch.zeros_like(part), part)
print(result)
'''
输出:
tensor([-0.4765, -0.0570,  0.3925,  0.2088,  0.1372, -0.4504, -0.4224, -0.3690])
tensor([0.0000, 0.0000, 0.3925, 0.2088, 0.1372, 0.0000, 0.0000, 0.0000])
'''

张量与求导

张量的导数:tensor.backward()/requires_grad

  • tensor.backward()

这个方法用于张量求导:将对基于该张量的需要计算导数的张量(设置requires_grad=True)全部求导数。

举个例子,我们知道对于一维向量$\mathbf{x}\in\mathbb{R}^d$,其内积的导数为

下面通过实际的例子验证一下。

python
x = torch.randn(size=(1, 6), requires_grad=True) # 一维向量,长度为6,注意要设置requires_grad=True,且这个参数默认为False,必须显式声明
h = x @ x.T # 向量内积
h.backward() # 求导
print('x = {}'.format(x))
print('h = {}'.format(h))
print('dh/dx = {}'.format(x.grad))
'''
输出:
x = tensor([[ 0.8951,  0.2311,  0.7308,  0.7671, -1.4649,  0.3229]],
       requires_grad=True)
h = tensor([[4.2275]], grad_fn=<MmBackward0>)
dh/dx = tensor([[ 1.7903,  0.4622,  1.4616,  1.5342, -2.9299,  0.6458]])
'''

torch.no_grad()

通常与with语句连用:

python
with torch.no_grad():
    # ...

with语句块内,不会再对张量进行自动求导,这样会大大节省内存。

模型的保存与加载

通常有两种方案对模型进行保存与加载。在下面的方案介绍中,Model为torch内置的模型类,model为该类的实例,path为模型存储路径。

方案一:仅保存模型参数(状态字典)

  • 模型保存:torch.save(model.state_dict(), path)
  • 模型加载:
    • model = Model(*params)
    • model.load_state_dict(torch.load(path))

方案二:保存整个模型

  • 模型保存:torch.save(model, path)
  • 模型加载:
    • import Model(一定要先导入模型类)
    • model = torch.load(path)

加载模型后,建议立即调用model.eval(),将模型设置为测试模式。


文章作者: 鹿卿
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 鹿卿 !
评论
来发评论吧~
Powered By Valine
v1.5.2