【Hivemall入門】RandomForestで毒キノコ推定モデルを作る


こんにちは。俺やで。

今回も前回から間が空いてしましたが、ビッグデータに対応したHiveで使える機械学習ライブラリ、
Hivemallの使い方について、書かせていただければと思います。

なお今回はQiitaのTreasure Data / Advent Calender 2015の12/3日分としても書かせていただいております。
Qiitaにリンク貼らせてもらうのも初めてで、ちょっと緊張しつつ書いてます。

今回は第3回です。第1回、第2回は以下を参照ください。
【第1回】【超入門】Hivemallで機械学習 〜Treasure Dataでロジスティック回帰編〜
【第2回】HivemallでMinhash!〜似てる記事を探し出そう。〜

今回は、Hivemall v0.4から使えるようになったRandomForestというモデルについて書こうと思います。

細かく書くと大変なことになるので、RandomForestとは何か、をエッセンスだけお伝えし、その後Hivemallの書き方についてご紹介します。
なお毎度のことながらTreasureDataでHivemallを試させていただいております。
TreasureData様、いつもお世話になります。
それではよろしくお願いします。

■1. RandomForestとは?

まずは決定木から

RandomForestは決定木のひとつです。ですから、まずは決定木の説明から。
まずはClassicな決定木(C5.0とかCARTとかCHAIDとか、1980年頃からある決定木の手法。RandomForestが提案されたのは2001年。)のアウトプットから見ていただいた方がわかりやすいと思います。「決定木」という名前そのままですが。
randomforest_01

30歳未満か以上かで分けると、「買った人」が比較的多いクラスタと「買ってない人」が比較的多いクラスタに分かれました。
この条件が「買った人」の特徴をある程度は捉えている、ということになりそうですね。

この図だけ見てもピンと来ないかもしれませんが、もし「”商品を買った人”と”買ってない人”の違いを明らかにしろ」と言われたら、ぱっと思いつくだけでも、年齢 / 性別 / 居住地 / 家族構成 / 年収 などなどいろいろな切り口が無数にあります。
そのうえ、年齢や年収など数字で表される変数(量的変数)になると、しきい値も決めなければならず、その候補も無限です。

では、どういう条件を設定すれば、”買った人”と”買ってない人”が最も明確に分かれるのか。
ここで登場するのが機械学習であり、その一手法である決定木でありんす!

どうやって「条件」を取り出すか

「条件」の取り出し方について、ざっくり説明します。

ジニ係数という言葉を耳にしたことのある方は多いのではと思いますが、ジニ係数とは「所得格差」を定量的に表すのによく使われる数字です。
ジニ係数が大きければ所得格差が大きく、ジニ係数が小さければ所得格差は小さい、ということになります。

「格差」を定量化できれば、あとは簡単です。「格差」を表す数字が最も小さくなるような条件を探せばいいだけです。
例えば下の図を見てください。
所得が10万円の2人と100万円の2人がいます。このコミュニティは所得格差が大きいですね。
しかしこの4人を30歳未満か以上かで分けると、所得格差のない2つのコミュニティに分けることができました。

randomforest_02

決定木では、こうやって「格差」がなるべく小さくなるような条件を取り出すわけです。

格差を表す情報量には、ジニ係数以外にも、エントロピーやカイ二乗統計量等があります。
どんな指標を使うかによっても決定木の精度は変わってきます。

RandomForestは木が森になっただけです。

ちょっと話が逸れ(るように見え)ますが、アンサンブルという機械学習の手法があります。

先ほどの例を使うと、所得が10万円か、100万円かを分類するモデルの作り方は、無数にあります。
Aさんは年齢で分けるかもしれませんし、Bさんは性別で分けるかもしれませんし、Cさんは仕事の種類で分けるかもしれません。
どんな変数を使うかがデータサイエンティストの腕の試しどころだったりするわけですが、
Aさん・Bさん・Cさんのモデルを全部組み合わせて使おう!
というのがアンサンブルです。

3つのモデルのうち、2つが「この人の所得は10万円だ」、1つが「100万円だ」と分類したのであれば、多数決で「10万円」を答えに選びます。
直感的には多数決をとってしまった方が良さそうですよね。
実際、これによって精度が上がることがしばしばあります。これがアンサンブルです。

RandomForestに話を戻します。
RandomForestは、
ひとつのデータセット(学習データ)からいろんなデータセットを作って、それぞれで決定木を作って、アンサンブルしちゃおう!
という、機械学習の手法です。

「いろんなデータセット」の作り方ですが、説明変数をランダムサンプリングしてモデルを作る試行をN回繰り返せばOKです。

randomforest_04

言ってしまえば、木が森になっただけです。

■2. RandomForestで毒キノコ推定モデルを作る

それでは実際にHivemallでRandomForestを作ってみたいと思います。

使うデータはmushroom

今回使うデータはmushroomです!
https://archive.ics.uci.edu/ml/datasets/Mushroom

8,124個のキノコについて、そのキノコが有毒かどうかと、そのキノコの112個の特徴について記録されているデータです。
8,124個のうち、4,208個が毒のないキノコで、残りの3,916個が有毒となっています。
112個の特徴には、例えば笠の形とか茎の色とか生息の仕方等があります。
この112個の特徴によって、そのキノコが毒キノコかどうかを推定するモデルを作ってみます!

実際のデータを見てみましょう。

1.無毒 or 2.有毒
(class)
特徴(features)
1 ["6:1","8:1","15:1","21:1","29:1","33:1","34:1","37:1","42:1"...]
2 ["6:1","8:1","20:1","21:1","23:1","33:1","34:1","36:1","42:1"...]
2 ["2:1","8:1","19:1","21:1","27:1","33:1","34:1","36:1","44:1"...]

1行目のレコードで言うと、このキノコは無毒で、6番目の特徴(cap-shapeがsunken)と8番目の特徴(cap-surfaceがgrooves)と15番目の特徴(cap-colorがgreen)と…を持っている、ということになります。

Hiveで前処理

さきほどのデータのテーブル名をmushroomとして処理をしていきます。

select 
  class - 1 as class 
  ,to_dense_features(split(features,','),112) as features
from 
  mushroom
0.無毒 or 1.有毒
(class)
特徴(features)
0 [null,null,null,null,null,null,1,null,1,null,null,null,null,null,null,1,...]
1 [null,null,null,null,null,null,1,null,1,null,null,null,null,null,null...]
1 [null,null,1,null,null,null,null,null,1,null,null,null,null,null,null...]

class-1としていますが、HivemallのRandomForestは教師のラベルを0から始まる連番にしなければならない仕様になっているためです。
元のデータは1(食べられる)と2(毒あり)になっていたので、1を引いてあげています。

nullばっかりで非常に見づらく恐縮ですが、1行目は先ほどと同じく(配列なので0から数えて)6,8,15番目の要素に「1」が立っています。
to_dense_featuresという関数がkey:valueが要素となっている配列をkey番目の要素にvalueが入っている配列に直してくれます。
2つ目の引数はkeyの最大値を与えてあげます。
to_dense_features、めちゃ便利です。
元データに「0番目の特徴」は存在しないので取り除けばよいですが、手間だったため残してしまっています。
(すみません。)

これがHivemallでRadnomForestを作るために必要なデータの形式です!

ついにRandomForest!

やっとRandomForestまでたどり着きました。
以下のクエリでRandomForestを作ることができます!

insert into table model
select 
  train_randomforest_classifier(
    to_dense_features(split(features,','),112)
    ,class - 1 as class
    ,"-trees 30 -vars 11" --30個モデル(木)を作る。説明変数のサンプルサイズを11に指定。
  )
from 
  mushroom

オプションに指定している、treesは作成するモデルの数( = 森を構成する木の数)、varsはサンプリングする説明変数の数を表しています。
ここではそれぞれ30と11を指定していますが、前者はテキトーで、後者はR言語のRandomForestのデフォルト値である、学習データの全説明変数の二乗根(√112 = 10.58…)としています。

結果は中身見てもごちゃごちゃしてて何がなんだかなので、
出力のカラムだけの記載とします。

カラム名 説明
pred_model 各モデル(木)の詳細
var_importance 各モデル(木)の説明変数の重要度
oob_errors 各モデル(木)のOOBエラー数
oob_tests 各モデル(木)のOOBレコード数

※OOBについて詳しく書こうと思いましたが、心折れたのでまたの機会に。過学習してないかどうかを確かめるための学習データから外したサンプルのことをOOB(out-of-bag)と言います。

あとは推定(モデルのあてはめ)です。
学習データに対するスコアリングになりますが、こんな感じで書きます。
with句部分はモデリング時と同じです。クエリ的にはイケてないですが、見やすいように。

with data as ( 
  select
    row_number() over() as id
    ,to_dense_features(split(features,','),112) as features
  from
    mushroom
  )
  
SELECT
  id
  ,rf_ensemble(predicted) as predicted --アンサンブル!
FROM (
  SELECT
    id
    ,tree_predict(p.pred_model, features, true) as predicted --各モデル(木)による推定
  FROM 
    data d
    CROSS JOIN model p --全データをすべてのモデル(木)とぶつける
  ) t
group by
  id

この結果はこんな感じで返ってきます。

id predicted
1 [1,1,[0,1]]
2 [0,1,[1,0]]
10 [0,0.93,[0.93,0.07]]

predictは3つの要素から構成されていますが、
array[0]が、推定されたclass、
array[1]が、classがarray[0]である確率、
array[2][N]が、classがNである確率を表しています。
つまりarray[0]とarray[1]はarray[2]の中から最大のものを取り出してるだけですね。

もっと言えば、id=1は100%の確率で毒キノコ(class=1)、id=10は93%の確率で食用キノコ(class=0)ですよ!ということです。

最後にモデルの評価ですが、これもクエリ一発で出せます。

select
  array_sum(var_importance) as var_importance,
  sum(oob_errors) / sum(oob_tests) as oob_err_rate
from
  model

1レコード返ってくるだけです!

カラム
var_importance [0,0.11797740644573695,0.15456155959612083,0.032269....]
oob_err_rate 0.011396011396011397

var_importanceには各説明変数の重要度が、oob_err_rateにはこの毒キノコ推定モデルの精度が返されています。
error_rateが1.1%なので、逆に言えば98.9%が正解というモデルができてるんですね。
var_importanceは値の大きい順に見れば、このモデルへの寄与が大きい変数順になります!

以上がHivemallでのRandomForestの作り方になります。
これだけ精度高く毒キノコであることが推定できれば、遭難してしまっても安心ですね!

データさえあればすぐにでも試せます。お試しあれ。
Hivemall万歳!

■3. 追記

バギングも大事

RandomForestとは何かを説明してきましたが、本当はバギングの説明をしなければなりません。
記事中に出てきたOOBもバギングの文脈で出てくる単語です。
ここハショってすみません。いつか書きます。いつか。

そして、申し遅れました。私ビッグデータ解析部吉村と申します。

またの機会に!


DACエンジニア採用情報

  関連記事

PPG_anteli-kunatokei_TP_V
Treasure Dataで大規模なマスタデータを扱う際にはtimeカラムインデックスを活用しよう

DACではTreasure Dataを利用して各種データの蓄積や集計を行っています。Treasure Dataは時系列のデータを扱うのに特にすぐれたアーキテクチャなのですが、セグメントIDとユーザーIDの組み合わせといった大量のマスタデータを利用した計算にも利用することもできます。そのような場合にt …

14391226325_8c35c2a652_z
D3.jsとその活用事例について

D3.jsとは? D3とは「Data Driven Document」の略で、データに基づいてドキュメントを操作するための JavaScript ライブラリです。 ご存知の方も多いと思いますが、ちょっとだけD3.jsの基本的な使い方、そして弊社プラットフォームでの利用についてご紹介したいと思います。 …

data-tenki
気象予報士とビッグデータ解析の意外な関係

DACから気象予報士が誕生しました ビッグデータ解析部のMikeです。 2015年1月の気象予報士試験に合格し、めでたく4月からアドテク業界ただ一人(本当?)の気象予報士となりました 。 そんなわけで、今回は気象予報士とビッグデータ解析の関係についてお話したいと思います。 なぜ気象予報士を目指したか …

【超入門】Hivemallで機械学習_サムネイル
【超入門】Hivemallで機械学習 〜Treasure Dataでロジスティック回帰編〜

こんにちは。俺やで。 ビッグデータとかデータサイエンティストとかいう言葉が未だブームですね。 (「データサイエンティスト」は下火か。) ビッグデータ扱えるエンジニアも、 統計解析ができるアナリストも、 どっちもできるスーパーマンも世の中にはたくさんいますが、 ビッグデータも統計解析も扱えるインフラは …

gasserverless
GoogleAppsScriptとTreasureData REST APIを使ってサーバレスにTwitterのデータを取得

またまたTreasureDataネタです。 ただ、今回はクエリ系のネタではなく、GoogleAppsScriptとTreasureDataのREST APIを使ってTwitterのデータをTreasureDataに入れてみたので、その方法を紹介したいと思います。 はじめに ログデータだけではなく、公 …

image1
トレジャーデータの新機能「Data Connector」でクライアントレスなビッグデータ連携を実現する

トレジャーデータは、スキーマレスな大量のデータ(ビッグデータ)をパブリッククラウド上に保管して集計や抽出をするためのサービスなのですが、他システムからの連携データをトレジャーデータのテーブルに格納するまでが一苦労でした。 他システムとの外部連携を行う場合、一般的にローカルサーバー内のストレージを外部 …

chain
PyStanによるはじめてのマルコフ連鎖モンテカルロ法

はじめに こんにちは。システム開発部の中村です。 社内で行っている『データ解析のための統計モデリング入門』(所謂緑本)の輪読会に参加した所、 大変わかりやすい本だったものの、Macユーザには悲しい事に実装サンプルがWinBUGSだったため、 9章の一般化線形モデルのベイズ推定によるアプローチをPyt …

Hivemall_Minhash_pic1_thum
HivemallでMinhash!〜似てる記事を探し出そう。〜

こんにちは。俺やで。 前回の投稿に続き(間が空きましたが)、 ビッグデータに対応したHiveで使える機械学習ライブラリ、 「Hivemall」の使い方、第2弾となります。 今回はMinhashという手法について書きたいと思います。 ※前回 【超入門】Hivemallで機械学習 〜Treasure D …

sqlカクテル
【入門編】TreasureDataでサイトのアクセス解析をしてみた~第2弾!~

今回もやります、集計クエリ解説シリーズ第2弾!! 前回は、Webログからセッション単位のデータを作成するだけでした。 第2弾では作成したテーブルを元に、より実践的なアクセス解析、サイト分析で使えるHiveQLについて、実際に使用したクエリとともに解説していきたいと思います。 今回やったこと 利用した …

4229031035_bc1650a045_o
【入門編】TreasureDataでWEBログ分析をしてみた

この記事は Treasure Data Advent Calendar 2015 – Qiita の24日目の記事です。 こんにちは。 今回はWEBログの集計や解析をする際によく使うHiveQLのクエリと、UDF(User Defined Functions)について実際の集計クエリを使 …