TensorFlow Sentiment Analysis
Sentiment analysis classifies text as expressing a positive or negative opinion. Product reviews, social media posts, customer feedback, and news articles are all examples of text where sentiment classification adds business value. This topic builds a complete sentiment analysis model on the IMDB movie review dataset — 50,000 reviews labeled positive or negative — using everything covered in the RNN section.
The Task
Input: "The acting was superb and the story kept me hooked throughout." Output: POSITIVE (probability: 0.94) Input: "Boring, predictable, and a complete waste of two hours." Output: NEGATIVE (probability: 0.03)
Step 1 — Load the IMDB Dataset
import tensorflow as tf
import numpy as np
# IMDB: 25,000 training reviews, 25,000 test reviews
# Already tokenized — words replaced with integer IDs
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data(
num_words=10000 # Keep only the 10,000 most frequent words
)
print(f"Training samples: {len(x_train)}") # 25,000
print(f"Test samples: {len(x_test)}") # 25,000
print(f"Label values: {set(y_train)}") # {0, 1}
# Reviews have variable length
print(f"Shortest review: {min(len(r) for r in x_train)} words")
print(f"Longest review: {max(len(r) for r in x_train)} words")
print(f"Average review: {np.mean([len(r) for r in x_train]):.0f} words")
Step 2 — Pad Sequences
MAXLEN = 200 # Reviews longer than 200 words get truncated
x_train = tf.keras.preprocessing.sequence.pad_sequences(
x_train, maxlen=MAXLEN, padding='post', truncating='post'
)
x_test = tf.keras.preprocessing.sequence.pad_sequences(
x_test, maxlen=MAXLEN, padding='post', truncating='post'
)
print(x_train.shape) # (25000, 200)
print(x_test.shape) # (25000, 200)
Step 3 — Build the Model
VOCAB_SIZE = 10000
EMBEDDING_DIM = 128
MAXLEN = 200
model = tf.keras.Sequential([
# Convert integer IDs to 128-dim vectors
tf.keras.layers.Embedding(VOCAB_SIZE, EMBEDDING_DIM, input_length=MAXLEN),
# Bidirectional LSTM to capture context from both directions
tf.keras.layers.Bidirectional(
tf.keras.layers.LSTM(64, return_sequences=True, dropout=0.2)
),
tf.keras.layers.Bidirectional(
tf.keras.layers.LSTM(32, dropout=0.2)
),
# Dense classification head
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dropout(0.4),
tf.keras.layers.Dense(1, activation='sigmoid') # Binary output
])
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='binary_crossentropy',
metrics=['accuracy',
tf.keras.metrics.AUC(name='auc'),
tf.keras.metrics.Precision(name='precision'),
tf.keras.metrics.Recall(name='recall')]
)
model.summary()
Step 4 — Train With Callbacks
callbacks = [
tf.keras.callbacks.EarlyStopping(
monitor='val_auc',
patience=3,
mode='max',
restore_best_weights=True
),
tf.keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=2,
min_lr=1e-6
)
]
history = model.fit(
x_train, y_train,
epochs=15,
batch_size=64,
validation_split=0.1,
callbacks=callbacks
)
Step 5 — Evaluate on Test Set
results = model.evaluate(x_test, y_test, verbose=1)
print(f"\nTest Loss: {results[0]:.4f}")
print(f"Test Accuracy: {results[1]:.2%}")
print(f"Test AUC: {results[2]:.4f}")
print(f"Test Precision: {results[3]:.4f}")
print(f"Test Recall: {results[4]:.4f}")
Step 6 — Predict on Raw Text
# The IMDB dataset uses its own vocabulary encoding
# For raw text prediction, we need to use our own vectorization
word_index = tf.keras.datasets.imdb.get_word_index()
# Offset by 3 because 0=padding, 1=start, 2=unknown
word_index = {k: v + 3 for k, v in word_index.items()}
word_index['<PAD>'] = 0
word_index['<START>'] = 1
word_index['<UNK>'] = 2
def encode_review(text):
tokens = text.lower().split()
ids = [word_index.get(word, 2) for word in tokens] # UNK=2 for unknown
ids = [id for id in ids if id < 10000] # Clip to vocab size
padded = tf.keras.preprocessing.sequence.pad_sequences(
[ids], maxlen=200, padding='post'
)
return padded
def predict_sentiment(text):
encoded = encode_review(text)
prob = model.predict(encoded, verbose=0)[0][0]
sentiment = "POSITIVE" if prob > 0.5 else "NEGATIVE"
print(f"Text: {text[:60]}...")
print(f"Sentiment: {sentiment} ({prob:.2%} positive)")
predict_sentiment("This was an absolutely brilliant film. Loved every minute.")
predict_sentiment("Terrible movie. Boring plot and awful acting throughout.")
Understanding What the Model Learned
Diagram — How the Model Processes a Review:
"The plot was uninspired and the acting was wooden"
Token IDs: [3, 105, 22, 847, 9, 3, 412, 22, 932, 0, 0...]
↓
Embedding: (200, 128) — each word becomes a 128-dim vector
↓
Bi-LSTM 1: (200, 128) — contextual representation at each word
Forward pass: left→right context
Backward pass: right→left context
↓
Bi-LSTM 2: (64,) — compressed sequence summary
↓
Dense(64): (64,) — further abstraction
↓
Dense(1): (1,) → sigmoid → 0.04 ← NEGATIVE (4% positive probability)
Comparing Models for Sentiment Analysis
Model IMDB Accuracy Training Time ──────────────────────────────────────────────────────────────── SimpleRNN ~72% Fast LSTM (single layer) ~85% Medium Bidirectional LSTM ~88% Medium Stacked Bidirectional LSTM ~89% Slow Transformer (custom) ~91% Slow BERT (fine-tuned, pre-trained) ~94% Very slow initially ────────────────────────────────────────────────────────────────
Visualizing Attention Weights (Optional Enhancement)
# Add a simple attention mechanism to see which words the model focuses on
class AttentionLayer(tf.keras.layers.Layer):
def __init__(self, units):
super().__init__()
self.W = tf.keras.layers.Dense(units, activation='tanh')
self.V = tf.keras.layers.Dense(1)
def call(self, lstm_output):
score = self.V(self.W(lstm_output)) # (batch, steps, 1)
weights = tf.nn.softmax(score, axis=1) # Normalize across steps
context = tf.reduce_sum(weights * lstm_output, axis=1) # Weighted sum
return context, weights
# High weight words in a negative review:
# "uninspired" (0.18), "wooden" (0.15), "terrible" (0.12)
# Low weight words: "the" (0.01), "and" (0.01), "was" (0.02)
Handling Class Imbalance in Sentiment Tasks
# If your dataset has unequal positive/negative samples
neg_count = sum(y_train == 0)
pos_count = sum(y_train == 1)
weight_for_neg = (1 / neg_count) * (len(y_train) / 2.0)
weight_for_pos = (1 / pos_count) * (len(y_train) / 2.0)
class_weight = {0: weight_for_neg, 1: weight_for_pos}
model.fit(x_train, y_train, class_weight=class_weight, epochs=10)
Sentiment analysis is one of the most commercially valuable NLP applications. Every customer service platform, product review system, and brand monitoring tool relies on some form of sentiment classification. With the skills from this topic — tokenization, embedding, bidirectional LSTM, and proper evaluation — you can build production-quality sentiment classifiers for any domain. The next topic begins the advanced section with pre-trained models and fine-tuning techniques.
