フルスタックの映画レコメンデーション システムの構築

1. 始める前に

映画やレストランのおすすめから、面白い動画のハイライト表示まで、レコメンデーション エンジン(レコメンダーとも呼ばれます)は、機械学習の非常に重要なアプリケーションです。レコメンダーを使用すると、候補の大きなプールから魅力的なコンテンツをユーザーに表示できます。たとえば、Google Play ストアには何百万ものアプリがあり、YouTube には数十億本もの動画が用意されています。アプリや動画は毎日追加されています。

この Codelab では、次のものを使用してフルスタックのレコメンダーを構築する方法を学習します。

  • TensorFlow Recommender を使用して、映画のレコメンデーション用の取得モデルとランキング モデルをトレーニングする
  • TensorFlow Serving を使用してモデルを提供する
  • Flutter を使用して、おすすめの映画を表示するクロス プラットフォーム アプリを作成する

前提条件

  • Dart を使った Flutter の開発に関する基本的な知識
  • TensorFlow を使用した機械学習に関する基本的な知識(トレーニングとデプロイなど)
  • レコメンデーション システムに関する基本的な知識がある
  • Python、ターミナル、Docker に関する基本的な知識

学習内容

  • TensorFlow Recommenders を使用して検索モデルとランキング モデルをトレーニングする方法
  • TensorFlow Serving を使用してトレーニング済みのレコメンデーション モデルを提供する
  • おすすめアイテムを表示するためのクロス プラットフォームの Flutter アプリを作成する方法

必要なもの

2. Flutter の開発環境をセットアップする

Flutter 開発では、この Codelab を完了するために、Flutter SDKエディタの 2 つのソフトウェアが必要です。

この Codelab のフロントエンドは、次のいずれかのデバイスを使用して実行できます。

  • iOS シミュレータ(Xcode ツールのインストールが必要)
  • Android Emulator(Android Studio でセットアップが必要)
  • ブラウザ(デバッグには Chrome が必要)
  • WindowsLinuxmacOS のデスクトップ アプリケーション。開発はデプロイする予定のプラットフォームで行う必要があります。たとえば、Windows のデスクトップ アプリを開発する場合は、適切なビルドチェーンにアクセスできるように Windows で開発する必要があります。オペレーティング システム固有の要件については、docs.flutter.dev/desktop に詳しい説明があります。

バックエンドには次のものが必要です。

  • Linux マシンまたは Intel ベースの Mac。

3. セットアップする

この Codelab のコードをダウンロードするには:

  1. この Codelab の GitHub リポジトリに移動します。
  2. [Code] > [Download zip] をクリックして、この Codelab のすべてのコードをダウンロードします。

2cd45599f51fb8a2.png

  1. ダウンロードした zip ファイルを解凍して、codelabs-main ルートフォルダを展開します。このフォルダに必要なリソースがすべて含まれています。

この Codelab では、リポジトリの tfrs-flutter/ サブディレクトリ内のファイルのみが必要です。このサブディレクトリには複数のフォルダが含まれています。

  • step0 フォルダから step5 フォルダには、この Codelab の各ステップで構築するスターター コードが含まれています。
  • finished フォルダには、完成したサンプルアプリの完全なコードが含まれています。
  • 各フォルダには、レコメンデーション エンジンのバックエンド コードを含む backend サブフォルダと、Flutter フロントエンド コードを含む frontend サブフォルダが含まれています。

4. プロジェクトの依存関係をダウンロードする

バックエンド

バックエンドの作成には Flask を使用します。ターミナルを開いて、次のコマンドを実行します。

pip install Flask flask-cors requests numpy

フロントエンド

  1. VS Code で、[File] > [Open folder] をクリックし、先ほどダウンロードしたソースコードの step0 フォルダを選択します。
  2. step0/frontend/lib/main.dart ファイルを開きます。スターター アプリに必要なパッケージのダウンロードを求める VS Code ダイアログが表示されたら、[Get packages] をクリックします。
  3. このダイアログが表示されない場合は、ターミナルを開いて step0/frontend フォルダで flutter pub get コマンドを実行します。

7ada07c300f166a6.png

5. ステップ 0: スターター アプリを実行する

  1. VS Code で step0/frontend/lib/main.dart ファイルを開き、Android Emulator または iOS Simulator が正しくセットアップされ、ステータスバーに表示されていることを確認します。

たとえば、Android Emulator で Google Pixel 5 を使用する場合は次のようになります。

9767649231898791.png

iOS シミュレータで iPhone 13 を使用する場合は次のようになります。

95529e3a682268b2.png

  1. [a19a0c68bc4046e6.png Start debugging] をクリックします。

アプリを実行して操作する

Android Emulator または iOS シミュレータでアプリを起動します。UI は非常にシンプルです。ユーザーがユーザー ID としてテキストを入力できるテキスト フィールドがあります。Flutter アプリはクエリ リクエストをバックエンドに送信します。バックエンドは 2 つのレコメンデーション モデルを実行し、映画のおすすめのランク付けされたリストを返します。フロントエンドは、レスポンスを受信すると、結果を UI に表示します。

d21427db9587560f.png 73e8272a5ce8dfbc.png

[Recommend] をクリックしても、まだバックエンドと通信できないため、何も起こりません。

6. ステップ 1: レコメンデーション エンジンの取得モデルとランキング モデルを作成する

実際のレコメンデーション エンジンは、多くの場合、複数のステージで構成されています。

  1. 取得ステージでは、すべての候補の中から数百の候補の初期セットを選択します。このモデルの主な目的は、ユーザーが関心のない候補者をすべて効率的に除外することです。検索モデルは数百万の候補を処理する可能性があるため、計算効率が優れている必要があります。
  2. ランキング ステージでは、検索モデルの出力を取得し、それらをファインチューニングして、最適な推奨事項をいくつか選択します。このタスクでは、ユーザーが興味を持ちそうなアイテムのセットを、数百件の候補リストに絞り込みます。
  3. ランキング後の段階では、多様性、新鮮さ、公平性を確保し、候補アイテムを数十個の有用な推奨事項のセットに並べ替えます。

70dfc0d7e989164f.png

この Codelab では、一般的な MovieLens データセットを使用して、検索モデルとランキング モデルをトレーニングします。以下のトレーニング コードは Colab で開いて、手順に沿って操作できます。

7. ステップ 2: レコメンデーション エンジンのバックエンドを作成する

検索モデルとランキング モデルをトレーニングしたので、これらをデプロイしてバックエンドを作成できます。

TensorFlow Serving を開始する

おすすめの映画リストを生成するには、検索モデルとランキング モデルの両方を使用する必要があるため、TensorFlow Serving を使用して両方を同時にデプロイします。

  • ターミナルで、パソコンの step2/backend フォルダに移動し、Docker を使用して TensorFlow Serving を起動します。
docker run -t --rm -p 8501:8501 -p 8500:8500 -v "$(pwd)/:/models/" tensorflow/serving --model_config_file=/models/models.config

Docker は、まず TensorFlow Serving のイメージを自動的にダウンロードします。これには 1 分ほどかかります。その後、TensorFlow Serving が起動します。次のようなログが出力されます。

2022-04-24 09:32:06.461702: I tensorflow_serving/model_servers/server_core.cc:465] Adding/updating models.
2022-04-24 09:32:06.461843: I tensorflow_serving/model_servers/server_core.cc:591]  (Re-)adding model: retrieval
2022-04-24 09:32:06.461907: I tensorflow_serving/model_servers/server_core.cc:591]  (Re-)adding model: ranking
2022-04-24 09:32:06.576920: I tensorflow_serving/core/basic_manager.cc:740] Successfully reserved resources to load servable {name: retrieval version: 123}
2022-04-24 09:32:06.576993: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: retrieval version: 123}
2022-04-24 09:32:06.577011: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: retrieval version: 123}
2022-04-24 09:32:06.577848: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:38] Reading SavedModel from: /models/retrieval/exported-retrieval/123
2022-04-24 09:32:06.583809: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:90] Reading meta graph with tags { serve }
2022-04-24 09:32:06.583879: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:132] Reading SavedModel debug info (if present) from: /models/retrieval/exported-retrieval/123
2022-04-24 09:32:06.584970: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2022-04-24 09:32:06.629900: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle.
2022-04-24 09:32:06.634662: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 2800000000 Hz
2022-04-24 09:32:06.672534: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/retrieval/exported-retrieval/123
2022-04-24 09:32:06.673629: I tensorflow_serving/core/basic_manager.cc:740] Successfully reserved resources to load servable {name: ranking version: 123}
2022-04-24 09:32:06.673765: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: ranking version: 123}
2022-04-24 09:32:06.673786: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: ranking version: 123}
2022-04-24 09:32:06.674731: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:38] Reading SavedModel from: /models/ranking/exported-ranking/123
2022-04-24 09:32:06.683557: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:90] Reading meta graph with tags { serve }
2022-04-24 09:32:06.683601: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:132] Reading SavedModel debug info (if present) from: /models/ranking/exported-ranking/123
2022-04-24 09:32:06.688665: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 110815 microseconds.
2022-04-24 09:32:06.690019: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/retrieval/exported-retrieval/123/assets.extra/tf_serving_warmup_requests
2022-04-24 09:32:06.693025: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: retrieval version: 123}
2022-04-24 09:32:06.702594: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle.
2022-04-24 09:32:06.745361: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/ranking/exported-ranking/123
2022-04-24 09:32:06.772363: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 97633 microseconds.
2022-04-24 09:32:06.774853: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/ranking/exported-ranking/123/assets.extra/tf_serving_warmup_requests
2022-04-24 09:32:06.777706: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: ranking version: 123}
2022-04-24 09:32:06.778969: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models
2022-04-24 09:32:06.779030: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled
2022-04-24 09:32:06.784217: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ...
[warn] getaddrinfo: address family for nodename not supported
2022-04-24 09:32:06.785748: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 245] NET_LOG: Entering the event loop ...

新しいエンドポイントを作成する

TensorFlow Serving は複数のシーケンシャル モデルの「チェーン」をサポートしていないため、検索モデルとランキング モデルを接続する新しいサービスを作成する必要があります。

  • 次のコードを step2/backend/recommendations.py ファイルの get_recommendations() 関数に追加します。
user_id = request.get_json()["user_id"]
retrieval_request = json.dumps({"instances": [user_id]})
retrieval_response = requests.post(RETRIEVAL_URL, data=retrieval_request)
movie_candidates = retrieval_response.json()["predictions"][0]["output_2"]

ranking_queries = [
    {"user_id": u, "movie_title": m}
    for (u, m) in zip([user_id] * NUM_OF_CANDIDATES, movie_candidates)
]
ranking_request = json.dumps({"instances": ranking_queries})
ranking_response = requests.post(RANKING_URL, data=ranking_request)
movies_scores = list(np.squeeze(ranking_response.json()["predictions"]))
ranked_movies = [
    m[1] for m in sorted(list(zip(movies_scores, movie_candidates)), reverse=True)
]

return make_response(jsonify({"movies": ranked_movies}), 200)

Flask サービスを開始する

これで Flask サービスを開始できます。

  • ターミナルで、step2/backend/ フォルダに移動して、次のコマンドを実行します。
FLASK_APP=recommender.py FLASK_ENV=development flask run

Flask は http://localhost:5000/recommend に新しいエンドポイントを立ち上げます。次のようなログが表示されます。

 * Serving Flask app 'recommender.py' (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 705-382-264
127.0.0.1 - - [25/Apr/2022 19:44:47] "POST /recommend HTTP/1.1" 200 -

エンドポイントにサンプル リクエストを送信して、想定どおりに動作することを確認できます。

curl -X POST -H "Content-Type: application/json" -d '{"user_id":"42"}' http://localhost:5000/recommend

エンドポイントは、ユーザー 42 におすすめの映画のリストを返します。

{
  "movies": [
    "While You Were Sleeping (1995)",
    "Preacher's Wife, The (1996)",
    "Michael (1996)",
    "Lion King, The (1994)",
    "Father of the Bride Part II (1995)",
    "Sleepless in Seattle (1993)",
    "101 Dalmatians (1996)",
    "Bridges of Madison County, The (1995)",
    "Rudy (1993)",
    "Jack (1996)"
  ]
}

これで、ユーザー ID に基づいて映画をおすすめするバックエンドを正常に構築しました。

8. ステップ 3: Android と iOS 用の Flutter アプリを作成する

バックエンドの準備が完了しました。リクエストの送信を開始して、Flutter アプリから映画のおすすめをクエリできます。

フロントエンド アプリは非常にシンプルです。ユーザー ID を受け取り、リクエスト(recommend() 関数内)を構築したばかりのバックエンドに送信する TextField が 1 つだけあります。レスポンスを受け取ると、アプリの UI におすすめの映画が ListView に表示されます。

  • 次のコードを step3/frontend/lib/main.dart ファイルの recommend() 関数に追加します。
final response = await http.post(
  Uri.parse('http://' + _server + ':5000/recommend'),
  headers: <String, String>{
    'Content-Type': 'application/json',
  },
  body: jsonEncode(<String, String>{
    'user_id': _userIDController.text,
  }),
);

アプリがバックエンドからレスポンスを受け取ると、UI を更新して、指定されたユーザーにおすすめの映画のリストを表示します。

  • 上記のコードのすぐ下に、次のコードを追加します。
if (response.statusCode == 200) {
  return List<String>.from(jsonDecode(response.body)['movies']);
} else {
  throw Exception('Error response');
}

実行

  1. [a19a0c68bc4046e6.png Start debugging] をクリックして、アプリが読み込まれるまで待ちます。
  2. ユーザー ID(42)を選択し、[推奨] を選択します。

badb59d8b96959ae.png a0d2d4020aebfb0a.png

9. ステップ 4: デスクトップ プラットフォームで Flutter アプリを実行する

Flutter では、Android と iOS だけでなく、Linux、Mac、Windows といったパソコンのプラットフォームもサポートしています。

Linux

  1. VSCode のステータスバーで、対象デバイスが 86cba523de82b4f9.png に設定されていることを確認します。
  2. [a19a0c68bc4046e6.png Start debugging] をクリックして、アプリが読み込まれるまで待ちます。
  3. ユーザー ID(42)を選択し、[推奨] を選択します。

2665514231033f1.png

Mac

  1. Mac の場合、アプリがバックエンドに HTTP リクエストを送信するため、適切なエンタイトルメントを設定する必要があります。詳しくは、Entitlements and the App Sandbox をご覧ください。

次のコードを step4/frontend/macOS/Runner/DebugProfile.entitlementsstep4/frontend/macOS/Runner/Release.entitlements にそれぞれ追加します。

<key>com.apple.security.network.client</key>
<true/>
  1. VSCode のステータスバーで、対象デバイスが eb4b0b5563824138.png に設定されていることを確認します。
  2. [a19a0c68bc4046e6.png Start debugging] をクリックして、アプリが読み込まれるまで待ちます。
  3. ユーザー ID(42)を選択し、[推奨] を選択します。

860d523a7ac537e0.png

Windows

  1. VSCode のステータスバーで、対象デバイスが 9587be1bb375bc0f.png に設定されていることを確認します。
  2. [a19a0c68bc4046e6.png Start debugging] をクリックして、アプリが読み込まれるまで待ちます。
  3. ユーザー ID(42)を選択し、[推奨] を選択します。

7d77c1e52a5927fc.png

10. ステップ 5: ウェブ プラットフォームで Flutter アプリを実行する

Flutter アプリにウェブのサポートを追加することもできます。ウェブ プラットフォームは、デフォルトで Flutter アプリ向けに自動的に有効になっているため、あとはリリースするだけです。

  1. VSCode のステータスバーで、対象デバイスが 71db93efa928d15d.png に設定されていることを確認します。
  2. [a19a0c68bc4046e6.png Start debugging] をクリックして、アプリが Chrome ブラウザに読み込まれるのを待ちます。
  3. ユーザー ID(42)を選択し、[推奨] を選択します。

9376e1e432c18bef.png

11. 完了

ユーザーにおすすめの映画を紹介するフルスタック アプリを構築しました。

このアプリでは映画のみをおすすめしますが、強力なレコメンデーション エンジンを構築するワークフロー全体を学び、Flutter アプリでレコメンデーションを利用するスキルを習得しました。学んだことを他のシナリオ(e コマース、食品、ショート動画など)に簡単に適用できます。

その他の情報