Kaldi中的nnet3——Data types in the "nnet3" setup.
条评论大部分翻译自kaldi的官网,以及自己的理解,如有错误还请指正
1、nnet3 outline
在nnet3中,使用一个通用的图结构,而不仅仅是一个组件序列,一个nnet3神经网络有下面两部分组成
- 无序的Components列表,每个Components都有个name
- 一个图(graph)结构,有一个类似粘合剂的结构,指明怎么将Components组合在一起,或者说怎么构成这个图
这里的图由一个个结点组成,每个结点表示神经网络中的某一层
图的结点通过Components的名字跟某个Component关联在一起,即通过Component的名字指明图的结点使用的是哪个Component。这种类似粘合剂的结构使得RNN得以工作,同时也使得我们可以处理边缘效应(edge effects, 可能发生在RNN中,比如RNN的0时刻,0时刻之前没有数据)。
当训练或者解码,一个神经网络的过程如下:
- 用户提供一个ComputationRequest,说明可用输入的索引(例如时间索引)和请求的输出
- ComputationRequest和神经网络一起被编译成一系列命令作为NnetComputing
- NnetComputing进一步优化速度(可以认为这是编译器优化,就像gcc的-O标志一样)。
- NnetComputer负责接收矩阵值输入,评估NnetComputing,并提供矩阵值输出。可以把这看作是一种非常有限的解释语言的运行时。
Components和graph的配置文件如下所示:1
2
3
4
5
6
7
8
9
10
11
12 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=LogSoftmaxComponent dim=115
Next the nodes
input-node name=input dim=12
component-node name=affine1_node component=affine1 input=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=affine2 input=nonlin1
component-node name=output_nonlin component=logsoftmax input=affine2
output-node name=output input=output_nonlin
对于Components,可以看到Component中有个name属性对Component进行命名,每个Component都要指出input-dim和output-dim,当input-dim和output-dim相同时,则可以通过dim指出,表示input-dim和output-dim相同,同时通过type指明Component的类型。
图由input-node, component-node,output-node组成,其中component-node中通过component属性指明component,同时通过name对这个component-node进行命名,除此之外,component-node中还有一个input属性(暂时称为属性),这个input就是前面所提到的类似于粘合剂的结构,指明如何将Components组合在一起构成图,从上面可以看到,除了input和affine1_node,其他结点的input都是上一个component-node的名字,由此指明可Component之间如何组合构成图。
2、Basic data structures in nnet3
2.1、Index
为了表示Component需要处理的输入矩阵,即Component的输入用一个矩阵表示,使用Index结构来索引矩阵的每一行。
Index是一个tuple(n, t, x), 其中n是minibatch的索引,即表示第n条数据,如果每个minibatch的大小为512,则0<=n<=511;t表示语音帧的索引,x是一个额外的索引,可能会在卷积神经网络中使用或者其他地方使用,大部分情况下为0。在神经网络的计算中,我们使用向量,比如,隐藏的激活层是一个1024维的向量,这就意味着有个矩阵的列数是1024,矩阵的行和Index是一一对应的。
当我们训练一个简单的前馈网络时,所有的Index可能只有n是不同的,因为没有时序信息。即1
[ (0, 0, 0) (1, 0, 0) (2, 0, 0) ... ]
当我们对一句简单的utterance进行解码,由于说话时序的,此时Index如下所示1
[ (0, 0, 0) (0, 1, 0) (0, 2, 0) ... ]
但我们在一个网络中使用时序上下文,其n和t都可能是不同的。1
[ (0, -1, 0) (0, 0, 0) (0, 1, 0) (1, -1, 0) (1, 0, 0) (1, 1, 0) ... ]
Index有一个默认的操作符,会依次根据n,t,x进行排序,当我们在代码中对这些vector进行打印时,我们通常看到的是其压缩的形式,其中x被省略了,t用一个范围表示,如下:1
[ (0, -1:1) (1, -1:1) ... ]
2.2、CIndex
CIndex是(int32, Index)的一对值,其中int32与网络的结点相对应,前面提到,一个神经网络由一系列的Component和Component-node组成,这里的int32就是与component-node的索引相对应。CIndex与一个特定的结点的输出相对应,通常情况下,CIndex和矩阵的行是一一对应的,与Index不同的是,CIndex除了会告诉我们矩阵的行,还会告诉我们来自哪个矩阵,比如一个affine1的结点,索引为2,输出是1000维的向量,CIndex(2,(0,0,0))则与一个列数为1000的矩阵的某些行对应。
2.3、ComputationGraph
一个ComputationGraph通过CIndex表示一个有向图。每个Cindex都有一个其他CIndex的列表的信息,比如说,在一个简单的前馈网络结构中,为了表达清楚,这里使用名字而不是int32来表示一个结点,比如(nonlin1,(0,0,0))依赖于(affine1,(0,0,0)),nonlin1,(1,0,0))依赖于(affine1,(1,0,0))。在ComputationGraph和其他地方,您将看到名为cindex_id的整型变量。每个cindex_id是存储在图中的Cindex数组中的一个索引,它标识一个特定的Cindex;cindex_id用于提高效率,因为使用单个整数比使用Cindex更容易。
2.4、ComputationRequest
一个ComputationRequest标志一组命名的输入和输出结点,每个结点都与一个Index列表关联。对于输入结点,Index列表标志哪些Index用来计算,对于输出结点,Index列表标志哪些Index需要计算,除此之外,ComputationRequest保存了一些标志,比如哪些输入输出需要计算或者提供反向传播的导数,模型模块是否需要更新。
例如,ComputationRequest可能指定有一个的输入结点,即Index列表 [(0,-1,0),(0,0,0),(0,1,0)];还有一个输出结点,即Index列表 [(0,0,0)]。因为对于语音来说,需要前后的上下文。实际上,我们通常只会在训练中像这样请求单独的输出帧;在训练过程中,我们通常在minibatch中有多条数据,所以索引的“n”维度也会有所不同。
目标函数及其导数的计算不属于核心神经网络框架;我们把这个留给用户。神经网络一般可以有多个输入和输出结点;这在多任务学习或处理多种不同类型输入数据的框架(例如多任务学习)中可能很有用。
2.5、NnetComputation
Nnetcomputing表示由Nnet和ComputationRequest编译生成的特定计算。它包含一系列命令,每个命令可以是一个传播操作、一个矩阵复制或添加操作,以及其他各种简单的矩阵命令,例如将特定行从一个矩阵复制到另一个矩阵;反向传播操作命令、矩阵大小调整命令等等。计算所作用的变量是一个矩阵列表,以及可能使用的矩阵行或列范围的子矩阵。
2.6、NnetComputer
NnetComputer对象负责实际执行Nnetcomputing。这段代码实际上非常简单(主要是一个带有switch语句的循环),因为大部分复杂性发生在编译和优化Nnetcomputing期间。
3、Neural networks in nnet3
3.1、Component
对于nnet3中的Componnet, 有Propagate和Backprop两个最重要的方法,可能包含其他一些参数(比如affine layer)或者只是实现了一个固定的非线性函数(比如Sigmoid component)。Component类最重要的部分如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class 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;
...
};
每个确定的组件都有一个输入的维度和输出的维度,根据这些维度一行一行的进行转换数据,在Propagate()中的in和out则具有相同数目的行,input的每行处理之后生成output的行。这意味中输入和输出对应Index是一样的。在Backprop有相似的逻辑。
Component有一个虚函数会返回一个位掩码,包含可以各种二进制标志,这二进制标志定义在枚举ComponentProperties。1
2
3
4
5class Component {
...
virtual int32 Properties() const = 0;
...
};
定义的二进制标志1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50//ComponentProperties
enum ComponentProperties {
kSimpleComponent = 0x001, // true if number of rows of input equals number of rows
// of output and this component doesn't care about the indexes
// (i.e. it maps each row of input to each row of output without
// regard to the index values). Will normally be true.
kUpdatableComponent = 0x002, // true if the component has parameters that can
// be updated. Components that return this flag
// must be dynamic_castable to type
// UpdatableComponent (but components of type
// UpdatableComponent do not have to return this
// flag, e.g. if this instance is not really
// updatable).
kPropagateInPlace = 0x004, // true if we can do the propagate operation in-place
// (input and output matrices are the same).
// Note: if doing backprop, you'd also need to check
// that the kBackpropNeedsInput property is not true.
kPropagateAdds = 0x008, // true if the Propagate function adds to, rather
// than setting, its output, for non-in-place
// propagation. The Component chooses whether to add
// or set, and the calling code has to accommodate
// it.
kReordersIndexes = 0x010, // true if the ReorderIndexes function might reorder
// the indexes (otherwise we can skip calling it).
// Must not be set for simple components.
kBackpropAdds = 0x020, // true if the Backprop function adds to, rather than
// setting, the "in_deriv" output for non-in-place
// backprop. The Component chooses whether to add or
// set, and the calling code has to accommodate it.
kBackpropNeedsInput = 0x040, // true if backprop operation needs access to
// forward-pass input.
kBackpropNeedsOutput = 0x080, // true if backprop operation needs access to
// forward-pass output (e.g. true for Sigmoid).
kBackpropInPlace = 0x100, // true if we can do the backprop operation in-place
// (input and output matrices may be the same).
kStoresStats = 0x200, // true if the StoreStats operation stores
// statistics e.g. on average node activations and
// derivatives of the nonlinearity, (as it does for
// Tanh, Sigmoid, ReLU and Softmax).
kInputContiguous = 0x400, // true if the component requires its input data (and
// input derivatives) to have Stride()== NumCols().
kOutputContiguous = 0x800, // true if the component requires its input data (and
// output derivatives) to have Stride()== NumCols().
kUsesMemo = 0x1000, // true if the component returns a void* pointer from its
// Propagate() function that needs to be passed into the
// corresponding Backprop function.
kRandomComponent = 0x2000 // true if the component has some kind of
// randomness, like DropoutComponent (these should
// inherit from class RandomComponent.
};
这些属性标识组件的各种特征,比如它是否包含可更新参数(kUpdatableComponent)、它的propagate函数是否支持就地操作(kpropagation inplace)以及其他各种东西。这些大部分属性在进行优化的过程中使用,所以从这里可以知道哪些优化是可用的。您还将注意到属性kSimpleComponent。如果设置了,则组件是“simple”,这意味着它将按照上面定义的那样逐行转换数据。non-simple组件可能允许不同行数的输入和输出,并且可能需要知道在输入和输出中使用什么索引。Progate和Backprop的const ComponentPrecomputedIndexes *indexes参数仅供非简单组件使用。
现在,请假设所有组件都是simple的,因为我们还没有实现任何non-simple组件,并且因为实现任何标准方法(RNNs、LSTMs等等)都不需要它们。与nnet2框架不同,组件不负责实现跨帧的拼接;相反,我们使用 Descriptors来处理它,如下所述。
3.2、Component-node
我们之前解释过,神经网络是一组命名的组件和一个关于“网络结点”的图,但是我们还没有解释什么是“网络结点”。NetworkNode实际上是一个结构体。NetworkNode可以是四种不同类型中的一种,由NodeType enum定义:1
enum NodeType { kInput, kDescriptor, kComponent, kDimRange };
其中最重要的三个是kInput、kDescriptor和kComponent(kDimRange支持将结点的输出分割成不同的部分)。kComponent结点是网络的“肉”,而Descriptor(冒着混合隐喻的风险)则是将其连接在一起的“胶水”,支持帧拼接和递归等操作。kInput结点非常简单,只提供一个地方来转储所提供的输入并声明其维度;他们什么都不做。您可能会惊讶于没有kOutput结点。原因是输出结点只是Descriptor。有一个规则,kComponent类型的每个结点必须在结点列表的前面紧跟着它自己的kDescriptor类型的结点;即每个kComponent类型的结点都必须在它的kDescriptor类型结点的后面。因此,一个类型为kDescriptor的结点如果没有立即紧接一个kComponent结点,则该结点将被绑定为一个输出结点;
3.3、Neural network config file
Neural network可以从配置文件创建。我们在这里给出一个非常简单的例子来说明配置文件如何与Descriptors相关联。这个网络有一个隐藏层,并在第一个结点上随着时间进行拼接:1
2
3
4
5
6
7
8
9
10
11
12# 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=LogSoftmaxComponent dim=115
# Next the nodes
input-node name=input dim=12
component-node name=affine1_node component=affine1 input=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=affine2 input=nonlin1
component-node name=output_nonlin component=logsoftmax input=affine2
output-node name=output input=output_nonlin
在配置文件中没有对Descriptors的引用(例如,没有“descriptors-node”)。相反,“input”字段(例如input =Append(…))是Descriptors。配置文件中的每个组件结点都被扩展为两个结点:一个是kComponent类型的结点,另一个是“input”字段定义的紧接在前面的kDescriptor类型的结点。
上面的配置文件没有给出dim-range结点的示例。dim-range结点的基本格式是这样的(本例将从组件affine1的65个维度中选取前50个维度):1
dim-range-node name=dim-range-node1 input-node=affine1_node dim-offset=0 dim=50
3.4、Descriptors in config files
描述符是一种非常有限的表达式类型,它引用图中其他结点中定义的变量。描述符是将组件连接在一起的粘合剂的一部分,它们负责对组件的输出扩展或求和,以便它们可以用作以后组件的输入。在本结中,我们将从配置文件格式的角度描述描述符;下面我们将解释它们如何出现在代码中。
最简单的描述符类型(基本情况)只是一个结点名,例如“affine1”(这里只允许出现kComponent或kInput类型的结点,以简化实现)。我们将在下面列出描述符中可能出现的一些类型的表达式,但是请记住,这个描述将为您提供描述符的样子,它比实际情况更一般;实际上,这些可能只出现在某个层次结构中,我们将在本页后面更精确地描述它。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 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>)
现在我们将描述代码内部使用的实际语法,这与上面的简化版本不同,因为表达式可能只出现在特定的层次结构中。这种语法也更接近于实际代码中的类名。读取描述符的代码试图以一种尽可能通用的方式对它们进行规范化,以便几乎所有上述语法都可以被读取并转换为内部表示。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20;;; <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>
描述符的设计应该有足够的限制,这样得到的表达式将相当容易计算(并生成反向传播代码)。当涉及到将组件连接在一起时,它们只应该只是heavy lifting,而任何更有趣或非线性的操作都应该在组件本身中执行。
注意:如果有必要对各种长度未知的索引(例如文件中的所有“t”值)进行求和或求平均值,我们打算在组件(非简单组件)中进行,而不是使用Descriptor。
3.5、Descriptors in code
我们将自底向上描述代码中的Descriptors 。基类ForwardingDescriptor 处理只引用单个值的Descriptor类型,没有任何Append(…)或Sum(…)表达式之类的。这个接口中最重要的函数是MapToInput():1
2
3
4
5class ForwardingDescriptor {
public:
virtual Cindex MapToInput(const Index &output) const = 0;
...
}
这个函数,给定一个请求的Index,返回一个CIndex。有几个ForwardingDescriptor 的派生类,包括SimpleForwardingDescriptor(基本情况,只包含一个结点索引)、OffsetForwardingDescriptor、ReplaceindexForwardingDescriptor等。
接下来是SumDescriptor,它支持表达式Sum(1
2
3
4
5
6class SumDescriptor {
public:
virtual void GetDependencies(const Index &ind,
std::vector<Cindex> *dependencies) const = 0;
...
};
函数GeyDependencies将添加所有可能涉及到这个Index计算的CIndex到dependecies中去。接下来,我们需要担心当某些请求的输入可能无法计算时会发生什么情况(例如,由于输入数据有限或边缘效应)。函数IsComputable()处理这个问题:1
2
3
4
5
6
7
8class SumDescriptor {
public:
...
virtual bool IsComputable(const Index &ind,
const CindexSet &cindex_set,
std::vector<Cindex> *input_terms) const = 0;
...
};
在这里,CindexSet对象是一组Cindexes的表示,在此上下文中,表示“我们知道的所有可计算Cindex的集合”。如果这个Index的Descriptor是可计算的,函数将返回true。例如,表达式Sum(X, Y)只有在X和Y可计算时才可计算。如果这个函数将返回true,那么它还将只向“input_terms”追加实际出现在求值表达式中的输入Cindexes。例如,在Failover(X, Y)的表达式中,如果X是可计算的,那么只有X会被加入到input_terms中,而Y不会。
4、more detail
4.1、Neural network nodes (detail)
现在我们将更详细地描述神经网络结点。如上所述,enum定义了四种类型的结点kInput, kDescriptor, kComponent, kDimRange。
实际的NetworkNode是一个结构体。为了避免指针的麻烦,因为c++不允许包含类的union,所以我们的布局有点乱:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct NetworkNode {
NodeType node_type;
// "descriptor" is relevant only for nodes of type kDescriptor.
Descriptor descriptor;
union {
// For kComponent, the index into Nnet::components_
int32 component_index;
// for kDimRange, the node-index of the input node.
int32 node_index;
} u;
// for kInput, the dimension of the input feature. For kDimRange, 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的components_数组。
- kDimRange结点只使用“node_index”、“dim”和“dim_offset”。
4.2、Neural network (detail)
我们将更详细地介绍类Nnet本身,它存储整个神经网络。最简单的解释方法就是列出私有数据成员:1
2
3
4
5
6
7
8
9
10class 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_相同的大小;这些名称与组件和结点相关联。注意,通过在对应组件结点的名称后面附加“_input”,
我们自动为kDescriptor类型的结点分配名称,这些结点位于对应的kComponent类型结点之前。kDescriptor结点的这些名称不会出现在神经网络的配置文件表示中。
4.3、NnetComputation (detail)
另一种重要的数据类型是结构体NnetComputing。这表示编译后的神经网络计算,包含一系列命令以及解释这些命令所需的其他信息。它在内部定义了许多类型,包括以下enum值:1
2
3
4
5
6enum CommandType {
kAllocMatrixUndefined, kAllocMatrixZeroed,
kDeallocMatrix, kPropagate, kStoreStats, kBackprop,
kMatrixCopy, kMatrixAdd, kCopyRows, kAddRows,
kCopyRowsMulti, kCopyToRowsMulti, kAddRowsMulti, kAddToRowsMulti,
kAddRowRanges, kNoOperation, kNoOperationMarker };
我们想突出kPropagate、kBackprop和kMatrixCopy作为命令的自解释示例。有一个Command的结构体,它表示一个命令及其参数。大多数参数都是矩阵和组件列表中的索引。1
2
3
4
5
6
7
8
9struct Command {
CommandType command_type;
int32 arg1;
int32 arg2;
int32 arg3;
int32 arg4;
int32 arg5;
int32 arg6;
};
还定义了一些结构类型,用于存储矩阵和子矩阵的大小信息。子矩阵是一个可能受限制的矩阵行和列范围,如matlab语法some_matrix(1:10, 1:20):1
2
3
4
5
6
7
8
9
10
11struct 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;
};
结构体NnetComputing的数据成员包括:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct 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卡)。
- 本文链接:http://joefi.github.io/2019/05/18/Kaldi中的nnet3/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!