编写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. 在主函数中调用流水线
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章