结合mahout的数据挖掘算法介绍

数据挖掘算法

前言:数据挖掘和机器学习包含了许多的算法,算法的介绍往往是枯燥乏味的。本文中结合mahout和小例子还解释这些算法。因此我们先介绍一下mahout

准备工作:Mahout环境的搭建

初识mahout

Hadoop是为了大数据而生的,在之前的学习中,我们也了解了Mapreduce程序的基本原理。但是,读者对如何将Hadoop应用到大数据还是没有一个清晰地认识。相信读者朋友们了解过数据挖掘的算法,这些算法如何与hadoop做一个无缝的结合,如何提高他们的复用性呢?像R语言和MatlAB以及其他的程序包实现了对数据挖掘算法的封装,用户不需要了解算法的具体实现,只需要将数据导入,调用算法,就能很快的得到结果,大大减轻了数据挖掘人员的负担。那hadoop有没有这样的包呢,封装常见的数据挖掘算法,提供给用户使用的API,让数据分析人员从繁重的代码工作中解脱出来。出于这样的目标,hadoop社区中的大神们就创建了mahout项目,该项目旨在提供数据挖掘的算法包,目前以及包含kmeans SVM等一系列的算法。

大家将自己想想成mahout的设计者,我们希望mahout有什么功能呢?

正确率高,当然这是最基本的要求。再好的算法封装,如果算法的效率不高,也不会被使用。

读取文件的类型足够多,能覆盖常见的数据文件类型。现在mahout能支持的数据类型包括,简单文本、attrCVS文件。这些文件在数据挖掘中非常常见。

函数的使用简单。提供的接口便于理解和记忆。

完善的开发文档。文档的好用程度直接影响到开发者的使用感。

对硬件的要求不高。目前mahout的软件环境是JDKmaven。硬件环境,搭建hadoop的环境即可。

调用语言的接口不止一种。Hadoop的系列项目都是基于java开发的,对于不熟悉Java的开发人员也希望能通过C++python等语言调用mahoutAPI

能根据自己的需求修改算法达到算法的最优。

能提交源码到社区。作为开发人员最令人振奋的莫过于分享自己的代码,mahout是开源的,程序员在社区中碰撞出思想的火花。

由于mahout是以应用为导向的,因此上面的建设目标有一部分就是mahout的特点。Mahout是开源的提供数据挖掘算法的算法包,它是不断发展着的。它的繁荣需要广大的开发人员贡献自己的力量。

我们吹嘘了这么多mahout的好处,你肯定迫不及待的想要mahout中大显身手了。现在我们就再自己的机子上安装mahout来试一试吧。

安装mahout

下载二进制包解压安装。

  现在mahout的官网提供了二进制包和源码包。这里我们选择二进制包直接解压。当然你可以选择mahout的源码包自己编译。由于mahout项目是一个不断发展者的项目,下载最新的源码同样会对您有很大的帮助。在本书编写时,最新的版本是0.9.

  Tar -zxvf   mahout-distribution-0.9.tar.gz

 为了以后配置环境变量的方便,我们将解压以后的文件夹重命名为mahout。当然你也可以不这样修改。

 

2,配置环境变量

  打开/etc/profile文件,输入下面三行内容。

  Export MAHOUT_HOME=/home/hadoop/hadoop/mahout

  Export CLASSPATH=:$CLASSPATH:$MAHOUT_HOME/lib

  Export PATH=.:$PATH:$MAHOUT_HOME/bin

  保存文件,并使其生效。

  Source /etc/profile

3,检验是否安装成功

  其实到这里mahout的安装已经完毕。我们想要检验一下是否安装成功,需要在hadoop环境下运行一个示例。

  首先,启动hadoop集群。

  Bin/start-all.sh

  使用jps查看hadoop集群是否启动成功。

  然后,输入mahout --help看是否列出了mahout的算法。如何显示算法则表明mahout安装成功。

运行一个mahout的例子

mahout包中有很多例子,其中一个是使用kmaens来对synthetic_control.data

这个数据集进行分类。

1)数据准备

下载测试数据。这个数据是由600X60double型的数据组成, 意思是有600个元组,每个元组是一个时间序列。   http://archive.ics.uci.edu/ml/databases/synthetic_control/synthetic_control.data

将它放到本地文件夹中,这里我放在了mahout_home下。

2)启动hadoop集群并将文件上传。

如何启动集群就不在赘述。将文件上传到/user/hadoop/testdata目录下。主要必须是这个目录,因此在mahout中这个目录已经写死了。首先创建testdata

Hadoop fs -mkdir testdata

Hadoop fs -put /home/hadoop/hadoop/mahout/synthetic_control.data testdata

Hadoop fs -lsr 查看文件是否上传成功。

3)运行程序

输入下面命令:Bin/hadoop jar /home/hadoop/hadoop/mahout/mahout-examples-0.9-job.jar org.apache.mahout.clustering.syntheticcontrol.kmeans.Job

将会运行kmeans程序,这个过程可能需要几分钟。在运行过程中留心观察会发现,这是个mapreduce程序。

4)查看结果

 运行完毕后,查看输出文件

 Hadoop fs -lsr output

这时会出现很多文件,这些文件就是分类结果集。也就说明测试程序成功。这些文件的含义是聚类的结果。

用maven构建mahout

在上一个章节中,我们安装了mahout并运行了一个mahout的k-means例子。在实际应用中,我们常常需要改动mahout的源码来适应自己的需求,因此构建mahout项目是必要的。对于现在的数据挖掘人员来说,不仅仅要了解算法,还要能将算法转化成程序,因此一些常用的开发工具也是需要掌握的,例如maven和ant,make等。玩转hadoop,要会配置hadoop集群、写java程序、使用linux,这对大部分程序员来说还是极富挑战性的。不过毕竟其乐无穷嘛,我们就笑着面对喽。

maven的简介和安装

1、maven简介

Maven是apache的一个子项目,网站是http://maven.apache.org/。在基于java的项目开发中往往需要引入各种各样的jar包,而且这些包有不同的版本号,不同的包和版本可能造成包的冲突。在实际开发中,jar包得引入和冲突问题是困扰程序员的一大难题。Maven是这么一种工具,希望程序员在构建java项目中更简单,对版本的控制也更容易。

现在的apache maven是源于jakarta子项目的java项目管理及自动构建工具。使用maven能快速得对项目编译、打包、发布、测试等。在maven中一个常用的功能是管理版本的一致性。

Maven的代码包在windows和linux中是一样的。

2、Maven在windows中的安装

    安装maven前,首先确认已经安装了JDK。注意JDK和maven的版本要匹配。具体参见官方文档。

下载maven:网址是:http://maven.apache.org/download.cgi,选择文件apache-maven-3.2.1-bin.zip。当然你也可以选择源码自己编译。

解压到本地:笔者这里解压到D:\tool\maven

添加maven的环境变量:

M2_HOME=D:\tool\maven

PATH:%M2_HOME%\bin

打开命令行模式检验是否安装成功:打开cmd,输入mvn -version 查看maven的版本号。出现如下信息,表明maven安装成功。

 

在eclipse中配置maven的插件

在eclipse的window->preferences->maven 设置maven的settings.xml文件的位置。设置完成后就能创建maven项目了。

 

3、Maven在linux中的安装

   下载文件:apache-maven-3.2.1.tar.gz

解压到本地:这里,我将maven解压到/usr/local/apache-maven/apache-maven-3.2.1

配置环境变量:在/etc/profile中配置M2_HOME,PATH

M2_HOME=/usr/local/apache-maven/apache-maven-3.2.1

PATH=$M2_HOME/bin:$PATH

打开命令行模式是否安装成功:打开终端,输入mvn -version,如果显示maven的版本信息表明安装成功。

在linux的eclipse中设置maven插件:和在windows中配置maven插件是一样的。

在eclipse中编译mahout源码

1、下载mahout源码,解压到网站是mahout.apache.org

2、导入到eclipse中,选择import->existing maven project,选择解压的目录。

3、导入后显示如下的画面

 

4、可以安装普通maven项目对其编译、测试、打包、发布。

注意:这一步经常出现的问题。导入的项目pom.xml文件报错。右击mahout项目选择maven->update project ,将强制update 的选项打钩,错误就将消失。

报这个错的时候:Resource Path Location Type Plugin execution not covered by lifecycle configuration: org,右击错误选择,选择quick fix。在新打开的对话框中选择Permanently mark goal generate in pom.xml as ignored in Eclipse build点击finish。再重新update一下项目即可。

在命令行下编译mahout源码

使用maven构建mahout项目

1、windows平台下构建maven项目

 1.1用maven创建一个标准化的java项目

 1.2导入项目到eclipse中

 1.3增加mahout依赖,修改pom.xml文件

 1.4下载依赖

2、Linux平台下构建maven项目

 

检验是否构建成功

 

 

BPNN

但凡是学过计算机和数学的人对神经网络这个名词就不会陌生。但是大部分人在相当长的时间内对神经网络的印象都停留在貌似和神经元有关的阶段。人的大脑如何发达,有上亿个神经元细胞,每时每刻都进行这大量的信息交换,但是就是这样我们还是不能理解按照我们大脑神经网络提出的人工神经网络算法。对于笔者也是,在过去的几年里,神经网络在我的脑海里就是一盆浆糊,我从没有过醍醐灌顶豁然开朗的感觉。“哎啊,原来是这样的!”的声音是我不止一次的期盼。

神经网络是一个典型的并行化的系统,它仿造人脑系统中神经元的工作系统来实现计算机中分类的计算。所以,神经网络最早是由心理学家提出来的。最早的有关神经网络的模型是有美国心理学家McCulloch和数理逻辑专家Pitts联合提出的M-P模型。看来交差学科才能创造出伟大的智慧啊。1949年,心理学家Hebb提出通过改变神经元连接强度达到学习的目的。随着,1957年感知器概念的提出迎来了神经网络的新高潮。但是,到了1969年由于当时的人工智能专家对神经网络抱有悲观态度,神经网络一度遭到冷落。后来由于大数据的发展,并行化的必要性,很多学者有想到了神经网络,并不断提出新的理论来改进它。比如美国波士顿大学的GrossbergCarpenter提出了自适应的网络等等。

目前神经网络的应用领域非常广泛,在模式识别、信号处理、智能控制、数据挖掘等领域前途无量。

广义的神经网络是一个大的系统,它包含了原来的简单神经网络,还包括像模拟退火、蚁群算法这样的改进神经网络。神经网络按照网络拓扑结构可分为前向神经网络和反馈神经网络;按照网络性能可分为连续性神经网络和离散型神经网络、确定性神经网络和随机性神经网络。本书中着重介绍的是前向型神经网络中的BPNN

不同的神经网络模型有不同的学习方式。分别是死记性学习、有监督的学习、无监督学习以及有监督和无监督的混合学习。BPNN采用的是有监督的学习,也就是说存在“教师”对网络的实际输出和“教师”指定的输出进行对比,得到一定范围的误差。然后系统通过误差函数进行权值的调整,使得误差函数达到最小值。

神经网络是最难理解的算法之一。很多人都只是听说,即是在自己的项目中用到了神经网络也不知道它的原理是什么。笔者在学习的过程中,也吃了不少苦头,原因是找不到问题的关键在哪里。

我们注意一下神经网络中的几个关键词。第一、有监督的学习。第二、神经元。第三、输入层、输出层、隐层。第四、反馈。

先说什么叫输入层、输出层和隐藏层。(神经网络有三层,输入层不是神经元,神经元只有两层)举个例子来说,我们小时候会玩的一种有奖游戏,有一个黑匣子,我们从上面投玻璃球(这可以当成是神经网络的输入层),我们不关心玻璃球在盒子里是怎么运动的(也就是神经网络的隐藏层),最后这个玻璃球会落入到相应的格子里(也就是神经网络的输出层)。也就是说,神经网络屏蔽了内部的计算,只是告诉我们结果。可能你没有玩过这个游戏,再举一个例子。

现在有这么一个感知器,它的功能是判断一个水果是苹果、香蕉还是橘子。我们输入是这个水果的颜色、味道、长度、形状特征。那我们输入了一个水果的特征,这个感知机告诉我们结果,至于这个计算过程是怎样的,我们没有兴趣知道,它是一系列的与特性向量有关的数学计算。

 

图一、屏蔽隐层的神经网络

对于人工神经网络来说,里面可能是这样的。主要我们也不知道隐层里面到底有几层。也不知道每层里面有几个神经元,各个神经元之间的关系。

 

图二、神经网络示意图

那么隐层是这么形成的呢?这就要说到神经网络的另外一个关键字,有监督的学习。所谓有监督的学习是在输入的时候已经告诉感知器这个是什么了。还拿上面的例子来说,就是在学习阶段,输入一个红色、甜、圆形、0.2kg的水果,告诉感知机这个就是苹果。再输入一个黄色、甜、长形、0.2kg的水果告诉它这个是香蕉。就这样通过大量的训练样本进行感知机的训练。在告诉感知机结果的同时,感知机也会有自己的判断。如果输入的是苹果,而感知机通过判断输出的是香蕉,感知机就会自动调整参数,直到误差控制在一定范围内。这个根据结果不断调整误差的过程,就叫做反馈。在BPNN中,因为误差是向前传播的,因此叫做前向反馈神经网络。这时隐藏层的就生成了,但是对我们来说这些都是我们看不见的东西。

那什么叫做感知机呢?通过上面的描述,感知机就是隐层所形成的神经网络。这个感知机概念的提出开始是单层的神经网络,随着多层次神经网络的发展,我们也将多层网络叫做感知机。感知机中包含若干神经元。不同的神经元组成不同的层次。

那什么叫做神经元呢?比较直观来说,上图中一个个圈就代表一个个神经元。每个神经元又有着复杂的内部结构。如下图所示。现在我们先不要纠结这些符号表示什么,大概有点印象即可。

 

图三、神经元内部结构

那么实际上最后的神经网络可能是这样子的。

 

图四、最终的神经网络

一个神经网络的形成大概就是这么一个过程,虽然实际上比这要复杂的多。这是一种不错的对于入门的人的一种直观认识。下面我们根据BPNN从专业的角度来分析什么是神经网络,以及如何实现一个神经网络了。大量的数学计算看起来让人头疼啊。

神经元

在前面我们引入了神经元的概念,见图三。由图可知,每个神经元只能有一个输出,但是有多个输入。其中图中,为第个神经元的内部状态,为神经元阀值,为输入信号,表示从第j个神经元到第个神经元连接的权值,说明神经元的每个输入都和一个权值相联系,正是这些权值决定了神经元网络的整体活跃性。权值的取值范围是【-1,1】,是浮点型数据。如果权值大于0,表示输入对神经元有激发(excitory)作用,反之权值为负数的话,对神经元有抑制(inhibitory)作用。当输入信号进入神经网络时,它们的值将会与它们对应的权值相乘,作为图中大圆的输入。大圆的‘核’是一个激励函数,它把所有的这些新的、经过权重调整后输入全部加起来,形成单个的激励值。激励值也是一个浮点数,且同样是可正可负的。根据激励值来产生函数的输出也即神经元的输出。如果激励值超过某个阀值,就会产生一个值为1的信号输出;如果激励值小于阀值,则输出0。由激励值产生的阶跃函数模型很明显是一个阶跃函数。

图五、阶跃函数

上述的假设可以描述为

     

上面的表达式表示的含义,是激励函数。H是状态转移函数。

由表达式可以看出,如果没有内部状态的话,,。

 

常用的神经元状态转移函数有:

(1)阶跃函数 1

        0

 

(2)准则线函数

     

 

(3)Sigmoid函数

 

 

(4)双曲线正切函数

其实说了这么多我们还是不知道神经元是什么。

感知机

也许通过上面的介绍,你对神经元有了了解,也许你还是云里雾里,都没关系,先看下面的内容,可能是你豁然开朗。大脑中的生物神经细胞和其他的神经细胞连接在一起为大脑的信息处理共同努力。在人工神经网络中也是一样的,细胞需要连接起来,为了简化神经网络系统,现在广泛使用的是分层的神经网络。我们先看一下单层感知机的原理。

由图可知,神经网络共有三层。输入层中的每个输入都送到了隐藏层,作为该层每个神经元的输入;然后,从隐藏层的每个神经元的输出都被连接到它下一层(可能还是隐藏层还有可能是输出层)的每一个神经细胞。在一个神经网络中,一般来说有很多隐藏层,每个隐藏层中的神经元的个数也各不相同。也有的问题只要有一层就够了,甚至有的问题没有隐藏层都是可以的,你只要把那些输入直接连接到输出神经细胞就行了。神经网络的层数和每层中神经元的个数取决于要解决的问题的复杂性。显然,神经网络中层数和神经元的个数越多,神经网络工作的速度就越低。而且,不是网络越复杂,分类的精确度就越高,因此有异常数据的出现等。因此,在实际应用中,神经网络的规模要适当的小。

说了这么多,我想此时,可能你更加迷茫了。比如,每个神经元的阀值时一样的吗?每个神经元输出到下一层神经元的权值是相同的吗?下面我们举个例子来说吧。还是上面分水果的例子。a b c d代表的是颜色、味道、形状、大小。

 

BP神经网络

在神经网络族中最常用的神经网络是BP神经网络。在《并行分布式处理》中对多层前馈的误差反向传播算法进行了深入研究,这使得BP神经网络在众多领域中得到广泛的应用。BP神经网络的主要思想是信号的向前传播和误差的后向传播。信号在正向传播中,输入信号通过隐藏层处理后传递给输出层。若输出层和预期的值不一样且大于误差可接受的范围之内,则进行误差反向传播过程。误差通过隐藏层向输入层传递,进行误差调整。通过不断调整各层之间的权值,直到输出误差达到可接受范围或者达到了最大学习次数为止。

BP神经网络可以有多个隐藏层。但是在《》一文中证明仅包含一个隐藏层的神经网络,只要有足够多的隐藏层神经元数目就能够以任意精度逼近一个连续的非线性函数,因此通常BP神经网络模型中只需要采用一个隐藏层就足够了。

BP神经网络的输入为n为输入层神经元的个数;输出层为m为输出层神经元的个数。隐藏层和输出层一般使用sigmoid函数作为激励函数。选用的sigmoid函数为上文介绍的。隐藏层的输入为,输出为。

在反向传播误差过程中,得到了BP神经网络的输出Y,其与预期的输出D之间存在误差,定义为 。由于   ,按照梯度最速下降法,权值和阀值的调整量和误差的梯度下降成正比。

Java代码实现BP神经网络

Java实现怎么样的神经网络呢?我们的输入、输出是什么,算法的思想是什么。我们需要对纯数据的数据集分类,第一步需要做,我们的程序的目的是输入一个数据的时候能够自动判断它的类别。我们知道什么、不知道什么。

可以将每个神经元当成一个类,它的属性和方法有哪些呢?我们知道,每个神经元有多个输入信号和单一的输出信号,输出信号是由多个输入信号经过神经元。的转换函数变换后得到的。在每一个学习周期内,神经元必须根据反转回来的误差调整自己的权值。

单机版进行化的神经网络可以采取多线程的方式,使得每个神经元都继承线程。

神经网络工具箱

我们上面只是简单的用代码介绍神经网络的基本原理,对开发一个可用的工具箱来说远远不够,接下来我们就使用一下神经网络的工具箱。

MATLAB 神经网络工具箱

Encog 神经网络工具箱

Mahout神经网络源码解读

Mahout中的神经网络也是单机版的。在mahout-core中的org.apache.mahout.classifier.mlppackage中。在这个包中有三个类,这三个类分别是,能不能利用这三个类来写什么呢。

类名

解释

NeuralNetwork.java

神经网络类

NeuralNetworkFunctions.java

神经网络方法

MultilayerPerceptron.java

多层感知机

 

wiki上时这么解释多层感知机的:一个多层感知器(MLP)是一种前馈人工神经网络模型映射的输入数据集合上的一组适当的输出。一个MLP组成的有向图中的节点的多个层,每个层完全连接到下一个。除了输入节点,每个节点是一个神经元(或处理单元)与非线性激活函数。MLP利用监督学习技术称为反向传播算法训练网络。[ 1 ] [ 2 ] MLP是一个修改的标准线性感知器,可以区分的数据是不是线性可分的。这里我们就不再详细介绍了。

mahout中没有神经网络的例子,是不是还没有开发好。我们自己写一个main函数吧。

HMM

马尔科夫过程是目前发展很快、应用很广的一种重要随机过程。它的特点是当前过程在t0时刻所处的状态为已知状态的条件下,过程在时刻t所处的状态仅与时刻t0所处的状态有关,而和过程t0时刻之前的状态无关,这种特性叫做无后效性。马尔科夫过程按照其状态和时间参数是连续的还是离散的,分成三类。(1)时间、状态都是离散的马尔科夫过程,成为马尔科夫链;(2)时间连续、状态离散的马尔科夫过程,称为连续时间的马尔科夫链;(3)时间、状态都连续的马尔科夫过程。马尔科夫链的应用十分广泛,包括文本分类、语音识别、信号处理等。在马尔科夫过程中常用的是马尔科夫链,马尔科夫链中最具代表性的是隐马尔科夫链。本文就具体介绍马尔科夫链和马尔科夫过程。

马尔科夫链

在概率论中马尔科夫链是这样定义的:由于马尔科夫链的状态和时间参数都是离散的,我们不妨假设:随机过程的状态空间是由有限个或可列多个状态构成,即状态空间为;过程只在时刻0,1,2...发生状态转移,即参数集为

将过程在时刻所处的状态记为,即。

假如随机过程在时刻处在任一状态的概率只与过程在时刻所处的状态有关,而与过程时刻以前所处的状态无关,即条件概率满足

则称随机过程为马尔科夫链,简称马氏链。(引自《概率统计和随机过程》)

总结:马尔科夫链是个离散过程。

隐马尔科夫链

在上面的介绍中,我们对马尔科夫链有了深刻的了解,隐马尔科夫链是马尔科夫链的一种,在语言识别、行为识别、文字识别等领域发挥着重要的作用。隐马尔可夫模型马尔可夫链的一种,它的状态不能直接观察到,但能通过观测向量序列观察到,每个观测向量都是通过某些概率密度分布表现为各种状态,每一个观测向量是由一个具有相应概率密度分布的状态序列产生。所以,隐马尔可夫模型是一个双重随机过程----具有一定状态数的隐马尔可夫链和显示随机函数集。

举个例子来说明HMM。在wiki上的例子这么写道:Alice Bob是好朋友,但是他们离得比较远,每天都是通过电话了解对方那天作了什么.Bob仅仅对三种活动感兴趣:公园散步,购物以及清理房间.他选择做什么事情只凭当天天气.Alice对于Bob所住的地方的天气情况并不了解,但是知道总的趋势.Bob告诉Alice每天所做的事情基础上,Alice想要猜测Bob所在地的天气情况.
  Alice认为天气的运行就像一个马尔可夫链其有两个状态 ”,但是无法直接观察它们,也就是说,它们对于Alice是隐藏的.每天,Bob有一定的概率进行下列活动:”散步”, “购物”, 或 清理”. 因为Bob会告诉Alice他的活动,所以这些活动就是Alice的观察数据.这整个系统就是一个隐马尔可夫模型HMM.
Alice知道这个地区的总的天气趋势,并且平时知道Bob会做的事情.也就是说这个隐马尔可夫模型的参数是已知的

  在这个问题中有这么几个概念:

状态:{ 雨,晴}

状态转移矩阵:给定前一天天气情况下的当前天气概率。

向量:定义系统初始化时每一个状态的概率。假设雨晴的概率是{0.6,0.4} 

在这些代码中,start_probability代表了Alice对于Bob第一次给她打电话时的天气情况的不确定性(Alice知道的只是那个地方平均起来下雨多些).在这里,这个特定的概率分布并非平衡的,平衡概率应该接近(在给定变迁概率的情况下){‘Rainy’: 0.571, ‘Sunny’: 0.429}。 transition_probability 表示马尔可夫链下的天气变迁情况,在这个例子中,如果今天下雨,那么明天天晴的概率只有30%.代码emission_probability 表示了Bob每天作某件事的概率.如果下雨,有 50% 的概率他在清理房间;如果天晴,则有60%的概率他在外头散步。
  AliceBob通了三天电话后发现第一天Bob去散步了,第二天他去购物了,第三天他清理房间了。Alice现在有两个问题:这个观察序列散步、购物、清理的总的概率是多少?(注:这个问题对应于HMM的基本问题之一:已知HMM模型λ及观察序列O,如何计算P(O|λ)最能解释这个观察序列的状态序列(晴/雨)又是什么?(注:这个问题对应HMM基本问题之二:给定观察序列O=O1,O2,…OT以及模型λ,如何选择一个对应的状态序列S = q1,q2,…qT,使得S能够最为合理的解释观察序列O?)
  至于HMM的基本问题之三:如何调整模型参数使得P(O|λ)最大?这个问题事实上就是给出很多个观察序列值,来训练以上几个参数的问题。

针对评估问题:前向算法

解码问题:Viterbi算法

学习问题:Baum-Welch算法向前向后算法)

马尔科夫链HMM有五个重要的元素

1、 隐含状态

一个可以由马尔科夫过程描述的系统的真实状态。比如上面示例中的天气。

2、 可观测状态O

在这个马尔科夫过程中可视的状态。比如上面示例中的Bob的活动。

3、 初始状态概率矩阵

马尔科夫模型在时间t=1时的隐含状态的概率。对应上面示例中的天气的初始概率矩阵。

4、 隐含状态转移概率矩阵A

包含了一个隐含状态到另一个隐含状态的概率。

5、 观测状态转移概率矩阵B

包含了给定隐马尔科夫模型的某一个特殊的隐藏状态,观察到的某个观测状态的概率。

隐马尔科夫链(HMM)的三类问题

在上面的例子中,我们已经提到了这几个问题。即:给定HMM求一个观测序列的概率;搜索最有可能生成一个观测序列的隐含状态训练;给定观测序列生成一个隐马尔科夫链。这三个问题对应不同的应用场景,也有着不同的算法。

1、评估问题,也就是给定HMM求一个观测序列的概率。比如,对于一个场景来说,建立了不同的HMM模型,我们想要知道哪一个HMM最有可能产生这个给定的观测序列。在上面的示例中,有两个同学分别建立了HMM的模型,现在有这么一个观测序列{ 逛街、打扫房间、逛街},我们想要计算每个模型中观测到的观测序列的概率。现在业界使用前向算法(forward algorithm)来计算给定HMM后的一个观测序列的概率,并根据这个结果选择最合适的HMM模型。在语音识别中这种类型的问题发生在当一大堆数目的马尔科夫模型被使用,并且每一个模型都对一个特殊的单词进行建模时。一个观察序列从一个发音单词中形成,并且通过寻找对于此观察序列最有可能的隐马尔科夫模型(HMM)识别这个单词。

2、解码问题,也就是给定观察序列搜索最可能的隐藏状态序列。另一个相关问题,也是最感兴趣的一个,就是搜索生成输出序列的隐藏状态序列。在许多情况下我们对于模型中的隐藏状态更感兴趣,因为它们代表了一些更有价值的东西,而这些东西通常不能直接观察到。考虑上文的例子,Alice只能知道Bob的活动状态,但是他更想知道天气的情况,天气状态在这里就是隐藏状态。我们使用Viterbi 算法(Viterbi algorithm)确定(搜索)已知观察序列及HMM下最可能的隐藏状态序列。Viterbi算法(Viterbi algorithm)的另一广泛应用是自然语言处理中的词性标注。在词性标注中,句子中的单词是观察状态,词性(语法类别)是隐藏状态(注意对于许多单词,如wind,fish拥有不止一个词性)。对于每句话中的单词,通过搜索其最可能的隐藏状态,我们就可以在给定的上下文中找到每个单词最可能的词性标注。

3、学习问题,即根据观察序列生成隐马尔科夫模型。与HMM相关的问题中最难的,根据一个观察序列(来自于已知的集合),以及与其有关的一个隐藏状态集,估计一个最合适的隐马尔科夫模型(HMM),也就是确定对已知序列描述的最合适的(,A,B)三元组。当矩阵A和B不能够直接被(估计)测量时,前向-后向算法(forward-backward algorithm)被用来进行学习(参数估计),这也是实际应用中常见的情况。

前向算法

我们的目标是计算给定隐马尔科夫模型HMM下的观察序列的概率。因此我们首先通过计算局部概率降低计算整个概率的复杂度,局部概率表示的是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相乘后的总和再求积的结果:

如何计算观察序列的概率(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指的是隐藏状态数目。

维特比算法

寻找最可能的隐藏状态序列,对于一个特殊的隐马尔科夫模型(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是按照最优(最可能)的路径到达该状态的概率。因此,选择其中最大的一个,并回溯找出所隐藏的状态路径,就是这个问题的最好答案。
  关于维特比算法,需要着重强调的一点是它不是简单的对于某个给定的时间点选择最可能的隐藏状态,而是基于全局序列做决策——因此,如果在观察序列中有一个“非寻常”的事件发生,对于维特比算法的结果也影响不大。
  这在语音处理中是特别有价值的,譬如当某个单词发音的一个中间音素出现失真或丢失的情况时,该单词也可以被识别出来。

前向-后向算法

根据观察序列生成隐马尔科夫模型

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

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

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

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

好了,后向算法就到此为止了,下一节我们粗略的谈谈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算法的主要目的是提供一个简单的迭代算法计算后验密度函数,它的最大优点是简单和稳定,但容易陷入局部最优。
 有了后向算法和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。

Java实现的HMM

类图,每个类的作用是

实现的原理是什么

MahoutHMM的应用

mahout中有个例子是使用HMM对文本进行分类。由于HMMmahout代码是单机版的,所以跑在Hadoop集群上和跑在本地是没有差别的。

PosTagger.java

 

 

BaumWelchTrainer.java

后向-前向算法

 

HmmAlgorithm.java

 

 

HmmEvaluator.java

 

 

HmmModel.java

 

 

HmmTrainer.java

 

 

HmmUtils.java

 

 

LossyHmmSerializer.java

 

 

RandomSequenceGenertator.java

 

 

ViterbiEvaluator.java

维特比算法

 

 

在这些类中最关键的类是HmmAlgorithm.java,这个类中实现了三种HMM算法。

 

三部曲:

1、训练成HMMmodel

2、测试HMMMode

3、输入HMMModel和测试语句进行分类。

现在有这样的需求该怎么做。自己调用HMM的代码实现一个文本分类。

K-means 算法

在分类算法中往往需要训练样本,它们是有监督的学习,这样做的成本比较高。而聚类算法是无监督的学习,在推荐系统中在对用户和商品继续分类时也经常用到这个算法。

聚类算法思想是将相近的点聚合在一起。举个例子来说,现在需要将淘宝用户分成K类,我们事先知道他们的行为特征,将这些行为抽象成特性向量。现在随机选择K个人,作为每个类别的中心点,对于其他人来说,根据计算他们到这K个人的距离(距离是通过特性向量计算的,标志的是两个之间的相似程度),将他们跟距离最短的(最相近的)人归为一类,形成一个新的集合。重新计算每个集合的中心值,将他作为新的该类的中心点。再次计算每个人的归属类别,直到所有的类别都不再发生变化为止。

聚类(Clustering)是将一些物理上的或者是抽象的对象进行分组的过程,聚类也是特殊的分类。聚类和分类的不同在于,分类是从上到下的分裂过程,聚类是从下向上的聚合过程。我们将聚类生成的组叫做簇(cluster),这些簇是数据对象的集合。好的聚类结果是,同一簇包含的数据集合相似度高,不同簇包含的数据集合之间的差异较大。相似度的表示方法有很多,比如空间坐标之间使用距离表示;抽象的事物之间使用特征向量表示,等等。

K-means是最常用的一种聚类算法,它使用均值作为新的中心点,因此叫做K-means算法。K-means算法以其易于理解、简单适应性广的特性在多个领域占有重要地位,特别是在一些统计分析软件例如SPSS中集成了K-means算法。

算法原理

 

图一、K-means算法示意图

如图所示,现在我们需要将A,B,C,D,E五个点进行聚类,图中的无符号的两个点表示当前中心点。依次计算这五个点到这两个中心点的聚类,这时将AB聚类成第一类别,将CDE聚类成第二类别;更新中心点,这时中心点变成图中新的无符号的两个圆点。进入下一次迭代过程,再次对这五个点进行聚类,此时ABC聚类成第一类别,DE聚类成第二类别。再次更新中心点。重复上述迭代过程,直到中心点的位置不再改变(用收敛近似表示)。最终的聚类结果就是最后一次迭代过程的结果。

 

距离函数

k-means算法中,数据之间的相似度是非常重要的概念。两个数据之间的相似度是靠距离来衡量的,在对数据进行处理时往往是将数据转换成多维向量,在通过向量距离来进行相似度。设两个维向量和 分别表示两个对象,有多种形式的距离向量可以采用。

(1)闽科夫斯基距离

其中。闽科夫距离是无限个距离度量的概化。当它的值为1时,为曼哈顿距离;当它的值为2时为欧几里得距离;当它接近无穷大时是切比雪夫距离。

(2)曼哈顿距离

(3)欧几里得距离

(4)切比雪夫距离

上面几种运算公式是比较常见的,它们表示的是以哪种发生逼近中心点的。图下图中图(a)是哈弗曼距离的逼近方式,(b)代表的是闽科夫斯基距离,(c)代表的是欧几里得距离。至于他们的优缺点和应用范围不在本书的重点介绍范围之内,读者们可以自行查找资料,选择最合适的算法。

    

    (a)                      (b)                        (c)

图二 几种距离公式的逼近方式

中心点计算公式

K-means算法中最重要的步骤之一是中心点的更新,k-means算法是用簇中的数据的均值来当做新的中心点。

个点的中心点的距离为

算法流程

K-means算法的算法流程可以用下表表示。

表一、k-means算法流程图

输入:m个数据

过程:

1)随机选择K个数据作为聚类的K个中心。

2)对其他所有的数据分别求其到K个中心的距离,距离哪个中心点最近就将哪个归为一类。

3)更新聚类中心

4)重复(2)(3)知道聚类中心不再移动。

输出:分类结果

 

K-means算法非常简单,那么我们动手写个程序来近距离的接触一下K-means算法吧。

基于javaK-means实现

数据描述

为了让读者更好的理解k-means算法,这里我们先用java实现一下。数据如下格式:每行数代表某个点的n为坐标向量,对这几个点进行聚类。它们被保存在文件中,文件中每行数字以’\t’隔开。在这里可以看出是空间坐标,三个数字分别表示(x,y,z)。要求将这些点分成三类,并将每一类包含的数据集合依次输出。

输入数据data.txt

0.3 0.2 0.4

0.4 0.2 0.4

0.5 0.2 0.4

5.0 5.2 5.4

6.0 5.2 6.4

4.0 5.2 4.4

10.3 10.4 10.5

10.3 10.4 10.5

10.3 10.5 10.6

算法思路

由于这些点在存储已经计算它们之间的距离是比较麻烦,我们将它定义为一个类,叫做Node.它包括x,y,z三个成员变量以及两个构造函数分别是有参数和无参数的。为了方便计算两个点之间的距离,我们还定义了一个compareNode(Node node)函数,这里使用的距离公式是欧几里得距离公式。该类的定义如下。

public class Node {

private double x;

private double y;

private double z;

Node(double x,double y,double z){

this.x = x;

this.y = y;

this.z = z;

}

double CompareNode(Node node){

double result = (this.x -node.getX())*(this.x -node.getX())

+(this.y -node.getY())*(this.y -node.getY())+

(this.z -node.getZ())*(this.z -node.getZ());

result = Math.sqrt(result);

return result;

}

}

 

K-means算法是完全按照上述算法流程实现的。下面是算法的伪代码和关键程序。由于篇幅原理,不能列出全部源码。首先将全部的数据读入到节点链表(nodes)中,再构建一个类别链表数组(classes),它们存放每个类别的节点;为了找到该节点属于某个类别,需要将该节点与每个节点的距离保存在double数组(disnums)中;为了判断迭代是否结束,在更新中心点的同时,计算新中心点和原来中心点的距离,小于某个阀值时该值是收敛的,视为该中心点不再变化。但是要想停止迭代需要所有的中心点都不能变化。所以使用布尔数值stop来表示每个中心点是否收敛。

 * 参数说明

 * nodes 从文件中读入的节点放在链表中.

 * classes 是一个链表数组,存放的是每个类别中的节点

 * centernodes 存放中心节点的数组

 * disnums存放每个节点与中心点的距离差,以便找到最小的那个

 * stop 布尔数组,表示每个中心点是否停止迭代

 * k 分组的个数

 

 

伪代码

 

给出关键代码的伪代码如下表。

表二、K-means伪代码

输入:文件

  初始化全部节点;

  初始化中心点;

  do{

   for(第一个节点->最后一个节点){

   for(第一个中心点->最后一个中心点){

   计算节点到中心点的距离,将该节点与其最近的中心点归为一类;

   }

   更新中心点;

   }

  }while(中心点在变化);

输出:聚类结果

 

关键代码

这里仅仅给出了程序主流程的代码,其中还调用了其他的函数,这些函数的实现详见附录。这段代码的含义是上述伪代码表示的内容。

void start(){

do{

/*

 * 每次循环都需要将原来的链表清空

 */

classes = new ArrayList[k];

for(int i=0;i<k;i++){

ArrayList<Node> numclass = new ArrayList<Node>();

classes[i] = numclass;

stop[i]=false;

}

/*

 * 计算每个节点到每个中心点的距离,找出最小的距离将其加入到该类别,

 * 更新中心点

 * 重复上面两个直到中心点不再变化为止。

 */

for(int i =0;i<nodes.size();i++){

disnums = new double[k];

for(int j=0;j<centernodes.size();j++){

double result = nodes.get(i).CompareNode(centernodes.get(j));

disnums[j]=result;

}

//加入相应的链表中

int location = minClass(disnums);

classes[location].add(nodes.get(i));

}

//更新中心点

for(int m=0;m<classes.length;m++){

Node newNode = new Node();

newNode = updataCenter(classes[m]);

if(centernodes.get(m).CompareNode(newNode)<0.001){

                //该中心点是否是收敛的

Stop[m]=true;

}

//更新中心点

centernodes.set(m, newNode);

}

}while(sstop() == false);

}

运行程序,能对这些点进行分类,证明我们的思路是正确的。当然,本文中的例子只是实现该算法的一种形式,读者可自行开发实现它。单机版的k-means算法时间复杂度是

。表示聚类个数,表示数据集的大小,表示计算聚类的时间复杂度,表示迭代次数。可以看出k-means算法与数据量n有很大的关系,当n增加时,时间复杂度增加。

基于hadoop

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值