深度学习推荐系统的工程实现
参考自《深度学习推荐系统》——王喆,用于学习和记录。
介绍
之前章节主要从理论和算法层面介绍了推荐系统的关键思想。但算法和模型终究只是“好酒”,还需要用合适的“容器”盛载才能呈现出最好的味道,这里的“容器”指的就是实现推荐系统的工程平台。
从工程的角度来看推荐系统,可以将其分为两大部分:数据部分和模型部分。数据部分主要指推荐系统所需数据流的相关工程实现;模型部分指的是推荐模型的相关工程实现,根据模型应用阶段的不同,可进一步分为离线训练部分和线上服务部分。根据推荐系统整体的工程架构,本章的主要内容可以分为以下三大部分:
(1)推荐系统的数据流:主要介绍与推荐系统数据流相关的大数据平台的主要框架和实现大数据平台的主流技术。
(2)深度学习推荐模型的离线训练:主要介绍训练深度学习推荐模型的主流平台,Spark MLlib、Parameter Server(参数服务器)、TensorFlow、PyTorch的主要原理。
(3)深度学习推荐模型的上线部署:主要介绍部署深度学习推荐模型的技术途径和模型线上服务的过程。
在此工程架构基础上,介绍工程和理论之间的权衡方法,探讨推荐算法工程师如何进行取舍才能达到工程和理论之间的平衡和统一。
推荐系统的数据流
本节要介绍的“推荐系统的数据流”指的是训练、服务推荐模型所需数据的处理流程。
大数据平台的发展经历了从批处理到流计算再到全面融合进化的阶段。架构模式的不断发展带来的是数据处理实时性和灵活性的大幅提升。按照发展的先后顺序,大数据平台主要有批处理、流计算、Lambda、Kappa 4种架构模式。
批处理大数据架构
在大数据平台诞生之前,传统数据库很难处理海量数据的存储和计算问题。针对这一难题,以Google GFS和Apache HDFS为代表的分布式存储系统诞生,解决了海量数据的存储问题;为了进一步解决数据的计算问题,MapReduce框架被提出,采用分布式数据处理再逐步Reduce的方法并行处理海量数据。“分布式存储+MapReduce”的架构只能批量处理已经落盘的静态数据,无法在数据采集、传输等数据流动的过程中处理数据,因此被称为批处理大数据架构。
相比之前以数据库为核心的数据处理过程,批处理大数据架构用分布式文件系统和MapReduce替换了原来的依托传统文件系统和数据库的数据存储和处理方法,批处理大数据架构示意图如图所示。
178
批处理大数据架构示意图
但该架构只能批量处理分布式文件系统中的数据,因此数据处理的延迟较大,严重影响相关应用的实时性,“流计算”的方案应运而生。
流计算大数据架构
流计算大数据架构在数据流产生及传递的过程中流式地消费并处理数据(如图所示)。流计算架构中“滑动窗口”的概念非常重要,在每个“窗口”内部,数据被短暂缓存并消费,在完成一个窗口的数据处理后,流计算平台滑动到下一时间窗口进行新一轮的数据处理。因此理论上,流计算平台的延迟仅与滑动窗口的大小有关。在实际应用中,滑动窗口的大小基本以分钟级别居多,这大大提升了原“批处理”架构下动辄几小时的数据延迟。
流计算大数据架构示意图
知名开源流计算平台包括Storm、Spark Streaming、Flink等,特别是近年来崛起的Flink,它将所有数据均看作“流”,把批处理当作流计算的一种特殊情况,可以说是“原生”的流处理平台。
在流计算的过程中,流计算平台不仅可以进行单个数据流的处理,还可以对多个不同数据流进行join操作,并在同一个时间窗口内做整合处理。除此之外,一个流计算环节的输出还可以成为下游应用的输入,整个流计算架构是灵活可重构的。因此,流计算大数据架构的优点非常明显,就是数据处理的延迟小,数据流的灵活性非常强。这对于数据监控、推荐系统特征实时更新,以及推荐模型实时训练有很大的帮助。
另外,纯流计算的大数据架构摒弃了批处理的过程,这使得平台在数据合法性检查、数据回放、全量数据分析等应用场景下显得捉襟见肘;特别是在时间窗口较短的情况下,日志乱序、join操作造成的数据遗漏会使数据的误差累计,纯流计算的架构并不是完美的。这就要求新的大数据架构能对流计算和批处理架构做一定程度的融合,取长补短。
Lambda架构
Lambda架构是大数据领域内举足轻重的架构,大多数一线互联网公司的数据平台基本都是基于Lambda架构或其后续变种架构构建的。
Lambda架构的数据通道从最开始的数据收集阶段裂变为两条分支:实时流和离线处理。实时流部分保持了流计算架构,保障了数据的实时性,而离线处理部分则以批处理的方式为主,保障了数据的最终一致性,为系统提供了更多数据处理的选择。Lambda架构示意图如图所示。
流计算部分为保障数据实时性更多是以增量计算为主,而批处理部分则对数据进行全量运算,保障其最终的一致性及最终推荐系统特征的丰富性。在将统计数据存人最终的数据库之前,Lambda架构往往会对实时流数据和离线层数据进行合并,并会利用离线层数据对实时流数据进行校检和纠错,这是Lambda架构的重要步骤。
Lambda架构示意图
Lambda架构通过保留流处理和批处理两条数据处理流程,使系统兼具实时性和全面性,是自前大部分公司采用的主流框架。但由于实时流和离线处理部分存在大量逻辑冗余,需要重复地进行编码工作,浪费了大量计算资源,那么有没有可能对实时流和离线部分做进一步的融合呢?
Kappa架构
Kappa架构是为了解决Lambda架构的代码冗余问题而产生的。Kappa架构秉持着一“Everythingisstreaming(一切皆是流)的原则,在这个原则之下,无论是真正的实时流,还是离线批处理,都被以流计算的形式执行。也就是说,离线批处理仅是“流处理”的一种特殊形式。从某种意义讲,Kappa架构也可以看作流计算架构的“升级”版本。
那么具体来讲,Kappa架构是如何通过同样的流计算框架实现批处理的呢?事实上,“批处理”处理的也是一个时间窗口的数据,只不过与流处理相比,这个时间窗口比较大,流处理的时间窗口可能是5分钟,而批处理可能需要1天。除此之外,批处理完全可以共享流处理的计算逻辑。
由于批处理的时间窗口过长,不可能在在线环境下通过流处理的方式实现,那么问题的关键就在于如何在离线环境下利用同样的流处理框架进行数据批处理。
为了解决这个问题,需要在原有流处理的框架上加上两个新的通路“原始数据存储”和“数据重播”。“原始数据存储”将未经流处理的数据或者日志原封不动地保存到分布式文件系统中,“数据重播”将这些原始数据按时间顺序进行重播,并用同样的流处理框架进行处理,从而完成离线状态下的数据批处理。这就是Kappa架构的主要思路(如图所示)。
Kappa架构示意图
Kappa架构从根本上完成了Lambda架构流处理部分和离线部分的融合,是非常优美和简洁的大数据架构。但在工程实现过程中,Kappa架构仍存在一些难点,例如数据回放过程的效率问题、批处理和流处理操作能否完全共享的问题。因此,当前业界的趋势仍以Lambda架构为主流,但逐渐向Kappa架构靠拢。
大数据平台与推荐系统的整合
大数据平台与推荐系统的关系是非常紧密的,无论是推荐系统特征的实时性还是模型训练的实时性都依赖于大数据平台对数据的处理速度。具体来讲,大数据平台与推荐系统的整合主要体现在两个方面:
(1)训练数据的处理。
(2)特征的预计算。
如图所示,无论采用哪种大数据架构,大数据平台在推荐系统中的主要任务都是对特征和训练样本的处理。根据业务场景的不同,完成特征处理之后,样本和特征数据最终流向两个方向:
(1)以HDFS为代表的离线海量数据存储平台,主要负责存储离线训练用的训练样本。
(2)以Redis为代表的在线实时特征数据库,主要负责为模型的在线服务提供实时特征。
大数据平台与推荐系统的整合
大数据架构的选择与推荐模型的训练方式是密切相关的。如果推荐模型希望进行准实时甚至实时的训练更新,那么对大数据平台数据处理能力的要求会非常高。利用流计算平台实时地对数据进行特征工程的计算,不同数据流的join操作是必须要进行的,甚至可以将模型的更新过程整合进流计算平台之中。
加入了机器学习层的架构也被称为新一代的Unified大数据架构,其在Lambda或Kappa架构上的流处理层新增了机器学习层,将机器学习和数据处理融为一体,被看作推荐系统和大数据平台的深度整合。
总而言之,互联网海量数据场景下的推荐系统与大数据平台的关系是密不可分的。业界所有前沿的推荐系统只有与大数据平台进行深度整合,才能完成推荐系统的训练、更新、在线服务的全部过程。
推荐模型离线训练之 Spark MLlib
在推荐、广告、搜索等互联网场景下,动辄TB甚至PB级的数据量导致几乎不可能在传统单机环境下完成机器学习模型的训练,分布式机器学习训练成为唯一的选择。在推荐模型的离线训练问题上,笔者将依次介绍分布式机器学习训Spark MLlib、Parameter Server 和 Tensor Flow。它们并不是唯三可供选择的平台,但它们分别代表三种主流的解决分布式训练的方法。本节从Spark MLlib开始,介绍最流行的大数据计算平台是如何处理机器学习模型的并行训练问题的。
虽然受到诸如Flink等后起之秀的挑战,但Spark仍是当之无愧的业界最主流的计算平台,而且为了照顾数据处理和模型训练平台的一致性,也有大量公司采用Spark原生的机器学习平台MLlib进行模型训练。选择SparkMLlib作为机器学习分布式训练的第一站,不仅因为Spark被广泛使用,更是因为Spark MLlib的并行训练方法代表着一种朴素的、直观的解决方案。
Spark的分布式计算原理
在介绍SparkMLlib的分布式机器学习训练方法之前,先回顾Spark的分布式计算原理,这是分布式机器学习的基础。
Spark是一个分布式计算平台。所谓分布式,指的是计算节点之间不共享内存,需要通过网络通信的方式交换数据。要强调的是,Spark最典型的应用方式是建立在大量廉价计算节点上的,这些节点可以是廉价主机,也可以是虚拟的Docker Container(Docker容器);但这种方式区别于CPU+GPU的架构,以及共享内存多处理器的高性能服务器架构。搞清楚这一点,对于理解后续的Spark的计算原理是重要的。
从图的 Spark 架构图可以看出,Spark 程序由 Cluster Manager(集群管理节点)进行调度组织,由WorkerNode(工作节点)进行具体的计算任务执行,最终将结果返回DriveProgram(驱动程序)。在物理的WorkerNode上,数据还可能分为不同的Partition(数据分片),可以说partition是Spark的基础处理单元。
Spark架构图
在执行具体的程序时,Spark会将程序拆解成一个任务DAG(DirectedAcyclicGraph,有向无环图),再根据DAG决定程序各步骤执行的方法。下图所示为某示例程序的DAG图,该程序分别从textFile和HadoopFile读取文件,经过一系列操作后进行join,最终得到处理结果。
在Spark平台上并行处理所示的DAG时,最关键的过程是找到哪些是可以并行处理的部分,哪些是必须shuffle(混洗)和reduce的部分。
某Spark任务的有向无环图
这里的shuffle指的是所有Partition的数据必须进行洗牌后才能得到下一步的数据,最典型的操作就是groupByKey操作和join操作。以join操作为例,必须对textFile 数据和 hadoopFile 数据做全量的匹配才能得到 join 操作后的dataframe(Spark保存数据的结构)。而 groupByKey 操作需要对数据中所有相同的key进行合并,也需要全局的shuffle才能完成。
与之相比,map、filter等操作仅需逐条地进行数据处理和转换,不需要进行数据间的操作,因此各Partition之间可以并行处理。
除此之外,在得到最终的计算结果之前,程序需要进行reduce操作,从各Partition。Partition,reduce度逐渐降低,直到将最终的计算结果汇总到master节点(主节点)上。
可以说,shuffle和reduce操作的发生决定了纯并行处理阶段的边界。如图所示,Spark的DAG被分割成了不同的并行处理阶段(stage)。
被 shuffle 操作分割的 DAG
需要强调的是,shufle操作需要在不同计算节点之间进行数据交换,非常消耗计算、通信及存储资源,因此shuffle操作是spark程序应该尽量避免的。一句话总结Spark的计算过程:Stage内部数据高效并行计算,Stage边界处进行消耗资源的shuffle操作或者最终的reduce操作。
Spark MLlib 的模型并行训练原理
有了Spark分布式计算过程的基础,就可以更清晰地理解SparkMLlib的模型并行训练原理。
在所有主流的机器学习模型中,RandomForest(随机森林)的模型结构特点决定了其可以完全进行数据并行的模型训练,而GBDT的结构特点决定了树之间只能进行串行的训练。本节不再赘述其Spark的实现方式,而将重点放在梯度下降类方法的实现上,因为梯度下降的并行程度直接决定了以逻辑回归为基础,以MLP为代表的深度学习模型的训练速度。
为了更准确地理解Spark并行梯度下降法的具体实现,这里深人SparkMLlib 的源码,直接贴出Spark做miniBatch梯度下降的源码的关键部分(代码摘自Spark2.4.3Gradient Descent run Mini Batch SGD):
while (i <= numIterations) {
// 迭代次数不超过上限
val bcWeights = data.context.broadcast(weights)
// 广播模型所有权重参数
val (gradientSum, lossSum, miniBatchSize) = data.sample(false, miniBatchFraction, 42 + i)
.treeAggregate()
val weights = updater.compute(weights, gradientSum / miniBatchSize)
// 根据梯度更新权重
i += 1
}
可见,Spark的minibatch过程只做了三件事:
(1)把当前的模型参数广播到各个数据Partition(可当作虚拟的计算节点)。
(2)各计算节点进行数据抽样得到minibatch的数据,分别计算梯度,再通treeAggregate,gradientSum。
(3)利用gradientSum更新模型权重。
这样一来,每次迭代的Stage和Stage的边界就非常清楚了,Stage内部的并行部分是各节点分别采样并计算梯度的过程,Stage的边界是汇总加和各节点梯度的过程。这里再强调一下汇总梯度的操作treeAggregate,该操作是进行类似树结构的逐层汇总,整个过程是一个reduce过程,并不包含shuffle操作,再加上采用分层的树形操作,每层内部的树节点操作并行执行,因此整个过程非常高效。
在选代次数达到上限或者模型已经充分收敛后,模型停止训练。这就是Spark MLlib 进行 mini batch 梯度下降的全过程,也是 Spark MLlib 实现分布式机器学习的最典型代表。
总结来说,Spark MLlib的并行训练过程其实是“数据并行”的过程,并不涉及过于复杂的梯度更新策略,也没有通过“参数并行”的方式实现并行训练。这样的方式简单、直观、易于实现,但也存在一些局限性。
Spark MLlib 并行训练的局限性
虽然Spark MLlib基于分布式集群,利用数据并行的方式实现了梯度下降的并行训练,但使用过SparkMLlib的读者应该有相关的经验,使用SparkMLlib训练复杂神经网络往往力不从心,不仅训练时间长,而且在模型参数过多时,经常存在内存溢出的问题。具体地讲,SparkMLlib的分布式训练方法有以下弊端:
-
采用全局广播的方式,在每轮迭代前广播全部模型参数。众所周知,Spark的广播过程非常消耗带宽资源,特别是当模型的参数规模过大时,广播过程和在每个节点都维护一个权重参数副本的过程都是极消耗资源的,这导致Spark在面对复杂模型时的表现不佳。
全局广播使得每个节点都维护全部模型参数,及其消耗资源 -
采用阻断式的梯度下降方式,每轮梯度下降由最慢的节点决定。从Spark 梯度下降的源码中可知,SparkMLlib的minibatch过程是在所有节点计算完各自的梯度之后,逐层聚合(aggregate),最终汇总生成全局的梯度。也就是说,如果数据倾斜等问题导致某个节点计算梯度的时间过长,那么这一过程将阻断其他所有节点,使其无法执行新的任务。这种同步阻断的分布式梯度计算方式,是SparkMLlib并行训练效率较低的主要原因。
梯度下降过程是由所有节点计算完汇总成全局梯度然后下降,因此时间由最慢的节点决定 -
Spark MLlib并不支持复杂深度学习网络结构和大量可调超参。事实上,SparkMLlib在其标准库里只支持标准的MLP的训练,并不支持RNN、LSTM等复杂网络结构,而且无法选择不同的激活函数等大量超参。这就导致SparkMLlib在支持深度学习方面的能力欠佳。
由此可见,如果想寻求更高效的训练速度和更灵活的网络结构,势必需要寻求其他平台的帮助。在这样的情况下,ParameterServer凭借其高效的分布式训练手段成为分布式机器学习的主流,而TensorFlow、PyTorch等深度学习平台则凭借灵活、可调整的网络结构,完整的训练、上线支持,成为深度学习平台的主要选择。接下来,Pata meter Server Tensor Flow。
推荐模型离线训练之 Parameter Server
上节对SparkMLlib的并行训练方法做了详细的介绍,Spark采取简单直接的数据并行的方法解决模型并行训练的问题,但Spark的并行梯度下降方法是同步阻断式的,且模型参数需通过全局广播的形式发送到各节点,因此Spark的并行梯度下降过程是相对低效的。
为了解决相应的问题,2014年分布式可扩展的ParameterServer方案被提出,几乎完美地解决了机器学习模型的分布式训练问题。时至今日,ParameterServer不仅被直接应用在各大公司的机器学习平台上,也被集成在TensorFlow、MXNet等主流的深度学习框架中,作为机器学习分布式训练重要的解决方案。
Parameter Server 的分布式训练原理
先以通用的机器学习问题为例,解释ParameterServer分布式训练的原理。
F ( w ) = ∑ i = 1 n ℓ ( x i , y i , w ) + Ω ( w ) F(w)=\sum_{i=1}^{n}\ell(x_{i},y_{i},w)+\Omega(w) F(w)=i=1∑nℓ(xi,yi,w)+Ω(w)
上式是一个通用的带正则化项的损失函数,其中 n n n 是样本总数, l ( x , y , w ) l(x,y,w) l(x,y,w) 是计算单个样本的损失函数, x x x 是特征向量, y y y 是样本标签, w w w 是模型参数。模型的训练目标就是使损失函数 F ( w ) F(w) F(w) 最小。为了求解 arg ( min F ( w ) ) \arg(\operatorname*{min}F(w)) arg(minF(w)) ,往往使用梯度下降法。ParameterServer的主要作用就是并行进行梯度下降的计算,完成模型参数的更新直至最终收敛。需要注意的是,公式中正则化项的存在需要汇总所有模型参数才能正确计算,较难进行模型参数的完全并行训练,因此ParameterServer采取了和SparkMLlib一样的数据并行训练产生局部梯度,再汇总梯度更新参数权重的并行化训练方案。
具体地讲,下面代码以伪码的方式列出了ParameterServer并行梯度下降的主要步骤。
Task Scheduler:// 总体并行训练流程
issue LoadData() to all workers // 分发数据到每一个worker节点
for iteration t = 0 to T do
// 每个worker节点并行执行 WORKERITERATE 方法,共 T 轮
Issue WORKERITERATE(t) to all Workers
end for
Worker r = 1 to m:
function LOADDATA()// worker 初始化过程
load a part of training data (x_i, y_i) // 每个 worker 载入部分训练数据
pull the working set w_r // 每个 worker 从 server 端拉取相关初始模型参数
end function
function WORKERITERATE(t)// worker 节点的迭代计算过程
compute gradient g_t // 仅利用本节点数据计算梯度
push gradient g_t to server // 将计算好的梯度 push 到 server 端
pull the working set w_(t+1) // 从 server 端拉取新一轮模型参数
end function
Servers:
function SERVERITERATE(t)// server 节点迭代计算过程
// 在收到 m 个 worker 节点计算的梯度后,汇总形成总梯度
w(t+1) = w(t) - n * (g(t) + λ * w(t))
// 利用汇总梯度,融合正则化项梯度,计算出新梯度
end function
可以看出,Parameter Server server worker,其主要功能如下:
- server节点的主要功能是保存模型参数、接受worker节点计算出的局部梯度、汇总计算全局梯度,并更新模型参数。
- worker节点的主要功能是保存部分训练数据,从server节点拉取最新的模型参数,根据训练数据计算局部梯度,上传给server节点。
在物理架构上,Parameter Server 和 Spark 的 manager-worker 架构基本一致,如图所示。
Parameter Server
可以看到,Parameter Server:服务器节点组(server group)和多个工作节点组(worker group)。资源管理中心(resource manager)负责总体的资源分配调度。
服务器节点组内部包含多个服务器节点(servernode),每个服务器节点负责维护一部分参数,服务器管理中心(servermanager)负责维护和分配server资源。
每个工作节点组对应一个Application(即一个模型训练任务),工作节点组之间,以及工作节点组内部的任务节点之间并不通信,任务节点只与server通信。
Parameter Server,Parameter Server如图所示。
Parameter Server
Parameter Server 的并行梯度下降流程中最关键的两个操作是 push 和 pull
push操作:worker节点利用本节点上的训练数据,计算好局部梯度,上传给server节点。
pull操作:为了进行下一轮的梯度计算,worker节点从server节点拉取最新的模型参数到本地。
概括整个ParameterServer的分布式训练流程如下:
(1)每个worker载人一部分训练数据。
(2)worker节点从server节点拉取(pull)最新的相关模型参数。
(3)worker节点利用本节点数据计算梯度。
(4)worker节点将梯度推送到server节点。
(5)server节点汇总梯度更新模型。
(6)跳转到第2步,直到迭代次数达到上限或模型收敛。
一致性与并行效率之间的取舍
在总结Spark的并行梯度下降原理时,笔者曾提到Spark并行梯度下降效率较低的原因是“同步阻断式”的并行梯度下降过程。
这种并行梯度下降过程需要所有节点的梯度都计算完成,由master节点汇总梯度,计算好新的模型参数后才能开始下一轮的梯度计算。这就意味着最“慢的节点会阻断其他所有节点的梯度更新过程。
另外,“同步阻断式”的并行梯度下降是“一致性”最强的梯度下降方法,因为其计算结果与串行梯度下降的计算结果严格一致。
那么,有没有在兼顾一致性的前提下,提高梯度下降并行效率的方法呢?
ParameterServer用“异步非阻断式”的梯度下降替代原来的“同步阻断式方法。下图所示为一个worker节点多次选代计算梯度的过程。可以看到节点在做第11次迭代(iter11)计算时,第10次选代后的push&pull过程并没有结束,也就是说,最新的模型权重参数还没有被拉取到本地,该节点仍在使用iter10的权重参数计算iter11的梯度。这就是所谓的异步非阻断式梯度下降法,其他节点计算梯度的进度不会影响本节点的梯度计算。所有节点始终都在并行工作,不会被其他节点阻断。
一个worker节点多次迭代计算梯度的过程
当然,任何技术方案都有“取”也有“舍”,异步梯度更新的方式虽然大幅加快了训练速度,但带来的是模型一致性的损失。也就是说,并行训练的结果与原来的单点串行训练的结果是不一致的,这样的不一致会对模型收敛的速度造成一定影响。所以最终选取同步更新还是异步更新取决于不同模型对一致性的敏感程度。这类似于一个模型超参数选取的问题,需要针对具体问题进行具体的验证。
除此之外,在同步和异步之间,还可以通过设置“最大延迟”等参数限制异步计算的程度。例如,可以限定在三轮迭代之内,模型参数必须更新一次。如果某worker节点计算了三轮梯度,还未完成一次从server节点拉取最新模型参数的过程,那么该worker节点就必须停下等待pull操作的完成。这是同步和异步之间的折中方法。
本节介绍了并行梯度下降方法中“同步”更新和“异步”更新之间的区别。在效果上,读者肯定关心下面两个指标:
(1)“异步”更新到底能够节省多少阻断时间(waitingtime)。
(2)“异步”更新会降低梯度更新的一致性,这是否会让模型的收敛时间变长。
针对上面两点疑问,ParameterServer论文的原文中提供了异步和同步更新的效率对比(基于Sparselogisticregression(稀疏逻辑回归)模型训练),对比了梯度同步更新策略和ParameterServer采取的异步更新策略的计算(computing)时间和阻断(waiting)时间;对比了不同策略的收敛速度。
不同策略的计算时间和阻断时间对比
System-A和System-B都是同步更新梯度的系统,ParameterServer是异步更新的策略,可以看出ParameterServer的计算时间占比远高于同步更新策略,这证明ParameterServer的计算效率有明显提高。
不同策略的收敛速度
可以看出,异步更新的ParameterServer的收敛速度比同步更新的System-A和System-B快,这证明异步更新带来的不一致性问题的影响没有想象中那么大。
多server节点的协同和效率问题
导致SparkMLlib并行训练效率低下的另一个原因是每次迭代都需要master节点将模型权重参数的广播发送到各worker节点。这导致两个问题:
(1)master节点作为一个瓶颈节点,受带宽条件的制约,发送全部模型参数的效率不高。
(2)同步地广播发送所有权重参数,使系统的整体网络负载非常大。
那么,ParameterServer如何解决单点master效率低下的问题呢?从架构图中可知,ParameterServer采用了服务器节点组内多server的架构,每个server主要负责部分模型参数。模型参数使用key-value的形式,因此每个server负责一个参数键范围(keyrange)内的参数更新就可以了。
那么另一个问题来了,每个server是如何决定自己负责哪部分参数范围呢?如果有新的server节点加人,那么如何在保证已有参数范围不发生大的变化的情况下加人新的节点呢?这两个问题的答案涉及一致性哈希(consistenthashing)的原理。ParameterServer节点组成的一致性哈希环如图所示。
ParameterServer节点组成的一致性哈希环
在ParameterServer的服务器节点组中,应用一致性哈希管理参数的过程大致有如下几步:
(1)将模型参数的key映射到一个环形的哈希空间,比如有一个哈希函数可以将任意key映射到 0 ∼ ( 2 32 ) − 1 \scriptstyle0\sim(2^{32})-1 0∼(232)−1 的哈希空间内,只要让 ( 2 32 ) (2^{32}) (232) -1这个桶的下一个桶是0这个桶,这个空间就变成了一个环形哈希空间。
(2)根据server节点的数量 n n n ,将环形哈希空间等分成nm个范围,让每个 server 间隔地分配 m m m 个哈希范围。这样做的目的是保证一定的负载均衡性,避免哈希值过于集中带来的server负载不均。
(3)在新加人一个server节点时,让新加人的server节点找到哈希环上的插入点,让新的server负责插人点到下一个插人点之间的哈希范围,这样做相当于把原来的某段哈希范围分成两份,新的节点负责后半段,原来的节点负责前半段。这样不会影响其他哈希范围的哈希分配,自然不存在大量的重哈希带来的数据大混洗的问题。
(4)删除一个server节点时,移除该节点相关的插人点,让临近节点负责该节点的哈希范围。
在ParameterServer的服务器节点组中应用一致性哈希原理,可以非常有效地降低原来单master节点带来的瓶颈问题。在应用一致性哈希原理后,当某worker节点希望拉取新的模型参数时,该节点将发送不同的“范围拉取请求”(rangepull)到不同的server节点,之后各server节点可以并行地发送自己负责的权重参数到该worker节点。
此外,由于在处理梯度的过程中server节点之间也可以高效协同,某worker节点在计算好自己的梯度后,只需要利用范围推送(rangepush)操作把梯度发送给一部分相关的server节点。当然,这一过程也与模型结构相关,需要跟模型本身的实现结合。总的来说,ParameterServer基于一致性哈希提供了参数范围拉取和参数范围推送的能力,让模型并行训练的实现更加灵活。
Parameter Server 技术要点总结
ParameterServer实现分布式机器学习模型训练的要点如下:
- 用异步非阻断式的分布式梯度下降策略替代同步阻断式的梯度下降策略。·
- 实现多server节点的架构,避免单master节点带来的带宽瓶颈和内存瓶颈。·
- == 使用一致性哈希、参数范围拉取、参数范围推送等工程手段实现信息的最小传递,避免广播操作带来的全局性网络阻塞和带宽浪费==。
ParameterServer仅仅是一个管理并行训练梯度的权重平台,并不涉及具体的模型实现,因此 Parameter Server 往往作为 MXNet、TensorFlow 的一个组件,要想具体实现一个机器学习模型,还需要依赖通用的、综合性的机器学习平台。下一节将介绍以TensorFlow为代表的机器学习平台的工作原理。
推荐模型离线训练之 TensorFlow
深度学习的应用在各领域日益深入,各大深度学习平台的发展也突飞猛进。Tensor Flow、亚马逊的MXNet、Facebook Py Torch,微软的CNTK 等均是各大科技巨头推出的深度学习框架。与ParameterServer主要聚焦在模型并行训练这一点上不同,各大深度学习框架囊括了模型构建、并行训练、上线服务等几乎所有与深度学习模型相关的步骤。本节以TensorFlow为主要对象,介绍深度学习框架的模型训练原理,特别是并行训练的技术细节。
TensorFlow的基本原理
TensorFlow的中文名为“张量流动”,这也非常准确地表达了TensorFlow的基本原理一根据深度学习模型架构构建一个有向图,让数据以张量的形式在其中流动起来。
张量(tensor)其实是矩阵的高维扩展,矩阵可以看作张量在二维空间上的特例。在深度学习模型中,大部分数据是以矩阵甚至更高维的张量表达的,因此谷歌为其深度学习平台取名为张量流动(tensorflow)再合适不过。
为了让张量流动起来,对于每一个深度学习模型,需要根据其结构建立一个由点和边组成的有向图,其中每个点代表着某种操作,比如池化(pooling)操作、激活函数等。每个点可以接收0个或多个输人张量,并产生0个或多个输出张量。这些张量就沿着点之间的有向边的方向流动,直到最终的输出层。
下图所示为一个简单的TensorFlow有向图。可以看出向量 b \pmb{b} b 、矩阵 W \pmb{W} W 向量 x \bm x x 是模型的输人,紫色的节点MatMul、Add、ReLU是操作节点,分别代表了矩阵乘、向量加、ReLU激活函数等操作。模型的输人张量 W \bm W W、 b \pmb{b} b、 x \bm x x 经过操作节点的处理变形之后,在点之间流动。
一个简单的TensorFlow有向图
事实上,任何复杂模型都可以转化为操作有向图的形式。这样做不仅有利于操作的模块化,以及定义和实现模型结构的灵活性,而且可以厘清各操作间的依赖关系,有利于判定哪些操作可以并行执行,哪些操作只能串行执行,为并行平台能够最大程度地提升训练速度打下基础。
TensorFlow基于任务关系图的并行训练过程
构建了由各“操作”构成的任务关系图,TensorFlow就可以基于任务关系图进行灵活的任务调度,以最大限度地利用多GPU或者分布式计算节点的并行计算资源。利用任务关系图进行调度的总原则是,存在依赖关系的任务节点或者子图(subgraph)之间需要串行执行,不存在依赖关系的任务节点或者子图之间可以并行执行。具体地讲,TensorFlow使用了一个任务队列来解决依赖关系调度问题。笔者以TensorFlow的一个官方任务关系图为例(如下图所示)进行具体原理的说明。
如图所示,图中将最原始的操作节点关系图进一步处理成了由操作节点(Operationnode)和任务子图组成的关系图。其中,子图是由一组串行的操作节点组成的。由于是纯串行的关系,所以在并行任务调度中可被视作一个不可再分割的任务节点。
在具体的并行任务调度过程中,TensorFlow维护了一个任务队列。当一个任务的前序任务全部执行完时,就可以将当前任务推送到任务队列尾。有空闲计算节点时,该计算节点就从任务队列中拉取出一个队首的任务进行执行。
仍以下图为例,在Input节点之后,Operation 1 和 Operation 3 会被同时推送到任务队列中,这时如果有两个空闲的GPU计算节点,Operation1和Operation 3会被拉取出,并进行并行执行。在Operation 1执行结束后,Subgraph1和Subgraph2会被先后推送到任务队列中串行执行。在Subgraph2执行完毕后,Operation 2的前序依赖被移除,Operation 2被推送到任务队列中,Operation 4的前序依赖是Subgraph2和Operation3,只有当这两个前序依赖全部执行完才会被推送到任务队列中。当所有计算节点上的任务都被执行完毕并且任务队列中已经没有待处理的任务时,整个训练过程结束。
TensorFlow官方给出的任务关系图示例
可以看出,TensorFlow的任务关系图与Spark的DAG任务关系图在原理上有相通之处,不同之处在于Spark DAG的作用是厘清任务的先后关系,任务的粒度还停留在join、reduce等粗粒度操作级别,Spark的并行机制更多是任务内部的并行执行;而TensorFlow的任务关系图则把任务拆解到非常细粒度的操作级别,通过并行执行互不依赖的子任务加速训练过程。 TensorFlow的任务关系图将任务拆解到非常细的粒度,任务被分解为非常小的操作单元,比如单个矩阵乘法、激活函数计算,而Spark的DAG(有向无环图)是厘清任务的先后关系,粒度停留在join、reduce等粗粒度的操作级别,更多的是任务内部的并行执行。
TensorFlow的单机训练与分布式训练模式
TensorFlow的计算平台也分为两种不同的模式,一种是单机训练,另一种是多机分布式训练。对单机训练来说,虽然执行过程中也包括CPU、GPU的并行计算过程,但总体上处于共享内存的环境,不用过多考虑通信问题;而多机分布式训练指的是多台不共享内存的独立计算节点组成的集群环境下的训练方法。计算节点间需要依靠网络通信,因此可以认为这是与上节介绍的ParameterServer相似的计算环境。
如图所示,TensorFlow的单机训练是在一个worker节点上进行的,单worker节点内部按照任务关系图的方式在不同GPU+CPU节点间进行并行计算;对分布式环境来说,平台存在多worker节点,如果采用TensorFlow 的 ParameterServer策略(tf.distribute.experimental.Parameter Server Strategy),则各worker节点会以数据并行的方式进行训练。也就是说,各worker节点以同样的任务关系图的方式进行训练,但训练数据不同,产生的梯度以ParameterServer的方式汇总更新。
TensorFlow的单机和分布式训练环境
这里介绍每个worker节点内部CPU和GPU的具体任务分工。GPU拥有多核优势,因此在处理矩阵加、向量乘等张量运算时,相比CPU拥有巨大优势。在处理一个任务节点或任务子图时,CPU主要负责数据和任务的调度,而GPU则负责计算密集度高的张量运算。
举例来说,在处理两个向量的元素乘操作时,CPU会居中调度,把两个向量对应范围的元素发送给GPU处理,再收集处理结果,最终生成处理好的结果向量。从这个角度讲,CPU+GPU的组合也像是一个“简化版”的ParameterServer。
TensorFlow 技术要点总结
TensorFlow的基本运行模式和任务拆解过程,以及并行化训练的原理的技术要点如下:
(1)TensorFlow直译为“张量流动”,主要原理是将模型训练过程转换成任务关系图,让数据以张量的形式在任务关系图中流动,完成整个训练。
(2)TensorFlow基于任务关系图进行任务调度和并行计算。
(3)对于分布式TensorFlow来说,其并行训练分为两层,一层是基于ParameterServer架构的数据并行训练过程;另一层是每个worker节点内部,CPU+GPU任务级别的并行计算过程。
深度学习推荐模型的上线部署
前几节介绍了深度学习推荐模型的离线训练平台,无论是TensorFlow、PyTorch,还是传统的SparkMLlib,都提供了比较成熟的离线并行训练环境。但推荐模型终究要在线上环境使用,如何将离线训练好的模型部署在线上的生产环境,进行线上实时推断,一直是业界的一个难点。本节将介绍在完成模型的离线训练后,部署推荐模型的主流方法。
预存推荐结果或 Embedding 结果
对于推荐系统线上服务来说,最简单直接的方法就是在离线环境下生成每个用户的推荐结果,然后将结果预存到Redis等线上数据库中。在线上环境直接取出预存数据推荐给用户即可。该方法的优缺点都非常明显,其优点如下。
(1)无须实现模型线上推断的过程,线下训练平台与线上服务平台完全解耦可以灵活地选择任意离线机器学习工具进行模型训练。
(2)线上服务过程没有复杂计算,推荐系统的线上延迟极低。
该方法的缺点如下。
(1)由于需要存储用户x物品x应用场景的组合推荐结果,在用户数量、物品数量等规模过大后,容易发生组合爆炸的情况,线上数据库根本无力支撑如此大规模结果的存储。
(2)无法引入线上场景类特征,推荐系统的灵活性和效果受限。
由于以上优缺点的存在,直接存储推荐结果的方式往往只适用于用户规模较小,或者一些冷启动、热门榜单等特殊的应用场景中。
直接预计算并存储用户和物品的Embedding是另一种线上“以存代算”的方式。相比直接存储推荐结果,存储Embedding的方式大大减少了存储量,线上也仅需做内积或余弦相似度运算就可以得到最终推荐结果,是业界经常采用的模型上线手段。
这种方式同样无法支持线上场景特征的引入,并且无法进行复杂模型结构的线上推断,表达能力受限,因此对于复杂模型,还需要从模型实时线上推断的角度人手。
自研模型线上服务平台
无论是在数年前深度学习刚兴起的时代,还是TensorFlow、PyTorch已经大行其道的今天,自研机器学习训练与线上服务的平台仍然是很多大、中型公司的重要选项。
为什么放着灵活且成熟的TensorFlow不用,而要从头进行模型和平台自研呢?
一个重要的原因是:TensorFlow等通用平台为了灵活性和通用性的要求,需要支持大量冗余的功能,导致平台过重,难以修改和定制。而自研平台的好处是可以根据公司业务和需求进行定制化的实现,并兼顾模型服务的效率。
另一个原因是当模型的需求比较特殊时,大部分深度学习框架无法支持。例如,某些推荐系统召回模型、“探索与利用”模型、与推荐系统具体业务结合得非常紧密的冷启动等算法,它们的线上服务方法一般也需要自研。
自研平台的端显而易见。由于实现模型的时间成本较高,自研一到两种模型是可行的,做到数十种模型的实现、比较和调优则很难。而在新模型层出不穷的今天,自研模型的选代周期过长。因此,自研平台和模型往往只在大公司采用,或者在已经确定模型结构的前提下,手动实现模型推断过程的时候采用。
预训练Embedding+轻量级线上模型
完全采用自研模型存在工作量大和灵活性差的问题,在各类复杂模型演化迅速的今天,自研模型的端更加明显,那么有没有能够结合通用平台的灵活性、功能的多样性,以及自研模型线上推断高效性的方法呢?答案是肯定的。
业界的很多公司采用了“复杂网络离线训练、生成Embedding存入内存数据库、线上实现逻辑回归或浅层神经网络等轻量级模型拟合优化目标”的上线方式。之前曾介绍过的“双塔”模型是非常典型的例子。
双塔模型分别用复杂网络对“用户特征”和“物品特征”进行了Embedding化,在最后的交叉层之前,用户特征和物品特征之间没有任何交互,这就形成了两个独立的“塔”,因此称为双塔模型。
在完成双塔模型的训练后,可以把最终的用户Embedding和物品Embedding存人内存数据库。而在进行线上推断时,也不用复现复杂网络,只需要实现最后输出层的逻辑即可。这里的输出层大部分情况下就是逻辑回归或softmax,也可以使用复杂一点的浅层神经网络。但无论选择哪种神经网络,线上实现的难度并不大。在从内存数据库中取出用户Embedding和物品Embedding之后,通过输出层的线上计算即可得到最终的预估结果。
在这样的架构下,还可以在输出层之前把用户Embedding和物品Embedding,以及一些场景特征连接起来,使模型能够引人线上场景特征,丰富模型的特征来源。
在 Graph Embedding 技术已经非常强大的今天,Embedding离线训练的方法已经可以融入大量用户和物品信息,输出层并不用设计得过于复杂,因此采用Embedding + + + 轻量级线上模型的方法进行模型服务,不失为一种灵活简单且不过多影响模型效果的方法。
利用PMML转换并部署模型
Embedding+轻量级模型的方法是实用且高效的,但无论如何还是割裂了模型。无法实现End2End训练 + + + End2End部署这种最完美的方式。有没有在离线训练完模型之后,直接部署模型的方式呢?本节介绍一种脱离平台的通用的模型部署方式一PMML。
PMML的全称是“预测模型标记语言”(Predictive Model Markup Language,PMML),是一种通用的以XML的形式表示不同模型结构参数的标记语言。在模型上线的过程中,PMML经常作为中间媒介连接离线训练平台和线上预测平台。
这里以SparkMLlib模型的训练和上线过程为例解释PMML在整个机器学习模型训练及上线流程中扮演的角色(如图所示)。
Spark MLlib 模型利用 PMML 的上线过程
图中的例子使用JPMML作为序列化和解析PMML文件的library(库)。JPM ML 项目分为 Spark 和 Java Server 两部分。Spark 部分的 library 完成 SparkMLlib 模型的序列化,生成PMML文件并保存到线上服务器能够触达的数据库或文件系统中;JavaServer部分则完成PMML模型的解析,并生成预估模型,完成与业务逻辑的整合。
JPMML在JavaServer部分只进行推断,不考虑模型训练、分布式部署等一系列问题,因此library比较轻,能够高效地完成推断过程。与JPMML相似的开源项目还有MLeap,同样采用了PMML作为模型转换和上线的媒介。
事实上,JPMML 和 MLeap 也具备 Scikit-learn、TensorFlow 中简单模型转换和上线能力。但针对TensorFlow中的复杂模型,PMML语言的表达能力是不够的,因此上线 TensorFlow 模型就需要 TensorFlow 的原生支持——Tensor FlowServing。
Tensor Flow Serving
TensorFlow Serving 是 TensorFlow 推出的原生的模型服务器。本质上讲,TensorFlow Serving的工作流程和PMML类工具的流程是一致的。不同之处在于,TensorFlow定义了自己的模型序列化标准。利用TensorFlow自带的模型序列化函数可将训练好的模型参数和结构保存至某文件路径。
TensorFlow Serving 最普遍也是最便捷的服务方式是使用 Docker 建立模型服务API。在准备好Docker环境后,仅需要通过拉取镜像的方式(pull image)即可完成TensorFlow Serving环境的安装和准备:
docker pull tensorflow/serving
在启动该dockercontainer后,也仅需一行命令即可启动模型服务API:
tensor flow model server--port=8500 --rest_api_port=8501 \
--model_name=$(MODEL_NAME} --model_base_path=S{MODEL_BASE_ PATH}/S{MODEL_NAME)
这里仅需注意之前保存模型的路径。
当然,要搭建一套完整的TensorFlow Serving服务并不是一件容易的事情,因为其中涉及模型更新、整个docker container集群的维护和按需扩展等一系列工程问题。TensorFlow Serving的性能问题仍被业界垢病,但它的易用性和对复杂模型的支持,使其成为上线TensorFlow模型的第一选择。
灵活选择模型服务方法
深度学习推荐模型的线上服务问题是非常复杂的工程问题,因为其与公司的线上服务器环境、硬件环境、离线训练环境、数据库/存储系统等有非常紧密的联系。正因为这样,各家采取的方式也各不相同。在这个问题上,即使本节已经列出了5种主要的上线方法,也无法囊括业界的所有推荐模型上线方式,甚至在一个公司内部,针对不同的业务场景,模型的上线方式也不尽相同。
因此,作为一名算法工程师,除了应对主流的模型服务方式有所了解,还应对公司客观的工程环境进行综合权衡,给出最适合的解决方案。
工程与理论之间的权衡
工程和理论往往是解决技术问题过程中矛盾又统一的两面。理论依赖工程的实现,脱离了工程的理论是无法发挥实际作用的空中楼阁;而工程又制约着理论的发展,被装在工程框架下的理论往往需要进行一些权衡和取舍才能落地。本节希望与读者讨论的是在推荐系统领域,如何在工程与理论之间进行权衡。
工程师职责的本质
工程和理论的权衡是工程师不得不考虑的问题,对这个问题的思考决定了一名工程师应具备的是“工程思维”,而不是学者具备的“研究思维”。推荐系统是一个工程性极强,以技术落地为首要目标的领域,“工程思维”的重要性不言而喻。接下来,笔者会站在工程师的角度阐述如何在工程和理论之间进行权衡。
无论你是算法工程师,还是研发工程师,甚至是设计电动汽车、神舟飞船、长征火箭的工程师,职责都是相同的,那就是:在现有实际条件的制约下,以工程完成和技术落地为目标,寻找并实现最优的解决方案。
回到推荐系统中来,这里的“现有实际条件的制约”可以是来自研发周期的制约、软硬件环境的制约、实际业务逻辑和应用场景的制约,也可以是来自产品经理的优化目标的制约,等等。正因这些制约的存在,一名工程师不可能像学术界的研究人员一样任意尝试新的技术,做更多探索性的创新。
也正是因为工程师永远以“技术落地”为目标,而不是炫耀自己的新模型、新技术是否走在业界前沿,所以在前沿理论和工程现实之间做权衡是一名工程师应该直有的基本素质。下面笔者用三个实际的案例帮助读者体会如何在实际工程中进行技术上的权衡。
Redis容量和模型上线方式之间的权衡
对线上推荐系统来说,为了进行在线服务,需要获取的数据包括两部分模型参数和线上特征。为了保证这两部分数据的实时性,很多公司采用内存数据库的方式实现,Redis自然是最主流的选择。但Redis需要占用大量内仔资源,而内存资源相比存储资源和计算资源文是比较稀缺和昂贵的资源,因此无论是用Aws(AmazonWebServices,亚马逊网络服务平台)、阿里云,还是自建数据中心,使用Redis的成本都比较高,Redis的容量就成了制约推荐模型上线方式的关键因素。
在这个制约因素的限制下,工程师要从两个方面考虑问题。
(1)模型的参数规模要尽量小,特别是对深度学习推荐系统而言,模型的参数量极较传统模型有了几个量级的提升,更应该着重考虑模型的参数规模。
(2)线上预估所需的特征数量不能无限制地增加,要根据重要性做一定程度的取舍。
在这样的制约因素下上线推荐系统,必然需要舍弃一些次要的要素,关注主要矛盾。一名成熟的工程师的思路应该是这样的:
(1)对于千万甚至更高量级的特征维度,理论上参数的数量级也在千万量级,线上服务是很难支持这种级别的数据量的,这就要求工程上关注模型的稀疏性,关注主要特征,舍弃大量次要特征,舍弃一定的模型预测准确度,提升线上预估 的速度,减小工程资源消耗。
(2)增强模型的稀疏性的关键技术点有哪些?加入L1正则化项,采用FTRL等稀疏性强的训练方法。
(3)实现目标的技术途径有多种,在无法确定哪种技术效果更佳的情况下,实现所有备选方案,通过离线和在线的指标进行比较观察。
(4)根据数据确定最终的技术途径,完善工程实现。
以上是模型侧的“瘦身”方法,针对在线特征的“瘦身”计划当然可以采用同样的思路。首先采用“主成分分析”等方法进行特征筛选,在不显著降低模型效果的前提下减少所用的特征。针对不好取舍的特征,进行离线评估和线上A/B测试,最终达到工程上可以接受的水平。
研发周期限制和技术选型的权衡
在实际的工程环境中,研发周期的制约同样是不可忽视的因素。这就涉及工程师对项目整体的把控能力和对研发周期的预估能力。在产品选代日益迅速的互联网领域,没人愿意成为拖累其他团队的最慢一环。
书籍作者曾经经历过多次产品和技术平台的大规模升级。在技术平台升级的过程中,要充分权衡产品新需求和技术平台整体升级的进度。例如,公司希望把机器学习平台从Spark整体迁移到TensorFlow,这是顺应深度学习浪潮的技术决策,但由于Spark平台自身的特性,编程语言、模型训练方式和TensorFlow有较大差别,整个迁移必然要经历一个较长的研发周期。在迁移的过程中,如果有新的产品需求,就需要工程师做出权衡,在进行技术升级的过程中兼顾日常的开发进度。
这里可能的技术途径有两个:
(1)集中团队的力量完成Spark到TensorFlow的迁移,在新平台上进行新模型和新功能的研发。
(2)团队一部分成员利用成熟稳定的Spark平台快速满足产品需求,为TensorFlow的迁移、调试、试运行留足充分的时间。与此同时,另一部分成员则全力完成TensorFlow的相关工作,力保在大规模迁移之前新平台的成熟度。
单纯从技术角度考虑,既然已经决定升级到TensorFlow平台,理论上没必要再花时间利用Spark平台研发新模型。这里需要搞清楚的问题有两个。
(1)再成熟的平台也需要整个团队磨合调试较长时间,绝不可能刚迁移至TensorFlow就让它支持重要的业务逻辑。
(2)技术平台的升级换代应作为技术团队的内部项目,最好对其他团队透明,不应成为减缓对业务支持的直接理由。
因此、从工程进度和风险角度考虑,第2个技术途径应成为更符合工程头际和公司实际的选择。
硬件平台环境和模型结构间的权衡
几平所有算法工程师都有过类似的抱怨一“公司的平台资源太少,训练一个模型要花将近一天的时间”。当然,“大厂”的资源相对充足,“小厂”囿于研发成本的限制更容易受到硬件平台环境的制约。但无论什么规模的公司,硬件资源总归是有限的,因此要学会在有限的硬件资源条件下优化模型相关的一切工程实现。
这里的“优化”实际上包括两个方面:
一方面是程序本身的优化。经常遇到一些实习生抱怨Spark跑得太慢,究其原因,是因为他们对Spark的shuffle机制没有深入了解,写的程序包含大量触发shuffle的操作,容易导致大量的数据倾斜问题。这样的问题本身并不涉及技术上的“权衡”,而是应该夯实自己的技术功底,尽量通过技术上的“优化”提升模型的训练效率和实时性。
另一方面的优化就需要一些技术上的取舍了。能否通过优化或者简化模型的结构大幅提升模型训练的速度,降低模型训练的消耗,提升推荐模型的实时性呢?典型的案例已经提到。在深度学习模型中,模型的整体训练收敛速度和模型的参数数量有很强的相关性,而模型的参数数量中输人层到Embedding层的数量占绝大部分。因此,为了大幅加快模型的训练速度,可以将Embedding部分单独抽取出来做预训练,这样可以做到上层模型的快速收敛。当然,这样的做法舍弃了End2End训练的一致性,但在硬件条件制约的情况下,增强模型实时性的收益可能远大于End2End训练带来的模型一致性收益。
其他类似的例子还包括简化模型结构的向题。如果通过增加模型复杂性(例如增加神经网络层级,增加每层神经元的数量)带来的收益已经趋于平缓,就没有必要浪费过多硬件资源做微乎其微的效果提升,而是应该把优化方向转换到提升系统实时性,挖掘其他有效信息,为模型引入更有效的网络结构等方面。
原文地址:https://blog.csdn.net/a_blade_of_grass/article/details/143743713
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!