QTW - 7333
Module 13: Recurrent Neural Networks
Part 1: Introduction Recurrent Neural Networks
Recurrent Neural Networks are designed for sequence-dependent data
like language and time series. Unlike dense and convolutional neural
networks that process data all at once, RNNs process inputs one step at
a time while maintaining a memory of previous outputs.
At each timestep, the network receives current input and the output
of the previous step. This is why RNNs are called “recurrent”—they feed
outputs back into themselves.
At timestep 0, there’s no previous input, so a matrix of zeros is
used. RNNs can be visualized in two ways:
- Unrolled view: layers shown sequentially over time
- Rolled view: the same layer reused repeatedly with shared weights
Each step’s output is used for prediction and is also passed forward
as input for the next time step.
The structure involves two sets of inputs:
- The actual data (e.g., stock prices)
- The output of the previous timestep
These are treated as one concatenated input matrix, which simplifies
implementation. The sequence length must be consistent for all input
data, which presents a challenge in NLP where sentence lengths vary.
This is solved with padding or truncating.
Each RNN layer has two sets of weights—one for the normal input and
one for the recurrent input. These are treated separately in libraries
like Keras, so two sets of weights are maintained and trained.
Part 2: A Brief NLP Introduction
NLP tasks require converting language into a numeric format RNNs can
work with. One-hot encoding is inefficient for large vocabularies and
results in sparse representations, so instead, we use word
embeddings—dense vectors that capture word meaning.
Sentences must be converted to the same length using padding (adding
a “pad” word token like 0) or truncation. The pad word does not carry
meaning and is just a filler. Padding is applied to make the input shape
consistent.
The position of a word in a sentence matters. RNNs process the
sentence in order, making them suitable for language tasks like
completing sentences or sentiment analysis.
Word vectors (embeddings) can be learned by the model or pre-trained.
They represent words in dense numerical form and live in
high-dimensional space. Words with similar meanings point in similar
directions in this space.
Cosine similarity is used to compare these vectors:
- Cosine similarity = dot product of two vectors divided by product of
their magnitudes
- Cosine distance = 1 - cosine similarity
- Close direction = semantic similarity
Part 3: Building RNN for NLP
Text is converted to unique integers via tokenization. Example:
“The movie was the best I have seen” → [2, 7, 15, 3, 9, 11, 19]
Each word is mapped to an integer. Common words are assigned lower
numbers. Unknown words are handled with a special token (e.g., 2).
Padding is added to make all sequences the same length (e.g., 500).
The network structure:
- Embedding layer (must be the first): learns dense
representations
- Simple RNN layer: processes sequence
- Dense layer with sigmoid: outputs sentiment (0 or 1)
Embedding uses row lookup rather than matrix multiplication. For each
input word integer, it retrieves the corresponding vector from the
embedding matrix. Output of each step is passed to the RNN, which
outputs to the final dense layer.
RNNs have short memory (~5 steps). LSTM and GRU extend memory
capability but use more parameters. RNNs can perform well even with
basic setups.
Part 4: Data Preparation
Use the IMDB dataset. Limit vocabulary size to 5000 for performance.
Data is already tokenized (words to integers). Each review is a list of
integers.
Use get_word_index()
to map integers back to words and
build a reverse lookup. Special tokens include:
- 0: PAD
- 1: START
- 2: UNK
- 3: UNUSED
Create a decode function to turn sequences into text.
Check review lengths:
- Max: ~2500
- 99% < 1000
- 95% < 600
- 90% < 467
- Cutoff = 500 captures ~92% of data
Use pad_sequences()
to trim/pad data to length 500.
Now all reviews are exactly 500 integers long, ready for input into
the model.
Part 5: Building Your Network
Model architecture:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, SimpleRNN, Dense
model = Sequential()
model.add(Embedding(input_dim=5000, output_dim=100, input_length=500))
model.add(SimpleRNN(256))
model.add(Dense(1, activation='sigmoid'))
Compile and train:
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=3, batch_size=64)
Explanation:
- Embedding outputs: (batch_size, 500, 100)
- SimpleRNN outputs: (batch_size, 256)
- Dense layer: compresses 256 to 1 output
- Total parameters = vocab_size × embedding_dim + RNN weights +
bias
Training takes time due to 500 steps per sample. After 3
epochs:
- Accuracy = ~75–78%
- Validation = ~76.5%
- Decent for a simple 3-layer RNN model
Part 6: Advanced RNN Layers
You can replace SimpleRNN
with LSTM
or
GRU
.
from tensorflow.keras.layers import LSTM, GRU
model.add(LSTM(256)) # or model.add(GRU(256))
- LSTM has 4 gates: input, forget, output, and cell state
- GRU has 3 gates: update, reset, new
- LSTM: 4× parameter count
- GRU: 3× parameter count
- Embedding layer unchanged
- Dense layer unchanged
Speed and memory tradeoffs:
RNN |
Low |
1× |
Fast, short memory |
GRU |
Medium |
3× |
Efficient |
LSTM |
High |
4× |
Best for long dependencies |
All three are valid. Choice depends on resources and problem.
Key Takeaways — QTW 7333 Module 13: Recurrent Neural
Networks
- RNNs handle sequential data
- Memory is built by feeding previous outputs as inputs
- Used for tasks like language, time series, prediction
- Inputs must be uniform length
- NLP input is padded/truncated to fixed size (commonly 500)
- Padding uses 0s; truncating drops from start or end
- Word representation matters
- One-hot = inefficient
- Embeddings = dense, learned representations
- Can be trained or pre-loaded (e.g., GloVe)
- Basic RNNs have short memory (~5 steps)
- Vanishing gradient limits long-term dependencies
- LSTM and GRU are drop-in replacements with gating memory
- Simple RNN architecture is three layers
- Embedding → RNN → Dense(sigmoid)
- Works well for sentiment classification (~76% accuracy on IMDB)
- LSTM = 4× parameters of RNN
GRU = 3× parameters of RNN
- Trade-off: GRU is faster, LSTM remembers more
- Both are better than basic RNN for long sequences
- Data pipeline matters
- IMDB is pre-tokenized
- Word index and reverse index are used for encoding/decoding
- Proper preprocessing is required for model input
- Cosine similarity measures semantic similarity in vector
space
- Used in NLP tasks for comparing word vectors
- Only direction of vector matters, not magnitude
- Training time increases with sequence length
- 500-timestep sequences take longer to run
- RNNs inherently run sequentially, limiting parallelization
- Output layer is a binary classifier using sigmoid
- For sentiment: 0 = negative, 1 = positive
- Loss = binary crossentropy
Got it.
Mathematical Representation – RNN, LSTM, GRU
1. Basic RNN
At each timestep \(t\):
2. LSTM (Long Short-Term Memory)
At each timestep \(t\):
Forget gate:
\(f_t = \sigma(W_f x_t + U_f h_{t-1} +
b_f)\)
Input gate:
\(i_t = \sigma(W_i x_t + U_i h_{t-1} +
b_i)\)
Candidate cell state:
\(\tilde{c}_t = \tanh(W_c x_t + U_c h_{t-1} +
b_c)\)
New cell state:
\(c_t = f_t \odot c_{t-1} + i_t \odot
\tilde{c}_t\)
Output gate:
\(o_t = \sigma(W_o x_t + U_o h_{t-1} +
b_o)\)
Hidden state:
\(h_t = o_t \odot \tanh(c_t)\)
3. GRU (Gated Recurrent Unit)
At each timestep \(t\):
Update gate:
\(z_t = \sigma(W_z x_t + U_z h_{t-1} +
b_z)\)
Reset gate:
\(r_t = \sigma(W_r x_t + U_r h_{t-1} +
b_r)\)
Candidate:
\(\tilde{h}_t = \tanh(W_h x_t + r_t \odot U_h
h_{t-1} + b_h)\)
Final hidden state:
\(h_t = (1 - z_t) \odot h_{t-1} + z_t \odot
\tilde{h}_t\)
3 Questions for Dr. Slater
How do we determine whether to use LSTM or GRU for a specific NLP
task in terms of performance versus resource efficiency?
In sequence prediction, when is it more appropriate to use the
final timestep output versus the full sequence of outputs?
How do pretrained embeddings (like GloVe or Word2Vec) affect RNN
performance compared to training embeddings from scratch?
LS0tDQp0aXRsZTogIjczMzMgTW9kdWxlIDEzIC0gUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyINCmF1dGhvcjogIkplc3NpY2EgTWNQaGF1bCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KKipRVFcgLSA3MzMzKiogIA0KKipNb2R1bGUgMTM6IFJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MqKg0KDQotLS0NCg0KKipQYXJ0IDE6IEludHJvZHVjdGlvbiBSZWN1cnJlbnQgTmV1cmFsIE5ldHdvcmtzKioNCg0KUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyBhcmUgZGVzaWduZWQgZm9yIHNlcXVlbmNlLWRlcGVuZGVudCBkYXRhIGxpa2UgbGFuZ3VhZ2UgYW5kIHRpbWUgc2VyaWVzLiBVbmxpa2UgZGVuc2UgYW5kIGNvbnZvbHV0aW9uYWwgbmV1cmFsIG5ldHdvcmtzIHRoYXQgcHJvY2VzcyBkYXRhIGFsbCBhdCBvbmNlLCBSTk5zIHByb2Nlc3MgaW5wdXRzIG9uZSBzdGVwIGF0IGEgdGltZSB3aGlsZSBtYWludGFpbmluZyBhIG1lbW9yeSBvZiBwcmV2aW91cyBvdXRwdXRzLg0KDQpBdCBlYWNoIHRpbWVzdGVwLCB0aGUgbmV0d29yayByZWNlaXZlcyBjdXJyZW50IGlucHV0IGFuZCB0aGUgb3V0cHV0IG9mIHRoZSBwcmV2aW91cyBzdGVwLiBUaGlzIGlzIHdoeSBSTk5zIGFyZSBjYWxsZWQgInJlY3VycmVudCLigJR0aGV5IGZlZWQgb3V0cHV0cyBiYWNrIGludG8gdGhlbXNlbHZlcy4NCg0KQXQgdGltZXN0ZXAgMCwgdGhlcmUncyBubyBwcmV2aW91cyBpbnB1dCwgc28gYSBtYXRyaXggb2YgemVyb3MgaXMgdXNlZC4gUk5OcyBjYW4gYmUgdmlzdWFsaXplZCBpbiB0d28gd2F5czogIA0KLSBVbnJvbGxlZCB2aWV3OiBsYXllcnMgc2hvd24gc2VxdWVudGlhbGx5IG92ZXIgdGltZSAgDQotIFJvbGxlZCB2aWV3OiB0aGUgc2FtZSBsYXllciByZXVzZWQgcmVwZWF0ZWRseSB3aXRoIHNoYXJlZCB3ZWlnaHRzDQoNCkVhY2ggc3RlcOKAmXMgb3V0cHV0IGlzIHVzZWQgZm9yIHByZWRpY3Rpb24gYW5kIGlzIGFsc28gcGFzc2VkIGZvcndhcmQgYXMgaW5wdXQgZm9yIHRoZSBuZXh0IHRpbWUgc3RlcC4NCg0KVGhlIHN0cnVjdHVyZSBpbnZvbHZlcyB0d28gc2V0cyBvZiBpbnB1dHM6ICANCi0gVGhlIGFjdHVhbCBkYXRhIChlLmcuLCBzdG9jayBwcmljZXMpICANCi0gVGhlIG91dHB1dCBvZiB0aGUgcHJldmlvdXMgdGltZXN0ZXANCg0KVGhlc2UgYXJlIHRyZWF0ZWQgYXMgb25lIGNvbmNhdGVuYXRlZCBpbnB1dCBtYXRyaXgsIHdoaWNoIHNpbXBsaWZpZXMgaW1wbGVtZW50YXRpb24uIFRoZSBzZXF1ZW5jZSBsZW5ndGggbXVzdCBiZSBjb25zaXN0ZW50IGZvciBhbGwgaW5wdXQgZGF0YSwgd2hpY2ggcHJlc2VudHMgYSBjaGFsbGVuZ2UgaW4gTkxQIHdoZXJlIHNlbnRlbmNlIGxlbmd0aHMgdmFyeS4gVGhpcyBpcyBzb2x2ZWQgd2l0aCBwYWRkaW5nIG9yIHRydW5jYXRpbmcuDQoNCkVhY2ggUk5OIGxheWVyIGhhcyB0d28gc2V0cyBvZiB3ZWlnaHRz4oCUb25lIGZvciB0aGUgbm9ybWFsIGlucHV0IGFuZCBvbmUgZm9yIHRoZSByZWN1cnJlbnQgaW5wdXQuIFRoZXNlIGFyZSB0cmVhdGVkIHNlcGFyYXRlbHkgaW4gbGlicmFyaWVzIGxpa2UgS2VyYXMsIHNvIHR3byBzZXRzIG9mIHdlaWdodHMgYXJlIG1haW50YWluZWQgYW5kIHRyYWluZWQuDQoNCi0tLQ0KDQoqKlBhcnQgMjogQSBCcmllZiBOTFAgSW50cm9kdWN0aW9uKioNCg0KTkxQIHRhc2tzIHJlcXVpcmUgY29udmVydGluZyBsYW5ndWFnZSBpbnRvIGEgbnVtZXJpYyBmb3JtYXQgUk5OcyBjYW4gd29yayB3aXRoLiBPbmUtaG90IGVuY29kaW5nIGlzIGluZWZmaWNpZW50IGZvciBsYXJnZSB2b2NhYnVsYXJpZXMgYW5kIHJlc3VsdHMgaW4gc3BhcnNlIHJlcHJlc2VudGF0aW9ucywgc28gaW5zdGVhZCwgd2UgdXNlIHdvcmQgZW1iZWRkaW5nc+KAlGRlbnNlIHZlY3RvcnMgdGhhdCBjYXB0dXJlIHdvcmQgbWVhbmluZy4NCg0KU2VudGVuY2VzIG11c3QgYmUgY29udmVydGVkIHRvIHRoZSBzYW1lIGxlbmd0aCB1c2luZyBwYWRkaW5nIChhZGRpbmcgYSAicGFkIiB3b3JkIHRva2VuIGxpa2UgMCkgb3IgdHJ1bmNhdGlvbi4gVGhlIHBhZCB3b3JkIGRvZXMgbm90IGNhcnJ5IG1lYW5pbmcgYW5kIGlzIGp1c3QgYSBmaWxsZXIuIFBhZGRpbmcgaXMgYXBwbGllZCB0byBtYWtlIHRoZSBpbnB1dCBzaGFwZSBjb25zaXN0ZW50Lg0KDQpUaGUgcG9zaXRpb24gb2YgYSB3b3JkIGluIGEgc2VudGVuY2UgbWF0dGVycy4gUk5OcyBwcm9jZXNzIHRoZSBzZW50ZW5jZSBpbiBvcmRlciwgbWFraW5nIHRoZW0gc3VpdGFibGUgZm9yIGxhbmd1YWdlIHRhc2tzIGxpa2UgY29tcGxldGluZyBzZW50ZW5jZXMgb3Igc2VudGltZW50IGFuYWx5c2lzLg0KDQpXb3JkIHZlY3RvcnMgKGVtYmVkZGluZ3MpIGNhbiBiZSBsZWFybmVkIGJ5IHRoZSBtb2RlbCBvciBwcmUtdHJhaW5lZC4gVGhleSByZXByZXNlbnQgd29yZHMgaW4gZGVuc2UgbnVtZXJpY2FsIGZvcm0gYW5kIGxpdmUgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZS4gV29yZHMgd2l0aCBzaW1pbGFyIG1lYW5pbmdzIHBvaW50IGluIHNpbWlsYXIgZGlyZWN0aW9ucyBpbiB0aGlzIHNwYWNlLg0KDQpDb3NpbmUgc2ltaWxhcml0eSBpcyB1c2VkIHRvIGNvbXBhcmUgdGhlc2UgdmVjdG9yczogIA0KLSBDb3NpbmUgc2ltaWxhcml0eSA9IGRvdCBwcm9kdWN0IG9mIHR3byB2ZWN0b3JzIGRpdmlkZWQgYnkgcHJvZHVjdCBvZiB0aGVpciBtYWduaXR1ZGVzICANCi0gQ29zaW5lIGRpc3RhbmNlID0gMSAtIGNvc2luZSBzaW1pbGFyaXR5ICANCi0gQ2xvc2UgZGlyZWN0aW9uID0gc2VtYW50aWMgc2ltaWxhcml0eQ0KDQotLS0NCg0KKipQYXJ0IDM6IEJ1aWxkaW5nIFJOTiBmb3IgTkxQKioNCg0KVGV4dCBpcyBjb252ZXJ0ZWQgdG8gdW5pcXVlIGludGVnZXJzIHZpYSB0b2tlbml6YXRpb24uIEV4YW1wbGU6ICANCiJUaGUgbW92aWUgd2FzIHRoZSBiZXN0IEkgaGF2ZSBzZWVuIiDihpIgWzIsIDcsIDE1LCAzLCA5LCAxMSwgMTldDQoNCkVhY2ggd29yZCBpcyBtYXBwZWQgdG8gYW4gaW50ZWdlci4gQ29tbW9uIHdvcmRzIGFyZSBhc3NpZ25lZCBsb3dlciBudW1iZXJzLiBVbmtub3duIHdvcmRzIGFyZSBoYW5kbGVkIHdpdGggYSBzcGVjaWFsIHRva2VuIChlLmcuLCAyKS4gUGFkZGluZyBpcyBhZGRlZCB0byBtYWtlIGFsbCBzZXF1ZW5jZXMgdGhlIHNhbWUgbGVuZ3RoIChlLmcuLCA1MDApLg0KDQpUaGUgbmV0d29yayBzdHJ1Y3R1cmU6DQoNCi0gRW1iZWRkaW5nIGxheWVyIChtdXN0IGJlIHRoZSBmaXJzdCk6IGxlYXJucyBkZW5zZSByZXByZXNlbnRhdGlvbnMgIA0KLSBTaW1wbGUgUk5OIGxheWVyOiBwcm9jZXNzZXMgc2VxdWVuY2UgIA0KLSBEZW5zZSBsYXllciB3aXRoIHNpZ21vaWQ6IG91dHB1dHMgc2VudGltZW50ICgwIG9yIDEpDQoNCkVtYmVkZGluZyB1c2VzIHJvdyBsb29rdXAgcmF0aGVyIHRoYW4gbWF0cml4IG11bHRpcGxpY2F0aW9uLiBGb3IgZWFjaCBpbnB1dCB3b3JkIGludGVnZXIsIGl0IHJldHJpZXZlcyB0aGUgY29ycmVzcG9uZGluZyB2ZWN0b3IgZnJvbSB0aGUgZW1iZWRkaW5nIG1hdHJpeC4gT3V0cHV0IG9mIGVhY2ggc3RlcCBpcyBwYXNzZWQgdG8gdGhlIFJOTiwgd2hpY2ggb3V0cHV0cyB0byB0aGUgZmluYWwgZGVuc2UgbGF5ZXIuDQoNClJOTnMgaGF2ZSBzaG9ydCBtZW1vcnkgKH41IHN0ZXBzKS4gTFNUTSBhbmQgR1JVIGV4dGVuZCBtZW1vcnkgY2FwYWJpbGl0eSBidXQgdXNlIG1vcmUgcGFyYW1ldGVycy4gUk5OcyBjYW4gcGVyZm9ybSB3ZWxsIGV2ZW4gd2l0aCBiYXNpYyBzZXR1cHMuDQoNCi0tLQ0KDQoqKlBhcnQgNDogRGF0YSBQcmVwYXJhdGlvbioqDQoNClVzZSB0aGUgSU1EQiBkYXRhc2V0LiBMaW1pdCB2b2NhYnVsYXJ5IHNpemUgdG8gNTAwMCBmb3IgcGVyZm9ybWFuY2UuIERhdGEgaXMgYWxyZWFkeSB0b2tlbml6ZWQgKHdvcmRzIHRvIGludGVnZXJzKS4gRWFjaCByZXZpZXcgaXMgYSBsaXN0IG9mIGludGVnZXJzLg0KDQpVc2UgYGdldF93b3JkX2luZGV4KClgIHRvIG1hcCBpbnRlZ2VycyBiYWNrIHRvIHdvcmRzIGFuZCBidWlsZCBhIHJldmVyc2UgbG9va3VwLiBTcGVjaWFsIHRva2VucyBpbmNsdWRlOiAgDQotIDA6IFBBRCAgDQotIDE6IFNUQVJUICANCi0gMjogVU5LICANCi0gMzogVU5VU0VEDQoNCkNyZWF0ZSBhIGRlY29kZSBmdW5jdGlvbiB0byB0dXJuIHNlcXVlbmNlcyBpbnRvIHRleHQuDQoNCkNoZWNrIHJldmlldyBsZW5ndGhzOiAgDQotIE1heDogfjI1MDAgIA0KLSA5OSUgPCAxMDAwICANCi0gOTUlIDwgNjAwICANCi0gOTAlIDwgNDY3ICANCi0gQ3V0b2ZmID0gNTAwIGNhcHR1cmVzIH45MiUgb2YgZGF0YQ0KDQpVc2UgYHBhZF9zZXF1ZW5jZXMoKWAgdG8gdHJpbS9wYWQgZGF0YSB0byBsZW5ndGggNTAwLg0KDQpOb3cgYWxsIHJldmlld3MgYXJlIGV4YWN0bHkgNTAwIGludGVnZXJzIGxvbmcsIHJlYWR5IGZvciBpbnB1dCBpbnRvIHRoZSBtb2RlbC4NCg0KLS0tDQoNCioqUGFydCA1OiBCdWlsZGluZyBZb3VyIE5ldHdvcmsqKg0KDQpNb2RlbCBhcmNoaXRlY3R1cmU6DQoNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLm1vZGVscyBpbXBvcnQgU2VxdWVudGlhbA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgRW1iZWRkaW5nLCBTaW1wbGVSTk4sIERlbnNlDQoNCm1vZGVsID0gU2VxdWVudGlhbCgpDQptb2RlbC5hZGQoRW1iZWRkaW5nKGlucHV0X2RpbT01MDAwLCBvdXRwdXRfZGltPTEwMCwgaW5wdXRfbGVuZ3RoPTUwMCkpDQptb2RlbC5hZGQoU2ltcGxlUk5OKDI1NikpDQptb2RlbC5hZGQoRGVuc2UoMSwgYWN0aXZhdGlvbj0nc2lnbW9pZCcpKQ0KYGBgDQoNCkNvbXBpbGUgYW5kIHRyYWluOg0KDQpgYGBweXRob24NCm1vZGVsLmNvbXBpbGUobG9zcz0nYmluYXJ5X2Nyb3NzZW50cm9weScsIG9wdGltaXplcj0nYWRhbScsIG1ldHJpY3M9WydhY2N1cmFjeSddKQ0KbW9kZWwuZml0KHhfdHJhaW4sIHlfdHJhaW4sIHZhbGlkYXRpb25fZGF0YT0oeF90ZXN0LCB5X3Rlc3QpLCBlcG9jaHM9MywgYmF0Y2hfc2l6ZT02NCkNCmBgYA0KDQpFeHBsYW5hdGlvbjoNCg0KLSBFbWJlZGRpbmcgb3V0cHV0czogKGJhdGNoX3NpemUsIDUwMCwgMTAwKSAgDQotIFNpbXBsZVJOTiBvdXRwdXRzOiAoYmF0Y2hfc2l6ZSwgMjU2KSAgDQotIERlbnNlIGxheWVyOiBjb21wcmVzc2VzIDI1NiB0byAxIG91dHB1dCAgDQotIFRvdGFsIHBhcmFtZXRlcnMgPSB2b2NhYl9zaXplIMOXIGVtYmVkZGluZ19kaW0gKyBSTk4gd2VpZ2h0cyArIGJpYXMNCg0KVHJhaW5pbmcgdGFrZXMgdGltZSBkdWUgdG8gNTAwIHN0ZXBzIHBlciBzYW1wbGUuIEFmdGVyIDMgZXBvY2hzOiAgDQotIEFjY3VyYWN5ID0gfjc14oCTNzglICANCi0gVmFsaWRhdGlvbiA9IH43Ni41JSAgDQotIERlY2VudCBmb3IgYSBzaW1wbGUgMy1sYXllciBSTk4gbW9kZWwNCg0KLS0tDQoNCioqUGFydCA2OiBBZHZhbmNlZCBSTk4gTGF5ZXJzKioNCg0KWW91IGNhbiByZXBsYWNlIGBTaW1wbGVSTk5gIHdpdGggYExTVE1gIG9yIGBHUlVgLg0KDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5sYXllcnMgaW1wb3J0IExTVE0sIEdSVQ0KDQptb2RlbC5hZGQoTFNUTSgyNTYpKSAgIyBvciBtb2RlbC5hZGQoR1JVKDI1NikpDQpgYGANCg0KLSBMU1RNIGhhcyA0IGdhdGVzOiBpbnB1dCwgZm9yZ2V0LCBvdXRwdXQsIGFuZCBjZWxsIHN0YXRlICANCi0gR1JVIGhhcyAzIGdhdGVzOiB1cGRhdGUsIHJlc2V0LCBuZXcgIA0KLSBMU1RNOiA0w5cgcGFyYW1ldGVyIGNvdW50ICANCi0gR1JVOiAzw5cgcGFyYW1ldGVyIGNvdW50ICANCi0gRW1iZWRkaW5nIGxheWVyIHVuY2hhbmdlZCAgDQotIERlbnNlIGxheWVyIHVuY2hhbmdlZA0KDQpTcGVlZCBhbmQgbWVtb3J5IHRyYWRlb2ZmczoNCg0KfCBMYXllciB8IE1lbW9yeSB8IFBhcmFtcyB8IE5vdGVzIHwNCnwtLS0tLS0tfC0tLS0tLS0tfC0tLS0tLS0tfC0tLS0tLS18DQp8IFJOTiAgIHwgTG93ICAgIHwgMcOXICAgICB8IEZhc3QsIHNob3J0IG1lbW9yeSB8DQp8IEdSVSAgIHwgTWVkaXVtIHwgM8OXICAgICB8IEVmZmljaWVudCB8DQp8IExTVE0gIHwgSGlnaCAgIHwgNMOXICAgICB8IEJlc3QgZm9yIGxvbmcgZGVwZW5kZW5jaWVzIHwNCg0KQWxsIHRocmVlIGFyZSB2YWxpZC4gQ2hvaWNlIGRlcGVuZHMgb24gcmVzb3VyY2VzIGFuZCBwcm9ibGVtLg0KDQotLS0NCg0KDQoNCi0tLQ0KDQoqKktleSBUYWtlYXdheXMg4oCUIFFUVyA3MzMzIE1vZHVsZSAxMzogUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyoqDQoNCjEuICoqUk5OcyBoYW5kbGUgc2VxdWVudGlhbCBkYXRhKiogIA0KICAgLSBNZW1vcnkgaXMgYnVpbHQgYnkgZmVlZGluZyBwcmV2aW91cyBvdXRwdXRzIGFzIGlucHV0cyAgDQogICAtIFVzZWQgZm9yIHRhc2tzIGxpa2UgbGFuZ3VhZ2UsIHRpbWUgc2VyaWVzLCBwcmVkaWN0aW9uDQoNCjIuICoqSW5wdXRzIG11c3QgYmUgdW5pZm9ybSBsZW5ndGgqKiAgDQogICAtIE5MUCBpbnB1dCBpcyBwYWRkZWQvdHJ1bmNhdGVkIHRvIGZpeGVkIHNpemUgKGNvbW1vbmx5IDUwMCkgIA0KICAgLSBQYWRkaW5nIHVzZXMgMHM7IHRydW5jYXRpbmcgZHJvcHMgZnJvbSBzdGFydCBvciBlbmQNCg0KMy4gKipXb3JkIHJlcHJlc2VudGF0aW9uIG1hdHRlcnMqKiAgDQogICAtIE9uZS1ob3QgPSBpbmVmZmljaWVudCAgDQogICAtIEVtYmVkZGluZ3MgPSBkZW5zZSwgbGVhcm5lZCByZXByZXNlbnRhdGlvbnMgIA0KICAgLSBDYW4gYmUgdHJhaW5lZCBvciBwcmUtbG9hZGVkIChlLmcuLCBHbG9WZSkNCg0KNC4gKipCYXNpYyBSTk5zIGhhdmUgc2hvcnQgbWVtb3J5ICh+NSBzdGVwcykqKiAgDQogICAtIFZhbmlzaGluZyBncmFkaWVudCBsaW1pdHMgbG9uZy10ZXJtIGRlcGVuZGVuY2llcyAgDQogICAtIExTVE0gYW5kIEdSVSBhcmUgZHJvcC1pbiByZXBsYWNlbWVudHMgd2l0aCBnYXRpbmcgbWVtb3J5DQoNCjUuICoqU2ltcGxlIFJOTiBhcmNoaXRlY3R1cmUgaXMgdGhyZWUgbGF5ZXJzKiogIA0KICAgLSBFbWJlZGRpbmcg4oaSIFJOTiDihpIgRGVuc2Uoc2lnbW9pZCkgIA0KICAgLSBXb3JrcyB3ZWxsIGZvciBzZW50aW1lbnQgY2xhc3NpZmljYXRpb24gKH43NiUgYWNjdXJhY3kgb24gSU1EQikNCg0KNi4gKipMU1RNID0gNMOXIHBhcmFtZXRlcnMgb2YgUk5OKiogIA0KICAgKipHUlUgPSAzw5cgcGFyYW1ldGVycyBvZiBSTk4qKiAgDQogICAtIFRyYWRlLW9mZjogR1JVIGlzIGZhc3RlciwgTFNUTSByZW1lbWJlcnMgbW9yZSAgDQogICAtIEJvdGggYXJlIGJldHRlciB0aGFuIGJhc2ljIFJOTiBmb3IgbG9uZyBzZXF1ZW5jZXMNCg0KNy4gKipEYXRhIHBpcGVsaW5lIG1hdHRlcnMqKiAgDQogICAtIElNREIgaXMgcHJlLXRva2VuaXplZCAgDQogICAtIFdvcmQgaW5kZXggYW5kIHJldmVyc2UgaW5kZXggYXJlIHVzZWQgZm9yIGVuY29kaW5nL2RlY29kaW5nICANCiAgIC0gUHJvcGVyIHByZXByb2Nlc3NpbmcgaXMgcmVxdWlyZWQgZm9yIG1vZGVsIGlucHV0DQoNCjguICoqQ29zaW5lIHNpbWlsYXJpdHkgbWVhc3VyZXMgc2VtYW50aWMgc2ltaWxhcml0eSBpbiB2ZWN0b3Igc3BhY2UqKiAgDQogICAtIFVzZWQgaW4gTkxQIHRhc2tzIGZvciBjb21wYXJpbmcgd29yZCB2ZWN0b3JzICANCiAgIC0gT25seSBkaXJlY3Rpb24gb2YgdmVjdG9yIG1hdHRlcnMsIG5vdCBtYWduaXR1ZGUNCg0KOS4gKipUcmFpbmluZyB0aW1lIGluY3JlYXNlcyB3aXRoIHNlcXVlbmNlIGxlbmd0aCoqICANCiAgIC0gNTAwLXRpbWVzdGVwIHNlcXVlbmNlcyB0YWtlIGxvbmdlciB0byBydW4gIA0KICAgLSBSTk5zIGluaGVyZW50bHkgcnVuIHNlcXVlbnRpYWxseSwgbGltaXRpbmcgcGFyYWxsZWxpemF0aW9uDQoNCjEwLiAqKk91dHB1dCBsYXllciBpcyBhIGJpbmFyeSBjbGFzc2lmaWVyIHVzaW5nIHNpZ21vaWQqKiAgDQogICAgLSBGb3Igc2VudGltZW50OiAwID0gbmVnYXRpdmUsIDEgPSBwb3NpdGl2ZSAgDQogICAgLSBMb3NzID0gYmluYXJ5IGNyb3NzZW50cm9weQ0KDQotLS0NCg0KR290IGl0Lg0KDQotLS0NCg0KKipNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24g4oCTIFJOTiwgTFNUTSwgR1JVKioNCg0KLS0tDQoNCioqMS4gQmFzaWMgUk5OKioNCg0KQXQgZWFjaCB0aW1lc3RlcCBcKCB0IFwpOg0KDQotIEhpZGRlbiBzdGF0ZTogIA0KICBcKCBoX3QgPSBcdGFuaChXX3t4aH0geF90ICsgV197aGh9IGhfe3QtMX0gKyBiX2gpIFwpDQoNCi0gT3V0cHV0OiAgDQogIFwoIHlfdCA9IFdfe2h5fSBoX3QgKyBiX3kgXCkNCg0KLS0tDQoNCioqMi4gTFNUTSAoTG9uZyBTaG9ydC1UZXJtIE1lbW9yeSkqKg0KDQpBdCBlYWNoIHRpbWVzdGVwIFwoIHQgXCk6DQoNCi0gRm9yZ2V0IGdhdGU6ICANCiAgXCggZl90ID0gXHNpZ21hKFdfZiB4X3QgKyBVX2YgaF97dC0xfSArIGJfZikgXCkNCg0KLSBJbnB1dCBnYXRlOiAgDQogIFwoIGlfdCA9IFxzaWdtYShXX2kgeF90ICsgVV9pIGhfe3QtMX0gKyBiX2kpIFwpDQoNCi0gQ2FuZGlkYXRlIGNlbGwgc3RhdGU6ICANCiAgXCggXHRpbGRle2N9X3QgPSBcdGFuaChXX2MgeF90ICsgVV9jIGhfe3QtMX0gKyBiX2MpIFwpDQoNCi0gTmV3IGNlbGwgc3RhdGU6ICANCiAgXCggY190ID0gZl90IFxvZG90IGNfe3QtMX0gKyBpX3QgXG9kb3QgXHRpbGRle2N9X3QgXCkNCg0KLSBPdXRwdXQgZ2F0ZTogIA0KICBcKCBvX3QgPSBcc2lnbWEoV19vIHhfdCArIFVfbyBoX3t0LTF9ICsgYl9vKSBcKQ0KDQotIEhpZGRlbiBzdGF0ZTogIA0KICBcKCBoX3QgPSBvX3QgXG9kb3QgXHRhbmgoY190KSBcKQ0KDQotLS0NCg0KKiozLiBHUlUgKEdhdGVkIFJlY3VycmVudCBVbml0KSoqDQoNCkF0IGVhY2ggdGltZXN0ZXAgXCggdCBcKToNCg0KLSBVcGRhdGUgZ2F0ZTogIA0KICBcKCB6X3QgPSBcc2lnbWEoV196IHhfdCArIFVfeiBoX3t0LTF9ICsgYl96KSBcKQ0KDQotIFJlc2V0IGdhdGU6ICANCiAgXCggcl90ID0gXHNpZ21hKFdfciB4X3QgKyBVX3IgaF97dC0xfSArIGJfcikgXCkNCg0KLSBDYW5kaWRhdGU6ICANCiAgXCggXHRpbGRle2h9X3QgPSBcdGFuaChXX2ggeF90ICsgcl90IFxvZG90IFVfaCBoX3t0LTF9ICsgYl9oKSBcKQ0KDQotIEZpbmFsIGhpZGRlbiBzdGF0ZTogIA0KICBcKCBoX3QgPSAoMSAtIHpfdCkgXG9kb3QgaF97dC0xfSArIHpfdCBcb2RvdCBcdGlsZGV7aH1fdCBcKQ0KDQotLS0NCg0KKiozIFF1ZXN0aW9ucyBmb3IgRHIuIFNsYXRlcioqDQoNCjEuIEhvdyBkbyB3ZSBkZXRlcm1pbmUgd2hldGhlciB0byB1c2UgTFNUTSBvciBHUlUgZm9yIGEgc3BlY2lmaWMgTkxQIHRhc2sgaW4gdGVybXMgb2YgcGVyZm9ybWFuY2UgdmVyc3VzIHJlc291cmNlIGVmZmljaWVuY3k/DQoNCjIuIEluIHNlcXVlbmNlIHByZWRpY3Rpb24sIHdoZW4gaXMgaXQgbW9yZSBhcHByb3ByaWF0ZSB0byB1c2UgdGhlIGZpbmFsIHRpbWVzdGVwIG91dHB1dCB2ZXJzdXMgdGhlIGZ1bGwgc2VxdWVuY2Ugb2Ygb3V0cHV0cz8NCg0KMy4gSG93IGRvIHByZXRyYWluZWQgZW1iZWRkaW5ncyAobGlrZSBHbG9WZSBvciBXb3JkMlZlYykgYWZmZWN0IFJOTiBwZXJmb3JtYW5jZSBjb21wYXJlZCB0byB0cmFpbmluZyBlbWJlZGRpbmdzIGZyb20gc2NyYXRjaD8NCg0KLS0tDQoNCg0K