Monolith Layers 实战指南:基于 TensorFlow Keras 的推荐系统层超集

原创2026-10-01 11:25:44704 阅读
文章标签:人工智能深度学习机器学习分布式训练

Monolith Layers 实战指南:基于 TensorFlow Keras 的推荐系统层超集

本篇技术指南围绕 Monolith(轻量级推荐系统)中 native_training/layers 模块展开,系统讲解 Monolith Layers 作为 TensorFlow Keras Layers 超集的设计思路、两种创建方式、与 Keras 的无缝混用,以及 Dense / MLP / 特征交叉 / 归一化 / 序列兴趣建模 / Logit 校正等各类层的能力与参数。读完本文,你将掌握如何在推荐模型(CTR/CVR 预估、召回排序、多任务学习)中直接使用 Monolith Layers 替换并增强 Keras 原生层,并理解其底层实现原理。

一、Monolith Layers 是什么:Keras 层的超集

Monolith 的 layers 模块位于 monolith/native_training/layers 目录,官方定义其为 "a super set of tensorflow keras layers"——即 TensorFlow Keras 层的超集。它在 Keras 原生层的基础上新增、增强了以下层族:

  • 基础网络层:Dense、MLP、AddBias
  • 归一化层:LayerNorm、GradNorm(模块内还实现了训练专用的 BatchNorm)
  • 特征交叉层:GroupInt(即 FFM)、AllInt、CDot、CAN、DCN、CIN
  • 特征变换/序列层:AutoInt、SeNet、iRazor、DIN、DIEN、DMR_U2I
  • 业务校正层:LogitCorrection
  • 池化层:SumPooling、AvgPooling、MaxPooling

从模块的 init.py 可以看到其导出策略:先通过 from tensorflow.keras.layers import * 引入全部 Keras 层,再导入并覆盖 Dense(del globals()['Dense'] 后用自实现的 Dense 替换),最后遍历 tf.keras.layers 下所有 Layer 子类,为不具备 params 方法的 Keras 层动态挂载 params 方法。这意味着你在 monolith.native_training.layers 命名空间里既能拿到 Monolith 增强层,也能拿到所有原生 Keras 层,二者共享同一套创建与参数化体系。

该目录还包含配套的单元测试(*_test.py,如 dense_test.py、feature_cross_test.py)以及自定义算子(kernels 目录下的 ffm_kernels.cc、feature_insight_kernels.cc 等),供工程落地时参考验证。

二、两种创建 Layer 的方式

Monolith Layers 提供两种等价的对象创建方式,这是其与纯 Keras 层最显著的区别。

方式一:构造函数直接创建(与 Keras 一致)

import tensorflow as tf
from monolith.native_training.layers import Dense

dense = Dense(units=100, activation=tf.keras.activations.relu)

方式二:通过 params() 创建(InstantiableParams 模式)

dense_p = Dense.params()
dense_p.units = 100
dense_p.activation = tf.keras.activations.relu
dense2 = dense_p.instantiate()

第二种方式的核心是 InstantiableParams:Dense.params() 返回一个可配置的参数对象,逐项设置参数后调用 instantiate() 生成层实例。这种模式在超参数需要集中管理、批量构造或序列化/反序列化(from_config)时非常实用——例如 mlp.py 中 MLP.from_config 就是从配置字典构建 InstantiableParams 再 instantiate() 的。

其底层实现见 native_training/utils.py:params(cls) 通过反射扫描类及其所有基类的 __init__ 签名(_get_all_parameters),把每个构造参数(含默认值)写入 InstantiableParams,再通过 with_params 装饰器把 params 静态方法挂到类上:

def with_params(cls):
  cls.params = types.MethodType(params, cls)
  return cls

Monolith 的所有增强层(Dense、MLP、GroupInt、DCN……)都带有 @with_params 装饰器,因此统一支持 ClassName.params() 这种创建方式;__init__.py 中的遍历逻辑还让原生 Keras 层也获得了 params 方法,保持了全模块 API 的一致性。

两种方式创建出的层可以自由混合使用,例如:

model = tf.keras.Sequential([
  dense,    # 构造函数创建
  dense2,   # params().instantiate() 创建
  tf.keras.layers.Dense(units=100, activation=tf.keras.activations.relu)  # 混用 Keras 原生层
])

三、与 Keras 无缝混用与一键替换

Monolith Layers 完全兼容 Keras Layers,可以与 tf.keras.layers 的原生层在同一模型中任意混排(如上例)。由于 API 形态一致、Layer 基类一致,Keras 的 Sequential、Model、compile/fit 等高层接口都能直接使用。

多数情况下,只需做一行替换即可把模型切换为 Monolith 增强层:

# 替换前
from tensorflow.keras import layers

# 替换后
from monolith.native_training import layers

官方建议在 Monolith 项目中优先使用 Monolith Layers,因为它们在推荐场景下提供了 Keras 原生层没有的能力(kernel norm、可训练权重范数、特征洞察、变量分区等)。

四、基础网络结构层:Dense 与 MLP

4.1 Dense:带 kernel_norm 与 ReLU 失活监控的全连接层

Dense 在 dense.py 中重新实现,数学形式为 y = active(wx + b)。与 Keras 原生 Dense 相比,它增加了权重归一化(kernel_norm)、变量分区(partitioner)、优化器绑定与 ReLU 失活监控等推荐训练场景急需的能力。

核心参数如下(默认值取自源码签名):

参数 默认值 说明
units 必填 输出神经元个数
activation None 激活函数,可用字符串或 tf.keras.activations.*
use_bias True 是否使用 bias
kernel_initializer 'glorot_uniform' kernel(W)初始化器
bias_initializer 'zeros' bias(b)初始化器
kernel_regularizer None kernel 正则化器
bias_regularizer None bias 正则化器
allow_kernel_norm False 是否开启 kernel_norm
kernel_norm_trainable False kernel_norm 是否可训练
partitioner None 变量分区器,可将大变量切分到不同 PS 机器
inactive_relu_monitor False 是否监控 ReLU 失活单元占比
inactive_relu_monitor_decay 0.1 失活率指数滑动平均的衰减因子
optimizer None 优化器,会绑定到层内创建的变量上

kernel_norm 的原理(见 dense.py):先对 kernel W 沿 axis=0 做 l2_normalize,把取值限制在 [-1, 1];再乘以可学习的范数 kernel_norm,使有效权重落在 [-kernel_norm, kernel_norm] 区间,从而有效抑制梯度爆炸。kernel_norm 初始值取 W 初始化的范数;当 kernel_norm_trainable=True 时,它会成为可训练变量,由优化器自适应调节。

ReLU 失活监控(见 dense.py):当 inactive_relu_monitor=True 且激活函数为 relu 时,层内会维护一个 inactive_relu_count_moving_avg 变量(注册到 METRIC_VARIABLES 与 GLOBAL_VARIABLES collection),按指数滑动平均统计每个输出单元的失活数量,并通过 summary.histogram 输出,帮助算法同学定位"神经元死亡"问题。

此外,Dense 重写了 add_weight 与 get_variable,把 optimizer 挂到每个变量上(含 PartitionedVariable 的每个分片),配合 partitioner 实现在参数服务器(PS)场景下的大变量分布式管理。

4.2 MLP:默认开启权重范数的多层感知器

MLP 在 mlp.py 中实现,本质是由多个 Dense 层堆叠而成,并默认启用 kernel_norm 相关能力。核心参数如下:

参数 默认值 说明
output_dims 必填(List[int]) 每一层的输出神经元个数
activations 默认前 n-1 层 relu,最后一层 None 每层激活函数(列表/字符串/单个均可)
initializers None 每层 kernel 初始化器列表
kernel_regularizer None kernel 正则化器
use_weight_norm True 是否开启 kernel_norm(对应 Dense 的 allow_kernel_norm)
use_learnable_weight_norm True kernel_norm 是否可训练
use_bias True 是否使用 bias
bias_regularizer None bias 正则化器
enable_batch_normalization False 是否开启 BatchNorm;开启后对输入及每个非最后 Dense 的输出做 BatchNorm
batch_normalization_momentum 0.99 BatchNorm 动量因子
batch_normalization_renorm False 是否使用 renorm
batch_normalization_renorm_clipping None renorm 的 clipping 配置
batch_normalization_renorm_momentum 0.99 renorm 动量

MLP.build(mlp.py)会按 output_dims 逐个构建 Dense 层,并把各子层的可训练/不可训练权重、loss 统一上收到 MLP 自身,因此它可以像单个层一样被嵌入任何模型。若 enable_batch_normalization=True,还会在输入处与每个非末层 Dense 之后插入 BatchNorm(最后一个 Dense 除外)。此外,MLP.call 支持透传 segment_names、segment_sizes、group_info、label 等 kwargs,在第一个 Dense 层处调用 feature_insight_data 输出特征洞察数据(底层算子见 kernels/feature_insight_kernels.cc)。

4.3 AddBias:处理多维度 shape 的加偏置层

AddBias(add_bias.py)执行 y = x + b,与直接用 + 相比,它自动处理了更多 shape 问题:图像数据存在 NCHW/NHWC 两种表示,时间序列也存在类似布局问题,AddBias 通过 data_format 参数('channels_first' / 'channels_last')透明地完成 bias 的 reshape 与广播,支持 2~5 维输入。

add_bias = AddBias(initializer=tf.initializers.Zeros())
y = add_bias(x, data_format='channels_first')

参数仅两个:initializer(bias 初始化器,默认 Zeros)、regularizer(bias 正则化器)。其 build 会根据输入 shape 的非 batch 维度构造 bias 变量(add_bias.py)。

五、归一化层:LayerNorm / GradNorm / BatchNorm

5.1 LayerNorm:样本内归一化

LayerNorm(norms.py)与 BatchNorm 类似,但在样本内做归一化:对最后一维求 mean/variance 后做 batch_normalization。与 BatchNorm 不同,LayerNorm 训练与推理使用同一套逻辑,无需区分模式。参数为 initializer(gamma 初始化器,默认 Ones)与 regularizer(beta/gamma 的正则化器)。

5.2 GradNorm:多任务梯度均衡

GradNorm(norms.py)面向多任务学习,通过将不同任务的梯度控制在特定范围来平衡各任务训练。它通过构造辅助 loss 实现,核心流程(源码注释与 call 实现吻合):

  1. 计算每个 head(loss)对 shared bottom 输入的梯度;
  2. 对梯度取 L2 范数得到 gnorms(n 维向量,n 为任务数);
  3. 用可学习权重(经 softmax 归一化)加权得到 wgnorms,求平均得到 avgnorm;
  4. 计算梯度差异 loss:scale * sum([(wgnorms - avgnorm) / (avgnorm + epsilon)]^loss_pow)(relative_diff=True 时)或 scale * sum([wgnorms - avgnorm]^loss_pow)(relative_diff=False 时);
  5. 加权求和原始 loss 作为 weighted_loss。

参数:loss_names(loss 名称列表,决定任务数)、scale(默认 1.0)、loss_pow(默认 2.0)、relative_diff(默认 False)、epsilon(默认 1e-6)。call 返回 (gnorm_loss, weighted_loss) 两个张量,方便在训练中同时参与反向传播。

5.3 BatchNorm:训练专用的全局分布归一化

模块还提供了训练专用的 BatchNorm(norms.py),除了标准的 momentum/center/scale 配置外,额外支持 training_use_global_dist(训练时使用全局分布统计量)、global_dist_momentum、stop_grad_of_var_mean 等分布式训练选项,并输出 mean/var 的 histogram 与 scalar 监控。

六、特征交叉层:GroupInt / AllInt / CDot / CAN / DCN / CIN

特征交叉(Feature Interaction)是推荐系统模型的核心,Monolith 在该方向提供了从经典 FM 到 DCN 的一整套实现,全部位于 feature_cross.py。

6.1 GroupInt(即 FFM):分组特征交叉

GroupInt 是 Group Interaction 的缩写(feature_cross.py),其设计动机是解决模型内二阶交叉的两个痛点:

  • 输出维度高:FM 用点积表示交叉,n 个特征输出 n(n-1)/2 维;
  • 重复交叉:FM 不区分原始特征与交叉特征,现实中往往重复交叉。

GroupInt 先由算法工程师将特征分成若干组(Group),用 sumpooling 聚合出 group embedding,再让 group embedding 两两交叉输出,输出规模远小于原生 FM,且人工分组减少了重复交叉。FFM = GroupInt(feature_cross.py),即 FFM 是 GroupInt 的别名。

关键参数:

参数 默认值 说明
interaction_type 'multiply' 交叉方式,dot 或 multiply
use_attention False 是否使用 attention(仅 multiply 模式可用,与 AFM 类似)
attention_units None 生成 attention 的 MLP 各层维度,最后一维必须为 1
activation 'relu' attention MLP 的激活函数
initializer GlorotNormal MLP 初始化器
regularizer None MLP 正则化器
out_type 'concat' 输出类型:stack / concat / None
keep_list False 输出是否保持 list

interaction_type='multiply' 且 use_attention=True 时,会把交叉结果 reshape 成 (-1, num_feature, last_dim_size) 送入 attention MLP 加权,实现 attention 线性组合。其底层交叉算子 ffm 见 layer_ops.py,GPU/CPU 实现见 kernels/ffm_kernels.cc 与 kernels/ffm_kernels.h。

6.2 AllInt:引入压缩矩阵的全交互

AllInt(All Interaction,feature_cross.py)在 GroupInt 基础上解决了"Group 需人工决定"的问题:它对所有特征做交叉,同时引入压缩矩阵降低输出维度:

On,c=Xn,k⋅Xn,kT⋅Cn,cO_{n,c} = X_{n,k} \cdot X_{n,k}^T \cdot C_{n,c}

为避免生成 (n, n) 大中间矩阵,实现上先算 X^T · C(得到较小的 (k, c) 矩阵)再做矩阵乘,计算效率更高(见 feature_cross.py)。参数:cmp_dim(压缩维维度,必填)、initializer、regularizer、use_bias(默认 True)、out_type、keep_list。

6.3 CDot:数据自适应的压缩交叉

CDot(Compression and Dot Interaction,feature_cross.py)可视为 AllInt 的升级版,改进有二:

  • AllInt 的压缩矩阵与输入无关,而 CDot 的压缩矩阵与输入数据相关,可自适应调节;
  • CDot 输出时会把压缩后的中间特征一并输出给上层 MLP(call 中 tf.concat([crossed, compressed], axis=1)),而 AllInt 不这么做。

CDot 也常与 MLP 联用:先提取二阶特征,再在二阶特征上提取高阶特征。参数:project_dim(投影维,必填)、compress_units(压缩 MLP 各层维度列表,必填)、activation(默认 relu)、initializer、regularizer。

6.4 CAN:Co-action Network 协同作用网络

CAN(feature_cross.py)的动机是:同一份 Embedding 既要拟合原始特征又要拟合交叉特征时容易"两头都拟合不好",因此通过扩展参数让二者学习相对独立。CAN 将特征对分为 weight side(item) 与 input side(user):weight side 可 reshape 成 MLP 参数,input side 作为 MLP 输入,通过多层 MLP 建模 co-action。

参数:layer_num(默认 2)、activation(默认 relu)、is_seq(是否为序列特征)、is_stacked(user 侧是否为多个特征 stack 起来)。build 中会校验 item 侧维度满足 iemb_size == (u_emb_size * (u_emb_size + 1)) * layer_num,并把 item embedding 切分为权重与偏置序列。

6.5 DCN:三种模式的 Deep & Cross Network

DCN(feature_cross.py)用显式交叉替代 MLP 做高阶特征提取,通过残差联接取得更好效果。支持三种模式(枚举定义见 utils.py 的 DCNType):

  • vector:x_{l+1} = x0 * (xl·w) + b + xl,w 形状为 (dim, 1);
  • matrix:x_{l+1} = x0 * (xl·w + b) + xl,w 形状为 (dim, dim);
  • mixed:低秩专家混合(Mixture of Low-Rank Experts),每个 expert 包含 U/V/C/G 四组参数,经 tanh 激活与 softmax 门控融合。

关键参数:layer_num(默认 1)、dcn_type(默认 DCNType.Matrix)、initializer、regularizer、num_experts(mixed 模式专家数,默认 1)、low_rank(mixed 模式低秩,默认 0)、allow_kernel_norm(默认 False)、use_dropout(默认 False)、keep_prob(默认 0.95)、mode(默认 TRAIN)。当 allow_kernel_norm=True 时,get_variable 会对每个变量做 l2_normalize 并乘以可训练范数(feature_cross.py)。

6.6 CIN:可卷积化的压缩交互网络

CIN(Compressed Interaction Network,feature_cross.py)是 xDeepFM 的核心,形式上介于 DCN 与 FM 之间:

  • 与 DCN 类似,每层计算都与 x0 有关,但不使用残差;
  • 与 FM 类似,交叉用 embedding 计算,但不用点积(bit-wise),而是对应元素相乘后线性组合(vector-wise);
  • 计算上可转化为 1D CNN 高效实现(源码中每层由 Conv1D(filters=uints, kernel_size=1) 完成)。

特点:vector-wise 交互、显式高阶交叉、模型大小不随交叉阶数指数膨胀。参数:hidden_uints(各隐含层单元数列表)、activation、initializer(默认 'glorot_uniform')、regularizer。输入要求 shape 为 (batch, num_feat, emb_size),输出为各层 reduce_sum 后 concat 的结果(feature_cross.py)。

6.7 输出形态控制:out_type 与 merge_tensor_list

多个交叉层通过 out_type / keep_list 控制输出形态,底层由 utils.py 的 merge_tensor_list 实现,支持 MergeType.CONCAT(默认,沿特征维拼接)、MergeType.STACK(堆叠为 3D 张量)与 None(保持 tensor 列表),keep_list=True 时返回 [tensor] 列表形态,方便上层继续处理。

七、特征变换与序列兴趣建模层

7.1 AutoInt:基于 Self-Attention 的特征变换

AutoInt(feature_trans.py)用自注意力做特征交互:对 (n, k) 的样本特征矩阵先计算自相关 softmax(X·X^T) 得到组合系数,再线性组合出 attention 输出。该过程可迭代多层(layer_num),每层是一个完整的 auto-int 单元。参数:layer_num(默认 1)、out_type(默认 concat)、keep_list。

7.2 SeNet:特征重标定

SeNet(feature_trans.py)位于特征变换模块,用于对特征/通道进行重标定(squeeze-and-excitation 风格),以显式建模特征之间的依赖关系,提升重要特征的表达权重。

7.3 iRazor:特征选择与 Embedding 维度搜索

iRazor(feature_trans.py)面向特征选择与 Embedding 维度搜索:为每个 Embedding 分配一个 0~1 的选择概率,训练完成后根据概率大小决定保留或裁剪对应特征/维度,从而在压缩模型规模的同时维持效果。

7.4 DIN / DIEN / DMR_U2I:用户行为序列建模

序列兴趣建模层集中在 feature_seq.py,用于将用户行为序列(点击、购买历史等)聚合为兴趣表达:

  • DIN(Deep Interest Network,feature_seq.py):基于三个假设(行为即兴趣、Target 与兴趣同空间、兴趣匹配用 Attention 表达)。以单个特征为例,将 queries(召回的 Target)tile 成与 keys(行为序列)同 shape 后,concat [queries, keys, queries-keys, queries*keys] 送入 MLP 得到 attention weight,再对 keys 加权求和(mode='sum' 时线性组合,否则仅相乘)。
  • DIEN(feature_seq.py):在 DIN 基础上引入兴趣演化建模。
  • DMR_U2I(feature_seq.py):面向 User-to-Item 场景的兴趣匹配建模。

三者共用 MLP 与 agru.py 中的 AGRUCell(基于注意力门控的 GRU 单元)等基础组件,可直接接入 CTR/CVR 模型的行为序列输入。

八、业务校正层与池化层

8.1 LogitCorrection:训练时校正采样偏差

LogitCorrection(logit_correction.py)解决因负采样等导致 CTR/CVR 预测与后验均值存在偏差的问题。校正既可在训练时也可在推理时进行,为减轻推理负担一般选择训练时进行。

参数:activation(默认 None,校正后可接激活函数)、sample_bias(是否校正采样偏差)。call 接收 (logits, sample_rate) 两个输入,get_sample_logits 按四种组合处理:

  • sample_rate=None 且 sample_bias=True:返回 safe_log_sigmoid(logits);
  • sample_rate 非空且 sample_bias=False:logits - log(sample_rate);
  • 两者都非空/都 True:safe_log_sigmoid(logits) - log(sample_rate);
  • 其他情况:原样返回 logits。

其中 safe_log_sigmoid 通过 relu 与 log1p(exp(-|logits|)) 的数值技巧避免溢出(logit_correction.py)。

8.2 SumPooling / AvgPooling / MaxPooling:向量列表池化

三个池化层(pooling.py)接收向量列表而非单个张量:单元素列表直接返回;多元素分别做 add_n(求和)、add_n / n(平均)、reduce_max(逐位取最大)。它们常用于将多个特征/塔的输出聚合为单一向量:

  • SumPooling:加法池化
  • AvgPooling:平均池化
  • MaxPooling:最大池化

九、多任务与工程配套

除 README 列出的层外,模块还提供了面向多任务与工程化的配套能力(可从 multi_task.py、lhuc.py 查看):

  • MMoE / SNR(multi_task.py):多任务学习的专家混合与随机正则化结构;
  • LHUCTower(lhuc.py):分层注意力塔结构。

所有层均有对应的 *_test.py 单测(如 dense_test.py、mlp_test.py、feature_cross_test.py、norms_test.py、logit_correction_test.py),可作为各层输入输出 shape、数值行为与参数生效方式的权威参考;构建配置见 BUILD。

十、实践建议

  • 优先使用 Monolith Layers:在 Monolith 项目中,直接 from monolith.native_training import layers,以获得 kernel_norm、可训练范数、变量分区与特征洞察等推荐场景增强能力;
  • 复杂超参数用 params 模式:需要集中管理或序列化层配置时,使用 Layer.params() + instantiate(),并可通过 get_config/from_config 往返持久化;
  • 特征交叉按需选型:特征规模小时可用 GroupInt(人工分组可控);不想人工分组时选 AllInt/CDot;需要高阶显式交叉时用 DCN 或 CIN;行为序列场景接入 DIN/DIEN;
  • 多任务训练用 GradNorm:构造 (gnorm_loss, weighted_loss) 参与优化,平衡各任务梯度量级;
  • 采样训练务必加 LogitCorrection:传入 sample_rate 并在训练阶段完成偏差校正,避免在线推理时的额外计算负担。

以上各层均可在 native_training/layers 目录下查看完整源码与测试,结合 README.md 快速上手,构建你的推荐模型。

登录后查看全文
monolith