From 64eb940788777b1d4bc912dd48d7377f169c70b9 Mon Sep 17 00:00:00 2001 From: how <376587752@qq.com> Date: Wed, 8 Apr 2026 08:26:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=86=85=E7=BD=AEDCN-V2?= =?UTF-8?q?=E5=B9=B6=E8=A1=A5=E5=85=85=E5=AE=98=E6=96=B9=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/models/dcn.md | 32 +++ easy_rec/python/model/dcn.py | 58 +++- easy_rec/python/protos/dcn.proto | 11 + samples/model_config/dcn_v2_on_taobao.config | 277 +++++++++++++++++++ 4 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 samples/model_config/dcn_v2_on_taobao.config diff --git a/docs/source/models/dcn.md b/docs/source/models/dcn.md index 8180badb4..74f42ee46 100644 --- a/docs/source/models/dcn.md +++ b/docs/source/models/dcn.md @@ -159,10 +159,42 @@ model_config { - embedding_regularization: 对embedding部分加regularization, 减少overfit ### 示例Config +- 现在除了通过 backbone 组件化方式外,也支持在内置 `DCN` 模型中开启 v2 的矩阵/低秩 Cross: + +```protobuf +model_config: { + model_class: 'DCN' + feature_groups: { group_name: 'all' ... } + dcn { + deep_tower { input: "all" dnn { hidden_units: [256, 128, 64] } } + cross_tower { + input: "all" + cross_num: 3 + version: 2 # 开启 DCN-V2 + projection_dim: 64 # 低秩 U*V,省略则使用 full-rank + diag_scale: 0.1 # 训练稳定性增强 + preactivation: "relu" # 与 Cross 层一致的预激活 + use_bias: true + } + final_dnn { hidden_units: [64, 32, 16] } + l2_regularization: 1e-4 + } + embedding_regularization: 1e-4 +} +``` + 1. DCN V1: [DCN_demo.config](https://easyrec.oss-cn-beijing.aliyuncs.com/config/dcn.config) 1. DCN V2: [dcn_backbone_on_movielens.config](https://github.com/alibaba/EasyRec/tree/master/examples/configs/dcn_backbone_on_movielens.config) +1. DCN V2(内置模型写法):[dcn_v2_on_taobao.config](file:///mlx_devbox/users/zhaohao.9426/playground/EasyRec/samples/model_config/dcn_v2_on_taobao.config) + +本地直接训练命令: + +```bash +python -m easy_rec.python.train_eval --pipeline_config_path samples/model_config/dcn_v2_on_taobao.config +``` + ### 参考论文 1. [DCN v1](https://arxiv.org/abs/1708.05123) diff --git a/easy_rec/python/model/dcn.py b/easy_rec/python/model/dcn.py index fcfa7e780..77b1e56a5 100644 --- a/easy_rec/python/model/dcn.py +++ b/easy_rec/python/model/dcn.py @@ -2,9 +2,12 @@ # Copyright (c) Alibaba, Inc. and its affiliates. import tensorflow as tf +import os from easy_rec.python.layers import dnn from easy_rec.python.model.rank_model import RankModel +from easy_rec.python.layers.keras.interaction import Cross +from easy_rec.python.layers.utils import Parameter from easy_rec.python.protos.dcn_pb2 import DCN as DCNConfig # NOQA @@ -44,6 +47,45 @@ def _cross_net(self, tensor, num_cross_layers): x = tf.math.add(tf.math.add(x0 * xw, b), x) return x + def _cross_net_v2(self, tensor, num_cross_layers): + x0 = tensor + x = tensor + params = {} # picked from proto, env acts only as fallback + cross_tower = self._model_config.cross_tower + if getattr(cross_tower, 'projection_dim', 0): + params['projection_dim'] = int(cross_tower.projection_dim) + preact = getattr(cross_tower, 'preactivation', '') + if preact: + params['preactivation'] = preact + diag = getattr(cross_tower, 'diag_scale', 0.0) + if diag: + params['diag_scale'] = float(diag) + if hasattr(cross_tower, 'use_bias'): + params['use_bias'] = bool(cross_tower.use_bias) + # env fallback + proj_env = os.getenv('EASYREC_DCN_V2_PROJ', None) + if 'projection_dim' not in params and proj_env: + try: + params['projection_dim'] = int(proj_env) + except Exception: + pass + preact_env = os.getenv('EASYREC_DCN_V2_PREACT', None) + if 'preactivation' not in params and preact_env: + params['preactivation'] = preact_env + diag_env = os.getenv('EASYREC_DCN_V2_DIAG', None) + if 'diag_scale' not in params and diag_env: + try: + params['diag_scale'] = float(diag_env) + except Exception: + pass + use_bias_env = os.getenv('EASYREC_DCN_V2_USE_BIAS', None) + if 'use_bias' not in params and use_bias_env: + params['use_bias'] = use_bias_env.lower() in ('1', 'true', 'yes') + layer_params = Parameter(params, True) + for i in range(num_cross_layers): + x = Cross(layer_params, name='cross_v2_%d' % i)([x0, x]) + return x + def build_predict_graph(self): tower_fea_arr = [] # deep tower @@ -56,7 +98,21 @@ def build_predict_graph(self): # cross tower cross_tower_config = self._model_config.cross_tower num_cross_layers = cross_tower_config.cross_num - cross_tensor = self._cross_net(self._features, num_cross_layers) + use_v2 = getattr(cross_tower_config, 'version', 1) == 2 + if not use_v2: + # auto-enable v2 if v2-specific params are set + if getattr(cross_tower_config, 'projection_dim', 0) or \ + getattr(cross_tower_config, 'preactivation', '') or \ + getattr(cross_tower_config, 'diag_scale', 0.0) != 0.0: + use_v2 = True + # env fallback switch + if not use_v2: + if os.getenv('EASYREC_DCN_V2', '0').lower() in ('1', 'true', 'yes'): + use_v2 = True + if use_v2: + cross_tensor = self._cross_net_v2(self._features, num_cross_layers) + else: + cross_tensor = self._cross_net(self._features, num_cross_layers) tower_fea_arr.append(cross_tensor) # final tower all_fea = tf.concat(tower_fea_arr, axis=1) diff --git a/easy_rec/python/protos/dcn.proto b/easy_rec/python/protos/dcn.proto index 7d061ffbe..516c8e026 100644 --- a/easy_rec/python/protos/dcn.proto +++ b/easy_rec/python/protos/dcn.proto @@ -8,6 +8,17 @@ message CrossTower { required string input = 1; // The number of cross layers required uint32 cross_num = 2 [default = 3]; + // DCN version: 1 for vector cross, 2 for matrix/low-rank cross + optional uint32 version = 3 [default = 1]; + // Parameters for DCN v2 (matrix/low-rank) + // If projection_dim is set, use low-rank U*V; otherwise use full-rank. + optional uint32 projection_dim = 4; + // Add diag scale to improve stability: W + diag_scale * I + optional float diag_scale = 5 [default = 0.0]; + // Preactivation applied to Dense output before element-wise product with x0 + optional string preactivation = 6; + // Whether to include bias terms in cross layers + optional bool use_bias = 7 [default = true]; }; message DCN { diff --git a/samples/model_config/dcn_v2_on_taobao.config b/samples/model_config/dcn_v2_on_taobao.config new file mode 100644 index 000000000..38f92eab3 --- /dev/null +++ b/samples/model_config/dcn_v2_on_taobao.config @@ -0,0 +1,277 @@ +train_input_path: "data/test/tb_data/taobao_train_data" +eval_input_path: "data/test/tb_data/taobao_test_data" +model_dir: "experiments/dcn_v2_taobao_ckpt" + +train_config { + log_step_count_steps: 100 + optimizer_config: { + adam_optimizer: { + learning_rate: { + exponential_decay_learning_rate { + initial_learning_rate: 0.001 + decay_steps: 1000 + decay_factor: 0.5 + min_learning_rate: 0.00001 + } + } + } + use_moving_average: false + } + save_checkpoints_steps: 100 + sync_replicas: True + num_steps: 2500 +} + +eval_config { + metrics_set: { + auc {} + } +} + +data_config { + input_fields { + input_name:'clk' + input_type: INT32 + } + input_fields { + input_name:'buy' + input_type: INT32 + } + input_fields { + input_name: 'pid' + input_type: STRING + } + input_fields { + input_name: 'adgroup_id' + input_type: STRING + } + input_fields { + input_name: 'cate_id' + input_type: STRING + } + input_fields { + input_name: 'campaign_id' + input_type: STRING + } + input_fields { + input_name: 'customer' + input_type: STRING + } + input_fields { + input_name: 'brand' + input_type: STRING + } + input_fields { + input_name: 'user_id' + input_type: STRING + } + input_fields { + input_name: 'cms_segid' + input_type: STRING + } + input_fields { + input_name: 'cms_group_id' + input_type: STRING + } + input_fields { + input_name: 'final_gender_code' + input_type: STRING + } + input_fields { + input_name: 'age_level' + input_type: STRING + } + input_fields { + input_name: 'pvalue_level' + input_type: STRING + } + input_fields { + input_name: 'shopping_level' + input_type: STRING + } + input_fields { + input_name: 'occupation' + input_type: STRING + } + input_fields { + input_name: 'new_user_class_level' + input_type: STRING + } + input_fields { + input_name: 'tag_category_list' + input_type: STRING + } + input_fields { + input_name: 'tag_brand_list' + input_type: STRING + } + input_fields { + input_name: 'price' + input_type: INT32 + } + + label_fields: 'clk' + batch_size: 4096 + num_epochs: 10000 + prefetch_size: 32 + input_type: CSVInput +} + +feature_config: { + features: { + input_names: 'pid' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10 + } + features: { + input_names: 'adgroup_id' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 100000 + } + features: { + input_names: 'cate_id' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10000 + } + features: { + input_names: 'campaign_id' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 100000 + } + features: { + input_names: 'customer' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 100000 + } + features: { + input_names: 'brand' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 100000 + } + features: { + input_names: 'user_id' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 100000 + } + features: { + input_names: 'cms_segid' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 100 + } + features: { + input_names: 'cms_group_id' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 100 + } + features: { + input_names: 'final_gender_code' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10 + } + features: { + input_names: 'age_level' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10 + } + features: { + input_names: 'pvalue_level' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10 + } + features: { + input_names: 'shopping_level' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10 + } + features: { + input_names: 'occupation' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10 + } + features: { + input_names: 'new_user_class_level' + feature_type: IdFeature + embedding_dim: 16 + hash_bucket_size: 10 + } + features: { + input_names: 'tag_category_list' + feature_type: TagFeature + separator: '|' + hash_bucket_size: 100000 + embedding_dim: 16 + } + features: { + input_names: 'tag_brand_list' + feature_type: TagFeature + separator: '|' + hash_bucket_size: 100000 + embedding_dim: 16 + } + features: { + input_names: 'price' + feature_type: IdFeature + embedding_dim: 16 + num_buckets: 50 + } +} +model_config: { + model_class: 'DCN' + feature_groups: { + group_name: 'all' + feature_names: 'user_id' + feature_names: 'cms_segid' + feature_names: 'cms_group_id' + feature_names: 'age_level' + feature_names: 'pvalue_level' + feature_names: 'shopping_level' + feature_names: 'occupation' + feature_names: 'new_user_class_level' + feature_names: 'adgroup_id' + feature_names: 'cate_id' + feature_names: 'campaign_id' + feature_names: 'customer' + feature_names: 'brand' + feature_names: 'price' + feature_names: 'pid' + feature_names: 'tag_category_list' + feature_names: 'tag_brand_list' + wide_deep: DEEP + } + dcn { + deep_tower { + input: "all" + dnn { + hidden_units: [256, 128, 96, 64] + } + } + cross_tower { + input: "all" + cross_num: 5 + version: 2 + projection_dim: 64 + diag_scale: 0.1 + preactivation: "relu" + use_bias: true + } + final_dnn { + hidden_units: [128, 96, 64, 32, 16] + } + l2_regularization: 1e-6 + } + embedding_regularization: 1e-4 +}