Home [SeSAC]혼공 머신러닝+딥러닝 Ch3, 4
Post
Cancel

[SeSAC]혼공 머신러닝+딥러닝 Ch3, 4

복습

인공지능의 세가지

  • 지도학습
    • 데이터와 라벨을 줌
  • 비지도학습
    • 라벨을 주지 않음
  • 강화학습
    • 데이터가 없음

지도학습

  • 출력값에 따른 분류
    1. 회귀
      • 연속적 수치
    2. 분류
      • 결과 종류별 확률 수치
      • ex) 개일 확률: 80%, 고양이일 확률: 20%

회귀, 분류 모두 prediction, 즉 예측이다

  • 수학적 분류
    1. 유사성 기반 학습 (Similarity-based Learning):
      • 이 방식은 새로운 데이터 포인트와 가장 유사한 학습 데이터 포인트를 찾아서 예측을 하는 방법입니다.
      • 예: K-Nearest Neighbors (KNN) 알고리즘. KNN은 입력 데이터와 가장 가까운 k개의 학습 데이터 포인트를 찾아서 이들의 레이블을 기반으로 예측을 합니다.
    2. 정보 기반 학습 (Information-based Learning):
      • 이 방식은 데이터에서 가장 유용한 특성(정보)을 찾아내서 예측 모델을 구성합니다.
      • 예: Decision Trees. 결정 트리는 데이터를 분할하는 데 가장 정보가 많은 특성을 기반으로 규칙을 만들어 학습합니다.
    3. 확률 기반 학습 (Probability-based Learning):
      • 이 방식은 데이터의 확률 분포를 학습하여 예측을 하는 방법입니다.
      • 예: Naive Bayes 알고리즘. 이 알고리즘은 특성 간의 독립성을 가정하고 각 특성의 확률 분포를 학습하여 예측을 합니다.
    4. 에러 기반 학습 (Error-based Learning):
      • 이 방식은 예측의 에러를 최소화하도록 모델을 조정하는 방법입니다.
      • 예: Neural Networks, Linear Regression. 이러한 알고리즘들은 에러를 최소화하는 파라미터를 찾기 위해 반복적으로 학습을 수행합니다.

Chapter 03 회귀 알고리즘과 모델 규제

03-1 K-최근접 이웃 회귀

데이터 준비

  • train_test_split
1
2
3
4
5
6
7
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)
    
print(train_input.shape, test_input.shape)
# (42,) (14,) # 0차원
  • [ 0, 1, 2, 3] : 0차원
  • [[0], [1], [2], [3], [4]]: 1차원
  • reshape
1
2
train_input = train_input.reshape(-1, 1) # -1: 모든 데이터에 대해 1차원으로 변경
test_input = test_input.reshape(-1, 1)

결정 계수($R^2$): 평가 척도

성능 평가
1
2
3
4
5
6
7
from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()

# k-최근접 이웃 회귀 모델을 훈련합니다
knr.fit(train_input, train_target)
knr.score(test_input, test_target)  # 0.992809406101064

knn은 유일하게 먼저 학습을 안하고, 질문이 주어졌을때 학습한다

비지도 학습은 레이블이 없기 때문에 성능 측정을 할 수 없다

회귀 모델이 사용하는 가장 보편적이고, 성능 평가 지표(metric): $R^2$

\[R^2 = 1-\tfrac{오차^2}{편차^2} = 1-\tfrac{\sum (예측값 - 실제값)^2}{\sum (실제값평균-실제값)^2}\]
평균 절댓값 오차
1
2
3
4
5
6
from sklearn.metrics import mean_absolute_error
# 테스트 세트에 대한 예측을 만듭니다
test_prediction = knr.predict(test_input)
# 테스트 세트에 대한 평균 절댓값 오차를 계산합니다
mae = mean_absolute_error(test_target, test_prediction)
print(mae)  # 19.157142857142862

과대적합(overfitting) vs 과소적합(underfitting)

  • 훈련 데이터 과대 적합 vs 테스트 데이터에 과대 적합
    • 과대적합: 훈련 세트 점수 > 테스트 세트 점수
    • 과소적합: 훈련 세트 점수 < 테스트 세트 점수 or 둘 다 너무 낮음
1
2
3
4
5
6
7
8
9
10
11
12
13
# 기존 테스트 스코어
knr.score(test_input, test_target)  # 0.992809406101064
# 기존 훈련 스코어
print(knr.score(train_input, train_target))  # 0.9698823289099254

# 이웃의 갯수를 3으로 설정합니다
knr.n_neighbors = 3
# 모델을 다시 훈련합니다
knr.fit(train_input, train_target)
# 훈련
print(knr.score(train_input, train_target))  # 0.9804899950518966
# 테스트
print(knr.score(test_input, test_target))  # 0.9746459963987609
  • k값을 3으로 낮췄더니 과대적합 (train score > test score)

현업에서는 대부분 과적합과의 싸움이다

과적합을 해결하는 2가지 방법:

  1. 데이터를 더 구한다
  2. 모델을 바보로 만든다
    • 훈련 데이터의 정답을 맞춰도 틀리게 처리함
  • 통계학에선 샘플에 집착해도 된다 (why? → 샘플 집단이 모집단과 같은 정규분포를 따름)
  • 머신러닝에선 그러면 안된다!!

확인 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# k-최근접 이웃 회귀 객체를 만듭니다
knr = KNeighborsRegressor()
# 5에서 45까지 x 좌표를 만듭니다
x = np.arange(5, 45).reshape(-1, 1)

# n = 1, 5, 10일 때 예측 결과를 그래프로 그립니다.
for n in [1, 5, 10]:
    # 모델 훈련
    knr.n_neighbors = n
    knr.fit(train_input, train_target)
    # 지정한 범위 x에 대한 예측 구하기
    prediction = knr.predict(x)
    # 훈련 세트와 예측 결과 그래프 그리기
    plt.scatter(train_input, train_target)
    plt.plot(x, prediction)
    plt.title('n_neighbors = {}'.format(n))
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

  • k=1
    • 훈련데이터는 정확하게 맞추는것 확인
    • 하지만 과적합일 확률이 높다

03-2 선형 회귀

k-최근접 이웃의 한계

  • k-최근접 이웃 회귀는 가장 가까운 샘플알 찾아 타깃을 평균한다
  • 따라서 새로운 샘플이 훈련 세트의 범위를 벗어나면 엉뚱한 값을 예측한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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]
     )

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)
# 훈련 세트와 테스트 세트를 2차원 배열로 바꿉니다
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor(n_neighbors=3)
# k-최근접 이웃 회귀 모델을 훈련합니다
knr.fit(train_input, train_target)

print(knr.predict([[50]]))
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]])

# 훈련 세트의 산점도를 그립니다
plt.scatter(train_input, train_target)
# 훈련 세트 중에서 이웃 샘플만 다시 그립니다
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
# 50cm 농어 데이터
plt.scatter(50, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

1
2
3
print(np.mean(train_target[indexes]))  # 1033.3333333333333

print(knr.predict([[100]]))  # [1033.33333333]
1
2
3
4
5
6
7
8
9
10
11
12
# 100cm 농어의 이웃을 구합니다
distances, indexes = knr.kneighbors([[100]])

# 훈련 세트의 산점도를 그립니다
plt.scatter(train_input, train_target)
# 훈련 세트 중에서 이웃 샘플만 다시 그립니다
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
# 100cm 농어 데이터
plt.scatter(100, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

선형 회귀

  • 널리 사용되는 대표적인 회귀 알고리즘
  • 수 많은 직선들 중 가장 원본과 오차가 적은 직선을 구함
1
2
# y=ax에서 lr객체에서 coef, intercept 보는법
print(lr.coef_, lr.intercept_)
1
2
3
4
5
6
7
8
9
10
from sklearn.linear_model import LinearRegression

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

# 50cm 농어에 대한 예측
print(lr.predict([[50]]))  # [1241.83860323]

print(lr.coef_, lr.intercept_)  # [39.01714496] -709.0186449535477
1
2
3
4
5
6
7
8
9
10
11
12
# 훈련 세트의 산점도를 그립니다
plt.scatter(train_input, train_target)
# 15에서 50까지 1차 방정식 그래프를 그립니다
plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])
# 50cm 농어 데이터
plt.scatter(50, 1241.8, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

print(lr.score(train_input, train_target))
print(lr.score(test_input, test_target))

$y=ax+b$ 에서 기하학적으론 $a$를 기울기, 머신러닝에선 가중치라 부른다

다항 회귀

  • 곡선
1
2
3
4
5
# 변환
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
6
7
8
9
10
11
12
13
14
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)

lr = LinearRegression()
lr.fit(train_poly, train_target)

print(lr.predict([[50**2, 50]]))  # [1573.98423528]

print(lr.coef_, lr.intercept_)  # [  1.01433211 -21.55792498] 116.0502107827827

# 구간별 직선을 그리기 위해 15에서 49까지 정수 배열을 만듭니다
point = np.arange(15, 50)
1
2
3
4
5
6
7
8
9
10
11
12
# 훈련 세트의 산점도를 그립니다
plt.scatter(train_input, train_target)
# 15에서 49까지 2차 방정식 그래프를 그립니다
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05)
# 50cm 농어 데이터
plt.scatter([50], [1574], marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

print(lr.score(train_poly, train_target))  # 0.9706807451768623
print(lr.score(test_poly, test_target))  # 0.9775935108325122

hyper-thesis = hyper-plane(초평면)

하이퍼플레인 (Hyperplane): 고차원 공간에서 데이터를 분리하는 결정 경계입니다.
1
2
3
- 2차원: 선 (Line)
- 3차원: 평면 (Plane)
- 그 이상의 차원: 상응하는 결정 경계

현실에선 다항식을 쓰지 않고 단항식을 쓰는 이유 2가지

  1. 과적합을 피하기 위해
  2. 차원이 높아지면 확인이 불가해서(시각화 불가)
    • 원본 기하학적 특성을 파악 할 수 없다

03-3 특성 공학과 규제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd

df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
print(perch_full)

import numpy as np

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]
     )

from sklearn.model_selection import train_test_split

# 훈련 세트와 테스트 세트로 나눔
train_input, test_input, train_target, test_target = train_test_split(perch_full, perch_weight, random_state=42)  # default: 75:25 

사이킷런의 변환기

1
2
3
4
5
from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures()
poly.fit([[2, 3]])
print(poly.transform([[2, 3]]))  # [[1. 2. 3. 4. 6. 9.]]
  • poly.transform([[2,3]]) → [[1. 2. 3. 4. 6. 9.]]
    • $[2^0, 2^1, 3^1, 2^2, 2*3, 3^2]$
    • $(1+2^x)$개 가 생김
    • poly.get_feature_names_out()으로 확인 가능
  • poly(degree=5): 5승까지 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
poly = PolynomialFeatures(include_bias=False)
poly.fit([[2, 3]])
print(poly.transform([[2, 3]]))  # [[2. 3. 4. 6. 9.]]

poly = PolynomialFeatures(include_bias=False)

poly.fit(train_input)
train_poly = poly.transform(train_input)  # 다항식으로 변환

print(train_poly.shape)  # (42, 9)

poly.get_feature_names_out()

test_poly = poly.transform(test_input)

다중 회귀 모델

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.linear_model import LinearRegression

lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))  # 0.9903183436982125

print(lr.score(test_poly, test_target))  # 0.9714559911594203

poly = PolynomialFeatures(degree=5, include_bias=False)  # 5승까지 생성

poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)

print(train_poly.shape)  # (42, 55)

lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))  # 0.9999999999997239

print(lr.score(test_poly, test_target))  # -144.40492666629603
  • train_score = 0.99, test_score = -144.40로 엄청난 과적합 확인

규제

1
2
3
4
5
6
7
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_poly)

train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)

Regularization(릿지, 라쏘)

  • 릿지는 계수를 제곱한 값을 기준으로 규제를 적용
  • 라쏘는 계수의 절댓값을 기준으로 규제를 적용

  • 일반적으로 릿지를 조금 더 선호
  • 두 알고리즘 모두 계수의 크기를 줄이지만 라쏘는 아예 0으로 만들 수도 있다

미분가능하다란?

  • 연속적이면서, 모든 지점에서 기울기를 구할 수 있어야 한다 → 매끄러운 선으로 표현이 되어야 한다
  • $f’(a)= lim_{h \to 0}{\frac{f(a+h)-f(a)​}{h}}$

릿지 회귀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from sklearn.linear_model import Ridge

ridge = Ridge()
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))  # 0.9896101671037343

print(ridge.score(test_scaled, test_target))  # 0.9790693977615382

import matplotlib.pyplot as plt

train_score = []
test_score = []

alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for alpha in alpha_list:
    # 릿지 모델을 만듭니다
    ridge = Ridge(alpha=alpha)
    # 릿지 모델을 훈련합니다
    ridge.fit(train_scaled, train_target)
    # 훈련 점수와 테스트 점수를 저장합니다
    train_score.append(ridge.score(train_scaled, train_target))
    test_score.append(ridge.score(test_scaled, test_target))

plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()

ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)

print(ridge.score(train_scaled, train_target))  # 0.9903815817570366
print(ridge.score(test_scaled, test_target))  # 0.9827976465386979

라쏘 회귀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from sklearn.linear_model import Lasso

lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))  # 0.989789897208096

print(lasso.score(test_scaled, test_target))  # 0.9800593698421884

train_score = []
test_score = []

alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for alpha in alpha_list:
    # 라쏘 모델을 만듭니다
    lasso = Lasso(alpha=alpha, max_iter=10000)
    # 라쏘 모델을 훈련합니다
    lasso.fit(train_scaled, train_target)
    # 훈련 점수와 테스트 점수를 저장합니다
    train_score.append(lasso.score(train_scaled, train_target))
    test_score.append(lasso.score(test_scaled, test_target))

plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()

lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)

print(lasso.score(train_scaled, train_target))  # 0.9888067471131866
print(lasso.score(test_scaled, test_target))  # 0.9824470598706695

print(np.sum(lasso.coef_ == 0))  # 40

용어 정리

#RMSE

Root Mean Squared Error (RMSE)

\(\sqrt{\frac{1}{n}\sum_{i=1}^{n}((y-\widehat{y})^2)}\)

  • 해석:
    • 예측 오차의 크기를 직관적으로 해석하기 쉽게 만드
    • 값이 낮을수록 모델의 성능이 좋다 #MSE

      Mean Squared Error(MSE)

      \(\frac{1}{n}\sum_{i=1}^{n}((y-\widehat{y})^2)\)

  • 해석:
    • MSE는 예측 오차의 제곱 평균
    • 값이 낮을수록 모델의 성능이 좋다

#R2

$R^2$ (결정 계수)

\(R^2 = 1-\tfrac{오차^2}{편차^2} = 1-\tfrac{\sum (예측값 - 실제값)^2}{\sum (실제값평균-실제값)^2}\)

  • 해석:
    • 값이 0과 1 사이에 있으며, 1에 가까울수록 모델이 데이터의 변동성을 잘 설명한다는 것을 의미
    • 반면, 0에 가까울수록 모델의 설명력이 떨어진다는 것을 의미합니다.
  • MSE는 예측 오차의 제곱 평균을 나타내며, 값이 낮을수록 좋다
  • RMSE는 MSE의 제곱근으로, 예측 오차의 실제 크기를 나타낸다.
  • **$R^2$는 모델이 데이터의 변동성을 얼마나 잘 설명하는지를 나타내는 지표로, 1에 가까울수록 좋다.

#비용함수

로지스틱 회귀의 비용 함수:

\[J(\theta) = -\frac{1}{m} \sum_{i=1}^{m} \left[ y^{(i)} \log(h_\theta(x^{(i)})) + (1 - y^{(i)}) \log(1 - h_\theta(x^{(i)})) \right]\] \[h_\theta(x) = \frac{1}{1 + e^{-\theta^T x}}\]

정규화(regularization) 기법

  • 불순물 추가 하여 과적합을 막는다 \((y-(W_{1}X_{1} + W_{2}X_{2}\cdots +W_{0})) + \lambda\) $\lambda$ : 불순물

#Ridge

릿지(Ridge) 회귀:

  • 정규화 항: $L2​$ 노름(norm)의 제곱, 즉 모든 계수의 제곱합입니다.
  • 목적 함수(Objective Function): \(J(\Theta )=MSE(\Theta)+\lambda \sum_{i=1}^{n}{\Theta_{i}^2 }\)

#Lasso

라소(Lasso) 회귀:

  • 정규화 항: $L1​$ 노름, 즉 모든 계수의 절대값의 합입니다.
  • 목적 함수(Objective Function): \(J(\Theta )=MSE(\Theta)+\lambda \sum_{i=1}^{n}\left |{\Theta_{i}} \right |\)

  • 릿지와 라소는 선형 회귀 모델에 정규화를 추가하여 과적합 방지
  • 릿지는 $L_{2}$ 정규화를 사용하여 계수를 작게 만들지만 0으로 만들지는 않는다
  • 라소는 $L_{1}$​ 정규화를 사용하여 계수를 0으로 만들 수 있어, 불필요한 특성을 자동으로 제거하는 효과가 있다

Chapter 04 다양한 분류 알고리즘

04-1 로지스틱 회귀

럭키백의 확률

1
2
3
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
print(pd.unique(fish['Species']))  # ['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']

fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()
print(fish_input[:5])  
# [[242.      25.4     30.      11.52     4.02  ]
# [290.      26.3     31.2     12.48     4.3056]
# [340.      26.5     31.1     12.3778   4.6961]
# [363.      29.      33.5     12.73     4.4555]
# [430.      29.      34.      12.444    5.134 ]]

fish_target = fish['Species'].to_numpy()

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)

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
k-최근접 이웃 분류기의 확률 예측
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=5)
kn.fit(train_scaled, train_target)
print(kn.score(train_scaled, train_target))  # 0.7983193277310925
print(kn.score(test_scaled, test_target))  # 0.85

print(kn.classes_)  # ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

print(kn.predict(test_scaled[:5]))  # ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']

import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4))
# [[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.    ]]

distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])  # [['Roach' 'Perch' 'Perch']]

로지스틱 회귀

1
2
3
4
5
6
7
8
9
import numpy as np
import matplotlib.pyplot as plt
z = np.arange(-5, 5, 0.1)

phi = 1/(1+np.exp(-z))
plt.plot(z, phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()

로지스틱 회귀로 이진 분류 수행하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]])  # ['A' 'C']

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]

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
print(lr.predict(train_bream_smelt[:5]))  # ['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
print(lr.predict_proba(train_bream_smelt[:5]))
# [[0.99759855 0.00240145]
# [0.02735183 0.97264817]
# [0.99486072 0.00513928]
# [0.98584202 0.01415798]
# [0.99767269 0.00232731]]
print(lr.classes_)  # ['Bream' 'Smelt']

print(lr.coef_, lr.intercept_)  # [[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]

decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)  # [-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]

from scipy.special import expit
print(expit(decisions))  # [0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]
로지스틱 회귀로 다중 분류 수행하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))  # 0.9327731092436975

print(lr.score(test_scaled, test_target))  # 0.925

print(lr.predict(test_scaled[:5]))  # ['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']

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]]

print(lr.classes_)  # ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

print(lr.coef_.shape, lr.intercept_.shape)  # (7, 5) (7,)

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]]

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]]

# 소프트맥스를 거친 결과를 나타낸다

용어 정리

더미 처리(dummy encoding), 원-핫 인코딩(One-Hot encoding)

  1. 더미 처리(Dummy Encoding):
    • 범주형 변수의 각 범주를 0 또는 1의 값을 갖는 새로운 변수(더미 변수)로 변환
    • k개의 범주를 가진 범주형 변수는 k-1개의 더미 변수로 변환
      • 이렇게 하면 다중 공선성(multicollinearity) 문제를 피할 수 있다
  2. 원-핫 인코딩(One-Hot Encoding):
    • 범주형 변수의 각 범주를 0 또는 1의 값을 갖는 새로운 변수로 변환
    • k개의 범주를 가진 범주형 변수는 k개의 새로운 변수로 변환
    • 따라서 원-핫 인코딩은 모든 범주를 대표하는 변수를 생성하므로 더미 처리보다 변수가 하나 더 많다

예시: 고려해볼 범주형 변수가 ‘색상’이며, 이 변수의 가능한 값이 ‘빨강’, ‘녹색’, ‘파랑’인 경우:

  • 더미 처리:
    • 빨강: [1, 0]
    • 녹색: [0, 1]
    • 파랑: [0, 0]
  • 원-핫 인코딩:
    • 빨강: [1, 0, 0]
    • 녹색: [0, 1, 0]
    • 파랑: [0, 0, 1]

로지스틱 함수(logistic function), 시그모이드 함수(sigmoid function)

\[\phi = \frac{1}{1+e^{-z}}\]
  • z에 선형회귀식 대입
  • 로지스틱 회귀는 ‘회귀’가 아닌 ‘분류’다
  • z값이 어떻게 변해도 0~1 사이의 값이 나온다
\[cost function = -(plog_{e}\hat{p})\]

Logistic regression 의 cost function은 다음과 같이 정의한다.

로지스틱 회귀의 비용 함수:

\[J(\theta) = -\frac{1}{m} \sum_{i=1}^{m} \left[ y^{(i)} \log(h_\theta(x^{(i)})) + (1 - y^{(i)}) \log(1 - h_\theta(x^{(i)})) \right]\] \[h_\theta(x) = \frac{1}{1 + e^{-\theta^T x}}\]
비용 함수 (Cost Function)에 대한 설명

비용 함수는 머신 러닝 및 최적화 문제에서 중요한 개념입니다. 주어진 모델의 예측과 실제 데이터 간의 차이를 측정하는 데 사용됩니다. 이것은 모델이 얼마나 정확한지를 평가하고, 모델을 최적화하기 위한 방향을 제시하는 데 도움이 됩니다. 일반적으로, 비용 함수는 주어진 입력 데이터와 모델의 예측 사이의 오차를 계산하는 방법을 정의합니다. 이것은 종종 손실 함수(loss function)라고도 불립니다. 가장 일반적인 예로는 평균 제곱 오차(Mean Squared Error, MSE)가 있습니다. 수학적으로, 비용 함수는 다음과 같이 표현될 수 있습니다: \(J(\theta) = \frac{1}{2m} \sum_{i=1}^{m} (h_{\theta}(x^{(i)}) - y^{(i)})^2\) 여기서:

  • $(J(\theta))$는 비용 함수입니다.
  • $(\theta)$는 모델의 파라미터(가중치와 편향)를 나타냅니다.
  • $(m)$은 데이터 샘플의 개수를 나타냅니다.
  • $(h_{\theta}(x^{(i)}))$는 모델의 예측입니다.
  • $(y^{(i)})$는 실제 데이터 값입니다.

비용 함수는 모델의 파라미터 (\theta)를 조정하여 최소화되어야 합니다. 이는 주어진 데이터에 대한 모델의 예측 오차를 최소화하는 것을 의미합니다. 최적화 알고리즘을 사용하여 비용 함수를 최소화하는 최적의 파라미터 값을 찾을 수 있습니다. 많은 머신 러닝 알고리즘에서는 비용 함수를 최적화하여 모델을 학습하고, 새로운 데이터에 대한 예측을 수행합니다. 비용 함수를 올바르게 선택하고 최적화하는 것은 모델의 품질과 성능에 큰 영향을 미칩니다.

Logistic Regression의 파라메터
  • C (Inverse of Regularization Strength):
    • 로지스틱 회귀의 정규화 매개변수.
    • 작은 C 값: 강한 정규화, 모델 간단하게 만듦, 과적합 방지.
    • 큰 C 값: 약한 정규화, 훈련 데이터에 더 정확하게 맞추려 함, 과적합 가능성 높음.
    • 모델의 훈련과 일반화 사이의 균형을 찾기 위해 조정.
  • max_iter (Maximum Number of Iterations):
    • 로지스틱 회귀 모델의 최대 반복 횟수 설정.
    • 최적화 알고리즘이 비용 함수 최소화 및 파라미터 업데이트 반복 수행.
    • 모델의 수렴 여부와 학습 속도에 영향을 줌.
    • 너무 큰 값: 수렴에 오랜 시간 소요, 설정에 주의 필요.
    • 너무 작은 값: 빠른 종료 가능, 모델 성능 저하 가능.
    • 데이터 크기와 모델 복잡도에 따라 적절한 값으로 조정.

04-2 확률적 경사 하강법


GD (Gradient Descent): 경사 하강법

Gradient Descent는 함수의 최솟값 (또는 최댓값)을 찾기 위한 반복적인 최적화 기법이다. 주로 머신 러닝과 딥 러닝에서 비용 함수 (cost function) 또는 손실 함수 (loss function)의 최솟값을 찾는 데 사용된다.

수학적 표현:

주어진 비용 함수 $( J(\theta) )$에 대한 Gradient Descent 업데이트 규칙은 다음과 같다:

\[\theta_{\text{new}} = \theta_{\text{old}} - \alpha \nabla J(\theta_{\text{old}})\]

여기서:

  • $(\theta)$: 최적화하려는 파라미터이다
  • $( \alpha)$: 학습률, 업데이트의 크기를 결정한다
  • $( \nabla J(\theta) )$: 비용 함수의 그래디언트
시각적 설명:

비용 함수 $( J(\theta) )$의 2D 플롯을 상상해보면, Gradient Descent의 목표는 이 함수의 가장 낮은 점 (골짜기)을 찾는 것이다. 시작점에서 기울기 (그래디언트)의 반대 방향으로 작은 스텝을 밟으면서 이 점을 찾는다.


SGD (Stochastic Gradient Descent): 확률적 경사 하강법

전체 데이터 세트에 대한 그래디언트를 계산하는 대신, SGD는 매 반복에서 하나의 훈련 샘플만을 사용하여 그래디언트를 계산한다.

수학적 표현:
\[\theta_{\text{new}} = \theta_{\text{old}} - \alpha \nabla J_i(\theta_{\text{old}})\]

여기서 $( J_i(\theta) )$는 i번째 훈련 샘플에 대한 비용이다.

시각적 설명:

SGD는 훨씬 더 노이즈가 있고 불안정 할 수 있다. 그 이유는 매 반복마다 단 하나의 샘플만을 사용하기 때문이다. 그러나 이러한 무작위성은 SGD가 지역 최솟값에서 벗어나게 도와주므로, 때로는 전체 배치 Gradient Descent보다 전역 최솟값을 더 쉽게 찾을 수 있다.


Accuracy, Precision, Recall, F1 Score


Accuracy (정확도)

정의: Accuracy는 전체 예측 중 올바르게 예측된 비율이다.

수식: \(\text{Accuracy} = \frac{\text{올바르게 예측된 샘플 수}}{\text{전체 샘플 수}}\)


Precision (정밀도)

정의: Precision은 양성으로 예측된 샘플 중 실제로 양성인 비율이다.

수식: \(\text{Precision} = \frac{\text{진짜 양성 (TP)}}{\text{진짜 양성 (TP) + 거짓 양성 (FP)}}\)


Recall (재현율)

정의: Recall은 실제 양성 중 양성으로 올바르게 예측된 비율이다.

수식: \(\text{Recall} = \frac{\text{진짜 양성 (TP)}}{\text{진짜 양성 (TP) + 거짓 음성 (FN)}}\)


F1 Score (F1 점수)

정의: F1 스코어는 Precision과 Recall의 조화 평균이다. 이는 두 지표 사이의 균형을 나타낸다.

수식: \(\text{F1 Score} = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}\)


Scikit-learn 라이브러리상에서 SGD는 Linear Regression, Logistic Regression을 포함하는 상위개념이다

실습 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
## SGDClassifier

import pandas as pd

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

fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

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)

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

from sklearn.linear_model import SGDClassifier

sc = SGDClassifier(loss='log_loss', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))  # 0.773109243697479
print(sc.score(test_scaled, test_target))  # 0.775

sc.partial_fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))  # 0.8151260504201681
print(sc.score(test_scaled, test_target))  # 0.85

## 에포크와 과대/과소적합

import numpy as np

sc = SGDClassifier(loss='log_loss', 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))

import matplotlib.pyplot as plt

plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

1
2
3
4
5
6
7
8
9
10
11
12
13
sc = SGDClassifier(loss='log_loss', max_iter=100, tol=None, random_state=42)
# tol = tolerance: None시 300번 다 돌아감
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))  # 0.957983193277311
print(sc.score(test_scaled, test_target))  # 0.925

sc = SGDClassifier(loss='hinge', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))  # 0.9495798319327731
print(sc.score(test_scaled, test_target))  # 0.9495798319327731

hinge function:

object permanance 대상 연속성

This post is licensed under CC BY 4.0 by the author.

빅데이터의 다음 단계는 예측 분석이다

[SeSAC]Roboflow