배운 내용
- DataFrame.info()
- DataFrame.fillna()
- DataFrame.isnull()
- Series.value_counts()
- DataFrame.groupby()
- DataFrame.drop()
- sns.barplot(x, y, hue, data=DataFrame)
- LabelEncoder.fit(), LabelEncoder.transform()
- train_test_split()
- enumerate(data)
- DataFrame.values[index]
- KFold
- cross_val_score()
- GridSearchCV
타이타닉 탑승자 데이터에 대한 개략적인 설명
- Passengerid: 탑승자 데이터 일련번호
- survived: 생존 여부, 0 = 사망, 1 = 생존
- pclass: 티켓의 선실 등급, 1 = 일등석, 2 = 이등석, 3 = 삼등석
- sex: 탑승자 성별
- name: 탑승자 이름
- Age: 탑승자 나이
- sibsp: 같이 탑승한 형제자매 또는 배우자 인원수
- parch: 같이 탑승한 부모님 또는 어린이 인원수
- ticket: 티켓 번호
- fare: 요금
- cabin: 선실 번호
- embarked: 중간 정착 항구 C = Cherbourg, Q = Queenstown, S = Southampton
타이타닉 예제 시작
파이썬의 대표적인 시각화 패키지인 맷플롯립과 시본을 이용해 차트와 그래프도 함께 시각화하며 데이터 분석을 진행하겠습니다.
DataFrame.info()로 로딩된 데이터 칼럼 타입을 확인해 보겠습니다. RangeIndex는 인덱스의 범위를 나타내므로 전체 로우 수를 알 수 있습니다. 891개의 로우로 구성됩니다.
판다스의 Dtype 중 object 타입은 string 타입으로 봐도 무방합니다. 판다스는 넘파이 기반으로 만들어졌고 넘파이의 String 타입이 길이 제한이 있어서 이에 대한 구분을 위해 object 타입으로 명기한 것입니다.
Non-Null Count는 Null이 아닌 값을 센 갯수입니다. 사이킷런 머신러닝 알고리즘은 Null 값을 허용하지 않으므로 어떻게 Null 값을 처리할지 결정해야 합니다.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
titanic_df = pd.read_csv('/content/drive/MyDrive/military/train.csv')
print('\n ### 학습 데이터 정보 ### \n')
print(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
None
'''
DataFrame.fillna() 함수를 사용해 간단히 Null 값을 평균 또는 고정값으로 변경하겠습니다. Age의 경우 평균 나이, 나머지 칼럼은 'N' 값으로 변경합니다. 그 후 Null 값이 없는지 확인합니다.
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
'''
Cabin(선실)의 경우 N이 687건으로 가장 많기도 하고 속성값이 제대로 정리가 되지 않은 것 같습니다. 예를 들어 'C23 C25 C27'과 같이 여러 Cabin이 한꺼번에 표기된 Cabin이 4건이 됩니다.
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 값 분포:
B96 B98 4
G6 4
C23 C25 C27 4
F2 3
C22 C26 3
..
C62 C64 1
D50 1
C91 1
C90 1
C32 1
Name: Cabin, Length: 147, dtype: int64
Embarked 값 분포:
S 644
C 168
Q 77
Name: Embarked, dtype: int64
'''
Cabin의 경우 선실 등급을 나타내는 첫 번째 알파벳이 중요해 보입니다. 왜냐하면 이 시절은 부자와 가난한 사람의 차별이 더 심하던 시절이었기 때문에 일등실에 투숙한 사람이 삼등실에 투숙한 사람보다 더 살아날 확률이 높았을 것이기 때문입니다. Cabin 속성의 앞 문자만 추출하겠습니다.
titanic_df['Cabin'] = titanic_df['Cabin'].str[:1]
print(titanic_df['Cabin'].head(3))
'''
0 NaN
1 C
2 NaN
Name: Cabin, dtype: object
'''
groupby.agg를 이용했다. DataFrameGroupBy['column']을 통해 Survived를 count했다.
titanic_df.groupby(['Sex', 'Survived'])['Survived'].count()
'''
Sex Survived
female 0 81
1 233
male 0 468
1 109
Name: Survived, dtype: int64
'''
이 결과를 통해 여자보다 남자가 더 많이 사망했음을 알 수 있습니다.
sns.barplot(x, y, data=DataFrame)
Seaborn 패키지로 데이터를 시각화하겠습니다. Seaborn은 matplotlib에 기반하고 있으며 DataFrame과의 연동이 간편하다는 장점이 있습니다.
sns.barplot(x='Pclass', y='Survived', hue='Sex', data=titanic_df)
'''
결과1
'''
x축을 'Pclass' 칼럼으로 y축을 'Survived' 칼럼으로 설정했습니다. hue는
데이터가 정렬되는 기준이 됩니다.
x, y, hue는 list, ndarray, Series로 이루어진 칼럼만 올 수 있습니다. 이 칼럼들을 가져올 DataFrame을 titanic_df로 설정했습니다.
부자일 수록, 즉 Pclass가 1인 사람이 더 많이 생존했음을 알 수 있습니다. 여자 생존자는 3등실에서 확연히 줄어들었고, 남자 생존자는 1등실에서 월등히 많았습니다.
Age에 따른 생존자 분류
Age의 경우 값 종류가 많기 때문에 범위별로 분류해 카테고리 값을 할당하겠습니다.
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()함수를 반환값으로 지정
titanic_df['Age_cat']=titanic_df['Age'].apply(lambda x: get_category(x))
# group_names 순으로 정렬
sns.barplot(x='Age_cat', y='Survived', hue='Sex', data=titanic_df, order=group_names)
'''결과2'''
# Age_cat 칼럼을 drop
titanic_df.drop('Age_cat', axis=1, inplace=True)
Age에 따라 생존자 수에 차이가 확연히 있다는 것을 알 수 있습니다. 여자 Baby의 경우는 생존자가 크지만, 여자 Child의 경우 생존확률이 가장 낮은 것이 그 예입니다. Sex, Pclass, Age에 따른 생존자 수의 차이가 있으므로 이 Column들은 중요한 피처임을 알 수 있습니다.
문자열 카테고리 피처를 숫자형 카테고리 피처로 변환하기
LabelEncoder 클래스를 이용해 레이블 인코딩을 적용하겠습니다. LabelEncoder 객체는 카테고리 값의 유형 수에 따라 0 ~ (카테고리 수-1) 까지의 숫자값으로 변화합니다. 사이킷런의 전처리 모듈의 대부분 인코딩 API는 fit(), transform()으로 데이터를 변환합니다.
from sklearn import preprocessing
def encode_features(dataDF):
features = ['Cabin', 'Sex', 'Embarked']
for feature in features:
# LableEncoder()가 Series를 인식하지 못해서 list형으로 바꿔주었다.
data = np.array(dataDF[feature]).tolist()
le = preprocessing.LabelEncoder()
le = le.fit(data)
dataDF[feature] = le.transform(data)
return dataDF
titanic_df = encode_features(titanic_df)
titanic_df.head()
'''결과3'''
Sex, Cabin, Embarked가 숫자형으로 바뀐 것을 알 수 있습니다.
전처리 함수 만들기
데이터를 전체적으로 전처리하는 함수 transform_features()를 만들겠습니다. Null 처리, 포매팅(drop), 인코딩을 내부 함수로 구성했습니다.
# 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:
# list로 자료형 바꾸기
data = np.array(df[feature]).tolist()
le = preprocessing.LabelEncoder()
le = le.fit(data)
df[feature] = le.transform(data)
return df
# 앞에서 설정한 데이터 전처리 함수 호출
def transform_features(df):
df = fillna(df)
df = drop_features(df)
df = format_features(df)
return df
타이타닉 생존자 데이터 세트를 다시 로드한 후, survived 속성을 클래스 결정값 데이터 세트로 만들고 나머지 속성을 피처 데이터 세트로 만들겠습니다.
# 원본 데이터를 다시 로딩하고, 피처 데이터 세트와 레이블 데이터 세트 추출
titanic_df = pd.read_csv('/content/drive/MyDrive/military/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()
train_test_split()을 통해 테스트 데이터를 추출합니다. 테스트 데이터 세트의 크기는 전체의 20%입니다.
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 알고리즘인 결정 트리, 랜덤 포레스트, 로지스틱 회귀를 이용해 타이타닉 생존자를 예측해보겠습니다. 로지스틱 회귀는 이름은 회귀이지만, 아주 강력한 분류 알고리즘입니다. 사이킷런에서 결정 트리를 위해 DecisionTreeClassifier, 랜덤 포레스트를 위해 RandomForestClassifier, 로지스틱 회귀를 위해서 LogisticRegression 클래스를 제공합니다.
train_test_split()으로 분리한 학습, 테스트 데이터 세트를 가지고 머신러닝 모델을 학습(fit)하고 예측(predict)할 것입니다. 예측 성능 평가는 accuracy_score()로 할 것입니다.
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)
dt_score = accuracy_score(y_test, dt_pred)
print('DecisionTreeClassifier 정확도: {0:.4f}'.format(dt_score))
# RandomForestClassifier 학습/예측/평가
rf_clf.fit(X_train, y_train)
rf_pred = rf_clf.predict(X_test)
rf_score = accuracy_score(y_test, rf_pred)
print('RandomForestClassifier 정확도: {0:.4f}'.format(rf_score))
# LogisticRegression 학습/예측/평가
lr_clf.fit(X_train, y_train)
lr_pred = lr_clf.predict(X_test)
lr_score = accuracy_score(y_test, lr_pred)
print('LogisticRegression 정확도: {0:.4f}'.format(lr_score))
'''
DecisionTreeClassifier 정확도: 0.7877
RandomForestClassifier 정확도: 0.8547
LogisticRegression 정확도: 0.8492
'''
아직 최적화 작업을 수행하지 않았고, 데이터 양도 충분하지 않기 때문에 어떤 알고리즘이 가장 성능이 좋다고 평가할 수는 없습니다. 다음으로는 교차검증으로 결정 트리 모델을 좀 더 평가해보겠습니다. KFold 클래스, cross_val_score(), GridSearchCV 클래스를 모두 사용합니다.
KFold를 이용한 교차 검증
enumerate함수를 사용하면, index와 데이터가 튜플형으로 묶인 list가 반환됩니다. Enumerate() in Python (tutorialspoint.com) DataFrame.values[index]를 사용했는데, index자리에 list가 들어갈 수 있습니다.
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
'''
cross_val_score()
K폴드의 평균정확도와 cross_val_score()의 평균 정확도가 약간 다른데, 이는 cross_val_score()가 StratifiedKFold를 이용해 폴드 세트를 분할하기 때문입니다.
from sklearn.model_selection import cross_val_score
scores = cross_val_score(dt_clf, X_titanic_df, y_titanic_df, scoring='accuracy',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
'''
GridSearchCV
max_depth, min_saples_split, min_samples_leaf의 하이퍼 파라미터를 변경하면서 성능을 측정하고, 최적의 하이퍼 파라미터로 학습된 Estimator를 이용해 예측 정확도를 출력하겠습니다.
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
'''
하이퍼 파라미터를 최적화 시킨 후 정확도가 8% 이상 증가했습니다. 일반적으로 이 정도 수준으로 증가하기는 매우 어렵습니다. 테스트용 데이터 세트가 작기 때문에 수치상으로 성능이 많이 증가한 것처럼 보입니다.
정리
사이킷런만큼 다양한 머신러닝 기능을 제공하는 패키지는 없습니다.
사이킷런은 데이터 가공 및 변환 과정의 전처리 작업, 학습 데이터와 테스트 데이터로 분리하는 데이터 세트 분리 작업을 거친 후 학습 데이터를 기반으로 머신러닝 알고리즘을 적용해 모델을 학습시킵니다. 그리고 학습된 모델을 기반으로 테스트 데이터에 대한 예측을 수행하고, 이렇게 예측된 결과값과 실제 결과값을 비교해 머신러닝 모델에 대한 평가를 수행하는 방식으로 구성됩니다.
데이터의 전처리 작업은 오류 데이터의 보정이나 결손값(Null) 처리 등의 데이터 클렌징 작업, 레이블 인코딩이나 원-핫 인코딩과 같은 인코딩 작업, 데이터의 스케일링/정규화 작업 등으로 머신러닝 알고리즘이 최적으로 수행될 수 있게 데이터를 사전 처리하는 것입니다.
머신러닝 모델을 적합하게 학습, 평가하기 위해 KFold, StratifiedKFold, cross_val_score() 등의 다양한 클래스와 함수를 제공합니다. 또한, 머신러닝 모델의 최적의 하이퍼 파라미터를 교차 검증을 통해 추출하기 위해 GridSearchCV를 제공합니다.
출처: 파이썬 머신러닝 완벽 가이드 (권민철)
'파이썬 머신 러닝 완벽 가이드' 카테고리의 다른 글
피마 인디언 당뇨병 예측 (0) | 2021.11.05 |
---|---|
평가 (0) | 2021.10.27 |
사이킷런 scikit-learn (0) | 2021.10.15 |
Pandas (0) | 2021.10.10 |
Numpy (0) | 2021.10.09 |