# Deep Interest Network (DIN)

> Alibaba 2018 - the candidate item is the attention QUERY over the behaviour sequence, so the user embedding is computed per candidate instead of once.

Pick for CTR ranking when relevance depends on which candidate is being scored. The local activation unit is linear in sequence length, unlike self-attention over the same sequence.

- Category: Recommendation
- Paper: Zhou et al. (2018). Deep Interest Network for Click-Through Rate Prediction. KDD 2018 (https://arxiv.org/abs/1706.06978)
- Layers: 15
- Parameters: 128.05M
- Input shape (batchless): 50
- Output shape: 1
- Verifier verdict: pass
- Graph JSON: https://neurarch.com/templates/din/model.json
- Open on the canvas: https://neurarch.com/?template=din

## Structure

| # | Layer | Type | Parameters | Output shape |
| --- | --- | --- | --- | --- |
| 1 | Behavior Seq (50 items) | Input | shape=[50] | 50 |
| 2 | Item Embed | Embedding | vocabSize=1000000 | 50 × 64 |
| 3 | Candidate Item | Input | shape=[1] | 1 |
| 4 | Candidate Embed | Embedding | vocabSize=1000000 | 1 × 64 |
| 5 | Local Activation Unit | Target Attention (DIN) | embedDim=64 | 64 |
| 6 | User Profile | Input | shape=[16] | 16 |
| 7 | User Tower | Linear | outFeatures=32, inFeatures=16 | 32 |
| 8 | [interest; user] | Concatenate |  | 96 |
| 9 | MLP 200 | Linear | outFeatures=200, inFeatures=96 | 200 |
| 10 | Dice (~PReLU) | PReLU |  | 200 |
| 11 | MLP 80 | Linear | outFeatures=80, inFeatures=200 | 80 |
| 12 | Dice (~PReLU) | PReLU |  | 80 |
| 13 | CTR Head | Linear | outFeatures=1, inFeatures=80 | 1 |
| 14 | Sigmoid | Sigmoid |  | 1 |
| 15 | pCTR | Output |  | 1 |

## Verifier findings

- **info** `output-activation` at `Sigmoid`: "Sigmoid" feeds directly into Output. PyTorch's nn.CrossEntropyLoss already applies log-softmax internally, an explicit Softmax causes double-application and degrades training stability. Fix: Remove Softmax/Sigmoid for training. Restore it in a separate inference wrapper or ONNX export.
- **info** `vanishing-gradient` at `Sigmoid`: Sigmoid saturates to [0,1] / [-1,1], and its gradient approaches zero for large inputs. In networks deeper than 5 layers, this halts learning in early layers. Fix: Use ReLU, GELU, or SiLU for hidden layers. Keep Sigmoid only at binary classification outputs; Tanh in specific contexts (GAN generators, LSTM gates).
- **info** `deep-no-norm`: 11 layers with no BatchNorm, LayerNorm, or GroupNorm. Without normalization, activations can explode or vanish across layers, causing slow or unstable training. Fix: Add BatchNorm after Conv2d (CV tasks), LayerNorm after attention/FFN (NLP/LLM), or GroupNorm for small batch sizes.
- **info** `init-activation-mismatch` at `CTR Head`: PyTorch initializes Linear/Conv with Kaiming (He) init, which is derived for ReLU-family activations. Feeding a saturating activation (sigmoid/tanh) from a He-initialized layer starts training in the saturated tails, shrinking early gradients. Fix: Initialize these layers with Xavier instead: nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain("sigmoid"|"tanh")), or switch the activation to a ReLU-family one.

## Exported PyTorch (first 46 lines)

```python
# Architecture designed with Neurarch: https://neurarch.com
# PyTorch: compatible with Python 3.8+ and torch>=1.12
# Colab: pip install torch torchvision  (usually pre-installed)

import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Tuple

class TargetAttention(nn.Module):
    """DIN's local activation unit. The CANDIDATE is the query over the
    behaviour sequence, so the sequence axis is consumed and one interest
    vector comes out per candidate."""

    def __init__(self, embed_dim: int, hidden_dim: int = 36):
        super().__init__()
        self.mlp = nn.Sequential(
            nn.Linear(4 * embed_dim, hidden_dim),
            nn.PReLU(),
            nn.Linear(hidden_dim, 1),
        )

    def forward(self, query: torch.Tensor, keys: torch.Tensor) -> torch.Tensor:
        if query.dim() == keys.dim() - 1:
            query = query.unsqueeze(-2)
        q = query[..., :1, :].expand_as(keys)
        feats = torch.cat([q, keys, q - keys, q * keys], dim=-1)
        w = self.mlp(feats).softmax(dim=-2)
        return (w * keys).sum(dim=-2)


class DeepInterestNetworkDIN(nn.Module):
    def __init__(self):
        super().__init__()

        self.embedding_1 = nn.Embedding(1000000, 64)
        self.embedding_2 = nn.Embedding(1000000, 64)
        self.targetAttention_1 = TargetAttention(embed_dim=64, hidden_dim=36)
        self.linear_1 = nn.Linear(16, 32)
        self.linear_2 = nn.Linear(96, 200)
        self.prelu_1 = nn.PReLU(num_parameters=1)
        self.linear_3 = nn.Linear(200, 80)
        self.prelu_2 = nn.PReLU(num_parameters=1)
        self.linear_4 = nn.Linear(80, 1)

    def forward(self, src, tgt=None, in3=None):
```

## Machine access

- Every architecture: https://neurarch.com/a/index.json
- Verify a graph of your own: `POST https://www.neurarch.com/api/v1/check` (see https://neurarch.com/developer.html)
- MCP server, so an agent edits the graph with the checks in the loop: https://neurarch.com/docs/mcp.md
