👨‍💼

2.5 타이타닉 Python - 2

앞서 했던 작업들 몇 가지를 반복할 것입니다. 앞에서는 train 데이터만 가지고 전처리를 하였기 때문이에요. 이 작업을 하기 위해 여러분들도 주피터 노트북을 다른 파일로 저장을 해주세요.
%matplotlib inline import matplotlib.pyplot as plt import pandas as pd import seaborn as sns train = pd.read_csv('train.csv') test = pd.read_csv('test.csv')
 
또한, 전처리를 하기 전에 2가지 명령어를 먼저 실행해 보도록 하겠습니다. 이 명령어를 통해 기초통계량을 볼 수 있어요.
train.describe() #평균이 결국 생존율입니다.
notion imagenotion image
  • count : 해당 값에 비어 있지 않은 값의 개수
  • mean : 평균
  • std : 표준편차
  • min : 최솟값
  • 25% : 해당 Column 값을 순서대로 정렬했을 때, 아래에서 부터 1/4번째 지점에 있는 값, 1사분위수라고 말합니다.
  • 50% : 해당 Column 값을 순서대로 정렬했을 때, 아래에서 부터 2/4번째 지점에 있는 값, 2사분위수, 중앙값이라고 말합니다.
  • 75% : 해당 Column 값을 순서대로 정렬했을 때, 아래에서 부터 3/4번째 지점에 있는 값, 3사분위수라고 말합니다.
  • max : 최댓값
 
그 다음은 상관도 분석입니다.
train.corr()
notion imagenotion image
상관도 분석은 우리가 구하려는 값에 얼마나 상관도가 있는지를 수치로 나타낸 값입니다. 예를들어 공부시간과 성적은 양의 상관관계가 있는 관계입니다.
이 2개를 먼저 실행해본 이유는, 전처리를 하고 나서 다시 실행해보고 비교해보고자 함입니다.

1. 전처리(Pre-Processing)

머신러닝을 사용하기 전, 더 정확하게 인공지능을 돌리기 위해 값들을 숫자로 표시해줍니다. 사람이 이해하기 쉽게 라벨링하는것과는 반대죠?
정확도를 높이고 싶으면 좀 더 구체적인 카테고리로 나누면 됩니다. 이 책에서는 복잡도를 높이지 않기 위해 카테고리를 적게 만들었어요.

1-1. Name

영어에는 이름 앞에 Mr, Mrs 등 결혼 여부를 알려줄 수 있습니다. 이 명칭을 추출하여 'Title'이라는 컬럼에 저장하도록 하겠습니다.
train 데이터에는 Mr, Mrs, Miss 외에 다양한 명칭들이 있는데 이 값들을 모두 분류화시켜주면 더 정확해질 수는 있겠지만, 그 데이터 양이 매우 적습니다.(총 27개) 복잡도를 증가시키지 않기 위해 다른 명칭들을 모두 'Other'로 통일하도록 하겠습니다.
train['Title'] = train['Name'].str.extract(' ([A-Za-z]+)\.', expand=False) train['Title'] = train['Title'].replace(['Lady', 'Countess','Capt', 'Col','Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona', 'Mlle', 'Ms', 'Mme'], 'Other') train['Title'] = train['Title'].replace('Mlle', 'Miss') train['Title'] = train['Title'].replace('Ms', 'Miss') train['Title'] = train['Title'].replace('Mme', 'Mrs')
train['Title'].value_counts()
 
출력하면 아래와 같은 결과화면이 나옵니다.
#Output Mr 517 Miss 182 Mrs 125 Master 40 Other 27 Name: Title, dtype: int64
 
이제 값들이 모두 정리가 되었으니 기계가 쉽게 알아들을 수 있도록 숫자로 변환시켜 주도록 하겠습니다.
train['Title_label'] = train['Title'].astype('category').cat.codes train[['Title','Title_label']]
  • Miss : 1
  • Mr : 2
  • Mrs : 3
  • Other : 4
카테고리 목록은 위와 같습니다.
여기서 astype을 통해 카테고리형(범주형)으로 데이터를 바꿉니다. 여기서 cat.categories['a', 'b', 'c']를 사용하면 이름을 재정의하며, cat.codes를 통해 각 카테고리를 숫자로 변형시킬 수도 있습니다.
해당내용이 자세히 포스팅 되어 있는 글을 첨부합니다.
같은 작업을 test에도 해주어야 합니다.
test['Title'] = test['Name'].str.extract(' ([A-Za-z]+)\.', expand=False) test['Title'] = test['Title'].replace(['Lady', 'Countess','Capt', 'Col','Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona', 'Mlle', 'Ms', 'Mme'], 'Other') test['Title'] = test['Title'].replace('Mlle', 'Miss') test['Title'] = test['Title'].replace('Ms', 'Miss') test['Title'] = test['Title'].replace('Mme', 'Mrs') test['Title'].value_counts()
test['Title_label'] = train['Title'].astype('category').cat.codes test[['Title','Title_label']]
 
이렇게 2번 작업하는 것을 방지하기 위해, 아래처럼 코드를 묶어주도록 하겠습니다.
전체데이터 = [train, test]
 
또한, 위의 작업을 아래처럼 mapping data를 만들어 진행하는 것도 많이 쓰이는 방식이니 참고해주세요!
for 데이터 in 전체데이터: 데이터['Title'] = 데이터['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
mapping_data = {"Mr": 0, "Miss": 1, "Mrs": 2, "Master": 3, "Dr": 3, "Rev": 3, "Col": 3, "Major": 3, "Mlle": 3,"Countess": 3, "Ms": 3, "Lady": 3, "Jonkheer": 3, "Don": 3, "Dona" : 3, "Mme": 3,"Capt": 3,"Sir": 3 } for 데이터 in 전체데이터: 데이터['Title'] = 데이터['Title'].map(mapping_data)
 
불필요한 데이터들은 지워주도록 하겠습니다. 다 지우신 다음에 train.head()를 통해 제대로 지워졌는지 확인하셔야 합니다. inplace=True값을 주면 원본에 반영됩니다.
# 불필요 데이터 지우기 train.drop('Name', axis=1, inplace=True) test.drop('Name', axis=1, inplace=True)
 
그런데 Name 말고도 Title도 필요가 없습니다. 이미 수치화 해주었기 때문입니다.
# 불필요 데이터 지우기 train.drop('Title', axis=1, inplace=True) test.drop('Title', axis=1, inplace=True)

1-2. Age

Age는 결측치가 있는 값이므로 아래와 같이 결측치를 채워주도록 하겠습니다. 각각의 Title_label의 그룹별 평균 값으로 결측치를 채우는 코드입니다.
train["Age"].fillna(train.groupby("Sex")["Age"].transform("median"), inplace=True) test["Age"].fillna(test.groupby("Sex")["Age"].transform("median"), inplace=True)
train.info()를 출력해보시면 age 결측값이 없는 것을 확인할 수 있습니다.
<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 Sex 891 non-null object 4 Age 891 non-null float64 5 SibSp 891 non-null int64 6 Parch 891 non-null int64 7 Ticket 891 non-null object 8 Fare 891 non-null float64 9 Cabin 204 non-null object 10 Embarked 889 non-null object 11 Title_label 891 non-null int8 dtypes: float64(2), int64(5), int8(1), object(4) memory usage: 77.6+ KB
이제 Age도 카테고리화 해보도록 하겠습니다. 그런데 여기서 왜 카테고리로 나눠야 할까요? 남자와 여자는 0과 1로 1차이가 극명하게 나뉘는 값이지만 요금과 나이의 경우 20과 30의 차가 10이라고 해서 극명하게 나뉘는 값은 아니기 때문에 값에 전체적인 범주에 걸맞게 낮춰주어야 합니다. 머신러닝이 알 수 있는 것은 결국 숫자니까요.
  • 1 : 0~10세
  • 2 : 11~25세
  • 3 : 25~36세
  • 4 : 36~60세
  • 5 : 60세 이상
for 데이터 in 전체데이터: 데이터.loc[ 데이터['Age'] <= 16, 'Age'] = 0 데이터.loc[(데이터['Age'] > 16) & (데이터['Age'] <= 26), 'Age'] = 1 데이터.loc[(데이터['Age'] > 26) & (데이터['Age'] <= 36), 'Age'] = 2 데이터.loc[(데이터['Age'] > 36) & (데이터['Age'] <= 62), 'Age'] = 3 데이터.loc[ 데이터['Age'] > 62, 'Age'] = 4
각각 head 정보를 확인해보세요. 아래처럼요.
train.head()
test.head()

1-3. Fare

Fare 값은 이미 숫자이지만 단순화하기 위해 pd.qcut 을 이용하여 요금별 구간을 5구간으로 나누어서 아래와 같이 진행할 수 있습니다. 그 전에 Age에서 했었던 방식처럼 if문을 조합하여 구간별로 나누셔도 괜찮습니다. 여기는 이러한 방법이 있다는 것을 알려드리기 위해 사용한 것입니다.
for 데이터 in 전체데이터: 데이터['Fare_bin'] = pd.qcut(train['Fare'], 5) 데이터['Fare_label'] = 데이터['Fare_bin'].astype('category').cat.codes
나누어진 값들을 라벨링하여 저장해주고 아래와 같이 출력해보세요.
train[['Fare','Fare_bin','Fare_label']]
test[['Fare','Fare_bin','Fare_label']]
  • (-0.001, 7.854] : 0
  • (7.854, 10.5] : 1
  • (10.5, 21.679] : 2
  • (21.679, 39.688] : 3
  • (39.688, 512.329] : 4
카테고리 목록은 위와 같습니다.
이제 불필요한 데이터들은 지우도록 하겠습니다.
# 불필요 데이터 지우기 for 데이터 in 전체데이터: 데이터.drop('Fare', axis=1, inplace=True) 데이터.drop('Fare_bin', axis=1, inplace=True)

1-4. Family

train["FamilySize"] = train["SibSp"] + train["Parch"] + 1 test["FamilySize"] = test["SibSp"] + test["Parch"] + 1
모든 가족들을 합치고, 나를 더해 FailySize라는 컬럼을 만듭니다. 여기서는 앞서 Name 컬럼에서 소개해드렸던 방식을 한 번 사용해보도록 하겠습니다.
mapping_data = {1: 0, 2: 0.4, 3: 0.8, 4: 1.2, 5: 1.6, 6: 2, 7: 2.4, 8: 2.8, 9: 3.2, 10: 3.6, 11: 4} for 데이터 in 전체데이터: 데이터['FamilySize'] = 데이터['FamilySize'].map(mapping_data)
자, 이제 필요없는 필드를 지워줘야 하겠죠?
 

1-5. Embarked

우선 embarked는 데이터가 다 안들어가 있으니 가장 빈도가 높은 'S'로 채워주도록 하겠습니다.
for 데이터 in 전체데이터: 데이터['Embarked'] = 데이터['Embarked'].fillna('S')
mapping_data = {"S": 0, "C": 1, "Q": 2} for 데이터 in 전체데이터: 데이터['Embarked'] = 데이터['Embarked'].map(mapping_data)
각각의 선착장 별로 0, 1, 2의 값을 부여하도록 하겠습니다.
성별 컬럼과 선착장 컬럼도 마찬가지로 숫자로 라벨링하여 저장해두도록 하겠습니다.
 
 

1-6. Sex

이번에는 매핑 방식이 아니라 카테고리로 변환하여 출력해보도록 하겠습니다. 일부러 여러 방식을 사용해보고 있습니다. head 정보를 출력해보시면 남성은 1로, 여성은 0으로 변환된 것을 볼 수 있습니다.
test['Sex'] = test['Sex'].astype('category').cat.codes train['Sex'] = train['Sex'].astype('category').cat.codes
 

1-7. Drop Data

사용하지 않는 데이터는 모두 지우도록 하겠습니다.
for 데이터 in 전체데이터: 데이터.drop('Ticket', axis=1, inplace=True) 데이터.drop('Cabin', axis=1, inplace=True) 데이터.drop('PassengerId', axis=1, inplace=True)
각각 head 정보를 출력해보세요.
train.head()
notion imagenotion image
test.head()
notion imagenotion image

1-8. 상관도 분석

맨 처음에 그렸던 상관도 분석 그래프를 여기서 그려보도록 하겠습니다. 그리고 다시 한 번 최상단 상관도 분석과 비교해보세요.
train.corr()
plt.figure(figsize=(15,15)) sns.heatmap(data=train.corr(), annot=True, fmt='.2f', linewidths=.5, cmap='Blues')

2. Modelling

이번 시간에는 사이킷런을 사용할텐데요. 뒤 챕터에서는 TensorFlow를 사용합니다. 사이킷런은 훌륭한 머신러닝 라이브러리입니다. 자세한 내용은 공식홈페이지(https://scikit-learn.org/) 참고 바랍니다.
예를 들어 우리가 자동차 운전을 할 때 엑셀을 밟으면서 그 원리에 대해 정확하게 몰라도, 자동차는 앞으로 나가죠. 마찬가지로 머신러닝에 쓰이는 대부분의 기능을 이처럼 모듈로 만들어 놓았고, 우리는 그 원리에 대해 모르더라도 결과값을 알아낼 수 있습니다. 이번시간에 사용할 사이킷런은 딥러닝이나 강화학습을 지원하지는 않습니다.
텐서플로우와 비교를 하자면 텐서플로우는 구글이 지원하는 머신러닝과 인공 신경망 라이브러리입니다. 성능과 확장성이 좋고 GPU, TPU와 같은 하드웨어를 지원합니다. 딥러닝에 주로 사용됩니다. 사이킷런은 지원하지 않아요. 또한 텐서보드는 사용자가 이해하기 쉽도록 시각화까지 해주죠. 처음에 공부하실 때에는 사이킷런을, 개념이 어느정도 잡히신 상태라면 텐서플로우를 추천해드립니다.
우리는 총 5개의 알고리즘을 사용할텐데요. 실무에서도 알고리즘을 돌려보고 가장 점수가 좋은 것을 사용합니다. 업체 있으시다면 점수가 낮게 나오더라도 비교적 상사나 고객에게 설명하기 쉬운 RandomForest를 사용하기도 해요. 가이드 라인은 사이킷런에서 제공하고 있습니다. 아래 도표를 참고 바랍니다. 절대적이지는 않습니다. 앞서 말씀드린 것처럼 점수 높은 것을 더 선호하는 편입니다.
 
https://scikit-learn.org/stable/tutorial/machine_learning_map/index.htmlhttps://scikit-learn.org/stable/tutorial/machine_learning_map/index.html
 
사이킷런에서 제공하고 있는 API Reference 모듈입니다. 여기는 5가지 알고리즘을 사용하지만, 더 많은 알고리즘들이 구현되어 있습니다.
from sklearn.neighbors import KNeighborsClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.naive_bayes import GaussianNB from sklearn.svm import SVC import numpy as np
주로 처음에는 아래 모듈을 사용해 train 데이터와 test 데이터를 분할하는데요. 여기는 이미 데이터가 분할되어 있기 때문에 아래 모듈은 사용하지 않습니다. 참고삼아 올립니다. 아래 코드는 입력하지 않으셔도 됩니다.
from sklearn.model_selection import train_test_split
자, 이제 train 데이터만 가지고 성능이 얼마나 되는지를 확인해보도록 하겠습니다. 이를 위해 기존 train 데이터에서 Survived 컬럼이 빠진 데이터 셋과 Survived 컬럼만 가진 데이터 셋으로 구분하여 얼마나 예측할 수 있는지 예측율을 보도록 하겠습니다.
train_data = train.drop('Survived', axis=1) target = train['Survived'] train_data.shape, target.shape

2.1 Cross Validation (K-fold)

한국어로는 교차검증이라고 합니다. train 데이터를 다시 K개의 test 데이터로 나눠 검증하는 절차를 말합니다.
데이터의 개수가 적은 경우 주로 사용합니다. 예를 들어 아래 데이터 같은 경우에는 10개로 나누고, 데이터를 뒤섞습니다. 그리고 1개의 데이터는 검증 데이터로, 나머지 9개의 데이터는 train 데이터로 사용해요. 이렇게 10번을 반복합니다.
from sklearn.model_selection import KFold from sklearn.model_selection import cross_val_score k_fold = KFold(n_splits=10, shuffle=True, random_state=0)

2.1.1 kNN

이렇게 분할된 데이터를 가지고 하나씩 알고리즘을 적용시켜 볼 텐데요. 가장 먼저 kNN 알고리즘을 살펴보도록 하겠습니다.
kNN은 이웃들이 어떤 값들을 가졌느냐에 따라 내 운명이(?) 결정되는 코드입니다. 예를 들어 아래 1개의 주성분이 성별(Sex)이고, 다른 주성분이 운임(Fare)이라면, 지금 라이켓의 위치는 아래와 같다고 생각해보겠습니다.
그리고 kNN에서 k는 이웃의 개수인데요. 이웃이 3이라고 했을때 나와 가장 가까운 이웃이 2명 살았고, 1명이 죽었죠? 그러면 라이켓은 산 것입니다.
그리고 오른쪽 상단에 이해를 돕기 위해 2차원으로 차원축소를 했다는 말이 보이시죠? 만약 6개의 독립변수라면, 6차원의 그래프가 있어야 합니다.
notion imagenotion image
그런데 이번에는 k를 5로 해보도록 하겠습니다. 라이켓은 어떻게 되었을까요? 네, 가장 가까운 이웃들이 많이 죽었기 때문에 이번에 라이켓은 살아남지 못했습니다.
notion imagenotion image
clf = KNeighborsClassifier(n_neighbors = 13) scoring = 'accuracy' score = cross_val_score(clf, train_data, target, cv=k_fold, n_jobs=1, scoring=scoring)
Score를 찍어보시면 여러개의 값이 출력된 것을 보실 수 있는데요. 이는 교차검증을 했기 때문에 여러개의 값이 나온 값입니다. 거기서 맞춘 확율을 뜻하죠.
 
# kNN 점수 확인 round(np.mean(score)*100, 2)
따라서 여러개의 확률을 평균내야 합니다. 이렇게 출력된 값이 평균 스코어에 해당하고, 앞으로 이 점수가 높게 나온 것을 사용하도록 할 것입니다.
 

2.1.2 Decision Tree

Decision Tree는 성분마다 의사결정 트리를 만들어 사망인지 생존인지를 구별하는 방법입니다. 각 의사결정(Decision)은 두 가지 경우에수에서 하나로 이어지거나, 또다른 의사결정 트리로 이어집니다.
notion imagenotion image
clf = DecisionTreeClassifier() scoring = 'accuracy' score = cross_val_score(clf, train_data, target, cv=k_fold, n_jobs=1, scoring=scoring)
# decision tree 점수 확인 round(np.mean(score)*100, 2)
79.69

2.1.3 Random Forest

Radom Forest는 여러개의 Decision Tree를 만들어 가장 많이 도달한 결론으로 최종 결정하는 알고리즘입니다. 아래트리는 이해를 돕기위해 간소화한 것이고 실제로는 더 깊은 트리가 발생하게 됩니다.
 
notion imagenotion image
clf = RandomForestClassifier(n_estimators=13) scoring = 'accuracy' score = cross_val_score(clf, train_data, target, cv=k_fold, n_jobs=1, scoring=scoring)
# Random Forest 점수 확인 round(np.mean(score)*100, 2)
81.15

2.1.4 Naive Bayes

나이브 베이즈는 공식으로 생존 확율을 계산합니다. 나이브 베이즈를 살펴보기 위해서는 먼저 베이즈 정리를 이해할 필요가 있습니다. 베이즈 정리는 아래 문서를 참고해주세요.
아래 그림은 2개의 feature로 간소화하여 그림으로 나타낸 것입니다.
notion imagenotion image
clf = GaussianNB() scoring = 'accuracy' score = cross_val_score(clf, train_data, target, cv=k_fold, n_jobs=1, scoring=scoring)
# Naive Bayes 점수 확인 round(np.mean(score)*100, 2)
78.78

2.1.5 SVM

서포트 벡터 머신(support vector machine, SVM)은 패턴 인식, 자료 분석을 위한 지도 학습(정답이 있는, 비지도는 정답이 없는) 모델이며, 주로 분류와 회귀 분석을 위해 사용합니다. 예를 들어 주 성분을 2차원으로 축소하고 2차원 평면에 아래와 같이 점을 찍었을 때, 새로운 데이터가 두 주 성분 중 어느 곳에 속하는지 결정하는 것이 목표입니다.
알고리즘에 대한 자세한 내용은 아래 위키 백과에서 참고 바랍니다.
notion imagenotion image
clf = SVC() scoring = 'accuracy' score = cross_val_score(clf, train_data, target, cv=k_fold, n_jobs=1, scoring=scoring)
round(np.mean(score)*100,2)
83.5

2.1.6 Testing

clf = SVC() clf.fit(train_data, target) test_data = test.drop("PassengerId", axis=1).copy() prediction = clf.predict(test_data)
제출값 = pd.DataFrame({"PassengerId": test["PassengerId"],"Survived": prediction}) 제출값.to_csv('제출파일.csv', index=False)
위에서 나온 예측값은 모두 다를 수 있습니다. 여기서 가장 높은 점수를 선택하신 후 제출하시면 됩니다. 실무에서도 가장 높은 값이 나오는 알고리름을 채택합니다.

3. References

  • pandas Tutorial에 있는 타이타닉 생존자 예측하기