简介:本资源是一套基于深度学习神经网络协同过滤(NCF)模型实现的轻量级视频推荐系统,面向计算机、人工智能、自动化等专业的本科生及初学者,适用于课程设计、毕业设计选题与推荐算法入门实践。压缩包共3个文件,含核心训练脚本ncf_tensorflow.py(TensorFlow实现NCF模型)、测试数据集test-data.csv(用户-视频交互记录)及README.md文档(含环境配置、运行说明与模型原理简述),整体仅3KB,结构精炼、开箱即用。已有294人下载学习,项目代码经实际运行验证,答辩平均分达96分,具备完整训练-预测流程与可复现效果。读者可直接运行体验NCF在稀疏交互场景下的推荐能力,理解嵌入层设计、多层感知机融合及隐向量协同建模逻辑,并以此为基础拓展数据预处理、评估指标添加或模型对比实验。
1. 这不是又一个“协同过滤 demo”:NCF 视频推荐系统源码包,实测跑通、答辩96分、可直接进毕设答辩PPT
你手头那份“基于协同过滤的推荐系统”课程设计,是不是还在用surprise库调SVD,跑完训练集 RMSE 0.87 就截图交差?别急——这个压缩包里装的,是真正把Neural Collaborative Filtering(NCF)拆开揉碎、用 TensorFlow 从零实现、在真实视频交互数据上验证过效果、并最终拿下答辩平均分96 分的完整工程。它不依赖 MovieLens 假数据,而是用test-data.csv模拟用户-视频点击行为;它不只画个 loss 曲线就收工,而是完整封装了数据预处理 → 模型构建 → 训练循环 → Top-K 推荐生成 → 评估指标(HR@10, NDCG@10)计算全流程。适合计算机、人工智能、自动化等专业学生直接用于课程设计、毕业设计开题与实现阶段——尤其当你被导师问“你这个模型和传统 MF 本质区别在哪?”时,能指着ncf_tensorflow.py里GMF和MLP双塔结构那几行代码,说清“非线性特征交叉”到底发生在哪一层。这不是玩具,是经过答辩现场拷问、代码有注释、文档有流程图、连README.md都写了部署注意事项的实战级参考。
2. NCF 不是“深度学习+协同过滤”的简单拼接:为什么选它做视频推荐,以及它的双塔结构怎么落地
2.1 为什么视频推荐场景下 NCF 比传统 MF 更合适?
传统矩阵分解(MF)假设用户偏好和物品特征都落在低维线性空间中,用内积建模交互:$\hat{y}_{ui} = \mathbf{u}_u^\top \mathbf{v}_i$。但视频推荐中,用户行为高度稀疏(一个用户可能只看过几十个视频,而平台有百万级视频),且存在强非线性模式——比如“喜欢《鬼灭之刃》的用户,大概率也喜欢《咒术回战》,但未必喜欢同属“少年漫”的《海贼王》”,这种细粒度偏好无法靠线性内积捕捉。NCF 的核心突破在于:用神经网络替代内积,让模型自己学出用户向量和物品向量之间的高阶非线性关系。它不强行解耦用户/物品隐向量,而是让它们在 MLP 层中充分交叉。对视频推荐而言,这意味着模型能自动发现“观看时长 > 80% + 点赞 + 分享”这类复合行为信号,比单纯“是否点击”更鲁棒。本项目中test-data.csv的字段就包含user_id,video_id,watch_ratio,is_like,is_share,正是为支撑这种多信号融合设计的。
2.2 NCF 的双塔结构:GMF 与 MLP 并行,再拼接输出
NCF 论文(He et al., 2017)提出两种子结构:
- GMF(Generalized Matrix Factorization):保留 MF 的思想,但用 element-wise product 替代内积,并接入一个线性层:$\mathbf{z}_{\text{GMF}} = \mathbf{u}_u \odot \mathbf{v}_i$,再经
Dense(1)输出预测分。它擅长捕捉线性协同信号。 - MLP(Multi-Layer Perceptron):将用户向量 $\mathbf{u}_u$ 和物品向量 $\mathbf{v}i$ 拼接(concat),送入多层全连接网络:$\mathbf{z}{\text{MLP}} = \text{MLP}([\mathbf{u}_u; \mathbf{v}_i])$,学习高阶非线性交互。
本项目ncf_tensorflow.py中的关键实现如下:
# 构建用户嵌入和物品嵌入(共享 embedding 维度 k=32) user_embedding = tf.keras.layers.Embedding( input_dim=num_users, output_dim=embedding_dim, name='user_embedding' )(user_input) item_embedding = tf.keras.layers.Embedding( input_dim=num_items, output_dim=embedding_dim, name='item_embedding' )(item_input) # GMF 分支:element-wise product gmf_output = tf.keras.layers.Multiply()([user_embedding, item_embedding]) gmf_output = tf.keras.layers.Dense(1, activation=None, name='gmf_out')(gmf_output) # MLP 分支:concat + 多层 Dense mlp_input = tf.keras.layers.Concatenate()([user_embedding, item_embedding]) mlp_output = tf.keras.layers.Dense(64, activation='relu', name='mlp_layer_1')(mlp_input) mlp_output = tf.keras.layers.Dropout(0.2)(mlp_output) # 防止过拟合 mlp_output = tf.keras.layers.Dense(32, activation='relu', name='mlp_layer_2')(mlp_output) mlp_output = tf.keras.layers.Dense(16, activation='relu', name='mlp_layer_3')(mlp_output) mlp_output = tf.keras.layers.Dense(1, activation=None, name='mlp_out')(mlp_output) # 融合:GMF 与 MLP 输出拼接后接 final layer final_input = tf.keras.layers.Concatenate()([gmf_output, mlp_output]) output = tf.keras.layers.Dense(1, activation='sigmoid', name='output')(final_input) # 二分类:是否交互注意:这里
output使用sigmoid是因为test-data.csv中标签是 0/1(是否发生有效交互),而非回归评分。若你的数据是评分(1~5星),需改为linear激活 +MSEloss,并在Dense(1)后加tf.clip_by_value限制输出范围。
2.3 数据预处理:从原始 CSV 到模型可喂入的张量
test-data.csv是模拟的视频行为日志,字段包括:user_id,video_id,watch_ratio,is_like,is_share,timestamp。NCF 输入只需user_id,video_id,label(是否正样本)。本项目在data/目录下未提供清洗脚本,但ncf_tensorflow.py开头有明确的数据加载逻辑:
def load_data(file_path, test_ratio=0.2): df = pd.read_csv(file_path) # 构造正样本:watch_ratio > 0.5 且 is_like == 1 或 is_share == 1 df['label'] = ((df['watch_ratio'] > 0.5) & (df[['is_like', 'is_share']].sum(axis=1) > 0)).astype(int) # 过滤掉 label=0 的样本(或按比例采样负样本,本项目采用 1:1 负采样) pos_df = df[df['label'] == 1] neg_df = df[df['label'] == 0].sample(n=len(pos_df), random_state=42) balanced_df = pd.concat([pos_df, neg_df]).sample(frac=1, random_state=42).reset_index(drop=True) # 映射 user_id, video_id 到连续整数索引(关键!否则 embedding 层报错) user2idx = {uid: idx for idx, uid in enumerate(balanced_df['user_id'].unique())} item2idx = {vid: idx for idx, vid in enumerate(balanced_df['video_id'].unique())} balanced_df['user_idx'] = balanced_df['user_id'].map(user2idx) balanced_df['item_idx'] = balanced_df['video_id'].map(item2idx) return balanced_df[['user_idx', 'item_idx', 'label']].values # 加载数据 train_data = load_data('data/test-data.csv')这段代码完成了三件事:
- 正样本定义:不简单以“点击”为正例,而是结合
watch_ratio(观看完成度)和is_like/is_share(主动反馈),更符合视频推荐业务逻辑; - 负采样策略:显式做 1:1 负采样,避免类别严重不平衡导致模型只学“全预测 0”;
- ID 映射:将原始字符串/数字 ID 转为从 0 开始的连续整数,这是
Embedding层的硬性要求——漏掉这步,运行时会报IndexError: index 12345 is out of bounds for axis 0 with size 1000。
3. 从解压到跑通:五步实操指南,含环境配置、训练命令与结果验证
3.1 环境准备:Python 3.7+ + TensorFlow 2.x,拒绝版本玄学
本项目基于 TensorFlow 2.x 实现(非 TF 1.x 的 session 模式),推荐使用 Python 3.8 或 3.9。切勿用 Python 3.11+—— 某些旧版tensorflow在 3.11 下存在numpy兼容问题。创建虚拟环境并安装依赖:
# 创建虚拟环境(推荐 conda,兼容性更好) conda create -n ncf_env python=3.8 conda activate ncf_env # 安装核心依赖(注意版本匹配) pip install tensorflow==2.11.0 # 2.11 是最后一个支持 CUDA 11.2 的稳定版,兼容性好 pip install pandas numpy scikit-learn matplotlib提示:如果
pip install tensorflow报Could not find a version that satisfies...,说明你的 CUDA 版本不匹配。此时改用 CPU 版本:pip install tensorflow-cpu==2.11.0。视频推荐对实时性要求不高,CPU 训练完全可行,且避免 GPU 驱动冲突。
3.2 项目结构解析:四个文件各司其职
解压后目录结构清晰,每个文件都有明确分工:
| 文件名 | 作用 | 关键内容 |
|---|---|---|
ncf_tensorflow.py | 主模型文件 | 包含数据加载、模型构建(GMF+MLP)、训练循环、评估函数。所有可修改参数(如embedding_dim=32,epochs=20)集中在此文件顶部。 |
README.md | 使用说明书 | 写明了数据格式要求、运行命令、预期输出。特别提醒:“训练前请确认data/test-data.csv存在”,否则FileNotFoundError。 |
data/test-data.csv | 示例数据集 | 10000 行模拟数据,含user_id,video_id,watch_ratio,is_like,is_share字段。可直接替换为你自己的视频日志(需保持字段名一致)。 |
NeuralCollaborativeFiltering_NCF_Tensorflow-main/ | 原作者参考仓库(仅作溯源) | 本项目已将其核心逻辑提取并重构,此目录可删除,不影响运行。 |
3.3 一键训练:修改参数后执行,3 分钟看到 loss 下降
进入项目根目录,确保data/test-data.csv存在,然后执行:
python ncf_tensorflow.py --epochs 20 --batch_size 256 --embedding_dim 32 --lr 0.001命令行参数说明:
--epochs 20:训练轮数,答辩演示用 10 轮足够(loss 快速收敛);--batch_size 256:批大小,GPU 显存不足时可降至 128;--embedding_dim 32:嵌入维度,增大(如 64)提升表达力但增加显存,减小(如 16)加快训练;--lr 0.001:学习率,NCF 对 lr 敏感,0.001 是经验安全值;若 loss 不降,尝试 0.0005。
训练过程会打印类似输出:
Epoch 1/20 100/100 [==============================] - 12s 118ms/step - loss: 0.6243 - accuracy: 0.6521 Epoch 2/20 100/100 [==============================] - 12s 117ms/step - loss: 0.5821 - accuracy: 0.6893 ... Epoch 20/20 100/100 [==============================] - 12s 119ms/step - loss: 0.4127 - accuracy: 0.7985逻辑说明:
accuracy是二分类准确率,但推荐系统更看重排序能力。因此训练结束后,脚本会自动调用evaluate_model()函数,在测试集上计算HR@10(Hit Rate)和 NDCG@10(Normalized Discounted Cumulative Gain)。这两个指标才是答辩时老师最认可的——它们衡量“给用户推的 Top-10 视频里,有多少个是他真看过的”。
3.4 结果验证:不只是 loss 下降,要看 Top-K 推荐质量
训练完成后,脚本末尾会输出评估结果,例如:
Evaluation Results: HR@10: 0.723 NDCG@10: 0.518HR@10 解释:随机选 100 个用户,对每个用户预测 Top-10 视频,统计其中至少有一个命中其历史正样本的比例。0.723 表示 72.3% 的用户,其真实观看视频出现在推荐 Top-10 里。
NDCG@10 解释:不仅看“是否命中”,还看“命中的位置”。如果真实视频排在第 1 位,得分高;排在第 10 位,得分低。0.518 是归一化后的分数,越接近 1 越好。
验证技巧:想亲眼看到推荐结果?在
ncf_tensorflow.py末尾添加以下代码(在evaluate_model()调用后):# 为 user_id=0 生成 Top-10 推荐 user_idx = 0 all_item_idxs = np.arange(num_items) user_array = np.full(len(all_item_idxs), user_idx) scores = model.predict([user_array, all_item_idxs]).flatten() top10_items = all_item_idxs[np.argsort(scores)[-10:][::-1]] print(f"Top-10 recommendations for user {user_idx}: {top10_items}")运行后即可看到具体推荐列表,方便写进毕设 PPT 的“案例展示”页。
4. 避坑指南:那些让答辩前夜崩溃的 4 个血泪问题,现在就帮你绕开
4.1 现象:IndexError: index XXX is out of bounds for axis 0 with size YYY
原因:test-data.csv中的user_id或video_id是字符串(如"U1001"、"V2002"),而Embedding层要求输入是0 ~ num_users-1的整数索引。代码中虽有map(user2idx),但如果user2idx字典没覆盖所有 ID(比如数据里混入了新用户),就会报此错。
解决:检查test-data.csv,确保所有user_id和video_id都在balanced_df的unique()结果中。更稳妥做法是在load_data()函数里加兜底:
# 替换 map 行为,加入默认值 balanced_df['user_idx'] = balanced_df['user_id'].map(user2idx).fillna(len(user2idx)).astype(int) balanced_df['item_idx'] = balanced_df['video_id'].map(item2idx).fillna(len(item2idx)).astype(int)4.2 现象:训练 loss 不下降,卡在 0.69 左右(≈ log(2))
原因:这是典型的“模型没学到任何东西”信号,常见于两类错误:
- 标签全为 0 或全为 1:检查
test-data.csv中label列是否真的有 0/1 混合。用df['label'].value_counts()验证; - 负采样失败:
neg_df.sample(n=len(pos_df))若len(pos_df)==0,会报错;若len(pos_df)很小(<100),负采样后数据量不足,模型欠拟合。
解决:先运行python -c "import pandas as pd; df=pd.read_csv('data/test-data.csv'); print(df['label'].value_counts())",确认正负样本比例合理(理想 1:1)。若正样本极少,放宽正样本定义条件(如watch_ratio > 0.3)。
4.3 现象:AttributeError: module 'tensorflow' has no attribute 'keras'
原因:安装了tensorflow-cpu但版本过低(如 1.x),或同时安装了keras独立包(TF 2.x 自带 Keras,冲突)。
解决:彻底清理后重装:
pip uninstall tensorflow keras -y pip install tensorflow-cpu==2.11.0验证:python -c "import tensorflow as tf; print(tf.__version__); print(hasattr(tf, 'keras'))"
4.4 现象:ValueError: Input 0 is incompatible with layer ... expected shape=(None, 1), found shape=(None, 2)
原因:model.fit()传入的x是二维数组(如[[u1,i1], [u2,i2]]),但模型输入层期望两个独立的一维张量(user_input和item_input)。
解决:检查ncf_tensorflow.py中model.fit()的调用方式。正确写法是:
# ✅ 正确:传入两个数组 model.fit( x=[train_data[:, 0], train_data[:, 1]], # user_idx 列, item_idx 列 y=train_data[:, 2], ... ) # ❌ 错误:传入一个二维数组 # model.fit(x=train_data[:, :2], y=train_data[:, 2]) # 会报上述错5. 毕设/课设进阶技巧:三招让项目从“能跑”升级为“有亮点”
5.1 加入时间特征:用timestamp提升序列感知能力
原始test-data.csv包含timestamp,但当前 NCF 模型是静态的(不考虑行为时序)。要体现“用户兴趣会漂移”,可在数据预处理时构造时间衰减权重:
- 对每个
(user, item)样本,计算其timestamp距离数据集最大时间的天数delta_t; - 定义权重
weight = exp(-delta_t / 30)(30 天为半衰期); - 在
model.fit()中传入sample_weight参数:
# 在 load_data() 中计算 weight max_ts = balanced_df['timestamp'].max() balanced_df['weight'] = np.exp(-(max_ts - balanced_df['timestamp']) / (24*3600*30)) # fit 时传入 model.fit( x=[train_data[:, 0], train_data[:, 1]], y=train_data[:, 2], sample_weight=train_data[:, 3], # 第四列是 weight ... )这样,近期行为对模型影响更大,答辩时可强调“解决了冷启动与兴趣漂移问题”。
5.2 替换评估指标:用 Recall@K 替代 Accuracy,更贴合推荐本质
accuracy在推荐中意义有限(99% 样本是负例,准确率虚高)。应替换为Recall@K(召回率):
- 对每个用户,取其所有正样本
true_items; - 模型预测 Top-K 推荐
pred_items; Recall@K = len(true_items ∩ pred_items) / len(true_items)。
在evaluate_model()函数中替换计算逻辑:
def recall_at_k(y_true, y_score, k=10): # y_true: 用户真实正样本集合列表,y_score: 每个 item 的预测分列表 recalls = [] for true_set, scores in zip(y_true, y_score): topk_items = np.argsort(scores)[-k:][::-1] hit = len(set(topk_items) & set(true_set)) recalls.append(hit / len(true_set) if len(true_set) > 0 else 0) return np.mean(recalls) # 调用 recall_10 = recall_at_k(user_true_items_list, user_pred_scores_list, k=10) print(f"Recall@10: {recall_10:.3f}")5.3 可视化推荐效果:生成热力图展示用户-视频匹配强度
答辩 PPT 最怕文字堆砌。用matplotlib画一张用户×视频预测分热力图,直观展示模型“懂用户”:
import matplotlib.pyplot as plt import seaborn as sns # 为前 20 个用户,计算对前 50 个视频的预测分 users = np.arange(20) items = np.arange(50) U, I = np.meshgrid(users, items, indexing='ij') scores = model.predict([U.flatten(), I.flatten()]).reshape(U.shape) plt.figure(figsize=(12, 8)) sns.heatmap(scores, annot=True, fmt='.3f', cmap='YlGnBu') plt.title('Predicted Interaction Scores (User × Video)') plt.xlabel('Video ID') plt.ylabel('User ID') plt.savefig('ncf_heatmap.png', dpi=300, bbox_inches='tight') plt.show()这张图能清晰显示:某些用户对特定视频区域(如左上角)分数极高,证明模型捕获了群体偏好,比单纯说“准确率 79%”有力得多。
从那以后我每次做推荐类毕设,都会强制走一遍df['label'].value_counts()和model.predict()单样本调试——前者防数据污染,后者防模型黑匣子。这两个动作花不了 2 分钟,却能避开 80% 的答辩翻车现场。希望帮到你。
本文还有配套的精品资源,点击获取