[머신러닝] 데이터관찰, Decision Tree, scikit learn, 데이터 나누기

쩡이·2023년 9월 19일

ML

목록 보기
1/14

머신러닝

머신러닝은 명시적인 프로그램에 의해서가 아니라 주어진 데이터를 통해 규칙을 찾는 것

꽃잎, 꽃받침의 길이/너비 정보를 이용해서 iris의 3종 품종을 구분할 수 있을까?
Iris의 품종 분류 를 주제로 실습해보자.

데이터관찰

직접 데이터를 관찰해보자

  1. iris 데이터를 불러보자
    from sklearn.datasets import load_iris
    iris = load_iris()

iris 데이터는 dict형태로 이루어진 것 같다.
키 값을 살펴 보면,

skllearn.datasets에서 제공하는 데이터의 key 값에 DESCR이 항상 있는데, 이는 description역할을 함


총 150개 데이터가 있다.


150개 데이터는 3종이 50개씩 구성돼있는 형태
Setosa : 0, Versicolor : 1, Virginica : 2

  1. padas로 데이터 프레임을 만들어보자
import pandas as pd

iris_pd = pd.DataFrame(iris.data, columns=iris['feature_names'])

iris_pd에 품종 정보 컬럼 추가

특성들의 관계성을 파악하기 위해 그래프를 그려보자

  1. boxplot(x=sepal length)
    x가 5.5 ~ 6 구간에서는 세 특성이 모두 겹쳐있다.
    sepal length로만으로는 구분하기 어려울 듯 하다.
    그리고 Virginica(species=2)의 sepal length 중 경향성에서 떨어진 하나가 관찰된다.

  2. boxplot(x=sepal width)
    sepal width는 sepal length보다 겹쳐진 부분이 더 많다.

  1. boxplot(x=petal length)
    petal length로 Setosa(species=1)은 잘 구분이 되고, 나머지 품종은 겹치는 부분이 있다.
  1. boxplot(x=petal width)
    petal width도 Setosa는 잘 구분하는데 나머지 두 품종은 구분하기 어렵다.
    위 그래프로 petal 특성으로 Setosa는 구분할 수 있지만 나머지 품종을 구분하기는 어려울 것으로 보인다.
    4개 특성 하나씩만으로는 3종을 구분하기 어려울 것 같다.
  1. pairplot으로 두 특성으로 구분해보자
    sns.pairplot(iris_pd, hue='species')
    petal length와 petal width로 구분하면 3종 구분이 가능할 것 같다.

petal length와 petal width로 좀 더 집중적으로 보면

sns.pairplot(data=iris_pd, 
             vars=['petal length (cm)', 'petal width (cm)'], 
             hue='species', height=4)

3종 구분이 가능할 것 같다.

petal length < 2.5 --> Setosa
petal width < 1.6 --> Versicolor
petal width > 1.6 --> Virginica(몇 개의 데이터는 놓칠 수 있음)

Decision Tree

조건을 따라서 분기하는 모형

상황설정

plt.figure(figsize=(12,6))
sns.scatterplot(x='petal length (cm)', y='petal width (cm)',
                data=iris_pd, hue='species', palette='Set2')

1,2번 종 구분을 위해 1,2번 종으로만 구성된 데이터프레임 지정

1, 2번 데이터에 집중해보자

plt.figure(figsize=(12,6))
sns.scatterplot(x='petal length (cm)', y='petal width (cm)',
                data=iris_12, hue='species', palette='Set2')


어디에서 자르는 것이 가장 좋을지(분할기준) 더 알아보자

지니 계수
Gini index 혹은 불순도율, 엔트로피와 비슷한 개념으로 엔트로피보다 계산량이 적다.
지니 계수가 낮아진다 -> 무질서도가 감소한다 -> 분할하는 것이 좋다
그러나 decision tree를 직접 짜는 것은 아주 복잡하고 번거로운 일이므로
이를 해결해주는 framework를 이용한다

Scikit learn

decision tree를 내가 직접 코딩하지 않아도 됨
2007년 구글 썸머 코드에서 처음 구현
현재 파이썬에서 가장 유명한 기계 학습 오픈 라이브러리

sklearn을 이용한 결정나무 구현

from sklearn.tree import DecisionTreeClassifier

iris_tree = DecisionTreeClassifier()

학습
우리는 petal length, petal width 특성만 보고 있기 때문에 iris 데이터에서 petal/length,width 값만 슬라이싱해서 가져온다
iris.data[:, 2:]

이 값을 tree모형에 학습시켜서 1,2번 종을 구분시킨다.
학습시킬때, 꼭 정답을 알려줘야한다(지도학습)
여기서 정답 역할을 하는 것이 iris.target
iris_tree.fit(iris.data[:, 2:], iris.target)

accuracy 확인
metrics 모듈에서 accuracy를 계산해주는 기능을 사용

from sklearn.metrics import accuracy_score

y_pred_tr = iris_tree.predict(iris.data[:, 2:])

accuracy_score(iris.target, y_pred_tr)

99.3%의 정확도를 가진다

지도학습
학습 대상이 되는 데이터에 정답(label)을 붙여서 학습시키고
모델을 얻어 완전히 새로운 데이터에 모델을 사용해서 답을 얻고자 하는 것

앞서 tree모형이 어떻게 경계면을 나눴는지 알아보면
1,2번 종을 구분하는 경계면이 6면으로 다소 복잡하게 나눠져있다.
정확성이 높게 나온 것은 경계면을 복잡하게 나눴기 때문이 아닐까?
150개의 iris 데이터에는 잘 맞을지라도 다른 데이터를 가져왔을 때 일반화 할 수 있을까?에 대한 의문이 생긴다.
그런 점에서 봤을 때, 복잡한 경계면은 결국 모델의 성능을 나쁘게 만들 수 있다.

과적합
내가 가진 데이터에만 너무 적합하면 일반적인 데이터에서 그 모델은 성능이 잘 나오지 않는다는 것

데이터 나누기

훈련/ 검증/ 평가로 데이터를 분리시킨다.
데이터를 나누는 이유는 적은 데이터라도 일반화된 성능 확보를 위한 과적합 테스트를 하기 위함이다.
확보한 데이터 중에서 모델 학습에 사용하지 않고 빼둔 데이터로 모델을 테스트한다.

데이터를 훈련용/테스트용 나누기

from sklearn.datasets import load_iris
import pandas as pd

iris = load_iris()

from sklearn.model_selection import train_test_split

features = iris.data[:, 2:]
labels = iris.target

X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=0.2, stratify=labels, random_state=13)
#test_size : 훈련용, 테스트용 비율을 결정 여기서는 8:2
#stratify : 특성을 특정하여 개수를 동일하게 맞춰준다. 여기서는 0,1,2를 모두 훈련용/test용 8개, 2개처럼 개수도 동일하게 맞춰줌 

분리 확인
훈련용 120개, test용 30개 8:2로 잘 나눠있다.
그리고 항상 unique를 확인하여 훈련용, test용 하나 당 몇 개씩 들어있는지? 확인해야함
위에서 stratify옵션을 주었기 때문에 동일하게 나왔다.

train 데이터만으로 결정나무 모델 만들기

모델을 단순화 시켜서 과적합을 막기 위해 max_depth를 두어서 성능을 제한한다.

from sklearn.tree import DecisionTreeClassifier

iris_tree = DecisionTreeClassifier(max_depth=2, random_state=13)
iris_tree.fit(X_train, y_train)
#모델 확인
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree

plt.figure(figsize=(12,8))
plot_tree(iris_tree)

종별로 40개씩 나누어졌고, 0번 종이 왼쪽으로 분기되고 나머지 종은 오른쪽으로 분기된다. 1번 40, 2번 40개인데, 다음 분기에서 1번으로 36개가 분기되는데 그 중 1번이 35개, 2번종인데 1번으로 분류된 것이 1개이다.
2번은 44중 1번이 2번으로 분류된 것이 5개, 2번이 39개이다.

훈련용 데이터 결정경계 확인

from mlxtend.plotting import plot_decision_regions

plt.figure(figsize=(14,8))
plot_decision_regions(X=X_train, y=y_train, clf=iris_tree, legend=2)
plt.show()

복잡하지 않은 결정경계가 나온다. max_depth를 두는 것이 좋을 것 같다.

훈련용 데이터에 대한 accuracy 계산

from sklearn.metrics import accuracy_score

y_pred_tr = iris_tree.predict(X_train)
accuracy_score(y_train, y_pred_tr)

일반적 데이터에서 accuracy 확인 -> 테스트 데이터 확인

y_pred_test = iris_tree.predict(X_test)
accuracy_score(y_test, y_pred_test)

test 데이터도 train 데이터 accuracy 근처에 있으므로 과적합은 아니라는 것

결정경계를 train/test를 구분하여 나타내기

#150개 데이터를 그리는데 train,test용을 구분하고, 결정경계까지 표현
scatter_highlight_kwargs = {'s':150, 'label':'Test data', 'alpha':0.9} #150개 데이터는 test data라벨 붙이고
scatter_kwargs = {'s':120, 'edgecolor':None, 'alpha':0.9} #일반적인 120개 데이터는 테두리없이

plt.figure(figsize=(12,8))
plot_decision_regions(X=features, y=labels,
                      X_highlight=X_test, clf=iris_tree, legend=2,
                      scatter_highlight_kwargs=scatter_highlight_kwargs,
                      scatter_kwargs=scatter_kwargs,
                      contour_kwargs={'alpha':0.2})
#feature 4개를 사용해보면
features = iris.data
labels = iris.target

X_train, X_test, y_train, y_test = train_test_split(features, labels, 
                                                    test_size=0.2,
                                                    stratify=labels,
                                                    random_state=13)
iris_tree = DecisionTreeClassifier(max_depth=2, random_state=13)
iris_tree.fit(X_train, y_train)

모델을 다른 데이터에 적용


대괄호를 두번 쓴 이유는 연산을 하기 위함

모델을 결정하는 중요도
iris_tree.feature_importances_

max_depth=2일때 중요도
max_depth에 따라 바뀔 수 있음

zip & 언패킹

list1 = ['a', 'b', 'c']
list2 = [1, 2, 3]

dict(zip(list1,list2)) #zip
x,y = zip(*pairs) #언패킹, 튜플형태로 반환됨
list(x)
list(y)

0개의 댓글