編寫NLP處理程序的套路學習1---------命名實體識別的Chanel實現

1、引子

    練武之人無論天資再聰明沒有師傅的指點,或者武林祕籍都不可能自創一套體系,因此各種武功祕籍成了武林爭奪的至寶,大家爭破頭皮也要擠進名門正派。對於學習自然科學及工程領域,自己如果沒有團隊,沒有師傅耐心的帶着學習。自學的東西可能非常零散,即使涉獵範圍非常廣泛。但是沒有系統性的學習難成氣候。就像學武術無師無派一樣,在這種情況之下,武林祕籍就顯得尤爲重要了,這個武林祕籍就是別人寫好的論文和代碼。論文像武功心法,代碼就是招式。所以缺一不可。所以好好研究一下別人的論文的同時,研究一下論文對應代碼是必須的。
    但是對代碼的學習也不能一蹴而就,從基本的套路學起是一個必經的步驟。

2、命名實體識別

    學習一個從RSS源獲取數據,將識別到的命名實體輸出到對應文件的例子。
依賴庫:
    nltk, queue, feedparse, uuid
這幾個庫的作用分別是:nltk處理文本,queue建立處理隊列feedparse對URL對應的內容進行解析,uuid產生的ID號。
queue主要用到的函數有(1)q1.put(data,True) 將數據放入隊列,線程安全,阻塞式添加;(2)q1.get()從隊列中取數據,線程安全,阻塞式獲取(2)q1.empty()當前隊列是否爲空;(3)q1.task_done()當前任務完成。表明以前排隊的任務(示例如使用一個url爬取網頁內容完成)已完成。由隊列使用者線程使用。每次調用get()方法從隊列中獲取任務,如果任務處理完畢,則條用task_done()方法,告知等待的隊列(queue.join()這裏在等待)任務的處理已完成。(4)q1.join()如果join()當前正在阻塞,則它將在所有項目都已處理後恢復(這意味着已爲每個已放入隊列的項目收到task_done()調用)。如果調用的次數超過隊列中放置的項目,則引發ValueError。
feedparse用到的函數爲feadparse.parse(url)將url對應的網頁進行解析。
nltk主要用到的函數有(1)ntlk.tokenize(sentence)分詞;(2)nltk.pos_tag(words)給單詞列表標定單詞類型。(3)nltk.ne_chunk()輸入爲標記了詞性的數據,輸出爲命名實體。

#__*__ coding=utf-8 __*__
# author: qian yanjun
# create_date: 2020.2.25
import nltk
import threading
import queue
import uuid
import feedparser

import sys
print(sys.getdefaultencoding())

# 定義線程隊列
threads = []
#定義兩個隊列一個存儲分過詞的句子,一個存儲標註過詞性的單詞
queues = [queue.Queue(), queue.Queue()]

#從給定數據源(RSS新聞源)獲取數據並分詞,並放入隊列中
def extractwords():
    url = "https://www.engadget.com/rss.xml"
    #將url對應的網站轉化爲新聞項目,feed的結構包括
    feed = feedparser.parse(url)
    #entry是一個鍵值對包含的鍵包括title、title_detail、link、conmments、published、authors等等
    for entry in feed["entries"]:
        #獲取項目的標題,標題爲字符串
        text = entry["title"]
        #跳過敏感詞
        if "反動" in text:
            continue
        #將內容分詞
        words = nltk.word_tokenize(text)
        #爲當前內容創建單獨的id,將建立Id:內容字典
        data = {"uuid":uuid.uuid4(), "input":words}
        queues[0].put(data,True)
        print(">>{}:{}".format(data["uuid"],text))

#對標題隊列中的所有實例進行詞性標註,放入另一個隊列中
def extractPOS():
    while True:
        if queues[0].empty():
            break
        else:
            #data是隊列中的字典鍵值對
            data = queues[0].get()
            #包含了標題中被分割的多個詞
            words = data["input"]
            #將標註標記賦值給postags
            postags = nltk.pos_tag(words)
            #完成隊列中的一個元素的操作
            queues[0].task_done()
            #在隊列列表中的另一個隊列中加入字典,鍵爲id,值爲該標題對應的標註標集
            queues[1].put({"uuid":data["uuid"],"input":postags})

#將隊列中的實體標註出來
def extractNE():
    #遍歷隊列1中的所有內容,並進行處理
    while True:
        #隊列爲空時,停止該線程
        if queues[1].empty():
            break
        else:
            #從隊列列表中取第一個隊列順序獲取該隊列的當前值,該當前值爲詞典
            data = queues[1].get()
            #獲取詞典“input"鍵對應的值,即當前標題對應的標註集
            postags = data["input"]
            #標記該操作已完成
            queues[1].task_done()
            #返回標註集對應的實體列表
            chunks = nltk.ne_chunk(postags,binary=False)
            #將隊列中所有的新聞標題
            print("<<{}:".format(data["uuid"]),end="")
            for path in chunks:
                try:
                    label = path.label()
                    print(path, end = ", ")
                except:
                    pass



def runProgram():
    #建立線程運行獲得指定數據源內的所有標題
    e = threading.Thread(target=extractwords())
    e.start()
    threads.append(e)

    #建立線程運行獲得數據源標題的所有詞性標註
    p = threading.Thread(target=extractPOS())
    p.start()
    threads.append(p)

    #建立線程獲取所有標題內的實體
    n = threading.Thread(target=extractNE())
    n.start()
    threads.append(n)

    #等待處理完所有的隊列
    queues[0].join()
    queues[1].join()

    #等待所有的線程完成操作
    for t in threads:
        t.join()

if __name__ == "__main__":
    runProgram()

    運行結果如下:
在這裏插入圖片描述
    非常不幸的是,調用nltk自帶的詞性標註和NER識別器在第一個標題中取得了很好的結果,但是第二個句子卻沒有識別出形影的實體。
    這段代碼實現了一個文本實體識別的方法,來自於《自然語言處理python進階》第八章。該代碼只更改了網站源,添加了中文註釋,但是這段代碼並不是一段安全的代碼。因爲使用了多線程,很容易出現死鎖。但,這段代碼給了我們一個編寫NLP應用程序代碼的基本套路。

  1. 定義全局變量
  2. 編寫處理函數
  3. 將處理函數放入流水線中
  4. 在主函數中調用流水線
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章