2017 年 1 月

第 32卷,第 1 期

此文章由机器翻译。

机器学习 - 探索 Microsoft CNTK 机器学习工具

作者:James McCaffrey

Microsoft 计算网络工具包 (CNTK) 是一个非常强大的命令行系统,可以创建神经网络预测系统。CNTK 最初是出于在 Microsoft 内部使用的目的而开发的,因而文档有些晦涩难懂。在本文中,我将介绍安装 CNTK、设置演示预测问题、创建神经网络模型、进行预测并解释结果的过程。

图 1 概述了 CNTK 的概念及本文目的。虽然它在图像中不可见,但我通过输入命令运行 CNTK 工具:

> cntk.exe configFile=MakeModel.cntk makeMode=false

CNTK 的运作方式
图 1 CNTK 的运作方式

该图仅显示输出消息的最后一部分。扩展名为 .cntk 的配置文件包含有关输入文件和要使用的神经网络设计的信息。创建预测模型并将其保存到磁盘。

创建预测模型后,我用它来进行预测。输入值在文件 NewData.txt 中且值为 (4.0, 7.0)。CNTK 工具与名为 MakePrediction.cntk 的第二个配置文件一起使用,以计算预测。预测结果保存到文件 Prediction_txt.pn,且结果为 (0.271139, 0.728821, 0.000040),这意味着预测结果是三个可能输出值中的第二个。

本文假设你已熟悉命令行程序并对什么是神经网络有了大致了解,但不假设你是机器学习 (ML) 专家或全面了解 CNTK。还可以从随附的下载中获取代码和数据。

设置问题

想象一个场景,你想从一个人的年龄和年收入预测一个人的政治倾向(保守、温和、自由)。这称为分类问题。这一想法采用一些具有已知值的数据并创建预测模型。此处可将模型视为一种复杂的数学函数,可接受两个数字输入值,然后发出一个指示三个类之一的值。

现在看看图 2 中的图表。存在 24 个数据点。两个在 ML 术语中名为特性的输入变量是 x0 和 x1。三种颜色表示三种不同的类,有时在 ML 术语中称为标签。虽然可以快速查看某个模式,但对于计算机系统来说,即使尝试为此简单数据集创建预测模型也是非常具有挑战性的。

定型数据
图 2 定型数据

CNTK 使用图 2 中所示的数据创建神经网络预测模型后,该模型将应用于图 3 中所示的九个测试数据点。你会看到,CNTK 正确预测了 9 个测试用例中的 8 个。实际是“red”类的 (8.0, 8.0) 处的测试项将被错误地预测为“blue”类。

测试数据(开圆圈)
图 3 测试数据(开圆圈)

安装 CNTK

只需从 GitHub 下载一个 .zip 文件夹并解压缩文件即可安装 CNTK。主 CNTK门户网站位于 github.com/Microsoft/CNTK。在此页面上可找到当前版本的链接 (github.com/Microsoft/CNTK/releases)。CNTK 的关键功能之一是,它可以选择使用 GPU 而不是计算机的 CPU。版本页面提供下载仅 CPU 版本或 GPU + CPU 版本的二进制文件两个选择。

出于演示目的,即使可以指示 GPU + CPU 版本使用仅 CPU 版本,我仍建议选择仅 CPU 版本。单击版本页面上的相关链接,你可以转到需要接受某些许可条款的页面,点击“接受”按钮后,会看到一个对话框,可在其中使用类似 CNTK-1-6-Windows-64bit-CPU-Only.zip 的名称将文件(当然,版本号可以不同)保存到计算机。

将 .zip 文件下载到桌面或任何便捷的目录。然后直接将所有文件解压到 C: 盘(最常见)或 C:\Program Files 目录。

解压的下载文件将有一个名为 cntk 的根目录。该根目录将包含多个目录,包括另一个同样名为 cntk 的目录,其中包含所有二进制文件,包括关键的 cntk.exe 文件。要完成安装过程,请将到 cntk.exe 文件的路径添加到系统 PATH 环境变量(通常为 C:\cntk\cntk)中。

创建数据文件

要创建和运行 CNTK 项目,需要具有扩展名为 .cntk 的配置文件,以及至少一个包含定型数据的文件。大多数 CNTK 项目也将有一个测试数据文件。此外,CNTK 工具将在项目运行时创建多个输出文件。

存在多种同时使用该文件与 CNTK 项目的方式。我建议创建保存数据文件和 .cntk 配置文件的项目根目录,并从该目录运行 CNTK。

我的计算机上已有 C:\Data 目录。对于演示,我在该目录中创建了一个名为 CNTK_Projects 的新子目录。在该目录中,我创建了一个名为 SimpleNeuralNet 的子目录作为演示项目根目录,用于保存 .cntk 文件、定型数据文件和测试数据文件。

CNTK 系统可使用多种不同类型的数据文件。该演示使用简单的文本文件。打开记事本的实例并使用图 4 中的 24 个数据项,或使用图 2 中的信息手动创建数据,然后将文件保存为 SimpleNeuralNet 目录中的 TrainData.txt。

图 4 定型数据

|features 1.0 5.0 |labels 1 0 0
|features 1.0 2.0 |labels 1 0 0
|features 3.0 8.0 |labels 1 0 0
|features 4.0 1.0 |labels 1 0 0
|features 5.0 8.0 |labels 1 0 0
|features 6.0 3.0 |labels 1 0 0
|features 7.0 5.0 |labels 1 0 0
|features 7.0 6.0 |labels 1 0 0
|features 1.0 4.0 |labels 1 0 0
|features 2.0 7.0 |labels 1 0 0
|features 2.0 1.0 |labels 1 0 0
|features 3.0 1.0 |labels 1 0 0
|features 5.0 2.0 |labels 1 0 0
|features 6.0 7.0 |labels 1 0 0
|features 7.0 4.0 |labels 1 0 0
|features 3.0 5.0 |labels 0 1 0
|features 4.0 4.0 |labels 0 1 0
|features 5.0 5.0 |labels 0 1 0
|features 4.0 6.0 |labels 0 1 0
|features 4.0 5.0 |labels 0 1 0
|features 6.0 1.0 |labels 0 0 1
|features 7.0 1.0 |labels 0 0 1
|features 8.0 2.0 |labels 0 0 1
|features 7.0 2.0 |labels 0 0 1
The training data looks like:
|features 1.0 5.0 |labels 1 0 0
|features 1.0 2.0 |labels 1 0 0
. . .
|features 7.0 2.0 |labels 0 0 1

“| features”标记表示输入值,“| labels”标记表示输出值。无论是“features”还是“labels”都不是保留字,因此,如果你愿意,可以使用类似“|predictors”和“|predicteds”的标记。值可以使用空格或制表符(演示数据使用空格)分隔。神经网络仅能理解数值,所以诸如“red”和“blue”的类标签必须编码为数字。神经网络分类器模型使用 1-of-N 编码。对于三个可能的类标签,你将使用 1 0 0 代表第一个类(演示中为“red”),01 0 代表第二个类(“blue”),0 01 代表第三个类(“green”)。由你来跟踪每个标签值如何编码。

在非演示情况下,可能需要注意输入数据标准化。在输入值大小差异很大的情况下,如果缩放数据以使所有量值大致相同,则可获得更好的结果。例如,假设输入数据是人的年龄(如 32.0)和年收入(如 48,500.00 美元)。可以通过将所有年龄值除以 10,将所有收入值除以 10,000 来预处理数据,得到标准化输入值,如 (3.20, 4.85)。输入数据标准化的三种最常见形式称为 z-分数标准化,最小-最大标准化和数量级标准化。

创建并保存 TrainData.txt 文件后,在 SimpleNeuralNet 目录中创建以下 9 项测试数据并保存为 TestData.txt 文件:

|features 1.0 1.0 |labels 1 0 0
|features 3.0 9.0 |labels 1 0 0
|features 8.0 8.0 |labels 1 0 0
|features 3.0 4.0 |labels 0 1 0
|features 5.0 6.0 |labels 0 1 0
|features 3.0 6.0 |labels 0 1 0
|features 8.0 3.0 |labels 0 0 1
|features 8.0 1.0 |labels 0 0 1
|features 9.0 2.0 |labels 0 0 1

了解神经网络输入和输出

要了解如何使用 CNTK,需要基本了解什么是神经网络,如何计算输出值以及如何解释这些输出值。查看图 5。该图显示了对应于演示问题的神经网络。

神经网络输入和输出
图 5 神经网络输入和输出

具有值 (8.0, 3.0) 的输入节点有两个,具有值 (0.3090, 0.0055, 0.6854) 的输出节点有三个。网络还具有五个隐藏的处理节点。

每个输入节点具有将其连接到所有隐藏节点的线。每个隐藏节点具有将其连接到所有输出节点的线。这些线表示名为权重的数值常量。使用基于 0 的索引标识节点,所以最顶层节点为 [0]。因此,从 input[0] 到 hidden[0] 的权重是 2.41,从 hidden[4] 到 output[2] 的权重是 -0.85。

每个隐藏节点和每个输出节点都有一个附加箭头。这些被称为偏差值。hidden[0] 的偏差为 -1.42,output[2] 的偏差为 -1.03。

输入-输出计算最好通过一个示例进行说明。首先,计算隐藏节点值。中间隐藏节点 hidden[2] 的值为 0.1054,通过对所有连接输入的乘积及其相关权重和偏差值求和,然后取该和的双曲正切值 (tanh) 来计算:

hidden[2] = tanh( (8.0)(-0.49) + (3.0)(0.99) + 1.04) )
          = tanh( -3.92 + 2.98 + 1.04 )
          = tanh( 0.1058 )
          = 0.1054

tanh 函数称为隐藏激活函数。神经网络可以使用几种不同的激活函数中的一种。除了 tanh,其他两个最常见的是逻辑 sigmoid(通常缩写为“sigmoid”)函数和线性整流函数。

计算所有隐藏节点值后,下一步是计算输出节点。首先,计算连接的隐藏节点的乘积及其相关联的权重和偏差值的和。例如,此步骤的 output[0] 为 1.0653,计算为:

output[0] = (1.0000)(-2.24) + (-0.1253)(1.18) +
            (0.1054)(0.55) + (-0.1905)(1.83) +
            (-1.0000)(-1.23) + 2.51
          = (-2.2400) + (-0.1478) +
            (0.0580) + (-0.3486) +
            (1.2300) + 2.51
          = 1.0653

同样,output[1] 计算为 -2.9547,output[2] 为 1.8619。

接下来,使用名为 softmax 的函数对三个初步输出值进行缩放,使它们总和为 1.0:

output[0] = e^1.0653 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.3090
output[1] = e^-2.9547 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.0055
output[2] = e^1.8619 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.6854

这三个值被解释为概率。因此,对于 (8.0, 3.0) 的输入和给定的权重及偏差值,输出是 (0.3090, 0.0055, 0.6854)。最高概率是第三个值,因此预测是第三个类,在此例中是“green”。

解释输出值的另一种方式是将它们进行映射,使得最高概率是 1,并且所有其他概率是 0。针对此示例,你会得到 (0, 0, 1),它映射到“green”的编码值。

确定权重和偏差的值的过程称为对网络定型,而这就是 CNTK 的用途所在。

创建配置文件

图 1 给出了如何使用 CNTK 的方法。在普通命令外壳中,我导航到位于 C:\Data\SimpleNeuralNet 的项目根目录。项目根目录包含文件 TrainData.txt 和 TestData.txt 以及 MakeModel.cntk 配置文件。通过执行如下命令调用 CNTK 工具:

> cntk.exe configFile=MakeModel.cntk makeMode=false

回想一下,系统 PATH 变量知道 cntk.exe 程序的位置,因此不必对其进行完全限定。.cntk 配置文件具有很多信息。makeMode=false 参数表示运行该程序并覆盖之前的任何结果。CNTK 命令行参数不区分大小写。

图 6 显示了配置文件的总体结构。配置文件的完整列表显示在图 7 中。

图 6 配置文件的结构

# MakeModel.cntk
command=Train:WriteProbs:DumpWeights:Test
# system parameters go here
Train = [
  action="train"
  BrainScriptNetworkBuilder = [
    # define network here
  ]
]
Test = [
  # training commands here
]
WriteProbs = [
  # output commands here
]
DumpWeights = [
  # output commands here
]

图 7 定型配置文件

# MakeModel.cntk
command=Train:WriteProbs:DumpWeights:Test
modelPath = "Model\SimpleNet.snn"
deviceId = -1
dimension = 2
labelDimension = 3
precision = "float"
# =====
Train = [
  action="train"
  # network description
  BrainScriptNetworkBuilder = [
    FDim = $dimension$
    HDim = 5
    LDim = $labelDimension$
    # define the neural network
    neuralDef (ftrs) = [
      W0 = Parameter (HDim, FDim)
      b0 = Parameter (HDim, 1) 
      W1 = Parameter (LDim, HDim)
      b1 = Parameter (LDim, 1)
      hn = Tanh (W0 * ftrs + b0)
      zn = W1 * hn + b1
    ].zn
    # specify inputs
    features = Input (FDim)
    labels   = Input (LDim)
    # create network
    myNet = neuralDef (features)
    # define training criteria and output(s)
    ce   = CrossEntropyWithSoftmax (labels, myNet)
    err  = ErrorPrediction (labels, myNet)
    pn   = Softmax (myNet)
    # connect to the NDL system.
    featureNodes    = (features)
    inputNodes      = (labels)
    criterionNodes  = (ce)
    evaluationNodes = (err)
    outputNodes     = (pn)
  ]
  # stochastic gradient descent
  SGD = [
    epochSize = 0
    minibatchSize = 1
    learningRatesPerSample = 0.04
    maxEpochs = 500
    momentumPerMB = 0.90
  ]
  # configuration for reading data
  reader = [
    readerType = "CNTKTextFormatReader"
    file = "TrainData.txt"
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
]
# test
Test = [
  action = "test"
  reader = [
    readerType = "CNTKTextFormatReader"
    file="TestData.txt"
    randomize = "false"
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
]
# log the output node values
WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="TestData.txt"       
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
  outputPath = "TestProbs_txt"
]
# dump weight and bias values
DumpWeights = [
  action = "dumpNode"
  printValues = "true"
]

可以根据需要命名 CNTK 配置文件,但使用 .cntk 作为扩展名是标准做法。可以将 # 字符或 // 令牌用于注释中,但注释不能跨行。在配置文件的顶部,给出以冒号分隔的要运行的模块列表,在这种情况下为四个:

command=Train:WriteProbs:DumpWeights:Test

注意,模块执行的顺序不必与它们定义的顺序相匹配。模块名称(在本例中为: Train、WriteProbs、DumpWeights、Test)不是关键字,所以可根据需要命名模块。注意,Train 模块具有指令 action=“train”。 这里的两个词都是关键字。

Train 模块使用定型数据文件创建预测模型,因此大多数 CNTK 配置文件将具有一个模块,通常命名为 Train,其中包含 action=“train” 命令。Train 模块以二进制格式将生成的模型信息写入磁盘。

Test 模块是可选的,但通常在创建模型时即包括在内。Test 模块将使用新创建的预测模型来评估定型数据的总体预测精度和预测误差。

WriteProbs 模块是可选的。该模块将把测试数据项的实际预测值写入项目根目录中的文本文件。这样就可以准确地查看正确预测了哪些测试用例,哪些未作出正确预测。

DumpWeights 模块将编写一个文本文件,其中包含定义预测模型的神经网络权重和偏差。可以使用此信息来发现故障点,并对之前未发现的新数据进行预测。

系统参数

MakeModel.cntk 配置文件设置五个系统参数:

modelPath = "Model\SimpleNet.snn"
deviceId = -1
dimension = 2
labelDimension = 3
precision = "float"

modelPath 变量指定生成的二进制模型的放置位置,以及模型调用方式。此处,“snn”代表简单的神经网络,但你可以使用任何扩展。deviceId 变量告诉 CNTK 是使用 CPU (-1) 还是 GPU (0)。

维度变量指定输入向量中的值的数量。labelDimension 指定可能的输出值的数量。precision 变量可以取浮点或双精度型的值。大多数情况下,最好使用浮点型,因为使用它来进行定型比使用双精度型快得多。

定型模块

配置文件中的 Train 模块演示包含三个主要子节: BrainScriptNetworkBuilder、SGD 和读取器。这些子节定义神经网络架构,如何对网络定型和如何读取定型数据。

定型模块定义开始为:

Train = [
  action="train"
  BrainScriptNetworkBuilder = [
    FDim = $dimension$
    HDim = 5
    LDim = $labelDimension$
...

Train 模块的 BrainScriptNetworkBuilder 节使用一种名为 BrainScript 的特殊脚本语言。变量 FDim、HDim 和 LDim 保存神经网络的特征、隐藏节点和标签节点的数量。这些名称不是必需的,因此可根据需要使用 NumInput、NumHidden 和 NumOutput 等名称。输入和输出节点数量由问题数据确定,但隐藏节点的数量是自由参数,且必须通过反复试验来确定。$ 令牌是替换运算符。Train 模块定义继续:

neuralDef (ftrs) = [
  W0 = Parameter (HDim, FDim)
  b0 = Parameter (HDim, 1) 
  W1 = Parameter (LDim, HDim)
  b1 = Parameter (LDim, 1)
  hn = Tanh (W0 * ftrs + b0)
  zn = W1 * hn + b1
].zn
...

这是 BrainScript 函数。变量 W0 是保存输入到隐藏权重的矩阵。Parameter 函数表示“构建矩阵”。 变量 b0 保存隐藏的节点偏差值。BrainScript 中的所有计算都在矩阵上执行,因此 b0 是具有一列而不是一个数组的矩阵。

变量 W1 和 b1 保存隐藏到输出的权重和输出节点偏差值。如前所述,使用乘积的和及 tanh 函数将隐藏节点的值计算到名为 hn 的变量中。变量 zn 保存 ­softmax 之前的输出值。用括号括起的点变量表示法是 BrainScript 函数返回值的方式。定型定义继续:

features = Input (FDim)
labels   = Input (LDim)
myNet = neuralDef (features)
...

此处定义输入特征和输出标签。变量名称“features”和“labels”不是关键字,但它们必须匹配定型和测试数据文件中使用的字符串。神经网络通过调用 neuralDef 函数创建。接下来,模块定义将在定型期间使用的信息:

ce   = CrossEntropyWithSoftmax (labels, myNet)
err  = ErrorPrediction (labels, myNet)
pn   = Softmax (myNet)
...

计算定型数据中的计算出的输出值与实际输出值的接近程度时,CrossEntropyWithSoftmax 函数指定应使用交叉熵误差。交叉熵误差是标准指标,但是平方误差是一种替代方法。

ErrorPrediction 函数指示 CNTK 计算和显示预测模型精度(针对定型数据的正确预测百分比)和交叉熵误差及困惑度,这是计算的输出和实际输出之间误差的度量值。

Softmax 函数指示 CNTK 对计算的输出值进行标准化,因此它们总和为 1.0,可以解释为概率。对于神经网络分类器,只有在极少数情况下才会使用 Softmax。定型模块定义结束:

...
  featureNodes    = (features)
  inputNodes      = (labels)
  criterionNodes  = (ce)
  evaluationNodes = (err)
  outputNodes     = (pn)
]

此处必需的系统变量 featureNodes、inputNodes、criterionNodes 和 outputNodes 以及可选的 evaluationNodes 与用户定义的变量相关联。

随机梯度下降 (SGD) 子部分定义 CNTK 定型神经网络的方式。在神经网络上下文中,SGD 通常被称为反向传播。子节定义为:

SGD = [
  epochSize = 0
  minibatchSize = 1
  learningRatesPerSample = 0.04
  maxEpochs = 500
  momentumPerMB = 0.90
]

epochSize 变量指定定型数据的使用量。特殊值 0 表示使用全部可用的定型数据。minibatchSize 变量指定在每个定型迭代中要处理的定型数据量。值 1 表示处理每个定型项后更新权重和偏差。这通常称为“在线”或“随机”定型。

如果将 minibatchSize 的值设置为定型项的数量(在演示情况下为 24),则将处理全部 24 个项并聚合结果,然后将仅更新权重和偏差。这有时称为“全批量”或“批量”定型。使用介于 1 和定型集大小之间的值称为“小批量”定型。

learningRatesPerSample 变量指定在每次迭代中调整权重和偏差的程度。学习率的值以及 SGD 子节中的其他参数必须通过反复试验来确定。神经网络通常对学习率的值极其敏感,例如,使用 0.04 可能向你提供一个非常准确的预测系统,但使用 0.039 或 0.041 可能向你提供一个非常差的系统。

maxEpochs 变量指定要执行多少次迭代以进行定型。太小的值将生成较差的模型(“模型欠拟合”),但迭代次数过多将使定型数据过度拟合。这会导致预测定型数据非常准确的模型在预测新数据时效果非常差。

momentumPerMB(每个小批量的动量,而不是你可能假设的每兆字节)是可增加或减少权重和偏差更新量的因素。就像学习率一样,动量值必须通过反复试验来确定,并且神经网络定型通常对动量值极其敏感。演示中使用的值 0.90 是默认值,因此可省略 momentumPerMB 参数。

演示配置文件的定型模块以设置阅读器子节的参数值结束:

...
  reader = [
    readerType = "CNTKTextFormatReader"
    file = "TrainData.txt"
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
] # end Train

CNTK 工具具有许多不同类型的读取器,可用于不同类型的输入数据。演示阅读器子节中参数的含义应清晰。注意,通过使用分号分隔符,我可在单行放置多个语句。

Test 模块

回顾一下,MakeModel.cntk 配置文件有一些全局系统参数(如 modelPath)和四个模块: Train、Test、WriteProbs、DumpWeights。Train 模块有三个子节: BrainScriptNetworkBuilder、SGD、读取器。

Test 模块非常简单,如图 8 所示。

图 8 Test 模块

Test = [
  action = "test"
  reader = [
    readerType="CNTKTextFormatReader"
    file = "TestData.txt"
    randomize = "false"
    input = [
      features = [ dim = $dimension$
        format = "dense" ]
      labels = [ dim = $labelDimension$
        format = "dense" ]
    ]
  ]
]

除文件参数值和随机参数的添加之外,测试模块的读取器子节应该与定型模块的读取器子节匹配。使用 SGD 定型时,以随机顺序处理数据项非常重要,true 是随机选择的默认值。但浏览测试数据时,无需使数据项的顺序随机化。

测试模块向 shell 发送一个精度指标和两个误差指标。如果恰好在“操作测试完成”消息之前回顾图 1,你将看到:

err = 0.11111111 * 9
ce = 0.33729280 * 9
perplexity = 1.40114927

err = 0.1111 * 9 表示使用该模型错误预测了 9 个测试数据项中的 11%。换句话说,正确预测了 9 个测试项中的 8 个。然而,定型输出不会告诉你正确预测了哪些数据项,错误预测了哪些数据项。

ce = 0.3372 * 9 表示平均交叉熵误差为 0.3372。对于针对 CNTK 的说明,只需将交叉熵视为误差项,因此值越小越好。

困惑度 = 1.4011 是次要指标。可将困惑度视为衡量预测精度的指标,其中值越小越好。例如,对于演示中的三个可能的输出值,如果预测是 (0.33, 0.33, 0.33),则根本没有强预测。在这种情况下,困惑度为 3.0,这是三个输出值中的最大值。

WriteProbs 模块

CNTK 演示配置文件中的第三个模块是 WriteProbs。此模块是可选的,但非常有用,因为它可提供有关针对测试数据进行的预测的其他信息。该模块在图 9 中定义。

图 9 WriteProbs 模块

WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="TestData.txt"       
    input = [
      features = [  dim = $dimension$
        format = "dense" ]
      labels = [ dim = $labelDimension$
        format = "dense"  ]
    ]
  ]
  outputPath = "TestProbs_txt"
]

除三处不同外,WriteProbs 模块与测试模块相同。首先,action 参数设置为“write”而不是“test”。 第二,已删除 randomize 参数(因为 false 是默认值)。第三,添加了 outputPath 参数。

执行 WriteProbs 模块时,它会将测试数据的确切输出值写入指定文件。在这种情况下,文件名将追加“.pn”,因为这是用于定型模块中输出节点的变量名。

对于 9 个演示测试项,文件 TestProbs_txt.pn 的内容是:

0.837386 0.162606 0.000008
0.990331 0.009669 0.000000
0.275697 0.724260 0.000042
0.271172 0.728788 0.000040
0.264680 0.735279 0.000041
0.427661 0.572313 0.000026
0.309024 0.005548 0.685428
0.000134 0.000006 0.999860
0.000190 0.000008 0.999801

前三个概率向量与前三个测试项匹配,映射到正确输出 (1, 0, 0),因此正确预测了前两个测试项。但第三个概率向量 (0.27, 0.74, 0.00) 映射到 (0, 1, 0),因此预测不正确。

接下来的三个概率向量与具有输出 (0, 1, 0) 的测试项匹配,因此全部三个预测均正确。类似地,最后三个概率向量与 (0, 0, 1) 匹配,因此它们也是正确的预测。

总而言之,Test 模块将向 shell 发送精度和误差指标,但不会告诉你哪些单独的测试项正确,也不会给出其误差。WriteProbs 模块将确切的输出值写入文件,你可以使用它们来确定哪些测试项未正确预测。

DumpWeights 模块

演示配置文件中四个模块的最后一个是 DumpWeights,它定义为:

DumpWeights = [
  action = "dumpNode"
  printValues = "true"
]

执行时,该模块将定型模型的权重和偏差值保存到文件中。默认情况下,文件名将与二进制模型(演示中为 SimpleNet.snn)相同,追加“.__ AllNodes __.txt”,且该文件将保存在 modelPath 参数(演示中为“Model”)指定的目录中。

运行 MakeModel.cntk 演示后,如果打开文件资源管理器并将其指向目录\SimpleNeuralNet\Model,则将看到 503 个文件:

SimpleNet.snn
SimpleNet.snn.__AllNodes__.txt
SimpleNet.snn.0
...
SimpleNet.snn.499
SimpleNet.ckp

SimpleNet.snn 是以二进制格式保存的定型模型,供 CNTK 使用。具有以数字结尾的名称的 500 个文件和以“.ckp”扩展名结尾的一个文件是二进制检查点文件。此处的原理是定型复杂的神经网络可能需要数小时甚至几天。回顾演示,将 maxEpochs 参数设置为 500。CNTK 工具定期保存定型信息,以防在出现系统故障时不必从头开始重新定型。

用于演示(删除了几行)的 AllNodes__.txt 文件的内容的前半部分是:

myNet.b0=LearnableParameter [5,1]
-1.42185283
 1.84464693
 1.04422486
 2.57946277
 1.65035748
 ################################################
myNet.b1=LearnableParameter [3,1]
 2.51937032
-1.5136646
-1.03768802

这些是隐藏节点偏差 (b0) 和输出节点偏差 (b1) 的值。回顾图 4 中的神经网络图,你会看到这些值被截断为两个小数。AllNodes__.txt 文件的后半部分如下所示:

myNet.W0=LearnableParameter [5,2]
 2.41520381 -0.806418538
-0.561291218 0.839902222
-0.490522146 0.995252371
-0.740959883 1.05180109
-2.72802472 2.81985259
 #################################################
myNet.W1=LearnableParameter [3,5]
-2.246624  1.186315  0.557211  1.837152 -1.232379
 0.739416  0.814771  1.095480  0.386835  2.120146
 1.549207 -1.959648 -1.627967 -2.235190 -0.850726

回想一下,演示网络有两个输入值、五个隐藏节点和三个输出节点。因此,W0 中存在 2 * 5 = 10 个输入到隐藏权重,W1 中存在 5 * 3 = 15 个隐藏到输出权重。

进行预测

具有定型模型后,即可以用它来进行预测。要进行预测,一种方法是使用带有“eval”操作模块的 CNTK 工具。演示采用这种方法。首先,创建具有单个项的新数据集并将其保存为文件 NewData.txt:

|features 4.0 7.0 |labels -1 -1 -1

因为这是新数据,所以输出标签使用 dummy -1 值。接下来,我创建了一个名为 MakePrediction.cntk 的配置文件,其中包含两个模块,名称分别为 Predict 和 WriteProbs。完整的文件在图 10 中呈现。

图 10 进行预测

# MakePrediction.cntk
stderr = "Log"   # write all messages to file
command=Predict:WriteProbs
modelPath = "Model\SimpleNet.snn" # where to find model
deviceId = -1 
dimension = 2 
labelDimension = 3 
precision = "float"
Predict = [
  action = "eval"
  reader = [
    readerType="CNTKTextFormatReader"
    file="NewData.txt"
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
]
WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="NewData.txt"       
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
  outputPath = "Prediction_txt"  # dump with .pn extension
]

运行时,输出概率保存在名为 Prediction_txt.pn 的文件中,该文件包含:

0.271139 0.728821 0.000040

到输出 (0, 1, 0) 的映射是“blue”。 查看图 2 中的定型数据,可轻松看出 (4.0, 7.0) 可以是“red”(1, 0, 0) 或“blue”(0, 1, 0)。

使用定型模型的两种替代技术是使用具有 CNTK 模型评估库的 C# 程序,或者使用直接使用模型权重和偏差值的自定义 Python 脚本。

总结

据我所知,CNTK 是 Windows 最强大的神经网络系统,通常供开发者使用。这篇文章只涵盖了 CNTK 用途的一小部分,但应足以支持你启动和运行简单的神经网络并了解文档。CNTK 的真正用途在于与深度神经网络(具有两个或多个隐藏层,且节点之间可能存在复杂连接的网络)协同工作。

CNTK 工具正处于积极开发中,因此,在你阅读本文章时,一些细节可能已经改变。然而,CNTK 团队告诉我,改动非常微小,你应该可以毫不费力地修改本文中呈现的演示。


Dr.James McCaffrey 供职于华盛顿地区雷蒙德市沃什湾的 Microsoft Research。他参与过多个 Microsoft 产品的工作,包括 Internet Explorer 和 Bing。Scripto可通过 jammc@microsoft.com 与 McCaffrey 取得联系。

衷心感谢以下 Microsoft 技术专家对本文的审阅: Adam Eversole、John Krumm、Frank Seide 和 Adam Shirey


在 MSDN 杂志论坛讨论这篇文章