考虑一个工作推荐任务,假设有两个 LinkedIn 成员 Alice 和 Annie,她们的信息非常相似。她们两个都有相同的用户特征,响应的是同一对公司。但她们的反应完全相反。如果我们使用这两个成员的数据来训练一个机器学习模型,那么模型将不会有效,因为训练样本相互矛盾。解决方案之一是根据成员的数据为每个成员训练一个单一的模型。这就是个性化的一个例子。
个性化的一种可能实现是将所有成员 ID 嵌入到一个模型中。这通常会导致一个非常大的模型,因为成员数量可能会达到数亿量级。GDMix(GeneralizedDeepMixed Model,通用深度混合模型)是 LinkedIn 为高效训练这类模型而创建的解决方案。它将一个大模型分解为全局模型(又称“固定效应模型”)和大量小模型(又称“随机效应模型”),然后分别求解。这种分而治之的方法,允许使用标准硬件对大型个性化模型进行有效的训练。GDMix 是其前身 Photon ML 的改进,它扩展并支持深度学习模型。要了解更多背景信息,请查看我们的工程博客。
当前版本的 GDMix 支持固定效应的逻辑回归模型和 DeText 模型,然后是随机效应的逻辑回归模型。在未来,如果增加的复杂性可以通过改进行度量来证明的话,我们可能会支持随机效应的深度模型。
作为一种基本的分类模型,逻辑回归模型因其模型简单和训练效率高,在搜索和推荐系统中得到了广泛的应用。我们的实现使用 TensorFlow 进行数据读取和梯度计算,并使用 SciPy 的 L-BFGS 求解器。这种组合利用了 TensorFlow 的多功能性和 L-BFGS 的快速收敛性。这种模式在功能上等效于 Photon-ML,但效率有所提高。我们的内部测试表明,在各种数据集上,训练速度提高到了 10%~40% 不等。
DeText 是一个强调文本特征的排名框架。GDMix 原生支持 DeText 作为全局模型进行训练。用户可以指定一个固定效应模型类型为 DeText,然后提供网络规格。GDMix 会自动对其进行训练和评分,并将模型与后续的随机效应模型进行连接。目前,只允许将 DeTex 的 Pointwise 损失函数与逻辑回归随机效应模型相连接。
GDMix 可以与任何深度学习的固定效应模型一起使用。GDMix 与其他模型之间的接口在文件 I/O 中,用户可以在 GDMix 外部训练一个模型,然后用模型对训练数据进行评分,并将评分保存到文件中,作为 GDMix 随机效应训练的输入。这使用户能够根据来自自定义固定效应模型的得分来训练随机效应模型,而 GDMix 本身并不支持这种模型。
对于逻辑回归模型,训练效率是通过并行训练实现的。由于固定效应模型通常需要对大量的数据进行训练,因此采用了基于 TensorFlow All-Reduce 操作的同步训练。每个工作器取一部分训练数据并计算局部梯度。梯度聚集然后馈入 L-BFGS 求解器。每个随机效应模型的训练数据集通常很小,但是模型的数量(例如,所有 LinkedIn 成员的单个模型)可以达到数亿量级。这就需要一种分区和并行的训练策略,其中每个工作器负责一部分人口,所有工作器独立并同时训练分配给它们的模型。
对于 DeText 模型,无论是基于 TensorFlow 的参数服务器异步分布式训练,还是基于 Horovod 的同步分布式训练,都可以提高效率。
GDMix 具有 Python 和 Scala 的混合实现,用于在 Spark 上训练模型和处理中间数据,GDMix 需要 Python 3.3+ 和 Apache Spark 2.0+。
用户需要:
gdmix-data
fat jar。gdmix-trainer
和gdmix-workflow
Python 包。wget -c https://linkedin.bintray.com/maven/com/linkedin/gdmix/gdmix-data-all_2.11/0.2.0/gdmix-data-all_2.11-0.2.0.jar -O gdmix-data-all_2.11.jarpip install gdmix-trainer gdmix-workflow
有关实际操作示例的详细信息,请参阅《尝试 movieLens 示例》( Tryout the movieLens example)和《在 Kubernetes 上运行 GDMix 进行分布式训练》( Run GDMix on Kubernetes for distributed training)中的分布式训练部分。
GDMix 的 Python 和 Scala 模块分别使用 setuptools 和 Grtadle 来管理代码。我们建议使用 miniconda 来管理 Python dev 虚拟环境。本地构建和测试如下所示。
# Build scala module
./gradlew wrapper --gradle-version 4.5
./gradlew clean build
# Create virtural env
conda create --name gdmix python=3.7.4 -y
conda activate gdmix
pip install pytest
# Install from local gdmix-trainer dev code
cd gdmix-trainer && pip install .
# Run pytest
pytest
# Install from local gdmix-workflow dev code
cd gdmix-workflow && pip install .
# Run pytest
pytest
GDmix 的整个训练流程如图 1 所示。数据准备步骤由用户提供,输入文件应满足下一节“输入数据”中所示的要求。固定效应捕捉全局趋势,而随机效应体现个性。针对推荐系统中大量交叉特征的复杂性,采用并行分块坐标下降法,将固定效应和随机效应看作“坐标”,在每个优化步骤中,每次只优化一个坐标,其余的坐标保持不变。通过对所有坐标进行几次迭代,我们得到了一个接近于原始问题解的解。
图 1:GDMix 概述
目前,GDMix 支持三种不同的操作模型,如下所示:
固定效应 | 随机效应 |
---|---|
逻辑回归 | 逻辑回归 |
DeText 支持的深度 NLP 模型 | 逻辑回归 |
用户设计的任意模型 | 逻辑回归 |
在最后一种模式中,客户在 GDMix 之外使用自己的模型训练固定效应模型,然后将该模型的得分作为 GDMix 随机效应训练的输入。
图 2:GDMix 操作模型
图 3 显示了固定效应模型和随机效应模型训练的作业,以一个固定效应模型global
和两个随机效应模型per-user
和per-movie
为例。global
模型是基于所有数据进行训练,通常需要分布式训练;而per-user
和per-movie
的随机效应模型则是根据实体建立许多独立的小模型。为了加速随机效应模型训练的并行性,需要一个数据分区作业,按照实体 ID(如用户 ID)对记录进行分组,这可以由 Spark 高效完成。此外,还要为每个模型计算度量。
图 3:GDMix Workflow 作业
输入数据应该按照以下结构组织,其中,固定效应和每个随机效应(per-user
和per-movie
)都有一个子目录,其中包含featureList
、metadata
、trainingData
和validationData
目录。
├── fixed-effect
│ ├── featureList
│ │ └── global
│ ├── metadata
│ │ └── tensor_metadata.json
│ ├── trainingData
│ │ └── part-00000.tfrecord
│ └── validationData
│ └── part-00000.tfrecord
├── per-user
│ ├── featureList
│ │ └── per_user
│ ├── metadata
│ │ └── tensor_metadata.json
│ ├── trainingData
│ │ └── part-00000.tfrecord
│ └── validationData
│ └── part-00000.tfrecord
├── per-movie
│ ├── featureList
│ │ └── per_movie
│ ├── metadata
│ │ └── tensor_metadata.json
│ ├── trainingData
│ │ └── part-00000.tfrecord
│ └── validationData
│ └── part-00000.tfrecord
trainingData
和validationData
是 tfrecord 格式的预处理训练和验证数据;featureList
目录包含一个列出所有特征名称的文本文件;metadata
目录中有一个 json 文件,存储训练样本的数量和元数据,如名称、数据类型、形状和 IF(稀疏向量,用于对 tfrecord 数据进行反序列化 )。global
模型的示例如下所示:
{
"numberOfTrainingSamples" : 179087,
"features" : [ {
"name" : "weight",
"dtype" : "float",
"shape" : [ ],
"isSparse" : false
}, {
"name" : "global",
"dtype" : "float",
"shape" : [ 50 ],
"isSparse" : true
}, {
"name" : "uid",
"dtype" : "long",
"shape" : [ ],
"isSparse" : false
} ],
"labels" : [ {
"name" : "response",
"dtype" : "int",
"shape" : [ ],
"isSparse" : false
} ]
}
GDmix 配置是一个 json 文件,指定了 GDMix 训练相关参数,如输入数据路径、输出路径、固定效应 / 随机效应模型的模型参数、计算资源(仅限分布式)等。有关 GDmix 配置的更多详细信息,请参见gdmix_config.md 文件。
在本节中,我们将介绍如何使用 GDmix 和 movieLens 数据来训练固定效应模型globe
和两个随机效应模型per-user
和per-movie
。在脚本 download_process_movieLens_data.py 中准备了每种模型的功能。per-user
使用特征年龄、性别和职业,per-movie
使用特征类型和上映日期,globe
使用所有这五个特征。
尝试 movieLens 示例的最简单方法是在预构建的 Docker 容器中运行它:
docker run --name gdmix -it linkedin/gdmix bash
global``per-user
和per-movie
的训练逻辑回归模型(有关详细信息,请参阅《训练逻辑回归模型》( Train logsitic regression models)一节):python -m gdmixworkflow.main --config_path lr-single-node-movieLens.config --jar_path gdmix-data-all_2.11.jar
global
训练一个深度和广度的神经网络模型,为per-user
和per-movie
训练两个逻辑回归模型(详见《训练神经网络和逻辑回归模型》( Train neural network model plus logsitic regression models)一节):python -m gdmixworkflow.main --config_path detext-single-node-movieLens.config --jar_path gdmix-data-all_2.11.jar
要直接运行 GDMix,用户需要遵循上面《作为用户》(As user)一节中的说明进行操作,我们将详细说明下面的步骤。因为我们还不支持 PySpark,因此需要安装 Spark。下面我们介绍如何在 CentOS/RHEL 7.x 上安装 Spark 2.4.6,在其他系统上的安全也可以类似地完成。
yum install -y java-1.8.0-openjdk
export JAVA_HOME=/etc/alternatives/jre
spark_version=2.4.6
spark_pkg=spark-${spark_version}-bin-hadoop2.7
wget https://downloads.apache.org/spark/spark-${spark_version}/${spark_pkg}.tgz
mkdir /opt/spark
tar -xf ${spark_pkg}.tgz && \
mv ${spark_pkg}/jars /opt/spark && \
mv ${spark_pkg}/bin /opt/spark && \
mv ${spark_pkg}/sbin /opt/spark && \
mv ${spark_pkg}/kubernetes/dockerfiles/spark/entrypoint.sh /opt/ && \
mv ${spark_pkg}/examples /opt/spark && \
mv ${spark_pkg}/kubernetes/tests /opt/spark && \
mv ${spark_pkg}/data /opt/spark && \
chmod +x /opt/*.sh && \
rm -rf spark-*
export SPARK_HOME=/opt/spark
export PATH=/opt/spark/bin:$PATH
export SPARK_CLASSPATH=$SPARK_CLASSPATH:/opt/spark/jars/
下载并运行提供的脚本 download_process_movieLens_data.py 来下载 movieLens 数据并对其进行预处理,--dest_path
参数可以用来将结果保存到其他路径,默认为当前路径下的movieLens
目录:
wget https://raw.githubusercontent.com/linkedin/gdmix/master/scripts/download_process_movieLens_data.py
pip install pandas
python download_process_movieLens_data.py
下载用于 Spark 处理中间数据的gdmix-data
fat jar:
wget -c https://linkedin.bintray.com/maven/com/linkedin/gdmix/gdmix-data-all_2.11/0.2.0/gdmix-data-all_2.11-0.2.0.jar -O gdmix-data-all_2.11.jar
安装 Python 包gdmix-trainer
和gdmix-workflow
:
pip install gdmix-trainer gdmix-workflow
为演示目的提供了一个 GDMix 配置 lr-single-node-movieLens.config ,下载它并使用以下命令开始 GDMix 训练:
wget https://raw.githubusercontent.com/linkedin/gdmix/master/gdmix-workflow/examples/movielens-100k/lr-single-node-movieLens.config
python -m gdmixworkflow.main --config_path lr-single-node-movieLens.config --jar_path gdmix-data-all_2.11.jar
在一台装有 16 块 Intel Xeon CPU 2.10GHz 的计算机上,完成训练需要 2 分钟。如果训练成功,结果目录(来自 GDMix 配置中的output_dir
字段)将在训练结束时显示:
GDMix training is finished, results are saved to lr-training.
结果目录lr-training
具有以下结构。固定效应模型和随机效应模型根据 GDMix 配置保存在子目录global
、per-user
和per-movie
中。在每个子目录中,metric
和model
目录将度量保存在验证数据集和训练的模型上,其余的是用于回放或调试的中间数据。
|-- global
| |-- metric
| | `-- evalSummary.json
| |-- models
| | `-- global_model.avro
| |-- train_scores
| | `-- part-00000.avro
| `-- validation_scores
| `-- part-00000.avro
|-- per-movie
| |-- metric
| | `-- evalSummary.json
| |-- models
| | `-- part-00000.avro
| |-- partition
| | |-- metadata
| | | `-- tensor_metadata.json
| | |-- partitionList.txt
| | |-- trainingData
| | | ...
| | `-- validationData
| | ...
| |-- train_scores
| | `-- partitionId=0
| | `-- part-00000-active.avro
| `-- validation_scores
| `-- partitionId=0
| `-- part-00000.avro
`-- per-user
|-- metric
| `-- evalSummary.json
|-- models
| `-- part-00000.avro
|-- partition
| |-- metadata
| | `-- tensor_metadata.json
| |-- partitionList.txt
| |-- trainingData
| | ...
| `-- validationData
| ...
|-- train_scores
| `-- partitionId=0
| `-- part-00000-active.avro
`-- validation_scores
`-- partitionId=0
`-- part-00000.avro
下面表格总结了每个模型的度量。正如我们所看到的,与单独使用global
固定效应模型相比,为per-user
和per-movie
添加随机效应模型可以显著提高 AUC。
global | LR | 0.6237 |
per-user | LR | 0.7058 |
per-movie | LR | 0.7599 |
作为比较,我们将训练一个由 DeText 支持的固定效应global
神经网络模型,并保持随机效应模型不变。我们增加了电影片名作为global
模型的一个附加特征,而神经网络是有广度和深度的,即电影片名是“深度的”,其余特征是“广度的”。
我们使用 detext-single-node-movieLens.config GDMix 配置来进行训练:
wget https://raw.githubusercontent.com/linkedin/gdmix/master/gdmix-workflow/examples/movielens-100k/detext-single-node-movieLens.config
python -m gdmixworkflow.main --config_path detext-single-node-movieLens.config --jar_path gdmix-data-all_2.11.jar
在一台装有 16 块 Intel Xeon CPU 2.10GHz 的计算机上,完成训练需要 3 分钟。下面的表格显示了每种模型的 AUC。具有广度和深度的固定效应global
模型的性能远远优于逻辑回归模型(0.7090 vs 0.6237),整体 AUC 也有所提高(0.7680 vs 0.7599)。我们仍然可以从per-user
的随机效果模型中看到显著的改进,但与per-movie
的模型相比并没有太多的改进。在生产环境中,我们可以部署深度和广度的global
和逻辑回归per-user
模型,以简化模型的部署。
模型 | 类型 | AUC |
---|---|---|
global | DeText | 0.7090 |
per-user | LR | 0.7665 |
per-movie | LR | 0.7680 |
请注意,由于 download_process_movieLens_data.py 中的随机训练 / 验证数据分区,用户可能会得到略微不同的结果。
GDMix 的分布式训练是基于 Kubernetes 的。它利用 Kubernetes 作业调度服务 Kubeflow 和 Spark-on-k8s-operator 在分布式运行 TensorFlow 和 Spark 作业。它还是用 Kubeflow Pipeline 来编排作业。在这种情况下,需要集中存储训练数据和模型。用户可以使用 Kubernetes-HDFS 或 NFS 作为集中存储。有关分布式训练的更多信息,请参阅 gdMix-workflow 的自述文件。下图显示了 Kubeflow Pipeline UI 中的 GDMix movieLens 示例的快照。
图 4:在 Kubeflow Pipeline 进行 GDMix 分布式训练
原文链接:
领取专属 10元无门槛券
私享最新 技术干货