4.3.6 다차원 배열의 쌍방향 차원 확장 브로드캐스팅

다차원 브로드캐스팅 작동 웹툰 일러스트

부족한 모양과 차원을 스스로 복제하는 마법 시스템

앞서 본 스칼라 점(1차원) 뿐만 아니라 선 모형을 가진 1D 다차원 배열이나 열 벡터조차도 서로 모양이 다르다면 Numpy는 에러를 뿜는 게 아니라 스스로 모양을 맞추기 위해 동시에 부족한 축 방향으로 자기 자신을 복제 확장(Broadcasting) 하는 마법을 보여줍니다.

이것만 알면 어떤 복잡한 행렬끼리의 연산일지라도 아주 편하게 코딩할 수 있습니다. 어떻게 늘어나는지 3가지 케이스를 살펴봅시다!

① 가로선 배열(1D)이 거대한 사분면(2D) 면적 깊숙이 하강침투 복제

먼저 아주 큰 3행 3열짜리 배열이 있고, 그 배열의 너비(칸 수 3)에 정확히 딱 맞는 가로줄 1차원 배열이 있다고 가정해 봅니다.

import numpy as np

# 거대한 3x3 2차원 공간 배열 x
x = np.arange(1, 10).reshape(3, 3)
x

출력:

array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
# 옆으로 세 칸 누워있는 1차원 배열 y
y = np.arange(-1, 2)
y

출력:

array([-1,  0,  1])

두 배열 x (3x3) 와 y (3칸) 곱셈을 수행하면 어떻게 될까요? Numpy는 가로 길이가 서로 3으로 일치함을 인지하고 1차원 y를 위에서 아랫방향으로 복제합니다. [-1, 0, 1] 선분 하나가 마치 도장 찍듯이 아래로 쿵, 쿵, 쿵 세 번 찍혀서 x와 동일한 3x3 모습으로 스스로를 위조한 뒤 계산에 임합니다.

# y가 3줄로 아래로 복제된 뒤 1:1 아다마르 곱 수행 
x * y

출력:

array([[-1,  0,  3],
       [-4,  0,  6],
       [-7,  0,  9]])

② 세로 기둥 배열(열 벡터)이 우측으로 옆구리 확장 펀치 복제

이번에는 3층 탑처럼 세로축으로 쌓여있는 좁은 2차원 열 벡터의 경우입니다.

# 세로로 3층이 쌓인 (3, 1) 모양의 배열 y
y = np.arange(3, 10, 3).reshape(3, 1)
y

출력:

array([[3],
       [6],
       [9]])

거대한 3x3 xx / y 나누기 연산을 요구하면 세로 기둥 탑 y는 자신의 우측 옆구리에 빈 공간을 메우기 위해 오른쪽 방향을 향해 탁, 탁, 탁 자신의 복제본을 기둥째로 박아 넣으며 넓어집니다. (3, 1) 배열 안의 한계점 1은 늘리기가 아주 쉬운 고무줄 포인트이기 때문입니다!

# y가 우측으로 복제되어 3x3 모양을 만든 뒤 1:1 나눗셈
x / y

출력:

array([[0.33333333, 0.66666667, 1.        ],
       [0.66666667, 0.83333333, 1.        ],
       [0.77777778, 0.88888889, 1.        ]])

③ 쌍방향 진격! 가로 배열과 세로 기둥 배열의 전면 교전

가장 멋진 마법이 구현되는 백미입니다. 만약 넓은 빈 공간을 두고 가로줄 1차원 배열과 세로줄 1차원 배열끼리 격돌하면 무슨 일이 벌어질까요?

다방면 배열 브로드캐스팅 애니메이션

# 옆으로 세 칸 누워있는 (3,) 배열 x
x = np.arange(1, 4)
x

출력:

array([1, 2, 3])
# 위아래 세 칸 쌓여있는 (3, 1) 배열 y
y = np.arange(1, 4).reshape(3, 1)
y

출력:

array([[1],
       [2],
       [3]])

곱셈 구구식을 돌리듯 x * y를 입력하면, x는 텅 빈 위아래를 채우기 위해 수직 하강 복제를 하고, y는 텅 빈 양옆을 채우기 위해 측면 분신술을 사용합니다! 결국 둘 다 거대한 (3, 3)으로 변신하여 완벽한 계산 테이블을 장악하게 됩니다.

# x는 아래로 세 번 늘어나고, y는 우측으로 세 번 늘어납니다! -> 그 결과 3x3 곱셈 테이블!
x * y

출력:

array([[1, 2, 3],
       [2, 4, 6],
       [3, 6, 9]])

모양이 극단적으로 다를 경우에도 이 마법은 통합니다! 세로 4층 탑 (4, 1) 배열과 가로 2칸짜리 (2,) 배열이 만나면? 둘은 합심해서 거대한 (4, 2) 2차원 사분면을 만들어냅니다.

x = np.arange(0, 10, 3).reshape(4, 1)  # 4층 탑 형태 -> 2열짜리 우측 확장을 준비
y = np.arange(1, 3)                    # 2칸 가로선 형태 -> 4층짜리 하강 확장을 준비

x + y

출력:

array([[ 1,  2],
       [ 4,  5],
       [ 7,  8],
       [10, 11]])
서브목차