赞
踩
目录
Google于 2016 年在DLRS上发表了一篇文章:2016-Wide & Deep Learning for Recommender Systems,模型的核心思想是结合线性模型的记忆能力(memorization)和 DNN 模型的泛化能力(generalization),在训练过程中同时优化 2 个模型的参数,从而达到整体模型的预测能力最优。
记忆(memorization)即从历史数据中发现item或者特征之间的相关性。
泛化(generalization)即相关性的传递,发现在历史数据中很少或者没有出现的新的特征组合。
一个推荐系统可被看作是一个搜索排序系统,这一系统的查询输入是一组(用户,上下文信息)的集合,输出是一个排序后的物品列表。接收到一个查询请求,推荐系统会在数据库中找到相关的物品并根据特定的目的进行排序,常见的目的有用户点击、购买。
与广义的搜索排序问题一样,推荐系统也面临这样一个挑战:如何同时实现记忆与泛化(memorization and generalization)。记忆可不严格地定义为,学习并利用历史数据中的高频共现物体或特征所具有的关系。另一方面,泛化指这一关系的转移能力(transitivity),以及发现在历史数据中罕见或未曾出现过的新的特征组合。基于记忆的推荐通常更集中于用户历史行为所涵盖的特定主题(topical)。而基于泛化的推荐会倾向于提升推荐物品的多样性。
在工业界的大规模线上推荐和排序系统中,广义线性模型如逻辑回归被广泛应用,因为它们简单、可扩展(scalable)且具有可解释性。这些模型常常基于独热编码(one-hot encoding)的二进制化的稀疏特征进行训练。例如,二进制特征“user_installed_app=netflix
”在用户安装了Netflix时为1。模型的记忆能力可通过系数特征的外积变换有效实现,如AND(user_installed_app=netflix, impression_app=pandora)
在用户安装了Netflix并随后见到过Pandora时值为1。这阐明了一对特征的共现是如何与目标标签产生关联的。使用更粗粒度的特征可实现模型的泛化,如AND(user_installed_category=video, impression_category=music)
,但常常是需要手工做特征工程的。外积变换的一个局限是,它无法对训练集中未出现过的“查询-物品特征对”进行泛化。
基于嵌入技术的模型,如因子分解机(factorization machines)、深度神经网络等,可对未见过的“查询-物品特征对”进行泛化,这是通过对一个低维稠密嵌入向量的学习来实现的,且这种模型依赖于较少的特征工程。然而,在“查询-物品矩阵”是稀疏、高维的时候,其低维表示是难以有效学习的,例如用户有特定偏好或物品的吸引力较窄(niche/narrow appeal)时。在这种情形下,这一“查询-物品对”应与大部分“查询-物品对”无关,但稠密嵌入会对所有“查询-物品对”给出非零的预测,因而会导致过于泛化并给出不相关的推荐。另一方面,线性模型使用外积特征变换可依赖相当少的参数来记住这些“例外规则”。
本文中,我们呈现Wide&Deep学习框架以在一个模型中同时达到记忆和泛化,这是通过协同训练一个线性模型模块和一个神经网络模块完成的,如下图所示。
在wide &deep模型中包括两个部分,分别为Wide模型和Deep模型,Wide模型如上图左边部分,Deep模型如上图右边部分。
wide &deep模型的思想来源是根据人脑有不断记忆并且泛化的过程,这里讲宽线形模型和深度神经网络模型相结合,汲取各自优势形成了wide &deep模型,以用于推荐排序。
wide &deep模型旨在使得训练得到的模型能够同时获得记忆和泛化的能力:
wide部分是一个广义线性模型,具有着的形式,如上图(左)所示。
是模型的预测,
是
个特征对应的向量,
是模型参数,
是模型偏差。最终在
的基础上增加sigmoid函数座位最终的输出,其实就是一个LR模型。
特征及和包括原始输入特征(raw input features)和变化得到的特征(transformed features)。其中一个最为重要的变换是外积变换(cross-product transformation),它被定义为:
其中,是一个bool型变量,在第
个变换
包含第i个特征时为1,否则为0。对于二进制特征,当且仅当所有组成特征(“gender=female” 和 “language=en”)都为1时值为1,否则为0。这将捕获到二进制特征间的交互,并向广义线性模型中添加非线性项。
deep部分是一个前馈神经网络,如上图(右)所示。对于类别型特征,原始输入是特征字符串(如“language=en”)。这些稀疏、高维的类别型特征首先被转化为低维稠密实值向量,通常被称为嵌入向量。嵌入向量的维度通常在O(10)到O(100)间。在模型训练阶段,嵌入向量被随机初始化并根据最小化最终的损失函数来学习向量参数。这些低维稠密嵌入向量随后在神经网络的前馈通路中被输入到隐藏层中。特别地,每个隐藏层进行了如下计算:
其中:f是激活函数,通常为ReLU,是层的序号。
wide模块和deep模块的组合依赖于对其输出的对数几率(log odds)的加权求和作为预测,随后这一预测值被输入到一个一般逻辑损失函数(logistic loss function)中进行联合训练。需注意的是,联合训练(joint training)和拼装(ensemble)是有区别的。在拼装时,独立模型是分别训练的,它们的预测结果是在最终推断结果时组合在一起的,而不是在训练的时候。作为对比,联合训练是在训练环节同时优化wide模型、deep模型以及它们总和的权重。在模型大小上也有不同:对拼装而言,由于训练是解耦的,独立模型常常需要比较大(如更多的特征和变换)来达到足够合理的准确度用于模型拼装;作为对比,联合训练中wide部分只需要补充deep部分的弱点,即只需要少量的外积特征变换而不是全部的wide模型。
对Wide&Deep模型的联合训练通过同时对输出向模型的wide和deep两部分进行梯度的反向传播(back propagating)来实现的,这其中应用了小批量随机优化(mini-batch stochastic optimization)的技术。在实验中,我们使用了FTRL(Follow-the-regularized-leader)算法以及使用正则化来优化wide部分的模型,并使用AdaGrad优化deep部分。
组合的模型如图1(中)所示。对于逻辑回归问题,模型的预测是:
在下图中呈现了一个对推荐系统的概览。用户访问应用商店时,将生成一个请求,它可能包括多种用户和上下文特征。推荐系统返回一个APP列表(也可称为"印象"/impression),用户可对这一列表进行点击、购买等行为。这些用户行为和查询、"印象",会被记录在日志中以作为学习训练使用的训练数据。
考虑到数据库中有超过一百万的APP,在服务延迟需求(通常为O(10)ms)下,难以对每个请求穷尽所有APP的分值计算。因此,在收到请求时的第一步是召回(retrieval)。召回系统通过使用一组信号,返回一个与当前请求最匹配的物品小列表,通常这一组信号是一组机器学习的模型和人工指定的规则的组合。在缩小候选池后,排序系统通过物品的分值进行排序。这些分支通常可表示为P(y|x),在给定特征x时用户行为标签为y的概率,特征x包括了用户特征(如国籍,语言,人口统计数据),上下文特征(如设备,小时数,星期数)以及印象(impression)特征(如APP的“年龄”,APP的历史统计数据)。
应用推荐流程的实现由三部分组成:数据生成,模型训练和模型服务,如下图所示。
数据生成
在这一步中,在一段时间中的用户和app的印象(impression)数据被用来生成训练数据集。每项数据对应于一个印象(impression)。标签是app获取与否:如果被印记(impressed)的APP得到了用户的安装则为1,否则为0。
模型训练
我们在实验中使用的模型结构如下图所示。在训练中,输入层接收输入数据和词汇表(vocabularies),并生成稀疏、稠密特征以及标签。wide部分由对用户安装app和印象(impression)app的外积变换构成。而对于deep部分,模型对每个类别型特征学习了一个32维的嵌入向量。我们将所有的潜入向量与稠密特征拼接在一起,得到了一个约1200维的稠密向量。这一拼接得到的向量随后被输入到三个ReLU层,并最终通过逻辑输出单元(logistic output unit)。
Wide&Deep模型基于超5000亿样本进行训练。每当有一组新的训练数据到达时,模型都需要被重新训练。然而,每次都进行重计算所需的计算量巨大,且会延迟数据抵达到模型更新的时效性。为了解决这一挑战,我们实现了一个热启动系统,它将使用旧模型的潜入向量、线性模型权重等参数对新模型进行初始化。
在将模型加载到服务中前,会做一次预运行,来确保它不会导致线上服务出错。我们的“靠谱测试”(sanity check)通过对新旧模型的经验化验证完成。
Embedding维度大小的建议,从经验上来讲,Embedding层维度大小可以用公式来定:
n是原始维度上特征不同取值的个数,k是一个常数,通常小于10.
线上应用
一旦模型完成了训练和验证,我们就将它加载到模型服务中。对于每个请求,该服务将收到一组召回系统提供的候选app列表以及用户特征来计算评分。随后,app按照评分从高到低排序,我们将向用户按照这一顺序展现app列表。评分通过一个Wide&Deep模型的前向推导(forward inference)来计算。
为了将请求响应速度优化至10ms,我们实现了多线程并行来优化服务性能,通过更小批次的并行,来替换先前在一个批次的推导步骤中对所有候选app进行评分。
该数据集由Barry Becker从1994人口普查数据库中提取得到。
预测任务是确定一个人年薪是否超过50K。
原文链接:http://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.names
实例数目
48842个连续或离散的实例。其中训练集32561个,测试集16281个。
45222个因有未知量而被移除的实例。其中训练集30162个,测试集15060个。
属性数目
6个连续变量,8个名词性属性。
属性信息
年龄:连续值;
工作类别:私人、自由职业非公司、自由职业公司、联邦政府、地方政府、州政府、无薪、无工作经验;
一个州内观测人数:连续值;
教育程度: Bachelors(学士), Some-college(大学未毕业), 11th(高二), HS-grad(高中毕业),Prof-school(职业学校), Assoc-acdm(大学专科), Assoc-voc(准职业学位), 9th(初三),7th-8th(初中一、二年级), 12th(高三), Masters(硕士), 1st-4th(小学1-4年级), 10th(高一), Doctorate(博士), 5th-6th(小学5、6年级), Preschool(幼儿园).
教育时间:连续值;
婚姻状态: Married-civ-spouse(已婚平民配偶), Divorced(离婚), Never-married(未婚), Separated(分居), Widowed(丧偶), Married-spouse-absent(已婚配偶异地), arried-AF-spouse(已婚军属)
职业:Tech-support(技术支持), Craft-repair(手工艺维修), Other-service(其他职业),Sales(销售), Exec-managerial(执行主管), Prof-specialty(专业技术),Handlers-cleaners(劳工保洁), Machine-op-inspct(机械操作), Adm-clerical(管理文书),Farming-fishing(农业捕捞), Transport-moving(运输), Priv-house-serv(家政服务),Protective-serv(保安), Armed-Forces(军人)
家庭角色:Wife(妻子), Own-child(孩子), Husband(丈夫), Not-in-family(离家), Other-relative(其他关系), Unmarried(未婚)
种族: White(白人), Asian-Pac-Islander(亚裔、太平洋岛裔), Amer-Indian-Eskimo(美洲印第安裔、爱斯基摩裔), Other(其他), Black(非遗)
性别: Female(女), Male(男)
资本收益:连续值
资本亏损:连续值
每周工作时长:连续值
原国籍:United-States(美国), Cambodia(柬埔寨), England(英国), Puerto-Rico(波多黎各),Canada(加拿大), Germany(德国), Outlying-US(Guam-USVI-etc) (美国海外属地), India(印度),Japan(日本), Greece(希腊), South(南美), China(中国), Cuba(古巴), Iran(伊朗), Honduras(洪都拉斯), Philippines(菲律宾), Italy(意大利), Poland(波兰), Jamaica(牙买加),Vietnam(越南), Mexico(墨西哥), Portugal(葡萄牙), Ireland(爱尔兰), France(法国),Dominican-Republic(多米尼加共和国), Laos(老挝), Ecuador(厄瓜多尔), Taiwan(台湾), Haiti(海地), Columbia(哥伦比亚), Hungary(匈牙利), Guatemala(危地马拉),Nicaragua(尼加拉瓜), Scotland(苏格兰), Thailand(泰国), Yugoslavia(南斯拉夫), El-Salvador(萨尔瓦多), Trinadad&Tobago(特立尼达和多巴哥), Peru(秘鲁), Hong(香港), Holand-Netherlands(荷兰)
类别:>50K,<=50K
这里采用tensorflow高级API-Estimators进行训练
5.2.1 导入包
- import numpy as np
- import tensorflow as tf
- import pandas as pd
- import random
- import math
- import re
-
- from sklearn import preprocessing
- from os import path, listdir
- from sklearn.datasets import load_svmlight_files
- from sklearn.model_selection import train_test_split
- from sklearn import metrics
- from tensorflow.contrib import layers
-
- from sklearn import metrics
-
- import time
- import datetime
-
- import os
- os.environ["CUDA_VISIBLE_DEVICES"]="0"
-
- import tensorflow as tf
5.2.2 数据准备
关于tf.feature_column的可以查阅博文:tf.feature_column_Andy_shenzl的博客-CSDN博客_tf.feature_column
- # 定义输入样本格式
- _CSV_COLUMNS = [
- 'age', 'workclass', 'fnlwgt', 'education', 'education_num',
- 'marital_status', 'occupation', 'relationship', 'race', 'gender',
- 'capital_gain', 'capital_loss', 'hours_per_week', 'native_country',
- 'income_bracket'
- ]
- _CSV_COLUMN_DEFAULTS = [[0], [''], [0], [''], [0], [''], [''], [''], [''], [''],
- [0], [0], [0], [''], ['']]
- _NUM_EXAMPLES = {
- 'train': 32561,
- 'validation': 16281,
- }
-
- """Builds a set of wide and deep feature columns."""
- def build_model_columns():
- # 1. 特征处理,包括:连续特征、离散特征、转换特征、交叉特征等
-
- # 连续特征 (其中在Wide和Deep组件都会用到)
- 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')
-
- # 离散特征
- education = tf.feature_column.categorical_column_with_vocabulary_list(
- 'education', [
- 'Bachelors', 'HS-grad', '11th', 'Masters', '9th', 'Some-college',
- 'Assoc-acdm', 'Assoc-voc', '7th-8th', 'Doctorate', 'Prof-school',
- '5th-6th', '10th', '1st-4th', 'Preschool', '12th'])
-
- marital_status = tf.feature_column.categorical_column_with_vocabulary_list(
- 'marital_status', [
- 'Married-civ-spouse', 'Divorced', 'Married-spouse-absent',
- 'Never-married', 'Separated', 'Married-AF-spouse', 'Widowed'])
-
- relationship = tf.feature_column.categorical_column_with_vocabulary_list(
- 'relationship', [
- 'Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried',
- 'Other-relative'])
-
- workclass = tf.feature_column.categorical_column_with_vocabulary_list(
- 'workclass', [
- 'Self-emp-not-inc', 'Private', 'State-gov', 'Federal-gov',
- 'Local-gov', '?', 'Self-emp-inc', 'Without-pay', 'Never-worked'])
-
- # 离散hash bucket特征
- occupation = tf.feature_column.categorical_column_with_hash_bucket(
- 'occupation', hash_bucket_size=1000
- )
-
- # 特征Transformations
- age_buckets = tf.feature_column.bucketized_column(
- age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65]
- )
-
- # 2. 设定Wide层特征
- """
- Wide部分使用了规范化后的连续特征、离散特征、交叉特征
- """
- # 基本特征列
- base_columns = [
- # 全是离散特征
- education, marital_status, relationship, workclass, occupation,
- age_buckets,
- ]
-
- # 交叉特征列
- crossed_columns = [
- tf.feature_column.crossed_column(
- ['education', 'occupation'], hash_bucket_size=1000),
- tf.feature_column.crossed_column(
- [age_buckets, 'education', 'occupation'], hash_bucket_size=1000
- )
- ]
-
- # wide特征列
- wide_columns = base_columns + crossed_columns
-
- # 3. 设定Deep层特征
- """
- Deep层主要针对离散特征进行处理,其中处理方式有:
- 1. Sparse Features -> Embedding vector -> 串联(连续特征),其中Embedding Values随机初始化。
- 2. 另外一种处理离散特征的方法是:one-hot和multi-hot representation. 此方法适用于低维度特征,其中embedding是通用的做法
- 其中:采用embedding_column(embedding)和indicator_column(multi-hot)API
- """
- # deep特征列
- deep_columns = [
- age,
- education_num,
- capital_gain,
- capital_loss,
- hours_per_week,
- tf.feature_column.indicator_column(workclass),
- 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)
- ]
- return wide_columns, deep_columns
5.2.3 定义输入
- def input_fn(data_file, num_epochs, shuffle, batch_size):
- """为Estimator创建一个input function"""
- #assert判断,为False时执行后面语句
- assert tf.gfile.Exists(data_file), "{0} not found.".format(data_file)
- def parse_csv(line):
- print("Parsing", data_file)
- # tf.decode_csv会把csv文件转换成Tensor。其中record_defaults用于指明每一列的缺失值用什么填充。
- columns = tf.decode_csv(line, record_defaults=_CSV_COLUMN_DEFAULTS)
- features = dict(zip(_CSV_COLUMNS, columns))
- #pop函数提取label
- labels = features.pop('income_bracket')
- # 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)
- '''
- 使用 tf.data.Dataset.map,我们可以很方便地对数据集中的各个元素进行预处理。
- map接收一个函数,Dataset中的每个元素都会被当作这个函数的输入,并将函数返回值作为新的Dataset
- 因为输入元素之间时独立的,所以可以在多个 CPU 核心上并行地进行预处理。
- num_parallel_calls 参数的最优值取决于你的硬件、训练数据的特质(比如:它的 size、shape)、
- map 函数的计算量 和 CPU 上同时进行的其它处理。比较简单的一个设置方法是:将 num_parallel_calls 设置为 CPU 的核心数。
- 例如,CPU 有四个核心时,将 num_parallel_calls 设置为 4 将会很高效。
- 相反,如果 num_parallel_calls 大于 CPU 的核心数,将导致低效的调度,导致输入管道的性能下降。
-
- 也可以设置shuffle
- shuffle的功能为打乱dataset中的元素,它有一个参数buffersize,表示打乱时使用的buffer的大小,建议舍的不要太小,一般是1000
- '''
-
- dataset = dataset.repeat(num_epochs)
- #repeat的功能就是将整个序列重复多次,主要用来处理机器学习中的epoch,假设原先的数据是一个epoch,使用repeat(2)就可以将之变成2个epoch
- dataset = dataset.batch(batch_size)
- '''
- batch是机器学习中批量梯度下降法(Batch Gradient Descent, BGD)的概念,
- 在每次梯度下降的时候取batch-size的数据量做平均来获取梯度下降方向,
- 例如我们将batch-size设为2,那么每次iterator都会得到2个数据
- '''
- iterator = dataset.make_one_shot_iterator()
- batch_features, batch_labels = iterator.get_next()
- return batch_features, batch_labels
5.2.4 模型准备
- # Wide & Deep Model
- def build_estimator(model_dir, model_type):
- """Build an estimator appropriate for the given model type."""
- wide_columns, deep_columns = build_model_columns()
- hidden_units = [100, 50]
-
- # Create a tf.estimator.RunConfig to ensure the model is run on CPU, which
- # trains faster than GPU for this model.
- run_config = tf.estimator.RunConfig().replace(
- session_config=tf.ConfigProto(device_count={'GPU': 0}))
-
- if model_type == 'wide':
- return tf.estimator.LinearClassifier(
- model_dir=model_dir,
- feature_columns=wide_columns,
- config=run_config)
- elif model_type == 'deep':
- return tf.estimator.DNNClassifier(
- model_dir=model_dir,
- feature_columns=deep_columns,
- hidden_units=hidden_units,
- config=run_config)
- else:
- return tf.estimator.DNNLinearCombinedClassifier(
- model_dir=model_dir,
- linear_feature_columns=wide_columns,
- dnn_feature_columns=deep_columns,
- dnn_hidden_units=hidden_units,
- config=run_config)
-
5.2.5 模型训练
- # 模型路径
- model_type = 'widedeep'
- model_dir = '/Users/admin/Desktop/model/推荐算法/widedeep'
-
- # Wide & Deep 联合模型
- model = build_estimator(model_dir, model_type)
-
-
- # ## 4)模型训练
-
- # In[11]:
-
- # 训练参数
- train_epochs = 10
- batch_size = 5000
- train_file = '/Users/admin/Desktop/model/推荐算法/widedeep/adult.data'
- test_file = '/Users/admin/Desktop/model/推荐算法/widedeep/adult.test'
-
- # 6. 开始训练
- for n in range(train_epochs):
- # 模型训练
- model.train(input_fn=lambda: input_fn(train_file, train_epochs, True, batch_size))
- # 模型评估
- results = model.evaluate(input_fn=lambda: input_fn(test_file, 1, False, batch_size))
- # 打印评估结果
- print("Results at epoch {0}".format((n+1) * train_epochs))
- print('-'*30)
- for key in sorted(results):
- print("{0:20}: {1:.4f}".format(key, results[key]))
模型最后一次输出结果
- Parsing /Users/admin/Desktop/model/推荐算法/widedeep/adult.data
- INFO:tensorflow:Create CheckpointSaverHook.
- INFO:tensorflow:Restoring parameters from /Users/admin/Desktop/model/推荐算法/widedeep/model.ckpt-1254
- INFO:tensorflow:Saving checkpoints for 1255 into /Users/admin/Desktop/model/推荐算法/widedeep/model.ckpt.
- INFO:tensorflow:loss = 8.967002, step = 1255
- INFO:tensorflow:Saving checkpoints for 1320 into /Users/admin/Desktop/model/推荐算法/widedeep/model.ckpt.
- INFO:tensorflow:Loss for final step: 1.0456264.
- Parsing /Users/admin/Desktop/model/推荐算法/widedeep/adult.test
- WARNING:tensorflow:Casting <dtype: 'float32'> labels to bool.
- WARNING:tensorflow:Casting <dtype: 'float32'> labels to bool.
- INFO:tensorflow:Starting evaluation at 2020-03-27-06:13:54
- INFO:tensorflow:Restoring parameters from /Users/admin/Desktop/model/推荐算法/widedeep/model.ckpt-1320
- INFO:tensorflow:Finished evaluation at 2020-03-27-06:13:56
- INFO:tensorflow:Saving dict for global step 1320: accuracy = 1.0, accuracy_baseline = 1.0, auc = 1.0, auc_precision_recall = 0.0, average_loss = 0.0018178094, global_step = 1320, label/mean = 0.0, loss = 7.3989387, prediction/mean = 0.001807458
- Results at epoch 100
- ------------------------------
- accuracy : 1.0000
- accuracy_baseline : 1.0000
- auc : 1.0000
- auc_precision_recall: 0.0000
- average_loss : 0.0018
- global_step : 1320.0000
- label/mean : 0.0000
- loss : 7.3989
- prediction/mean : 0.0018
2020/04/02 更
报错1:
- Field 0 in record 0 is not a valid int32: 5.0
发现数据类型有些问题,因为我的数据中有浮点数,但如果默认值设置的全是0,TensorFlow根据默认值推测数据应当全是INT32类型,而不是float32。
解决方案:
将默认值由1改为0.0即可,这样TensorFlow就会推断所有值均为浮点数,所有的数值都可识别了。
报错2:
- InvalidArgumentError : Shape in shape_and_slice spec [,] does not match the shape stored in checkpoi
-解决方法:
classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,hidden_units=[10, 20, 10],n_classes=2,model_dir="/tmp/iris_model")
这里的model_dir给改了。。因为tensorflow默认会先去找已经训练过的模型
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。