簡易感情分析のテキストマイニングで地域振興を考えてみる -MeCab起動編-

プログラミング

自分が使えるのはC言語だけ。
Rubyはずっと前に画像収集のダウンローダみたいなのを必要に駆られて作った。Macのターミナルで簡単に動いて、環境とかよくわからなくても使えたのが良かった。もはや忘却の彼方だけど。
ネガポジ判定のサンプルコードとかネット上に落ちてるし、とりあえずRubyを使うことにした。

色々調べた結果、プログラムの流れ的にはこんな感じにすればいいっぽい。

1.Twitterからつぶやき収集

2.MeCabで形態素解析

3.感情極性データベースを使ってネガポジ判定

4.ネガティブなつぶやきをピックアップして分析

Ruby導入

まずはRubyを使えるようにしなくては。そのままでも使えかもしれないけど、よく分からんから色々入れとくことにした。
Rubyの環境は下のサイトとか見つつ整えた。何やってるのかイマイチ分からなくてもHello Worldまでたどり着いた。
パッと調べたかんじだとテキストマイニング的なことやってる人はRubyとかPythonとか使ってるみたい。あるいはその2つが普及してるだけか。Pythonもいつか勉強してみたいな。

[Rubyで始めるプログラミング入門 第1回 〜環境構築編〜]
[Ruby Mac OS10.10にrbenvでRubyをインストールする]
[rbenv や Bundler を用いた Ruby環境構築]

MeCabで形態素解析

次にMeCabで形態素解析というものをする。これをすると文章を単語に区切ってくれる。
例えば次の文をMeCabにぶち込むと、こんな感じで返してくれる。

「カラスがカァカァ鳴いている。」

“カラス”, “名詞,一般,*,*,*,*,カラス,カラス,カラス”
“が”, “助詞,格助詞,一般,*,*,*,が,ガ,ガ”
“カァカァ”,”名詞,一般,*,*,*,*,*”
“鳴い”, “動詞,自立,*,*,五段・カ行イ音便,連用タ接続,鳴く,ナイ,ナイ”
“て”,”助詞,接続助詞,*,*,*,*,て,テ,テ”
“いる”, “動詞,非自立,*,*,一段,基本形,いる,イル,イル”
“。”, “記号,句点,*,*,*,*,。,。,。”

しかも単語の基本形を出してくれるから「鳴いた」「鳴けば」「鳴かない」って単語も、全部「鳴く」の変形だなって分かる。つまり単語の比較がしやすい。
辞書はMeCabにデフォルトで入ってるものからmecab-ipadic-NEologdへアップグレードしといた。
参考にしたのはこのあたりのサイト。

[RubyでMeCabを使う方法と新語辞書NEologd対応]
[MeCabをMacにインストールする手順]
[Macにmecabインストール]
[MeCab の形態素解析と辞書のお話]
[13日目: めかぶと納豆とツイート解析とRCC]

単語感情極性データベースと比較

文章を単語で区切ったら、その単語がどのくらいネガティブか、あるいはポジティブかを評価していく。
ここでは単語ごとにネガポジかの評価がつけられてるデータベースを使う。「最悪」はネガティブ、「おいしい」はポジティブみたいな。
ネット上で有名そうなDBは2つ。高村研究室のDBと、そのDBも一部利用して作られた乾・岡崎研究室のDB。

高村研究室・単語感情極性対応表

高村研のDBは単語に点数がつけられてる。プラスの数字ならポジ、マイナスの数字ならネガ。
例えば「素晴らしい:すばらしい:形容詞:0.998617」、「呆れる:あきれる:動詞:-0.99819」。
[単語感情極性対応表|高村大也]

このDBを使ってネガポジ判定してる人結構いる。
[ツイートの初歩的な感情分析]
[僕が感情豊かであることをネガ・ポジ判定で証明する]

乾・岡崎研究室・日本語評価極性辞書

乾・岡崎研究室のDBは名詞DBと用言DBに分かれている。名詞はネガ・ポジ・ニュートラル、用言はネガ・ポジの判定がついてる。細かい点数はついてなく、単純な2値と3値。
例えば「ネガ(評価) うらめしい」、「ポジ(評価) 品 が ある」という感じ。元から単語がいい感じに区切られてる。
[日本語評価極性辞書|乾・岡崎研究室]

このDB使ってる人はあまり見かけない。下のサイトでは使ってるっぽいけど、コードが全く理解できなかった。うまい人はああ書くのか。
[ネガポジ判定を行うGem作ってみた]

結局、DBは乾・岡崎研究室のを使わせてもらうことにした。情報量とか使い勝手とか考えて。

ネガポジ判定を実装

いろんな先人のコード見つつ、コード書いていくと色々と改善点に気づいた。

DBもMeCabにかけるべし!

まず感情極性DBにも形態素解析をかけないとダメ。感情極性DBはMeCab用に作ってるわけじゃないから、単語の区切り方とかが違うものもたまーーーにある。
例えば「それは鬼に金棒だ」をMeCabにかけると「それ は 鬼に金棒 だ」と区切られる。でも感情極性DBにあるのは「鬼 に 金棒 だ」。つまりこの2つを単純に比較すると区切り位置の違いで一致せず、ポジティブと判定されない。
感情極性DBもMeCab仕様に変換してから比較する。

MeCabは前後の文章も見てるっぽい

ただDBをMeCabにかけると逆にダメになるものもある。例えば「先進的です」って単語をMeCabで区切ると、文章内では「彼 は 先進 的 です」になる。一方でDBに登録されている単語をそれ単体でMeCabで区切ると。「先進的 です」に分けられる。この場合はDBをMeCabにかけたせいで、比較しても一致していないと判定されることになる。
どうやらMeCabは単語の前後の文章も見て区切りかたを決めてるっぽい。その代表的な例が「〜的です」という形の単語。「〜的だ」とかは問題ないけどなぁ。
まぁこういうのはレアケースと思って無視しよう。

ソースコード

とりあえず文章をネガポジ判定する部分のコードを書いた。初心者コードなのは勘弁。とりあえず動くとこまでいった。もうクタクタだ・・・。

require 'natto'

#ネガポジ判定する文章
text = "今日は学校が最悪だった。テストの結果良くなかった、めっちゃ疲れた。家に帰って食べたケーキは美味しかったけどね。"

#処理スタート
nm = Natto::MeCab.new

# 名詞データベース格納処理
list_meisi = Array.new
File.open('pn.csv.m3.120408.trim.csv', 'r') do |file|
    file.each{ |line|
        tmp = Array.new
        # ネガポジ情報格納
        tmp.push line.chomp.split("\t")[1]
        # 単語を形態素解析して基本形を格納
        nm.parse(line.chomp.split("\t")[0].encode("UTF-8")) do |n|
            next if n.is_eos?
            tmp.push n.feature.split(',')[6]
        end
        list_meisi << tmp if tmp.index("*") == nil
        }
end

# 用言データベース格納処理
list_yougen = Array.new

File.open('wago.121808.pn.txt', 'r') do |file|
    file.each{ |line|
        #単語が空の場合はスキップ
        next if line.chomp.split(/\t/)[1] == nil

        tmp = Array.new
        # ネガポジ情報格納
        tmp.push line.chomp.split(/\t/)[0]
        # 単語を結合して形態素解析して基本形を格納
        nm.parse(line.chomp.split(/\t/)[1].gsub(" ", "").encode("UTF-8")) do |n|
            next if n.is_eos?
            tmp.push n.feature.split(',')[6]
        end
        list_yougen << tmp if tmp.index("*") == nil
    }
end




#文章を形態素解析し単語ごとに区切る

sentence = Array.new
tmp = Array.new
next_word = 0

        nm.parse(text.encode("UTF-8")) do |n|
            #文章の終端まできたら終わり
            next if n.is_eos?
            #単語の基本型が*になる不明文字の場合はスキップ
            next if n.feature.split(',')[6] == "*"
            #単語の基本形を取得
            tmp << n.feature.split(',')[6]
        end
        sentence.push tmp


# 文章の形態素解析結果を展開
        sentence.each{ |s|
            tmp = Array.new
            negative_word = Array.new
            neutral_word = Array.new
            positive_word = Array.new
            negative_point = 0
            neutral_point = 0
            positive_point = 0

        s.each_with_index{ |word, i|
            #処理済み単語はスキップ
            next if next_word > i

            #用言を比較処理
            list_yougen.each{ |yougen|
                #単語と比較し一致したらネガポジ値加算、次の単語番号を指定
                if s[i,yougen.size-1] == yougen[1,yougen.size-1]
                     if yougen[0,1] == ["ネガ(経験)"] || yougen[0,1] == ["ネガ(評価)"] then
                         negative_point += 1
                         negative_word.push yougen[1,yougen.size-1]
                    elsif yougen[0,1] == ["ポジ(経験)"] || yougen[0,1] == ["ポジ(評価)"] then
                        positive_point += 1
                        positive_word.push yougen[1,yougen.size-1]
                    end

                    next_word = i+yougen.size
                    break
                end
                }
            
            #処理済み単語はスキップ
            next if next_word > i

            #名詞を比較処理
            list_meisi.each{ |meisi|
                #単語と比較し一致したらネガポジ値加算、次の単語番号を指定
                if s[i,meisi.size-1] == meisi[1,meisi.size-1]
                    if meisi[0,1] == ["n"] then
                        negative_point += 1
                        negative_word.push meisi[1,meisi.size-1]
                    elsif meisi[0,1] == ["p"] then
                        positive_point += 1
                        positive_word.push meisi[1,meisi.size-1]
                    elsif meisi[0,1] == ["e"] then
                        neutral_point += 1
                        neutral_word.push meisi[1,meisi.size-1]
                    end

                    next_word = i+meisi.size
                    break
                end
                }
        }
        
        #結果出力
        puts "本文:#{text}"
        puts "▼ネガティブワード:#{negative_point}個"
        print "#{negative_word}\n"

        puts ("▼ポジティブワード:#{positive_point}個。")
        print "#{positive_word}\n"
            }

これの実行結果がこれ。ネガポジ判定に引っかかった単語は基本形で表示してある。「テスト」がネガティブワードかは置いといて、ネガティブツイートの大まかな識別には使えそう???

本文:今日は学校が最悪だった。テストの結果良くなかった、めっちゃ疲れた。家に帰って食べたケーキは美味しかったけどね。
▼ネガティブワード:4個
[[“最悪”], [“テスト”], [“良い”, “ない”], [“疲れる”]]
▼ポジティブワード:1個。
[[“美味しい”]]

ただし処理スピードが激遅だからなんとかせねば。大量の文章を処理するときにやばい。句読点まで比較処理してるから、そういうとこ削ろうか。
次はTwitterからつぶやきを大量に取得するコードを書いていく。

To Be Continued→

LEAVE A REPLY

*
*
* (公開されません)