갑상선암 진단 분류 해커톤 : 양성과 악성, AI로 정확히 구분하라!

데이콘 해커톤 | 알고리즘 | 입문 | 정형 | 분류 | F1 Score

  • moneyIcon 상금 : 데이스쿨 프로 구독권
  • 2025.05.07 ~ 2025.06.30 09:59 + Google Calendar
  • 990명 마감

 

파생변수, 모델 등 실험 결과 공유

2025.06.10 01:58 407 조회

시도했던 파생변수와 모델, 실험 결과입니다.

참고해 보시고 인사이트가 있다면 자유롭게 의견 주세요.


[파생변수 41개]

leave-one-out 실험에서 CV 상승이 있었던 목록입니다.

최대한 비선형 조합으로 구성했습니다.

import numpy as np, pandas as pd
from sklearn.neighbors import NearestNeighbors
from sklearn.cluster  import KMeans


EPS = 1e-6
log1p   = np.log1p
clippos = lambda x: np.clip(x, 0, None)


# ──────────────────────────────────────────────
# ① 보조 함수
# ──────────────────────────────────────────────
def safe_ratio(a, b, eps=EPS):
    """ 0-division 방지 비율 """
    return a / np.where(b == 0, eps, b)


def safe_log(x):
    """ log1p, 음수 클리핑 포함 """
    return log1p(clippos(x) + EPS)


# ──────────────────────────────────────────────
# ② 통계 객체(빈도, KNN) 생성
# ──────────────────────────────────────────────
def build_stats(df: pd.DataFrame):
    tmp = df.copy()
    tmp["T4_T3"] = safe_ratio(tmp["T4_Result"], tmp["T3_Result"])
    return dict(
        country_freq = (
            tmp["Country"]
              .value_counts(normalize=True)
              .apply(np.log1p)
              .to_dict()
        ),
        knn = NearestNeighbors(n_neighbors=20).fit(
            tmp[["T3_Result", "T4_Result", "TSH_Result"]]
        )
    )


# ──────────────────────────────────────────────
# ③ 파생 41 feature 생성
# ──────────────────────────────────────────────
def add_41_features(df: pd.DataFrame, st: dict):
    X = df.copy()


    # ── 1. 단순 비율·로그  ─────────────────────
    X["T4_T3"]        = safe_ratio(X["T4_Result"], X["T3_Result"])
    X["T4_TSH"]       = X["T4_Result"] * X["TSH_Result"]
    X["log_T4_T3"]    = safe_log(X["T4_T3"])


    # ── 2. 고차·함수형  ────────────────────────
    X["Nodule_cubic"] = np.maximum(X["Nodule_Size"], 0) ** 3
    X["Age_sq"]       = X["Age"] ** 2
    X["T4_T3_sigmoid"] = 1 / (1 + np.exp(-(X["T4_T3"] - 1)))
    X["TSH_zclip"]     = np.clip(X["TSH_Result"], -2, 2)


    # ── 3. 플래그 결합 ─────────────────────────
    X["Smoke_Weight"] = (X["Smoke"] | X["Weight_Risk"]).astype(int)
    X["Smoke_Diab"]   = (X["Smoke"] & X["Diabetes"]).astype(int)
    X["Rad_Iodine"]   = (X["Radiation_History"] & X["Iodine_Deficiency"]).astype(int)
    X["RadSmoke_flag"] = (X["Radiation_History"] & X["Smoke"]).astype(int)


    # ── 4. RBF 변환  ───────────────────────────
    for μ in (-1, 0, 1):
        X[f"Nod_rbf_{μ}"] = np.exp(-(X["Nodule_Size"] - μ) ** 2 / 0.5)
    X["Nod_RBF_combo"] = X["Nod_rbf_-1"] + X["Nod_rbf_1"] - X["Nod_rbf_0"]
    X.drop(columns=[f"Nod_rbf_{μ}" for μ in (-1, 0, 1)], inplace=True)


    # ── 5. 클러스터 레이블 ─────────────────────
    X["HormoneCluster"] = KMeans(4, random_state=0).fit(
        X[["T3_Result", "T4_Result", "TSH_Result"]]
    ).labels_
    X["HormoneNodClust"] = KMeans(6, random_state=1).fit(
        X[["T3_Result", "T4_Result", "TSH_Result", "Nodule_Size"]]
    ).labels_


    # ── 6. 해시·빈도 ───────────────────────────
    X["CountryRaceHash"] = ((X["Country"] * 100 + X["Race"]) % 127).astype(int)
    X["Country_freq_enc"] = X["Country"].map(st["country_freq"]).fillna(0)


    # ── 7. 밀도(KNN)  ──────────────────────────
    dist = st["knn"].kneighbors(
        X[["T3_Result", "T4_Result", "TSH_Result"]],
        return_distance=True
    )[0].mean(axis=1)
    X["KNN_density"] = 1 / (1 + dist)


    # 결측·무한 값 정리
    X = X.replace([np.inf, -np.inf], np.nan).fillna(0)


    # ── 최종 41개 열 목록을 고정 순서로 추출
    DERIVED_41 = [
        "T4_T3", "T4_TSH", "log_T4_T3",
        "Nodule_cubic", "Age_sq",
        "Smoke_Weight", "Smoke_Diab", "Rad_Iodine",
        "Nod_RBF_combo",
        "HormoneCluster", "HormoneNodClust",
        "CountryRaceHash", "T4_T3_sigmoid", "TSH_zclip",
        "KNN_density", "Country_freq_enc", "RadSmoke_flag",
    ]
    # RBF, 비율·로그 등에서 파생된 나머지 컬럼을 자동 포함
    extra = [c for c in X.columns if c not in DERIVED_41][: (41 - len(DERIVED_41))]
    DERIVED_41 += extra


    return X[DERIVED_41].copy(), DERIVED_41




[시도했던 모델 - CV 검증]

lightgbm

xgboost

catboost

randomforest

gbdt

logistic regression


simple MLP

nn+odst layer (참고 : https://www.kaggle.com/code/albansteff/refactoring-nn-pairwise-ranking-loss)

TabM

TabNet

FT-transformer


최종 후보(CV 상위 5개 모델) : cat, lgb, rf, gbdt, nn+odst  


[실험 결과 - 파생변수 추가 후 optuna]

[CV]

cat best val F1 = 0.4871

lgb best val F1 = 0.4872

rf  best val F1 = 0.4872

gbdt best val F1 = 0.4872

nn+odst best val F1 = 0.4870


[LB] best : 0.5109489051

cat 단일 : 0.5104622871

lgb 단일 : 0.4937910884

**rf 단일 : 0.5109489051

odst 단일 : 0.5085158151

**gbdt 단일 : 0.5109489051


ensemble : 0.5109489051