In [1]:
import warnings
warnings.filterwarnings('ignore')
In [2]:
import mxnet as mx
import pandas as pd
import tqdm

from typing import List, Tuple
from mxnet import gluon
from mxnet.gluon import nn
from pathlib import Path
from category_encoders import OrdinalEncoder
from model.mtmo_mx import MTMO, MTMO_base
from model.dataset_mx import MtmoDataset
from mxnet import npx
from mxnet import nd
from mxnet.gluon.data import DataLoader


WORK_DIR = Path().resolve()

def load_dataset() -> pd.DataFrame:
    """
    Load training inputs.
    
    Returns:
        pd.DataFrame: pandas dataframe
    """
    df = pd.read_csv("./data/bp_cvr_ctrl.csv", index_col=False)
    return df

def obtain_embedding_layer_size(df: pd.DataFrame, embedding_layer_dim: int) -> List[Tuple[int, int]]:
    """
    Obtain embedding_layer size

    Args:
        df (pd.DataFrame): Training dataset
        embedding_layer_dim (int): Number of embedded dimensions
    Returns:
        List[Tuple[int, int]]: List of (Unique number of categories, embedding_dim)
    """
    df_feature = df.drop(columns=['biddingprice', 'conversion', "control"])

    # Get embedding layer size
    max_idxs = list(df_feature.max())
    embedding_layer_size = []
    for i in max_idxs:
        embedding_layer_size.append((int(i + 1), embedding_layer_dim))

    return embedding_layer_size

def train_mtmo(df: pd.DataFrame) -> None:
    """
    Train MTMO.

    Args:
        df (pd.DataFrame): Encoded dataset
    """
    #num_gpus = npx.num_gpus()
    #ctx = mx.gpu(gpu)
    #ctx = mx.cpu()

    #test dataloader
    dataset_test = MtmoDataset(df[df["control"] == 0])
    train_dataloader_test = mx.gluon.data.DataLoader(dataset_test, batch_size=16, shuffle=True)
    
    #ctrl dataloader
    dataset_ctrl = MtmoDataset(df[df["control"] == 1])
    train_dataloader_ctrl = mx.gluon.data.DataLoader(dataset_ctrl, batch_size=16, shuffle=True)
    
    #model
    embedding_layer_size = obtain_embedding_layer_size(df, 5)
    model = MTMO_base()
    
    #loss functions, optimizers
    loss_fn_cls = gluon.loss.SigmoidBCELoss()
    loss_fn_reg = gluon.loss.L2Loss()
    optimizer_test = gluon.Trainer(model.collect_params(), 'sgd', {'learning_rate': 0.01})
    optimizer_ctrl = gluon.Trainer(model.collect_params(), 'adam', {'learning_rate': 0.001})
    batch_size = 16
    epochs = 30

    #training
    model.hybridize()
    for epoch in range(epochs):
        tp_total_loss_test = 0.0
        tp_bp_loss_test = 0.0
        tp_cvr_loss_test = 0.0
        
        tp_total_loss_ctrl = 0.0
        tp_bp_loss_ctrl = 0.0
        tp_cvr_loss_ctrl = 0.0
        
        #test task
        for i, (inputs_test, biddingprice_test, conversion_test) in enumerate(train_dataloader_test):
            biddingprice_test_py = nd.expand_dims(biddingprice_test, 1)
            conversion_test_py = nd.expand_dims(conversion_test, 1)
            with mx.autograd.record():
                bp_test, p_cvr_test = model(inputs_test)
                bp_loss_test = loss_fn_reg(bp_test.flatten(), biddingprice_test)
                cvr_loss_test = loss_fn_cls(p_cvr_test, conversion_test)
                total_loss_test = bp_loss_test + cvr_loss_test
                mx.autograd.backward(total_loss_test.nansum())
            optimizer_test.step(batch_size)
            now_lr_test = optimizer_test.learning_rate
            tp_total_loss_test += total_loss_test
            tp_bp_loss_test += bp_loss_test
            tp_cvr_loss_test += cvr_loss_test
            
        tp_total_loss_test = tp_total_loss_test / len(train_dataloader_test)
        tp_bp_loss_test = tp_bp_loss_test / len(train_dataloader_test)
        tp_cvr_loss_test = tp_cvr_loss_test / len(train_dataloader_test)
        print(f'epoch: {epoch+1}, train_loss_TEST_TASK: {tp_total_loss_test}')  
        
        #ctrl task
        for i, (inputs_ctrl, biddingprice_ctrl, conversion_ctrl) in enumerate(train_dataloader_ctrl):
            biddingprice_ctrl_py = nd.expand_dims(biddingprice_ctrl, 1)
            conversion_ctrl_py = nd.expand_dims(conversion_ctrl, 1)
            with mx.autograd.record():
                bp_ctrl, p_cvr_ctrl = model(inputs_ctrl)
                bp_loss_ctrl = loss_fn_reg(bp_ctrl.flatten(), biddingprice_ctrl)
                cvr_loss_ctrl = loss_fn_cls(p_cvr_ctrl, conversion_ctrl)
                total_loss_ctrl = bp_loss_ctrl + cvr_loss_ctrl
                mx.autograd.backward(total_loss_ctrl.nansum())
            optimizer_ctrl.step(batch_size)
            now_lr_ctrl = optimizer_ctrl.learning_rate
            tp_total_loss_ctrl += total_loss_ctrl
            tp_bp_loss_ctrl += bp_loss_ctrl
            tp_cvr_loss_ctrl += cvr_loss_ctrl

        tp_total_loss_ctrl = tp_total_loss_ctrl / len(train_dataloader_ctrl)
        tp_bp_loss_ctrl = tp_bp_loss_ctrl / len(train_dataloader_ctrl)
        tp_cvr_loss_ctrl = tp_cvr_loss_ctrl / len(train_dataloader_ctrl)
        print(f'epoch: {epoch+1}, train_loss_CONTROL_TASK: {tp_total_loss_ctrl}')  
In [3]:
df = load_dataset()
df
Out[3]:
feature1 feature2 feature3 biddingprice conversion control
0 1 1 1 2.01 1 1
1 2 2 2 2.04 0 1
2 3 3 3 1.45 0 0
3 4 4 4 3.45 0 1
4 5 5 1 4.00 1 0
5 1 6 5 1.23 0 0
6 2 7 6 2.32 0 1
In [4]:
category_columns = ['feature1', 'feature2', 'feature3']
encoder = OrdinalEncoder(cols=category_columns, handle_unknown='impute').fit(df)
df = encoder.transform(df)
train_mtmo(df)
epoch: 1, train_loss_TEST_TASK: 
[8.62151   1.7348814 1.4287004]
<NDArray 3 @cpu(0)>
epoch: 1, train_loss_CONTROL_TASK: 
[3.3143055 2.6961389 6.5755267 2.7496517]
<NDArray 4 @cpu(0)>
epoch: 2, train_loss_TEST_TASK: 
[1.7090356 8.558825  1.4016223]
<NDArray 3 @cpu(0)>
epoch: 2, train_loss_CONTROL_TASK: 
[2.722022  6.5049357 3.2557647 2.6794322]
<NDArray 4 @cpu(0)>
epoch: 3, train_loss_TEST_TASK: 
[8.495208  1.3743348 1.6830173]
<NDArray 3 @cpu(0)>
epoch: 3, train_loss_CONTROL_TASK: 
[3.1966267 2.6941004 2.662689  6.4332805]
<NDArray 4 @cpu(0)>
epoch: 4, train_loss_TEST_TASK: 
[1.656776  8.430145  1.3467298]
<NDArray 3 @cpu(0)>
epoch: 4, train_loss_CONTROL_TASK: 
[3.137936  2.6658368 6.360365  2.6458442]
<NDArray 4 @cpu(0)>
epoch: 5, train_loss_TEST_TASK: 
[1.6302229 8.361879  1.3187424]
<NDArray 3 @cpu(0)>
epoch: 5, train_loss_CONTROL_TASK: 
[6.284238  2.628588  2.6366503 3.0766547]
<NDArray 4 @cpu(0)>
epoch: 6, train_loss_TEST_TASK: 
[1.2898209 8.286601  1.6028273]
<NDArray 3 @cpu(0)>
epoch: 6, train_loss_CONTROL_TASK: 
[2.607107  6.2064266 2.6116507 3.0138392]
<NDArray 4 @cpu(0)>
epoch: 7, train_loss_TEST_TASK: 
[8.208974  1.2603858 1.5751398]
<NDArray 3 @cpu(0)>
epoch: 7, train_loss_CONTROL_TASK: 
[2.5771189 2.5944636 2.949321  6.127756 ]
<NDArray 4 @cpu(0)>
epoch: 8, train_loss_TEST_TASK: 
[8.13189   1.2303796 1.5468522]
<NDArray 3 @cpu(0)>
epoch: 8, train_loss_CONTROL_TASK: 
[6.0472555 2.5465946 2.8837414 2.5770898]
<NDArray 4 @cpu(0)>
epoch: 9, train_loss_TEST_TASK: 
[1.1999923 1.5180264 8.052104 ]
<NDArray 3 @cpu(0)>
epoch: 9, train_loss_CONTROL_TASK: 
[5.963794  2.816373  2.5593448 2.5149956]
<NDArray 4 @cpu(0)>
epoch: 10, train_loss_TEST_TASK: 
[1.1688788 1.488097  7.969059 ]
<NDArray 3 @cpu(0)>
epoch: 10, train_loss_CONTROL_TASK: 
[2.7467475 2.4823556 5.8764305 2.5415492]
<NDArray 4 @cpu(0)>
epoch: 11, train_loss_TEST_TASK: 
[7.8825173 1.1370294 1.4573431]
<NDArray 3 @cpu(0)>
epoch: 11, train_loss_CONTROL_TASK: 
[2.6747384 2.4490395 2.5234365 5.785742 ]
<NDArray 4 @cpu(0)>
epoch: 12, train_loss_TEST_TASK: 
[1.42618   7.792291  1.1044526]
<NDArray 3 @cpu(0)>
epoch: 12, train_loss_CONTROL_TASK: 
[2.6003847 2.504911  2.414864  5.6929955]
<NDArray 4 @cpu(0)>
epoch: 13, train_loss_TEST_TASK: 
[1.0712123 7.69821   1.3942409]
<NDArray 3 @cpu(0)>
epoch: 13, train_loss_CONTROL_TASK: 
[2.5235982 5.59688   2.4859056 2.379958 ]
<NDArray 4 @cpu(0)>
epoch: 14, train_loss_TEST_TASK: 
[1.3616843 7.600129  1.0373111]
<NDArray 3 @cpu(0)>
epoch: 14, train_loss_CONTROL_TASK: 
[2.4664032 2.3438663 2.444786  5.4978127]
<NDArray 4 @cpu(0)>
epoch: 15, train_loss_TEST_TASK: 
[1.0030949 7.497924  1.3281574]
<NDArray 3 @cpu(0)>
epoch: 15, train_loss_CONTROL_TASK: 
[5.394723  2.4463882 2.3065438 2.3640413]
<NDArray 4 @cpu(0)>
epoch: 16, train_loss_TEST_TASK: 
[0.9683027 1.2936819 7.391499 ]
<NDArray 3 @cpu(0)>
epoch: 16, train_loss_CONTROL_TASK: 
[2.4258494 5.287532  2.2808514 2.2679763]
<NDArray 4 @cpu(0)>
epoch: 17, train_loss_TEST_TASK: 
[7.2807903  0.93303794 1.2582934 ]
<NDArray 3 @cpu(0)>
epoch: 17, train_loss_CONTROL_TASK: 
[2.4047806 5.176232  2.2281609 2.195344 ]
<NDArray 4 @cpu(0)>
epoch: 18, train_loss_TEST_TASK: 
[1.2220438 0.8974302 7.1657715]
<NDArray 3 @cpu(0)>
epoch: 18, train_loss_CONTROL_TASK: 
[2.107689  2.1871045 5.060853  2.3831804]
<NDArray 4 @cpu(0)>
epoch: 19, train_loss_TEST_TASK: 
[7.0464544  0.86163175 1.1850003 ]
<NDArray 3 @cpu(0)>
epoch: 19, train_loss_CONTROL_TASK: 
[2.3610518 4.9414644 2.0180984 2.1448271]
<NDArray 4 @cpu(0)>
epoch: 20, train_loss_TEST_TASK: 
[1.1472461 0.8258177 6.9228916]
<NDArray 3 @cpu(0)>
epoch: 20, train_loss_CONTROL_TASK: 
[2.1013613 4.81818   2.3384032 1.9268289]
<NDArray 4 @cpu(0)>
epoch: 21, train_loss_TEST_TASK: 
[1.1088824 6.795183  0.7901859]
<NDArray 3 @cpu(0)>
epoch: 21, train_loss_CONTROL_TASK: 
[2.3152504 2.0567534 1.8341818 4.691158 ]
<NDArray 4 @cpu(0)>
epoch: 22, train_loss_TEST_TASK: 
[6.6634746  0.75495607 1.0700263 ]
<NDArray 3 @cpu(0)>
epoch: 22, train_loss_CONTROL_TASK: 
[4.5606027 2.011064  1.740502  2.2916136]
<NDArray 4 @cpu(0)>
epoch: 23, train_loss_TEST_TASK: 
[0.7203683 6.5279636 1.0308125]
<NDArray 3 @cpu(0)>
epoch: 23, train_loss_CONTROL_TASK: 
[2.2675204 4.426769  1.964368  1.6461773]
<NDArray 4 @cpu(0)>
epoch: 24, train_loss_TEST_TASK: 
[0.68668145 6.388895   0.9913916 ]
<NDArray 3 @cpu(0)>
epoch: 24, train_loss_CONTROL_TASK: 
[2.2430053 4.289958  1.5516343 1.9167548]
<NDArray 4 @cpu(0)>
epoch: 25, train_loss_TEST_TASK: 
[6.2465706 0.9519297 0.6541693]
<NDArray 3 @cpu(0)>
epoch: 25, train_loss_CONTROL_TASK: 
[4.1505184 2.2181091 1.8683283 1.4573362]
<NDArray 4 @cpu(0)>
epoch: 26, train_loss_TEST_TASK: 
[0.623118   6.101339   0.91260624]
<NDArray 3 @cpu(0)>
epoch: 26, train_loss_CONTROL_TASK: 
[2.1928785 4.008845  1.3637757 1.8192067]
<NDArray 4 @cpu(0)>
epoch: 27, train_loss_TEST_TASK: 
[5.953603   0.87361264 0.59382135]
<NDArray 3 @cpu(0)>
epoch: 27, train_loss_CONTROL_TASK: 
[1.7695205 1.2714694 2.1673682 3.8653736]
<NDArray 4 @cpu(0)>
epoch: 28, train_loss_TEST_TASK: 
[0.5665754  0.83514893 5.8038077 ]
<NDArray 3 @cpu(0)>
epoch: 28, train_loss_CONTROL_TASK: 
[1.7194128 3.7205803 1.1809494 2.1416378]
<NDArray 4 @cpu(0)>
epoch: 29, train_loss_TEST_TASK: 
[0.5416727  0.79742193 5.652446  ]
<NDArray 3 @cpu(0)>
epoch: 29, train_loss_CONTROL_TASK: 
[2.1157537 3.5749736 1.0927551 1.6690369]
<NDArray 4 @cpu(0)>
epoch: 30, train_loss_TEST_TASK: 
[5.500046  0.7606407 0.5193966]
<NDArray 3 @cpu(0)>
epoch: 30, train_loss_CONTROL_TASK: 
[1.6185547 1.0074217 2.089786  3.429088 ]
<NDArray 4 @cpu(0)>
Exception ignored in: <bound method DataLoader.__del__ of <model.dataset_mx.MtmoDataset object at 0x7fa1f001a908>>
Traceback (most recent call last):
  File "/home/chjiang/.local/lib/python3.6/site-packages/mxnet/gluon/data/dataloader.py", line 675, in __del__
    if self._worker_pool:
AttributeError: 'MtmoDataset' object has no attribute '_worker_pool'
Exception ignored in: <bound method DataLoader.__del__ of <model.dataset_mx.MtmoDataset object at 0x7fa1f001a710>>
Traceback (most recent call last):
  File "/home/chjiang/.local/lib/python3.6/site-packages/mxnet/gluon/data/dataloader.py", line 675, in __del__
    if self._worker_pool:
AttributeError: 'MtmoDataset' object has no attribute '_worker_pool'