티스토리 뷰
https://www.kaggle.com/competitions/hms-harmful-brain-activity-classification/overview
이 글을 쓰고 있는 2024년 2월 13일 현재, 캐글에서 진행하고 있는 컴피티션이다.
필자는 본 경진대회를 진행하면서, 실제로 배운 데이터 분석 요소들을 적용해보려고 한다.
글로 써질 것들은 모델을 학습하고, 만들기 까지의 과정이다.
오늘 시간에는 피처 엔지니어링을 진행한다.
분석 결과 정리
일단 여태까지의 분석 요소들을 종합해본다.
분석 결과1 : id의 종속관계에 대한 가설 수립 및 일치화
따라서 id를 통한 그룹화로 타깃값 분포 보기
시간에 따라 나열되어 있으므로, 시계열 데이터.
그리고 vote에 대한 인사이트 수립
결측치 없음
위의 결과 1을 통해 EDA로 분석 결과 2를 도출했다.
id의 종속관계에 대한 가설 수립 및 일치화 ->순서가 없고, 정규분포를 따르지 않으므로 가설 기각
시간에 따라 나열되어 있으므로, 시계열 데이터.-> 대립가설 채택.(시계열 데이터인 sub_id와 second)
그리고 vote에 대한 인사이트 수립 -> 분석결과 3에서 실행
따라서
분석 결과2: label_id는 제거. 나머지 피처는 일단 전부 모델링 고려
eeg_sub_id, eeg_second와 spectogram_sub_id, spectogram _second는 시계열 데이터로 활용
추가로 다른 사람의 EDA를 참고하며, vote 사이의 관계성도 탐구했다.
분석 결과 3 : vote의 분포에서는 각자 0이 많다.
vote 사이의 pair plot을 그려본 결과, 어느정도 반비례하는 선형성을 띈다는 걸 알 수 있었다.
vote에서는 이상치가 조금 보이므로, 정규성을 확보해주기 위해 정규화가 필요하다.
offset second는 두 변수 다 왜도가 너무 치우쳐져 있다.
->vote는 전체를 합한 값으로 0~1 사이로 만든다.
분석 결과4 : eeg에 대해서 중복을 피해야 한다. patient_id에 대해서는 순차화 필요.
->eeg를 groupby로 묶고, 나머지 열들에 대해서 진행
이를 바탕으로 피처 엔지니어링을 진행한다.
피처 엔지니어링 1: eeg로 묶기
분석 결과 중 eeg로 묶는 걸 가장 먼저 진행한다.
이렇게 진행하는 이유는 간단하다. EDA 과정에서, eeg 그룹으로 묶인 LB 값이 가장 높게 나왔기 때문.테스트 데이터에서는 EEG의 중복이 없다는 뜻이다.
즉, train의 고유값 개수인 17089개로 일단 훈련을 진행한다.
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings(action='ignore') # 경고 메시지 생략
df=pd.read_csv('/kaggle/input/hms-harmful-brain-activity-classification/train.csv')
test=pd.read_csv('/kaggle/input/hms-harmful-brain-activity-classification/test.csv')
submission=pd.read_csv('/kaggle/input/hms-harmful-brain-activity-classification/sample_submission.csv')
TARGETS = df.columns[-6:]
df.head()
일단 위에서 코드를 불러온다.
train = df.groupby('eeg_id')[['spectrogram_id','spectrogram_label_offset_seconds']].agg(
{'spectrogram_id':'first','spectrogram_label_offset_seconds':'min'})
print(train)
train.columns = ['spec_id','min']
tmp = df.groupby('eeg_id')[['spectrogram_id','spectrogram_label_offset_seconds']].agg(
{'spectrogram_label_offset_seconds':'max'})
train['max'] = tmp
tmp = df.groupby('eeg_id')[['patient_id']].agg('first')
train['patient_id'] = tmp
print(tmp)
여기서 그룹으로 묶는데, 가장 먼저 eeg_id로 묶는다.
그러면 eeg_id로 묶인 17089개*14열의 행 데이터 중에서, sepctrogram_id와 offset_seconds라는 열을 각각 시리즈로 가져오고, agg를 통해 spectrogram_id는 첫번째 값(first), offset_seconds는 min 값을 가져온다.
그럼 결과가 이렇게 나온다.
spectrogram_id spectrogram_label_offset_seconds
eeg_id
568657 789577333 0.0
582999 1552638400 0.0
642382 14960202 1008.0
751790 618728447 908.0
778705 52296320 0.0
... ... ...
4293354003 1188113564 0.0
4293843368 1549502620 0.0
4294455489 2105480289 0.0
4294858825 657299228 0.0
4294958358 260520016 2508.0
[17089 rows x 2 columns]
patient_id
eeg_id
568657 20654
582999 20230
642382 5955
751790 38549
778705 40955
... ...
4293354003 16610
4293843368 15065
4294455489 56
4294858825 4312
4294958358 25986
[17089 rows x 1 columns]
그리고 나서, 위의 컬럼 이름을 spec_id와 min으로 바꿔준다.
그런 후에, 이렇게 나온 train을 베이스로 뒤에 tmp를 추가하는데, offset max와 paitient id를 각각 추가한다.
paitient id도 first 값만 추가해준다.
이렇게 하는 이유는, eeg 아이디는 여러개여도 spectro_id, paitient id는 eeg_id별로 하나다. 즉, 모델 학습에 있어서 중복을 피하기가 수월하다.
피처 엔지니어링 2: vote 정규화
그런 후에는 이제 vote를 정규화 해줄 차례이다.
eeg_id로 묶은 각 행에서의 vote의 수를 합하고, 그 값을 각 열마다 나눠주면 된다.
tmp = df.groupby('eeg_id')[TARGETS].agg('sum')
for t in TARGETS:
train[t] = tmp[t].values
y_data = train[TARGETS].values
y_data = y_data / y_data.sum(axis=1,keepdims=True)
train[TARGETS] = y_data
tmp = df.groupby('eeg_id')[['expert_consensus']].agg('first')
train['target'] = tmp
train = train.reset_index()
print('Train non-overlapp eeg_id shape:', train.shape )
train.head()
파생 피처로 TARGETS에 대한 sum 피처를 일단 만들어준다. 그러면 eeg_id 별, 각 vote에서의 sum을 알려준다.
이 값을 일단 train[t]로 하여 tmp[t].values로 각 열에 추가한다.(열 생성)
seizure_vote lpd_vote gpd_vote lrda_vote grda_vote other_vote
eeg_id
568657 0 0 12 0 8 28
582999 0 132 0 11 0 11
642382 0 0 0 0 0 2
751790 0 0 1 0 0 0
778705 0 0 0 0 0 2
... ... ... ... ... ... ...
4293354003 0 0 0 0 1 1
4293843368 0 0 0 0 1 1
4294455489 0 0 0 0 0 1
4294858825 0 0 0 0 5 70
4294958358 0 0 0 0 0 2
그 후에는 y_data = train[TARGETS].values를 하는데, 이렇게 하는 이유는 sum을 해줘서 따로 나눠주기 위함이다.
y_data.sum(axis=1, keepdims=True)를 하게 되면, 열의 합을 하는데 keepdims로 인해 브로드캐스팅 문제(차원이 맞지 않는 문제)를 해결할 수 있다.
왜냐면 y_data는 17089*6 행렬이고, y_data를 행에 대해 sum 하면 17089*1, 즉 1차원으로 압축된다. 근데 이 값의 실제 결과는 (17089,). 즉, 1차원이 죽은 차원이 되므로 브로드캐스팅 문제에 직면한다.
연산을 하려면 계속 17089*1 행렬이어야 한다. 그래서 keepdims를 하면 여기선 axis 1이므로 각 행에 대한 차원은 유지되고, 연산 값은 전체의 합으로 들어가게 된다.
따라서 그렇게 구한 값을 나눠주면? 정규화와 똑같다.
그렇게 구한 값을 다시 train[TARGETS]에 부여해준다.
그 후에는 eeg_id별 consensus까지 대입해주면 완벽
가장 기초적인 피처 엔지니어링 세트가 완성되었다.
eeg_id spec_id min max patient_id seizure_vote lpd_vote gpd_vote lrda_vote grda_vote other_vote target
0 568657 789577333 0.0 16.0 20654 0.0 0.000000 0.25 0.000000 0.166667 0.583333 Other
1 582999 1552638400 0.0 38.0 20230 0.0 0.857143 0.00 0.071429 0.000000 0.071429 LPD
2 642382 14960202 1008.0 1032.0 5955 0.0 0.000000 0.00 0.000000 0.000000 1.000000 Other
3 751790 618728447 908.0 908.0 38549 0.0 0.000000 1.00 0.000000 0.000000 0.000000 GPD
4 778705 52296320 0.0 0.0 40955 0.0 0.000000 0.00 0.000000 0.000000 1.000000 Other
... ... ... ... ... ... ... ... ... ... ... ... ...
17084 4293354003 1188113564 0.0 0.0 16610 0.0 0.000000 0.00 0.000000 0.500000 0.500000 GRDA
17085 4293843368 1549502620 0.0 0.0 15065 0.0 0.000000 0.00 0.000000 0.500000 0.500000 GRDA
17086 4294455489 2105480289 0.0 0.0 56 0.0 0.000000 0.00 0.000000 0.000000 1.000000 Other
17087 4294858825 657299228 0.0 12.0 4312 0.0 0.000000 0.00 0.000000 0.066667 0.933333 Other
17088 4294958358 260520016 2508.0 2508.0 25986 0.0 0.000000 0.00 0.000000 0.000000 1.000000 Other
분석 결과대로, eeg_id로 묶어주었고, vote는 정규화, label은 없애버렸다.
이렇게 비교분석된 데이터 셋을 가지고 describe()를 해보면,
eeg_id spec_id min max patient_id seizure_vote lpd_vote gpd_vote lrda_vote grda_vote other_vote
count 1.708900e+04 1.708900e+04 17089.000000 17089.000000 17089.000000 17089.000000 17089.000000 17089.000000 17089.000000 17089.000000 17089.000000
mean 2.135226e+09 1.080640e+09 401.650711 431.761191 32839.981977 0.152810 0.142456 0.104062 0.065407 0.114851 0.420413
std 1.235712e+09 6.251739e+08 1226.839779 1232.863269 18351.751174 0.331563 0.295541 0.258825 0.187005 0.271425 0.418454
min 5.686570e+05 3.537330e+05 0.000000 0.000000 56.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 1.062096e+09 5.396648e+08 0.000000 4.000000 17408.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
50% 2.123560e+09 1.073264e+09 0.000000 40.000000 32068.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.333333
75% 3.208261e+09 1.641428e+09 308.000000 346.000000 48272.000000 0.000000 0.068966 0.000000 0.000000 0.000000 0.941176
max 4.294958e+09 2.147388e+09 17556.000000 17632.000000 65494.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
id들은 범주화 취급해야 하므로 넘어가고, min이나 max 값에 대해서도 비교적 깔끔하게 나타나는 걸 알 수 있다.
조금 아쉬운 감이 있다면, min,max 값도 정규화를 하는 게 좋겠다는 생각.
이건 이제 베이스라인을 구축하고 나서 생각해봐야 할 것 같다.
오늘은 여기까지.