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 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:

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))

Speed and memory tradeoffs:

Layer Memory Params Notes
RNN Low Fast, short memory
GRU Medium Efficient
LSTM High Best for long dependencies

All three are valid. Choice depends on resources and problem.



Key Takeaways — QTW 7333 Module 13: Recurrent Neural Networks

  1. RNNs handle sequential data
    • Memory is built by feeding previous outputs as inputs
    • Used for tasks like language, time series, prediction
  2. Inputs must be uniform length
    • NLP input is padded/truncated to fixed size (commonly 500)
    • Padding uses 0s; truncating drops from start or end
  3. Word representation matters
    • One-hot = inefficient
    • Embeddings = dense, learned representations
    • Can be trained or pre-loaded (e.g., GloVe)
  4. Basic RNNs have short memory (~5 steps)
    • Vanishing gradient limits long-term dependencies
    • LSTM and GRU are drop-in replacements with gating memory
  5. Simple RNN architecture is three layers
    • Embedding → RNN → Dense(sigmoid)
    • Works well for sentiment classification (~76% accuracy on IMDB)
  6. 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
  7. Data pipeline matters
    • IMDB is pre-tokenized
    • Word index and reverse index are used for encoding/decoding
    • Proper preprocessing is required for model input
  8. Cosine similarity measures semantic similarity in vector space
    • Used in NLP tasks for comparing word vectors
    • Only direction of vector matters, not magnitude
  9. Training time increases with sequence length
    • 500-timestep sequences take longer to run
    • RNNs inherently run sequentially, limiting parallelization
  10. 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\):


3. GRU (Gated Recurrent Unit)

At each timestep \(t\):


3 Questions for Dr. Slater

  1. How do we determine whether to use LSTM or GRU for a specific NLP task in terms of performance versus resource efficiency?

  2. In sequence prediction, when is it more appropriate to use the final timestep output versus the full sequence of outputs?

  3. How do pretrained embeddings (like GloVe or Word2Vec) affect RNN performance compared to training embeddings from scratch?


LS0tDQp0aXRsZTogIjczMzMgTW9kdWxlIDEzIC0gUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyINCmF1dGhvcjogIkplc3NpY2EgTWNQaGF1bCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KKipRVFcgLSA3MzMzKiogIA0KKipNb2R1bGUgMTM6IFJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MqKg0KDQotLS0NCg0KKipQYXJ0IDE6IEludHJvZHVjdGlvbiBSZWN1cnJlbnQgTmV1cmFsIE5ldHdvcmtzKioNCg0KUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyBhcmUgZGVzaWduZWQgZm9yIHNlcXVlbmNlLWRlcGVuZGVudCBkYXRhIGxpa2UgbGFuZ3VhZ2UgYW5kIHRpbWUgc2VyaWVzLiBVbmxpa2UgZGVuc2UgYW5kIGNvbnZvbHV0aW9uYWwgbmV1cmFsIG5ldHdvcmtzIHRoYXQgcHJvY2VzcyBkYXRhIGFsbCBhdCBvbmNlLCBSTk5zIHByb2Nlc3MgaW5wdXRzIG9uZSBzdGVwIGF0IGEgdGltZSB3aGlsZSBtYWludGFpbmluZyBhIG1lbW9yeSBvZiBwcmV2aW91cyBvdXRwdXRzLg0KDQpBdCBlYWNoIHRpbWVzdGVwLCB0aGUgbmV0d29yayByZWNlaXZlcyBjdXJyZW50IGlucHV0IGFuZCB0aGUgb3V0cHV0IG9mIHRoZSBwcmV2aW91cyBzdGVwLiBUaGlzIGlzIHdoeSBSTk5zIGFyZSBjYWxsZWQgInJlY3VycmVudCLigJR0aGV5IGZlZWQgb3V0cHV0cyBiYWNrIGludG8gdGhlbXNlbHZlcy4NCg0KQXQgdGltZXN0ZXAgMCwgdGhlcmUncyBubyBwcmV2aW91cyBpbnB1dCwgc28gYSBtYXRyaXggb2YgemVyb3MgaXMgdXNlZC4gUk5OcyBjYW4gYmUgdmlzdWFsaXplZCBpbiB0d28gd2F5czogIA0KLSBVbnJvbGxlZCB2aWV3OiBsYXllcnMgc2hvd24gc2VxdWVudGlhbGx5IG92ZXIgdGltZSAgDQotIFJvbGxlZCB2aWV3OiB0aGUgc2FtZSBsYXllciByZXVzZWQgcmVwZWF0ZWRseSB3aXRoIHNoYXJlZCB3ZWlnaHRzDQoNCkVhY2ggc3RlcOKAmXMgb3V0cHV0IGlzIHVzZWQgZm9yIHByZWRpY3Rpb24gYW5kIGlzIGFsc28gcGFzc2VkIGZvcndhcmQgYXMgaW5wdXQgZm9yIHRoZSBuZXh0IHRpbWUgc3RlcC4NCg0KVGhlIHN0cnVjdHVyZSBpbnZvbHZlcyB0d28gc2V0cyBvZiBpbnB1dHM6ICANCi0gVGhlIGFjdHVhbCBkYXRhIChlLmcuLCBzdG9jayBwcmljZXMpICANCi0gVGhlIG91dHB1dCBvZiB0aGUgcHJldmlvdXMgdGltZXN0ZXANCg0KVGhlc2UgYXJlIHRyZWF0ZWQgYXMgb25lIGNvbmNhdGVuYXRlZCBpbnB1dCBtYXRyaXgsIHdoaWNoIHNpbXBsaWZpZXMgaW1wbGVtZW50YXRpb24uIFRoZSBzZXF1ZW5jZSBsZW5ndGggbXVzdCBiZSBjb25zaXN0ZW50IGZvciBhbGwgaW5wdXQgZGF0YSwgd2hpY2ggcHJlc2VudHMgYSBjaGFsbGVuZ2UgaW4gTkxQIHdoZXJlIHNlbnRlbmNlIGxlbmd0aHMgdmFyeS4gVGhpcyBpcyBzb2x2ZWQgd2l0aCBwYWRkaW5nIG9yIHRydW5jYXRpbmcuDQoNCkVhY2ggUk5OIGxheWVyIGhhcyB0d28gc2V0cyBvZiB3ZWlnaHRz4oCUb25lIGZvciB0aGUgbm9ybWFsIGlucHV0IGFuZCBvbmUgZm9yIHRoZSByZWN1cnJlbnQgaW5wdXQuIFRoZXNlIGFyZSB0cmVhdGVkIHNlcGFyYXRlbHkgaW4gbGlicmFyaWVzIGxpa2UgS2VyYXMsIHNvIHR3byBzZXRzIG9mIHdlaWdodHMgYXJlIG1haW50YWluZWQgYW5kIHRyYWluZWQuDQoNCi0tLQ0KDQoqKlBhcnQgMjogQSBCcmllZiBOTFAgSW50cm9kdWN0aW9uKioNCg0KTkxQIHRhc2tzIHJlcXVpcmUgY29udmVydGluZyBsYW5ndWFnZSBpbnRvIGEgbnVtZXJpYyBmb3JtYXQgUk5OcyBjYW4gd29yayB3aXRoLiBPbmUtaG90IGVuY29kaW5nIGlzIGluZWZmaWNpZW50IGZvciBsYXJnZSB2b2NhYnVsYXJpZXMgYW5kIHJlc3VsdHMgaW4gc3BhcnNlIHJlcHJlc2VudGF0aW9ucywgc28gaW5zdGVhZCwgd2UgdXNlIHdvcmQgZW1iZWRkaW5nc+KAlGRlbnNlIHZlY3RvcnMgdGhhdCBjYXB0dXJlIHdvcmQgbWVhbmluZy4NCg0KU2VudGVuY2VzIG11c3QgYmUgY29udmVydGVkIHRvIHRoZSBzYW1lIGxlbmd0aCB1c2luZyBwYWRkaW5nIChhZGRpbmcgYSAicGFkIiB3b3JkIHRva2VuIGxpa2UgMCkgb3IgdHJ1bmNhdGlvbi4gVGhlIHBhZCB3b3JkIGRvZXMgbm90IGNhcnJ5IG1lYW5pbmcgYW5kIGlzIGp1c3QgYSBmaWxsZXIuIFBhZGRpbmcgaXMgYXBwbGllZCB0byBtYWtlIHRoZSBpbnB1dCBzaGFwZSBjb25zaXN0ZW50Lg0KDQpUaGUgcG9zaXRpb24gb2YgYSB3b3JkIGluIGEgc2VudGVuY2UgbWF0dGVycy4gUk5OcyBwcm9jZXNzIHRoZSBzZW50ZW5jZSBpbiBvcmRlciwgbWFraW5nIHRoZW0gc3VpdGFibGUgZm9yIGxhbmd1YWdlIHRhc2tzIGxpa2UgY29tcGxldGluZyBzZW50ZW5jZXMgb3Igc2VudGltZW50IGFuYWx5c2lzLg0KDQpXb3JkIHZlY3RvcnMgKGVtYmVkZGluZ3MpIGNhbiBiZSBsZWFybmVkIGJ5IHRoZSBtb2RlbCBvciBwcmUtdHJhaW5lZC4gVGhleSByZXByZXNlbnQgd29yZHMgaW4gZGVuc2UgbnVtZXJpY2FsIGZvcm0gYW5kIGxpdmUgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZS4gV29yZHMgd2l0aCBzaW1pbGFyIG1lYW5pbmdzIHBvaW50IGluIHNpbWlsYXIgZGlyZWN0aW9ucyBpbiB0aGlzIHNwYWNlLg0KDQpDb3NpbmUgc2ltaWxhcml0eSBpcyB1c2VkIHRvIGNvbXBhcmUgdGhlc2UgdmVjdG9yczogIA0KLSBDb3NpbmUgc2ltaWxhcml0eSA9IGRvdCBwcm9kdWN0IG9mIHR3byB2ZWN0b3JzIGRpdmlkZWQgYnkgcHJvZHVjdCBvZiB0aGVpciBtYWduaXR1ZGVzICANCi0gQ29zaW5lIGRpc3RhbmNlID0gMSAtIGNvc2luZSBzaW1pbGFyaXR5ICANCi0gQ2xvc2UgZGlyZWN0aW9uID0gc2VtYW50aWMgc2ltaWxhcml0eQ0KDQotLS0NCg0KKipQYXJ0IDM6IEJ1aWxkaW5nIFJOTiBmb3IgTkxQKioNCg0KVGV4dCBpcyBjb252ZXJ0ZWQgdG8gdW5pcXVlIGludGVnZXJzIHZpYSB0b2tlbml6YXRpb24uIEV4YW1wbGU6ICANCiJUaGUgbW92aWUgd2FzIHRoZSBiZXN0IEkgaGF2ZSBzZWVuIiDihpIgWzIsIDcsIDE1LCAzLCA5LCAxMSwgMTldDQoNCkVhY2ggd29yZCBpcyBtYXBwZWQgdG8gYW4gaW50ZWdlci4gQ29tbW9uIHdvcmRzIGFyZSBhc3NpZ25lZCBsb3dlciBudW1iZXJzLiBVbmtub3duIHdvcmRzIGFyZSBoYW5kbGVkIHdpdGggYSBzcGVjaWFsIHRva2VuIChlLmcuLCAyKS4gUGFkZGluZyBpcyBhZGRlZCB0byBtYWtlIGFsbCBzZXF1ZW5jZXMgdGhlIHNhbWUgbGVuZ3RoIChlLmcuLCA1MDApLg0KDQpUaGUgbmV0d29yayBzdHJ1Y3R1cmU6DQoNCi0gRW1iZWRkaW5nIGxheWVyIChtdXN0IGJlIHRoZSBmaXJzdCk6IGxlYXJucyBkZW5zZSByZXByZXNlbnRhdGlvbnMgIA0KLSBTaW1wbGUgUk5OIGxheWVyOiBwcm9jZXNzZXMgc2VxdWVuY2UgIA0KLSBEZW5zZSBsYXllciB3aXRoIHNpZ21vaWQ6IG91dHB1dHMgc2VudGltZW50ICgwIG9yIDEpDQoNCkVtYmVkZGluZyB1c2VzIHJvdyBsb29rdXAgcmF0aGVyIHRoYW4gbWF0cml4IG11bHRpcGxpY2F0aW9uLiBGb3IgZWFjaCBpbnB1dCB3b3JkIGludGVnZXIsIGl0IHJldHJpZXZlcyB0aGUgY29ycmVzcG9uZGluZyB2ZWN0b3IgZnJvbSB0aGUgZW1iZWRkaW5nIG1hdHJpeC4gT3V0cHV0IG9mIGVhY2ggc3RlcCBpcyBwYXNzZWQgdG8gdGhlIFJOTiwgd2hpY2ggb3V0cHV0cyB0byB0aGUgZmluYWwgZGVuc2UgbGF5ZXIuDQoNClJOTnMgaGF2ZSBzaG9ydCBtZW1vcnkgKH41IHN0ZXBzKS4gTFNUTSBhbmQgR1JVIGV4dGVuZCBtZW1vcnkgY2FwYWJpbGl0eSBidXQgdXNlIG1vcmUgcGFyYW1ldGVycy4gUk5OcyBjYW4gcGVyZm9ybSB3ZWxsIGV2ZW4gd2l0aCBiYXNpYyBzZXR1cHMuDQoNCi0tLQ0KDQoqKlBhcnQgNDogRGF0YSBQcmVwYXJhdGlvbioqDQoNClVzZSB0aGUgSU1EQiBkYXRhc2V0LiBMaW1pdCB2b2NhYnVsYXJ5IHNpemUgdG8gNTAwMCBmb3IgcGVyZm9ybWFuY2UuIERhdGEgaXMgYWxyZWFkeSB0b2tlbml6ZWQgKHdvcmRzIHRvIGludGVnZXJzKS4gRWFjaCByZXZpZXcgaXMgYSBsaXN0IG9mIGludGVnZXJzLg0KDQpVc2UgYGdldF93b3JkX2luZGV4KClgIHRvIG1hcCBpbnRlZ2VycyBiYWNrIHRvIHdvcmRzIGFuZCBidWlsZCBhIHJldmVyc2UgbG9va3VwLiBTcGVjaWFsIHRva2VucyBpbmNsdWRlOiAgDQotIDA6IFBBRCAgDQotIDE6IFNUQVJUICANCi0gMjogVU5LICANCi0gMzogVU5VU0VEDQoNCkNyZWF0ZSBhIGRlY29kZSBmdW5jdGlvbiB0byB0dXJuIHNlcXVlbmNlcyBpbnRvIHRleHQuDQoNCkNoZWNrIHJldmlldyBsZW5ndGhzOiAgDQotIE1heDogfjI1MDAgIA0KLSA5OSUgPCAxMDAwICANCi0gOTUlIDwgNjAwICANCi0gOTAlIDwgNDY3ICANCi0gQ3V0b2ZmID0gNTAwIGNhcHR1cmVzIH45MiUgb2YgZGF0YQ0KDQpVc2UgYHBhZF9zZXF1ZW5jZXMoKWAgdG8gdHJpbS9wYWQgZGF0YSB0byBsZW5ndGggNTAwLg0KDQpOb3cgYWxsIHJldmlld3MgYXJlIGV4YWN0bHkgNTAwIGludGVnZXJzIGxvbmcsIHJlYWR5IGZvciBpbnB1dCBpbnRvIHRoZSBtb2RlbC4NCg0KLS0tDQoNCioqUGFydCA1OiBCdWlsZGluZyBZb3VyIE5ldHdvcmsqKg0KDQpNb2RlbCBhcmNoaXRlY3R1cmU6DQoNCmBgYHB5dGhvbg0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLm1vZGVscyBpbXBvcnQgU2VxdWVudGlhbA0KZnJvbSB0ZW5zb3JmbG93LmtlcmFzLmxheWVycyBpbXBvcnQgRW1iZWRkaW5nLCBTaW1wbGVSTk4sIERlbnNlDQoNCm1vZGVsID0gU2VxdWVudGlhbCgpDQptb2RlbC5hZGQoRW1iZWRkaW5nKGlucHV0X2RpbT01MDAwLCBvdXRwdXRfZGltPTEwMCwgaW5wdXRfbGVuZ3RoPTUwMCkpDQptb2RlbC5hZGQoU2ltcGxlUk5OKDI1NikpDQptb2RlbC5hZGQoRGVuc2UoMSwgYWN0aXZhdGlvbj0nc2lnbW9pZCcpKQ0KYGBgDQoNCkNvbXBpbGUgYW5kIHRyYWluOg0KDQpgYGBweXRob24NCm1vZGVsLmNvbXBpbGUobG9zcz0nYmluYXJ5X2Nyb3NzZW50cm9weScsIG9wdGltaXplcj0nYWRhbScsIG1ldHJpY3M9WydhY2N1cmFjeSddKQ0KbW9kZWwuZml0KHhfdHJhaW4sIHlfdHJhaW4sIHZhbGlkYXRpb25fZGF0YT0oeF90ZXN0LCB5X3Rlc3QpLCBlcG9jaHM9MywgYmF0Y2hfc2l6ZT02NCkNCmBgYA0KDQpFeHBsYW5hdGlvbjoNCg0KLSBFbWJlZGRpbmcgb3V0cHV0czogKGJhdGNoX3NpemUsIDUwMCwgMTAwKSAgDQotIFNpbXBsZVJOTiBvdXRwdXRzOiAoYmF0Y2hfc2l6ZSwgMjU2KSAgDQotIERlbnNlIGxheWVyOiBjb21wcmVzc2VzIDI1NiB0byAxIG91dHB1dCAgDQotIFRvdGFsIHBhcmFtZXRlcnMgPSB2b2NhYl9zaXplIMOXIGVtYmVkZGluZ19kaW0gKyBSTk4gd2VpZ2h0cyArIGJpYXMNCg0KVHJhaW5pbmcgdGFrZXMgdGltZSBkdWUgdG8gNTAwIHN0ZXBzIHBlciBzYW1wbGUuIEFmdGVyIDMgZXBvY2hzOiAgDQotIEFjY3VyYWN5ID0gfjc14oCTNzglICANCi0gVmFsaWRhdGlvbiA9IH43Ni41JSAgDQotIERlY2VudCBmb3IgYSBzaW1wbGUgMy1sYXllciBSTk4gbW9kZWwNCg0KLS0tDQoNCioqUGFydCA2OiBBZHZhbmNlZCBSTk4gTGF5ZXJzKioNCg0KWW91IGNhbiByZXBsYWNlIGBTaW1wbGVSTk5gIHdpdGggYExTVE1gIG9yIGBHUlVgLg0KDQpgYGBweXRob24NCmZyb20gdGVuc29yZmxvdy5rZXJhcy5sYXllcnMgaW1wb3J0IExTVE0sIEdSVQ0KDQptb2RlbC5hZGQoTFNUTSgyNTYpKSAgIyBvciBtb2RlbC5hZGQoR1JVKDI1NikpDQpgYGANCg0KLSBMU1RNIGhhcyA0IGdhdGVzOiBpbnB1dCwgZm9yZ2V0LCBvdXRwdXQsIGFuZCBjZWxsIHN0YXRlICANCi0gR1JVIGhhcyAzIGdhdGVzOiB1cGRhdGUsIHJlc2V0LCBuZXcgIA0KLSBMU1RNOiA0w5cgcGFyYW1ldGVyIGNvdW50ICANCi0gR1JVOiAzw5cgcGFyYW1ldGVyIGNvdW50ICANCi0gRW1iZWRkaW5nIGxheWVyIHVuY2hhbmdlZCAgDQotIERlbnNlIGxheWVyIHVuY2hhbmdlZA0KDQpTcGVlZCBhbmQgbWVtb3J5IHRyYWRlb2ZmczoNCg0KfCBMYXllciB8IE1lbW9yeSB8IFBhcmFtcyB8IE5vdGVzIHwNCnwtLS0tLS0tfC0tLS0tLS0tfC0tLS0tLS0tfC0tLS0tLS18DQp8IFJOTiAgIHwgTG93ICAgIHwgMcOXICAgICB8IEZhc3QsIHNob3J0IG1lbW9yeSB8DQp8IEdSVSAgIHwgTWVkaXVtIHwgM8OXICAgICB8IEVmZmljaWVudCB8DQp8IExTVE0gIHwgSGlnaCAgIHwgNMOXICAgICB8IEJlc3QgZm9yIGxvbmcgZGVwZW5kZW5jaWVzIHwNCg0KQWxsIHRocmVlIGFyZSB2YWxpZC4gQ2hvaWNlIGRlcGVuZHMgb24gcmVzb3VyY2VzIGFuZCBwcm9ibGVtLg0KDQotLS0NCg0KDQoNCi0tLQ0KDQoqKktleSBUYWtlYXdheXMg4oCUIFFUVyA3MzMzIE1vZHVsZSAxMzogUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcyoqDQoNCjEuICoqUk5OcyBoYW5kbGUgc2VxdWVudGlhbCBkYXRhKiogIA0KICAgLSBNZW1vcnkgaXMgYnVpbHQgYnkgZmVlZGluZyBwcmV2aW91cyBvdXRwdXRzIGFzIGlucHV0cyAgDQogICAtIFVzZWQgZm9yIHRhc2tzIGxpa2UgbGFuZ3VhZ2UsIHRpbWUgc2VyaWVzLCBwcmVkaWN0aW9uDQoNCjIuICoqSW5wdXRzIG11c3QgYmUgdW5pZm9ybSBsZW5ndGgqKiAgDQogICAtIE5MUCBpbnB1dCBpcyBwYWRkZWQvdHJ1bmNhdGVkIHRvIGZpeGVkIHNpemUgKGNvbW1vbmx5IDUwMCkgIA0KICAgLSBQYWRkaW5nIHVzZXMgMHM7IHRydW5jYXRpbmcgZHJvcHMgZnJvbSBzdGFydCBvciBlbmQNCg0KMy4gKipXb3JkIHJlcHJlc2VudGF0aW9uIG1hdHRlcnMqKiAgDQogICAtIE9uZS1ob3QgPSBpbmVmZmljaWVudCAgDQogICAtIEVtYmVkZGluZ3MgPSBkZW5zZSwgbGVhcm5lZCByZXByZXNlbnRhdGlvbnMgIA0KICAgLSBDYW4gYmUgdHJhaW5lZCBvciBwcmUtbG9hZGVkIChlLmcuLCBHbG9WZSkNCg0KNC4gKipCYXNpYyBSTk5zIGhhdmUgc2hvcnQgbWVtb3J5ICh+NSBzdGVwcykqKiAgDQogICAtIFZhbmlzaGluZyBncmFkaWVudCBsaW1pdHMgbG9uZy10ZXJtIGRlcGVuZGVuY2llcyAgDQogICAtIExTVE0gYW5kIEdSVSBhcmUgZHJvcC1pbiByZXBsYWNlbWVudHMgd2l0aCBnYXRpbmcgbWVtb3J5DQoNCjUuICoqU2ltcGxlIFJOTiBhcmNoaXRlY3R1cmUgaXMgdGhyZWUgbGF5ZXJzKiogIA0KICAgLSBFbWJlZGRpbmcg4oaSIFJOTiDihpIgRGVuc2Uoc2lnbW9pZCkgIA0KICAgLSBXb3JrcyB3ZWxsIGZvciBzZW50aW1lbnQgY2xhc3NpZmljYXRpb24gKH43NiUgYWNjdXJhY3kgb24gSU1EQikNCg0KNi4gKipMU1RNID0gNMOXIHBhcmFtZXRlcnMgb2YgUk5OKiogIA0KICAgKipHUlUgPSAzw5cgcGFyYW1ldGVycyBvZiBSTk4qKiAgDQogICAtIFRyYWRlLW9mZjogR1JVIGlzIGZhc3RlciwgTFNUTSByZW1lbWJlcnMgbW9yZSAgDQogICAtIEJvdGggYXJlIGJldHRlciB0aGFuIGJhc2ljIFJOTiBmb3IgbG9uZyBzZXF1ZW5jZXMNCg0KNy4gKipEYXRhIHBpcGVsaW5lIG1hdHRlcnMqKiAgDQogICAtIElNREIgaXMgcHJlLXRva2VuaXplZCAgDQogICAtIFdvcmQgaW5kZXggYW5kIHJldmVyc2UgaW5kZXggYXJlIHVzZWQgZm9yIGVuY29kaW5nL2RlY29kaW5nICANCiAgIC0gUHJvcGVyIHByZXByb2Nlc3NpbmcgaXMgcmVxdWlyZWQgZm9yIG1vZGVsIGlucHV0DQoNCjguICoqQ29zaW5lIHNpbWlsYXJpdHkgbWVhc3VyZXMgc2VtYW50aWMgc2ltaWxhcml0eSBpbiB2ZWN0b3Igc3BhY2UqKiAgDQogICAtIFVzZWQgaW4gTkxQIHRhc2tzIGZvciBjb21wYXJpbmcgd29yZCB2ZWN0b3JzICANCiAgIC0gT25seSBkaXJlY3Rpb24gb2YgdmVjdG9yIG1hdHRlcnMsIG5vdCBtYWduaXR1ZGUNCg0KOS4gKipUcmFpbmluZyB0aW1lIGluY3JlYXNlcyB3aXRoIHNlcXVlbmNlIGxlbmd0aCoqICANCiAgIC0gNTAwLXRpbWVzdGVwIHNlcXVlbmNlcyB0YWtlIGxvbmdlciB0byBydW4gIA0KICAgLSBSTk5zIGluaGVyZW50bHkgcnVuIHNlcXVlbnRpYWxseSwgbGltaXRpbmcgcGFyYWxsZWxpemF0aW9uDQoNCjEwLiAqKk91dHB1dCBsYXllciBpcyBhIGJpbmFyeSBjbGFzc2lmaWVyIHVzaW5nIHNpZ21vaWQqKiAgDQogICAgLSBGb3Igc2VudGltZW50OiAwID0gbmVnYXRpdmUsIDEgPSBwb3NpdGl2ZSAgDQogICAgLSBMb3NzID0gYmluYXJ5IGNyb3NzZW50cm9weQ0KDQotLS0NCg0KR290IGl0Lg0KDQotLS0NCg0KKipNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24g4oCTIFJOTiwgTFNUTSwgR1JVKioNCg0KLS0tDQoNCioqMS4gQmFzaWMgUk5OKioNCg0KQXQgZWFjaCB0aW1lc3RlcCBcKCB0IFwpOg0KDQotIEhpZGRlbiBzdGF0ZTogIA0KICBcKCBoX3QgPSBcdGFuaChXX3t4aH0geF90ICsgV197aGh9IGhfe3QtMX0gKyBiX2gpIFwpDQoNCi0gT3V0cHV0OiAgDQogIFwoIHlfdCA9IFdfe2h5fSBoX3QgKyBiX3kgXCkNCg0KLS0tDQoNCioqMi4gTFNUTSAoTG9uZyBTaG9ydC1UZXJtIE1lbW9yeSkqKg0KDQpBdCBlYWNoIHRpbWVzdGVwIFwoIHQgXCk6DQoNCi0gRm9yZ2V0IGdhdGU6ICANCiAgXCggZl90ID0gXHNpZ21hKFdfZiB4X3QgKyBVX2YgaF97dC0xfSArIGJfZikgXCkNCg0KLSBJbnB1dCBnYXRlOiAgDQogIFwoIGlfdCA9IFxzaWdtYShXX2kgeF90ICsgVV9pIGhfe3QtMX0gKyBiX2kpIFwpDQoNCi0gQ2FuZGlkYXRlIGNlbGwgc3RhdGU6ICANCiAgXCggXHRpbGRle2N9X3QgPSBcdGFuaChXX2MgeF90ICsgVV9jIGhfe3QtMX0gKyBiX2MpIFwpDQoNCi0gTmV3IGNlbGwgc3RhdGU6ICANCiAgXCggY190ID0gZl90IFxvZG90IGNfe3QtMX0gKyBpX3QgXG9kb3QgXHRpbGRle2N9X3QgXCkNCg0KLSBPdXRwdXQgZ2F0ZTogIA0KICBcKCBvX3QgPSBcc2lnbWEoV19vIHhfdCArIFVfbyBoX3t0LTF9ICsgYl9vKSBcKQ0KDQotIEhpZGRlbiBzdGF0ZTogIA0KICBcKCBoX3QgPSBvX3QgXG9kb3QgXHRhbmgoY190KSBcKQ0KDQotLS0NCg0KKiozLiBHUlUgKEdhdGVkIFJlY3VycmVudCBVbml0KSoqDQoNCkF0IGVhY2ggdGltZXN0ZXAgXCggdCBcKToNCg0KLSBVcGRhdGUgZ2F0ZTogIA0KICBcKCB6X3QgPSBcc2lnbWEoV196IHhfdCArIFVfeiBoX3t0LTF9ICsgYl96KSBcKQ0KDQotIFJlc2V0IGdhdGU6ICANCiAgXCggcl90ID0gXHNpZ21hKFdfciB4X3QgKyBVX3IgaF97dC0xfSArIGJfcikgXCkNCg0KLSBDYW5kaWRhdGU6ICANCiAgXCggXHRpbGRle2h9X3QgPSBcdGFuaChXX2ggeF90ICsgcl90IFxvZG90IFVfaCBoX3t0LTF9ICsgYl9oKSBcKQ0KDQotIEZpbmFsIGhpZGRlbiBzdGF0ZTogIA0KICBcKCBoX3QgPSAoMSAtIHpfdCkgXG9kb3QgaF97dC0xfSArIHpfdCBcb2RvdCBcdGlsZGV7aH1fdCBcKQ0KDQotLS0NCg0KKiozIFF1ZXN0aW9ucyBmb3IgRHIuIFNsYXRlcioqDQoNCjEuIEhvdyBkbyB3ZSBkZXRlcm1pbmUgd2hldGhlciB0byB1c2UgTFNUTSBvciBHUlUgZm9yIGEgc3BlY2lmaWMgTkxQIHRhc2sgaW4gdGVybXMgb2YgcGVyZm9ybWFuY2UgdmVyc3VzIHJlc291cmNlIGVmZmljaWVuY3k/DQoNCjIuIEluIHNlcXVlbmNlIHByZWRpY3Rpb24sIHdoZW4gaXMgaXQgbW9yZSBhcHByb3ByaWF0ZSB0byB1c2UgdGhlIGZpbmFsIHRpbWVzdGVwIG91dHB1dCB2ZXJzdXMgdGhlIGZ1bGwgc2VxdWVuY2Ugb2Ygb3V0cHV0cz8NCg0KMy4gSG93IGRvIHByZXRyYWluZWQgZW1iZWRkaW5ncyAobGlrZSBHbG9WZSBvciBXb3JkMlZlYykgYWZmZWN0IFJOTiBwZXJmb3JtYW5jZSBjb21wYXJlZCB0byB0cmFpbmluZyBlbWJlZGRpbmdzIGZyb20gc2NyYXRjaD8NCg0KLS0tDQoNCg0K