{ "cells": [ { "cell_type": "markdown", "id": "d79aeaf0-df88-45ad-8514-60071e41e107", "metadata": {}, "source": [ "# SageMaker에서 NCF 모델 배포 및 엔드포인트 생성\n", "\n", "이 노트북은 기본 커널을 conda_python3를 사용 합니다." ] }, { "cell_type": "markdown", "id": "20e909dc-28bf-422e-95b6-49481ba8dfcc", "metadata": {}, "source": [ "## 0. 환경 설정" ] }, { "cell_type": "code", "execution_count": null, "id": "c9f96893-0a50-4eac-8154-029d86a16682", "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import time\n", "import os\n", "import sagemaker\n", "from datetime import datetime" ] }, { "cell_type": "code", "execution_count": null, "id": "9b69dd72-5617-4c48-87bb-8928822cf59f", "metadata": {}, "outputs": [], "source": [ "sagemaker_session = sagemaker.session.Session()\n", "role = sagemaker.get_execution_role()\n", "bucket = sagemaker.session.Session().default_bucket()\n", "\n", "print(\"role: \", role)\n", "print(\"bucket: \", bucket)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "21b672c2", "metadata": {}, "source": [ "### 데이터셋을 S3 로 업로드" ] }, { "cell_type": "code", "execution_count": null, "id": "982692cc", "metadata": {}, "outputs": [], "source": [ "item_meta_bucket='' # replace with the name of your S3 bucket\n", "data_filename = \"dataset/merged_data.csv\"\n", "\n", "response_upload = boto3.Session().resource('s3').Bucket(item_meta_bucket).Object(data_filename).upload_file(data_filename)\n", "\n", "s3_its_filename = \"s3://{}/{}\".format(item_meta_bucket, data_filename)\n", "\n", "print(\"s3_merged_data_filename: \\n\", s3_its_filename)" ] }, { "cell_type": "markdown", "id": "96437587-6e27-4206-a484-d114194afe26", "metadata": {}, "source": [ "## 1. 모델(model.tar.gz)을 S3에 업로드\n" ] }, { "cell_type": "code", "execution_count": null, "id": "a56a3b80-6085-4296-b719-c33d93fa483e", "metadata": {}, "outputs": [], "source": [ "model_prefix = 'ncf/model'\n", "model_filename = \"./model/model.tar.gz\"\n", "\n", "model_s3_path = sagemaker_session.upload_data(model_filename, bucket, model_prefix)\n", "print(\"model: \\n\", model_s3_path)" ] }, { "cell_type": "markdown", "id": "bd8e9390-49f9-4070-bf1c-9788fe4a47f9", "metadata": {}, "source": [ "## 2. SageMaker에서 모델 생성\n", "Model Registry에 등록" ] }, { "cell_type": "code", "execution_count": null, "id": "612a086f-cef0-4f8b-9082-ce4166b89a86", "metadata": {}, "outputs": [], "source": [ "# Define model name\n", "model_name = 'ncf-tf-model'" ] }, { "cell_type": "code", "execution_count": null, "id": "933ab37b-f753-4866-aecc-69a7752936fe", "metadata": {}, "outputs": [], "source": [ "# Get the current session's region\n", "session = boto3.Session()\n", "region = session.region_name\n", "\n", "print(\"Current region:\", region)" ] }, { "cell_type": "markdown", "id": "5a6bd2ae-0011-4741-a84d-ddbdc8449071", "metadata": {}, "source": [ "
\n", "등록된 모델(model.tar.gz)은 TensorFlow 2.6 환경에서 작성되었습니다.
\n", "따라서 컨테이너 이미지는 'tensorflow-inference:2.6-cpu'를 사용합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "96c51467-10d8-41be-9db2-03b700d64014", "metadata": {}, "outputs": [], "source": [ "sagemaker_client = boto3.client('sagemaker', region_name=region)\n", "\n", "# Create a model in SageMaker\n", "create_model_response = sagemaker_client.create_model(\n", " ModelName=model_name,\n", " ExecutionRoleArn=role,\n", " PrimaryContainer={\n", " 'Image': '763104351884.dkr.ecr.{}.amazonaws.com/tensorflow-inference:2.6-cpu'.format(region),\n", " 'ModelDataUrl': 's3://{}/{}'.format(bucket, model_prefix + '/model.tar.gz'),\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "b1208a58-c773-4bc3-bf66-20c7ab6b45ee", "metadata": {}, "source": [ "## 3. SageMaker Endpoint 생성" ] }, { "cell_type": "code", "execution_count": null, "id": "f43bc2b5-ac7a-4dda-b74e-b75aed005693", "metadata": {}, "outputs": [], "source": [ "# Define the endpoint config and endpoint names\n", "endpoint_config_name = 'ncf-model-endpoint-config'\n", "endpoint_name = 'ncf-model-endpoint'" ] }, { "cell_type": "code", "execution_count": null, "id": "91999e06-ec82-4386-9c5d-635d7be48e6d", "metadata": {}, "outputs": [], "source": [ "# Create endpoint configuration\n", "create_endpoint_config_response = sagemaker_client.create_endpoint_config(\n", " EndpointConfigName=endpoint_config_name,\n", " ProductionVariants=[\n", " {\n", " 'VariantName': 'AllTraffic',\n", " 'ModelName': model_name,\n", " 'InitialInstanceCount': 1,\n", " 'InstanceType': 'ml.m5.xlarge', # Choose the desired instance type\n", " 'InitialVariantWeight': 1\n", " }\n", " ]\n", ")\n", "\n", "print(\"Endpoint config created:\", create_endpoint_config_response['EndpointConfigArn'])" ] }, { "cell_type": "code", "execution_count": null, "id": "83339d2d-fadb-47af-ae83-1ddb803fd8ab", "metadata": {}, "outputs": [], "source": [ "# Create endpoint\n", "create_endpoint_response = sagemaker_client.create_endpoint(\n", " EndpointName=endpoint_name,\n", " EndpointConfigName=endpoint_config_name\n", ")\n", "\n", "print(\"Creating endpoint...\")" ] }, { "cell_type": "markdown", "id": "5847541d-89cf-4f96-8387-1689c6bbb04e", "metadata": {}, "source": [ "#### Endpoint 생성 완료까지 대기" ] }, { "cell_type": "code", "execution_count": null, "id": "cbddae5e-18a3-4075-9a9e-43bfbd84a8ea", "metadata": {}, "outputs": [], "source": [ "# Wait for the endpoint to be in service\n", "endpoint_status = 'Creating'\n", "while endpoint_status == 'Creating':\n", " time.sleep(30)\n", " endpoint_status = sagemaker_client.describe_endpoint(EndpointName=endpoint_name)['EndpointStatus']\n", " print(\"Endpoint status:\", endpoint_status)\n", "\n", "print(\"Endpoint created:\", create_endpoint_response['EndpointArn'])" ] }, { "cell_type": "markdown", "id": "0134b33c-5773-47aa-98f3-ae32970415e0", "metadata": {}, "source": [ "# 아래의 SageMaker Endpoint ARN으로 추론을 합니다.\n", "Lambda Function에서 NCF 모델 추론은 아래 SageMaker Endpoint ARN을 사용합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "2ee05597-373b-44ae-a1c1-84c2717becb7", "metadata": {}, "outputs": [], "source": [ "print(\"SageMaker Endpoint ARN : \", create_endpoint_response['EndpointArn'])" ] }, { "cell_type": "markdown", "id": "50015b8d-50ae-47b8-aef9-021ca1be9eca", "metadata": {}, "source": [ "#### 변수 저장\n", "clean-up을 위해 필요한 변수 저장" ] }, { "cell_type": "code", "execution_count": null, "id": "3cbf1830-e87a-4762-80e4-cad7e92f83d6", "metadata": {}, "outputs": [], "source": [ "%store endpoint_config_name\n", "%store endpoint_name\n", "%store model_name" ] }, { "cell_type": "code", "execution_count": null, "id": "910b921f-bb83-4ab4-aaeb-da59f6d61b9a", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "d50410f1-b712-4094-ae49-9829f58b2bc4", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "331f3da3-d36c-4b72-adbe-a35b61ecd82f", "metadata": {}, "source": [ "## (OPTION) Inference Test\n", "아래의 코드에서 user_id 에 1, 2, 100 등 값을 넣어서 10개의 추천 아이템 (item_id)가 잘 출력되는지 확인" ] }, { "cell_type": "markdown", "id": "35dc60d0-8b5c-4397-99a1-71b3bb22df31", "metadata": {}, "source": [ "### a. 입력한 user_id 에 대한 ITEM_ID (10개) 만 출력" ] }, { "cell_type": "code", "execution_count": null, "id": "d94938fc-66c2-495a-b86e-c5abd65f3d76", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import json\n", "import sagemaker\n", "\n", "# Load dataset and create user_to_index and item_to_index dictionaries\n", "data = pd.read_csv('./dataset/merged_data.csv') \n", "user_ids = data['user_id'].unique()\n", "item_ids = data.groupby('item_id').size().sort_values(ascending=False).index.to_numpy()\n", "\n", "user_to_index = {user_id: index for index, user_id in enumerate(user_ids)}\n", "item_to_index = {item_id: index for index, item_id in enumerate(item_ids)}\n", "\n", "# 추론할 USER_ID 입력\n", "user_id = 3\n", "\n", "user_idx = user_to_index[user_id]\n", "item_idx_list = np.array([item_to_index[item_id] for item_id in item_ids])\n", "user_input = np.full(len(item_ids), user_idx).reshape(-1, 1)\n", "item_input = item_idx_list.reshape(-1, 1)\n", "\n", "\n", "# Define SageMaker client\n", "sagemaker_client = boto3.client('sagemaker-runtime')\n", "\n", "# Perform inference using invoke_endpoint()\n", "response = sagemaker_client.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType='application/json',\n", " Body=json.dumps(\n", " {\"user_input\": user_input.tolist(),\n", " \"item_input\": item_input.tolist()}\n", " )\n", ")\n", "\n", "# Parse response\n", "predictions = json.loads(response['Body'].read().decode('utf-8'))\n", "\n", "# Convert predictions to a 1-dimensional array\n", "predictions_array = np.array(predictions['predictions']).reshape(-1)\n", "\n", "# Get the indices of the top 10 items\n", "top_10_indices = np.argsort(predictions_array)[-10:][::-1]\n", "\n", "# Get the item_ids for the top 10 items\n", "top_10_item_ids = [item_ids[idx] for idx in top_10_indices]\n", "\n", "print(\"Top 10 item IDs for user ID\", user_id, \":\\n\", top_10_item_ids)" ] }, { "cell_type": "markdown", "id": "f6a82a48-019e-4a86-a019-76d75f993502", "metadata": {}, "source": [ "### b. 입력한 user_id 에 대한 ITEM_ID 및 상세 정보(10개)를 함께 출력" ] }, { "cell_type": "code", "execution_count": null, "id": "842b115a-0b2e-4b22-91a1-d685495e14c6", "metadata": {}, "outputs": [], "source": [ "def get_item_list_details(items_df, item_id_list):\n", " '''\n", " 해당 ITEM_ID 의 부가 정보를 제공\n", " '''\n", " df = pd.DataFrame(data={'ITEM_ID':item_id_list})\n", " rec_item_df = df.merge(items_df)\n", " return rec_item_df" ] }, { "cell_type": "code", "execution_count": null, "id": "14b228b4-3a0b-48b8-9441-bda171b65ad1", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import json\n", "import sagemaker\n", "\n", "# Load dataset and create user_to_index and item_to_index dictionaries\n", "data = pd.read_csv('./dataset/merged_data.csv') \n", "user_ids = data['user_id'].unique()\n", "item_ids = data.groupby('item_id').size().sort_values(ascending=False).index.to_numpy()\n", "\n", "user_to_index = {user_id: index for index, user_id in enumerate(user_ids)}\n", "item_to_index = {item_id: index for index, item_id in enumerate(item_ids)}\n", "\n", "# 추론할 USER_ID 입력\n", "user_id = 3\n", "\n", "user_idx = user_to_index[user_id]\n", "item_idx_list = np.array([item_to_index[item_id] for item_id in item_ids])\n", "user_input = np.full(len(item_ids), user_idx).reshape(-1, 1)\n", "item_input = item_idx_list.reshape(-1, 1)\n", "\n", "\n", "# Define SageMaker client\n", "sagemaker_client = boto3.client('sagemaker-runtime')\n", "\n", "# Perform inference using invoke_endpoint()\n", "response = sagemaker_client.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType='application/json',\n", " Body=json.dumps(\n", " {\"user_input\": user_input.tolist(),\n", " \"item_input\": item_input.tolist()}\n", " )\n", ")\n", "\n", "# Parse response\n", "predictions = json.loads(response['Body'].read().decode('utf-8'))\n", "\n", "# Convert predictions to a 1-dimensional array\n", "predictions_array = np.array(predictions['predictions']).reshape(-1)\n", "\n", "# Get the indices of the top 10 items\n", "top_10_indices = np.argsort(predictions_array)[-10:][::-1]\n", "\n", "# Get the item_ids for the top 10 items\n", "top_10_item_ids = [item_ids[idx] for idx in top_10_indices]\n", "\n", "print(\"Top 10 item IDs for user ID\", user_id, \":\\n\")\n", "\n", "## join with 'training_item.csv'\n", "items_df = pd.read_csv(\"./dataset/training_item.csv\")\n", "get_item_list_details(items_df, top_10_item_ids)" ] }, { "cell_type": "code", "execution_count": null, "id": "f6dae47a-c2bd-4a98-af08-33ddae759e26", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_python3" }, "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.10.6" } }, "nbformat": 4, "nbformat_minor": 5 }