编译| 荣淮阳
来源| 人工智能前沿课程
PyTorch 在学术界和工业界的应用研究中都受到了广泛的关注。它是一个灵活的深度学习框架,使用许多有用的工具和功能来加快您的工作速度。 PyTorch 的学习曲线不太陡峭,但实现高效、干净的代码可能很困难。使用它两年多后,以下是我最喜欢的功能,我希望在我开始学习PyTorch 时就知道这些功能。
1 DatasetFolder
在学习PyTorch 时,人们做的第一件事就是实现自己的某种数据集。这是一个低级错误,没有必要浪费时间写这样的东西。通常,数据集是数据列表(或numpy 数组)或磁盘上的文件。因此,最好在磁盘上组织数据,而不是创建自定义数据集并加载奇怪的格式。
分类器最常见的数据格式之一是拥有一个目录,其中包含代表类的子文件夹和代表样本的子文件夹中的文件,如下所示。
有一个内置方法可以做到这一点。要加载此类数据集,无论数据是图像、文本文件还是其他内容,只需使用“DatasetFolder”即可。令人惊讶的是,这个类不是PyTorch 核心的一部分,而是torchvision 包的一部分。此类非常全面,允许您从文件夹中过滤文件,使用自定义代码加载它们,并动态转换原始文件。示例:
from torchvision.datasets import DatasetFolderfrom pathlib import Path# 该文件夹中有一个文本文件ds=DatasetFolder('/Users/marcin/Dev/tmp/my_text_dataset',loader=lambda path: Path(path).read_text(), extensions=( ' .txt',), #仅加载.txt文件transform=lambda text: text[:100], #仅获取前100个字符)#我们已经拥有了所需的一切len(ds) , ds.classes, ds.class_to_idx (20, [ 'novels', 'thrillers'], {'novels': 0, 'thrillers': 1}) 如果您正在处理图像,请使用基于DatasetLoader 的torchvision.datasets.ImageFolder 类,还有。 - 设置加载图像。
2 尽量少用.to\(device\),用zeros\_like/ones\_like之类的代替
我从GitHub 存储库中阅读了大量PyTorch 代码。最让我沮丧的是,几乎每个存储库都有一堆*.to(device) 行,将数据从CPU 或GPU 移动到其他地方。此类语句通常出现在许多存储库和初学者教程中。我们强烈建议实现尽可能少的操作,并利用内置的PyTorch 功能来自动执行此类操作。在任何地方使用.to(device) 通常会导致性能不佳和异常。
预期的设备类型cuda 对象但得到设备类型cpu
当然,有些情况是不可避免的,但大多数(如果不是全部)都在这里。一种情况是用全0或全1来初始化张量。当模型的输出已经在CUDA 上并且此时您需要另一个也在CUDA 上的张量时,通常会发生这种情况。您可以使用*_like 运算符。
my_output # 在任何设备上,如果是cuda,也是my_zeros cudamy_zeros=torch.zeros_like(my_output_from_model) 在内部,PyTorch 调用:
my_zeros=torch.zeros(my_output.size(), dtype=my_output.dtype,layout=my_output.layout, device=my_output.device) 所以所有设置都是正确的,代码中出现错误的可能性会更低。类似的操作还有:
torch.zeros_like()torch.ones_like()torch.rand_like()torch.randn_like()torch.randint_like()torch.empty_like()torch.full_like()
3 Register Buffer ( nn.Module.register_buffer)
建议不要在任何地方使用。到(设备)。在某些情况下,模型或损失函数可能需要在转移调用期间预设和使用的参数。例如,缩放损失的“权重”参数,或者不改变但每次都会使用的固定张量。在这种情况下,请使用nn.Module.register_buffer 方法。这告诉PyTorch 将传递的值存储在模块中并随模块移动这些值。当您初始化模块然后将其移动到GPU 时,这些值会自动移动。此外,保存模块状态也会保存缓冲区。
注册后,您可以像任何其他模块属性一样使用forward函数访问这些值。
torch import nnimport torchclass ModuleWithCustomValues(nn.Module): def __init__(self,weights, alpha): super().__init__() self.register_buffer('weights', torch.tensor(weights)) self.register_buffer('alpha) ', torch.tensor(alpha)) defforward(self, x): return x * self.weights + self.alpham=ModuleWithCustomValues(weights=[1.0, 2.0], alpha=1e-4)m(torch.tensor( [1.23, 4.56]))tensor([1.2301, 9.1201])
4 Built-in Identity()
使用迁移学习时,您可能需要用1:1 个映射替换某些层。为了达到这个目的,你可以使用nn.Module来只返回Enter。价值。 PyTorch 包含此类。
例如,假设您想在分类层之前从预训练的ResNet50 中获取图像表示。方法如下:
从torchvision.models 导入resnet50model=resnet50(pretrained=True)model.fc=nn.Identity()last_layer_output=model(torch.rand((1, 3, 224, 224)))last_layer_output.shapetorch.Size([1, 2048])
5 Pairwise distances: torch.cdist
下次当您遇到计算两个张量之间的欧几里德距离(或一般为:p 范数)的问题时,请记住torch.cdist。它就是这样做的,并且在使用欧几里得距离时自动使用矩阵乘法,从而提高了性能。
点1=torch.tensor([[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]])点2=torch.tensor([[0.0, 0.0], [-1.0, -1.0], [-2.0 , -2.0], [-3.0, -3.0]]) # 批量equaltorch.cdist(points1, Points2, p=2.0)tensor([[0.0000, 1.4142, 2.8284, 4.2426], [1.4142, 2.8284, 4.2426, 5.6569] ,[2.8284, 4.2426, 5.6569, 7.0711]])无论有没有矩阵乘法,性能都得到了提高,在我的带有mm 的机器上速度提高了一倍多。
%%timeitpoints1=torch.rand((512, 2))points2=torch.rand((512, 2))torch.cdist(points1, Points2, p=2.0,compute_mode='donot_use_mm_for_euclid_dist') 每个循环867s142s ( 7 次运行的平均值 标准差,每次运行1000 个循环)
%%timeitpoints1=torch.rand((512, 2))points2=torch.rand((512, 2))torch.cdist(points1, Points2, p=2.0) 每个循环417s52.9s (meanstd.dev) ) 7 次运行,每次1000 次循环)
6 Cosine similarity: F.cosine_similarity
与上一点类似,计算欧氏距离并不总是必要的。使用向量时,通常选择余弦相似度作为度量。 PyTorch 还内置了余弦相似度的实现。
将torch.nn.function 导入为Fvector1=torch.tensor([0.0, 1.0])vector2=torch.tensor([0.05, 1.0])print(F.cosine_similarity(vector1, Vector2, dim=0))vector3=torch I将要。张量([0.0, -1.0])print(F.cosine_similarity(vector1, Vector3, dim=0))张量(0.9988)张量(-1.)
PyTorch中批量计算余弦距离
import torch.nn.function as Fbatch_of_vectors=torch.rand( (4, 64))similarity_matrix=F.cosine_similarity(batch_of_vectors.unsqueeze(1),batch_of_vectors.unsqueeze(0), dim=2)similarity_matrixtensor([[1.0000, 0.6922, 0.6480, 0.6789], [0.6922, 1 .000 0,
7 归一化向量: F.normalize
最后一点是向量和距离之间的关系仍然是松散的,但归一化是松散的。我通常会像这样调整向量的大小:提高计算稳定性。最常用的归一化是L2,它可以在PyTorch中应用如下:
Vector=torch.tensor([99.0, -512.0, 123.0, 0.1, 6.66])normalized_vector=F.normalize(vector, p=2.0, dim=0)normalized_vectortensor([ 1.8476e-01, -9.5552e-01, 2.2955 e-01, 1.8662e-04, 1.2429e-02]) 在PyTorch 中执行标准化的旧方法是:
向量=torch.tensor([99.0, -512.0, 123.0, 0.1, 6.66])normalized_vector=向量/torch.norm(向量, p=2.0)normalized_vectortensor([ 1.8476e-01, -9.5552e-01, 2.2955e- 01, 1.8662e-04, 1.2429e-02])
在PyTorch中批量进行L2归一化
patch_of_vectors=torch.rand((4, 64))normalized_batch_of_vectors=F.normalize(batch_of_vectors, p=2.0, dim=1)normalized_batch_of_vectors.shape, 火炬。 Norm(normalized_batch_of_vectors, dim=1) # 所有向量的长度都是1.0(torch.Size([4, 64]), tensor([1.0000, 1.0000, 1.0000, 1.0000]))
8 线性层 + 分块技巧 (torch.chunk)
这是我最近的一个创意我发现的伎俩。假设您要将输入映射到N 个不同的线性投影。为此,请创建N nn.Linear。或者,您可以创建单个线性层,执行前向传递,并将输出拆分为N 个块。这种方法通常可以提高性能,因此这是一个值得牢记的技巧。
d=1024batch=torch.rand((8, d))layers=nn.Linear(d, 128,bias=False), nn.Linear(d, 128,bias=False), nn.Linear(d, 128,偏差=假)one_layer=nn.Linear(d,128 * 3,偏差=假)%%timeito1=层[0](批)o2=层[1](批)o3=层[2](批)289每个循环s 30.8 s(7 次运行,每次1000 个循环的平均值 标准差)
%%timeito1, o2, o3=torch.chunk(one_layer(batch), 3, dim=1) 每个循环202 s 8.09 s(7 次运行,每次1000 次循环的平均值 标准差)
9 Masked select (torch.masked_select)
有时您需要仅对输入张量的一部分执行计算。例如,假设我们只想计算满足某些条件的张量的损失。您可以使用torch.masked_select 来执行此操作。请注意,如果您想要渐变,也可以使用此操作。
数据=torch.rand((3, 3)).requires_grad_()print(data)mask=数据data.mean()print(mask)torch.masked_select(data,mask)tensor([[0.0582, 0.7170, 0.7713] , [0.9458, 0.2597, 0.6711], [0.2828, 0.2232, 0.1981]], require_grad=True)张量([[False, True, True], [True, False, True], [False, False, False]])通过使用掩码作为输入张量的“索引器”可以实现类似的行为:tensor([0.7170, 0.7713, 0.9458, 0.6711], grad_fn=MaskedSelectBackward)
直接在tensor上应用mask
。
data[mask]tensor([0.7170, 0.7713, 0.9458, 0.6711], grad_fn=IndexBackward) 在某些情况下,理想的解决方案是将mask 中的所有false 值填充为0。你可以这样做:
数据*maskTensor([[[[0.0000,0.7170,0.7713],[0.9458,0.0000,0.6711],[0.0000,0.0000,0.0000,0.0000]]如果条件为真,则该函数可用于从第一个张量获取元素;如果条件为假,则可用于从第二个张量获取元素。
x=torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], require_grad=True)y=-xcondition_or_mask=x=3.0torch.where(condition_or_mask, x, y)tensor([ 1. 2. 3 . -4. -5.], grad_fn=SWhereBackward)
10 使用 torch.where来对tensors加条件
此函数的一个示例用法是用指定位置处的另一个张量的值填充一个张量。一维张量更容易理解,因此我们将首先演示它们,然后再转向更高级的示例。
数据=torch.tensor([1, 2, 3, 4, 5]) 索引=torch.tensor([0, 1]) 值=torch.tensor([-1, -2, -3, -4], - 5])data.scatter(0,index,values)tensor([-1, -2, 3, 4, 5]) 上面的例子很简单,但是把index改为Index=torch我们看看是不是这样。tensor( [0, 1, 4]) 会发生什么:
数据=torch.tensor([1, 2, 3, 4, 5]) 索引=torch.tensor([0, 1, 4]) 值=torch.tensor([-1, -2, -3, -4 , -5])data.scatter(0,index,values)tensor([-1, -2, 3, 4, -3]) 为什么最后一个值是-3?这是违反直觉的。这就是PyTorch分散函数的核心思想。索引变量指示数据张量的第i 个值应放置在值张量中的位置。我希望下面这个操作的简单Python版本会更清晰。
data_orig=torch.tensor([1, 2, 3, 4, 5])index=torch.tensor([0, 1, 4])values=torch.tensor([-1, -2, -3, -4 , -5])scattered=data_orig.scatter(0, index, value)data=data_orig.clone()for idx_in_values, where_to_put_the_value in enumerate(index): What_value_to_put=value[idx_in_values] data[where_to_put_the_value]=What_value_to_putdata, 分散(tensor ([-1, -2, 3, 4, -3]), tensor([-1, -2, 3, 4, -3]))
11 在给定的位置给张量填入值(Tensor.scatter)
索引的形状有关一定要记住那。值的形状和索引值对应于数据中的位置。
data=torch.zeros((4, 4)).float()index=torch.tensor([ [0, 1], [2, 3], [0, 3], [1, 2]])values=torch.arange(1, 9).float().view(4, 2)values, data.scatter(1,index,values)(tensor([[1. 2.], [3. 4 .] , [5. 6.], [7. 8.]]),张量([[1. 2. 0. 0.], [0. 0. 3. 4. ], [5. 0. 0. 6.], [0. 7. 8. 0.]]))
2D数据的PyTorch scatter例子
当我学习PyTorch 时,我很惊讶地发现这个确实存在塔。可以传递调整大小的图像(或任何中间张量)并保持梯度流动。当使用CNN 和GAN 时,这种方法特别有用。
# 图片来自https://commons.wikimedia.org/wiki/File:A_Female_British_Shorthair_at_the_age_of_20_months.jpgimg=Image.open('./cat.jpg')img to_pil_image(
F.interpolate(to_tensor(img).unsqueeze(0),%20#%20批量大小为1
模式='双线性',
比例因子=2.0,
align_corners=False).squeeze(0)%20#%20删除批量维度
)
%20%20查看如何存储梯度流。
F.interpolate(to_tensor(img).unsqueeze(0).requires_grad_(),mode='bicubic',scale_factor=2.0,align_corners=False)tensor([[[[0.9216,%200.9216,%200.9216,%200.8361,%200.8272,%200.8219],%20[0.9214,%200.9214,%200.9214,%200.8361,%200.8272,%200.8219],%20[0.9212,%200.9212,%200.9212,%200.8361,%200.8272,%200.8219],%209098,%208,%200.9098,%200.3592,%200.3486,%200.3421],%20[0.9098,%200.9098,%200.9098,%200.3566,%200.3463,%200.3400],%20[0.9098,%200.9098,%200.9098,%200.%203550,3449,%200.338%207%20]%20.%200.5380,%200.5292,%200.5238],%20[0.6196,%200.6196,%200.6196,%200.3631,%200.3525,%200.3461],%20[0.6196,%200.6196,%200.6196,%200.3605,%200.343%209%20],%20[%200%20.6196,%200.6196,%200.6196,%200.3589,%200.3488,%200.3426]],%20[[0.4353,%200.4353,%200.4353,%200.1913,%200.1835,%200.1787],%20[0.4352,%202,%204352,%200.1913,%200.1835,%200.1787],%20[0.4349,%200.4349,%200.4349,%200.1913,%200.1835,%200.1787],%20[0.3333,%200.3333,%200.3333,%200.3827,%20[%200.3333%20,%200%20.3333%20,%200.3333,%200.3801,%200.3698,%200.3635],%20[0.3333,%200.3333,%200.3333,%200.3785,%200.3684,%200.3622]]],grad_fn=UpsampleBicubic2D向后1)%203%20010%20使用PyTorch%20%20和torchvision%20%20时,您不需要使用matplotlib%20%20或任何外部库来复制和粘贴代码来显示图像网格。只需使用torchvision.utils.make_grid%20%20即可。
from%20%20torchvision.utils%20%20import%20%20make_gridfrom%20%20torchvision.transforms.function%20%20import%20%20to_tensor,%20to_pil_imagefrom%20%20PIL%20%20import%20%20Imageimg=Image.open('./cat.jpg')to_pil_image( make_grid( [to_tensor(i) for i in [img, img, img] ], nrow=2, # 单行图像数量padding=5 # 'Frame' 大小)) 原文链接:https://zablo.net/blog/post/pytorch-13-features-you-Should-know/
版权声明:本文转载于网络,版权归作者所有。如有侵权,请联系本站编辑删除。