MovieLens个性化推荐系统实战(一):数据洞察与特征工程(数据清洗、特征构建)
1. MovieLens数据集初探第一次接触MovieLens数据集时我被它的完整性惊艳到了。这个由GroupLens实验室维护的经典数据集包含了真实用户对电影的真实评分记录就像一座未经开采的金矿。我使用的ml-1m版本包含6000名用户对4000部电影的100万条评分足够丰富又不会让初学者望而生畏。数据集主要包含三个文件users.dat用户 demographic 信息性别、年龄、职业、邮编movies.dat电影元数据片名、类型ratings.dat用户-电影评分矩阵用户ID、电影ID、评分、时间戳用pandas加载这些数据时有个小坑需要注意原始文件使用::作为分隔符这在CSV中并不常见。我第一次读取时直接用了默认逗号分隔结果所有数据都挤在第一列。正确的打开方式是这样的import pandas as pd # 用户数据 user_cols [user_id, gender, age, occupation, zip] users pd.read_csv(users.dat, sep::, namesuser_cols, enginepython) # 电影数据 movie_cols [movie_id, title, genres] movies pd.read_csv(movies.dat, sep::, namesmovie_cols, enginepython, encodingISO-8859-1) # 评分数据 rating_cols [user_id, movie_id, rating, timestamp] ratings pd.read_csv(ratings.dat, sep::, namesrating_cols, enginepython)数据集中的年龄和职业字段都是编码后的数值刚开始看可能有点懵。年龄分段从1开始编码比如1代表Under 1818代表18-24职业编码更丰富0代表other12是programmer15是scientist等。理解这些编码对后续构建用户画像至关重要。2. 数据质量诊断与清洗2.1 缺失值处理拿到数据后我做的第一件事就是检查完整性。意外的是这个数据集出奇的干净用isnull().sum()检查三张表都没有缺失值。这在真实业务场景中几乎是不可能的——通常用户数据会有大量缺失特别是demographic信息。MovieLens可能已经做过预处理但我们在实际项目中一定要做好缺失值应对方案。2.2 异常值检测评分数据的异常值检查特别重要。理论上评分应该是1-5的整数但实际业务中可能有极端值。我用describe()快速查看了评分分布ratings[rating].describe()输出显示最小值1最大值5完全符合预期。为了更直观我还画了箱线图确认没有离群点import seaborn as sns sns.boxplot(xratings[rating])2.3 重复记录处理重复记录会严重影响推荐效果。检查重复时我发现一个细节单纯用duplicated()检查评分表会漏掉用户对同一电影多次评分的情况可能发生在不同时间。正确的做法是检查user_id和movie_id的组合是否唯一print(f重复评分记录: {ratings.duplicated(subset[user_id,movie_id]).sum()})结果显示为零说明数据集在这方面也很干净。不过在实际业务中这种情况很常见通常需要保留最新评分或计算平均分。3. 特征工程实战3.1 电影特征构建原始数据中的电影类型是个宝藏字段但存储格式是Action|Adventure|Sci-Fi这样的管道分隔字符串。我把它展开成多列二元特征# 类型独热编码 genres movies[genres].str.get_dummies(sep|) movies pd.concat([movies, genres], axis1)这样每部电影就有了18维的类型特征向量。比如《星球大战》对应的Sci-Fi列就是1而Romance列是0。另一个重要特征是电影热度。我通过计算每部电影的评分次数来体现movie_stats ratings.groupby(movie_id)[rating].agg([count,mean]) movies movies.merge(movie_stats, left_onmovie_id, right_indexTrue)这里有个实用技巧为了避免热门电影主导推荐我对评分次数做了对数变换import numpy as np movies[log_count] np.log1p(movies[count])3.2 用户特征工程用户行为特征对推荐系统至关重要。我构建了几个关键特征活跃度特征user_activity ratings.groupby(user_id).agg({ rating: [count,mean], movie_id: nunique }) user_activity.columns [rating_count, avg_rating, unique_movies] users users.merge(user_activity, left_onuser_id, right_indexTrue)类型偏好特征 通过合并评分和电影类型数据可以计算用户对各类电影的偏好程度# 合并用户评分和电影类型 merged ratings.merge(movies, onmovie_id) # 计算用户对各类电影的评分均值 genre_cols movies.columns[-18:-2] # 18个类型列 user_genre_pref merged.groupby(user_id)[genre_cols].mean() users users.merge(user_genre_pref, left_onuser_id, right_indexTrue)人口统计特征 原始的用户年龄和职业字段需要适当编码。年龄可以直接使用原始数值也可以分段one-hot编码职业字段我采用了embedding技术这在后续模型中可以学习到更丰富的语义关系。4. 时间特征与冷启动处理4.1 时间特征提取时间戳字段包含宝贵信息。我将其转换为多个时间维度ratings[date] pd.to_datetime(ratings[timestamp], units) ratings[day_of_week] ratings[date].dt.dayofweek ratings[month] ratings[date].dt.month ratings[hour] ratings[date].dt.hour这些时间特征可以揭示用户行为模式比如周末看电影更多晚上评分更高等。4.2 冷启动问题缓解新用户和新电影缺乏历史数据是个老大难问题。我准备了几个解决方案对于新用户利用注册时收集的基本信息性别、年龄等实施基于内容的推荐直到积累足够行为数据设计引导流程快速收集偏好对于新电影使用元数据类型、导演、演员等计算内容相似度采用热门推荐策略直到积累足够评分结合文本描述和海报图像进行多模态推荐5. 特征选择与重要性分析不是所有特征都同等重要。我使用随机森林做了特征重要性分析from sklearn.ensemble import RandomForestRegressor # 准备特征矩阵X和目标y X users.drop([user_id,zip], axis1) X pd.get_dummies(X, columns[gender,occupation]) y users[avg_rating] # 训练随机森林 model RandomForestRegressor() model.fit(X, y) # 获取特征重要性 importance pd.DataFrame({ feature: X.columns, importance: model.feature_importances_ }).sort_values(importance, ascendingFalse)结果显示用户活跃度rating_count和某些类型偏好如Drama、Comedy对预测用户平均评分最重要。这验证了我们的特征工程方向是正确的。6. 特征存储与监控最后处理好的特征需要妥善存储。我推荐使用Feast这样的特征存储系统它支持特征版本控制线上线下一致性保证特征新鲜度监控对于小规模项目也可以用Parquet文件存储users.to_parquet(processed_users.parquet) movies.to_parquet(processed_movies.parquet) ratings.to_parquet(processed_ratings.parquet)在实际项目中我养成了定期检查特征质量的习惯包括特征分布变化监控特征与目标相关性变化缺失率监控这些实践帮助我在多个推荐系统项目中避免了特征漂移带来的性能下降。