からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

こぶつば楽曲の歌詞をテキスト分類したい⑤~ランダムフォレスト~

〇はじめに

 この記事ではRを使って教師あり学習のランダムフォレストによる文書分類を行います。ランダムフォレストとは決定木の手法を用いたアンサンブル学習です。基本的な内容は本に沿ってやっているので、詳しい説明は参考書籍をご参照ください。
 こぶしとつばきの歌詞を文字・単語に切り分け、曲ごとの出現頻度を特徴量として各グループに分類することに挑戦します。

・分析データ

 ハロプロのグループ「こぶしファクトリー」と「つばきファクトリー」の歌詞。詳しくは下の記事をご参照ください。

特徴語の選択について www.anarchive-beta.com 特徴語の頻度表作成について(前半部分) www.anarchive-beta.com

・主な参考書籍

『Rによるやさしいテキストマイニング』小林雄一郎,オーム社

〇ランダムフォレストによる文書分類

・処理コード

使用パッケージ

library(randomForest)


ランダムフォレストによる分類

rf_model   <- randomForest(TYPE ~ ., data = train_data)
rf_predict <- predict(rf_model, test_data, type = "class")
rf_result  <- table(test_data$TYPE, rf_predict)

#分類精度
rf_result
sum(diag(rf_result)) / sum(rf_result)

randomForest()を使って訓練データから分類モデルを作る。引数に教師データの列(TYPE) ~ .data =に訓練データを指定する。predict()で分類モデルを使って評価データの分類を行う。のだが、次のエラーが発生したのでその対処をば。

エラーメッセージ1

Error in eval(predvars, data, env) : object '!' not found

列名が"!"などの記号類だと扱えないようなので、下のような処理で回避した。他に "(" ")" "?" "、" "…" "「" "」"でもエラーが発生した。

# データの調整
tmp_data1 <- all_data[, c(16, 47, 48, 65, 102, 104, 108, 109)]                     # エラーとなる列名(特徴語)の列を抜き出す
colnames(tmp_data1) <- paste("C", c(16, 47, 48, 65, 102, 104, 108, 109), sep = "") # 列名を書き換える
tmp_data2 <- all_data[, -c(16, 47, 48, 65, 102, 104, 108, 109)]                    # エラーにならない列を抽出
all_data <- cbind(tmp_data1, tmp_data2) # 統合

該当する列を抜き出して、適当な列名に変更後に再統合した。

エラーメッセージ2

Error in y - ymean : non-numeric argument to binary operator

文意はよく解らなかったのだが、教師データに不都合がある模様。

train_data$TYPE <-  factor(train_data$TYPE)

調べたら教師データの要素に対してfactor()してやれば動くとのこと。

検証

# データの調整
colnames(all_data) <- c(paste("C", 1:(ncol(all_data)-1), sep = ""), "TYPE") # 列名を書き換える
all_data$TYPE <- factor(all_data$TYPE) # エラー回避のための処理

# 検証
result <- NULL
for(i in 1:100) {
  # 訓練・評価データのランダム振り分け
  n <- c(sample(1:length(file_name1), length(file_name1) / 2), 
         sample((length(file_name1) + 1):(length(file_name1) + length(file_name2)), length(file_name2) / 2))
  train_data <- all_data[n, ]
  test_data  <- all_data[-n, ]
  
  # ランダムフォレスト
  rf_model   <- randomForest(TYPE ~ ., data = train_data)
  rf_predict <- predict(rf_model, test_data, type = "class")
  rf_result  <- table(test_data$TYPE, rf_predict)
  result <- c(result, sum(diag(rf_result)) / sum(rf_result))
}
mean(result)
sd(result)

分類精度の検証として、100回分の分析結果の平均と標準偏差をみる。ここでは数値のみに注目するので、エラー回避のために列名(特徴語)を適当な名前に置き換える。両グループから半数ずつランダムに訓練・評価データに振り分けて分類を行うのを100回繰り返す。

・分析結果

各特徴語の抽出パターンで比較するために、シングル楽曲を訓練データとして構築した分類モデルを使って、アルバム楽曲を評価データとして分類した結果である。それに加えて、両グループから半数ずつランダムに訓練データ・評価データに振り分けて分類するのを100回繰り返した際の正解率の平均値と標準偏差も載せている。

文字の出現頻度を特徴量とした場合

相対頻度
両グループから50語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   1   6
##   tbk   0   6

> sum(diag(rf_result)) / sum(rf_result)
## [1] 0.5384615

分析を100回行った正解率の平均値は0.5995238で標準偏差は0.09208215だった。
どちらかに全振りすると大体50%の正解率になるゆえ…

ちなみに訓練データでテストしたところ下の結果でした。

> rf_result
##      rf_predict
##       kbs tbk
##   kbs  13   0
##   tbk   0  15

> sum(diag(rf_result))/sum(rf_result)
## [1] 1

他の分類手法でも当然高精度だったが100%は初めて見た(全てで確認した訳ではないが)。

両グループから100語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   4   3
##   tbk   1   5

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.6923077

分析を100回行った正解率の平均値は0.7204762で標準偏差は0.0794098だった。

両グループから200語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   5   2
##   tbk   1   5

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.7692308

分析を100回行った正解率の平均値は0.7252381で標準偏差は0.07801301だった。

特徴語の数が多い方が精度が上がっている。

観測頻度
両グループから50語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   1   6
##   tbk   0   6

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.5384615

分析を100回行った正解率の平均値は0.5780952で標準偏差は0.1006222だった。

両グループから100語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   3   4
##   tbk   2   4

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.5384615

分析を100回行った正解率の平均値は0.6671429で標準偏差は0.09017189だった。

両グループから200語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   3   4
##   tbk   1   5

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.6153846

分析を100回行った正解率の平均値は0.672381で標準偏差は0.1118876だった。

基本的にテキトーに分類すれば50%近くの精度になるので、60%くらいだと高いとは言えないが一応プラスの精度ではあると言えるのでは。相対頻度の方が観測頻度よりも高くなるのは他の手法と同様だった。

単語の出現頻度を特徴量とした場合

相対頻度
両グループから50語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   5   2
##   tbk   2   4

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.6923077

分析を100回行った正解率の平均値は0.7266667で標準偏差は0.08082921だった。

両グループから100語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   6   1
##   tbk   1   5

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.8461538

分析を100回行った正解率の平均値は0.72で標準偏差は0.07987714だった。

両グループから200語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   4   3
##   tbk   1   5

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.6923077

分析を100回行った正解率の平均値は0.7328571で標準偏差は0.0811897だった。

特徴語の数に依らず高い分類精度が出た。

相対頻度,品詞限定(名詞,動詞,形容詞,形容動詞)

相対頻度

歌詞の内容によって特徴が出ているのでは、との考えのもと意味的役割を持つ名詞・動詞・形容詞・形容動詞に限定した分析を行う。

両グループから50語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   4   3
##   tbk   1   5

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.6923077

分析を100回行った正解率の平均値は0.6880952で標準偏差は0.0821656だった。

両グループから100語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   5   2
##   tbk   0   6

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.8461538

分析を100回行った正解率の平均値は0.7095238で標準偏差は0.09042681だった。

両グループから200語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   5   2
##   tbk   0   6

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.8461538

分析を100回行った正解率の平均値は0.7147619で標準偏差は0.1036442だった。

こちらも比較的高い精度となった。他の分類手法でも品詞を限定しない方が高くなる傾向にある。

TF-IDF

TF-IDFはその単語がどれだけ文書を特徴づけているのかに注目した値なので、TF-IDFも基準として使い分析を行う。

両グループから50語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   5   2
##   tbk   1   5

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.7692308

分析を100回行った正解率の平均値は0.6552381で標準偏差は0.09622385だった。

両グループか100語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   5   2
##   tbk   0   6

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.8461538

分析を100回行った正解率の平均値は0.6519048で標準偏差は0.08445307だった。

両グループか200語ずつを特徴語とした結果

> rf_result
##      rf_predict
##       kbs tbk
##   kbs   4   3
##   tbk   0   6

> sum(diag(rf_result))/sum(rf_result)
## [1] 0.7692308

分析を100回行った正解率の平均値は0.6466667で標準偏差は0.08404118だった。

シングル,アルバムで振り分けた場合の分類精度はかなり高かったのだが、ランダム振り分けだと60%代に落ち着いた。

続いて特徴語について詳しく見ていく。精度の高かった「両グループから200単語ずつを特徴語とした結果」を取り上げる。

# 説明変数の重要度を可視化
varImpPlot(rf_model, pch = 16, main = NA)

f:id:anemptyarchive:20190123190715p:plain
ジニ係数減少量上位語
C46,C22,C82はそれぞれ"?","…","、"を置き換えたもの。
ランダムフォレストでは決定木という手法が用いられている。決定木では、各説明変数の値がx以上か以下かによって全データ群を段階的に分割(分類)していく。その分割によって不純度(データが分類できていない状態)がどれだけ減少したのか、つまりデータ群をどれだけどちらか一方に偏らせられたのかを示す値がジニ係数の減少量である。つまり、どの特徴語が分類にどれくらい寄与したのかが分かる。

この図からは「よ」「言う」「前」「?」「…」などが分類に大きな影響を与えていることが分かる。

# 説明変数の部分従属プロットを描画
x <- 16 # 上位何位まで表示するかを指定
variable_names <- colnames(all_data)
rk <- order(rf_model$importance, decreasing = TRUE)[1:x]
par(mfrow = c(4,4)) # 縦と横の表示する数
for(i in rk) {
  partialPlot(rf_model, all_data, variable_names[i], 
            main = variable_names[i], 
            xlab = variable_names[i], 
            ylab = "Partial Dependenvy")
}

f:id:anemptyarchive:20190123191542p:plain
説明変数の部分従属プロット
この図は縦軸の値が高いほど第1群(こぶし)である可能性が高く、逆に低いほど第2群(つばき)の可能性が高いことを示している。また、横軸の値はその語の頻度である。つまり、頻度が上がるほど縦軸の値が高くなる(右上がり)語は第1群に特徴的であり、逆に頻度が上がるほど縦軸の値が低くなる(右下がり)語は第2群に特徴的な語となる。

頻度表を確認すると「よ」は訓練データの内、こぶしでは6曲に平均4.96回(2.29回)出現し、つばきでは15曲(全曲)に平均10.77回出現した。「言う」は訓練データの内、こぶしでは2曲に平均3.21回(0.49回)出現し、つばきでは10曲に平均7.20回(4.80回)出現する。括弧内は訓練データ全曲で割った平均値。

以上です。

〇おわりに

 今まで扱った分類手法の中で一番可能性を感じられる結果でした。ランダムフォレストは機械学習分野でよく使われている手法のようですし、理解が追い付いた段階でしっかりとした記事としてまとめたいです。今までの手法では精度が思ったよりも悪かったので、特徴語の選び方を変えて第3弾に移ろうかとも思っていたのですが、このまま最後まで通すことにします。でも、次の予定のサポートベクターマシンも今のところイマイチの結果ですねぇ。

 重要度7位に「君」20位に「私」があったので、簡単に一人称と二人称について調べてみました。「私」「自分」「君」「あなた」が使われている歌詞をざっと歌詞検索してみたところ、こぶしはそれぞれ8,8,6,1曲で、つばきは9,4,5,7曲でした。自分が抱いているこぶしの「頑張れ自分」、つばきの「私とあなたの微妙な関係」という楽曲の印象(今のは無理矢理寄せて表現しました笑)を、歌詞分類することで客観的に証明できるのでは?という当初の動機も達成できそうな片鱗が見えたのも嬉しかったです。

 最後までお読みくださりありがとうございました。次回もよろしくお願いします!

www.anarchive-beta.com