「Dish」 を支えるランチ推薦アルゴリズム

 はじめまして、株式会社ピケのサーバサイドを担当している古内と申します。
 私の軽い自己紹介としてはエキサイト株式会社に新卒で約一年間エンジニアとして勤めて、現在の会社でもエンジニアをしています。
 最近はエンジニアリングだけでなくプロダクトのアイデアを考えたり、ユーザーさんに口頭インタビューするなど外での活動もしていて大手企業のエンジニアと言うよりスタートアップあるあるのなんでもやる人になってきました。

目次

1. Dish とは
2. この記事に書いてあること
3. 「サクッと」の要件
4. 開発 & 本番環境
5. 飲食店情報
6. アルゴリズム
7. まとめ

1. Dish とは

Dish とは「飲食店をサクッと決められる」をコンセプトにしたアプリです。

食べたいものは特にないけど、自分一人や友人、同僚とランチに行くってありますよね?

その時ってだいたい悩んで数分歩いて決めるとか、スマホで検索していると思います。

チーム内のメンバーもみんな「いい感じにできたらいいなぁ」と思い、以下の動画を仮説検証で出してみました。

投稿してみた結果、予想以上の反応だったので驚きながらもアプリを作ることになりました。

作る過程でサーバサイドでどういったことをしているかをこの記事にしています。

2. この記事に書いてあること

この記事では Dish で飲食店情報を届けるために

・どこから飲食店情報を取得すればユーザーの求める情報が得られるか?
・飲食店情報をどういう順番で届ければユーザーは嬉しいのか?

を考えてサーバサイドを実装した記事です。
何かの参考になれば幸いです。

3. 「サクッと」の要件

【「サクッと」を定義する 】

なんとなく「サクッと」の感覚はわかるけど実際何を持って「サクッと」なのだろう考えるところから始まりました。

「サクッと」の定義はユーザーインタビューやアンケート、チーム内の意見を参考にして以下の条件になりました。

・数タップで飲食店を見たい
・現在地周辺の飲食店だけで良い
・営業中の飲食店を見たい
・飲食店をテンポよく見たい
・最大 1500 円くらいの飲食店を見たい
・ケーキ屋さんとかはいらない
・評価の良い飲食店を見たい
・飲食店の画像を見たい
・チェーン店は避けたい

これらの条件を満たすサーバサイドの実装の解説をします。

4. 開発 & 本番環境

【 開発環境 】

Mac + Docker + Docker Compose

この構成だと フロントに git clone と docker-compose up だけで環境を渡せるのがすごく楽です。
また、k8s までは必要ないけど簡単に API と DB を用意したいという理由もあります。

【 本番環境 】

GAE / FE + Google Cloud SQL + Google Cloud Storage

※ GAE / FE = Google App Engine Flexible Environment

GAE を使うとデプロイごとにバージョンができ、元のバージョンに戻すということが簡単に行えるので使っています。
開発環境と同様に k8s ほど必要はなく、簡単に本番環境を用意したいという理由もあります。

【 言語とフレームワーク 】

Python 3.6 + Django + Django Rest Framework

この構成の理由は弊社のスキルセットが Django だったからという理由が一番大きいです。
他の理由としては、API と Admin を同時開発を高速にできるという点です。

5. 飲食店情報

【 飲食店情報をどこから持ってくるか 】

結論からいいますとDish の飲食店情報は Foursquare を使っています。
Foursquare は以下の条件を満たしていました。

・経緯度から検索可能
・営業時間を得られる
・駅とか公園の情報はいらない
・飲食店のレビュー、写真、値段等が得られる
・スタートアップでも料金的に使える

他にもいろいろ候補はありました。例えば…

Google Places はプランの改定がありリクエスト数を考えるととてもスタートアップが払える金額にはならないと判断したため断念しました。また、ホテルのレストランとか出てきてしまうこともありました。

ぐるなびは弊社が期待する飲食店のレビューがなく、ぐるなび単体での利用はできないと判断しました。

Yelp は飲食店のレビューが日本語ではないものが多く、日本人が使う想定の Dish では性質上合わないと判断して断念しました。

食べログはそもそも API をやめてしまっていました。

などなど断念した理由は様々です。

【 Foursquare API で利用したもの 】

Dish では 3 つの API を利用しています。

1 つは、Explore API です。
この API は 経緯度から飲食店の一覧を取得することができます。
また、以下のようなリクエストパラメータを含めて使ってます。

・ll=35.6572296,139.6956903
・openNow=1
・limit=50
・price=1,2
・section=food

openNow=1 これがキモです。
これを入れると現在時刻で営業している飲食店のみを取得できます。
他にも Search API もありますが営業中の飲食店のみの取得ができないので使いませんでした。

残り 2 つの API は Detail と Photos API です。
これらの API は簡単で、店舗 ID を含めてリクエストするだけでした。

以上の 3 つの API を使っています。

【 Foursquare API の苦労した点 】

苦労した点がありましたのでこれから Foursquare を使われる方の参考になればと思います。

苦労した点は…

「Explore API はプランによってレスポンス内容が違う」

getting started に載っている Explore API を見ると hours、price、rating などのレスポンスがあります。
Explore API を見ると hours、price、rating などのレスポンスがありません。

?????

結論からいいますと Enterprise プランのみ hours、price、rating などのレスポンスの取得ができるということでした。

API バージョンが違うのか?プランがだめなのか?いろいろ API を叩いても解決できませんでした。

結局、仲間が英語でメールで問い合わせるという非常に面倒くさいことをして解決しました。

6. アルゴリズム

【 アルゴリズムでしたいこと 】

取得した各飲食店をユーザーの求めるものになるように評価して、ソートする。

【 Dish の飲食店評価要素 】

以下の 5 つが飲食店を評価する要素にしました。

・現在地からの飲食店の距離
・残りの営業時間
・評価と評価数
・チェーン店ではないこと
・ランチに適したお店であること

【 各要素の単位を揃える 】

まずは、各要素を評価するために
距離、時間等を -5 ~ 5 の間の数値にする作業をしました。

Dish では min max normalization を使いました。
このノーマライズ方法を選んだ理由としては 50 件の飲食店中 10 ~ 20 件の良い特徴のある飲食店だけ出せば良いので数値の最小値、最大値で大きく影響したほうが良い指標になると考えたからです。

【 人間の評価感覚に近づける 】

人間の評価感覚では、食べログで「評価が 3.3 と 3.5 の違いは大きく感じる」現象があります。

このような人間的な感覚の関数を実装するにあたって私は jupyter と numpy と matplotlib を使いました。
これらのツールを使った理由は、dish では -5 ~ 5 の値 にノーマライズされていて、 0.01 刻みで自作した評価関数に渡すとどのような曲線になるのだろうと直感的にわかるようにしたかったからです。
以下のような画像を見ながら「距離がこうだったら…」とか「営業時間がこうだったら…」を自分の感覚を頼りに 1 ~ 10 の間の値を返す評価関数を作りました。

【 各要素に係数をかける 】

最終的には評価計算された各要素に係数をかけるようにしました。
理由は、距離と営業時間では飲食店を決定する影響度に違いがあるからです。

例えば、Dish では、距離、飲食店の評価、営業時間の重要度の順で評価しています。
以下は係数の設定例です。

・距離の評価値 = 距離の評価関数の結果 * 1.0
・飲食店の評価の評価値 = 飲食店の評価の評価関数の結果 * 0.7
・営業時間の評価値 = 営業時間の評価関数の結果 * 0.5

最後に評価計算された各要素をすべて足したものが飲食店の Dish 独自の評価値になります。

あとは Dish 独自の評価値の大きい順の 10 件とか 20 件を返すようにすれば Dish API 完成です!

【 補足 】

補足ですがチェーン店に関してはチェーン店リストを作り、チェーン店だった場合は Dish 独自の評価値が下がるように 0.1 をかけるような処理をしています。
また、ケーキ屋さんのようなランチに適していないと思われる飲食店はそもそも除外するような処理をしています。

【 アルゴリズムまとめ 】

・人間的な評価感覚に近づける
・評価優先順位は 距離 > 飲食店評価
 > 営業時間
・チェーン店を出さない
・ケーキ屋さんのような飲食店を出さない

7. まとめ

全体を通して実現されたことは

「納得感のある飲食店を優先的に見れる」


ということです。

リリース後もユーザーさんの声を聞いてどういう飲食店を見たいのかを考えて、計算式の見直しやチェーン店リスト等のアップデートを重ねてより良い体験を提供できるようがんばります。

この記事で Dish にご興味いただいた方はぜひ使ってみてください!

以上、Dish サーバサイド開発の話でした。
ありがとうございました!

( 反応がよければ Django とかもっと細かく書いてみます。 )


Dish の他の記事もあります。

約4万RTされたグルメアプリ「Dish」を2ヶ月でリリースするまで - ren

デザインの視点でみる「Dish」の誕生から動画公開まで - nakanishy

React Native をプロダクションで使ってわかった良かった点・悪かった点 - nakanishy

この記事が気に入ったら、サポートをしてみませんか?気軽にクリエイターを支援できます。

69

furuuchi

#エンジニア 系記事まとめ

noteに投稿されたエンジニア系の記事のまとめ。コーディングTIPSよりは、考察や意見などを中心に。
4つのマガジンに含まれています

コメント3件

こんにちは!
dishとても使いやすいです!UXすごいです。
一つ質問なのですがフロントはswiftで実装した感じですか?
自分もこのぐらい素晴らしいUXのアプリを作りたいのですがreact native でこのレベルまでできるのか心配です。やはりswiftではないと厳しいでしょうか。
Kosuke さん

こんにちは、Dish のフロントエンドを担当している中西です。
気に入っていただき、ありがとうございます!

Dish のフロントは全て React Native で実装しています。Swift や Objective-C のコードは自分では一切書いていません。なので、今の Dish でできていることは React Native でできると思っていただいて大丈夫だと思います!

RN での実装の詳細はまた後日記事にする予定なので見てみてください。
Keita Nakanishiさん

ご回答ありがとうございます!
そうなのですね!貴重なアドバイス誠にありがとうございました!
記事楽しみにしております。
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。