{
"cells": [
{
"cell_type": "markdown",
"id": "1447214c",
"metadata": {},
"source": [
"# Implementing a Recommender System with SageMaker, MXNet, and Gluon\n",
"_**신경망 및 임베딩을 사용하여 비디오 추천 만들기**_\n",
"\n",
"--- \n",
"\n",
"---\n",
"\n",
"이 노트북의 내용은 하위 내용들을 참조했습니다. 자세한 내용은 링크를 통해 확인할 수 있습니다.\n",
"\n",
"*[Cyrus Vahid's 2017 re:Invent Talk](https://github.com/cyrusmvahid/gluontutorials/blob/master/recommendations/MLPMF.ipynb)\n",
"\n",
"*[AWS Amazon SageMaker Examples](https://github.com/aws/amazon-sagemaker-examples/blob/master/introduction_to_applying_machine_learning/gluon_recommender_system/gluon_recommender_system.ipynb)\n",
"\n",
"\n",
"\n",
"## Contents\n",
"\n",
"1. [Background](#Background)\n",
"1. [Setup](#Setup)\n",
"1. [Data](#Data)\n",
" 1. [Explore](#Explore)\n",
" 1. [Clean](#Clean)\n",
" 1. [Prepare](#Prepare)\n",
"1. [Train Locally](#Train-Locally)\n",
" 1. [Define Network](#Define-Network)\n",
" 1. [Set Parameters](#Set-Parameters)\n",
" 1. [Execute](#Execute)\n",
"1. [Train with SageMaker](#Train-with-SageMaker)\n",
" 1. [Wrap Code](#Wrap-Code)\n",
" 1. [Move Data](#Move-Data)\n",
" 1. [Submit](#Submit)\n",
"1. [Host](#Host)\n",
" 1. [Evaluate](#Evaluate)\n",
"1. [Wrap-up](#Wrap-up)\n",
"\n",
"---\n",
"\n",
"## Background\n",
"\n",
"여러 면에서 추천 시스템은 현재 기계 학습의 인기를 촉진하는 촉매제였습니다. 아마존의 초기 성공 중 하나는 “이 제품을 구매하고 구매 한 고객...” 기능이었으며, 백만 달러짜리 Netflix Prize 는 이 연구에 박차를 가하고 대중의 인식을 높이며 수많은 다른 데이터 과학 대회에 영감을 불어 넣었습니다.\n",
"\n",
"추천자 시스템은 다양한 데이터 소스와 ML 알고리즘을 활용할 수 있으며, 대부분은 다양한 비지도, 지도 및 강화 학습 기술을 전체적인 프레임워크에 결합합니다. 그러나 핵심 구성 요소는 거의 항상 유사한 항목에 대한 사용자의 과거 평점과 다른 유사한 사용자의 행동을 기반으로 특정 항목에 대한 사용자의 평가 (또는 구매) 를 예측하는 모델입니다. 이에 필요한 최소 데이터셋은 사용자의 아이템 평가 기록입니다. 이 노트북 예제의 경우 160,000개 이상의 디지털 동영상에 2백만 명 이상의 아마존 고객의 별 1~5개 등급을 사용합니다. 이 데이터 세트에 대한 자세한 내용은 [AWS Public Datasets page](https://s3.amazonaws.com/amazon-reviews-pds/readme.html) 에서 확인할 수 있습니다.\n",
"\n",
"행렬 분해 (Matrix factorization)는 대부분의 사용자 항목 예측 모델의 초석이었습니다. 이 메서드는 사용자가 행을 인덱싱하고 항목이 열을 인덱싱하는 사이즈가 크고, 스파스된 단일 행렬의 항목 등급으로 시작합니다. 그런 다음 곱했을때 더 큰 행렬의 정보와 관계를 유지하는 밀도가 낮은 두 개의 저차원 행렬을 찾습니다.\n",
"\n",
"\n",
"\n",
"행렬 분해는 딥 러닝 및 임베딩을 통해 확장되고 일반화되었습니다. 이러한 기술을 통해 향상된 성능과 유연성을 위해 비선형성을 도입할 수 있습니다. 이 노트북은 신경망 기반 모델에 적합하여 Amazon 비디오 데이터 세트에 대한 권장 모델을 생성합니다. 먼저 노트북에서 데이터를 탐색하고 데이터 샘플에 대한 모델을 학습하는 것으로 시작합니다. 나중에 전체 데이터세트로 확장하고 SageMaker 관리형 학습 클러스터를 사용하여 모델을 만들 것입니다. 그런 다음 엔드포인트에 배포하고 모델을 검증합니다.\n",
"\n",
"---\n",
"\n",
"## Setup\n",
"\n",
"_이 노트북은 ml.p2.xlarge 노트북 인스턴스에서 생성 및 테스트되었습니다._\n",
"\n",
"먼저 다음을 지정해 보도록 합니다.:\n",
"\n",
"- 학습 및 모델 데이터에 사용할 S3 버킷 및 prefix 입니다. 이 인스턴스는 노트북 인스턴스, 학습 및 호스팅과 동일한 리전 내에 있어야 합니다.\n",
"- 데이터에 대한 교육 및 호스팅 액세스 권한을 부여하는데 IAM 역할 ARN 이 사용됩니다. 이러한 파일을 만드는 방법은 설명서를 참조하십시오. 노트북 인스턴스, 교육 및/또는 호스팅에 둘 이상의 역할이 필요한 경우 `get_execution_role ()` 호출을 적절한 전체 IAM 역할 ARN 문자열로 바꾸십시오.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b852dd58",
"metadata": {},
"outputs": [],
"source": [
"import sagemaker\n",
"import boto3\n",
"\n",
"role = sagemaker.get_execution_role()\n",
"region = boto3.Session().region_name\n",
"\n",
"# S3 bucket for saving code and model artifacts.\n",
"# Feel free to specify a different bucket and prefix\n",
"bucket = sagemaker.Session().default_bucket()\n",
"prefix = \"sagemaker/DEMO-gluon-recsys\""
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c0309f92",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import mxnet as mx\n",
"from mxnet import gluon, nd, ndarray\n",
"from mxnet.metric import MSE\n",
"import pandas as pd\n",
"import numpy as np\n",
"from sagemaker.mxnet import MXNet\n",
"import json\n",
"import matplotlib.pyplot as plt\n",
"import json"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "6924455d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"download: s3://amazon-reviews-pds/tsv/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz to ../../../tmp/recsys/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz\n"
]
}
],
"source": [
"!mkdir /tmp/recsys/\n",
"!aws s3 cp s3://amazon-reviews-pds/tsv/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz /tmp/recsys/"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "b3384d6a",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"b'Skipping line 92523: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 343254: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 524626: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 623024: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 977412: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 1496867: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 1711638: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 1787213: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 2395306: expected 15 fields, saw 22\\n'\n",
"b'Skipping line 2527690: expected 15 fields, saw 22\\n'\n"
]
},
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
marketplace
\n",
"
customer_id
\n",
"
review_id
\n",
"
product_id
\n",
"
product_parent
\n",
"
product_title
\n",
"
product_category
\n",
"
star_rating
\n",
"
helpful_votes
\n",
"
total_votes
\n",
"
vine
\n",
"
verified_purchase
\n",
"
review_headline
\n",
"
review_body
\n",
"
review_date
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
US
\n",
"
12190288
\n",
"
R3FU16928EP5TC
\n",
"
B00AYB1482
\n",
"
668895143
\n",
"
Enlightened: Season 1
\n",
"
Digital_Video_Download
\n",
"
5
\n",
"
0
\n",
"
0
\n",
"
N
\n",
"
Y
\n",
"
I loved it and I wish there was a season 3
\n",
"
I loved it and I wish there was a season 3... ...
\n",
"
2015-08-31
\n",
"
\n",
"
\n",
"
1
\n",
"
US
\n",
"
30549954
\n",
"
R1IZHHS1MH3AQ4
\n",
"
B00KQD28OM
\n",
"
246219280
\n",
"
Vicious
\n",
"
Digital_Video_Download
\n",
"
5
\n",
"
0
\n",
"
0
\n",
"
N
\n",
"
Y
\n",
"
As always it seems that the best shows come fr...
\n",
"
As always it seems that the best shows come fr...
\n",
"
2015-08-31
\n",
"
\n",
"
\n",
"
2
\n",
"
US
\n",
"
52895410
\n",
"
R52R85WC6TIAH
\n",
"
B01489L5LQ
\n",
"
534732318
\n",
"
After Words
\n",
"
Digital_Video_Download
\n",
"
4
\n",
"
17
\n",
"
18
\n",
"
N
\n",
"
Y
\n",
"
Charming movie
\n",
"
This movie isn't perfect, but it gets a lot of...
\n",
"
2015-08-31
\n",
"
\n",
"
\n",
"
3
\n",
"
US
\n",
"
27072354
\n",
"
R7HOOYTVIB0DS
\n",
"
B008LOVIIK
\n",
"
239012694
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
Digital_Video_Download
\n",
"
5
\n",
"
0
\n",
"
0
\n",
"
N
\n",
"
Y
\n",
"
Five Stars
\n",
"
excellant this is what tv should be
\n",
"
2015-08-31
\n",
"
\n",
"
\n",
"
4
\n",
"
US
\n",
"
26939022
\n",
"
R1XQ2N5CDOZGNX
\n",
"
B0094LZMT0
\n",
"
535858974
\n",
"
On The Waterfront
\n",
"
Digital_Video_Download
\n",
"
5
\n",
"
0
\n",
"
0
\n",
"
N
\n",
"
Y
\n",
"
Brilliant film from beginning to end
\n",
"
Brilliant film from beginning to end. All of t...
\n",
"
2015-08-31
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" marketplace customer_id review_id product_id product_parent \\\n",
"0 US 12190288 R3FU16928EP5TC B00AYB1482 668895143 \n",
"1 US 30549954 R1IZHHS1MH3AQ4 B00KQD28OM 246219280 \n",
"2 US 52895410 R52R85WC6TIAH B01489L5LQ 534732318 \n",
"3 US 27072354 R7HOOYTVIB0DS B008LOVIIK 239012694 \n",
"4 US 26939022 R1XQ2N5CDOZGNX B0094LZMT0 535858974 \n",
"\n",
" product_title product_category star_rating \\\n",
"0 Enlightened: Season 1 Digital_Video_Download 5 \n",
"1 Vicious Digital_Video_Download 5 \n",
"2 After Words Digital_Video_Download 4 \n",
"3 Masterpiece: Inspector Lewis Season 5 Digital_Video_Download 5 \n",
"4 On The Waterfront Digital_Video_Download 5 \n",
"\n",
" helpful_votes total_votes vine verified_purchase \\\n",
"0 0 0 N Y \n",
"1 0 0 N Y \n",
"2 17 18 N Y \n",
"3 0 0 N Y \n",
"4 0 0 N Y \n",
"\n",
" review_headline \\\n",
"0 I loved it and I wish there was a season 3 \n",
"1 As always it seems that the best shows come fr... \n",
"2 Charming movie \n",
"3 Five Stars \n",
"4 Brilliant film from beginning to end \n",
"\n",
" review_body review_date \n",
"0 I loved it and I wish there was a season 3... ... 2015-08-31 \n",
"1 As always it seems that the best shows come fr... 2015-08-31 \n",
"2 This movie isn't perfect, but it gets a lot of... 2015-08-31 \n",
"3 excellant this is what tv should be 2015-08-31 \n",
"4 Brilliant film from beginning to end. All of t... 2015-08-31 "
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.read_csv(\n",
" \"/tmp/recsys/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz\",\n",
" delimiter=\"\\t\",\n",
" error_bad_lines=False,\n",
")\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"id": "0ee36dfc",
"metadata": {},
"source": [
"이 데이터셋에는 다음과 같은 정보가 포함되어 있습니다.\n",
"\n",
"- `marketplace`: 2자리 국가 코드 (이 경우 모두 “US”).\n",
"- `customer_id`: 단일 작성자가 작성한 리뷰를 집계하는 데 사용할 수 있는 임의의 식별자입니다.\n",
"- `review_id`: 리뷰의 고유 ID입니다.\n",
"- `product_id`: 아마존 표준 식별 번호 (ASIN) 입니다.`http://www.amazon.com/dp/` 링크는 제품 상세 페이지로 연결됩니다.\n",
"- `product_parent`: 해당 ASIN의 상위 항목입니다.여러 ASIN (동일한 상품의 색상 또는 형식 변형) 은 단일 상위 항목으로 롤업될 수 있습니다.\n",
"- `product_title`: 상품의 제목 설명입니다.\n",
"- `product_category`: 리뷰를 그룹화하는 데 사용할 수있는 광범위한 제품 카테고리 (이 경우 디지털 비디오).\n",
"- `star_rating`: 리뷰의 평점 (별 1~5개)\n",
"- `helpful_votes`: 검토에 도움이 되는 투표 수입니다.\n",
"- `total_votes`: 검토가 받은 총 투표 수입니다.\n",
"- `vine`: 리뷰가 [바인](https://www.amazon.com/gp/vine/help) 프로그램의 일부로 작성되었습니까?\n",
"- `verified_purchase`: 구매의 리뷰였나요?\n",
"- `review_headline`: 리뷰 자체의 제목입니다.\n",
"- `review_body`: 리뷰의 텍스트입니다.\n",
"- `review_date`: 리뷰가 작성된 날짜입니다.\n",
"\n",
"이 예제에서는 데이터 범위를 `customer_id`, `product_id`, 및 `star_rating` 으로 제한해 보겠습니다. 추천 시스템에 추가 기능을 포함하면 도움이 될 수 있지만 상당한 처리 (특히 텍스트 데이터) 가 필요하므로 이 노트북의 범위를 벗어날 수 있습니다.\n",
"\n",
"*참고: 나중에 노트북에서 추천 사항을 확인할 수 있도록 데이터세트에 `product_title` 을 보관할 예정이지만 알고리즘 교육에는 사용되지 않습니다.*\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "853be358",
"metadata": {},
"outputs": [],
"source": [
"df = df[[\"customer_id\", \"product_id\", \"star_rating\", \"product_title\"]]"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f059df6a",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
customer_id
\n",
"
product_id
\n",
"
star_rating
\n",
"
product_title
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
12190288
\n",
"
B00AYB1482
\n",
"
5
\n",
"
Enlightened: Season 1
\n",
"
\n",
"
\n",
"
1
\n",
"
30549954
\n",
"
B00KQD28OM
\n",
"
5
\n",
"
Vicious
\n",
"
\n",
"
\n",
"
2
\n",
"
52895410
\n",
"
B01489L5LQ
\n",
"
4
\n",
"
After Words
\n",
"
\n",
"
\n",
"
3
\n",
"
27072354
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
\n",
"
\n",
"
4
\n",
"
26939022
\n",
"
B0094LZMT0
\n",
"
5
\n",
"
On The Waterfront
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" customer_id product_id star_rating product_title\n",
"0 12190288 B00AYB1482 5 Enlightened: Season 1\n",
"1 30549954 B00KQD28OM 5 Vicious\n",
"2 52895410 B01489L5LQ 4 After Words\n",
"3 27072354 B008LOVIIK 5 Masterpiece: Inspector Lewis Season 5\n",
"4 26939022 B0094LZMT0 5 On The Waterfront"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.head()"
]
},
{
"cell_type": "markdown",
"id": "8a78c214",
"metadata": {},
"source": [
"대부분의 사람들은 대부분의 비디오를 보지 못하고 사람들은 우리가 실제로 보는 것보다 더 적은 수의 동영상을 평가하기 때문에 데이터가 희박할 것으로 예상합니다. 알고리즘은 일반적으로 이러한 희소 문제와 잘 작동하지만 여전히 필요없는 부분을 정리하고 싶을 수 있습니다. 확인해야 할 몇 가지 기본 백분위수를 살펴보겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "b3946342",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"customers\n",
" 0.00 1.0\n",
"0.01 1.0\n",
"0.02 1.0\n",
"0.03 1.0\n",
"0.04 1.0\n",
"0.05 1.0\n",
"0.10 1.0\n",
"0.25 1.0\n",
"0.50 1.0\n",
"0.75 2.0\n",
"0.90 4.0\n",
"0.95 5.0\n",
"0.96 6.0\n",
"0.97 7.0\n",
"0.98 9.0\n",
"0.99 13.0\n",
"1.00 2704.0\n",
"Name: customer_id, dtype: float64\n",
"products\n",
" 0.00 1.00\n",
"0.01 1.00\n",
"0.02 1.00\n",
"0.03 1.00\n",
"0.04 1.00\n",
"0.05 1.00\n",
"0.10 1.00\n",
"0.25 1.00\n",
"0.50 3.00\n",
"0.75 9.00\n",
"0.90 31.00\n",
"0.95 73.00\n",
"0.96 95.00\n",
"0.97 130.00\n",
"0.98 199.00\n",
"0.99 386.67\n",
"1.00 32790.00\n",
"Name: product_id, dtype: float64\n"
]
}
],
"source": [
"customers = df[\"customer_id\"].value_counts()\n",
"products = df[\"product_id\"].value_counts()\n",
"\n",
"quantiles = [\n",
" 0,\n",
" 0.01,\n",
" 0.02,\n",
" 0.03,\n",
" 0.04,\n",
" 0.05,\n",
" 0.1,\n",
" 0.25,\n",
" 0.5,\n",
" 0.75,\n",
" 0.9,\n",
" 0.95,\n",
" 0.96,\n",
" 0.97,\n",
" 0.98,\n",
" 0.99,\n",
" 1,\n",
"]\n",
"print(\"customers\\n\", customers.quantile(quantiles))\n",
"print(\"products\\n\", products.quantile(quantiles))"
]
},
{
"cell_type": "markdown",
"id": "d771d830",
"metadata": {},
"source": [
"보시다시피 고객의 약 5% 만이 5 개 이상의 동영상을 평가했으며 9 명 이상의 고객이 동영상의 25% 만 평가했습니다.\n",
"\n",
"### Clean\n",
"\n",
"데이터를 정리해 봅니다"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "66d2b715",
"metadata": {},
"outputs": [],
"source": [
"customers = customers[customers >= 5]\n",
"products = products[products >= 10]\n",
"\n",
"reduced_df = df.merge(pd.DataFrame({\"customer_id\": customers.index})).merge(\n",
" pd.DataFrame({\"product_id\": products.index})\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "76667241",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
customer_id
\n",
"
product_id
\n",
"
star_rating
\n",
"
product_title
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
27072354
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
\n",
"
\n",
"
1
\n",
"
16030865
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
\n",
"
\n",
"
2
\n",
"
44025160
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
\n",
"
\n",
"
3
\n",
"
18602179
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
\n",
"
\n",
"
4
\n",
"
14424972
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" customer_id product_id star_rating product_title\n",
"0 27072354 B008LOVIIK 5 Masterpiece: Inspector Lewis Season 5\n",
"1 16030865 B008LOVIIK 5 Masterpiece: Inspector Lewis Season 5\n",
"2 44025160 B008LOVIIK 5 Masterpiece: Inspector Lewis Season 5\n",
"3 18602179 B008LOVIIK 5 Masterpiece: Inspector Lewis Season 5\n",
"4 14424972 B008LOVIIK 5 Masterpiece: Inspector Lewis Season 5"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reduced_df.head()"
]
},
{
"cell_type": "markdown",
"id": "78fec0b2",
"metadata": {},
"source": [
"이제 리뷰가 5개 이상인 고객이 있기 때문에 고객 및 제품 목록을 다시 만들겠습니다. 하지만 모든 리뷰는 리뷰가 5개 미만인 제품에 대한 리뷰입니다 (반대의 경우도 마찬가지입니다)."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "83e40f97",
"metadata": {},
"outputs": [],
"source": [
"customers = reduced_df[\"customer_id\"].value_counts()\n",
"products = reduced_df[\"product_id\"].value_counts()"
]
},
{
"cell_type": "markdown",
"id": "752dbdf8",
"metadata": {},
"source": [
"다음으로 각 사용자와 항목에 번호를 매겨 고유한 순차적 index 를 지정합니다. 이렇게 하면 순차 index 가 등급 행렬의 행과 열을 나타내는 sparse 형식으로 정보를 유지할 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "2270a822",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"customers\n",
" 0.00 1.0\n",
"0.01 2.0\n",
"0.02 3.0\n",
"0.03 3.0\n",
"0.04 4.0\n",
"0.05 4.0\n",
"0.10 4.0\n",
"0.25 5.0\n",
"0.50 6.0\n",
"0.75 9.0\n",
"0.90 14.0\n",
"0.95 19.0\n",
"0.96 21.0\n",
"0.97 24.0\n",
"0.98 28.0\n",
"0.99 37.0\n",
"1.00 467.0\n",
"Name: customer_id, dtype: float64\n",
"products\n",
" 0.00 1.00\n",
"0.01 1.00\n",
"0.02 2.00\n",
"0.03 2.00\n",
"0.04 2.00\n",
"0.05 2.00\n",
"0.10 3.00\n",
"0.25 6.00\n",
"0.50 10.00\n",
"0.75 22.00\n",
"0.90 58.00\n",
"0.95 111.00\n",
"0.96 136.64\n",
"0.97 172.48\n",
"0.98 236.00\n",
"0.99 379.00\n",
"1.00 5461.00\n",
"Name: product_id, dtype: float64\n"
]
}
],
"source": [
"print(\"customers\\n\", customers.quantile(quantiles))\n",
"print(\"products\\n\", products.quantile(quantiles))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "eaaffefb",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
customer_id
\n",
"
product_id
\n",
"
star_rating
\n",
"
product_title
\n",
"
user
\n",
"
item
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
27072354
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
10463
\n",
"
107
\n",
"
\n",
"
\n",
"
1
\n",
"
16030865
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
489
\n",
"
107
\n",
"
\n",
"
\n",
"
2
\n",
"
44025160
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
32100
\n",
"
107
\n",
"
\n",
"
\n",
"
3
\n",
"
18602179
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
2237
\n",
"
107
\n",
"
\n",
"
\n",
"
4
\n",
"
14424972
\n",
"
B008LOVIIK
\n",
"
5
\n",
"
Masterpiece: Inspector Lewis Season 5
\n",
"
32340
\n",
"
107
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" customer_id product_id star_rating \\\n",
"0 27072354 B008LOVIIK 5 \n",
"1 16030865 B008LOVIIK 5 \n",
"2 44025160 B008LOVIIK 5 \n",
"3 18602179 B008LOVIIK 5 \n",
"4 14424972 B008LOVIIK 5 \n",
"\n",
" product_title user item \n",
"0 Masterpiece: Inspector Lewis Season 5 10463 107 \n",
"1 Masterpiece: Inspector Lewis Season 5 489 107 \n",
"2 Masterpiece: Inspector Lewis Season 5 32100 107 \n",
"3 Masterpiece: Inspector Lewis Season 5 2237 107 \n",
"4 Masterpiece: Inspector Lewis Season 5 32340 107 "
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"customer_index = pd.DataFrame(\n",
" {\"customer_id\": customers.index, \"user\": np.arange(customers.shape[0])}\n",
")\n",
"product_index = pd.DataFrame({\"product_id\": products.index, \"item\": np.arange(products.shape[0])})\n",
"\n",
"reduced_df = reduced_df.merge(customer_index).merge(product_index)\n",
"reduced_df.head()"
]
},
{
"cell_type": "markdown",
"id": "6a7f172a",
"metadata": {},
"source": [
"### Prepare\n",
"\n",
"먼저 train 세트와 test 세트를 분할해 보겠습니다. 이를 통해 고객이 평가는 했지만 학습에 포함되지 않은 비디오에 대한 모델의 정확도를 추정할 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "79f7c130",
"metadata": {},
"outputs": [],
"source": [
"test_df = reduced_df.groupby(\"customer_id\").last().reset_index()\n",
"\n",
"train_df = reduced_df.merge(\n",
" test_df[[\"customer_id\", \"product_id\"]],\n",
" on=[\"customer_id\", \"product_id\"],\n",
" how=\"outer\",\n",
" indicator=True,\n",
")\n",
"train_df = train_df[(train_df[\"_merge\"] == \"left_only\")]"
]
},
{
"cell_type": "markdown",
"id": "e032e67a",
"metadata": {},
"source": [
"이제 Pandas 데이터 프레임을 MXNet NDArray로 변환하고, 이를 사용하여 SparseMatrixDataset 클래스의 멤버를 만든 다음 이를 MXNet 데이터 Iterator 에 추가할 수 있습니다. 이 프로세스는 테스트와 제어 모두에서 동일합니다."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "31c9ef69",
"metadata": {},
"outputs": [],
"source": [
"batch_size = 1024\n",
"\n",
"train = gluon.data.ArrayDataset(\n",
" nd.array(train_df[\"user\"].values, dtype=np.float32),\n",
" nd.array(train_df[\"item\"].values, dtype=np.float32),\n",
" nd.array(train_df[\"star_rating\"].values, dtype=np.float32),\n",
")\n",
"test = gluon.data.ArrayDataset(\n",
" nd.array(test_df[\"user\"].values, dtype=np.float32),\n",
" nd.array(test_df[\"item\"].values, dtype=np.float32),\n",
" nd.array(test_df[\"star_rating\"].values, dtype=np.float32),\n",
")\n",
"\n",
"train_iter = gluon.data.DataLoader(\n",
" train, shuffle=True, num_workers=4, batch_size=batch_size, last_batch=\"rollover\"\n",
")\n",
"test_iter = gluon.data.DataLoader(\n",
" train, shuffle=True, num_workers=4, batch_size=batch_size, last_batch=\"rollover\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "4e577ca6",
"metadata": {},
"source": [
"---\n",
"\n",
"## Train Locally\n",
"\n",
"### Define Network\n",
"\n",
"matrix factorization 작업의 신경망 버전을 정의하는 것부터 시작하겠습니다. 이 경우 네트워크는 매우 간단합니다. 주요 구성 요소는 다음과 같습니다.\n",
"- [Embeddings](https://mxnet.incubator.apache.org/api/python/gluon/nn.html#mxnet.gluon.nn.Embedding) 은 인덱스를 고정 크기의 고밀도 벡터로 변환합니다. 이 경우, 64입니다.\n",
"- ReLU 활성화가 적용된 [Dense layers](https://mxnet.incubator.apache.org/api/python/gluon.html#mxnet.gluon.nn.Dense).각 고밀도 레이어는 임베딩 수와 동일한 수의 단위를 갖습니다. 여기서 ReLU 활성화는 matrix factorization 에 약간의 비선형성을 추가합니다.\n",
"- [Dropout layers](https://mxnet.incubator.apache.org/api/python/gluon.html#mxnet.gluon.nn.Dropout) 는 과적합을 방지하는 데 사용할 수 있습니다.\n",
"- 사용자 매트릭스와 아이템 매트릭스의 행렬 곱셈을 통해 등급 매트릭스의 추정치를 생성합니다.\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "06a7aa31",
"metadata": {},
"outputs": [],
"source": [
"class MFBlock(gluon.HybridBlock):\n",
" def __init__(self, max_users, max_items, num_emb, dropout_p=0.5):\n",
" super(MFBlock, self).__init__()\n",
"\n",
" self.max_users = max_users\n",
" self.max_items = max_items\n",
" self.dropout_p = dropout_p\n",
" self.num_emb = num_emb\n",
"\n",
" with self.name_scope():\n",
" self.user_embeddings = gluon.nn.Embedding(max_users, num_emb)\n",
" self.item_embeddings = gluon.nn.Embedding(max_items, num_emb)\n",
"\n",
" self.dropout_user = gluon.nn.Dropout(dropout_p)\n",
" self.dropout_item = gluon.nn.Dropout(dropout_p)\n",
"\n",
" self.dense_user = gluon.nn.Dense(num_emb, activation=\"relu\")\n",
" self.dense_item = gluon.nn.Dense(num_emb, activation=\"relu\")\n",
"\n",
" def hybrid_forward(self, F, users, items):\n",
" a = self.user_embeddings(users)\n",
" a = self.dense_user(a)\n",
"\n",
" b = self.item_embeddings(items)\n",
" b = self.dense_item(b)\n",
"\n",
" predictions = self.dropout_user(a) * self.dropout_item(b)\n",
" predictions = F.sum(predictions, axis=1)\n",
" return predictions"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "3f49fe70",
"metadata": {},
"outputs": [],
"source": [
"num_embeddings = 64\n",
"\n",
"net = MFBlock(\n",
" max_users=customer_index.shape[0],\n",
" max_items=product_index.shape[0],\n",
" num_emb=num_embeddings,\n",
" dropout_p=0.5,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "bde5097c",
"metadata": {},
"source": [
"### Set Parameters\n",
"\n",
"네트워크 가중치를 초기화하고 최적화 파라미터를 설정해 보겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "d1d6e08b",
"metadata": {},
"outputs": [],
"source": [
"# Initialize network parameters\n",
"ctx = mx.gpu()\n",
"net.collect_params().initialize(mx.init.Xavier(magnitude=60), ctx=ctx, force_reinit=True)\n",
"net.hybridize()\n",
"\n",
"# Set optimization parameters\n",
"opt = \"sgd\"\n",
"lr = 0.02\n",
"momentum = 0.9\n",
"wd = 0.0\n",
"\n",
"trainer = gluon.Trainer(\n",
" net.collect_params(), opt, {\"learning_rate\": lr, \"wd\": wd, \"momentum\": momentum}\n",
")"
]
},
{
"cell_type": "markdown",
"id": "aaaf44a6",
"metadata": {},
"source": [
"### Execute\n",
"\n",
"신경망의 훈련을 수행하는 함수를 정의해 보겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "48f01170",
"metadata": {},
"outputs": [],
"source": [
"def execute(train_iter, test_iter, net, epochs, ctx):\n",
"\n",
" loss_function = gluon.loss.L2Loss()\n",
" for e in range(epochs):\n",
"\n",
" print(\"epoch: {}\".format(e))\n",
"\n",
" for i, (user, item, label) in enumerate(train_iter):\n",
" user = user.as_in_context(ctx)\n",
" item = item.as_in_context(ctx)\n",
" label = label.as_in_context(ctx)\n",
"\n",
" with mx.autograd.record():\n",
" output = net(user, item)\n",
" loss = loss_function(output, label)\n",
"\n",
" loss.backward()\n",
" trainer.step(batch_size)\n",
"\n",
" print(\n",
" \"EPOCH {}: MSE ON TRAINING and TEST: {}. {}\".format(\n",
" e,\n",
" eval_net(train_iter, net, ctx, loss_function),\n",
" eval_net(test_iter, net, ctx, loss_function),\n",
" )\n",
" )\n",
" print(\"end of training\")\n",
" return net"
]
},
{
"cell_type": "markdown",
"id": "a2181b9b",
"metadata": {},
"source": [
"또한 주어진 데이터셋에서 네트워크를 평가하는 함수를 정의해 보겠습니다. 위의 `execute` 함수에 의해 호출되어 train 및 test 데이터 세트에 평균 제곱 오차 값을 제공합니다.\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "f9b3ffaa",
"metadata": {},
"outputs": [],
"source": [
"def eval_net(data, net, ctx, loss_function):\n",
" acc = MSE()\n",
" for i, (user, item, label) in enumerate(data):\n",
"\n",
" user = user.as_in_context(ctx)\n",
" item = item.as_in_context(ctx)\n",
" label = label.as_in_context(ctx)\n",
" predictions = net(user, item).reshape((batch_size, 1))\n",
" acc.update(preds=[predictions], labels=[label])\n",
"\n",
" return acc.get()[1]"
]
},
{
"cell_type": "markdown",
"id": "791ea91f",
"metadata": {},
"source": [
"이제 몇개의 epoch 숫자를 통해 학습을 실행합니다."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "ab4ebcc4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"epoch: 0\n",
"[2022-01-11 05:39:26.465 ip-172-16-26-178:21390 INFO utils.py:27] RULE_JOB_STOP_SIGNAL_FILENAME: None\n",
"[2022-01-11 05:39:26.540 ip-172-16-26-178:21390 INFO profiler_config_parser.py:111] Unable to find config at /opt/ml/input/config/profilerconfig.json. Profiler is disabled.\n",
"EPOCH 0: MSE ON TRAINING and TEST: 1.1804483084089727. 1.1803953215420484\n",
"epoch: 1\n",
"EPOCH 1: MSE ON TRAINING and TEST: 1.0181562126395236. 1.018070052819423\n",
"epoch: 2\n",
"EPOCH 2: MSE ON TRAINING and TEST: 0.9422775065875149. 0.9423712044212949\n",
"end of training\n",
"CPU times: user 55 s, sys: 5.55 s, total: 1min\n",
"Wall time: 1min 26s\n"
]
}
],
"source": [
"%%time\n",
"\n",
"epochs = 3\n",
"\n",
"trained_net = execute(train_iter, test_iter, net, epochs, ctx)"
]
},
{
"cell_type": "markdown",
"id": "23f42830",
"metadata": {},
"source": [
"#### Early Validation\n",
"\n",
"train 오류가 감소하는 것을 볼 수 있지만 test 정확도는 약간 반등합니다. 모델이 개별 사용자에 대해 어떻게 예측하는지 확인해 보겠습니다. 무작위로 선택할 수도 있지만, 이 경우 #6 사용자를 사용해 보겠습니다.\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "f96e4058",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"product_index[[\"u6_predictions\", \"u7_predictions\"]].plot.scatter(\"u6_predictions\", \"u7_predictions\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "5d6c5cb6",
"metadata": {},
"source": [
"이 상관 관계가 거의 완벽하다는 것을 알 수 있습니다. 기본적으로 item 들의 평균 평점은 사용자 전체에서 우세하며 모든 사람에게 잘 리뷰된 동일한 item 을 추천합니다. 결과적으로 더 많은 임베딩을 추가 할 수 있으며 사용자 간의 차등 선호도를 더 잘 포착 할 수 있기 때문에 이러한 관계는 사라질 것입니다.\n",
"\n",
"그러나 64 차원 임베딩으로는 3번의 epoch만 실행하는 데 7분이 걸렸습니다. Notebook Instance 외부에서 이 작업을 실행하면 생산성이 향상될 수 있습니다.\n",
"\n",
"---\n",
"\n",
"## Train with SageMaker\n",
"\n",
"이제 이 작은 데이터세트에 대해 train 을 마쳤으므로 SageMaker 의 분산, 완전 관리되는 학습 환경에서 train 을 확장할 수 있습니다.\n",
"\n",
"### Wrap Code\n",
"\n",
"SageMaker의 사전 빌드된 MXNet 컨테이너를 사용하려면 위의 코드를 파이썬 스크립트로 래핑해야 합니다. SageMaker의 사전 구축된 컨테이너를 사용하는 데에는 많은 유연성이 있으며 자세한 문서는 [here](https://github.com/aws/sagemaker-python-sdk#mxnet-sagemaker-estimators) 에서 찾을 수 있지만 이 예제에서는 다음과 같이 구성되었습니다.\n",
"\n",
"1. 모든 데이터 준비를 `prepare_train_data` 함수로 래핑 (원하는 대로 이름을 지정할 수도 있습니다)\n",
"1. 위의 단어 단위로 클래스와 함수 복사 및 붙여넣기\n",
"1. 다음과 같은 기능을 하는 `train` 함수를 정의합니다.\n",
" 1. SageMaker 교육 클러스터에서 입력 TSV 데이터셋을 선택하는 새로운 코드를 약간 추가합니다.\n",
" 1. 하이퍼파라미터의 딕셔너리를 취합니다 (위에서 전역으로 지정했습니다)\n",
" 1. 네트를 만들고 훈련을 실행합니다.\n"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "3b38bdcb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mjson\u001b[39;49;00m\n",
"\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mlogging\u001b[39;49;00m\n",
"\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mos\u001b[39;49;00m\n",
"\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mtime\u001b[39;49;00m\n",
"\n",
"\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mmxnet\u001b[39;49;00m \u001b[34mas\u001b[39;49;00m \u001b[04m\u001b[36mmx\u001b[39;49;00m\n",
"\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mnumpy\u001b[39;49;00m \u001b[34mas\u001b[39;49;00m \u001b[04m\u001b[36mnp\u001b[39;49;00m\n",
"\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36mmxnet\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m gluon, nd, ndarray\n",
"\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36mmxnet\u001b[39;49;00m\u001b[04m\u001b[36m.\u001b[39;49;00m\u001b[04m\u001b[36mmetric\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m MSE\n",
"\n",
"os.system(\u001b[33m\"\u001b[39;49;00m\u001b[33mpip install pandas\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n",
"\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mpandas\u001b[39;49;00m \u001b[34mas\u001b[39;49;00m \u001b[04m\u001b[36mpd\u001b[39;49;00m\n",
"\n",
"logging.basicConfig(level=logging.DEBUG)\n",
"\n",
"\u001b[37m#########\u001b[39;49;00m\n",
"\u001b[37m# Globals\u001b[39;49;00m\n",
"\u001b[37m#########\u001b[39;49;00m\n",
"\n",
"batch_size = \u001b[34m1024\u001b[39;49;00m\n",
"\n",
"\n",
"\u001b[37m##########\u001b[39;49;00m\n",
"\u001b[37m# Training\u001b[39;49;00m\n",
"\u001b[37m##########\u001b[39;49;00m\n",
"\n",
"\n",
"\u001b[34mdef\u001b[39;49;00m \u001b[32mtrain\u001b[39;49;00m(channel_input_dirs, hyperparameters, hosts, num_gpus, **kwargs):\n",
"\n",
" \u001b[37m# get data\u001b[39;49;00m\n",
" training_dir = channel_input_dirs[\u001b[33m\"\u001b[39;49;00m\u001b[33mtrain\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m]\n",
" train_iter, test_iter, customer_index, product_index = prepare_train_data(training_dir)\n",
"\n",
" \u001b[37m# get hyperparameters\u001b[39;49;00m\n",
" num_embeddings = hyperparameters.get(\u001b[33m\"\u001b[39;49;00m\u001b[33mnum_embeddings\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[34m64\u001b[39;49;00m)\n",
" opt = hyperparameters.get(\u001b[33m\"\u001b[39;49;00m\u001b[33mopt\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33msgd\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n",
" lr = hyperparameters.get(\u001b[33m\"\u001b[39;49;00m\u001b[33mlr\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[34m0.02\u001b[39;49;00m)\n",
" momentum = hyperparameters.get(\u001b[33m\"\u001b[39;49;00m\u001b[33mmomentum\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[34m0.9\u001b[39;49;00m)\n",
" wd = hyperparameters.get(\u001b[33m\"\u001b[39;49;00m\u001b[33mwd\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[34m0.0\u001b[39;49;00m)\n",
" epochs = hyperparameters.get(\u001b[33m\"\u001b[39;49;00m\u001b[33mepochs\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[34m5\u001b[39;49;00m)\n",
"\n",
" \u001b[37m# define net\u001b[39;49;00m\n",
" ctx = mx.gpu()\n",
"\n",
" net = MFBlock(\n",
" max_users=customer_index.shape[\u001b[34m0\u001b[39;49;00m],\n",
" max_items=product_index.shape[\u001b[34m0\u001b[39;49;00m],\n",
" num_emb=num_embeddings,\n",
" dropout_p=\u001b[34m0.5\u001b[39;49;00m,\n",
" )\n",
"\n",
" net.collect_params().initialize(mx.init.Xavier(magnitude=\u001b[34m60\u001b[39;49;00m), ctx=ctx, force_reinit=\u001b[34mTrue\u001b[39;49;00m)\n",
" net.hybridize()\n",
"\n",
" trainer = gluon.Trainer(\n",
" net.collect_params(), opt, {\u001b[33m\"\u001b[39;49;00m\u001b[33mlearning_rate\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: lr, \u001b[33m\"\u001b[39;49;00m\u001b[33mwd\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: wd, \u001b[33m\"\u001b[39;49;00m\u001b[33mmomentum\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: momentum}\n",
" )\n",
"\n",
" \u001b[37m# execute\u001b[39;49;00m\n",
" trained_net = execute(train_iter, test_iter, net, trainer, epochs, ctx)\n",
"\n",
" \u001b[34mreturn\u001b[39;49;00m trained_net, customer_index, product_index\n",
"\n",
"\n",
"\u001b[34mclass\u001b[39;49;00m \u001b[04m\u001b[32mMFBlock\u001b[39;49;00m(gluon.HybridBlock):\n",
" \u001b[34mdef\u001b[39;49;00m \u001b[32m__init__\u001b[39;49;00m(\u001b[36mself\u001b[39;49;00m, max_users, max_items, num_emb, dropout_p=\u001b[34m0.5\u001b[39;49;00m):\n",
" \u001b[36msuper\u001b[39;49;00m(MFBlock, \u001b[36mself\u001b[39;49;00m).\u001b[32m__init__\u001b[39;49;00m()\n",
"\n",
" \u001b[36mself\u001b[39;49;00m.max_users = max_users\n",
" \u001b[36mself\u001b[39;49;00m.max_items = max_items\n",
" \u001b[36mself\u001b[39;49;00m.dropout_p = dropout_p\n",
" \u001b[36mself\u001b[39;49;00m.num_emb = num_emb\n",
"\n",
" \u001b[34mwith\u001b[39;49;00m \u001b[36mself\u001b[39;49;00m.name_scope():\n",
" \u001b[36mself\u001b[39;49;00m.user_embeddings = gluon.nn.Embedding(max_users, num_emb)\n",
" \u001b[36mself\u001b[39;49;00m.item_embeddings = gluon.nn.Embedding(max_items, num_emb)\n",
"\n",
" \u001b[36mself\u001b[39;49;00m.dropout_user = gluon.nn.Dropout(dropout_p)\n",
" \u001b[36mself\u001b[39;49;00m.dropout_item = gluon.nn.Dropout(dropout_p)\n",
"\n",
" \u001b[36mself\u001b[39;49;00m.dense_user = gluon.nn.Dense(num_emb, activation=\u001b[33m\"\u001b[39;49;00m\u001b[33mrelu\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n",
" \u001b[36mself\u001b[39;49;00m.dense_item = gluon.nn.Dense(num_emb, activation=\u001b[33m\"\u001b[39;49;00m\u001b[33mrelu\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n",
"\n",
" \u001b[34mdef\u001b[39;49;00m \u001b[32mhybrid_forward\u001b[39;49;00m(\u001b[36mself\u001b[39;49;00m, F, users, items):\n",
" a = \u001b[36mself\u001b[39;49;00m.user_embeddings(users)\n",
" a = \u001b[36mself\u001b[39;49;00m.dense_user(a)\n",
"\n",
" b = \u001b[36mself\u001b[39;49;00m.item_embeddings(items)\n",
" b = \u001b[36mself\u001b[39;49;00m.dense_item(b)\n",
"\n",
" predictions = \u001b[36mself\u001b[39;49;00m.dropout_user(a) * \u001b[36mself\u001b[39;49;00m.dropout_item(b)\n",
" predictions = F.sum(predictions, axis=\u001b[34m1\u001b[39;49;00m)\n",
"\n",
" \u001b[34mreturn\u001b[39;49;00m predictions\n",
"\n",
"\n",
"\u001b[34mdef\u001b[39;49;00m \u001b[32mexecute\u001b[39;49;00m(train_iter, test_iter, net, trainer, epochs, ctx):\n",
" loss_function = gluon.loss.L2Loss()\n",
" \u001b[34mfor\u001b[39;49;00m e \u001b[35min\u001b[39;49;00m \u001b[36mrange\u001b[39;49;00m(epochs):\n",
" \u001b[36mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mepoch: \u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(e))\n",
" \u001b[34mfor\u001b[39;49;00m i, (user, item, label) \u001b[35min\u001b[39;49;00m \u001b[36menumerate\u001b[39;49;00m(train_iter):\n",
"\n",
" user = user.as_in_context(ctx)\n",
" item = item.as_in_context(ctx)\n",
" label = label.as_in_context(ctx)\n",
"\n",
" \u001b[34mwith\u001b[39;49;00m mx.autograd.record():\n",
" output = net(user, item)\n",
" loss = loss_function(output, label)\n",
" loss.backward()\n",
" trainer.step(batch_size)\n",
"\n",
" \u001b[36mprint\u001b[39;49;00m(\n",
" \u001b[33m\"\u001b[39;49;00m\u001b[33mEPOCH \u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m: MSE ON TRAINING and TEST: \u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m. \u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(\n",
" e,\n",
" eval_net(train_iter, net, ctx, loss_function),\n",
" eval_net(test_iter, net, ctx, loss_function),\n",
" )\n",
" )\n",
" \u001b[36mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mend of training\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n",
" \u001b[34mreturn\u001b[39;49;00m net\n",
"\n",
"\n",
"\u001b[34mdef\u001b[39;49;00m \u001b[32meval_net\u001b[39;49;00m(data, net, ctx, loss_function):\n",
" acc = MSE()\n",
" \u001b[34mfor\u001b[39;49;00m i, (user, item, label) \u001b[35min\u001b[39;49;00m \u001b[36menumerate\u001b[39;49;00m(data):\n",
"\n",
" user = user.as_in_context(ctx)\n",
" item = item.as_in_context(ctx)\n",
" label = label.as_in_context(ctx)\n",
"\n",
" predictions = net(user, item).reshape((batch_size, \u001b[34m1\u001b[39;49;00m))\n",
" acc.update(preds=[predictions], labels=[label])\n",
"\n",
" \u001b[34mreturn\u001b[39;49;00m acc.get()[\u001b[34m1\u001b[39;49;00m]\n",
"\n",
"\n",
"\u001b[34mdef\u001b[39;49;00m \u001b[32msave\u001b[39;49;00m(model, model_dir):\n",
" net, customer_index, product_index = model\n",
" net.save_params(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/model.params\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir))\n",
" f = \u001b[36mopen\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/MFBlock.params\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir), \u001b[33m\"\u001b[39;49;00m\u001b[33mw\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n",
" json.dump(\n",
" {\n",
" \u001b[33m\"\u001b[39;49;00m\u001b[33mmax_users\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: net.max_users,\n",
" \u001b[33m\"\u001b[39;49;00m\u001b[33mmax_items\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: net.max_items,\n",
" \u001b[33m\"\u001b[39;49;00m\u001b[33mnum_emb\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: net.num_emb,\n",
" \u001b[33m\"\u001b[39;49;00m\u001b[33mdropout_p\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: net.dropout_p,\n",
" },\n",
" f,\n",
" )\n",
" f.close()\n",
" customer_index.to_csv(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/customer_index.csv\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir), index=\u001b[34mFalse\u001b[39;49;00m)\n",
" product_index.to_csv(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/product_index.csv\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir), index=\u001b[34mFalse\u001b[39;49;00m)\n",
"\n",
"\n",
"\u001b[37m######\u001b[39;49;00m\n",
"\u001b[37m# Data\u001b[39;49;00m\n",
"\u001b[37m######\u001b[39;49;00m\n",
"\n",
"\n",
"\u001b[34mdef\u001b[39;49;00m \u001b[32mprepare_train_data\u001b[39;49;00m(training_dir):\n",
" f = os.listdir(training_dir)\n",
" df = pd.read_csv(os.path.join(training_dir, f[\u001b[34m0\u001b[39;49;00m]), delimiter=\u001b[33m\"\u001b[39;49;00m\u001b[33m\\t\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, error_bad_lines=\u001b[34mFalse\u001b[39;49;00m)\n",
" df = df[[\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33mstar_rating\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m]]\n",
" customers = df[\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].value_counts()\n",
" products = df[\u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].value_counts()\n",
"\n",
" \u001b[37m# Filter long-tail\u001b[39;49;00m\n",
" customers = customers[customers >= \u001b[34m5\u001b[39;49;00m]\n",
" products = products[products >= \u001b[34m10\u001b[39;49;00m]\n",
"\n",
" reduced_df = df.merge(pd.DataFrame({\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: customers.index})).merge(\n",
" pd.DataFrame({\u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: products.index})\n",
" )\n",
" customers = reduced_df[\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].value_counts()\n",
" products = reduced_df[\u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].value_counts()\n",
"\n",
" \u001b[37m# Number users and items\u001b[39;49;00m\n",
" customer_index = pd.DataFrame(\n",
" {\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: customers.index, \u001b[33m\"\u001b[39;49;00m\u001b[33muser\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: np.arange(customers.shape[\u001b[34m0\u001b[39;49;00m])}\n",
" )\n",
" product_index = pd.DataFrame(\n",
" {\u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: products.index, \u001b[33m\"\u001b[39;49;00m\u001b[33mitem\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: np.arange(products.shape[\u001b[34m0\u001b[39;49;00m])}\n",
" )\n",
"\n",
" reduced_df = reduced_df.merge(customer_index).merge(product_index)\n",
"\n",
" \u001b[37m# Split train and test\u001b[39;49;00m\n",
" test_df = reduced_df.groupby(\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m).last().reset_index()\n",
"\n",
" train_df = reduced_df.merge(\n",
" test_df[[\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m]],\n",
" on=[\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m],\n",
" how=\u001b[33m\"\u001b[39;49;00m\u001b[33mouter\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m,\n",
" indicator=\u001b[34mTrue\u001b[39;49;00m,\n",
" )\n",
" train_df = train_df[(train_df[\u001b[33m\"\u001b[39;49;00m\u001b[33m_merge\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m] == \u001b[33m\"\u001b[39;49;00m\u001b[33mleft_only\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)]\n",
"\n",
" \u001b[37m# MXNet data iterators\u001b[39;49;00m\n",
" train = gluon.data.ArrayDataset(\n",
" nd.array(train_df[\u001b[33m\"\u001b[39;49;00m\u001b[33muser\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].values, dtype=np.float32),\n",
" nd.array(train_df[\u001b[33m\"\u001b[39;49;00m\u001b[33mitem\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].values, dtype=np.float32),\n",
" nd.array(train_df[\u001b[33m\"\u001b[39;49;00m\u001b[33mstar_rating\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].values, dtype=np.float32),\n",
" )\n",
" test = gluon.data.ArrayDataset(\n",
" nd.array(test_df[\u001b[33m\"\u001b[39;49;00m\u001b[33muser\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].values, dtype=np.float32),\n",
" nd.array(test_df[\u001b[33m\"\u001b[39;49;00m\u001b[33mitem\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].values, dtype=np.float32),\n",
" nd.array(test_df[\u001b[33m\"\u001b[39;49;00m\u001b[33mstar_rating\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m].values, dtype=np.float32),\n",
" )\n",
"\n",
" train_iter = gluon.data.DataLoader(\n",
" train, shuffle=\u001b[34mTrue\u001b[39;49;00m, num_workers=\u001b[34m4\u001b[39;49;00m, batch_size=batch_size, last_batch=\u001b[33m\"\u001b[39;49;00m\u001b[33mrollover\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\n",
" )\n",
" test_iter = gluon.data.DataLoader(\n",
" train, shuffle=\u001b[34mTrue\u001b[39;49;00m, num_workers=\u001b[34m4\u001b[39;49;00m, batch_size=batch_size, last_batch=\u001b[33m\"\u001b[39;49;00m\u001b[33mrollover\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\n",
" )\n",
"\n",
" \u001b[34mreturn\u001b[39;49;00m train_iter, test_iter, customer_index, product_index\n",
"\n",
"\n",
"\u001b[37m#########\u001b[39;49;00m\n",
"\u001b[37m# Hosting\u001b[39;49;00m\n",
"\u001b[37m#########\u001b[39;49;00m\n",
"\n",
"\n",
"\u001b[34mdef\u001b[39;49;00m \u001b[32mmodel_fn\u001b[39;49;00m(model_dir):\n",
" \u001b[33m\"\"\"\u001b[39;49;00m\n",
"\u001b[33m Load the gluon model. Called once when hosting service starts.\u001b[39;49;00m\n",
"\u001b[33m\u001b[39;49;00m\n",
"\u001b[33m :param: model_dir The directory where model files are stored.\u001b[39;49;00m\n",
"\u001b[33m :return: a model (in this case a Gluon network)\u001b[39;49;00m\n",
"\u001b[33m \"\"\"\u001b[39;49;00m\n",
" ctx = mx.cpu()\n",
" f = \u001b[36mopen\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/MFBlock.params\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir), \u001b[33m\"\u001b[39;49;00m\u001b[33mr\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n",
" block_params = json.load(f)\n",
" f.close()\n",
" net = MFBlock(\n",
" max_users=block_params[\u001b[33m\"\u001b[39;49;00m\u001b[33mmax_users\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m],\n",
" max_items=block_params[\u001b[33m\"\u001b[39;49;00m\u001b[33mmax_items\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m],\n",
" num_emb=block_params[\u001b[33m\"\u001b[39;49;00m\u001b[33mnum_emb\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m],\n",
" dropout_p=block_params[\u001b[33m\"\u001b[39;49;00m\u001b[33mdropout_p\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m],\n",
" )\n",
" net.load_params(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/model.params\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir), ctx)\n",
" customer_index = pd.read_csv(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/customer_index.csv\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir))\n",
" product_index = pd.read_csv(\u001b[33m\"\u001b[39;49;00m\u001b[33m{}\u001b[39;49;00m\u001b[33m/product_index.csv\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m.format(model_dir))\n",
" \u001b[34mreturn\u001b[39;49;00m net, customer_index, product_index\n",
"\n",
"\n",
"\u001b[34mdef\u001b[39;49;00m \u001b[32mtransform_fn\u001b[39;49;00m(net, data, input_content_type, output_content_type):\n",
" \u001b[33m\"\"\"\u001b[39;49;00m\n",
"\u001b[33m Transform a request using the Gluon model. Called once per request.\u001b[39;49;00m\n",
"\u001b[33m\u001b[39;49;00m\n",
"\u001b[33m :param net: The Gluon model.\u001b[39;49;00m\n",
"\u001b[33m :param data: The request payload.\u001b[39;49;00m\n",
"\u001b[33m :param input_content_type: The request content type.\u001b[39;49;00m\n",
"\u001b[33m :param output_content_type: The (desired) response content type.\u001b[39;49;00m\n",
"\u001b[33m :return: response payload and content type.\u001b[39;49;00m\n",
"\u001b[33m \"\"\"\u001b[39;49;00m\n",
" ctx = mx.cpu()\n",
" parsed = json.loads(data)\n",
"\n",
" trained_net, customer_index, product_index = net\n",
" users = (\n",
" pd.DataFrame({\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: parsed[\u001b[33m\"\u001b[39;49;00m\u001b[33mcustomer_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m]})\n",
" .merge(customer_index, how=\u001b[33m\"\u001b[39;49;00m\u001b[33mleft\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)[\u001b[33m\"\u001b[39;49;00m\u001b[33muser\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m]\n",
" .values\n",
" )\n",
" items = (\n",
" pd.DataFrame({\u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m: parsed[\u001b[33m\"\u001b[39;49;00m\u001b[33mproduct_id\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m]})\n",
" .merge(product_index, how=\u001b[33m\"\u001b[39;49;00m\u001b[33mleft\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)[\u001b[33m\"\u001b[39;49;00m\u001b[33mitem\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m]\n",
" .values\n",
" )\n",
"\n",
" predictions = trained_net(\n",
" nd.array(users).as_in_context(ctx), nd.array(items).as_in_context(ctx)\n",
" )\n",
" response_body = json.dumps(predictions.asnumpy().tolist())\n",
"\n",
" \u001b[34mreturn\u001b[39;49;00m response_body, output_content_type\n"
]
}
],
"source": [
"!pygmentize recommender.py"
]
},
{
"cell_type": "markdown",
"id": "5a7dd115",
"metadata": {},
"source": [
"### Move Data\n",
"\n",
"작은 데이터 샘플을 탐색할 때는 데이터를 메모리에 보관하는 것이 좋지만, 더 크고 오래 실행되는 프로세스의 경우 SageMaker Training 을 사용하여 백그라운드에서 실행하는 것이 좋습니다. 이를 위해 SageMaker train 에서 선택할 수 있도록 데이터 세트를 S3로 이동해 보겠습니다. 이는 주기적인 재교육, 더 큰 데이터 세트로 확장 또는 프로덕션 워크로드를 더 큰 하드웨어로 이전하는 것과 같은 사용 사례에 적합합니다.\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "0649b393",
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"boto3.client(\"s3\").copy(\n",
" {\n",
" \"Bucket\": \"amazon-reviews-pds\",\n",
" \"Key\": \"tsv/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz\",\n",
" },\n",
" bucket,\n",
" prefix + \"/train/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "75b21839",
"metadata": {},
"source": [
"### Submit\n",
"\n",
"이제 세이지메이커 파이썬 SDK에서 MXNet estimator 를 만들 수 있습니다. 그러기 위해서는 다음 사항을 전달해야 합니다.\n",
"1. SageMaker train 클러스터의 인스턴스 유형 및 수입니다. SageMaker의 MXNet 컨테이너는 분산 GPU 트레이닝을 지원하므로 원하는 경우 이를 여러 ml.p2 또는 ml.p3 인스턴스로 쉽게 설정할 수 있습니다.\n",
" - *참고로, 이 경우 recommender.py 스크립트를 일부 변경해야 합니다. 컨텍스트를 key-value 저장소에 올바르게 설정하고 훈련 데이터를 배포할지 여부와 방법을 결정해야 하기 때문입니다.*\n",
"1.출력 모델 아티팩트에 대한 S3 경로와 S3 입력 및 출력 경로에 대한 액세스 권한이 있는 역할\n",
"1.신경망을 위한 하이퍼파라미터입니다.64 차원 임베딩을 사용하면 권장 사항이 평균과 너무 가깝게 되돌아 갔으므로 로컬 인스턴스 외부에서 훈련 할 때 이것을 몇 배 늘려 보겠습니다.또한 시간이 지남에 따라 정확도가 어떻게 진화하는지 확인하기 위해 시대를 늘릴 것입니다.다른 모든 하이퍼파라미터는 그대로 두겠습니다.\n",
"\n",
"`.fit () `을 사용하면 인스턴스를 스핀업하고, 적절한 패키지와 데이터를 로드하고, `recommender.py`에서 `train` 함수를 실행하고, 모델 아티팩트를 래핑하여 S3에 저장하고, 클러스터를 분해하여 완료하는 SageMaker 교육 작업이 생성됩니다."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4ae16a52",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"train_instance_type has been renamed in sagemaker>=2.\n",
"See: https://sagemaker.readthedocs.io/en/stable/v2.html for details.\n",
"train_instance_count has been renamed in sagemaker>=2.\n",
"See: https://sagemaker.readthedocs.io/en/stable/v2.html for details.\n",
"train_instance_type has been renamed in sagemaker>=2.\n",
"See: https://sagemaker.readthedocs.io/en/stable/v2.html for details.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"2022-01-11 08:49:14 Starting - Starting the training job...ProfilerReport-1641890954: InProgress\n",
"...\n",
"2022-01-11 08:50:00 Starting - Preparing the instances for training...."
]
}
],
"source": [
"m = MXNet(\n",
" \"recommender.py\",\n",
" py_version=\"py3\",\n",
" role=role,\n",
" train_instance_count=1,\n",
" train_instance_type=\"ml.p3.8xlarge\",\n",
" output_path=\"s3://{}/{}/output\".format(bucket, prefix),\n",
" hyperparameters={\n",
" \"num_embeddings\": 512,\n",
" \"opt\": opt,\n",
" \"lr\": lr,\n",
" \"momentum\": momentum,\n",
" \"wd\": wd,\n",
" \"epochs\": 10,\n",
" },\n",
" framework_version=\"1.1\",\n",
")\n",
"\n",
"m.fit({\"train\": \"s3://{}/{}/train/\".format(bucket, prefix)})"
]
},
{
"cell_type": "markdown",
"id": "ab5d92ae",
"metadata": {},
"source": [
"---\n",
"\n",
"## Host\n",
"\n",
"이제 모델을 학습하였으므로 실시간 프로덕션 엔드포인트에 쉽게 배포할 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3074aab8",
"metadata": {},
"outputs": [],
"source": [
"predictor = m.deploy(initial_instance_count=1, instance_type=\"ml.m4.xlarge\")"
]
},
{
"cell_type": "markdown",
"id": "9fe683bb",
"metadata": {},
"source": [
"이제 엔드포인트가 생겼으니 테스트해 보겠습니다.로컬 모델의 상단 및 하단 ASIN에 대한 사용자 #6 등급을 예측합니다.\n",
"\n",
"*이 작업은 별도의 웹 서비스에서 HTTP POST 요청을 전송하여 수행할 수 있지만, 작업을 쉽게 하기 위해 SageMaker Python SDK에서 `.predict () `메서드를 사용하겠습니다.*\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f4be412",
"metadata": {},
"outputs": [],
"source": [
"type(predictor)"
]
},
{
"cell_type": "code",
"execution_count": 57,
"id": "2efef411",
"metadata": {},
"outputs": [],
"source": [
"test = json.dumps(\n",
" {\n",
" \"customer_id\": customer_index[customer_index[\"user\"] == 6][\n",
" \"customer_id\"\n",
" ].values.tolist(),\n",
" \"product_id\": [\"B00KH1O9HW\", \"B00M5KODWO\"],\n",
" }\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 65,
"id": "bfd5db50",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[5.103163719177246, 1.5584197044372559]"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"predictor.predict(json.loads(\n",
" json.dumps(\n",
" {\n",
" \"customer_id\": customer_index[customer_index[\"user\"] == 6][\n",
" \"customer_id\"\n",
" ].values.tolist(),\n",
" \"product_id\": [\"B00KH1O9HW\", \"B00M5KODWO\"],\n",
" }\n",
" ))\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a102d545",
"metadata": {},
"source": [
"*참고로, 일부 예측은 실제로 5보다 크며, 등급이 해당 값으로 제한되는 것을 설명하기 위해 특별한 조치를 취하지 않았기 때문에 예상됩니다.예상 평점으로만 순위를 매길 예정이므로 특정 사용 사례에는 문제가 발생하지 않습니다.*"
]
},
{
"cell_type": "markdown",
"id": "dbeb4022",
"metadata": {},
"source": [
"### Evaluate\n",
"\n",
"먼저 모델이 얼마나 잘 작동하고 있는지 측정하기 위해 naive baseline 을 계산해 보겠습니다. 가장 간단한 estimator 는 모든 사용자 항목 등급이 모든 평점에 대한 평균 평점이라고 가정하는 것입니다.\n",
"\n",
"*각 개별 비디오의 평균을 사용하면 더 나은 결과를 얻을 수 있지만, 이 경우 동일한 결론이 유지되므로 중요하지 않습니다.*"
]
},
{
"cell_type": "code",
"execution_count": 66,
"id": "75f842ac",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Naive MSE: 1.6493581487802726\n"
]
}
],
"source": [
"print(\"Naive MSE:\", np.mean((test_df[\"star_rating\"] - np.mean(train_df[\"star_rating\"])) ** 2))"
]
},
{
"cell_type": "markdown",
"id": "1ac82a9b",
"metadata": {},
"source": [
"이제 테스트 데이터셋에 대한 예측을 계산해 보겠습니다.\n",
"\n",
"*참고로 위의 CloudWatch 출력과 거의 일치하지만 eval_net 함수에서 부분 미니 배치를 건너뛰기 때문에 약간 다를 수 있습니다.*"
]
},
{
"cell_type": "code",
"execution_count": 68,
"id": "e072d251",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"MSE: 1.2696960969112074\n"
]
}
],
"source": [
"test_preds = []\n",
"for array in np.array_split(test_df[[\"customer_id\", \"product_id\"]].values, 40):\n",
" test_preds += predictor.predict(\n",
" json.loads(json.dumps({\"customer_id\": array[:, 0].tolist(), \"product_id\": array[:, 1].tolist()}))\n",
" )\n",
"\n",
"test_preds = np.array(test_preds)\n",
"print(\"MSE:\", np.mean((test_df[\"star_rating\"] - test_preds) ** 2))"
]
},
{
"cell_type": "markdown",
"id": "b3624090",
"metadata": {},
"source": [
"신경망과 임베딩 모델이 훨씬 더 나은 결과를 생성한다는 것을 알 수 있습니다 (평균 제곱 오차의 경우 약 1.27 대 1.65).\n",
"\n",
"추천 시스템의 경우 주관적인 정확성도 중요합니다. 임의의 사용자가 직관적으로 이해되는지 확인하기 위해 몇 가지 추천 아이템을 살펴보겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 69,
"id": "6ba24f0c",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
customer_id
\n",
"
product_id
\n",
"
star_rating
\n",
"
product_title
\n",
"
user
\n",
"
item
\n",
"
\n",
" \n",
" \n",
"
\n",
"
55340
\n",
"
50818682
\n",
"
B00DAHSY58
\n",
"
5
\n",
"
Under The Dome, Season 1
\n",
"
6
\n",
"
5
\n",
"
\n",
"
\n",
"
96053
\n",
"
50818682
\n",
"
B00LI0VA4Q
\n",
"
5
\n",
"
Extant, Season 1
\n",
"
6
\n",
"
6
\n",
"
\n",
"
\n",
"
39816
\n",
"
50818682
\n",
"
B00L86ZKAK
\n",
"
5
\n",
"
Under The Dome, Season 2
\n",
"
6
\n",
"
20
\n",
"
\n",
"
\n",
"
182902
\n",
"
50818682
\n",
"
B006IX92XO
\n",
"
5
\n",
"
Rome Season 1
\n",
"
6
\n",
"
27
\n",
"
\n",
"
\n",
"
83341
\n",
"
50818682
\n",
"
B00821OX98
\n",
"
5
\n",
"
Falling Skies Season 1
\n",
"
6
\n",
"
40
\n",
"
\n",
"
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
\n",
"
\n",
"
1060778
\n",
"
50818682
\n",
"
B00TY68T6I
\n",
"
1
\n",
"
The Returned Season 1
\n",
"
6
\n",
"
11493
\n",
"
\n",
"
\n",
"
938194
\n",
"
50818682
\n",
"
B00KQFOLBI
\n",
"
1
\n",
"
Mirage Men
\n",
"
6
\n",
"
12443
\n",
"
\n",
"
\n",
"
957127
\n",
"
50818682
\n",
"
B001JDQXCG
\n",
"
1
\n",
"
The New World (2005)
\n",
"
6
\n",
"
13451
\n",
"
\n",
"
\n",
"
900998
\n",
"
50818682
\n",
"
B00IAKN6V2
\n",
"
1
\n",
"
Welcome To The Jungle
\n",
"
6
\n",
"
17000
\n",
"
\n",
"
\n",
"
996297
\n",
"
50818682
\n",
"
B00A4OC0LO
\n",
"
1
\n",
"
The Love You Save
\n",
"
6
\n",
"
21007
\n",
"
\n",
" \n",
"
\n",
"
323 rows × 6 columns
\n",
"
"
],
"text/plain": [
" customer_id product_id star_rating product_title user \\\n",
"55340 50818682 B00DAHSY58 5 Under The Dome, Season 1 6 \n",
"96053 50818682 B00LI0VA4Q 5 Extant, Season 1 6 \n",
"39816 50818682 B00L86ZKAK 5 Under The Dome, Season 2 6 \n",
"182902 50818682 B006IX92XO 5 Rome Season 1 6 \n",
"83341 50818682 B00821OX98 5 Falling Skies Season 1 6 \n",
"... ... ... ... ... ... \n",
"1060778 50818682 B00TY68T6I 1 The Returned Season 1 6 \n",
"938194 50818682 B00KQFOLBI 1 Mirage Men 6 \n",
"957127 50818682 B001JDQXCG 1 The New World (2005) 6 \n",
"900998 50818682 B00IAKN6V2 1 Welcome To The Jungle 6 \n",
"996297 50818682 B00A4OC0LO 1 The Love You Save 6 \n",
"\n",
" item \n",
"55340 5 \n",
"96053 6 \n",
"39816 20 \n",
"182902 27 \n",
"83341 40 \n",
"... ... \n",
"1060778 11493 \n",
"938194 12443 \n",
"957127 13451 \n",
"900998 17000 \n",
"996297 21007 \n",
"\n",
"[323 rows x 6 columns]"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reduced_df[reduced_df[\"user\"] == 6].sort_values([\"star_rating\", \"item\"], ascending=[False, True])"
]
},
{
"cell_type": "markdown",
"id": "caefe5e8",
"metadata": {},
"source": [
"보시다시피 #6 사용자는 드라마틱 한 TV 시리즈와 공상 과학을 좋아하는 것 같지만 코미디를 싫어합니다.\n",
"\n",
"이제 카탈로그에 있는 모든 일반 동영상에 대한 사용자 #6 등급을 반복하여 예측하여 어떤 동영상을 추천하고 어떤 동영상을 추천하지 않을지 알아보겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 71,
"id": "5ec30227",
"metadata": {},
"outputs": [],
"source": [
"predictions = []\n",
"for array in np.array_split(product_index[\"product_id\"].values, 40):\n",
" predictions += predictor.predict(json.loads(\n",
" json.dumps(\n",
" {\n",
" \"customer_id\": customer_index[customer_index[\"user\"] == 6][\n",
" \"customer_id\"\n",
" ].values.tolist()\n",
" * array.shape[0],\n",
" \"product_id\": array.tolist(),\n",
" }\n",
" )\n",
" ))\n",
"\n",
"predictions = pd.DataFrame({\"product_id\": product_index[\"product_id\"], \"prediction\": predictions})"
]
},
{
"cell_type": "code",
"execution_count": 72,
"id": "67e08d46",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
product_id
\n",
"
prediction
\n",
"
product_title
\n",
"
\n",
" \n",
" \n",
"
\n",
"
33345
\n",
"
B004N796H4
\n",
"
6.154150
\n",
"
Doc Hollywood
\n",
"
\n",
"
\n",
"
3583
\n",
"
B00Z620OVW
\n",
"
5.988809
\n",
"
Poldark, Season 1
\n",
"
\n",
"
\n",
"
15890
\n",
"
B00EMSTBL0
\n",
"
5.968293
\n",
"
The Incredible Dr. Pol, Season 3
\n",
"
\n",
"
\n",
"
432
\n",
"
B003ITOL8C
\n",
"
5.901760
\n",
"
Doc Martin Season 2
\n",
"
\n",
"
\n",
"
1576
\n",
"
B00366GELO
\n",
"
5.868101
\n",
"
Foyle's War Season 4
\n",
"
\n",
"
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
\n",
"
\n",
"
2806
\n",
"
B00A7YXU0G
\n",
"
1.930092
\n",
"
Savage Island
\n",
"
\n",
"
\n",
"
3952
\n",
"
B00NESTCC2
\n",
"
1.890782
\n",
"
2035 Forbidden Dimensions
\n",
"
\n",
"
\n",
"
4556
\n",
"
B00DKGWLGM
\n",
"
1.885139
\n",
"
Battle Earth
\n",
"
\n",
"
\n",
"
7531
\n",
"
B00M5KODWO
\n",
"
1.558420
\n",
"
The Becoming: 2015
\n",
"
\n",
"
\n",
"
3620
\n",
"
B00M5P22D6
\n",
"
1.536700
\n",
"
The Becoming: 2015
\n",
"
\n",
" \n",
"
\n",
"
38385 rows × 3 columns
\n",
"
"
],
"text/plain": [
" product_id prediction product_title\n",
"33345 B004N796H4 6.154150 Doc Hollywood\n",
"3583 B00Z620OVW 5.988809 Poldark, Season 1\n",
"15890 B00EMSTBL0 5.968293 The Incredible Dr. Pol, Season 3\n",
"432 B003ITOL8C 5.901760 Doc Martin Season 2\n",
"1576 B00366GELO 5.868101 Foyle's War Season 4\n",
"... ... ... ...\n",
"2806 B00A7YXU0G 1.930092 Savage Island\n",
"3952 B00NESTCC2 1.890782 2035 Forbidden Dimensions\n",
"4556 B00DKGWLGM 1.885139 Battle Earth\n",
"7531 B00M5KODWO 1.558420 The Becoming: 2015\n",
"3620 B00M5P22D6 1.536700 The Becoming: 2015\n",
"\n",
"[38385 rows x 3 columns]"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"titles = reduced_df.groupby(\"product_id\")[\"product_title\"].last().reset_index()\n",
"predictions_titles = predictions.merge(titles)\n",
"predictions_titles.sort_values([\"prediction\", \"product_id\"], ascending=[False, True])"
]
},
{
"cell_type": "markdown",
"id": "a04c6215",
"metadata": {},
"source": [
"실제로, 우리의 높은 평가를받은 쇼에는 잘 검토 된 TV 드라마와 공상 과학이 있습니다. 가장 낮은 등급의 쇼에는 코미디가 포함됩니다.\n",
"\n",
"*가중치의 임의 초기화로 인해 후속 실행의 결과가 약간 다를 수 있습니다.*\n",
"\n",
"#7 사용자와의 권장 사항에서 더 이상 완벽한 상관 관계가 없는지 확인하겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 73,
"id": "696541bb",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
""
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"predictions_user7 = []\n",
"for array in np.array_split(product_index[\"product_id\"].values, 40):\n",
" predictions_user7 += predictor.predict(json.loads(\n",
" json.dumps(\n",
" {\n",
" \"customer_id\": customer_index[customer_index[\"user\"] == 7][\n",
" \"customer_id\"\n",
" ].values.tolist()\n",
" * array.shape[0],\n",
" \"product_id\": array.tolist(),\n",
" }\n",
" )\n",
" ))\n",
"plt.scatter(predictions[\"prediction\"], np.array(predictions_user7))\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "c3a2a605",
"metadata": {},
"source": [
"---\n",
"\n",
"## Wrap-up\n",
"\n",
"이 예에서는 고객 평점을 예측하는 딥 러닝 모델을 개발했습니다. 이는 다양한 사용 사례에서 추천 시스템의 기초가 될 수 있습니다. 그러나 개선할 수 있는 방법에는 여러 가지가 있습니다. 예를 들어 우리는 다음과 같은 부분은 거의 수행하지 않았습니다.\n",
"- 하이퍼파라미터 튜닝\n",
"- 과적합에 대한 제어 (early stopping, dropout 등)\n",
"- 목표 변수를 이진화하여 결과를 개선할 수 있는지 테스트\n",
"- 기타 정보 출처 포함 (비디오 장르, 과거 등급, 검토 시간)\n",
"- 사용자 및 아이템 포함에 대한 기준 조정 \n",
"\n",
"모델을 개선하는 것 외에도 다음과 같은 방법으로 엔지니어링을 개선할 수 있습니다.\n",
"- 분산 교육을 위한 컨텍스트 및 키 값 저장 설정\n",
"- GPU를 완전히 활용할 수 있도록 데이터 수집 (예: 데이터 반복기의 num_workers) 을 미세 조정\n",
"- 데이터 세트가 단일 머신 이상으로 확장됨에 따라 전처리가 어떻게 변경되어야 하는지 생각\n",
"\n",
"그 외에도 추천 시스템은 능동적 학습, 강화 학습, 세분화, 앙상블 등의 연구 및 기술의 매우 활발한 영역이며 균형 잡힌 권장 사항을 제공하기 위해 조사해야합니다.\n",
"\n",
"### Clean-up (optional)\n",
"\n",
"endpoint 를 삭제하여 호스팅 비용이 발생하지 않도록 삭제해 보겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 75,
"id": "051ee12d",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"The endpoint attribute has been renamed in sagemaker>=2.\n",
"See: https://sagemaker.readthedocs.io/en/stable/v2.html for details.\n"
]
}
],
"source": [
"sagemaker.Session().delete_endpoint(predictor.endpoint)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee085309",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "conda_amazonei_mxnet_p36",
"language": "python",
"name": "conda_amazonei_mxnet_p36"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}