MeCabのラッパーを実装
Mecabのインストール、Mecabのrubyバインディングのインストールを行ったので、Mecabのrubyバインディングのラッパークラスを作ってみました。
目的
商品名や説明分を形態素に分解してキーワードのインデックスを作り、そのキーワードインデックスからの検索を行いたので。
仕様
以下のような要求を満たせるよう実装を試みました。
-
-
- 集合(Set)のかたちで結果を返せるようにする。
- 助詞や助動詞は結果から省きたい。
- 文中の前後をつなげたものも結果に含みたい(東京都は東京,京都,京,東,都などの要素に分解されるが、東京と都をつなげて東京都も結果に含みたい)
- 文字数1の動詞や感嘆詞などは省きたい。
-
実装
で、実装してきます。
# Mecabのラッパー。 # 形態素解析を行う。 # require "set" require 'MeCab' class MorphsParser
最初に品詞コードの集合を定義していきます。
# MeCabの品詞コードの範囲 NOUN_CODES = 37..67 VERB_CODES = 31..33 ADJECTIVE_CODES = 10..12 EXCLAMATION_CODES = 2..2 SIGN_CODES = 4..9 #SAHEN_CODE = 36 # 結果としてい返したい品詞コード(名詞,動詞,形容詞,記号)の集合 @@include_codes = Set.new + NOUN_CODES + VERB_CODES + ADJECTIVE_CODES + # EXCLAMATION_CODES + SIGN_CODES # 動詞の品詞コードの集合 @@verb_codes = Set.new + VERB_CODES # 感嘆詞の品詞コードの集合 @@exclamation_codes = Set.new + VERB_CODES #@@include_codes.add(SAHEN_CODE)
コンストラクタでは、各オプションの定義を行います。
# initialize # argsには以下のoptionの指定が可能。 # :only_some_pos 助詞、接続詞、助動詞などを除外する場合true # :con_morphs_num 続いて現れる形態素をつなげたものも結果に含む場合その数 >=1 (1なら結合なし) # :min_char_of_verb 結果に含む動詞の最小文字数(default 1) # :min_char_of_exlamation 結果に含む感嘆詞の最小文字数(default 1) def initialize(args) @only_some_pos = if args.has_key? :only_some_pos then args[:only_some_pos] else false end @con_morphs_num = if args.has_key? :con_morphs_num then args[:con_morphs_num] else 1 end @min_char_of_verb = if args.has_key? :min_char_of_verb then args[:min_char_of_verb] else 1 end @min_char_of_exlamation = if args.has_key? :min_char_of_exlamation then args[:min_char_of_exlamation] else 1 end end
解析の本体部分は以下のように。
# # 解析された形態素の集合(Set)を返す。 # def to_set(value) morphs = Set.new; c = MeCab::Tagger.new(ARGV.join(" ")) n = c.parseToNode(value) len = n.sentence_length; for i in 0..len b = n.begin_node_list(i) while b do # printf "B[%d] %s\t%s %s \n", i, b.surface, b.posid, b.feature; unless _must_add(b) then b = b.bnext next end morphs.add(b.surface) if @con_morphs_num > 1 _connect_morphs(b, morphs) end b = b.bnext end e = n.end_node_list(i) while e do #printf "E[%d] %s\t%d %s\n", i, e.surface, e.posid, e.feature; unless _must_add(e) then e = e.bnext next end morphs.add(e.surface) if @con_morphs_num > 1 _connect_morphs(e, morphs) end e = e.bnext end end morphs end private # # 形態素ノードを結果に含むべきかの判定 # 全ての品詞を返さないよう指定(@only_some_pos)している場合は、 # 結果として返すべき品詞コード(@@include_codes)に含まれてなければfalseを返す。 # また、品詞がOKでも文字数が指定(@min_char_of_verb, @min_char_of_exclamation) # より少ない場合はfalseを返す。 # def _must_add(node) if @only_some_pos and !@@include_codes.include? node.posid then return false end if (@@verb_codes.include? node.posid and @min_char_of_verb > node.surface.split(//).size) then return false end if @@exclamation_codes.include? node.posid and @min_char_of_exclamation > node.surface.split(//).size then return false end return true end # 前後の形態素をつなげる # * +node+ - 形態素のノード # * +morphs+ - 形態素の集合(Set) def _connect_morphs(node, morphs) s = node.surface Range.new(2,@con_morphs_num).each do |i| node = node.next if node.nil? return morphs end s << node.surface morphs.add(s) end morphs end end
テスト
簡単なテストコードをしてみます。
p = MorphsParser.new(:only_some_pos => true, :con_morphs_num => 3, :min_char_of_verb => 5, :min_char_of_exclamation => 5) p p.to_set("大阪府から東京都へは新幹線で移動します。")
結果は以下、いまいちかな..
#<Set: {"しま", "府から東京", "府", "東京都へ", "大", "線", "ら", "新", "都へは", "京", "都へ", "新幹線で移動", "京都", "東京", "都", "東京都", "から", "動", "新幹線で", "へ", "東", "府から", "大阪", "。", "す", "ます", "移", "幹", "幹線", "新幹線", "阪", "大阪府", "大阪府から"}>