基于組件化EasyRec框架快速搭建深度推薦算法模型
有層出不窮的算法idea想要快速驗(yàn)證?核心算法模塊如何快速?gòu)?fù)用到不同場(chǎng)景的不同模型中?如何通過(guò)排列組合構(gòu)建出新的模型?組件化EasyRec框架可以幫助你以“搭積木”的方式快速構(gòu)建想要的模型結(jié)構(gòu),快來(lái)試一試吧!
限制說(shuō)明
僅支持0.8.0或以上版本的組件化的EasyRec框架。
為何需要組件化
1. 靈活搭建模型,所思即所得
依靠動(dòng)態(tài)可插拔的公共組件,以“搭積木”的方式快速構(gòu)建想要的模型結(jié)構(gòu)??蚣芴峁┝?膠水"語(yǔ)法,實(shí)現(xiàn)組件間的無(wú)縫銜接。
2. 實(shí)現(xiàn)組件復(fù)用,一次開(kāi)發(fā)到處可用
很多模型之所以被稱(chēng)之為一個(gè)新的模型,是因?yàn)橐肓艘粋€(gè)或多個(gè)特殊的子模塊(組件),然而這些子模塊并不僅只能用在該模型中,通過(guò)組合各個(gè)不同的子模塊可以輕易組裝一個(gè)新的模型。
過(guò)去一個(gè)新開(kāi)發(fā)的公共可選模塊,比如Dense Feature Embedding Layer
、SENet
添加到現(xiàn)有模型中,需要修改所有模型的代碼才能用上新的特性,過(guò)程繁瑣易出錯(cuò)。隨著模型數(shù)量和公共模塊數(shù)量的增加,為所有模型集成所有公共可選模塊將產(chǎn)生組合爆炸的不可控局面。
組件化實(shí)現(xiàn)了底層公共模塊與上層模型的解耦。
3. 提高實(shí)驗(yàn)迭代效率,好的想法值得快速驗(yàn)證
為已有模型添加新特性將變得十分方便。開(kāi)發(fā)一個(gè)新的模型,只需要實(shí)現(xiàn)特殊的新模塊,其余部分可以通過(guò)組件庫(kù)中的已有組件拼裝。
現(xiàn)在我們只需要為新的特征開(kāi)發(fā)一個(gè)Keras Layer類(lèi),并在指定package中添加import語(yǔ)句,框架就能自動(dòng)識(shí)別并添加到組件庫(kù)中,不需要額外操作。新人不再需要熟悉EasyRec的方方面面就可以為框架添加功能,開(kāi)發(fā)效率大大提高。
組件化的目標(biāo)
不再需要實(shí)現(xiàn)新的模型,只需要實(shí)現(xiàn)新的組件! 模型通過(guò)組裝組件完成。
各個(gè)組件專(zhuān)注自身功能的實(shí)現(xiàn),模塊中代碼高度聚合,只負(fù)責(zé)一項(xiàng)任務(wù),也就是常說(shuō)的單一職責(zé)原則。
主干網(wǎng)絡(luò)
組件化EasyRec模型使用一個(gè)可配置的主干網(wǎng)絡(luò)作為核心部件。主干網(wǎng)絡(luò)是由多個(gè)組件塊
組成的一個(gè)有向無(wú)環(huán)圖(DAG),框架負(fù)責(zé)按照DAG的拓?fù)渑判驁?zhí)行個(gè)組件塊
關(guān)聯(lián)的代碼邏輯,構(gòu)建TF Graph的一個(gè)子圖。DAG的輸出節(jié)點(diǎn)由concat_blocks
配置項(xiàng)定義,各輸出組件塊
的輸出tensor拼接之后輸入給一個(gè)可選的頂部MLP層,或者直接鏈接到最終的預(yù)測(cè)層。
案例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數(shù)據(jù)集效果對(duì)比:
Model | Epoch | AUC |
Wide&Deep | 1 | 0.8558 |
Wide&Deep(Backbone) | 1 | 0.8854 |
備注:通過(guò)組件化的方式搭建的模型效果比內(nèi)置的模型效果更好是因?yàn)?code data-tag="code" code-type="xCode" class="code">MLP組件有更好的初始化方法。
通過(guò)protobuf messagebackbone
來(lái)定義主干網(wǎng)絡(luò),主干網(wǎng)絡(luò)有多個(gè)積木塊(block
)組成,每個(gè)block
代表一個(gè)可復(fù)用的組件。
每個(gè)
block
有一個(gè)唯一的名字(name),并且有一個(gè)或多個(gè)輸入和輸出。每個(gè)輸入只能是某個(gè)
feature group
的name,或者另一個(gè)block
的name,或者是一個(gè)block package
的名字。當(dāng)一個(gè)block
有多個(gè)輸入時(shí),會(huì)自動(dòng)執(zhí)行merge操作(輸入為list時(shí)自動(dòng)合并,輸入為tensor時(shí)自動(dòng)concat)。所有
block
根據(jù)輸入與輸出的關(guān)系組成一個(gè)有向無(wú)環(huán)圖(DAG),框架自動(dòng)解析出DAG的拓?fù)潢P(guān)系,按照拓?fù)渑判驁?zhí)行塊所關(guān)聯(lián)的模塊。當(dāng)
block
有多個(gè)輸出時(shí),返回一個(gè)python元組(tuple),下游block
可以配置input_slice
通過(guò)python切片語(yǔ)法獲取到輸入元組的某個(gè)元素作為輸入,或者通過(guò)自定義的input_fn
配置一個(gè)lambda表達(dá)式函數(shù)獲取元組的某個(gè)值。每個(gè)
block
關(guān)聯(lián)的模塊通常是一個(gè)keras layer對(duì)象,實(shí)現(xiàn)了一個(gè)可復(fù)用的子網(wǎng)絡(luò)模塊??蚣苤С旨虞d自定義的keras layer,以及所有系統(tǒng)內(nèi)置的keras layer。可以為
block
關(guān)聯(lián)一個(gè)input_layer
對(duì)輸入的feature group
配置的特征做一些額外的加工,比如執(zhí)行batch normalization
、layer normalization
、feature dropout
等操作,并且可以指定輸出的tensor的格式(2d、3d、list等)。注意:當(dāng)block
關(guān)聯(lián)的模塊是input_layer
時(shí),必須設(shè)定feature_group_name為某個(gè)feature group
的名字,當(dāng)block
關(guān)聯(lián)的模塊不是input_layer
時(shí),block的name不可與某個(gè)feature group
重名。還有一些特殊的
block
關(guān)聯(lián)了一個(gè)特殊的模塊,包括lambda layer
、sequential layers
、repeated layer
和recurrent layer
。這些特殊layer分別實(shí)現(xiàn)了自定義表達(dá)式、順序執(zhí)行多個(gè)layer、重復(fù)執(zhí)行某個(gè)layer、循環(huán)執(zhí)行某個(gè)layer的功能。DAG的輸出節(jié)點(diǎn)名由
concat_blocks
配置項(xiàng)指定,配置了多個(gè)輸出節(jié)點(diǎn)時(shí)自動(dòng)執(zhí)行tensor的concat操作。如果不配置
concat_blocks
,框架會(huì)自動(dòng)拼接DAG的所有葉子節(jié)點(diǎn)并輸出。可以為主干網(wǎng)絡(luò)配置一個(gè)可選的
MLP
模塊。
案例2:DeepFM 模型
配置文件:deepfm_backbone_on_movielens.config
這個(gè)Case重點(diǎn)關(guān)注下兩個(gè)特殊的block
,一個(gè)使用了lambda
表達(dá)式配置了一個(gè)自定義函數(shù);另一個(gè)的加載了一個(gè)內(nèi)置的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數(shù)據(jù)集效果對(duì)比:
Model | Epoch | AUC |
DeepFM | 1 | 0.8867 |
DeepFM(Backbone) | 1 | 0.8872 |
案例3:DCN 模型
配置文件:dcn_backbone_on_movielens.config
這個(gè)Case重點(diǎn)關(guān)注一個(gè)特殊的 DCNblock
,用了recurrent layer
實(shí)現(xiàn)了循環(huán)調(diào)用某個(gè)模塊多次的效果。通過(guò)該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
}
上述配置對(duì)CrossLayer
循環(huán)調(diào)用了3次,邏輯上等價(jià)于執(zhí)行如下語(yǔ)句:
x1 = Cross()(x0, x0)
x2 = Cross()(x0, x1)
x3 = Cross()(x0, x2)
MovieLens-1M數(shù)據(jù)集效果對(duì)比:
Model | Epoch | AUC |
DCN (內(nèi)置) | 1 | 0.8576 |
DCN_v2 (backbone) | 1 | 0.8770 |
備注:新實(shí)現(xiàn)的Cross
組件對(duì)應(yīng)了參數(shù)量更多的v2版本的DCN,而內(nèi)置的DCN模型對(duì)應(yīng)了v1版本的DCN。
案例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數(shù)據(jù)集效果對(duì)比:
Model | Epoch | AUC |
DLRM | 1 | 0.79785 |
DLRM (backbone) | 1 | 0.7993 |
備注:DotInteraction
是新開(kāi)發(fā)的特征兩兩交叉做內(nèi)積運(yùn)算的模塊。
這個(gè)案例中'dot' block的第一個(gè)輸入是一個(gè)tensor,第二個(gè)輸入是一個(gè)list,這種情況下第一個(gè)輸入會(huì)插入到list中,合并成一個(gè)更大的list,作為block的輸入。
案例5:為 DLRM 模型添加一個(gè)新的數(shù)值特征Embedding組件
配置文件:dlrm_on_criteo_with_periodic.config
與上一個(gè)案例相比,多了一個(gè)PeriodicEmbedding
Layer,組件化編程的靈活性與可擴(kuò)展性由此可見(jiàn)一斑。
重點(diǎn)關(guān)注一下PeriodicEmbedding
Layer的參數(shù)配置方式,這里并沒(méi)有使用自定義protobuf message的傳參方式,而是采用了內(nèi)置的google.protobuf.Struct
對(duì)象作為自定義Layer的參數(shù)。實(shí)際上,該自定義Layer也支持通過(guò)自定義message傳參??蚣芴峁┝艘粋€(gè)通用的Parameter
API 用通用的方式處理兩種傳參方式。
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
}
Criteo數(shù)據(jù)集效果對(duì)比:
Model | Epoch | AUC |
DLRM | 1 | 0.79785 |
DLRM (backbone) | 1 | 0.7993 |
DLRM (periodic) | 1 | 0.7998 |
案例6:使用內(nèi)置的keras layer搭建DNN模型
該案例只為了演示可以組件化EasyRec可以使用TF內(nèi)置的原子粒度keras layer作為通用組件,實(shí)際上我們已經(jīng)有了一個(gè)自定義的MLP組件,使用會(huì)更加方便。
該案例重點(diǎn)關(guān)注一個(gè)特殊的sequential block
,這個(gè)組件塊內(nèi)可以定義多個(gè)串聯(lián)在一起的layers,前一個(gè)layer的輸出作為后一個(gè)layer的輸入。相比定義多個(gè)普通block
的方式,sequential block
會(huì)更加方便。
備注:調(diào)用系統(tǒng)內(nèi)置的keras layer,只能通過(guò)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數(shù)據(jù)集效果:
Model | Epoch | AUC |
MLP | 1 | 0.8616 |
案例7:對(duì)比學(xué)習(xí)(使用組件包)
配置文件:contrastive_learning_on_movielens.config
該案例為了演示block package
的使用,block package
可以打包一組block
,構(gòu)成一個(gè)可被復(fù)用的子網(wǎng)絡(luò),即被打包的子網(wǎng)絡(luò)以共享參數(shù)的方式在同一個(gè)模型中調(diào)用多次。與之相反,沒(méi)有打包的block
是不能被多次調(diào)用的(但是可以多次復(fù)用結(jié)果)。
block package
主要為自監(jiān)督學(xué)習(xí)、對(duì)比學(xué)習(xí)等場(chǎng)景設(shè)計(jì)。
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
是用來(lái)計(jì)算對(duì)比學(xué)習(xí)損失的layer,詳見(jiàn)'組件詳細(xì)參數(shù)'。
額外的input配置:
ignore_input: true 表示忽略當(dāng)前這路的輸入;添加該路輸入只是為了控制拓?fù)浣Y(jié)構(gòu)的執(zhí)行順序
reset_input: 重置本次
package
調(diào)用時(shí)input_layer的配置項(xiàng);可以配置與package
定義時(shí)不同的參數(shù)
注意這個(gè)案例沒(méi)有為名為item_tower
的package配置concat_blocks
,框架會(huì)自動(dòng)設(shè)置為DAG的葉子節(jié)點(diǎn)。
在當(dāng)前案例中,item_tower
被調(diào)用了3次,前2次調(diào)用時(shí)輸入層dropout配置生效,用于計(jì)算對(duì)比學(xué)習(xí)損失函數(shù);最后1次調(diào)用時(shí)重置了輸入層配置,不執(zhí)行dropout。 主模型的item_tower
與對(duì)比學(xué)習(xí)輔助任務(wù)中的item_tower
共享參數(shù);輔助任務(wù)中的item_tower
通過(guò)對(duì)輸入特征embedding做dropout來(lái)生成augmented sample;主模型的item_tower
不執(zhí)行數(shù)據(jù)增強(qiáng)操作。
MovieLens-1M數(shù)據(jù)集效果:
Model | Epoch | AUC |
MultiTower | 1 | 0.8814 |
ContrastiveLearning | 1 | 0.8728 |
一個(gè)更復(fù)雜一點(diǎn)的對(duì)比學(xué)習(xí)模型案例:CL4SRec
案例8:多目標(biāo)模型 MMoE
多目標(biāo)模型的model_class一般配置為"MultiTaskModel",并且需要在model_params
里配置多個(gè)目標(biāo)對(duì)應(yīng)的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
}
注意這個(gè)案例沒(méi)有為backbone配置concat_blocks
,框架會(huì)自動(dòng)設(shè)置為DAG的葉子節(jié)點(diǎn)。
案例9:多目標(biāo)模型 DBMTL
多目標(biāo)模型的model_class一般配置為"MultiTaskModel",并且需要在model_params
里配置多個(gè)目標(biāo)對(duì)應(yīng)的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
里為每個(gè)子任務(wù)的Tower配置relation_dnn
,同時(shí)還需要通relation_tower_names
配置任務(wù)間的依賴(lài)關(guān)系。
這個(gè)案例同樣沒(méi)有為backbone配置concat_blocks
,框架會(huì)自動(dòng)設(shè)置為DAG的葉子節(jié)點(diǎn)。
案例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
}
}
}
該案例體現(xiàn)了如何應(yīng)用重復(fù)組件塊。
更多案例
兩個(gè)新的模型:
FiBiNet模型配置文件:fibinet_on_movielens.config
MaskNet模型配置文件:masknet_on_movielens.config
MovieLens-1M數(shù)據(jù)集效果:
Model | Epoch | AUC |
MaskNet | 1 | 0.8872 |
FibiNet | 1 | 0.8893 |
序列模型:
DIN模型配置文件:DIN_backbone.config
BST模型配置文件:BST_backbone.config
CL4SRec模型:CL4SRec
其他模型:
Highway Network:Highway Network
Cross Decoupling Network:CDN
DLRM+SENet:dlrm_senet_on_criteo.config
組件庫(kù)介紹
1.基礎(chǔ)組件
類(lèi)名 | 功能 | 說(shuō)明 | 示例 |
MLP | 多層感知機(jī) | 可定制激活函數(shù)、initializer、Dropout、BN等 | |
Highway | 類(lèi)似殘差鏈接 | 可用來(lái)對(duì)預(yù)訓(xùn)練embedding做增量微調(diào) | |
Gate | 門(mén)控 | 多個(gè)輸入的加權(quán)求和 | |
PeriodicEmbedding | 周期激活函數(shù) | 數(shù)值特征Embedding | |
AutoDisEmbedding | 自動(dòng)離散化 | 數(shù)值特征Embedding |
備注:Gate組件的第一個(gè)輸入是權(quán)重向量,后面的輸入拼湊成一個(gè)列表,權(quán)重向量的長(zhǎng)度應(yīng)等于列表的長(zhǎng)度
2.特征交叉組件
類(lèi)名 | 功能 | 說(shuō)明 | 示例 |
FM | 二階交叉 | DeepFM模型的組件 | |
DotInteraction | 二階內(nèi)積交叉 | DLRM模型的組件 | |
Cross | bit-wise交叉 | DCN v2模型的組件 | |
BiLinear | 雙線(xiàn)性 | FiBiNet模型的組件 | |
FiBiNet | SENet & BiLinear | FiBiNet模型 |
3.特征重要度學(xué)習(xí)組件
類(lèi)名 | 功能 | 說(shuō)明 | 示例 |
SENet | 建模特征重要度 | FiBiNet模型的組件 | |
MaskBlock | 建模特征重要度 | MaskNet模型的組件 | |
MaskNet | 多個(gè)串行或并行的MaskBlock | MaskNet模型 | |
PPNet | 參數(shù)個(gè)性化網(wǎng)絡(luò) | PPNet模型 |
4. 序列特征編碼組件
類(lèi)名 | 功能 | 說(shuō)明 | 示例 |
DIN | target attention | DIN模型的組件 | |
BST | transformer | BST模型的組件 | |
SeqAugment | 序列數(shù)據(jù)增強(qiáng) | crop, mask, reorder |
5. 多目標(biāo)學(xué)習(xí)組件
類(lèi)名 | 功能 | 說(shuō)明 | 示例 |
MMoE | Multiple Mixture of Experts | MMoE模型的組件 |
6. 輔助損失函數(shù)組件
類(lèi)名 | 功能 | 說(shuō)明 | 示例 |
AuxiliaryLoss | 用來(lái)計(jì)算輔助損失函數(shù) | 常用在自監(jiān)督學(xué)習(xí)中 |
各組件的詳細(xì)參數(shù)請(qǐng)查看“組件詳細(xì)參數(shù)”。
如何自定義組件
在easy_rec/python/layers/keras
目錄下新建一個(gè)py
文件,也可直接添加到一個(gè)已有的文件中。我們建議目標(biāo)類(lèi)似的組件定義在同一個(gè)文件中,減少文件數(shù)量;比如特征交叉的組件都放在interaction.py
里。
定義一個(gè)繼承tf.keras.layers.Layer
的組件類(lèi),至少實(shí)現(xiàn)兩個(gè)方法:__init__
、call
。
def __init__(self, params, name='xxx', reuse=None, **kwargs):
pass
def call(self, inputs, training=None, **kwargs):
pass
__init__
方法的第一個(gè)參數(shù)params
接收框架傳遞給當(dāng)前組件的參數(shù)。支持兩種參數(shù)配置的方式:google.protobuf.Struct
、自定義的protobuf message對(duì)象。params對(duì)象封裝了對(duì)這兩種格式的參數(shù)的統(tǒng)一讀取接口,如下:
檢查必傳參數(shù),缺失時(shí)報(bào)錯(cuò)退出:
params.check_required(['embedding_dim', 'sigma'])
用點(diǎn)操作符讀取參數(shù):
sigma = params.sigma
;支持連續(xù)點(diǎn)操作符,如params.a.b
:注意數(shù)值型參數(shù)的類(lèi)型,
Struct
只支持float類(lèi)型,整型需要強(qiáng)制轉(zhuǎn)換:embedding_dim = int(params.embedding_dim)
數(shù)組類(lèi)型也需要強(qiáng)制類(lèi)型轉(zhuǎn)換:
units = list(params.hidden_units)
指定默認(rèn)值讀取,返回值會(huì)被強(qiáng)制轉(zhuǎn)換為與默認(rèn)值同類(lèi)型:
activation = params.get_or_default('activation', 'relu')
支持嵌套子結(jié)構(gòu)的默認(rèn)值讀?。?code data-tag="code" code-type="xCode" class="code">params.field.get_or_default('key', def_val)
判斷某個(gè)參數(shù)是否存在:
params.has_field(key)
【不建議,會(huì)限定傳參方式】獲取自定義的proto對(duì)象:
params.get_pb_config()
讀寫(xiě)
l2_regularizer
屬性:params.l2_regularizer
,傳給Dense層或dense函數(shù)。
【可選】如需要自定義protobuf message參數(shù),先在easy_rec/python/protos/layer.proto
添加參數(shù)message的定義, 再把參數(shù)注冊(cè)到定義在easy_rec/python/protos/keras_layer.proto
的KerasLayer.params
消息體中。
__init__
方法的reuse
參數(shù)表示該Layer對(duì)象的權(quán)重參數(shù)是否需要被復(fù)用。開(kāi)發(fā)時(shí)需要按照可復(fù)用的邏輯來(lái)實(shí)現(xiàn)Layer對(duì)象,推薦嚴(yán)格按照keras layer的規(guī)范來(lái)實(shí)現(xiàn)。 盡量在__init__
方法中聲明需要依賴(lài)的keras layer對(duì)象;僅在必要時(shí)才使用tf.layers.*
函數(shù),且需要傳遞reuse參數(shù)。
提示:實(shí)現(xiàn)Layer對(duì)象時(shí)盡量使用原生的 tf.keras.layers.* 對(duì)象,且全部在 __init__ 方法中預(yù)先聲明好。
call
方法用來(lái)實(shí)現(xiàn)主要的模塊邏輯,其inputs
參數(shù)可以是一個(gè)tenor,或者是一個(gè)tensor列表??蛇x的training
參數(shù)用來(lái)標(biāo)識(shí)當(dāng)前是否是訓(xùn)練模型。
最后也是最重要的一點(diǎn),新開(kāi)發(fā)的Layer需要在easy_rec.python.layers.keras.__init__.py
文件中導(dǎo)出才能被框架識(shí)別為組件庫(kù)中的一員。例如要導(dǎo)出blocks.py
文件中的MLP
類(lèi),則需要添加: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
如何搭建模型
組件塊
和組件包
是搭建主干網(wǎng)絡(luò)的核心部件,本小節(jié)將會(huì)介紹組件塊
的類(lèi)型、功能和配置參數(shù);同時(shí)還會(huì)介紹專(zhuān)門(mén)為參數(shù)共享子網(wǎng)絡(luò)設(shè)計(jì)的組件包
。
通過(guò)組件塊
和組件包
搭建模型的配置方法請(qǐng)參考上文描述的各個(gè)案例。
組件塊
的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;
}
}
組件塊
會(huì)自動(dòng)合并多個(gè)輸入:
若多路輸入中某一路的輸入類(lèi)型是
list
,則最終結(jié)果被Merge成一個(gè)大的list,保持順序不變;若多路輸入中的每一路輸入都是tensor,默認(rèn)是執(zhí)行輸入tensors按照最后一個(gè)維度做拼接(concat),以下配置項(xiàng)可以改變默認(rèn)行為:
input_concat_axis
用來(lái)指定輸入tensors拼接的維度merge_inputs_into_list
設(shè)為true,則把輸入合并到一個(gè)列表里,不做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;
}
每一路輸入可以配置一個(gè)可選的
input_fn
,指定一個(gè)lambda函數(shù)對(duì)輸入做一些簡(jiǎn)單的變換。比如配置input_fn: 'lambda x: [x]'
可以把輸入變成列表格式。input_slice
可以用來(lái)獲取輸入元組/列表的某個(gè)切片。比如,當(dāng)某路輸入是一個(gè)列表對(duì)象是,可以用input_slice: '[1]'
配置項(xiàng)獲取列表的第二個(gè)元素值作為這一路的輸入。extra_input_fn
是一個(gè)可選的配置項(xiàng),用來(lái)對(duì)合并后的多路輸入結(jié)果做一些額外的變換,需要配置成lambda函數(shù)的格式。
目前總共有7種類(lèi)型的組件塊
,分別是空組件塊
、輸入組件塊
、Lambda組件塊
、KerasLayer組件塊
、循環(huán)組件塊
、重復(fù)組件塊
、序列組件塊
。
1. 空組件塊
當(dāng)一個(gè)block
不配置任何layer時(shí)就稱(chēng)之為空組件塊
,空組件塊
只執(zhí)行多路輸入的Merge操作。
2. 輸入組件塊
輸入組件塊
關(guān)聯(lián)一個(gè)input_layer
,獲取、加工并返回原始的特征輸入。
輸入組件塊
比較特殊,它只能有且只有一路輸入,并且只能用feature_group_name
項(xiàng)配置輸入為一個(gè)feature_group
的name
。
輸入組件塊
有一個(gè)特權(quán):它的名字可以與其輸入的feature_group
同名。其他組件塊
則無(wú)此殊榮。
配置示例:
blocks {
name: 'all'
inputs {
feature_group_name: 'all'
}
input_layer {
only_output_feature_list: true
}
}
InputLayer可以通過(guò)配置獲取不同格式的輸入,并且可以執(zhí)行一些如dropout
之類(lèi)的額外操作,其參數(shù)定義的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;
}
輸入層的定義如上,配置下說(shuō)明如下:
do_batch_norm
是否對(duì)輸入特征做batch normalization
do_layer_norm
是否對(duì)輸入特征做layer normalization
dropout_rate
輸入層執(zhí)行dropout的概率,默認(rèn)不執(zhí)行dropoutfeature_dropout_rate
對(duì)特征整體執(zhí)行dropout的概率,默認(rèn)不執(zhí)行only_output_feature_list
輸出list格式的各個(gè)特征only_output_3d_tensor
輸出feature group
對(duì)應(yīng)的一個(gè)3d tensor,在embedding_dim
相同時(shí)可配置該項(xiàng)output_2d_tensor_and_feature_list
是否同時(shí)輸出2d tensor與特征listoutput_seq_and_normal_feature
是否輸出(sequence特征, 常規(guī)特征)元組
3. Lambda組件塊
Lambda組件塊
可以配置一個(gè)lambda函數(shù),執(zhí)行一些較簡(jiǎn)單的操作。示例如下:
blocks {
name: 'wide_logit'
inputs {
feature_group_name: 'wide'
}
lambda {
expression: 'lambda x: tf.reduce_sum(x, axis=1, keepdims=True)'
}
}
4. KerasLayer組件塊
KerasLayer組件塊
是最核心的組件塊,負(fù)責(zé)加載、執(zhí)行組件代碼邏輯。
class_name
是要加載的Keras Layer的類(lèi)名,支持加載自定義的類(lèi)和系統(tǒng)內(nèi)置的Layer類(lèi)。st_params
是以google.protobuf.Struct
對(duì)象格式配置的參數(shù);還可以用自定義的protobuf message的格式傳遞參數(shù)給加載的Layer對(duì)象。
配置示例:
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. 循環(huán)組件塊
循環(huán)組件塊
可以實(shí)現(xiàn)類(lèi)似RNN的循環(huán)調(diào)用結(jié)構(gòu),可以執(zhí)行某個(gè)Layer多次,每次執(zhí)行的輸入包含了上一次執(zhí)行的輸出。在DCN網(wǎng)絡(luò)中有循環(huán)組件塊的示例,如下:
recurrent {
num_steps: 3
fixed_input_index: 0
keras_layer {
class_name: 'Cross'
}
}
上述配置對(duì)Cross
Layer循環(huán)調(diào)用了3次,邏輯上等價(jià)于執(zhí)行如下語(yǔ)句:
x1 = Cross()(x0, x0)
x2 = Cross()(x0, x1)
x3 = Cross()(x0, x2)
num_steps
配置循環(huán)執(zhí)行的次數(shù)fixed_input_index
配置每次執(zhí)行的多路輸入組成的列表中固定不變的元素;比如上述示例中的x0
keras_layer
配置需要執(zhí)行的組件
6. 重復(fù)組件塊
重復(fù)組件塊
可以使用相同的輸入重復(fù)執(zhí)行某個(gè)組件多次,實(shí)現(xiàn)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
配置重復(fù)執(zhí)行的次數(shù)output_concat_axis
配置多次執(zhí)行結(jié)果tensors的拼接維度,若不配置則輸出多次執(zhí)行結(jié)果的列表keras_layer
配置需要執(zhí)行的組件input_slice
配置每個(gè)執(zhí)行組件的輸入切片,例如[i]
獲取輸入列表的第 i 個(gè)元素作為第 i 次重復(fù)執(zhí)行時(shí)的輸入;不配置時(shí)獲取所有輸入input_fn
配置每個(gè)執(zhí)行組件的輸入函數(shù),例如input_fn: "lambda x, i: [x[0][i], x[1]]"
重復(fù)組件塊
的使用案例MaskNet+PPNet+MMoE。
7. 序列組件塊
序列組件塊
可以依次執(zhí)行配置的多個(gè)Layer,前一個(gè)Layer的輸出是后一個(gè)Layer的輸入。序列組件塊
相對(duì)于配置多個(gè)首尾相連的普通組件塊要更加簡(jiǎn)單。示例如下:
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 }
}
}
}
}
}
通過(guò)組件包實(shí)現(xiàn)參數(shù)共享的子網(wǎng)絡(luò)
組件包
封裝了由多個(gè)組件塊
搭建的一個(gè)子網(wǎng)絡(luò)DAG,作為整體可以被以參數(shù)共享的方式多次調(diào)用,通常用在自監(jiān)督學(xué)習(xí)模型中。
組件包
的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;
}
組件塊
通過(guò)package_name
參數(shù)配置一路輸入來(lái)調(diào)用組件包
。
一個(gè)使用組件包
來(lái)實(shí)現(xiàn)對(duì)比學(xué)習(xí)的案例如下:
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
}
真實(shí)案例
在工業(yè)級(jí)推薦系統(tǒng)中,物品獲得的用戶(hù)反饋行為通常遵循長(zhǎng)尾分布,少量頭部物品獲得了絕大部分的用戶(hù)行為(點(diǎn)擊、收藏、轉(zhuǎn)化等),剩余的大量中長(zhǎng)尾物品獲得的反饋數(shù)據(jù)卻很少。基于長(zhǎng)尾分布的用戶(hù)行為日志訓(xùn)練的推薦模型會(huì)越來(lái)越偏好頭部物品,在導(dǎo)致“富者越富”的同時(shí)傷害中長(zhǎng)尾物品的曝光機(jī)會(huì)和用戶(hù)滿(mǎn)意度。
我們?cè)跇I(yè)務(wù)效果優(yōu)化的過(guò)程中,觀(guān)察到如下現(xiàn)象:
召回?cái)U(kuò)量(增加召回?cái)?shù)量或新的召回類(lèi)型)很多時(shí)候不能帶來(lái)總體大盤(pán)指標(biāo)的提升;
召回結(jié)果過(guò)濾掉“精品池”之外的物品通常能夠帶來(lái)效果指標(biāo)的提升;
添加粗排模型,粗排覆蓋率指標(biāo)提升,但不一定能帶來(lái)總體業(yè)務(wù)指標(biāo)的提升;
本質(zhì)上,越能夠保持“獨(dú)立同分布”假設(shè)的優(yōu)化越能夠帶來(lái)大盤(pán)指標(biāo)的提升,而越偏離“獨(dú)立同分布”假設(shè)的優(yōu)化通常都不能帶來(lái)理想的效果。這里的“獨(dú)立同分布”假設(shè)是指精排模型的訓(xùn)練集數(shù)據(jù)和測(cè)試集數(shù)據(jù)(通常是生產(chǎn)環(huán)境獲得的截?cái)嗪蟮恼倩鼗虼峙沤Y(jié)果)應(yīng)遵循同一數(shù)據(jù)分布。精排模型是在高度傾斜的長(zhǎng)尾行為數(shù)據(jù)上訓(xùn)練出來(lái)的,因而會(huì)在頭部物品上產(chǎn)生“過(guò)擬合”現(xiàn)象,而在中長(zhǎng)尾物品上產(chǎn)生“欠擬合”現(xiàn)象。
“精品池”過(guò)濾進(jìn)一步強(qiáng)化了召回的物品滿(mǎn)足行為的長(zhǎng)尾分布,匹配精排模型的“獨(dú)立同分布”要求,最終也帶來(lái)了業(yè)務(wù)指標(biāo)的提升;
召回?cái)U(kuò)量、添加粗排模型是在讓長(zhǎng)尾分布變得平滑,試圖增加中長(zhǎng)尾物品的數(shù)量,偏離了精排模型的“獨(dú)立同分布”要求,最終往往無(wú)法達(dá)成期望的效果提升。
通過(guò)分析精排模型的特征重要度,我們發(fā)現(xiàn)重要度較高的特征主要集中在少量的“記憶性”特征上,而大量的中長(zhǎng)尾特征的重要度都很低?!坝洃浶浴碧卣髦傅氖菦](méi)有泛化能力的特征,如物品ID、用戶(hù)對(duì)物品ID在過(guò)去一段時(shí)間上的行為統(tǒng)計(jì),在這些特征上無(wú)法學(xué)到能夠遷移到其他物品的知識(shí)。常規(guī)的模型結(jié)構(gòu)會(huì)產(chǎn)生特征重要度的長(zhǎng)尾分布,最終帶來(lái)了模型偏好物品的長(zhǎng)尾分布。
基于以上分析,亟需設(shè)計(jì)一種更加合理的模型結(jié)構(gòu),讓模型能夠在“記憶”能力之外學(xué)習(xí)到更多“泛化”能力。Cross Decoupling Network (CDN) 為上述問(wèn)題提出了一個(gè)可行的解決方案,它引入一個(gè)基于物品分布的門(mén)控機(jī)制,讓頭部的物品主要擬合“記憶特征”,中長(zhǎng)尾物品主要擬合“泛化特征”。通過(guò)加權(quán)求和的方式在各個(gè)特征上學(xué)習(xí)到的表征特征,再去擬合最終的業(yè)務(wù)目標(biāo)。
我們?cè)谝粋€(gè)真實(shí)的業(yè)務(wù)場(chǎng)景設(shè)計(jì)了如下圖的模型結(jié)構(gòu),并基于組件化EasyRec輕松搭建了模型。
該案例的配置請(qǐng)查看文檔:基于組件化EasyRec搭建深度推薦算法模型
組件化EasyRec詳細(xì)使用文檔:https://easyrec.readthedocs.io/en/latest/component/backbone.html