用条件随机场做网络小说命名实体识别 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
enenaaa
V2EX    程序员

用条件随机场做网络小说命名实体识别

  •  1
     
      enenaaa 2017-12-18 11:17:08 +08:00 5521 次点击
    这是一个创建于 2921 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一直想用统计学习方法来改善拨云搜索,这次先在命名实体上小小尝试一下。

    线性链条件随机场

    对于无向图中的节点,定义一组特征函数,使其状态仅受邻近节点和观测序列的影响。

    在标注任务中,节点只有前后两个邻近节点,即线性链条件随机场。

    按照定义,节点有两类特征函数:转移特征函数和状态特征函数。转移特征函数表示上一个节点状态及观测序列对当前节点的影响,状态特征函数表示观测序列对当前节点的影响。每个函数都有个权重,模型经过训练后,通过函数及其权重即可推算节点状态。

    直观地看,对于 NER 任务,文本为观测序列,一个词对应一个节点,标签为节点状态。我们为每个节点定义一组特征函数,因为节点对应的词不一样,它们的特征函数也不一样。经过训练后,模型通过词对应的特征函数集合,即可计算出最可能的标签。

    python-CRFsuite

    因为要同 python 程序衔接,这里选用 CRFsuite 库。 与 CRF++晦涩的模板文件不同,CRFsuite 以键值对的形式定义特征函数。优点是灵活直观,缺点嘛就是不太好自定义转移特征了,不过对本文任务没影响。

    方案设计

    此次任务目标是识别小说里的地名、道具、技能、门派及特殊人名等实体。由于文本的特殊性,可以预见通用的训练语料效果不会太好。所以我用基于规则的方法来标注八本有代表性的小说,以此作为训练语料。用另外的四本小说来评测,然后与前述规则方法的结果做比较。

    训练语料的八本小说:凡人修仙传, 问题妹妹恋上我, 轮回剑典, 极品家丁, 雪鹰领主, 武林半侠传, 儒道之天下霸主, 无限之高端玩家。

    评测的四本小说:择天记,大道争锋,魔魂启临,俗人回档。

    标签采用 BMEO 四种状态,B 实体开始,M 实体中间词,E 实体结尾词,O 无关词。

    预期目标

    原有的规则识别是在匹配到特殊字词后,按照特定句式、字词及词频来判定是否命名实体。CRF 在定义特征时,综合考虑了上下文内容和词性,预计泛化会更好一点。但是语料标注及分词词性本身并不靠谱,估计准确率有所降低。

    实现

    1. 生成训练语料

    将文本分词后, 送入原识别程序。然后对结果标注,以 json 格式保存。格式如:

    ["这是", "r", "O"], ["清", "a", "B"], ["虚", "d", "M"], ["门", "n", "E"], ["的", "uj", "O"], ["飞行", "vn", "O"], ["法器", "n", "O"], ["雪", "n", "O"], ["虹", "n", "O"], 

    单元里有文字、词性和标签 3 个属性。

    2. 定义特征

    以当前词及前后两个词的文字和词性为特征函数。这里都是状态特征,转移特征是默认的。

    def _word2features(wordlist, i): """ 返回特征列表 """ features = [ 'bias', 'word:'+wordlist[i][0], 'word_attr:'+wordlist[i][1] ] if i > 0: features.append('word[-1]:'+wordlist[i-1][0]) features.append('word[-1]_attr:'+wordlist[i-1][1]) if i > 1: features.append('word[-2]:'+wordlist[i-2][0]) features.append('word[-2,-1]:'+wordlist[i-2][0]+wordlist[i-1][0]) features.append('word[-2]_attr:'+wordlist[i-2][1]) if i < len(wordlist)-1: features.append('word[1]:'+wordlist[i+1][0]) features.append('word[1]_attr:'+wordlist[i+1][1]) if i < len(wordlist)-2: features.append('word[2]:'+wordlist[i+2][0]) features.append('word[1,2]:'+wordlist[i+1][0]+wordlist[i+2][0]) features.append('word[2]_attr:'+wordlist[i+2][1]) return features 

    这里直接把特征属性组合为名称, 这样其值就是函数权重,默认为 1。

    3. 训练模型

    默认用 L-BFGS 算法训练。 参数也并没有调到最优。

    model = pycrfsuite.Trainer(verbose=True) model.append(train_x, train_y) model.set_params({ 'c1': 1.0, # coefficient for L1 penalty 'c2': 1e-3, # coefficient for L2 penalty 'max_iterations': 100, # stop earlier 'feature.possible_states': True, # include transitions that are possible, but not observed 'feature.possible_transitions': True, 'feature.minfreq': 3 }) model.train("./test.crf") 

    4. 评测

    tagger = pycrfsuite.Tagger() tagger.open('./test.crf') test_y = tagger.tag(test_x) 

    这里抽选大道争锋的标注结果,其他 3 本也差不多,如下:

    '少清派', '邪派', '玄门正宗', '正宗玄门', '丹师', '魔宗', '莹云', '种法术', '蛇出来', '琴楠', '大玄门', '一玄门', '一身皮', '了莹', '大派', '丹术', '丹三', '紫眉', '丹主', '黑衣道人', '巧巧', '阵图', '喜欢道士', ' 十大玄门', '玄袍道人', '小派', '铜炉', '玄功', '剑招', '十多人', '二岛', '世家玄门', '九曲溪宫', ',玄门', '派玄门', '诸多玄门', '丹成功', '剑出来', '了玄门', '责翠', '源剑', '玄门小派', '魔门', '玄门十大派', '栖鹰', '三大玄门', '丹功', '丹境', '于各个玄门', '各大派', '一杆长枪', '丹出来', '阵门', '仙派', '九转功', '丹炉', '楚玄门', '青云一', '禽鹰', '凝功', '源经', '莹莹', '半声', '祭出来', '了出来', '黑风', '玄族', '可玄门', '师出来', '主宫', '玄门大派' 

    规则方法结果:

    '此阵', '六大魔宗', '女冠', '化形丹', '蓬远派', '桂从尧', '功德院中', '参神契', '此水', '血魄', '自思', '玄门世家', '广源派', '一观', '双翅', '闯阵', '四象阵', '沉香舟', '澜云密册', '讨争', '补天阁', '道姑', '晁掌阁', '入山', '派中', '玄灵山', '清羽门下', '符书', '元阳派', '大门大派', '观容师妹', '入阵', '善渊观', '越真观', '幽阴重水', '水行真光', '玄光境界', '景管事', '破阵', '宝丰观中', '剑符', '修道者', '沉香教中', '管事', '万福一礼', '化 丹修士', '山河童子', 'jī动', '年轻道人', '符诌', '紫眉道人', '斩神阵', '丹鼎院', '太昊派', '败下阵', '歉然', '此丹', '剑丸', '疑 huò', '此老', '溟沧派', '张衍神色', '甫一', '砀域水国', '血衣修士', '正 sè', '少清派', '两名老道', ' 魔门', '丹煞', '观中', '师徒一脉', '德修观', '候伯叙', '魔简', '入门弟子', '苍梧山', '老道', '小金丹', '宁冲玄一', '下院入门', '化丹', '小童', '符御卿', '力士', '众弟子', '中年修士', '文安', '文俊', '老魔', '熬通', '道诀', '丘老道', '手一', '玄光境', '丹鼎院中', '玄门正宗', '天阁', '血魄宗', '南华派', '年轻修士', '拿眼', '阵中', '小派', '北辰派', '太昊门', '化一', '阵门', '风师兄', '道书', '玲儿', '玄功', '琴楠', '儒雅道人', '神梭', '凝丹', '珍茗', '玄门十派', '沉香教', '张衍洒然', '无需多', '中年道人', '魔宗', '沧派', '清羽门', '守阵', '魔穴', '解读道书', '十六派', '九魁妖王', '秀儿', '执事道童', '了声', '化丹境界', '待张衍', '玄门大派', '陶真人门下', '两派' 

    统计结果:

    | 项目 | CRF | 规则 |

    | ---- | ---- | ---- |

    | 实体数量 | 71 | 138 |

    | 都有 | 9 | 9 |

    | 差异数量 | 62 | 129 |

    | 准确数 | 25(35%) | 105(76%) |

    | 独有准确数 | 16(8) | -- |

    正确的标准是组合正确,且确实为新实体。有多种组合方案的,都算正确,例如:紫眉和紫眉道人。

    独有准确数括号里的数字去掉了因词频按规则方法应抛弃的实体。

    可以看出几点:

    • 规则方法识别的数量比 CRF 要多。这是因为语料偏少,CRF 只训练了 8 本小说,并没有学习到所有的规则信息。
    • 两者的准确率都有问题。例如 CRF 里的“了出来”,“师出来”。规则方式里的“甫一”,“正 sè”。都是错误地识别了人名,其原因各有不同,但 CRF 受到了标注语料的影响,有可能将错误率放大。
    • CRF 识别出了超出规则的实体,这是期望的目标。

    小结

    在不靠谱的训练语料及不靠谱的评价标准下,CRF 仍然给出了有意义的结果。

    15 条回复    2017-12-18 16:42:14 +08:00
    jy02201949
        1
    jy02201949  
       2017-12-18 11:59:00 +08:00   3
    我连标题都整不明白。。。
    neosfung
        2
    neosfung  
       2017-12-18 12:00:24 +08:00   1
    state of the art 的方法是 bi-lstm 后面接一个 crf 层
    现在几乎所有的领域都要用一下深度学习才显得自己潮流
    当然了,还是支持楼主的动手能力
    cosmic
        3
    cosmic  
       2017-12-18 12:13:52 +08:00   1
    https://github.com/zjy-ucas/ChineseNER 就是楼上说的 Bi-LSTM+CRF
    推荐一下用这个试试,目前我用这个做 NER 准确率,召回率都很高。当然,前提是你需要自己标注一些数据。
    Jface
        4
    Jface  
       2017-12-18 12:14:50 +08:00 via Android
    不明觉厉,文科生一脸懵逼
    enenaaa
        5
    enenaaa  
    OP
       2017-12-18 12:51:53 +08:00
    @neosfung
    @cosmic 谢谢。
    没理解错的话,bi-lstm 的作用是在观测序列中加入更多的上下文信息。 但是我理解里实体的影响距离应该是比较短的。如果加入长距离的上下文, 在跨领域的文本(假设不同风格的小说文本)中效果会不会变差
    winglight2016
        6
    winglight2016  
       2017-12-18 12:59:45 +08:00
    @cosmic 数据量很大的时候,怎么“自己标注”呢?我现在想分析几百兆的短信内容,抽取实体,大佬有没有高效方法推荐一下?
    cosmic
        7
    cosmic  
       2017-12-18 13:32:21 +08:00   1
    @enenaaa 基本上都是从句子层面出发来做 NER 的,距离影响的长短,会根据语料算出来的。对于不同风格的文章,我的建议是增加样本量
    cosmic
        8
    cosmic  
       2017-12-18 13:33:53 +08:00
    @winglight2016 可以去众包平台上找人标注。也可以先拿公开的语料训练,然后把错误的结果拿出来,人工修正加入语料里
    neosfung
        9
    neosfung  
      &nbp;2017-12-18 14:19:18 +08:00   1
    @enenaaa

    lstm 的作用,就是捕捉长距离的信息,然后计算当前词语的各个 tag 的概率(point wise)。

    crf 放在最后一层的作用是,计算一个全局( sentence wise)的最优。等于 lstm 把每个词语的各个 tag 的概率算出来,crf 再在这里选择一个全局最优的标注序列(有点像 loss 函数,呵呵)。

    pytorch 的官方例子可以看这里 http://pytorch.org/tutorials/beginner/nlp/advanced_tutorial.html#bi-lstm-conditional-random-field-discussion

    _get_lstm_features 函数就是获得每个词语的 tag 的概率了
    winglight2016
        10
    winglight2016  
       2017-12-18 15:11:00 +08:00
    @cosmic 感谢大佬回复,不过这种方法似乎不适合个人操作,有没有无监督学习方法可以应用啊?
    natforum
        11
    natforum  
       2017-12-18 15:18:42 +08:00
    http://xuanpai.sinaapp.com/ 可以参考这个
    gouchaoer
        12
    gouchaoer  
       2017-12-18 15:21:03 +08:00
    卧槽,这个年代还有有人玩 crf
    gouchaoer
        13
    gouchaoer  
       2017-12-18 15:23:32 +08:00
    数学不好,当初没搞懂 crf
    scp055
        14
    scp055  
       2017-12-18 15:46:46 +08:00
    不知楼主这个项目有 github 吗,想学习一下。谢谢
    enenaaa
        15
    enenaaa  
    OP
       2017-12-18 16:42:14 +08:00
    @scp055 没。 不过主要代码都贴上面了。 剩下的就是分词、文件操作之类。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5286 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 47ms UTC 06:04 PVG 14:04 LAX 22:04 JFK 01:04
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86