선형대수는 숫자 데이터의 계산에만 사용되는 것이 아니다. 직선과 화살표, 이미지 등을 다루는 기하학에서도 선형대수는 중요한 역할을 한다. 이 절에서는 선형대수를 기하학에서 어떻게 응용하고 선형대수의 연산이 기하학적으로 어떤 의미를 가지는지 알아본다.
차원 벡터 는 차원의 공간에서
라고 생각할 수 있다.
예를 들어 2차원 벡터
는 2차원 공간에서 좌표가 , 좌표가 인 점으로 생각할 수도 있고 또는 원점에서 이 점을 가리키는 화살표로 생각할 수도 있다. 벡터를 화살표로 생각하는 경우에는 길이와 방향을 고정시킨 채 평행이동할 수 있다.
import numpy as np
import matplotlib.pylab as plt
plt.rc("font", size=18) # 그림의 폰트 크기를 18로 고정
gray = {"facecolor": "gray"}
black = {"facecolor": "black"}
red = {"facecolor": "red"}
green = {"facecolor": "green"}
blue = {"facecolor": "blue"}
a = np.array([1, 2])
plt.plot(0, 0, 'kP', ms=20)
plt.plot(a[0], a[1], 'ro', ms=20)
plt.annotate('', xy=[-0.6, 1.6], xytext=(0.2, 0.7), arrowprops=gray)
plt.annotate('', xy=a, xytext=(0, 0), arrowprops=black)
plt.annotate('', xy=a + [-1, 1], xytext=(-1, 1), arrowprops=black)
plt.text(0.35, 1.15, "$a$")
plt.text(1.15, 2.25, "$(1,2)$")
plt.text(-0.7, 2.1, "$a$")
plt.text(-0.9, 0.6, "평행이동")
plt.xticks(np.arange(-2, 4))
plt.yticks(np.arange(-1, 4))
plt.xlim(-2.4, 3.4)
plt.ylim(-0.8, 3.4)
plt.show()
벡터 의 길이는 놈(norm) 으로 정의한다.
넘파이 linalg 서브 패키지의 norm()
명령으로 벡터의 길이를 계산할 수 있다. 위에서 예로 든 2차원 벡터 의 길이는 이다.
a = np.array([1, 2])
np.linalg.norm(a)
2.23606797749979
양의 실수와 벡터를 곱하면 벡터의 방향은 변하지 않고 실수의 크기만큼 벡터의 길이가 커진다. 만약 음의 실수를 곱하면 벡터의 방향이 반대가 된다.
a = np.array([1, 2])
b = 2 * a
c = -a
plt.annotate('', xy=b, xytext=(0, 0), arrowprops=red)
plt.text(0.8, 3.1, "$2a$")
plt.text(2.2, 3.8, "$(2, 4)$")
plt.annotate('', xy=a, xytext=(0, 0), arrowprops=gray)
plt.text(0.1, 1.3, "$a$")
plt.text(1.1, 1.4, "$(1, 2)$")
plt.plot(c[0], c[1], 'ro', ms=10)
plt.annotate('', xy=c, xytext=(0, 0), arrowprops=blue)
plt.text(-1.3, -0.8, "$-a$")
plt.text(-3, -2.5, "$(-1, -2)$")
plt.plot(0, 0, 'kP', ms=20)
plt.xticks(np.arange(-5, 6))
plt.yticks(np.arange(-5, 6))
plt.xlim(-4.4, 5.4)
plt.ylim(-3.2, 5.2)
plt.show()
길이가 1인 벡터를 단위벡터(unit vector)라고 한다. 예를 들어 다음과 같은 벡터들은 모두 단위벡터다.
영벡터가 아닌 임의의 벡터 에 대해 다음 벡터는 벡터 와 같은 방향을 가리키는 단위벡터가 된다.
a = np.array([1, 0])
b = np.array([0, 1])
c = np.array([1/np.sqrt(2), 1/np.sqrt(2)])
np.linalg.norm(a), np.linalg.norm(b), np.linalg.norm(c)
(1.0, 1.0, 0.9999999999999999)
벡터와 벡터의 합도 벡터가 된다. 이때 두 벡터의 합은 그 두 벡터를 이웃하는 변으로 가지는 평행사변형의 대각선 벡터가 된다.
a = np.array([1, 2])
b = np.array([2, 1])
c = a + b
plt.annotate('', xy=a, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=b, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=c, xytext=(0, 0), arrowprops=black)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(a[0], a[1], 'ro', ms=10)
plt.plot(b[0], b[1], 'ro', ms=10)
plt.plot(c[0], c[1], 'ro', ms=10)
plt.plot([a[0], c[0]], [a[1], c[1]], 'k--')
plt.plot([b[0], c[0]], [b[1], c[1]], 'k--')
plt.text(0.35, 1.15, "$a$")
plt.text(1.15, 0.25, "$b$")
plt.text(1.25, 1.45, "$c$")
plt.xticks(np.arange(-2, 5))
plt.yticks(np.arange(-1, 4))
plt.xlim(-1.4, 4.4)
plt.ylim(-0.6, 3.8)
plt.show()
지금까지 벡터의 스칼라곱이 어떤 새로운 벡터가 되고 두 벡터의 합이 어떤 새로운 벡터가 되는지 살펴보았다. 여러 개의 벡터를 스칼라곱을 한 후 더한 것을 선형조합(linear combination)이라고 한다.
이 식에서 은 스칼라 계수다.
x1 = np.array([1, 2])
x2 = np.array([2, 1])
x3 = 0.5 * x1 + x2
plt.annotate('', xy=0.5*x1, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=x2, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=x3, xytext=(0, 0), arrowprops=black)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(x1[0], x1[1], 'ro', ms=10)
plt.plot(x2[0], x2[1], 'ro', ms=10)
plt.plot(x3[0], x3[1], 'ro', ms=10)
plt.plot([x1[0], 0], [x1[1], 0], 'k--')
plt.text(0.6, 2.0, "$x_1$")
plt.text(-0.5, 0.5, "$0.5x_1$")
plt.text(1.15, 0.25, "$x_2$")
plt.text(2.5, 1.6, "$0.5x_1 + x_2$")
plt.xticks(np.arange(-2, 5))
plt.yticks(np.arange(-1, 4))
plt.xlim(-1.4, 4.4)
plt.ylim(-0.6, 3.8)
plt.show()
벡터의 차 는 벡터 가 가리키는 점으로부터 벡터 가 가리키는 점을 연결하는 벡터다. 그 이유는 벡터 에 벡터 를 더하면, 즉 벡터 와 벡터 를 연결하면 벡터 가 되어야 하기 때문이다.
a = np.array([1, 2])
b = np.array([2, 1])
c = a - b
plt.annotate('', xy=a, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=b, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=a, xytext=b, arrowprops=black)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(a[0], a[1], 'ro', ms=10)
plt.plot(b[0], b[1], 'ro', ms=10)
plt.text(0.35, 1.15, "$a$")
plt.text(1.15, 0.25, "$b$")
plt.text(1.55, 1.65, "$a-b$")
plt.xticks(np.arange(-2, 5))
plt.yticks(np.arange(-1, 4))
plt.xlim(-0.8, 2.8)
plt.ylim(-0.8, 2.8)
plt.show()
나중에 인공신경망 부분에서 공부하게 될 word2vec 방법을 이용하면 단어(word)를 공간에서 점 또는 벡터(vector)로 표현할 수 있다. word2vec으로 만들어진 벡터는 단어의 의미에 따라 다음처럼 평행사변형 관계를 가질 수 있다.
한국 - 서울
은 서울
에서 한국
으로 향하는 벡터다. 즉 의미론적으로 수도 이름을 나라 이름으로 바꾸는 행위(action)에 비유할 수 있다. 이러한 행위를 도쿄
에 대해서 적용한 결과가 도쿄 + (한국 - 서울)
이다. word2vec 학습 결과에서 이렇게 계산한 위치에 가장 가까이 있는 단어를 찾으면 도쿄
가 나온다.
a = np.array([2, 2])
b = np.array([3, 4])
c = np.array([4, 1])
d = a + (c - a)
e = b + (c - a)
plt.annotate('', xy=b, xytext=a, arrowprops=black)
plt.annotate('', xy=e, xytext=d, arrowprops=black)
plt.annotate('', xy=c, xytext=[0, 0], arrowprops=gray)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(a[0], a[1], 'ro', ms=10)
plt.plot(b[0], b[1], 'ro', ms=10)
plt.plot(c[0], c[1], 'ro', ms=10)
plt.text(1.6, 1.5, "서울")
plt.text(2.5, 4.3, "한국")
plt.text(3.5, 0.5, "도쿄")
plt.text(4.9, 3.2, "일본")
plt.xticks(np.arange(-2, 7))
plt.yticks(np.arange(-1, 6))
plt.xlim(-1.4, 6.4)
plt.ylim(-0.6, 5.8)
plt.show()
두 벡터가 가리키는 점 사이의 거리를 유클리드 거리(Euclidean distance)라고 한다. 두 벡터의 유클리드 거리는 벡터의 차의 길이로 구할 수 있다.
벡터의 놈의 정의와 벡터의 차의 정의에서 유클리드 거리는 다음처럼 구한다.
즉,
두 벡터의 내적은 다음처럼 벡터의 길이 , 와 두 벡터 사이의 각도 의 코사인 함수값으로 계산할 수도 있다.
여기에서 는 코사인(cosine)이라고 하는 함수이다. 코사인은 사인(sine)이라고 하는 함수와 함께 정의할 수 있다. 사인과 코사인을 합쳐서 삼각함수라고 한다.
사인 의 값은 라는 각을 가지는 직각 삼각형에서 빗변(hypotenuse)과 높이(opposite)의 비율을 뜻한다.
코사인 의 값은 라는 각을 가지는 직각 삼각형에서 빗변(hypotenuse)과 밑변(adjacent)의 비율을 뜻한다.
plt.plot([0, 1], [0, 2], 'k-', lw=3)
plt.plot([0, 1], [0, 0], 'k-', lw=3)
plt.plot([1, 1], [0, 2], 'k-', lw=3)
plt.text(0.05, 1, "빗변 h")
plt.text(0.35, -0.2, "밑변 b")
plt.text(1.05, 1, "높이 a")
plt.text(0.12, 0.06, r"$\theta$")
plt.xticks(np.arange(-2, 4))
plt.yticks(np.arange(-1, 4))
plt.xlim(-1.1, 2.1)
plt.ylim(-0.5, 2.3)
plt.show()
의 값은 가 0에 가까워질수록 0에 가까워지고 가 에 가까워질수록 1에 가까워진다.
반대로 의 값은 가 0에 가까워질수록 1에 가까워지고 가 에 가까워질수록 0에 가까워진다.
함수의 그래프로 표현하면 다음과 같다.
x = np.linspace(0, np.pi/2, 100)
y1 = np.sin(x)
y2 = np.cos(x)
plt.plot(x, y1, 'r--', lw=3, label=r"$\sin\theta$")
plt.plot(x, y2, 'b-', lw=3, label=r"$\cos\theta$")
plt.legend()
plt.xticks([0, np.pi/4, np.pi/2], [r'$0^{\circ}$', r'$45^{\circ}$', r'$90^{\circ}$'])
plt.xlabel(r"$\theta$")
plt.title(r"$\sin\theta$와 $\cos\theta$의 그래프")
plt.show()
두 벡터 와 가 이루는 각이 90도이면 서로 직교(orthogonal)라고 하며 로 표시한다.
이므로 서로 직교인 두 벡터의 내적은 0이 된다.
예를 들어 다음 두 벡터는 서로 직교한다.
a = np.array([1, 1])
b = np.array([-1, 1])
a @ b
0
만약 개의 단위벡터 가 서로 직교하면 정규직교(orthonormal)라고 한다.
두 벡터의 방향이 비슷할수록 벡터가 비슷하다고 간주하여 두 벡터 사이의 각의 코사인값을 코사인 유사도(cosine similarity)라고 한다. 코사인값은 각도가 0일때 가장 커지므로 두 벡터가 같은 방향을 가리키고 있으면 코사인 유사도가 최댓값 1을 가진다.
코사인 유사도는 나중에 공부할 추천시스템(recommender system)에서 사용자의 취향이 얼마나 비슷한지를 계산할 때 사용된다. 코사인 유사도를 이용하면 다음처럼 코사인 거리(cosine distance)도 정의할 수 있다.
어떤 두 벡터 , 의 합이 다른 벡터 가 될 때 가 두 벡터 성분(component) , 으로 분해(decomposition)된다고 말한다.
벡터 를 다른 벡터 에 직교하는 성분과 벡터 에 평행한 성분으로로 분해할 수 있는데, 평행한 성분을 벡터 에 대한 투영성분(projection), 벡터 에 직교하는 성분을 벡터 에 대한 직교성분(rejection)이라고 하며 각각 다음과 같이 표기한다.
투영성분의 길이는 다음처럼 구할 수 있다.
만약 벡터 자체가 이미 단위벡터이면 단위벡터에 대한 투영길이는 내적이 된다.
투영성분 성분 벡터는 투영성분 길이와 벡터 방향의 단위벡터의 곱이다.
직교성분 벡터는 원래의 벡터에서 투영성분 성분 벡터를 뺀 나머지다.
a = np.array([1, 2])
b = np.array([2, 0])
a2 = (a @ b) / np.linalg.norm(b) * np.array([1, 0])
a1 = a - a2
plt.annotate('', xy=b, xytext=(0, 0), arrowprops=green)
plt.annotate('', xy=a2, xytext=(0, 0), arrowprops=blue)
plt.annotate('', xy=a1, xytext=(0, 0), arrowprops=blue)
plt.annotate('', xy=a, xytext=(0, 0), arrowprops=red)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(a[0], a[1], 'ro', ms=10)
plt.plot(b[0], b[1], 'ro', ms=10)
plt.text(0.35, 1.15, "$a$")
plt.text(1.55, 0.15, "$b$")
plt.text(-0.5, 1.05, "$a^{\perp b}$")
plt.text(0.50, 0.15, "$a^{\Vert b}$")
plt.xticks(np.arange(-10, 10))
plt.yticks(np.arange(-10, 10))
plt.xlim(-1.2, 4.1)
plt.ylim(-0.5, 3.2)
plt.show()
어떤 벡터 가 있을 때
직선의 방정식을 구해보자.
위 두 조건을 만족하는 직선 상의 임의의 점을 가리키는 벡터를 라고 하면, 벡터 가 가리키는 점과 벡터 가 가리키는 점을 이은 벡터 는 조건에 따라 벡터 와 직교해야 한다. 따라서 다음 식이 성립한다.
정리하면 다음과 같아진다.
이 직선과 원점 사이의 거리는 벡터 의 놈 이다.
예를 들어
일 때
이 방정식은 벡터 가 가리키는 점 를 지나면서 벡터 에 수직인 직선을 뜻한다. 이 직선과 원점 사이의 거리는 이다.
이번에는 벡터 가 가리키는 점을 지나야 한다는 조건을 없애고 단순히
직선 의 방정식을 구해보자.
이때는 직선이 가 아니라 와 방향이 같고 길이가 다른 벡터 을 지날 것이다. 는 양의 실수이다.
위에서 했던 방법으로 다시 직선의 방정식을 구하면 다음과 같다.
여기에서 는 임의의 수가 될 수 있으므로 단순히 벡터 에 수직인 직선의 방정식은 다음과 같이 나타낼 수 있다.
이 직선과 원점 사이의 거리는 다음과 같다.
예를 들어 이면 벡터 에 수직이고 원점으로부터의 거리가 인 직선이 된다.
이번에는 직선 과 이 직선 위에 있지 않은 점 사이의 거리를 구해보자.
벡터 에 대한 벡터 의 투영성분 의 길이는 다음과 같다.
직선과 점 사이의 거리는 이 길이에서 원점에서 직선까지의 거리 를 뺀 값의 절댓값이다.
직선의 방정식이 이면 직선과 점의 거리는 다음과 같다.
이 공식은 나중에 분류 방법의 하나인 서포트 벡터 머신(SVM: Support Vector Machine)에서 사용된다.