4、用户冷启动实战:构建新用户兴趣探索机制

好,咱们进入实战环节了。

前面讲了那么多理论,现在该动手了。用户冷启动,说白了就是:一个刚注册的用户,你对他一无所知,该怎么给他推荐内容?

我当年在电商平台做推荐系统时,最头疼的就是这个。新用户来了,没有点击记录,没有购买历史,连搜索词都没有。你给他推什么?推爆款?推新品?还是随机推?

嗯,这里有个核心矛盾:你想尽快了解他,就得让他多尝试不同内容。但尝试太多,他又可能因为推荐不精准而流失。

这就是冷启动阶段的探索与利用平衡问题。

4.1 冷启动的核心挑战

先理清问题本质。新用户冷启动,我们面临三个难题:

  • 数据稀疏:用户画像几乎是空的,只有注册时填的性别、年龄等基础信息
  • 反馈延迟:用户点不点击、看不看完,这些信号需要时间积累
  • 兴趣漂移:用户今天想买书,明天想买鞋,你没法用一次行为定终身

我见过不少团队,一上来就用协同过滤给新用户推东西。结果呢?推荐列表全是热门商品,用户觉得「这平台没什么特别的」,就走了。

⚠️ 避坑指南:我曾经犯过一个错误——给新用户直接推高点击率的商品。结果发现,用户点击了,但转化率极低。为什么?因为那些商品虽然点击率高,但大多是「标题党」,用户点进去发现不是自己想要的。所以,冷启动阶段不能只看点击率,要看「探索效率」

4.2 多臂老虎机算法:解决探索与利用的利器

多臂老虎机(Multi-Armed Bandit,MAB)这个名字挺有意思。想象一下,你面前有N台老虎机,每台的中奖概率不同。你只有有限的钱,怎么玩才能赢最多?

这就是探索与利用的经典场景:你既要尝试新机器(探索),又要多玩已知高回报的机器(利用)。

在推荐系统里,每台老虎机就是一个候选推荐策略或一个商品类别。新用户来了,我们就像赌徒一样,要决定给他展示哪个策略。

常用的MAB算法有三种,我一个个说。

4.2.1 Epsilon-Greedy:最简单的探索策略

这个算法思路很直白:以概率ε进行随机探索,以概率1-ε选择当前最优。

举个例子。假设我们有三个商品类别:A(图书)、B(电子产品)、C(服装)。新用户来了,我们设置ε=0.2。

  • 80%的概率:选择当前点击率最高的类别
  • 20%的概率:随机选一个类别

代码实现也很简单:

import random
import numpy as np

class EpsilonGreedy:
    def __init__(self, n_arms, epsilon=0.1):
        self.n_arms = n_arms
        self.epsilon = epsilon
        self.counts = [0] * n_arms  # 每个臂被选择的次数
        self.values = [0.0] * n_arms  # 每个臂的平均回报
        
    def select_arm(self):
        if random.random() < self.epsilon:
            # 探索:随机选一个
            return random.randint(0, self.n_arms - 1)
        else:
            # 利用:选当前均值最高的
            return np.argmax(self.values)
    
    def update(self, chosen_arm, reward):
        self.counts[chosen_arm] += 1
        n = self.counts[chosen_arm]
        value = self.values[chosen_arm]
        # 增量更新均值
        self.values[chosen_arm] = ((n - 1) * value + reward) / n

这个算法有个问题:ε是固定的。 刚开始探索多点没问题,但用户已经积累了100次点击后,你还用20%的概率去随机探索,就有点浪费了。

我个人的习惯是:让ε随时间衰减。 比如ε = 1 / sqrt(t+1),这样前期探索多,后期逐渐收敛到利用。

4.2.2 UCB(Upper Confidence Bound):更聪明的探索

UCB的思路是:对每个臂,不仅看它的平均回报,还要看它的「潜力」。

公式是这样的:

UCB = 平均回报 + sqrt(2 * ln(总尝试次数) / 该臂尝试次数)

你想想看,如果一个臂尝试次数很少,那么第二项就会很大。这意味着它虽然当前平均回报低,但「不确定性高」,值得再试一次。

代码实现:

class UCB:
    def __init__(self, n_arms):
        self.n_arms = n_arms
        self.counts = [0] * n_arms
        self.values = [0.0] * n_arms
        
    def select_arm(self):
        total_counts = sum(self.counts)
        # 每个臂至少尝试一次
        for arm in range(self.n_arms):
            if self.counts[arm] == 0:
                return arm
        
        ucb_values = []
        for arm in range(self.n_arms):
            bonus = np.sqrt(2 * np.log(total_counts) / self.counts[arm])
            ucb_values.append(self.values[arm] + bonus)
        return np.argmax(ucb_values)
    
    def update(self, chosen_arm, reward):
        self.counts[chosen_arm] += 1
        n = self.counts[chosen_arm]
        value = self.values[chosen_arm]
        self.values[chosen_arm] = ((n - 1) * value + reward) / n

UCB的好处是不需要设置ε,探索是自动进行的。但要注意,它假设回报是稳定的。如果用户兴趣变化很快,UCB可能反应不过来。

💡 实战技巧:我在项目中遇到过,UCB在冷启动初期表现很好,但用户兴趣漂移后效果下降。我的解决方案是:加一个滑动窗口,只计算最近N次点击的回报,而不是全部历史。这样算法能更快适应变化。

4.2.3 Thompson Sampling:贝叶斯视角的探索

Thompson Sampling是我个人最喜欢的算法。它从贝叶斯角度出发,对每个臂维护一个回报概率的分布,然后从分布中采样,选采样值最大的臂。

说白了,就是:我不确定每个臂的真实回报率,但我有个「信念」。每次选择时,我根据信念随机抽一个值,选最大的那个。

对于二分类问题(点击/不点击),我们通常用Beta分布来表示信念:

class ThompsonSampling:
    def __init__(self, n_arms):
        self.n_arms = n_arms
        # Beta分布参数:alpha = 成功次数+1, beta = 失败次数+1
        self.alphas = [1] * n_arms
        self.betas = [1] * n_arms
        
    def select_arm(self):
        samples = []
        for arm in range(self.n_arms):
            # 从Beta分布中采样
            sample = np.random.beta(self.alphas[arm], self.betas[arm])
            samples.append(sample)
        return np.argmax(samples)
    
    def update(self, chosen_arm, reward):
        if reward == 1:
            self.alphas[chosen_arm] += 1
        else:
            self.betas[chosen_arm] += 1

Thompson Sampling有个天然优势:不确定性越高的臂,采样值的方差越大,被选中的概率也越大。 这正好符合「探索」的需求。

我记得有一次做AB测试,Thompson Sampling的冷启动效果比UCB好了15%。为什么?因为UCB的探索是确定性的(选上界最高的),而Thompson Sampling是随机采样的,天然带有随机性,能更均匀地探索各个臂。

4.3 冷启动阶段的探索与利用平衡策略

光有算法还不够,你得知道什么时候用哪种策略。我一般把冷启动分为三个阶段:

阶段 用户行为数 推荐策略 探索比例
纯冷启动 0-5次 基于人口统计 + 随机探索 80%
弱冷启动 5-30次 MAB算法(推荐Thompson Sampling) 30%-50%
温启动 30次以上 协同过滤 + MAB混合 10%-20%

这里有个关键点:探索不是盲目的。 你不能真的给新用户随机推东西。我一般会先根据用户注册信息(年龄、性别、地域)做一个粗粒度的兴趣预测,然后在这个「候选池」里做MAB探索。

举个例子。一个25岁男性用户注册,我猜他可能对电子产品、运动户外感兴趣。那我就把候选类别缩小到5-10个,然后用Thompson Sampling在这几个类别里探索。这样既保证了探索效率,又不会推荐太离谱的内容。

🎯 核心要点:冷启动的探索与利用平衡,不是「探索越多越好」,而是「在有限的探索次数内,尽快找到用户的兴趣点」。所以,候选池的质量比探索算法本身更重要。

4.4 实战中的坑与解决方案

最后分享几个我踩过的坑:

  • 坑1:冷启动阶段用全局热门。结果新用户看到的全是爆款,觉得平台没个性。解决方案:用分层热门,按用户画像分群,每个群有自己的热门列表。
  • 坑2:MAB算法不区分用户。所有新用户共享一个MAB模型。结果呢?喜欢看书的用户被推了电子产品。解决方案:按用户画像分桶,每个桶独立维护MAB。
  • 坑3:只关注点击率。用户点了,但没转化,或者点了就关。解决方案:用多目标奖励,比如点击+停留时长+转化,加权求和作为MAB的reward。

嗯,说到这里,我想起一个项目。当时我们给新用户推了10个商品类别,用UCB算法。结果发现有个类别「家居用品」一直没人点,UCB就再也不推了。但后来我们分析发现,不是用户不喜欢家居,而是推荐位太靠后,用户根本没看到。

所以,位置偏差一定要处理。 我后来在reward里加了位置惩罚项,曝光位置越靠后,reward打折越多。这样算法就不会因为位置问题而误判某个类别的真实价值。

好了,这一章的内容就到这里。下一章我们会讲物品冷启动,也就是新商品上架后,怎么在没有用户行为的情况下做推荐。到时候会讲到内容特征提取、图神经网络等方法,敬请期待。