와인 분류하기
01. 로지스틱 회귀로 와인 분류하기
##데이터 준비하고 정보 확인
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head()
wine.info()
wine.describe()
head
(n=5)
pandas.DataFrame.head
처음 n 개의 행을 출력한다. (default 5)
특징(feature)으로는 alcohol(도수), sugar(당도), pH(산성)이 있다.
맨 오른쪽에 있는 class는 화이트 와인과 레드 와인을 숫자로 표현한 것인데,
이 데이터에서는 class가 0인 데이터가 화이트 와인, 1인 데이터를 레드 와인으로 표현하였다.
info()
pandas.DataFrame.info
데이터프레임의 각 열의 데이터 타입과 null이 아닌 값 및 메모리 사용량 정보를 출력한다.
*누락된 데이터가 있는지 확인. 누락된 값의 경우 데이터를 버리거나 평균값으로 채운 뒤 사용할 수 있음. 보통 두 가지 모두 시도해보며, 훈련세트의 통계값으로 테스트 세트를 변환하기 때문에 훈련세트의 평균값으로 테스트 세트의 누락된 값을 채워야 함.
총 6497개의 데이터가 존재하며, null 값이 없어 따로 처리할 필요는 없다.
Column(특징)은 위에서 봤던 것처럼 alcohol, sugar, pH가 존재한다.
describe()
pandas.DataFrame.describe
데이터프레임의 분포 경향, 분산 등 요약 통계 출력한다.
*최소, 최대, 평균값을 알려줌. 이 함수를 통해 데이터의 스케일이 다른 것을 확인할 수 있고 특성을 표준화할 필요성을 느끼게 됨.
와인 분류 데이터 셋
· Target: 와인 종류(class = 0: 레드 / class = 1: 화이트)
· Features: alcohol, sugar, pH
02. 데이터 전처리
##데이터 전처리
#훈련 데이터셋과 테스트 데이터셋으로 나누기
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size = 0.2, random_state = 42)
print(train_input.shape, test_input.shape)
train_test_split( ) 메소드를 통해 전체 데이터를 훈련용 데이터셋과 테스트용 데이터셋으로 나누고,
실제로 데이터가 잘 나누어졌는지를 출력해보자.
3개의 column으로 이루어진 데이터가 대략 8 : 2 비율로 적절히 나누어진 모습을 확인할 수 있다.
#데이터 표준화 작업
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
로지스틱 회귀에서는 각각의 특징(alcohol, sugar, pH)들의 데이터 스케일이 다르므로,
StandardScaler( )를 통해 데이터를 표준화해주어야 한다.
이러한 과정을 데이터 전처리(Data Scaling)라고 부른다.
03. 로지스틱 회귀로 분류 후 계수 값을 통한 모델 분석
#로지스틱 회귀 모델로 훈련 및 학습
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
print(lr.coef_, lr.intercept_)
로지스틱 회귀 모델을 선언하여 fit() 메소드를 통해 훈련용 데이터셋을 훈련시킨다.
그 다음 과대(Overfitting), 과소(Underfitting) 여부를 확인하기 위해
훈련용 데이터셋과 테스트용 데이터셋의 점수를 출력시켜보고,
각 특징들 중에서 어떤 특징이 모델에서 중요한 역할을 하는지 확인해보기 위해,
훈련시킨 모델의 coef_와 intercept_에 접근하여 가중치와 절편을 분석해보자.
78% 정도의 정확도가 출력되었다. 과소적합이 발생했다.
- alcohol, sugar, pH 순으로 가중치와 절편이 출력되었는데, 도수(alcohol)과 당도(sugar)의 가중치가 양수인 것으로 보아 알코올과 당도가 클수록 양성 클래스(화이트 와인)일 가능성이 높아지고, 맨 오른쪽에 있는 pH가 클수록 음성 클래스(레드 와인)일 가능성이 높을 것이라고 예상할 수 있다.
- 해당 가중치와 절편으로 만든 Z 방정식의 값이 0보다 크면 양성 클래스, 0보다 작으면 음성 클래스로 분류된다.
- 또한 sugar 계수 값이 가장 크므로 sugar가 와인의 종류를 결정하는데 가장 큰 영향을 주고 있다고 예측할 수 있다.
- 다항식의 경우 설명하기가 어려운 경우가 발생한다.
결정트리
01. 결정트리(Decision Tree)
데이터를 나눌 수 있는 질문을 통해 정답 추론
- leaf node가 섞이지 않은 상태로 완전히 분류
- 복잡성(entropy)이 낮도록 만드는 것
- 결정트리는 분류와 회귀 모두에 사용할 수 있다.
- 각 특성이 개별 처리되기 때문에 데이터 스케일에 영향을 받지 않아 특성의 정규화나 표준화가 필요 없다.
- 과대적합되는 경향이 있다.
- 여러 개의 모델을 함께 사용하는 앙상블 모델이 존재한다. (RandomForest, GradientBoosting, XGBoost)
DecisionTreeClassifier 클래스
- sklearn.tree.DecisionTreeClassifier (사이킷런 라이브러리의 결정트리분류기 클래스)
◈ parameters
criterion: {"gini", "entropy", "log_loss"}, default = "gini"
· 불순도(분활 기준): 분할의 quality를 측정
splitter: {"best", "random"}, dafault = "best"
· 각 노드에서 분할을 선택하는 데 사용되는 전략
max_depth: int, default = None
· 트리의 최대 깊이 (값이 클수록 모델의 복잡도가 올라간다.)
min_samples_split: int or float, default = 2
· 자식 노드를 분할하는데 필요한 최소 샘플 수
random_state: int, default = None
· 난수 seed 설정
min_impurity_decrease: float, default = 0.0
· 최소 불순도
◈ Attributes
classes_
feature_importances_
◈ Methods
DecisionTreeClassifier 클래스를 사용하여 결정 트리 모델을 훈련해보자. 사용법은 이전과 동일하다. fit() 메서드를 호출해 모델을 훈련하고 score() 메서드로 정확도를 평가한다.
다른 점으로는, 결정 트리는 로지스틱 회귀와 달리 데이터 전처리(Data Scaling) 과정이 필요 없다는 점이다.
결정 트리는 개별적인 특성을 기준으로 데이터에게 질문을 던져 분류하기 때문이다.
##결정트리 선언 및 훈련 & 테스트
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state = 42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
로지스틱 회귀 모델에 비해 정확도가 확연히 높아졌다.
하지만, 훈련 데이터에 대한 점수가 상대적으로 너무 높기 때문에 이 모델은 훈련 데이터에 과대적합(Overfitting) 되었다고 할 수 있다.
02. 학습 과정
##데이터를 나눌 수 있는 질문을 통해 정답 추론
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10, 7))
plot_tree(dt)
plt.show()
plot_tree
- sklearn.tree.plot_tree
- 매개변수로 전달받은 결정 트리 모델을 이미지로 표현해준다.
- 의사 결정 트리를 플로팅한다.
◈ Parameters
max_depth: int, default = None
· 의사결정트리의 최대 깊이
features_names: list of strings, default = None
· 나타내고자 하는 요소 명
filled: bool, default = False
· filled = True: 불순도에 따라 색을 채움 - 양성/음성 별 색상
· 어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시한다.
그림을 보면, 결정 트리에 너무 많은 가지(branch)가 존재하는데, 이런 부분이 과대 적합(Overfitting)의 요인이 될 수 있다.
max_depth를 1로 설정하여 깊이(depth)를 1까지만 그려보자. 매개변수 filled를 True로 설정해주면 클래스의 비율에 따라 노드의 색상을 다르게 칠해준다.
#조금 자세하게 보기 위해 파라미터를 조정해보자.
plt.figure(figsize = (10, 7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
figure
- matplotlib.pyplot.figure
- 새로운 figure를 생성하거나 기존 figure를 활성화한다.
◈ Parameters
figsize: (float, float), default: rcParams["figure.figsize"] ( default: [6.4, 4.8] )
· width, height in inches
· 차트의 그림 사이즈를 조정하는 변수이다.
- plot_tree() 함수에서 filled = True로 지정하면 클래스마다 색깔을 부여하고, 어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시한다.
- 결정트리에서 예측하는 방법은 리프 노드에서 가장 많은 클래스가 예측 클래스가 된다. k-최근접 이웃과 유사하다. Decision Tree는 각각의 feature에 대한 통계값, 분포도 갖고 있어 가장 영향력이 좋을 만한 값을 선정한다. 이때 맨 처음 선정된 Feature가 sugar이고 기준값을 -0.239(표준값)로 잡은 것이다.
- 위의 그림에서 첫 번째 노드를 보면 sugar 특성을 기준으로 나뉘었다. gini는 지니 불순도를 의미하며 부모와 자식 노드 사이의 불순도 차이를 정보 이득이라고 부른다. value = [1258, 3939] 에서 왼쪽은 class=0 음성 클래스(레드 와인)의 개수, 오른쪽은 class=1 양성 클래스(화이트 와인)의 개수가 출력되었다.
- 왼쪽 자식 노드를 보면 gini 계수가 조금 올랐고 양성 음성 클래스의 value 차이가 얼마 나지 않는다. 색이 옅어진 것을 확인할 수 있다. (어떤 클래스의 비율이 높아지면 색은 짙어진다)
- 오른쪽 자식 노드를 보면 gini 계수가 확 줄었고 양성 음성 클래스의 value 차이가 커졌다. 색이 짙어진 것을 확인할 수 있다.
- 트리가 아래로 내려올수록 gini 계수의 비율을 낮추는 것이 목표이며, value는 한쪽으로 치우친 값을 나타내는 게 좋다.
03. 불순도: 결정 트리에서 분류 기준 선택을 위한 개념
▶ 불순도 (Impurity)
- 결정트리에서 분기기준을 선택하기 위해서 불순도(impurity)라는 개념을 사용한다.
- 복잡성을 의미하며 해당 범주 안에 서로 다른 데이터가 얼마나 섞였는지를 의미
- 다양한 개체들이 섞여 있을수록 불순도가 높아진다.
- 분기 기준 설정 시 현재 노드의 불순도에 비해 자식 불순도가 감소되도록 하여 불순도를 낮춰야 함
- 부모 노드와 자식 노드의 불순도 차이: information gain(정보 이득)
▶ 불순도 함수 (Gini, Entropy)
- 불순도를 수치적으로 나타낼 수 있는 대표적인 불순도 함수는 두 가지가 있다.
- 지니 지수(Gini)
- 지니 지수의 최댓값은 0.5이다.
- 엔트로피 지수(Entropy)
- 지니 불순도(Gini impurity) = 1 - (음성 클래스 비율^2 + 양성 클래스 비율^2 )
- 총 샘플의 수(5197), 음성클래스(1258), 양성클래스(3939): 1 - ( 1258/5197 ^2 + 3939/5197 ^2 ) = 0.367
- 최악의 경우 : 음성 클래스와 양성 클래스의 비율이 동일하여 지니 불순도가 0.5가 나오는 경우
- 1 - ( 50/100 ^2 + 50/100 ^2 ) = 0.5
- 순수 노드 : 음성 클래스와 양성 클래스 둘 중 하나로 100% 쏠려서 지니 불순도가 0이 나오는 경우
- 1 - ( 0/100 ^2 + 100/100 ^2 ) = 0
- 엔트로피 불순도(entropy impurity) = - 음성 클래스 비율 x log_2 (음성 클래스 비율) - 양성 클래스 비율 x log_2 (양성 클래스 비율)
- - ( 1258/5197) x log_2 ( 1258/5197 ) - ( 3939/5197 ) x log_2 ( 3939/5197 ) = 0.798
- 분기를 하였을 때 이러한 불순도 함수의 값(지니 지수 또는 엔트로피 지수)이 줄어드는 방향으로 트리를 형성해야 한다.
▶ 정보이득(Information Gain)
- 분기 이전의 불순도와 분기 이후의 불순도의 차이
- 불순도가 1인 상태에서 0.7인 상태로 바뀌었다면 정보 이득은 0.3이다.
- 정보이득 = 부모의 불순도 - ( 왼쪽 노드 샘플 수 / 부모의 샘플 수) X 왼쪽 노드 불순도 - (오른쪽 노드 샘플 수 / 부모의 샘플 수 ) X 오른쪽 노드 불순도
04. 일반화와 가지치기(Pruning)
기존 DecisionTreeClassifier에 max_depth를 3으로 설정해주어, 최대로 뻗을 수 있는 가지의 길이를 3으로 제한해주면 가지치기 작업이 끝이 난다.
##높이를 제한하여 수행 - 표준화한 데이터셋 활용
dt = DecisionTreeClassifier(max_depth=3, random_state=42) ## max_depth로 높이를 제한
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
## 결정트리 출력
plt.figure(figsize=(20, 15))
plot_tree(dt, filled=True, feature_names = ['alcohol', 'sugar', 'pH'])
plt.show()
84%의 정확도를 가지면서 과대적합이 어느 정도 해결된 모습을 확인할 수 있다.
- 루트 노드 다음에 있는 깊이 1의 노드는 모두 당도를 기준으로 훈련세트를 나눈다. 하지만 깊이 2의 노드는 맨 왼쪽의 노드만 당도를 기준으로 나누고 왼쪽에서 두 번째 노드는 알코올 도수를 기준으로 나눈다. 오른쪽의 두 노드는 pH를 사용했다.
- 깊이 3에 있는 노드가 최종 노드인 리프 노드이다.
- 불순도는 클래스별 비율을 가지고 계산한다.
05. 데이터를 표준화하지 않고 결정 트리 출력
높이를 제한하여 수행 - 전처리 하지 않은 데이터 셋 활용: 표준화한 데이터셋과 동일한 결과
##전처리를 하지 않은 데이터로 모델을 다시 학습시켜 보자.
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))
print(dt.feature_importances_)
coef_에 접근했을 때 당도(sugar)의 가중치가 높게 나왔듯이, 중요도(importance) 또한 당도에서 확연히 높은 수치를 보여 주고 있다.
과대 적합(overfitting)은 매개변수 max_depth를 조정하는 것 외에도, min_impurity_decrease를 조정하여 해결할 수도 있다. 매개변수 min_impurity_decrease는 최소 불순도를 조절하여 트리를 구성하는데, 이 모델에는 0.00005라는 수치를 줘보자.
dt = DecisionTreeClassifier(criterion='gini', random_state=42, min_impurity_decrease = 0.00005)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))
기존보다 과대적합이 해결됨과 동시에, max_depth를 조절했을 때보다 정확도가 약간 높아졌다. 그러나 과대적합이 완전히 해결되지는 않는다.
이유를 살펴보기 위해 트리를 시각화하여 확인해보자.
plt.figure(figsize=(20, 15))
plot_tree(dt, filled=True, feature_names = ['alcohol', 'sugar', 'pH'])
plt.show()
최소 불순도(min_impurity)만 조절하다 보니 가지(branch)의 깊이가 꽤나 깊어졌기 때문인 듯하다.
그럼 최소 불순도와 최대 깊이를 동시에 적절히 조절해보면 되지 않을까?
하지만 '적절히'라는 단어만큼 애매하고 어려운 것이 없다.
과연 몇으로 설정해야 적절한 값이 될 수 있을까?
머신러닝에서는 모델이 아닌 사람이 직접 지정해줘야 하는 매개변수를 하이퍼 파라미터(Hyper Parameter)라고 부른다.
이 글에서 등장한 min_impurity_decrease와 max_depth 또한 마찬가지로 하이퍼 파라미터라고 할 수 있다.
사이킷런(sklearn)에는 여러 값들 중에 어떤 값이 하이퍼 파라미터에 가장 적합한지 골라주는 'AutoML' 클래스가 있다.
바로 'GridSearchCV'이다.
GridSearchCV는 교차 검증(Cross Validation)과 동시에 적절한 하이퍼 파라미터(Hyper Parameter) 값을 골라주는 유용한 클래스이다.
'인공지능과 정보보호' 카테고리의 다른 글
[Week3] 머신러닝 기본 with Colab (1) | 2022.10.20 |
---|