体系详解:输入层、线性层、Join 层与损失层)
人工智能机器学习分布式训练图计算后端【免费下载链接】angelA Flexible and Powerful Parameter Server for large-scale machine learning项目地址https://gitcode.com/gh_mirrors/an/angel点击查看免费下载本文以 Angel 的 计算图AngelGraph 为基础系统讲解图中节点的核心抽象——层Layer。Angel 中的绝大多数算法如 FM、DeepFM、PNN、NFM、DNN 等都由层组装而成理解层的分类、构造函数与 JSON 配置方式是使用 Angel 搭建自定义深度模型的前提。读完本文你将掌握层的三类拓扑结构verge / linear / join、输入层与特征交叉层的数学含义、各层的 JSON 配置写法以及层在源码层面的参数存储与训练机制。1. 层与计算图三类拓扑结构Angel 中的大部分算法都是基于计算图构建的图中的节点即为层layer。按层的拓扑结构可将层分为三类verge边缘节点只有输入或只有输出的层例如输入层与损失层。输入层主要有SimpleInputLayer、Embedding损失层主要有SimpleLossLayer、SoftmaxLossLayer。linear线性层有且仅有一个输入与一个输出的层。全连接层即FCLayer特征交叉层这类层较多不同算法使用不同的特征交叉方式也可以组合使用主要有BiInnerCrossPNN(inner) 中使用的特征交叉方式BiOutterCrossPNN(outter) 中使用的特征交叉方式目前未实现BiInnerSumCrossFM 中使用的二阶特征隐式交叉方式BiInteractionCrossNFM 中使用的特征交叉方式。join聚合层有两个或多个输入、一个输出的层这类层也较多主要有ConcatLayer将多个输入层拼接起来输出一个 Dense 矩阵SumPooling将输入元素对应相加后输出MulPooling将输入元素对应相乘后输出DotPooling先将对应元素相乘然后按行相加输出 n 行一列的矩阵。从源码结构看这三类层分别落在 angel-ps/mllib/src/main/scala/com/tencent/angel/ml/core/network/layers 下的verge、linear、join三个子目录中并由 Layer.scala 定义统一基类。1.1 层的通用机制基类与状态机所有层都继承自 Layer.scala 中的抽象类Layer(name, outputDim)其内部通过STATUS枚举维护前向/反向状态object STATUS extends Enumeration { type STATUS Value val Null, Forward, Backward, Gradient, Update Value }一次训练迭代中层的状态严格按Null → Forward → Backward → Gradient → Update流转calOutput()计算前向输出calBackward()/calGradOutput()计算反向梯度pushGradient()推送梯度update()调用优化器更新参数。基类还维护了input与consumer两个列表用于在计算图中建立层与层之间的连接关系。需要训练参数的层会混入Trainabletrait它要求实现pullParams(epoch)、pushGradient()、update(epoch, batchSize)、init(taskId)、loadParams/saveParams等接口损失层则混入LossLayertrait提供predict()、calLoss()、getLossFunc()等能力。这些接口在后续各层的源码分析中会反复出现。2. 输入层vergeAngel 中的输入层有两类SimpleInputLayer与Embedding。2.1 SimpleInputLayer顾名思义它是直接接受输入的层构造函数如下见 SimpleInputLayer.scalaclass SimpleInputLayer(name: String, outputDim: Int, transFunc: TransFunc, override val optimizer: Optimizer)(implicit graph: AngelGraph) extends InputLayer(name, outputDim)(graph) with Trainable它的主要特点为接收稠密/稀疏输入当输入是稠密时内部参数是稠密的参数连续存储于一个数组调用 BLAS 库完成计算当输入是稀疏时内部参数用 RowBasedMatrix 存储每行都是一个稀疏向量计算用 Angel 内部数学库需要指定outputDim可以指定传输函数transFunc和优化器optimizer。完成的计算用公式表达为$$f(x) \text{transFunc}(x \cdot \mathbf{w} \text{bias})$$一种典型的 JSON 表达为{ name: wide, type: Simpleinputlayer, outputdim: 10, transfunc: identity, optimizer: adam }源码纵深参数矩阵如何设计在源码实现中SimpleInputLayer会在 PS参数服务器上创建两个矩阵${name}_weight与${name}_bias。权重矩阵的形状由「输入数据格式 × 模型存储类型」的组合决定对应 SimpleInputLayer.scala输入数据格式模型存储类型权重矩阵形状说明densedense/component_dense(numSlot1) × (indexRange × outputDim)稠密数据 稠密模型权重形状为(inputDim, outputDim)即一个样本特征与输出之间的直接映射libsvm/dummydense/component_dense(outputDim × (numSlot1)) × indexRange稀疏数据 稠密模型权重形状为(outputDim, inputDim)libsvm/dummysparse/component_sparse(outputDim × (numSlot1)) × indexRange并setValidIndexNum(modelsize)稀疏数据 稀疏模型只保留有效索引densesparse不允许直接抛出AngelException(Dense data, sparse model, pls. change model to dense)其中numSlot OptUtils.getSlotNum(optimizer)是优化器需要的额外参数槽位数例如带动量的优化器需要额外的动量槽indexRange来自SharedConf即输入特征的总维数。bias 矩阵固定为1 × outputDim。前向计算路径同样按数据格式分派SimpleInputLayer.scala稠密输入直接走graph.placeHolder.getFeats.dot(weight, parallel).iadd(bias)的 BLAS 路径稀疏输入则对每个输出维度逐列执行getFeats.dot(weight.getRow(colId))最后统一通过transFunc(forward)施加激活函数。参数初始化使用RandomNormal(0.0, 0.0001)分布。2.2 EmbeddingEmbedding 是很多深度学习算法共有的层。它的构造函数如下见 Embedding.scalaclass Embedding(name: String, outputDim: Int, val numFactors: Int, override val optimizer: Optimizer)(implicit graph: AngelGraph) extends InputLayer(name, outputDim)(graph) with Trainable除了name与optimizer这两个参数外Embedding 的另外两个参数含义如下outputDim指 lookup 的输出。Angel 的 Embedding 目前假设每个样本都具有相同的 field 数目且每个 field 都是 One-hot 的。第一个条件在大部分情况下都成立但第二个条件较为严格有些情况下并不成立以后会放宽这一限制numFactors指 Embedding 向量的维数。关于 Embedding 矩阵的大小是这样获得的系统中存有输入数据的维数这个维数被获取后成为 Embedding 矩阵的列数而numFactors就是 Embedding 矩阵的行数注虽然内部实现上有点不同但这样理解是可以的。Embedding 在抽象意义上是一张表并提供查表的方法lookup/calOutput。Angel 的 Embedding 的特别之处在于查完表后还允许有一些运算所以包括两个步骤查表根据索引到表中查出相应的列计算组装有时数据不是 one-hot要将查得的向量乘以一个值。下面展示值为 1one-hot 编码的结果用 dummy 格式表示与值为浮点数用 libsvm 格式表示两种情况下稀疏向量 embedding 的结果$$(1,5,40,\cdots,10000) \rightarrow (\mathbf{v}1,\mathbf{v}5,\mathbf{v}{40},\cdots,\mathbf{v}{10000})$$$$(1:0.3,;5:0.7,;40:1.9,\cdots,10000:3.2) \rightarrow (0.3\mathbf{v}1,;0.7\mathbf{v}5,;1.9\mathbf{v}{40},\cdots,3.2\mathbf{v}{10000})$$一种典型的 JSON 表达为{ name: embedding, type: Embedding, numfactors: 8, outputdim: 104, optimizer: { type: momentum, momentum: 0.9, reg2: 0.01 } }源码纵深查表与组装的实现Embedding 在 PS 上创建${name}_embedding矩阵其行数为(numSlot 1) * numFactors、列数为indexRangeEmbedding.scala。也就是说输入数据的维数indexRange对应矩阵列数numFactors 决定矩阵行数与文档中的描述一致多出的numSlot行是优化器槽位。查表pullParams通过GetColsFunc/GetColsParam按当前 batch 的索引批量取列当epoch 0时附带RandomNormalInitFunc(0.0, 0.00001)完成随机初始化计算组装calOutput遍历每个样本的稀疏向量存储若特征值为 1 则直接取对应列向量否则执行emVector.mul(value)按值缩放最终拼成RBCompIntDoubleMatrix/RBCompIntFloatMatrixEmbedding.scala。源码支持Int/Long索引 ×Double/Float值共四种稀疏存储组合梯度推送pushGradient按特征索引聚合各样本梯度mergeUpdate再除以OptUtils.getNormal(mode, graph)完成归一化最后通过UpdateColsFunc写回 PS。3. 线性层linear线性层是指有且仅有一个输入一个输出的层主要包括全连接层FCLayer和一系列特征交叉层。3.1 FCLayerFCLayer 层是 DNN 中最常见的层其计算可用下面的公式表达$$f(x) \text{transFunc}(x \cdot \mathbf{w} \text{bias})$$在 Angel 中的构造函数如下见 FCLayer.scalaclass FCLayer(name: String, outputDim: Int, inputLayer: Layer, transFunc: TransFunc, override val optimizer: Optimizer )(implicit graph: AngelGraph) extends LinearLayer(name, outputDim, inputLayer)(graph) with Trainable从构造函数与计算公式可知它与 DenseInputLayer/SparseInputLayer 十分相似有所不同的是前者的输入是一个 Layer后者直接输入数据在构造函数中不要指定输入 Layer。在参数存储上FCLayer 与 DenseInputLayer 一样也使用稠密的方式用 BLAS 计算。源码中权重矩阵${name}_weight为(numSlot 1) × (inputLayer.outputDim * outputDim)bias 为1 × outputDimFCLayer.scala。当输入层是 Embedding 时calOutput()会先把稀疏的 RBComp 矩阵转为 BLAS 稠密矩阵rbCompDense2Blas再做矩阵乘反向时再把梯度转回 RBComp 形式传回 Embedding形成完整的稠密/稀疏衔接。由于 FCLayer 通常是多个叠在一起使用在参数配置方面做了一些简化即将多个叠在一起的 FCLayer 的参数约简。下面是一个例子{ name: fclayer, type: FCLayer, outputdims: [ 100, 100, 1 ], transfuncs: [ relu, relu, identity ], inputlayer: embedding }这里有三个 FCLayer 叠在一起输入是第一层的输入输出为最后一层的输出每一层的outputdim、transfunc用列表表示即outputdims以列表的形式给出每个 FCLayer 的 outputDimtransfuncs以列表的形式给出每个 FCLayer 的 transfunc。注也可以为叠合的 FCLayer 指定 optimizer此时所有 layer 都有相同的 optimizer。如果分开写则为{ name: fclayer_0, type: FCLayer, outputdim: 100, transfuncs: relu, inputlayer: embedding }, { name: fclayer_1, type: FCLayer, outputdim: 100, transfuncs: relu, inputlayer: fclayer_0 }, { name: fclayer, type: FCLayer, outputdim: 1, transfuncs: identity, inputlayer: fclayer_1 }3.2 BiInnerSumCross特征交叉层计算公式如下$$f(\mathbf{u}_1, \mathbf{u}2, \cdots, \mathbf{u}k) \sum{i}^{k} \sum{ji1}^{k} \mathbf{u}_i^T \mathbf{u}_j$$其中 $(\mathbf{u}_1, \mathbf{u}_2, \cdots, \mathbf{u}_k)$ 是 Embedding 的输出结果。具体而言是 Embedding 结果做两两内积再求和。因此BiInnerSumCross没有参数是 untrainable 的输出维度为 1。构造函数如下见 BiInnerSumCross.scalaclass BiInnerSumCross(name: String, inputLayer: Layer)( implicit graph: AngelGraph) extends LinearLayer(name, 1, inputLayer)(graph)JSON 参数例子如下{ name: biinnersumcross, type: BiInnerSumCross, inputlayer: embedding, outputdim: 1 }从源码看其calOutput()并没有显式做两两内积而是利用恒等式 $\sum_{ij} \mathbf{u}_i^T\mathbf{u}_j \frac{1}{2}\left[(\sum_i \mathbf{u}_i)^T(\sum_i \mathbf{u}_i) - \sum_i \mathbf{u}_i^T\mathbf{u}_i\right]$通过一次求和向量sumVector与各自的平方点积完成计算BiInnerSumCross.scala把时间复杂度从 $O(k^2 \cdot d)$ 降到 $O(k \cdot d)$这正是 FM 能在高维稀疏场景下高效计算二阶交叉的原因。3.3 BiInnerCross特征交叉层计算公式如下$$f(\mathbf{u}_1, \mathbf{u}_2, \cdots, \mathbf{u}_k) (\mathbf{u}_1^T\mathbf{u}_2,; \mathbf{u}_1^T\mathbf{u}_3,; \mathbf{u}_1^T\mathbf{u}4,; \cdots,; \mathbf{u}{k-1}^T\mathbf{u}_k)$$其中 $(\mathbf{u}_1, \mathbf{u}_2, \cdots, \mathbf{u}_k)$ 是 Embedding 的输出结果。具体而言是 Embedding 结果做两两内积因此输出的维度为 $C_k^2 \frac{k(k-1)}{2}$。由此可见BiInnerCross也是没有参数是 untrainable 的输出维度为 $C_k^2$。构造函数如下见 BiInnerCross.scalaclass BiInnerCross(name: String, outputDim: Int, inputLayer: Layer)( implicit graph: AngelGraph) extends LinearLayer(name, outputDim, inputLayer)(graph)JSON 参数例子如下{ name: biInnerCross, type: BiInnerCross, outputdim: 78, inputlayer: embedding }从源码实现看calOutput()对每个样本的 field 向量做双重循环两两点积依次写入输出矩阵BiInnerCross.scala。值得注意的是其反向过程getValidateGrad通过numField ceil(sqrt(2 * outputDim))从输出维度反推出 field 个数再把上游梯度按 $C_k^2$ 的排列索引映射回每个 field 对实现精确的反向传播BiInnerCross.scala。3.4 BiInteractionCross特征交叉层计算公式如下$$f(\mathbf{u}_1, \mathbf{u}2, \cdots, \mathbf{u}k) \sum{i}^{k} \sum{ji1}^{k} \mathbf{u}_i \otimes \mathbf{u}_j$$其中 $(\mathbf{u}_1, \mathbf{u}_2, \cdots, \mathbf{u}_k)$ 是 Embedding 的输出结果。具体而言是 Embedding 结果做两两对应元素积 $\mathbf{u}_i \otimes \mathbf{u}_j$再相加因此输出的维度与 $\mathbf{u}_k$ 相同与输入数据的维度无关。由此可见BiInteractionCross也是没有参数是 untrainable 的。构造函数如下见 BiInteractionCross.scalaclass BiInteractionCross(name: String, outputDim: Int, inputLayer: Layer)( implicit graph: AngelGraph) extends LinearLayer(name, outputDim, inputLayer)(graph)JSON 参数例子如下{ name: biinteractioncross, type: BiInteractionCross, outputdim: 8, inputlayer: embedding }同样地源码利用 $(\sum_i \mathbf{u}_i)^2 - \sum_i \mathbf{u}_i^2$ 再除以 2 的等价变换实现高效计算BiInteractionCross.scala避免显式的两两元素级相乘其输出维度为outputDim即numFactors。注文档中提到的BiOutterCrossPNN outer 的特征交叉方式在当前仓库源码的linear目录中仍保留同名文件但文档已注明目前未实现。4. Join 层joinjoin 层是指有多个输入、一个输出的层代码位于 join 目录主要有ConcatLayer将多个输入层拼接起来输出一个 Dense 矩阵SumPooling将输入元素对应相加后输出MulPooling将输入元素对应相乘后输出DotPooling先将对应元素相乘然后按行相加输出 n 行一列的矩阵。4.1 ConcatLayer将多个输入层拼接起来输出一个 Dense 矩阵构造函数如下class ConcatLayer(name: String, outputDim: Int, inputLayers: Array[Layer])(implicit graph: AngelGraph) extends JoinLayer(name, outputDim, inputLayers)(graph)JSON 参数例子如下{ name: concatlayer, type: ConcatLayer, outputdim: 182, inputlayers: [ embedding, biInnerCross ] }有多个输入层用inputlayers以列表的形式指定。outputdim应等于各输入层输出维度之和例如 104 维的 embedding 拼上 78 维的 biInnerCross 得到 182 维。4.2 SumPoolingLayer将输入元素对应相加后输出输出一个 Dense 矩阵构造函数如下class SumPooling(name: String, outputDim: Int, inputLayers: Array[Layer])(implicit graph: AngelGraph) extends JoinLayer(name, outputDim, inputLayers)(graph)JSON 参数例子如下{ name: sumPooling, type: SumPooling, outputdim: 1, inputlayers: [ wide, fclayer ] }有多个输入层用inputlayers以列表的形式指定。各输入层需维度一致按位相加后输出维度仍为outputdim。4.3 MulPoolingLayer将输入元素对应相乘后输出输出一个 Dense 矩阵构造函数如下class MulPooling(name: String, outputDim: Int, inputLayers: Array[Layer])(implicit graph: AngelGraph) extends JoinLayer(name, outputDim, inputLayers)(graph)JSON 参数例子如下{ name: mulPooling, type: MulPooling, outputdim: 1, inputlayers: [ wide, fclayer ] }有多个输入层用inputlayers以列表的形式指定。与SumPooling不同这里是对应元素相乘。4.4 DotPoolingLayer先将对应元素相乘然后按行相加输出 n 行一列的矩阵构造函数如下class DotPooling(name: String, outputDim: Int, inputLayers: Array[Layer])(implicit graph: AngelGraph) extends JoinLayer(name, outputDim, inputLayers)(graph)JSON 参数例子如下{ name: dotPooling, type: DotPooling, outputdim: 1, inputlayers: [ wide, fclayer ] }有多个输入层用inputlayers以列表的形式指定。它等价于先做对应元素相乘再对每行求和因此输出固定为n 行 × 1 列。5. 损失层损失层位于网络的最上层只有输入、没有输出用于计算损失。关于损失函数请参考 Angel 中的损失函数。5.1 SimpleLossLayerSimpleLossLayer 的构造函数如下源码位于 verge/SimpleLossLayer.scalaclass SimpleLossLayer(name: String, inputLayer: Layer, lossFunc: LossFunc)( implicit graph: AngelGraph) extends LinearLayer(name, 1, inputLayer)(graph) with LossLayerJSON 参数例子如下{ name: simplelosslayer, type: Simplelosslayer, lossfunc: logloss, inputlayer: sumPooling }它接收一个inputLayer与一个lossFunc由于实现了LossLayertrait具备predict()预测、calLoss()计算损失等能力。文中同时提到的SoftmaxLossLayer用于多分类场景同样位于verge目录。6. 实战一个完整的层组合示例DeepFMangel-ps/mllib/src/test/jsons/deepfm.json 是仓库测试集中一个完整可用的 DeepFM 网络配置几乎用到了本文介绍的全部层类型非常适合对照理解{ data: { format: dummy, indexrange: 148, numfield: 13, validateratio: 0.1, sampleratio: 0.2 }, model: { modeltype: T_FLOAT_DENSE, modelsize: 148 }, train: { epoch: 5, numupdateperepoch: 10, lr: 0.5, decayclass: WarmRestarts, decayalpha: 0.05 }, default_optimizer: Momentum, layers: [ { name: wide, type: simpleinputlayer, outputdim: 1, transfunc: identity }, { name: embedding, type: embedding, numfactors: 8, outputdim: 104, optimizer: { type: momentum, momentum: 0.9, reg2: 0.01 } }, { name: fclayer, type: FCLayer, outputdims: [100, 100, 1], transfuncs: [relu, relu, identity], inputlayer: embedding }, { name: biinnersumcross, type: BiInnerSumCross, inputlayer: embedding, outputdim: 1 }, { name: sumPooling, type: SumPooling, outputdim: 1, inputlayers: [wide, biinnersumcross, fclayer] }, { name: simplelosslayer, type: simplelosslayer, lossfunc: logloss, inputlayer: sumPooling } ] }逐层解读这个网络的组装逻辑数据层format dummyone-hot 稀疏格式indexrange 148定义特征总维数numfield 13定义 field 个数wide 分支SimpleInputLayer输出 1 维identity激活相当于线性部分embedding 分支Embedding将 148 维稀疏特征查表为每个 field 8 维的向量numfactors 8outputdim 104即numfield × numfactors使用带reg2 0.01的 Momentum 优化器深度分支三层堆叠的FCLayer100 → 100 → 1输入为 embeddingFM 分支BiInnerSumCross对 embedding 做二阶隐式交叉输出 1 维融合SumPooling将wide、biinnersumcross、fclayer三个 1 维输出相加损失SimpleLossLayer使用logloss二分类逻辑回归损失计算最终损失。这一配置结构也在仓库其他测试 JSON 中得到印证fm.json使用BiInnerSumCross SumPoolingpnn.json使用BiInnerCross ConcatLayernfm.json使用BiInteractionCross FCLayer SumPoolinggraph.json则展示Embedding BiInnerSumCross FCLayer SumPooling的组合均位于 angel-ps/mllib/src/test/jsons。7. 小结Angel 的层体系围绕三类拓扑组织verge 承担输入SimpleInputLayer、Embedding与损失SimpleLossLayer、SoftmaxLossLayerlinear 承担全连接FCLayer与特征交叉BiInnerSumCross、BiInnerCross、BiInteractionCrossjoin 承担多路特征融合ConcatLayer、SumPooling、MulPooling、DotPooling。在 计算图 的框架下这些层通过 JSON 声明连接关系与超参数底层由 Layer.scala 的状态机驱动前向与反向传播参数以 PS 矩阵的形式分布式存储与更新。掌握各层的构造函数、输出维度推导与 JSON 写法即可参照仓库测试 JSON 灵活组装出 FM、DeepFM、PNN、NFM 等经典模型或设计自己的深度网络结构。赞分享人工智能机器学习分布式训练图计算后端【免费下载链接】angelA Flexible and Powerful Parameter Server for large-scale machine learning项目地址https://gitcode.com/gh_mirrors/an/angel点击查看免费下载相关推荐TensorSpace Layer API完全指南从输入层到输出层的全方位解析TensorSpace Layer API完全指南从输入层到输出层的全方位解析 TensorSpace Layer API是构建神经网络3D可视化模型的核心工数据可视化深度学习3D渲染AI 可解释性Apollo GraphQL部署指南从开发到生产的完整流程Apollo GraphQL部署指南从开发到生产的完整流程 Apollo GraphQL是一个功能强大的开源工具集用于构建和部署GraphQL API。本指kepler.gl Line 图层Line Layer完全指南2D 连线层的原理、数据要求与配置详解kepler.gl Line 图层Line Layer完全指南2D 连线层的原理、数据要求与配置详解 kepler.gl 的 Line 图层Line L数据可视化前端GIS上一篇fastblock Raft心跳合并技术解决多Raft组通信开销问题下一篇揭秘XSched架构四大核心组件如何实现跨XPU统一调度创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考