{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":53666,"databundleVersionId":6589269,"sourceType":"competition"}],"dockerImageVersionId":30635,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This is a coding exercise; a pytorch adaptation of the CNN-Transformer-GRU model in \n\nhttps://www.kaggle.com/code/cdeotte/11th-place-gold-cv-835-public-lb-788 .\n\nThe data composes a multivariate time-series segmentation problem where we're not trying to predict future events.\nThe events we are trying to predict are onset of sleep and waking up. Initially, my hunch (and those of some others)\nwas that these events are pretty sparse and so it might be better to convert from onset/wakeup events to asleep/awake events.\nHowever, interestingly, it appears that many of the best models didn't use this approach. Instead, a good approach appears to be\nto smooth the onset/wakeup labels. Here I am using a gaussian (with hyperparameter 'sigma' determining the degree of smoothing).\n\nEach sample is a 1 day window of data (17280 rows of 5 second intervals). The model output (for a sample) is of shape\n(batch_size,3,1440); 1440 predictions for each class onset/wakeup/none (many people don't include the none class, it's not strictly necessary).\nEach prediction being a course grained prediction over a cell of the 17280 rows.\n\nThere are many series of data in the dataset with unique series_ids.\n\nFor training, for each series_id, I initially split up the samples to be consecutive and non-overlapping 1 day windows. However,\nI was able to get better results by adding a random forward offset in time to each sample. The hypothesis being, the model\nbeing invariant to row translation of the data would make it generalise better.\n\nFor generating the submission, I created predictions over windows with various offsets and then averaged over the offsets,\njust in case there was some lingering row translation dependence of the model.\n\nThe below code only looks at one train/test split; full cross-validation was prohibitive for me. The choice of split is pretty arbitrary.\n\nThe model is interesting in that it involves CNN, Transformer and RNN blocks combined. My results aren't as good as those cited; more and better engineered features would presumably help.\n\nUsing polars, the code was made to be memory efficient, so the data is processed on the fly rather than all at once.\n\nUPDATE: Fixed a bug in the use of the scheduler. Hopefully this improves the model training but I haven't tested it.","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib as mpl\nimport matplotlib.pyplot as plt\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom datetime import date, timedelta\nfrom torchvision import transforms\nimport time\nimport math\nimport gc\nimport polars as pl\nimport skimage.measure\nimport torch.optim.lr_scheduler as lr_scheduler\nimport random\nimport warnings\n\nwarnings.filterwarnings('ignore', category=Warning)\n\nmodel_name = \"/CNNTransGRU.pt\"\nload_model = False\nwin_size = 1440\nsigma = 32\n\nnum_blocks = 4\n\n# TransBlock params\nnum_heads = 4\ndropout = 0.0\n\n# WaveBlock params\nn = 12\nnum_channels = 16\nkernel_size = 3\n\n# GRU params\nnum_layers = 1\nhidden_size = 128\noutput_dim = 3\n\nbatch_size = 20\n\n# training params\nw_decay = 0.0\nlearning_rate = 1e-4\nepochs = 90\nstarting_epoch = 0\n\ntrain_events = pd.read_csv('/kaggle/input/child-mind-institute-detect-sleep-states/train_events.csv')\ntrain_events.dropna(inplace=True)\n\ndf = pl.scan_parquet('/kaggle/input/child-mind-institute-detect-sleep-states/train_series.parquet')\n\n#####################################\n\nnorm_list = [\n    ((pl.col('anglez')-pl.col('anglez').mean())/\n     (pl.col('anglez').std()+0.001)),\n    (pl.col('enmo')/(pl.col('enmo').std()+0.001))\n    ]\n\ncast_list = [\n    pl.col('timestamp').str.to_datetime(),\n    pl.col('enmo').cast(pl.Float32),\n    pl.col('anglez').cast(pl.Float32),\n    pl.col('timestamp').str.to_datetime().dt.hour().cast(pl.UInt8).alias('hour'),\n    pl.col('timestamp').str.to_datetime().dt.minute().cast(pl.UInt8).alias('minute')\n    ]\n\ncell_size = int(17280/win_size)\nwindows = [30,60,120]\ncols = ['anglez','enmo']\nmapping_dict = {True:1.,False:0,}\nnew_cols = []\nexp_list = []\nexp_list_b = []\n\nfor col in cols:\n    exp_list.extend([\n        pl.col(col).rolling_mean(win,center=True,min_periods=int(win/2)).over('series_id')\\\n            .alias(f'{col}_mean_{win}') for win in windows\n        ])\n        \n    exp_list.extend([\n        pl.col(col).rolling_std(win,center=True,min_periods=int(win/2)).over('series_id')\\\n            .alias(f'{col}_std_{win}') for win in windows\n        ])\n        \n    exp_list.extend([\n        pl.col(col).over('series_id').diff().abs().rolling_median(win,center=True,min_periods=int(win/2))\\\n            .alias(f'{col}_meddiff_{win}') for win in windows\n        ])\n    \n    new_cols.extend([f'{col}_meddiff_{win}' for win in windows])\n\nnew_win = 360\nweights_back = [1]*int(new_win/2)+[0]*int(new_win/2)\nweights_fwd = [0]*int(new_win/2)+[1]*int(new_win/2)\nmapper = {True:1,False:0}\nfor col in new_cols:\n    exp_list_b.extend([\n        (pl.col(col).over('series_id').rolling_mean(new_win,center=True,weights=weights_fwd)\\\n         -pl.col(col).over('series_id').rolling_mean(new_win,center=True,weights=weights_back))\\\n            .alias(f'{col}_diffmean_{new_win}')\n        ])\n        \n    exp_list_b.extend([\n        (pl.col(col).over('series_id')<=0.05).map_dict(mapper).rolling_mean(new_win,center=True,min_periods=int(new_win/2))\\\n            .alias(f'{col}_bin_{new_win}')\n        ])\n\n####################################\n\nseries_ids_unique = df.select(pl.col('series_id')).unique(maintain_order=True).collect()\nseries_ids_unique = series_ids_unique.filter(pl.col('series_id').is_in(pl.Series(train_events['series_id'])))\n\nsplit_ind = int(0.8*len(series_ids_unique))\ntrain_ids_unique = series_ids_unique[:split_ind]\n\ntest_ids_unique = series_ids_unique[split_ind:]\n\ntest_ids = df.select(pl.col('series_id')).filter(pl.col('series_id')\\\n                .is_in(list(test_ids_unique['series_id'].to_numpy())))\ntrain_ids = df.select(pl.col('series_id')).filter(pl.col('series_id')\\\n                .is_in(list(train_ids_unique['series_id'].to_numpy())))\n\ntest = df.filter(pl.col('series_id').is_in(list(test_ids_unique['series_id'])))\ntrain = df.filter(pl.col('series_id').is_in(list(train_ids_unique['series_id'])))\n\ntest_id_count = test.group_by(\"series_id\", maintain_order=True).agg(pl.count()).collect().to_pandas()\ntrain_id_count = train.group_by(\"series_id\", maintain_order=True).agg(pl.count()).collect().to_pandas()\n    \n\nclass GetDataset(Dataset):\n    def __init__(self,rawdata,dts,sid,transform=None,train_mode=True,epoch=1):\n        self.raw_data = rawdata\n        self.transform = transform\n        # create index map\n        self.length = len(dts)-1\n        self.dts = dts\n        self.sid = sid\n        self.train_mode = train_mode\n        self.epoch = epoch\n\n    def __len__(self):\n        return self.length\n\n    def __getitem__(self, idx):\n        if torch.is_tensor(idx):\n            idx = idx.tolist()\n        \n        dat_y, dat_X = prepareData(self.raw_data,self.dts[idx],\n                                   sigma,self.sid,idx*self.epoch,self.train_mode)\n        sample = {'data': dat_X, 'labels': dat_y}\n\n        if self.transform:\n            sample = self.transform(sample)\n\n        return sample\n\nclass ToTensor(object):\n    def __call__(self, sample):\n        new_data = torch.from_numpy(np.array(sample['data']\\\n            .to_pandas(),dtype='float32'))\n            \n        new_labels = sample['labels']\n        new_labels = torch.from_numpy(np.array(new_labels,dtype='float32'))\n        new_labels = new_labels.squeeze()\n        new_labels = torch.transpose(new_labels,0,1)\n        \n        return {'data': new_data,\n                'labels': new_labels}\n\ndef prepareData(data,s,sigma,sid,seed,train_mode):\n    \n    new_data = data.filter((pl.col('timestamp')>=s))\n    random.seed(seed)\n    if train_mode:\n        rand = random.randint(0,17280)\n    else:\n        rand = 0\n    new_data = new_data.slice(rand,17280)\n    \n    length_new_data = new_data.select(pl.count()).to_pandas().loc[0,'count']\n    if length_new_data == 17280:\n        pool_size = int(length_new_data/win_size)\n        new_data = new_data.slice(0,pool_size*win_size)\n        \n        data_X = new_data.drop(['series_id','step','timestamp']).to_pandas()\n        # pool data_X\n        cols = list(data_X.columns)\n        \n        data_X = pd.DataFrame(skimage.measure.block_reduce(data_X,(pool_size,1),np.mean))\n        data_X.columns = cols\n        data_X = pl.DataFrame(data_X)\n        data_X = data_X.with_columns(norm_list) # normalise over day window rather than over series\n        \n        data_steps = train_events.loc[train_events['series_id']==sid]\n        data_steps = data_steps[['event','step']]\n        data_y = pd.DataFrame()\n        full_steps = new_data.select(pl.col('step')).to_pandas()\n        full_steps = pd.DataFrame(skimage.measure.block_reduce(full_steps,(pool_size,1),func=np.mean))\n        \n        norm = 1/ np.sqrt(math.pi / sigma)\n        for evt in ['onset','wakeup']:\n            steps = data_steps[data_steps.event == evt]['step'].values\n            col = evt\n            data_y[col] = [0.0]*win_size\n            if len(steps)>0:\n                for i in steps:\n                    x = 0.5*((full_steps.astype(np.int64) - i)/sigma)**2\n                    x = x.loc[:,0]\n                    data_y[col] = data_y[col] + np.exp(-x)*norm\n                data_y[col] /= data_y[col].max() + 0.00001\n        \n        data_y['norm'] = data_y['onset'] + data_y['wakeup']\n        data_y.loc[data_y['norm']<1,'norm'] = 1\n        data_y['onset'] = data_y['onset']/data_y['norm']\n        data_y['wakeup'] = data_y['wakeup']/data_y['norm']\n        \n        data_y['not'] = 1 - pd.Series(data_y.iloc[:,0]) - pd.Series(data_y.iloc[:,1])\n        data_y['zeroes'] = 0\n        data_y['not'] = np.max(data_y[['not','zeroes']],axis=1)\n        data_y.drop(['zeroes','norm'],axis=1,inplace=True)\n        \n        # pool data_y\n        \n        data_y[['onset','wakeup']] = data_y[['onset','wakeup']].fillna(0)\n        data_y['not'] = data_y['not'].fillna(1)\n        data_y = pl.DataFrame(data_y)\n    else:\n        data_X = pl.DataFrame(pd.DataFrame(1.0,index=np.arange(win_size),columns=new_data.columns))\\\n            .drop(['series_id','step','timestamp'])\n        data_y = pl.DataFrame({'onset':[0.0]*len(data_X),\n                          'wakeup':[0.0]*len(data_X),\n                          'not':[1.0]*len(data_X)})\n        \n    return data_y, data_X\n\ndef createFeatures(data):\n\n    # add duplicated flag\n    data = data.with_columns(pl.all_horizontal(pl.col(['anglez','enmo']).is_duplicated())\\\n        .map_dict(mapping_dict).alias('duplicated').rolling_median(3,center=True,min_periods=1))\n    \n    data = data.with_columns(cast_list).with_columns(exp_list)\\\n            .with_columns(pl.all().fill_null(strategy='forward'))\\\n            .with_columns(pl.all().fill_null(strategy='backward'))\\\n            .with_columns(exp_list_b)\\\n            .with_columns(pl.all().fill_null(strategy='forward'))\\\n            .with_columns(pl.all().fill_null(strategy='backward'))\\\n            .drop('anglez_shift_plus','anglez_shift_minus').collect()\n    \n    return data\n\ndevice = (\n    \"cuda\"\n    if torch.cuda.is_available()\n    else \"mps\"\n    if torch.backends.mps.is_available()\n    else \"cpu\"\n)\nprint(f\"Using {device} device\")\n\nclass WaveBlock(nn.Module):\n    def __init__(self,n,num_channels,kernel_size):\n        super().__init__()\n        self.dilation_rates = [2**i for i in range(n)]\n        self.conv_list_tanh = nn.ModuleList()\n        self.conv_list_sig = nn.ModuleList()\n        self.conv_list_conv = nn.ModuleList()\n        for dilation_rate in self.dilation_rates:\n            self.conv_list_tanh.append(nn.Sequential(nn.LazyConv1d(out_channels=num_channels,\n                                                              kernel_size=kernel_size,\n                                                              padding='same',\n                                                              dilation=dilation_rate),\n                                                nn.Tanh()))\n            self.conv_list_sig.append(nn.Sequential(nn.LazyConv1d(out_channels=num_channels,\n                                                              kernel_size=kernel_size,\n                                                              padding='same',\n                                                              dilation=dilation_rate),\n                                                nn.Sigmoid()))\n            self.conv_list_conv.append(nn.LazyConv1d(out_channels=num_channels,\n                          kernel_size=1,\n                          padding='same'))\n        self.conv_start = nn.LazyConv1d(out_channels=num_channels,\n                                        kernel_size=1,\n                                        padding='same')\n    \n    def forward(self,x):\n        x = torch.transpose(x,1,2)\n        x = self.conv_start(x)\n        res_x = x\n        for i in range(len(self.dilation_rates)):\n            tanh_x = self.conv_list_tanh[i](x)\n            sig_x = self.conv_list_sig[i](x)\n            x = torch.mul(tanh_x,sig_x)\n            x = self.conv_list_conv[i](x)\n            res_x = res_x + x\n        res_x = torch.transpose(res_x,1,2)\n        return res_x\n        \nclass PositionalEncoding(nn.Module):\n    def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 5000):\n        super().__init__()\n        self.dropout = nn.Dropout(p=dropout)\n\n        position = torch.arange(max_len).unsqueeze(1)\n        div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))\n        pe = torch.zeros(1, max_len, d_model)\n        pe[0, :, 0::2] = torch.sin(position * div_term)\n        pe[0, :, 1::2] = torch.cos(position * div_term)\n        self.register_buffer('pe', pe)\n\n    def forward(self,x):\n        x = x + self.pe[:x.size(0)]\n        return self.dropout(x)\n\nclass TransBlock(nn.Module):\n    def __init__(self,num_channels,num_heads,dropout):\n        super().__init__()\n        self.attn = nn.MultiheadAttention(num_channels,num_heads,dropout=dropout,batch_first=True)\n        self.ff1 = nn.LazyLinear(num_channels)\n        self.ff2 = nn.LazyLinear(num_channels)\n        self.ln1 = nn.LayerNorm(num_channels)\n        self.ln2 = nn.LayerNorm(num_channels)\n        self.drop = nn.Dropout(dropout)\n        self.gelu = nn.GELU()\n    \n    def forward(self,x):\n        y,y_weights = self.attn(x,x,x)\n        x = self.ln1(x + y)\n        z = self.drop(self.ff2(self.gelu(self.ff1(x))))\n        x = self.ln2(x + z)\n        return x\n\nclass TransWaveBlock(nn.Module):\n    def __init__(self,num_heads,dropout,n,num_channels,kernel_size):\n        super().__init__()\n        self.wave1 = WaveBlock(n,num_channels,kernel_size)\n        self.wave2 = WaveBlock(n,num_channels,kernel_size)\n        self.trans = TransBlock(num_channels,num_heads,dropout)\n    \n    def forward(self,x):\n        skip = x\n        x = self.wave1(x)\n        x = self.wave2(x)\n        x = self.trans(x)\n        x = 0.9*x + 0.1*skip\n        return x\n\nclass GRUNet(nn.Module):\n    def __init__(self, num_channels, hidden_size, output_dim, batch_first,bidir):\n        super().__init__()\n        self.gru = nn.GRU(num_channels,\n                                hidden_size,\n                                batch_first=batch_first,\n                                bidirectional=bidir,\n                                num_layers=num_layers,\n                                dropout=dropout)\n        self.out = nn.LazyLinear(output_dim)\n        self.relu = nn.ReLU()\n        self.flatten = nn.Flatten()\n    \n    def forward(self,X):\n        X,h = self.gru(X)\n        X = self.out(self.relu(X))\n        X = torch.transpose(X,1,2)\n        return X\n\nclass WaveTransBiGRU(nn.Module):\n    def block(self,num_blocks,num_heads,dropout,n,num_channels,kernel_size):\n        blk = []\n        for i in range(num_blocks):\n            blk.append(TransWaveBlock(num_heads,dropout,n,num_channels,kernel_size))\n        return nn.Sequential(*blk)\n    \n    def __init__(self,\n                 num_blocks,\n                 num_heads,\n                 dropout,\n                 hidden_size,\n                 output_dim,\n                 n,\n                 num_channels,\n                 kernel_size):\n        super().__init__()\n        self.transblk = self.block(num_blocks,num_heads,dropout,n,num_channels,kernel_size)\n        self.gru = GRUNet(num_channels,hidden_size,output_dim,batch_first=True,bidir=True)\n        self.pos_enc = PositionalEncoding(d_model=num_channels,max_len=win_size)\n        self.ff = nn.LazyLinear(num_channels)\n    \n    def forward(self,x):\n        x = self.ff(x)\n        x = self.pos_enc(x)\n        x = self.transblk(x)\n        x = self.gru(x)\n        return x\n\nseed = 42\ntorch.manual_seed(seed)\n\nmodel = WaveTransBiGRU(num_blocks,\n                   num_heads,\n                   dropout,\n                   hidden_size,\n                   output_dim,\n                   n,\n                   num_channels,\n                   kernel_size).to(device)\nif load_model:\n    model.load_state_dict(torch.load(path + model_name))\n    model.eval()\n\n# Initialise the loss function\nloss_fn = nn.CrossEntropyLoss()\n\n# Initialise the optimiser (in this case stochastic gradient descent)\noptimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=w_decay)\nscheduler = lr_scheduler.CosineAnnealingLR(optimizer=optimizer,T_max=epochs,eta_min=1e-06)\n\nepoch_count = []\nloss_values = []\ntest_loss_values = []\n\n# here we've only split up the dataset between test and train.\ndef train_loop(data,model,loss_fn,optimizer,loss_vals,log,sigma,epoch):\n    \n    # loop over ids\n    l_vals = []\n    for idx in train_id_count['series_id']:\n        #print(idx)\n        data_idx = data.filter(pl.col('series_id')==idx)\n        data_idx = data_idx.sort('timestamp')\n        \n        data_idx = createFeatures(data_idx)\n        \n        length_data = data_idx.select(pl.count()).to_pandas().loc[0,'count']\n        \n        if length_data > win_size*3:\n            \n            start = data_idx.select(pl.col('timestamp')).slice(0,1).to_pandas().loc[0,'timestamp']\n            end = data_idx.select(pl.col('timestamp')).slice(length_data-1,1).to_pandas().loc[0,'timestamp']\n            dts = pd.date_range(start,end+timedelta(days=1),freq='d').tolist()\n    \n            train_data = GetDataset(rawdata=data_idx,\n                                    dts=dts,\n                                    transform=transforms.Compose([ToTensor()]),\n                                    sid=idx,\n                                    train_mode=True,\n                                    epoch=epoch)\n            \n            dataloader = DataLoader(train_data,batch_size=batch_size,shuffle=True)\n            \n            model.train()\n            for i,batch in enumerate(dataloader):\n                X = batch['data'].to(device)\n                y = batch['labels'].to(device)\n                \n                if torch.any(torch.isnan(X.to('cpu'))) or torch.any(torch.isnan(y.to('cpu'))):\n                    print('Nan!')\n                    break\n                \n                pred = model(X)\n                loss = loss_fn(pred, y)\n                optimizer.zero_grad()\n                loss.backward()\n                optimizer.step()\n            scheduler.step()\n                \n            ### Testing\n            if log:\n                model.eval()\n                with torch.no_grad():\n                    pred_tot = torch.tensor([])\n                    y_tot = torch.tensor([])\n                    for i,batch in enumerate(dataloader):\n                        X1 = batch['data'].to(device)\n                        y1 = batch['labels']\n                        train_pred = model(X1).to('cpu')\n                        \n                        pred_tot = torch.cat((pred_tot,train_pred))\n                        y_tot = torch.cat((y_tot,y1))\n                        \n                \n                    train_loss = loss_fn(pred_tot,y_tot).item()\n                    l_vals.append(train_loss)\n\n        del data_idx\n        gc.collect()\n    \n    if log:\n        loss_vals.append(np.mean(l_vals))\n            \ndef test_loop(data,model,loss_fn,loss_vals,log,sigma,epoch):\n    \n    # loop over ids\n    l_vals = []\n    for idx in test_id_count['series_id']:\n        data_idx = data.filter(pl.col('series_id')==idx)\n        data_idx = data_idx.sort('timestamp')\n        \n        data_idx = createFeatures(data_idx)\n        \n        length_data = data_idx.select(pl.count()).to_pandas().loc[0,'count']\n        \n        start = data_idx.select(pl.col('timestamp')).slice(0,1).to_pandas().loc[0,'timestamp']\n        end = data_idx.select(pl.col('timestamp')).slice(length_data-1,1).to_pandas().loc[0,'timestamp']\n        dts = pd.date_range(start,end+timedelta(days=1),freq='d').tolist()\n        \n        if length_data > win_size*3:\n            \n            test_data = GetDataset(rawdata=data_idx,\n                                    dts=dts,\n                                    transform=transforms.Compose([ToTensor()]),\n                                    sid=idx,\n                                    train_mode=False,\n                                    epoch=epoch)\n            \n            dataloader = DataLoader(test_data,batch_size=batch_size,shuffle=True)\n        \n            if log:\n                pred_tot = torch.tensor([])\n                y_tot = torch.tensor([])\n                with torch.no_grad():\n                    for i,batch in enumerate(dataloader):\n                        X = batch['data'].to(device)\n                        y = batch['labels']\n                        \n                        pred = model(X).to('cpu')\n                        \n                        pred_tot = torch.cat((pred_tot,pred))\n                        y_tot = torch.cat((y_tot,y))\n                        \n                test_loss = loss_fn(pred_tot, y_tot).item()\n                l_vals.append(test_loss)\n        \n        del data_idx\n        gc.collect()\n        \n    loss_vals.append(np.mean(l_vals))\n\nt1 = time.time()\ntt = time.time()\nfor t in range(starting_epoch,starting_epoch+epochs):\n    tt = time.time()\n    print(f\"Epoch {t+1} \")\n    train_loop(train, model, loss_fn, optimizer,loss_values,log=True,sigma=sigma,epoch=t)\n    print('Train Loss: ',loss_values[len(loss_values)-1])\n    test_loop(test,model,loss_fn,test_loss_values,log=True,sigma=sigma,epoch=t)\n    print('Test Loss: ',test_loss_values[len(test_loss_values)-1])\n    epoch_count.append(int(t))\n    print(str(round((time.time()-tt)/60)) + \"mins\\n-------------------------------\")\nt2 = time.time()\nprint(\"Done! Calc time: \" + str(round((t2-t1)/60)) + 'mins')\n\nepoch_count_ = np.array(torch.tensor(epoch_count).numpy())\nloss_values_ = np.array(torch.tensor(loss_values).numpy())\ntest_loss_values_ = np.array(torch.tensor(test_loss_values).numpy())\n\nname = \"\"\nplt.plot(epoch_count_,loss_values_,label=\"Train loss\")\nplt.plot(epoch_count_,test_loss_values_,label=\"Test loss\")\nplt.title(name)\nplt.ylabel(\"Loss\")\nplt.xlabel(\"Epoch\")\nplt.legend()\n\ntorch.save(model.state_dict(), path + model_name)","metadata":{"execution":{"iopub.status.busy":"2024-03-16T02:38:43.748542Z","iopub.execute_input":"2024-03-16T02:38:43.749167Z","iopub.status.idle":"2024-03-16T02:39:29.259100Z","shell.execute_reply.started":"2024-03-16T02:38:43.749117Z","shell.execute_reply":"2024-03-16T02:39:29.257683Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Generate Submission**","metadata":{}},{"cell_type":"code","source":"def prepareDataSub(data,s,sigma,sid,seed,train_mode):\n    \n    new_data = data.filter((pl.col('step')>=s))\n    random.seed(seed)\n    if train_mode:\n        rand = random.randint(0,17280)\n    else:\n        rand = 0\n    new_data = new_data.slice(rand,17280)\n    \n    length_new_data = new_data.select(pl.count()).to_pandas().loc[0,'count']\n    if length_new_data == 17280:\n        pool_size = int(length_new_data/win_size)\n        data_X = new_data.drop(['series_id','step','timestamp']).to_pandas()\n        # pool data_X\n        cols = list(data_X.columns)\n        \n        data_X = pd.DataFrame(skimage.measure.block_reduce(data_X,(pool_size,1),np.mean))\n        data_X.columns = cols\n        data_X = pl.DataFrame(data_X)\n        data_X = data_X.with_columns(norm_list) # normalise over day window rather than over series\n    else:\n        data_X = pl.DataFrame(pd.DataFrame(1.0,index=np.arange(win_size),columns=new_data.columns))\\\n            .drop(['series_id','step','timestamp'])\n    \n    data_y = pl.DataFrame({'onset':[0.0]*win_size,\n                      'wakeup':[0.0]*win_size,\n                      'not':[0.0]*win_size})\n        \n    return data_y, data_X\n\ndef model_output(dataset,model,prob_idx,idx):\n    with torch.no_grad():\n        sm = nn.Softmax(0)\n        sample = dataset.__getitem__(math.floor(idx))['data']\n        sample = sample.unsqueeze(0)\n        output = model(sample.to(device)).to('cpu')\n        output = output.squeeze()\n        output = sm(output)\n        return output[prob_idx].item()\n\nsubmission = pd.DataFrame({'series_id':[],\n                    'timestamp':[],\n                    'step':[],\n                    'event':[],\n                    'score':[]\n                    })\n\nsm = nn.Softmax(1)\n\npool_size = int(17280/win_size)\noffsets = list(range(0,17280,pool_size*100))\n\ntest = pl.scan_parquet('/kaggle/input/child-mind-institute-detect-sleep-states/test_series.parquet')\ntest_ids_unique = test.select(pl.col('series_id')).unique(maintain_order=True).collect()\n\nfor idx in test_ids_unique.to_pandas()['series_id']:\n    print(idx)\n    #print(idx)\n    # to choose onset and wakeup events for each id, split test data into days\n    data = test.filter(pl.col('series_id')==idx)\n    data = data.sort('timestamp')\n    \n    data = createFeatures(data)\n    \n    length_data = data.select(pl.count()).to_pandas().loc[0,'count']\n    \n    steps = data.select(pl.col('step')).to_pandas()\n    start_steps = steps.iloc[list(range(0,length_data,17280))].reset_index(drop=True)\n    \n    merged_preds_onset = pd.DataFrame()\n    merged_preds_wakeup = pd.DataFrame()\n    for offset in offsets:\n        #print(offset)\n        preds = pd.DataFrame(None,columns=['timestamp','step',0,1])\n        for i in range(len(start_steps)-1):\n            s = start_steps['step'][i] + offset\n            \n            stepmap = data.select([pl.col('timestamp'),\n                                   pl.col('step')]).filter((pl.col('step')>=s)).to_pandas()\n            stepmap = stepmap.iloc[:17280,:]\n            if len(stepmap) == 17280:    \n                stepmap = stepmap.iloc[list(range(int(pool_size/2),17280,pool_size))].reset_index(drop=True)\n                \n                test_y, test_X = prepareDataSub(data,s,sigma,idx,0,False)\n                sample = ToTensor().__call__({'data':test_X,'labels':test_y})\n                pred = model(sample['data'].unsqueeze(dim=0).to(device)).to('cpu')\n                pred = sm(pred.squeeze().T)\n                pred = pd.DataFrame(pred.detach())\n                \n                pred = pd.concat([stepmap,pred],axis=1)\n                preds = pd.concat([preds,pred],axis=0)\n                del sample\n        \n        preds_onset = preds.loc[:,['timestamp','step',0]]\n        preds_wakeup = preds.loc[:,['timestamp','step',1]]\n        if offset==0:\n            merged_preds_onset = preds_onset\n            merged_preds_wakeup = preds_wakeup\n        else:\n            merged_preds_onset = merged_preds_onset.merge(preds_onset,how='left',\\\n                                                        on=['timestamp','step'],\\\n                                                        suffixes=(None,\"_\"+str(offset)))\n            merged_preds_wakeup = merged_preds_wakeup.merge(preds_wakeup,how='left',\\\n                                                        on=['timestamp','step'],\\\n                                                        suffixes=(None,\"_\"+str(offset)))\n    \n    mean_preds_onset = pd.concat([merged_preds_onset.drop(['timestamp','step'],axis=1).mean(axis=1),\\\n                                  merged_preds_onset.loc[:,['timestamp','step']]],axis=1)\n    mean_preds_wakeup = pd.concat([merged_preds_wakeup.drop(['timestamp','step'],axis=1).mean(axis=1),\\\n                                   merged_preds_wakeup.loc[:,['timestamp','step']]],axis=1)\n    \n    del merged_preds_onset, merged_preds_wakeup, preds, preds_onset, preds_wakeup\n    gc.collect()\n    \n    for i in range(len(start_steps)-1):\n        # get optimal indices for onset/wakeup prob_indices\n        s = start_steps.loc[i,'step']\n        e = start_steps.loc[i+1,'step']\n        sub_mean_preds_onset = mean_preds_onset\\\n            .loc[np.logical_and(mean_preds_onset['step']>=s,mean_preds_onset['step']<e)].reset_index(drop=True)\n        sub_mean_preds_wakeup = mean_preds_wakeup\\\n            .loc[np.logical_and(mean_preds_wakeup['step']>=s,mean_preds_wakeup['step']<e)].reset_index(drop=True)\n        for prob_idx in ('onset','wakeup'):\n            if prob_idx == 'onset':\n                p_index = list(sub_mean_preds_onset.index[sub_mean_preds_onset\\\n                                                          .loc[:,0]==np.max(sub_mean_preds_onset.loc[:,0])])[0]\n                score = sub_mean_preds_onset.loc[p_index,0]\n                step = sub_mean_preds_onset.loc[p_index,'step']\n                ts = sub_mean_preds_onset.loc[p_index,'timestamp']\n            else:\n                p_index = list(sub_mean_preds_wakeup.index[sub_mean_preds_wakeup\\\n                                                          .loc[:,0]==np.max(sub_mean_preds_wakeup.loc[:,0])])[0]\n                score = sub_mean_preds_wakeup.loc[p_index,0]\n                step = sub_mean_preds_wakeup.loc[p_index,'step']\n                ts = sub_mean_preds_wakeup.loc[p_index,'timestamp']\n\n            sub = pd.DataFrame({'series_id':[idx],\n                                'timestamp':[ts],\n                                'step':[step],\n                                'event':[prob_idx],\n                                'score':[score]})\n            submission = pd.concat([submission,sub])\n        \n        del sub_mean_preds_onset, sub_mean_preds_wakeup\n        gc.collect()\n    \n    del mean_preds_onset, mean_preds_wakeup\n    gc.collect()\n\nsubmission.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-03-16T02:40:13.117099Z","iopub.execute_input":"2024-03-16T02:40:13.117439Z","iopub.status.idle":"2024-03-16T02:40:14.968355Z","shell.execute_reply.started":"2024-03-16T02:40:13.117414Z","shell.execute_reply":"2024-03-16T02:40:14.967536Z"},"trusted":true},"execution_count":null,"outputs":[]}]}