suyeongpark

지성을 추구하는 사람/ suyeongpark@abyne.com

이상엽/ 해석학/ 함수열과 멱급수

정의

Def 1. [함수열과 함수열급수]

\emptyset \neq D \subset \mathbb{R} 이고 모든 n \in \mathbb{R} 에 대하여 f_{n} : D \to \mathbb{R} 일 때 \{f_{n}\} D 에서의 함수열이라 한다.

또한 \{f_{n}\} 이 함수열일 때 \sum_{n = 1}^{\infty} f_{n} 을 함수열 급수라 한다.

  • (쉽게 말해서 수열의 형태로 묶은 함수를 함수열이라고 한다. 그렇게 만들어진 함수열은 급수형태로도 표현 가능)

Def 2. [멱급수]

실수 c 와 수열 \{a_{n}\} 에 대하여 함수열 \{f_{n}\}

f_{n} (x) = a_{n}(x - c)^{n}

과 같이 표현될 때의 함수열 급수

\sum_{n=1}^{\infty} f_{n} = \sum_{n=0}^{\infty} a_{n} (x - c)^{n}

를 멱급수라 한다.

  • (함수열이 다항함수의 형태로 구성될 때 멱급수라고 한다)
  • (멱은 power의 번역인데, 덮어씌워지는 것, 누적되는 것이라 이해하면 된다)

Def 3. [해석함수]

어떤 \delta > 0 에 대하여 (c-\delta, c+\delta) 에서 함수 f 가 멱급수로 표현될 수 있으면,

f(x) = \sum_{n=0}^{\infty} a_{n} (x-c)^{n} 이면 f x=c 에서 해석적이라 한다.

또한 함수 f 가 열린구간 I 의 모든 점에서 해석적이면 f I 에서의 해석함수라 한다.

  • (멱급수로 표현 가능한 것을 해석함수라고 한다)

점별, 균등수렴

  • (f(x) = f_{1}(x) + f_{2}(x) + f_{3}(x) + ... 과 같은 형태로 분해가 가능할 때 해석함수라고 한다.)
    • (물론 이것이 의미가 있으려면 함수 f(x) 가 수렴성을 가져야 함. 그래서 우선은 수렴성을 판단해야 한다)
  • (이렇게 되면 함수 f(x) 를 보지 않고 그 분해된 각각의 \{f_{n}(x)\} 들을 보고 그 합으로써 f(x) 를 이해할 수 있다.)
  • (라고 수학자들이 최초에 생각했으나, f(x) 의 하위 $latex  f_{1}(x), f_{2}(x), f_{3}(x), … &s=2$이 모두 연속이거나 미분, 적분 가능해도 정작 그 하위 함수들의 합인 함수 f(x) 는 연속이지도, 미분, 적분가능하지 않을 수 있는 Case가 계속 발견되었음)
  • (그래서 어떻게 해야 하위 함수들의 성질을 그대로 원래 함수에도 적용할 수 있을지를 고민했고 그런 것이 적용 가능한 경우를 바이어슈트라스가 발견해서 균등수렴이라고 정의 함. 그것이 안되는 기존의 수렴은 점별수렴이라고 한다.)

함수열의 수렴

Def. [점별수렴과 균등수렴]

\{f_{n}\} \{f_{n}\} 가 각각 \{f_{n}\} 에서 정의된 함수열과 함수라 하자

  1. 임의의 x \in D 와 임의의 \epsilon > 0 에 대해 n \geq N \Rightarrow |f_{n}(x) - f(x)| < \epsilon 을 만족시키는 자연수 N 이 존재하면 \{f_{n}\} D 에서 f 로 점별수렴한다고 한다. 이때 f \{f_{n}\} 의 극한함수라 하고, f(x) = \lim_{n \to \infty} f_{n}(x) 로 표현한다.
  2. 임의의 \epsilon > 0 에 대하여 n \geq N \Rightarrow |f_{n}(x) - f(x)| < \epsilon 를 임의의 x \in D 에 대하여 만족시키는 자연수 N 이 존재하면 \{f_{n}\} D 에서 f 로 균등수렴한다고 한다.

Thm. \{f_{n}\} D 에서 균등수렴하면 점별수렴한다.

함수열급수의 수렴

Thm 1. [코시판정법]

f_{n} : D \to \mathbb{R} 이라 할 떄, 다음 조건을 만족하는 \sum_{n=1}^{\infty} f_{n} D 에서 균등수렴한다.

\forall \epsilon > 0, \exists N \in \mathbb{N} : \forall m, n \in \mathbb{N}

with, m > n \geq \mathbb{N}, \forall x \in D, |\sum_{k=n+1}^{m} f_{k}(x)| < \epsilon

Thm 2. [바이어슈트라스판정법]

n \in \mathbb{N} 에 대하여 f_{n} : D \to \mathbb{R} 이라 할 때, 적당한 양의 상수 M_{n} > 0 이 존재하여 모든 x \in D 에 대하여 |f_{n}(x) | \leq M_{n} 이고 \sum_{n=1}^{\infty} M_{n} < \infty 이면 \sum_{n=1}^{\infty} f_{n} D 에서 균등수렴한다.

멱급수

  • (해석함수는 멱급수로 표현되는 함수)
  • (멱급수란 함수열급수 중에서 다항 함수로 표현되는 급수)

멱급수의 수렴

Thm 1. [근판정법]

모든 n \in \mathbb{N} 에 대하여 a_{n} \geq 0 이고 \lim_{n \to \infty} \sqrt[n]{a_{n}} = M 일 때 다음이 성립한다.

  1. M < 1 이면 \sum_{n=1}^{\infty} a_{n} 은 수렴한다.
  2. M > 1 이면 \sum_{n=1}^{\infty} a_{n} 은 발산한다.
  • (M = 1 인 경우에서는 수렴, 발산법을 알 수 없음. 직접 계산해 봐야 함)

Cor. [멱급수의 수렴]

\sum_{n=0}^{\infty} a_{n} (x - c)^{n} 에 대하여 \alpha = \lim_{n \to \infty} \sqrt[n]{|a_{n}|} 일 때, R = {1 \over \alpha} 라 하면 \sum_{n=0}^{\infty} a_{n} (x - c)^{n}

  1. |x - c| < R 에서 절대수렴한다.
  2. |x - c| > R 에서 발산한다.

\alpha = 0 이면 R = \infty \alpha = \infty 이면 R = 0 으로 간주한다.

  • (어떤 구간에 대해 수렴 여부 판정. 여기서 R은 수렴 반지름이라고 한다)
  • (여기서 절대수렴은 점별수렴에 대한 것이다)

Def. [수렴반지름과 수렴구간]

Cor에서 구한 R 을 멱급수 \sum_{n=0}^{\infty} a_{n} (x - c)^{n} 의 수렴반지름이라 하고 \sum_{n=0}^{\infty} a_{n} (x - c)^{n} 이 수렴하는 점들 전체의 집합을 수렴구간이라 한다.

Thm 1. [수렴반지름과 균등수렴]

\sum_{n=0}^{\infty} a_{n} (x - c)^{n} 의 수렴반지름을 R 이라 하고 0 < r < R 일 때 \sum_{n=0}^{\infty} a_{n} (x - c)^{n} [c-r, c+r] 에서 균등수렴한다.

  • (수렴 반지름 안에 속하는 폐구간은 균등수렴한다)

멱급수의 연속

Thm 1. [함수열의 연속]

구간 I 에서 연속인 함수열 \{f_{n}\} f 로 균등수렴하면 f I 에서 연속이다.

Cor. [함수열급수의 연속]

구간 I 에서 연속인 함수열 \{f_{n}\} 에 대하여 \sum_{n=1}^{\infty} f_{n} f 로 균등수렴하면 f I 에서 연속이다.

Lemma. [아벨의 공식]

수열 \{a_{n}\}, \{b_{n}\} 과 임의의 자연수 n, m (n > m) 에 대하여 다음이 성립한다.

\sum_{k=m}^{n} a_{k}b_{k} = a_{n} \sum_{k=m}^{n} b_{k} + \sum_{j=m}^{n-1} (a_{j} - a_{j+1}) \sum_{k=m}^{j} b_{k}

Thm 2. [아벨 정리]

수렴반지름이 R \sum_{n=0}^{\infty} a_{n} (x - c)^{n} x = c + R 에서 수렴하면 (c - R, c + R] 의 임의의 폐부분집합에서 균등수렴한다.

Thm 3. [멱급수의 연속]

함수 f(x) = \sum_{n=0}^{\infty} a_{n} (x - c)^{n} 는 수렴구간에서 연속이다.

멱급수의 미분

Thm 1. [함수열의 미분]

다음을 만족하는 함수열 \{f_{n}\} 은 유계구간 I 에서 균등수렴한다.

  1. 임의의 x_{0} \in I 에 대하여 \{f_{n}(x_{0})\} 가 수렴한다. (점별 수렴)
  2. \{f_{n}\} I 에서 미분가능하며, I 에서 \{f_{n}'\} 는 균등수렴한다.

또한 이때 \{f_{n}\} 의 극한함수를 f 라 하면 f I 에서 미분가능하고 임의의 x \in I 에 대하여 f'(x) = \lim_{n \to \infty} f_{n}'(x) 이다.

Cor. [함수열급수의 미분]

다음이 만족하면 함수열급수 \sum_{n=1}^{\infty} f_{n} 은 유계구간 I 에서 균등수렴한다.

  1. 임의의 x_{0} \in I 에 대하여 \sum_{n=1}^{\infty} f_{n} (x_{0}) 가 수렴한다.
  2. \{f_{n}\} I 에서 미분가능하며, I 에서 \sum_{n=1}^{\infty} f_{n}' 은 균등수렴한다.

이때 f = \sum_{n=1}^{\infty} f_{n} 이라 하면 f I 에서 미분가능하고 임의의 x \in I 에 대하여 f'(x) = \sum_{n=1}^{\infty} f_{n}'(x) 이다.

Lemma.

\sum_{n=0}^{\infty} a_{n} (x - c)^{n} 의 수렴반지름이 R 이면 \sum_{n=0}^{\infty} n a_{n} (x - c)^{n-1} 의 수렴반지름도 R 이다.

Thm 3. [멱급수의 미분]

함수 f(x) = \sum_{n=0}^{\infty} a_{n} (x - c)^{n} 의 수렴반지름이 R 이면 f (c - R, c + R) 에서 미분가능하며, 이때 f 의 도함수는

f'(x) = \sum_{n=0}^{\infty} n a_{n} (x - c)^{n-1}

이다.

멱급수의 적분

Thm 1. [균등수렴과 적분]

\{f_{n}\} [a, b] 에서 f 로 균등수렴하고 f_{n} \in \mathfrak{R}[a, b] 이면 f \in \mathfrak{R}[a, b] 이고

\lim_{n \to \infty} \int_{a}^{b} f_{n} = \int_{a}^{b} f

이다.

Thm 2. [항별적분]

f_{n} \in \mathfrak{R}[a, b] \{f_{n}\} 에 대하여 \sum_{n=1}^{\infty} f_{n} [a, b] 에서 f 로 균등수렴하면 f \in \mathfrak{R}[a, b] 이고

\int_{a}^{b} f = \sum_{n=1}^{\infty} \int_{a}^{b} f_{n}

이다.

Thm 3. [멱급수의 적분]

함수 f(x) = \sum_{n=0}^{\infty} a_{n} (x - c)^{n} [a, b] 에서 수렴하면 f \in \mathfrak{R}[a, b] 이고

\int_{a}^{b} f(x) dx = \sum_{n=0}^{\infty} a_{n} \int_{a}^{b} (x-c)^{n} dx

이다.

Thm 4. [멱급수의 특이적분]

함수 f(x) = \sum_{n=0}^{\infty} a_{n} (x - c)^{n} [a, b) 에서 수렴하고 멱급수 \sum_{n=0}^{\infty} {a_{n} \over n + 1} (b - c)^{n+1} 이 수렴하면 f [a, b) 에서 특이적분가능하고

\int_{a}^{b} f(x) dx = \sum_{n=0}^{\infty} a_{n} \int_{a}^{b} (x-c)^{n} dx

이다.

20.03.28

소프트뱅크의 첫 투자 실패 스타트업 ‘Brandless’

이렇게 큰 주목과 기대를 받은 Brandless가 한순간에 폐업을 결정하게 된 이유 3 가지를 분석했다.

1. 다양한 제품과 소비자 타겟을 고려하지 않은 3달러 정찰제의 실수
2. 아마존, 월마트는 알고 있었지만 Brandless는 몰랐던 것 – 소모품은 수익 창출에 크게 기여하지 않는다
3. 소비자의 선택은 제품 본질이 아닌 마케팅과 브랜딩이다

재미있는 내용이라 정리. 1, 2번은 실제 현장을 보지 못한 사람들이 흔히 하기 쉬운 실수이고, 3번은 세상이 합리적인 기준에 의해 돌아간다고 믿는 똑똑한 사람들이 하기 쉬운 실수임.

폴리에틸렌 먹는 애벌레

사실 애벌레 60마리가 일주일간 먹는 비닐 크기는 30㎠에 불과하기 때문에 대용량의 플라스틱 비닐을 분해하기 위해서는 효율을 높일 필요가 있습니다. 만약 미생물만 따로 분리해 배양할 수 있다면 효율을 높일 수 있을 것입니다. 하지만 이런 공생 미생물은 숙주 내 환경에 잘 적응해 그 안에서만 사는 경우가 드물지 않습니다. 다행히 연구팀이 찾아낸 미생물은 일주일 정도 숙주 밖에서도 폴리에틸렌을 분배했습니다. 

 다만 실제 플라스틱 처리에 이 미생물을 사용하기 전에 몇 가지 해결해야 할 문제점이 있습니다. 대량 배양도 문제지만, 폴리에틸렌을 분해해 독성이 있는 물질인 에텔렌 글리콜(ethylene glycol)을 만들기 때문입니다. 따라서 일반 환경에서 사용하기는 무리지만, 이 미생물을 연구해 플라스틱 봉투를 더 유용한 물질로 분해해 다시 재활용하는 연구는 가능할 것으로 기대합니다. 

플라스틱 먹고 산다고 해서 신기하다 했더니만 먹고 독을 만들어 내는거면 오히려 더 안 좋은거잖아?

머신 러닝 교과서/ 모델 평가와 하이퍼파라미터 튜닝의 모범 사례

파이프라인을 사용한 효율적인 워크플로

  • 테스트 세트에 있는 별도의 샘플처럼 새로운 데이터의 스케일을 조정하고 압축하기 위해 훈련 세트에서 학습한 파라미터를 재사용해야 한다.
  • 이 장에서는 이를 위해 아주 유용하게 사용할 수 있는 사이킷런의 Pipeline 클래스를 배우겠다.

위스콘신 유방암 데이터셋

  • 이 장에서는 위스콘신 유방암 데이터셋을 사용하겠다. 여기에는 악성과 양성인 종양 세포 샘플 569개가 포함되어 있다.
  • 이 절에서는 3단계로 나누어 데이터셋을 읽고 훈련 세트와 테스트 세트로 분할 하겠다.
    1. pandas를 사용하여 UCI 서버에서 데이터셋을 Load
    2. 30개의 특성을 넘파이 배열 X에 할당.
      • LabelEncoder 객체를 사용하여 클래스 레이블을 원본 문자열 표현에서 정수로 변환한다.
    3. 첫 번째 모델 파이프라인을 구성하기 전에 데이터셋을 훈련 세트(80%)와 별도의 테스트 세트(20%)로 나눈다.
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split

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

X = df.loc[:, 2:].values
y = df.loc[:, 1].values

le = LabelEncoder()

y = le.fit_transform(y)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, stratify=y, random_state=1)

파이프라인으로 변환기와 추정기 연결

  • 위스콘신 유방암 데이터셋을 선형 분류기에 주입하기 전에 특성을 표준화 해야 한다.
    • 여기서는 우선 주성분 분석(PCA)를 통해 초기 30차원에서 좀 더 낮은 2차원 부분 공간으로 데이터를 압축한다.
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline

pipe_lr = make_pipeline(StandardScaler(), PCA(n_components=2), LogisticRegression(solver='liblinear', random_state=1))

pipe_lr.fit(X_train, y_train)

y_pred = pipe_lr.predict(X_test)
  • make_pipeline 함수는 여러 개의 사이킷런 변환기(입력에 대해 fit 메서드와 transform 메서드를 지원하는 객체)와 그 뒤에 fit 메서드와 predict 메서드를 구현한 사이킷런 추정기를 연결할 수 있다.
  • 위 코드에서는 StandardScaler와 PCA 두 개의 변환기와 LogisticRegression 추정기를 make_pipeline 의 입력으로 넣었고, 이 함수는 이 객체들을 사용하여 사이킷런의 Pipeline 클래스 객체를 생성하여 반환한다.
  • 사이킷런의 Pipeline 클래스를 메타 추정기(meta-estimator)나 개별 변환기와 추정기를 감싼 래퍼(wrapper)로 생각할 수 있다.
    • Pipeline 객체의 fit 메서드를 호출하면 데이터가 중간 단계에 있는 모든 변환기의 fit 메서드와 transform 메서드를 차례로 거쳐 추정기 객체에 도달한다.
    • 추정기는 변환된 훈련 세트를 사용하여 학습한다.
  • 위 예제에서 pipe_lr 파이프라인의 fit 메서드를 호출할 때 먼저 훈련 세트에 StandardScaler의 fit 메서드와 transform 메서드가 호출된다.
    • 변환된 훈련 데이터는 파이프라인의 다음 요소인 PCA 객체로 전달된다. 이전 단계와 비슷하게 스케일 조정된 입력 데이터에 PCA의 fit 메서드와 transform 메서드가 호출된다.
    • 그 다음 파이프라인의 최종 요소인 추정기에 훈련 데이터가 전달된다.
    • 마침내 LogisticRegression 추정기가 StandardScaler와 PCA로 변환된 훈련 데이터로 학습한다.
    • 파이프라인의 중간 단계 횟수에는 제한이 없지만, 파이프라인의 마지막 요소는 추정기가 되어야 한다.
  • 파이프라인에서 fit 메서드를 호출하는 것과 비슷하게 predict 메서드도 제공한다.
    • Pipeline 인스턴스의 predict 메서드를 호출할 때 주입된 데이터는 중간 단계의 transform 메서드를 통과한다.
    • 마지막 단계에서 추정기 객체가 변환된 데이터에 대한 예측을 반환한다.
  • 사이킷런의 파이프라인은 매우 유용한 래퍼 도구이기 때문이 책의 나머지 부분에서 자주 사용할 것이다.
    • 객체가 동작하는 아래 그림을 참고하라.

k-겹 교차 검증을 사용한 모델 성능 평가

  • 모델이 너무 간단하면 under-fitting 이 발생하고 너무 복잡하면 훈련 데이터에 over-fitting이 될 수 있다.
  • 적절한 편향-분산 트레이드오프를 찾으려면 모델을 주의 깊게 평가해야 한다. 이 절에서는 보편적인 교차 검증 기법인 홀드아웃 교차 검증(holdout cross-validation)과 k-겹 교차 검증(k-fold corss-validation)을 다루겠다.

홀드아웃 방법

  • 홀드아웃 방법은 데이터셋을 훈련 세트와 테스트 세트로 나눈 후, 전자는 모델 훈련에 사용하고 후자는 일반화 성능을 추정하는데 사용한다.
  • 일반적인 머신 러닝 애플리케이션에서는 처음 본 데이터에서 예측 성능을 높이기 위해 하이퍼파라미터를 튜닝하고 비교해야 하는데, 이 과정을 모델 선택이라고 한다.
    • 모델 선택이라는 용어는 주어진 분류 문제에서 튜닝할 파라미터(또는 하이퍼 파라미터)의 최적 값을 선택해야 하는 것을 의미한다.
    • 모델 선택에 같은 테스트 세트를 반복해서 재사용하면 훈련 세트의 일부가 되는 셈이고 결국 모델은 over-fitting이 될 것이다.
    • 아직도 많은 사람이 모델 선택을 위해 테스트 세트를 사용하는데 이는 좋은 머신 러닝 작업 방식이 아니다.
  • 모델 선택에 홀드아웃 방법을 사용하는 가장 좋은 방법은 데이터를 훈련 세트, 검증 세트, 테스트 세트 3개의 부분으로 나누는 것이다.
    • 훈련 세트는 여러 모델을 훈련하는데 사용한다.
    • 검증 세트에 대한 성능은 모델 선택에 사용한다.
    • 테스트 세트를 분리했기 때문에 새로운 데이터에 대한 일반화 능력을 덜 편향되게 추정할 수 있는 장점이 있다.
  • 아래 그림은 홀드아웃 교차 검증의 개념을 보여준다. 검증 세트를 사용하여 반복적으로 다른 파라미터 값에서 모델을 훈련한 후 성능을 평가한다.
    • 만족할 만한 하이퍼 파라미터 값을 얻었다면 테스트 세트에서 모델의 일반화 성능을 추정한다.

  • 홀드아웃 방법은 훈련 데이터를 훈련 세트와 검증 세트로 나누는 방법에 따라 성능 추정이 민감할 수 있다는 것이 단점이다.
    • 검증 세트의 성능 추정이 어떤 샘플을 사용하느냐에 따라 달라질 것이다.

k-겹 교차 검증

  • k-겹 교차 검증에서는 중복을 허락하지 않고 훈련 데이터셋을 k개의 폴드로 랜덤하게 나눈다.
    • k-1 개의 폴드로 모델을 훈련하고 나머지 하나의 폴드로 성능을 평가한다.
    • 이 과정을 k번 반복하여 k개의 모델과 성능 추정을 얻는다.
  • 그 다음 서로 독립적인 폴드에서 얻은 성능 추정을 기반으로 모델의 평균 성능을 계산한다.
    • 홀드아웃 방법에 비해 훈련 세트의 분할에 덜 민감한 성능 추정을 얻을 수 있다.
    • 일반적으로 모델 튜닝에 k-겹 교차 검증을 사용한다. 즉, 만족할 만한 일반화 성능을 내는 최적의 하이퍼파라미터 값을 찾기 위해 사용한다.
  • 만족스러운 하이퍼파라미터 값을 찾은 후에는 전체 훈련 세트를 사용하여 모델을 다시 훈련한다.
    • 그 다음 독립적인 테스트 세트를 사용하여 최종 성능 추정을 한다.
    • k-겹 교차 검증 후에 전체 훈련 세트로 모델을 학습하는 이유는 훈련 샘플이 많을수록 학습 알고리즘이 더 정확하고 안정적인 모델을 만들기 때문이다.
  • k-겹 교차 검증이 중복을 허락하지 않는 리샘플링 기법이기 때문에 모든 샘플 포인트가 훈련하는 동안 (테스트 폴드로) 검증에 딱 한번 사용되는 장점이 있다.
    • 이로 인해 홀드아웃 방법보다 모델 성능의 추정에 분산이 낮다.
  • 아래 그림은 k=10일 때 k-겹 교차 검증의 개념을 요약한 것이다.
    • 훈련 데이터는 10개의 폴드로 나누어지고 열 번의 반복 동안 아홉 개의 폴드는 훈련에, 한 개의 폴드는 모델 평가를 위해 사용된다.
    • 각 폴드의 추정 성능 E_{i} 를 사용하여 모델의 평균 성능 E를 계산한다.

  • 경험적으로 보았을 때 k-겹 교차 검증에서 좋은 기본값은 k=10이다.
    • 예컨대 론 코하비(Ron Kohavi)는 여러 종류의 실제 데이터셋에서 수행한 실험을 통해 10-겹 교차 검증이 가장 뛰어난 편향-분산 트레이드오프를 가진다고 제안했다
  • 비교적 작은 훈련 세트로 작업한다면 폴드 개수를 늘리는 것이 좋다.
    • k값이 증가하면 더 많은 훈련 데이터가 각 반복에 사용되고 모델 성능을 평균하여 일반화 성능을 추정할 때 더 낮은 편향을 만든다.
    • k 값이 아주 크면 교차 검증 알고리즘의 실행 시간이 늘어나고 분산이 높은 추정을 만든다. 이는 훈련 폴드가 서로 많이 비슷해지기 때문이다.
    • 다른 말로 하면 대규모 데이터셋으로 작업할 때는 k=5와 같은 작은 k 값을 선택해도 모델의 평균 성능을 정확하게 추정할 수 있다.
    • 또 폴드마다 모델을 학습하고 평가하는 계산 비용을 줄일 수 있다.
  • 기본 k-겹 교차 검증 방법보다 좀 더 향상된 방법은 계층적 k-겹 교차 검증(stratified k-fold cross-validation)이다.
    • 이는 좀 더 나은 편향과 분산 추정을 만든다. 특히 론 코하비가 보인 것처럼 클래스 비율이 동등하지 않을 때도 그렇다.
    • 계층적 교차 검증은 각 폴드에서 클래스 비율이 전체 훈련 세트에 있는 클래스 비율을 대표하도록 유지한다.
    • 사이킷런의 StratifiedKFold 반복자를 사용하여 구현할 수 있다.
import numpy as np
from sklearn.model_selection import StratifiedKFold

kfold = StratifiedKFold(n_splits=10, random_state=1).split(X_train, y_train)

scores = []

for k, (train, test) in enumerate(kfold):
   pipe_lr.fit(X_train[train], y_train[train])
   score = pipe_lr.score(X_train[test], y_train[test])
    scores.append(score)
  • 먼저 sklearn.model_selection 모듈에 있는 StratifiedKFold 클래스를 훈련 세트의 y_train 클래스 레이블에 전달하여 초기화한다. 그 후 n_splits 매개변수로 폴드 개수를 지정한다.
    • kfold 반복자를 사용하여 k개의 폴드를 반복하여 얻은 train의 인덱스를 로지스틱 회귀 파이프라인을 훈련하는데 사용할 수 있다.
    • pipe_lr 파이프라인을 사용하므로 각 반복에서 샘플의 스케일이 적절하게 조정된다 (예컨대 표준화를 통해)
    • 그 다음 테스트 인덱스를 사용하여 모델의 정확도 점수를 계산한다.
    • 이 점수를 리스트에 모아서 추정한 정확도의 평균과 표준편차를 계산한다.
  • 사이킷런은 k-겹 교차 검증 함수를 제공하기 때문에 좀 더 간단하게 계층별 k-겹 교차 검증을 사용하여 모델을 평가할 수 있다.
 
from sklearn.model_selection import cross_val_score

scores = cross_val_score(estimator=pipe_lr, X=X_train, y=y_train, cv=10, n_jobs=1)
  • cross_val_score 함수의 아주 유용한 기능은 각 폴드의 평가를 컴퓨터에 있는 복수 개의 CPU 코어에 분산할 수 있다는 점이다.
    • n_jobs 매개변수를 1로 설정하면 하나의 CPU 코어만 성능 평가에 사용되며, 2로 설정하면 2개의 CPU 코어에 교차 검증을 10회씩 분산할 수 있다. -1을 설정하면 컴퓨터에 설치된 모든 CPU 코어를 사용하여 병렬처리를 한다.

학습 곡선과 검증 곡선을 사용한 알고리즘 디버깅

학습 곡선으로 편향과 분산 문제 분석

  • 훈련 데이터셋에 비해 모델이 너무 복잡하면, 즉 모델의 자유도나 모델 파라미터가 너무 많으면 모델이 훈련 데이터에 과대적합되고 처음 본 데이터에 일반화 되지 못하는 경향이 있다.
  • 보통 훈련 샘플을 더 모으면 과대적합을 줄이는데 도움이 되지만, 실전에서는 데이터를 더 모으는 것이 매우 비싸거나 불가능한 경우가 많다.
  • 모델의 훈련 정확도와 검증 정확도를 훈련 세트의 크기 함수로 그래프를 그려보면 모델에 높은 분산의 문제가 있는지 높은 편향의 문제가 있느지 쉽게 감지할 수 있다.

  • 왼쪽 위 그래프는 편향이 높은 모델로서 훈련 정확도와 교차 검증 정확도가 모두 낮다. 이는 훈련 데이터에 과소적합되었다는 것을 나타낸다.
    • 이 문제를 해결하는 일반적인 방법은 모델의 파라미터 개수를 늘리는 것이다.
  • 오른쪽 위 그래프는 분산이 높은 모델을 보여준다. 훈련 정확도와 교차 검증 정확도 사이에 큰 차이가 있다는 것을 나타낸다.
    • 과대적합 문제를 해결하려면 더 많은 훈련 데이터를 모으거나 모델 복잡도를 낮추거나 규제를 증가시킬 수 있다.
  • 사이킷런의 학습 곡선 함수를 사용하면 모델을 평가할 수 있다.
from sklearn.model_selection import learning_curve
import numpy as np
import matplotlib.pyplot as plt

pipe_lr = make_pipeline(StandardScaler(), LogisticRegression(solver='liblinear', penalty='l2', random_state=1))

train_sizes, train_scores, test_scores = learning_curve(estimator=pipe_lr, X=X_train, y=y_train, train_sizes=np.linspace(0.1, 1.0, 10), cv=10, n_jobs=1)

train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

plt.plot(train_sizes, train_mean, color='blue', marker='o', markersize=5, label='training accuracy')
plt.fill_between(train_sizes, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue')
plt.plot(train_sizes, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy')
plt.fill_between(train_sizes, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green')
plt.grid()
plt.xlabel('Number of training samples')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')
plt.ylim([0.8, 1.03])
plt.tight_layout()
plt.show()

  • learning_curve 함수의 train_sizes 매개변수를 통해 학습 곡선을 생성하는데 사용할 훈련 샘플의 개수나 비율을 지정할 수 있다.
    • 기본적으로 learning_curve 함수는 계층별 k-겹 교차 검증을 사용하여 분류기의 교차 검증 정확도를 계산한다.
    • cv 매개변수를 통해 k 값을 10으로 지정했기 때문에 계층별 10-겹 교차 검증을 사용한다.

검증 곡선으로 과대적합과 과소적합 조사

  • 검증 곡선은 과대적합과 과소적합 문제를 해결하여 모델 성능을 높일 수 있는 유용한 도구이다.
    • 사이킷런의 validation_curve를 이용해서 검증곡선을 만들 수 있다.
from sklearn.model_selection import validation_curve
import numpy as np
import matplotlib.pyplot as plt

param_range = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]

train_scores, test_scores = validation_curve(estimator=pipe_lr, X=X_train, y=y_train, param_name='logisticregression__C', param_range=param_range, cv=10)

train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

plt.plot(param_range, train_mean, color='blue', marker='o', markersize=5, label='training accuracy')
plt.fill_between(param_range, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue')
plt.plot(param_range, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy')
plt.fill_between(param_range, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green')
plt.grid()
plt.xscale('log')
plt.legend(loc='lower right')
plt.xlabel('Parameter C')
plt.ylabel('Accuracy')
plt.ylim([0.8, 1.0])
plt.tight_layout()
plt.show()

  • learning_curve와 유사하게 validation_curve도 기본적으로 계층별 k-겹 교차 검증을 사용하여 모델의 성능을 추정한다.
    • validation_curve 함수 안에서 평가하기 원하는 매개변수를 지정한다. 위 경우에는 LogisticRegression 분류기의 규제 매개변수인 C이다.
    • param_range 매개변수에는 값 범위를 지정한다.
    • C 값이 바뀜에 따라 정확도 차이가 미묘하지만 규제 강도를 높이면 (C값을 줄이면) 모델이 데이터에 조금 과소적합 되는 것을 볼 수 있다. 규제 강도가 낮아지는 큰 C 값에서는 모델이 데이터에 조금 과대적합 되는 경향을 보인다.

그리드 서치를 사용한 머신 로닝 모델 세부 튜닝

  • 머신 러닝에는 두 종류의 파라미터가 있는데, 하나는 훈련 데이터에 학습되는 파라미터로 로지스틱 회귀의 가중치가 그 예이다.
    • 다른 하나는 별도로 최적화 되는 학습 알고리즘의 파라미터로 튜닝 파라미터고, 하이퍼파라미터라고도 부른다. 예컨대 로지스틱 회귀의 규제 매개변수나 결정 트리의 깊이 매개변수이다.
  • 이전 절에서는 검증 곡선을 사용하여 하이퍼파라미터를 튜닝하여 모델 성능을 향상시켰다면 이번 절에서는 그리드 서치라는 인기 있는 하이퍼파라미터 최적화 기법을 살펴보겠다.

그리드 서치를 사용한 하이퍼파라미터 튜닝

  • 그리드 서치가 사용하는 방법은 아주 간단한데, 리스트로 지정된 여러 가지 하이퍼파라미터 값 전체를 모두 조사한다.
    • 이 리스트에 있는 값의 모든 조합에 대해 모델 성능을 평가하여 최적의 조합을 찾는다.
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC

pipe_svc = make_pipeline(StandardScaler(), SVC(random_state=1))

param_range = [0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0]

param_grid = [{'svc__C': param_range, 'svc__kernel':['linear']}, {'svc__C': param_range, 'svc__gamma':param_range, 'svc__kernel':['rbf']}]

gs = GridSearchCV(estimator=pipe_svc, param_grid=param_grid, scoring='accuracy', cv=10, n_jobs=1)
gs = gs.fit(X_train, y_train)

clf = gs.best_estimator_
clf.fit(X_train, y_train)
  • 위 코드는 sklearn.model_selection 모듈에 있는 GridSearchCV 클래스의 객체를 만들고 SVM을 위한 파이프라인을 훈련하고 튜닝한다.
  • 훈련 세트를 사용하여 그리드 서치를 수행한 후 최상의 모델 점수는 best_score_ 속성에서, 이 모델의 매개변수는 betst_params_ 속성에서 확인할 수 있다.
  • 독립적인 테스트 세트를 사용하여 최고 모델의 성능을 추정할 수 있는데 이 모델은 GridSearchCV 객체의 best_estimator_ 속성에서 얻을 수 있다.

중첩 교차 검증을 사용한 알고리즘 선택

  • 그리드 서치와 k-겹 교차 검증을 함께 사용하면 머신 러닝 모델의 성능을 세부 튜닝하기 좋다.
  • 여러 종류의 머신 러닝 알고리즘을 비교하려면 중첩 교차 검증(nested cross-validation) 방법이 권장된다.
    • 오차 예측에 대한 편향을 연구하는 중에 바르마(Varma)와 사이몬(Simon)은 중첩된 교차 검증을 사용했을 때 테스트 세트에 대한 추정 오차는 거의 편향되지 않는다는 결론을 얻었다.
  • 중첩 교차 검증은 바깥쪽 k-겹 교차 검증 루프가 데이터를 훈련 폴드와 테스트 폴드로 나누고 안쪽 루프가 훈련 폴드에서 k-겹 교차 검증을 수행하여 모델을 선택한다.
    • 모델이 선택되면 테스트 폴드를 사용하여 모델 성능을 평가한다.
    • 아래 그림은 바깥 루프에 다섯 개의 폴드를 사용하고 안쪽 루프에 두 개의 폴드를 사용하는 중첩 교차 검증의 개념을 보여준다.
    • 이런 방식은 계산 성능이 중요한 대용량 데이터셋에서 유용하다.
    • 중첩 교차 검증의 폴드 개수를 고려하여 5 x 2 교차 검증이라고도 한다.

  • 사이킷런에서는 다음과 같이 중첩 교차 검증을 수행할 수 있다.
gs = GridSearchCV(estimator=pipe_svc, param_grid=param_grid, scoring='accuracy', cv=2)

scores = cross_val_score(gs, X_train, y_train, scoring='accuracy', cv=5)
  • 반면 평균 교차 검증 점수는 모델의 하이퍼파라미터를 튜닝했을 때 처음 본 데이터에서 기대할 수 있는 추정값이 된다.
    • 예컨대 중첩 교차 검증을 사용하여 SVM 모델과 단일 결정 트리 분류기를 비교할 수 있다.

여러 가지 성능 평가 지표

  • 이전 절에서는 정확도를 사용하여 모델을 평가했다. 이 지표는 일반적으로 분류 모델의 성능을 정량화 하는데 유용하다.
  • 주어진 문제에 모델이 적합한지 측정할 수 있는 다른 성능 지표도 여럿 있는데, 정밀도(precision), 재현율(recall), F1-Score 이다.

오차 행렬

  • 학습 알고리즘의 성능을 행렬로 펼쳐 놓은 오차 행렬(confusion matrix)를 을 살펴보자.
    • 오차 행렬은 아래 그림과 같이 진짜 양성(True Positive, TP), 진짜 음성(True Negative, TN), 거짓 양성(False Positive, FP), 거짓 음성(False Negative, FN)의 개수를 적은 정방 행렬이다.

  • 이 행렬은 타깃 클래스와 예측 클래스의 레이블을 직접 세어 계산할 수 있지만 사이킷런에서 제공하는 편리한 confusion_matrix 함수를 사용할 수 있다.
from sklearn.metrics import confusion_matrix

pipe_svc.fit(X_train, y_train)

y_pred = pipe_svc.predict(X_test)

confmat = confusion_matrix(y_true=y_test, y_pred=y_pred)

fig, ax = plt.subplots(figsize=(2.5,2.5))

ax.matshow(confmat, cmap=plt.cm.Blues, alpha=0.3)

for i in range(confmat.shape[0]):
   for j in range(confmat.shape[1]):
        ax.text(x=j, y=i, s=confmat[i, j], va='center', ha='center')

plt.xlabel('predicted label')
plt.ylabel('true label')
plt.tight_layout()
plt.show()

분류 모델의 정밀도와 재현율 최적화

  • 예측 오차(ERR)와 정확도(AcC) 모두 얼마나 많은 샘플을 잘못 분류했는지 일반적인 정보를 알려준다.
    • 오차는 잘못된 예측의 합을 전체 예측 샘플 개수로 나눈 것이다.
    • 정확도는 옳은 예측의 합을 전체 예측 샘플 개수로 나누어 계산한다.

ERR = {FP + FN \over FP + FN + TP + TN}

  • 예측 정확도는 오차에서 바로 계산할 수 있다.

ACC = {TP + TN \over FP + FN + TP + TN} = 1 - ERR

  • 진짜 양성 비율 (True Positive Rate, TPR)과 거짓 양성 비율 (False Positive Rate, FPR)은 클래스 비율이 다른 경우 유용한 성능 지표이다.

FPR = {FP \over N} = {FP \over FP + TN}

TPR = {TP \over P} = {TP \over FN + TP}

  • 정확도(PRE)와 재현율(REC) 성능 지표는 진짜 양성과 진짜 음성 샘플의 비율과 관련이 있다. 사실 재현율은 TPR의 다른 이름이다.

PRE = {TP \over TP + FP}

REC = TPR = {TP \over P} = {TP \over FN + TP}

  • 실전에서는 PRE와 REC를 조합한 F1-Score를 자주 사용한다.

F1 = 2 \times {PRE \times REC \over PRE + REC}

  • (위 식에서 2배를 해주는 까닭은 2배를 안 하면 점수가 0-0.5 사이의 값이 되기 때문. 보기 좋게 0-1의 값을 만들어주기 위해 2배를 하는 것이다)
  • 이런 성능 지표들은 모두 사이킷런에 구현되어 있다.
from sklearn.metrics import precision_score, recall_score, f1_score

print ('정밀도: %.3f' % precision_score(y_true=y_test, y_pred=y_pred))
print ('재현율: %.3f' % recall_score(y_true=y_test, y_pred=y_pred))
print ('F1: %.3f' % f1_score(y_true=y_test, y_pred=y_pred))
  • GridSearchCV의 scoring 매개변수를 사용하여 정확도 대신 다른 성능 지표를 사용할 수 도 있다.
  • 사이킷런에서 양성 클래스는 레이블이 1인 클래스이다.
    • 양성 레이블을 바꾸고 싶다면 make_scorer 함수를 사용하여 자신만의 함수를 만들 수 있다.
    • (예시 생략)

ROC 곡선 그리기

  • ROC(Receiver Operating Characteristic) 그래프는 분류기의 임계 값을 바꾸어 가며 계산된 FPR과 TPR 점수를 기반으로 분류 모델을 선택하는 유용한 도구이다.
    • ROC 그래프의 대각선은 랜덤 추측으로 해석할 수 있고 대각선 아래에 위치한 분류 모델은 랜덤 추측 보다 나쁜 셈이다.
    • 완벽한 분류기의 그래프는 TPR이 1이고 FPR이 0인 왼쪽 위 구석에 위치한다.
    • ROC 곡선의 아래 면적인 ROC AUC(ROC Area Under the Curve)를 계산하여 분류 모델의 성능을 종합할 수 있다.
  • ROC 곡선과 비슷하게 분류 모델의 확률 임계값을 바꾸어가며 정밀도-재현율 곡선을 그릴 수 있다.
    • 정밀도-재현율 곡선을 그리는 함수도 사이킷런에 구현되어 있다.
from sklearn.metrics import roc_curve, auc
from scipy import interp

pipe_lr = make_pipeline(StandardScaler(), PCA(n_components=2), LogisticRegression(solver='liblinear', penalty='l2', random_state=1, C=100.0))

X_train2 = X_train[:, [4, 14]]

cv = list(StratifiedKFold(n_splits=3, random_state=1).split(X_train, y_train))

fig = plt.figure(figsize=(7,5))

mean_tpr = 0.0
mean_fpr = np.linspace(0, 1, 100)

all_tpr = []

for i, (train, test) in enumerate(cv):
    probas = pipe_lr.fit(X_train2[train], y_train[train]).predict_proba(X_train2[test])
    fpr, tpr, thresholds = roc_curve(y_train[test], probas[:, 1], pos_label=1)
   mean_tpr += interp(mean_fpr, fpr, tpr)
    mean_tpr[0] = 0.0
   roc_auc = auc(fpr, tpr)
    plt.plot(fpr, tpr, label='ROC fold %d (area=%0.2f)' % (i+1, roc_auc))

plt.plot([0,1], [0,1], linestyle='--', color=(0.6,0.6,0.6), label='random guessing')

mean_tpr /= len(cv)
mean_tpr[-1] = 1.0
mean_auc = auc(mean_fpr, mean_tpr)

plt.plot(mean_fpr, mean_tpr, 'k--', label='mean ROC (area = %0.2f)' % mean_auc, lw=2)
plt.plot([0, 0, 1], [0, 1, 1], linestyle=':', color='black', label='perfect performance')
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.legend(loc='lower right')
plt.xlabel('false positive rate')
plt.ylabel('true positive rate')
plt.tight_layout()
plt.show()

  • ROC AUC 점쉥만 관심 있다면 sklearn.metrics 모듈의 roc_auc_score 함수를 사용할 수도 있다.
    • ROC AUC로 분류 모델의 성능을 조사하면 불균형한 데이터셋에서 분류기의 성능에 대해 더 많은 통찰을 얻을 수 있다.
    • 정확도를 ROC 곡선 하나의 구분점으로 해석할 수 있지만 브래들리(A. P. Bradley)는 ROC AUC와 정확도가 대부분 서로 비례한다는 것을 보였다.

다중 분류의 성능 지표

  • 이 절에서 언급한 성능 지표는 이진 분류에 대한 것이다. 사이킷런은 이런 평균 지표에 매크로(Macro)와 마이크로(Micro) 평균 방식을 구현하여 OvA(One-Versus-All) 방식을 사용하는 다중 분류로 확장한다.
    • 마이크로 평균은 클래스별로 TP, TN, FP, FN을 계산한다.
    • 예컨대 k개의 클래스가 있는 경우 정밀도의 마이크로 평균은 다음과 같이 계산한다.

PRE_{micro} = {TP_{1} + TP_{2} + ... + TP_{k} \over TP_{1} + TP_{2} + ... + TP_{k} + FP_{1} + FP_{2} + ... + FP_{k}}

  • 매크로 평균은 단순하게 클래스별 정밀도의 평균이다.

PRE_{macro} = {PRE_{1} + PRE_{2} + ... + PRE_{k} \over k}

  • 마이크로 평균은 각 샘플이나 예측에 동일한 가중치를 부여하고자 할 때 사용하고, 매크로 평균은 모든 클래스에 동일한 가중치를 부여하여 분류기의 전반적인 성능을 평가한다. 이 방식에서는 가장 빈도 높은 클래스 레이블의 성능이 중요하다.
  • 사이킷런에서 이진 성능 지표로 다중 분류 모델을 평가하면 정규화 또는 가중치가 적용된 매크로 평균이 기본으로 적용된다.
    • 가중치가 적용된 매크로 평균은 평균을 계산할 때 각 클래스 레이블의 샘플 개수를 가중하여 계산한다.
    • 가중치 적용된 매크로 평균은 레이블마다 샘플 개수가 다른 불균형한 클래스를 다룰 때 유용하다.
  • 사이킷런에서 가중치가 적용된 매크로 평균이 다중 분류 문제에서 기본값이지만 sklearn.metrics 모듈 아래에 있는 측정함수들은 average 매개변수로 평균 계산 방식을 지정할 수 있다.
    • 예컨대 precision_score나 make_scorer 함수이다.

불균형한 클래스 다루기

  • 클래스 불균형은 실전 데이터를 다룰 때 자주 나타나는 문제로 한개 또는 여러 개의 클래스 샘플이 데이터셋에 너무 많을 때 등장한다.
  • 이 장에서 사용한 유방암 데이터셋이 90%는 건강한 환자라고 가정할 때, 지도 학습 알고리즘을 사용하지 않고 모든 샘플에 대해 다수의 클래스(양성 종양)를 예측하기만 해도 테스트 세트에서 90% 정확도를 달성할 수 있다.
  • 불균형한 데이터셋을 다룰 때 도움이 되는 몇 가지 기법을 알아보겠다.
  • 우선 불균형한 데이터셋을 만들어 보자.
X_imb = np.vstack((X[y == 0], X[y == 1][:40]))
y_imb = np.hstack((y[y == 0], y[y == 1][:40]))

y_pred = np.zeros(y_imb.shape[0])
np.mean(y_pred == y_imb) * 100
  • 이런 데이터셋에 분류 모델을 훈련할 때 모델을 비교하기 위해 정확도를 사용하는 것보다 다른 지표를 사용하는 것이 낫다.
    • 애플리케이션에서 주요 관심 대상이 무엇인지에 따라 정밀도, 재현율, ROC 곡선 등을 사용할 수 있다.
  • 머신 로닝 모델을 평가하는 것과 별개로 클래스 불균형은 모델이 훈련되는 동안 학습 알고리즘 자체에 영향을 미친다.
    • 머신 러닝 알고리즘이 일반적으로 훈련하는 동안 처리한 샘플에서 계산한 보상 또는 비용 함수의 합을 최적화 한다.
    • 결정 규칙은 다수 클래스 쪽으로 편향되기 쉽다.
    • 다른 말로 하면 알고리즘이 훈련 과정에서 비용을 최소화하거나 보상을 최대화하기 위해 데이터셋에서 가장 빈도가 높은 클래스의 예측을 최적화하는 모델을 학습한다.
  • 모델을 훈련하는 동안 불균형한 클래스를 다루는 한 가지 방법은 소수 클래스에서 발생한 예측 오류에 큰 벌칙을 부여하는 것이다.
    • 사이킷런에서 대부분의 분류기에 구현된 class_weight 매개변수를 class_weight=’balanced’로 설정해서 이런 벌칙을 편리하게 조정할 수 있다.
  • 불균형한 클래스를 다루는데 널리 사용되는 다른 전략은 소수 클래스의 샘플을 늘리거나 다수 클래스의 샘플을 줄이거나 인공적으로 훈련 샘플을 생성하는 것이다.
    • 아쉽지만 여러 도메인에 걸쳐 가장 잘 작동하는 보편적인 솔루션이나 기법은 없기 때문에 실전에서는 주어진 문제에 여러 전략을 시도해서 결과를 평가하고 가장 적절한 기법을 선택하는 것이 좋다.
  • 사이킷런 라이브러리에서는 데이터셋에서 중복을 허용한 샘플 추출 방식으로 소수 클래스의 샘플을 늘리는데 사용할 수 있는 resample 함수를 제공한다.
    • 다음 코드는 불균형한 유방암 데이터셋에서 소수 클래스를 선택하여 클래스 레이블이 0인 샘플 개수와 동일할 때까지 새로운 샘플을 반복적으로 추출한다.
from sklearn.utils import resample

X_upsampled, y_upsampled = resample(X_imb[y_imb==1], y_imb[y_imb==1], replace=True, n_samples=X_imb[y_imb==0].shape[0], random_state=123)
  • 샘플을 추출한 후 클래스 0인 원본 샘플과 업샘플링된 클래스 1을 연결하여 균형 잡힌 데이터셋을 얻을 수 있다.
  • 비슷하게 데이터셋에서 다수 클래스의 훈련 샘플을 삭제하여 다운샘플링(downsampling)을 할 수 있다. resample 함수를 사용하여 다운샘플링을 수행하려면 클래스 레이블 1과 0을 서로 바꾸면 된다.

머신 러닝 교과서/ 차원 축소를 사용한 데이터 압축

주성분 분석을 통한 비지도 차원 축소

  • 특성 선택과 마찬가지로 여러 특성 추출 기법을 사용하여 데이터셋의 특성 개수를 줄일 수 있다.
    • 특성 선택과 특성 추출의 차이는 원본 특성을 유지하느냐에 있다.
    • 순차 후진 선택 같은 특성 선택 알고리즘을 사용할 때는 원본 특성을 유지하지만, 특성 추출은 새로운 특성 공간으로 데이터를 변환하거나 투영한다.
    • 차원 축소 관점에서 보면 특성 추출은 대부분의 관련 있는 정보를 유지하면서 데이터를 압축하는 방법으로 이해할 수 있다.
  • 특성 추출이 저장 공간을 절약하거나 학습 알고리즘의 계산 효율성을 향상할 뿐만 아니라 차원의 저주 문제를 감소시켜 예측 성능을 향상하기도 한다. 특히 규제가 없는 모델로 작업할 때 그렇다.

주성분 분석의 주요 단계

  • PCA는 비지도 선형 변환 기법으로 주로 특성 추출과 차원 축소 용도로 많은 분야에서 널리 사용된다.
  • PCA는 특성 사이의 상관관계를 기반으로 하여 데이터에 있는 특성을 잡아낼 수 있다.
    • 요약하자면 PCA는 고차원 데이터에서 분산이 가장 큰 방향을 찾고 좀 더 작거나 같은 수의 차원을 갖는 새로운 부분 공간으로 이를 투영한다.
    • 새로운 부분 공간의 직교 좌표는 주어진 조건 하에서 분산이 최대인 방향으로 해석할 수 있다.
    • 새로운 특성 축은 아래 그림과 같이 서로 직각을 이룬다. 아래 그림에서 x_{1}, x_{2} 는 원본 특성 축이고 PC1, PC2 는 주성분이다.

  • PCA를 사용하여 차원을 축소하기 위해 d \times k 차원의 변환행렬 W 를 만든다.
    • 이 행렬로 샘플 벡터 x 를 새로운 k 차원의 특성 부분 공간으로 매핑한다.
    • 이 부분 공간은 원본 d 차원의 특성 공간보다 작은 차원을 가진다.

x = [x_{1}, x_{2}, ... , x_{d}], x \in \mathbb{R}^{d}

\downarrow xW, W \in \mathbb{R}^{d \times k}

z = [z_{1}, z_{2}, ... , z_{k}], z \in \mathbb{R}^{k}

  • 원본 d 차원 데이터를 새로운 k 차원의 부분 공간(일반적으로 k < d )으로 변환하여 만들어진 첫 번째 주성분이 가장 큰 분산을 가질 것이다.
    • 모든 주성분은 다른 주성분들과 상관관계가 있더라도 만들어진 주성분은 서로 직각을 이룰 것이다.
    • PCA 방향은 데이터 스케일에 매우 민감하다.
    • 특성의 스케일이 다르고 모든 특성의 중요도를 동일하게 취급하려면 PCA를 적용하기 전에 특성을 표준화 전처리해야 한다.
  • 차원 축소 PCA 알고리즘의 단계는 다음과 같다.
    1. d 차원 데이터셋을 표준화 전처리한다.
    2. 공분산 행렬(covariance matrix)을 만든다.
    3. 공분산 행렬을 고유 벡터(eigenvector)와 고윳값(eigenvalue)으로 분해한다.
    4. 고윳값을 내림차순으로 정렬하고 그에 해당하는 고유 벡터의 순위를 매긴다.
    5. 고윳값이 가장 큰 k 개의 고유 벡터를 선택한다. 여기서 k 는 새로운 특성 부분 공간의 차원이다. (k \leq d )
    6. 최상위 k 개의 고유벡터로 투영행렬(projection matrix) W 를 만든다.
    7. 투영 행렬 W 를 사용해서 d 차원 입력 데이터셋 X 를 새로운 k 차원의 특성 부분 공간으로 변환한다.

주성분 추출단계

  • 우선 PCA 처음 4단계를 처리한다.
    1. 데이터를 표준화 전처리 한다.
    2. 공분산 행렬을 구성한다.
    3. 공분산 행렬의 고윳값과 고유벡터를 구한다.
    4. 고윳값을 내림차순으로 정렬하여 고유 벡터의 순위를 매긴다.
  • Wine 데이터셋을 가져와서 70:30 비율로 훈련 세트와 테스트 세트를 나누고 표준화를 적용하여 단위 분산을 갖도록 한다.
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)

X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:,0].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)

sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.transform(X_test)
  • 그 다음으로 공분산 행렬을 만드는 단계를 진행한다. 공분산 행렬은 d \times d 차원의 대칭 행렬로 특성 상호 간의 공분산을 저장한다. d 는 데이터셋에 있는 차원 개수이다.
    • 예컨대 전체 샘플에 대한 두 특성 x_{j} x_{k} 사이의 공분산은 다음 식으로 계산할 수 있다.

\sigma_{jk} = {1 \over n} \sum_{i = 1}^{n} (x_{j}^{(i)} - \mu_{j})(x_{k}^{(i)} - \mu_{k})

  • 여기서 \mu_{j} \mu_{k} 는 특성 j k 의 샘플 평균이다.
  • 데이터셋을 표준화 전처리했기 때문에 샘플 평균은 0이다.
  • 두 특성 간 양의 공분산은 특성이 함께 증가하거나 감소하는 것을 나타낸다. 반면 음의 공분산은 특성이 반대 방향으로 달라진다는 것을 나타낸다.
    • 예컨대 세 개의 특성으로 이루어진 공분산 행렬은 다음과 같이 쓸 수 있다. (여기서 \Sigma 는 대문자 표기일 뿐 합을 뜻하는 것이 아니므로 주의)

\Sigma = \left[ \begin{array}{rrr} \sigma_{1}^{2} & \sigma_{12} & \sigma_{13} \\ \sigma_{21} & \sigma_{2}^{2} & \sigma_{23} \\ \sigma_{31} & \sigma_{32} & \sigma_{3}^{2} \end{array} \right]

  • 공분산 행렬의 고유 벡터가 주성분(최대 분산의 방향)을 표현한다. 이에 대응되는 고윳값은 주성분의 크기이다.
    • Wine 데이터셋의 경우 13 x 13 차원의 공분산 행렬로부터 13개의 고유벡터와 고윳값을 얻을 수 있다.
  • 다음 단계로 공분산 행렬의 고유벡터와 고윳값의 쌍을 구해 보자.
    • 선형대수학에 따르면 고유벡터 v 는 다음 식을 만족한다.

\Sigma v = \lambda v

  • 여기서 \lambda 는 스케일을 담당하는 고윳값이다. 고윳벡터와 고윳값을 직접 계산하는 것은 복잡한 작업이기 때문에 Numpy의 linalg.eig 함수를 이용하여 계산한다.
import numpy as np

cov_mat = np.cov(X_train_std.T)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)
  • Note) numpy.linalg.eig 함수는 대칭과 비대칭 정방 행렬을 모두 다룰 수 있지만 이따금 복소수 고윳값을 반환한다. 이와 비슷하게 에르미트 행렬을 분해하기 위해 구현된 numpy.linalg.eigh 함수는 공분산 행렬과 같은 대칭 행렬을 다룰 때 수치적으로 더 안정된 결과를 만든다. (numpy.linalg.eigh는 항상 실수 고윳값을 반환한다.)
  • Note) 사이킷런의 PCA 클래스는 직접 고윳값과 고유 벡터를 계산하는 대신 특이값 분해 (singular value decomposition) 방식을 이용하여 주성분을 구한다.

총분산과 설명된 분산

  • 데이터셋 차원을 새로운 특성 부분 공간으로 압축하여 줄여야 하기에 가장 많은 정보(분산)를 가진 고유 벡터(주성분) 일부만 선택한다.
    • 고윳값은 고유 벡터의 크기를 결정하므로 고윳값을 내림차순으로 정렬한다.
    • 고윳값 순서에 따라 최상위 k 개의 고유벡터를 선택한다.
  • 가장 정보가 많은 k 개의 고유 벡터를 선택하기 전에 고윳값의 설명된 분산(explained variance) 그래프로 그려보겠다.
    • 고윳값 \lambda_{j} 의 설명된 분산 비율은 전체 고윳값의 합에서 고윳값 \lambda_{j} 의 비율이다.

{\lambda_{j} \over \sum_{d}^{j=1} \lambda_{j}}

  • numpy의 cumsum 함수로 설명된 분산의 누적 합을 계산하고 matplotlib의 step 함수로 그래프를 그리면 다음과 같다.
import matplotlib.pyplot as plt

tot = sum(eigen_vals)

var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)]

cum_var_exp = np.cumsum(var_exp)

plt.bar(range(1, 14), var_exp, alpha=0.5, align='center', label='individual explained variance')
plt.step(range(1, 14), cum_var_exp, where='mid', label='cumulative explained variance')
plt.ylabel('Explained variacne ratio')
plt.xlabel('Principal component index')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

  • 랜덤 포레스트는 클래스 소속 정보를 사용하여 노드의 불순도를 계산하는 방면, 분산은 특성 축을 따라 값들이 퍼진 정도를 측정한다.

특성 변환

  • 공분산 행렬을 고유 벡터와 고윳값 쌍으로 성공적으로 분해한 후 Wine 데이터 셋을 새로운 주성분 축으로 변환하는 3개의 단계는 다음과 같다.
    • 고윳값이 가장 큰 k 개의 고유 벡터를 선택한다. 여기서 k 는 새로운 특성 부분 공간의 차원이다 (k \leq d )
    • 최상위 k 개의 고유벡터로 투영 행렬 W 를 만든다.
    • 투영행렬 W 를 사용해서 d 차원 입력 데이터셋 X 를 새로운 k 차원의 특성 부분 공간으로 변환한다.
  • 좀 더 쉽게 설명하면 고윳값의 내림차순으로 고유 벡터를 정렬하고 선택된 고유 벡터로 투영 행렬을 구성한다. 이 투영 행렬을 사용하여 데이터를 저차원 부분 공간으로 변환한다.
eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i]) for i in range(len(eigen_vals))]
eigen_pairs.sort(key=lambda k: k[0], reverse=True)
  • 앞의 코드를 이용하여 최상위 두 개의 고유 벡터로부터 13 x  2 차원의 투영행렬 W 를 만든다.
  • 투영 행렬을 사용하면 샘플 x (1 x 13 차원의 행 벡터)를 PCA 부분 공간(두 개의 주성분)을 투영하여 x' 를 얻을 수 있다. 두 개의 특성으로 구성된 2차원 샘플 벡터이다.

x' = xW

w = np.hstack((eigen_pairs[0][1][:, np.newaxis], eigen_pairs[1][1][:, np.newaxis]))
  • 비슷하게 124 x 13 차원의 훈련 데이터셋을 행렬 내적으로 두 개의 주성분으로 변환할 수 있다.

X' = XW

X_train_pca = X_train_std.dot(w)
  • 124 x 2 차원 행렬로 변환된 Wine 훈련 세트를 2차원 산점도로 시각화 하면 아래와 같다.
colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']

for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train==l, 0], X_train_pca[y_train==l, 1], c=c, label=l, marker=m)

plt.ylabel('PC 1')
plt.xlabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()

  • 위 이미지에서 볼 수 있듯이 데이터가 y축 보다 x 축을 따라 더 넓게 퍼져 있다. 이는 이전에 만든 설명된 분산의 그래프와 동일한 결과이다.

사이킷런의 주성분 분석

  • 다음의 코드를 실행하면 두 개의 주성분 축으로 줄어든 훈련 데이터로 만든 결정 경계를 볼 수 있다.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

def plot_decision_regions(X, y, classifier, resolution=0.02):
    # 마커와 컬러맵을 설정
   markers = ('s', 'x', 'o', '^', 'v')
   colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # 결정 경계를 그린다.
   x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
   x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
   xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
   Z = Z.reshape(xx1.shape)

   plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
   plt.ylim(xx2.min(), xx2.max())   

    # 샘플의 산점도를 그린다.
   for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha = 0.6, c = cmap.colors[idx], edgecolor='black', marker = markers[idx], label = cl)

from sklearn.linear_model import LogisticRegression
from sklearn.decomposition import PCA

pca = PCA(n_components=2)

X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)

lr = LogisticRegression(solver='liblinear', multi_class='auto')
lr.fit(X_train_pca, y_train)

plot_decision_regions(X_train_pca, y_train, classifier=lr)

plt.ylabel('PC 1')
plt.xlabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()

  • 테스트 세트에 적용한 결과는 아래와 같다.

선형 판별 분석을 통한 지도 방식의 데이터 압축

  • 선형 판별 분석(Linear Discriminant Analysis, LDA)은 규제가 없는 모델에서 차원의 저주로 인한 과대적합 정도를 줄이고 계산 효율성을 높이기 위한 특성 추추르이 기법으로 사용할 수 있다.
  • LDA 이면에 있는 일반적인 개념은 PCA와 매우 비슷하다. PCA가 데이터셋에 있는 분산이 최대인 직교 성분 축을 찾으려고 하는 반면, LDA 목표는 클래스를 최적으로 구분할 수 있는 특성 부분 공간을 찾는 것이다.

주성분 분석 vs 선형 판별 분석

  • PCA와 LDA 모두 데이터셋의 차원 개수를 줄일 수 있는 선형 변환 기법이다. 전자는 비지도 학습 알고리즘이지만, 후자는 지도 학습 알고리즘이다.
    • 직관적으로 LDA가 PCA 보다 분류 작업에서 더 뛰어난 특성 추출 기법이라고 생각할 수 있다.
    • 마르티네스는 PCA를 통한 전처리가 특정 이미지 인식 작업에 더 뛰어난 분류 결과를 내는 경향이 있다고 보고 했다. 예컨대 각 클래스에 속한 샘플이 몇 개 되지 않았을 때이다.
  • 다음 그림은 이진 분류 문제를 위한 LDA 개념을 요약하여 나타낸다. 클래스1의 샘플은 동그라미고, 클래스 2의 샘플은 덧셈 기호이다.

  • x축(LD 1)으로 투영하는 선형 판별 벡터는 두 개의 정규 분포 클래스를 잘 구분한다. y 축(LD 2)으로 투영하는 선형 판별 벡터는 데이터셋에 있는 분산을 많이 잡아내지만, 클래스 판별 정보가 없기 떄문에 좋은 선형 판별 벡터가 되지 못한다.
  • LDA는 데이터가 정규분포라고 가정한다. 또 클래스가 동일한 공분산 행렬을 가지고 샘플은 서로 통계적으로 독립적이라고 가정한다.
    • 하나 이상의 가정이 조금 위반되더라도 여전히 LDA는 차원 축소를 상당히 잘 수행한다.

선형 판별 분석의 내부 동작 방식

  • LDA 수행에 필요한 단계는 다음과 같다.
    1. d 차원의 데이터셋을 표준화 전처리한다 (d 는 특성 개수)
    2. 각 클래스에 대해 d 차원의 평균 벡터를 계산한다.
    3. 클래스 간의 산포 행렬(scatter matrix) S_{B} 와 클래스 내 산포 행렬 S_{W} 를 구성한다.
    4. S_{W}^{-1} S_{B} 행렬의 고유 벡터와 고윳값을 계산한다.
    5. 고윳값을 내림차순으로 정렬하여 고유 벡터의 순서를 매긴다.
    6. 고윳값이 가장 큰 k 개의 고유 벡터를 선택하여 d \times k 차원의 변환 행렬 W 를 구성한다. 이 행렬의 열이 고유벡터이다.
    7. 변환 행렬 W 를 사용하여 새로운 특성 부분 공간으로 투영한다.
  • 여기서 볼 수 있듯 LDA는 행렬을 고윳값과 고유 벡터로 분해하여 새로운 저차원 특성 공간을 구성한다는 점에서 PCA와 매우 닮았다.

산포 행렬 계산

  • PCA에서 했기 때문에 데이터셋의 특성을 표준화하는 단계는 건너뛰고 바로 평균 벡터 계산을 진행한다.
    • 평균 벡터를 사용하여 클래스 간의 산포 행렬과 클래스 내 산포 행렬을 구성한다.
    • 평균 벡터 m_{i} 는 클래스 i 의 샘플에 대한 특성의 평균값 \mu_{m} 을 저장한다.

m_{i} = {1 \ over n_{i}} \sum_{x \in D_{i}}^{c} x_{m}

  • 3개의 평균 벡터가 만들어진다.

m_{i} = \left[ \begin{array}{rrrr} \mu_{i, alcohol} \\ \mu_{i, malic acid} \\ ... \\ \mu_{i, proline} \end{array} \right] i \in \{1 , 2, 3 \}

np.set_printoptions(precision=4)
mean_vecs = []

for label in range(1, 4):
   mean_vecs.append(np.mean(X_train_std[y_train==label], axis=0))
  • 평균 벡터를 사용하여 클래스 내 산포 팽렬 S_{W} 를 계산할 수 있다.

S_{W} = \sum_{i=1}^{c} S_{i}

  • 이 행렬은 개별 클래스 i 의 산포 행렬 S_{i} 를 더하여 구한다.

S_{i} = \sum_{x \in D_{i}}^{c} (x - m_{i})(x - m_{i})^{T}

d = 13
S_W = np.zeros((d,d))

for label, mv in zip(range(1, 4), mean_vecs):
    class_scatter = np.zeros((d, d))

   for row in X_train_std[y_train == label]:
       row, mv = row.reshape(d, 1), mv.reshape(d, 1)
       class_scatter += (row - mv).dot((row - mv).T)   

    S_W += class_scatter
  • 개별 산포 행렬 S_{i} 를 산포 행렬 S_{W} 로 모두 더하기 전에 스케일 조정을 해야 한다.
    • 산포 행렬을 클래스 샘플 개수 \eta_{i} 로 나누면 사실 산포 행렬을 계산하는 것이 공분산 행렬 \Sigma_{i} 를 계산하는 것과 같아진다.
    • 즉 공분산 행렬은 산포 행렬의 정규화 버전이다.

\Sigma_{i} = {1 \over n_{i}} S_{i} = {1 \over n_{i}} \sum_{x \in D_{i}}^{c} (x - m_{i})(x - m_{i})^{T}

d = 13
S_W = np.zeros((d,d))

for label, mv in zip(range(1, 4), mean_vecs):
    class_scatter = np.cov(X_train_std[y_train==label].T, bias=True)
    S_W += class_scatter
  • 클래스 내 산포 행렬(또는 공분산 행렬)을 계산한 후 다음 단계로 넘어가 클래스 간의 산포 행렬 S_{B} 를 계산한다.

S_{B} = \sum_{i = 1}^{c} n_{i} (m_{i} - m)(m_{i} - m)^{T}

  • 여기서 m 은 모든 클래스의 샘플을 포함하여 계산된 전체 평균이다.
mean_overall = np.mean(X_train_std, axis=0)
mean_overall = mean_overall.reshape(d, 1)
d = 13
S_B = np.zeros((d, d))

for i, mean_vec in enumerate(mean_vecs):
   n = X_train[y_train == i +1, :].shape[0]
    mean_vec = mean_vec.reshape(d, 1)
    S_B += n * (mean_vec - mean_overall).dot((mean_vec - mean_overall).T)

새로운 특성 부분 공간을 위해 선형 판별 벡터 선택

  • LDA의 남은 단계는 PCA와 유사하다. 공분산 행렬에 대한 고윳값 분해를 수행하는 대신 행렬 S_{W}^{-1} S_{B} 의 고윳값을 계산하면 된다.
eigen_vals, eigen_vecs = np.linalg.eig(np.linalg.inv(S_W).dot(S_B))
  • 고유 벡터와 고윳값 쌍을 계산한 후 내림차순으로 고윳값을 정렬한다.
eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:,i]) for i in range(len(eigen_vals))]
eigen_pairs = sorted(eigen_pairs, key=lambda k: k[0], reverse=True)
  • LDA에서 선형 판별 벡터는 최대 c-1 개이다. c 는 클래스 레이블의 개수이다. 클래스 내 산포 행렬 S_{B} 가 랭크(rank) 1 또는 그 이하인 c 개의 행렬을 합한 것이기 때문이다.
    • 0이 아닌 고윳값이 두 개만 있는 것을 볼 수 있다.
  • 선형 판별 벡터(고유 벡터)로 잡은 클래스 판별 정보가 얼마나 많은지 측정하기 위해 선형 판별 벡터를 그려보면 아래 그림과 같다.
tot = sum(eigen_vals.real)
discr = [(i/tot) for i in sorted(eigen_vals.real, reverse=True)]
cum_discr = np.cumsum(discr)

plt.bar(range(1, 14), discr, alpha=0.5, align='center', label='individual discriminability')
plt.step(range(1, 14), cum_discr, where='mid', label='cumulative "discriminability"')
plt.ylabel('"discriminability" ratio')
plt.xlabel('Linear Discriminants')
plt.ylim([-0.1, 1.1])
plt.legend(loc='best')
plt.tight_layout()
plt.show()

  • 두 개의 판별 고유 벡터를 열로 쌓아서 변환 행렬 W 를 만든다.
w = np.hstack((eigen_pairs[0][1][:, np.newaxis].real, eigen_pairs[1][1][:, np.newaxis].real))

새로운 특성 공간으로 샘플 투영

  • 이전 절에서 만든 변환 행렬 W 를 훈련 세트에 곱해서 데이터를 변환할 수 있다.

X' = XW

X_train_lda = X_train_std.dot(w)

colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']

for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_lda[y_train==l, 0], X_train_lda[y_train==l, 1] * -1, c=c, label=l, marker=m)

plt.ylabel('LD 1')
plt.xlabel('LD 2')
plt.legend(loc='lower right')
plt.tight_layout()
plt.show()

사이킷런의 LDA

  • 사이킷런에 구현된 LDA 클래스를 살펴보겠다.
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA

lda = LDA(n_components=2)
X_train_lda = lda.fit_transform(X_train_std, y_train)

lr = LogisticRegression(solver='liblinear', multi_class='auto')
lr = lr.fit(X_train_lda, y_train)

plot_decision_regions(X_train_lda, y_train, classifier=lr)

plt.ylabel('LD 1')
plt.xlabel('LD 2')
plt.legend(loc='lower right')
plt.tight_layout()
plt.show()

  • 테스트 결과는 다음과 같다.

커널 PCA를 사용하여 비선형 매핑

  • 많은 머신 러닝 알고리즘은 입력 데이터가 선형적으로 구분 가능하다는 가정을 한다. 그래서 지금까지 다루었던 알고리즘들은 선형적으로 완벽하게 분리되지 못한 이유를 잡음 때문이라고 가정한다.
  • 그러나 실제 애플리케이션에서는 비선형 문제를 더 자주 맞닥뜨리게 될 것이다.
    • 이런 비선형 문제를 다룰 때 PCA와 LDA 같은 차원 축소를 위한 선형 변환 기법은 최선의 선택이 아니다.
    • 이절에서는 PCA의 커널화 버전 또는 KPCA를 다루겠다.
    • 커널 PCA를 사용하여 선형적으로 구분되지 않는 데이터를 선형 분류기에 적합한 새로운 저차원 부분 공간으로 변환하는 방법을 살펴보겠다.

커널 함수와 커널 트릭

  • 커널 SVM에 관해 배운 것을 떠올려 보면 비선형 문제를 해결하기 위해 클래스가 선형으로 구분되는 새로운 고차원 특성 공간으로 투영할 수 있었다.
    • k 고차원 부분 공간에 있는 샘플 x \in \mathbb{R}^{d} 를 변환하기 위해 비선형 매핑 함수 \phi 를 정의한다.

\phi : \mathbb{R}^{d} \to \mathbb{R}^{k} (k >> d)

  • \phi 함수를 d 차원의 원본 데이터셋에서 더 큰 k 차원의 특성 공간으로 매핑하기 위해 원본 특성의 비선형 조합을 만드는 함수로 생각할 수 있다.
    • 예컨대 2차원 (d=2) 특성 벡터 x \in \mathbb{R}^{d} 가 있으면 (x d 개의 특성으로 구성된 열 벡터) 매핑 가능한 3D 공간은 다음과 같다.

x = [x_{1}, x_{2}]^{T}

\downarrow \phi

z = [x_{1}^{2}, \sqrt{2x_{1}x_{2}}, x_{2}^{2}]^{T}

  • 다른 말로 하면 커널 PCA를 통한 비선형 매핑을 수행하여 데이터를 고차원 공간으로 변환한다.
    • 그다음 고차원 공간에 표준 PCA를 사용하여 샘플이 선형 분류기로 구분될 수 있는 저차원 공간으로 데이터를 투영한다 (샘플이 이 입력 공간에서 잘 구분될 수 있다고 가정).
    • 이 방식의 단점은 계산 비용이 매우 비싸다는 것이다. 여기에 커널 트릭(kernel trick)이 등장한다.
    • 커널 트릭을 사용하면 원본 특성 공간에서 두 고차원 특성 벡터의 유사도를 계산할 수 있다.
  • 커널 트릭에 대해 알아보기 전에 표준 PCA 방식을 다시 살펴보자
    • 두 개의 특성 k, j 사이의 공분산은 다음과 같이 계산한다.

\sigma_{jk} = {1 \over n} \sum_{i = 1}^{n} (x_{j}^{(i)} - \mu_{j})(x_{k}^{(i)} - \mu_{k})

  • \mu_{j} = 0, \mu_{k} = 0 처럼 특성 평균을 0에 맞추었으므로 이 식은 다음과 같이 간단히 쓸 수 있다.

\sigma_{jk} = {1 \over n} \sum_{i = 1}^{n} x_{j}^{(i)} x_{k}^{(i)}

  • 이 식은 두 특성 간의 공분산을 의미한다. 공분산 행렬 \Sigma 를 계산하는 일반식으로 써보자.

\Sigma = {1 \over n} \sum_{i = 1}^{n} x^{(i)} x^{(i)^{T}}

  • 베른하르트 슐코프(Bernhard Scholkopf)는 이 방식을 일반화 하여 \phi 를 통한 비선형 특성 조합으로 원본 특성 공간의 샘플 사이의 내적을 대체했다.

\Sigma = {1 \over n} \sum_{i = 1}^{n} \phi (x^{(i)}) \phi(x^{(i)})^{T}

  • 이 공분산 행렬에서 고유 벡터(주성분)를 얻기 위해서는 다음 식을 풀어야 한다.

\Sigma v = \lambda v

\Rightarrow {1 \over n} \sum_{i = 1}^{n} \phi (x^{(i)}) \phi(x^{(i)})^{T} v = \lambda v

\Rightarrow v = {1 \over n \lambda} \sum_{i = 1}^{n} \phi (x^{(i)}) \phi(x^{(i)})^{T} = \sum_{i=1}^{n} a^{(i)} \phi(x^{(i)})

  • 여기서 \lambda v 는 공분산 행렬 \Sigma 의 고윳값과 고유 벡터이다. a 는 커널(유사도) 행렬 K 의 고유 벡터를 추출함으로써 구할 수 있다.
  • 커널 SVM을 사용하여 비선형 문제 풀기를 떠올리면 커널 트릭을 사용하여 샘플 x 끼리의 \phi 함수 내적을 커널 함수 K 로 바꿈으로써 고유 벡터를 명싲거으로 계산할 필요가 없었다.

\kappa (x^{(i)}, x^{(j)}) = \phi (x^{(i)})^{T} \phi (x^{(j)})

  • 다른 말로 하면 커널 PCA로 얻은 것은 표준 PCA 방식에서처럼 투영 행렬을 구성한 것이 아니고 각각의 성분에 이미 투영된 샘플이다.
    • 기본적으로 커널 함수는 두 벡터 사이의 내적을 계산할 수 있는 함수이다. 즉, 유사도를 측정할 수 있는 함수이다.
  • 가장 널리 사용되는 커널은 다음과 같다.
  • 다항 커널
    • \kappa (x^{(i)}, x^{(j)}) = (x^{(i)T} x^{(j)} + \theta)^{P}
    • 여기서 \theta 는 임계 값이고 P 는 사용자가 지정한 거듭제곱이다.
  • 하이퍼볼릭 탄젠트(hyperbolic tangent) (시그모이드(sigmoid)) 커널
    • \kappa (x^{(i)}, x^{(j)}) = tanh (\eta x^{(i)T} x^{(j)} + \theta)
  • 방사 기저 함수(Radial Basis Function, RBF) 또는 가우시안 커널
    • \kappa (x^{(i)}, x^{(j)}) = \exp(- {\|x^{(i)} - x^{(j)}\|^{2} \over 2 \sigma^{2}})
    • 변수 \gamma = {1 \over 2 \sigma^{2}} 을 도입하여 종종 다음과 같이 쓴다.
    • \kappa (x^{(i)}, x^{(j)}) = \exp(- \gamma \|x^{(i)} - x^{(j)}\|^{2})
  • 지금까지 배운 것을 요약하면 RBF 커널 PCA를 구현하기 위해 다음 3 단계를 정의할 수 있다.
    1. 커널 (유사도) 행렬 K 를 다음 식으로 계산한다.
      • \kappa (x^{(i)}, x^{(j)}) = \exp(- \gamma \|x^{(i)} - x^{(j)}\|^{2})
      • 샘플의 모든 쌍에 대해 구한다.
        • K = \left[ \begin{array}{rrrr} \kappa (x^{(1)}, x^{(1)}) & \kappa (x^{(1)}, x^{(2)}) & ... & \kappa (x^{(1)}, x^{(n)}) \\  \kappa (x^{(2)}, x^{(1)}) & \kappa (x^{(2)}, x^{(2)}) & ... & \kappa (x^{(2)}, x^{(n)}) \\ ... & ... & ... & ... \\ \kappa (x^{(n)}, x^{(1)}) & \kappa (x^{(n)}, x^{(2)}) & ... & \kappa (x^{(n)}, x^{(n)}) \end{array} \right] 
        • 100개의 훈련 샘플이 담긴 데이터셋이라면 각 싸으이 유사도를 담은 대칭 커널 행렬은 100 x 100 차원이 된다.
    2. 다음 식을 사용하여 커널 행렬 K 를 중앙에 맞춘다.
      • K' = K - 1_{n} K - K 1_{n} + 1_{n} K 1_{n}
      • 여기서 1_{n} 은 모든 값이 {1 \over n} n \times n 차원 행렬이다. (커널 행렬과 같은 차원)
    3. 고윳값 크기대로 내림차순으로 정렬하여 중앙에 맞춘 커널 행렬에서 최상위 k 개의 고유 벡터를 고른다. 표준 PCA와 다르게 고유 벡터는 주성분 축이 아니며, 이미 이 축에 투영된 샘플이다.
  • 위 단계의 2번째에서 왜 커널 행렬을 중앙에 맞추었는지 궁금할 수 있다.
    • 우리는 앞서 표준화된 전처리된 데이터를 다룬다고 가정했다. 공분산 행렬을 구성하고 비선형 특성 조합으로 내적을 \phi 를 사용한 비선형 특성 조합으로 내적을 대체할 때 사용한 모든 특성의 평균이 0이다.
    • 반면 새로운 특성 공간을 명시적으로 계산하지 않기 때문에 이 특성 공간이 중앙에 맞추어져 있는지 보장할 수 없다.
    • 이것이 새로운 두 번째 단계에서 커널 행렬의 중앙을 맞추는 것이 필요한 이유이다.

파이썬으로 커널 PCA 구현

  • RBF 커널 PCA 코드
from scipy.spatial.distance import pdist, squareform
from scipy import exp
from scipy.linalg import eigh
import numpy as np

def rbf_kernel_pca(X, gamma, n_components):
    """
   RBF 커널 PCA 구현

    매개변수
   ----------
   X: {넘파이 ndarray}, shape = [n_samples, n_features]

    gamma: float
     RBF 커널 튜닝 매개변수
    n_components: int
     변환한 주성분 개수   

    반환값
   -----------
   X_pc: {넘파이 ndarray}, shape = [n_samples, k_features]
      투영된 데이터셋
    """

   # M x N 차원의 데이터셋에서 샘플 간의 유클리디안 거리의 제곱을 계산
    sq_dists = pdist(X, 'sqeuclidean')

   # 샘플 간의 거리를 정방 대칭 행렬로 반환
    mat_sq_dists = squareform(sq_dists)

    # 커널 행렬을 계산
    K = exp(-gamma * mat_sq_dists)

   # 커널 행렬을 중앙에 맞춘다
    N = K.shape[0]
   one_n = np.ones((N, N)) / N
    K = K - one_n.dot(K) - K.dot(one_n) + one_n.dot(K).dot(one_n)

   # 중앙에 맞춰진 커널 행렬의 고윳값과 고유 벡터를 구한다.
    # scipy.linalg.eigh 함수는 오름차순으로 반환한다.
    eigvals, eigvecs = eigh(K)
    eigvals, eigvecs = eigvals[::-1], eigvecs[:, ::-1]

   # 최상위 k개의 고유 벡터를 선택한다(투영 결과)
    X_pc = np.column_stack([eigvecs[:, i] for i in range(n_components)])

    return X_pc

예제 1

  • 반달 모양을 띤 100개의 샘플로 구성된 2차원 데이터셋을 구성
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt

X, y = make_moons(n_samples=100, random_state=123)

plt.scatter(X[y==0, 0], X[y==0, 1], color='red', marker='^', alpha=0.5)
plt.scatter(X[y==1, 0], X[y==1, 1], color='blue', marker='o', alpha=0.5)
plt.show()

  • PCA의 주성분에 데이터셋을 투영한다.
scikit_pca = PCA(n_components=2)

X_spca = scikit_pca.fit_transform(X)

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(7,3))

ax[0].scatter(X_spca[y==0, 0], X_spca[y==0, 1], color='red', marker='^', alpha=0.5)
ax[0].scatter(X_spca[y==1, 0], X_spca[y==1, 1], color='blue', marker='o', alpha=0.5)
ax[1].scatter(X_spca[y==0, 0], np.zeros((50,1))+0.02, color='red', marker='^', alpha=0.5)
ax[1].scatter(X_spca[y==1, 0], np.zeros((50,1))-0.02, color='blue', marker='^', alpha=0.5)

ax[0].set_xlabel('PC1')
ax[0].set_ylabel('PC2')
ax[1].set_ylim([-1, 1])
ax[1].set_yticks([])
ax[1].set_xlabel('PC1')

plt.show()

  • 이 데이터에 앞서 작성한 rbf_kernel_pca를 적용해 보자
X_kpca = rbf_kernel_pca(X, gamma=15, n_components=2)

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(7,3))

ax[0].scatter(X_kpca[y==0, 0], X_kpca[y==0, 1], color='red', marker='^', alpha=0.5)
ax[0].scatter(X_kpca[y==1, 0], X_kpca[y==1, 1], color='blue', marker='o', alpha=0.5)
ax[1].scatter(X_kpca[y==0, 0], np.zeros((50,1))+0.02, color='red', marker='^', alpha=0.5)
ax[1].scatter(X_kpca[y==1, 0], np.zeros((50,1))-0.02, color='blue', marker='^', alpha=0.5)

ax[0].set_xlabel('PC1')
ax[0].set_ylabel('PC2')
ax[1].set_ylim([-1, 1])
ax[1].set_yticks([])
ax[1].set_xlabel('PC1')

plt.tight_layout()
plt.show()

  • 이제 두 클래스는 선형적으로 구분이 잘 되므로 선형 분류기를 위한 훈련 데이터로 적합하다.
  • 아쉽지만 보편적인 \gamma 파라미터 값은 없다. 주어진 문제에 적합한 \gamma 를 찾으려면 실험이 필요하다.

예제 2

  • 동심원 데이터 셋을 구성
from sklearn.datasets import make_circles

X, y = make_circles(n_samples=1000, random_state=123, noise=0.1, factor=0.2)

plt.scatter(X[y==0, 0], X[y==0, 1], color='red', marker='^', alpha=0.5)
plt.scatter(X[y==1, 0], X[y==1, 1], color='blue', marker='o', alpha=0.5)
plt.tight_layout()
plt.show()

  • 기본 PCA 적용
scikit_pca = PCA(n_components=2)

X_spca = scikit_pca.fit_transform(X)

fit, ax = plt.subplots(nrows=1, ncols=2, figsize=(7,3))

ax[0].scatter(X_spca[y==0, 0], X_spca[y==0, 1], color='red', marker='^', alpha=0.5)
ax[0].scatter(X_spca[y==1, 0], X_spca[y==1, 1], color='blue', marker='o', alpha=0.5)
ax[1].scatter(X_spca[y==0, 0], np.zeros((500,1))+0.02, color='red', marker='^', alpha=0.5)
ax[1].scatter(X_spca[y==1, 0], np.zeros((500,1))-0.02, color='blue', marker='^', alpha=0.5)

ax[0].set_xlabel('PC1')
ax[0].set_ylabel('PC2')
ax[1].set_ylim([-1, 1])
ax[1].set_yticks([])
ax[1].set_xlabel('PC1')

plt.tight_layout()
plt.show()

  • 적절한 gamma를 부여하여 RBF 커널 PCA를 구현
X_kpca = RbfKernelPCA.rbf_kernel_pca(X, gamma=15, n_components=2)

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(7,3))

ax[0].scatter(X_kpca[y==0, 0], X_kpca[y==0, 1], color='red', marker='^', alpha=0.5)
ax[0].scatter(X_kpca[y==1, 0], X_kpca[y==1, 1], color='blue', marker='o', alpha=0.5)
ax[1].scatter(X_kpca[y==0, 0], np.zeros((500,1))+0.02, color='red', marker='^', alpha=0.5)
ax[1].scatter(X_kpca[y==1, 0], np.zeros((500,1))-0.02, color='blue', marker='^', alpha=0.5)

ax[0].set_xlabel('PC1')
ax[0].set_ylabel('PC2')
ax[1].set_ylim([-1, 1])
ax[1].set_yticks([])
ax[1].set_xlabel('PC1')

plt.tight_layout()
plt.show()

새로운 데이터 포인트 투영

  • 앞선 예제는 하나의 데이터셋을 새로운 특성에 투영했는데, 실전에서는 하나 이상의 변환해야 할 데이터셋이 있다.
  • 장의 서두에서 보았던 기본 PCA 방법을 보면 변환 행렬과 입력 샘플 사이의 내적을 계산해서 데이터를 투영했다.
    • 변환 행렬의 열은 공분산 행렬에서 얻은 최상위 k 개의 고유 벡터(v) 이다.
  • 커널 PCA 이면의 아이디어로 돌아가 보면 중심을 맞춘 커널 행렬의 고유 벡터(a) 를 구했다.
    • 즉 샘플은 이미 주성분 축 v 에 투영되어 있다.
    • 새로운 샘플 x' 를 주성분 축에 투영하려면 다음을 계산해야 한다.

\phi (x')^{T} v

  • 다행히 커널 트릭을 사용하여 명시적으로 \phi (x')^{T} v 를 계산할 필요가 없다.
    • 기본 PCA와 다르게 커널 PCA는 메모리 기반 방법이다.
    • 즉, 새로운 샘플을 투영하기 위해 매번 원본 훈련 세트를 재사용해야 한다.
    • 훈련 세트에 있는 i 번째 새로운 샘플과 새로운 샘플 x' 사이 RBF 커널(유사도)을 계산해야 한다.

\phi (x')^{T} v = \sum_{i} a^{(i)} \phi (x')^{T} \phi (x^{(i)}) = \sum_{i} a^{(i)} \kappa (x', x^{(i)})

  • 커널 행렬 K 의 고유 벡터 a 와 고윳값 \lambda 는 다음 식을 만족한다.

Ka = \lambda a

  • 새로운 샘플과 훈련 세트의 샘플 간 유사도를 계산한 후 고윳값으로 고유 벡터 a 를 정규화 해야 한다.
from scipy.spatial.distance import pdist, squareform
from scipy import exp
from scipy.linalg import eigh
import numpy as np

def rbf_kernel_pca(X, gamma, n_components):
    """
   RBF 커널 PCA 구현

    매개변수
   ----------
   X: {넘파이 ndarray}, shape = [n_samples, n_features]

    gamma: float
     RBF 커널 튜닝 매개변수
    n_components: int
     변환한 주성분 개수   

    반환값
   -----------
   X_pc: {넘파이 ndarray}, shape = [n_samples, k_features]
      투영된 데이터셋
    """

   # M x N 차원의 데이터셋에서 샘플 간의 유클리디안 거리의 제곱을 계산
    sq_dists = pdist(X, 'sqeuclidean')

   # 샘플 간의 거리를 정방 대칭 행렬로 반환
    mat_sq_dists = squareform(sq_dists)

    # 커널 행렬을 계산
    K = exp(-gamma * mat_sq_dists)

   # 커널 행렬을 중앙에 맞춘다
    N = K.shape[0]
   one_n = np.ones((N, N)) / N
    K = K - one_n.dot(K) - K.dot(one_n) + one_n.dot(K).dot(one_n)

   # 중앙에 맞춰진 커널 행렬의 고윳값과 고유 벡터를 구한다.
    # scipy.linalg.eigh 함수는 오름차순으로 반환한다.
    eigvals, eigvecs = eigh(K)
   eigvals, eigvecs = eigvals[::-1], eigvecs[:, ::-1]

   # 최상위 k개의 고유 벡터를 선택한다
alphas = np.column_stack([eigvecs[:, i] for i in range(n_components)])

# 고유 벡터에 상응하는 고윳값을 선택한다.
lambdas = [eigvals[i] for i in range(n_components)]

return alphas, lambdas
  • 수정된 커널 PCA를 이용하여 반달 데이터 셋을 테스트 해 보자
X, y = make_moons(n_samples=100, random_state=123)
alphas, lambdas = RbfKernelPCA2.rbf_kernel_pca(X, gamma=15, n_components=1)

x_new = X[25]
x_proj = alphas[25]

def project_x(x_new, X, gamma, alphas, lambdas):
    pair_dist = np.array([np.sum((x_new - row)**2) for row in X])
   k = np.exp(-gamma * pair_dist)
    return k.dot(alphas/lambdas)

x_reproj = project_x(x_new, X, gamma=15, alphas=alphas, lambdas=lambdas)

plt.scatter(alphas[y==0, 0], np.zeros((50)), color='red', marker='^', alpha=0.5)
plt.scatter(alphas[y==1, 0], np.zeros((50)), color='blue', marker='o', alpha=0.5)
plt.scatter(x_proj, 0, color='black', label='original projection of point X[25]', marker='x', s=100)
plt.scatter(x_proj, 0, color='green', label='remapped point X[25]', marker='x', s=500)
plt.legend(scatterpoints=1)
plt.tight_layout()
plt.show()

사이킷런의 커널 PCA

  • 편리하게도 사이킷런은 sklearn.decomposition 모듈 아래 커널 PCA 클래스를 구현해 두었으므로 아래와 같이 사용하면 된다.
from sklearn.datasets import make_moons
from sklearn.decomposition import KernelPCA
import matplotlib.pyplot as plt

X, y = make_moons(n_samples=100, random_state=123)

scikit_kpca = KernelPCA(n_components=2, kernel='rbf', gamma=15)

X_skernpca = scikit_kpca.fit_transform(X)

plt.scatter(X_skernpca[y==0, 0], X_skernpca[y==0, 1], color='red', marker='^', alpha=0.5)
plt.scatter(X_skernpca[y==1, 0], X_skernpca[y==1, 1], color='blue', marker='o', alpha=0.5)
plt.xlabel('PC1')
plt.xlabel('PC2')
plt.tight_layout()
plt.show()

머신 러닝 교과서/ 좋은 훈련 세트 만들기: 데이터 전처리

누락된 데이터 다루기

  • 실제 애플리케이션에서는 여러 이유로 누락된 샘플이 있는 경우가 많다.

테이블 형태 데이터에서 누락된 값 식별

  • (CSV 파일에서 누락된 항목 체크하는 부분 생략. 데이터가 null 이거나 비어 있는 string인지를 체크를 하면 된다.)

누락된 값이 있는 샘플이나 특성 제외

  • (누락된 데이터가 있는 row를 drop 할 수도 있지만, 그런 데이터가 많은 경우 문제가 될 수 있다는 내용)

누락된 값 대체

  • 데이터를 제거하기 어려우면 보간 기법을 사용하여 데이터셋에 있는 다른 훈련 샘플로부터 누락된 값을 추정할 수 있다.
  • 가장 흔한 기법 중 하나는 평균으로 대체하는 것이다.
    • 사이킷런의 Imputer 클래스를 사용하면 간단히 처리할 수 있다.
    • (코드 생략)

사이킷런 추정기 API 익히기

  • Imputer 클래스는 데이터 변환에 사용되는 사이킷런의 변환기(transfomer) 클래스이다. 이런 추정기의 주요 메서드 두 개는 fit과 transform이다.
    • fit 메서드를 사용하여 훈련데이터에서 모델 파라미터를 학습한다.
    • transform 메서드를 사용하여 학습한 파라미터로 데이터를 변환한다.
    • 변환하려는 데이터 배열은 모델 학습에 사용한 데이터의 특성 개수와 같아야 한다.

  • 3장에서 사용한 분류기는 변환기 클래스와 개념상 매우 유사한 API를 가진 사이킷런의 추정기(estimator)이다.
    • 추정기는 predict 메서드가 있지만 transform 메서드도 가질 수 있다.
    • 추정기를 훈련할 때는 fit 메서드를 사용해서 모델의 파라미터를 학습했다.
    • 그 후 predict 메서드를 사용하여 새로운 데이터 샘플에 대한 예측을 만든다.

범주형 데이터 다루기

순서가 있는 특성과 순서가 없는 특성

  • 범주형 데이터에 관해 이야기할 때 순서가 있는 것과 없는 것을 구분해야 한다.
    • 순서가 있는 특성은 정렬하거나 차례대로 놓을 수 있는 범주형 특성으로 생각할 수 있다.
    • 예컨대 티셔츠 사이즈는 XL > L > M 과 같이 순서를 정할 수 있으므로 순서가 있는 특성이다.
    • 반면 티셔츠의 컬러는 순서가 없는 특성이다.

순서 특성 매핑

  • 학습 알고리즘이 순서 특성을 올바르게 인식하려면 문자열을 정수로 바꾸어야 한다.
    • 티셔츠의 size 특성(XL, L, M)은 숫자가 아니라서 이를 바꾸어주는 매핑 함수를 만들어야 한다.
  • (매핑하는 코드 예시 생략)

클래스 레이블 인코딩

  • 많은 머신 러닝 라이브러리는 클래스 레이블이 정수로 인코딩 되었을 것으로 기대한다.
  • (매핑 예시 코드 생략)

순서가 없는 특성에 원-핫 인코딩 적용

  • color와 같이 순서가 없는 특성을 숫자로 바꾸는 경우 학습 알고리즘이 blue가 red 보다 크다는 식의 순서를 부여할 수 있다.
  • 이 문제를 해결하기 위한 통상적인 방법은 원-핫 인코딩(one-hot encoding) 기법이다.
    • 이 방식의 아이디어는 순서 없는 특성에 들어 있는 고유한 값마다 새로운 더미(dummy) 특성을 만드는 것이다.
    • 예컨대 blue에 대한 샘플은 blue=1, red=0, green=0 과 같이 변환하여 사용하는 것이다.
  • 사이킷런의 preprocessing 모듈에 구현된 OneHotEncoder를 사용하면 이런 변환을 수행할 수 있다.
  • 원-핫 인코딩으로 더미 변수를 만드는 더 편리한 방법은 판다스의 get_dummies 메서드를 사용하는 것이다.
  • 원-핫 인코딩된 데이터셋을 사용할 때 다중 공선성(multicollinearity) 문제를 주의해야 한다.
    • 어떤 알고리즘에서는 이슈가 될 수 있다 (예컨대 역행렬을 구해야 하는 경우)
    • 특성 간의 상관관계가 높으면 역행렬을 계산하기 어려워 수치적으로 불안정해진다.
    • 변수 간의 상관관계를 감소하려면 원-핫 인코딩된 배열에서 특성 열 하나를 삭제한다. 이렇게 특성을 삭제해도 잃는 정보는 없는데, 예컨대 blue 열을 삭제해도, green = 0, red=0이면 blue임을 유추할 수 있다.

데이터셋을 훈련 세트와 테스트 세트로 나누기

  • (UCI 머신러닝 저장소에서 Wine 데이터셋을 받는 내용 생략)
  • 사이킷런의 model_selection 모듈에 있는 train_test_split 함수를 사용하면 데이터셋을 훈련 세트와 테스트 세트로 편리하게 나눌 수 있다.

특성 스케일 맞추기

  • 특성 스케일 조정은 전처리 파이프라인에서 아주 중요한 단계이다.
    • 결정 트리와 랜덤 포레스트는 특성 스케일 조정에 대해 걱정할 필요가 없는 몇 안 되는 알고리즘 중 하나이다.
    • 그러나 경사 하강법 같은 대부분의 머신 러닝과 최적화 알고리즘은 특성의 스케일이 같을 때 훨씬 성능이 좋다.
  • 두 개의 특성에서 첫 번째 특성이 1-10 사이의 scale을 갖고 있고 두 번째 특성이 1-100000 사이의 스케일을 갖는다고 가정하면, k-최근접 이웃 같은 알고리즘에서는 샘플 간의 거리가 두 번째 특성 축에 의해 좌우될 것이다.
  • 스케일이 다른 특성을 맞추는 대표적인 방법은 정규화(normalization)와 표준화(standardization)이 있다.
  • 정규화는 특성의 스케일을 [0, 1] 범위에 맞추는 것을 의미한다.
    • 최소-최대 스케일 변환(min-max scaling)의 특별한 경우이다.
    • 데이터를 정규화 하기 위해 다음과 같이 특성의 열마다 최소-최대 변환을 적용하여 샘플 x^{(i)} 에서 새로운 값 x_{norm}^{(i)} 을 계산한다.

x_{norm}^{(i)} = {x^{(i)} - x_{min} \over x_{max} - x_{min}}

  • 최소-최대 스케일 변환을 통한 정규화는 범위가 정해진 값이 필요할 때 유용하게 사용할 수 있는 일반적인 기법이다.
  • 표준화는 많은 머신러닝 알고리즘 특히 경사하강법 같은 최적화 알고리즘에서 널리 사용된다.
    • 표준화를 사용하면 특성의 평균을 0에 맞추고 표준편차를 1로 만들어 정규분포와 같은 특징을 가지도록 만든다. 이는 가중치를 더 쉽게 학습할 수 있도록 만든다.
    • 또 표준화는 이상치 정보가 유지되기 때문에 제한된 범위로 데이터를 조정하는 최소-최대 스케일 변환에 비해 알고리즘이 이상치에 덜 민감하다.

x_{sid}^{(i)} = {x^{(i)} - \mu_{x} \over \sigma_{x}}

  • 여기서 \mu_{x} 는 어떤 특성의 샘플 평균이고 \sigma_{x} 는 그에 해당하는 표준 편차이다.

유용한 특성 선택

  • 모델이 테스트 세트보다 훈련 세트에서 성능이 높다면 과대적합(overfitting)에 대한 강력한 신호이다.
    • 새로운 데이터에는 잘 인반화하지 못하기 때문에 모델 분산이 크다고 말한다.
    • 과대적합의 이유는 주어진 훈련 데이터에 비해 모델이 너무 복잡하기 때문이다.
  • 일반화 오차를 감소시키기 위해 많이 사용하는 방법은 다음과 같다.
    • 더 많은 훈련 데이터를 모은다.
    • 규제를 통해 복잡도를 제한한다.
    • 파라미터 개수가 적은 간단한 모델을 선택한다.
    • 데이터 차원을 줄인다.

모델 복잡도 제한을 위한 L1 규제와 L2 규제

  • 3장에서 L2 규제(L2 gerularization)는 개별 가중치 값을 제한하여 모델 복잡도를 줄이는 한 방법이라고 설명했다.
    • 가중치 벡터 w 의 L2 규제는 다음과 같이 정의한다.

L2:\|w\|_{2}^{2} = \sum_{j=1}^{m} w_{j}^{2}

  • 모델 복잡도를 줄이는 또 다른 방법은 L1 규제(L1 gerularization)이다.

L1:\|w\|_{2} = \sum_{j=1}^{m} |w_{j}|

  • 이는 가중치 제곱을 그냥 가중치 절댓값으로 바꾼 것이다.
    • L2 규제와 대조적으로 L1 규제는 보통 희소한 특성 벡터를 만든다.
    • 대부분의 특성 가중치가 0이 된다.
    • 실제로 관련 없는 특성이 많은 고차원 데이터셋일 경우 이런 희소성이 도움이 될 수 있다.
    • 특히 샘플보다 관련 없는 특성이 더 많은 경우이다.
    • 이런 맥락으로 보면 L1 규제는 특성 선택의 기법이 될 수 있다.

L2 규제의 기하학적 해석

  • L2 규제는 비용 함수에 페널티 항(penalty term)을 추가한다.
    • 규제가 없는 비용 함수로 훈련한 모델에 비해 가중치 값을 아주 작게 만드는 효과를 낸다.
  • L1 규제가 어떻게 희소성을 만드는지 이해하기 위해 규제의 기하학적 해석에 대해 고찰해 보자
    • 두 개의 가중치 값 w_{1} w_{2} 에 대한 볼록한 비용 함수의 등고선을 그려보면 다음과 같다.

  • 2장 아달린에서 사용했던 제곱 오차합(SSE) 비용 함수가 구 모양이어서 로지스틱 회귀의 비용 함수보다 그리기 쉽다.
    • 여기서 얻은 개념은 로지스틱 회귀에도 동일하게 적용 가능하다.
    • 그림 4-4와 같이 우리의 목표는 훈련 데이터에서 비용 함수를 최소화하는 가중치 값의 조합을 찾는 것임을 기억하라 (타원의 중심 포인트)
  • 규제를 더 작은 가중치를 얻기 위해 비용 함수에 추가하는 페널티 항으로 생각할 수 있다. 다른 말로 하면 큰 가중치를 제한한다.
    • 규제 파라미터 \lambda 로 규제의 강도를 크게 하면 가중치가 0에 가까워지고 훈련 데이터에 대한 모델 의존성은 줄어든다.
  • L2 페널티 항에 대한 이 개념을 아래 그림에 표현해 보자.

  • 위 그림에서 이차식인 L2 규제 항은 회색 공으로 표현되어 있다.
    • 가중치 값은 규제 예산을 초과할 수 없다. 즉, 가중치 값의 조합이 회색공 바깥에 놓일 수 없다.
    • 반면 우리는 여전히 비용 함수를 최소화해야 한다.
    • 페널티 제약이 있는 상황에서 최선은 L2 회색 공과 규제가 없는 비용 함수의 등고선이 만나는 지점이다.
    • 규제 파라미터 \lambda 가 커질수록 페널티 비용이 빠르게 증가하여 L2 공을 작게 만든다.
    • 예컨대 규제 파라미터를무한대로 증가하면 가중치 값이 L2 공의 중심인 0이 될 것이다.
  • 이 예시에서 중요한 핵심을 정리하면, 우리의 목표는 규제가 없는 비용과 페널티 항의 합을 최소화 하는 것이다.
    • 이는 모델을 학습할만한 충분한 훈련 데이터가 없을 때 편향을 추가하여 모델을 간단하게 만듦으로써 분산을 줄이는 것으로 해석할 수 있다.

L1 규제를 사용한 희소성

  • L1 규제 이면에 있는 주요 개념은 L2의 것과 유사하다.
    • L1 페널티는 가중치 절댓값의 합이기 때문에 아래 그림과 같이 다이아몬드 모양의 제한범위를 그릴 수 있다. (L2 항은 이차식)

  • 그림 4-6에서 w_{1} = 0 일 때 비용 함수의 등고선이 L1 다이아몬드와 만나는 것을 볼 수 있다.
    • L1 규제의 등고선은 날카롭기 때문에 비용 함수의 포물선과 L1 다이아몬드의 경계가 만나는 최적점은 축에 가까이 위치할 가능성이 높다.
    • 이것이 희소성이 나타나는 이유이다.
  • 규제의 강도를 달리하여 그래프를 그리면 아래와 같다.

순서 특성 선택 알고리즘

  • 모델 복잡도를 줄이고 과대적합을 피하는 다른 방법은 특성 선택을 통한 차원 축소(dimensionality reduction)이다. 이는 규제가 없는 모델에서 특히 유용하다.
  • 차원 축소 기버벵는 두 개의 주요 카테고리인 특성 선택(feature selection)과 특성 추출(feature extraction)이 있다.
    • 특성 선택은 원본 특성에서 일부를 선택하고 특성 추출은 일련의 특성에서 얻은 정보로 새로운 특성을 만든다.
    • 여기서는 특성 선택을 살펴보고 5장에서 특성 추출 기법에 대해 배워보겠다.
  • 순차 특성 선택(sequential feature selection) 알고리즘은 탐욕적 알고리즘(greedy search algorithm)으로 초기 d 차원의 특성 공간을 k < d k s=2 차원의 특성 부분 공간으로 축소한다.
    • 특성 선택 알고리즘은 주어진 문제에 가장 관련이 높은 특성 부분 집합을 자동으로 선택하는 것이 목적으로 관계 없는 특성이나 잡음을 제거하여 계산 효율성을 높이고 모델의 일반화 오차를 줄인다.
    • 규제를 제공하지 않는 알고리즘을 사용할 때 유용하다.
  • 전통적인 순차 특성 알고리즘은 순차 후진 선택 (Sequential Backward Selection, SBS)이다.
    • 계산 효율성을 향상하기 위해 모델 성능을 가능한 적게 희생하면서 초기 특성의 부분 공간으로 차원을 축소한다.
    • 과대적합의 문제를 안고 있는 모델이라면 SBS가 예측 성능을 높일 수도 있다.
  • SBS 알고리즘 이면의 아이디어는 매우 간단하다.
    • SBS는 새로운 특성의 부분 공간이 목표하는 특성 개수가 될 때까지 전체 특성에서 순찾거으로 특성을 제거한다.
    • 각 단계에서 어떤 특성을 제거할지 판단하기 위해 최소화할 기준 함수를 정의한다.
    • 기준 함수에서 계산한 값은 어떤 특성을 제거하기 전후의 모델 성능 차이이다.
    • 각 단계에서 제거할 특성은 기준 값이 가장 큰 특성으로 정의할 수 있다.
    • 이해하기 쉽게 말하면 각 단계에서 제거했을 때 성능 손실이 최대가 되는 특성을 제거한다.
  • SBS 정의에 따라 이 알고리즘을 간단히 네 단계로 정의할 수 있다.
    1. 알고리즘을 k = d 로 초기화한다. d 는 전체 특성 공간 X_{d} 의 차원이다.
    2. 조건 x^{-} = arg max J(X_{k} - x) 를 최대화하는 특성 x^{-} 를 결정한다. 여기서 x \in X_{k} 이다.
    3. 특성 집합에서 특성 x^{-} 를 제거한다. 즉 X_{k-1}:=X_{k} - x^{-}; k:=k-1 이다.
    4. k 가 목표하는 특성 개수가 되면 종료한다. 아니면 2단계로 돌아간다.
  • SBS 알고리즘은 아직 사이킷런에 구현되어 있지 않다. 간단한 알고리즘이므로 다음과 같이 직접 구현할 수 있다.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)

X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)

sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

fig = plt.figure()
ax = plt.subplot(111)

colors = ['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black', 'pink', 'lightgreen', 'lightblue', 'gray', 'indigo', 'orange']

weights, params = [], []

for c in np.arange(-4, 6.):
   lr = LogisticRegression(solver='liblinear', multi_class='auto', penalty='l1', C=10.**c, random_state=0)
    lr.fit(X_train_std, y_train)
    weights.append(lr.coef_[1])
    params.append(10**c)

weights = np.array(weights)

for column, color in zip(range(weights.shape[1]), colors):
    plt.plot(params, weights[:, column], label=df_wine.columns[column+1], color=color)

plt.axhline(0, color='black', linestyle='--', linewidth=3)
plt.xlim([10**(-5), 10**5])
plt.ylabel('weight coefficient')
plt.xlabel('C')
plt.xscale('log')
plt.legend(loc='upper left')
ax.legend(loc='upper center', bbox_to_anchor=(1.38, 1.03), ncol=1, fancybox=True)
plt.show()
  • 위 SBS 구현을 이용해서 KNN 분류기의 정확도를 그리면 다음과 같다.
    • 아래 그림에서 볼 수 있듯 특성 개수가 줄어들었을 때 검증 세트에서 KNN 분류기의 정확도가 향상되었다. 이는 3장의 KNN 알고리즘에서 설명했던 차원의 저주가 감소하기 때문이다.

랜덤 포레스트의 특성 중요도 사용

  • 데이터셋에 유용한 특성을 선택하는 또 다른 방법은 랜덤 포레스트를 사용하는 것이다.
    • 랜덤 포레스트를 사용하면 앙상블에 참여한 모든 결정 트리에서 계산한 평균적인 불순도 감소로 특성 중요도를 측정할 수 있다.
    • 데이터셋이 선형적으로 구분 가능한지 여부를 가정할 필요가 없다.
  • 편리하게도 사이킷런의 랜덤 포레스트 구현은 특성 중요도 값을 이미 수집하고 있다.
    • RandomForestClassifier 모델을 훈련한 후 feature_importances_ 속성에서 확인할 수 있다.
  • 다음은 Wine 데이터셋에서 500개의 트리를 가진 랜덤 포레스트를 훈련하고 각각의 중요도에 따라 13개의 특성에 순위를 매긴 결과이다.
    • 트리 기반 모델은 표준화나 정규화를 할 필요가 없다.

  • 500개의 결정 트리에서 평균적인 불순도 감소를 기반으로 이 데이터셋에서 가장 판별력이 좋은 특성은 Proline, Flavanoids, Color intensity, OD280/OD315 of diluted wines, Alcohol이다.
  • 랜덤 포레스트에서 두 개 이상의 특성이 매우 상관관계가 높다면 하나의 특성은 매우 높은 순위를 갖지만 다른 특성 정보는 완전히 잡아내지 못할 수 있다.
    • 특성 중요도 값을 해석하는 것보다 모델의 예측 성능에만 관심이 있다면 이 문제를 신경 쓸 필요는 없다.

[유튜브] 박곰희TV

투자 관련 내용을 잘 정리해주는 채널. 거시적인 경제 이야기보다는 개인의 자산 관리에 대한 이야기를 주로 다룸.

금융 상품은 숫자를 다룬다는 것을 너머 그 자체로 굉장히 복잡한 경우가 많은데, 그런 복잡한 내용을 굉장히 쉽게 이해할 수 있게 잘 설명해 줌. 연금 저축 계좌의 공제와 세금에 대해 이렇게 쉽게 이해할 수 있게 설명해 주는 사람은 처음 봤다. (4분 5초부터)

이상엽/ 해석학/ 수열, 급수의 극한

수열과 극한

  • (수열은 수를 순서 있게 나열한 것. 현대적으로 보면 결국 함수)

수열의 정의

Def 1. [수열]

함수 f : \mathbb{N} \to \mathbb{R} 를 수열 \{a_{n}\} 이라 하고 f(m) = a_{m} \{a_{n}\} m 번째 항이라 한다.

Def 2. [부분수열]

\{a_{n}\} 에 대하여 자연수 수열 \{n_{k}\}

n_{1} < n_{2} < ... < n_{k} < ...

이면 \{a_{n_{k}}\} \{a_{n}\} 의 부분 수열이라 한다.

Def 3. [증가(감소)수열]

  1. \forall n \in \mathbb{N}, a_{n} \leq a_{n+1} \{n_{k}\} 를 단조증가수열이라 한다.
    • (a_{n} \geq a_{n+1} 이면 단조감소수열)
  2. \forall n \in \mathbb{N}, a_{n} < a_{n+1} \{n_{k}\} 를 순증가수열이라 한다.
    • (a_{n} > a_{n+1} 이면 순감소수열)

Def 4. [유게인 수열]

\exists M > 0 : \forall n \in \mathbb{N}, |a_{n}| \leq M 이면 \{a_{n}\} 을 유계인 수열이라 한다.

수열의 극한

Def 1. [수열의 수렴]

\{a_{n}\} 이라 하자. \forall \epsilon > 0, \exists N \in \mathbb{N} : \forall n \geq \mathbb{N}, | a_{n} - a | < \epsilon 이 성립하면 \{a_{n}\} a 로 수렴한다고 하고 이를 \lim_{n \to \infty} a_{n} = a 로 표현한다.

Def 2. [수열의 발산]

적당한 \epsilon > 0 와 모든 N \in \mathbb{N} 에 대하여 \exists n \geq \mathbb{N} : |a_{n} - a| \geq \epsilon 이면 \{a_{n}\} 은 발산한다고 한다.

Thm 1. [수열 극한의 유일성]

\{a_{n}\} 이 수렴하면 그 극한은 유일하다.

Thm 2. [수열 극한의 연산]

\lim_{n \to \infty} a_{n} = a 이고 \lim_{n \to \infty} b_{n} = b 이면 다음이 성립한다.

  1. \lim_{n \to \infty}(a_{n} \pm b_{n}) = a \pm b (복부호 동순)
  2. \lim_{n \to \infty} a_{n} b_{n} = ab
  3. \lim_{n \to \infty} {a_{n} \over b_{n}} = {a \over b} (b \neq 0, \forall n \in \mathbb{N}, b_{n} \neq 0)

코시 수열

Def 1. [코시수열의 정의]

\forall \epsilon > 0, \exists N \in \mathbb{N} : \forall m, n \in \mathbb{N}

with, m \geq n > N, |a_{m} - a_{n} | < \epsilon 가 성립하면 \{a_{n}\} 을 코시수열이라 한다.

Thm 1. [코시 수열과 수렴판정]

\{a_{n}\} 이 코시수열이면 \{a_{n}\} 은 수렴한다.

Def 2. [실수의 구성적 정의]

  1. 유리수 코시수열의 집합 \mathbb{R}* 에 대하여 \mathbb{R}* \times \mathbb{R}* 의 동치관계 E : \{a_{n}\} E\{b_{n}\} \Leftrightarrow \lim_{n \to \infty}(a_{n} - b_{n}) = 0 의 동치류 [\{a_{n}\}] 을 실수라 하고, 이들의 집합을 \mathbb{R} 이라 표현한다.
  2. \lim_{n \to \infty} a_{n} = \alpha 이면 [\{a_{n}\}] = \alpha 라 한다.

Thm 2. [실수의 완비성]

\mathbb{R} 의 공집합이 아닌 부분집합이 위로 유계이면 그 부분집합은 상한을 갖는다.

주요 정리

단조수렴정리

Thm 1. [단조수렴정리]

\{a_{n}\} 이 단조증가(감소)하고 위(아래)로 유계이면 \{a_{n}\} 은 수렴한다.

  • (그 수렴하는 값은 상한(하한)이 된다)

Thm 2. [축소구간정리]

모든 n \in \mathbb{N} I_{n} = [a_{n}, b_{n}] 에 대하여

  1.  I_{n} = [a_{n}, b_{n}] 이 유계인 폐구간이고
  2. I_{n+1} \subset I_{n} 이며
  3. lim_{n \to \infty} (b_{n} - a_{n}) = 0 이면

\cap_{n = 1}^{\infty} I_{n} = \{ \alpha \} \alpha \in \mathbb{R} 가 존재한다.

  • (임의의 구간 잡고 그 구간을 간격을 무한히 좁혀가면, 그 수렴하는 값에 대응되는 실수가 존재한다.)

B-W 정리

Thm 1. [샌드위치 정리]

L \in \mathbb{R} 일 때 모든 n \in \mathbb{R} 에 대하여 a_{n} \leq b_{n} \leq c_{n} 이고 \lim_{n \to \infty} a_{n} = \lim_{n \to \infty} c_{n} = L 이면 \lim_{n \to \infty} b_{n} = L 이다.

Thm 2. [볼차노-바이어슈트라스 정리]

\{a_{n}\} 이 유계인 수열이면 \{a_{n}\} 은 수렴하는 부분수열을 갖는다.

Cor. [최대 최소정리]

f [a, b] 에서 연속 \Rightarrow \exists a_{0}, b_{0} \in [a, b] : \forall x \in [a, b], f(a_{0}) \leq f(x) \leq f(b_{0})

급수와 극한

급수의 정의

  • (급수란 수열의 합)

Def 1. [급수]

수열 \{a_{n}\} 에 대하여

a_{1} + a_{2} + ... = \sum_{n = 1}^{\infty} a_{n}

을 (무한)급수라 한다.

이때 a_{n} 을 급수의 n 번째 항이라 하며

S_{n} = \sum_{k = 1}^{n} a_{k} = a_{1} + a_{2} + ... + a_{n}

을 급수의 부분합이라 한다.

Def 2. [재배열급수]

f : \mathbb{N} \to \mathbb{N} 가 전단사 함수일 때 \sum_{n = 1}^{\infty} a_{f(n)} 의 재배열급수라 한다.

  • (급수에 대해 순서를 적절하게 재배열할 것을 재배열급수라고 한다)
  • (수열에서는 순서가 중요하기 때문에 더하는 순서도 중요하다)

급수의 극한

Def 1. [급수의 수렴과 발산]

\sum_{n = 1}^{\infty} a_{n} 에 대한 부분합의 수열 \{S_{n}\} S \in \mathbb{R} 로 수렴하면 \sum_{n = 1}^{\infty} a_{n} S 로 수렴한다고 하고 \sum_{n = 1}^{\infty} a_{n} = S 로 표현한다.

만약 \{S_{n}\} 이 어떤 실수 값으로 수렴하지 않으면 \sum_{n = 1}^{\infty} a_{n} 은 발산한다고 한다.

  • (수열의 부분합들로 이루어진 수열의 합이 어떤 값으로 수렴하게 되면 급수는 수렴한다고 한다)
  • (무한급수의 합은 S_{n} = {a(1 - r^{n}) \over 1 - r} 와 같다. 여기서 a 는 첫항, r 는 첫항에 곱해지는 공비. 공비는 1이 되면 안 된다.)

Def 2. [절대수렴과 조건수렴]

n \in \mathbb{N} 에 대하여 a_{n} \in \mathbb{R} 이라 하자

  1.  \sum_{n = 1}^{\infty} |a_{n}| 이 수렴하면 \sum_{n = 1}^{\infty} a_{n} 은 절대수렴한다고 한다.
    • (수열을 재배열 해도 수렴하는 값이 동일. 수열에 절대값을 씌운 후에 합해도 수렴하는 경우에 가능)
  2. \sum_{n = 1}^{\infty} |a_{n}| 은 발산하지만 \sum_{n = 1}^{\infty} a_{n} 은 수렴하면 \sum_{n = 1}^{\infty} a_{n} 은 조건수렴한다고 한다.
    • (수열을 재배열 하면 수렴하는 값이 달라짐)

여러가지 정리

Thm 1.

\alpha, \beta \in \mathbb{R} 이고 \sum_{n = 1}^{\infty} a_{n} = \alpha, \sum_{n=1}^{\infty} b_{n} = \beta 이면 \sum_{n = 1}^{\infty} (a_{n} \pm b_{n}) = \alpha \pm \beta 이다. (복부호 동순)

Thm 2.

\sum_{n = 1}^{\infty} a_{n} 이 수렴하면 \lim_{n \to \infty} a_{n} = 0 이다.

Thm 3.

\sum_{n = 1}^{\infty} a_{n} \sum_{n = 1}^{\infty} a_{n} 의 임의의 재배열 급수 \sum_{n = 1}^{\infty} a_{f(n)} 에 대하여

\sum_{n = 1}^{\infty} a_{n} 이 절대수렴하고 \sum_{n = 1}^{\infty} a_{n} = L 이면 \sum_{n = 1}^{\infty} a_{f(n)} = L 이다.

  • (절대수렴인 경우 재배열을 어떻게 하더라도 원래 수열과 같은 값으로 수렴한다)

머신 러닝 교과서/ 사이킷런을 타고 떠나는 머신 러닝 분류 모델 투어

분류 알고리즘 선택

  • 모든 경우에 뛰어난 성능을 낼 수 있는 분류 모델은 없다. 실제로 최소한 몇 개의 학습 알고리즘 성능을 비교하고 해당 문제에 최선인 모델을 선택하는 것이 항상 권장된다.
  • 머신 러닝 알고리즘을 훈련하기 위한 다섯 가지 주요 단계는 다음과 같다.
    • 특성을 선택하고 훈련 샘플을 모은다.
    • 성능 지표를 선택한다.
    • 분류 모델과 최적화 알고리즘을 선택한다.
    • 모델의 성능을 평가한다.
    • 알고리즘을 튜닝한다.

사이킷런 첫걸음: 퍼셉트론 훈련

  • 2장에서 했던 것을 쉬운 인터페이스로 분류 알고리즘을 최적화하여 구현한 사이킷런 API를 사용해 보겠다.
import numpy as np
from sklearn import datasets

iris = datasets.load_iris()
X = iris.data[:, [2, 3]]
y = iris.target

print('클래스 레이블:', np.unique(y))

--
클래스 레이블: [0 1 2]
  • np.unique(y) 함수는 iris.target에 저장된 세 개의 고유한 클래스 레이블을 반환한다.
    • 결과는 0 1 2가 나오는데 이는 붓꽃 클래스 이름인 Iris-setosa, Iris-versicolor, Iris-virginica의 정수 형태
    • 사이킷런의 많은 함수와 클래스 메서드는 문자열 형태의 클래스 레이블을 다룰 수 있지만, 정수 레이블을 권장한다.
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)

print('y의 레이블 카운트:', np.bincount(y))
print('y_train의 레이블 카운트:', np.bincount(y_train))
print('y_test의 레이블 카운트:', np.bincount(y_test))

---
y의 레이블 카운트: [50 50 50]
y_train의 레이블 카운트: [35 35 35]
y_test의 레이블 카운트: [15 15 15]
  • 훈련된 모델 성능을 평가하기 위해 데이터셋을 훈련 세트와 테스트 세트로 분할 한다. 사이킷런 model_selection 모듈의 train_test_split 함수를 이용하면 X와 y 배열을 나눌 수 있다.
    • 30%는 테스트 데이터, 70%는 훈련 데이터가 된다.
  • 데이터를 분할 하기 전에 데이터셋을 섞는 것이 좋으므로 유사 난수 생성기에 random_state 매개변수로 고정된 랜덤시드(random_state=1)을 전달한다.
  • 마지막으로 stratify=y를 통해 계층화(stratification) 기능을 사용하는데, 여기서 계층화는 train_test_split 함수가 훈련 세트와 테스트 세트의 클래스 레이블 비율을 입력 데이터셋과 동일하게 만든다는 의미이다.
from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
sc.fit(X_train)

X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)
  • 앞장의 경사 하강법 예제에서 보았던 것처럼 머신러닝 알고리즘과 최적화 알고리즘은 성능을 위해 특성 스케일 조정이 필요하다. 여기서는 사이킷런의 preprocessing 모듈의 StandardScaler 클래스를 사용해서 특성을 표준화 했다.
  • 위 코드에서 StandardScaler의 fit 메서드는 훈련 세트의 각 특성 차원마다 \mu (샘플 평균)와 \sigma (표준 편차)를 계산한다.
    • transform 메서드를 실행하면 계산된 \mu \sigma 를 사용하여 훈련세트를 표준화 한다.
    • 훈련 세트와 테스트 샘플이 서로 같은 비율로 이동되도록 동 일한 \mu \sigma 를 사용하여 테스트 세트를 표준화 한다.
from sklearn.linear_model import Perceptron

ppn = Perceptron(max_iter=40, eta0=0.1, tol=1e-3, random_state=1)
ppn.fit(X_train_std, y_train)

y_pred = ppn.predict(X_test_std)

print('잘못 분류된 샘플 개수: %d' % (y_test != y_pred).sum())

---
잘못 분류된 샘플 개수: 14
  • 훈련 데이터를 표준화한 후 퍼셉트론 모델을 훈련한다. 사이킷런의 알고리즘은 대부분 기본적으로 OvR(One-versus-Rest) 방식을 사용하여 다중 분류(multiclass classification)를 지원한다.
  • 사이킷런의 인터페이스는 2장에서 구현한 퍼셉트론과 미슷하다. linear_model 모듈에서 Perceptron 클래스를 로드하여 새로운 객체를 생성한 후 fit 메서드를 사용하여 모델을 훈련한다.
    • eta0은 학슙릴이고, max_iter는 훈련 세트를 반복할 에포크 횟수를 말한다.
  • 2장에서 했던 것처럼 적절한 학습률을 찾으려면 어느 정도 실험이 필요하다.
    • 학습률이 너무 크면 알고리즘은 전역 최솟값을 지나치고, 너무 작으면 학습 속도가 느리기 때문에 대규모 데이터셋에서 수렴하기까지 많은 에포크가 필요하다.
  • 사이킷런 라이브러리는 metrics 모듈 아래에 다양한 성능 지표를 구현해 놓았는데, 예는 아래와 같다.
from sklearn.metrics import accuracy_score

print('정확도: %.2f' % accuracy_score(y_test, y_pred))
print('정확도: %.2f' % ppn.score(X_test_std, y_test))

---
정확도: 0.69
정확도: 0.69
  • 마지막으로 2장에서 사용했던 plot_decision_regions 함수를 아래와 같이 수정한다.
import numpy as np
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt

def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
   # 마커와 컬러맵을 설정
   markers = ('s', 'x', 'o', '^', 'v')
   colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # 결정 경계를 그린다.
   x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
   x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
   Z = Z.reshape(xx1.shape)
   plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.xlim(xx2.min(), xx2.max())

   for idx, cl in enumerate(np.unique(y)):
       plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolor='black')   

   if test_idx:
       X_test, y_test = X[test_idx, :], y[test_idx]
       plt.scatter(X_test[:, 0], X_test[:, 1], c='', edgecolor='black', alpha=1.0, linewidth=1, marker='o', s=100, label='test set')
  • 수정된 plot_decision_regions 함수를 이용하여 그래프를 그리면 아래 그림과 같다.
plot_decision_regions(X=X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105, 150))

plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

로지스틱 회귀를 사용한 클래스 확률 모델링

  • 퍼셉트론 규칙은 간단하고 좋은 모델이지만 가장 큰 단점은 클래스가 선형적으로 구분되지 않을 때 수렴할 수 없다는 점이다.
  • 선형 이진 분류 문제에는 로지스틱 회귀(logistic regression)이 더 강력한 알고리즘이다.
    • 이름이 회귀지만 로지스틱 회귀는 분류 모델이다.

로지스틱 회귀의 이해와 조건부 확률

  • 로지스틱 회귀는 구현하기 쉽고 선형적으로 구분되는 클래스에 뛰어난 성능을 내는 분류 모델로 산업계에서 가장 널리 사용되는 알고리즘 중 하나이다.
    • 로지스틱 회귀모델도 퍼셉트론이나 아달리과 마찬가지로 이진 분류를 위한 선형 모델이지만 다중 분류로 확장할 수 있다. 예컨대 OvR 방식을 사용한다.
  • 확률 모델로서 로지스틱 회귀 모델 이면에 있는 아이디어를 설명하기 위해 먼저 오즈비(odds ratio)를 설명한다.
    • 오즈는 특정 이벤트가 발생할 확률로 오즈비는 {P \over 1 - P } 처럼 쓸 수 있다.
    • 여기서 P 는 양성 샘플일 확률로서 양성 샘플은 예측하려는 대상을 말한다.
  • 오즈비에 로그 함수(로그 오즈)를 취해 로짓(logit) 함수를 정의한다.

logit(P) = \log {P \over 1 - P}

  • 여기서 \log 자연로그이다. logit 함수는 0과 1사이의 입력 값을 받아 실수 범위 값으로 변환한다.
    • 특성의 가중치 합과 로그 오즈 사이의 선형 관계를 다음과 같이 쓸 수 있다.

logit(P(y=1|x)) = w_{0} x_{0} + w_{1} x_{1} + ... + w_{m} x_{m} \\ = \sum_{i=0}^{m} w_{i} x_{i} = w^{T} x

  • 여기서 P(y=1|x) 는 특성 x 가 주어졌을 때 이 샘플이 클래스 1에 속할 조건부 확률이다.
  • 어떤 샘플이 특정 클래스에 속할 확률을 예측하는 것이 관심 대상이므로 logit 함수를 거꾸로 뒤집는데, 이 함수를 로지스틱 시그모이드 함수(logistic sigmoid function)라고 한다.
    • 함수 모양이 S자 형태를 띠기 때문에 간단하게 시그모이드 함수(sigmoid function) 라고도 한다.

\phi(z) = {1 \over 1 + e^{-z}}

  • 여기서 z 는 가중치와 샘플 특성의 선형 조합으로 이루어진 최종 입력이다. 즉 z = w^{T} x 이다.
  • 시그모이드 함수를 그리면 아래와 같다.

  • z 가 무한대로 가면 (z \to \infty ) e^{-z} 가 매우 작아지기 때문에 \phi(z) 는 1에 가까워진다.
    • 비슷하게 z \to -\infty 이면 분모가 점점 커지기 때문에 \phi(z) 는 0에 수렴한다.
    • 이 시그모이드 함수는 실수 입력 값을 [0, 1] 사이의 값으로 변환한다. 중간은 \phi(0) = 0.5 이다.
  • 아달린에서 활성화 함수로 항등 함수 \phi(z) = z 를 사용했는데, 로지스틱 회귀에서는 시그모이드 함수가 활성화 함수가 된다.
    • 아달린과 로지스틱 회귀의 차이는 아래 그림과 같다.

  • 가중치 w 와 곱해지는 특성 x 에 대한 시그모이드 함수의 출력을 특정 샘플이 클래스 1에 속할 확률 \phi(z) = P(y=1|x; w) 로 해석한다.
    • 예컨대 어떤 붓꽃 샘플이 \phi(z) = 0.8 이라면 이 샘플은 Iris-versicolor일 확률이 80%라는 뜻이 된다.
    • 이 예측 확률은 임계 함수를 사용하여 간단하게 이진 출력으로 바꿀 수 있다.

\hat{y} = \begin{cases} 1 & \phi(z) \geq 0.5 \\ 0 & else \end{cases}

  • 앞의 시그모이드 함수 그래프를 보면 다음과 동일하다는 것을 알 수 있다.

\hat{y} = \begin{cases} 1 & z \geq 0.0 \\ 0 & else \end{cases}

  • 실제로 클래스 레이블을 예측하는 것 외에 클래스에 속할 확률(임계 함수를 적용하기 전 시그모이드 함수 출력)을 추정하는 거싱 유용한 애플리케이션도 많다.
    • 예컨대 비가 오는지 예측하는 것 뿐만 아니라 비 올 확률을 예측해야 하는 경우에 로지스틱 회귀를 사용할 수 있다.
    • 비슷하게 어떤 증상이 있는 환자가 특정 질병을 가질 확률을 예측하는데 로지스틱 회귀를 사용할 수 있다.

로지스틱 비용 함수의 가중치 학습

  • 이전 장에서 다음과 같은 제곱 오차합 비용 함수를 정의했었다.

J(w) = \sum_{i} {1 \over 2} (\phi(z^{(i)}) - y^{(i)})^{2}

  • 아달린 분류 모델에서 이 함수를 최소화하는 가중치 w 를 학습한다. 로지스틱 회귀의 비용 함수를 유도하는 방법을 설명하기 위해 먼저 로지스틱 회귀 모델을 만들 때 최대화 하려는 가능도(likehood) L 을 정의하겠다.
    • 데이터셋에 있는 각 샘플이 서로 독립적이라고 가정한다. 공식은 다음과 같다.

L(w) = P(y|x;w) = \Pi_{i = 1}^{n} P(y^{(i)} | x^{(i)}; w) \\ = \Pi_{i=1}^{n}(\phi(z^{(i)}))^{y^{(i)}} (1 - \phi(z^{(i)}))^{1 - y^{(i)}}

  • 실전에서는 이 공식의 (자연)로그를 최대화하는 것이 더 쉽다. 이 함수를 고르 가능도 함수라고 한다.

l(w) = \log L(w) \\= \sum_{i=1}^{n} [y^{(i)} \log(\phi(z^{(i)})) + (1 - y^{(i)}) \log (1 - \phi(z^{(i)}))]

  • 첫째, 로그 함수를 적용하면 가능도가 매우 작을 때 일어나는 수치상의 언더플로(underflow)를 미연에 방지한다.
  • 둘째, 계수의 곱을 계수의 합으로 바꿀 수 있다. 이렇게 하면 도함수를 구하기 쉽다.
  • 경사 상승법 같은 최적화 알고리즘을 사용하여 이 로그 가능도 함수를 최대화 할 수 있다.
    • 또는 로그 가능도 함수를 다시 비용 함수 J로 표현하여 경사 하강법을 사용하여 최소화 할 수 있다.

J(w) = \sum_{i=1}^{n} [-y^{(i)} \log(\phi(z^{(i)})) - (1 - y^{(i)}) \log (1 - \phi(z^{(i)}))]

  • 이 비용 함수를 더 잘 이해하기 위해 샘플이 하나일 때 비용을 계산해 보자

J(\phi(z), y; w) = -y \log(\phi(z)) - (1 - y) \log (1 - \phi(z))]

  • 이 식을 보면 y = 0 일 때 첫 번째 항이 0 이 되고  y = 1 일 때 두 번째 항이 0 이 된다.

J(\phi(z), y; w) = \begin{cases} - \log(\phi(z)) & y = 1 \\ - \log(1 - \phi(z)) & y = 0 \end{cases}

  • 클래스 1에 속한 샘플을 정확히 예측하면 비용이 0에 가까워지는 것을 볼 수 있다(실선)
    • 비슷하게 클래스 0에 속한 샘플을 y = 0 으로 정확히 예측하면 y축의 비용이 0에 가까워진다(점선)
    • 예측이 잘못되면 비용이 무한대가 된다.

아달린 구현을 로지스틱 회귀 알고리즘으로 변경

  • 로지스틱 회귀를 구현하려면 2장의 아달린 구현에서 비용 함수 J 를 새로운 비용함수로 바꾸기만 하면 된다.

J(w) = \sum_{i=1}^{n} [y^{(i)} \log(\phi(z^{(i)})) + (1 - y^{(i)}) \log (1 - \phi(z^{(i)}))]

  • 이 함수로 에포크마다 모든 훈련 샘플을 분류하는 비용을 계산한다.
    • 선형 활성화 함수를 시그모이드 활성화로 바꾸고 임계 함수가 클래스 레이블 -1과 1이 아니고 0과 1을 반환하도록 변경한다.
    • 아달린 코드에 이 세가지를 반영하면 로지스틱 회귀 모델을 얻을 수 있다.
import numpy as np

class LogisticRegressionGD(object):
   """ 경사 하강법을 사용한 로지스틱 회귀 분류기
    매개변수
   ----------
   eta : float
       학습률 (0.0과 1.0사이)
    n_iter : int
       훈련 데이터셋 반복 횟수
    random_state : int
        가중치 무작위 초기화를 위한 난수 생성기 시드

    속성
   ---------
   w_ : 1d-array
       학습된 가중치
    errors_ : list
       에포크마다 누적된 분류 오류
    """

    def __init__(self, eta = 0.05, n_iter = 100, random_state = 1):
       self.eta = eta
       self.n_iter = n_iter
        self.random_state= random_state

    def fit(self, X, y):
       """ 훈련 데이터 학습
        매개변수
       ----------
       X : {array-like}, shape = [n_samples, n_features]
            n_samples개의 샘플과 n_features개의 특성으로 이루어진 훈련 데이터
       y : array-like, shape = [n_samples]
           타겟 값       

        반환값
       ---------
        self : object

        """
       rgen = np.random.RandomState(self.random_state)
        self.w_ = rgen.normal(loc = 0.0, scale = 0.01, size = 1 + X.shape[1])
        self.cost_ = []

       for i in range(self.n_iter):
           net_input = self.net_input(X)
           output = self.activation(net_input)
            errors = (y - output)
           self.w_[1:] += self.eta * X.T.dot(errors)
           self.w_[0] += self.eta * errors.sum()       

        # 오차 제곱합 대신 로지스틱 비용을 계산한다.
        cost = (-y.dot(np.log(output)) - ((1-y).dot(np.log(1-output))))
       self.cost_.append(cost)

        return self

   def net_input(self, X):
       """ 최종 입력 계산 """
        return np.dot(X, self.w_[1:]) + self.w_[0]

    def activation(self, z):
       """ 로지스틱 시그모이드 활성화 계산 """       
        return 1. / (1. + np.exp(-np.clip(z, -250, 250)))

    def predict(self, X):
       """ 단위 계단 함수를 사용하여 클래스 레이블을 반환합니다 """
        return np.where(self.net_input(X) >= 0.0, 1, 0)
  • 다음 코드로 로지스틱 모델을 실행해 보면 아래 그림과 같은 결과를 얻을 수 있다.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)

X_train_01_subset = X_train[(y_train == 0) | (y_train == 1)]
y_train_01_subset = y_train[(y_train == 0) | (y_train == 1)]

lrgd = LogisticRegressionGD(eta=0.05, n_iter=1000, random_state=1)
lrgd.fit(X_train_01_subset, y_train_01_subset)

plot_decision_regions(X=X_train_01_subset, y=y_train_01_subset, classifier=lrgd)

plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

사이킷런을 사용하여 로지스틱 회귀 모델 훈련

  • 사이킷런에서 로지스틱 회귀를 사용하는 법을 배워보자. 이 구현은 매우 최적화 되어 있고 다중 분류도 지원한다. (OvR이 기본값)
    • 아래 코드에 대한 결과는 아래와 같다.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(solver='liblinear', multi_class='auto', C=100.0, random_state=1)lr.fit(X_train_std, y_train)

plot_decision_regions(X=X_combined_std, y=y_combined, classifier=lr, test_idx=range(105, 150))

plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

  • 훈련 샘플이 어떤 클래스에 속할 확률은 predict_proba 메서드를 사용하여 계산한다. 예컨대 테스트 세트에 있는 처음 세 개의 샘플 확률은 다음과 같이 예측할 수 있다.
lr.predict_proba(X_test_std[:3, :])

---
arry([[3.20136878e-08, 1.46953648e-01, 8.53046320e-01],
[8.34428069e-01, 1.65571931e-01, 4.57896429e-12],
[8.49182775e-08, 1.50817225e-01, 4.65678779e-13]])
  • 첫 번째 행은 첫 번째 붓꽃의 클래스 소속 확률이고,두 번째 행은 두 번째 꽃의 클래스 소속 확률이다.
    • 당연히 열을 모두 더하면 1이 된다.
  • 첫 번째 행에서 가장 큰 값은 대략 8.53인데, 이는 첫 번째 샘플이 클래스 3(Iris-virginica)에 속할 확률이 85.7%라는 뜻이다.
  • 넘파이의 argmax 함수를 이용하면 결과를 다음과 같이 얻을 수 있다.
lr.predict_proba(X_test_std[:3, :]).argmax(axis=1)

---
arry([2, 0, 0])
  • 조건부 활률로부터 얻은 클래스 레이블은 수동적인 방법이므로 직접 predict 메서드를 호출하여 결과를 얻을 수도 있다.
lr.predict(X_test_std[:3, :])

---
arry([2, 0, 0])
  • 샘플 하나의 클래스 레이블을 예측할 때 주의할 점이 있다. 사이킷런은 입력 데이터로 2차원 배열을 기대한다. 고로 하나의 행을 2차원 포맷으로 먼저 변환해야 한다.
    • 하나의 행을 2차원 배열로 변환하는 한 가지 방법은 넘파이 reshape 메서드를 사용하여 새로운 차원을 추가하는 것이다.
lr.predict(X_test_std[0, :]).reshape(1, -1)

---
arry([2])

규제를 사용하여 과대적합 피하기

  • 과대적합(overfitting)은 머신러닝에서 자주 발생하는 문제이다.
    • 모델이 훈련 데이터로는 잘 동작하지만 본 적 없는 데이터(테스트 데이터)로는 잘 일반화되지 않는 현상이다. 모델이 과대적합일 때 분산이 크다고 말한다.
    • 모델 파라미터가 너무 많아 주어진 데이터에서 너무 복잡한 모델을 만들기 때문이다.
  • 비슷하게 모델이 과소적합(underfitting)일 때도 있다(편향이 크다) 이는 훈련 데이터에 있는 패턴을 감지할 정도로 충분히 모델이 복잡하지 않다는 것을 의미하는 것으로 이 때문에 새로운 데이터에서도 성능이 낮다.

  • 좋은 편향-분산 트레이드오프를 찾는 한 가지 방법은 규제를 사용하여 모델의 복잡도를 조정하는 것이다.
    • 규제(regularization)은 공선성(collinearity)(특성 간의 높은 상관관계)을 다루거나 데이터에서 잡음을 제거하여 과대적합을 방지할 수 있는 매우 유용한 방법이다.
    • 규제는 과도한 파라미터(가중치) 값을 제한하기 위해 추가적인 정보(편향)을 주입하는 개념이다.
    • 가장 널리 사용하는 규제 형태는 다음과 같은 L2 규제이다 (이따금 L2 축소 또는 가중치 감쇠라고 부른다)

{\lambda \over 2} \|w\|^{2} = {\lambda \over 2} \sum_{j=1}^{m} w_{j}^{2}

  • 이 식에서 \lambda 는 규제 하이퍼파라미터이다.
  • 로지스틱 회귀의 비용 함수는 규제 항을 추가해서 규제를 적용한다. 규제 항은 모델 훈련 과정에서 가중치를 주이는 역할을 한다.

J(w) = \sum_{i=1}^{n} [-y^{(i)} \log(\phi(z^{(i)})) - (1 - y^{(i)}) \log (1 - \phi(z^{(i)}))] + {\lambda \over 2} \|w\|^{2}

  • 규제 하이퍼파라미터 \lambda 를 사용하여 가중치를 작게 유지하면서 훈련 데이터에 얼마나 잘 맞출지를 조정할 수 있다.
    • \lambda 값을 증가하면 규제 강도가 높아진다.
  • 사이킷런의 LogisticRegression 클래스의 매개변수 C는 서포트 벡터 머신 형식에서 따왔다. 매개변수 C는 규제 하이퍼파라미터 \lambda 의 역수이다.
    • 결과적으로 역 규제 파라미터 C의 값을 감소시키면 규제 강도가 증가한다.
weights, params = [], []

for c in np.arange(-5, 5):
   lr = LogisticRegression(solver='liblinear', multi_class='auto', C=10.**c, random_state=1)
   lr.fit(X_train_std, y_train)
    weights.append(lr.coef_[1])
    params.append(10.**c)

weights = np.array(weights)

plt.plot(params, weights[:, 0], label='petal length')
plt.plot(params, weights[:, 1], linestyle='--', label='petal width')
plt.xlabel('C')
plt.ylabel('weight coefficient')
plt.legend(loc='upper left')
plt.xscale('log')
plt.show()
  • 위 코드를 실행하면 역 규제 매개변수 C의 값을 바꾸면서 열 개의 로지스틱 회귀 모델을 훈련한다.
    • 시연을 위해 모든 분류기에서 클래스 1의 가중치 값만 사용한다.
    • 다중 분류에는 OvR 기법을 사용한다.
  • 아래의 결과 그래프에서 볼 수 있듯이 매개변수 C가 감소하면 가중치 절댓값이 줄어든다. 즉, 규제 강도가 증가한다.

서포트 벡터 머신을 사용한 최대 마진 분류

  • 서포트 벡터 머신(Support Vector Machine, SVM)은 퍼셉트론의 확장으로 생각할 수 있다. 앞선 퍼셉트론 알고리즘을 사용하여 분류 오차를 최소화 했는데, SVM의 최적화 대상은 마진을 최대화 하는 것이다.
    • 마진은 클래스를 구분하는 초평면(결정 경계)과 이 초평면에 가장 가까운 훈련 샘플 사이의 거리로 정의한다.
    • 이런 샘플을 서포트 벡터(support vector)라고 한다.

최대 마진

  • 큰 마진의 결정 경계를 원하는 이유는 일반화 오차가 낮아지는 경향이 있기 때문이다. 반면 작은 마진의 모델을 과대적합(over fitting)이 되기 쉽다.
  • 마진 최대화를 이해하기 위해 결정 경계와 나란히 놓인 양성 샘플 쪽의 초평면과 음성 샘플 쪽의 초평면을 살펴보자.
    • 두 초평면은 다음과 같이 쓸 수 있다.
  1. w_{0} + w^{T} x_{pos} = 1
  2. w_{0} + w^{T} x_{neg} = -1
  • 위의 두 선형식 1과 2를 빼면 다음 결과를 얻을 수 있다.

w^{T} (x_{pos} - x_{neg}) = 2

  • 이 식을 다음과 같은 벡터 w의 길이로 정규화 할 수 있다.

\|w\| = \sqrt{\sum_{j=1}^{m} w_{j}^{2}}

  • 결과 식은 다음과 같다.

{w^{T} (x_{pos} - x_{neg}) \over \|w\|} = {2 \over \|w\|}

  • 이 식의 좌변은 양성 쪽 초평면과 음성 쪽 초평면 사이의 거리로 해석할 수 있다. 이것이 최대화하려고 하는 마진(margin)이다.
  • SVM의 목적 함수는 샘플을 정확하게 분류된다는 제약 조건하에서 {2 \over \|w\|} 를 최대화함으로써 마진을 최대화하는 것이다.
    • 이 제약은 다음과 같이 쓸 수 있다.

\begin{cases} w_{0} + w^{T} x^{(i)} \geq 1 & y^{(i)} = 1 \\ w_{0} + w^{T} x^{(i)} \leq -1 & y^{(i)} = -1  \end{cases} (i = 1, 2, ...  N)

  • 여기서 N 은 데이터셋에 있는 샘플 개수이다.
  • 이 두 식이 말하는 것은 다음과 같다. 모든 음성 샘플은 음성 쪽 초평면 너머에 있어야 하고, 양성 샘플은 양성 쪽 초평면 너머에 있어야 한다. 이를 다음과 같이 간단히 쓸 수 있다.

\forall i, y^{(i)} (w_{0} + w^{T} x^{(i)}) \geq 1

  • 실제로는 동일한 효과를 내면서 콰드라틱 프로그래밍(quadratic programming) 방법으로 풀 수 있는 {1 \over 2} \|w\|^{2} 을 최소화하는 것이 더 쉽다.
    • 콰드라틱 프로그래밍에 대한 설명은 이 책의 범위를 넘어서는 것이므로 생략한다

슬랙 변수를 사용하여 비선형 분류 문제 다루기

  • 최대 마진 분류 이면에 있는 수학 개념에 너무 깊이 들어가지는 않을 것이다. 1995년 블라드미르 바프닉(Vladimir Vapnik)이 소개한 슬랙 변수 \zeta (zeta) 만 간략히 소개하겠다. 이를 소프트 마진 분류(soft maring classification)라고 한다.
    • 슬랙 변수는 선형적으로 구분되지 않는 데이터에서 선형 제약 조건을 완화할 필요가 있기 때문에 도입되었다.
    • 이를 통해 적절히 비용을 손해 보면서 분류 오차가 있는 상황에서 최적화 알고리즘이 수렴한다.
    • 양수 값은 슬랙 변수를 선형 제약 조건에 더하면 된다.

\begin{cases} w_{0} + w^{T} x^{(i)} \geq 1 & y^{(i)} = 1 - \zeta^{(i)} \\ w_{0} + w^{T} x^{(i)} \leq -1 & y^{(i)} = -1 + \zeta^{(i)} \end{cases} (i = 1, 2, ...  N)

  • 여기서 N 은 데이터셋에 있는 샘플 개수이다.
  • 최소화할 새로운 목적 함수는 다음과 같다.

{1 \over 2} \|w\|^{2} + C(\sum_{i} \zeta^{(i)})

  • 변수 C 를 통해 분류 오차에 대한 비용을 조정할 수 있다.
    • C 값이 크면 오차에 대한 비용이 커진다. C   값이 작으면 분류 오차에 덜 엄격해 진다.
    • 매개변수 C 를 사용하여 마진 폭을 제어할 수 있고 아래 그림과 같이 편향-분산의 트레이드오프를 조정한다.

  • 이 개념은 규제와 관련이 있다. 이전 절에서 언급한 것처럼 규제가 있는 로지스틱 회귀 모델은 C 값을 줄이면 편향이 늘고 모델 분산이 줄어든다.
from sklearn.svm import SVC

svm = SVC(kernel='linear', C=1.0, random_state=1)
svm.fit(X_train_std, y_train)

plot_decision_regions(X=X_combined_std, y=y_combined, classifier=svm, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

사이킷런의 다른 구현

  • 이전 절에서 보았던 사이킷런 라이브러리의 LogisticRegression 클래스는 LIBLINEAR 라이브러리를 사용한다. 국립 타이완 대학교에서 개발한 것으로 최적화가 매우 잘된 C/C++ 라이브러리이다.
    • SVM을 훈련하는 SVC 클래스는 LIBSVM 라이브러리를 사용한다. 이 라이브러리는 SVM에 특화된 C/C++라이브러리이다.
    • 순수한 파이썬 구현에 비해 LIBLINEAR와 LIBSVM은 많은 선형 분류기를 아주 빠르게 훈련할 수 있는 장점이 있다.
  • 이따금 데이터셋이 너무 커서 컴퓨터 메모리 용량에 맞지 않는 경우에 사이킷런은 대안으로 SGDClassifier 클래스를 제공한다.
    • 이클래스는 partial_fit 메서드를 사용하여 온라인 학습을 지원한다.
    • SGDClasssfier 클래스 이면에 있는 개념은 2장에서 아달린을 위해 구현한 확률적 경사 하강법과 비슷하다.
    • 기본 매개변수를 사용한 퍼셉트론, 로지스틱 회귀, 서포트 벡터 머신의 확률적 경사 하강법 버전은 다음과 같다.
from sklearn.linear_model import SGDClassifier

ppn = SGDClassifier(loss='perceptron')
lr = SGDClassifier(loss='log')
svm = SGDClassifier(loss='hinge')

커널 SVM을 사용하여 비선형 문제 풀기

  • 머신러닝 기술자 사이에서 SVM이 인기가 높은 또 다른 이유는 비선형 분류 문제를 풀기 위해 커널 방법을 사용할 수 있기 때문이다.

선형적으로 구분되지 않는 데이터를 위한 커널 방법

  • 비선형 분류 문제가 어떤 모습인지 보기 위해 샘플 데이터셋을 만들어 보자.
    • 다음 코드에서 넘파이 logical_or 함수를 사용하여 XOR 형태의 간단한 데이터셋을 만든다.
import matplotlib.pyplot as plt
import numpy as np

np.random.seed(1)
X_xor = np.random.randn(200,2)
y_xor = np.logical_xor(X_xor[:,0] > 0, X_xor[:, 1] > 0)
plt.scatter(X_xor[y_xor == 1, 0], X_xor[y_xor == 1, 1], c='b', marker='x', label='1')
plt.scatter(X_xor[y_xor == -1, 0], X_xor[y_xor == -1, 1], c='r', marker='s', label='-1')
plt.xlim([-3, 3])
plt.ylim([-3, 3])
plt.legend(loc='best')
plt.tight_layout()
plt.show()
  • 위 코드를 실행하면 아래 그림과 같이 랜덤한 잡음이 섞인 XOR 데이터셋이 만들어진다.

  • 이런 데이터셋은 양성 클래스와 음성 클래스를 선형 초평면으로 구분할 수 없다.
    • 이렇게 선형적으로 구분되지 않는 데이터를 다루는 커널 방법(kernel method)의 기본 아이디어는 매핑 함수 \phi 를 사용하여 원본 특성의 비선형 조합을 선형적으로 구분되는 고차원 공간에 투영하는 것이다.
    • 아래 그림에서 볼 수 있듯이 2차원 데이터셋을 다음과 같은 투영을 통해 새로운 3차원 특성 공간으로 변환하면 클래스를 구분할 수 있다.

\phi(x_{1}, x_{2}) = (z_{1}, z_{2}, z_{3}) = (x_{1}, x_{2}, x_{1}^{2} + x_{2}^{2})

  • 고차원 공간에서 두 클래스를 구분하는 선형 초평면은 원본 특성 공간으로 되돌리면 비선형 결정 경계가 된다.

커널 기법을 사용하여 고차원 공간에서 분할 초평면 찾기

  • SVM으로 비선형 문제를 풀기 위해 매핑 함수 \phi 를 사용하여 훈련 데이터를 고차원 특성 공간으로 변환한다.
    • 그 다음 이 새로운 특성 공간에서 데이터를 분류하는 선형 SVM 모델을 훈련한다.
    • 동일한 매핑함수 \phi 를 사용하여 새로운 본 적 없는 데이터를 변환하고 선형 SVM 모델을 사용하여 분류할 수 있다.
  • 이런 매핑 방식의 한 가지 문제점은 새로운 특성을 만드는 계산 비용이 매우 비싸다는 것이다. 특히 고차원 데이터일 때 더욱 그렇다.
    • 여기에 소위 커널 기법이 등장하게 된다. SVM 을 훈련하기 위해 콰드라틱 프로그래밍 문제를 어떻게 푸는지 상세히 다루지는 않겠지만 실전에서 필요한 것은 점곱 x^{(i)T} x^{(j)} \phi (x^{(i)})^{T} \phi(x^{(j)}) 로 바꾸는 것이다.
    • 두 포인트 사이 점곱을 계산하는 데 드는 높은 비용을 절감하기 위해 커널 함수(kernel function) \mathcal{K} (x^{(i)}, x^{(j)}) = \phi(x^{(i)})^{T} \phi(x^{(j)}) 를 정의한다.
  • 가장 널리 사용되는 커널 중 하나는 방사 기저 함수(Radial Basis Function, RBF)이다. 가우시안 커널(Gaussian Kernel)이라고도 한다.

\mathcal{K} (x^{(i)}, x^{(j)}) = \exp({\|x^{(i)} - x^{(j)}\|^{2} \over 2 \sigma^{2}})

  • 간단하게 다음과 같이 쓰기도 한다.

\mathcal{K} (x^{(i)}, x^{(j)}) = \exp(-\gamma \|x^{(i)} - x^{(j)}\|^{2})

  • 여기서 \gamma = {1 \over 2 \sigma^{2}} 은 최적화 대상 파라미터가 아니다.
  • 대략적으로 말하면 커널(kernel)이란 용어를 샘플 간의 유사도 함수(similarity function)로 해석할 수 있다.
    • 음수 부호가 거리 측정을 유사도 점수로 바꾸는 역할을 한다.
    • 지수 함수로 얻게 되는 유사도 점수는 1(매우 유사)과 0(매우 다름) 사이의 범위를 가진다.
  • 이제 커널 SVM을 훈련하여 XOR 데이터를 구분하는 비선형 결정 경계를 그려보겠다.
svm = SVC(kernel='rbf', random_state=1, gamma=0.10, C=10.0)
svm.fit(X_xor, y_xor)

plot_decision_regions(X=X_xor, y=y_xor, classifier=svm)

plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

  • gamma=0.1로 지정한 매개변수 \gamma 를 가우시안 구(Gaussian Sphere)의 크기를 제한하는 매개변수로 이해할 수 있다.
    • \gamma 값을 크게 하면 서포트 벡터의 영향이나 범위가 줄어든다. 결정 경계는 더욱 샘플에 가까워지고 구불구불해진다.
    • \gamma 를 잘 이해하기 위해 붓꽃 데이터셋에서 RBF 커널 SVM을 적용해 보자
svm = SVC(kernel='rbf', random_state=1, gamma=0.2, C=1.0)
svm.fit(X_train_std, y_train)

plot_decision_regions(X=X_combined_std, y=y_combined, classifier=svm, test_idx=range(105, 150))

plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()
  • 비교적 \gamma 값을 작게 했기 때문에 RBF 커널 SVM 모델이 만든 결정 경계는 아래 그림과 같이 부드럽다.

  • \gamma 값을 크게 하면 (위 코드에서 gamma를 100.0으로 설정)아래 그림과 같이 클래스 0과 클래스 1 주위로 결정 경계가 매우 가깝게 나타난다.

  • 이런 분류기는 훈련 데이터에서는 잘 맞지만 본 적 없는 데이터에서는 일반화 오차가 높을 것이다. 여기서 \gamma 매개변수가 과대적합을 조절하는 중요한 역할도 한다는 것을 알 수 있다.

결정트리 학습

  • 결정 트리(decision tree) 분류기는 설명이 중요할 때 아주 유용한 모델이다. 결정 트리라는 이름처럼 일련의 질문에 대한 결정을 통해 데이터를 분해하는 모델로 생각할 수 있다.

  • 결정 트리는 훈련 데이터에 있는 특성을 기반으로 샘플의 클래스 레이블을 추정할 수 있는 일련의 질문을 학습한다.
    • 위 그림은 범주형 변수를 사용한 결정 트리를 설명하고 있지만 동일한 개념이 붓꽃 데이터셋 같은 실수형 특성에도 적용된다.
    • 예컨대 꽃받침 너비 특성 축에 기준 값을 정하고 ‘꽃받침 너비가 2.8cm보다 큰가’라는 질문을 할 수 있다.
  • 결정 알고리즘을 사용하면 트리의 루트(root)에서 시작해서 정보 이득(Information Gain, IG)이 최대가 되는 특성으로 데이터를 나눈다.
    • 반복 과정을 통해 리프 노드(leaf node)가 순수해질 때까지 모든 자식 노드에서 이 분할 작업을 반복한다. 즉 이 노드의 모든 샘플은 동일한 클래스에 속한다.
    • 실제로 이렇게 하면 노드가 많은 깊은 트리가 만들어지고 과대적합될 가능성이 높다. 일반적으로 트리의 최대 깊이를 제한하여 트리를 가지치기(pruning) 한다.

정보 이득 최대화: 자원을 최대로 활용

  • 가장 정보가 풍부한 특성으로 노드를 나누기 위해 트리 알고리즘으로 최적화할 목적 함수를 정의한다. 이 목적 함수는 각 분할에서 정보 이득을 최대화한다.
    • 정보 이득은 다음과 같이 정의한다.

IG(D_{p}, f) = I(D_{P}) - \sum_{j=1}^{m} {N_{j} \over N_{p}} I(D_{j})

  • 여기서 f 는 분할에 사용할 특성이다.
    • D_{P}, D_{j} 는 부모와 j 번째 자식 노드의 데이터셋이다.
    • I 는 불순도(impurity) 지표이다.
    • N_{p} 는 부모 노드에 있는 전체 샘플 개수이다.
    • N_{j} j 번째 자식 노드에 있는 샘플 개수이다.
  • 여기서 볼 수 있듯이 정보 이득은 단순히 부모 노드의 불순도와 자식 노드의 불순도 합의 차이이다.
    • 자식 노드의 불순도가 낮을수록 정보이득이 커진다.
  • 구현을 간단하게 하고 탐색 공간을 줄이기 위해 (사이킷런을 포함해서) 대부분의 라이브러리는 이진 결정 트리를 사용한다.
    • 즉 부모 노드는 두 개의 자식 노드 D_{left}, D_{right} 로 나누어진다.

IG(D_{p}, f) = I(D_{P}) - {N_{left} \over N_{p}} I(D_{left}) - {N_{right} \over N_{p}} I(D_{right})

  • 이진 결정 트리에 널리 사용되는 세 개의 불순도 지표 또는 분할 조건은 지니 불순도(Gini impurity, I_{G} ), 엔트로피(entropy, I_{H} ), 분류 오차(Classification error, I_{g} )이다.
    • 샘플이 있는 모든 클래스 (p(i|t) \neq 0) 에 대한 엔트로피 정의는 다음과 같다.

I_{H}(t) = -\sum_{i=1}^{c} p(i|t) \log_{2} p(i|t)

  • 여기서 p(i|t) 는 특정 노드 t 에서 클래스 i 에 속한 샘플 비율이다.
    • 한 노드의 모든 샘플이 같은 클래스이면 엔트로피는 0이 된다. 클래스 분포가 균등하면 엔트로피는 최대가 된다.
    • 예컨대 이진 클래스일 경우 p(i=1|t) = 1 또는 p(i=0|t) = 0 이면 엔트로피는 0이다.
    • 클래스가 p(i=1|t) = 0.5 p(i=0|t) = 0.5 처럼 균등하게 분포되어 있으면 엔트로피는 1이 된다.
    • 엔트로피 조건을 트리의 상호 의존 정보를 최대화하는 것으로 이해할 수 있다.
  • 자연스럽게 지니 불순도는 잘못 분류될 확률을 최소화하기 위한 기준으로 이해할 수 있다.

I_{G}(t) = \sum_{i=1}^{c} p(i|t)(1 - p(i|t)) = 1 - \sum_{i=1}^{c} p(i|t)^{2}

  • 엔트로피와 비슷하게 지니 불순도는 클래스가 완벽하게 섞여 있을 때 최대가 된다. 예컨대 이진 클래스 환경 (c = 2 )에서는 다음과 같다.

I_{G}(t) = 1 - \sum_{i=1}^{c} 0.5^{2} = 0.5

  • 실제로는 지니 불순도와 엔트로피 모두 매우 비슷한 결과가 나온다. 보통 불순도 조건을 바꾸어 트리를 평가하는 것보다 가지치기 수준을 바꾸면서 튜닝하는 것이 훨씬 낫다.
  • 또 다른 불순도 지표는 분류 오차이다.

I_{E} = 1 - max \{p(i|t)\}

  • 가지치기에는 좋은 기준이지만 결정 트리를 구성하는데는 권장되지 않는다. 노드의 클래스 확률 변화에 덜 민감하기 때문이다. 아래 그림의 두 개의 분할 시나리오를 보면서 이를 확인해 보겠다.

  • 부모 노드에서 데이터셋 D_{P} 로 시작한다. 이 데이터셋은 클래스 1이 40개 샘플, 클래스 2가 40개의 샘플로 이루어져 있다.
    • 이를 두 개의 데이터셋 D_{left}, D_{right} 으로 나눈다. 분류 오차를 분할 기준으로 사용했을 때 정보 이득은 시나리오 A, B가 동일하다. (IG_{E} = 0.25 )

I_{E}(D_{P}) = 1 - 0.5 = 0.5

A:I_{E}(D_{left}) = 1 - {3 \over 4} = 0.25

A:I_{E}(D_{right}) = 1 - {3 \over 4} = 0.25

A:IG_{E} = 0.5 - {4 \over 8} 0.25 - {4 \over 8} 0.25 = 0.25

B:I_{E}(D_{left}) = 1 - {4 \over 6} = 0.33

A:I_{E}(D_{right}) = 1 - 1 = 0

A:IG_{E} = 0.5 - {6 \over 8} \times {1 \over 3} - 0 = 0.25

  • 지니 불순도는 시나리오 A(IG_{G} = 0.125 ) 보다 B(IG_{G} = 0.1\bar{\bar{6}} )가 더 순수하기 때문에 값이 더 높다.

I_{G}(D_{P}) = 1 - (0.5^{2} + 0.5^{2}) = 0.5

A:I_{G}(D_{left}) = 1 - (({3 \over 4}^{2} + {1 \over 4}^{2})) = 0.375

A:I_{G}(D_{right}) = 1 - (({1 \over 4}^{2} + {3 \over 4}^{2})) = 0.375

A:IG_{G}(D_{left}) = 0.5 - {4 \over 8}0.375 - {4 \over 8}0.375 = 0.125

B:I_{G}(D_{left}) = 1 - (({2 \over 6}^{2} + {4 \over 6}^{2})) = 0.\bar{4}

B:I_{G}(D_{right}) = 1 - (1^{2} + 0^{2}) = 0

B:IG_{G} = 0.5 - {6 \over 8}0.\bar{4} - 0 = 0.1\bar{\bar{6}}

  • 비슷하게 엔트로피 기준도 시나리오 A(IG_{H} = 0.19 )보다 시나리오 B(IG_{H} = 0.31 )를 선호한다.

I_{H}(D_{P}) = - (0.5 \log_{2} (0.5) + 0.5 \log_{2} (0.5)) = 1

A:I_{H}(D_{left}) = -(({3 \over 4} \log_{2}({3 \over 4}) + {1 \over 4} \log_{2} ({1 \over 4}))) = 0.81

A:I_{H}(D_{right}) = -(({1 \over 4} \log_{2}({1 \over 4}) + {3 \over 4} \log_{2} ({3 \over 4}))) = 0.81

A:IG_{H} = 1 - {4 \over 8}0.81 - {4 \over 8}0.81 = 0.19

B:I_{H}(D_{left}) = - (({2 \over 6} \log_{2}({2 \over 6}) + {4 \over 6} \log_{2} ({4 \over 6}))) = 0.92

B:I_{H}(D_{right}) = 0

B:IG_{H} = 1 - {6 \over 8}0.92 - 0 = 0.31

  • 3개의 불순도를 시각화하면 아래와 같다.

결정 트리 만들기

  • 결정 트리는 특성 공간을 사각 격자로 나누기 때문에 복잡한 결정 경계를 만들 수 있다. 결정 트리가 깊어질 수록 결정 경계가 복잡해지고 과대적합되기 쉽기 때문에 주의해야 한다.
    • 사이킷런을 사용하여 지니 불순도 조건으로 최대 깊이가 4인 결정 트리를 훈련해 본 결과는 다음과 같다.
from sklearn.tree import DecisionTreeClassifier

tree = DecisionTreeClassifier(criterion='gini', max_depth=4, random_state=1)
tree.fit(X_train, y_train)

X_combined = np.vstack((X_train, X_test))
y_combined = np.hstack((y_train, y_test))

plot_decision_regions(X=X_combined, y=y_combined, classifier=tree, test_idx=range(105, 150))

plt.xlabel('petal length [cm]')
plt.ylabel('petal width [cm]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

  • 사이킷런은 훈련한 후 결정 트리를 .dot 파일로 추출할 수 있는 기능을 갖고 있는데, GraphViz 프로그램을 이용하면 이 파일을 시각화 할 수 있다.
    • (이하 프로그램 설치하는 내용 생략)

랜덤 포레스트로 여러 개의 결정 트리 연결

  • 랜덤 포레스트(random forest)는 뛰어난 분류 성능, 확장성, 쉬운 사용법 때문에 지난 10년간 머신러닝 애플리케이션에서 큰 인기를 누렸다.
    • 랜덤 포레스트는 결정 트리의 앙상블(ensemble)로 생각할 수 있다.
    • 랜덤 포레스트 이면의 아이디어는 여러 개의 (깊은) 결정 트리를 평균 내는 것이다.
    • 개개의 트리는 분산이 높은 문제가 있지만 앙상블은 견고한 모델을 만들어 일반화 성능을 높이고 과대적합의 위험을 줄여준다.
  • 랜덤 포레스트의 알고리즘은 다음 4단계로 요약할 수 있다.
    1. n개의 랜덤한 부트스트랩(bootstrap) 샘플을 뽑는다(훈련 세트에서 중복을 허용하면서 랜덤하게 n개의 샘플을 선택한다)
    2. 부트스트랩 샘플에서 결정 트리를 학습한다. 각 노드에서 다음과 같이 한다.
      1. 중복을 허용하지 않고 랜덤하게 d개의 특성을 선택한다.
      2. 정보 이득과 같은 목적 함수를 기준으로 최선의 분할을 만드는 특성을 사용해서 노드를 분할한다.
    3. 단계 1, 2를 k번 반복한다.
    4. 각 트리의 예측을 모아 다수결 투표(majority voting)로 클래스 레이블을 할당한다.
  • 단계 2에서 각각의 결정 트리를 훈련할 때 조금 다른 점이 있다. 각 노드에서 최선의 분할을 찾기 위해 모든 특성을 평가하는 것이 아니라 랜덤하게 선택된 일부 특성만 사용한다.
  • 랜덤 포레스트는 결정 트리만큼 해석이 쉽지 않지만 하이퍼 파라미터 튜닝에 많은 노력을 기울이지 않아도 되는 것이 큰 장점이다.
    • 일반적으로 랜덤 포레스트는 가지치기할 필요가 없다. 앙상블 모델이 개별 결정 트리가 만드는 잡음으로부터 매우 안정되어 있기 때문이다.
    • 실전에서 신경 써야 할 파라미터는 랜덤 포레스트가 만들 트리 개수(단계 3) 하나이다.
    • 일반적으로 트리 개수가 많을수록 계산 비용이 증가하는 만큼 랜덤 포레스트 분류기의 성능이 좋아진다.
  • 실전에서 자주 사용되지는 않지만 랜덤 포레스트 분류기에서 최적화할만한 다른 하이퍼 파라미터는 부트스트랩 샘플의 크기 n(단계 1)과 각 분할에서 무작위로 선택할 특성 개수 d(단계 2-a)이다.
    • 부트스트랩 샘플의 크기 n을 사용하면 랜덤 포레스트의 편향-분산 트레이드 오프를 조절할 수 있다.
  • 부트스트랩 샘플 크기가 작아지면 개별 트리의 다양성이 증가한다. 특정 훈련 샘플이 부트스트랩 샘플에 포함될 확률이 낮기 때문이다.
    • 결국 부트스트랩 샘플 크기가 감소하면 랜덤 포레스트의 무작위성이 증가하고 과대적합의 영향이 줄어든다.
    • 일반적으로 부트스트랩 샘플이 작을수록 랜덤 포레스트의 전체적인 성능이 줄어든다. 훈련 성능과 테스트 성능 사이에 격차가 작아지지만 전체적인 테스트 성능이 감소하기 때문이다.
    • 반대로 부트스트랩 샘플 크기가 증가하면 과대적합 가능성이 늘어난다. 부트스트랩 샘플과 개별 결정 트리가 서로 비슷해지기 때문에 원본 훈련 데이터셋에 더 가깝게 학습된다.
  • 사이킷런의 RandomForestClassifier를 포함하여 대부분의 라이브러리에서는 부트스트랩 샘플 크기를 원본 훈련 세트의 샘플 개수와 동일하게 해야 한다.
    • 보통 이렇게 하면 균형 잡힌 편향-분산 트레이드오프를 얻는다.
    • 분할에 사용할 특성 개수 d는 훈련 세트에 있는 전체 특성 개수보다 작게 지정하는 편이다.
    • 사이킷런과 다른 라이브러리에서 사용하는 적당한 기본값은 d = \sqrt{m} 이다. 여기서 m 은 훈련 세트에 있는 특성 개수이다.
from sklearn.ensemble import RandomForestClassifier

forest = RandomForestClassifier(criterion='gini', n_estimators=25, random_state=1, n_jobs=2)
forest.fit(X_train, y_train)

X_combined = np.vstack((X_train, X_test))
y_combined = np.hstack((y_train, y_test))

plot_decision_regions(X=X_combined, y=y_combined, classifier=forest, test_idx=range(105, 150))

plt.xlabel('petal length')
plt.ylabel('petal width')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

k-최근접 이웃: 게으른 학습 알고리즘

  • k-최근접 이웃(K-Nearest Neighbor, KNN)은 앞선 학습 알고리즘과 근본적으로 다른데, KNN은 전형적인 게으른 학습기(lazy learner)이다.
    • 훈련 데이터에서 판별 함수(discriminative function)를 학습하는 대신 훈련 데이터셋을 메모리에 저장하기 때문이다.
  • (참고)
    • 머신러닝 알고리즘은 모수 모델(parametric model)과 비모수 모델(nonparametric model)로 묶을 수 있다.
    • 모수 모델은 새로운 데이터 포인트를 분류할 수 있는 함수를 학습하기 위해 훈련 데이터셋에서 모델 파라미터를 추정한다. 훈련이 끝나면 원본 훈련 데이터셋이 더는 필요하지 않다. 전형적인 모수 모델은 퍼셉트론, 로지스틱 회귀, 선형 SVM이다.
    • 비모수 모델은 고정된 개수의 파라미터로 설명될 수 없다. 훈련 데이터가 늘어남에 따라 파라미터 개수도 늘어난다. 비모수 모델 예는 결정 트리/랜덤 포레스트와 커널 SVM이다.
    • KNN은 비모수 모델에 속하며 인스턴스 기반 모델이라 한다. 인스턴스 기반 모델은 훈련 데이터셋을 메모리에 저장하는 것이 특징이다.
    • 게으른 학습은 인스턴스 기반 학습의 특별한 경우이며 학습 과정에 비용이 전혀 들지 않는다.
  • KNN 알고리즘은 매우 간단해서 다음 단계로 요약할 수 있다.
    1. 숫자 k와 거리 측정 기준을 선택한다.
    2. 분류하려는 샘플에서 k개의 최근접 이웃을 찾는다.
    3. 다수결 투표를 통해 클래스 레이블을 할당한다.

  • 위 그림은 새로운 데이터 포인트(물음표로 표시된 포인트)가 어떻게 이웃 다섯 개의 다수결 투표를 기반으로 삼각형 클래스 레이블에 할당 되는지를 보여준다.
  • 선택한 거리 측정 기준에 따라 KNN 알고리즘이 훈련 데이터셋에서 분류하려는 포인트와 가장 가까운 샘플 k개를 찾는다.
    • 새로운 데이터 포인트의 클래스 레이블은 이 k개의 최근접 이웃에서 다수결 투표를 하여 결정된다.
  • 이런 메모리 기반 방식의 분류기는 수집된 훈련 데이터에 즉시 적응할 수 있는 것이 주요 장점이다. 새로운 샘블을 분류하는 계산 복잡도는 단점이다.
    • 데이터셋의 차원(특성)이 적고 알고리즘이 KD-트리 같은 효율적인 데이터 구조로 구현되어 있지 않다면 최악의 경우 훈련 데이터셋의 샘플 개수에 선형적으로 증가한다.
    • 또 훈련 데이터가 없기 때문에 훈련 샘플을 버릴 수 없다. 대규모 데이터셋에서 작업한다면 저장 공간에 문제가 생긴다.
from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski')
knn.fit(X_train_std, y_train)

plot_decision_regions(X=X_combined_std, y=y_combined, classifier=knn, test_idx=range(105, 150))

plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

  • 적절한 k를 선택하는 것은 과대적합과 과소적합 사이에서 올바른 균형을 잡기 위해 중요하다. 데이터셋의 특성에 알맞은 거리 측정 지표를 선택해야 한다.
    • 붓꽃 데이터셋의 센티미터 단위를 가진 특성처럼 시룻 값을 가진 특성에는 보통 간단한 유클리디안 거리를 사용한다.
    • 유클리디안 거리를 사용하려면 각 특성이 동일하게 취급되도록 표준화를 하는 것이 중요하다.
    • 위 코드에서 사용한 minkowski 거리를 유클리디안 거리와 맨해튼 거리를 일반화한 것으로 다음과 같이 쓸 수 있다.

d(x^{(i)}, x^{(j)}) = \sqrt[p]{\sum_{k} |x_{k}^{(i)} - x_{k}^{(j)}|^{p}}

  • 매개변수 p=2 로 지정하면 유클리디안 거리가 되고 p=1 로 지정하면 맨해튼 거리가 된다.
    • 사이킷런에는 다른 거리 측정 기준이 많으며 metric 매개변수로 지정할 수 있다.

21세기 자본/ 21세기 자본-노동의 소득분배

자본/소득 비율에서 자본-노동 소득분배율까지

  • 자본수익률은 어떻게 결정되는가? 이를 살펴보기 위해 영국과 프랑스의 경우를 활용했다.
    • 비록 U자 곡선의 깊이가 확연히 나타나지는 않지만 국민소득 중 자본소득의 몫 \alpha 의 일반적인 변화 추이는 \beta 와 같은 U 자 곡선으로 설명될 수 있다.
    • 다시 말해 자본수익률 r 은 자본량 \beta 의 변화를 약화시켰던 것으로 보인다. 즉, r \beta 가 낮은 기간에는 높고 \beta 가 높은 기간에는 낮게 나타난다.
  • 좀 더 정확히 말하면 18세기 후반부터 19세기에 영국과 프랑스에서 국민 소득 중 자본소득의 몫이 35-40%였는데, 20세기 중반에는 20-25%로 줄어들었고, 20세기 말과 21세기 초에는 25-30%로 늘어났다.

  • 이것은 18세기와 19세기의 5-6% 정도의 평균 자본수익률과 상응하는데, 이 수익률은 20세기 중반에는 7-8%로 높아졌고 20세기 후반과 21세기 초반에는 2-5%로 떨어졌다.

  • 실제 수익률은 자산 형태와 개인 재산의 규모에 따라 크게 달라지며 불평등을 증대시키는 경향이 있다.
    • 구체적으로 산업자본을 포함해 위험부담이 가장 큰 자산의 수익률은 흔히 7-8% 이상인 반면, 위험부담이 적은 자산의 수익률은 상당히 낮다.
    • 18세기와 19세기의 농경지는 4-5%였고, 21세치 초반의 부동산은 3-4%정도 이다.
    • 당좌예금이나 저축예금 등 소규모 자산의 실질수익률은 겨우 1-2% 밖에 안되거나 심지어 더 적고, 물가상승률이 은행의 낮은 명목이자율을 초과할 경우에는 마이너스가 되기도 한다.

소득이라는 유량: 한 시점의 총량보다 더 평가하기 어려운 것

  • 자본에 대한 보상이 포함되어 있을 비임금노동자들의 소득과 관계된 또 다른 중요한 주의 사항은 이러한 소득을 다른 소득과 구분하기 어렵다는 것이다.
    • (이하 자료 평가가 어렵다는 내용 생략)

순수한 자본수익률이라는 개념

  • 도표 6.3-6.4에 표시된 평균 수익률이 어느정도 과대평가되어 있다고 생각하는 것은 이 때문이다. 따라서 나는 여기서 순수한(pure) 자본 수익률이라고 부를 수 있는 수익률을 표시한다.
    • (이하 자료 정리에 대한 내용 생략)

역사적 관점에서 본 자본수익률

  • 추정치를 바탕으로 얻어낸 중요한 결론은 다음과 같다.
    • 18세기와 20세기 초까지 프랑스와 영국의 순자본수익률은 연 4-5% 안팎을 오르내렸으며 더 일반적으로는 3-6%를 오갔다. 장기적으로 뚜렷한 상향 추세나 하향 추세는 나타나지 않았다.
    • 두 차례 세계대전으로 인한 대규모의 재산 붕괴와 막대한 자본 손실이 나타난 이후 순수한 수익률은 6%를 넘었지만, 이후 과거의 낮은 수준으로 매우 빠르게 회귀했다.
    • 그러나 아주 장기적으로 본다면 순수한 자본수익률은 약간 하락했을 가능성이 있다. 왜냐하면 18세기와 19세기에는 순수한 자본수익률이 종종 4-5%를 웃돌았던데 반해, 21세기 초에는 자본/소득 비율이 과거의 높은 수준을 회복함에 따라 3-4%에 가까워진 것으로 보이기 때문이다.
  • 어쨌든 아주 장기간에 걸쳐 순수한 자본수익률이 사실상 안정적이라는 것은 이 연구에서 가장 중요한 사실이다.
  • 이 수치들을 좀 더 합리적인 관점에서 보기 위해 무엇보다 다음의 사실을 상기할 필요가 있다.
    • 18세기와 19세기에 자본이 소득으로 전환될 때 그 비율은 가장 흔하고 가장 위험이 적은 형태의 자본의 경우 보통 연 5%정도였다.
    • 즉 자본 자산의 가치는 그 자산에 의해 창출된 연간 소득의 20배에 해당되는 것으로 평가되었다. 때로 5배로 증가하기도 했다.
  • (이하 문학작품에서 나온 수치들 생략)

21세기 초의 자본수익률

  • (생략)

실질자산과 명목자산

  • (자료에서 명목과 실질 구분 내용 생략)
  • 인플레이션이 어떤 경우에는 자산 및 자산 수익률 그리고 자산의 분배에 실질적인 영향을 미칠 수 있다는 사실을 결코 부인하는 것은 아니다. 그러나 인플레이션의 효과는 장기간의 구조적 효과라기보다는 재체로 자산의 범주 사이에서 부를 재분배하는 효과다.
    • 앞서 인플레이션이 두 차례 세계대전 이후에 부유한 국가의 공공 부채를 청산하는데 사실상 중심적인 역할을 했음을 설명했다.
    • 하지만 높은 인플레이션이 상당 기간 지속되는 동안 투자자들은 실질자산에 투자함으로써 자신들의 자산을 지키려고 노력했다.
    • 당좌예금과 저축예금 같은 소규모 자산은 인플레이션에 심각하게 영향을 받는데 반해, 대규모 자산은 보통 장기적으로 물가와 가장 잘 연동되며 가장 다각화되어 있다고 믿을만한 충분한 이유가 있다.

자본은 무엇을 위해 사용되는가?

  • 자본시장과 노동시장을 모두 순수하고 완전한 경쟁 시장이라고 가정하는 가장 단순한 경제모형을 따를 경우 자본수익률은 자본의 한계생산성, 즉 자본을 한 단위 추가적으로 투입할 때 나오는 추가적인 생산과 정확히 일치해야 한다.
    • 하지만 좀 더 복잡하고 현실적인 모형 속에서 자본수익률은 다양한 관련 집단의 상대적인 협상력에 달려 있다.
    • 상황에 따라 자본수익률은 자본의 한계생산성보다 더 높을 수도 있고 낮을 수도 있다.
  • 어쨌든 자본수익률은 두 가지 힘에 의해 결정된다. 첫째는 기술이고 둘째는 자본총량의 규모다.
  • 기술은 당연히 핵심적인 역할을 한다.
  • 만약 자본이 생산요소로서 전혀 유용하지 않다면 자본의 한계생산성은 제로가 된다.
    • 자본수익률이 제로인 사회라면 국민소득과 국민생산은 노동의 몫이 될 것이다.
    • 이러한 사회를 충분히 상상할 수 있지만 모든 인간사회에서 상황은 이와 다르게 전개되었다.
  • 모든 문명사회에서 자본은 두 가지 경제적 기능을 수행한다.
    • 첫째, 자본은 주거 서비스를 창출한다.
    • 둘째, 자본은 다른 상품과 서비스를 만들어내는 과정에서 생산요소 (토지, 도구, 건물, 기계, 특허 등)의 역할을 한다.

자본의 한계생산성 개념

  • 자본의 한계생산성이란 자본 한 단위를 추가적으로 투입할 때 이뤄지는 추가적인 생산의 가치로 정의할 수 있다.
    • 예컨대 어떤 농업사회에서 한 사람이 00유로에 상당하는 토지 혹은 도구를 추가해 연간 5유로에 상당하는 식량 생산을 증가시킬 수 있다고 가정하자.
    • 이때 100유로의 투자에 대한 자본의 한계생산성은 연간 5유로 또는 5%라고 말할 수 있다.
  • 이처럼 순수하게 완전경쟁 조건일 때 한계생산성은 자본가가 노동자로부터 얻는 연간 수익률이다.
    • 만약 자본가가 5% 이상의 수익을 어드려고 한다면 노동자는 다른 자본가에서 토지와 도구를 빌려올 것이다.
    • 그리고 만약 노동자가 5% 미만의 수익을 지불하려 한다면 토지와 농기구는 다른 노동자에게 가게 될 것이다.
    • 분명 자본가가 토지나 도구를 임대한다든가 노동력을 구매하는데 독점적인 지위를 갖는 상황이 있을 수 있다. 후자의 경우 공급 독점이 아니라 ‘수요 독점’에 해당된다. 이럴 경우 자본가는 노동자에게 자기 자본의 한계생산성보다 더 큰 수익률을 요구할 수 있다.
  • 자본의 한계생산성이라는 개념은 해당 사회의 자본-노동 소득분배를 결정짓는 제도 및 규칙과 관계없이 정의된다는 점을 명백히 밝힐 필요가 있다.
    • 예컨대 토지와 도구의 소유주가 자신의 자본을 스스로 활용한다면 그 자신에게 투자한 자본의 수익을 별도로 따지지는 않을 것이다.
    • 그럼에도 그 자본은 여전히 유용하며, 한계생산성은 그에 따른 수익이 외부 투자자에게 지불되는 경우와 똑같을 것이다.
    • 이것은 자본의 전부 혹은 일부를 집산화하기로 결정한 경제체제에서나 혹은 구소련의 예처럼 개인의 자본 수익을 모두 없앤 극단적인 곳에서나 마찬가지로 적용된다.

너무 많은 자본은 자본수익률을 떨어뜨린다

  • 너무 많은 자본은 자본수익률을 하락시킨다. 자본-노동 소득분배의 구조를 결정하는 규칙이나 제도와는 상관없이, 자본총량이 증가할수록 자본의 한계생산성이 감소하리라는 것은 당연히 예상할 수 있다.
    • 이미 각 농업노동자가 수천 헥타르의 농지에서 농사를 짓고 있다면 1헥타르의 농지가 추가됨으로써 발생하는 추가적인 산출은 제한적일 것이다. 한계생산성은 어느한도를 넘어서면 감소하기 때문이다.
  • 흥미로운 질문은 자본총량이 증가할 때 자본의 한계생산성이 얼마나 빠르게 감소하느냐하는 것이다.
    • 특히 자본/소득 비율 \beta 가 증가할 때 자본수익률 r 이 얼마나 많이 하락하느냐가 핵심이다.
  • 두 가지 경우를 생각해 볼 수 있다.
    • 첫째, 자본수익률 r 이 자본/소득 비율 \beta 가 증가하는 비율보다 더 큰 폭으로 하락한다면 이는 \beta 가 증가할 때 국민소득에서 자본소득이 차지하는 몫 \alpha = r \times \beta 가 하락한다는 것을 의미한다. 다시말해 r 의 하락이 자본/소득 비율 \beta 의 증가를 상쇄하고도 남는 것이다.
    • 둘째, 자본수익률 r 이 자본/소득비율 \beta 가 증가하는 비율보다 덜 하락한다면 국민소득에서 자본소득이 차지하는 몫 \alpha = r \times \beta 는 증가한다. 이 경우 자본수익률 하락의 효과는 자본/소득 비율의 증가에 따라 자본의 몫이 증가하는 것을 막지는 못하고 다만 완화할 뿐이다.
  • 영국과 프랑스에서 관찰된 역사적 추이에 근거해서 살펴보면 장기적으로 두 번째 경우가 더 적절해 보인다.
    • 소득에서 자본이 차지하는 몫 \alpha 는 자본/소득 비율 \beta 와 마찬가지로 U자 곡선을 따라 간다.
    • 그러나 자본수익률의 역사적 변화는 \alpha 의 U자 곡선의 깊이를 상당히 축소시킨다. 이는 2차대전 이후 자본 부족 현상이 나타나면서 한계생산성 체감의 원리에 맞게 자본수익률이 크게 높아졌기 때문이다.
    • 그러나 그 영향이 자본/소득 비율을 나타내는 \beta 의 U자 곡선의 영향을 압도할 만큼 그리고 자본의 몫을 나타내는 \alpha 를 역 U자 곡선으로 바꾸어놓을만큼 강력하지는 않았다.
  • 그럼에도 불구하고 중요한 것은 두 경우 모두 이론적으로는 가능하다는 사실이다. 모든 것은 기술의 변화무쌍함에 달려 있다.
    • 더 정확히 말하면 모든 것은 한 사회가 소비하고자 하는 다양한 상품과 서비스를 생산하기 위해 자본과 노동을 결합시킬 수 있는 이용 가능한 다양한 기술의 종류에 달려 있다.
  • 이 문제에 관해 생각할 때 경제학자들은 흔히 ‘생산함수’의 개념을 사용한다.
    • 이는 해당 사회에 존재하는 기술적 가능성들을 반영한 수학 공식인데, 생산함수의 특징 중 하나는 자본과 노동 사이의 대체탄력성을 정의하는 것이다.
    • 즉, 필요한 상품과 서비스를 생산하기 위해 노동을 자본으로, 자본을 노동으로 대체하는 것이 얼마나 수월한지 그 정도를 측정하는 것이다.
  • 예컨대 생산함수의 계수가 완전히 고정되어 있다면 대체탄력성은 제로다. 즉 농부에게 더도 덜도 아닌 1헥타르의 농지와 하나의 도구만이 필요할 것이다.
    • 각 노동자가 100분의 1헥타르의 추가적인 농지 혹은 하나의 도구를 더 가진다고 해도 추가적인 자본의 한계생산성은 제로가 될 것이다.
  • 반대로 대체탄력성이 무한대라면 자본의 한계생산성은 이용 가능한 자본과 노동의 양으로부터 완전히 독립적이다. 특히 자본수익률이 고정되어 있고 그것이 자본의 양에 영향을 받지 않을 때는 더욱 그렇다.
    • 왜냐하면 이 경우 자본을 축적하면 언제나 고정된 비율로, 예컨대 자본 한 단위당 연 5% 혹은 10%라는 식으로 생산이 증가할 수 있기 때문이다.
    • 자본을 더 추가하는 것만으로 마음대로 생산을 증가시킬 수 있는 완전히 자동화된 경제를 생각하면 된다.
  • 이 두 극단적인 경우 모두 현실에서는 적절하지 않다. 중요한 문제는 노동과 자본 사이의 대체탄력성이 1보다 큰가 아니면 작은가 하는 것이다.
    • 대체탄력성이 0과 1사이라면 자본/소득 비율 \beta 의 증가는 자본의 한계생산성을 많이 감소시켜 자본의 몫 \alpha = r \times \beta 을 감소시키는 결과를 초래한다.
    •  반면 대체탄력성이 1보다 크면 자본/소득 비율 \beta 의 증가는 자본의 한계생산성을 제한적으로 감소시켜 자본의 몫 \alpha = r \times \beta 을 증가시키는 결과를 초래한다.
    • 탄력성이 정확히 1이라면 두 효과가 상쇄된다. 자본수익률 r 은 자본/소득 비율 \beta 의 증가와 같은 비율로 감소해 자본의 몫 \alpha = r \times \beta 은 변하지 않는다.

코브-더글러스를 넘어서: 자본-노동 소득분배율의 안정성에 관한 문제

  • 대체탄력성이 정확하게 1인 경우는 1928년 경제학자 찰스 코브(Charles Cobb)와 폴 더글러스(Paul Douglas)가 처음 제안한 소위 ‘코브-더글러스 생산함수’와 일치한다.
    • 코브-더클러스 생산함수에 따르면 어떤 상황이 일어나더라도 소득에서 차지하는 자본의 몫은 순수한 기술적 매개변수로 생각되는 고정된 계수 \alpha 와 항상 동일하다.
    • 예컨대 \alpha = 30% 라면 자본/소득 비율에 상관없이 자본소득은 국민소득의 30%를 차지할 것이다.
    • 저축률과 성장률이 장기적인 자본/소득 비율 \beta = {s \over g} 가 국민소득의 6배가 되도록 한다면, 자본수익률은 5%가 되고 그 결과 자본/소득 비율은 30%가 된다.
    • 장기적인 자본총량이 국민소득의 3배밖에 되지 않는다면 자본수익률은 10%로 상승할 것이다. 장기적인 \beta = {s \over g} 가 국민소득 10배에 해당될 정도의 저축률과 성장률을 기록한다면 자본수익률은 3%로 하락할 것이다.
    • 어떤 경우라도 자본의 몫은 항상 30%가 될 것이다.
  • 코브-더글러스의 생산함수는 2차대전 이후 일부는 긍정적이고 일부는 부정적인 이유로 그러기 단순하다는 이유로 경제학 교과서들에서 큰 인기를 끌었다.
    • 하지만 무엇보다 그 인기의 원인은 자본-노동 소득분배율의 안정성이 사회질서에 대한 상당히 평화롭고 조화로운 견해를 제시했기 때문이었다.
    • 사실 소득에서 자본이 차지하는 몫의 안정성은 비록 그것이 사실로 밝혀진다 해도 결코 조화로움을 보장하지 않는다. 왜냐하면 그 안정이 자본 소유와 소득 분배의 극단적이고도 옹호할 수 없는 불평등과 함께 나타날 수 있기 때문이다.
    • 게다가 통념과는 달리 국민소득 중 자본의 몫의 안정성은 결코 자본/소득 비율의 안정성을 의미하지 않는다. 자본/소득 비율은 시기나 국가에 따라 매우 다양한 수치를 보일 수 있고, 따라서 특히 자본 소유의 엄청난 국제적 불균형이 나타날 수도 있다.
  • 하지만 여기서 강조하고 싶은 점은, 역사적 현실은 완벽하게 안정적인 자본-노동 소득분배율이 시사하는 관념보다 훨씬 더 복잡하다는 것이다.
    • 코브-더글러스 가설은 때로는 특정한 시기나 부문들에 대한 훌륭한 추정이며 어쨌든 더 깊이 있는 생각을 발전시키기 위한 유용한 출발점이다.
    • 그러나 이 가설은 장기적으로, 단기적으로 또는 중기적으로 관찰된 역사적 패턴의 다양성을 만족스럽게 설명하지 못한다.
  • (이하 이론이 불완전하다는 내용 생략)

21세기 자본-노동의 대체: 1보다 큰 대체탄력성

  • 아주 장기적으로 볼 때 자본과 노동 간의 대체탄력성은 1보다 컸던 것으로 보인다.
    • 따라서 자본/소득 비율 \beta 의 증가는 국민소득 가운데 자본소득이 차지하던 몫인 \alpha 를 약간 증가시켰던 것으로 보이며, 그 반대의 경우도 마찬가지다.
    • 직관적으로도 대체탄력성이 1보다 크다는 것은 장기적으로 자본에 다양한 용도가 있다는 현실의 상황과 맞아떨어진다.
    • 게다가 관찰된 역사적 변화들은 자본으로 할 수 있는 새롭고 유용한 것들을 찾아내는 일이 언제든 가능하다는 것을 시사한다.
  • 21세기에 노동에 대한 자본의 대체탄력성이 1을 넘어 얼마나 더 커질 것인지를 예측하는 것은 분명 매우 어려운 일이다.
    • 역사적 자료에 따르면 1.3-1.6으로 추정할 수 있다.
  • 상대적으로 잘 정립된 단 하나의 이론은 부유한 국가들에서 최근 수십 년간 관찰된 자본/소득의 비율 \beta 가 상승세를 보이고 있다는 점이다.
    • 만약 21세기의 성장이 둔화된다면 \beta 의 상승세는 세계 곳곳으로 확산될 수 있다.
    • 또한 이런 추세는 아마도 국민 소득에서 자본이 차지하는 몫인 \alpha 를 지속적으로 증가시킬 것이다. 분명히 자본수익률 r \beta 가 증가함에 따라 하락할 가능성이 크다.
    • 하지만 역사적 경험에 따르면 결국 물량효과가 가격효과를 능가할 가능성이 훨씬 더 높다. 이는 자본축적의 효과가 자본수익률 하락의 효과를 능가할 것임을 의미한다.
  • 1970-2010년에 가장 부유한 국가들에서 자본/소득 비율이 증가한 만큼 국민소득 중 자본소득이 차지하는 몫이 증가했다는 것을 보여준다.
    • 그러나 이러한 상승은 대체탄력성이 1보다 크다는 사실 뿐만 아니라 자본의 이동성이 높아지고 투자를 유치하기 우해 국가 간의 경쟁이 격화된 수십 년 사이 노동에 대한 자본의 협상력이 강화된 사실과도 일치한다는 점에 주목해야 한다.
    • 이 두가지 효과는 수십 년간 서로를 강화했고, 이런 현상은 미래에소 지속될 가능성이 있다.
    • 어쨌든 \alpha 의 지속적인 상승과 함께 구준히 증가하는 \beta 의 상승을 막을 자기 조정 메커니즘이 없다는 점을 지적하는 것이 중요하다.

전통적 농경사회: 1보다 작은 대체탄력성

  • 노동을 자본으로 대체할 수많은 기회가 존재한다는 것이 현대 경제의 중요한 특징인데, 자본이 주로 토지 형태를 취했던 농경사회의 전통 경제에서는 이런 특징이 없었다는 점이 흥미롭다.
    • (결국 자본을 확장성 있는 사업 –기술 위주–에 투자하느냐, 확장성 없는 사업 –부동산– 에 투자하느냐가 중요한 듯)
    • 이용가능한 역사적 자료들은 전통적 농경사회에서 대체탄력성이 1보다 현저하게 낮았다는 점을 분명하게 시사한다.
    • 특히 미국이 유럽보다 토지가 훨씬 더 풍부했음에도 18세기와 19세기에 자본/소득 비율과 지대로 측정된 토지의 가치가 유럽보다 상당히 낮았던 이유를 설명할 수 있는 유일한 방법이다.
  • 이러한 사실은 매우 논리적인데, 만약 자본이 노동에 대한 준비된 대체물로 기능하려면 자본은 다양한 형태로 존재해야 한다.

인적자본은 환상에 불과한가?

  • 장기적으로 볼 때 국민소득에서 자본이 차지하는 몫은 1800-1810년 35-40%에서 2000-2010년 25-30%로 하락했고, 같은 기간 국민소득에서 노동이 차지하는 몫은 60-65% 에서 70-75%로 증가했다.
    • 노동의 몫이 늘어난 것은 단지 생산과정에서 노동이 더 중요해졌기 때문이다. 따라서 토지, 부동산 및 금융자본에 돌아갈 수익을 줄어들게 한 것은 바로 증대되고 있는 인적자본의 힘이다.
  • 이 해석이 옳다면 그것이 가리키는 변화는 실로 대단히 중요하다. 하지만 주의해야 할 점이 있다. 첫째, 앞서 지적한 바와 같이 현 시점에서는 자본/소득의 비율이 보이는 장기적인 변화를 완벽하게 판단하기 매우 어렵다는 점이다. 자본의 몫이 앞으로 수십 년동안 19세기 초반 수준으로 늘어날 가능성도 충분히 있다.
    • 비록 기술의 구조적 형태 그리고 자본과 노동의 상대적 중요성이 변하지 않거나 또는 기술의 변화가 조금 더디다고 해도 여전히 현재 자본/소득의 비율이 증가하는 현상은 국민소득에서 자본소득이 차지하는 몫을 역사적인 최고점까지, 어쩌면 그 이상 수즌으로 끌어올릴 것이다. 왜냐하면 노동에 대한 자본의 장기적 대체탄력성이 명백히 1보다 크기 때문이다.
    • 이것이 이 연구의 가장 중요한 교훈이다. 현대의 기술은 여전히 엄청난 양의 자본을 사용하며, 더 중요한 것은 자본이 여러 용도로 활용된다는 점이다.
    • 따라서 자본가는 자본수익률을 제로로 만들지 않으면서 어마어마한 자본을 축적할 수 있다.
    • 이 같은 상황에서는 비록 기술이 상대적으로 노동에 유리한 방식으로 변한다 할지라도 장기적으로 자본소득이 감소할 이유는 없는 것이다.
  • 둘째, 국민소득에서 자본소득이 차지하는 목스이 추정치가 35-40%에서 25-30%로 장기적인 감소를 보인 것은 분명 그럴듯하고 의미있는 변화지만, 인류 문명을 변화시킬 만큼은 아니다.
    • 분명 인간의 기술 수준은 지난 두 세기 동안 현저하게 발달해왔다.
    • 하지만 그만큼 산업, 금융 그리고 부동산의 자본총량 또한 엄청나게 증가했다.

자본-노동 소득분배율의 중기적 변화

  • 역사적으로 관찰된 가장 중요한 점은 의심의 여지 없이 1800-1860년에 걸친 산업혁명 초기에 소득에서 자본이 차지하는 몫이 증가했다는 사실이다.
    • 로버트 앨런이 제시한 영국에 대한 자료에 따르면 국민소득에서 자본이 차지하는 몫은 18세기 후반에서 19세기 초반에 걸쳐 35-40%에서 19세기 중반에는 45-50%로 10%가량 증가했다.
    • 이 자료는 이러한 상승이 1870-1900년에 자본의 몫이 비슷한 정도로 하락해 거의 상쇄되었고, 1900-1910년에 다시 약간 높아진 결과 벨 에포크 시대에 자본이 차지하는 몫은 프랑스 혁명기나 나폴레옹 시대와 크게 다를 바 없다는 사실을 보여준다.
    • 그러므로 우리는 장기간에 걸친 지속적인 경향보다는 오히려 ‘중기적’인 변동에 관해 말할 수 있다.
  • 19세기 전반에 국민소득의 10%가 자본으로 옮겨갔다는 사실은 결코 사소한 문제가 아니었다.
    • 좀 더 구체적으로 말하면 이 기간에 경제성장의 가장 많은 몫이 자본가의 이윤으로 돌아간 반면 임금은 정체되었기 때문이다.
    • 앨런은 그 원인을 주로 기술 변화로 인한 자본생산성의 증가와 농촌에서 도시로의 노동력의 대이동 때문이라고 설명했다.
  • 이용 가능한 역사적 자료를 보면 프랑스도 이와 비슷한 흐름을 따랐음을 알 수 있다.
    • 1810-1850년의 활발한 산업성장에도 불구하고 노동자의 임금은 심각하게 정체되어 있었음을 보여준다.
  • 임금과 이윤이 차지하는 몫의 이 같은 변화는 2차대전 이후 세 가지 서로 다른 국면을 거쳤음을 주목해야 한다.
    • 이윤은 1945년에서 1968년까지 가파르게 증가했고, 이어 1968년에서 1983년까지는 현격히 감소했다가. 1983년 이후 매우 급격하게 증가했으며 1990년대 초에 안정되었다.

마르크스와 이윤율 하락의 재검토

  • 마르크스에게 “부르주아는 제 스스로 무덤을 판다”라는 핵심적인 메커니즘은 ‘무한 축적의 원리’였다.
    • 다시 말해 자본가들은 끊임없이 증가하는 자본을 축적하는데, 이는 결국 참단한 이윤율, 즉 자본수익률의 하락으로 이어져 마침내 그들 스스로 몰락한다는 것이다.
    • 마르크스는 수학적 모형을 사용하지 않고 그의 산문이 반드시 명쾌한 것은 아니었기 때문에 그의 생각을 확실히 알기는 어렵지만, 그의 생각을 이해하기 위한 논리적이고 일관된 한 가지 방법은 동태적인 법칙 \beta = {s \over g} 를 성잘률 g 가 제로 혹은 제로에 근접하는 특별한 경우에서 검토하는 것이다.
  • g 는 생산성 증가율과 인구증가율의 합인 장기적인 구조적 성장률을 측정한 것이다.
    • 그러나 1950년대 로버트 솔로(Robert Solow)가 성장에 대한 전반적인 연구를 수행하기 전, 19세기와 20세기 초 모든 경제학자와 마찬가지로 마르크스의 머리속에는 생산성의 영구적이고 지속적인 향상에 의해 추동되는 구조적 성장이라는 개념이 명백히 정립되거나 공식화되어 있지 않았다.
    • 그 당시 암묵적인 가설은 생산의 증가, 특히 제조업 생산의 증가는 무엇보다 주로 산업자본의 축적으로 설명될 수 있다는 것이었다.
    • 다시 말해 더 많은 생산은 각각의 노동자가 더 많은 기계와 설비를 이용했기 때문이지 노동과 자본의 생산성 자체가 증가했기 때문은 아니라는 것이다.
    • 오늘날 사람들은 생산성 증가만이 장기적인 구조적 성장을 가능케 한다는 것을 알고 있다.
  • 구조적 성장이 없고 생산성과 인구증가율의 합인 g 가 제로일 경우 마르크스가 묘사했던 것과 아주 유사한 논리적 모순에 처하게 된다.
    • 저축률 s 가 플러스가 되는 순간부터, 즉 자본가가 권력을 키우고 이익을 보존하기 위해 혹은 자신의 삶의 수준이 이미 풍족한 상태에 이르렀다는 이유로 매년 더 많은 자본을 축적하는 순간부터 자본/소득 비율은 무한대로 상승한다.
    • 일반적으로 g 가 제로에 가까우면 장기적으로 자본/소득 비율 \beta = {s \over g} 는 무한대에 접근하게 된다.
    • 그리고 \beta 가 극도로 커지면 자본수익률 r 은 점점 더 낮아져 제로에 근접해야 한다.
    • 그렇지 않다면 소득에서 자본이 차지하는 몫 \alpha = r \times \beta 는 결국 전체 국민소득을 잠식해 버릴 것이다.
  • 마르크스가 지적한 동태적 모순은 따라서 현실적인 어려움에 직면하게 되는데, 이 문제를 해결할 유일한 논리적 탈출구는 자본축적 과정을 균형잡히게 할 수 있는 구조적 성장 뿐이다.
    • 법칙 \beta = {s \over g} 가 명확히 보여주듯이 새로운 자본의 단위들이 영원히 추가되는 것을 상쇄할 수 있는 요소는 생산성과 인구의 영원한 증가 뿐이다. 그렇지 않은 경우 자본가들은 정말로 자기 무덤을 판다.
    • (마르크스가 옳았다는 이야기)

두 케임브리지 논쟁을 넘어서

  • 자본축적과 동태적 균형의 가능성에 관한 문제가 계속 논란을 일으켰는데, 1950년대와 1960년대 있었던 유명한 ‘케임브리지 자본 논쟁’이 좋은 사례다. (영국의 케임브리지와 미국의 케임브리지 사이의 대결이므로 소위 ‘두 케임브리지 논쟁’이라 불린다)
  • 이 논쟁의 요점을 상기해 보자. 경제학자 로이 해러드(Roy Harrod)와 에브세이 도마(Evsey Domar)가 1930년대 후반에 처음 \beta = {s \over g} 공식을 명확하게 소개했을 때, 이 공식은 흔히 g = {s \over \beta} 로 변환되었다.
    • 특히 해러드는 1939년 \beta 는 이용가능한 기술에 의해 고정되므로(계수가 고정되어 있고 노동과 자본의 대체가 불가능한 생산함수의 경우처럼) 성장률은 전적으로 저축률에 의해 결정된다고 주장했다.
    • 만약 저축률이 10%이고 기술 여건상 자본/소득 비율이 5라면 생산 능력은 연 2% 성장하게 된다.
    • 그러나 성장률은 인구증가율과 반드시 일치해야 하므로 성장은 ‘면도날 위’에서 균형을 잡고 있는 본질ㅈ거으로 불안정한 과정이라는 결론에 이르게 된다.
    • 이 이론에서는 자본이 항상 너무 많거나 너무 적어서 과잉 설비와 투기적 거품을 초래하거나 실업을 유발한다. 그렇지 않으면 아마도 업종 부문과 시기에 따라 이 둘 모두를 유발할 것이다.
    • 해러드의 직관이 완전히 틀린 것은 아니었다. 그럼에도 불구하고 노동에 대한 자본의 대체탄력성이 장기간에 걸쳐 1보다 컸다는 사실이 보여주듯이, 자본/소득 비율은 장깆거으로 상대적인 신축성을 보인다.
  • 1948년 도마는 해러드보다 g = {s \over \beta} 법칙을 좀 더 낙관적이고 융통성 있는 방식으로 발전시켰다.
    • 도마는 저축률과 자본/소득 비율이 어느 정도로는 서로 조정 가능하다는 사실을 강조했다.
    • 더 중요한 것은 1956년 솔로가 처음 제시한 대체 가능한 생산요소들을 가진 생산함수인데, 이로써 해러드의 공식을 뒤집어 \beta = {s \over g}라고 쓰게 되었다.
    • 이 공식은 장기적으로 자본/소득 비율이 저축률과 경제의 구조적 성장률에 의해 조정되며, 그 반대가 아니라고 주장한다.
  • 그러나 1950년대와 1960년대에도 매사추세츠주 케임브리지에 기반을 둔 경제학자(솔로와 새뮤얼슨을 포함하며, 대체로 가능한 생산요소들을 가진 생산함수를 옹호했다)와 영국 케임브리지에서 근무하는 경제학자 사이에 논쟁은 계속되었다.
    • 영국 케임브리지 학자들은 성장이 항상 완벽한 균형을 이룬다고 주장하는 솔로의 모형이 케인스가 주장한 단기적인 변동을 지닌 중요성을 부정한다고 생각했다.
    • 솔로가 주장한 소위 신고전파 성장 모형이 승리를 거둔 것은 1970년대 이후였다.
  • (이하 위 논쟁이 무의미했고, 자료가 부족했다는 내용 생략)

저성장 체제 속에서의 자본의 귀환

  • 역사적 저성장기로 돌아가보자. 특히 인구 증가가 제로이거나 혹은 오히려 감소할 때, 자본의 몫은 필연적으로 증가한다.
    • 저성장 사회에서 대규모 자본총량이 재건되는 경향은 \beta = {s \over g} 법칙으로 나타낼 수 있으며, 다음과 같이 요약할 수 있다. 정체된 사회에서는 과거에 축적된 부가 자연히 매우 중요한 위치를 차지하게 된다.
  • 오늘날 유럽에서 자본/소득 비율은 이미 국민소득의 5-6배 정도로 증가했는데, 이는 18세기와 19세기, 1차대전 직전 수준과 비교해 약간 낮은 수치이다.
    • 세계적인 수준에서 볼 때 21세기에 자본/소득 비율이 이 수준을 유지하거나 더 높아지는 것이 전적으로 가능하다.
    • 저축률이 약 10%를 유지하고 장기간의 성장률이 약 1.5%에서 안정화 된다면, 글로벌 자본총량은 이론적으로 연소득의 6-7배까지 높아질 것이다.
    • 성장률이 1%로 떨어지면 자본총량은 소득의 10배까지 늘어날 가능성이 있다.

변덕스러운 기술

  • 2부의 주된 교훈은 역사적 과정에서 자본과 자본 소유로부터 나오는 소득의 중요성을 필연적으로 감소시킬 수 있는 자연발생적인 힘은 결코 존재하지 않는다는 것이다.
  • 요약하자면 생산성의 향상과 지식의 확산에 기초한 현대의 성장은 마르크스가 예견한 대재앙을 피해 자본축적 과정이 균형을 이루도록 만들었다. 그러나 뿌리 깊은 자본의 구조를 변화시키지는 못했다.

줄리언 반스의 아주 사적인 미술 산책

줄리언 반스의 아주 사적인 미술 산책

영국 작가 줄리언 반스가 쓴 미술 감상 에세이. 저자가 개인적으로 좋아하는 미술가와 그 작품에 대해 자신의 감상을 담았다. 그런데 나는 미술에 대해 문외한이라 책에 담긴 미술가와 그 작품들을 대부분은 몰라서 내용을 따라가기는 쉽지 않았다.

미술 감상에 조예가 깊은 작가의 에세이이기 때문에 책을 읽다 보면 마치 내가 작품을 구석 구석 들여다 보며 감상하는 듯한 느낌이 든다. 책에 소개되는 작품이나 작가에 대한 사전 지식이 있다면 보다 풍부하게 책의 내용을 음미할 수 있을 것 같다는 생각이 들었음.

작품 뿐만 아니라 작가의 삶에 대해서도 함께 짚고 있는데, 결국 작품이라는 것은 그 작가 개인의 성향과 그의 삶이 반영되어 나오는 것이기 때문에, 작품을 그저 눈으로만 바라보는 것보다는 풍부한 감상이 될 수 있었다.

다만 책에 담긴 작가나 작품들은 내 기준에서는 개인적인 인물이나, 일상적인 풍경 등을 주제로 그려진 그림들이었는데, 내 개인 성향상 사회적인 메시지를 담거나 어떤 패턴 –예컨대 에셔나 몬드리안 같은– 을 담은 작품이 내 취향에는 더 맞겠다는 생각이 들었다.

여튼 심미안이라는 것은 결국 부지런히 다니면서 작품들을 감상하며 경험을 쌓아야 길러지는 것이기에 기회가 닿는대로 부지런히 돌아다니면서 내가 좋아하는 작품을 찾고 그 작품을 만들어낸 작가와 그 당시 사회상을 함께 이해하며 길러야 겠다는 생각이 들었다.