この記事はTreasure Data Advent Calendar 11日目の記事です。
こんにちは、データマイニングチームのy_kawasakiです。(毎回所属が変わってます!)
最近、大量のデータを前に途方にくれていました。TreasureData(以下TD)という、武器を手に入れて、がっつりと戦っているところです。
TDにはHivemallという、Hive上で動くscalable machine learning libraryが実装されています。Hivemallの使い方はgithubのwikiによくまとめられているのですが、それは、あくまで、Hive上で動かすことを前提に書かれています。今回は、TD上で動いているHivemallを使いたいため、一部、変更したりする必要があります。
とりあえず、手を動かしたいということで、どこのご家庭にも常備してある、あやめのデータを使いたいと思います。
データの準備
とりあえず、オーソドックスに2値分類をやってみようと思いますので、あやめのうち、setosaとversicolorの2種に絞ってデータを用意します。setosaを1に、versicolorを-1に置きまえます。さらに、学習用と試験用に適当な割合(一般的には8対2程度らしい)に分けておきます。このデータをTDへアップします。
テーブル名はiris_train、iris_test、変数名としてs_width、s_length、v_width、v_lengthとしておきます。
特徴量行列を作る
特徴量行列は、要素名と値を:でつなぎます。そのための関数も用意されています。
SELECT ROWID() AS rowid, ARRAY(FEATURE('s_width', s_width), FEATURE('v_width', v_width), FEATURE('s_length', s_length), FEATURE('v_length', v_length) ) AS features, species AS label FROM iris_train
これを、tbl_fvとして書き出します。
特徴量行列の正規化
今回用いるクラス分類の手法では、正規化を行ったほうがよいので(※要出典)正規化をします。正規化の手法はいくつかあるのですが、適当に、Z-Scoreを用いたいと思います。
Z-Scoreでは、平均と分散を用いるため、それらを算出するため、一旦、先ほど作った、特徴量行列を展開します。
SELECT rowid, extract_feature(feature) AS feature, extract_weight(feature) AS VALUE FROM tbl_fv LATERAL VIEW explode(features) exploded AS feature
これをtbl_explode_fvとして保存します。
平均と分散を求めます。
SELECT feature, STDDEV(VALUE) AS stddev, AVG(VALUE) AS avg FROM tbl_explode_fv GROUP BY feature
さらに、tbl_statsとして保存しておきます。
続いて、Z-Scoreを求めて正規化された、特徴量行列を作成します。
WITH norm AS( SELECT t1.rowid, t1.feature, zscore(t1.value, t2.avg, t2.stddev) AS zscore FROM tbl_explode_fv t1 JOIN tbl_stats t2 ON t1.feature = t2.feature ), norm_fv AS( SELECT rowid, feature(feature, zscore) AS feature FROM norm WHERE zscore != 0.0 ), train_normalized as ( SELECT rowid, collect_list(feature) AS features FROM norm_fv GROUP BY rowid ) SELECT l.rowid, ADD_BIAS(r.features) as features, l.label FROM tbl_fv l JOIN train_normalized r ON l.rowid = r.rowid
これをtbl_trainとして保存します。
データをかさ増しする
やらなくてもいいのですが、今回はデータセットが小さいので、学習が安定しない可能性が高いので、ガガッと大きくしたいと思います。それから、学習順序に依存しにくくするため、シャッフルも行います。
WITH tbl_train_x AS ( SELECT AMPLIFY(500, uid, features, label) AS (uid, features, label) FROM tbl_train ) SELECT RAND_AMPLIFY(3, 10000, uid, features, label) AS (uid, features, label) FROM tbl_train_x
これを、tbl_train_rxとして保存します。
モデルの作成
いよいよ、モデルの作成です。
SELECT feature, argmin_kld(weight, cover) AS weight FROM ( SELECT TRAIN_SCW(features, label) AS (feature, weight, cover) FROM tbl_train_fv_rx ) t GROUP BY feature
これをtbl_modelとして保存します。
予測
モデルが完成したので、最初に分割したテスト用データの予測を行います。
WITH tbl_test_fv AS ( SELECT ROWID() AS rowid, ARRAY(FEATURE('s_width', s_width), FEATURE('v_width', v_width), FEATURE('s_length', s_length), FEATURE('v_length', v_length) ) AS features, species AS label FROM iris_test ), tbl_test_explode AS ( SELECT rowid, extract_feature(feature) AS feature, extract_weight(feature) AS VALUE FROM tbl_test_fv LATERAL VIEW explode(features) exploded AS feature ), tbl_test_norm_fv AS ( SELECT t1.rowid, t1.feature, zscore(t1.value, t2.avg, t2.stddev) AS value FROM tbl_test_explode t1 JOIN tbl_stats t2 ON t1.feature = t2.feature ) SELECT t.rowid, SUM(m.weight * t.value) AS total_weight, CASE WHEN SUM(m.weight * t.value) > 0.0 THEN 1 ELSE -1 END AS label FROM tbl_test_norm_fv t LEFT OUTER JOIN tbl_model m ON t.feature = m.feature GROUP BY t.rowid
これで、予測が完成です!長いですが雛形を作ってしまえばどんどん行っていくことができます。 実際には、WITH句を駆使してなっがいSQL1つのJOBにしています。
ちなみに、モデルの評価を行ったところ、精度は100%でした。過学習はしてないかな?(まぁ、プロットするとわかりますが簡単に線形分離できそうなデータなんですけどね)
TDを使うと機械学習本来のところに注力することができていい感じです。ビッグデータを楽しみましょう!