Backpropagation, 벡터로 미분하기

안 형준·2021년 8월 30일
0

DLmath

목록 보기
1/5
post-thumbnail

Gradient Descent 방법에서 backpropagation이나 가중치 업데이트 등의 토픽을 다루다보면
Loss function을 가중치로 미분하는 연산이 등장합니다. 이때, 가중치는 행렬이므로 스칼라 함수를 행렬로 미분하는 연산을 정의해야 합니다.

1. Notation

스칼라는 x,y,cx, y, c 등의 소문자로, 열벡터(n×1)(n \times 1)a\bf a, b\bf b 등의 볼드체 소문자로, 행렬은 A,BA, B 등 대문자로 표기하겠습니다. 따라서 행벡터(1×n)(1 \times n)aT\bf a^T, bT\bf b^T가 됩니다.
벡터의 크기는 aTa\bf a^T a, 내적은 aTb\bf a^T b 등으로 표현할 수 있습니다. 예외적으로 Loss function의 경우 스칼라지만 대문자 LL로 표기하겠습니다

2. Numerator layout vs Denominator layout

위키피디아 링크
y\bf y (m×1)(m \times 1)x\bf x (n×1)(n \times 1)로 미분한 결과의 차원이 (n×m)(n\times m)이 되는 표기법과 (m×n)(m\times n)이 되는 표기법이 공존합니다.

분자중심표기(Numerator layout): yx\frac{\partial \bf y}{\partial \bf x}(m×n)(m\times n) 행렬이 됩니다. 즉, 행의 개수가 분자와 같습니다.

분모중심표기(Denominator layout): yx\frac{\partial \bf y}{\partial \bf x}(n×m)(n\times m) 행렬이 됩니다. 즉, 행의 개수가 분모와 같습니다.

식을 보면 x\bf x는 원래의 형태인 열벡터처럼 행을 따라 x1,x2,x3,...xnx_1, x_2, x_3, ... x_n으로 나열되어 있고, y\bf y는 원래의 형태가 아닌 그 트랜스포즈처럼 열을 따라 y1,y2,y3...,ymy_1, y_2, y_3 ..., y_m으로 나열됩니다. 즉 분모는 원래의 모양대로 나열되고, 분자는 트랜즈포즈로 나열됩니다.
이 포스트에서는 분모중심표기를 따르겠습니다. SGD는 가중치를 WWαWLW \rightarrow W -\alpha\nabla_W L에 따라 업데이트하는데 WL\nabla_W L이 곧 LW\frac{\partial L}{\partial W}이고, WW와 연산이 가능하려면 같은 모양이어야 하기 때문입니다.

3. Cross Entropy Loss 를 Weight로 미분하기

이제부터는 직접 예시를 통해 벡터에 대한 미분과 행렬에 대한 미분을 살펴봅니다.

LL이 Loss function (스칼라)이고 W는 가중치 행렬일 때, LW\frac{\partial L}{\partial W}는 분모인 WW의 모양대로 나열됩니다.
입력 노드가 4개이고, 출력 노드가 3개인 1-layer Perceptron 을 생각해보겠습니다.

이 모델으로 분류 문제를 풀도록 합니다.

이 때, activation으로는 softmax를 사용하고, Loss function은 cross entropy를 사용합니다.
WW(4×3)(4\times 3)의 행렬이고, 입력 xT{\bf{x^T}}np.array([[0.2, -0.4, 0.3, 0.5]])를 사용해 봅니다.

xT{\bf{x^T}}에 대해서 살펴본다면 batch_size를 NN, 차원을 DD라고 할 때 (N×D)(N\times D) 모양입니다. N=1N=1이고, 행벡터 형태로 사용하기 때문에 트랜스포즈로 표기했습니다.

xTW{\bf{x^T}}W의 모양은 (1×3)(1\times 3)으로 각 행에 대해 softmax를 적용한 뒤에도 (1×3)(1\times 3)이 유지됩니다.
zT=xTW{\bf{z^T}}={\bf{x^T}}W 라고 하고, softmax를 ff라고 하면 적용한 결과는 yT=f(zT){\bf{y^T}}={f(\bf{z^T})} 로 표기할 수 있습니다.

chain rule (yxi==1myuuxi\frac{\partial y}{\partial x_i} = \sum_{\ell = 1}^m \frac{\partial y}{\partial u_\ell} \frac{\partial u_\ell}{\partial x_i})에 의해,

LW=i,jLyiTyiTzjTzjTW\frac{\partial L}{\partial W} = \sum_{i, j}\frac{\partial L}{\partial \bf{y^T_i}} * \frac{\partial \bf{y^T_i}}{\partial \bf{z^T_j}}* \frac{\partial \bf{z^T_j}}{\partial W} 입니다.

1. LyT\frac{\partial L}{\partial \bf{y^T}} 구하기(스칼라를 벡터로 미분)

벡터 미분에 앞서, Loss function을 yT\bf{y^T}의 각 component로 미분한다면 어떻게 될 지 살펴봅시다.
label이 one-hot encoded vector라고 한다면 Cross Entropy Loss 는 다음과 같습니다.

# calculate cross entropy
def cross_entropy(p, q):
	return -sum([p[i]*log(q[i]) for i in range(len(p))])

ce = cross_entropy(expected, predicted)
loss = mean(ce, axis=0)

p{p}는 label이고 q{q}는 예측값입니다.
이 때 label은 one-hot encoded vector입니다. 따라서 단 하나의 index에 대해서만 그 값이 1이고, 나머지는 0이 됩니다.

Lqi=1Npiqi\frac{\partial L}{\partial q_i}=-\frac{1}{N}\frac{p_i}{q_i}

N=1N=1이고, jj가 정답 인덱스라고 한다면,

Lqi=0(ij)  or1qj(i=j)\frac{\partial L}{\partial q_i}= 0(i\neq j) \;or -\frac{1}{q_j}(i=j) 입니다.

LL은 스칼라이고 , yT\bf{y^T}는 벡터이므로 LyT\frac{\partial L}{\partial \bf{y^T}}yT\bf{y^T}와 그 형태가 같고, 그 값들은 LLyT\bf{y^T}의 각 성분으로 미분한 것과 같습니다. 예측값 yT\bf{y^T}y_pred로 저장하고 LyT\frac{\partial L}{\partial \bf{y^T}}y_pred_grad의 변수에 넣는다면 다음과 같습니다.

label = np.array([[0, 1, 0]])
y_pred = np.array([[0.3, 0.4, 0.3]])

y_pred_grad = np.where(label==1, -1.0/y_pred, 0)

print(y_pred_grad)
# np.array([[0, -2.5, 0]])

2. yTzT\frac{\partial \bf{y^T}}{\partial \bf{z^T}} 구하기(벡터를 벡터로 미분)

(1×3)(1\times 3)벡터를 (1×3)(1\times 3)벡터로 미분하는 것이니 그 형태가 (3×3)(3\times 3)이 될 것을 알 수 있습니다.
yT\bf{y^T}의 각 성분을 y1,y2,y3y_1, y_2, y_3, zT\bf{z^T}의 각 성분을 z1,z2,z3z_1, z_2, z_3라 하면 yTzTij=yjzi\frac{\partial \bf{y^T}}{\partial \bf{z^T}}_{ij} = \frac{\partial {y_j}}{\partial {z_i}}입니다.

yi=ezijezjy_i =\frac{e^{z_i}}{\sum_j {e^{z_j}}}이므로 미분하면,

yjzi=yjyi  (ifij)oryj(1yj)  (ifi=j)\frac{\partial {y_j}}{\partial {z_i}}=-y_j y_i\;(if \quad i\neq j)\quad or \quad y_j(1-y_j)\;(if \quad i=j)
yTzT=[y1(1y1)y2y1y3y1y1y2y2(1y2)y3y2y1y3y2y3y3(1y3)]\frac{\partial \bf{y^T}}{\partial \bf{z^T}}= \left[ \begin{array}{cc} y_1(1-y_1) & -y_2y_1 & -y_3y_1 \\ -y_1y_2 & y_2(1-y_2) & -y_3y_2 \\ -y_1y_3 & -y_2y_3 & y_3(1-y_3) \end{array} \right]

이 됩니다.

3. zTW\frac{\partial \bf{z^T}}{\partial W} 구하기(벡터를 행렬로 미분)

zT=xT\bf{z^T} = \bf{x^T} WW에서
zTW\frac{\partial \bf{z^T}}{\partial W}xT\bf{x^T} 로 쓰고 싶을 수 있습니다. 그래도 되는지 살펴보겠습니다.

wikipedia에서 Matrix Calculus를 살펴보면 벡터를 행렬로 미분하는 경우는 굉장히 드물고, 잘 정의되지 않는다고 합니다.

따라서, x\bf xxT\bf x^T 중 어느 쪽이 맞는지 비교해보고 넘어가겠습니다.
1, 2에서 얻은 결과

LyT=[0  1y2  0]yTzT=[y1(1y1)y2y1y3y1y1y2y2(1y2)y3y2y1y3y2y3y3(1y3)]\frac{\partial L}{\partial \bf{y^T}}=[0 \;-\frac{1}{y_2} \;0] \\ \newline \\ \frac{\partial \bf{y^T}}{\partial \bf{z^T}}= \left[ \begin{array}{cc} y_1(1-y_1) & -y_2y_1 & -y_3y_1 \\ -y_1y_2 & y_2(1-y_2) & -y_3y_2 \\ -y_1y_3 & -y_2y_3 & y_3(1-y_3) \end{array} \right]

를 통해,
LzT=[y1y21y3]\frac{\partial L}{\partial \bf{z^T}}=[y_1\quad y_2 -1\quad y_3]임을 알 수 있고, 간단히 y_pred - label로 계산할 수 있게 됩니다.

LW=zTWLzT\frac{\partial L}{\partial W}=\frac{\partial \bf{z^T}}{\partial W}\frac{\partial L}{\partial \bf{z^T}}에서 분모중심표기에 따라 차원을 비교하면,
(4×3)=(4×N)(N×3)(4\times 3) =(4 \times N) * (N \times 3)이 되어야 한다는 것을 알 수 있고,
따라서 zTW=x\frac{\partial \bf{z^T}}{\partial W} = \bf{x} 입니다.

결론

1-layer perceptron에서 입력이 xT\bf x^T이고 softmax layer를 통과한 출력이 yT\bf y^T, label이 ytrueT\bf y_{true}^T이고 Loss function이 Cross Entropy일 때,

LW=x\frac{\partial L}{\partial W}=\bf{x}[y1y21y3]=x[y_1\quad y_2 -1\quad y_3]\\\quad =\bf{x} 1N(yTytrueT)\frac{1}{N}(\bf{y^T}-\bf{y_{true}^T})입니다.
이 때, x\bf{x}는 입력xT\bf{x^T} (N×D)(N\times D)의 트랜스포즈이므로 (D×N)(D\times N)입니다

참고

https://atmos.washington.edu/~dennis/MatrixCalculus.pdf

밑바닥부터 시작하는 딥러닝 3

profile
물리학과 졸업/ 인공지능 개발자로의 한 걸음

0개의 댓글