ブログ

Google Colaboratoryでニューラル協調フィルタリング

ソフトウェアエンジニアの冨田です。
GPUを買うまででもないし、クラウドでGPUのインスタンスを借りてくるのは高くつきそうで怖いけれど、手元のデータを学習させてみたいな、と思ったことはないでしょうか。私はあります。
そんな私も、Google Colaboratoryというサービスを使うと高額請求に怯えることなく手軽に学習を試してみることができます。Google ColaboratoryはGoogleのクラウドで実行されるJupyter Notebook環境です。面倒な環境構築なしにJupyter Notebook環境が使用できることに加え、GPUやTPUが無料で使用できることから最近話題になっています。
今回の記事では、Google Colaboratory環境を活用し、簡単なニューラル協調フィルタリングを動かしてみることで、Colaboratoryの便利さを実感してみようと思います。

ニューラル協調フィルタリング

まずは実装する題材であるニューラル協調フィルタリングについて説明しておきます。詳細は以前のレコメンドについての記事に譲りますが、ユーザーのアイテムへの評価をもとに推薦を行う協調フィルタリングという手法があります。それをニューラルネットで実装したものをニューラル協調フィルタリングと呼んだりします。
ユーザーID、アイテムID、評価値の組が与えられた時に、下記のようなモデルで評価値の予測を行います。図はNeural Collaborative Filtering [Xiangnan He+, 2017]1 から引用しています。

Neural Collaborative Filtering [Xiangnan He+, 2017] より引用

Neural Collaborative Filtering [Xiangnan He+, 2017] より引用

使用するデータセット

今回はレコメンドのベンチマークとしてよく使われる、MovieLens10Mデータセットを使用します。このデータセットには72,000ユーザーによる1万種類の映画へのレビュー(1〜5の値)が約1000万件ほど含まれています。
今回はユーザーIDと映画IDが与えられた時に、ユーザーのレビューのスコア(1〜5)を予測することが目標となります。

Google Driveからデータを読み込む

まずはMovieLensのページから10Mデータセットをダウンロード、展開します。Google ColaboratoryではGoogleドライブに保存されているデータを読み込むことができるので、今回は展開したデータをGoogleドライブに保存します。
ColaboratoryのPython3ノートブックを開き、下記コードを実行し、指示に従って認証コードを取得するとGoogle Driveの内容が /drive にマウントされます。他にもGoogle Driveの内容を見る方法はあるようですが、この方法が一番お手軽に思えます。

from google.colab import drive
drive.mount('/drive')

データセットをGoogle Driveの data/ml-10m/ratings.dat という場所に保存してある場合、下記のようなコードですぐにデータを読み込むことができます。

csv_path = '/drive/My Drive/data/ml-10m/ratings.dat'
df = pd.read_csv(
    csv_path, sep='::', names=['user', 'item', 'rating', 'timestamp'], dtype=float)
print(df.head(3))
# user item rating timestamp
# 0 1.0 122.0 5.0 838985046.0
# 1 1.0 185.0 5.0 838983525.0
# 2 1.0 231.0 5.0 838983392.0

この後ユーザーID・映画IDを0からの連番に直す、レビューのスコアは1〜5だと扱いにくいので0〜1に変換するという前処理を行い、8:2の比率で学習データと検証データに分割しました。(コードは省略)

モデルの準備

細かい解説は省きますが、ニューラル協調フィルタリングの図をTensorFlow/Kerasを使ってコードに落としてみたものが下記になります。Embedding Layerの次元は64としてあります。

from tensorflow import keras
embedding_size = 64
user_size = 69878
item_size = 10677
user_input = keras.layers.Input(shape=(1,), name='user_input')
item_input = keras.layers.Input(shape=(1,), name='item_input')
user_embedding = keras.layers.Embedding(
    user_size, embedding_size, input_length=1, name='user_embedding')
item_embedding = keras.layers.Embedding(
    item_size, embedding_size, input_length=1, name='item_embedding')
user_latent = keras.layers.Flatten(name='flattened_user')(user_embedding(user_input))
item_latent = keras.layers.Flatten(name='flattened_item')(item_embedding(item_input))
merged_input = keras.layers.concatenate([user_latent, item_latent], name='merged_input')
out = keras.layers.Dense(100, activation=tf.nn.relu)(merged_input)
out = keras.layers.Dense(50, activation=tf.nn.relu)(out)
out = keras.layers.Dense(10, activation=tf.nn.relu)(out)
out = keras.layers.Dense(1, activation=tf.nn.sigmoid)(out)
model = keras.models.Model(inputs=[user_input, item_input], outputs=out)

GPUを利用して学習してみる

Google ColaboratoryはデフォルトではCPUを使う設定になっているようなので、まずはCPUで学習してみます。バッチサイズ1024で学習してみたところ、1エポックあたり260秒ほどかかっていました。
では、早速GPUを使用した学習を試してみます。ノートブックのメニューバーから[ランタイム]→[ランタイムのタイプを変更]でノートブックの設定を開き、ハードウェアアクセラレータの項目でGPUを選択します。
コードは1文字も変えずに再度ノートブックを実行すると、今度は1エポックあたり70秒ほどで終了しました。確かに学習速度が早くなっていますね!
損失関数にはMean Squared Errorを使用し、Mean Absolute Error (MAE) を指標として見ていたのですが、4エポック学習を終えたところで検証セットのMAEが0.1540となり、それ以降は過学習が進むという動きでした。スコアは前処理の時に0-1に収まるように正規化しているので、もとのMAEに変換すると0.6160に相当します。

まとめ

今回はGoogle Colaboratoryを利用し、ニューラル協調フィルタリングの学習をしてみました。慣れ親しんだJupyter Notebook環境が環境構築なしで使え、学習データをGoogle Driveにさえ保存しておけば転送などの面倒な作業なしで学習が始められました。
GPUや(今回は利用しませんでしたが)TPUも設定を変えるだけで無料で使用することができ、非常に便利です。本格的な学習を行う際には連続使用できる時間やメモリに制限に注意が必要ですが、小さな実験を行うには非常に便利なサービスだと思います。

おまけ:ジャンル情報も使ってみる

MovieLensのデータセットには、映画のジャンルに関する情報も付与されています。ジャンルはアクションやSFなど18種類に分類されており、各映画ごとに複数個のジャンルがラベル付けされています。
先ほど紹介したモデルに少し変更を加え、ユーザーと映画のEmbeddingに加え、映画のジャンル情報も活用するモデルを用意しました。先ほどの図のNeural CF LayerのLayer 1に繋がるジャンルベクトルを新たに追加するイメージです。
ジャンルベクトルは20次元2とし、タグ付けされているジャンルに対応する要素が1に、それ以外の要素は0になるようなベクトルとしました。
学習にかかる時間は1エポックあたり5秒ほど伸びましたが、検証セットでのMAEは0.1528となりました。今回はジャンル情報を追加しただけですが、更なる映画のメタデータやユーザーのメタデータなども使用できるのであれば、さらにモデルの性能が良くなる3かもしれませんね。

Notes


  1. https://dl.acm.org/citation.cfm?id=3052569  
  2. ドキュメントにはジャンルは18種類と書いてありましたが、実際には「ジャンル情報なし」と「IMAX」という記述がなされていることもあったため、それらもラベルとみなし20次元としました。 
  3. 今回はMAEの値を見ていましたが、この数値が良いからといって実際に”良い”レコメンドができるモデルであるとは限らないことには注意が必要です。最終的にはA/Bテストなどオンラインでの評価を行ってモデルの良し悪しを判断する必要があります。