1. 程式人生 > >NLTK學習筆記(七):文字資訊提取

NLTK學習筆記(七):文字資訊提取

目錄

如何構建一個系統,用於從非結構化的文字中提取結構化的資訊和資料?哪些方法使用這類行為?哪些語料庫適合這項工作?是否可以訓練和評估模型?

資訊提取,特別是結構化資訊提取,可以類比資料庫的記錄。對應的關係綁定了對應的資料資訊。針對自然語言這類非結構化的資料,為了獲取對應關係,應該搜尋實體對應的特殊關係,並且用字串、元素等一些資料結構記錄。

實體識別:分塊技術

比如:We saw the yellow dog ,按照分塊的思想,會將後三個詞語分到NP中,而裡面的三個詞又分別對應 DT/JJ/NN;saw 分到VBD中;We 分到NP中。對於最後三個詞語來說,NP就是組塊(較大的集合)。為了做到這點,可以藉助NLTK自帶的分塊語法,類似於正則表示式,來實現句子分塊。

分塊語法的構建

注意三點即可:

  • 基本的分塊:組塊 :{組塊下的子組塊}(類似於:"NP: {<DT>?<JJ>*<NN>}"這樣的字串)。而?*+儲存了正則表示式的意義。
import nltk
sentence = [('the','DT'),('little','JJ'),('yellow','JJ'),('dog','NN'),('brak','VBD')]
grammer = "NP: {<DT>?<JJ>*<NN>}"
cp = nltk.RegexpParser(grammer) #生成規則
result = cp.parse(sentence) #進行分塊
print(result)

result.draw() #呼叫matplotlib庫畫出來
  • 可以為不包括再大塊中的識別符號序列定義一個縫隙}<VBD|IN>+{
import nltk
sentence = [('the','DT'),('little','JJ'),('yellow','JJ'),('dog','NN'),('bark','VBD'),('at','IN'),('the','DT'),('cat','NN')]
grammer = """NP: 
            {<DT>?<JJ>*<NN>}
            }<VBD|NN>+{
            """  #加縫隙,必須儲存換行符
cp = nltk.RegexpParser(grammer) #生成規則
result = cp.parse(sentence) #進行分塊
print(result)
  • 可以遞迴式的呼叫,這符合語言結構中的遞迴巢狀。例如:VP: {<NP|PP|CLAUSE>*} PP:{<NN><VP>} 。此時,RegexpParser函式的引數loop即可以設定為2,多次迴圈,來防止遺漏。

樹狀圖

如果呼叫print(type(result))檢視型別就會發現,是 nltk.tree.Tree。從名字看出來這是一種樹狀結構。nltk.Tree 可以實現樹狀結構,並且支援拼接技術,提供結點的查詢和樹的繪製。

tree1 = nltk.Tree('NP',['Alick'])
print(tree1)
tree2 = nltk.Tree('N',['Alick','Rabbit'])
print(tree2)
tree3 = nltk.Tree('S',[tree1,tree2])
print(tree3.label()) #檢視樹的結點
tree3.draw()

IOB標記

分別代表內部,外部,開始(就是英語單詞的首字母)。對於上面講的 NP,NN這樣的分類,只需要在前面加上 I-/B-/O-即可。這樣就能使規則外的集合被顯式出來,類似上面的加縫隙。

開發和評估分塊器

NLTK已經為我們提供了分塊器,減少了手動構建規則。同時,也提供了已經分塊好的內容,供我們自己構建規則時候進行參考。

#這段程式碼在python2下執行
from nltk.corpus import conll2000
print conll2000.chunked_sents('train.txt')[99] #檢視已經分塊的一個句子

text = """
   he /PRP/ B-NP
   accepted /VBD/ B-VP
   the DT B-NP
   position NN I-NP
   of IN B-PP
   vice NN B-NP
   chairman NN I-NP
   of IN B-PP
   Carlyle NNP B-NP
   Group NNP I-NP
   , , O
   a DT B-NP
   merchant NN I-NP
   banking NN I-NP
   concern NN I-NP
   . . O
"""
result = nltk.chunk.conllstr2tree(text,chunk_types=['NP'])

對於之前自己定義的規則cp,可以使用cp.evaluate(conll2000.chunked_sents('train.txt')[99]) 來測試正確率。利用之前學過的Unigram標註器,可以進行名詞短語分塊,並且測試準確度

class UnigramChunker(nltk.ChunkParserI):
    """
        一元分塊器,
        該分塊器可以從訓練句子集中找出每個詞性標註最有可能的分塊標記,
        然後使用這些資訊進行分塊
    """
    def __init__(self, train_sents):
        """
            建構函式
            :param train_sents: Tree物件列表
        """
        train_data = []
        for sent in train_sents:
            # 將Tree物件轉換為IOB標記列表[(word, tag, IOB-tag), ...]
            conlltags = nltk.chunk.tree2conlltags(sent)

            # 找出每個詞性標註對應的IOB標記
            ti_list = [(t, i) for w, t, i in conlltags]
            train_data.append(ti_list)

        # 使用一元標註器進行訓練
        self.__tagger = nltk.UnigramTagger(train_data)

    def parse(self, tokens):
        """
            對句子進行分塊
            :param tokens: 標註詞性的單詞列表
            :return: Tree物件
        """
        # 取出詞性標註
        tags = [tag for (word, tag) in tokens]
        # 對詞性標註進行分塊標記
        ti_list = self.__tagger.tag(tags)
        # 取出IOB標記
        iob_tags = [iob_tag for (tag, iob_tag) in ti_list]
        # 組合成conll標記
        conlltags = [(word, pos, iob_tag) for ((word, pos), iob_tag) in zip(tokens, iob_tags)]

        return nltk.chunk.conlltags2tree(conlltags)
test_sents = conll2000.chunked_sents("test.txt", chunk_types=["NP"])
train_sents = conll2000.chunked_sents("train.txt", chunk_types=["NP"])

unigram_chunker = UnigramChunker(train_sents)
print(unigram_chunker.evaluate(test_sents))

命名實體識別和資訊提取

命名實體:確切的名詞短語,指特定型別的個體,如日期、人、組織等 。如果自己去許槤分類器肯定頭大(ˉ▽ ̄~)~~。NLTK提供了一個訓練好的分類器--nltk.ne_chunk(tagged_sent[,binary=False]) 。如果binary被設定為True,那麼命名實體就只被標註為NE;否則標籤會有點複雜。

sent = nltk.corpus.treebank.tagged_sents()[22]
print(nltk.ne_chunk(sent,binary=True))

如果命名實體被確定後,就可以實現關係抽取來提取資訊。一種方法是:尋找所有的三元組(X,a,Y)。其中X和Y是命名實體,a是表示兩者關係的字串,示例如下:

#請在Python2下執行
import re
IN = re.compile(r'.*\bin\b(?!\b.+ing)')
for doc in nltk.corpus.ieer.parsed_docs('NYT_19980315'):
    for rel in nltk.sem.extract_rels('ORG','LOC',doc,corpus='ieer',pattern = IN):
        print nltk.sem.show_raw_rtuple(rel)