目标与背景
nnet3概要
nnet3中基础数据结构
Cindexes
Cindex类是一个二元组(int32, Index),其中int32对应于神经网络中结点的索引。根据上文所述,一个神经网络由:
多个组件
多个结点之上的图(对应于特定网络计算)
其中,Cindexes与编译计算中矩阵的行有一一对应关系。之前提高,Indexes与矩阵行有对应关系;区别在于,除了矩阵行号之外,Cindex告诉我们位于哪一个矩阵。
比如,假设图中有一个名为"affine1"的结点,该结点的输出维数为1000,在结点列表中索引为2,那么"affine1"组件的输出——Cindex(2, (0, 0, 0)),对应于列宽为1000的矩阵的某一行。
ComputationGraph
ComputationGraph表示由Cindexes组成的有向图,其中每个Cindex都依赖于一个Cindexes列表。对于一个简单的前馈结构,其ComputationGraph将拥有一个由多个线性结构组成的简单拓扑,则有:
(nonlin1, (0, 0, 0))依赖于(affine1, (0, 0, 0));
(nonlin1, (1, 0, 0))依赖于(affine1, (1, 0, 0));
以此类推
在ComputationGraph或其他类中,可能会看到多个名为cindex_id的整数。每个cindex_id是储存于图中的某个Cindexes数组索引。
ComputationRequest
ComputationRequest标识一组命名输入结点和输出结点,每个结点都有一个Indexes关联列表。对于输入节点,列表标识了哪些索引要用于计算; 对于输出节点,它标识了哪些索引需要输出(computed)。另外,ComputationRequest包含各种flags,例如:哪个输出/输入结点提供提供/请求反向传播微分,以及是否执行模型更新。
例如,ComputationRequest可能会指定一个名为"input"的输入结点、索引[(0,-1,0),(0,0,0),(0,1,0)]以及一个请求索引[(0,0,0)]的"output"输出节点。如果网络需要左右各一帧的上下文,这将是有意义的。实际上,我们通常只会在训练期间请求输出单个帧; 并且在训练期间,我们通常会在小批次中使用多个examples,因此索引中"n"也会有所不同。
输出层的目标函数及其导数的计算并不是神经网络核心框架的一部分;我们将其留给用户。神经网络通常可以具有多个输入和输出节点;这在多任务学习或处理多种不同类型的输入时(例如多视图学习)是很有用的。
NnetComputation (brief)
将Nnet与ComputationRequest编译后,得到NnetComputation,表示特定的计算。它包含一系列的Commands,包括:Propagate、矩阵复制、矩阵相加、矩阵某一行复制到另一个矩阵、Backprop、调制矩阵大小等运算。计算对象是矩阵的列表,以及特定行列范围的子矩阵。 Computation还包含各种索引(整数数组等),这些索引有时需要作为特定矩阵运算的参数。
将在下面的NnetComputation (detail)中进行详述。
NnetComputer
NnetComputer对象负责实际执行NnetComputation。 这个代码实际上很简单(主要是一个switch语句的循环),因为大部分代码位于NnetComputation的编译与优化。
nnet3中的神经网络
上一节介绍了框架组成。本节将详细介绍神经网络结构、如何将组件组合在一起、如何表示对时间为t-1的输入的依赖。
Component(基础)
nnet3中的Component,是带有Propagate与Backprop函数的对象。Component还包含一些参数或对固定非线性单元的实现(如Sigmoid组件)。Component接口的重要代码如下:
class Component { public: virtual void Propagate(const ComponentPrecomputedIndexes *indexes, const CuMatrixBase<BaseFloat> &in, CuMatrixBase<BaseFloat> *out) const = 0; virtual void Backprop(const std::string &debug_info, const ComponentPrecomputedIndexes *indexes, const CuMatrixBase<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; ... }; |
请忽略const ComponentPrecomputedIndexes *indexes参数。
一个特定的组件具有输入维度和输出维度,并且它通常对进行"逐行"转换。也就是说,Propagate()中的输入和输出矩阵具有相同的行数,并且处理输入的每一行以创建相应的输出行。就索引而言,这意味着对应于每个输入和输出元素的索引是相同的。Backprop函数中保留了类似的逻辑。
Components (properties)
Component有一个虚函数Properties(),返回值为ComponentProperties的各种二进制flags枚举的位掩码。
class Component
{
...
virtual int32 Properties() const = 0;
...
};
枚举包括:
kUpdatableComponent //是否包含可更新的参数
kPropagateInPlace //其传播函数是否支持就地操作等
许多优化代码都需要这些代码,以便知道程序适用于哪些优化。你还会注意到一个枚举值kSimpleComponent。如果设置了该枚举,则组件是"简单的",这意味着它按照上面定义的逐行转换数据。非简单组件可能允许具有不同行数的输入和输出,并且可能需要知道在输入和输出处使用了哪些索引。Propgate和Backprop函数中的const ComponentPrecomputedIndexes *indexes参数仅用于非简单组件使用。假设本文提到的所有组件都是简单组件,因为它们不是实现任何RNN,LSTM等所必需的。与nnet2框架不同,组件不负责实现诸如splicing跨帧拼接的操作;相反,我们使用Descriptors来处理,这将在下面解释。
配置文件中的描述符
描述符是一种非常受限的表达式,是对图中多个其他结点的引用。描述符相当于"粘合剂",用于将组件连接在一起。描述符负责对组件的输出进行附加操作或求和操作,以便作为后续组件的输入。在本节中,我们从配置文件格式的角度来介绍描述符;下面我们描述符的语法。
最简单类型的描述符(基本情况)是一个结点名称,例如,"affine1"(只支持kComponent或kInput类型的结点)。下面是关于描述符的语法;
# caution, this is a simplification that overgenerates descriptors. <descriptor> ::= <node-name> ;; node name of kInput or kComponent node. <descriptor> ::= Append(<descriptor>, <descriptor> [, <descriptor> ... ] ) <descriptor> ::= Sum(<descriptor>, <descriptor>) <descriptor> ::= Const(<value>, <dimension>) ;; e.g. Const(1.0, 512) <descriptor> ::= Scale(<scale>, <descriptor>) ;; e.g. Scale(-1.0, tdnn2) ;; Failover or IfDefined might be useful for time t=-1 in a RNN, for instance. <descriptor> ::= Failover(<descriptor>, <descriptor>) ;; 1st arg if computable, 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 RNNs or similar schemes. It chooses ;; one argument based on the value of t (in the requested Index) modulo the number of ;; arguments <descriptor> ::= Switch(<descriptor>, <descriptor> [, <descriptor> ...]) ;; For use in clockwork RNNs or similar, Round() rounds the time-index t of the ;; requested Index to the next-lowest multiple of the integer <t-modulus>, ;; and evaluates the input argument for the resulting Index. <descriptor> ::= Round(<descriptor>, <t-modulus>) ;; <t-modulus> is an integer ;; ReplaceIndex replaces some <variable-name> (t or x) 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> ::= Const(<value>, <dimension>) <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 an integer <fwd-descriptor> ::= ReplaceIndex(<fwd-descriptor>, <variable-name>, <value>) ;; <node-name> is the name of a node of type kInput or kComponent. <fwd-descriptor> ::= Scale(<scale>, <node-name>) <fwd-descriptor> ::= <node-name> |
描述符的设计应该足够严格,以至于得到的表达式将相当容易计算(并生成反向代码)。当描述符与组件相连接时,它们只应该执行资源繁重的操作,而非线性的操作都应该在组件中执行。
注意:如果有必要对各种未知长度的索引(例如文件中的所有"t"值)进行求和或求平均值,我们打算在一个Component中执行此操作。
NnetComputation (detail)
NnetComputation表示神经网络计算的编译版本(可执行版本),其中定义了一些类型,包括如下的枚举类型:
enum CommandType { kAllocMatrixUndefined, kAllocMatrixZeroed, kDeallocMatrix, kPropagate, kStoreStats, kBackprop, kMatrixCopy, kMatrixAdd, kCopyRows, kAddRows, kCopyRowsMulti, kCopyToRowsMulti, kAddRowsMulti, kAddToRowsMulti, kAddRowRanges, kNoOperation, kNoOperationMarker }; |
以下的struct Command代表一个单独的命令及其参数。其中大多数参数都是矩阵索引 以及 组件列表索引。
struct Command { CommandType command_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; }; struct SubMatrixInfo { int32 matrix_index; // index into "matrices": the underlying matrix. int32 row_offset; int32 num_rows; int32 col_offset; int32 num_cols; }; |
结构体NnetComputation包含以下数据成员:
struct Command { ... std::vector<Command> commands; std::vector<MatrixInfo> matrices; std::vector<SubMatrixInfo> submatrices; // used in kAddRows, kAddToRows, kCopyRows, kCopyToRows. contains row-indexes. std::vector<std::vector<int32> > indexes; // used in kAddRowsMulti, kAddToRowsMulti, kCopyRowsMulti, kCopyToRowsMulti. // contains pairs (sub-matrix index, row index)- or (-1,-1) meaning don‘t // do anything for this row. std::vector<std::vector<std::pair<int32,int32> > > indexes_multi; // Indexes used in kAddRowRanges commands, containing pairs (start-index, // end-index) std::vector<std::vector<std::pair<int32,int32> > > indexes_ranges; // Information about where the values and derivatives of inputs and outputs of // the neural net live. unordered_map<int32, std::pair<int32, int32> > input_output_info; bool need_model_derivative; // the following is only used in non-simple Components; ignore for now. std::vector<ComponentPrecomputedIndexes*> component_precomputed_indexes; ... }; |
其名称带由"indexes"的向量以向量索引作为输入的矩阵函数(如CopyRows,AddRows等)的参数(我们将在执行计算之前将这些向量复制到GPU卡中)。
原文:https://www.cnblogs.com/JarvanWang/p/9152613.html