词性标注在HanLP中的实现

词性标注

词性标注指的是为句子中每个单词预测一个词性标签的任务。

r:代词 u:助词 n:名词 v:动词 nr:人名 p:介词 a:形容词

词性标注语料库与标注集

同中文分词一样,语言学界在标注规范上存在分歧,导致目前还没有一个被广泛接受的汉语词性划分标准。无论是词性划分的颗粒度,还是词性标签都不统一。一方面,各研究机构各持己见、派系林立,标注了大量互不兼容的语料库。另一方面,部分语料库受到严格版权控制,成为内部材料,得不到充分共享利用。

《人民日报》语料库和PKU标注集

语料库中的一句样例为:

国家语委语料库与863标注集

语料库中的一句样例为:

《诛仙》语料库与CTB标注集

语料库中的一句样例为:

词性标注模型

最朴素的词性标注模型:通过将中文分词中的汉字替换为词语,{B,M,E,S} 替换为“名词、动词、形容词等”,序列标注模型马上就可以用来做词性标注。

词性标注模型同时解决了在句子中挑选最符合语境的词语和预测新词的词性的问题。

下载语料库

首先需要对PKU语料库进行下载

from  pyhanlp import *
import zipfile
import os
from pyhanlp.static import download, remove_file, HANLP_DATA_PATH
#获取测试数据路径
def test_data_path():
    data_path = os.path.join(HANLP_DATA_PATH, 'test')
    if not os.path.isdir(data_path):
        os.mkdir(data_path)
    return data_path
## 验证是否存在 MSR语料库,如果没有自动下载
def ensure_data(data_name, data_url):
    root_path = test_data_path()
    dest_path = os.path.join(root_path, data_name)
    if os.path.exists(dest_path):
        return dest_path
    if data_url.endswith('.zip'):
        dest_path += '.zip'
    download(data_url, dest_path)
    if data_url.endswith('.zip'):
        with zipfile.ZipFile(dest_path, "r") as archive:
            archive.extractall(root_path)
        remove_file(dest_path)
        dest_path = dest_path[:-len('.zip')]
    return dest_path
## 下载 PKU 语料库
PKU98 = ensure_data("pku98", "http://file.hankcs.com/corpus/pku98.zip")

下载完成后,就可以将该语料库对各种模型进行训练而进行词性标注了

基于HMM的词性标注

在HanLP中HMMPOSTagger为HMM词性标注器

FirstOrderHiddenMarkovModel为一阶HMM

HMMPOSTagger = JClass('com.hankcs.hanlp.model.hmm.HMMPOSTagger')
AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer')
PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter')
FirstOrderHiddenMarkovModel = JClass('com.hankcs.hanlp.model.hmm.FirstOrderHiddenMarkovModel')
def train_hmm_pos(corpus, model):
    tagger = HMMPOSTagger(model)  # 创建词性标注器
    tagger.train(corpus)  # 训练
    print(', '.join(tagger.tag("他", "认为", "人生", "充满", "了", "希望")))  # 预测
    analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), tagger)  # 同时进行分词和词性标注
    print(analyzer.analyze("他认为人生充满了希望").translateLabels())
    return tagger
train_hmm_pos(PKU98, FirstOrderHiddenMarkovModel())

由于词性标注其并不负责分词,tagger.tag仅接受分词后的词语序列

AbstractLexicalAnalyzer是对HMM和感知机分词器结合,可以同时执行分词和词性标注

结果

r, v, n, v, u, n
他/代词 认为/动词 人生/名词 充满/动词 了/助词 希望/名词

然而,由于HMM是根据上一个状态而推断出当前状态,容易出现“一步走错,满盘皆输”的情况

 print(analyzer.analyze("李狗蛋的希望是希望上学").translateLabels())
李狗蛋/动词 的/动词 希望/动词 是/动词 希望/动词 上学/动词

基于感知机的词性标注

感知机能够利用丰富的上下文特征,是优于隐马尔可夫模型的选择,对于词性标注也是如此。

在HanLP中,感知机词性标注器实现为PerceptronPoSTagger

POS_MODEL = os.path.join(PKU98, 'pos.bin')
AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer')
PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter')
POSTrainer = JClass('com.hankcs.hanlp.model.perceptron.POSTrainer')
PerceptronPOSTagger = JClass('com.hankcs.hanlp.model.perceptron.PerceptronPOSTagger')
def train_perceptron_pos(corpus):
    trainer = POSTrainer()
    trainer.train(corpus, POS_MODEL)  # 训练
    tagger = PerceptronPOSTagger(POS_MODEL)  # 加载
    print(', '.join(tagger.tag("李狗蛋", "的", "希望", "是", "希望", "上学")))  # 预测
    analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), tagger)  
    print(analyzer.analyze("李狗蛋的希望是希望上学").translateLabels())  
    return tagger
train_perceptron_pos(PKU98)

结果

nr, un, v, v, v
李狗蛋/人名 的/助词 希望/名词 是/动词 希望/动词 上学/动词

这次的运行结果完全正确,感知机能成功的识别出新词的词性

基于条件随机场的词性标注

条件随机场进行词性标注的精度比感知机高,在HanLP中实现为CRFPPOSTagger

CRFTagger是通用的序列标注器

AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer')
PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter')
CRFPOSTagger = JClass('com.hankcs.hanlp.model.crf.CRFPOSTagger')
def train_crf_pos(corpus):
    tagger = CRFPOSTagger(None)  # 创建空白标注器
    tagger.train(corpus, POS_MODEL)  # 训练
    tagger = CRFPOSTagger(POS_MODEL) # 加载
    print(', '.join(tagger.tag("李狗蛋", "的", "希望", "是", "希望", "上学")))  # 预测
    analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), tagger)  
    print(analyzer.analyze("李狗蛋的希望是希望上学").translateLabels())  
    return tagger
tagger = train_crf_pos(PKU98)

结果

nr, un, v, v, v
李狗蛋/人名 的/助词 希望/名词 是/动词 希望/动词 上学/动词

词性标注评测

将 PKU 语料库按 9:1 分隔为训练集和测试集,分别用以上三种模型来训练,准确率如下:

算法 准确率
一阶隐马尔可夫模型 44.99%
二阶隐马尔可夫模型 40.53%
结构化感知机 83.07%
条件随机场 82.12%

结构化感知机和条件随机场都要优于隐马尔可夫模型,判别式模型能够利用更多的特征来进行训练,从而提高更多的精度。

自定义词性

将一些特定词语打上自定义的标签。HanLP提供了自定义词性的功能,实现方式有两种

朴素实现

根据词典的规则,将自定义的词性作为词典交给HanLP

from pyhanlp import *
CustomDictionary.insert("苹果", "手机品牌")
CustomDictionary.insert("iPhone X", "手机型号")
analyzer = PerceptronLexicalAnalyzer()#加载配置文件指定的模型构造词法分析器
analyzer.enableCustomDictionaryForcing(True)
print(analyzer.analyze("你们苹果iPhone X保修吗?"))
print(analyzer.analyze("多吃苹果有益健康"))
你们/r 苹果/手机品牌 iPhone X/手机型号 保修/v 吗/y ?/w
多/d 吃/v 苹果/手机品牌 有益健康/i

可以看到,通过字典的形式只是机械的进行匹配

标注语料

词性的确定需要根据上下文语境,因此可以通过标注一份语料库,然后训练一个统计模型。

语料库规模,与所有机器学习问题一样,数据越多,模型越准。

通过以《诛仙》语料库为例进行训练

AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer')
PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter')
CRFPOSTagger = JClass('com.hankcs.hanlp.model.crf.CRFPOSTagger')
ZHUXIAN = ensure_data("zhuxian", "http://file.hankcs.com/corpus/zhuxian.zip") + "/train.txt"#诛仙语料库
def train_perceptron_pos(corpus):
    trainer = POSTrainer()
    trainer.train(corpus, POS_MODEL)  # 训练感知机模型
    tagger = PerceptronPOSTagger(POS_MODEL)  # 加载
    return tagger
posTagger = train_perceptron_pos(ZHUXIAN)  # 训练
analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), posTagger)  # 包装
print(analyzer.analyze("陆雪琪的天琊神剑不做丝毫退避,直冲而上,瞬间,这两道奇光异宝撞到了一起。"))  # 分词+标注
陆雪琪/NR 的/DEG 天琊/NR 神剑/NN 不/AD 做/VV 丝毫/NN 退避/VV ,/PU 直冲/VV 而/MSP 上/VV ,/PU 瞬间/NN ,/PU 这/DT 两/CD 道/M 奇光/NN 异宝/NN 撞到/VV 了/AS 一起/AD 。/PU

在学习了新的语料库之后,模型自动切换为新的词性标注集。

总结

提高词性标注的准确率,无非是标注更多的语料,设计更多的特征,采用更高精度的模型