안녕하세요. 이번 글에서는 Kaggle Playground Series S6E6 대회와 관련해서 다른 참가자가 공유한 baseline notebook을 공부한 내용을 정리하겠습니다.
캐글 경진 대회는 처음 참가해봤는데 토크나 파일 코드 공유 같은게 엄청 활발하더라고요. 근데 대부분 남들이 따라한 코드를 붙여넣은(?)게 많더라고요. 그중에 어떤 외국분 한 분의 코드가 눈에 띄었는데 셀별로 분석을 너무 잘했길래 분석을 해봤습니다.
이 글은 제 코드나 제 제출물을 설명하는 글이 아닙니다. 다른 사람이 작성한 notebook을 읽고, 어떤 구조로 문제를 풀었는지, 어떤 feature engineering과 검증 방식을 사용했는지, 그리고 그래프가 무엇을 말하는지 공부용으로 분석한 기록입니다.
분석한 notebook 파일명은 다음과 같습니다.
stellar-catb-hgbc-xgb-lgbm-realmlp-baseline.ipynb
노트북 제목에는 CatBoost, HGBC, XGBoost, LightGBM, RealMLP가 모두 들어가 있다. 하지만 실제 실행 설정을 보면 모든 모델이 한 번에 학습된 것은 아니다. 이 notebook은 여러 모델을 선택적으로 실행할 수 있는 template에 가깝고, 저장된 실행 결과 기준으로는 HistGradientBoostingClassifier, 즉 HGBC 단일 모델이 학습된 상태였다.
1. 전체 구조
notebook은 전체적으로 다음 흐름을 가진다.
1. package import
2. train/test/external data 로드
3. 비정상 sample 제거
4. label encoding
5. physics-based feature engineering
6. fold split 설정
7. preprocessing pipeline 구성
8. model space 정의
9. 선택한 모델 학습
10. OOF score 평가
11. subset별 성능 분석
12. submission 생성
전체 구조가 잘 정리되어 있다. 단순히 모델 하나를 학습하고 제출하는 코드가 아니라, feature engineering, preprocessing, cross validation, subset error analysis까지 한 notebook 안에 담아둔 형태이다.
특히 좋게 본 부분은 전처리를 fold 안에서 수행하려고 한 점이다. train fold에서 fit하고 validation fold와 test에는 transform만 적용하는 구조를 만들었다. 이런 방식은 data leakage를 줄이는 데 중요하다고 한다.
2. 데이터 로드와 외부 데이터
notebook은 competition data와 외부 데이터를 함께 불러온다.
train.csv
test.csv
star_classification.csv
외부 데이터는 fedesoriano/stellar-classification-dataset-sdss17의 star_classification.csv를 사용한다. 다만 실제 실행 설정에서는 외부 데이터를 학습에 합치지 않았다.
관련 설정은 다음과 같다.
ADD_EXTERN_DATA = False
즉, 외부 데이터는 로드하고 feature engineering까지 적용했지만, 저장된 실행 결과 기준으로는 competition train data만 사용한 것으로 보인다.
외부 데이터에 없는 categorical column은 다음처럼 None으로 채운다.
spectral_type = None
galaxy_population = None
이 부분은 주의가 필요하다. 외부 데이터를 실제로 합치면 spectral_type, galaxy_population의 분포가 competition data와 달라진다. 모델이 이 None category를 별도 신호로 받아들일 수 있으므로, 외부 데이터를 사용할 때는 성능이 오히려 흔들릴 가능성도 있다.
3. 비정상 sample 제거
notebook은 u 값이 5보다 작은 sample을 제거한다.
u > 5
competition train은 원래 577,347행인데, 이 조건을 적용한 뒤 577,344행이 되었다. 즉 3개 row가 제거된 것이다.
이 처리는 광도 magnitude 값에서 비현실적으로 보이는 sample을 제거하려는 의도로 보인다. 다만 이런 제거가 항상 public/private leaderboard에 좋은지는 별도 검증이 필요하다. 3개 row뿐이라 영향은 작아 보이지만, balanced accuracy에서는 작은 변화도 class별 성능에 영향을 줄 수 있다.
4. Label Encoding
target label은 다음처럼 숫자로 바꾼다.
GALAXY -> 0
QSO -> 1
STAR -> 2
최종 submission을 만들 때는 다시 원래 label로 되돌린다.
이 방식은 일반적인 multi-class classification 처리 방식이다. 모델 학습에는 숫자 label을 쓰고, 제출 파일에는 Kaggle이 요구하는 문자열 label을 사용한다.
5. Feature Engineering
이 notebook에서 가장 중요한 부분은 feature engineering이다.
원본 feature만 사용하는 것이 아니라, 천문 데이터 특성을 반영한 파생 feature를 대량으로 만든다. 실행 결과 기준으로 numerical feature는 101개까지 늘어난다.
주요 feature group은 다음과 같다.
1. 색지수 color index
2. magnitude 통계량
3. spectral curvature
4. redshift 변환
5. redshift interaction
6. sky coordinate encoding
7. normalized color feature
8. ratio feature
9. second-order interaction
10. adjacent color shape descriptor
5.1 색지수 feature
천문 데이터에서는 각 band의 magnitude 자체보다 band 간 차이가 더 강한 신호가 될 수 있다.
u_g, g_r, r_i, i_z
u_r, u_i, u_z
g_i, g_z
r_z
이 feature들은 u, g, r, i, z band 간의 차이를 나타낸다. 천체의 색과 스펙트럼 특성을 간접적으로 표현하는 feature라고 볼 수 있다.
우리도 이전 작업에서 색지수 feature를 사용했다. 이 notebook은 거기서 한 단계 더 나아가 wider baseline color index와 interaction까지 확장한다.
5.2 magnitude 통계 feature
mag_mean
mag_std
mag_min
mag_max
mag_range
mag_median
griz_mean
optical_center
이 feature들은 전체 band의 밝기 수준, band 간 변동성, optical band 중심값을 요약한다.
특히 mag_std, mag_range는 천체 class를 나누는 데 유용할 수 있어 보인다. class마다 band별 밝기 변화 패턴이 다르기 때문이다.
5.3 spectral curvature feature
이 notebook은 단순 색지수뿐 아니라 색지수의 변화량도 만든다. 예를 들면 다음과 같은 feature다.
curve_ugr
curve_gri
curve_riz
curve_ugri
curve_griz
이 feature들은 band sequence에서 slope가 어떻게 변하는지를 나타낸다. 단순히 u-g가 크다/작다를 보는 것이 아니라, u-g와 g-r의 차이처럼 곡률에 가까운 정보를 본다.
이 부분은 참고할 만하다. 우리 feature set에도 curvature 계열을 추가해보면 모델이 STAR와 GALAXY 경계 row를 더 잘 구분할 가능성이 있다.
5.4 redshift 변환과 interaction
redshift는 이 대회에서 매우 중요한 feature로 보인다.
notebook은 redshift를 다음처럼 변환한다.
redshift_log1p
redshift_sqrt
redshift_sq
그리고 색지수, 곡률, magnitude 통계와 redshift를 곱한 interaction feature를 많이 만든다.
color_feature * redshift
color_feature * redshift_log1p
이건 꽤 인상 깊은 접근이다. 같은 색지수라도 redshift 영역에 따라 class 의미가 달라질 수 있기 때문이다.
5.5 sky coordinate encoding
좌표 feature인 alpha, delta는 각도값이다. 각도값을 그대로 쓰면 0도와 360도가 멀리 떨어진 값처럼 보이는 문제가 생길 수 있다.
alpha_sin
alpha_cos
delta_sin
delta_cos
sky_x
sky_y
이 처리는 좌표를 주기적/구면적 성질에 맞게 표현하려는 시도다. tabular model에서는 이런 변환이 도움이 될 수 있다.
6. Categorical Feature 처리
원래 categorical feature는 다음 2개이다.
spectral_type
galaxy_population
notebook은 이 둘을 조합한 n-gram categorical feature를 만든다.
2gram_spectral_type_galaxy_population
실행 로그에는 다음처럼 나온다.
Number of original categorical features: 2
Number of extended categorical features: 1
Number of numerical features: 101
여기서 주의할 점이 있다. N_GRAMS_EXT = True일 때 실제 categorical column 목록은 원본 2개가 아니라 n-gram column만 사용되는 구조로 보인다. 즉, spectral_type, galaxy_population 각각을 따로 쓰는 것이 아니라 둘을 합친 조합 category를 쓰는 방식에 가깝다.
이 방식은 장점과 단점이 있다.
장점:
spectral_type과 galaxy_population의 상호작용을 직접 표현할 수 있다.
단점:
각 categorical feature의 독립적인 효과를 놓칠 수 있다.
우리 작업에서는 원본 categorical 2개를 모두 사용했다. 다음 실험에서는 원본 category와 조합 category를 함께 쓰는 방향도 해볼 수 있다.
7. Fold Split 전략
notebook은 5-fold CV를 사용한다.
FOLDS = 5
STRAT = True
기본 stratification은 target 기준이다. 또한 별도로 strat_bin을 만든다.
spectral_type + galaxy_population + class
이 조합을 이용하면 class뿐 아니라 categorical 조합까지 fold에 균형 있게 나눌 수 있다. 다만 저장된 실행에서는
EXTENDED_STRAT = False라서 실제 split에는 target만 사용한 것으로 보인다.
실행 결과는 다음과 같다.
Total rows: 577344
Dev train: 461875 (80.00%)
Dev valid: 115469 (20.00%)
Number of unique elements in strat_bin column: 24
Number of unique elements in multicat column: 8
이 구조는 좋다. 실제로 extended stratification을 켜지 않았더라도, subset 분석을 위한 multicat과 strat_bin을 만들어둔 점은 참고할 만하다.
8. Preprocessing Pipeline
preprocessing은 꽤 복잡하게 구성되어 있다.
1. numerical feature median imputation
2. StandardScaler
3. categorical ordinal encoding
4. numerical feature bucketizing
5. category별 target statistics
특히 중요한 부분은 target statistics이다.
categorical feature나 bucketized numerical feature별로 target의 평균, 표준편차, skew, median, min, max, count 등을 계산하는 구조가 있다.
다만 실제 preprocessing pipeline에서는 일부만 활성화되어 있다.
cat_stats:
mean, std, skew, median, min, max, count
numerical stats나 nonlinear transform, PCA, LDA 등은 코드에는 있지만 대부분 주석 처리되어 있다.
이 notebook은 실험 template 성격이 강하다. 여러 preprocessing 아이디어를 만들어두고, 필요한 것만 켜서 실험하는 방식이다.
9. 모델 정의
1 SVC
2 RandomForestClassifier
3 KNeighborsClassifier
4 GradientBoostingClassifier
5 XGBoost
6 AdaBoost
7 HistGradientBoostingClassifier
8 LightGBM
9 CatBoost
10 SGDClassifier
11 RealMLP
하지만 실제 실행 설정은 다음과 같다.
EST_IDS = [7]
즉, 실행된 모델은 HistGradientBoostingClassifier 하나이다.
제목만 보면 CatBoost, XGBoost, LightGBM, RealMLP가 모두 학습된 것처럼 보일 수 있지만, 실제 출력 기준으로는 그렇지 않다. -> 여러 모델들을 실험하신거 같다.
10. 실제 학습 결과
HGBC 5-fold 학습 결과는 다음과 같다.
fold 1 val balanced accuracy: 0.964066
fold 2 val balanced accuracy: 0.962459
fold 3 val balanced accuracy: 0.962984
fold 4 val balanced accuracy: 0.962876
fold 5 val balanced accuracy: 0.962383
평균 결과는 다음과 같다.
Average train balanced accuracy: 0.966449
Average val balanced accuracy: 0.962954
OOF balanced accuracy: 0.962954
train score와 validation score 차이는 크지 않다. 과적합이 심해 보이지는 않는다. 다만 우리 실험에서 얻은 LightGBM/CatBoost OOF보다 낮다.
우리 실험 결과와 비교하면 다음과 같다.
이 notebook HGBC OOF: 0.962954
우리 LightGBM OOF: 0.964320
우리 CatBoost OOF: 0.964947
따라서 이 notebook의 모델 결과 자체를 우리 최종 제출 전략으로 가져오기보다는, feature engineering과 subset 분석 방식을 참고하는 것이 더 적절해 보인다.
11. Ensemble Weight 함수의 주의점
notebook에는 여러 estimator의 validation probability를 이용해 balanced accuracy가 가장 좋은 weight를 찾는 함수가 있다.
아이디어 자체는 좋다. OOF probability를 이용해 ensemble weight를 찾고, 같은 weight로 test probability를 섞으려는 구조이다.
하지만 코드상 주의할 부분이 있다.
함수 마지막에서 test blend를 만들 때 best_weights가 아니라 마지막 loop의 weights를 사용하는 것으로 보인다.
수정 방향은 다음과 같다.
test_blend = np.tensordot(best_weights, test_probs, axes=(0, 0))
현재 실행에서는 estimator가 1개라서 실제 영향은 없다. 하지만 여러 모델을 켜서 ensemble할 경우에는 이 부분이 문제가 될 수 있다.
이런 작은 버그는 public score에 직접 영향을 줄 수 있으므로, 다른 사람 코드를 참고할 때도 그대로 복사하기보다 반드시 읽고 확인해야 한다.
12. 그래프 1: 수치 Feature 분포

첫 번째 그래프는 feature engineering 이후 만들어진 numerical feature들의 분포를 한 번에 확인하는 그래프이다. 원본 feature뿐 아니라 색지수, 곡률, redshift interaction, ratio feature, normalized color feature 등이 모두 포함되어 있다.
그래프를 보면 일부 feature는 비교적 정규분포처럼 보이지만, redshift 관련 feature와 interaction feature는 한쪽으로 긴 tail을 가진다. ratio feature나 redshift를 곱한 feature도 분포가 뾰족하거나 치우쳐 보인다. 이는 천체 데이터에서 redshift와 색지수 조합이 특정 구간에 몰려 있다는 뜻으로 볼 수 있다.
다만 subplot 수가 너무 많아서 feature별 의미를 자세히 읽기에는 어렵다. 이 그래프는 class 분리력을 보는 그래프라기보다, feature가 정상적으로 만들어졌는지와 이상하게 튀는 분포가 있는지 확인하는 진단용 그래프에 가깝다.
13. 그래프 2: Train / Validation / Test 분포 비교

두 번째 그래프는 train, validation, test의 feature distribution을 비교하는 그래프이다. 작성자는 fold split이 안정적인지, 그리고 test set이 train 분포와 크게 다른지 확인하려고 한 것으로 보인다.
대부분의 numerical feature에서 train, validation, test 분포가 거의 겹쳐 보인다. 이는 validation split이 train 전체를 어느 정도 잘 대표하고 있고, test set도 큰 분포 차이는 없어 보인다는 뜻이다. 이런 경우 OOF score를 어느 정도 신뢰할 수 있다.
다만 아래쪽 target 그래프에서 test 쪽에 None이 보이는데, 이것은 external data 때문이 아니다. test set에는 class label이 없기 때문에 notebook에서 target column에 None을 넣었기 때문이다.
따라서 이 그래프의 핵심은 external data 분석이 아니라, competition train/validation/test 사이의 distribution shift가 크지 않아 보인다는 점이다.
14. 그래프 3: Categorical Subset별 Balanced Accuracy
spectral_type, galaxy_population 각각의 category에서 모델 성능이 어디서 낮은지 확인한다.

세 번째 그래프는 categorical subset별 balanced accuracy를 전체 평균 OOF score와 비교하려는 의도로 보인다.
즉 `spectral_type_A/F`, `spectral_type_G/K`, `spectral_type_M`, `spectral_type_O/B`, `galaxy_population_Blue_Cloud`, `galaxy_population_Red_Sequence` 같은 subset에서 모델이 어디를 어려워하는지 보려는 그래프이다.
그래프를 보면 `spectral_type_O/B`가 가장 낮은 성능 구간으로 보인다. `spectral_type_M`, `spectral_type_A/F`도 평균보다 낮아 보인다. 반면 `galaxy_population_Blue_Cloud`, `galaxy_population_Red_Sequence`, `spectral_type_G/K`는 평균에 비교적 가까운 쪽으로 보인다.
다만 그래프 구현에는 실수가 있어 보인다. subset score 자체를 막대 길이로 그린 것이 아니라 `subset_score - overall_oof_score`를 막대 너비로 넣고 `left=overall_oof_score`를 지정했다. 그래서 평균보다 낮은 subset은 막대가 왼쪽으로 뒤집혀 표시된다.
작성자의 의도는 subset별 score 막대와 전체 평균 기준선을 함께 보여주는 것이었던 것 같다. 그래프 모양은 이상하지만, 의도 자체는 모델의 약한 category를 찾는 것이다.
15. 그래프 4: Multi-category Worst Subset
spectral_type + galaxy_population 조합 중 어디가 가장 약한가

네 번째 그래프는 `spectral_type + galaxy_population` 조합별 balanced accuracy가 낮은 구간을 찾으려는 그래프이다. 단일 category가 아니라 두 categorical feature의 조합을 본다.
그래프를 보면 `O/B_Blue_Cloud`가 가장 낮은 성능 구간으로 보인다. 그 다음으로 `A/F_Blue_Cloud`, `M_Red_Sequence`, `M_Blue_Cloud`, `O/B_Red_Sequence` 등이 평균보다 낮아 보인다. 즉 모델은 단순히 특정 `spectral_type` 하나에서만 약한 것이 아니라, `spectral_type`과 `galaxy_population` 조합에 따라 어려운 구간이 달라지는 것으로 보인다.
이 분석은 꽤 중요하다. 전체 OOF score만 보면 모델이 어디서 틀리는지 알 수 없지만, 조합 category별로 보면 취약 구간이 드러난다. 특히 우리처럼 row-level patch를 하는 상황에서는 이런 조합별 취약 구간이 후보 탐색에 도움이 될 수 있다.
다만 이 그래프도 세 번째 그래프와 같은 시각화 문제가 있다. score 자체가 아니라 `subset_score - overall_oof_score`를 막대 너비로 사용해서 평균보다 낮은 구간이 왼쪽으로 뒤집혀 보인다. 또한 제목은 worst 10이지만 실제 가능한 조합은 8개뿐이라, 사실상 8개 조합 전체를 낮은 순서로 보여주는 그래프에 가깝다.
16. 이 notebook에서 배울 점
이 notebook에서 가장 배울 만한 부분은 subset별 error analysis이다. 전체 OOF score만 보는 것이 아니라, 어떤 spectral_type과 galaxy_population 조합에서 모델이 약한지 확인하려는 접근이 좋다.
다만 그래프 구현은 직관적이지 않으므로 그대로 가져오기보다는 score와 sample count를 표로 먼저 확인하고, 이후 올바른 bar chart로 다시 그리는 것이 좋다.
17. 아쉬운 점과 주의할 점
아쉬운 점도 있다.
첫째, feature가 매우 많다. feature가 많다고 항상 성능이 좋아지는 것은 아니다. 특히 public/private split이 있는 Kaggle 대회에서는 public LB에 맞춘 feature가 private에서 흔들릴 수 있다.
둘째, ensemble weight 함수에 작은 버그 가능성이 있다. 여러 모델을 켜면 test blend가 best weight가 아닌 마지막 weight로 계산될 수 있다.
18. 우리 연구와 연결할 점
우리 연구에 연결할 부분은 특정 subset 이름을 그대로 믿는 것이 아니라, patch 후보를 subset별로 나누어 보는 방식이다.
현재 우리는 `STAR->GALAXY` 방향 patch가 public LB를 올렸다는 것을 확인했다. 다음에는 이 patch 후보들이 `spectral_type`, `galaxy_population` 조합 중 어디에 몰리는지 확인해야 한다.
예를 들어 `O/B_Blue_Cloud`나 `M_Red_Sequence` 같은 취약 구간에 `STAR->GALAXY` 후보가 많이 몰려 있다면, 그 subset을 따로 probe하는 전략을 세울 수 있다.
19. 마무리
이 notebook은 최고점 solution이라기보다는, 잘 정리된 baseline template와 분석 아이디어를 제공하는 자료에 가깝다.
특히 중요한 점은 subset별 성능 분석이다. 전체 score만 보면 모델의 약점을 알 수 없지만, categorical subset과 조합 subset으로 나누면 어떤 구간에서 모델이 흔들리는지 볼 수 있다.
다만 세 번째와 네 번째 그래프는 구현상 막대가 이상하게 그려졌기 때문에 그대로 해석하면 안 된다. 작성자의 의도는 취약 subset을 찾는 것이고, 이 의도는 우리 row-level patch 전략에도 연결할 수 있다.