続・ひらがなの状態遷移図

前回のGraphvizでの状態遷移図の出力時に

:rankdir => "LR"

というオブションをつけていたのだが、これをやると帰ってこない(がんばって配置しようとして終わらない?)らしくこれを外せばよさげ。

以上のことを踏まえて青空文庫からとってこれるだけの文章を集めて状態遷移表(図)を作る。
前準備としてSiteSuckerをもちいてサイト全体をローカルに引っ張ってくる。

青空文庫にあるテキストデータは実際には

http://www.aozora.gr.jp/cards/****/files/*****.html

にあるHTMLファイルにあるので、これを解析すればよさそう。特に今回は”ひらがな”だけなのでHTMLをパースする必要もなくただひらがなだけ集めてくればいい。
注意点として青空文庫のHTMLファイルはすべてSJISになっているらしく、今回の環境ではUTF-8でやったのでこの変換作業が必要になる。
手順としては

  1. htmlファイルの探索
  2. 漢字/カタカナを含むSJISのhtmlファイルをEUC-JPのひらがなからなるファイルに変換
  3. EUC-JP -> UTF-8への変換
  4. それぞれのファイル(文章)から平仮名のみを抽出->配列の生成
  5. あるひらがなからあるひらがなへの遷移数をカウント
  6. すべての文章の遷移数を合計、そこから遷移確率の算出
  7. グラフに出力

それぞれ1から3までをhira_analyze.rb, 4から6までをhira_map_generator.rb,7をhira_graph.rbが担当。
2ではkakasi, 3ではnkf, 4ではmojiモジュール,7ではGraphvizを使ってこれを行った。

ちなみにGraphvizで図を出力する際にものすごい時間がかかったので1%以上の遷移確率がないエッジについては省略した。
それでもできた画像は32767x4291、56.7MB。そして生成に106ぷんかかった。
一応これがzip圧縮したファイル


require 'nkf'

p "***** searching for directory *****"

data_dir = "./cards"
kakasi_output_dir = "./kakasi_euc_out"
utf8_file_dir = "./utf8_output"

origin_file_array = Array.new

Dir.foreach(data_dir) do |ent| # ./cards/****
	if ent.to_s[0].chr == "." then
		next
	end
	
	child_dir = data_dir + "/" + ent
	
	if !File.directory? child_dir then
		next
	end

	Dir.foreach(child_dir) do |child_ent| # ./cards/----/****
		unless child_ent == "files" then
			next
		end

		files_dir_path = child_dir + "/" + child_ent

		Dir.foreach(files_dir_path) do |file_ent| #./cards/----/files/****
			if file_ent.to_s[0].chr == "." then
				next
			end

			unless file_ent.include?(".html") then
				next
			end

			child_ent_path = files_dir_path + "/" + file_ent

			origin_file_array << child_ent_path
		end
	end
end


p "Total #{origin_file_array.length} files found"



p "***** kakasi proccessing *****"
p " converting sjis kanji, katakana included text to euc hiragana text"
unless File.exists?(kakasi_output_dir) then
	Dir.mkdir(kakasi_output_dir)
end

origin_file_array.each do |file_to_kakasi|
	filename = File.basename(file_to_kakasi)
	output_file_name = kakasi_output_dir + "/" + filename


	print "kakasi processing : #{file_to_kakasi} > #{output_file_name}"
	system("kakasi -JH -KH -i sjis -o euc < #{file_to_kakasi} > #{output_file_name}")
	print " ... done\n"

end

p "done kakasi : kanji katakana sjis -> hiragana euc converting"



p "***** euc->utf8 converting *****"

unless File.exists?(utf8_file_dir) then
	Dir.mkdir(utf8_file_dir)
end

Dir.foreach(kakasi_output_dir) do |file|
	orig_file_path = kakasi_output_dir + "/" + file
	orig_file_name = File.basename(file)
	if orig_file_name[0].chr == "." then
		next
	end

	utf8_file_path = utf8_file_dir + "/" + orig_file_name
	print "processing #{orig_file_path} -> #{utf8_file_path}"
	
	File.open(orig_file_path) do |eucfile|
		tmp = eucfile.read
		utf8_tmp = NKF.nkf('-w',tmp)
		
		File.open(utf8_file_path,"w") do |utf8_file|
			utf8_file.puts utf8_tmp
		end
	end
	print " .. done\n"
end

$KCODE = "UTF8"

require "rubygems"
require "moji"
require 'graphviz'

data_dir = "./utf8_output" #

hiraganas = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわをんぁぃぅぇぉゃゅょがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽっ".split(//)


$hira_to_num = Hash.new

i = 0

hiraganas.each do |char|
	$hira_to_num[char] = i
	i+=1
end

$hiragana_num = hiraganas.length
p $hiragana_num

def file_to_hiragana_list(file_path)
	data_array = nil;
	File.open(file_path) do |file|
		data = file.read 
		data_array = data.split("")

		data_array.delete_if do |char|
			if !Moji.type?(char, Moji::ZEN_HIRA) then
				true
			else
				false
			end
		end
	end

	return data_array
end


def count_in_array(array)
	return_array = Array.new($hiragana_num){|i|
		Array.new($hiragana_num) {|i|
			0.0
		}
	}

	if array == nil then
		return return_array
	end
	
	limit = array.length() -1


	0.upto(limit) do |n|
		current_char = array[n]
		next_char = array[n+1]
		
		current_num = $hira_to_num[current_char]
		next_num = $hira_to_num[next_char]

		if current_num == nil || next_num == nil then
			next
		end
		i = return_array[current_num][next_num]
		i += 1
		return_array[current_num][next_num] = i
	end

	return return_array

end
		
def merge_array(base_array, array_to_add)
	limit = $hiragana_num - 1
	0.upto(limit) do |n|
		0.upto(limit) do |m|
			base_array[n][m] += array_to_add[n][m]
		end
	end
end



def generate_p_array(array)
	p_array = count_in_array(nil)

	limit = $hiragana_num -1
	0.upto(limit) do |n|
		total = 0
		0.upto(limit) do |m|
			total += array[n][m]
		end
		0.upto(limit) do |m|
			if total == 0 then
				p_array[n][m] = 0
			else
				p_array[n][m] = array[n][m] / total
			end
		end
	end

	return p_array
end




p "***** HIRAGANA RIST *****"
p $hira_to_num


p "***** Text -> hash in hash *****"

hira_map = count_in_array(nil)

i = 0

Dir.foreach(data_dir) do |entry_name|
	if entry_name[0].chr == "." then
		next
	end

	target_file_path = data_dir + "/" + entry_name
	
	print "processing : #{target_file_path}\n"

	print "array -> hiragana list "
	array = file_to_hiragana_list(target_file_path)
	print "-> counter hash "
	count_array = count_in_array(array)
	print "-> merging hash "
	merge_array(hira_map, count_array)

	p "-> done"
	i+=1
end

p "***** saving counter *****`"
File.open("counter.yml","w") do |file|
	file.write YAML.dump(hira_map)
end



p " ... done"
p " ***** generating probability array *****"
p_array = generate_p_array(hira_map)

p "***** Probability array is like this *****"
p p_array

p "***** YAMLizing map *****"
File.open("hiragana_map","w") do |file|
	file.write YAML.dump(p_array)
	file.flush
end

p "DONE ALL #{i} files"


$KCODE = "UTF8"

require "rubygems"
require "moji"
require "graphviz"


$hiraganas = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわをんぁぃぅぇぉゃゅょがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽっ".split(//)


$hira_to_num = Hash.new

i = 0

$hiraganas.each do |char|
	$hira_to_num[char] = i
	i+=1
end

$hiragana_num = $hiraganas.length

p_array = Array.new

File.open("./hiragana_map") do |file|
	tmp_str = file.read
	p_array = YAML.load(tmp_str)
end

GraphViz::new("G", 
							{
	:type => "digraph",
	:use => "dot",
	:output => "png",
	:charset => "utf8",
	:file => "hiragana.png"
}){|g|
	g.node[:fontname] = "osaka"
	g.node[:style] = "filled"
	g.edge[:fontname] = "osaka"

	limit = $hiragana_num - 1

	$hiraganas.each do |char|
		n = $hira_to_num[char]
		origin = g.add_node(char)	
		0.upto(limit) do |m|
			target_char = $hiraganas[m]
			target = g.add_node(target_char)
			if p_array[n][m] > 0.01 then
				g.add_edge(origin, target, :label => p_array[n][m].to_s )
			end
		end
	end
}.output()