풀 스택 영화 추천 시스템 빌드

1. 시작하기 전에

영화나 식당을 추천하는 것부터 재미있는 동영상을 강조하는 것까지, 추천 엔진(추천자)은 머신러닝의 매우 중요한 응용 분야입니다. 추천자는 방대한 후보 풀에서 사용자에게 매력적인 콘텐츠를 제시하는 데 도움이 됩니다. 예를 들어 Google Play 스토어는 수백만 개의 앱을 설치하고, YouTube는 시청할 동영상을 수십억 개 제공합니다. 매일 더 많은 앱과 동영상이 추가됩니다.

이 Codelab에서는 다음을 사용하여 풀 스택 추천자를 빌드하는 방법을 알아봅니다.

  • 영화 추천을 위해 검색 및 순위 모델을 학습시키는 TensorFlow Recommenders
  • TensorFlow Serving으로 모델 제공
  • Flutter로 추천 영화를 표시하는 크로스 플랫폼 앱 만들기

기본 요건

  • Flutter 개발 및 Dart에 관한 기본 지식
  • 학습 및 배포와 같은 TensorFlow를 활용한 머신러닝에 관한 기본 지식
  • 추천 시스템에 관한 기본 지식
  • Python, 터미널, Docker에 관한 기본 지식

학습할 내용

  • TensorFlow 추천자를 사용하여 검색 및 순위 모델을 학습시키는 방법
  • TensorFlow Serving을 사용하여 학습된 추천 모델을 제공하는 방법
  • 추천 항목을 표시하는 크로스 플랫폼 Flutter 앱을 빌드하는 방법

필요한 항목

2. Flutter 개발 환경 설정

Flutter 개발의 경우 이 Codelab을 완료하려면 Flutter SDK편집기라는 두 가지 소프트웨어가 필요합니다.

다음 기기 중 하나를 사용하여 Codelab의 프런트엔드를 실행할 수 있습니다.

  • iOS 시뮬레이터(Xcode 도구 설치 필요)
  • Android Emulator(Android 스튜디오 설정 필요)
  • 브라우저(디버깅 시 Chrome 필요)
  • Windows, Linux 또는 macOS 데스크톱 애플리케이션. 배포에 사용할 플랫폼에서 개발해야 합니다. 따라서 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 시뮬레이터가 올바르게 설정되어 있고 상태 표시줄에 표시되는지 확인합니다.

예를 들어 Android Emulator로 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

지금 추천을 클릭해도 앱이 아직 백엔드와 통신할 수 없으므로 아무 일도 일어나지 않습니다.

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 앱에서 영화 추천을 쿼리하도록 요청을 보낼 수 있습니다.

프런트엔드 앱은 매우 간단합니다. 사용자 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) 추천을 선택합니다.

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 요청을 백엔드로 전송하므로 적절한 사용 권한을 설정해야 합니다. 자세한 내용은 사용 권한 및 앱 샌드박스를 참고하세요.

다음 코드를 각각 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 앱에서 추천을 사용하는 스킬을 마스터했습니다. 배운 내용을 다른 시나리오 (예: 전자상거래, 음식, 짧은 동영상)에 쉽게 적용할 수 있습니다.

자세히 알아보기