字节推荐算法

一、介绍DeepFM,Deep 和 FM 的区别,两者的特征数量是一样的么?

DeepFM 是一种结合 深度学习 (Deep Learning) 和 因子分解机 (Factorization Machine, FM) 的推荐系统模型。它能够同时学习 低阶特征交互(FM 部分) 和 高阶特征交互(DNN 部分)。

  • FM
    FM 的核心思想是对特征交叉进行矩阵分解,即用向量内积代替了传统的 one-hot 乘法计算,能够学习特征之间的 二阶交互(如性别 × 购买记录)
    公式:y^=w0+∑iwixi+∑i∑j>i⟨vi,vj⟩xixj\hat{y} = w_0 + \sum_{i} w_i x_i + \sum_{i}\sum_{j>i} \langle v_i, v_j \rangle x_i x_jy^=w0+iwixi+ij>ivi,vjxixj
  • DNN
    DNN 侧负责学习高阶特征交互,结构通常是多层全连接神经网络
  • 两者的特征数量没有具体说法,可以一样,也可以不一样。但是FM结构一般用于分类特征的交叉。DNN往往可以加更多的连续特征,所以一般还是不一样。
  • 但是往往FM 和 DNN会共享同一个embedding,那么两者的特征数量又是一样的。

二、了解召回么,召回中 UserCF(User-Based Collaborative Filtering,基于用户的协同过滤)对应的user向量怎么生成的。

在 召回阶段,UserCF(User-Based Collaborative Filtering,基于用户的协同过滤)需要计算用户相似度,以便推荐相似用户喜欢的物品。核心问题之一就是 如何生成用户向量(User Embedding)

2.1. 基于用户行为(User-Item 矩阵)

直接将用户对物品的行为特征当作用户矩阵。缺点就是太稀疏了,因为用户往往对大部分物品没有操作。

2.2. 通过矩阵分解生成 User Embedding

由于 User-Item 矩阵通常非常稀疏,我们可以使用矩阵分解(Matrix Factorization, MF)来生成低维的用户向量。

  • SVD 分解 (Singular Value Decomposition)
    通过 SVD(奇异值分解),可以将 用户-物品交互矩阵A 分解成:
    A≈UkΣkVkT A \approx U_k \Sigma_k V_k^T AUkΣkVkT
    例如,一个原始的 10000 用户 × 100000 物品矩阵,可以分解成:
    UUU矩阵 (10000 × 128):每个用户有 128 维 embedding
    VVV矩阵 (100000 × 128):每个物品有 128 维 embedding
    最终的 User Embedding 就是 矩阵 UUU 的行向量,表示每个用户的低维表示。
  • ALS(交替最小二乘)
    ALS (Alternating Least Squares) 是常用于 UserCF 的矩阵分解方法,它迭代优化:
    R≈PQTR≈PQ^TRPQT
    • PPP 是用户矩阵
    • QQQ 是物品矩阵

三、优化器了解么?介绍一下优化器

神经网络的优化器(Optimizer)是用于更新模型参数(如权重和偏差)的算法,核心目标是最小化损失函数:

3.1 基础的优化器

  • 随机梯度下降(SGD, Stochastic Gradient Descent)
    • 公式:θ=θ−η⋅∇L(θ)\theta = \theta - \eta \cdot \nabla L(\theta)θ=θηL(θ)
      • η\etaη:学习率
      • ∇L(θ)\nabla L(\theta)L(θ):损失函数 𝐿(𝜃) 对参数 𝜃 的梯度

3.2 带动量的优化算法

  • 动量梯度下降(Momentum SGD)
    • 公式:vt=βvt−1+(1−β)∇L(θ) θ=θ−ηvtv_t = \beta v_{t-1} + (1-\beta) \nabla L(\theta) \\ \ \\ \theta = \theta - \eta v_tvt=βvt1+(1β)L(θ) θ=θηvt
    • 通过指数加权平均(EMA)的方法,缓解震荡问题;
    • 在陡峭方向收敛更快,在平坦方向减小震荡
  • Nesterov 动量(NAG, Nesterov Accelerated Gradient)
    • 公式vt=βvt−1+(1−β)∇L(θ−ηvt−1) θ=θ−ηvtv_t = \beta v_{t-1} + (1 - \beta) \nabla L(\theta - \eta v_{t-1}) \\ \ \\ \theta = \theta - \eta v_tvt=βvt1+(1β)L(θηvt1) θ=θηvt
    • 计算梯度时使用“提前一步”的权重,收敛更快
    • 比普通 Momentum 具有更好的泛化能力

3.3 自适应学习率优化算法

  • Adagrad(Adaptive Gradient)

    • 公式:Gt=Gt−1+∇L(θ)⊙∇L(θ) θ=θ−ηGt+ϵ∇L(θ)G_t = G_{t-1} + \nabla L(\theta) \odot \nabla L(\theta) \\ \ \\ \theta = \theta - \frac{\eta}{\sqrt{G_t} + \epsilon} \nabla L(\theta)Gt=Gt1+L(θ)L(θ) θ=θGt +ϵηL(θ)
    • 特点
      • 自适应调整不同参数的学习率
      • 主要问题是随着时间推移,学习率不断减小,最终趋近于 0
  • RMSprop(Root Mean Square Propagation)

    • 公式:
      Gt=βGt−1+(1−β)∇L(θ)⊙∇L(θ) θ=θ−ηGt+ϵ∇L(θ) G_t = \beta G_{t-1} + (1 - \beta) \nabla L(\theta) \odot \nabla L(\theta) \\ \ \\ \theta = \theta - \frac{\eta}{\sqrt{G_t} + \epsilon} \nabla L(\theta) Gt=βGt1+(1β)L(θ)L(θ) θ=θGt +ϵηL(θ)
    • 特点
      • 解决了 Adagrad 学习率衰减过快的问题
      • 适用于时序任务
  • Adam(Adaptive Moment Estimation)
    mt=β1mt−1+(1−β1)∇L(θ) vt=β2vt−1+(1−β2)∇L(θ)⊙∇L(θ) mt^=mt1−β1t,vt^=vt1−β2t θ=θ−ηvt^+ϵmt^ m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla L(\theta) \\ \ \\ v_t = \beta_2 v_{t-1} + (1 - \beta_2) \nabla L(\theta) \odot \nabla L(\theta) \\ \ \\ \hat{m_t} = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v_t} = \frac{v_t}{1 - \beta_2^t} \\ \ \\ \theta = \theta - \frac{\eta}{\sqrt{\hat{v_t}} + \epsilon} \hat{m_t} mt=β1mt1+(1β1)L(θ) vt=β2vt1+(1β2)L(θ)L(θ) mt^=1β1tmt,vt^=1β2tvt θ=θvt^ +ϵηmt^

  • AdamW(Weight Decay Adam)

    • 公式:
      θ=θ−η(mt^vt^+ϵ+λθ) \theta = \theta - \eta \left( \frac{\hat{m_t}}{\sqrt{\hat{v_t}} + \epsilon} + \lambda \theta \right) θ=θη(vt^ +ϵmt^+λθ)
    • 特点:
      • AdamW 在 Adam 的基础上增加了 权重衰减(Weight Decay)

3.4. 其他优化算法

  • AdaDelta
    进一步改进 RMSprop,使学习率自适应变化
    Δθt=−Δθt−12+ϵGt+ϵ∇L(θ) \Delta \theta_t = - \frac{\sqrt{\Delta \theta_{t-1}^2 + \epsilon}}{\sqrt{G_t + \epsilon}} \nabla L(\theta) Δθt=Gt+ϵ Δθt12+ϵ L(θ)
  • Nadam(Nesterov-accelerated Adam)
    结合 Adam 和 NAG 的优化方法:
    mt=β1mt−1+(1−β1)∇L(θ) mt^=mt1−β1t θ=θ−η(β1mt^+(1−β1)∇L(θ)vt^+ϵ) m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla L(\theta) \\ \ \\ \hat{m_t} = \frac{m_t}{1 - \beta_1^t} \\ \ \\ \theta = \theta - \eta \left( \frac{\beta_1 \hat{m_t} + (1 - \beta_1) \nabla L(\theta)}{\sqrt{\hat{v_t}} + \epsilon} \right) mt=β1mt1+(1β1)L(θ) mt^=1β1tmt θ=θη(vt^ +ϵβ1mt^+(1β1)L(θ))
  • Lion(EvoLved Sign Momentum)
    mt=β1mt−1+(1−β1)∇L(θ) θ=θ−η(sign(mt)+λθ) m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla L(\theta) \\ \ \\ \theta = \theta - \eta (\text{sign}(m_t) + \lambda \theta) mt=β1mt1+(1β1)L(θ) θ=θη(sign(mt)+λθ)
优化器 适用场景 优点 缺点
SGD 适用于小型数据集 计算简单,稳定 震荡大,收敛慢
Momentum 适用于梯度震荡较大的情况 解决震荡问题,加速收敛 需要调参(β 值)
NAG 适用于快速收敛的场景 提前更新,收敛更快 计算量略大
Adagrad 适用于稀疏数据 自适应学习率 学习率下降过快
RMSprop 适用于非平稳目标 平稳收敛,适用于 RNN 需要调 β
Adam 适用于大多数任务 结合 Momentum 和 RMSprop 泛化能力较差
AdamW 适用于大规模深度网络 加入 Weight Decay,提高泛化 计算量略大
Nadam 适用于 NLP 任务 结合 Adam 和 NAG 计算量大

4、如何判断模型过拟合

  • 训练集验证集效果差异大
  • 交叉验证的不同折差异大
  • 。。。

5、算法题1-数组中的第K个最大元素(hot100)

这是一道经典的使用快排或者堆排序的题(求TOPK)
回顾一下快排

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        import random                              
        def quicksort(nums,left,right):
            flag=nums[random.randint(left,right)]   
            i,j=left,right                          #设定从左到右的指针i,从右到左的指针j
            while i<=j:
                while nums[i]<flag: i+=1            #i从左往右扫,找到大于等于flag的数。
                while nums[j]>flag: j-=1            #j从右往左扫,找到小于等于flag的数。
                if i<=j:
                    nums[i],nums[j]=nums[j],nums[i] #交换左右指针下标对应的数值
                    i+=1                            #左指针继续往右走
                    j-=1                            #右指针继续往左走
            if i<right: quicksort(nums,i,right)     #递归解决flag左边的低位数组的排序
            if j>left:  quicksort(nums,left,j)      #递归解决flag右边的低位数组的排序
        quicksort(nums,0,len(nums)-1)               #函数入口,将整个数组的信息传入
        return nums                                 #返回修改后的nums

那么这道题使用快排就可以写为

class Solution:
    def findKthLargest(self, nums, k):
        def quick_select(nums, k):
            # 随机选择基准数
            pivot = random.choice(nums)
            big, equal, small = [], [], []
            # 将大于、小于、等于 pivot 的元素划分至 big, small, equal 中
            for num in nums:
                if num > pivot:
                    big.append(num)
                elif num < pivot:
                    small.append(num)
                else:
                    equal.append(num)
            if k <= len(big):
                # 第 k 大元素在 big 中,递归划分
                return quick_select(big, k)
            if len(nums) - len(small) < k:
                # 第 k 大元素在 small 中,递归划分
                return quick_select(small, k - len(nums) + len(small))
            # 第 k 大元素在 equal 中,直接返回 pivot
            return pivot
        return quick_select(nums, k)
Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐