BigQuery MLで行列分解を用いたレコメンドモデルを構築した話 Vol.1

こんにちは！LIFULLエンジニアの吉永です。

本日はBigQuery ML（以降BQML）で行列分解を用いたレコメンドモデルを構築するまでの流れを自身の理解促進と忘備録代わりにご紹介します。
※私は機械学習に関してはド素人ですので誤った表現、記述などあるかもしれませがご容赦ください。

## BQMLとは？

名前の通り、BigQueryに保存されているデータを使った機械学習が簡単にできるサービスです。

https://cloud.google.com/bigquery/docs/bqml-introduction?hl=ja

詳細は上記の公式ドキュメントを参照していただければと思いますが、私なりの理解で要約すると下記になります。

- BigQueryに保存されているデータを別の領域にコピーするなどが必要なく機械学習ができる
    - 機械学習をする場合ビッグデータを機械学習環境へコピーするなどが必要だが、BigQueryから直接参照できるので効率が良い
- [Googleが用意したトレーニングされたモデル](https://cloud.google.com/bigquery/docs/bqml-introduction?hl=ja#internally_trained_models)が利用できる
    - レコメンドモデルを作りたいなら[行列分解](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-matrix-factorization)モデルを利用すればすぐに構築できる
- 機械学習させる為のパラメーターや設定をSQLで書ける
    - 学習させたいデータをSQLでSELECTするので、SQLの知識があれば直感的に機械学習を開始できる


## BQMLでどんなレコメンドモデルを作ったのか？

[LIFULL HOME'S](https://www.homes.co.jp/)で賃貸物件を検索する際の条件（家賃上限は8万円、駅徒歩20分以内のような条件）をレコメンドするモデルを構築しました。

と言ってもユーザーに検索条件をレコメンドするわけではなく、モデルからレコメンドされた検索条件で物件を検索した結果をレコメンド物件として出力するのに用いています。

[LIFULL HOME'S](https://www.homes.co.jp/)の検索条件は多岐に渡り、レコメンドするにしてもどのような組み合わせをレコメンドするのか？が非常に難しく、実装するのはなかなか骨が折れました。

## 要件定義

今回構築したレコメンドモデルは[LIFULL HOME'S](https://www.homes.co.jp/)でユーザーが物件の空室状況を問合せた後に送信しているメール内に掲載する、問合せた物件と似た条件の物件をレコメンドする為のモデルになります。

なので、当初の要件としては「問合せた物件と似た条件の物件をレコメンドしたい」になります。

物件そのものをレコメンドすることも手段としてはありえたのですが、日々更新されていく物件情報に追従する為には定期的に機械学習を行ってモデルの鮮度を保つ必要があり、MLOps的な観点も考慮する必要がありました。

今回はひとまずPoCとしてBQMLを活用したレコメンドモデルを構築して、一度PDCAサイクルを回してみたいというのが優先度が高かった為、モデルを高頻度で学習させて更新していく形は一旦なしとなり、学習サイクルを長くしても影響の少ない手法として、物件そのものではなく、物件を検索する条件をレコメンドする方向性に落ち着きました。

上記を踏まえて要件をもう少し具体化した結果、「ユーザーが物件を問合せた際に設定していた検索条件をベースに、より良い物件に出会える確率の高い検索条件をレコメンドしたい」になりました。

## 学習用データについて

### 未加工の元データ

LIFULLでは下記のようなデータが既にBigQueryに保存されていました。
実際のテーブル構造そのものは載せられないのですが、イメージが湧くようなものを記載すると下記のようなテーブルです。

| カラム内容 | データ型 | 凡例 | 備考 |
| ---- | ---- | ---- | ---- |
| ユーザーID | 文字列 | abcdefghijklmn12345 | サイト内でユーザーを識別可能なID |
| 問合せた物件ID | 文字列 | b-1234567890 | サイト内で物件を識別可能なID |
| 物件問合せ時に設定していた家賃下限 | 浮動小数点 | 3.0 | 整数が万円単位 |
| 物件問合せ時に設定していた家賃下限 | 浮動小数点 | 8.5 | 整数が万円単位 |
| 物件問合せ時に設定していた専有面積下限 | 数値 | 20 | 平方メートル単位 |
| 物件問合せ時に設定していた専有面積下限 | 数値 | 40 | 平方メートル単位 |
| 物件問合せ時に設定していた駅徒歩分数 | 数値 | 10 | 駅から物件までの徒歩分数 |
| 物件問合せ時に設定していた都道府県 | 文字列 | 東京都 |  |
| 物件問合せ時に設定していた市区町村 | 文字列 | 千代田区 |  |
| 物件問合せ時の日時情報 | 日時 | 2023-09-12 12:20:30 |  |

上記のテーブルにサイトで問合せが入るたびにレコードがインサートされると思っていただければと思います。

そして、実際にはもっと多くのカラムがあり、例えば`2階以上`、`駐車場付き`、`オートロック`などのようなこだわり条件も保存されています。
こだわり条件もレコメンドする上では非常に重要なデータであることは認識はしていたのですが、こだわり条件を加味した行列分解になると学習データの組み合わせがあまりにも細分化されすぎてしまい、期待する結果を得られなそうということで、第一弾として必要最低限な検索条件の組み合わせに落ち着いたという背景があります。

### 学習用に加工したデータ

上記のデータを元にBQMLで学習をさせる用のテーブルへ一部作り変えを行いました。

| カラム内容 | データ型 | 凡例 | 備考 |
| ---- | ---- | ---- | ---- |
| 検索条件ID | 文字列 | abcdefg1234567890 | 検索条件をjson文字列にした後でハッシュ化した値 |
| 検索条件json | 文字列 | {"pref": "東京都","yachin_min": 3.0,"yachin_max": 8.5} | 検索条件をjson文字列化したもの |
| 検索条件が問合せに利用された回数 | 数値 | 10 | この検索条件の組み合わせが問合せ時に利用された通算回数 |

元データの各カラムをjson文字列として、その文字列を入力して得たハッシュ値を主キーとするレコードを作成し、更に問合せ時に利用された回数の通算を集計しました。

### 行列分解で学習させたデータとレーティングについて

行列分解は2次元のマトリックスでユーザーとアイテムの組み合わせにレーティングを加味したデータを学習させてレコメンドモデルを作成します。

分かりやすい例だとECサイトのレコメンドモデルを作る際はECサイトのユーザーとアイテムとアイテムのレビュー平均値と購入回数を加味したレーティングなどを行うことで、似た傾向を持ったユーザーへアイテムをレコメンドできるという理屈です。

もう少し分かりやすいように表にしてみます。

| ユーザー | モバイルバッテリー | 充電ケーブル | スマホケース |
| ---- | ---- | ---- | ---- |
| Aさん | 3 |  | 4 |
| Bさん |  | 2 | 3 |
| Cさん | 2 | 4 |  |
| Dさん | 3 | 3 | 3 |
| Eさん |  | 2 |  |
| Fさん | 2 |  |  |

上記の表はユーザーとアイテムの組み合わせで、交差しているマスに入力されている数値がレーティングだと思ってください。
レーティングが例えば5点満点のレビュー評価をつけるものだとして、Aさんはモバイルバッテリーに評価3をつけた、スマホケースには評価4をつけたというイメージです。
この中で、例えばFさんにアイテムをレコメンドしたいとなった時、Fさんと似た傾向のユーザーとして、モバイルバッテリーに同じ評価2をつけているCさんが候補になります。この時、Cさんは充電ケーブルに評価4をつけているので、Fさんにも同じく充電ケーブルを購入してもらえれば評価4を付けてもらえて満足してもらえる可能性が高いかもしれません。

大分大雑把な説明ですが、行列分解によるレコメンドイメージはこんな感じです。

よって行列分解では、ユーザー、アイテム、レーティングを何にするかを決める必要があります。

今回検索条件のレコメンドモデルを学習させるにあたっては以下のようにしました。

ユーザー、アイテム：共に検索条件IDによるマトリックス。当然同一のID同士の組み合わせも出来上がるが、その組み合わせはレーティング対象外
レーティング：検索条件と条件が近くて、問合せ時に利用された回数が多いほどレーティングが高くなる

レーティングに関しては、例えばユーザー側の検索条件の家賃上限が8万円だとしたら、アイテム側の検索条件の家賃上限がそのプラスマイナス5,000円以内なら条件が近いとみなすなどです。
これをそれぞれの検索条件毎に比較し、最終的に近い検索条件の場合は問合せ利用回数と係数をかけ合わせてレーティングを決定させました。

なので、行列分解によって学習させたモデルに期待していたことは「ユーザーが問合せ時に利用した検索条件に似た検索条件の中で他のユーザーも利用していた回数が多い検索条件をレコメンドしてもらう」でした。

## まとめ

今回はBQMLで行列分解を用いたレコメンドモデルの学習させるまでの一連の流れ、考え方の整理などを紹介しました。
次回以降はもう少し具体的にBQMLでの学習手順など踏まえて紹介できればと思います。

最後まで読んでいただきありがとうございました。
また次の記事でお会いしましょう。