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應用程序代碼的基本套路。
- 定義全局變量
- 編寫處理函數
- 將處理函數放入流水線中
- 在主函數中調用流水線