wide & deep模型是Google在2016年发布的一类用于分类和回归的模型。该模型应用到了Google Play的应用推荐中,有效的增加了Google Play的软件安装量。目前wide & deep模型已经开源,并且在TensorFlow上提供了高级API。
wide & deep模型旨在使得训练得到的模型能够同时兼顾记忆(Memorization)与泛化(Generalization)能力:
这篇论文的主要贡献分为以下三点:
其使用到的特征包括:
wide部分对应上图中的左侧部分,通常是一个广义线性模型即LR:。
特征集合包含的是原始输入和他们对应的特征转换,其中一个比较重要的转换是:cross-product transformation特征交叉(特征交叉前需要各个特征进行one-hot),其对应的公式如下:
属于0,1
上边的公式实现的其实就是one-hot编码,比如当gender=female,language=en时为1,其他为0。
deep部分对应上图中的右侧部分,是一个前馈神经网络。对于分类特征,原始输入是字符串(比如language=en)。这些稀疏、高维的分类特征第一步是转化为低维、密集的向量。这些向量通常在10-100维之间,一般采用随机的方法进行初始化,在训练过过程中通过最小化损失函数来优化模型。这些低维的向量传到神经网络的隐层中去。每个隐层的计算方式如下:
其中:
wide部分和deep部分使用输出结果的对数几率加权和作为预测值,然后将其输入到一个逻辑回归函数用来联合训练。论文中强调了联合训练(Join training)和整体训练(ensemble)的区别。
wide & deep的join training采用的是下批量梯度下降算法(min-batch stochastic optimization)进行优化的。在实验中,wide部分采用的是Follow-the-regularized-leader(FTRL)+L1,deep部分采用的是Adga。
L1 FTRL会让Wide部分的大部分权重都为0,我们准备特征的时候就不用准备那么多0权重的特征了,这大大压缩了模型权重,也压缩了特征向量的维度。
Deep部分的输入,要么是Age,#App Installs这些数值类特征,要么是已经降维并稠密化的Embedding向量,工程师们不会也不敢把过度稀疏的特征向量直接输入到Deep网络中。所以Deep部分不存在严重的特征稀疏问题,自然可以使用精度更好,更适用于深度学习训练的AdaGrad去训练。
对于LR,模型的预测结果如下:
其中:
这个是原论文中的架构图,我们自己在实践的时候不一定完全遵守。比如架构图中Wide部分只是使用了交叉特征,我们在使用的时候可以把原始的离散特征或者打散后的连续特征加过来。
论文中提到了一个注意点:如果每一次都重新训练的话,将会花费大量的时间和精力,为了解决这个问题,采取的方案是热启动,即每次新产生训练数据的时候,从之前的模型中读取embedding和线性模型的权重来初始化新模型,在接入实时流之前使用之前的模型进行校验,保证不出问题。
有些时候对于用户或者待推荐的物体会有Text和Image,为了增加效果,可能会使用到多模态特征。
Text 和 Image 的 embedding 向量,采用 和Wide模型一样的方式加入到整体模型。
几个简单的思路。
train_data = "./../data/adult/adult.train"
test_data = "./../data/adult/adult.test"
train = pd.read_csv(train_data, sep=",", names=["age", "workclass", "fnlwgt", "education", "education_num", "marital_status", "occupation", "relationship", "race","sex", "capital_gain", "capital_loss", "hours_per_week", "native_country", "label"])
print(train.head(5))
age workclass fnlwgt ... hours_per_week native_country label
0 39 State-gov 77516 ... 40 United-States <=50K
1 50 Self-emp-not-inc 83311 ... 13 United-States <=50K
2 38 Private 215646 ... 40 United-States <=50K
3 53 Private 234721 ... 40 United-States <=50K
4 28 Private 338409 ... 40 Cuba <=50K
定义基本特征、连续特征和dnn使用的特征:
# 定义基本连续的特征,linear 和 dnn 都会使用到
age = tf.feature_column.numeric_column("age")
education_num = tf.feature_column.numeric_column("education_num")
capital_gain = tf.feature_column.numeric_column("capital_gain")
capital_loss = tf.feature_column.numeric_column("capital_loss")
hours_per_week = tf.feature_column.numeric_column("hours_per_week")
# 定义离散特征
workclass = tf.feature_column.categorical_column_with_vocabulary_list(
key="workclass",
vocabulary_list=["Private", "Self-emp-not-inc", "Self-emp-inc", "Federal-gov", "Local-gov", "State-gov",
"Without-pay", "Never-worked", "?"]
)
education = tf.feature_column.categorical_column_with_vocabulary_list(
key="education",
vocabulary_list=["Bachelors", "Some-college", "11th", "HS-grad", "Prof-school", "Assoc-acdm", "Assoc-voc", "9th",
"7th-8th", "12th", "Masters", "1st-4th", "10th", "Doctorate", "5th-6th", "Preschool"]
)
marital_status = tf.feature_column.categorical_column_with_vocabulary_list(
key="marital_status",
vocabulary_list=["Married-civ-spouse", "Divorced", "Never-married", "Separated", "Widowed", "Married-spouse-absent",
"Married-AF-spouse"]
)
relationship = tf.feature_column.categorical_column_with_vocabulary_list(
key="relationship",
vocabulary_list=["Wife", "Own-child", "Husband", "Not-in-family", "Other-relative", "Unmarried"]
)
# 定义Hash特征,展示embedding的使用
occupation = tf.feature_column.categorical_column_with_hash_bucket(
key="occupation",
hash_bucket_size=1000
)
# age 特征分桶
age_bucket = tf.feature_column.bucketized_column(
source_column=age,
boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65]
)
base_columns = [workclass, education, marital_status, relationship, occupation, age_bucket]
crossed_columns = [
tf.feature_column.crossed_column(
keys=["education", "occupation"], hash_bucket_size=1000
),
tf.feature_column.crossed_column(
keys=[age_bucket, "education", "occupation"], hash_bucket_size=1000
)
]
deep_columns = [
age,
education_num,
capital_gain,
capital_loss,
hours_per_week,
tf.feature_column.indicator_column(workclass), # 做one-hot,然后送入dnn layer
tf.feature_column.indicator_column(education),
tf.feature_column.indicator_column(marital_status),
tf.feature_column.indicator_column(relationship),
# 展示embedding的使用
tf.feature_column.embedding_column(occupation, dimension=8)
]
定义数据:
# 定义数据
_CSV_COLUMNS = [
"age", "workclass", "fnlwgt", "education", "education_num",
"marital_status", "occupation", "relationship", "race", "sex",
"capital_gain", "capital_loss", "hours_per_week", "native_country", "label"
]
_CSV_COLUMN_DEFAULTS = [
[0], [''], [0], [''], [0],
[''], [''], [''], [''], [''],
[0], [0], [0], [''], ['']
]
_NUM_EXAMPLES = {
"train": 32561,
"validation": 16281
}
定义模型:
def create_model():
model = tf.estimator.DNNLinearCombinedClassifier(
model_dir="./model/wd/",
linear_feature_columns=base_columns + crossed_columns,
dnn_feature_columns=deep_columns,
dnn_hidden_units=[100, 50],
linear_optimizer="Ftrl",
dnn_optimizer="Adagrad",
n_classes=2,
batch_norm=False
)
return model
定义input_fn函数:
def input_fn(data_file, num_epochs, shuffle, batch_size):
"""为Estimator创建一个input function"""
assert tf.io.gfile.GFile(data_file), "{0} not found.".format(data_file)
def parse_csv(line):
# tf.decode_csv会把csv文件转换成 a list of Tensor,一列一个
# record_defaults用于指明每一列的缺失值用什么填充
columns = tf.io.decode_csv(line, record_defaults=_CSV_COLUMN_DEFAULTS)
features = dict(zip(_CSV_COLUMNS, columns))
labels = features.pop('label')
# tf.equal(x, y) 返回一个bool类型Tensor, 表示x == y, element-wise
# 注意数据重的空格
return features, tf.equal(labels, ' >50K')
dataset = tf.data.TextLineDataset(data_file).map(parse_csv, num_parallel_calls=5)
if shuffle:
dataset = dataset.shuffle(buffer_size=_NUM_EXAMPLES['train'] + _NUM_EXAMPLES['validation'])
dataset = dataset.repeat(num_epochs)
dataset = dataset.batch(batch_size)
return dataset
main函数:
if __name__ == "__main__":
train_epochs = 20
batch_size = 256
model = create_model()
for n in range(train_epochs):
print("train model start ...")
model.train(input_fn=lambda: input_fn(train_data, train_epochs, True, batch_size))
predict_results = model.predict(input_fn=lambda: input_fn(test_data, train_epochs, False, batch_size))
print("test model start ...")
results = model.evaluate(input_fn=lambda: input_fn(test_data, train_epochs, False, batch_size))
# print(results)
print('{0:-^30}'.format('evaluate at epoch %d' % ((n + 1))))
# results 是一个字典
print(pd.Series(results).to_frame('values'))
最后运行20个epoch之后,输出结果为:
-----evaluate at epoch 20-----
values
accuracy 0.826301
accuracy_baseline 0.763774
auc 0.852878
auc_precision_recall 0.686778
average_loss 0.381445
label/mean 0.236226
loss 0.381446
precision 0.727232
prediction/mean 0.249888
recall 0.423557
global_step 51008.000000
Process finished with exit code 0
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有