[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)
지금까지는 Variable 즉, 변수들 등등등이 모두 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들을 이해하기 쉽게 수식으로 나타내면 아래와 같다.
[+] operation에만 집중하면...
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!
우리의 목표? backward의 목표?
구하기!
자 위에 Remind를 보면서 잘생각해야함 y는 3개가 있고 x는 1개만 있는거야!
를 구하기 위해 거꾸로 거슬러 올라가보자.
- 에 대한 의 gradient를 구해보자.
에 대한 의 gradient !
- 에 대한 의 gradient를 구해보자.
- 각각을 곱해서 더해보자.
직접 구해보면...
이므로...
이므로...
아까 위에서 Code로 확인한
x.grad
값 또한 74!
y.grad
,z.grad
도 체크해보자.
[+] 먼저 A.grad
의 의미는 Tensor A에 대한! gradient라는 의미이고... 담겨있는 값은 retain_grad()
를 호출한 Tensor에 대한 .backward()
호출한 Tensor의 Gradient이다.
ex1) Tensor A에 requires_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 "를 구하게 됩니다.
ex2) Tensor A에 requires_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 " 또한 구하게 됩니다.
y.grad
는 이고 function은 하나의 input 에 대해서 3개의 output 즉,(1x3) Tensor
를 내보낸다.따라서
y.grad
는(1x3) Tensor
일 것!
y.grad
값도grad(tensor([4., 7., 9.])
[+] 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도 구하지 않게 된다.
z.backward()
는 RuntimeError뜸
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
y.detach
requires_grad=True
Backward
의grad_fn
이용
의
grad_fn
이용하려 했는데 없음...=
w.grad
만 저장됨.
11. Tensor로의 확장 ~ 13. detach() 이후 requires_grad 재호출까지 사용된 Network의 흐름을 재설명 해보겠음.
Forward
input값()는 즉, MulFunction
을 거쳐
이 됨
그 후 DotFunction
을 거쳐서
이 됨
[!] MulFunction
: 1개의 input을 3개의 output으로 만듦
[!] DotFunction
: 3개의 input을 1개의 output으로 만듦
Backward
x의 requires_grad=True + z.backward() 호출 gradient of z with respect to x 구하기
먼저 이 DotBackward
을 거쳐서
가 됨.
그 후 가 MulBackward
를 거쳐서
가 됨.
[!] MulBackward
: 3개의 input을 1개의 output으로 만듦 만들어야함
[!] DotBackward
: 1개의 input을 3개의 output으로 만듦 만들어야함
[+] 어떤 Tensor의 requires_grad가 True일 때, 해당 Tensor에 대한 연산
이 이루어지면 그에 대한 역방향 편미분 즉, Gradient를 구할 수 있는 연산이 연산Backward
에 저장된다. 이 연산Backward
는 연산
방향의 역방향 편미분이다.
즉 연산
였다면 연산Backward
는 이다.
[+] backward 함수는 호출한 Tensor로부터 requires_grad=True (+is_leaf=True)인 Tensor까지 거슬러 올라가면서 연산Backward
를 하는 것인데 중요한 점은 (이전의 값을 넣어주면서) 이어진다는 것이다. 뭔소리인지 모르겠지? 아래에서 설명해드림
z.backward()의 input은 이다.
(일단 토달지 말고 따라와보셈)
DotFunction
였으므로DotBackward
는 임
여기에 input 이걸 각각에 곱해줌 (이게 Jacobian Vector Product, JVP인가!?)
그래서 가 되는거임!
여기서 를 y.backward()의 input으로 넘겨줌
(y.backward()는 정확한거아님)
MulFunction
이었으므로MulBackward
는 임
여기에 input이었던 를 각각에 곱해줌 (JVP?)
그래서 가 되는거임!
그러면 이제 아래와 같은 상황에서 를 구하려면 어떻게 해야될까? (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()를 호출하면 이 input으로 전달되고
의 grad_fn인DotBackward
를 통해 을 구해서 input 와 곱한다.
그 값은 일 것이고 w.grad에 저장될 것이다.
그 다음 그 값을 넘겨서...
의 grad_fn인???
를 통해 를 구해 곱해줘야하는데...
의 is_leaf도 True이고, grad_fn도 없네?
- 우리가 가진 것 :
- 우리가 필요한 것 :
그러면 즉, 에 저장되어 있는
MulBackward
!
그리고MulBackward
에 input으로 우리가 가진 을 넘겨주면 되겠네!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이 저장된 것을 볼 수 있다
[+] 에 저장된 MulBackward
, 즉 '만'의 값을 봐보자. (체인룰로 상쇄되지 않게끔 input으로 를 넘겨주지 말고 그냥 1 tensor를 넘겨줘보자.)
[+] 참고로 로의 연산
은 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를 호출하였으므로
를 구함.
y의 grad_fn, 즉MulBackward
는 에 대한 의 그라디언트를 구해주는 Function이므로 그냥 y.backward()만 쓰면 에러뜸!
backward()를 호출할 때 gradient를 특별히 설정하지 않으면 기본값 1()로 설정됨따라서 [1, 1, 1] Tensor를 넘기면
을 구하게 되는거임.
이었으므로...
이 값이 x.grad에 저장!