与TFBertModel相比,TFBertMainLayer的准确性较低

时间:2020-06-20 06:37:47

标签: keras transformer bert-language-model

我在保存包裹在TFBertModel中的Keras的权重方面遇到问题。问题描述为here in GitHub issuehere in Stack Overflow。两种情况下建议的解决方案是使用

 config = BertConfig.from_pretrained(transformer_model_name)
 bert = TFBertMainLayer(config=config,trainable=False)

代替

 bert = TFBertModel.from_pretrained(transformer_model_name, trainable=False)

问题在于,当我将模型更改为以前的代码时,准确性降低了10%,尽管两种情况下的参数计数都相同。我想知道是什么原因,如何预防?

1 个答案:

答案 0 :(得分:2)

由于未加载预训练的权重,因此似乎直接实例化了MainLayer的代码片段中的性能下降。您可以通过以下任一方式加载权重:

  1. 呼叫TFBertModel.from_pretrained并从已加载的MainLayer抓取TFBertModel
  2. 直接创建MainLayer,然后以与from_pretrained类似的方式加载权重

为什么会这样

调用TFBertModel.from_pretrained时,它使用函数TFPreTrainedModel.from_pretrained(通过继承)处理一些事情,包括下载,缓存和加载模型权重。

class TFPreTrainedModel(tf.keras.Model, TFModelUtilsMixin, TFGenerationMixin):
    ...
    @classmethod
    def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
        ...
        # Load model
        if pretrained_model_name_or_path is not None:
            if os.path.isfile(os.path.join(pretrained_model_name_or_path, TF2_WEIGHTS_NAME)):
            # Load from a TF 2.0 checkpoint
            archive_file = os.path.join(pretrained_model_name_or_path, TF2_WEIGHTS_NAME)
            ...
            resolved_archive_file = cached_path(
                    archive_file,
                    cache_dir=cache_dir,
                    force_download=force_download,
                    proxies=proxies,
                    resume_download=resume_download,
                    local_files_only=local_files_only,
            )
            ...
            model.load_weights(resolved_archive_file, by_name=True)

(如果您阅读了实际的代码,上面...的内容已经很多了。)

但是,当您直接实例化TFBertMainLayer时,它不会执行任何设置工作。

@keras_serializable
class TFBertMainLayer(tf.keras.layers.Layer):
    config_class = BertConfig

    def __init__(self, config, **kwargs):
        super().__init__(**kwargs)
        self.num_hidden_layers = config.num_hidden_layers
        self.initializer_range = config.initializer_range
        self.output_attentions = config.output_attentions
        self.output_hidden_states = config.output_hidden_states
        self.return_dict = config.use_return_dict
        self.embeddings = TFBertEmbeddings(config, name="embeddings")
        self.encoder = TFBertEncoder(config, name="encoder")
        self.pooler = TFBertPooler(config, name="pooler")
   
   ... rest of the class

基本上,您需要确保已加载这些砝码。

解决方案

(1)使用TFAutoModel.from_pretrained

您可以依靠Transformers.TFAutoModel.from_pretrained加载模型,然后仅从MainLayer的特定子类中获取TFPreTrainedModel字段。例如,如果您要访问distilbert主层,则它看起来像:

    model = transformers.TFAutoModel.from_pretrained(`distilbert-base-uncased`)
    assert isinstance(model, TFDistilBertModel)
    main_layer = transformer_model.distilbert

您可以在modeling_tf_distilbert.html 中看到MainLayer是模型的一个字段。 这是更少的代码和更少的重复,但是有一些缺点。更改要使用的预训练模型要容易一些,因为现在您依赖于字段名称,如果更改模型类型,则必须更改字段名称(例如{{1 }} MainLayer字段称为TFAlbertModel)。此外,这似乎不是使用拥抱面的预期方式,因此这可能会在您的鼻子下发生变化,并且您的代码可能会因拥抱面更新而中断。

albert

(2)从class TFDistilBertModel(TFDistilBertPreTrainedModel): def __init__(self, config, *inputs, **kwargs): super().__init__(config, *inputs, **kwargs) self.distilbert = TFDistilBertMainLayer(config, name="distilbert") # Embeddings [DOCS] @add_start_docstrings_to_callable(DISTILBERT_INPUTS_DOCSTRING) @add_code_sample_docstrings( tokenizer_class=_TOKENIZER_FOR_DOC, checkpoint="distilbert-base-uncased", output_type=TFBaseModelOutput, config_class=_CONFIG_FOR_DOC, ) def call(self, inputs, **kwargs): outputs = self.distilbert(inputs, **kwargs) return outputs

重新实现重量加载逻辑

您可以通过本质上复制/粘贴from_pretrained中与加载权重有关的部分来实现。这也有一些严重的缺点,您将复制逻辑,这些逻辑可能会与拥抱库不同步。尽管您可能会以对基础模型名称更改更灵活,更健壮的方式来编写它。

结论

理想情况下,这是拥抱面团队内部要解决的问题,方法是提供标准函数来创建MainLayer,将权重加载逻辑包装到可以调用的自身函数中,或者通过支持模型类的序列化