본문 바로가기
Data/머신러닝

[파이썬머신러닝완벽가이드]02.사이킷런으로 시작하는 머신러닝(2)

by mozi2 2023. 4. 28.
반응형

1.데이터 전처리 

ML 알고리즘 만큼 중요하다.

어떠한 데이터에 기반하냐에 따라 결과도 달라진다 .

* ML 알고리즘을 적용하기 전 데이터에 대해 미리 처리해야할 사항

1) 결손값, Null 값은 허용되지 않는다. 따라서 Null 값을 다른 값으로 변환해야 한다.

  -> 평균값으로 대체하거나 더 정밀한 대체값을 선정해햐 한다.

2) 모든 문자열 값은 이코딩 되서 숫자형으로 변환해야 한다. 

1. 데이터 인코딩

- 방법: 레이블 인코딩(Label encoding) // 원-핫 인코딩(One Hot encoding)

1. 레이블 인코딩

 : 카테고리 피처를 코드형 숫자 값으로 변환하는 것


  ex) 데이터의 상품 구분이 냉장고, TV, 전자레인지 등으로 돼 있다면,
    냉장고:1, TV:2, 전자레인지 :3 이런식으로 숫자형 값으로 변환해준다.

from sklearn.preprocessing import LabelEncoder

items = ['TV','냉장고','전자레인지','컴퓨터','선풍기','믹서','믹서']
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)

print('인코딩 변환 값 :',labels)
인코딩 변환 값 : [0 1 4 5 3 2 2]

이런 방법으로 사용이 가능하며, 각 카테고리별로 인코딩 된 것을 확인할 수 있다.  

또한,  invers_transform()을 통해 인코딩 된 값을 다시 디코딩 할 수도 있다. 

#classes_ : 0부터 순서대로 변환된 인코딩에 대한 원본값을 가짐
print('원본 레이블: ', encoder.classes_, '\n')

# inverse_transform: 인코딩된 레이블을 다시 디코딩
print('디코딩 원본값:',encoder.inverse_transform([4,3,1,1,0,2,4]))
원본 레이블:  ['TV' '냉장고' '믹서' '선풍기' '전자레인지' '컴퓨터'] 

디코딩 원본값: ['전자레인지' '선풍기' '냉장고' '냉장고' 'TV' '믹서' '전자레인지']

레이블링은 이렇게 간단하게 문자열 값을 숫자형 카테고리 값으로 변환한다.
하지만 몇몇 ML 알고리즘에 이렇게 적용할 경우 예측 성능이 떨어질 수 있다.
 -> 이유는, 숫자 값으로 변환이 되며 크고 작은 숫자의 특성이 작용하기 때문이다.

따라서 레이블 인코딩은 선형회귀와 같은 ML 알고리즘에는 적용하지 않아야 한다.
트리계열의 알고리즘은 이러한 특성을 반영하지 않으므로 레이블 인코딩에도 별 문제는 없다.

2. 원 핫 인코딩(One - Hot Encoding)

피처 값의 유형에 따라 새로운 피처를 추가하여 고유 값에 해당하는 칼럼에만 1을 표시하고,
나머지 칼럼에는 0을 표시하는 형식

해당 고유 값에 매칭되는 피처만 1이 되고 , 나머지 피처는 0을 입력하며 이러한 특성으로 원 핫 인코딩(One - Hot Encoding)으로 불린다.

from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder

import numpy as np

items = ['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서']

# LabelEncoder 작업진행
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)

#2차원 데이터로 변환 
labels = labels.reshape(-1,1)

#원핫 인코딩 적용
oh_encoder = OneHotEncoder()
oh_encoder.fit(labels)
oh_labels=oh_encoder.transform(labels)

print('원 핫 인코딩데이터')
print(oh_labels.toarray())
print('원 핫 인코딩 차원')
print(oh_labels.shape)
원 핫 인코딩데이터
[[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]]
원 핫 인코딩 차원
(8, 6)

#OneHotEncoding 을 더 쉽게하는 get_dummies()

import pandas as pd
df = pd.DataFrame({'item':['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서']})

pd.get_dummies(df)

 3. 피처 스케일링과 정규화

서로 다른 변수의 값 범위를 일정한 수준으로맞추는 작업을 피처 스케일링(feature scaling)이라고 한다. 
대표적인 방법으로는 표준화, 정규화가 있다.

- 표준화: 데이터의 피처의 평균이 0이고, 분산이 1인 경우 가우시안 정규분포를 가진 값으로 변환하는 의미
  ex) 새로운 데이터 x_new의 값에서 피처 x의 평균을 빼고 x의 표준편차로 나눈값

- 정규화: 데이터의 값 범위가 1~ 1000000 정도 된다면 변수를 모두 동일한 크기 단위로 비교하기 위해 값을 
  최소 0 /최대 1의 값으로 변화
  ex) 새로운 데이터 x_new 의 값에서 피처 x 의 최솟값을 뺀 값을 피처 x의 최댓값과 최솟값의 차이로 나눈 값으로 변환

1) 표준화(StandardScaler)

 - Sklearn에서 구현한 RBF 커널을 이용하는 SVM, 선형회귀, 로지스틱 회귀는 데이터가 가우시안 분포를 가지고 있다고 가정하고 구현됐기 때문에 표준화 작업을 하는 것이 성능 향상에 도움

from sklearn.datasets import load_iris
import pandas as pd

iris =load_iris()
iris_data=iris.data
df=pd.DataFrame(data=iris_data, columns=iris.feature_names)

print('features들의 평균값:', df.mean())

print('\n features들의 분산값:', df.var())
features들의 평균값: sepal length (cm)    5.843333
sepal width (cm)     3.057333
petal length (cm)    3.758000
petal width (cm)     1.199333
dtype: float64

 features들의 분산값: sepal length (cm)    0.685694
sepal width (cm)     0.189979
petal length (cm)    3.116278
petal width (cm)     0.581006
dtype: float64

from sklearn.preprocessing import StandardScaler

#StandardSCcaler 작업시 스케일 변환 된 numpy ndarray 형식으로 반환되, DataFrame로 변환
scaler= StandardScaler()
scaler.fit(df)
iris_scaled=scaler.transform(df)

iris_df_scaled=pd.DataFrame(data=iris_scaled, columns=iris.feature_names)

print('feature들의 평균 값',iris_df_scaled.mean())
print('\n feature들의 분산 값',iris_scaled.mean())
feature들의 평균 값 sepal length (cm)   -1.690315e-15
sepal width (cm)    -1.842970e-15
petal length (cm)   -1.698641e-15
petal width (cm)    -1.409243e-15
dtype: float64

 feature들의 분산 값 -1.4684549872375404e-15

2) MinMaxScaler

- 데이터 값을 0과 1사이의 범위 값으로 변환(음수 값이 있으며 -1에서 1 값으로 변환)

- 데이터의 분포가 가우시안 분포가 아닐 경우에 적용

from sklearn.preprocessing import MinMaxScaler

min=MinMaxScaler()
min.fit(df)
iris_scaled = min.transform(df)

iris_df_scaled=pd.DataFrame(data=iris_scaled,columns=iris.feature_names)

print('feature 들의 최소값', iris_df_scaled.min())
print('\n feature들의 최대값',iris_df_scaled.max())
feature 들의 최소값 sepal length (cm)    0.0
sepal width (cm)     0.0
petal length (cm)    0.0
petal width (cm)     0.0
dtype: float64

 feature들의 최대값 sepal length (cm)    1.0
sepal width (cm)     1.0
petal length (cm)    1.0
petal width (cm)     1.0
dtype: float64

4. 학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점

- 학습 데이터로만 fit을 수행해야하며, 테스트 데이터에는 학습데이터에서 fit()을 수행한 결과를 transform()해야 함
- 만약 학습, 테스트 값 둘다 fit()을 진행하게 된다면 서로 다른 원본 값이 동일한 값으로 변할 수도 있음.
- 머신러닝 모델은 학습 데이터를 기반으로 학습이 되기 때문에 반드시 test 데이터는 학습 데이터의 기준에 적용되어야 함.

* 가능하다면 전체 데이터의 스케일링을 적용한 뒤에 학습과 테스트 데이터 분리

from sklearn.preprocessing import MinMaxScaler

train_array = np.arange(0, 11).reshape(-1 ,1)
test_array = np.arange(0, 6).reshape(-1 ,1)

min_scaler = MinMaxScaler()

min_scaler.fit(train_array)
train_scaled = min_scaler.transform(train_array)
print('원본 train_array 데이터: ', np.round(train_array.reshape(-1), 2))
print('Scaled된 train_array 데이터: ', np.round(train_scaled.reshape(-1), 2))


test_scaled = min_scaler.transform(test_array)
print('원본 test_array 데이터: ', np.round(test_array.reshape(-1), 2))
print('Scaled된 test_array 데이터: ', np.round(test_scaled.reshape(-1), 2))
원본 train_array 데이터:  [ 0  1  2  3  4  5  6  7  8  9 10]
Scaled된 train_array 데이터:  [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]

원본 test_array 데이터:  [0 1 2 3 4 5]
Scaled된 test_array 데이터:  [0.  0.1 0.2 0.3 0.4 0.5]

5. 사이킷런으로 수행하는 타이타닉 생존자

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

# 타이타닉 데이터는 kaggle에서 다운
df = pd.read_csv('각자 파일 경로/train.csv')
df

1) NULL 값 채우기

df['Age'].fillna(df['Age'].mean(), inplace = True)
df['Cabin'].fillna('N', inplace = True)
df['Embarked'].fillna('N', inplace = True)

df.isnull().sum()
PassengerId    0
Survived       0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Ticket         0
Fare           0
Cabin          0
Embarked       0
dtype: int64

2) 값 분포 확인

print('Age 컬럼의 값 분포 \n', df['Age'].value_counts())
print('Cabin 컬럼의 값 분포 \n', df['Cabin'].value_counts())
print('Embarked 컬럼의 값 분포 \n', df['Embarked'].value_counts())
Age 컬럼의 값 분포 
 29.699118    177
24.000000     30
22.000000     27
18.000000     26
28.000000     25
            ... 
36.500000      1
55.500000      1
0.920000       1
23.500000      1
74.000000      1
Name: Age, Length: 89, dtype: int64
Cabin 컬럼의 값 분포 
 N              687
C23 C25 C27      4
G6               4
B96 B98          4
C22 C26          3
              ... 
E34              1
C7               1
C54              1
E36              1
C148             1
Name: Cabin, Length: 148, dtype: int64
Embarked 컬럼의 값 분포 
 S    644
C    168
Q     77
N      2
Name: Embarked, dtype: int64

- Cabin 속성에서 중복이 발생하기 때문에 앞글자만 추출

df['Cabin'] = df['Cabin'].str[:1]
print(df['Cabin'].head())
0    N
1    C
2    N
3    C
4    N
Name: Cabin, dtype: object

3) 중간확인 -어떤 유형의 승객 생존 확률이 높은지

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 = 'Sex', y = 'Survived', data = df)

sns.barplot(x = 'Pclass', y = 'Survived', hue = 'Sex',data = df)

# 입력 age에 따른 구분 값을 반환하는 함수 설정. DataFrame의 apply lambda 식에 사용

def get_category(age):
  cat = ''
  if age <= -1: cat = 'Unkown'
  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

# 막대 그래프의 크기 설정
plt.figure(figsize = (10, 10))

# X축의 값을 순차적으로 표현하기 위한 설정
group_names = ['Unkown','Baby','Child','Teenager','Student','Young Adult','Adult','Elderly']

# lambda 식에 위헤서 생성한 get_category() 함수를 반환값으로 지정
# get_category(x)는 입력값으로 'Age' 칼럼 값을 받아서 해당하는 cat 반환

df['Age_cat'] = df['Age'].apply(lambda x : get_category(x))
sns.barplot(x = 'Age_cat', y = 'Survived', hue = 'Sex', data = df, order = group_names)
df.drop('Age_cat', axis = 1, inplace = True)

# 문자열 LabelEncoder화

from sklearn.preprocessing import LabelEncoder
label = LabelEncoder()

def encode_features(dataDF):
  features = ['Cabin', 'Sex','Embarked']
  for i in features:
     le= label.fit(dataDF[i])
     dataDF[i] = le.transform(dataDF[i])

  return dataDF

df = encode_features(df)
df.head()

print(df['Cabin'].value_counts())
7    687
2     59
1     47
3     33
4     32
0     15
5     13
6      4
8      1
Name: Cabin, dtype: int64

4) EDA 함수 총 정리

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):
  features = ['Cabin', 'Sex','Embarked']
  for i in features:
     le= label.fit(df[i])
     df[i] = le.transform(df[i])

  return df


def transform_features(df):
  df = fillna(df)
  df = drop_features(df)
  df = format_features(df)
  return df

5) 데이터 재로드 후 전처리 함수 적용

import warnings
warnings.filterwarnings('ignore')

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

df = pd.read_csv('개인 파일 경로/train.csv')

df = transform_features(df)

X = df.drop(columns = ['Survived'])
y = df[['Survived']]

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size = 0.2, random_state = 11)

dt_clf = DecisionTreeClassifier(random_state = 11)
rf_clf = RandomForestClassifier(random_state = 11)
lr_clf = LogisticRegression()

# DecisionTreeClassifier의 학습 / 예측 / 평가
dt_clf.fit(X_train, y_train)
predict_1 = dt_clf.predict(X_val)
print('DecisionTreeClassifier 정확도 : ' , accuracy_score(y_val, predict_1))

# RandomForestClassifier 학습 / 예측 / 평가
rf_clf.fit(X_train, y_train)
predict_2 = rf_clf.predict(X_val)
print('RandomForestClassifier 정확도 : ' , accuracy_score(y_val, predict_2))

# LogisticRegression 학습 / 예측 / 평가
lr_clf.fit(X_train, y_train)
predict_3 = lr_clf.predict(X_val)
print('LogisticRegression 정확도 : ' , accuracy_score(y_val, predict_3))
DecisionTreeClassifier 정확도 :  0.7988826815642458
RandomForestClassifier 정확도 :  0.8435754189944135
LogisticRegression 정확도 :  0.8659217877094972

5) 데이터 재로드 후 전처리 함수 적용

import warnings
warnings.filterwarnings('ignore')

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

df = pd.read_csv('개인 파일 경로/train.csv')

df = transform_features(df)

X = df.drop(columns = ['Survived'])
y = df[['Survived']]

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size = 0.2, random_state = 11)

dt_clf = DecisionTreeClassifier(random_state = 11)
rf_clf = RandomForestClassifier(random_state = 11)
lr_clf = LogisticRegression()

# DecisionTreeClassifier의 학습 / 예측 / 평가
dt_clf.fit(X_train, y_train)
predict_1 = dt_clf.predict(X_val)
print('DecisionTreeClassifier 정확도 : ' , accuracy_score(y_val, predict_1))

# RandomForestClassifier 학습 / 예측 / 평가
rf_clf.fit(X_train, y_train)
predict_2 = rf_clf.predict(X_val)
print('RandomForestClassifier 정확도 : ' , accuracy_score(y_val, predict_2))

# LogisticRegression 학습 / 예측 / 평가
lr_clf.fit(X_train, y_train)
predict_3 = lr_clf.predict(X_val)
print('LogisticRegression 정확도 : ' , accuracy_score(y_val, predict_3))
DecisionTreeClassifier 정확도 :  0.7988826815642458
RandomForestClassifier 정확도 :  0.8435754189944135
LogisticRegression 정확도 :  0.8659217877094972

6) 교차검증 - KFold, cross_var_score(), GridSearchCV / 폴드 수 = 5

 1.KFold

from sklearn.model_selection import KFold


def exec_kfold(clf, folds = 5):
  kfold = KFold(n_splits = folds)
  scores = []

  # KFold 교차 검증 수행
  for iter_count, (train_index, test_index) in enumerate(kfold.split(X)):
    # X 데이터에서 교차 검증별로 학습과 검증 데이터를 가리키는 index 생성
    X_train, X_val = X.values[train_index], X.values[test_index]
    y_train, y_val = y.values[train_index], y.values[test_index]

    clf.fit(X_train, y_train)
    predictions = clf.predict(X_val)
    accuracy = accuracy_score(y_val, predictions)

    scores.append(accuracy)
    print('교차 검증 {0} 정확도: {1:.4f}'.format(iter_count, accuracy))
  

  mean_score = np.mean(scores)
  print('평균 정확도: {0:.4f}'.format(mean_score))

exec_kfold(dt_clf, folds = 5)
교차 검증 0 정확도: 0.7486
교차 검증 1 정확도: 0.7640
교차 검증 2 정확도: 0.8202
교차 검증 3 정확도: 0.7809
교차 검증 4 정확도: 0.7921
평균 정확도: 0.7812

2. cross_val_score()

  • KFold와 성능 차이가 있는 이유는 StratifiedKFold를 사용해 폴드 세트를 구분하기 때문이다.
from sklearn.model_selection import cross_val_score

scores = cross_val_score(dt_clf, X, y, 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.7486
교차 검증 1 정확고 : 0.7753
교차 검증 2 정확고 : 0.8090
교차 검증 3 정확고 : 0.7584
교차 검증 4 정확고 : 0.8034
평균 정확도 : 0.7789

3. GridSearchCV

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_clf = GridSearchCV(dt_clf, param_grid = parameters, scoring = 'accuracy', cv = 5)
grid_clf.fit(X_train, y_train)

print("최적의 파라미터 : ", grid_clf.best_params_)
print("최고의 정확도 : {0:.4f}".format(grid_clf.best_score_))

best_clf = grid_clf.best_estimator_

# GridSearchCV의 최적 하이퍼 파라미터로 학습된 Estimator로 예측 및 수행 평가 수행
dpredictions = best_clf.predict(X_val)
accuracy = accuracy_score(y_val, dpredictions)

print('테스트 세트에서의 정확도 : {0:.4f}'.format(accuracy))
최적의 파라미터 :  {'max_depth': 5, 'min_samples_leaf': 1, 'min_samples_split': 5}
최고의 정확도 : 0.7993
테스트 세트에서의 정확도 : 0.8659
728x90
반응형