深度学习模型交叉特征建模不理想?试试DCNv2[论文笔记&源码解读]

论文介绍

DCN-v2优化了DCN的cross layer,权重参数w由原来的vector变为方阵matrix,增加了网络层的表达能力;同时,为了保证线上应用的耗时不会因为cross layer参数量的增加而增加。观察到cross layer的matrix具有低秩性,使用矩阵分解,将方阵matrix转换为两个低维的矩阵、最后在低秩空间内,利用MoE多专家系统,对特征交叉做非线性变化,进一步增加对交叉特征的建模。vector -> matrix; moe

设想:DCNv2是不是可以结合多任务学习、MMoE架构,设计一个新的点击率、转换率模型。效果会不会很好呢?不过,这种方法对训练样本量可能要求比较高。可以尝试一下。

构建推荐系统的关键点在于学习有效的交叉特征。特征的交叉一般通过哈达玛积来表示,比如x1表示性别,男、女;x2表示是否喜欢体育;x1&x2联合特征,会有4种取值。通过特征交叉,可以给模型带来一定的非线性表示。DCN在实际应用中,当处理十亿级别的训练数据样本时,其Cross网络部分在建模特征交叉时表达能力受限。尽管,交叉特征建模在学术上已经提出了很多新的技术方法, 但在实际工程中,许多深度学习模型仍然是通过传统的前馈神经网络来实现特征交叉建模。

通盘考虑DCN网络的优缺点以及交叉特征的建模方式,这里提出了一种新模型架构DCNv2:优化DCN的Cross部分,丰富其对交叉特征的建模能力。

我们直接对比DCN、DCNv2的网络架构。、

dcn
DCN-v2

两者的差别在于Cross网络的建模方式。

DCN Cross layer:


cross layer-vector

DCNv2 Cross layer:


cross layer-matrix

网络层权重参数由原来的vector变为matrix。DCN网络的cross layer的建模是element-wise;DCNv2 cross layer可以实现element-wise和feature-wise的特征交叉。


待理解

我们先回归一下DCN网络模型。DCN借鉴Google的Wide&Deep模型结构,Deep部分是一个N的MLP,用于学习隐性高阶交叉特征;Cross网络通过显性的交叉公式建模高阶交叉特征。cross网络的参数为两个d维度的向量:w和b。通过公式优化,可以简化运算,不会影响模型的线上耗时。这是DCN网络的优点。

但是,DCN网络的Cross网络、Deep网络的参数量不平衡,Deep部分的参数占据DCN总参数的绝大部分。


eg

举例来说,对于1层cross和1层deep的DCN网络输入经过embedding和stack处理后维度为d,Cross部分网络参数为2d,Deep为d*d,当MLP的层数增多时,deep部分的参数量也急速增加。DCN网路的绝大部分参数都用于对隐性交叉特征进行建模。Cross部分的表达能力反而受限。

DCN-v2优化了cross网络的建模方式,增加了cross网络部分的表达能力;deep部分保持不变。下面主要就Cross网络进行详细介绍。

cross layer-matrix

从原来的向量变为矩阵。由于线上应用对模型的耗时、资源占用要求很高。原来的DCN的网络正是由于Cross网络既能对交叉特征进行显性建模,而且对模型的耗时影响忽略不计。如果改进后的cross layer直接应用到线上,cross网络虽然会增加模型的表达能力,但是cross部分的权重参数会给模型的耗时带来影响。所以,为了能进一步减少cross网络的模型耗时,论文通过实验发现了cross layer的权重参数具有低秩性。

低秩性

我们可以对cross layer的权重参数进行矩阵分解,将原来[d,d]的W方阵,分解为两个矩阵U,V,[d,r], r << d. 这样就可以实现cross layer的参数缩减,同时也增加了cross网络的表达能力。

矩阵分解

通过上面的公式,我们可以发现:

  1. cross在低维空间进行交叉特征的建模;
  2. 我们将输入x从维度d映射到r,然后又映射会维度d。[d >> r].

低维空间的交叉特征建模使得我们可以利用MoE。MoE由两部分组成:experts专家和gating门(一个关于输入x的函数)。我们可以使用多个专家,每个专家学习不同的交叉特征,最后通过gating将各个专家的学习结果整合起来,作为输出。这样就又能进一步增加对交叉特征的建模能力。

MoE
continue

同时,我们也可以对低纬映射空间中做更多的非线性变化,进一步增加模型的表达能力。

通过实验发现,对比其他模型,DCN-v2表现更好。

image-20210101121920940
试验结果

其他,需要注意的是。论文提出,deep和cross的结合方式,选用并行or串行结构,与训练数据密切相关。不同的数据分布,应该选择不同的处理方式,并行结构并不是通用的。

源码阅读

Cross Layer

将DCN的Cross layer和不使用MoE结构的cross layer。两者的区别在于交叉特征的建模方式,权重参数一个是向量,一个是矩阵。

[图片上传失败...(image-801292-1609475511103)]

DCN-v2
class CrossNet(nn.Module):
    """The Cross Network part of Deep&Cross Network model,
    which leans both low and high degree cross feature.
      Input shape
        - 2D tensor with shape: ``(batch_size, units)``.
      Output shape
        - 2D tensor with shape: ``(batch_size, units)``.
      Arguments
        - **in_features** :输入特征维度
        - **input_feature_num**: Positive integer, shape(Input tensor)[-1]
        - **layer_num**: cross网络的网络层数
        - **parameterization**: "vector"  or "matrix" ,表示网络层的建模方式
        - **l2_reg**: 正则系数
        - **seed**: 随机种子
    """

    def __init__(self, in_features, layer_num=2, parameterization='vector', seed=1024, device='cpu'):
        super(CrossNet, self).__init__()
        self.layer_num = layer_num
        self.parameterization = parameterization
        # 参数声明
        if self.parameterization == 'vector': # DCN
            # weight in DCN.  (in_features, 1)
            self.kernels = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(torch.empty(in_features, 1))) for i in range(self.layer_num)])
        elif self.parameterization == 'matrix': # DCNv2
            # weight matrix in DCN-M.  (in_features, in_features)
            self.kernels = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(torch.empty(in_features, in_features))) for i in range(self.layer_num)])
        else:  # error
            raise ValueError("parameterization should be 'vector' or 'matrix'")

        self.bias = torch.nn.ParameterList([nn.Parameter(nn.init.zeros_(torch.empty(in_features, 1))) for i in range(self.layer_num)])
        self.to(device)

    def forward(self, inputs):
        x_0 = inputs.unsqueeze(2)
        x_l = x_0
        for i in range(self.layer_num):
            if self.parameterization == 'vector':
            # x0 * (xl.T * w )+ b + xl
                xl_w = torch.tensordot(x_l, self.kernels[i], dims=([1], [0]))
                dot_ = torch.matmul(x_0, xl_w)
                x_l = dot_ + self.bias[i]
            elif self.parameterization == 'matrix':
            # x0 * (Wxl + bl) + xl
                dot_ = torch.matmul(self.kernels[i], x_l)  # W * xi  (bs, in_features, 1)
                dot_ = dot_ + self.bias[i]  # W * xi + b
                dot_ = x_0 * dot_  # x0 · (W * xi + b)  Hadamard-product
            else:  # error
                print("parameterization should be 'vector' or 'matrix'")
                pass
            x_l = dot_ + x_l
        x_l = torch.squeeze(x_l, dim=2)
        return x_l

Cross Network + MoE

公式

计算每个专家的输出,然后通过gating将多个专家的输出进行加权平均。

class CrossNetMix(nn.Module):
    """DCN-Mix:
      1 添加MoE,学习不同子空间内的交叉特征
      2 在低维空间增加更多的非线性变换
      Input shape
        - 2D tensor with shape: ``(batch_size, units)``.
      Output shape
        - 2D tensor with shape: ``(batch_size, units)``.
      Arguments
        - **in_features** : 输入特征维度
        - **low_rank** : 低维度空间的维度
        - **num_experts** : 专家数目.
        - **layer_num**: cross网路层数
        - **device**:"cpu" or "cuda:0"
    """

    def __init__(self, in_features, low_rank=32, num_experts=4, layer_num=2, device='cpu'):
        super(CrossNetMix, self).__init__()
        self.layer_num = layer_num
        self.num_experts = num_experts

        # 参数声明
        # U: (in_features, low_rank)
        self.U_list = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(
            torch.empty(num_experts, in_features, low_rank))) for i in range(self.layer_num)])
        # V: (in_features, low_rank)
        self.V_list = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(
            torch.empty(num_experts, in_features, low_rank))) for i in range(self.layer_num)])
        # C: (low_rank, low_rank)
        self.C_list = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(
            torch.empty(num_experts, low_rank, low_rank))) for i in range(self.layer_num)])
        self.gating = nn.ModuleList([nn.Linear(in_features, 1, bias=False) for i in range(self.num_experts)])

        self.bias = torch.nn.ParameterList([nn.Parameter(nn.init.zeros_(
            torch.empty(in_features, 1))) for i in range(self.layer_num)])
        self.to(device)

    def forward(self, inputs):
        x_0 = inputs.unsqueeze(2)  # (bs, in_features, 1)
        x_l = x_0
        for i in range(self.layer_num):# 逐个网络层
            output_of_experts = []
            gating_score_of_experts = []
            for expert_id in range(self.num_experts): # 每个的若干个专家
                # 针对每个专家,计算gating score,output
                # (1) G(x_l)
                # 根据输入计算gating值,最后softmax计算权重系数
                gating_score_of_experts.append(self.gating[expert_id](x_l.squeeze(2)))
                # (2) E(x_l)
                # 计算每个专家的输出
                # 2.1 将输入映射到低维空间
                v_x = torch.matmul(self.V_list[i][expert_id].t(), x_l)  # (bs, low_rank, 1)

                # 2.2 在低维空间进行非线性变换
                v_x = torch.tanh(v_x)
                v_x = torch.matmul(self.C_list[i][expert_id], v_x)
                v_x = torch.tanh(v_x)

                # 2.3 从低维空间映射回高维度
                uv_x = torch.matmul(self.U_list[i][expert_id], v_x)  # (bs, in_features, 1)

                dot_ = uv_x + self.bias[i]
                # 哈达玛积:逐元素乘法
                dot_ = x_0 * dot_  # Hadamard-product

                output_of_experts.append(dot_.squeeze(2))# [bs, d]

            # (3) 专家输出结果加权平均,得到这一层网络的输出内容
            output_of_experts = torch.stack(output_of_experts, 2)  # (bs, in_features, num_experts)
            gating_score_of_experts = torch.stack(gating_score_of_experts, 1)  # (bs, num_experts, 1)
            moe_out = torch.matmul(output_of_experts, gating_score_of_experts.softmax(1))
            x_l = moe_out + x_l  # (bs, in_features, 1)

        x_l = x_l.squeeze()  # (bs, in_features)
        return x_l

进一步思考:将DCN-V2和MTL多任务学习结合起来效果会怎么样呢?

后续打算,参考PyTorch实现,写一版TensorFlow版本的DCN-v2。

Reference

DCN-v2

pytoch实现

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容