PyTorch 2.2 中文官方教程(六)(1)https://developer.aliyun.com/article/1482505
使用 Better Transformer 进行快速 Transformer 推理
原文:
pytorch.org/tutorials/beginner/bettertransformer_tutorial.html
译者:飞龙
本教程将 Better Transformer(BT)作为 PyTorch 1.12 版本的一部分进行介绍。在本教程中,我们展示了如何使用 Better Transformer 进行 torchtext 的生产推理。Better Transformer 是一个生产就绪的快速路径,可加速在 CPU 和 GPU 上部署具有高性能的 Transformer 模型。快速路径功能对基于 PyTorch 核心nn.module
或 torchtext 的模型透明地工作。
可以通过 Better Transformer 快速路径执行加速的模型是使用以下 PyTorch 核心torch.nn.module
类TransformerEncoder
、TransformerEncoderLayer
和MultiHeadAttention
的模型。此外,torchtext 已更新为使用核心库模块以从快速路径加速中受益。 (未来可能会启用其他模块以进行快速路径执行。)
Better Transformer 提供了两种加速类型:
- 为 CPU 和 GPU 实现的原生多头注意力(MHA)以提高整体执行效率。
- 利用 NLP 推理中的稀疏性。由于输入长度可变,输入标记可能包含大量填充标记,处理时可以跳过,从而实现显著加速。
快速路径执行受一些标准的限制。最重要的是,模型必须在推理模式下执行,并且在不收集梯度磁带信息的输入张量上运行(例如,使用 torch.no_grad 运行)。
要在 Google Colab 中查看此示例,请点击这里。
本教程中的 Better Transformer 功能
- 加载预训练模型(在 PyTorch 版本 1.12 之前创建,没有 Better Transformer)
- 在 CPU 上运行和基准推理,使用 BT 快速路径(仅原生 MHA)
- 在(可配置的)设备上运行和基准推理,使用 BT 快速路径(仅原生 MHA)
- 启用稀疏性支持
- 在(可配置的)设备上运行和基准推理,使用 BT 快速路径(原生 MHA + 稀疏性)
附加信息
有关 Better Transformer 的更多信息可以在 PyTorch.Org 博客A Better Transformer for Fast Transformer Inference中找到。
- 设置
1.1 加载预训练模型
我们通过按照torchtext.models中的说明从预定义的 torchtext 模型中下载 XLM-R 模型。我们还将设备设置为在加速器测试上执行。(根据需要启用 GPU 执行环境。)
import torch import torch.nn as nn print(f"torch version: {torch.__version__}") DEVICE = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") print(f"torch cuda available: {torch.cuda.is_available()}") import torch, torchtext from torchtext.models import RobertaClassificationHead from torchtext.functional import to_tensor xlmr_large = torchtext.models.XLMR_LARGE_ENCODER classifier_head = torchtext.models.RobertaClassificationHead(num_classes=2, input_dim = 1024) model = xlmr_large.get_model(head=classifier_head) transform = xlmr_large.transform()
1.2 数据集设置
我们设置了两种类型的输入:一个小输入批次和一个带有稀疏性的大输入批次。
small_input_batch = [ "Hello world", "How are you!" ] big_input_batch = [ "Hello world", "How are you!", """`Well, Prince, so Genoa and Lucca are now just family estates of the Buonapartes. But I warn you, if you don't tell me that this means war, if you still try to defend the infamies and horrors perpetrated by that Antichrist- I really believe he is Antichrist- I will have nothing more to do with you and you are no longer my friend, no longer my 'faithful slave,' as you call yourself! But how do you do? I see I have frightened you- sit down and tell me all the news.` It was in July, 1805, and the speaker was the well-known Anna Pavlovna Scherer, maid of honor and favorite of the Empress Marya Fedorovna. With these words she greeted Prince Vasili Kuragin, a man of high rank and importance, who was the first to arrive at her reception. Anna Pavlovna had had a cough for some days. She was, as she said, suffering from la grippe; grippe being then a new word in St. Petersburg, used only by the elite.""" ]
接下来,我们选择小批量或大批量输入,预处理输入并测试模型。
input_batch=big_input_batch model_input = to_tensor(transform(input_batch), padding_value=1) output = model(model_input) output.shape
最后,我们设置基准迭代次数:
ITERATIONS=10
- 执行
在 CPU 上运行和基准推理,使用 BT 快速路径(仅原生 MHA)
我们在 CPU 上运行模型,并收集性能信息:
- 第一次运行使用传统(“慢速路径”)执行。
- 第二次运行通过将模型置于推理模式并使用 model.eval()启用 BT 快速路径执行,并使用 torch.no_grad()禁用梯度收集。
当模型在 CPU 上执行时,您会看到改进(其幅度取决于 CPU 模型)。请注意,快速路径概要显示大部分执行时间在本地 TransformerEncoderLayer 实现 aten::_transformer_encoder_layer_fwd 中。
print("slow path:") print("==========") with torch.autograd.profiler.profile(use_cuda=False) as prof: for i in range(ITERATIONS): output = model(model_input) print(prof) model.eval() print("fast path:") print("==========") with torch.autograd.profiler.profile(use_cuda=False) as prof: with torch.no_grad(): for i in range(ITERATIONS): output = model(model_input) print(prof)
在(可配置的)设备上运行和基准推理,使用 BT 快速路径(仅原生 MHA)
我们检查 BT 的稀疏性设置:
model.encoder.transformer.layers.enable_nested_tensor
我们禁用了 BT 的稀疏性:
model.encoder.transformer.layers.enable_nested_tensor=False
我们在设备上运行模型,并收集用于设备上原生 MHA 执行的性能信息:
- 第一次运行使用传统的(“慢路径”)执行。
- 第二次运行通过将模型置于推理模式并使用 model.eval()禁用梯度收集来启用 BT 快速执行路径。
在 GPU 上执行时,您应该看到显着的加速,特别是对于小输入批处理设置:
model.to(DEVICE) model_input = model_input.to(DEVICE) print("slow path:") print("==========") with torch.autograd.profiler.profile(use_cuda=True) as prof: for i in range(ITERATIONS): output = model(model_input) print(prof) model.eval() print("fast path:") print("==========") with torch.autograd.profiler.profile(use_cuda=True) as prof: with torch.no_grad(): for i in range(ITERATIONS): output = model(model_input) print(prof)
2.3 在(可配置的)DEVICE 上运行和对比推理,包括 BT 快速执行路径和不包括 BT 快速执行路径(原生 MHA + 稀疏性)
我们启用稀疏性支持:
model.encoder.transformer.layers.enable_nested_tensor = True
我们在 DEVICE 上运行模型,并收集原生 MHA 和稀疏性支持在 DEVICE 上的执行的概要信息:
- 第一次运行使用传统的(“慢路径”)执行。
- 第二次运行通过将模型置于推理模式并使用 model.eval()禁用梯度收集来启用 BT 快速执行路径。
在 GPU 上执行时,您应该看到显着的加速,特别是对于包含稀疏性的大输入批处理设置:
model.to(DEVICE) model_input = model_input.to(DEVICE) print("slow path:") print("==========") with torch.autograd.profiler.profile(use_cuda=True) as prof: for i in range(ITERATIONS): output = model(model_input) print(prof) model.eval() print("fast path:") print("==========") with torch.autograd.profiler.profile(use_cuda=True) as prof: with torch.no_grad(): for i in range(ITERATIONS): output = model(model_input) print(prof)
总结
在本教程中,我们介绍了在 torchtext 中使用 PyTorch 核心 Better Transformer 支持 Transformer 编码器模型的快速变压器推理。我们演示了在 BT 快速执行路径可用之前训练的模型中使用 Better Transformer 的方法。我们演示并对比了 BT 快速执行路径模式、原生 MHA 执行和 BT 稀疏性加速的使用。
从头开始的自然语言处理:使用字符级 RNN 对名称进行分类
原文:
pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html
译者:飞龙
注意
点击这里下载完整的示例代码
我们将构建和训练一个基本的字符级循环神经网络(RNN)来对单词进行分类。本教程以及其他两个“从头开始”的自然语言处理(NLP)教程 NLP From Scratch: Generating Names with a Character-Level RNN 和 NLP From Scratch: Translation with a Sequence to Sequence Network and Attention,展示了如何预处理数据以建模 NLP。特别是这些教程不使用 torchtext 的许多便利函数,因此您可以看到如何在低级别上处理 NLP 以建模 NLP。
字符级 RNN 将单词作为一系列字符读取 - 在每一步输出一个预测和“隐藏状态”,将其先前的隐藏状态馈送到每个下一步。我们将最终预测视为输出,即单词属于哪个类别。
具体来说,我们将在来自 18 种语言的几千个姓氏上进行训练,并根据拼写预测名称来自哪种语言:
$ python predict.py Hinton (-0.47) Scottish (-1.52) English (-3.57) Irish $ python predict.py Schmidhuber (-0.19) German (-2.48) Czech (-2.68) Dutch
推荐准备工作
在开始本教程之前,建议您已经安装了 PyTorch,并对 Python 编程语言和张量有基本的了解:
pytorch.org/
获取安装说明- 使用 PyTorch 进行深度学习:60 分钟入门以开始使用 PyTorch 并学习张量的基础知识
- 通过示例学习 PyTorch 提供广泛和深入的概述
- 如果您以前是 Lua Torch 用户,请参阅 PyTorch for Former Torch Users
了解 RNN 以及它们的工作原理也会很有用:
- 循环神经网络的不合理有效性展示了一堆现实生活中的例子
- 理解 LSTM 网络专门讨论 LSTMs,但也对 RNNs 有启发性
准备数据
注意
从这里下载数据并将其解压缩到当前目录。
data/names
目录中包含 18 个名为[Language].txt
的文本文件。每个文件包含一堆名称,每行一个名称,大多数是罗马化的(但我们仍然需要从 Unicode 转换为 ASCII)。
我们最终会得到一个字典,其中包含每种语言的名称列表,{language: [names ...]}
。通用变量“category”和“line”(在我们的案例中用于语言和名称)用于以后的可扩展性。
from io import open import glob import os def findFiles(path): return glob.glob(path) print(findFiles('data/names/*.txt')) import unicodedata import string all_letters = string.ascii_letters + " .,;'" n_letters = len(all_letters) # Turn a Unicode string to plain ASCII, thanks to https://stackoverflow.com/a/518232/2809427 def unicodeToAscii(s): return ''.join( c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn' and c in all_letters ) print(unicodeToAscii('Ślusàrski')) # Build the category_lines dictionary, a list of names per language category_lines = {} all_categories = [] # Read a file and split into lines def readLines(filename): lines = open(filename, encoding='utf-8').read().strip().split('\n') return [unicodeToAscii(line) for line in lines] for filename in findFiles('data/names/*.txt'): category = os.path.splitext(os.path.basename(filename))[0] all_categories.append(category) lines = readLines(filename) category_lines[category] = lines n_categories = len(all_categories)
['data/names/Arabic.txt', 'data/names/Chinese.txt', 'data/names/Czech.txt', 'data/names/Dutch.txt', 'data/names/English.txt', 'data/names/French.txt', 'data/names/German.txt', 'data/names/Greek.txt', 'data/names/Irish.txt', 'data/names/Italian.txt', 'data/names/Japanese.txt', 'data/names/Korean.txt', 'data/names/Polish.txt', 'data/names/Portuguese.txt', 'data/names/Russian.txt', 'data/names/Scottish.txt', 'data/names/Spanish.txt', 'data/names/Vietnamese.txt'] Slusarski
现在我们有category_lines
,一个将每个类别(语言)映射到一系列行(名称)的字典。我们还跟踪了all_categories
(只是一个语言列表)和n_categories
以供以后参考。
print(category_lines['Italian'][:5])
['Abandonato', 'Abatangelo', 'Abatantuono', 'Abate', 'Abategiovanni']
将名称转换为张量
现在我们已经组织好所有的名称,我们需要将它们转换为张量以便使用。
为了表示单个字母,我们使用大小为<1 x n_letters>
的“one-hot 向量”。一个 one-hot 向量除了当前字母的索引处为 1 之外,其他位置都填充为 0,例如,"b" = <0 1 0 0 0 ...>
。
为了构成一个单词,我们将其中的一堆连接成一个 2D 矩阵。
额外的 1 维是因为 PyTorch 假设一切都是批处理 - 我们这里只是使用批处理大小为 1。
import torch # Find letter index from all_letters, e.g. "a" = 0 def letterToIndex(letter): return all_letters.find(letter) # Just for demonstration, turn a letter into a <1 x n_letters> Tensor def letterToTensor(letter): tensor = torch.zeros(1, n_letters) tensor[0][letterToIndex(letter)] = 1 return tensor # Turn a line into a <line_length x 1 x n_letters>, # or an array of one-hot letter vectors def lineToTensor(line): tensor = torch.zeros(len(line), 1, n_letters) for li, letter in enumerate(line): tensor[li][0][letterToIndex(letter)] = 1 return tensor print(letterToTensor('J')) print(lineToTensor('Jones').size())
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., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) torch.Size([5, 1, 57])
创建网络
在自动求导之前,在 Torch 中创建一个循环神经网络涉及在几个时间步上克隆层的参数。这些层保存了隐藏状态和梯度,现在完全由图本身处理。这意味着您可以以非常“纯粹”的方式实现 RNN,就像常规的前馈层一样。
这个 RNN 模块(主要是从PyTorch for Torch 用户教程中复制的)只是在输入和隐藏状态上操作的 2 个线性层,输出后是一个LogSoftmax
层。
import torch.nn as nn class RNN(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(RNN, self).__init__() self.hidden_size = hidden_size self.i2h = nn.Linear(input_size + hidden_size, hidden_size) self.h2o = nn.Linear(hidden_size, output_size) self.softmax = nn.LogSoftmax(dim=1) def forward(self, input, hidden): combined = torch.cat((input, hidden), 1) hidden = self.i2h(combined) output = self.h2o(hidden) output = self.softmax(output) return output, hidden def initHidden(self): return torch.zeros(1, self.hidden_size) n_hidden = 128 rnn = RNN(n_letters, n_hidden, n_categories)
要运行此网络的一步,我们需要传递一个输入(在我们的情况下,是当前字母的张量)和一个先前的隐藏状态(最初我们将其初始化为零)。我们将得到输出(每种语言的概率)和下一个隐藏状态(我们将其保留到下一步)。
input = letterToTensor('A') hidden = torch.zeros(1, n_hidden) output, next_hidden = rnn(input, hidden)
为了提高效率,我们不希望为每一步创建一个新的张量,因此我们将使用lineToTensor
代替letterToTensor
并使用切片。这可以通过预先计算批量张量来进一步优化。
input = lineToTensor('Albert') hidden = torch.zeros(1, n_hidden) output, next_hidden = rnn(input[0], hidden) print(output)
tensor([[-2.9083, -2.9270, -2.9167, -2.9590, -2.9108, -2.8332, -2.8906, -2.8325, -2.8521, -2.9279, -2.8452, -2.8754, -2.8565, -2.9733, -2.9201, -2.8233, -2.9298, -2.8624]], grad_fn=<LogSoftmaxBackward0>)
如您所见,输出是一个<1 x n_categories>
张量,其中每个项目都是该类别的可能性(可能性越高,越可能)。
训练
为训练做准备
在进行训练之前,我们应该编写一些辅助函数。第一个是解释网络输出的函数,我们知道它是每个类别的可能性。我们可以使用Tensor.topk
来获取最大值的索引:
def categoryFromOutput(output): top_n, top_i = output.topk(1) category_i = top_i[0].item() return all_categories[category_i], category_i print(categoryFromOutput(output))
('Scottish', 15)
我们还希望快速获取一个训练示例(一个名称及其语言):
import random def randomChoice(l): return l[random.randint(0, len(l) - 1)] def randomTrainingExample(): category = randomChoice(all_categories) line = randomChoice(category_lines[category]) category_tensor = torch.tensor([all_categories.index(category)], dtype=torch.long) line_tensor = lineToTensor(line) return category, line, category_tensor, line_tensor for i in range(10): category, line, category_tensor, line_tensor = randomTrainingExample() print('category =', category, '/ line =', line)
category = Chinese / line = Hou category = Scottish / line = Mckay category = Arabic / line = Cham category = Russian / line = V'Yurkov category = Irish / line = O'Keeffe category = French / line = Belrose category = Spanish / line = Silva category = Japanese / line = Fuchida category = Greek / line = Tsahalis category = Korean / line = Chang
训练网络
现在训练这个网络所需的全部工作就是向其展示一堆示例,让它猜测,并告诉它是否错误。
对于损失函数,nn.NLLLoss
是合适的,因为 RNN 的最后一层是nn.LogSoftmax
。
criterion = nn.NLLLoss()
每次训练循环将:
- 创建输入和目标张量
- 创建一个初始化的零隐藏状态
- 逐个读取每个字母
- 保留下一个字母的隐藏状态
- 将最终输出与目标进行比较
- 反向传播
- 返回输出和损失
learning_rate = 0.005 # If you set this too high, it might explode. If too low, it might not learn def train(category_tensor, line_tensor): hidden = rnn.initHidden() rnn.zero_grad() for i in range(line_tensor.size()[0]): output, hidden = rnn(line_tensor[i], hidden) loss = criterion(output, category_tensor) loss.backward() # Add parameters' gradients to their values, multiplied by learning rate for p in rnn.parameters(): p.data.add_(p.grad.data, alpha=-learning_rate) return output, loss.item()
现在我们只需运行一堆示例。由于train
函数返回输出和损失,我们可以打印其猜测并跟踪损失以绘图。由于有成千上万的示例,我们仅打印每print_every
个示例,并计算损失的平均值。
import time import math n_iters = 100000 print_every = 5000 plot_every = 1000 # Keep track of losses for plotting current_loss = 0 all_losses = [] def timeSince(since): now = time.time() s = now - since m = math.floor(s / 60) s -= m * 60 return '%dm %ds' % (m, s) start = time.time() for iter in range(1, n_iters + 1): category, line, category_tensor, line_tensor = randomTrainingExample() output, loss = train(category_tensor, line_tensor) current_loss += loss # Print ``iter`` number, loss, name and guess if iter % print_every == 0: guess, guess_i = categoryFromOutput(output) correct = '✓' if guess == category else '✗ (%s)' % category print('%d %d%% (%s) %.4f %s / %s %s' % (iter, iter / n_iters * 100, timeSince(start), loss, line, guess, correct)) # Add current loss avg to list of losses if iter % plot_every == 0: all_losses.append(current_loss / plot_every) current_loss = 0
5000 5% (0m 29s) 2.6379 Horigome / Japanese ✓ 10000 10% (0m 58s) 2.0172 Miazga / Japanese ✗ (Polish) 15000 15% (1m 29s) 0.2680 Yukhvidov / Russian ✓ 20000 20% (1m 58s) 1.8239 Mclaughlin / Irish ✗ (Scottish) 25000 25% (2m 29s) 0.6978 Banh / Vietnamese ✓ 30000 30% (2m 58s) 1.7433 Machado / Japanese ✗ (Portuguese) 35000 35% (3m 28s) 0.0340 Fotopoulos / Greek ✓ 40000 40% (3m 58s) 1.4637 Quirke / Irish ✓ 45000 45% (4m 28s) 1.9018 Reier / French ✗ (German) 50000 50% (4m 57s) 0.9174 Hou / Chinese ✓ 55000 55% (5m 27s) 1.0506 Duan / Vietnamese ✗ (Chinese) 60000 60% (5m 57s) 0.9617 Giang / Vietnamese ✓ 65000 65% (6m 27s) 2.4557 Cober / German ✗ (Czech) 70000 70% (6m 57s) 0.8502 Mateus / Portuguese ✓ 75000 75% (7m 26s) 0.2750 Hamilton / Scottish ✓ 80000 80% (7m 56s) 0.7515 Maessen / Dutch ✓ 85000 85% (8m 26s) 0.0912 Gan / Chinese ✓ 90000 90% (8m 55s) 0.1190 Bellomi / Italian ✓ 95000 95% (9m 25s) 0.0137 Vozgov / Russian ✓ 100000 100% (9m 55s) 0.7808 Tong / Vietnamese ✓
PyTorch 2.2 中文官方教程(六)(3)https://developer.aliyun.com/article/1482508