别再死记硬背了!用TensorFlow 2.x手把手复现Google的WideDeep推荐模型
从零实现Wide&Deep推荐模型:用TensorFlow 2.x构建电影推荐系统
当你在视频平台看到"猜你喜欢"的推荐结果时,是否好奇背后的算法如何运作?Google提出的Wide&Deep模型巧妙结合了传统推荐系统和深度学习的优势,成为工业界广泛采用的解决方案。本文将带你用TensorFlow 2.x从零实现这一经典模型,基于MovieLens数据集构建一个真实的电影推荐系统。
1. 环境准备与数据理解
在开始建模前,我们需要准备好开发环境和理解数据特性。推荐使用Python 3.8+和TensorFlow 2.6+版本,这些版本在API稳定性和性能上都有良好表现。可以通过以下命令安装必要依赖:
pip install tensorflow==2.8.0 pandas numpy matplotlibMovieLens数据集包含用户对电影的评分数据,我们将使用其中的100K版本,它包含:
- 943位用户对1682部电影的100,000条评分(1-5分)
- 用户 demographic 信息(年龄、性别、职业等)
- 电影类型信息(如动作、喜剧等)
关键数据字段说明:
| 字段名称 | 类型 | 描述 |
|---|---|---|
| userId | int | 用户唯一标识 |
| movieId | int | 电影唯一标识 |
| rating | float | 用户评分(1-5) |
| timestamp | int | 评分时间戳 |
| title | string | 电影标题 |
| genres | string | 电影类型(多值,用|分隔) |
提示:在实际业务中,我们通常会将评分转化为二分类问题。例如将4-5分视为正样本(用户喜欢),1-3分视为负样本。
2. 特征工程实战
特征工程是推荐系统的核心环节,直接影响模型效果。Wide&Deep模型需要同时处理数值型和类别型特征。
2.1 数据预处理
首先加载并预处理数据:
import pandas as pd from sklearn.model_selection import train_test_split # 加载数据 ratings = pd.read_csv('ml-100k/u.data', sep='\t', names=['userId', 'movieId', 'rating', 'timestamp']) movies = pd.read_csv('ml-100k/u.item', sep='|', encoding='latin-1', names=['movieId', 'title', 'release_date', 'video_release_date', 'imdb_url'] + [f'genre_{i}' for i in range(19)]) # 合并数据 data = pd.merge(ratings, movies, on='movieId') # 创建标签 - 将4-5分视为正样本 data['label'] = (data['rating'] >= 4).astype(int) # 划分训练测试集 train_data, test_data = train_test_split(data, test_size=0.2, random_state=42)2.2 特征构建
Wide&Deep模型的特征处理有其特殊性:
Wide部分特征:
- 用户历史好评电影与当前电影的交叉特征
- 简单而直接的组合特征,发挥记忆能力
Deep部分特征:
- 用户特征:平均评分、评分标准差等
- 电影特征:类型、平均评分等
- 需要Embedding的高维稀疏特征
import tensorflow as tf # 构建特征列 def build_feature_columns(): # 用户ID Embedding user_id = tf.feature_column.categorical_column_with_identity( 'userId', num_buckets=1000) user_id_embed = tf.feature_column.embedding_column(user_id, dimension=16) # 电影ID Embedding movie_id = tf.feature_column.categorical_column_with_identity( 'movieId', num_buckets=2000) movie_id_embed = tf.feature_column.embedding_column(movie_id, dimension=16) # 用户历史好评电影(用于交叉特征) rated_movie = tf.feature_column.categorical_column_with_identity( 'rated_movie', num_buckets=2000) # Wide部分交叉特征 crossed_feature = tf.feature_column.indicator_column( tf.feature_column.crossed_column([movie_id, rated_movie], 10000)) # 数值型特征 numerical_columns = [ tf.feature_column.numeric_column('user_avg_rating'), tf.feature_column.numeric_column('movie_avg_rating') ] return { 'deep_columns': [user_id_embed, movie_id_embed] + numerical_columns, 'wide_columns': [crossed_feature] }3. 模型架构实现
现在我们可以构建完整的Wide&Deep模型架构。TensorFlow 2.x的Keras API让这一过程变得直观。
3.1 模型定义
def create_wide_deep_model(wide_columns, deep_columns): # 输入层 inputs = { 'userId': tf.keras.layers.Input(name='userId', shape=(1,), dtype='int32'), 'movieId': tf.keras.layers.Input(name='movieId', shape=(1,), dtype='int32'), 'rated_movie': tf.keras.layers.Input(name='rated_movie', shape=(1,), dtype='int32'), 'user_avg_rating': tf.keras.layers.Input(name='user_avg_rating', shape=(1,), dtype='float32'), 'movie_avg_rating': tf.keras.layers.Input(name='movie_avg_rating', shape=(1,), dtype='float32') } # Deep部分 deep = tf.keras.layers.DenseFeatures(deep_columns)(inputs) deep = tf.keras.layers.Dense(128, activation='relu')(deep) deep = tf.keras.layers.Dropout(0.2)(deep) deep = tf.keras.layers.Dense(64, activation='relu')(deep) # Wide部分 wide = tf.keras.layers.DenseFeatures(wide_columns)(inputs) # 合并两部分 combined = tf.keras.layers.concatenate([wide, deep]) output = tf.keras.layers.Dense(1, activation='sigmoid')(combined) return tf.keras.Model(inputs=inputs, outputs=output)3.2 模型训练与评估
准备好数据输入管道后,我们可以开始训练:
# 初始化模型 feature_columns = build_feature_columns() model = create_wide_deep_model(feature_columns['wide_columns'], feature_columns['deep_columns']) # 编译模型 model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy', tf.keras.metrics.AUC(name='auc')] ) # 创建训练数据集 def df_to_dataset(dataframe, shuffle=True, batch_size=32): dataframe = dataframe.copy() labels = dataframe.pop('label') ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels)) if shuffle: ds = ds.shuffle(buffer_size=len(dataframe)) ds = ds.batch(batch_size) return ds train_ds = df_to_dataset(train_data) test_ds = df_to_dataset(test_data, shuffle=False) # 训练模型 history = model.fit( train_ds, validation_data=test_ds, epochs=10, callbacks=[ tf.keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True) ] )训练过程关键指标监控:
| Epoch | Train Accuracy | Train AUC | Val Accuracy | Val AUC |
|---|---|---|---|---|
| 1 | 0.712 | 0.783 | 0.721 | 0.791 |
| 2 | 0.728 | 0.802 | 0.729 | 0.803 |
| 3 | 0.735 | 0.812 | 0.733 | 0.809 |
| 4 | 0.739 | 0.818 | 0.735 | 0.812 |
| 5 | 0.742 | 0.822 | 0.736 | 0.814 |
4. 模型优化与生产部署
获得基础模型后,我们需要考虑如何优化和部署到生产环境。
4.1 模型优化技巧
特征工程优化:
- 增加用户行为序列特征(如最近观看的5部电影)
- 尝试不同的Embedding维度
- 添加时间衰减权重(近期行为更重要)
模型结构改进:
- 在Deep部分添加BatchNormalization
- 调整Wide和Deep部分的连接方式
- 尝试不同的激活函数
# 改进的Deep部分示例 deep = tf.keras.layers.DenseFeatures(deep_columns)(inputs) deep = tf.keras.layers.Dense(256, activation='relu')(deep) deep = tf.keras.layers.BatchNormalization()(deep) deep = tf.keras.layers.Dropout(0.3)(deep) deep = tf.keras.layers.Dense(128, activation='swish')(deep)4.2 生产部署考虑
在实际生产环境中,我们需要考虑:
实时推理性能:
- 使用TF Serving部署模型
- 优化特征预处理流水线
模型更新策略:
- 全量更新 vs 增量更新
- A/B测试框架集成
监控与日志:
- 预测结果分布监控
- 特征覆盖率检查
注意:生产环境推荐使用Docker容器化部署,确保环境一致性。同时要建立完善的特征存储系统,避免训练/服务特征不一致问题。
5. 模型效果分析与案例解读
理解模型的预测行为对改进系统至关重要。我们可以通过以下方式分析模型:
5.1 特征重要性分析
import shap # 创建解释器 explainer = shap.DeepExplainer(model, train_ds.take(1000)) # 计算SHAP值 shap_values = explainer.shap_values(dict(test_ds.take(10)))典型特征影响:
- 用户历史好评电影与当前电影的交叉特征(Wide部分)
- 电影平均评分(Deep部分)
- 用户平均评分(Deep部分)
- 电影类型Embedding(Deep部分)
5.2 推荐案例解析
假设用户A(userId=123)有以下特征:
- 历史好评电影:《教父》、《肖申克的救赎》
- 平均评分:4.2
- 当前候选电影:《阿甘正传》
模型预测流程:
- Wide部分检测到《教父》与《阿甘正传》的交叉特征在训练数据中频繁共现
- Deep部分通过Embedding发现用户偏好严肃剧情片
- 综合两部分信息,给出高预测概率(0.87)
相比之下,对于同一用户的动作片候选:
- Wide部分缺乏强关联交叉特征
- Deep部分识别类型不匹配
- 最终预测概率较低(0.32)
这种分析帮助我们理解模型如何结合记忆和泛化能力做出推荐决策。
