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应用程序代码的基本套路。
- 定义全局变量
- 编写处理函数
- 将处理函数放入流水线中
- 在主函数中调用流水线