resolve merge conflict
[notebooks.git] / fmda / presentations / rnn_data_structure.ipynb
blob8cfd4aa7e9375acf9ac854bebf77d24748b2b341
2  "cells": [
3   {
4    "cell_type": "markdown",
5    "id": "e8155370-6650-4d18-b3e4-c39260d217b4",
6    "metadata": {},
7    "source": [
8     "# Input Data Structure for RNNs\n",
9     "\n",
10     "## Background\n",
11     "\n",
12     "RNNs are a type of timeseries model that relates the outcome at time $t$ to the outcome at previous times. Like other machine learning models, training is typically done by calculating the gradient of the output with respect to the weights, or parameters, of the model. With recursive or other type of autoregressive models, the gradient calculation at time $t$ ends up depending on the gradient at $t-1, t-2, ...,$ and to $t=0$. This ends up being computationally expensive, but more importantly can lead to \"vanishing\" or \"exploding\" gradient problems, where many gradients are multiplied together and either blow up or shrink. See LINK_TO_RECURSIVE_GRADIENT_LATEX for more info...\n",
13     "\n",
14     "RRNs and other timeseries neural network architectures* get around this issue by approximating the gradient in more stable ways. In addition to many model architecture and hyperparameter options, timeseries neural networks use two main ways of restructuring the input data.\n",
15     "\n",
16     "* **Sequence Length:** The input data is divided into smaller collections of ordered events, known as sequences. When calculating the gradient with respect to the model weights, the gradient only looks back this number of timesteps. Also known as `timesteps`, `sequence_length`, or just \"sample\" in `tensorflow` functions and related literature. For a sequence length of 3, the unique sequences of length 3 are: `[1,2,3], [2,3,4], ..., [T-2, T-1, T]`, for a total number of sequences `T-timesteps+1`\n",
17     "\n",
18     "* **Batch size:** Sequences are grouped together into batches. The batch size determines how many sequences the network processes in a single step of calculating loss and updating weights. Used as `batch_size` in `tensorflow`.\n",
19     "\n",
20     "The total number of batches is therefore determined from the total number of observations $T$ and the batch size. In a single batch, the loss is typically calculated for each sequence and then averaged to produce a single value. Then, the gradient of the loss with respect to the parameters (weights and biases) is computed for each sequence in the batch. So each batch will have a single gradient calculation that is the sum of the gradients of each sequence in the batch.\n",
21     "\n",
22     "**Note:* these same data principles apply to more complex versions of timeseries neural network layers, such as LSTM and GRU."
23    ]
24   },
25   {
26    "cell_type": "markdown",
27    "id": "39fa2a9d-c74b-445d-a875-e7591c7a6666",
28    "metadata": {},
29    "source": [
30     "## Stateless vs Stateful Networks\n",
31     "\n",
32     "RNNs have a hidden state that represents the recurrent layer output at a previous time. There is a weight and bias at each RNN cell that determines the relative contribution of the previous output to the current output. When updating weights in RNNs, there are two main types of training scheme:\n",
33     "\n",
34     "**Stateless:** the hidden state is reset to the initial state (often zero) at the start of each new sequence in a batch. So, the network treats each sequence independently, and no information is carried over in time between sequences. These models are simpler, but work better when time dependence is relatively short.\n",
35     "* **Input Data Shape:** (`n_sequence`, `timesteps`, `features`), where `n_sequence` is total number of sequences (a function of total observed times `T` and the user choice of timesteps). The input data does NOT need to be structured with batch size in stateless RNNs.\n",
36     "* **Tensorflow RNN Args:** for a stateless RNN, use the `input_shape` parameter, with `input_shape`=(`timesteps`, `features`). Then, `batch_size` can be declared in the fitting stage with `model.fit(X_train, batch_size = __)`. \n",
37     "\n",
38     "**Stateful:** the hidden states are carried over from one sequence to the next within a batch. Longer time dependencies can be learned in this way.\n",
39     "* **Input Data Shape:** (`batch_size`, `timesteps`, `features`). In order for the hidden state to be passed between sequences, the input data must be formatted using the `batch_size` hyperparameter.\n",
40     "* **Tensorflow RNN Args:** for a stateful RNN, use the `batch_input_shape` parameter, with `batch_input_shape`=(`batch_size`, `timesteps`, `features`)"
41    ]
42   },
43   {
44    "cell_type": "code",
45    "execution_count": null,
46    "id": "2fa3f116-148d-4251-a8a5-e908710bc3e4",
47    "metadata": {},
48    "outputs": [],
49    "source": [
50     "import numpy as np\n",
51     "from tensorflow.keras.preprocessing import timeseries_dataset_from_array\n",
52     "import sys\n",
53     "sys.path.append(\"..\")\n",
54     "from moisture_rnn import staircase, staircase_2\n",
55     "import pandas as pd"
56    ]
57   },
58   {
59    "cell_type": "markdown",
60    "id": "50e9011c-ee92-4bc2-bf6b-461b5bf9c662",
61    "metadata": {},
62    "source": [
63     "## Examples\n",
64     "\n",
65     "### Data Description\n",
66     "\n",
67     "Consider $T=100$ observations of a variable in time $y_t$, so $t=1, ..., 100$. A feature matrix with $3$ features has dimensions $100\\times 3$, and must be restructured for use in RNNs. In the code below each feature is a sequential list 1, 2, ..., 100 (since python numbering starts at 0)."
68    ]
69   },
70   {
71    "cell_type": "code",
72    "execution_count": null,
73    "id": "2c03bc7a-13c4-453c-97d1-d158c85e3b9f",
74    "metadata": {},
75    "outputs": [],
76    "source": [
77     "# print_dict_summary(train)"
78    ]
79   },
80   {
81    "cell_type": "code",
82    "execution_count": null,
83    "id": "03048248-0c81-491d-b0ce-a099bd984dae",
84    "metadata": {},
85    "outputs": [],
86    "source": [
87     "T=100 # total number of times obseved\n",
88     "features = 3\n",
89     "\n",
90     "# data = np.column_stack(\n",
91     "#     (np.arange(1, 101)+10, \n",
92     "#      np.arange(1, 101)+20, \n",
93     "#      np.arange(1, 101)+30))\n",
94     "data = np.arange(1, 101).reshape(-1, 1)\n",
95     "\n",
96     "# Generate random response vector, needed by staircase func\n",
97     "y = np.arange(1, 101).reshape(-1, 1)\n",
98     "\n",
99     "print(f\"Response Data Shape: {y.shape}\")\n",
100     "print(\"First 10 rows\")\n",
101     "print(y[0:10])\n",
102     "\n",
103     "# Print head of data\n",
104     "print(f\"Feature Data Shape: {data.shape}\")\n",
105     "print(\"First 10 rows\")\n",
106     "data[0:10,:]"
107    ]
108   },
109   {
110    "cell_type": "markdown",
111    "id": "eb559e3f-b7ca-40a6-8d64-98df3bcb0f51",
112    "metadata": {},
113    "source": [
114     "The rows of the input data array represent all features at a single timepoint. The first digit represents the feature number, and the second digit represents time point. So value $13$ represents feature 1 at time 3."
115    ]
116   },
117   {
118    "cell_type": "markdown",
119    "id": "571efd5b-7a38-4a79-9f34-8c5c7a64933f",
120    "metadata": {},
121    "source": [
122     "### Single Batch Example\n",
123     "\n",
124     "With a stateless RNN, the input data is structured to be of shape (`n_sequence`, `timesteps`, `features`). The `batch_size` is not needed to structure the data for a stateless RNN.\n",
125     "\n",
126     "When using functions that expect `batch_size` to structure the data, an option is to set `batch_size` to be some large number greater than the total number of observed times $T$, so that all the data is guarenteed to be in one batch. *NOTE:* here we trick the function by using a large batch size, but `batch_size` could still be declared at the fitting stage of the model.\n",
127     "\n",
128     "Suppose in we use `timesteps=5`, so we would get sequences of times `[1,2,3,4,5]` for the first sequence, then `[2,3,4,5,6]` for the next, and so on until `[96,97,98,99,100]`. \n",
129     "\n",
130     "Thus, there are `100-5+1=96` possible ordered sequences of length `5`. "
131    ]
132   },
133   {
134    "cell_type": "markdown",
135    "id": "7e17e4dc-62be-4001-ae07-1e9a985f5b9e",
136    "metadata": {},
137    "source": [
138     "We need to structure the input data to the RNN to be of shape (96, 5, 3). *Note:* since the model is stateless, and the sequences are treated independently, the actual order of the sequences doesn't matter."
139    ]
140   },
141   {
142    "cell_type": "markdown",
143    "id": "af506b7e-7ffd-484f-a1d5-a748da8f2f6d",
144    "metadata": {},
145    "source": [
146     "For a stateless RNN, the batches could consist of any collection of the sequences, since the sequences are indepenent. \n",
147     "\n",
148     "We want all of the data sequences to be in a single batch, but number of batches is not a direct user input for most built-in functions. To get around this, we make the batch size some number larger than the total number of observed times."
149    ]
150   },
151   {
152    "cell_type": "markdown",
153    "id": "865805e0-caf8-4931-b9d2-6bd64cda0189",
154    "metadata": {},
155    "source": [
156     "We now recreate this using the custom `staircase` function, which produces the same results for the input data:"
157    ]
158   },
159   {
160    "cell_type": "code",
161    "execution_count": null,
162    "id": "e350c887-af78-4036-bd66-f0e9fe574d1d",
163    "metadata": {},
164    "outputs": [],
165    "source": [
166     "X_train, y_train = staircase(data, y, timesteps = 5, datapoints = len(y), verbose=True)"
167    ]
168   },
169   {
170    "cell_type": "code",
171    "execution_count": null,
172    "id": "7b51ca33-40ea-4ef4-a26d-b1a875e61313",
173    "metadata": {},
174    "outputs": [],
175    "source": [
176     "print(X_train.shape)\n",
177     "print(y_train.shape)"
178    ]
179   },
180   {
181    "cell_type": "code",
182    "execution_count": null,
183    "id": "e321e115-1126-4aa8-9cee-020c1e464ed0",
184    "metadata": {},
185    "outputs": [],
186    "source": [
187     "pd.DataFrame((data))"
188    ]
189   },
190   {
191    "cell_type": "code",
192    "execution_count": null,
193    "id": "32140b50-12e8-4352-80fd-507db473c5c5",
194    "metadata": {},
195    "outputs": [],
196    "source": [
197     "print(X_train)"
198    ]
199   },
200   {
201    "cell_type": "markdown",
202    "id": "b14965b3-75cf-43e9-b3fe-85900e473a86",
203    "metadata": {},
204    "source": [
205     "### Stateful Multi-Batch Example"
206    ]
207   },
208   {
209    "cell_type": "markdown",
210    "id": "ce8685b5-7e79-462b-b782-6a493dc63184",
211    "metadata": {},
212    "source": [
213     "We now need the data in the format (`batch_size`, `timesteps`, `features`) for each batch. "
214    ]
215   },
216   {
217    "cell_type": "code",
218    "execution_count": null,
219    "id": "341283b8-f66a-4c6b-a314-a3df4d1f08bb",
220    "metadata": {},
221    "outputs": [],
222    "source": [
223     "X_train, y_train = staircase_2(data, y, timesteps = 5, batch_size = 32, verbose=False)"
224    ]
225   },
226   {
227    "cell_type": "code",
228    "execution_count": null,
229    "id": "8b5edf06-2340-4358-9824-4d501812795e",
230    "metadata": {},
231    "outputs": [],
232    "source": [
233     "X_train"
234    ]
235   },
236   {
237    "cell_type": "markdown",
238    "id": "f0905891-6b67-4a47-93f7-222dedcf74cb",
239    "metadata": {},
240    "source": [
241     "## References\n",
242     "\n",
243     "https://d2l.ai/chapter_recurrent-neural-networks/bptt.html\n",
244     "\n",
245     "https://www.tensorflow.org/guide/keras/working_with_rnns#cross-batch_statefulness\n",
246     "\n",
247     "Tensorflow `timeseries_dataset_from_array` tutorial: https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/timeseries_dataset_from_array\n",
248     "\n",
249     "Wiki BPTT: https://en.wikipedia.org/wiki/Backpropagation_through_time#:~:text=Backpropagation%20through%20time%20(BPTT)%20is,independently%20derived%20by%20numerous%20researchers.\n",
250     "\n",
251     "https://machinelearningmastery.com/understanding-stateful-lstm-recurrent-neural-networks-python-keras/"
252    ]
253   },
254   {
255    "cell_type": "code",
256    "execution_count": null,
257    "id": "b3898af7-b504-4308-87b2-54c217709a54",
258    "metadata": {},
259    "outputs": [],
260    "source": []
261   }
262  ],
263  "metadata": {
264   "kernelspec": {
265    "display_name": "Python 3 (ipykernel)",
266    "language": "python",
267    "name": "python3"
268   },
269   "language_info": {
270    "codemirror_mode": {
271     "name": "ipython",
272     "version": 3
273    },
274    "file_extension": ".py",
275    "mimetype": "text/x-python",
276    "name": "python",
277    "nbconvert_exporter": "python",
278    "pygments_lexer": "ipython3",
279    "version": "3.10.9"
280   }
281  },
282  "nbformat": 4,
283  "nbformat_minor": 5