2. 数据采集与预处理:用户行为日志采集、数据清洗与标准化
各位同学,咱们今天聊点实在的。做推荐系统,最怕什么?不是算法不够牛,而是数据一塌糊涂。我见过太多团队,模型调得飞起,结果上线一看,效果还不如简单的热门推荐。为什么?数据源头就脏了。
所以,数据采集与预处理,这步走稳了,后面的路才顺。说白了,这就是盖楼打地基。地基歪了,楼再漂亮也得塌。
2.1 用户行为日志采集:从点击到购买,一个都不能少
用户行为日志,是推荐系统的“原油”。我们得把用户的一举一动都记下来。我个人习惯,把行为分成三类:
- 显式反馈:用户主动表达的行为。比如点击“喜欢”、给商品打分、写评价。这类数据最珍贵,但量少。
- 隐式反馈:用户无意识留下的痕迹。比如浏览商品、点击详情、加入购物车、收藏、购买。这类数据量大,但噪音多。
- 上下文信息:行为发生时的环境。比如时间、地点、设备、网络环境。这些信息往往能解释行为背后的原因。
我在项目中遇到过一个问题:用户点击了商品,但页面加载太慢,用户等不及就走了。这个“点击”算有效吗?嗯,这里要注意,单纯的点击事件,如果不结合停留时长,很容易误导模型。
所以,我们采集的日志,至少要包含以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| user_id | string | 用户唯一标识 |
| item_id | string | 商品唯一标识 |
| behavior_type | string | 行为类型:click、fav、cart、buy |
| timestamp | long | 行为发生时间戳(毫秒级) |
| duration | int | 停留时长(秒),仅点击行为有 |
| device | string | 设备类型:ios、android、pc |
你想想看,如果只采集“点击”和“购买”,中间那些“收藏”、“加购”的行为就丢了。而这些恰恰是用户意图最强烈的信号。我曾经接手过一个项目,数据只存了点击和购买,结果模型死活学不会“用户犹豫”这个状态。后来补上了收藏和加购数据,效果立竿见影。
2.2 数据清洗:把脏数据“洗”干净
数据采集上来,你以为就能直接用了?太天真了。真实的数据,比你想象的要脏得多。我总结了几类常见的问题:
- 缺失值:比如用户没有登录,user_id 为空;或者页面没加载完,duration 为 0。
- 异常值:比如一个用户一秒钟点击了 100 次商品,这明显是爬虫或者脚本行为。
- 重复数据:网络抖动导致同一条日志被发送了两次。
- 无效数据:比如用户点击了一个已经下架的商品,或者商品 ID 不存在。
怎么处理?我一般按这个流程来:
数据清洗三步走:
- 过滤:直接删除明显无效的数据。比如 user_id 为空、item_id 不存在的记录。
- 去重:根据 user_id + item_id + behavior_type + timestamp 四个字段去重,保留第一条。
- 修正:对于异常值,比如 duration 超过 1 小时的,直接截断到 3600 秒。
举个例子,我曾经遇到过一个用户,一天之内收藏了 5000 件商品。这正常吗?肯定不正常。这种用户要么是爬虫,要么是竞对来刷数据的。我的做法是:统计每个用户每天的行为次数,超过 3 个标准差以上的,直接标记为异常用户,整条数据剔除。
注意: 清洗数据时,不要一刀切。比如用户停留时长为 0,可能是用户秒关页面,也可能是页面加载失败。我建议保留这些数据,但单独标记一个“异常”字段,让模型自己去学习。
2.3 数据标准化:让不同行为“站在同一起跑线”
数据洗干净了,但还有个问题:不同行为的“价值”不一样。购买显然比点击重要得多。如果不做处理,模型会认为“点击 100 次”和“购买 1 次”是等价的。这显然不合理。
所以,我们需要对行为进行标准化。说白了,就是给不同行为赋予不同的权重。我个人习惯用经验值:
| 行为类型 | 权重 | 说明 |
|---|---|---|
| click | 1 | 基础行为,表示用户注意到了商品 |
| fav | 3 | 收藏,表示用户有潜在兴趣 |
| cart | 5 | 加购,购买意图非常强烈 |
| buy | 10 | 购买,最终转化行为 |
当然,这个权重不是死的。我在做母婴电商时,发现“收藏”的权重其实比“加购”还高。为什么?因为妈妈们习惯先收藏,等促销再买。所以,权重要根据业务场景动态调整。
除了行为权重,还有时间衰减。用户昨天的行为,比一个月前的行为更有参考价值。我常用的公式是:
# 时间衰减函数
def time_decay(timestamp, current_time, half_life=7*24*3600):
"""
timestamp: 行为发生时间戳
current_time: 当前时间戳
half_life: 半衰期,默认7天
"""
delta = current_time - timestamp
weight = 0.5 ** (delta / half_life)
return weight
# 最终得分 = 行为权重 * 时间衰减
final_score = behavior_weight * time_decay(timestamp, current_time)
嗯,这里要注意,半衰期的设置很关键。电商大促期间,用户行为变化很快,半衰期可以缩短到 1 天。平时稳定期,可以延长到 14 天。我建议你根据业务数据,跑一下不同半衰期的效果,选一个最优的。
小技巧: 如果你不想手动调参,可以用机器学习方法自动学习时间衰减。比如把时间差作为一个特征,让模型自己去拟合。但说实话,对于大多数场景,经验公式已经够用了。
2.4 实战:一个完整的预处理 Pipeline
最后,我给大家一个完整的预处理流程。这是我个人在项目中常用的,你可以直接拿来用:
import pandas as pd
import numpy as np
def preprocess_logs(df):
"""
用户行为日志预处理
"""
# 1. 过滤无效数据
df = df[df['user_id'].notna()]
df = df[df['item_id'].notna()]
# 2. 去重
df = df.drop_duplicates(subset=['user_id', 'item_id', 'behavior_type', 'timestamp'])
# 3. 处理异常值
# 停留时长超过1小时的,截断
df['duration'] = df['duration'].clip(upper=3600)
# 4. 行为权重映射
weight_map = {'click': 1, 'fav': 3, 'cart': 5, 'buy': 10}
df['behavior_weight'] = df['behavior_type'].map(weight_map)
# 5. 时间衰减
current_time = df['timestamp'].max()
df['time_decay'] = 0.5 ** ((current_time - df['timestamp']) / (7*24*3600))
# 6. 计算最终得分
df['final_score'] = df['behavior_weight'] * df['time_decay']
return df
# 使用示例
raw_data = pd.read_csv('user_logs.csv')
clean_data = preprocess_logs(raw_data)
print(f"原始数据量: {len(raw_data)}, 清洗后数据量: {len(clean_data)}")
你看,整个流程下来,数据从“原油”变成了“精炼油”。后面不管是做协同过滤,还是深度学习,都能直接用了。
好了,这一章就到这里。数据采集和预处理,看似基础,但决定了推荐系统的上限。下一章,我们聊聊特征工程——怎么从这些行为数据里,挖出更有价值的特征。