1. 程式人生 > >TF使用例子-LSTM實現序列標註

TF使用例子-LSTM實現序列標註

?wx_fmt=png&wxfrom=5&wx_lazy=1

LeadAI學院祝您聖誕節快樂

?wx_fmt=png&wxfrom=5&wx_lazy=1

正文共6974個字,13張圖,預計閱讀時間18分鐘。

本文主要改寫了一下"Sequence Tagging with Tensorflow"(https://link.jianshu.com?t=https://guillaumegenthial.github.io/sequence-tagging-with-tensorflow.html)程式。

原文是基於英文的命名實體識別(named entity recognition)問題,由於博主找不到相應的中文資料集(其實是沒備份資料丟了,如果有同學提供,萬分感謝)。因此,本文用了msra的分詞資料(https://link.jianshu.com?t=http://sighan.cs.uchicago.edu/bakeoff2005/)。

另外,由於用到了詞向量,所以用了搜狗實驗室釋出的2008新聞資料(https://link.jianshu.com?t=https://www.sogou.com/labs/resource/ca.php),提前訓練了300維度的字向量(用的gensim包訓練word2vector,另外後續可以嘗試Glove)。

?wx_fmt=png&wxfrom=5&wx_lazy=1

1、序列標註

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

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

為每個字做標註之後的結果就是:

琪(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)

當然,你可能想把“彈幕遊戲”作為一個詞,這取決於你如何標註這個資料,但是標註的時候要統一和規範。比如網上有PKU的資料標註規範(http://sighan.cs.uchicago.edu/bakeoff2005/data/pku_spec.pdf)。


其它比如像詞性的標註都屬於同一類問題。

?wx_fmt=png

2、常用方法

常用方法有MEMM (Maximum Entropy Markov Model)【1】,CRF (Conditional Random Field)【2】與 LSTM+CRF【3】。

【1】【2】原理待補充。
【3】型別的模型大致如圖:

?wx_fmt=png

這是一個雙向的LSTM,這裡的英文單詞可以類比成中文的字,在輸出結果的時候再用crf對輸出結果進行調整(排除不太可能的標註順序)。


本文簡單的用tensorflow實現了雙向LSTM+CRF在中文文字分詞上標註問題結果。

?wx_fmt=png

3、TF實現簡單的序列標註

預處理

首先,我們需要為每個字建立一個id,另外可以設定一個閾值把出現次數小於該閾值的字用UNK(unknown)來統一表示。另外數字可以同義用NUM來代替。


然後我們把訓練集按照6:3:1的比例分成訓練集,驗證集,測試集。並把格式整理成一列句子,一列標註。這裡用的是BIEs標註方案。

李  B
元   E
與   s

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

建模

這部分主要是翻譯了原文。


由於tensorflow是batch處理資料樣本的,所以我們需要對句子做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])

假設embeddings是我們預先訓練好的詞向量,那麼我麼可以這樣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設定成False而不是tf.constant,否則會有記憶體問題。(另外如果不需要訓練embedding層的話也沒必要設定成True)

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

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

?wx_fmt=png

對於序列標註問題,前後字對於當前字的標註結果都會有影響,所以用雙向的LSTM是很有意義的。這次我們用每個time step的隱藏層狀態,程式碼如下:

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)在句子層面做預測。


兩種方法的目的都是為了讓最後的序列標註結果的概率最大。先來計算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的概率最大,最後一串序列的結果就是序列中每個字的標註概率相乘得到的。這種結果都是區域性的,也就是說某個字在標註的時候並沒有考慮前後面字的標註結果的影響。

對於linear-chain CRF: 定義了一個全域性的score,考慮了標註結果之間的轉移。如下圖:

?wx_fmt=png

如果我們不考慮轉移情況,都選取區域性最大的值,我們就會標註為PER-PER-LOC了。

訓練

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

# 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)

預測

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

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

對於CRF,傳遞一下訓練時候得到的轉移矩陣T,用viterbi的方法搜尋到最優解即可:

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

?wx_fmt=png

結果

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

琪斯美是日本的“東方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']

附分詞例項程式碼

戳這裡(https://link.jianshu.com?t=https://github.com/Slyne/tf_tagging.git) 資料見README

附keras實現的簡易版本程式碼

keras官方版本目前還木有實現crf層,但是網上有同學自己實現了,戳這裡(https://link.jianshu.com?t=https://github.com/phipleg/keras/blob/crf/keras/layers/crf.py)

例子:

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)

?wx_fmt=png

相關文獻

【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).

原文連結:https://www.jianshu.com/p/4cfcce68fc3b

查閱更為簡潔方便的分類文章以及最新的課程、產品資訊,請移步至全新呈現的“LeadAI學院官網”:

www.leadai.org

請關注人工智慧LeadAI公眾號,檢視更多專業文章

?wx_fmt=jpeg

大家都在看

640.png?

640.png?