BigQuery ML でロジスティック回帰してみる

こんにちは。nana music でサーバーサイドエンジニアをしている @_kobacky です。
これは nana music アドベントカレンダー2日目の記事になります。この週末で BigQuery ML で遊んでみたので、今回はそれについて簡単にまとめてみようと思います。

この記事では下記のような内容について書きます。

- BigQuery ML の概要
- BigQuery ML でロジスティック回帰してみる
- BigQuery ML が出力する結果を理解するためのちょっとした説明

また今回とりあえず BigQuery ML を触ってみることを目的とするので、ちゃんと性能を出すためのデータ分析的な話はありません。[^1]
[^1]: 次章の例での説明変数もとりあえず簡単に出せるものを使ったのみです。予めご了承ください m(_ _)m
BigQueryで集計したりはできるけど機械学習とかよくわからん・・みたいな人を対象読者として意識してみました。

## BigQuery ML の概要
BigQuery ML は Google が提供しているデータウェアハウスサービスである BigQuery 上で動作する Machine Learning の機能です。
今年の夏に発表され現時点では β 版として線形回帰(数値予測に使う)と二項ロジスティック回帰(2値分類に使う)のみが提供されています。

BigQuery 上の SQL のみで学習・予測・評価などが完結するのが特徴の一つです。
nana においてもマーケティング担当者などが BigQuery を使って集計をしたりしていますが、BigQuery ML を使えばそういった方たちも簡単に機械学習やってみることが可能になるというのがいいなぁと思っています。

## BigQuery ML のロジスティック回帰を使ってみる
良く言われることですが、何か勉強したいものがある時はとりあえず触ってみるのが一番だと思います。なのでとりあえずやってみましょう。機械学習に馴染みのない方は良くわからないことも出てくるかなと思いますが、次章でちょっと説明するのでまずは細かいことはわからなくても読み進めてみてもらえればと思います。
今回はせっかくなので nana のデータ使ってやってみようと思い、下記のような感じで新規ユーザーの1ヶ月後再訪予測という問題設定にしてみました。

- ユーザーの属性や登録後1週間の行動量を説明変数とする
- 登録後30日後から1週間の間にアプリを利用したか否かを目的変数とする

### 入力データ作成
まずは BigQuery のデータを集計して下記のようなテーブル(signup_users)を作成しておきます。

| カラム名 | 内容 | 用途 |
| --- | --- | --- |
| user_id | ユーザーID |  |
| signup_time | ユーザー登録した日時 | データの絞込 |
| post | ユーザー登録後1週間のうちに音源を投稿した数 | 説明変数 |
| play | ユーザー登録後1週間のうちに投稿を再生した数 | 説明変数 |
| follow | ユーザー登録後1週間のうちにフォローしたユーザーの人数 | 説明変数 |
| follower | ユーザー登録後1週間のうちにフォローされたユーザーの人数 | 説明変数 |
| reaccess | ユーザー登録後30日-37日の間にアクセスがあるか否か | 目的変数 |

ちなみに nana のアプリを知らない人は今ひとつデータについてピンと来ないかもしれませんが、その場合は[ぜひ nana を使ってみて下さい](https://nana-music.com/)！(ちゃっかり宣伝)

### モデル作成
先ほど作ったデータを使って[ドキュメント](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create)を参考にモデル作成のクエリを書いてみます。
今回は今年8月の登録ユーザーのデータ用いました。

```sql
CREATE MODEL
  `dataset_name.regression_reaccess`
OPTIONS
  ( model_type='logistic_reg',
    max_iterations=10 ) AS
SELECT
  post,
  play,
  follow,
  follower,
  country,
  reaccess as label
FROM
  `dataset_name.signup_users`
WHERE
  signup_time >= TIMESTAMP("2018-08-01 00:00:00")
  AND signup_time < TIMESTAMP("2018-09-01 00:00:00")
```

- OPTIONSの中にパラメータを指定します。model_type に `logistic_reg` を指定しているのでロジスティック回帰になります。
- AS 以降の SELECT 文で学習データを指定します。説明変数と目的変数として利用するカラムを指定します。目的変数は `label` というカラム名にします。
- country のような名義尺度は自動的に `one-hot encoding` されます。すなわち country の場合は選択された国だけが 1 で他国は全て 0 という形の配列に展開されます。

実行の結果、データセット内に `regression_reaccess` が作成されます。
選択するとモデルの詳細として下記のような情報が確認できます。学習がどんな感じで進んだかをパッと確認できるのは面白いですね。

<img width="526" alt="スクリーンショット 2018-12-02 14.21.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/18814/d6007b8c-f9cc-fcfe-5361-bfd12311c7cf.png">



- 評価データの損失値が表示されていますが、今回 OPTIONS の data_split_method を指定せずデフォルトの auto_split モードになっている(且つ、モデル作成のために指定したデータが 500 件以上ある)ためデータの一部が自動的に評価データとして扱われています。[^2]
- 今回 max_iterations = 10 で指定していますが、No.0 〜 No.7 の8回のイテレーションで終わっていることがわかります。これは OPTIONS の min_rel_progress を指定せずデフォルトの 0.01 になっているので、 イテレーションの最大回数になる前に学習が停止しているということです。実際結果を見てみると No.6 と No.7 の損失値の差分が No.6 の損失値(0.3607)の 1% より小さくなっているために学習が停止したことがわかります。

[^2]: 一部のデータを学習に使わず評価データとして用いることで過学習が防がれます。

### 予測
では作成したモデルを使って [ドキュメント](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-predict)を参考に、9月の登録ユーザーの再訪を予測してみます。

```sql
SELECT
  *
FROM
  ML.PREDICT(MODEL `dataset_name.regression_reaccess`,
    (
    SELECT
      play,
      post,
      follow,
      follower,
      country
    FROM
      `dataset_name.signup_users`
    WHERE
      signup_time >= TIMESTAMP("2018-09-01 00:00:00")
      AND signup_time < TIMESTAMP("2018-10-01 00:00:00")))
```

予測対象のユーザー一人につき一行、下記のような予測結果が得られます。

<img width="737" alt="スクリーンショット 2018-12-02 4.01.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/18814/b07beae2-45af-3a92-aad4-e5a7fff61ce1.png">

- predicted_label_probs.prob の値が予測の確からしさを表しています。この結果の場合、 `true の値 > false の値` となっているので、予測結果である predicted_label が true となっています。

### その他
下記のようなこともクエリ一つで行えます。

- [モデルの評価](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create)ができます。ロジスティック回帰の場合は分類タスクなので precision rate や racall rate などの指標を計算できます。
- [重みの取得](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-weights)ができます。どの説明変数が目的変数の分類に寄与しているかの参考になります。


## 出力結果の内容を理解するために
前章でやってみたように、実際使ってみると割と簡単に結果は出せることがわかりました。
ただそもそもどうやって学習・予測しているのかを大まかにでも知らないと BigQuery ML が表示した結果が何を意味しているのかわかりにくいと思いますので、簡単にイメージだけ説明します。(既によくわかってる人は読まなくて大丈夫です)

すごく単純化した図ですが、下記の図で考えてみます。

<img width="586" alt="スクリーンショット 2018-12-02 14.18.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/18814/bdfcf27f-e3a6-0a0f-1eb3-7d033ddcec16.png">


- 横軸・縦軸は投稿数・フォロー数などの説明変数の値を表します
- 点はデータを表し、色は実際に再訪したか否かのラベルを表します
- 赤・青の線はモデルがデータを分類する境界線を表します (赤より青の方が良い境界線)

この図を使ってものすごく大雑把に説明すると、学習・予測とは下記のように説明できます。

- 学習：ラベルのわかっているデータを用いて、図中の青線のようにデータを正確に分類する境界線を探すこと
- 予測：ラベルのわからないテストデータを図の中にプロットし、境界線より上か下かによってどちらのラベルであるかを予測すること

学習において境界線を探すステップは下記のような感じになります。

1. その時点の境界線を元にデータを分類してみて、どれぐらい分類を間違えてるのかの指標である損失を計算する
2. 損失が少なくなるであろう方向に線を動かす (各説明変数の重みを更新することで線が動く)
3. 「1」に戻る

これを意識した上でモデル作成の結果をもう一度見てみると下記のことが確認できます。

- 繰り返しが8回あるので、上記 1 〜 3 を 8 回繰り返している
- 繰り返しが進むごとに損失が段々小さくなり、間違えにくいモデルになっていっている

学習が完了すると、各説明変数の重みが決定します。
予測においては、そのデータの説明変数と重みの値とを用いて前章でみた `predicted_label_probs.prob` の値が計算され、その値が閾値(今回はデフォルト値で0.5)より大きいか否かによってそのデータが境界線の上下どちらかにあるかを知ることができます。

## まとめ
- BigQuery ML のロジスティック回帰を使ってみました
- BigQuery ML の出力結果を材料にしつつ、分類タスクの機械学習の考え方を少しだけ説明してみました

個人的な話ですが、筆不精な僕は今回初めてアドベント・カレンダーに投稿してみたんですが、軽いノリで何かやってみよう・書いてみようと思うきっかけを提供してくれるのはアドベントカレンダーの良いところやなぁと思いました！[^3]

[^3]: ただし軽いノリでエントリーして当日あたふたするのは僕の悪いところ・・

弊社 nana music のアドベントカレンダーはまだまだ続きますので、引き続きお楽しみください〜。明日は nana の DevOps の要たる @Carlin がなんか書きます！