ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [PyTorch] requires_grad 알아보기
    AI-ML 2024. 5. 26. 10:22
    728x90
    반응형

     

    - 목차

     

    들어가며.

    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

     

     

    반응형
Designed by Tistory.