第二章:数据准备与探索

大家好,欢迎来到协同过滤的第二课。

上一章我们聊了协同过滤的基本原理,说白了就是「物以类聚,人以群分」。但有个问题——你拿什么去聚?拿什么去分?

答案就是:数据。

这一章,我们就来聊聊数据准备与探索。我个人觉得,这是整个工程化流程里最枯燥、但也最关键的环节。数据搞不好,模型再花哨也是白搭。

2.1 用户行为数据采集

先说说数据从哪来。

用户行为数据,说白了就是用户和物品之间的交互记录。比如:

  • 用户A 点击了商品X
  • 用户B 购买了商品Y
  • 用户C 给电影Z打了5分

这些数据通常来自日志系统。我在项目中遇到过最典型的情况:业务方说「我们有海量数据」,结果一查,日志字段乱七八糟,时间戳格式都不统一。

常见的采集方式有:

  • 客户端埋点:APP或网页上报用户操作
  • 服务端日志:记录API请求和响应
  • 数据库流水:订单表、收藏表等业务数据
注意: 我曾经踩过一个坑——客户端埋点上报的数据,因为网络延迟,时间戳居然比服务端日志晚了整整2小时。后来我养成了一个习惯:采集时务必记录「客户端时间」和「服务端接收时间」两个字段。

2.2 数据清洗与预处理

数据采集完了,别急着建模。先看看数据干不干净。

我一般会做这几步:

  1. 去重:同一个用户对同一个物品的重复行为,只保留一条
  2. 过滤异常值:比如评分超过5分、时间戳在未来等
  3. 处理缺失值:用户ID为空?直接扔掉
  4. 标准化格式:时间戳统一成Unix时间戳,用户ID统一成字符串

举个例子,一段简单的清洗代码:

import pandas as pd

# 加载原始数据
df = pd.read_csv('user_behavior.csv')

# 去重
df = df.drop_duplicates(subset=['user_id', 'item_id', 'behavior_type'])

# 过滤异常评分
df = df[(df['rating'] >= 1) & (df['rating'] <= 5)]

# 删除缺失值
df = df.dropna(subset=['user_id', 'item_id'])

print(f'清洗后数据量:{len(df)}')

嗯,这里要注意:清洗不是越狠越好。我曾经因为过滤太猛,把一些冷门用户全干掉了,结果模型对长尾物品完全失效。所以清洗前,先看看数据分布。

2.3 隐式反馈与显式反馈

用户行为数据,按反馈形式可以分为两类:

类型 定义 例子 特点
显式反馈 用户主动表达偏好 评分、点赞、收藏 信号强,但数据稀疏
隐式反馈 用户行为间接反映偏好 点击、浏览时长、购买 数据丰富,但噪声大

你想想看,现实中大部分场景都是隐式反馈。用户不会天天给你打分,但会天天点来点去。

我个人习惯把隐式反馈处理成「置信度」或「权重」。比如:

  • 点击 = 1 分
  • 加入购物车 = 3 分
  • 购买 = 5 分

这样就把隐式反馈「量化」了,方便后续建模。

小技巧: 如果数据里有「浏览时长」,可以做个归一化。比如用户A看了10秒,用户B看了100秒,但B可能只是忘了关页面。我一般会取中位数或分位数来截断,避免极端值干扰。

2.4 数据探索性分析(EDA)

EDA 是数据准备里我最喜欢的一步。为什么?因为你能直观地看到数据长什么样。

我通常会做以下几个维度的分析:

2.4.1 用户-物品交互矩阵的稀疏度

协同过滤最怕什么?稀疏!

假设有100万用户、10万物品,交互记录只有1000万条。那矩阵的稀疏度是多少?

稀疏度 = 1 - (交互数 / (用户数 * 物品数))
       = 1 - (10,000,000 / (1,000,000 * 100,000))
       = 1 - 0.0001
       = 99.99%

99.99% 的稀疏度,意味着绝大多数用户和物品之间没有交互。这种情况下,协同过滤的效果会大打折扣。

我记得有一次做电商推荐,稀疏度高达99.999%,模型几乎学不到东西。后来我们不得不引入内容特征做冷启动。

2.4.2 用户行为分布

看看每个用户有多少行为:

user_behavior_count = df.groupby('user_id').size()
print(user_behavior_count.describe())

输出可能长这样:

count    100000.00
mean         10.50
std          25.30
min           1.00
25%           2.00
50%           5.00
75%          12.00
max        5000.00

看到没?大部分用户只有个位数行为,但少数「重度用户」有几千条。这种长尾分布非常典型。

关键洞察: 如果50%的用户行为数少于5,那这些用户的推荐质量很难保证。我一般会设置一个阈值(比如行为数 >= 3),过滤掉「僵尸用户」。

2.4.3 物品流行度分布

同理,看看物品被交互的次数:

item_popularity = df.groupby('item_id').size()
print(item_popularity.describe())

你会发现,少数热门物品占据了大部分交互。这就是「二八定律」在推荐系统里的体现。

我曾经遇到一个项目,Top 1% 的物品占了80%的交互。这种情况下,协同过滤很容易偏向热门物品,导致推荐结果缺乏多样性。

2.4.4 时间维度分析

用户行为有没有时间规律?比如:

  • 周末的点击量是不是比工作日高?
  • 晚上10点是不是购物高峰?
  • 某个促销活动期间,行为量有没有暴增?

这些信息对模型训练和评估都很重要。我一般会画个时间序列图,看看数据有没有明显的周期性或异常波动。

import matplotlib.pyplot as plt

df['date'] = pd.to_datetime(df['timestamp'], unit='s')
daily_count = df.groupby(df['date'].dt.date).size()

plt.figure(figsize=(12, 4))
plt.plot(daily_count.index, daily_count.values)
plt.title('每日用户行为量')
plt.show()

2.5 避坑指南

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

  • 数据泄露:千万别把未来的数据当训练集。比如用今天的购买行为去预测昨天的推荐,这属于作弊。我一般会按时间切分数据集。
  • 冷启动用户:新用户没有历史行为,协同过滤直接挂掉。我的做法是:先给默认推荐,等用户攒够5个行为再启用个性化模型。
  • 数据倾斜:热门物品的交互量可能是冷门物品的几万倍。如果不做处理,模型会变成「热门推荐器」。我常用的是采样或加权策略。
重要提醒: 数据准备阶段花的时间,往往比建模还多。别急,慢慢来。数据质量决定了模型的天花板,算法只是逼近这个天花板的手段。

好了,这一章就到这里。下一章我们会正式进入协同过滤的算法实现,包括基于用户的协同过滤和基于物品的协同过滤。到时候见!


公众号:蓝海资料掘金营,微信deep3321