词性标注
一般而言,文本里的动词可能比较重要,而助词可能不太重要:
- 我今天真好看
- 我今天真好看啊
甚至有时候同一个词有着不同的意思:
- 我一把把把把住了
越是常用的词,不同的用法越多。由于兼类使用程度高,兼类现象涉及汉语中大部分词类,因而造成在汉语文本中词类歧义排除的任务量巨大。
那么这个时候可以先对词汇进行词性标注(即在文本中判定每个词的语法范畴,确定其词性并加以标注的过程),再基于词性提取关键词。因此,词性标注为文本处理提供了相当关键的信息。
方法
- 基于规则的词性标注方法:较早的⼀种词性标注方法,其基本思想是按兼类词搭配关系和上下文语境建造词类消歧规则。早期的规则⼀般由人工构建。随着标注语料库规模的增⼤,可利用的资源也变得越来越多,这时候以人工提取规则的方法显然有些力不从心,而需要通过数据统计让机器发现一些规律。
- 基于统计模型的词性标注方法:将词性标注看作是⼀个序列标注问题。其基本思想是,给定带有各自标注的词的序列,我们可以确定下⼀个词最可能的词性。常用的模型有有隐马尔可夫模型(HMM)、条件随机场(CRF)等统计模型。
- 基于统计方法与规则方法相结合的词性标注方法 :这类方法的主要特点在于对统计标注结果的筛选,只对那些被认为可疑的标注结果,才采用规则方法进行歧义消解,而不是对所有情况都既使用统计方法又使⽤规则方法。
- 基于深度学习的词性标注⽅法:可以当作序列标注的任务来做,目前深度学习解决序列标注任务常用方法包括 LSTM+CRF、BiLSTM+CRF 等。
工具
- jieba
- SnowNLP
- THULAC
- StanfordCoreNLP
- HanLP
- NLTK
- SpaCy:注意不支持中文
实例
以 jieba 为例,我们来实现中文的词性标注:
import jieba.posseg
# jieba.posseg.cut()能够同时实现分词并词性标注
sentence_taged = jieba.posseg.cut("处理自然语言就是那么简单啊。")
for i in sentence_taged:
print(i.word, i.flag) # flag 即表示词性
# 输出结果:
# 处理 v
# 自然语言 l
# 就是 d
# 那么 r
# 简单 a
# 啊 zg
# 。 x
接下来以 NLTK 为例,我们来实现英文的词性标注:
使用 NLTK 对英文文本进行处理时,首先需要下载 NLTK 拓展包,可以使用 nltk.download()
来选择性下载所需拓展包或者直接使用 python -m nltk.downloader all
下载全部数据拓展包。
import nltk
# 注意,nltk.word_tokenize()是对英文的分词,nltk.pos_tag()为词性标注
sentence_taged = nltk.pos_tag(nltk.word_tokenize("Great minds think alike. "))
print(sentence_taged)
# 输出结果:
# [('Great', 'JJ'), ('minds', 'NNS'), ('think', 'VBP'), ('alike', 'RB'), ('.', '.')]
词干提取和词形还原
搜索引擎中键入 “apple”,查询相关的文章,假如有个文本中包含了 “apples” ,它是否也该返回作为搜索结果呢?理论上是的。所以在这里,就需要对文本中的 “apples” 做一些特殊处理,以便其能与 “apple” 产生关联,这里就涉及到了两种技术,即词干提取以及词形还原。
但是词干提取与词形还原只针对具有形态变化的语言,比如英文、法文、德文等,中文是固定形态语言,因此并不存在这两种预处理操作。 接下来应用 NLTK 工具包进行词干提取以及词形还原:
# 导入下面三种词干提取器进行对比
import nltk.stem.porter as pt
import nltk.stem.lancaster as lc
import nltk.stem.snowball as sb
words = ['table', 'probably', 'wolves', 'playing', 'is', 'dog', 'the', 'beaches',
'grounded', 'dreamt', 'envision']
pt_stemmer = pt.PorterStemmer()
lc_stemmer = lc.LancasterStemmer()
sb_stemmer = sb.SnowballStemmer("english")
for word in words:
pt_stem = pt_stemmer.stem(word)
lc_stem = lc_stemmer.stem(word)
sb_stem = sb_stemmer.stem(word)
print("%8s %8s %8s %8s" % (word, pt_stem, lc_stem, sb_stem))
输出结果:
table tabl tabl tabl
probably probabl prob probabl
wolves wolv wolv wolv
playing play play play
is is is is
dog dog dog dog
the the the the
beaches beach beach beach
grounded ground ground ground
dreamt dreamt dreamt dreamt
envision envis envid envis
算法
虽然在以上的结果中,三种词干提取的算法结果一致,但是还是有所区别的:
- Porter:比较旧的算法,核心原理是删除单词的共同结尾,以便将它们解析为通用形式。通常情况下,不建议将它用于复杂的应用。
- Snowball:种算法也称为 Porter2 词干算法,几乎被普遍认为比Porter 更好,Snowball 在 Porter 的基础上加了很多优化。Snowball 与 Porter 相比差异约为 5%,一般任务中,推荐使用这种方法。
- Lancaster:算法比较激进,有时候会处理成⼀些比较奇怪的单词。如果在 nltk 中使用词干分析器,则可以非常轻松地将自己的自定义规则添加到此算法中。
步骤
对于某个文本,实施词形还原需要以下三个步骤:
- 分词
- 词性标注
- 词形还原
from nltk import word_tokenize, pos_tag
from nltk.corpus import wordnet
# 获取单词的词性
def get_wordnet_pos(tag):
if tag.startswith('J'):
return "j"
elif tag.startswith('V'):
return "v"
elif tag.startswith('N'):
return "n"
elif tag.startswith('R'):
return "r"
else:
return None
sentence = 'football is a family of team sports that involve, to varying degrees, kicking a ball to score a goal.'
tokens = word_tokenize(sentence) # 第一步:分词
tagged_sent = pos_tag(tokens) # 第二步:获取单词词性
wnl = WordNetLemmatizer() # 第三步:词干提取器
lemmas_sent = []
for tag in tagged_sent:
wordnet_pos = get_wordnet_pos(tag[1]) or wordnet.NOUN # tag[1]指单词词性
lemmas_sent.append(wnl.lemmatize(tag[0], pos=wordnet_pos)) # tag[0]指单词本身
print(lemmas_sent)
# 输出结果:
# ['football', 'be', 'a', 'family', 'of', 'team', 'sport', 'that', 'involve', ',', 'to', 'vary', 'degree', ',', 'kick', 'a', 'ball', 'to', 'score', 'a', 'goal', '.']