【基礎】Treasure Dataでできる機械学習まとめ

#想定読者

 - Treasure Dataは触ったことある（触れる環境がある）
 - SQLはある程度書ける（サブクエリもできる）
 - 機械学習はあんまりやったことない（これからやりたい）

#３行で言うと

- Treasure Dataの機械学習についてまとまった日本語ドキュメントがなかったのでまとめてみた
- 主要な５つの機械学習機能を、とりあえずTreasure Data上で実装できるようになることを目指す
- 一部の機械学習については詳細なSQLや実行結果まで載せるが、一部は外部リンクや公式ドキュメントの紹介のみ


#注意事項
 - この記事は、「とりあえずTreasure Dataで機械学習を動かすこと」を目的とします。精度・パフォーマンスの観点ではベストな実装でない可能性が多分にありますがご了承ください。（良い実装方法があれば是非コメントでお知らせ下さい）

 - 以降、特段の記載のあるもの以外は、[Hivemallユーザーガイド](https://hivemall.incubator.apache.org/userguide/index.html)を参考にしています。不明な点など、適宜原文をご参照ください。


    - Treasure Dataって何？という方は[こちら](https://qiita.com/dich1/items/2de9952efd059ab82ab2)
    - Hivemallって何？という方は[こちら](https://qiita.com/onunu/items/69beeed32a8e852674f0)
 

#Treasure Dataで機械学習？

Treasure DataにはHivemallという機械学習ライブラリが組み込まれており、以下画像のクエリ画面で、Hiveに切り替えるだけで利用可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462447/eeac707e-7e9b-d795-b7ef-6aac01d804fa.png)
SQLベースで実行できるため、Pythonを書かなくても（書けなくても）機械学習ができることが最大のメリットだと感じています。

とはいえ、Hivemallを使うには、種々の関数を覚え、使いこなす必要がありますので、この記事ではそれら機械学習関連の機能をご紹介します。

#この記事で取り上げる機械学習機能

Hivemallユーザーガイドにリストアップされてるものから、個人的に利用頻度が高いと想定する以下の５つをご紹介します。

- [回帰（Regression）](#回帰regression)
- [２値分類（Binary classification）](#２値分類binary-classification)
- [ランダムフォレスト（RandomForest）](#ランダムフォレストrandomforest)
- [リコメンド（Recommendation）](#リコメンドrecommendation)
- [異常検知（Anomaly detection）](#異常検知anomaly-detection)


#データの準備

回帰や分類の機械学習を行う時は、ユーザーガイドにて[インプットフォーマット](https://hivemall.incubator.apache.org/userguide/getting_started/input-format.html)が定められています。
インプットとして準備が必要な必要なデータは
 - `features`　（説明変数）
 - `label`　（目的変数）
の２つです。
Treasure Dataではテーブルの形で用意するので、上記２カラムのテーブルを準備することとなります。

繰り返しですが、上記は回帰や分類のタスクで必要なデータフォーマットであり、それ以外（リコメンドや異常検知）のタスクでは異なるデータを準備する必要があります。


###featuresの準備

features（説明変数）については、配列の形で準備する必要があります。
通常、説明変数には複数の変数（場合によっては100を超える）を使うと思いますが、すべて配列化して１カラムに投入します。

さらに配列の中身は、`インデックス:値`というフォーマットである必要があります。
インデックスは数値でも文字列でもOKのようです。

 - インデックスが数値の例）　  `["1:3.4",  "2:0.5",  "3:0.231"]`
 - インデックスが文字列の例）  `["height:1.5", "length:2.0"]`

 
上記のインデックスフォーマットを準備するための関数`feature`があるので、これを利用しましょう。

この部分は、下記サイトを参考にさせて頂きました。
[Treasure Dataのhivemallで類似ユーザレコメンドを試す](https://ceblog.mediba.jp/post/170605665442/hivemall%E3%81%A7%E3%83%A6%E3%83%BC%E3%82%B6%E5%88%86%E6%9E%90%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B)

```sql:Hive
select
  user_id
  , ARRAY(FEATURE(1,変数１),
          FEATURE(2,変数２),
          FEATURE(3,変数３),
          FEATURE(4,変数４),
          FEATURE(5,変数５),
          FEATURE(6,変数６),
          FEATURE(7,変数７)
          ) as features
    , label
from
  テーブル名
```

上記を実行するとこんな感じになります。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462447/7d235332-6081-e3ac-ccb4-dc0c75475e1d.png)
さて、これで機械学習を実行する準備ができました。
ここからは、個別の分析手法について見ていきます。

　＊本来は、さらにデータの前処理をする必要がありますが、まずは機械学習を回してみましょう。
　　（例えばデータの正規化とか　⇒[Feature Scalingはなぜ必要？](https://qiita.com/ttskng/items/2a33c1ca925e4501e609)）
#Treasure Dataで実行できる主要な機械学習

##回帰（Regression）
[`train_arow_regr`という関数](https://hivemall.incubator.apache.org/userguide/misc/funcs.html#regression)が用意されています


```sql:ユーザーガイドより
SELECT 
  feature,
  argmin_kld(weight, covar) as weight
FROM (
  SELECT 
     train_arow_regr(features,label) as (feature,weight,covar)
  FROM 
     training_data
 ) t 
GROUP BY feature
```

上記を、最初に準備したデータセットに対して実行するとこんな感じです。
各変数の重み（回帰係数）が算出されました。
![スクリーンショット 2020-12-30 10.45.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462447/0457ca38-d856-37bb-1f1a-ee7b8e7e7d12.png)
（回帰の決定係数や残差も出せるかな？と思ったのですが、算出する方法がわからなかったので、ご存知の方がいらっしゃったらご教示頂ければ幸いです。）

ちなみに、単回帰分析であれば、わざわざHivemallを使わなくとも、Prestoで実現可能なようです。
　⇒[Prestoで単回帰分析を行う](https://qiita.com/getty104/items/0f87433fff915659b542)


##２値分類（Binary classification）

[`train_classifier`関数](https://hivemall.incubator.apache.org/userguide/binaryclass/general.html)が用意されています。
###訓練
```sql:ユーザーガイドより
--WITH classification_model as ( ←予測のときに使います
select 
 feature,
 avg(weight) as weight
from (
  select 
     train_classifier(
       add_bias(features), label, 
       "-loss logistic -iter 30"
     ) as (feature,weight)
  from 
     a9a_train
 ) t 
group by feature;
--)
```
↓こちらが実行結果です。変数６を特に重く評価するモデルができあがりました。
![スクリーンショット 2020-12-30 11.43.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462447/d05f5dae-1c2e-2268-6175-e2bcb93d151c.png)

続いて、上記のモデルをテストデータに適用して、labelを予測してみます。
###予測
```sql:ユーザーガイドより
WITH test_exploded as (
  select
    rowid,
    label,
    extract_feature(feature) as feature,
    extract_weight(feature) as value
  from
    a9a_test LATERAL VIEW explode(add_bias(features)) t AS feature
),
--predict as (  ←評価の時に使います
  select
    t.rowid,
    sigmoid(sum(m.weight * t.value)) as prob,
    (case when sigmoid(sum(m.weight * t.value)) >= 0.5 then 1.0 else 0.0 end)as label
  from
    test_exploded t
    LEFT OUTER JOIN classification_model m 
      ON (t.feature = m.feature)
  group by
    t.rowid
--)
```
↓実行結果はこちらです。`prob`が「その人がどの程度label=1でありそうか？」を示すスコアです。（1に近いほど、label=1である可能性が強いと予測されている）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462447/706735c8-21b0-1395-1e8f-88f49cd617af.png)

さて、予測はできましたが、このモデルは実際正確に予測できているのでしょうか？
次は予測精度を評価してみましょう。
###評価
```sql:ユーザーガイドより
WITH submit as (
  select
    t.label as actual,
    p.label as predicted,
    p.prob as probability
  from
    a9a_test t
    JOIN predict p
      on (t.rowid = p.rowid)
)
select 
  sum(if(actual = predicted, 1, 0)) / count(1) as accuracy
from
  submit;
```
↓実行結果です。予測したラベルと、テストデータの実際のラベルの一致度が算出されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462447/e1cc5874-c79d-6898-8d6a-3db69db69013.png)
　＊　＊　＊

ちなみに、`logress`関数も、ほぼ同様の動きをするようです。
`logress`関数については、以下記事で詳細が解説されていますので、こちらをご参照ください。（当記事よりも遥かに詳細かつわかりやすいです。。。）

 - [【超入門】Hivemallで機械学習 〜Treasure Dataでロジスティック回帰編〜（アヤメの種別判定）](http://yebisupress.dac.co.jp/2015/03/17/%E3%80%90%E8%B6%85%E5%85%A5%E9%96%80%E3%80%91hivemall%E3%81%A7%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92-%E3%80%9C%E3%83%AD%E3%82%B8%E3%82%B9%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E5%9B%9E%E5%B8%B0%E7%B7%A8/)
 - [Hive/Hivemallを利用した広告クリックスルー率(CTR)の推定](https://qiita.com/myui/items/f726ca3dcc48410abe45)

　

[ユーザーガイド](https://hivemall.incubator.apache.org/userguide/binaryclass/general.html)によると、`train_classifier`も`logress`もさほど精度は変わらないとのこと。

>However, at the same time, the options -loss logloss -opt SGD -reg no for train_classifier indicates that Hivemall uses the generic classifier as logress. Hence, the accuracy of prediction based on either logress and train_classifier would be (almost) same under the configuration.


##ランダムフォレスト（RandomForest）
`train_randomforest_classifier `、`train_randomforest_regressor`など[複数の関数が用意されています。](https://hivemall.incubator.apache.org/userguide/misc/funcs.html#decision-trees-and-randomforest)

ランダムフォレストについては、詳細な別記事がいくつかありますので、そちらをご紹介するにとどめます。

 - [SQLだけで機械学習するぞ-Hivemall入門-（タイタニック号乗客の生存予測）](https://qiita.com/onunu/items/69beeed32a8e852674f0)

>TRAIN_RANDOMFOREST_CLASSIFIER() 関数では-treesで決定木の数、-attrsでは各説明変数が[Q:量的]か[C:質的]かどうかを明示する必要があります。

>```sql:Hive
SELECT
  TRAIN_RANDOMFOREST_CLASSIFIER(features, label, "-trees 500 -attrs C,C,C,Q,Q,Q,C,Q,C,C")
FROM
  train
```

- [Hivemall を利用した機械学習実践入門（Random Forestによるドラッグストアのセールス予測）](
https://tug.red/entry/2016/03/29/hivemall_kaggle_rossmann_1/)

>ここでは、UNION ALL句を用いることで並列で決定木の学習を行っています。“-attr”オプションには変数が質的変数なものにはC、量的変数にはQを指定します

>```sql:
  SELECT 
    -- C: Categorical Variable, Q: Quantitative Variable
    train_randomforest_regr(features, label, '-trees 20 -attrs C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,Q')
  FROM
    training3
  UNION ALL
  SELECT 
    train_randomforest_regr(features, label, '-trees 20 -attrs C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,Q')
  FROM
    training3
  --（以下略）
```

- [Hivemallを利用したLTV予測（LTVの高いロイヤルカスタマーの予測）](https://techblog.haldata.net/entry/2020/02/28/102526)

>test2テーブルとmodelテーブルをHivemallで用意された”tree_predict”関数に与えることで、LTVの予測値を取得することができます。複数の決定木で推測された値の平均をLTVの予測値とします

>```sql:Hive
SELECT 
  member_id,
  EXP(predicted)-1 as predicted,
  ltv
FROM(
  SELECT
     member_id,
     avg(predicted) AS predicted,
     avg(ltv) AS ltv
  FROM(
    SELECT
      t.member_id,
      tree_predict(p.model_id, p.model, t.features, false) as predicted
      t.ltv
    FROM
      model p
      LEFT OUTER JOIN test2 t
  ) t1
  group by
    member_id
) t2;
```


##リコメンド（Recommendation）

調べた中では、
- `minhash`
- `train_mf （train_mf_sgd）`
- `train_slim`
- `協調フィルタリング`
の４つが使えそうです。


`minhash`については、こちらのサイトにて詳細解説されています。
[HivemallでMinhash！〜似てる記事を探し出そう。〜](http://yebisupress.dac.co.jp/2015/06/26/hivemall-minhash/)

ただし、上記記事の筆者も仰られていますが、きちんと精度の高いリコメンドをしたい場合は、`train_mf`の方が適しているようです。

>```sql:Hive
select
  minhash(id, words, "-n 100") as (clusterId, rowid) –"’-n 100’は100個hash関数を使うためのオプション"
from
  news20;
```

その`train_mf`についてはこちら。
[HivemallでMatrix Factorization（まだ観ていない映画のリコメンド）](https://qiita.com/myui/items/dccb4f58799f080e24ab)

>```sql:Hive
select 
  train_mf_sgd(userid, movieid, rating, "-factor ${factor} -mu ${mu} -iter ${iters}") as (idx, u_rank, m_rank, u_bias, m_bias)
from 
  training
```

`train_slim`, `協調フィルタリング`については、日本語の記事が見つからなかったので、ユーザーガイドをご参照下さい。

 - [train_slim]((https://hivemall.incubator.apache.org/userguide/recommend/movielens_slim.html))

>```sql:ユーザーガイドより
select
  i, nn, avg(w) as w
from (
  select
    train_slim(i, r_i, knn_i, j, r_j) as (i, nn, w)
  from (
    select * from slim_training_item
    CLUSTER BY i
  ) t1
) t2
group by i, nn
```


 - [協調フィルタリング](https://hivemall.incubator.apache.org/userguide/recommend/item_based_cf.html)
（一発で実現する関数はなく、コサイン類似度とeach_top_kを組み合わせ）

私は、リコメンドといえば協調フィルタリングをまず想起するのですが、Treasure Dataでやろうとすると意外と手間がかかりそうです。

>```sql:ユーザーガイドより
--実際のクエリはめちゃくちゃ長いので一部を転記
WITH similarity as (
  select
    o.itemid,
    o.other,
    cosine_similarity(t1.feature_vector, t2.feature_vector) as similarity
  from
    cooccurrence o
    JOIN item_features t1 ON (o.itemid = t1.itemid)
    JOIN item_features t2 ON (o.other = t2.itemid)
),
topk as (
  select
    each_top_k( -- get top-10 items based on similarity score
      10, itemid, similarity,
      itemid, other -- output items
    ) as (rank, similarity, itemid, other)
  from (
    select * from similarity
    where similarity > 0 -- similarity > 0.01
    CLUSTER BY itemid
  ) t
)
```

##異常検知（Anomaly detection）

そもそも異常検知には、大きく2つのやり方があるらしいです。

- タイプ1: Outlier（外れ値）
- タイプ2: Change point（変化点）　
- （⇒タイプ2のうち、特に時系列の変化に特化したものをタイプ3とする場合もあり）


ちなみにこちらの記事によると⇒[Yahoo!の異常検知フレームワーク"EGADS"](https://takuti.me/ja/note/yahoo-egads/)

>（Change-pointと）Outlierとの違いは、Change-pointはもっと大域的かつ中長期的な変化を発見する点

とのこと。

要は、

- Outlier
    - 局所的な異常検知（PVだったら、特定の記事がバズって1日だけスパイクした場合を検知）
- Change Point
    - より大局的な変化を検知（Googleの検索アルゴリズムが変わって、PV数のトレンドが変わったポイントを検知）

っていうイメージですかね。

Hivemallにも`Outlier（外れ値）`、`Change Point（変化点）`の両方の関数が用意されています。


###Outlier（外れ値）

以下のようなクエリで、平均からのユークリッド距離が遠いデータを洗い出すようです。
（[ユーザーガイドはこちら](https://hivemall.incubator.apache.org/userguide/anomaly/lof.html)）

```sql:ユーザーガイドより
select
  each_top_k(
    -${k}, t1.rowid, euclid_distance(t1.features, t2.features), 
    t1.rowid, 
    t2.rowid
  ) as (rank, distance, target, neighbour)
from 
  train_normalized t1
  LEFT OUTER JOIN train_normalized t2
where
  t1.rowid != t2.rowid
```

###Change Point（変化点）

変化点の検知には`sst`という関数が用意されています。
（Singular Spectrum Transformationの略）

（[ユーザーガイドはこちら](https://hivemall.incubator.apache.org/userguide/anomaly/sst.html)）

```sql:ユーザーガイドより
SELECT
  num,
  sst(value, "-threshold 0.005") AS result
FROM
  timeseries
ORDER BY num ASC
```

　

特に時系列のChange Pointを検知したい場合には、`changefinder`という関数が用意されています。

Changefinderの仕組みについてはこちらを参照
　⇒[changefinderとはなにかを数式なしで解説](https://qiita.com/anntoque/items/8f2bc9672922f31eccce)

（[ユーザーガイドはこちら](https://hivemall.incubator.apache.org/userguide/anomaly/changefinder.html)）

```sql:ユーザーガイドより
SELECT
  num,
  changefinder(value, "-outlier_threshold 0.03 -changepoint_threshold 0.0035") AS result
FROM
  timeseries
ORDER BY num ASC
;
```


#まとめ
Treasure Dataの機械学習についてまとまった日本語のドキュメントがなかったので、ひとまず主要なものを列記しました。
随時、実際に動かしてみた内容も別記事で追記していきたいと思います。