日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

基于組件化EasyRec框架快速搭建深度推薦算法模型

有層出不窮的算法idea想要快速驗證?核心算法模塊如何快速復用到不同場景的不同模型中?如何通過排列組合構建出新的模型?組件化EasyRec框架可以幫助你以“搭積木”的方式快速構建想要的模型結構,快來試一試吧!

限制說明

僅支持0.8.0或以上版本的組件化的EasyRec框架。

為何需要組件化

1. 靈活搭建模型,所思即所得

依靠動態可插拔的公共組件,以“搭積木”的方式快速構建想要的模型結構。框架提供了"膠水"語法,實現組件間的無縫銜接。

2. 實現組件復用,一次開發到處可用

很多模型之所以被稱之為一個新的模型,是因為引入了一個或多個特殊的子模塊(組件),然而這些子模塊并不僅只能用在該模型中,通過組合各個不同的子模塊可以輕易組裝一個新的模型。

過去一個新開發的公共可選模塊,比如Dense Feature Embedding LayerSENet添加到現有模型中,需要修改所有模型的代碼才能用上新的特性,過程繁瑣易出錯。隨著模型數量和公共模塊數量的增加,為所有模型集成所有公共可選模塊將產生組合爆炸的不可控局面。

組件化實現了底層公共模塊與上層模型的解耦。

3. 提高實驗迭代效率,好的想法值得快速驗證

為已有模型添加新特性將變得十分方便。開發一個新的模型,只需要實現特殊的新模塊,其余部分可以通過組件庫中的已有組件拼裝。

現在我們只需要為新的特征開發一個Keras Layer類,并在指定package中添加import語句,框架就能自動識別并添加到組件庫中,不需要額外操作。新人不再需要熟悉EasyRec的方方面面就可以為框架添加功能,開發效率大大提高。

組件化的目標

不再需要實現新的模型,只需要實現新的組件! 模型通過組裝組件完成。

各個組件專注自身功能的實現,模塊中代碼高度聚合,只負責一項任務,也就是常說的單一職責原則。

主干網絡

組件化EasyRec模型使用一個可配置的主干網絡作為核心部件。主干網絡是由多個組件塊組成的一個有向無環圖(DAG),框架負責按照DAG的拓撲排序執行個組件塊關聯的代碼邏輯,構建TF Graph的一個子圖。DAG的輸出節點由concat_blocks配置項定義,各輸出組件塊的輸出tensor拼接之后輸入給一個可選的頂部MLP層,或者直接鏈接到最終的預測層。

image.png

案例1. Wide&Deep 模型

配置文件:wide_and_deep_backbone_on_movielens.config

model_config: {
 model_name: "WideAndDeep"
 model_class: "RankModel"
 feature_groups: {
   group_name: 'wide'
   feature_names: 'user_id'
   feature_names: 'movie_id'
   feature_names: 'job_id'
   feature_names: 'age'
   feature_names: 'gender'
   feature_names: 'year'
   feature_names: 'genres'
   wide_deep: WIDE
 }
 feature_groups: {
   group_name: 'deep'
   feature_names: 'user_id'
   feature_names: 'movie_id'
   feature_names: 'job_id'
   feature_names: 'age'
   feature_names: 'gender'
   feature_names: 'year'
   feature_names: 'genres'
   wide_deep: DEEP
 }
 backbone {
   blocks {
     name: 'wide'
     inputs {
     	feature_group_name: 'wide'
     }
     input_layer {
     	only_output_feature_list: true
     	wide_output_dim: 1
     }
   }
   blocks {
     name: 'deep_logit'
     inputs {
     	feature_group_name: 'deep'
     }
     keras_layer {
     	 class_name: 'MLP'
       mlp {
         hidden_units: [256, 256, 256, 1]
         use_final_bn: false
         final_activation: 'linear'
       }
     }
   }
   blocks {
     name: 'final_logit'
     inputs {
       block_name: 'wide'
       input_fn: 'lambda x: tf.add_n(x)'
     }
     inputs {
       block_name: 'deep_logit'
     }
     merge_inputs_into_list: true
     keras_layer {
       class_name: 'Add'
     }
   }
   concat_blocks: 'final_logit'
 }
 model_params {
 	l2_regularization: 1e-4
 }
 embedding_regularization: 1e-4
}

MovieLens-1M數據集效果對比:

Model

Epoch

AUC

Wide&Deep

1

0.8558

Wide&Deep(Backbone)

1

0.8854

備注:通過組件化的方式搭建的模型效果比內置的模型效果更好是因為MLP組件有更好的初始化方法。

通過protobuf messagebackbone來定義主干網絡,主干網絡有多個積木塊(block)組成,每個block代表一個可復用的組件。

  • 每個block有一個唯一的名字(name),并且有一個或多個輸入和輸出。

  • 每個輸入只能是某個feature groupname,或者另一個blockname,或者是一個block package的名字。當一個block有多個輸入時,會自動執行merge操作(輸入為list時自動合并,輸入為tensor時自動concat)。

  • 所有block根據輸入與輸出的關系組成一個有向無環圖(DAG),框架自動解析出DAG的拓撲關系,按照拓撲排序執行塊所關聯的模塊。

  • block有多個輸出時,返回一個python元組(tuple),下游block可以配置input_slice通過python切片語法獲取到輸入元組的某個元素作為輸入,或者通過自定義的input_fn配置一個lambda表達式函數獲取元組的某個值。

  • 每個block關聯的模塊通常是一個keras layer對象,實現了一個可復用的子網絡模塊。框架支持加載自定義的keras layer,以及所有系統內置的keras layer。

  • 可以為block關聯一個input_layer對輸入的feature group配置的特征做一些額外的加工,比如執行batch normalizationlayer normalizationfeature dropout等操作,并且可以指定輸出的tensor的格式(2d、3d、list等)。注意:block關聯的模塊是input_layer時,必須設定feature_group_name為某個feature group的名字,當block關聯的模塊不是input_layer時,blockname不可與某個feature group重名。

  • 還有一些特殊的block關聯了一個特殊的模塊,包括lambda layersequential layersrepeated layerrecurrent layer。這些特殊layer分別實現了自定義表達式、順序執行多個layer、重復執行某個layer、循環執行某個layer的功能。

  • DAG的輸出節點名由concat_blocks配置項指定,配置了多個輸出節點時自動執行tensorconcat操作。

  • 如果不配置concat_blocks,框架會自動拼接DAG的所有葉子節點并輸出。

  • 可以為主干網絡配置一個可選的MLP模塊。

image.png

案例2:DeepFM 模型

配置文件:deepfm_backbone_on_movielens.config

這個Case重點關注下兩個特殊的block,一個使用了lambda表達式配置了一個自定義函數;另一個的加載了一個內置的keras layertf.keras.layers.Add

model_config: {
  model_name: 'DeepFM'
  model_class: 'RankModel'
  feature_groups: {
    group_name: 'wide'
    feature_names: 'user_id'
    feature_names: 'movie_id'
    feature_names: 'job_id'
    feature_names: 'age'
    feature_names: 'gender'
    feature_names: 'year'
    feature_names: 'genres'
    wide_deep: WIDE
  }
  feature_groups: {
    group_name: 'features'
    feature_names: 'user_id'
    feature_names: 'movie_id'
    feature_names: 'job_id'
    feature_names: 'age'
    feature_names: 'gender'
    feature_names: 'year'
    feature_names: 'genres'
    feature_names: 'title'
    wide_deep: DEEP
  }
  backbone {
    blocks {
      name: 'wide_logit'
      inputs {
        feature_group_name: 'wide'
      }
      input_layer {
        wide_output_dim: 1
      }
    }
    blocks {
      name: 'features'
      inputs {
        feature_group_name: 'features'
      }
      input_layer {
        output_2d_tensor_and_feature_list: true
      }
    }
    blocks {
      name: 'fm'
      inputs {
        block_name: 'features'
        input_slice: '[1]'
      }
      keras_layer {
        class_name: 'FM'
      }
    }
    blocks {
      name: 'deep'
      inputs {
        block_name: 'features'
        input_slice: '[0]'
      }
      keras_layer {
        class_name: 'MLP'
        mlp {
          hidden_units: [256, 128, 64, 1]
          use_final_bn: false
          final_activation: 'linear'
        }
      }
    }
    blocks {
      name: 'add'
      inputs {
        block_name: 'wide_logit'
        input_fn: 'lambda x: tf.reduce_sum(x, axis=1, keepdims=True)'
      }
      inputs {
        block_name: 'fm'
      }
      inputs {
        block_name: 'deep'
      }
      merge_inputs_into_list: true
      keras_layer {
        class_name: 'Add'
      }
    }
    concat_blocks: 'add'
  }
  model_params {
    l2_regularization: 1e-4
  }
  embedding_regularization: 1e-4
}

MovieLens-1M數據集效果對比:

Model

Epoch

AUC

DeepFM

1

0.8867

DeepFM(Backbone)

1

0.8872

案例3:DCN 模型

配置文件:dcn_backbone_on_movielens.config

這個Case重點關注一個特殊的 DCNblock,用了recurrent layer實現了循環調用某個模塊多次的效果。通過該Case還是在DAG之上添加了MLP模塊。

model_config: {
  model_name: 'DCN V2'
  model_class: 'RankModel'
  feature_groups: {
    group_name: 'all'
    feature_names: 'user_id'
    feature_names: 'movie_id'
    feature_names: 'job_id'
    feature_names: 'age'
    feature_names: 'gender'
    feature_names: 'year'
    feature_names: 'genres'
    wide_deep: DEEP
  }
  backbone {
    blocks {
      name: "deep"
      inputs {
        feature_group_name: 'all'
      }
      keras_layer {
        class_name: 'MLP'
        mlp {
          hidden_units: [256, 128, 64]
        }
      }
    }
    blocks {
      name: "dcn"
      inputs {
        feature_group_name: 'all'
        input_fn: 'lambda x: [x, x]'
      }
      recurrent {
        num_steps: 3
        fixed_input_index: 0
        keras_layer {
          class_name: 'Cross'
        }
      }
    }
    concat_blocks: ['deep', 'dcn']
    top_mlp {
      hidden_units: [64, 32, 16]
    }
  }
  model_params {
    l2_regularization: 1e-4
  }
  embedding_regularization: 1e-4
}

上述配置對CrossLayer循環調用了3次,邏輯上等價于執行如下語句:

x1 = Cross()(x0, x0)
x2 = Cross()(x0, x1)
x3 = Cross()(x0, x2)

MovieLens-1M數據集效果對比:

Model

Epoch

AUC

DCN (內置)

1

0.8576

DCN_v2 (backbone)

1

0.8770

備注:新實現的Cross組件對應了參數量更多的v2版本的DCN,而內置的DCN模型對應了v1版本的DCN。

image.png

案例4:DLRM 模型

配置文件:dlrm_backbone_on_criteo.config

model_config: {
  model_name: 'DLRM'
  model_class: 'RankModel'
  feature_groups: {
    group_name: "dense"
    feature_names: "F1"
    feature_names: "F2"
    ...
    wide_deep:DEEP
  }
  feature_groups: {
    group_name: "sparse"
    feature_names: "C1"
    feature_names: "C2"
    feature_names: "C3"
    ...
    wide_deep:DEEP
  }
  backbone {
    blocks {
      name: 'bottom_mlp'
      inputs {
        feature_group_name: 'dense'
      }
      keras_layer {
        class_name: 'MLP'
        mlp {
          hidden_units: [64, 32, 16]
        }
      }
    }
    blocks {
      name: 'sparse'
      inputs {
        feature_group_name: 'sparse'
      }
      input_layer {
        output_2d_tensor_and_feature_list: true
      }
    }
    blocks {
      name: 'dot'
      inputs {
        block_name: 'bottom_mlp'
      }
      inputs {
        block_name: 'sparse'
        input_slice: '[1]'
      }
      keras_layer {
        class_name: 'DotInteraction'
      }
    }
    blocks {
      name: 'sparse_2d'
      inputs {
        block_name: 'sparse'
        input_slice: '[0]'
      }
    }
    concat_blocks: ['sparse_2d', 'dot']
    top_mlp {
      hidden_units: [256, 128, 64]
    }
  }
  model_params {
    l2_regularization: 1e-5
  }
  embedding_regularization: 1e-5
}

Criteo數據集效果對比:

Model

Epoch

AUC

DLRM

1

0.79785

DLRM (backbone)

1

0.7993

備注:DotInteraction是新開發的特征兩兩交叉做內積運算的模塊。

這個案例中'dot' block的第一個輸入是一個tensor,第二個輸入是一個list,這種情況下第一個輸入會插入到list中,合并成一個更大的list,作為block的輸入。

案例5:為 DLRM 模型添加一個新的數值特征Embedding組件

配置文件:dlrm_on_criteo_with_periodic.config

與上一個案例相比,多了一個PeriodicEmbeddingLayer,組件化編程的靈活性與可擴展性由此可見一斑。

重點關注一下PeriodicEmbeddingLayer的參數配置方式,這里并沒有使用自定義protobuf message的傳參方式,而是采用了內置的google.protobuf.Struct對象作為自定義Layer的參數。實際上,該自定義Layer也支持通過自定義message傳參。框架提供了一個通用的ParameterAPI 用通用的方式處理兩種傳參方式。

model_config: {
  model_class: 'RankModel'
  feature_groups: {
    group_name: "dense"
    feature_names: "F1"
    feature_names: "F2"
    ...
    wide_deep:DEEP
  }
  feature_groups: {
    group_name: "sparse"
    feature_names: "C1"
    feature_names: "C2"
    ...
    wide_deep:DEEP
  }
  backbone {
    blocks {
      name: 'num_emb'
      inputs {
        feature_group_name: 'dense'
      }
      keras_layer {
        class_name: 'PeriodicEmbedding'
        st_params {
          fields {
            key: "output_tensor_list"
            value { bool_value: true }
          }
          fields {
            key: "embedding_dim"
            value { number_value: 16 }
          }
          fields {
            key: "sigma"
            value { number_value: 0.005 }
          }
        }
      }
    }
    blocks {
      name: 'sparse'
      inputs {
        feature_group_name: 'sparse'
      }
      input_layer {
        output_2d_tensor_and_feature_list: true
      }
    }
    blocks {
      name: 'dot'
      inputs {
        block_name: 'num_emb'
        input_slice: '[1]'
      }
      inputs {
        block_name: 'sparse'
        input_slice: '[1]'
      }
      keras_layer {
        class_name: 'DotInteraction'
      }
    }
    blocks {
      name: 'sparse_2d'
      inputs {
        block_name: 'sparse'
        input_slice: '[0]'
      }
    }
    blocks {
      name: 'num_emb_2d'
      inputs {
        block_name: 'num_emb'
        input_slice: '[0]'
      }
    }
    concat_blocks: ['num_emb_2d', 'dot', 'sparse_2d']
    top_mlp {
      hidden_units: [256, 128, 64]
    }
  }
  model_params {
    l2_regularization: 1e-5
  }
  embedding_regularization: 1e-5
}

image.png

Criteo數據集效果對比:

Model

Epoch

AUC

DLRM

1

0.79785

DLRM (backbone)

1

0.7993

DLRM (periodic)

1

0.7998

案例6:使用內置的keras layer搭建DNN模型

配置文件:mlp_on_movielens.config

該案例只為了演示可以組件化EasyRec可以使用TF內置的原子粒度keras layer作為通用組件,實際上我們已經有了一個自定義的MLP組件,使用會更加方便。

該案例重點關注一個特殊的sequential block,這個組件塊內可以定義多個串聯在一起的layers,前一個layer的輸出作為后一個layer的輸入。相比定義多個普通block的方式,sequential block會更加方便。

備注:調用系統內置的keras layer,只能通過google.proto.Struct的格式傳參。

model_config: {
  model_class: "RankModel"
  feature_groups: {
    group_name: 'features'
    feature_names: 'user_id'
    feature_names: 'movie_id'
    feature_names: 'job_id'
    feature_names: 'age'
    feature_names: 'gender'
    feature_names: 'year'
    feature_names: 'genres'
    wide_deep: DEEP
  }
  backbone {
    blocks {
      name: 'mlp'
      inputs {
        feature_group_name: 'features'
      }
      layers {
        keras_layer {
          class_name: 'Dense'
          st_params {
            fields {
              key: 'units'
              value: { number_value: 256 }
            }
            fields {
              key: 'activation'
              value: { string_value: 'relu' }
            }
          }
        }
      }
      layers {
        keras_layer {
          class_name: 'Dropout'
          st_params {
            fields {
              key: 'rate'
              value: { number_value: 0.5 }
            }
          }
        }
      }
      layers {
        keras_layer {
          class_name: 'Dense'
          st_params {
            fields {
              key: 'units'
              value: { number_value: 256 }
            }
            fields {
              key: 'activation'
              value: { string_value: 'relu' }
            }
          }
        }
      }
      layers {
        keras_layer {
          class_name: 'Dropout'
          st_params {
            fields {
              key: 'rate'
              value: { number_value: 0.5 }
            }
          }
        }
      }
      layers {
        keras_layer {
          class_name: 'Dense'
          st_params {
            fields {
              key: 'units'
              value: { number_value: 1 }
            }
          }
        }
      }
    }
    concat_blocks: 'mlp'
  }
  model_params {
    l2_regularization: 1e-4
  }
  embedding_regularization: 1e-4
}

MovieLens-1M數據集效果:

Model

Epoch

AUC

MLP

1

0.8616

案例7:對比學習(使用組件包)

配置文件:contrastive_learning_on_movielens.config

該案例為了演示block package的使用,block package可以打包一組block,構成一個可被復用的子網絡,即被打包的子網絡以共享參數的方式在同一個模型中調用多次。與之相反,沒有打包的block是不能被多次調用的(但是可以多次復用結果)。

block package主要為自監督學習、對比學習等場景設計。

model_config: {
  model_name: "ContrastiveLearning"
  model_class: "RankModel"
  feature_groups: {
    group_name: 'user'
    feature_names: 'user_id'
    feature_names: 'job_id'
    feature_names: 'age'
    feature_names: 'gender'
    wide_deep: DEEP
  }
  feature_groups: {
    group_name: 'item'
    feature_names: 'movie_id'
    feature_names: 'year'
    feature_names: 'genres'
    wide_deep: DEEP
  }
  backbone {
    blocks {
      name: 'user_tower'
      inputs {
        feature_group_name: 'user'
      }
      keras_layer {
        class_name: 'MLP'
        mlp {
          hidden_units: [256, 128]
        }
      }
    }
    packages {
      name: 'item_tower'
      blocks {
        name: 'item'
        inputs {
          feature_group_name: 'item'
        }
        input_layer {
          dropout_rate: 0.2
        }
      }
      blocks {
        name: 'item_encoder'
        inputs {
          block_name: 'item'
        }
        keras_layer {
          class_name: 'MLP'
          mlp {
            hidden_units: [256, 128]
          }
        }
      }
    }
    blocks {
      name: 'contrastive_learning'
      inputs {
        package_name: 'item_tower'
      }
      inputs {
        package_name: 'item_tower'
      }
      merge_inputs_into_list: true
      keras_layer {
        class_name: 'AuxiliaryLoss'
        st_params {
          fields {
            key: 'loss_type'
            value: { string_value: 'info_nce' }
          }
          fields {
            key: 'loss_weight'
            value: { number_value: 0.1 }
          }
          fields {
            key: 'temperature'
            value: { number_value: 0.2 }
          }
        }
      }
    }
    blocks {
      name: 'top_mlp'
      inputs {
        block_name: 'contrastive_learning'
        ignore_input: true
      }
      inputs {
        block_name: 'user_tower'
      }
      inputs {
        package_name: 'item_tower'
        reset_input {}
      }
      keras_layer {
        class_name: 'MLP'
        mlp {
          hidden_units: [128, 64]
        }
      }
    }
    concat_blocks: 'top_mlp'
  }
  model_params {
    l2_regularization: 1e-4
  }
  embedding_regularization: 1e-4
}

AuxiliaryLoss是用來計算對比學習損失的layer,詳見'組件詳細參數'。

額外的input配置:

  • ignore_input: true 表示忽略當前這路的輸入;添加該路輸入只是為了控制拓撲結構的執行順序

  • reset_input: 重置本次package調用時input_layer的配置項;可以配置與package定義時不同的參數

注意這個案例沒有為名為item_towerpackage配置concat_blocks,框架會自動設置為DAG的葉子節點。

在當前案例中,item_tower被調用了3次,前2次調用時輸入層dropout配置生效,用于計算對比學習損失函數;最后1次調用時重置了輸入層配置,不執行dropout。 主模型的item_tower與對比學習輔助任務中的item_tower共享參數;輔助任務中的item_tower通過對輸入特征embeddingdropout來生成augmented sample;主模型的item_tower不執行數據增強操作。

MovieLens-1M數據集效果:

Model

Epoch

AUC

MultiTower

1

0.8814

ContrastiveLearning

1

0.8728

一個更復雜一點的對比學習模型案例:CL4SRec

案例8:多目標模型 MMoE

多目標模型的model_class一般配置為"MultiTaskModel",并且需要在model_params里配置多個目標對應的Tower。model_name為任意自定義字符串,僅有注釋作用。

model_config {
  model_name: "MMoE"
  model_class: "MultiTaskModel"
  feature_groups {
    group_name: "all"
    feature_names: "user_id"
    feature_names: "cms_segid"
    ...
    feature_names: "tag_brand_list"
    wide_deep: DEEP
  }
  backbone {
    blocks {
      name: 'all'
      inputs {
        feature_group_name: 'all'
      }
      input_layer {
        only_output_feature_list: true
      }
    }
    blocks {
      name: "senet"
      inputs {
        block_name: "all"
      }
      keras_layer {
        class_name: 'SENet'
        senet {
          reduction_ratio: 4
        }
      }
    }
    blocks {
      name: "mmoe"
      inputs {
        block_name: "senet"
      }
      keras_layer {
        class_name: 'MMoE'
        mmoe {
          num_task: 2
          num_expert: 3
          expert_mlp {
            hidden_units: [256, 128]
          }
        }
      }
    }
  }
  model_params {
    task_towers {
      tower_name: "ctr"
      label_name: "clk"
      dnn {
        hidden_units: [128, 64]
      }
      num_class: 1
      weight: 1.0
      loss_type: CLASSIFICATION
      metrics_set: {
       auc {}
      }
    }
    task_towers {
      tower_name: "cvr"
      label_name: "buy"
      dnn {
        hidden_units: [128, 64]
      }
      num_class: 1
      weight: 1.0
      loss_type: CLASSIFICATION
      metrics_set: {
       auc {}
      }
    }
    l2_regularization: 1e-06
  }
  embedding_regularization: 5e-05
}

注意這個案例沒有為backbone配置concat_blocks,框架會自動設置為DAG的葉子節點。

案例9:多目標模型 DBMTL

多目標模型的model_class一般配置為"MultiTaskModel",并且需要在model_params里配置多個目標對應的Tower。model_name為任意自定義字符串,僅有注釋作用。

model_config {
 model_name: "DBMTL"
 model_class: "MultiTaskModel"
 feature_groups {
 group_name: "all"
 feature_names: "user_id"
 feature_names: "cms_segid"
 ...
 feature_names: "tag_brand_list"
 wide_deep: DEEP
 }
 backbone {
 blocks {
 name: "mask_net"
 inputs {
 feature_group_name: "all"
 }
 keras_layer {
 class_name: 'MaskNet'
 masknet {
 mask_blocks {
 aggregation_size: 512
 output_size: 256
 }
 mask_blocks {
 aggregation_size: 512
 output_size: 256
 }
 mask_blocks {
 aggregation_size: 512
 output_size: 256
 }
 mlp {
 hidden_units: [512, 256]
 }
 }
 }
 }
 }
 model_params {
 task_towers {
 tower_name: "ctr"
 label_name: "clk"
 loss_type: CLASSIFICATION
 metrics_set: {
 auc {}
 }
 dnn {
 hidden_units: [256, 128, 64]
 }
 relation_dnn {
 hidden_units: [32]
 }
 weight: 1.0
 }
 task_towers {
 tower_name: "cvr"
 label_name: "buy"
 loss_type: CLASSIFICATION
 metrics_set: {
 auc {}
 }
 dnn {
 hidden_units: [256, 128, 64]
 }
 relation_tower_names: ["ctr"]
 relation_dnn {
 hidden_units: [32]
 }
 weight: 1.0
 }
 l2_regularization: 1e-6
 }
 embedding_regularization: 5e-6
}

DBMTL模型需要在model_params里為每個子任務的Tower配置relation_dnn,同時還需要通relation_tower_names配置任務間的依賴關系。

這個案例同樣沒有為backbone配置concat_blocks,框架會自動設置為DAG的葉子節點。

案例10:MaskNet + PPNet + MMoE

model_config: {
  model_name: 'MaskNet + PPNet + MMoE'
  model_class: 'RankModel'
  feature_groups: {
    group_name: 'memorize'
    feature_names: 'user_id'
    feature_names: 'adgroup_id'
    feature_names: 'pid'
    wide_deep: DEEP
  }
  feature_groups: {
    group_name: 'general'
    feature_names: 'age_level'
    feature_names: 'shopping_level'
    ...
    wide_deep: DEEP
  }
  backbone {
    blocks {
      name: "mask_net"
      inputs {
        feature_group_name: "general"
      }
      repeat {
        num_repeat: 3
        keras_layer {
          class_name: "MaskBlock"
          mask_block {
            output_size: 512
            aggregation_size: 1024
          }
        }
      }
    }
    blocks {
      name: "ppnet"
      inputs {
        block_name: "mask_net"
      }
      inputs {
        feature_group_name: "memorize"
      }
      merge_inputs_into_list: true
      repeat {
        num_repeat: 3
        input_fn: "lambda x, i: [x[0][i], x[1]]"
        keras_layer {
          class_name: "PPNet"
          ppnet {
            mlp {
              hidden_units: [256, 128, 64]
            }
            gate_params {
              output_dim: 512
            }
            mode: "eager"
            full_gate_input: false
          }
        }
      }
    }
    blocks {
      name: "mmoe"
      inputs {
        block_name: "ppnet"
      }
      inputs {
        feature_group_name: "general"
      }
      keras_layer {
        class_name: "MMoE"
        mmoe {
          num_task: 2
          num_expert: 3
        }
      }
    }
  }
  model_params {
    l2_regularization: 0.0
    task_towers {
      tower_name: "ctr"
      label_name: "is_click"
      metrics_set {
        auc {
          num_thresholds: 20000
        }
      }
      loss_type: CLASSIFICATION
      num_class: 1
      dnn {
        hidden_units: 64
        hidden_units: 32
      }
      weight: 1.0
    }
    task_towers {
      tower_name: "cvr"
      label_name: "is_train"
      metrics_set {
        auc {
          num_thresholds: 20000
        }
      }
      loss_type: CLASSIFICATION
      num_class: 1
      dnn {
        hidden_units: 64
        hidden_units: 32
      }
      weight: 1.0
    }
  }
}

該案例體現了如何應用重復組件塊

更多案例

兩個新的模型:

MovieLens-1M數據集效果:

Model

Epoch

AUC

MaskNet

1

0.8872

FibiNet

1

0.8893

序列模型:

其他模型:

組件庫介紹

1.基礎組件

類名

功能

說明

示例

MLP

多層感知機

可定制激活函數、initializer、Dropout、BN

案例1

Highway

類似殘差鏈接

可用來對預訓練embedding做增量微調

Highway Network

Gate

門控

多個輸入的加權求和

CDN

PeriodicEmbedding

周期激活函數

數值特征Embedding

案例5

AutoDisEmbedding

自動離散化

數值特征Embedding

dlrm_on_criteo_with_autodis.config

備注:Gate組件的第一個輸入是權重向量,后面的輸入拼湊成一個列表,權重向量的長度應等于列表的長度

2.特征交叉組件

類名

功能

說明

示例

FM

二階交叉

DeepFM模型的組件

案例2

DotInteraction

二階內積交叉

DLRM模型的組件

案例4

Cross

bit-wise交叉

DCN v2模型的組件

案例3

BiLinear

雙線性

FiBiNet模型的組件

fibinet_on_movielens.config

FiBiNet

SENet & BiLinear

FiBiNet模型

fibinet_on_movielens.config

3.特征重要度學習組件

類名

功能

說明

示例

SENet

建模特征重要度

FiBiNet模型的組件

MMoE

MaskBlock

建模特征重要度

MaskNet模型的組件

CDN

MaskNet

多個串行或并行的MaskBlock

MaskNet模型

DBMTL

PPNet

參數個性化網絡

PPNet模型

PPNet

4. 序列特征編碼組件

類名

功能

說明

示例

DIN

target attention

DIN模型的組件

DIN_backbone.config

BST

transformer

BST模型的組件

BST_backbone.config

SeqAugment

序列數據增強

crop, mask, reorder

CL4SRec

5. 多目標學習組件

類名

功能

說明

示例

MMoE

Multiple Mixture of Experts

MMoE模型的組件

案例8

6. 輔助損失函數組件

類名

功能

說明

示例

AuxiliaryLoss

用來計算輔助損失函數

常用在自監督學習中

案例7

各組件的詳細參數請查看“組件詳細參數”。

如何自定義組件

easy_rec/python/layers/keras目錄下新建一個py文件,也可直接添加到一個已有的文件中。我們建議目標類似的組件定義在同一個文件中,減少文件數量;比如特征交叉的組件都放在interaction.py里。

定義一個繼承tf.keras.layers.Layer的組件類,至少實現兩個方法:__init__call

def __init__(self, params, name='xxx', reuse=None, **kwargs):
 pass
def call(self, inputs, training=None, **kwargs):
 pass

__init__方法的第一個參數params接收框架傳遞給當前組件的參數。支持兩種參數配置的方式:google.protobuf.Struct、自定義的protobuf message對象。params對象封裝了對這兩種格式的參數的統一讀取接口,如下:

  • 檢查必傳參數,缺失時報錯退出:params.check_required(['embedding_dim', 'sigma'])

  • 用點操作符讀取參數:sigma = params.sigma;支持連續點操作符,如params.a.b

  • 注意數值型參數的類型,Struct只支持float類型,整型需要強制轉換:embedding_dim = int(params.embedding_dim)

  • 數組類型也需要強制類型轉換:units = list(params.hidden_units)

  • 指定默認值讀取,返回值會被強制轉換為與默認值同類型:activation = params.get_or_default('activation', 'relu')

  • 支持嵌套子結構的默認值讀取:params.field.get_or_default('key', def_val)

  • 判斷某個參數是否存在:params.has_field(key)

  • 【不建議,會限定傳參方式】獲取自定義的proto對象:params.get_pb_config()

  • 讀寫l2_regularizer屬性:params.l2_regularizer,傳給Dense層或dense函數。

【可選】如需要自定義protobuf message參數,先在easy_rec/python/protos/layer.proto添加參數message的定義, 再把參數注冊到定義在easy_rec/python/protos/keras_layer.protoKerasLayer.params消息體中。

__init__方法的reuse參數表示該Layer對象的權重參數是否需要被復用。開發時需要按照可復用的邏輯來實現Layer對象,推薦嚴格按照keras layer的規范來實現。 盡量在__init__方法中聲明需要依賴的keras layer對象;僅在必要時才使用tf.layers.*函數,且需要傳遞reuse參數。

提示:實現Layer對象時盡量使用原生的 tf.keras.layers.* 對象,且全部在 __init__ 方法中預先聲明好。

call方法用來實現主要的模塊邏輯,其inputs參數可以是一個tenor,或者是一個tensor列表。可選的training參數用來標識當前是否是訓練模型。

最后也是最重要的一點,新開發的Layer需要在easy_rec.python.layers.keras.__init__.py文件中導出才能被框架識別為組件庫中的一員。例如要導出blocks.py文件中的MLP類,則需要添加:from .blocks import MLP

FM layer的代碼示例:

class FM(tf.keras.layers.Layer):
 """Factorization Machine models pairwise (order-2) feature interactions without linear term and bias.

 References
 - [Factorization Machines](https://www.csie.ntu.edu.tw/~b97053/paper/Rendle2010FM.pdf)
 Input shape.
 - List of 2D tensor with shape: ``(batch_size,embedding_size)``.
 - Or a 3D tensor with shape: ``(batch_size,field_size,embedding_size)``
 Output shape
 - 2D tensor with shape: ``(batch_size, 1)``.
 """

 def __init__(self, params, name='fm', reuse=None, **kwargs):
 super(FM, self).__init__(name, **kwargs)
 self.reuse = reuse
 self.use_variant = params.get_or_default('use_variant', False)

 def call(self, inputs, **kwargs):
 if type(inputs) == list:
 emb_dims = set(map(lambda x: int(x.shape[-1]), inputs))
 if len(emb_dims) != 1:
 dims = ','.join([str(d) for d in emb_dims])
 raise ValueError('all embedding dim must be equal in FM layer:' + dims)
 with tf.name_scope(self.name):
 fea = tf.stack(inputs, axis=1)
 else:
 assert inputs.shape.ndims == 3, 'input of FM layer must be a 3D tensor or a list of 2D tensors'
 fea = inputs

 with tf.name_scope(self.name):
 square_of_sum = tf.square(tf.reduce_sum(fea, axis=1))
 sum_of_square = tf.reduce_sum(tf.square(fea), axis=1)
 cross_term = tf.subtract(square_of_sum, sum_of_square)
 if self.use_variant:
 cross_term = 0.5 * cross_term
 else:
 cross_term = 0.5 * tf.reduce_sum(cross_term, axis=-1, keepdims=True)
 return cross_term

如何搭建模型

組件塊組件包是搭建主干網絡的核心部件,本小節將會介紹組件塊的類型、功能和配置參數;同時還會介紹專門為參數共享子網絡設計的組件包

通過組件塊組件包搭建模型的配置方法請參考上文描述的各個案例

組件塊protobuf定義如下:

message Block {
 required string name = 1;
 // the input names of feature groups or other blocks
 repeated Input inputs = 2;
 optional int32 input_concat_axis = 3 [default = -1];
 optional bool merge_inputs_into_list = 4;
 optional string extra_input_fn = 5;

 // sequential layers
 repeated Layer layers = 6;
 // only take effect when there are no layers
 oneof layer {
 InputLayer input_layer = 101;
 Lambda lambda = 102;
 KerasLayer keras_layer = 103;
 RecurrentLayer recurrent = 104;
 RepeatLayer repeat = 105;
 }
}

組件塊會自動合并多個輸入:

  1. 若多路輸入中某一路的輸入類型是list,則最終結果被Merge成一個大的list,保持順序不變;

  2. 若多路輸入中的每一路輸入都是tensor,默認是執行輸入tensors按照最后一個維度做拼接(concat),以下配置項可以改變默認行為:

  • input_concat_axis用來指定輸入tensors拼接的維度

  • merge_inputs_into_list設為true,則把輸入合并到一個列表里,不做concat操作

message Input {
 oneof name {
 string feature_group_name = 1;
 string block_name = 2;
 string package_name = 3;
 }
 optional string input_fn = 11;
 optional string input_slice = 12;
}
  • 每一路輸入可以配置一個可選的input_fn,指定一個lambda函數對輸入做一些簡單的變換。比如配置input_fn: 'lambda x: [x]'可以把輸入變成列表格式。

  • input_slice可以用來獲取輸入元組/列表的某個切片。比如,當某路輸入是一個列表對象是,可以用input_slice: '[1]'配置項獲取列表的第二個元素值作為這一路的輸入。

  • extra_input_fn是一個可選的配置項,用來對合并后的多路輸入結果做一些額外的變換,需要配置成lambda函數的格式。

目前總共有7種類型的組件塊,分別是空組件塊輸入組件塊Lambda組件塊KerasLayer組件塊循環組件塊重復組件塊序列組件塊

1. 空組件塊

當一個block不配置任何layer時就稱之為空組件塊空組件塊只執行多路輸入的Merge操作。

2. 輸入組件塊

輸入組件塊關聯一個input_layer,獲取、加工并返回原始的特征輸入。

輸入組件塊比較特殊,它只能有且只有一路輸入,并且只能用feature_group_name項配置輸入為一個feature_groupname

輸入組件塊有一個特權:它的名字可以與其輸入的feature_group同名。其他組件塊則無此殊榮。

配置示例:

blocks {
 name: 'all'
 inputs {
 feature_group_name: 'all'
 }
 input_layer {
 only_output_feature_list: true
 }
}

InputLayer可以通過配置獲取不同格式的輸入,并且可以執行一些如dropout之類的額外操作,其參數定義的protobuf如下:

message InputLayer {
 optional bool do_batch_norm = 1;
 optional bool do_layer_norm = 2;
 optional float dropout_rate = 3;
 optional float feature_dropout_rate = 4;
 optional bool only_output_feature_list = 5;
 optional bool only_output_3d_tensor = 6;
 optional bool output_2d_tensor_and_feature_list = 7;
 optional bool output_seq_and_normal_feature = 8;
}

輸入層的定義如上,配置下說明如下:

  • do_batch_norm是否對輸入特征做batch normalization

  • do_layer_norm是否對輸入特征做layer normalization

  • dropout_rate輸入層執行dropout的概率,默認不執行dropout

  • feature_dropout_rate對特征整體執行dropout的概率,默認不執行

  • only_output_feature_list輸出list格式的各個特征

  • only_output_3d_tensor輸出feature group對應的一個3d tensor,在embedding_dim相同時可配置該項

  • output_2d_tensor_and_feature_list是否同時輸出2d tensor與特征list

  • output_seq_and_normal_feature是否輸出(sequence特征, 常規特征)元組

3. Lambda組件塊

Lambda組件塊可以配置一個lambda函數,執行一些較簡單的操作。示例如下:

blocks {
 name: 'wide_logit'
 inputs {
 feature_group_name: 'wide'
 }
 lambda {
 expression: 'lambda x: tf.reduce_sum(x, axis=1, keepdims=True)'
 }
}

4. KerasLayer組件塊

KerasLayer組件塊是最核心的組件塊,負責加載、執行組件代碼邏輯。

  • class_name是要加載的Keras Layer的類名,支持加載自定義的類和系統內置的Layer類。

  • st_params是以google.protobuf.Struct對象格式配置的參數;

  • 還可以用自定義的protobuf message的格式傳遞參數給加載的Layer對象。

配置示例:

keras_layer {
 class_name: 'MLP'
 mlp {
 hidden_units: [64, 32, 16]
 }
}

keras_layer {
 class_name: 'Dropout'
 st_params {
 fields {
 key: 'rate'
 value: { number_value: 0.5 }
 }
 }
}

5. 循環組件塊

循環組件塊可以實現類似RNN的循環調用結構,可以執行某個Layer多次,每次執行的輸入包含了上一次執行的輸出。在DCN網絡中有循環組件塊的示例,如下:

recurrent {
 num_steps: 3
 fixed_input_index: 0
 keras_layer {
 class_name: 'Cross'
 }
}

上述配置對CrossLayer循環調用了3次,邏輯上等價于執行如下語句:

x1 = Cross()(x0, x0)
x2 = Cross()(x0, x1)
x3 = Cross()(x0, x2)
  • num_steps配置循環執行的次數

  • fixed_input_index配置每次執行的多路輸入組成的列表中固定不變的元素;比如上述示例中的x0

  • keras_layer配置需要執行的組件

6. 重復組件塊

重復組件塊可以使用相同的輸入重復執行某個組件多次,實現multi-head的邏輯。示例如下:

repeat {
 num_repeat: 2
 keras_layer {
 class_name: "MaskBlock"
 mask_block {
 output_size: 512
 aggregation_size: 2048
 input_layer_norm: false
 }
 }
}
  • num_repeat配置重復執行的次數

  • output_concat_axis配置多次執行結果tensors的拼接維度,若不配置則輸出多次執行結果的列表

  • keras_layer配置需要執行的組件

  • input_slice配置每個執行組件的輸入切片,例如[i]獲取輸入列表的第 i 個元素作為第 i 次重復執行時的輸入;不配置時獲取所有輸入

  • input_fn配置每個執行組件的輸入函數,例如input_fn: "lambda x, i: [x[0][i], x[1]]"

重復組件塊的使用案例MaskNet+PPNet+MMoE

7. 序列組件塊

序列組件塊可以依次執行配置的多個Layer,前一個Layer的輸出是后一個Layer的輸入。序列組件塊相對于配置多個首尾相連的普通組件塊要更加簡單。示例如下:

blocks {
 name: 'mlp'
 inputs {
 feature_group_name: 'features'
 }
 layers {
 keras_layer {
 class_name: 'Dense'
 st_params {
 fields {
 key: 'units'
 value: { number_value: 256 }
 }

 fields {
 key: 'activation'
 value: { string_value: 'relu' }
 }
 }
 }
 }
 layers {
 keras_layer {
 class_name: 'Dropout'
 st_params {
 fields {
 key: 'rate'
 value: { number_value: 0.5 }
 }
 }
 }
 }
 layers {
 keras_layer {
 class_name: 'Dense'
 st_params {
 fields {
 key: 'units'
 value: { number_value: 1 }
 }
 }
 }
 }
}

通過組件包實現參數共享的子網絡

組件包封裝了由多個組件塊搭建的一個子網絡DAG,作為整體可以被以參數共享的方式多次調用,通常用在自監督學習模型中。

組件包protobuf消息定義如下:

message BlockPackage {
 // package name
 required string name = 1;
 // a few blocks generating a DAG
 repeated Block blocks = 2;
 // the names of output blocks
 repeated string concat_blocks = 3;
}

組件塊通過package_name參數配置一路輸入來調用組件包

一個使用組件包來實現對比學習的案例如下:

model_config {
 model_class: "RankModel"
 feature_groups {
 group_name: "all"
 feature_names: "adgroup_id"
 feature_names: "user"
 ...
 feature_names: "pid"
 wide_deep: DEEP
 }

 backbone {
 packages {
 name: 'feature_encoder'
 blocks {
 name: "fea_dropout"
 inputs {
 feature_group_name: "all"
 }
 input_layer {
 dropout_rate: 0.5
 only_output_3d_tensor: true
 }
 }
 blocks {
 name: "encode"
 inputs {
 block_name: "fea_dropout"
 }
 layers {
 keras_layer {
 class_name: 'BSTCTR'
 bst {
 hidden_size: 128
 num_attention_heads: 4
 num_hidden_layers: 3
 intermediate_size: 128
 hidden_act: 'gelu'
 max_position_embeddings: 50
 hidden_dropout_prob: 0.1
 attention_probs_dropout_prob: 0
 }
 }
 }
 layers {
 keras_layer {
 class_name: 'Dense'
 st_params {
 fields {
 key: 'units'
 value: { number_value: 128 }
 }
 fields {
 key: 'kernel_initializer'
 value: { string_value: 'zeros' }
 }
 }
 }
 }
 }
 }
 blocks {
 name: "all"
 inputs {
 name: "all"
 }
 input_layer {
 only_output_3d_tensor: true
 }
 }
 blocks {
 name: "loss_ctr"
 merge_inputs_into_list: true
 inputs {
 package_name: 'feature_encoder'
 }
 inputs {
 package_name: 'feature_encoder'
 }
 inputs {
 package_name: 'all'
 }
 keras_layer {
 class_name: 'LOSSCTR'
 st_params{
 fields {
 key: 'cl_weight'
 value: { number_value: 1 }
 }
 fields {
 key: 'au_weight'
 value: { number_value: 0.01 }
 }
 }
 }
 }
 }
 model_params {
 l2_regularization: 1e-5
 }
 embedding_regularization: 1e-5
}

真實案例

在工業級推薦系統中,物品獲得的用戶反饋行為通常遵循長尾分布,少量頭部物品獲得了絕大部分的用戶行為(點擊、收藏、轉化等),剩余的大量中長尾物品獲得的反饋數據卻很少。基于長尾分布的用戶行為日志訓練的推薦模型會越來越偏好頭部物品,在導致“富者越富”的同時傷害中長尾物品的曝光機會和用戶滿意度。

我們在業務效果優化的過程中,觀察到如下現象:

  1. 召回擴量(增加召回數量或新的召回類型)很多時候不能帶來總體大盤指標的提升;

  2. 召回結果過濾掉“精品池”之外的物品通常能夠帶來效果指標的提升;

  3. 添加粗排模型,粗排覆蓋率指標提升,但不一定能帶來總體業務指標的提升;

本質上,越能夠保持“獨立同分布”假設的優化越能夠帶來大盤指標的提升,而越偏離“獨立同分布”假設的優化通常都不能帶來理想的效果。這里的“獨立同分布”假設是指精排模型的訓練集數據和測試集數據(通常是生產環境獲得的截斷后的召回或粗排結果)應遵循同一數據分布。精排模型是在高度傾斜的長尾行為數據上訓練出來的,因而會在頭部物品上產生“過擬合”現象,而在中長尾物品上產生“欠擬合”現象。

  • “精品池”過濾進一步強化了召回的物品滿足行為的長尾分布,匹配精排模型的“獨立同分布”要求,最終也帶來了業務指標的提升;

  • 召回擴量、添加粗排模型是在讓長尾分布變得平滑,試圖增加中長尾物品的數量,偏離了精排模型的“獨立同分布”要求,最終往往無法達成期望的效果提升。

通過分析精排模型的特征重要度,我們發現重要度較高的特征主要集中在少量的“記憶性”特征上,而大量的中長尾特征的重要度都很低。“記憶性”特征指的是沒有泛化能力的特征,如物品ID、用戶對物品ID在過去一段時間上的行為統計,在這些特征上無法學到能夠遷移到其他物品的知識。常規的模型結構會產生特征重要度的長尾分布,最終帶來了模型偏好物品的長尾分布。

基于以上分析,亟需設計一種更加合理的模型結構,讓模型能夠在“記憶”能力之外學習到更多“泛化”能力。Cross Decoupling Network (CDN) 為上述問題提出了一個可行的解決方案,它引入一個基于物品分布的門控機制,讓頭部的物品主要擬合“記憶特征”,中長尾物品主要擬合“泛化特征”。通過加權求和的方式在各個特征上學習到的表征特征,再去擬合最終的業務目標。

我們在一個真實的業務場景設計了如下圖的模型結構,并基于組件化EasyRec輕松搭建了模型。

image.png

該案例的配置請查看文檔:基于組件化EasyRec搭建深度推薦算法模型

組件化EasyRec詳細使用文檔:https://easyrec.readthedocs.io/en/latest/component/backbone.html