BrainScript漫步
本节介绍“brainscript”语言的基本概念。一种新的语言?不要担心,这是非常直接的。
在CNTK中,在CNTK网络描述语言”brainscript”中描述使用BrainScriptNetworkBuilder
来定制网络定义 。同样,网络的描述被称为”brainscript”。
brainscript提供了一种简单的方式来定义网络,它使用表达式、变量、原始和自定义函数,嵌套块,和其他很好的理解概念。类似于脚本语言的语法。
ok,让我们开始一个完整的brainscript例子!
一个完整的brainscript例子网络定义
下面的例子显示了一个隐藏层和一个分类层的简单的神经网络的网络描述。不妨先花上几分钟的时间来试着猜一下这是什么意思。当你阅读读下去的时候,你会发现,你猜对了大部分。
BrainScriptNetworkBuilder = { # (we are inside the train section of the CNTK config file)
SDim = 28*28 # feature dimension
HDim = 256 # hidden dimension
LDim = 10 # number of classes
# define the model function. We choose to name it 'model()'.
model (features) = {
# model parameters
W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}
W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}
# model formula
r = RectifiedLinear (W0 * features + b0) # hidden layer
z = W1 * r + b1 # unnormalized softmax
}.z
# define inputs
features = Input {SDim}
labels = Input {LDim}
# apply model to features
z = model (features)
# define criteria and output(s)
ce = CrossEntropyWithSoftmax (labels, z) # criterion (loss)
errs = ErrorPrediction (labels, z) # additional metric
P = Softmax (z) # actual model usage uses this
# connect to the system. These five variables must be named exactly like this.
featureNodes = (features)
inputNodes = (labels)
criterionNodes = (ce)
evaluationNodes = (errs)
outputNodes = (P)
}
BrainScript Syntax Basics
关于brainscript语法的几个一般描述。
brainscript采用一种简单的语法,旨在像数学公式一样表达神经网络。因此,最基本的语法单位是assignment,可以用来定义变量和函数。例如:
Softplus (x) = Log (1 + Exp (x))
h = Softplus (W * v + b)
Lines, Comments, Include
一条执行语句可以放在单行上,也可以放在多行上。还可以多个执行语句放在一行上,但是需要用一个分号来分开。例如:
SDim = 28*28 ; HDim = 256 ; LDim = 10 # feature, hidden, and label dimension
BrainScript不是空格敏感。
BrainScript 可以支持在行尾 Python 风格 # 和 C++风格 //的注释. 还有 C 语法的内联注释(/* this is a comment*/
),但与c不同的是注释不能被分割成多行。
进入路径时可以在文件内容中的任何地方插入include "PATH”
语句。支持绝对和相对路径(有或没有子目录都可以)。如果是相对路径的话,按照以下顺序搜索:当前工作目录;include的目录;配置文件所在的目录;最后是含有CNTK可执行程序的目录。所有的brainscript函数都会默认引入一个cntk.core.bs
脚本文件,该文件于CNTK可执行文件属于同一目录。
表达式
接下来是描述你的网络的brainscript表达式。brainscript表达式的语法类似于数学公式。最简单的表达式是常量,例如数字和字符串。类数学公式的例子是W1 * r + b
。另一个常见的表达是函数调用,例如RectifiedLinear (.)
。
BrainScript 是一种动态语言。一个重要的表达式类型是record,使用{...}
语法定义,并通过点语法访问。例如,r = { x = 13 ; y = 42 }
分配一个记录两成员r,其中第一个成员可以访问r.x.
除了通常的数学运算符,BrainScript 有条件表达式(if c then t else f
),数组表达式,和简单的Lambda表达式。最后,对接CNTK C++代码,BrainScript 可以直接实例化有限的一组预定义的C++对象,主要是计算网络组成的ComputationNode
。有关更多细节,请参见表达式。
BrainScript 表达式是在于第一次使用时开始计算的。BrainScript 的主要目的是描述网络,所以一个表达式的值往往不是最终的价值,而是在描述之后图计算中的一个节点(如在W1 * r + b
)。只有BrainScript 表达式标量(如28×28)是计算时直接计算的。没有执行的表达式(例如,由于条件)从来就不会进行计算。
注:现在不推荐的NDLNetworkBuilder
版本仅支持函数调用语法;例如,上述的公式会被写成Plus (Times (W1, r), b1)
.。
变量
变量可以保存任何BrainScript 表达式的值(数字、字符串、数组、记录、λ、CNTK C++对象),并可以在表达式中替代。变量是不可变的,即只赋值一次。例如,上面的网络例子中的开头部分:
SDim = 28*28
HDim = 256
LDim = 10
这里的变量被设置为在随后的表达式中用作参数的标量数值。这些值是在训练中使用的数据样本,隐藏层和标签的尺寸。这个例子中的设置是MNIST数据集,这是一个收集的28 x 28
像素的图像。每个图像是一个手写体数字(0-9),所以可能有10个标签,可以应用到每个图像。隐藏的激活维HDim
是一个用户的选择。
大多数变量是记录成员(外部引入的BrainScript 块是隐式的记录)。此外,变量可以是函数参数,也可以作为数组元素存储。
定义网络
一个网络主要就是通过对输入的公式描述计算输出,我们称这种模型的函数,它通常在BrainScript的具体函数中定义。作为模型函数的一部分,用户必须声明模型参数。最后,必须定义网络的输入和标准/输出。所有这些都被定义为变量。输入和标准/输出变量必须被传递到系统。
网络的模型函数和模型参数
模型函数包含具体的网络公式和相应的模型参数。我们的例子使用了矩阵输入和内部提供的能量函数rectifiedlinear(),所以网络的核心功能是由这些方程:
r = RectifiedLinear (W0 * features + b0)
z = W1 * r + b1
模型参数是矩阵,偏置向量,或构成学习模型后训练出的其他值。使用模型参数张量将输入样本数据转换为所需的输出,并通过学习过程中更新。上面的示例网络包含以下矩阵参数:
W0 = ParameterTensor {(HDim:SDim)}
b0 = ParameterTensor {(HDim)}
在这个例子中,W0
是权重矩阵B0
是偏差。ParameterTensor{}
为CNTK内部函数,作用是实例化一个向量,矩阵,或任意阶张量,并将维数参数转化为brainscript数组(通过冒号:级联数字)。向量的维数是一个单一的数字,而矩阵维数应定义为(numrows:numcols)。默认情况下,当通过层henormal时,当直接实例化时, 参数使用均匀随机数来初始化,其他选项的见这里的完整描述。与常规的函数不同,parametertensor { }
使用大括号来代替圆括号()
。BrainScript 的约定中,大括号的功能是创建参数或对象,而不是一个函数。
brainscript函数的形式是f(x)=an expression of x
,比如说SQR(x)=×××
我们上述例子中,实际的模型函数有点复杂:
model (features) = {
# model parameters
W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}
W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}
# model formula
r = RectifiedLinear (W0 * features + b0) # hidden layer
z = W1 * r + b1 # unnormalized softmax
}.z
来解释下外面的 { ... }
和最后的 .z
,大括号里面的内容是定义了6个变量(W0, b0, W1, b1, r, and z
).模型函数的值是z,可以通过z.的形式来访问,其他的外部变量都无法直接访问。语句{ ... ; x = ... }.x
是一种使用内部变量的用法.
试试将上面的语法使用单一表达式的方式来实现,如下:
model (features) = ParameterTensor {(LDim:HDim)} * (RectifiedLinear (ParameterTensor {(HDim:SDim)}
* features + ParameterTensor {HDim})) + ParameterTensor {LDim}
这个方式很难读,更重要的是不允许一个变量在表达式的多个地方出现。
输入inputs
如下例子,inputs的作用是将样本和标签输入到神经网络:
features = Input {SDim}
labels = Input {LDim}
Input{}
是定义cntk网络时需要的第二个特殊的函数(第一个是Parameter{}
),它将网络外面的输入接收为一个变量:通过reader
。Input{}
的参数是数据维数,在这个例子中,features
输入的参数就是样本数据的维数(我们定义成的SDim
),labels
输入的参数就是标签数据的维数,input的变量名称与reader中的参数名称一致。
训练标准和网络输出
我们仍然需要声明网络的输出是如何与世界交互的。我们的模型函数计算逻辑值(非归一化日志概率)。这些逻辑值可以用来
声明网络标准
测量精度
Ctrl + I
给定一个输入计算输出类的概率,根据一个分类的决定(注意,非标准化的日志后
Z
通常可以用来直接分类)。
ce = CrossEntropyWithSoftmax (labels, z)
CrossEntropyWithSoftmax()
将输入的样本通过softmax()
函数计算出的结果,与真实的值label
比较,使用cross-entropy
算出错误值,这个错误值通过反向传播(BP)来更新网络中的参数,因此,上面例子中,我们通过softmax()
的归一化计算出来的值p,并不是在训练中使用,而是要得到p需要使用网络来计算。(请注意,在许多情况下,Z通常是足够的分类,在这种情况下,Z本身将是输出)
CNTK使用Stochastic Gradient Descent (SGD) 在做为学习算法,SGD需要使用全模型参数来计算目标函数的梯度,更重要的是,SGD不要求用户指定的梯度,相反,CNTK每一个内置函数都有其对应的函数导数。系统自动执行网络参数的反向传播更新。这部分对用户不可见。用户再也不用关注梯度。
除了训练标准,计算在训练阶段还要经常预测错误率,通过不断的训练,来验证和改进网络系统。CNTK使用如下的函数来处理:
errs = ClassificationError (labels, z)
这个概率是网络通过比较正确标签和错误率计算而来的。通常是由系统来显示,虽然这是有用的,但它不是强制性使用ClassificationError()
.
Inputs, Outputs, and Criteria与系统的通信
既然所有变量都定义完了,我们必须告诉系统哪些变量应该当作输入、输出和标准来处理。通过定义如下5个特殊的变量来实现这一过程:
featureNodes = (features)
labelNodes = (labels)
criterionNodes = (ce)
evaluationNodes = (errs)
outputNodes = (z:P)
它的值是数组,支持用冒号分隔(冒号:是一个brainscript中通过级联两个值或数组的形式)。上面的outputnodes表明Z和P都为输出节点。
注:不推荐使用ndlnetworkbuilder
要求数组中的元素是由逗号分隔的。
概述
上述的7个特殊名称我们必须要知道:
ParameterTensor{}
: 初始化和声明一个可学习的参数Input{}
:声明一个可以从data reader输入数据的变量featureNodes, labelNodes, criterionNodes, evaluationNodes, and outputNodes
: 声明系统中于输入、输出、标准相关的特殊名称变量。
另外,本文未提到的还有3个CNTK内置的特殊函数:
Constant()
: 声明一个常数PastValue()
andFutureValue()
: 在循环中不同的时间步长中访问网络函数中的变量。
还有一些其他的内置函数,例如用c++实现的Sigmoid()
or Convolution()
,在BrainScript 中实现了预定义的库函数例如BS.RNNs.LSTMP()
,还有作为库函数命名空间的(例如:BS.RNNs
)参看 Full-Function-Reference 的全部介绍