AI/제주 신용카드 빅데이터 경진대회

[4] GAN으로 데이터 늘리기 (feat.행복)

eraser 2020. 8. 18. 01:33
반응형

 GAN 모델을 활용해 결측치 데이터를 불려 보기로 했다. 수업 시간에 배운 GAN 모델의 네트워크 구조를 그대로 사용한다. 이후 이전의 시계열 모형 Baseline을 수정하고, 늘린 데이터를 활용해 시계열 모델을 학습한다. 이전에도 계속 나타났던 primary key와 결측치 문제로 인해, 강사님의 조언을 얻어 데이터 feature의 개수를 3개로 제한했다.

 

 


# GAN 모델링

 

 데이터를 전처리한 후, GAN 네트워크를 사용해 데이터를 불린다.

 


 

1. 전처리

 

 연령대(AGE), 성별(SEX_CTGO_CD), 생애주기(FLC) 피쳐만 사용한다. 사용한 피쳐에 맞게 데이터를 groupby를 통해 집계한다. 집계 후 생긴 결측치는 각 피쳐의 평균값(mean)으로 채워 주었다.

 

 


 

2. GAN 모델 빌드

 

 수업 때 사용했던 네트워크를 그대로 활용했다. shape 및 데이터 구성만 달라졌다.

 

from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras import backend as K
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import tensorflow as tf
tf.compat.v1.disable_eager_execution()


# 모델 파라미터 설정
d_input = data.shape[1] # feature 수: 3
d_hidden = 256
d_output = 1
g_input = 32
g_hidden = 256
g_output = d_input

# 가짜 데이터 생성
def makeZ(m, n):
    z = np.random.uniform(-1.0, 1.0, size=[m, n])
    return z

# 옵티마이저 설정
def myOptimizer(lr):
    return RMSprop(learning_rate=lr)
    
# Discriminator 모델
def build_D():
    d_x = Input(batch_shape=(None, d_input))
    d_h = Dense(d_hidden, activation='relu')(d_x)
    d_o = Dense(d_output, activation='sigmoid')(d_h)
    
    d_model = Model(d_x, d_o)
    d_model.compile(loss='binary_crossentropy', optimizer=myOptimizer(0.0001))
    
    return d_model

# Generator 모델
def build_G():
    g_x = Input(batch_shape=(None, g_input))
    g_h = Dense(g_hidden, activation='relu')(g_x)
    g_o = Dense(g_output, activation='linear')(g_h)
    
    g_model = Model(g_x, g_o)
    
    return g_model
    
# GAN 네트워크
def build_GAN(discriminator, generator):
    discriminator.trainable = False # discriminator 학습하지 않음.
    z = Input(batch_shape=(None, g_input))
    Gz = generator(z)
    DGz = discriminator(Gz)
    
    gan_model = Model(z, DGz)
    gan_model.compile(loss='binary_crossentropy', optimizer=myOptimizer(0.0005)) 
    
    return gan_model

 

 

 이후 모델을 학습한다.

 

# 학습

K.clear_session() 
D = build_D() # discriminator
G = build_G() # generator
GAN = build_GAN(D, G) # GAN

n_batch_cnt = 6
n_batch_size = int(data.shape[0] / n_batch_cnt)
EPOCHS = 1000

for epoch in range(EPOCHS):
    for n in range(n_batch_cnt):
        from_, to_ = n*n_batch_size, (n+1)*n_batch_size
        if n == n_batch_cnt -1 : 
            to_ = data.shape[0]
        
        # 학습 데이터 미니배치 준비
        X_batch = real_data[from_: to_]
        Z_batch = makeZ(m=X_batch.shape[0], n=g_input)
        Gz = G.predict(Z_batch) # 가짜 데이터로부터 분포 생성
        
        # discriminator 학습 데이터 준비
        d_target = np.zeros(X_batch.shape[0]*2)
        d_target[:X_batch.shape[0]] = 0.9 
        d_target[X_batch.shape[0]:] = 0.1
        bX_Gz = np.concatenate([X_batch, Gz]) # 묶어줌.
        
        # generator 학습 데이터 준비
        g_target = np.zeros(Z_batch.shape[0])
        g_target[:] = 0.9 # 모두 할당해야 바뀜.
        
        # discriminator 학습        
        loss_D = D.train_on_batch(bX_Gz, d_target) # loss 계산
        
        # generator 학습        
        loss_G = GAN.train_on_batch(Z_batch, g_target)
        
    if epoch % 10 == 0:
        z = makeZ(m=data.shape[0], n=g_input)
        print("Epoch: %d, D-loss = %.4f, G-loss = %.4f" %(epoch, loss_D, loss_G))

 

 

더보기

 학습 단계에서 loss 변화 추이는 다음과 같다. 1000번 학습했는데, 그렇게 잘 떨어지는지는 모르겠다. 오히려 막판에 loss가 상승했다.

epoch = 0, D-Loss = 0.580, G-Loss = 0.695
epoch = 10, D-Loss = 0.606, G-Loss = 0.686
epoch = 20, D-Loss = 0.616, G-Loss = 0.755
epoch = 30, D-Loss = 0.640, G-Loss = 0.759
epoch = 40, D-Loss = 0.700, G-Loss = 0.675
epoch = 50, D-Loss = 0.740, G-Loss = 0.730
epoch = 60, D-Loss = 0.759, G-Loss = 0.695
epoch = 70, D-Loss = 0.697, G-Loss = 0.838
epoch = 80, D-Loss = 0.692, G-Loss = 0.823
epoch = 90, D-Loss = 0.689, G-Loss = 0.767

...

epoch = 950, D-Loss = 0.609, G-Loss = 0.840
epoch = 960, D-Loss = 0.647, G-Loss = 0.940
epoch = 970, D-Loss = 0.624, G-Loss = 0.822
epoch = 980, D-Loss = 0.590, G-Loss = 0.922
epoch = 990, D-Loss = 0.665, G-Loss = 0.871

 

 

 학습한 GAN 모델에서 가짜 데이터를 예측하여 시계열 데이터 학습에 사용할 데이터를 생성하고, pickle 형식으로 저장해 두었다. GAN 모델이 생성해 낸 가짜 데이터를 확인해 보자.

 

 

 Baseline 예시이므로 한 업종(건강보조식품 소매업)에 대한 그림만 그려서 확인한다.

 

 

 

 

 

 왼쪽이 실제 대회 주최 측에서 제공한 데이터를 가지고 각각의 피쳐에 대해 집계한 결과, 오른쪽이 학습한 GAN 모델을 가지고 만들어낸 데이터이다. 전체적인 특징이 비슷하게 나타남을 확인할 수 있다. 9월, 1월에 특히 매출이 높다. (명절이 있기 때문이 아닐까?) 다만, GAN 모델을 가지고 만들어 낸 데이터가 조금 더 정제되어(?) 있는 듯한 느낌이다.

 


 

 

 

# LSTM 모델링

 

 만든 데이터를 통해 기존의 LSTM Baseline 코드를 활용하여 시계열 모델을 만들고, 예측한다. 일단 2020년 3월까지의 데이터를 활용해 2020년 4월 데이터, 즉, 1기간만 예측해 보았다.

 

# Baseline 파라미터 적용, 1기간 예측
n_step = 5
n_features = 1
n_futures = 1 
n_hidden = 128
n_output = n_features
EPOCHS = 300
BATCH = 32

X_input = Input(batch_shape = (None, n_step, n_features))
X_lstm = LSTM(n_hidden)(X_input)
X_output = Dense(n_output)(X_lstm)

model = Model(X_input, X_output)
model.compile(loss='mse', optimizer=Adam(learning_rate=0.001))
    
hist = model.fit(X, y,
                 epochs=EPOCHS,
                 batch_size=BATCH,
                 verbose=1)    

 

 

 제출해 본 결과, 2.34xx 정도의 점수가 나온다(!). 그 동안 리더보드에 제출했을 때 나온 점수 중 가장 좋다. 희망을 가지고 강사님의 도움을 받아 다음과 같은 부분을 변경했다.

  • 1년치 데이터로 다음 데이터를 학습한다. 2019년 1월부터 12월까지의 데이터로 2020년 1월의 데이터를, 2019년 2월부터 2020년 1월까지의 데이터로 2020년 2월의 데이터를, 2019년 3월부터 2020년 2월까지의 데이터로 2020년 3월의 데이터를 학습하는 방식이다.
  • 네트워크 구성 방식, 학습률, 드롭아웃 등의 사항을 변경했다. 은닉 노드 수도 줄였다.
# 파라미터 변경, 1기간 예측.
n_step = 12
n_features = 3
n_futures = 1 
n_hidden = 50
n_output = n_features
EPOCHS = 500
BATCH = 256

X_input = Input(batch_shape = (None, n_step, n_features))
X_lstm = LSTM(n_hidden)(X_input)
X_dense = Dense(n_output)(X_lstm)
X_dense = Dropout(0.5)(X_dense)
X_output = Dense(n_output)(X_dense)

model = Model(X_input, X_output)
model.compile(loss='mse', optimizer=Adam(learning_rate=0.0005))    
hist = model.fit(X, y,
                 epochs=EPOCHS,
                 batch_size=BATCH,
                 shuffle=True
                 verbose=1)    

 

 예측하고 결과를 제출했는데, 1.074xxx 정도의 점수가 나왔다!!! 기존에 리더보드 상에서 가장 높은 점수가 3월 데이터만 가지고 4월 데이터를 예측한 1.56xxx 정도였다. 애초에 해당 코드가 공개된 터라, 팀 차원에서 이 점수를 기준으로 삼아 깎아 내려갈 수 있는 방법을 고민해 보자고 했는데, 매우 좋은 결과가 나왔다!

 


 

# 배운 점, 더 생각해 볼 점

 

 GAN 모델을 배울 때도 모델이 참 똑똑하다(?)고 생각했는데, 이 정도일 줄 몰랐다. 확실히 결측치가 많은 것보다, 그리고 그 많은 결측치를 전부 동일하게 채우는 것보다, GAN을 사용해 기존 데이터를 모방하는 게 더 좋은 성능을 낸다. 물론 4월 데이터에 대해서만 채점이 이루어지기 때문에, 예측 기간을 늘려 7월 데이터까지 예측했을 때도 좋은 결과가 나올지는 미지수다.

 한편으로, 기존에 계속해서 답답했던 부분을 해결할 수 있었다. 첫째, 확실히, 이 데이터에 대해서 만큼은 feature를 모두 사용하지 않는 것이 좋다. 둘째, GAN 모델을 사용할 때도 결측치는 0으로 채우거나 없애는 것보다, 평균값을 채울 때 더 성능이 좋았다. 셋째, 데이터 양 자체는 많지 않으므로, 시계열 모형을 만들 때 은닉 노드 수를 너무 많이 잡지 않는 것이 좋다. 넷째, 드롭아웃을 적용하자.

 

1. 다른 피쳐들을 사용해서 GAN + LSTM 적용해 보자.

2. 모방 데이터가 더 실제 데이터와 근사한 모습을 띠게 하려면 어떻게 해야 할까? 또한, 지금보다 더 비슷해진다면, 예측 결과가 좋아질까? 7월의 경우, 코로나 영향으로 인해 지금 데이터와 비슷해 진다고 해서 더 잘 예측할 것이라고 생각이 들지는 않는다. 오히려 너무 훈련 데이터 분포에 맞게 과적합시키는 것도 안 좋을 수 있지 않을까?
반응형