形態素解析&マルコフ連鎖で文章の自動生成に挑戦

文章の自動生成

文章をコンピュータに自動的に生成させるという試みは様々な場所で行われているようで、本格的に作るなら文法を守ったり文脈やストーリーなどを意識して文章を作るということをしなければなりませんが、今回は一番簡単にできる文章生成にチャレンジしてみます。

 

今回作るのは、人気botのしゅうまい君に使われている技術です。


 

しゅうまい君は、大量のtwitterユーザーのツイートを読み込み、そのツイートの単語を適当に組み合わせて新たなツイートを生成しています。

おそらく、しゅうまい君は今回紹介するアルゴリズムを改良したものを使っていると思いますが、ベースとなる技術は同じだと思われます。


アルゴリズムの流れ

ある文章のデータを形態素解析によって語句毎に分解し、マルコフ連鎖で語句を繋ぎ合わせて文章を再構築させるというものです。


形態素解析

形態素解析とは、文章を形態素という単位まで細かく分解することです。

例えば、「庭には二羽ニワトリがいる。」という文だと、「庭/に/は/二/羽/ニワトリ/が/いる/。」といった感じで分割されるようです。

形態素解析には、ヤフーのテキスト解析APIを使用。回数等の制限はありますが、試しに使ってみる程度なら大丈夫でした。

http://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html

 

使い方は、HTTPリクエストを送るとレスポンスとしてXML形式で結果が返ってくるので、それを読み取って活用します。


マルコフ連鎖

マルコフ連鎖をWikiPediaで調べてみると、「マルコフ連鎖は、未来の挙動が現在の値だけで決定され、過去の挙動と無関係である(マルコフ性)。」と出てきます。

今回は、このマルコフ連鎖を用いて文章自動生成を行います。

 

文章を生成するときに、ある形態素に繋げられそうな形態素を選んで徐々に繋げていき文章を作っていきます。

ある形態素というのが、マルコフ連鎖の定義で言うところの現在の値で、次に繋げられそうな形態素というのが未来の挙動です。

 

例えば、「あんずちゃん/の/SSR/が/欲しい/ので/、/バイト/を/初めて/お金/を/稼ごう/と/思います/。」このような文章が与えられているとします。(本来はもっと大量の文章を与えます。)

このとき、「を」という形態素に繋がりそうな形態素は2つありますね。「初めて」と「稼ごう」です。

この繋がりそうな2つの形態素の中から、繋げる形態素を選びます。

実際にはもっと大量の文章が与えられるため、繋がりそうな形態素の数も増えるはずです。


javaでのサンプルとなります。

// URLを生成
String urlstr = "http://jlp.yahooapis.jp/MAService/V1/parse?" +
"appid=" + this.appid + /* ヤフーテキスト解析APIで取得したID */
"&sentence=" + URLEncoder.encode(sentence, "UTF-8") +
"&response=surface,pos" +
"&filter=" +
"&results=ma";

// 生成したURLでYahooAPIへリクエストを送り、xml形式で返ってくる結果を取得
URL url = new URL(urlstr);
InputStream input = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input,"UTF-8"));

// マルコフ連鎖辞書型(自作クラス)
private MarkovDictionary dic = new MarkovDictionary();
// xmlを解析
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new ByteArrayInputStream(reader.readLine().getBytes("UTF-8"))));

NodeList wordlist = doc.getElementsByTagName("word_list").item(0).getChildNodes();
for(int i=0; i<wordlist.getLength(); i++){
    Node node = wordlist.item(i);
    NodeList nodelist = node.getChildNodes();

    String str = nodelist.item(0).getFirstChild().getNodeValue();
    String type = nodelist.item(1).getFirstChild().getNodeValue();
    dic.add(str, type);
}

System.out.println(dic.buildSentence());

以下は、マルコフ連鎖で文を作る部分。

import java.util.ArrayList;

class WordSet {
	String str;
	String type;
	public WordSet(String str, String type){
		this.str = str;
		this.type = type;
	}
	public String toString(){
		return "["+str+","+type+"]";
	}
}

class MarkovNode {
	WordSet[] word = new WordSet[3];
	public MarkovNode(WordSet wordset1, WordSet wordset2, WordSet wordset3){
		word[0] = wordset1;
		word[1] = wordset2;
		word[2] = wordset3;
	}
}

public class MarkovDictionary {
	
	final int maxSentenceNum = 200;
	
	ArrayList<MarkovNode> dic = new ArrayList<MarkovNode>();
	
	ArrayList<WordSet> tmp = new ArrayList<WordSet>();
	
	// マルコフ辞書に3つ並んだ形態素を登録
	public void add(String str, String type){
		tmp.add(new WordSet(str, type));
		if(tmp.size() == 3){
			dic.add(new MarkovNode(tmp.get(0), tmp.get(1), tmp.get(2)));
			tmp.remove(0);
		}
	}
	
	// マルコフ連鎖で文章を生成
	public String buildSentence(){
		String sen = "";
		ArrayList<WordSet> sen3 = new ArrayList<WordSet>();
		// 最初の3つの形態素はランダムに決める
		MarkovNode tmpNode = dic.get((int)(dic.size()*Math.random()));
		sen3.add( tmpNode.word[0] );
		sen3.add( tmpNode.word[1] );
		sen3.add( tmpNode.word[2] );
		
		// 最大値に達するまで形態素を繋げて文章を生成
		for(int i=0;i<maxSentenceNum;i++){
			String addSen = find2Match(sen3.get(1).str, sen3.get(2).str);
			if(addSen == null) break;
			sen += sen3.get(0).str;
			sen3.remove(0);
			sen3.add(new WordSet(addSen,""));
		}
		
		sen += sen3.get(0).str + sen3.get(1).str + sen3.get(2).str;
		
		return sen;
	}
	
	// 繋げられそうな次の形態素を探す
	// 2重マルコフ連鎖なので、2つの形態素を用いる
	private String find2Match(String str1, String str2){
		ArrayList<String> ansList = new ArrayList<String>();
		for(int i=0; i<dic.size(); i++){ if(dic.get(i).word[0].str.equals(str1) && dic.get(i).word[1].str.equals(str2)){ ansList.add( dic.get(i).word[2].str ); } } if(ansList.size() > 0){
			return ansList.get( (int)(ansList.size()*Math.random()) );
		}
		else return null;
	}
	
	public void show(){
		for(int i=0; i<dic.size(); i++){
			System.out.println(dic.get(i).word[0].toString() + ", " + dic.get(i).word[1].toString() + ", " + dic.get(i).word[2].toString());
		}
	}

}

 

とまあ、こんな感じで、簡単な文章作成ができました。

頑張れば、ブログの記事を自動で書いてくれるようなものを作ることができるかもしれませんね。

 


コメントを残す