Project

[Dacon] 태양광 발전량 예측 AI 경진대회 - CNN (Pinball loss : 2.01536 / Private rank : 7위 / 상위 2%)

donghoon shin 2021. 6. 26. 12:55

대회 설명

7일(Day 0~ Day6) 동안의 데이터를 인풋으로 활용하여, 향후 2일(Day7 ~ Day8) 동안의 30분 간격의 발전량(TARGET)을 예측 (1일당 48개씩 총 96개 타임스텝에 대한 예측)

 

데이터 구성

Hour - 시간
Minute - 분
DHI - 수평면 산란일사량(Diffuse Horizontal Irradiance (W/m2))
DNI - 직달일사량(Direct Normal Irradiance (W/m2))
WS - 풍속(Wind Speed (m/s))
RH - 상대습도(Relative Humidity (%))
T - 기온(Temperature (Degree C))
Target - 태양광 발전량 (kW)

 

모델링 과정

본 대회에서 Light GBM을 활용한 예측과 CNN을 활용한 예측을 진행하였다. 본 대회의 평가지표인 pinball_loss(quantile)를 최소화하는 모델 구축을 위해 두 가지 방법 모두 quantile_loss를 loss_function으로 설정하였다.
또한, 예측해야하는 2일 중 각각의 날짜를 Target값으로 설정하여 결론적으로는, 일자별(2) * 0.1~0.9quantile별(9)별로 총 18번의 학습을 통해 예측값을 구하였다.

 

CNN 

 

시간대별로 명확한 Target 값 차이가 존재하기 때문에, 동일 시간대의 값을 위주로 학습하는 것이 중요하다고 생각되었다. 이를 위해 Dilated rate를 하루 간격인 48로 설정하여(24h/30m), 여러층의 Conv Layer를 거쳐 임베딩 값의 time_step이 7일치에서 최종적으로는 1일치로 줄어들도록 모델을 구성하였다.
또한, 특정 시간대의 바로 앞, 뒤 시점의 값을 고려하여 더욱 복잡한 시계열 특성을 학습할 수 있도록, 가장 첫번째 Conv Layer window_size 3의 Conv Layer로 구성하였다.  (하단 그림 참조)

 

 

 

 

github : 

https://github.com/hoonsnote/Dacon/blob/8b574e16b9deacb30cdf95569a46d5b8efea2307/Solar%20power%20generation%20forecast/2.CNN.ipynb

 

 


 

  • Library import 및 데이터 불러오기
import pandas as pd
import numpy as np
from tqdm.notebook import tqdm
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler, OneHotEncoder, RobustScaler, MinMaxScaler
import seaborn as sns
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import accuracy_score, recall_score, roc_curve, precision_score, f1_score, auc
import matplotlib.pyplot as plt
from datetime import datetime
import tensorflow as tf
import keras
import keras.backend as K
from keras import metrics
from tensorflow.keras.models import Sequential, Model,load_model
from tensorflow.keras.layers import Permute,multiply,Add,Multiply,BatchNormalization,Dropout, Conv1D, Input, Flatten, Bidirectional, MaxPooling1D, Activation, Flatten, Dense, Dropout, BatchNormalization, LSTM, TimeDistributed, SpatialDropout1D, GaussianNoise
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from keras.layers.merge import concatenate
from sklearn.metrics import mean_absolute_error


train=pd.read_csv('/content/drive/MyDrive/dacon/solar/train/train.csv')
for i in range(0,81):
    test = "test_%d = pd.read_csv('/content/drive/MyDrive/dacon/solar/test/%d.csv')"%(i,i)
    exec(test)
    
# DHI, DNI, T 변수만 활용
X_train=train.drop(['Day','Hour','Minute','WS','RH'],axis=1)
y_train=train[['TARGET']]

 

 

  • 데이터 전처리
# Scaling 
scaling=RobustScaler()
X_train[X_train.columns]=scaling.fit_transform(X_train)


# X(7일), y1(1일 뒤 예측), y2(2일 뒤 예측) 데이터 분할
def multivariate_split(dataset, target, start_index, end_index, history_size,
                      target_size, step, single_step=False):
  data = []
  labels1 = []
  labels2 = []

  start_index = start_index + history_size
  if end_index is None:
    end_index = len(dataset) - target_size

  for i in tqdm(range(start_index, end_index)):
    indices = range(i-history_size, i, step)
    data.append(dataset.loc[indices])

    if single_step:
      labels.append(target.loc[i+target_size-1,:])
    else:
      labels1.append(target.loc[i:i+target_size-1-48,:])
      labels2.append(target.loc[i+48:i+target_size-1,:])

  return np.array(data), np.array(labels1), np.array(labels2)
  
  
  
history_size=336
target_size=96
step=1

X_train1, y_train1, y_train2 = multivariate_split(X_train,y_train,
                                      0, len(X_train)-96, 
                                      history_size, target_size, step)
                                      
                                      
                                      
# 연속된 Sequence Dataset Shuffle
s = np.arange(X_train1.shape[0])
np.random.shuffle(s)

X_train_1 = X_train1[s]
y_train_1 = y_train1[s]
y_train_2 = y_train2[s]​

 

 

  • CNN Modeling
# custom loss function
def quantile_loss(q,y,f):
    e = (y-f)
    return K.mean(K.maximum(q*e, (q-1)*e), axis=-1)
    
    
 def solar_model():
  num=64
  strides_size=1
  model_input = Input(shape=(336,4))
  model = Conv1D(num,3,padding='same',activation='relu')(model_input)
  model = Conv1D(num,7,padding='same',activation='relu',dilation_rate=48)(model)
  model = Conv1D(num,2,padding='valid',activation='relu',dilation_rate=48)(model)
  model = Conv1D(num,2,padding='valid',activation='relu',dilation_rate=48)(model)
  model = Conv1D(num,2,padding='valid',activation='relu',dilation_rate=48)(model)
  model = Conv1D(num,2,padding='valid',activation='relu',dilation_rate=48)(model)
  model = Conv1D(num/2,2,padding='valid',activation='relu',dilation_rate=48)(model)
  model = Conv1D(num/4,2,padding='valid',activation='relu',dilation_rate=48)(model)
  model = Conv1D(1,1,activation='relu',dilation_rate=48)(model)
  model = Flatten()(model)
  model = Model(inputs=model_input, outputs = model)
  return model
  
  
  
qs = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
prediction=[]

for q in tqdm(qs):
  print('='*20 + ' ' + str(q) + 'day1' +' ' + '='*20)
  model_1 = solar_model()
  adam=keras.optimizers.Adam(lr=0.0001)
  model_1.compile(loss=lambda y,f: quantile_loss(q,y,f), optimizer=adam)
  es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=5)
  mc = ModelCheckpoint('best_model_1.h5', monitor='val_loss', mode='min', verbose=1, save_best_only=True)
  history=model_1.fit(X_train_1, y_train_1, epochs=30, callbacks=[es,mc], validation_split=0.2, batch_size=128)
  model_1.load_weights('/content/best_model_1.h5')
  
  print('='*20 + ' ' + str(q) + 'day2' + ' ' + '='*20)
  model_2 = solar_model()
  adam=keras.optimizers.Adam(lr=0.0001)
  model_2.compile(loss=lambda y,f: quantile_loss(q,y,f), optimizer=adam)
  es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=5)
  mc = ModelCheckpoint('best_model_2.h5', monitor='val_loss', mode='min', verbose=1, save_best_only=True)
  history=model_2.fit(X_train_1, y_train_2, epochs=30, callbacks=[es,mc], validation_split=0.2, batch_size=128)
  model_2.load_weights('/content/best_model_2.h5')

  pred_total = []
  for i in range(0,81):
    tmp = pd.read_csv(f'/content/drive/MyDrive/dacon/solar/test/{i}.csv')
    tmp = tmp.drop(['Day','Hour','Minute','WS','RH'],axis=1)
    tmp = scaling.transform(tmp)
    tmp=np.array(tmp).reshape(1,336,4)
    pred = []
    pred.extend(model_1.predict(tmp))
    pred.extend(model_2.predict(tmp))
    pred_total.append(pred)
  prediction.append(np.array(pred_total).reshape(96*81))

 

 

  • 예측 결과 DataFrame 생성
# Prediction 및 Submission 파일 생성
df_final=pd.DataFrame(np.array(prediction).T)
submission=pd.read_csv('/content/drive/MyDrive/dacon/solar/sample_submission.csv')
cols=submission.columns.tolist()[1:]
submission[cols]=df_final.values
submission.to_csv('/content/drive/MyDrive/dacon/solar/submission.csv',index=False)