搜广推校招面经六十四

发布于:2025-04-03 ⋅ 阅读:(23) ⋅ 点赞:(0)

滴滴搜推算法

一、定义一个树结构、特征结构。写一个决策树对样本打分

逆天啊,上来就是暴击

import numpy as np
class TreeNode:
    def __init__(self, feature_index=None, threshold=None, left=None, right=None, score=None):
        self.feature_index = feature_index   # 用于分裂的特征索引
        self.threshold = threshold           # 分裂阈值
        self.left = left                     # 左子树
        self.right = right                   # 右子树
        self.score = score                   # 叶子节点的分数
        
    def is_leaf(self):
        return self.score is not None

class DecisionTree:
    def __init__(self, root):
        self.root = root
    def score_sample(self, sample):
        """对单个样本进行打分"""
        node = self.root
        while not node.is_leaf():
            if sample[node.feature_index] <= node.threshold:
                node = node.left
            else:
                node = node.right
        return node.score
        
# 示例树(手动构造)
leaf1 = TreeNode(score=0.1)
leaf2 = TreeNode(score=0.7)
leaf3 = TreeNode(score=0.4)
leaf4 = TreeNode(score=0.9)

node2 = TreeNode(feature_index=1, threshold=1.5, left=leaf1, right=leaf2)
node3 = TreeNode(feature_index=2, threshold=2.5, left=leaf3, right=leaf4)

root = TreeNode(feature_index=0, threshold=0.5, left=node2, right=node3)
decision_tree = DecisionTree(root)

# 测试样本
sample1 = np.array([0.3, 2.0, 3.0])  # 走左子树,再走右子树,得分 0.7
sample2 = np.array([0.8, 0.5, 2.0])  # 走右子树,再走左子树,得分 0.4

print(decision_tree.score_sample(sample1))  # 输出: 0.7
print(decision_tree.score_sample(sample2))  # 输出: 0.4

二、itemcf 和 usercf 的使用场景、分别有什么好处

2.1. ItemCF(基于物品的协同过滤)

使用场景

  • 适用于商品、电影、音乐、书籍等推荐系统,尤其是商品数量相对固定,而用户行为动态变化的场景。
  • 适合短期兴趣推荐,如电商平台的“看了又看”或“买了还买”。
  • 适用于内容较丰富但用户行为较少的场景,比如新用户冷启动时。

优点

  • 计算相对稳定,商品相似度矩阵变化较小,推荐结果较稳定。
  • 适用于用户行为稀疏的数据集,即便单个用户数据较少,也能提供较好的推荐效果。
  • 容易解释,能通过物品相似度解释推荐原因(例如:你买了A,所以推荐B)。
  • 适合长尾商品推荐,提高物品曝光率。

缺点

  • 无法解决用户兴趣变化的问题,例如短期流行趋势较难捕捉。
  • 依赖用户的行为数据,如果冷启动商品缺乏行为数据,则无法计算相似度。
  • 计算物品相似度可能会受到热门物品的影响,导致热门物品推荐过多。

2.2. UserCF(基于用户的协同过滤)

使用场景

  • 适用于社区、社交平台、新闻推荐等场景,尤其是用户互动较多的情况。
  • 适合基于社交关系的推荐,如“和你兴趣相似的用户喜欢这个”。
  • 适用于需要个性化推荐的场景,如个性化视频推荐。

优点

  • 适用于短期兴趣推荐,比如新闻推荐,能够快速适应用户兴趣变化。
  • 能挖掘用户之间的相似性,适用于社交推荐,例如好友推荐、共同兴趣群体推荐。
  • 对冷启动用户友好,可以利用相似用户的历史行为来提供推荐。

缺点

  • 计算量大,用户数量多时计算相似度代价高。
  • 依赖用户的行为数据,用户行为过少时难以计算出有效的相似度。
  • 用户兴趣变化快时,历史相似用户可能不再合适,导致推荐效果下降。
  • 容易受流行用户的影响,可能导致热门用户行为被过度推荐。

2.3. 使用场景对比

维度 ItemCF UserCF
用户数量 用户数远大于物品数时更适用 物品数远大于用户数时更适用
实时性 新用户加入系统影响小 新物品加入系统影响小
行为密度 在用户行为较丰富的场景表现更好 在用户行为较稀疏的场景更稳定
场景案例 电商商品推荐、内容推荐 社交推荐、小众兴趣发现
  • 结合两者可实现更好的推荐效果,如混合推荐(Hybrid Recommendation)。

三、双塔召回一味的打压负样本有什么问题?

在推荐系统中,负样本通常是 用户未交互的物品,但其中可能包含用户潜在感兴趣的物品(即 硬负样本 Hard Negatives)。

3.1. 这里首先要理解什么叫打压负样本?

在双塔召回中,打压负样本通常是指通过采样或损失函数设计,使模型能够更好地区分正样本和负样本,实际上是让模型更关注负样本的学习,更好识别到负样本。
eg:
1. 增加难负样本(hard negative)的比例
2. 在损失函数中给负样本更大权重(如对比学习中)
3. 进行负样本挖掘(negative mining)

3.2. 可能带来的问题

3.2.1. 召回多样性下降 (用户未交互的item不一定是真负样本(可能只是没曝光))

  • 如果负样本被过分打压,模型可能学会简单地记住某些类别的物品 永远不推荐,这可能会导致召回多样性降低。
  • 例如,用户可能对某个类别的物品 偶尔感兴趣,但如果模型一味打压该类别的物品,它们可能永远不会被召回。

3.2.2. 可能损害硬负样本(Hard Negative)的贡献

  • 硬负样本(如用户可能感兴趣但未交互的物品)可以提供有价值的监督信号。
  • 过度打压负样本可能会让模型强行拉开所有负样本的距离,而忽略了其中一些“接近正样本”的负样本,影响推荐效果。

3.2.3. 过拟合(忽略掉全局分布,过度拟合特定的负样本)

  • 过度强调负样本的区分,会使模型过于依赖当前训练数据中的负样本分布,而忽略潜在的 泛化能力

四、为什么mmoe要加极化,这样容易导致波动增加。

这里我没太理解极化,chatgpt说,极化是指:某些专家始终被选中,而其他专家几乎不被选中(即门控权重接近 1 或 0),就会导致极化现象。
那我理解极化就是指每个门控gate的输出不经过激活函数,直接输出logit,做为每个experts的权重。

五、53. 最大子数组和(力扣hot100_数组)

在这里插入图片描述
这道题没法使用滑动窗口,因为存在负数,滑入一个数后,结果可能变大,也可能变小。

  • 思路1:前缀和
    这道题和121. 买卖股票的最佳时机一样,都是求最大子数组和。我们可以维护一个最小前缀和,用当前前缀和 减去维护的 最小前缀和 ,就得到最大数组和。
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        ans = -inf
        min_pre_sum = pre_sum = 0
        for x in nums:
            pre_sum += x  # 当前的前缀和
            ans = max(ans, pre_sum - min_pre_sum)  # 减去前缀和的最小值
            min_pre_sum = min(min_pre_sum, pre_sum)  # 维护前缀和的最小值
        return ans
  • 动态规划
    维护 dp[i] 表示以 nums[i] 结尾的最大子数组和。
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        dp = [0] * len(nums)
        dp[0] = nums[0]
        for i in range(1, len(nums)):
            dp[i] = max(dp[i - 1], 0) + nums[i]
        return max(dp)