日志不是垃圾,是金矿:聊聊基于日志的大规模用户行为建模如何撑起推荐系统
大家好,我是 Echo_Wish。
很多做推荐系统的同学,一上来就聊模型:DeepFM、DIN、Transformer……仿佛模型越“深”,推荐就越准。
但我这些年做大数据和推荐落地,反而越来越笃定一件事:
推荐系统真正的“灵魂”,不在模型,而在日志。
没有高质量行为日志,再牛的模型都是空中楼阁。今天我们就聊聊——基于日志的大规模用户行为建模在推荐中的应用,以及它背后那些容易被忽视的关键问题。
一、日志是什么?不是埋点,是用户的“时间线”
在推荐系统里,日志通常包括:
- 曝光日志(exposure)
- 点击日志(click)
- 加购日志(cart)
- 收藏日志(favorite)
- 下单日志(purchase)
- 停留时长(dwell time)
本质上,这些是用户和系统交互的时间序列。
举个简单例子:
user_id | item_id | action | timestamp
----------------------------------------
1001 | A | exposure | 10:01
1001 | A | click | 10:02
1001 | B | exposure | 10:03
1001 | B | exposure | 10:04
1001 | C | click | 10:05
这不是冷冰冰的数据,这是用户真实的“注意力轨迹”。
而推荐系统要做的,就是在这个轨迹里,预测“下一步”。
二、行为建模第一步:把“日志”变成“特征”
1️⃣ 基础行为统计特征
比如我们想构造一个用户对某类商品的偏好程度:
from pyspark.sql import functions as F
# 假设 logs 是用户行为日志表
user_item_stat = logs.groupBy("user_id", "item_category") \
.agg(
F.sum(F.when(F.col("action") == "click", 1).otherwise(0)).alias("click_cnt"),
F.sum(F.when(F.col("action") == "purchase", 1).otherwise(0)).alias("buy_cnt")
)
# 加一个简单的偏好分
user_item_stat = user_item_stat.withColumn(
"preference_score",
F.col("click_cnt") * 0.3 + F.col("buy_cnt") * 0.7
)
这类统计特征是推荐系统的基石。
但问题来了:
静态统计 ≠ 行为趋势
2️⃣ 引入时间衰减:行为有“保质期”
用户三个月前点过篮球鞋,不代表现在还想买。
我们可以引入时间衰减:
from pyspark.sql.functions import unix_timestamp, current_timestamp, exp
logs = logs.withColumn(
"time_diff",
unix_timestamp(current_timestamp()) - unix_timestamp("timestamp")
)
# 半衰期为7天
half_life = 7 * 24 * 3600
logs = logs.withColumn(
"decay_weight",
exp(-F.col("time_diff") / half_life)
)
weighted_score = logs.groupBy("user_id", "item_category") \
.agg(
F.sum(
F.when(F.col("action") == "click", 1)
.otherwise(0) * F.col("decay_weight")
).alias("decayed_click_score")
)
这一步,是很多推荐系统从“能用”到“好用”的关键。
三、从“统计特征”到“序列建模”
当用户行为规模上亿条,光靠统计特征就不够了。
这时候就需要:
- 序列特征
- session 切分
- 行为 embedding
- Transformer / RNN 等建模
我们来看一个简化的用户行为序列构造方式:
# 按时间排序,构造行为序列
from pyspark.sql.window import Window
w = Window.partitionBy("user_id").orderBy("timestamp")
sequence_df = logs.withColumn(
"rank", F.row_number().over(w)
).groupBy("user_id").agg(
F.collect_list("item_id").alias("item_seq")
)
输出可能是:
user_id | item_seq
1001 | [A, B, C, D, E]
接下来我们就可以喂给深度模型:
# 伪代码
user_embedding = Transformer(item_seq)
score = dot(user_embedding, item_embedding)
这里的核心思想是:
用户不是“标签集合”,而是“行为序列”。
这是推荐系统思维方式的升级。
四、大规模日志的工程挑战
很多人只看到模型,却忽视数据工程。
但真实世界里,你会遇到:
1️⃣ 日志延迟与乱序
实时日志可能晚到几分钟甚至几小时。
解决方案:
- 使用 watermark
- 做窗口容错
- 延迟特征生成
2️⃣ 日志噪音
用户误点、爬虫流量、刷点击行为。
如果不做清洗,模型会学到垃圾模式。
比如:
# 过滤异常高频点击
abnormal_users = logs.groupBy("user_id") \
.count() \
.filter("count > 10000")
logs = logs.join(abnormal_users, "user_id", "left_anti")
日志清洗,往往比模型调参更重要。
五、我的一点真实感受
我见过太多团队:
- 模型调到天花乱坠
- embedding 维度拉到 512
- GPU 集群轰鸣
结果上线效果不涨。
最后发现:
- 曝光日志丢了
- 点击埋点漏 30%
- 时间戳错时区
推荐系统的本质不是“算法秀肌肉”,而是:
谁能更真实地还原用户行为。
大规模日志建模的核心,不是堆算力,而是三件事:
- 行为定义清晰
- 特征构造合理
- 数据链路稳定
模型只是放大器。
六、推荐系统的终极问题
当我们基于日志建模时,有个值得思考的问题:
我们是在“预测兴趣”,还是在“放大行为”?
如果用户偶然点了一次某类内容,我们持续推荐——
那是个性化,还是信息茧房?
日志是历史,但推荐决定未来。
作为工程师,我们不能只关心 AUC。
我们要问:
- 是否引导用户探索?
- 是否给新内容机会?
- 是否平衡多样性?
这也是我越来越关注“探索机制”和“多样性建模”的原因。
七、总结
基于日志的大规模用户行为建模,本质上是一件三层结构的事:
第一层:数据层 —— 日志清洗、去重、时间对齐
第二层:特征层 —— 统计特征 + 时间衰减 + 序列构造
第三层:模型层 —— 深度建模、embedding、注意力机制
但真正决定推荐质量的,不是最底层,而是最顶层对“用户行为”的理解。