# CRF++源码分析——模型的加载#
来源:互联网 发布:股票那个软件最好 编辑:程序博客网 时间:2024/06/02 01:58
CRF++源码分析——模型的加载
接触CRF++有一段时间了,也看了一些CRF++的一些源码,再次打算对CRF++的源码进行分析,整理下以前对CRF++的理解。对CRF++的分析主要分为三部分:
1-模型的训练
2-模型的加载
3-任务的应用
由于模型训练的分析比较复杂,所以我打算放在最后来分析,按2->3->1的顺序来讲。关于CRF++的一些基本知识不再单独讲解,不熟悉的可以参考CRF++自带的说明文档,或者和说明文档比较接近的这篇文章来了解!本文以CRF++中自带样例中的一个(/example/chunking下)来分析。
由于知识储备有限,分析的有问题的地方还请见谅和指教!
CRF++在执行具体任务(对各种序列打标签)之前会将学习到的模型加载到自己的内存中,本模块主要分析CRF++加载模型的过程!
1、特征值
为了下面的讲解方便,我们引入特征值的概念。
我们知道对于Unigram类型的特征模板,产生的特征函数的数量是L.N,对于Bigram类型的特征模板,产生特征函数的数量是L.L.N(其中L是标注集中类别数量,N是从模板中扩展处理的字符串种类)。我们在特征模板与特征函数之间增加一层概念。
设x是特征模板t扩展处理的字符串集X(t)(上边的N表示的是本集合的大小\势)元素,我们将x应用于T(即将T所指定的位置的字符串指定为x)得到T的一个特征值。我们有(Y表示标注集,Y的大小为L):
- 特征值是包括特征模板与对应位置字符串组成的二元组,特征模板t生成的特征值集合为 。
V(t)={(t,x)|x∈X(t)},|V(t)|=|X(t)|=N - Unigram特征函数是包括特征模板,对应位置字符串和标注组成的三元组,U特征模板t生成的特征函数的集合.
F(t)={(t,x,y)|x∈X(t),y∈Y}={(v,y)|v∈V(t),y∈Y},|F(t)|=|V(t)|.|Y|=NL - Bigram特征函数是包括特征模板,对应位置字符串和两个标注组成的四元组,U特征模板t生成的特征函数的集合,
F(t)={(t,x,y1,y2)|x∈X(t),y1∈Y,y2∈Y}={(v,y1,y2)|v∈V(t),y1∈Y,y2∈Y} .|F(t)|=|V(t)|.|Y|.|Y|=N.L.L
注意,对于观察序列中的某个观察单位来说,特征模板指定的位置可能会超出序列的范围。例如,有观察序列(x1,x2,…) ;对x1,x2 来讲,位置%x[-2,0]分别向前超出了2个,1个单位,为了保证特征值的完备性,CRF++用_B−1,_B−2,… 分别表示观察序列前1,2,… 个位置的观测值。同理,用_B+1,_B+2,… 分别表示观察序列后1,2,… 个位置的观测值。事实上,设一个模板t=U00:%x[-3,0],那么_B−1,_B−2,_B−3∈X(t) (假设训练集中存在长度大于等于3的序列,这种假设一般总会成立的)。
2、模型文件
如果我们对用CRF++训练模型的时候带上(-t)参数,那么训练程序在产生一个二进制的模型文件的同时会产生一个txt格式的可视化的模型文件model.txt。对CRF++来讲,两种格式的文件所存储的数据是等价的,而且CRF++加载的模型一般是二进制的,txt格式的模型文件是为方便我们分析。既然两种格式的文件是等价的,我们便通过可以分析txt格式的模型来分析二进制模型文件的加载过程。
模型文件中给出了五个集合(列表),在model.txt中以空行隔开,按顺序从前往后分别为参数集合、标注列表Y、特征模板集合T、特征值集合V,特征函数的权重列表W。这五个中以集合结尾的表示集合内部在文件中的先后顺序是对CRF是无关的,以列表结尾的表示列表在文件中的先后位置是十分重要的。
(1)参数集
上图为我们的样例训练出的模型文件中的参数集,几个参数的意义分别为:
1. version 表示此模型的版本,应该是用来检查模型文件版本与CRF++版本的一致性。
2. cost-factor: 计算代价函数时会乘以此因子,应该和训练时的参数c没有关系。还没有对这个参数跟踪,理解不好,以后会补上。
3. maxid:特征函数与其权重是一一对应的,因此此参数表示特征函数,也就是特征函数权重列表的大小。
4. xsize:xsize 特征的列数。本样本的训练语料为三列,其中前两列为特征列,第三列为标注列。如下图所示:
(2)标注列表Y
上图为本样例模型文件的标注列表,即为训练语料第三列所有标注的集合。可看出此集合的容量为14。因为CRF++系统会用到标注在列表中位置信息,因此标注在列表中的位置是重要的。
(3)特征模板集合T
上图为模型文件中的特征模板集合,其即为我们训练时在模板文件中定义的特征模板。其中模板B00没有关联特征列的信息,只建立了前后标注的联系,因此其对应的特征值只有一个。
(4)特征值集合V
上面两个图是我在样例模型文件中截取的部分片段,可以看到特征模板B00的特征值只有一个。特征值分为两部分,空格前是一个整形值id,空格后表示一个具体的特征值。
特征值的顺序对CRF++是没有影响的,如果训练中没有设定-f参数的话,此集合会按空格后的特征值的顺序出现在文件中;因为按id排序便于我们分析数据,我们要对此集合按id重新排序。一个更为简单的方法是训练时增加-f 的参数,此时会按id的大小在文件中出现,而且加了-f参数后并不会对我们的分析产生影响。上图是按id顺序出现的特征值。
一个让人兴奋的地方是相邻Bigram类型的特征值的id差
(5)特征函数的权重列表W
特征函数的权重与特征函数是一一对应的,参数集合中的maxid给出了此列表的大小。我们用p1(f)表示特征函数f的权重在权重列表中的位置(从1开始),用p2(y)表示标注y在标注列表中的位置(从1开始),id(v)表示在特征值集合中特征值v对应的id,我们有
- 对于Unigram类型的特征函数
f=(v,y) ,有p1(f)=id(v)+p2(y) . - 对于Unigram类型的特征函数
f=(v,y1,y2) ,有p1(f)=id(v)+p2(y1)∗p2(y2) .
3、模型加载
CRF++在执行具体任务之前会将模型文件加载到内存中对应的数据结构中,模型加载接口的参数有四个:
本文章分析的所有的源码都在CRF++的根目录下。
模型加载的入口在tagger.cpp文件中,入口函数有几个可以用,但是核心部分都是调用相同的函数块,因此分析一个足矣。其中一个如下
166 bool ModelImpl::open(const Param ¶m) {167 nbest_ = param.get<int>("nbest");168 vlevel_ = param.get<int>("verbose");169 const std::string model = param.get<std::string>("model");170 feature_index_.reset(new DecoderFeatureIndex);171 if (!feature_index_->open(model.c_str())) {172 WHAT << feature_index_->what();173 feature_index_.reset(0);174 return false;175 }176 const double c = param.get<double>("cost-factor");177 feature_index_->set_cost_factor(c);178 return true;179 }
除第171行外其他部分的功能是获取上面提到的4个传入参数,CRF++会将模型文件加载到DecoderFeatureIndex类型对象中,下面我们分析函数
bool DecoderFeatureIndex::open(const char *model_filename),其对应带面段如下
192 bool DecoderFeatureIndex::open(const char *model_filename) {193 CHECK_FALSE(mmap_.open(model_filename)) << mmap_.what();194 return openFromArray(mmap_.begin(), mmap_.file_size());195 }
其中第193行是将二进制的模型文件按字节加载到数据结构mmap_中,此二进制数据的解析实在第194行,我们来分析函数
bool DecoderFeatureIndex::openFromArray(const char *ptr, size_t size),模型文件解析的核心逻辑便在此代码中。
197 bool DecoderFeatureIndex::openFromArray(const char *ptr, size_t size) {198 unsigned int version_ = 0;199 const char *end = ptr + size;200 read_static<unsigned int>(&ptr, &version_);201 202 CHECK_FALSE(version_ / 100 == version / 100)203 << "model version is different: " << version_204 << " vs " << version;205 int type = 0;206 read_static<int>(&ptr, &type);207 read_static<double>(&ptr, &cost_factor_);208 read_static<unsigned int>(&ptr, &maxid_);209 read_static<unsigned int>(&ptr, &xsize_);210 211 unsigned int dsize = 0;212 read_static<unsigned int>(&ptr, &dsize);213 214 unsigned int y_str_size;215 read_static<unsigned int>(&ptr, &y_str_size);216 const char *y_str = read_ptr(&ptr, y_str_size);217 size_t pos = 0;218 while (pos < y_str_size) {219 y_.push_back(y_str + pos);220 while (y_str[pos++] != '\0') {}221 }222 223 unsigned int tmpl_str_size;224 read_static<unsigned int>(&ptr, &tmpl_str_size);225 const char *tmpl_str = read_ptr(&ptr, tmpl_str_size);226 pos = 0;227 while (pos < tmpl_str_size) {228 const char *v = tmpl_str + pos;229 if (v[0] == '\0') {230 ++pos;231 } else if (v[0] == 'U') {232 unigram_templs_.push_back(v);233 } else if (v[0] == 'B') {234 bigram_templs_.push_back(v);235 } else {236 CHECK_FALSE(true) << "unknown type: " << v;237 }238 while (tmpl_str[pos++] != '\0') {}239 }240 241 make_templs(unigram_templs_, bigram_templs_, &templs_);242 243 da_.set_array(const_cast<char *>(ptr));244 ptr += dsize;245 246 alpha_float_ = reinterpret_cast<const float *>(ptr);247 ptr += sizeof(alpha_float_[0]) * maxid_;248 249 CHECK_FALSE(ptr == end) << "model file is broken.";250 251 return true;252 }
在分析此函数前,我们先看一下函数中多次调用的一个函数块
17 const char *read_ptr(const char **ptr, size_t size) { 18 const char *r = *ptr; 19 *ptr += size; 20 return r; 21 } 22 23 template <class T> static inline void read_static(const char **ptr, 24 T *value) { 25 const char *r = read_ptr(ptr, sizeof(T)); 26 memcpy(value, r, sizeof(T)); 27 }
*ptr指向一个T类型的对象,此方法的功能便是将*ptr地址的数据反序列化到T类型对象value中,并使指针*ptr后移。下面来分析openFromArray函数。
事实上,模型文件的加载便是按我们上文提到的各个部分的顺序依次解析。
(1) 参数集和的解析
对应于198-210行,分别从二进制数据结构mmp_中加载参数集中的各个参数。
(2) 标注列表Y的解析
对应于214-222行,首先,216行读取到整个标注类表串;然后218-221行按字符\0分开各个标注,并读取到属性y_(按在文件中的顺序)中。
(3) 特征模板集合T的解析
对应于223-241行,同样,首先读取这个的模板集合;然后,按\0分开,将Unigram类型模板读取到属性unigram_templs_中,将Bigram类型模板读取到属性bigram_templs_中;最后在241行将两种类型的模板合并到属性templs_中。
(4) 特征值集合V的解析
对应于243-245行,在211-212行读取的参数dsize其实是模型文件中特征值集合大小,然后通过set_array方法将特征值到对应的特征函数权重起始id的映射存入到属性da_(DoubleArrayImpl类型)中。为了提高搜索速度和空间利用率,CRF++采用Double Array Trie的结构存储此映射,因此二进制文件中是此结构的序列化(也是一个数组)。
(5) 特征函数权重列表W的解析
对应于246-247行,每个特征函数对应一个float(或double)类型的权重,将模型文件中的权重列表按顺序解析到数组alpha_float_中。
至此,CRF++便完成了模型加载。接下来便可以用加载到的这些统计信息来执行具体的任务了。
- # CRF++源码分析——模型的加载#
- 基于spark实现的CRF模型的使用与源码分析
- CRF++模型文件格式分析
- 定制你自己的CRF模型
- Nginx源码分析 ——Nginx的进程模型
- 分词算法模型学习笔记(三)——CRF
- Crf模型
- crf模型
- CRF模型
- Launcher3源码分析 — 加载Workspace的数据
- Launcher3源码分析 — 加载Workspace的数据
- Launcher3源码分析 — 加载Workspace的数据 .
- Backbone源码分析——MVC模型
- HMM,MEMM,CRF模型的比较
- HMM,MEMM,CRF模型的比较(转)
- CRF++模型可视化输出的格式
- CRF序列标注模型几个问题的理解
- CRF和MRF概率模型的关系
- 第十一章 连接查询和分组查询
- 文件调用-JSP中include指令和include行为区别
- ARM 中断--IRQ and FIQ配置--外部配置
- usaco1.1.4 Broken Necklace
- IBM Rational AppScan使用详细说明
- # CRF++源码分析——模型的加载#
- 总结各种排序算法(C++)
- 更新物料某属性
- 内聚度和耦合度
- NOIP2016提高组 第一天第三题 换教室 classroom 题解
- 用三个"W"分析活动运营,你真能做好吗?
- jfinal +beetl集成开发web全集
- S5PV210微处理器的启动过程
- 20161125 APS.NET小记