{"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 numpy as np\nimport pandas as pd\nimport scipy.stats as stats\nfrom multiprocessing import Process, Manager\nimport multiprocessing as mp\nimport random\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport torch\nimport torch.nn as nn\nfrom torch.nn import functional as F\n\nfrom sklearn.metrics import classification_report\nfrom sklearn.preprocessing import StandardScaler, MinMaxScaler\nfrom sklearn.linear_model import LinearRegression\nfrom statsmodels.tsa.seasonal import seasonal_decompose\nfrom scipy.optimize import curve_fit\n\nimport gc\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"id":"mXvnzeF7qI4o","execution":{"iopub.status.busy":"2023-10-15T10:33:27.853991Z","iopub.execute_input":"2023-10-15T10:33:27.854423Z","iopub.status.idle":"2023-10-15T10:33:34.018423Z","shell.execute_reply.started":"2023-10-15T10:33:27.854393Z","shell.execute_reply":"2023-10-15T10:33:34.017482Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Functions","metadata":{}},{"cell_type":"code","source":"# clip upper and lower 10 percentile\n\ndef clipper(series):\n    upper = np.percentile(series, 90)\n    lower = np.percentile(series, 10)\n\n    if series.min()==0:\n        series = series.apply(lambda x: upper if x>upper else x)\n\n    else:\n        series = series.apply(lambda x: upper if x>upper else x)\n        series = series.apply(lambda x: lower if x<lower else x)\n\n    return series","metadata":{"id":"_7cP3wYc4v49","execution":{"iopub.status.busy":"2023-10-15T10:33:41.673504Z","iopub.execute_input":"2023-10-15T10:33:41.674055Z","iopub.status.idle":"2023-10-15T10:33:41.680871Z","shell.execute_reply.started":"2023-10-15T10:33:41.674020Z","shell.execute_reply":"2023-10-15T10:33:41.679468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# plot graph\n\ndef plotter(data, feature):\n    plt.figure(figsize=(30, 4))\n    plt.plot(data['step'], data[feature])\n    for ox in data[data['event']==1].index:\n        plt.axvline(ox, color='black', alpha=0.7, linestyle=\"dashed\")\n    for wx in data[data['event']==2].index:\n        plt.axvline(wx, color='red', alpha=0.7, linestyle=\"dashdot\")\n\n    plt.xlabel(\"step\")\n    plt.ylabel(feature)\n\n    plt.autoscale(enable=True, axis='both', tight=True)\n\n    plt.show()","metadata":{"id":"tAFvG5ooy0d7","execution":{"iopub.status.busy":"2023-10-15T10:33:42.366402Z","iopub.execute_input":"2023-10-15T10:33:42.366772Z","iopub.status.idle":"2023-10-15T10:33:42.374746Z","shell.execute_reply.started":"2023-10-15T10:33:42.366745Z","shell.execute_reply":"2023-10-15T10:33:42.373354Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# extracts individual series and creates key value pair\n\ndef split_process(series_id, series_dict):\n    sid = tn_series.loc[series_id]\n    sid.reset_index(drop=True,inplace=True)\n    sid['comb'] = sid['anglez'] * sid['enmo']    # new column comb = anglez * enmo\n    series_dict[series_id] = sid\n    del sid\n\n    if len(series_dict)%50==0:\n        print(f\"{len(series_dict)}/277\")\n\n    return None","metadata":{"execution":{"iopub.status.busy":"2023-10-15T10:33:42.934795Z","iopub.execute_input":"2023-10-15T10:33:42.935155Z","iopub.status.idle":"2023-10-15T10:33:42.941593Z","shell.execute_reply.started":"2023-10-15T10:33:42.935130Z","shell.execute_reply":"2023-10-15T10:33:42.940257Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# calculates top 3 sine components of series and superimposes\n\ndef sinify(xval, yval):\n\n    fft_result = np.fft.fft(yval)\n    freqs = np.fft.fftfreq(len(xval))\n\n    freq_sorted = np.argsort(np.abs(fft_result[1:]))[::-2] + 1\n    top = freq_sorted[:3]\n\n    def sine_function1(x, A, phi, offset=0):\n        return A * np.sin(2.0 * np.pi * dom_freq * x + phi) + offset\n\n    waves = []\n    for i in range(3):\n        dom_freq = freqs[top[i]]\n\n        initial_guess = [2.0, np.pi / 4, 0.0]\n        params, covariance = curve_fit(sine_function1, xval, yval, p0=initial_guess)\n\n        A_fit, phi_fit, offset_fit = params\n        waves.append(sine_function1(xval, A_fit, phi_fit))\n\n    super_wave = np.sum(np.array(waves), axis=0) + offset_fit\n\n    return super_wave","metadata":{"execution":{"iopub.status.busy":"2023-10-15T10:33:43.482195Z","iopub.execute_input":"2023-10-15T10:33:43.482847Z","iopub.status.idle":"2023-10-15T10:33:43.491725Z","shell.execute_reply.started":"2023-10-15T10:33:43.482807Z","shell.execute_reply":"2023-10-15T10:33:43.490492Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# fit a line to series window and save coefficients \n\ndef line_fit(y):\n    x = np.arange(grouper)\n\n    def lin(x, a, b):\n        return (a*x) + b\n\n    params, _ = curve_fit(lin, x, y)\n\n    return params","metadata":{"execution":{"iopub.status.busy":"2023-10-15T10:33:43.930713Z","iopub.execute_input":"2023-10-15T10:33:43.931469Z","iopub.status.idle":"2023-10-15T10:33:43.937185Z","shell.execute_reply.started":"2023-10-15T10:33:43.931434Z","shell.execute_reply":"2023-10-15T10:33:43.936005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# group successive rows as per window size with the following specifications\n# combined (anglez * enmo) to be grouped as std (standard dev) and array (actual window elements)\n# anglez and enmo to be grouped as mean\n# event to be grouped as sum (event present in window or not) and array (position of event presence in window)\n\n\n# new features created from comb std - sinified daily trend, diff bet consecutive samples, rolling mean\n# new features from windowed series elements - coefficients of closest straight line fit\n\n\ndef transform_df(gdf, grouper):\n\n    gdf['step'] = (gdf['step']/grouper).astype('int')\n    \n    if gdf.loc[gdf.index[len(gdf)-1]].step - gdf.loc[gdf.index[len(gdf)-2]].step == 1:\n        gdf = gdf.loc[:gdf.index[len(gdf)-2]]\n    \n    gdf = gdf.groupby(['step'], as_index=False).agg(std=('comb', 'std'),\n                                                    avga=('anglez', 'mean'),\n                                                    avge=('enmo', 'mean'),\n                                                    anglezs = ('anglez', list),\n                                                    enmos = ('enmo', list),\n                                                    elements=('comb', list),\n                                                    oevent = ('event', list),\n                                                    event=('event', 'sum'))\n\n    \n    gdf['elements'] = gdf['elements'].apply(lambda x: np.array(x))\n    gdf['oevent'] = gdf['oevent'].apply(lambda x: np.array(x))\n    gdf['anglezs'] = gdf['anglezs'].apply(lambda x: np.array(x))\n    gdf['enmos'] = gdf['enmos'].apply(lambda x: np.array(x))\n\n    dec = seasonal_decompose(gdf['std'], model='additive', period=72, extrapolate_trend='freq')\n    gdf['trend'] = dec.trend\n\n\n    gdf['trend'] = sinify(gdf['step'], gdf['trend'])\n\n\n    gdf['delta'] = (gdf['std'].diff(-1) * -1).fillna(value=0.0)\n\n\n    gdf['stdra'] = gdf['std'].rolling(100).mean()\n    gdf['stdra'] = gdf['stdra'].fillna(method='ffill')\n    gdf['stdra'] = gdf['stdra'].fillna(method='bfill')\n\n\n    last = len(gdf) - 1\n    if len(gdf.elements.loc[last])!=grouper:\n        gdf.drop(index=last, inplace=True)\n\n\n    all_par = np.stack(gdf['elements'].apply(line_fit))\n    all_par = np.transpose(all_par)\n\n    for i, j in enumerate(all_par):\n        gdf['coef'+str(i)] = j\n\n\n    return gdf","metadata":{"execution":{"iopub.status.busy":"2023-10-15T10:33:46.212620Z","iopub.execute_input":"2023-10-15T10:33:46.213043Z","iopub.status.idle":"2023-10-15T10:33:46.226530Z","shell.execute_reply.started":"2023-10-15T10:33:46.213010Z","shell.execute_reply":"2023-10-15T10:33:46.225316Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Get data","metadata":{}},{"cell_type":"code","source":"tn_series = pd.read_parquet(\"/kaggle/input/child-mind-institute-detect-sleep-states/train_series.parquet\", columns=[\"series_id\", \"step\", \"anglez\", \"enmo\"])\ntn_events = pd.read_csv(\"/kaggle/input/child-mind-institute-detect-sleep-states/train_events.csv\")\ntn_events.drop(columns=['timestamp', 'night'], inplace=True)\ntn_series.set_index('series_id', inplace=True)","metadata":{"id":"aF0NTPr-q30p","execution":{"iopub.status.busy":"2023-10-15T10:34:29.368130Z","iopub.execute_input":"2023-10-15T10:34:29.368536Z","iopub.status.idle":"2023-10-15T10:34:43.545190Z","shell.execute_reply.started":"2023-10-15T10:34:29.368508Z","shell.execute_reply":"2023-10-15T10:34:43.543987Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# stores each series in a dictionary for easy access\n\nall_series_ids = list(set(tn_series.index))\nset1 = all_series_ids[1:139]\nset2 = all_series_ids[139:]\n\n\nif __name__ == \"__main__\":\n\n    manager = Manager()\n    series_dict = manager.dict()\n\n    split_process(all_series_ids[0], series_dict)\n\n    for a, b in zip(set1, set2):\n        p1 = Process(target=split_process, args=(a, series_dict, ))\n        p2 = Process(target=split_process, args=(b, series_dict, ))\n\n        p1.start()\n        p2.start()\n\n        p1.join()\n        p2.join()\n\n\ndel tn_series\n","metadata":{"id":"ubRFKCT5KiIv","execution":{"iopub.status.busy":"2023-10-15T10:35:14.443245Z","iopub.execute_input":"2023-10-15T10:35:14.443619Z","iopub.status.idle":"2023-10-15T10:36:08.294766Z","shell.execute_reply.started":"2023-10-15T10:35:14.443592Z","shell.execute_reply":"2023-10-15T10:36:08.292529Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Process data","metadata":{}},{"cell_type":"code","source":"# drop null rows from events data\n# dropping series from series dict that have no event data\n# series 'c5365a55ebb7' has only a single day's data hence dropped\n\ntn_events.dropna(inplace=True)\nto_delete = list(set(series_dict.keys()) - set(series_dict.keys()).intersection(set(tn_events['series_id'].unique()))) + ['c5365a55ebb7']\nfor item in to_delete:\n    del series_dict[item]","metadata":{"execution":{"iopub.status.busy":"2023-10-15T10:36:27.807103Z","iopub.execute_input":"2023-10-15T10:36:27.807610Z","iopub.status.idle":"2023-10-15T10:36:27.828913Z","shell.execute_reply.started":"2023-10-15T10:36:27.807569Z","shell.execute_reply":"2023-10-15T10:36:27.827456Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# for each series, get the step/timestamp of the last wakeup event + buffer of 5000 steps\n# any series data after this step is irrelevant to modeling and will be chopped off\n\nseries_lim = {}\n\nfor sid in tn_events['series_id'].unique():\n    \n    edf = tn_events[tn_events['series_id']==sid]\n    indices = edf.index\n    end = edf['step'].loc[indices[len(indices)-1]].astype('int') + 5000\n    series_lim[sid] = end\n    \n    del edf","metadata":{"id":"T56KBRJ-KsUS","execution":{"iopub.status.busy":"2023-10-15T10:36:32.358002Z","iopub.execute_input":"2023-10-15T10:36:32.358810Z","iopub.status.idle":"2023-10-15T10:36:32.719096Z","shell.execute_reply.started":"2023-10-15T10:36:32.358743Z","shell.execute_reply":"2023-10-15T10:36:32.717247Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# chop off extra bit from each series based on timestep obtained above\n\nfor skey in list(series_dict.keys()):\n    \n    wdf = series_dict[skey]\n    wdf.set_index('step', inplace=True)\n    lims = series_lim[skey]\n    wdf = wdf.loc[: lims]\n    wdf.reset_index(inplace=True)\n    series_dict[skey] = wdf\n    \n    del wdf","metadata":{"id":"ZIdeLdWKKvyD","execution":{"iopub.status.busy":"2023-10-15T10:36:36.683499Z","iopub.execute_input":"2023-10-15T10:36:36.683893Z","iopub.status.idle":"2023-10-15T10:36:48.864723Z","shell.execute_reply.started":"2023-10-15T10:36:36.683864Z","shell.execute_reply":"2023-10-15T10:36:48.863600Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# group the series by defined size \n\ntn_events['event'] = tn_events['event'].map({\"onset\": 1, \"wakeup\": 2})     # label event data 1=sleep, 2=wakeup\ntn_events.set_index('series_id', inplace=True)\n\ngrouper = 25      # series window size (smaller sizes give closer results but also increase computation costs)","metadata":{"id":"pAf1W4duK1WK","execution":{"iopub.status.busy":"2023-10-15T10:36:56.253552Z","iopub.execute_input":"2023-10-15T10:36:56.253985Z","iopub.status.idle":"2023-10-15T10:36:56.263598Z","shell.execute_reply.started":"2023-10-15T10:36:56.253950Z","shell.execute_reply":"2023-10-15T10:36:56.262218Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# merge the series data with the event data\n# no event labels = 0\n# perform the grouping operations and feature engineering\n# update existing series dictionary\n\ndef mergengroup(skey, series_dict, event_data, grouper, ctr):\n    mdf = series_dict[skey]\n    mdf = mdf.merge(event_data, on=[\"step\"], how=\"left\")\n    mdf['event'] = mdf['event'].fillna(value=0).astype('int')\n    mdf = transform_df(mdf, grouper)\n    series_dict[skey] = mdf\n    del mdf\n    \n    with ctr.get_lock():\n        ctr.value += 1\n        if ctr.value%50==0:\n            print(f\"{ctr.value}/268\")\n        \n    return None    ","metadata":{"execution":{"iopub.status.busy":"2023-10-15T10:37:00.039972Z","iopub.execute_input":"2023-10-15T10:37:00.040359Z","iopub.status.idle":"2023-10-15T10:37:00.047326Z","shell.execute_reply.started":"2023-10-15T10:37:00.040330Z","shell.execute_reply":"2023-10-15T10:37:00.046398Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# execute merging and grouping operation on each series id by multiprocessing\n\nall_keys = list(series_dict.keys())\nhpoint = len(series_dict)//2\npart1 = all_keys[:hpoint]\npart2 = all_keys[hpoint:]\n\nif __name__ == \"__main__\":\n    \n    ctr = mp.Value('i', 0)\n\n    for a, b in zip(part1, part2):\n        proc1 = Process(target=mergengroup, args=(a, series_dict, tn_events.loc[a], grouper, ctr))\n        proc2 = Process(target=mergengroup, args=(b, series_dict, tn_events.loc[b], grouper, ctr))\n\n        proc1.start()\n        proc2.start()\n\n        proc1.join()\n        proc2.join()\n\n\ndel tn_events","metadata":{"execution":{"iopub.status.busy":"2023-10-15T10:37:05.391401Z","iopub.execute_input":"2023-10-15T10:37:05.392140Z","iopub.status.idle":"2023-10-15T11:05:16.276956Z","shell.execute_reply.started":"2023-10-15T10:37:05.392089Z","shell.execute_reply":"2023-10-15T11:05:16.275086Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-10-15T11:05:27.560585Z","iopub.execute_input":"2023-10-15T11:05:27.561100Z","iopub.status.idle":"2023-10-15T11:05:27.883321Z","shell.execute_reply.started":"2023-10-15T11:05:27.561052Z","shell.execute_reply":"2023-10-15T11:05:27.881986Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train-test split","metadata":{}},{"cell_type":"code","source":"# split series dict for train and test\n\nrandom.seed(3)\ntrain_size = int(0.8 * len(series_dict))\ntest_size = len(series_dict) - train_size\ntrain_id = random.sample(series_dict.keys(), train_size)\ntest_id = random.sample([x for x in series_dict.keys() if x not in train_id], train_size)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# just like the complete date, train and test series will also be in dictionaries for quick access\n\ntrain_dict = {}\ntest_dict = {}\n\nfor item in test_id:\n    test_dict[item] = series_dict[item]\n    \nfor item in train_id:\n    train_dict[item] = series_dict[item]","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"rnd = random.choice(train_id)\nplotter(train_dict[rnd], 'trend')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = \"cuda\"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Event pointer in window","metadata":{}},{"cell_type":"code","source":"def position_data(dict1):\n\n    for i, item in enumerate(dict1.keys()):\n        newdf = dict1[item]\n        newdf = newdf[newdf.event!=0][['elements', 'oevent']].reset_index(drop=True)\n        newdf['rmean'] = [pd.Series(x).rolling(3).mean().fillna(method='ffill').fillna(method='bfill') for x in newdf.elements]\n        newdf['del'] = [(pd.Series(x).diff(-1) * -1).fillna(value=0.0) for x in newdf.elements]\n        \n        if i==0:\n            xtn = torch.stack([torch.tensor(newdf[['rmean', 'del', 'elements']].loc[x], dtype=torch.float32) for x in newdf.index])\n            xtn = torch.transpose(xtn, 1, 2)\n            ytn = torch.stack([torch.tensor(x, dtype=torch.int64) for x in newdf.oevent])\n            \n        else:\n            posx = torch.stack([torch.tensor(newdf[['rmean', 'del', 'elements']].loc[x], dtype=torch.float32) for x in newdf.index])\n            posx = torch.transpose(posx, 1, 2)\n            posy = torch.stack([torch.tensor(x, dtype=torch.int64) for x in newdf.oevent])\n            xtn = torch.cat((xtn, posx), dim=0)\n            ytn = torch.cat((ytn, posy), dim=0)\n        \n        del newdf\n        \n    return xtn, ytn","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"posxtn, posytn = position_data(train_dict)\nposxts, posyts = position_data(test_dict)\n\nposxtn, posytn = posxtn.to(device), posytn.to(device)\nposxts, posyts = posxts.to(device), posyts.to(device)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for i, item in enumerate(ctn):\n    newdf = series_dict[item]\n    newdf = newdf[newdf.event!=0][['elements', 'oevent']].reset_index(drop=True)\n    newdf['rmean'] = [pd.Series(x).rolling(3).mean().fillna(method='ffill').fillna(method='bfill') for x in newdf.elements]\n    newdf['del'] = [(pd.Series(x).diff(-1) * -1).fillna(value=0.0) for x in newdf.elements]\n    if i==0:\n        posxtn = torch.stack([torch.tensor(newdf[['rmean', 'del', 'elements']].loc[x], dtype=torch.float32) for x in newdf.index])\n        posxtn = torch.transpose(posxtn, 1, 2)\n        posytn = torch.stack([torch.tensor(x, dtype=torch.int64) for x in newdf.oevent])\n    else:\n        posx = torch.stack([torch.tensor(newdf[['rmean', 'del', 'elements']].loc[x], dtype=torch.float32) for x in newdf.index])\n        posx = torch.transpose(posx, 1, 2)\n        posy = torch.stack([torch.tensor(x, dtype=torch.int64) for x in newdf.oevent])\n        posxtn = torch.cat((posxtn, posx), dim=0)\n        posytn = torch.cat((posytn, posy), dim=0)\n    \n\nnew_df = series_dict[cts]\nnew_df = new_df[new_df.event!=0][['elements', 'oevent']].reset_index(drop=True)\nnewdf['rmean'] = [pd.Series(x).rolling(3).mean().fillna(method='ffill').fillna(method='bfill') for x in newdf.elements]\nnewdf['del'] = [(pd.Series(x).diff(-1) * -1).fillna(value=0.0) for x in newdf.elements]\n\nposxts = torch.stack([torch.tensor(newdf[['rmean', 'del', 'elements']].loc[x], dtype=torch.float32) for x in newdf.index])\nposxts = torch.transpose(posxts, 1, 2)\nposyts = torch.stack([torch.tensor(x, dtype=torch.int64) for x in newdf.oevent])\ndel new_df","metadata":{"execution":{"iopub.status.busy":"2023-10-15T14:30:06.611238Z","iopub.execute_input":"2023-10-15T14:30:06.611664Z","iopub.status.idle":"2023-10-15T14:31:03.302512Z","shell.execute_reply.started":"2023-10-15T14:30:06.611630Z","shell.execute_reply":"2023-10-15T14:31:03.301158Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"uq_elem, pcounts = torch.unique(torch.flatten(posytn), return_counts=True)\npos_class_weight = dict(sorted(zip(uq_elem, pcounts)))\npos_class_weight = torch.tensor([1/k.item() for k in pos_class_weight.values()], dtype=torch.float32)\npos_class_weight = pos_class_weight.to(device)","metadata":{"execution":{"iopub.status.busy":"2023-10-15T14:33:36.126640Z","iopub.execute_input":"2023-10-15T14:33:36.127108Z","iopub.status.idle":"2023-10-15T14:33:36.138142Z","shell.execute_reply.started":"2023-10-15T14:33:36.127072Z","shell.execute_reply":"2023-10-15T14:33:36.136983Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class posfinder(nn.Module):\n    \n    def __init__(self):\n        super().__init__()\n        \n        self.lnrm = nn.LayerNorm(3)\n        self.seqa = nn.LSTM(input_size=3, hidden_size=64, num_layers=1, batch_first=True, bidirectional=True)\n        self.seqb = nn.LSTM(input_size=64*2, hidden_size=32, num_layers=1, batch_first=True, bidirectional=True)\n        self.seqc = nn.LSTM(input_size=32*2, hidden_size=8, num_layers=1, batch_first=True, bidirectional=True)\n        self.lin = nn.Linear(8*2, 3)\n        \n    def forward(self, x):\n        x = self.lnrm(x)\n        x, _ = self.seqa(x)\n        x, _ = self.seqb(x)\n        x, _ = self.seqc(x)\n        \n        out = self.lin(x)\n        \n        return out\n    ","metadata":{"execution":{"iopub.status.busy":"2023-10-15T14:53:39.979737Z","iopub.execute_input":"2023-10-15T14:53:39.980225Z","iopub.status.idle":"2023-10-15T14:53:39.994457Z","shell.execute_reply.started":"2023-10-15T14:53:39.980189Z","shell.execute_reply":"2023-10-15T14:53:39.993277Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"checker = posfinder()\n\nif torch.cuda.device_count() > 1:\n    checker = nn.DataParallel(checker)\n    \n    \nchecker = checker.to(device)\n\npos_optimiser = torch.optim.Adam(checker.parameters(), lr=1e-3)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for pep in range(700):    \n\n    for i in range(0, len(posxtn), 200):\n        X = posxtn[i: i+200]\n        Y = posytn[i: i+200]\n        \n        yout = checker(X)\n        pos_loss = torch.zeros(len(Y))\n        \n        for i, item in enumerate(zip(yout, Y)):\n            pos_loss[i] = F.cross_entropy(item[0], item[1], weight=pos_class_weight)\n            \n        mean_pos_loss = pos_loss.mean()\n        \n        pos_optimiser.zero_grad()\n        mean_pos_loss.backward()\n        pos_optimiser.step()\n    \n    if pep%100==0:\n        with torch.no_grad():\n            tnout = checker(posxtn)\n            tsout = checker(posxts)\n            \n        pos1 = torch.zeros(len(posytn))\n        pos2 = torch.zeros(len(posyts))\n        \n        for i, item in enumerate(zip(tnout, posytn)):\n            pos1[i] = F.cross_entropy(item[0], item[1], weight=pos_class_weight)\n            \n        for i, item in enumerate(zip(tsout, posyts)):\n            pos2[i] = F.cross_entropy(item[0], item[1], weight=pos_class_weight)\n            \n            \n        print(f\"Epoch: {pep}, Training loss: {pos1.mean()}, Test loss: {pos2.mean()}\")","metadata":{"execution":{"iopub.status.busy":"2023-10-15T14:53:46.914998Z","iopub.execute_input":"2023-10-15T14:53:46.916837Z","iopub.status.idle":"2023-10-15T15:19:31.746291Z","shell.execute_reply.started":"2023-10-15T14:53:46.916772Z","shell.execute_reply":"2023-10-15T15:19:31.744182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = test_dict[random.choice(test_id)]\ndf = df[df.event!=0][['elements', 'oevent']].reset_index(drop=True)\ndf['rmean'] = [pd.Series(x).rolling(3).mean().fillna(method='ffill').fillna(method='bfill') for x in df.elements]\ndf['del'] = [(pd.Series(x).diff(-1) * -1).fillna(value=0.0) for x in df.elements]","metadata":{"execution":{"iopub.status.busy":"2023-10-15T15:20:36.845651Z","iopub.execute_input":"2023-10-15T15:20:36.846209Z","iopub.status.idle":"2023-10-15T15:20:37.141820Z","shell.execute_reply.started":"2023-10-15T15:20:36.846174Z","shell.execute_reply":"2023-10-15T15:20:37.140597Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def pos_predicted(out_tens):\n    out_tens = out_tens.cpu()\n    scores = torch.max(F.softmax(out_tens, dim=1), dim=1)[0]\n    out_tens = torch.argmax(F.softmax(out_tens, dim=1), dim=1)\n    scores = scores * out_tens\n    \n    predicted = [x if i==torch.argmax(scores) else 0 for i, x in enumerate(out_tens)]\n    \n    return predicted","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"rand_pos = random.choice(df.index)\ninpt = torch.tensor(df[['rmean', 'del', 'elements']].loc[rand_pos], dtype=torch.float32)\ninpt = torch.transpose(inpt.unsqueeze(0), 1, 2)\ninpt = inpt.to(device)\n\nwith torch.no_grad():\n    outpt = checker(inpt).squeeze(0)\n\n# scores = torch.max(F.softmax(outpt, dim=1), dim=1)[0]\n# outpt = torch.argmax(F.softmax(outpt, dim=1), dim=1)\n# scores = scores * outpt\npos_pred = pos_predicted(outpt)","metadata":{"execution":{"iopub.status.busy":"2023-10-15T15:21:43.870297Z","iopub.execute_input":"2023-10-15T15:21:43.870709Z","iopub.status.idle":"2023-10-15T15:21:43.882678Z","shell.execute_reply.started":"2023-10-15T15:21:43.870677Z","shell.execute_reply":"2023-10-15T15:21:43.881341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.plot(df.elements.loc[rand_pos])\nplt.plot(df.rmean.loc[rand_pos], linestyle='dotted')\nplt.plot(df.oevent.loc[rand_pos], color='black')\nplt.plot(pos_pred, color='red')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-10-15T15:21:38.986455Z","iopub.execute_input":"2023-10-15T15:21:38.986870Z","iopub.status.idle":"2023-10-15T15:21:39.246041Z","shell.execute_reply.started":"2023-10-15T15:21:38.986839Z","shell.execute_reply":"2023-10-15T15:21:39.244933Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del df","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Autoencoding for embedding series windows","metadata":{"id":"1z33TtjCQxBC"}},{"cell_type":"code","source":"# the window embedding is a general operation independent of series id or timestamp\n# every window's own temporal sequence is relevant only, therefore all the windows can be stacked together\n\ntrain_series = pd.concat([x['elements'] for x in train_dict.values()], ignore_index=True).reset_index(drop=True)\ntest_series = pd.concat([x['elements'] for x in test_dict.values()], ignore_index=True).reset_index(drop=True)","metadata":{"id":"eZNON_95rUq4","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# stacks tensors of shape (1, window size) giving a tensor of shape (train samples, window size) \n\ntn_conv_inp = torch.stack([torch.tensor(x, dtype=torch.float32) for x in train_series])\nts_conv_inp = torch.stack([torch.tensor(x, dtype=torch.float32) for x in test_series])\n\ndel train_series, test_series","metadata":{"id":"gSc6Q7Qmr6RR","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# pads a 2d tensor with 2d zero tensors for uniformity in sequence length required for model input\n\ndef pad_seq(tnsr, size, gpsize):\n    \n    length = len(tnsr)\n\n    if length%size!=0:\n        pad_length = (np.ceil(length/size).astype('int')*size) - length\n\n        tnsr = torch.cat((tnsr, torch.zeros((pad_length, gpsize))), dim=0)\n    \n    return length, tnsr","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"batch_enc = 10000    # batch size to train auto encoder\n\n\n# as the autoencoder also uses a convolution layer for which batch size must be uniform\n# padding both train and test tensors so that number of samples is a multiple of batch size\n# if not padded, possibility of last batch containing less samples than batch size resulting in error\n\ntrain_original_length, tn_conv_inp = pad_seq(tn_conv_inp, batch_enc, grouper)\ntest_original_length, ts_conv_inp = pad_seq(ts_conv_inp, batch_enc, grouper)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# simple convolution model \n\nclass convolution(nn.Module):\n\n    def __init__(self, ssize, lng):\n        super().__init__()\n        msize = int(ssize/2)    # as the task is split between 2 GPUs each with half of the batch\n        \n        self.conv = nn.Conv1d(msize, msize, 5, padding=\"same\")\n        self.relu = nn.ReLU()\n        # self.bnorm = nn.BatchNorm1d(ssize)\n        self.lnorm = nn.LayerNorm(lng)\n\n    def forward(self, x):\n\n        x = self.conv(x)\n        x = self.relu(x)\n\n        # x = x.unsqueeze(0)\n        # x = self.bnorm(x)\n        # out = x.squeeze(0)\n        \n        out = self.lnorm(x)\n\n        return out\n","metadata":{"id":"Pd18rjh0sAC9","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class autoencoder(nn.Module):\n\n    def __init__(self, ssize, sizez):\n        super().__init__()\n\n        self.conv = convolution(ssize, sizez)\n        \n        embed_size = 2     # specifying what size the window series must be embedded down to\n        \n        self.enc1 = nn.LSTM(input_size=1, hidden_size=32, num_layers=1, batch_first=True, bidirectional=False)\n        self.enc2 = nn.LSTM(input_size=32, hidden_size=16, num_layers=1, batch_first=True, bidirectional=False)\n        self.enc3 = nn.LSTM(input_size=16, hidden_size=embed_size, num_layers=1, batch_first=True, bidirectional=False)\n\n        self.dec1 = nn.LSTM(input_size=embed_size, hidden_size=embed_size, num_layers=1, batch_first=True, bidirectional=False)\n        self.dec2 = nn.LSTM(input_size=embed_size, hidden_size=16, num_layers=1, batch_first=True, bidirectional=False)\n        self.dec3 = nn.LSTM(input_size=16, hidden_size=32, num_layers=1, batch_first=True, bidirectional=False)\n\n        self.otp = nn.Linear(32, 1)\n\n\n    def forward(self, x):\n        x = x.squeeze(2)\n        x = self.conv(x)\n        x = x.unsqueeze(2)\n\n        x, _ = self.enc1(x)\n        x, _ = self.enc2(x)\n        x, (hid, _) = self.enc3(x)     # window series of size 25 or 50 or 100 embedded down to 2 or 4 (as chosen)\n\n        x = torch.transpose(hid, 0, 1)\n        x = torch.flatten(x, start_dim=1).unsqueeze(1)\n        x = x.repeat(1, grouper, 1)\n\n        x, _ = self.dec1(x)\n        x, _ = self.dec2(x)\n        x, _ = self.dec3(x)\n\n        out = self.otp(x)     # reconstructed window series\n\n        return out, hid\n","metadata":{"id":"ITNXcECol7j1","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = autoencoder(batch_enc, grouper)\n\nae_lr = 1e-3\n\nif torch.cuda.device_count() > 1:\n    model = nn.DataParallel(model)\n    \nmodel = model.to(device)\n\noptimizer = torch.optim.Adam(model.parameters(), lr=ae_lr)","metadata":{"id":"PU18e4chIUMc","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for ep in range(50):\n\n    batch_loss = torch.zeros(batch_enc)     # stores mse loss in each batch of size batch_enc \n\n    for b in range(0, len(tn_conv_inp), batch_enc):\n        xi = tn_conv_inp[b: b+batch_enc]\n        xi = xi.unsqueeze(2)     # expanding the (sample size, window size) to (sample size, window size, 1) ~ (batches, samples, features)\n        xi = xi.to(device)\n\n        y, _ = model(xi)\n\n        mean_loss = F.mse_loss(y, xi, reduction='mean')     # input is the ground truth for reconstruction\n        optimizer.zero_grad()\n        mean_loss.backward(retain_graph=True)\n        optimizer.step()\n\n        \n    if ep==20:\n        ae_lr = 1e-5     # decrease learning rate at epoch 20\n        \n    if ep%5==0:\n\n        lozz = torch.zeros(np.ceil(len(tn_conv_inp)/batch_enc).astype('int'))    # stores evaluation mse loss in each batch of size batch_enc \n        lid = 0\n\n        for i in range(0, len(tn_conv_inp), batch_enc):\n            xi = tn_conv_inp[i: i+batch_enc]\n            xi = xi.unsqueeze(2)\n            xi = xi.to(device)\n\n            model.eval()\n            with torch.no_grad():\n                rec, _ = model(xi)\n                rec_loss = F.mse_loss(rec, xi, reduction='mean')\n                lozz[lid] = rec_loss\n            model.train()\n            lid+=1\n        print(f\"Epoch: {ep}, Reconstruction loss: {lozz.mean()}\")\n\n#     else:\n#         print(f\"Epoch: {ep}\")\n\n","metadata":{"id":"9yx5TQCMgmMT","outputId":"75149eb0-e744-4352-8710-9acdd33ea1d1","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# use the window embeddings as feature that represents the original windowed series\n# create a new feature for reconstruction error for wach window\n\n# creating a function to apply above on each train/test series separately as it is difficult to split the clubbed data into its constituent series\n\ndef embed_out(df):\n    \n    input_tensor = torch.stack([torch.tensor(x, dtype=torch.float32) for x in df['elements']])\n    \n    original_length, input_tensor = pad_seq(input_tensor, batch_enc, grouper)\n    \n\n    for i in range(0, len(input_tensor), batch_enc):\n        xi = input_tensor[i: i+batch_enc]\n        xi = xi.unsqueeze(2)\n        xi = xi.to(device)\n\n        with torch.no_grad():\n            if i==0:\n                out, hd = model(xi)    # the first batch creates 2 tensors out and hd to which succeeding batches will concatenate data\n\n            else:\n                o, h = model(xi)\n                out = torch.cat((out, o), dim=0)    # concatenates by dim 0 as it pertains to the batch dimension for the final output. Shape (batch, seq length, 1)\n                hd = torch.cat((hd, h), dim=1)     # concatenates by dim 1 as it pertains to the batch dimension for the hidden state (embedding state). Shape (1, batch, embed length)\n\n\n    out = out.squeeze(2)     # remove 3rd dimension of output to give a 2d tensor (df length, window size)\n\n    hd = torch.flatten(hd, start_dim=0, end_dim=1).squeeze(0) # flattening before squeezing dim 0 as shape was (2, batch, embed length) as 2 parallel GPUs were used\n    \n\n    out = out[:original_length]      # remove padding values by slicing outputs back to original length\n    hd = hd[:original_length]\n\n    \n    \n    # make new feature for reconstruction error\n    \n    err = []\n\n    for s1, s2 in zip(input_tensor, out):\n        s1 = s1.to(device)\n        err.append(F.mse_loss(s1, s2, reduction='mean').item())\n\n    df['rec_loss'] = err\n\n    \n    # add features for window embeddings. Using them as features to represent the original window of type tensor\n    hd = torch.transpose(hd, 0, 1)\n    \n    for n in range(len(hd)):\n        df['embedded'+str(n)] = hd[n].cpu()\n\n\n    del out, hd\n\n\n    return df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# add embedded series window and reconstruction loss to train and test dictionaries\n\nfor k in train_dict.keys():\n    train_dict[k] = embed_out(train_dict[k])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for k in test_dict.keys():\n    test_dict[k] = embed_out(test_dict[k])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gc.collect()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"columns = train_dict[rnd].columns.values\nfeatures = [x for x in columns if x not in ['step', 'elements', 'event']]\nlabels = 'event'","metadata":{"id":"zmx3NLNp9BSB","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# pads each series in a dictionary so that lengths match the longest series across the workspace, returns padded tensors and original length\n\ndef pseudo_stacker(dictn, features, labels, max_l):\n    lengths = []\n    for i, k in enumerate(dictn.keys()):\n        if i==0:\n            xval = torch.tensor(dictn[k][features].to_numpy(), dtype=torch.float32)\n            xl, xr = pad_seq(xval, max_l, len(features))\n            xr = xr.unsqueeze(0)\n            yval = torch.tensor(dictn[k][labels].to_numpy(), dtype=torch.int64)\n            ypad = max_l - xl\n            yr = torch.cat((yval, torch.zeros(ypad)))\n            yr = yr.unsqueeze(0)\n            lengths.append(xl)\n            del xval, yval\n        \n        else:\n            xval = torch.tensor(dictn[k][features].to_numpy(), dtype=torch.float32)\n            xl, x = pad_seq(xval, max_l, len(features))\n            x = x.unsqueeze(0)\n            yval = torch.tensor(dictn[k][labels].to_numpy(), dtype=torch.int64)\n            ypad = max_l - xl\n            y = torch.cat((yval, torch.zeros(ypad)))\n            y = y.unsqueeze(0)\n            lengths.append(xl)\n            del xval, yval\n            \n            xr = torch.cat((xr, x), dim=0)\n            yr = torch.cat((yr, y), dim=0)\n            \n    \n    return lengths, xr, yr.to(torch.int64)","metadata":{"id":"9PCMu3YZ6vxb","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# predicting the events at an individual level prevents us from clubbing all the series together\n# temporal sequence of individual series has to be kept separate and all series have to have uniform length, therefore padding is performed\n# each individual series will be treated as a batch\n\nmax_l = np.max([len(x) for x in train_dict.values()] + [len(x) for x in test_dict.values()])   # length of the longest series among train + test\ntrain_l, x_train, y_train = pseudo_stacker(train_dict, features, labels, max_l)\ntest_l, x_test, y_test = pseudo_stacker(test_dict, features, labels, max_l)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# getting weights for each of the classes 0, 1 and 2 by inverse frequency method as dataset is highly imbalanced\n# oversampling for temporally related samples seems ill advised\n\nunq_elm, cnts = torch.unique(torch.flatten(y_train), return_counts=True)\nclass_weight = dict(sorted(zip(unq_elm, cnts)))\nclass_weight = torch.tensor([1/k.item() for k in class_weight.values()], dtype=torch.float32)\nclass_weight = class_weight.to(device)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# sequence to sequence analysis trained on each series window and associated features and labels on whether that window contains an event\n# individual series id is a batch\n\nclass seq2seq(nn.Module):\n    \n    def __init__(self, in_size):\n        super().__init__()\n        \n        # using a bidirectional LSTM in this case\n        \n        self.seq1 = nn.LSTM(input_size=in_size, hidden_size=16, num_layers=1, batch_first=True, bidirectional=True)\n        self.seq2 = nn.LSTM(input_size=16*2, hidden_size=16, num_layers=1, batch_first=True, bidirectional=True)\n        self.seq3 = nn.LSTM(input_size=16*2, hidden_size=8, num_layers=1, batch_first=True, bidirectional=True)\n        \n        self.lin = nn.Linear(8*2, 3)\n        \n    def forward(self, x):\n        \n        x, _ = self.seq1(x)\n        x, _ = self.seq2(x)\n        x, _ = self.seq3(x)\n        out = self.lin(x)\n        \n        return out\n    ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"classif = seq2seq(len(features))\n\nif torch.cuda.device_count() > 1:\n    classif = nn.DataParallel(classif)\n    \n    \nclassif = classif.to(device)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# evaluation losses\n\n@torch.no_grad()\ndef get_losses():\n    classif.eval()\n    \n    # as each series is a batch, we can use them as input one at a time without having to set batch size\n\n    train_loss = torch.zeros(len(y_train))\n    for i, item in enumerate(zip(x_train, y_train)):\n        x, y = item[0].to(device), item[1].to(device)\n        ptn = classif(x.unsqueeze(0))\n        train_loss[i] = F.cross_entropy(ptn.squeeze(0)[:train_l[i]], y[:train_l[i]], weight=class_weight)\n\n    test_loss = torch.zeros(len(y_test))\n    for j, jtem in enumerate(zip(x_test, y_test)):\n        x, y = jtem[0].to(device), jtem[1].to(device)\n        pts = classif(x.unsqueeze(0))\n        test_loss[j] = F.cross_entropy(pts.squeeze(0)[:test_l[j]], y[:test_l[j]], weight=class_weight)\n\n    print(f\"Training loss: {train_loss.mean():.05f}, Validation loss: {test_loss.mean():.05f}\")    # mean cross entropy loss over all batches\n\n    classif.train()","metadata":{"id":"Cx9IjJKvHPmh","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"optimiser = torch.optim.Adam(classif.parameters(), lr=1e-3)\n\nfor epp in range(20):\n\n    for i, inp in enumerate(zip(x_train, y_train)):\n    \n        # as each series is a batch, we can use them as input one at a time without having to set batch size\n\n        x_inp = inp[0].to(device)\n        y_lab = inp[1].to(device)\n        logits = classif(x_inp)\n\n        # squeezing logits as output is of the shape (1, n, 3) as it contains a batch dimension whereas the ground truth is of shape (n, 1)\n        lozzes = F.cross_entropy(logits.squeeze(0)[:train_l[i]], y_lab[:train_l[i]], weight=class_weight)\n\n        optimiser.zero_grad()\n        lozzes.backward()\n        optimiser.step()\n\n    if epp%2==0:\n        print(f\"Epoch: {epp}\", end=\" \")\n        get_losses()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# checking result on any random test series\nrand_test = random.choice(np.arange(len(test_dict)))\ntest = test_dict[list(test_dict.keys())[rand_test]]\n\n\nwith torch.no_grad():\n    x_pr = x_test[rand_test].to(device)\n    pr = classif(x_pr)\n\n    out_probs = F.softmax(pr.squeeze(0), dim=1) # predicted class probabilities\n    preds = torch.argmax(out_probs, dim=1)    # max probability class\n    scores = torch.max(out_probs, dim=1)      # probability or confidence scores\n    \n    preds = preds[:test_l[rand_test]]    # removing the padded section for this specific series\n\npredictions = preds.cpu().numpy()\nconfidence = scores[0].cpu().numpy()[:test_l[rand_test]]   # scores[0] indexed because torch.max() returns a tuple where first element is the max value\ntest['pevent'] = predictions\ntest['conf'] = confidence","metadata":{"id":"OcKIAdSMIeFC","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(30, 4))\n\nplt.plot(test['step'], test['trend'])\n\n# ground truths\nfor i in test[test.event==1].index:\n    plt.axvline(x=i, color='black', linestyle='solid', linewidth=10, alpha=0.3)\nfor i in test[test.event==2].index:\n    plt.axvline(x=i, color='orange', linestyle='solid', linewidth=10, alpha=0.3)\n    \n# predictions with more than 0.99 probability\nfor i in test[(test.pevent==1) & (test.conf>0.99)].index:\n    plt.axvline(x=i, color='black', linestyle='dashed')\nfor i in test[(test.pevent==2) & (test.conf>0.99)].index:\n    plt.axvline(x=i, color='orange', linestyle='dashed')\n\nplt.show()    # observation, quite a good result","metadata":{"id":"8LRxRm0f5xBz","outputId":"f9c7acb1-76ab-4f0b-fd0d-16c5ec7808d1","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}