이제 이미지나 자연어 등의 비정형 데이터에 대해서는 딥러닝을 기반으로 한 연구만이 진행되고 있다고 봐도 무방합니다. 다만 정형 데이터에 대해서는 아직도 머신러닝 모델, 특히 트리 기반의 부스팅 계열 모델들의 성능이 더 뛰어납니다.
제가 참여했던 프로젝트에서도 정형 데이터에 대한 모델링을 수행할 때는 일반적으로 LightGBM 등 머신러닝 계열 모델의 성능이 딥러닝 모델들보다 조금 더 좋은 결과를 보여주었습니다.
대표적으로 LightGBM, XGBoost, CatBoost 등의 모델이 있는데, 이 모델들은 아직까지도 정형 데이터를 사용하는 경진대회에서 상위권을 차지하고 있을 만큼 성능이 좋습니다. 하지만 최근 들어 TabNet이라는 딥러닝 모델이 가끔씩 대회에서 등장하고 있습니다. 이 모델은 저자들의 테스트 기준으로 위의 부스팅 기반 SoTA 모델들과 비슷하거나 더 나은 성능을 보인다고 합니다.
이번 페이지에서는 어떠한 아키텍쳐를 구성했기에 TabNet이 정형 데이터에 대해 뛰어난 성능을 보일 수 있었는지 살펴보도록 하겠습니다.
Related work
Feature Selection
기존의 Feature seletion은 Lasso Regularization, Instance-wise feature selection등의 방법을 사용합니다. 이러한 방법은 선택되지 않은 변수를 아예 사용하지 않는 방식이라고 볼 수 있는데, 논문에서는 이를 Hard feature selection이라고 표현합니다. TabNet은 머신러닝에서 자주 사용되는 위의 Hard feature selection은 아니지만, Soft feature selection을 구현하여 사용한다고 합니다.
위의 Figure 1을 보시면 변수들을 몇 개 선택한 후에 input processing을 하는 과정을 반복하는 것을 확인할 수 있습니다. 다만 이 그림만 봐서는 Soft feature selection을 어떻게 구현했는지 확인하기 어려우므로 Soft feature selection는 나중에 설명하도록 하겠습니다.
Tree-based learning & Intergration of DNNs into DTs
위의 LightGBM, XGBoost 등의 모델들은 트리 기반의 모델들입니다. 이러한 모델들은 Information Gain 연산 등을 통해 글로벌 변수들을 효율적으로 선택할 수 있다는 장점을 가지고 있습니다. 저자들은 트리 기반 모델들과 DNN의 결합(Intergration of DNNs into DTs)을 통해 우수한 성능을 얻을 수 있다고 합니다.
TabNet 이전의 Intergration of DNNs into DTs를 연구한 논문들은 좋은 결과를 얻지는 못했지만, TabNet은 sequential attention과 soft feature selection을 사용해서 좋은 성능을 보입니다.
Self-supervised learning
도 가능합니다.
TabNet for Tabular Learning
아래와 같은 구조를 통해 conventional DNN building blocks이 DT(의사결정나무)와 같은 역할을 합니다.
Mask를 통해 변수를 선택한 후, 그 변수들을 가지고 연산을 하여 마치 오른쪽 결정 경계와 같은 모습을 만드는 구조입니다.
Architecture
전체적인 구조는 다음과 같습니다.
위의 그림 중 (a) TabNet encoder architecture를 기준으로 순서대로 정리해보겠습니다.
- BN : 최초 Feature에 대해 배치 정규화를 적용합니다.
- Feature transformer
이후에 아래의 Step을 반복합니다.
- Attentive transformer
- Mask : Attentive transformer에서 나온 Mask(M[i])에 대해 feature f를 곱하여 이후 step의 Feature transformer에 들어가는 input을 조절합니다. Mask를 통해 변수를 soft selection한다고 이해할 수 있습니다.
- Feature transformer
- Split : 구현된 함수를 보면 Feature transformer의 output을 두 개로 복제하여 하나는 relu로, 하나는 Attentive transformer로 보내는 것으로 보입니다.
이제 각각의 모듈들을 상세하게 확인해보도록 하겠습니다.
Feature transformer
4계층 네트워크로, 앞의 두 블록은 파라미터가 모든 step에서 공유되며, 나머지 2개는 독립적인 파라미터를 가지고 있습니다. 각각의 블록은 FC - BN - GLU로 구성되어 있으며 블록 간에는 Skip connection이 구성되어 있습니다. 또한 그 결과값에 대해 sqrt(0.5)를 곱합으로써 네트워크 전체의 분산이 극단적으로 변하지 않도록 안정화를 하고 있습니다.
여기서 사용되는 Batch Normalization은 Ghost BN을 사용합니다. 즉 하나의 블록은 정확하게는 FC - Ghost BN - GLU로 구성되어 있다고 볼 수 있습니다.
GLU
LSTM 게이트와 유사한 방법으로, 두 개의 레이어에서 시작하고 그 중 하나에 시그모이드를 적용한 후 두 개를 함께 곱하는 알고리즘을 사용하며 이를 통해 어떤 맥락에서 무엇이 관련이 있는지에 따라 계층에서 전달되는 정보를 제어합니다.
class GLU_Layer(torch.nn.Module):
def __init__(
self, input_dim, output_dim, fc=None, virtual_batch_size=128, momentum=0.02
):
super(GLU_Layer, self).__init__()
self.output_dim = output_dim
if fc:
self.fc = fc
else:
self.fc = Linear(input_dim, 2 * output_dim, bias=False)
initialize_glu(self.fc, input_dim, 2 * output_dim)
self.bn = GBN(
2 * output_dim, virtual_batch_size=virtual_batch_size, momentum=momentum
)
def forward(self, x):
x = self.fc(x)
x = self.bn(x)
out = torch.mul(x[:, : self.output_dim], torch.sigmoid(x[:, self.output_dim :]))
return out
위의 코드는 pytorch_tabnet의 GLU 클래스 코드로, GBN은 Ghost BN을 의미합니다. 이 코드에서는 Linear 레이어가 적용되어 있지만 다른 글들을 읽어봤을 때 Conv1d를 사용하는 코드도 존재하는 것으로 보입니다.
Ghost Batch Normalization
nano batch로 사이즈를 줄인 후 각각에 대해 정규화하여 마치 노이즈를 추가한 것과 같은 효과를 주는 방법입니다. 아래 URL에 자세한 설명을 확인하실 수 있습니다.
class GBN(torch.nn.Module):
"""
Ghost Batch Normalization
https://arxiv.org/abs/1705.08741
"""
def __init__(self, input_dim, virtual_batch_size=128, momentum=0.01):
super(GBN, self).__init__()
self.input_dim = input_dim
self.virtual_batch_size = virtual_batch_size
self.bn = BatchNorm1d(self.input_dim, momentum=momentum)
def forward(self, x):
chunks = x.chunk(int(np.ceil(x.shape[0] / self.virtual_batch_size)), 0)
res = [self.bn(x_) for x_ in chunks]
return torch.cat(res, dim=0)
Skip connection
위의 그림처럼 F(x)에 이전의 x값을 더하는 방식을 말합니다. Feature transformer의 구조를 보면 GLU 블록 간에 이러한 방식을 사용하고 있다는 걸 확인할 수 있습니다.
Attentive transformer
이전 단계 a[i - 1]의 feature를 input으로 받아 Mask를 구합니다. 여기서 Mask를 M[i]로 표현하면, M[i] = sparsemax(P[i - 1] · hi(a[i - 1]))로 표현할 수 있습니다.
h[i]
이 식에서 hi는 훈련가능한 함수입니다. 그림에서 FC + BN 영역입니다.
P[i]
P[i]는 특정 피쳐가 이전에 얼마나 사용됬는지를 나타내는 prior scale입니다.
P[0]은 모든 값이 1인 B×D 형태로 구성되어 있으며, (자기 지도 학습에서와 같이) 일부 기능이 사용되지 않는 경우, 모델의 학습을 돕기 위해 해당 P[0] 항목이 0으로 구성됩니다.
여기서 감마(γ)값은 relaxation 역할을 하는 파라미터입니다. 쉽게 말해 이전 시점의 마스크 M[j]를 얼마나 재사용할지 조절하는 파라미터입니다.
추가로 저자들은 M[i]와 관련하여 sparsity regularization를 위해 위와 같은 Loss를 전체 Loss에 추가했습니다.
sparsemax
조금 더 극단적인 softmax 함수입니다. 위의 h[i]와 P[i]의 곱에 대해 sparsemax를 적용하여 최종적으로 Mask M[i]를 계산합니다.
pytorch_tensor에서 작성한 sparsemax, entmax 코드입니다. https://github.com/dreamquark-ai/tabnet/blob/develop/pytorch_tabnet/sparsemax.py
Autoencoder
지금까지 설명한 encoder 구조에 decoder를 연결하면 Autoencoder가 되며, 연결하지 않고 그대로 학습에 사용해도 됩니다.