これで無理なら諦めて!世界一やさしいデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

【Rで決定木&ランダムフォレスト 】スピワゴ小沢さんと井戸田さんのTweetを分類!

前回は、タイタニックの生存者データを使って、Pythonで決定木とランダムフォレストの実践をしてみました。
www.randpy.tokyo

今回はRの実践編です!
以前にTwitter APIを使ってWord Cloudとかしてみたので、その流れでツイートデータを使ってみたいと思います。
www.randpy.tokyo

対象にするのは、大好きなスピードワゴンのお二人のツイートデータです。
お二人のツイートを決定木を使って分析し、井戸田さんのツイートと小沢さんのツイートを分類してみます。

その後、ランダムフォレストを使って同じデータを分析し、分類の精度がどれほど変化するのか検証します。

決定木やランダムフォレストの理論編は、以下を参考にしてください。
かなり分かりやすく書けていると思います。
www.randpy.tokyo
www.randpy.tokyo

流れとしては、

  • 井戸田さんと小沢さんのツイートを取得
  • 決定木で分類
  • ランダムフォレストで精度を確認

といった感じで進めていきます!


なお、この後紹介していくコードは、例のごとくGithub上にあげているので、まとめて見たい方は以下を参考にしてください。

github.com

テキストマイニング系の参考図書ですが、以下が良さそうです。

Rによるテキストマイニング入門

Rによるテキストマイニング入門

Rによるやさしいテキストマイニング: 機械学習編

Rによるやさしいテキストマイニング: 機械学習編

rtweetを使ってツイート取得

さて、早速ツイートを取得していきます!…その前に必要なライブラリを読み込んでおきましょう。

library(rtweet)
library(tm)
library(RMeCab)
library(dplyr)
library(purrr)
library(magrittr)
library(randomForest)
library(rpart)
library(kernlab)
library(stringr)
library("rpart.plot")

今回は、「rtweet」というライブラリを使って、RからTwitter APIを叩いてみます。
もし、まだインストールしていない場合は、

install.packages("rtweet")

のようにして、それぞれインストールしておきましょう。

次にTwitter APIを使うための認証が必要です。

CONSUMERKEY = "XXXXXXXXXXXXXXXXXXX"
CONSUMERSECRET = "XXXXXXXXXXXXXXXXXXX"
APPNAME = "XXXXXXXXXXXXXXXXXXX"

twitter_token = create_token(
  app = APPNAME,
  consumer_key = CONSUMERKEY,
  consumer_secret = CONSUMERSECRET
)

なお、Twitter APIを使うのが初めてで、上の処理ががよく分からない、という場合は以下を参考にAPIを使える準備を整えておきましょう。
www.randpy.tokyo

Itoda_account = "junjunitojun"
Ozawa_account = "ozwspw"

Itoda_tweets = get_timeline(Itoda_account, n = 10000, token = twitter_token, include_rts = FALSE)
Ozawa_tweets = get_timeline(Ozawa_account, n = 10000, token = twitter_token, include_rts = FALSE)

Itoda_tweets_texts = Itoda_tweets$text
Ozawa_tweets_texts = Ozawa_tweets$text

Itoda_tweets_texts_onlyJa = str_replace_all(Itoda_tweets_texts, "\\p{ASCII}", "") %>% iconv(from = "UTF-8", to = "CP932") %>% na.omit()
Ozawa_tweets_texts_onlyJa = str_replace_all(Ozawa_tweets_texts, "\\p{ASCII}", "") %>% iconv(from = "UTF-8", to = "CP932") %>% na.omit()

text_all = c(Itoda_tweets_texts_onlyJa, Ozawa_tweets_texts_onlyJa)
text_all = as.data.frame(text_all)

「get_timeline」という関数でツイートを取得しています。引数としては、アカウント名や、最大取得数(実際にはAPIの関係で10000件も取得できませんが、とりあえず10000を指定しています。)、先ほど認証したToken、RTを含むかどうか、などを与えてあげます。
この辺りは、公式ドキュメントを見てもらうのが手っ取り早いです!

「get_timeline」関数により得られたツイートは、リツイート数や投稿時間など様々なデータを持っていますが、今回必要なのはツイートのテキスト情報だけなので、「Itoda_tweets$text」という処理でテキストのみ取得します。

その後、「str_replace_all(Itoda_tweets_texts, "\\p{ASCII}", "")」で日本語のみ抽出しています(雑な処理ですがURLなどを省くためです。)。

次に「iconv(from = "UTF-8", to = "CP932")」で文字コード変換を行っています(たぶんこれはWindowsユーザーだと必須です、Macユーザーなら要らないかも)。

それぞれ取得できたら「text_all 」という一つの変数にまとめ、この後形態素解析をするためにデータフレーム化しておきます。

形態素解析でtf-idf行列を作成

ツイートが取得できたので、形態素解析を行います。
また、今回分類するための特徴量としては、tf-idfを使うことにります。

tf-idfについては、まだ本ブログに記事が無いので、解説は他のサイトに委ねます。PythonでTF-IDF計算とかが簡潔で分かりやすいかもしれません。

tf-idfは、ものすごく簡単にいうと、

  • 出現頻度が多いほど
  • 他のデータに出現頻度が少ないほど

値が大きくなります。

doc_matrix = docDF(text_all, col = 1, type = 1, pos = c("名詞", "形容詞"), minFreq = 3, weight = "tf*idf*norm")
doc_matrix = doc_matrix %>% filter(POS2 %in% c("一般", "固有名詞","自立"))

RMecabライブラリの、「docDF」関数に先ほど作ったデータフレームを渡します。また、その際に対象とする品詞や最低頻出回数を指定することができます。今回は、品詞としては名詞と形容詞、そして最低頻出回数は3回としました。

なお、次に品詞のサブタイプとして、一般名詞・固有名詞、そして形容詞の中でも自立語を取得するようにしています。

作成できた行列を確認してみましょう。データ数が多いので10行目までと10列目まで表示します。

> doc_matrix [1:10,1:10]
         TERM   POS1     POS2 Row1 Row2 Row3      Row4 Row5 Row6 Row7
1          ー   名詞     一般  NaN    0  NaN 0.3144419    0    0    0
2          ー   名詞 固有名詞  NaN    0  NaN 0.0000000    0    0    0
3        ーー   名詞     一般  NaN    0  NaN 0.0000000    0    0    0
4      ーーー   名詞     一般  NaN    0  NaN 0.0000000    0    0    0
5    ーーーー   名詞     一般  NaN    0  NaN 0.0000000    0    0    0
6  ーーーーー   名詞     一般  NaN    0  NaN 0.0000000    0    0    0
7        あざ   名詞     一般  NaN    0  NaN 0.0000000    0    0    0
8  あたたかい 形容詞     自立  NaN    0  NaN 0.0000000    0    0    0
9  あったかい 形容詞     自立  NaN    0  NaN 0.0000000    0    0    0
10       あと   名詞     一般  NaN    0  NaN 0.0000000    0    0    0

1列目には単語、2列目には品詞、3列目には品詞のサブタイプ、そして4列目以降は各ツイートのtf-idfの値が記録されています。
この作られた行列を元に、データを決定木やランダムフォレストに使えるように整形していきます。

doc_matrix_tfidf = doc_matrix[,4:ncol(doc_matrix)]
doc_matrix_t = doc_matrix_tfidf %>% t()

rownames(doc_matrix_t) = c(1:nrow(doc_matrix_t))
colnames(doc_matrix_t) = doc_matrix[,1]

type1 = c(rep(1, times = length(Itoda_tweets_texts_onlyJa))
          , rep(0, times = length(Ozawa_tweets_texts_onlyJa)))

doc_matrix_t_1 = cbind(doc_matrix_t, type1)
doc_matrix_t_1_naomit = doc_matrix_t_1 %>% na.omit()

doc_matrix_t_1_naomit[is.nan(doc_matrix_t_1_naomit)] = NA
doc_matrix_t_1_naomit = doc_matrix_t_1_naomit %>% na.omit()
tmp_data_frame = as.data.frame(doc_matrix_t_1_naomit)

まず必要な4列目以降のデータのみ取得して転置し、行名には単なる数字、列名には単語名を指定しています。
その後、井戸田さんと小沢さんのツイートを分類できるように、井戸田さんのツイートデータについては最終列に「1」を、小沢さんのツイートには「0」を入れています。

あとはnanデータを削ったりすれば完了です!
お疲れ様でした!!

決定木分類

ここまできたら、決定木にかけてみるだけです。

まずは分類の精度を測定するために、訓練データとテストデータに分けてみます。奇数行と偶数行で分けています。

train_sample = sample(nrow(tmp_data_frame), nrow(tmp_data_frame)*0.5)
train_tmp_data_frame = tmp_data_frame[train_sample, ]
test_tmp_data_frame = tmp_data_frame[-train_sample, ]

Rでは、「rpart」という決定木のためのライブラリがあるのでそちらを使います。

train_tmp_data_frame$type1 = as.factor(train_tmp_data_frame$type1)
dt_model = rpart(type1 ~ ., method="class", data = train_tmp_data_frame)

これで分類にかけられたので、テストデータを使ってどれほど精度があるのか確認してみます。

dt_model_predict = predict(dt_model, test_tmp_data_frame, type="class")
dt_model_type = table(test_tmp_data_frame$type1, dt_model_predict)
> sum(diag(dt_model_type)) / sum(dt_model_type) 
[1] 0.7549091

大体75%ぐらいの確率で小沢さんとツイートと井戸田さんのツイートを分類できていることが分かりました。

ではグラフを描いて可視化してみましょう。可視化は以下を実行すればOKです。

rpart.plot(dt_model_2, type = 4, extra=1)

f:id:Np-Ur:20171027203839p:plain

時期が時期だからでしょうか、ドラゴンズというワードが目立ちますね。「ドラゴンズ」というワードが含まれているツイートは井戸田さんのツイートである可能性が高いことが分かります。
他にも

  • ノンストップ
  • 井戸田
  • ハンバーグ

といったワードが含まれていると、大体井戸田さんのようですね。個人的にはやはり「ハンバーグ」が印象的です!

「ハンバーーーーーグ!!!!」
f:id:Np-Ur:20171027211415p:plain
(画像はイメージです。)

ランダムフォレストでも分類

次にランダムフォレストでも分類してみます。randomForestライブラリを使います。

tmp_data_frame$type1 = as.factor(tmp_data_frame$type1)
rf_model = randomForest(type1 ~ . , data = tmp_data_frame, ntree = 100, proximity = TRUE)

はい、これだけです。非常に簡単ですね!

> rf_model

Call:
 randomForest(formula = type1 ~ ., data = tmp_data_frame, ntree = 100,      proximity = TRUE) 
               Type of random forest: classification
                     Number of trees: 100
No. of variables tried at each split: 26

        OOB estimate of  error rate: 16.73%
Confusion matrix:
     0   1 class.error
0 1830  82  0.04288703
1  378 460  0.45107399

精度は、100-16.37で、83.27%でした。やはり決定木に比べた分類の精度が上がっていますね!

しかし少し詳しく見ると、小沢さんの方のツイートは大方間違いなく小沢さんツイートとして分類できていますが、井戸田さんの方はあまり精度がよくありませんね。
このあたり改良の余地はまだまだありそうです。

なお、理論編でも勉強しましたが、ランダムフォレストでは木の各頂点において分類する際に用いる変数の数を制限します。
今回せっかくなので、チューニングしながら、その変数の数として最適な値を選んでみましょう。

といっても、randomForestライブラリにある、tuneRF関数を使うだけなので非常に簡単です。
この辺りは、Rで機械学習するならチューニングもグリッドサーチ関数orオプションでお手軽にの記事を主に参考にしました。誠にありがとうございます。

結構時間はかかりますが、実行すると以下のように表示されると思います。
パソコンのスペックによりますが、10分ぐらいかかるかもです。

> rf_tune = tuneRF(tmp_data_frame[,1:(ncol(tmp_data_frame)-1)],tmp_data_frame[,ncol(tmp_data_frame)],doBest=T)
mtry = 26  OOB error = 17.24% 
Searching left ...
mtry = 13 	OOB error = 17.13% 
0.006329114 0.05 
Searching right ...
mtry = 52 	OOB error = 17.24% 
0 0.05 

この結果から、変数の数は13とするのが、良さそうということが分かります。

そちらを使って再度実行してみると、

>(rf_model = randomForest(type1 ~ . , data = tmp_data_frame, ntree = 100, proximity = TRUE, mtry = 13 ))

Call:
 randomForest(formula = type1 ~ ., data = tmp_data_frame, ntree = 100,      proximity = TRUE, mtry = 13) 
               Type of random forest: classification
                     Number of trees: 100
No. of variables tried at each split: 13

        OOB estimate of  error rate: 16.65%
Confusion matrix:
     0   1 class.error
0 1842  70  0.03661088
1  388 450  0.46300716

ちょっと精度が上がりましたね!

なお、分類に寄与した変数(単語)を見るためには、varImpPlotという関数が便利です。

> varImpPlot(rf_model)

f:id:Np-Ur:20171027212134p:plain

まとめ

今回は、決定木とランダムフォレストのR実践編ということで、スピードワゴンの井戸田さんと小沢さんのツイート分類をしてみました。

「この単語が入っているなら井戸田さんの可能性が高い」ということは分かっても、「この単語なら小沢さん」というのは読み取ることはあまりできませんでしたね。

取得するツイート数を増やしたり、前処理に時間をかけたり、別の素性を使ってみるなどすれば、もっと精度は上げられそうです。

今回の分析が実務で使えるかというと、全くそんなことはないぐらい雑な分析ですが、決定木とランダムフォレストの面白さは分かってもらえたのではと思います!!
是非、ツイートデータに限らず色々なデータを使って分析してみてください。