创建NTL+语言的神经网络 | IFCM
IFC Markets 網上CFD經紀商

创建NTL+语言的神经网络

引言

人工神经网络 - 是由数学模型, 及其机器硬件和软件的实现, 是基于生物神经网络的组合和功能 - 大脑的神经细胞网络.

当前神经网络应用广泛, 例如识别,分类,联合存储,规律性的确定,预测等等.

神经网络需要单独的数学或基于数学包的附加模型, 以便于提供不同类型和配置方案的网络结构

我们试图基于NTL+语言重新创建自己的网络,并同时尝试面向对象的编程.

问题选择

在本文中我们来测试, 基于过去的烛图可以预测未来柱图的确定走势: 下降或上涨。

这涉及分类问题。我们公司的金融工具允许使用历史数据,以此获得专业的评估。并创建基于多层感知的网络。

因此,根据k柱的收盘价创建网络而预测随后烛图的状态. 如果其收盘价高于前一柱, 那么取输出值1(价格上涨)。在其他情况下,输出值取0(价格没有变化或下跌)。

网络建设

一般而言,多层感知具有一个输入层,一个或多个隐藏层和一个输出层。单个隐藏层足够进行输入转为输出. 使用更多数量的隐藏层只能导致网络学习速度明显放缓, 而没有显著的学习质量.

在网络中保留1个输入层, 一个隐藏层和一个输出层. 在下面的图中显示网络的体系结构. x1 - xn - 输入 (收盘价), wi,j - 自节点i至j的边缘权重; y1 - ym 隐层神经元, o1 - ok 网络输出.


网络也使用输入偏移(偏移输入). 使用这些输入可以确保我们的网络沿x轴移动激活的函数, 从而不仅改变激活函数的斜率, 还可以保证线性偏移.

1. 激活函数表 = 1
2. 激活函数表 при =2 和 = 1
3. 激活函数表, 偏移为 =2, = 1 和 = 1

每个网络节点的值将按照下面的公式计算:
, где f(x) - 激活函数, n - 前一层的节点数.

激活函数

激活函数来计算经过加法器之后获得输出信号。 人工神经元通常作为一个非线性函数的参数。最常用的激活函数:

费米函数(S型指数):


合理的S型:


双曲正切


确定每个节点的输出需要选择合理的S型, 因其计算需要较少时间.

网络学习的过程

为了学习, 我们的神经网络实现了误差的反向传输. 该方法是利用了多层感知达到误差最小化的重复算法. 算法的主要思想是: 在神经网络输出计算后, 计算每个节点和边的误差W的偏差, 并且错误的计算方法是由输出端至输入端. 之后按照误差值校正权重W . 该算法对激活函数的唯一的要求是: 被微分. S型和双曲正切满足这一要求.

因此, 在学习的过程中, 我们需要做到以下几点:

  1. 初始化所有边的权重随机值.
  2. 对于所有的输出, 神经网络计算统计修正
    , 其中 oj - 神经网络计算的输出, tj - 实际值
  3. 除了最后一个节点, 其他都由下面的公式计算
    , wj,k 节点输出的边的权重用于计算修正 节点计算的误差位于两个系那个林的输出层.
  4. 对于每个神经网络的边计算修正:
    , 其中oi 自边的输出的节点计算输出 被计算的节点输出, 用于计算修正, 而被计算的节点修正, 其中包括确定的边.
  5. 校正所有边的权重值:
  6. 重复步骤2 - 5 , 针对所有学习的例子或暂时未达到质量准则的.

准备输入数据

对于实现神经网络的学习需要准备输入数据, 有质量的输入值可以很好的影响网络的工作及其参数w的稳定速度, 也就是学习的过程.

所有的输入向量被正常化, 以便起分量处于区间[0;1] 或 [-1;1]. 正常化使得所有输入向量在网络学习的过程中是平等的, 从而保障学习的修正.

我们来正常化输入向量 - 使其分量处于[0;1], 对此需要使用下列公式:

作为向量的分量, 将进入柱体的收盘价, 并且作为输入移动至[n+k;n+1]柱体的收盘价, 其中k是输入向量的大小. 希望值的确定基于n柱体. 其值大小的确定将遵循: 如果n柱的收盘价大于n+1柱的收盘价, 则取希望值为1, 如果低于或等于, 那么取0;

在图表中标出了黄色的柱体, 其参与组成每组数据(#1, #2), 橙色的是用于确定希望值.

在网络学习的过程中, 也会影响数据的次序也会影响学习的过程. 如果输入向量均匀提供1和0,那么学习的过程更稳定.

同样形成了单独的数据组, 用于评估网络工作的有效性. 这些数据网络不会用于学习, 仅用于按照最小二次方的方法计算错误. 在测试组数据添加10%的输入例子. 其结果是90%例子将用学习, 10%用于评估.

最小二次方计算错误的功能有下列方式:
,
где - 网络输出信号和 - 输出信号的需要值.

查看准备为神经网络输入向量的脚本代码

分析DataSet类 - 数据组. 该类包括:

  • 输入值向量数组 - input
  • 生出输出值- output
  • Normalize 方法- 标准化(正常化)数据
  • OutputDefine方法- 确定价格的实际值
  • AddData方法- 在输入向量的记录值和变量的实际值
  • To_file 方法 - 单矩阵的数据组, 针对下一条目

class DataSet { array <double> input(inputVectorSize); double output; //Normalization void Normalize() { double min = input[0]; double max = input[0]; //Finding minimum and maximum values for(uint i=0;i<input.length();i++) { if(input[i]>max) max=input[i]; if(input[i]<min) min=input[i]; } //Normalizing data for(uint i=0;i<input.length();i++) { //If min==max, setting all values to 0 if(max-min<0.000005) { input[i] = 0; } else { input[i]=(input[i]-min)/(max-min); } } } //Calculating the output void OutputDefine() { //If the price goes up, output is set to 1. if(input[inputVectorSize-1]<output) { output=1; } //If the price goes down or does not change, output is set to 0 else { output=0; } } //Splitting the rawInput array into two parts: input array and output value void AddData(array <double> rawInput) { //If the sizes of the arays don't match, we show a message. if(rawInput.length()!=uint(inputVectorSize+1)) { System.Print("Wrong input array size"); } //Writing to the 'input' array and 'output' variable for(int i=0;i<inputVectorSize;i++) { input[i]=rawInput[i]; } output=rawInput[inputVectorSize]; } // Merging the 'input' array and 'output' variable into one array array<double> To_file() { array<double> temp(inputVectorSize+1); for(int i=0;i<inputVectorSize;i++) { temp[i]=input[i]; } temp[inputVectorSize]=output; return temp; } }

我们来看Run()函数的程序代码, 其实现下列步骤:

  • 加载历史数据. 包括图表的字符和时期.
  • 截数据组为输入向量+ 输出值的长度
  • 形成输入向量的数据组. 标准化这些向量. 确定希望值是1或0.
  • 形成输入向量, 相对对于0和1
  • 数据组主要部分的记录和用于测试的其余部分的记录, 并通过最小二次方的方法进行评估.
int Run() { // The array that holds close prices of all loaded bars array <double> data (Chart.Bars-1); // Writing data to the array 'data' for(int i=1; i < Chart.Bars;i++) { data[i-1]=Close[i]; } // Truncating excessive data that can't form the whole set data.resize((data.length/(inputVectorSize+1))*(inputVectorSize+1)); int num=0; // The array is for holding all input vectors array<DataSet> sets(data.length/(inputVectorSize+1)); array <double> temp(inputVectorSize+1); // The array is for holding input vectors in the correct order array <DataSet> alternation; // Forming elements in the 'sets' array: normalizing each vector and calculating output (0 or 1) for(int i=data.length-1;i>=0;i-=(inputVectorSize+1)) { for(int j=0;j<inputVectorSize+1;j++) { temp[j]=data[i-j]; } sets[num].AddData(temp); sets[num].OutputDefine(); sets[num].Normalize(); num++; } // Forming the array 'alternation', where all the sets are sorted, so that they go 1,0,1,0 etc. uint len = sets.length; for(uint i=0;i<len;i++) { for(uint j=0;j<sets.length;j++) { if(sets[j].output==0 && state==1) { alternation.insertLast(sets[j]); sets.removeAt(j); state = 0; break; } if(sets[j].output==1 && state==0) { alternation.insertLast(sets[j]); sets.removeAt(j); state = 1; break; } } } // The file for writing data for learning file f; f.Open("data.txt",fmWrite|fmText); // Calculating the number of sets with data for learning and testing data uint datasets = int(alternation.length()*0.9); uint testsets = alternation.length() - datasets; System.Print("datasets SETS = "+datasets); // Writing data for learning to the file for(uint i=0;i<datasets;i++) { for(int j=0;j<inputVectorSize+1;j++) { f.WriteDouble(alternation[i].To_file()[j]); } } f.Close(); System.Print("testsets SETS = "+testsets); // Data for testing // Writing to the file file t; t.Open("test.txt",fmWrite|fmText); for(uint i=0;i<testsets;i++) { for(int j=0;j<inputVectorSize+1;j++) { t.WriteDouble(alternation[i+datasets].To_file()[j]); } } t.Close(); return(0); }

在本文中, 您还需要声明一个全局变量int state = 0, 需要存在轮换的输入向量.

创建神经网络的种类

对于我们的网络需要:

  • 联合输入层和隐藏层的类layer
  • 联合隐藏层和输出层的类layer
  • 连接各层的类 net

创建类layer-层

我们需要包含下列属性和方法的类:

  • 保存神经网络的输入'input'数组
  • 保存神经网络的输出'output'数组
  • 保存神经网络的修正'delta'数组
  • 所有边的权重'weights' 的双层数组
  • 'LoadInputs'方法赋值输入层
  • 'LoadWeights'方法加载层的权重
  • 'SaveWeights'方法保存层的权重至硬盘中
  • 选出使用数组所需要的内存
  • 'RandomizeWeights'方法, 填写权重随机值
  • 'OutputCalculation'方法,计算输出值
  • 'CalculatingDeltaLast' 和 CalculatingDeltaPrevious方法, 计算修正值
  • 'WeightsCorrection'方法, 修正权重值
  • 补充(诊断)方法, 输出信息至屏幕.:
    • PrintInputs() - 打印输入层
    • PrintOutputs() - 打印输出层 ('OutputCalculation'帮助下, 在计算输出之后打开)
    • PrintDelta() - 打印层的修正(在'CalculatingDeltaLast' 或 'CalculatingDeltaPrevious'帮助下, 在计算修正之后打开)
    • PrintWeights() - 打印层的权重w

    所得类放置在单独的文件中. 此外, 在该文件中将存储的网络配置: 输入层, 隐藏层和输出层的节点数量. 这些变量被取出使得同样用于准备输入数据的脚本.

    extern int inputVectorSize = 8; // input vector extern int L1_Innersize = 8; // number of knots in the hidden layer without bias extern int L2_Innersize= 1; // number of knots in the output layer without bias extern double nu = 0.1; //learning rate in the backpropagation method class layer { // input values array<double> input; // output values array<double> output; // output array size int outputSize=0; // weights array<array <double>> weights; // amendments array<double> delta; // default constructor layer() { } // loading input values from the 'inp' array. bool LoadInputs(array <double> inp) { if(inp.length()+1 != input.length()) { System.Print("Loading is not possible. Array sizes do not match"); return false; } for(uint i=0;i<inp.length();i++) { input[i]=inp[i]; } return true; } // loading weights from a specifies file bool LoadWeights(string filename) { file f; if(!f.Open(filename,fmRead|fmText)) { return false; } for(uint i=0;i<weights.length();i++) { for(uint j=0;j<weights[0].length();j++) { weights[i][j]=f.ReadDouble(); } } f.Close(); return true; } // loading weights to a specified file bool SaveWeights(file f) { for(uint i=0;i<weights.length();i++) { for(uint j=0;j<weights[0].length();j++) { if(!f.WriteDouble(weights[i][j])) { f.Close(); return false; } } } return true; } // constructor // inp - array of values // size - size of output array layer(array<double> inp, int size) { input = inp; input.insertLast(1); // Bias outputSize = size; //weigths array array<array<double>> temp_weights(size, array<double>(inp.length()+1)); array<double> temp_output(size); output = temp_output; weights = temp_weights; array<double> delta_temp(size); delta = delta_temp; } //randomizing weights void RandomizeWeights() { for(uint i=0;i<weights.length();i++) { for(uint j=0;j<weights[0].length;j++) { weights[i][j]=(-0.5+double(Math.Rand())/32767)*0.1; } } } // printing weights void PrintWeights() { string line; for(uint i=0;i<weights.length();i++) { line = "weights"; for(uint j=0;j<weights[0].length;j++) { line += " ["+i+"]["+j+"]="+weights[i][j]; } System.Print(line); } } // printing input values void PrintInputs() { for(uint i=0;i<input.length;i++) { System.Print("inputs["+i+"]="+input[i]); } } // print output values void PrintOutputs() { for(uint i=0;i<output.length;i++) { System.Print("outputs["+i+"]="+output[i]); } } // printing delta values void PrintDelta() { for(uint i=0;i<delta.length();i++) { System.Print("delta ["+i+"]="+delta[i]); } } // output calculation void OutputCalculation() { // temporary array to store output values array<double> a(outputSize,0); for(int k=0;k<outputSize;k++) { for(uint i=0;i<input.length();i++) { a[k]+=weights[k][i]*input[i]; } a[k]=Activation(a[k]); } output = a; } //Changing weights of the last layer void CalculatingDeltaLast(array <double> realValues) { //System.Print("delta.length()="+delta.length); if(realValues.length()!=delta.length()) { System.Print("不同大小的数组的差异"); return; } for(uint i=0;i<realValues.length();i++) { delta[i]=-output[i]*(1-output[i])*(realValues[i] - output[i]); } } //Changing weight for any layers except for the last. void CalculatingDeltaPrevious(layer &inout LayerLast) { for(uint j=0;j<LayerLast.input.length()-1;j++) { double sum = 0; for(uint k=0;k<LayerLast.output.length();k++) { sum += LayerLast.delta[k]*LayerLast.weights[k][j]; } delta[j]=output[j]*(1-output[j])*sum; } } //Changed weights void WeightsCorrection() { for(uint i=0;i<weights.length();i++) { for(uint j=0;j<weights[0].length();j++) { weights[i][j] = weights[i][j] - nu*delta[i]*input[j]; } } } }

    我们还需要辅助的功能: 标准化输入向量和激活功能

    void Normalize(array<double> &inout input) { double min = input[0]; double max = input[0]; //finding minimum and maximun for(uint i=0;i<input.length();i++) { if(input[i]>max) max=input[i]; if(input[i]<min) min=input[i]; } for(uint i=0;i<input.length();i++) { if(max-min<0.000005) { input[i] = 0.0; } else { input[i]=(input[i]-min)/(max-min); } } } // Activation function double Activation(double x) { return x/(Math.Abs(x)+1); }

    创建网络的类net

    我们还需要这样的类, 即可以统一层和唯一的网络, 因此这样的类需要包括:

    • 'Calculate'方法, 连接层来计算网络的输出. 该方法将用于网络的学习.
    • 'CalculateAndLearn'方法, 计算输出, 修正和错误. 计算输出您需要使用前一个方法, 而对于修正和错误您需要引用每个层的相应的方法
    • 'SaveNetwork '方法, 保存网络的参数.
    • 'LoadNetwork'方法, 加载网络的参数.
    class net { array <double> x(inputVectorSize); array <double> y(L1_Innersize); layer L1(x,L1_Innersize); array<double> L1_outputs(L1_Innersize); layer L2(y,L2_Innersize); //Calculating output using current weights array<double> Calculate(array <double> data, bool randomWeights) { if(data.length()!=x.length()) { System.Print("Array sizes do not match"); } x = data; Normalize(x); L1.LoadInputs(x); if(randomWeights) { L1.RandomizeWeights(); L2.RandomizeWeights(); } L1.OutputCalculation(); L2.LoadInputs(L1.output); L2.OutputCalculation(); return L2.output; } //Calculating output and changing weights void CalculateAndLearn(array <double> data, array <double> realValues, bool randomWeights) { Calculate(data, randomWeights); L2.CalculatingDeltaLast(realValues); L1.CalculatingDeltaPrevious(L2); L2.WeightsCorrection(); L1.WeightsCorrection(); } //Saving the weights of the whole network bool SaveNetwork(string filename) { file f; if(!f.Open(filename,fmWrite|fmRead|fmText)) { f.Open(filename,fmWrite|fmText); } L1.SaveWeights(f); L2.SaveWeights(f); f.Close(); return true; } //Loading all weights bool LoadNetwork(string filename) { file fn; if(fn.Open(filename,fmRead|fmText)==false) { return false; } for(uint i=0;i<L1.weights.length();i++) { for(uint j=0;j<L1.weights[0].length();j++) { L1.weights[i][j]=fn.ReadDouble(); } } for(uint i=0;i<L2.weights.length();i++) { for(uint j=0;j<L2.weights[0].length();j++) { L2.weights[i][j]=fn.ReadDouble(); } } return true; } }

    检查网络的工作

    在本节中,我们以一个基本的例子来检查网络的工作, 在其帮助下我们可以确定输出向量的正确分类. 对此将使用2个输入层和隐藏层, 1个输出层的神经网络. 参数nu 设置为1. 输入数据的集如下:
    输入 1,2 和希望值 1
    输入 2,1 和希望值 0
    在文找中将是如下形式::

    1.00000000 2.00000000 1.00000000 2.00000000 1.00000000 0.00000000

    输入不同数量的学习的例子, 可以看到我们的网络学习的过程.


    图中红线对应1的学习例子. 蓝线 - 对应0. 根据各自横轴截取学习的例子, 根据纵轴计算学习例子的网络值. 可以看出, 如果例子的数量少(25及其以下)网络不能差别不同的输入向量, 如果数量多- 很明确的分为两类.

    评估网络

    为了评估学习的效果, 我们使用最小二次方的方法计算错误. 对此要创建具有以下代码的实用程序并运行它. 在此添加2个文件: "test.txt" - 计算错误时用到的数据和希望值, "NT.txt" - 已计算参数的文件.

    该脚本完成下列步骤:

    • 创建net类的对象NT
    • 校对参数w 并加载至NT
    • 创建数组的网络输出和输入
    • 校对输入值并置于数组'x'中, 校对输入并置于'reals'
    • 计算网络的输出, 提供给数字'x'
    • 计算错误error, 所有计算值和实际值差异的平方和
    • 当临近文件结尾, 输出错误值并退出脚本
    #include "Libraries\NN.ntl" int Run() { net NT; file f; int counter = 0; // Loading weights NT.LoadNetwork("NT.txt"); double error=0; // Declaring an array for storing culculated outputs array <double> CalculatedOutput (L2_Innersize); // Opening a file with test data if(f.Open("test.txt",fmRead|fmText)==false) { System.Print("Input data not found"); return -1; } // Array holds input values array <double> x(inputVectorSize); // Actual output array <double> reals(L2_Innersize); while(true) { for(int i=0;i<inputVectorSize;i++) { x[i]=f.ReadDouble(); if(f.IsEOF()) { System.Print("counter="+counter+"error="+0.5*error+"error/counter="+(0.5*error/counter)); return -1; } } //forming real examples for(int j=0;j<L2_Innersize;j++) { reals[j]=f.ReadDouble(); System.Print("Real exit = "+reals[j]); } CalculatedOutput = NT.Calculate(x,false); System.Print("Calculated exit = "+CalculatedOutput[0]); //calculating error for(uint i=0;i<reals.length();i++) { error+=(reals[i]-CalculatedOutput[i])*(reals[i]-CalculatedOutput[i]); } counter++; } return(0); }

    创建指标

    我们需要变量用于保存被计算的网络输出, 如果输出值为1 , 那么1个变量足够, 但是网络常有多个输出, 因此需要一个值的数组我们将其放置在行array CalculatedOutput (L2_Innersize);. 在初始函数中, 我们定义了指标的参数, 类型, 相关的属性值(两个内存值). 同样我们还需要重置网络的参数, 即加载所有的在学习中的权重参数. 这通过对象NT的 LoadNetwork(string s)方法, 及一个参数- 包含权重参数的文件名. 在函数Draw中我们组成了输入向量x , 对应指数[pos+inputVectorSize-1; pos]收盘价, 其中pos - 计算值的柱体号, 而inputVectorSize 输入向量的长度. 在结尾调取对象NT的函数Calculate , 返回数组的值(如果网络有几个输出值), 因为我们的输出只有一个 - 使用指数为0的数组的单元.

    #set_indicator_separate #include "Libraries\NN.ntl" double ExtMapBuffer1[]; double ExtMapBuffer2[]; int ExtCountedBars=0; net NT; array <double> CalculatedOutput (L2_Innersize); int Initialize() { Indicator.SetIndexCount(2); Indicator.SetIndexBuffer(0,ExtMapBuffer1); Indicator.SetIndexStyle(0,2,0,3,0xFF0000); Indicator.SetIndexBuffer(1,ExtMapBuffer2); Indicator.SetIndexStyle(1,2,0,3,0xFF0000); NT.LoadNetwork("NT.txt"); return(0); } int Run() { ExtCountedBars=Indicator.Calculated; if (ExtCountedBars<0) { System.Print("Error"); return(-1); } if (ExtCountedBars>0) ExtCountedBars--; Draw(); return(0); } void Draw() { int pos=Chart.Bars-ExtCountedBars-1; array <double> x(inputVectorSize); while(pos>=0) { ExtMapBuffer1[pos]=0; // forming input vector for(int i=pos+inputVectorSize-1; i>=pos; i--) { x[i-pos]=Close[i]; } ExtMapBuffer2[pos]=NT.Calculate(x,false)[0]; pos--; } }

    显示的直方图反映下一个柱图的预期. 值从0.5-1说明下一个柱图很有可能是下降的. 值从0至0.5则说明下一个柱图很有可能是上涨的. 0.5对应的是不确定. 即下一个柱图可能上涨, 也可能下降.

    概要

    神经网络是技术分析的强大工具. 在本文中展示了通过NTL+利用面向对象的语言来创建神经网络的过程. 使用面向对象的方法使得简化代码, 并更容易用在未来的脚本中. 在实践中, 我们创建了类 layer 描述神经网络的层和类net描述整体的网络. 类net我们用于确定错误数量和计算权重的指标. 此外还举例展示了如何利用学习网络.

Close support
Call to Skype Call to QQ Call Back