Wide&Deep模型与DCN模型
AI摘要: 本文介绍了Wide & Deep模型和DCN(Deep & Cross Network)模型的原理、实现及其在处理特征学习中的应用。文章详细描述了Wide部分的简单线性变换和Deep部分的多层感知机结构,并解释了如何通过Cross Network改进Wide部分以显式建模特征之间的交叉。
Wide & Deep 原理
网上有很多博客讲解提到Wide
部分提供记忆能力,学习简单的特征模式,而Deep
部分提供泛华能力,学习复杂的特征模式。看起来云里雾里,但是直接看Wide & Deep代码就很清晰了:
class WideAndDeepModel(nn.Module):
def __init__(self, categorical_dims, numerical_dim, embedding_dim, hidden_layers):
super(WideAndDeepModel, self).__init__()
# Wide 部分
self.wide = nn.Linear(len(categorical_dims) + numerical_dim, 1)
# Deep 部分
self.embeddings = nn.ModuleList([
nn.Embedding(dim, embedding_dim) for dim in categorical_dims
])
deep_input_dim = len(categorical_dims) * embedding_dim + numerical_dim
layers = []
for dim in hidden_layers:
layers.append(nn.Linear(deep_input_dim, dim))
layers.append(nn.ReLU())
deep_input_dim = dim
layers.append(nn.Linear(deep_input_dim, 1))
self.deep = nn.Sequential(*layers)
def forward(self, categorical_data, numerical_data):
# Wide 部分
wide_input = torch.cat([categorical_data.float(), numerical_data], dim=1)
wide_output = self.wide(wide_input)
# Deep 部分
embeddings = [emb(categorical_data[:, i]) for i, emb in enumerate(self.embeddings)]
deep_input = torch.cat(embeddings + [numerical_data], dim=1)
deep_output = self.deep(deep_input)
# 合并 Wide 和 Deep 输出
output = torch.sigmoid(wide_output + deep_output)
return output
Wide
部分就是一个简答的Linear
线性层,非常简单直接的线性变换;Deep
部分就是稍微复杂一点的MLP
,线性层之间套Relu
激活函数而已,更深一点的神经网络罢了;- Wide和Deep两个部分的输出都丢到sigmoid函数中归一化;
这样就容易理解了,浅层神经网络学习浅层特征,深层神经网络学习复杂特征。
其实在CV中更好理解一点,用CNN来学习猫狗分类,浅层网络学习颜色、灰度强度等简单特征,深层网络就学习轮廓之类的复杂特征。
Wide & Deep常见问题
Deep & Cross模型
Deep & Cross模型是Wide & Deep模型最常见简单的进化版,就是将Wide部分改成Cross网络。
其实后续还有很多改进,基本都是将弱鸡的Wide部分替换为更加多样化的交叉操作,比如Cross,FM之类的模块。(传统推荐模型和深度学习模型在此刻会师)
具体来看DCN:
- Deep部分:无需多言,基本没改,还是个DNN(深度学习神经网络,负责学习特征的复杂非线性组合)
- Wide部分:Cross Network(交叉网络,负责显式地建模特征之间的交叉,捕捉高阶特征之间的相互作用)。
这两种网络的各自输出,然后融合起来,通常是通过简单的加权求和或者简单拼接的方式,最终的输出经过线性层和激活函数得到预测结果(和原版Wide & Deep一模一样)
Cross Network
对于输入向量 , 交叉网络的第 层可以表示为: ,这样可以学习到不同特征之间的交叉
之所以每次的二阶交叉部分都是和操作,是希望更多地保留原始的显示特征,避免在多层交叉之后学习到的全是深层隐式特征(这是Deep部分负责的事情)
公式懒得看,直接看模型结构图中的Wide(Cross)部分
代码实现:
class DCN(nn.Module):
def __init__(self, categorical_dims, numerical_dim, embedding_dim, hidden_layers, cross_layers):
super(DCN, self).__init__()
# 嵌入层,用于将类别特征映射到低维稠密空间
self.embeddings = nn.ModuleList([
nn.Embedding(dim, embedding_dim) for dim in categorical_dims
])
# Cross Network 部分
input_dim = len(categorical_dims) * embedding_dim + numerical_dim
self.cross_layers = nn.ModuleList([
nn.Linear(input_dim, input_dim) for _ in range(cross_layers)
])
# Deep Network 部分
deep_layers = []
for dim in hidden_layers:
deep_layers.append(nn.Linear(input_dim, dim))
deep_layers.append(nn.ReLU())
input_dim = dim
self.deep = nn.Sequential(*deep_layers)
# 输出层
self.output = nn.Linear(hidden_layers[-1] + len(categorical_dims) * embedding_dim + numerical_dim, 1)
def forward(self, categorical_data, numerical_data):
# 嵌入层:对类别特征进行编码
embeddings = [emb(categorical_data[:, i]) for i, emb in enumerate(self.embeddings)]
embed_concat = torch.cat(embeddings, dim=1)
# 拼接嵌入和数值特征作为输入
x = torch.cat([embed_concat, numerical_data], dim=1) # [batch, Category_num * Dim + Numeric_num]
# Cross Network 部分
x_cross = x.clone() # [batch, Category_num * Dim + Numeric_num]
for layer in self.cross_layers:
x_cross = x + layer(x_cross) # 层间显式交叉
# Deep Network 部分
x_deep = self.deep(x)
# 合并 Cross 和 Deep 输出
combined = torch.cat([x_cross, x_deep], dim=1)
output = torch.sigmoid(self.output(combined))
return output