論文の再現実験: Neural Graph Collaborative Filteringを実装してみた
本記事では「Neural Graph Collaborative Filtering (NGCF)」と呼ばれる、グラフ構造を使った推薦システムの手法を自分で実装して論文の再現実験をしてみたことについて書きます。近年の推薦システムの研究では、ユーザーとアイテムの特徴量だけでなく、ユーザーとアイテム間の関係性を考慮した手法が注目されています。NGCFもその一つで、ユーザーとアイテムのグラフ構造を学習することで、より高い精度で推薦を行うことができます。本記事では、まずNGCFの手法や特徴について解説し、その後に実装と再現実験の方法を紹介します。
Neural Graph Collaborative Filteringとは?
NGCFは、SIGIR'19 に採択された論文「Neural Graph Collaborative Filtering」で提案されました。
NGCFは、ユーザとアイテムの相互作用を表すグラフ構造を利用して推薦を行う機械学習モデルです。具体的に言うと、グラフ畳み込みネットワーク (Graph Convolutional Network, GCN) を用いて、ノード間の情報を交換することで、高度な特徴表現を学習することができます。このようにして学習された特徴表現を用いて、推薦が行われます。
実装
実装にはPyTorchを使い、実験はNvidia Tesla T4を用いて行いました。ハイパラやデータセットは論文と同一のものを用いました。Poetryを使っている方であれば
poetry install poetry run python main.py
だけで実行できますのでぜひ試してみてください。モデルの肝の部分は↓のように実装できます。
class NGCF(nn.Module): # ... def forward(...): # ... ego_embeddings = torch.cat( [self.embedding_dict["user_emb"], self.embedding_dict["item_emb"]], 0 ) all_embeddings = [ego_embeddings] for k in range(len(self.layers)): # Eq. (7) in the paper side_embeddings = torch.sparse.mm(A_hat, ego_embeddings) sum_embeddings = ( torch.matmul( side_embeddings + ego_embeddings, self.weight_dict[f"W_gc_{k}"] ) + self.weight_dict[f"b_gc_{k}"] ) bi_embeddings = torch.mul(ego_embeddings, side_embeddings) bi_embeddings = ( torch.matmul(bi_embeddings, self.weight_dict[f"W_bi_{k}"]) + self.weight_dict[f"b_bi_{k}"] ) ego_embeddings = nn.LeakyReLU(negative_slope=0.2)( sum_embeddings + bi_embeddings ) ego_embeddings = nn.Dropout(self.mess_dropout[k])(ego_embeddings) norm_embeddings = F.normalize(ego_embeddings, p=2, dim=1) all_embeddings += [norm_embeddings] # Eq. (9) in the paper all_embeddings = torch.cat(all_embeddings, 1)
ego_embedding
がグラフ上を伝搬させるもので、全ユーザ・アイテムのembeddingをひとまとめにした行列になっています。A_hat
はインタラクション行列から作成したラプラシアン行列 (詳しくは論文の(8)式を見てください)です。これらを使って for文の中で各層での畳込み操作をしています。最終出力で得られるユーザ・アイテムのembeddingは各層のそのユーザ・アイテムのembeddingをくっつけたものになっています。
結果
400 epochs学習させたときのRecall変化は以下のようになりました。
400 epochs後の各データセットでの指標は、
Dataset | Recall@20 | NDCG@20 | Precision@20 |
---|---|---|---|
Gowalla | 0.1538 | 0.1295 | 0.0472 |
Amazon | 0.0311 | 0.0239 | 0.0132 |
論文にはRecallとNDCGの結果しか載っていないのですが、Precisionは少し他と比べると低いようです。
また、モデルの性能を比較するためにMatrix Factorization + BPR Loss に対しても実験してみました。データセットはGowallaでbatch sizeや学習率はNGCFと同じにしました。結果 Recall@20 は 0.1400 となりNGCFよりかは9 %ほど評価値が低いという結果になりました。MFでの論文の値は Recall@20 = 0.1291 なので論文よりかは高い値が出ています。何回か実験してみるとEarly stopのかかるところが実験によって結構ばらつき、結果として評価値がそこそこブレることがわかりました。なのでEarly stopの基準を論文で設定されているものより多くすればMFの性能は論文で示されている値よりもよいものになりそうです。しかし、MFとNGCFの評価値の差は10 %近くあるのでこのブレを考慮してもNGCFの方が優れたモデルであるということはできるでしょう(ちゃんとは検証できてないですが)。
ただ、学習が進む速度は当たり前ですが遥かにMFのほうが早く、 Gowalla だと
という感じで、思ったよりNGCF遅いなといった感じでした。なので例えば実務でMFを使っていたとして、10 %精度が良くなります、でも学習時間は15倍に伸びます、CPUだともっと遅くなるのでGPUも使いたいです、はちょっとう〜んとなると思います。
感想
今回はじめてグラフ系のモデルを実装・実験してみたのですが、学習に思ったより時間がかかるという印象でした。Amazon Bookのデータセットでは400 epoch回すのに4日ほどかかりました。NGCFをより効率化したモデルであるLightGCNについても今後実装をしてみたいと思いました。
あと余談ですがNGCFはGPUメモリ思ったより使わないな、というのは結構驚きでした。↓はGowallaデータセットで訓練している最中の の結果ですが、1 GBしか使っていなかったので最初は三度見くらいしました。
+-----------------------------------------------------------------------------+ | NVIDIA-SMI 470.103.01 Driver Version: 470.103.01 CUDA Version: 11.4 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |===============================+======================+======================| | 0 Tesla T4 On | 00000000:00:04.0 Off | 0 | | N/A 38C P0 26W / 70W | 1090MiB / 15109MiB | 9% Default | | | | N/A | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=============================================================================| | 0 N/A N/A 27394 C ...OdQNlrNe-py3.8/bin/python 1087MiB | +-----------------------------------------------------------------------------+
おまけ
計算メモ1
計算メモ2