2주간 파이썬의 Sklearn 라이브러리를 공부하였습니다. 굉장히 편리하게 데이터를 전 처리하고 모델링할 수 있는 다양한 기능들을 배울 수 있었습니다. 이번 글에는 Sklearn 라이브러리를 활용하여 데이터를 전처리하는 방법들을 익히고, 실습으로 타이타닉의 생존자를 예측하는 문제의 데이터를 전처리해보겠습니다.
문제 소개 : Titanic - Machine Learning from Disaster
*Titanic 문제는 Kaggle의 Hello World라고 불릴 정도로 많은 사람들이 처음 Kaggle에서 머신러닝 문제를 풀 때 시작하는 문제입니다.
1912년에 타이타닉호가 빙산에 부딪히며 침몰한 사건이 있었습니다. 2,224명의 승객과 크루원들이 탑승하여 있었는데 그중 1,502명이 죽은 끔찍한 사고였습니다. 이 문제는 타이타닉 사건에서 생존자를 예측하는 문제입니다.
머신러닝으로 해결하기 위해 주어지는 데이터는 탑승한 사람들에 대한 정보입니다. 데이터의 칼럼을 알아보겠습니다.
- PassengerId : 승객 번호
- survival : 생존유무(1: 생존, 0: 사망)
- pclass : 티켓 등급(1 = 1st, 2 = 2nd, 3 = 3rd)
- Name : 이름
- sex : 성별
- Age : 나이
- sibsp : 타이타닉호 안에 본인의 형제가 몇 명 탑승했는지?
- parch : 타이타닉호 안에 본인의 부모 혹은 자식이 몇 명 탑승했는지?
- ticket : 티켓 번호
- fare : 요금
- cabin : 선실 번호
- embarked : 승선한 곳(C = Cherbourg, Q = Queenstown, S = Southampton)
데이터를 보며 직관적으로 생존과 연관이 없어보이는 것이 있으신가요?
저는 처음 데이터를 보며 이름, 승객 번호, 티켓 번호는 생존에 영향을 크게 끼치지 않을 것 같다고 생각하였습니다.
본격적으로 데이터에 대한 전처리를 하기에 앞서 먼저 문제를 풀기 전 2주간 배웠던 Sklearn 라이브러리의 기능들을 소개하도록 하겠습니다.
문제를 풀기 전 알아야할 것1 (데이터 분리와 교차검증)
데이터를 활용하여 모델을 생성하는 이유는 무엇일까요? 정답은 미래에 발생하는 일을 예측하기 위해서입니다. 데이터를 활용하여 생성한 모델이 어느 정도로 미래를 좋게 예측할지는 테스트를 하지 않으면 알 수 있는 방법이 없습니다. 학습을 하기 위해서 데이터를 Train Data와 Test Data로 분리해야 합니다. 왜냐하면 학습시킨 데이터를 활용해 테스트를 할 경우 항상 좋은 성능을 가지고 있다는 가짜 정보를 제공할 수 있기 때문입니다. 따라서 테스트 데이터는 학습하기 전까지는 모델이 철저히 모르는 상태이어야 합니다.
train_test_split
Sklearn에서는 model_selection에 train_test_split이라는 메소드를 제공해주고 있습니다.
이 메소드를 활용하면 굉장히 편리하게 데이터를 분리할 수 있습니다.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(feature, label, test_size = 0.2)
train_test_split에 인자로 feature, label, test_size를 전달받습니다.
label은 우리가 예측하려고 하는 결과물을 의미하며 feature는 그 결과물을 예측하기 위한 column들을 의미합니다. 즉 타이타닉에서 label은 생존이며 feature는 성별, 나이와 같은 정보입니다.
그리고 test_size는 테스트할 데이터의 퍼센트를 의미합니다. 0.2라면 20%의 데이터를 테스트데이터로 사용함을 의미합니다.
이 메소드의 결과는 train features, test features, train label, test label순으로 리턴됩니다.
이 방법은 단순하고 빠르게 처리할 수 있다는 장점이 있지만 몇 가지 문제점을 가지고 있습니다. 학습할 데이터를 선정할 때 특징적인 부분만 가지고 와서 학습을 하게 된다면 어떻게 될까요? 조금 극단적인 예를 들어보겠습니다. 주식의 상승 및 하락에 대한 예측을 수행하는 머신러닝을 만든다고 가정하겠습니다. 주가에 대한 데이터 70%는 우상향 하는 정보이며 30%는 하락하는 데이터입니다. test_size를 0.3로 선정하고 분류를 했을 때 학습 데이터 70%가 우상향 하는 정보로 분류되었다고 생각해보겠습니다. 그렇다면 그 모델은 학습 데이터에 대하여만 좋은 결과를 보여주는 안 좋은 모델이 됩니다.
KFold
이를 해결하기 위한 방안이 바로 k-fold방법입니다. k-fold는 교차검증 방법은 학습하는 데이터를 k개로 쪼개어 k번 검증 데이터를 다르게 처리하는 방법입니다. 쉽게 이해하기 위해 5-fold를 예로 들어보겠습니다. 5-fold는 데이터를 5개로 쪼개고 5번의 교차검증을 하는 방법을 의미합니다. 데이터를 A,B,C,D,E 부분으로 쪼개었다고 생각해보겠습니다. 처음 학습할 때 A를 테스트로 활용하고 B, C, D, E를 학습 데이터로 활용하였다고 하겠습니다. 그 모델에 대하여 성능을 평가하였다면 다음에는 B를 테스트로 활용하고 나머지 A, C, D, E를 활용해 학습 데이터로 활용하고 성능 평가까지 진행합니다. 그다음에는 C를 테스트하고 A, B, D, E를 학습 데이터로 활용하게 됩니다. 이렇게 총 5번의 교차 검증을 진행하는 것이 k-fold 교차 검증입니다.
from sklearn.model_selection import KFold
kfold = KFold(n_splits=5)
for train_index, test_index in kfold.split(features):
X_train, X_test = features[train_index], features[test_index]
y_train, y_test = label[train_index], label[test_index]
kfold를 사용하는 방법은 다음과 같습니다. sklearn.model_selection에는 KFold라는 클래스가 있습니다. 그 클래스에 인자로 n_splits를 넣어 몇번의 교차검증을 진행할지 결정해줍니다.
그리고 KFold 클래스에 있는 split 메소드를 사용하면 각 차수에 교차검증에서 쓰일 학습 데이터와 테스트 데이터의 인덱스 값들이 들어있는 리스트를 return 받습니다.
즉 위처럼 for문을 돌게 된다면 첫번째로 도는 for문에서는 첫 번째 학습할 데이터가 들어있는 인덱스 정보, 첫 번째 테스트할 데이터가 들어있는 인덱스 정보를 전달받게 됩니다. 두 번째로 for문을 돌 때는 당연히 두 번째로 학습할 데이터가 있는 인덱스 정보, 두 번째 테스트할 데이터가 들어있는 인덱스 정보를 전달받게 됩니다. 위 for문에서 주어진 인덱스들을 활용하여 교차검증을 진행하고 적절한 모델을 찾으면 됩니다.
하지만 이 교차검증에도 몇 가지 단점이 있습니다. 좀 더 많은 검증을 요구하기 때문에 단순히 Test와 Train 데이터로 나눌 때 보다 느리다는 문제가 있습니다. 그리고 Classification(분류)를 진행하는 과정에서 데이터가 적을 경우 약간의 모순이 발생할 수 있습니다. 즉 검증할 데이터는 1번 클래스에 대한 데이터인데, 학습한 데이터들은 2번, 3번 클래스에 대한 정보만 있는 경우가 있을 수도 있다는 것 입니다. 이를 방지하기 위해 Sklearn에서는 StratifiedKFold를 제공합니다.
StratifiedKFold
StratifiedKFold는 Classification에서 사용되는 방법으로 교차검증시 Lable이 균등하도록 데이터가 분배되는 방법입니다.
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=3)
for train_index, test_index in skf.split(features, label):
X_train, X_test = features[train_index], features[test_index]
y_train, y_test = label[train_index], label[test_index]
다른점이 있다면 StratifiedKFold 클래스가 제공하는 split에서는 반드시 label값을 넣어주어야합니다.
지금까지 교차검증을 알아보았는데요. KFold와 StratifiedKFold를 수행하는 것이 너무 번거롭다고 생각이 듭니다. 왜냐하면 for문에서 적절한 인덱스로 추출해서 학습을 진행하고 일일히 검증을 해야하기 때문입니다. 이를 편리하게 하기 위해 Sklearn에 model_selection에서는 cross_val_score를 제공합니다.
cross_val_score
from sklearn.model_selection import cross_val_score
cross_val_score(dt_clf, features, label, scoring='accuracy',cv=3)
cross_val_score는 교차검증 결과를 편리하게 제공해주는 메소드입니다.
estimator, X(features), y(label), scoring, cv의 값을 인자로 넘겨주게 됩니다.
- estimator는 학습할 모델의 종류를 의미합니다.
- X는 학습하는 모델의 feature를 의미합니다.
- y는 학습하는 모델의 label을 의미합니다.
- scoring는 모델을 평가하는 지표를 의미합니다.(accuracy, recall, precision 등)
- cv는 k-fold에서 k로 몇 번 교차검증을 할지에 대한 지표입니다.
만약 estimator가 Classification이라면 StratifiedKFold로 Regression이라면 KFold로 교차검증이 이루어집니다.
cross_val_score의 결과값은 입력된 scoring의 방법에 따른 결과값이 리스트로 나오게 됩니다.
문제를 풀기 전 알아야할 것2(데이터 전처리)
데이터를 활용해 성능이 좋은 모델을 만들기 위해서는 데이터를 좋은 모습으로 가공해야합니다. 데이터를 전처리하는 이유를 정리해보았습니다.
- NA 데이터를 처리하기 위해서
- 이상치 데이터를 처리하기 위해서
- object 값을 인코딩하기 위해서
- 데이터의 Scaling을 위해서
- 필요 없는 정보를 제거하기 위해서
등 데이터를 전처리하는데 다양한 이유가 있을 것 입니다. 이번 글에는 우리는 위 방법 중 Encoding과 Scaling 두가지에 대하여 알아보겠습니다.
Encoding
Encoding은 문자나 텍스트를 컴퓨터가 인식할 수 있도록 만들어주는 것을 의미합니다. 즉 문자열 데이터를 숫자로 변경해주는 것입니다. 위 타이타닉의 feature중에 성별을 예로들면 남자를 0 여자를 1로 변경해주는 것을 Encoding이라고 합니다. Encoding을 하는 방법은 LabelEncoding, One-Hot Encoding이 있습니다. LabelEncoding은 문자열 데이터를 숫자로 변환해주는 것을 의미합니다.
from sklearn.preprocessing import LabelEncoder
animals = ['고양이', '사자', '토끼', '호랑이', '강아지', '강아지']
encoder = LabelEncoder()
encoder.fit(animals)
print(encoder.transform(animals)) # 1 2 3 4 0 0
위 예의 코드는 동물들의 명칭을 가진 list를 인코딩하는 코드입니다. sklearn의 preprocessing에는 LabelEncoder라는 클래스가 있습니다. 이 클래스로 LabelEncoder 객체를 생성하고 fit()을 활용해 Encoding할 데이터를 넣어주면 그 데이터들에 대하여 Encoder를 얻게 됩니다. 즉 위 예에서는 고양이 0, 사자 3, 토끼 1, 호랑이 2, 강아지 4로 Encoding하는 Encoder를 얻게 되는 것입니다. transform()을 활용하면 인코딩된 데이터를 얻을 수 있습니다.
LabelEncoding은 굉장히 간단하게 인코딩된다는 장점이 있습니다. 하지만 인코딩이된 값을 보았을 때 고양이, 토끼, 호랑이, 사자, 강아지는 아무 차이가 없습니다. 하지만 고양이와 토끼의 차이는 1, 고양이와 호랑이의 차이는 2로 Object일 때는 없던 거리의 개념이 인코딩을 하니 생겨나는 문제점을 발견할 수 있습니다.
이러한 문제를 해결하기 위해 One-Hot Encoding 방식이 나오게 되었습니다. One-Hot 직역하면 하나만 뜨겁다는 것을 의미합니다. One-Hot Encoding은 하나만 1 나머지 0의 개념입니다.
from sklearn.preprocessing import OneHotEncoder
import numpy as np
animals = ['고양이', '사자', '토끼', '호랑이', '강아지', '강아지']
animals = np.array(animals).reshape(-1,1)
encoder = OneHotEncoder()
encoder.fit(animals)
print(encoder.transform(animals).toarray())
#[[0. 1. 0. 0. 0.]
# [0. 0. 1. 0. 0.]
# [0. 0. 0. 1. 0.]
# [0. 0. 0. 0. 1.]
# [1. 0. 0. 0. 0.]
# [1. 0. 0. 0. 0.]]
One-Hot Encoding을 위해 들어가는 데이터는 조금 다릅니다. 2차원 행렬에서 각 행에 값이 1개씩 있는 데이터가 들어가야합니다. 따라서 ndarray로 reshape(-1,1)을 사용하여 만들어주었습니다. One-HotEncoder는 LabelEncoder와 동일하게 fit() 후 transform()을 해주어 Encoding 결과를 얻을 수 있습니다. 자기 자신에 대하여만 1이고 다른 값은 0이 되어 One-Hot Encoding이라고 부릅니다.
One-Hot Encoding을 일일히 모든 열에 대하여 처리해주기 위해 numpy를 활용해 2차원 배열로 만들고 fit하고 적용하는데 있어서 굉장히 번거로움을 느낄 것입니다. 이는 Pandas의 get_dummies를 사용하면 간단하게 처리가 가능합니다.
Pandas get_dummies
import pandas as pd
df = pd.DataFrame({animals = ['고양이', '사자', '토끼', '호랑이', '강아지', '강아지']})
pd.get_dummies(df)
판다스의 get_dummies는 데이터프레임의 모든 Object열에 대하여 One-Hot Encoing을 수행한 DataFrame으로 return합니다. 여러개의 Object열이 있을 때 한번에 처리할 수 있는 간편한 방법입니다.
Scaling
데이터의 칼럼들은 각자 다른 값을 가지고 있습니다. 키가 170이 넘는 사람은 많지만 몸무게가 170을 넘는 경우는 거의 없습니다. 이 처럼 칼럼에 따라 다른 규모를 가지게 됩니다. 규모의 차이가 엄청나게 많이 나는 칼럼들을 그대로 학습해버린다면 어떻게 될까요?
0~1사이의 값을 갖는 A라는 feature와 100에서 10000사이의 값을 갖는 B라는 feature가 있다고 가정하겠습니다. A는 아무리 크게 차이가 난다고 해도 고작 1의 차이가 나지만, B는 9900의 차이가 나는 문제가 발생합니다. 즉 A의 차이를 B의 차이에 비해 모델이 사소하다고 잘못 판단하는 경우가 발생할 수 있습니다.
따라서 데이터들의 규모를 맞추어주는 작업이 필요하고, 이를 Scaling이라고 부릅니다. Scaling의 방법은 정규분포를 활용하여 수행하는 방법과 최대,최소값을 활용하는 방법이 있습니다.
StandardScaler
StandardScaler는 정규화를 활용하여 스케일링을 수행하는 방법입니다. 정규화는 평균을 기준으로 어느정도의 표준편차만큼 떨어졌는지에 대한 분포로 평균과 표준편차를 활용하여 스케일링이되는 방법을 의미합니다.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(df)
df_scaled = scaler.transform(df)
StandardScaler 클래스를 활용하여 생성하고 fit과 transform을 하여 스케일링된 결과를 얻을 수 있습니다.
Min-Max Scaler
Min-Max Scaler는 최대값과 최소값을 사용하여 스케일링하는 방법입니다.
수식은 간단합니다. $X_{s}=\frac{X-Min}{Max-Min}$ 각 feature의 Max값과 Min값을 활용해 이 수식에 대입하여 변경해주면 됩니다. 이 경우 제일 큰 값은 1, 제일 작은값은 0을 만족하게 됩니다.
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(df)
df_scale= scaler.transform(df)
MinMax도 동일하게 fit과 transform을 활용하여 처리해주면 됩니다.
참고로 fit과 transform을 동시에 처리하는 방법이 있습니다. 바로 fit_transform()을 활용하는 방법입니다. 하지만 이 방법을 추천하지는 않습니다. 왜냐하면 Scaler를 적용하기 위해 사용한 스케일러를 알아야할 때가 있기 때문입니다.
스케일러를 알고 있어야하는 이유는 매우 중요하며 당연한 것이지만 자주 실수하는 내용입니다. 데이터를 학습할 때 항상 학습 데이터와 테스트 데이터를 분리한다고 말씀드렸습니다. 만약 학습데이터를 전처리하는 과정에서 스케일링을 했다면 테스트데이터를 학습하기 전에 스케일링을 동일하게 해주어야합니다. 이 때 주의해야할 점이 테스트데이터와 학습 데이터는 최대,최소도 그리고 평균값도 서로 다르기 때문에 만약 테스트데이터에 따로 스케일러를 만들어서 학습을 하게된다면 전혀 다른 값이 나오게 되는 문제점이 발생합니다! 테스트 데이터셋을 스케일링할 때 학습데이터에 적용한 스케일러를 활용해 처리해야한다는 것을 반드시 기억해주시기 바랍니다.(학습데이터와 테스트 데이터를 분리전에 전처리하는 경우에는 상관 없습니다.
문제 풀이 : Titanic - Machine Learning from Disaster
지금까지 알아본 Sklearn 라이브러리를 활용하여 데이터를 전처리하는 방법을 알아보았습니다. 이제 라이브러리를 활용하여 데이터를 전처리하고 타이타닉 문제를 해결해보겠습니다.
<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
데이터의 특징을 info()를 출력하여 확인한 결과입니다. 타이타닉 데이터는 총 891개의 데이터가 있음을 알 수 있습니다.
이 데이터의 문제점을 생각해보겠습니다. 먼저 처음에 데이터를 소개할 때 불 필요할 것 같은 feature가 있는 것 같다고 말씀드렸습니다. 이 부분은 학습을 할 때 날려주면 해결할 수 있을 것 같습니다. 그리고 데이터를 보면 Age, Cabin, Embarked feature는 NA값을 가지고 있습니다. 그리고 데이터타입에 object도 학습을 위해 인코딩 해주어야할 것 같습니다. 방금까지 설명드린 스케일링은 크게 필요가 없어보이기 때문에 처리하지 않도록 하겠습니다.
데이터의 info()를 통해 발견된 3가지 문제를 해결하며 이번 글을 마무리하도록 하겠습니다.
- 불 필요한 Feature 제거
- NA 처리
- Encoding
불 필요한 Feature 제거
승객의 번호와 이름 그리고 티켓은 전혀 생존에 큰 영향을 미칠 것 같지 않다고 생각이 됩니다. 그 3가지 Feature를 제거해보겠습니다. 3가지 Feature를 제거하는 것은 코드 한줄이면 해결할 수 있습니다.
df.drop(['PassengerId','Name','Ticket'],axis=1, inplace = True)
inplace = True를 해줌으로 써 drop한 결과를 바로 저장하여 처리할 수 있습니다. axis=1을 해줌으로써 column을 지워주게 됩니다.
NA 처리
결측치를 처리하는 방법은 다양합니다. 그냥 drop을 하는 방법도 있는데 이는 데이터의 손실을 불러일으켜 모델의 성능이 저하될 수도 있으므로 신중하게 사용해야합니다. NA를 처리하는 방법중 하나인 다른 값으로 대체하는 방법을 사용하였습니다. 다른 값으로 대체할 때는 최빈값, 평균, 새로운 값등 다양한 방법이 있습니다.
df['Embarked'].fillna(df['Embarked'].value_counts().index[0],inplace=True)
df['Cabin'].fillna('N',inplace=True)
df['Age'].fillna(df['Age'].mean(),inplace=True)
Embarked의 경우 NA값이 고작 2개이므로 Embarked의 값들 중 제일 많은 값을 찾아서 넣어주었습니다.
Cabin의 경우 NA값이 매우 많기 때문에 따로 'N'으로 넣어주었습니다.
그리고 나이의 경우 평균을 넣어주어 처리하였습니다.
Encoding
마지막으로 Encoding을 해보도록 하겠습니다.
features = ['Cabin', 'Sex', 'Embarked']
le = LabelEncoder()
for feature in features:
le.fit(df[feature])
df[feature] = le.transform(df[feature])
인코딩은 라벨인코더를 활용하였고, 남아있는 3개의 Object에 대하여 처리해주었습니다.
다음 글에서는
이번 글에서는 Sklearn 라이브러리를 활용하여 데이터를 전처리하는 방법에 대하여 알아보았습니다.
다음 시간에는 모델을 평가하는 Evaluation방법에 대하여 알아보도록 하겠습니다.
'Artificial intelligence > Etc' 카테고리의 다른 글
Hello RL : Frozen Lake 소개 및 기본 코드 (1) | 2022.09.15 |
---|---|
Object Detection의 개요 (0) | 2022.07.30 |
모델 평가(Evaluation) (0) | 2022.07.12 |
파이썬으로 머신러닝 시작하기 (0) | 2022.07.03 |
머신러닝이란? (0) | 2021.07.16 |