ML in Real Life: Embeddings with Keras Examples - From Theory to Production-Ready Code


You've spent three months trying to build a recommendation system, burned through your ML budget, and still can't figure out why your "state-of-the-art" embeddings perform worse than random suggestions.
This scenario plays out daily across tech companies worldwide.
While embeddings power Netflix recommendations, Google Search, and ChatGPT's language understanding, most developers hit a wall when implementing them.
Tutorials overflow with buzzwords like "dense representations" and "semantic similarity" but lack the concrete Keras code that actually works.
The gap between theory and practice costs teams months of development time, thousands in wasted compute resources, and the crushing frustration of watching "simple" ML concepts remain mysteriously out of reach.
This guide eliminates that gap.
You'll master embeddings through first principles, see production-ready Keras implementations for text classification, recommenders, and tabular ML, and walk away with battle-tested code patterns you can deploy immediately. No abstractions, no toy examples—just practical knowledge that works.
What Embeddings Actually Do
Embeddings solve a fundamental problem in machine learning: how do you feed discrete, symbolic data to algorithms that need continuous numbers?
Traditional approaches use one-hot encoding, which creates massive sparse vectors where only one element equals 1.
This approach wastes memory, offers no similarity structure, and forces each category to learn independently.
Embeddings replace this with dense vectors where similar items cluster together in space.
The magic happens during training—your loss function sculpts the vector space so that semantically similar items end up geometrically close.
Think of embeddings as a learned translation system. They convert discrete symbols (words, user IDs, product categories) into coordinates in a high-dimensional space.
The coordinates themselves mean nothing initially—they're just random numbers. But as your model trains, gradient descent pushes related items closer together and unrelated items further apart.
Why Embeddings Outperform Alternatives
Embeddings excel because they enable parameter sharing across similar items.
When you use one-hot encoding with linear models, each category learns its own weight vector independently.
If "iPhone 14" and "iPhone 15" appear in your dataset, the model treats them as completely unrelated entities.
Embeddings let these similar items share statistical strength by placing them near each other in vector space.
The Keras Embedding Layer Explained
Keras makes embeddings accessible through a single layer that hides complexity while exposing essential controls.
tf.keras.layers.Embedding(
input_dim, # Vocabulary size (max index + 1)
output_dim, # Embedding dimension
mask_zero=False, # Whether index 0 represents padding
embeddings_initializer='uniform',
embeddings_regularizer=None,
trainable=True
)
The most common mistake happens with input_dim
—it must equal your maximum index plus one. If your user IDs range from 0 to 99999, set input_dim=100000
. Off-by-one errors here cause silent failures where the model learns garbage for out-of-bounds indices.
The mask_zero
parameter deserves special attention for sequence tasks. When set to True
, Keras treats index 0 as padding and propagates this mask to downstream layers. This prevents padding tokens from contributing to attention weights or pooling operations. Always reserve index 0 for padding when you enable masking.
Output dimension controls the expressiveness-efficiency tradeoff. Larger dimensions provide more capacity but consume more memory and computation.
Text Classification: Your First Embedding Implementation
Let's build a complete text classification system that demonstrates core embedding concepts.
import tensorflow as tf
from tensorflow.keras import layers, models
# Sample data - in practice, load from your dataset
texts = tf.constant([
"i love this product amazing quality",
"terrible service very disappointed",
"excellent customer support helpful staff",
"poor quality broke immediately",
"fantastic experience highly recommend",
"worst purchase ever complete waste"
])
labels = tf.constant([1, 0, 1, 0, 1, 0], dtype=tf.float32)
# Configuration
max_tokens = 10000
sequence_length = 20
embedding_dim = 64
# Text preprocessing pipeline
text_vectorizer = layers.TextVectorization(
max_tokens=max_tokens,
output_mode="int",
output_sequence_length=sequence_length,
standardize="lower_and_strip_punctuation"
)
# Fit the vectorizer on training data
text_vectorizer.adapt(texts.batch(8))
# Model architecture
def create_text_classifier():
# Input: raw strings
text_input = layers.Input(shape=(), dtype=tf.string, name='text')
# Convert to integer sequences
x = text_vectorizer(text_input) # Shape: (batch, sequence_length)
# Embed tokens into dense vectors
x = layers.Embedding(
input_dim=max_tokens,
output_dim=embedding_dim,
mask_zero=True, # Handle padding
name='token_embedding'
)(x) # Shape: (batch, sequence_length, embedding_dim)
# Pool across sequence dimension
x = layers.GlobalAveragePooling1D()(x) # Shape: (batch, embedding_dim)
# Classification head
x = layers.Dense(128, activation='relu')(x)
x = layers.Dropout(0.2)(x)
output = layers.Dense(1, activation='sigmoid', name='prediction')(x)
return models.Model(text_input, output)
# Build and compile model
model = create_text_classifier()
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'auc']
)
# Train the model
history = model.fit(
texts, labels,
batch_size=2,
epochs=10,
validation_split=0.2,
verbose=1
)
# Inspect learned embeddings
embedding_layer = model.get_layer('token_embedding')
vocab = text_vectorizer.get_vocabulary()
# Get embeddings for specific tokens
token_index = vocab.index('excellent')
token_embedding = embedding_layer.get_weights()[0][token_index]
print(f"Embedding for 'excellent': {token_embedding[:5]}...")
This example demonstrates several key concepts:
The TextVectorization
layer converts raw strings into integer sequences.
The Embedding
layer transforms these integers into dense vectors. GlobalAveragePooling1D
aggregates sequence embeddings into fixed-size representations. The mask_zero=True
parameter ensures padding tokens don't affect the average.
Notice how the model accepts raw strings as input but processes integer sequences internally. You can modify tokenization logic without retraining the entire model.
Recommender Systems: Two-Tower Architecture
Recommender systems showcase embeddings' power for modeling user-item interactions with efficient serving through precomputed item vectors and fast retrieval.
import tensorflow as tf
from tensorflow.keras import layers, Model
# Toy interaction data
user_ids = tf.constant([10, 42, 7, 99, 5, 5, 10, 12], dtype=tf.int32) # shape: (N,)
item_ids = tf.constant([1001, 5, 8, 8, 7, 3, 1001, 5], dtype=tf.int32) # shape: (N,)
labels = tf.constant([1, 0, 1, 0, 1, 0, 1, 0], dtype=tf.float32) # click/purchase
num_users, num_items, emb_dim = 100_000, 50_000, 64
# Two-tower architecture
user_in = layers.Input(shape=(), dtype=tf.int32, name="user_id") # <-- pass scalars per row
item_in = layers.Input(shape=(), dtype=tf.int32, name="item_id")
u = layers.Embedding(num_users, emb_dim, name="user_emb")(user_in) # (batch, emb_dim)
v = layers.Embedding(num_items, emb_dim, name="item_emb")(item_in) # (batch, emb_dim)
score = layers.Dot(axes=1)([u, v]) # (batch, 1)
out = layers.Activation("sigmoid")(score)
recsys = Model([user_in, item_in], out)
recsys.compile(optimizer="adam", loss="binary_crossentropy", metrics=["AUC"])
# Train — you can pass a dict keyed by Input names or a list [user_ids, item_ids]
# shapes: user_ids: (batch,), item_ids: (batch,), y: (batch,)
recsys.fit({"user_id": user_ids, "item_id": item_ids}, labels,
batch_size=4, epochs=5, validation_split=0.25)
# Extract embeddings for similarity search
user_embeddings = recsys.get_layer("user_emb").get_weights()[0] # (num_users, emb_dim)
item_embeddings = recsys.get_layer("item_emb").get_weights()[0] # (num_items, emb_dim)
# Find similar items via dot product
def find_similar_items(item_id, top_k=5):
query_vec = item_embeddings[item_id] # (emb_dim,)
scores = item_embeddings @ query_vec # (num_items,)
top_indices = scores.argsort()[-top_k-1:-1][::-1] # exclude self
return top_indices, scores[top_indices]
similar_items, similarity_scores = find_similar_items(item_id=5)
print(f"Items similar to 5: {similar_items}")
The two-tower design separates user and item representations, enabling precomputation of item vectors for millisecond retrieval.
Dot product scoring makes the architecture compatible with approximate nearest neighbor libraries like Faiss.
Tabular Data: Mixing Categorical and Numeric Features
Real-world datasets combine high-cardinality categoricals with numeric features. Embeddings handle the categoricals while standard preprocessing handles the numerics.
import tensorflow as tf
import numpy as np
import pandas as pd
from tensorflow.keras import layers, Model
from sklearn.preprocessing import StandardScaler
# Generate synthetic tabular data
np.random.seed(42)
n_samples = 10000
# Categorical features
n_cities, n_devices, n_categories, n_dim = 500, 2000, 100, 128
data = {
'city_id': np.random.randint(0, n_cities, n_samples),
'device_id': np.random.randint(0, n_devices, n_samples),
'category_id': np.random.randint(0, n_categories, n_samples),
# Numeric features
'age': np.random.normal(35, 12, n_samples).clip(18, 80),
'income': np.random.lognormal(10.5, 0.5, n_samples),
'session_duration': np.random.exponential(300, n_samples),
'previous_purchases': np.random.poisson(2, n_samples),
'days_since_signup': np.random.exponential(365, n_samples)
}
df = pd.DataFrame(data)
# Create target variable with realistic relationships
df['target'] = (
0.3 * (df['age'] > 30) +
0.2 * (df['income'] > 50000) +
0.1 * (df['session_duration'] > 300) +
0.2 * (df['previous_purchases'] > 1) +
0.1 * (df['category_id'] < 20) + # Some categories are more valuable
np.random.normal(0, 0.3, n_samples)
).clip(0, 1)
# Preprocess numeric features
numeric_features = ['age', 'income', 'session_duration', 'previous_purchases', 'days_since_signup']
scaler = StandardScaler()
df[numeric_features] = scaler.fit_transform(df[numeric_features])
# Split data
train_size = int(0.8 * len(df))
train_df = df[:train_size]
val_df = df[train_size:]
def create_tabular_model(n_cities, n_devices, n_categories, n_numeric_features):
# Categorical inputs
city_input = layers.Input(shape=(), dtype=tf.int32, name='city_id')
device_input = layers.Input(shape=(), dtype=tf.int32, name='device_id')
category_input = layers.Input(shape=(), dtype=tf.int32, name='category_id')
# Numeric inputs
numeric_input = layers.Input(shape=(n_numeric_features,), dtype=tf.float32, name='numeric_features')
# Embed categorical features with different dimensions based on cardinality
city_embedding = layers.Embedding(
input_dim=n_cities,
output_dim=n_dim,
name='city_embedding'
)(city_input)
device_embedding = layers.Embedding(
input_dim=n_devices,
output_dim=n_dim,
name='device_embedding'
)(device_input)
category_embedding = layers.Embedding(
input_dim=n_categories,
output_dim=n_dim,
name='category_embedding'
)(category_input)
# Concatenate all features
combined_features = layers.Concatenate(name='feature_concat')([
city_embedding,
device_embedding,
category_embedding,
numeric_input
])
# Deep neural network
x = layers.Dense(256, activation='relu')(combined_features)
x = layers.BatchNormalization()(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(128, activation='relu')(x)
x = layers.BatchNormalization()(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(64, activation='relu')(x)
x = layers.Dropout(0.2)(x)
# Output layer
output = layers.Dense(1, activation='sigmoid', name='prediction')(x)
return Model([city_input, device_input, category_input, numeric_input], output)
# Build model
model = create_tabular_model(n_cities, n_devices, n_categories, len(numeric_features))
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='binary_crossentropy',
metrics=['auc', 'precision', 'recall']
)
# Prepare training data
def prepare_inputs(df):
return {
'city_id': df['city_id'].values,
'device_id': df['device_id'].values,
'category_id': df['category_id'].values,
'numeric_features': df[numeric_features].values
}
train_inputs = prepare_inputs(train_df)
val_inputs = prepare_inputs(val_df)
# Train model
history = model.fit(
train_inputs, train_df['target'].values,
validation_data=(val_inputs, val_df['target'].values),
batch_size=256,
epochs=50,
)
This tabular example demonstrates several advanced techniques:
Adaptive embedding dimensions based on cardinality prevent over-parameterization. L2 regularization controls embedding magnitude and prevents overfitting. Batch normalization and dropout improve generalization across mixed feature types. The analysis function provides insights into learned representations.
The key insight is treating categorical and numeric features as complementary information sources. Embeddings capture similarity structure in categoricals while standard preprocessing handles numerics. The concatenation layer fuses these representations for the downstream neural network.
Conclusion
Embeddings transform discrete symbols into continuous representations that capture learned similarity. This simple concept powers some of the most successful machine learning systems in production today.
You've seen how Keras makes embeddings accessible through a clean API that handles the complexity while exposing essential controls.
The examples demonstrate three fundamental patterns: text classification, collaborative filtering, and mixed tabular features. Each pattern adapts to different data types and business requirements while sharing core principles.
The key insights to remember:
Start simple and iterate. Begin with basic architectures before adding complexity like attention or hierarchical embeddings. Monitor embedding quality throughout development to catch issues early. Focus on the loss function—it defines what similarity means in your vector space.
Handle production concerns upfront. Plan for vocabulary changes, out-of-vocabulary tokens, and serving latency from the beginning. Implement proper monitoring and quality checks before deployment. Consider memory constraints and quantization strategies for large-scale systems.
Embeddings excel at capturing relationships but require careful tuning. Dimension selection balances expressiveness with efficiency. Regularization prevents overfitting on sparse categorical features. Proper masking and sequence handling are crucial for temporal data.
The techniques in this guide provide a foundation for building robust embedding systems. Whether you're building recommenders, processing text, or analyzing tabular data, these patterns will serve you well.
Start with the simplest approach that solves your problem, then add sophistication only when metrics demand it.
Subscribe to my newsletter
Read articles from Juan Carlos Olamendy directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Juan Carlos Olamendy
Juan Carlos Olamendy
🤖 Talk about AI/ML · AI-preneur 🛠️ Build AI tools 🚀 Share my journey 𓀙 🔗 http://pixela.io