Run The Bridge

Chapter 4 머신러닝에 필요한 수학의 기본 본문

Python/머신러닝 그리고 AI

Chapter 4 머신러닝에 필요한 수학의 기본

anfrhrl5555 2020. 7. 22. 17:31
728x90


 와 같이 세로로 늘어 놓은 것을 세로 벡터라고 합니다.


c = [1 2], d = [1 3 5 4]와 같이 가로로 늘어 놓은 것을 가로 벡터라고 합니다.

 

벡터를 구성하는 숫자 하나하나를 요소라고 부르며 벡터가 가지는 요소의 수를 벡터의 차원이라고 합니다.

 

앞의 예에서 a2차원 세로벡터, d4차원의 가로 벡터입니다.

 

일반적인 숫자의 묶음(집합)스칼라라고 부릅니다.


T라는 기호는 벡터의 오른쪽 위에 쓰며, 세로 벡터를 가로 벡터로, 가로 벡터를 세로 벡터로 변환한다는 의미입니다.

T를 전치라고 부릅니다.




파이썬으로 벡터를 정의하기

1
Import numpy as np
cs
1
2
= np.array([21])
print(a)
cs

결과: [2, 1]

type: <class 'numpy.ndarray'>



세로 벡터를 나타내기

1
2
= np.array([[1,2], [3,4]])
print(c)
cs


결과: 



2 x 1의 2차원 배열을 만들면, 세로 벡터를 만들 수 있습니다.

1
2
= np.array([[1], [2]])
print(d)
cs


결과: 




전치로 나타내기

전치는 '변수명.T'로 나타냅니다.

1
print(d.T)
cs

결과: [[1 2]]



덧셈과 뺄셈


벡터의 덧셈 a+b는 각 요소를 더하면 됩니다.


벡터의 뺼셈 a-b는 각 요소를 빼면 됩니다.


스칼라의 곱셈


스칼라에 벡터를 곱하면 스칼라 값이 벡터의 요소 전체에 적용됩니다. 다음의 예를 들면

파이썬에서는

1
print(2 * a)
cs

결과: [4 2]


도형으로 보면 벡터의 크기가 스칼라 배가 됩니다.



내적

벡터에는 내적이라고 부르는 곱셈 연산이 있으며, 머신러닝의 수학에서 자주 등장합니다.


내적은 같은 차원을 가진 두 벡터 간의 연산에서 "·"로 나타냅니다.


대응하는 요소들을 곱한 뒤 더한 값을 취합니다.


파이썬에서는 변수명1.dot(변수명2)로 내적을 계산합니다.

1
2
3
= np.array([13])
= np.array([42])
print(b.dot(c))
cs

결과: 10



벡터의 크기

| 와 | 사이에 나타냅니다. 2차원 벡터의 크기는 다음과 같이 계산이 가능합니다. 


3차원 벡터의 크기는 다음과 같습니다.



일반적으로 D차원 벡터의 크기는 다음과 같습니다.



파이썬에서는 np.linalg.norm()으로 벡터의 크기를 구할 수 있습니다.

1
2
= np.array([13])
print(np.linalg.norm(a))
cs

결과: 3.1622776601683795



합의 기호


n을 1부터 5까지 바꾸면서 모두 더한다는 의미입니다.


∑는 긴 덧셈을 간결하게 나타내는 방법입니다.


∑기호의 오른쪽 f(n)에 대해서, n을 a부터 1씩 늘려 b가 될 때까지 변화시키고, 모두 더한다는 의미를 가집니다



예를 들어 이면  프로그래밍에서 말하는 for문과 같습니다.



 합의 기호가 들어간 수식을 변형시키기


머신러닝 문제를 생각하는 과정에서 합의 기호가 들어간 수식을 변형하는 경우가 많습니다.


다음의 예를 참고하세요



f(n)이 '스칼라 x n의 함수'인 경우는 스칼라를 합의 기호 밖에 낼 수 있습니다.



의 내적은 다음과 같습니다.



위의 그림에 왼쪽은 '행렬표기(벡터표기)', 오른쪽은 '성분 표기'라고 부르며, 양자를 왔다갔다하는 식입니다.



합을 내적으로 계산하기


벡터의 내적으로 나타내므로, 파이썬에서는 for문을 사용하지 않아도 내적으로 계산할 수 있습니다.

1
2
3
4
import numpy as np
= np.ones(1000)  # [1 1 1 1 ... 1]
= np.arange(11001)  # [1 2 3 .... 1000]
print(a.dot(b))

cs

결과: 500500.0


곱의 기호

합의 기호 ∑ 와 사용법이 비슷한 곱의 기호 ∏도 있습니다. ∏의 경우에는 다음과 같이 모든 것을 곱합니다.


다음은 가장 간단한 예입니다.


다항식에 곱의 기호를 작용하는 예입니다.



미분

머신러닝은 결국 함수에서 최소나 최대인 입력을 찾는 문제입니다.


함수의 최소 지점은 기울기가 0이 되는 성질이 있으므로 이러한 문제를 풀려면 함수의 기울기를 잘 아는것이 중요합니다.


그 함수의 기울기를 도출하는 방법으론 '미분'이 사용되며, 오차 함수(error function)의 최솟값을 구하기 위한 방법으로 미분(편미분)이 나옵니다.



을 미분하면 가 되므로

 w = -1일 때, 기울기는 -2 

 w =1 일 때, 기울기는 2이다.



함수 f(w)의 w에 관한 미분은 다음과같이 다양한 표시 방법이 있습니다.


   ,      ,    


미분은 함수의 기울기를 나타냅니다 함수의 기울기도 w가 바뀌면 변화하기 때문에 w의 함수로 되어 있습니다.



일반적으로 w^2형식의 함수는 다음의 공식을 사용하여 미분을 쉽게 구할 수 있습니다.




1차 함수는 직선이므로 어떤 w에서도 기울기는 바뀌지 않습니다.



중첩된 함수의 미분


간단하게 g(w)를 f(w)에 대입하고 그 식을 전개하여 미분해 푼다.



중첩된 함수의 미분: 연쇄 법칙


연쇄 법칙의 공식: 



먼저 df / dg 부분은 'f를 g로 미분한다'는 의미로 미분 공식을 적용하면 다음과 같습니다.


후반의 dg/ dw는 g를 w로 미분한다는 의미이며, 다음과 같습니다



이 연쇄 법칙은 삼중, 사중으로 확장할 수 있습니다.





편미분

머신러닝에서 실제로 사용하는 것은 편미분입니다.


복수의 변수를 갖는 함수의 예로, w0과 w1의 함수를 생각합니다.


이 중 하나의 변수만, 예를 들어 w0에만 주목하여 다른 변수, 여기서는 w1를 상수라고 간주하여 미분하는 것을 편미분이라 합니다.


으로 나타냅니다.

편미분은 그 함수의 편미분한 변수 방향에서의 '기울기'




w0에서 미분한 결과: 



w1에서 미분한 결과: 


w0 과 w1에 대한 편미분은 각각 w0방향의 기울기, w1방향의 기울기를 부여할 수 있습니다.


편미분에 의해 임의의(w0, w1)에서 두 방향의 기울기를 계산할 수 있습니다.


이 두 기울기를 세트로 하여 벡터로 해석할 수 있습니다. 이것을 f의 w에 대한 경사라고 부르며, 경사는 기울기가 가장 큰 방향과 그 크기를 나타냅니다.


으로 표현이 가능합니다.



경사를 그림으로 나타내기

f를 등고선으로 표시하고, w의 공간을 격자형으로 나눴을 때 각 점에서의 경사 를 화살표로 그립니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
import matplotlib.pyplot as plt
 
def f(w0, w1):  # (A) f의 정의
    return w0**2 + 2* w0  * w1 + 3
def df_dw0(w0, w1):  # (B) f의 w0에 관한 편미분
    return 2 * w0 + 2 * w1
def df_dw1(w0, w1):  # (C) f의 w1에 관한 편미분
    return 2 * w0 + 0 * w1
 
w_range = 2
dw = 0.25
w0 = np.arange(-w_range, w_range + dw, dw)
w1 = np.arange(-w_range, w_range + dw, dw)
wn = w0.shape[0]
ww0, ww1 = np.meshgrid(w0, w1)  # (D)
ff = np.zeros((len(w0), len(w1)))
dff_dw0 = np.zeros((len(w0), len(w1)))
dff_dw1 = np.zeros((len(w0), len(w1)))
for i0 in range(wn):  # E
    for i1 in range(wn):
        ff[i1, i0] = f(w0[i0], w1[i1])
        dff_dw0[i1, i0] = df_dw0(w0[i0], w1[i1])
        dff_dw1[i1, i0] = df_dw1(w0[i0], w1[i1])
 
plt.figure(figsize=(94))
plt.subplots_adjust(wspace=0.3)
plt.subplot(121)
cont = plt.contour(ww0, ww1, ff, 10, colors='k')  # (F) f의 등고선 표시
cont.clabel(fmt='%2.0f', fontsize=8)
plt.xticks(range(-w_range, w_range + 11))
plt.yticks(range(-w_range, w_range + 11))
plt.xlim(-w_range - 0.5, w_range + .5)
plt.ylim(-w_range - .5, w_range + .5)
plt.xlabel('$w_0$', fontsize=14)
plt.ylabel('$w_1$', fontsize=14)
 
plt.subplot(122)
plt.quiver(ww0, ww1, dff_dw0, dff_dw1)  # (G) f의 경사 벡터 표시
plt.xlabel('$w_0$', fontsize=14)
plt.ylabel('$w_1$', fontsize=14)
plt.xticks(range(-w_range, w_range + 11))
plt.yticks(range(-w_range, w_range + 11))
plt.xlim(-w_range - 0.5, w_range + .5)
plt.ylim(-w_range - .5, w_range + .5)        
plt.show()
 
cs

결과: 


우선 (A)에서 함수 f를 정의하고 있습니다. 그리고 (B)로 w0방향의 편미분을 돌려주는 dff_dw0을 정의하고, (C)에서 w1방향의 편미분을 돌려주는 함수 


dff_dw1를 정의하고 있습니다.


(D)의 ww0, ww1 = np.meshgrid(w0, w1)에서 격자모양으로 나눈 w0과 w1을 2차원 배열 ww0과 ww1에 저장하고 있습니다.


이 ww0과 ww1에 대한 f와 편미분의 값이 (E)로 계산되며, ff와 dff_dw0, dff_dw1에 저장됩니다.


(F)에서 ff가 등고선을 표시하며, (G)에서 경사가 화살표로 표시됩니다.


화살표를 표시하는 명령문 (G)는 plt.quiver(ww0, ww1, dff_dw0, dff_dw1)명령을 통해, 좌표 점(ww0, ww1)부터 방향(dff_dw0, dff_dw1)의 화살표를 그립니다.


위 그림에서 왼쪽의 등고선 수치를 보면 오른쪽 위와 왼쪽 아래가 높고, 왼쪽 위와 오른쪽 아래가 낮은 지형을 볼 수 있습니다.


위 그림 오른쪽이 이 지형의 경사도 입니다.


화살표는 각 점에서 경사가 높은 쪽을 향하고 있으며 경사가 가파른 정도로도 화살표가 긴 것을 알 수 있습니다.


화살표를 따라가면 어느 지점에서 시작하더라도 그래프의 보다 높은 부분으로 진행합니다.


반대로 화살표를 거꾸로 더듬어보면 지형의 낮은 부분으로 진행합니다. 이렇게 경사는 그 함수의 최대점과 최소점을 찾는 데 중요한 개념입니다.


머신러닝에서는 오차 함수의 최소점을 구하기 위해 오차 함수의 경사를 계산합니다.



다변수의 중첩 함수의 미분

여러 층으로 된 신경망의 학습 규칙을 도출할 때 등장합니다.


예를들어처럼 나타낼 경우 'f의 w0에 관한 미분, w1에 대한 미분은 연쇄 법칙을 사용하면 어떻게 나타낼 수 있는가?' 라는 질문입니다.



ex) 의 경우  다음시간에



f값을 g_0으로 미분한 값, f를 g_1로 미분한 값


g_0를 w_0으로 미분한 값, g_1를 w_0으로 미분한 값으로 볼 수 있다.


수학에서 다변수 함수(multivariate function)는 둘 이상의 독립 변수를 갖는 함수입니다.



합과 미분의 교환


머신러닝에서는 계산 과정에서 합의 기호로 표현된 함수를 미분할 경우가 많습니다.


직관적으로 생각하면 합을 계산하고 미분하면 좋을 것입니다 즉, 다음과 같습니다.


미분을 먼저 계산하고 버리는 것이 계산이 편해지거나 미분밖에 계산할 수 없을 때가 많기 때문에, 머신러닝에서는 


을 많이 사용합니다.



예를 들어, 다음의 식을 생각해봅시다.

위를 w_0으로 미분하면 위의 식을 사용하여 미분 기호를 합의 기호 앞으로 이동합니다.

가 됩니다.



행렬

가 존재하면 각 행과 열은 로 표현이 가능하며 로도 가능합니다.


일반적으로 1행, 1열 이렇게 표현하지만 파이썬의 배열 인덱스는 0에서 시작하므로 0행 0열부터 세도록 합니다.



행렬의 덧셈과 뺄셈

가 존재하면 대응하는 요소에 연산을 수행하면 됩니다.


쉽습니다.


뺄셈도 마찬가지로 대응하는 요소에 연산을 수행하면 됩니다.

행렬의 덧셈과 뺄셈은 같은 크기의 행렬끼리가 아니면 할 수 없습니다.


파이썬에서 행렬 계산은 벡터와 마찬가지로 numpy 라이브러리를 import 해야합니다.




스칼라 배


행렬에 스칼라 값을 곱할 때는 모든 요소에 대한 곱합니다.



파이썬에서는 다음과 같습니다.



행렬의 곱


A와 B를 벡터로 간주했을 때의 내적 그 자체입니다. 파이썬으로는 A와 B의 내적계산은 다음과 같습니다.

1
2
3
A=np.array([1,2,3])
B=np.array([4,5,6])
print(A.dot(B))
cs
 = 32


A.dot(B)는 내적에 국한된 연산이 아니라 행렬 곱을 계산하는 연산입니다.


그렇다고 하면 A와 B가 가로 벡터인 채로 행렬 곱을 계산해버린 것은 이상한 느낌이 듭니다.


사실, 파이썬에서는 행렬을 곱할 때에는 계산이 가능하도록 행렬의 방향을 자동으로 조정하고 있습니다.


이 경우에는 B는 세로 벡터로 해석되어, 내적이 계산되고 있습니다.



단위 행렬

정방 행렬의 경우 대각선 성분이 1이고, 그 이외에는 0인 특별한 행렬을 I로 나타내며, 단위 행렬이라고 부릅니다.


3x3의 단위 행렬은 다음과 같습니다.


파이썬에서는 np.identity(n) 명령으로 nxn 단위 행렬이 생성됩니다.


각 요소에는 '.'이 붙어 있습니다.


소수도 나타낼 수 있는 float형임을 의미합니다.



역행렬


행렬의 나누기를 생각합니다.


스칼라의 경우 3으로 나누면 3의 역수인 1/3을 곱하는 것과 같습니다.


역수는 곱하면 1이 되는 수로, a의 역수는 1/a 이며 a^-1로 나타낼 수 있습니다.


역행렬이란, 곱하면 단위 행렬 I가 되는 행렬



2 x 2 행렬의  역행렬은 이다.





의 역행렬은 이며


가 된다.


파이썬에서는 np. linalg.inv(A)에서 A의 역행렬을 구할 수 있습니다.

1
2
3
A=np.array([[1,2], [3,4]])
invA = np.linalg.inv(A)
print(invA)
cs
가 나옵니다.


전치


를 A^T는 행과 열을 교환하여 가 됩니다.



파이썬에선 다음과 같이 표현하며 결과는 

1
2
3
= np.array([[123], [456]])
print(A)
print(A.T)
cs


가 나옵니다.


일반적인 경우를 성분 표시로 나타내면 다음과 같습니다.


AB를 한 번에 전치하는 경우 다음의 관계식이 성립됩니다.


행렬과 연립 방정식


양쪽에 역행렬을 곱해준다.

가 됩니다.


지수 함수와 로그 함수


지수의 정의

a > 0, n을 양의 정수라고 할 때



지수의 공식

a > 0, b > 0, m, n을 실수라고 할 때



로 정리할 수 있다.



지수함수는 다음으로 정의되는 함수입니다.



a를 사용함을 강조한다면, 'a를 밑으로 하는 지수 함수'라고 말합니다.


여기서 밑a는 0이상의 1이 아닌 숫자입니다.

7
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
 
= np.linspace(-44100)  # -4부터 4까지 100개를 분할하여라
= 2**x  # y = 2^x
y2 = 3**x  # y2 = 3^x
y3 = 0.5**x  # y3 = 0.5^x
 
plt.figure(figsize=(55))  # 그래프의 가로, 세로 크기
plt.plot(x, y, 'black', linewidth=3, label='$y=2^x$')  # y=2^x의 그래프 설정
plt.plot(x, y2, 'cornflowerblue', linewidth=3, label='$y=3^x$')  # y=3^x의 그래프 설정
plt.plot(x, y3, 'gray', linewidth=3, label='$y=0.5^x$')  # y =0.5^x의 그래프 설정
plt.ylim(-26)  # y는 -2 ~ 6까지 표시
plt.xlim(-44)  # x는 -4 ~ 4까지 표시
plt.legend(loc='lower right')  # 범례 위치
plt.yticks(range(-2, 7, 1)) # -2부터 6까지 ticks표시 plt.xticks(range(-4, 5, 1)) # -4부터 4까지 ticks표시
plt.grid(True)
plt.show()  # 보여주세요
cs


a >1일 때 x가 증가하면 반드시 y가 증가한다.


단조 증가 함수가 되고, 0 < a < 1의 경우는 단조 감소 함수가 된다.


밑 a가 클수록 그래프는 급격히 증가한다.


그래프는 항상 0보다 위에 있기 때문에, 지수 함수는 음수와 양수를 모두 양수로 옮기는 함수로 불린다.



로그


로그의 정의(1, 2, 3)


a를 1이 아닌 양의 실수라고 할 때, y= a^x로 하면


로그의 공식(4, 5, 6, 7)


a, b를 1이 아닌 양의 실수라고 할 때


로 변환이 가능합니다.


그래프를 그리면 y = a^y의 그래프와 y = x 선에서 대칭인 것을 알 수 있습니다.

= np.linspace(-88100)  # -8부터 8까지 100개로 분할
= 2**x  # y = 2^x
 
x2 = np.linspace(0.0018100)  # np.log(0)는 에러가 되므로 0은 포함하지 않음
y2 = np.log(x2) / np.log(2)  # 밑을 2로 한 log를 공식(7)로 계산
plt.figure(figsize=(5,5))
plt.plot(x, y, 'black', linewidth=3, label='$2^x$')  # y = 2^x의 모양
plt.plot(x2, y2, 'cornflowerblue', linewidth=3, label='$y=log_2x$')  # y = log_2x의 모양
plt.plot(x, x, 'black', linestyle='--', linewidth=1)  # 대각선
plt.ylim(-88)  # -8부터 8까지 표시
plt.xlim(-88)  # -8부터 8까지 표시
plt.legend(loc='lower right')
plt.grid(True)  # 바둑무늬 표시
plt.show()
cs

왼쪽이 y=2^x , 오른쪽이 log_2x 그래프이다.

a 를 밑으로 한 로그 함수 y = log_ax


y = a^x의 그래프와 y = x 선에서 대칭


그래프는 a > 0의 범위에서만 정의된다.


x가 커지면 커질수록 그래프는 증가하지만, 그 기울기는 점점 완만해진다.



a가 밑인 로그는 너무 크거나 작은 수를 다루기 쉬운 크기의 수로 만들어 주는 편리한 함수입니다.


예를 들어 100000000 = 10^8을 a = 10의 로그로 나타내면 log_1010^8 = 8이 되며, 0.000000001 = 10^-8은 log_1010^-8 = -8이 됩니다.



밑을 명기하지 않고 logx로 쓰면 밑에 e를 사용한 셈이 됩니다. e은 2.718....의 무리수로 '자연 로그' 또는 '네이피어 수'라고 합니다.


왜 이런 어중간한 숫자가 특별 대우를 받는지는 후에 설명합니다.



또한 로그는 곱셈을 덧셈으로 변환합니다. 


함수 f(x)가 있고, f(x)을 최소화하는 x*를 구하고 싶은 경우가 많습니다.


이때 로그를 취한 logf(x)도 x = x*일 때 최소화 됩니다.


로그는 단조 증가 함수이기 때문에 최솟값은 바뀌어도, 최솟값을 취하는 값은 변하지 않습니다.


이는 최댓값을 찾을때도 마찬가지입니다.


f(x)의 최댓값을 취하는 값은 f(x)의 로그를 취해도 바뀌지 않습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
= np.linspace(-44100)  # -4부터 4까지 100칸 분할
= (x-1** 2 + 2  # (x-1)^2 + 2
logy = np.log(y)  # logy
 
plt.figure(figsize=(44))  # 가로 세로 크기
plt.plot(x, y, 'black', linewidth=3, label='$(x-1)^2+2$')  # x, y를 넣고 색은 블랙, 폭은 3만큼 설정
plt.plot(x, logy, 'cornflowerblue', linewidth=3, label='$logy$')  # x, logy를 넣고 색은 저 색, 폭은 3만큼 설정
plt.yticks(range(-491))  # -4부터 8까지 ticks표시
plt.xticks(range(-451))  # -4부터 4까지 ticks표시
plt.ylim(-48)  # -4부터 8까지 표시
plt.xlim(-44)  # -4부터 4까지 표시
# legend 추가, 범례 표시
plt.legend(loc='lower right')
plt.grid(True)  # 격자 무늬 표시
plt.show()
cs



는  x = 1일 떄, 최솟값을 취한다.


도 x = 1일 때, 최솟값을 취한다.



이 성질로 인해 f(x)을 최소화하는 x*을 구하려고 할 때, logf(x)을 최소화하는 x*을 구하는 기법이 많이 사용됩니다.



지수 함수의 미분


지수 y = a^x의 x에 대한 미분은 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
= np.linspace(-44100)  # -4 ~ 4까지 100등분
= 2 
= a**x  # a^x
dy = np.log(a) * y  # log_ay
 
plt.figure(figsize=(44))
plt.plot(x, y, 'gray', linestyle='--', linewidth=3, label='$y=a^x$')  # x, y값을 넣고 색깔, 스타일, 폭 지정
plt.plot(x, dy, 'black', linewidth=3, label='$y=log_ay')  # 동일
plt.legend(loc='upper left')
plt.ylim(-18)  # x좌표 -1 ~ 8까지 표시
plt.xlim(-44)  # y좌표 -4부터 4까지 표시
plt.grid(True)  # 바둑무늬 출력
plt.show()
cs



특별한 경우는 a = e일 경우입니다. loge = 1이 되므로 다음과 같습니다.


즉 a = e의 경우는 미분해도 함수의 형태가 변하지 않습니다. 


이것은 미분 계산을 할 때 매우 편리합니다.


그런 이유로 e를 밑으로 한 지수 함수를 다양한 곳에서 사용합니다.


시그모이드 함수, 소프트맥스 함수, 가우스 함수에서 e를 사용합니다.




로그 함수의 미분


로그 함수의 미분은 반비례가 됩니다.



1
2
3
4
5
6
7
8
9
10
11
= np.linspace(0.00014100)
= np.log(x)  # logx
dy = 1 / x  # 1/x
 
plt.figure(figsize=(44))
plt.plot(x, y, 'gray', linestyle='--', linewidth=3, label='$$')
plt.plot(x, dy, 'black', linewidth=3)
plt.ylim(-88)  # x좌표 -8 ~ 8까지 표시
plt.xlim(-14)  # y좌표 -1부터 4까지 표시
plt.xticks(range(-1, 5, 1)) plt.yticks(range(-8, 9, 1))
plt.legend(loc='lower right')
plt.grid(True)
plt.show()
cs



는 반비례 관계인것을 알 수 있다.




의 형태의 미분도 나오고 있지만, z = 1- x를 


로 두면 연쇄 법칙을 사용하면 다음을 이끌어 낼 수 있습니다.




시그모이드 함수


매끄러운 계단같은 함수입니다.




로 나타냅니다.


e^{-x}는 exp(-x)로도 쓸 수 있으므로, 다음처럼 나타낼 수 있습니다.





1
2
3
4
5
6
7
8
9
10
11
= np.linspace(-1010100)
= 1 / (1 + np.exp(-x))
 
plt.figure(figsize=(44))
plt.plot(x, y, 'black', linewidth=3, label='$y=1/(1 + np.exp(-x)$')
 
plt.ylim(-12)  # y좌표 간격
plt.xlim(-1010)  # x좌표 간격
plt.grid(True)  #뒤의 격자 무늬
plt.legend(loc='lower right')
plt.show()
cs



x의 음의 무한에서 양의 무한까지의 수를 0에서 1 사이로 변환하는 단조 증가의 함수


실수를 확률로 변환할 때에도 사용된다.


시그모이드 함수는 음에서 양의 실수를 0에서 1까지의 사이로 변환하기 때문에 확률을 나타낼 때 자주 사용합니다.


시그모이드 함수는 6장에서 분류 문제로 등장합니다. 또한 7장의 신경망에서 뉴런의 특성을 나타내는 중요한 함수로 등장합니다.


시그모이드 함수의 미분공식은 다음을 생각합니다.


식에 맞게 f(x) = 1 + exp(-x)라고 생각합니다.

이 f(x)의 미분은 f'(x) = -exp(-x)입니다. 따라서 다음의 내용을 얻습니다.



여기서 식을 조금 변형해서


1/(1+exp(-x)는 y자체였기 때문에, y로 바꿔쓰면 깔끔한 형태로 변환이 가능합니다.







소프트맥스 함수


3개의 수 x_0 = 2, x_1 = 1, x_2 = -1가 있고, 이 수의 대소 관계를 유지하면서 각각의 확률을 나타내는 y0, y1, y2로 변환하려고 합니다.


확률이므로 0에서 1사이의 숫자가 아니면 안되고, 또한 모두 더하면 1이 되어야 합니다.


이런 경우게 사용되는 것이 소프트맥스 함수 입니다. 먼저 각 x_i의 exp의 합계 u을 구해 둡니다.


를 사용하여 변환식은 다음과 같습니다.



실제로 프로그램에서 소프트맥스 함수를 만들어 테스트해봅시다.


1
2
3
4
5
6
7
def softmax(x0, x1, x2):
    u = np.exp(x0) + np.exp(x1) + np.exp(x2)
    return np.exp(x0) / u, np.exp(x1) / u, np.exp(x2) / u
 
= softmax(21-1)
print(np.round(y, 2))
print(np.sum(y))
cs


소프트맥스 함수를 그림으로 보면 어떨까요?


입력과 출력이 3차원이므로 그대로 그릴 수는 없습니다.


그래서 x_2만 1로 고정하여 다양한 x0과 x1을 입력했을 때의 y0과 y1를 플롯합니다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from mpl_toolkits.mplot3d import Axes3D
 
xn = 20
x0 = np.linspace(-44, xn)
x1 = np.linspace(-44, xn)
 
= np.zeros((xn, xn, 3))
for i0 in range(xn):
    for i1 in range(xn):
        y[i1, i0, :] = softmax(x0[i0], x1[i1], 1)
        
xx0, xx1 = np.meshgrid(x0, x1)
plt.figure(figsize=(83))
for i in range(2):
    ax = plt.subplot(12, i+1, projection='3d')
    ax.plot_surface(xx0, xx1, y[:, :, i], rstride=1, cstride=1, alpha=0.3, color='blue', edgecolor='black')
    ax.set_xlabel('$x_0$', fontsize=14)
    ax.set_ylabel('$y_0$', fontsize=14)
    ax.view_init(40-125)
 
plt.show()
cs


x2를 1로 고정하여 x0과 x1를 움직이면,  y0과 y1은 0과 1사이의 값으로 변화합니다. ( 왼쪽)


x0이 커지면 y0은 1에 가까워지고, x1이 커지면 y1이 1에 가까워집니다.


y2는 그림으로 나타내고 있지는 않지만 y2는 1에서 y0는 1에서 y0과 y1을 뺀 나머지이므로 상상할 수 있습니다.


소프트맥스 함수는 3개의 변수뿐 아니라 그 이상의 변수에 사용할 수 있습니다.


변수의 수를 K로 하면 다음과 같습니다.



여기서 I_ij는 i = j일 때 1


i -=/ j 일 때 0이 되는 함수입니다.




가우스 함수

입니다.




1
2
3
4
5
6
7
8
9
10
11
def gauss(mu, sigma, a):
    return a * np.exp(-(x - mu)**2 / sigma**2)
 
= np.linspace(-44100)
plt.figure(figsize=(44))
plt.plot(x, gauss(011), 'black', linewidth=3)
plt.plot(x, gauss(230.5), 'gray', linewidth=3)
plt.ylim(-51.5)
plt.xlim(-44)
plt.grid(True)
plt.show()
cs



가우스 함수에서 확률 분포를 나타낼 수 있지만, 그 경우에는 x에 관한 적분이 1이 되도록 만듭니다.





2차원 가우스 함수


가우스 함수를 2차원으로 확장할 수 있습니다.


2차원 가우스 함수는 9장 혼합 가우시안 모델에서 등장합니다.


입력을 2차원 벡터 x = [x0, x1]^T라고 했을 때, 가우스 함수의 기본형식은 다음과 같습니다.



그래프로 나타내면 원점을 중심으로 동심원을 가진 종 모양이 됩니다.


이 기본형으로 중심을 이동시키거나 길고 가늘게 만들기 위해 몇 가지 매개 변수를 더한 형태가 다음과 같습니다.



먼저 함수의 형태를ㄹ 나타내는 매개 변수는 μ 와 ∑  입니다.


μ는 평균 벡터(중심 벡터)로 불리는 매개 변수이며, 분포의 중심을 나타냅니다.



∑ 는 공분산 행렬로 2 x 2 행렬입니다.


행렬 요소의 σ0와 σ1, x0 방향과 x1방향의 분포의 퍼짐을 나타냅니다.


σ2는 분포의 기울기에 대응하고 있으며, 양수라면 오른쪽으로 치우친 분포가, 음수를 넣으면 왼쪽으로 치우친 분포가 표현됩니다.


파이썬 프로그래밍으로도 그려봅시다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
%matplotlib inline
 
def gauss(x, mu, sigma):
    N, D = x.shape
    c1 = 1 / (2 * np.pi)**(D / 2)
    c2 = 1 / (np.linalg.det(sigma)**(1 / 2))
    inv_sigma = np.linalg.inv(sigma)
    c3 = x - mu
    c4 = np.dot(c3, inv_sigma)
    c5 = np.zeros(N)
    for d in range(D):
        c5 = c5 + c4[:, d] * c3[:, d]
    p = c1 * c2 * np.exp(-c5 / 2)
    return p
cs


인수의 입력 데이터 x는 N x 2의 행렬, mu는 크기가 2인 벡터, sigma는 2 x 2 행렬입니다.

여기에 적당한 수치를 대입하여 gauss(x, mu, sigma)를 테스트합니다.



1
2
3
4
= np.array([[12], [21], [34]])
mu = np.array([12])
sigma = np.array([[10], [01]])
print(gauss(x, mu, sigma))
cs


위의 결과에서 입력한 3개의 데이터에 대한 함수 값이 돌아오는 것이 확인되었습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
 
X_range0 = [-33]
X_range1 = [-33]
 
def gauss(x, mu, sigma):
    N, D = x.shape
    c1 = 1 / (2 * np.pi) ** (D / 2)
    c2 = 1 / (np.linalg.det(sigma) ** (1 / 2))
    inv_sigma = np.linalg.inv(sigma)
    c3 = x - mu
    c4 = np.dot(c3, inv_sigma)
    c5 = np.zeros(N)
    for d in range(D):
        c5 = c5 + c4[:, d] * c3[:, d]
    p = c1 * c2 * np.exp(-c5 / 2)
    return p
 
# 등고선 표시
def show_contour_gauss(mu, sig):
    xn = 40
    x0 = np.linspace(X_range0[0], X_range0[1], xn)
    x1 = np.linspace(X_range1[0], X_range1[1], xn)
    xx0, xx1 = np.meshgrid(x0, x1)
    x = np.c_[np.reshape(xx0, xn * xn, 1), np.reshape(xx1, xn * xn, 1)]
    f = gauss(x, mu, sig)
    f = f.reshape(xn, xn)
    f = f.T
    cont = plt.contour(xx0, xx1, f, 15, colors='k')
    plt.grid(True)
 
# 3D표시
def show3d_gauss(ax, mu, sig):
    xn = 40
    x0 = np.linspace(X_range0[0], X_range0[1], xn)
    x1 = np.linspace(X_range1[0], X_range1[1], xn)
    xx0, xx1 = np.meshgrid(x0, x1)
    x = np.c_[np.reshape(xx0, xn * xn, 1), np.reshape(xx1, xn * xn, 1)]
    f = gauss(x, mu, sig)
    f = f.reshape(xn, xn)
    f = f.T
    ax.plot_surface(xx0, xx1, f,
                    rstride=2, cstride=2, alpha=0.3,
                    color='blue', edgecolor='black')
 
# 메인
mu = np.array([10.5])
sigma = np.array([[21], [11]])
Fig = plt.figure(1, figsize=(73))
Fig.add_subplot(121)
show_contour_gauss(mu, sigma)
plt.xlim(X_range0)
plt.ylim(X_range1)
plt.xlabel('$x_0$', fontsize=14)
plt.ylabel('$x_1$', fontsize=14)
 
Ax = Fig.add_subplot(122, projection='3d')
show3d_gauss(Ax, mu, sigma)
Ax.set_zticks([0.050.10])
Ax.set_xlabel('$x_0$', fontsize=14)
Ax.set_ylabel('$x_1$', fontsize=14)
Ax.view_init(40-100)
plt.show()
cs




이상으로 chapter 4 머신러닝에 필요한 수학의 기본을 정리해보았다.


수학을 안한지 한참 되어서 봐도 무슨말인지 모르겠다! 


공부하면서 천천히 배워가야겠다.

728x90

'Python > 머신러닝 그리고 AI' 카테고리의 다른 글

DALL-E 체험기  (4) 2022.08.01
Chapter5 지도학습: 회귀  (0) 2020.08.05
Chapter3 그래프그리기  (0) 2020.07.21
Chapter2 파이썬 기초  (0) 2020.07.21
Comments