news 2026/6/13 19:56:43

别再死记硬背了!用TensorFlow 2.x手把手复现WideDeep模型(附电影推荐实战代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再死记硬背了!用TensorFlow 2.x手把手复现WideDeep模型(附电影推荐实战代码)

从零实现Wide&Deep模型:用TensorFlow 2.x构建电影推荐系统

在推荐系统领域,Wide&Deep模型就像一位同时拥有超强记忆力和丰富想象力的天才——它能精确记住用户过去的每一个偏好,又能敏锐地发现潜在的新兴趣。这种独特的双重能力,使得它从2016年诞生至今依然是工业界推荐系统的中流砥柱。今天,我们将用TensorFlow 2.x从零开始构建这个经典模型,并应用于MovieLens电影推荐场景,让你不仅理解其设计哲学,更能亲手实现一个完整的推荐系统。

1. 环境准备与数据探索

工欲善其事,必先利其器。在开始模型构建前,我们需要搭建合适的开发环境并深入了解数据特性。推荐使用Python 3.8+和TensorFlow 2.6+版本,这些版本在稳定性和功能支持上达到了最佳平衡。

基础环境配置

pip install tensorflow==2.8.0 pandas numpy matplotlib

MovieLens数据集包含用户对电影的评分、电影元数据和用户属性等信息。我们先对数据进行初步探索:

import pandas as pd # 加载数据集 ratings = pd.read_csv('ratings.csv') movies = pd.read_csv('movies.csv') # 数据概览 print(f"评分记录数: {len(ratings):,}") print(f"独立用户数: {ratings['userId'].nunique():,}") print(f"电影数量: {movies['movieId'].nunique():,}") # 评分分布可视化 ratings['rating'].hist(bins=5)

典型的数据预处理步骤包括:

  • 处理缺失值(如填充平均评分)
  • 将评分转换为二元标签(例如4分以上为正向反馈)
  • 划分训练集和测试集(按时间或随机划分)

关键统计量示例

指标数值
总评分记录25,000,000
用户平均评分次数165
电影平均被评次数72
评分时间跨度1995-2023

2. 特征工程:构建记忆与泛化的基石

Wide&Deep模型的威力很大程度上取决于特征的设计。我们需要精心构造两类特征:Wide部分使用的交叉特征和Deep部分使用的嵌入特征。

2.1 类别型特征处理

电影和用户的ID需要转换为嵌入向量:

import tensorflow as tf # 用户ID嵌入 user_embedding = tf.keras.layers.Embedding( input_dim=num_users + 1, output_dim=32, name='user_embedding' ) # 电影ID嵌入 movie_embedding = tf.keras.layers.Embedding( input_dim=num_movies + 1, output_dim=32, name='movie_embedding' )

对于电影类型这种多值类别特征,我们需要特殊处理:

# 电影类型多值处理示例 genres = tf.keras.layers.StringLookup(vocabulary=genre_list) genre_embedding = tf.keras.layers.Embedding( input_dim=len(genres.get_vocabulary()) + 1, output_dim=8, name='genre_embedding' )

2.2 构造交叉特征

Wide部分的核心是特征交叉,它能显式地捕捉特征组合效应:

# 用户历史好评电影与当前电影的交叉 crossed_feature = tf.feature_column.crossed_column( ['user_rated_movies', 'current_movie'], hash_bucket_size=10000 ) # 转换为指标列 crossed_feature = tf.feature_column.indicator_column(crossed_feature)

提示:交叉特征哈希桶大小的选择需要平衡记忆能力和计算开销,通常建议设为可能唯一组合数的1/10到1/5

2.3 数值型特征标准化

对于评分次数、平均分等数值特征,标准化能提升模型训练稳定性:

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() train['rating_count_scaled'] = scaler.fit_transform(train[['rating_count']]) test['rating_count_scaled'] = scaler.transform(test[['rating_count']])

3. 模型架构:Wide与Deep的完美融合

现在来到最核心的部分——构建Wide&Deep模型架构。我们将使用TensorFlow的Functional API,它比Sequential API更适合构建复杂模型结构。

3.1 输入层设计

首先定义各种特征的输入层:

# 数值型特征输入 numerical_inputs = { 'avg_rating': tf.keras.layers.Input(shape=(1,), dtype=tf.float32, name='avg_rating'), 'rating_count': tf.keras.layers.Input(shape=(1,), dtype=tf.float32, name='rating_count') } # 类别型特征输入 categorical_inputs = { 'user_id': tf.keras.layers.Input(shape=(1,), dtype=tf.int32, name='user_id'), 'movie_id': tf.keras.layers.Input(shape=(1,), dtype=tf.int32, name='movie_id') }

3.2 Deep部分构建

Deep部分通常由多个全连接层组成:

# 嵌入层处理 user_emb = user_embedding(categorical_inputs['user_id']) movie_emb = movie_embedding(categorical_inputs['movie_id']) # 拼接所有特征 deep = tf.keras.layers.concatenate([ tf.keras.layers.Flatten()(user_emb), tf.keras.layers.Flatten()(movie_emb), numerical_inputs['avg_rating'], numerical_inputs['rating_count'] ]) # 全连接层 deep = tf.keras.layers.Dense(256, activation='relu')(deep) deep = tf.keras.layers.BatchNormalization()(deep) deep = tf.keras.layers.Dense(128, activation='relu')(deep)

3.3 Wide部分构建

Wide部分相对简单,主要处理交叉特征:

wide = tf.keras.layers.DenseFeatures(crossed_feature)(categorical_inputs)

3.4 模型组合与编译

将两部分输出拼接后通过最终输出层:

output = tf.keras.layers.concatenate([deep, wide]) output = tf.keras.layers.Dense(1, activation='sigmoid')(output) model = tf.keras.Model( inputs={**numerical_inputs, **categorical_inputs}, outputs=output ) # 编译模型 model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy', tf.keras.metrics.AUC(name='auc')] )

模型结构可视化

tf.keras.utils.plot_model(model, show_shapes=True, show_layer_names=True)

4. 模型训练与调优技巧

有了完整的模型架构后,我们需要关注训练过程中的各种细节和调优技巧。

4.1 数据管道优化

使用TensorFlow Dataset API构建高效数据管道:

def create_dataset(df, batch_size=32): dataset = tf.data.Dataset.from_tensor_slices(( { 'user_id': df['userId'].values, 'movie_id': df['movieId'].values, 'avg_rating': df['avg_rating'].values, 'rating_count': df['rating_count'].values }, df['label'].values )) return dataset.shuffle(10000).batch(batch_size).prefetch(tf.data.AUTOTUNE) train_ds = create_dataset(train_data) val_ds = create_dataset(val_data)

4.2 训练策略

采用分阶段训练策略往往效果更好:

  1. 先冻结Wide部分,只训练Deep部分
  2. 解冻全部参数进行联合训练
  3. 使用学习率衰减策略
# 第一阶段:仅训练Deep部分 for layer in model.layers: if 'wide' in layer.name: layer.trainable = False model.fit(train_ds, epochs=5, validation_data=val_ds) # 第二阶段:联合训练 for layer in model.layers: layer.trainable = True reduce_lr = tf.keras.callbacks.ReduceLROnPlateau( monitor='val_auc', factor=0.2, patience=3, min_lr=1e-5 ) model.fit( train_ds, epochs=20, validation_data=val_ds, callbacks=[reduce_lr] )

4.3 常见问题解决

问题1:Wide部分权重过大

  • 解决方案:对Wide部分输出添加L2正则化
wide = tf.keras.layers.Dense(1, kernel_regularizer='l2')(wide)

问题2:类别不平衡

  • 解决方案:使用加权损失函数
pos_weight = len(neg_samples) / len(pos_samples) model.compile( loss=tf.keras.losses.BinaryCrossentropy( from_logits=False, label_smoothing=0.1 ), loss_weights=[1., pos_weight], ... )

问题3:过拟合

  • 解决方案:增加Dropout层和早停策略
deep = tf.keras.layers.Dropout(0.3)(deep) early_stopping = tf.keras.callbacks.EarlyStopping( monitor='val_auc', patience=5, restore_best_weights=True )

5. 模型评估与线上服务

训练完成后,我们需要全面评估模型性能并考虑如何部署到生产环境。

5.1 离线评估指标

除了常规的准确率和AUC,推荐系统还需要关注:

  • 覆盖率(Coverage)
  • 新颖度(Novelty)
  • 多样性(Diversity)
# 计算Top-K推荐指标 k = 10 predictions = model.predict(test_ds) top_k = np.argsort(predictions, axis=0)[-k:] # 计算覆盖率 unique_items = len(np.unique(top_k)) coverage = unique_items / total_items

5.2 在线A/B测试指标

线上评估通常关注:

  • 点击率(CTR)
  • 转化率(Conversion Rate)
  • 用户停留时长

A/B测试结果示例

指标Wide&Deep纯Deep模型提升
CTR3.2%2.7%+18.5%
转化率1.8%1.5%+20%
平均观看时长12.7min10.3min+23.3%

5.3 模型部署方案

TensorFlow Serving是生产部署的理想选择:

# 保存模型 model.save('wide_deep_model', save_format='tf') # 启动TensorFlow Serving docker run -p 8501:8501 \ --mount type=bind,source=$(pwd)/wide_deep_model,target=/models/wide_deep \ -e MODEL_NAME=wide_deep -t tensorflow/serving

对于需要低延迟的场景,可以考虑模型量化:

converter = tf.lite.TFLiteConverter.from_saved_model('wide_deep_model') converter.optimizations = [tf.lite.Optimize.DEFAULT] tflite_model = converter.convert()

在实际项目中,Wide&Deep模型通常需要与其他策略结合使用。比如可以将模型预测分数与业务规则进行加权融合,或者将模型作为召回阶段的候选生成器,再配合排序模型进行精排。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 13:44:18

PCIe 4.0实战避坑指南:Switch配置、Lane分配与信号完整性那些事儿

PCIe 4.0实战避坑指南:Switch配置、Lane分配与信号完整性那些事儿当你在实验室里第一次点亮搭载PCIe 4.0的设备时,那种16GT/s的高速数据传输带来的兴奋感,很快就会被信号完整性问题带来的调试噩梦所取代。这不是一篇教科书式的协议解析&#…

作者头像 李华
网站建设 2026/6/12 10:42:46

推文主题建模实战:突破LDA局限的BTM+语义增强方案

1. 项目概述:为什么在推文上做主题建模,不是“换个数据跑LDA”那么简单你手头有一堆推文——每条平均23个词,带URL、用户名、#话题标签、emoji、缩写(ur, w/ , imo)、拼写错误(thx, lolz)、还有…

作者头像 李华
网站建设 2026/6/12 5:24:51

中小企业AI安全自检清单:聚焦业务流韧性与数据主权

1. 这不是“AI会不会取代你”的焦虑贩卖,而是老板和运营者必须亲手做的安全体检“你的业务安全吗?——来自AI的挑战”这个标题,我第一次看到时心里咯噔一下。不是因为害怕AI,而是因为太熟悉这种问法背后藏着的模糊地带&#xff1a…

作者头像 李华
网站建设 2026/6/13 13:25:38

Linux zone 体系设计:物理内存为什么要分区

本篇目标:理解 Linux 为什么要把物理内存划分为不同的 zone,zone 如何参与 buddy allocator、GFP 分配、NUMA fallback、内存回收和热插拔,以及 ZONE_DMA、ZONE_NORMAL、ZONE_MOVABLE、ZONE_DEVICE 等 zone 分别解决什么问题。1. 问题背景&am…

作者头像 李华