封装主流排序/多目标模型,配置化的训练方式,统一的 serving 接口。
- 简单易用,用户只需要提供几个配置文件即可使用,包括特征处理、模型选择、超参调节等等
- 模型众多,几乎包含所有主流排序模型,简单易用,支持灵活组装各种模型
- 端到端的 Ensemble 模型训练
- 线上、线下特征一致,这是因为特征处理逻辑在模型中完成
- 训练数据为原始特征格式,特征处理逻辑在模型中完成,用户通过配置文件进行配置即可
- 导出模型的 serving 接口统一
- 基于 Tensorflow Estimator 接口实现
- 支持多 GPU 训练
- 支持训练时加载 lua 脚本自定义样本 label 和 weight
- LR
- DNN
- Wide&Deep
- DSSM
- FM
- FwFM
- AFM
- IAFM
- KFM
- NIFM
- NFM
- CCPM
- DeepFM
- xDeepFM
- DCN
- IPNN
- KPNN
- PIN
- FGCNN
- FiBiNET
- AutoInt
- DeepX (model slots 模式)
输入数据包含 3 种:
- 特征描述文件;
- 训练样本;
- 特征配置文件;
TSV 格式(tab分隔, 需要包含 header),描述特征信息。
格式为:
特征id 特征名 特征数据类型 特征类别
特征数据有如下几类:
- int
- float
- string
- string_list
- weighted_string_list
特征类别有如下几类:
- user
- item
- ctx
- extra
- target
Note: 特征ID小于100的是保留 ID, 用于 label 和 weight 的定义。
示例:
id name data_type field_type
101 u_omgid string user
102 u_devtype string user
103 u_sex string user
104 u_age int user
105 u_city string user
230 i_kd_rowkey string item
234 i_kd_cid1 string item
235 i_kd_cid2 string item
236 i_kd_cid3 string item
完整版可参考这里
格式(tab 分隔):
特征ID|特征值 特征ID|特征值 ...
示例:
1|0 2|1.0 335|6045dd66693957ah,0735de71818377bk,1775de1ee54475aw,0375e033f88380bk,1645dce0956051bv,7035dfafb24649aw 337|7625d1f3a7a736bk
其中:特征ID 1 表示 label, 2 表示对应的权重。
完整版参考这里.
配置文件使用 python 脚本生成,使用时将 tools/conf_generator/conf_generator.py 文件拷贝到工作目录. 参考 data/conf.py 文件.
先创建特征选择类:
assembler = Assembler()
通过 Assembler
的 add_*
方法添加特征, 共有 7 种特征类型:
- int
- float
- string
- string_list 变长,逗号分隔
- float_list 变长,逗号分隔
- weighted_string_list 变长,逗号分隔
- weighted_video_play_list 变长,逗号分隔,定制化格式,用户视频播放列表
def add_int(self, iname, default_value, force_add=False, alias=None)
# force_add: 与 transform 解耦,为 True 时,即使 transform 没用到,也会添加到 json 配置中
# alias: 特征别名,transform 中使用别名,为 None 则默认为 iname,当需要对同一个
# 特征做两种不同的处理时,可以用这个功能,例如对 ctr 特征不同的归一化方式
def add_float(self, iname, default_value, force_add=False, alias=None)
def add_string(self, iname, dict_file='', min_count=1, oov_buckets=None,
top_k=None, default_key=None, force_add=False, alias=None):
# min_count 表示字符串的最小出现次数,小于这个阈值的丢弃或者 hash (当 oov_buckets > 0 时)
# oov_buckets > 0 时,小于 min_count 的字符串 hash 进大小为 oov_buckets 的桶内;
# top_k 为最大词典数, 默认不限制
# default_key 为缺失情况的默认值,类型是 string, 有一个特殊的 key '_extra_', 表示缺失值分配一个特殊 ID
例如 tag_list 这种变长特征,逗号分隔
def add_string_list(self, iname, dict_file='', min_count=1, width=None,
top_k=None, oov_buckets=None, default_key=None,
scan_from='head', reverse=False,
force_add=False, alias=None):
# width 表示长度, 不足则用 -1, top_k 为最大词典数, 默认不限制
# default_key 为缺失情况的默认值,类型是 string, 有一个特殊的 key '\_extra\_', 表示缺失值分配一个特殊 ID
# scan_from: 'head' 或 'tail', 'head' 表示从前往后扫描,'tail' 表示从后往前添加元素
# reverse: 是否翻转,例如: 1,2,3,4,5,6, width 为 3, 若 scan_from='head', reverse=True,
# 则输出为 3,2,1; 若 scan_from='tail', reverse=False, 则输出为 6,5,4, reverse=True, 输出为 4,5,6
def add_float_list(self, iname, default_value, width, scan_from='head',
reverse=False, force_add=False, alias=None)
# width 表示长度, 不足则用 default_value
带权重的 string list 特征,例如画像特征,tag 会带有权重
def add_weighted_string_list(self, iname, dict_file='', min_count=1,
width=None, top_k=None, min_weight=0.0,
oov_buckets=None, default_key=None,
scan_from='head', reverse=False,
force_add=False, alias=None):
# width 表示长度,不足默认 -1,top_k 表示最大词典数,min_weight 为最小权重值
# min_weight 表示最小权重,小于该值的key将会被过滤掉
# default_key 为缺失情况的默认值,类型是 string, 有一个特殊的 key '\_extra\_', 表示缺失值分配一个特殊 ID
用户视频播放历史列表,格式为 VideoID:playtime:videotime:algoid:playcnt:timestamp 返回正负播放历史和权重
def add_weighted_video_play_list(self, iname, filter_type, params=None,
dict_file='', min_count=1,
width_pos=None, width_neg=None, top_k=None,
oov_buckets=None, default_key=None,
scan_from='tail', reverse=False,
force_add=False, alias=None)
# filter_type: 用户定义的过滤函数
# params: 过滤函数对应的参数,类型为 Python dict
# width_pos: 正浏览历史长度
# width_neg: 负浏览历史长度
先创建特征转换类:
transform = Transform()
共有 10 种特征转换方式:
- numeric: 连续特征处理,支持 log 转换, 归一化;
- bucketized: 连续特征分桶;
- embedding: 离散特征 embedding;
- shared_embedding: 多个特征共享 embedding
- pretrained_embedding: 加载预训练 embedding;
- indicator: one-hot or multi-hot;
- categorical_identity: int 型特征离散处理;
- cross: 特征交叉;
- weighted_categorical: 带权重的离散特征处理;
通过对应的 Transform.add_*
方法添加特征转换.
TODO - 支持更多归一化方法
def add_numeric(self, iname, places=[],
normalizer_fn=None, substract=0, denominator=1.0, exp=1.0,
oname=None)
# normalizer_fn 可以为 'log-norm', 'norm', 表示归一化方式, substract, denominator 表示归一化参数, exp 为指数项;
# normalizer_fn 为 'log-norm' 时, 转换公式为 ((log(x+1.0) - substract) / denominator) ^ exp;
# normalizer_fn 为 'norm' 时, 转换公式为 ((x - substract) / denominator) ^ exp;
# 输入是原始 int/float 类型的特征名;
# 输出是 numeric_column;
# 可以放在 wide, deep, 不可以作为 cross_column 输入;
# oname 默认为 iname + '.numeric'
def add_bucketized(self, iname, places, boundaries, oname=None)
# boundaries 为数值数组, 代表分桶边界, Buckets include the left boundary, and
# exclude the right boundary. Namely, boundaries=[0., 1., 2.] generates buckets
# (-inf, 0.), [0., 1.), [1., 2.), and [2., +inf).
# 输入可以是原始 int/float 类型的特征名, 也可以是 numeric_column
# 返回的是 bucketized_column
# 可以放在 wide, deep, 也可以作为 cross_column 的输入
# oname 默认为 iname + '.bucketized'
def add_embedding(self, iname, places, dimension, combiner='mean', oname=None)
# dimension 为 embedding 维度
# combiner: 'mean', 'sqrtn', 'sum'
# 可以放在 deep
# 输入可以是 categorical_*_column, 也可以是原始 string/string_list 类型, string_list类型时使用 'mean' combiner
# oname 默认为 iname + 'embedding'
def add_shared_embedding(self, inames, places, dimension, combiner='mean', oname=None)
# 多种特征共享 embedding;
# inames 为数组
# oname 默认为 '#'.join(inames) + '.embedding'
# places 为 list, 其元素个数与 inames 一致,元素类型也为 list,表示每个特征的位置
# combiner: 'mean', 'sqrtn', 'sum'
transform.add_shared_embedding(['i_kd_cid1.identity', 'i_kd_cid2.identity'],
[['dssm1', 'deep'], ['dssm2', 'deep']], 20)
def add_pretrained_embedding(self, iname, places, dimension, trainable,
pretrained_embedding_file, combiner='mean', oname=None)
# trainable 是否训练 embedding
# pretrained_embedding_file 预训练 embedding 文件, 格式为:
# 第一行包含头部: total, dimension
# 之后每行: item_id <num> <num> ...
# oname 默认为 iname + '.pretrained_embedding'
# combiner: 'mean', 'sqrtn', 'sum'
def add_indicator(self, iname, places, oname=None)
# 可以直接用于 string_, string__list 类型的特征, int/float 类型的特征需要先用 identity 转换一下
transform.add_indicator('user.city, 'user.city.indicator', ['wide'])
# user.gender 是 int 类型的特征, 用 add_categorical_indentity 转换一下
transform.add_categorical_identity('user.gender', 'user.gender.identity', [], 3)
transform.add_indicator('user.gender.identity', 'user.gender.indicator', ['wide', 'deep'])
# oname 默认为 iname + '.indicator'
def add_categorical_identity(self, iname, places, num_buckets=-1, oname=None)
# num_buckets 代表离散化的类别数, string/string_list 可以用 -1, 代表使用词典大小, int/float 类型则必须指定大小
# 输入是原始 int/float/string/string_list 的特征
# oname 默认 iname + '.identity'
def add_cross(self, inames, hash_bucket_size, oname=None)
# 此时 inames 为特征名数组, 代表参与交叉的特征, hash_bucket_size 为 hash 空间大小
# oname 默认为 '#'.join(inames) + '.cross'
def add_weighted_categorical(self, inames, places, oname=None)
# inames 包含两个元素,第 1 个为 categorical column 特征名, 第 2 个为权重列,为原始字符串
# oname 默认为 inames[0] + '.weighted'
运行环境:
- python 2.7.x
- tensorflow 1.14.0
因为需要编译自定义 op, 所以需要源码编译 tensorflow。
serving 时需要用到自定义 op,所以需要重新编译 tesorflow serving,将自定义 op 编译进最后的二进制中。
编译 tensorflow op:
$ mkdir build
$ cd build
$ cmake ..
$ make
执行 oh_my_go.sh
脚本:
$ ./oh_my_go.sh