R による文書分類入門 & KNB コーパスの文書分類

Tokyo.R #31 で「R による文書分類入門」と題して発表させていただきました。

お詫び

今回はいつも以上にグダグダな発表になってしまいました。
時間配分がぐちゃぐちゃ、例を挙げようとしてもすぐ出てこない、口頭で説明する予定だったところをし忘れる、発表中にバッテリーがなくなって中断、サポートベクターマシンのハードマージンの説明を削除したらマージンの説明ができなくて焦る、などなど・・・。

初めて勉強会で発表した時よりもひどい出来だった気がします。
発表自体久しぶりだったので、過去の発表が美化されているだけでいつもこんな感じだったかもしれませんが・・・。

とりあえず準備不足であったことは間違いないです。(万全の態勢で臨めた発表は過去にないんですが)
この場を借りてお詫び申し上げます。

KNB コーパスの文書分類

当初は「R による自然言語処理入門」と題して、基礎的な内容の説明、自然言語処理で使われる代表的な手法の説明(文書分類、文書クラスタリング、系列ラベリング関連)、実際のコーパス(KNB コーパス)を使った実践みたいな盛り沢山の内容にする予定でした。
せっかく KNB コーパスのパーサーを書いたので公開しておきます。

KNB corpus parser for R

次のような感じで使います。Windows では動作しないかもしれません・・・。

> source("KNB.R")
> # KNB コーパスを data ディレクトリ下にダウンロードして解凍
> # 返り値は解凍後のファイル名
> dir <- KNB$downloadCorpus("data")
trying URL 'http://nlp.ist.i.kyoto-u.ac.jp/kuntt/KNBC_v1.0_090925.tar.bz2'
Content type 'application/x-bzip2' length 4418998 bytes (4.2 Mb)
opened URL
==================================================
downloaded 4.2 Mb

> # KNB コーパスの内容をパース
> docs <- KNB$parseDocs(dir)
> # パース結果を元にデータフレームを作成
> # normalize が TRUE だと RUnicode パッケージ(Windows 不可)で Unicode 正規化を行う
> corpus <- KNB$makeCorpusDF(docs, normalize = TRUE)
> head(corpus)
     surface    reading       term pos1     pos2 pos3               sentence
1 プリペイド ぷりぺいど プリペイド 名詞 普通名詞    *  KN001_Keitai_1-1-1-01
2     カード     かーど     カード 名詞 普通名詞    *  KN001_Keitai_1-1-1-01
3       携帯   けいたい       携帯 名詞 サ変名詞    *  KN001_Keitai_1-1-1-01
4       布教   ふきょう       布教 名詞 サ変名詞    *  KN001_Keitai_1-1-1-01
5                            特殊     句点    *  KN001_Keitai_1-1-1-01
6                つき          名詞 時相名詞    * KN001_Keitai_1-1-10-01
             doc category
1 KN001_Keitai_1   Keitai
2 KN001_Keitai_1   Keitai
3 KN001_Keitai_1   Keitai
4 KN001_Keitai_1   Keitai
5 KN001_Keitai_1   Keitai
6 KN001_Keitai_1   Keitai

なんとなく想像がつくと思いますが、corpus の各要素は surface が文書に出てきた時の活用形の単語、reading が読み、term が原形、pos1, pos2, pos3 が品詞、sentence が文のファイル名、doc が文書名、category がテーマです。
単語文書行列を作成する際、分類に影響のなさそうな単語は除きたいので、一度コーパスで使われている品詞にどのようなものがあるか見てみると良いでしょう。

> tapply(corpus$term, corpus$pos1, function(x) length(unique(x)))
    特殊     名詞     助詞   接尾辞     動詞   判定詞   指示詞     副詞 
      99     4083      120      239     1067       12       57      581 
  形容詞   助動詞   接頭辞   接続詞   連体詞   感動詞 未定義語 
     702       38       38       53       26       80       16 

「特殊」とかどのような単語か気になりますよね!

> unique(corpus$term[corpus$pos1 == "特殊"])
 [1] "。"             "~"             "、"             "."             
 [5] "’"             "("              ")"              "「"            
 [9] "」"             ":"              "→"             ","             
[13] "・"             "々"             "?"              " "             
[17] "!"              "+"              "-"             "w"             
[21] "☆"             "―"             "★"             "(*^-^*)"      
[25] "♪"             "『"             "』"             "笑"            
[29] "l"              "☆☆☆"         "☆☆☆☆"       "<"             
[33] ">"              "☆☆"           "*"              "(^^;)"         
[37] "(>▽<)"         "←"             "★★"           "〓〓〓"        
[41] "■■■"         "(vv)〓"         "○"             "a"             
[45] "b"              "”"             "n"              "f"             
[49] "t"              "■"             "\"              "\\(^o^)/"      
[53] "ー"             "c"              "e"              "/"             
[57] "g"              "(^□^;)"        "(^_^)"          "^^;"           
[61] "^^"             "(-_-;)"       "^^;)"           "◎"            
[65] "爆爆"           "&"              "⇔"             "爆"            
[69] "〓"             "="              "orz"            "×"            
[73] "(><)"           "“"             ">>"             "○○"          
[77] "●●"           "△△"           "k"              "i"             
[81] "["              "]"              "h"              "><"            
[85] "↑"             "↓"             "s"              "♪♪"          
[89] "↓↓"           "(  &#769;д`)↓"       "ヾ(*  &#769;∀`*)ノ”" "ξ"            
[93] "j"              "〇〇"           "××"           "---"        
[97] "汗"             "+_+"            "(>◆<)"        

うん・・・特殊ですね・・・。まぁいらないでしょう。
そんなこんなで使えそうな品詞を限定したら単語文書行列を作ってみます。今回は「携帯電話」と「京都観光」の文書を分類してみましょうか。

> # 抽出する品詞
> pos <- c("名詞", "接尾辞", "動詞", "副詞", "形容詞", "接頭辞", "連体詞")
> # 抽出するテーマ
> theme <- c("Keitai", "Kyoto")
> subcorpus <- droplevels(subset(corpus, pos1 %in% pos & category %in% theme))
> # 品詞を区別するために pos1 の情報も使う
> library(Matrix)
> D <- xtabs(~ paste(term, pos1, sep = ".") + doc, data = subcorpus, sparse = TRUE)
> dim(D)
[1] 5177  170
> library(kernlab)
> model <- ksvm(as.matrix(t(D[, trainIndex])), label[trainIndex],
+               kernel = "vanilladot", scaled = FALSE, kpar = list())
> pred <- predict(model, as.matrix(t(D[, -trainIndex])))
> table(pred, truth = label[-trainIndex])
        truth
pred     Keitai Kyoto
  Keitai     38     0
  Kyoto       1    46

なかなか良い感じに分類できましたね!

テーマ名に使われている “携帯.名詞”, “電話.名詞”, “京都.名詞”, “観光.名詞” とかを除いてみても高い正解率になるので逆に困るんですが。
最終的に、やはり文書数が少ないので文書分類には不向きだと判断しました。
無理やり n 単語以上の文の分類とかにしても良かったかもしれませんが。

Unicode 正規化

全角の英数字も半角の英数字も同じように扱いたいことがあるかと思います。そんな時は Unicode を正規化すると良いです。
R には Unicode を正規化するためのパッケージがありませんが、私の書いたものでよければあります。
Windows ではインストールできませんが・・・。

abicky/RUnicode · GitHub

> library(RUnicode)
> unormalize(c("Tokyo.R", "Tokyo.R"))
[1] "Tokyo.R" "Tokyo.R"

あとは全部小文字に統一すると表記ゆれはだいぶ解消できるかと思います。

余談

以下、読む価値のない余談です。

今回は yokkuns さんよりお誘いがあって発表することになったんですが、できれば機械学習系の内容とのことだったんで、当初は楽天データチャレンジでゴルフ場のレビューの要約をした話をする予定でした。
でもデータは簡単に手に入らないですし、他の人にとってあまり応用できない話になりそうだったので他のネタを考えることにしました。

次は、自然言語処理に縁のない研究室で独学で自然言語処理を勉強し始めた過去の自分に向けて、現在の自分の知識を集約した「R による自然言語処理入門」を発表しようと考えました。
アジェンダも決めて、「今回は大作だ!」と意気込んでスライドを作成していたんですが、各手法の説明を書いている時に「入門」と謳っておきながら明らかに説明不足だと思い始めます。
それでアジェンダを見返すと、まぁ 30 分で説明できるわけないよね(そしてスライドを作る時間もない)、ということでいろいろ削って文書分類だけになりました。文書分類だけに絞ってもだいぶいい加減な説明になってしまいました。

半分冗談なんですが1、今回自然言語処理入門にしようと思ったのは、多くの人に自然言語処理に興味をもってもらって、ビジネスで自然言語処理を使う人が増えて、自分の(自然言語処理のできる)転職先候補が増えればいいな、という思惑もあったりします。

何はともあれ、今回の発表がこれから自然言語処理を勉強する人にとって役立てば幸いです。
あと、Python を使える人は Python で自然言語処理をした方が良いです。

  1. 半分本気ってことですが