您的位置:首页 > 编程语言 > C#

基于C#的序列分类器:Part I:隐马尔科夫模型

2013-07-18 22:42 253 查看
原文地址:http://www.codeproject.com/Articles/541428/Sequence-Classifiers-in-Csharp-Part-I-Hidden-Marko

By César
de Souza, 11 Mar 201

序列分类器系列:

基于C#的序列分类器:Part I:隐马尔科夫模型

基于C#的序列分类器:Part II:隐条件随机场模型

1.介绍

假设你有一堆有序的事件。任何一种序列都可以,例如你最喜欢的球队这赛季得分的序列。或者通过你的手指画出的一系列运动的有序的点(x,y)。或者有序的字母集合形成的一个单词集合。如果有一种方法可以形成这写序列岂不是很好?或许,你可以精心构造一些可以模拟这些序列的模型,或者他们模拟出的结果和你想得到的序列差不多,隐马模型可以到来就是为了这个。

可以肯定的是,我们操作一个事物的模型比操作一个事物本身要容易的多。我们用类图来做这些事情,举个例子,隐马模型是序列模型。也因为这样,他们允许轻松的处理这些序列。如果你有他们的一些模型,假设他们遵循一定的规则,那么你可以做一些关于它们的猜想。接下来你可以把你认为遵循相同模式的序列放在一个组里,依次进行分类。那么这个工作就是通过隐马模型进行最基本的序列分类工作。

这篇文章重点给读着呈现的是Accord.NET Framework是如何工作的;展示一下序列分类器在框架中的哪个位置,简单叙述一下他们的代码。文中还会介绍马尔科夫名字空间是怎么组织,在这个组织的支撑下是如何产生结果的。也会稍微提一下隐条件随机场,这才是我写这个系列的主要目的。

2.隐马尔科夫模型

想象有一个黑盒。起初,你展示一个可以观察到的序列,然后告诉这个黑盒:找出其中的规律,学习这个序列。之后,你在这个盒子的输入端插入任意一个可以观察的序列,然后得到一个与其他序列相似度差异的标尺,作为输出结果。



评估一个序列的概率:许多方法中的一个使用到了隐马模型

如果您仅仅是想使用隐马模型来学习它的内部构造,那就可以直接去看代码那部分。你可能是非常迫切使用隐马模型做什么项目,或者是马上测试什么程序,这些都没问题。不过,抽空还是到这里看看详细的构造。

隐马模型是一个概率模型,像其他模型一样,隐马模型试图通过一种简洁的、易控的方法去模拟接近一些东西(或者是这种东西所表现的的行为),并且这种方法很容易使用和掌握。相比较去处理一个真实的物体,处理它的近似模型会更容易一些。作为概率模型中的一种,隐马模型试图捕获一个事物所表现的概率情况,而不是注重这个事物有什么样的规则。

马尔科夫模型也特别适合随着时间推移而定义的模型。这个模型假定这些行为包含固定数量的内部状态。例如,想一下“pinch-and-zoom”这个手势(就是苹果手机中,两个手指往外阔,图片就会放大的效果)。这个手势有几个状态:第一,两个手指触摸屏幕;第二,两个手指相离运动。一个人可以很容易的想到这两点就是两个状态,这两点缺一不可的组合成为一个pinch-and-zoom的手势。

这些模型有一个原因也被称为隐形的。我们没有必要告诉它上面所提及的所有状态应该是什么样。我只需要告诉它我们认为应该有多少个状态,并且我们需要什么结果。这个模型应该可以计算出一个状态序列,并且这个结果可以支持我们的观点。

成为马尔科夫(Being MarKov)意味着这个模型具有马尔科夫的一些属性:它假设,在任何一个序列中,当前所观察到的序列仅仅依赖于最接近的一个序列。假设有两个观测到的序列x=<x1,x2,...,xt>以及一个和它一一对应的y=<y1,y2,...yt>。现在,不要在数学面前太羞涩,我们可以把它想象成二维空间点序列,或者是三位空间的点序列,再或者是位图中的点序列。这些状态序列也仅仅是由一些整形的标签组成的,例如<1,2,4>或者<5,3,6,8>。这些状态标签,我们可以假设它们是由模型生成的。马尔科夫模型的特性就是小而整洁的思想并且在所有的科学领域都有深远的影响,从语言识别到DNA匹配,以及其他所有以序列分析作为主要任务的领域。

假设一个马尔科夫概率模型,任何一个序列A在一个给出序列B发生的情况下,A发生的概率可以如下表示:



p(yt|yt-1) 表示在yt-1发生的前提下,yt 发生的概率,p(xt|yt) 表示在yt 发生的情况下xt 发生的概率。为了计算这些概率,我们简单假设两个矩阵A和B。A矩阵表示的是状态的概率:它给出的p表示从一个状态跳转到另一个状态的概率,而B矩阵表示的是观测概率矩阵,使这个分布式密度p(xt|yt) 相关于一个给定状态
yt

。在离散的状态下,B确实是一个矩阵。在连续的状态下,B是一个向量的概率分布。整个模型定义可以用以下元组表示



n表示系统中一共有多少状态,A表示转换概率的矩阵,B表示的是观测概率矩阵(离散状态下)或者向量的概率分布(连续状态下),Pi表示的是初始状态的向量,它的值由模型中每一种可能的初始状态的概率决定。下图是Accord.NET Framework中实现离散和连续的隐马模型的接口函数

IHiddenMarkovModel :




注意到,我们有初始概率向量p,状态总数n和状态转换矩阵A作为接口函数的成员变量。就像我之前提到的,矩阵B可以是一个矩阵也可以是一个向量概率分布。这样,我们的IHiddenMarkovModel接口函数有两个实现:一个是HiddenMarkovModel和通用的HiddenMarkovModel<TDistribution>:




规范化问题

我们注意到,非通用的接口实现类(即HiddenMarkovModel)中参数Emissions为2维的double类型,而在通用类型接口(即HiddenMarkovModel<TDisrtibute>)的实现中是一个向量的概率分布Emissions。Accord.NET框架包含了30多种不同的概率分布,包括单变量、多变量的离散或者连续的情况。

很多问题我们都可以用隐马模型来解决。然而,我们做的最经典的问题就是可以直接解决一些隐马模型称之为规范化的问题。这些就是,1.评价一个序列的概率问题;2.找到一个与原始序列最相近的序列状态;3.用已给出的大量训练数据训练一个马尔科夫模型。

前两个问题通过使用上述的IHiddenMarkov接口中的Decode和Evaluate方法可以实现。最后一个问题,学习问题,是一个单独的机制,因为它有多种解决办法。

评估一个序列的概率

我们通过以上的例子已经知道如何计算一个联合概率,例如一个的观测到的x序列且受y序列状态影响。然而就像我们之前提到的,这个y序列我们并不知道它具体的值。这个序列y,对于我们来说是隐藏的,或者在模型中我们把它设定为隐藏的。所以我们避免了关于y的任意一种假设情况而是为了算出x的概率考虑y的所有情况。换句话说,通过计算y的所有可能概率来对y进行边缘化处理:



如果现在有3个可能的状态(1,2,3),长度为4的观察状态序列,我们可以假设
(1,1,1,1)
(1,1,1,2)
(1,1,1,3)
(1,1,2,1)...(指数级别的清单)...
(3,3,3,3)
很明显,列出所有的情况是很麻烦的,即使是很短的序列或者是很小的数字。很幸运,已经有人针对这个问题研究出一个解决方案,winform下的向前算法(Forward algorithm),这个算法允许我们在精确的线性时间内计算出例如p(x)这样的函数。相当不错!同样的,向前算法在许多其他算法模型中都是关键的一部分,例如Baum Welch estimation算法等这样马尔科夫模型之外的算法。
向前算法计算一个状态概率矩阵,用来评估在一个序列下任一时刻每一个状态的概率。通过连接所有概率,在最后的时刻,我们考虑所有可能的状态转换的组合,可以获得全部发生的序列。(向后算法基本上是一样的,反向的计算概率,而评价算法适用于二者)。



在Accord.NET 框架下,向前(或者向后)概率矩阵或者日志概率矩阵都可以通过
Accord.Statistics.Models.Markov
namespace
.
名字空间下的的
ForwardBackwardAlgorithm
static
class 类中的任意一个静态方法来无缝的进行计算。

下面让我们来创建一个隐马模型,通过使用Accord.NET来计算一个随机整数序列的概率值。首先,我们应该引入统计学和数学的名字空间,相应的引入他们的组件。代码如下:

// Create a hidden Markov model with random parameter probabilities
HiddenMarkovModel hmm = new HiddenMarkovModel(states: 3, symbols: 2);
 
// Create an observation sequence of up to 2 symbols (0 or 1)
int[] observationSequence = new[] { 0, 1, 1, 0, 0, 1, 1, 1 };
 
// Evaluate its log-likelihood. Result is -5.5451774444795623
double logLikelihood = hmm.Evaluate(observationSequence);
 
// Convert to a likelihood: 0.0039062500000
double likelihood = Math.Exp(logLikelihood);


我们也可以创建一个连续的常规的分布式离散密度的模型,如下:

// Create a hidden Markov model with equal Normal state densities
NormalDistribution density = new NormalDistribution(mean: 0, stdDev: 1);
 
HiddenMarkovModel<normaldistribution> hmm = 
    new HiddenMarkovModel<normaldistribution>(states: 3, emissions: density);
 
// Create an observation sequence of univariate sequences
double[] observationSequence = new[] { 0.1, 1.4, 1.2, 0.7, 0.1, 1.6 };
 
// Evaluate its log-likelihood. Result is -8.748631199228
double logLikelihood = hmm.Evaluate(observationSequence);
 
// Convert to a likelihood: 0.00015867837561
double likelihood = Math.Exp(logLikelihood);


评价算法的实现就是调用向前算法中的静态方法。因为我们的模型没有经过任何训练所以有一个小的似然值(likelihood)。默认情况下,框架的初始化默认所有的概率都是相等的。

解码序列

在某些情况下,我们似乎更感兴趣于那些被跟随的潜在的状态序列,因为他们生成了可观测的观察序列。为了解决这个问题,我们使用维特比(Viterbi)算法:是安德鲁维特比发明的,在噪声通信信道中用于错误纠正的算法。它应用比较广泛,举个例子,DVD播放器中用于错误纠正的模块。

这个算法就是根据观察到的序列来找到最可能的序列,而且最优的序列也就是我们常说的维特比路径,在每一步中都是选取最大概率而形成的路径。因此在隐马模型中,它通常被称为最大和算法(向前算法,由于它是联合了所有的特性,所以它也被称为和积(sum-product)算法)。

我们来接吗一个相同的序列。然而,这次我们人工的定义模型中显式声明的矩阵A、B以及概率向量p。

// Create a model with given probabilities
HiddenMarkovModel hmm = new HiddenMarkovModel(
    transitions: new[,] // matrix A
    {
        { 0.25, 0.25, 0.00 },
        { 0.33, 0.33, 0.33 },
        { 0.90, 0.10, 0.00 },
    },
    emissions: new [,] // matrix B
    {
        { 0.1, 0.1, 0.8 },
        { 0.6, 0.2, 0.2 },
        { 0.9, 0.1, 0.0 },
    },
    initial: new[]  // vector pi
    { 
        0.25, 0.25, 0.0 
    });
 
// Create an observation sequence of up to 2 symbols (0 or 1)
int[] observationSequence = new[] { 0, 1, 1, 0, 0, 1, 1, 1 };
 
// Decode the sequence: the path will be 1-1-1-1-2-0-1
int[] stateSequence = hmm.Decode(observationSequence);


这个例子中的值没有太大的意义。然而,最终得到的一个序列1-1-1-1-2-0-1 ,意思是模型开始状态是1,然后接连3个1,然后转到状态2,又转到状态0,最后回到1。经过仔细的研究我们发现,这个可以用来进行段序列的分类,并且有效的执行段的分割。

假设,我们举个例子,如果我们的观察序列是由一个声音(utterance)序列组成。解码算法可以为每一个声音附加一个整数型的标签。随着工作的进展,我们可以把那些解码成音素的且带有统一便签的声音(utterance)凝聚在一起且极度与口语中的单词相近。

从序列中学习

到现在为止,我们既可以创建有随机参数的模型,也可以人工定义我们想要的参数。但是大多数情况下,我们真正想要的是从一些信息源那里自动学习我们的模型。

这里,我们有很多种办法。

首先,我们要决定我们是否需要使用一个有监督或者无监督的学习算法。一个有监督的学习算法,在隐马模型的上下文中,在训练过程中,期待隐状态和观测的状态是相联系的。而无监督的学习算法就不需要。



看上图可知,有监督和无监督的学习。注意到,有监督的学习需要一个与观测序列相关联的维特比路径,而无监督的学习则只需要一个观测矩阵。

朴素最大似然估计就是一个有监督学习算法,而 Baum-Welch algorithm 和维比特算法则属于无监督学习算法的一种(维比特算法的工作机理有点类似于一个半无监督学习)。以下这个图片向我们展示了Accord.NET框架中一些关于学习的类。我们注意到,在对于简单的离散状态提高效率的情况下,这里对于离散模型和通用模型有分开的类。



现在我们来看一个使用Baum-Welch算法来学习一个离散模型和一个等价的连续模型的例子。这些框架是最常见的使用情景:

// Suppose a set of sequences with something in
// common. From the example below, it is natural
// to consider those all start with zero, growing
// sequentially until reaching the maximum symbol 3

int[][] inputSequences =
{
    new[] { 0, 1, 2, 3 },
    new[] { 0, 0, 0, 1, 1, 2, 2, 3, 3 },
    new[] { 0, 0, 1, 2, 2, 2, 3, 3 },
    new[] { 0, 1, 2, 3, 3, 3, 3 },
};
 

// Now we create a hidden Markov model with arbitrary probabilities
HiddenMarkovModel hmm = new HiddenMarkovModel(states: 4, symbols: 4);
 
// Create a Baum-Welch learning algorithm to teach it
BaumWelchLearning teacher = new BaumWelchLearning(hmm);
 
// and call its Run method to start learning
double error = teacher.Run(inputSequences);
            
// Let's now check the probability of some sequences:
double prob1 = Math.Exp(hmm.Evaluate(new[] { 0, 1, 2, 3 }));       // 0.013294354967987107
double prob2 = Math.Exp(hmm.Evaluate(new[] { 0, 0, 1, 2, 2, 3 })); // 0.002261813011419950
double prob3 = Math.Exp(hmm.Evaluate(new[] { 0, 0, 1, 2, 3, 3 })); // 0.002908045300397080

// Now those obviously violate the form of the training set:
double prob4 = Math.Exp(hmm.Evaluate(new[] { 3, 2, 1, 0 }));       // 0.000000000000000000
double prob5 = Math.Exp(hmm.Evaluate(new[] { 0, 0, 1, 3, 1, 1 })); // 0.000000000113151816


这了有一个有趣的观察。输出值不是准确的概率,而是似然估计值。似然估计不是一个正规化的概率,所以当这些值独自出现或者脱离上下文的时候,则不能告诉我们太多的东西。然而,它可以与其他的似然估计值进行比较。在上面的例子中,这个序列的似然估计值与训练集(至少7个数量级)中的似然估计值要相近的多,而与那些不合适这个模型的序列的似然估计值则要差一些。

在接下来本文的部分将是非常有用的东西------创建序列分类器

状态切换拓扑结构

隐马模型中经常被定义的一个通用特性就是它们的状态装换拓扑图。举个例子,分析时间状态序列的例子---口语单词。一旦我们在一个给定的状态里,不能马上回到之前的那个状态。状态转换只能向前走。



状态转换拓扑图 左:遍历拓扑图 右:前置拓扑图
一个只能向前走的拓扑图叫前置拓扑图。任意一个状态可以到达其他任意一个状态的拓扑图叫做遍历式拓扑图。为了定义这些拓扑图,我们要做的就是设定一个相应的初始转换矩阵:上或左三角形为前置拓扑,所有值都大于零的为遍历拓扑图。但是也有一些自定义的拓扑图,例如band-forward 矩阵和随机或者等概率的初始化。基于这个原因,框架中提供了ITopology 接口,很多具体类型都继承它来实现,所示如下:



使用上述任何一个拓扑规范来初始化一个模型,只需要简单的建立一个 ITopology 类 并且把它传到一个隐马模型的构造函数中。它自己就知道该做什么了!



序列分类

到现在为止,我们已经介绍了什么事隐马模型,它能做什么,怎么样创建它。但是,倘若我们有很多种类的序列,我们想区分它们。举个例子,我们有pinch-and-zoom(放大缩小)手势,滑动手势,以及其他一些种类的手势。就像我们文中之前介绍的,每一种手势可以看做多为空间坐标中的序列点。假设我们希望区分他们:假设一个方法,返回1代表是缩放手势,返回2代表滑动手势,翻悔代表其他手势。为了解决这个问题,我们需要一个序列分类器。

但是我们怎么来创建一个呢?



一序列分配器给一个观察序列分配一个整数标签。就想在IHiddenMarkovClassifier接口中实现的计算方法一样。
让我们回到那个黑盒代表的一个隐马模型。在这表示,我们在一侧给出观察序列,而在另一侧给出它的概率。如果我们想评估每一种序列的模型(缩放手势一个模型,滑动手势一个模型),那么对于每一种被标签标记的手势我们需要三个模型来来给出其似然值。

现在我们回忆起,比较这些似然值:如果我们选择一个高似然值的模型作为一个新手势标签的最佳选择,那么我们需要一个序列分类的方法。更具体的说,我们需要一个最大似然分类器。

然而,观测序列发生所产生的一个类模型,它的似然值不同于给定序列的类的似然值。对于所有的问题,选择最大似然值不一定是最优的,举个例子,类的比例不平衡,或者对于每一个序列我们只训练了很少。这个有解决方案吗?给每一种模型产生的每一种似然值乘以一个因子:添加这个简单技巧的同时,乘以一个类概率的猜测,然而我们有一个贝叶斯最大后向分类器。

在最大似然估决策规则中,我们把序列的似然估计看成是类的给定序列的概率。最大似然决策规则可以表示为:



意思就是“我们选择类ωj 作为序列的评价标签,如果ωj 的结果在方程的最大概率输出中”。每一个模型ωj 给出一个评价概率p(x|ωj),因此我们可以用给出的似然值替换p(x|ωj)作为方程的输出。下图表示的就是这个过程:



这个最大后向决策规则,换句话说,试图选择那些序列给出的有最大概率的类,也是更符合逻辑的。然而,作为我们模型输出结果的个别模型似然值与我们感兴趣的后向概率之间缺少安全的连接桥。贝叶斯公式,尽管公式和理论非常完美,我们经常没有一个可靠的方式评估所需的类概率p(ωj)。贝叶斯公式如下:



这意味着我们可以获得最大后验(MAP)决策规则通过:



这似乎就是一个更合理的目标,假设我们可以指出涉及到的概率的真实评价。一个有趣的事情是,ML公式和MAP公式在本文中是一样的,选择同样的前向概率p(ωj) 换句话说,使用了非信息化的前向概率。

转到上述公式和方法的具体实现,对于离散的和通用的分布模型有不同的类。
HiddenMarkovClassifier
HiddenMarkovClassifier<TDistribution>
都实现于接口
IHiddenMarkovClassifier




如上图所示,他们每一个都有IHiddenMarkovModels向量;HiddenMarkovModels就是离散分类的实例,HiddenMarkovModels<TDistribution>就是通用版本的实例。我们也有通过调整双精度float类型值的向前向量的具体向前概率(这可以被视为只是扩展因素)。

属性的敏感性和阈值用于阈值模型,一种把包含拒绝在这些模型里的方法,但是超出了本文的范围。

为了学习隐马模型,所有我们要做的就是介绍用于训练例子的分类器中的隐马模型内部结构,这些样品就是各自的一个分类标签。这就意味着每个模型都需要用自己的序列进行本地化的训练,没有其他任何的兄弟模型或者其他序列的知识。我们很快就可以看到,这个隐马模型分类器是通用模型,这其中每一个模型都是去评价一个分离的密度模型,并且联合使用了贝叶斯公式。



为了完成这个内部模型的学习,我们可以使用HiddenMarkovClassifierLearning这个类。这些类简单的应用任意一个选择学习算法到内部的模型中,但是允许较大的灵活度。这个学习算法通过使用lambda表达式为每一个模型单独的进行创建、配置以及调整。

// Suppose we would like to learn how to classify the
// following set of sequences among three class labels: 

int[][] inputSequences =
{
    // First class of sequences: starts and
    // ends with zeros, ones in the middle:
    new[] { 0, 1, 1, 1, 0 },        
    new[] { 0, 0, 1, 1, 0, 0 },     
    new[] { 0, 1, 1, 1, 1, 0 },     

    // Second class of sequences: starts with
    // twos and switches to ones until the end.
    new[] { 2, 2, 2, 2, 1, 1, 1, 1, 1 },
    new[] { 2, 2, 1, 2, 1, 1, 1, 1, 1 },
    new[] { 2, 2, 2, 2, 2, 1, 1, 1, 1 },

    // Third class of sequences: can start
    // with any symbols, but ends with three.
    new[] { 0, 0, 1, 1, 3, 3, 3, 3 },
    new[] { 0, 0, 0, 3, 3, 3, 3 },
    new[] { 1, 0, 1, 2, 2, 2, 3, 3 },
    new[] { 1, 1, 2, 3, 3, 3, 3 },
    new[] { 0, 0, 1, 1, 3, 3, 3, 3 },
    new[] { 2, 2, 0, 3, 3, 3, 3 },
    new[] { 1, 0, 1, 2, 3, 3, 3, 3 },
    new[] { 1, 1, 2, 3, 3, 3, 3 },
};

// Now consider their respective class labels
int[] outputLabels =
{
    /* Sequences  1-3 are from class 0: */ 0, 0, 0,
    /* Sequences  4-6 are from class 1: */ 1, 1, 1,
    /* Sequences 7-14 are from class 2: */ 2, 2, 2, 2, 2, 2, 2, 2
};

// We will use a single topology for all inner models, but we 
// could also have explicitled different topologies for each:

ITopology forward = new Forward(states: 3);

// Now we create a hidden Markov classifier with the given topology
HiddenMarkovClassifier classifier = new HiddenMarkovClassifier(classes: 3,
    topology: forward, symbols: 4);

// And create a algorithms to teach each of the inner models
var teacher = new HiddenMarkovClassifierLearning(classifier,

    // We can specify individual training options for each inner model:
    modelIndex => new BaumWelchLearning(classifier.Models[modelIndex])
    {
        Tolerance = 0.001, // iterate until log-likelihood changes less than 0.001
        Iterations = 0     // don't place an upper limit on the number of iterations
    });

// Then let's call its Run method to start learning
double error = teacher.Run(inputSequences, outputLabels);

            
// After training has finished, we can check the 
// output classificaton label for some sequences. 

int y1 = classifier.Compute(new[] { 0, 1, 1, 1, 0 });    // output is y1 = 0
int y2 = classifier.Compute(new[] { 0, 0, 1, 1, 0, 0 }); // output is y1 = 0

int y3 = classifier.Compute(new[] { 2, 2, 2, 2, 1, 1 }); // output is y2 = 1
int y4 = classifier.Compute(new[] { 2, 2, 1, 1 });       // output is y2 = 1

int y5 = classifier.Compute(new[] { 0, 0, 1, 3, 3, 3 }); // output is y3 = 2
int y6 = classifier.Compute(new[] { 2, 0, 2, 2, 3, 3 }); // output is y3 = 2

从上面可以看到,这个分类器可以对我们之前测试的例子进行正确的测试。但是这不意味着它始终能正确的进行分类。

代码的使用

之前已经介绍了一些例子,然而,这里我们演示一个具体的简单应用的例子,使用的是带连续多元高斯(正常)密度的隐马模型分类器,其中使用到了框架。

假设我们想要检测鼠标手势。

在屏幕坐标系中,鼠标手势可以看成是2维点的序列。作为2维点序列,我认为可以使用System.Collection.Generic.List泛型类,其中的T为了System.Drawing.points 类来正确的表示这些序列点,或者使用任何包含Point的实现自IEnumberable接口的类。






上面这两个手势一红色标记为开始端,以蓝色标记为结束端。一旦开始点击鼠标,手势以红色开始以蓝色结尾来绘制。这也只是从时间方向上来查看这个手势。这个手势本身由Point类的Llist泛型类存储。

在把这些点传输到我们的学习算法之前,我们需要把它们转换成double类型数组。每一个点都转换成一个double[ ]{x,y}类型的并且包含x和y。一个完整的手势存储在double[][] 类型的二维数组中。

下一步我们创建我们自己的模型:

hmm = new HiddenMarkovClassifier<MultivariateNormalDistribution>(numberOfClasses,
  new Forward(states), new MultivariateNormalDistribution(2), classes);

这列我们考虑一个固定数量的状态,固定数量为5。一般都是依据一些先验知识或者其他建设模型,但是经常会慢慢调整看看哪种情况最有效。

模型建好后,我们可以创建学习算法来训练分类器。

// Create the learning algorithm for the ensemble classifier
var teacher = new HiddenMarkovClassifierLearning<MultivariateNormalDistribution>(hmm,

   // Train each model using the selected convergence criteria
   i => new BaumWelchLearning<MultivariateNormalDistribution>(hmm.Models[i])
   {
       Tolerance = tolerance,
       Iterations = iterations,
       FittingOptions = new NormalOptions()
       {
          Regularization = 1e-5
       }
   }
);

这些就是我们需要创建识别模型的全部东西。现在,如果你想查看下该例子,请在本文的最上面下载这个桌面应用例子。这个应用程序使用鼠标来识别各种不同的手势。事实上,你也可以用它来做其他事情,比如信号识别。这只是一个简单的例子教你怎么用隐马模型。这个应用程序如下:



桌面程序的主页面
这个桌面程序打开后,你需要创建一个一个画布。点击画布你可以画一个你喜欢的任意手势。手势以红色开始,逐渐变成蓝色。这就给出了一个关于时间定位的手势信息。当你画完手势的时候,通过点击右边栏的文本 标记“what‘s this”来告诉程序你画的是什么。





在画布上你可以随便的画,gridview上显示你画过的东西。
当你注册了几个手势之后,你就可以学习一个分类器。只需要点击“learn a Hidden Markov Model classifier”按钮来完成学习。分类器将会迅速的学习(使用默认的学习参数),然后试图把你创造的那些手势进行分类。绿色标记的手势就是已经分类器成功识别。




学习和识别新的手势,试一下,然后看看它的执行情况!
现在,如果你试着画另一个手势,你会注意到,一旦你点击鼠标松开之后,应用程序会试着给这个手势进行分类,也会问这个答案是否正确。你可以尝试多次,通过添加更多无法识别的手来加强模型的学习。点击“Learn a Hidden Markov Model Classifier”按钮来再次学习。

现在,你会发现有些手势最终还是无法被正确识别。如果你很好奇我们是如何改进分类器的,那么轻看我本系列的下一篇文章“Part II:隐条件随机场模型”。

结论

在本篇文章中,我们探索了什么事隐马模型以及它能做什么。经过一个简要的理论性描述,本文重点展示了如何使用Accord.NET框架创建、学习和使用隐马模型。文中讲到的例子是一个桌面应用程序,它示范了如何使用连续密度,高斯,隐马模型去识别屏幕中的鼠标手势。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: