2021SC@SDUSC
utils.py写的函数主要被extract.py文件调用,主要有对关键词的初步提取(通过构造语法树,利用正则表达式等方法,之后将介绍),评价召回率、精确度等score分数等函数,此篇博客中将仔细分析。首先来看utils.py的几个函数
本篇博客中我们将分析extract_candidates函数和get_ngram函数。
一、词块划分与语法树的构建
首先来看第一个函数,提取候选关键短语,这里我们先来介绍nltk的句子分割器、分词器和词性标注器。
import nltk
sentences = nltk.sent_tokenize("I like a dog. But he likes a pig. What a pity!")
# 句子分割器
print(sentences)
sentences = [nltk.word_tokenize(sent) for sent in sentences]
# 分词器
print(sentences)
sentences = [nltk.pos_tag(sent) for sent in sentences]
# 词性标注器
print(sentences)
其中,nltk.sent_tokenize函数可以对一段文本或语料文件实现分句,一般以英文中的"."为标志(也可以是"?","!"等标志句尾),将文本分为一句一句话,一句话是一个string类型,作为列表的一个元素,最后所有句子组成的列表,例如,一段文本中有20句话,最后就返回长度为20的列表。
对于此时sentences中的每一个sent,也就是列表中的每一个元素,就是一句话,调用nltk.word_tokenize()函数实现词划分,返回列表的列表,每隔句子被划分为tokens返回一个列表,再由句子的列表组成列表。
此时再对每一个token进行词性标注,调用nltk.pos_tag()函数,最后每个token会返回一个元组,又单词和词性组成。
结果如下:
图1:分句、分词和词性标注结果
接下来我们来分析词块、正则表达式与语法树。
图2:词块划分实例
在自然语言处理中,我们常常把语句划为token,然而,token在英文语料中一般就是单词,这样的划分不利于我们联系上下文进行分析,因此,还可以将文本划分为词块。如图2所示,小的方框是单词,如we,saw等,下方标注了词性,如yellow为jj形容词。大的方框就是词块,如we,the yellow dog。下方标注的同样是其类型,如we是np,也就是名词短语。
要找到一个给定句子的词块划分结构,可以采用RegexpParser词块划分器,接下来分析如何调用词块划分器。
import nltk
sentence = [("the", "DT"), ("little", "JJ"), ("yellow", "JJ"),
("dog", "NN"), ("barked", "VBD"), ("at", "IN"), ("the", "DT"), ("cat", "NN")]
# sentence不是原始的句子,而是经过了句划分、词划分与词性标注后的列表
grammar = "NP: {<DT>?<JJ>*<NN>}"
cp = nltk.RegexpParser(grammar) # 用指定的正则表达式构建RegexpParser
result = cp.parse(sentence)
print(result)
result.draw()
这里用到了构建词块划分的正则表达式,我们来简单解释一下该表达式。NP代表名词短语的词块,DT,JJ,NN是词性标注,分别是限定词、形容词、常用名词单数形式。该表达式表示一个名词短语块由一个可选的限定词后面跟着任意数目的形容词然后一个名词组成。用这样的正则表达式构建词块划分器,然后用该划分器划分sentence,注意这里的sentence不是原始语料,而是像我们之前经过了词、句划分后再经过词性标注得到的元组的列表。最后通过调用draw函数可以绘制该句子的语法树。
为了更好地共同分析词语与词块,可以使用语法树。其结构大致如图4,树根为标签S,之后可以划分出词块NP,NP,而在一个名词短语中又可以包含形容词JJ和单数名词NN,(与我们之前指定的正则表达式相匹配),同时,构建了语法树之后,同时也就有了子树subtree的概念,这里的子树一般就是一个词块,划分不出词块时就是词汇。
得到的语法树如下:
图3:构建出的语法树
图4:构建出的语法树
二、Extract_candidate的分析
def extract_candidates(text):
GRAMMAR_EN = """ NP:
{<NN.*|JJ>*<NN.*>}"""
# 名词短语由可选的形容词和名词构成
keyphrase_candidate = set()
# 初始化候选关键词短语为空集合
np_parser = nltk.RegexpParser(GRAMMAR_EN)
# 构造词块划分器
tag = nltk.pos_tag_sents(nltk.word_tokenize(sent) for sent in nltk.sent_tokenize(text))
#词性标注
trees = np_parser.parse_sents(tag)
# 通过词块划分器生成语法树
for tree in trees:
for subtree in tree.subtrees(filter=lambda t: t.label() == 'NP'):
#取名词短语
# Concatenate the token with a space
keyphrase_candidate.add(' '.join(word for word, tag in subtree.leaves()))
#print(keyphrase_candidate)
keyphrase_candidate = {kp for kp in keyphrase_candidate if len(kp.split()) <= 4}
# 过滤掉长度大于4的名词短语
#print(keyphrase_candidate)
return keyphrase_candidate
这里也是先声明了正则表达式,说明了该语法树的格式,具体来说这里是对名词短语的划分。然后先将候选的关键词集合初始化为空集合,用正则表达式初始化词块划分器。之后是词性标注,这里是先调用nltk.sent_tokenize()分句,再调用nltk.word_tokenize() 分词,最后调用nltk.pos_tag_sents()进行词性标注,返回的结果是元组的列表。
用处理好的(经过词性标注后的tokens)sentence构建语法树,得到trees,这里每一个sentence对应一个tree,然后遍历每个tree,取出subtree,也就是词块,过滤出所有NP名词短语,取这些名词短语(节点)的叶子(也就是单词)加keyphrase_candidate集合中,这里word是叶子结点的单词,tag是每个单词对应的词性。
取出所有NP之后,再进行一次筛选,计算NP中单词的个数,这里使用kp.split()之后再调用len()实现的,然后过滤掉长度大于4的关键短语,即候选的关键短语最多由4个词构成。最后返回keyphrase_candidate集合。
这里我们传入"I like a dog. But he likes a pig."进行测试,得到结果如下,集合中的每个元素对应与一句话的关键短语。
图5:候选关键词输出结果
三、get_ngram的分析
接下来我们来分析get_ngram函数
def get_ngram(text):
can_list=[] # 初始化can_list为列表
for e in text:
tmp = extract_candidates(e.lower) #调用extract_candidates提取候选关键短语
tmp2 = set()
t=set()
for q in tmp:
if '_of' in q:
if q[-3:]=='_of':
continue
q = q.replace('_of', ' of') # 将_of修正为of
t.add(q)
can_list.append(t) # 向can_list中加入修正后的关键短语的集合
return can_list
这里首先是初始化了空列表can_list,然后调用了之前分析的extract_candidate函数,对于tmp中的元素q,若含"_of",判断是否是单词的最后三个字符是"_of",是则跳过,不是则将"_of"修正为"of"然后向集合t中加入修正后的元素p,最后将整个集合作为元素加入can_list。
四、评价指标的分析
在get_fscore函数中,有用到评价指标precision和recall,这里我们先说明这个指标,方便我们下一篇博客中分析get_fscore函数并进行相应计算。
首先是混淆矩阵,在进行分类时,有如下四种情况,我们这里的分类就是指我们的关键短语提取结果和label进行比较的结果。
- True Positive(真正,TP):将正类预测为正类数
- True Negative(真负,TN):将负类预测为负类数
- False Positive(假正,FP):将负类预测为正类数 (Type I error)
- False Negative(假负,FN):将正类预测为负类数 (Type II error)
precision:表示被分为正例的示例中实际为正例的比例。计算公式如下:
recall:度量有多个正例被分为正例,计算公式如下:
综合评价指标,F-Mesure:
F1综合了precision和recall,F1高时说明试验比较有效。 在分析完这些参数之后,我们将在下篇博客中分析get_fscore函数。
五、对爬虫数据的词频统计实现
在nlp中,很多时候需要对词频进行统计,这里我们将分析对网络爬虫的页面中词频统计的代码并绘制词频图。
from bs4 import BeautifulSoup
import urllib.request
import nltk
from nltk.corpus import stopwords
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
首先是相关包的导入,我们将用urllib爬取web页面。爬取下来的数据含有大量的html标签。
response = urllib.request.urlopen('http://php.net/')
html = response.read()
soup = BeautifulSoup(html,"html5lib")
text = soup.get_text(strip=True) # 取出html标签,得到元素内容
tokens = text.split() # 分词
clean_tokens = list()
sr = stopwords.words('english') # 得到英文停词列表
# 去除停词
for token in tokens:
if not token in sr:
clean_tokens.append(token)
# 调用FreqDist得到词频
freq = nltk.FreqDist(clean_tokens)
爬取页面的结果如下,我们需要用BeautifulSoup模块清洗文本,这里的解释器是html5lib,需要提前安装。 调用完BeautifulSoup模块后,首先将网络页面解析成了便于我们理解的html文档格式,接着调用get_text方法就可以去除文档的标签,得到html元素中的内容部分。
图6:爬取完的数据
图7:html文档格式
图7:html元素内容
之后调用split()函数以空格分,得到了tokens。然后加载nltk.corpus中的停词,删去停词。调用nltk实现的FreqDist函数,可以构建一个词典,有词和词频的对应。之后迭代字典中的项,就可绘制词频图。
for key,val in freq.items():
# 输出不同词的计数
print (str(key) + ':' + str(val))
# 绘制freq
freq.plot(20,cumulative=False)
这里从网页爬取完数据后,对html标签的处理非常重要,首先可以调整为html文档格式, 再用get_text得到html元素的内容,之后分词,就可直接点用FreqDist函数得到词典然后绘图。