본문 바로가기

Infomation

신경망을 이용한 경제(주식) 예측 코드 분석

http://swlock.blogspot.kr/2014/11/blog-post.html


신경망을 이용한 경제(주식) 예측 코드 분석

신경망을 이용해 뭘할 수 있을까? 고민이 많을것 같은데요.. 요즈음 deep learning이나 이미지 인식쪽에 주제가 많은 것 같습니다.
codeproject를 살피다 보니 흥미로운 주제를 발견하였습니다.
이름하여 경제(주식)예측 입니다.
http://www.codeproject.com/Articles/175777/Financial-predictor-via-neural-network

그전에 해당글의 저자가 아래와 같이 경고를 하고 있습니다. 해당 글은 교육 목적으로 쓰여졌으며 설명된 어플은 실제 시나리오에서는 사용되어질 수 없다고 합니다.
I would like to warn you that the entire article is written for educational purposes, thus the described application cannot be used in real-world scenario.
어떤한 방법으로 예측을 구현했는지 소스를 분석하도록 하겠습니다. 소스는 c#으로 되어 있습니다. java와비슷하면서도 c++과 비슷해서 어떻게 동작하는지 확인하는건 어렵지 않을것 같습니다.
The data that will be feed to neural network at the input, represents historical data of the S&P500, DOW, NASDAQ Composite and Prime Interest Rate. In general terms, these are leading indicators of stock market activity, which have a common fluctuation pattern.

입력에서 신경 네트워크에 입력으로 사용될 데이터는 S&P500, 다우, 나스닥, 프라임 금리의 과거 데이터를 사용합니다. 이러한 공통의 변동 패턴이 주식 시장 활동의 지표를 선도하고 있습니다.

Input

입력 데이터로는 4개의 지표의 과거 10개의 데이터를 사용합니다. 결국 4*10=40개의 입력 데이터 x를 넣고 11번째의 데이터를 y에 넣어서 학습을 합니다.
Pairs used in prediction:
#12...1011
NASDAQ2288.552301.66...2231.65?
DOW12376.7212319.73...12350.61?
S&P5001110.881112.92...1099.5?
PIR3.253.25...3.25?

그러면 입력 데이터는 어떻게 되어있는지 살펴보도록 하겠습니다.
dow.csv
Date,Open,High,Low,Close,Volume,Adj Close
2011-04-01,12321.02,12454.52,12301.11,12376.72,4223740000,12376.72
2011-03-31,12350.84,12422.96,12277.05,12319.73,3566270000,12319.73
...

nasdaq.csv
Date,Open,High,Low,Close,Volume,Adj Close
2011-04-01,2796.67,2802.63,2779.71,2789.60,2090120000,2789.60
2011-03-31,2774.23,2783.98,2769.52,2781.07,1896420000,2781.07
...

rates.csv
date,prime
1970-01,8.50
1970-02,8.50
...

SP500.csv
Date,Open,High,Low,Close,Volume,Adj Close
2011-04-01,1329.48,1337.85,1328.89,1332.41,4223740000,1332.41
2011-03-31,1327.44,1329.77,1325.03,1325.83,3566270000,1325.83
...


csv 파일을 읽어들이는 파일은 FinancialMarketPredictor\Entities\FinancialPredictorManager.cs 입니다.
dow의 경우 많은 항목 중 double amount = csv.GetDouble(ADJ_CLOSE_HEADER); Adj Close 값을 읽어들어입니다. 값을 봐서는 Close(종가) 값과 동일한데 Adj의 의미는 조정된 의미니까 조정된 종가 의미라고 받아들이면 되는데요. dow값이 Close, Adj Close값이 항상 같습니다.
nasdarq의 경우도 double amount = csv.GetDouble(ADJ_CLOSE_HEADER); adj close값을 읽어들입니다.
s&p500도 마찬가지로 adj close값을읽고 rates의경우는 하나의 값 밖에는 없습니다.
모두 읽었으면 데이터를 정규화 합니다. NormalizeData() 함수 이용, 즉 최대 최소값을 읽어서 해당범위의 값으로 바꾸면 모든 수치가 0~1 사이의 범위에 있게 만들어 지는데 이 과정을 정규화라고 합니다.
이 모든 과정을 FinancialPredictorManager.cs Load()라는 함수를 통해서 이루어 집니다.

        public void Load(String sp500Filename, String primeFilename, String pathToDow, String pathToNasdaq)
        {
            if (!File.Exists(sp500Filename))
                throw new ArgumentException("sp500Filename targets an invalid file");
            if (!File.Exists(primeFilename))
                throw new ArgumentException("primeFilename targets an invalid file");
            if (!File.Exists(pathToDow))
                throw new ArgumentException("pathToDow targets an invalid file");
            if (!File.Exists(pathToNasdaq))
                throw new ArgumentException("pathToNasdaq targets an invalid file");
            try
            {
                LoadSp500(sp500Filename);
            }
            catch
            {
                throw new NotSupportedException("Loading SP500 file failed. Not supported file format. Make sure \"date\" and \"adj close\" column headers are written in the file");
            }
            try
            {
                LoadPrimeInterestRates(primeFilename);
            }
            catch
            {
                throw new NotSupportedException("Loading Prime Interest Rate file failed. Not supported file format. Make sure \"date\" and \"prime\" column headers are written in the file");
            }
            try
            {
                LoadDowIndexes(pathToDow);
            }
            catch
            {
                throw new NotSupportedException("Loading Dow indexes file failed. Not supported file format. Make sure \"date\" and \"adj close\" column headers are written in the file");
            }
            try
            {
                LoadNasdaqIndexes(pathToNasdaq);
            }
            catch
            {
                throw new NotSupportedException("Loading Nasdaq indexes file failed. Not supported file format. Make sure \"date\" and \"adj close\" column headers are written in the file");
            }
            MaxDate = MaxDate.Subtract(new TimeSpan(_inputSize, 0, 0, 0)); /*Subtract 10 last days*/
            StitchFinancialIndexes();
            _samples.Sort();            /*Sort by date*/
            NormalizeData();
        }

사용자가 train 버튼을 누르게 되면 FinancialMarketPredictor.cs BtnStartTrainingClick()를 거쳐서 _predictor.TrainNetworkAsync(trainFrom, trainTo, callback);  -> TrainNetwork()를 호출하게 됩니다.
기본적으로 data를 설정하고 train후 에러를 검사하고 반복하는 형태입니다.

        private void TrainNetwork(DateTime trainFrom, DateTime trainTo, TrainingStatus status)
        {
......
            try
            {
               
                var trainSet = new BasicNeuralDataSet(_input, _ideal);
                train = new ResilientPropagation(_network, trainSet);
                double error;
                do
                {
                    train.Iteration();
                    error = train.Error;
                    if (status != null)
                        status.Invoke(epoch, error, TrainingAlgorithm.Resilient);
                    epoch++;
                } while (error > MAX_ERROR);
            }

검색해봤는데 ResilientPropagation 함수를 아무리 찾아봐도 없었습니다.
구글 검색 해보니 NN엔진을 사용한것 같습니다.

Encog Machine Learning Framework 

상당히 잘되어있는 라이브러리처럼 보입니다. 와우... GPU도 지원한다고 합니다.
Encog can also make use of a GPU to further speed processing time.
http://www.heatonresearch.com/encog

다시 소스로 돌아가서 input data를 찾아보도록 하겠습니다.

        public void CreateTrainingSets(DateTime trainFrom, DateTime trainTo)
        {
            // find where we are starting from
            int startIndex = -1;
            int endIndex = -1;
            foreach (FinancialIndexes sample in _manager.Samples)
            {
                if (sample.Date.CompareTo(trainFrom) < 0)
                    startIndex++;
                if (sample.Date.CompareTo(trainTo) < 0)
                    endIndex++;
            }
            // create a sample factor across the training area
            _trainingSize = endIndex - startIndex;
            _input = new double[_trainingSize][];
            _ideal = new double[_trainingSize][];

            // grab the actual training data from that point
            for (int i = startIndex; i < endIndex; i++)
            {
                _input[i - startIndex] = new double[INPUT_TUPLES * INDEXES_TO_CONSIDER];
                _ideal[i - startIndex] = new double[OUTPUT_SIZE];
                _manager.GetInputData(i, _input[i - startIndex]);
                _manager.GetOutputData(i, _ideal[i - startIndex]);
            }

CreateTrainingSets()함수에서 _input 함수에 입력 데이터가 들어가며 _ideal에 훈련을 위한 출력 데이터가 들어갑니다. 갯수는 INPUT_TUPLES = 10, INDEXES_TO_CONSIDER = 4, OUTPUT_SIZE = 4 입니다.
cvs 파일에서 읽어들인 normalize된 변수들을 GetInputData를 통해서 40개의 입력을 받아들이는데 startIndex~endIndex를 사용자가 날짜로 선택하게 되는데 GetInputData에 첫번째 인자에 의해서 4개씩 4개씩 모두 40개가 데이터에 들어옵니다.

        public void GetInputData(int offset, double[] input)
        {
            // get SP500, prime data, NASDAQ, Dow
            for (int i = 0; i < _inputSize; i++)
            {
                FinancialIndexes sample = _samples[offset + i];
                input[i*4]       = sample.Sp;
                input[i*4 + 1]   = sample.PrimeInterestRate;
                input[i*4 + 2]   = sample.DowIndex;
                input[i*4 + 3]   = sample.NasdaqIndex;
            }
        }
출력 데이터는 현재 offset+_inputSize 즉 10을 더한 위치의 값이 됩니다.
        public void GetOutputData(int offset, double[] output)
        {
            FinancialIndexes sample = _samples[offset + _inputSize];
            output[0]     = sample.Sp;
            output[1] = sample.PrimeInterestRate;
            output[2] = sample.DowIndex;
            output[3] = sample.NasdaqIndex;
            
        }
정리하자면 input의 경우 [idx+0],[idx+1],[idx+2],[idx+3],~~,[idx+9]:10개씩 * 각가의 데이터 4개, 출력의 경우 [idx+10]:1개를 각각의 데이터 4개가 됩니다.

입력과 출력이 어떻게 만들어 졌는지 알면 신경망에서는 모든 분석이 완료되었다고 생각이 되네요.
예측시에는 input의 경우 [idx+0],[idx+1],[idx+2],[idx+3],~~,[idx+9]:10개씩 * 각가의 데이터 4개 모두 40개를 입력에 넣고 나오는 출력값을 보면 알게 될겁니다.
소스는 다음과 같습니다.

        public List<PredictionResults> Predict(DateTime predictFrom, DateTime predictTo)
        {
            List<PredictionResults> results = new List<PredictionResults>();
            double[] present = new double[INPUT_TUPLES * INDEXES_TO_CONSIDER];
            double[] actualOutput = new double[OUTPUT_SIZE];
            int index = 0;
            foreach (var sample in _manager.Samples)
            {
                if (sample.Date.CompareTo(predictFrom) > 0 && sample.Date.CompareTo(predictTo) < 0)
                {
                    PredictionResults result = new PredictionResults();
                    _manager.GetInputData(index - INPUT_TUPLES, present);
                    _manager.GetOutputData(index - INPUT_TUPLES, actualOutput);
                    var data = new BasicNeuralData(present);
                    var predict = _network.Compute(data);
                    result.ActualSp = actualOutput[0] * (_manager.MaxSp500 - _manager.MinSp500) + _manager.MinSp500;
                    result.PredictedSp = predict[0] * (_manager.MaxSp500 - _manager.MinSp500) + _manager.MinSp500;
                    result.ActualPir = actualOutput[1] * (_manager.MaxPrimeRate - _manager.MinPrimeRate) + _manager.MinPrimeRate;
                    result.PredictedPir = predict[1] * (_manager.MaxPrimeRate - _manager.MinPrimeRate) + _manager.MinPrimeRate;
                    result.ActualDow = actualOutput[2] * (_manager.MaxDow - _manager.MinDow) + _manager.MinDow;
                    result.PredictedDow = predict[2] * (_manager.MaxDow - _manager.MinDow) + _manager.MinDow;
                    result.ActualNasdaq = actualOutput[3] * (_manager.MaxNasdaq - _manager.MinNasdaq) + _manager.MinNasdaq;
                    result.PredictedNasdaq = predict[3] * (_manager.MaxNasdaq - _manager.MinNasdaq) + _manager.MinNasdaq;
                    result.Date = sample.Date;
                    ErrorCalculation error = new ErrorCalculation();
                    error.UpdateError(actualOutput, predict.Data);
                    result.Error = error.CalculateRMS();
                    results.Add(result);
                }
                index++;
            }
            return results;
        }