{"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":7076502,"sourceType":"datasetVersion","datasetId":4002961}],"dockerImageVersionId":30579,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nfrom functools import wraps\nimport time\nimport json\nimport pandas as pd\nimport numpy as np\nimport joblib\nimport gc\nfrom tqdm.auto import tqdm\nimport random, math\nfrom math import sqrt, pi, exp\nfrom scipy.interpolate import interp1d\nimport torch\nfrom torch import nn, Tensor\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset, SubsetRandomSampler\nfrom timm.scheduler import CosineLRScheduler\nfrom sklearn.metrics import average_precision_score\n\nfrom pyarrow.parquet import ParquetFile\nimport pyarrow as pa \nimport ctypes\n\nimport matplotlib.pyplot as plt\nplt.style.use(\"ggplot\")\n\ntorch.set_num_interop_threads(4)\ntorch.set_num_threads(4)\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"Device: {device}\")","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:50:07.395365Z","iopub.execute_input":"2023-11-22T00:50:07.395886Z","iopub.status.idle":"2023-11-22T00:50:13.997355Z","shell.execute_reply.started":"2023-11-22T00:50:07.395857Z","shell.execute_reply":"2023-11-22T00:50:13.996515Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def track_time(f):\n    @wraps(f)\n    def wrap(*args, **kw):\n        ts = time.time()\n        result = f(*args, **kw)\n        te = time.time()\n        print('func:%r took: %2.4f sec' % \\\n          (f.__name__, te-ts))\n        return result\n    return wrap","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:50:15.834264Z","iopub.execute_input":"2023-11-22T00:50:15.835160Z","iopub.status.idle":"2023-11-22T00:50:15.841163Z","shell.execute_reply.started":"2023-11-22T00:50:15.835127Z","shell.execute_reply":"2023-11-22T00:50:15.839986Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"scaler = joblib.load(\"/kaggle/input/sleep-study-gru/minmax_scaler.joblib\")\ncolumns_to_scale = ['anglez', 'enmo']\nclass DataParser:\n    def __init__(self, data_dir: str = \"/kaggle/input/child-mind-institute-detect-sleep-states\") -> None:\n        self.data_dir = data_dir\n\n    @track_time\n    def _clean(self, df: pd.DataFrame) -> pd.DataFrame:\n        if \"step\" in df.columns and \"timestamp\" in df.columns:\n            df = df.dropna(subset=[\"step\", \"timestamp\"])\n            return df\n        else:\n            raise KeyError(\"Missing columns: either `step` or `timestamp` not exist.\")\n\n    @track_time\n    def _transform(self, df: pd.DataFrame) -> pd.DataFrame:\n        if \"night\" in df.columns:\n            df[\"night\"] = df[\"night\"].astype(np.int16)\n\n        if \"step\" in df.columns and \"timestamp\" in df.columns:\n            df[\"step\"] = df[\"step\"].astype(np.int32)\n            df[\"timestamp\"] = pd.to_datetime(df[\"timestamp\"], format=\"%Y-%m-%dT%H:%M:%S%z\", utc=True)\n\n        if \"anglez\" and \"enmo\" in df.columns:\n            normalized_data = scaler.fit_transform(df[columns_to_scale])\n            df['anglez_norm'] = normalized_data[:, 0]\n            df['enmo_norm'] = normalized_data[:, 1]\n            \n        df['hour'] = df['timestamp'].dt.hour\n\n        return df\n\n    def load_data(self, file_name: str, file_type: str) -> pd.DataFrame:\n        if file_type == \"parquet\":\n            df = pd.read_parquet(os.path.join(self.data_dir, file_name))\n            df = self._clean(df)\n            df = self._transform(df)\n        else:\n            df = pd.read_csv(os.path.join(self.data_dir, file_name))\n            df = self._clean(df)\n            \n        return df","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:50:16.434567Z","iopub.execute_input":"2023-11-22T00:50:16.435207Z","iopub.status.idle":"2023-11-22T00:50:16.453560Z","shell.execute_reply.started":"2023-11-22T00:50:16.435173Z","shell.execute_reply":"2023-11-22T00:50:16.452525Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"parser = DataParser()\ntest_series = parser.load_data(\"test_series.parquet\", \"parquet\")\nids = test_series.series_id.unique()\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:51:42.510391Z","iopub.execute_input":"2023-11-22T00:51:42.511502Z","iopub.status.idle":"2023-11-22T00:51:42.707839Z","shell.execute_reply.started":"2023-11-22T00:51:42.511455Z","shell.execute_reply":"2023-11-22T00:51:42.706885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ResidualBiGRU(nn.Module):\n    def __init__(self, hidden_size, n_layers=1, bidir=True):\n        super(ResidualBiGRU, self).__init__()\n\n        self.hidden_size = hidden_size\n        self.n_layers = n_layers\n\n        self.gru = nn.GRU(\n            hidden_size,\n            hidden_size,\n            n_layers,\n            batch_first=True,\n            bidirectional=bidir,\n        )\n        dir_factor = 2 if bidir else 1\n        self.fc1 = nn.Linear(\n            hidden_size * dir_factor, hidden_size * dir_factor * 2\n        )\n        self.ln1 = nn.LayerNorm(hidden_size * dir_factor * 2)\n        self.fc2 = nn.Linear(hidden_size * dir_factor * 2, hidden_size)\n        self.ln2 = nn.LayerNorm(hidden_size)\n\n    def forward(self, x, h=None):\n        res, new_h = self.gru(x, h)\n        # res.shape = (batch_size, sequence_size, 2*hidden_size)\n\n        res = self.fc1(res)\n        res = self.ln1(res)\n        res = nn.functional.relu(res)\n        \n        res = self.fc2(res)\n        res = self.ln2(res)\n        res = nn.functional.relu(res)\n        \n        # skip connection\n        res = res + x\n\n        return res, new_h\n\nclass MultiResidualBiGRU(nn.Module):\n    def __init__(self, input_size, hidden_size, out_size, n_layers, kernel_size=3, bidir=True):\n        super(MultiResidualBiGRU, self).__init__()\n\n        self.input_size = input_size\n        self.hidden_size = hidden_size\n        self.out_size = out_size\n        self.n_layers = n_layers\n\n        # Convolutional layer\n        self.conv1d = nn.Conv1d(in_channels=input_size, out_channels=(hidden_size - input_size) // 2, kernel_size=kernel_size, padding=kernel_size // 2)\n        self.conv_gn = nn.GroupNorm(1, (hidden_size - input_size) // 2)\n\n        self.conv1d_5 = nn.Conv1d(in_channels=input_size, out_channels=(hidden_size - input_size) // 2, kernel_size=5, padding=2)\n        self.conv1d_gn_5 = nn.GroupNorm(1, (hidden_size - input_size) // 2)\n\n        self.res_bigrus = nn.ModuleList([\n            ResidualBiGRU(hidden_size, n_layers=1, bidir=bidir) for _ in range(n_layers)\n        ])\n        \n        self.fc_out = nn.Linear(hidden_size, out_size)\n\n    def forward(self, x, h=None):\n        # Transpose x to match Conv1d input shape (batch_size, channels, sequence_length)\n        x = x.transpose(1, 2)\n\n        # Convolutional layer forward pass\n        # x = self.conv1d(x)\n        # x = self.conv_gn(x)\n        # x = nn.functional.relu(x)\n\n        x1 = self.conv1d(x)\n        x1 = self.conv_gn(x1)\n        x1 = nn.functional.relu(x1)\n\n        x2 = self.conv1d_5(x)\n        x2 = self.conv1d_gn_5(x2)\n        x2 = nn.functional.relu(x2)\n        \n        x = torch.cat((x, x1, x2), dim=1)\n        x = x.transpose(1, 2)\n        \n        # if we are at the beginning of a sequence (no hidden state)\n        if h is None:\n            # (re)initialize the hidden state\n            h = [None for _ in range(self.n_layers)]\n\n        # x = self.fc_in(x)\n        # x = self.ln(x)\n        # x = nn.functional.relu(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        x = self.fc_out(x)\n        return x, new_h  # log probabilities + hidden states","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:51:42.997283Z","iopub.execute_input":"2023-11-22T00:51:42.997650Z","iopub.status.idle":"2023-11-22T00:51:43.015748Z","shell.execute_reply.started":"2023-11-22T00:51:42.997620Z","shell.execute_reply":"2023-11-22T00:51:43.014393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"SAMPLE_FREQ = 12","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:51:43.382245Z","iopub.execute_input":"2023-11-22T00:51:43.382634Z","iopub.status.idle":"2023-11-22T00:51:43.387198Z","shell.execute_reply.started":"2023-11-22T00:51:43.382601Z","shell.execute_reply":"2023-11-22T00:51:43.386152Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SleepDataset(Dataset):\n    def __init__(\n        self,\n        series_ids,\n        series,\n    ):\n        series_ids = series_ids\n        series = series.reset_index()\n        self.data = []\n        \n        for viz_id in tqdm(series_ids):\n            self.data.append(series.loc[(series.series_id==viz_id)].copy().reset_index())\n            \n    def downsample_seq_generate_features(self, feat, downsample_factor=SAMPLE_FREQ, std_only=False, is_hour=False):\n        if len(feat) % downsample_factor != 0:\n            feat = np.concatenate([feat, np.zeros(downsample_factor-((len(feat))%downsample_factor))+feat[-1]])\n\n        feat = np.reshape(feat, (-1, downsample_factor))\n        \n        if is_hour:\n            feat_hour = np.max(feat, 1)\n            hour_sin = np.sin(feat_hour * (2 * np.pi / 24))\n            hour_cos = np.cos(feat_hour * (2 * np.pi / 24))\n            return np.dstack([hour_sin, hour_cos])[0]\n\n        feat_mean   = np.mean(feat,1)\n        feat_std    = np.std(feat,1)\n        feat_median = np.median(feat,1)\n        feat_max    = np.max(feat,1)\n        feat_min    = np.min(feat,1)\n\n        if std_only:\n            return np.dstack([feat_std])[0]\n    \n        return np.dstack([feat_mean, feat_std, feat_median, feat_max, feat_min])[0]\n    \n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, index):\n        X = self.data[index][[\"anglez_norm\", \"enmo_norm\", \"hour\"]]\n        \n        X_anglez = self.downsample_seq_generate_features(X.values[:, 0], SAMPLE_FREQ, std_only=True)\n        X_enmo   = self.downsample_seq_generate_features(X.values[:, 1], SAMPLE_FREQ)\n        X_hour   = self.downsample_seq_generate_features(X.values[:, 2], SAMPLE_FREQ, is_hour=True)\n        \n        X = np.concatenate([X_anglez, X_enmo, X_hour], -1)\n        X = torch.from_numpy(X)\n        return X\n    \ntest_ds = SleepDataset(test_series.series_id.unique(),test_series)\ndel test_series\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:51:43.981468Z","iopub.execute_input":"2023-11-22T00:51:43.981886Z","iopub.status.idle":"2023-11-22T00:51:44.190010Z","shell.execute_reply.started":"2023-11-22T00:51:43.981852Z","shell.execute_reply":"2023-11-22T00:51:44.189018Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"max_chunk_size = 150000","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:51:44.983834Z","iopub.execute_input":"2023-11-22T00:51:44.984606Z","iopub.status.idle":"2023-11-22T00:51:44.988792Z","shell.execute_reply.started":"2023-11-22T00:51:44.984570Z","shell.execute_reply":"2023-11-22T00:51:44.987796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def filter_close_indices_efficient(indices, threshold=5):\n    # Simulate the values as the indices themselves\n    values = indices\n    # Calculate the differences between adjacent elements\n    differences = np.diff(indices)\n    # Identify indices where the difference is less than or equal to the threshold\n    close_indices = np.where(differences <= threshold)[0]\n    # For each pair of close indices, keep the one with the higher value\n    # This is simplified as we are using the indices as values\n    to_remove = [i for i in close_indices if values[i] < values[i + 1]]\n    # Remove the indices that are too close and have smaller values\n    filtered_indices = np.delete(indices, to_remove)\n\n    return filtered_indices","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = MultiResidualBiGRU(input_size=8, hidden_size=64, out_size=2, n_layers=5).to(device)\nmodel.load_state_dict(torch.load(f'/kaggle/input/sleep-study-gru/model_best_30.pth',map_location=device))\nsubmission = pd.DataFrame()\nfor i in range(len(test_ds)):\n    X = test_ds[i]\n    X = X.unsqueeze(0)\n    \n    seq_len = X.shape[1]\n    X = X.to(device)\n    h = None\n    pred = torch.zeros((seq_len,2)).half()\n    pred = torch.zeros((seq_len,2)).half()\n    for j in range(0, seq_len, max_chunk_size):\n        y_pred, h = model(X[j: j + max_chunk_size].float(), h)\n        h = [hi.detach() for hi in h]\n        pred[j : j + max_chunk_size] = y_pred.detach()\n        del y_pred;gc.collect()\n    del h,X;gc.collect()\n    pred = pred.numpy()\n    series_id = ids[i]\n    \n    pred[pred < 0.0] = 0.0\n    days = len(pred)/(17280/SAMPLE_FREQ)\n    scores0,scores1 = np.zeros(len(pred),dtype=np.float16),np.zeros(len(pred),dtype=np.float16)\n    for min_interval in [6, 12, 30, 60, 90]:\n        for index in range(len(pred)):\n            if pred[index,0]==max(pred[max(0,index-min_interval):index+min_interval,0]):\n                scores0[index] = max(pred[max(0,index-min_interval):index+min_interval,0])\n            if pred[index,1]==max(pred[max(0,index-min_interval):index+min_interval,1]):\n                scores1[index] = max(pred[max(0,index-min_interval):index+min_interval,1])\n    # candidates_onset = np.argsort(scores0)[-max(1,round(days)):]\n    # candidates_wakeup = np.argsort(scores1)[-max(1,round(days)):]\n    \n    # remove close candidate for onset\n    candidates_onset = np.argsort(scores0)[-max(1,round(days)):]\n    candidates_onset = filter_close_indices_efficient(np.sort(candidates_onset))\n    # remove close candidate for wakeup\n    candidates_wakeup = np.argsort(scores1)[-max(1,round(days)):]\n    candidates_wakeup = filter_close_indices_efficient(np.sort(candidates_wakeup))\n    \n    onset = test_ds.data[i][['step']].iloc[np.clip(candidates_onset*SAMPLE_FREQ,0,len(test_ds.data[i])-1)].astype(np.int32)\n    onset['event'] = 'onset'\n    onset['series_id'] = series_id\n    onset['score']= scores0[candidates_onset]\n    wakeup = test_ds.data[i][['step']].iloc[np.clip(candidates_wakeup*SAMPLE_FREQ,0,len(test_ds.data[i])-1)].astype(np.int32)\n    wakeup['event'] = 'wakeup'\n    wakeup['series_id'] = series_id\n    wakeup['score']= scores1[candidates_wakeup]\n    submission = pd.concat([submission,onset,wakeup],axis=0)\n    del onset,wakeup,candidates_onset,candidates_wakeup,scores0,scores1,pred,series_id,\n    gc.collect()\nsubmission = submission.sort_values(['series_id','step']).reset_index(drop=True)\nsubmission['row_id'] = submission.index.astype(int)\nsubmission['score'] = submission['score'].fillna(submission['score'].mean())\nsubmission = submission[['row_id','series_id','step','event','score']]\nsubmission.to_csv('submission.csv',index=False)","metadata":{"execution":{"iopub.status.busy":"2023-11-22T01:07:20.140112Z","iopub.execute_input":"2023-11-22T01:07:20.140574Z","iopub.status.idle":"2023-11-22T01:07:22.453441Z","shell.execute_reply.started":"2023-11-22T01:07:20.140545Z","shell.execute_reply":"2023-11-22T01:07:22.452373Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission","metadata":{"execution":{"iopub.status.busy":"2023-11-22T00:51:49.794935Z","iopub.execute_input":"2023-11-22T00:51:49.795327Z","iopub.status.idle":"2023-11-22T00:51:49.810577Z","shell.execute_reply.started":"2023-11-22T00:51:49.795298Z","shell.execute_reply":"2023-11-22T00:51:49.809695Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}