條件隨機場實現命名實體識別

超人汪小建發表於2019-03-02

前言

NLP 被很多人稱為人工智慧皇冠上的明珠,可見其在 AI 領域的重要性,而命名實體識別(NER)又一直是 NLP 領域的研究熱點,所以這塊任務是 NLP 必談的。

NER 早期的實現主要是基於詞典和規則,然後是基於傳統的機器學習,比如 HMM、MEMM 和 CRF。隨後深度學習崛起則很多用 CRF 結合迴圈神經網路或卷積神經網路來做。而最近期的則是基於注意力模型和遷移學習等。

其實 NER 的主流核心演算法是條件隨機場(CRF),包括後來的深度學習和注意力模型都是需要結合 CRF 來使用,所以這篇文章看看 CRF 怎麼實現命名實體識別。

關於條件隨機場

CRF 即條件隨機場(Conditional Random Fields),是在給定一組輸入隨機變數條件下另外一組輸出隨機變數的條件概率分佈模型,它是一種判別式的概率無向圖模型,既然是判別式,那就是對條件概率分佈建模。

在 NLP 中,CRF 是用於標註和劃分序列資料的概率化模型,根據 CRF 的定義,相對序列就是給定觀測序列 X 和輸出序列 Y,然後通過定義條件概率 P(Y|X) 來描述模型。

詳細可以看前面的文章《機器學習之條件隨機場(CRF)》。

NER語料庫

為方便可直接用 nltk 提供的命名實體識別語料庫,通過以下進行下載。

>>> import nltk
>>> nltk.download(`conll2002`)
[nltk_data] Downloading package conll2002 to
[nltk_data]     C:Users84958AppDataRoaming
ltk_data...
[nltk_data]   Unzipping corporaconll2002.zip.
複製程式碼

讀取語料,

train_sents = list(nltk.corpus.conll2002.iob_sents(`esp.train`))
test_sents = list(nltk.corpus.conll2002.iob_sents(`esp.testb`))
複製程式碼

特徵函式

定義我們的特徵函式,這裡其實更像是定義特徵函式的模板,因為真正的特徵函式會根據這個定義的模板去生成,而且一般生成的特徵函式數量是相當大的,然後通過訓練確定每個特徵函式對應的權重。

以下面程式碼看特徵的選取,包括單詞小寫、單詞倒數2和3的字尾、是否為大寫、是否為title、是否為數字、標籤、標籤字首、前一個單詞的相關屬性、後一個單詞的相關屬性。

def word2features(sent, i):
    word = sent[i][0]
    postag = sent[i][1]
    features = [
        `bias`,
        `word.lower=` + word.lower(),
        `word[-3:]=` + word[-3:],
        `word[-2:]=` + word[-2:],
        `word.isupper=%s` % word.isupper(),
        `word.istitle=%s` % word.istitle(),
        `word.isdigit=%s` % word.isdigit(),
        `postag=` + postag,
        `postag[:2]=` + postag[:2],
    ]
    if i > 0:
        word1 = sent[i - 1][0]
        postag1 = sent[i - 1][1]
        features.extend([
            `-1:word.lower=` + word1.lower(),
            `-1:word.istitle=%s` % word1.istitle(),
            `-1:word.isupper=%s` % word1.isupper(),
            `-1:postag=` + postag1,
            `-1:postag[:2]=` + postag1[:2],
        ])
    else:
        features.append(`BOS`)
    if i < len(sent) - 1:
        word1 = sent[i + 1][0]
        postag1 = sent[i + 1][1]
        features.extend([
            `+1:word.lower=` + word1.lower(),
            `+1:word.istitle=%s` % word1.istitle(),
            `+1:word.isupper=%s` % word1.isupper(),
            `+1:postag=` + postag1,
            `+1:postag[:2]=` + postag1[:2],
        ])
    else:
        features.append(`EOS`)
    return features
複製程式碼

訓練模型

接著可以開始建立 Trainer 進行訓練,將語料的每個句子轉成特徵及標籤列表,然後設定好 Trainer 的相關引數,並將樣本新增到 Trainer 中開始訓練。最終會將模型儲存到model_path中。

def train():
    X_train = [sent2features(s) for s in train_sents]
    y_train = [sent2labels(s) for s in train_sents]

    trainer = pycrfsuite.Trainer(verbose=False)
    trainer.set_params({
        `c1`: 1.0,  
        `c2`: 1e-3, 
        `max_iterations`: 50, 
        `feature.possible_transitions`: True
    })

    for xseq, yseq in zip(X_train, y_train):
        trainer.append(xseq, yseq)

    trainer.train(model_path)
複製程式碼

預測

建立 Tagger 並載入模型,即可在測試集中選擇一個的句子打標籤。

def predict():
    tagger = pycrfsuite.Tagger()
    tagger.open(model_path)
    example_sent = test_sents[3]
    print(` `.join(sent2tokens(example_sent)), end=`

`)
    print("Predicted:", ` `.join(tagger.tag(sent2features(example_sent))))
    print("Correct:  ", ` `.join(sent2labels(example_sent)))
複製程式碼

比如下面的預測結果。

García Aranda presentó a la prensa el sistema Amadeus , que utilizan la mayor parte de las agencias de viajes españolas para reservar billetes de avión o tren , así como plazas de hotel , y que ahora pueden utilizar también los usuarios finales a través de Internet .

Predicted: B-PER I-PER O O O O O O B-MISC O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O B-MISC O
Correct:   B-PER I-PER O O O O O O B-MISC O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O B-MISC O
複製程式碼

評估

最後是評估我們的模型總體效果,將測試集中所有句子輸入到訓練出來的模型,將得到的預測結果與測試集句子對應的標籤對比,輸出各項指標。

def bio_classification_report(y_true, y_pred):
    lb = LabelBinarizer()
    y_true_combined = lb.fit_transform(list(chain.from_iterable(y_true)))
    y_pred_combined = lb.transform(list(chain.from_iterable(y_pred)))

    tagset = set(lb.classes_) - {`O`}
    tagset = sorted(tagset, key=lambda tag: tag.split(`-`, 1)[::-1])
    class_indices = {cls: idx for idx, cls in enumerate(lb.classes_)}

    return classification_report(
        y_true_combined,
        y_pred_combined,
        labels=[class_indices[cls] for cls in tagset],
        target_names=tagset,
    )


def evaluate():
    tagger = pycrfsuite.Tagger()
    tagger.open(model_path)
    X_test = [sent2features(s) for s in test_sents]
    y_test = [sent2labels(s) for s in test_sents]
    y_pred = [tagger.tag(xseq) for xseq in X_test]
    print(bio_classification_report(y_test, y_pred))
複製程式碼

比如下面的結果。

             precision    recall  f1-score   support

      B-LOC       0.78      0.75      0.76      1084
      I-LOC       0.66      0.60      0.63       325
     B-MISC       0.69      0.47      0.56       339
     I-MISC       0.61      0.49      0.54       557
      B-ORG       0.79      0.81      0.80      1400
      I-ORG       0.80      0.79      0.80      1104
      B-PER       0.82      0.87      0.84       735
      I-PER       0.87      0.93      0.90       634

avg / total       0.77      0.76      0.76      6178
複製程式碼

github

https://github.com/sea-boat/nlp_lab/blob/master/crf_ner/crf_ner.py

最後

crf 比起 hmm 來對特徵的設計更加靈活,而且 crf 是無向圖,比如 hmm 的有向圖可以提取出更多的特徵,所以總體效果要比 hmm 要好,但是它也有自己的缺點,複雜度高,大量的特徵函式使得訓練時代價高。

————-推薦閱讀————

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇

——————廣告時間—————-

跟我交流,向我提問:

這裡寫圖片描述

公眾號的選單已分為“分散式”、“機器學習”、“深度學習”、“NLP”、“Java深度”、“Java併發核心”、“JDK原始碼”、“Tomcat核心”等,可能有一款適合你的胃口。

為什麼寫《Tomcat核心設計剖析》

歡迎關注:

這裡寫圖片描述

相關文章