1. 程式人生 > >使用條件隨機場模型解決文字分類問題(附Python程式碼)

使用條件隨機場模型解決文字分類問題(附Python程式碼)

對深度學習感興趣,熱愛Tensorflow的小夥伴,歡迎關注我們的網站!http://www.tensorflownews.com。我們的公眾號:磐創AI。

 

一. 介紹

世界上每天都在生成數量驚人的文字資料。Google每秒處理超過40,000次搜尋,而根據福布斯報道,每一分鐘我們都會發送1600萬條簡訊,並在Facebook上釋出510,00條評論。那麼一個外行人來說,是否真的很難處理如此龐大的資料量?

 

僅新聞網站和其他線上媒體每小時就會產生大量的文字內容。如果沒有合適的工具,分析文字資料的模式則是令人生畏的。今天我們將討論一種對文字進行實體識別的方法,稱為條件隨機場(CRF)。

本文通過自己註釋一個數據集來解釋條件隨機場概念,並給出一個python的實現。這是一個非常有趣的概念,相信你會享受理解它的過程!

 

目錄

  • 什麼是實體識別?
  • 案例研究目標和理解不同的方法
  • 公式化條件隨機場(CRF)
  • 使用GATE對文字資料進行打標註釋
  • 用Python構建和訓練CRF模組

 

二. 什麼是實體識別?

隨著對自然語言處理(NLP)受到更多的關注,實體識別最近也變得越發流行。我們通常可以將實體定義為文字中資料科學家更感興趣的一部分。一些被經常提取的實體示例是人名,地址,帳號,位置等。這些只是簡單的示例,我們可以針對自己手動特定的問題定義自己的實體

舉一個採用實體識別的簡單應用:如果資料集中存在任何帶有“倫敦”的文字,演算法會自動將這段文字歸類或分類為一個位置。

讓我們以一個簡單的案例研究來更好地理解我們的主題。

三. 案例研究目標和理解不同的方法

假設你是保險公司分析團隊的一員,每天索賠團隊會收到客戶關於其索賠的數千封電子郵件。索賠運營團隊會檢查每封電子郵件,並在處理這些電子郵件之前使用郵件中的詳細資訊來更新線上表單。

系統會要求您與IT團隊合作,自動完成預填充線上表單的過程。對於這個任務,分析團隊需要實現自定義實體識別演算法。

要識別文字中的實體,必須能夠識別它的模式。例如,如果我們要識別索賠號,我們可以檢視它周圍的單詞,例如“我的ID是”或“我的號碼是”等。下面提到的幾種方法都可以用來來識別模式。

  • 正則表示式:正則表示式(RegEx)是有限狀態自動機的一種形式。它非常有助於識別遵循特定結構的模式。例如,可以使用RegEx很好地識別電子郵件ID,電話號碼等。然而,這種方法的缺點是需要知道在索賠號之前出現的所有可能的確切詞。這不是一種自學習的方法,而是一種蠻力的方法
  • 隱馬爾可夫模型(HMM):這是一種識別和學習模式的序列建模演算法。儘管HMM考慮了通過觀察估計實體周圍的未來狀態來學習它的模式,但它假設這些特徵彼此獨立。這種方法比正則表示式更好,因為我們不需要對確切的單詞集進行建模。但就效能而言,它並非實體識別的最佳方法
  • MaxEnt Markov模型(MEMM):這也是一種序列建模演算法。這並不假設特徵彼此獨立,也不考慮觀察未來狀態來學習模式。在效能方面,它也並非實體識別的最佳方法
  • 條件隨機場(CRF):這也是一種序列建模演算法。這不僅假設特徵相互依賴,而且在學習模式時也考慮未來的觀察。這結合了HMM和MEMM的優點。在效能方面,它被認為是解決實體識別問題的最佳方法

四. 條件隨機場的公式表達

單詞包(BoW)方法適用於多文字分類問題。這種方法假定單詞的存在或不存在比單詞的順序更重要。然而,在諸如實體識別,詞性識別之類的問題中,單詞序列則顯得更重要。條件隨機場(CRF)在這時候就可以發揮作用了,因為它使用到了單詞序列而不僅僅是單詞。

現在讓我們瞭解下CRF的公式表達。

下面是CRF的公式,其中Y是隱藏狀態(例如詞性),X是觀察到的變數(在我們的示例中,就是實體單詞或其周圍的其他單詞)。

 

從廣義上講,CRF公式有兩個組成部分:

歸一化:你可能已經觀察到在我們具有權重和特徵的等式的右側沒有概率。但是左邊是概率,因此需要歸一化。歸一化常數Z(x)是所有可能的狀態序列的總和,使得總和為1。你可以在本文的參考部分中找到更多詳細資訊,以瞭解我們如何得到這個值。

權重和特徵:這部分可以被視為具有權重和相應特徵的邏輯迴歸公式。通過最大似然估計來進行權重估計,並由我們自己定義特徵。

五. 註釋訓練資料

現在您已瞭解CRF模型,讓我們著手準備訓練資料。這樣做的第一步是註釋。註釋是使用相應的實體標記標記單詞的過程。為簡單起見,我們假設我們只需要2個實體來填充線上表格,即索賠人姓名和索賠號。

以下是按原樣收到的示例電子郵件。需要對這些電子郵件進行註釋,以便可以訓練CRF模型。帶註釋的文字需要採用XML格式。雖然您可以選擇以您的方式註釋文件,但我將引導您使用GATE這個提供文字基礎服務的軟體來執行這個操作。

 

收到的電郵:

“Hi,

I am writing thisemail to claim my insurance amount. My id is abc123 and I claimed iton 1st January 2018. I did not receive any acknowledgement. Please help.

Thanks,

randomperson”

 

帶註釋的電子郵件:

“<document>Hi, Iam writing this email to claim my insurance amount. My idis <claim_number>abc123</claim_number> and I claimed on1st January 2018. I did not receive any acknowledgement. Please help.Thanks, <claimant>randomperson</claimant></document>”

 

使用GATE的註釋功能:

讓我們瞭解如何使用文字工程通用架構(GATE)。請按照以下步驟安裝GATE。

從以下連結下載最新版本:

https://gate.ac.uk/download/#latest

通過執行下載的安裝程式按安裝步驟安裝GATE平臺。

安裝後,執行應用程式可執行檔案。應用程式開啟後,右鍵單擊“LanguageResources”>New>GATE Document,將電子郵件迭代載入到語言資源中。給每個電子郵件命名,將編碼設定為“utf-8”,這樣就沒有Python方面的問題,通過單擊sourceUrl部分的圖示導航到需要註釋的電子郵件。

一次開啟一封電子郵件並開始註釋練習。構建註釋有兩種方法。

A. 將註釋好的xml載入到GATE並使用它

B. 在GATE中即時建立註釋並使用它們。在本文中,我們將演示這種方法。

單擊“LanguageResources”部分中的電子郵件開啟它。單擊“Annotation Sets”,然後選擇單詞或單詞並將游標放在其上幾秒鐘就會出現註釋的彈出視窗,然後您可以輸入註釋代替“_NEW_”並按Enter鍵,這將建立一個新註釋,如下所示。對每封電子郵件的所有註釋重複這個操作。

一旦所有要訓練的電子郵件都被註釋,通過導航到Language Resources> NEW> GATE Corpus建立易於使用的語料庫。

通過右鍵單擊語料庫並導航到“InlineXML(.xml)”,將語料庫儲存到你電腦上的資料夾裡。

在下一個彈出視窗中,選擇預先填充的註釋型別並將其刪除。手動鍵入註釋並新增它們以代替預先填充的註釋。通過單擊將“includeFeatures”選項設定為false,然後在rootElement框中鍵入“document”。完成所有這些更改後,單擊“save as”圖示將檔案儲存到計算機上的資料夾中。

由GATE生成好的註釋郵件

重複上述過程將所有帶註釋的電子郵件儲存在一個資料夾中。

六. 用Python構建和訓練CRF模組

首先下載pycrf模組。對於PIP安裝,命令是“ pip install python-crfsuite ”,對於conda安裝,命令是“ conda install -cconda-forge python-crfsuite ”。

安裝完成後,您就可以開始訓練和構建自己的CRF模組了。好,咱們開工吧!

#匯入需要的工具包
from bs4 import BeautifulSoup as bs
from bs4.element import Tag
import codecs
import nltk
from nltk import word_tokenize, pos_tag
from sklearn.model_selection import train_test_split
import pycrfsuite
import os, os.path, sys
import glob
from xml.etree import ElementTree
import numpy as np
from sklearn.metrics import classification_report

讓我們再定義一些函式。

# 獲得所有郵件新增到一個列表中
def append_annotations(files):
   xml_files = glob.glob(files+"/*.xml")
   xml_element_tree = None
   new_data = ""
   for xml_file in xml_files:
       data =ElementTree.parse(xml_file).getroot()
       #printElementTree.tostring(data)       
       temp =ElementTree.tostring(data)
       new_data += (temp)
   return(new_data)

# 該函式移除文字中的特殊符號
def remov_punct(withpunct):
   punctuations ='''!()-[]{};:'"\,<>./[email protected]#$%^&*_~'''
   without_punct = ""
   char = 'nan'
   for char in withpunct:
       if char not inpunctuations:
           without_punct =without_punct + char
   return(without_punct)

# 該函式將從每條郵件文字中提取我們想要的特徵
def extract_features(doc):
   return [word2features(doc, i)for i in range(len(doc))]

def get_labels(doc):
   return [label for (token,postag, label) in doc]

接著我們匯入註釋好的訓練資料。

files_path = "./Annotated"
allxmlfiles = append_annotations(files_path)
soup = bs(allxmlfiles, "html5lib")

#對帶註釋的單詞進行標記
docs = []
for d in soup.find_all("document"):
   sents = []
   for wrd in d.contents:
       tags = []
       NoneType = type(None)
       if isinstance(wrd.name,NoneType) == True:
           withoutpunct =remov_punct(wrd)
           temp =word_tokenize(withoutpunct)
           for token in temp:
               tags.append((token,'NA'))
       else:
           withoutpunct =remov_punct(wrd)
           temp =word_tokenize(withoutpunct)
           for token in temp:
              tags.append((token,wrd.name))
       sents = sents + tags
docs.append(sents)

下面這部分程式碼的功能是生成特徵,這些特徵是使用nltk工具包生成的,作為命名實體識別演算法的輸入,你也可以用其他特定工具生成特徵。(例如在中文語料上可以用HanLP,FNLP等工具生成特徵)。

data = []

for i, doc in enumerate(docs):
   tokens = [t for t, label indoc]   
   tagged =nltk.pos_tag(tokens)   
   data.append([(w, pos, label)for (w, label), (word, pos) in zip(doc, tagged)])

def word2features(doc, i):
   word = doc[i][0]
   postag = doc[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
       ]
       
    #若單詞不在結尾處
   if i > 0:
       word1 = doc[i-1][0]
       postag1 = doc[i-1][1]
       features.extend([
           '-1:word.lower=' +word1.lower(),
           '-1:word.istitle=%s' %word1.istitle(),
           '-1:word.isupper=%s' %word1.isupper(),
           '-1:word.isdigit=%s' %word1.isdigit(),
           '-1:postag=' + postag1
       ])
   else:
       # 註明該單詞位於文字開頭
       features.append('BOS')

   # 若單詞不在結尾處
   if i < len(doc)-1:
       word1 = doc[i+1][0]
       postag1 = doc[i+1][1]
       features.extend([
           '+1:word.lower=' +word1.lower(),
           '+1:word.istitle=%s' %word1.istitle(),
           '+1:word.isupper=%s' %word1.isupper(),
           '+1:word.isdigit=%s' %word1.isdigit(),
           '+1:postag=' + postag1
       ])
   else:
       # 註明該單詞位於文字結尾
       features.append('EOS')
return features

#提取出特徵和標籤,並劃分為測試資料和訓練資料。
X = [extract_features(doc) for doc in data]
y = [get_labels(doc) for doc in data]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 載入模型測試一下
tagger = pycrfsuite.Tagger()
tagger.open('crf.model')
y_pred = [tagger.tag(xseq) for xseq in X_test]

# 通過選擇行標來檢視預測值
i = 0
for x, y in zip(y_pred[i], [x[1].split("=")[1] for x inX_test[i]]):
print("%s (%s)" % (y, x))

# 建立一個標籤的索引
labels = {"claim_number": 1, "claimant":1,"NA": 0}

# 將預測出來的標籤序列轉化為一維ndarray
predictions = np.array([labels[tag] for row in y_pred for tag in row])
truths = np.array([labels[tag] for row in y_test for tag in row])

# 列印分類報告
print(classification_report(
   truths, predictions,
target_names=["claim_number","claimant","NA"]))

到目前為止,你已經瞭解如何註釋訓練資料,如何使用Python訓練CRF模型,以及最後如何從文字中識別實體。雖然此演算法提供了一些基本功能,但你也可以提出自己的一組功能來提高模型的準確性。

 

對深度學習感興趣,熱愛Tensorflow的小夥伴,歡迎關注我們的網站!http://www.tensorflownews.com。我們的公眾號:磐創AI。