剑客
关注科技互联网

条件随机场和CRF++使用

条件随机场和CRF++使用

在吴军的《数学之美》这本书里对 条件随机场(Conditional random field,CRF) 的应用有些介绍,主要是在自然语言处理(NLP)里实现句子文法分析,词性标注,实体抽取之类的。另外一个比较有趣的例子是洛杉矶警方用条件随机场来预测未来犯罪可能发生的时间地点和犯罪类型。 最近在使用CRF在文本信息抽取中,本文简单介绍其原理和实现方法。

条件随机场简介

CRF 是给定随机变量X ,随机变量Y是一个马尔科夫随机场的模型。这样说好像也不是很清楚,看《统计学习方法》中对条件随机场的数学定义:

设X与Y 是随机变量,$$P(Y|X)$$是在给定X的条件下Y的条件概率分布。如随机变量Y构成一个由无向图$$G=(V,E)$$ 表示的马尔可夫随机场即 $$P(Y_v|X,Y_w,w/neq v)=P(Y_v|X,Y_w,w/sim v)$$ 则称条件概率分布$$P(Y|X)$$为条件随机场。

理解数学定义的前提是需要知道什么是马尔科夫随机场,马尔科夫随机场主要是具有马尔科夫性(pairwise Markov property),所谓马尔科夫性就是为了让问题简化所做出的假设。在概率图模型里主要是成对马尔科夫性,局部马尔科夫性和全局马尔科夫性。这些性质主要的意思就是在概率图模型里顶点所在的随机变量只和满足某些条件的顶点相关,具体是哪些条件我不想抄书。

如果不懂上面的定义也没关系,看看条件随机场在NLP 中的应用或许就理解了。在NLP 里经常使用的被成为线性条件随机场(linear chain conditional random field) 主要是因为在标注问题中,上面定义里说的条件随机场的概率无向图是链状的(此处应该有图),实在找不到好图就去《An Introduction to Conditional Random Fields for Relational Learning》这篇Paper 里截图了如下,这个图很有意思,从Native Bayes, 到HMM,到最后的CRF 它们都是有关系的。 条件随机场和CRF++使用

标注问题是给定一个句子如"正午的太阳" 如果分词之后的结果"正午/的/太阳"这就是观测序列X.假如要做的是词性标注那词性的序列为“名词/助词/名词” 这就是状态序列Y.本来一个词是什么词性和很多因素有关系,到底和哪些因素有关谁也不知道。为了问题简化就有了马尔科夫性。模型的训练过程就是寻找条件概率$$P(Y|X)$$分布函数参数的过程。预测的时候是给定观测$$X$$预测状态$$Y$$的过程。 怎么训练和计算模型参数,怎么做预测,其实已经有很多成熟的东西。为了快速实现功能,甚至可以不用了解其中的细节,当然去了解下会更好。下面介绍下用CRF++ 的使用。

CRF++

CRF++ 是C++ 实现的CRF 工具,Google 之后发现评价比较高的工具有比较清晰的 文档 如果和我一样喜欢看源代码也有在 GitHub 开源

Install

  1. 从源码安装CRF++,我的系统环境Ubuntu 14.04
git clone https://github.com/taku910/crfpp.git
./configure
make
sudo make install

如果make的时候发生找不到winmain.h的错误: 可以下面这种方式修复:

sed -i '/#include "winmain.h"/d' crf_test.cpp
sed -i '/#include "winmain.h"/d' crf_learn.cpp
make
sudo make install``

后面需要用到Python 使用训练好的模型所以也一起安装CRFPP, cd python 目录下

python setup.py build
sudo python setup.py install

然后在Python 或者Ipython 里输入 import CRFPP 如果发生如下错误

ImportError: libcrfpp.so.0: cannot open shared object file: No such file or directory

可用下面的方法解决

sudo vim /etc/ld.so.conf

添加

include /usr/local/lib

保存后加载一下

sudo /sbin/ldconfig -v

上面的安装过程如果基本包含了各种会遇到的坑

模板

CRF++ 的模板可能是使用CRF++比较让人费解的。理解模板的前提是理解CRF++的逻辑。 举个例子: 下面是CRF++自带的example 中的部分训练数据

Confidence NN B
in IN O
the DT B
pound NN I
is VBZ O
widely RB O
expected VBN O
to TO O
take VB O
another DT B
sharp JJ I
dive NN I

第一列是词,第二列是词性,第三列是需要预测的状态,在这里不管它什么意思,如果是实体识别你就自己定义号这个标签就好,在中文里我是一个字最为第一列,第二列也是使用的词性。 对应的模板文件为:

# Unigram
U00:%x[-2,0]
U01:%x[-1,0]
U02:%x[0,0]
U03:%x[1,0]
U04:%x[2,0]
U05:%x[-1,0]/%x[0,0]
U06:%x[0,0]/%x[1,0]

U10:%x[-2,1]
U11:%x[-1,1]
U12:%x[0,1]
U13:%x[1,1]
U14:%x[2,1]
U15:%x[-2,1]/%x[-1,1]
U16:%x[-1,1]/%x[0,1]
U17:%x[0,1]/%x[1,1]
U18:%x[1,1]/%x[2,1]

U20:%x[-2,1]/%x[-1,1]/%x[0,1]
U21:%x[-1,1]/%x[0,1]/%x[1,1]
U22:%x[0,1]/%x[1,1]/%x[2,1]

# Bigram
B

模板定义的是,概率图中的随机变量和哪些相关。CRF++会根据这个文件产生对应的特征…. (未完…..)

Train

有了template, 和按要求准备号训练数据,就可以训练了

crf_learn template_file train_file model_file

Python 调用CRF++ 模型

最后训练好的模型,是要放到生产环境去使用的,总不能用一个终端命令来解决吧。 CRF++ 提供了C++,Python,Ruby ,Java ,perl 的几种方式来调用最终的模型。我使用Python ,Python 和C++能比较方便。 如果上面的Python 环境装好之后,直接使用 python 路径下的 test.py 修改你产生的模型文件路径就可以了预存了。 我做了下封装,写成了两个Function

def load_model():
    # modelname.model 路径我发现只能使用相对路径

    tagger = CRFPP.Tagger("-m models/modelname.model -v 3 -n2")
    tagger.clear()
    tagger.add("Confidence NN")
    tagger.add("in IN")
    tagger.add("the DT")
    tagger.add("pound NN")
    tagger.add("is VBZ")
    tagger.add("widely RB")
    tagger.add("expected VBN")
    tagger.add("to TO")
    tagger.add("take VB")
    tagger.add("another DT")
    tagger.add("sharp JJ")
    tagger.add("dive NN")
    tagger.add("if IN")
    tagger.add("trade NN")
    tagger.add("figures NNS")
    tagger.add("for IN")
    tagger.add("September NNP")
    return tagger

下面这个functin 就是使用模型预测了,需要注意的是,此处使用Python2.7,传给 tagger.parse 的字符串需要是 str

def recognise(sentence):
    """
    sentence unicode
    return list of every word and tag
    """
    tagger = load_model()
    word_list = cut_sentence(sentence)
    word_list = ['/t'.join(item) for item in word_list]
    word_str = '/n'.join(word_list)
    result = tagger.parse(word_str.encode('utf-8'))
    result_list = result.split('/n')
    ner_result = []
    ## chose the first result
    for lin in result_list[1:]:
        lint = lin.split()
        if not len(lin):
            break
        ner_result.append(lint[:3])
    return ner_result
分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址