Content ITV PRO
This is Itvedant Content department
Sequence Models : RNNs & their limitations
Business Scenario
Welcome!
Previously you cleaned, tokenized, and normalized customer queries/reviews, converted text, explored word vectors using an Embedding layer and performed an end-to-end text analysis on a new customer query
Today, your manager has assigned you a task to build a Simple RNN using the same preprocessed dataset from previous lab and check whether understanding the order of words in customer queries can improve prediction accuracy and reduce incorrect query routing.
Git Pull
git pull origin branchNamePre-Lab Preparation
Topic : Recurrent Neural Network
1) Understand RNN fundamentals and hidden-state mechanism.
2) Learn different types of RNN architectures.
3) Understand the sequential processing bottleneck.
4) Explore the vanishing gradient problem.
Recurrent Neural Network
RNN
Task 1: Reload Lab 3's Preprocessed Query Dataset
import re
import numpy as np, pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize
import tensorflow as tf
from tensorflow import keras
from keras.layers import TextVectorization, Embedding, SimpleRNN, Dense
from keras import Sequential
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
RANDOM_SEED = 42
DATA_PATH = "smartcart_customer_queries.csv"
MAX_TOKENS = 2000
EMBEDDING_DIM = 16
RNN_UNITS = 16
TEST_SIZE = 0.2
EPOCHS = 30
BATCH_SIZE = 32
Dataset :
Dataset :
import re
import numpy as np, pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize
import tensorflow as tf
from tensorflow import keras
from keras.layers import TextVectorization, Embedding, SimpleRNN, Dense
from keras import Sequential
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
RANDOM_SEED = 42
DATA_PATH = "smartcart_customer_queries.csv"
MAX_TOKENS = 2000
EMBEDDING_DIM = 16
RNN_UNITS = 16
TEST_SIZE = 0.2
EPOCHS = 30
BATCH_SIZE = 32
np.random.seed(RANDOM_SEED)
tf.random.set_seed(RANDOM_SEED)
nltk.download("punkt"); nltk.download("punkt_tab")
nltk.download("stopwords"); nltk.download("wordnet"); nltk.download("omw-1.4")
lemmatizer = WordNetLemmatizer()
stop_words = set(stopwords.words("english"))
def preprocess(text: str) -> str:
"""Clean, tokenize, drop stopwords, and lemmatize one query."""
text = str(text).lower()
text = re.sub(r'[^a-z0-9\s]', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
tokens = word_tokenize(text)
tokens = [t for t in tokens if t not in stop_words]
tokens = [lemmatizer.lemmatize(t) for t in tokens]
return ' '.join(tokens)
df = pd.read_csv(DATA_PATH)
df['processed_text'] = df['query_text'].apply(preprocess)
train_df, test_df = train_test_split(
df, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=df['category']
)
sequence_length = int(np.percentile(
train_df['processed_text'].str.split().apply(len), 95))
import re
import numpy as np, pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize
import tensorflow as tf
from tensorflow import keras
from keras.layers import TextVectorization, Embedding, SimpleRNN, Dense
from keras import Sequential
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
RANDOM_SEED = 42
DATA_PATH = "smartcart_customer_queries.csv"
MAX_TOKENS = 2000
EMBEDDING_DIM = 16
RNN_UNITS = 16
TEST_SIZE = 0.2
EPOCHS = 30
BATCH_SIZE = 32
np.random.seed(RANDOM_SEED)
tf.random.set_seed(RANDOM_SEED)
nltk.download("punkt"); nltk.download("punkt_tab")
nltk.download("stopwords"); nltk.download("wordnet"); nltk.download("omw-1.4")
lemmatizer = WordNetLemmatizer()
stop_words = set(stopwords.words("english"))
def preprocess(text: str) -> str:
"""Clean, tokenize, drop stopwords, and lemmatize one query."""
text = str(text).lower()
text = re.sub(r'[^a-z0-9\s]', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
tokens = word_tokenize(text)
tokens = [t for t in tokens if t not in stop_words]
tokens = [lemmatizer.lemmatize(t) for t in tokens]
return ' '.join(tokens)
df = pd.read_csv(DATA_PATH)
df['processed_text'] = df['query_text'].apply(preprocess)
train_df, test_df = train_test_split(
df, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=df['category']
)
sequence_length = int(np.percentile(
train_df['processed_text'].str.split().apply(len), 95))
int_vectorizer = TextVectorization(
output_mode='int',
max_tokens=MAX_TOKENS,
output_sequence_length=sequence_length
)
int_vectorizer.adapt(train_df['processed_text'].tolist()) # TRAIN text only
label_encoder = LabelEncoder().fit(df['category'])
class_names = label_encoder.classes_
X_train = int_vectorizer(train_df['processed_text'].tolist()).numpy()
y_train = label_encoder.transform(train_df['category'])
X_test = int_vectorizer(test_df['processed_text'].tolist()).numpy()
y_test = label_encoder.transform(test_df['category'])
vocab_size = int_vectorizer.vocabulary_size()
print(f"Train/test split: {len(train_df)} / {len(test_df)} queries")
print(f"Sequence length (training cutoff): {sequence_length}")
print(f"Vocabulary size (fit on train only): {vocab_size}")
Output
Task 2 : Build a Simple RNN
def build_rnn_model(vocab_size: int, num_classes: int) -> keras.Model:
"""Embedding -> SimpleRNN -> Dense classifier for query intent."""
model = Sequential([
Embedding(input_dim=vocab_size, output_dim=EMBEDDING_DIM),
SimpleRNN(RNN_UNITS),
Dense(num_classes, activation='softmax')])
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
rnn_model = build_rnn_model(vocab_size, num_classes=len(class_names))
rnn_model.summary()Embedding has no fixed input_length on purpose — Simple RNN shares its weights across every timestep, so it can accept sequences of any length at inference time
Task 3 : Train and Evaluate on Short Queries
early_stop = keras.callbacks.EarlyStopping(
monitor='val_accuracy', patience=5, restore_best_weights=True)
history = rnn_model.fit(
X_train, y_train,
validation_split=0.2,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
callbacks=[early_stop],
verbose=0)
test_loss, test_accuracy = rnn_model.evaluate(X_test, y_test, verbose=0)
print(f"Stopped after {len(history.history['loss'])} epochs "
f"(best weights restored)")
print(f"Held-out test accuracy on short queries: {test_accuracy:.3f}")Output
Task 4: Test on Longer Shopping Queries
long_query_samples = [
{"ticket_id": "SC-4831-01", "expected_category": "Electronics",
"query": "looking for a lightweight wireless bluetooth headphone "
"with good battery life and noise cancelling under budget "
"two thousand rupees with fast charging support"},
{"ticket_id": "SC-4831-02", "expected_category": "Electronics",
"query": "need a budget smartphone with good camera long battery "
"backup fast charging support and at least six gb ram "
"under fifteen thousand rupees"},
{"ticket_id": "SC-4831-03", "expected_category": "Apparel",
"query": "want a slim fit cotton casual shirt for men in navy blue "
"color size large with full sleeves for daily office wear"},
{"ticket_id": "SC-4831-04", "expected_category": "Apparel",
"query": "looking for comfortable running shoes for women with good "
"grip lightweight sole and breathable material for daily "
"gym and outdoor jogging"},
{"ticket_id": "SC-4831-05", "expected_category": "Grocery",
"query": "want organic basmati rice five kilogram pack with long "
"grain good aroma and no added chemicals for daily home "
"cooking"},
{"ticket_id": "SC-4831-06", "expected_category": "Grocery",
"query": "looking for cold pressed olive oil one litre bottle with "
"rich flavor and no preservatives for healthy daily cooking"},]long_query_samples = [
{"ticket_id": "SC-4831-01", "expected_category": "Electronics",
"query": "looking for a lightweight wireless bluetooth headphone "
"with good battery life and noise cancelling under budget "
"two thousand rupees with fast charging support"},
{"ticket_id": "SC-4831-02", "expected_category": "Electronics",
"query": "need a budget smartphone with good camera long battery "
"backup fast charging support and at least six gb ram "
"under fifteen thousand rupees"},
{"ticket_id": "SC-4831-03", "expected_category": "Apparel",
"query": "want a slim fit cotton casual shirt for men in navy blue "
"color size large with full sleeves for daily office wear"},
{"ticket_id": "SC-4831-04", "expected_category": "Apparel",
"query": "looking for comfortable running shoes for women with good "
"grip lightweight sole and breathable material for daily "
"gym and outdoor jogging"},
{"ticket_id": "SC-4831-05", "expected_category": "Grocery",
"query": "want organic basmati rice five kilogram pack with long "
"grain good aroma and no added chemicals for daily home "
"cooking"},
{"ticket_id": "SC-4831-06", "expected_category": "Grocery",
"query": "looking for cold pressed olive oil one litre bottle with "
"rich flavor and no preservatives for healthy daily cooking"},]
def evaluate_on_queries(model, vectorizer, samples):
"""Run raw queries through preprocessing + vectorizer + model,
returning a results table with predictions and confidence."""
texts = [preprocess(s['query']) for s in samples]
sequences = vectorizer(texts).numpy()
probs = model.predict(sequences, verbose=0)
results = pd.DataFrame(samples)
results['predicted_category'] = class_names[np.argmax(probs, axis=1)]
results['confidence'] = probs.max(axis=1).round(2)
return results
# Re-vectorize without the 7-token training cutoff -- these queries run
# well past it, and we want to see how the RNN handles the full length.
eval_vectorizer = TextVectorization(output_mode='int', max_tokens=MAX_TOKENS)
eval_vectorizer.adapt(train_df['processed_text'].tolist()) # same vocab
results_df = evaluate_on_queries(rnn_model, eval_vectorizer,
long_query_samples)
long_query_accuracy = (
results_df['predicted_category'] == results_df['expected_category']
).mean()
print(results_df[['ticket_id', 'expected_category',
'predicted_category', 'confidence']])
print(f"\nAccuracy on {len(long_query_samples)} queries from SC-4831: "
f"{long_query_accuracy:.2f}")
Output
Great job!
You have successfully reloaded Lab 3’s padded query sequences, built and trained a Simple RNN classifier, evaluated its accuracy and confidence on short and longer shopping queries, and explored its key limitations—sequential bottleneck, vanishing gradients, and limited long-range context—motivating the need for a better architecture
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Topic : Long Short-term Memory
1) LSTM gating mechanism
2) GRU gating mechanism
3) Memory Retention
By Content ITV