NCF¶
NCF 用神经网络替代传统矩阵分解里的点积。
矩阵分解通常是 user_embedding dot item_embedding。这个做法简单、快,但它默认用户和电影因子的交互方式比较线性。NCF 想问的是:能不能让 MLP 自己学一个更复杂的交互函数?
flowchart LR
U[userId] --> UE[用户 embedding]
M[movieId] --> ME[电影 embedding]
UE --> Cat[拼接]
ME --> Cat
Cat --> MLP[多层感知机]
MLP --> Score[预测分数]
在 MovieLens 上,NCF 通常输入用户 ID 和电影 ID,查出两个 embedding,拼接后送进全连接网络。目标可以是评分预测,也可以把高评分当成喜欢做二分类。
第一版建议对比三个模型:
- 矩阵分解
- 只有 MLP 的 NCF
- GMF 加 MLP 的组合版本
NCF 很适合用来理解一件事:深度模型不一定天然更强。如果数据切分小,或者负采样做得不好,简单的矩阵分解可能很难被超过。
它和矩阵分解到底差在哪¶
矩阵分解的核心交互是点积:
score = user_embedding dot movie_embedding
点积很像在问:用户向量和电影向量方向接近吗?接近就分高。
NCF 换了一个问题:把用户向量和电影向量交给一个神经网络,让网络自己判断它们怎么组合才合理。
这让模型更灵活,但也更难训练。灵活不是免费午餐。数据不够、负样本不好、模型太大时,NCF 可能只是更会记训练集。
一条训练样本长什么样¶
假设用户 42 给 The Matrix 打了 5 分。做二分类时,可以把它变成正样本:
userId = 42
movieId = The Matrix
label = 1
再采样一部用户 42 没评分过的电影当负样本:
userId = 42
movieId = Random Movie
label = 0
模型会分别查 embedding:
| 输入 | 查到的向量 |
|---|---|
| userId 42 | 用户 embedding |
| The Matrix | 电影 embedding |
然后把两个向量拼起来,送进 MLP,输出一个 0 到 1 之间的分数。分数越高,表示模型越认为用户会喜欢这部电影。
GMF 加 MLP 是什么¶
NCF 论文里常见一个组合结构:一边保留类似矩阵分解的 GMF,一边使用 MLP,最后把两边结果合起来。
flowchart TB
U[userId] --> U1[GMF 用户向量]
I[movieId] --> I1[GMF 电影向量]
U --> U2[MLP 用户向量]
I --> I2[MLP 电影向量]
U1 --> GMF[逐元素相乘]
I1 --> GMF
U2 --> Cat[拼接后进 MLP]
I2 --> Cat
GMF --> Join[合并]
Cat --> Join
Join --> Score[预测]
这样做的意思是:既保留点积类模型的简单协同信号,也让 MLP 学复杂交互。
怎么判断 NCF 有没有学到东西¶
不要只看 loss。拿几个用户打印推荐列表:
| 用户历史高评分 | NCF 推荐 |
|---|---|
| The Matrix, Inception, Interstellar | Blade Runner, Arrival, The Dark Knight |
如果推荐全是热门电影,可能模型学到了流行度,但个性化不够。
如果推荐和用户历史完全没关系,先检查负采样、ID 映射、训练集和测试集切分。
运行¶
默认全量运行:
./04-deep-ranking/ncf/run.sh --sample-ratings none --num-workers 8 --save-checkpoints --checkpoint-every 0
【非主线】想先快速试跑:
./04-deep-ranking/ncf/run.sh --sample-ratings 2000000 --num-workers 8 --save-checkpoints --checkpoint-every 0
默认命令只保存 checkpoints/best.pt。报告会写入验证指标、测试集预测样例和 checkpoint 大小。
常见坑¶
不要把所有没评分电影都当负样本。那样负样本数量太大,而且语义也不准。
不要一开始堆很深的 MLP。两三层足够做第一版。
不要忘记和矩阵分解对比。NCF 的意义不是名字更高级,而是它在同一套数据切分和指标下真的带来提升。