비지도 학습

  • vs 지도학습
    • 종속변수 = 타깃
  • 비지도학습은 종속변수 및 타겟이 없음
  • 분류
    • 다중분류
    • 전체조건이 (다양한 유형) 데이터가 많아야함
    • 딥러닝과 연관 (자연어처리, 이미지)

데이터 불러오기

1
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2022-03-31 01:37:42--  https://bit.ly/fruits_300_data
Resolving bit.ly (bit.ly)... 67.199.248.10, 67.199.248.11
Connecting to bit.ly (bit.ly)|67.199.248.10|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following]
--2022-03-31 01:37:42--  https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy
Resolving github.com (github.com)... 140.82.114.3
Connecting to github.com (github.com)|140.82.114.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following]
--2022-03-31 01:37:42--  https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.110.133, 185.199.111.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3000128 (2.9M) [application/octet-stream]
Saving to: ‘fruits_300.npy’

fruits_300.npy      100%[===================>]   2.86M  --.-KB/s    in 0.07s   

2022-03-31 01:37:43 (41.4 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
1
2
3
4
5
6
import numpy as np
import matplotlib.pyplot as plt

fruits = np.load('/content/fruits_300.npy')
print(fruits.shape)
print(fruits.ndim)
(300, 100, 100)
3
  • 첫번째 차원(300) = 샘플의 개수
  • 두번째 차원(100) = 이미지 높이
  • 세번째 차원(100) = 이미지 너비
  • 이미지 크기 100 x 100
1
fruits[0, :, 0]
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 6, 1, 2, 3, 1, 2, 3,
       1, 1, 1, 2, 2, 2, 5, 2, 2, 5, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1,
       1, 2, 2, 1, 1, 3, 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], dtype=uint8)
  • 이미지 시각화
    • 흑백 사진을 담고 있다.
    • 0 ~ 255까지의 정숫값을 가진다
1
2
plt.imshow(fruits[0], cmap = 'gray')
plt.show()

png

1
2
plt.imshow(fruits[0], cmap = 'gray_r')
plt.show()

png

  • 여러 이미지 시각화
1
2
3
4
5
fig, axs = plt.subplots(1, 2)
axs[0].imshow(fruits[100], cmap = 'gray_r')
axs[1].imshow(fruits[200], cmap = 'gray_r')

plt.show()

png

픽셀값 분석

  • 배열을 계산할 때 1차원 배열로 펼쳐서 계산하면 편리하기 때문에 100 x 100 이미지를 펼쳐서 10,000인 1차원 배열로 만든다.
1
2
3
4
5
6
7
apple = fruits[0:100].reshape(-1, 100 * 100)
pineapple = fruits[100:200].reshape(-1, 100 * 100)
banana = fruits[200:300].reshape(-1, 100*100)

print(apple.shape)
print(pineapple.shape)
print(banana.shape)
(100, 10000)
(100, 10000)
(100, 10000)
  • 100 x 100 이미지를 펼친 10,000인 1차원 배열로 만들었으니 열을 사용해 샘플의 픽셀 평균값을 계산
  • axis = 0 vs axis = 1 차이 확인(p.293)
1
2
# axis = 1 열
print(apple.mean(axis = 1))
[ 88.3346  97.9249  87.3709  98.3703  92.8705  82.6439  94.4244  95.5999
  90.681   81.6226  87.0578  95.0745  93.8416  87.017   97.5078  87.2019
  88.9827 100.9158  92.7823 100.9184 104.9854  88.674   99.5643  97.2495
  94.1179  92.1935  95.1671  93.3322 102.8967  94.6695  90.5285  89.0744
  97.7641  97.2938 100.7564  90.5236 100.2542  85.8452  96.4615  97.1492
  90.711  102.3193  87.1629  89.8751  86.7327  86.3991  95.2865  89.1709
  96.8163  91.6604  96.1065  99.6829  94.9718  87.4812  89.2596  89.5268
  93.799   97.3983  87.151   97.825  103.22    94.4239  83.6657  83.5159
 102.8453  87.0379  91.2742 100.4848  93.8388  90.8568  97.4616  97.5022
  82.446   87.1789  96.9206  90.3135  90.565   97.6538  98.0919  93.6252
  87.3867  84.7073  89.1135  86.7646  88.7301  86.643   96.7323  97.2604
  81.9424  87.1687  97.2066  83.4712  95.9781  91.8096  98.4086 100.7823
 101.556  100.7027  91.6098  88.8976]
  • 각 과일에 대한 히스토그램 작성
1
2
3
4
5
plt.hist(np.mean(apple, axis = 1), alpha = 0.8) # alpha 는 그래프의 색상 농도
plt.hist(np.mean(pineapple, axis = 1), alpha = 0.8)
plt.hist(np.mean(banana, axis = 1), alpha = 0.8)
plt.legend(['apple', 'pineapple', 'banana']) # legend() 과일 분류 상자
plt.show()

png

1
2
3
4
5
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].bar(range(10000), np.mean(apple, axis = 0))
axs[1].bar(range(10000), np.mean(pineapple, axis = 0))
axs[2].bar(range(10000), np.mean(banana, axis = 0))
plt.show()

png

1
2
3
4
5
6
7
8
apple_mean = np.mean(apple, axis = 0).reshape(100, 100)
pineapple_mean = np.mean(pineapple, axis = 0).reshape(100, 100)
banana_mean = np.mean(banana, axis = 0).reshape(100, 100)
fig, axs = plt.subplots(1, 3, figsize=(20,5))
axs[0].imshow(apple_mean, cmap = 'gray_r')
axs[1].imshow(pineapple_mean, cmap = 'gray_r')
axs[2].imshow(banana_mean, cmap = 'gray_r')
plt.show()

png

평균값과 가까운 사진 고르기

1
2
3
abs_diff = np.abs(fruits - apple_mean)
abs_mean = np.mean(abs_diff, axis =(1,2))
print(abs_mean.shape)
(300,)
1
2
3
4
5
6
7
apple_index = np.argsort(abs_mean)[:100]
fig, axs = plt.subplots(10, 10, figsize = (10, 10))
for i in range(10):
for j in range(10):
axs[i, j].imshow(fruits[apple_index[i*10 + j]], cmap = 'gray_r')
axs[i, j].axis('off')
plt.show()

png

Comment and share

K-평균

  • 각각의 픽셀값(3차원 -> 1차원 배열) 평균 구함

    • 픽셀의 평균값을 활용해서 사과, 파앤애플, 바나나의 근사한 이미지를 추출하는 것
  • 어떻게 평균값을 구할 수 있을까?

    • K-평균 알고리즘 (K-Means) 알고리즘
    • 평균값 = Cluster Center = Centroid
1
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2022-03-31 02:16:44--  https://bit.ly/fruits_300_data
Resolving bit.ly (bit.ly)... 67.199.248.10, 67.199.248.11
Connecting to bit.ly (bit.ly)|67.199.248.10|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following]
--2022-03-31 02:16:44--  https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy
Resolving github.com (github.com)... 192.30.255.113
Connecting to github.com (github.com)|192.30.255.113|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following]
--2022-03-31 02:16:44--  https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3000128 (2.9M) [application/octet-stream]
Saving to: ‘fruits_300.npy’

fruits_300.npy      100%[===================>]   2.86M  --.-KB/s    in 0.05s   

2022-03-31 02:16:44 (62.6 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
1
2
3
4
import numpy as np
fruits = np.load('/content/fruits_300.npy')
print(fruits.shape)
print(fruits.ndim)
(300, 100, 100)
3
  • 3차원(샘플개수, 너비, 높이)
  • 2차원(샘플개수, 너비 x 높이)
1
2
fruits_2d = fruits.reshape(-1, 100 * 100)
fruits_2d.shape
(300, 10000)
  • K-평균 알고리즘 활용
1
2
3
from sklearn.cluster import KMeans
km = KMeans(n_clusters = 3, random_state = 42)
km.fit(fruits_2d)
KMeans(n_clusters=3, random_state=42)
  • 모형 학습 후, labels
1
print(km.labels_)
[2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 0 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 0 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 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 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 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 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
print(np.unique(km.labels_, return_counts = True))
(array([0, 1, 2], dtype=int32), array([111,  98,  91]))
  • 그래프를 직접 그려본다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt

def draw_fruits(arr, ratio=1):
n = len(arr) # n은 샘플 개수입니다
# 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 10으로 나누어 전체 행 개수를 계산합니다.
rows = int(np.ceil(n/10))
# 행이 1개 이면 열 개수는 샘플 개수입니다. 그렇지 않으면 10개입니다.
cols = n if rows < 2 else 10
fig, axs = plt.subplots(rows, cols,
figsize=(cols*ratio, rows*ratio), squeeze=False)
for i in range(rows):
for j in range(cols):
if i*10 + j < n: # n 개까지만 그립니다.
axs[i, j].imshow(arr[i*10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
1
draw_fruits(fruits[km.labels_ == 0])

png

클러스터 중심

1
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio = 3) # fruits_2d 샘플의 클러스터 중심이기 때문에 이미지로 출력하려면 100 X 100 크기로 변환

png

1
print(km.transform(fruits_2d[100:101])) # transform 메서드는 훈련데이터 샘플에서 클러스터 중심까지 거리로 변환해준다.
[[3393.8136117  8837.37750892 5267.70439881]]
1
print(km.predict(fruits_2d[100:101]))
[0]
1
draw_fruits(fruits[100:101])

png

최적의 K-평균 찾기

  • inertia
    • 목적함수 값이 최소화될 때까지 군집의 중심위치와 각 데이터가 소속될 군집를 반복해서 찾는다. 이 값을 관성(inertia)이라고 함
    • 클러스터에 속한 샘플이 얼마나 가깝게 모여있는지를 나타내는 값
  • 엘보우 방법
    • 클러스터의 개수가 늘어나면 클러스터 개개의 크기는 줄어들기 때문에 이너셔도 같이 줄어듬, 그렇기 때문에 클러스터 개수를 늘려가면서 이너셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는다.
1
2
3
4
5
6
7
inertia = []
for k in range(2, 7):
km = KMeans(n_clusters = k, random_state = 42)
km.fit(fruits_2d)
inertia.append(km.inertia_)
plt.plot(range(2, 7), inertia)
plt.show
<function matplotlib.pyplot.show>

png

Comment and share

트리의 앙상블

  • LightGBM 기억!
    • GBM –> XGBoost –> LightBGM
    • 참고 1. 모델 개발 속도가 빨라졌는지?
    • 참고 2. 모델의 성능이 좋아졌는지?
  • TabNet (2019)
    • 딥러닝 컨셉 이해

랜덤 포레스트(Forest)

  • 결정 트리 나무를 500개 심기
  • 최종적인 결정은 투표 방식
    • 나무-1 : 양성
    • 나무-2 : 음성
    • 나무-3 : 양성
    • 나무-500 : 양성
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

wine = pd.read_csv('https://bit.ly/wine_csv_data')

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

train_input, test_input, train_target, test_target = train_test_split(data,
target,
test_size=0.2,
random_state=42)
  • p.267
    • cross_validate() 교차 검증 수행
1
2
3
4
5
6
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs = -1, random_state = 42) # 모든 CPU 코어를 사용
scores = cross_validate(rf, train_input, train_target,
return_train_score = True, n_jobs = -1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9973541965122431 0.8905151032797809
1
2
rf.fit(train_input, train_target)
print(rf.feature_importances_)
[0.23167441 0.50039841 0.26792718]
1
2
3
rf = RandomForestClassifier(oob_score = True, n_jobs = -1, random_state = 42) # oob(out of bag) 부트스트랩 샘플에 포함되지 않고 남는 샘플
rf.fit(train_input, train_target)
print(rf.oob_score_)
0.8934000384837406

그레이디언트 부스팅

  • 이전 트리(깊이가 얕은 결정 트리)의 오차를 보완하는 방식으로 사용
  • 학습률 매개변수로 속도를 조절
  • 장점 : 과대적합을 잘 억제시킴
  • 단점 : 속도가 느림
1
2
3
4
5
6
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state = 42)
scores = cross_validate(gb, train_input, train_target,
return_train_score = True, n_jobs = -1) # return_train_score 훈련 점수 포함 여부

print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.8881086892152563 0.8720430147331015
1
2
3
4
5
gb = GradientBoostingClassifier(n_estimators = 500, learning_rate = 0.2, random_state = 42)
scores = cross_validate(gb, train_input, train_target,
return_train_score = True, n_jobs = -1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9464595437171814 0.8780082549788999
  • 흐름
      1. 데이터 전처리 / 시각화
      1. 기본 모형으로 전체 흐름을 설계
      1. 여러 모형을 비교 대조
      1. 교차검증. 하이퍼파라미터 성능 비교

Comment and share

데이터 불러오기

  • 와인 데이터
    • alcohol(알코올 도수), sugar(당도), pH(산도)
    • class 0 = 레드 와인
    • class 1 = 화이트 와인
1
2
3
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head()

alcohol sugar pH class
0 9.4 1.9 3.51 0.0
1 9.8 2.6 3.20 0.0
2 9.8 2.3 3.26 0.0
3 9.8 1.9 3.16 0.0
4 9.4 1.9 3.51 0.0

  <script>
    const buttonEl =
      document.querySelector('#df-32f88054-395d-476e-b732-de1fe6fb312f button.colab-df-convert');
    buttonEl.style.display =
      google.colab.kernel.accessAllowed ? 'block' : 'none';

    async function convertToInteractive(key) {
      const element = document.querySelector('#df-32f88054-395d-476e-b732-de1fe6fb312f');
      const dataTable =
        await google.colab.kernel.invokeFunction('convertToInteractive',
                                                 [key], {});
      if (!dataTable) return;

      const docLinkHtml = 'Like what you see? Visit the ' +
        '<a target="_blank" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'
        + ' to learn more about interactive tables.';
      element.innerHTML = '';
      dataTable['output_type'] = 'display_data';
      await google.colab.output.renderOutput(dataTable, element);
      const docLink = document.createElement('div');
      docLink.innerHTML = docLinkHtml;
      element.appendChild(docLink);
    }
  </script>
</div>
  • info()
    • 결측치 확인 / 변수 타입
1
wine.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6497 entries, 0 to 6496
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   alcohol  6497 non-null   float64
 1   sugar    6497 non-null   float64
 2   pH       6497 non-null   float64
 3   class    6497 non-null   float64
dtypes: float64(4)
memory usage: 203.2 KB
1
wine.describe()

alcohol sugar pH class
count 6497.000000 6497.000000 6497.000000 6497.000000
mean 10.491801 5.443235 3.218501 0.753886
std 1.192712 4.757804 0.160787 0.430779
min 8.000000 0.600000 2.720000 0.000000
25% 9.500000 1.800000 3.110000 1.000000
50% 10.300000 3.000000 3.210000 1.000000
75% 11.300000 8.100000 3.320000 1.000000
max 14.900000 65.800000 4.010000 1.000000

  <script>
    const buttonEl =
      document.querySelector('#df-b999d81a-0559-4b92-8e93-747feee74f33 button.colab-df-convert');
    buttonEl.style.display =
      google.colab.kernel.accessAllowed ? 'block' : 'none';

    async function convertToInteractive(key) {
      const element = document.querySelector('#df-b999d81a-0559-4b92-8e93-747feee74f33');
      const dataTable =
        await google.colab.kernel.invokeFunction('convertToInteractive',
                                                 [key], {});
      if (!dataTable) return;

      const docLinkHtml = 'Like what you see? Visit the ' +
        '<a target="_blank" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'
        + ' to learn more about interactive tables.';
      element.innerHTML = '';
      dataTable['output_type'] = 'display_data';
      await google.colab.output.renderOutput(dataTable, element);
      const docLink = document.createElement('div');
      docLink.innerHTML = docLinkHtml;
      element.appendChild(docLink);
    }
  </script>
</div>

표준화 작업

1
2
data = wine[['alcohol','sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

훈련데이터와 테스트데이터로 분리

1
2
3
4
5
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)
(5197, 3) (1300, 3)
  • 표준화 진행
1
2
3
4
5
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

모델 만들기

로지스틱회귀

1
2
3
4
5
6
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_)
0.7808350971714451
0.7776923076923077
[[ 0.51270274  1.6733911  -0.68767781]] [1.81777902]

로지스틱 회귀

  • 수식

의사결정트리의 기본 알고리즘을 활용해서, MS, 구글 등 이런 회사들이 신규 알고리즘을 만듬

  • XGBoost, LightGBM, CatBoost
  • 캐글 정형데이터
  • LightGBM (지금 현재 실무에서 많이 쓰임)
    • 4월 말 까지는 코드에 집중 대회 나감
    • PPT (알고리즘 소개)
1
2
3
4
5
6
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)) # 테스트 세트
0.996921300750433
0.8592307692307692
1
2
3
4
5
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize = (10, 7))
plot_tree(dt)
plt.show()

png

  • filled = True
    • 클래스 마다 색깔을 부여하고, 어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시
1
2
3
plt.figure(figsize = (10, 7))
plot_tree(dt, max_depth = 1, filled = True, feature_names = ['alcohol','sugar', 'pH'])
plt.show()

png

가지치기

  • 과대적합을 방지하기 위한 것

  • max_depth

    • 트리의 최대 깊이
    • default = None
    • 완벽하게 클래스 값이 결정될 때 까지 분할 또는 데이터 개수가 min_samples_split보다 작아질 때까지 분할
    • 깊이가 깊어지면 과적합될 수 있으므로 적절히 제어 필요
1
2
3
4
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))
0.8454877814123533
0.8415384615384616
1
2
3
plt.figure(figsize = (20, 15))
plot_tree(dt, filled = True, feature_names = ['alcohol', 'sugar', 'pH'])
plt.show() # 지니계수(불순도) 및 value 값 판단

png

1
2
3
4
5
6
7
8
9
10
11
import graphviz
from sklearn import tree

# DOT data
dot_data = tree.export_graphviz(dt, out_file=None,
feature_names = ['alcohol', 'sugar', 'pH'],
filled=True)

# Draw graph
graph = graphviz.Source(dot_data, format="png")
graph

svg

1
graph.render("decision_tree_graphivz")
'decision_tree_graphivz.png'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from matplotlib.colors import ListedColormap, to_rgb
import numpy as np

plt.figure(figsize=(20, 15))
artists = plot_tree(dt, filled = True,
feature_names = ['alcohol', 'sugar', 'pH'])

colors = ['blue', 'red']
for artist, impurity, value in zip(artists, dt.tree_.impurity, dt.tree_.value):
r, g, b = to_rgb(colors[np.argmax(value)])
f = impurity * 2
artist.get_bbox_patch().set_facecolor((f + (1-f)*r, f + (1-f)*g, f + (1-f)*b))
artist.get_bbox_patch().set_edgecolor('black')

plt.show()

png

Comment and share

교차 검증과 그리드 서치

  • 키워드 : 하이퍼 파라미터 ( 그리드서치 vs 램덤서치)
  • 데이터가 작을 떄 주로 사용
  • 하이퍼 파라미터
    • max_depth : 3, 정확도가 84%
  • 결론
    • 모르면 디폴드만 쓰자!
    • 가성비 (시간 대비 성능 보장 안됨!)

검증 세트

  • 테스트 세트 (1회성)
  • 훈련 데이터를 훈련 데이터 + 검증 데이터로 재 분할

현실

  • 테스트 데이터가 별도로 존재하지 않음!
  • 전체 데이터 = 훈련 (6) : 검증 (2) : 테스트 (2)
    • 테스트 데이터는 모르는 데이터로 생각!
1
2
3
4
5
import pandas as pd
wine = pd.read_csv("https://bit.ly/wine_csv_data")

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
1
2
3
4
5
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
)
1
2
3
sub_input, val_input, sub_target, val_target = train_test_split(
train_input, train_target, test_size = 0.2, random_state = 42
)
1
print(sub_input.shape, val_input.shape)
(4157, 3) (1040, 3)

모델 만든 후 평가

1
2
3
4
5
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state = 42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))
0.9971133028626413
0.864423076923077

교차 검증

  • : 훈련 세트에서 무작위로 검증 세트를 각각 다르게 떼어 내어 평가하는 과정을 여러 번 반복
  • 교차 검증의 목적 : 좋은 모델이 만들어진다!
    • 좋은 모델 != 성능 좋은 모델
    • 좋은 모델 = 과대적합이 아닌 모델 = 모형의 오차가 적은 모델 = 안정적인 모델
  • 교재 245p
    • 모델평가 1 : 90% (소요시간 : 1시간)
    • 모델평가 2 : 85%
    • 모델평가 3 : 80%
  • 단점 : 시간이 오래 걸림

교차 검증 함수

1
2
3
from sklearn.model_selection import cross_validate # cross_validate 교차 검증 함수
scores = cross_validate(dt, train_input, train_target)
print(scores)
{'fit_time': array([0.02901554, 0.01234174, 0.01105666, 0.01976061, 0.01070189]), 'score_time': array([0.00157857, 0.00140238, 0.00126791, 0.00145054, 0.00131822]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
  • 최종점수 평균 구하기
1
2
import numpy as np
print(np.mean(scores['test_score'])) # test_score = 위에서 검증한 폴드의 점수 **혼동주의**
0.855300214703487
  • 훈련 세트 섞은 후, 10-폴드 교차검증
1
2
3
4
5
from sklearn.model_selection import StratifiedKFold
splitter = StratifiedKFold(n_splits = 10, shuffle = True, random_state = 42) # n_splits 몇 폴드 교차 검증을 할지
scores = cross_validate(dt, train_input, train_target, cv = splitter) # cv = splitter 최적의 분할과 최적의 랜덤 분할을 선택하는 랜덤분할

print(np.mean(scores['test_score']))
0.8574181117533719

하이퍼파라미터 튜닝

  • 하이퍼파라미터란 : 모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터
  • 사이킷런과 같은 머신러닝 라이브러리를 사용할 때 이런 하이퍼파라미터는 모두 class나 method의 매개변수로 표현
  • 랜덤 서치 사용
  • 자동으로 잡아주는 라이브러리들이 등장하기 시작함
    • hyperopt
1
- GridSearchCV class는 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행
  File "<ipython-input-10-258156bef445>", line 1
    - GridSearchCV class는 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행
                        ^
SyntaxError: invalid syntax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
%%time

from sklearn.model_selection import GridSearchCV
params = {
'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005],
'max_depth' : [3, 4, 5, 6, 7]
}
# dt = DecisionTreeClassifier(random_state = 42)
gs = GridSearchCV(DecisionTreeClassifier(random_state = 42), params, n_jobs = -1)
gs.fit(train_input, train_target)
dt = gs.best_estimator_ # best_estimator는 훈련이 끝나면 25개의 모델중에서 검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련
print(dt)
print(dt.score(train_input, train_target))
print(gs.best_params_) # best_params 그리드 서치로 찾은 최적의 매개변수
DecisionTreeClassifier(max_depth=7, min_impurity_decrease=0.0005,
                       random_state=42)
0.8830094285164518
{'max_depth': 7, 'min_impurity_decrease': 0.0005}
CPU times: user 308 ms, sys: 63.9 ms, total: 372 ms
Wall time: 4.09 s
1
print(gs.cv_results_['mean_test_score']) # 각 매개변수에서 수행한 교차 검증의 평균 점수
[0.84125583 0.84125583 0.84125583 0.84125583 0.84125583 0.85337806
 0.85337806 0.85337806 0.85337806 0.85318557 0.85780355 0.85799604
 0.85857352 0.85857352 0.85838102 0.85645721 0.85799678 0.85876675
 0.85972866 0.86088306 0.85607093 0.85761031 0.85799511 0.85991893
 0.86280466]

랜덤 서치

  • p.252. 매개변수 값의 목록을 전달하는 것이 아니라 매개변수를 샘플링 할 수 있도록 확률 분포 객체를 전달.
1
2
3
from scipy.stats import uniform, randint
rgen = randint(0, 10)
rgen.rvs(10) # rvs 무작위로 표본을 만듬
array([8, 5, 3, 1, 5, 9, 3, 1, 7, 8])
1
np.unique(rgen.rvs(1000), return_counts = True) # return_counts = True 는 중복되지 않는 요소들이 입력 배열에 나타난 회 수를 리턴
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
 array([ 95, 103, 106, 100,  91, 102, 104,  92,  97, 110]))
1
2
3
4
5
6
7
8
9
10
11
from sklearn.model_selection import RandomizedSearchCV
# p.254
params = {
'min_impurity_decrease' : uniform(0.0001, 0.001),
'max_depth' : randint(20,50)
}


gs = RandomizedSearchCV(DecisionTreeClassifier(random_state = 42), params,
n_iter = 100, n_jobs = -1, random_state = 42)
gs.fit(train_input, train_target)
RandomizedSearchCV(estimator=DecisionTreeClassifier(random_state=42),
                   n_iter=100, n_jobs=-1,
                   param_distributions={'max_depth': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fb54e9ea550>,
                                        'min_impurity_decrease': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fb54e104390>},
                   random_state=42)
1
gs.best_params_ # 최적의 매개변수 조합 출력
{'max_depth': 29, 'min_impurity_decrease': 0.000437615171403628}

Comment and share

확률적 경사 하강법

  • 1차 가장 큰 차이(기존 ML모형)

    • 샘플링 방식이 달라짐
    • 샘플링을 더 세분화함
  • 2차 가장 큰 차이

    • 오차를 보정(기울기)
  • 오차 = 손실 = coast

    • 미분을 하여 오차가 가장 적을때까지 내려감.
  • 경사 하강법이 쓰인 여러 알고리즘

    • (이미지, 텍스트) 딥러닝 기초 알고리즘
    • 트리 알고리즘 + 경사 하강법 융합 = 부스트 계열
    • 대표 알고리즘 : LightGBM, Xgboost, Catboost

SGDClassifier

  • 확률적 경사하강법 분류기
1
2
3
import pandas as pd 
fish = pd.read_csv("https://bit.ly/fish_csv_data")
fish.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 159 entries, 0 to 158
Data columns (total 6 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Species   159 non-null    object 
 1   Weight    159 non-null    float64
 2   Length    159 non-null    float64
 3   Diagonal  159 non-null    float64
 4   Height    159 non-null    float64
 5   Width     159 non-null    float64
dtypes: float64(5), object(1)
memory usage: 7.6+ KB
  • 배열로 변환하는 코드
    • 독립변수 = fish_input
    • 종속변수 = fish_target
1
2
fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']]
fish_target = fish['Species'].to_numpy()
  • 훈련 세트와 테스트 세트로 분리
1
2
3
4
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(fish_input, fish_target, random_state = 42)

train_input.shape, test_input.shape, train_target.shape, test_target.shape
((119, 5), (40, 5), (119,), (40,))
  • 표준화 처리
    • 다시 한번 강조하지만 꼭 훈련 세트에서 학습한 통계값으로 테스트 세트도 변환한다.
    • 키워드 : Data Leakage 방지
    • 데이터 분석 희망자 필수 공부!
1
2
3
4
5
6
7
8
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)

# ss 훈련 데이터만 활용해서 학습(?)이 끝난 상태
# 표준화 처리를 훈련 데이터와 테스트 데이터에 동시 적용
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

모델 학습

  • 2개의 매개 변수 지정
  • loss = “log” = 로지스틱 손실 함수로 지정
  • max_iter = 에포크 횟수 지정
    • 에포크란(epoch)_01 : 훈련 데이터셋에 포함된 모든 데이터들이 한 번씩 모델을 통과한 횟수로, 모든 학습 데이터셋을 학습하는 횟수
    • 에포크란_02 : 1 epoch는 전체 학습 데이터셋이 한 신경망에 적용되어 순전파와 역전파를 통해 신경망을 한 번 통과했다는 의미가 된다, 즉 epoch가 10회라면, 학습 데이터 셋 A를 10회 모델에 학습시켰다는 것
    • 에포크란_03 : epoch를 높일수록, 다양한 무작위 가중치로 학습을 해보므로, 적합한 파라미터를 찾을 확률이 올라간다.(즉, 손실 값이 내려가게 된다.) 하지만 지나치게 epoch를 높이게 되면, 그 학습 데이터셋에 과적합되어 다른데이터에 대해선 제대로 된 예측을 하지 못할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.linear_model import SGDClassifier

# 매개변수 지정
# 하이퍼파라미터 설정
## 매개변수 값을 dictionary 형태로 추가하는 코드 작성 가능
## 강사는 입문자들에게는 비추천
sc = SGDClassifier(loss = "log", max_iter = 40, random_state = 42)

# 모형 학습
sc.fit(train_scaled, train_target)

# 스코어 확인 (정확도)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target)) # 샘플링의 차이로 값이 일정하지 않고 다를 수 있다.
0.8571428571428571
0.8
  • 적절한 에포크 숫자를 찾자.
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
sc = SGDClassifier(loss = "log", max_iter = 100, tol = None, random_state = 42)
train_score = []
test_score = []
classes = np.unique(train_target)
for _ in range(0, 300):
sc.partial_fit(train_scaled, train_target, classes = classes)
train_score.append(sc.score(train_scaled, train_target))
test_score.append(sc.score(test_scaled, test_target))

# 정확도
print(train_score[:5])
print(test_score[:5])
[0.5294117647058824, 0.6218487394957983, 0.6386554621848739, 0.7310924369747899, 0.7226890756302521]
[0.65, 0.55, 0.575, 0.7, 0.7]
  • 모형 시각화
1
2
3
4
5
6
7
8
9
10
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot(train_score)
ax.plot(test_score)
ax.set_xlabel("Epoch")
ax.set_ylabel("Accuracy")
plt.show()
# 파란색 훈련, 노란색 테스트
# 훈련데이터가 안정화되고 테스트데이터도 안정화되면서 가까운 곳은 epoch가 100일때의 지점이라고 알 수 있음.

png

Comment and share

데이터 불러오기

  • 컬럼 설명 177p 그림
1
2
3
4
import pandas as pd

fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()

Species Weight Length Diagonal Height Width
0 Bream 242.0 25.4 30.0 11.5200 4.0200
1 Bream 290.0 26.3 31.2 12.4800 4.3056
2 Bream 340.0 26.5 31.1 12.3778 4.6961
3 Bream 363.0 29.0 33.5 12.7300 4.4555
4 Bream 430.0 29.0 34.0 12.4440 5.1340

  <script>
    const buttonEl =
      document.querySelector('#df-65932b7d-508a-47e0-8f70-ce7c6944b42c button.colab-df-convert');
    buttonEl.style.display =
      google.colab.kernel.accessAllowed ? 'block' : 'none';

    async function convertToInteractive(key) {
      const element = document.querySelector('#df-65932b7d-508a-47e0-8f70-ce7c6944b42c');
      const dataTable =
        await google.colab.kernel.invokeFunction('convertToInteractive',
                                                 [key], {});
      if (!dataTable) return;

      const docLinkHtml = 'Like what you see? Visit the ' +
        '<a target="_blank" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'
        + ' to learn more about interactive tables.';
      element.innerHTML = '';
      dataTable['output_type'] = 'display_data';
      await google.colab.output.renderOutput(dataTable, element);
      const docLink = document.createElement('div');
      docLink.innerHTML = docLinkHtml;
      element.appendChild(docLink);
    }
  </script>
</div>

데이터 변환

  • 배열로 변환
  • 독립변수
1
2
3
print(pd.unique(fish['Species'])) # unique()함수는 괄호안에 있는 열의 고유한 값을 추출함.
fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()
fish_input.shape
['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']





(159, 5)
  • target 배열로 변환
  • 종속변수
  • to_numpy() method는 pandas 객체를 numpy 배열 객체인 ndarray로 반환 링크 텍스트
1
fish_target = fish['Species'].to_numpy()

훈련 데이터와 테스트데이터

1
2
3
4
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
fish_input, fish_target, random_state = 42
)
  • 표준화 전처리

    • 데이터의 결측치 및 이상치를 확인하거나 제거하고 불일치되는 부분을 일관성 있는 데이터의 형태로 전환 하기도 하는 이 전 과정을 데이터의 전처리라고 일컫는다.
  • 대표적인 사이킷런 스케일링의 종류

    • StandardScaler : 기본 스케일. 평균과 표준편차 사용
    • MinMaxScaler : 최대/최소값이 각각 1, 0이 되도록 스케일링
    • MaxAbsScaler : 최대절대값과 0이 각각 1, 0이 되도록 스케일링
    • RobustScaler : 중앙값(median)과 IQR(interquartile range) 사용. 아웃라이어의 영향을 최소화
1
2
3
4
5
6
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)

train_scaled = ss.transform(train_input) # 표준화
test_scaled = ss.transform(test_input)
1
2
3
print(train_input[:5])
print(train_scaled[:5])
print(test_scaled[:5])
[[720.      35.      40.6     16.3618   6.09  ]
 [500.      45.      48.       6.96     4.896 ]
 [  7.5     10.5     11.6      1.972    1.16  ]
 [110.      22.      23.5      5.5225   3.995 ]
 [140.      20.7     23.2      8.5376   3.2944]]
[[ 0.91965782  0.60943175  0.81041221  1.85194896  1.00075672]
 [ 0.30041219  1.54653445  1.45316551 -0.46981663  0.27291745]
 [-1.0858536  -1.68646987 -1.70848587 -1.70159849 -2.0044758 ]
 [-0.79734143 -0.60880176 -0.67486907 -0.82480589 -0.27631471]
 [-0.71289885 -0.73062511 -0.70092664 -0.0802298  -0.7033869 ]]
[[-0.88741352 -0.91804565 -1.03098914 -0.90464451 -0.80762518]
 [-1.06924656 -1.50842035 -1.54345461 -1.58849582 -1.93803151]
 [-0.54401367  0.35641402  0.30663259 -0.8135697  -0.65388895]
 [-0.34698097 -0.23396068 -0.22320459 -0.11905019 -0.12233464]
 [-0.68475132 -0.51509149 -0.58801052 -0.8998784  -0.50124996]]

k-최근접 이웃 분류기의 확률 예측

1
2
3
4
5
6
7
from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)

print(kn.score(train_scaled, train_target))
print(kn.score(test_scaled, test_target))
0.8907563025210085
0.85
  • 182p

  • 다중분류

    • 타깃 데이터에 2개 이상의 클래스가 포함된 문제
1
2
3
4
import numpy as np
proba = kn.predict_proba(test_scaled[:5]) # predict_proba() method는 클래스별 확률값을 반환
print(np.round(proba, decimals = 4))
print(kn.classes_) # 타깃값을 그대로 사이킷런 모델에 전달하면 순서가 자동으로 알파벳순으로 매겨짐, 따라서 이 전 KNeighborsClassifier에서 정렬된 타깃값이 저장된 classes_ 를사용
[[0.     0.     1.     0.     0.     0.     0.    ]
 [0.     0.     0.     0.     0.     1.     0.    ]
 [0.     0.     0.     1.     0.     0.     0.    ]
 [0.     0.     0.6667 0.     0.3333 0.     0.    ]
 [0.     0.     0.6667 0.     0.3333 0.     0.    ]]
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

로지스틱 회귀

  • 중요도 : 최상

  • Why?

    • 로지스틱 회귀
      • 기초 통계로도 활용 (의학통계)
      • 머신러닝 분류모형의 기초 모형인데, 성능이 생각보다 나쁘지 않음
        • 데이터셋, 수치 데이터 기반
      • 딥러닝 : 초기모형에 해당됨.
  • 로지스틱회귀(Logistic regression)와 선현 회귀(linear regression)의 차이점

    • 선형회귀의 결과값은 연속적인값 , 오차값을 줄이기 위해 MSE(Mean Square Error)을 사용
    • 로지스틱회귀의 결과값은 범주값, 그래프로 표현할시 0% ~ 100%로 사용하기 편함, 오차값을 줄이기 위해 Log Loss(cross entropy)를 사용
      Youtube Link
  • 이진 분류를 수행 할 때 시그모이드 함수의 출력이 0.5 보다 크면 양성 클래스, 0.5보다 작으면 음성 클래스로 판단(정확히 0.5일때 사이킷런은 음성 클래스로 판단.***라이브러리마다 다를 수 있음)

1
2
3
4
5
6
7
8
9
10
11
import numpy as np 
import matplotlib.pyplot as plt
z = np.arange(-5, 5, 0.1)
phi = 1 / (1 + np.exp(-z)) # 시그모이드 함수(로지스틱함수)
# print(z)
# print(phi)

plt.plot(z, phi, color='green') # 문서를 봐야함
plt.xlabel('z')
plt.ylabel('phi')
plt.show()

png

로지스틱 회귀로 이진 분류 수행하기

  • 넘파이 배열은 True, False 값을 전달하여 행을 선택 할수 있음
1
2
char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]]) # True 인 원소만 출력
['A' 'C']
1
2
3
4
# 도미와 빙어의 행만 골라내기
bream_smelt_indexes = (train_target == 'Bream') | (train_target == 'Smelt')
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]
  • p186.
  • 모형 만들고 예측하기!
1
2
3
4
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression() # 로지스틱 회귀
# 독립변수 종속변수
lr.fit(train_bream_smelt, target_bream_smelt)
LogisticRegression()
1
2
3
4
# 예측하기 
# 클래스로 분류
# 확률값 -> 0.5
print(lr.predict(train_bream_smelt[:5]))
['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
1
2
print(lr.predict_proba(train_bream_smelt[:5]))
print(lr.classes_)
[[0.99759855 0.00240145]
 [0.02735183 0.97264817]
 [0.99486072 0.00513928]
 [0.98584202 0.01415798]
 [0.99767269 0.00232731]]
['Bream' 'Smelt']
  • 방정식의 각 기울기와 상수를 구하는 코드
1
print(lr.coef_, lr.intercept_) # LinearRegression 클래스가 구한 모델 파라미터는 가중치와 절편이 coef_와 intercept_ 인스턴스 변수에 따로 저장되어 있음
[[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]
  • z식
  • z값을 출력하자!
    • decisions_function() method 사용으로 z 값 구하기
1
2
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
[-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]
  • scipy(사이파이) 라이브러리 안의 expit(시그모이드함수)() 사용
1
2
from scipy.special import expit
print(expit(decisions))
[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]

로지스틱 회귀로 다중 분류 수행하기

  • LogisticRegression 클래스는 기본적으로 반복적인 알고리즘을 사용합니다. max_iter 매개변수에서 반복 횟수를 지정하며 기본값은 100입니다. 여기에 준비한 데이터셋을 사용해 모델을 훈련하면 반복 횟수가 부족하다는 경고가 발생합니다. 충분하게 훈련시키기 위해 반복 횟수를 1,000으로 늘리겠습니다.
  • 기본적으로 릿지 회귀와 같이 계수의 제곱을 규제합니다. 이런 규제를 L2 규제라고도 부릅니다. 릿지 회귀에서는 alpha 매개변수로 규제의 양을 조절했습니다. alpha가 커지면 규제도 커집니다. LogisticRegression에서 규제를 제어하는 매개변수는 C입니다. 하지만 C는 alpha와 반대로 작을수록 규제가 커집니다. C의 기본값은 1입니다. 여기에서는 규제를 완화하기 위해 20으로 늘리겠습니다.
1
2
3
4
lr = LogisticRegression(C = 20, max_iter = 1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
0.9327731092436975
0.925
  • 과대적합 및 과소적합으로 치우치지 않았기 때문에 처음 5개의 샘플에 대한 예측을 출력.
1
print(lr.predict(test_scaled[:5]))
['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
1
2
proba = lr.predict_proba(test_scaled[:5]) # 예측 확률 출력
print(np.round(proba, decimals = 3))
[[0.    0.014 0.841 0.    0.136 0.007 0.003]
 [0.    0.003 0.044 0.    0.007 0.946 0.   ]
 [0.    0.    0.034 0.935 0.015 0.016 0.   ]
 [0.011 0.034 0.306 0.007 0.567 0.    0.076]
 [0.    0.    0.904 0.002 0.089 0.002 0.001]]
1
print(lr.classes_)
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
1
print(lr.coef_.shape, lr.intercept_.shape)
(7, 5) (7,)
  • 이진 분류에서는 시그모이드 함수를 사용해 z를 0과 1사이의 값으로 변환했습니다. 다중 분류는 이와 달리 소프트맥스(softmax) 함수를 사용하여 7개의 z값을 확률로 변환합니다.
    • 소프트맥스 함수란 : 여러 개의 선형 방정식의 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만듭니다. 이를 위해 지수 함수를 사용하기 때문에 정규화된 지수 함수라고도 부릅니다.
    • 소프트맥스 티스토리 링크
1
2
3
# decision_function() method로 z1~z7까지의 값을 구함
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals = 2))
[[ -6.5    1.03   5.16  -2.73   3.34   0.33  -0.63]
 [-10.86   1.93   4.77  -2.4    2.98   7.84  -4.26]
 [ -4.34  -6.23   3.17   6.49   2.36   2.42  -3.87]
 [ -0.68   0.45   2.65  -1.19   3.26  -5.75   1.26]
 [ -6.4   -1.99   5.82  -0.11   3.5   -0.11  -0.71]]
1
2
3
4
# 소프트 맥스 함수를 사용해 확률로 바꿈
from scipy.special import softmax
proba = softmax(decision, axis = 1)
print(np.round(proba, decimals = 3))
[[0.    0.014 0.841 0.    0.136 0.007 0.003]
 [0.    0.003 0.044 0.    0.007 0.946 0.   ]
 [0.    0.    0.034 0.935 0.015 0.016 0.   ]
 [0.011 0.034 0.306 0.007 0.567 0.    0.076]
 [0.    0.    0.904 0.002 0.089 0.002 0.001]]

Comment and share

데이터 준비

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np

perch_length = np.array(
[8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0,
21.0, 21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5,
22.5, 22.7, 23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5,
27.3, 27.5, 27.5, 27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0,
36.5, 36.0, 37.0, 37.0, 39.0, 39.0, 39.0, 40.0, 40.0, 40.0,
40.0, 42.0, 43.0, 43.0, 43.5, 44.0]
)
perch_weight = np.array(
[5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0,
110.0, 115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0,
130.0, 150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0,
197.0, 218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0,
514.0, 556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0,
820.0, 850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0,
1000.0, 1000.0]
)

K - 최근접 이웃 회귀(Regression)

  • 중요도 : 下 (이런 알고리즘이 있다 정도)
1
2
3
4
5
6
7
8
import matplotlib.pyplot as plt

# 객체 지향으로 변경
fig, ax = plt.subplots()
plt.scatter(perch_length, perch_weight)
ax.set_xlabel("length")
ax.set_ylabel("weight")
plt.show()

png

훈련데이터 테스트데이터셋 분리

1
2
3
4
5
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
perch_length, perch_weight, random_state = 42
)
train_input.shape, test_input.shape, train_target.shape, test_target.shape
((42,), (14,), (42,), (14,))
  • reshape() 사용하여 2차원 배열로 바꿈
1
2
3
4
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)

print(train_input.shape, test_input.shape)
(42, 1) (14, 1)

결정계수

  • 모델이 얼마만큼 정확한지?
  • 절대값은 아님 / 상대적인 값
1
2
3
4
5
6
7
8
9
10
from sklearn.neighbors import KNeighborsRegressor

# knr 클래스 부러오기
knr = KNeighborsRegressor()

# 모형 학습
knr.fit(train_input, train_target)

# 테스트 점수 확인
knr.score(test_input, test_target)
0.992809406101064

MAE

  • 타깃과 예측의 절댓값 오치를 평균하여 반환
1
2
3
4
5
6
# sklearn.metrics는 패키지 아래 여러 가지 측정 도구를 제공
from sklearn.metrics import mean_absolute_error

# 예측 데이터 만들기
test_prediction = knr.predict(test_input)
test_prediction
array([  60. ,   79.6,  248. ,  122. ,  136. ,  847. ,  311.4,  183.4,
        847. ,  113. , 1010. ,   60. ,  248. ,  248. ])
  • mae를 구한다
1
2
3
# mean_absolute_error는 타깃과 예측의 절댓값 오차를 평균하여 반환
mae = mean_absolute_error(test_target, test_prediction)
print(mae)
19.157142857142862
  • 평균적으로 19g정도 다르다.

과대적합 vs 과소적합

  • 공통점은 머신러닝 모형이 실제 테스트 시 잘 예측을 못함
  • 과대 적합: 훈련데이터에는 예측 잘함 / 테스트데이터에서는 예측을 잘 못함
    • 처리하기 곤란
  • 과소 적합: 훈련데이터에는 예측 못함 / 테스트데이터에서는 예측을 잘 함 or 둘 다 예측을 잘 못함

  • 0.97 정도나옴
1
2
# 훈련 데이터 점수 확인
knr.score(train_input, train_target)
0.9698823289099254
  • 훈련데이터로 검증 0.98
1
2
3
4
5
6
7
# Default 5를 3으로 변경
# 머신러닝 모형을 살짝 변경
knr.n_neighbors = 3

# 모델을 다시 훈련
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
0.9804899950518966
1
print(knr.score(test_input, test_target))
0.9746459963987609
  • MAE 구하기

  • 평균적으로 35.4g 다름

1
2
3
4
# 예측 데이터 만들기
test_prediction = knr.predict(test_input)
mae = mean_absolute_error(test_target, test_prediction)
print(mae)
35.42380952380951

결론

  • k 그룹을 5로 했을 때, R2 점수는 0.98, MAE는 19 였음
  • k 그룹을 3으로 했을 때, R2 점수는 0.97, MAE는 35 였음
  • k 그룹을 7로 했을 때, R2 점수는 0.97, MAE는 32였음

Comment and share

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np

perch_length = np.array(
[8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0,
21.0, 21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5,
22.5, 22.7, 23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5,
27.3, 27.5, 27.5, 27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0,
36.5, 36.0, 37.0, 37.0, 39.0, 39.0, 39.0, 40.0, 40.0, 40.0,
40.0, 42.0, 43.0, 43.0, 43.5, 44.0]
)
perch_weight = np.array(
[5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0,
110.0, 115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0,
130.0, 150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0,
197.0, 218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0,
514.0, 556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0,
820.0, 850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0,
1000.0, 1000.0]
)

훈련 세트와 테스트 세트 분리

1
2
3
4
5
6
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
perch_length, perch_weight, random_state = 42
)

train_input.shape, test_input.shape, train_target.shape, test_target.shape
((42,), (14,), (42,), (14,))
  • reshape(-1)은 무슨 의미인가?

    • x.reshape(-1)은 x.reshape(1, -1)과 같이 1차원 배열을 반환합니다.
    • x.reshape(-1, 1) => shape(12, 1)
    • x.reshape(-1, 2) => shape(6, 2)
    • x.reshape(-1, 3) => shape(4, 3)

    ++++ 출처: https://rfriend.tistory.com/345 [R, Python 분석과 프로그래밍의 친구 (by R Friend)]

1
2
3
4
train_input = train_input.reshape(-1, 1) # 행렬 재배치
test_input = test_input.reshape(-1, 1)

print(train_input.shape, test_input.shape)
(42, 1) (14, 1)

모델 만들기

1
2
3
4
5
6
7
from sklearn.neighbors import KNeighborsRegressor

# knn 클래스 부러오기
knr = KNeighborsRegressor(n_neighbors=3)

# 모형 학습
knr.fit(train_input, train_target)
KNeighborsRegressor(n_neighbors=3)

예측

  • p132
1
print(knr.predict([[50]]))
[1033.33333333]

시각화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import matplotlib.pyplot as plt

# 50cm 농어의 이웃을 구하라
distances, indexes = knr.kneighbors([[50]])

# 훈련 세트의 산점도를 구하라

fig, ax = plt.subplots(figsize=(8, 5), facecolor="#c1f1f1")
plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
plt.scatter(50, 1033, marker='^')
ax.set_xlabel('length')
ax.set_ylabel('weight')
plt.show()

png

  • 머신러닝 모델은 주기적으로 훈련해야 합니다.
    • MLOps (Machine Learning & Operations)
    • 최근에 각광받는 데이터 관련 직업 필수 스킬!
    • 입사와 함꼐 공부시작 (데이터 분석가, 머신러닝 엔지니어, 데이터 싸이언티스트 희망자)

선형 회귀 (머신러닝)

  • 평가지표 확인이 더 중요! R2 점수, MAE, MSE
  • 5가지 가정들…
  • 잔차의 정규성
  • 등분산성, 다중공선성, etc..
  • 종속변수 ~ 독립변수간의 “인과관계”를 찾는 과정..
1
2
3
4
5
6
7
8
9
10
from matplotlib.ticker import LinearLocator
from sklearn.linear_model import LinearRegression

lr = LinearRegression()

# 선형 회귀 모델 훈련
lr.fit(train_input, train_target)

# 50cm 농어 예측
print(lr.predict([[200]]))
[7094.41034777]
1
2
3
4
5
6
7
fig, ax = plt.subplots(figsize=(8, 5), facecolor="#c1f1f1")
plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
plt.scatter(50, 1033, marker='^') # 임의의 데이터 생성
ax.set_xlabel('length')
ax.set_ylabel('weight')
plt.show()

png

회귀식을 찾기

  • 하나의 직선을 그리려면 기울기와 절편이 있어야합니다.
  • 농어의 무게 = 기울기 x 농어 길이 + 절편
  • y = a * x + b
1
2
# 기울기, 상수
print(lr.coef_, lr.intercept_)
[39.01714496] -709.0186449535477
  • 기울기 : 계수 = 가중치(딥러닝, 기울기)
1
2
3
4
5
6
7
8
9
10
11
fig, ax = plt.subplots(figsize=(8, 5), facecolor="#c1f1f1")
plt.scatter(train_input, train_target)

# 15~50까지의 1차 방정식 그래프를 그린다.
plt.plot([15,50],
[15 * lr.coef_ + lr.intercept_,
50 * lr.coef_ + lr.intercept_])
ax.set_xlabel('length')
ax.set_ylabel('weight')
plt.scatter(50, 1241.8, marker='^')
plt.show()

png

  • 모형 평가 (p.138)
    • 과소적합이 됨

다항회귀의 필요성

  • 치어를 생각해보자
  • 치어가 1cm
1
print(lr.predict([[1]]))
[-670.00149999]
  • (p. 140) 1차방정식을 2차방정식으로 만드는 과정이 나옴
  • 넘파이 브로드캐스팅 링크 텍스트
    • 배열의 크기가 동일하면 상관 없음
    • 배열의 크기가 다른데, 연산을 할때, 브로드캐스팅 원리가 적용
    • 브로드캐스팅 튜토리얼 등을 찾아서 추가적으로 공부를 해야함
1
2
3
4
train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))

print(train_poly.shape, test_poly.shape)
(42, 2) (14, 2)
1
2
3
4
5
lr = LinearRegression()

lr.fit(train_poly, train_target)

print(lr.predict([[50 ** 2, 50]]))
[1573.98423528]
1
print(lr.coef_, lr.intercept_) # y = ax2 + bx + c 이차방적식, x = "length"
[  1.01433211 -21.55792498] 116.0502107827827
  • KNN의 문제점
    • 농어의 길이가 커져도 무게는 동일함 (현실성 제로)
  • 단순 선형회귀(1차 방정식)의 문제점
    • 치어(1cm)의 무게가 음수로 나옴 (현실성 제로)
  • 다항 회귀(2차 방정식)로 변경
    • 현실성 있음

Comment and share

생선 분류 문제

  • 이 문제는 박혜선 저자의 혼자 공부하는 머신러닝 + 딥러닝 교제를 보며 코드를 실습한 것입니다. 링크 텍스트

  • 도미의 특성(feature) ..데이터의 특징

  • 산점도 그리기(matplotlib)

1
2
3
4
5
6
7
8
9
10
11
12
13
bream_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0, 
31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0,
35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0]
bream_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0,
500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0,
700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0]

import matplotlib.pyplot as plt

plt.scatter(bream_length, bream_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

png

1
2
3
4
5
6
7
8
smelt_length = [9.8, 10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
smelt_weight = [6.7, 7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]

plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

png

첫번째 머신러닝 프로그램

  • 두개의 데이터 합치기
1
2
length = bream_length + smelt_length
weight = bream_weight + smelt_weight
1
2
3
fish_data = [[l, w] for l, w in zip(length, weight)] # zip() 함수는 나열된 리스트 각각에서 하나씩 원소를 꺼내 반환.
print(fish_data)
print(type(fish_data))
[[25.4, 242.0], [26.3, 290.0], [26.5, 340.0], [29.0, 363.0], [29.0, 430.0], [29.7, 450.0], [29.7, 500.0], [30.0, 390.0], [30.0, 450.0], [30.7, 500.0], [31.0, 475.0], [31.0, 500.0], [31.5, 500.0], [32.0, 340.0], [32.0, 600.0], [32.0, 600.0], [33.0, 700.0], [33.0, 700.0], [33.5, 610.0], [33.5, 650.0], [34.0, 575.0], [34.0, 685.0], [34.5, 620.0], [35.0, 680.0], [35.0, 700.0], [35.0, 725.0], [35.0, 720.0], [36.0, 714.0], [36.0, 850.0], [37.0, 1000.0], [38.5, 920.0], [38.5, 955.0], [39.5, 925.0], [41.0, 975.0], [41.0, 950.0], [9.8, 6.7], [10.5, 7.5], [10.6, 7.0], [11.0, 9.7], [11.2, 9.8], [11.3, 8.7], [11.8, 10.0], [11.8, 9.9], [12.0, 9.8], [12.2, 12.2], [12.4, 13.4], [13.0, 12.2], [14.3, 19.7], [15.0, 19.9]]
<class 'list'>
1
2
fish_target =  [1] * 35 + [0] * 14 # 도미와 빙어를 구분
print(fish_target)
[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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

KNeighborsClassifier()

  • 가장 가까운 이웃을 참고하여 정답을 예측하는 사이킷런(scikit-learn)
1
from sklearn.neighbors import KNeighborsClassifier
1
kn = KNeighborsClassifier()

훈련 (training)

  • 도미를 찾기 위한 기준을 학습

  • 사이킷런 fit()

    • scikit_learn Training method
1
kn.fit(fish_data, fish_target) # 학습
KNeighborsClassifier()
  • 사이킷런 score()
    • scikit_learn Evaluating method
    • 0 과 1사이의 값을 반환
    • 1은 모든 데이터의 값을 맞춘것이고 0.5는 절반만 맞춤
1
kn.score(fish_data, fish_target) # 정확도 평가 (0 ~ 1.0)
1.0

K - 최근접 알고리즘

  • 어떤 데이터에 대한 답을 구할 때 주위의 다른 데이터를 보고 다수를 차지하는것을 정답으로 사용함.
  • 데이터가 아주 많은 경우 사용하기 어려움.
    • 데이터가 크면 메모리가 많이 필요하고 직선거리를 계산하는 데도 많은 시간이 필요하기 때문
1
2
3
# predict method는 새로운 데이터의 정답을 예측
# fit() method와 마찬가지로 리스트의 리스트를 전달해야 하기때문에 삼각형 포인트를 리스트로 2번 감쌈
kn.predict([[30, 600]])
array([1])
1
print(kn._fit_X) # _fit_X 속성에는 fish_data를 모두 가지고 있음 
[[  25.4  242. ]
 [  26.3  290. ]
 [  26.5  340. ]
 [  29.   363. ]
 [  29.   430. ]
 [  29.7  450. ]
 [  29.7  500. ]
 [  30.   390. ]
 [  30.   450. ]
 [  30.7  500. ]
 [  31.   475. ]
 [  31.   500. ]
 [  31.5  500. ]
 [  32.   340. ]
 [  32.   600. ]
 [  32.   600. ]
 [  33.   700. ]
 [  33.   700. ]
 [  33.5  610. ]
 [  33.5  650. ]
 [  34.   575. ]
 [  34.   685. ]
 [  34.5  620. ]
 [  35.   680. ]
 [  35.   700. ]
 [  35.   725. ]
 [  35.   720. ]
 [  36.   714. ]
 [  36.   850. ]
 [  37.  1000. ]
 [  38.5  920. ]
 [  38.5  955. ]
 [  39.5  925. ]
 [  41.   975. ]
 [  41.   950. ]
 [   9.8    6.7]
 [  10.5    7.5]
 [  10.6    7. ]
 [  11.     9.7]
 [  11.2    9.8]
 [  11.3    8.7]
 [  11.8   10. ]
 [  11.8    9.9]
 [  12.     9.8]
 [  12.2   12.2]
 [  12.4   13.4]
 [  13.    12.2]
 [  14.3   19.7]
 [  15.    19.9]]
1
print(kn._y) # _y속성에 fish_target을 가지고 있음
[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 0 0
 0 0 0 0 0 0 0 0 0 0 0 0]
  • 매개변수를 49로 했을때 정확도보다 기본값으로 했을때의 정확도가 높기때문에 kn49가 아닌 kn 모델을 채택
1
2
# 기본적으로는 5개의 데이터를 기본값으로 참고, n_neighbors로 매개변수를 바꿀수 있음
kn49 = KNeighborsClassifier(n_neighbors=49) # 참고 데이터를 49개로 한 kn49 모델
1
2
kn49.fit(fish_data, fish_target)
kn49.score(fish_data, fish_target) # 49개 데이터중 도미가 35개로 다수를 차지하므로 어떤 데이터를 넣어도 무조건 도미로 예측
0.7142857142857143
1
print(35 / 49) # 도미 35개, 빙어 14개 중에서 도미가 차지하는 비율
0.7142857142857143
  • 확인 문제
  1. 데이터를 표현하는 하나의 성질로써, 예를 들어 국가 데이터의 경우 인구수, GDP, 면적 등이 하나의 국가를 나타냅니다. 머신러닝에서 이런 성질을 무엇이라고 부르나요? : 특성

  2. 가장 가까운 이웃을 참고하여 정답을 예측하는 알고리즘이 구현된 사이킷런 클래스는 무엇인가요? : KNeighborsClassfier

  3. 사이킷런 모델을 훈련할떄 사용하는 메서드는 어떤 것인가요? : fit()

Comment and share

Author's picture

Winters

개발자를 꿈꾸는 어른이


개발자(예비)


대한민국/서울