Study/머신러닝

머신러닝 BASIC _ 지도학습 방법론

김 도경 2024. 10. 30. 13:00

[2024.10.30] 필수 온라인 강의 Part15 Machine Learning Basic CH04 지도학습 방법론

ML 방법론의 분류
  • 학습 방법에 따른 ML방법론의 구분
    - Supervision
          - 입력 데이터(x)에 대해 어떤 결과가 정답으로 나와야하는지 직접 지정해주는 것( 사람이 설정한 라벨(y))

    - 지도학습(Supervised Learning)
           - 일반적인 ML모델의 학습 방식으로, 입력 데이터(x)에서 함수의 출력값이 라벨(y)에 가까워지도록 학습하는 방식
    - 비지도학습(Unsupervised Learning)
            - Supervision, 즉 레이블(y)를 활용하지 않고 입력 데이터(x)로만 모델을 학습하는 방법론

  • 지도학습의 세부분류
  • 회귀 모델 : 모델의 종속변수(y)가 연속형 변수인 모델
             ( 방 개수, 면적, 주변 지하철역과의 거리 등을 통해 집 가격을 예측,날씨 데이터를 보고 내일의 강수량을 예측)
    회귀모델 방법론
          - 선형회귀모델 (Linear Regression) 
                  : f(x) = ax + b 꼴의 선형 모델을 사용
          - 상관관계 분석 (Correlation Analysis)
                  : 선형회귀모델을 통해 데이터를 얼마나 잘 설명할 수 있는지 확인하는 상관관계 분석

    분류 모델 : 모델의 종속변수(y)가 범주형 또는 이산형 변수인 모델
             ( 이메일의 내용을 보고 스팸메일인지 아닌지 구분, 손글씨 숫자 이미지를 보고 0~9 중 어느 숫자인지 구분)

       - 로지스틱 회귀모델 (Logistic Regression)
               : 이진 분류를 위한 모델링 방법론
              - 회귀모델을 통해 확률에 대한 모델링이 가능하도록 만든 로지스틱 함수
              - 어떤식으로 이를 확장해 3개 이상의 클래스를 분류하는 다중분류문제로 만드는지

    - k-최근접이웃 (k-Nearest Neighbor) 분류기
    - 결정트리 (Decision Tree)
    - 서포트 벡터머신 (SVM, Support Vector Machine)
회귀모델의 정의와 개념 소개
  • “회귀”라는 이름의 기원
      - 프랜시스 골턴(F.Galton)의 1885년 논문, “Regression toward Mediocrity in Hereditary Stature” (유전에 의한, 평균 신장으로의 회귀)에서 유래한 이름
     - 분석 과정에서 아무것도 회귀하지 않는다.

  • 회귀모델
    - 입력값이 무엇이든, 출력값이 연속형 변수인 모델을 사용하는 방법론
             - 부모의 키 → 자녀의 키, 오늘의 기온 → 내일의 기온
    - 입력변수는 여러개거나 범주형이라도 상관
             - 거주지역과 나이, 성별 → 연간 소득 금액, 특정 회사의 수익 관련 지표들 → 1주일 후 주식의 가격
    - 출력변수가 여러개인 경우 있음
            - 사람 얼굴 이미지 → 눈, 코, 입의 위치 좌표

  • 회귀모델링 방법론의 세부 구분
    - 입력변수의 개수
          - 한개인 경우 → 단순 회귀분석
          - 여러개인 경우 → 다중 회귀분석
    - 출력변수는 여러개
           - 한개인 경우 → (단변량) 회귀분석
           - 여러개인 경우 → 다변량 회귀분석
    - 사용하는 모델
           - 선형 회귀분석
           - 비선형 회귀분석

  • Line Fitting 관점에서의 선형회귀모델
          - 직선의 기울기(a)와 y절편(b)을 조절해 데이터(점들)와의 오차가 가장 작아지도록 만들기
          - MSE 손실함수 : 오차는 목표값과 출력값 간 차이의 제곱으로 정의됨

  • convex함수
    - 최적화 문제로서의 선형회귀 
    - 이 관점에서 linear regression은 오른쪽과 같은 손실함수의 argmin(최소값이 되는 a, b)값을 찾는 컨벡스 최적화(Convex Optimization) 문제

    - 최적화의 해석적 해법
         - 빠르고 간편하게 정확한 값을 구할 수 있음
         - 미리 해법이 알려진 경우에만 사용할수 있어 사용 조건이 까다롭거나, 아예 쓸수 없는 경우도 많음
        : 인수분해, 근의 공식

    - 최적화의 수치적 해법(Numerical Solution, Newton’s Method)
          - 결과가 근사값으로 나오고, 여러 단계를 거쳐야해 시간이 많이 필요한 경우가 많음
          - 대신 좀더 일반적으로, 많은 경우에 사용이 가능하다.
             : 점진적으로, 근사적인 해를 찾는 방법

    - 최소제곱 선형회귀분석의 해석적 해법
        : 데이터 X, y가 다음과 같이 정의되고, MSE손실함수를 사용하는 다중 선형회귀 분석

  • 상관분석과 상관계수
    상관관계(Correlation)
           - 한 변수가 변화할 때 다른 변수가 함께 변화하는 경향성을 보일 때 두 변수 사이의 관계를 상관관계
    인과관계(Causation)
            - 한 변수의 변화가 원인이 되어 그 결과로서 다른 변수를 변화시킬 때, 두 변수 사이의 관계를 인과관계

  • 상관관계 분석(Correlation Analysis)
         - 두 변수 사이의 상관관계를 판단하기 위한 분석과정
         - 피어슨 상관계수(Pearson Correlation Coefficient) : 상관 분석의 결과
        -  두 변수 사이에 어느 정도로 강한 선형상관관계가 있는지를 -1에서 1 범위의 숫자로 나타낸 것.

    - 상관관계 분석은 다음의 4가지 기본가정을 전제
            : 대상 변수들이 이 가정을 만족하지 않는다면 상관계수를 구해도 의미가 없음
    1. 선형성: 두 변인 X와 Y의 관계가 직선적
            - 오른쪽과 같은 데이터는 매우 강한 비선형적 상관관계가 있음
            - 단순선형 회귀모델로 분석하면 0에 가까운 상관계수가 나옴
    2. 등분산성: X의 값에 관계없이 Y의 분산이 일정
            - 모든 입력범수의 범위에 대해 잔차의 분산이 동일해야 한다는 가정.
    3. 정규성: 각 변인은 모두 정규분포
          - 일반적으로 분석 전에 표준화(Standardization)방식의 전처리를 진행

    4. 독립성: 각 샘플들은 모두 독립적
          - 각 샘플들은 모두 독립적으로 추출

  • 상관행렬(Correlation Matrix)
       - 입력 피쳐에 포함된 모든 변수 쌍의 조합에 대한 상관계수를 행렬의 형태로 나타낸 것
       - 계산 결과는 대칭행렬로 나오지만, 일반적으로 의미가 없는 중복값과 대각선 원소를 제거한 후, 다음과 같은 하삼각행렬의 형태로 시각화
       : 데이터 탐색 시, 변수간의 상호 연관성을 파악하는데 유용


    - 공선성(Collinearity)
      - 상관행렬을 시각화했을 때 몇몇 변수들간의 상관계수가 1.0이나 -1.0으로 나타나는 경우가 있는데, 이런 경우 두 변수 사이에 공선성이 있다고 표현
         - 한 변수가 서로 다른 단위로 표기되어 데이터셋 내에 중복으로 포함되는 경우
         - 같은 값이라고 생각함 : 즉, 의미가 없음
         - 다중공선성(Multicollinearity) : 한 변수가 여러 변수들의 선형결합으로 나타나는 경우를 의미
               - 다른 여러 변수들의 평균값이 별도 변수로 데이터셋에 포함되어있는 경우

    * 공선성, 또는 다중공선성이 나타나지 않을 때까지 변수를 제거하는 방식의 전처리를 진행

  • 상관계수는 직선의 기울기가 아니다!!!!!!!!!!!!!!
    - 상관계수는 상관관계의 강도를 나타낸 것
    - 회귀 직선의 기울기가 변해도, 그 부호가 바뀌지 않는 한 값이 변하지 않음
    - 회귀분석의 결과로 나오는 회귀계수(결정계수)는, 직선의 기울기 값 그 자체를 의미하므로 데이터의 기울기가 변하면 따라서 변화

    - 분석에 사용하는 변수들이 모두 평균 0, 표준편차 1인 표준정규분포를 따르는 경우에는, 상관계수와 회귀계수의 값이 일치
선형 회귀 모델링 방법론 실습
  • 실습 데이터셋 준비
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

 

  • 데이터셋을 불러와 기본정보 확인
    - 데이터셋: Diabetes Data :  sklearn에서 제공하는 당뇨병 진행도 예측 데이터셋
# 당뇨병 데이터셋을 로드
diabetes = load_diabetes(scaled=False)

# 데이터셋의 "설명"(description)섹션 확인
print(diabetes["DESCR"])

 

  • 데이터셋의 전처리
    - 평균, 표준편차가 각각 0, 1이 되도록 만드는 표준화(Standardization) 방식의 전처리를 별도로 진행

- 인풋 데이터를 pandas의 DataFrame 오브젝트로 변환, 표준화 전처리 적용

# 분석에 필요한 정보만 가져온 후, pandas 데이터프레임으로 변환
data = diabetes["data"]
data = pd.DataFrame(data, columns=diabetes["feature_names"])

# Standardization 방식의 전처리 적용을 위해 feature별 평균값, 표준편차를 계산
fts_mean = data.mean(axis=0)
fts_std = data.std(axis=0)

# 평균이 0, 표준편차가 1이 되도록 표준화
data = (data - fts_mean) / fts_std

# 결과 확인. 모든 변수가 평균 0, 표준편차 1로 조정된 것을 확인합니다.
data.describe()

 

- 목표값에도 같은 방식으로 전처리 적용

target = diabetes["target"]
# target 변수의 모양 확인
print(target.shape)

# 처음 10개 값들만 뽑아서 확인
print("Original target values: ", target[:10])

# target의 평균값, 표준편차를 계산
tgt_mean = target.mean()
tgt_std = target.std()

# 표준화 적용
target = (target - tgt_mean) / tgt_std

# 결과 확인
print("Scaled target values: ", target[:10])
print(f"Mean: {target.mean()}, Std: {target.std()}")

 

  • 데이터셋 ㅜㄴ할
    - Scikit learn에서 제공하는 train_test_split 함수를 활용
    - 전체 데이터셋을 7:3비율로 분할해 학습용, 평가
# 재현성을 위해 random state를 지정
random_state = 1234

# train과 test를 7:3의 비율로 분할
train_data, test_data, train_target, test_target = train_test_split(data, target, test_size=0.3, random_state=random_state)

# 분할이 잘 이뤄졌는지 확인
print(f"train data: {train_data.shape}")
print(f"train target: {train_target.shape}")
print(f"test data: {test_data.shape}")
print(f"test target: {test_target.shape}")

 

  • 사이킷런을 활용한 선형회귀 모델 학습

- scikit learn패키지에 포함된 LinearRegression모델을 불러와 초기화

- 학습용 데이터셋을 이용해 학습

# (다중) 선형회귀모델 초기화
multi_regressor = LinearRegression()

# 학습용 데이터셋을 활용해 학습 진행
multi_regressor.fit(train_data, train_target)

# 회귀식의 intercept(Ax+b에서 b부분) 확인
# 데이터을 평균이 0이 되도록 전처리했으므로 0에 가까운 값이 나온다.
print("intercept: ", multi_regressor.intercept_)
# 학습된 회귀식 확인
print("coefficients: ", multi_regressor.coef_)

 

- 학습용, 평가용 데이터셋에서 모델의 예측값 계산
- 계산된 예측값을 목표값과 비교해 MSE손실함수값을 확인

# 학습, 평가 데이터셋에서 회귀식의 예측값 계산
multi_train_pred = multi_regressor.predict(train_data)
multi_test_pred = multi_regressor.predict(test_data)

# 위의 예측값을 목표값과 비교해 MSE 손실함수의 값 계산
multi_train_mse = mean_squared_error(multi_train_pred, train_target)
multi_test_mse = mean_squared_error(multi_test_pred, test_target)
print(f"Train MSE: {multi_train_mse:.5f}")
print(f"Test MSE: {multi_test_mse:.5f}")

 

- 결과를 시각화

  - 모델의 예측 결과가 얼마나 목표값과 비슷하게 나왔는지 확인
  - 목표값을 x축, 예측값을 y축으로 하는 산점도
  - 주황색으로 표시된 x=y 직선에 가까울수록 잘 예측

plt.figure(figsize=(4, 4))

plt.xlabel("target")
plt.ylabel("prediction")
# 테스트셋의 목표값(x축)과 모델의 예측값(y축)을 산점도로 도식화
y_pred = multi_regressor.predict(test_data)
plt.plot(test_target, y_pred, '.')

# y = x 직선을 플롯. 이 직선에 가까운 점일수록 정확한 예측이다.
x = np.linspace(-2.5, 2.5, 10)
y = x
plt.plot(x, y)

plt.show()

 

  • 해석적 해법을 통해 파라미터를 계산
    - 최소제곱 손실함수를 사용하는 선형회귀모델의 경우, 
                θ=(XTX)1XTy 이라는 해석적 해법(Analytical Solution)이 알려져 있음
    - 최소제곱 선형회귀문제의 해석적 해법을 이용해, numpy만으로 파라미터를 직접 계산

최소제곱 손실함수를 Ax=b 형태로 변환 후 numpy를 이용해 solve

# 위의 식을 일반적인 linear equation인 Ax=b 형태로 변형
A = train_data.T @ train_data
b = train_data.T @ train_target

# numpy를 활용해 linear equation 풀기
coef = np.linalg.solve(A, b)

# 학습된 parameter를 이용해 예측값을 내놓는 함수를 정의
def predict(data, coef):
    return data @ coef

# 학습, 평가 데이터셋에서 회귀식의 예측값 계산
train_pred = predict(train_data, coef)
test_pred = predict(test_data, coef)

# 위의 예측값을 목표값과 비교해 MSE 손실함수의 값 계산
train_mse = mean_squared_error(train_pred, train_target)
test_mse = mean_squared_error(test_pred, test_target)

print(f"Multi Regression Train MSE is {train_mse:.5f}")
print(f"Multi Regression Test MSE is {test_mse:.5f}")
# scikit-learn 패키지를 활용한 학습 결과
print(multi_regressor.coef_)
# 해석적 해법을 이용해 직접 계산한 학습 결과
print(coef)

 

-> 해석적 해법을 통해 구한 파라미터값이 위에서 scikit learn 패키지를 사용해 구한것과 정확히 일치하는것을 확인

 

  • 상관행렬의 시각화
    - 상관계수는 두 변수 x, y 사이의 상관관계의 강도를 나타내는 수치
           - 두 변수 사이의 단순선형회귀분석이 얼마나 효과적일지를 나타내는 것으로도 해석

- pandas DataFrame오브젝트의 상관행렬을 구하기
- 상관행렬의 중복되는 부분을 제거하고 나머지 부분을 시각화

# 데이터의 상관계수 행렬을 생성
corr = data.corr(numeric_only=True)

# figure에서 생략될 부분을 지정하는 mask 행렬을 생성
mask = np.ones_like(corr, dtype=bool)
mask = np.triu(mask)

# 시각화될 그림의 크기를 지정
# 히트맵 형태로 상관행렬 시각화하기
plt.figure(figsize=(13,10))
sns.heatmap(data=corr, annot=True, fmt='.2f', mask=mask, linewidths=.5, cmap='RdYlBu_r')
plt.title('Correlation Matrix')
plt.show()

 

  • 상관계수와 회귀계수(결정계수)
    - 상관행렬에서 두 변수를 선택해 단순선형회귀분석 모델을 학습

    - 단순선형회귀분석을 진행한 결과, 회귀계수의 값이 두 변수의 상관계수와 매우 비슷한 값
       - 전처리 파트에서 진행한것과 같이 각 변수들에 대해 평균이 0, 표준편차가 1이 되도록 표준화 전처리를 진행해준 경우 항상 이렇게 단순선형회귀의 계수는 상관계수와 같은 값이 나옴
# 단순선형회귀분석을 진행할 두 변수를 선정
x_feature = "s3"
y_feature = "s2"

# 모델 초기화 및 학습
simple_regressor = LinearRegression()
simple_regressor.fit(data[[x_feature]], data[[y_feature]])

# 결과 회귀계수 확인
coef = simple_regressor.coef_
print(coef)

 

분류 모델의 정의와 개념 소개

 

  • 이진분류(Binary Classification)
    - 출력값이 참 혹은 거짓(또는 0/1)두가지로만 나오는, 가장 간단한 형태의 분류모델
          - 질병 검사의 양성/음성, 스팸메일 필터링
    - 로지스틱 회귀는 이진 분류문제를 위한 모델 ( 결과값이 1일 확률을 예측하는 방식)

    - 확률의 모델링과 로지스틱 함수
            - 선형회귀모델의 결과를 그대로 확률로 해석할 경우, 문제가 생김
            - 출력 결과가 0~1범위를 벗어날 수 있음 / 학습이 불안정

  • 로지스틱(Logistic) 함수
       - 인풋을 0과 1사이로 변환해 확률로 해석될 수 있는 결과를 내보내주는 함수
       - 시그모이드(Sigmoid) 함수 : 오른쪽 그림과 같은 S자형 곡선그래프가 나오는 함수.
                - 딥러닝에서는 시그모이드함수라고 하면 보통 로지스틱 함수

       - 크로스엔트로피(Cross Entropy)
                - 로지스틱 함수를 통과한, 모델의 출력값 확률에 대해 적용하도록 만들어진 손실함수
                - 학습이 더 잘 됨 : 모델의 예측 결과가 완전히 틀린 경우에 BCE는 매우 큰 패널티를 주지만 MSE는 그렇지 않음
                - 모델이 0.8등 정답에 어느정도 근접한 결과를 내놓은 경우 MSE는 로스값이 너무 작아져 학습이 매우 느려짐
                - 로지스틱함수와 결합했을 때 미분식이 매우 간단       : 코드로의 구현이 쉽다.

  • 다중분류문제의 모델링
     - 클래스가 k개인 다중분류 문제의 출력값은, i번째 원소가 i번째 클래스가 정답일 확률을 나타내는 k차 벡터의 형태

     - 원 핫 인코딩(One Hot Encoding)
          - 정답 클래스가 i번째일 경우, i번째 원소만 1이고 나머지 모든 값이 0인 벡터로 라벨을 표현하는 방식.

     - 소프트맥스(Softmax)
          - 다중분류 모델에서 결과 logit 벡터를 각각 클래스에 대한 확률을 나타내는 벡터로 변환해주도록 설계된 함수

     - 크로스엔트로피(Cross Entropy)
           - 로지스틱 함수를 통과한, 모델의 출력값 확률에 대해 적용하도록 만들어진 손실함수

  • 다양한 방식의 분류문제 모델링
     - kNN(k-Nearest Neighbor) 알고리즘
          - 특정 인풋이 들어왔을 때, 학습 데이터셋에서 가장 근접한 k개의 라벨을 기준으로 출력값을 결정하는 간단한 방식
          - k=3인 경우(실선): 빨간색 2개 / 파란색1개 → 빨강으로 분류됨.
          - k=5인 경우(점선): 빨간색 2개 / 파란색3개 → 파랑으로 분류됨.


     - 결정트리(의사결정나무, Decision Tree)
        - 독립변수(X)내의 대소 관계나 특정 임계값(threshold)과의 비교 등의 판단을 계층적으로 적용하여 최종 결과를 분류하는 모델. (스무고개와 유사한 방식.)

     - 불순도(Impurity)
        - 한 범주 안에 데이터가 서로 얼마나 섞여 있는지를 측정하는 지표
        - 결정트리의 학습과정에서 손실함수와 같은 역할

      - 결정포레스트(Decision Forest or Random Forest)
         - 랜덤한 구조의 결정트리를 여러개 생성한 뒤, 각 트리의 결과를 종합해 최종 출력값을 결정하는 방식.
         - >> 이렇게 여러개 모델의 결과를 합쳐 더 나은 최종 성능을 내는 방법을 머신러닝 모델의 앙상블(Ensemble)

  • 서포트벡터머신(SVM)을 활용한 이진분류

    - 결정경계(Decision Boundary)
         - 이진분류 모델에서 판단의 기준이 되는 초평면 (Hyperplane). 결정경계를 기준으로 공간의 양쪽이 서로 다른 클래스로 분류

    - 선형분리가능한(Linearly Separable) 데이터셋
         - 하나의 결정경계를 통해 이진분류 데이터셋의 두 클래스가 완전히 나뉘어질 수 있을 때 이 데이터셋은 선형분리가 가능

    - 서포트 벡터 머신 (SVM, Support Vector Machine)
         - 선형분리 가능한 데이터셋에서, 데이터의 두 클래스를 가장 잘 분리하는 결정경계를 찾아내기 위한 방법론
         - 각 클래스에 속하는 데이터포인트들과 결정경계 사이의 거리 인 마진 (Margin)값을 최대화하도록 학습

    - 커널 트릭(Kernel Trick)
          - SVM은 기본적으로 선형분리 가능한 데이터에 대해서만 사용할 수 있는 방법론
          - 특정 커널함수를 통해 데이터를 고차원으로 변환해 선형분리가 가능해지도록 만들고 이후 SVM을 적용
분류문제 모델링 방법론 실습
  • 실습 데이터셋 준비
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 데이터 관련 임포트
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer  # 로지스틱 회귀와 결정트리 실습에 사용할 데이터셋

# SVM 실습에 사용할 합성 데이터 생성 및 시각화 툴
from sklearn.datasets import make_blobs
from sklearn.datasets import make_circles

# 로지스틱 회귀모델
from sklearn.linear_model import LogisticRegression

# 결정트리 모델과 시각화 관련 툴
import graphviz
from sklearn.tree import export_graphviz
from sklearn.tree import DecisionTreeClassifier

# SVM 모델
from sklearn.svm import SVC  # SVM 분류기(classifier)
from sklearn.gaussian_process.kernels import RBF

 

- 데이터셋: Breast cancer wisconsin (diagnostic) dataset : 위스콘신 유방암 데이터셋
      - 유방암이 악성(malignant)인지, 양성 (benign)인지 여부를 분류하는 이진분류(Binary Classification)문제를 실습

- 이진분류(Binary Classification)
     - 종속변수가 범주형 변수(Categorical Variable)로 주어지는 분류문제(Classification)의 일종
      - 결과가 1또는 0(True 또는 False) 두가지 범주중 하나로 결정되는 문제

  • 데이터셋을 불러와 기본정보 확인
cancer = load_breast_cancer()

print(cancer["DESCR"])

 

- feature의 전체 수량과 종류 확인

- target의 전체 수량과 종류 확인

# 데이터셋의 입력값이 몇개의 샘플과 몇개의 변수로 구성되어있는지 확인
print(cancer["data"].shape)

# 데이터셋의 인풋값을 구성하는 feature들의 이름을 확인
print(cancer["feature_names"])

 

- target에서 각 범주별 빈도 확인

# 데이터셋의 target을 확인
print(cancer["target"].shape)
# target의 각 범주가 어떤 클래스에 해당하는지 확인
print(cancer["target_names"])

# 데이터셋의 target을 살펴봅시다.
cancer["target"]

# target내에 양성, 악성 샘플 수 확인
count_malignant, count_benign = np.bincount(cancer["target"])
print(f"총 {count_malignant}개의 악성 샘플과 {count_benign}개의 양성 샘플 포함.")

 

  • 데이터셋의 시각화 및 특성 파악
    - 본격적인 분류문제 모델링을 진행하기 이전에, 간단한 분석과 시각화를 통해 데이터의 특성을 살펴봄
    - 각 피쳐들의 값에 따라 악성(malignant)과 양성 (benign)샘플이 어떻게 분포하는지 히스토그램을 통해 확인

- 각각 악성 또는 양성 중 한가지 샘플만 포함하도록 데이터셋을 둘로 분리

# 이후 코드가 간단해지도록 alias 설정
data = cancer["data"]
target = cancer["target"]

# 데이터에서 악성, 양성에 해당하는 샘플만 선택해 새로운 변수로 저장
data_malignant = data[target == 0]
data_benign = data[target == 1]

# 데이터셋 분리의 결과 확인
print("malignant(악성) 샘플 : ", data_malignant.shape)
print("benign(양성) 샘플 : ", data_benign.shape)

 

- 분리한 데이터셋을 활용해 한가지 변수에 대해 히스토그램 시각화

# 시각화에 사용할 변수의 인덱스를 지정
feature_idx = 0

# histogram의 형태로 악성, 양성 각각 샘플의 분포를 시각화.
plt.hist(data_malignant[:, feature_idx], bins=20, alpha=0.3)
plt.hist(data_benign[:, feature_idx], bins=20, alpha=0.3)

# 데이터셋의 피쳐, 클래스 이름 정보를 활용해 그래프에 정보를 추가
plt.title(cancer["feature_names"][feature_idx])
plt.legend(cancer["target_names"])

 

- 데이터셋 내의 모든 변수에 대해 같은 방식의 히스토그램 시각화 진행

# 30개의 전체 변수 각각에 대해 같은 방식의 시각화를 진행
plt.figure(figsize=[20,15])
for feature_idx in range(30):
    plt.subplot(6, 5, feature_idx + 1)

    # histogram의 형태로 악성, 양성 각각 샘플의 분포를 시각화.
    plt.hist(data_malignant[:, feature_idx], bins=20, alpha=0.3)
    plt.hist(data_benign[:, feature_idx], bins=20, alpha=0.3)

    # 데이터셋의 피쳐, 클래스 이름 정보를 활용해 그래프에 정보를 추가
    plt.title(cancer["feature_names"][feature_idx])
    # 악성/양성에 대한 범례(legend)는 첫번째 histogram에만 표시
    if feature_idx == 0:
        plt.legend(cancer["target_names"])
    plt.xticks([])

 

  • 로지스틱 회귀(Logistic Regression)을 이용한 이진 분류
    - 로지스틱 회귀는 이진분류 문제의 해결을 위한 방법론
          - 데이터가 범주1(True)에 속할 확률을 0에서 1사이 값으로 예측
          - 그 확률값이 정해진 한계값(Threshold)보다 큰지 작은지에 따라 해당 샘플이 둘중 어느 범주에 속하는지 분류해주는 지도 학습 알고리즘

- 데이터셋을 8:2로 랜덤 분할
- 모델을 초기화하고 학습 데이터셋을 fitting
- 평가 데이터셋에서 결과 스코어(정확도) 확인

# 결과 재현성을 위해 random seed를 설정합니다.
random_state = 1234

# 데이터셋을 8:2로 분할해 학습용 / 평가용으로 사용합니다.
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=random_state)

# 모델을 초기화하고 학습 데이터에서 최적화를 진행합니다.
model = LogisticRegression(max_iter=5000)
model.fit(X_train, y_train)

# 테스트셋에 대한 결과 정확도를 계산합니다.
score = model.score(X_test, y_test)

print('scores =', score)

 

  • 한계값 조정을 통한 결과 컨트롤
     - 로 지스틱 회귀모델의 한계값(Threshold)을 조절해 모델의 출력값으로 나오는 악성/양성의 빈도를 조절
    오류
       - False Positive : 실제로는 암이 아닌데(negative), 암이라고 잘못 판단하는 경우
       - False Negative : 실제로는 암인데(positive) 암이 아니라고 잘못 판단하는 경우 : 더 심각함
    -> 한계값 조절을 통한 False Negative줄이기

 - 사이킷런 모델에서 예측 결과를 확률값으로 받아오기

# 학습된 모델에서 결과 확률값을 그대로 가져오기
probs = model.predict_proba(X_test)[:, 1]

# 기본값인 0.5를 기준으로 판단한 결과는 원래 모델의 예측 함수(predict)와 동일
print("원래 예측값: \n", model.predict(X_test))
prediction = (probs > 0.5).astype(int)            
print(f"한계값 0.5로 판단한 예측값: \n", prediction)            # 한계값 = 0.5

# y_test == 0(양성)이지만 prediction == 1(음성)인 False Negative의 수를 계산해 출력
false_neg = ~y_test & prediction  # ~, &는 각각 bitwise "not", "and" 연산자
print(f"위음성(False Negative) 개수: {false_neg.sum()} 개")

threshold = 0.7         # 한계값을 조절해 위음성 빈도를줄이기

prediction = (probs > threshold).astype(int)
print(f"한계값 {threshold}로 판단한 예측값: \n", prediction)

false_neg = ~y_test & prediction
print(f"위음성(False Negative) 개수: {false_neg.sum()} 개")

 

  • 결정트리(Decision Tree)와 불순도(Impurity)
    - 결정 트리
         - 트리 구조를 사용하여 데이터를 분할하고 예측을 수행하는 머신러닝 모델

         - 트리의 각 노드에서는 특정 기준에 따라 데이터를 이진분류하며, 입력 샘플이 여러 노드를 거쳐 최종적으로 리프 노드 (트리의 끝)에 도달하면, 최종적인 예측값이 결정
    - 불순도
         - 한 노드에 서로 다른 범주의 데이터가 얼마나 섞여 있는지를 나타내는 지표
         - 불순도가 낮을수록 해당 노드의 데이터는 한 범주에 속하게 됨
         - 순도를 측정하는 방법으로는 주로 지니 불순도(Gini impurity)나 엔트로피(Entropy)가 사용

- 의사결정트리 학습

# 결정트리 학습
dec_tree = DecisionTreeClassifier(max_depth=10, random_state=1234)
dec_tree.fit(X_train, y_train)

# 결과 정확도 출력
print(f"학습 데이터셋 분류 정확도: {dec_tree.score(X_train, y_train):.3f}")
print(f"평가 데이터셋 분류 정확도: {dec_tree.score(X_test, y_test):.3f}")

 

- graphviz 패키지를 활용해 저장한 파일을 시각화

# "tree.dot"이라는 이름으로, 학습한 그래프 파일을 저장
export_graphviz(dec_tree, out_file="tree.dot", class_names=cancer["target_names"],
                feature_names=cancer["feature_names"], impurity=True, filled=True)

# 저장한 파일을 불러와 graphviz로 시각화
with open("tree.dot") as f:
    dot_graph = f.read()
display(graphviz.Source(dot_graph))

 

  • 결정트리의 시각화와 해석

- Feature Importance
   - 결정트리에서 각각의 변수가 예측 결과에 얼마나 중요한 역할을 했는지를 나타내는 지표
   - 결정트리의 노드에서 해당 변수를 활용해 불순도를 얼마나 감소시켰는지를 나타냄
   - 랜덤으로 학습되는 개별 모델에 의존하는 값이므로, 이 값이 낮게 나타났다고 해서 그 변수가 전혀 중요치 않다는 결론을 내릴 수는 없음

 

- feature importance를 출력해 확인

print("Feature importances:")                # feature importance를 출력해 확인
print(dec_tree.feature_importances_)

 

feature importance를 출력해 확인

# feature별 importance값을 bar graph형태로 시각화

n_features = data.shape[1]
plt.barh(np.arange(n_features), dec_tree.feature_importances_, align='center')
plt.yticks(np.arange(n_features), cancer["feature_names"])
plt.xlabel("Feature importance")
plt.ylabel("Feature")
plt.ylim(-1, n_features)

 

- feature importance가 유능한지 확인

# 시각화를 수행할 변수를 지정합니다.
feature_name = "worst concave points"  # feature importance 높은 node
feature_threshold = 0.142
# feature_name = "worst fractal dimension"  # feature importance 낮은 node
# feature_threshold = 0.065

# 주어진 변수 이름을 통해 index를 찾고 feature importance값을 출력
list_feature_names = cancer["feature_names"].tolist()
feature_idx = list_feature_names.index(feature_name)  # builtin list의 index함수
print("feature importance: ", dec_tree.feature_importances_[feature_idx])

# histogram의 형태로 악성, 양성 각각 샘플의 분포를 시각화.
plt.hist(data_malignant[:, feature_idx], bins=20, alpha=0.3)
plt.hist(data_benign[:, feature_idx], bins=20, alpha=0.3)

# 데이터셋의 피쳐, 클래스 이름 정보를 활용해 그래프에 정보를 추가
plt.axvline(feature_threshold)
plt.title(cancer["feature_names"][feature_idx])
plt.legend(["threshold"] + list(cancer["target_names"]))
plt.show()
  • 사이킷런을 활용한 합성 데이터 생성
    - SVM 모델링을 진행할 합성데이터를 생성하고, 시각화를 통해 간단히 특성

- make_blobs 함수를 사용해 랜덤 데이터 생성

- 생성된 데이터 시각화

# 재현성을 위한 랜덤시드 고정
random_state = 20

# 합성데이터 생성
X, y = make_blobs(
    n_samples=100,  # 샘플의 수
    centers=2,  # 클러스터의 수. 이진분류 실습이므로 2로 설정
    cluster_std=1.2,  # 샘플의 표준편차
    random_state=random_state
)

plt.scatter(X[:,0], X[:,1], c=y, s=30)
plt.title('datasets random_state=20')
plt.show()
  • blobs데이터를 활용한 SVM 분류모델의 학습
    - 주어진 데이터를 두 개의 그룹으로 분리하는 방법
    - 데이터들과 거리가 가장 먼 초평면(hyperplane)을 결정경계로 선택해 경계의 양쪽을 별개의 클래스로 분류하는 방법
    - 주어진 각 클래스의 데이터셋들로부터 가장 멀리 떨어진 초평면을 결정경계로 선택하는 방식으로 학습

- 결정경계 시각화를 위한 보조함수 정의

def make_xy_grid(xlim, ylim, n_points):
    #   1. x, y 각각이 일정 간격으로 변화하는 grid를 생성
    xx = np.linspace(*xlim, n_points)
    yy = np.linspace(*ylim, n_points)
    YY, XX = np.meshgrid(yy, xx)

    #   2. grid 위의 900개 점 좌표들을 순서대로 나타낸 array
    xy = np.stack([XX.reshape(-1), YY.reshape(-1)], axis=1)  # shape: (n^2, 2)
    return XX, YY, xy

- 사이킷런의 SVM 분류기(Support Vector Classifier)를 학습한 후 분류 결과를 시각화

# 생성한 데이터로 SVM 분류기 모델 학습
clf = SVC(kernel='linear', C=1.0)
clf.fit(X, y)

# 데이터셋 산점도 시각화
plt.scatter(X[:,0], X[:,1], c=y, s=30, cmap=plt.cm.Paired)

# 시각화를 위해 x, y값 범위를 확인
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()

# 모델의 결정 경계 시각화
XX, YY, xy = make_xy_grid(xlim, ylim, 30)
Z = clf.decision_function(xy).reshape(XX.shape)

# 위에서 생성한 결정경계와 마진 시각화
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])

# 서포트 벡터를 표시
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=100, linewidth=1, facecolors='none', edgecolors='k')
plt.title('C=1.0')
plt.show()

 

  • 선형분리가능성(Linear separability)과 커널트릭(Kernel Trick)
    - 선형분리가 불가능한 데이터셋이 주어졌을 때, 커널트릭을 활용해 SVM분류기를 학습하는 방법
    - 커널 트릭 :  선형분리 가능한 고차원 공간으로 맵핑해주면 SVM을 사용해 이진분류를 수행
    - RBF(Radial Basis Function)이라는 커널함수를 사용해 선형분리 불가능한 데이터셋에 대해 SVM 분류를 적용

- 사이킷런의 make_circles함수를 사용해 선형분리 불가한 데이터셋을 생성

- 생성한 데이터셋을 시각화

# 데이터 생성
X,y = make_circles(factor=0.1, noise=0.1) # factor: 생성할 원의 반지름 비율

# 생성한 데이터를 시각화
plt.figure(figsize=(4, 4))
plt.scatter(X[:,0], X[:,1], c=y, s=30, cmap=plt.cm.Paired)
plt.title('factor=0.1')
plt.show()

 

- 생성한 데이터셋의 인풋에 RBF 커널함수 적용

- 3D로 시각화

# RBF 커널함수 적용
z = RBF(1.0).__call__(X)[0]

# 3D 공간에 커널함수 적용된 데이터셋을 시각화
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.scatter(X[:, 0], X[:, 1], z, c=y, s=30, cmap=plt.cm.Paired)

ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')

plt.title('RBF Kernel')
plt.show()
plt.clf()

 

- 사이킷런의 SVC함수에 내장된 커널함수 옵션을 활용해 데이터셋 분류

- 분류 결과 및 결정경계 시각화

def plot_svc_decision_function(model,ax=None):
    if ax is None:
        ax = plt.gca()
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    x = np.linspace(xlim[0],xlim[1],30)
    y = np.linspace(ylim[0],ylim[1],30)
    Y,X = np.meshgrid(y,x)
    xy = np.vstack([X.ravel(), Y.ravel()]).T
    P = model.decision_function(xy).reshape(X.shape)

    # 결정경계 시각화
    ax.contour(X, Y, P,colors="k",levels=[-1,0,1],alpha=0.5,linestyles=["--","-","--"])
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

#데이터셋 호출.
X,y=make_circles(factor=0.2,noise=0.1) #factor = R2/R1, noise=std
# 커널트릭을 적용한 SVM분류기 학습
#kernel을 rbf(Radial Basis Function)을 이용하여 SVC를 작동시켜보자.
clf = SVC(kernel="rbf").fit(X,y)

plt.scatter(X[:,0],X[:,1],c=y,s=30,cmap=plt.cm.Paired)
plot_svc_decision_function(clf)
plt.title('Train SVM')
plt.show()