【NLP】BERT模型解析記錄

1.bert簡單介紹

BERT(Bidirectional Encoder Representations from Transformers)是谷歌在2018年10月份的論文《Pre-training of Deep Bidirectional Transformers for Language Understanding》中提出的一個預(yù)訓(xùn)練模型框架,發(fā)布后對NLP領(lǐng)域產(chǎn)生了深遠(yuǎn)影響豹芯,各種基于bert的模型如雨后春筍般涌出亿卤。

在此對bert模型做一個簡單的記錄用于后期學(xué)習(xí)參考,文中會標(biāo)注相關(guān)出處热凹,如遇未注明或出現(xiàn)錯誤泵喘,請告知。如遇侵權(quán)般妙,請告知刪除~

bert模型分為pre-trainingfine-tuning兩個階段纪铺。

1.1. pre-training階段

在預(yù)訓(xùn)練階段,bert構(gòu)造了兩個訓(xùn)練任務(wù)Mask LMNext Sentence Prediction碟渺。
Mask LM
Mask LM是谷歌提出的一種學(xué)習(xí)句子內(nèi)部關(guān)系的一種trick鲜锚,該trick的靈感來源于完形填空(跟word2vecCBOW模型類似),基本思想是隨機(jī)遮掩(mask)句子中一些詞苫拍,并利用上下文信息對其進(jìn)行預(yù)測芜繁。
mask的具體做法是隨機(jī)對每個句子中15%的詞語按如下規(guī)則進(jìn)行

# 80%的時間采用[mask]
my dog is hairy ===> my dog is [MASK]
# 10%的時間隨機(jī)取一個詞來代替mask的詞
my dog is hairy ===> my dog is apple
# 10%的時間保持不變
my dog is hairy ===> my dog is hairy

使用該trick后可以使得模型對上下文信息具有完全意義上的雙向表征。

Q:模型在進(jìn)行[mask]的時候為什么要以一定的概率保持不變呢绒极?
A:如果100%的概率都用[MASK]來取代被選中的詞骏令,那么在fine tuning的時候模型可能會有一些沒見過的詞。
Q:那么為什么要以一定的概率使用隨機(jī)詞呢垄提?
A:這是因為Transformer要保持對每個輸入token分布式的表征榔袋,否則Transformer很可能會記住這個[MASK]就是"hairy"。至于使用隨機(jī)詞帶來的負(fù)面影響铡俐,文章中說了凰兑,所有其他的token(即非"hairy"的token)共享15%*10% = 1.5%的概率,其影響是可以忽略不計的审丘。

然而聪黎,該trick具有以下兩個缺點:
1)pre-training和fine-tuning階段不一致,該trick在fine-tuning階段是不可見的备恤,只有在pre-training階段才是用該trick稿饰。
2) 模型收斂速度慢。在每個batch中只有15%的token被預(yù)測露泊,因此需要在pre-training階段花費更多的訓(xùn)練次數(shù)喉镰。

Next Sentence Prediction
在自動問答(QA)、自然語言理解(NLI)等任務(wù)中惭笑,需要弄清上下兩句話之間的關(guān)系侣姆,為了模型理解這種關(guān)系生真,需要訓(xùn)練Next Sentence Prediction。
構(gòu)造訓(xùn)練語料方式:50%時間下一句是真正的下一句捺宗,50%時間下一句是語料中隨機(jī)的一句話柱蟀。

在pre-training階段,模型的損失函數(shù)是Mask LM和Next Sentence Prediction最大似然概率均值之和蚜厉。

1.2. fine-tuning階段

完成預(yù)訓(xùn)練之后的bert模型就具備了fine-tuning的能力长已,論文中將bert應(yīng)用于11項NLP任務(wù)中,在當(dāng)時均取得了STOA的效果昼牛。

基于bert的下游任務(wù)
  • MNLI(Multi-Genre Natural Language Inference):輸入句子對术瓮,預(yù)測第二個句子相對于第一個句子是entailment, contradiction, or neutral三種類型中的哪一種。
  • QQP(Quora Question Pairs):輸入句子對贰健,二分類任務(wù)胞四,判斷兩個問句在語義上是否等價。
  • QNLI(Question Natural Language Inference):輸入句子對伶椿,二分類任務(wù)辜伟,正樣本包含答案,負(fù)樣本不包含脊另。
  • STS-B(Semantic Textual Similarity Benchmark):輸入句子對导狡,計算兩個句子之間的語義相似度得分(1-5分)。
  • MRPC(Microsoft Research Paraphrase Corpus):輸入句子對尝蠕,判斷兩個句子在語義上是否等價。
  • RTE(Recognizing Textual Entailment):輸入句子對载庭,是否是蘊(yùn)含關(guān)系看彼,與MNLI類似,只是樣本數(shù)量小得多囚聚。
  • SWAG(Situations With Adversarial Generations):給定一個句子靖榕,判斷候選的四個句子那個是輸入句子的續(xù)寫。
  • SST-2(Stanford Sentiment Treebank):電影評論的情感分析顽铸。
  • CoLA(Corpus of Linguistic Acceptability):判斷一個句子是否在語義上是可接受的茁计。
  • SQuAD(Standford Question Answering Dataset):知識問答,給定一個問題和一段包含答案的文本谓松,找出該文本中答案的所在的范圍星压。
  • CoNLL 2003 Named Entity Recognition:命名實體識別。

在fine-tuning階段鬼譬,根據(jù)下游任務(wù)的性質(zhì)娜膘,可選擇不同的bert輸出特征作為下游任務(wù)的輸入。bert模型的輸出主要有model.get_pooling_out()model.get_sequence_out()
model.get_pooling_out()輸出的是每個句子開頭位置[CLS]的向量表示优质,也可以簡單理解為該句子所屬類別的向量表示竣贪,其shape=[batch size, hidden size]
model.get_sequence_out()輸出的是整個句子每個token的向量表示军洼,需要注意的是該向量表示也包括了[CLS],其shape=[batch_size, seq_length, hidden_size]

2.模型結(jié)構(gòu)

2.1. bert輸入

bert模型輸入

bert的輸入由以下三部分組成
單詞embedding(token embeddings)演怎,這個就是我們之前一直提到的單詞embedding匕争,表示當(dāng)前詞的embedding唬涧。
句子embedding(segmentation embeddings )脊奋,表示當(dāng)前詞所在句子的index embedding丁溅,因為bert的輸入是由兩個句子構(gòu)成的尝偎,那么每個句子有個整體的embedding項對應(yīng)給每個單詞晚树。
位置信息embedding(position embeddings)镜沽,表示當(dāng)前詞所在位置的index embedding迈窟,這是因為NLP中單詞順序是很重要的特征睁枕,需要在這里對位置信息進(jìn)行編碼盗胀。

把單詞對應(yīng)的三個embedding疊加艘蹋,就形成了Bert的輸入。bert輸入的三個embedding都是通過學(xué)習(xí)得到的票灰。

2.2.bert結(jié)構(gòu)

模型結(jié)構(gòu)

對比OpenAI GPT(Generative pre-trained transformer)女阀,BERT是雙向的Transformer block連接;就像單向RNN和雙向RNN的區(qū)別屑迂,直覺上來講效果會好一些浸策。
對比ELMo,雖然都是“雙向”惹盼,但目標(biāo)函數(shù)其實是不同的庸汗。ELMo是分別以

作為目標(biāo)函數(shù),獨立訓(xùn)練處兩個representation然后拼接手报,而BERT則是以
作為目標(biāo)函數(shù)訓(xùn)練LM蚯舱。
原文請參考:https://zhuanlan.zhihu.com/p/46652512

通過閱讀bert源碼,可以很清晰地得知其模型結(jié)構(gòu)掩蛤,bert模型實現(xiàn)部分主要在modeling.py文件中枉昏。
embedding_lookup函數(shù)實現(xiàn)的是token embedding
embedding_postprocessor函數(shù)實現(xiàn)的是segment embeddingposition embedding

token embeddingsegment embeddingposition embedding相加就可以得到bert模型的輸入(也即源碼中的self.embedding_output)揍鸟,然后將self.embedding_output輸入到12層transformer(只有encoder)中兄裂,即可得到bert的model.get_sequence_out()輸出。
model.get_pooling_out()則是在model.get_sequence_out()中取出第一個token(也即[CLS])對應(yīng)的向量表示阳藻。
然為了在下游的分類任務(wù)中能夠得到相同維度的representation晰奖,因此會經(jīng)過一個dense層將[CLS]的representation轉(zhuǎn)換為固定維度(hidden_size),所以model.get_pooling_out()的維度是[batch size, hidden size]

如需要詳細(xì)的bert源碼解讀腥泥,可參考https://zhuanlan.zhihu.com/p/69106080 (PART I)畅涂,也可關(guān)注筆者公眾號【NLPer筆記簿】,后臺回復(fù)bert即可獲取bert源碼解讀完整版道川。

with tf.variable_scope(scope, default_name="bert", reuse=tf.AUTO_REUSE):
  with tf.variable_scope("embeddings"):
    # Perform embedding lookup on the word ids.
    (self.embedding_output, self.embedding_table) = embedding_lookup(
        input_ids=input_ids,
        vocab_size=config.vocab_size,
        embedding_size=config.hidden_size,
        initializer_range=config.initializer_range,
        word_embedding_name="word_embeddings",
        use_one_hot_embeddings=use_one_hot_embeddings)

    # Add positional embeddings and token type embeddings, then layer
    # normalize and perform dropout.
    self.embedding_output = embedding_postprocessor(
        input_tensor=self.embedding_output,
        use_token_type=True,
        token_type_ids=token_type_ids,
        token_type_vocab_size=config.type_vocab_size,
        token_type_embedding_name="token_type_embeddings",
        use_position_embeddings=True,
        position_embedding_name="position_embeddings",
        initializer_range=config.initializer_range,
        max_position_embeddings=config.max_position_embeddings,
        dropout_prob=config.hidden_dropout_prob)

  with tf.variable_scope("encoder"):
    # This converts a 2D mask of shape [batch_size, seq_length] to a 3D
    # mask of shape [batch_size, seq_length, seq_length] which is used
    # for the attention scores.
    attention_mask = create_attention_mask_from_input_mask(
        input_ids, input_mask)

    # Run the stacked transformer.
    # `sequence_output` shape = [batch_size, seq_length, hidden_size].
    self.all_encoder_layers = transformer_model(
        input_tensor=self.embedding_output,
        attention_mask=attention_mask,
        hidden_size=config.hidden_size,
        num_hidden_layers=config.num_hidden_layers,
        num_attention_heads=config.num_attention_heads,
        intermediate_size=config.intermediate_size,
        intermediate_act_fn=get_activation(config.hidden_act),
        hidden_dropout_prob=config.hidden_dropout_prob,
        attention_probs_dropout_prob=config.attention_probs_dropout_prob,
        initializer_range=config.initializer_range,
        do_return_all_layers=True)

  self.sequence_output = self.all_encoder_layers[-1]
  # The "pooler" converts the encoded sequence tensor of shape
  # [batch_size, seq_length, hidden_size] to a tensor of shape
  # [batch_size, hidden_size]. This is necessary for segment-level
  # (or segment-pair-level) classification tasks where we need a fixed
  # dimensional representation of the segment.
  with tf.variable_scope("pooler"):
    # We "pool" the model by simply taking the hidden state corresponding
    # to the first token. We assume that this has been pre-trained
    first_token_tensor = tf.squeeze(self.sequence_output[:, 0:1, :], axis=1)
    self.pooled_output = tf.layers.dense(
        first_token_tensor,
        config.hidden_size,
        activation=tf.tanh,
        kernel_initializer=create_initializer(config.initializer_range))

2.3.bert輸出

其實bert輸出已經(jīng)在模型結(jié)構(gòu)章節(jié)介紹過了午衰,bert輸出主要有model.get_sequence_out()model.get_pooling_out()兩種輸出立宜,其shape分別為[batch_size, seq_length, hidden_size]和[batch_size, hidden_size]。
model.get_sequence_out()輸出主要用于特征提取再處理的序列任務(wù)臊岸,而model.get_pooling_out()輸出可直接接softmax進(jìn)行分類(當(dāng)然需要外加一層dense層將hidden_size轉(zhuǎn)換為num_tag)橙数。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市帅戒,隨后出現(xiàn)的幾起案子灯帮,更是在濱河造成了極大的恐慌,老刑警劉巖逻住,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钟哥,死亡現(xiàn)場離奇詭異,居然都是意外死亡瞎访,警方通過查閱死者的電腦和手機(jī)腻贰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扒秸,“玉大人播演,你說我怎么就攤上這事“榘拢” “怎么了写烤?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拾徙。 經(jīng)常有香客問我洲炊,道長,這世上最難降的妖魔是什么尼啡? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任暂衡,我火速辦了婚禮,結(jié)果婚禮上玄叠,老公的妹妹穿的比我還像新娘古徒。我一直安慰自己拓提,他們只是感情好读恃,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著代态,像睡著了一般寺惫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹦疑,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天西雀,我揣著相機(jī)與錄音,去河邊找鬼歉摧。 笑死艇肴,一個胖子當(dāng)著我的面吹牛腔呜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播再悼,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼核畴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冲九?” 一聲冷哼從身側(cè)響起谤草,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎莺奸,沒想到半個月后丑孩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡灭贷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年温学,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氧腰。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡枫浙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出古拴,到底是詐尸還是另有隱情箩帚,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布黄痪,位于F島的核電站紧帕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏桅打。R本人自食惡果不足惜是嗜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挺尾。 院中可真熱鬧鹅搪,春花似錦、人聲如沸遭铺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魂挂。三九已至甫题,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涂召,已是汗流浹背坠非。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留果正,地道東北人炎码。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓盟迟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親潦闲。 傳聞我的和親對象是個殘疾皇子队萤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容