推荐算法之:FNN、PNN、ONN、NFM深度神经网络模型详解
模型核心比喻如何做特征交叉?特点与场景FNN两步走学徒先用FM预训练,再用DNN学习起点高,训练快。是早期思想的代表。PNN主动红娘在输入后立即加入“乘积层”显式捕捉二阶交叉,适合强交叉信号场景。ONN灵活红娘用多种“核函数”进行交叉PNN的升级版,交叉方式更灵活,能力更强。NFM精华汤厨师用“交互池化层”浓缩二阶交叉,再送DNN经典且高效,平衡了效果与复杂度,应用广泛。FNN (预热) -> P
通俗易懂的方式讲解各个模型的概念
我们暂时把复杂的数学公式放在一边,用一些生动的比喻来理解这些推荐算法模型。它们都属于“深度学习推荐模型”的大家庭,目标是比传统模型更智能地学习特征之间的关系。
在开始之前,我们先统一一个核心概念:特征交叉。
想象一下你在电商网站买东西,你有“性别”和“商品品类”两个特征。
- 性别=女 和 品类=口红 单独看,可能都有一定的购买概率。
- 但当它们组合在一起(“女性 & 口红”)时,这个组合特征会极大地提升购买概率。这种组合就是“特征交叉”。
这些模型的核心任务,就是自动、高效地找到并利用这些强大的交叉特征。
1. FNN - 因子分解机神经网络
通俗解释:
FNN = FM + 深度神经网络
你可以把它想象成一个 “两步走”的学徒。
- 第一步:拜师学艺。 它先跟着一位叫 FM 的老师(一个较简单的模型)学习。FM老师教会它如何为每个特征生成一个高质量的“特征向量”(可以理解为特征的“身份证”或“词向量”)。
- 第二步:深度学习。 学徒出师后,把这些已经学好的、有意义的“特征向量”作为初始知识,输入到一个深度神经网络中进行更深层次的学习和组合。
核心思想: 用FM预训练好的向量来初始化神经网络的输入层,而不是随机初始化,这样能让神经网络起点更高、训练更快、效果更好。
常见使用场景:
- 可以作为其他复杂模型的基准模型。
- 适用于特征维度非常高,且希望模型能有一个“良好开端”的场景。它在早期深度学习推荐系统中很常见。
2. PNN - 乘积神经网络
通俗解释:
PNN = 在特征“相亲大会”上直接牵线搭桥
FNN是把特征向量直接扔进神经网络,让网络自己去慢慢摸索特征之间的关系。而PNN更“主动”。
想象一个“特征相亲大会”,所有特征向量都来了。
- FNN 的做法是:让大家先随便坐着,然后让主持人(DNN)慢慢引导他们互相认识。
- PNN 的做法是:一上来就先搞一个“破冰环节”——乘积层。在这个环节里,它强制让两个特征向量进行“内积”或“外积”操作(可以理解为让它们握手、交流,看看合不合拍),直接计算出它们之间的交叉信号。然后,再把所有这些“初次交流”的结果和原始特征一起,送给后面的神经网络去做深度判断。
核心思想: 在输入层之后,立即显式地引入一个“乘积层”来捕获特征之间的二阶交叉信息,然后再交给DNN处理高阶交叉。
常见使用场景:
- 适用于需要强特征交叉信号的场景,比如广告点击率预估,其中“用户历史行为”和“当前广告内容”的交叉至关重要。
- 当你认为特征之间的两两组合(二阶交叉)是影响预测结果的关键因素时。
3. ONN - 操作神经网络
通俗解释:
PNN的升级版、更灵活的“红娘”
PNN的“破冰环节”(乘积层)手段比较单一,主要就是内积和外积。ONN觉得这不够,它说:“为什么只能用这两种方式交流?我们可以有更多花样!”
ONN引入了一个更通用的概念——核函数。你可以把这些核函数想象成各种不同的“互动游戏”:
- 游戏1:内积(比比谁更相似)
- 游戏2:外积(一起合作创造新东西)
- 游戏3:余弦相似度(忽略长度,只看方向是否一致)
- … 还有很多自定义游戏。
每个特征对都可以选择自己喜欢的“游戏”来互动,产生更丰富的交叉信号。
核心思想: 将PNN中固定的乘积操作(内/外积)泛化为多种多样的、可学习的核函数,让模型自己选择最好的特征交叉方式。
常见使用场景:
- 特征关系非常复杂的场景,单一的交叉方式不足以描述。
- 是PNN的强大替代品,理论上效果应该更好,但计算可能更复杂。
- 在处理类似“用户历史行为序列”这种多值特征时,这种灵活的交叉方式可能更有优势。
4. NFM - 神经网络因子分解机
通俗解释:
FM + 一个“特征交互浓缩层”
FM模型本身就很擅长学习二阶特征交叉,但它缺乏学习更高阶交叉的能力。NFM巧妙地将FM和深度学习结合。
它的工作流程是:
- 像FM一样:把所有特征向量化。
- 进行两两交叉:但不是像FM那样直接把交叉结果相加。它把所有这些两两交叉的结果(一堆向量)收集起来。
- “浓缩”成一碗精华汤:这是一个关键步骤——Bi-Interaction Pooling(双向交互池化层)。它把所有交叉向量进行“求和并取平均”,浓缩成一个浓缩的、固定长度的“精华向量”。这个向量包含了所有二阶交叉信息的精华。
- 深度加工:最后,把这个“精华向量”喂给一个深度神经网络,让网络去学习更高阶、更复杂的交叉模式。
核心思想: 使用一个简洁高效的“双向交互池化层”来显式地捕获二阶特征交叉,并将其结果作为DNN的输入,以捕获高阶交叉。
常见使用场景:
- 非常常用和有效的模型,被认为是结合FM和DNN的典范之一。
- 在各种点击率预估、推荐系统排序阶段都有广泛应用。
- 当你想比纯FM模型效果更好,又想避免像PNN/ONN那样复杂的结构时,NFM是一个非常好的折中选择。
总结与对比
| 模型 | 核心比喻 | 如何做特征交叉? | 特点与场景 |
|---|---|---|---|
| FNN | 两步走学徒 | 先用FM预训练,再用DNN学习 | 起点高,训练快。是早期思想的代表。 |
| PNN | 主动红娘 | 在输入后立即加入“乘积层” | 显式捕捉二阶交叉,适合强交叉信号场景。 |
| ONN | 灵活红娘 | 用多种“核函数”进行交叉 | PNN的升级版,交叉方式更灵活,能力更强。 |
| NFM | 精华汤厨师 | 用“交互池化层”浓缩二阶交叉,再送DNN | 经典且高效,平衡了效果与复杂度,应用广泛。 |
学习建议:
你可以按照这个顺序来理解它们的演进:
FNN (预热) -> PNN (显式交叉) -> ONN (更灵活交叉) -> NFM (优雅且高效的结合)
希望这个解释能帮助你轻松地理解这些模型!它们是构建现代推荐系统非常重要的基石。
下面我将为每个模型提供一个简化的PyTorch实现示例,帮助您理解它们的核心结构。这些示例都基于一个假设的点击率预测场景。
代码演示各个模型的使用
0.训练数据集准备
首先,让我们创建一个简单的模拟数据集:
"""
@Author : lidp
@WeChat : 851298348
@Version : 1.0
@Desc : 功能描述
跳槽涨薪,就学大模型开发;
一对一技术指导,加VX:851298348
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
# 设置随机种子
torch.manual_seed(42)
# 模拟数据参数
batch_size = 32
num_features = 10 # 特征字段数量
embed_dim = 4 # 嵌入维度
# 创建模拟输入数据 (假设是稀疏特征的one-hot编码)
# 在实际应用中,这些会是用户、物品等特征的嵌入表示
x_sparse = torch.randint(0, 2, (batch_size, num_features)).float()
print(f"输入数据形状: {x_sparse.shape}")
1. FNN (因子分解机神经网络)
class FNN(nn.Module):
def __init__(self, num_features, embed_dim, hidden_dims):
super(FNN, self).__init__()
# FM预训练层 (这里简化为线性变换模拟预训练效果)
self.fm_layer = nn.Linear(num_features, num_features * embed_dim)
# 深度神经网络部分
layers = []
input_dim = num_features * embed_dim
for hidden_dim in hidden_dims:
layers.extend([
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Dropout(0.2)
])
input_dim = hidden_dim
layers.append(nn.Linear(input_dim, 1))
layers.append(nn.Sigmoid())
self.dnn = nn.Sequential(*layers)
def forward(self, x):
# FM特征变换
fm_features = self.fm_layer(x) # [batch_size, num_features * embed_dim]
# DNN处理
output = self.dnn(fm_features)
return output
# 演示FNN
fnn_model = FNN(num_features=num_features, embed_dim=embed_dim,
hidden_dims=[64, 32, 16])
fnn_output = fnn_model(x_sparse)
print(f"FNN输出形状: {fnn_output.shape}, 值范围: [{fnn_output.min():.3f}, {fnn_output.max():.3f}]")
2. PNN (乘积神经网络)
class PNN(nn.Module):
def __init__(self, num_features, embed_dim, hidden_dims):
super(PNN, self).__init__()
self.num_features = num_features
self.embed_dim = embed_dim
# 特征嵌入层
self.embedding = nn.Linear(num_features, num_features * embed_dim)
# 乘积层计算
self.product_dim = num_features * (num_features - 1) // 2
# 组合线性部分和乘积部分
total_dim = num_features * embed_dim + self.product_dim
# DNN部分
layers = []
input_dim = total_dim
for hidden_dim in hidden_dims:
layers.extend([
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Dropout(0.2)
])
input_dim = hidden_dim
layers.append(nn.Linear(input_dim, 1))
layers.append(nn.Sigmoid())
self.dnn = nn.Sequential(*layers)
def forward(self, x):
batch_size = x.size(0)
# 获取嵌入特征
embedded = self.embedding(x).view(batch_size, self.num_features, self.embed_dim)
# 线性部分 (原始嵌入)
linear_part = embedded.view(batch_size, -1)
# 乘积部分 (内积计算)
product_list = []
for i in range(self.num_features):
for j in range(i + 1, self.num_features):
# 计算特征i和j的内积
inner_product = torch.sum(embedded[:, i, :] * embedded[:, j, :], dim=1, keepdim=True)
product_list.append(inner_product)
product_part = torch.cat(product_list, dim=1)
# 拼接线性部分和乘积部分
combined = torch.cat([linear_part, product_part], dim=1)
# DNN处理
output = self.dnn(combined)
return output
# 演示PNN
pnn_model = PNN(num_features=num_features, embed_dim=embed_dim,
hidden_dims=[64, 32])
pnn_output = pnn_model(x_sparse)
print(f"PNN输出形状: {pnn_output.shape}, 值范围: [{pnn_output.min():.3f}, {pnn_output.max():.3f}]")
3. ONN (操作神经网络)
class ONN(nn.Module):
def __init__(self, num_features, embed_dim, hidden_dims):
super(ONN, self).__init__()
self.num_features = num_features
self.embed_dim = embed_dim
# 多种核的嵌入层
self.kernel_types = ['inner', 'outer', 'concat']
self.embeddings = nn.ModuleDict({
kernel: nn.Linear(num_features, num_features * embed_dim)
for kernel in self.kernel_types
})
# 计算不同核的输出维度
inner_dim = num_features * (num_features - 1) // 2 # 内积
outer_dim = num_features * (num_features - 1) // 2 * embed_dim # 外积
concat_dim = num_features * embed_dim # 拼接
total_dim = inner_dim + outer_dim + concat_dim
# DNN部分
layers = []
input_dim = total_dim
for hidden_dim in hidden_dims:
layers.extend([
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Dropout(0.2)
])
input_dim = hidden_dim
layers.append(nn.Linear(input_dim, 1))
layers.append(nn.Sigmoid())
self.dnn = nn.Sequential(*layers)
def forward(self, x):
batch_size = x.size(0)
# 不同核的特征表示
kernel_outputs = []
for kernel_type in self.kernel_types:
embedded = self.embeddings[kernel_type](x).view(batch_size, self.num_features, self.embed_dim)
if kernel_type == 'inner':
# 内积操作
inner_products = []
for i in range(self.num_features):
for j in range(i + 1, self.num_features):
product = torch.sum(embedded[:, i, :] * embedded[:, j, :], dim=1, keepdim=True)
inner_products.append(product)
kernel_output = torch.cat(inner_products, dim=1)
elif kernel_type == 'outer':
# 外积操作 (简化版)
outer_products = []
for i in range(self.num_features):
for j in range(i + 1, self.num_features):
# 外积的扁平化版本
outer_product = (embedded[:, i, :].unsqueeze(2) * embedded[:, j, :].unsqueeze(1)).view(batch_size, -1)
outer_products.append(outer_product)
kernel_output = torch.cat(outer_products, dim=1)
else: # concat
# 拼接操作
kernel_output = embedded.view(batch_size, -1)
kernel_outputs.append(kernel_output)
# 组合所有核的输出
combined = torch.cat(kernel_outputs, dim=1)
# DNN处理
output = self.dnn(combined)
return output
# 演示ONN
onn_model = ONN(num_features=num_features, embed_dim=embed_dim,
hidden_dims=[128, 64])
onn_output = onn_model(x_sparse)
print(f"ONN输出形状: {onn_output.shape}, 值范围: [{onn_output.min():.3f}, {onn_output.max():.3f}]")
4. NFM (神经网络因子分解机)
class NFM(nn.Module):
def __init__(self, num_features, embed_dim, hidden_dims):
super(NFM, self).__init__()
self.num_features = num_features
self.embed_dim = embed_dim
# 特征嵌入层
self.embedding = nn.Linear(num_features, num_features * embed_dim)
# Bi-Interaction Pooling 层
# 输出维度为 embed_dim
# DNN部分
layers = []
input_dim = embed_dim # Bi-Interaction Pooling的输出维度
for hidden_dim in hidden_dims:
layers.extend([
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Dropout(0.2)
])
input_dim = hidden_dim
layers.append(nn.Linear(input_dim, 1))
layers.append(nn.Sigmoid())
self.dnn = nn.Sequential(*layers)
def forward(self, x):
batch_size = x.size(0)
# 获取嵌入特征
embedded = self.embedding(x).view(batch_size, self.num_features, self.embed_dim)
# Bi-Interaction Pooling (核心创新)
# 公式: 1/2 * [ (sum_i v_i)^2 - sum_i (v_i)^2 ]
sum_of_vectors = torch.sum(embedded, dim=1) # [batch_size, embed_dim]
sum_of_squares = torch.sum(embedded * embedded, dim=1) # [batch_size, embed_dim]
# Bi-Interaction 输出
bi_interaction = 0.5 * (sum_of_vectors * sum_of_vectors - sum_of_squares) # [batch_size, embed_dim]
# DNN处理
output = self.dnn(bi_interaction)
return output
# 演示NFM
nfm_model = NFM(num_features=num_features, embed_dim=embed_dim,
hidden_dims=[64, 32])
nfm_output = nfm_model(x_sparse)
print(f"NFM输出形状: {nfm_output.shape}, 值范围: [{nfm_output.min():.3f}, {nfm_output.max():.3f}]")
综合比较和训练示例
# 比较所有模型的输出
print("\n=== 模型输出比较 ===")
models = {
'FNN': fnn_output,
'PNN': pnn_output,
'ONN': onn_output,
'NFM': nfm_output
}
for name, output in models.items():
print(f"{name}: 均值={output.mean().item():.4f}, 标准差={output.std().item():.4f}")
# 简单的训练循环示例
def train_demo(model, data, epochs=100):
"""演示训练过程"""
model.train()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.BCELoss()
# 创建模拟标签
y_demo = torch.randint(0, 2, (data.size(0), 1)).float()
for epoch in range(epochs):
optimizer.zero_grad()
output = model(data)
loss = criterion(output, y_demo)
loss.backward()
optimizer.step()
if epoch % 20 == 0:
print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
# 演示训练NFM (最实用的模型)
print("\n=== NFM训练演示 ===")
train_demo(nfm_model, x_sparse, epochs=100)
关键要点总结
- FNN: 两阶段模型,先用FM初始化,再用DNN精调
- PNN: 在输入层后立即添加乘积操作来捕捉特征交叉
- ONN: PNN的扩展,支持多种特征交互操作(内积、外积、拼接等)
- NFM: 使用Bi-Interaction Pooling高效捕捉二阶特征交互,再输入DNN
这些简化示例展示了每个模型的核心思想。在实际应用中,您需要:
- 使用真实的稀疏特征嵌入
- 调整模型结构和超参数
- 添加正则化防止过拟合
- 使用合适的数据预处理和特征工程
希望这些示例能帮助您更好地理解这些推荐算法模型!
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)