天天看點

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結

【自然語言處理(NLP)】基于LSTM的命名實體識别

作者簡介:在校大學生一枚,華為雲享專家,阿裡雲專家部落客,騰雲先鋒(TDP)成員,雲曦智劃項目總負責人,全國高等學校計算機教學與産業實踐資源建設專家委員會(TIPCC)志願者,以及程式設計愛好者,期待和大家一起學習,一起進步~

.

部落格首頁:ぃ靈彧が的學習日志

.

本文專欄:機器學習

.

專欄寄語:若你決定燦爛,山無遮,海無攔

.

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結

(文章目錄)

前言

(一)、任務描述

命名實體識别任務主要識别文本中的實體,并且給識别出的實體進行分類,比如人名、地名、機構名或其它類型。本質上,對于給定的文本,隻需要對其中的每個單詞進行分類,隻不過需要對分類的标簽進行重新定義。

(二)、環境配置

本示例基于飛槳開源架構2.0版本。

import paddle
import numpy as np
import matplotlib.pyplot as plt
print(paddle.__version__)
           

輸出結果如下圖1所示:

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結

一、資料準備

(一)、導入相關包

import paddle
import paddle.nn as nn

import paddlenlp
from paddlenlp.datasets import MapDataset
from paddlenlp.data import Stack, Tuple, Pad
from paddlenlp.layers import LinearChainCrf, ViterbiDecoder, LinearChainCrfLoss
from paddlenlp.metrics import ChunkEvaluator
           

(二)、資料集加載

from paddlenlp.datasets import load_dataset

# 由于MSRA_NER資料集沒有dev dataset,我們這裡重複加載test dataset作為dev_ds
train_ds, dev_ds, test_ds = load_dataset(
        'msra_ner', splits=('train', 'test', 'test'), lazy=False)

label_vocab = {label:label_id for label_id, label in enumerate(train_ds.label_list)}
words = set()
word_vocab = []
for item in train_ds:
    word_vocab += item['tokens'] 
word_vocab = {k:v+2 for v,k in enumerate(set(word_vocab))}
word_vocab['PAD'] = 0
word_vocab['OOV'] = 1
lens = len(train_ds)+len(dev_ds)
print(len(train_ds)/lens,len(dev_ds)/lens,len(test_ds)/lens)
           

輸出結果如下圖2所示:

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結
def convert_tokens_to_ids(tokens, vocab, oov_token='OOV'):
    token_ids = []
    oov_id = vocab.get(oov_token) if oov_token else None
    for token in tokens:
        token_id = vocab.get(token, oov_id)
        token_ids.append(token_id)
    return token_ids


def convert_example(example):
        tokens, labels = example['tokens'],example['labels']
        token_ids = convert_tokens_to_ids(tokens, word_vocab, 'OOV')
        label_ids = labels #convert_tokens_to_ids(labels, label_vocab, 'O')
        return token_ids, len(token_ids), label_ids

train_ds.map(convert_example)
dev_ds.map(convert_example)
test_ds.map(convert_example)
# print(train_ds)
           

輸出結果如下圖3所示:

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結

(三)、構造dataloder

batchify_fn = lambda samples, fn=Tuple(
        Pad(axis=0, pad_val=word_vocab.get('OOV')),  # token_ids
        Stack(),  # seq_len
        Pad(axis=0, pad_val=label_vocab.get('O'))  # label_ids
    ): fn(samples)

train_loader = paddle.io.DataLoader(
        dataset=train_ds,
        batch_size=32,
        shuffle=True,
        drop_last=True,
        return_list=True,
        collate_fn=batchify_fn)

dev_loader = paddle.io.DataLoader(
        dataset=dev_ds,
        batch_size=32,
        drop_last=True,
        return_list=True,
        collate_fn=batchify_fn)

test_loader = paddle.io.DataLoader(
        dataset=test_ds,
        batch_size=32,
        drop_last=True,
        return_list=True,
        collate_fn=batchify_fn)
           

二、網絡建構

随着深度學習的發展,目前主流的序列化标注任務基于詞向量(word embedding)進行表示學習。下面介紹模型的整體訓練流程如下:

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結

序列标注任務常用的模型是RNN+CRF。GRU和LSTM都是常用的RNN單元。這裡我們以Bi-LSTM+CRF模型為例,介紹如何使用 PaddlePaddle 定義序列化标注任務的網絡結構。如下圖所示,LSTM的輸出可以作為 CRF 的輸入,最後 CRF 的輸出作為模型整體的預測結果。

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結
class BiLSTMWithCRF(nn.Layer):
    def __init__(self,
                 emb_size,
                 hidden_size,
                 word_num,
                 label_num,
                 use_w2v_emb=False):
        super(BiLSTMWithCRF, self).__init__()
        self.word_emb = nn.Embedding(word_num, emb_size)
        self.lstm = nn.LSTM(emb_size,
                          hidden_size,
                          num_layers=2,
                          direction='bidirectional')
        self.fc = nn.Linear(hidden_size * 2, label_num + 2)  # BOS EOS
        self.crf = LinearChainCrf(label_num)
        self.decoder = ViterbiDecoder(self.crf.transitions)

    def forward(self, x, lens):
        embs = self.word_emb(x)
        output, _ = self.lstm(embs)
        output = self.fc(output)
        _, pred = self.decoder(output, lens)
        return output, lens, pred

# Define the model netword and its loss
network = BiLSTMWithCRF(300, 300, len(word_vocab), len(label_vocab))
model = paddle.Model(network)
           

三、網絡配置

定義網絡結構後,需要配置優化器、損失函數、評價名額。

評價名額

針對每條序列樣本的預測結果,序列标注任務将預測結果按照語塊(chunk)進行結合并進行評價。評價名額通常有 Precision、Recall 和 F1。

  1. Precision,精确率,也叫查準率,由模型預測正确的個數除以模型總的預測的個數得到,關注模型預測出來的結果準不準
  2. Recall,召回率,又叫查全率, 由模型預測正确的個數除以真實标簽的個數得到,關注模型漏了哪些東西
  3. F1,綜合評價名額,計算公式如下,$F1 = \frac{2PrecisionRecall}{Precision+Recall}$,同時考慮 Precision 和 Recall ,是 Precision 和 Recall 的折中。

paddlenlp.metrics

中內建了

ChunkEvaluator

評價名額,并逐漸豐富中,

optimizer = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
crf_loss = LinearChainCrfLoss(network.crf)
chunk_evaluator = ChunkEvaluator(label_list=label_vocab.keys(), suffix=True)
model.prepare(optimizer, crf_loss, chunk_evaluator)
           

四、模型訓練

model.fit(train_data=train_loader,
              eval_data=dev_loader,
              epochs=5, 
              save_dir='./results',
              log_freq=100)
           

輸出結果如下圖4所示:

【自然語言處理(NLP)】基于LSTM的命名實體識别【自然語言處理(NLP)】基于LSTM的命名實體識别前言一、資料準備二、網絡建構三、網絡配置四、模型訓練五、模型評估六、模型預測總結

五、模型評估

調用

model.evaluate

,檢視序列化标注模型在測試集(test.txt)上的評測結果。

代碼如下:
model.evaluate(eval_data=test_loader, log_freq=10)
           

六、模型預測

利用已有模型,可在未知label的資料集(此處複用測試集test.txt)上進行預測,得到模型預測結果及各label的機率。

def parse_decodes(ds, decodes, lens, label_vocab):
    decodes = [x for batch in decodes for x in batch]
    lens = [x for batch in lens for x in batch]
    print(len(decodes),len(decodes))
    id_label = dict(zip(label_vocab.values(), label_vocab.keys()))
    # print(id_label)
    outputs = []
    i=0
    for idx, end in enumerate(lens):
        sent = ds.data[idx]['tokens'][:end]
        tags = [id_label[x] for x in decodes[idx][:end]]
        sent_out = []
        tags_out = []
        words = ""
        for s, t in zip(sent, tags):
            # {'B-PER': 0, 'I-PER': 1, 'B-ORG': 2, 'I-ORG': 3, 'B-LOC': 4, 'I-LOC': 5, 'O': 6}?
            if t.startswith('B-') or t == 'O':
                if len(words):
                    sent_out.append(words)         # 上一個實體儲存
                tags_out.append(t.split('-')[-1])   # 儲存該實體的類型
                words = s                          
            else:   # 儲存實體的
                words += s
        if len(sent_out) < len(tags_out):
            sent_out.append(words)
        if len(sent_out) != len(tags_out):
            print(len(sent_out),len(tags_out))
            continue
        cs = [str((s, t)) for s, t in zip(sent_out, tags_out)]
        ss = ''.join(cs)
        i+=1
        outputs.append(ss)

    return outputs
           
outputs, lens, decodes = model.predict(test_data=test_loader)
print(len(decodes),len(decodes[0]))
print(len(lens),len(lens[0]))

preds = parse_decodes(test_ds, decodes, lens, label_vocab)
print(preds[0])
print('----------------')
print(preds[1])
print('----------------')
print(preds[2]) 
           

總結

本系列文章内容為根據清華社出版的《自然語言處理實踐》所作的相關筆記和感悟,其中代碼均為基于百度飛槳開發,若有任何侵權和不妥之處,請私信于我,定積極配合處理,看到必回!!!

繼續閱讀