这篇文章主要讲解CRF++实现预测的过程,预测的算法以及代码实现相对来说比较简单,所以这篇文章理解起来也会比上一篇条件随机场训练的内容要容易。

预测

  上一篇条件随机场训练的源码详解中,有一个地方并没有介绍。 就是训练结束后,会把待优化权重alpha等变量保存到文件中,也就是输出到指定的模型文件。在执行预测的时候会从模型文件读出相关的变量,这个过程其实就是数据序列化与反序列化,该过程跟条件随机场算法关系不大,因此为了突出重点源码解析里就没有介绍这部分,有兴趣的朋友可以自己研究一下。

  CRF++预测的入口代码在crf_test.cpp的main函数中,最终会调用tragger.cpp的int crfpp_test(const Param &param)函数,期间会做一些输入参数的处理、异常处理、读取模型文件等操作。一切准备就绪就会打开待预测的文件,进行预测。正式探讨预测代码之前,我们先看下预测的理论基础。条件随机场的预测用到了维特比算法,公式如下:

\begin{aligned} y^* &= \arg \max_yP_w(y|x) \\ &=  \arg \max_y\frac{ \exp \left \{ \sum_{k=1}^Kw_kf_k(y,x) \right\}}{Z_w(x)} \\ &=  \arg \max_y \exp \left \{\sum_{k=1}^Kw_kf_k(y,x) \right\} \\ &= \arg \max_y \ \sum_{k=1}^Kw_kf_k(y,x) \end{aligned}

从公式我们可以看出,我们求的概率最大值就是要求代价最大。接下来就看下CRF++的源码,代码在tragger.cpp的crfpp_test函数中:

while (*is) {//is是打开的测试文件,可以输入多个测试文件做预测
tagger.parse_stream(is.get(), os.get());
} bool TaggerImpl::parse_stream(std::istream *is,
std::ostream *os) {
if (!read(is) || !parse()) {//read函数在特征篇讲过,不再赘述,调用parse函数进行预测
return false;
}
if (x_.empty()) {
return true;
}
toString(); //格式化输出,-v 会输出每个词预测为某个label的概率,-n会输出预测序列概率最大的前n个,如果理解上一篇训练过程,再看这个函数就比较容易理解,无非就是概率计算,这里不再赘述
os->write(os_.data(), os_.size()); //输出到输出文件
return true;
}
bool TaggerImpl::parse() {
CHECK_FALSE(feature_index_->buildFeatures(this)) //构建特征,同特征篇代码,不再赘述
<< feature_index_->what(); if (x_.empty()) {
return true;
}
buildLattice(); //构建无向图,因为要计算代价最大的序列,训练篇讲过,不再赘述
if (nbest_ || vlevel_ >= ) {
forwardbackward(); //前向后向算法,为了计算单词节点的概率,训练篇讲过,不再赘述
}
viterbi(); //维特比算法, 做预测的代码
if (nbest_) {
initNbest();
} return true;
}
void TaggerImpl::viterbi() {
for (size_t i = ; i < x_.size(); ++i) { //遍历每个词
for (size_t j = ; j < ysize_; ++j) { //遍历每个词的每个label
double bestc = -1e37;
Node *best = ;
const std::vector<Path *> &lpath = node_[i][j]->lpath;
for (const_Path_iterator it = lpath.begin(); it != lpath.end(); ++it) { //从前一个词到当前词的代价之和 = max(前一个节点的代价 + 前一个节点的边代价 + 当前节点代价)
double cost = (*it)->lnode->bestCost +(*it)->cost +
node_[i][j]->cost;
if (cost > bestc) { //记录截止当前节点最大的代价, 以及对应的前一个节点
bestc = cost;
best = (*it)->lnode;
}
}
node_[i][j]->prev = best; //记录前一个几点
node_[i][j]->bestCost = best ? bestc : node_[i][j]->cost; //记录最大的代价值, 如果best = 0代表第一个词,没有左边,最大代价就是节点的代价node_[i][j]->cost
}
} double bestc = -1e37;
Node *best = ;
size_t s = x_.size()-;
for (size_t j = ; j < ysize_; ++j) { //遍历最后一个词的节点,截止到最后一个词的代价最大值就是整个句子的最大代价
if (bestc < node_[s][j]->bestCost) {
best = node_[s][j];
bestc = node_[s][j]->bestCost;
}
} for (Node *n = best; n; n = n->prev) {//记录代价最大的预测序列
result_[n->x] = n->y;
} cost_ = -node_[x_.size()-][result_[x_.size()-]]->bestCost;
}

预测的核心代码就看完了,大部分复用了训练过程的逻辑。可以看到预测的过程跟公式是一致的,无非就是求能够让代价最大的label序列(标记序列),这就是维特比算法。

总结

  至此,我们的条件随机场之CRF++源码详解系列就结束了,主要涵盖了特征处理、训练以及预测三个核心过程。结合CRF++源码我们可以更形象的、更通俗的去理解条件随机场模型。以后想起条件随机场模型,我们脑海浮现的不再是一堆公式,而是一个无向图,在图上进行代价计算、前向后向计算、期望值的计算以及梯度的计算等一系列的过程。希望这个系列对于正在学习条件随机场的朋友能有帮助,如果本文阐述的有歧义、不通俗、不容易理解的地方,欢迎留言区交流,我将及时更正、回复,希望我们一起提高。

条件随机场之CRF++源码详解-预测的更多相关文章

  1. 条件随机场之CRF++源码详解-训练

    上篇的CRF++源码阅读中, 我们看到CRF++如何处理样本以及如何构造特征.本篇文章将继续探讨CRF++的源码,并且本篇文章将是整个系列的重点,会介绍条件随机场中如何构造无向图.前向后向算法.如何计 ...

  2. 条件随机场之CRF++源码详解-特征

    我在学习条件随机场的时候经常有这样的疑问,crf预测当前节点label如何利用其他节点的信息.crf的训练样本与其他的分类器有什么不同.crf的公式中特征函数是什么以及这些特征函数是如何表示的.在这一 ...

  3. 条件随机场之CRF++源码详解-开篇

    介绍 最近在用条件随机场做切分标注相关的工作,系统学习了下条件随机场模型.能够理解推导过程,但还是比较抽象.因此想研究下模型实现的具体过程,比如:1) 状态特征和转移特征具体是什么以及如何构造 2)前 ...

  4. Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解

    Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解 今天主要理一下StreamingContext的启动过程,其中最为重要的就是Jo ...

  5. [转]Linux内核源码详解--iostat

    Linux内核源码详解——命令篇之iostat 转自:http://www.cnblogs.com/york-hust/p/4846497.html 本文主要分析了Linux的iostat命令的源码, ...

  6. saltstack源码详解一

    目录 初识源码流程 入口 1.grains.items 2.pillar.items 2/3: 是否可以用python脚本实现 总结pillar源码分析: @(python之路)[saltstack源 ...

  7. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

  8. 源码详解系列(六) ------ 全面讲解druid的使用和源码

    简介 druid是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,druid还扩展 ...

  9. Mybatis源码详解系列(四)--你不知道的Mybatis用法和细节

    简介 这是 Mybatis 系列博客的第四篇,我本来打算详细讲解 mybatis 的配置.映射器.动态 sql 等,但Mybatis官方中文文档对这部分内容的介绍已经足够详细了,有需要的可以直接参考. ...

随机推荐

  1. Python的虚拟环境virtualenv

    原文地址:blog.sina.com.cn/s/blog_4ddef8f80101eu0w.html Python的虚拟环境可以使一个Python程序拥有独立的库library和解释器interpre ...

  2. 深入解析Java AtomicInteger 原子类型

    深入解析Java AtomicInteger原子类型 在进行并发编程的时候我们需要确保程序在被多个线程并发访问时可以得到正确的结果,也就是实现线程安全.线程安全的定义如下: 当多个线程访问某个类时,不 ...

  3. Java中ArrayList循环遍历并删除元素的陷阱

    ava中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下. 先写个测试代码: import java.util.ArrayList; public ...

  4. map_server地图服务器

    http://wiki.ros.org/map_server 概述 map_server提供map_server ROS节点,它提供地图数据作为一个ROS服务器.也提供map_saver命令行功能,能 ...

  5. python f-string

    文章目录 1. 主要内容 1.1. 旧时代的格式化字符串 1.1.1. Option #1: %-formatting 1.1.2. 怎样使用 %-formatting 1.1.3. 为什么 %-fo ...

  6. ARMV8 datasheet学习笔记3:AArch64应用级体系结构

    1.前言 本文主要从应用的角度介绍ARMV8的编程模型和存储模型 2. AArch64应用级编程模型 从应用的角度看到的ARM处理器元素: 可见的元素(寄存器/指令) 说明 可见的寄存器 R0-R30 ...

  7. sga 操作命令

    用SYS用户以SYSDBA身份登录系统alter system set sga_max_size=2000m scope=spfile;alter system set sga_target=2000 ...

  8. 修改tomcat的server.xml配置web项目

    <!-- VideoWeb:自己编写的web项目名 path:表示当输入访问项目.. 如果path="" URL=localhost:8080就可以访问项目了 如果path= ...

  9. Golang依赖管理工具:glide从入门到精通使用

    这是一个创建于 2017-07-22 05:33:09 的文章,其中的信息可能已经有所发展或是发生改变. 介绍 不论是开发Java还是你正在学习的Golang,都会遇到依赖管理问题.Java有牛逼轰轰 ...

  10. Java桌面程序打包成exe可执行文件

    前言: 我们都知道Java可以将二进制程序打包成可执行jar文件,双击这个jar和双击exe效果是一样一样的,但感觉还是不同.其实将java程序打包成exe也需要这个可执行jar文件. 准备: ecl ...