1. 准备工作
从推荐电影或餐馆到突出显示娱乐视频,推荐引擎(也称为 Recommender)都是机器学习非常重要的应用。Recommender 可以帮助您从大量的候选定位设置中向用户呈现引人入胜的内容。例如,Google Play 商店提供数百万个可供安装的应用,而 YouTube 则提供数十亿个视频供观看者观看。而且,每天都会有更多应用和视频加入。
在此 Codelab 中,您将学习如何使用以下工具构建全栈 Recommender:
- 使用 TensorFlow Recommenders 训练电影推荐检索和排名模型
- 使用 TensorFlow Serving 应用模型
- 利用 Flutter 创建一个跨平台应用来显示推荐的电影
前提条件
- 了解有关使用 Dart 开发 Flutter 应用的基本知识
- 了解有关使用 TensorFlow 进行机器学习(例如训练与部署)的基本知识
- 基本熟悉推荐系统
- 具备 Python、终端和 Docker 方面的基础知识
学习内容
- 如何使用 TensorFlow Recommenders 训练检索和排名模型
- 如何使用 TensorFlow Serving 提供经过训练的推荐模型
- 如何构建用于展示推荐商品的跨平台 Flutter 应用
所需条件
- Flutter SDK
- 适用于 Flutter 的 Android 和 iOS 设置
- 适用于 Flutter 的桌面设置
- 适用于 Flutter 的 Web 设置
- 适用于 Flutter 和 Dart 的 Visual Studio Code (VS Code) 设置
- Docker
- Bash
- Python 3.7 及更高版本
- 对 Colab 的访问权限
2. 设置您的 Flutter 开发环境
对于 Flutter 开发,您需要使用两款软件才能完成此 Codelab:Flutter SDK 和一款编辑器。
您可以使用以下任意设备运行此 Codelab 的前端:
- iOS 模拟器(需要安装 Xcode 工具)。
- Android 模拟器(需要在 Android Studio 中设置)。
- 浏览器(需要使用 Chrome,以便进行调试)。
- 对于 Windows、Linux 或 macOS 桌面应用,您必须在打算部署到的平台上进行开发。因此,如果您要开发 Windows 桌面应用,则必须在 Windows 上进行开发,才能使用相应的构建链。如需详细了解针对各种操作系统的具体要求,请访问 docs.flutter.dev/desktop。
对于后端,您需要:
- Linux 计算机或基于 Intel 的 Mac。
3. 进行设置
如需下载此 Codelab 的代码,请执行以下操作:
- 找到此 Codelab 的 GitHub 代码库。
- 点击 Code(代码)> Download zip(下载 Zip 文件),下载此 Codelab 的所有代码。
- 解压缩下载的 ZIP 文件,这会解压缩
codelabs-main
根文件夹,其中包含您需要的所有资源。
对于此 Codelab,您只需要代码库的 tfrs-flutter/
子目录(其中包含多个文件夹)中的文件:
step0
到step5
文件夹包含您在此 Codelab 中每一步进行构建的起始代码。finished
文件夹包含已完成示例应用的完成后的代码。- 每个文件夹都包含一个
backend
子文件夹(包含推荐引擎后端代码)和一个frontend
子文件夹(包含 Flutter 前端代码)。
4. 下载项目的依赖项
后端
我们将使用 Flask 创建后端。打开终端并运行以下命令:
pip install Flask flask-cors requests numpy
前端
- 在 VS Code 中,点击 File(文件)> Open folder(打开文件夹),然后从您之前下载的源代码中选择
step0
文件夹。 - 打开
step0/frontend/lib/main.dart
文件。如果您看到一个 VS Code 对话框,提示您下载起始应用所需的软件包,请点击 Get packages(获取软件包)。 - 如果您没有看到此对话框,请打开终端,然后在
step0/frontend
文件夹中运行flutter pub get
命令。
5. 第 0 步:运行起始应用
- 在 VS Code 中打开
step0/frontend/lib/main.dart
文件,确保 Android 模拟器或 iOS 模拟器已正确设置并显示在状态栏中。
例如,当您将 Pixel 5 与 Android 模拟器搭配使用时,会看到以下内容:
当您将 iPhone 13 与 iOS 模拟器搭配使用时,会看到以下内容:
- 点击 Start debugging(开始调试)。
运行和探索应用
应用应在 Android 模拟器或 iOS 模拟器上启动。界面非常简单。有一个文本字段,可让用户输入文本作为用户 ID。Flutter 应用会将查询请求发送到后端,后端会运行 2 个推荐模型并返回按排名的电影推荐列表。前端在收到响应后将在界面中显示结果。
如果您现在点击 Recommend,将不会有任何反应,因为应用尚无法与后端通信。
6. 第 1 步:为推荐引擎创建检索和排名模型
实际的商品推荐引擎通常由多个阶段组成:
- 检索阶段负责从所有可能的候选项中选择初始的数百个候选项。此模型的主要目标是高效剔除用户不感兴趣的所有候选定位设置。由于检索模型可能需要处理数百万个候选项,因此必须提高计算效率。
- 排名阶段会获取检索模型的输出并对其进行微调,以便尽可能选出一些最符合要求的建议。其任务是将用户可能感兴趣的项目范围缩小到可能的候选项目候选名单(以数百为单位)。
- 排名后阶段有助于确保多样性、新鲜度和公平性,并且将候选推荐项重新整理为数十种顺序的一系列有用推荐。
在此 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 不支持“chaining”多个顺序模型,则需要创建一项连接检索和排名模型的新服务。
- 将以下代码添加到
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 应用发送从 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'); }
运行应用
- 点击 Start debugging(开始调试),然后等待应用加载。
- 输入用户 ID(例如42),然后选择 Recommend(推荐)。
9. 第 4 步:在桌面平台上运行 Flutter 应用
除了 Android 和 iOS 之外,Flutter 还支持包括 Linux、Mac 和 Windows 在内的桌面平台。
Linux
- 确保目标设备在 VSCode 的状态栏中设置为 。
- 点击 Start debugging(开始调试),然后等待应用加载。
- 输入用户 ID(例如42),然后选择 Recommend(推荐)。
Mac
- 对于 Mac,您需要设置适当的使用权,因为应用会向后端发送 HTTP 请求。如需了解详情,请参阅使用权和应用沙盒。
将以下代码分别添加到 step4/frontend/macOS/Runner/DebugProfile.entitlements
和 step4/frontend/macOS/Runner/Release.entitlements
:
<key>com.apple.security.network.client</key>
<true/>
- 确保目标设备在 VSCode 的状态栏中设置为 。
- 点击 Start debugging(开始调试),然后等待应用加载。
- 输入用户 ID(例如42),然后选择 Recommend(推荐)。
Windows
- 确保目标设备在 VSCode 的状态栏中设置为 。
- 点击 Start debugging(开始调试),然后等待应用加载。
- 输入用户 ID(例如42),然后选择 Recommend(推荐)。
10. 第 5 步:在 Web 平台上运行 Flutter 应用
您还可以向 Flutter 应用添加 Web 支持。默认情况下,系统会自动为 Flutter 应用启用 Web 平台,因此您只需启动该应用即可。
- 确保目标设备在 VSCode 的状态栏中设置为 。
- 点击 Start debugging(开始调试),然后等待应用在 Chrome 浏览器中加载。
- 输入用户 ID(例如42),然后选择 Recommend(推荐)。
11. 恭喜
您构建了一个全栈应用来向用户推荐电影!
虽然该应用仅推荐电影,但您已经学习了构建强大的推荐引擎的总体工作流,并掌握了在 Flutter 应用中使用推荐的技能。你可以轻松将所学内容运用到其他场景(例如电子商务、美食和短视频)。