本文內容列表
版權聲明:本文爲博主原創文章,轉載請註明出處: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(['你好嗎', '今天天氣不錯', '謝謝啊'])
參考資料
下面這兩篇博客,自認爲分析的比較好
- BERT簡單使用:簡化了官網文檔中的代碼,很清晰
- 使用BERT生成句向量:作者基於Google開源的BERT代碼進行了進一步的簡化,方便生成句向量與做文本分類