在机器学习和深度学习领域,CNTK(Cognitive Toolkit)是一个强大的工具,它支持多种神经网络模型的构建和训练。尽管CNTK通常用于复杂的神经网络模型,但它同样适用于执行简单的任务,如矩阵乘法和数据集的描述性统计参数计算。本文将展示如何使用CNTK实现一个简单的线性回归模型,该模型仅包含一个神经元和偏置参数,总共只有两个参数:权重(w)和偏置(b)。
使用CNTK来解决如此简单的任务的原因非常直接:通过学习这样的简单模型,可以了解CNTK库的工作原理,并观察到CNTK中的一些不那么显而易见的操作。上述模型可以轻松扩展到逻辑回归模型,只需添加激活函数即可。除了线性回归(代表没有激活函数的神经网络配置)之外,逻辑回归是包含激活函数的最简单的神经网络配置。
假设有一个简单的数据集,它代表一个简单的线性函数。生成的数据集如下表所示:
已经知道所呈现数据集的线性回归参数是:w 和 b,因此希望利用CNTK库来获取这些值,或者至少是接近这些值的参数值。
使用CNTK开发线性回归(LR)模型的任务可以描述为几个步骤:
步骤1:在Visual Studio中创建C#控制台应用程序,更改当前架构,并在解决方案中添加最新的“CNTK.GPU”NuGet包。
步骤2:开始编写代码,添加两个变量:特征和标签。定义训练数据集,通过创建批次来开始。
首先,需要添加一些using语句,并定义计算将发生的设备。通常,如果机器包含NVIDIA兼容的显卡,可以定义CPU或GPU。因此,演示从以下代码片段开始:
using System;
using System.Linq;
using System.Collections.Generic;
using CNTK;
namespace LR_CNTK_Demo
{
class Program
{
static void Main(string[] args)
{
// 定义设备
var device = DeviceDescriptor.UseDefaultDevice();
// 定义变量和数据集
Variable x = Variable.InputVariable(new int[] { 1 }, DataType.Float, "input");
Variable y = Variable.InputVariable(new int[] { 1 }, DataType.Float, "output");
// 定义训练数据集
var xValues = Value.CreateBatch(new NDShape(1, 1), new float[] { 1f, 2f, 3f, 4f, 5f }, device);
var yValues = Value.CreateBatch(new NDShape(1, 1), new float[] { 3f, 5f, 7f, 9f, 11f }, device);
}
}
}
步骤3:创建一个线性回归网络模型,通过传递输入变量和用于计算的设备。已经讨论过,该模型由一个神经元和一个偏置参数组成。
private static Function createLRModel(Variable x, DeviceDescriptor device)
{
var initV = CNTKLib.GlorotUniformInitializer(1.0, 1, 0, 1);
var b = new Parameter(new NDShape(1, 1), DataType.Float, initV, device, "b");
var W = new Parameter(new NDShape(2, 1), DataType.Float, initV, device, "w");
var Wx = CNTKLib.Times(W, x, "wx");
var l = CNTKLib.Plus(b, Wx, "wx_b");
return l;
}
首先,创建初始化器,它将初始化网络参数的初始值。然后,定义偏置和权重参数,并以线性模型的形式将它们组合起来,并返回为Function类型。createModel函数在main方法中被调用。一旦模型创建,可以检查它,并证明模型中只有两个参数。
步骤4:创建Trainer,它将用于训练网络参数w和b。
public Trainer createTrainer(Function network, Variable target)
{
var lrate = 0.082;
var lr = new TrainingParameterScheduleDouble(lrate);
var zParams = new ParameterVector(network.Parameters().ToList());
Function loss = CNTKLib.SquaredError(network, target);
Function eval = CNTKLib.SquaredError(network, target);
var llr = new List();
var msgd = Learner.SGDLearner(network.Parameters(), lr, l);
llr.Add(msgd);
var trainer = Trainer.CreateTrainer(network, loss, eval, llr);
return trainer;
}
首先,定义了主神经网络参数的学习率。然后,创建Loss和Evaluation函数。有了这些参数,可以创建SGD学习器。一旦SGD学习器对象被实例化,就通过调用CreateTrainer静态CNTK方法创建trainer,并将其作为函数返回。createTrainer方法在main方法中被调用:
步骤5:训练过程:一旦定义了变量、数据集、网络模型和trainer,就可以开始训练过程。
for (int i = 1; i <= 200; i++)
{
var d = new Dictionary();
d.Add(x, xValues);
d.Add(y, yValues);
trainer.TrainMinibatch(d, true, device);
var loss = trainer.PreviousMinibatchLossAverage();
var eval = trainer.PreviousMinibatchEvaluationAverage();
if (i % 20 == 0)
Console.WriteLine($"It={i}, Loss={loss}, Eval={eval}");
if (i == 200)
{
var b0_name = paramValues[0].Name;
var b0 = new Value(paramValues[0].GetValue()).GetDenseData(paramValues[0]);
var b1_name = paramValues[1].Name;
var b1 = new Value(paramValues[1].GetValue()).GetDenseData(paramValues[1]);
Console.WriteLine($"\nTraining process finished with the following regression parameters:\nb={b0[0][0]}, w={b1[0][0]}\n");
}
}
如所见,在仅200次迭代中,回归参数就获得了几乎预期的值。由于训练过程与经典回归参数确定不同,无法获得确切的值。为了估计回归参数,神经网络使用称为随机梯度下降(SGD)的迭代方法。另一方面,经典回归通过最小化最小二乘误差并解决未知数为b和w的系统方程来使用回归分析程序。