- 亚马逊语音识别合作
意向种子企业,小语种方向 - 政府推荐参加资本力量
1+6融资活动 - 上市公司众为兴
合作伙伴 - 河南职教中心
成人学历和能力培训合作联盟成员
精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
神经网络技术在机器学习中成为主流,也逐渐发展出来高级版本,LSTM长短期记忆网络就是一种高级版本。神经网络中网络细胞运算处理运算因子,LSTM加入了因子类型,扩展了细胞运算架构,能够有效地提升学习率,识别出好结果。
锐英源软件在kaldi中应用了LSTM,执行了LSTM训练用的一些脚本,对LSTM结合Python经验丰富,本文结合C#语言应用LSTM技术,描述了一个算法实现细节,信息来源于国外网站,锐英源软件进行了翻译和注解,部分信息著作权归属国外专家。
-------------------------翻译开始---------------------
在这篇文章中,我将展示如何在 C# 中实现 CNTK 106 教程。本教程讲座是用 Python 编写的,没有 C# 中的相关示例。出于这个原因,我决定将这个非常好的教程翻译成 C#。该教程可以在CNTK 106: Part A – Time series prediction with LSTM (Basics)中找到,并使用正弦波函数来预测时间序列数据。对于这个问题,使用了 Long Short Term Memory,LSTM,Recurrent Neural Network。
本教程的目标是预测连续函数(正弦波)的模拟数据。根据函数的先前值,其中是在时间观察到的幅度信号,对 的值的预测将预测相应的未来时间点。
本教程的精彩之处在于使用了非常适合此类问题的 LSTM 循环神经网络。你可能知道,LSTM 是一种特殊的循环神经网络,它能够在训练过程中从经验中学习。更多关于这个奇妙版本的循环神经网络的信息可以在这里找到。
博文分为几个小节:
由于模拟数据集很大,原始教程有两种运行模式,由变量isFast控制。在快速模式的情况下,变量设置为True,本教程将使用此模式。稍后,读者可能会False为了看到更好的训练模型而将值更改为,但训练时间会更长。此博客文章的演示向用户公开了批量大小和迭代次数的变量,因此用户可以根据需要定义这些数字。
为了生成模拟的正弦波数据,我们将实现几个辅助方法。让 和 分别是正弦波的过去值和未来(期望的预测值)的有序集合。两种方法的实现:
采用generateWaveDataset周期函数、一组独立值(对应于这种情况下的时间)并通过提供时间步长和时移来生成波函数。该方法与generate_data()原始教程中的 python 方法有关。
//split data on training and testing part
var a = new float[xsin.Length - timeShift];
var b = new float[xsin.Length - timeShift];
for (int l = 0; l < xsin.Length; l++)
{
//
if (l < xsin.Length - timeShift) a[l] = xsin[l]; // if (l >= timeShift)
b[l - timeShift] = xsin[l];
}
//make arrays of data
var a1 = new List<float[]>();
var b1 = new List<float[]>();
for (int i = 0; i < a.Length - timeSteps + 1; i++)
{
//features
var row = new float[timeSteps];
for (int j = 0; j < timeSteps; j++)
row[j] = a[i + j];
//create features row
a1.Add(row);
//label row
b1.Add(new float[] { b[i + timeSteps - 1] });
}
//split data into train, validation and test data set
var xxx = splitData(a1.ToArray(), 0.1f, 0.1f);
var yyy = splitData(b1.ToArray(), 0.1f, 0.1f);
var retVal = new Dictionary<string, (float[][] train, float[][] valid, float[][] test)>();
retVal.Add("features", xxx);
retVal.Add("label", yyy);
return retVal;
}
注:注意代码里的()元组形式,这和python类似了。另外var动态变量类型也和python类似了。
生成数据后,要创建三个数据集: train、validate 和test,test是上述方法生成的数据拆分而来。下面的splitData方法将原dataset分成三个datasets:
return (data.Skip(0).Take(posVal).ToArray(),
data.Skip(posVal).Take(posTest - posVal).ToArray(), data.Skip(posTest).ToArray());
}
为了可视化数据,创建了 Windows 窗体项目。此外,使用 ZedGraph .NET 类库来可视化数据。下图显示了生成的数据。
如博客文章开头所述,我们将创建 LSTM 循环神经网络,每个输入有 1 个 LSTM 单元。我们有 N 个输入,每个输入都是连续函数中的一个值。LSTM 的 N 个输出是产生单个输出的密集层的输入。在 LSTM 和密集层之间,我们插入了一个 dropout 层,该层随机丢弃来自 LSTM 的 20% 的值,以防止模型过度拟合训练数据集。我们想在训练期间使用 dropout 层,但是在使用模型进行预测时,我们不想丢弃值。
LSTM 的实现可以用一种方法进行总结,但真正的实现可以在本文随附的演示示例中查看。
以下方法实现了上图所示的 LSTM 网络。该方法的参数已经定义。
Func<Variable, Function> pastValueRecurrenceHook = (x) => CNTKLib.PastValue(x);
//creating LSTM cell for each input variable
Function LSTMFunction = LSTMPComponentWithSelfStabilization<float>(
input,
new int[] { LSTMDim },
new int[] { cellDim },
pastValueRecurrenceHook,
pastValueRecurrenceHook,
device).Item1;
//after the LSTM sequence is created return the last cell in order to
//continue generating the network
Function lastCell = CNTKLib.SequenceLast(LSTMFunction);
//implement drop out for 10%
var dropOut = CNTKLib.Dropout(lastCell,0.2, 1);
//create last dense layer before output
var outputLayer = FullyConnectedLinearLayer(dropOut, outDim, device, outputName);
return outputLayer;
}
注:Func<Variable, Function> pastValueRecurrenceHook = (x) => CNTKLib.PastValue(x);Func委托,结果是Function函数,x是参数,实际函数代码是CNTKLib.PastValue(x)。
为了训练模型,nextBatch()实施了产生以提供训练功能的批次方法。请注意,由于 CNTK 支持可变序列长度,我们必须将批次作为序列列表提供。这是生成小批量数据的便利功能,通常称为小批量。
float[] asBatch(float[][] data, int start, int count)
{
var lst = new List<float>();
for (int i = start; i < start + count; i++) { if (i >= data.Length)
break;
lst.AddRange(data[i]);
}
return lst.ToArray();
}
for (int i = 0; i <= X.Length - 1; i += mMSize)
{ var size = X.Length - i; if (size > 0 && size > mMSize)
size = mMSize;
var x = asBatch(X, i, size);
var y = asBatch(Y, i, size);
yield return (x, y);
}
}
注意:由于本教程是作为 WinForms C# 项目实现的,它可以可视化训练和测试数据集,并且可以在训练过程中显示最佳找到的模型,所以还有很多其他实现的方法,这里没有提到,但是可以可以在此博客文章所附的演示源代码中找到。
使用 LSTM 时,用户应注意以下几点:
由于 LSTM 必须使用未知维度的轴,因此应该以不同的方式定义变量,就像我们在之前的博客文章中看到的那样。因此,输入和输出变量使用以下代码清单进行初始化:
如原始教程中所述:“指定动态轴使递归引擎能够以预期的顺序处理时间序列数据。请花点时间了解如何在 CNTK 中同时使用静态和动态轴,如此处所述“,动态轴是 LSTM 中的关键点。
现在继续执行定义学习率、动量、学习者和训练者。
Function trainingLoss = CNTKLib.SquaredError(lstmModel, label, "squarederrorLoss");
Function prediction = CNTKLib.SquaredError(lstmModel, label, "squarederrorEval");
// prepare for training
TrainingParameterScheduleDouble learningRatePerSample =
new TrainingParameterScheduleDouble(0.0005, 1);
TrainingParameterScheduleDouble momentumTimeConstant = CNTKLib.MomentumAsTimeConstantSchedule(256);
IList<Learner> parameterLearners = new List<Learner>() {
Learner.MomentumSGDLearner(lstmModel.Parameters(),
learningRatePerSample, momentumTimeConstant, /*unitGainMomentum = */true) };
//create trainer
var trainer = Trainer.CreateTrainer(lstmModel, trainingLoss, prediction, parameterLearners);
现在代码已经准备好了,这 10 个 epoch 应该会返回可接受的结果:
//Combine variables and data in to Dictionary for the training
var batchData = new Dictionary<Variable, Value>();
batchData.Add(feature, xValues);
batchData.Add(label, yValues);
//train minibatch data
trainer.TrainMinibatch(batchData, device);
}
if (this.InvokeRequired)
{
// Execute the same method, but this time on the GUI thread
this.Invoke(
new Action(() =>
{
//output training process
progressReport(trainer, lstmModel.Clone(), i, device);
}
));
}
else
{
//output training process
progressReport(trainer, lstmModel.Clone(), i, device);
}
}
模型评估在训练过程中实施。通过这种方式,我们可以看到学习过程以及模型是如何变得越来越好的。
对于每个小批量,progress都会调用该方法来更新训练和测试数据集的图表。
reportOnGraphs(trainer, model, iteration, device);
}
private void reportOnGraphs(Trainer trainer, Function model, int i, DeviceDescriptor device)
{
currentModelEvaluation(trainer, model, i, device);
currentModelTest(trainer, model, i, device);
}