简介
再推荐模型中,对于特征feat interaction的探索占据了相当一部分的工作。
这也说明对于CTR问题来说,feature Interaction的探索是判断最终用户click的重要一环。
除了FM和FFM等因子分解机模型之外,还有Cross Layer的出现也是非常具有代表性的特征交互模型。
这里从DCN模型入手,然后单独将cross layer抽离出来,然后对于他的数学原理还有代码实现细节都进行比较深入的探索。
DCN中的Cross Layer
DCN模型整体架构是非常简单的:
从整体上来说,他和DeepFM模型有非常相似的结构,可以说只是把FM模块替换成了Cross Layer。
操作流程
这里我们从底向上来看DCN的整体流程。
-
对于Dense Feat,直接进行concat,对于Sparse Feature先进行emb,然后再concat
-
将得到的最终的输入向量输入到两个模块(Cross & DNN)
-
将两个部分的输出进行concat,然后输入到一个Dense层
-
通过predictor得到最终的prob
从整体来看,为什么再已经存在FM的情况下仍然出现了Cross模块,这里主要是因为FM只限于二阶的特征交叉,这是不够的,所以科学家们还致力于探索高阶的 特征交互所带来的意想不到的贡献。
So,Cross Layer的优势就是可以自动的学习高阶的特征interactionu权重和特征组合。摆脱了人工Feat interaction。
数学原理
图片引自:https://zhuanlan.zhihu.com/p/55234968
Cross Layer 有layer_num层,我们举第L层的例子,求解第L+1层。
这里我们可以看到,这是一个非常简单的计算公式,我们做一个简单的变换:
x l − 1 − x l = x 0 ∗ x l T ∗ w + b x_{l-1} - x_l = x_0 * x_l^T * w + b xl−1−xl=x0∗xlT∗w+b
可以看出每一层其实是对上一层的残差的拟合,残差的使用的好处主要是防止了梯度消失问题,也使得训练更加快速。
那么经过layer_num次的迭代之后我们就可以得到如下式子:
图片引自: https://zhuanlan.zhihu.com/p/55234968
这里就再次证明了Cross确实可以自动的学习feat interaction权重和组合。
Torch实现
然后我们就来实现Cross Layer,每一步都对照着下面这个式子:
x l − 1 − x l = x 0 ∗ x l T ∗ w + b x_{l-1} - x_l = x_0 * x_l^T * w + b xl−1−xl=x0∗xlT∗w+b
class CrossNet(nn.Module):
"""
: param layer_num: the number of layers in cross module
: param input_size: the dim of input vector
: param seed: random seed which can be ignored
: param device: cpu or gpu ..
"""
def __init__(self, layer_num, input_size, seed=1024, device='cpu'):
super(CrossNet, self).__init__()
self.layer_num = layer_num
## define the w in the above formula
## 注意这里的维度,如果不清楚的话可以看上面的计算第L+1层的图
self.weights = nn.ParameterList(
[nn.Parameter(nn.init.xavier_normal_(torch.empty(input_size, 1))) for _ in range(layer_num)]
)
## define the b in the above formula
## 同样这里的dim和上面的图示中是一样的
self.bias = nn.ParameterList(
[nn.Parameter(nn.init.xavier_normal_(torch.empty(input_size, 1))) for _ in range(layer_num)]
)
## to device标准操作,没什么好说的
self.to(device)
接下来就是forward, 我把每个tensor的维度都写在注释里了
def forward(self, x):
## x [bat_s dim]
x_0 = x.unsqueeze(2)
## x_0 [batch_size, dim, 1]
x_l = x_0
for i in range(self.layer_num):
## x_l [batch_size, dim, 1]
## weights[i] [dim, 1]
## xl_w [batch_size, 1, 1]
xl_w = torch.tensordot(x_l, self.weights[i], dims=([1], [0]))
res_ = torch.matmul(x_0, xl_w)
x_l = res_ + self.bias[i] + x_l
x_l = torch.squeeze(x_l, dim=2)
return x_l
所有的 步骤都是上面formula的转化。