为了账号安全,请及时绑定邮箱和手机立即绑定

word2vec 的 RNN 模型(GRU)回归不学习

word2vec 的 RNN 模型(GRU)回归不学习

叮当猫咪 2021-11-16 15:13:58
我正在将 Keras 代码转换为 PyTorch,因为我对后者比对前者更熟悉。但是,我发现它并没有在学习(或只是勉强学习)。下面我提供了我几乎所有的 PyTorch 代码,包括初始化代码,以便您可以自己尝试。您唯一需要自己提供的是词嵌入(我相信您可以在网上找到许多 word2vec 模型)。第一个输入文件应该是一个带有标记文本的文件,第二个输入文件应该是一个带有浮点数的文件,每行一个。因为我已经提供了所有代码,所以这个问题可能看起来很大而且太宽泛了。但是,我认为我的问题足够具体:我的模型或训练循环中有什么问题导致我的模型没有改进或几乎没有改进。(结果见下文。)我试图在适用情况下提供了很多意见,我所提供的形状变换以及因此您不必有运行代码,看看是怎么回事。检查数据准备方法并不重要。最重要的部分是 的前向方法RegressorNet,以及 的训练循环RegressionNN(诚然,这些名称选择不当)。我认为错误在某处。对于 LazyTextDataset,可以参考下面的类。from torch.utils.data import Datasetimport linecacheclass LazyTextDataset(Dataset):    def __init__(self, paths):        # labels are in the last path        self.paths, self.labels_path = paths[:-1], paths[-1]        with open(self.labels_path, encoding='utf-8') as fhin:            lines = 0            for line in fhin:                if line.strip() != '':                    lines += 1            self.num_entries = lines    def __getitem__(self, idx):        data = [linecache.getline(p, idx + 1) for p in self.paths]        label = linecache.getline(self.labels_path, idx + 1)        return (*data, label)    def __len__(self):        return self.num_entries正如我之前所写,我正在尝试将 Keras 模型转换为 PyTorch。原始 Keras 代码不使用嵌入层,而是使用每个句子预先构建的 word2vec 向量作为输入。在下面的模型中,没有嵌入层。Keras 摘要如下所示(我无权访问基本模型设置)。
查看完整描述

1 回答

?
慕无忌1623718

TA贡献1744条经验 获得超4个赞

在交换轴时使用permute而不是使用view,请参阅答案的结尾以获得有关差异的直觉。

关于 RegressorNet(神经网络模型)

  1. 如果您使用from_pretrained. 正如文档所述,它使用梯度更新。

  2. 这部分:

    self.w2v_rnode = nn.GRU(embeddings.size(1), hidden_dim, bidirectional=True, dropout=drop_prob)

    尤其是dropout没有可提供性num_layers是完全没有意义的(因为不能用浅层网络指定 dropout)。

  3. BUG 和主要问题:在您的forward函数中,您使用的view不是permute,这里是:

    w2v_out, _ = self.w2v_rnode(embeds.view(-1, batch_size, embeds.size(2)))

    请参阅此答案和每个函数的相应文档,并尝试改用此行:

    w2v_out, _ = self.w2v_rnode(embeds.permute(1, 0, 2))

    您可以考虑batch_first=Truew2v_rnode创建期间使用参数,您不必以这种方式排列索引。

  4. 检查torch.nn.GRU 的文档,您是在序列的最后一步之后,而不是在您拥有的所有序列之后,因此您应该在:

    _, last_hidden = self.w2v_rnode(embeds.permute(1, 0, 2))

    但我认为这部分很好,否则。

数据准备

没有进攻,但是prepare_lines非常不可读,似乎非常难以维持为好,不说察觉最终的bug(我想这是就出在这里)。

首先,您似乎正在手动填充。请不要那样做,使用torch.nn.pad_sequence处理批处理!

本质上,首先您将每个句子中的每个单词编码为指向嵌入的索引(就像您在 中所做的那样prepare_w2v),然后您使用torch.nn.pad_sequenceandtorch.nn.pack_padded_sequence 或者 torch.nn.pack_sequence如果行已经按长度排序。

适当的配料

这部分非常重要,似乎您根本没有这样做(这可能是您实现中的第二个错误)。

PyTorch 的 RNN 单元不是将输入作为填充张量,而是作为torch.nn.PackedSequence对象。这是一个有效的对象存储索引,它指定每个序列的未填充长度。

此处此处以及网络上的许多其他博客文章中查看有关该主题的更多信息。

批处理中的第一个序列必须是最长的,所有其他序列必须以降序长度提供。接下来是:

  1. 您必须每次按序列长度对批次进行排序,以类似的方式对目标进行排序

  2. 对您的批次进行排序,将其推送到网络,然后将其取消排序以匹配您的目标。

两者都可以,这是您的电话,对您来说似乎更直观。我喜欢做的或多或少如下,希望它有所帮助:

  1. 为每个单词创建唯一的索引并适当地映射每个句子(您已经完成了)。

  2. torch.utils.data.Dataset为每个geitem创建返回单个句子的常规对象,它作为由特征 ( torch.Tensor) 和标签(单个值)组成的元组返回,似乎您也在这样做。

  3. 创建自定义collate_fn以与torch.utils.data.DataLoader一起使用,它负责在此场景中对每个批次进行排序和填充(+ 它返回要传递到神经网络的每个句子的长度)。

  4. 使用分类和填充特性它们的长度我用torch.nn.pack_sequence里面的神经网络的forward方法(做嵌入了!)通过RNN层来推动它。

  5. 根据用例,我使用torch.nn.pad_packed_sequence解压缩它们。在您的情况下,您只关心最后一个隐藏状态,因此您不必那样做。如果您使用了所有隐藏输出(例如注意力网络的情况),您将添加这部分。

说到第三点,这里是 的示例实现collate_fn,您应该明白了:

import torch



def length_sort(features):

    # Get length of each sentence in batch

    sentences_lengths = torch.tensor(list(map(len, features)))

    # Get indices which sort the sentences based on descending length

    _, sorter = sentences_lengths.sort(descending=True)

    # Pad batch as you have the lengths and sorter saved already

    padded_features = torch.nn.utils.rnn.pad_sequence(features, batch_first=True)

    return padded_features, sentences_lengths, sorter



def pad_collate_fn(batch):

    # DataLoader return batch like that unluckily, check it on your own

    features, labels = (

        [element[0] for element in batch],

        [element[1] for element in batch],

    )

    padded_features, sentences_lengths, sorter = length_sort(features)

    # Sort by length features and labels accordingly

    sorted_padded_features, sorted_labels = (

        padded_features[sorter],

        torch.tensor(labels)[sorter],

    )

    return sorted_padded_features, sorted_labels, sentences_lengths

collate_fnin一样使用它们DataLoaders,您应该差不多就好了(可能需要稍作调整,因此您必须了解其背后的想法)。

其他可能的问题和提示

  • 训练循环:很多小错误的好地方,您可能希望通过使用PyTorch Ignite来最小化这些错误。我在经历你的 Tensorflow-like-Estimator-like-API-like 训练循环(例如self.model = self.w2v_vocab = self.criterion = self.optimizer = self.scheduler = None这个)时遇到了令人难以置信的困难。请不要这样做,将每个任务(数据创建、数据加载、数据准备、模型设置、训练循环、日志记录)分离到各自的模块中。总而言之,PyTorch/Keras 比 Tensorflow 更具可读性和完整性是有原因的。

  • 使嵌入的第一行等于向量包含零:默认情况下,torch.nn.functional.embedding期望第一行用于填充。因此,您应该从 1 开始为每个单词建立唯一索引,或者padding_idx为不同的值指定一个参数(尽管我强烈反对这种方法,充其量会造成混淆)。

我希望这个答案至少对您有所帮助,如果有什么不清楚的,请在下面发表评论,我会尝试从不同的角度/更详细地解释它。

一些最后的评论

此代码不可重现,也不是问题的具体内容。我们没有您使用的数据,我们也没有您的词向量,随机种子不固定等。

附注。最后一件事:检查您在数据的非常小的子集(比如 96 个示例)上的性能,如果它不收敛,则很可能您的代码中确实存在错误。

关于时间:它们可能已关闭(我想是因为没有排序和没有填充),通常 Keras 和 PyTorch 的时间非常相似(如果我按预期理解您的问题的这一部分),可以正确有效地实现。

置换 vs 视图 vs 重塑解释

这个简单的例子显示了permute()和之间的区别view()。第一个交换轴,而第二个不改变内存布局,只是将数组分块成所需的形状(如果可能)。

import torch


a = torch.tensor([[1, 2], [3, 4], [5, 6]])


print(a)

print(a.permute(1, 0))

print(a.view(2, 3))

输出将是:


tensor([[1, 2],

        [3, 4],

        [5, 6]])

tensor([[1, 3, 5],

        [2, 4, 6]])

tensor([[1, 2, 3],

        [4, 5, 6]])

reshape几乎就像view, 是为那些来自 的人添加的numpy,所以对他们来说更容易、更自然,但它有一个重要的区别:

  • view 从不复制数据并且仅在连续内存上工作(因此在像上面那样排列之后,您的数据可能不连续,因此访问它可能会更慢)

  • reshape 如果需要可以复制数据,因此它也适用于非连续数组。


查看完整回答
反对 回复 2021-11-16
  • 1 回答
  • 0 关注
  • 323 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信