SJ_Koding

01. [Dacon basic], 펭귄 몸무게 예측 경진대회 참가 코드[최종 26위 / 725명, private score : 308.10401(RMSE)] 본문

AI Competition

01. [Dacon basic], 펭귄 몸무게 예측 경진대회 참가 코드[최종 26위 / 725명, private score : 308.10401(RMSE)]

성지코딩 2022. 1. 6. 15:52

저는 오늘 처음으로 Dacon AI경진대회를 참가해보았습니다.

주제는 펭귄 몸무게 예측으로, 머신러닝 입문자를 위한 Basic 대회입니다. (작성일 기준, 저한테 맞는 수준입니다 ㅎ)

https://dacon.io/competitions/official/235862/overview/description

 

펭귄 몸무게 예측 경진대회 - DACON

좋아요는 1분 내에 한 번만 클릭 할 수 있습니다.

dacon.io

구글 코랩에서 CPU환경으로 진행을 하였으며 skleran 모듈을 이용하여 모델을 구성했습니다.

또한 평가 지표로 RMSE를 사용합니다. Mean Squared Error에 Root를 씌운 값으로 평가를 진행합니다.

 

데이터는 3개의 csv파일로 주어집니다.

1. train.csv : 학습 데이터

  • id : 샘플 아이디
  • Species: 펭귄의 종을 나타내는 문자열
  • Island : 샘플들이 수집된 Palmer Station 근처 섬 이름
  • Clutch Completion : 관찰된 펭귄 둥지의 알이 2개인 경우 Full Clutch이며 Yes로 표기
  • Culmen Length (mm) : 펭귄 옆모습 기준 부리의 가로 길이
  • Culmen Depth (mm) : 펭귄 옆모습 기준 부리의 세로 길이
  • Flipper Length (mm) : 펭귄의 팔(날개) 길이
  • Sex : 펭귄의 성별
  • Delta 15 N (o/oo) : 토양에 따라 변화하는 안정 동위원소 15N:14N의 비율
  • Delta 13 C (o/oo) : 먹이에 따라 변화하는 안정 동위원소 13C:12C의 비율
  • Body Mass (g): 펭귄의 몸무게를 나타내는 숫자 (g)

2. test.csv : 테스트 데이터

  • id : 샘플 아이디
  • Species: 펭귄의 종을 나타내는 문자열
  • Island : 샘플들이 수집된 Palmer Station 근처 섬 이름
  • Clutch Completion : 관찰된 펭귄 둥지의 알이 2개인 경우 Full Clutch이며 Yes로 표기
  • Culmen Length (mm) : 펭귄 옆모습 기준 부리의 가로 길이
  • Culmen Depth (mm) : 펭귄 옆모습 기준 부리의 세로 길이
  • Flipper Length (mm) : 펭귄의 팔(날개) 길이
  • Sex : 펭귄의 성별
  • Delta 15 N (o/oo) : 토양에 따라 변화하는 안정 동위원소 15N:14N의 비율
  • Delta 13 C (o/oo) : 먹이에 따라 변화하는 안정 동위원소 13C:12C의 비율

3. sample_submissoin.csv : 제출 양식

  • id : 샘플 아이디
  • Body Mass (g) : 펭귄의 몸무게를 나타내는 숫자 (g)

데이터 상세 설명

  • 성인 Adélie, Chinstrap 및 Gentoo 펭귄의 둥지 관찰, 펭귄 크기 데이터 및 혈액 샘플의 동위원소 측정을 포함한 데이터입니다.
  • 남극의 Palmer Station 근처 Palmer 군도에 있는 섬에서 관찰된 Adélie, chinstrap, Gentoo 펭귄의 먹이를 찾는 성체의 크기 측정, 클러치 관찰 및 혈액 동위원소 비율.
  • 데이터는 Kristen Gor man 박사와 Palmer Station Long Term Ecological Research(LTER) 프로그램에 의해 수집되어 제공되었습니다.
  • 출처: https://allisonhorst.github.io/palmerpenguins/

 

코드 설명


1. 데이터 로드 및 결측치 확인

먼저 df.read_csv() 함수를 통해 데이터를 DataFrame으로 변환시킵니다.

구글 Colab 환경에서 진행을 했기 때문에, Google Drive Mount를 통해 데이터를 저장하고 불러왔습니다. 따라서 경로는 알맞게 수정해주어야 합니다.

import pandas as pd 
df_train = pd.read_csv('/content/drive/MyDrive/dacon/penguin_weight_predict/train.csv')
df_test = pd.read_csv('/content/drive/MyDrive/dacon/penguin_weight_predict/test.csv')
df_train.info(), df_test.info()

출력결과

일단 info()함수를 통해 데이터를 조회했을 때, non-null Count가 다른 부분이 있다면, 결측치 (NaN)값이 존재한다는 것을 확인할 수 있습니다.

 

결측치가 몇 개인지 조회하려면 isna()sum()함수를 사용합니다. 그리고 그 전에 id컬럼을 제거하라는 공지가 있어 id컬럼을 삭제하는 작업도 진행합니다. (df_train과 df_test 둘 다 적용시켜줍니다.)

df_train.drop('id', axis=1, inplace = True) # id는 제외하고 분석
df_test.drop('id', axis=1, inplace = True)
print(f'df_train 결측치 조회\n{df_train.isna().sum()}') 
print(f'df_test 결측치 조회\n{df_test.isna().sum()}')

출력 결과

출력 결과를 보면, 'Sex', 'Delta 15 N (o/oo)', 'Delta 13 C (o/oo)' 에 대하여 결측치가 존재하는 것을 볼 수 있습니다.

train셋에는 총 9개, test셋에는 총 23개의 결측치가 존재하는 것을 확인했습니다.

 

2. 데이터 결측치 처리 및 범주형 데이터 처리 (pd.get_dummies())

머신러닝에서 결측치는 항상 처리해주어야합니다. 저는 'Delta @'에 대해서는 평균값 대체, 'Sex'에 대해 행 삭제 기법을 사용하였습니다.

df_train.fillna(df_train.mean(), inplace=True)
df_test.fillna(df_train.mean(), inplace=True)

temp_train = df_train.dropna()
temp_test = df_test.dropna()

 

코드를 보면 해당 결측치가 존재하는 컬럼에 mean값을 채워주어 결측치를 처리하고

그 후에 결측치의 행 (axis = 0)을 제거하면 어차피 'Sex' 컬럼의 결측치만 남아있는 상태로 3개의 행이 삭제될 것입니다.

따라서 출력결과를 보면 결측치가 더 이상 존재하지 않습니다.

 

test셋에서도 동일하게 적용 하지만 'Sex'컬럼에 대한 행 삭제를 진행하지 않았습니다. 앞서 설명드린 데이터 중 sample_submission.csv파일의 행 개수와 달라지기 때문에 평가를 진행할 수 없었습니다. 따라서 'Sex'가 결측치 인 경우 모두 'MALE'로 변환시켜주었습니다. 'Sex'에 따른 몸무게는 충분히 의미가 있을 거라 생각했습니다. 따라서 보간법의 개념을 추가하였습니다. 

X_train_Sex = temp_train.select_dtypes(include=['int', 'float'])
X_train_Sex = X_train_Sex.drop('Body Mass (g)', axis=1)
Y_train_Sex = temp_train['Sex']

from sklearn.model_selection import train_test_split

sex_x_train, sex_x_test, sex_y_train, sex_y_test = train_test_split(X_train_Sex, Y_train_Sex
)
from sklearn.ensemble import RandomForestClassifier
model_sex = RandomForestClassifier().fit(sex_x_train, sex_y_train)
model_sex.score(sex_x_test, sex_y_test)

0.8571428571428571

df_train.loc[df_train['Sex'].isna(), 'Sex'] = \
model_sex.predict(df_train[df_train['Sex'].isna()].select_dtypes(include=['int', 'float']).drop('Body Mass (g)', axis=1))

df_test.loc[df_test['Sex'].isna(), 'Sex'] = \
model_sex.predict(df_test[df_test['Sex'].isna()].select_dtypes(include=['int', 'float']))

한 줄로 동시에 쓰느랴 복잡해 보이지만, 'Sex'에 대해 학습된 model_sex의 predict를 사용하여 값을 가져와 대입시켜주었습니다. 여기서 select_dtypes는 지정된 열의 타입만 가져오게 됩니다. 저는 int형과 float형을 추출하였고, test셋에는 'Body Mass (g)' 컬럼이 없기 때문에, 이를 포함하여 학습을 시키면 안됩니다. 따라서 drop을 통해 해당 열을 제거하였습니다.

temp_train.loc[df_train['Sex'] == 'MALE', 'Sex'] = 0
temp_train.loc[df_train['Sex'] == 'FEMALE', 'Sex'] = 1

그 후, 성별에 대해 'MALE'은 0, 'FEMALE'은 1로 지정하였습니다.

df_train = pd.get_dummies(df_train)
df_test = pd.get_dummies(df_test)

pd.get_dummies함수는 정형 데이터에 대해 자동으로 One-Hot인코딩을 진행해주는 똑똑한 함수입니다.

 

X = df_train.drop('Body Mass (g)', axis=1) 
Y = df_train['Body Mass (g)']

X 출력

자동으로 원 핫 인코딩이 잘 된 모습

Y 출력

3. 데이터 분할 및 여러 개의 모델 학습, 선택

from sklearn.neighbors import KNeighborsRegressor
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.1)

모델의 선택은 시행착오라고 배웠습니다. 다양한 모델의 성능을 확인하고 모델을 선택해보겠습니다.

 

model = KNeighborsRegressor(n_neighbors=5).fit(x_train, y_train)
score_List.append(f'KNeighborsRegressor: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = LinearRegression().fit(x_train, y_train)
score_List.append(f'LinearRegression: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = Ridge().fit(x_train, y_train)
score_List.append(f'Ridge: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = Lasso().fit(x_train, y_train)
score_List.append(f'Lasso: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = DecisionTreeRegressor().fit(x_train, y_train)
score_List.append(f'DecisionTreeRegressor: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = RandomForestRegressor().fit(x_train, y_train)
score_List.append(f'RandomForestRegressor: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = GradientBoostingRegressor().fit(x_train, y_train)
score_List.append(f'GradientBoostingRegressor: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = XGBRegressor().fit(x_train, y_train)
score_List.append(f'XGBRegressor: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')

model = LGBMRegressor().fit(x_train, y_train)
score_List.append(f'LGBMRegressor: train_score: {model.score(x_train, y_train)}, val_score: {model.score(x_test, y_test)}')
[print(i) for i in score_List]

train_score가 아닌 val_score를 봐주셔야합니다.

LinearRegression, Ridge, Lasso, LGBMRegressor 등등이 성능이 좋게 나온 것을 확인할 수 있었습니다.

 

4. 데이터 앙상블 (LinearRegression, Ridge, Lasso)

model_LR = LinearRegression().fit(X, Y)
model_RID = Ridge().fit(X, Y)
model_LA = Lasso().fit(X, Y)
model_LGBM = LGBMRegressor().fit(X, Y)
model_XGB = XGBRegressor().fit(X, Y)

제가 선택한 모델 5개에 대하여, 분할된 셋이 아닌 기존 셋을 통해 모델 학습을 진행해 줍니다.

pred_LR = model_LR.predict(df_test)
pred_RID = model_RID.predict(df_test)
pred_LA = model_LA.predict(df_test)
pred_LGBM = model_LGBM.predict(df_test)
pred_XGB = model_XGB.predict(df_test)
pred = pred_LR*0.2 + pred_RID*0.2 + pred_LA*0.2 + pred_LGBM*0.2 + pred_XGB*0.2

그 후 predict의 값을 구하고, 적절한 비율로 곱한 후 모두 더함으로써 모델 앙상블을 진행하였습니다.

 

submission = pd.read_csv('/content/drive/MyDrive/dacon/penguin_weight_predict/submission.csv')
submission['Body Mass (g)'] = pred

submission.to_csv('/content/drive/MyDrive/dacon/penguin_weight_predict/submission.csv', index=False)
submission

그 후 submission에 대입하고 사이트에 제출하였습니다.

스코어는 275.71371이 나왔으며 684명중 18위를 기록했습니다.

첫 AI경진대회를 참가해보았습니다. Basic난이도였기에 가능했습니다. 하지만 앞으로 발전하기 위한 첫 걸음이므로 큰 의미가 있다고 생각합니다.

 

감사합니다.

-sjkoding-