사이킷런으로 수행하는 타이타닉 생존자 예측
캐글에서 제공하는 타이타닉 탑승자 데이터를 기반으로 생존자 예측을 사이킷런으로 수행행 보겠습니다.
1. 타이타닉 데이터 전처리
이번 예제에서는 파이썬의 대표적인 시각화 패키지인 맷플롯립과 시본을 이용해 차트와 그래프도 함께 시각화하면서 데이터 분석을 진행 해보자
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
titanic_df = pd.read_csv('./excel/titanic_train.csv')
titanic_df.head(3)
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley ... |
female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2... | 7.9250 | NaN | S |
info()
로딩된 데이터 칼럼 타입을 확인해 보겠습니다. DataFrame의 info() 메서드를 통해 쉽게 확인이 가능 합니다.
titanic_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
결측치 처리
DataFrame의 fillna()함수를 사용해서 간단하게 Null값을 평균 또는 고정 값 대체
Age의 경우는 평균 나이, 나머지 칼럼은 ‘N’ 값으로 변경
titanic_df['Age'].fillna(titanic_df['Age'].mean(),inplace=True)
titanic_df['Cabin'].fillna('N',inplace = True)
titanic_df['Embarked'].fillna('N',inplace = True)
print('데이터 셋 Null 값 개수',titanic_df.isnull().sum().sum())
데이터 셋 Null 값 개수 0
남아있는 문자열 피처는 Sec, Cabin, Embarked입니다. 먼저 이 피처들의 값 분류를 살펴 보겠습니다.
#값 분류
print(' Sex 값 분포 :\n',titanic_df['Sex'].value_counts())
print('\n Cabin 값 분포 :\n',titanic_df['Cabin'].value_counts())
print('\n Embarked 값 분포 :\n',titanic_df['Embarked'].value_counts())
Sex 값 분포 :
male 577
female 314
Name: Sex, dtype: int64
Cabin 값 분포 :
N 687
G6 4
C23 C25 C27 4
B96 B98 4
F2 3
...
A23 1
E58 1
A32 1
C128 1
A7 1
Name: Cabin, Length: 148, dtype: int64
Embarked 값 분포 :
S 644
C 168
Q 77
N 2
Name: Embarked, dtype: int64
Sex. Embarked 값은 별문제가 없으나, Cabin(선실)의 경우 N이 687 건으로 가장 많은 것도 특이하지만, 속성값이 제대로 정리가 되지 않음
예를 들어 ‘C23 C25 C27’과 같이 여러 Cabin이 한꺼번에 표기된 Cabin값이 4건이나 됩니다.
Cabin의 경우 선실 번호 중 선실 등급을 나타내는 첫 번째 알파벳이 중요해 보인다.
왜냐하면 이 시절에는 지금보다도 빈부격차가 더 크던시절이기에 일등실에 투숙한 사람이 삼등실에 투숙한 사람보다 더 살아날 확률이 높았을 것이기 때문입니다. Cabin 속성의 경우 앞 문자만 추출
titanic_df['Cabin']=titanic_df['Cabin'].str[:1]
titanic_df['Cabin'].head(3)
0 N
1 C
2 N
Name: Cabin, dtype: object
데이터 탐색
- 어떤 유형의 승객이 생존확률이 높았는지 보자
- 성별이 생존 확률에 어떤 영향을 미쳤는지, 성별에 따른 생존자 수를 비교해 보자.
- 부자와 가난한 사람의 생존확률 , 일등실, 이등실, 삼등실에 따라 생종확률을 살펴보자.
- 나이에 따른 생존확률
성별에 따른 생존확률
titanic_df.groupby(['Sex','Survived'])['Survived'].count()
Sex Survived
female 0 81
1 233
male 0 468
1 109
Name: Survived, dtype: int64
Seaborn 패키지를 이용해 시각화를 진행
- barplot() 을이용
- X축에 'Sex'칼럼
- Y축에 'Survived' 칼럼
sns.barplot(x='Sex',y='Survived',data=titanic_df)
빈부에 따른 생존 확률
- barplot() 함수에 x 좌표에 ‘Pclass’를 , 그리고 hue파라미터를 추가해 hue=’Sex’와 같이 입력하면 된다
sns.barplot(x='Pclass',y='Survived',hue = 'Sex',data=titanic_df)
나이에 따른 생존확률
# 입력 age 에 따라 구분 값을 반환하는 함수 설정, DataFrame의 apply lambda 식에 사용.
def get_category(age):
cat = ''
if age <= -1: cat = 'Unknown'
elif age <= 5: cat = 'Baby'
elif age <= 12: cat = 'Child'
elif age <= 18: cat = 'Teenager'
elif age <= 25: cat = 'Student'
elif age <= 35: cat = 'Young Adult'
elif age <= 60: cat = 'Adult'
else : cat = 'Elderly'
return cat
#막대 그래프의 크기 figure를 설정
plt.figure(figsize=(10,6))
#x축의 값을 순차적으로 표시하기 위한 설정
group_names = ['Unknown', 'Baby', 'Child', 'Teenager', 'Student', 'Young Adult', 'Adult', 'Elderly']
#lambda 식에 위에서 생성한 get_category() 함수를 반환값으로 지정.
#get_category(X)는 입력값으로 'Age' 칼럼 값을 받아서 해당하는 cat 반환
titanic_df['Age_cat'] =titanic_df['Age'].apply(lambda x : get_category(x))
sns.barplot(x='Age_cat', y='Survived',hue ='Sex', data=titanic_df,order=group_names)
titanic_df.drop('Age_cat',axis=1,inplace=True)
여자 Baby의 경우 비교적 생존 확률이 높음
아쉽게도 여자 Child의 경우는 다른 연령대에 비해 생존 확률이 낮다.
그리고 여자 Senior의 경우는 매우 생존 확률이 높음.
이제까지 분석한 결과 Sex, Age, PClass 등이 중요하게 생존을 좌우하는 피처임을 어느 정도 확인할 수 있었습니다.
2. 문자열 카테고리 인코딩
- 문자열 카테고리 피처를 숫자형 카테고리 피처로 변환
- LabelEncoder 클래스를 이용해 레이블인코딩을 적용
from sklearn import preprocessing
def encode_features(dataDF):
features = ['Cabin','Sex','Embarked']
for feature in features:
le = preprocessing.LabelEncoder()
le = le.fit(dataDF[feature])
dataDF[feature] = le.transform(dataDF[feature])
return dataDF
titanic_df = encode_features(titanic_df)
titanic_df.head()
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | 1 | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | 7 | 3 |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley ... |
0 | 38.0 | 1 | 0 | PC 17599 | 71.2833 | 2 | 0 |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | 0 | 26.0 | 0 | 0 | STON/O2.... | 7.9250 | 7 | 3 |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath ... |
0 | 35.0 | 1 | 0 | 113803 | 53.1000 | 2 | 3 |
4 | 5 | 0 | 3 | Allen, Mr. William Henry | 1 | 35.0 | 0 | 0 | 373450 | 8.0500 | 7 | 3 |
Sex, Cabin, Embarked 속성이 숫자형으로 바뀐 것을 알 수 있습니다.
지금까지 피처를 가공한 내역을 정리하고 이를 함수로 만들어서 쉽게 재사용 하자.
데이터의 전처리를 전체적으로 호출하는 함수는 transform_features() 이며 Null 처리, 포매팅, 인코딩을 수행하는 내부 함수로 구성했다.
#Null 처리 함수
def fillna(df):
df['Age'].fillna(df['Age'].mean(),inplace = True)
df['Cabin'].fillna('N',inplace=True)
df['Embarked'].fillna('N', inplace = True)
df['Fare'].fillna(0,inplace = True)
return df
#머신러닝 알고리즘에 불필요한 속성 제거
def drop_features(df):
df.drop(['PassengerId', 'Name', 'Ticket'],axis=1,inplace=True)
return df
#레이블 인코딩 수행
def format_features(df):
df['Cabin'] = df['Cabin'].str[:1]
features = ['Cabin','Sex','Embarked']
for feature in features:
le = LabelEncoder()
le = le.fit(df[feature])
df[feature] = le.transform(df[feature])
return df
# 앞에서 설정한 데이터 전처리 함수 호출
def transform_features(df):
df = fillna(df)
df = drop_features(df)
df= format_features(df)
return df
3. 생존 결과 예측
원본 CSV 파일을 다시 로딩하고 타이타닉 생존자 데이터 셋의 레이블인 Survived 속성만 별도 분리해 클래스 결정값 데이터 셋으로 만듦
Survived 속성을 드롭해 피처 데이터 셋으로 만듦
피처 데이터 세트에 transform_features()를 적용해 데이터를 가공합니다.
#원본 데이터 재로딩하고, 피처 데이터 세트와 레이블 데이터 세트 추출.
titanic_df =pd.read_csv('./excel/titanic_train.csv')
y_titanic_df=titanic_df['Survived']
X_titanic_df=titanic_df.drop('Survived',axis=1)
X_titanic_df = transform_features(X_titanic_df)
- 내려받은 학습 데이터 셋을 기반으로 해서 train_test_split() API를 이용해 별도의 테스트 데이터 셋을 추출
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test=train_test_split(X_titanic_df, y_titanic_df, \
test_size=0.2, random_state=11)
ML 알고리즘 사용
ML 알고리즘인 결정 트리, 랜덤 포레스트, 로지스틱 회귀를 이용해 타이타닉 생존자를예측해 보겠습니다.
이들 사이킷런 클래스를 이용해 train_test_split() 으로 분리한 학습 데이터와 테스트 데이터를 기반으로 머신러닝 모델을 학습하고(fit) , 예측(predict)할 것입니다. 예측 성능 평가는 정확도로 할 것이며 이를 위해 acuuracy_score() API를 사용합니다.
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
#결정트리, Random Forest, 로지스틱 회귀를 위한 사이킷런 Classifier 클래스 생성
dt_clf = DecisionTreeClassifier(random_state=11)
rf_clf = RandomForestClassifier(random_state=11)
lr_clf = LogisticRegression()
#DecisionTreeClassifier 학습/예측/평가
dt_clf.fit(X_train, y_train)
dt_pred = dt_clf.predict(X_test)
print('DecisionTreeClassifier 정확도: {0:.4f}'.format(accuracy_score(y_test, dt_pred)))
#RandomForestClassifier 학습/예측/평가
rf_clf.fit(X_train, y_train)
rf_pred = rf_clf.predict(X_test)
print('RandomForestClassifier 정확도:{0:.4f}'.format(accuracy_score(y_test, rf_pred)))
# LogisticRegression 학습/예측/평가
lr_clf.fit(X_train , y_train)
lr_pred = lr_clf.predict(X_test)
print('LogisticRegression 정확도: {0:.4f}'.format(accuracy_score(y_test, lr_pred)))
DecisionTreeClassifier 정확도: 0.7877
RandomForestClassifier 정확도:0.8547
LogisticRegression 정확도: 0.8492
C:\anaconda3\lib\site-packages\sklearn\linear_model\_logistic.py:940: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG)
3개의 알고리즘 중 LogisticRegression이 타 알고리즘에 비해 높은 정확도 나타냄
아직 최적화 작업을 안했기 때문에 어떤 알고리즘이 좋다고 판단하기에는 이르다..
교차 검증
교차 검증으로 결정 트리 모델을 좀 더 평가해 보자. 앞에서 언급한 교차 검증을 위한 사이킷런 model_selection 패키지의 KFold클래스, cross_val_score(), GridSearchCV 클래스 모두 사용
먼저 사이킷런의 KFold 클래스를 이용해 교차 검증을 수행하며 ,폴드 개수는 5개로 설정합니다.
from sklearn.model_selection import KFold
def exec_kfold(clf, folds=5):
# 폴드 세트를 5개인 KFold객체를 생성, 폴드 수만큼 예측결과 저장을 위한 리스트 객체 생성.
kfold = KFold(n_splits=folds)
scores = []
# KFold 교차 검증 수행.
for iter_count , (train_index, test_index) in enumerate(kfold.split(X_titanic_df)):
# X_titanic_df 데이터에서 교차 검증별로 학습과 검증 데이터를 가리키는 index 생성
X_train, X_test = X_titanic_df.values[train_index], X_titanic_df.values[test_index]
y_train, y_test = y_titanic_df.values[train_index], y_titanic_df.values[test_index]
# Classifier 학습, 예측, 정확도 계산
clf.fit(X_train, y_train)
predictions = clf.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
scores.append(accuracy)
print("교차 검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))
# 5개 fold에서의 평균 정확도 계산.
mean_score = np.mean(scores)
print("평균 정확도: {0:.4f}".format(mean_score))
# exec_kfold 호출
exec_kfold(dt_clf , folds=5)
교차 검증 0 정확도: 0.7542
교차 검증 1 정확도: 0.7809
교차 검증 2 정확도: 0.7865
교차 검증 3 정확도: 0.7697
교차 검증 4 정확도: 0.8202
평균 정확도: 0.7823
평균 정확도는 약 78.23%입니다. 이번에는 교차 검증을 cross_val_score() API를 이용해 수행합니다.
from sklearn.model_selection import cross_val_score
scores = cross_val_score(dt_clf, X_titanic_df , y_titanic_df , cv=5)
for iter_count,accuracy in enumerate(scores):
print("교차 검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))
print("평균 정확도: {0:.4f}".format(np.mean(scores)))
교차 검증 0 정확도: 0.7430
교차 검증 1 정확도: 0.7753
교차 검증 2 정확도: 0.7921
교차 검증 3 정확도: 0.7865
교차 검증 4 정확도: 0.8427
평균 정확도: 0.7879
cross_val_score()와 방금 전 K폴드의 평균 정확도가 약간 다른데 이는 cross_val__score()가 StratifiedKFold를 이용해 폴드 셋을 분할하기 때문이다.
하이퍼 파라미터 추적
GridSearchCV를 이용해 DecisionTreeClassifier의 최적 하이퍼 파라미터를 찾고 예측 성능을 측정해 보자.
from sklearn.model_selection import GridSearchCV
parameters = {'max_depth':[2,3,5,10],
'min_samples_split':[2,3,5], 'min_samples_leaf':[1,5,8]}
grid_dclf = GridSearchCV(dt_clf , param_grid=parameters , scoring='accuracy' , cv=5)
grid_dclf.fit(X_train , y_train)
print('GridSearchCV 최적 하이퍼 파라미터 :',grid_dclf.best_params_)
print('GridSearchCV 최고 정확도: {0:.4f}'.format(grid_dclf.best_score_))
best_dclf = grid_dclf.best_estimator_
# GridSearchCV의 최적 하이퍼 파라미터로 학습된 Estimator로 예측 및 평가 수행.
dpredictions = best_dclf.predict(X_test)
accuracy = accuracy_score(y_test , dpredictions)
print('테스트 세트에서의 DecisionTreeClassifier 정확도 : {0:.4f}'.format(accuracy))
GridSearchCV 최적 하이퍼 파라미터 : {'max_depth': 3, 'min_samples_leaf': 5, 'min_samples_split': 2}
GridSearchCV 최고 정확도: 0.7992
테스트 세트에서의 DecisionTreeClassifier 정확도 : 0.8715
최적화된 하이퍼 파라미터은 max_depth=3, min_samples_leaf=1, min_samples_split=2로 DecisionTreeClassifier를 학습시킨 뒤 예측 정확도가 약 87.15%로 향상됨
하이퍼 파라미터 변경 전보다 약 8% 이상이 증가햇는데, 일반적으로 하이퍼 파라미터를 튜닝하더라도 이 정도 수준으로 증가하기는 매우 어렵다.
테스트용 데이터 셋이 작기 때문에 수치상으로 예측 성능이 많이 증가한 것처럼 보인다.
'머신러닝' 카테고리의 다른 글
머신러닝 성능 평가 (0) | 2020.08.20 |
---|---|
numpy, pandas 기초 (0) | 2020.08.20 |
데이터 전처리 (0) | 2020.08.19 |
사이킷런의 Model Selection 모듈 (0) | 2020.08.19 |
사이킷런 기반의 프레임 워크 (0) | 2020.08.19 |