使用 CNTK 和 C# 通过 MinibatchSource 训练 Iris 数据





5.00/5 (2投票s)
通过这篇博文,我们将实现完整的 C# 程序来训练 Iris 数据。
到目前为止(文章 1、文章 2、文章 3),我们已经了解了什么是 CNTK,如何将其与 Python 一起使用,以及如何创建一个简单的 C# .NET 应用程序并调用基本的 CNTK 方法。在这篇博文中,我们将实现一个完整的 C# 程序来训练 Iris 数据。
使用 CNTK 的第一步是如何获取数据并将其输入到训练器。在之前的文章中,我们准备了 CNTK 格式的 Iris 数据,该格式适用于使用 MinibatchSource 的情况。为了使用 MinibatchSource
,我们需要创建两个流
- 一个用于特征,以及
- 一个用于标签
此外,特征和标签变量也必须使用流创建,以便在使用变量访问数据时,训练器知道数据来自文件。
数据准备
如上所述,我们将使用 CNTK MinibatchSource
加载 Iris 数据。为本次演示准备了两个文件
var dataFolder = "Data";
var dataPath = Path.Combine(dataFolder, "trainIris_cntk.txt");
var trainPath = Path.Combine(dataFolder, "testIris_cntk.txt");
var featureStreamName = "features";
var labelsStreamName = "label";
一个文件路径包含用于训练的 Iris 数据,第二个路径包含用于测试的数据,这将在以后的文章中使用。创建用于训练和验证的 minibatchSource
时,这两个文件将作为参数。
从文件中获取数据的第一步是定义具有正确信息的流配置。当从文件中提取数据时,将使用该信息。通过提供文件中特征的数量和标签的 one-hot 向量分量的数量,以及特征和标签的名称,完成配置。在本博文的末尾,数据被附加,以便读者可以看到如何为 minibatchSource
准备数据。
以下代码定义了 Iris 数据集的流配置。
//stream configuration to distinct features and labels in the file
var streamConfig = new StreamConfiguration[]
{
new StreamConfiguration(featureStreamName, inputDim),
new StreamConfiguration(labelsStreamName, numOutputClasses)
};
此外,必须通过提供上述流名称来创建特征和标签变量。
//define input and output variable and connecting to the stream configuration
var feature = Variable.InputVariable(new NDShape(1,inputDim), DataType.Float, featureStreamName);
var label = Variable.InputVariable(new NDShape(1, numOutputClasses), DataType.Float, labelsStreamName);
现在,输入和输出变量与文件中的数据连接,并且 minibachSource
可以处理它们。
创建前馈神经网络模型
一旦我们定义了流和变量,我们就可以定义网络模型。CNTK 的实现方式使您可以定义任意数量的隐藏层以及任何激活函数。对于本次演示,我们将创建一个具有一个隐藏层的简单前馈神经网络。下图显示了 NN 模型。
为了实现上述 NN 模型,我们需要实现三个方法
-
static Function applyActivationFunction(Function layer, NNActivation actFun)
-
static Function simpleLayer(Function input, int outputDim, DeviceDescriptor device)
-
static Function createFFNN(Function input, int hiddenLayerCount, int hiddenDim, int outputDim, NNActivation activation, string modelName, DeviceDescriptor device)
第一个方法仅将指定的激活函数应用于传递的层。该方法非常简单,应如下所示
static Function applyActivationFunction(Function layer, Activation actFun)
{
switch (actFun)
{
default:
case Activation.None:
return layer;
case Activation.ReLU:
return CNTKLib.ReLU(layer);
case Activation.Sigmoid:
return CNTKLib.Sigmoid(layer);
case Activation.Tanh:
return CNTKLib.Tanh(layer);
}
}
public enum Activation
{
None,
ReLU,
Sigmoid,
Tanh
}
该方法将层作为参数,并返回应用了激活函数的层。
下一个方法是创建具有 n 个权重和一个偏差的简单层。该方法显示在以下列表中
static Function simpleLayer(Function input, int outputDim, DeviceDescriptor device)
{
//prepare default parameters values
var glorotInit = CNTKLib.GlorotUniformInitializer(
CNTKLib.DefaultParamInitScale,
CNTKLib.SentinelValueForInferParamInitRank,
CNTKLib.SentinelValueForInferParamInitRank, 1);
//create weight and bias vectors
var var = (Variable)input;
var shape = new int[] { outputDim, var.Shape[0] };
var weightParam = new Parameter(shape, DataType.Float, glorotInit, device, "w");
var biasParam = new Parameter(new NDShape(1,outputDim), 0, device, "b");
//construct W * X + b matrix
return CNTKLib.Times(weightParam, input) + biasParam;
}
初始化参数后,使用输出组件的数量以及前一层或输入变量创建 Function
对象。这在 NN 层创建中称为链式规则。通过这种策略,用户可以创建非常复杂的 NN 模型。
最后一个方法执行图层的创建。它从 main 方法调用,并且可以通过提供参数来创建任意前馈神经网络。
static Function createFFNN(Variable input, int hiddenLayerCount,
int hiddenDim, int outputDim, Activation activation, string modelName, DeviceDescriptor device)
{
//First the parameters initialization must be performed
var glorotInit = CNTKLib.GlorotUniformInitializer(
CNTKLib.DefaultParamInitScale,
CNTKLib.SentinelValueForInferParamInitRank,
CNTKLib.SentinelValueForInferParamInitRank, 1);
//hidden layers creation
//first hidden layer
Function h = simpleLayer(input, hiddenDim, device);
h = ApplyActivationFunction(h, activation);
//2,3, ... hidden layers
for (int i = 1; i < hiddenLayerCount; i++)
{
h = simpleLayer(h, hiddenDim, device);
h = ApplyActivationFunction(h, activation);
}
//the last action is creation of the output layer
var r = simpleLayer(h, outputDim, device);
r.SetName(modelName);
return r;
}
现在我们已经实现了用于 NN 模型创建的方法,下一步将是训练实现。
训练过程是迭代的,其中 minibachSource
在每次迭代中为训练器提供数据。
在每次迭代中计算 Loss
和 evaluation
函数,并在迭代进度中显示。迭代进度由一个单独的方法定义,该方法如下面的代码清单所示
private static void printTrainingProgress
(Trainer trainer, int minibatchIdx, int outputFrequencyInMinibatches)
{
if ((minibatchIdx % outputFrequencyInMinibatches) == 0 &&
trainer.PreviousMinibatchSampleCount() != 0)
{
float trainLossValue = (float)trainer.PreviousMinibatchLossAverage();
float evaluationValue = (float)trainer.PreviousMinibatchEvaluationAverage();
Console.WriteLine($"Minibatch: {minibatchIdx} CrossEntropyLoss =
{trainLossValue}, EvaluationCriterion = {evaluationValue}");
}
}
在迭代过程中,Loss
函数不断降低其值,表明该模型变得越来越好。迭代过程完成后,将在训练数据的准确性上下文中显示模型。
完整程序实现
以下清单显示了使用 CNTK 进行 Iris 数据集训练的完整源代码实现。首先,定义几个变量,以便定义 NN 模型的结构:输入和输出变量的数量。此外,main 方法实现了迭代过程,其中 minibatchSource
通过将相关数据传递给训练器来处理数据。有关更多信息将在单独的博文中介绍。迭代过程完成后,将显示模型结果,程序终止。
public static void TrainIris(DeviceDescriptor device)
{
var dataFolder = "";//files must be on the same folder as program
var dataPath = Path.Combine(dataFolder, "iris_with_hot_vector.csv");
var trainPath = Path.Combine(dataFolder, "iris_with_hot_vector_test.csv");
var featureStreamName = "features";
var labelsStreamName = "labels";
//Network definition
int inputDim = 4;
int numOutputClasses = 3;
int numHiddenLayers = 1;
int hidenLayerDim = 6;
uint sampleSize = 130;
//stream configuration to distinct features and labels in the file
var streamConfig = new StreamConfiguration[]
{
new StreamConfiguration(featureStreamName, inputDim),
new StreamConfiguration(labelsStreamName, numOutputClasses)
};
// build a NN model
//define input and output variable and connecting to the stream configuration
var feature = Variable.InputVariable(new NDShape(1, inputDim), DataType.Float, featureStreamName);
var label = Variable.InputVariable
(new NDShape(1, numOutputClasses), DataType.Float, labelsStreamName);
//Build simple Feed Froward Neural Network model
// var ffnn_model = CreateMLPClassifier(device, numOutputClasses,
// hidenLayerDim, feature, classifierName);
var ffnn_model = CreateFFNN(feature, numHiddenLayers,
hidenLayerDim, numOutputClasses, Activation.Tanh, "IrisNNModel", device);
//Loss and error functions definition
var trainingLoss = CNTKLib.CrossEntropyWithSoftmax(new Variable(ffnn_model), label, "lossFunction");
var classError = CNTKLib.ClassificationError(new Variable(ffnn_model), label, "classificationError");
// prepare the training data
var minibatchSource = MinibatchSource.TextFormatMinibatchSource(
dataPath, streamConfig, MinibatchSource.InfinitelyRepeat, true);
var featureStreamInfo = minibatchSource.StreamInfo(featureStreamName);
var labelStreamInfo = minibatchSource.StreamInfo(labelsStreamName);
// set learning rate for the network
var learningRatePerSample = new TrainingParameterScheduleDouble(0.001125, 1);
//define learners for the NN model
var ll = Learner.SGDLearner(ffnn_model.Parameters(), learningRatePerSample);
//define trainer based on ffnn_model, loss and error functions , and SGD learner
var trainer = Trainer.CreateTrainer(ffnn_model, trainingLoss, classError, new Learner[] { ll });
//Preparation for the iterative learning process
//used 800 epochs/iterations. Batch size will be the same as sample size since the data set is small
int epochs = 800;
int i = 0;
while (epochs > -1)
{
var minibatchData = minibatchSource.GetNextMinibatch(sampleSize, device);
//pass to the trainer the current batch separated by the features and label.
var arguments = new Dictionary<Variable, MinibatchData>
{
{ feature, minibatchData[featureStreamInfo] },
{ label, minibatchData[labelStreamInfo] }
};
trainer.TrainMinibatch(arguments, device);
Helper.PrintTrainingProgress(trainer, i++, 50);
// MinibatchSource is created with MinibatchSource.InfinitelyRepeat.
// Batching will not end. Each time minibatchSource completes an sweep (epoch),
// the last minibatch data will be marked as end of a sweep. We use this flag
// to count number of epochs.
if (Helper.MiniBatchDataIsSweepEnd(minibatchData.Values))
{
epochs--;
}
}
//Summary of training
double acc = Math.Round((1.0 - trainer.PreviousMinibatchEvaluationAverage()) * 100, 2);
Console.WriteLine($"------TRAINING SUMMARY--------");
Console.WriteLine($"The model trained with the accuracy {acc}%");
//// validate the model
// this will be posted as separate blog post
}
可以在此处找到带有格式化的 Iris 数据集用于训练的完整源代码。