用深度神經(jīng)網(wǎng)絡(luò)處理NER命名實體識別問題

本文結(jié)構(gòu):

  1. 什么是命名實體識別(NER)
  2. 怎么識別?

cs224d Day 7: 項目2-用DNN處理NER問題
課程項目描述地址


什么是NER?

命名實體識別(NER)是指識別文本中具有特定意義的實體,主要包括人名、地名、機構(gòu)名、專有名詞等。命名實體識別是信息提取、問答系統(tǒng)、句法分析、機器翻譯等應(yīng)用領(lǐng)域的重要基礎(chǔ)工具,作為結(jié)構(gòu)化信息提取的重要步驟。摘自BosonNLP

怎么識別?

先把解決問題的邏輯說一下,然后解釋主要的代碼,有興趣的話,完整代碼請去這里看
代碼是在 Tensorflow 下建立只有一個隱藏層的 DNN 來處理 NER 問題。

1.問題識別:

NER 是個分類問題。

給一個單詞,我們需要根據(jù)上下文判斷,它屬于下面四類的哪一個,如果都不屬于,則類別為0,即不是實體,所以這是一個需要分成 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ò)對其進(jìn)行訓(xùn)練。

模型如下:

輸入層的 x^(t) 為以 x_t 為中心的窗口大小為3的上下文語境,x_t 是 one-hot 向量,x_t 與 L 作用后就是相應(yīng)的詞向量,詞向量的長度為 d = 50 :

我們建立一個只有一個隱藏層的神經(jīng)網(wǎng)絡(luò),隱藏層維度是 100,y^ 就是得到的預(yù)測值,維度是 5:

用交叉熵來計算誤差:

J 對各個參數(shù)進(jìn)行求導(dǎo):

得到如下求導(dǎo)公式:

在 TensorFlow 中求導(dǎo)是自動實現(xiàn)的,這里用Adam優(yōu)化算法更新梯度,不斷地迭代,使得loss越來越小直至收斂。

3.具體實現(xiàn)

def test_NER() 中,我們進(jìn)行 max_epochs 次迭代,每次,用 training data 訓(xùn)練模型 得到一對 train_loss, train_acc,再用這個模型去預(yù)測 validation data,得到一對 val_loss, predictions,我們選擇最小的 val_loss,并把相應(yīng)的參數(shù) weights 保存起來,最后我們是要用這些參數(shù)去預(yù)測 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')  # 最好的值時,它的 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.用這個model去預(yù)測 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 對應(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)去,計算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ù)測,得到預(yù)測結(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ù)測結(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,兩個variable_scope都帶dropout

    return output 


關(guān)于 L2正則化 和 dropout 是什么, 如何減小過擬合問題的,可以看這篇博客,總結(jié)的簡單明了。

  • 用 cross entropy 來計算 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ù)測值,計算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 這個分類問題就搞定了,當(dāng)然為了提高精度等其他問題,還是需要查閱文獻(xiàn)來學(xué)習(xí)的。下一次先實現(xiàn)個 RNN。

[cs224d]

Day 1. 深度學(xué)習(xí)與自然語言處理 主要概念一覽
Day 2. TensorFlow 入門
Day 3. word2vec 模型思想和代碼實現(xiàn)
Day 4. 怎樣做情感分析
Day 5. CS224d-Day 5: RNN快速入門
Day 6. 一文學(xué)會用 Tensorflow 搭建神經(jīng)網(wǎng)絡(luò)
Day 7. 用深度神經(jīng)網(wǎng)絡(luò)處理NER命名實體識別問題
Day 8. 用 RNN 訓(xùn)練語言模型生成文本
Day 9. RNN與機器翻譯
Day 10. 用 Recursive Neural Networks 得到分析樹
Day 11. RNN的高級應(yīng)用


我是 不會停的蝸牛 Alice
85后全職主婦
喜歡人工智能,行動派
創(chuàng)造力,思考力,學(xué)習(xí)力提升修煉進(jìn)行中
歡迎您的喜歡,關(guān)注和評論!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容