티스토리 뷰

반응형

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 값도 정규화를 하는 게 좋겠다는 생각.

이건 이제 베이스라인을 구축하고 나서 생각해봐야 할 것 같다.

 

오늘은 여기까지.

반응형