[PyTorch] Autograd-03 : Practice03

olxtar·2022년 4월 8일
0

[PyTorch] Autograd

목록 보기
5/5
post-thumbnail

[PyTorch] Autograd-03 : Practice02에 이어서...

참고 : https://teamdable.github.io/techblog/PyTorch-Autograd



# 기본 세팅 및 사용하는 함수

import torch

def get_tensor_info(tensor):
  info = []
  for name in ['requires_grad', 'is_leaf', 'retains_grad', 'grad_fn', 'grad']:
    info.append(f'{name}({getattr(tensor, name, None)})')
  info.append(f'tensor({str(tensor)})')
  return ' '.join(info)



11. Tensor로의 확장

지금까지는 Variable 즉, 변수들 x,q,w,y,zx,q,w,y,z 등등등이 모두 1x1사이즈 였지? (Tensor였긴하네...)
근데 이제는 다차원의 Tensor로 확장시켜보자. (일단은 2차원 정도만...)





x = torch.tensor(5.0, requires_grad=True)
y = x * torch.tensor([2.0, 3.0, 5.0])
z = y @ torch.tensor([4.0, 7.0, 9.0])

print('x', get_tensor_info(x))
print('y', get_tensor_info(y))
print('z', get_tensor_info(z))

>>>
x requires_grad(True) is_leaf(True) retains_grad(False) grad_fn(None) grad(None) tensor(tensor(5., requires_grad=True))
y requires_grad(True) is_leaf(False) retains_grad(False) grad_fn(<MulBackward0 object at 0x000001DFB14DF820>) grad(None) tensor(tensor([10., 15., 25.], grad_fn=<MulBackward0>))
z requires_grad(True) is_leaf(False) retains_grad(False) grad_fn(<DotBackward0 object at 0x000001DFB14561F0>) grad(None) tensor(tensor(370., grad_fn=<DotBackward0>))

먼저 선언한 Tensor들과 Variable들을 이해하기 쉽게 수식으로 나타내면 아래와 같다.

  • x=5x=5

  • y=f(x)=x  ×  [2,3,5]=[x,x,x]×[2,3,5]=[5,5,5]  ×  [2,3,5]=[2x1,3x2,5x3]=[10,15,25]=[y1,y2,y3]y=f(x)=x\;\times\;\begin{bmatrix}2, 3, 5 \end{bmatrix}=[x,{\color{gray}x,x}]\times[2,3,5]=[5,{\color{gray}5,5}]\;\times\;[2,3,5]={\color{orange}[2x_1,3x_2,5x_3]}=[10,15,25]=[y_1,y_2,y_3]

  • z=g(y)=y    [4,7,9]=[y1,y2,y3][479]=[10,15,25][479]=                  4y1+7y2+9y3=10×4  +15×7  +25×9  =370=zz=g(y)=y \;\cdot\;[4,7,9]=[y_1,y_2,y_3]\cdot\begin{bmatrix}4 \\ 7 \\ 9 \end{bmatrix}=[10,15,25]\cdot\begin{bmatrix}4 \\ 7 \\ 9 \end{bmatrix}=\;\;\;\;\;\;\;\;\;{\color{orange}4y_1+7y_2+9y_3}=10\times4\;+15\times7\;+25\times9\;=370=z

[+] operation에만 집중하면...
y=f(x)=[2x,3x,5x]=[y1,y2,y3]y=f(x)={\color{orange}[2x,3x,5x]}=[y_1,y_2,y_3]
z=g(y)=4y1+7y2+9y3=zz=g(y)={\color{orange}4y_1+7y_2+9y_3}=z scalar!

y는 x가 들어오면 x값(broadcast)에 각각 2,3,5를 곱해줘서 (1x3) Tensor로 내보냄
z는 y가 들어오면 각각 4,7,9를 곱해준 뒤 더해서 Scalar Tensor로 내보냄

이처럼 forward 방향으로의 연산이 진행된다.
이제는 backward 방향으로의 연산을 살펴보자.



y.retain_grad()               #y와 z에서의 gradient도 확인하기 위해 backward() 전에 사용.
z.retain_grad()
z.backward()

print('x_after_backward', get_tensor_info(x))
print('y_after_backward', get_tensor_info(y))
print('z_after_backward', get_tensor_info(z))

>>>
x_after_backward requires_grad(True) is_leaf(True) retains_grad(False) grad_fn(None) grad(74.0) tensor(tensor(5., requires_grad=True))
y_after_backward requires_grad(True) is_leaf(False) retains_grad(True) grad_fn(<MulBackward0 object at 0x000001DFB14DF5E0>) grad(tensor([4., 7., 9.])) tensor(tensor([10., 15., 25.], grad_fn=<MulBackward0>))
z_after_backward requires_grad(True) is_leaf(False) retains_grad(True) grad_fn(<DotBackward0 object at 0x000001DFB14561F0>) grad(1.0) tensor(tensor(370., grad_fn=<DotBackward0>))

backward 방향으로의 연산...

먼저 우리가 유심히 봐야할 부분은 grad이다.
위 코드의 출력결과에서
x.grad는 74
y.grad는 [4, 7, 9]
z.grad는 1 이 나왔다.
위의 값을 기억하면서 아래의 그림과 수식을 체크해보자.

업로드중..

[+] Remind!
y=f(x)=[2x,3x,5x]=[y1,y2,y3]y=f(x)=[2x,3x,5x]=[y_1,y_2,y_3]
z=g(y)=4y1+7y2+9y3=zz=g(y)=4y_1+7y_2+9y_3=z


우리의 목표? backward의 목표? \rightarrow zx\frac{\partial z}{\partial x} 구하기!
자 위에 Remind를 보면서 잘생각해야함 y는 3개가 있고 x는 1개만 있는거야!

zx\frac{\partial z}{\partial x}를 구하기 위해 거꾸로 거슬러 올라가보자.

  1. y1,y2,y3y_1,y_2,y_3에 대한 zz의 gradient를 구해보자. yy에 대한 zz의 gradient yz\frac{\partial}{\partial y}z!
    zy1,zy2,zy3\frac{\partial z}{\partial y_1}, \frac{\partial z}{\partial y_2}, \frac{\partial z}{\partial y_3}

  1. xx에 대한 y1,y2,y3y_1,y_2,y_3의 gradient를 구해보자.
y1x,y2x,y3x\frac{\partial y_1}{\partial x}, \frac{\partial y_2}{\partial x}, \frac{\partial y_3}{\partial x}

  1. 각각을 곱해서 더해보자.
zy1y1x+zy2y2x+zy3y3x=zx+zx+zx\frac{\partial z}{\cancel{\partial y_1}}\cdot\frac{\cancel{\partial y_1}}{\partial x}+\frac{\partial z}{\cancel{\partial y_2}}\cdot\frac{\cancel{\partial y_2}}{\partial x}+\frac{\partial z}{\cancel{\partial y_3}}\cdot\frac{\cancel{\partial y_3}}{\partial x}=\frac{\partial z}{\partial x}+\frac{\partial z}{\partial x}+\frac{\partial z}{\partial x}

직접 구해보면...
z=g(y)=4y1+7y2+9y3z=g(y)=4y_1+7y_2+9y_3 이므로...

y1z=y1(4y1+7y2+9y3)=4\frac{\partial}{\partial y_1}z=\frac{\partial}{\partial y_1}(4y_1+7y_2+9y_3)=4
y2z=y2(4y1+7y2+9y3)=7\frac{\partial}{\partial y_2}z=\frac{\partial}{\partial y_2}(4y_1+7y_2+9y_3)=7
y3z=y3(4y1+7y2+9y3)=9\frac{\partial}{\partial y_3}z=\frac{\partial}{\partial y_3}(4y_1+7y_2+9y_3)=9


y=f(x)=[2x,3x,5x]=[y1,y2,y3]y=f(x)=[2x,3x,5x]={\color{gray}[y_1,y_2,y_3]}
y1=f(x)=2xy_1=f(x)=2x
y2=f(x)=3xy_2=f(x)=3x
y3=f(x)=5xy_3=f(x)=5x
이므로...

y1x=2\frac{\partial y_1}{\partial x}=2
y2x=3\frac{\partial y_2}{\partial x}=3
y3x=5\frac{\partial y_3}{\partial x}=5


zx=zy1y1x+zy2y2x+zy3y3x=\therefore\frac{\partial z}{\partial x}=\frac{\partial z}{\partial y_1}\cdot\frac{\partial y_1}{\partial x}+\frac{\partial z}{\partial y_2}\cdot\frac{\partial y_2}{\partial x}+\frac{\partial z}{\partial y_3}\cdot\frac{\partial y_3}{\partial x}=

42  +  73  +  95=744\cdot2\;+\;7\cdot3\;+\;9\cdot5=74

아까 위에서 Code로 확인한 x.grad값 또한 74!

y.grad, z.grad도 체크해보자.

[+] 먼저 A.grad의 의미는 Tensor A에 대한! gradient라는 의미이고... 담겨있는 값은 retain_grad()를 호출한 Tensor에 대한 .backward()호출한 Tensor의 Gradient이다.

ex1) Tensor Arequires_grad=True라 하였고,
Tensor B.backward()를 호출하면 (Tensor B는 Tensor A로부터 파생됨)
" Tensor A에 대한 Tensor B의 기울기"
" Gradient of Tensor B with respect to (w.r.t) Tensor A "를 구하게 됩니다.

BA\frac{\partial {\color{blue}B}}{\partial {\color{red}A}}

ex2) Tensor Arequires_grad=True라 하였고,
Tensor x.retain_grad()를 호출하고 (Tensor x는 Tensor A로부터 파생됨)
Tensor B.backward()를 호출하면 (Tensor B는 Tensor x로부터 파생됨)
" Tensor x에 대한 Tensor B의 기울기"
" Gradient of Tensor B with respect to (w.r.t) Tensor x " 또한 구하게 됩니다.

Bx\frac{\partial {\color{blue}B}}{\partial {\color{green}x}}

y.gradzy\frac{\partial z}{\partial y}이고 yy function은 하나의 input xx에 대해서 3개의 output 즉, (1x3) Tensor를 내보낸다.

y=f(x)=[2x,3x,5x]=[y1,y2,y3]y=f(x)={\color{gray}[2x,3x,5x]}=[y_1,y_2,y_3]

따라서 y.grad(1x3) Tensor일 것! \rightarrow [zy1,zy2,zy3][\frac{\partial z}{\partial y_1},\frac{\partial z}{\partial y_2},\frac{\partial z}{\partial y_3}]


y1z=y1(4y1+7y2+9y3)=4\frac{\partial}{\partial y_1}z=\frac{\partial}{\partial y_1}(4y_1+7y_2+9y_3)=4

y2z=y2(4y1+7y2+9y3)=7\frac{\partial}{\partial y_2}z=\frac{\partial}{\partial y_2}(4y_1+7y_2+9y_3)=7

y3z=y3(4y1+7y2+9y3)=9\frac{\partial}{\partial y_3}z=\frac{\partial}{\partial y_3}(4y_1+7y_2+9y_3)=9

[4,7,9]\therefore [4,7,9]

y.grad값도 grad(tensor([4., 7., 9.])








12. detach()

[+] detach : v. 떼다, 떼어네다, 분리하다, 분리되다.

x = torch.tensor(5.0, requires_grad=True)
y = x * torch.tensor([2.0, 3.0, 5.0])
w = y.detach()                                    #y를 detach!
z = w @ torch.tensor([4.0, 7.0, 9.0])

print('x', get_tensor_info(x))
print('y', get_tensor_info(y))
print('w', get_tensor_info(w))
print('z', get_tensor_info(z))

z.backward()

print('x_after_backward', get_tensor_info(x))
print('y_after_backward', get_tensor_info(y))
print('w_after_backward', get_tensor_info(w))
print('z_after_backward', get_tensor_info(z))

>>>
x requires_grad(True) is_leaf(True) retains_grad(False) grad_fn(None) grad(None) tensor(tensor(5., requires_grad=True))
y requires_grad(True) is_leaf(False) retains_grad(False) grad_fn(<MulBackward0 object at 0x000001DFB5E0FEE0>) grad(None) tensor(tensor([10., 15., 25.], grad_fn=<MulBackward0>))
w requires_grad(False) is_leaf(True) retains_grad(False) grad_fn(None) grad(None) tensor(tensor([10., 15., 25.]))
z requires_grad(False) is_leaf(True) retains_grad(False) grad_fn(None) grad(None) tensor(tensor(370.))

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

requires_grad가 True인 Tensor를 이용하여 계산을 한 결과 Tensor 즉, requires_grad=True인 Tensor로부터 파생 및 연산된 Tensor들은 자동으로 requires_grad=True가 된다.

이것을 의도적으로 끊고 싶을 때 (해당 계산(Operation)을 통해 backward()를 부르지 않을 것이라면) .detach()를 호출한다.

detach()가 return한 Tensor는 requires_grad가 False로 설정되고 해당 Tensor에서 파생된 Tensor도 requires_grad가 False가 됨.

...
y = x * torch.tensor([2.0, 3.0, 5.0])
w = y.detach()                                    #y를 detach!
z = w @ torch.tensor([4.0, 7.0, 9.0])
...

위 코드에서처럼 y Tensor 선언 후 detach로 끊어버린다. 그러면 y.detach()가 return한 값 w에서부터는 requires_grad가 False가 되고
이는 곧 "여기서 부터는 backward에 대비하지 마라" 라는 의미가 되므로
is_leaf는 True가 되고 grad_fn도 구하지 않게 된다.

\therefore z.backward()는 RuntimeError뜸








13. detach() 이후 requires_grad() 재호출

x = torch.tensor(5.0, requires_grad=True)
y = x * torch.tensor([2.0, 3.0, 5.0])
w = y.detach()
w.requires_grad_()
z = w @ torch.tensor([4.0, 7.0, 9.0])

11. detach()에 이어서...
위 코드와 같이 detach()이후 다시 requires_grad_()를 통해 다시 붙여주면 어떻게 될까?




print('x', get_tensor_info(x))
print('y', get_tensor_info(y))
print('w', get_tensor_info(w))
print('z', get_tensor_info(z))


x requires_grad(True) is_leaf(True) retains_grad(False) grad_fn(None) grad(None) tensor(tensor(5., requires_grad=True))
y requires_grad(True) is_leaf(False) retains_grad(False) grad_fn(<MulBackward0 object at 0x000001DFB1451F70>) grad(None) tensor(tensor([10., 15., 25.], grad_fn=<MulBackward0>))
w requires_grad(True) is_leaf(True) retains_grad(False) grad_fn(None) grad(None) tensor(tensor([10., 15., 25.], requires_grad=True))
z requires_grad(True) is_leaf(False) retains_grad(False) grad_fn(<DotBackward0 object at 0x000001DFB1451EB0>) grad(None) tensor(tensor(370., grad_fn=<DotBackward0>))

x,y,w,z 모두 requires_grad는 True가 되었으나
w의 is_leaf가 True로 되어있다. (detach때문임)

또한 w의 grad_fn도 존재하지 않기 때문에 z.backward()를 호출해도 x.grad는 저장되지 않고 w.grad만 저장됩니다.




z.backward()

print('x_after_backward', get_tensor_info(x))
print('y_after_backward', get_tensor_info(y))
print('w_after_backward', get_tensor_info(w))
print('z_after_backward', get_tensor_info(z))

>>>
x_after_backward requires_grad(True) is_leaf(True) retains_grad(False) grad_fn(None) grad(None) tensor(tensor(5., requires_grad=True))
y_after_backward requires_grad(True) is_leaf(False) retains_grad(False) grad_fn(<MulBackward0 object at 0x000001DFB14564F0>) grad(None) tensor(tensor([10., 15., 25.], grad_fn=<MulBackward0>))
w_after_backward requires_grad(True) is_leaf(True) retains_grad(False) grad_fn(None) grad(tensor([4., 7., 9.])) tensor(tensor([10., 15., 25.], requires_grad=True))
z_after_backward requires_grad(True) is_leaf(False) retains_grad(False) grad_fn(<DotBackward0 object at 0x000001DFB14518B0>) grad(None) tensor(tensor(370., grad_fn=<DotBackward0>))

설명...

Forward
x=5x=5
y=f(x)=[2x,3x,5x]=[y1,y2,y3]y=f(x)=[2x,3x,5x]={\color{gray}[y_1,y_2,y_3]}
w=w= y.detach \rightarrow requires_grad=True
z=g(w)=4w1+7w2+9w3z=g(w)=4w_1+7w_2+9w_3

Backward
zz=1\frac{\partial z}{\partial z}=1
\updownarrow zzgrad_fn 이용

zw1=4,  zw2=7,  zw3=9\frac{\partial z}{\partial w_1}=4, \; \frac{\partial z}{\partial w_2}=7, \; \frac{\partial z}{\partial w_3}=9

\updownarrow wwgrad_fn 이용하려 했는데 없음...

\therefore [zzzw1,    zzzw2,    zzzw3]=[zw1,zw2,zw3]=[4,7,9][\frac{\partial z}{\partial z}\cdot\frac{\partial z}{\partial w_1}, \;\; \frac{\partial z}{\partial z}\cdot\frac{\partial z}{\partial w_2}, \;\; \frac{\partial z}{\partial z}\cdot\frac{\partial z}{\partial w_3}]=[\frac{\partial z}{\partial w_1},\frac{\partial z}{\partial w_2},\frac{\partial z}{\partial w_3}]=[4,7,9]=w.grad만 저장됨.








14. detach() 이후 재연결시키기

11. Tensor로의 확장 ~ 13. detach() 이후 requires_grad 재호출까지 사용된 Network의 흐름을 설명 해보겠음.

\rightarrowForward
input값(x=5x=5)는 f(x)f(x) 즉, MulFunction을 거쳐
[y1,y2,y3]=[2x,3x,5x]=[10,15,25][y_1,y_2,y_3]=[2x,3x,5x]=[10,15,25]이 됨
그 후 DotFunction을 거쳐서
z=4y1+7y2+9y3=40+105+225=370z=4y_1+7y_2+9y_3=40+105+225=370이 됨

[!] MulFunction : 1개의 input을 3개의 output으로 만듦
[!] DotFunction : 3개의 input을 1개의 output으로 만듦


\leftarrowBackward
x의 requires_grad=True + z.backward() 호출 \rightarrow gradient of z with respect to x 구하기
먼저 zz=1{\color{orange}\frac{\partial z}{\partial z}}=1DotBackward을 거쳐서
[zzzy1,  zzzy2,  zzzy3]=[4,7,9][{\color{orange}\frac{\partial z}{\partial z}}\frac{\partial z}{\partial y_1},\;{\color{orange}\frac{\partial z}{\partial z}}\frac{\partial z}{\partial y_2},\;{\color{orange}\frac{\partial z}{\partial z}}\frac{\partial z}{\partial y_3}]=[4,7,9]가 됨.

그 후 zy1,zy2,zy3=[4,7,9]{\color{orange}\frac{\partial z}{\partial y_1},\frac{\partial z}{\partial y_2},\frac{\partial z}{\partial y_3}}=[4,7,9]MulBackward를 거쳐서
zy1y1x+zy2y2x+zy3y3x=42+73+95=74{\color{orange}\frac{\partial z}{\partial y_1}}\frac{\partial y_1}{\partial x}+{\color{orange}\frac{\partial z}{\partial y_2}}\frac{\partial y_2}{\partial x}+{\color{orange}\frac{\partial z}{\partial y_3}}\frac{\partial y_3}{\partial x}=4\cdot2+7\cdot3+9\cdot5=74가 됨.

[!] MulBackward : 3개의 input을 1개의 output으로 만듦 만들어야함
[!] DotBackward : 1개의 input을 3개의 output으로 만듦 만들어야함
[+] 어떤 Tensor의 requires_grad가 True일 때, 해당 Tensor에 대한 연산이 이루어지면 그에 대한 역방향 편미분 즉, Gradient를 구할 수 있는 연산이 연산Backward에 저장된다. 이 연산Backward연산 방향의 역방향 편미분이다.
AA\rightarrow연산B\rightarrow B였다면 연산BackwardBA\frac{\partial B}{\partial A}이다.
[+] backward 함수는 호출한 Tensor로부터 requires_grad=True (+is_leaf=True)인 Tensor까지 거슬러 올라가면서 연산Backward를 하는 것인데 중요한 점은 (이전의 값을 넣어주면서) 이어진다는 것이다. 뭔소리인지 모르겠지? 아래에서 설명해드림

z.backward()의 input은 zz=1\frac{\partial z}{\partial z}=1이다. (일단 토달지 말고 따라와보셈)
[y1,y2,y3][y_1,y_2,y_3]\rightarrowDotFunctionz\rightarrow z였으므로 DotBackward[zy1,  zy2,  zy3][\frac{\partial z}{\partial y_1},\;\frac{\partial z}{\partial y_2},\;\frac{\partial z}{\partial y_3}]
여기에 input zz=1\frac{\partial z}{\partial z}=1 이걸 각각에 곱해줌 (이게 Jacobian Vector Product, JVP인가!?)
그래서 [zzzy1,  zzzy2,  zzzy3]=[4,7,9][\frac{\partial z}{\partial z}\frac{\partial z}{\partial y_1},\;\frac{\partial z}{\partial z}\frac{\partial z}{\partial y_2},\;\frac{\partial z}{\partial z}\frac{\partial z}{\partial y_3}]=[4,7,9]가 되는거임!


여기서 [4,7,9][4,7,9]를 y.backward()의 input으로 넘겨줌 (y.backward()는 정확한거아님)
xx\rightarrowMulFunction[y1,y2,y3]\rightarrow [y_1,y_2,y_3]이었으므로 MulBackwardy1x+y2x+y3x\frac{\partial y_1}{\partial x}+\frac{\partial y_2}{\partial x}+\frac{\partial y_3}{\partial x}
여기에 input이었던 [4,7,9][4,7,9]를 각각에 곱해줌 (JVP?)
그래서 zy1y1x+zy2y2x+zy3y3x=42+73+95=74\frac{\partial z}{\partial y_1}\frac{\partial y_1}{\partial x}+\frac{\partial z}{\partial y_2}\frac{\partial y_2}{\partial x}+\frac{\partial z}{\partial y_3}\frac{\partial y_3}{\partial x}=4\cdot2+7\cdot3+9\cdot5=74가 되는거임!



그러면 이제 아래와 같은 상황에서 zx\frac{\partial z}{\partial x}를 구하려면 어떻게 해야될까? (13. detach() 이후 requires_grad() 재호출과 같은 상황)

x = torch.tensor(5.0, requires_grad=True)
y = x * torch.tensor([2.0, 3.0, 5.0])
w = y.detach()
w.requires_grad_()
z = w @ torch.tensor([4.0, 7.0, 9.0])

print('x', get_tensor_info(x))
print('y', get_tensor_info(y))
print('w', get_tensor_info(w))
print('z', get_tensor_info(z))

#z.backward()

>>>
x requires_grad(True) is_leaf(True) retains_grad(None) grad_fn(None) grad(None) tensor(tensor(5., requires_grad=True))
y requires_grad(True) is_leaf(False) retains_grad(None) grad_fn(<MulBackward0 object at 0x7f517f15b9e8>) grad(None) tensor(tensor([10., 15., 25.], grad_fn=<MulBackward0>))
w requires_grad(True) is_leaf(True) retains_grad(None) grad_fn(None) grad(None) tensor(tensor([10., 15., 25.], requires_grad=True))
z requires_grad(True) is_leaf(False) retains_grad(None) grad_fn(<DotBackward object at 0x7f517f15b9b0>) grad(None) tensor(tensor(370., grad_fn=<DotBackward>))

z.backward()를 호출하면 zz=1\frac{\partial z}{\partial z}=1이 input으로 전달되고
zz의 grad_fn인 DotBackward를 통해 zw\frac{\partial z}{\partial w}을 구해서 input zz\frac{\partial z}{\partial z}와 곱한다.
그 값은 [4,7,9][4,7,9]일 것이고 w.grad에 저장될 것이다.
그 다음 그 값을 넘겨서...
w(y)w{\color{gray}(y)}의 grad_fn인 ???를 통해 w(y)x\frac{\partial w{\color{gray}(y)}}{\partial x}를 구해 곱해줘야하는데...
ww의 is_leaf도 True이고, grad_fn도 없네?

  • 우리가 가진 것 : zzzw=zzzy{\color{gray}\frac{\partial z}{\partial z}}\frac{\partial z}{\partial w}={\color{gray}\frac{\partial z}{\partial z}}\frac{\partial z}{\partial y}
  • 우리가 필요한 것 : yx    zzzyyx=zx\frac{\partial y}{\partial x}\;\;\because {\color{gray}\frac{\partial z}{\partial z}}\frac{\partial z}{\partial y}\cdot\frac{\partial y}{\partial x}=\frac{\partial z}{\partial x}

그러면 yx\frac{\partial y}{\partial x} 즉, yy에 저장되어 있는 MulBackward!
그리고 MulBackward에 input으로 우리가 가진 zw(y)\frac{\partial z}{\partial w{\color{gray}(y)}}을 넘겨주면 되겠네!

y.backward(gradient=w.grad)
z.backward()
y.backward(gradient=w.grad)

print('x_after_backward', get_tensor_info(x))

>>>
x_after_backward requires_grad(True) is_leaf(True) retains_grad(None) grad_fn(None) grad(74.0) tensor(tensor(5., requires_grad=True))

x.grad에 74.0이 저장된 것을 볼 수 있다





[+] yy에 저장된 MulBackward, 즉 yx\frac{\partial y}{\partial x}'만'의 값을 봐보자. (체인룰로 상쇄되지 않게끔 input으로 zy\frac{\partial z}{\partial y}를 넘겨주지 말고 그냥 1 tensor를 넘겨줘보자.)
[+] 참고로 xyx\rightarrow y로의 연산은 1개의 input에 대해 3개의 output이 나왔음. 따라서 MulBackward는 3개의 input을 줘야함. 즉 Scalar값이 아닌 3개의 값을 가진 Tensor를 줘야함!

x = torch.tensor(5.0, requires_grad=True)
y = x * torch.tensor([2.0, 3.0, 5.0])
z = y @ torch.tensor([4.0, 7.0, 9.0])

t = torch.tensor( [1.0, 1.0, 1.0])       # y.backward에게 줄 1 tensor

y.backward(t)
x.grad

>>>
10

x의 requires_grad가 True이고 y한테 backward를 호출하였으므로
yx\frac{\partial y}{\partial x}를 구함.
y의 grad_fn, 즉 MulBackwardxx에 대한 y1,y2,y3y_1,y_2,y_3의 그라디언트를 구해주는 Function이므로 그냥 y.backward()만 쓰면 에러뜸!
backward()를 호출할 때 gradient를 특별히 설정하지 않으면 기본값 1(ZZ\frac{\partial Z}{\partial Z})로 설정됨

따라서 [1, 1, 1] Tensor를 넘기면

1y1x+1y2x+1y3x1\cdot\frac{\partial y_1}{\partial x}+1\cdot\frac{\partial y_2}{\partial x}+1\cdot\frac{\partial y_3}{\partial x}을 구하게 되는거임.

y=[2x,3x,5x]=[y1,y2,y3]y=[2x,3x,5x]=[y_1,y_2,y_3]이었으므로...

1y1x+1y2x+1y3x=2+3+5=101\cdot\frac{\partial y_1}{\partial x}+1\cdot\frac{\partial y_2}{\partial x}+1\cdot\frac{\partial y_3}{\partial x}=2+3+5=10

이 값이 x.grad에 저장!

profile
예술과 기술

0개의 댓글