想象一个场景:你有一个特别懂你口味的老朋友小D。每次你站在餐厅门口纠结吃什么时,小D不会死板地问“你今天想吃辣还是清淡”,而是快速翻阅脑海中的“点餐史”——你上个月连着点了三次重庆小面、上周突然开始尝试日料、昨天刚点了一份寿喜锅。他会综合这些记录,判断“你这家伙最近应该迷上了日料”,然后从容地替你点一份三文鱼刺身。 这个“翻阅点餐史并做出判断”的过程,就是推荐系统里的序列推荐。而DIN与DIEN,就是两代让小D变得更聪明的核心算法。DIN(深度兴趣网络)让小D学会用“注意力”权衡历史记录中每道菜与当前候选菜的相关性——你点过一百家店,但小D只会重点参考最对味的几家。DIEN(深度兴趣演化网络)则更进一步,让小D不只是看静态清单,而是用GRU循环神经网络追踪你口味的演变轨迹——从重辣到清淡、从火锅到日料,每一步变化都有迹可循。 本篇文章将带你彻底搞懂这两个模型:它们如何把“行为序列”变成“数字向量”、如何用注意力打分代替简单的平均、如何用辅助损失让模型记住每一步真实兴趣,以及工业界如何落地这些技术。全程用“老朋友帮你点餐”这个比喻,保证零基础也能听懂。

岗位关联

对AI从业者而言,DIN/DIEN是推荐系统领域最经典的行为序列建模方法。掌握它们,你就掌握了深度学习模型处理“用户历史行为”的底层思维,这是成为推荐算法工程师的必备技能。

目录
  1. 可视化演示
  2. 推荐痛点
  3. 向量表征
  4. 注意力机制
  5. DIN架构
  6. DIEN演化
  7. 辅助损失
  8. 长序列优化
  9. 工程落地
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示DIN注意力激活用户兴趣、DIEN的GRU兴趣演化建模及辅助损失监督训练,含序列行为与损失曲线。

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

DIN注意力:个性化兴趣激活 目标商品与历史行为计算注意力 高相关行为权重放大 v = sum_i a_i * e_i DIN通过注意力机制动态激活与目标相关的用户兴趣,实现个性化推荐。

推荐痛点

小D面临的第一个难题是:你的点餐史实在太“散”了。一年365天,你只点过一百多次外卖,分散在几十家店里,每家店只点了一两次。用推荐系统的行话说,这叫作行为数据稀疏——用户和商品的交互记录,相对于整个商品库而言,就像是夜空中零星的几颗星。而推荐的候选商品呢?可能是几百万道菜。用稀疏到可怜的“星星”去预测几百万种可能中你最爱哪一道,这几乎是大海捞针。

更麻烦的是,小D知道你的点餐顺序也很重要。你周一连吃三天重辣火锅,周三肠胃不适点了白粥,周末又去尝试了日料——这些行为序列里隐藏着“口味变化”的线索。如果只统计“你一共点了多少次火锅”“多少次日料”,就会丢掉“时间先后”这个宝贵信息。传统的协同过滤或矩阵分解,正是这样把序列拍扁成一个静态评分矩阵,导致小D只能看出“你爱吃辣”却看不出“你正在转向清淡”。

最后,几百万候选商品不可能每个都送到小D脑子里过一遍。推荐系统必须先做一轮快速筛选(召回),把候选压缩到几百个,再让模型细细排序。DIN和DIEN正是为这个“细细排序”环节而生:它们要做的,是从你稀疏、有序的行为序列中,动态提炼出“此刻”最真实的兴趣向量。

小D遇到的三座大山——稀疏、有序、海量——恰好决定了DIN和DIEN的设计思路:用嵌入向量把稀疏行为稠密化,用注意力机制尊重序列的丰富性,用工程技巧解决海量候选的计算压力。

Su={v1,v2,v3,…,vt}S_u = \{ v_1, v_2, v_3, \dots, v_t \}
这个公式表示用户 $u$ 的行为序列:按时间顺序排列的行为记录集合,每个 $v_t$ 是第 $t$ 个交互物品的嵌入向量。
  1. 行为序列的定义$S_u$ 表示用户 $u$ 的全部历史行为,按时间先后排列
    Su={v1,v2,v3,…,vt}S_u = \{ v_1, v_2, v_3, \dots, v_t \}
  2. 单个行为$v_t$ 是用户在第 $t$ 个时刻交互物品的向量表示
    vt∈Rdv_t \in \mathbb{R}^d
  3. 序列的用途推荐模型要从这组向量中提炼出用户的当前兴趣
    vU=f(Su)v_U = f(S_u)

向量表征

小D的脑子里不可能直接存放“重庆小面”这四个汉字,他需要一种更高效的“口味坐标”。比如,可以给每道菜设置几个维度的分数:辣度、麻度、油腻度、鲜度、价格接受度……这样一来,“重庆小面”就变成了一个五维向量(0.9, 0.8, 0.6, 0.7, 0.3),而“三文鱼刺身”则接近(0.0, 0.1, 0.0, 0.9, 0.8)。向量越接近,说明口味越相似。这就是Embedding(嵌入)的直觉——把离散的、互相没有度量关系的物品ID,映射到一个连续的稠密向量空间。

在推荐模型里,原始输入是物品ID的One-Hot编码:一个长度等于商品总量的向量,只有当前物品那一位是1,其余全是0。这个向量极其稀疏且有百万维。模型的第一层就是一个Embedding查找表(Lookup Table):一张形状为“商品数 × 嵌入维度”的矩阵,输入One-Hot向量,输出对应的那一行稠密向量。整个过程可以理解为“按索引取一行”,而不是做真正的矩阵乘法。

Embedding的维度通常取64或128。维度太小,装不下复杂的口味差异;维度太大,训练参数过多,容易过拟合。关键的是,Embedding不是随机初始化的——它在训练过程中会被不断调整,使得经常一起被点击的商品在向量空间里靠近,就像小D逐渐发现“重庆小面”和“红油抄手”在口味坐标里确实挨得很近。

有了Embedding,小D的点餐史就变成了一串稠密的“口味坐标点”。但问题来了:这串坐标点少则几十、多则上千,候选菜只有一道,该怎么综合它们来预判你今天爱吃什么?下一节的注意力机制,就是解决这个问题的关键。

vi=E⋅one_hot(i)v_i = E \cdot \text{one\_hot}(i)
用户点击的第 $i$ 个物品,通过嵌入矩阵 $E$ 映射成稠密向量 $v_i$。
  1. 嵌入矩阵$E$ 是大小为“物品数 × 维度”的参数矩阵,每一行是一个物品的向量
    E∈R∣V∣×dE \in \mathbb{R}^{|V| \times d}
  2. One-Hot输入$\text{one\_hot}(i)$ 是只有第 $i$ 位为1的稀疏向量
    one_hot(i)=[0,…,1,…,0]\text{one\_hot}(i) = [0,\dots,1,\dots,0]
  3. 查表映射二者相乘等价于取出 $E$ 的第 $i$ 行
    vi=E[i]v_i = E[i]

注意力机制

假设候选菜是“三文鱼刺身”,小D开始翻阅你的点餐史坐标。他很快发现:重庆小面(0.9, 0.8, 0.6, 0.7, 0.3)跟刺身的坐标八竿子打不着,而三文鱼寿司(0.1, 0.2, 0.0, 0.9, 0.7)几乎和刺身重叠。如果小D采用最简单的办法——把历史所有菜的平均坐标当作你的口味——那么重庆小面会严重拉偏判断,导致他给你推荐一碗“黑暗料理刺身面”。这正是朴素平均池化(Average Pooling)的致命伤。

DIN给出的解决方案是注意力机制:每当要判断一个候选商品时,模型先为每个历史行为计算一个“相关性分数”,再把这个分数转换成权重,最后对历史向量做加权求和。相关性分数由一个小型神经网络(在DIN中叫作Activation Unit,激活单元)算出,它同时输入候选商品向量和历史行为向量,输出一个标量。这个标量经过Softmax归一化后,就是每个历史行为对当前决策的“话语权”。

由此,模型不再一视同仁地看待所有历史行为。当候选是刺身时,“三文鱼寿司”那条记录获得巨大权重,“重庆小面”的权重趋近于零;当候选变成麻辣烫时,权重格局又会完全反转。这就是“软性”地搜索历史——不是硬选某一条,而是按相关性给所有记录分配不同程度的重要性。

这一节的小D已经学会了“看菜下碟”:候选刺身时重点参考日料记录,候选火锅时重点参考川渝记录。但DIN的注意力是对每个候选商品独立计算的,所以不同候选菜会“激活”不同的兴趣子集——这就是局部激活的思想,正是DIN的灵魂。

ej=a(vA,vj),αj=exp⁡(ej)∑k=1texp⁡(ek),vU=∑j=1tαjvje_j = a(v_A, v_j), \qquad \alpha_j = \frac{\exp(e_j)}{\sum_{k=1}^{t} \exp(e_k)}, \qquad v_U = \sum_{j=1}^{t} \alpha_j v_j
候选商品向量 $v_A$ 逐个与历史行为 $v_j$ 算相关性得分 $e_j$,经 Softmax 变成权重 $\alpha_j$,加权求和得到用户兴趣向量 $v_U$。
  1. 打分函数$a(v_A, v_j)$ 是激活单元,输入候选向量与历史行为向量的拼接(及外积特征),输出一个标量得分
    ej=a(vA,vj)e_j = a(v_A, v_j)
  2. Softmax归一化将所有得分变成非负且和为1的权重,保证权重可解释、数值稳定
    αj=exp⁡(ej)∑k=1texp⁡(ek)\alpha_j = \frac{\exp(e_j)}{\sum_{k=1}^{t} \exp(e_k)}
  3. 加权求和把历史行为向量按权重混合,得到与候选商品强相关的用户兴趣表示
    vU=∑j=1tαjvjv_U = \sum_{j=1}^{t} \alpha_j v_j

DIN架构

DIN的全称是Deep Interest Network(深度兴趣网络)。它的核心设计理念用一个词概括:局部激活。小D不会因为“你三个月前点过两次湘菜”就认定你爱吃湘菜,而是看“当前这道候选菜”与你的历史记录的匹配程度。每一次推荐决策,小D都从庞大的“点餐史”中动态提取最相关的一部分来形成当下的用户兴趣向量。

DIN的整体架构可以拆成四层。最底层是Embedding层:把用户历史行为序列中的每个物品ID、候选商品ID、以及上下文特征(如时间、位置)都映射成稠密向量。第二层是注意力池化层:候选商品向量与每个历史行为向量共同送入激活单元,算出注意力权重,加权求和得到用户兴趣向量 $v_U$。第三层是拼接层:把 $v_U$、候选商品向量 $v_A$、以及其他特征向量拼成一个长向量。最后一层是多层感知机(MLP):全连接层加PReLU激活函数,最终输出一个点击率预测分数。

这里有一个容易被忽视却关键的细节:DIN的激活单元(Activation Unit)不是简单的点积或余弦相似度,而是一个带PReLU激活的小网络,它除了拼接候选向量和历史向量,还额外拼接了二者的外积(元素级乘积)。外积捕捉的是“候选与历史在每一维度上是否同向”,比单纯拼接更能表达非线性交互关系。打个比方:候选菜与历史菜在“辣度”维度上都是高分,外积就会产生一个强信号,提示“这道菜跟你的记录在口味上是共鸣的”。

DIN通过这种候选引导的注意力池化,实现了多样兴趣并存。你可能同时爱吃火锅和日料,这两类兴趣在历史记录中都有体现。当候选是毛肚时,火锅兴趣被激活;当候选是北极贝时,日料兴趣被激活。模型不再强迫把所有兴趣压缩成一个模糊的平均向量,这是DIN相比传统方法最大的进步。

到这里,小D已经能从静态的点餐史里为每道候选菜“定制”一个口味判断了。但DIN还有一个弱点:它把点餐史当成一个无序的清单来处理。你“上个月爱火锅、这周爱日料”的演变过程,DIN是看不到的。如果说DIN是小D盯着照片墙回忆你爱吃啥,那么DIEN就是小D在脑内播放一部你口味演变的“连续剧”。

vU=∑j=1texp⁡(a(vA,vj))∑k=1texp⁡(a(vA,vk))⋅vjv_U = \sum_{j=1}^{t} \frac{\exp(a(v_A, v_j))}{\sum_{k=1}^{t} \exp(a(v_A, v_k))} \cdot v_j
DIN 完整公式:先对每个历史行为 $v_j$ 与候选 $v_A$ 计算注意力得分并归一化,再加权求和得到候选感知的用户兴趣向量。
  1. 打分函数$a(v_A, v_j)$ 是带PReLU的激活单元,输出相关性得分
    ej=a(vA,vj)=PReLU(W[ej;vj;ej⊙vj])e_j = a(v_A, v_j) = \text{PReLU}(W[e_j; v_j; e_j \odot v_j])
  2. 权重归一化Softmax 将得分转为概率分布
    αj=exp⁡(ej)∑k=1texp⁡(ek)\alpha_j = \frac{\exp(e_j)}{\sum_{k=1}^{t} \exp(e_k)}
  3. 兴趣加权池化权重与历史向量的加权和,即最终的候选感知兴趣表示
    vU=∑j=1tαjvjv_U = \sum_{j=1}^{t} \alpha_j v_j

DIEN演化

DIEN(Deep Interest Evolution Network,深度兴趣演化网络)的出发点非常直接:兴趣不是静止的,而是随时间演化的。小D回忆你的点餐史时,不应该只看到一张张孤立的照片,而应该看到一条连贯的时间线——你如何从“无辣不欢”慢慢变成“日料爱好者”。要建模这种演化过程,自然需要引入循环神经网络(RNN),而DIEN选择的是它的经典变体GRU(门控循环单元)。

GRU的核心思想是:维护一个隐藏状态向量 $h_t$,它像小D脑中的“当前口味快照”,每读入一个新的行为向量 $e_t$,就更新一次快照。更新时有两个“门”在起关键作用:更新门 $z_t$ 控制新旧信息的混合比例,重置门 $r_t$ 控制过去记忆被遗忘的程度。这样,GRU既能记住“你长期爱吃辣”的稳定偏好,又能快速捕捉“最近开始吃日料”的新变化。遗忘门与更新门的默契配合,让长距离依赖不至于像普通RNN那样梯度消失。

但DIEN没有直接拿GRU的输出当用户兴趣,这中间还有一层精妙的设计。原始GRU的输出 $h_t$ 是所有历史行为的平铺编码,没有针对当前候选商品做筛选。DIEN增加了一个兴趣演化层:把GRU的每个时刻输出 $h_t$ 与候选商品向量 $v_A$ 一起送给注意力打分,做一次兴趣演化的“加权整合”。换句话说,DIEN用GRU建模“过程”,再用注意力根据候选商品从过程中提炼“此刻最相关的演化结果”。

由此,DIEN输出的不再是“一张静态合影”,而是一部“兴趣演化的纪录片”。但这里有一个隐患:小D回忆历史时的每个时间步快照,是否真的对应了你当时的真实口味?如果模型只是机械地把行为序列塞进GRU,没有人在中途纠正它的记忆方向,那么误差会一路累积。下一节的辅助损失,正是给这个演化过程装上的“路标”。

ht=GRU(ht−1,et)h_t = \text{GRU}(h_{t-1}, e_t)
第 $t$ 个时刻的兴趣隐含状态 $h_t$,由上一时刻状态 $h_{t-1}$ 和当前行为向量 $e_t$ 经 GRU 计算得出。
  1. 更新门决定过去状态保留多少、新输入采纳多少
    zt=σ(Wz⋅[ht−1,et])z_t = \sigma(W_z \cdot [h_{t-1}, e_t])
  2. 重置门决定过去状态有多少被用来计算候选新状态
    rt=σ(Wr⋅[ht−1,et])r_t = \sigma(W_r \cdot [h_{t-1}, e_t])
  3. 候选状态用重置后的旧状态和新输入合成“候选快照”
    h~t=tanh⁡(Wh⋅[rt⊙ht−1,et])\tilde{h}_t = \tanh(W_h \cdot [r_t \odot h_{t-1}, e_t])
  4. 状态更新在旧状态与新候选之间做插值,完成一次演化
    ht=(1−zt)⊙ht−1+zt⊙h~th_t = (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t

辅助损失

DIEN的训练过程遇到一个独特困难:GRU的中间隐藏状态 $h_t$ 没有直接的监督信号。主损失(点击率损失)只监督最后一刻的输出,相当于小D背诵一整本故事书,老师只检查最后一个字有没有背对——中间错多少都不管。结果就是GRU的中间记忆很可能学得不准,影响最终效果。

DIEN的解决方案叫辅助损失(Auxiliary Loss):在GRU的每个时间步 $t$,利用用户的真实行为作为“参考答案”,来修正隐藏状态。具体做法是:用第 $t$ 步的隐藏状态 $h_t$ 去预测第 $t+1$ 步用户会点击什么商品。预测对了,说明 $h_t$ 确实捕捉到了你当时的兴趣;预测错了,说明记忆跑偏,模型会修正。同时,为了增强判别力,还要从全体商品中随机抽取一些用户没有点击过的商品(负样本)参与计算,让模型学会区分“下一步会点的”与“不会点的”。

这个辅助损失乘以一个权重系数 $\alpha$ (通常取0.1~0.5),与主损失相加构成联合训练目标。它的妙处在于不需要额外标注成本——正样本就是行为序列里的下一个真实点击,负样本就是随机采样的其他物品。这正是工业界最爱的“免费午餐”。不过要注意:辅助损失在推理阶段不再参与计算,只作为训练时的“导航信号”帮助GRU学出更准确的中间兴趣状态。

有了辅助损失,小D回顾点餐史时就不只是机械地背故事,而是每回忆一步就会拿“你下一顿实际吃了什么”来验证自己的记忆是否准确。这个设计让兴趣演化的每一步都有了“锚点”。接下来,小D将面临更加现实的考验——当你的点餐史长达几千条时,他的CPU可扛不住。

Laux=−1N(∑(ht,et+1)log⁡σ(htTet+1)+∑(ht,e^t+1)log⁡(1−σ(htTe^t+1)))L_{\text{aux}} = -\frac{1}{N} \left( \sum_{(h_t, e_{t+1})} \log \sigma(h_t^T e_{t+1}) + \sum_{(h_t, \hat{e}_{t+1})} \log(1 - \sigma(h_t^T \hat{e}_{t+1})) \right)
辅助损失对比:用隐藏状态 $h_t$ 分别与真实下一行为 $e_{t+1}$(正样本)和随机负样本 $\hat{e}_{t+1}$ 计算内积,经 sigmoid 后求交叉熵。
  1. 正样本项$h_t$ 与真实下一行为的内积,经 sigmoid 后希望趋近1
    log⁡σ(htTet+1)\log \sigma(h_t^T e_{t+1})
  2. 负样本项$h_t$ 与随机负样本的内积,希望趋近0
    log⁡(1−σ(htTe^t+1))\log(1 - \sigma(h_t^T \hat{e}_{t+1}))
  3. 整体联合与主损失 $L_{\text{target}}$ 加权相加
    L=Ltarget+α⋅LauxL = L_{\text{target}} + \alpha \cdot L_{\text{aux}}

长序列优化

理论上,DIN和DIEN可以处理任意长度的行为序列,但现实中算力是有限的。假设用户有3000条历史行为,每条行为都要与候选商品做一次注意力计算,而候选有500个——这意味着一场推荐就要做150万次激活单元运算。这对线上服务来说是难以承受的。小D得想办法在不动摇判断质量的前提下,把“翻阅点餐史”的速度提上去。

工业界常用的三大招数第一招是分段截断:保留最近一段时间内的行为(比如最近15天),超过时限的旧行为直接丢弃。这听起来粗暴,但很有效——毕竟你对半个月前那顿外卖的记忆,早就像水中的墨迹一样淡去了。第二招是哈希技巧(Hash Trick):把长达百万的商品ID通过哈希函数映射到一个小得多的桶空间(比如10万个桶),一来大幅压缩嵌入矩阵的规模,二来高频商品与低频商品在哈希碰撞时还能共享部分统计信息。代价是理论上存在哈希冲突,但工程实践中利远大于弊。

第三招是负采样提速。DIEN的辅助损失需要一个正样本和若干个负样本。如果负样本从全部商品中随机抽取,理论上没问题,但采样效率低。实际工程中会采用按热度加权的负采样:越热门的商品越有可能被当作负样本。这样模型不仅能学会“喜欢什么”,更能学到“不喜欢那些大家都喜欢的庸俗货”,排序的区分度会更好。

经过这三招,小D面对上千条点餐史也能在一眨眼的功夫内完成判断。截断让计算量可控,哈希让内存缩水,负采样让辅助损失的训练既快又稳。至此,模型的“内功”已经修炼完毕,剩下的就是如何把它装进真实的推荐系统流水线中。

P(neg=i)∝count(i)0.75P(\text{neg} = i) \propto \text{count}(i)^{0.75}
负采样概率公式:商品 $i$ 被抽为负样本的概率与其热度(点击次数)的0.75次方成正比。
  1. 热度计数count$(i)$ 是商品 $i$ 在全体用户中的出现次数,反映其热度
    count(i)\text{count}(i)
  2. 幂次平滑0.75次方削弱头部热门的采样概率,避免热门商品被过度抽样
    count(i)0.75\text{count}(i)^{0.75}
  3. 归一化概率按比例归一化,作为负采样的概率分布
    P(neg=i)=count(i)0.75∑jcount(j)0.75P(\text{neg} = i) = \frac{\text{count}(i)^{0.75}}{\sum_j \text{count}(j)^{0.75}}

工程落地

当小D终于掌握DIN和DIEN的全部本领后,他要面对的终极考验是:如何把这套本事变成一条稳定的、低延迟的、可评估的工业级流水线。现代推荐系统通常采用召回—粗排—精排—重排的四级漏斗结构。DIN/DIEN主要应用在排序阶段:从召回模块送来的几百个候选中,精确预测用户对每个候选的点击概率,然后按概率从高到低排序。

离线评估阶段,最常用的指标是AUC(ROC曲线下面积)。AUC可以粗略理解为:随机抽一个正样本和一个负样本,模型把正样本排在负样本前面的概率。AUC越大,排序能力越强。但在推荐场景中,不同用户的行为习惯差异极大:一个活跃用户可能贡献上千条样本,一个沉默用户只有几条。如果直接算全局AUC,活跃用户会主导结果。

于是有了GAUC(Group AUC):先按用户分组,各自算AUC,再按每个用户的行为次数加权平均。GAUC = 0.72 就意味着“平均来看,对每个用户,正样本排在负样本前面的概率是72%”。这个指标更公平地反映了模型在不同用户身上的表现。最终,模型还会通过在线A/B测试验证:在实际流量中,新模型是否真的提升了点击率或转化率。

至此,小D从“懂口味的老朋友”进化为“标准化的推荐引擎”:Embedding层消化原始行为,DIN/DIEN提炼候选感知的兴趣,辅助损失护航兴趣演化的每一步,工程优化保证百万级用户、千万级商品的实时服务。这条从感知到落地的完整链条,也正是推荐算法工程师日常工作的核心。

---

GAUC=∑uwu⋅AUCu∑uwu\text{GAUC} = \frac{\sum_{u} w_u \cdot \text{AUC}_u}{\sum_{u} w_u}
GAUC 是用户粒度 AUC 的加权平均,权重 $w_u$ 通常取用户 $u$ 的样本数。
  1. 用户维度AUC对每个用户 $u$ 单独计算其样本集合上的 AUC
    AUCu\text{AUC}_u
  2. 样本数权重$w_u$ 表示用户 $u$ 的行为数量,活跃用户权重更大
    wu=impressionsuw_u = \text{impressions}_u
  3. 加权平均所有用户AUC按权重求平均,得到全局评估指标
    GAUC=∑uwuAUCu∑uwu\text{GAUC} = \frac{\sum_{u} w_u \text{AUC}_u}{\sum_{u} w_u}

AI 应用场景

电商平台商品推荐

阶段:精排阶段用户行为建模

解决的问题:传统方法把用户所有购买/点击记录平均池化成一个静态向量,无法区分“最近想买婴儿车”和“以前爱买运动鞋”的差异。DIN引入候选商品引导的注意力,推荐奶粉时自动放大母婴类历史行为的权重;DIEN通过GRU追踪用户从“备孕”到“育儿”的兴趣演化,准确预测下一阶段的消费需求。

具体案例:某母婴电商平台,用户前三个月浏览婴儿推车、奶瓶,近两周搜索幼儿园书包。DIN/DIEN联合模型判断其兴趣已从“婴儿期”演化到“学龄前”,优先推荐书包、文具类目商品,点击率提升12%。

短视频信息流排序

阶段:精排模型CTR预估

解决的问题:短视频场景中用户观看行为极其稠密,一天产生数百条行为。传统RNN难以捕捉“用户兴趣漂移”,且长序列计算开销大。DIN高效处理长序列多样性兴趣,DIEN建模用户从“萌宠”到“美食教程”的观看偏好迁移,配合长序列截断和负采样优化,在数百毫秒内完成排序。

具体案例:某短视频APP在精排阶段部署DIEN,用用户最近50条观看记录建模兴趣演化,配合辅助损失预测“下一条想看什么”。灰度实验显示,人均观看时长提升8%,有效改善了“信息茧房”问题。

广告点击率预估

阶段:广告系统CTR/CVR预估

解决的问题:广告场景对延迟极其敏感,且用户行为序列可能长达数月。DIN的注意力机制能快速定位与当前广告最相关的历史行为,辅助损失通过“下一行为预测”增强模型对用户即时意图的捕捉,让广告排序更贴合用户当前的兴趣状态。

具体案例:某信息流广告平台在排序模型中加入DIN,利用用户近30天的广告点击序列作为行为输入。相比Base模型,CTR提升1.9个百分点,广告收入提高6%;同时通过哈希技巧压缩Embedding,模型体积减少40%,推理延迟维持在10ms以内。

Python 演示

python69 行
import numpy as np  def sigmoid(x):    return 1.0 / (1.0 + np.exp(-np.clip(x, -20, 20)))  def gru_step(x_t, h_prev, Wz, Wr, Wh):    concat = np.concatenate([h_prev, x_t])    z = sigmoid(Wz @ concat)    r = sigmoid(Wr @ concat)    h_tilde = np.tanh(Wh @ np.concatenate([r * h_prev, x_t]))    return (1.0 - z) * h_prev + z * h_tilde  def attention_score(cand_vec, hist_vec, W_a):    concat = np.concatenate([cand_vec, hist_vec, cand_vec * hist_vec])    return float(W_a @ np.tanh(concat))  def din_interest(cand_vec, hist_seq, W_a):    scores = np.array([attention_score(cand_vec, h, W_a) for h in hist_seq])    exp_scores = np.exp(scores - scores.max())    weights = exp_scores / exp_scores.sum()    return weights @ hist_seq, weights  def aux_loss(hidden_states, next_embs, rng, n_neg=3):    loss = 0.0    n = len(hidden_states)    for t in range(n):        pos_logit = sigmoid(hidden_states[t] @ next_embs[t])        loss += -np.log(pos_logit + 1e-8)        for _ in range(n_neg):            neg_vec = rng.randn(next_embs.shape[1])            neg_logit = sigmoid(hidden_states[t] @ neg_vec)            loss += -np.log(1.0 - neg_logit + 1e-8)    return loss / (n * (1 + n_neg))  def main():    rng = np.random.default_rng(42)    emb_dim = 8    seq_len = 6    cand_vec = rng.normal(size=emb_dim)    hist_seq = rng.normal(size=(seq_len, emb_dim))    W_a = rng.normal(size=emb_dim * 3)     user_vec, attn_w = din_interest(cand_vec, hist_seq, W_a)    print("注意力权重:", np.round(attn_w, 3))    print("加权用户兴趣向量:", np.round(user_vec, 3))     Wz = rng.normal(size=(emb_dim, emb_dim * 2))    Wr = rng.normal(size=(emb_dim, emb_dim * 2))    Wh = rng.normal(size=(emb_dim, emb_dim * 2))    h = np.zeros(emb_dim)    states = []    for t in range(seq_len):        h = gru_step(hist_seq[t], h, Wz, Wr, Wh)        states.append(h)    states = np.array(states)     next_embs = rng.normal(size=(seq_len - 1, emb_dim))    loss = aux_loss(states[:-1], next_embs, rng)    print("辅助损失:", round(loss, 4))  if __name__ == "__main__":    main()
目的:用纯NumPy模拟DIN的注意力池化和DIEN的GRU兴趣演化,演示两者的前向计算过程。
  1. attention_score(cand_vec, hist_vec, W_a)对应DIN的激活单元,拼接候选、历史及外积特征后打分。
  2. din_interest()用Softmax权重对历史行为加权聚合,得到候选感知的用户兴趣向量。
  3. gru_step()实现DIEN的GRU单元,通过更新门和重置门控制兴趣状态的演化。
  4. aux_loss()计算DIEN辅助损失:用当前隐藏状态预测下一行为,并与负样本对比。
  5. main()随机初始化一张“点餐史”,完整演算从兴趣向量到辅助损失的流程。

常见误区

误区反正用户向量就是把他所有点过的菜向量加起来除以数量,平均之后不就是一个大概的兴趣吗?为什么要搞注意力这么复杂的东西?

正解平均池化假设所有历史行为对当前决策同等重要,但现实中“三文鱼寿司”和“重庆小面”绝不应当拥有相同话语权。DIN用注意力机制让与当前候选相关的行为获得高权重,无关行为自动弱化。平均池化就像小D把你过去十年吃过的所有菜品搅成一锅粥,而注意力让他能精准捞出此刻最对味的那几道菜。

为何常见:初学者接触Embedding后,最容易想到的聚合方式就是求平均,因为它最简单、直观,而且在小规模实验中看似可用,掩盖了它在兴趣多样性场景下的缺陷。

误区DIEN的结构不就是DIN里的历史行为Embedding换成GRU的输出吗?那直接拿GRU最后一个隐状态不就行了吗?

正解如果只取GRU最后一个隐状态,相当于对全部历史行为做无差别的时序压缩——丢失了候选商品的引导信号。DIEN在GRU之上还加了一个兴趣演化层,用候选商品对每一步的隐状态做注意力加权,真正输出的是“候选相关的演化兴趣”。差之毫厘,效果谬以千里。

为何常见:很多文章把DIEN简化描述为"DIN + GRU",导致初学者误以为只是换了个编码器,忽略了演化层和辅助损失的联动设计。

误区既然模型输出了注意力权重,那我直接看权重最大的历史行为,就知道用户最偏好什么了,这还可以用来做可解释性分析。

正解注意力权重只是中间产物,服务于“形成一个更好的用户向量”这个最终目标。它受到候选商品的强烈影响——同一个用户,候选是火锅和候选是日料时,注意力分布完全不同。因此单看一组权重无法代表用户的稳定偏好,用来做全局解释非常危险。

为何常见:注意力机制在NLP领域常被当作解释工具,人们习惯性地把它迁移到推荐场景,忽略了推荐中注意力是“候选感知的、动态变化的”这一特殊性。

误区用户行为太长,直接把最近的50条留下,更早的全扔掉,这不就完事了吗?反正老数据也没用。

正解直接截断会丢失长期稳定的偏好信息,比如“三年前就开始买某品牌”这类信号。工业界真正做法是分层处理:短期序列进GRU建模演化,长期序列做统计特征或负采样压缩。截断只是众多手段之一,且截断窗口需要根据业务反复调优,不能一刀切。

为何常见:截断在代码实现上太简单了,且确实能降低计算量,所以很多实践者第一反应就是它,忽视了它对长尾兴趣的伤害。

误区辅助损失是DIEN为了训练GRU才设计的,DIN没有时序建模,根本用不上。是不是所有注意力模型都该加辅助损失?

正解辅助损失的本质是“用行为序列的内部结构提供额外监督”。虽然原始论文在DIEN中引入它,但同类思想可以迁移到任何使用行为序列的推荐模型中——比如用当前Embedding预测下一行为,增强Embedding对兴趣演化的表达能力。真正值得注意的是:辅助损失增加了训练计算量,是否值得要在实验中验证,不能盲目堆叠。

为何常见:初学者容易把某个模型的特定设计误认为“仅限该模型”,缺乏对损失函数背后的通用原理的思考。

面试题

Q1. DIN中注意力机制的核心作用是什么?

单选题 · easy

  • A.对所有历史行为一视同仁地求平均
  • B.按与候选商品的相关性加权聚合历史行为← 正确答案
  • C.对候选商品做数据增强以扩充样本
  • D.将用户行为序列压缩成固定长度的编码

答案:B。DIN的关键创新是让候选商品引导注意力权重分配:与候选相关的历史行为获得更高权重,实现“局部激活”的兴趣池化。A是平均池化,C/D都不是注意力的作用。

Q2. 用户行为序列在输入模型前,通常先经过哪一层处理?

单选题 · easy

  • A.Softmax层
  • B.Embedding层← 正确答案
  • C.池化层
  • D.BN层

答案:B。物品ID是离散稀疏的One-Hot向量,必须经过Embedding层映射为稠密向量才能输入后续网络。Softmax用于输出层,池化和BN是中间层的可选操作。

Q3. 关于DIN中Activation Unit(激活单元)的描述,正确的有哪些?

多选题 · mid

  • A.输入包括候选商品向量与历史行为向量的拼接← 正确答案
  • B.输入中还包括二者的外积特征← 正确答案
  • C.输出是一个与候选商品同维度的向量
  • D.激活函数通常选用PReLU← 正确答案

答案:ABD。激活单元输入候选与历史向量的拼接及外积,输出是标量相关性得分(C错误),激活函数用PReLU而非ReLU,可以避免负区间神经元死亡。

Q4. DIN相比传统平均池化最主要的优势是什么?

单选题 · mid

  • A.训练速度更快,参数量更少
  • B.能建模用户兴趣的时序演化过程
  • C.针对不同候选商品动态提取不同的用户兴趣← 正确答案
  • D.不需要用户历史行为数据也能训练

答案:C。DIN的注意力是“候选感知”的,同一用户面对不同候选会激活不同的兴趣子集,解决了平均池化把多样兴趣压成一锅粥的问题。B是DIEN的特点。

Q5. DIEN中GRU模块的主要职责是什么?

单选题 · mid

  • A.对候选商品做特征交叉
  • B.对用户行为序列做时序编码,捕捉兴趣演化← 正确答案
  • C.对注意力权重做归一化处理
  • D.对Embedding向量做降维

答案:B。GRU是循环神经网络变体,按时间步读取行为序列并更新隐藏状态,从而刻画兴趣的漂移轨迹。DIEN在GRU之上还有兴趣演化层,但GRU本身负责时序编码。

Q6. 关于DIEN辅助损失,下列说法正确的有?

多选题 · hard

  • A.利用第t步隐状态预测第t+1步的真实行为← 正确答案
  • B.需要额外的人工标注数据
  • C.负样本通常从全体物品中按热度采样← 正确答案
  • D.推理阶段仍然参与计算

答案:AC。辅助损失的正样本是序列中的下一真实行为,负样本按热度采样,无需人工标注(B错误);它只在训练时提供监督信号,推理时被丢弃(D错误)。

Q7. 工业界处理超长用户行为序列时,通常不会采用哪项措施?

单选题 · hard

  • A.按时间窗截断最近N条行为
  • B.使用哈希技巧压缩Embedding表规模
  • C.将全部历史行为完整输入DIN,不设上限← 正确答案
  • D.对负样本做热度加权采样

答案:C。全部历史行为完整输入会导致注意力计算的复杂度随序列长度线性增长,线上延迟不可接受。工业界必做截断、哈希压缩等优化,C描述的方案在理论论文中常见但工程上不可行。

Q8. 在DIEN的GRU更新公式中,“更新门”(update gate)的作用是什么?

单选题 · mid

  • A.决定候选商品是否参与注意力计算
  • B.控制新旧信息在隐藏状态中的混合比例← 正确答案
  • C.决定历史行为是否进入Embedding层
  • D.控制辅助损失的反向传播路径

答案:B。GRU的更新门 $z_t$ 取值在0-1之间,$z_t$ 接近1时保留更多新信息,接近0时保留更多旧状态。重置门控制遗忘,但混合比例的决策者是更新门。

Q9. GAUC相比全局AUC的优势体现在哪些方面?

多选题 · mid

  • A.按用户分组计算AUC后加权平均← 正确答案
  • B.避免活跃用户样本主导评估结果← 正确答案
  • C.能直接反映模型预测的绝对准确率
  • D.更公平地衡量不同用户上的排序效果← 正确答案

答案:ABD。GAUC先对每个用户算AUC,再用样本数加权平均,让评估更关注用户粒度的排序能力,避免头部用户主导。AUC本身衡量的是相对排序而非绝对准确率,C错误。

Q10. 下列哪个是DIN/DIEN在推荐系统中的典型应用位置?

单选题 · easy

  • A.召回阶段
  • B.精排/排序阶段← 正确答案
  • C.数据处理阶段
  • D.特征存储阶段

答案:B。DIN/DIEN是精排模型的核心:输入候选集与用户行为序列,输出点击率预测分数用于排序。召回阶段通常使用双塔、向量检索等轻量方法,而非完整DIN/DIEN。