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

1. 始める前に

映画やレストランのおすすめ、おもしろい動画のハイライトなど、レコメンデーション エンジン(レコメンダー)は、ML の非常に重要な応用となっています。Recommender を使用すると、多数の候補の中から魅力的なコンテンツをユーザーに提供できます。たとえば、Google Play ストアではインストールできるアプリが数多く用意されており、YouTube では数十億本もの動画を視聴できます。さらに多くのアプリや動画が日々追加されています。

この Codelab では、以下を使用してフルスタック Recommender を構築する方法を学びます。

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

前提条件

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

学習内容

  • TensorFlow Recommender を使用して取得モデルとランキング モデルをトレーニングする方法
  • 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 のみが含まれています。レスポンスを受信すると、アプリの 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)を選択して [Recommend] を選択します。

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)を選択して [Recommend] を選択します。

2665514231033f1.png

Mac

  1. Mac の場合、アプリがバックエンドに HTTP リクエストを送信するため、適切な利用資格を設定する必要があります。詳しくは、利用資格とアプリ サンドボックスをご覧ください。

このコードをそれぞれ 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)を選択して [Recommend] を選択します。

860d523a7ac537e0.png

Windows

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

7d77c1e52a5927fc.png

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

もう 1 つできることは、Flutter アプリにウェブサポートを追加することです。ウェブ プラットフォームは、デフォルトで Flutter アプリに対して自動的に有効になるため、ユーザーが行う作業は起動することだけです。

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

9376e1e432c18bef.png

11. 完了

ユーザーに映画をおすすめするフルスタック アプリを作成しました。

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

その他の情報