{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **[Recbole Pipeline]**  \n## **📝 Reference Notebook**  \n1) https://www.kaggle.com/code/shionhonda/h-m-which-algorithm-is-the-best-ask-recbole  \n2) https://www.kaggle.com/code/astrung/recbole-using-all-items-for-prediction  \n3) https://www.kaggle.com/code/astrung/sequential-model-fixed-missing-last-item  \n\n## **🚗 Goal of Notebook**  \n1) 제공되는 데이터에 대한 분석을 통해 솔루션에 활용할 모델 아키텍처를 기획하고,  \n2) 추천시스템 모델 라이브러리인 **'Recbole'**을 Config Setting만 필요에 따라 바꿔서 빠르게 테스트 하기 위해 제작하고   \n추후 전체 파이프라인으로 확장시키는 것이 최종 목표  \n\n## **📋 Contexts of Notebook**  \n#### **📊🔍 Step 1. Data Analysis for Model Architecture**  \n#### **💻🗄️ Step 2. Make Pipeline for Recbole Library**  \n#### **💻🏃‍ Step 3. Let's Run Model for Train/Validation**  ","metadata":{}},{"cell_type":"code","source":"!pip install recbole\n!pip install flask-ngrok\n!pip install pyngrok\n!pip install wtforms","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-12-13T17:02:02.726636Z","iopub.execute_input":"2022-12-13T17:02:02.727391Z","iopub.status.idle":"2022-12-13T17:02:50.136814Z","shell.execute_reply.started":"2022-12-13T17:02:02.727298Z","shell.execute_reply":"2022-12-13T17:02:50.135614Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# !nvidia-smi\n# !head /proc/cpuinfo\n# !head /proc/meminfo","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:02:50.139519Z","iopub.execute_input":"2022-12-13T17:02:50.140235Z","iopub.status.idle":"2022-12-13T17:02:50.147160Z","shell.execute_reply.started":"2022-12-13T17:02:50.140196Z","shell.execute_reply":"2022-12-13T17:02:50.146093Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport wandb\nimport sqlite3\nimport sys, os, gc, random, time, datetime, pickle\nimport torch\n\nfrom datetime import datetime\nfrom tqdm.notebook import tqdm\nfrom kaggle_secrets import UserSecretsClient\nfrom recbole.config import Config\nfrom recbole.data import create_dataset, data_preparation\nfrom recbole.quick_start import run_recbole, load_data_and_model\nfrom recbole.data.interaction import Interaction\nfrom recbole.utils.case_study import full_sort_topk\nfrom recbole.trainer import Trainer\nfrom recbole.utils import init_seed, init_logger\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:02:50.148956Z","iopub.execute_input":"2022-12-13T17:02:50.149363Z","iopub.status.idle":"2022-12-13T17:02:53.922440Z","shell.execute_reply.started":"2022-12-13T17:02:50.149328Z","shell.execute_reply":"2022-12-13T17:02:53.921420Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Wandb Login\nuser_secrets = UserSecretsClient()\nsecret_value_0 = user_secrets.get_secret(\"wandb\")\n\n!wandb login $secret_value_0 # WandB Login","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:02:53.924973Z","iopub.execute_input":"2022-12-13T17:02:53.925693Z","iopub.status.idle":"2022-12-13T17:02:56.414243Z","shell.execute_reply.started":"2022-12-13T17:02:53.925653Z","shell.execute_reply":"2022-12-13T17:02:56.413044Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🏃‍♂️ **Extra Step**  \n\n## (1) 💪 **Motivation**  \n현재 H&M 대회에서 정상적으로 돌아간 코드를 그대로 경로, 데이터, 변수명을 수정하고 돌리는데 계속,  \n\n\"Some feat is empty, please chㅁeck the filtering settings\"가 발생하는 상황  \n3일간 삽질을 지속하고, API Docs도 읽어보고, 구글링도 해봣지만 전혀 송용히 없었다.  \n그래서 최후의 보루를 사용하기ㄹ 했다.  이거 마저 안된다면 나는... 어쩌지  \n\n## (2) 🎯 **Strategy**  \n맨 처음 Recbole API를 알게 된 그 캐글 아저씨의 노트북 방식과 여기 노트북 방식을 앙상블 해보려고 한다. \n이거 무슨 말이냐면 dataset config & .inter, .user, .item 세 가지 데이터 세트 파일을 구성하는 방법은 예전 캐글 아저씨껄로,  \n그 이후는 현재 노트북 그대로 사용하겠다는 소리","metadata":{}},{"cell_type":"code","source":"labels = pd.read_csv('/kaggle/input/naver-webtoon-recsys-interaction-meta-data/naver_webtoon/webtoon_interaction_train.csv') # interaction DataFrame은 꼭 오름차순 정렬이 필요하다\narticles = pd.read_csv('/kaggle/input/naver-webtoon-recsys-interaction-meta-data/naver_webtoon/webtoon_articles.csv')\ncustomers = pd.read_csv('/kaggle/input/naver-webtoon-recsys-interaction-meta-data/naver_webtoon/webtoon_customers.csv')","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:02:56.416113Z","iopub.execute_input":"2022-12-13T17:02:56.416864Z","iopub.status.idle":"2022-12-13T17:02:57.866426Z","shell.execute_reply.started":"2022-12-13T17:02:56.416821Z","shell.execute_reply":"2022-12-13T17:02:57.865447Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Interaction DataFrame Preprocess Complete\nlabels['posted_time'] = pd.to_datetime(labels['posted_time'], format=\"%Y-%m-%d\")\nlabels.sort_values(by=labels.columns[0], ascending=True, inplace=True)\nlabels = labels.reset_index(drop=True)\nlabels['timestamp'] = labels.posted_time.values.astype(np.int64) // 10 ** 9\n\nlabels","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:02:57.867705Z","iopub.execute_input":"2022-12-13T17:02:57.868643Z","iopub.status.idle":"2022-12-13T17:02:58.325473Z","shell.execute_reply.started":"2022-12-13T17:02:57.868607Z","shell.execute_reply":"2022-12-13T17:02:58.324488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Item DataFrame Preprocess => 할게 없네\n# articles[articles['item_name'] == '참교육'].item_id","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:03:41.275082Z","iopub.execute_input":"2022-12-13T17:03:41.275460Z","iopub.status.idle":"2022-12-13T17:03:41.284121Z","shell.execute_reply.started":"2022-12-13T17:03:41.275427Z","shell.execute_reply":"2022-12-13T17:03:41.282973Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # User DataFrame Preprocess => 얘도 딱히 할게??\n# customers","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:04:05.457936Z","iopub.execute_input":"2022-12-13T17:04:05.458345Z","iopub.status.idle":"2022-12-13T17:04:05.470112Z","shell.execute_reply.started":"2022-12-13T17:04:05.458312Z","shell.execute_reply":"2022-12-13T17:04:05.469164Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Convert Each DataFrame to Recbole API Column name style\nlabels_temp = labels[['user_id', 'item_id', 'timestamp']].rename(columns={'user_id': 'user_id:token',\n                                                                          'item_id': 'item_id:token',\n                                                                          'timestamp': 'timestamp:float'})\nuser_temp = customers[['user_id']].rename(columns={'user_id': 'user_id:token'})\nitem_temp = articles[['item_id', 'story_author', 'image_author', 'genre']].rename(columns={'item_id': 'item_id:token',\n                                                                                           'story_author': 'story_author:token_seq',\n                                                                                           'image_author': 'image_author:token_seq',\n                                                                                           'genre': 'genre:token'})\n\nlabels_temp, user_temp, item_temp","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:04:05.692129Z","iopub.execute_input":"2022-12-13T17:04:05.693137Z","iopub.status.idle":"2022-12-13T17:04:05.807468Z","shell.execute_reply.started":"2022-12-13T17:04:05.693099Z","shell.execute_reply":"2022-12-13T17:04:05.806442Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!mkdir ./naver_webtoon\nlabels_temp.to_csv('/kaggle/working/naver_webtoon/naver_webtoon.inter',\n              index=False, \n              sep='\\t')\nuser_temp.to_csv('/kaggle/working/naver_webtoon/naver_webtoon.user',\n              index=False, \n              sep='\\t')\nitem_temp.to_csv('/kaggle/working/naver_webtoon/naver_webtoon.item',\n              index=False, \n              sep='\\t')","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:04:11.612681Z","iopub.execute_input":"2022-12-13T17:04:11.613053Z","iopub.status.idle":"2022-12-13T17:04:15.121032Z","shell.execute_reply.started":"2022-12-13T17:04:11.613022Z","shell.execute_reply":"2022-12-13T17:04:15.119828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **📊🔍 Step 1. Data Analysis for Model Architecture**","metadata":{}},{"cell_type":"code","source":"# # Step 1.1 Data Path Setting & Check DataFrame\n# %time \n# data_path = '../input/h-and-m-personalized-fashion-recommendations/'\n# labels = pd.read_csv(data_path + 'transactions_train.csv', \n#                      dtype={'article_id' : 'str'}) # Recbole은 Feature의 Type Casting이 매우 중요해서 dtype 정의가 필요함\n# customer = pd.read_csv(data_path + 'customers.csv') \n# articles = pd.read_csv(data_path + 'articles.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Step 1.1 Check Sample Submission DataFrame\n# submission_df = pd.read_csv(data_path + 'sample_submission.csv')\n# submission_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Step 1.2 Check DataFrame\n# labels['t_dat'] = pd.to_datetime(labels['t_dat'], format=\"%Y-%m-%d\")\n# labels['timestamp'] = labels.t_dat.values.astype(np.int64) // 10 ** 9\n# labels","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# customer.head(10)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# articles.head(10)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n# # New Target Customer\n# train_period = labels[labels.index > 30223078]\n# new_target_uuser = train_period.customer_id.unique()\n# new_df = labels[labels.index >= 20808224]\n# new_df = new_df.query('customer_id in @new_target_uuser')\n# new_target_inter = new_df.customer_id.value_counts().to_list()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Make list for Target User\n# find_k = [sequence for sequence in new_target_inter if sequence >= 5]\n# statistic_df = new_df.groupby('customer_id').count()\n# five_length_uuser = statistic_df[statistic_df['article_id'] >= 5].index.to_list() \n# len(five_length_uuser), len(find_k)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"유저 프로파일 정보를 담고 있는 데이터 프레임과 제출용 데이터프레임의 행 개수가 동일합니다....  \n즉, H&M에서 제공하는 모든 유저에 대한 결과 추론이 필요합니다  \n그렇다면 위에 제시된 유저들이 H&M과 얼마나 많은 Interaction_Log를 남겼는지 확인해봅시다.","metadata":{}},{"cell_type":"code","source":"# # Step 1.2 Compared Transaction DataFrame & Customer DataFrame\n# # There are 1362281 Customer in 31788324 Transaction_Log During Almost 2 years\n# full_user_id = labels['customer_id'].value_counts() # Find Unique Customer ID in Transaction DataFrame\n# full_user_id","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"제공된 유저 ID 1371980개 중에서 1362281개의 ID가 2년 동안 회사와 Interaction 기록을 남겼습니다.  \n사실 제공된 모든 Transaction Data를 Computing Resource의 한계로 전부 활용하는 것은 불가능합니다.  \n따라서 우리는 Test Period에 맞춰 Train/Validation Dataset을 다음과 같이 구성할 예정입니다.  \n한편 Interaction이란 구매, 좋아요 & 싫어요 표현, 상품 정보 클릭 등 제품에 대한 유저 개인의 생각이나 감정을 남기는 행위를 의미합니다.  \n우리 데이터에서는 제품을 구매하는 행위를 Interaction이라고 표현합니다. \n\n### **[Definition of Dataset]**  \n**1) Train Dataset: 5 Weeks from Last Day of Validation Period**    \n**2) Validation Dataset: 1 Weeks from Last Day of Transaction Data's Date (2020.09.22)**  \n**3) Test Period: 2020.09.23 ~ 2020.09.29**  \n\n따라서 우리는 전체 데이터 수집 기간(약 2년)동안 몇명의 유저 ID가 Log(구매)를 남겼는지 보다는 설정한 Train/Validation 기간에 몇 명의 유저가 기록을 남겼는지 파악하는 것이 중요합니다. 아래 코드에서 확인해보겠습니다.","metadata":{}},{"cell_type":"code","source":"# # Step 1.3 Check Unique user_id Interaction Log in Train/Validation Period\n# # Train_Period => 20.08.12 ~ 20.09.15 \n# # 274594개의 유저 ID가 1324934 건의 Interaction Log를 남김\n# # Test_Period => 20.09.23 ~ 20.09.29\n# # Train Period를 최대한 가능하면 많이 잡는게 좋을 것 같음\n# # Inference Time에는 Validation까지 넣고 모델에 넣고 돌리면 되겠다. \n# train_user_id = labels[labels['t_dat'] > '2020-08-11']\n# train_user_id = train_user_id[train_user_id['t_dat'] < '2020-09-16'].customer_id\n# print(f'{len(train_user_id.value_counts())}개의 유저 ID가 {len(train_user_id)}개의 Interaction Log 기록')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Step 1.3 Check Unique user_id Interaction Log in Train/Validation Period\n# unique_train_user = train_user_id.value_counts()\n\n# sns.set_style(style='dark')\n# plt.figure(figsize=(15,10))\n# plt.hist(unique_train_user, bins=50, color='green', log=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Validation_Period => 20.09.16 ~ 20.09.22\n# # 68984개의 유저 ID가  240311 건의 Interaction Log를 남김\n# validation_user_id = labels[labels['t_dat'] > '2020-09-15'].customer_id\n# print(f'{len(validation_user_id.value_counts())}개의 유저 ID가 {len(validation_user_id)}개의 Interaction Log 기록')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# unique_validation_user = list(validation_user_id.value_counts())\n# sns.set_style(style='dark')\n# plt.figure(figsize=(15,10))\n# plt.hist(unique_validation_user, bins=50, color='green', log=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## **[Insight]**  \n다른 캐글러들의 EDA Notebook을 참고해보면 애초에 기록된 Interaction 대비 개별 유저 ID의 개수가 적을뿐더러,  \n소수의 헤비 유저들에게 Interaction이 편중되어 있음을 알 수 있습니다. 실제로 위에 Interaction과 user_id 개수 비교해본 것만 봐도 알 수 있죠, Cold Start Problem이 필연적으로 발생하기 때문에 Hybrid Model을 사용해야 합니다.  \nSequential RecSys 역시 구매 기록이란게 남아 있어야 사용할 수 있기 때문에 이를 채울 방법을 꼭 찾아야 할 것 같습니다.  \n**어떤 수치를 기준으로 잡아야 할지 아직 전혀 감이 오지 않습니다만, 추후에 기준점을 하나 잡아서 기준보다 낮은 아이들은 Cold Start 문제를 해결할 수 있는 방식으로 추론을 진행해서 결과들을 이어붙이는 방식으로 모델링을 해야할 것 같습니다.**    \n**다시 말해, 유저 ID의 Interaction Log 횟수에 따라 Segmentation 하여 Targeting Group마다 다른 추천 알고리즘을 적용하고 결과를 Ensemble하는 방식이 필요한 것 같습니다.**  \n이제야 왜 다른 캐글러들이 무조건 Hybrid를 추천하는지 알 것 같네요.... 다행히도 Recbole에 Sequential model 뿐만 아니라  \n다양한(CF, Context-Aware, Knowledge-Based 등)모델을 API 방식으로 편하게 사용할수 있도록 제공하고 있기 때문에  \n좀 더 꼼꼼한 Interaction 분석을 통해 Segmentation 기준을 명확히 잡고 기준이 맞는지 테스트할 파이프라인을  \n구축하면 충분히 좋은 성능을 낼 수 있을 것 같습니다.","metadata":{}},{"cell_type":"markdown","source":"## **[Flow of RecSys]**   \n한편, 왜 Transaction만을 분석했는지 궁금하실 수도 있을 것 같아 설명드립니다.  \n저번에 카톡으로 말씀드렸듯, 일반적으로 유저와의 Interaction 정보를 활용하는(CF)가 Contents-Based(CB)보다는 성능이 월등히 좋다고 알려져서 입니다. 실제로 이번 대회의 Public Code를 살펴보셔도 알 수 있습니다. 리서치 결과(저도 정확히 아는 것은 아니지만 ㅠㅠㅠ) 최근 추천 시스템 분야에서 흐름이 이미 성능이 압도적으로 좋다는 Interaction 기반의 모델(CF)를 Basic Idea로 이것의 단점들을 해결하는 시도들로 흘러가고 있다는 것을 알 수 있었습니다. 저 또한 최근 트랜드를 기저로 솔루션을 만들려고 하다보니 위와 같은 흐름이 나온 것 같습니다.  \n제가 리서치 했던 것을 바탕으로 최근 전체 흐름을 정리하자면,  \n\n**Step 1) Contents-Based Filtering**    \n=> 단독으로 사용하기에 성능이 너무 구림  \n\n**Step 2) Collaborative Filtering (User-Item Interaction Based)**    \n=> 단독으로 사용해도 괜찮으나 Cold Start Problem & 실제 인간의 구매 행위를 정확히 반영하지 못하는 단점이 존재  \n\n**Step 3) Hybrid Model(CB & CF)**  \n=> Cold Start 문제는 해결할 수 있지만 모델 구현의 복잡도 상승 & 실제 인간의 구매 행위 반영 못한다는 단점은 여전  \n\n**Step 4) Vanilla DL Model (DeepFM, WideFM 등)**  \n=> 여전히 인간의 구매 행위를 정확히 반영하지 못함  \n=> 구매의 시퀀스 정보와 반영 불가  \n**(사실 여기 부분은 제대로 공부 못했습니다 ㅎㅎ)**    \n\n**------------------------------------------------여기부터 Sequential Model--------------------------------------------------**\n\n**Step 5) LSTM & GRU Based Model (LSTM4Rec, GRU4Rec)**  \n=> 시퀀스 정보를 반영하면서 어느정도 인간의 구매 행위를 반영할 수 있게 되었음  \n=> 하지만 LSTM & GRU 모델의 고질적인 \"장기 의존성 문제\"를 해결할 수 없어 추천 성능에는 한계가 존재  \n=> 장기 의존성 문제는 시퀀스 정보가 길어질수록, 모델이 무언가 추론할 때 먼 과거의 정보는 비교적 최근 정보보다 덜 반영하여 결과를 도출하게 되는 문제를 의미합니당  \n\n**Step 6) Transformer(Self-Attention) Based Model (SASRec, Bert4Rec 등)**  \n=> Self-Attention의 등장으로 시퀀스 정보를 반영하면서 \"장기 의존성 문제\"를 해결할 수 있게 되었음  \n=> 덕분에 단순 과거 정보를 활용하는 것을 넘어 시퀀스의 상대적인 중요도를 모두 반영한 모델링 가능  \n=> 하지만 실제 구매는 단순히 과거 정보에만 의존하여 구매하지 않는다는 점을 지적  \n\n**Step 7) Sequential & Context-Aware Based Model (FDSA, CL4Rec 등)**  \n=> 시퀀스를 반영하면서 아이템의 특성 정보까지 반영하려는 노력  \n=> 실제 인간의 구매행위를 그나마 가장 근사 시킨 모델 종류들이라고 평가  \n\n구글링 하다보니 CL4Rec, FDSA가 나오면서 GraphSage, PinSage 얘기가 나오던데 아직 거기까지는 커버하지 못했습니다......","metadata":{}},{"cell_type":"markdown","source":"# **💻🗄️ Step 2. Make Pipeline for Recbole Library**    ","metadata":{}},{"cell_type":"code","source":"# # Step 2.1 Base Dataset Class for Definition of Data Config File for Recbole\n# class BaseDataset(object):\n#     def __init__(self, input_path, output_path):\n#         super(BaseDataset, self).__init__()\n#         self.dataset_name = '' \n#         self.input_path = input_path \n#         self.output_path = output_path \n#         self.check_output_path() \n\n#         # input file\n#         self.inter_file = os.path.join(self.input_path, 'inters.dat')\n#         self.item_file = os.path.join(self.input_path, 'items.dat')\n#         self.user_file = os.path.join(self.input_path, 'users.dat')\n#         self.sep = '\\t' # 구분점\n\n#         # output file\n#         self.output_inter_file, self.output_item_file, self.output_user_file = self.get_output_files()\n\n#         # selected feature fields\n#         self.inter_fields = {}\n#         self.item_fields = {}\n#         self.user_fields = {}\n\n#     def check_output_path(self):\n#         if not os.path.isdir(self.output_path):\n#             os.makedirs(self.output_path)\n\n#     def get_output_files(self):\n#         output_inter_file = os.path.join(self.output_path, self.dataset_name + '.inter')\n#         output_item_file = os.path.join(self.output_path, self.dataset_name + '.item')\n#         output_user_file = os.path.join(self.output_path, self.dataset_name + '.user')\n#         return output_inter_file, output_item_file, output_user_file\n\n#     def load_inter_data(self) -> pd.DataFrame():\n#         raise NotImplementedError\n\n#     def load_item_data(self) -> pd.DataFrame():\n#         raise NotImplementedError\n\n#     def load_user_data(self) -> pd.DataFrame():\n#         raise NotImplementedError\n\n#     def convert_inter(self):\n#         try:\n#             input_inter_data = self.load_inter_data()\n#             self.convert(input_inter_data, self.inter_fields, self.output_inter_file)\n#         except NotImplementedError:\n#             print('This dataset can\\'t be converted to inter file\\n')\n\n#     def convert_item(self):\n#         try:\n#             input_item_data = self.load_item_data()\n#             self.convert(input_item_data, self.item_fields, self.output_item_file)\n#         except NotImplementedError:\n#             print('This dataset can\\'t be converted to item file\\n')\n\n#     def convert_user(self):\n#         try:\n#             input_user_data = self.load_user_data()\n#             self.convert(input_user_data, self.user_fields, self.output_user_file)\n#         except NotImplementedError:\n#             print('This dataset can\\'t be converted to user file\\n')\n\n#     @staticmethod\n#     def convert(input_data, selected_fields, output_file):\n#         output_data = pd.DataFrame()\n#         for column in selected_fields:\n#             output_data[column] = input_data.iloc[:, column]\n#         with open(output_file, 'w') as fp:\n#             fp.write('\\t'.join([selected_fields[column] for column in output_data.columns]) + '\\n')\n#             for i in tqdm(range(output_data.shape[0])):\n#                 fp.write('\\t'.join([str(output_data.iloc[i, j])\n#                                     for j in range(output_data.shape[1])]) + '\\n')\n\n#     def parse_json(self, data_path):\n#         with open(data_path, 'rb') as g:\n#             for l in g:\n#                 yield eval(l)\n\n#     def getDF(self, data_path):\n#         i = 0\n#         df = {}\n#         for d in self.parse_json(data_path):\n#             df[i] = d\n#             i += 1\n#         data = pd.DataFrame.from_dict(df, orient='index')\n        \n#         return data","metadata":{"execution":{"iopub.status.busy":"2022-12-13T13:57:01.564210Z","iopub.execute_input":"2022-12-13T13:57:01.564819Z","iopub.status.idle":"2022-12-13T13:57:01.589150Z","shell.execute_reply.started":"2022-12-13T13:57:01.564767Z","shell.execute_reply":"2022-12-13T13:57:01.587290Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Step 2.2 Custom Dataset Class for Definition of Data Config File for Recbole\n# class NaverDataset(BaseDataset):\n#     def __init__(self, input_path, output_path):\n#         super(NaverDataset, self).__init__(input_path, output_path)\n#         self.dataset_name = \"naver_webtoon\"\n\n#         self.inter_file = os.path.join(self.input_path, \"webtoon_interaction_train.csv\")\n#         self.item_file = os.path.join(self.input_path, \"webtoon_articles.csv\")\n#         self.user_file = os.path.join(self.input_path, \"webtoon_customers.csv\")\n\n#         self.sep = \",\"\n\n#         # output_path-\n#         output_files = self.get_output_files()\n#         self.output_inter_file = output_files[0]\n#         self.output_item_file = output_files[1]\n#         self.output_user_file = output_files[2]\n\n#         # selected feature fields\n#         self.inter_fields = {\n#             0: \"user_id:token\",\n#             1: \"item_id:token\",\n#             2: \"posted_time:float\",\n#         }\n\n#         self.item_fields = {\n#             0: \"item_id:token\",\n#             1: \"story_author:token_seq\",\n#             2: \"image_author:token_seq\",\n#             3: \"genre:token_seq\",\n#         }\n\n#         self.user_fields = {\n#             0: \"user_id:token\",\n#         }\n    \n#     # inter => Transaction Train CSV\n#     def load_inter_data(self):\n#         df = pd.read_csv(self.inter_file,\n#                          delimiter=self.sep,\n#                          engine=\"python\",\n#                          dtype={\"posted_time\": \"object\",\n#                                 \"user_id\": \"object\",\n#                                 \"item_id\": \"object\",\n#                                })\n        \n#         df.sort_values(by=df.columns[0], ascending=True, inplace=True)\n#         print(df)\n#         print(df.dtypes)\n#         df = df.reset_index(drop=True)\n#         df['posted_time'] = df['posted_time'].apply(lambda x: datetime.timestamp(datetime.strptime(x, \"%Y-%m-%d\")))\n#         print(df)\n#         print(df.dtypes)\n#         return df\n\n#     def load_item_data(self):\n#         return pd.read_csv(self.item_file,\n#                            delimiter=self.sep,\n#                            engine=\"python\",\n#                            dtype={\"item_id\": \"object\",\n#                                   \"story_author\": \"object\",\n#                                   \"image_author\": \"object\",\n#                                   \"genre\": \"object\",\n#                                   })\n\n#     # user => Customer CSV\n#     def load_user_data(self):\n#         return pd.read_csv(self.user_file,\n#                            delimiter=self.sep,\n#                            engine=\"python\",\n#                            dtype={\"user_id\": \"object\",})","metadata":{"execution":{"iopub.status.busy":"2022-12-13T13:57:01.594244Z","iopub.execute_input":"2022-12-13T13:57:01.594815Z","iopub.status.idle":"2022-12-13T13:57:01.614980Z","shell.execute_reply.started":"2022-12-13T13:57:01.594763Z","shell.execute_reply":"2022-12-13T13:57:01.613571Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Step 2.3 Init Dataset Class Instance => Dataset Config Setting \n# # For Colab Setting\n# %time\n# naverds = NaverDataset('../input/naver-webtoon-recsys-interaction-meta-data/naver_webtoon', \"./naver_webtoon\")\n# naverds.convert_inter() # HMDataset에 BaseDataset을 상속해줬기 때문에 Call 가능, Convert_inter Call하면 이제 자식의 메서드 load_inter_data Call\n# naverds.convert_user()\n# naverds.convert_item()\n# del naverds # Recbole API에 데이터 셋을 전달했기 때문에 해당 클래스 인스턴스를 지워 메모리 공간을 확보합니다\n# gc.collect()","metadata":{"execution":{"iopub.status.busy":"2022-12-13T13:57:03.323054Z","iopub.execute_input":"2022-12-13T13:57:03.324532Z","iopub.status.idle":"2022-12-13T14:01:06.031425Z","shell.execute_reply.started":"2022-12-13T13:57:03.324469Z","shell.execute_reply":"2022-12-13T14:01:06.030288Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## **[Code Implementation]**  \n**Step 1. 자식 클래스인 'HMDataset' Instance 호출**    \n=> 이 때, 입력값 경로(Transaction, User, Item) & 출력값 경로(Model Train/Validation Result)를 전달  \n\n**Step 2. 부모 클래스의 메서드 'convert_inter'를 호출**  \n=> 해당 메서드 호출 시, 자식 클래스의 메서드 load_inter_data 호출  \n=> 메서드 'load_inter_data'는 사용자가 Transaction DataFrame을 활용해 정의한 Train Dataset을 반환  \n=> User, Item도 위 과정과 동일  \n\n**Step 3. 부모 클래스의 메서드 'convert' 호출**  \n=> 자식 클래스 메서드가 전달해준 DataFrame, 해당 DataFrame에서 사용할 Feature를 정의한 Dictionary, Output File 경로를 Parameter로 전달  \n=> Recbole API 경로에 Data Config 전달  \n\n### **데이터 준비는 끝~!!**","metadata":{}},{"cell_type":"markdown","source":"# **💻🏃‍ Step 3. Let's Run Model for Train/Validation**","metadata":{}},{"cell_type":"code","source":"# Step 2.4 Definition of Train/Validation Config\n# This Config for FDSA Sequential Models\n \nsequential_cfg = \"\"\"\ngpu_id: 0\nseed: 42\nreproducibility: True\nlog_wandb: True\ndata_path: /kaggle/working/\ndataset: naver_webtoon\nfield_separator: \"\\t\"\nUSER_ID_FIELD: user_id\nITEM_ID_FIELD: item_id\nRATING_FIELD: ~\nTIME_FIELD: timestamp\nshow_progress: False\nload_col:\n    inter: [user_id, item_id, timestamp]\n    user: [user_id]\n    item: [item_id, story_author, image_author, genre]\nloss_fn: 'CE'\nlearner: 'adam'\ntrain_batch_size: 4096\neval_batch_size: 4096\nepochs: 30\nlearning_rate: 0.001\nweight_decay: 0.00001\nenable_amp: True\nenable_scaler: True\nuser_inter_num_interval: \"[5,inf)\"\nitem_inter_num_interval: \"[0,inf)\"\nfilter_inter_by_user_or_item: false\nneg_sampling: None\neval_args:\n    split: {'LS': 'valid_only'}\n    group_by: user\n    order: TO\n    mode: uni100\nmetrics: ['Hit', 'MAP', 'NDCG']\ntopk: 10\nvalid_metric: MAP@10\n\"\"\"\n\nwith open(\"naver_webtoon/sequential_config.yaml\", \"w\") as f:\n    f.write(sequential_cfg)","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:04:17.498792Z","iopub.execute_input":"2022-12-13T17:04:17.499989Z","iopub.status.idle":"2022-12-13T17:04:17.507428Z","shell.execute_reply.started":"2022-12-13T17:04:17.499914Z","shell.execute_reply":"2022-12-13T17:04:17.506260Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# \"\"\"\n\n# Test for BPR Loss Function CFG\n\n\n# \"\"\"\n# # Step 2.4 Definition of Train/Validation Config\n# # This Config for FDSA Sequential Models\n \n# sequential_cfg = \"\"\"\n# gpu_id: 0\n# seed: 42\n# reproducibility: True\n# log_wandb: True\n# data_path: /kaggle/working/\n# dataset: naver_webtoon\n# field_separator: \"\\\\t\"\n# USER_ID_FIELD: user_id\n# ITEM_ID_FIELD: item_id\n# RATING_FIELD: ~\n# TIME_FIELD: timestamp\n# show_progress: False\n# load_col:\n#     inter: [timestamp, user_id, item_id]\n#     user: [user_id]\n#     item: [item_id, story_author, image_author, genre]\n# loss_fn: 'CE'\n# learner: 'adam'\n# train_batch_size: 4096\n# eval_batch_size: 4096\n# epochs: 2\n# learning_rate: 0.0003\n# weight_decay: 0.0000001\n# enable_amp: True\n# enable_scaler: True\n# user_inter_num_interval: \"[5,inf)\"\n# item_inter_num_interval: \"[0,inf)\"\n# filter_inter_by_user_or_item: false\n# neg_sampling: None\n# eval_args:\n#     split: {'LS': 'valid_only'}\n#     group_by: None\n#     order: TO\n#     mode: uni100\n# metrics: ['Hit', 'MAP', 'NDCG']\n# topk: 10\n# valid_metric: MAP@10\n# save_dataset: True\n# save_dataloaders: True\n\n# \"\"\"\n\n# with open(\"/kaggle/working/naver_webtoon/sequential_config.yaml\", \"w\") as f:\n#     f.write(sequential_cfg)","metadata":{"execution":{"iopub.status.busy":"2022-12-13T14:33:58.519112Z","iopub.execute_input":"2022-12-13T14:33:58.519526Z","iopub.status.idle":"2022-12-13T14:33:58.526907Z","shell.execute_reply.started":"2022-12-13T14:33:58.519494Z","shell.execute_reply":"2022-12-13T14:33:58.525654Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Dictionary Key 설명  \n1) RS : Ratio-Based Splitting, Train/Validation/Test Dataset 분배 비율 설정    \n2) TO : Temporal Ordering, 정확히 무엇을 의미하는지 아직 모르겠음  \n3) user_inter_num_interval : K개 이상 Interaction이 있는 유저만 데이터에 포함  \n4) item_inter_num_interval : k개 이상 Interaction이 있는 아이템만 데이터에 포함  \n\n","metadata":{}},{"cell_type":"code","source":"# \"\"\"\n\n# Test for BPR Loss Function \n\n# \"\"\"\n\n# # Model Config for Sequential Model => FDSA, TransRec, SASRec, BERT4Recs\n# def sequential_run(model_name):\n#     parameter_dict = {\n#             'neg_sampling': None,\n#     }\n#     fdsa_parameter_dict ={\n#             \"loss_type\": \"BPR\",\n#             \"train_neg_sample_args\": {\"distribution\": 'uniform', 'sample_num': 1},\n#             \"neg_sampling\": None,\n#             \"selected_features\": ['story_author', \n#                                   'image_author',\n#                                   'genre',]}\n#     # FDSA\n#     if model_name in [\n#         \"FDSA\",\n#         # \"GRU4Rec\",\n#         # \"TransRec\",\n#         # \"SASRec\",\n#         # \"BERT4Rec\",\n#       ]:\n\n#         return run_recbole(\n#             model=model_name,\n#             dataset='naver_webtoon',\n#             config_file_list=[\"/kaggle/working/naver_webtoon/sequential_config.yaml\"],\n#             config_dict=fdsa_parameter_dict,\n#         )\n#     # GRU4Rec, TransRec, SASRec, BERT4Rec\n#     else:\n#         return run_recbole(\n#             model=model_name,\n#             dataset='naver_webtoon',\n#             config_file_list=[\"/kaggle/working/naver_webtoon/sequential_config.yaml\"],\n#             config_dict = parameter_dict,\n#         )","metadata":{"execution":{"iopub.status.busy":"2022-12-12T10:25:33.700616Z","iopub.execute_input":"2022-12-12T10:25:33.701031Z","iopub.status.idle":"2022-12-12T10:25:33.711697Z","shell.execute_reply.started":"2022-12-12T10:25:33.700994Z","shell.execute_reply":"2022-12-12T10:25:33.710523Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Model Config for Sequential Model => FDSA, TransRec, SASRec, BERT4Recs\ndef sequential_run(model_name):\n    parameter_dict = {\n            'neg_sampling': None,\n    }\n    fdsa_parameter_dict ={\n            \"train_neg_sample_args\": None,\n            \"neg_sampling\": None,\n            \"selected_features\": ['story_author', \n                                  'image_author',\n                                  'genre',]}\n    # FDSA\n    if model_name in [\n        \"FDSA\",\n        # \"GRU4Rec\",\n        # \"TransRec\",\n        # \"SASRec\",\n        # \"BERT4Rec\",\n      ]:\n\n        return run_recbole(\n            model=model_name,\n            dataset='naver_webtoon',\n            config_file_list=[\"naver_webtoon/sequential_config.yaml\"],\n            config_dict=fdsa_parameter_dict,\n        )\n    # GRU4Rec, TransRec, SASRec, BERT4Rec\n    else:\n        return run_recbole(\n            model=model_name,\n            dataset='naver_webtoon',\n            config_file_list=[\"naver_webtoon/sequential_config.yaml\"],\n            config_dict = parameter_dict,\n        )","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:04:20.547053Z","iopub.execute_input":"2022-12-13T17:04:20.547424Z","iopub.status.idle":"2022-12-13T17:04:20.555131Z","shell.execute_reply.started":"2022-12-13T17:04:20.547391Z","shell.execute_reply":"2022-12-13T17:04:20.554055Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Let's Run & Check Result for FDSA Sequential \nmodel_list = [\"FDSA\",] # \"TransRec\", \"SASRec\", \"BERT4Rec\",] # Sequential\nfor model_name in tqdm(model_list):\n    # Wandb init\n    wandb.init(project=\"test-project\",\n               entity=\"bigdata_team23\",\n               name=f'[test_final_kaggle]_{model_name}',)\n    start_point = time.time()\n    print(f\"running {model_name}...\")\n    result = sequential_run(model_name)\n    end_point = time.time()\n    print(f'Running Time: {(end_point - start_point)/60:.2f} Mins')\n    print(result)","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:04:20.815037Z","iopub.execute_input":"2022-12-13T17:04:20.815411Z","iopub.status.idle":"2022-12-13T17:12:06.422414Z","shell.execute_reply.started":"2022-12-13T17:04:20.815379Z","shell.execute_reply":"2022-12-13T17:12:06.421396Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## **[Insight]**  \n### **💡Insight 1. Cold Start Problem**\n여러 카테고리의 모델들을 돌려보니 Inference해야 하는 유저 그룹의 Interaction 분포가 **\"특정 헤비 유저들에게 편중되어\"\"**  \n**“Cold Start Problem”**이 두드러졌습니다. 그 중에서 우리가 메인으로 사용하려던 **\"FDSA Sequential\"** 모델은 일정 에폭을  \n넘어가면 학습이 되지 않아 테스트 모델 중에서 **\"가장 높은 Train Loss\"**를 기록했지만  \n**\"Validation Score (mAP@12)에서는 가장 높은 성능\"**을 꾸준히 기록했습니다.    \n따라서 FDSA 단일 모델 파이프라인을 구성하려던 계획에 수정이 필요합니다.  \n\n### **💡Insight 2. User Group Segment & Hybrid System**  \nFDSA가 특정 유저 그룹에 높은 추천 정확도를 보인 것에서 착안하여, Interaction 수와 같은 특정 기준으로  \n전체 유저 그룹을 Segment하여 개별 타겟 그룹에 최적화 된 모델을 각각 적용하는 \"Hybrid System\" 구축이 필요하다고 생각했습니다.  \n결국 유저 그룹을 어떤 방식으로 여러 하위 그룹으로 Segment하는가에 따라서 이번 대회의 성패가 달려 있습니다.","metadata":{}},{"cell_type":"markdown","source":"## **[Strategy]**  \n### **🚗 Step 1. Segment User Groupby existing interaction Log in Train Period**    \n### **✈️ Step 2. Make Pipelines for Each User Group**    ","metadata":{}},{"cell_type":"markdown","source":"# **💯🏅 Step 4. Inference for Each Target User**  \n","metadata":{}},{"cell_type":"code","source":"# # # 경로 내부 저장된 파일 확인\n# # path_dir = '/kaggle/working/flask_app_demo/templates'\n# path_dir = '/kaggle/working/saved'\n# os.listdir(path_dir)","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:12:21.266444Z","iopub.execute_input":"2022-12-13T17:12:21.267164Z","iopub.status.idle":"2022-12-13T17:12:21.279942Z","shell.execute_reply.started":"2022-12-13T17:12:21.267122Z","shell.execute_reply":"2022-12-13T17:12:21.278615Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# \"\"\"\n\n# Step 1. Reload Best model & Dataset in Validation Score\n# => This Model is trained by full Train DataSet Not Split Cross Validation Method\n# => It's because spliting Train/Validation DataSet is hard to apply for sequential RecSys\n\n# \"\"\"\n# config, model, dataset, train_data, valid_data, test_data = load_data_and_model(\n#     model_file = \"./saved/FDSA-Dec-13-2022_16-03-31.pth\",\n#     )\n# del train_data, valid_data # Inference Stage에서 필요없음\n# gc.collect()","metadata":{"execution":{"iopub.status.busy":"2022-12-13T16:22:07.999909Z","iopub.execute_input":"2022-12-13T16:22:08.000320Z","iopub.status.idle":"2022-12-13T16:22:49.097729Z","shell.execute_reply.started":"2022-12-13T16:22:08.000282Z","shell.execute_reply":"2022-12-13T16:22:49.096703Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dataset.uid_field, dataset.iid_field, dataset.time_field","metadata":{"execution":{"iopub.status.busy":"2022-12-13T16:22:54.052691Z","iopub.execute_input":"2022-12-13T16:22:54.053080Z","iopub.status.idle":"2022-12-13T16:22:54.066807Z","shell.execute_reply.started":"2022-12-13T16:22:54.053048Z","shell.execute_reply":"2022-12-13T16:22:54.065396Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Get Target User_ID => Type 1\n# external_user_ids = dataset.id2token(dataset.uid_field, list(range(dataset.user_num)))[1:]\n# external_user_ids, len(external_user_ids)","metadata":{"execution":{"iopub.status.busy":"2022-12-13T16:22:58.988852Z","iopub.execute_input":"2022-12-13T16:22:58.989447Z","iopub.status.idle":"2022-12-13T16:22:59.011840Z","shell.execute_reply.started":"2022-12-13T16:22:58.989409Z","shell.execute_reply":"2022-12-13T16:22:59.010765Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Get Target User_ID => Type 1\n# external_item_ids = dataset.id2token(dataset.iid_field, list(range(dataset.item_num)))[1:]\n# external_item_ids, len(external_item_ids[:-100])","metadata":{"execution":{"iopub.status.busy":"2022-12-13T16:22:59.167894Z","iopub.execute_input":"2022-12-13T16:22:59.168278Z","iopub.status.idle":"2022-12-13T16:22:59.182238Z","shell.execute_reply.started":"2022-12-13T16:22:59.168245Z","shell.execute_reply":"2022-12-13T16:22:59.180750Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# \"\"\"\n\n# Step 2. Definition of Function for Inference\n# => full_sort_prediction\n# => This is code for \"Type 1 RecSys\"\n\n# \"\"\"\n# def add_last_item(old_interaction, last_item_id, max_len=50):\n#     new_seq_items = old_interaction['article_id_list'][-1]\n#     if old_interaction['item_length'][-1].item() < max_len:\n#         new_seq_items[old_interaction['item_length'][-1].item()] = last_item_id\n#     else:\n#         new_seq_items = torch.roll(new_seq_items, -1)\n#         new_seq_items[-1] = last_item_id\n#     return new_seq_items.view(1, len(new_seq_items))\n\n# def predict_item(external_user_id, dataset, model):\n#     \"\"\"\n\n#     article_id_list => interaction_sequence recorded by Target User\n\n#     \"\"\"\n#     model.eval()\n#     with torch.no_grad():\n#         uid_series = dataset.token2id(dataset.uid_field, [external_user_id])\n#         index = np.isin(dataset.inter_feat[dataset.uid_field].numpy(), uid_series)\n#         input_interaction = dataset[index]\n      \n#         test = {\n#                 'article_id_list': add_last_item(input_interaction, \n#                                                  input_interaction['article_id'][-1].item(),\n#                                                  model.max_seq_length),\n#                 'item_length': torch.tensor([input_interaction['item_length'][-1].item() + 1\n#                                              if input_interaction['item_length'][-1].item() < model.max_seq_length else model.max_seq_length])\n#         }\n#         new_inter = Interaction(test) # 마지막 정보도 함께 추론에 사용된다.\n#         new_inter = new_inter.to(config['device'])\n#         new_scores = model.full_sort_predict(new_inter) # 개별 모델 클래스 내부 메소드\n#         new_scores = new_scores.view(-1, test_data.dataset.item_num)\n#         new_scores[:, 0] = -np.inf  # set scores of [pad] to -inf\n#     return torch.topk(new_scores, 12)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# \"\"\"\n\n# Step 2. Definition of Function for Inference\n# => full_sort_prediction\n# => This is code for \"Type 2 RecSys\"\n\n# \"\"\"\n# def add_last_item(old_interaction, last_item_id, max_len=50):\n#     new_seq_items = old_interaction['item_id_list'][-1]\n#     if old_interaction['item_length'][-1].item() < max_len:\n#         new_seq_items[old_interaction['item_length'][-1].item()] = last_item_id\n#     else:\n#         new_seq_items = torch.roll(new_seq_items, -1)\n#         new_seq_items[-1] = last_item_id\n#     return new_seq_items.view(1, len(new_seq_items))\n\n# def predict_item_2(external_user_id, new_inter_sequences, dataset, model):\n#     \"\"\"\n\n#     article_id_list => interaction_sequence recorded by Target User\n\n#     \"\"\"\n#     model.eval()\n#     with torch.no_grad():\n#         uid_series = dataset.token2id(dataset.uid_field, [external_user_id])\n#         print(uid_series)\n#         iid_series = torch.tensor([dataset.token2id(dataset.iid_field, [new_inter_sequence]) for new_inter_sequence in new_inter_sequences]).flatten()\n#         print(iid_series)\n#         iid_length = torch.tensor([len(iid_series)])\n#         iid_series = np.pad(iid_series,(0, 50-len(iid_series)), \n#                             mode='constant',\n#                             constant_values=0)\n        \n#         test = {\n#                 'item_id_list': torch.tensor([iid_series]),\n#                 'item_length': iid_length\n#         }\n#         new_inter = Interaction(test) # 마지막 정보도 함께 추론에 사용된다.\n\n#         new_inter = new_inter.to(config['device'])\n#         new_scores = model.full_sort_predict(new_inter) # 개별 모델 클래스 내부 메소드\n#         new_scores = new_scores.view(-1, test_data.dataset.item_num)\n#         new_scores[:, 0] = -np.inf  # set scores of [pad] to -inf\n#     return torch.topk(new_scores, 10)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Test code for Type 2 RecSys\n# new_inter_sequences = ['참교육', '약한영웅', '뷰티풀 군바리', '낙향문사전', '스터디그룹']\n# score, rank = predict_item_2(\"달걀\", new_inter_sequences, dataset, model)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# \"\"\" \n\n# This is For-Loop for Inference Target User's buying next Period\n\n# [Inference Step]\n# 1) Range: [0, 25000] => Colab\n# 2) Range: [25000, 50000] => Kaggle Notebook Stream\n# 3) Range: [50000, 105333] => Kaggle Notebook Background\n\n# \"\"\"\n# outer_topk_item_list = []\n# for uuser in tqdm(external_user_ids[50000:105333]):\n#     result = predict_item(uuser, dataset, model)\n#     inter_topk_items = result[-1][-1]\n#     outer_topk_item_list.append(dataset.id2token(dataset.iid_field, inter_topk_items.cpu()).tolist())\n\n#     del result, inter_topk_items\n#     gc.collect()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# group_one_submission = pd.DataFrame(external_user_ids[50000:105333],\n#                                     columns=['customer_id'])\n# group_one_submission['prediction'] = [' '.join(outer_topk_item) for outer_topk_item in outer_topk_item_list]\n# group_one_submission.to_csv('(Range_3)final_group_submission.csv', index=False)\n# group_one_submission.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **🌐💻 Step 5. Make Simple Web Application by using Flask & Ngrok**  \n## **1) 💻🤖Flask Web Application Environment를 Kaggle Notebook VM에서 실행시키기**  \n## **2) Flask Enviroment에 FDSA Model Serving**  \n## **3) Orginal DataSet Serving to Flask SQLite3**  \n## **4) Save User's Input in Flask SQLite3**  \n## **5) Let's Inference for New User**  \n## **6) Return & Display Recommend Result for Each User**  ","metadata":{}},{"cell_type":"markdown","source":"## **💻🤖Step 6.1 Flask Web Application Evironment to Kaggle Notebook VM**\n## **🌪️💻Step 6.2 Serving Orginal DataSet to Flask & SQLite3**  \n=> 어차피 DB에 있는 유저 22만 중에 학습에 사용된 애들은 6만명이니까 나머지 16만 중에서 아무나 잡고  \n대상의 user_id를 현재 어플리케이션을 사용하고 있는 유저에게 할당하고 그 사람이 입력한 interaction을  \nnew_inter를 활용해 해당 user_id에 다시 할당해 추론 진행","metadata":{}},{"cell_type":"code","source":"# # Input authtoken for flask-ngrok\n# # Download dependency package\n# !ngrok authtoken '2Ii9RdJHMMKne3BkAImn71cOF5E_36RMXo3gioBo3myEYLi4g'\n# !wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.tgz\n# !tar -xvf /kaggle/working/ngrok-stable-linux-amd64.tgz","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:12:33.100266Z","iopub.execute_input":"2022-12-13T17:12:33.101255Z","iopub.status.idle":"2022-12-13T17:12:38.987832Z","shell.execute_reply.started":"2022-12-13T17:12:33.101216Z","shell.execute_reply":"2022-12-13T17:12:38.986717Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Make Root Directory & Sub-Directory(static, templates)\n# os.mkdir('/kaggle/working/flask_app_demo')\n# os.mkdir('/kaggle/working/flask_app_demo/static')\n# os.mkdir('/kaggle/working/flask_app_demo/templates')\n# %cd flask_app_demo","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:12:38.990531Z","iopub.execute_input":"2022-12-13T17:12:38.990965Z","iopub.status.idle":"2022-12-13T17:12:39.003321Z","shell.execute_reply.started":"2022-12-13T17:12:38.990920Z","shell.execute_reply":"2022-12-13T17:12:39.002263Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Drop Unecessary Columns\n# webtoon_data_path = '/kaggle/input/naver-webtoon-recsys-interaction-meta-data/naver_webtoon/'\n# webtoon_inter = pd.read_csv(webtoon_data_path + 'webtoon_interaction_train.csv')\n# webtoon_articles = pd.read_csv(webtoon_data_path + 'webtoon_articles.csv')\n# webtoon_customers = pd.read_csv(webtoon_data_path + 'webtoon_customers.csv')\n\n# webtoon_inter.drop(columns=['user_name', 'posted_time', 'item_name'], inplace=True)\n# webtoon_articles.drop(columns=['description', 'thumbnail'], inplace=True)\n# webtoon_customers.drop(columns=['user_name'], inplace=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Serving Orginal Dataset to Flask & SQLite3\n# # Make DB for Each DataFrame\n\n# # inter.DB\n# conn_inter = sqlite3.connect('webtoon_interaction_train.sqlite') # inter_DB\n# c_inter = conn_inter.cursor()\n# c_inter.execute('DROP TABLE IF EXISTS webtoon_interaction_train_db')\n# webtoon_inter.to_sql('webtoon_interaction_train_db', conn_inter)\n# # c_inter.execute('CREATE TABLE webtoon_interaction_train_db'\\\n# #                 '(timestamp INTEGER, user_id TEXT, item_id TEXT)')\n\n# # articles.DB\n# conn_articles = sqlite3.connect('webtoon_articles.sqlite') # inter_DB\n# c_articles = conn_articles.cursor()\n# c_articles.execute('DROP TABLE IF EXISTS webtoon_articles_db')\n# webtoon_articles.to_sql('webtoon_articles_db', conn_articles)\n# # c_articles.execute('CREATE TABLE webtoon_articles_db'\\\n# #                    '(item_id TEXT, item_name TEXT, story_author TEXT, image_author TEXT, type TEXT, genre TEXT)')\n\n# # customers.DB\n# conn_customers = sqlite3.connect('webtoon_customers.sqlite') # inter_DB\n# c_customers = conn_customers.cursor()\n# c_customers.execute('DROP TABLE IF EXISTS webtoon_customers_db')\n# webtoon_customers.to_sql('webtoon_customers_db', conn_customers)\n# # c_customers.execute('CREATE TABLE webtoon_customers_db'\\\n# #                     '(user_id TEXT, user_name TEXT)')\n\n# conn_inter.commit()\n# conn_articles.commit()\n# conn_customers.commit()\n\n# conn_inter.close()\n# conn_articles.close()\n# conn_customers.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Check sqlite db shape\n# test = pd.read_sql('SELECT * from webtoon_articles_db', conn_articles)\n# print(test)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# webtoon_inter.to_sql('webtoon_interaction_train_db', conn_inter)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# %%writefile app.py\n# # Make Python Script in dir.flask_app_demo\n# import numpy as np\n# import pandas as pd\n# import wandb\n# import sqlite3\n# import sys, os, gc, random, time, datetime, pickle\n# import torch\n\n# from datetime import datetime\n# from flask import Flask, render_template, request, flash\n# from flask_ngrok import run_with_ngrok\n\n# from datetime import datetime\n# from tqdm.notebook import tqdm\n# from kaggle_secrets import UserSecretsClient\n# from recbole.config import Config\n# from recbole.data import create_dataset, data_preparation\n# from recbole.quick_start import run_recbole, load_data_and_model\n# from recbole.data.interaction import Interaction\n# from recbole.utils.case_study import full_sort_topk\n# from recbole.trainer import Trainer\n# from recbole.utils import init_seed, init_logger\n\n# from wtforms import Form, TextAreaField, validators, StringField \n# from wtforms.validators import DataRequired \n\n# app = Flask(__name__)\n# #######################################################################################################\n# \"\"\"\n\n# Stage 0\n\n# \"\"\"\n# # Step 0. Item DataFrame => Need to Encoding & Decoding User Input & Output \n# item_df = pd.read_csv('/kaggle/input/naver-webtoon-recsys-interaction-meta-data/naver_webtoon/webtoon_articles.csv')\n\n# # Step 1. Upload Pretrained Model, Dataset on Flask API\n# config, model, dataset, train_data, valid_data, test_data = load_data_and_model(\n#     model_file = \"/kaggle/working/saved/FDSA-Dec-13-2022_17-05-34.pth\",\n#     )\n# del train_data, valid_data # Unnecessary Data\n# gc.collect() # Garbage Collecting\n# #######################################################################################################\n\n# # Step 2. Function for making \"new_inter_sequence\"\n# def new_inter_sequence(input_dict):\n#     external_inter = [value for key, value in input_dict.items() if value != \"\"]\n#     internal_inter = [item_df[item_df['item_name'] == value].item_id for internal in internal_inter]\n#     return internal_inter\n\n# #######################################################################################################\n# \"\"\"\n\n# Stage 1\n\n# \"\"\"\n# # Type 1) Recommend WebToon for \"Record Comment more than 5 time in Period((22.08.01 ~ 22.11.27))\"\n# def add_last_item(old_interaction, last_item_id, max_len=50):\n#     new_seq_items = old_interaction['item_id_list'][-1]\n#     if old_interaction['item_length'][-1].item() < max_len:\n#         new_seq_items[old_interaction['item_length'][-1].item()] = last_item_id\n#     else:\n#         new_seq_items = torch.roll(new_seq_items, -1)\n#         new_seq_items[-1] = last_item_id\n#     return new_seq_items.view(1, len(new_seq_items))\n\n# # 사용자 입력을 통해, DB에 저장된 닉네임 찾기 => Type 1 Recommend System\n# def sqlite_entry(path, document, y):\n#     conn = sqlite3.connect(path) # Path: 위에서 설정한 세가지 데이터 베이스 경로 전달\n#     c = conn.cursor()\n#     c.execute(\"\") # 저장할 데이터 스키마 형식 지정\n#     conn.commit()\n#     conn.close()\n\n\n# #######################################################################################################\n# \"\"\"\n\n# Stage 2\n\n# \"\"\"\n# # Type 1) Recommend WebToon for \"No Record Comment more than 5 time in Period((22.08.01 ~ 22.11.27))\"\n# #         But, number of currently watching WebToons are at least 5\n# def type2_recommend(external_user_id, new_inter_sequences, dataset, model):\n#     model.eval() # Turn on Evaluation Mode\n#     with torch.no_grad():\n#         try:\n#             uid_series = dataset.token2id(dataset.uid_field, [external_user_id])\n#             iid_series = torch.tensor([dataset.token2id(dataset.iid_field, [new_inter_sequence]) for new_inter_sequence in new_inter_sequences]).flatten()\n#             iid_length = torch.tensor([len(iid_series)])\n#             iid_series = np.pad(iid_series,(0, 50-len(iid_series)), \n#                                 mode='constant',\n#                                 constant_values=0)\n#             test = {\n#                     'item_id_list': torch.tensor([iid_series]),\n#                     'item_length': iid_length\n#             }\n#             new_inter = Interaction(test) # 마지막 정보도 함께 추론에 사용된다.\n#             new_inter = new_inter.to(config['device'])\n#             new_scores = model.full_sort_predict(new_inter) # 개별 모델 클래스 내부 메소드\n#             new_scores = new_scores.view(-1, test_data.dataset.item_num)\n#             new_scores[:, 0] = -np.inf  # set scores of [pad] to -inf\n#             return torch.topk(new_scores, 10)\n        \n#         except Exception as e:\n#             print(e)\n                              \n# #######################################################################################################\n# run_with_ngrok(app)\n# #######################################################################################################\n# @app.route('/')\n# def type2_main():\n#         return render_template('type2_main.html')\n\n# @app.route('/result', methods=['POST', 'GET'])\n# def result():\n#     if request.method == 'POST':\n#         new_inter_sequences = new_inter_sequence(request.form) # Transfer User's Input to list type\n#         result = type2_recommend(\"100038813\", new_inter_sequences, dataset, model) # Output => Top-10 Rank\n#         rank = result[-1][-1]\n#         top_10_list = dataset.id2token(dataset.iid_field, rank.cpu()).tolist()\n#         top_10 = [item_df[item_df['item_id'] == item].item_name for item in top_10_list]\n        \n#     return render_template(\"type2_result.html\", result=top_10)\n    \n# if __name__ == '__main__':\n#     app.run()","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:12:51.240342Z","iopub.execute_input":"2022-12-13T17:12:51.240744Z","iopub.status.idle":"2022-12-13T17:12:51.255459Z","shell.execute_reply.started":"2022-12-13T17:12:51.240710Z","shell.execute_reply":"2022-12-13T17:12:51.254020Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Make HTML Script in dir.templates\n# %cd templates","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:15:38.947706Z","iopub.execute_input":"2022-12-13T17:15:38.948117Z","iopub.status.idle":"2022-12-13T17:15:38.959271Z","shell.execute_reply.started":"2022-12-13T17:15:38.948081Z","shell.execute_reply":"2022-12-13T17:15:38.958073Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# %%writefile type2_main.html\n# <!DOCTYPE html>\n# <html lang=\"en\">\n#    <h1>💬🙅‍♀️Type 2. Recommend for No Comment</h1>\n#    <h3>🙇Please Input your current watching WebToon</h3>\n#    <h4>You can input: </h4>\n#    <h5>🪢1) Sequence of watching Pattern </h5>\n#    <h5>🗄️2) list of watching WebToon </h5>\n#    <h5>Input Example 2: </h5>\n#    <body>\n\n#       <form action = '/result' method = \"POST\">\n#          <p>WebToon 1 <input type = \"text\" name = \"WebToon 1\" /></p>\n#          <p>WebToon 2 <input type = \"text\" name = \"WebToon 2\" /></p>\n#          <p>WebToon 3 <input type = \"text\" name = \"WebToon 3\" /></p>\n#          <p>WebToon 4 <input type =\"text\" name = \"WebToon 4\" /></p>\n#          <p>WebToon 5 <input type =\"text\" name = \"WebToon 5\" /></p>\n#          <p>WebToon 6 <input type =\"text\" name = \"WebToon 6\" /></p>\n#          <p>WebToon 7 <input type =\"text\" name = \"WebToon 7\" /></p>\n#          <p>WebToon 8 <input type =\"text\" name = \"WebToon 8\" /></p>\n#          <p>WebToon 9 <input type =\"text\" name = \"WebToon 9\" /></p>\n#          <p>WebToon 10 <input type =\"text\" name = \"WebToon 10\" /></p>\n#          <p><input type = \"submit\" value = \"submit\" /></p>\n#       </form>\n    \n#    </body>\n# </html>\n","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:15:39.999481Z","iopub.execute_input":"2022-12-13T17:15:40.000532Z","iopub.status.idle":"2022-12-13T17:15:40.013662Z","shell.execute_reply.started":"2022-12-13T17:15:40.000484Z","shell.execute_reply":"2022-12-13T17:15:40.011874Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# %%writefile type2_result.html\n# <html lang=\"en\">\n#    <body>\n\n#       <table>\n#          {% for idx, value in enumerate(result) %}\n\n#             <tr>\n#                <td> {{ idx }}위 </td>\n#                <td> {{ value }} </td>\n#             </tr>\n\n#         {% endfor %}\n#       </table>\n\n#    </body>\n# </html>","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:15:40.323392Z","iopub.execute_input":"2022-12-13T17:15:40.324117Z","iopub.status.idle":"2022-12-13T17:15:40.334002Z","shell.execute_reply.started":"2022-12-13T17:15:40.324077Z","shell.execute_reply":"2022-12-13T17:15:40.332924Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Let's run our web application\n# %cd /kaggle/working/flask_app_demo\n# !python app.py","metadata":{"execution":{"iopub.status.busy":"2022-12-13T17:15:41.261263Z","iopub.execute_input":"2022-12-13T17:15:41.261670Z","iopub.status.idle":"2022-12-13T17:16:59.990777Z","shell.execute_reply.started":"2022-12-13T17:15:41.261635Z","shell.execute_reply":"2022-12-13T17:16:59.989689Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# model_path = '/kaggle/working/FDSA_all_model_structure'\n# torch.save(model, model_path)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}