본문 바로가기
Etc/Kaggle & DACON

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

by VAMOSSS 2022. 1. 11.
반응형

DACON - Penguin Body Mass

▶ DACON - Penguin Codeshare

 

2021.12.27 ~ 2022.01.07에 진행된 DACON 펭귄 몸무게 예측 경진대회 참가 후기이다.

머신러닝 입문, 데이터 분석 연습용으로 만들어진 대회라 데이터의 크기가 그렇게 크지 않았다.

실제 데이터에 적용하면서 연습하려고 참가하였고, 개인적으로 이것저것 많이 시도해보려고했다.

대회가 끝나고 다른 분들이 다양한 코드를 공유해주셔서 참고해서 보안했지만 여기서는 내가 한 방식에 대해서 작성해보려한다.

아래에 참고하면 좋을 다른 분들의 코드를 공유해주신 링크들을 첨부해놓겠다.

이 대회는 평가지표로 RMSE를 사용했다.

 

결과적으로 나는 대회종료 후 RMSE : 272.31852 로 725명 중 12등으로 기록되어있다.

대회 링크는 아래에 첨부해놓겠다.

Data

  • train.csv
  • test.csv
  • sample_submission.csv

train data를 사용해서 모델을 구현하고 test data로 예측하여 submission에 담가 제출하는 형식의 대회이다.

train.csv의 Feature는 아래와 같았고 "Body Mass (g)"가 Target이다.

자세한 설명은 DACON을 참고하시면 좋을 것 같다.

  • 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)

 

저의 자세한 코드는 아래의 Github을 참고하시면 됩니다!

 

Load Data

train, test data를 load하고 head(), info(), describe()를 사용하여 간단하게 데이터를 살펴보았다.

'id' columns은 데이터의 순서를 의미하므로 제거하고 진행하였다.

df_train = pd.read_csv('./data/train.csv')
df_test = pd.read_csv('./data/test.csv')

# id 제거
df_train.drop(['id'], axis=1, inplace=True)
df_test.drop(['id'], axis=1, inplace=True)

df_train.info()

 

EDA

전체적으로 Null data(결측값)이 존재하는지 살펴보았다.

train, test data 둘 다 'Sex', 'Delta 15 N (o/oo)', 'Delta 13 C (o/oo)'에 결측값이 존재하였다.

# train data
print('\nNull Data Count\n')
for i,cnt in enumerate(df_train.isnull().sum()):
    if cnt > 0:
        print("{} : {}".format(df_train.columns[i],cnt))
        
# test data
print('\nNull Data Count\n')
for i,cnt in enumerate(df_test.isnull().sum()):
    if cnt > 0:
        print("{} : {}".format(df_test.columns[i],cnt))

Target인 'Body Mass (g)' column의 통계량을 확인하고, 시각화를 해보았다.

이상치도 특히 존재하지 않았고, 편향되어있다고 볼 수도 없었다.

plt.figure(figsize=(14,14))

plt.subplot(2,2,1)
plt.hist(df_train['Body Mass (g)'])
plt.title('Histogram - Body Mass (g)', fontdict={'fontsize':20})
plt.ylabel('Count')

plt.subplot(2,2,2)
plt.boxplot(df_train['Body Mass (g)'])
plt.title('Boxplot - Body Mass (g)', fontdict={'fontsize':20})

plt.subplot(2,2,3)
sns.kdeplot(df_train['Body Mass (g)'])
plt.title('KDE - Body Mass (g)', fontdict={'fontsize':20})

plt.subplot(2,2,4)
sns.distplot(df_train['Body Mass (g)'],
                   color='b',
                   label='Skewness : {:.2f}'.format(df_train['Body Mass (g)'].skew()))
plt.legend(loc='best')
plt.title('Distplot - Body Mass (g)', fontdict={'fontsize':20})

plt.show()

다른 Feature들에 대해서도 통계량과 시각화를 통해 데이터를 살펴보았다.

sns.distplot, sns.boxplot, sns.countplot, plot(kind='kde'), sns.violinplot 등의 plot을 사용했다.

 

'Clutch Completion' Feature는 Yes가 전체의 90%이다.

plt.figure(figsize=(7,7))
sns.countplot(df_train['Clutch Completion'])
plt.title("Countplot - Clutch Completion", fontsize=20)
plt.show()

 

각 Feature끼리 관련이 있어보이는 것들은 산점도를 찍어보기도 하고, 각 범주로 그룹화하여 살펴보기도 했다.

 

Feature 간의 correlation을 확인한 heatmap code다.

from sklearn.preprocessing import LabelEncoder

# 상관계수 계산을 위해 텍스트 형식의 데이터를 숫자로 변환

corr_df = df_train.copy()
corr_df[categorical_feature] = corr_df[categorical_feature].astype(str).apply(LabelEncoder().fit_transform)

# 상관관계 분석도
plt.figure(figsize=(14,7))

heat_table = corr_df.corr()
mask = np.zeros_like(heat_table)
mask[np.triu_indices_from(mask)] = True

heatmap_ax = sns.heatmap(heat_table, annot=True, mask=mask, cmap='coolwarm')
heatmap_ax.set_xticklabels(heatmap_ax.get_xticklabels(), fontsize=15, rotation=45)
heatmap_ax.set_yticklabels(heatmap_ax.get_yticklabels(), fontsize=15)
plt.title("correlation between features", fontsize=40)

plt.show()

 

Feature Engineering

  • Fill Null data : 결측값 채우기
  • One-hot Encoding
  • Drop columns

결측치는 모델링을 통해 예측하여 채웠다. 

# 결측치를 채우기 전에 일단 범주형 데이터를 숫자형 데이터로 처리

## Sex
df_train['Sex'] = df_train['Sex'].map({'MALE':1,
                                      'FEMALE':2})
df_test['Sex'] = df_test['Sex'].map({'MALE':1,
                                      'FEMALE':2})
                                      
df_train = pd.get_dummies(df_train, columns=['Species','Island'], prefix=['Species','Island'])
df_test = pd.get_dummies(df_test, columns=['Species','Island'], prefix=['Species','Island'])  

# Clutch Completion 포함 X
sex_feature = ['Culmen Length (mm)', 'Culmen Depth (mm)','Flipper Length (mm)',
               'Species_Adelie Penguin (Pygoscelis adeliae)','Species_Chinstrap penguin (Pygoscelis antarctica)',
               'Species_Gentoo penguin (Pygoscelis papua)', 'Island_Biscoe','Island_Dream', 'Island_Torgersen']

sex_model = AdaBoostClassifier()
sex_model.fit(df_train[sex_feature].iloc[df_train['Sex'].dropna().index], df_train['Sex'].iloc[df_train['Sex'].dropna().index])
df_train['Sex'].iloc[np.where(df_train['Sex'].isnull()==True)] = sex_model.predict(df_train[df_train['Sex'].isnull()][sex_feature])
df_test['Sex'].iloc[np.where(df_test['Sex'].isnull()==True)] = sex_model.predict(df_test[df_test['Sex'].isnull()][sex_feature])
## Delta 15 N

# Sex도 포함
Delta_feature = ['Culmen Length (mm)', 'Culmen Depth (mm)','Flipper Length (mm)',
               'Species_Adelie Penguin (Pygoscelis adeliae)','Species_Chinstrap penguin (Pygoscelis antarctica)',
               'Species_Gentoo penguin (Pygoscelis papua)', 'Island_Biscoe','Island_Dream', 'Island_Torgersen', 'Sex']

d15_model = AdaBoostRegressor()
d15_model.fit(df_train[Delta_feature].iloc[df_train['Delta 15 N (o/oo)'].dropna().index],
             df_train['Delta 15 N (o/oo)'].iloc[df_train['Delta 15 N (o/oo)'].dropna().index])
df_train['Delta 15 N (o/oo)'].iloc[np.where(df_train['Delta 15 N (o/oo)'].isnull()==True)] = d15_model.predict(df_train[df_train['Delta 15 N (o/oo)'].isnull()][Delta_feature])
df_test['Delta 15 N (o/oo)'].iloc[np.where(df_test['Delta 15 N (o/oo)'].isnull()==True)] = d15_model.predict(df_test[df_test['Delta 15 N (o/oo)'].isnull()][Delta_feature])
## Delta 13 C

d13_model = LinearRegression()

d13_model.fit(df_train[Delta_feature].iloc[df_train['Delta 13 C (o/oo)'].dropna().index],
             df_train['Delta 13 C (o/oo)'].iloc[df_train['Delta 13 C (o/oo)'].dropna().index])
df_train['Delta 13 C (o/oo)'].iloc[np.where(df_train['Delta 13 C (o/oo)'].isnull()==True)] = d13_model.predict(df_train[df_train['Delta 13 C (o/oo)'].isnull()][Delta_feature])
df_test['Delta 13 C (o/oo)'].iloc[np.where(df_test['Delta 13 C (o/oo)'].isnull()==True)] = d13_model.predict(df_test[df_test['Delta 13 C (o/oo)'].isnull()][Delta_feature])

 

Categorical Features에 One-hot Encoding을 적용하였다.

범주형 Feature의 값들 사이에 수치적인 관계가 없으므로 Label Encoding를 적용하지 않았다.

 

EDA에서 봤듯이 'Clutch Completion' Feature는 너무 편향되어있어 drop 하고 진행하였다.

 

Modeling

  • LinearRegression
  • Lasso
  • Ridge
  • ElasticNet
  • RandomForest
  • LightGBM
  • XGBoost

모델에 대한 이해가 아직 많이 부족하여 다양한 회귀 모델에 대해 적합해보고 RMSE를 확인하였다.

K-Fold Validation을 활용하여 진행하였다.

 

다양한 모델을 사용해서 제출을 해보았을 때, LinearRegression, Lasso, Ridge, LightGBM, XGBoost에 가중치를 각각 0.2씩 Ensemble하였을 때 Public 점수가 가장 높게 나왔다.

모델 선택에 대해서 더 많은 공부의 필요성을 많이 느꼈다.

Hyperparameter tuning을 진행하였다면 더 좋은 결과를 낼 수 있었을 것 같다.

 

LinearRegression의 코드만 첨부하겠다.

다른 모델에 대한 코드는 제 Github 참고해주세요!

def RMSE(true, pred):
    score = np.sqrt(np.mean(np.square(true-pred)))
    return score
X_train = df_train.drop('Body Mass (g)', axis=1).values
target = df_train['Body Mass (g)'].values

X_test = df_test.values

model_rmse = dict()
feature = X_train

lr = LinearRegression()

kfold = KFold(n_splits=5)

cv_rmse = [] # 각 cv회차의 rmse 점수를 계산하여 넣어줄 리스트를 생성. 이후 RMSE값의 평균을 구하기 위해 사용.
n_iter =0 # 반복 횟수 값을 초기 설정.

for train_idx, test_idx in kfold.split(feature):
    x_train, x_test = feature[train_idx], feature[test_idx]
    y_train, y_test = target[train_idx], target[test_idx]
    
    lr = lr.fit(x_train, y_train)
    pred = lr.predict(x_test)
    n_iter += 1
    
    error = RMSE(y_test, pred)
    train_size = x_train.shape[0]
    test_size = x_test.shape[0]
    
    print('\n{0}번째 교차 검증 RMSE : {1}, 학습 데이터 크기 : {2}, 검증 데이터 크기 : {3}'.format(n_iter, error, train_size, test_size))
    
    cv_rmse.append(error)
    
print('\n ==> 이 방정식의 평균 에러(RMSE)는 {} 입니다.'.format(np.mean(cv_rmse)))
model_rmse['LinearRegression'] = np.mean(cv_rmse)
lr = LinearRegression()
lr = lr.fit(X_train, target)
y_pred_lr = lr.predict(X_test)
# submission

submission_lr = pd.read_csv('./data/sample_submission.csv')
submission_lr['Body Mass (g)'] = y_pred_lr

submission_lr.to_csv("./data/submission_lr.csv", index=False)

 

다른 분들 진행 방식

대회가 종료되고 다른 분들의 코드를 살펴보며 나와 다른 방식에 대해 참고하여 코드를 수정해보았다.

다른 분들의 진행방식 중 인상깊었던 몇가지에 대해 추가적으로 적어보려한다.

  • One-hot Encoding을 진행하시고 Gentoo펭귄이 다른 펭귄들보다 좀 무거운 종이라 생각해서 Species가 Gentoo인 펭귄만(Species_Gentoo) 1에서 1.337로 바꿔준 분이 계셨다. - Link
  • 근거에 대해서 잘 풀어주셨다. [Public 3등] - Link

 

Reference

 DACON - Penguin

 

 DACON_Penguin - Park-taenam

반응형

댓글