Monday, March 9, 2020

(PyCox) 세 번째, 활용 및 후기

안녕하세요?
이번에는 활용방법 및 후기를 작성해보도록 하겠습니다.

일단 활용방법은 모델들마다 조금씩 차이는 있습니다. 그렇지만 지금은 Cox-PH (DeepSurv)를 기준으로 설명드리겠습니다.
(참고: https://nbviewer.jupyter.org/github/havakv/pycox/blob/master/examples/cox-ph.ipynb)

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn_pandas import DataFrameMapper

import torch
import torchtuples as tt

from pycox.datasets import metabric
from pycox.models import CoxPH
from pycox.evaluation import EvalSurv
대략 이 예시에서 사용된 것들입니다. 이를 참고하면 좋을 것 같습니다.

df_train = metabric.read_df()
df_test = df_train.sample(frac=0.2)
df_train = df_train.drop(df_test.index)
df_val = df_train.sample(frac=0.2)
df_train = df_train.drop(df_val.index)
예시 데이터로 METABRIC dataset을 사용했다고 합니다.
데이터 셋중에서 20%는 Test dataset으로, 16%는 Validation dataset으로 (80%의 20%이므로!) 그리고 나머지 64%는 Train dataset으로 사용했군요.


Dataset은 다음과 같이 생겼습니다.
x0~x8이 input으로 들어가고 duration 및 event가 label들에 해당한다고 보시면 아주 편하겠죠?
물론 정확한 설명은 아니지만 저는 survival dataset을 이미 안다는 전제하에 이를 설명하는 것입니다.

정확히 이해하시고 싶으시다면, DeepSurv논문을 한번 보시는 것을 추천드립니다. (https://bmcmedresmethodol.biomedcentral.com/articles/10.1186/s12874-018-0482-1에서 읽을 수 있으며 읽기에 어려운 논문은 아닙니다.)

cols_standardize = ['x0', 'x1', 'x2', 'x3', 'x8']
cols_leave = ['x4', 'x5', 'x6', 'x7']

standardize = [([col], StandardScaler()) for col in cols_standardize]
leave = [(col, None) for col in cols_leave]

x_mapper = DataFrameMapper(standardize + leave)

x_train = x_mapper.fit_transform(df_train).astype('float32')
x_val = x_mapper.transform(df_val).astype('float32')
x_test = x_mapper.transform(df_test).astype('float32')

get_target = lambda df: (df['duration'].values, df['event'].values)
y_train = get_target(df_train)
y_val = get_target(df_val)
durations_test, events_test = get_target(df_test)
val = x_val, y_val


Standardization을 하는 방법입니다. cols_standardize는 standardize를 할 column들이고 나머지는 안하는 column들입니다.
label같은 경우는 standardize가 필요도 없고 그냥 가져오기만 하면 됩니다.

in_features = x_train.shape[1]
num_nodes = [32, 32]
out_features = 1
batch_norm = True
dropout = 0.1
output_bias = False

net = tt.practical.MLPVanilla(in_features, num_nodes, out_features, batch_norm,
                              dropout, output_bias=output_bias)
# net = torch.nn.Sequential(
#     torch.nn.Linear(in_features, 32),
#     torch.nn.ReLU(),
#     torch.nn.BatchNorm1d(32),
#     torch.nn.Dropout(0.1),
    
#     torch.nn.Linear(32, 32),
#     torch.nn.ReLU(),
#     torch.nn.BatchNorm1d(32),
#     torch.nn.Dropout(0.1),
    
#     torch.nn.Linear(32, out_features)
# )
안에 들어가는 network를 결정해주는 것인데, 여기서 tt는 torchtuple으로 이 오픈소스 제작자가 만든 PyTorch기반의 package입니다. 저렇게 작성하면 net은 다음과 같이 생성됩니다.

torch.nn.Sequential로 PyTorch의 MLP 하나가 만들어지죠?
그래서 MLP를 자기가 직접 설계한것을 사용하고 싶으면 net에 torch.nn.Sequential로 설계한 것을 집어넣어도 됩니다.

model = CoxPH(net, tt.optim.Adam)
model.optimizer.set_lr(0.01)
epochs = 512
callbacks = [tt.callbacks.EarlyStopping()]
verbose = True
%%time
log = model.fit(x_train, y_train, batch_size, epochs, callbacks, verbose,
                val_data=val, val_batch_size=batch_size)
model.partial_log_likelihood(*val).mean()
일단 모델은 이렇게 Training을 시킵니다.
Constructor에 optimizer와 net으로 instance를 만든후에 learning rate를 지정해줍니다.
이후에 fit이라는 method로 training을 시켜줄수 있습니다.
fit method의 내부는 torchtuples 패키지에 자세히 적혀져 있으므로 이를 확인해주셨으면 합니다. (여기서는 주제를 벗어나므로 설명하지 않습니다.)

_ = model.compute_baseline_hazards()
surv = model.predict_surv_df(x_test)
ev = EvalSurv(surv, durations_test, events_test, censor_surv='km')
ev.concordance_td()
ev.integrated_brier_score(time_grid)
ev.integrated_nbll(time_grid)
Prediction + Evaluation 방법입니다.
baseline_hazards를 계후에 predict_surv_df method 통해서 prediction 값들이 나옵니다.
이를 EvalSurv에 실제 값들과 같이 넣으면은 evaluation을 직접 할수가 있습니다.

이정도까지 활용방법을 대략 설명드렸습니다.

자 그러면 이제 후기를 설명드리도록 하겠습니다.
저는 이제 METABRIC dataset이 아니라 산학프로젝트로 받은 데이터셋을 사용했는데 (공개하면 안되어 그냥 Dataset이라고만 해두겠습니다.) 저도 이것을 활용했습니다.
이제 계속 활용하면서 느낀 장단점을 설명해두겠습니다.


  • 장점
    • PyTorch로부터 나와서 PyTorch를 사용해 본 사람들에겐 편하다.
      • network도 PyTorch기반이고 많은 요소들이 PyTorch 기반을 해서 PyTorch에서 사용하던 요소들도 pycox에 활용할 수 있습니다.
    • 여러 모델들을 제공하여 상황에 따라서 다른 모델들을 사용하기도 편하며 training+prediction 구조가 모델마다 거의 비슷하다.
      • 위에 설명드린 사용법은 CoxPH로만 설명드렸지만 다른 모델들도 비슷합니다.
      • 그리고 또한 Deep-learning 기반의 survival model들을 지원하는 package들이 별로 없는데, 여기서는 여러가지 model들을 지원합니다.
    • torchtuples에서 제공하는 편리한 기능들도 사용가능하다.
      • 예: fit method, make_dataset method(여기서는 설명 안했지만)
  • 단점
    • 지나치게 torchtuples에서 의존하여 내부적으로 어떻게 구현했는지를 보려면 torchtuples package도 꼭 살펴봐야한다.
      • 제가 제일 크게 느낀 단점으로 거의 모든게 torchtuples에서 상속받아 활용해서 torchtuples package를 이해하는게 필수적입니다.
    • 자유도가 그렇게 높지 않다.
      • PyTorch기반인데, fit method로 training이 된다는 것은 그렇게 자유도가 높지 않다는 뜻입니다. PyTorch를 사용해보았다면 DataLoader를 만드는 것이 필수적이란 것을 알겁니다.
      • 그렇지만 fit method를 통해서 내부적으로 DataLoader를 만들고 사용하는데, 찾아보시면 sampler는 사용할수 없게 됩니다.
      • 그래서 sampler 등등을 포기할수 밖에 없는데, 사용하려면 torchtuples에 구현된 fit method를 보고 직접 따로 구현해야합니다. 
    • Gradient-based model이라서 그런지 covariates(공분산)를 계산하여 training-prediction하는 모델들보다 결과가 비교적 안 좋게 나옵니다.
      • sksurv가 covariates를 계산해서 prediction하는 package인데, 아직까지 더 좋은 결과를 보인적이 없습니다.
이 정도로 후기를 작성하고 마치도록 하겠습니다.
감사합니다!

No comments:

Post a Comment