用深度神經(jīng)網(wǎng)絡(luò)處理NER命名實(shí)體識(shí)別問題
本文結(jié)構(gòu):
- 什么是命名實(shí)體識(shí)別(NER)
- 怎么識(shí)別?
cs224d Day 7: 項(xiàng)目2-用DNN處理NER問題
什么是NER?
命名實(shí)體識(shí)別(NER)是指識(shí)別文本中具有特定意義的實(shí)體,主要包括人名、地名、機(jī)構(gòu)名、專有名詞等。命名實(shí)體識(shí)別是信息提取、問答系統(tǒng)、句法分析、機(jī)器翻譯等應(yīng)用領(lǐng)域的重要基礎(chǔ)工具,作為結(jié)構(gòu)化信息提取的重要步驟。摘自 BosonNLP
怎么識(shí)別?
先把解決問題的邏輯說一下,然后解釋主要的代碼,有興趣的話,完整代碼請(qǐng)去 這里看 。
代碼是在 Tensorflow 下建立只有一個(gè)隱藏層的 DNN 來處理 NER 問題。
1.問題識(shí)別:
NER 是個(gè)分類問題。
給一個(gè)單詞,我們需要根據(jù)上下文判斷,它屬于下面四類的哪一個(gè),如果都不屬于,則類別為0,即不是實(shí)體,所以這是一個(gè)需要分成 5 類的問題:
- • Person (PER)
- • Organization (ORG)
- • Location (LOC)
- • Miscellaneous (MISC)
我們的訓(xùn)練數(shù)據(jù)有兩列,***列是單詞,第二列是標(biāo)簽。
- EU ORG
- rejects O
- German MISC
- Peter PER
- BRUSSELS LOC
2.模型:
接下來我們用深度神經(jīng)網(wǎng)絡(luò)對(duì)其進(jìn)行訓(xùn)練。
模型如下:
輸入層的 x^(t) 為以 x_t 為中心的窗口大小為3的上下文語境,x_t 是 one-hot 向量,x_t 與 L 作用后就是相應(yīng)的詞向量,詞向量的長(zhǎng)度為 d = 50 :
我們建立一個(gè)只有一個(gè)隱藏層的神經(jīng)網(wǎng)絡(luò),隱藏層維度是 100,y^ 就是得到的預(yù)測(cè)值,維度是 5:
用交叉熵來計(jì)算誤差:
J 對(duì)各個(gè)參數(shù)進(jìn)行求導(dǎo):
得到如下求導(dǎo)公式:
在 TensorFlow 中求導(dǎo)是自動(dòng)實(shí)現(xiàn)的,這里用Adam優(yōu)化算法更新梯度,不斷地迭代,使得loss越來越小直至收斂。
3.具體實(shí)現(xiàn)
在 def test_NER() 中,我們進(jìn)行 max_epochs 次迭代,每次,用 training data 訓(xùn)練模型 得到一對(duì) train_loss, train_acc ,再用這個(gè)模型去預(yù)測(cè) validation data,得到一對(duì) val_loss, predictions ,我們選擇最小的 val_loss ,并把相應(yīng)的參數(shù) weights 保存起來,***我們是要用這些參數(shù)去預(yù)測(cè) test data 的類別標(biāo)簽:
- def test_NER():
- config = Config()
- with tf.Graph().as_default():
- model = NERModel(config) # 最主要的類
- init = tf.initialize_all_variables()
- saver = tf.train.Saver()
- with tf.Session() as session:
- best_val_loss = float('inf') # ***的值時(shí),它的 loss 它的 迭代次數(shù) epoch
- best_val_epoch = 0
- session.run(init)
- for epoch in xrange(config.max_epochs):
- print 'Epoch {}'.format(epoch)
- start = time.time()
- ###
- train_loss, train_acc = model.run_epoch(session, model.X_train,
- model.y_train) # 1.把 train 數(shù)據(jù)放進(jìn)迭代里跑,得到 loss 和 accuracy
- val_loss, predictions = model.predict(session, model.X_dev, model.y_dev) # 2.用這個(gè)model去預(yù)測(cè) dev 數(shù)據(jù),得到loss 和 prediction
- print 'Training loss: {}'.format(train_loss)
- print 'Training acc: {}'.format(train_acc)
- print 'Validation loss: {}'.format(val_loss)
- if val_loss < best_val_loss: # 用 val 數(shù)據(jù)的loss去找最小的loss
- best_val_loss = val_loss
- best_val_epoch = epoch
- if not os.path.exists("./weights"):
- os.makedirs("./weights")
- saver.save(session, './weights/ner.weights') # 把最小的 loss 對(duì)應(yīng)的 weights 保存起來
- if epoch - best_val_epoch > config.early_stopping:
- break
- ###
- confusion = calculate_confusion(config, predictions, model.y_dev) # 3.把 dev 的lable數(shù)據(jù)放進(jìn)去,計(jì)算prediction的confusion
- print_confusion(confusion, model.num_to_tag)
- print 'Total time: {}'.format(time.time() - start)
- saver.restore(session, './weights/ner.weights') # 再次加載保存過的 weights,用 test 數(shù)據(jù)做預(yù)測(cè),得到預(yù)測(cè)結(jié)果
- print 'Test'
- print '=-=-='
- print 'Writing predictions to q2_test.predicted'
- _, predictions = model.predict(session, model.X_test, model.y_test)
- save_predictions(predictions, "q2_test.predicted") # 把預(yù)測(cè)結(jié)果保存起來
- if __name__ == "__main__":
- test_NER()
4.模型是怎么訓(xùn)練的呢?
首先導(dǎo)入數(shù)據(jù) training,validation,test:
- # Load the training set
- docs = du.load_dataset('data/ner/train')
- # Load the dev set (for tuning hyperparameters)
- docs = du.load_dataset('data/ner/dev')
- # Load the test set (dummy labels only)
- docs = du.load_dataset('data/ner/test.masked')
把單詞轉(zhuǎn)化成 one-hot 向量后,再轉(zhuǎn)化成詞向量:
- def add_embedding(self):
- # The embedding lookup is currently only implemented for the CPU
- with tf.device('/cpu:0'):
- embedding = tf.get_variable('Embedding', [len(self.wv), self.config.embed_size]) # assignment 中的 L
- window = tf.nn.embedding_lookup(embedding, self.input_placeholder) # 在 L 中直接把window大小的context的word vector搞定
- window = tf.reshape(
- window, [-1, self.config.window_size * self.config.embed_size])
- return window
建立神經(jīng)層,包括用 xavier 去初始化***層, L2 正則化和用 dropout 來減小過擬合的處理:
- def add_model(self, window):
- with tf.variable_scope('Layer1', initializer=xavier_weight_init()) as scope: # 用initializer=xavier去初始化***層
- W = tf.get_variable( # ***層有 W,b1,h
- 'W', [self.config.window_size * self.config.embed_size,
- self.config.hidden_size])
- b1 = tf.get_variable('b1', [self.config.hidden_size])
- h = tf.nn.tanh(tf.matmul(window, W) + b1)
- if self.config.l2: # L2 regularization for W
- tf.add_to_collection('total_loss', 0.5 * self.config.l2 * tf.nn.l2_loss(W)) # 0.5 * self.config.l2 * tf.nn.l2_loss(W)
- with tf.variable_scope('Layer2', initializer=xavier_weight_init()) as scope:
- U = tf.get_variable('U', [self.config.hidden_size, self.config.label_size])
- b2 = tf.get_variable('b2', [self.config.label_size])
- y = tf.matmul(h, U) + b2
- if self.config.l2:
- tf.add_to_collection('total_loss', 0.5 * self.config.l2 * tf.nn.l2_loss(U))
- output = tf.nn.dropout(y, self.dropout_placeholder) # 返回 output,兩個(gè)variable_scope都帶dropout
- return output
關(guān)于 L2正則化 和 dropout 是什么, 如何減小過擬合問題的,可以看 這篇博客,總結(jié)的簡(jiǎn)單明了。
用 cross entropy 來計(jì)算 loss:
- def add_loss_op(self, y):
- cross_entropy = tf.reduce_mean( # 1.關(guān)鍵步驟:loss是用cross entropy定義的
- tf.nn.softmax_cross_entropy_with_logits(y, self.labels_placeholder)) # y是模型預(yù)測(cè)值,計(jì)算cross entropy
- tf.add_to_collection('total_loss', cross_entropy) # Stores value in the collection with the given name.
- # collections are not sets, it is possible to add a value to a collection several times.
- loss = tf.add_n(tf.get_collection('total_loss')) # Adds all input tensors element-wise. inputs: A list of Tensor with same shape and type
- return loss
接著用 Adam Optimizer 把loss最小化:
- def add_training_op(self, loss):
- optimizer = tf.train.AdamOptimizer(self.config.lr)
- global_step = tf.Variable(0, name='global_step', trainable=False)
- train_op = optimizer.minimize(loss, global_step=global_step) # 2.關(guān)鍵步驟:用 AdamOptimizer 使 loss 達(dá)到最小,所以更關(guān)鍵的是 loss
- return train_op
每一次訓(xùn)練后,得到了最小化 loss 相應(yīng)的 weights。
這樣,NER 這個(gè)分類問題就搞定了,當(dāng)然為了提高精度等其他問題,還是需要查閱文獻(xiàn)來學(xué)習(xí)的。下一次先實(shí)現(xiàn)個(gè) RNN。