10.13 Update:最近新出了一個state-of-the-art預訓練模型,傳送門:git
李入魔:【NLP】Google BERT詳解zhuanlan.zhihu.com長期以來,詞向量一直是NLP任務中的主要表徵技術。隨着2017年末以及2018年初的一系列技術突破,研究證明預訓練的語言表徵通過精調後能夠在衆多NLP任務中達到更好的表現。目前預訓練有兩種方法:github
本文主要對ELMo、ULMFiT以及OpenAI GPT三種預訓練語言模型做簡要介紹。markdown
原文連接:Deep contextualized word representations網絡
ELMo是從雙向語言模型(BiLM)中提取出的Embedding。訓練時使用BiLSTM,給定N個tokens (t1, t2,...,tN), 目標爲最大化:session
ELMo對於每一個token , 經過一個L層的biLM計算出2L+1個表示:架構
其中 是對token進行直接編碼的結果(這裏是字符經過CNN編碼), 是每一個biLSTM層輸出的結果。app
應用中將ELMo中全部層的輸出R壓縮爲單個向量, , 最簡單的壓縮方法是取最上層的結果作爲token的表示: , 更通用的作法是經過一些參數來聯合全部層的信息:dom
其中 是softmax出來的權重, 是一個任務相關的scale參數,在優化過程當中很重要,同時由於每層BiLM的輸出分佈不一樣, 能夠對層起到normalisation的做用。ide
論文中使用的預訓練BiLM在Jozefowicz et al.中的CNN-BIG-LSTM基礎上作了修改,最終模型爲2層biLSTM(4096 units, 512 dimension projections),並在第一層和第二層之間增長了殘差鏈接。同時使用CNN和兩層Highway對token進行字符級的上下文無關編碼。使得模型最終對每一個token輸出三層向量表示。函數
- 正則化:
1. Dropout
2. 在loss中添加權重的懲罰項 (實驗結果顯示ELMo適合較小的 )
- TF版源碼解析:
1. 模型架構的代碼主要在training模塊的LanguageModel類中,分爲兩步:第一步建立word或character的Embedding層(CNN+Highway);第二步建立BiLSTM層。
2. 加載所需的預訓練模型爲model模塊中的BidirectionalLanguageModel類。
import tensorflow as tf
from keras import backend as K
import keras.layers as layers
from keras.models import Model
# Initialize session
sess = tf.Session()
K.set_session(sess)
# Instantiate the elmo model
elmo_model = hub.Module("https://tfhub.dev/google/elmo/1", trainable=True)
sess.run(tf.global_variables_initializer())
sess.run(tf.tables_initializer())
# We create a function to integrate the tensorflow model with a Keras model
# This requires explicitly casting the tensor to a string, because of a Keras quirk
def ElmoEmbedding(x):
return elmo_model(tf.squeeze(tf.cast(x, tf.string)), signature="default", as_dict=True)["default"]
input_text = layers.Input(shape=(1,), dtype=tf.string)
embedding = layers.Lambda(ElmoEmbedding, output_shape=(1024,))(input_text)
dense = layers.Dense(256, activation='relu')(embedding)
pred = layers.Dense(1, activation='sigmoid')(dense)
model = Model(inputs=[input_text], outputs=pred)
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()複製代碼
優勢:
缺點:
速度較慢,對每一個token編碼都要經過language model計算得出。
原文連接:Universal Language Model Fine-tuning for Text Classification
ULMFiT是一種有效的NLP遷移學習方法,核心思想是經過精調預訓練的語言模型完成其餘NLP任務。文中所用的語言模型參考了Merity et al. 2017a的AWD-LSTM模型,即沒有attention或shortcut的三層LSTM模型。
ULMFiT的過程分爲三步:
1. General-domain LM pre-train
2. Target task LM fine-tuning
文中介紹了兩種fine-tuning方法:
由於網絡中不一樣層能夠捕獲不一樣類型的信息,所以在精調時也應該使用不一樣的learning rate。做者爲每一層賦予一個學習率 ,實驗後發現,首先經過精調模型的最後一層L肯定學習率 ,再遞推地選擇上一層學習率進行精調的效果最好,遞推公式爲:
爲了針對特定任務選擇參數,理想狀況下須要在訓練開始時讓參數快速收斂到一個合適的區域,以後進行精調。爲了達到這種效果,做者提出STLR方法,即讓LR在訓練初期短暫遞增,在以後降低。如圖b的右上角所示。具體的公式爲:
文中做者使用的
3. Target task classifier fine-tuning
爲了完成分類任務的精調,做者在最後一層添加了兩個線性block,每一個都有batch-norm和dropout,使用ReLU做爲中間層激活函數,最後通過softmax輸出分類的機率分佈。最後的精調涉及的環節以下:
- PyTorch版源碼解析 (FastAI第10課)
# location: fastai/lm_rnn.py
def get_language_model(n_tok, emb_sz, n_hid, n_layers, pad_token,
dropout=0.4, dropouth=0.3, dropouti=0.5, dropoute=0.1, wdrop=0.5, tie_weights=True, qrnn=False, bias=False):
"""Returns a SequentialRNN model.
A RNN_Encoder layer is instantiated using the parameters provided.
This is followed by the creation of a LinearDecoder layer.
Also by default (i.e. tie_weights = True), the embedding matrix used in the RNN_Encoder
is used to instantiate the weights for the LinearDecoder layer.
The SequentialRNN layer is the native torch's Sequential wrapper that puts the RNN_Encoder and
LinearDecoder layers sequentially in the model.
Args:
n_tok (int): number of unique vocabulary words (or tokens) in the source dataset
emb_sz (int): the embedding size to use to encode each token
n_hid (int): number of hidden activation per LSTM layer
n_layers (int): number of LSTM layers to use in the architecture
pad_token (int): the int value used for padding text.
dropouth (float): dropout to apply to the activations going from one LSTM layer to another
dropouti (float): dropout to apply to the input layer.
dropoute (float): dropout to apply to the embedding layer.
wdrop (float): dropout used for a LSTM's internal (or hidden) recurrent weights.
tie_weights (bool): decide if the weights of the embedding matrix in the RNN encoder should be tied to the
weights of the LinearDecoder layer.
qrnn (bool): decide if the model is composed of LSTMS (False) or QRNNs (True).
bias (bool): decide if the decoder should have a bias layer or not.
Returns:
A SequentialRNN model
"""
rnn_enc = RNN_Encoder(n_tok, emb_sz, n_hid=n_hid, n_layers=n_layers, pad_token=pad_token,
dropouth=dropouth, dropouti=dropouti, dropoute=dropoute, wdrop=wdrop, qrnn=qrnn)
enc = rnn_enc.encoder if tie_weights else None
return SequentialRNN(rnn_enc, LinearDecoder(n_tok, emb_sz, dropout, tie_encoder=enc, bias=bias))
def get_rnn_classifier(bptt, max_seq, n_class, n_tok, emb_sz, n_hid, n_layers, pad_token, layers, drops, bidir=False,
dropouth=0.3, dropouti=0.5, dropoute=0.1, wdrop=0.5, qrnn=False):
rnn_enc = MultiBatchRNN(bptt, max_seq, n_tok, emb_sz, n_hid, n_layers, pad_token=pad_token, bidir=bidir,
dropouth=dropouth, dropouti=dropouti, dropoute=dropoute, wdrop=wdrop, qrnn=qrnn)
return SequentialRNN(rnn_enc, PoolingLinearClassifier(layers, drops))複製代碼
優勢:
對比其餘遷移學習方法(ELMo)更適合如下任務:
- 非英語語言,有標籤訓練數據不多
- 沒有state-of-the-art模型的新NLP任務
- 只有部分有標籤數據的任務
缺點:
對於分類和序列標註任務比較容易遷移,對於複雜任務(問答等)須要新的精調方法。
原文連接:Improving Language Understanding by Generative Pre-Training (未出版)
OpenAI Transformer是一類可遷移到多種NLP任務的,基於Transformer的語言模型。它的基本思想同ULMFiT相同,都是在儘可能不改變模型結構的狀況下將預訓練的語言模型應用到各類任務。不一樣的是,OpenAI Transformer主張用Transformer結構,而ULMFiT中使用的是基於RNN的語言模型。文中所用的網絡結構以下:
模型的訓練過程分爲兩步:
1. Unsupervised pre-training
第一階段的目標是預訓練語言模型,給定tokens的語料 ,目標函數爲最大化似然函數:
該模型中應用multi-headed self-attention,並在以後增長position-wise的前向傳播層,最後輸出一個分佈:
2. Supervised fine-tuning
有了預訓練的語言模型以後,對於有標籤的訓練集 ,給定輸入序列 和標籤 ,能夠經過語言模型獲得 ,通過輸出層後對 進行預測:
則目標函數爲:
整個任務的目標函數爲:
- TF版源碼解析
# location: finetune-transformer-lm/train.py
def model(X, M, Y, train=False, reuse=False):
with tf.variable_scope('model', reuse=reuse):
# n_special=3,做者把數據集分爲三份
# n_ctx 應該是 n_context
we = tf.get_variable("we", [n_vocab+n_special+n_ctx, n_embd], initializer=tf.random_normal_initializer(stddev=0.02))
we = dropout(we, embd_pdrop, train)
X = tf.reshape(X, [-1, n_ctx, 2])
M = tf.reshape(M, [-1, n_ctx])
# 1. Embedding
h = embed(X, we)
# 2. transformer block
for layer in range(n_layer):
h = block(h, 'h%d'%layer, train=train, scale=True)
# 3. 計算語言模型loss
lm_h = tf.reshape(h[:, :-1], [-1, n_embd])
lm_logits = tf.matmul(lm_h, we, transpose_b=True)
lm_losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=lm_logits, labels=tf.reshape(X[:, 1:, 0], [-1]))
lm_losses = tf.reshape(lm_losses, [shape_list(X)[0], shape_list(X)[1]-1])
lm_losses = tf.reduce_sum(lm_losses*M[:, 1:], 1)/tf.reduce_sum(M[:, 1:], 1)
# 4. 計算classifier loss
clf_h = tf.reshape(h, [-1, n_embd])
pool_idx = tf.cast(tf.argmax(tf.cast(tf.equal(X[:, :, 0], clf_token), tf.float32), 1), tf.int32)
clf_h = tf.gather(clf_h, tf.range(shape_list(X)[0], dtype=tf.int32)*n_ctx+pool_idx)
clf_h = tf.reshape(clf_h, [-1, 2, n_embd])
if train and clf_pdrop > 0:
shape = shape_list(clf_h)
shape[1] = 1
clf_h = tf.nn.dropout(clf_h, 1-clf_pdrop, shape)
clf_h = tf.reshape(clf_h, [-1, n_embd])
clf_logits = clf(clf_h, 1, train=train)
clf_logits = tf.reshape(clf_logits, [-1, 2])
clf_losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=clf_logits, labels=Y)
return clf_logits, clf_losses, lm_losses複製代碼
優勢:
缺點:
對於某些類型的任務須要對輸入數據的結構做調整
從Wrod Embedding到OpenAI Transformer,NLP中的遷移學習從最初使用word2vec、GLoVe進行字詞的向量表示,到ELMo能夠提供前幾層的權重共享,再到ULMFiT和OpenAI Transformer的整個預訓練模型的精調,大大提升了NLP基本任務的效果。同時,多項研究也代表,以語言模型做爲預訓練模型,不只能夠捕捉到文字間的語法信息,更能夠捕捉到語義信息,爲後續的網絡層提供高層次的抽象信息。另外,基於Transformer的模型在一些方面也展示出了優於RNN模型的效果。
最後,關於具體任務仍是要進行多種嘗試,可使用以上方法作出模型baseline,再調整網絡結構提高效果。