「恋するプログラム」のMac向け読み替え その3
その2に続き、Macユーザーが恋するプログラム
を勉強する上で、読み替えや説明が必要だったことをメモして行く。
-
CHAPTER 7-1
サンプル(KOISURU_PROGRAM/sample/study)を動かす上での変更は、その1のCHAPTER 3-2での読み替えで作成したproto.rb
を除き、これまでやってきたものと同じである。proto.rb
では、学習結果の保存とログ機能追加のため、以下の変更を行う。- proto.rbの変更点
-
--- a/proto.rb 2017-01-20 00:49:46.000000000 +0900 +++ b/proto.rb 2017-01-20 21:23:05.000000000 +0900 @@ -5,14 +5,33 @@ return unmo.name + ':' + unmo.responder_name + '> ' end +def putlog(log) + @log.push(log) +end + puts('Unmo System prototype : proto') proto = Unmo.new('proto') + +# ログ +@log = ["Unmo System : #{proto.name} Log -- #{Time.now}"] + while true print('> ') input = gets input.chomp! + putlog('> ' + input) break if input == '' response = proto.dialogue(input) - puts(prompt(proto) + response) + + withPrompt = prompt(proto) + response + puts(withPrompt) + putlog(withPrompt) end + +proto.save + +open('log.txt', 'a') do |f| + f.puts(@log) + f.puts +end
-
CHAPTER 7-2
この章以降、形態素解析というものを使うようになる。本を読めば詳しく説明してあるが、いきなりここに来て何の事か分からない方は、こちらを見て欲しい。恋するプログラム
では、茶筅(ちゃせん)
を形態素解析エンジンにしているが、筆者は和布蕪(めかぶ)
を用いることにした。何故そうしたのかはよく覚えていない。多分、こちらの記事がきっかけだったような気がする。
後から知ったのだが、Macに茶筅(ちゃせん)をインストールすることもできるようだ。-
和布蕪(めかぶ)のインストール
インストール手順はこちらの手順に従えば良い。ただしファイルの入手先がリンク切れしている。ファイルは以下からダウンロードする。 -
mecab.rbの作成
こちらの記事では、natto
というgemを導入しているが、筆者はMeCab本家謹製のMeCab-Ruby
を用いた。- mecab.rbの内容
-
require 'MeCab' module MeCab def setarg(opt) @mecab = MeCab::Tagger.new(opt) end def analyze(text) @mecab.parse(text) end module_function :setarg, :analyze end # $0は、実行しているスクリプトファイル名 # __FILE__は、現在のファイル名 # require時は両者は異なるが、単体実行時は同じになる。 # すなわち、モジュール単体テスト用のスクリプトをこのブロック内に書く if $0 == __FILE__ # 出力フォーマットを指定して和布蕪を作る MeCab::setarg('-F%m\s%F-[0,1,2]\n') # 標準入力を取得 while line = gets() do line.chomp! break if line.empty? # 解析する puts MeCab.analyze(line) end end
-
mecab.rbのテスト
本にあるあたしはプログラムの女の子です
の解析をテストしてみる。- mecab.rbのテスト
-
$ ruby mecab.rb あたしはプログラムの女の子です あたし 名詞-代名詞-一般 は 助詞-係助詞 プログラム 名詞-サ変接続 の 助詞-連体化 女の子 名詞-一般 です 助動詞 EOS
オッケーだ。
-
-
CHAPTER 7-3
サンプルKOISURU_PROGRAM/sample/study2)を動かす上での変更のうち、文字コード変更等の既に説明した内容は省いて説明する。-
morph.rbの作成
morph.rbも本の通りchasen.rbではなくmecab.rbを利用するものに書き換える。- morph.rbの内容
-
require_relative 'mecab' module Morph def init_analyzer # %m\s%F-[0,1,2]\tは、 # 単語と品詞をスペース(\s)で区切ってグループ化し、タブ文字(\t)でグループ # を区切るというフォーマットを設定している。品詞は0,1,2番目まで表示する MeCab::setarg('-F%m\s%F-[0,1,2]\t'); end def analyze(text) # MeCab::analyze(text)の結果は形態素と品詞をスペースで区切ったもの。形態素ごとにタブで区切られている # gsub!(/EOS$/,'')で解析結果の最後にある「EOS」を除去 # chompで改行除去 # split(/\t/)でタブを区切りに分割した配列を作る # mapで、その配列の全要素に対してpart.split(/\s/)を実行することで、 # 要素からスペースを区切りに分割した配列を作る。 # つまり、形態素と品詞の配列を要素とする配列が戻り値となる return MeCab::analyze(text).gsub!(/EOS$/,'').chomp.split(/\t/).map do |part| part.split(/\s/) end end def keyword?(part) return /名詞-(一般|固有名詞|サ変接続|形容動詞語幹)/ =~ part end module_function :init_analyzer, :analyze, :keyword? end
-
dictionary.rb dictionaryクラス study_patternメソッドとPatternItemクラス add_phraseメソッド
サンプルからの変更はないが、コード理解のためのポイントをメモしておく。- study_patternメソッド
-
def study_pattern(input, parts) # unmo.rbでpartsに代入された形態素解析結果は配列が要素になっている。 # 「word, part」の2つのブロックパラメータで、その配列の要素を、それぞれに代入する。 parts.each do |word, part| # Morphモジュールのkeyword?メソッドに合致しなければ、 # つまり、キーワードでなければ飛ばす。 next unless Morph::keyword?(part) # Regexp::quote(word)は、wordに含まれる正規表現の特殊文字をエスケープする。 # それが元のwordと一致しない場合は次へ飛ばす。 # つまり、正規表現の時は次へ飛ばしている?(本には記述がない) next if Regexp::quote(word) != word # @patternはパターン辞書を読み込んだ配列 # findはブロック(ptn_item.pattern == word)の条件を満たす # ものを探す。 duped = @pattern.find{|ptn_item| ptn_item.pattern == word} if duped # add_phraseメソッドは次項参照 duped.add_phrase(input) else # PatternItem.newによって、PatternItemクラスのinitialize # メソッドが自動的に呼ばれる。 # PatternItemクラスのinitializeメソッドは(pattern, phrases)の # 引数を持っている。 # よって、パターン辞書1行分の@phrasesが作られる。 @pattern.push(PatternItem.new(word, input)) end return end end
- add_phraseメソッド
-
def add_phrase(phrase) # pは@phrasesの一要素、つまりneedとphraseからなるハッシュ # よってp['phrase']は、ハッシュのphraseを取り出したもの。 # それが、引数と一致するかチェックしている。 return if @phrases.find{|p| p['phrase'] == phrase} # 一致しなければ追加 @phrases.push({'need'=>0, 'phrase'=>phrase}) end
-
dictionary.rb dictionaryクラス saveメソッドとPatternItemクラス make_lineメソッド
同様にサンプルからの変更はないが、コード理解のためのポイントをメモしておく。- saveメソッド
-
def save open('dics/random.txt', 'w') do |f| f.puts(@random) end open('dics/pattern.txt', 'w') do |f| # make_lineメソッドは下を参照 @pattern.each{|ptn_item| f.puts(ptn_item.make_line)} end end
- make_lineメソッド
-
def make_line # @modifyは機嫌変動値。@patternはパターン pattern = @modify.to_s + "##" + @pattern # 応答例のハッシュの配列(@phrases)を必要機嫌値needと応答例phrase # を「##」で区切ったものの配列に分解 phrases = @phrases.map{|p| p['need'].to_s + "##" + p['phrase']} # phrases.join('|')で、配列phrasesを'|'で区切った文字列にする。 # patternと上記をタブで区切ってパターン辞書1行分のデータとなる。 return pattern + "\t" + phrases.join('|') end
-
proto.rbの変更
Morphモジュールの初期化(つまり和布蕪の初期化)を追加する。- proto.rbの変更点
-
--- a/proto.rb 2017-01-21 16:28:58.000000000 +0900 +++ b/proto.rb 2017-01-21 16:30:52.000000000 +0900 @@ -12,6 +12,9 @@ puts('Unmo System prototype : proto') proto = Unmo.new('proto') +# 形態素解析モジュールの初期化 +Morph::init_analyzer + # ログ @log = ["Unmo System : #{proto.name} Log -- #{Time.now}"]
-
-
CHAPTER 7-4
サンプルKOISURU_PROGRAM/sample/study3)を動かす上での変更を説明する。-
文字コードの変換
文字コードの変換はコマンドラインでもできる。
ただし「¥」や「~」など、置き換えが必要な文字もあるので、テキストエディタで修正する。iconv -f SJIS -t UTF8 SJIS.txt > UTF8.txt
-
改行コードの変換
改行コードの変換はコマンドラインでもできる。perl -pe 's/\r//' dos.txt > dos2unix.txt
-
文字、改行コード両方の変換
どちらも変換する場合は、以下のようにする。iconv -f SJIS -t UTF8 SJIS.txt | perl -pe 's/\r//' > dos2unix.txt
-
dictionary.rbの誤記訂正
テンプレート辞書読み込み部に誤記と思われる部分があったので訂正する。- dictionary.rbの訂正内容
-
--- a/dictionary.rb 2017-01-21 19:41:14.000000000 +0900 +++ b/dictionary.rb 2017-01-21 19:42:03.000000000 +0900 @@ -24,7 +24,7 @@ open('dics/template.txt') do |f| f.each do |line| count, template = line.chomp.split(/\t/) - next if count.nil? or pattern.nil? + next if count.nil? or template.nil? count = count.to_i # (2) @template[count] = [] unless @template[count] @template[count].push(template)
-
dictionary.rb dictionaryクラス initializeメソッド(抜粋)
テンプレート辞書読み込み部のコード理解のためのポイントをメモしておく。- initializeメソッド(抜粋)
-
def initialize (一部省略) @template = [] open('dics/template.txt') do |f| f.each do |line| # 読み込んだ行をタブで分割した配列を作り、その要素を多重代入 count, template = line.chomp.split(/\t/) # count、templateどちらかがnilなら飛ばす next if count.nil? or template.nil? count = count.to_i # @template[count]がfalse、つまり配列要素(の配列)が存在しない時 # に空配列で初期化 @template[count] = [] unless @template[count] @template[count].push(template) end end end
-
dictionary.rb dictionaryクラス study_templateメソッド
テンプレート辞書学習部のコード理解のためのポイントをメモしておく。- study_templateメソッド
-
def study_template(parts) template = '' count = 0 # unmo.rbでpartsに代入された形態素解析結果は配列が要素になっている。 # 「word, part」の2つのブロックパラメータで、その配列の要素を、それぞれに代入する。 parts.each do |word, part| # partがキーワードの時、wordを置き換えてカウントアップ if Morph::keyword?(part) word = '%noun%' count += 1 end # キーワードだけ置き換えた文を生成 template += word end # count > 0が偽の場合、つまり0の時はリターン return unless count > 0 # @template[count]がfalse、つまり配列要素(の配列)が存在しない時 # に空配列で初期化 @template[count] = [] unless @template[count] # include?メソッドが偽の場合、つまり重複していない場合に登録 unless @template[count].include?(template) @template[count].push(template) end end
-
dictionary.rb dictionaryクラス saveメソッド(抜粋)
テンプレート辞書学習部のコード理解のためのポイントをメモしておく。- saveメソッド(抜粋)
-
def save (一部省略) open('dics/template.txt', 'w') do |f| # each_with_indexで配列要素とインデックスをブロックパラメータにする @template.each_with_index do |templates, i| next if templates.nil? # templates配列に対して templates.each do |template| # インデックスとテンプレートのタブ区切りテキストを生成 f.puts(i.to_s + "\t" + template) end end end end
-
responder.rb TemplateResponderクラス
テンプレート応答生成部のコード理解のためのポイントをメモしておく。- TemplateResponderクラス
-
class TemplateResponder < Responder def response(input, parts, mood) keywords = [] # unmo.rbでpartsに代入された形態素解析結果は配列が要素になっている。 # 「word, part」の2つのブロックパラメータで、その配列の要素を、それぞれに代入する。 parts.each do |word, part| # キーワードの場合keywords配列に追加 keywords.push(word) if Morph.keyword?(part) end # キーワードの数 count = keywords.size # templatesに代入しつつ空かどうかをチェック if count > 0 and templates = @dictionary.template[count] # templatesからランダムに選択 template = select_random(templates) # keywords.shiftは、keywords配列の先頭の要素を外す # gsubのブロック呼び出しgsub(/a/){}では、 # 「%noun%」が登場するたびに{}が実行される return template.gsub(/%noun%/){keywords.shift} end return select_random(@dictionary.random) end end
-
今回は、盛り沢山で長くなった。その4に続く。
この投稿へのトラックバック
トラックバックはありません。
- トラックバック URL
この投稿へのコメント