-
[PyTorch] requires_grad 알아보기AI-ML 2024. 5. 26. 10:22728x90반응형
- 목차
들어가며.
PyTorch 의 Tensor 는 grad 라는 속성을 가집니다.
생성되는 모든 Tensor 는 None 상태의 grad 값을 가지고 있는데요.
이는 이름의 의미하는바와 같이 Gradient 인 미분 기울기 값을 의미합니다.
아래와 같이 임의의 Tensor 를 생성한 이후에 grad 속성을 조회하게 되면 None 이 저장되어 있음을 알 수 있습니다.
import torch torch.ones(1).grad # --> None
그럼 grad 에 해당하는 미분 기울기 값은 언제 생성될까요 ?
이는 Backpropagation 과정에서 생성됩니다.
아래와 같이 Tensor 를 생성하고 backward Function를 통해서 역전파를 시켜주게 되면 Tensor 의 grad 에 값이 할당됩니다.
( 아래에서 grad 값이 1 인 이유는 값이 1 인 Input Tensor 와 값이 1 인 Output Tensor 사이의 관계를 1의 곱과 같기 때문입니다.)
import torch tensor = torch.ones(1, requires_grad=True) tensor.backward() tensor.grad # tensor([1.])
이렇게 Deep Learning 에서 여러 Layer 들은 역전파 과정에서 Gradient 가 계산됩니다.
여기에서 requires_grad 를 True 로 설정을 해야 자동미분이 활성화되어서 Tensor 의 grad 속성에 값이 할당됩니다.
만약 requires_grad 가 True 상태가 아니라면 역전파 과정에서 grad 속성에 값이 할당되지 않습니다.
이어지는 글에서 requires_grad 의 쓰임새에 대해서 자세히 알아보는 시간을 가져보겠습니다.
requires_grad.
requires_grad 가 활성화되어야 자동 미분이 가능.
Tensor 는 기본적으로 requires_grad 옵션이 True 로 설정되어야 자동미분이 가능합니다.
requires_grad 값이 False 로 설정되어 있다면 grad 값이 None 으로 고정되어 버립니다.
아래의 예시들은 requires_grad 의 활성/비활성화 여부에 따라서 Tensor 의 grad 값의 차이를 보여줍니다.
import torch tensor1 = torch.tensor(1., requires_grad=True) tensor2 = torch.tensor(2., requires_grad=True) tensor3 = torch.tensor(3., requires_grad=True) output_tensor = torch.mul(tensor1, tensor2) output_tensor = torch.mul(output_tensor, tensor3) output_tensor.backward() print(f'tensor1.grad: {tensor1.grad}') print(f'tensor2.grad: {tensor2.grad}') print(f'tensor3.grad: {tensor3.grad}') print(f'output_tensor.grad: {output_tensor.grad}') # tensor1.grad: 6.0 # tensor2.grad: 3.0 # tensor3.grad: 2.0 # output_tensor.grad: None
아래와 같이 requires_grad 옵션이 비활성화된다면 Tensor 의 grad 값은 None 으로 고정됩니다.
import torch tensor1 = torch.tensor(1., requires_grad=False) tensor2 = torch.tensor(2., requires_grad=True) tensor3 = torch.tensor(3., requires_grad=False) output_tensor = torch.mul(tensor1, tensor2) output_tensor = torch.mul(output_tensor, tensor3) output_tensor.backward() print(f'tensor1.grad: {tensor1.grad}') print(f'tensor2.grad: {tensor2.grad}') print(f'tensor3.grad: {tensor3.grad}') print(f'output_tensor.grad: {output_tensor.grad}') # tensor1.grad: None # tensor2.grad: 3.0 # tensor3.grad: None # output_tensor.grad: None
requires_grad 를 Model 에 적용하기.
이제 PyTorch Model 에 비활성화된 requires_grad 옵션을 적용해보도록 하겠습니다.
먼저 2개의 Dense Layer 로 구성된 간단한 모델을 구축해보도록 하겠습니다.
아래의 이미지는 Bias 없이 1개의 Weight 를 가지는 2개의 Layer 로 구성된 Model 입니다.
첫번째 Layer 는 Weight 가 1 인 Layer, 두번째 Layer 는 Weight 가 2 인 상태입니다.
그래서 Input Tensor 를 아래 모델을 거치게 되면 값이 2배로 곱셈이 된 Output Tensor 로 출력됩니다.
위의 설명에 해당하는 모델의 구조를 코드로 구현한 결과는 아래와 같습니다.
class GradModel(nn.Module): def __init__(self, first_layer_need_grad=True, last_layer_need_grad=True): super(GradModel, self).__init__() self.layer1 = nn.Linear(1, 1, bias=False) self.layer1.weight = nn.Parameter(torch.ones(1), requires_grad=first_layer_need_grad) self.layer2 = nn.Linear(1, 1, bias=False) self.layer2.weight = nn.Parameter(torch.ones(1) * 2, requires_grad=last_layer_need_grad) def forward(self, x): output = self.layer2(self.layer1(x)) return output
먼저 역전파 이후에 Layer 의 Weight 와 Grad 가 어떻게 업데이트되는지 확인해보도록 하겠습니다.
import torch import torch.nn as nn class GradModel(nn.Module): def __init__(self, first_layer_need_grad=True, last_layer_need_grad=True): super(GradModel, self).__init__() self.layer1 = nn.Linear(1, 1, bias=False) self.layer1.weight = nn.Parameter(torch.ones(1), requires_grad=first_layer_need_grad) self.layer2 = nn.Linear(1, 1, bias=False) self.layer2.weight = nn.Parameter(torch.ones(1) * 2, requires_grad=last_layer_need_grad) def forward(self, x): output = self.layer2(self.layer1(x)) return output model = GradModel(first_layer_need_grad=True, last_layer_need_grad=True) optimizer = torch.optim.SGD(model.parameters(), lr=0.01) input_tensor = torch.ones(1) batch_input_tensor = input_tensor.view(1, 1) for _ in range(3): output = model(batch_input_tensor) print('\n output \n ', output) optimizer.zero_grad() output.backward() print('\n before optimizer.step weight \n ') for param in model.named_parameters(): print(param) print("grad: ", param[1].grad) optimizer.step() print('\n after optimizer.step weight \n ') for param in model.named_parameters(): print(param) print('\n after optimizer.step grad\n ') for param in model.named_parameters(): print(param[0], param[1].grad)
먼저 Backpropagation 이후에 Layer 의 grad 에 미분 기울기 값이 할당됩니다.
layer1.weight 는 기존과 동일한 1, layer2.weight 또한 기존과 동일하게 2 의 값을 가집니다.
그리고 layer1 의 grad 는 Input Tensor 의 크기인 1 과 layer2 의 Weight 인 2 에 영향을 받기 때문에
layer1.grad = 1 x 2 로써 2 의 값이 할당됩니다.
이와 같이 layer2 의 Grad 또한 Input Tensor 와 layer 1 의 Weight 의 크기에 영향을 받으므로
layer2.grad = 1 x 1 로써 1 의 값이 세팅됩니다.
before optimizer.step weight ('layer1.weight', Parameter containing: tensor([1.], requires_grad=True)) grad: tensor([2.]) ('layer2.weight', Parameter containing: tensor([2.], requires_grad=True)) grad: tensor([1.])
optimizer.step 에 의한 최적화 이후의 과정.
optimizer.step 은 Model 의 Parameter 들을 업데이트하게 됩니다.
즉, optimizer.step 이후에 Model 의 각 Layer 의 Weight 는 변경되죠.
layer1 의 Weight 는 1 -> 0.98 로 변경됩니다.
그 이유는 Learing Rate 가 0.01 인 상태에서 layer1.grad 가 2 이기 때문에
2 * 0.01 만큼 layer1 의 Weight 가 감소합니다.
after optimizer.step weight ('layer1.weight', Parameter containing: tensor([0.9800], requires_grad=True)) ('layer2.weight', Parameter containing: tensor([1.9900], requires_grad=True)) after optimizer.step grad layer1.weight tensor([2.]) layer2.weight tensor([1.])
만약에 아래의 예시처럼 layer2 의 requires_grad 가 False 라면, layer2 의 Weight 는 업데이트되지 않습니다.
layer2 의 Weight 가 변경되지 않은채 고정적이므로 layer1 은 Grad 또한 고정적이게 됩니다.
왜냐하면 layer1 의 grad 는 layer2 의 Weight 에 영향을 받기 때문이죠.
model = GradModel(first_layer_need_grad=True, last_layer_need_grad=False) optimizer = torch.optim.SGD(model.parameters(), lr=0.01) input_tensor = torch.ones(1) batch_input_tensor = input_tensor.view(1, 1) for _ in range(3): output = model(batch_input_tensor) print('\n output \n ', output) optimizer.zero_grad() output.backward() print('\n before optimizer.step weight \n ') for param in model.named_parameters(): print(param) print("grad: ", param[1].grad) optimizer.step() print('\n after optimizer.step weight \n ') for param in model.named_parameters(): print(param) print('\n after optimizer.step grad\n ') for param in model.named_parameters(): print(param[0], param[1].grad)
after optimizer.step weight ('layer1.weight', Parameter containing: tensor([0.9800], requires_grad=True)) ('layer2.weight', Parameter containing: tensor([2.])) after optimizer.step grad layer1.weight tensor([2.]) layer2.weight None ------------------------------------------- after optimizer.step weight ('layer1.weight', Parameter containing: tensor([0.9600], requires_grad=True)) ('layer2.weight', Parameter containing: tensor([2.])) after optimizer.step grad layer1.weight tensor([2.]) layer2.weight None ------------------------------------------- after optimizer.step weight ('layer1.weight', Parameter containing: tensor([0.9400], requires_grad=True)) ('layer2.weight', Parameter containing: tensor([2.])) after optimizer.step grad layer1.weight tensor([2.]) layer2.weight None
반응형'AI-ML' 카테고리의 다른 글
[scikit-surprise] SVD 모델 추론하기 (0) 2024.05.26 [PyTorch] squeeze, unsqueeze 알아보기 (0) 2024.05.21 [ scikit-surprise ] SVD Regularization Terms 알아보기 (0) 2024.05.18 [scikit-surprise] SVD 모델 생성하기 (0) 2024.05.18 [scikit-surprise] Dataset 이해하기 (0) 2024.05.14