用Python实战处理KuaiRec数据集:从下载到稀疏矩阵转换的完整流程
KuaiRec作为推荐系统领域罕见的99.6%高密度数据集,为研究曝光偏差和矩阵补全提供了绝佳实验场。本文将手把手带你完成从数据下载到特征工程的完整pipeline,特别针对实际编码中可能遇到的video_id 1225缺失等陷阱提供解决方案。所有代码均经过Colab环境验证,可直接复制到你的Jupyter Notebook中运行。
1. 环境准备与数据获取
推荐使用Python 3.8+和以下依赖库组合,避免版本冲突:
pip install pandas==1.4.2 scikit-learn==1.0.2 scipy==1.8.0数据集下载后目录结构应如下所示:
KuaiRec/ ├── data/ │ ├── big_matrix.csv # 主数据集 │ ├── small_matrix.csv # 全曝光子集 │ ├── item_feat.csv # 视频标签特征 │ └── social_network.csv # 用户社交关系常见踩坑点:
- 解压后检查文件编码应为UTF-8,否则读取时需指定
encoding='gb18030' - 原始列名可能包含
photo_id(旧版)或video_id(新版),建议统一处理
2. 数据加载与探索性分析
使用pandas加载数据时,建议采用内存优化策略:
import pandas as pd def load_large_csv(path, usecols=None): dtype = { 'user_id': 'int32', 'video_id': 'int32', 'watch_ratio': 'float16' } return pd.read_csv(path, usecols=usecols, dtype=dtype) df_big = load_large_csv('KuaiRec/data/big_matrix.csv') print(f"数据集维度: {df_big.shape}") print(f"用户数: {df_big.user_id.nunique()}") print(f"视频数: {df_big.video_id.nunique()}")关键统计量对比表:
| 指标 | Big Matrix | Small Matrix |
|---|---|---|
| 用户数 | 7,176 | 1,411 |
| 视频数 | 10,729 | 3,327 |
| 密度 | 13.4% | 99.6% |
| 记录数 | 12,530,806 | 4,676,570 |
3. 数据清洗与特殊值处理
针对video_id 1225缺失问题,建议在预处理阶段建立黑名单机制:
# 定义无效视频ID集合(可扩展) BLACKLIST_VIDEOS = {1225} def clean_data(df): # 处理异常观看比例(超过500%的截断到5) df['watch_ratio'] = df['watch_ratio'].clip(upper=5) # 过滤黑名单视频 df = df[~df['video_id'].isin(BLACKLIST_VIDEOS)] # 处理重复记录(取平均观看比例) df = df.groupby(['user_id', 'video_id'])['watch_ratio'].mean().reset_index() return df df_clean = clean_data(df_big)注意:当进行负采样时,需要跳过黑名单中的视频ID,避免生成无效样本
4. 特征工程实战技巧
4.1 视频标签特征处理
原始item_feat.csv需要转换为one-hot编码格式:
def process_item_features(feat_path): # 读取原始特征(每个视频最多4个标签) raw_feat = pd.read_csv(feat_path) # 标签索引转换为整数 tag_cols = [f'tag_{i}' for i in range(4)] for col in tag_cols: raw_feat[col] = raw_feat[col].fillna(-1).astype(int) + 1 # 生成标签出现频次统计 tag_counts = pd.value_counts(raw_feat[tag_cols].values.ravel()) print(f"Top 5热门标签:\n{tag_counts.head()}") return raw_feat item_feat = process_item_features('KuaiRec/data/item_feat.csv')4.2 社交特征融合方法
社交网络数据需要转换为邻接矩阵:
from scipy.sparse import csr_matrix def build_social_matrix(network_path, user_ids): social_df = pd.read_csv(network_path) # 创建用户ID到矩阵索引的映射 user_idx = {uid: i for i, uid in enumerate(sorted(user_ids))} # 构建稀疏邻接矩阵 rows = social_df['user_id_from'].map(user_idx) cols = social_df['user_id_to'].map(user_idx) data = np.ones(len(social_df)) return csr_matrix((data, (rows, cols)), shape=(len(user_ids), len(user_ids))) social_mat = build_social_matrix( 'KuaiRec/data/social_network.csv', df_clean['user_id'].unique() )5. 稀疏矩阵转换高级技巧
5.1 高效内存映射方案
对于大型矩阵,推荐使用scipy的coo_matrix格式暂存:
from sklearn.preprocessing import LabelEncoder from scipy.sparse import coo_matrix # 创建用户和视频的编码器 user_encoder = LabelEncoder() video_encoder = LabelEncoder() user_ids = user_encoder.fit_transform(df_clean['user_id']) video_ids = video_encoder.fit_transform(df_clean['video_id']) # 构建COO格式稀疏矩阵 sparse_coo = coo_matrix( (df_clean['watch_ratio'], (user_ids, video_ids)), shape=(len(user_encoder.classes_), len(video_encoder.classes_)) ) # 转换为CSR格式便于后续计算 sparse_csr = sparse_coo.tocsr()5.2 分块存储策略
当矩阵过大无法一次性载入内存时,可采用分块处理:
def save_sparse_chunks(matrix, chunk_size=1000000): for i in range(0, matrix.shape[0], chunk_size): chunk = matrix[i:i+chunk_size] save_npz(f'sparse_chunk_{i}.npz', chunk) # 加载时按需读取 def load_sparse_chunks(pattern='sparse_chunk_*.npz'): from scipy.sparse import vstack import glob chunks = [load_npz(f) for f in sorted(glob.glob(pattern))] return vstack(chunks)6. 模型就绪数据封装
最终将处理好的数据封装为PyTorch Dataset类:
import torch from torch.utils.data import Dataset class KuaiRecDataset(Dataset): def __init__(self, sparse_matrix): self.matrix = sparse_matrix self.users, self.items = sparse_matrix.nonzero() self.ratings = sparse_matrix.data def __len__(self): return len(self.ratings) def __getitem__(self, idx): return ( torch.LongTensor([self.users[idx]]), torch.LongTensor([self.items[idx]]), torch.FloatTensor([self.ratings[idx]]) ) # 示例用法 dataset = KuaiRecDataset(sparse_csr) dataloader = torch.utils.data.DataLoader(dataset, batch_size=1024)提示:对于CTR预测任务,可以将watch_ratio二值化(如>0.5视为正样本)