2018 年 6 月

第 33 卷,第 6 期

测试运行 - 使用 CNTK 实现神经回归

通过James McCaffrey |年 6 月 2018

在提供的代码下载msdn.com/magazine/0518magcode

回归问题的目标是做出一个预测,其中要预测的值是单个数值。例如,你可能会

要预测根据其权重、 年龄和性别个人的高度。有许多可用于解决回归问题的技术。这篇文章中,我将介绍如何使用 CNTK 库来创建一个神经网络回归模型。

了解本文所述观点的一个好方法是查看图 1 中的演示程序。演示计划创建回归模型有关的已知的游艇 Hydrodynamics 数据集基准。目标是预测的游艇外壳,基于六个预测器变量的轻松的度量值: center buoyancy 外壳、 prismatic 系数、 长度偏移量比率、 无线发送 draught 比率、 长度杆比率和 Froude 数。

图 1 回归使用 CNTK 神经网络

演示程序使用两个隐藏层,其中每个有五个处理节点创建一个神经网络。训练后,该模型用于对两个数据项进行预测。第一项具有预测值的值 (0.52、 0.79、 0.55、 0.41、 0.65、 0.00)。预测的外壳抵制 0.0078 且实际的防范 0.0030。第二个项具有预测值的值 (1.00、 1.00、 0.55、 0.56、 0.46、 1.00)。预测的外壳抵制 0.8125 且实际的防范 0.8250。模型似乎非常准确。

本文假定你有中间或更好的编程技能,但不会假定你知道有关 CNTK 或神经网络的很多。演示将会编码使用 Python,机器学习的默认语言,但即使你不知道 Python 应能要遵循而无需太多困难。本文展示了演示程序的所有代码。处找不到使用该演示程序的游艇外壳数据文件bit.ly/2Ibsm5D,并同样可下载随附这篇文章中。

了解数据

创建机器学习模型时,数据准备几乎始终是项目的最耗时的一部分。原始数据集有 308 的项,如下所示:

-2.3 0.568 4.78 3.99 3.17 0.125 0.11
-2.3 0.568 4.78 3.99 3.17 0.150 0.27
...
-5.0 0.530 4.78 3.75 3.15 0.125 0.09
...
-2.3 0.600 4.34 4.23 2.73 0.450 46.66

该文件是用空格分隔。前六个值是预测值的值 (通常称为机器学习术语的功能)。在每个行上的最后一个值是"residuary 抵制每的偏移量单位重量。"

由于没有多个预测器变量,则不可以在关系图中显示完整的数据集。但你可以通过检查中的关系图获取大致了解数据的结构图 2。该图形会指出只 prismatic 系数的预测指标值和外壳抵御。你可以看到,prismatic 系数值,通过本身,不向您提供足够信息以给外壳轻松的准确预测。

图 2 部分游艇外壳数据

使用神经网络,时,通常需要规范化数据以创建准确的预测模型。我使用最小-最大值规范化六个预测器值和外壳抵制值上。我删除原始数据到 Excel 电子表格,并为每个列中,我计算出的最大和最小值的值。然后,对于每个列,我替换与每个值 v (v-min) / (max-min)。例如,最小 prismatic 系数值为 0.53,最大值为 0.60。列中的第一个值是 0.568 并且它被规范化为 (0.568-0.53) / (0.60-0.53) = 0.038 / 0.07 = 0.5429。

规范化后, 插入标记 | 预测值和 | 入的 Excel 电子表格,因此 CNTK 数据读取器对象可轻松地读取数据的能力。然后我要为制表符分隔文件中导出数据。生成的数据如下所示:

|predictors  0.540000  0.542857 . . |resistance  0.001602
|predictors  0.540000  0.542857 . . |resistance  0.004166
...

最小-最大值规范化的备用方法包括 z 评分规范化和数量级规范化。

演示程序

图 3 展示了完整的演示程序(为节省空间,进行了少量小幅改动)。我删除了所有常规错误检查。出于个人偏好和节省空间的考虑,我缩进了两个空格字符,而不是常规的四个空格字符。请注意,\ 字符由续行符 Python。

图 3 回归演示程序

# hydro_reg.py
# CNTK 2.4 with Anaconda 4.1.1 (Python 3.5, NumPy 1.11.1)
# Predict yacht hull resistance based on six predictors

import numpy as np
import cntk as C

def create_reader(path, input_dim, output_dim, rnd_order,
  sweeps):
  x_strm = C.io.StreamDef(field='predictors',
    shape=input_dim, is_sparse=False)
  y_strm = C.io.StreamDef(field='resistance',
    shape=output_dim, is_sparse=False)
  streams = C.io.StreamDefs(x_src=x_strm, y_src=y_strm)
  deserial = C.io.CTFDeserializer(path, streams)
  mb_src = C.io.MinibatchSource(deserial,
    randomize=rnd_order, max_sweeps=sweeps)
  return mb_src

# ========================================================

def main():
  print("\nBegin yacht hull regression \n")
  print("Using CNTK version = " + \
    str(C.__version__) + "\n")
  input_dim = 6  # center of buoyancy, etc.
  hidden_dim = 5
  output_dim = 1  # residuary resistance
  train_file = ".\\Data\\hydro_data_cntk.txt"
  # data resembles:
  # |predictors 0.540  0.542 . . |resistance  0.001
  # |predictors 0.540  0.542 . . |resistance  0.004
  # 1. create neural network model
  X = C.ops.input_variable(input_dim, np.float32)
  Y = C.ops.input_variable(output_dim)
  print("Creating a 6-(5-5)-1 tanh regression NN for \
yacht hull dataset ")
  with C.layers.default_options():
    hLayer1 = C.layers.Dense(hidden_dim,
      activation=C.ops.tanh, name='hidLayer1')(X)
    hLayer2 = C.layers.Dense(hidden_dim,
      activation=C.ops.tanh, name='hidLayer2')(hLayer1)  
    oLayer = C.layers.Dense(output_dim,
      activation=None, name='outLayer')(hLayer2)
  model = C.ops.alias(oLayer)  # alias
  # 2. create learner and trainer
  print("Creating a squared error batch=11 Adam \
fixed LR=0.005 Trainer \n")
  tr_loss = C.squared_error(model, Y)
  max_iter = 50000
  batch_size = 11
  learn_rate = 0.005
  learner = C.adam(model.parameters, learn_rate, 0.99)
  trainer = C.Trainer(model, (tr_loss), [learner])
  # 3. create reader for train data
  rdr = create_reader(train_file, input_dim, output_dim,
    rnd_order=True, sweeps=C.io.INFINITELY_REPEAT)
  hydro_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  # 4. train
  print("Starting training \n")
  for i in range(0, max_iter):
    curr_batch = rdr.next_minibatch(batch_size,
      input_map=hydro_input_map)
    trainer.train_minibatch(curr_batch)
    if i % int(max_iter/10) == 0:
      mcee = trainer.previous_minibatch_loss_average
      print("batch %6d: mean squared error = %8.4f" % \
        (i, mcee))
  print("\nTraining complete")
  # (could save model to disk here)
  # 5. use trained model to make some predictions
  np.set_printoptions(precision=2, suppress=True)
  inpts = np.array(
    [[0.520000, 0.785714, 0.550000, 0.405512, \
      0.648352, 0.000000],
     [1.000000, 1.000000, 0.550000, 0.562992, \
      0.461538, 1.000000]],
    dtype=np.float32)
  actuals = np.array([0.003044, 0.825028],
    dtype=np.float32)
  for i in range(len(inpts)):
    print("\nInput: ", inpts[i])
    pred = model.eval(inpts[i])
    print("predicted resistance: %0.4f" % pred[0][0])
    print("actual resistance:    %0.4f" % actuals[i])
  print("\nEnd yacht hull regression ")

# ========================================================

if __name__ == "__main__":
  main()

安装 CNTK 可能会有点棘手。首先,你安装 Python,其中包含必要的 Python 解释器,SciPy、 NumPy 等所需的包以及有用的实用工具,例如 pip Anaconda 的分发。我使用 Anaconda3 4.1.1 64 位具有 Python 3.5。在安装 Anaconda 之后, 你 CNTK 作为 Python 包安装,不独立系统,使用 pip 实用程序。我通过普通命令行界面运行的命令如下:

>pip install https://cntk.ai/PythonWheel/CPU-Only/cntk-2.4-cp35-cp35m-win_amd64.whl

Hydro_reg.py 演示具有一个帮助器函数,create_reader。您可以将 create_reader 看作 CNTK 回归问题的样本。你将需要在大多数情况下更改的唯一操作是数据文件中的标记名称。

所有控制逻辑都包含在一个主函数中。代码开始:

def main():
  print("Begin yacht hull regression \n")
  print("Using CNTK version = " + \
    str(C.__version__) + "\n")
  input_dim = 6  # center of buoyancy, etc.
  hidden_dim = 5
  output_dim = 1  # residuary resistance
  train_file = ".\\Data\\hydro_data_cntk.txt"
...

因为 CNTK 少时和持续开发,它是一个好办法显示的版本,正在使用 (在此情况下 2.4)。由数据集的结构确定输入节点个数。对于回归问题,输出节点个数始终设置为 1。隐藏层的数目和每个隐藏层中的处理节点数是可用的参数-它们必须由试用版和错误。

演示计划用于定型 308 的所有项。另一种方法是将数据集拆分为训练集 (通常 80%的数据) 和测试集 (剩余 20%)。训练后,可以计算上要验证的度量值包括类似于在定型数据上的测试数据集的模型的丢失和准确性度量值。

创建的神经网络模型

演示设置 CNTK 对象,以保留的预测值和 true 外壳抵制值:

X = C.ops.input_variable(input_dim, np.float32)
Y = C.ops.input_variable(output_dim)

因为很少需要 64 位精度,CNTK 默认情况下使用 32 位值。Input_variable 函数的名称可以是如果您是初次接触 CNTK 有点混乱。在这里,"input_"是指返回的对象保存来自输入数据 (对应于输入和输出的神经网络) 的值的事实。

使用这些语句来创建神经网络:

print("Creating a 6-(5-5)-1 NN")
with C.layers.default_options():
  hLayer1 = C.layers.Dense(hidden_dim,
    activation=C.ops.tanh, name='hidLayer1')(X)
  hLayer2 = C.layers.Dense(hidden_dim,
    activation=C.ops.tanh, name='hidLayer2')(hLayer1)  
  oLayer = C.layers.Dense(output_dim,
    activation=None, name='outLayer')(hLayer2)
model = C.ops.alias(oLayer)  # alias

没有在此处继续相当多的位。"With"语句 Python 可用来将一组的通用参数的值传递给多个函数。在这种情况下,神经网络权重和偏移值是使用 CNTK 默认值初始化的。神经网络的高度敏感为初始的权重和偏置值,因此提供非默认值是尝试神经网络出现故障,若要了解的首要任务之一-毫无疑问常见情况。

神经网络有两个隐藏的层。作为 X 对象,也用作第一个隐藏层; 的输入第一个隐藏的层充当输入到第二个隐藏层;和第二个隐藏层行为作为输入到输出层。

有两个隐藏的层使用 tanh (双曲正切值) 激活。两种主要的替代方法是逻辑 sigmoid 和纠正线性单元 (ReLU) 激活。输出层使用"None"激活,这意味着输出节点的值不修改。这是要使用的回归问题的设计模式。使用未激活有时称为使用标识激活函数,因为数学 identity 函数是 f (x) = x,即不起作用。

演示计划创建用于输出层的别名"模型"。此方法是可选的稍有点复杂。这里的思路是神经网络是实质上是一个复杂的数学函数。从概念上讲,输出节点作为一个整体表示这两个层的网络和网络/模型。

训练模型

CNTK 功能的核心是定型神经网络模型的能力。这些语句准备定型:

tr_loss = C.squared_error(model, Y)
max_iter = 50000
batch_size = 11
learn_rate = 0.005
learner = C.adam(model.parameters, learn_rate, 0.99)
trainer = C.Trainer(model, (tr_loss), [learner])

丢失 (错误) 函数是必需的因此在训练对象知道如何调整权重和偏置以减少错误。CNTK 2.4 有九个丢失函数,但简单 squared_error 几乎始终是适用于回归问题。迭代次数对应于更新操作的数目,而必须由试用版和错误。

训练器对象需要一个学习器对象。可以将一个学习器视为一种算法。CNTK 支持八个学习算法。对于回归问题,我通常可以获得良好的结果使用基本随机梯度下降或更复杂的 Adam ("自适应动量估算")。

批大小是 CNTK 用于确定执行权重和偏差的更新频率。演示将批次大小设置为 11。因此,308 项目将分组到 308 / 11 = 28 随机选择批次。每个批次进行分析并且然后执行更新。学习速率控制权重和偏置调整的量。确定批次大小的良好值,最大迭代,数和学习速率是通常的最大挑战创建神经网络预测模型时。

到,该演示调用的程序定义 create_reader 函数,创建读取器对象。和 input_map 创建告诉读者功能值,并其中预测值是:

rdr = create_reader(train_file, input_dim, output_dim,
  rnd_order=True, sweeps=C.io.INFINITELY_REPEAT)
hydro_input_map = {
  X : rdr.streams.x_src,
  Y : rdr.streams.y_src
}

Rnd_order 参数可确保项将会以不同方式处理在每个传递,这非常重要,以防从停滞出定型的数据。INFINITELY_REPEAT 自变量允许通过 308 项数据集的多个传递的培训。

对模型进行定型后准备,如下所示:

for i in range(0, max_iter):
  curr_batch = rdr.next_minibatch(batch_size,
    input_map=hydro_input_map)
  trainer.train_minibatch(curr_batch)
  if i % int(max_iter/10) == 0:
    mcee = trainer.previous_minibatch_loss_average
    print("batch %6d: mean squared error = %8.4f" % \
      (i, mcee))

Next_minibatch 函数提取了从数据 11 项。训练函数使用 Adam 算法来更新权重和偏置基于计算的外壳抵制值和实际抵制值之间的平方误差。当前 11 项批次中的平方的错误将显示每个 50,000 / 10 = 5,000 个批处理,以便您可以直观地监视培训进度:你想要看到通常减少的丢失/错误值。

使用模型

已训练模型后,该演示程序进行一些预测。首先,规范化的数据集中的两个任意项的预测指标值是所选 (项 99 和 238),并放入到数组的数组样式矩阵:

inpts = np.array(
  [[0.520000, 0.785714, 0.550000, 0.405512,
    0.648352, 0.000000],
   [1.000000, 1.000000, 0.550000, 0.562992,
    0.461538, 1.000000]],
  dtype=np.float32)

接下来,相应的规范化的实际外壳抵制值被放入数组中:

actuals = np.array([0.003044, 0.825028], dtype=np.float32)

然后,预测因子值用于计算预测的值使用 model.eval 函数,并显示预测值和实际值:

for i in range(len(inpts)):
  print("\nInput: ", inpts[i])
  pred = model.eval(inpts[i])
  print("predicted resistance: %0.4f" % pred[0][0])
  print("actual resistance:    %0.4f" % actuals[i])
print("End yacht hull regression ")

请注意,预测的外壳抵制值返回作为与单个值数组的数组矩阵。因此,值本身是在 [0] [0] (第 0 行,第 0 列)。处理 CNTK 形状向量和矩阵是一项重要的语法挑战。使用 CNTK 时我花费大量时间打印对象并显示其形状,沿 print(something.shape) 的行。

总结

创建神经网络回归模型时,没有任何预定义的准确性度量值。如果你想要计算必须定义它的一个预测值才能含义的预测准确性足够近的对应的实际值才能被视为正确。通常情况下,你将指定百分比/比例,例如 0.10,并对评估预测的值作为该百分比的实际值内是否正确。

因为演示模型使用规范化的数据,如果您使用模型来新的、 进行预测前所未有的预测指标值,你需要对它们使用相同的最小-最大值所使用的培训数据进行规范。规范化预测的外壳抵制值,pv,同样,,因此你必须取消规范化通过计算 pv * (max-min) + 最小值。

术语"回归"可以有多个不同的含义。这篇文章中术语是指其中的目标是预测单个数字值 (外壳抵制) 问题方案。古典统计信息线性回归方法是神经网络回归中,但通常远远小于精确比简单得多。机器学习逻辑回归技术预测单个数字值介于 0.0 和 1.0,它是解释为概率,然后用来预测某个分类的值,如"male"(p < 0.5) 或"female"(p > 0.5) 之间。


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