AI/빅콘테스트 2020 챔피언리그

[2] 판매실적 예측_1. DeepFM, xDeepFM 모델링

eraser 2020. 8. 31. 01:53
반응형

 공모전의 첫 번째 문제인 판매실적 예측을 위해 모델링을 진행했다. 예측한 판매실적에 대한 성능은 MAPE로 측정한다. 

 

 


 

# 문제 상황

 

모델링을 진행하는 과정에서 겪었던 문제를 크게 두 가지로 요약하면 다음과 같다.

 

 첫째, 데이터의 희소성(Sparsity)이다. 2020년 6월의 판매실적을 예측해야 하나, 주어진 데이터가 2019년(과 2020년 1월 일부)의 데이터이다. 2020년 1월부터 5월까지의 데이터가 없는 상태에서 6월의 판매실적을 예측해야 한다.

 둘째, 판매실적에 영향을 미치는 변수가 너무 많다는 점이다.  홈쇼핑 판매실적은 편성 시간 외에도 소비자의 경제 상황 판단, 선호도 등 수많은 외부 변수의 영향을 받는다. 내부 데이터에 대한 EDA를 통해 상품 판매의 계절성 및 주기성, 판매 시간대의 중요성 등을 파악할 수는 있었으나, 수많은 변수 중 어떤 변수가 판매실적에 더 많은 영향을 미치는지 측정할 수 없다는 점도 큰 문제이다.

 


# DeepFM 모델

 

 위와 같은 문제를 해결하기 위해, 광고대행사의 CTR(광고에 대한 클릭량) 예측을 유사 사례로 참고했다. 유저들의 클릭량에 영향을 미치는 변수를 분석할 때에도 데이터가 희소하며, 수많은 변수 중 어떤 변수가 어느 정도의 영향을 주는지 알 수 없다는 문제가 있다. 광고대행사들은 이러한 문제를 해결하기 위해 FM(Factorization Machine) 기법을 사용한다.

 

FM의 원리

 FM 모델의 경우 서로 다른 변수의 잠재벡터 간 내적을 구함으로써, 희소한 데이터 문제를 해결하고, 변수 간 숨겨진 상호작용과 그 정도를 찾아낼 수 있다.

 

 이에 FM 모델과 딥러닝 네트워크의 구조를 통합한 신경망 DeepFM 모델을 이용함으로써 판매실적을 예측해 보고자 하였다. 해당 네트워크의 경우, 직접적인 변수들이 영향을 미치는 Dense Layer와 간접적인 변수들이 영향을 미치는 Embedding Layer의 추가를 통해 예측하고자 하는 값에 대한 변수들의 직접적 영향과 간접적 영향을 비교할 수 있다는 장점을 가지고 있다. 

DeepFM의 원리 (출처: DeepFM: A Factorization-Machine based Neural Network for CTR Prediction)

 

 특히, 이러한 DeepFM 네트워크를 더 발전시킨 xDeepFM 모델까지 사용하여 판매 실적 예측에 활용하고자 하였다.

 


 

 

# 사용 라이브러리

 

  • DeepCTR : 딥러닝 기반의 CTR 모델들을 구현해 놓은 라이브러리이다.
 

Welcome to DeepCTR’s documentation! — DeepCTR 0.8.2 documentation

© Copyright 2017-present, Weichen Shen Revision e9c8f08f.

deepctr-doc.readthedocs.io

  • pandas
  • numpy
  • sklearn

 

# 데이터 전처리

 

 주어진 내부 데이터에 대해서는 크게 세 가지의 전처리 작업을 진행했다. 첫째, 결측치 및 취급액이 0인 데이터를 제거했다. 둘째, DeepFM 모델의 성능 향상을 위해 몇몇 특성을 범주화했다. 셋째, 시청률 데이터에서 순간적으로 시청률이 올라간 시간대를 계산하여 zapping time 측정에 활용했다.

 수집한 외부 데이터에 대해서는 다음과 같은 전처리 작업을 진행했다. 첫째, 생활인구의 변동성 측정을 위해 표준화를 진행했다. 둘째, 상품 유사도 측정을 위해 활용한 상품 설명 관련 텍스트 데이터를 전처리했다. 셋째, 날씨, 경제지표 등의 데이터를 모델링에 활용하기 위해 특성을 범주화했다.

 


 

# 모델링

 

 deepctr 라이브러리를 활용하기 위한 핵심은 범주형 데이터 및 수치형 데이터의 구분이다. 전자는 sparse feature로, 후자는 dense feature로 모델에서 활용한다. 각각을 분류하여 SparseFeat, DenseFeat object로 만들어 주면 된다. 사소하지만, 컬럼명 설정에도 주의해야 한다. 라이브러리에서 정의하는 네이밍 규칙이 있다.

 이후 모델링 단계는 쉽다. Tensorflow 기반으로 구현되어 있기 때문에, 일반적으로 Keras를 사용할 때처럼 compile, fit 등의 메소드를 사용하면 된다. 다만 model을 생성할 때 linear feature와 dnn feature를 넘기고, task를 명시해 주어야 한다. DeepFM, xDeepFM 모델 각각에서 사용하는 파라미터가 조금씩 다르지만, 이는 지금 단계에서 고민할 것은 아니고, 논문과 documentation을 보며 그 의미를 파악해야 한다.

 

 

1. DeepFM

# 모듈 불러오기
from deepctr.models import DeepFM
from deepctr.feature_column import SparseFeat, get_feature_names
...

# 데이터 로드 및 전처리
...

# feature로 사용할 수 있는 데이터 추출
df = data[[x for x in data.columns if '맵핑' in x or x == '취급액']]

# feature 이름 영어로 변환
...

# feature, target 정의
sparse_features = list(df.columns)[1:]
target = ['AMT']

# feature column 정의
fixlen_feature_columns = [SparseFeat(feat, df[feat].nunique(), embedding_dim=6) \
                          for feat in sparse_features]
linear_feature_columns = fixlen_feature_columns
dnn_feature_columns = fixlen_feature_columns
feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)

# 트레인, 테스트 셋 분리
X_train, X_test, y_train, y_test = train_test_split(df[sparse_features], df[target],
                                                    test_size=0.2, random_state=2020)

# 입력 데이터
X_train = {name:X_train[name].values for name in feature_names}
X_test = {name:X_test[name].values for name in feature_names}

# 모델 생성 및 컴파일
model = DeepFM(linear_feature_columns, dnn_feature_columns, task='regression')
model.compile(optimizer=optimizers.Adam(lr=0.001), loss='mape')

# 모델 학습
hist = model.fit(X_train, y_train,
                 batch_size=256,
                 epochs=500,
                 verbose=1,
                 validation_split=0.2)

# 결과 plot
plt.plot(hist.history['loss'], label='Train Loss')
plt.plot(hist.history['val_loss'], label='Validation Loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.title('Model Train-Validation Loss')
plt.show()

 

 시험 삼아 500번의 에폭으로 모델링을 진행했을 때 결과는 다음과 같다. 주황색이 validation MAPE, 파란색이 train MAPE 값이다.

deepFM baseline model 결과

 

 

2. xDeepFM

 

 xDeepFM의 경우, 모델을 생성하는 부분만 다르다. 다만, DeepFM 네트워크를 사용한 이후, 데이터를 추가하는 과정을 거치면서 fixlen_feature_columns를 생성하는 부분이 좀 바뀌었다. 추가한 데이터의 범주가 연속적이지 않았기 때문에, 임베딩 시 max 값에 1을 더한 차원의 공간으로 mapping하도록 변경했다.

 

# 모듈 불러오기
from deepctr.feature_column import SparseFeat, DenseFeat, get_feature_names
from deepctr.models.xdeepfm import xDeepFM
...

(생략)

# 범주형 변수 인코딩
categorical_features = ...
encoders = {}
for column in categorical_features:
    encoder = LabelEncoder()
    encoder.fit(data[column])
    encoders[column] = encoder
for column in categorical_features:
    encoder = encoders[column]
    data[column] = encoder.transform(data[column])

# 수치형 변수 scaling
numerical_features = ...
for num_col in numerical_features:
    ms = MinMaxScaler()
    ms.fit(data[[num_col]])
    data[[num_col]] = ms.transform(data[[num_col]])

...

# define feature, target for xdeepfm model # FIXME: 컬럼명 주의
sparse_features = categorical_features
dense_features = numerical_features
target = ['AMT']
fixlen_feature_columns = [SparseFeat(feat, np.array(data[feat]).max()+1, embedding_dim=6) for feat in sparse_features] + \
                         [DenseFeat(feat, 1, ) for feat in dense_features]
linear_feature_columns = fixlen_feature_columns
dnn_feature_columns = fixlen_feature_columns
feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)

# prepare data for xdeepfm model
X_train = {name:X_train[name].values for name in feature_names}
X_val = {name:X_val[name].values for name in feature_names}
test_data = {name:test_data[name].values for name in feature_names}

# create model and compile
model = xDeepFM(linear_feature_columns, dnn_feature_columns, 
                dnn_use_bn=True, task='regression')
model.compile(optimizer=optimizers.Adam(lr=0.001), loss='mape')

# train model
hist = model.fit(X_train, y_train,
                 batch_size=256,
                 epochs=500,
                 verbose=1,
                 validation_split=0.2)
                 
# plot results
plt.plot(hist.history['loss'], label='Train Loss')
plt.plot(hist.history['val_loss'], label='Validation Loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.title('Model Train-Validation Loss')
plt.show()

 

 

 

 

반응형