読者です 読者をやめる 読者になる 読者になる

出現頻度と連接頻度に基づく専門用語抽出

自然言語処理 Ruby

この前の続き。先週の週末にやるつもりだったけど、暇がなかった。

前回の流れとしては

  • 専門用語を一つの単語として取ってくるのは難しい
  • MeCabを使うと細かくなりすぎる
    • 専門用語には名詞のsequenceが多そう
    • じゃあ、名詞つなげてみればいいんじゃね?

ということで名詞を繋げてみるだけというところをやりました(それだけ。。。)。id:niamさんがコメントしてくださったように"出現頻度と連接頻度に基づく専門用語抽出",自然言語処理, 2003を使うと専門用語らしさ(?)のようなスコア付けができるようなので、それをやってみることにしました。とりあえずp6のLR(CN)のところまでを実装。あとはスコア付けの関数を2つくらい用意して、評価指標の関数を用意すれば、という感じです。

# -*- coding: utf-8 -*-
# implementation for this paper
# http://www.r.dl.itc.u-tokyo.ac.jp/~nakagawa/academic-res/jnlp10-1.pdf

class Term
  def initialize(technical_word)
    @technical_word = technical_word
    @simple_term = technical_word.flatten.uniq # 単名詞
  end
  
  def print(word)
    return "[#{t.stop_sharp_ldn(word).uniq.map{|i| "[" + i.join(", ") + "]"}.join(", ")}]"
  end

  def stop_sharp_ldn(word) # wordの左側のところを取ってこさせる
    result = []
    @technical_word.each{|array|
      tmp = []
      if array.index(word) > 0
        array.each{|i|
          if word != i
            tmp.push i
          else
            tmp.push i
            break
          end
        }
        result.push tmp
      end
    }
    return result
  end

  def stop_sharp_rdn(word) # wordの右側のところを取ってこさせる
    result = []
    @technical_word.each{|array|
      tmp = []
      flag = false
      array.each{|i|
        if word == i
          flag = true
          tmp.push i
        elsif flag
          tmp.push i
        end
      }
      result.push tmp
    }
    result.delete([word])
    return result
  end

  def sharp_ldn(word) # #LDN(N)
    return stop_sharp_ldn(word).uniq.length
  end
  def sharp_rdn(word) # #RDN(N)
    return stop_sharp_rdn(word).uniq.length
  end

  def sharp_ln(word) # #LN(N)
    return stop_sharp_ldn(word).length
  end

  def sharp_rn(word) # #RN(N)
    return stop_sharp_rdn(word).length
  end

  def lr(words) # LR(CN)
    result = 1
    words.each{|word|
      result *= (sharp_ln(word) + 1) * (sharp_rn(word) + 1)
    }
    result = result ** (1.0 / (2 * words.length))
  end

end

technical_word = [["トライグラム", "統計"], 
                  ["トライグラム"], 
                  ["単語", "トライグラム"], 
                  ["クラス", "トライグラム"], 
                  ["単語", "トライグラム"], 
                  ["トライグラム"],
                  ["トライグラム", "抽出"],
                  ["単語", "トライグラム", "統計"],
                  ["トライグラム"],
                  ["文字", "トライグラム"]
                 ]

t = Term.new(technical_word)

puts "[#{t.stop_sharp_ldn("トライグラム").map{|i| "[" + i.join(", ") + "]"}.join(", ")}]"
puts "[#{t.stop_sharp_rdn("トライグラム").map{|i| "[" + i.join(", ") + "]"}.join(", ")}]"

puts "#LDN(トライグラム) = #{t.sharp_ldn("トライグラム")}"
puts "#RDN(トライグラム) = #{t.sharp_rdn("トライグラム")}"

puts "#LN(トライグラム) = #{t.sharp_ln("トライグラム")}"
puts "#RN(トライグラム) = #{t.sharp_rn("トライグラム")}"

puts "LR(トライグラム) = #{t.lr(["トライグラム"])}"

実行結果。

/Users/syou6162/ruby% ruby term_extraction.rb
[[単語, トライグラム], [クラス, トライグラム], [単語, トライグラム], [単語, トライグラム], [文字, トライグラム]]
[[トライグラム, 統計], [トライグラム, 抽出], [トライグラム, 統計]]
#LDN(トライグラム) = 3
#RDN(トライグラム) = 2
#LN(トライグラム) = 5
#RN(トライグラム) = 3
LR(トライグラム) = 4.89897948556636

あとこれに投げるようのMeCabからのラッパーも書かないといけないなー。ということで書いた。

# -*- coding: utf-8 -*-

require 'MeCab'
require 'pp'

class MeCabWrapper
  def initialize(mecab)
    @mecab = mecab
    @extraction = []
  end
  
  def add_text(text)
    n = @mecab.parseToNode(text) 
    list = Array.new
    prev_word = ""
    prev_pos = ""
    tmp = []
    while n do
      f = n.feature.split(/,/) 
      if /名詞/ =~ f[0]
        tmp.push n.surface
      else
        if prev_pos == "名詞"
          list.push tmp # これまでが名詞のsequenceだったら、つっこむ
          tmp = []
        end
      end
      prev_word = n.surface
      prev_pos = f[0]
      n = n.next
    end 
    @extraction = list
  end
end

mw = MeCabWrapper.new(MeCab::Tagger.new("-Ochasen"))
sentence = "梅雨時期梅雨時期だけあって、今日今日今日はちょっとじめじめしてますね。明日今日はさらっとしてほしいです。" # 名詞のsequenceができるようにめちゃくちゃにして作った
mw.add_text(sentence)
pp mw

実行結果。配列の配列ができて準備が整った感じ。

#<MeCabWrapper:0x4ad24c
 @extraction=
  [["\346\242\205\351\233\250",
    "\346\231\202\346\234\237",
    "\346\242\205\351\233\250",
    "\346\231\202\346\234\237"],
   ["\344\273\212\346\227\245",
    "\344\273\212\346\227\245",
    "\344\273\212\346\227\245"],
   ["\346\230\216\346\227\245", "\344\273\212\346\227\245"]],
 @mecab=#<MeCab::Tagger:0x4ad274 @__swigtype__="_p_MeCab__Tagger">>