복잡하지만 단순하게

복잡하지만 단순하게

제목에서 유추할 수 있듯이 복잡성, 복잡계에 대한 책. 개인적으로 복잡 네트워크를 좋아해서 관련한 책은 어지간하면 다 보려고 하기 때문에 신간이 나와서 읽었음.

이 책에서 주요하게 다뤄지는 내용은 자기조직화에 대한 것인데 읽다보니 자기조직화라는 것은 결국 네트워크 구조에서 일어나는 최적화 문제라는 생각이 좀 들었다. –더 생각해서 차후에 정리해 봐야지

이 전에 읽었던 복잡계 관련책 –<전체를 보는 방법> — 을 읽으면서도 느꼈지만, 책에서 다뤄지는 내용이 대부분은 예상이 가능해서 –아주 디테일하게 다루는 내용에는 차이가 있지만– 책 자체는 대단히 빨리 읽을 수 있었다. 나에게는 특별히 새롭게 느껴지는 내용은 없었고 복잡성 과학은 아직 갈길이 멀다는 생각이 들었음.

복잡성 과학이 아무래도 아직은 세상에서 일어나는 현상에 대해 설명하는 것에 그치고, 그 근원적으로 일어나는 일에 대한 설명은 부족하기 때문. 일어난 일에 대한 설명은 합리적이지만, 그 일이 일어난 근원적인 부분에 도대체 무엇이 존재했는지에 대한 설명은 부족하게 느껴짐. 마치 플라톤의 동굴 그림자처럼 사물의 실체는 못보고 그림자만 보고 유추를 하는게 아닌가 싶었다. –게다가 책에서는 너무 이것저것 창발(emergence) 을 가져다 붙이는 것이 아닌가 싶은 느낌도 들었다.

반 고흐, 영혼의 편지 1, 2

반 고흐, 영혼의 편지 1

고흐가 동생인 테오와 주고 받은 편지를 엮은 책.  고흐가 예술을 하는 사람으로써 자신이 하는 일에 대한 순수한 애정과 깊은 고뇌를 가졌는지를 알 수 있다. –2권은 친구와 주고 받은 편지를 엮은 것인데, 1권의 성공 덕에 출간된 느낌이라 고흐에 대해 큰 애착이 없다면 1권만 보는 게 나을 듯.

하지만 그 순수함이 그를 비극으로 몰고 갔음을 생각해 보면 세상은 참 아이러니하다는 생각이 들었다. 테오의 편지로부터 그의 말년에 그 당시 예술가로부터 서서히 인정 받는 시기였음을 알 수 있는데, 안타깝게도 그의 정신은 이미 그 전에 돌이킬 수 없는 상태에 빠졌고 –고갱과의 다툼 후에 잘랐던 귀가 계속 문제였을 것으로 추정– 결국 안타깝게도 생을 마감하게 되었기 때문.

나도 한 때 나의 일을 하는 시기에 심리적 압박을 참 많이 받았었는데, 나의 결과가 세상에 인정 받기 까지는 시차가 존재하게 마련이고, –10년-20년 어쩌면 평생 걸릴 수도 있다– 그 시기 동안 마음을 다스리며 그저 묵묵하게 이겨 내는 것 밖에는 없다는 생각이 들었다.

비단 예술가 뿐만 아니라 학문적이든 정치적이든 업적을 쌓은 사람들 위인들도 어릴 때부터 세상의 대접을 받은 경우는 극히 드물다. 그 사람들도 무시를 받던 시기가 있었고, 그 시기를 결국 이겨냈기 때문에 결국 우리가 아는 사람들이 된거일테니까. 편하게 산 사람은 존경 받지 못한다. 어려움을 이겨낸 사람이 존경 받는 것.

이상엽/ 해석학/ 해석함수

테일러급수 전개

  • (테일러 급수는 멱급수 –다항함수의 급수– 의 한 형태)
  • (어떤 함수가 어떤 한 포인트에서 해석적이라는 것은 그 점에서 테일러급수가 수렴한다는 뜻)
    • (해석적인 함수는 항상 그 함수에 대응되는 테일러급수 전개가 가능하다)

Def. [해석함수]

어떤 \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 에서의 해석함수라 한다.

Thm. [테일러급수 전개]

함수 f 가 구간 I 에서 해석함수이면 무한 번 미분가능하고 임의의 c \in I 에 대하여 구간 (c  -\delta, c + \delta) 에서

f(x) = \sum_{n=0}^{\infty} {f^{(n)}(c) \over n!}(x - c)^{n}

을 만족시키는 \delta > 0 이 존재한다. 이때 우변의 멱급수를 해석함수 f 의 테일러급수라 하고, 특히 c = 0 인 경우에는 매클로린급수라 한다.

  • (멱급수에서 a_{n} = {f^{(n)}(c) \over n!} 형태로 정의한 것이 테일러급수. 함수가 해석적이라면 위와 같이 a_{n}을 변환할 수 있다는 뜻)
  • (함수 f 가 해석적이면 f 는 무한번 미분 가능. 그러나 f 가 무한번 미분 가능하다고 해서 f 가 해석적인 것은 아님)

해석함수와 연산

여러가지 해석함수

  • (아래와 같은 각종 함수를 테일러급수 형태로 전개가 가능함. 다시 말해 다항함수로 표현 가능. 다만 정의된 구간 안에서만 가능)

{1 \over x} = 1 + (1-x) + (1-x)^{2} + ... \\= \sum_{n=0}^{\infty}(1-x)^{n}, (0 < x < 2)

\sqrt{x} = 1 - {1-x \over 2} - {(1-x)^{2} \over 8} - {(1-3)^{3} \over 16} - ... (0 < x < 2)

참고) y = {1 \over x} y = \sum_{k=0}^{n} (1-x)^{k} 의 그래프 비교

e^{x} = 1 + x + {x^{2} \over 2!} + ... \\= \sum_{n=0}^{\infty} {x^{n} \over n!}, (-\infty < x < \infty)

\ln x = (x-1) - {(x-1)^{2} \over 2} + {(x-1)^{3} \over 3} - ... \\= \sum_{n=1}^{\infty} {(-1)^{n+1} \over n}(x-1)^{n}, (0 < x \leq 2)

참고) y = e^{x} y = \sum_{k=0}^{n} {x^{k} \over k!} 의 그래프 비교

참고) y = \ln x y = \sum_{k=0}^{n} {x^{k} \over k!} 의 그래프 비교

\sin x = x - {x^{3} \over 3!} + {x^{5} \over 5!} - {x^{7} \over 7!} + ... \\= \sum_{n=0}^{\infty} {(-1)^{n} \over (2n + 1)!} x^{2n+1}, (-\infty < x < \infty)

\cos x = 1 - {x^{2} \over 2!} + {x^{4} \over 4!} - {x^{6} \over 6!} + ... \\= \sum_{n=0}^{\infty} {(-1)^{n} \over (2n)!} x^{2n}, (-\infty < x < \infty)

참고) y = \sin x y = \sum_{k=0}^{n} {(-1)^{k} x^{2k+1} \over (2k+1)!} 의 그래프 비교

해석함수의 연산

Thm 1. [해석함수의 사칙연산]

함수 f 는 개구간 I 에서 해석적이고 g 는 개구간 J 에서 해석적이면 다음이 성립한다.

  1. cf, f \pm g, fg I \cap J 에서 해석적이다. (단 c 는 상수)
  2. g(x_{0}) \neq 0 x_{0} \in I \cap J 에 대해 {f \over g} x = x_{0} 의 근방에서 해석적이다.

Thm 2. [해석함수의 합성]

함수 f 는 개구간 I 에서 해석적이고 g 는 개구간 J 에서 해석적일 때, f(I) \subset J 이면 합성함수 g \circ f I 에서 해석적이다.

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

정의

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 이다.

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