BERT之提取特徵向量 及 bert-as-server的使用

版權聲明:本文爲博主原創文章,轉載請註明出處:https://blog.csdn.net/ling620/article/details/94738471

前一篇文章 BERT介紹及中文文本相似度任務實踐 簡單介紹了使用BERT進行中文文本相似度計算的方法,這篇文章着重對特徵提取方法進行講述。

一、 提取句向量

1、句向量簡介

1-1傳統句向量

更多采用word embedding的方式取加權平均,該方法的一大弊端,就是無法理解上下文的語義,同一個詞在不同的語境意思可能不一樣,但是卻會被表示成同樣的word embedding, BERT生成句向量的優點在於可理解句意,並且排除了詞向量加權引起的誤差。

1-2 BERT句向量

BERT包括兩個版本,12層transformer和24層的transformer,官方提供了12層的中文模型。
每一層transformer的輸出值,理論上來說都可以作爲句向量,但是到底該取哪一層呢,根據hanxiao(肖涵)大神的實驗數據,最佳結果是取倒數第二層,最後一層的值太接近於目標,前面幾層的值可能語義還未充分的學習到。

2、 extract_features.py源碼分析

該文件使用預訓練的模型文件,對測試樣例進行句向量提取。不包含訓練過程,只是執行BERT的前向過程,使用固定的參數對輸入樣例進行轉換。

那麼,從main函數開始吧!

2-1 參數

文件的一開始,是文件的參數,如下所示:

if __name__ == "__main__":
    flags.mark_flag_as_required("input_file")
    flags.mark_flag_as_required("vocab_file")
    flags.mark_flag_as_required("bert_config_file")
    flags.mark_flag_as_required("init_checkpoint")
    flags.mark_flag_as_required("output_file")
    tf.app.run()

這幾個參數比較好理解,如何不明白,可參考BERT介紹及中文文本相似度任務實踐文章中run_classifier.py文件中的參數,意義相同。
我們可以直接使用官網提供的中文預訓練模型chinese_L-12_H-768_A-12

除了必填的這幾個參數外,其他參數我們可以從文件的最開始位置查看,部分如下:

flags = tf.flags

FLAGS = flags.FLAGS

flags.DEFINE_string("input_file", None, "")

flags.DEFINE_string("output_file", './out.txt', "")

flags.DEFINE_string("layers", "-1,-2,-3,-4", "")

flags.DEFINE_string(
    "bert_config_file", None,
    "The config json file corresponding to the pre-trained BERT model. "
    "This specifies the model architecture.")

flags.DEFINE_integer(
    "max_seq_length", 128,
    "The maximum total input sequence length after WordPiece tokenization. "
    "Sequences longer than this will be truncated, and sequences shorter "
    "than this will be padded.")

flags.DEFINE_string(
    "init_checkpoint", None,
    "Initial checkpoint (usually from a pre-trained BERT model).")

其中,layers表示取模型第幾層的輸出值作爲詞向量, 默認值是[-1, -2, -3, -4]即表示倒數第一層、倒數第二層、倒數第三層和倒數第四層。我們可以指定想要的層數,在這裏我們可以只取倒數第二層的輸出作爲詞向量,即設爲[-2]
max_seq_length:表示是輸入序列經過tokenize之後的長度,大於這個長度的部分,會被截取掉,小於這個長度的序列,會在其後進行填充。

2-2 main()函數

說完了文件運行所需要的參數,那就從main函數開始吧。

examples = read_examples(FLAGS.input_file)

features = convert_examples_to_features(
    examples=examples, seq_length=FLAGS.max_seq_length, tokenizer=tokenizer)

上面兩行代碼實現了 讀入輸入文件,並對其進行預處理,對輸入文本處理爲 網絡輸入所需的格式。
其中,convert_examples_to_features函數最終將輸入文本封裝爲InputFeatures對象並添加到list中返回。如下所示:

def convert_examples_to_features(examples, seq_length, tokenizer):
    """Loads a data file into a list of `InputBatch`s."""
    features = []
    # 此處是將輸入補零到最大序列長度
    # Zero-pad up to the sequence length.
    while len(input_ids) < seq_length:
        input_ids.append(0)
        input_mask.append(0)
        input_type_ids.append(0)
    ......
        features.append(
            InputFeatures(
                unique_id=example.unique_id,
                tokens=tokens,
                input_ids=input_ids,
                input_mask=input_mask,
                input_type_ids=input_type_ids))
    return features

其中,裏面有一步是將輸入向量補零到最大長度。

下面的代碼實現加載預訓練模型的接口。

    model_fn = model_fn_builder(
        bert_config=bert_config,
        init_checkpoint=FLAGS.init_checkpoint,
        layer_indexes=layer_indexes,
        use_tpu=FLAGS.use_tpu,
        use_one_hot_embeddings=FLAGS.use_one_hot_embeddings)

    # If TPU is not available, this will fall back to normal Estimator on CPU
    # or GPU.
    estimator = tf.contrib.tpu.TPUEstimator(
        use_tpu=FLAGS.use_tpu,
        model_fn=model_fn,
        config=run_config,
        predict_batch_size=FLAGS.batch_size)

    input_fn = input_fn_builder(
        features=features, seq_length=FLAGS.max_seq_length)

具體加載模型參數的代碼位於input_fn_builder函數中的model_fn函數中,如下所示:

def model_fn_builder(bert_config, init_checkpoint, layer_indexes, use_tpu,
                     use_one_hot_embeddings):
    """Returns `model_fn` closure for TPUEstimator."""

    def model_fn(features, labels, mode, params):  # pylint: disable=unused-argument
        """The `model_fn` for TPUEstimator."""
        # 創建Bert模型
        model = modeling.BertModel(
            config=bert_config,
            is_training=False,
            input_ids=input_ids,
            input_mask=input_mask,
            token_type_ids=input_type_ids,
            use_one_hot_embeddings=use_one_hot_embeddings)

        if mode != tf.estimator.ModeKeys.PREDICT:
            raise ValueError("Only PREDICT modes are supported: %s" % (mode))

        # 獲取模型中所有的訓練參數
        tvars = tf.trainable_variables()
        scaffold_fn = None
        # 加載Bert模型
        (assignment_map,
         initialized_variable_names) = modeling.get_assignment_map_from_checkpoint(
             tvars, init_checkpoint)
        。。。。。。
        。。。。。。
        all_layers = model.get_all_encoder_layers() # 所有層

        predictions = {
            "unique_id": unique_ids,
        }
		# 循環讀取layer_indexes中所要返回的哪一層輸出,如只有一個數如[-2]則只倒數第二層
        for (i, layer_index) in enumerate(layer_indexes):
            predictions["layer_output_%d" % i] = all_layers[layer_index]

        output_spec = tf.contrib.tpu.TPUEstimatorSpec(
            mode=mode, predictions=predictions, scaffold_fn=scaffold_fn)
        return output_spec

    return model_fn
      

再後面的代碼就是將得到的結果保存到輸出文件中去的過程。
其中,注意一個操作,如下面代碼所示:

for (i, token) in enumerate(feature.tokens):
    all_layers = []
    for (j, layer_index) in enumerate(layer_indexes):
        layer_output = result["layer_output_%d" % j]
        layers = collections.OrderedDict()
        layers["index"] = layer_index
        layers["values"] = [
            round(float(x), 6) for x in layer_output[i:(i + 1)].flat
        ]
        all_layers.append(layers)

上面這部分代碼的意思是隻取出輸入 經過tokenize之後的長度 的向量。
即如果max_seq_lenght設爲128, 如果輸入的句子爲我愛你,則經過tokenize之後的輸入tokens=[["CLS"], '我', '愛','你',["SEP"]],實際有效長度爲5,而其餘128-5位均填充0。
上面代碼就是隻取出有效長度的向量。
layer_output的維度是(128, 768)layers["values"]的維度是是(5,768)

3、如何得到句向量呢?

由上面的流程可知,若輸入語句爲我愛你, 則得到的輸出向量維度是(5,768),那句向量的維度應該是(738,)纔對,一種比較常用的方法就是對該句話中 所有字向量取均值,即得到該句的句向量。

二、Bert-as-server

這是啥?
bert-as-service能讓你簡單通過兩行代碼,即可使用預訓練好的模型生成句向量和 ELMo 風格的詞向量:
bert-as-server的作者是肖涵博士

你可以將 bert-as-service 作爲公共基礎設施的一部分,部署在一臺 GPU 服務器上,使用多臺機器從遠程同時連接實時獲取向量,當做特徵信息輸入到下游模型。

直接看效果:
在這裏插入圖片描述

git源碼地址: bert-as-server

具體的使用方法,可直接取git上查找,說的也比較仔細;另外,也有許多博客進行了講解,都比較仔細。

1、 安裝

使用pip進行安裝,命令如下:

pip install bert-serving-server  # server
pip install bert-serving-client  # client, independent of `bert-serving-server`

要求Python >= 3.5, Tensorflow >= 1.10

2、開啓服務

打開服務命令:
bert-serving-start -model_dir /tmp/english_L-12_H-768_A-12/ -num_worker=4
如下圖所示,開啓成功後顯示如下:
在這裏插入圖片描述
咳咳,注意一下紅框標識位置,後面會用到。

3、使用客戶端獲取句子編碼向量

在這裏插入圖片描述

BERT 的另一個特性是可以獲取一對句子的向量,句子之間使用|||作爲分隔
使用方法

bc.encode(['你好嗎|||今天天氣不錯'])

當然,更希望一次測試多個句子,如下:

bc.encode(['你好嗎', '今天天氣不錯', '謝謝啊'])

參考資料

下面這兩篇博客,自認爲分析的比較好

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章