FANCOMI Ad-Tech Blog

株式会社ファンコミュニケーションズ nend・新規事業のエンジニア・技術ブログ

TreasureDataで機械学習してみる

この記事は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を使うと機械学習本来のところに注力することができていい感じです。ビッグデータを楽しみましょう!