Создание нейронной сети на языке NTL+

Введение

Искусственные нейронные сети - математические модели, а также их программные или аппаратные реализации, построенные по принципу организации и функционирования биологических нейронных сетей - сетей нервных клеток (нейронов) мозга.

В текущий момент нейронные сети достаточно широко применяются во многих задачах распознавания, классификации, ассоциативной памяти, определения закономерностей, прогнозирования и т.д.

Для работы с нейронными сетями существуют отдельные математические продукты или дополнительные модули к основным математическим пакетам, предоставляющие широкие возможности по конструированию сетей различных типов и конфигураций.

Мы же попробуем создать свою собственную сеть с нуля при помощи языка NTL+, а заодно попробуем его возможности объектно-ориентированного программирования.

Выбор задачи

В этой статье давайте проверим гипотезу, что по прошедшим барам можно предсказать с определенной вероятностью вид будущего бара: снижающийся или растущий.

Данная задача относится к задачам классификации. В нашем распоряжении также доступна обширная историческая информация по финансовым инструментам, что позволит использовать эти исторические данные для получения "экспертной" оценки. В связи с этим, будем создавать сеть на основе многослойного персептрона.

Итак, попытаемся создать сеть, предсказывающую по ценам закрытия k баров, вид последующего бара. Если его цена закрытия выше цены закрытия предыдущего бара, то примем желаемое значение выхода за 1 (цена выросла). В остальных случаях, примем желаемое значение выхода за 0 (цена не изменилась или упала).

Конструирование сети

В общем виде многослойный персептрон имеет один входной слой, один или несколько скрытых слоев и один выходной слой. Единственного скрытого слоя достаточно для преобразования входов таким образом, чтобы линейно разделить выходное представление. Использование большего числа скрытых слоев часто приводит лишь к заметному снижению скорости обучения сети без заметного выигрыша в качестве обучения.

Остановимся на сети с одним входным слоем, одним скрытым и одним выходным слоем. На следующем рисунке представлена архитектура нашей сети в общем виде. x1 - xn - входы (цены закрытия), wi,j - веса ребер, выходящих из узла i и входящих в j; y1 - ym нейроны скрытого слоя, o1 - ok выходы сети.


Также в сети используются входы смещения (bias inputs). Использование этих входов обеспечивает возможность нашей сети по сдвигу активационной функции вдоль оси абсцисс, тем самым сеть может не только изменять крутизну активационных функций, но и обеспечивать ее линейное смещение.

1. График активационной функции при = 1
2. График активационной функции при =2 и = 1
3. График активационной функции при задании смещения при =2, = 1 и = 1

Значения для каждого узла сети будем вычислять по формуле:
, где f(x) - функция активации, а n-число узлов в предыдущем слое.

Функции активации

Функция активации вычисляет выходной сигнал, получаемый после прохождения сумматора. Искусственный нейрон обычно представляют, как некоторую нелинейную функцию от единственного аргумента. Наиболее часто используются следующие функции активации:

Функция Ферми (экспоненциальная сигмоида):


Рациональная сигмоида:


Гиперболический тангенс:


Для определения выходов каждого слоя была выбрана рациональная сигмоида, так как ее вычисление занимаем меньше всего процессорного времени.

Процесс обучения сети

Для обучения нашей сети реализуем метод обратного распространения ошибки. Этот метод является итеративным алгоритмом, используемый с целью минимизации ошибки работы многослойного персептрона. Основная идея алгоритма: после вычисления выходов сети, вычисляются поправки каждого узла и ошибкa w для каждого ребра, при этом вычисление ошибки происходит в направлении от выходов сети к ее входам. Затем происходим корректировка весов w в соответствии с вычисленным значением ошибки. Этот алгоритм накладывает единственное требование на активационную функцию - она должна быть дифференцируемой. Сигмоиды и гиперболический тангенс удовлетворяют этому требованию.

Итак, для процесса обучения нам понадобится выполнить следующую последовательность действий:

  1. Инициализировать веса всех ребер малыми случайными значениями
  2. Для всех выходов сети подсчитать поправку
    , где oj - вычисленный выход сети, tj - фактическое значение
  3. Для каждого узла, кроме последнего вычислить поправку по формуле
    , где wj,k веса на ребрах, выходящих из узла, для которого вычисляется поправка, а вычисленные поправки для узлов, находящихся ближе к выходному слою.
  4. Для каждого ребра сети вычислить поправку:
    , где oi рассчитанный выход узла из которого выходит ребро, для которого вычисляется ошибка, а вычисленная поправка узла в который входит указанное ребро.
  5. Откорректировать значения весов для всех ребер:
  6. Повторять шаги 2 - 5 для всех обучающих примеров или пока не будет достигнут выбранный критерий качества обучения.

Подготовка входных данных

Для осуществления процесса обучения сети необходимо подготовить входные данные, качественные входные значения оказывают значительное влияние на работу сети и скорость стабилизации ее коэффициентов w, то есть, фактически, на сам процесс обучения.

Все входные вектора рекомендуется нормализовать, чтобы их компоненты лежали в диапазоне [0;1] или [-1;1]. Нормализация делает все входные вектора равноправными в процессе обучения сети, тем самым обеспечивается корректная процедура её обучения.

Будем нормализовывать наши входные вектора - преобразовывать значения их компонент к диапазону [0;1], для этого воспользуемся следующий формулой:

В качестве компонент вектора будут выступать цены закрытия баров, при этом в качестве входов будут подаваться цены закрытия баров c индексами [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() и выполняющий следующую последовательность действий:

  • Загрузка всей истории, имеющейся в терминале во внутренний массив. Загрузка осуществляется по тому символу и таймфрейму для которого на данный момент выводится график.
  • Обрезание массива баров до размера, кратному длине входного вектора + выходное значение
  • Формирования массива входных векторов. Нормализация этих векторов. Определение желаемого значения 0 или 1
  • Формирование массива отсортированных входных векторов, где вектора соответствующие 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]; } } } }

    Также нам понадобятся вспомогательные функции, которые мы поместим вне классов. Это будут 2 функции: нормализация входного вектора и функция активации.

    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; } }

    Проверка работы сети

    В этом подразделе проверим работу нашей сети на элементарном примере, с его помощью определим, правильность классификации входных векторов. Для этого будем использовать сеть с двумя входами, двумя узлами скрытого слоя и одним выходом. Параметр 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" - файл с уже вычисленными коэффициентами для работы сети.

    Данный скрипт выполняет следующую последовательность действий:

    • Создает объект NT класса net
    • Считывает коэффициенты 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); }

    Создаем индикатор

    Создадим индикатор, показывающий решение сети о следующем баре при помощи гистограммы. Логика работы индикатора простая. Значение 0 будет соответствовать снижению значения следующего бара, значение 1 - увеличение значения, а 0.5 - равновероятные события уменьшения и увеличения цены закрытия. Для вывода в отдельном окне укажем #set_indicator_separate, также нужно указать файл, в котором определен класс net, его мы подключаем в строке #include "Libraries\NN.ntl". Кроме этого нам понадобится переменная для хранения рассчитанного выхода сети для заданного входа, если выходное значение одно, то достаточно единственной переменной, но сеть может иметь несколько выходов, так что в общем виде нам потребуется массив значений: его мы объявляем в строке array CalculatedOutput (L2_Innersize);. В функции инициализации мы определяем параметры нашего индикатора, его тип, связываем значения гистограммы с двумя буферами значений. Также нам понадобится восстановить параметры нашей сети, то есть загрузить в нее все весовые коэффициенты, вычисленные в процессе обучения. Это осуществляется при помощи метода LoadNetwork(string s) объекта NT, с единственным параметром - именем файла, содержащем весовые коэффициенты. В функции Draw мы формируем входной вектор x соответствующий ценам закрытия баров с индексами [pos+inputVectorSize-1; pos], где pos - номер бара для которого вычисляется значение, а inputVectorSize длина входного вектора. В конце вызывается метод Calculate объекта NT, возвращающий массив значений (если в нашей сети несколько выходов), но так как у нас выход только один - используем элемент массива с индексом 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 мы использовали для индикатора, определения величины ошибки и вычисления собственных весов сети. Кроме того, было показано, как можно использовать обученную сеть на примере индикатора-гистограммы, показывающем прогноз сети по изменению цены.

IFCMARKETS. CORP. не является компанией, регулиремой ЦБ РФ, и не предоставляет финансовые услуги на территории РФ. Переведенная страница не ориентирована для граждан РФ.