京东 搜索推荐算法 一面

一、交叉熵损失的推导(贝叶斯)与理解

1.1. 交叉熵损失的定义

设:

  • 真实标签为 one-hot 编码:y=(y1,y2,...,yC)\boldsymbol{y} = (y_1, y_2, ..., y_C)y=(y1​,y2​,...,yC​)
  • 模型预测输出为概率分布:y^=(y^1,y^2,...,y^C)\hat{\boldsymbol{y}} = (\hat{y}_1, \hat{y}_2, ..., \hat{y}_C)y^​=(y^​1​,y^​2​,...,y^​C​)
    则交叉熵损失函数定义为:
    LCE(y,y^)=−∑i=1Cyilog⁡y^i \mathcal{L}_{CE}(\boldsymbol{y}, \hat{\boldsymbol{y}}) = - \sum_{i=1}^C y_i \log \hat{y}_i LCE​(y,y^​)=−i=1∑C​yi​logy^​i​
    由于 one-hot 中只有 yk=1y_k = 1yk​=1,其余为 0,所以上式可简化为:
    LCE=−log⁡y^k \mathcal{L}_{CE} = -\log \hat{y}_k LCE​=−logy^​k​
    其中 kkk 是真实标签对应的类别索引。

1.2. 从贝叶斯角度推导交叉熵

最大似然估计(MLE)

分类模型将类别预测为一个条件概率分布:
P(y=k∣x;θ)=y^k P(y = k \mid \boldsymbol{x}; \theta) = \hat{y}_k P(y=k∣x;θ)=y^​k​
最大似然目标:
LMLE(θ)=∏n=1NP(y(n)∣x(n);θ) \mathcal{L}_{MLE}(\theta) = \prod_{n=1}^N P(y^{(n)} \mid \boldsymbol{x}^{(n)}; \theta) LMLE​(θ)=n=1∏N​P(y(n)∣x(n);θ)
取负对数:
log⁡LMLE(θ)=−∑n=1Nlog⁡P(y(n)∣x(n);θ)\log \mathcal{L}_{MLE}(\theta) = - \sum_{n=1}^N \log P(y^{(n)} \mid \boldsymbol{x}^{(n)}; \theta) logLMLE​(θ)=−n=1∑N​logP(y(n)∣x(n);θ)
若用 softmax 输出预测概率,上式等价于:
LCE=−∑n=1Nlog⁡y^y(n)(n) \mathcal{L}_{CE} = - \sum_{n=1}^N \log \hat{y}^{(n)}_{y^{(n)}} LCE​=−n=1∑N​logy^​y(n)(n)​
即为交叉熵损失。

贝叶斯决策理论视角

  • 真实标签 yyy 服从真实分布 PPP
  • 模型预测的是 QQQ,希望 QQQ 趋近于 PPP
  • 信息论中的交叉熵:
    H(P,Q)=−∑iP(i)log⁡Q(i) H(P, Q) = -\sum_i P(i) \log Q(i) H(P,Q)=−i∑​P(i)logQ(i)
  • 若 PPP 为 one-hot,则交叉熵就是:
    H(P,Q)=−log⁡Q(k) H(P, Q) = - \log Q(k) H(P,Q)=−logQ(k)
  • 最小化交叉熵,即最大化正确类别的后验概率 P(y∣x)P(y \mid x)P(y∣x),这与贝叶斯分类器的目标一致。

1.3. 交叉熵与 KL 散度关系

KL 散度定义为:
KL(P∥Q)=∑iP(i)log⁡P(i)Q(i)=H(P,Q)−H(P) \text{KL}(P \| Q) = \sum_i P(i) \log \frac{P(i)}{Q(i)} = H(P, Q) - H(P) KL(P∥Q)=i∑​P(i)logQ(i)P(i)​=H(P,Q)−H(P)

  • 其中 H(P)H(P)H(P) 为常数
  • 所以 最小化交叉熵 H(P,Q)H(P, Q)H(P,Q) 等价于最小化 KL 散度

二、L1 与 L2 范数的假设分布是什么?

2.1 结论

范数类型损失项形式对应的先验分布特性说明
L1 范数λ∣w∣1\lambda |\boldsymbol{w}|_1λ∣w∣1​拉普拉斯分布(Laplace)促进稀疏性(特征选择),零点密集
L2 范数λ∣w∣22\lambda |\boldsymbol{w}|_2^2λ∣w∣22​高斯分布(Gaussian)促进平滑解,特征保留但值趋小

2.2. L1 范数对应拉普拉斯分布(Laplace Prior)

损失函数形式

L1 正则化(Lasso Regression)形式为:
LLasso=∑i=1N(yi−f(xi))2+λ∑j∣wj∣ \mathcal{L}_{\text{Lasso}} = \sum_{i=1}^N (y_i - f(x_i))^2 + \lambda \sum_j |w_j| LLasso​=i=1∑N​(yi​−f(xi​))2+λj∑​∣wj​∣

2.3. L2 范数对应高斯分布(Gaussian Prior)

损失函数形式

L2 正则化(Ridge Regression)形式为:
LRidge=∑i=1N(yi−f(xi))2+λ∑jwj2 \mathcal{L}_{\text{Ridge}} = \sum_{i=1}^N (y_i - f(x_i))^2 + \lambda \sum_j w_j^2 LRidge​=i=1∑N​(yi​−f(xi​))2+λj∑​wj2​

2.4. 图像直观比较

高斯分布(L2):
      ___
     /   \
    /     \
---         ---   → 尾部衰减快,整体光滑,产生稠密参数

拉普拉斯分布(L1):
     /\
    /  \
---    ---    → 尖峰密集,激励参数为 0,产生稀疏性

三、GBDT中的梯度有何意义,与XGBoost的区别是什么

3.1. GBDT中的梯度意义

GBDT(Gradient Boosting Decision Tree,梯度提升决策树)是一种加法模型,通过不断地拟合残差来逼近目标函数。其“梯度”主要体现在以下几个方面:

  • 在每一轮迭代中,GBDT以当前模型的预测结果与真实值之间的残差为新一轮的学习目标。
  • 这个“残差”实际上是损失函数对当前模型输出的一阶导数(负梯度),即:

3.2. XGBoost与GBDT的区别

3.2.1. 二阶导数的使用

GBDT与XGBoost都是通过拟合这个负梯度(残差)来更新模型,即相当于用决策树去拟合损失函数的下降方向。

  • GBDT:只使用一阶导数(即负梯度)来拟合残差。
  • XGBoost:同时使用一阶导数和二阶导数,进行更精确的损失函数泰勒展开,提高收敛速度和精度。

3.2.2. 正则化的使用

  • GBDT:没有显式正则化项。
  • XGBoost:在目标函数中加入了正则化项(树的复杂度),控制过拟合。

四、Attention机制介绍与时间复杂度分析

4.1. 基本思想

对于一个输入序列,Attention机制将其映射为三个矩阵:

  • Q(Query):查询向量
  • K(Key):键向量
  • V(Value):值向量
    然后通过如下公式计算注意力权重和输出:
    Attention(Q, K, V) = softmax( (QK^T) / √d_k ) V
    其中:
  • Q ∈ ℝ^{n×d}
  • K ∈ ℝ^{n×d}
  • V ∈ ℝ^{n×d}
  • n 是序列长度,d 是特征维度
  • √d_k 是缩放因子,用于避免梯度爆炸

4.2. 多头注意力(Multi-head Attention)的时间复杂度分析

为了增强模型表达能力,Transformer 使用了多头注意力,将输入分别映射到多个子空间后独立计算 Attention,最后将各头的输出拼接起来。

4.2.1. Q、K、V 的线性变换

假设:

  • 输入矩阵为 X ∈ ℝ^{n×d_model}
  • Wq,Wk,WvW_q, W_k, W_vWq​,Wk​,Wv​ 分别是三个线性变换矩阵,维度为 d_modelmodelmodel×dkd_kdk​
    那么计算:
  • Q=XWqQ = XW_qQ=XWq​,时间复杂度:O(n·d_modelmodelmodel·dkd_kdk​)
  • K=XWkK = XW_kK=XWk​,时间复杂度:O(n·d_modelmodelmodel·dkd_kdk​)
  • V=XWvV = XW_vV=XWv​,时间复杂度:O(n·d_modelmodelmodel·dvd_vdv​)
a. 计算 QKTQK^TQKT
  • Q ∈ ℝ^{n×d_k},K ∈ ℝ^{n×d_k}
  • QK^T 的结果是 n×n 矩阵
  • 时间复杂度:O(n²·d_k)
b. softmax 和乘以 V
  • softmax(QK^T):O(n²)
  • softmax(QK^T) × V:O(n²·d_v)
c. 总体时间复杂度

O(n²·d_modelmodelmodel)

五、介绍DQN(各种DQN变种的意义)

DQN(Deep Q-Network)是将深度神经网络应用于强化学习Q-learning的一种方法,用于在高维状态空间中逼近Q函数。
核心思想:使用一个深度神经网络 Q(s, a; θ) 来逼近最优动作价值函数 Q*,并通过不断地采样 (s, a, r, s') 来更新网络参数。

  • 输入:状态(如图像)
  • 输出:每个动作对应的 Q 值
  • 更新目标:
    y=r+γ∗maxa′Q(s′,a′;θ−)y = r + γ * max_{a'} Q(s', a'; θ⁻)y=r+γ∗maxa′​Q(s′,a′;θ−)

5.1. Double DQN(双重DQN)

背景问题:

DQN 中使用 max Q(s', a') 容易造成 过估计(Overestimation)。

改进方式:

将动作选择与动作评估解耦:y=r+γ∗Q(s′,argmaxaQ(s′,a;θ),θ−)y = r + γ * Q(s', argmax_a Q(s', a; θ), θ⁻)y=r+γ∗Q(s′,argmaxa​Q(s′,a;θ),θ−)

  • 使用主网络选择动作(argmax)
  • 使用目标网络评估该动作的 Q 值

作用:

减少 Q 值过估计,提高学习稳定性和性能。

5.2. Dueling DQN(决斗DQN)

背景问题:

在某些状态下,动作选择并不重要(如等待状态)。

改进方式:

将 Q 值拆分为状态值 V(s) 与动作优势 A(s, a):Q(s,a)=V(s)+A(s,a)−meanaA(s,a)Q(s, a) = V(s) + A(s, a) - mean_a A(s, a)Q(s,a)=V(s)+A(s,a)−meana​A(s,a)

  • V(s):表示当前状态本身的价值
  • A(s, a):表示某个动作相对于平均动作的优势

作用:

增强对状态的表达能力,加快收敛速度。

5.3. Prioritized Experience Replay(优先经验回放)

背景问题:

原始 DQN 使用 均匀采样,效率较低。

改进方式:

按 TD-error 大小进行采样: pi∝∣δi∣+εp_i ∝ |δ_i| + εpi​∝∣δi​∣+ε

  • TD-error δ 越大,代表该样本越有学习价值

作用:

更频繁采样有学习价值的样本,加快收敛,提升性能。

5.4. Multi-step DQN(多步 DQN)

背景问题:

一步 TD 更新传播奖励信号慢。

改进方式:

使用 n 步回报进行更新:y=rt+γrt+1+γ2rt+2+...+γn∗Q(st+n,at+n)y = r_t + γr_{t+1} + γ²r_{t+2} + ... + γⁿ*Q(s_{t+n}, a_{t+n})y=rt​+γrt+1​+γ2rt+2​+...+γn∗Q(st+n​,at+n​)

作用:

加快奖励传播速度,提升训练效率。

5.5. Rainbow DQN(彩虹 DQN)

概述:

将多种 DQN 改进融合为一个综合模型。

包含:

  • Double DQN
  • Dueling DQN
  • Prioritized Replay
  • Multi-step Learning
  • Noisy Network
  • Distributional RL

作用:

在多个强化学习 benchmark(如 Atari)上取得 SOTA 表现。

5.6. 总结

变种名称主要改进点意义与优势
Double DQN解耦动作选择与估值减少过高估计,提高稳定性
Dueling DQN分离状态值与动作优势加强状态表示能力
Prioritized Replay按 TD-error 采样加快训练收敛速度
Multi-step DQN多步奖励回报奖励传播更高效
Noisy DQN内建可学习噪声更有效的探索策略
Distributional DQN学习 Q 值分布表达能力增强
Rainbow DQN多项技术融合综合最强性能

六、手撕快排(空间复杂度有要求)

class Solution:
    def partition(self, nums, left, right):
        """划分函数:以nums[right]为基准,返回基准值的正确位置索引"""
        pivot = nums[right]
        i = left - 1  # 指向小于基准的子数组末尾
        for j in range(left, right):
            if nums[j] <= pivot:
                i += 1
                nums[i], nums[j] = nums[j], nums[i]      # 将小元素交换到左侧
        nums[i+1], nums[right] = nums[right], nums[i+1]  # 基准归位
        return i + 1
        
    def topk_split(self, nums, k, left, right):
        """快速选择算法核心:找到第k小元素的位置后停止递归"""
        if left < right:
            # 注意必须添加 self. 调用类方法
            index = self.partition(nums, left, right)
            if index == k:
                return                                    # 找到目标位置,终止递归
            elif index < k:
                self.topk_split(nums, k, index+1, right)  # 处理右半部分
            else:
                self.topk_split(nums, k, left, index-1)   # 处理左半部分
                
    #获得前k小的数
    def topk_smalls(nums, k):
        topk_split(nums, k, 0, len(nums)-1)
        return nums[:k]
        
    #获得前k大的数 
    def topk_larges(nums, k):
        #parttion是按从小到大划分的,如果让index左边为前n-k个小的数,则index右边为前k个大的数
        topk_split(nums, len(nums)-k, 0, len(nums)-1) #把k换成len(nums)-k
        return nums[len(nums)-k:] 

更多推荐