【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として処理をしていきます。
[sql]
select
class – 1 as class
,to_dense_features(split(features,’,’),112) as features
from
mushroom
[/sql]

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を作ることができます!

[sql]
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
[/sql]

オプションに指定している、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句部分はモデリング時と同じです。クエリ的にはイケてないですが、見やすいように。
[sql]
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
[/sql]

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

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)ですよ!ということです。

最後にモデルの評価ですが、これもクエリ一発で出せます。
[sql]
select
array_sum(var_importance) as var_importance,
sum(oob_errors) / sum(oob_tests) as oob_err_rate
from
model
[/sql]

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エンジニア採用情報

  関連記事

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

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

Treasure Dataの新機能(Data Tank)をAudienceOneのレポート機能で利用した話

Data Tankとは? Treasure Dataの新機能でTreasure Dataのプラットフォーム上に構築されたデータマートです。 Tableau等のBIツールとの接続を想定されており、AWSでいうところのRedshift的なものだと考えるとわかりやすいかと。 Data TankはPostg …

HyperLoglogでcount distinctを速くする

こんにちは。俺やで。 HyperLoglogについて書きます。おもしろいです。名前が。 ■1. HyperLoglogとは? count distinctを速くするアルゴリズム 以前、Minhashについて書きました。 (Treasure Dataさんのブログにも載せていただきました。ありがとうござ …

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

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

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

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

no image
いま必要なのは「アナリティクスアプローチ」

こんにちは。 ビッグデータ解析部のakiです。 解析部で、Markezineでの連載をはじめましたのでご紹介です。 いま必要なのは「アナリティクスアプローチ」、ビッグデータ活用の課題とこれから (http://markezine.jp/article/detail/21293) マーケターのかた向け …

Tableau 9.2で郵便番号の特性を地図で可視化してみる

Tableau 9.2から郵便番号地図が表示可能に 弊社ではデータ分析ツールのTableauを利用しています。オーディエンスデータの重複を分析したり、デモグラフィック属性を表示したりするなどデータの可視化に役立ちますTableauでは9.2から日本の郵便番号を用いて地図を可視化できるようになりました …

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

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

Amazon ElastiCache/Redisのパフォーマンス確認

はじめに こんにちは、AudienceOne開発部です。AudienceOne開発部ではいわゆるビッグデータと呼ばれる大量のデータをアドホックあるいは定常的に日々ETLだの集合演算だのをする一方で、様々な大規模データ処理ソリューションを継続的に検証しております。 本記事は、その中でもユーザが保持して …

Google BigQuery / Tableauを使ってみた

TableauからGoogle BigQueryへ接続してみました。 弊社で利用しているTreasureDataからデータ出力してBigQueryへロード、Tableauから接続まで実際に行った手順について記載します。 TreasureDataからAmazonS3へデータ出力 まず、データが蓄積され …