2022-11-13 16:25:46

지도학습 분류 알고리즘 중에 의사결정 나무(decision tree)라는 것이 있습니다. 스무고개와 같은 방식으로 어떤 특성에 대해 질문을 던져가며 답을 찾아가는 모델이라고 볼 수 있습니다. 의사결정 나무에 관한 이론적 이해는 "결정 트리(Decision Tree) 알고리즘, ID3 소개"를 참고하시기 바랍니다.

 

실습을 위해 UCI 머신러닝 저장소에서 제공하는 유방암 데이터셋을 활용하도록 하겠습니다. 데이터셋은 아래 링크에서 다운로드 받을 수 있습니다. 

 

http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/

 

유방암(breast cancer) 데이터셋

유방암 데이터셋은 csv 파일로 제공되는데 다음과 같이 699개의 행, 11개의 컬럼으로 구성되어 있습니다. 

 

 

각각 다음과 같은 의미를 갖습니다.

 

1) id - 아이디

2) clump thickness

3) uniformity of cell size

4) uniformity of cell shape

5) marginal adhesion

6) single epithelial cell size

7) bare nuclei

8) bland chromatin

9) normal nucleoli

10) mitoses

11) class - 클래스. 2: 양성, 4: 악성

 

따라서, 11번째 컬럼을 라벨값으로 삼으면 되고, 2번째-10번째 컬럼을 특성으로 삼으면 됩니다. 세포와 관련된 특성들입니다. 아이디는 유방암을 예측하는데 전혀 도움이 되지 않기 때문에 무시하면 됩니다. 

 

데이터 전처리

우선 데이터셋을 pandas 데이터프레임으로 갖고 와서 모델 학습 및 검증에 적합한 형태가 되도록 전처리를 해주도록 하겠습니다. 먼저 유방암 데이터셋 csv 파일을 판다스 데이터프레임으로 변환해주겠습니다.

 

import pandas as pd

uci_path = 'https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data'
df = pd.read_csv(uci_path, header=None)
print(df)

 

 

아직 컬럼명이 없는 상태입니다. 컬럼명을 부여하겠습니다. 그리고 데이터프레임의 info 메소드를 활용해서 NaN 값이 있는지 확인하고 각 컬럼의 데이터 타입을 확인하겠습니다. 

 

df.columns = ['id', 'clump', 'cell_size', 'cell_shape', 'adhesion', 'epithlial', 'bare_nuclei', 'chromatin', 'normal_nucleoli', 'mitoses', 'class']
print(df.info())

 

 

bare_nuclei 컬럼을 제외하고는 모두 정수 데이터입니다. 모델을 훈련시킬 때는 숫자형 데이터여야 하기 때문에, bare_nuclei도 숫자형 데이터로 바꿔줄 필요가 있습니다. 우선 bare_nuclei 컬럼이 어떤 값들로 구성되어 있는지 고유값을 확인해보겠습니다. 

 

print(df['bare_nuclei'].unique())

 

 

'?'라는 데이터가 들어있네요. 나머지는 숫자를 담고 있는 문자열입니다. 따라서 ?를 NaN으로 바꿔주고, bare_nuclei 컬럼이 NaN 값인 행을 제거해주도록 하겠습니다. 그 다음에 bare_nuclei의 자료형을 int로 변경해주겠습니다. 

 

import numpy as np
df['bare_nuclei'].replace('?', np.nan, inplace=True)
df.dropna(subset=['bare_nuclei'], axis=0, inplace=True)
df['bare_nuclei'] = df['bare_nuclei'].astype('int')
print(df.info(), '\n')

 

 

결과적으로 683개의 행이 남았습니다. 특성으로 사용할 열들의 데이터타입도 모두 숫자형이 되었기 때문에 모델을 학습시키고 검증하기에 적합해졌습니다.

 

이제 특성으로 삼을 열과 클래스로 삼을 열을 정리한 후 표준화를 해줘서 각 특성이 비슷한 값의 범위를 갖도록 하겠습니다. 

 

X = df[['clump', 'cell_size', 'cell_shape', 'adhesion', 'epithlial', 'bare_nuclei', 'chromatin', 'normal_nucleoli', 'mitoses']]
y = df['class']

from sklearn import preprocessing
X = preprocessing.StandardScaler().fit(X).transform(X)

 

X와 y를 훈련셋과 테스트셋으로 분류하겠습니다. 70%를 훈련셋으로 나머지 30%를 테스트셋으로 삼겠습니다. 

 

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=10)

 

이제 모델 학습 및 검증을 위한 모든 준비가 끝났습니다. 여기까지는 알고리즘을 SVM을 사용하든, 의사결정 나무를 사용하든 상관없이 다 동일합니다. 

 

모델 학습, 검증

이제 의사결정 나무 모델을 훈련 데이터셋으로 훈련시키고, 테스트 데이터에 대한 예측값을 산출해보겠습니다.

 

from sklearn import tree
tree_model = tree.DecisionTreeClassifier(criterion='entropy', max_depth=5)
tree_model.fit(X_train, y_train)
y_hat = tree_model.predict(X_test)

 

y_hat에 테스트 샘플들에 대한 예측 클래스 값들이 들어있습니다. 이 예측 클래스가 실제 클래스와 얼마나 같은지 모델의 성능을 평가하기 위해 confusion matrix와 precision, recall, f1-score 등을 확인해보겠습니다. 

 

from sklearn import metrics
matrix = metrics.confusion_matrix(y_test, y_hat)
print(matrix)

report = metrics.classification_report(y_test, y_hat)
print(report)

 

 

 

confusion matrix를 보니 양성(2)을 양성(2)으로 잘 예측한 케이스가 127건이고, 양성(2)을 악성(4)으로 잘못 예측한 케이스가 4건이고, 악성을 양성으로 잘못 예측한 케이스가 2건이고, 악성을 악성으로 잘 예측한 케이스가 72건입니다. 양성을 예측하는 기준으로 f1 점수는 0.98이고, 악성을 예측하는 기준으로는 f1 점수가 0.96이네요. 현재 이 모델은 매우 높은 성능을 보인다고 볼 수 있습니다. 

 

전체 코드

전체 코드는 다음과 같습니다.

 

import pandas as pd

uci_path = 'https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data'
df = pd.read_csv(uci_path, header=None)
print(df)

df.columns = ['id', 'clump', 'cell_size', 'cell_shape', 'adhesion', 'epithlial', 'bare_nuclei', 'chromatin', 'normal_nucleoli', 'mitoses', 'class']
print(df.info())

print(df['bare_nuclei'].unique())

import numpy as np
df['bare_nuclei'].replace('?', np.nan, inplace=True)
df.dropna(subset=['bare_nuclei'], axis=0, inplace=True)
df['bare_nuclei'] = df['bare_nuclei'].astype('int')
print(df.info(), '\n')

X = df[['clump', 'cell_size', 'cell_shape', 'adhesion', 'epithlial', 'bare_nuclei', 'chromatin', 'normal_nucleoli', 'mitoses']]
y = df['class']

from sklearn import preprocessing
X = preprocessing.StandardScaler().fit(X).transform(X)

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=10)

from sklearn import tree
tree_model = tree.DecisionTreeClassifier(criterion='entropy', max_depth=5)
tree_model.fit(X_train, y_train)
y_hat = tree_model.predict(X_test)

from sklearn import metrics
matrix = metrics.confusion_matrix(y_test, y_hat)
print(matrix)

report = metrics.classification_report(y_test, y_hat)
print(report)

 

관련 글

- 고유값 분해와 뗄레야 뗄 수 없는 주성분분석(PCA)  

- [python+pandas] 판다스 데이터 프레임에서 컬럼의 고유값을 알고 싶으면, unique 메소드  

- [python] scikit-learn의 confusion matrix 해석하기