[개발자를 위한 실전 선형대수학] Chapter 4 연습문제

[chapter 4] 행렬, 파트 1 : 행렬과 행렬의 기본 연산

4-1

3 x 4 행렬 생성 후, 두 번째 행 네 번째 열의 원소 추출하기.

A = np.arange(12).reshape(3,4)
a = 1
b = 3

print(f'The matrix element at index ({a+1}, {b+1}) is {A[a, b]}')

4-2

그림 4-6의 왼쪽 행렬 C를 만들고 파이썬의 슬라이싱을 사용해 처음 5개의 행과 5개의 열로 이루어진 하위 행렬 추출하기.

이 행렬을 $C_1$이라 부른다.

A = np.arange(100).reshape(10,10)
C1 = A[0:5:1, 0:5:1]
print(C1)
#[[ 0  1  2  3  4]
# [10 11 12 13 14]
# [20 21 22 23 24]
# [30 31 32 33 34]
# [40 41 42 43 44]]

4-3

4-2에서 추출한 행렬을 확장해 다른 4개의 5x5 블록을 추출하기.

그리고 그림 4-7처럼 블록들을 재구성해 새로운 행렬을 만들기.

A = np.arange(100).reshape(10,10)
C1 = A[0:5:1, 0:5:1]
C2 = A[5:10:1, 0:5:1]
C3 = A[0:5:1, 5:10:1]
C4 = A[5:10:1, 5:10:1]

newMatrix = np.vstack( (np.hstack((C4,C2)), # np.hstack((A, B)) : 가로로 붙임. row 개수가 같아야 함
                        np.hstack((C3,C1))) ) # np.vstack((A, B)) : 세로로 붙임. col 개수가 같아야 함
print(newMatrix)
#[[55 56 57 58 59 50 51 52 53 54]
# [65 66 67 68 69 60 61 62 63 64]
# [75 76 77 78 79 70 71 72 73 74]
# [85 86 87 88 89 80 81 82 83 84]
# [95 96 97 98 99 90 91 92 93 94]
# [ 5  6  7  8  9  0  1  2  3  4]
# [15 16 17 18 19 10 11 12 13 14]
# [25 26 27 28 29 20 21 22 23 24]
# [35 36 37 38 39 30 31 32 33 34]
# [45 46 47 48 49 40 41 42 43 44]]

4-4

행과 열에 대해 for 루프 사용해서 원소별 행렬 덧셈 구현하기. 크기가 일치하지 않는 행렬을 더하면 어떻게 될까

A = np.zeros((6,4))
B = np.ones((6,4))

C = np.zeros(A.shape)
for i in range(A.shape[0]):
  for j in range(A.shape[1]):
    C[i,j] = A[i,j] + B[i,j]

print(C)
# 크기가 일치하지 않는 행렬을 더하면 IndexError가 발생한다.

4-5

3 x 4 크기의 두 개의 난수 행렬과 난수 스칼라를 만들고, 아래를 증명하기.

$$ \sigma(A+B) = \sigma A + \sigma B = A \sigma + B \sigma  $$

x = np.random.rand() 

A = np.random.rand(3,4) 
B = np.random.rand(3,4)

left = x*(A+B)
center = x*A + x*B
right = A*x + B*x

print(np.round(2*left - center-right, 8)) 
#[[ 0.  0.  0.  0.]
# [ 0.  0. -0.  0.]
# [ 0.  0. -0.  0.]]
#  = 세 개의 결과가 모두 같다.

4-6

for 루프 사용해 행렬 곱셈 코딩한 뒤, NumPy @ 연산자를 활용해 결과를 비교하기.

A = np.random.randn(4,3)
B = np.random.randn(3,4)

C1 = np.zeros((4,4))
for i in range(A.shape[0]):
  for j in range(B.shape[1]):
    C1[i,j] = np.dot(A[i,:], B[:,j])

C2 = A@B
print(C1)
print(C2) # 두 결과가 같다

4-7

다음의 5단계를 활용하여 LIVE EVIL 규칙을 확인하기.

  • 네 개의 난수 행렬 L,I,V,E를 생성하기. 각각 크기는 2x6, 6x3, 3x5, 5x2
  • 이 네 개의 행렬을 곱한 다음 전치
  • 각 행렬을 전치한 뒤, 순서를 바꾸지 않고 모두 곱함
  • 각 행렬을 전치한 뒤, LIVE EVIL 규칙에 따라 역순으로 모두 곱함

2단계의 결과가 3, 4단계와 일치하는지 확인하기.

# 난수 행렬 생성
L = np.random.randn(2,6)
I = np.random.randn(6,3)
V = np.random.randn(3,5)
E = np.random.randn(5,2)

# 이 네 개의 행렬을 곱한 다음 전치
A1 = np.transpose(L@I@V@E)

# 각 행렬을 전치한 뒤, 순서를 바꾸지 않고 모두 곱함
# A2 = L.T @ I.T @ V.T @ E.T - 에러남

# 각 행렬을 전치한 뒤, LIVE EVIL 규칙에 따라 역순으로 모두 곱함
A3 = E.T @ V.T @ I.T @ L.T
print(A1)
print(A3) # 두 결과가 같다.

4-8

행렬의 대칭 여부를 확인하는 파이썬 함수 작성하기.

행렬을 입력받고 그 행렬이 대칭이면 True, 행렬이 비대칭이면 False를 출력해야 한다.

def isMatrixSymmetric(S):
  # S가 대칭행렬이면 D는 모든 원소가 0인 행렬이 된다.
  D = S-S.T

  # D의 모든 항을 제곱해서 더한다 → 오차의 합(SSE)
  # 완전 대칭행렬이면 D = 0 → SSE = 0
  sse = np.sum(D**2)
   
   # 부동소수점 연산에는 항상 아주 작은 오차가 있을 수 있어서 완전히 0인지 확인하면 안 됨
   # 거의 0에 가까우면 대칭이라고 인정
  return sse < 10**-15

4-9

비대칭 사각 행렬로부터 대칭 행렬을 만들기 위해서는 행렬을 자신의 전치로 평균화하면 된다.

파이썬에서 이를 구현하고, 결과가 대칭인지 확인하기.

A = np.random.randn(4,4)
AtA = (A + A.T) / 2

a = isMatrixSymmetric(AtA)
print(a)

4-10

연습문제 2-3의 두 번째 부분을 다시 구현하는데, 이번에는 스칼라-벡터 곱셈 대신 행렬-벡터 곱셈을 사용하기.

# 3차원 벡터 2개
# v1 = np.array([ 3,5,1 ])
# v2 = np.array([ 0,2,2 ])

# 연습문제 2-3에서 사용했던 위 벡터 2개 말고 행렬 사용
A = np.array( [ [3,0],
                [5,2],
                [1,2] ] )

xlim = [-4,4]
scalars = np.random.uniform(low=xlim[0],high=xlim[1],size=(100,2))

points = np.zeros((100,3))
for i in range(len(scalars)):
  points[i,:] = A@scalars[i] # 행렬-벡터 곱셈

# draw the dots in the figure
fig = go.Figure( data=[go.Scatter3d(x=points[:,0], y=points[:,1], z=points[:,2], mode='markers')])
fig.show()

4-11

대각행렬의 속성 두가지는 아래와 같다.

  • 대각 행렬을 왼쪽에서 곱하면 오른쪽 행렬의 행이 해당 대각 원소 크기만큼 조정된다.
  • 대각 행렬을 오른쪽에서 곱하면 왼쪽 행렬의 열이 행 대각 원소 크기만큼 조정된다.

세 개의 4x4 행렬을 만드는데, 모든 원소가 1인 행렬, 대각 원소가 1, 4, 9, 16인 대각 행렬, 이전 대각 행렬의 원소의 제곱근으로 이루어진 대각행렬이다.

첫 번째 대각 행렬을 모든 원소가 1인 행렬의 앞과 뒤에서 각각 곱한 행렬을 출력하면 다음과 같다.

# 대각 행렬을 왼쪽에서 곱함
[
    [1. 1. 1. 1.]
    [4. 4. 4. 4.]
    [9. 9. 9. 9.]
    [16. 16. 16. 16]
]

# 대각 행렬을 오른쪽에서 곱함
[
    [1. 4. 9. 16.]
    [1. 4. 9. 16.]
    [1. 4. 9. 16.]
    [1. 4. 9. 16.]
]

제곱근한 대각 행렬을 모든 원소가 1인 행렬의 앞과 뒤에서 곱하면 다음과 같은 결과가 나온다.

# 제곱근한 대각 행렬을 앞과 뒤에서 곱함
[
    [1. 2. 3. 4.]
    [2. 3. 6. 8.]
    [3. 6. 9. 12.]
    [4. 8. 12. 16.]
]

즉, 결과 행렬의 (i,j) 번째 원소가 대각 행렬의 i번째와 j번째 원소의 곱만큼 증가하도록 행과 열의 크기가 조정된다.

4-12

대각 행렬 곱셈은 아다마르곱과 같다.

두 개의 3x3 대각 행렬에 대해 파이썬 코드로 작성하기.

# Create two diagonal matrices
N = 5
D1 = np.diag( np.random.randn(N) )
D2 = np.diag( np.random.randn(N) )

# two forms of multiplication
hadamard = D1*D2
standard = D1@D2

# compare them
hadamard - standard