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:
# | 1 | 2 | ... | 10 | 11 |
NASDAQ | 2288.55 | 2301.66 | ... | 2231.65 | ? |
DOW | 12376.72 | 12319.73 | ... | 12350.61 | ? |
S&P500 | 1110.88 | 1112.92 | ... | 1099.5 | ? |
PIR | 3.25 | 3.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; }
이것도 참고하세요.
http://swlock.blogspot.kr/2015/04/blog-post.html
감사합니다.
답글삭제오~ 감사합니다
답글삭제