AIFFEL(220107)_Iris의 세 가지 품종, 분류해볼 수 있겠어요?


학습목표

  1. scikit-learn에 내장된 예제 데이터셋의 종류를 알고 활용할 수 있다.
  2. scikit-learn에 내장된 분류 모델들을 학습시키고 예측해 볼 수 있다.
  3. 모델의 성능을 평가하는 지표의 종류에 대해 이해하고, 활용 및 확인해 볼 수 있다.
  4. Decision Tree, XGBoost, RandomForest, 로지스틱 회귀 모델을 활용해서 간단하게 학습 및 예측해 볼 수 있다.
  5. 데이터셋을 사용해서 스스로 분류 기초 실습을 진행할 수 있다.

📖 Iris의 세 가지 품종, 분류해 볼까요?_(1) 붓꽃 분류 문제

붓꽃 분류, 어떤 데이터로 할 건데?


붓꽃 데이터가 예제 데이터로 많이 쓰이는 이유는 사이킷런(scikit-learn) 에 내장된 데이터이기 때문이기도 하다.

사이킷런에서 제공하는 데이터 : Scikit-learn 데이터셋
scikit-learn은 간단하고 작은 데이터셋인 Toy datasets과 비교적 복잡하고 현실 세계를 반영한 Real world datasets, 두 가지 종류의 데이터셋을 제공한다.

<붓꽃 데이터>

데이터셋에는 총 150개의 데이터, 각 데이터에는 4개(sepal, petal 각각의 길이와 폭)의 정보가 담겨있다. 카테고리를 나타내는 클래스는 세 가지(setosa, versicolour, virginica)가 있다.

데이터를 얼마나 이해하고 있느냐는 그 데이터를 활용한 결과와 성능에 중대한 요소가 된다. 따라서 어떤 데이터셋을 다루든, 그 데이터셋이 담고 있는 정보를 먼저 잘 확인하고 시작하는 것이 좋다.

📖 Iris의 세 가지 품종, 분류해 볼까요?_(2) 데이터 준비, 그리고 자세히 살펴보기는 기본!

scikit-learn의 예제 데이터셋은 다음과 같이 sklearn 라이브러리의 datasets 패키지 안에 있다.

load_iris를 import 해와서 iris 데이터를 로딩해 본다.

from sklearn.datasets import load_iris

iris = load_iris()

print(dir(iris))
# dir()는 객체가 어떤 변수와 메서드를 가지고 있는지 나열함
# 실행결과
['DESCR', 'data', 'data_module', 'feature_names', 'filename', 'frame', 'target', 'target_names']

iris에는 어떤 정보들이 담겼을지, keys() 라는 메서드로 확인해 본다.

iris.keys()
# 실행결과
dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module'])

iris에는 data, target, frame, target_names, DESCR, feature_names, filename 까지 총 7개의 정보가 담겨있다.

가장 중요한 데이터는 iris_data 변수에 저장한 후, 데이터의 크기를 확인해본다.

iris_data = iris.data  🔎 둘의 차이가 뭐냐

print(iris_data.shape) 
#shape는 배열의 형상정보를 출력
# 실행결과
(150, 4)

위에서 확인했듯 총 150개의 데이터가 각각 4개의 정보를 담고 있는 것으로 보인다.

예시로 하나의 데이터를 확인해본다.

iris_data[0]
# 실행결과
array([5.1, 3.5, 1.4, 0.2])  # sepal length, sepal width, petal length, petal width

우리는 꽃잎과 꽃받침의 길이가 주어지는 경우 그 꽃은 세 가지 붓꽃 품종(setosa, versicolor, virginica) 중 어떤 것인지 맞추어 보고 싶은 것이다.
따라서 머신러닝 모델에게 꽃잎, 꽃받침의 길이와 폭 정보를 입력했을 때, 붓꽃의 품종을 출력하도록 학습을 시켜야 한다.
이렇게 머신러닝 모델이 출력해야 하는 정답을 라벨(label) 또는 타겟(target) 이라고 한다.

붓꽃 데이터에서 타겟 정보는 target으로 볼 수 있다.

iris_label = iris.target 
# iris 데이터의 target을 iris_label이라는 변수에 저장
print(iris_label.shape)
iris_label
# 실행결과
(150,) 🔎 여기서 왜 (, 150)이 아니라 (150,)로 출력이 되는걸까?
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

길이와 형태를 보면 총 150개의 데이터가 들어있고, 각 값은 0, 1, 또는 2로 나타난다. 이 숫자(라벨의 이름)들은 target_names에서 확인할 수 있다.

iris.target_names
# 실행결과
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')

이 순서 그대로 0이라면 setosa, 1이라면 versicolor, 2라면 virginica를 나타낸다.

나머지 변수들도 확인해 본다. DESCR에는 데이터셋의 설명이 담겨있다.

print(iris.DESCR) 🔎 DESCR이 뭐냐
# 실행결과
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')


feature_names에는 다음과 같이 4개의 각 feature에 대한 설명이 담겨있다.

iris.feature_names
# 실행결과
['sepal length (cm)',
 'sepal width (cm)',
 'petal length (cm)',
 'petal width (cm)']

filename에는 데이터셋의 전체 이름을 보여준다. iris데이터셋은 csv 형태의 파일로 되어있다.

iris.filename
# 실행결과
'iris.csv'

📖 첫 번째 머신러닝 실습, 간단하고도 빠르게!_(1) 머신러닝 모델을 학습시키기 위한 문제지와 정답지 준비

데이터 준비가 끝났으므로 분류하는 머신러닝 모델을 학습시켜본다. iris 데이터는 행과 열이 있는 2차원 데이터이므로 *pandas를 활용해 본다.

* pandas : 판다스라고 불리는 이 라이브러리는 파이썬에서 표 형태로 이루어진 2차원 배열 데이터를 다루는 데에 가장 많이 쓰이는 도구이다. 표 데이터를 활용해서 데이터 분석을 하기도 하고, 대형 데이터의 여러 통계량을 다루는 것에도 최적화되어 있다.

import pandas as pd   # pandas 임포트, pd라는 약어로 많이 사용

print(pd.__version__)  # pandas 버전 확인

붓꽃 데이터셋을 pandas가 제공하는 DataFrame이라는 자료형으로 변환

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

DataFrame을 만들면서 data에는 iris_data를 넣어주고, 각 컬럼에는 feature_names로 이름을 붙여준다.

정답 데이터도 함께 있다면 데이터를 다루가 더 편하므로 label이란 칼럼을 추가한다.

iris_df["label"] = iris.target
iris_df


여기서 4가지의 feature 데이터들은 바로 머신러닝 모델이 풀어야 하는 문제지이고 0, 1, 2와 같이 표현된 label 데이터는 머신러닝 모델에게 정답지라고 할 수 있다.

<머신러닝 모델에게 문제지와 정답지>

  • 문제지 : 머신러닝 모델에게 입력되는 데이터. feature라고 부르기도 한다. 변수 이름으로는 X를 많이 사용한다.
  • 정답지 : 머신러닝 모델이 맞혀야 하는 데이터. label 또는 target이라고 부르기도 한다. 변수 이름으로는 y를 많이 사용한다.

머신러닝 모델을 학습시키려면 학습에 사용하는 training dataset모델의 성능을 평가하는 데 사용하는 test dataset으로 데이터셋을 나누는 작업이 필요하다.
(150개의 데이터가 있지만, 이 150개를 모두 학습시키는 데에 사용해버리면 학습이 완료된 모델의 성능을 공정하게 평가할 수 없기 때문)

데이터셋을 분리하는 것은 scikit-learn이 제공하는 train_test_split이라는 함수로 할 수 있다.

sklearn.model_selection 패키지의 train_test_split을 활용하여, training datasettest dataset을 간단히 분리해 본다.

from sklearn.model_selection import train_test_split

X_train<, X_test, y_train, y_test = train_test_split(iris_data, iris_label, test_size=0.2, random_state=7)

print('X_train 개수: ', len(X_train),', X_test 개수: ', len(X_test))
# 실행결과
X_train 개수:  120 , X_test 개수:  30

이렇게 각 파라미터를 넣어줌으로써 우리는 학습용 데이터와 테스트용 데이터를 생성하며 각 데이터에서 4개의 feature 데이터만 있는 X, 그리고 정답 label 데이터만 있는 y를 얻을 수 있다.

Xy 뒤에 붙은 traintest는 당연히 위에서 말했던 학습용 데이터와 테스트용 데이터를 뜻한다.
test_size로는 test dataset의 크기를 조절할 수 있다. 0.2는 전체의 20%를 테스트 데이터로 사용하겠다는 것을 나타낸다.
마지막으로 쓰인 random_statetrain 데이터와 test 데이터를 분리(split)하는데 적용되는 랜덤성을 결정한다. 랜덤을 조절할 수 있는 값이 바로 random_state, 또는 random_seed이다. 이 값이 같다면 코드는 항상 같은 랜덤 결과를 나타낸다.

* sklearn.model_selection.train_test_split(*arrays, test_size=None, train_size=None, random_state=None, shuffle=True, stratify=None) random_state=None으로 default되어있기 때문에 따로 지정해주지 않아도 random하게 섞여서 만들어 진다. 동일한 결과를 보기 위해서 지정

X_train부터 y_test까지 만들어진 데이터셋을 확인해 본다.

X_train.shape, y_train.shape
X_test.shape, y_test.shape
# 실행결과
((120, 4), (120,))
((30, 4), (30,))

y를 확인해본다.

y_train, y_test
# 실행결과
(array([2, 1, 0, 2, 1, 0, 0, 0, 0, 2, 2, 1, 2, 2, 1, 0, 1, 1, 2, 0, 0, 0,
        2, 0, 2, 1, 1, 1, 0, 0, 0, 1, 2, 1, 1, 0, 2, 0, 0, 2, 2, 0, 2, 0,
        1, 2, 1, 0, 1, 0, 2, 2, 1, 0, 0, 1, 2, 0, 2, 2, 1, 0, 1, 0, 2, 2,
        0, 0, 2, 1, 2, 2, 1, 0, 0, 2, 0, 0, 1, 2, 2, 1, 1, 0, 2, 0, 0, 1,
        1, 2, 0, 1, 1, 2, 2, 1, 2, 0, 1, 1, 0, 0, 0, 1, 1, 0, 2, 2, 1, 2,
        0, 2, 1, 1, 0, 2, 1, 2, 1, 0]),
 array([2, 1, 0, 1, 2, 0, 1, 1, 0, 1, 1, 1, 0, 2, 0, 1, 2, 2, 0, 0, 1, 2,
        1, 2, 2, 2, 1, 1, 2, 2]))

앞에서 확인했던 label과는 다르게 0, 1, 2가 무작위로 섞여 있다.

📖 첫 번째 머신러닝 실습, 간단하고도 빠르게!_(2) 첫 번째 머신러닝 모델 학습시키기

지도학습(Supervised Learning)은 정답이 있는 문제에 대해 학습하는 것을 말한다. 붓꽃 품종 문제는 지도학습에 해당한다.
지도학습은 다시 두 가지로 나눌 수 있는데, 바로 분류(Classification)와 회귀(Regression)이다. 분류는 입력받은 데이터를 특정 카테고리 중 하나로 분류해내는 문제이고 회귀는 입력받은 데이터에 따라 특정 필드의 수치를 맞히는 문제이다.

붓꽃 품종 문제는 feature 데이터를 입력받으면 setosa, versicolor, virginica 세 가지 품종 중 하나로 분류해내는, 분류 문제이다.

회귀 문제는 예를 들어, 집에 대한 정보(평수, 위치, 층수 등)를 입력받아 그 집의 가격을 맞히는 문제는 회귀 문제에 해당한다. 즉, 카테고리를 분류하는 것이 아니라, 실제 값의 수치를 어림해서 맞히는 것이다.

따라서 붓꽃 문제는

  • 첫 번째, 머신러닝 중 정답이 있고 그 정답을 맞히기 위해 학습하는 지도 학습(Supervised Learning)이며,
  • 지도학습 중에서는 특정 카테고리 중 주어진 데이터가 어떤 카테고리에 해당하는지를 맞히는 분류(Classification)문제

지도학습 중에서도 분류를 할 수 있는 모델을 사용한다.
분류 모델은 아주 다양하지만, Decision Tree 모델을 사용해 보도록 한다.
Decision Tree는 직관적이면서도 간단하게 사용할 수 있어 분류 문제를 풀 때 가장 기본적으로 쓰이는 모델 중 하나이다.
의사결정나무-ratsgo님의 블로그

Decision Tree는 sklearn.tree 패키지 안에 DecisionTreeClassifier 라는 이름으로 내장되어 있다. 모델을 import해서 가져오고, decision_tree 라는 변수에 모델을 저장한다.

from sklearn.tree import DecisionTreeClassifier

decision_tree = DecisionTreeClassifier(random_state=32)
print(decision_tree._estimator_type)
# 실행결과
classifier

모델 학습시키기

decision_tree.fit(X_train, y_train)
# 실행결과
DecisionTreeClassifier(random_state=32)

여기서 눈여겨 볼 점은 학습하는 메서드의 이름이 fit이라는 점이다.
training dataset 으로 모델을 학습시킨다는 것은, 달리 말하면 training dataset에 맞게 모델을 fitting, 즉 맞추는 것이라고 할 수 있다.
training dataset에 있는 데이터들을 통해 어떠한 패턴을 파악하고, 그 패턴에 맞게 예측을 할 수 있도록 학습되기 때문이다.
즉, 다른 말로 하면 모델은 training dataset에 존재하지 않는 데이터에 대해서는 정확한 정답 카테고리가 무엇인지 알지 못한다. 다만 training dataset을 통해 학습한 패턴으로 새로운 데이터가 어떤 카테고리에 속할지 예측할 뿐이다.
그렇기 때문에 새로운 데이터에 대해서도 잘 맞출 수 있기 위해서는 training dataset이 어떻게 구성되어 있는지가 매우 중요하다.
(더 다양한, 더 일반화 된 데이터로 학습이 될수록 새로운 데이터에 대해서도 잘 맞출 수 있기 때문)

📖 첫 번째 머신러닝 실습, 간단하고도 빠르게!_(3) 첫 번째 머신러닝 모델 평가하기

학습이 완료되었으니 test 데이터로 예측해 본다.

y_pred = decision_tree.predict(X_test)
y_pred
# 실행결과
array([2, 1, 0, 1, 2, 0, 1, 1, 0, 1, 2, 1, 0, 2, 0, 2, 2, 2, 0, 0, 1, 2,
       1, 1, 2, 2, 1, 1, 2, 2])

X_test 데이터에는 정답인 label이 없고 feature 데이터만 존재했다. 따라서 학습이 완료된 decision_tree 모델에 X_test 데이터로 predict를 실행하면 모델이 예측한 y_pred을 얻게 된다.

실제 정답인 y_test와 비교해 얼마나 맞았는지 확인해본다.

y_test
# 실행결과
array([2, 1, 0, 1, 2, 0, 1, 1, 0, 1, 1, 1, 0, 2, 0, 1, 2, 2, 0, 0, 1, 2,
       1, 2, 2, 2, 1, 1, 2, 2])

scikit-learn에서 성능 평가에 대한 함수들이 모여있는 sklearn.metrics 패키지를 이용하면 예측한 결과에 대한 수치를 조금 더 편리하게 확인할 수 있다.

성능을 평가하는 방법에도 다양한 척도가 있는데, 그 중 정확도(Accuracy)를 간단히 확인해 본다.

from sklearn.metrics import accuracy_score

accuracy = accuracy_score(y_test, y_pred)
accuracy
# 실행결과
0.9   # 90%의 정확도를 보인다는 뜻

정확도는 전체 개수 중 맞은 것의 개수의 수치를 나타낸다.

우리의 모델은 30개의 데이터에 대해 예측을 했으니, 그 중 맞은 것은 30 * 0.9 = 27 개라는 것을 역추적해 볼 수 있다. 즉 30개 중 27개는 옳은 카테고리로, 3개는 틀린 카테고리로 분류했다는 의미이다.

<전체 코드 정리_DecisionTree>

# (1) 필요한 모듈 import
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import classification_report

# (2) 데이터 준비
iris = load_iris()
iris_data = iris.data
iris_label = iris.target

# (3) train, test 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(iris_data, 
                                                    iris_label, 
                                                    test_size=0.2, 
                                                    random_state=7)

# (4) 모델 학습 및 예측
decision_tree = DecisionTreeClassifier(random_state=32)
decision_tree.fit(X_train, y_train)
y_pred = decision_tree.predict(X_test)

print(classification_report(y_test, y_pred))
# 실행결과
              precision    recall  f1-score   support

           0       1.00      1.00      1.00         7
           1       0.91      0.83      0.87        12
           2       0.83      0.91      0.87        11

    accuracy                           0.90        30
   macro avg       0.91      0.91      0.91        30
weighted avg       0.90      0.90      0.90        30

📖 첫 번째 머신러닝 실습, 간단하고도 빠르게!_(4) 다른 모델도 해 보고 싶다면? 코드 한 줄만 바꾸면 돼!

이제는 다른 모델들도 활용해본다.

Random Forest


Random Forest : Decision Tree 모델을 여러 개 합쳐놓음으로써 Decision Tree의 단점을 극복한 모델이다. 이러한 기법을 앙상블(Ensemble) 기법이라고 한다. 단일 모델을 여러 개 사용하는 방법을 취함으로써 모델 한 개만 사용할 때의 단점을 집단지성으로 극복하는 개념
RandomForest 설명

Random Forest의 숲은 수많은 의사 결정 트리가 모여서 생성된다. 예를 들어 건강 위험도를 예측하는데 있어 1,000개의 의사 결정 트리 중 678개의 트리가 건강 위험도가 높다고 의견을 내고, 나머지는 위험도가 낮다는 의견을 냈을 경우, 숲은 그 의견들을 통합하여 건강 위험도가 높다고 하는 것이다. 데이터 사이언스에서는 이렇게 의견을 통합하거나 여러가지 결과를 합치는 방식을 “앙상블” (Ensemble method)이라고 한다.
Random Forest의 Random은 각각의 의사 결정 트리를 만드는데 있어 쓰이는 요소들을 무작위적으로 선정한다는 뜻이다. 건강 위험도를 30개의 요소(흡연 여부, 나이, 등등)로 설명할 수 있으면, 의사 결정 트리의 한 단계를 생성하면서 모든 요소들을 고려하지 않는다. 30개 중 무작위로 일부만 선택하여, 그 선택된 일부 중 가장 건강 위험도를 알맞게 예측하는 한 가지 요소가 의사 결정 트리의 한 단계가 된다.
Random Forest는 상위 모델들이 예측하는 편향된 결과보다, 다양한 모델들의 결과를 반영함으로써 더 다양한 데이터에 대한 의사결정을 내릴 수 있게 한다.

from sklearn.ensemble import RandomForestClassifier

X_train, X_test, y_train, y_test = train_test_split(iris_data, iris_label, test_size=0.2, random_state=21)

random_forest = RandomForestClassifier(random_state=32)
random_forest.fit(X_train, y_train)
y_pred = random_forest.predict(X_test)

print(classification_report(y_test, y_pred))
# 실행결과
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        11
           1       1.00      0.83      0.91        12
           2       0.78      1.00      0.88         7

    accuracy                           0.93        30
   macro avg       0.93      0.94      0.93        30
weighted avg       0.95      0.93      0.93        30

다른 scikit-learn 내장 분류모델


1) Support Vector Machine (SVM)
SVM : Support Vector와 Hyperplane(초평면)을 이용해서 분류를 수행하게 되는 대표적인 선형 분류 알고리즘

2 차원 공간에서, 즉 데이터에 2개의 클래스만 존재할 때,

  • Decision Boundary(결정 경계): 두 개의 클래스를 구분해 주는 선

  • Support Vector: Decision Boundary에 가까이 있는 데이터

  • Margin: Decision Boundary와 Support Vector 사이의 거리

    Margin이 넓을수록 새로운 데이터를 잘 구분할 수 있다. (Margin 최대화 -> robustness 최대화)

  • Kernel Trick: 저차원의 공간을 고차원의 공간으로 매핑해주는 작업. 데이터의 분포가 Linearly separable 하지 않을 경우 데이터를 고차원으로 이동시켜 Linearly separable하도록 만든다.

  • cost: Decision Boundary와 Margin의 간격 결정. cost가 높으면 Margin이 좁아지고 train error가 작아진다. 그러나 새로운 데이터에서는 분류를 잘 할 수 있다. cost가 낮으면 Margin이 넓어지고, train error는 커진다.

  • γ: 한 train data당 영향을 미치는 범위 결정. γ가 커지면 영향을 미치는 범위가 줄어들고, Decision Boundary에 가까이 있는 데이터만이 선의 굴곡에 영향을 준다. 따라서 Decision Boundary는 구불구불하게 그어진다. (오버피팅 초래 가능) 작아지면 데이터가 영향을 미치는 범위가 커지고, 대부분의 데이터가 Decision Boundary에 영향을 준다. 따라서 Decision Boundary를 직선에 가까워진다.

많은 선형 분류 모델은 대부분 이진 분류 모델이다. 그런데 이진 분류 알고리즘을 일대다(one-vs.-rest 또는 one-vs.-all) 방법을 사용해 다중 클래스 분류 알고리즘으로 사용할 수 있다. 일대다 방식은 각 클래스를 다른 모든 클래스와 구분하도록 이진 분류 모델을 학습시킨다. 클래스의 수만큼 이진 분류 모델이 만들어지고 예측할 때는 만들어진 모든 이진 분류기가 작동하여 가장 높은 점수를 내는 분류기의 클래스를 예측값으로 선택한다.

<SVM 모델 사용>

from sklearn import svm
svm_model = svm.SVC()

print(svm_model._estimator_type)
# 실행결과
classifier

2) Stochastic Gradient Descent Classifier (SGDClassifier)
SGD (Stochastic Gradient Descent) : 배치 크기가 1인 경사하강법 알고리즘 즉, 확률적 경사하강법은 데이터 세트에서 무작위로 균일하게 선택한 하나의 예를 의존하여 각 단계의 예측 경사를 계산한다.
배치 : 경사하강법에서 배치는 단일 반복에서 기울기를 계산하는 데 사용하는 예(data)의 총 개수이다. Gradient Descent 에서의 배치는 전체 데이터 셋라고 가정

<최소값을 찾는 과정>

대규모의 작업에서는 데이터 셋에 수십억, 수천억 개의 예가 포함되는 경우가 많고 대규모의 데이터 셋에는 엄청나게 많은 특성이 포함되어 있다. 따라서 배치가 거대해질 수 있다. 배치가 너무 커지면 단일 반복으로도 계산하는 데 오랜 시간이 걸릴 수 있고 무작위로 샘플링된 예가 포함된 대량의 데이터 셋에는 중복 데이터가 포함되어 있을 수 있다. 실제로 배치 크기가 커지면 중복의 가능성도 그만큼 높아진다.
데이터 세트에서 예(data)를 무작위로 선택하면 (노이즈는 있겠지만) 훨씬 적은 데이터 세트로 중요한 평균값을 추정할 수 있다. 확률적 경사하강법(SGD)은 이 아이디어를 더욱 확장한 것으로서, 반복당 하나의 예(배치 크기 1)만을 사용한다.
'확률적(Stochastic)'이라는 용어는 각 배치를 포함하는 하나의 예가 무작위로 선택된다는 것을 뜻한다.

단점 : 반복이 충분하면 SGD가 효과는 있지만 노이즈가 매우 심하다. 확률적 경사하강법의 여러 변형 함수의 최저점에 가까운 점을 찾을 가능성이 높지만 항상 보장되지는 않는다. (최저점을 찾지 못할 수 있다.)
단점 극복 : 미니 배치 확률적 경사하강법(미니 배치 SGD)는 전체 배치 반복과 SGD 의 절충안

미니 배치는 일반적으로 무작위로 선택한 10개에서 1,000개 사이의 예로 구성된다. 미니 배치 SGD는 SGD의 노이즈를 줄이면서도 전체 배치보다는 더 효율적이다.

<SGD Classifier 모델 사용>

from sklearn.linear_model import SGDClassifier
sgd_model = SGDClassifier()

print(sgd_model._estimator_type)
# 실행결과
classifier

3) Logistic Regression
Logistic Regression 모델 : 가장 널리 알려진 선형 분류 알고리즘. 소프트맥스(softmas) 함수를 사용한 다중 클래스 분류 알고리즘이며, 다중 클래스 분류를 위한 로지스틱 회귀를 소프트맥스 회귀(Softmax Regression)라고도 한다. 이름은 회귀지만, 실제로는 분류를 수행한다.

소프트맥스 함수: 클래스가 N개일 때, N차원의 벡터가 각 클래스가 정답일 확률을 표현하도록 정규화를 해주는 함수. 위의 그림은 4차원의 벡터를 입력으로 받아 3개의 클래스를 예측하는 경우의 소프트맥스 회귀의 동작 과정을 보여준다. 3개의 클래스 중 1개의 클래스를 예측해야 하므로 소프트맥스 회귀의 출력은 3차원의 벡터고, 각 벡터의 차원은 특정 클래스일 확률이다. 오차와 실제값의 차이를 줄이는 과정에서 가중치와 편향이 학습된다.

<Logistic Regression 모델 사용>

from sklearn.linear_model import LogisticRegression
logistic_model = LogisticRegression()

print(logistic_model._estimator_type)
# 실행결과
classifier

📖 내 모델은 얼마나 똑똑한가? 다양하게 평가해 보기_(1) 정확도에는 함정이 있다

모델의 성능을 정확도라는 척도를 통해 확인했었다. 모델의 성능을 평가하는 데에는 정확도뿐만 아니라 다른 척도들이 존재한다.

정확도에는 함정이 있다.


어떤 함정이 있는지, 손글씨 데이터인 MNIST 데이터셋으로 확인해 본다.

from sklearn.datasets import load_digits

digits = load_digits()
digits.keys()
# 실행결과
dict_keys(['data', 'target', 'frame', 'feature_names', 'target_names', 'images', 'DESCR'])

가장 중요한 data를 먼저 확인

digits_data = digits.data
digits_data.shape
# 실행결과
(1797, 64)

1,797개의 데이터 중 첫 번째 데이터를 샘플로 확인

digits_data[0]
# 실행결과
array([ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.,  0.,  0., 13., 15., 10.,
       15.,  5.,  0.,  0.,  3., 15.,  2.,  0., 11.,  8.,  0.,  0.,  4.,
       12.,  0.,  0.,  8.,  8.,  0.,  0.,  5.,  8.,  0.,  0.,  9.,  8.,
        0.,  0.,  4., 11.,  0.,  1., 12.,  7.,  0.,  0.,  2., 14.,  5.,
       10., 12.,  0.,  0.,  0.,  0.,  6., 13., 10.,  0.,  0.,  0.])

예상대로 64개의 숫자로 이루어진 배열(array)이 출력되었다. 각 숫자는 손글씨 데이터의 이미지 픽셀값을 의미한다. 길이 64의 숫자 배열은 사실 (8 x 8) 크기의 이미지를 일렬로 쭉 펴놓은 것이다.

이미지가 어떻게 생겼는지 한 번 확인
이미지는 아래처럼 간단히 확인할 수 있다. 다만, 일렬로 펴진 64개 데이터를 (8, 8)로 reshape해주는 것을 잊으면 안 된다!🔎

import matplotlib.pyplot as plt
%matplotlib inline

plt.imshow(digits.data[0].reshape(8, 8), cmap='gray')
plt.axis('off')
plt.show()


해상도가 낮아 흐리게 보인다.

target 데이터 확인

digits_label = digits.target
print(digits_label.shape)
digits_label[:20]
# 실행결과
(1797,)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

총 1,797개의 데이터가 있고, 0부터 9까지의 숫자로 나타난다. 각 이미지 데이터가 어떤 숫자를 나타내는지를 담고 있는 데이터이다.
각 이미지 데이터가 입력되었을 때 그 이미지가 숫자 몇을 나타내는 이미지인지를 맞추는 분류 모델을 학습시키면 되는데, 이번에는 정확도의 함정을 확인하는 실험이기 때문에 약간의 장치를 넣어볼 것이다.

숫자 10개를 모두 분류하는 것이 아니라, 해당 이미지 데이터가 3인지 아닌지를 맞히는 문제로 변형해서 풀어보는 것. 즉 입력된 데이터가 3이라면 3을, 3이 아닌 다른 숫자라면 0을 출력하도록 하는 모델을 만들어 본다.
그러려면 targetdigits_label을 살짝 변형시켜야 한다.

new_label = [3 if i == 3 else 0 for i in digits_label]
new_label[:20]
# 실행결과
[0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0]

이제 이 문제를 풀기 위해 다시 Decision Tree를 학습시키고, 정확도를 확인해본다.

from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier

X_train, X_test, y_train, y_test = train_test_split(digits_data,
                                                    new_label,
                                                    test_size=0.2,
                                                    random_state=15)

decision_tree = DecisionTreeClassifier(random_state=15)
decision_tree.fit(X_train, y_train)
y_pred = decision_tree.predict(X_test)

accuracy = accuracy_score(y_test, y_pred)
accuracy
# 실행결과
0.9388888888888889

바로 이곳에 함정이 있다. 우리가 풀려고 했던 문제를 생각해보면 총 10개의 숫자 중 3에만 집중을 해서, 3이라면 3으로, 3이 아니라면 0으로 맞추는 문제로 변형했었다.
그런 이유로, 정답 데이터인 label은 0이 매우 많고 3은 적은 불균형 데이터가 되었다. 9개의 숫자들은 label이 모두 0이 되었고, 3만 3으로 남아있었으니 대략 90%의 label이 모두 0이라는 이야기가 된다.
이 말은 즉 모델이 전혀 학습하지 않고 정답을 모두 0으로만 선택해도 정확도가 90%가량이 나오게 된다는 것이다.

<확인해보기>
길이는 y_pred와 같으면서 0으로만 이루어진 리스트를 fake_pred라는 변수로 저장해 보고, 이 리스트와 실제 정답인 y_test간의 정확도를 확인해 보

fake_pred = [0] * len(y_pred)

accuracy = accuracy_score(y_test, fake_pred)
accuracy
# 실행결과
0.925

이러한 문제는 불균형한 데이터, unbalanced 데이터에서 자주 발생할 수 있다.
즉 정확도는 정답의 분포에 따라 모델의 성능을 잘 평가하지 못하는 척도가 될 수 있는 것이다. 그렇기 때문에 분류 문제에서는 정확도 외에 다양한 평가 척도를 사용한다.

📖 내 모델은 얼마나 똑똑한가? 다양하게 평가해 보기_(2) 정답과 오답에도 종류가 있다!

위에서 확인한 정확도는 전체 데이터 중 맞은 데이터만 신경 쓰는 척도이다.
양성 데이터를 얼마나 많이 맞았느냐도 중요하겠지만, 음성 데이터를 얼마나 안 틀렸느냐도 중요한 경우가 있다. (이는 문제에 따라 달라질 것.)
예를 들어 코로나바이러스에 감염되었는지 얼마나 많은 의심되는 환자를 진단하는 경우, 실제 음성인데 양성으로 오진을 하면 그나마 환자에게는 다행인 일이나 실제 양성인데 음성이라고 오진을 하는 경우는 환자에게 치명적인 상황이 될 것이다.

이렇듯 같은 오진이라도 양성을 잡아내는 데에 실패하는 오진과, 음성을 잡아내는 데에 실패하는 오진은 그 중요도가 다를 수 있다.

이렇게 정답과 오답을 구분하여 표현하는 방법을 오차 행렬(confusion matrix) 이라고 한다.

다음 그림에서 각 행은 실제 클래스(Actual Class)를 나타낸다. 코로나의 예시를 계속 생각해 볼 때, Actual Class가 Positive라면 환자는 실제 코로나바이러스에 감염된 것이고 반대로 Actual Class가 Negative라면 환자는 건강하다.

반면 각 열은 예측된 클래스(Predicted Class)입니다.
Predicted Class가 Positive라면 진단 결과가 양성, Negative라면 진단 결과가 음성인 것을 말한다.

그래서 각 칸에 나타난 TP, FN, FP, TN은 아래와 같다.

  • TP(True Positive) : 실제 환자에게 양성판정 (참 양성)
  • FN(False Negative) : 실제 환자에게 음성판정 (거짓 음성)
  • FP(False Positive) : 건강한 사람에게 양성판정 (거짓 양성)
  • TN(True Negative) : 건강한 사람에게 음성판정 (참 음성)

    TP, FN, FP, TN의 수치로 계산되는 성능 지표 중 대표적으로 쓰이는 것은 정밀도(Precision), 재현율(Recall, Sensitivity), F1 스코어(f1 score)이다.

Precision과 Recall, F1 score, 그리고 원래 확인했던 정확도(Accuracy)까지 수식은 아래와 같다.

Precision과 Recall의 분자는 둘 다 T P이다. T P는 맞게 판단한 양성이므로, 이 값은 높을수록 좋다. 하지만 분모에는 각각 F P와 F N가 있다. 이 값들은 잘못 판단된 것들이므로 낮을수록 좋다.
즉, T P는 높고 F P또는 F N이 낮을수록 좋은 예측이므로, Precision과 Recall 값이 클수록 좋다. 다만, 둘은 아래 사항이 다르다.

Precision은 분모에 있는 F P가 낮을수록 커진다. Precision이 높아지려면 False Positive, 즉 음성인데 양성으로 판단하는 경우가 적어야 한다.

  • Precision이 크려면 음성인데 양성으로 판단하는 경우가 적어야 한다. 음성을 놓치지 말아야 한다! 어떤 경우가 있나?

Recall은 분모에 있는 F N이 낮을수록 커집니다. Recall이 높아지려면 False Negative, 즉 양성인데 음성으로 판단하는 경우가 적어야 한다.

  • Recall이 크려면 양성인데 음성으로 판단하는 경우가 적어야 한다. 양성을 놓치지 말아야 한다. 이건 어떤 경우가 있나?

예1) 전체 메일함에서 스팸 메일을 거르는 모델에게는 Precision이 더 중요할까, Recall이 더 중요할까? (스팸 메일을 positive, 정상 메일을 negative로 생각)
메일 처리 모델은 스팸 메일을 못 거르는 것은 괜찮지만, 정상 메일을 스팸 메일로 분류하는 것은 더 큰 문제이다. 즉 음성을 양성으로 판단하면 안 된다. 따라서 Precision이 더 중요하다.

예2) 암 환자를 진단하는 모델에게는 Precision, Recall 중 무엇이 더 중요한가요?
암을 진단하는 경우 실제 환자를 한 명이라도 놓치면 안 된다. 즉 양성을 음성으로 판단하면 안 되기 때문에 Recall이 더 중요하다.

F1 score는 Recall과 Precision의 조화평균이다. Accuracy는 전체 데이터 중 올바르게 판단한 데이터 개수의 비율이다.

오차 행렬에 대해 알아보자
What is Confusion Matrix and Advanced Classification Metrics?

오차 행렬은 sklearn.metrics 패키지 내의 confusion_matrix로 확인할 수 있다.

모델이 예측했던 손글씨 결과에 관해 확인

from sklearn.metrics import confusion_matrix

confusion_matrix(y_test, y_pred)
# 실행결과
array([[320,  13],
       [  9,  18]])

위에서 이미지로 봤던 오차 행렬과 같이 각각은 왼쪽 위부터 순서대로 T P, F N, F P, T N의 개수를 나타낸다. 특히, 손글씨 문제에서의 0은 Positive 역할을, 3은 Negative 역할을 한다.
T P와 T N의 값이 320, 18로 비교적 크고 F N과 F P의 값은 13, 9로 작다.

모든 숫자를 0으로 예측한 fake_pred의 경우를 확인

confusion_matrix(y_test, fake_pred)
# 실행결과
array([[333,   0],
       [ 27,   0]])

모든 데이터를 0, 즉 Positive로 예측했고 Negative로 예측한 것은 없기 때문에 F N과 T N은 둘 다 0이다.

모델이 예측했던 손글씨 결과의 Precision, Recall, F1 score는 각각 얼마가 되는지 확인해 본다. sklearn.metricsclassification_report를 활용하면 각 지표를 한 번에 확인할 수 있다.

from sklearn.metrics import classification_report

print(classification_report(y_test, y_pred))
# 실행결과
              precision    recall  f1-score   support

           0       0.97      0.96      0.97       333
           3       0.58      0.67      0.62        27

    accuracy                           0.94       360
   macro avg       0.78      0.81      0.79       360
weighted avg       0.94      0.94      0.94       360

0은 개수가 333개로 많기 때문에 precisionrecall에서 모두 0.97, 0.96으로 어렵지 않게 높은 점수를 받았다. 반면 3은 27개뿐이기 때문에 모두 맞추기가 어려웠을 것. precisionrecall은 각각 0.58, 0.67이 나왔다.

fake_pred의 점수 확인

print(classification_report(y_test, fake_pred, zero_division=0))
# 실행결과
              precision    recall  f1-score   support

           0       0.93      1.00      0.96       333
           3       0.00      0.00      0.00        27

    accuracy                           0.93       360
   macro avg       0.46      0.50      0.48       360
weighted avg       0.86      0.93      0.89       360

0에 대한 precision과 recall은 0.93, 1로 매우 높지만 3에 대한 precision과 recall은 둘 다 0이다. 0은 잘 잡아내지만, 3은 단 하나도 맞추지 못했다는 뜻이다.

다시 한번 정확도를 확인해 본다.

accuracy_score(y_test, y_pred), accuracy_score(y_test, fake_pred)
# 실행결과
(0.9388888888888889, 0.925)

모델의 성능은 정확도만으로 평가하면 안 된다는 것을 알 수 있다.
특히, label이 불균형하게 분포되어있는 데이터를 다룰 때는 더 조심해야 한다.

좋은 웹페이지 즐겨찾기