{"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":"gpu","dataSources":[{"sourceId":53666,"databundleVersionId":6589269,"sourceType":"competition"},{"sourceId":6507308,"sourceType":"datasetVersion","datasetId":3706667},{"sourceId":7076196,"sourceType":"datasetVersion","datasetId":3905210},{"sourceId":151583600,"sourceType":"kernelVersion"}],"dockerImageVersionId":30558,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Import","metadata":{}},{"cell_type":"code","source":"import numpy as np\n\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport seaborn as sns\n\nfrom tqdm.auto import tqdm\n\nimport gc\nimport ctypes\n\nimport pandas as pd\nimport polars as pl\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\nclass PATHS:\n    EVENTS = r'/kaggle/input/child-mind-institute-detect-sleep-states/train_events.csv'\n    TRAIN_SERIES = r'/kaggle/input/zzzs-lightweight-training-dataset-target/Zzzs_train.parquet'\n    TEST_SERIES = r'/kaggle/input/child-mind-institute-detect-sleep-states/test_series.parquet'\n    \n    MODEL_PATH = r'/kaggle/input/child-mind-institute-models/large_unetbigru.pth'\n    \n    SUBMISSION_FILENAME = r'submission.csv'\n    \nclass CFG:\n    DEVICE = 'cuda'","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2023-11-29T18:51:15.636394Z","iopub.execute_input":"2023-11-29T18:51:15.637251Z","iopub.status.idle":"2023-11-29T18:51:15.646901Z","shell.execute_reply.started":"2023-11-29T18:51:15.637217Z","shell.execute_reply":"2023-11-29T18:51:15.645877Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"general_transformations = [\n    pl.col('timestamp').str.to_datetime(),\n]\nseries_transformations = [\n    pl.col('anglez').cast(pl.Int16),\n    (pl.col('enmo')*1000).cast(pl.UInt16),\n]\n\ndf_events = (pl.scan_csv(PATHS.EVENTS).with_columns(\n    general_transformations\n).collect().to_pandas())\n\ntrain_series = (pl.scan_parquet(PATHS.TRAIN_SERIES).with_columns(\n    general_transformations+series_transformations\n).collect().to_pandas())\n\ntrain_series['sleep'] = 1 - train_series['awake']\ntrain_series.drop('awake', axis=1, inplace=True)\n\nprint(train_series.shape)\n\ntrain_series.head()","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:16.363337Z","iopub.execute_input":"2023-11-29T18:51:16.364028Z","iopub.status.idle":"2023-11-29T18:51:27.980902Z","shell.execute_reply.started":"2023-11-29T18:51:16.363999Z","shell.execute_reply":"2023-11-29T18:51:27.979939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We use polars to transform data before we read the whole set. You can see, that we didn't load the test set, that's because, according to how much time is spent on the submission, the test set seems to be huge, which can lead to *Notebook Timeout* error while submitting. So, we will load it gradually when we will make the submission","metadata":{}},{"cell_type":"code","source":"df_ = train_series[(train_series['series_id'] == 'a9a2f7fac455') &\n                   (train_series['step'] >= (0*24*3600)/5) &\n                   (train_series['step'] <= (2*24*3600)/5)\n]\n\ndf_awake_ = df_[df_['sleep'] == 0]\ndf_sleep_ = df_[df_['sleep'] == 1]\n\nfig = plt.figure(figsize=(30, 10))\n\nplt.title('Anglez and ENMO sleep plot for 5acc9d63b5fd series')\n\nax_anglez = fig.add_subplot(2, 1, 1)\nax_enmo = fig.add_subplot(2, 1, 2)\n\nsns.lineplot(data=df_awake_, x='timestamp', y='anglez', label='awake', ax=ax_anglez)\nsns.lineplot(data=df_sleep_, x='timestamp', y='anglez', color='orange', label='sleep', ax=ax_anglez)\n\nsns.lineplot(data=df_awake_, x='timestamp', y='enmo', ax=ax_enmo)\nsns.lineplot(data=df_sleep_, x='timestamp', y='enmo', color='orange', ax=ax_enmo)","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:27.982646Z","iopub.execute_input":"2023-11-29T18:51:27.982918Z","iopub.status.idle":"2023-11-29T18:51:33.916404Z","shell.execute_reply.started":"2023-11-29T18:51:27.982894Z","shell.execute_reply":"2023-11-29T18:51:33.915473Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"From the plot above, we can see, that it's a **change-point** (or break-point) detection task. Change-point is a point at which a significant change occurs","metadata":{}},{"cell_type":"markdown","source":"# Modeling","metadata":{}},{"cell_type":"code","source":"class SleepDataset(Dataset):\n    def __init__(self, series_file, events=None, series_ids=None, continuous=None, sigma=None):\n        self.series_file = series_file\n        self.events = events\n        self.continuous = continuous\n        self.sigma = sigma\n        \n        self.series_lazy = pl.scan_parquet(series_file)\n        self.series_ids = self.series_lazy.select(pl.col('series_id')).unique().collect().to_numpy().squeeze()\n    \n    def load_data(self, series_id):\n        data = self.series_lazy.filter(pl.col('series_id') == series_id)\n        \n        if self.events is not None:\n            if self.continuous is not None:\n                start, end = self.continuous\n            else:\n                start, end = 0, 1000000\n\n            gap = 6*60*12\n            tmp = self.events[(self.events.series_id == series_id) & (self.events.night >= start) & (self.events.night <= end)]\n            \n            if tmp.step.isna().sum() != len(tmp): #All events are not NaN\n                data = (data\n                        .filter(pl.col('step') > tmp.step.min()-gap)\n                        .filter(pl.col('step') < tmp.step.max()+gap))\n            \n            data = data.collect().to_pandas()\n            \n            if self.sigma is not None:\n                data = data.set_index(['series_id', 'step']).join(tmp.set_index(['series_id', 'step'])[['event', 'night']]).reset_index()\n                norm = 1/ np.sqrt(np.pi / self.sigma)\n                for evt in ['onset', 'wakeup']:\n                    steps = data[data.event == evt]['step'].values\n                    col = f'{evt}_val'\n                    data[col] = 0.0\n\n                    for i in steps:\n                        x = 0.5*((data.step.astype(np.int64) - i)/self.sigma)**2\n                        data[col] += np.exp(-x)*norm\n\n                    if data[col].sum() > 0:\n                        data[col] /= data[col].sum()\n        else:\n            data = data.collect().to_pandas()\n            \n        return data\n\n    def __len__(self):\n        return len(self.series_ids)\n\n    def __getitem__(self, index):\n        series_id = self.series_ids[index]\n        data = self.load_data(series_id)\n        \n        X = data[['anglez','enmo']]\n        \n        #Feature Engineering\n        for col in ['anglez', 'enmo']:\n            X[f'{col}_diff'] = X[col].diff().bfill()\n            \n        for per in [12, 60, 120, 360, 720]:\n            col = 'anglez'\n            for agg in ['std']:\n                X[f'{col}_{per}_{agg}'] = X[col].rolling(per, center=True, min_periods=1).agg(agg)\n                \n            col = 'enmo'\n            for agg in ['mean']:\n                X[f'{col}_{per}_{agg}'] = X[col].rolling(per, center=True, min_periods=1).agg(agg)\n\n        X = torch.from_numpy(X.values.astype(np.float32))\n\n        return_dict = {'X': X}\n\n        if self.sigma is not None:\n            Y = data[['onset_val', 'wakeup_val']].values.astype(np.float32)\n            Y = torch.from_numpy(Y)\n            return_dict['target'] = Y\n\n        return return_dict","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:33.917735Z","iopub.execute_input":"2023-11-29T18:51:33.918086Z","iopub.status.idle":"2023-11-29T18:51:33.939435Z","shell.execute_reply.started":"2023-11-29T18:51:33.918054Z","shell.execute_reply":"2023-11-29T18:51:33.938516Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This model has a convolutional layer, which reduces the input data (since RNN forgets about previously hidden states too quickly), a ResidualRNN, and a ConvTranspose layer, to get the shape of predictions back to the input data shape","metadata":{}},{"cell_type":"code","source":"#The ResidualBiGRU structure is taken from https://www.kaggle.com/code/werus23/sleep-critical-point-infer?scriptVersionId=147143158\nclass ResidualBiGRU(nn.Module):\n    def __init__(self, hidden_size, n_layers=1, bidirectional=True, use_layernorm=False):\n        super(ResidualBiGRU, self).__init__()\n\n        self.hidden_size = hidden_size\n        self.n_layers = n_layers\n        self.bidirectional = bidirectional\n        self.use_layernorm = use_layernorm\n\n        self.gru = nn.GRU(\n            hidden_size,\n            hidden_size,\n            n_layers,\n            bidirectional=bidirectional,\n#             batch_first=True,\n        )\n        dir_factor = 2 if self.bidirectional else 1\n\n        self.fc1 = nn.Linear(hidden_size*dir_factor, hidden_size*dir_factor*2)\n        self.ln1 = nn.LayerNorm(hidden_size*dir_factor*2) if self.use_layernorm else None\n        self.fc2 = nn.Linear(hidden_size*dir_factor*2, hidden_size)\n        self.ln2 = nn.LayerNorm(hidden_size) if self.use_layernorm else None\n\n    def forward(self, x_in, h=None):\n        res, new_h = self.gru(x_in, h)\n        \n        res = self.fc1(res)\n        if self.use_layernorm:\n            res = self.ln1(res)\n        res = F.relu(res)\n\n        res = self.fc2(res)\n        if self.use_layernorm:\n            res = self.ln2(res)\n        res = F.relu(res)\n\n        #skip connection\n        res = res + x_in\n\n        return res, new_h","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:33.941768Z","iopub.execute_input":"2023-11-29T18:51:33.942070Z","iopub.status.idle":"2023-11-29T18:51:33.975667Z","shell.execute_reply.started":"2023-11-29T18:51:33.942040Z","shell.execute_reply":"2023-11-29T18:51:33.974750Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class DoubleConvLayer(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size, stride, padding, use_layernorm):\n        super(DoubleConvLayer, self).__init__()\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding)\n        \n        self.ln = nn.LayerNorm(out_channels) if use_layernorm else None\n        \n    def forward(self, x):\n        x = self.conv1(x)\n        \n        if self.ln is not None:\n            x = self.ln(x.transpose(-1, -2)).transpose(-1, -2)\n            \n        x = F.relu(x)\n        \n        return x\n\nclass DecoderLayer(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size, stride, padding, output_padding, use_layernorm=False):\n        super(DecoderLayer, self).__init__()\n        self.convtrans = nn.ConvTranspose1d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding, output_padding=output_padding)\n        \n#         self.double_conv = DoubleConvLayer(out_channels*2, out_channels, kernel_size=kernel_size, stride=stride, padding=padding, use_layernorm=False)\n        \n    def crop_tensor(self, tensor, target_tensor):\n        #Tensor shape: (n_channels, seq_len)\n        target_size = target_tensor.size()[-1]\n        tensor_size = tensor.size()[-1]\n        delta = tensor_size - target_size\n        delta = delta // 2\n        return tensor[:, delta:tensor_size - delta][:, :target_size]\n        \n    def forward(self, x, x_orig, verbose=False):\n        x = self.convtrans(x)\n        \n#         x_orig = self.crop_tensor(x_orig, x)\n#         x = torch.cat([x, x_orig], axis=0)\n        \n#         x = F.relu(x)\n#         x = self.double_conv(x)\n            \n        x = F.relu(x)\n        return x\n    \nclass UNetBiGRU(nn.Module):\n    def __init__(self, arch, n_layers, bidirectional=True):\n        super(UNetBiGRU, self).__init__()\n        \n        self.arch = arch\n        self.n_layers = n_layers\n        self.bidirectional = bidirectional\n        \n#         self.hour_emb = nn.Embedding(num_embeddings=24, embedding_dim=self.hour_embedding_dim)\n        \n        self.double_convs = nn.ModuleList([DoubleConvLayer(in_channels, out_channels,\n                                                           kernel_size, stride=stride,\n                                                           padding=kernel_size//2, use_layernorm=False)\n                                          for i, (in_channels, out_channels, stride, kernel_size) in enumerate(self.arch)])\n        \n        self.rnn_hidden_size = self.arch[-1][1] #Last out_channels\n        \n        self.res_bigrus = nn.ModuleList(\n            [\n                ResidualBiGRU(self.rnn_hidden_size, n_layers=1, bidirectional=self.bidirectional, use_layernorm=True)\n\n                for _ in range(n_layers)\n            ]\n        )\n        \n        self.up_convs = nn.ModuleList([DecoderLayer(out_channels, in_channels,\n                                                    kernel_size, stride=stride,\n                                                    padding=kernel_size//2, output_padding=0,\n                                                    use_layernorm=False)\n                                        for i, (in_channels, out_channels, stride, kernel_size) in enumerate(self.arch[::-1])])\n        \n        self.out_conv = nn.Conv1d(14, 2, stride=1, kernel_size=1) #To make onset and wakeup outputs be interconnected\n\n    def forward(self, x, h=None, verbose=False):\n        first_shape = x.shape #(seq_len, n_channels)\n        \n#         hour_emb = self.hour_emb(hour)\n#         x = torch.cat([x, hour_emb], axis=1)\n        \n        if h is None:\n            h = [None for _ in range(self.n_layers)]\n        \n        down_convs = [x]\n        for i, double_conv in enumerate(self.double_convs):\n            x = double_conv(x.transpose(-1, -2)).transpose(-1, -2)\n            down_convs.append(x)\n        \n        new_h = []\n        for i, res_bigru in enumerate(self.res_bigrus):\n            x, new_hi = res_bigru(x, h[i])\n            new_h.append(new_hi)\n        \n        for i, up_conv in enumerate(self.up_convs):\n            i += 2\n            x = up_conv(x.transpose(-1, -2), down_convs[-i].transpose(-1, -2), verbose=verbose).transpose(-1, -2)\n            \n        out = self.out_conv(x.transpose(-1,-2)).transpose(-1,-2)\n        \n        #Padding\n        padded_out = torch.zeros((first_shape[0], 2))\n        seq_diff = first_shape[0] - out.shape[0]\n        \n        delta = seq_diff//2 \n        print(out.shape) if verbose==True else None\n        if seq_diff%2 == 0:\n            padded_out[delta:first_shape[0]-delta, :] = out\n        else:\n            padded_out[delta:first_shape[0]-delta-1, :] = out\n        \n        return padded_out+1, new_h","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:33.976971Z","iopub.execute_input":"2023-11-29T18:51:33.977269Z","iopub.status.idle":"2023-11-29T18:51:33.999696Z","shell.execute_reply.started":"2023-11-29T18:51:33.977236Z","shell.execute_reply":"2023-11-29T18:51:33.998813Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset = SleepDataset(\n    series_file=PATHS.TEST_SERIES,\n)\n\narch = [\n    (14, 16, 2, 5),\n    (16, 32, 2, 5),\n    (32, 64, 2, 3),\n]\n\nclassifier = UNetBiGRU(\n    arch=arch,\n    n_layers=6,\n    bidirectional=True,\n)\nclassifier = classifier.to(CFG.DEVICE)\n\nclassifier.load_state_dict(torch.load(PATHS.MODEL_PATH))","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:34.000781Z","iopub.execute_input":"2023-11-29T18:51:34.001038Z","iopub.status.idle":"2023-11-29T18:51:34.112311Z","shell.execute_reply.started":"2023-11-29T18:51:34.001016Z","shell.execute_reply":"2023-11-29T18:51:34.111384Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"day = 1\n\nX = dataset[0]['X']\n# y = dataset[0]['target']\n\nanglez = X[:, 0]\nenmo = X[:, 1]\n\nfig = plt.figure(figsize=(20, 6))\n\nax_angle = fig.add_subplot(3, 1, 1)\nax_enmo = fig.add_subplot(3, 1, 2)\nax_pred = fig.add_subplot(3, 1, 3)\n\nwith torch.no_grad():\n    pred = torch.softmax(classifier(X.to(CFG.DEVICE))[0], dim=0).cpu().numpy()\n#     pred = classifier(X.to(CFG.DEVICE))[0].cpu().numpy()\n\nax_pred.axhline(pred[:, 0].max()*0.1)\n\nax_angle.plot(anglez[(day-1)*12*60*24:day*12*60*24])\nax_enmo.plot(enmo[(day-1)*12*60*24:day*12*60*24])\nax_pred.plot(pred[(day-1)*12*60*24:day*12*60*24])","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:34.113592Z","iopub.execute_input":"2023-11-29T18:51:34.113942Z","iopub.status.idle":"2023-11-29T18:51:37.148461Z","shell.execute_reply.started":"2023-11-29T18:51:34.113910Z","shell.execute_reply":"2023-11-29T18:51:37.147588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"max_chunk_size = 1*12*60*24\nmin_interval = 1","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:37.149785Z","iopub.execute_input":"2023-11-29T18:51:37.150139Z","iopub.status.idle":"2023-11-29T18:51:37.155520Z","shell.execute_reply.started":"2023-11-29T18:51:37.150107Z","shell.execute_reply":"2023-11-29T18:51:37.154352Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"markdown","source":"The submission is a slight modified version taken from https://www.kaggle.com/code/danielphalen/cmss-grunet-infer notebook","metadata":{}},{"cell_type":"code","source":"def clean_memory():\n    gc.collect()\n    ctypes.CDLL(\"libc.so.6\").malloc_trim(0)\n    torch.cuda.empty_cache()","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:37.156991Z","iopub.execute_input":"2023-11-29T18:51:37.157414Z","iopub.status.idle":"2023-11-29T18:51:37.176447Z","shell.execute_reply.started":"2023-11-29T18:51:37.157381Z","shell.execute_reply":"2023-11-29T18:51:37.175692Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_predictions(res_df, target, SIGMA):\n    \"\"\"Function will take a predicted dataframe, and find local maxima to get event location.  The score is determined by the area under the curve\"\"\"\n    q = res_df[target].max() * 0.1\n    tmp = res_df.loc[res_df[target] > q].copy()\n    print(f'Target max = {q}, len = {len(tmp)}')\n    tmp['gap'] = tmp['step'].diff()\n    tmp = tmp[tmp['gap'] > 5*5]\n    print(f'Target max = {q}, len = {len(tmp)}')\n    res = []\n    for i in range(len(tmp) + 1):\n        start_i = 0 if i == 0 else tmp['step'].iloc[i-1]\n        end_i = tmp['step'].iloc[i] if i < len(tmp) else res_df['step'].max()\n        v = res_df.loc[(res_df['step'] > start_i) & (res_df['step'] < end_i)]\n        if v[target].max() > q:\n            print('Locate in ', start_i, end_i)\n            idx = v.idxmax()[target]\n            step = v.loc[idx, 'step']\n            span = 3*SIGMA\n            score = res_df.loc[(res_df['step'] > step - span) & (res_df['step'] < step + span), target].sum()\n            res.append([step, target, score])\n    \n    return res","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:51:37.179195Z","iopub.execute_input":"2023-11-29T18:51:37.179468Z","iopub.status.idle":"2023-11-29T18:51:37.190298Z","shell.execute_reply.started":"2023-11-29T18:51:37.179440Z","shell.execute_reply":"2023-11-29T18:51:37.189380Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def create_predictions(test_ds, i, net):\n    net.eval()\n    with torch.no_grad():\n        series_id = test_ds.series_ids[i]\n        data = test_ds.load_data(series_id)\n        X = test_ds[i]['X']\n        pred = torch.zeros(len(X), 2).to(CFG.DEVICE, non_blocking=True)\n\n        h = None\n\n        seq_len = X.shape[0]\n        for j in range(0, seq_len, max_chunk_size):\n            X_chunk = X[j: j + max_chunk_size].float().to(CFG.DEVICE, non_blocking=True)\n            y_pred, h = classifier(X_chunk, h=h)\n            h = [hi.detach() for hi in h]\n            \n            # Trim edges where convolution not done well\n            v = torch.min(y_pred)\n            y_pred[:10, :] = v\n            y_pred[-10:, :] = v\n            \n            pred[j:j+len(X_chunk), :] = y_pred\n\n            del X_chunk, y_pred\n            \n        clean_memory()\n    \n    res_df = pd.DataFrame(torch.softmax(pred.cpu(), axis=0).numpy(), columns=['onset', 'wakeup'])   \n    res_df['step'] = data['step'].values\n    return res_df","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:54:37.369652Z","iopub.execute_input":"2023-11-29T18:54:37.370018Z","iopub.status.idle":"2023-11-29T18:54:37.380355Z","shell.execute_reply.started":"2023-11-29T18:54:37.369987Z","shell.execute_reply":"2023-11-29T18:54:37.379357Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"all_df = []\nwith torch.no_grad():\n    for i in tqdm(range(len(dataset))):\n        series_id = dataset.series_ids[i]\n        # print(series_id)\n        data = dataset.load_data(series_id)\n        res_df = None\n        tmp = create_predictions(dataset, i, classifier)\n        if res_df is None:\n            res_df = tmp\n        else:\n            res_df['onset'] += tmp['onset']\n            res_df['wakeup'] += tmp['wakeup']\n                \n        onset_pred = pd.DataFrame(get_predictions(res_df, target='onset', SIGMA=20), columns=['step', 'event', 'score'])\n        wakeup_pred = pd.DataFrame(get_predictions(res_df, target='wakeup', SIGMA=20), columns=['step', 'event', 'score'])\n        \n        onset_pred['score'] /= onset_pred['score'].max()\n        wakeup_pred['score'] /= wakeup_pred['score'].max()\n        \n        pred_df = pd.concat([wakeup_pred, onset_pred,], axis=0)\n        \n        pred_df['series_id'] = series_id\n        pred_df['row_id'] = pred_df.index\n        pred_df = pred_df.sort_values(by='step').drop_duplicates(subset='step').reset_index(drop=True)\n        min_step, max_step = res_df.step.min(), res_df.step.max()\n        pred_df = pred_df[(pred_df.step > min_step + 12*60) & (pred_df.step < max_step - 12*60)]\n\n        all_df.append(pred_df)\n        clean_memory()\n\n    pred_df = pd.concat(all_df).reset_index(drop=True)\n    pred_df['row_id'] = pred_df.index\n    pred_df = pred_df[['row_id', 'series_id', 'step', 'event', 'score']]\n    pred_df = pred_df.sort_values(by=['series_id', 'step'])","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:54:37.628092Z","iopub.execute_input":"2023-11-29T18:54:37.628456Z","iopub.status.idle":"2023-11-29T18:54:38.682519Z","shell.execute_reply.started":"2023-11-29T18:54:37.628425Z","shell.execute_reply":"2023-11-29T18:54:38.681603Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred_df.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:54:38.684142Z","iopub.execute_input":"2023-11-29T18:54:38.684399Z","iopub.status.idle":"2023-11-29T18:54:38.689790Z","shell.execute_reply.started":"2023-11-29T18:54:38.684376Z","shell.execute_reply":"2023-11-29T18:54:38.688886Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred_df","metadata":{"execution":{"iopub.status.busy":"2023-11-29T18:54:38.695787Z","iopub.execute_input":"2023-11-29T18:54:38.696109Z","iopub.status.idle":"2023-11-29T18:54:38.705170Z","shell.execute_reply.started":"2023-11-29T18:54:38.696083Z","shell.execute_reply":"2023-11-29T18:54:38.704238Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}