07 — Advanced Recommender Algorithms: DIN, DIEN, DCN, xDeepFM, AutoInt
“When MLPs aren’t enough — learning the right interactions explicitly.”
Introduction
Vanilla MLPs in deep recommenders learn feature interactions only implicitly and inefficiently. A series of architectures fixed this: DCN does multiplicative cross terms, xDeepFM combines explicit and implicit, AutoInt uses self-attention over fields, DIN/DIEN model user interest as attention over historical behavior. These models are the workhorses of Alibaba/ByteDance/Kuaishou-scale CTR systems.
1. Map of the Territory
flowchart TB
A[Beyond MLPs] --> B[Cross Networks<br/>DCN, DCNv2]
A --> C[Explicit High-Order FM<br/>xDeepFM]
A --> D[Self-Attention Fields<br/>AutoInt]
A --> E[Attention over History<br/>DIN, DIEN, DSIN, BST]
A --> F[Sequential Transformers<br/>SASRec, BERT4Rec — Blog 08]
2. DCN: Deep & Cross Network (Wang 2017, v2 in 2020)
Replaces the wide arm of Wide&Deep with a cross network of L layers:
\[x_{l+1} = x_0 (W_l x_l + b_l) + x_l\]Each layer adds an explicit polynomial degree. L layers → (L+1)-th order interactions.
DCN-v2 improves with a low-rank factorization $W_l = U_l V_l^\top$:
import torch.nn as nn, torch
class CrossNetV2(nn.Module):
def __init__(self, dim, n_layers=3, rank=16):
super().__init__()
self.U = nn.ParameterList([nn.Parameter(torch.randn(dim, rank)*0.01) for _ in range(n_layers)])
self.V = nn.ParameterList([nn.Parameter(torch.randn(rank, dim)*0.01) for _ in range(n_layers)])
self.b = nn.ParameterList([nn.Parameter(torch.zeros(dim)) for _ in range(n_layers)])
def forward(self, x0):
x = x0
for U, V, b in zip(self.U, self.V, self.b):
x = x0 * (V.T @ (U @ x.T)).T + b + x
return x
Pros: explicit polynomial interactions, fewer params than DNN of equivalent expressivity. Cons: still combines all fields uniformly — AutoInt addresses this.
3. xDeepFM (Lian 2018)
Adds a Compressed Interaction Network (CIN) that learns explicit vector-wise high-order interactions (vs DCN’s bit-wise).
\[X^k_h = \sum_{i,j} W^{k,h}_{ij} (X^{k-1}_i \odot X^0_j)\]CIN + DNN + linear, jointly trained.
Pros: vector-wise interactions, often higher CTR vs DeepFM. Cons: CIN is expensive — quadratic in number of feature maps per layer.
4. AutoInt (Song 2019)
Multi-head self-attention over feature embeddings (treating fields as tokens):
\[\text{Att}(Q,K,V) = \text{softmax}(QK^\top / \sqrt{d}) V\]The model learns which field interactions matter rather than enumerating all pairs.
class AutoInt(nn.Module):
def __init__(self, field_dims, d=16, heads=2, blocks=3):
super().__init__()
self.embeds = nn.ModuleList([nn.Embedding(n, d) for n in field_dims])
enc = nn.TransformerEncoderLayer(d_model=d, nhead=heads, dim_feedforward=64,
batch_first=True, dropout=0.1)
self.transformer = nn.TransformerEncoder(enc, num_layers=blocks)
self.out = nn.Linear(d * len(field_dims), 1)
def forward(self, x): # x: [B, F]
e = torch.stack([emb(x[:, i]) for i, emb in enumerate(self.embeds)], 1) # B, F, d
h = self.transformer(e) # B, F, d
return torch.sigmoid(self.out(h.flatten(1))).squeeze(-1)
Pros: interpretable attention weights, automatic feature selection. Cons: more parameters, careful tuning required.
5. DIN: Deep Interest Network (Alibaba 2018)
Key idea: a user’s interest is specific to the candidate. Use attention over historical items, with the candidate as the query:
\[a_i = \text{softmax}\left( \text{MLP}(e_i, e_{\text{cand}}, e_i \odot e_{\text{cand}}, e_i - e_{\text{cand}}) \right)\] \[v_{\text{user}} = \sum_i a_i e_i\]class DIN(nn.Module):
def __init__(self, n_items, d=32):
super().__init__()
self.item_emb = nn.Embedding(n_items, d)
self.att_mlp = nn.Sequential(nn.Linear(4*d, 64), nn.ReLU(), nn.Linear(64, 1))
self.scorer = nn.Sequential(nn.Linear(2*d, 64), nn.ReLU(), nn.Linear(64, 1))
def forward(self, hist, cand): # hist: B,L cand: B
h_emb = self.item_emb(hist) # B, L, d
c_emb = self.item_emb(cand).unsqueeze(1).expand_as(h_emb)
a_in = torch.cat([h_emb, c_emb, h_emb*c_emb, h_emb-c_emb], -1)
a = self.att_mlp(a_in).squeeze(-1).softmax(-1)
u_emb = (a.unsqueeze(-1) * h_emb).sum(1) # B, d
return torch.sigmoid(self.scorer(torch.cat([u_emb, self.item_emb(cand)], -1))).squeeze(-1)
Variants:
- DIEN: GRU + attention to model interest evolution
- DSIN: divides history into sessions; intra-session transformer + inter-session interaction
- BST (Behavior Sequence Transformer): replaces GRU/attention with a Transformer block
Pros: state-of-the-art for industrial CTR. Cons: sequence length L drives serving latency — typically L=50–500.
6. Comparison Table
| Model | Interaction Modeling | Sequence-aware | Strength | Weakness |
|---|---|---|---|---|
| DeepFM | 2nd-order auto + MLP | ✗ | Simple, strong baseline | Quadratic FM cost |
| DCN-v2 | Explicit polynomial | ✗ | Compact, easy to train | All-field interactions |
| xDeepFM | Vector-wise explicit | ✗ | High lift in ads | Heavy compute |
| AutoInt | Self-attention over fields | ✗ | Interpretable | More params |
| DIN | Attention over history | ✓ | Industry SOTA CTR | Latency scales w/ L |
| DIEN | GRU + attention | ✓ | Models interest drift | Complex training |
| BST | Transformer over history | ✓ | Cleanest formulation | Compute heavy |
7. Architecture Diagram
flowchart LR
F[Sparse Features] --> E[Embedding Tables]
H[User History Seq] --> EH[History Embeds]
C[Candidate Item] --> EC[Candidate Embed]
EH --> A[Attention<br/>Q=Candidate]
EC --> A
A --> U[User Representation]
E --> X[Cross Net /<br/>FM / AutoInt]
U --> X
X --> MLP --> O[CTR Score]
8. Training Tips
- Bucketize numerical features, then embed. Almost always lifts vs raw normalization.
- For DIN-style models, mask padding in attention.
- Use sampled softmax when the output space is large (item-id classification head).
- Gradient clipping at 1.0 stabilizes attention models.
- Adam + warmup for transformer-based variants.
9. End-to-End: DCN-v2 on Criteo
# pip install torch pandas pyarrow
import pandas as pd, torch
from torch.utils.data import DataLoader, TensorDataset
df = pd.read_parquet("criteo_subsample.parquet") # 1M rows ~ enough
cat_cols = [c for c in df.columns if c.startswith("C")]
num_cols = [c for c in df.columns if c.startswith("I")]
# Hash trick on categoricals
for c in cat_cols:
df[c] = df[c].fillna("MISS").astype(str).apply(lambda x: hash(x) % 100_000)
df[num_cols] = df[num_cols].fillna(0).pipe(lambda x: (x.clip(lower=0)+1).pow(0.5))
X_cat = torch.tensor(df[cat_cols].values, dtype=torch.long)
X_num = torch.tensor(df[num_cols].values, dtype=torch.float)
y = torch.tensor(df["label"].values, dtype=torch.float)
class DCNv2(torch.nn.Module):
def __init__(self, n_cat, hash_size=100_000, d=16):
super().__init__()
self.emb = torch.nn.ModuleList([torch.nn.Embedding(hash_size, d) for _ in range(n_cat)])
in_dim = n_cat * d + 13
self.cross = CrossNetV2(in_dim, n_layers=3, rank=16)
self.deep = torch.nn.Sequential(
torch.nn.Linear(in_dim, 256), torch.nn.ReLU(),
torch.nn.Linear(256, 128), torch.nn.ReLU())
self.head = torch.nn.Linear(in_dim + 128, 1)
def forward(self, xc, xn):
e = torch.cat([emb(xc[:, i]) for i, emb in enumerate(self.emb)], 1)
x0 = torch.cat([e, xn], 1)
return torch.sigmoid(self.head(torch.cat([self.cross(x0), self.deep(x0)], 1))).squeeze(-1)
model = DCNv2(n_cat=len(cat_cols)).cuda()
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
ds = DataLoader(TensorDataset(X_cat, X_num, y), batch_size=4096, shuffle=True)
for epoch in range(3):
for xc, xn, yy in ds:
xc, xn, yy = xc.cuda(), xn.cuda(), yy.cuda()
p = model(xc, xn)
loss = torch.nn.functional.binary_cross_entropy(p, yy)
opt.zero_grad(); loss.backward(); opt.step()
10. Production Tips
- For DIN-family, cache user history embeddings so only the candidate side is computed per request.
- TensorRT / ONNX for ranking models — 3–5× speedup typical.
- Distillation: train a heavy AutoInt then distill into a smaller MLP for tight-SLO ranking.
- Knowledge distillation from a deep teacher into a shallow student improves p99 latency dramatically.
11. Pitfalls
- Hash collisions on categorical IDs — use 100K+ buckets per high-cardinality field.
- Forgetting to use layer norm in transformer-based variants — training diverges.
- Treating CTR head output as calibrated without temperature scaling.
- Position bias confounds attention — always include position as a feature or model it explicitly (Blog 17).
12. Public Datasets
- Taobao Ad Display/Click — https://tianchi.aliyun.com/dataset/56 (DIN’s canonical eval)
- Criteo — https://www.kaggle.com/c/criteo-display-ad-challenge
- Avazu — https://www.kaggle.com/c/avazu-ctr-prediction
- Amazon Books / Electronics — https://nijianmo.github.io/amazon/ (used in DIEN paper)
- iPinYou — http://contest.ipinyou.com/
13. Further Reading
- Wang et al., Deep & Cross Network (DCN) (ADKDD 2017) and DCN-v2 (WWW 2021)
- Lian et al., xDeepFM (KDD 2018)
- Song et al., AutoInt (CIKM 2019)
- Zhou et al., DIN: Deep Interest Network for CTR Prediction (KDD 2018)
- Zhou et al., DIEN: Deep Interest Evolution Network (AAAI 2019)
- Feng et al., DSIN (IJCAI 2019)
- Chen et al., Behavior Sequence Transformer (BST) (DLP-KDD 2019)