神经网络语言模型的效果不及预期

问题描述

我正在尝试使用神经网络来实现一些相对标准的语言模型,以更好地理解它们,并希望将来使它们适应其他问题。我一直在使用WikiText-2数据集,以便可以将结果与现有基准进行比较。到目前为止,我已经实现了一个简单的RNN模型(使用LSTM或GRU单元),以及一个使用TensorFlow 2的基于变压器的模型。

我还没有对超参数进行广泛或系统的研究,但经过摆弄,我无法在验证数据集(对于任何一个模型)上使混淆度远低于220-240。鉴于已发布的报告,这几乎不及我预期的那样。例如,我希望LSTM达到100左右。

我怀疑在数据处理或模型结构中肯定存在错误,无效的假设或缺少的步骤。有很多代码,所以我不会全部发布,但是我将尝试包括相关部分。

编辑:我已经上传了转换器模型here的完整实现。 除了下面的观察,我还注意到val_lossval_xent间的区别,我无法解释。其中val_lossval_xent都是分类交叉熵的掩蔽版本,但是一个是Keras Loss的子类,一个是Keras Metric的子类。

数据处理

我对数据集的理解是,为了测试模型学习长距离依赖项的能力,基本上应该将其视为一个连续的长文本。从理论上讲,可以将其分解为单独的文档,但是我发现准确地确定文档边界并不是一件容易的事。似乎前导和尾随=表示一篇新文章,但是该规则有一些例外。因此,我只是忽略了文章的边界,而是将文件视为一个连续的长文本。从理论上讲,模型应该了解=符号的含义。

由于模型只能在有限长度的序列上工作,因此我将数据集分成固定大小的片段,这将是模型的序列长度。下面的代码实现了这些假设。我怀疑这是否可扩展,但它适用于像这样的较小数据集。

# Load a dataset from the WikiText file
dataset = tf.data.TextLineDataset(path_to_wiki_file.tokens)

# Strip leading and trailing whitespace from each line
dataset = dataset.map(tf.strings.strip)

# Remove blank lines
dataset = dataset.filter(lambda line: tf.not_equal(tf.strings.length(line),0))

# Split the tokens on whitespace
dataset = dataset.map(tf.strings.split)

# Create one long sequence from the input
dataset = dataset.unbatch()

# Map the string token to a unique integer id (starting at 1).
# word2id is a tf.lookup.StaticHashTable
dataset = dataset.map(lambda t: word2id.lookup(t)

# Create fixed length pieces
dataset = dataset.window(seq_len+1)
dataset = dataset.flat_map(lambda w: w.batch(seq_len+1))

# Note I've also tried randomly selecting arbitrary windows of the
# text during training,which seems to improve performance to a 
# small degree.
# The implementation is also not efficient,but works ok for this
# dataset. Something like:
# dataset = dataset.window(seq_len,shift=1)
# dataset = dataset.flat_map(lambda w: w.batch(seq_len))
# And then randomly selecting a subset of them

# Create the x,y pairs
dataset = dataset.map(lambda t: (t[:-1],t[1:])

# Cache the preprocessing steps
dataset = dataset.cache()

# Create the batches,padding if necessary (although only the
# last batch should need to be padded)
dataset = dataset.padded_batch(bsz,padded_shapes=(seq_len,seq_len))

基于RNN的模型

基于RNN的模型相当简短,易于解释。

import tf.keras.layers as tfl

class RnnLanguageModel(tf.keras.models.Model):
    def __init__(self,vocab_size,n_embedding_units,n_layers,n_units,**kwargs):
        super().__init__(**kwargs)

        # The layers are defined here.
        self.embedding_layer = tfl.Embedding(vocab_size,n_embedding_units)
        self.recurrent_layers = [
            tfl.LSTM(n_units,stateful=True,return_sequences=True) 
            for _ in range(n_layers)
        ]
        self.final_layer = tfl.Dense(vocab_size)

    def call(inputs,training):
        # Get the embeddings for the current input
        x = self.embedding_layer(inputs)
        
        # Apply the recurrent layers
        for layer in self.recurrent_layers:
            x = layer(x)

        # A final dense layer that outputs the final predictions.
        logits = self.final_layer(x)

        return logits

基于变压器的模型

有太多代码可以在此处发布,但几乎所有内容都是从TensorFlow tutorial on Transformers开始的。我对模型所做的唯一重大更改是在DecoderLayer中。由于我们没有来自编码器的输入,因此注意模块之一不适用。因此,我的解码器层的调用函数如下所示:

def call(self,x,training,look_ahead_mask):
    # enc_output.shape == (batch_size,input_seq_len,n_model_dim)

    # (batch_size,target_seq_len,n_model_dim)
    attn,attn_weights_block = self.mha1(x,look_ahead_mask)
    attn = self.dropout1(attn,training=training)
    out = self.layer_norm1(attn + x)

    # There isn't an encoder so we don't need the other layers used
    # in the transformer tutorial.

    # (batch_size,n_model_dim)
    ffn_output = self.ffn(out)
    ffn_output = self.dropout2(ffn_output,training=training)

    # (batch_size,n_model_dim)
    result = self.layer_norm2(ffn_output + out)  

    return result,attn_weights_block

观察

除了无法达到预期的困惑之外,我在实现过程中还注意到了一些事情。

变压器似乎对某些超参数极为敏感,而对其他一些参数则完全不敏感。例如,如果层数太多或模型尺寸太大,则模型将根本不学习任何东西。另一方面,更改序列长度,头数和前馈单元似乎并没有太大的区别(这有所区别,只是几乎没有我的期望)。

我还注意到,用于翻译的原始论文的自定义学习率时间表似乎根本无法解决此问题(至少使用认参数)。

任何帮助或见解将不胜感激。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)