阿味第一次光顾美食街,点了麻辣串;隔了两天,他把烤鱼也尝了一遍;再后来,他的朋友圈里全是奶盖茶,最近却总在轻食沙拉摊前驻足。街口的老店主想猜他明天会点什么,可他心里明白:阿味的口味从来不是一张静止的照片,而是一条流动的河。传统的推荐模型常把用户行为当一篮子货品去称重,而 DIEN(Deep Interest Evolution Network)要做的,就是像老店主一样,把阿味的小票按时间顺序从头读到尾,读出一本会变的口味账本——用 GRU 抽取兴趣,用注意力门控追踪演化,再用“下一顿真实点了什么”来校准账目。这篇内容,就带你从零认识这套“兴趣进化”的推荐思路。

岗位关联

掌握 DIEN,你将理解如何用序列建模捕捉用户兴趣的动态演化,这是电商、广告、短视频推荐系统的核心能力,也是从传统 CTR 预估走向深度学习推荐的分水岭。

目录
  1. 可视化演示
  2. 问题背景:为什么推荐要关心“兴趣会变”
  3. 整体架构:一桌四道菜的推荐流水线
  4. 兴趣抽取层:GRU 逐章翻阅口味账本
  5. 兴趣进化层:AUGRU 让相关记忆大声说话
  6. 辅助损失设计:用“下一顿”校准账本
  7. 与 DIN 对比:从快照到故事
  8. 长序列处理:上千张小票怎么读完
  9. 工业落地:从离线实验到线上大盘
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

交互展示DIEN四模块架构流程、GRU兴趣进化机制和辅助损失训练策略,理解如何建模用户兴趣演化提升推荐效果。

本节配有交互动效(进入视口后自动加载,约 3KB)。

DIEN四模块架构流程 行为序列嵌入→兴趣提取→兴趣进化→预测 GRU建模兴趣时序演化 h_t = GRU(e_t, h_{t-1}) 四模块级联捕捉用户兴趣动态变化

问题背景:为什么推荐要关心“兴趣会变”

阿味的故事每天都在美食街重演:他上个月无辣不欢,这个月却抱着奶盖茶不放,最近更是在轻食沙拉摊前徘徊。老店主如果只记得“他爱吃辣”的旧印象,就会在他转身走向沙拉摊时,推荐一整屏麻辣火锅。这种偏好随时间迁移的现象,在推荐系统里叫兴趣漂移——用户的兴趣不是一张静止的相片,而是一条不断改道的河流。

传统的推荐链路大致是:召回(从千万商品里捞出几百个候选)→ 排序(对候选逐个算点击率)→ 重排(多样性打散)。排序环节里,点击率预估模型(CTR 模型)是核心。早期模型把用户历史行为“压平”成一个兴趣向量池,隐式假设兴趣在统计窗口内不变;后来的 DIN 模型用候选商品当“探照灯”,从历史行为里找出相关部分做注意力加权,已经进步很多。但 DIN 丢掉了一个关键维度:时间顺序。它不关心“辣 → 甜 → 健康”这条演化路径是怎么走出来的。

DIEN 的出发点,就是把推荐问题重新表述为:给定用户 u 的行为序列 i_1, i_2, ..., i_T 与候选商品 v,估计点击概率:

老店主把一沓小票按时间排开,不是数一数有几次辣的,而是顺着时间的河流,看口味是如何一步步走到今天的。最后停在河口的那个状态 h_T,才是预测明天最好的起点。DIEN 的一切设计,都围绕“如何把这个 h_T 算准”展开。

y^=P(click=1∣u,  v,  hT)\displaystyle \hat{y} = P(\text{click}=1 \mid u,\; v,\; h_T)
这个公式想说的是:用户点不点候选商品,取决于他的画像、商品本身,以及一个会随时间变化的兴趣状态 h_T。h_T 不是平均印象,而是从整段行为序列里“演化”出来的当前兴趣。
  1. \hat{y}(预估点击率)模型的输出是一个 0 到 1 之间的概率值,代表用户点击候选商品的可能性。
    y^∈[0,1]\hat{y} \in [0, 1]
  2. P(\text{click}=1 \mid \cdot)(条件概率记号)在给定条件下“点击事件发生”的概率,是 CTR 模型的标准写法。
    P(click=1∣⋅)P(\text{click}=1 \mid \cdot)
  3. u / v(用户与候选商品)u 是用户画像特征向量,v 是候选商品的特征向量,它们构成预测的静态部分。
    u,  vu,\; v
  4. h_T(当前兴趣状态)由行为序列压缩出的隐藏状态,下标 T 表示序列的最后一步,浓缩了兴趣演化的终点。
    hT=GRU(x1,…,xT)h_T = \mathrm{GRU}(x_1, \dots, x_T)

整体架构:一桌四道菜的推荐流水线

老店主决定翻新厨房,把“猜口味”变成一条流水线:先洗菜——把每道菜的 ID 翻译成“色香味”特征卡片;再读单——按顺序默读阿味的所有小票;接着追味——只挑与候选菜有关的记忆线去追踪变化;最后拍板——综合判断他今天点不点这道菜。DIEN 的架构,恰好就是这四道工序。

第一道工序是嵌入表示层。商品 ID、类目、店铺等稀疏特征不能直接做数学运算,需要查表映射成稠密的向量,就像把“麻辣串”三个字翻译成一串数字化的“色香味”。第二道是兴趣抽取层,用 GRU 逐时刻读取行为序列,每个时刻输出一个隐藏状态 h_t,相当于边读小票边更新口味账本。第三道是兴趣进化层,结合候选商品,用注意力门控从 h_t 中提炼出与当前预测相关的演化路径,得到最终兴趣状态 h_T'。第四道是预测输出层,把进化后的兴趣、候选商品嵌入、用户画像拼接起来,交给多层感知机(MLP)压成点击率。

用公式概括这条流水线:

流水线里最关键的两道工序是“读单”与“追味”,也就是 GRU 与 AUGRU。后面两节,我们就钻进这两道工序,看看账本到底是怎么被写出来、又是怎么被聚焦的。

xt=Embedding(it),y^=MLP(hT′,  ve,  ue)\displaystyle x_t = \mathrm{Embedding}(i_t), \qquad \hat{y} = \mathrm{MLP}(h_T^{\prime},\; v_e,\; u_e)
前一半把行为 ID 变成向量,后一半把“进化后的兴趣 + 候选商品 + 用户画像”三路信息汇合,输出最终点击率。中间两道工序(抽取与进化)是 DIEN 的灵魂。
  1. x_t(行为嵌入)第 t 个行为的商品 ID 经查表得到稠密向量,是序列建模的输入单位。
    xt=Embedding(it)x_t = \mathrm{Embedding}(i_t)
  2. h_T'(进化后兴趣)兴趣进化层输出的最终状态,相比抽取层的 h_T,它已经被候选商品“过滤”过,只保留与预测相关的主线。
    hT′∈Rdh_T^{\prime} \in \mathbb{R}^d
  3. v_e / u_e(候选与用户嵌入)候选商品向量与用户画像向量,与兴趣状态一起送入预测层。
    ve,  uev_e,\; u_e
  4. MLP(预测层)多层感知机把拼接后的高维特征逐层压缩,最终映射成点击率。
    y^=MLP(hT′,ve,ue)\hat{y} = \mathrm{MLP}(h_T^{\prime}, v_e, u_e)

兴趣抽取层:GRU 逐章翻阅口味账本

老店主读小票的方式,不是跳读,而是从头到尾默读:读第一张时心里有了“爱吃辣”的印象,读第三张时改成“辣里带点甜”,每读一张,印象就更新一次。这种“读一张、改一版”的机制,正是循环神经网络(RNN)的直觉——用一个不断传递的隐藏状态 h_t 逐时刻浏览序列。

RNN 家族里,DIEN 选用了 GRU(门控循环单元)。GRU 给“更新记忆”装了两个门:更新门 z_t 决定新信息进来多少、旧信息留多少;重置门 r_t 决定回忆过去时开放多少旧账。这两个门让账本既能记住久远的信息,又不会被每次输入冲成白纸。GRU 在每一时刻的计算如下:

经过这个环节,阿味的每一张小票都被“翻译”进了账本 h_t。账本是会变的——三天前的麻辣还占着几页,今天的奶盖已经在改写目录。但账本记得太多,对预测“轻食沙拉”这样一道具体菜来说,并不全是好事。于是需要下一道工序:兴趣进化层。

zt=σ(Wz⋅[ ht−1, xt ]+bz)rt=σ(Wr⋅[ ht−1, xt ]+br)h~t=tanh⁡(Wh⋅[ rt⊙ht−1, xt ]+bh)ht=(1−zt)⊙ht−1+zt⊙h~t\begin{aligned}z_t &= \sigma(W_z \cdot [\,h_{t-1},\, x_t\,] + b_z) \\r_t &= \sigma(W_r \cdot [\,h_{t-1},\, x_t\,] + b_r) \\\tilde{h}_t &= \tanh(W_h \cdot [\,r_t \odot h_{t-1},\, x_t\,] + b_h) \\h_t &= (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t\end{aligned}
这四个式子合起来就是一次“账本更新”:先算出该忘多少、该记多少,再基于“重置后的旧记忆 + 新行为”生成候选想法,最后按更新门的比例把新旧记忆混合成新的账本页。
  1. 更新门 z_t输出 0 到 1 之间的数,决定新输入对当前记忆的改写程度,数值越大,新记忆占比越高。
    zt=σ(Wz⋅[ht−1,xt]+bz)z_t = \sigma(W_z \cdot [h_{t-1}, x_t] + b_z)
  2. 重置门 r_t决定计算候选状态时,多大程度上忽略旧记忆;数值越大,越依赖过去的账目。
    rt=σ(Wr⋅[ht−1,xt]+br)r_t = \sigma(W_r \cdot [h_{t-1}, x_t] + b_r)
  3. 候选状态 \tilde{h}_t把“被重置过的旧记忆”与“新行为”融合,生成一个临时的新想法。
    h~t=tanh⁡(Wh⋅[rt⊙ht−1,xt]+bh)\tilde{h}_t = \tanh(W_h \cdot [r_t \odot h_{t-1}, x_t] + b_h)
  4. 最终状态 h_t按更新门在新旧之间取加权平均,完成一次账本的翻页。
    ht=(1−zt)⊙ht−1+zt⊙h~th_t = (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t

兴趣进化层:AUGRU 让相关记忆大声说话

老店主想判断阿味会不会点轻食沙拉。他翻账本时,绝不会让三个月前那顿麻辣火锅和昨天那杯燕麦拿铁拥有同样的音量——与“健康”有关的记忆应该更大声,无关的应该小声甚至安静。这种“按候选商品调节每段记忆的重要性”的直觉,就是兴趣进化层的核心。

兴趣进化层做两件事。第一,用候选商品 v 对每个时刻的兴趣状态 h_t 打分,算出注意力强度 a_t——它衡量“这一段历史兴趣与候选商品有多相关”。第二,设计了一个专门的门控单元 AUGRU(Attention-based GRU),让注意力分数直接参与更新门的控制:a_t 越大,候选状态流入得越多;a_t 越小,旧状态被保留得越多。相比把注意力简单乘在隐藏状态上,这种“门控级”的介入更平滑,能保留演化过程本身。

于是,账本不再是一条四平八稳的时间线,而是一条被候选菜“探照”出来的演化路径:从辣到甜的过渡、从甜到健康的渐变,都被压缩进最终状态 h_T'。老店主看见的不只是“他爱吃什么”,更是“他正在往哪个方向吃”。

at=σ(Va⋅ht+Wa⋅ev)\displaystyle a_t = \sigma(V_a \cdot h_t + W_a \cdot e_v)
ht′=(1−at⋅ut)⊙ht−1′+(at⋅ut)⊙h~t′\displaystyle h_t^{\prime} = \big(1 - a_t \cdot u_t\big) \odot h_{t-1}^{\prime} + \big(a_t \cdot u_t\big) \odot \tilde{h}_t^{\prime}
第一行算注意力,第二行是 AUGRU 的更新:注意力与更新门相乘,相当于给“写新记忆”这道门配了一个旋钮——候选越相关,旋钮开得越大,新兴趣进得来;越不相关,旋钮关得越小,旧状态稳得住。
  1. 注意力分数 a_t把历史兴趣状态与候选嵌入拼起来过一层网络,再用 sigmoid 压到 0~1,衡量关联强度。
    at=σ(Va⋅ht+Wa⋅ev)a_t = \sigma(V_a \cdot h_t + W_a \cdot e_v)
  2. 更新门 u_tAUGRU 内部 GRU 算出的常规更新门,表达“按时间自然应该更新多少”。
    ut=σ(Wu⋅[ht−1′,xt])u_t = \sigma(W_u \cdot [h_{t-1}^{\prime}, x_t])
  3. 注意力缩放门 a_t \cdot u_t注意力与更新门相乘,候选越相关,门开得越大,新信息占比越高。
    at⋅uta_t \cdot u_t
  4. 最终状态 h_t'按缩放门组合旧状态与候选状态,实现“相关记忆大声、无关记忆安静”。
    ht′=(1−atut)⊙ht−1′+atut⊙h~t′h_t^{\prime} = (1 - a_t u_t) \odot h_{t-1}^{\prime} + a_t u_t \odot \tilde{h}_t^{\prime}

辅助损失设计:用“下一顿”校准账本

账本记了半天,怎么知道记对没有?老店主有个笨但有效的办法:每次阿味点完下一道菜,就拿小票回来对账——如果账本能在点单前“猜到”他下一顿吃啥,说明记对了;猜不中,就说明账本写歪了。这个“对账”的机制,在 DIEN 里叫辅助损失(auxiliary loss)。

问题在于,兴趣抽取层的每个隐藏状态 h_t 虽然在理论上蕴含“下一步最可能发生什么”,但标准的 CTR 训练只会用最后一个状态去预测点击,中间状态拿不到任何监督,容易“写歪”。DIEN 的做法是:在训练时给每个 h_t 加一个辅助任务——用它去预测下一时刻的真实行为 i_{t+1},同时拉入若干负样本(用户没点过的商品)做对比,逼着每一页账本都尽量写实:

这就像老店主在账本的每一页都设了一个对账单,逼着每一页都尽量写实,而不是只靠最后一页圆谎。对账成本不高,却让整本账从“事后总结”变成“实时预测”,训练也因此更稳定。

Laux=−1N∑t=1N−1[log⁡σ(ht⊤eit+1)+∑k=1Klog⁡(1−σ(ht⊤enegk))]\displaystyle L_{aux} = -\frac{1}{N}\sum_{t=1}^{N-1} \Big[ \log\sigma\big(h_t^{\top} e_{i_{t+1}}\big) + \sum_{k=1}^{K}\log\big(1-\sigma\big(h_t^{\top} e_{neg_k}\big)\big) \Big]
L=Ltarget+α Laux\displaystyle L = L_{target} + \alpha \, L_{aux}
第一行算“对账偏差”:真实下一行为的得分要高,负样本的得分要低;第二行把对账任务和主任务(点击率预测)加权合并,α 控制对账占多大分量。
  1. N(序列长度)对序列中每个时刻 t 都算一笔辅助损失,最后取平均,让每一页账本都被监督。
    N=∣Su∣N = |S_u|
  2. 相似度 h_t^T e_{i_{t+1}}用 t 时刻的兴趣状态与“真实下一行为”的嵌入做点积,数值越大说明猜得越准。
    scoret=ht⊤eit+1\text{score}_t = h_t^{\top} e_{i_{t+1}}
  3. 正样本项 log σ(·)鼓励真实下一行为的得分高,让账本往“能预测未来”的方向校准。
    log⁡σ(ht⊤eit+1)\log \sigma\big(h_t^{\top} e_{i_{t+1}}\big)
  4. 负采样项随机抽 K 个用户没点过的商品当反面教材,压低它们的得分,避免模型偷懒只给高分。
    ∑k=1Klog⁡(1−σ(ht⊤enegk))\sum_{k=1}^{K} \log\big(1 - \sigma(h_t^{\top} e_{neg_k})\big)
  5. 总损失 L主任务损失加辅助损失,α 通常取 1 附近,平衡“猜点击”与“猜下一行为”两个目标。
    L=Ltarget+αLauxL = L_{target} + \alpha L_{aux}

与 DIN 对比:从快照到故事

在 DIEN 之前,DIN 已经是推荐系统圈里的明星模型。老店主可以把 DIN 理解成:把阿味所有小票摊在桌上,对着候选菜挑出相关的那几张,求一个加权平均——这本质上是一张“兴趣快照”。

DIN 用激活单元计算每个历史行为与候选商品的相似度 f(v_{i_t}, v_A),再用 softmax 归一化成权重,对所有行为向量做加权求和:

两者的差异可以归结为两点:激活单元差异——DIN 的注意力作用在“行为向量”上,DIEN 的注意力作用在“兴趣状态”上;序列建模差异——DIN 没有显式的状态演化,DIEN 用 GRU 抽取 + AUGRU 进化,把顺序变成故事线。在 Amazon 与阿里公开数据集上,DIEN 的 AUC 与 GAUC 都优于 DIN,尤其当序列里存在明显兴趣漂移时,差距更显著。快照能告诉你“他喜欢辣”,故事能告诉你“他正在从辣走向甜”——这就是 DIEN 的价值。

vU=∑t=1Tat vit,at=exp⁡(f(vit, vA))∑j=1Texp⁡(f(vij, vA))\displaystyle v_U = \sum_{t=1}^{T} a_t \, v_{i_t}, \qquad a_t = \frac{\exp\big(f(v_{i_t},\, v_A)\big)}{\sum_{j=1}^{T} \exp\big(f(v_{i_j},\, v_A)\big)}
DIN 的公式强调的是“哪些历史行为与候选相关”,它把所有相关行为按权重累加成单一向量 v_U,但完全不关心这些行为谁先谁后。DIEN 的 h_T' 则来自一条有时间方向的路径。
  1. a_t(注意力权重)softmax 归一化后的相似度分数,所有时刻的权重加起来等于 1。
    at=exp⁡(f(vit,vA))∑jexp⁡(f(vij,vA))a_t = \frac{\exp(f(v_{i_t}, v_A))}{\sum_j \exp(f(v_{i_j}, v_A))}
  2. f(激活单元)DIN 用一个小型神经网络计算“历史行为与候选商品”的匹配度,是注意力的打分器。
    f(vit,vA)f(v_{i_t}, v_A)
  3. v_U(加权求和)把所有行为向量按注意力权重累加,得到一张不含时间信息的“兴趣快照”。
    vU=∑t=1Tatvitv_U = \sum_{t=1}^{T} a_t v_{i_t}

长序列处理:上千张小票怎么读完

阿味成了熟客,一年攒了上千张小票。老店主若真的一张张默读,客人早就等急了。现实系统里,长序列带来两个麻烦:计算变贵、噪声变多。GRU 每读一个行为,就要做若干次矩阵乘法,代价随序列长度线性增长:

工业界的应对手段主要有三招。第一招是滑动窗口截断:只保留最近几十到几百条行为,避免远古记忆干扰当前判断。第二招是注意力筛选:先用候选商品对历史行为快速打分,只把相关的那一小段喂给兴趣进化层,相当于老店主只挑与“沙拉”有关的健康餐小票来读。第三招是时间分桶与采样:把行为按天或按小时分桶,每桶压缩成一条代表行为,既保留演化趋势又大幅降量。核心原则始终是:保留演化主线,丢掉无关枝节。

老店主不会把一千张小票全摊桌上,而是先按时间排好,把最近、最相关的那几十张抽出来,再顺着它们读口味变化。账本仍然完整,只是懂得取舍——这正是长序列场景下 DIEN 能优雅落地的原因。

FLOPs≈O(T×d2),T=∣Su∣\displaystyle \mathrm{FLOPs} \approx O\big(T \times d^2\big), \qquad T = |S_u|
复杂度公式想说的是:序列越长、隐藏维度越大,训练与推理的算力成本就越高。工业界不可能无限堆长序列,必须想办法“消化”它。
  1. T(序列长度)用户行为条数,是复杂度里最主要的变量。
    T=∣Su∣T = |S_u|
  2. d(隐藏维度)GRU 隐藏状态的维度,矩阵乘法发生在 d × d 量级。
    d=dim⁡(ht)d = \dim(h_t)
  3. 单步开销每个时间步要算更新门、重置门、候选状态等多个矩阵乘,代价约 d²。
    O(d2)O(d^2)
  4. 总开销T 步串行叠加,总代价随序列长度线性增长。
    O(T⋅d2)O(T \cdot d^2)

工业落地:从离线实验到线上大盘

流水线开张了。老店主不会只看“整体猜中率”就下结论,而是把顾客分成熟客、新客、甜口党、健康党,分组看猜得准不准。推荐系统里也有类似的分组评估指标——GAUC(Group AUC),它按用户分组计算 AUC 再做加权平均,避免高活跃用户的样本主导全局指标:

在工程实践里,DIEN 的落地经验可以沉淀为几条:辅助损失权重 α 通常取 1 附近,负采样数 K 取 4~8;序列长度在电商场景常见为 50~200,嵌入维度 32~64;优化器用 Adam,学习率从 1e-3 起步并做衰减。离线指标上,DIEN 相比 DIN 在 CTR 与 GAUC 上通常有零点几个百分点到 1% 左右的稳定提升,配合线上 AB 实验,转化率和用户停留时长的改善都值得期待。

老店主最终发现,预测的秘诀不是记住每一道菜,而是听懂每一次口味转弯的声音。DIEN 就是这样一套“听懂转弯”的推荐模型:用 GRU 读懂历史,用 AUGRU 聚焦演化,用辅助损失校准账本,再用工程技巧消化长序列——四招合起来,把“猜你喜欢”从刻舟求剑,变成顺水推舟。

GAUC=∑uwu⋅AUCu∑uwu\displaystyle \mathrm{GAUC} = \frac{\sum_{u} w_u \cdot \mathrm{AUC}_u}{\sum_{u} w_u}
普通 AUC 把所有人的预测混在一起算,容易被“点得多的用户”带偏;GAUC 先把每个用户单独算一遍“猜中率”,再按展示次数加权平均,更像老店主的“分组复盘”。
  1. AUC_u(单用户 AUC)同一个用户内部,“点击样本得分高于未点击样本”的概率,衡量对该用户排序的好坏。
    AUCu\mathrm{AUC}_u
  2. w_u(用户权重)通常取该用户的展示次数,活跃用户的权重大,消费少的用户也不会被淹没。
    wu=#impressionsuw_u = \#\text{impressions}_u
  3. 加权平均分子是每个用户的 AUC 乘权重后求和,分母是权重总数,得到全局分组指标。
    GAUC=∑uwu⋅AUCu∑uwu\mathrm{GAUC} = \frac{\sum_u w_u \cdot \mathrm{AUC}_u}{\sum_u w_u}

AI 应用场景

电商“猜你喜欢”的点击率预估

阶段:召回 → 排序(CTR 预估)→ 重排

解决的问题:传统模型把用户浏览记录当静态池子,用户从“看手机评测”到“搜手机壳”的兴趣迁移捕捉不到,导致推荐停留在旧品类。DIEN 用兴趣进化层把“评测 → 配件 → 外设”的迁移路径建模出来,对候选商品的点击率预估更准。

具体案例:用户近 7 天依次浏览了 iPhone 评测、无线耳机、磁吸支架,系统候选商品是“磁吸充电宝”。DIEN 的注意力门控发现最近的“配件类”兴趣正在增强,给出比 DIN 更高的点击概率,最终该商品进入首屏并产生转化。

短视频信息流的内容推荐

阶段:在线预估(实时打分)

解决的问题:短视频场景里兴趣漂移发生在几小时内——中午刷美食教程,晚上刷睡眠白噪音。静态兴趣向量会持续推荐刚刷完的重复题材,DIEN 能捕捉这种日内演化。

具体案例:某用户午间连续看做饭视频,晚间转向助眠音频。DIEN 的 GRU 抽取层逐时刻更新兴趣状态,AUGRU 根据候选“白噪音歌单”放大晚间兴趣权重,使该视频进入推荐流,完播率显著高于对照组。

广告点击率预估与冷启动

阶段:广告排序(pCTR 预估)

解决的问题:新广告没有历史点击数据,只能从用户对相近品类的渐进兴趣推断。DIEN 的辅助损失用“下一行为”监督每个时刻,让模型学会品类迁移规律,缓解新广告冷启动。

具体案例:用户从关注“露营帐篷”逐步过渡到“户外电源”,系统对候选“便携太阳能板”做预估。DIEN 捕捉到“户外装备 → 电源设备”的演化方向,给出合理的高分预估,广告投放后点击率比基线提升明显。

Python 演示

python78 行
import mathimport random  def sigmoid(x):    return 1.0 / (1.0 + math.exp(-x))  def dot(a, b):    return sum(x * y for x, y in zip(a, b))  class DienDemo:    """Simplified DIEN: GRU extraction + AUGRU evolution + aux loss"""     def __init__(self, dim=4, seed=7):        random.seed(seed)        self.dim = dim        self.wz = self._mat()        self.wr = self._mat()        self.wh = self._mat()        self.wq = [random.uniform(-0.5, 0.5) for _ in range(dim)]        self.wp = [random.uniform(-0.5, 0.5) for _ in range(dim * 2)]     def _mat(self):        d = self.dim        return [[random.uniform(-0.5, 0.5) for _ in range(d * 2)] for _ in range(d)]     def _gru(self, h, x):        d = self.dim        inp = h + x        z = [sigmoid(dot(self.wz[i], inp)) for i in range(d)]        r = [sigmoid(dot(self.wr[i], inp)) for i in range(d)]        rc = [r[i] * h[i] for i in range(d)] + x        hc = [math.tanh(dot(self.wh[i], rc)) for i in range(d)]        hn = [(1 - z[i]) * h[i] + z[i] * hc[i] for i in range(d)]        return hn, z     def evolve(self, seq, cand):        d = self.dim        h = [0.0] * d        states = []        for x in seq:            h, _ = self._gru(h, x)            states.append(list(h))        h2 = [0.0] * d        atts = []        for x, hs in zip(seq, states):            att = sigmoid(dot(self.wq, hs))            atts.append(att)            hc, z = self._gru(h2, x)            h2 = [(1 - att * z[i]) * h2[i] + att * z[i] * hc[i] for i in range(d)]        score = sigmoid(dot(self.wp, h2 + cand))        return score, atts, states, h2     def aux_loss(self, states, next_emb):        loss = 0.0        for h, nxt in zip(states, next_emb):            loss += -math.log(max(sigmoid(dot(h, nxt)), 1e-7))        return loss / len(states)  def make_seq(dim, n):    return [[random.uniform(0.0, 1.0) for _ in range(dim)] for _ in range(n)]  if __name__ == "__main__":    dim = 4    n = 6    model = DienDemo(dim)    seq = make_seq(dim, n)    cand = make_seq(dim, 1)[0]    next_emb = make_seq(dim, n)    score, atts, states, h_final = model.evolve(seq, cand)    loss = model.aux_loss(states, next_emb)    print("ctr =", round(score, 4))    print("attention =", [round(a, 3) for a in atts])    print("aux loss =", round(loss, 4))
目的:用纯 Python 实现简化版 DIEN 前向流程——GRU 抽取行为序列兴趣状态,注意力门控驱动 AUGRU 进化,并用“下一行为”计算辅助损失,演示兴趣进化推荐的核心思路。
  1. inp = h + x把旧状态与新输入拼成一个长向量,交给各个门“开会”讨论。
  2. z = [sigmoid(dot(self.wz[i], inp)) for i in range(d)]计算更新门,决定新记忆写入的比例。
  3. hn = [(1 - z[i]) * h[i] + z[i] * hc[i] for i in range(d)]GRU 核心公式:新旧账本按更新门混合。
  4. att = sigmoid(dot(self.wq, hs))用候选商品的打分向量给历史兴趣状态算注意力强度。
  5. h2 = [(1 - att * z[i]) * h2[i] + att * z[i] * hc[i] for i in range(d)]AUGRU:注意力缩放更新门,控制兴趣演化路径。
  6. score = sigmoid(dot(self.wp, h2 + cand))把进化后的兴趣与候选向量拼接,预测点击率。
  7. loss += -math.log(max(sigmoid(dot(h, nxt)), 1e-7))辅助损失:用当前状态预测下一行为,偏差越小越好。

常见误区

误区DIEN 不就是把 DIN 的历史行为过一遍 RNN,再加个注意力吗?本质还是 DIN 那套加权求和。

正解DIEN 是“GRU 抽取 + AUGRU 进化”的双层架构,它不是给 DIN 套壳,而是把“兴趣”从静态向量重新定义为动态轨迹。DIN 的注意力作用在行为向量上做加权池化,DIEN 的注意力作用在兴趣状态上并融入更新门,还配有辅助损失监督每个时刻。账本不是简单摆一排小票,而是按时间写出一本会变的故事书。

为何常见:两者都处理用户行为序列、都提到注意力,表面结构相似,初学者容易认为只是把 DIN 的输入换成 RNN 输出而已。

误区主损失才是训练主力,辅助损失只是锦上添花的小技巧,去掉也不影响大局。

正解辅助损失给每个隐藏状态提供直接梯度,防止 GRU 只学到“靠最后一步蒙答案”,是兴趣抽取质量的关键。实验表明去掉辅助损失后 DIEN 效果明显下降。它就像老店主每页设置的对账单——没有它,中间几页账本写歪了也没人发现。

为何常见:很多教程把辅助损失一句话带过,看起来像“额外加分项”,低估了它对中间状态监督的必要性。

误区AUGRU 就是先把 GRU 跑完,再把注意力当缩放系数乘到每个时刻的隐藏状态上。

正解把注意力乘到隐藏状态上更接近 AIGRU 的思路(乘在输入上);AUGRU 是把注意力分数 a_t 与更新门 u_t 相乘,从“记忆写入”层面决定新信息占比,能更平滑地保留演化轨迹。一个在门外调音量,一个在门内控流量,效果差别很大。

为何常见:注意力最常见的用法是“乘在向量上”,所以容易想当然地认为 AUGRU 也只是换个位置做缩放。

误区用户行为越多越好,全塞进模型,信息最全,推荐效果一定最好。

正解过长序列带来三类问题:远古兴趣形成噪声、计算开销线性增长、训练不稳定。工业界通常截断到最近若干条,或用注意力筛选、时间分桶压缩,只保留“演化主线”。老店主也不会把一千张小票全摊桌上,他会挑最近最相关的几十张来读。

为何常见:“数据越多越好”的一般直觉在序列建模里并不成立,历史噪音需要被刻意剔除,这一点容易被忽略。

面试题

Q1. DIEN 与 DIN 最核心的区别是什么?

单选题 · mid

  • A.DIEN 使用注意力机制而 DIN 没有
  • B.DIEN 显式建模兴趣的时序演化过程← 正确答案
  • C.DIEN 用双塔结构替代了激活单元
  • D.DIN 需要用户画像而 DIEN 不需要

答案:B。DIN 把历史行为加权求和成“快照”,不关心先后顺序;DIEN 用 GRU 抽取 + AUGRU 进化,把顺序变成演化路径。A 错在 DIN 也有注意力,C、D 是虚构差异。就像快照能看出“爱辣”,故事才能看出“从辣走向甜”。

Q2. 辅助损失在 DIEN 中起到了哪些作用?

多选题 · mid

  • A.用下一行为监督中间隐藏状态← 正确答案
  • B.让抽取层学到用户的真实兴趣← 正确答案
  • C.避免隐藏状态退化成信息瓶颈← 正确答案
  • D.直接生成最终的点击率预测

答案:ABC。辅助损失用 h_t 预测真实下一行为 i_{t+1},为每个中间状态提供梯度,防止状态退化,让抽取层学到真实兴趣。点击率预测由主任务完成,D 错误。这就像给账本每一页都设对账单,而不是只检查最后一页。

Q3. AUGRU 中的注意力门是如何生效的?

单选题 · mid

  • A.直接替换 GRU 的更新门
  • B.与更新门相乘后再控制信息流动← 正确答案
  • C.只作用在候选隐藏状态上
  • D.在输出层做加权求和

答案:B。AUGRU 把注意力分数 a_t 与更新门 u_t 相乘,得到缩放后的门控,再控制新旧信息的混合比例。它不是替换更新门,也不是简单乘在输出上。就像在“写新记忆”的门上加了一个由候选商品控制的旋钮。

Q4. 兴趣抽取层的基座单元是什么?

单选题 · easy

  • A.全连接神经网络
  • B.自注意力编码器
  • C.门控循环单元← 正确答案
  • D.卷积神经网络

答案:C。DIEN 的兴趣抽取层用 GRU(门控循环单元)逐时刻读取行为序列,输出隐藏状态序列。全连接、自注意力、卷积都不是它在序列抽取环节的基座。GRU 就像老店主逐张读小票、逐次更新账本的“翻页动作”。

Q5. GAUC 相比普通 AUC 的优势是什么?

单选题 · mid

  • A.计算速度上比普通 AUC 更快
  • B.按用户分组加权消除群体偏差← 正确答案
  • C.无需真实点击标签作为输入
  • D.能直接衡量召回率提升幅度

答案:B。GAUC 先按用户算单用户 AUC,再按展示次数加权平均,避免高活跃用户主导整体指标。它不是算得更快,也不是免标签,更不是衡量召回率。就像老店主分组复盘,不能只看整体猜中率。

Q6. 针对超长行为序列,工业界常用的处理手段有哪些?

多选题 · hard

  • A.只取最近若干行为参与建模← 正确答案
  • B.用注意力分数筛选相关行为← 正确答案
  • C.把行为按时间分桶后压缩← 正确答案
  • D.永远保留全部历史行为

答案:ABC。滑动窗口截断、注意力筛选、时间分桶采样是三类主流做法,都是为了在保留演化主线的同时降低噪声与计算量。全量保留在长序列下既不经济也易受远古噪声干扰。老店主只挑最近最相关的几十张小票来读。

Q7. DIEN 的最终兴趣表示从哪里来?

单选题 · mid

  • A.兴趣抽取层最后一个隐藏状态
  • B.兴趣进化层最后的隐藏状态向量← 正确答案
  • C.所有行为嵌入的平均池化结果
  • D.候选商品嵌入向量的本身

答案:B。DIEN 用兴趣进化层 AUGRU 的输出 h_T' 作为最终兴趣表示,它已被候选商品过滤、只保留演化主线。抽取层的 h_T 只是中间产物,平均池化是更早的粗放做法,候选嵌入本身不代表用户兴趣。

Q8. 辅助损失在训练时主要监督什么?

单选题 · easy

  • A.候选商品最终是否被点击
  • B.各时间步状态能否预测下一行为← 正确答案
  • C.用户画像特征是否准确完整
  • D.注意力分数的稀疏程度高低

答案:B。辅助损失用每个时刻的隐藏状态 h_t 去预测下一时刻的真实行为 i_{t+1},监督的是“状态能否预测未来”,而不是最终点击、画像准确性或注意力稀疏性。就像老店主拿“下一顿真实点单”回来对账。

Q9. 关于 DIEN 的训练稳定性,以下说法正确的是?

多选题 · hard

  • A.辅助损失能为中间状态提供梯度信号← 正确答案
  • B.负采样可以加速辅助损失的收敛← 正确答案
  • C.学习率过大对 GRU 训练没有影响
  • D.AUGRU 门控可抑制无关兴趣干扰← 正确答案

答案:ABD。辅助损失为每个隐藏状态提供直接梯度,负采样让正负样本对比更鲜明、收敛更快;AUGRU 的注意力门控能压低无关历史兴趣的干扰。学习率过大会让 GRU 训练震荡甚至发散,C 错误。对账和筛选,都是让模型更稳的手段。

Q10. 关于兴趣漂移的建模,DIEN 与 DIN 的本质差异是什么?

单选题 · hard

  • A.将兴趣视为不断演化的过程并建模← 正确答案
  • B.用时间衰减因子代替序列建模
  • C.移除嵌入层直接处理原始特征
  • D.用双向 GRU 替代单向 GRU

答案:A。DIEN 的本质是把兴趣当作随行为序列演化的动态过程,用 GRU 抽取、AUGRU 进化来显式建模。DIN 没有时间方向的演化建模;时间衰减、移除嵌入、双向 GRU 都不是 DIEN 的核心设计。账本的价值在于它记录的是“口味如何变”,而不只是“现在爱吃什么”。