TF使用例子-LSTM實現(xiàn)序列標注

本文主要改寫了一下"Sequence Tagging with Tensorflow"程序。原文是基于英文的命名實體識別(named entity recognition)問題,由于博主找不到相應(yīng)的中文數(shù)據(jù)集(其實是沒備份數(shù)據(jù)丟了,如果有同學(xué)提供,萬分感謝)。因此,本文用了msra的分詞數(shù)據(jù)。另外,由于用到了詞向量,所以用了搜狗實驗室發(fā)布的2008新聞數(shù)據(jù),提前訓(xùn)練了300維度的字向量(用的gensim包訓(xùn)練word2vector,另外后續(xù)可以嘗試Glove)。


Part I 序列標注

序列標注就是給定一串序列,對序列中的每個元素做一個標記。比如我們希望識別一句話里面的人名,地名,組織機構(gòu)名(命名實體識別)。有如下的句子:

琪斯美是日本的“東方project”系列彈幕游戲及其衍生作品的登場角色之一。

為每個字做標注之后的結(jié)果就是:

琪(B-PER)斯(I-PER)美(E-PER)是(O)日(B-LOC)本(E-LOC)的(O)“(O)東(B-ORG)方(I-ORG)project(E-ORG)”(O)系(O)列(O)彈(O)幕(O)游(O)戲(O)及(O)其(O)衍(O)生(O)作(O)品(O)的(O)登(O)場(O)角(O)色(O)之(O)一(O)。(O)*

這里標注采用的是BIEO,即Begin, Intermediate, End, Other(?我也不知道O是什么)

琪(B-PER)斯(I-PER)美(E-PER) 表示的含義就是 “琪”是人名開始,“斯”是人名中間的字,“美”是人名的末尾的字。其它符號同理。

這里可以看到,實際上就是用一串符號來標注出你感興趣的部分。那么對于分詞問題也是同理:

琪斯美是日本的“東方project”系列彈幕游戲及其衍生作品的登場角色之一。
琪斯美 是 日本 的 “ 東方project ” 系列 彈幕 游戲 及 其 衍生 作品 的 登場 角色 之一。
琪(B)斯(I)美 (E)是(S) 日(B)本(E) 的(S) “(S) 東(B)方(I)project(E) ”(S) 系(B)列(E) 彈(B)幕(E) 游(B)戲(E) 及(S) 其(S) 衍(B)生(E) 作(B)品(E) 的(S) 登(B)場(E) 角(B)色(E) 之(B)一(E)。(S)

當然,你可能想把“彈幕游戲”作為一個詞,這取決于你如何標注這個數(shù)據(jù),但是標注的時候要統(tǒng)一和規(guī)范。比如網(wǎng)上有PKU的數(shù)據(jù)標注規(guī)范(http://sighan.cs.uchicago.edu/bakeoff2005/data/pku_spec.pdf)。
其它比如像詞性的標注都屬于同一類問題。


Part II 常用方法

常用方法有MEMM (Maximum Entropy Markov Model)【1】,CRF (Conditional Random Field)【2】與 LSTM+CRF【3】。
【1】【2】原理待補充。
【3】類型的模型大致如圖:


這是一個雙向的LSTM,這里的英文單詞可以類比成中文的字,在輸出結(jié)果的時候再用crf對輸出結(jié)果進行調(diào)整(排除不太可能的標注順序)。
本文簡單的用tensorflow實現(xiàn)了雙向LSTM+CRF在中文文本分詞上標注問題結(jié)果。


Part III tensorflow實現(xiàn)簡單的序列標注

預(yù)處理

首先,我們需要為每個字建立一個id,另外可以設(shè)置一個閾值把出現(xiàn)次數(shù)小于該閾值的字用UNK(unknown)來統(tǒng)一表示。另外數(shù)字可以同義用NUM來代替。
然后我們把訓(xùn)練集按照6:3:1的比例分成訓(xùn)練集,驗證集,測試集。并把格式整理成一列句子,一列標注。這里用的是BIEs標注方案。

李 B
元 E
與 s

卞 B
德 I
培 E
初 s
識 s
于 s
1 B
9 I
4 I
7 I
年 E
。 s

建模

這部分主要是翻譯了原文。
由于tensorflow是batch處理數(shù)據(jù)樣本的,所以我們需要對句子做padding,讓它們一樣長,所以我們需要先對其定義2個placeholders,一個表示句子,一個表示每個句子除去padding的實際長度:

#shape = (batch size, max length of sentence in batch)
word_ids = tf.placeholder(tf.int32, shape=[None, None])
#shape = (batch size)`
sequence_lengths = tf.placeholder(tf.int32, shape=[None])

假設(shè)embeddings是我們預(yù)先訓(xùn)練好的詞向量,那么我么可以這樣load詞向量。

L = tf.Variable(embeddings, dtype=tf.float32, trainable=False)
# shape = (batch, sentence, word_vector_size)
pretrained_embeddings = tf.nn.embedding_lookup(L, word_ids)

這里trainable設(shè)置成False而不是tf.constant,否則會有內(nèi)存問題。(另外如果不需要訓(xùn)練embedding層的話也沒必要設(shè)置成True)

原文把每個英文單詞作為一個詞,并考慮了這個詞當中的字母的特征,而我們這里直接只考慮每個字,所以省略了字母特征這一塊。

一旦我們有了詞的表示之后,我們只用跑一個LSTM或者bi-LSTM,得到另一串向量(LSTM的隱藏層,或者bi-LSTM的前向后向的隱藏層的組合)。

對于序列標注問題,前后字對于當前字的標注結(jié)果都會有影響,所以用雙向的LSTM是很有意義的。這次我們用每個time step的隱藏層狀態(tài),代碼如下:

word_embeddings = pretrained_embeddings
lstm_cell = tf.contrib.rnn.LSTMCell(hidden_size)

(output_fw, output_bw), _ = tf.nn.bidirectional_dynamic_rnn(lstm_cell, 
    lstm_cell, word_embeddings, sequence_length=sequence_lengths, 
    dtype=tf.float32)

context_rep = tf.concat([output_fw, output_bw], axis=-1)

解碼

這一步,我們可以用兩種方式來為每個tag打分:
方法一: 用softmax,然后argmax選擇score值最大的那個tag,這種方法是基于字級別的。
方法二: 用條件隨機場(Conditional Random Field, CRF)在句子層面做預(yù)測。
兩種方法的目的都是為了讓最后的序列標注結(jié)果的概率最大。先來計算scores:

W = tf.get_variable("W", shape=[2*self.config.hidden_size, self.config.ntags], 
                dtype=tf.float32)

b = tf.get_variable("b", shape=[self.config.ntags], dtype=tf.float32, 
                initializer=tf.zeros_initializer())

ntime_steps = tf.shape(context_rep)[1]
context_rep_flat = tf.reshape(context_rep, [-1, 2*hidden_size])
pred = tf.matmul(context_rep_flat, W) + b
scores = tf.reshape(pred, [-1, ntime_steps, ntags])

對于softmax, 實際上是使得每個字屬于某個tag的概率最大,最后一串序列的結(jié)果就是序列中每個字的標注概率相乘得到的。這種結(jié)果都是局部的,也就是說某個字在標注的時候并沒有考慮前后面字的標注結(jié)果的影響。
對于linear-chain CRF: 定義了一個全局的score,考慮了標注結(jié)果之間的轉(zhuǎn)移。如下圖:



如果我們不考慮轉(zhuǎn)移情況,都選取局部最大的值,我們就會標注為PER-PER-LOC了。

訓(xùn)練

用CRF得到loss, 另外tf.contrib.crf.crf_log_likelihood還會返回轉(zhuǎn)移矩陣T,方便我們在做預(yù)測的時候用它:

# shape = (batch, sentence)
labels = tf.placeholder(tf.int32, shape=[None, None], name="labels")

log_likelihood, transition_params = tf.contrib.crf.crf_log_likelihood(
scores, labels, sequence_lengths)

loss = tf.reduce_mean(-log_likelihood)

用local softmax的到的loss, 這里用mask過濾掉pad上去的token帶來的loss:

losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=scores, labels=labels)
# shape = (batch, sentence, nclasses)
mask = tf.sequence_mask(sequence_lengths)
# apply mask
losses = tf.boolean_mask(losses, mask)

loss = tf.reduce_mean(losses)

最后定義我們的train_op:

optimizer = tf.train.AdamOptimizer(self.lr)
train_op = optimizer.minimize(self.loss)

預(yù)測

對于local softmax直接選擇每個time step最高的值就可以:

labels_pred = tf.cast(tf.argmax(self.logits, axis=-1), tf.int32)

對于CRF,傳遞一下訓(xùn)練時候得到的轉(zhuǎn)移矩陣T,用viterbi的方法搜索到最優(yōu)解即可:

# shape = (sentence, nclasses)
score = ...
viterbi_sequence, viterbi_score = tf.contrib.crf.viterbi_decode(
                                score, transition_params)

結(jié)果

樓主按照上述方法對msra的分詞數(shù)據(jù)跑了60個epoch后在(驗證集和測試集)上的準確率是96%左右,f1大概也在95%的樣子。以下是分出來的結(jié)果:

琪斯美是日本的“東方project”系列彈幕游戲及其衍生作品的登場角色之一。
['B', 'I', 'E', 's', 'B', 'E', 's', 's', 'B', 'E', 'B', 'I', 'I', 'I', 'I', 'I', 'E', 's', 'B', 'E', 'B', 'E', 'B', 'E', 'B', 'E', 'B', 'E', 'B', 'E', 's', 'B', 'E', 'B', 'E', 'B', 'E', 's']

附分詞實例代碼

戳這里 數(shù)據(jù)見README

附keras實現(xiàn)的簡易版本代碼

keras官方版本目前還木有實現(xiàn)crf層,但是網(wǎng)上有同學(xué)自己實現(xiàn)了,戳這里
例子:

n_words = 10000
maxlen = 32
(X_train, y_train), (X_test, y_test) = load_treebank(nb_words=n_words, maxlen=maxlen)

n_samples, n_steps, n_classes = y_train.shape

model = Sequential()
model.add(Embedding(n_words, 128, input_length=maxlen, dropout=0.2))
model.addBidirectional(LSTM(64, dropout_W=0.2, dropout_U=0.2, return_sequences=True),merge_mode='concat'))
model.add(Dropout(0.2))
model.add(TimeDistributed(Dense(n_classes)))
model.add(Dropout(0.2))
crf = ChainCRF()
model.add(crf)
model.compile(loss=crf.loss, optimizer='rmsprop', metrics=['accuracy'])

local softmax的代碼如下:

model = Sequential()
# keras.layers.embeddings.Embedding(input_dim, output_dim, init='uniform', input_length=None, W_regularizer=None, activity_regularizer=None, W_constraint=None, mask_zero=False, weights=None, dropout=0.0)
model.add(Embedding(max_features, 128, dropout=0.2))
model.add(Bidirectional(LSTM(64, dropout_W=0.2, dropout_U=0.2, return_sequences=True),merge_mode='concat'))  # try using a GRU instead, for fun
model.add(TimeDistributed(Dense(num_class)))
model.add(Activation('softmax'))


# try using different optimizers and different optimizer configs
model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
model.fit(X_train, y_train, batch_size=batch_size, nb_epoch=50,
          validation_data=(X_test, y_test))
score, acc = model.evaluate(X_test, y_test,
                            batch_size=batch_size)


相關(guān)文獻

【1】McCallum, Andrew, Dayne Freitag, and Fernando CN Pereira. "Maximum Entropy Markov Models for Information Extraction and Segmentation."Icml. Vol. 17. 2000.
【2】Lafferty, John, Andrew McCallum, and Fernando Pereira. "Conditional random fields: Probabilistic models for segmenting and labeling sequence data."Proceedings of the eighteenth international conference on machine learning, ICML. Vol. 1. 2001.
【3】Huang, Zhiheng, Wei Xu, and Kai Yu. "Bidirectional LSTM-CRF models for sequence tagging."arXiv preprint arXiv:1508.01991(2015).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

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