학교 다닐 때 행렬로 연립방정식 풀었던 기억이 날 듯 합니다. 선형대수(Linear Algebra)는 통계, 기계학습, 공학, 영상/이미지 처리 등 여러 분야에서 활용이 됩니다. 선형대수를 전부 다루려면 너무나 방대하므로, 이번 포스팅에서는 Python의 NumPy에 있는 선형대수(Linear Algebra) 함수들 중에서 자주 사용하는 함수에 대해서만 선별적으로 소개하겠습니다. 그리고 선형대수의 이론적인 부분은 별도로 참고할 수 있는 링크를 달도록 하겠습니다. 


  • 단위행렬 (Unit matrix): np.eye(n)
  • 대각행렬 (Diagonal matrix): np.diag(x)
  • 내적 (Dot product, Inner product): np.dot(a, b)
  • 대각합 (Trace): np.trace(x)
  • 행렬식 (Matrix Determinant): np.linalg.det(x)
  • 역행렬 (Inverse of a matrix): np.linalg.inv(x)
  • 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)
  • 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)
  • 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)
  • 최소자승 해 풀기 (Compute the Least-squares solution): m, c = np.linalg.lstsq(A, y, rcond=None)[0]




 1. 단위행렬 혹은 항등행렬 (Unit matrix, Identity matrix): np.eye(n)


단위행렬은 대각원소가 1이고, 나머지는 모두 0인 n차 정방행렬을 말하며, numpy의 eye() 함수를 사용해서 만들 수 있습니다. 


* 참고 링크 : https://rfriend.tistory.com/141



import numpy as np


unit_mat_4 = np.eye(4)


print(unit_mat_4)

[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

 




  1. 대각행렬 (Diagonal matrix): np.diag(x)


대각행렬은 대각성분 이외의 모든 성분이 모두 '0'인 n차 정방행렬을 말합니다. 아래 예시의 행렬에서 빨간색으로 표시한 원소를 '0'으로 바꾼 행렬이 대각행렬입니다. 


* 참고 링크 : http://rfriend.tistory.com/141


 

In [1]: import numpy as np


In [2]: x = np.arange(9).reshape(3, 3)


In [3]: print(x)

[[0 1 2]

 [3 4 5]

 [6 7 8]]


In [4]: np.diag(x)

Out[4]: array([0, 4, 8])


In [5]: np.diag(np.diag(x))

Out[5]:

array([[0, 0, 0],

        [0, 4, 0],

        [0, 0, 8]])





  2. 내적 (Dot product, Inner product): np.dot(a, b), a.dot(b)


matrix dot product, inner product, scalar product, projection product

Python에서 '*' 를 사용한 두 행렬 간 곱은 원소 간 곱(element-wise product)을 반환하며, 선형대수에서 말하는 행렬 간 내적 곱을 위해서는 np.dot() 함수를 이용해야 합니다. 


원소 간 곱 (element-wise product)

: a*b

내적 (dot product, inner product)

: np.dot(a, b)

 

In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [8]: a*a

Out[8]:

array([[0, 1],

        [4, 9]])


In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [9]: np.dot(a, a)

Out[9]:

array([[ 2, 3],

        [ 6, 11]])



np.dot(a, b) NumPy 함수와 a.dot(b)의 배열 메소드의 결과는 동일합니다. 



In [10]: a.dot(a)

Out[10]:

array([[ 2, 3],

        [ 6, 11]])

 




  3. 대각합 (Trace): np.trace(x)


정방행렬의 대각에 위치한 원소를 전부 더해줍니다. 

아래의 2차 정방행렬 예의 대각합은 0+5+10+15 = 30 이 됩니다. (파란색으로 표시함)



In [12]: b = np.arange(16).reshape(4, 4)


In [13]: print(b)

[[ 0 1 2 3]

 [ 4 5 6 7]

 [ 8 9 10 11]

 [12 13 14 15]]


In [14]: np.trace(b)

Out[14]: 30

 



3차원 행렬에 대해서도 대각합을 구할 수 있습니다. 2차원은 대각선 부분의 원소 값을 전부 더하면 되지만 3차원 행렬에서는 대각(diagonal)이 어떻게 되나 좀 헷갈릴 수 있겠습니다. 아래의 3차원 행렬의 대각합을 구하는 예를 살펴보면, [0+12+24, 1+13+25, 2+14+26] = [36, 39, 42] 가 됩니다. 



In [15]: c = np.arange(27).reshape(3, 3, 3)


In [16]: print(c)

[[[ 0 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]]]


In [17]: np.trace(c)

Out[17]: array([36, 39, 42])

 




  4. 행렬식 (Matrix Determinant): np.linalg.det(x)


역행렬이 존재하는지 여부를 확인하는 방법으로 행렬식(determinant, 줄여서 det)이라는 지표를 사용합니다. 이 행렬식이 '0'이 아니면 역행렬이 존재하고, 이 행렬식이 '0'이면 역행렬이 존재하지 않습니다. 


* 참고 링크 : http://rfriend.tistory.com/142


아래의 예에서 array([[1, 2], [3, 4]]) 의 행렬식이 '-2.0'으로서, '0'이 아니므로 역행렬이 존재한다고 판단할 수 있습니다. 



In [18]: d = np.array([[1, 2], [3, 4]])


In [19]: np.linalg.det(a)

Out[19]: -2.0

 




  5. 역행렬 (Inverse of a matrix): np.linalg.inv(x)


역행렬은 n차정방행렬 Amn과의 곱이 항등행렬 또는 단위행렬 In이 되는 n차정방행렬을 말합니다. A*B 와 B*A 모두 순서에 상관없이 곱했을 때 단위행렬이 나오는 n차정방행렬이 있다면 역행렬이 존재하는 것입니다.

역행렬은 가우스 소거법(Gauss-Jordan elimination method), 혹은 여인수(cofactor method)로 풀 수 있습니다. 




In [20]: a = np.array(range(4)).reshape(2, 2)


In [21]: print(a)

[[0 1]

 [2 3]]


In [22]: a_inv = np.linalg.inv(a)


In [23]: a_inv

Out[23]:

array([[-1.5, 0.5],

        [ 1. , 0. ]])




위의 예제에서 np.linalg.inv() 함수를 사용하여 푼 역행렬이 제대로 푼 것인지 확인을 해보겠습니다. 역행렬의 정의에 따라서 원래의 행렬에 역행렬을 곱하면, 즉, a.dot(a_inv) 또는 np.dot(a, a_inv) 를 하면 단위행렬(unit matrix)가 되는지 확인해보겠습니다. 



In [24]: a.dot(a_inv)

Out[24]:

array([[1., 0.],

         [0., 1.]])

 




  6. 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)


정방행렬 A에 대하여 Ax = λx  (상수 λ) 가 성립하는 0이 아닌 벡터 x가 존재할 때 상수 λ 를 행렬 A의 고유값 (eigenvalue), x 를 이에 대응하는 고유벡터 (eigenvector) 라고 합니다. 


np.linalg.eig() 함수는 고유값(eigenvalue) w, 고유벡터(eigenvector) v 의 두 개의 객체를 반환합니다. 


In [25]: e = np.array([[4, 2],[3, 5]])


In [26]: print(e)

[[4 2]

[3 5]]


In [27]: w, v = np.linalg.eig(e)


#  w: the eigenvalues lambda

In [28]: print(w)

[2. 7.]


# v: the corresponding eigenvectors, one eigenvector per column

In [29]: print(v)

[[-0.70710678 -0.5547002 ]

[ 0.70710678 -0.83205029]]

 



고유벡터는 배열 인덱싱하는 방법을 사용해서 각 고유값에 대응하는 고유벡터를 선택할 수 있습니다. 



# eigenvector of eigenvalue lambda 2

In [30]: print(v[:, 0]

[-0.70710678 0.70710678]


# eigenvector of eigenvalue labmda 7

In [31]: print(v[:, 1]

[-0.5547002 -0.83205029]

 




  7. 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)


특이값 분해는 고유값 분해(eigen decomposition)처럼 행렬을 대각화하는 한 방법으로서, 정방행렬뿐만 아니라 모든 m x n 행렬에 대해 적용 가능합니다. 특이값 분해는 차원축소, 데이터 압축 등에 사용할 수 있습니다. 이론적인 부분은 설명하자면 너무 길기 때문에 이 포스팅에서는 설명하지 않겠으며, 아래의 링크를 참고하시기 바랍니다. 


* 참고 링크 : http://rfriend.tistory.com/185


아래의 np.linalg.svd(A) 예제는 위의 참고 링크에서 사용했던 예제와 동일한 것을 사용하였습니다. 


In [32]: A = np.array([[3,6], [2,3], [0,0], [0,0]])


In [33]: print(A)

[[3 6]

 [2 3]

 [0 0]

 [0 0]]


In [34]: u, s, vh = np.linalg.svd(A)


In [35]: print(u)

[[-0.8816746 -0.47185793 0. 0. ]

 [-0.47185793 0.8816746 0. 0. ]

 [ 0. 0. 1. 0. ]

 [ 0. 0. 0. 1. ]]


In [36]: print(s)

[7.60555128 0.39444872]


In [37]: print(vh)

[[-0.47185793 -0.8816746 ]

 [ 0.8816746 -0.47185793]]

 




  8. 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)


아래의 두 개 연립방정식의 해(x0, x1)를 np.linalg.solve(a, b) 함수를 사용하여 풀어보겠습니다. 



위의 연립방정식을 어떻게 행렬로 입력하고, np.linalg.solve(a, b)에 입력하는지 유심히 살펴보시기 바랍니다. 



In [38]: a = np.array([[4, 3], [3, 2]])


In [39]: b = np.array([23, 16])


In [40]: x = np.linalg.solve(a, b)


In [41]: print(x)

[2. 5.]

 



NumPy 가 제대로 x0, x1의 해를 풀었는지 확인해보겠습니다. x0=2, x1=5 가 해 맞네요!



In [42]: np.allclose(np.dot(a, x), b)

Out[42]: True

 




  9. 최소자승 해 풀기 (Compute the Least-squares solution)

     : m, c = np.linalg.lstsq(A, y, rcond=None)[0]


회귀모형 적합할 때 최소자승법(Least-squares method)으로 잔차 제곱합을 최소화하는 회귀계수를 추정합니다. 


* 참고 링크 : https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html



아래의 예에서는 회귀계수 m, y절편 c를 최소자승법을 사용해서 구해보겠습니다. 



In [43]: x = np.array([0, 1, 2, 3])


In [44]: y = np.array([-1, 0.2, 0.9, 2.1])


In [45]: A = np.vstack([x, np.ones(len(x))]).T


In [46]: A

Out[46]:

array([[0., 1.],

        [1., 1.],

        [2., 1.],

        [3., 1.]])


In [47]: m, c = np.linalg.lstsq(A, y, rcond=None)[0]


In [48]: print(m, c)

0.9999999999999999 -0.9499999999999997

 



아래의 그래프에서 점은 원래의 데이터이며, 빨간색 선은 최소자승법으로 추정한 회귀식의 적합선이 되겠습니다. 



In [49]: import matplotlib.pyplot as plt

    ...: plt.plot(x, y, 'o', label='Original data', markersize=10)

    ...: plt.plot(x, m*x + c, 'r', label='Fitted line')

    ...: plt.legend()

    ...: plt.show()




많은 도움이 되었기를 바랍니다. 


728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 특성방정식(chacacteristic equations)을 이용하여 고유값(eigenvalue)과 고유벡터(eigenvector)를 구하는 방법에 대하여 알아보았습니다.

 

이번 포스팅에서는 행렬의 대각화(diagonalization), 그리고 고유값과 고유벡터를 이용하여 n차 정방행렬의 p제곱을 구하는 방법을 소개하겠습니다.  이번 포스팅을 보고 나시면 왜 고유값, 고유벡터가 다방면에 두루두루 쓰이는지, 왜 중요한지 그 원리가 이해되실 거예요.

 

 

먼저, 대각행렬, 대각화, 대각행렬의 p 제곱으로 시작하겠습니다. 대각성분을 제외한 모든 성분이 0인 행렬을 대각행렬(diagonal matrix) 이라고 하며 diag(a11, a22, ..., ann) 으로 표기합니다. (아래의 예시 참조).  그리고 적절한 기저변환을 통하여 주어진 행렬을 대각행렬로 변환하는 것을 대각화(diagonolization)이라고 합니다.  대각화를 하면 정말 유용한 특성이 있는데요, n차 정방행렬의 p 제곱을 구하는 것이 정말 쉽다는 점입니다!!! 그냥 대각성분을 p 제곱 해주는 것으로 끝나거든요!!! (아래의 그림 참조).  

 

 

 

 

 

그럼 다음 단계로, 고유값, 고유벡터와 대각화가 무슨 관련이 있는지로 넘어가보겠습니다.

 

지난번 포스팅에서 예로 들었던 2차 정방행렬 A=를 가지고 계를 예를 들어보겠습니다.  정방행렬 A의 고유값 λ = {7, 2} 였으며, λ1 = 7에 대응하는 고유벡터는 (2  3)^T, λ2=2에 대응하는 고유벡터는 (-1  1)^T 였습니다.

 

 

 (* 고유값, 고유벡터 구하기 자세한 내용은 ☞  http://rfriend.tistory.com/182 )

 

 

위의 2차 정방행렬 A의 고유값(eigenvalue), 고유벡터(eigenvector)를 가져다가 고유값, 고유벡터의 정의에 대입한 두 개의 식을 정리하면 아래와 같습니다.  (중간에 보면 식의 양변에 역행렬(inverse matrix)를 곱하는 것이 나오는데요, 혹시 역행렬 잘 모르시면 http://rfriend.tistory.com/142 참고하세요. 행렬식(determinant) = 2*1 - (-1)*3 = 5로서 0이 아니므로 역행렬 존재합니다)

 

제일 아래에 정리된 결과의 형태를 유심히 보시기 바랍니다.

 

 

 

 

 

위에 정리한 식의 제일 아래 식에 설명을 달아보면 아래와 같습니다.  고유값과 고유벡터가 존재하는 정방행렬의 경우는 아래와 같이 분해가 가능하답니다.

 

 

 

 

위와 같이 정방행렬 A를 고유값과 고유벡터를 사용해서 분해를 하면 p 제곱하는 것이 어떻게 진행되는지 예를 들어보겠습니다.

 

 

 

 

위의 마지막의 계산 결과를 보면 재미있지요? ^^

 

계산 해본 김에 위의 2차 정방행렬 A의 3제곱을 계산해보겠습니다.

 

 

 

 

짜잔~ 제일 마지막에 3제곱한 결과를 보면 정말 아름답지 않은가요? ^^!

 

그럼, 문제를 하나 내볼께요.  위의 2차 정방행렬 A를 100 제곱하면 결과가 어떻게 나올까요? 

....

....

...

네, 맞습니다.  짐작하셨겠지만... 아래처럼 나옵니다.   

 

 

 

 

위에서 열심히 예를 들어서 계산을 해봤는데요, 이를 일반화해보자면,

n차 정방행렬(n order square matrix) 의 p제곱은 아래와 같이 쓸 수 있습니다.

 

 

 

 

여기까지 따라 오시느라 수고 많으셨습니다.

위의 고유값(eigenvalue)과 고유벡터(eigenvector)를 이용한 n차 정방행렬의 p제곱의 정리를 이용하면 p가 매우 크더라도 연산량을 줄여서 쉽게 p제곱을 구할 수 있습니다.  이게 참 중요하게, 요긴하게 여기저기서 많이 쓰입니다.

 

 

p차 정방행렬 A는 p개의 고유값(eigenvalue)  과 각 고유값에 대응하는 p개의 고유벡터(eigenvector) 를 가집니다. 이때 아래의 6개의 식은 동치(equivalent) 관계에 있는 명제들입니다. 

 

 

 

 

다음번 포스팅에서는 이번 포스팅에서 소개한 n차 정방행렬의 p제곱하는 방법을 적용한 마르코프 과정(Markov process)에 대해서 알아보겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 공감 ♡ 꾸욱~ 눌러주세요. ^^

 

728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 행 사다리꼴(Row echelon form)과 계수(Rank)를 이용해서 선형연립방정식 해의 존재성(existence)과 유일성(uniqueness)을 알아보는 방법을 소개하였습니다.

 

이번 포스팅에서는 고유값(eigenvalue)과 고유벡터(eigenvector)에 대해서 알아보겠습니다.

 

필자는 경영학을 전공했었는데요, 통계학과의 다변량통계분석 과목을 (겁도 없이 무대뽀로...) 수강했었습니다.  차원축소 기법 중 하나인 요인분석(factor analysis) 시간에 고유값(eigenvalue)과 고유벡터(eigenvector)를 처음 접했었는데요, 그땐 당초에 무슨 소리인지 교수님의 강의를 하나도 못 알아들었습니다.  지금 보면 그냥 이해가 되고 어려워 보이지 않는데요, 그땐 참 소련말처럼 들리고 어렵더라고요. ^^;;; 

 

요즘 회사에서 제조업쪽에 분석 사례 관련 논문을 자주 찾아보는 편인데요, 왠만한 논문에는 고유값(eigenvalue)과 고육벡터(eigenvector) 표기를 마주치곤 합니다.  그만큼 아주 중요하고 많이 쓰이는 개념입니다.

 

다소 단순하게 보이는 벡터 방정식 "정방행렬 A에 대해 Ax = λx "로부터 놀랄만큼 많은 관련 이론과 풍부한 응용예가 유도된다. 실제로 공학, 물리학, 기하학, 수치해석, 이론수학, 생물학, 환경과학, 도시계획, 경제학, 심리학 등 많은 분야에서 고유값 문제가 나타난다

 

- Erwin Keryszig, 선형대수와 벡터 미적분학, 범함서적주식회사

 

자, 그럼 고유값(eigenvalue)과 고유벡터(eigenvector)의 정의에 대해서 부터 시작하시지요.

 

 

 

정방행렬 A에 대하여 Ax = λx  (상수 λ) 가 성립하는 0이 아닌 벡터 x가 존재할 때
상수 λ 를 행렬 A의 고유값 (eigenvalue), x 를 이에 대응하는 고유벡터 (eigenvector) 라고 합니다.  

 

 

 

 

행렬 A에 대한 고유값(eigenvalue) λ ("Lambda", "람다" 라고 읽음)은 특성값(characteristic value), 또는 잠정근(latent root) 라고도 합니다. (eigen은 '고유' 또는 '특성'을 뜻하는 독일어임. '아이겐'이라고 읽음)

 

Ax = λx 를 만족하는 0이 아닌 고유벡터(eigenvector) x 는 특성벡터(characteristic vector) 라고도 합니다.

 

그리고 행렬 A의 모든 고유값의 집합을 A의 스펙트럼(spectrum) 이라고 하며, 최대로 서로 다른 n개의 고유값을 가질 수 있습니다.  A의 고유값의 절대값의 최대값을 A의 스펙트럼 반경 (spectrum radius)라고 합니다.

 

이때 행렬 A는 n*n 정방행렬(square matrix) 이라는 점 다시 한번 상기하시구요, Ax = λx를 만족하는 모든 상수 λ와 0이 아닌 모든 벡터 x (1개 ~ 최대 n 개)를 찾는 것이 우리가 할 일입니다.  

 

 

좀더 쉽게 이해할 수 있도록 고유값(eigenvalue)과 고유벡터(eigenvector)가 가지는 의미를 아래의 예를 들어서 설명하겠습니다.

 

 

 정방행렬 A

(square matrix A)

 

  고유값 λ

(eigenvalue)

λ = 7 

λ = 2 

 고유벡터 x

(eigenvector)

 

 

 

 

 

 

 

고유값(eigenvalue)와 고유벡터(eigenvector)의 기하학적인 의미를 살펴보면, 벡터 x에 대해 n차 정방행렬 A를 곱하는 결과와 상수 λ를 곱하는 결과가 같다는 의미입니다. 즉, 행렬의 곱의 결과가 원래 벡터와 "방향"은 같고, "배율"만 상수 λ 만큼만 비례해서 변했다는 의미입니다.  이게 고유값(eigenvalue)과 고유벡터(eigenvector)가 무척 중요한 이유입니다.  행렬과 벡터 곱을 했더니 "방향"도 바뀌고 "크기(배율)"도 모두 바뀌는 것과, "방향"은 그대로 있고 "크기(배율)"만 바뀌는 것 중에 뭐가 연산이 간단할 지 생각해보시면 됩니다.  

아래의 2차 정방행렬 A=(4,3   2, 5) 에 의해 대응되는 선형사상 f에 의한 c1(2, 3)+c2(-1, 1) 의 상을 가지고 기하학적인 의미의 예를 들어보겠습니다. 

 

 

 

 

위의 결과를 좌표에 나타내보면 아래와 같습니다.  좀 복잡해보이긴 하는데요, 화살표의 eigenvector (2, 3), (-1, 1)의 R^2 공간이 정방행렬 A=(4, 3   2, 5)에 의해서 오른쪽 R^2 공간으로 변환될 때 "방향"은 똑같고, "배율"만 eigenvalue λ 배수 (7배, 2배) 만큼 변했다는 것을 알 수 있습니다.  

 

왼쪽의 A, B, C, D, E 의 좌표점들이 오른쪽에는 A', B', C', D', E' 로 정방행렬 A=(4, 3   2, 5)에 의해 변환되었습니다. 계산 예시로 B (2, 3) -> B' (14, 21)와 D (-1, 1) -> D' (-2, 2) 만 아래 그래프 위에 겹쳐서 제시해보았습니다.

 

 

 

 

 

좀더 직관적으로 이해할 수 있도록 아래에 사람 얼굴()이 어떻게 변환되는지 겹쳐서 제시해보았습니다.  eigenvector의 방향은 똑같고 (same direction), 크기만 eigenvalue 만큼씩 배수(magnification)가 되었습니다.

 

 

 

이제 고유값(eigenvalue)와 고유벡터(eigenvector)의 정의와 기하학적인 의미에 대해서는 이해가 좀 되시는지요?

 

행렬식이랑 좌표에 그림 그리려니 시간이 어마무시 걸리네요. ㅜ_ㅜ  

아... 봄날의 토요일 오후가 그냥 가버렸어요... 흑...

 

다음번 포스팅에서는 고유값(eigenvalue)과 고유벡터(eigenvector)를 구하는 방법에 대해서 소개하도록 하겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 공감 ♡ 꾸욱~ 눌러주세요. ^^

 

728x90
반응형
Posted by Rfriend
,