{"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":"markdown","source":"# Description\n\n##### The OOF of the model named \"model_best_fold_0_imu_v4\";\n##### The inference process is: (1) prediction at wifi observations, (ii) interpolation these prediction to the true waypoint observation, (iii) Saito's post-processing + magnetic-fields based step adjustment, (iv) push-to-corridor post-processing;\n\n##### This version: \n* $\\alpha = 7.2, \\beta = 0.3$;\n* Use a modified step-heading estimating function.\n\n##### This model score profile is:\n* OOF CV before interpolation and before post-processing (MPE at wifi observations): 6.358;\n* OOF CV after interpolation and before post-processing (MPE at waypoint observations): 6.773;\n* Public LB after interpolation and before post-processing: 6.461;\n* OOF CV after interpolation and after post-processing: ;\n* Public LB after interpolation and after post-processing: 3.937;\n* Moving average of headings, time window: 50 steps (1 second).","metadata":{"papermill":{"duration":0.042063,"end_time":"2021-05-09T15:52:55.256711","exception":false,"start_time":"2021-05-09T15:52:55.214648","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Necessary packages","metadata":{"papermill":{"duration":0.038179,"end_time":"2021-05-09T15:52:55.33272","exception":false,"start_time":"2021-05-09T15:52:55.294541","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nfrom glob import glob\nimport numpy as np\nimport math\nimport pandas as pd\nfrom tqdm.notebook import tqdm\nimport copy\nimport pickle as pkl\nimport json\nimport matplotlib.pyplot as plt\nimport multiprocessing\n\nimport scipy\nfrom scipy.ndimage import gaussian_filter1d\nfrom scipy.spatial.distance import cdist\nfrom sklearn.model_selection import GroupKFold\n\nimport torch\nfrom torch import nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim import AdamW, lr_scheduler\n\nimport gc\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","papermill":{"duration":2.533465,"end_time":"2021-05-09T15:52:57.904221","exception":false,"start_time":"2021-05-09T15:52:55.370756","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Import data","metadata":{"papermill":{"duration":0.037386,"end_time":"2021-05-09T15:52:57.981737","exception":false,"start_time":"2021-05-09T15:52:57.944351","status":"completed"},"tags":[]}},{"cell_type":"code","source":"ts_data = glob('../input/iln-interpolate-waypoints-using-imu-data/*.csv')\nts = {}\nfor site in tqdm(ts_data):\n    df = pd.read_csv(site, usecols = ['wifi_ts'])\n    site = site.split('/')[-1].split('_')[0]\n    ts[site] = df.values","metadata":{"papermill":{"duration":21.038413,"end_time":"2021-05-09T15:53:19.058516","exception":false,"start_time":"2021-05-09T15:52:58.020103","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_dir = '../input/indoor-with-delta'\nfiles = glob(f'{data_dir}/*_train.csv')\n\ntrain_data = pd.read_pickle(os.path.join(data_dir, 'train_all.pkl'))\ntrain_data['timestamp'] = np.zeros(train_data.shape[0], dtype = int)","metadata":{"papermill":{"duration":27.146424,"end_time":"2021-05-09T15:53:46.244095","exception":false,"start_time":"2021-05-09T15:53:19.097671","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Add timestamp","metadata":{"papermill":{"duration":0.038652,"end_time":"2021-05-09T15:53:46.321698","exception":false,"start_time":"2021-05-09T15:53:46.283046","status":"completed"},"tags":[]}},{"cell_type":"code","source":"ts = []\nfor file in tqdm(files):\n    df = pd.read_csv(file, usecols = ['wifi_ts', 'path']).sort_values('wifi_ts')\n    ts.append(df)\n    \nts = pd.concat(ts)\nts = ts.groupby('path').apply(lambda x: x['wifi_ts'].values).to_dict()","metadata":{"papermill":{"duration":25.560749,"end_time":"2021-05-09T15:54:11.921153","exception":false,"start_time":"2021-05-09T15:53:46.360404","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"timestamp = []\n\nfor path in tqdm(train_data['path'].unique()):\n    timestamp.append(ts[path])\ntrain_data['timestamp'] = np.concatenate(timestamp)","metadata":{"papermill":{"duration":0.139138,"end_time":"2021-05-09T15:54:12.099829","exception":false,"start_time":"2021-05-09T15:54:11.960691","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Format the data","metadata":{"papermill":{"duration":0.039321,"end_time":"2021-05-09T15:54:12.17955","exception":false,"start_time":"2021-05-09T15:54:12.140229","status":"completed"},"tags":[]}},{"cell_type":"code","source":"train_data.rename(columns = {'site': 'building'}, inplace = True)\ntrain_data['group'] = train_data['path'].map(dict(zip(train_data['path'].unique(), range(train_data['path'].nunique()))))\ntrain_data['site_path_timestamp'] = ['_'.join([i,j,str(k)]) for i, j, k in zip(train_data.building, train_data.path, train_data.timestamp)]\ntrain_data.head()","metadata":{"papermill":{"duration":0.547881,"end_time":"2021-05-09T15:54:12.767175","exception":false,"start_time":"2021-05-09T15:54:12.219294","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('The number of NaN values is:')\nprint(train_data.isna().any().any())\nprint('Data information:')\ntrain_data.info()","metadata":{"papermill":{"duration":8.12481,"end_time":"2021-05-09T15:54:20.932749","exception":false,"start_time":"2021-05-09T15:54:12.807939","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Wifi encoder and building map","metadata":{"papermill":{"duration":0.041354,"end_time":"2021-05-09T15:54:21.016666","exception":false,"start_time":"2021-05-09T15:54:20.975312","status":"completed"},"tags":[]}},{"cell_type":"code","source":"with open('../input/iln-wifi-and-building-mapping/label_encoder_bssid.pkl', 'rb') as f:\n    lbl_bssid = pkl.load(f)\n    \nwith open('../input/iln-wifi-and-building-mapping/building_map.json', 'r') as f:\n    building_map = json.load(f)\n\nbssid_map = dict(zip(lbl_bssid.classes_, lbl_bssid.transform(lbl_bssid.classes_)))","metadata":{"papermill":{"duration":0.755552,"end_time":"2021-05-09T15:54:21.814511","exception":false,"start_time":"2021-05-09T15:54:21.058959","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Encode BSSID\nbssid_features = [i for i in train_data.columns if i.startswith('bssid_')]\nrssi_features = [i for i in train_data.columns if i.startswith('rssi_')]\ntimegap_features = [i for i in train_data.columns if i.startswith('gap_')]\ntrain_data[bssid_features] = train_data[bssid_features].applymap(lambda x: bssid_map[x])","metadata":{"papermill":{"duration":190.502148,"end_time":"2021-05-09T15:57:32.36155","exception":false,"start_time":"2021-05-09T15:54:21.859402","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data[bssid_features][train_data[rssi_features] == -999] = 0","metadata":{"papermill":{"duration":270.927621,"end_time":"2021-05-09T16:02:03.331342","exception":false,"start_time":"2021-05-09T15:57:32.403721","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Encode building\ntrain_data['building'] = train_data.building.map(building_map)\ntrain_data.head()","metadata":{"papermill":{"duration":1.671948,"end_time":"2021-05-09T16:02:05.049786","exception":false,"start_time":"2021-05-09T16:02:03.377838","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* IMU data","metadata":{"papermill":{"duration":0.042862,"end_time":"2021-05-09T16:02:05.138611","exception":false,"start_time":"2021-05-09T16:02:05.095749","status":"completed"},"tags":[]}},{"cell_type":"code","source":"imu_data = pd.read_pickle('../input/ilnaggregated-imu/train_imu_all.pkl')\nimu_data['path'] = [i.split('_')[-1] for i in imu_data['site_floor_path']]\nimu_data.head()","metadata":{"papermill":{"duration":2.571436,"end_time":"2021-05-09T16:02:07.752817","exception":false,"start_time":"2021-05-09T16:02:05.181381","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Add user ID","metadata":{"papermill":{"duration":0.044329,"end_time":"2021-05-09T16:02:07.851313","exception":false,"start_time":"2021-05-09T16:02:07.806984","status":"completed"},"tags":[]}},{"cell_type":"code","source":"users = pd.read_csv('../input/retrieving-user-id-from-leaked-wifi-feature/df.csv', usecols = ['path_id', 'user_id'])\nuser_map = dict(zip(users['path_id'], users['user_id']))","metadata":{"papermill":{"duration":0.220376,"end_time":"2021-05-09T16:02:08.116164","exception":false,"start_time":"2021-05-09T16:02:07.895788","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data['user_id'] = train_data['path'].map(user_map)\ntrain_data.head()","metadata":{"papermill":{"duration":0.139586,"end_time":"2021-05-09T16:02:08.299746","exception":false,"start_time":"2021-05-09T16:02:08.16016","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Feature extraction","metadata":{"papermill":{"duration":0.045782,"end_time":"2021-05-09T16:02:08.39382","exception":false,"start_time":"2021-05-09T16:02:08.348038","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def feature_extraction_wifi(x):\n    # Index\n    idx = x.index.values.astype(int)\n    \n    # Path ID\n    path = x['path'].unique()[0]\n    \n    # Building\n    building = x['building'].unique().astype(int)\n    \n    # User ID\n    user = x['user_id'].unique().astype(int)\n    \n    # BSSID\n    bssid_feature = x[bssid_features].values.astype(int)\n    \n    # RSSI\n    rssi_feature = x[rssi_features].values.astype(float)\n    for i in range(len(rssi_features)):\n        rssi_feature[:,i] = gaussian_filter1d(rssi_feature[:,i], sigma = 2)\n    \n    # Timegap\n    timegap_feature = x[timegap_features].values.astype(float)\n    \n    # Delta waypoints\n    del_waypoints = x[['delta_x_hat', 'delta_y_hat']].values.astype(float)\n    \n    # Waypoints\n    waypoints = x[['x', 'y']].values.astype(float)\n    \n    # Floor\n    floor = x['floor'].unique().astype(int) + 2\n    \n    return path, idx, building, user, bssid_feature, rssi_feature, timegap_feature, del_waypoints, waypoints, floor","metadata":{"papermill":{"duration":0.059037,"end_time":"2021-05-09T16:02:08.499035","exception":false,"start_time":"2021-05-09T16:02:08.439998","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Raw IMU\nacce_ = ['acce_x', 'acce_y', 'acce_z']\ngyro_ = ['gyro_x', 'gyro_y', 'gyro_z']\nmagn_ = ['magn_x', 'magn_y', 'magn_z']\nahrs_ = ['ahrs_x', 'ahrs_y', 'ahrs_z']\nimu_ = acce_ + gyro_ + magn_ + ahrs_\n\ndef feature_extraction_imu(df):\n    imu = df[imu_].values.astype(int)\n    return imu\n\nimu_group = imu_data.groupby('path').apply(feature_extraction_imu)","metadata":{"papermill":{"duration":6.985568,"end_time":"2021-05-09T16:02:15.529103","exception":false,"start_time":"2021-05-09T16:02:08.543535","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Drop the Wifi data points that do not have the corresponding IMU data","metadata":{"papermill":{"duration":0.043914,"end_time":"2021-05-09T16:02:15.617095","exception":false,"start_time":"2021-05-09T16:02:15.573181","status":"completed"},"tags":[]}},{"cell_type":"code","source":"drop_path = train_data.path.unique()[np.in1d(train_data.path.unique(), np.array(imu_group.index))]\ntrain_data = train_data[train_data['path'].isin(drop_path.tolist())].reset_index(drop = True)","metadata":{"papermill":{"duration":9.237515,"end_time":"2021-05-09T16:02:24.900573","exception":false,"start_time":"2021-05-09T16:02:15.663058","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataset","metadata":{"papermill":{"duration":0.043901,"end_time":"2021-05-09T16:02:24.988986","exception":false,"start_time":"2021-05-09T16:02:24.945085","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class ILN_Dataset(Dataset):\n    def __init__(self, group, imu_group, max_len_wifi = 512, max_len_imu = 25_000):\n        self.group = group\n        self.imu_group = imu_group\n        self.max_len_wifi = max_len_wifi\n        self.max_len_imu = max_len_imu\n        \n    def __len__(self):\n        return len(self.group)\n    \n    def __getitem__(self, idx):\n        # Extract sequences\n        path, idx, building, user, bssid_feature, rssi_feature, timegap_feature, del_waypoints, waypoints, floor = self.group.iloc[idx]\n        \n        # Load IMU data\n        imu = self.imu_group.loc[path]\n        \n        seq_len_imu = imu.shape[0]\n        mask_imu = np.ones(seq_len_imu)\n        mask_imu_ = np.zeros(self.max_len_imu)\n        \n        # Sequence length\n        seq_len_wifi = bssid_feature.shape[0]\n        mask = np.ones(seq_len_wifi)\n        \n        mask_ = np.zeros(self.max_len_wifi)\n        idx_ = np.zeros(self.max_len_wifi)\n        bssid_feature_ = np.zeros((self.max_len_wifi, bssid_feature.shape[1]))\n        rssi_feature_ = np.zeros((self.max_len_wifi, rssi_feature.shape[1]))\n        timegap_feature_ = np.zeros((self.max_len_wifi, timegap_feature.shape[1]))\n        del_waypoints_ = np.zeros((self.max_len_wifi, del_waypoints.shape[1]))\n        waypoints_ = np.zeros((self.max_len_wifi, waypoints.shape[1]))\n        \n        imu_ = np.zeros((self.max_len_imu, imu.shape[1]))\n        \n        if seq_len_wifi <= self.max_len_wifi:   # Pad\n            mask_[-seq_len_wifi:] = mask\n            idx_[-seq_len_wifi:] = idx\n            bssid_feature_[-seq_len_wifi:,:] = bssid_feature\n            rssi_feature_[-seq_len_wifi:,:] = rssi_feature\n            timegap_feature_[-seq_len_wifi:,:] = timegap_feature\n            del_waypoints_[-seq_len_wifi:,:] = del_waypoints\n            waypoints_[-seq_len_wifi:,:] = waypoints\n        else:    # Cut\n            mask_ = mask[-self.max_len_wifi:]\n            idx_ = idx[-self.max_len_wifi:]\n            bssid_feature_ = bssid_feature[-self.max_len_wifi:,:]\n            rssi_feature_ = rssi_feature[-self.max_len_wifi:,:]\n            timegap_feature_ = timegap_feature[-self.max_len_wifi:,:]\n            del_waypoints_ = del_waypoints[-self.max_len_wifi:,:]\n            waypoints_ = waypoints[-self.max_len_wifi:,:]\n        \n        if seq_len_imu > 0:\n            if seq_len_imu <= self.max_len_imu:   # Pad\n                mask_imu_[-seq_len_imu:] = mask_imu\n                imu_[-seq_len_imu:,:] = imu\n            else:    # Cut\n                mask_imu_ = mask_imu[-self.max_len_imu:]\n                imu_ = imu[-self.max_len_imu:,:]\n            \n        return {\n            'mask': torch.tensor(mask_, dtype = torch.bool),\n            'mask_imu': torch.tensor(mask_imu_, dtype = torch.bool),\n            'idx': torch.tensor(idx_, dtype = torch.int),\n            'imu': torch.tensor(imu_, dtype = torch.float),\n            'building': torch.tensor(building, dtype = torch.long),\n            'user': torch.tensor(user, dtype = torch.long),\n            'bssid': torch.tensor(bssid_feature_, dtype = torch.long),\n            'rssi': torch.tensor(rssi_feature_, dtype = torch.float),\n            'timegap': torch.tensor(timegap_feature_, dtype = torch.float),\n            'del_waypoints': torch.tensor(del_waypoints_, dtype = torch.float),\n            'waypoints': torch.tensor(waypoints_, dtype = torch.float),\n            'floor': torch.tensor(floor, dtype = torch.long)\n        }","metadata":{"papermill":{"duration":0.069385,"end_time":"2021-05-09T16:02:25.102585","exception":false,"start_time":"2021-05-09T16:02:25.0332","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{"papermill":{"duration":0.044437,"end_time":"2021-05-09T16:02:25.191306","exception":false,"start_time":"2021-05-09T16:02:25.146869","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def clones(module, N):\n    \"Produce N identical layers.\"\n    return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])\n\ndef attention(query, key, value, mask=None, dropout=None):\n    \"Compute 'Scaled Dot Product Attention'\"\n    d_k = query.size(-1)\n    scores = torch.matmul(query, key.transpose(-2, -1)) \\\n             / math.sqrt(d_k)\n    if mask is not None:\n        scores = scores.masked_fill(mask == 0, -1e9)\n    p_attn = F.softmax(scores, dim=-1)\n    if dropout is not None:\n        p_attn = dropout(p_attn)\n    return torch.matmul(p_attn, value), p_attn\n\n\nclass MultiHeadedAttention(nn.Module):\n    def __init__(self, d_model, nhead, dropout=0.1):\n        \"Take in model size and number of heads.\"\n        super(MultiHeadedAttention, self).__init__()\n        assert d_model % nhead == 0\n        # We assume d_v always equals d_k\n        self.d_k = d_model // nhead\n        self.nhead = nhead\n        self.linears = clones(nn.Linear(d_model, d_model, bias=False), 4) # Q, K, V, last\n        self.attn = None\n        self.dropout = nn.Dropout(p=dropout)\n\n    def forward(self, query, key, value, mask=None):\n        \"Implements Figure 2\"\n        if mask is not None:\n            # Same mask applied to all h heads.\n            mask = mask.unsqueeze(1)\n        nbatches = query.size(0)\n\n        # 1) Do all the linear projections in batch from d_model => h x d_k\n        query, key, value = \\\n            [l(x).view(nbatches, -1, self.nhead, self.d_k).transpose(1, 2)\n             for l, x in zip(self.linears, (query, key, value))]\n\n        # 2) Apply attention on all the projected vectors in batch.\n        x, self.attn = attention(query, key, value, mask=mask,\n                                 dropout=self.dropout)\n\n        # 3) \"Concat\" using a view and apply a final linear.\n        x = x.transpose(1, 2).contiguous() \\\n            .view(nbatches, -1, self.nhead * self.d_k)\n        return self.linears[-1](x)\n\n\nclass PositionwiseFeedForward(nn.Module):\n    \"Implements FFN equation.\"\n    def __init__(self, d_model, d_ff, dropout=0.1):\n        super(PositionwiseFeedForward, self).__init__()\n        self.w_1 = nn.Linear(d_model, d_ff)\n        self.w_2 = nn.Linear(d_ff, d_model)\n        self.dropout = nn.Dropout(dropout)\n\n    def forward(self, x):\n        return self.w_2(self.dropout(F.relu(self.w_1(x))))\n    \nclass EncoderLayer(nn.Module):\n    \"\"\"\n    Single Encoder block of SAINT\n    \"\"\"\n    def __init__(self, d_model, nhead, dim_feedforward = 1024, dropout = 0.1):\n        super().__init__()\n        self._self_attn = MultiHeadedAttention(d_model, nhead, dropout)\n        self._ffn = PositionwiseFeedForward(d_model, dim_feedforward, dropout)\n        self._layernorms = clones(nn.LayerNorm(d_model, eps=1e-6), 2)\n        self._dropout = nn.Dropout(dropout)\n\n    def forward(self, src, mask = None):\n        \"\"\"\n        query: question embeddings\n        key: interaction embeddings\n        \"\"\"\n        # self-attention block\n        src2 = self._self_attn(query=src, key=src, value=src, mask=mask)\n        src = src + self._dropout(src2)\n        src = self._layernorms[0](src)\n        src2 = self._ffn(src)\n        src = src + self._dropout(src2)\n        src = self._layernorms[1](src)\n        return src","metadata":{"papermill":{"duration":0.069387,"end_time":"2021-05-09T16:02:25.30492","exception":false,"start_time":"2021-05-09T16:02:25.235533","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Lin_MultiHeadedAttention(nn.Module):\n    def __init__(self, d_model, nhead, dropout=0.1, max_len = 512, target_len = 400):\n        \"Take in model size and number of heads.\"\n        super(Lin_MultiHeadedAttention, self).__init__()\n        assert d_model % nhead == 0\n        # We assume d_v always equals d_k\n        self.d_k = d_model // nhead\n        self.nhead = nhead\n        self.linears = clones(nn.Linear(d_model, d_model, bias=False), 4) # Q, K, V, last\n        self.linear_key_value = clones(nn.Linear(max_len, 256, bias= False), 3)\n        self.linear_query = nn.Linear(max_len, target_len)\n        self.attn = None\n        self.dropout = nn.Dropout(p=dropout)\n\n    def forward(self, query, key, value, mask=None):\n        \"Implements Figure 2\"\n        if mask is not None:\n            # Same mask applied to all h heads.\n            mask = mask.unsqueeze(-1)\n            # Mask query, key and value\n            query = query * mask\n            key = key * mask\n            mask = value * mask\n        nbatches = query.size(0)\n        \n        # 1) Do all the linear projections in batch from d_model => h x d_k\n        query, key, value = \\\n            [l(x).view(nbatches, -1, self.nhead, self.d_k).transpose(1, 2)\n             for l, x in zip(self.linears, (query, key, value))]\n        \n        # 2) Linear projections\n        key, value = \\\n            [l(x).transpose(-1, -2)\n             for l, x in zip(self.linear_key_value, (key.transpose(-1,-2), value.transpose(-1,-2)))]\n        \n        query = self.linear_query(query.transpose(-1, -2)).transpose(-1, -2)\n\n        # 3) Apply attention on all the projected vectors in batch.\n        x, self.attn = attention(query, key, value, mask=None,\n                                 dropout=self.dropout)\n\n        # 4) \"Concat\" using a view and apply a final linear.\n        x = x.transpose(1, 2).contiguous() \\\n            .view(nbatches, -1, self.nhead * self.d_k)\n        return self.linears[-1](x)\n    \nclass Lin_EncoderLayer(nn.Module):\n    \"\"\"\n    Single Encoder block of SAINT\n    \"\"\"\n    def __init__(self, d_model, nhead, dim_feedforward = 1024, dropout = 0.1, max_len = 512, target_len = 400):\n        super().__init__()\n        self._self_attn = Lin_MultiHeadedAttention(d_model, nhead, dropout = dropout, max_len = max_len, target_len = target_len)\n        self._ffn = PositionwiseFeedForward(d_model, dim_feedforward, dropout)\n        self._layernorms = clones(nn.LayerNorm(d_model, eps=1e-6), 2)\n        self._dropout = nn.Dropout(dropout)\n\n    def forward(self, src, mask = None):\n        \"\"\"\n        query: question embeddings\n        key: interaction embeddings\n        \"\"\"\n        # self-attention block\n        src2 = self._self_attn(query=src, key=src, value=src, mask=mask)\n        src = self._dropout(src2)\n        src = self._layernorms[0](src)\n        src2 = self._ffn(src)\n        src = src + self._dropout(src2)\n        src = self._layernorms[1](src)\n        return src","metadata":{"papermill":{"duration":0.066408,"end_time":"2021-05-09T16:02:25.415893","exception":false,"start_time":"2021-05-09T16:02:25.349485","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class PositionalEncoding(nn.Module):\n\n    def __init__(self, d_model, dropout=0.1, max_len=5000):\n        super(PositionalEncoding, self).__init__()\n        self.max_len = max_len\n        self.dropout = nn.Dropout(p=dropout)\n        self.div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))\n\n    def forward(self, x, mask = None):\n        if mask is not None:\n            position = torch.cumsum(mask.unsqueeze(-1), dim = 1)\n        else:\n            position = torch.repeat_interleave(torch.arange(0, self.max_len).unsqueeze(-1).unsqueeze(0), x.shape[0], dim = 0)\n        \n        pe = torch.zeros(x.shape).to(x.device)\n        div_term = self.div_term.to(x.device)\n        pe[:,:,0::2] = torch.sin(position * div_term)\n        pe[:,:,1::2] = torch.cos(position * div_term)\n        \n        x = x + pe\n        \n        return self.dropout(x)","metadata":{"papermill":{"duration":0.059786,"end_time":"2021-05-09T16:02:25.520443","exception":false,"start_time":"2021-05-09T16:02:25.460657","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ILN_Transformer(nn.Module):\n    def __init__(self, num_feature_bssid = 100, num_feature_rssi = 100, num_building = 24, num_bssid = 216210, num_user = 27551, \n                 num_floor = 11, num_feature_imu = 12, d_model = 512, nhead = 4, max_len = 512, max_len_imu = 25_000, droprate = 0.1):\n        super().__init__()\n        self.num_feature_bssid = num_feature_bssid\n        self.num_feature_rssi = num_feature_rssi\n        self.num_building = num_building\n        self.num_bssid = num_bssid\n        self.num_user = num_user\n        self.num_floor = num_floor\n        self.d_model = d_model\n        self.nhead = nhead\n        self.max_len = max_len\n        self.max_len_imu = max_len_imu\n        self.droprate = droprate\n        \n        ############################################ Wifi ############################################\n        # Embedding layers\n        self.building_embedding = nn.Embedding(num_embeddings = self.num_building, embedding_dim = self.d_model)\n        self.user_embedding = nn.Embedding(num_embeddings = self.num_user, embedding_dim = self.d_model)\n        self.bssid_embedding = nn.Embedding(num_embeddings = self.num_bssid, embedding_dim = self.d_model // 2, padding_idx = 0)\n        \n        # Linear layers for BSSID\n        self.linear_bssid = nn.Linear(self.num_feature_bssid, 1)\n        \n        # Linear layers for RSSI and timegap\n        self.linear_rssi_timegap = nn.Linear(2, 1)\n        self.linear_rssi = nn.Linear(self.num_feature_rssi, self.d_model // 2)\n        self.layer_norm_rssi = nn.LayerNorm(self.d_model // 2)\n        self.dropout_rssi = nn.Dropout(droprate)\n        \n        ############################################ IMU ############################################\n        # Linear projection\n        self.linear_imu_rotation = nn.Sequential(\n            nn.Linear(num_feature_imu, d_model // 2),\n            nn.LayerNorm(d_model // 2),\n            nn.ReLU(),\n            nn.Dropout(droprate)\n        )\n        \n        # Positional encoder\n        self.position_imu = PositionalEncoding(d_model = self.d_model // 2, dropout = self.droprate, max_len = self.max_len_imu)   # Because the IMU sequences are super long, we need a smaller model size to fit to the memory capacity\n        \n        # Attention\n        self.attn_encoder_imu = Lin_EncoderLayer(d_model = self.d_model // 2, nhead = self.nhead, dim_feedforward = 256, dropout = self.droprate, max_len = self.max_len_imu, target_len = self.max_len)\n        \n        # self.shrink_time = nn.Linear(self.max_len_imu, self.max_len)\n        \n        # Output layer\n        self.imu_output = nn.LSTM(input_size = d_model // 2, hidden_size = d_model // 2, bidirectional = True, batch_first = True)\n        \n        ############################################ Concatenate and prediction ############################################\n        # Middle linear layers\n        self.middle_linear = nn.Sequential(\n            nn.Linear(2 * d_model, d_model),\n            nn.LayerNorm(d_model),\n            nn.ReLU(),\n            nn.Dropout(droprate)\n        )\n        \n        # LSTM\n        self.lstms = clones(nn.LSTM(input_size = self.d_model, hidden_size = self.d_model // 2, bidirectional = True, batch_first = True), 3)\n        \n        # Positional encoder\n        self.position = PositionalEncoding(d_model = self.d_model, dropout = self.droprate, max_len = self.max_len)\n        \n        # Attention\n        self.attn_encoder = EncoderLayer(d_model = self.d_model, nhead = self.nhead, dim_feedforward = 512, dropout = self.droprate)\n        \n        self.lstm = nn.LSTM(input_size = self.d_model + 2, hidden_size = self.d_model // 2, bidirectional = True, batch_first = True)\n        self.linear_after_encoder = nn.Linear(self.d_model, self.d_model)\n        self.layernorm_after_encoder = nn.LayerNorm(self.d_model)\n        \n        # Output\n        self.linear_waypoints = nn.Linear(self.d_model, 2)\n        \n        # Activation\n        self.softmax = nn.Softmax()\n        self.prelu = nn.PReLU()\n        self.relu = nn.ReLU()\n        self.dropout = nn.Dropout(self.droprate)\n        \n    def _get_pad_mask(self, seq, pad_idx):\n        return (seq == pad_idx).unsqueeze(-2).to(seq.device)\n    \n    def _get_subsequent_mask(self, seq):\n        sz_b, len_s = seq.size()\n        subsequent_mask = torch.triu(torch.ones((1, len_s, len_s), device = seq.device), diagonal = 1).bool()\n        return subsequent_mask\n        \n    def forward(self, mask, mask_imu, building, user, bssid, rssi, timegap, imu, del_waypoints):\n        ############################################ Wifi ############################################\n        # Embed\n        building = self.building_embedding(building)\n        user = self.user_embedding(user)\n        bssid = self.bssid_embedding(bssid)\n        \n        # BSSID\n        bssid = self.linear_bssid(bssid.transpose(-2,-1)).squeeze(-1)\n        \n        # RSSI and timegap\n        rssi = torch.cat((rssi.unsqueeze(-1), timegap.unsqueeze(-1)), dim = -1)\n        rssi = self.linear_rssi_timegap(rssi).squeeze(-1)\n        rssi = self.linear_rssi(rssi)\n        rssi = self.layer_norm_rssi(rssi)\n        rssi = self.dropout_rssi(rssi)\n        \n        ############################################ IMU ############################################\n        # Projection\n        imu = self.linear_imu_rotation(imu)\n        \n        # Positional embedding\n        imu = self.position_imu(imu, mask = mask_imu)\n        \n        # Attention\n        imu = self.attn_encoder_imu(imu, mask = mask_imu)\n        \n        # Shrink time\n        # imu = self.shrink_time(imu.transpose(-1,-2)).transpose(-1,-2)\n        \n        # Output IMU\n        imu, _ = self.imu_output(imu)\n        \n        ############################################ Concatenate and prediction ############################################\n        x_wifi = torch.cat((bssid, rssi), dim = -1)\n        \n        x = self.middle_linear(torch.cat((bssid, rssi, imu), dim = -1)) + building + user\n        \n        for i, layer in enumerate(self.lstms):\n            x, _ = layer(x)\n        \n        # Positional encoding\n        x = self.position(x, mask = mask)\n        \n        # Mask\n        mask = ~(self._get_pad_mask(mask, False) | self._get_subsequent_mask(mask))\n        \n        # Feed it to the Encoder\n        x = self.attn_encoder(x, mask)\n        \n        # Feed it to the lstm\n        x, _ = self.lstm(torch.cat((x + x_wifi + imu, del_waypoints), dim = -1))\n        \n        # Feed over one more linear layer\n        x = self.linear_after_encoder(self.dropout(self.prelu(x)))\n        x = self.layernorm_after_encoder(x)\n        x = self.dropout(self.prelu(x))\n        \n        # Concatenate floor output and x + building\n        x = self.linear_waypoints(x)\n        \n        return x","metadata":{"papermill":{"duration":0.080369,"end_time":"2021-05-09T16:02:25.645897","exception":false,"start_time":"2021-05-09T16:02:25.565528","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Configuration and util functions","metadata":{"papermill":{"duration":0.045701,"end_time":"2021-05-09T16:02:25.740426","exception":false,"start_time":"2021-05-09T16:02:25.694725","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"* Metrics","metadata":{"papermill":{"duration":0.045065,"end_time":"2021-05-09T16:02:25.831936","exception":false,"start_time":"2021-05-09T16:02:25.786871","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def metrics(output_way, output_floor, way, floor):\n    first_term = np.mean(np.sqrt(np.sum((output_way - way)**2, axis = 1)))\n    second_term = 15 * np.mean(np.abs(output_floor - floor))\n    return first_term, second_term\n\ndef compute_cv_score(oof_):\n    output_way = oof_[['pred_x', 'pred_y']].values\n    output_floor = oof_['pred_floor'].values\n    \n    way = oof_[['true_x', 'true_y']].values\n    floor = oof_['true_floor'].values\n    \n    loss_waypoints, loss_floor = metrics(output_way, output_floor, way, floor)\n    return loss_waypoints, loss_floor","metadata":{"papermill":{"duration":0.056973,"end_time":"2021-05-09T16:02:25.934549","exception":false,"start_time":"2021-05-09T16:02:25.877576","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Inference functions","metadata":{"papermill":{"duration":0.045344,"end_time":"2021-05-09T16:02:26.02549","exception":false,"start_time":"2021-05-09T16:02:25.980146","status":"completed"},"tags":[]}},{"cell_type":"code","source":"def infer_fn(model, infer_dataloader, device = 'cpu'):\n    model.eval()\n    \n    index = []\n    \n    way_pred = []\n    \n    way_true = []\n    floor_true = []\n    \n    loss = 0\n    \n    # tbar = tqdm(infer_dataloader)\n    for item in infer_dataloader:\n        padding_mask = item['mask'].to(device)\n        padding_mask_imu = item['mask_imu'].to(device)\n        imu = item['imu'].to(device)\n        building = item['building'].to(device)\n        user = item['user'].to(device)\n        idx = item['idx'].to(device)\n        bssid = item['bssid'].to(device)\n        rssi = item['rssi'].to(device)\n        timegap = item['timegap'].to(device)\n        del_waypoints = item['del_waypoints'].to(device)\n        waypoints = item['waypoints'].to(device)\n        floor = item['floor'].to(device)\n        \n        # Feed input to the model\n        with torch.no_grad():\n            output_waypoints = model(padding_mask, padding_mask_imu, building, user, bssid, rssi, timegap, imu, del_waypoints)\n        \n        # True floor\n        floor = torch.repeat_interleave(floor.unsqueeze(1), waypoints.shape[1], dim = 1)\n        \n        # Extract waypoints\n        output_waypoints = output_waypoints[padding_mask,:]\n        waypoints = waypoints[padding_mask,:]\n        \n        floor = floor[padding_mask,:] - 2\n        \n        idx = idx[padding_mask]\n        \n        # Store results\n        index.append(idx.cpu().detach().numpy())\n        \n        way_pred.append(output_waypoints.cpu().detach().numpy())\n        way_true.append(waypoints.cpu().detach().numpy())\n        \n        floor_true.append(floor.cpu().detach().numpy())\n        \n    # Stack\n    index = np.concatenate(index)\n    \n    way_pred = np.vstack(way_pred)\n    way_true = np.vstack(way_true)\n    \n    floor_true = np.vstack(floor_true)\n    \n    oof_ = pd.DataFrame(np.hstack((way_true, floor_true, way_pred, floor_true)), index = index, \n                        columns = ['true_x', 'true_y', 'true_floor', 'pred_x', 'pred_y', 'pred_floor'])\n    \n    return oof_","metadata":{"papermill":{"duration":0.067062,"end_time":"2021-05-09T16:02:26.13939","exception":false,"start_time":"2021-05-09T16:02:26.072328","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Configuration","metadata":{"papermill":{"duration":0.046427,"end_time":"2021-05-09T16:02:26.232099","exception":false,"start_time":"2021-05-09T16:02:26.185672","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class config():\n    mode = 'infer'\n    # For training\n    split = GroupKFold(n_splits = 5)\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    # For dataloader\n    batch_size = 64\n    num_workers = 4\n    # For model\n    N = 2    # Number of encoder layers\n    d_model = 256\n    nhead = 2\n    max_len = 400\n    max_len_imu = 1024\n    droprate = 0.3\n    \ncfg = config()","metadata":{"papermill":{"duration":0.057276,"end_time":"2021-05-09T16:02:26.335614","exception":false,"start_time":"2021-05-09T16:02:26.278338","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Main","metadata":{"papermill":{"duration":0.045525,"end_time":"2021-05-09T16:02:26.426806","exception":false,"start_time":"2021-05-09T16:02:26.381281","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model_path = '../input/iln-models'\noof_ = []\n    \nfor i, (trn_idx, val_idx) in enumerate(tqdm(cfg.split.split(train_data.iloc[:,:200], \n                                                            train_data.building, \n                                                            train_data.group))):\n    \n    val = train_data.iloc[val_idx].copy()\n    \n    # Dataloader\n    infer_group = val.groupby('group').apply(feature_extraction_wifi)\n    infer_dataset = ILN_Dataset(infer_group, imu_group, max_len_wifi = cfg.max_len, max_len_imu = cfg.max_len_imu)\n    infer_dataloader = DataLoader(infer_dataset, batch_size = cfg.batch_size, num_workers = cfg.num_workers, shuffle = False)\n    \n    ckp = torch.load(os.path.join(model_path, f'model_best_fold_{i}_imu_v4.pt'), map_location = cfg.device)\n    model = ILN_Transformer(d_model = cfg.d_model, nhead = cfg.nhead, max_len_imu = cfg.max_len_imu,\n                            max_len = cfg.max_len, droprate = cfg.droprate).to(cfg.device)\n    model.load_state_dict(ckp['model_state_dict'])\n    \n    val_pred = infer_fn(model, infer_dataloader, device = cfg.device)\n    \n    val_pred['site_path_timestamp'] = val.loc[val_pred.index, 'site_path_timestamp'].values\n    \n    oof_.append(val_pred)\n\noof_ = pd.concat(oof_)\noof_ = oof_.sort_values('site_path_timestamp').reset_index(drop = True)","metadata":{"papermill":{"duration":895.287599,"end_time":"2021-05-09T16:17:21.759787","exception":false,"start_time":"2021-05-09T16:02:26.472188","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"oof_.to_csv('oof_wifi.csv', index = None)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* CV at wifi observations","metadata":{"papermill":{"duration":0.046598,"end_time":"2021-05-09T16:17:21.853412","exception":false,"start_time":"2021-05-09T16:17:21.806814","status":"completed"},"tags":[]}},{"cell_type":"code","source":"loss_waypoints, loss_floor = compute_cv_score(oof_)\nprint('OOF MPE waypoints:', loss_waypoints, ', OOF MPE floor:', loss_floor, ', OOF MPE:', loss_waypoints + loss_floor)","metadata":{"papermill":{"duration":0.074051,"end_time":"2021-05-09T16:17:21.97423","exception":false,"start_time":"2021-05-09T16:17:21.900179","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Interpolate the prediction","metadata":{"papermill":{"duration":0.04729,"end_time":"2021-05-09T16:17:22.070973","exception":false,"start_time":"2021-05-09T16:17:22.023683","status":"completed"},"tags":[]}},{"cell_type":"code","source":"oof_[['site', 'path', 'timestamp']] = [i.split('_') for i in oof_.site_path_timestamp]\noof_['siteid_path'] = ['_'.join([i, j]) for i, j in zip(oof_['site'], oof_['path'])]\noof_.set_index('siteid_path', inplace = True)\noof_.head()","metadata":{"papermill":{"duration":2.142835,"end_time":"2021-05-09T16:17:24.26087","exception":false,"start_time":"2021-05-09T16:17:22.118035","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_waypoints = pd.read_csv('../input/indoor-location-train-waypoints/train_waypoints.csv')\ntrain_waypoints['site_path_timestamp'] = ['_'.join([i, j, str(k)]) for i, j, k in zip(train_waypoints.site, train_waypoints.path, train_waypoints.timestamp)]\nss = train_waypoints[['site_path_timestamp', 'floor', 'x', 'y', 'site', 'path', 'timestamp']]\nss.rename(columns = {'x': 'true_x', 'y': 'true_y'}, inplace = True)\nss['pred_floor'] = ss['floor'].values\n\nsamples = pd.DataFrame(ss.groupby(['site','path'])['timestamp'].apply(lambda x: list(x)))\nbuildings = oof_.site.unique()\nsamples.head()","metadata":{"papermill":{"duration":0.93005,"end_time":"2021-05-09T16:17:25.239267","exception":false,"start_time":"2021-05-09T16:17:24.309217","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!git clone --depth 1 https://github.com/location-competition/indoor-location-competition-20 indoor_location_competition_20\n!rm -rf indoor_location_competition_20/data\n\nfrom indoor_location_competition_20.io_f import read_data_file\nimport indoor_location_competition_20.compute_f as compute_f","metadata":{"papermill":{"duration":48.538391,"end_time":"2021-05-09T16:18:13.826668","exception":false,"start_time":"2021-05-09T16:17:25.288277","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from scipy.interpolate import interp1d\nfrom scipy.ndimage.filters import uniform_filter1d\n\ncolacce = ['Unnamed: 0','acce_x','acce_y','acce_z']\ncolahrs = ['Unnamed: 0','ahrs_x','ahrs_y','ahrs_z']\n\nerror_paths = []\n\nfor building in buildings:\n    print(building)\n    paths = oof_[oof_.site == building]['path'].unique()\n    # Acceleration info\n    for path_id in paths:\n        # Load IMU data of this path_id\n        tfm = pd.read_csv(glob(f'../input/ilnimu-clean/{building}_*_{path_id}.csv')[0], usecols = ['Unnamed: 0'] + imu_)\n        # Original predicted values:\n        xy = oof_.loc[building + '_' + path_id]\n        acce_datas = np.array(tfm[colacce],dtype = np.float)\n        ahrs_datas = np.array(tfm[colahrs],dtype = np.float)\n        posi_datas = np.array(xy[['timestamp', 'pred_x', 'pred_y']], dtype = np.float).reshape(-1,3)\n        # Outlier removal:\n        xyout = uniform_filter1d(posi_datas, size = 3, axis = 0, mode = 'reflect').reshape(-1,3)\n        xydiff = np.abs(posi_datas - xyout)\n        xystd = np.std(xydiff,axis = 0) * 3\n        posi_datas = posi_datas[(xydiff [:,1] < xystd[1]) & (xydiff[:,2] < xystd[2])]\n        # Step detection:\n        step_timestamps, step_indexs, step_acce_max_mins = compute_f.compute_steps(acce_datas)\n        stride_lengths = compute_f.compute_stride_length(step_acce_max_mins)\n        # Orientation detection:\n        headings = compute_f.compute_headings(ahrs_datas)\n        step_headings = compute_f.compute_step_heading(step_timestamps, headings)\n        rel_positions = compute_f.compute_rel_positions(stride_lengths, step_headings)\n        # Running average:\n        posi_datas = uniform_filter1d(posi_datas, size = 3, axis = 0, mode = 'reflect')[0::3,:]\n        # The 1st prediction timepoint should be earlier than the 1st step timepoint.\n        try:\n            rel_positions = rel_positions[rel_positions[:,0] > posi_datas[0,0],:]\n            # If two consecutive predictions are in-between two step datapoints,\n            # the last one is removed, causing error (in the \"split_ts_seq\" function).\n            posi_index = [np.searchsorted(rel_positions[:,0], x, side = 'right') for x in posi_datas[:,0]]\n            u, i1, i2 = np.unique(posi_index, return_index = True, return_inverse = True)\n            posi_datas = np.vstack([np.mean(posi_datas[i2 == i],axis = 0) for i in np.unique(i2)])\n            # Position correction:\n            step_positions = compute_f.correct_positions(rel_positions, posi_datas)\n            # Interpolate for timestamps in the testing set:\n            t = step_positions[:,0]\n            x = step_positions[:,1]\n            y = step_positions[:,2]\n        \n            fx = interp1d(t, x, kind = 'linear', fill_value = (x[0], x[-1]), bounds_error = False) #fill_value=\"extrapolate\"\n            fy = interp1d(t, y, kind = 'linear', fill_value = (y[0], y[-1]), bounds_error = False)\n            # Output result:\n            t0 = np.array(samples.loc[(building,path_id),'timestamp'], dtype = np.float64)\n\n            ss.loc[(ss.site == building) & (ss.path == path_id), 'pred_x'] = fx(t0)\n            ss.loc[(ss.site == building) & (ss.path == path_id), 'pred_y'] = fy(t0)\n        except:\n            error_paths.append(path_id)\n            pass\n\nss = ss.dropna(axis = 0).rename(columns = {'floor': 'true_floor'}).reset_index(drop = True)\nss['timestamp'] = ss['timestamp'].astype(float)\nss.head()","metadata":{"papermill":{"duration":6127.314214,"end_time":"2021-05-09T18:00:21.259901","exception":false,"start_time":"2021-05-09T16:18:13.945687","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ss.to_csv('oof_waypoints_before_pp.csv', index = None)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Compute the MPE CV","metadata":{"papermill":{"duration":0.124818,"end_time":"2021-05-09T18:00:21.511141","exception":false,"start_time":"2021-05-09T18:00:21.386323","status":"completed"},"tags":[]}},{"cell_type":"code","source":"loss_waypoints, loss_floor = compute_cv_score(ss)\nprint('OOF MPE waypoints:', loss_waypoints, ', OOF MPE floor:', loss_floor, ', OOF MPE:', loss_waypoints + loss_floor)","metadata":{"papermill":{"duration":0.155399,"end_time":"2021-05-09T18:00:21.792049","exception":false,"start_time":"2021-05-09T18:00:21.63665","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Post-processing steps","metadata":{"papermill":{"duration":0.127905,"end_time":"2021-05-09T18:00:22.047576","exception":false,"start_time":"2021-05-09T18:00:21.919671","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"* Visualization functions","metadata":{"papermill":{"duration":0.199022,"end_time":"2021-05-09T18:00:22.443988","exception":false,"start_time":"2021-05-09T18:00:22.244966","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Helper Functions\ndef split_col(df):\n    df = pd.concat([\n        df['site_path_timestamp'].str.split('_', expand=True) \\\n        .rename(columns={0:'site',\n                         1:'path',\n                         2:'timestamp'}),\n        df\n    ], axis=1).copy()\n    return df\n\nfloor_map = {\"B2\":-2, \"B1\":-1, \"F1\":0, \"F2\": 1, \"F3\":2,\n             \"F4\":3, \"F5\":4, \"F6\":5, \"F7\":6,\"F8\":7,\"F9\":8,\n             \"1F\":0, \"2F\":1, \"3F\":2, \"4F\":3, \"5F\":4, \"6F\":5,\n             \"7F\":6, \"8F\": 7, \"9F\":8}\n\ndef plot_preds(\n    site,\n    floorNo,\n    sub=None,\n    true_locs=None,\n    base=\"../input/indoor-location-navigation\",\n    show_train=True,\n    show_preds=True,\n    show_smoothed=False,\n    fix_labels=True,\n    map_floor=None\n):\n    \"\"\"\n    Plots predictions on floorplan map.\n    \n    map_floor : use a different floor's map\n    \"\"\"\n    if map_floor is None:\n        map_floor = floorNo\n    # Prepare width_meter & height_meter (taken from the .json file)\n    floor_plan_filename = f\"{base}/metadata/{site}/{map_floor}/floor_image.png\"\n    json_plan_filename = f\"{base}/metadata/{site}/{map_floor}/floor_info.json\"\n    with open(json_plan_filename) as json_file:\n        json_data = json.load(json_file)\n\n    width_meter = json_data[\"map_info\"][\"width\"]\n    height_meter = json_data[\"map_info\"][\"height\"]\n\n    floor_img = plt.imread(f\"{base}/metadata/{site}/{map_floor}/floor_image.png\")\n\n    fig, ax = plt.subplots(figsize=(12, 12))\n    plt.imshow(floor_img)\n\n    if show_train:\n        true_locs = true_locs.query('site == @site and floorNo == @map_floor').copy()\n        true_locs[\"x_\"] = true_locs[\"x\"] * floor_img.shape[0] / height_meter\n        true_locs[\"y_\"] = (\n            true_locs[\"y\"] * -1 * floor_img.shape[1] / width_meter\n        ) + floor_img.shape[0]\n        true_locs.query(\"site == @site and floorNo == @map_floor\").groupby(\"path\").plot(\n            x=\"x_\",\n            y=\"y_\",\n            style=\"+\",\n            ax=ax,\n            label=\"train waypoint location\",\n            color=\"grey\",\n            alpha=0.5,\n        )\n\n    if show_preds:\n        if not show_smoothed:\n            sub = sub.query('site == @site and floorNo == @floorNo').copy()\n            sub[\"x_\"] = sub[\"pred_x\"] * floor_img.shape[0] / height_meter\n            sub[\"y_\"] = (\n                sub[\"pred_y\"] * -1 * floor_img.shape[1] / width_meter\n            ) + floor_img.shape[0]\n            for path, path_data in sub.query(\n                \"site == @site and floorNo == @floorNo\"\n            ).groupby(\"path\"):\n                path_data.plot(\n                    x=\"x_\",\n                    y=\"y_\",\n                    style=\".-\",\n                    ax=ax,\n                    title=f\"{site} - floor - {floorNo}\",\n                    alpha=1,\n                    label=path,\n                )\n                \n        else:\n            sub = sub.query('site == @site and floorNo == @floorNo').copy()\n            sub[\"x_\"] = sub[\"pred_x\"] * floor_img.shape[0] / height_meter\n            sub[\"y_\"] = (\n                sub[\"pred_y\"] * -1 * floor_img.shape[1] / width_meter\n            ) + floor_img.shape[0]\n            for path, path_data in sub.query(\n                \"site == @site and floorNo == @floorNo\"\n            ).groupby(\"path\"):\n                path_data.plot(\n                    x=\"x_\",\n                    y=\"y_\",\n                    style=\".-\",\n                    ax=ax,\n                    title=f\"{site} - floor - {floorNo}\",\n                    alpha=1,\n                    label=path,\n                )\n                \n    if fix_labels:\n        handles, labels = ax.get_legend_handles_labels()\n        by_label = dict(zip(labels, handles))\n        plt.legend(\n            by_label.values(), by_label.keys(), loc=\"center left\", bbox_to_anchor=(1, 0.5)\n        )\n        \n    return fig, ax\n\ndef sub_process(sub, train_waypoints):\n    train_waypoints['isTrainWaypoint'] = True\n    sub = sub.rename(columns = {'pred_floor': 'floor', 'pred_x': 'x', 'pred_y': 'y'})\n    #sub = split_col(sub).copy()\n    sub = sub.merge(train_waypoints[['site','floorNo','floor']].drop_duplicates(), how='left')\n    sub = sub.merge(\n        train_waypoints[['x','y','site','floor','isTrainWaypoint']].drop_duplicates(),\n        how = 'left',\n        on = ['site','x','y','floor'])\n    sub['isTrainWaypoint'] = sub['isTrainWaypoint'].fillna(False)\n    sub = sub.rename(columns = {'floor': 'pred_floor', 'x': 'pred_x', 'y': 'pred_y'})\n    return sub.copy()","metadata":{"_kg_hide-output":true,"papermill":{"duration":0.310004,"end_time":"2021-05-09T18:00:22.950738","exception":false,"start_time":"2021-05-09T18:00:22.640734","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Visualization of raw prediction","metadata":{"papermill":{"duration":0.205705,"end_time":"2021-05-09T18:00:23.371877","exception":false,"start_time":"2021-05-09T18:00:23.166172","status":"completed"},"tags":[]}},{"cell_type":"code","source":"sub = sub_process(ss, train_waypoints)\n\n# Plot the training Data For an example Floor\nexample_site = '5d2709bb03f801723c32852c'\nexample_floorNo = 'F4'\n\nplot_preds(example_site, example_floorNo, sub,\n           train_waypoints, show_preds=True)\nplt.show()","metadata":{"papermill":{"duration":4.341468,"end_time":"2021-05-09T18:00:28.212446","exception":false,"start_time":"2021-05-09T18:00:23.870978","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Saito's post-processing","metadata":{"papermill":{"duration":0.134679,"end_time":"2021-05-09T18:00:28.48264","exception":false,"start_time":"2021-05-09T18:00:28.347961","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import math\n\norder = 3\nfs = 50.0  # sample rate, Hz\n# fs = 100\n# cutoff = 3.667  # desired cutoff frequency of the filter, Hz\ncutoff = 3\n\nstep_distance = 0.8\nw_height = 1.7\nm_trans = -5\n\nfrom scipy.signal import butter, lfilter\n\ndef butter_lowpass(cutoff, fs, order=5):\n    nyq = 0.5 * fs\n    normal_cutoff = cutoff / nyq\n    b, a = butter(order, normal_cutoff, btype='low', analog=False)\n    return b, a\n\ndef butter_lowpass_filter(data, cutoff, fs, order=5):\n    b, a = butter_lowpass(cutoff, fs, order=order)\n    y = lfilter(b, a, data)\n    return y\n\ndef peak_accel_threshold(data, timestamps, threshold):\n    d_acc = []\n    last_state = 'below'\n    crest_troughs = 0\n    crossings = []\n\n    for i, datum in enumerate(data):\n        \n        current_state = last_state\n        if datum < threshold:\n            current_state = 'below'\n        elif datum > threshold:\n            current_state = 'above'\n\n        if current_state is not last_state:\n            if current_state is 'above':\n                crossing = [timestamps[i], threshold]\n                crossings.append(crossing)\n            else:\n                crossing = [timestamps[i], threshold]\n                crossings.append(crossing)\n\n            crest_troughs += 1\n        last_state = current_state\n    return np.array(crossings)\n\ndef steps_compute_rel_positions(sample_file):\n    \n    mix_acce = np.sqrt(sample_file.acce[:,1:2]**2 + sample_file.acce[:,2:3]**2 + sample_file.acce[:,3:4]**2)\n    mix_acce = np.concatenate([sample_file.acce[:,0:1], mix_acce], 1)\n    mix_df = pd.DataFrame(mix_acce)\n    mix_df.columns = [\"timestamp\",\"acce\"]\n    \n    filtered = butter_lowpass_filter(mix_df[\"acce\"], cutoff, fs, order)\n\n    threshold = filtered.mean() * 1.1\n    crossings = peak_accel_threshold(filtered, mix_df[\"timestamp\"], threshold)\n\n    step_sum = len(crossings)/2\n    distance = w_height * 0.4 * step_sum\n\n    mag_df = pd.DataFrame(sample_file.magn)\n    mag_df.columns = [\"timestamp\",\"x\",\"y\",\"z\"]\n    \n    acce_df = pd.DataFrame(sample_file.acce)\n    acce_df.columns = [\"timestamp\",\"ax\",\"ay\",\"az\"]\n    \n    mag_df = pd.merge(mag_df,acce_df,on=\"timestamp\")\n    mag_df.dropna()\n    \n    time_di_list = []\n\n    for i in mag_df.iterrows():\n\n        gx,gy,gz = i[1][1],i[1][2],i[1][3]\n        ax,ay,az = i[1][4],i[1][5],i[1][6]\n\n        roll = math.atan2(ay,az)\n        pitch = math.atan2(-1*ax , (ay * math.sin(roll) + az * math.cos(roll)))\n\n        q = m_trans - math.degrees(math.atan2(\n            (gz*math.sin(roll)-gy*math.cos(roll)),(gx*math.cos(pitch) + gy*math.sin(roll)*math.sin(pitch) + gz*math.sin(pitch)*math.cos(roll))\n        )) -90\n        if q <= 0:\n            q += 360\n        time_di_list.append((i[1][0],q))\n\n    d_list = [x[1] for x in time_di_list]\n    \n    steps = []\n    step_time = []\n    di_dict = dict(time_di_list)\n\n    for n,i in enumerate(crossings[:,:1]):\n        if n % 2 == 1:\n            continue\n        direct_now = di_dict[i[0]]\n        dx = math.sin(math.radians(direct_now))\n        dy = math.cos(math.radians(direct_now))\n#         print(int(n/2+1),\"歩目/x:\",dx,\"/y:\",dy,\"/角度：\",direct_now)\n        steps.append((i[0],dx,dy))\n        step_time.append(i[0])\n    \n        step_dtime = np.diff(step_time)/1000\n        step_dtime = step_dtime.tolist()\n        step_dtime.insert(0,5)\n        \n        rel_position = []\n\n        wp_idx = 0\n#         print(\"WP:\",round(sample_file.waypoint[0,1],3),round(sample_file.waypoint[0,2],3),sample_file.waypoint[0,0])\n#         print(\"------------------\")\n        for p,i in enumerate(steps):\n            step_distance = 0\n            if step_dtime[p] >= 1:\n                step_distance = w_height*0.25\n            elif step_dtime[p] >= 0.75:\n                step_distance = w_height*0.3\n            elif step_dtime[p] >= 0.5:\n                step_distance = w_height*0.4\n            elif step_dtime[p] >= 0.35:\n                step_distance = w_height*0.45\n            elif step_dtime[p] >= 0.2:\n                step_distance = w_height*0.5\n            else:\n                step_distance = w_height*0.4\n\n#             step_x += i[1]*step_distance\n#             step_y += i[2]*step_distance\n            \n            rel_position.append([i[0], i[1]*step_distance, i[2]*step_distance])\n#     print(rel_position)\n    \n    return np.array(rel_position)","metadata":{"papermill":{"duration":0.169742,"end_time":"2021-05-09T18:00:28.788237","exception":false,"start_time":"2021-05-09T18:00:28.618495","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def compute_step_heading(step_timestamps, headings):\n    sin_head = np.sin(headings[:,1])\n    cos_head = np.cos(headings[:,1])\n    head = np.vstack((np.searchsorted(step_timestamps, headings[:,0]), sin_head, cos_head)).T\n    split_steps = np.split(head[:,1:], np.unique(head[:, 0], return_index = True)[1][1:])\n    step_headings = np.array([np.sum(arr, axis = 0) for arr in split_steps[:-1]])\n    step_headings = np.arctan2(step_headings[:,0], step_headings[:,1])\n    step_headings = np.vstack((step_timestamps, step_headings)).T\n    return step_headings\n\ndef compute_rel_positions(acce_datas, ahrs_datas):\n    step_timestamps, step_indexs, step_acce_max_mins = compute_f.compute_steps(acce_datas)\n    headings = compute_f.compute_headings(ahrs_datas)\n    headings[:,1] = uniform_filter1d(headings[:,1], size = 50)\n    stride_lengths = compute_f.compute_stride_length(step_acce_max_mins)\n    step_headings = compute_f.compute_step_heading(step_timestamps, headings)    # compute_f.\n    rel_positions = compute_f.compute_rel_positions(stride_lengths, step_headings)\n    return rel_positions\n\ndef correct_path(args):\n    path, path_df = args\n    \n    site = path_df.site.unique()[0]\n    floor = path_df.pred_floor.unique()[0]\n    \n    T_ref  = path_df['timestamp'].values\n    xy_hat = path_df[['pred_x', 'pred_y']].values\n    \n    # Find the file path\n    floor_name = os.listdir('../input/indoor-location-navigation/train/' + site)\n\n    for name in floor_name:\n        if floor_map[name] == floor:\n            file = '../input/indoor-location-navigation/train/' + site + '/' + name + '/' + path + '.txt'\n            break\n            \n    try:\n        example = read_data_file(file)\n    \n        rel_positions1 = compute_rel_positions(example.acce, example.ahrs)\n        rel_positions2 = steps_compute_rel_positions(example)\n        rel1 = rel_positions1.copy()\n        rel2 = rel_positions2.copy()\n        rel1[:,1:] = rel_positions1[:,1:] / 2\n        rel2[:,1:] = rel_positions2[:,1:] / 2\n        rel_positions = np.vstack([rel1,rel2])\n        rel_positions = rel_positions[np.argsort(rel_positions[:, 0])]\n\n        if T_ref[-1] > rel_positions[-1, 0]:\n            rel_positions = [np.array([[0, 0, 0]]), rel_positions, np.array([[T_ref[-1], 0, 0]])]\n        else:\n            rel_positions = [np.array([[0, 0, 0]]), rel_positions]\n        rel_positions = np.concatenate(rel_positions)\n\n        T_rel = rel_positions[:, 0]\n        delta_xy_hat = np.diff(scipy.interpolate.interp1d(T_rel, np.cumsum(rel_positions[:, 1:3], axis=0), axis=0)(T_ref), axis=0)\n\n        N = xy_hat.shape[0]\n        delta_t = np.diff(T_ref)\n        alpha = (7.2)**(-2) * np.ones(N)\n        beta  = (0.3 + 0.3 * 1e-3 * delta_t)**(-2)\n        A = scipy.sparse.spdiags(alpha, [0], N, N)\n        B = scipy.sparse.spdiags(beta, [0], N-1, N-1)\n        D = scipy.sparse.spdiags(np.stack([-np.ones(N), np.ones(N)]), [0, 1], N-1, N)\n\n        Q = A + (D.T @ B @ D)\n        c = (A @ xy_hat) + (D.T @ (B @ delta_xy_hat))\n        xy_star = scipy.sparse.linalg.spsolve(Q, c)\n\n        return pd.DataFrame({\n            'site_path_timestamp' : path_df['site_path_timestamp'],\n            'pred_floor' : path_df['pred_floor'],\n            'pred_x' : xy_star[:, 0],\n            'pred_y' : xy_star[:, 1],\n            'true_floor' : path_df['true_floor'],\n            'true_x' : path_df['true_x'],\n            'true_y' : path_df['true_y'],\n        })\n    except:\n        print('Mis-predict floor: ', file)\n        return pd.DataFrame({\n            'site_path_timestamp' : path_df['site_path_timestamp'],\n            'pred_floor' : path_df['pred_floor'],\n            'pred_x' : path_df['pred_x'],\n            'pred_y' : path_df['pred_x'],\n            'true_floor' : path_df['true_floor'],\n            'true_x' : path_df['true_x'],\n            'true_y' : path_df['true_y'],\n        })","metadata":{"papermill":{"duration":0.165085,"end_time":"2021-05-09T18:00:29.090508","exception":false,"start_time":"2021-05-09T18:00:28.925423","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"processes = multiprocessing.cpu_count()\nwith multiprocessing.Pool(processes = processes) as pool:\n    dfs = pool.imap_unordered(correct_path, ss.groupby('path'))\n    dfs = tqdm(dfs)\n    dfs = list(dfs)\nss = pd.concat(dfs).sort_values('site_path_timestamp').reset_index(drop = True)","metadata":{"papermill":{"duration":4110.879295,"end_time":"2021-05-09T19:09:00.107887","exception":false,"start_time":"2021-05-09T18:00:29.228592","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ss.to_csv('oof_waypoints_after_Saito.csv', index = None)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss_waypoints, loss_floor = compute_cv_score(ss)\nprint(\"After Saito's post-processing\")\nprint('OOF MPE waypoints:', loss_waypoints, ', OOF MPE floor:', loss_floor, ', OOF MPE:', loss_waypoints + loss_floor)\n\ntmp = ss['site_path_timestamp'].apply(lambda s : pd.Series(s.split('_')))\nss['site'] = tmp[0]\nss['path'] = tmp[1]\nss['timestamp'] = tmp[2].astype(float)","metadata":{"papermill":{"duration":27.601741,"end_time":"2021-05-09T19:09:27.847918","exception":false,"start_time":"2021-05-09T19:09:00.246177","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Visualize post-processing after Saito's post processing","metadata":{"papermill":{"duration":0.136665,"end_time":"2021-05-09T19:09:28.123141","exception":false,"start_time":"2021-05-09T19:09:27.986476","status":"completed"},"tags":[]}},{"cell_type":"code","source":"sub = sub_process(ss, train_waypoints)\n\n# Plot the training Data For an example Floor\nexample_site = '5d2709bb03f801723c32852c'\nexample_floorNo = 'F4'\n\nplot_preds(example_site, example_floorNo, sub,\n           train_waypoints, show_preds=True)\nplt.show()","metadata":{"papermill":{"duration":4.253672,"end_time":"2021-05-09T19:09:32.513665","exception":false,"start_time":"2021-05-09T19:09:28.259993","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Push to corridor post-processing","metadata":{"papermill":{"duration":0.149904,"end_time":"2021-05-09T19:09:33.042165","exception":false,"start_time":"2021-05-09T19:09:32.892261","status":"completed"},"tags":[]}},{"cell_type":"code","source":"from shapely.geometry import Polygon\nfrom shapely.ops import nearest_points\nfrom shapely.geometry import Point\n\ndef fix_prediction(args):\n    # Unpack\n    (site, floor), df = args\n    \n    # Find the file path\n    floor_name = os.listdir('../input/indoor-location-navigation-scaled-geojson/scaled_geojson/' + site)\n    for name in floor_name:\n        if floor_map[name] == floor:\n            file = '../input/indoor-location-navigation-scaled-geojson/scaled_geojson/' + site + '/' + name + '/shapely_geometry.pkl'\n            break\n            \n    # Open the corridor\n    with open(file, 'rb') as f:\n        corridor = pkl.load(f)\n        \n    # Find the outside-corridor points and force them into the corridor\n    out_corridor = []\n    out_corridor_idx = []\n    corridor_nearest_points = []\n    for i in range(df.shape[0]):\n        p = Point(df[['pred_x', 'pred_y']].iloc[i].values)\n        if not p.within(corridor):\n            out_corridor.append(p)\n            out_corridor_idx.append(df[['pred_x', 'pred_y']].index[i])\n            nearest_p, _ = nearest_points(corridor, p)\n            x, y = nearest_p.xy[0][0], nearest_p.xy[1][0]\n            corridor_nearest_points.append([x, y])\n    \n    if len(corridor_nearest_points) != 0:\n        df.loc[out_corridor_idx, ['pred_x', 'pred_y']] = np.array(corridor_nearest_points)\n    \n    return df","metadata":{"papermill":{"duration":0.797646,"end_time":"2021-05-09T19:09:33.988667","exception":false,"start_time":"2021-05-09T19:09:33.191021","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ss[['site', 'path', 'timestamp']] = np.array([i.split('_') for i in ss.site_path_timestamp])\n\nprocesses = multiprocessing.cpu_count()\nwith multiprocessing.Pool(processes = processes) as pool:\n    dfs = pool.imap_unordered(fix_prediction, ss.groupby(['site', 'pred_floor']))\n    dfs = tqdm(dfs)\n    dfs = list(dfs)\nss = pd.concat(dfs).sort_values('site_path_timestamp')","metadata":{"papermill":{"duration":63.641709,"end_time":"2021-05-09T19:10:37.777743","exception":false,"start_time":"2021-05-09T19:09:34.136034","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ss.to_csv('oof_waypoints_after_pp.csv', index = None)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss_waypoints, loss_floor = compute_cv_score(ss)\nprint('After Push-to-corridor post-processing')\nprint('OOF MPE waypoints:', loss_waypoints, ', OOF MPE floor:', loss_floor, ', OOF MPE:', loss_waypoints + loss_floor)","metadata":{"papermill":{"duration":0.16725,"end_time":"2021-05-09T19:10:38.092771","exception":false,"start_time":"2021-05-09T19:10:37.925521","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Visualize after Push-to-Corridor post-processing","metadata":{"papermill":{"duration":0.147826,"end_time":"2021-05-09T19:10:38.387636","exception":false,"start_time":"2021-05-09T19:10:38.23981","status":"completed"},"tags":[]}},{"cell_type":"code","source":"sub = sub_process(ss, train_waypoints)\n\n# Plot the training Data For an example Floor\nexample_site = '5d2709bb03f801723c32852c'\nexample_floorNo = 'F4'\n\nplot_preds(example_site, example_floorNo, sub, \n           train_waypoints, show_preds = True)\nplt.show()","metadata":{"papermill":{"duration":4.223494,"end_time":"2021-05-09T19:10:42.75853","exception":false,"start_time":"2021-05-09T19:10:38.535036","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]}]}