你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

BrainScript 基本概念

BrainScript--A Walk-Through

本部分介绍“BrainScript”语言的基本概念。 新语言? 别担心 & 继续阅读,这是非常直接的。

在 CNTK 中,自定义网络使用 BrainScriptNetworkBuilder CNTK 网络描述语言“BrainScript”中的 和 描述来定义。同样,网络描述称为大脑脚本。

BrainScript 提供了一种简单的方式来使用表达式、变量、基元和自定义函数、嵌套块和其他众所周知的概念以类似代码的方式定义网络。 它在语法上看起来类似于脚本语言。

好的,让我们用一个完整的 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 语法基础知识

在深入探讨之前,请记下一些有关 BrainScript 语法的一般说明。

BrainScript 使用一个简单的语法,旨在以类似于数学公式的方式表达神经网络。 因此,基本语法单位是赋值,它同时用于变量赋值和函数定义。 例如:

Softplus (x) = Log (1 + Exp (x))
h = Softplus (W * v + b)

行、批注、包括

尽管赋值通常写在单行上,但表达式可以跨多行。 但是,若要将多个赋值放在一行上,则必须用分号分隔它们。 例如:

SDim = 28*28 ; HDim = 256 ; LDim = 10    # feature, hidden, and label dimension

除了要求在没有换行符的情况下在作业之间使用分号外,BrainScript 不区分空格。

BrainScript 使用 Python 样式和 C++//样式 # 了解行尾注释。 内联注释使用 C 语法 (/* this is a comment*/) ,但与 C 不同,这些注释可能不会跨多行。

对于 CNTK 配置文件中嵌入的 BrainScript (,而不是通过 include 指令) 从单独文件读取 BrainScript,由于与配置分析程序交互, (有些奇怪) 附加限制,即任何括号、大括号或括号都必须在 C/C++ 样式注释和字符串文本中均衡。 因此,C/C++样式的注释中没有笑脸!

include "PATH"指令可以在任何位置使用,以在 语句的点插入文件的内容。 此处, PATH 可以是具有或不包含子目录) (的绝对路径或相对相对路径。 如果它是相对路径,则按顺序搜索以下位置:当前工作目录;目录 (包含外部文件(如果有)的) ;目录 (包含配置文件 (s) 的) ;最后是包含 CNTK 可执行文件的目录。 所有内置 BrainScript 函数都以这种方式包含在 CNTK 可执行文件旁边的名为 CNTK.core.bs 的文件中。

表达式

接下来,你需要了解 BrainScript 表达式-描述网络的公式。 BrainScript 表达式 以类似于常用编程语言的语法编写。 最简单的表达式是文本,例如数字和字符串。 类似于数学的示例是 W1 * r + b,其中 * 引用标量、矩阵或张量乘积,具体取决于变量的类型。 另一种常见的表达式是函数调用,例如 RectifiedLinear (.)

BrainScript 是一种动态类型化语言。 一个重要的表达式类型是 记录,使用 {...} 语法定义,并通过点语法访问。 例如, 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) )的 BrainScript 表达式。 从不 (使用表达式,例如,由于条件) ,永远不会计算。

注意:现在弃用 NDLNetworkBuilder 的版本仅支持函数调用语法;例如,必须编写 Plus (Times (W1, r), b1)

变量

变量可以保存任何 BrainScript 表达式的值, (数字、字符串、记录、数组、lambda、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 函数中。 BrainScript 函数以 的形式 f(x) = an expression of x声明。 例如, Sqr (x) = x * x 是有效的 BrainScript 函数声明。 不可能更简单和直接,对吧?

现在,上述示例的实际模型函数稍微复杂一点:

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 (、b0W1b1rz) 。 但是,模型函数的值只是 z;所有其他值都是函数的内部值。 因此,我们使用 .z 来选择要返回的记录成员。 这只是用于访问记录成员的点语法。 这样,其他记录成员就不能从外部访问。 但它们继续作为表达式的一部分存在以计算 z。 模式 { ... ; x = ... }.x 是使用局部变量的一种方式。

请注意,记录语法不是必需的。 或者, model(features) 也可以通过记录声明为不绕道,作为单个表达式:

model (features) = ParameterTensor {(LDim:HDim)} * (RectifiedLinear (ParameterTensor {(HDim:SDim)}
                   * features + ParameterTensor {HDim})) + ParameterTensor {LDim}

这更难理解,更重要的是,不允许在公式中的多个位置使用相同的参数。

输入

网络输入由示例数据和与示例关联的标签定义:

features = Input {SDim}
labels   = Input {LDim}

Input{} 是模型定义所需的第二个特殊 CNTK 基元, (第一个是 Parameter{}) 。 它创建一个从网络外部接收输入的变量:从读取器。 的参数 Input{} 是数据维度。 在此示例中, features 输入将具有我们在变量 SDim) 中定义的示例数据 (维度,输入 labels 将具有标签的维度。 输入的变量名称应与读取器定义中的相应条目匹配。

训练条件和网络输出

我们仍然需要声明网络的输出如何与世界交互。 模型函数 (非规范化对数概率) 计算 logit 值。 这些 logit 值可用于

  • 定义训练条件,
  • 度量准确度,以及
  • 计算给定输入的输出类的概率,以基于 (分类决策,请注意,未规范化的对数后计 z 通常可直接) 进行分类。

示例网络使用类别标签,这些标签表示为单热向量。 对于 MNIST 示例,这些值将显示为包含 10 个浮点值的数组,除 1.0 的正确标签类别外,所有这些浮点值均为零。 像我们这样的分类任务通常使用 SoftMax() 函数来获取每个标签的概率。 然后对网络进行优化,将正确类 (交叉熵) 的对数概率最大化,并将所有其他类的对数概率降到最低。 这是我们 的训练标准,或 损失函数。 在 CNTK 中,为了提高效率,这两个操作通常组合在一个函数中:

ce = CrossEntropyWithSoftmax (labels, z)

CrossEntropyWithSoftmax() 函数接受输入,计算 SoftMax() 函数,使用交叉熵从实际值计算误差,并且该错误信号用于通过反向传播更新网络中的参数。 因此,在上面的示例中,在训练期间不使用我们计算为 P的规范化Softmax()值。 但是, 使用 网络 (需要再次注意,在许多情况下,对于分类来说, z 这通常就足够了;在这种情况下, z 本身将是输出) 。

CNTK 使用 随机梯度下降 (SGD) 作为学习算法。 SGD 需要计算目标函数相对于所有模型参数的梯度。 重要的是,CNTK 不需要用户指定这些渐变。 相反,CNTK 中的每个内置函数也有对应的派生函数,系统会自动执行网络参数的反向传播更新。 这对用户不可见。 用户永远无需关注渐变。 永远。

除了训练条件之外,预测的错误率通常还会在训练阶段进行计算,以验证系统的改进,因为训练正在进一步进行。 这在 CNTK 中使用以下函数进行处理:

errs = ClassificationError (labels, z)

将网络生成的概率与实际标签进行比较,并计算错误率。 这通常由系统显示。 虽然这很有用,但并非必须使用 ClassificationError()

将输入、输出和条件传达给系统

定义所有变量后,我们必须告诉系统它应将哪一个变量视为输入、输出和条件。 这是通过定义 5 个必须恰好具有以下名称的特殊变量来完成的:

featureNodes    = (features)
labelNodes      = (labels)
criterionNodes  = (ce)
evaluationNodes = (errs)
outputNodes     = (z:P)

值是数组,其中值应用冒号分隔, (冒号 : 是 BrainScript 运算符,它通过将两个值或数组) 串联来形成数组。 上面显示了这一点,它 outputNodes同时 z 将 和 P 声明为输出。

(注意:已弃用 NDLNetworkBuilder 的 需要改为用逗号分隔数组元素。)

特殊名称摘要

如上所示,我们必须注意 7 个特殊名称,它们带有“magic”属性:

  • ParameterTensor{}:声明并初始化可学习参数。
  • Input{}:声明一个从数据读取器进行连接和馈送的变量。
  • featureNodeslabelNodescriterionNodesevaluationNodes、 和 outputNodes:向系统声明要用作输入、输出和条件的变量。

此外,CNTK 中还有 3 个具有内置“magic”的更多特殊函数,这些函数在其他地方进行了讨论:

  • Constant():声明常量。
  • PastValue()FutureValue():在不同的时间步调访问网络函数中的变量,以形成循环。

任何其他预定义名称要么是内置基元函数(如 Sigmoid()Convolution() ),要么使用 C++ 实现、在 BrainScript 中实现的预定义库函数(例如 BS.RNNs.LSTMP()),要么是充当库函数 (命名空间的记录,例如 BS.RNNs) 。 有关完整列表,请参阅 BrainScript-Full-Function-Reference

下一步: BrainScript 表达式