本文为 kaldi 官网关于 nnet3 的译文:
简介
本文覆盖最新的nnet3。为了了解Kaldi里的所有DNN,例如karel的版本,参考 Deep Neural Networks in Kaldi。
nnet3的创建是为了以更自然的方式不需要实际编码就支持更多通用种类的网络,而不仅仅是反馈网络(例如RNN和LSTM)。和nnet2一样支持多机器多GPU并行(利用基于naturalgradient-stabilized SGD with model averaging),参见论文this paper 。
文章包含以下几个部分
- nnet3数据类型
- nnet3编译
- nnet3优化
- [关于脚本的介绍]
目标和背景
前面nnet1和nnet2配置是基于组件对象,神经网络是一个组件的堆栈。每个组件对应于一层神经网络,我们将每一层表示成一个仿射变换紧跟一个非线性变换,所以每一层有两个组件。这些旧组件有一个前向传播函数和一个反向传播函数,都配置成可以在minibatche,以及其他功能操作。
这两个设置不仅仅支持一个非线性的线性序列,同时还有其他不同的方式。在nnet1,更复杂的网络拓扑结构是由组件内组件表示(components-within-components):例如并行(ParallelComponent),可能包含多个序列内的组件本身。同时,LSTMs被定义在c++级别上通过实现LSTM组件来实现。在nnet2 代码里,网络有时间索引的概念,直接作为框架的一部分来支持特征越时间拼接的功能。这使我们可以通过中间层次网络的拼接来支持TDNNs。
nnet3 代码的目的是支持nnet1和nnet2代码库支持的各种拓扑甚至更多;为了以更自然的方式支持,配置文件驱动使得不需要编码的方式来支持最有趣的新思想。
方法概述
在nnet3,我们有一个通用图结构而不是一个序列的组件。一个“nnet3”神经网络(classNnet)包括:
- 指定组件的列表,没有特定的顺序
- 一个图结构,其中包含“粘合剂”,指定组件如何组合
图是按名字访问组件(这使某些类型的参数共享)。这“粘合剂”所做的部分工作,是实现诸如递归神经网络(RNN)的结构,在这样的结构里时间t可以依赖时间t - 1。它还使我们能够以自然的方式处理边缘效应(如当我们到达文件的开始部分,边缘效应会发生在RNN中)。
这里将给出组件的一个示例配置文件表示和图(我们稍后将详细讨论它):
# First the components component name=affine1 type=NaturalGradientAffineComponent input-dim=48 output-dim=65 component name=relu1 type=RectifiedLinearComponent dim=65 component name=affine2 type=NaturalGradientAffineComponent input-dim=65 output-dim=115 component name=logsoftmax type=LogSoftmaxComponentdim=115 # Next the nodes input-node name=input dim=12 component-node name=affine1_node component=affine1input=Append(Offset(input, -1), Offset(input, 0), Offset(input, 1),Offset(input, 2)) component-node name=nonlin1 component=relu1 input=affine1_node component-node name=affine2 component=affine2input=nonlin1 component-node name=output_nonlin component=logsoftmaxinput=affine2 output-node name=output input=output_nonlin
图和组件,以及提供的输入和要求的输出,将被用来构造一个“计算图”(类ComputationGraph),这是编译过程一个重要的阶段。计算图将是一个非循环图,其中节点对应向量化数值。非循环图的每个节点将由神经网络图的节点(即网络的层)加上一些额外的索引确定:时间(t),表明minibatch内的示例(例如0到511,对于512的minibatch)的索引(n),加上一个“额外”指数(x),最终可能是在卷积方法有用的但目前通常是零。
- 为将上述内容公式化,我们定义一个索引 Index 是一个元组(n,t,x)。我们还将定义一个Cindex 作为一个元组(节点索引node-index,索引 Index ),节点索引是对应到神经网络(即层)中的一个节点的索引。我们实际进行的计算在编译阶段表示为一个Cindexes上有向无环图。
使用神经网络的过程(无论是训练或解码)如下:
- 用户提供计算请求ComputationRequest 说明可用的是什么输入的什么索引(例如time-indexes)要求的是什么输出
- 计算请求ComputationRequest 连同神经网络作为一个NnetComputation被编译成一系列的命令
- NnetComputation 进一步做速度上的优化(可以认为这是编译上的优化就像gcc's -O 选项).
- 类NnetComputer 负责接收矩阵化输入,评估NnetComputation ,以及提供矩阵化输出。可以将这理解成非常局限的运行时解释语言
nnet3的基本数据结构
索引Index
如上面提到的,索引 Index是一个元组 (n t x), 其中 n 是该在 minibatch的索引 ,t 是指时间索引,x 是一个占位符索引,以供将来使用,通常会是零。神经网络计算中我们处理矢量化数值, (例如1024维的数量对应于一个隐藏层激励)。在实际的神经网络计算中,1024年将成为一个矩阵的列数,索引和矩阵的行之间存在一对一的对应。nnet3的框架因此不同于Theano的包,该包使用张量操作:相反,我们通过将张量包装成矩阵来有效操作。这使我们能够使用BLAS操作优化。
作为索引的一个例子:如果我们训练非常简单的前馈网络,索引可能只在“n”维度变化,我们可以任意“t”值设置为0,所以索引如下
[ (0, 0, 0) (1, 0, 0) (2, 0, 0) ... ]
另一方面,如果我们使用相同类型的网络解码一个句子,索引只会在“t”维度不同,所以我们会有
[ (0, 0, 0) (0, 1, 0) (0, 2, 0) ... ]
对应于矩阵的行。在网络使用时间信息的情况下,在早期的层我们在训练时需要不同的“t”值,所以我们可能会遇到在“n”和“t”都不同的索引列表。如:
[ (0, -1,0) (0, 0, 0) (0, 1, 0) (1, -1, 0) (1, 0, 0) (1, 1, 0) ...]
索引结构体Index 有默认的排序操作,默认由n来排序,然后t,那么x,所以通常我们也按上面排序。当你看到代码打印向量索引,你会经常看到他们是紧凑的形式,其中在“x”索引省略(如果零),和“t”的范围值页是紧凑表示,因此上述向量可能写成
[ (0, -1:1) (1,-1:1) ... ]
索引Cindexes
Cindex 是一对(int32,Index),其中int32对应神经网络中的一个节点的索引。正如上面提到的,一个神经网络由一组命名组件和一种在“节点”和节点索引上的图组成,。Cindex 在编译过程中使用,它们对应于“计算图”的节点,计算图对应于一个特定的神经网络计算。Cindex和特定节点的输出之间有一个对应关系,通常会有(至少,在优化之前)Cindex 和编译计算中矩阵的行之间的一一对应。我们前面提到的,有一个索引和矩阵的行之间的对应关系,所不同的是,一个Cindex除了告诉矩阵哪一行,也告诉我们哪个矩阵,。例如,假设图中有一个节点叫做“affine1”,输出维度1000和节点列表编号为2,在Cindex (2,(0,0,0))相当于列维度1000的矩阵的某一行,这将被分配为“affine1”组件的输出。
计算图ComputationGraph
一个ComputationGraph 代表一个Cindexes上的有向图,其中每个Cindex都有一个它依赖的其他Cindexes的列表。在一个简单的前馈结构,图形将有一个简单的拓扑,该拓扑有多个线性结构, (为清楚起见,使用名字不是整数),我们可能会有一个依赖于(affine1(0,0,0))的(nonlin1(0,0,0)),和依赖于 (affine1(1,0,0)) 的(nonlin1(1,0,0)),等等。在ComputationGraph 和其他地方,你会看到称为cindex_ids的整数。每一个cindex_id都是一系列存储在图中的Cindexes的索引,它标识一个特定的Cindex;cindex_ids是为了效率起见,作为一个整数比Cindex容易使用。
计算请求ComputationRequest
ComputationRequest 指定一组命名的输入和输出节点,每个都有一个关联的索引列表。对输入节点,这个列表确定哪些索引要提供给计算;对输出节点,它确定了哪些索引要求被计算。此外,ComputationRequest 包含各种标志,如关于哪些输出/输入节点有需要提供/或者被要求backprop微分,和模型更新是否执行的信息。
作为一个例子,一个ComputationRequest 可能会指定一个输入节点,命名为“输入”,有索引((0,1,0)(0,0,0)、(0,1,0)]提供;和一个输出节点,名叫“输出”,与索引((0,0,0))被要求。这将在网络需要左边和右边各一帧的上下文的时候有意义。其实我们通常在训练中,只要求这样的独立输出帧;在训练中我们通常会有多个minibatch的例子,所以索引的“n”维度也有所不同。
计算的目标函数及其输出的导数不是核心神经网络框架的一部分,我们把它交给用户。一般神经网络可能有多个输入和输出节点;这可能在多任务学习或处理多个不同类型的输入数据的框架中是有用的(如多视点学习)。
NnetComputation (brief)
NnetComputation 代表已经从一个Nnet和ComputationRequest编译出特定的计算。它包含一个命令序列,每一个都可能是一个传播操作,一个矩阵复制或添加操作,等其他简单矩阵命令,例如复制矩阵特定的行从一个矩阵到另一个矩阵;方向传播Backprop操作,矩阵大小命令,等等。计算作用的变量是矩阵的列表,以及可能占据一个矩阵的某些行或列的范围的子矩阵。计算还包含各种索引的集合(整数数组等等),这有时需要被作为特定的参数参于矩阵运算。
我们将在下面更详细地描述这个NnetComputation (细节)
NnetComputer
NnetComputer 对象是负责实际执行NnetComputation。这段代码实际上是很简单(主要是一个switch语句的循环),因为大多数的复杂性NnetComputation发生在编译和优化。
nnet3的神经网络
前一节中概述了一个框架是如何结合在一起的。在本节中,我们将更详细介绍的神经网络本身的结构,以及我们如何把组件粘合在一起以及表示输入对时间t – 1的依赖关系 .
组件(基础)
nnet3组件Component,是一个具有正向传播和反向传播功能的对象。它可能包含参数(比如,一个仿射层)或者它可能只实现一个固定的非线性,如Sigmoid组件。组件的接口最重要的部分如下
class Component { public: virtual voidPropagate(const ComponentPrecomputedIndexes *indexes, const CuMatrixBase<BaseFloat> &in, CuMatrixBase<BaseFloat> *out) const = 0; virtual voidBackprop(const std::string &debug_info, const ComponentPrecomputedIndexes *indexes, constCuMatrixBase<BaseFloat> &in_value, const CuMatrixBase<BaseFloat> &out_value, const CuMatrixBase<BaseFloat> &out_deriv, Component *to_update, // may be NULL; may be identical // to "this" or different. CuMatrixBase<BaseFloat> *in_deriv) const = 0; ... };
目前,请忽略ComponentPrecomputedIndexes *indexs参数。某个特定组件有输入维度和输出维度,它通常会“逐行”转换数据。即Propagate函数的输入输出矩阵有相同数量的行,输入的每一行被处理去生成相应的输出的行。就索引而言,这意味着对应输入和输出的每个元素的索引都是相同的。类似的逻辑在Backprop函数。
组件(属性)
一个组件有一个叫Properties虚函数,它会返回包含不同二进制标志位的比特掩盖数的,这些二进制标志位是定义在ComponentProperties的枚举变量。
class Component { ... virtual int32Properties() const = 0; ... };
这些属性确定组件的各种特征,如是否包含可更新的参数(kUpdatableComponent),其传播功能是否支持就地操作(kPropagateInPlace),和其他的东西。其中很多是优化代码所需的,这样就可以知道哪些优化是适用的。
您还会注意到一个kSimpleComponent枚举值。如果设置,那么组件是“简单”的,这意味着它的转换和逐行的数据同上面的定义。复杂组件允许输入和输出不同数量的行,而且可能需要知道索引使用的输入和输出。
Propgate和Backprop函数的const ComponentPrecomputedIndexes参数只是非简单组件使用的。现在,请假设所有的组件都是简单的,因为我们还没有实现任何复杂组件,因为他们在实现任何标准的方法(RNNs LSTMs等等) 都不需要。与nnet2框架不同,组件是不负责实现诸如跨帧拼接;相反,我们使用描述符来处理Descriptors ,具体下面将会解释。
神经网络节点(概述)
我们先前解释说,一个神经网络是命名组件和“网络节点”上的图的集合,但我们还没有解释什么是“网络节点”。NetworkNode 实际上是一个结构体。NetworkNode是四个不同的类别中的一种,由NodeType 枚举定义
enum NodeType { kInput, kDescriptor, kComponent,kDimRange };
三个最重要的是kInput,kDescriptor,kComponent,(kDimRange是用来支持将节点的输出分裂到各个部分)。kComponent节点是网络的“肉体部分”,并(含有一定混淆隐喻的风险)描述符kDescriptor是 “粘合剂”将前者组合在一起,支持诸如帧拼接和复发。kInput节点非常简单,只需要提供一个地方来转储提供的输入和声明其维度;他们其实不做任何事。你也许会很惊讶,没有kOutput节点。原因是,输出节点其实就只是描述符。有一个规则,每个kComponent类型节点必须在节点列表之前有其“拥有”的节点类型kDescriptor;这条规则使得图形编译变得更加容易。因此, kDescriptor节点类型,如果不是有kComponent节点紧随其后,就是一个输出节点,为方便起见,类Nnet 有功能IsOutputNode(int32 node_index) andIsComponentInputNode(int32 node_index)可以告诉这些分开。
下面章节,我们将更加深入的讨论细节 Neural network nodes (detail).
神经网络配置文件
神经网络可以从配置文件创建。我们给一个非常简单的例子来说明配置文件是怎样与描述符关联。这个网络有一个隐藏层,并且在第一个节点在时间轴上做拼接:
# First the components component name=affine1type=NaturalGradientAffineComponent input-dim=48 output-dim=65 component name=relu1 type=RectifiedLinearComponent dim=65 component name=affine2type=NaturalGradientAffineComponent input-dim=65 output-dim=115 component name=logsoftmax type=LogSoftmaxComponentdim=115 # Next the nodes input-node name=input dim=12 component-node name=affine1_node component=affine1input=Append(Offset(input, -1), Offset(input, 0), Offset(input, 1),Offset(input, 2)) component-node name=nonlin1 component=relu1input=affine1_node component-node name=affine2 component=affine2input=nonlin1 component-node name=output_nonlin component=logsoftmaxinput=affine2 output-node name=output input=output_nonlin
在配置文件中没有提到描述符(如无“descriptor-node”)。而是,“input”字段(例如Input= Append(....))是描述符。配置文件中的每个component-node被扩展到两个节点:一个kComponent类型节点,以及紧挨着它前面的“input”字段所定义的kDescriptor类型节点。
上述配置文件未包含 “dim-range node” 的样例,基本 “dim-range node” 样例如下(使用 component affine1 65 维特征中的 50 维):
dim-range-node name=dim-range-node1 input-node=affine1_node dim-offset=0 dim=50
代码中的描述符Descriptors
描述符Descriptor 是一种非常有限的表达式,能够访问定义在图中其他节点的数值。在本节中,我们从其配置文件格式的角度来描述Descriptors描述符,下面我们将解释他们是如何呈现在代码中。
最简单的Descriptor 类型 (基本情况)只是一个节点名,例如:“affine1”(只允许节点类型kComponent或kInput出现在这里,为了简化实现)。下面我们将列出一些类型的表达式,它们可能出现在描述符中,但请记住,这种描述将会给你一个描述符Descriptors的大体描述,比实际情况更一般化;实际上这些可能只出现在一个特定的层次结构,我们将后续进一步更精确地描述这个页面。
# caution, this is a simplification that overgeneratesdescriptors. <descriptor> ::= <node-name> ;; node name of kInput or kComponent node. <descriptor> ::= Append(<descriptor>,<descriptor> [, <descriptor> ... ] ) <descriptor> ::= Sum(<descriptor>,<descriptor>) ;; Failover or IfDefined might be useful for time t=-1 ina RNN, for instance. <descriptor> ::= Failover(<descriptor>,<descriptor>) ;; 1st arg ifcomputable, else 2nd <descriptor> ::= IfDefined(<descriptor>) ;; the arg if defined, else zero. <descriptor> ::= Offset(<descriptor>,<t-offset> [, <x-offset> ] ) ;; offsets are integers ;; Switch(...) is intended to be used in clockwork RNNsor similar schemes. It chooses ;; one argument based on the value of t (in the requestedIndex) modulo the number of ;; arguments <descriptor> ::= Switch(<descriptor>,<descriptor> [, <descriptor> ...]) ;; For use in clockwork RNNs or similar, Round() roundsthe time-index t of the ;; requested Index to the next-lowest multiple of theinteger <t-modulus> ;; and evaluates the input argument for the resultingIndex. <descriptor> ::= Round(<descriptor>,<t-modulus>) ;; <t-modulus>is an integer ;; ReplaceIndex replaces some <variable-name> (t orx) in the requested Index ;; with a fixed integer <value>. E.g. might be useful when incorporating ;; iVectors; iVector would always have time-index t=0. <descriptor> ::= ReplaceIndex(<descriptor>,<variable-name>, <value>)
现在,我们将介绍实际的语法,这不同于上面的简化版本,因为表达式可能只出现在一定的网络结构。这个语法也更紧密地与实际代码的类名称对应。
;;; <descriptor> == class Descriptor <descriptor> ::= Append(<sum-descriptor>[, <sum-descriptor> ... ] ) <descriptor> ::= <sum-descriptor> ;;equivalent to Append() with one arg. ;;; <sum-descriptor> == class SumDescriptor <sum-descriptor> ::= Sum(<sum-descriptor>,<sum-descriptor>) <sum-descriptor> ::=Failover(<sum-descriptor>, <sum-descriptor>) <sum-descriptor> ::=IfDefined(<sum-descriptor>) <sum-descriptor> ::= <fwd-descriptor> ;;; <fwd-descriptor> == class ForwardingDescriptor ;; <t-offset> and <x-offset> are integers. <fwd-descriptor> ::= Offset(<fwd-descriptor>, <t-offset> [, <x-offset> ] ) <fwd-descriptor> ::= Switch(<fwd-descriptor>, <fwd-descriptor> [,<fwd-descriptor> ...]) ;; <t-modulus> is an integer <fwd-descriptor> ::= Round(<fwd-descriptor>, <t-modulus>) ;; <variable-name> is t or x; <value> is aninteger <fwd-descriptor> ::= ReplaceIndex(<fwd-descriptor>, <variable-name>,<value>) ;; <node-name> is the name of a node of type kInputor kComponent. <fwd-descriptor> ::= <node-name>
描述符的设计应该是足够严格,使得产生的表达式将相当容易计算(和生成backprop代码)。当在组件里做任何更有趣或非线性操作,要将组件联系在一起时,他们才做繁重工作时,
注意:如果有必要在未知长度的各种索引上做累加或平均(例如一个文件中的所有“t”值),我们打算在一个组件Component 操作——一个复杂的组件,而不是使用描述符。
代码中的描述符Descriptors
我们将在代码中从下到上描述描述符。基类ForwardingDescriptor 处理Descriptor 类型,该类型将只访问某个单一数值,没有任何附加Append (…)或Sum (…)类似的表达。在此接口中最重要的功能是MapToInput():
class ForwardingDescriptor { public: virtual CindexMapToInput(const Index &output) const = 0; ... }
给定一个特定要求的索引Index,这个函数将返回一个对应于输入值的Cindex (引用其他节点)。函数的参数是一个Index索引,而不是一个Cindex ,因为这个数值是不会依赖于自身描述符对应结点的节点索引。有几个ForwardingDescriptor的派生类,包括SimpleForwardingDescriptor(基本情况,持有一个节点索引),OffsetForwardingDescriptorReplaceIndexForwardingDescriptor等等。
下一个级别的层次结构是类SumDescriptor,用于支持表达式Sum (< desc >、< desc >), Failover (< desc >、< desc >),和IfDefined(<desc >)。很清楚,某个给定索引到SumDescriptormay的请求有可能会返回不同的Cindexes,所以我们用于ForwardingDescriptor的接口行不通。我们还需要支持可选的依赖关系。以下就是我们如何在代码级别管理的:
class SumDescriptor { public: virtual voidGetDependencies(const Index &ind, std::vector<Cindex> *dependencies) const = 0; ... };
函数GetDependencies将所有可能参与计算Index数值的Cindexes附加到dependencies。接下来我们需要担心当请求的输入可能是不可计算的(例如,因为有限的输入数据或边缘效应),会发生什么。函数IsComputable()将进行处理:
class SumDescriptor { public: ... virtual boolIsComputable(const Index &ind, const CindexSet&cindex_set, std::vector<Cindex> *input_terms) const = 0; ... };
这里,CindexSet 对象表示一组Cindexes的集合,在这种情况下代表“所有我们知道是可计算的Cindexes的集合,”。如果这个索引的描述符Descriptor 是可计算的,那么函数将返回true。例如,表达式Sum (X,Y)只会当X和Y是可计算的,才可计算。如果这个函数返回true,它实际上将只添加出现在评估表达式的输入Cindexes到“input_terms”。例如(放宽来说),在一个Failover(X, Y)表达式形式,如果X是可计算的,那么只会附加X到“input_terms”,而不是Y。
类 Descriptor是顶级层次结构。它可以被认为是一个SumDescriptors向量,注意,这个向量长度通常是一。它的功能是附加信息(例如附加向量),它负责Append(...)的语法。它有与SumDescriptor相同的接口的函数GetDependencies()和 IsComputable()以及函数,比如允许用户访问的向量单个SumDescriptors的函数NumParts()和 Part(int32 n),。
神经网络节点(详情)
下面我们将详细描述网络节点,如上所述,节点有四种类型,枚举定义如下
enum NodeType { kInput, kDescriptor, kComponent,kDimRange };
实际的NetworkNode 是一个结构体. 避免指针的麻烦,因为c++不允许组合里面包含类,我们有一个略微凌乱的布局:
struct NetworkNode { NodeTypenode_type; //"descriptor" is relevant only for nodes of type kDescriptor. Descriptordescriptor; union { // ForkComponent, the index into Nnet::components_ int32component_index; // forkDimRange, the node-index of the input node. int32node_index; } u; // for kInput,the dimension of the input feature. ForkDimRange, the dimension // of the output(i.e. the length of the range) int32 dim; // for kDimRange,the dimension of the offset into the input component's feature. int32 dim_offset; };
总结了不同类型的节点和他们的实际使用的成员:
- kInput 节点只使用"dim"
- kDescriptor节点只使用"descriptor"
- kComponent节点只使用"component_index", 这标注了 Nnet组件序列的索引.
- kDimRange节点只使用"node_index", "dim", "dim_offset".
神经网络(详情)
我们将给更多类Nnet本身的细节,这将存储整个神经网络。最简单来解释的方法是只列出私有数据成员:
class Nnet { public: ... private: std::vector<std::string> component_names_; std::vector<Component*> components_; std::vector<std::string> node_names_; std::vector<NetworkNode>nodes_; };
component_names_应该有和components_ 相同的大小,node_names_应该有nodes_;相同的大小,这些将名字与组件和节点关联起来。注意,我们自动分配名称到类型kDescriptor的节点,它先于相应的kComponent类型的节点,通过添加“_input”到对应的组件节点的名称。这些kDescriptor节点的名字不出现在神经网络的配置文件表示中。
NnetComputation(详情)
另一个重要的数据类型是NnetComputation。这代表了一个编译好的神经网络计算,包含一系列的命令和其他必要的解释它们的信息。在内部它定义了许多类型,包括下面的枚举值:
enum CommandType{ kAllocMatrixUndefined, kAllocMatrixZeroed, kDeallocMatrix,kPropagate, kStoreStats, kBackprop, kMatrixCopy,kMatrixAdd, kCopyRows, kAddRows, kCopyRowsMulti,kCopyToRowsMulti, kAddRowsMulti, kAddToRowsMulti, kAddRowRanges,kNoOperation, kNoOperationMarker };
我们特别指出kPropagate,kBackprop和kMatrixCopy作为命令的自解释例子。有一个结构体Command代表一个命令及其参数。大部分的参数都被索引到矩阵和组件的列表。
struct Command { CommandTypecommand_type; int32 arg1; int32 arg2; int32 arg3; int32 arg4; int32 arg5; int32 arg6; };
还有几个结构体类型定义,用于存储大小信息矩阵和子矩阵。子矩阵是一个矩阵范围限制的行和列,可能像matlab语法,
some_matrix(1:10,1:20):
struct MatrixInfo{
int32 num_rows;
int32 num_cols;
};
structSubMatrixInfo {
int32matrix_index; // index into"matrices": the underlying matrix.
int32row_offset;
int32 num_rows;
int32col_offset;
int32 num_cols;
};
NnetComputation数据结构体成员 包括以下:
struct Command { ... std::vector<Command> commands; std::vector<MatrixInfo> matrices; std::vector<SubMatrixInfo> submatrices; // used inkAddRows, kAddToRows, kCopyRows, kCopyToRows. contains row-indexes. std::vector<std::vector<int32> > indexes; // used inkAddRowsMulti, kAddToRowsMulti, kCopyRowsMulti, kCopyToRowsMulti. // contains pairs(sub-matrix index, row index)- or (-1,-1) meaning don't // do anythingfor this row. std::vector<std::vector<std::pair<int32,int32> > >indexes_multi; // Indexes usedin kAddRowRanges commands, containing pairs (start-index, // end-index) std::vector<std::vector<std::pair<int32,int32> > >indexes_ranges; // Informationabout where the values and derivatives of inputs and outputs of // the neural netlive. unordered_map<int32, std::pair<int32, int32> >input_output_info; boolneed_model_derivative; // the followingis only used in non-simple Components; ignore for now. std::vector<ComponentPrecomputedIndexes*>component_precomputed_indexes; ... };
名字中带“索引”的向量是矩阵函数如CopyRows AddRows等等的参数,它们需要索引向量作为输入(我们在执行之前计算之前,将复制这些到GPU)。
更多单个命令的信息和他们的参数的意思可以在这里找到here。