维特比算法在隐马尔可夫模型中的应用

前言

文章标题的两个概念也许对于许多同学们来说都相对比较陌生,都比较偏向于于理论方面的知识,但是这个算法非常的强大,在很多方面都会存在他的影子。2个概念,1个维特比算法,1个隐马尔可夫模型。你很难想象,输入法的设计也会用到其中的一些知识。

HMM-隐马尔可夫模型

隐马尔可夫模型如果真的要展开来讲,那短短的一篇文章当然无法阐述的清,所以我会以最简单的方式解释。隐马尔可夫模型简称HMM,根据百度百科中的描述,隐马尔可夫模型描述的是一个含有隐含未知参数的马尔可夫模型。模型的本质是从观察的参数中获取隐含的参数信息。一般的在马尔可夫模型中,前后之间的特征会存在部分的依赖影响。示例图如下:


隐马尔可夫模型在语音识别中有广泛的应用。其实在输入法中也有用到。举个例子,假设我输入wszs,分别代表4个字,所以观察特征就是w, s, z, s,那么我想挖掘出他所想表达的信息,也就是我想打出的字是什么,可能是"我是张三",又可能是“晚上再说”,这个就是可能的信息,最可能的信息,就会被放在输入选择越靠前的位置。在这里我们就会用到一个叫维特比的算法了。

Viterbi-维特比算法

维特比算法这个名字的产生是以著名科学家维特比命名的,维特比算法是数字通信中非常常用的一种算法。那么维特比算法到底是做什么的呢。简单的说,他是一种特殊的动态规划算法,也就是DP问题。但是这里可不是单纯的寻找最短路径这些问题,可能他是需要寻找各个条件因素下最大概率的一条路径,假设针对观察特征,会有多个隐含特征值的情况。比如下面这个是多种隐含变量的组合情况,形成了一个密集的篱笆网络。


于是问题就转变成了,如何在这么多的路径中找到最佳路径。如果这是输入法的例子,上面的每一列的值就是某个拼音下对应的可能的字。于是我们就很容易联想到可以用dp的思想去做,每次求得相邻变量之间求得后最佳的值,存在下一列的节点上,而不是组合这么多种情况去算。时间复杂度能降低不少。但是在马尔可夫模型中,你还要考虑一些别的因素。所以总的来说,维特比算法就是一种利用动态规划算法寻找最有可能产生观察序列的隐含信息序列,尤其是在类似于隐马尔可夫模型的应用中。

算法实例

下面给出一个实际例子,来说明一下维特比算法到底怎么用,如果你用过动态规划算法,相信一定能很迅速的理解我想表达的意思。下面这个例子讲的是海藻的观察特征与天气的关系,通过观测海藻的特征状态,退出当天天气的状况。当然当天天气的预测还可能受昨天的天气影响,所以这是一个很棒的隐马尔可夫模型问题。问题的描述是下面这段话:

假设连续观察3天的海藻湿度为(Dry,Damp,Soggy),求这三天最可能的天气情况。天气只有三类(Sunny,Cloudy,Rainy),而且海藻湿度和天气有一定的关系。问题具体描述,链接在此

ok,状态转移概率矩阵和混淆矩阵都已给出,详细代码以及相关数据,请点击链接:https://github.com/linyiqun/DataMiningAlgorithm/tree/master/Others/DataMining_Viterbi

直接给出代码解答,主算法类ViterbiTool.java:

package DataMining_Viterbi;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * 维特比算法工具类
 * 
 * @author lyq
 * 
 */
public class ViterbiTool {
	// 状态转移概率矩阵文件地址
	private String stmFilePath;
	// 混淆矩阵文件地址
	private String confusionFilePath;
	// 初始状态概率
	private double[] initStatePro;
	// 观察到的状态序列
	public String[] observeStates;
	// 状态转移矩阵值
	private double[][] stMatrix;
	// 混淆矩阵值
	private double[][] confusionMatrix;
	// 各个条件下的潜在特征概率值
	private double[][] potentialValues;
	// 潜在特征
	private ArrayList<String> potentialAttrs;
	// 属性值列坐标映射图
	private HashMap<String, Integer> name2Index;
	// 列坐标属性值映射图
	private HashMap<Integer, String> index2name;

	public ViterbiTool(String stmFilePath, String confusionFilePath,
			double[] initStatePro, String[] observeStates) {
		this.stmFilePath = stmFilePath;
		this.confusionFilePath = confusionFilePath;
		this.initStatePro = initStatePro;
		this.observeStates = observeStates;

		initOperation();
	}

	/**
	 * 初始化数据操作
	 */
	private void initOperation() {
		double[] temp;
		int index;
		ArrayList<String[]> smtDatas;
		ArrayList<String[]> cfDatas;

		smtDatas = readDataFile(stmFilePath);
		cfDatas = readDataFile(confusionFilePath);

		index = 0;
		this.stMatrix = new double[smtDatas.size()][];
		for (String[] array : smtDatas) {
			temp = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				try {
					temp[i] = Double.parseDouble(array[i]);
				} catch (NumberFormatException e) {
					temp[i] = -1;
				}
			}

			// 将转换后的值赋给数组中
			this.stMatrix[index] = temp;
			index++;
		}

		index = 0;
		this.confusionMatrix = new double[cfDatas.size()][];
		for (String[] array : cfDatas) {
			temp = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				try {
					temp[i] = Double.parseDouble(array[i]);
				} catch (NumberFormatException e) {
					temp[i] = -1;
				}
			}

			// 将转换后的值赋给数组中
			this.confusionMatrix[index] = temp;
			index++;
		}

		this.potentialAttrs = new ArrayList<>();
		// 添加潜在特征属性
		for (String s : smtDatas.get(0)) {
			this.potentialAttrs.add(s);
		}
		// 去除首列无效列
		potentialAttrs.remove(0);

		this.name2Index = new HashMap<>();
		this.index2name = new HashMap<>();

		// 添加名称下标映射关系
		for (int i = 1; i < smtDatas.get(0).length; i++) {
			this.name2Index.put(smtDatas.get(0)[i], i);
			// 添加下标到名称的映射
			this.index2name.put(i, smtDatas.get(0)[i]);
		}

		for (int i = 1; i < cfDatas.get(0).length; i++) {
			this.name2Index.put(cfDatas.get(0)[i], i);
		}
	}

	/**
	 * 从文件中读取数据
	 */
	private ArrayList<String[]> readDataFile(String filePath) {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		return dataArray;
	}

	/**
	 * 根据观察特征计算隐藏的特征概率矩阵
	 */
	private void calPotencialProMatrix() {
		String curObserveState;
		// 观察特征和潜在特征的下标
		int osIndex;
		int psIndex;
		double temp;
		double maxPro;
		// 混淆矩阵概率值,就是相关影响的因素概率
		double confusionPro;

		this.potentialValues = new double[observeStates.length][potentialAttrs
				.size() + 1];
		for (int i = 0; i < this.observeStates.length; i++) {
			curObserveState = this.observeStates[i];
			osIndex = this.name2Index.get(curObserveState);
			maxPro = -1;

			// 因为是第一个观察特征,没有前面的影响,根据初始状态计算
			if (i == 0) {
				for (String attr : this.potentialAttrs) {
					psIndex = this.name2Index.get(attr);
					confusionPro = this.confusionMatrix[psIndex][osIndex];

					temp = this.initStatePro[psIndex - 1] * confusionPro;
					this.potentialValues[BaseNames.DAY1][psIndex] = temp;
				}
			} else {
				// 后面的潜在特征受前一个特征的影响,以及当前的混淆因素影响
				for (String toDayAttr : this.potentialAttrs) {
					psIndex = this.name2Index.get(toDayAttr);
					confusionPro = this.confusionMatrix[psIndex][osIndex];

					int index;
					maxPro = -1;
					// 通过昨天的概率计算今天此特征的最大概率
					for (String yAttr : this.potentialAttrs) {
						index = this.name2Index.get(yAttr);
						temp = this.potentialValues[i - 1][index]
								* this.stMatrix[index][psIndex];

						// 计算得到今天此潜在特征的最大概率
						if (temp > maxPro) {
							maxPro = temp;
						}
					}

					this.potentialValues[i][psIndex] = maxPro * confusionPro;
				}
			}
		}
	}

	/**
	 * 根据同时期最大概率值输出潜在特征值
	 */
	private void outputResultAttr() {
		double maxPro;
		int maxIndex;
		ArrayList<String> psValues;

		psValues = new ArrayList<>();
		for (int i = 0; i < this.potentialValues.length; i++) {
			maxPro = -1;
			maxIndex = 0;

			for (int j = 0; j < potentialValues[i].length; j++) {
				if (this.potentialValues[i][j] > maxPro) {
					maxPro = potentialValues[i][j];
					maxIndex = j;
				}
			}

			// 取出最大概率下标对应的潜在特征
			psValues.add(this.index2name.get(maxIndex));
		}

		System.out.println("观察特征为:");
		for (String s : this.observeStates) {
			System.out.print(s + ", ");
		}
		System.out.println();

		System.out.println("潜在特征为:");
		for (String s : psValues) {
			System.out.print(s + ", ");
		}
		System.out.println();
	}

	/**
	 * 根据观察属性,得到潜在属性信息
	 */
	public void calHMMObserve() {
		calPotencialProMatrix();
		outputResultAttr();
	}
}
测试结果输出:

观察特征为:
Dry, Damp, Soggy, 
潜在特征为:
Sunny, Cloudy, Rainy, 

参考文献

百度百科-隐马尔可夫模型

百度百科-维特比

<<数学之美>>第二版-吴军

http://blog.csdn.net/jeiwt/article/details/8076739

posted @ 2020-01-12 19:09  回眸,境界  阅读(102)  评论(0编辑  收藏  举报