[DACON] 펭귄 몸무게 예측 경진대회
from google.colab import drive
drive.mount('/content/drive')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
import warnings
warnings.filterwarnings("ignore")
path = '/content/drive/MyDrive/Penguin/'
train = pd.read_csv(path + 'train.csv')
test = pd.read_csv(path + 'test.csv')
sample_submission = pd.read_csv(path + 'sample_submission.csv')
train.head()
혹시 이 코드를 그대로 사용하신다면 path만 파일이 저장되어있는 주소로 바꾸시면 됩니다.
print(train.shape)
print(test.shape)
우선 id포함 변수 개수가 10개이며 데이터 개수가 114, 228개로 다른 대회에 비해 적은 것을 알 수 있어요.
복잡한 모델을 사용해 과적합이 되는 것을 많이 조심해야겠습니다.
또 하나 특이한 점은 테스트 데이터가 약 2배 더 많네요.
train['Species'].value_counts()
train['Island'].value_counts()
train['Sex'].value_counts()
train.drop(['id'], axis = 1, inplace = True)
test.drop(['id'], axis = 1, inplace = True)
discrete_names = ['Species', 'Island', 'Clutch Completion', 'Sex']
continuous_names = ['Culmen Length (mm)', 'Culmen Depth (mm)', 'Flipper Length (mm)', 'Delta 15 N (o/oo)', 'Delta 13 C (o/oo)']
id : 데이터를 구분하기 위한 고유값, Species : 펭귄의 종(총 3개 종이 있음), Island : 샘플이 수집된 근처 섬(총 3개 섬이 있음)
Clutch Completion : 팽귄 둥지 알 개수가 2개인 경우 Yes, Culmen Length/Culmen Depth : 펭귄 부리의 가로/세로 길이
Flipper Length : 펭귄 날개 길이, Sex : 펭귄 성별, Delta 15 N : 토양과 관련된 동위원소 비율, Delta 13 C : 먹이에 관련된 동위원소 비율
id는 당연히 의미가 없으니 제외하고 Species/Island/Sex은 이산형 변수, 나머지 값은 연속형 변수 입니다.
train.info()
train[train['Sex'].isnull()]
train[train['Delta 15 N (o/oo)'].isnull()]
우선 변수들의 결측치 여부를 info 함수를 이용해서 알아봤습니다.
3개 행에서 Sex와 2개의 Delta 변수가 결측치로 관측되었습니다.
6,8,70 번 관측치가 Sex변수가 NULL 값이고 8, 18, 109번 관측치가 2개의 Delta 변수에 대해서 NULL 값이 나왔습니다.
test.info()
테스트 데이터에서도 위에 언급한 3개의 변수들이 일부 NULL 값을 가진걸 확인 했습니다.
소규모 데이터이기 때문에 조금 신중하게 접근할 필요가 있겠습니다.
train['Body Mass (g)'].hist()
반응변수인 몸무게의 히스토그램입니다. 이상치가 있어보이진 않고, 정규분포와 꽤 유사해보입니다.
plt.figure(figsize=(20,15))
plt.suptitle("Histogram", fontsize=40)
for i in range(len(continuous_names)):
plt.subplot(2,3,i+1)
plt.title(continuous_names[i])
plt.hist(train[continuous_names[i]])
(데이콘 운영자님이 작성하신 코드를 일부 참고했습니다.)
연속형 변수의 히스토그램을 그렸을 때 이상치도 크게 없어보이고 나름(?) 정규성을 근접하게라도 따르는 것 같습니다.
하지만 각 변수 마다 스케일이 다르기 때문에 표준화가 필요할 것 같아요.
plt.figure(figsize=(15,10))
ax = sns.heatmap(train.drop(discrete_names, axis = 1).corr(), annot=True)
plt.show()
모든 변수가 반응변수(Body Mass)와 꽤 높은 상관관계를 가지고 있습니다. 딱히 버릴 변수는 없을 것 같아요.
또한 설명변수끼리도 상관관계가 어느정도 있는 것 같아요. 추후에 차원축소(PCA)를 적용할 수도 있겠습니다.
plt.figure(figsize=(20,15))
for i in range(len(discrete_names)):
plt.subplot(2,2,i+1)
plt.xlabel(discrete_names[i])
plt.ylabel('Body Mass (g)')
sns.violinplot(x= train[discrete_names[i]], y= train['Body Mass (g)'])
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
(데이콘 운영자님의 코드를 참고했습니다.)
각각의 이산형 변수로 구분한 반응변수를 그래프로 나타냈습니다. 쉽게 얘기해서 이산형 변수와 반응변수의 관계를 알 수 있습니다.
네 변수 모두 유의미하게 반응변수에 영향을 끼친다고 볼 수 있습니다. 즉 버릴변수는 없어보입니다.
train.fillna(train.mean(), inplace = True)
test.fillna(train.mean(), inplace = True)
train = pd.get_dummies(train)
test = pd.get_dummies(test)
print(train.shape)
print(test.shape)
두 Delta 변수 결측값은 연속형이기 때문에 평균값으로 채우기로 했습니다.
그리고 이산형 변수는 모두 get_dummies 함수를 사용해서 원-핫 인코딩 처리 했습니다.
라벨인코딩에 경우 간단하나, 이 변수들은 순서형 자료가 아닌 범주형 자료이기 때문에 사용이 부적절하다고 생각했습니다.
이 경우 Sex 변수 결측값은 Sex_men, Sex_women 어느 경우에도 속하지 않기 때문에 두 행 전부 0으로 처리됩니다.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
train_scaler = scaler.fit_transform(train[continuous_names])
train[continuous_names] = pd.DataFrame(data=train_scaler, columns=continuous_names)
test_scaler = scaler.transform(test[continuous_names])
test[continuous_names] = pd.DataFrame(data=test_scaler, columns=continuous_names)
train[continuous_names].head()
앞서 말한대로 연속형 변수에 경우 변수간 스케일을 맞춰주기 위해 StandardScaler를 사용했습니다.
StandardScaler는 평균 0, 분산 1이 되도록 값을 바꿔주기 때문에 변수간 스케일이 동등해집니다.
여기서 저는 '테스트 데이터(test.csv)를 학습에 사용' 하지 않기 위해 트레인 데이터만 스케일링에 사용했는데요.
테스트 데이터를 스케일링에 사용된다면 엄밀히 말해서 data leakage에 해당할 수도 있을거 같습니다.
이 부분은 제가 문의게시판에 올려보겠습니다.
(윗 코드 결측치를 평균 대치 한 부분도 train 평균 값만을 사용했는데 같은 이치입니다.)
train_label = train['Body Mass (g)']
train.drop(['Body Mass (g)'], axis = 1, inplace = True)
모델 적합을 위해 반응변수를 따로 label로 빼줍니다.
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score
alphas = [0,0.1,1,10,100]
for alpha in alphas:
ridge = Ridge(alpha=alpha)
neg_mse_scores = cross_val_score(ridge, train, train_label, scoring = 'neg_mean_squared_error', cv = 5)
avg_rmse = np.mean(np.sqrt(-neg_mse_scores))
print('alpha 값 ', alpha, '일때 평균 rmse :', np.round(avg_rmse,4))
릿지 회귀 모델을 사용하였습니다. 교차검증을 통해 최적의 알파(하이퍼파라미터)를 찾는 과정입니다.
릿지 회귀를 간단히 설명하면 일반 선형 회귀에 응용으로 회귀계수 크기를 패널티로 부여한 모델입니다.
전반적으로 회귀계수 크기가 수축하는 효과를 보이며 과적합을 방지할 수 있습니다.
다만 릿지 회귀는 입력 특성 스케일에 민감하기에 앞서 스케일링 작업을 해주었습니다.
ridge = Ridge(alpha = 10)
ridge.fit(train, train_label)
sample_submission['Body Mass (g)'] = ridge.predict(test)
sample_submission.to_csv('Penguin_final_1.csv',index=False)
앞서 교차검증으로 나온 최적의 알파 값은 10이기 때문에 이를 대입해서 모델을 만듭니다.
이 모델에 Public MSE 값은 309.92 정도가 나옵니다.
from sklearn.linear_model import Lasso
alphas = [0,0.1,1,10,100]
for alpha in alphas:
lasso = Lasso(alpha=alpha)
neg_mse_scores = cross_val_score(lasso, train, train_label, scoring = 'neg_mean_squared_error', cv = 5)
avg_rmse = np.mean(np.sqrt(-neg_mse_scores))
print('alpha 값 ', alpha, '일때 평균 rmse :', np.round(avg_rmse,4))
lasso = Lasso(alpha = 10)
lasso.fit(train, train_label)
sample_submission['Body Mass (g)'] = lasso.predict(test)
sample_submission.to_csv('Penguin_final_2.csv',index=False)
from sklearn.linear_model import ElasticNet
alphas = [0,0.1,1,10,100]
#alphas = [0.07,0.1,0.5,1,3]
for alpha in alphas:
elasticNet = ElasticNet(alpha=alpha)
neg_mse_scores = cross_val_score(elasticNet, train, train_label, scoring = 'neg_mean_squared_error', cv = 5)
avg_rmse = np.mean(np.sqrt(-neg_mse_scores))
print('alpha 값 ', alpha, '일때 평균 rmse :', np.round(avg_rmse,4))
elasticNet = ElasticNet(alpha = 0.1)
elasticNet.fit(train, train_label)
sample_submission['Body Mass (g)'] = elasticNet.predict(test)
sample_submission.to_csv('Penguin_final_3.csv',index=False)