グラフデータベースNeo4jを使って、レコメンド機能を実験してみる

**追記(2018/12/11)**

- より詳しい内容を **[オールアバウト Tech Blog](https://allabout-tech.hatenablog.com/entry/2017/03/02/094600)** に記載してあります。
- Neo4jユーザー勉強会にて発表した資料は、**[ブログ](https://sussan-po.com/2018/01/20/neo4j-recommend/)**または**[Speaker Deck](https://speakerdeck.com/sussan0416/neo4jwoyong-itakahuexun-rixing-dong-falsefen-xi)** からどうぞ

---

メリークリスマス:tada:
最終日の今日は、私が先日、余暇でやってみた「レコメンド機能」の実験についてお話します。

今回は、弊社サービスの[CafeSnap](https://cafesnap.me/)を題材に、カフェ推薦機能を実現することを想定して、手を動かしてみました。

# テーマ

**私におすすめのカフェを推薦する**
→ 「私（@sutchan）が行ったカフェ」に行ったことのある人が、行っているカフェをリストアップする

# グラフデータベース「Neo4j」を使って実験

グラフデータベースは、グラフ構造をもったデータを保存、探索する用途に向いているそうです。
上のような、サービス内を含む実世界の人の行動を分析したり、人と人の関係性を探索したりするのを得意としているらしいです。

→ [Neo4j](https://neo4j.com/)

## 使用したデータ

CafeSnapに実装してある「カフェお気に入り機能」で登録されたデータを利用して、実験してみます。**ざっくり**、以下のとおりです。

|テーブル|構造|
|:--|:--|
|お気に入り|カフェID, ユーザID|
|カフェ|カフェID, カフェ名, エリアID|
|エリア|エリアID, エリア名|
|ユーザ|ユーザID, ユーザ名|

※実験に必要なデータのみ使用しました
※各テーブルの詳細なデータ件数については記載しません

## お気に入り機能とは……

カフェお気に入り機能は、カフェの店舗に「**行った**」または「**行きたい**」のスター:star:をつけて、お気に入りのカフェリストを作ることができる機能です。
今回は、この機能の「**行った**」の情報を利用してカフェ推薦機能ができるかどうか実験してみます。

![Screenshot_20161216-000938.png](https://qiita-image-store.s3.amazonaws.com/0/75329/01bb78c3-0dc9-6594-ead5-ecb109a0cbeb.png)


# 実験

実験を始めます。データは予めインポートしておきます。
カフェノード（図 黄色円）、エリアノード（図 青円）、ユーザノード（図 赤円）を、以下の図のようにRelationshipを貼っておきます。

<img width="503" alt="スクリーンショット 2016-12-25 21.27.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/75329/386c27a5-d61d-a563-1fb5-e8c62fddb597.png">

インポートの仕方については、**neo4jのチュートリアルがとても参考になります！**

## 私が行ったカフェ

早速やってみます。まずは、手始めに、私が行ったことのあるカフェを表示してみます。
デフォルトでは、グラフ状に表示しますが、リストで表示することも可能です。

<img width="588" alt="スクリーンショット 2016-12-25 21.32.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/75329/f320ef07-52e0-7886-c69b-c76d519354a3.png">

私が行ったカフェは、69件ありました。

Neo4jは、Cypher QLで問い合わせします。
上のグラフを表示するのに必要なクエリは、以下ような雰囲気。

```
MATCH (u:User{user_name:"*****"})-[:VISITED]->(c:Cafe)
RETURN u,c
```

## 「私が行ったカフェ」に行っている人々

グラフに表示すると非常に多くなってしまったので割愛。。。

## 「『私が行ったカフェ』に行っている人々」が行っているカフェ

以下のようなクエリで求めてみました。
行った人が多い順に、ランキングしてみます。

```
MATCH (u:User{user_name:"*****"})-[:VISITED]->(c:Cafe)<-[:VISITED]-(u2:User)-[:VISITED]->(c2:Cafe)
WHERE c2.cafe_id contains c.cafe_id
RETURN c2.cafe_name,count(u2) as count
ORDER BY count DESC
```

MATCHの部分で、私が行ったカフェ、に行っている人、が行っているカフェを探し、
WHEREの部分で、私が行ったカフェ以外のカフェに絞り込み指定をします。
RETURNは、表示する項目の指定です。

結果をテーブルで表示してみると、以下のような感じ。

<img width="426" alt="スクリーンショット 2016-12-25 22.11.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/75329/99a6d5db-498c-e02e-6774-ce91b407cbd6.png">

カ……カフェ名だけ見ても、想像つかないですよね:sweat:

# まとめ（実験してみてどうだったか）

最終的に表示できた「『私が行ったカフェ』に行った人」が行ったカフェランキングは、
カフェのジャンルや雰囲気を考えると、自分の好みに近い感じのカフェかもという感じはありました。

というのも、自分が行ったことがあるのに、「行った」に登録し忘れているカフェがありました。
4つもありました（1番目のLife〜、2番目の4/4〜、7番目のブルー〜、9番目のGRILL〜）。
この点から、おそらく自分の好みの系統のカフェが出ているとも感じられるし「:star:のつけ忘れがないかを確認する機能」としても有効かなという印象を受けました。

## 動作速度

ローカルの環境（MacBook Pro 13inch）での実験でしたが、結果が出るまでに1秒弱かかっていることが気になりました。
もちろん、対象となるデータ数が多いこともあるのですが、若干遅い感じはありました。Javaが原因？？わかりません。

## クエリは直感的

Cypher QLは直感的で分かりやすかったです。
数時間実験しただけなのですが、意図した結果が得られるところまでは、サクサクと書けるようになりました。

# 次の展開

今回は、カフェを推薦することをテーマに実験しましたが、この構造を利用すると「あなたにオススメのユーザ」や「もしかして友達？」なんていうSNSっぽい機能も実現できそうです。

グラフデータベースは、人物相関図のような構造を持っています。ドラマやアニメのレコメンドなどもできそうで、面白いことができそうです。