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

1. 시작하기 전에

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

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

  • TensorFlow Recommenders를 사용하여 영화 추천을 위한 검색 및 순위 지정 모델 학습
  • TensorFlow Serving을 사용하여 모델 서빙
  • Flutter를 사용하여 추천 영화를 표시하는 크로스 플랫폼 앱을 만듭니다.

기본 요건

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

학습할 내용

  • TensorFlow Recommenders를 사용하여 검색 및 순위 지정 모델을 학습하는 방법
  • 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

지금 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 앱에서 영화 추천을 쿼리하기 위해 요청을 전송할 수 있습니다.

프런트엔드 앱은 매우 간단합니다. 사용자 ID를 입력받고 방금 빌드한 백엔드로 요청을 전송하는 텍스트 필드만 있습니다 (recommend() 함수). 응답을 받은 후 앱 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 앱에서 추천을 사용하는 기술을 숙달했습니다. 학습한 내용을 다른 시나리오 (예: 전자상거래, 음식, 짧은 동영상)에 쉽게 적용할 수 있습니다.

자세히 알아보기