构建全栈影片推荐系统

1. 准备工作

从推荐电影或餐厅到突出显示有趣的视频,推荐引擎(也称为推荐器)是机器学习的一项非常重要的应用。推荐器可帮助您向用户展示大量候选内容中感兴趣的内容。例如,Google Play 商店提供数百万个可供安装的应用,YouTube 上则有数十亿个可供观看的视频。每天都会有更多应用和视频加入。

在此 Codelab 中,您将学习如何使用以下工具构建全栈推荐器:

  • 使用 TensorFlow Recommenders 训练检索模型和排名模型,以实现电影推荐
  • 使用 TensorFlow Serving 来提供模型
  • 使用 Flutter 创建一款跨平台应用,以显示推荐的电影

前提条件

  • 了解有关使用 Dart 开发 Flutter 应用的基本知识
  • 了解有关使用 TensorFlow 进行机器学习(例如训练与部署)的基本知识
  • 基本熟悉推荐系统
  • 了解有关 Python、终端和 Docker 的基本知识

学习内容

  • 如何使用 TensorFlow Recommenders 训练检索模型和排名模型
  • 如何使用 TensorFlow Serving 提供训练好的推荐模型
  • 如何构建用于展示推荐商品的跨平台 Flutter 应用

所需条件

2. 设置您的 Flutter 开发环境

对于 Flutter 开发,您需要使用两款软件才能完成此 Codelab:Flutter SDK一款编辑器

您可以使用以下任何设备运行此 Codelab 的前端:

  • iOS 模拟器(需要安装 Xcode 工具)。
  • Android 模拟器(需要在 Android Studio 中设置)。
  • 浏览器(需要使用 Chrome,以便进行调试)。
  • 对于 WindowsLinuxmacOS 桌面应用,您必须在打算部署到的平台上进行开发。因此,如果您要开发 Windows 桌面应用,则必须在 Windows 上进行开发,才能使用相应的构建链。如需详细了解针对各种操作系统的具体要求,请访问 docs.flutter.dev/desktop

对于后端,您需要:

  • Linux 计算机或基于 Intel 的 Mac。

3. 进行设置

如需下载此 Codelab 的代码,请执行以下操作:

  1. 找到此 Codelab 的 GitHub 代码库
  2. 点击 Code(代码)> Download zip(下载 Zip 文件),下载此 Codelab 的所有代码。

2cd45599f51fb8a2.png

  1. 解压缩已下载的 ZIP 文件,这会解压缩 codelabs-main 根文件夹,其中包含您需要的所有资源。

在此 Codelab 中,您只需要代码库的 tfrs-flutter/ 子目录(其中包含多个文件夹)中的文件:

  • step0step5 文件夹包含此 Codelab 中每一步的起始代码,您可以根据这些代码进行构建。
  • finished 文件夹包含已完成示例应用的完成后的代码。
  • 每个文件夹都包含一个 backend 子文件夹(其中包含推荐引擎后端代码)和一个 frontend 子文件夹(其中包含 Flutter 前端代码)

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 模拟器或 iOS 模拟器已正确设置并显示在状态栏中。

例如,当您将 Pixel 5 与 Android 模拟器搭配使用时,会看到以下内容:

9767649231898791.png

当您将 iPhone 13 与 iOS 模拟器搭配使用时,会看到以下内容:

95529e3a682268b2.png

  1. 点击 a19a0c68bc4046e6.png Start debugging(开始调试)。

运行和探索应用

应用应在 Android 模拟器或 iOS 模拟器上启动。界面非常简单。系统提供了文本字段,可让用户输入文本作为用户 ID。Flutter 应用会将查询请求发送到后端,后端会运行 2 个推荐模型,并返回排名靠前的电影推荐列表。前端会在收到响应后在界面中显示结果。

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 映像,此过程需要一分钟时间。之后,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 应用中查询电影推荐。

前端应用相当简单。它只有一个 TextField,用于接收用户 ID 并将请求(在 recommend() 函数中)发送到您刚刚构建的后端。收到响应后,应用界面会在 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,
  }),
);

应用收到来自后端的响应后,您会更新界面以显示指定用户的推荐电影列表。

  • 在上述代码的正下方添加以下代码:
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 应用

除了 Android 和 iOS 之外,Flutter 还支持桌面平台,包括 Linux、Mac 和 Windows。

Linux

  1. 确保目标设备在 VSCode 的状态栏中设置为 86cba523de82b4f9.png
  2. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用加载。
  3. 输入用户 ID(即 42),然后选择推荐

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),然后选择推荐

860d523a7ac537e0.png

Windows

  1. 确保目标设备在 VSCode 的状态栏中设置为 9587be1bb375bc0f.png
  2. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用加载。
  3. 输入用户 ID(即 42),然后选择推荐

7d77c1e52a5927fc.png

10. 第 5 步:在 Web 平台上运行 Flutter 应用

您还可以向 Flutter 应用添加 Web 支持。默认情况下,系统会自动为 Flutter 应用启用 Web 平台,因此您只需启动应用即可。

  1. 确保目标设备在 VSCode 的状态栏中设置为 71db93efa928d15d.png
  2. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用在 Chrome 浏览器中加载。
  3. 输入用户 ID(即 42),然后选择推荐

9376e1e432c18bef.png

11. 恭喜

您已经构建了一个全栈应用,可以向用户推荐电影了!

虽然此应用仅推荐电影,但您已了解构建强大的商品推荐引擎的总体工作流程,并掌握了在 Flutter 应用中使用推荐的技能。您可以轻松地将所学知识应用于其他场景(例如电子商务、美食和短视频)。

了解详情