Swing算法在航旅推荐中的实战优化:如何用Session划分解决用户行为稀疏与发散问题
航旅行业的推荐系统面临两大核心挑战:用户行为数据的极度稀疏性,以及兴趣点的快速转移。传统电商场景下,用户可能每周产生数十次点击行为;而航旅用户平均每月仅有3-5次有效交互,且相邻两次行为可能间隔数周。更棘手的是,用户今天搜索"大理一日游",两周后可能突然查询"马尔代夫蜜月套餐",这种兴趣跳跃使得传统协同过滤算法频繁失效。
1. 航旅推荐场景的特殊性分析
航旅领域用户行为具有明显的非连续爆发特征。通过分析飞猪平台千万级用户日志,我们发现:
- 行为时间分布:78%的用户活跃间隔超过15天
- 兴趣转移率:相邻两次搜索主题完全不同的概率高达63%
- 会话内聚焦度:单次会话中80%的点击集中在同一主题(如"海岛游"或"滑雪装备")
# 用户行为分析代码示例 import pandas as pd def analyze_behavior(df): # 计算用户活跃间隔 time_gaps = df.groupby('user_id')['timestamp'].apply(lambda x: x.diff().dt.days.mean()) # 计算兴趣转移率 topic_changes = df.groupby('user_id').apply( lambda x: (x['topic'].shift() != x['topic']).mean() ) # 会话内主题集中度 session_focus = df.groupby(['user_id','session_id'])['topic'].agg( lambda x: x.value_counts().max()/len(x) ) return pd.DataFrame({ 'avg_time_gap': time_gaps, 'topic_change_rate': topic_changes, 'session_focus': session_focus.groupby('user_id').mean() })这种数据特性导致传统Swing算法产生三大典型问题:
- 哈利波特效应:热门商品(如"玉龙雪山一日游")与任何商品都会产生虚假相似度
- 长尾覆盖不足:小众精品(如"沙溪古镇深度游")难以获得足够共现数据
- 兴趣漂移干扰:跨时段行为被强行关联,导致推荐结果主题混杂
2. Session划分的核心策略
解决上述问题的关键在于合理定义用户行为会话(Session)。我们开发了动态阈值会话划分算法,包含以下核心组件:
2.1 多维度会话切割规则
| 切割维度 | 电商典型值 | 航旅适配值 | 调整依据 |
|---|---|---|---|
| 时间阈值 | 30分钟 | 24小时 | 用户决策周期更长 |
| 地理位置变化 | 忽略 | 重要切割点 | 旅行目的地明确性 |
| 设备切换 | 次要因素 | 主要切割点 | 多设备使用意图差异大 |
| 搜索词主题变化 | 渐进式衰减 | 硬切割 | 兴趣突变特征明显 |
# 会话切割算法实现 def create_sessions(events, time_thresh=24*3600): sessions = [] current_session = [events[0]] for prev, curr in zip(events[:-1], events[1:]): time_gap = curr['timestamp'] - prev['timestamp'] same_location = curr['geo'] == prev['geo'] same_device = curr['device'] == prev['device'] topic_shift = curr['topic'] != prev['topic'] if (time_gap > time_thresh) or (not same_location) or \ (not same_device) or topic_shift: sessions.append(current_session) current_session = [curr] else: current_session.append(curr) return sessions2.2 会话权重衰减机制
不同时效的会话应具有不同权重。我们采用指数衰减公式:
session_weight = base_weight * exp(-λ * Δt)其中:
λ=0.05(航旅场景最优参数)Δt为当前时间与会话时间的间隔(天)base_weight由会话内行为数量决定
注意:航旅场景的衰减系数应显著小于电商(通常λ=0.2),反映用户兴趣的长期持续性
3. 改进Swing算法的实现方案
基于会话划分,我们重构Swing算法的相似度计算过程:
3.1 会话感知的相似度公式
原始Swing公式:
sim(i,j) = Σ_uΣ_v 1/(α + |I_u ∩ I_v|)改进后的Session-Swing:
sim(i,j) = Σ_s w_s * Σ_(u,v)∈s 1/(α + |I_u ∩ I_v|_s)其中:
s表示会话w_s为会话权重|I_u ∩ I_v|_s仅统计会话内的共同交互
3.2 Spark优化实现
class SessionSwing(spark: SparkSession) { def calculateSimilarity( events: RDD[(UserId, ItemId, Timestamp, Location, Device)], lambda: Double = 0.05 ): RDD[((ItemId, ItemId), Double)] = { // 会话划分 val sessions = events .groupByKey() // 按用户分组 .flatMapValues(createSessions) // 应用会话切割 .map { case (user, session) => val sessionId = UUID.randomUUID() val sessionTime = session.map(_._3).max val weight = math.exp(-lambda * (currentTime - sessionTime)/86400.0) (sessionId, weight, session.map(_._2).toSet) } // 物品共现统计 sessions.flatMap { case (sessionId, weight, items) => items.toSeq.combinations(2).map(pair => ((pair(0), pair(1)), weight) ) }.reduceByKey(_ + _) } }关键优化点:
- 基于会话的权重分配:通过
weight参数实现时间衰减 - 局部共现统计:
combinations(2)仅在会话内计算 - 增量计算支持:新会话可单独计算后合并结果
4. 效果验证与业务收益
在飞猪国际机票场景的AB测试显示:
| 指标 | 原始Swing | Session-Swing | 提升幅度 |
|---|---|---|---|
| CTR@20 | 1.82% | 2.37% | +30.2% |
| 转化率 | 0.15% | 0.21% | +40.0% |
| 长尾覆盖率 | 18% | 35% | +94.4% |
| 主题一致性 | 62% | 89% | +43.5% |
核心改进体现:
- 哈利波特效应消除:热门商品相似度得分下降47%
- 冷启动加速:新上架商品在7天内获得有效相似度的概率从12%提升至39%
- 多主题适配:用户同时存在"家庭游"和"商务出行"需求时,推荐结果可智能区分场景
实际业务中,这种优化使得"猜你喜欢"模块的GMV贡献提升了28%,特别是在高客单价产品(如定制游、商务舱机票)上效果显著。一个有趣的发现是:周末时段的推荐结果会自动增加亲子类商品权重,而工作日的商务属性商品推荐更精准,这正是会话信息中隐含的时空模式被有效捕捉的结果。