{"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 datetime\nimport glob\nimport logging\nimport multiprocessing\nimport os\nimport sys\nimport json\nimport time\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport torch\nfrom scipy.interpolate import interp1d\nfrom sklearn.metrics import average_precision_score\nfrom sklearn.preprocessing import StandardScaler\nfrom torch import nn, optim\nfrom torch.cuda.amp import GradScaler, autocast\nfrom torch.nn.utils.rnn import pad_sequence\nfrom torch.optim import lr_scheduler\nfrom torch.utils.data import DataLoader, Dataset, SubsetRandomSampler\nfrom tqdm.notebook import tqdm\n\nRND_SEED = 0\nGPU_ID = 0\nUSE_GPU = True\n\nif torch.cuda.is_available() and USE_GPU:\n    gpu_name = torch.cuda.get_device_name(GPU_ID)\n    print(f\"Using GPU {GPU_ID} - {gpu_name}\")\n    device = torch.device(f\"cuda:{GPU_ID}\")\nelse:\n    device = torch.device(\"cpu\")\n\nN_CPU_CORES = multiprocessing.cpu_count()\n\nBASE_FOLDER = os.path.join(\n    \"..\", \"input\", \"tlvmc-parkinsons-freezing-gait-prediction\"\n)\n\nprint(f\"Number of CPU cores available: {N_CPU_CORES}\")","metadata":{"execution":{"iopub.status.busy":"2023-07-23T16:21:17.380626Z","iopub.execute_input":"2023-07-23T16:21:17.381387Z","iopub.status.idle":"2023-07-23T16:21:22.674809Z","shell.execute_reply.started":"2023-07-23T16:21:17.381348Z","shell.execute_reply":"2023-07-23T16:21:22.673727Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"class ResidualBiGRU(nn.Module):\n    def __init__(self, hidden_size, n_layers=1):\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=True,\n        )\n        self.fc1 = nn.Linear(hidden_size * 2, hidden_size * 4)\n        self.ln1 = nn.LayerNorm(hidden_size * 4)\n        self.fc2 = nn.Linear(hidden_size * 4, 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  # log probabilities + hidden state\n\n\nclass MultiResidualBiGRU(nn.Module):\n    def __init__(self, input_size, hidden_size, out_size, n_layers):\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        self.fc_in = nn.Linear(input_size, hidden_size)\n        self.ln = nn.LayerNorm(hidden_size)\n        self.res_bigrus = nn.ModuleList(\n            [ResidualBiGRU(hidden_size, n_layers=1) for _ in range(n_layers)]\n        )\n        self.fc_out = nn.Linear(hidden_size, out_size)\n\n    def forward(self, x, h=None):\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\n        return x, new_h  # log probabilities + hidden states","metadata":{"execution":{"iopub.status.busy":"2023-07-23T16:22:05.497183Z","iopub.execute_input":"2023-07-23T16:22:05.497545Z","iopub.status.idle":"2023-07-23T16:22:05.512876Z","shell.execute_reply.started":"2023-07-23T16:22:05.497514Z","shell.execute_reply":"2023-07-23T16:22:05.511856Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Auxiliary functions","metadata":{}},{"cell_type":"code","source":"def get_model_id(model, params, extra_info=\"\", timestamp=\"\"):\n    model_id = f\"{type(model).__name__}\"\n    for k, v in params.items():\n        model_id += f\"_{k}{v}\"\n    if extra_info != \"\":\n        model_id += f\"_{extra_info}\"\n    if timestamp != \"\":\n        model_id += f\"_{timestamp}\"\n    else:\n        now = datetime.datetime.now().strftime(\"%y%m%d%H%M%S\")\n        model_id += f\"_{now}\"\n    return model_id\n\n\ndef read_seq(fpath):\n    seq_id = fpath.split(os.path.sep)[-1].split(\".\")[0]\n    seq = pd.read_csv(fpath)\n    return seq_id, seq\n\n\ndef resample_seq_df(df, in_hz, out_hz, with_classes=True, with_bool_cols=True):\n    in_ms = (1 / in_hz) * 1000\n    out_ms = (1 / out_hz) * 1000\n    FLOAT_COLS = [\"AccV\", \"AccML\", \"AccAP\"]\n    if with_classes:\n        CLASSES_COLS = [\"StartHesitation\", \"Turn\", \"Walking\"]\n    if with_bool_cols:\n        BOOL_COLS = [\"Valid\", \"Task\"]\n\n    df[\"Time\"] = pd.to_timedelta(df[\"Time\"] * in_ms, unit=\"ms\")\n    df = df.set_index(\"Time\")\n\n    resampled_df = (\n        df[FLOAT_COLS]\n        .resample(f\"{out_ms}ms\")\n        .mean()  # new val = \"mean\" in the 7.8125ms interval\n        .interpolate()  # sometimes there is no previous value in the 7.8125ms\n        # interval: we interpolate (linearly by default)\n    )\n\n    cols = []\n    if with_classes:\n        cols = cols + CLASSES_COLS\n    if with_bool_cols:\n        cols = cols + BOOL_COLS\n    if cols != []:\n        resampled_df[cols] = (\n            df[cols]\n            .resample(f\"{out_ms}ms\")\n            .first()\n            .ffill()  # new val = previous val\n        )\n\n    # needed as the introduction of NaNs forced pd to make all cols float\n    if with_classes:\n        resampled_df[CLASSES_COLS] = resampled_df[CLASSES_COLS].astype(int)\n    if with_bool_cols:\n        resampled_df[BOOL_COLS] = resampled_df[BOOL_COLS].astype(bool)\n\n    return resampled_df\n\n\ndef convert_g_to_ms2(df):\n    # 1g = 9.80665m/s^2\n    df[\"AccV\"] = df[\"AccV\"] * 9.80665\n    df[\"AccML\"] = df[\"AccML\"] * 9.80665\n    df[\"AccAP\"] = df[\"AccAP\"] * 9.80665\n    return df\n\n\ndef normalize(seq_features):\n    return StandardScaler().fit_transform(seq_features)\n\n\ndef preprocess_tdcs_seq(seq_df, device, down_hz=None):\n    FEATURES = [\"AccV\", \"AccML\", \"AccAP\"]\n\n    if down_hz is not None:\n        # downsample the data from 128Hz to ??Hz\n        seq_df = resample_seq_df(\n            seq_df, 128, down_hz, with_classes=False, with_bool_cols=False\n        )\n\n    # extracting the features columns and normalizing them\n    seq = seq_df[FEATURES].values\n    seq = normalize(seq)\n    seq = torch.from_numpy(seq).float().to(device)\n    seq = seq.unsqueeze(0)  # adding batch dim\n    return seq\n\n\ndef preprocess_defog_seq(seq_df, device, down_hz=None):\n    if down_hz is None:\n        # upsampling the data from 100Hz to 128Hz\n        # seq = upsample_defog(seq)\n        seq_df = resample_seq_df(\n            seq_df, 100, 128, with_classes=False, with_bool_cols=False\n        )\n    else:\n        # downsample the data from 100Hz to ??Hz\n        seq_df = resample_seq_df(\n            seq_df, 100, down_hz, with_classes=False, with_bool_cols=False\n        )\n\n    # upsampling the data from 100Hz to 128Hz\n    # seq_df = upsample_defog(seq_df, with_categorical_cols=False)\n\n    # defog data is in g: we convert it into m/s^2\n    seq_df = convert_g_to_ms2(seq_df)\n\n    return preprocess_tdcs_seq(seq_df, device)\n\n\ndef predict_lbls_df(model, seq, seq_id):\n    #     with autocast():  # mixed precision\n    pred, h = model(seq)\n    pred = torch.nn.functional.softmax(pred[0], dim=1)\n    pred = pred.cpu().numpy()[:, :3]\n    return pred\n\n\ndef resample_seq(seq_inhz, in_hz, out_hz):\n    out_size = int(seq_inhz.shape[0] * (out_hz / in_hz))\n    time_inhz = np.linspace(0, 1, seq_inhz.shape[0])\n    time_outhz = np.linspace(0, 1, out_size)\n\n    seq_outhz = np.zeros((out_size, seq_inhz.shape[1]))\n\n    for i in range(seq_inhz.shape[1]):\n        interp_func = interp1d(time_inhz, seq_inhz[:, i])\n        seq_outhz[:, i] = interp_func(time_outhz)\n\n    return seq_outhz\n\n\ndef build_res_df(preds):\n    CLASSES = [\"StartHesitation\", \"Turn\", \"Walking\"]\n    steps_ids = [(seq_id + \"_\" + str(i)) for i in range(preds.shape[0])]\n    res_df = pd.DataFrame(data=preds, columns=CLASSES, index=steps_ids)\n    return res_df","metadata":{"execution":{"iopub.status.busy":"2023-07-23T16:22:08.581637Z","iopub.execute_input":"2023-07-23T16:22:08.582007Z","iopub.status.idle":"2023-07-23T16:22:08.605259Z","shell.execute_reply.started":"2023-07-23T16:22:08.581974Z","shell.execute_reply":"2023-07-23T16:22:08.603995Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Submission","metadata":{}},{"cell_type":"code","source":"MODELS_FOLDER = os.path.join(\"..\", \"input\", \"models\")\nmodel_id = \"best_model\" # best model\n# model_id = \"simplified_model\" # simplified model, using only 1 layer instead of 3\n\nparams_path = os.path.join(MODELS_FOLDER, model_id, \"params.json\")\nwith open(params_path, \"r\", encoding=\"utf-8\") as f:\n    PARAMS = json.load(f)\nprint(PARAMS)\n\nmodel = MultiResidualBiGRU(\n    PARAMS[\"ISIZE\"],\n    PARAMS[\"HSIZE\"],\n    PARAMS[\"NC\"],\n    PARAMS[\"NL\"],\n)\n\nmodel_path = os.path.join(MODELS_FOLDER, model_id, \"model.pth\")\nmodel = model.to(device)\nmodel.load_state_dict(torch.load(model_path))\nmodel.eval()","metadata":{"execution":{"iopub.status.busy":"2023-07-23T16:22:12.673129Z","iopub.execute_input":"2023-07-23T16:22:12.673696Z","iopub.status.idle":"2023-07-23T16:22:17.064190Z","shell.execute_reply.started":"2023-07-23T16:22:12.673656Z","shell.execute_reply":"2023-07-23T16:22:17.063225Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"TEST_ROOT_PATH = os.path.join(BASE_FOLDER, \"test\")\ndefog_TEST_PATH = os.path.join(TEST_ROOT_PATH, \"defog\")\ntDCS_TEST_PATH = os.path.join(TEST_ROOT_PATH, \"tdcsfog\")\n\ndefog_test_fpaths = glob.glob(os.path.join(defog_TEST_PATH, \"**\"))\ntdcs_test_fpaths = glob.glob(os.path.join(tDCS_TEST_PATH, \"**\"))\n\nSUB_PATH = os.path.join(BASE_FOLDER, \"sample_submission.csv\")\nsub_df = pd.read_csv(SUB_PATH)\n\n# Id column temporarily becomes the explicit pandas index\nsub_df.set_index(\"Id\", inplace=True)\n\nmodel.eval()\nwith torch.no_grad():\n    for root_path in [tDCS_TEST_PATH, defog_TEST_PATH]:\n        fpaths = glob.glob(os.path.join(root_path, \"**\"))\n        for fpath in fpaths:\n            # reading the sequence and its id\n            seq_id, seq = read_seq(fpath)\n            # out_size = seq.shape[0]  # useful for defog\n\n            # preprocessing the sequence\n            if root_path == defog_TEST_PATH:\n                seq = preprocess_defog_seq(\n                    seq, device, down_hz=PARAMS[\"DOWNHZ\"]\n                )\n            else:\n                seq = preprocess_tdcs_seq(seq, device, down_hz=PARAMS[\"DOWNHZ\"])\n\n            # using the model to predict labels\n            preds = predict_lbls_df(model, seq, seq_id)\n\n            if root_path == defog_TEST_PATH:\n                in_hz = 128 if PARAMS[\"DOWNHZ\"] is None else PARAMS[\"DOWNHZ\"]\n                preds = resample_seq(preds, in_hz, 100)\n            elif PARAMS[\"DOWNHZ\"] is not None:  # tdcs\n                preds = resample_seq(preds, PARAMS[\"DOWNHZ\"], 128)\n\n            res_df = build_res_df(preds)\n\n            # updating the submission dataframe with these partial results\n            sub_df.update(res_df)\n\nmodel.train()\n\n# making Id back to a column and saving the dataframe in a csv file\nsub_df.reset_index(inplace=True)\nsub_df.to_csv(\"submission.csv\", index=False)\n\ndisplay(sub_df)","metadata":{"execution":{"iopub.status.busy":"2023-07-23T16:22:24.312995Z","iopub.execute_input":"2023-07-23T16:22:24.313352Z","iopub.status.idle":"2023-07-23T16:22:38.489806Z","shell.execute_reply.started":"2023-07-23T16:22:24.313321Z","shell.execute_reply":"2023-07-23T16:22:38.488872Z"},"trusted":true},"execution_count":null,"outputs":[]}]}