레이블이 Deep Learning인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Deep Learning인 게시물을 표시합니다. 모든 게시물 표시

2019년 5월 4일 토요일

LSTM 예제 코드 분석 (predicting stock price)


LSTM 에 대한 이론적인 설명은 조금만 검색해봐도 알 수 있습니다. 그래서 예제를 통한 입력 형태와 출력를 살펴 보았습니다.

예제 소스는 아래 stock price predicting 주제중에 제일 마지막 아래 있는 LSTM 예제입니다. 해당 소스를 분석하여 설명을 추가 하였습니다.

https://www.analyticsvidhya.com/blog/2018/10/predicting-stock-price-machine-learningnd-deep-learning-techniques-python/

일단 실행 가능한 소스를 공유합니다. 위 링크의 소스가 부분 부분 나뉘어져 있어서 막상 실행하려고 하면 실행이 안되어 약간 수정 및 로그를 추가 하였습니다.

Source
#import packages
import pandas as pd
import numpy as np

#to plot within notebook
import matplotlib.pyplot as plt
#주피터 노트북을 사용하지 않기때문에 삭제하였습니다.
#%matplotlib inline

#setting figure size
from matplotlib.pylab import rcParams
# 차트의 기본 크기를 설정합니다. 
rcParams['figure.figsize'] = 8,4 #그림(figure)의 크기. (가로,세로) 인치 단위

#for normalizing data
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler(feature_range=(0, 1))

#read the file
df = pd.read_csv('NSE-TATAGLOBAL11.csv')
#Date,Open,High,Low,Last,Close,Total Trade Quantity,Turnover (Lacs)
#2018-10-08,208.0,222.25,206.85,216.0,215.15,4642146.0,10062.83

#print the head
print(df.head())

#importing required libraries
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import Dense, Dropout, LSTM

#creating dataframe
# index를 sort함 sort 의미는 없음 
data = df.sort_index(ascending=True, axis=0)
print(data.head())

#빈 dataframe을 만듭니다 갯수는 df 갯수만큼
#  Date Close
#0  NaN   NaN
#1  NaN   NaN
#2  NaN   NaN
new_data = pd.DataFrame(index=range(0,len(df)),columns=['Date', 'Close'])
print(new_data.head())

# 앞에서 만든 빈 new_data 에 Date, Close만 넣습니다.
#이런식으로 안해도 될텐데...
for i in range(0,len(data)):
    new_data['Date'][i] = data['Date'][i]
    new_data['Close'][i] = data['Close'][i]
print(new_data.head())

#setting index
#index를 date로 설정합니다.
new_data.index = new_data.Date
#그러면 date는 필요없어지니 삭제합니다
new_data.drop('Date', axis=1, inplace=True)

#creating train and test sets
# numpy로 변환합니다. 1차원인 Close만 남습니다
dataset = new_data.values

train = dataset[0:987,:]
valid = dataset[987:,:]
# 각각 아래 데이터가 생성 되었습니다.
#(987, 1)
#(248, 1)
print(train.shape)
print(valid.shape)

#converting dataset into x_train and y_train
#값을 0 ~ 1 사이의 값으로 변환 합니다
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(dataset)

x_train, y_train = [], []
for i in range(60,len(train)):
    x_train.append(scaled_data[i-60:i,0])
    y_train.append(scaled_data[i,0])

# x_train [0:60]=>0:59까지데이터 y_train [60]
x_train, y_train = np.array(x_train), np.array(y_train)
#(927, 60) (927,)
# x_train 60깨 input으로 가지는 데이터가 927개 들어있다.
print(x_train.shape,y_train.shape)
print(x_train)

# 입력 format에 맞게 변환 합니다.
x_train = np.reshape(x_train, (x_train.shape[0],x_train.shape[1],1))
print("input_shape")
print(x_train.shape,y_train.shape)
print(x_train)

# create and fit the LSTM network
model = Sequential()
# x_train.shape[1] 이것은 timestep이 된다
model.add(LSTM(units=50, return_sequences=True, input_shape=(x_train.shape[1],1)))
model.add(LSTM(units=50))
model.add(Dense(1))

model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(x_train, y_train, epochs=1, batch_size=1, verbose=2)

# 입력 데이터중 마지막 데이터를 가지고 valid 데이터를 만듭니다.
#predicting 246 values, using past 60 from the train data
inputs = new_data[len(new_data) - len(valid) - 60:].values
inputs = inputs.reshape(-1,1)
inputs  = scaler.transform(inputs)

X_test = []
for i in range(60,inputs.shape[0]):
    X_test.append(inputs[i-60:i,0])
X_test = np.array(X_test)

X_test = np.reshape(X_test, (X_test.shape[0],X_test.shape[1],1))

closing_price = model.predict(X_test)
print(closing_price.shape)
closing_price = scaler.inverse_transform(closing_price)

rms=np.sqrt(np.mean(np.power((valid-closing_price),2)))
print(rms)

#for plotting
train = new_data[:987]
valid = new_data[987:]
valid['Predictions'] = closing_price
plt.plot(train['Close'])
plt.plot(valid[['Close','Predictions']])
plt.show()


입력으로 사용하는 NSE-TATAGLOBAL11.csv 파일은 원본 링크의 아래 부분에서 다운로드 하였습니다.
Note: Here is the dataset I used for the code: Download
https://s3-ap-south-1.amazonaws.com/av-blog-media/wp-content/uploads/2019/03/NSE-TATAGLOBAL11.csv

NSE-TATAGLOBAL11.csv 
Date,Open,High,Low,Last,Close,Total Trade Quantity,Turnover (Lacs)
2018-10-08,208.0,222.25,206.85,216.0,215.15,4642146.0,10062.83
2018-10-05,217.0,218.6,205.9,210.25,209.2,3519515.0,7407.06
2018-10-04,223.5,227.8,216.15,217.25,218.2,1728786.0,3815.79
2018-10-03,230.0,237.5,225.75,226.45,227.6,1708590.0,3960.27
2018-10-01,234.55,234.6,221.05,230.3,230.9,1534749.0,3486.05
2018-09-28,234.05,235.95,230.2,233.5,233.75,3069914.0,7162.35
2018-09-27,234.55,236.8,231.1,233.8,233.25,5082859.0,11859.95
2018-09-26,240.0,240.0,232.5,235.0,234.25,2240909.0,5248.6
2018-09-25,233.3,236.75,232.0,236.25,236.1,2349368.0,5503.9
2018-09-24,233.55,239.2,230.75,234.0,233.3,3423509.0,7999.55
2018-09-21,235.0,237.0,227.95,233.75,234.6,5395319.0,12589.59
2018-09-19,235.95,237.2,233.45,234.6,234.9,1362058.0,3202.78
...(생략)...


Output
         Date    Open    High     Low    Last   Close  Total Trade Quantity  Turnover (Lacs)
0  2018-10-08  208.00  222.25  206.85  216.00  215.15             4642146.0         10062.83
1  2018-10-05  217.00  218.60  205.90  210.25  209.20             3519515.0          7407.06
2  2018-10-04  223.50  227.80  216.15  217.25  218.20             1728786.0          3815.79
3  2018-10-03  230.00  237.50  225.75  226.45  227.60             1708590.0          3960.27
4  2018-10-01  234.55  234.60  221.05  230.30  230.90             1534749.0          3486.05
Using TensorFlow backend.
         Date    Open    High     Low    Last   Close  Total Trade Quantity  Turnover (Lacs)
0  2018-10-08  208.00  222.25  206.85  216.00  215.15             4642146.0         10062.83
1  2018-10-05  217.00  218.60  205.90  210.25  209.20             3519515.0          7407.06
2  2018-10-04  223.50  227.80  216.15  217.25  218.20             1728786.0          3815.79
3  2018-10-03  230.00  237.50  225.75  226.45  227.60             1708590.0          3960.27
4  2018-10-01  234.55  234.60  221.05  230.30  230.90             1534749.0          3486.05
  Date Close
0  NaN   NaN
1  NaN   NaN
2  NaN   NaN
3  NaN   NaN
4  NaN   NaN
         Date   Close
0  2018-10-08  215.15
1  2018-10-05   209.2
2  2018-10-04   218.2
3  2018-10-03   227.6
4  2018-10-01   230.9
(987, 1)
(248, 1)
C:\ProgramData\Anaconda3\lib\site-packages\sklearn\utils\validation.py:595: DataConversionWarning: Data with input dtype object was converted to float64 by MinMaxScaler.
  warnings.warn(msg, DataConversionWarning)
(927, 60) (927,)
[[0.50425818 0.47758853 0.51792918 ... 0.72859704 0.7492156  0.77140296]
 [0.47758853 0.51792918 0.56006275 ... 0.7492156  0.77140296 0.77364411]
 [0.51792918 0.56006275 0.57485433 ... 0.77140296 0.77364411 0.73352757]
 ...
 [0.23576871 0.24518153 0.23733752 ... 0.23330345 0.22725235 0.22277006]
 [0.24518153 0.23733752 0.22882116 ... 0.22725235 0.22277006 0.24092335]
 [0.23733752 0.22882116 0.20528911 ... 0.22277006 0.24092335 0.24585388]]
input_shape
(927, 60, 1) (927,)
[[[0.50425818]
  [0.47758853]
  [0.51792918]
  ...
  [0.72859704]
  [0.7492156 ]
  [0.77140296]]

 [[0.47758853]
  [0.51792918]
  [0.56006275]
  ...
  [0.7492156 ]
  [0.77140296]
  [0.77364411]]

 [[0.51792918]
  [0.56006275]
  [0.57485433]
  ...
  [0.77140296]
  [0.77364411]
  [0.73352757]]

 ...

 [[0.23576871]
  [0.24518153]
  [0.23733752]
  ...
  [0.23330345]
  [0.22725235]
  [0.22277006]]

 [[0.24518153]
  [0.23733752]
  [0.22882116]
  ...
  [0.22725235]
  [0.22277006]
  [0.24092335]]

 [[0.23733752]
  [0.22882116]
  [0.20528911]
  ...
  [0.22277006]
  [0.24092335]
  [0.24585388]]]
WARNING:tensorflow:From C:\ProgramData\Anaconda3\lib\site-packages\tensorflow\python\framework\op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
WARNING:tensorflow:From C:\ProgramData\Anaconda3\lib\site-packages\tensorflow\python\ops\math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.cast instead.
Epoch 1/1
2019-05-04 18:53:28.235217: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
 - 40s - loss: 0.0040
(248, 1)
5.49749408305034
lstm_help.py:131: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  valid['Predictions'] = closing_price

소스 코드에 관한 상세한 설명은 주석으로 넣었습니다.
전체적인 코드 흐름은 다음과 같습니다.
1. cvs 파일 읽기
2. date, close 컬럼만 추출 (new_data), close 는 종가를 의미함
3. date를 index로 만들고 date 컬럼 삭제, close컬럼만 남음
4. train, valid 데이터 셋으로 분리
5. MinMaxScaler 를 이용하여 0~1 사이 값으로 변환
6. x_train, y_train 데이터 구성, 이때 x_train은 60개의 time_steps 값을 가짐, 즉 close의 이전 60개 값이 x_train이 되고 현재값이 y_train 의 값 1개가 된다.
7. x_train, y_train 를 numpy로 변환
8. LSTM의 입력 포맷에 맞게 변환, 3개 차원 [전체 데이터 갯수, time_steps, feature수 ] 여기에서는 time_series = 60, feature수는 'close'컬럼 하나이므로 feature수는 1이 된다.
9. LSTM 모델링
10. train
11. 마지막 데이터를 이용하여 valid 데이터를 만듭니다. 코드상은 아래 부분입니다.
inputs = new_data[len(new_data) - len(valid) - 60:].values
new_data는 전체 데이터이므로 전체 데이터-valid데이터 크기-60(time_steps 값)위치 부터 마지막까지의 데이터입니다. 60을 빼주는 이유는 valid데이터 위치를 예측 하기 위해서는 이전 60개의 데이터가 필요하기 때문입니다.
12. 위에서 만든 inputs를 이용해서 X_test 데이터를 만듭니다. predict하기 때문에 Y_test는 필요 없습니다.

train의 입력 출력이 가장 어려운 부분이라 그림으로 표시해 보았습니다.



2019년 4월 28일 일요일

Keras LSTM 입력 포맷의 이해 Understanding Input shapes in LSTM | Keras



LSTM을 사용해보려고 하는데 입력과 출력에 대해서 자세히 나온 부분이 없어서 정리해 보았습니다.
기본적인 내용은 아래 참조 링크를 사용하였습니다.

참조 링크 : https://medium.com/@shivajbd/understanding-input-and-output-shape-in-lstm-keras-c501ee95c65e

참조 링크 : https://towardsdatascience.com/predicting-stock-price-with-lstm-13af86a74944

위 링크 저자도 처음에 혼란스러웠다고 되어있네요.

LSTM을 하기 위해서는 3차원적 데이터가 필요합니다. (대부분의 예제의 경우 batch(일괄처리)값 설정없이 input_shape을 사용합니다. 만약 batch를 설정하려면 batch_input_shape 값에 batch값을 추가하여 차원을 추가한 입력을 사용해야합니다.)

[data_size, time_steps, features] 3차원 array로 구성됩니다.

Keras input_shape 예제
model = keras.models.Sequential()
model.add(keras.layers.LSTM(unit=3, input_shape=(3, 5)))

위 예에서 (2,10)은 time_steps=3, features=5 가 됩니다. data_size는 따로 넘기지 않습니다. data_size 정보는 없어도 넘어오는 크기로 부터 전체 data size를 알 수 있기 때문입니다.

이것을 그림으로 그려보면 다음과 같습니다.


time_steps는 Network에 사용할 시간 단위입니다. 이 값은 LSTM에서 과거의 몇개 데이터를 볼것인가 가지고 결정합니다. 위 그림에서 녹색 box부분을 보면 3개 데이터를 이용해서 다음 한개를 추론 하기 때문에 time_steps:3 이 됩니다.
data_size는 데이터의 크기 입니다. 위 그림에서는 N으로 표시 하였습니다. 커다란 박스의 개수가 3개 있고, 추가로 2개 더 그릴수 있으므로 데이터는 5개가 될 것입니다.
Features 는 일반적인 경우 X의 차원을 의미합니다. LSTM에서는 input이 time_steps에 의해서 여러개의 입력 나타내지는데 그것의 속성의 수가 됩니다. 여기에서는 Open, High, Low, Close, Volume 이렇게 5개가 들어갑니다.

Batch에 대하여

Batch는 일괄 처리되는 작업의 양이다. 위에서 설명된 data_size를 한번에 처리하는 갯수를 의미하며, batch크기에 의해 weight 변화가 일어나며 batch크기 단위로 data loading 함수도 구현이 가능하다. 주의할점은 data_size/batch_size일때 나머지가 없어야 한다.

batch 되는 양이 있다면 아래와 같이 설정할 수도 있습니다.

Keras batch_input_shape 예제 
model = keras.models.Sequential()
model.add(keras.layers.LSTM(unit=4, batch_input_shape=(3, 4, 6)))
batch_size, time_steps, features 각각은 3,4,6이 됩니다.

실제 데이터의 크기는 [6,4,6]으로 준비가 된 모습입니다. batch_size로 차원을 구성하는것이 아닙니다. batch_size = 3으로 6/3이 되므로 나머지가 없기 때문에 문제가 없습니다. 나머지가 생기게 되면 batch 작업시 문제가 생깁니다.

unit=4 은 해당 layer의 출력 크기가 된다.


2019년 2월 11일 월요일

deep learning pytorch KFold 예제 (example)



1. 들어가기에 앞서


이번 예제는 pytorch를 이용하여 XOR 데이터를 넣어서 학습한뒤 결과를 보는 예제입니다. XOR함수는 X1, X2 두개의 입력을 가지는데 두개의 값이 같은 경우에 Y값이 1 이 되는 함수 입니다. 이 부분은 lightGBM, XGBoost 예제에서도 동일하기 때문에 해당 내용을 그대로 복사하였습니다.

pytorch를 이용한 Deep learning 예제 (이 문서)
https://swlock.blogspot.com/2019/02/deep-learning-pytorch-kfold-example.html

XGBoost 예제
https://swlock.blogspot.com/2019/02/xgboost-stratifiedkfold-kfold.html

lightGBM 설치 및 예제 (How to install Lightgbm and example)
https://swlock.blogspot.com/2019/01/lightgbm-how-to-install-lightgbm-and.html


교차검증에 대해잘 모르겠으면 아래 링크에서 추가 확인이 가능합니다.
scikit-learn 이용한 (cross-validation) 교차 검증 iterators StratifiedKFold, KFold 사용법
https://swlock.blogspot.com/2019/01/scikit-learn-cross-validation-iterators.html


2. XOR data로 pytorch에서 사용하기
XOR함수는 입력을 2개를 받고 두개의 값이 같으면 1이되고 다르면 0이 되는 함수 입니다.
0,1 정수만 넣어서 예제를 만들면 test와 train 데이터가 겹치는 부분도 있고 해서 여기에서는 random 을 이용해서 실수 형태로 만들었습니다. 또한 Y가 한개인것보다 복잡한 sample을 만들기 위해 y1, y2는 서로 반대가 되는 값을 넣었습니다.


2.1 XOR data 만들기

파일명 : makexordata.py
import numpy as np
import pandas as pd

np.random.seed(0)

def get_int_rand(min, max):
 value = np.random.randint(min, high=max+1, size=1)
 return int(value[0])

def get_rand(min, max):
 value = np.random.rand(1)*(max-min)+min
 return float(value[0])

#make train set
df = pd.DataFrame([],columns=['x1', 'x2', 'y1', 'y2'])

for i in range(1000):
 x1 = get_int_rand(0,1)
 x2 = get_int_rand(0,1)
 if x1 == x2 :
  y1 = 0
  y2 = 1
 else  :
  y1 = 1
  y2 = 0
 x1 = get_rand(x1-0.3, x1+0.3)
 x2 = get_rand(x2-0.3, x2+0.3)

 df = df.append(pd.DataFrame(np.array([x1,x2,int(y1),int(y2)]).reshape(1,4),columns=['x1', 'x2', 'y1', 'y2']))
 
df.reset_index(inplace=True,drop=True)
print(df.head())

df.to_csv("train.csv", encoding='utf-8', index=False)


# make test set
df = pd.DataFrame([],columns=['x1', 'x2'])

for i in range(100):
 x1 = get_int_rand(0,1)
 x2 = get_int_rand(0,1)
 x1 = get_rand(x1-0.3, x1+0.3)
 x2 = get_rand(x2-0.3, x2+0.3)

 df = df.append(pd.DataFrame(np.array([x1,x2,int(0),int(0)]).reshape(1,4),columns=['x1', 'x2', 'y1', 'y2']))
 
df.reset_index(inplace=True,drop=True)
print(df.head())

df.to_csv("test.csv", encoding='utf-8', index=False)


실행결과는 아래와 같습니다. 위 코드는 데이터를 만들기 위한 용도이므로 자세한 설명은 생략합니다. 결과는 아래와 같고 train.csv, test.csv 파일을 생성해 냅니다. 형태는 아래 결과 와 같습니다. test.csv파일의 y1, y2는 0으로 가득차 있으며, 해당값을 예측해야 합니다.



(base) E:\>python makexordata.py
         x1        x2   y1   y2
0  0.129114  1.061658  1.0  0.0
1  0.954193  1.087536  0.0  1.0
2  0.235064  1.278198  1.0  0.0
3  0.175035  1.017337  1.0  0.0
4  0.255358  0.742622  1.0  0.0
e:\ProgramData\Anaconda3\lib\site-packages\pandas\core\frame.py:6211: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.

To accept the future behavior, pass 'sort=False'.

To retain the current behavior and silence the warning, pass 'sort=True'.

  sort=sort)
         x1        x2   y1   y2
0  1.077771  0.167151  0.0  0.0
1  0.189848 -0.200354  0.0  0.0
2 -0.264823  0.820102  0.0  0.0
3 -0.231184  0.062009  0.0  0.0
4  0.957680  0.889091  0.0  0.0

데이터를 보는 방법은 다음과 같습니다.
x1값이 0.5 보다 작으면 0으로, 0.5 보다 크면 1로 생각하고 두개 값이 (대략)0이면 y1=0, y2=1, 0,1또는 1,0으로 다를 경우 y1=1, y2=0 이 됩니다.(XOR은 입력값이 배타적일때 참이 됩니다.) 결과 값 또한 0~1 사이의 binary 형태의 데이터입니다.
만들려고 하는 예제는 위 데이터(train.csv)를 가지고 학습한뒤 test.csv 에 주어지는 x1, x2를 이용하여 y1, y2를 예측하는 예제입니다.


2.2 pytorch 예제 코드 작성

이제 본론입니다. 해당 코드는 KFold 교차 검증 이용한 반복 훈련,  Feature Select, MinMaxScaler, StandardScaler 내용을 포함하는 예제입니다.
전반적인 흐름은 train.csv 파일을 읽어서 MinMaxScaler, StandardScaler 를 할 수도 있으며, 훈련 후 그 결과를 result.csv 파일에 저장하는 코드입니다.

EPOCHS_TO_TRAIN = 300
FOLD_COUNT = 2
RUNNING_RATE = 0.1
DROPOUT = 0.2
LAYER1 = 50
LAYER2 = 30
EPOCHS_FOR_DISPLAY = 1

STD_ALL = False
NOR_ALL = True
PREFIX = __file__[:-3]
BASE_PATH = "./"

SELECT_NEED = False
Y1_SELECT_LEVEL = 0
Y2_SELECT_LEVEL = 0

Y1_FEATURE_LIST_FOR_SELECT=[
 ('x1',1),
 ('x2',0)
]

Y2_FEATURE_LIST_FOR_SELECT=[
 ('x1',1),
 ('x2',0)
]

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import time
import pandas as pd
import numpy as np
from sklearn.model_selection import KFold
from sklearn import preprocessing
from sklearn.metrics import mean_squared_error
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable

np.random.seed(1000)

class writeLog():
 def write(self, fileName, text):
  print(text)
  f=open(fileName,'a')
  f.write(text)
  f.write("\n")
  f.close()
 def writeWithoutCR(self, fileName, text):
  f=open(fileName,'a')
  f.write(text)
  f.close()

log = writeLog()
log.write(PREFIX+"log.txt","start")

train_df = pd.read_csv(BASE_PATH+'train.csv', header=0, encoding='utf8')
test_df = pd.read_csv(BASE_PATH+'test.csv', header=0, encoding='utf8')

#drop Y
Y1train_df = train_df.pop('y1')
Y2train_df = train_df.pop('y2')

Y1test_df = test_df.pop('y1')
Y2test_df = test_df.pop('y2')

allX = pd.concat([train_df, test_df], axis=0)
train_size = train_df.shape[0]
test_size = test_df.shape[0]
log.write(PREFIX+"log.txt","train size:"+str(train_df.shape))
log.write(PREFIX+"log.txt","test size:"+str(test_df.shape))
log.write(PREFIX+"log.txt","all size:"+str(allX.shape))
del (train_df, test_df)

allX = pd.get_dummies(allX)
allX = allX.fillna(value=0)

if NOR_ALL == True:
 names = allX.columns
 scaler = preprocessing.MinMaxScaler()
 allX = scaler.fit_transform(allX)
 allX = pd.DataFrame(allX, columns=names)

if STD_ALL == True:
 names = allX.columns
 scaler = preprocessing.StandardScaler()
 allX = scaler.fit_transform(allX)
 allX = pd.DataFrame(allX, columns=names)

if SELECT_NEED :
 Y1sel_feature = []
 for feature, count in Y1_FEATURE_LIST_FOR_SELECT :
  if count>=Y1_SELECT_LEVEL :
   Y1sel_feature.append(feature)
 log.write(PREFIX+"log.txt","Y1 selected:%d"%(len(Y1sel_feature)))
 
 Y2sel_feature = []
 for feature, count in Y2_FEATURE_LIST_FOR_SELECT :
  if count>=Y2_SELECT_LEVEL :
   Y2sel_feature.append(feature)
 log.write(PREFIX+"log.txt","Y2 selected:%d"%(len(Y2sel_feature)))
 
 Ysel_feature = []
 Ysel_feature = Y1sel_feature + Y2sel_feature
 Ysel_feature = list(set(Ysel_feature))
 log.write(PREFIX+"log.txt","Y selected:%d"%(len(Ysel_feature)))
else:
 Ysel_feature = []
 Y1sel_feature = []
 Y2sel_feature = []
 if len(Ysel_feature)==0:
  Ysel_feature = allX.columns
 if len(Y1sel_feature)==0:
  Y1sel_feature = allX.columns
 if len(Y2sel_feature)==0:
  Y2sel_feature = allX.columns

#분리
trainX = allX[0:int(train_size)]
predictX = allX[int(train_size):int(allX.shape[0])]
log.write(PREFIX+"log.txt","train size:"+str(trainX.shape))
log.write(PREFIX+"log.txt","test size:"+str(predictX.shape))
del (allX)

def OutputData(filename,Y1,Y2):
 test_csv = pd.read_csv(BASE_PATH+'test.csv', header=0, encoding='utf8')
 test_csv.pop('y1')
 test_csv.pop('y2')
 predAll = pd.concat([test_csv,pd.DataFrame(Y1, columns=['y1'])], axis=1)
 predAll = pd.concat([predAll,pd.DataFrame(Y2, columns=['y2'])], axis=1)
 predAll.to_csv(path_or_buf=PREFIX+filename, index=False)
 del predAll

def initialize_weights(model):
 if type(model) in [nn.Linear]:
  nn.init.xavier_normal(model.weight.data)
 elif type(model) in [nn.LSTM, nn.RNN, nn.GRU]:
  nn.init.xavier_normal(model.weight_hh_10)
  nn.init.xavier_normal(model.weight_ih_10)

class Net(nn.Module):
 def __init__(self):
  super(Net, self).__init__()
  self.fc1 = nn.Linear(len(Ysel_feature), LAYER1, True)
  self.fc2 = nn.Linear(LAYER1, LAYER2, True)
  self.fc3 = nn.Linear(LAYER2, 2, True)
  self.drop = nn.Dropout(p=DROPOUT)
  self.LeakyReLU = nn.LeakyReLU(negative_slope=0.01)

 def forward(self, x):
  x = self.fc1(x)
  x = self.LeakyReLU(x)
  x = self.drop(x)
  x = self.fc2(x)
  x = self.LeakyReLU(x)
  x = torch.sigmoid(self.fc3(x))
  return x
 
 def name(self):
  return "Net"

def Train(trainX, trainY, predictX, Ystr, ysize, EPOCHS, sel_feature, fold_count, early_stopping_rounds=3):
 
 kfold = KFold(n_splits=fold_count, random_state=0, shuffle=True)
 
 features = trainX.columns.tolist()
 if len(sel_feature)==0:
  sel_feature = features
 
 cv_pred = np.zeros(ysize)
 cv_rmse1 = 0
 cv_rmse2 = 0

 trainXnp = trainX.as_matrix(columns=sel_feature)
 trainYnp = trainY.as_matrix()
 
 i = 0
 for train_index, validate_index in kfold.split(trainXnp):
  i = i + 1
  log.write(PREFIX+"log.txt","[%d]Fold"%(i))
  X_train, X_validate = trainXnp[train_index], trainXnp[validate_index]
  y_train, y_validate = trainYnp[train_index], trainYnp[validate_index]
  
  train_inputs = (torch.from_numpy(X_train))
  train_inputs = train_inputs.float()
  train_targets = (torch.from_numpy(y_train))
  train_targets = train_targets.float()
  val_inputs = (torch.from_numpy(X_validate))
  val_inputs = val_inputs.float()
  val_targets = (torch.from_numpy(y_validate))
  val_targets = val_targets.float()
  
  model = Net()
  criterion = nn.BCELoss()
  optimizer = optim.SGD(model.parameters(), lr = RUNNING_RATE)
  
  min_loss = 100000000000.0
  min_epoch = -1
  min_count = 0
  break_true = False
  train_loss = 0.0
  for epoch in range(0,EPOCHS):
   for phase in ['train','valid']:
    if epoch % EPOCHS_FOR_DISPLAY == 0 :
     printlog = True
    else:
     printlog = False
    if phase == 'train' :
     running_loss = 0.0
     model.train(True)
     for input, target in zip(train_inputs, train_targets):
      optimizer.zero_grad()
      input = Variable(input)
      taregt = Variable(target)
      output = model(input)
      loss = criterion(output, target)
      loss.backward()
      optimizer.step()
      running_loss += loss.data.numpy()
      train_loss = running_loss
    else:
     running_loss = 0.0
     model.train(False)
     y_pred = []
     for input, target in zip(val_inputs, val_targets):
      input = Variable(input)
      target = Variable(target)
      output = model(input)
      loss = criterion(output, target)
      y_pred.append(output.detach().numpy())
      running_loss += loss.data.numpy()
     y_pred = np.array(y_pred)
     rmse1 = mean_squared_error(y_validate[:,0], y_pred[:,0])
     rmse2 = mean_squared_error(y_validate[:,1], y_pred[:,1])
     if printlog:
      log.write(PREFIX+"log.txt","vald:%d loss:%f rmse:%f %f=%f tloss:%f"%
      (epoch, running_loss, rmse1, rmse2, (rmse1+rmse2)/2, train_loss))
     if min_loss > running_loss :
      min_loss = running_loss
      min_epoch = epoch
      min_rmse1 = rmse1
      min_rmse2 = rmse2
      log.write(PREFIX+"log.txt","min epoch:%d"%(epoch))
      torch.save(model.state_dict(), model.name())
      min_count = 0
     else:
      min_count += 1
      if early_stopping_rounds < min_count :
       log.write(PREFIX+"log.txt","early_stopping_rounds:%d %d"%(epoch,early_stopping_rounds))
       break_true = True
    if break_true :
     break
   if break_true:
    break
  
  model.load_state_dict(torch.load(model.name()), strict = False)
  model.train(False)
  
  test_inputs = torch.from_numpy(predictX.as_matrix(columns=sel_feature))
  test_inputs = test_inputs.float()
  test_out = []
  for input in test_inputs:
   input = Variable(input)
   output = model(input)
   test_out.append(output.detach().numpy())
  cv_rmse1 += min_rmse1
  cv_rmse2 += min_rmse2
  cv_pred += np.array(test_out)
 cv_rmse1 /= fold_count
 cv_rmse2 /= fold_count
 cv_pred /= fold_count


 return (cv_pred, cv_rmse1, cv_rmse2)

trainY = pd.concat([Y1train_df, Y2train_df], axis=1)
predictY = pd.concat([Y1test_df, Y2test_df], axis=1)

y_pred, rmse1, rmse2 = Train(trainX, trainY, predictX, "All", predictY.shape, EPOCHS_TO_TRAIN, Y1sel_feature, FOLD_COUNT)

log.write(PREFIX+"log.txt","Total rmse %e + %e = %e"%(rmse1,rmse2,(rmse1+rmse2)/2))

OutputData("Result.csv",y_pred[:,0],y_pred[:,1])

2.3 코드 설명

코드는 lightGBM,.xgboost과 50%정도는 중복되기 때문에 일부 설명을 복사하였습니다.
아래 값은 EPOCH 을 반복하는 값입니다. EPOCH 은 전체 데이터를 한번 훈련하는 것을 1 epoch라고 합니다. 여기에서는 300번 훈련한다는 의미 입니다.
EPOCHS_TO_TRAIN = 300

교차 검증시 Fold 할 값입니다.
FOLD_COUNT = 2

그리고 running rate도 적절한 값을 넣어 줍니다.
RUNNING_RATE = 0.1

Dropout은 특정한 node를 사용안하게 하는 비율입니다. 보통 overfitting 방지 목적으로 사용하는데 숫자가 작을수록 사용안하는 노드가 줄어듭니다.
DROPOUT = 0.2

이번 예제는 liner layer 사용 하였습니다. 그때의 입출력 갯수의 값입니다. 입력과 출력 설명은 이 문서에서는 생략하겠습니다.
LAYER1 = 50
LAYER2 = 30

화면에 출력을 얼마나 할것인가를 조절하는 값입니다. 여기에서는 1 epoch 마다 출력하게 됩니다.
EPOCHS_FOR_DISPLAY = 1

평균과 표준 편차를 이용하여 -1~1 사이의 값으로 변환 합니다.
https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html
STD_ALL = False

최소값(Min)과 최대값(Max)을 사용해서 '0~1' 사이의 범위로 데이터를 변환합니다. (딥러닝에서는 많이 사용합니다. 그래서 True로 설정 하였습니다.)
수식적인 방법은 아래와 같습니다.

X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0)) X_scaled = X_std * (max - min) + min
https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html
NOR_ALL = True

x1,x2 중 특정 x값을 선택하기 위해 만들어진 기능입니다. 여기에서는 x가 두개밖에 없어서 필요는 없습니다.
SELECT_NEED = False
랜덤 함수의 값을 고정합니다. 이것은 수행시킬때마다 값을 랜덤값을 만들기 위해 사용합니다.
np.random.seed(1000)

로그를 출력하기 위한 클래스 입니다.
class writeLog():
def write(self, fileName, text):
print(text)
f=open(fileName,'a')
f.write(text)
f.write("\n")
f.close()
def writeWithoutCR(self, fileName, text):
f=open(fileName,'a')
f.write(text)
f.close()

데이터인 train과 test용 csv 파일을 읽습니다.
train_df = pd.read_csv(BASE_PATH+'train.csv', header=0, encoding='utf8')test_df = pd.read_csv(BASE_PATH+'test.csv', header=0, encoding='utf8')

읽은 data파일은 y값이 포함되어 있으므로 제거합니다.
#drop YY1train_df = train_df.pop('y1')Y2train_df = train_df.pop('y2')Y1test_df = test_df.pop('y1')Y2test_df = test_df.pop('y2')

두개의 dataframe을 아래쪽으로 붙여서 allX를 만듭니다. 이렇게 하는 이유는 데이터 변환을 하기 위해서 그렇습니다.
allX = pd.concat([train_df, test_df], axis=0)

다음으로 앞에서 설명한 옵션에 따라서 Scaler 동작을 합니다. 일부 Machine learning에서는 효과가 좋습니다.
if NOR_ALL == True:
names = allX.columns
scaler = preprocessing.MinMaxScaler()
allX = scaler.fit_transform(allX)
allX = pd.DataFrame(allX, columns=names)

if STD_ALL == True:
names = allX.columns
scaler = preprocessing.StandardScaler()
allX = scaler.fit_transform(allX)
allX = pd.DataFrame(allX, columns=names)

그 후 X feature중에 필요한것만 선택하도록 합니다. 이름이 Y1sel_feature라고 되어있지만, 실은 해당 y값을 train할때 입력이 되는 x를 선택하기 위한 이름입니다. 이 부분은 사용자가 feature를 넣고 빼고 할때 사용하기 위함입니다. Deep learning에서 Y는 여러개 한꺼번에 입력하기 때문에 Y1sel_feature,Y2sel_feature 은 사용하지 않습니다. 의미가 없지만 예제와 소스를 같이 사용하기 위해서 그대로 두었습니다. 대신 Ysel_feature 를 사용합니다.
if SELECT_NEED :
Y1sel_feature = []
for feature, count in Y1_FEATURE_LIST_FOR_SELECT :
if count>=Y1_SELECT_LEVEL :
Y1sel_feature.append(feature)
log.write(PREFIX+"log.txt","Y1 selected:%d"%(len(Y1sel_feature)))

Y2sel_feature = []
for feature, count in Y2_FEATURE_LIST_FOR_SELECT :
if count>=Y2_SELECT_LEVEL :
Y2sel_feature.append(feature)
log.write(PREFIX+"log.txt","Y2 selected:%d"%(len(Y2sel_feature)))

Ysel_feature = []
Ysel_feature = Y1sel_feature + Y2sel_feature
Ysel_feature = list(set(Ysel_feature))
log.write(PREFIX+"log.txt","Y selected:%d"%(len(Ysel_feature)))
else:
Ysel_feature = []
Y1sel_feature = []
Y2sel_feature = []
if len(Ysel_feature)==0:
Ysel_feature = allX.columns
if len(Y1sel_feature)==0:
Y1sel_feature = allX.columns
if len(Y2sel_feature)==0:
Y2sel_feature = allX.columns

지금까지 X feature를 allX에서 다시 분리합니다.
#분리
trainX = allX[0:int(train_size)]predictX = allX[int(train_size):int(allX.shape[0])]

최종 출력을 위한 함수 입니다.
def OutputData(filename,Y1,Y2):
test_csv = pd.read_csv(BASE_PATH+'test.csv', header=0, encoding='utf8')
test_csv.pop('y1')
test_csv.pop('y2')
predAll = pd.concat([test_csv,pd.DataFrame(Y1, columns=['y1'])], axis=1)
predAll = pd.concat([predAll,pd.DataFrame(Y2, columns=['y2'])], axis=1)
predAll.to_csv(path_or_buf=PREFIX+filename, index=False)
del predAll

network class입니다. hidden layer가 2개 넣었고 dropout과 LeakyReLU 함수를이용하였으며, 마지막 출력단은 0~1 사이의 값이 필요하므로 sigmoid를 사용하였습니다.
class Net(nn.Module):
 def __init__(self):
  super(Net, self).__init__()
  self.fc1 = nn.Linear(len(Ysel_feature), LAYER1, True)
  self.fc2 = nn.Linear(LAYER1, LAYER2, True)
  self.fc3 = nn.Linear(LAYER2, 2, True)
  self.drop = nn.Dropout(p=DROPOUT)
  self.LeakyReLU = nn.LeakyReLU(negative_slope=0.01)

 def forward(self, x):
  x = self.fc1(x)
  x = self.LeakyReLU(x)
  x = self.drop(x)
  x = self.fc2(x)
  x = self.LeakyReLU(x)
  x = torch.sigmoid(self.fc3(x))
  return x
 
 def name(self):
  return "Net"
이제 가장 중요한 train 함수입니다. trainX는 X의 dataframe이고 trainY는 y의 dataframe입니다. predictX 는 나중에 예측하기위한 X값입니다. Ystr는 출력을 위한 문자열입니다. 즉 Y값이 여러개일때 출력시 어떤 Y값인지 구분하기 위해서 인자를 하나 더 넣었습니다. 하지만 deep learning 에서 Y는 구분하지 않기때문에 의미는 없습니다. EPOCHS 은 앞에서 설명하였습니다. sel_feature 는 앞에서 Ysel_feature로 선택된 컬럼 값입니다. fold_count는 교차 검증 Fold인자가 됩니다. early_stopping_rounds이 값은 평가값이 일정 기간동안 최저값이 되지 안되면 중단하도록 합니다.
def Train(trainX, trainY, predictX, Ystr, ysize, EPOCHS, sel_feature, fold_count, early_stopping_rounds=5):


실제 호출은 다음과 같습니다.
y_pred, rmse1, rmse2 = Train(trainX, trainY, predictX, "All", predictY.shape, EPOCHS_TO_TRAIN, Y1sel_feature, FOLD_COUNT)

train 함수 안에서는 KFold 수 만큼 루프를 돌게됩니다. 교차 검증 완료시 cv_rmse1, cv_rmse2, cv_pred 값이 발생하는데 이것을 최종 fold_count로 나누어 줍니다. 출력값은 cv_pred num array에 누적됩니다.
  cv_rmse1 += min_rmse1
  cv_rmse2 += min_rmse2
  cv_pred += np.array(test_out)
 cv_rmse1 /= fold_count
 cv_rmse2 /= fold_count
 cv_pred /= fold_count

pytorch 에서는 입력을 pytorch 변수로 변환해야 하는데 from_numpy함수를 이용하고 torch변수는 모두 float 형태로 변환 하였습니다.(간혹 문제가 발생하는 경우가 있어서 전체 변경하였습니다.)
  train_inputs = (torch.from_numpy(X_train))

  train_inputs = train_inputs.float()

  train_targets = (torch.from_numpy(y_train))

  train_targets = train_targets.float()

  val_inputs = (torch.from_numpy(X_validate))

  val_inputs = val_inputs.float()

  val_targets = (torch.from_numpy(y_validate))

  val_targets = val_targets.float()

pytorch의 기본적인 train은 아래와 같은 형태를 취합니다. model에 입력과 타겟값을 넣고 loss를 구하고 loss.backward()하여 optimizer.step()을 행하는 형태입니다.
optimizer.zero_grad()input = Variable(input)taregt = Variable(target)output = model(input)loss = criterion(output, target)loss.backward()optimizer.step()
validation에서는 model.train(False) 가 되며, 출력값만 있으면 되므로 간단하게 아래와 같이 됩니다.
input = Variable(input)target = Variable(target)output = model(input)

필요에 따라서 loss와 결과가 필요하기 때문에 추가 코드를 넣었습니다.
loss = criterion(output, target)y_pred.append(output.detach().numpy())running_loss += loss.data.numpy()

train중 학습을 해도 loss가 감소되지 않으면 중지하도록 코드 하였습니다. 중지하기전에 최소 loss가 될때를 저장해 두었다가 나중에 예측하는 param으로 사용합니다. xgboost의 early_stopping_rounds기능과 유사하게 구현해 보았습니다.

if min_loss > running_loss :
 min_loss = running_loss
 min_epoch = epoch
 min_rmse1 = rmse1
 min_rmse2 = rmse2
 log.write(PREFIX+"log.txt","min epoch:%d"%(epoch))
 torch.save(model.state_dict(), model.name())
 min_count = 0
else:
 min_count += 1
 if early_stopping_rounds < min_count :
  log.write(PREFIX+"log.txt","early_stopping_rounds:%d %d"%(epoch,early_stopping_rounds))
  break_true = True

train을 통해 진짜로 예측된값을 리턴받아야 하기때문에 이전에 최소로 학습된 데이터를 로딩합니다.
  model.load_state_dict(torch.load(model.name()), strict = False)

  model.train(False)
로딩된 네트워크 값을 이용해서 예측을 합니다.
  test_inputs = torch.from_numpy(predictX.as_matrix(columns=sel_feature))
  test_inputs = test_inputs.float()
  test_out = []
  for input in test_inputs:
   input = Variable(input)
   output = model(input)
   test_out.append(output.detach().numpy())
  cv_rmse1 += min_rmse1
  cv_rmse2 += min_rmse2
  cv_pred += np.array(test_out)

아래 값은 validation데이터의 rmse값을 리턴하기 위해 구현한 내용입니다.
  cv_rmse1 += min_rmse1

  cv_rmse2 += min_rmse2


2.4 실행 결과


(base) E:\work\ai\pytorch\xor>python NN_xor_cv.py
start
train size:(1000, 2)
test size:(100, 2)
all size:(1100, 2)
train size:(1000, 2)
test size:(100, 2)
NN_xor_cv.py:175: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.
  trainXnp = trainX.as_matrix(columns=sel_feature)
NN_xor_cv.py:176: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.
  trainYnp = trainY.as_matrix()
[1]Fold
vald:0 loss:381.167028 rmse:0.295309 0.287722=0.291515 tloss:327.426832
min epoch:0
vald:1 loss:25.544507 rmse:0.007797 0.007785=0.007791 tloss:109.076330
min epoch:1
vald:2 loss:14.051988 rmse:0.004656 0.004768=0.004712 tloss:42.358824
min epoch:2
vald:3 loss:398.590590 rmse:0.199223 0.197968=0.198595 tloss:38.308674
vald:4 loss:3.552140 rmse:0.000418 0.000405=0.000412 tloss:22.012489
min epoch:4
vald:5 loss:0.980270 rmse:0.000042 0.000040=0.000041 tloss:5.295693
min epoch:5
vald:6 loss:2.086842 rmse:0.000115 0.000118=0.000117 tloss:19.377938
vald:7 loss:331.050024 rmse:0.173799 0.173541=0.173670 tloss:26.756935
vald:8 loss:2.741837 rmse:0.000602 0.000600=0.000601 tloss:9.742526
vald:9 loss:2.365121 rmse:0.000179 0.000180=0.000179 tloss:19.654218
early_stopping_rounds:9 3
NN_xor_cv.py:260: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.
  test_inputs = torch.from_numpy(predictX.as_matrix(columns=sel_feature))
[2]Fold
vald:0 loss:288.255821 rmse:0.192850 0.193520=0.193185 tloss:337.972763
min epoch:0
vald:1 loss:52.830412 rmse:0.020778 0.020908=0.020843 tloss:168.357505
min epoch:1
vald:2 loss:15.876434 rmse:0.005517 0.005562=0.005539 tloss:54.561382
min epoch:2
vald:3 loss:4.527604 rmse:0.001074 0.001117=0.001095 tloss:29.563572
min epoch:3
vald:4 loss:11.396077 rmse:0.005104 0.005100=0.005102 tloss:15.989310
vald:5 loss:1.294892 rmse:0.000076 0.000079=0.000078 tloss:10.330235
min epoch:5
vald:6 loss:0.893226 rmse:0.000037 0.000036=0.000037 tloss:28.060664
min epoch:6
vald:7 loss:3.602118 rmse:0.001702 0.001662=0.001682 tloss:33.778341
vald:8 loss:0.198577 rmse:0.000004 0.000004=0.000004 tloss:3.830715
min epoch:8
vald:9 loss:0.294545 rmse:0.000016 0.000017=0.000017 tloss:3.244088
vald:10 loss:10.637149 rmse:0.006106 0.006137=0.006122 tloss:15.555545
vald:11 loss:1.834899 rmse:0.000758 0.000777=0.000768 tloss:11.499690
vald:12 loss:0.237293 rmse:0.000003 0.000003=0.000003 tloss:2.695498
early_stopping_rounds:12 3
NN_xor_cv.py:260: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.
  test_inputs = torch.from_numpy(predictX.as_matrix(columns=sel_feature))
Total rmse 2.263878e-05 + 2.175447e-05 = 2.219662e-05

최종 RMSE는 rmse 2.263878e-05 + 2.175447e-05 = 2.219662e-05 가 평균값이라 더해서 2로 나누어 줍니다. 앞쪽은 Y1 rmse값이 되고 뒤쪽 값은 Y2 rmse 값이 됩니다.

2.5 결과 데이터 확인


x1,x2,y1,y2
1.077770981623956,0.16715055841696874,0.999789297580719,0.0002013216399063822
0.1898476282284382,-0.2003537468439492,7.648291375517147e-05,0.9999271035194397
-0.2648225646697367,0.8201024089303411,0.9999933242797852,6.2870740293874405e-06
-0.23118448473185735,0.06200855703372005,4.3045685060860706e-06,0.9999959468841553
......

1번째 라인
[x1=1.077770981623956(대략)1]XOR[x2=0.16715055841696874(대략)0]= 1

결과 y1=0.999789297580719 대략(1) 결과는 정상적으로 보입니다.