第6回北海道開発オフに参加してきた!
謹賀新年。昨年のうちに書けなかったことを、休みのあいだに書いてしまおうというわけです。
今回もモクモクと書いてきました。日本語の動詞を活用するためのクラスの叩き台です。
背景
今回は@niku_name氏が取り組んでましたが、日本語を分析する手法として、私たちはすでに、形態素解析という方法を手にしています。形態素解析では、以下のように、文を単語に分割することができます。
もとの文:「このように、形態素に分割することができます。」 この この コノ 連体詞 よう よう ヨウ 名詞-非自立-助動詞語幹 に に ニ 助詞-副詞化 、 、 、 記号-読点 形態素 形態素 ケイタイソ 名詞-一般 に に ニ 助詞-格助詞-一般 分割 分割 ブンカツ 名詞-サ変接続 する する スル 動詞-自立 サ変・スル 基本形 こと こと コト 名詞-非自立-一般 が が ガ 助詞-格助詞-一般 でき できる デキ 動詞-自立 一段 連用形 ます ます マス 助動詞 特殊・マス 基本形 。 。 。 記号-句点
しかし、その分割した結果を自由自在に再構成することは、今のところ困難です。私たちは、「できません」という表現を「でき」+「ませ」+「ん」に分割し「できる」「ます」「ん」という原形を得ることはできますが、「できる」「ます」「ん」の組をくっつけようとしても、そのままだと「できるますん」になってしまいます。正しくつなげるためには、操作の過程で、「できる」を「でき」に、「ます」を「ませ」に活用する必要があります。
よくあるマルコフ連鎖を使っているボットなどでは、活用する品詞である動詞などについて、その活用した結果のみ(「歩か」や「見れ」など)をデータとして持っているために、その問題は解消されています。ただし、この手法では、「丁寧な表現」や「否定の表現」といった意味表現についての操作の自由度がなくなってしまいます。「できる」という動詞に「否定」「丁寧」という意味を加えて「できません」を得る、というような操作をプログラム上で実現するには、この問題は解決されなくてはいけません。
そんなわけで、今回は動詞の活用をするためのクラスをRubyで書いてみました。ただし、CaboCha::Token型の品詞情報が得られていることが前提。動詞について、文字列としての情報(「歩く」「見る」など)と、活用型の情報(五段・一段・カ変・サ変)がわかっていれば、あとに続く助動詞に合わせて的確に活用できるというものです。
verb.rb
#!/usr/bin/ruby class Verb def initialize(token) if token.pos !~ /^動詞/ then @stem = '' @conj = '' end if token.ctype[/^五段/] then @stem = token.base.split(//u)[0..-2].join @conj = Conj.new('godan', token.base.split(//u)[-1]) elsif token.ctype[/^一段/] then @stem = token.base.split(//u)[0..-2].join @conj = Conj.new('ichidan') elsif token.ctype[/^サ変/] then if token.base[/する$/] then @stem = token.base.sub(/する$/, '') @conj = Conj.new('sahen', 'する') elsif token.base[/ずる$/] then @stem = token.base.sub(/ずる$/) @conj = Conj.new('sahen', 'ずる') else @stem = '' @conj = '' end elsif token.ctype[/^カ変/] then if token.base[/来る$/] then @stem = token.base.sub(/来る$/, '') @conj = Conj.new('kahen', '来る') elsif token.baes[/くる$/] then @stem = token.base.sub(/くる$/, '') @conj = Conj.new('kahen', 'くる') else @stem = '' @conj = '' end else @stem = '' @conj = '' end end def auxil_conjugate(token) if token.pos !~ /^助動詞/ then @stem + @conj.shushi end if ['ない', 'ぬ', 'ん'].include?(token.base) then @stem + @conj.mizen elsif ['たい', 'ます'].include?(token.base) then @stem + @conj.renyou elsif ['まい', 'らしい'].include?(token.base) then @stem + @conj.shushi elsif token.base == 'う' then @stem + @conj.mizen_u elsif token.base == 'た' then @stem + @conj.renyou_ta # このままだと連濁「だ」ができない else @stem + @conj.shushi end end end class Verb::Conj Godan = [ ['す', 'そ', 'さ', 'せ', 'せ', 'し', 'し'], ['つ', 'と', 'た', 'て', 'て', 'っ', 'ち'], ['る', 'ろ', 'ら', 'れ', 'れ', 'っ', 'り'], ['う', 'お', 'わ', 'え', 'え', 'っ', 'い'], ['ぬ', 'の', 'な', 'ね', 'ね', 'ん', 'に'], ['む', 'も', 'ま', 'め', 'め', 'ん', 'み'], ['ぶ', 'ぼ', 'ば', 'べ', 'べ', 'ん', 'び'], ['ぐ', 'ご', 'が', 'げ', 'げ', 'い', 'ぎ'], ['く', 'こ', 'か', 'け', 'け', 'い', 'き'], ] Ichidan = ['る', '', '', 'れ', ['ろ', 'よ'], '', ''] Sahen_s = ['する', ['さ', 'し', 'せ'], ['さ', 'し', 'せ'], 'すれ', ['しろ', 'せよ'], 'し', 'し'] Sahen_z = ['ずる', ['ざ', 'じ', 'ぜ'], ['ざ', 'じ', 'ぜ'], 'ずれ', ['じろ', 'ぜよ'], 'じ', 'じ'] Kahen_h = ['くる', 'こ', 'こ', 'くれ', 'こい', 'き', 'き'] Kahen_k = ['来る', '来', '来', '来れ', '来い', '来', '来'] def initialize(type, cons = nil) @type = type @cons = if @type == 'godan' then Godan.assoc(cons) elsif @type == 'ichidan' then Ichidan elsif @type == 'sahen' then if cons == 'する' then Sahen_s else Sahen_z end elsif @type == 'kahen' then if cons == 'くる' then Kahen_h else Kahen_k end end end def shushi @cons[0] end def mizen_u @cons[1] end def mizen @cons[2] end def katei @cons[3] end def meirei @cons[4] end def renyou_ta @cons[5] end def renyou @cons[6] end end
サ変以外はほぼ活用できる予感。まだ音便の処理は組み込んでいません。ケーススタディが不足していますが、思うにAPIに問題があるのかなと。助動詞などを引数として与えるより、「過去」とか「推量」とかの意味素性をメソッドに割り当てるほうが現実的かもしれませんが、接続のパターンが多様すぎて、どうやってすべてに対応したものか、大変悩ましいです。