TwoTower Tutorial: Dual-Encoder Retrieval#
Introduction#
TwoTower is a production dual-encoder model for large-scale candidate retrieval. It learns separate user and item embeddings and scores pairs via dot product — ideal for the first stage of a recommendation pipeline.
How TwoTower Works#
Architecture#
User Tower: Encodes user features / interaction history into an embedding
Item Tower: Encodes item features into an embedding
Scoring: Dot product (or cosine similarity) between user and item vectors
User features → User Tower → u_emb ─┐
├─ dot(u_emb, i_emb) → score
Item features → Item Tower → i_emb ─┘
Tutorial with cr_learn#
Step 1: Import and Load Data#
TwoTower expects a user–item interaction matrix.
from corerec.engines.two_tower import TwoTower
from cr_learn import ml_1m
from sklearn.model_selection import train_test_split
import numpy as np
data = ml_1m.load()
ratings_df = data['ratings']
train_df, test_df = train_test_split(ratings_df, test_size=0.2, random_state=42)
user_list = sorted(train_df['user_id'].unique())
item_list = sorted(train_df['movie_id'].unique())
user_idx = {u: i for i, u in enumerate(user_list)}
item_idx = {it: j for j, it in enumerate(item_list)}
train_mat = np.zeros((len(user_list), len(item_list)), dtype=np.float32)
for _, row in train_df.iterrows():
train_mat[user_idx[row['user_id']], item_idx[row['movie_id']]] = float(row['rating'])
print(f"Loaded {len(ratings_df)} ratings")
Step 2: Initialize Model#
model = TwoTower(
user_input_dim=64,
item_input_dim=64,
embedding_dim=128,
num_epochs=5,
batch_size=256,
learning_rate=0.001,
verbose=True
)
Step 3: Train#
model.fit(user_list, item_list, train_mat)
print("Training complete!")
Step 4: Predict and Recommend#
sample_user = user_list[0]
sample_item = item_list[0]
score = model.predict(sample_user, sample_item)
print(f"Predicted score: {score:.3f}")
recs = model.recommend(user_id=sample_user, top_k=10)
print(f"Top-10 recommendations: {recs}")
Step 5: Save & Load#
model.save('artifacts/two_tower')
loaded = TwoTower.load('artifacts/two_tower')
print(loaded.recommend(sample_user, top_k=5))
Key Takeaways#
✅ Use for: Large catalogs, retrieval stage, real-time serving
❌ Not for: Fine-grained ranking without a second-stage ranker