わらってごまかす

ごまかしちゃだめ

ナイーブベイズ分類器でTwitterの男女判別!

Twitterの分析をする上で避ける事ができないのが男女判別。Twitter Analiticsではすでに自分のフォローワーの男女比が表示されます。
Twitter者はフォロー関係などを使って判別しているらしいのですが、今回はつぶやかれているテキストを使って、「ナイーブベイズ」という手法で分類してみます。

データについて

そもそも今回は勉強中のPythonでツイート収集、分析を行おうと思ったのですが、途中でどうしても解決できないエラーが有ったので一旦諦めました。
データは性別を目視で確認できた男62ユーザー,女93ユーザー(両方100人くらい集めたはずが途中で闇に消えました……)からそれぞれの最新1000ツイートを取得しテキストファイルとして保存してあります。

ナイーブベイズとは

ナイーブベイズとは「単純ベイズ分類器」とも呼ばれ、スパムメールフィルターなどに用いられています。理論はシンプルながらも、高い精度を出すことができます。(数式が出てきますが、頑張ればきっと理解できるように書きました?)

ナイーブベイズの理論はベイズの定理を応用して次のように表せます。

{ \displaystyle
P(cat|doc) = \frac{P(doc|cat)P(cat)}{p(doc)} \propto P(doc|cat)P(cat)
}

{ \displaystyle P(cat|doc) } は文章docが与えられた時にカテゴリcatに属する確率(これを知りたい!)
{ \displaystyle P(doc|cat) } はカテゴリcataに属するときに、文章docである確率(わかりづらいのであとで解説)
{ \displaystyle P(cat) } はカテゴリcatである確率(catの文章数 / 全文章数)
{ \displaystyle P(doc) } は文章docである確率

{ \displaystyle \propto }は右に比例するという意味です。
今回は確率そのものを知りたいわけではなく、最も確率が高いカテゴリを知りたいので、確率に比例する{ \displaystyle P(doc|cat)P(cat) }を最大化すればいいわけです!

具体的に考える

まず、{ \displaystyle P(cat) } は、男(女)の文書数 / 全体の文書数で簡単に出せます。

次に{ \displaystyle P(doc|cat) } ですがけっこうややこしいです。

ナイーブベイズでは、まず文章を単語に切り分け、単語の集合を「文書」として扱います。集合なので、文章の中で出てくる順番は関係なく、このような表現をbag-of-wardsと呼ばれます。
そしてそれぞれの単語の出現回数を文章ごとに調べてこのような行列にします。
f:id:fujit33:20150514012713p:plain:w500
この行列を使うと、単語Aの文書T内での出現回数 / 単語Aの総出現回数 を調べることができます。
それをカテゴリに属する文章の、全ての単語に関してしらべ、掛け合わせたものが{ \displaystyle P(doc|cat) }です!!!!
数式にすると

{ \displaystyle P(doc|cat) = P(word_i|cat) \cdots = \prod_i P(word_i|cat) }

となります。{ \displaystyle \prod }{ \displaystyle \sum }の掛け算バージョンで、iが1ずつ増えていき、単語を順番に変えながら確率を出し、それらを全て掛け合わせると言う意味です。

ついに求める数式が!?

これらより、最終的に求めるカテゴリmapは

{ \displaystyle cat_{map} = arg max_{cat} P(doc|cat) = arg max_{cat} P(cat)\prod_i P(word_i|cat) }

となります。{ \displaystyle arg max_{cat} }とはcatについて最大化する値を返すということです。しかし問題が有り、文章内の単語数は膨大なため、{ \displaystyle P(word_i|cat)}をすべてかけ合わせるとひっじょーーーーに小さい数字になり、計算がアンダーフローを起こしてしまう可能性があります。(あるそうです)アンダーフローとは、簡単にいえば数字が小さくなりすぎて、そのプログラミング言語じゃ処理できないよってことです。

だからといってここまできて諦めるわけにはいかないので、対数をとって掛け算ではなく足し算の計算に変えてあげます。「対数の計算なんて忘れた」って人はググッて下さい。

すると式はこう変換できます。

{ \displaystyle cat_{map} = arg max_{cat} P(doc|cat) = arg max_{cat} \big(log(P(cat)) + \sum_i log(P(word_i|cat))\big) }

ラプラススムージング

実はこの式のままでは欠点があって、{ \displaystyle log(P(word_i|cat)) } は単語が一回も出現しない場合だと0になってしまい、log 0は計算できません。それを回避するために、すべての単語に出現回数を1足してあげるという処理をします。この処理をラプラススムージングと呼びます。

Rで実装

ここまでをようやくRで実装するのですが、今回はあらびきさん(@a_bicky)のこちらのコードをお借りしました。少し解説を加えています。10行でナイーブベイズを実装してしまうあらびきさんはすごいです……

library(RMeCab)

d <- t(docMatrix2("usertimelines"))

ncol(d)
myNaiveBayes <- function(x, y) {
    # 文章のカテゴリ
    lev <- levels(y) #1
    # それぞれのカテゴリでの単語の出現数
    ctf <- sapply(lev, function(label) colSums(x[y == label,])) #2
    # ラプラススムージングを行ったそれぞれのカテゴリでの単語の出現確率
    ctp <- t(t(ctf + 1) / (colSums(ctf) + nrow(ctf))) #3
    # それぞれのカテゴリの文章数
    nc <- table(y, dnn = NULL) #4
    # カテゴリの文書数の割合
    cp <- nc / sum(nc) #5
    structure(list(lev = lev, cp = cp, ctp = ctp), class = "myNaiveBayes") #6
}

predict.myNaiveBayes <- function(model, x) {
    # モデルにそれぞれの単語の出現確率の対数をすべて足す
    prob <- apply(x, 1, function(x) colSums(log(model$ctp) * x)) #7
    # 学習データの文書量で重みを変える
    prob <- prob + log(as.numeric(model$cp)) #8
    # 確率が高い方のインデックスを返す
    level <- apply(prob, 2, which.max) #9
    # 確率が高いほうのラベルを返す
    model$lev[level] #10!!
}


y <- factor(sub("^([a-z]*?)_.*", "\\1", rownames(d), perl = TRUE)) # データにラベル付け
table(y)
train.index <- c(1:42, 63:135) # 学習データにする文書の選択
model <- myNaiveBayes(d[train.index,], y[train.index]) # 学習
test <- y[-train.index] # テストデータの回答ラベル
result <- predict(model, d[-train.index,]) # テスト

# 結果発表(ドキドキ)
table(test, result) 
mean(test == result)

結果

> table(test, result)
       result
test    man woman
  man    18     2
  woman   2    18
> mean(test == result)
[1] 0.9

正解率は90%でした。

まとめ

Twitterは特殊な文章であるので、全然分類できないことを覚悟していたので案外すんなりいってうれしいですが、肩透かし感もあります。90%分類できたのですが、まだ工夫によって正解率はあげられるはずです。例えば、絵文字や顔文字など、Twitter特有のよく使われる文字に注目すると効果があると思います。
また、今回は男女の2分類でしたが、カテゴリを増やした分類もやってみたいと思います。どうやら、多項モデルの方が精度が上がるという論文もあるようです。でも今はいろいろな手法を試したいから後回しかな……

どうでもいいつぶやき
最近、名著『パターン認識機械学習』(PRML)で機械学習の勉強をしています。難しい……


(参考リスト)
単純ベイズ分類器 - Wikipedia
第3回 ベイジアンフィルタを実装してみよう:機械学習 はじめよう|gihyo.jp … 技術評論社
ナイーブベイズを用いたテキスト分類 - 人工知能に関する断創録