<aside> <img src="/icons/condense_yellow.svg" alt="/icons/condense_yellow.svg" width="40px" /> Python | 语言 | 儿童发展 | 常识 | 生物 | 物理 | 偏见 | 基准 | 注意力 | 行为 | 知识图 | 多标签 | 多模态 | 数据点 | 生物医学 | 商业文本 | 对话 | 混合密集 | 稀疏表征 | GPU | MATLAB | C++ | 基因 | 蛋白质 | 图像合成

</aside>

🎯要点

🎯语言学、儿童发展、数学、常识推理、生物学、物理学、社会偏见、软件开发基准评估语言模型 | 🎯解缠注意力模型 | 🎯语言模型行为测试 | 🎯知识图谱关联信息提取模型 | 🎯多标签和多模态数据点分类器 :Python/MATLAB/C++/C | 🎯生物医学语言推理模型 | 🎯商业文本语言模型 | 🎯倾听同理心长记忆个性对话模型 | 🎯混合密集和稀疏表征和多 GPU 训练和推理 | 🎯自定义语言序列建模 | 🎯基因组变异分析模型 | 🎯组蛋白基因表达预测模型 | 🎯蛋白质功能推断模型 | 🎯高质量图像合成模型

🎯语言模型可解释和可视化:Python记忆组合透明度语言模型

✂️梗概

🍇Python机器翻译和摘要中序列到序列任务

序列到序列任务表示输入和输出是序列(不一定具有相同长度)的任务。该领域的热门任务包括机器翻译和摘要。为此,我们通常有一个 Transformer 编码器用于解释输入序列,以及一个解码器用于以自回归方式生成输出。然而,在这里,我们将回到一个更简单的示例任务并仅使用编码器。给定 0 到 M 之间的 N 数字序列,任务是反转输入序列。在 Numpy 表示法中,如果我们的输入是 x,则输出应该是 x[::-1]。尽管这个任务听起来非常简单,但递归神经网络可能会遇到问题,因为该任务需要长期依赖。 Transformer 就是为了支持这种功能而构建的,因此,我们希望它能够表现良好。

首先,让我们在下面创建一个数据集类。

 class ReverseDataset(data.Dataset):
 
     def __init__(self, num_categories, seq_len, size):
         super().__init__()
         self.num_categories = num_categories
         self.seq_len = seq_len
         self.size = size
 
         self.data = torch.randint(self.num_categories, size=(self.size, self.seq_len))
 
     def __len__(self):
         return self.size
 
     def __getitem__(self, idx):
         inp_data = self.data[idx]
         labels = torch.flip(inp_data, dims=(0,))
         return inp_data, labelsPy

我们创建 0 到 num_categories-1 之间任意数量的随机数字序列。标签只是在序列维度上翻转的张量。我们可以在下面创建相应的数据加载器。

 dataset = partial(ReverseDataset, 10, 16)
 train_loader = data.DataLoader(dataset(50000), batch_size=128, shuffle=True, drop_last=True, pin_memory=True)
 val_loader   = data.DataLoader(dataset(1000), batch_size=128)
 test_loader  = data.DataLoader(dataset(10000), batch_size=128)

让我们看一下数据集的任意样本:

 inp_data, labels = train_loader.dataset[0]
 print("Input data:", inp_data)
 print("Labels:    ", labels)
 Input data: tensor([9, 6, 2, 0, 6, 2, 7, 9, 7, 3, 3, 4, 3, 7, 0, 9])
 Labels:     tensor([9, 0, 7, 3, 4, 3, 3, 7, 9, 7, 2, 6, 0, 2, 6, 9])

在训练期间,我们将输入序列传递给 Transformer 编码器,并预测每个输入标记的输出。我们使用标准交叉熵损失来执行此操作。每个数字都表示为一个独热向量。请记住,将类别表示为单个标量会极大地降低模型的表达能力,因为在我们的示例中和并不比和更紧密相关。独热向量的替代方法是使用学习到的嵌入向量,因为它由 PyTorch 模块 nn.Embedding 提供。但是,像我们的例子一样使用带有附加线性层的独热向量具有与嵌入层相同的效果(self.input_net 将独热向量映射到密集向量,其中权重矩阵的每一行代表特定类别的嵌入)。

为了实现动态训练,我们创建一个继承自 TransformerPredictor 的新类,并覆盖训练、验证和测试步骤函数。

 class ReversePredictor(TransformerPredictor):
 
     def _calculate_loss(self, batch, mode="train"):
         # Fetch data and transform categories to one-hot vectors
         inp_data, labels = batch
         inp_data = F.one_hot(inp_data, num_classes=self.hparams.num_classes).float()
 
         # Perform prediction and calculate loss and accuracy
         preds = self.forward(inp_data, add_positional_encoding=True)
         loss = F.cross_entropy(preds.view(-1,preds.size(-1)), labels.view(-1))
         acc = (preds.argmax(dim=-1) == labels).float().mean()
 
         # Logging
         self.log(f"{mode}_loss", loss)
         self.log(f"{mode}_acc", acc)
         return loss, acc
 
     def training_step(self, batch, batch_idx):
         loss, _ = self._calculate_loss(batch, mode="train")
         return loss
 
     def validation_step(self, batch, batch_idx):
         _ = self._calculate_loss(batch, mode="val")
 
     def test_step(self, batch, batch_idx):
         _ = self._calculate_loss(batch, mode="test")

最后,我们可以创建一个类似于我们在 PyTorch Lightning 中看到的训练函数。我们创建一个 pl.Trainer 对象,运行几个时期,登录 TensorBoard,并根据验证保存我们的最佳模型。之后,我们在测试集上测试我们的模型。我们在这里传递给训练器的另一个参数是 gradient_clip_val。这会在采取优化器步骤之前剪切所有参数的梯度范数,并防止模型在例如尖锐损失表面处获得非常高的梯度时发散。对于 Transformers,梯度剪切可以帮助在前几次迭代期间以及之后进一步稳定训练。在普通的 PyTorch 中,您可以通过 torch.nn.utils.clip_grad_norm_(...) 应用梯度剪切。剪辑值通常在 0.5 到 10 之间,具体取决于您想要剪辑大梯度的程度。解释完这些之后,我们来实现训练功能:

 def train_reverse(**kwargs):

     root_dir = os.path.join(CHECKPOINT_PATH, "ReverseTask")
     os.makedirs(root_dir, exist_ok=True)
     trainer = pl.Trainer(default_root_dir=root_dir,
                          callbacks=[ModelCheckpoint(save_weights_only=True, mode="max", monitor="val_acc")],
                          accelerator="gpu" if str(device).startswith("cuda") else "cpu",
                          devices=1,
                          max_epochs=10,
                          gradient_clip_val=5)
     trainer.logger._default_hp_metric = None
      pretrained_filename = os.path.join(CHECKPOINT_PATH, "ReverseTask.ckpt")
     if os.path.isfile(pretrained_filename):
         print("Found pretrained model, loading...")
         model = ReversePredictor.load_from_checkpoint(pretrained_filename)
     else:
         model = ReversePredictor(max_iters=trainer.max_epochs*len(train_loader), **kwargs)
         trainer.fit(model, train_loader, val_loader)
 

     val_result = trainer.test(model, val_loader, verbose=False)
     test_result = trainer.test(model, test_loader, verbose=False)
     result = {"test_acc": test_result[0]["test_acc"], "val_acc": val_result[0]["test_acc"]}
 
     model = model.to(device)
     return model, result

最后,我们可以训练模型了。在这个设置中,我们将在多头注意力中使用单个编码器块和单个头。之所以选择这种方式,是因为任务比较简单,在这种情况下,注意力实际上可以被解释为预测的“解释”。