{"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":"## Introduction","metadata":{}},{"cell_type":"markdown","source":"**Recommendation Using Item to Item GNN Embedding**\n\n1. [Create dataset](https://www.kaggle.com/code/cafelatte1/otto-create-dataset-gnn-embedding)\n2. [Training](https://www.kaggle.com/cafelatte1/otto-training-gnn-embedding)\n3. [Inference](https://www.kaggle.com/cafelatte1/otto-inference-gnn-embedding)","metadata":{}},{"cell_type":"markdown","source":"## Setup","metadata":{}},{"cell_type":"code","source":"!nvcc -V","metadata":{"execution":{"iopub.status.busy":"2023-01-17T05:33:31.048893Z","iopub.execute_input":"2023-01-17T05:33:31.049531Z","iopub.status.idle":"2023-01-17T05:33:32.168538Z","shell.execute_reply.started":"2023-01-17T05:33:31.049441Z","shell.execute_reply":"2023-01-17T05:33:32.167346Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# !pip install -q --no-cache-dir merlin-dataloader==0.0.2","metadata":{"execution":{"iopub.status.busy":"2023-01-17T05:33:32.170626Z","iopub.execute_input":"2023-01-17T05:33:32.171170Z","iopub.status.idle":"2023-01-17T05:33:32.177183Z","shell.execute_reply.started":"2023-01-17T05:33:32.171109Z","shell.execute_reply":"2023-01-17T05:33:32.175926Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # UPGRADE TORCH VERSION TO FIT WITH TORCH GEOMETRIC GPU\n# !pip install -q --no-cache-dir torch==1.12.1+cu102 torchvision==0.13.1+cu102 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu102","metadata":{"execution":{"iopub.status.busy":"2023-01-17T05:33:32.178711Z","iopub.execute_input":"2023-01-17T05:33:32.180118Z","iopub.status.idle":"2023-01-17T05:33:32.188790Z","shell.execute_reply.started":"2023-01-17T05:33:32.180067Z","shell.execute_reply":"2023-01-17T05:33:32.187683Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"GLOBAL_SEED = 42\n\nimport os\nos.environ[\"PYTHONIOENCODING\"] = \"utf8\"\nos.environ['PYTHONHASHSEED'] = str(GLOBAL_SEED)\nimport sys\nfrom glob import glob\n\nimport pandas as pd\nimport numpy as np\nfrom numpy import random as np_rnd\nimport random as rnd\nimport shutil\nimport gc\nimport datetime\nfrom collections import defaultdict, Counter\nfrom tqdm import tqdm\nfrom multiprocessing import Pool, cpu_count\nimport time\nimport pickle\n\nimport torch\nfrom torch import nn\nimport torch.nn.functional as F\nfrom torch.optim import AdamW, Adam, SparseAdam\nfrom transformers import get_polynomial_decay_schedule_with_warmup\n\n# from merlin.io import Dataset\n# from merlin.loader.torch import Loader\n\nimport networkx as nx\nimport matplotlib.pyplot as plt\n\nfrom annoy import AnnoyIndex\ntry:\n    import cudf\n    import cuml\n    import cupy\nexcept:\n    pass\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ntorch.__version__","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:12:59.423621Z","iopub.execute_input":"2023-01-17T06:12:59.424052Z","iopub.status.idle":"2023-01-17T06:12:59.447606Z","shell.execute_reply.started":"2023-01-17T06:12:59.424019Z","shell.execute_reply":"2023-01-17T06:12:59.446357Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for i in glob(\"/kaggle/input/pytorch-geometric/pytorch_geometric_gpu/*\" if torch.cuda.is_available() else \"/kaggle/input/pytorch-geometric/pytorch_geometric_cpu/*\") :\n    !pip install -q --no-cache-dir {i}\n    \n!pip install -q --no-cache-dir torch-geometric\n\nfrom torch_geometric.data import Data\nfrom torch_geometric.utils import coalesce\nfrom torch_geometric.loader import DataLoader\nfrom torch_geometric.nn import GCNConv","metadata":{"execution":{"iopub.status.busy":"2023-01-17T05:33:40.009659Z","iopub.execute_input":"2023-01-17T05:33:40.010355Z","iopub.status.idle":"2023-01-17T05:34:59.907575Z","shell.execute_reply.started":"2023-01-17T05:33:40.010319Z","shell.execute_reply":"2023-01-17T05:34:59.906518Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def seed_everything(seed=42):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    # python random\n    rnd.seed(seed)\n    # numpy random\n    np_rnd.seed(seed)\n    # tf random\n    try:\n        tf_rnd.set_seed(seed)\n    except:\n        pass\n    # RAPIDS random\n    try:\n        cp.random.seed(seed)\n    except:\n        pass\n    # pytorch random\n    try:\n        torch.manual_seed(seed)\n        torch.cuda.manual_seed(seed)\n        torch.backends.cudnn.deterministic = True\n    except:\n        pass\n\ndef pickleIO(obj, src, op=\"w\"):\n    if op==\"w\":\n        with open(src, op + \"b\") as f:\n            pickle.dump(obj, f)\n    elif op==\"r\":\n        with open(src, op + \"b\") as f:\n            tmp = pickle.load(f)\n        return tmp\n    else:\n        print(\"unknown operation\")\n        return obj\n    \ndef findIdx(data_x, col_names):\n    return [int(i) for i, j in enumerate(data_x) if j in col_names]\n\ndef createFolder(directory):\n    try:\n        if not os.path.exists(directory):\n            os.makedirs(directory)\n    except OSError:\n        print('Error: Creating directory. ' + directory)\n        \ndef create_submission(df):\n    df = df.reset_index()\n    df[\"type\"] = df[\"type\"].map(CFG.contentType_mapper)\n    df[\"session_type\"] = df[\"session\"].astype(\"str\") + \"_\" + df[\"type\"].astype(\"str\") + \"s\"\n    df = df[[\"session_type\", \"prediction\"]].rename({\"prediction\": \"labels\"}, axis=1)\n    return df\n\ndef create_get_ts(ts):\n    return int((ts.replace(tzinfo=CFG.tz) - CFG.ts_zero).total_seconds())\n\ndef visualize_graph(G, color):\n    plt.figure(figsize=(7,7))\n    plt.xticks([])\n    plt.yticks([])\n    nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False, node_color=color, cmap=\"Set2\")\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-17T05:34:59.909062Z","iopub.execute_input":"2023-01-17T05:34:59.909417Z","iopub.status.idle":"2023-01-17T05:34:59.925660Z","shell.execute_reply.started":"2023-01-17T05:34:59.909379Z","shell.execute_reply":"2023-01-17T05:34:59.923876Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CFG:\n    local = False\n    debug = False\n    tz = datetime.timezone.utc\n    ts_zero = datetime.datetime(1970, 1, 1, tzinfo=tz)\n    contentType_mapper = pd.Series([\"clicks\", \"carts\", \"orders\"], index=[0, 1, 2])\n    target_weight = (0.1, 0.3, 0.6)\n    \n    epochs = 20\n    early_stopping_rounds = 10\n    eta = 5e-4\n    weight_decay = 1e-4\n    max_grad_norm = 1e+2\n    \n    embed_dim = 32\n    \nif CFG.local:\n    CFG.folder_path = \"./dataset/\"\nelse:\n    CFG.folder_path = \"/kaggle/input/\"","metadata":{"execution":{"iopub.status.busy":"2023-01-17T05:34:59.928269Z","iopub.execute_input":"2023-01-17T05:34:59.929001Z","iopub.status.idle":"2023-01-17T05:34:59.951872Z","shell.execute_reply.started":"2023-01-17T05:34:59.928956Z","shell.execute_reply":"2023-01-17T05:34:59.950670Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading Data","metadata":{}},{"cell_type":"code","source":"node_feature = pickleIO(None, \"/kaggle/input/create-dataset-gnn-embedding/node_feature.pkl\", \"r\").to(device)\nedge_index = pickleIO(None, \"/kaggle/input/otto-training-gnn-embedding/sampled_edge_train.pkl\", \"r\").to(device)","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:12:20.777645Z","iopub.execute_input":"2023-01-17T06:12:20.778054Z","iopub.status.idle":"2023-01-17T06:12:22.808491Z","shell.execute_reply.started":"2023-01-17T06:12:20.778021Z","shell.execute_reply":"2023-01-17T06:12:22.807468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading Model & Building Nearest Neighbors Searcher (Annoy)","metadata":{}},{"cell_type":"code","source":"class GCN(torch.nn.Module):\n    def __init__(self, n_aids, embed_dim):\n        super().__init__()\n        self.aid_factors = nn.Embedding(n_aids, embed_dim, sparse=False)\n        self.gcn = GCNConv(embed_dim, embed_dim)\n        self.gcn_act = nn.ReLU()\n        self.lin = nn.Linear(embed_dim, embed_dim)\n    def forward(self, x, edge_index):\n        x = self.aid_factors(x)\n        x = self.gcn(x, edge_index=edge_index)\n        x = self.gcn_act(x)\n        x = self.lin(x)\n        return x","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:12:28.552150Z","iopub.execute_input":"2023-01-17T06:12:28.552566Z","iopub.status.idle":"2023-01-17T06:12:28.561354Z","shell.execute_reply.started":"2023-01-17T06:12:28.552532Z","shell.execute_reply":"2023-01-17T06:12:28.559967Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = GCN(len(node_feature), 32)\nmodel.load_state_dict(torch.load(\"/kaggle/input/otto-training-gnn-embedding/model_fold0_best.pth\", map_location=torch.device('cpu'))[\"model\"])\nmodel.eval()\nmodel.to(device)\nwith torch.no_grad():\n    embeddings = model(node_feature, edge_index).detach().cpu().numpy()\ndel model, edge_index; gc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:12:29.975947Z","iopub.execute_input":"2023-01-17T06:12:29.976322Z","iopub.status.idle":"2023-01-17T06:12:39.449452Z","shell.execute_reply.started":"2023-01-17T06:12:29.976290Z","shell.execute_reply":"2023-01-17T06:12:39.448215Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"embeddings","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:13:08.087645Z","iopub.execute_input":"2023-01-17T06:13:08.088337Z","iopub.status.idle":"2023-01-17T06:13:08.097214Z","shell.execute_reply.started":"2023-01-17T06:13:08.088277Z","shell.execute_reply":"2023-01-17T06:13:08.096214Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\nntrees = 100\n\nsearcher = AnnoyIndex(CFG.embed_dim, 'euclidean')\nsearcher.set_seed(GLOBAL_SEED)\n\n# Add items\nfor idx, value in enumerate(embeddings):\n    searcher.add_item(idx, value)\n\n# Building tree\nsearcher.build(ntrees) ","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:14:22.765470Z","iopub.execute_input":"2023-01-17T06:14:22.765879Z","iopub.status.idle":"2023-01-17T06:16:40.831084Z","shell.execute_reply.started":"2023-01-17T06:14:22.765850Z","shell.execute_reply":"2023-01-17T06:16:40.829789Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del node_feature; gc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:16:40.832772Z","iopub.execute_input":"2023-01-17T06:16:40.833105Z","iopub.status.idle":"2023-01-17T06:16:41.076339Z","shell.execute_reply.started":"2023-01-17T06:16:40.833077Z","shell.execute_reply":"2023-01-17T06:16:41.074903Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Inference","metadata":{}},{"cell_type":"code","source":"test = pd.read_parquet('../input/otto-full-optimized-memory-footprint/test.parquet')\nsession_types = ['clicks', 'carts', 'orders']\ntest_session_AIDs = test.reset_index(drop=True).groupby('session')['aid'].apply(list)\ntest_session_types = test.reset_index(drop=True).groupby('session')['type'].apply(list)\ndel test; gc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:16:45.751489Z","iopub.execute_input":"2023-01-17T06:16:45.751917Z","iopub.status.idle":"2023-01-17T06:17:49.796595Z","shell.execute_reply.started":"2023-01-17T06:16:45.751881Z","shell.execute_reply":"2023-01-17T06:17:49.795671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"n_aids = 20\n\noutput = {\n    \"session\": [],\n    \"type\": [],\n    \"rec\": [],\n    \"score\": [],\n}\n\nfor SESS, AIDs, types in tqdm(zip(test_session_AIDs.index, test_session_AIDs.values, test_session_types.values), total=len(test_session_AIDs.index)):\n    # Get more co-visitation items order > cart > click\n    # If you want to analyze & get the items with details, you may need more RAM & CPU resources\n#     candidates = []\n#     for aid, t in zip(AIDs, types):\n#         candidates += searcher.get_nns_by_item(aid, 21)[1:]\n    candidates = searcher.get_nns_by_item(AIDs[-1], 21)[1:]\n    if len(candidates) == 0: candidates = AIDs.copy()\n\n    candidates = Counter(candidates)\n    rec, score = zip(*candidates.most_common(n_aids))\n    \n    output[\"session\"].extend([SESS] * 3)\n    output[\"type\"].extend([0, 1, 2])\n    output[\"rec\"].extend([\" \".join(pd.Series(rec, dtype=\"str\").values)] * 3)\n    output[\"score\"].extend([\" \".join(pd.Series(score, dtype=\"str\").values)] * 3)\n\noutput = pd.DataFrame(output).set_index([\"session\", \"type\"])","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:17:49.798635Z","iopub.execute_input":"2023-01-17T06:17:49.799477Z","iopub.status.idle":"2023-01-17T06:18:23.813244Z","shell.execute_reply.started":"2023-01-17T06:17:49.799436Z","shell.execute_reply":"2023-01-17T06:18:23.811010Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"output","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:18:23.814358Z","iopub.status.idle":"2023-01-17T06:18:23.814962Z","shell.execute_reply.started":"2023-01-17T06:18:23.814662Z","shell.execute_reply":"2023-01-17T06:18:23.814693Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"output.reset_index().to_parquet(\"./raw_output.parquet\")","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:18:23.816455Z","iopub.status.idle":"2023-01-17T06:18:23.817047Z","shell.execute_reply.started":"2023-01-17T06:18:23.816799Z","shell.execute_reply":"2023-01-17T06:18:23.816822Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Submission","metadata":{}},{"cell_type":"code","source":"output[\"session_type\"] = [str(i[0]) + \"_\" + str(CFG.contentType_mapper[i[1]]) for i in output.index]","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:18:23.817984Z","iopub.status.idle":"2023-01-17T06:18:23.818552Z","shell.execute_reply.started":"2023-01-17T06:18:23.818259Z","shell.execute_reply":"2023-01-17T06:18:23.818286Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission = pd.read_csv(\"/kaggle/input/otto-recommender-system/sample_submission.csv\")\nsubmission = submission.set_index(\"session_type\")\nsubmission.loc[output[\"session_type\"].values, \"labels\"] = output[\"rec\"].values\nsubmission = submission.reset_index()\nsubmission.to_csv(\"./submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:18:23.819755Z","iopub.status.idle":"2023-01-17T06:18:23.820320Z","shell.execute_reply.started":"2023-01-17T06:18:23.820037Z","shell.execute_reply":"2023-01-17T06:18:23.820065Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission","metadata":{"execution":{"iopub.status.busy":"2023-01-17T06:18:23.821873Z","iopub.status.idle":"2023-01-17T06:18:23.822266Z","shell.execute_reply.started":"2023-01-17T06:18:23.822082Z","shell.execute_reply":"2023-01-17T06:18:23.822100Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}