본문 바로가기

Mathematics/Linear Algebra

[선형대수학] 3장 - 행렬 <알고리즘 구현으로 배우는 선형대수 with 파이썬>

본 글은 [장철원 저 - 선형대수와 통계학으로 배우는 머신러닝 with 파이썬]을 읽고, 개인적으로 찾아본 내용을 더해 정리한 글입니다.
본 글에 나오는 소스코드들의 원본은 출판사의 github 레포지토리(https://github.com/bjpublic/LinearAlgebra) 에서 찾아볼 수 있습니다.
까먹었을 때 바로바로 확인하기 위한 용도로 정리하는 것이기에, 제 필요에 따라 생략, 변형 및 추가된 내용이 많습니다.
책의 내용을 그대로 담지 않도록 노력하였으나, 혹여 문제가 있다면 cuffyluv.1@gmail.com으로 연락주시기 바랍니다.


  • 데이터 셋 : 데이터들의 집합
  • 데이터의 특성(피쳐, feature) : ‘표’에서의 ‘열’ 또는 ‘열 벡터’ ex. 나이, 성별, 몸무게, …
  • 데이터의 레코드(record) : ‘표’에서의 ‘행’ 또는 ‘행 벡터’ ex. 나이라는 특성에 대한 ‘10’, ‘20’, …
  • 파이썬에서는 벡터를 List로 표현 가능함.
# 벡터의 덧셈
u = [1, 2, 3]
v = [4, 5, 6]

n = len(u)
w = []

for i in range(0, n):
    val = u[i] + v[i]
    w.append(val)
  • 넘파이에서는 벡터를 np.array 메서드를 사용해 표현함.
# 벡터의 덧셈 
u = np.array([1,2,3])
v = np.array([4,5,6])
w = u + v
# 여기선 그냥 두 벡터를 '+' 연산자로 더해주기만 해도 됨!! Wow!!
# cf) 여기서 u, v, w의 자료형 : np.ndarray

→ 뺄셈, 곱셈, 나눗셈 같은 나머지 연산들도 쉽게 구현 가능(필요시 책 참고)

여담) 왜 굳이 넘파이의 np.adarray를 사용하는 걸까? 파이썬에서도 List로 벡터 표현이 가능한데??

https://iop8890.tistory.com/9 해당 블로그 글 참고.

→ 요약 : ndarray는 C언어의 배열처럼 연속적으로 메모리에 배치됨.

** 컴퓨터 구조에서 배웠던 spatial locality를 생각하자!! 한 번 참조된 곳 주변이 더 참조될 확률이 높음 → memory의 속도 향상 → 전체 연산 속도 향상(pipeline에서 가장 시간이 많이 걸리는 곳이 memory였으니까)

 

  • 정사각 행렬(squared matrix) : ‘행 크기 = 열 크기’ 인 행렬
  • 행렬의 원소 곱(matrix element multiplication)(=성분곱) : AO.B (’아다마르 곱’이라고도 함.)
  • 행렬의 곱(matrix multiplication) : AB (X기호는 쓰지 않는 게 원칙인듯)

** 행렬의 곱에서는 특수한 경우를 제외하곤 교환 법칙이 성립하지 않는다는 것에 주의!!

 

  • 행렬의 대각합(trace) : tr(A) = a11 + a22 + …

→ squared matrix에 대해서만 trace가 정의됨.

 

  • 파이썬의 행렬 합, 행렬 곱 (나머지 연산들은 책 참고)
# 행렬 합
A = [[2,7], [3,4], [6,1]]
B = [[1,4], [4,-1], [2,5]]

n = len(A)
p = len(A[0])

res = [] # 최종 결과가 저장될 '행렬'
for i in range(0, n):
    row = [] # 행 하나를 나타내는 '벡터'
    for j in range(0, p):
        val = A[i][j] + B[i][j]
        row.append(val) # '더한 값'을 하나하나씩 '행'에 넣어줌
    res.append(row) # '행'을 하나하나씩 '행렬'에 넣어줌.
# 행렬 곱
A = [[2,7], [3,4], [5,2]]
B = [[3,-3,5], [-1,2,-1]]

n = len(A)
p1 = len(A[0]) # 앞 행렬의 열 개수
p2 = len(B[0]) # 뒷 행렬의 열 개수

res = []
for i in range(0, n):
    row = []
    for j in range(0, p2):
        val = 0
        for k in range(0, p1):
            val += A[i][k] * B[k][j] // 여기서 k는 i, j랑 무관하게,
            ``` 
            그저 행 하나를 채우기 위해 슉슉 도는 것 뿐임!
            그 도는 행위는, 앞 행렬의 열 개수만큼 돌면 되겠지?
            그러고 나면 행 하나가 완성되니 그걸 결과 행렬에 넣어주고,
            이 행위를 뒷 행렬의 열 개수만큼 해주면 최종 행렬 완성!!
            ```
        row.append(val)    
    res.append(row)
  • 넘파이의 행렬 합, 원소 곱, 행렬 곱 (나머지 연산들은 책 참고)
# 행렬의 덧셈
A = np.array([[2,7], [3,4], [6,1]])
B = np.array([[1,4], [4,-1], [2,5]])
C = A + B
# 행렬의 원소곱
A = np.array([[1,5], [6,4], [2,7]])
B = np.array([[5,-1], [1,2], [4,1]])
C = np.multiply(A, B)
# 행렬 곱
A = np.array([[2,7], [3,4], [5,2]])
B = np.array([[3,-3,5], [-1,2,-1]])
C = np.matmul(A, B)

여기서, 원소곱이랑 곱에서는 기본 연산자(* 같은)가 아닌, 특정 메서드가 사용된다는 거 기억하자!!