精华内容
下载资源
问答
  • 2019-07-01 17:40:20

    HMM(隐马尔科夫模型)是自然语言处理中的一个基本模型,用途比较广泛,如汉语分词、词性标注及语音识别等,在NLP中占有很重要的地位。网上关于HMM的介绍讲解文档很多,我自己当时开始看的时候也有点稀里糊涂。后来看到wiki上举得一个关于HMM的例子才如醍醐灌顶,忽然间明白HMM的三大问题是怎么回事了。例子我借助中文wiki重新翻译了一下,并对三大基本问题进行说明,希望对读者朋友有所帮助:

      Alice 和Bob是好朋友,但是他们离得比较远,每天都是通过电话了解对方那天作了什么.Bob仅仅对三种活动感兴趣:公园散步,购物以及清理房间.他选择做什么事情只凭当天天气.Alice对于Bob所住的地方的天气情况并不了解,但是知道总的趋势.在Bob告诉Alice每天所做的事情基础上,Alice想要猜测Bob所在地的天气情况.
      Alice认为天气的运行就像一个马尔可夫链. 其有两个状态 “雨”和”晴”,但是无法直接观察它们,也就是说,它们对于Alice是隐藏的.每天,Bob有一定的概率进行下列活动:”散步”, “购物”, 或 “清理”. 因为Bob会告诉Alice他的活动,所以这些活动就是Alice的观察数据.这整个系统就是一个隐马尔可夫模型HMM.
      Alice知道这个地区的总的天气趋势,并且平时知道Bob会做的事情.也就是说这个隐马尔可夫模型的参数是已知的.可以用程序语言(Python)写下来:
       // 状态数目,两个状态:雨或晴
       states = (‘Rainy’, ‘Sunny’)
       // 每个状态下可能的观察值
       observations = (‘walk’, ’shop’, ‘clean’)            
       //初始状态空间的概率分布
       start_probability = {‘Rainy’: 0.6, ‘Sunny’: 0.4}
       // 与时间无关的状态转移概率矩阵
       transition_probability = {
       ’Rainy’ : {‘Rainy’: 0.7, ‘Sunny’: 0.3},
       ’Sunny’ : {‘Rainy’: 0.4, ‘Sunny’: 0.6},
       }
       //给定状态下,观察值概率分布,发射概率
       emission_probability = {
       ’Rainy’ : {‘walk’: 0.1, ’shop’: 0.4, ‘clean’: 0.5},
       ’Sunny’ : {‘walk’: 0.6, ’shop’: 0.3, ‘clean’: 0.1},
       }
      在这些代码中,start_probability代表了Alice对于Bob第一次给她打电话时的天气情况的不确定性(Alice知道的只是那个地方平均起来下雨多些).在这里,这个特定的概率分布并非平衡的,平衡概率应该接近(在给定变迁概率的情况下){‘Rainy’: 0.571, ‘Sunny’: 0.429}。 transition_probability 表示马尔可夫链下的天气变迁情况,在这个例子中,如果今天下雨,那么明天天晴的概率只有30%.代码emission_probability 表示了Bob每天作某件事的概率.如果下雨,有 50% 的概率他在清理房间;如果天晴,则有60%的概率他在外头散步。
      Alice和Bob通了三天电话后发现第一天Bob去散步了,第二天他去购物了,第三天他清理房间了。Alice现在有两个问题:这个观察序列“散步、购物、清理”的总的概率是多少?(注:这个问题对应于HMM的基本问题之一:已知HMM模型λ及观察序列O,如何计算P(O|λ)?) 最能解释这个观察序列的状态序列(晴/雨)又是什么?(注:这个问题对应HMM基本问题之二:给定观察序列O=O1,O2,…OT以及模型λ,如何选择一个对应的状态序列S = q1,q2,…qT,使得S能够最为合理的解释观察序列O?)
      至于HMM的基本问题之三:如何调整模型参数, 使得P(O|λ)最大?这个问题事实上就是给出很多个观察序列值,来训练以上几个参数的问题。

     

    HMM学习最佳范例与崔晓源的博客

    分类 隐马尔科夫模型 

      “HMM学习最佳范例”与“崔晓源的博客”本来是不搭边的,由于自己花了几乎一个晚上浏览崔师兄的博客,没有时间写文章了,所以最终决定放在这里做成大杂烩,不过我觉得这个大杂烩还是有点价值的。
      先说说HMM,通过Google Analytics 发现,读者经常通过与“隐马尔科夫模型、HMM”相关的关键字访问52nlp的,因为这里曾经写了一篇简单的介绍HMM的文章。事实上,对于HMM,由于自己没有直接实践过,仅停留在“纸上得来”的程度,所以心里也虚。某天赶巧遇到了国外这个专门介绍HMM及其相关算法的主页:http://www.comp.leeds.ac.uk/roger/HiddenMarkovModels/html_dev/main.html
      里面图文并茂还动感十足,写得又通俗易懂,可以说是我见到过的介绍HMM最好的范例了。读完了立即有翻译全文的冲动,这样一方面可以给有需要的读者以帮助,另一方面翻译虽然耗时,但却能仔细把握一下比较细节的地方,这也是我翻译“MIT自然语言处理”的初衷。不过Google了一下,发现已经有人把这件事做了,这个人就是崔晓源,中文译名是“隐马尔科夫模型HMM自学”。
      原计划这一篇博客题目为“HMM学习最佳范例”的,准备介绍这个英文主页和崔晓源的翻译,出于尊重译者劳动的缘故,Google“隐马尔科夫模型HMM自学”,可是发现其被转载了很多,除了知道译者是“崔晓源”外,原始出处似乎被丢失了。不过最终还是找到了原始出处:
      http://blogcui.spaces.live.com/blog/cns!46BDB23E24219CE9!144.entry?_c=BlogPart
      其实就是崔师兄在msn spaces上的博客,仔细对比了一下原文,发现崔师兄的这个翻译是一个简化和缩略版本,有些地方只是概况性的翻译了一下,省去了一些内容,所以这个介绍HMM主页还有再翻译的必要。有可能的话,今后在52nlp上我会慢慢翻译HMM这个系列。
      对比完之后,我就浏览开他的博客了,没想到,一发而不可收,主要在于他的博客多数内容都很有价值。博客最集中更新的一段时间,是在05年5月到9月份他在MSRA-NLC组实习的时候,不过可惜的是,现在几乎不更新了,但是技术博客的好处再于其有效期更长,所以许多东西仍很可以参考,读者如果对NLP,IR或者机器学习感兴趣,不妨按时间顺序读读他的日志,定会有不小收获的,这里绝非广告。他目前在MSRA工作,以下是他的“About me”:
      ”A man full of enthusiasm for advanced technology business, natrual language processing, IR and search engine technology. I’m finding my way. You never do a bad job only if you choose the right time. So keep your pace accordingly.“
      我在上面最大的发现是这个关于机器学习的英文博客:

     

    几种不同程序语言的HMM版本

    分类 隐马尔科夫模型 

      “纸上得来终觉浅,绝知此事要躬行”,在继续翻译《HMM学习最佳范例》之前,这里先补充几个不同程序语言实现的HMM版本,主要参考了维基百科。读者有兴趣的话可以研究一下代码,这样对于HMM的学习会深刻很多!

    C语言版:
    1、 HTK(Hidden Markov Model Toolkit):
      HTK是英国剑桥大学开发的一套基于C语言的隐马尔科夫模型工具箱,主要应用于语音识别、语音合成的研究,也被用在其他领域,如字符识别和DNA排序等。HTK是重量级的HMM版本。
      HTK主页:http://htk.eng.cam.ac.uk/
    2、 GHMM Library:
      The General Hidden Markov Model library (GHMM) is a freely available LGPL-ed C library implementing efficient data structures and algorithms for basic and extended HMMs.
      GHMM主页:http://www.ghmm.org/
    3、 UMDHMM(Hidden Markov Model Toolkit):
      Hidden Markov Model (HMM) Software: Implementation of Forward-Backward, Viterbi, and Baum-Welch algorithms.
      这款属于轻量级的HMM版本。
      UMDHMM主页:http://www.kanungo.com/software/software.html

    Java版:
    4、 Jahmm Java Library (general-purpose Java library):
      Jahmm (pronounced “jam”), is a Java implementation of Hidden Markov Model (HMM) related algorithms. It’s been designed to be easy to use (e.g. simple things are simple to program) and general purpose.
      Jahmm主页:http://code.google.com/p/jahmm/

    Malab版:
    5、 Hidden Markov Model (HMM) Toolbox for Matlab:
      This toolbox supports inference and learning for HMMs with discrete outputs (dhmm’s), Gaussian outputs (ghmm’s), or mixtures of Gaussians output (mhmm’s).
      Matlab-HMM主页:http://www.cs.ubc.ca/~murphyk/Software/HMM/hmm.html

    Common Lisp版:
    6、CL-HMM Library (HMM Library for Common Lisp):
      Simple Hidden Markov Model library for ANSI Common Lisp. Main structures and basic algorithms implemented. Performance speed comparable to C code. It’s licensed under LGPL.
      CL-HMM主页:http://www.ashrentum.net/jmcejuela/programs/cl-hmm/

    Haskell版:
    7、The hmm package (A Haskell library for working with Hidden Markov Models):
      A simple library for working with Hidden Markov Models. Should be usable even by people who are not familiar with HMMs. Includes implementations of Viterbi’s algorithm and the forward algorithm.
      Haskell-HMM主页:http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hmm
      注:Haskell是一种纯函数式编程语言,它的命名源自美国数学家Haskell Brooks Curry,他在数学逻辑方面上的工作使得函数式编程语言有了广泛的基础。

      是否还有C++版、Perl版或者Python版呢?欢迎补充!

    HMM学习最佳范例一:介绍

      隐马尔科夫模型(HMM)依然是读者访问“我爱自然语言处理”的一个热门相关关键词,我曾在《HMM学习最佳范例与崔晓源的博客》中介绍过国外的一个不错的HMM学习教程,并且国内崔晓源师兄有一个相应的翻译版本,不过这个版本比较简化和粗略,有些地方只是概况性的翻译了一下,省去了一些内容,所以从今天开始计划在52nlp上系统的重新翻译这个学习教程,希望对大家有点用。

    一、介绍(Introduction)
      我们通常都习惯寻找一个事物在一段时间里的变化模式(规律)。这些模式发生在很多领域,比如计算机中的指令序列,句子中的词语顺序和口语单词中的音素序列等等,事实上任何领域中的一系列事件都有可能产生有用的模式。
      考虑一个简单的例子,有人试图通过一片海藻推断天气——民间传说告诉我们‘湿透的’海藻意味着潮湿阴雨,而‘干燥的’海藻则意味着阳光灿烂。如果它处于一个中间状态(‘有湿气’),我们就无法确定天气如何。然而,天气的状态并没有受限于海藻的状态,所以我们可以在观察的基础上预测天气是雨天或晴天的可能性。另一个有用的线索是前一天的天气状态(或者,至少是它的可能状态)——通过综合昨天的天气及相应观察到的海藻状态,我们有可能更好的预测今天的天气。
      这是本教程中我们将考虑的一个典型的系统类型。
      首先,我们将介绍产生概率模式的系统,如晴天及雨天间的天气波动。
      然后,我们将会看到这样一个系统,我们希望预测的状态并不是观察到的——其底层系统是隐藏的。在上面的例子中,观察到的序列将是海藻而隐藏的系统将是实际的天气。
      最后,我们会利用已经建立的模型解决一些实际的问题。对于上述例子,我们想知道:
      1. 给出一个星期每天的海藻观察状态,之后的天气将会是什么?
      2. 给定一个海藻的观察状态序列,预测一下此时是冬季还是夏季?直观地,如果一段时间内海藻都是干燥的,那么这段时间很可能是夏季,反之,如果一段时间内海藻都是潮湿的,那么这段时间可能是冬季。

    二、生成模式(Generating Patterns)

    1、确定性模式(Deterministic Patterns)
      考虑一套交通信号灯,灯的颜色变化序列依次是红色-红色/黄色-绿色-黄色-红色。这个序列可以作为一个状态机器,交通信号灯的不同状态都紧跟着上一个状态。
        
      注意每一个状态都是唯一的依赖于前一个状态,所以,如果交通灯为绿色,那么下一个颜色状态将始终是黄色——也就是说,该系统是确定性的。确定性系统相对比较容易理解和分析,因为状态间的转移是完全已知的。

    2、非确定性模式(Non-deterministic patterns)
      为了使天气那个例子更符合实际,加入第三个状态——多云。与交通信号灯例子不同,我们并不期望这三个天气状态之间的变化是确定性的,但是我们依然希望对这个系统建模以便生成一个天气变化模式(规律)。
      一种做法是假设模型的当前状态仅仅依赖于前面的几个状态,这被称为马尔科夫假设,它极大地简化了问题。显然,这可能是一种粗糙的假设,并且因此可能将一些非常重要的信息丢失。
      当考虑天气问题时,马尔科夫假设假定今天的天气只能通过过去几天已知的天气情况进行预测——而对于其他因素,譬如风力、气压等则没有考虑。在这个例子以及其他相似的例子中,这样的假设显然是不现实的。然而,由于这样经过简化的系统可以用来分析,我们常常接受这样的知识假设,虽然它产生的某些信息不完全准确。
                
      一个马尔科夫过程是状态间的转移仅依赖于前n个状态的过程。这个过程被称之为n阶马尔科夫模型,其中n是影响下一个状态选择的(前)n个状态。最简单的马尔科夫过程是一阶模型,它的状态选择仅与前一个状态有关。这里要注意它与确定性系统并不相同,因为下一个状态的选择由相应的概率决定,并不是确定性的。
      下图是天气例子中状态间所有可能的一阶状态转移情况:
        
      对于有M个状态的一阶马尔科夫模型,共有个状态转移,因为任何一个状态都有可能是所有状态的下一个转移状态。每一个状态转移都有一个概率值,称为状态转移概率——这是从一个状态转移到另一个状态的概率。所有的个概率可以用一个状态转移矩阵表示。注意这些概率并不随时间变化而不同——这是一个非常重要(但常常不符合实际)的假设。
      下面的状态转移矩阵显示的是天气例子中可能的状态转移概率:
        
      -也就是说,如果昨天是晴天,那么今天是晴天的概率为0.5,是多云的概率为0.375。注意,每一行的概率之和为1。
      要初始化这样一个系统,我们需要确定起始日天气的(或可能的)情况,定义其为一个初始概率向量,称为向量。
              
      -也就是说,第一天为晴天的概率为1。
      现在我们定义一个一阶马尔科夫过程如下:
       状态:三个状态——晴天,多云,雨天。
       向量:定义系统初始化时每一个状态的概率。
       状态转移矩阵:给定前一天天气情况下的当前天气概率。
      任何一个可以用这种方式描述的系统都是一个马尔科夫过程。

    3、总结
      我们尝试识别时间变化中的模式,并且为了达到这个目我们试图对这个过程建模以便产生这样的模式。我们使用了离散时间点、离散状态以及做了马尔科夫假设。在采用了这些假设之后,系统产生了这个被描述为马尔科夫过程的模式,它包含了一个向量(初始概率)和一个状态转移矩阵。关于假设,重要的一点是状态转移矩阵并不随时间的改变而改变——这个矩阵在整个系统的生命周期中是固定不变的。

    三、隐藏模式(Hidden Patterns)

    1、马尔科夫过程的局限性
      在某些情况下,我们希望找到的模式用马尔科夫过程描述还显得不充分。回顾一下天气那个例子,一个隐士也许不能够直接获取到天气的观察情况,但是他有一些水藻。民间传说告诉我们水藻的状态与天气状态有一定的概率关系——天气和水藻的状态是紧密相关的。在这个例子中我们有两组状态,观察的状态(水藻的状态)和隐藏的状态(天气的状态)。我们希望为隐士设计一种算法,在不能够直接观察天气的情况下,通过水藻和马尔科夫假设来预测天气。
      一个更实际的问题是语音识别,我们听到的声音是来自于声带、喉咙大小、舌头位置以及其他一些东西的组合结果。所有这些因素相互作用产生一个单词的声音,一套语音识别系统检测的声音就是来自于个人发音时身体内部物理变化所引起的不断改变的声音。
      一些语音识别装置工作的原理是将内部的语音产出看作是隐藏的状态,而将声音结果作为一系列观察的状态,这些由语音过程生成并且最好的近似了实际(隐藏)的状态。在这两个例子中,需要着重指出的是,隐藏状态的数目与观察状态的数目可以是不同的。一个包含三个状态的天气系统(晴天、多云、雨天)中,可以观察到4个等级的海藻湿润情况(干、稍干、潮湿、湿润);纯粹的语音可以由80个音素描述,而身体的发音系统会产生出不同数目的声音,或者比80多,或者比80少。
      在这种情况下,观察到的状态序列与隐藏过程有一定的概率关系。我们使用隐马尔科夫模型对这样的过程建模,这个模型包含了一个底层隐藏的随时间改变的马尔科夫过程,以及一个与隐藏状态某种程度相关的可观察到的状态集合。

    2、隐马尔科夫模型(Hidden Markov Models)
      下图显示的是天气例子中的隐藏状态和观察状态。假设隐藏状态(实际的天气)由一个简单的一阶马尔科夫过程描述,那么它们之间都相互连接。
      
      隐藏状态和观察状态之间的连接表示:在给定的马尔科夫过程中,一个特定的隐藏状态生成特定的观察状态的概率。这很清晰的表示了‘进入’一个观察状态的所有概率之和为1,在上面这个例子中就是Pr(Obs|Sun), Pr(Obs|Cloud) 及 Pr(Obs|Rain)之和。(对这句话我有点疑惑?)
      除了定义了马尔科夫过程的概率关系,我们还有另一个矩阵,定义为混淆矩阵(confusion matrix),它包含了给定一个隐藏状态后得到的观察状态的概率。对于天气例子,混淆矩阵是:
      
      注意矩阵的每一行之和是1。

    3、总结(Summary)
      我们已经看到在一些过程中一个观察序列与一个底层马尔科夫过程是概率相关的。在这些例子中,观察状态的数目可以和隐藏状态的数码不同。
      我们使用一个隐马尔科夫模型(HMM)对这些例子建模。这个模型包含两组状态集合和三组概率集合:
      * 隐藏状态:一个系统的(真实)状态,可以由一个马尔科夫过程进行描述(例如,天气)。
      * 观察状态:在这个过程中‘可视’的状态(例如,海藻的湿度)。
      * 向量:包含了(隐)模型在时间t=1时一个特殊的隐藏状态的概率(初始概率)。
      * 状态转移矩阵:包含了一个隐藏状态到另一个隐藏状态的概率
      * 混淆矩阵:包含了给定隐马尔科夫模型的某一个特殊的隐藏状态,观察到的某个观察状态的概率。
      因此一个隐马尔科夫模型是在一个标准的马尔科夫过程中引入一组观察状态,以及其与隐藏状态间的一些概率关系。

    四、隐马尔科夫模型(Hidden Markov Models)

    1、定义(Definition of a hidden Markov model)
      一个隐马尔科夫模型是一个三元组(, A, B)。
      :初始化概率向量;
      :状态转移矩阵;
      :混淆矩阵;
      在状态转移矩阵及混淆矩阵中的每一个概率都是时间无关的——也就是说,当系统演化时这些矩阵并不随时间改变。实际上,这是马尔科夫模型关于真实世界最不现实的一个假设。

    2、应用(Uses associated with HMMs)
      一旦一个系统可以作为HMM被描述,就可以用来解决三个基本问题。其中前两个是模式识别的问题:给定HMM求一个观察序列的概率(评估);搜索最有可能生成一个观察序列的隐藏状态训练(解码)。第三个问题是给定观察序列生成一个HMM(学习)。
     a) 评估(Evaluation)
      考虑这样的问题,我们有一些描述不同系统的隐马尔科夫模型(也就是一些( ,A,B)三元组的集合)及一个观察序列。我们想知道哪一个HMM最有可能产生了这个给定的观察序列。例如,对于海藻来说,我们也许会有一个“夏季”模型和一个“冬季”模型,因为不同季节之间的情况是不同的——我们也许想根据海藻湿度的观察序列来确定当前的季节。
      我们使用前向算法(forward algorithm)来计算给定隐马尔科夫模型(HMM)后的一个观察序列的概率,并因此选择最合适的隐马尔科夫模型(HMM)。
      在语音识别中这种类型的问题发生在当一大堆数目的马尔科夫模型被使用,并且每一个模型都对一个特殊的单词进行建模时。一个观察序列从一个发音单词中形成,并且通过寻找对于此观察序列最有可能的隐马尔科夫模型(HMM)识别这个单词。
     b) 解码( Decoding)
      给定观察序列搜索最可能的隐藏状态序列。
      另一个相关问题,也是最感兴趣的一个,就是搜索生成输出序列的隐藏状态序列。在许多情况下我们对于模型中的隐藏状态更感兴趣,因为它们代表了一些更有价值的东西,而这些东西通常不能直接观察到。
      考虑海藻和天气这个例子,一个盲人隐士只能感觉到海藻的状态,但是他更想知道天气的情况,天气状态在这里就是隐藏状态。
      我们使用Viterbi 算法(Viterbi algorithm)确定(搜索)已知观察序列及HMM下最可能的隐藏状态序列。
      Viterbi算法(Viterbi algorithm)的另一广泛应用是自然语言处理中的词性标注。在词性标注中,句子中的单词是观察状态,词性(语法类别)是隐藏状态(注意对于许多单词,如wind,fish拥有不止一个词性)。对于每句话中的单词,通过搜索其最可能的隐藏状态,我们就可以在给定的上下文中找到每个单词最可能的词性标注。
     C)学习(Learning)
      根据观察序列生成隐马尔科夫模型。
      第三个问题,也是与HMM相关的问题中最难的,根据一个观察序列(来自于已知的集合),以及与其有关的一个隐藏状态集,估计一个最合适的隐马尔科夫模型(HMM),也就是确定对已知序列描述的最合适的(,A,B)三元组。
      当矩阵A和B不能够直接被(估计)测量时,前向-后向算法(forward-backward algorithm)被用来进行学习(参数估计),这也是实际应用中常见的情况。

    3、总结(Summary)
      由一个向量和两个矩阵(,A,B)描述的隐马尔科夫模型对于实际系统有着巨大的价值,虽然经常只是一种近似,但它们却是经得起分析的。隐马尔科夫模型通常解决的问题包括:
      1. 对于一个观察序列匹配最可能的系统——评估,使用前向算法(forward algorithm)解决;
      2. 对于已生成的一个观察序列,确定最可能的隐藏状态序列——解码,使用Viterbi 算法(Viterbi algorithm)解决;
      3. 对于已生成的观察序列,决定最可能的模型参数——学习,使用前向-后向算法(forward-backward algorithm)解决。

    五、前向算法(Forward Algorithm)

    计算观察序列的概率(Finding the probability of an observed sequence)

    1.穷举搜索( Exhaustive search for solution)
      给定隐马尔科夫模型,也就是在模型参数(, A, B)已知的情况下,我们想找到观察序列的概率。还是考虑天气这个例子,我们有一个用来描述天气及与它密切相关的海藻湿度状态的隐马尔科夫模型(HMM),另外我们还有一个海藻的湿度状态观察序列。假设连续3天海藻湿度的观察结果是(干燥、湿润、湿透)——而这三天每一天都可能是晴天、多云或下雨,对于观察序列以及隐藏的状态,可以将其视为网格:

      网格中的每一列都显示了可能的的天气状态,并且每一列中的每个状态都与相邻列中的每一个状态相连。而其状态间的转移都由状态转移矩阵提供一个概率。在每一列下面都是某个时间点上的观察状态,给定任一个隐藏状态所得到的观察状态的概率由混淆矩阵提供。
      可以看出,一种计算观察序列概率的方法是找到每一个可能的隐藏状态,并且将这些隐藏状态下的观察序列概率相加。对于上面那个(天气)例子,将有3^3 = 27种不同的天气序列可能性,因此,观察序列的概率是:
      Pr(dry,damp,soggy | HMM) = Pr(dry,damp,soggy | sunny,sunny,sunny) + Pr(dry,damp,soggy | sunny,sunny ,cloudy) + Pr(dry,damp,soggy | sunny,sunny ,rainy) + . . . . Pr(dry,damp,soggy | rainy,rainy ,rainy)
      用这种方式计算观察序列概率极为昂贵,特别对于大的模型或较长的序列,因此我们可以利用这些概率的时间不变性来减少问题的复杂度。

    2.使用递归降低问题复杂度
      给定一个隐马尔科夫模型(HMM),我们将考虑递归地计算一个观察序列的概率。我们首先定义局部概率(partial probability),它是到达网格中的某个中间状态时的概率。然后,我们将介绍如何在t=1和t=n(>1)时计算这些局部概率。
      假设一个T-长观察序列是:
         
      
     2a.局部概率(’s)
      考虑下面这个网格,它显示的是天气状态及对于观察序列干燥,湿润及湿透的一阶状态转移情况:
       
      我们可以将计算到达网格中某个中间状态的概率作为所有到达这个状态的可能路径的概率求和问题。
      例如,t=2时位于“多云”状态的局部概率通过如下路径计算得出:
       
      我们定义t时刻位于状态j的局部概率为at(j)——这个局部概率计算如下:
      t ( j )= Pr( 观察状态 | 隐藏状态j ) x Pr(t时刻所有指向j状态的路径)
      对于最后的观察状态,其局部概率包括了通过所有可能的路径到达这些状态的概率——例如,对于上述网格,最终的局部概率通过如下路径计算得出:
       
      由此可见,对于这些最终局部概率求和等价于对于网格中所有可能的路径概率求和,也就求出了给定隐马尔科夫模型(HMM)后的观察序列概率。
      第3节给出了一个计算这些概率的动态示例。

    2b.计算t=1时的局部概率’s
      我们按如下公式计算局部概率:
      t ( j )= Pr( 观察状态 | 隐藏状态j ) x Pr(t时刻所有指向j状态的路径)
      特别当t=1时,没有任何指向当前状态的路径。故t=1时位于当前状态的概率是初始概率,即Pr(state|t=1)=P(state),因此,t=1时的局部概率等于当前状态的初始概率乘以相关的观察概率:
             
      所以初始时刻状态j的局部概率依赖于此状态的初始概率及相应时刻我们所见的观察概率。

    2c.计算t>1时的局部概率’s
      我们再次回顾局部概率的计算公式如下:
      t ( j )= Pr( 观察状态 | 隐藏状态j ) x Pr(t时刻所有指向j状态的路径)
      我们可以假设(递归地),乘号左边项“Pr( 观察状态 | 隐藏状态j )”已经有了,现在考虑其右边项“Pr(t时刻所有指向j状态的路径)”。
      为了计算到达某个状态的所有路径的概率,我们可以计算到达此状态的每条路径的概率并对它们求和,例如:
          
      计算所需要的路径数目随着观察序列的增加而指数级递增,但是t-1时刻’s给出了所有到达此状态的前一路径概率,因此,我们可以通过t-1时刻的局部概率定义t时刻的’s,即:
         
      故我们所计算的这个概率等于相应的观察概率(亦即,t+1时在状态j所观察到的符号的概率)与该时刻到达此状态的概率总和——这来自于上一步每一个局部概率的计算结果与相应的状态转移概率乘积后再相加——的乘积。
      注意我们已经有了一个仅利用t时刻局部概率计算t+1时刻局部概率的表达式。
      现在我们就可以递归地计算给定隐马尔科夫模型(HMM)后一个观察序列的概率了——即通过t=1时刻的局部概率’s计算t=2时刻的’s,通过t=2时刻的’s计算t=3时刻的’s等等直到t=T。给定隐马尔科夫模型(HMM)的观察序列的概率就等于t=T时刻的局部概率之和。

    2d.降低计算复杂度
      我们可以比较通过穷举搜索(评估)和通过递归前向算法计算观察序列概率的时间复杂度。
      我们有一个长度为T的观察序列O以及一个含有n个隐藏状态的隐马尔科夫模型l=(,A,B)。
      穷举搜索将包括计算所有可能的序列:
       
      公式
        
      对我们所观察到的概率求和——注意其复杂度与T成指数级关系。相反的,使用前向算法我们可以利用上一步计算的信息,相应地,其时间复杂度与T成线性关系。
    注:穷举搜索的时间复杂度是,前向算法的时间复杂度是,其中T指的是观察序列长度,N指的是隐藏状态数目。

    3.总结
      我们的目标是计算给定隐马尔科夫模型HMM下的观察序列的概率——Pr(observations |)。
      我们首先通过计算局部概率(’s)降低计算整个概率的复杂度,局部概率表示的是t时刻到达某个状态s的概率。
      t=1时,可以利用初始概率(来自于P向量)和观察概率Pr(observation|state)(来自于混淆矩阵)计算局部概率;而t>1时的局部概率可以利用t-时的局部概率计算。
      因此,这个问题是递归定义的,观察序列的概率就是通过依次计算t=1,2,…,T时的局部概率,并且对于t=T时所有局部概率’s相加得到的。
      注意,用这种方式计算观察序列概率的时间复杂度远远小于计算所有序列的概率并对其相加(穷举搜索)的时间复杂度。

      我们使用前向算法计算T长观察序列的概率:
         
      其中y的每一个是观察集合之一。局部(中间)概率(’s)是递归计算的,首先通过计算t=1时刻所有状态的局部概率:
         
      然后在每个时间点,t=2,… ,T时,对于每个状态的局部概率,由下式计算局部概率:
         
      也就是当前状态相应的观察概率与所有到达该状态的路径概率之积,其递归地利用了上一个时间点已经计算好的一些值。
      最后,给定HMM,,观察序列的概率等于T时刻所有局部概率之和:
         
      再重复说明一下,每一个局部概率(t > 2 时)都由前一时刻的结果计算得出。
      对于“天气”那个例子,下面的图表显示了t = 2为状态为多云时局部概率的计算过程。这是相应的观察概率b与前一时刻的局部概率与状态转移概率a相乘后的总和再求积的结果:
       

    总结(Summary)

      我们使用前向算法来计算给定隐马尔科夫模型(HMM)后的一个观察序列的概率。它在计算中利用递归避免对网格所有路径进行穷举计算。
      给定这种算法,可以直接用来确定对于已知的一个观察序列,在一些隐马尔科夫模型(HMMs)中哪一个HMM最好的描述了它——先用前向算法评估每一个(HMM),再选取其中概率最高的一个。

      首先需要说明的是,本节不是这个系列的翻译,而是作为前向算法这一章的补充,希望能从实践的角度来说明前向算法。除了用程序来解读hmm的前向算法外,还希望将原文所举例子的问题拿出来和大家探讨。
      文中所举的程序来自于UMDHMM这个C语言版本的HMM工具包,具体见《几种不同程序语言的HMM版本》。先说明一下UMDHMM这个包的基本情况,在linux环境下,进入umdhmm-v1.02目录,“make all”之后会产生4个可执行文件,分别是:
      genseq: 利用一个给定的隐马尔科夫模型产生一个符号序列(Generates a symbol sequence using the specified model sequence using the specified model)
      testfor: 利用前向算法计算log Prob(观察序列| HMM模型)(Computes log Prob(observation|model) using the Forward algorithm.)
      testvit: 对于给定的观察符号序列及HMM,利用Viterbi 算法生成最可能的隐藏状态序列(Generates the most like state sequence for a given symbol sequence, given the HMM, using Viterbi)
      esthmm: 对于给定的观察符号序列,利用BaumWelch算法学习隐马尔科夫模型HMM(Estimates the HMM from a given symbol sequence using BaumWelch)。
      这些可执行文件需要读入有固定格式的HMM文件及观察符号序列文件,格式要求及举例如下:
      HMM 文件格式:
    ——————————————————————–
        M= number of symbols
        N= number of states
        A:
        a11 a12 … a1N
        a21 a22 … a2N
        . . . .
         . . . .
         . . . .
        aN1 aN2 … aNN
        B:
        b11 b12 … b1M
        b21 b22 … b2M
        . . . .
        . . . .
         . . . .
        bN1 bN2 … bNM
        pi:
        pi1 pi2 … piN
    ——————————————————————–

      HMM文件举例:
    ——————————————————————–
        M= 2
        N= 3
        A:
        0.333 0.333 0.333
        0.333 0.333 0.333
        0.333 0.333 0.333
        B:
        0.5 0.5
        0.75 0.25
        0.25 0.75
        pi:
        0.333 0.333 0.333
    ——————————————————————–

      观察序列文件格式:
    ——————————————————————–
        T=seqence length
        o1 o2 o3 . . . oT
    ——————————————————————–

      观察序列文件举例:
    ——————————————————————–
        T= 10
        1 1 1 1 2 1 2 2 2 2
    ——————————————————————–

      对于前向算法的测试程序testfor来说,运行:
       testfor model.hmm(HMM文件) obs.seq(观察序列文件)
      就可以得到观察序列的概率结果的对数值,这里我们在testfor.c的第58行对数结果的输出下再加一行输出:
       fprintf(stdout, “prob(O| model) = %fn”, proba);
      就可以输出运用前向算法计算观察序列所得到的概率值。至此,所有的准备工作已结束,接下来,我们将进入具体的程序解读。
      首先,需要定义HMM的数据结构,也就是HMM的五个基本要素,在UMDHMM中是如下定义的(在hmm.h中):

    typedef struct
    {
    int N; /* 隐藏状态数目;Q={1,2,…,N} */
    int M; /* 观察符号数目; V={1,2,…,M}*/
    double **A; /* 状态转移矩阵A[1..N][1..N]. a[i][j] 是从t时刻状态i到t+1时刻状态j的转移概率 */
    double **B; /* 混淆矩阵B[1..N][1..M]. b[j][k]在状态j时观察到符合k的概率。*/
    double *pi; /* 初始向量pi[1..N],pi[i] 是初始状态概率分布 */
    } HMM;

    前向算法程序示例如下(在forward.c中):
    /*
     函数参数说明:
     *phmm:已知的HMM模型;T:观察符号序列长度;
     *O:观察序列;**alpha:局部概率;*pprob:最终的观察概率
    */
    void Forward(HMM *phmm, int T, int *O, double **alpha, double *pprob)
    {
      int i, j;   /* 状态索引 */
      int t;    /* 时间索引 */
      double sum; /*求局部概率时的中间值 */

      /* 1. 初始化:计算t=1时刻所有状态的局部概率: */
      for (i = 1; i <= phmm->N; i++)
        alpha[1][i] = phmm->pi[i]* phmm->B[i][O[1]];
      
      /* 2. 归纳:递归计算每个时间点,t=2,… ,T时的局部概率 */
      for (t = 1; t < T; t++)
      {
        for (j = 1; j <= phmm->N; j++)
        {
          sum = 0.0;
          for (i = 1; i <= phmm->N; i++)
            sum += alpha[t][i]* (phmm->A[i][j]);
          alpha[t+1][j] = sum*(phmm->B[j][O[t+1]]);
        }
      }

      /* 3. 终止:观察序列的概率等于T时刻所有局部概率之和*/
      *pprob = 0.0;
      for (i = 1; i <= phmm->N; i++)
        *pprob += alpha[T][i];
    }

      下一节我将用这个程序来验证英文原文中所举前向算法演示例子的问题。

      在HMM这个翻译系列的原文中,作者举了一个前向算法的交互例子,这也是这个系列中比较出彩的地方,但是,在具体运行这个例子的时候,却发现其似乎有点问题。
      先说一下如何使用这个交互例子,运行时需要浏览器支持java,我用的是firefox。首先在Set按钮前面的对话框里上观察序列,如“Dry,Damp, Soggy” 或“Dry Damp Soggy”,观察符号间用逗号或空格隔开;然后再点击Set按钮,这样就初始化了观察矩阵;如果想得到一个总的结果,即Pr(观察序列|隐马尔科夫模型),就点旁边的Run按钮;如果想一步一步观察计算过程,即每个节点的局部概率,就单击旁边的Step按钮。
      原文交互例子(即天气这个例子)中所定义的已知隐马尔科夫模型如下:
      1、隐藏状态 (天气):Sunny,Cloudy,Rainy;
      2、观察状态(海藻湿度):Dry,Dryish,Damp,Soggy;
      3、初始状态概率: Sunny(0.63), Cloudy(0.17), Rainy(0.20);
      4、状态转移矩阵:

                 weather today
                 Sunny Cloudy Rainy
         weather Sunny 0.500 0.375 0.125
        yesterday Cloudy 0.250 0.125 0.625
              Rainy  0.250 0.375 0.375

      5、混淆矩阵:

                observed states
               Dry Dryish Damp Soggy
             Sunny 0.60 0.20 0.15 0.05
        hidden  Cloudy 0.25 0.25 0.25 0.25
        states  Rainy 0.05 0.10 0.35 0.50

      为了UMDHMM也能运行这个例子,我们将上述天气例子中的隐马尔科夫模型转化为如下的UMDHMM可读的HMM文件weather.hmm:
    ——————————————————————–
        M= 4
        N= 3 
        A:
        0.500 0.375 0.125
        0.250 0.125 0.625
        0.250 0.375 0.375
        B:
        0.60 0.20 0.15 0.05
        0.25 0.25 0.25 0.25
        0.05 0.10 0.35 0.50
        pi:
        0.63 0.17 0.20
    ——————————————————————–
      在运行例子之前,如果读者也想观察每一步的运算结果,可以将umdhmm-v1.02目录下forward.c中的void Forward(…)函数替换如下:
    ——————————————————————–
    void Forward(HMM *phmm, int T, int *O, double **alpha, double *pprob)
    {
      int i, j; /* state indices */
      int t; /* time index */
      double sum; /* partial sum */
      
      /* 1. Initialization */
      for (i = 1; i <= phmm->N; i++)
      {
        alpha[1][i] = phmm->pi[i]* phmm->B[i][O[1]];
        printf( “a[1][%d] = pi[%d] * b[%d][%d] = %f * %f = %f\n”,i, i, i, O[i], phmm->pi[i], phmm->B[i][O[1]], alpha[1][i] );
      }
      
      /* 2. Induction */
      for (t = 1; t < T; t++)
      {
        for (j = 1; j <= phmm->N; j++)
        {
          sum = 0.0;
          for (i = 1; i <= phmm->N; i++)
          {
            sum += alpha[t][i]* (phmm->A[i][j]);
            printf( “a[%d][%d] * A[%d][%d] = %f * %f = %f\n”, t, i, i, j, alpha[t][i], phmm->A[i][j], alpha[t][i]* (phmm->A[i][j]));
            printf( “sum = %f\n”, sum );
          }
          alpha[t+1][j] = sum*(phmm->B[j][O[t+1]]);
          printf( “a[%d][%d] = sum * b[%d][%d]] = %f * %f = %f\n”,t+1, j, j, O[t+1], sum, phmm->B[j][O[t+1]], alpha[t+1][j] );
        }
      }

      /* 3. Termination */
      *pprob = 0.0;
      for (i = 1; i <= phmm->N; i++)
      {
        *pprob += alpha[T][i];
        printf( “alpha[%d][%d] = %f\n”, T, i, alpha[T][i] );
        printf( “pprob = %f\n”, *pprob );
      }
    }
    ——————————————————————–
      替换完毕之后,重新“make clean”,“make all”,这样新的testfor可执行程序就可以输出前向算法每一步的计算结果。
      现在我们就用testfor来运行原文中默认给出的观察序列“Dry,Damp,Soggy”,其所对应的UMDHMM可读的观察序列文件test1.seq:
    ——————————————————————–
        T=3
        1 3 4
    ——————————————————————–
      好了,一切准备工作就绪,现在就输入如下命令:
        testfor weather.hmm test1.seq > result1
      result1就包含了所有的结果细节:
    ——————————————————————–
    Forward without scaling
    a[1][1] = pi[1] * b[1][1] = 0.630000 * 0.600000 = 0.378000
    a[1][2] = pi[2] * b[2][3] = 0.170000 * 0.250000 = 0.042500
    a[1][3] = pi[3] * b[3][4] = 0.200000 * 0.050000 = 0.010000

    pprob = 0.026901
    log prob(O| model) = -3.615577E+00
    prob(O| model) = 0.026901


    ——————————————————————–
      黑体部分是最终的观察序列的概率结果,即本例中的Pr(观察序列|HMM) = 0.026901。
      但是,在原文中点Run按钮后,结果却是:Probability of this model = 0.027386915。
      这其中的差别到底在哪里?我们来仔细观察一下中间运行过程:
      在初始化亦t=1时刻的局部概率计算两个是一致的,没有问题。但是,t=2时,在隐藏状态“Sunny”的局部概率是不一致的。英文原文给出的例子的运行结果是:
      Alpha = (((0.37800002*0.5) + (0.0425*0.375) + (0.010000001*0.125)) * 0.15) = 0.03092813
      而UMDHMM给出的结果是:
    ——————————————————————–
      a[1][1] * A[1][1] = 0.378000 * 0.500000 = 0.189000
      sum = 0.189000
      a[1][2] * A[2][1] = 0.042500 * 0.250000 = 0.010625
      sum = 0.199625
      a[1][3] * A[3][1] = 0.010000 * 0.250000 = 0.002500
      sum = 0.202125
      a[2][1] = sum * b[1][3]] = 0.202125 * 0.150000 = 0.030319
    ——————————————————————–
      区别就在于状态转移概率的选择上,原文选择的是状态转移矩阵中的第一行,而UMDHMM选择的则是状态转移矩阵中的第一列。如果从原文给出的状态转移矩阵来看,第一行代表的是从前一时刻的状态“Sunny”分别到当前时刻的状态“Sunny”,“Cloudy”,“Rainy”的概率;而第一列代表的是从前一时刻的状态“Sunny”,“Cloudy”,“Rainy”分别到当前时刻状态“Sunny”的概率。这样看来似乎原文的计算过程有误,读者不妨多试几个例子看看,前向算法这一章就到此为止了。

    HMM学习最佳范例六:维特比算法

    寻找最可能的隐藏状态序列(Finding most probable sequence of hidden states)


      对于一个特殊的隐马尔科夫模型(HMM)及一个相应的观察序列,我们常常希望能找到生成此序列最可能的隐藏状态序列。

    1.穷举搜索
      我们使用下面这张网格图片来形象化的说明隐藏状态和观察状态之间的关系:

      我们可以通过列出所有可能的隐藏状态序列并且计算对于每个组合相应的观察序列的概率来找到最可能的隐藏状态序列。最可能的隐藏状态序列是使下面这个概率最大的组合:
          Pr(观察序列|隐藏状态的组合)
      例如,对于网格中所显示的观察序列,最可能的隐藏状态序列是下面这些概率中最大概率所对应的那个隐藏状态序列:
      Pr(dry,damp,soggy | sunny,sunny,sunny), Pr(dry,damp,soggy | sunny,sunny,cloudy), Pr(dry,damp,soggy | sunny,sunny,rainy), . . . . Pr(dry,damp,soggy | rainy,rainy,rainy)
      这种方法是可行的,但是通过穷举计算每一个组合的概率找到最可能的序列是极为昂贵的。与前向算法类似,我们可以利用这些概率的时间不变性来降低计算复杂度。

    2.使用递归降低复杂度
      给定一个观察序列和一个隐马尔科夫模型(HMM),我们将考虑递归地寻找最有可能的隐藏状态序列。我们首先定义局部概率,它是到达网格中的某个特殊的中间状态时的概率。然后,我们将介绍如何在t=1和t=n(>1)时计算这些局部概率。
      这些局部概率与前向算法中所计算的局部概率是不同的,因为它们表示的是时刻t时到达某个状态最可能的路径的概率,而不是所有路径概率的总和。
     2a.局部概率’s和局部最佳途径
      考虑下面这个网格,它显示的是天气状态及对于观察序列干燥,湿润及湿透的一阶状态转移情况:
       
      对于网格中的每一个中间及终止状态,都有一个到达该状态的最可能路径。举例来说,在t=3时刻的3个状态中的每一个都有一个到达此状态的最可能路径,或许是这样的:
      
      我们称这些路径局部最佳路径(partial best paths)。其中每个局部最佳路径都有一个相关联的概率,即局部概率或。与前向算法中的局部概率不同,是到达该状态(最可能)的一条路径的概率。
      因而(i,t)是t时刻到达状态i的所有序列概率中最大的概率,而局部最佳路径是得到此最大概率的隐藏状态序列。对于每一个可能的i和t值来说,这一类概率(及局部路径)均存在。
      特别地,在t=T时每一个状态都有一个局部概率和一个局部最佳路径。这样我们就可以通过选择此时刻包含最大局部概率的状态及其相应的局部最佳路径来确定全局最佳路径(最佳隐藏状态序列)。

    2b.计算t=1时刻的局部概率’s
      我们计算的局部概率是作为最可能到达我们当前位置的路径的概率(已知的特殊知识如观察概率及前一个状态的概率)。当t=1的时候,到达某状态的最可能路径明显是不存在的;但是,我们使用t=1时的所处状态的初始概率及相应的观察状态k1的观察概率计算局部概率;即
              
      ——与前向算法类似,这个结果是通过初始概率和相应的观察概率相乘得出的。

    2c.计算t>1时刻的局部概率’s
      现在我们来展示如何利用t-1时刻的局部概率计算t时刻的局部概率。
      考虑如下的网格:
        
      我们考虑计算t时刻到达状态X的最可能的路径;这条到达状态X的路径将通过t-1时刻的状态A,B或C中的某一个。
      因此,最可能的到达状态X的路径将是下面这些路径的某一个
           (状态序列),…,A,X
           (状态序列),…,B,X
    或      (状态序列),…,C,X
      我们想找到路径末端是AX,BX或CX并且拥有最大概率的路径。
      回顾一下马尔科夫假设:给定一个状态序列,一个状态发生的概率只依赖于前n个状态。特别地,在一阶马尔可夫假设下,状态X在一个状态序列后发生的概率只取决于之前的一个状态,即
       Pr (到达状态A最可能的路径) .Pr (X | A) . Pr (观察状态 | X)
      与此相同,路径末端是AX的最可能的路径将是到达A的最可能路径再紧跟X。相似地,这条路径的概率将是:
       Pr (到达状态A最可能的路径) .Pr (X | A) . Pr (观察状态 | X)
      因此,到达状态X的最可能路径概率是:
      
      其中第一项是t-1时刻的局部概率,第二项是状态转移概率以及第三项是观察概率。
      泛化上述公式,就是在t时刻,观察状态是kt,到达隐藏状态i的最佳局部路径的概率是:
         
      这里,我们假设前一个状态的知识(局部概率)是已知的,同时利用了状态转移概率和相应的观察概率之积。然后,我们就可以在其中选择最大的概率了(局部概率)。

    2d.反向指针,’s
      考虑下面这个网格
       
      在每一个中间及终止状态我们都知道了局部概率,(i,t)。然而我们的目标是在给定一个观察序列的情况下寻找网格中最可能的隐藏状态序列——因此,我们需要一些方法来记住网格中的局部最佳路径。
      回顾一下我们是如何计算局部概率的,计算t时刻的’s我们仅仅需要知道t-1时刻的’s。在这个局部概率计算之后,就有可能记录前一时刻哪个状态生成了(i,t)——也就是说,在t-1时刻系统必须处于某个状态,该状态导致了系统在t时刻到达状态i是最优的。这种记录(记忆)是通过对每一个状态赋予一个反向指针完成的,这个指针指向最优的引发当前状态的前一时刻的某个状态。
      形式上,我们可以写成如下的公式
        
      其中argmax运算符是用来计算使括号中表达式的值最大的索引j的。
      请注意这个表达式是通过前一个时间步骤的局部概率’s和转移概率计算的,并不包括观察概率(与计算局部概率’s本身不同)。这是因为我们希望这些’s能回答这个问题“如果我在这里,最可能通过哪条路径到达下一个状态?”——这个问题与隐藏状态有关,因此与观察概率有关的混淆(矩阵)因子是可以被忽略的。

    2e.维特比算法的优点
      使用Viterbi算法对观察序列进行解码有两个重要的优点:
      1. 通过使用递归减少计算复杂度——这一点和前向算法使用递归减少计算复杂度是完全类似的。
      2.维特比算法有一个非常有用的性质,就是对于观察序列的整个上下文进行了最好的解释(考虑)。事实上,寻找最可能的隐藏状态序列不止这一种方法,其他替代方法也可以,譬如,可以这样确定如下的隐藏状态序列:
        
    其中
        
      这里,采用了“自左向右”的决策方式进行一种近似的判断,其对于每个隐藏状态的判断是建立在前一个步骤的判断的基础之上(而第一步从隐藏状态的初始向量开始)。
      这种做法,如果在整个观察序列的中部发生“噪音干扰”时,其最终的结果将与正确的答案严重偏离。
      相反, 维特比算法在确定最可能的终止状态前将考虑整个观察序列,然后通过指针“回溯”以确定某个隐藏状态是否是最可能的隐藏状态序列中的一员。这是非常有用的,因为这样就可以孤立序列中的“噪音”,而这些“噪音”在实时数据中是很常见的。

    3.小结
      维特比算法提供了一种有效的计算方法来分析隐马尔科夫模型的观察序列,并捕获最可能的隐藏状态序列。它利用递归减少计算量,并使用整个序列的上下文来做判断,从而对包含“噪音”的序列也能进行良好的分析。
      在使用时,维特比算法对于网格中的每一个单元(cell)都计算一个局部概率,同时包括一个反向指针用来指示最可能的到达该单元的路径。当完成整个计算过程后,首先在终止时刻找到最可能的状态,然后通过反向指针回溯到t=1时刻,这样回溯路径上的状态序列就是最可能的隐藏状态序列了。

    1、维特比算法的形式化定义
      维特比算法可以形式化的概括为:
      对于每一个i,i = 1,… ,n,令:
         
      ——这一步是通过隐藏状态的初始概率和相应的观察概率之积计算了t=1时刻的局部概率。
      对于t=2,…,T和i=1,…,n,令:
         
      ——这样就确定了到达下一个状态的最可能路径,并对如何到达下一个状态做了记录。具体来说首先通过考察所有的转移概率与上一步获得的最大的局部概率之积,然后记录下其中最大的一个,同时也包含了上一步触发此概率的状态。
      令:
         
      ——这样就确定了系统完成时(t=T)最可能的隐藏状态。
      对于t=T-1,…,1
      令:
         
      ——这样就可以按最可能的状态路径在整个网格回溯。回溯完成时,对于观察序列来说,序列i1 … iT就是生成此观察序列的最可能的隐藏状态序列。

      2.计算单独的’s和’s
      维特比算法中的局部概率’s的计算与前向算法中的局部概率’s的很相似。下面这幅图表显示了’s和’s的计算细节,可以对比一下前向算法3中的计算局部概率’s的那幅图表:
      
      唯一不同的是前向算法中计算局部概率’s时的求和符号()在维特比算法中计算局部概率’s时被替换为max——这一个重要的不同也说明了在维特比算法中我们选择的是到达当前状态的最可能路径,而不是总的概率。我们在维特比算法中维护了一个“反向指针”记录了到达当前状态的最佳路径,即在计算’s时通过argmax运算符获得。

    总结(Summary)

      对于一个特定的隐马尔科夫模型,维特比算法被用来寻找生成一个观察序列的最可能的隐藏状态序列。我们利用概率的时间不变性,通过避免计算网格中每一条路径的概率来降低问题的复杂度。维特比算法对于每一个状态(t>1)都保存了一个反向指针(),并在每一个状态中存储了一个局部概率()。
      局部概率是由反向指针指示的路径到达某个状态的概率。
      当t=T时,维特比算法所到达的这些终止状态的局部概率’s是按照最优(最可能)的路径到达该状态的概率。因此,选择其中最大的一个,并回溯找出所隐藏的状态路径,就是这个问题的最好答案。
      关于维特比算法,需要着重强调的一点是它不是简单的对于某个给定的时间点选择最可能的隐藏状态,而是基于全局序列做决策——因此,如果在观察序列中有一个“非寻常”的事件发生,对于维特比算法的结果也影响不大。
      这在语音处理中是特别有价值的,譬如当某个单词发音的一个中间音素出现失真或丢失的情况时,该单词也可以被识别出来。

      仍然需要说明的是,本节不是这个系列的翻译,而是作为维特比算法这一章的补充,将UMDHMM这个C语言版本的HMM工具包中的维特比算法程序展示给大家,并运行包中所附带的例子。关于UMDHMM这个工具包的介绍,大家可以参考前向算法4中的介绍。

    维特比算法程序示例如下(在viterbi.c中):
    void Viterbi(HMM *phmm, int T, int *O, double **delta, int **psi,int *q, double *pprob)
    {
      int i, j; /* state indices */
      int t; /* time index */

      int maxvalind;
      double maxval, val;

      /* 1. Initialization */

      for (i = 1; i <= phmm->N; i++)
      {
        delta[1][i] = phmm->pi[i] * (phmm->B[i][O[1]]);
        psi[1][i] = 0;
      }

      /* 2. Recursion */
      for (t = 2; t <= T; t++)
      {
        for (j = 1; j <= phmm->N; j++)
        {
          maxval = 0.0;
          maxvalind = 1;
          for (i = 1; i <= phmm->N; i++)
          {
            val = delta[t-1][i]*(phmm->A[i][j]);
            if (val > maxval)
            {
              maxval = val;
              maxvalind = i;
            }
          }

          delta[t][j] = maxval*(phmm->B[j][O[t]]);
          psi[t][j] = maxvalind;

        }
      }

      /* 3. Termination */

      *pprob = 0.0;
      q[T] = 1;
      for (i = 1; i <= phmm->N; i++)
      {
        if (delta[T][i] > *pprob)
        {
          *pprob = delta[T][i];
          q[T] = i;
        }
      }

      /* 4. Path (state sequence) backtracking */

      for (t = T – 1; t >= 1; t–)
        q[t] = psi[t+1][q[t+1]];

    }

      在UMDHMM包中所生成的4个可执行程序中,testvit是用来测试维特比算法的, 对于给定的观察符号序列及HMM,利用Viterbi 算法生成最可能的隐藏状态序列。这里我们利用UMDHMM包中test.hmm和test.seq来测试维特比算法,关于这两个文件,具体如下:
      test.hmm
    ——————————————————————–
        M= 2
        N= 3
        A:
        0.333 0.333 0.333
        0.333 0.333 0.333
        0.333 0.333 0.333
        B:
        0.5 0.5
        0.75 0.25
        0.25 0.75
        pi:
        0.333 0.333 0.333
    ——————————————————————–

      test.seq
    ——————————————————————–
        T= 10
        1 1 1 1 2 1 2 2 2 2
    ——————————————————————–

      对于维特比算法的测试程序testvit来说,运行:
       testvit test.hmm test.seq
      结果如下:
      ————————————
      Viterbi using direct probabilities
      Viterbi MLE log prob = -1.387295E+01
      Optimal state sequence:
      T= 10
      2 2 2 2 3 2 3 3 3 3
      ————————————
      Viterbi using log probabilities
      Viterbi MLE log prob = -1.387295E+01
      Optimal state sequence:
      T= 10
      2 2 2 2 3 2 3 3 3 3
      ————————————
      The two log probabilites and optimal state sequences
      should identical (within numerical precision).

      序列“2 2 2 2 3 2 3 3 3 3”就是最终所找到的隐藏状态序列。好了,维特比算法这一章就到此为止了。

    HMM学习最佳范例七:前向-后向算法1

    分类 隐马尔科夫模型 

    七、前向-后向算法(Forward-backward algorithm)

    根据观察序列生成隐马尔科夫模型(Generating a HMM from a sequence of obersvations)

      与HMM模型相关的“有用”的问题是评估(前向算法)和解码(维特比算法)——它们一个被用来测量一个模型的相对适用性,另一个被用来推测模型隐藏的部分在做什么(“到底发生了”什么)。可以看出它们都依赖于隐马尔科夫模型(HMM)参数这一先验知识——状态转移矩阵,混淆(观察)矩阵,以及向量(初始化概率向量)。
      然而,在许多实际问题的情况下这些参数都不能直接计算的,而要需要进行估计——这就是隐马尔科夫模型中的学习问题。前向-后向算法就可以以一个观察序列为基础来进行这样的估计,而这个观察序列来自于一个给定的集合,它所代表的是一个隐马尔科夫模型中的一个已知的隐藏集合。
      一个例子可能是一个庞大的语音处理数据库,其底层的语音可能由一个马尔可夫过程基于已知的音素建模的,而其可以观察的部分可能由可识别的状态(可能通过一些矢量数据表示)建模的,但是没有(直接)的方式来获取隐马尔科夫模型(HMM)参数。
      前向-后向算法并非特别难以理解,但自然地比前向算法和维特比算法更复杂。由于这个原因,这里就不详细讲解前向-后向算法了(任何有关HMM模型的参考文献都会提供这方面的资料的)。
      总之,前向-后向算法首先对于隐马尔科夫模型的参数进行一个初始的估计(这很可能是完全错误的),然后通过对于给定的数据评估这些参数的的价值并减少它们所引起的错误来重新修订这些HMM参数。从这个意义上讲,它是以一种梯度下降的形式寻找一种错误测度的最小值。
      之所以称其为前向-后向算法,主要是因为对于网格中的每一个状态,它既计算到达此状态的“前向”概率(给定当前模型的近似估计),又计算生成此模型最终状态的“后向”概率(给定当前模型的近似估计)。 这些都可以通过利用递归进行有利地计算,就像我们已经看到的。可以通过利用近似的HMM模型参数来提高这些中间概率进行调整,而这些调整又形成了前向-后向算法迭代的基础。

    注:关于前向-后向算法,原文只讲了这么多,后继我将按自己的理解补充一些内容。

      要理解前向-后向算法,首先需要了解两个算法:后向算法和EM算法。后向算法是必须的,因为前向-后向算法就是利用了前向算法与后向算法中的变量因子,其得名也因于此;而EM算法不是必须的,不过由于前向-后向算法是EM算法的一个特例,因此了解一下EM算法也是有好处的,说实话,对于EM算法,我也是云里雾里的。好了,废话少说,我们先谈谈后向算法。

    1、后向算法(Backward algorithm)
      其实如果理解了前向算法,后向算法也是比较好理解的,这里首先重新定义一下前向算法中的局部概率at(i),称其为前向变量,这也是为前向-后向算法做点准备:
       
      相似地,我们也可以定义一个后向变量Bt(i)(同样可以理解为一个局部概率):
       
      后向变量(局部概率)表示的是已知隐马尔科夫模型及t时刻位于隐藏状态Si这一事实,从t+1时刻到终止时刻的局部观察序列的概率。同样与前向算法相似,我们可以从后向前(故称之为后向算法)递归地计算后向变量:
      1)初始化,令t=T时刻所有状态的后向变量为1:
         
      2)归纳,递归计算每个时间点,t=T-1,T-2,…,1时的后向变量:
      
      这样就可以计算每个时间点上所有的隐藏状态所对应的后向变量,如果需要利用后向算法计算观察序列的概率,只需将t=1时刻的后向变量(局部概率)相加即可。下图显示的是t+1时刻与t时刻的后向变量之间的关系:
       
      上述主要参考自HMM经典论文《A tutorial on Hidden Markov Models and selected applications in speech recognition》。下面我们给出利用后向算法计算观察序列概率的程序示例,这个程序仍然来自于UMDHMM

    后向算法程序示例如下(在backward.c中):

    void Backward(HMM *phmm, int T, int *O, double **beta, double *pprob)
    {
      int i, j; /* state indices */
      int t; /* time index */
      double sum;

      /* 1. Initialization */
      for (i = 1; i <= phmm->N; i++)
        beta[T][i] = 1.0;

      /* 2. Induction */
      for (t = T - 1; t >= 1; t--)
      {
        for (i = 1; i <= phmm->N; i++)
        {
          sum = 0.0;
          for (j = 1; j <= phmm->N; j++)
            sum += phmm->A[i][j] *
                  (phmm->B[j][O[t+1]])*beta[t+1][j];
          beta[t][i] = sum;
        }
      }

      /* 3. Termination */
      *pprob = 0.0;
      for (i = 1; i <= phmm->N; i++)
        *pprob += beta[1][i];
    }

      好了,后向算法就到此为止了,下一节我们粗略的谈谈EM算法。

    前向-后向算法是Baum于1972年提出来的,又称之为Baum-Welch算法,虽然它是EM(Expectation-Maximization)算法的一个特例,但EM算法却是于1977年提出的。那么为什么说前向-后向算法是EM算法的一个特例呢?这里有两点需要说明一下。
      第一,1977年A. P. Dempster、N. M. Laird、 D. B. Rubin在其论文“Maximum Likelihood from Incomplete Data via the EM Algorithm”中首次提出了EM算法的概念,但是他们也在论文的介绍中提到了在此之前就有一些学者利用了EM算法的思想解决了一些特殊问题,其中就包括了Baum在70年代初期的相关工作,只是这类方法没有被总结而已,他们的工作就是对这类解决问题的方法在更高的层次上定义了一个完整的EM算法框架。
      第二,对于前向-后向算法与EM算法的关系,此后在许多与HMM或EM相关的论文里都被提及,其中贾里尼克(Jelinek)老先生在1997所著的书“Statistical Methods for Speech Recognition”中对于前向-后向算法与EM算法的关系进行了完整的描述,读者有兴趣的话可以找来这本书读读。
      关于EM算法的讲解,网上有很多,这里我就不献丑了,直接拿目前搜索“EM算法”在Google排名第一的文章做了参考,希望读者不要拍砖:

      EM 算法是 Dempster,Laind,Rubin 于 1977 年提出的求参数极大似然估计的一种方法,它可以从非完整数据集中对参数进行 MLE 估计,是一种非常简单实用的学习算法。这种方法可以广泛地应用于处理缺损数据,截尾数据,带有讨厌数据等所谓的不完全数据(incomplete data)。
      假定集合Z = (X,Y)由观测数据 X 和未观测数据Y 组成,Z = (X,Y)和 X 分别称为完整数据和不完整数据。假设Z的联合概率密度被参数化地定义为P(X,Y|Θ),其中Θ 表示要被估计的参数。Θ 的最大似然估计是求不完整数据的对数似然函数L(X;Θ)的最大值而得到的:
       L(Θ; X )= log p(X |Θ) = ∫log p(X ,Y |Θ)dY ;(1)
      EM算法包括两个步骤:由E步和M步组成,它是通过迭代地最大化完整数据的对数似然函数Lc( X;Θ )的期望来最大化不完整数据的对数似然函数,其中:
       Lc(X;Θ) =log p(X,Y |Θ) ; (2)
      假设在算法第t次迭代后Θ 获得的估计记为Θ(t ) ,则在(t+1)次迭代时,
      E-步:计算完整数据的对数似然函数的期望,记为:
       Q(Θ |Θ (t) ) = E{Lc(Θ;Z)|X;Θ(t) }; (3)
      M-步:通过最大化Q(Θ |Θ(t) ) 来获得新的Θ 。
      通过交替使用这两个步骤,EM算法逐步改进模型的参数,使参数和训练样本的似然概率逐渐增大,最后终止于一个极大点。
      直观地理解EM算法,它也可被看作为一个逐次逼近算法:事先并不知道模型的参数,可以随机的选择一套参数或者事先粗略地给定某个初始参数λ0 ,确定出对应于这组参数的最可能的状态,计算每个训练样本的可能结果的概率,在当前的状态下再由样本对参数修正,重新估计参数λ ,并在新的参数下重新确定模型的状态,这样,通过多次的迭代,循环直至某个收敛条件满足为止,就可以使得模型的参数逐渐逼近真实参数。
      EM算法的主要目的是提供一个简单的迭代算法计算后验密度函数,它的最大优点是简单和稳定,但容易陷入局部最优。
      参考原文见:http://49805085.spaces.live.com/Blog/cns!145C40DDDB4C6E5!670.entry

      注意上面那段粗体字,读者如果觉得EM算法不好理解的话,就记住这段粗体字的意思吧!
      有了后向算法和EM算法的预备知识,下一节我们就正式的谈一谈前向-后向算法。

     隐马尔科夫模型(HMM)的三个基本问题中,第三个HMM参数学习的问题是最难的,因为对于给定的观察序列O,没有任何一种方法可以精确地找到一组最优的隐马尔科夫模型参数(A、B、)使P(O|)最大。因而,学者们退而求其次,不能使P(O|)全局最优,就寻求使其局部最优(最大化)的解决方法,而前向-后向算法(又称之为Baum-Welch算法)就成了隐马尔科夫模型学习问题的一种替代(近似)解决方法。
      我们首先定义两个变量。给定观察序列O及隐马尔科夫模型,定义t时刻位于隐藏状态Si的概率变量为:
            
      回顾一下第二节中关于前向变量at(i)及后向变量Bt(i)的定义,我们可以很容易地将上式用前向、后向变量表示为:
       
      其中分母的作用是确保:
      给定观察序列O及隐马尔科夫模型,定义t时刻位于隐藏状态Si及t+1时刻位于隐藏状态Sj的概率变量为:
        
      该变量在网格中所代表的关系如下图所示:
     
      同样,该变量也可以由前向、后向变量表示:
       
      而上述定义的两个变量间也存在着如下关系:
                
      如果对于时间轴t上的所有相加,我们可以得到一个总和,它可以被解释为从其他隐藏状态访问Si的期望值(网格中的所有时间的期望),或者,如果我们求和时不包括时间轴上的t=T时刻,那么它可以被解释为从隐藏状态Si出发的状态转移期望值。相似地,如果对在时间轴t上求和(从t=1到t=T-1),那么该和可以被解释为从状态Si到状态Sj的状态转移期望值。即:
       
       

      上一节我们定义了两个变量及相应的期望值,本节我们利用这两个变量及其期望值来重新估计隐马尔科夫模型(HMM)的参数,A及B:

     

      如果我们定义当前的HMM模型为,那么可以利用该模型计算上面三个式子的右端;我们再定义重新估计的HMM模型为,那么上面三个式子的左端就是重估的HMM模型参数。Baum及他的同事在70年代证明了因此如果我们迭代地的计算上面三个式子,由此不断地重新估计HMM的参数,那么在多次迭代后可以得到的HMM模型的一个最大似然估计。不过需要注意的是,前向-后向算法所得的这个结果(最大似然估计)是一个局部最优解。
      关于前向-后向算法和EM算法的具体关系的解释,大家可以参考HMM经典论文《A tutorial on Hidden Markov Models and selected applications in speech recognition》,这里就不详述了。下面我们给出UMDHMM中的前向-后向算法示例,这个算法比较复杂,这里只取主函数,其中所引用的函数大家如果感兴趣的话可以自行参考UMDHMM。

    前向-后向算法程序示例如下(在baum.c中):

    void BaumWelch(HMM *phmm, int T, int *O, double **alpha, double **beta, double **gamma, int *pniter, double *plogprobinit, double *plogprobfinal)
    {
      int i, j, k;
      int t, l = 0;

      double logprobf, logprobb, threshold;
      double numeratorA, denominatorA;
      double numeratorB, denominatorB;

      double ***xi, *scale;
      double delta, deltaprev, logprobprev;

      deltaprev = 10e-70;

      xi = AllocXi(T, phmm->N);
      scale = dvector(1, T);

      ForwardWithScale(phmm, T, O, alpha, scale, &logprobf);
      *plogprobinit = logprobf; /* log P(O |intial model) */
      BackwardWithScale(phmm, T, O, beta, scale, &logprobb);
      ComputeGamma(phmm, T, alpha, beta, gamma);
      ComputeXi(phmm, T, O, alpha, beta, xi);
      logprobprev = logprobf;

      do
      {

        /* reestimate frequency of state i in time t=1 */
        for (i = 1; i <= phmm->N; i++)
          phmm->pi[i] = .001 + .999*gamma[1][i];

        /* reestimate transition matrix and symbol prob in
            each state */
        for (i = 1; i <= phmm->N; i++)
        {
          denominatorA = 0.0;
          for (t = 1; t <= T - 1; t++)
            denominatorA += gamma[t][i];

          for (j = 1; j <= phmm->N; j++)
          {
            numeratorA = 0.0;
            for (t = 1; t <= T - 1; t++)
              numeratorA += xi[t][i][j];
            phmm->A[i][j] = .001 +
                     .999*numeratorA/denominatorA;
          }

          denominatorB = denominatorA + gamma[T][i];
          for (k = 1; k <= phmm->M; k++)
          {
            numeratorB = 0.0;
            for (t = 1; t <= T; t++)
            {
              if (O[t] == k)
                numeratorB += gamma[t][i];
            }

            phmm->B[i][k] = .001 +
                     .999*numeratorB/denominatorB;
          }
        }

        ForwardWithScale(phmm, T, O, alpha, scale, &logprobf);
        BackwardWithScale(phmm, T, O, beta, scale, &logprobb);
        ComputeGamma(phmm, T, alpha, beta, gamma);
        ComputeXi(phmm, T, O, alpha, beta, xi);

        /* compute difference between log probability of
          two iterations */
        delta = logprobf - logprobprev;
        logprobprev = logprobf;
        l++;

      }
      while (delta > DELTA); /* if log probability does not
                  change much, exit */

      *pniter = l;
      *plogprobfinal = logprobf; /* log P(O|estimated model) */
      FreeXi(xi, T, phmm->N);
      free_dvector(scale, 1, T);
    }

      
      前向-后向算法就到此为止了。

    八、总结(Summary)

      通常,模式并不是单独的出现,而是作为时间序列中的一个部分——这个过程有时候可以被辅助用来对它们进行识别。在基于时间的进程中,通常都会使用一些假设——一个最常用的假设是进程的状态只依赖于前面N个状态——这样我们就有了一个N阶马尔科夫模型。最简单的例子是N = 1。
      存在很多例子,在这些例子中进程的状态(模式)是不能够被直接观察的,但是可以非直接地,或者概率地被观察为模式的另外一种集合——这样我们就可以定义一类隐马尔科夫模型——这些模型已被证明在当前许多研究领域,尤其是语音识别领域具有非常大的价值。
      在实际的过程中这些模型提出了三个问题都可以得到立即有效的解决,分别是:
      * 评估:对于一个给定的隐马尔科夫模型其生成一个给定的观察序列的概率是多少。前向算法可以有效的解决此问题。
      * 解码:什么样的隐藏(底层)状态序列最有可能生成一个给定的观察序列。维特比算法可以有效的解决此问题。
      * 学习:对于一个给定的观察序列样本,什么样的模型最可能生成该序列——也就是说,该模型的参数是什么。这个问题可以通过使用前向-后向算法解决。
      隐马尔科夫模型(HMM)在分析实际系统中已被证明有很大的价值;它们通常的缺点是过于简化的假设,这与马尔可夫假设相关——即一个状态只依赖于前一个状态,并且这种依赖关系是独立于时间之外的(与时间无关)。
      关于隐马尔科夫模型的完整论述,可参阅:
      L R Rabiner and B H Juang, `An introduction to HMMs’, iEEE ASSP Magazine, 3, 4-16.

      全文完!

      后记:这个翻译系列终于可以告一段落了,从6月2日起至今,历史四个多月,期间断断续续翻译并夹杂些自己个人的理解,希望这个系列对于HMM的学习者能有些用处,我个人也就很满足了。接下来,我会结合HMM在自然语言处理中的一些典型应用,譬如词性标注、中文分词等,从实践的角度讲讲自己的理解,欢迎大家继续关注52nlp。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    HMM在自然语言处理中的应用一:词性标注1

      词性标注(Part-of-Speech tagging 或 POS tagging)是指对于句子中的每个词都指派一个合适的词性,也就是要确定每个词是名词、动词、形容词或其他词性的过程,又称词类标注或者简称标注。词性标注是自然语言处理中的一项基础任务,在语音识别、信息检索及自然语言处理的许多领域都发挥着重要的作用。因此,在关于自然语言处理的书籍中,都会将词性标注单列一章重点讲解,对此有兴趣的读者可参考《自然语言处理综论》第一版第8章或《统计自然语言处理基础》第10章,本文部分内容也参考自这两本自然语言处理的经典书籍。
      我们以Brown语料库中的句子为例,词性标注的任务指的是,对于输入句子:
     The Fulton County Grand Jury said Friday an investigation of Atlanta’s recent primary election produced “ no evidence ” that any irregularities took place .
      需要为句子中的每个单词标上一个合适的词性标记,既输出含有词性标记句子:
     The/at-tl Fulton/np-tl County/nn-tl Grand/jj-tl Jury/nn-tl said/vbd Friday/nr an/at investigation/nn of/in Atlanta’s/np$ recent/jj primary/jj election/nn produced/vbn “/“ no/at evidence/nn ”/” that/cs any/dti irregularities/nns took/vbd place/nn ./.
      在进行词性标注时,前提条件之一便是选择什么样的标记集?Brown语料库标记集有87个,而英语中其他标记集多数是从Brown语料库中的标记集发展而来的,如最常用的Penn Treebank标记集,包含45个标记,是小标记集。汉语标记集中常用的有北大《人民日报》语料库词性标记集、计算所汉语词性标记集等。
      关于Brwon语料库标记集的详细信息可参考:    
       http://www.comp.leeds.ac.uk/amalgam/tagsets/brown.html
      关于计算所汉语词性标记集的详细信息可参考:
       http://www.ictclas.org/ictclas_docs_003.html
      在确定使用某个标记集之后,下一步便是如何进行词性标注了!如果每个单词仅仅对应一个词性标记,那么词性标注就非常容易了。但是语言本身的复杂性导致了并非每一个单词只有一个词性标记,而存在一部分单词有多个词性标记可以选择,如book这个单词,既可以是动词(book that flight),也可以是名词(hand me that book),因此,词性标注的关键问题就是消解这样的歧义,也就是对于句子中的每一个单词在一定的上下文中选择恰如其分的标记。
      关于词性标注歧义问题,对Brown语料库进行统计,按歧义程度排列的词型数目(The number of word types in Brown corpus by degree of ambiguity)DeRose(1988)给出了如下的标记歧义表:
      无歧义(Unambiguous)只有1个标记: 35,340
        歧义(Ambiguous) 有2-7个标记: 4,100
                    2个标记:3,764
                    3个标记:264
                    4个标记:61
                    5个标记:12
                    6个标记:2
                    7个标记:1
      可见英语中的大多数单词都是没有歧义的,也就是这些单词只有一个单独的标记。但是,英语中的最常用单词很多都是有歧义的,因此,任何一个词性标注算法的关键归根结底还是如何解决词性标注中的歧义消解问题。
      大多数的标注算法可以归纳为三类:一类是基于规则的标注算法(rule-based tagger),一类是随机标注算法(stochastic tagger),最后一类是混合型的标注算法。基于规则的标注算法一般都包括一个手工制作的歧义消解规则库;随机标注算法一般会使用一个训练语料库来计算在给定的上下文中某一给定单词具有某一给定标记的概率,如基于HMM的标注算法;而混合型标注算法具有上述两种算法的特点,如TBL标注算法。
      关于词性标注的基本问题本节就到此为止了,也算是一点准备工作,下一节我们将进入与HMM相关的词性标注问题的正题!

    HMM在自然语言处理中的应用一:词性标注2

      上一节我们对自然语言处理中词性标注的基本问题进行了描述,从本节开始我们将详细介绍HMM与词性标注的关系以及如何利用HMM进行词性标注。首先回顾一下隐马尔科夫模型(HMM)的定义和三大基本问题,并由此与词性标注的基本问题进行一个对比。
      隐马尔科夫模型(HMM)是什么?说白了,就是一个数学模型,用一堆数学符号和参数表示而已,包括隐藏状态集合、观察符号集合、初始概率向量, 状态转移矩阵A,混淆矩阵B。
      隐马尔科夫模型(HMM)的三大基本问题与解决方案包括:
      1. 对于一个观察序列匹配最可能的系统——评估,使用前向算法(forward algorithm)解决;
      2. 对于已生成的一个观察序列,确定最可能的隐藏状态序列——解码,使用维特比算法(Viterbi algorithm)解决;
      3. 对于已生成的观察序列,决定最可能的模型参数——学习,使用前向-后向算法(forward-backward algorithm)解决。
      回顾完HMM,这里暂且先放下词性标注,瞎扯一下数学建模。
      记得以前在大学里参加数学建模竞赛,本着拿奖的目的,稀里糊涂的就和几个同学一起组队参加,并没有仔细考虑过数学建模的本质到底是什么。反正感觉和平常作数学题不同,数学题都是定义好的,只需给出一个解答就行,而数学建模给的问题都很实际,并没有按数学题的形式出题,不仅要把这个实际问题转化为一个合理的数学问题,还要给出一个解答,由于自己概括问题的能力有限,在数学建模竞赛上也基本毫无建树。
      我在Google上搜索了一下数学建模的定义,有好几种解释,觉得下面这个最符合本质:
      把现实世界中的实际问题加以提炼,抽象为数学模型,求出模型的 解,验证模型的合理性,并用该数学模型所提供的解答来解释现实问题,我们把 数学知识的这一应用过程称为数学建模。
      好了,这就是数学建模,如果把词性标注问题作为一个数学建模的题目来出,该如何作答?套用上面的定义,可以解释为:
      1、对词性标注问题进行提炼:词性标注本质上是一个分类问题,对于句子中的每一个单词W,找到一个合适的词类类别T,也就是词性标记,不过词性标注考虑的是整体标记的好坏,既整个句子的序列标记问题;
      2、抽象为数学模型:对于分类问题,有很多现成的数学模型和框架可以套用,譬如HMM、最大熵模型、条件随机场、SVM等等;
      3、求出模型的解:上述模型和框架一旦可以套用,如何求解就基本确定好了,就像HMM中不仅描述了三大基本问题,并相应的给出了求解方案一样;
      4、验证模型的合理性:就是词性标注的准确率等评测指标了,在自然语言处理中属于必不可少的评测环节;
      5、解释现实问题:如果词性标注的各项指标够好,就可以利用该数学模型构造一个词性标注器来解决某种语言的标注问题了!
      词性标注的数学建模就这样了,自然语言处理中的多数分类问题与此相似。这里讲得是HMM的应用,所以其他模型暂且不表,以后有机会有条件了我们再说。
      如何建立一个与词性标注问题相关联的HMM模型?首先必须确定HMM模型中的隐藏状态和观察符号,也可以说成观察状态,由于我们是根据输入句子输出词性序列,因此可以将词性标记序列作为隐藏状态,而把句子中的单词作为观察符号,那么对于Brown语料库来说,就有87个隐藏状态(标记集)和将近4万多个观察符号(词型)。
      确定了隐藏状态和观察符号,我们就可以根据训练语料库的性质来学习HMM的各项参数了。如果训练语料已经做好了标注,那么学习这个HMM模型的问题就比较简单,只需要计数就可以完成HMM各个模型参数的统计,如标记间的状态转移概率可以通过如下公式求出:
            P(Ti|Tj) = C(Tj,Tk)/C(Tj)
      而每个状态(标记)随对应的符号(单词)的发射概率可由下式求出:
            P(Wm|Tj) = C(Wm,Tj)/C(Tj)
      其中符号C代表的是其括号内因子在语料库中的计数。
      如果训练语料库没有标注,那么HMM的第三大基本问题“学习”就可以派上用处了,通过一些辅助资源,如词典等,利用前向-后向算法也可以学习一个HMM模型,不过这个模型比之有标注语料库训练出来的模型要差一些。
      总之,我们已经训练了一个与语料库对应的HMM词性标注模型,那么如何利用这个模型来解决词性标注问题呢?当然是采用维特比算法解码了, HMM模型第二大基本问题就是专门来解决这个问题的。
      说完了如何建模,下一节我们将利用UMDHMM这个HMM工具包来实现一个toy版本的HMM词性标注器。

    HMM在自然语言处理中的应用一:词性标注3

      原计划这一节讲解如何利用UMDHMM这个HMM工具包来实现一个toy版本的HMM词性标注器,自己也写了几个相关的小脚本,不过由于处理过程中需要借用Philip Resnik教授写的另外几个小脚本,所以这里先介绍一下他的工作。
      Resnik教授利用UMDHMM写了一个关于如何使用隐马尔科夫模型的介绍和练习,主要目标包括以下四个方面:
      1、 在一个“似英语”的文本上训练一个HMM模型(Train an HMM on a sample of English-like text );
      2、对于训练好的模型进行检测(Inspect the resulting model);
      3、根据训练好的模型随机生成句子(Generate sentences at random from the model);
      4、对于测试句子寻找最可能隐藏状态序列(Create test sentences and find the most likely hidden state sequence)。
      我的工作和Resnik教授的主要区别再于,他的训练集没有进行词性标注,利用了前向-后向算法生成HMM模型,并且需要读者有一定想象能力来作虚拟词性标注;而我所用的训练集是有标注的,主要是通过统计的方法生成HMM模型,并且对于测试集标注是直观的。但是,殊途同归,都是为了建立一个HMM模型,都是为了能利用UMDHMM。
      关于如何下载和使用这个工具包具体请参考“Exercise: Using a Hidden Markov Model”,这里我主要讲解一些要点和一个例子。
      下载这个工具包主要是在命令行方式下利用ftp命令,估计有的读者不太习惯这种方式,所以我在网络硬盘上上传了一个已下载的版本,有需要的读者可以去这个地址下载: solaris.tar.gz
      首先对这个工具包解压:tar -zxvf solaris.tar.gz
      主要包括几个perl脚本,UMDHMM编译后生成的几个可执行程序以及两个样例文件夹,需要注意的是,几个perl脚本需要根据你所使用的环境修改perl的执行路径,另外UMDHMM的几个可执行程序是Resnik教授在Solaris 5.5系统下编译的,并不适用于其他操作系统,因此最好自己单独编译一下UMDHMM,关于UMDHMM的编译和使用,不太清楚的读者可以先看一下我之前写得一点介绍:UMDHMM
      对于这几个perl脚本,需要作一点预处理,第一使其可执行:chmod u+x *.pl 或 chmod 755 *.pl;第二,修改每个脚本的perl解释器目录,由于我用的是ubuntu9.04,所以处理的方法是,注释掉第一行,将第二行”/usr/local/bin/perl5“修改为“/usr/bin/perl”。
      修改完毕perl脚本使其可执行之后,就可以进入example0目录进行练习了:cd example0
      example0目录下有一个example0.train文件,只有一行,但是包含了一百句英语句子,这一百句英语句子只用了11个单词和两个标点符号”.”和“?”生成,是一个“似英语”句子生成器生成的,主目录下有这个程序,是lisp程序写的,我不明白怎么使用。如下所示部分句子:

    the plane can fly . the typical plane can see the plane . a typical fly can see . who might see ? the large can might see a can . the can can destroy a large can …

      对于这个训练集,Resnik教授建议读者写一个简单的词性列表,并尝试为每一个单词分配一个词性标记,并且同一个单词可以有不同的标记。注意这个练习并不是要在这个文件中进行,可以在别的地方,譬如纸上或者心里都可以,不做也行的。我就偷懒了,因为不知道如何标记,并且手工标记的工作量较大,我用了一个基于Brown语料库训练的词性标注器标注了一下,这个之后再详细说明。
      由于UMDHMM这个工具包处理的都是数字而非符号,所以需要先将这个训练集转换为数字序列,由create_key.pl这个脚本完成:
      ../create_key.pl example0.key < example0.train > example0.seq
      这一步生成两个文件:example0.key及example0.seq,前者主要将训练集中出现的单词符号映射为数字编号,如:

    1 the
    2 plane
    8 a
    4 fly
    3 can
    7 see
    12 large
    11 ?
    10 might
    9 who
    6 typical
    5 .
    13 destroy

      后者主要根据example0.key中的编号对训练集进行转换,并且形式为UMDHH中的训练集输入形式,如:

    T= 590
    1 2 3 4 5 1 6 2 3 7 1 2 5 8 6 4 3 7 5 9 10 7 11 1 12 3 10 7 8 3 5 1 3 3 13 8 12 3 5 9 10 7 11 9 10 4 11 9 3 4 11 1 3 10 7 5 1 2 3 4 8 6 4 5 9 3 4 11 1 12 4 3 4 5 9 3 7 11 9 3 7 8 3 11…

      其中T代表了训练集中的单词符号数目。
      今晚有点晚了,先到此为止吧,明晚继续!

    HMM在自然语言处理中的应用一:词性标注4

    分类 标注自然语言处理隐马尔科夫模型 

      在继续昨晚的工作之前,先聊两句Philip Resnik教授。作为美国马里兰大学的教授,他的主要研究领域是自然语言处理,不过最近他被美国某个网站评为“当代卫生保健领域最具创新性和最有影响力的百位革新者之一(the most creative and influential innovators working in healthcare today)” ,Resnik教授也非常吃惊(Much to my surprise),之所以入选,再于他利用自然语言处理来提高医用编码(medical coding)的水平,具体什么是医用编码我不太清楚,不过这项工作至少说明自然语言处理还是有相当的应用前景的。
      另外,05年的时候,他的一个学生开发了一套统计机器翻译系统,取名为“Hiero”,在当年NIST机器翻译评测中表现出色,虽然没有拿到第一,但是其提出的“层次短语模型”的论文获得了当年ACL的最佳论文,此人名叫David Chiang ,中文名蒋伟。
      一年之前有一段时间我对Web平行语料库自动采集比较感兴趣,就找了很多这方面的paper,其中最有名的当属Resnik教授的Strand和LDC的BITS了,只是当时没有仔细考虑过他是何方神圣。今天仔细读了一下他的个人主页,觉得他在自然语言处理领域也是一个比较神奇的人物,有兴趣的读者不妨看看他的这个主页,对于扩展研究思路和把握当前的研究动态还是非常有好处的。好了,以下我们转入HMM词性标注的正题。

      在将训练集转换成UMDHMM需要的形式后,就可以利用UMDHMM中编译好的可执行程序esthmm来训练HMM模型了。esthmm的作用是,对于给定的观察符号序列,利用BaumWelch算法(前向-后向算法)学习隐马尔科夫模型HMM。这里采用如下的命令训练HMM模型:
      ../esthmm -N 7 -M 13 example0.seq > example0.hmm
      其中 N指示的隐藏状态数目,这里代表词性标记,这个例子中可以随便选,我选的是7,下一节会用到。注意Resnik教授给出的命令:
      esthmm 6 13 example0.seq > example0.hmm
      是错误的,需要加上”-N”和“-M”。example0.hmm的部分形式如下:

    M= 13
    N= 7
    A:
    0.001002 0.001003 0.001000 0.001000 0.462993 0.001000 0.538002

    B:
    0.001000 0.366300 0.420021 0.215676 0.001000 0.001001 0.001001 0.001000 0.001001 0.001000 0.001000 0.001001 0.001000

    pi:
    0.001000 0.001000 0.001005 0.001000 0.001000 0.999995 0.001000

      抛开这个HMM模型的效果如何,这里不得不感叹前向-后向算法或者EM算法的神奇。当然这里只是一个练习,实际处理中需要加上一些辅助手段,譬如词典之类的,这种无监督的学习是非常有难度的。
      有了这个HMM模型,就可以作些练习了。首先我们利用genseq来随机生成句子:
      ../genseq -T 10 example0.hmm > example0.sen.seq
      其中T指示的是输出序列的长度,如下所示:

    T= 10
    8 12 4 5 9 3 7 5 9 3

      注意 Resink教授给出的命令仍然是错的,上面的输出结果可读性不好,读者可以对照着example0.key将这个句子写出来,不过Resnik教授写了一个ints2words.pl的脚本,帮助我们完成了这件事:
      ../ints2words.pl example0.key < example0.sen.seq > example0.sen
      example0.sen中包含的就是这个HMM模型随机生成的句子:

    a large fly . who can see . who can

      虽然不是一句整句,但是局部还是可读的,注意这两步可以利用管道命令合并在一起:
      ../genseq -T 10 example0.hmm | ../ints2words.pl example0.key
      注意每次的结果并不相同。
      最后一个练习也是最重要的一个:对于一个测试句子寻找其最可能的隐藏状态序列(Finding the Hidden State Sequence for a Test Sentence),对于本文来说,也就是词性序列了。我们使用testvit来完成这个任务,当然,前提是先准备好测试句子。可以根据exampl0.key中的单词和标点自己组织句子,也可以利用上一个练习随机生成一个句子,不过我选择了训练集中的第91句,比较典型:

    the can can destroy the typical fly .

      虽然违背了自然语言处理中实验的训练集与测试集分离的原则,不过考虑到这只是一个练习,另外也是为下一节做个小准备,我们就以此句话为例建立一个文件example0.test.words。不过UMDHMM还是只认数字,所以Resnik教授有为我们写了一个words2seq.pl处理此事:
      ../words2seq.pl example0.key < example0.test.words > example0.test
      example0.test就是UMDHMM可以使用的测试集了,如下所示:

    T= 8
    1 3 3 13 1 6 4 5

      现在就可以使用testvit,这次Resnik教授没有写错:
      ../testvit example0.hmm example0.test
      看到结果了吗?我们得到了一个隐藏状态序列:


    Optimal state sequence:
    T= 8
    6 1 5 2 6 3 1 7

      如果之前你已经建立好了隐藏状态与词性标记的一一映射,那么就可以把它们所对应的词性标记一个一个写出来了!这个词性标注结果是否与你的期望一样?
      如果你还没有建立这个映射,那么就可以好好发挥一下想象力了!无论如何,恭喜你和52nlp一起完成了Philip Resnik教授布置的这个练习。

    HMM在自然语言处理中的应用一:词性标注5

    分类 标注自然语言处理隐马尔科夫模型 

      上一节我们谈完了Resnik教授基于UMDHMM设计的词性标注的练习,不过自始至终,还没有见到一个词性标记的影子。虽然这一过程展示了自然语言处理中EM算法在无监督学习任务中的重要作用,但是这类方法的标注准确性还相对较低,在实际应用中多是那些建立在有词性标注训练集基础上的机器学习算法,如最大熵模型、决策树等,所学习的词性标注器能获得较高的标注准确率。本节我们就以一个标注好的训练集为基础,来学习一个最简单的HMM词性标注器。
      首先就是准备训练集,作为一个练习,52nlp也本着尽量简单的原则,所以这里仍然选用Resnik教授所使用的example0.train,这个训练集虽然包含了一百句“似英语”的句子,但是只有一行,所以我们首先做一个断句处理,不过这些句子只采用了“.”和“?”作为句尾标志,因此断句相对简单。不过实际处理中英文断句问题比较麻烦,也有很多学者这方面做了很多研究工作。这里52nlp写了一个简单的sentsplit.pl脚本来处理这个训练集:
      ./sentsplit.pl example0.train example0.sentences
      example0.sentences就成了每一句为一行的训练集,如下所示:

    the plane can fly .
    the typical plane can see the plane .
    a typical fly can see .
    who might see ?
    the large can might see a can .
    the can can destroy a large can .

      但是,这个训练集只包含纯粹的单词句子,因此需要做一下词性标注,当然人工标注并检查是最好的了,但是我不懂,于是找了一个开源的词性标注工具对这些句子进行了标注,关于这个词性标注器的细节,下一节我会具体介绍,先来看标注后得到的包含词性标记的训练集example0.all,部分示例如下:

    the/at plane/nn can/md fly/vb ./.
    the/at typical/jj plane/nn can/md see/vb the/at plane/nn ./.
    a/at typical/jj fly/nn can/md see/vb ./.
    who/wps might/md see/vb ?/.
    the/at large/jj can/nn might/md see/vb a/at can/nn ./.

      无论什么方法,建立HMM词性标注器的关键就是根据这个训练集来学习一个合适的HMM模型了。我们先来确定HMM模型中的隐藏状态(词性标记)和观察符号(词型),这里只考察能从训练集中观察的到的词性标记和词型,因此上一节用到的create_key.pl这个脚本就可以派上用处了。对于确定训练集中的词型,利用example0.sentences就可以:
      ../create_key.pl words.key < example0.sentences > example0.seq   
      所得到的words.key就包含了训练集中的词型及其数字编号:

    1 the
    2 plane
    8 a
    4 fly
    3 can
    7 see
    12 large
    11 ?
    10 might
    9 who
    6 typical
    5 .
    13 destroy

      注意另一个副产品example0.seq在这一节里并不需要。同样我们也需要利用create_key.pl来确定训练集中的词性标记及其编号,不过这里我们需要先将example0.all中的词性标记序列抽取出来。这里52nlp写了一个简单的脚本extractpos.pl来处理此事:
      ./extractpos.pl example0.all example0.pos
      所得到的example0.pos文件部分示例如下:

    at nn md vb .
    at jj nn md vb at nn .
    at jj nn md vb .
    wps md vb .
    at jj nn md vb at nn .
    at nn md vb at jj nn .

      有了这个文件,就可以再次利用create_key.pl了:
      ../create_key.pl pos.key < example0.pos > example0.posseq
      所得到的pos.key就包含了训练集中的词性标记及其数字编号:

    4 vb
    6 jj
    3 md
    2 nn
    7 wps
    5 .
    1 at

      同样,另一个副产品example0.posseq这里也不需要。
      确定好了该HMM模型中的隐藏状态(词性标记)和观察符号(词型)后,下一步便是要计算HMM模型中其他三个基本要素了,包括初始概率向量, 状态转移矩阵A,混淆矩阵B。
      我们先预处理一下语料库,主要的目标是对一元词性、二元词性及词型与词性的组合进行计数,这里52nlp写了一个脚本pretrain.pl来处理此事:
      ./pretrain.pl example0.all lex ngram
      所得到的lex文件主要是统计词型及其词性标记的组合在训练集中出现的次数:

    typical jj 25
    large jj 22
    might md 42
    fly nn 20
    a at 58
    ? . 57
    plane nn 34
    the at 35
    who wps 57
    can nn 39
    see vb 45
    destroy vb 9
    fly vb 46
    . . 43
    can md 58

      ngram文件主要包含的是一元词性及二元词性在训练集中的出现次数:

    vb 100
    jj 47
    md 100
    nn 93
    wps 57
    . 100
    at 93
    vb . 50
    md vb 100
    vb at 50
    at jj 47
    wps md 57
    nn . 50
    at nn 46
    jj nn 47
    nn md 43

      有了这几个预处理文件,我们就可以训练一个简单的HMM词性标注模型了,这里52nlp写了一个约100行的脚本hmmtrain.pl来处理此事:
      ./hmmtrain.pl words.key pos.key ngram lex example.hmm
      其中前四个是输入(准备)文件,最后一个example.hmm是输出文件,也就是本节的核心目标:一个合适的HMM词性标注模型,我们来简单看一下example.hmm:

    M= 13
    N= 7
    A:
    0.0100 0.4700 0.0100 0.0100 0.0100 0.4800 0.0100

    B:
    0.3396 0.0094 0.0094 0.0094 0.0094 0.0094 0.0094 0.5566 0.0094 0.0094 0.0094 0.0094 0.0094

    pi:
    0.1576 0.1576 0.1695 0.1695 0.1695 0.0797 0.0966

      有兴趣的读者,可以对比一下上一节利用BaumWelch算法(前向-后向算法)所学习的HMM词性标注模型example0.hmm。
      关于这个脚本,其中对于状态转移矩阵A,混淆矩阵B的计算采用了最简单的加一平滑来处理那些在训练集中的未出现事件,关于加一平滑,不清楚读者可以在“MIT自然语言处理第三讲:概率语言模型(第四部分)” 中找到参考,或者任何一本自然语言处理书中关于ngram语言模型的章节都会介绍的。
      现在我们就可以作上一节最后一个词性标注的练习了,仍然选择训练集中的第91句:

    the can can destroy the typical fly .

      可以利用Resnik教授的words2seq.pl来对此句进行转换,或者利用上一节已经处理好的UMDHMM可读的example0.test

    T= 8
    1 3 3 13 1 6 4 5

      现在就可以使用testvit及刚刚训练好的example.hmm来作词性标注了:
      ../testvit example.hmm example0.test
      同样得到了一个隐藏状态序列:


    Optimal state sequence:
    T= 8
    1 2 3 4 1 6 2 5

      不过这次我们已经有了词性标记序列及其数字编号,可以对应着把它们写出来:

    at nn md vb at jj nn .

      与测试句子合在一起即是:

    the/at can/nn can/md destroy/vb the/at typical/jj fly/nn ./.

      对照example.all里的第91句:

    the/at can/nn can/md destroy/vb the/at typical/jj fly/nn ./.

      二者是一样的,不过这个绝不能说明此HMM词性标注器是100%正确的。
      好了,本节就到此为止了,这一节的相关例子及小脚本可以单独按链接下载,也可以打包在这里供下载:52nlpexample.zip
      不过这套小工具还不足以处理实际问题中的词性标注问题,下一节我将介绍一个更加健壮的HMM词性标注开源工具。

    HMM在自然语言处理中的应用一:词性标注6

    分类 标注自然语言处理隐马尔科夫模型 

      有一段时间没有谈HMM和词性标注了,今天我们继续这个系列的最后一个部分:介绍一个开源的HMM词性标注工具并且利用Brown语料库构造一个英文词性标注器。
      上一节借用umdhmm构造的HMM词性标注工具是二元语法(bigram)标注器,因为我们只考虑了前一个词性标记和当前词性标记,算的上是最基本的马尔科夫模型标注器。这个HMM词性标注器可以通过好几种方式进行扩展,一种方式就是考虑更多的上下文,不只考虑前面一个词性标记,而是考虑前面两个词性标记,这样的标注器称之为三元语法(trigram)标注器,是非常经典的一种词性标注方法,在《自然语言处理综论》及《统计自然语言处理基础》中被拿来介绍。
      正因为经典, 所以老外已经做足了功课,包括paper以及开源工具,我查了一下,其中比较有名的一个是TnT,作者既写了一篇“TnT — Statistical Part-of-Speech Tagging”,被引用869次,又开发了一套开源工具(http://www.coli.uni-saarland.de/~thorsten/tnt/),可谓“知行合一”。但是要获得这个工具必须填一个表,并且传真给对方,比较麻烦。不过幸好在英文维基百科关于词性标注的介绍页面上有替代品:Part-of-speech_tagging.
      在这个页面的“External links(外部链接)”的最后一行,提到了一个名叫Citar的利用C++开发的隐马尔科夫模型(HMM)三元语法词性标注器:
      “Citar LGPL C++ Hidden Markov Model trigram POS tagger, a Java port named Jitar is also available”
      同时,它也提供Java版本的Jitar。不过可惜,这个页面目前无法直接访问到。如果读者对这个词性标注工具感兴趣的话,这里提供一个Citar的下载链接: citar-0.0.2.zip
      以下是citar的简要介绍:
      Citar is a simple part-of-speech tagger, based on a trigram Hidden Markov Model (HMM). It (partly) implements the ideas set forth in [1]. Citaris written in C++. There is also a Java/JDK counterpart named Jitar,
    which is available at: http://code.google.com/p/jitar/
      其中[1]指的是“TnT — Statistical Part-of-Speech Tagging”,其具体的实现思想在这篇文章里描述的很细致,我觉得主要需要注意的几个地方是trigram的平滑算法,未登录词的处理方法(主要是针对英文的),以及柱搜索(beam search)解码算法。
      编译citar直接make就可以了,生成三个可执行文件:train,tag,evaluate。顾名思义,“train”是用来一些必要的文件的,tag则是进行标注的,而evaluate则是用来评价标注结果的。下面我们以Brown语料库为例来演示如何使用这三个可执行程序。
      关于Brown语料库,我是从NLTK的包中得到的,NLTK提供了两个版本的语料库,一个是纯文本格式,另外一个是XML格式,都进行了词性标注,如果你对NLTK不熟悉,可以从下面两个链接直接下载这两个语料库:
      1、XML格式的brown语料库,带词性标注;
      2、普通文本格式的brown语料库,带词性标注;
      至于Brown语料库的具体介绍,大家可以参考这个页面:BROWN CORPUS MANUAL。在这个练习中,我采用的是纯文本格式的brown语料库,但是这些文件是按照类别分布在很多小文件里,并且包含很多空行,所以我处理了一下这个文件,把它们合并到一个大的文件中,并且去除了行首的空格及空行,共得到57340个带词性标注的句子(brown.corpus)。我们首先对这个语料库进行划分,从中选取前55340句作为训练集(brown.train),选取剩余的2000句作为测试集(brown.test),现在我们就来运行这三个命令。
      首先利用train来训练:
      ../train brown.train brown.lex brown.ngram
      其中输入文件是训练集brown.train,而输出文件是brown.lex及brown.ngram,如果大家还记着上一节里我也生成了两个前处理文件lex和ngram的话,那么就不难理解这两个文件的内容含义了。事实上,我当时就是模仿citar的这个预处理思想做得,只是结果文件里的格式稍有不同而已。
      有了上述两个文件,就可以利用tag进行词性标注了,我们拿citar里的一个示例句子来实验一下:
      echo “The cat is on the mat .” | ../tag brown.lex brown.ngram
      得到如下的结果:
      The/at cat/nn is/bez on/in the/at mat/nn ./.
      如果对一个没有标注的文件进行标注,可以利用如下的命令:
      ../tag brown.lex brown.ngram < input > output
      最后,我利用evaluate来验证一下基于brown.train训练出来的词性标注器的准确率,在测试集brown.test上进行测试:
      ../evaluate brown.lex brown.ngram brown.test
      得到如下的结果:
      Accuracy (known): 0.964621
      Accuracy (unknown): 0.740937
      Accuracy (overall): 0.956389
      说明这个词性标注器对于语料库中已存在的词的标注准确率是96.46%,对于未登录词的标注准确率是74.09%,而整体标注准确虑是95.63%。
      好了,关于Citar我们就到此为止,有兴趣的读者可以找一些标注好的语料库来试试,包括中文的词性标注语料库,只不过它用于英文的未登录词处理方法对于中文并不合适而已。上面所提到的几个文件,包括处理好的brown.corpus,训练集brown.train,测试集brown.test及中间生成的brown.lex,brown.ngram我已经打包放在了网络硬盘里,可以在如下地址下载:browntest.zip
      关于HMM在词性标注中的应用就说完了,再次回头说词性标注时,我会基于其他的模型来作相关的词性标注练习。下一个关于HMM在自然语言处理的应用,将会谈谈中文分词的相关问题,欢迎继续关注52nlp

    更多相关内容
  • 不少零售企业的整体性数据收集平台还不是很完善,数据提取、整合、可视化、探索分析等程度相对落后,定期的商业报告也还是通过抓取数据仓库里面的数据,组成固定格式的报表并用于简单分析。即便有的企业构建了自己...

    传统零售困局

    过去 20 年,零售业与零售消费者发生了翻天覆地的变化。随着电商等行业新星的加入,传统零售商及大型零售连锁的客流、销售、利润,普遍呈下跌趋势。虽然有房租、人员成本上涨、商品同质化等一系列原因,但从零售业发展来看,卖方市场向买方市场的转变,导致传统零售以线下门店为主导、商品为中心模式的瓶颈逐渐显现,这也迫使传统零售商纷纷向以顾客为中心的经营模式转变,并更加重视强化品类管理、优化供应链。

    零售行业演变趋势

    虽然改变模式成为 2017 年零售商们的口号,但更多的还是停留在方案层面,没有真正落实与执行。在变革到来前,摆在传统零售商面前的问题不是要不要变,而是变什么和怎么变。

    变什么?新零售的三大关键 —— 人 + 货 + 场

    “人”—— 传统零售经营以采销为主,模式简单粗放,对消费者关注不足。在新供需关系下,回归消费者这一零售生态本源成为核心。零售商要挖掘数据价值,如基于目标客群的特征和行踪数据,整合品牌商数据,掌握不同消费客户群的行为变化和趋势。

    “货”—— 零售业的发展客观原因造成零售供应链长期深度依赖供应商,采购团队把关注点放在了来自于供应商的收费和返点,而不是去经营及提升门店坪效和毛利率,从而导致零售行业在品类管理和供应链专业能力培养上长期滞后,并形成了地方管理团队各自为营的分散式管理局面。但在新环境下,零售商想打破在与供应商谈判和合作中被动局面,需要统一管理的业绩考核分析体系整合供应链能力。

    “场”—— 过去传统零售以线下门店为主导,现在是线上线下融合打通,多个场景融合;过去是以地理位置为中心的商业,现在是场景化的、以人为中心的商业。零售全渠道发展是大势所趋,通过大数据分析能力提升精细化运营,对目标客群消费场景洞察、通过品类运作配合创新促销活动等。

    零售大数据分析面临的问题

    零售企业内部的信息孤岛现象严重:不同业务部门,如运营、商品、生产、财务等内部均有大量数据产生,但是各部门各自为政,不愿公开自己数据库的真实数据,因而数据的获取和深度分析难度较大,而通过建立统一的数据平台有望打破这种信息孤岛格局,继而让数据因分享产生价值。

    业务部门主要任务是自己部门日常的经营,包括操作流程、考核指标、门店管理、人员管理等,对于大数据能给自己带来什么样的价值则认知程度不高,同时加上对于数据分析的技术和工具知之甚少,因此很难把自己对于本职业务的理解和数据分析做紧密的结合,转化为数据分析所需要的种种要求,导致 IT 团队、大数据团队很难深入理解业务,而最终 IT 部门交付的成果对业务带来的价值有限,而业务又提不出具体需求。

    不少零售企业的整体性数据收集平台还不是很完善,数据提取、整合、可视化、探索分析等程度相对落后,定期的商业报告也还是通过抓取数据仓库里面的数据,组成固定格式的报表并用于简单分析。即便有的企业构建了自己的数据平台,虽然能够实时地呈现业务分析报告,也只是停留在报表的提供层面。

    如何变?大数据分析助力新零售破局

    永洪科技从自主研发的敏捷 BI 产品入手,一步步横向扩展,陆续推出了高性能计算引擎、自服务数据准备、深度分析等重要模块,构成了一站式数据分析平台。多年沉淀打磨平台的结果,客户都能以极低的门槛、极高的易用性、端到端完整的数据处理能力快速构建属于自己的数据应用。积累至今,永洪科技基于众多零售项目实践进行提炼,与客户、合作伙伴一起打造了数据应用体系以及零售数据分析解决方案。

    零售数据应用体系

    • 用户分析

    用户分析主要是对顾客群体的购买行为的分析。通过对用户的年龄、人均购买次数、购买总金额、消费地区、购买途径等数据进行分析,深入洞察消费者购物行为和偏好,寻找到高价值客户的消费规律,通过精准营销,提升销售额。

    • 门店分析

    实体门店仰赖营业额作为绩效指标,但对于营业额不佳的门店,却难以有更进一层的数字观察与改进。通过数据分析,进行门店客流追踪,不仅能根据日、周、月、年等做周期分析、跨门店分析,还能结合 POS 营业额等销售数据做整合分析,进而预测下一周期的营运绩效,助力零售商达到经营效益最大化。

    • 商品分析

    商品分析的主要数据来自销售数据和商品基础数据。主要分析商品的类别结构、品牌结构、价格结构、毛利结构、结算方式结构、产地结构等,从而产生商品广度、商品深度、商品淘汰率、商品引进率、商品置换率、重点商品、畅销商品、滞销商品、季节商品等多种指标。通过对这些指标的分析来指导企业商品结构的调整,加强所营商品的竞争能力和合理配置。

    • 渠道分析

    如果企业同时运营着线上渠道和线下渠道,需要对加盟商的情况做到心里有数,因为库存和缺货是所有的零售迫切解决的一个难题。如果零售商对用户的需求没有进行精准的预测,则会导致供需失衡,造成高库存或者是缺货状态,最终将造成成本上的浪费。通过对渠道数据进行分析,可以对供需平衡进行高效管控,降低成本。

    WHO—— 购买和使用产品的消费者是谁、WHEN—— 在什么时候购买、WHERE—— 在什么地方购买、WHAT—— 消费者购买了什么产品、WHY—— 消费者为什么购买,助力新零售企业管理者深度洞察顾客消费偏好,提高营销决策水平,实现销售额的高速增长。

    展开全文
  • 生鲜产品具有鲜活易腐、不耐贮运、生产季节性强、消费弹性系数小等特点。随着生鲜供应链的逐渐成熟,生鲜产品的运输季节、地域限制被克服。本次疫情期间生鲜电商的爆发将生鲜供应链行业推向了关注的焦点,优秀的生鲜...

    市场分析

    生鲜供应链市场特点:

    中国生鲜供应链以环节多、链条长、耗损大为特征。农业作为供应链最源头的供给端,提供了肉禽、蔬菜、水果、海鲜等生鲜产品。生鲜产品具有鲜活易腐、不耐贮运、生产季节性强、消费弹性系数小等特点。随着生鲜供应链的逐渐成熟,生鲜产品的运输季节、地域限制被克服。本次疫情期间生鲜电商的爆发将生鲜供应链行业推向了关注的焦点,优秀的生鲜供应链能力成为制约生鲜新零售成功发展的决定性因素。生鲜供应链行业从幕后走向台前,深耕行业的龙头企业将迎来新历史发展机遇。

    生鲜供应链发展趋势与机会:

    尽管生鲜电商模式上线以来屡遭质疑,但必须承认电子商务对于生鲜农产品流通效率提升、交易成本优化、供应链品控管理优化等多个环节有着巨大的价值。生鲜农产品不同于其他商品交易,安全品质的鉴别困难导致了生鲜农产品的渠道优势在线下,而生鲜电商正在寻求电商模式背后的包括供应链管理、标准化认证、交易金融以及生鲜品牌建设等一系列延伸价值,通过重塑用户心中的生鲜信用标准来改变价值链。

    产品价值

    1、信息化管理;2、人工智能;3、全渠道电子商务集成;4、大数据决策;5、智能硬件集成。

    四大核心价值解决

    生鲜供应链难题:

    1、信息化管理:人工接单容易漏单错单、手抄账单对账不清、人工统计繁琐易错、结算周期长坏账多、采购猫腻难以避免

    2、全渠道电子商务集成:经销商订货渠道、线上零售商城、直销微分销渠道、O2O渠道、门店渠道、社区团购。经销商订货渠道

    3、大数据决策:数据中台、数据大屏、智能运营、用户画像

    4、人工智能:智能采购分析、智能商品类别推荐、智能分拣、智能车辆路径规划、智能价格管理、智能语音下单。

      数字化供应链系统架构图

    5

        产品服务

    智能采购-spo云

    商城采购、电商采购、寻源采购、采购协调、采购大脑、采购中台,让企业采购交易更高效

    立足采购方的企业互联网采购服务平台,构建全球供应商网络资源库,帮助企业实现全球采购,源头采购,地标农产品采购,构建数字商业新生态。

    TSM 溯源管理

    农产品生产、加工、运输、销售等全流程追溯

    平台以DNA追溯、区块链追溯、直播追溯、溯源码追溯、环境监测追溯、智能售货冷柜为核心技术,集成了线上电商平台(小程序、APP) ,后台追溯系统(PC后台),运营端管理平台(农场小程序、溯源信息采集员小程序、团长小程序),线下智能售货冷柜管理系统为体的溯源电商和线下营销平台。

    CRM 客户管理

    客户全场景全生命周期管理,客户线索尽在掌握。

    提供以SCRM+营销自动化为核心的企业一站式营销云解决方案,帮助企业打造业绩导向型营销闭环,助力业绩增长。

    BMS 结算管理

    支持所有业务计费,多种账单分析,应收应付快速结算

    制定应收应付规范,统计客户货主应收账单,实现成本应付对账及利润可视化。系统基于物流行业繁冗的计费模式,针对物流仓储各环节的计费复杂性与重复性,制定多种计费模型的仓储、物流及采购费用计算等。

    SCF 供应链金融

    以数字化供应链SaaS云平台为载体提供供应链金融服务

    以生鲜产业供应链为基础,以SaaS云平台为载体,将银行、信贷、基金等金融服务引入实体产业,同时为金融机构开放供应链场景。

    供应链大数据平台

    农业大数据云平台,数据分析与挖掘,数据可视化

    涉及到耕地、播种、施肥、杀虫、收割、存储、育种等各环节,特别是农产品供应链大数据,进行数据分析与数据大屏显示,实时监控。

    现代农业产业园云平台

    现代农业产业园的信息化建设,优化调整产业体系

    搭建高度现代化、信息化农业产业园体系,农业种养殖、数字化供应链、精准化生产、农业电子商务、农业生态化五大服务应用。

    OMS 订单管理

    全渠道订单管理,全链路管理状态展现

    帮助客户处理从订单接收到费用结算的全生命周期管理。通过订单中心汇集内、外部多渠道订单,OMS云与WMS云、TMS云无缝集成,实现订单全流程可视化,集中管理与执行监控,确保订单准确、准时交付。

    WMS 仓储管理

    提供多仓库、多货主、多客户的仓储管理方案

    提供精细化的库内作业管理、业务流程高可配置性、增值业务服务及自动计费引擎、运营可视化分析。全面提升仓库作业效率、提高库存准确率、提升库存周转效率、全方位追踪员工作业绩效。

    TMS 运输管理

    配送过程全程可视化,移动化应用,操作更便捷

    基于订单模型、订单任务管理,基于装载任务的车辆司机管理,配送过程全程实时可视化、交接高效便捷,全面兼容Android、IOS系统的司机APP。

    RSM 冷库管理

    轻松搞定出入库管理,简化流程,提升效率

    主要面向工厂冷库,第三方冷库和配送冷库,从装箱清单、清关报检、入库、库内分选、货权转移、出库、配送等业务管理,手持车载辅助库内作业,自动计费对账结算。

    SHS 智能硬件

    强大的软件功能结合智能化的硬件设备

    PC智能电子秤(称)、POS收银机、RFID溯源标签、物流机器人、智能导示设备、手持设备、电子展示屏。

    CKM 中央厨房

    一站式解决中央厨房食材生产加工、财务、税务管理难题

    自动生成加工计划,基于BOM单自动汇总采购任务,记录加工信息,任务流转更高效,加工食材全程追溯。

    OPC 开放应用

    云应用市场,整合数字供应链和第三方伙伴的云产品和插件

    客户可以在开放应用中按需在财务、税务、溯源、物流、发票、社交、仓库等诸多方面选择购买适用的云产品和插件。

    展开全文
  • 点击上方,选择星标或置顶,不定期资源大放送!阅读大概需要11分钟Follow小博主,每天更新前沿干货仅学术分享,如有侵权,联系删除转载于 :中南云麓谷、中南大学交通运输工程学院、CSU交...

    点击上方,选择星标置顶,不定期资源大放送

    阅读大概需要11分钟

    Follow小博主,每天更新前沿干货

    仅做学术分享,如有侵权,联系删除

    转载于 :中南云麓谷、中南大学交通运输工程学院、CSU交通院研究生会

    站在中国高速铁路发展的风口,每项科研成果的发现与实现,都有可能助力国家交通事业更进一步。而每一项科研成果背后,都离不开矢志科研的专家学者以及即将担起交通事业发展的青年人的努力。而我们今天的主人公郭子健,正是这批青年人之一。

    郭子健,交通运输工程学院载运工具运用工程专业2018级全日制博士研究生,中南大学与伯明翰大学联合培养博士生,研究方向——列车空气动力学。

    今年26岁的郭子健,已经共发表SCI论文21篇,总被引123次。其中,他以第一作者或通讯作者身份发表SCI一区论文8篇,三区1篇,四区1篇,有6篇发表在中科院一区期刊上;参与国家自然科学基金、国家十三五重大专项及其他纵横向科研项目近十项,多次参与国内外学术会议;荣获国家奖学金、茅以升铁路教育专项奖学金、中南大学优秀学生标兵等荣誉。

    “年少有为”,郭子健是对于这四个字的高度诠释。而他荣誉的背后是什么?达成如此成就有何秘籍?让我们一起走近郭子健,听听他的故事。

    文章担使命

    2018年十一月份,郭子健成功发表了自己的第一篇SCI。他记得很清楚,其主题是探讨重联列车引起的列车风问题。作为开启研究生新阶段的第一个重磅课题,郭子健对其看得很重,每天都全身心扑在论文上面。而在这一个亟待解决的问题上,这篇论文算是发表最早的一个。

    第一篇SCI论文的成功,给郭子健带来了巨大的满足感和成就感。而作为一名交通人,肩负着要推动我国的交通事业发展的使命,其光荣感与责任感成为郭子健坚持发表论文的源动力

    “一年内以第一作者的身份发表四篇SCI一区论文”,这听起来,好像是不可能完成的事,而郭子健做到了。四篇论文,方向迥异,时限一年,任务艰巨。对此,郭子健说:“我习惯于就写完一个再写另一个,没有刻意去规划每一篇要用多久去写,只能在时间上只争朝夕。”

    而在发表SCI论文的撰写过程中,郭子健印象最深而且难度最大的一篇正是这四篇论文之中的最后一篇——关于用隧道中的列车风来产生可再生电能的新型理念研究。“我们传统的话只做列车空气动力学,但是这一篇涉及到其他的学科领域,对我来说就完全是陌生的。”交叉学科,范围广,难度大。为此,郭子健只能靠自己一点一点摸索,从基础开始,查阅论文,请教老师,逐渐理清了自己的思路。

    从一个新手小白出发,在摸索之中慢慢进步,如今在发表多篇论文之后,郭子健给我们分享到:

    1.论文最重要的就是要知道写论文的主题是什么,所以说第一步就要广读文献来确定主题;之后,根据主题精读相关文献;有了一些文献经验之后,理清这篇文章的结论是想阐述哪些方面的内容,列出相应提纲。很重要的一个部分是列图表,需要明白用哪些图和表来论证主题。完成上述步骤之后,论文的骨架便搭建完毕,然后再深层次的补充一些内容和分析就基本能完成论文工作。

    2.科研工作中必然离不开阅读文献,一个初学者很可能面临着“看不太懂”、“不知道从何下手”等问题,对此,郭子健的心得体会是:文献阅读我们应该首先从自己身边人的文章出发,比如师兄师姐的文章,仔细研读并虚心请教,这样可以快速弄明白课题组的研究方法和流程,随后就可以去阅读其他优秀的论文专著,拓展自己对学科掌握的宽度。“这个过程一定不能急功近利,不能一味地去完成数量目标,而是要根据自己对文献的吸收情况来弹性地改变阅读进度,自己学到的才算是自己。”

    3.看论文的过程很枯燥而且看完容易忘,请问师兄在阅读文献方面有什么实用的整理方法?

    这个当然要推荐文献管理软件了,我自己在用的有Notexpress和Endnote。首先是笔记功能,在阅读每一篇文献的时候,最重要的是把我们的收获和疑问记录下来,然后有时间就去回顾,温习收获并尝试解决疑问。另外我很喜欢的一个功能就是标签,这个可以帮助我们快速索引某类文献,对我这个专业来说,我会加一些比如“横风”、“列车风”和“头型”等等这样的标签给对应的文献,在后续需要回顾或引用的时候就可以马上找到,对我来说是很实用的。在查阅大量文献的过程中,做好阅读笔记是十分必要且关键的环节,需要记录文章的重点研究内容和框架。

    4.无论在哪一个领域,创新性都是科研工作中至关重要且最难以实现的一点。写论文,依旧如此。“从模仿开始,融百家所长,最后走出一条自己的路。”对于论文创作,郭子健依旧建议稳步进行。通过模仿优秀论文中写作的长处,来不断完善自己的文章,熟能生巧之后就会形成自己独特的写作风格。

    因此,我们在文献阅读过程中读到值得学习的地方就应该及时做好笔记,同时对读过的文献进行标签分类,回顾时,通过标签可以迅速找到自己的目标,也有助于形成自己的体系。而这背后,潜藏着艰难的文献管理工作。在此,郭子健分享他平时经常使用的文献管理工具,如Endnote、Notexpress等。

    以笔筑梦,循序渐进,21篇SCI论文成为郭子健硕博之路的一个见证,而这一路程仍在继续,等待着郭子健未来新的精彩。

    参天科研树

    “三,二,一,发射!”火箭升空的刹那间,强大的民族自豪感拨动着郭子健的心弦。从那时起,年少的他便暗暗对自己许诺:“未来,我也要成为科研人中的一员。”

    “推动研究领域方面前进一小步,用自己的力量为社会做出改变,这就是我科研的初心。”郭子健不负少年时的许诺,踏上科研路。肩负起研博生做好科研的责任,砥砺自己作为科研人的初心,郭子健用的是一个个精准的数据与一篇篇严谨的论文。科研路上,风雨兼程,对他而言,唯责任与热爱不被辜负。

    师者,所以传道受业解惑也。谈到他的导师刘堂红时,他分享到:“导师很严谨,也很严肃,在这些科研人格方面,我学到了很多。”有次做仿真分析,郭子健做出了他以为很不错的数据,刘老师却说:“这些数据看起来不错了,但是肯定还有更多的深层关系,你再做做看。”这种对待科研一丝不苟的态度深深地影响到了郭子健,也为他未来的科研之路奠定下了沉稳严谨的性格基础。

    “老师也不是一直都很严肃的,上次我们一起去新疆,老师真的是一改风范……”那时去新疆做科研,他生病了,刘老师则对他细心地关怀照顾,像一个贴心的朋友陪伴在他身旁。也许,这就是亦师亦友。

    黎明之前总是最黑暗的,这样才能显出日出的耀眼。郭子健的科研之旅并非一帆风顺。2018年,他已在英国交流学习,同时也要跟进一个十三五专项研究,他的任务是设计一个高速列车气动外形的优化平台。对于这个未接触过的领域,他的科研工作都是从零开始:学编程,做接口,一个按钮一个按钮去完善平台的功能……遇到棘手的学术难题,网上没有答案,他只能将说明书“翻烂”,进行头脑风暴去想新点子。

    测试中的bug一次次挫败着他尝试与坚持的勇气,那时他已然落入困境,却因时差而无人可以倾诉。“那时,我想过甩手给别人,自己一走了之。但是,我知道,这是不负责任的表现,我无法允许自己这样做。”郭子健选择咬牙坚持到底,柳暗花明最是风景,他做到了。项目顺利结题,得到验收专家的好评,这也许就是为那完全靠自己敲出来的4100多行代码画上的最美的句号。

    用比他人更多一点的努力换来自己满意的成绩,这叫做劳有所得。躬身实践,恶劣的天气无法阻挡奔赴科研的心,这叫做身体力行。提出新领域时的质疑,不会吵到自己宁静的三分田,这叫做大胆创新。

    郭子健,他仍是那个看到火箭升空会心潮澎湃的少年,未来的科研路上,他会不负少年时的许诺,向着科研奔赴山海。如今,种子已破土,正参天。

    坎坷留学路

    每一次努力,都是幸运的伏笔。郭子健的公派留学之路并非一帆风顺,出国资格申请期间需要自己提前联系好国外院校的指导老师,但郭子健在联系校外导师时遇到了些许阻力。“我给列车空气动力学的许多教授发送了邮件,但大部分都石沉大海,收到的回复不多……”郭子健回忆到。值得庆幸的是,郭子健终于在公派留学申请的截止时间前几日收到了来自英国伯明翰大学Hassan教授的回信。有惊无险,郭子健翻开了去往伯明翰留学的第一页。

    刚到伯明翰时,长沙还是穿短袖的季节。由于地域气候的改变,郭子健在英国生活的第一周便感冒发烧了,现在郭子健回想起来依旧是苦涩地说:“发烧的那一周太痛苦了!”同时,饮食方面也和国内天差地别,一时间难以完全适应,所以郭子健需要自己动手做饭。这样以来,生活上难免占用比较多的时间。之后,郭子健开始不断调整自己的生活节奏,很快,他便完成了生活模式的切换,成功适应了留学生活的方方面面。

    少年的肩,想要担起草长莺飞和清风明月,郭子健意气风发地说到:“很想能在列车空气动力这个领域做出自己的一点点贡献。”在留学期间,郭子健也是从事列车空气动力学的研究。一直将这一方向坚持下去,只是因为一份纯粹而简单的热爱。被问到想毕业之后的打算时,郭子健表示后希望自己能继续在高校从事这一领域的工作。成为交通人的8年时光,值得郭子健用未来去回馈和感恩。

    在科研生活之外,郭子健也找到了自己生活的亮色。自小就爱打乒乓球的郭子健一直没有放弃这一爱好,踊跃报名参加一些乒乓球比赛,并于2018年6月获得高速中心乒乓球男单季军。“当时非常开心,抱着试一试的态度去参赛没想到收获了意外之喜。”郭子健如是说。

    发光不是太阳的权利,而是每个人都可以。郭子健在周围同龄人的眼中像舞台上一位闪闪发光的主角,但其实每个人都可以找到属于自己的那一束追光。“在本科阶段就要尽早确定好自己的职业规划,清晰自己未来的发展定位是什么,有时候会带来事半功倍的惊喜。”

    郭子健一直在追光,在为梦想拼搏的路上,每个人都会遇到属于自己的点点星光,最终与光同航。与交通事业齐发展,砥砺自身成长,收获一路荣光。高歌猛进,玉汝于成,与呼啸而过的中国高铁一般,郭子健必然笃行致远,不负韶华。

    
    
    展开全文
  • 例如,销售规划要反映季节性需求,而生产规划要考虑均衡生产。在不同的销售环境下,生产规划的侧重点也不同。对现货生产(MTS)类型的产品,生产规划在确定月产率,要考虑已有库存量。如果要提高成品库存资金周转...
  • 格力美的,谁代表中国制造的未来

    千次阅读 2018-11-21 17:01:27
    空调作为一个家电品类,与其他家电最大的区别就是其销售的季节性,每年的旺季与淡季差别明显,旺季一般为夏季四个月左右,销量会瞬间爆发,而淡季销量要低得多。针对这一特点,格力是业内率先要求经销商“压货”的...
  • 最容易理解的HMM文章

    2016-01-06 16:08:00
     在这些代码中,start_probability代表了Alice对于Bob第一次给她打电话的天气情况的不确定(Alice知道的只是那个地方平均起来下雨多些).在这里,这个特定的概率分布并非平衡的,平衡概率应该接近(在给定变迁概率的...
  • mahout 实战

    千次阅读 2015-06-08 15:05:49
    需要特别指出的在于,不同的数据准确不同,粒度也不同,在使用需要考虑到噪音所带来的影响。 5.2.2 相似度计算   这一步其实就是计算用户间以及物品间的相似度。以下是几种计算相似度的方法: ...
  • 史上最详细最容易理解的HMM文章 .

    千次阅读 2015-12-04 14:08:59
     在这些代码中,start_probability代表了Alice对于Bob第一次给她打电话的天气情况的不确定(Alice知道的只是那个地方平均起来下雨多些).在这里,这个特定的概率分布并非平衡的,平衡概率应该接近(在给定变迁概率的...
  • HMM(隐马尔科夫模型)

    千次阅读 2014-09-10 22:28:45
     在这些代码中,start_probability代表了Alice对于Bob第一次给她打电话的天气情况的不确定(Alice知道的只是那个地方平均起来下雨多些).在这里,这个特定的概率分布并非平衡的,平衡概率应该接近(在给定变迁概率的...
  • hmm

    千次阅读 2014-09-08 14:18:37
     在这些代码中,start_probability代表了Alice对于Bob第一次给她打电话的天气情况的不确定(Alice知道的只是那个地方平均起来下雨多些).在这里,这个特定的概率分布并非平衡的,平衡概率应该接近(在给定变迁概率的...
  • 曼昆经济学原理_第五版[1].txt.doc

    万次阅读 2013-03-23 20:00:48
    知道经济学研究稀缺资源配置 考察人们面临的一些交替关系 知道机会成本的含义 懂得在作出决策如何运用边际推理 讨论激励如何影响人们的行为 考虑为什么人们或国家之间的交易可以使各方面受益 ...
  • 新东方四六级写作

    万次阅读 2013-03-25 09:44:21
     它可以在任何场合下进行,在淋浴,在工作 ,在厨房里或拖拉机上。 它既包括在学校所受的正规教育,也包括一切非正规教育。 传 授知识的人可以是德高望重的老者,可以是收音机里进行政治辩论的人们,可以是...
  • 于丹论语心得

    2012-03-26 11:37:00
    灰色也只有在和其他色彩搭配,才能显示它的普适,显示它的高贵和纯粹。因此,灰色的孔子又必须链接多彩的世界。这世界充满生命活力,姹紫嫣红,千姿百态,万类霜天竞自由。正因为有了生活的五彩缤纷,理论的灰色...
  • 当和尚遇到钻石

    万次阅读 2009-04-07 11:11:00
    然而有一天,师父对一群大学生了一场特别有启发的开示,我听完之后,便告诉他我愿意依照他的指示找份商业界的工作。     在那之前几年,我曾经在某次日常打坐中,有过某种灵视( vision )。从那时起,我就...
  • ERP系统的计划层次

    千次阅读 2005-07-01 10:32:00
    例如,销售规划要反映季节性需求,而生产规划要考虑均衡生产。在不同的销售环境下,生产规划的侧重点也不同。对现货生产(MTS)类型的产品,生产规划在确定月产率,要考虑已有库存量。如果要提高成品库存资金周转...

空空如也

空空如也

1 2
收藏数 30
精华内容 12
关键字:

做季节性调整时 数据有为0的怎么办

友情链接: qtPcreator.zip