펌 : https://eagle705.github.io/cslog/2018/06/14/PyTorch_0.4/
PyTorch 0.4 version up 정리
14 Jun 2018 in ComputerScience on DeepLearning
본 문서는 PyTorch v0.4에서 변경된 APIs를 기술하기 위한 문서입니다. 공식 Migration guide를 참조했습니다.
A. 소개
- 오픈소스 명: PyTorch
- Github URL: https://github.com/pytorch/pytorch
- 0.4 Migration guide: https://pytorch.org/2018/04/22/0_4_0-migration-guide.html
- 총평: Tensor정책은 TF를 좀 따라간 것 같고, dtype 부분은 Numpy-style을 따라 간것 같습니다. gpu 할당 방식은 multi-gpu를 좀 더 고려한 api느낌이네요
B. Migration Guide
- Tensors와 Variables가 합쳐짐
- 0-dimensional (scalar) Tensors를 지원함
- Backprop을 위한 값들을 저장하지 않게 했던 volatileflag가 Depreciation됨
- dtypes, devices그리고 Numpy-style Tensor 생성 함수가 추가됨 (테스트 필요)
- Writing device-agnostic code (
무슨뜻이지) - 새로운 edge-case constraints가 nn.Module안에서 submodules, parameters, buffers 이름등으로 생김
Tensors와 Variables가 합쳐짐
torch.Tensor와 torch.autograd.Variable이 이젠 같은 클래스가 되었습니다.
torch.Tensor 클래스는 old Variable 처럼 히스토리 추적이 가능하게 되었습니다.
예전엔 딱 자료구조용인 Tensor를 선언하고 그걸 Variable로 랩핑해줬다면, 지금은 마치 TensorFlow의 Tensor처럼 안에 기능이 다 통합 된 것 같습니다.
지금도 Variable랩핑이 전 처럼 가능하지만, 리턴값은 전과 다르게 torch.Tensor로 나올 것 입니다.
즉, 예전코드와 호환은 잘 된다는 것이겠지요. 다만 예전 코드에서의 Variable은 의미상으론 읽기 편해지나, 기능상으론 redundant해졌다고도 볼 수 있을 것 같습니다.
Tensor의 type() 함수가 변경됨
예전엔 type()함수가 데이터의 타입을 리턴했습니다만,
지금은 torch.Tensor를 리턴합니다.(Variable도 그렇겠죠??) 에전처럼 데이터타입을 보고싶으면,
type() 대신 x.type() 또는 isinstance() 를 사용해야합니다.
일반적인 Python의 쓰임과 달라서 저도 많이 당황했습니다.
>>> x = torch.DoubleTensor([1, 1, 1]) >>> print(type(x)) # was torch.DoubleTensor "<class 'torch.Tensor'>" >>>
print(x.type()) # OK: 'torch.DoubleTensor' 'torch.DoubleTensor' >>> print(isinstance(x, torch.DoubleTensor)) # OK: True True
autograd가 tracking history를 하는 시점?
autograd는 Tensor의 gradient를 계산하기 위해 computational graph를 고려하기 위해 고안되었었습니다. autograd의 핵심 flag인 requires_grad는 이제 Tensors의 attribute가 되었습니다. 그렇다면 이제 Tensors는 과연 언제부터 computational graph를 고려하게 될까요? 아래 예제를 통해 보시면 직접 requires_grad flag를 True로 지정해줘야함을 알 수 있습니다.
>>> x = torch.ones(1) # create a tensor with requires_grad=False (default) >>> x.requires_grad False >>> y = torch.ones(1) # another tensor with requires_grad=False >>> z = x + y >>> # both inputs have requires_grad=False. so does the output >>> z.requires_grad False >>> # then autograd won't track this computation. let's verify! >>> z.backward() RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn >>> >>> # now create a tensor with requires_grad=True >>> w = torch.ones(1, requires_grad=True) >>> w.requires_grad True >>> # add to the previous result that has require_grad=False >>> total = w + z >>> # the total sum now requires grad! >>> total.requires_grad True >>> # autograd can compute the gradients as well >>> total.backward() >>> w.grad tensor([ 1.]) >>> # and no computation is wasted to compute gradients for x, y and z, which don't require grad >>> z.grad == x.grad == y.grad == None True
requires_grad flag 조작하기
default 값은 False로 되어있습니다. 아래와 같이 변경할 수 있습니다. 함수 뒤에가 _ 이면 in-place(새로 대입할 필요가 없는, 그 자리에서 교체되는)로 보시면 됩니다
>>> existing_tensor.requires_grad_() >>> existing_tensor.requires_grad True >>> my_tensor = torch.zeros(3, 4, requires_grad=True) >>> my_tensor.requires_grad True
Tensor와 Variables가 합쳐졌다면, 그럼 .data은 어떻게 된거죠?
.data는 원래 Variables로 부터 Tensor를 추출해내는데 사용되었었습니다. y = x.data는 이제 다음과 같은 의미를 갖는데, y는 Tensor가 되고 x와 같은 데이터를 share(복사가 아니라 share라는게 매우 중요)합니다. 하지만 x의 computational history와 분리되고 requires_grad=False 처리가 됩니다.
하지만 .data는 다소 unsafe할때가 있습니다. x.data의 변화가 autograd에 의해서 추적이 안되기 때문에, gradient를 계산할 때 값이 잘못될 수 있습니다. (.data는 값을 복사하는게 아니라 share하기 때문에 값이 바뀌면 당연히 gradient에 영향을 줘야하는데 require_grad=False니 못주는 상황입니다.)
그러므로 추천하기로는 x.detach()사용을 권합니다. 얘는 .data와 비슷한 역할(share data, require_grad=False)을 하지만 값이 바뀌면 autograd가 바뀐걸 알 수 있습니다. 기억할건, 왠만하면 .detach()를 사용하면 된다는 것입니다. 아래 예제를 보시죠.
Tensor.detach() 사용할 때 (권장),
>>> a = torch.tensor([1,2,3.], requires_grad = True) >>> out = a.sigmoid() >>> c = out.detach() >>> c.zero_() tensor([ 0., 0., 0.]) >>> out # modified by c.zero_() !! tensor([ 0., 0., 0.]) >>> out.sum().backward() # Requires the original value of out, but that was overwritten by c.zero_() RuntimeError: one of the variables needed for gradient computation has been modified by an
Tensor.data 사용할 때 (비추),
>>> a = torch.tensor([1,2,3.], requires_grad = True) >>> out = a.sigmoid() >>> c = out.data >>> c.zero_() tensor([ 0., 0., 0.]) >>> out # out was modified by c.zero_() tensor([ 0., 0., 0.]) >>> out.sum().backward() >>> a.grad # The result is very, very wrong because `out` changed! tensor([ 0., 0., 0.])
Scalar Tensors지원 (0-dimensional Tensors)
이전 버전에서는 Tensor vector에서 인덱싱하면 Python number를 줬지만 Variablevector에서는 Tensor vector와는 다르게(inconsistently!) vector of size (1,)을 리턴했습니다. sum함수도 마찬가지였습니다. 이제는 numpy.array 스타일처럼 Scalar Tensor를 지원합니다. (.item() 을 주목해서보자. 나중에 사용할 수도)
>>> torch.tensor(3.1416) # create a scalar directly tensor(3.1416) >>> torch.tensor(3.1416).size() # scalar is 0-dimensional torch.Size([]) >>> torch.tensor([3]).size() # compare to a vector of size 1 torch.Size([1]) >>> >>> vector = torch.arange(2, 6) # this is a vector >>> vector tensor([ 2., 3., 4., 5.]) >>> vector.size() torch.Size([4]) >>> vector[3] # indexing into a vector gives a scalar tensor(5.) >>> vector[3].item() # .item() gives the value as a Python number 5.0 >>> mysum = torch.tensor([2, 3]).sum() >>> mysum tensor(5) >>> mysum.size() torch.Size([])
losses 계산
기존 패턴은 total_loss += loss.data[0] 방식이었습니다. 0.4.0 전에는 loss도 Variable에 랩핑된 텐서로써 (1,) size를 가졌었습니다. 하지만 0.4.0에서는 loss는 이제 0 dimension을 갖는 scalar입니다. loss.item() 을 사용하세요. scalar에서부터 Python number를 얻을 땐 앞으로 .item()을 사용해야합니다.
Note that if you don’t convert to a Python number when accumulating losses, you may find increased memory usage in your program. This is because the right-hand-side of the above expression used to be a Python float, while it is now a zero-dim Tensor. The total loss is thus accumulating Tensors and their gradient history, which may keep around large autograd graphs for much longer than necessary.
Deprecation of volatile flag
volatile는 이전 버전에서는 주로 inference할때 많이 사용되었었습니다. volatile flag는 이제 deprecated 되었고, 효과가 없습니다. 전에는 Variable에서 volatile=True 조건이면 autograd가 추적하지 않았지만 이젠 torch.no_grad(), torch.set_grad_enabled(grad_mode) 외에 다른 것들로 대체 되었습니다.
>>> x = torch.zeros(1, requires_grad=True) >>> with torch.no_grad(): ... y = x * 2 >>> y.requires_grad False >>> >>> is_train = False >>> with torch.set_grad_enabled(is_train): ... y = x * 2 >>> y.requires_grad False >>> torch.set_grad_enabled(True) # this can also be used as a function >>> y = x * 2 >>> y.requires_grad True >>> torch.set_grad_enabled(False) >>> y = x * 2 >>> y.requires_grad False
조금 더 이해하고 포스팅을 수정해야겠습니다.
ToDo
- new data types
- gpu device allocation
(문법이 조금 바뀜, .cuda() -> .to(torch.device("cuda:0" if torch.cuda.is_available() else "cpu")) )