기계학습을 위한 수학

선형대수

다차원의 구조를 가진 수치를 다루는 수학의 분야입니다. 따라서 많은 데이터를 다루는 기계학습에서 선형대수는 필수 입니다.

스칼라, 벡터, 행렬, 텐서

스칼라는 숫자 하나로 구성된 데이터를 의미합니다. 벡터는 여러 스칼라로 구성된 것으로 배열(array)라고도 합니다. 행렬은 여러개의 벡터가 2차원 배열로 구성되어 표처럼 나열한 것입니다. 텐서는 크기가 같은 행렬의 집합으로 다차원 배열을 의미합니다. 거꾸로 말해보면 0차원 텐서가 스칼라, 1차원 텐서가 벡터, 2차원 텐서가 행렬입니다.

In [1]:
import numpy as np

# 스칼라
a = 1.5
# 1차원 벡터
b = np.array([1, 2, 3, 4])
# 2x2 벡터 = 행렬
c = np.array([[1, 2], [3, 4]])
# 2x2x2 벡터 = 텐서
d = np.array([[[1, 2], [3, 4], [5, 6], [7, 8]]])

벡터의 특성

벡터의 연산

벡터의 덧셈과 뺄셈

벡터의 아다마르 곱(Hadamard product)

벡터의 아다마르 곱은 각 요소를 곱한 것입니다. 따라서 벡터의 크기가 같아야 하고 교환법칙도 성립합니다.

$ A \odot B = B \odot A $

In [2]:
A = np.array([[1, 2], [4, 5]])
B = np.array([[4, 5], [1, 2]])

A * B
Out[2]:
array([[ 4, 10],
       [ 4, 10]])

벡터의 내적(Dot product)

벡터의 내적은 곱의 한 종류입니다. A와 B가 벡터이면, 길이가 같아야 하고 A와 B가 행렬이나 다차원 배열이면 크기가 같아야 합니다. 내적은 Numpydot()으로 구할 수 있습니다.

행렬 곱셈에 대해서는 교환법칙이 성립하지 않음으로 내적을 구하는 순서가 중요합니다.

$AB \neq BA$

In [3]:
np.dot(A, B)
Out[3]:
array([[ 6,  9],
       [21, 30]])
In [4]:
np.dot(B, A)
Out[4]:
array([[24, 33],
       [ 9, 12]])

벡터의 전치(Transpose)

행과 열을 바꾸는 것으로 전치된 행렬 $A$는 $A^T$로 표현합니다. Numpy에서는 .T로 구현합니다. 보통 내적을 계산하기 위한 전처리에 많이 사용됩니다.

In [5]:
A = np.array([[1, 2, 3], [4, 5, 6]])  # 2x3행렬
B = np.array([[4, 5, 6], [1, 2, 3]])  # 2x3행렬

np.dot(A, B.T)
Out[5]:
array([[32, 14],
       [77, 32]])

백터의 외적(Cross product)

$A \times B$, A cross B 라고 읽습니다.

cross product = X : 외적 연산자로 좌변의 벡터에서 우변의 벡터로 cross 곱하면 두 벡터에 동시에 수직인 법선벡터의 방향이 표시된다.

추가적으로 벡터의 내적과 외적은 서로 반대되는 개념이 아닙니다. 그냥 벡터의 여러가지 성질을 나타내기 위한 서로 다른 개념입니다.

In [6]:
np.cross(A, B)
Out[6]:
array([[-3,  6, -3],
       [ 3, -6,  3]])

벡터의 놈(Norm)

벡터의 크기를 나타내는 양입니다. 기계학습에서는 L1, L2 놈을 과적합을 방지하기 위한 정규화 방법으로 사용합니다. Numpylinalg.norm()함수를 사용합니다.

과적합(overfitting): 예측모델이 학습데이터에 대해서는 잘 예측하지만, 학습한 적이 없는 데이터에 대해서는 잘 예측하지 못하는 현상

종류 거리를 구하는 방법 특징
L1 놈 절대값 가중치가 작은 경우를 0으로 만들어 변수 선별이 가능.
L2 놈 제곱값 모든 특징들이 다 필요하다고 보는 경우에 사용.
In [7]:
# L1 놈
np.linalg.norm(A, 1)
Out[7]:
9.0
In [8]:
# L2 놈
np.linalg.norm(A)
Out[8]:
9.539392014169456

코사인 유사도

이름에서 알 수 있듯이 벡터의 유사도를 수치화하는 방법입니다. 같은 방향인 경우 최댓값이 1이고 반대 방향인 경우 최솟값이 -1입니다. 코사인 유사도를 구하는 방법은 내적과 놈을 사용합니다.

In [9]:
A = np.array([1, 2])  # 2x2행렬
B = np.array([4, 5])  # 2x2행렬


def cosine_similar(x, y):
    return np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))


cosine_similar(A, B)
Out[9]:
0.9778024140774094

행렬

단위행렬

단위행렬은 행과 열의 수가 같고, 왼쪽 위에서부터 오른쪽 아래로 1이며, 나머지 요소는 0인 행렬입니다. $E$로 표기합니다. Numpy에서는 eye() 함수로 단위행렬을 만들 수 있습니다.

In [10]:
np.eye(2)  # 2x2 단위 행렬
Out[10]:
array([[1., 0.],
       [0., 1.]])

역행렬

행렬 A에 곱하면 단위행렬이 되는 행렬로 $A^{-1}$로 나타냅니다. $AA^{-1} = E$으로 표현합니다. 행렬에 따라서 역행렬이 존재하지 않는 경우도 있습니다. 역행렬이 존재여부는 행렬식(determinant, 줄여서 det)에 의해 판단하며 이 행렬식이 '0'이 아니면 역행렬이 존재하고, 이 행렬식이 '0'이면 역행렬이 존재하지 않습니다. Numpy에서는 linalg.det() 함수로 행렬식을 계산하고 linalg.inv()로 역행렬을 구할 수 있습니다.

In [11]:
A = np.array([[1, 2], [4, 5]])
np.linalg.det(A)
Out[11]:
-2.9999999999999996
In [12]:
np.linalg.inv(A)
Out[12]:
array([[-1.66666667,  0.66666667],
       [ 1.33333333, -0.33333333]])

미분과 적분

미분과 적분은 변화를 예측하고 원인을 이해하는데 필요합니다. 예를 들어 운전을 할때 시간당 변화율을 안다면 얼마나 멀리 갔는지를 예측할 수 있죠.

심파이(Sympy)

Sympy은 수학적인 심볼들을 사용하여 수식을 표현하고, 이러한 심볼들을 다루는 연산을 수행하기 위한 라이브러리입니다. Sympy를 사용하면 파이썬으로 다양한 수학적인 문제를 해결할 수 있습니다. 아래와 같이 sympy를 설치하세요.

conda install sympy
# 혹은 pip install sympy
In [13]:
import sympy as sp
from sympy.abc import x, y

# Juypter에서 LaTeX 표현을 위해 필요
sp.init_printing(use_latex="mathjax")

간단한 수식 만들기

In [14]:
exp = x**2 + 1 * x + 6
exp
Out[14]:
$\displaystyle x^{2} + x + 6$

편미분

편미분은 다변수 함수에서 관심이 있는 한 변수만 변수로 생각하고, 나머지 변수들은 상수로 취급한 뒤 미분하는 방법입니다.

In [15]:
sp.diff(exp, x)
Out[15]:
$\displaystyle 2 x + 1$
In [16]:
sp.diff(exp, x, x)  # x로 2번 미분
Out[16]:
$\displaystyle 2$

전미분

다변수 함수를 모든 변수에 대해 미분하는 것을 전미분이라 합니다.

In [17]:
exp = x**3 + y**2 + 1 * x + 5
exp
Out[17]:
$\displaystyle x^{3} + x + y^{2} + 5$
In [18]:
# dy/dx
sp.idiff(exp, y, x)
Out[18]:
$\displaystyle - \frac{3 x^{2} + 1}{2 y}$
In [19]:
# dx/dy
sp.idiff(exp, x, y)
Out[19]:
$\displaystyle - \frac{2 y}{3 x^{2} + 1}$

적분

적분(integral)은 미분과 반대되는 개념입니다. 적분에는 부정적분(indefinite integral)과 정적분(definite integral)이 있습니다. 부정적분은 미분되기 전의 원래 함수를 찾는 과정이라고 할 수 있으며 정적분은 특정 구간에서의 함수의 면적을 구하는 것으로 구분할 수 있습니다.

부정적분

In [20]:
x, y = sp.symbols("x y")
exp = x**3 - 2 * x**2 + x + 25
exp
Out[20]:
$\displaystyle x^{3} - 2 x^{2} + x + 25$
In [21]:
# 부정 적분
F = sp.integrate(exp)
F
Out[21]:
$\displaystyle \frac{x^{4}}{4} - \frac{2 x^{3}}{3} + \frac{x^{2}}{2} + 25 x$

정적분

위에서 구한 부정적분 값에 구간(0에서 3)을 넣어 계산을 합니다.

In [22]:
(F.subs(x, 3) - F.subs(x, 0)).evalf()
Out[22]:
$\displaystyle 81.75$