马尔可夫算法

学习《程序设计实践》第三章。

把输入想像成由一些互相重叠的短语构成的序列,该算法把每个短语分成两部分:一部分由多个词构成的前缀,另一部分是只包含一个词的后缀。马尔可夫链算法能够生成输出短语的序列,其方法是依据(在我们的情况下)原文本的统计性质,随机性地选择跟在前缀后面的特定后缀。采用三个词的短语就能够工作得很好——利用连续两个词构成的前缀来选择作为后缀的一个词:
设置w1和w2为文本的前两个词
输出w1和w2
循环:
  随机地选出w3,它是文本中w1w2的后缀中的一个
  打印w3
  把w1和w2分别换成w2和w3
  重复循环


选择二词前缀,则每个输出词w3都是根据它前面的一对词(w1,w2)得到的。前缀中词的个数对设计本身并没有影响,程序应该能对付任意的前缀长度。我们把一个前缀和它所有可能后缀的集合放在一起,称其为一个状态。


数据结构选择:

对于后缀,我们需要在输出时随机选择一个,考虑用List或Set为容器(代码中用List)。对于前缀,我们需要快速查找,并每个前缀对应一系列的后缀,考虑用Map储存,因其可以产生<key,value>键值对。

即:

Map<Prefix,List<String>> stateTable = new HashMap<Prefix,List<String>>();

 

1.前缀以类Prefix表示,类Prefix有一个属性:List<String> pref; 前缀保存的两个词,w1,w2按顺序存入。

Prefix有两个构造方法:Prefix(int npref, String word)用于把npref个的word复制到pref。

Prefix(Prefix prefix)用于把prefix复制到当前实例。

另外重写了Prefix的hashCode和equals方法,以便于作为Map的key。

package chapter3;

import java.util.ArrayList;
import java.util.List;

/**
 * 保存前缀向量的词
 * @author bosshida
 *
 */
public class Prefix {
	public List<String> pref;

	//n copies of str
	public Prefix(int npref, String word) {
		pref = new ArrayList<String>();
		for(int i=0; i<npref; i++){
			pref.add(word);
		}
	}

	// duplicate existing prefix
	public Prefix(Prefix prefix) {
		this.pref = new ArrayList<String>(prefix.pref);
	}
	
	private static final int MULTIPLIER = 31; //for hashcode()
	public int hashCode(){
		int h = 0;
		for(int i=0; i<pref.size(); i++){
			h = h*MULTIPLIER + pref.get(i).hashCode();
		}
		return h;
	}
	
	//compare two prefixes for equal words
	public boolean equals(Object o){
		Prefix p = (Prefix)o;
		for(int i=0; i<pref.size(); i++){
			if(!pref.get(i).equals(p.pref.get(i))){
				return false;
			}
		}
		return true;
	}

}

 

2.类Chain,用于读取输入、构造散列表并产生输出。

类内有build(InputStream in)和generate(int nwords)方法,build()用于从输入流产生状态表(stateTable,也就是散列表Map),generate()用于产生输出。

package chapter3;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;

/**
 * 读取输入,构造散列表,产生输出
 * @author bosshida
 *
 */
public class Chain {
	static final int NPREF = 2; //size of prefix
	static final String NOWORD = "\n"; //word that can't appear
	Map<Prefix,List<String>> stateTable = new HashMap<Prefix,List<String>>();//key=prefix,value=suffix list;
	Prefix prefix = new Prefix(NPREF,NOWORD);//initial prefix
	Random random = new Random();
	
	//chain build:build state table from input stream
	public void build(InputStream in) throws Exception {
		Scanner scanner = new Scanner(in);
		
		while(scanner.hasNext()){
			add(scanner.next());
		}
		add(NOWORD);
	}

	private void add(String word) {
		List<String> suf = stateTable.get(prefix);
		if(suf == null){
			suf = new ArrayList<String>();
			stateTable.put(new Prefix(prefix),suf);
		}
		suf.add(word);
		prefix.pref.remove(0);
		prefix.pref.add(word);
	}

	//chain generate: generate output words
	public void generate(int nwords) {
		prefix = new Prefix(NPREF,NOWORD);
		for(int i=0; i<nwords; i++){
			List<String> suf = stateTable.get(prefix);
			int r = Math.abs(random.nextInt() % suf.size());
			String word = suf.get(r);
			if(word.equals(NOWORD)){
				break;
			}
			System.out.print(word+" ");
			prefix.pref.remove(0);
			prefix.pref.add(word);
		}
	}
	
}

 

3.公共接口类Markov。main。

package chapter3;

import java.io.File;
import java.io.FileInputStream;

/**
 * 马尔可夫算法
 * @author bosshida
 *
 */
public class Markov {
	private static final int MAXGEN = 1000; //max words generated
	
	public static void main(String[] args) throws Exception {
		Chain chain = new Chain();
		int nwords = MAXGEN;
		FileInputStream fis = new FileInputStream(new File("e:/Book/alan.txt"));
		
		chain.build(fis);
		chain.generate(nwords);
	}
	
}

 

以上程序在一本20万个英文单词的书为输入,产生1000字的输出。输出结果单句长度太长,而且语法很多错误,不过也是至少比随机性的输出有规律。


对于后续的探究。

本代码中,后缀以List保存,会有重复词的出现,重复词相当于这些词的权重加大,在输出的出现的机率增加,可能这是有益的。或者想各词出现的概率尽量一至,可考虑用Set保存后缀词。


为产生的句子不过于太长,可在建立状态表stateTable时,增加有标点的单词的权重(保存的后缀词是带标点符号的)。为增加权重可采取方法有:(1)单词有标点时,可重复增加该单词,以增大该单词的出现概率。(2)可修改Map的结构,value改为用:Suffix类,包含String word, int weight。不过这样改随机取词的方法要修改。


另外可以试下中文词语,有空再试吧。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值