俗話說的好,NER該怎麼做?雙向LSTM+CRF啊。這LSTM聽說比一般的RNN牛逼,那這雙向的LSTM豈不是更牛逼了?(BERT心想:渣渣)
不管是實際業務需要,還是參加一些NLP比賽,亦或是參加面試,都有可能用到NER,那雙向LSTM+CRF簡直就是NER界的標配。今天我們就手把手教大家如何用Keras優雅的搭建Bi-LSTM+CRF模型。
我們假設大家已經有了可供訓練的數據(文末提供數據)。其數據格式應該為這樣:
這裡我們以字為單元進行標註。「O」是other的縮寫,表示非實體詞。「B」表示「開頭」,即實體詞的開頭字符,「I」表示「中間」,實體詞的中間字符。我們的數據樣例裡面是使用的是「BIO」標籤,沒有「E」,「E」表示的是實體詞的結尾。但是在絕大多數情況下,沒有「E」問題也不大,畢竟很難找出混淆實體的例子。
關於命名實體識別(NER)任務和Bi-LSTM還有CRF,我們在之前的文章中寫過:
從零開始學自然語言處理(六)—— 命名實體識別
從零開始學自然語言處理(22)—— 效果震撼的Bi-LSTM
從零開始學自然語言處理(23)—— 巧妙的條件隨機場(CRF)(上)
從零開始學自然語言處理(24)—— 巧妙的條件隨機場(CRF)(下)
我們引入我們需要的python包:
from keras.models import Modelfrom keras.layers import Embedding, Bidirectional, LSTM, Dense, Dropout, Inputfrom keras_contrib.layers import CRFfrom keras.preprocessing.sequence import pad_sequencesfrom keras.utils import np_utilskeras_contrib.layers.CRF是某個大神封裝在keras的CRF包,keras可以直接調用。
構建模型。
class bi_lstm_crf(): def __init__(self, vocab_size, n_class, embedding_dim=128, rnn_units=128, drop_rate=0.3, ): self.vocab_size = vocab_size #詞彙量 self.n_class = n_class #NER label種類數 self.embedding_dim = embedding_dim #embedding層維度 self.rnn_units = rnn_units #lstm單元維度 self.drop_rate = drop_rate
def creat_model(self): inputs = Input(shape=(None,)) embedding = Embedding(input_dim=self.vocab_size, output_dim=self.embedding_dim)(inputs) x = Bidirectional(LSTM(units=self.rnn_units, return_sequences=True))(embedding) #雙向lstm對輸入進行編碼 x = Dropout(self.drop_rate)(x) x = Dense(self.n_class)(x) crf = CRF(self.n_class, sparse_target=False) x = crf(x) model = Model(inputs=inputs, outputs=x) model.compile('adam', loss=crf.loss_function, metrics=[crf.accuracy]) return model是不是很簡單,模型的代碼量其實只有不到10行。首先將文本進行id化,然後輸入到embedding層取出對應的詞向量,然後經過雙向LSTM編碼,再經過一層Dense(其實可有可無)的線性變換,最後經過CRF層,即可。
數據處理的代碼稍微複雜一點。我們第一步要做的就是構建詞典,即給語料中的每一個詞一個唯一的id。同時,我們也需要對label(即那些B、I、O)進行id化處理。具體代碼如下:
from collections import Counterdef get_dict(min_num = 5): paths = [ 'data/example.train', 'data/example.dev', 'data/example.test' ]
label_dict = [] word_dict = [] for path in paths: with open(path) as f: for line in f: p = line.split() if len(p) == 1: word_dict.append(p[0]) elif len(p) == 2: word_dict.append(p[0]) label_dict.append(p[1])
label_dict = Counter(label_dict) word_dict = dict(Counter(word_dict)) label2id = {key: index for index, key in enumerate(label_dict)} id2label = {v: k for k, v in label2id.items()} word_dict = {v:k for k,v in dict(word_dict).items() if word_dict.get(k)>min_num } word2id = {key: index+2 for index, key in enumerate(word_dict)} word2id['PAD'] = 0 word2id['UNK'] = 1 id2word = {v: k for k, v in word2id.items()} return word2id, id2word, label2id, id2label
word2id, id2word, label2id, id2label = get_dict()再寫一個生成訓練數據的函數:
def get_data(path, train=True): X, Y = [], [] x, y = [], [] with open(path) as f: for line in f: if line == '\n': X.append(x) Y.append(y) x = [] y = [] continue p = line.split() x.append(word2id.get(p[0], 1)) y.append(label2id.get(p[1])) if len(x)>0: X.append(x) Y.append(y)
return X, Y訓練數據X,Y以及驗證數據val_x,val_y,對其進行補零和截斷操作。同時,將label進行to_categorical操作,轉化為one-hot編碼。
X, Y = get_data('data/example.train')val_x, val_y = get_data('data/example.dev')X, Y = pad_sequences(X, maxlen=200), pad_sequences(Y, maxlen=200)val_x, val_y = pad_sequences(val_x, maxlen=200), pad_sequences(val_y, maxlen=200)Y =np_utils.to_categorical(Y)val_y =np_utils.to_categorical(val_y)訓練模型:
lstm_crf = bi_lstm_crf(vocab_size=len(word2id), n_class=len(label2id))model = lstm_crf.creat_model()model.fit(X, Y, batch_size=64, epochs=2, validation_data=[val_x, val_y])簡單訓練幾輪就可以達到不錯的效果。
歡迎關注公眾號「數據科學雜談」,回復「CRF」獲取本文全部代碼和數據,本公眾號原創技術文章第一時間推送。
精選好文推薦:
今日頭條面試,這幾個坑爹問題讓我與50w年薪擦肩而過...(Transformer)
面試阿里P7,這個問題沒回答出來,幫忙看看!(Encoder-Decoder)
面試官青睞的高頻面試問題,被我徹底搞懂!(Attention機制)
深入理解Python中import機制
不會虛擬環境搭建?那還怎麼寫項目代碼!