40.1 broadcast_to 함수와 sum_to 함수(넘파이 버전)
np.broadcast_to(x, shape)는 ndarray 인스턴스인 x의 원소를 복제하여 shape 인수로 지정한 형상이 되도록 해줍니다.

broadcast_to 함수의 역전파의 입력 x의 형상과 같아지도록 기울기의 합을 구해줍니다.
sum_to(x, shape)로 해당 부분을 구현해주면 되고 이 함수는 x의 원소의 합을 구해 shape 형상으로 만들어주는 함수 입니다.
넘파이에서는 이런함수가 없고 DeZero에서 sum_to 함수를 만들어줍니다.
def sum_to(x, shape):
"""Sum elements along axes to output an array of a given shape.
Args:
x (ndarray): Input array.
shape:
Returns:
ndarray: Output array of the shape.
"""
ndim = len(shape)
lead = x.ndim - ndim
lead_axis = tuple(range(lead))
axis = tuple([i + lead for i, sx in enumerate(shape) if sx == 1])
y = x.sum(lead_axis + axis, keepdims=True)
if lead > 0:
y = y.squeeze(lead_axis)
return y
위의 코드는 주어진 배열의 원소를 특정 축(axes)을 따라 합하여 주어진 shape를 가진 배열을 반환하는 기능을 수행합니다.
x는 입력배열, shape는 출력 배열입니다.
lead는 입력 배열과 출력 배열간의 차원의 차이입니다.
lead_axis는 차원의 차이의 갯수를 축으로 튜플 타입으로 담은 것입니다.
axis = tuple([i + lead for i, sx in enumerate(shape) if sx == 1])
여기가 제일 이해가 안가는 부분이였는데 그래도 이해한 부분까지 설명하자면,
출력 배열에서 1인 부분은 해당 차원에서 모든 원소를 합산하는 것을 의미합니다.
합산해야할 축들의 인덱스를 결정합니다.
합산해야할 축을 결정했으니
x.sum(lead_axis + axis, keepdims=True)로 리드축과 axis에서 원소를 합산해서 새로운 배열 y를 만들어줍니다.
그 이후 lead가 0보다 크면 squeeze 함수를 사용해서 불필요한 차원을 제거합니다.
최종적으로 합산된 배열 y를 반환합니다.
실제 사용하는데 예시코드는 아래에 있습니다.
import numpy as np
from dezero.utils import sum_to
x = np.array([[1,2,3],[4,5,6]])
y = sum_to(x, (1,3))
print(y)
y = sum_to(x, (2,1))
print(y)
x는 2행 3열이기때문에 shape는 (2,3)입니다.
이 x를 1행 3열로 만들어 주는게 첫번째 y의 코드입니다.
2행을 하나의 행으로 만들어줄때 각 위치에 있는 원소를 더하고 하나의 차원을 뺴줘서 y는 [[5, 7, 9]]가 됩니다.
두번째 y는 2행은 그대로이고 열에 있는 원소를 하나로 만들어줍니다. 열을 하나로 만들어주려면 각 행자리에 있는 원소들을 더해줍니다. [[1+2+3], [4+5+6]]으로 해주면 두번째 y의 결과는 [[6],[15]]가 됩니다.

위의 그림은 sum_to 함수의 역전파는 broadcast_to함수를 사용하여 입력 ㅌ의 형상과 같아지도록 기울기의 원소를 복제합니다.
40.2 broadcast_to 함수와 sum_to 함수(DeZero 버전)
class BroadcastTo(Function):
def __init__(self, shape):
self.shape = shape
def forward(self, x):
self.x_shape = x.shape
xp = dezero.cuda.get_array_module(x)
y = xp.broadcast_to(x, self.shape)
return y
def backward(self, gy):
gx = sum_to(gy, self.x_shape)
return gx
def broadcast_to(x, shape):
if x.shape == shape:
return as_variable(x)
return BroadcastTo(shape)(x)
class SumTo(Function):
def __init__(self, shape):
self.shape = shape
def forward(self, x):
self.x_shape = x.shape
y = utils.sum_to(x, self.shape)
return y
def backward(self, gy):
gx = broadcast_to(gy, self.x_shape)
return gx
def sum_to(x, shape):
if x.shape == shape:
return as_variable(x)
return SumTo(shape)(x)
위의 코드에서 역전파 부분을 잘 보면되는데
BroadcastTo 백워드에서 gy를 인풋 쉐이프로 형상을 일치 시키고
SumTo의 백워드에서는 입력 x와 쉐이프가 같아지게 기울기 원소를 복제합니다.
이부분이 이해하기 너무 복잡한데 아래의그림을 다시 보면

sum_to는 자리값을 더해서 쉐이프를 변경해주면 브로드캐스트는 아웃풋 쉐잎대로 그대로 늘려주는 거라고 이해하면 좋을 것 같습니다.
40.3 브로드캐스트 대응
브로드캐스트란 형상이 다른 다차원 배열끼리의 연산을 가능하게 하는 넘파이 기능입니다.
sum_to를 구현한 이유는 넘파이 브로드 캐스트에 대응하기 위해서입니다.
현재, DeZero에서 브로드캐스트의 역전파가 일어나지 않기 때문에 이 부분을 수정해줘야합니다.
class Add(Function):
def forward(self, x0, x1):
self.x0_shape, self.x1_shape = x0.shape, x1.shape
y = x0 + x1
return y
def backward(self, gy):
gx0, gx1 = gy, gy
if self.x0_shape != self.x1_shape: # for broadcaset
gx0 = dezero.functions.sum_to(gx0, self.x0_shape)
gx1 = dezero.functions.sum_to(gx1, self.x1_shape)
return gx0, gx1
순전파 때 브로드캐스트가 일어난다면 입력되는 x0과 x1의 형상이 다를 것이고 두 형상이 다르면 브로드캐스트용 역전파를 계산하는 것입니다.
기울기 gx0을 x0의 형상이 되도록 합을 구하고 기울기 gx1은 x1의 형상이 되도록 합을 구합니다.
위의 코드가 잘 적용됬는지 확인하는 코드는 아래에 있습니다.
if '__file__' in globals():
import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import numpy as np
from dezero import Variable
x0 = Variable(np.array([1, 2, 3]))
x1 = Variable(np.array([10]))
y = x0 + x1
print(y)
y.backward()
print(x1.grad)'Study 공부할레나' 카테고리의 다른 글
| [스터디/밑시딥3] 제 4고지 신경망 만들기_42. 선형 회귀 (1) | 2023.10.10 |
|---|---|
| [스터디/밑시딥3] 제 4고지 신경망 만들기_41. 행렬의 곱 (1) | 2023.10.09 |
| [스터디/밑시딥3] 제 4고지 신경망 만들기_38. 형상 변환 함수 (1) | 2023.10.08 |
| [스터디/밑시딥3] 제 4고지 신경망 만들기_37. 텐서를 다루다 (1) | 2023.10.08 |
| [스터디/밑시딥3] 제 4고지 신경망 만들기_39. 합계 함수 (0) | 2023.09.19 |