{"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":"code","source":"import os\nimport pickle\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport torch\nimport torch.nn as nn\n\nfrom sklearn.preprocessing import LabelEncoder","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-05-08T13:52:27.240942Z","iopub.execute_input":"2022-05-08T13:52:27.241488Z","iopub.status.idle":"2022-05-08T13:52:27.246114Z","shell.execute_reply.started":"2022-05-08T13:52:27.241451Z","shell.execute_reply":"2022-05-08T13:52:27.245306Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"seed=22232\ntorch.manual_seed(seed)\ntorch.cuda.manual_seed_all(seed)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:52:27.250466Z","iopub.execute_input":"2022-05-08T13:52:27.250737Z","iopub.status.idle":"2022-05-08T13:52:27.258601Z","shell.execute_reply.started":"2022-05-08T13:52:27.250706Z","shell.execute_reply":"2022-05-08T13:52:27.257872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(device)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:52:27.279835Z","iopub.execute_input":"2022-05-08T13:52:27.280261Z","iopub.status.idle":"2022-05-08T13:52:27.286168Z","shell.execute_reply.started":"2022-05-08T13:52:27.280229Z","shell.execute_reply":"2022-05-08T13:52:27.285144Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_obj(filepath):\n    obj=None\n    with open(filepath, 'rb') as file:\n        obj = pickle.load(file)\n    return obj\n\ndef save_obj(obj, filepath):\n    with open(filepath, 'wb') as file:\n        pickle.dump(obj, file)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:52:27.386954Z","iopub.execute_input":"2022-05-08T13:52:27.387321Z","iopub.status.idle":"2022-05-08T13:52:27.392722Z","shell.execute_reply.started":"2022-05-08T13:52:27.387285Z","shell.execute_reply":"2022-05-08T13:52:27.391943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# loading cooccurance matrices","metadata":{}},{"cell_type":"code","source":"%%time\nprod_group_cooccur_matrix = np.load(\"../input/hm-cooccurance-matrix-dataset/prod_group_cooccur_matrix.npy\")\nprod_type_cooccur_matrix = np.load(\"../input/hm-cooccurance-matrix-dataset/prod_type_cooccur_matrix.npy\")\ngarment_cooccur_matrix = np.load(\"../input/hm-cooccurance-matrix-dataset/garment_cooccur_matrix.npy\")\nsection_cooccur_matrix = np.load(\"../input/hm-cooccurance-matrix-dataset/section_cooccur_matrix.npy\")\n\n","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:52:27.394332Z","iopub.execute_input":"2022-05-08T13:52:27.394836Z","iopub.status.idle":"2022-05-08T13:52:27.495561Z","shell.execute_reply.started":"2022-05-08T13:52:27.394802Z","shell.execute_reply":"2022-05-08T13:52:27.494645Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\ntrain_df = pd.read_pickle(\"../input/hmdataset-candidate-generation/train_df.pkl\")\narticle_df = pd.read_pickle(\"../input/hmdataset-candidate-generation/article_df.pkl\")\narticle_info = load_obj(\"../input/hmdataset-candidate-generation/article_info.pkl\")\n\ntrain_df['num_unique_weeks'] = train_df.week.apply(lambda lst: len(set(lst)))\ntrain_df.head()","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:52:27.499162Z","iopub.execute_input":"2022-05-08T13:52:27.499436Z","iopub.status.idle":"2022-05-08T13:52:42.500779Z","shell.execute_reply.started":"2022-05-08T13:52:27.499402Z","shell.execute_reply":"2022-05-08T13:52:42.499887Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\ncandidate_map = np.load(\"../input/hm-train-candidate-for-ranking/candidate_map.npy\")\nprint(\"candidate_map:\", candidate_map.shape)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:52:42.501968Z","iopub.execute_input":"2022-05-08T13:52:42.502277Z","iopub.status.idle":"2022-05-08T13:52:48.986592Z","shell.execute_reply.started":"2022-05-08T13:52:42.502245Z","shell.execute_reply":"2022-05-08T13:52:48.985355Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Config","metadata":{}},{"cell_type":"code","source":"class config:\n    NUM_EPOCHS = 20\n    BATCH_SIZE = 1024\n    VAL_BATCH_SIZE = 128\n    \n    NUM_NEGATIVE_SAMPLES = 200\n    MAX_TARGETS = 10\n    MAX_SEQ_LEN = 64\n    \n    NUM_ARTICLES = article_df.article_id.nunique()\n    NUM_PROD_TYPES = article_df.product_type_name.nunique()\n    NUM_PROD_GROUPS = article_df.product_group_name.nunique()\n    NUM_SECTIONS = article_df.section_name.nunique()\n    NUM_GARMENTS = article_df.garment_group_name.nunique()\n    NUM_SALE_CHANNELS = 2\n    \n    PAD_ARTICLE_ID = article_df.article_id.nunique()\n    PAD_PROD_TYPE = article_df.product_type_name.nunique()\n    PAD_PROD_GROUP = article_df.product_group_name.nunique()\n    PAD_SECTION = article_df.section_name.nunique()\n    PAD_GARMENT_GROUP = article_df.garment_group_name.nunique()\n    PAD_SALES_CHANNEL = 2","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:02.017605Z","iopub.execute_input":"2022-05-08T13:53:02.017891Z","iopub.status.idle":"2022-05-08T13:53:02.034215Z","shell.execute_reply.started":"2022-05-08T13:53:02.017862Z","shell.execute_reply":"2022-05-08T13:53:02.032952Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"padding_map = {\n    'price': 0,\n    \"sales_channel_id\": 2,\n    'article_id': config.PAD_ARTICLE_ID,\n    'product_type_name': config.PAD_PROD_TYPE,\n    'product_group_name': config.PAD_PROD_GROUP,\n    'section_name':config.PAD_SECTION,\n    'garment_group_name': config.PAD_GARMENT_GROUP\n}","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:02.035991Z","iopub.execute_input":"2022-05-08T13:53:02.036430Z","iopub.status.idle":"2022-05-08T13:53:02.041427Z","shell.execute_reply.started":"2022-05-08T13:53:02.036392Z","shell.execute_reply":"2022-05-08T13:53:02.040612Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataset","metadata":{}},{"cell_type":"code","source":"class HMDataset(torch.utils.data.Dataset):\n    def __init__(self, df, phase='train'):\n        self.df = df\n        self.phase = phase\n    \n    def get_article_metadata(self, article_ids):\n        product_type_name = [ article_info[article_id]['product_type_name'] for article_id in article_ids]\n        product_group_name = [ article_info[article_id]['product_group_name'] for article_id in article_ids]\n        section_name = [ article_info[article_id]['section_name'] for article_id in article_ids]\n        garment_group_name = [ article_info[article_id]['garment_group_name'] for article_id in article_ids]\n        \n        product_type_name = product_type_name[-config.MAX_SEQ_LEN:]\n        product_group_name = product_group_name[-config.MAX_SEQ_LEN:]\n        section_name = section_name[-config.MAX_SEQ_LEN:]\n        garment_group_name = garment_group_name[-config.MAX_SEQ_LEN:]\n        \n        return (product_type_name, product_group_name, section_name, garment_group_name)\n    \n    def get_price_details(self, prices):\n        price_feats = np.zeros(15)\n        \n        min_price = np.min(prices)\n        max_price = np.max(prices)\n        mean_price = np.mean(prices)\n        price_diff1 = np.abs(mean_price - min_price)\n        price_diff2 = np.abs(max_price - mean_price)\n        \n        price_feats[0] = min_price\n        price_feats[1] = max_price\n        price_feats[2] = mean_price\n        price_feats[3] = price_diff1\n        price_feats[4] = price_diff2\n        \n        price_feats[5] = np.sqrt(min_price)\n        price_feats[6] = np.sqrt(max_price)\n        price_feats[7] = np.sqrt(mean_price)\n        price_feats[8] = np.sqrt(price_diff1)\n        price_feats[9] = np.sqrt(price_diff2)\n        \n        price_feats[10] = np.square(min_price)\n        price_feats[11] = np.square(max_price)\n        price_feats[12] = np.square(mean_price)\n        price_feats[13] = np.square(price_diff1)\n        price_feats[14] = np.square(price_diff2)\n        \n        price_feats = price_feats+1e-9\n        return price_feats\n    \n    \n    def get_popularity_details(self, row):\n        week = row.week[-config.MAX_SEQ_LEN:]\n        article_ids = row.article_id[-config.MAX_SEQ_LEN:]\n        seqlen = len(week)\n        popularities = []\n        \n        for i in range(seqlen):\n            popularities.append( article_info[ article_ids[i] ][ week[i] ]['normalized_popularity'] )\n        \n        min_popularity = np.min(popularities)\n        max_popularity = np.max(popularities)\n        mean_popularity = np.mean(popularities)\n        popularity_diff1 = np.abs(mean_popularity - min_popularity)\n        popularity_diff2 = np.abs(max_popularity - mean_popularity)\n        \n        popularity_feats=np.zeros(15)\n        popularity_feats[0] = min_popularity\n        popularity_feats[1] = max_popularity\n        popularity_feats[2] = mean_popularity\n        popularity_feats[3] = popularity_diff1\n        popularity_feats[4] = popularity_diff2\n        \n        popularity_feats[5] = np.sqrt(min_popularity)\n        popularity_feats[6] = np.sqrt(max_popularity)\n        popularity_feats[7] = np.sqrt(mean_popularity)\n        popularity_feats[8] = np.sqrt(popularity_diff1)\n        popularity_feats[9] = np.sqrt(popularity_diff2)\n        \n        popularity_feats[10] = np.square(min_popularity)\n        popularity_feats[11] = np.square(max_popularity)\n        popularity_feats[12] = np.square(mean_popularity)\n        popularity_feats[13] = np.square(popularity_diff1)\n        popularity_feats[14] = np.square(popularity_diff2)\n        \n        popularity_feats = popularity_feats+1e-9\n        return popularity_feats\n    \n    def get_cooccurrence_feats(self, weeks, article_ids, last_week):\n        week_diff = np.array(weeks) - last_week\n        week_diff = np.clip(week_diff, 0, 38)\n        \n        seqlen = len(week_diff)\n        product_type_name = [ article_info[article_id]['product_type_name'] for article_id in article_ids]\n        product_group_name = [ article_info[article_id]['product_group_name'] for article_id in article_ids]\n        section_name = [ article_info[article_id]['section_name'] for article_id in article_ids]\n        garment_group_name = [ article_info[article_id]['garment_group_name'] for article_id in article_ids]\n        \n        \n        prodtype_feats=np.zeros(config.NUM_PROD_TYPES)\n        prodgroup_feats = np.zeros(config.NUM_PROD_GROUPS)\n        section_feats = np.zeros(config.NUM_SECTIONS)\n        garment_feats = np.zeros(config.NUM_GARMENTS)\n        \n        for i in range(len(week_diff)):\n            w = week_diff[i]\n            week_product_type = product_type_name[i]\n            week_product_group = product_group_name[i]\n            week_section_name = section_name[i]\n            week_garment = garment_group_name[i]\n            \n            \n            prodtype_feats += prod_type_cooccur_matrix[week_product_type, w, :]\n            prodgroup_feats += prod_group_cooccur_matrix[week_product_group, w, :]\n            section_feats += section_cooccur_matrix[week_section_name, w, :]\n            garment_feats += garment_cooccur_matrix[week_garment, w, :]\n        \n        \n        prodtype_feats = prodtype_feats/max(1, prodtype_feats.sum())\n        prodgroup_feats = prodgroup_feats/max(1, prodgroup_feats.sum())\n        section_feats = section_feats/max(1, section_feats.sum())\n        garment_feats = garment_feats/max(1, garment_feats.sum())\n        \n        cooccur_feats = np.concatenate([prodtype_feats, prodgroup_feats, section_feats , garment_feats])\n        return cooccur_feats\n        \n    \n    def get_inputs(self, idx, last_week_idx, last_week):\n        row = self.df.iloc[idx]\n        article_id = row.article_id.copy()[:last_week_idx]\n        price = row.price.copy()[:last_week_idx]\n        sales_channel_id = row.sales_channel_id.copy()[:last_week_idx]\n        week = row.week.copy()[:last_week_idx]\n        \n        \n        price_feats = self.get_price_details(price)\n        popularity_feats = self.get_popularity_details(row)\n        cooccurrence_feats = self.get_cooccurrence_feats(week, article_id, last_week)\n        (product_type_name, product_group_name, section_name, garment_group_name) = self.get_article_metadata(article_id)\n        \n        inputs = {\n            \"sales_channel_id\": sales_channel_id,\n            'article_id':article_id,\n            'product_type_name': product_type_name,\n            'product_group_name': product_group_name,\n            'section_name':section_name,\n            'garment_group_name': garment_group_name,\n        }\n        return inputs, price_feats, popularity_feats, cooccurrence_feats\n    \n    def pad_sequence(self, x, max_seqlen, padid):\n        seqdiff = max_seqlen - len(x)\n        if seqdiff == 0:\n            return x\n        x = x + [padid]*seqdiff\n        return x\n    \n    def get_targets(self, article_ids, last_week_idx, week):\n        next_week_idx = last_week_idx+1\n        seqlen = len(week)\n        while next_week_idx < seqlen and week[next_week_idx]==week[last_week_idx]:\n            next_week_idx+=1\n        targets = article_ids[last_week_idx: next_week_idx].copy()\n        targets = list(set(targets))\n        return targets\n        \n    def get_inputs_perweekid(self, idx, last_week, week):\n        last_week_idx = week.index(last_week)\n        if last_week_idx == 0:\n            last_week_idx = -1\n        \n        inputs, price_feats, popularity_feats, cooccurrence_feats = self.get_inputs(idx, last_week_idx, last_week)\n        targets = self.get_targets(self.df.iloc[idx]['article_id'], last_week_idx, week)\n        for k,v in inputs.items():\n            inputs[k] = v[-config.MAX_SEQ_LEN:]\n            \n        \n        seqlen = len(inputs['article_id'])\n        for k,v in inputs.items():\n            inputs[k] = self.pad_sequence(v, config.MAX_SEQ_LEN, padding_map[k])\n        \n        inputs['seqlen'] = seqlen\n        #Convert To Tensors\n        price_feats = torch.tensor(price_feats, dtype=torch.float32)\n        popularity_feats = torch.tensor(popularity_feats, dtype=torch.float32)\n        cooccurrence_feats = torch.tensor(cooccurrence_feats, dtype=torch.float32)\n        for k,v in inputs.items():\n            inputs[k] = torch.tensor(v, dtype=torch.long)\n        \n        targets = targets.copy()\n        np.random.seed(np.random.randint(1, 10000))\n        np.random.shuffle(targets)\n        targets = targets[:config.MAX_TARGETS]\n        targets = targets + [config.PAD_ARTICLE_ID]*(config.MAX_TARGETS - len(targets))\n        targets = torch.tensor(targets, dtype=torch.long)\n        return (inputs, targets, price_feats, popularity_feats, cooccurrence_feats)\n    \n    def get_train_inputs(self, idx):\n        row  = self.df.iloc[idx]\n        user_id = row.customer_id\n        \n        week = row.week[-config.MAX_SEQ_LEN:].copy()\n        num_weeks = len(set(week))\n        unique_weeks = sorted(set(week), reverse=True)\n        \n        all_inputs = []\n        all_targets = []\n        all_prices = []\n        all_popularities = []\n        all_cooccurences=[]\n        all_trainable = []\n        \n        negsamples = candidate_map[user_id]\n        for _ in range(1):\n            is_trainable = torch.tensor(0.0, dtype=torch.long)\n            cur_inputs = {\n                'seqlen': torch.tensor(0, dtype=torch.long),\n                \"sales_channel_id\": torch.full((config.MAX_SEQ_LEN, ), config.PAD_SALES_CHANNEL),\n                'article_id': torch.full((config.MAX_SEQ_LEN, ), config.PAD_ARTICLE_ID),\n                'product_type_name': torch.full((config.MAX_SEQ_LEN, ), config.PAD_PROD_TYPE),\n                'product_group_name': torch.full((config.MAX_SEQ_LEN, ), config.PAD_PROD_GROUP),\n                'section_name': torch.full((config.MAX_SEQ_LEN, ), config.PAD_SECTION),\n                'garment_group_name': torch.full((config.MAX_SEQ_LEN, ), config.PAD_GARMENT_GROUP)\n            }\n            cur_target = torch.full((config.MAX_TARGETS, ), config.PAD_ARTICLE_ID)\n            cur_price_feats = torch.zeros(15)\n            cur_popularity_feats = torch.zeros(15)\n            cur_cooccurence_feats = torch.zeros(217)\n            \n            all_inputs.append(cur_inputs)\n            all_prices.append(cur_price_feats)\n            all_popularities.append(cur_popularity_feats)\n            all_cooccurences.append(cur_cooccurence_feats)\n            all_targets.append(cur_target)\n            all_trainable.append(is_trainable)\n            \n        \n        #if num_weeks<=10:\n        (inputs, targets, price_feats, popularity_feats, cooccurrence_feats) = self.get_inputs_perweekid(idx, unique_weeks[-1], week)\n        all_inputs[0] = inputs\n        all_targets[0] = targets\n        all_prices[0] = price_feats\n        all_popularities[0] = popularity_feats\n        all_cooccurences[0] = cooccurrence_feats\n        all_trainable[0] = torch.tensor(1.0, dtype=torch.long)\n            \n        #else:\n        #    train_set_idx=0\n        #    for k in range(num_weeks-1, max(2, num_weeks-2-1), -1):\n        #        (inputs, targets, price_feats, popularity_feats, cooccurrence_feats) = self.get_inputs_perweekid(idx, unique_weeks[k], week)\n                \n        #        all_inputs[train_set_idx] = inputs\n        #        all_targets[train_set_idx] = targets\n        #        all_prices[train_set_idx] = price_feats\n        #        all_popularities[train_set_idx] = popularity_feats\n        #        all_cooccurences[train_set_idx] = cooccurrence_feats\n        #        all_trainable[train_set_idx] = torch.tensor(1.0, dtype=torch.long)\n        #        train_set_idx += 1\n        return (all_inputs, all_prices, all_popularities, all_cooccurences, all_targets, all_trainable, negsamples)\n    \n    \n    def get_val_inputs(self, idx):\n        row = self.df.iloc[idx]\n        user_id = row.customer_id\n        \n        negsamples = candidate_map[user_id]\n        article_id = row.article_id.copy()[-config.MAX_SEQ_LEN:]\n        sales_channel_id = row.sales_channel_id.copy()[-config.MAX_SEQ_LEN:]\n        price = row.price.copy()[-config.MAX_SEQ_LEN:]\n        week = row.week.copy()[-config.MAX_SEQ_LEN:]\n        \n        price_feats = self.get_price_details(price)\n        popularity_feats = self.get_popularity_details(row)\n        cooccurrence_feats = self.get_cooccurrence_feats(week, article_id, 0)\n        (product_type_name, product_group_name, section_name, garment_group_name) = self.get_article_metadata(article_id)\n        \n        inputs = {\n            \"sales_channel_id\": sales_channel_id,\n            'article_id':article_id,\n            'product_type_name': product_type_name,\n            'product_group_name': product_group_name,\n            'section_name':section_name,\n            'garment_group_name': garment_group_name,\n        }\n        \n        seqlen = len(inputs['article_id'])\n        for k,v in inputs.items():\n            inputs[k] = self.pad_sequence(v, config.MAX_SEQ_LEN, padding_map[k])\n        \n        inputs['seqlen'] = seqlen\n        price_feats = torch.tensor(price_feats, dtype=torch.float32)\n        popularity_feats = torch.tensor(popularity_feats, dtype=torch.float32)\n        cooccurrence_feats = torch.tensor(cooccurrence_feats, dtype=torch.float32)\n        for k,v in inputs.items():\n            inputs[k] = torch.tensor(v, dtype=torch.long)\n        \n        \n        negsamples = torch.tensor(negsamples, dtype=torch.long)\n        targets = row.val_article_ids\n        targets = list(set(targets.copy()))\n        targets = targets[:config.MAX_TARGETS]\n        targets = targets + [config.PAD_ARTICLE_ID]*(config.MAX_TARGETS - len(targets))\n        targets = torch.tensor(targets, dtype=torch.long)\n        return (inputs, targets, price_feats, popularity_feats, cooccurrence_feats, negsamples)\n        \n    def __getitem__(self, idx):\n        if self.phase == 'train':\n            (all_inputs, all_prices, all_popularities, all_cooccurences, all_targets, all_trainable, negsamples) = self.get_train_inputs(idx)\n            negsamples = torch.tensor(negsamples, dtype=torch.long)\n            return (all_inputs, all_prices, all_popularities, all_cooccurences, all_targets, all_trainable, negsamples)\n        else:\n            (inputs, targets, price_feats, popularity_feats, cooccurrence_feats, negsamples) = self.get_val_inputs(idx)\n            return (inputs, price_feats, popularity_feats, cooccurrence_feats, negsamples, targets)\n    \n    def __len__(self):\n        return len(self.df)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:02.301835Z","iopub.execute_input":"2022-05-08T13:53:02.302111Z","iopub.status.idle":"2022-05-08T13:53:02.372959Z","shell.execute_reply.started":"2022-05-08T13:53:02.302081Z","shell.execute_reply":"2022-05-08T13:53:02.372089Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# model","metadata":{}},{"cell_type":"code","source":"class Embedding(nn.Module):\n    def __init__(self):\n        super(Embedding, self).__init__()\n        self.article_embeddings = nn.Embedding(1+config.NUM_ARTICLES, 256 , padding_idx=config.PAD_ARTICLE_ID)\n        self.prod_type_embeddings = nn.Embedding(1+config.NUM_PROD_TYPES, 32, padding_idx=config.PAD_PROD_TYPE)\n        self.prodgroup_embeddings = nn.Embedding(1+config.NUM_PROD_GROUPS, 32 ,padding_idx=config.PAD_PROD_GROUP)\n        \n        self.section_embeddings = nn.Embedding(1+config.NUM_SECTIONS, 32, padding_idx=config.PAD_SECTION)\n        self.garment_embeddings = nn.Embedding(1+config.NUM_GARMENTS, 32, padding_idx=config.PAD_GARMENT_GROUP)\n        self.sales_embeddings   = nn.Embedding(1+config.NUM_SALE_CHANNELS, 32, padding_idx=config.PAD_SALES_CHANNEL)\n        \n        self.dropout = nn.Dropout(0.1)\n    \n    def get_article_embeddings(self, article_ids):\n        return self.article_embeddings(article_ids)\n    \n    def forward(self, inputs):\n        xarticles = self.article_embeddings(inputs['article_id'])\n        xprod_type = self.prod_type_embeddings(inputs['product_type_name'])\n        xprod_group = self.prodgroup_embeddings(inputs['product_group_name'])\n        xsection = self.section_embeddings(inputs['section_name'])\n        xgarment = self.garment_embeddings(inputs['garment_group_name'])\n        xsales_channel = self.sales_embeddings(inputs['sales_channel_id'])\n        \n        x = torch.cat([xarticles, xprod_type, xprod_group, xsection, xgarment, xsales_channel], dim=-1)\n        x = self.dropout(x)\n        x = torch.sum(x, dim=1)\n        return x","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:02.374965Z","iopub.execute_input":"2022-05-08T13:53:02.375856Z","iopub.status.idle":"2022-05-08T13:53:02.391326Z","shell.execute_reply.started":"2022-05-08T13:53:02.375801Z","shell.execute_reply":"2022-05-08T13:53:02.390156Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class HistoryEncoder(nn.Module):\n    def __init__(self):\n        super(HistoryEncoder, self).__init__()\n        self.mlp = nn.Sequential(\n            nn.Dropout(0.1),\n            nn.Linear(446+217, 1024),\n            nn.LeakyReLU(),\n            nn.BatchNorm1d(1024),\n            nn.Dropout(0.2),\n            \n            nn.Linear(1024, 512),\n            nn.LeakyReLU(),\n            nn.BatchNorm1d(512),\n            nn.Dropout(0.15),\n            \n            nn.Linear(512, 256),\n            nn.LeakyReLU()\n        )\n        \n    def forward(self, x, inputs, price_feats, popularity_feats, cooccur_feats):\n        seqlen = inputs['seqlen'].unsqueeze(dim=-1)\n        x = x * (1/seqlen)\n        x = torch.cat([x, price_feats, popularity_feats, cooccur_feats], dim=-1)\n        x = self.mlp(x)\n        return x","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:02.393150Z","iopub.execute_input":"2022-05-08T13:53:02.393488Z","iopub.status.idle":"2022-05-08T13:53:02.416504Z","shell.execute_reply.started":"2022-05-08T13:53:02.393444Z","shell.execute_reply":"2022-05-08T13:53:02.415284Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Model(nn.Module):\n    def __init__(self):\n        super(Model, self).__init__()\n        self.embeddings = Embedding()\n        self.history_encoder = HistoryEncoder()\n        self.n_sqrt = np.sqrt(256)\n    \n    def get_article_embeddings(self, article_ids):\n        return self.embeddings.article_embeddings(article_ids)\n    \n    def get_history_embeddings(self, inputs, price_feats, popularity_feats, cooccur_feats):\n        x = self.embeddings(inputs)\n        h = self.history_encoder(x, inputs, price_feats, popularity_feats, cooccur_feats)\n        return h\n    \n    def get_logits(self, h, v):\n        z = h.unsqueeze(dim=1) * v\n        z = z.sum(dim=-1)\n        z = z/self.n_sqrt\n        return z\n    \n    def forward(self, inputs, price_feats, popularity_feats, cooccur_feats, targets):\n        h = self.get_history_embeddings(inputs, price_feats, popularity_feats, cooccur_feats)\n        v = self.embeddings.get_article_embeddings(targets)\n        z = self.get_logits(h, v)\n        return (h, v, z)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:02.418143Z","iopub.execute_input":"2022-05-08T13:53:02.418401Z","iopub.status.idle":"2022-05-08T13:53:02.431642Z","shell.execute_reply.started":"2022-05-08T13:53:02.418355Z","shell.execute_reply":"2022-05-08T13:53:02.430667Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# evaluation","metadata":{}},{"cell_type":"code","source":"def evaluate(val_dataloader, model):\n    model.eval()\n    total_pos = 0.0\n    rank_5 = 0.0\n    rank_10 = 0.0\n    rank_50 = 0.0\n    \n    n_sqrt = np.sqrt(256)\n    for it, (inputs, price_feats, popularity_feats, cooccurrence_feats, negsamples, targets) in enumerate(val_dataloader):\n        negsamples = negsamples.to(device)\n        targets = targets.to(device)\n        for k,v in inputs.items():\n            inputs[k] = v.to(device)\n        \n        price_feats = price_feats.to(device)\n        popularity_feats = popularity_feats.to(device)\n        cooccurrence_feats = cooccurrence_feats.to(device)\n        \n        with torch.no_grad():\n            u = model.get_article_embeddings(negsamples)\n            (h, v, zpos) = model(inputs, price_feats, popularity_feats, cooccurrence_feats, targets)\n            \n            z = (h.unsqueeze(dim=1) * u).sum(dim=-1)\n            z = z/n_sqrt\n            for i in range(config.MAX_TARGETS):\n                z1 = zpos[:, i].unsqueeze(dim=-1)\n                label = targets[:, i]\n                z1 = z1[label!=config.PAD_ARTICLE_ID]\n                \n                score_diff = z1 - z[label!=config.PAD_ARTICLE_ID, :]\n                score_diff = (score_diff < 0).sum(dim=-1)\n                \n                rank_5 += (score_diff<=5).sum().item()\n                rank_10 += (score_diff<=10).sum().item()\n                rank_50 += (score_diff<=50).sum().item()\n                total_pos += len(score_diff)\n    rank_5 /= total_pos\n    rank_10 /= total_pos\n    rank_50 /= total_pos\n    return rank_5, rank_10, rank_50","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:30.974347Z","iopub.execute_input":"2022-05-08T13:53:30.975256Z","iopub.status.idle":"2022-05-08T13:53:30.988595Z","shell.execute_reply.started":"2022-05-08T13:53:30.975216Z","shell.execute_reply":"2022-05-08T13:53:30.987530Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# train epoch","metadata":{}},{"cell_type":"code","source":"def train_ops(is_trainable, inputs, price_feats, popularity_feats, cooccur_feats, targets, negsamples, model):\n    is_trainable = is_trainable.to(bool)\n    loss=torch.tensor(0.0, device=device)\n    if is_trainable.sum().item() <= 5:\n        return loss\n    \n    batch_max_seqlen = inputs['seqlen'].max()\n    for k,v in inputs.items():\n        if k=='seqlen':\n            continue\n        inputs[k]=v[:, :batch_max_seqlen]\n    for k,v in inputs.items():\n        inputs[k]=v[is_trainable].to(device)\n    \n    price_feats = price_feats[is_trainable].to(device)\n    popularity_feats = popularity_feats[is_trainable].to(device)\n    cooccur_feats = cooccur_feats[is_trainable].to(device)\n    targets = targets[is_trainable].to(device)\n    negsamples = negsamples[is_trainable].to(device)\n    \n    vneg = model.get_article_embeddings(negsamples)\n    (h, v, zpos) = model(inputs, price_feats, popularity_feats, cooccur_feats, targets)\n    zneg = model.get_logits(h, vneg)\n\n    loss = torch.tensor(0.0, device=device)\n    total_sample_count=0\n    for i in range(config.MAX_TARGETS):\n        pos_score = zpos[:, i].unsqueeze(-1)\n        bpe_loss = -torch.log(torch.sigmoid(pos_score - zneg)).mean(dim=-1)\n        mask = (targets[:, i]!=config.PAD_ARTICLE_ID)\n        loss += bpe_loss[mask].sum()\n        total_sample_count+=mask.sum()\n    \n    loss = loss/total_sample_count\n    h_norm = torch.abs(torch.norm(h, dim=-1) - 1).mean()\n    loss = loss + 1e-3 * h_norm    \n    return loss, h_norm","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:31.020458Z","iopub.execute_input":"2022-05-08T13:53:31.021285Z","iopub.status.idle":"2022-05-08T13:53:31.034235Z","shell.execute_reply.started":"2022-05-08T13:53:31.021249Z","shell.execute_reply":"2022-05-08T13:53:31.033100Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_epoch(train_dataloader, model, optimizer, schedular):\n    epoch_loss=[]\n    epoch_norm = []\n    model.train()\n    for it, (all_inputs, all_prices, all_popularities, all_cooccurences, all_targets, all_trainable, negsamples) in enumerate(train_dataloader):\n        batch_losses = []\n        batch_norms = []\n        \n        for train_id in range(len(all_inputs)):\n            inputs = all_inputs[train_id]\n            price_feats = all_prices[train_id]\n            popularity_feats = all_popularities[train_id]\n            cooccur_feats = all_cooccurences[train_id]\n            targets = all_targets[train_id]\n            is_trainable = all_trainable[train_id]\n            cur_loss,cur_norm = train_ops(is_trainable, inputs, price_feats, popularity_feats, cooccur_feats, targets, negsamples, model)\n            \n            batch_losses.append(cur_loss)\n            batch_norms.append(cur_norm)\n        \n        loss = (batch_losses[0])# + 0.2 * batch_losses[1])/2\n        h_norm = (batch_norms[0])# + batch_norms[1])/2\n        \n        model.zero_grad(set_to_none=True)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), 10)\n        optimizer.step()\n        schedular.step()\n        \n        epoch_loss.append(loss.item())\n        epoch_norm.append(h_norm.item())\n        \n        if it%200 == 0:\n            print(\"iteration:{} | loss: {:.4f} | hnorm:{:.4f}\".format(it, np.mean(epoch_loss), np.mean(epoch_norm) ))\n    return np.mean(epoch_loss), np.mean(epoch_norm)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:31.077726Z","iopub.execute_input":"2022-05-08T13:53:31.078543Z","iopub.status.idle":"2022-05-08T13:53:31.089967Z","shell.execute_reply.started":"2022-05-08T13:53:31.078486Z","shell.execute_reply":"2022-05-08T13:53:31.088876Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# train setup","metadata":{}},{"cell_type":"code","source":"train_dataset = HMDataset(train_df)\nval_dataset = HMDataset(train_df[train_df.has_val==1], phase='eval')\n\n\ntrain_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=config.BATCH_SIZE, \n                                               shuffle=True,\n                                               pin_memory=True, drop_last=True)\n\nval_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=config.VAL_BATCH_SIZE, \n                                             shuffle=False, pin_memory=True, drop_last=False)\n\nprint(\"number of train iterations:\", len(train_dataloader))\nprint(\"number of val iteration:\", len(val_dataloader))\n\nmodel = Model().to(device)\noptimizer=torch.optim.AdamW(model.parameters(), lr=5e-3, weight_decay=1e-4)\nschedular = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    (config.NUM_EPOCHS * len(train_dataloader))+10,\n    eta_min = 1e-6\n)","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:31.143205Z","iopub.execute_input":"2022-05-08T13:53:31.143818Z","iopub.status.idle":"2022-05-08T13:53:31.308124Z","shell.execute_reply.started":"2022-05-08T13:53:31.143778Z","shell.execute_reply":"2022-05-08T13:53:31.307291Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# train model","metadata":{}},{"cell_type":"code","source":"%%time\nbest_rank5 = None\nbest_rank10 = None\nbest_rank50  = None\n\nfor e in range(config.NUM_EPOCHS):\n    epoch_loss, epoch_norm = train_epoch(train_dataloader, model, optimizer, schedular)\n    print(\"epoch: {} | train loss: {:.4f} | norm:{:.4f}\".format(e, epoch_loss, epoch_norm))\n    (rank_5, rank_10, rank_50) = evaluate(val_dataloader, model)\n    \n    if best_rank5 is None or rank_5 > best_rank5:\n        best_rank5 = rank_5\n        torch.save(model, \"model_best_rank_05.pt\")\n    \n    if best_rank10 is None or rank_10 > best_rank10:\n        best_rank10 = rank_10\n        torch.save(model, \"model_best_rank_10.pt\")\n        \n    if best_rank50 is None or rank_50 > best_rank50:\n        best_rank50 = rank_50\n        torch.save(model, \"model_best_rank_50.pt\")\n    \n    torch.save(model, \"model.pt\")\n    print(\"====================\")\n    print()\n    print(\"precision_05:{:.4f} | precision_10:{:.4f} | precision_50:{:.4f}\".format(rank_5, rank_10, rank_50))\n    print()\n    print(\"best rank05:{:.4f} | best rank10:{:.4f} | best rank50:{:.4f}\".format(best_rank5, best_rank10, best_rank50))","metadata":{"execution":{"iopub.status.busy":"2022-05-08T13:53:35.991840Z","iopub.execute_input":"2022-05-08T13:53:35.992316Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}