使用HMM隐式马尔科夫链实现基于拼音的文本纠错

HMM实现基于拼音的文本纠错

文章将从以下4个小节进行描述:

1.问题描述

2.思路

3.源码

4.应用


1.问题描述

对于歌曲的语音搜索实现方案之一如下。

使用百度语音进行用户语音识别,返回的字符串调用歌词搜索,而歌词搜索使用的是分词的方式进行的索引建立,因此字符串识别的准确率直接影响最后返回歌曲的正确性。

而百度语音是基于非特定样本进行的训练,其语言环境涵盖非常广,而对于歌曲语言搜索这个业务来说,我们的语料库只是特定的歌词文本集,那么如何解决这个纠正语言识别结果的问题便是这个模型的解决目标。


2.思路

语音识别返回的结果之所以与歌词库不匹配在于其上下文环境不符合歌词环境,但大部分文字是同音的,因此使用第三方java开源包将识别结果先转换为拼音形式,在使用HMM模型对拼音重新组合,从而得到符合歌词环境的字符串。

而在HMM模型中,几个比较重要的概念就是隐藏状态和可观测状态,以及隐藏状态到可观测状态的发射概率、隐藏状态间的转移概率。在这里,可观测状态明显就是拼音形式,而隐藏状态则是它背后的汉字形式,而在歌词环境中,汉字之间是有上下文联系的,也就是说汉字之间在歌词这个语料库中有其独特的转移概率,而每个汉字到其所有的发音都有一个发射概率,且和为1。但是在这里,由于使用的java包可以简单的认为每个字都是单音的,因此,对于每个字而言,它们都只有一个发音,而发射概率为1。

所以现在主要需要做的就是训练HMM模型,即计算字间的转移概率。


3.源码

以下是简要的代码demo

package HmmViterbi_Correction;


import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Stack;


public class HmmModel {
private static String CharSet = "UTF-8";

//拼音与字符对照表
private HashMap<String, HashSet<Character>> pinyinMap = new HashMap<String, HashSet<Character>>(); 
//字符与其条件概率对照表:<char1, <char2, pro(char1,char2|char1)>>
private HashMap<Character, HashMap<Character, Double>> transferMap = new HashMap<Character, HashMap<Character, Double>>();
//单字符统计表
private HashMap<Character, Integer> countMap = new HashMap<Character, Integer>();


public static void main(String[] args) {
HmmModel hmm=new HmmModel("data/pinyin.txt");
//hmm.showPinyinMap();
//hmm.showTransferMap();

String str="窝德爱移除酒香四年";
String output = hmm.predict(str);
System.out.println(output);
}


/**
* 预测
*/
public String predict(String str) {
String buffer = PinYin.simplify(str).trim();
int len=buffer.length();
String[] pinyins = new String[len];
for (int i = 0; i < len; i++) {
pinyins[i] = PinYin.toPY(buffer.charAt(i));
}

HashSet<Character> temp = pinyinMap.get(pinyins[0]); //首字符
HashSet<PathNode> chars1 = new HashSet<PathNode>();
for (char ch:temp) {
chars1.add(new PathNode(1, ch));
}

for (int i = 1; i < pinyins.length; i++) {
HashSet<Character> chars2 = pinyinMap.get(pinyins[i]);    //后一个字的候选集
HashSet<PathNode> chars3 = new HashSet<PathNode>();
for (char ch2:chars2) {
double maxValue=-1;
PathNode pNode=null;
for (PathNode node1:chars1) {
double value = node1.prob * checkProb(node1.ch,ch2); //p(ch2|ch1)
if (value>maxValue) { //如果超过了当前最大值
maxValue=value;
pNode=new PathNode(value, ch2);
pNode.preNode=node1;
}
}
chars3.add(pNode);
}
chars1=chars3;
}

PathNode targetNode=null;
double maxValue=-1;
for (PathNode node:chars1) {
if (node.prob>maxValue) {
maxValue=node.prob;
targetNode=node;
}
}


Stack<Character> charStack=new Stack<Character>();
PathNode path=targetNode;
while (path!=null) {
charStack.push(path.ch);
path=path.preNode;
}

StringBuilder stringBuilder=new StringBuilder();
while (!charStack.isEmpty()) {
stringBuilder.append(charStack.pop());
}
return stringBuilder.toString();
}


//需考虑null的情况
private double checkProb(char ch1, char ch2) { 
HashMap<Character, Double> proMap = transferMap.get(ch1);
if (proMap==null) {
return 0.01;
}
Double value = proMap.get(ch2);
if (value==null) {
return 0.01;
}
return value;
}


public HmmModel(String path) {
learnfromFile(path);
}

/**
* 训练
*/
private void learnfromFile(String path) {
BufferedReader br=null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(path), Charset.forName(CharSet)));
String line=null;
while ((line=br.readLine())!=null) {
line=line.trim();
if (line.equals("")) { //跳过空行
continue;
}
line = PinYin.simplify(line);  //繁体转简体
for (int i = 0; i < line.length(); i++) {
if (i>0) {
addProb(line.charAt(i-1), line.charAt(i));
}

char ch = line.charAt(i);
Integer count=countMap.get(ch);
if (count==null) {
countMap.put(ch, 1);
} else {
countMap.put(ch, ++count);
}

String py = PinYin.toPY(ch);
HashSet<Character> charSet=null;
if ((charSet = pinyinMap.get(py))==null) {
charSet=new HashSet<Character>();
charSet.add(ch);
pinyinMap.put(py, charSet);
} else {
charSet.add(ch);
}
}
}
for (Character ch : transferMap.keySet()) {
int dinominator=countMap.get(ch);
HashMap<Character, Double> proMap = transferMap.get(ch);
for (Character ch2 : proMap.keySet()) {
double newValue = proMap.get(ch2)/dinominator;
proMap.put(ch2, newValue);
}
}

} catch (IOException e) {
System.err.println("reader load failed");
e.printStackTrace();
} finally {
if (br!=null) {
try {
br.close();
} catch (IOException e) {
System.err.println("reader close failed");
e.printStackTrace();
}
}
}
}

private void addProb(char ch1, char ch2) { //顺序:ch1 ch2 ch3... p(ch2|ch1)=p(ch1,ch2)/p(ch1)
HashMap<Character, Double> proMap = transferMap.get(ch1);
if (proMap==null) {
proMap=new HashMap<Character, Double>();
transferMap.put(ch1, proMap);
}
Double value=proMap.get(ch2);
if (value==null) {
proMap.put(ch2, 1.0);
} else {
proMap.put(ch2, ++value);
}
}

public void showPinyinMap() {
System.out.println("pinyinMap:");
for (Entry<String, HashSet<Character>> entry : pinyinMap.entrySet()) {
System.out.print(entry.getKey()+"->[");
HashSet<Character> charSet=entry.getValue();
for (Character ch : charSet) {
System.out.print(ch+",");
}
System.out.println("]");
}
}

public void showTransferMap() {
for (Entry<Character, HashMap<Character, Double>> entry : transferMap.entrySet()) {
char ch1 = entry.getKey();
HashMap<Character, Double> probMap=entry.getValue();
for (Entry<Character, Double> entry2 : probMap.entrySet()) {
char ch2 = entry2.getKey();
double value = entry2.getValue();
System.out.print(ch2+"|"+ch1+"="+value+" ");
}
System.out.println();
}
}

}


class PathNode implements Comparable<PathNode> {
public PathNode preNode=null;
public char ch;
public double prob=0;

public PathNode(double probability, char ch) {
this.ch=ch;
prob = probability;
}


@Override
public int compareTo(PathNode o) {
if (this.prob > o.prob) {
return 1;

if (this.prob < o.prob) {
return -1;
}
return 0;
}

@Override
public String toString() {
StringBuilder stb=new StringBuilder();
stb.append("(").append(ch).append(",").append(prob).append(")");
return stb.toString();
}

}



4.应用

此算法基于HMM实现,适合用于语音识别等纠错场景,当然对于这种隐藏状态和可见状态并存的模型,这都是适用的。


  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值