본문 바로가기
Deep Learning (Computer Vision)/Model Compression and Optimization

Pytorch Tutorial보다 친절한 Pytorch Pruning Tutorial (1)

by 187cm 2023. 5. 29.
반응형

 

Colab 자료 - https://colab.research.google.com/github/pytorch/tutorials/blob/gh-pages/_downloads/f40ae04715cdb214ecba048c12f8dddf/pruning_tutorial.ipynb#scrollTo=mRMctJEUvqbS

번역본 - https://tutorials.pytorch.kr/intermediate/pruning_tutorial.html

 

- 위에 존재하는 번역본을 조금 더 자세하게 풀어써보며, Pruning을 공부해보자

- 위에 내용이 보고 따라하면 충분할 순 있지만, 왜 이게 이렇게 될까를 생각해보면 약간 불친절한 설명서이다. 이부분을 조금 더 친절하게 설명해보고자 한다.

 

우선 %matplotlib inline을 통해 matplotlib결과를 바로 볼 수 있도록 한다.

%matplotlib inline

Pruning Tutorial

- 최첨단 모델은 많은 수의 파라미터로 인해 쉽게 배포되기가 어렵다. 

- 모델의 정확도를 훼손하지 않으면서 파라미터 수를 줄여 압축하는 최적의 경량화 기법을 파악하는 것은 하드웨어 소비량을 줄일 수 있기에 중요하다. 

- 따라서 이 pruning (가지치기) 기법은 모델 학습 간 역학 차이를 조사하거나, 초기화가 운이 좋게 잘된 케이스의 신경망 구조를 찾는 기술들에 대해 반대 의견을 제시하기도 한다.

 

Requirements

- torch >= 1.4

- 기본적인 torch 함수와 함께, torch.nn.utils.prune 내장함수를 통해 purning을 진행한다.

- We utilize the Pytorch library and perform pruning using torch.nn.utils.prune module. 

import torch
from torch import nn
import torch.nn.utils.prune as prune
import torch.nn.functional as F

Create a model

- 아래의 그림과 같은 모델 구조를 가진 LeNet을 예시로 Pruning을 진행합니다. 단일채널(흑백) 사진 32x32 크기의

MNIST Dataset을 입력으로 넣습니다.

- As shown in the image, We empoly a simple LeNet architecture, using MNIST images the consist of 32x32 and

single-channel image. 

- 여기서 x.nelement()는 데이터의 전체 크기(Batchsize*Width*Height*Channel) 입니다. 

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square conv kernel
        self.conv1 = nn.Conv2d(1, 6, 5) 
        self.conv2 = nn.Conv2d(6, 16, 5) 
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 5x5 image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) # 32x32x1 -> 28x28x6 -> 14x14x6
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)      # 14x14x6 -> 10x10x16 -> 5x5x16
        x = x.view(-1, int(x.nelement() / x.shape[0]))  # 5x5x16 -> -1, 400*Batchsize/Batchsize 
        x = F.relu(self.fc1(x))                         # 1, 400 -> 400, 120 
        x = F.relu(self.fc2(x))                         # 400, 120 -> 120, 84
        x = self.fc3(x)                                 # 120, 84 -> 84, 10
        return x                                        # 10

model = LeNet().to(device=device)

Initiate weight pruning

module = model.conv1
print(list(module.named_parameters()))

- 위의 출력 결과는 1번째 layer인 convolution layer1에 저장된 weight값(빨간색)과 밑에 보이는 Bias값(파란색)이 저장된 것을 볼 수 있다.

- 1번째 Conv layer는 self.conv1 = nn.Conv2d(1,6,3) 이므로 Conv filter의 shape이 [6,1,3,3]이 되는 것을 볼 수 있다. 

- 가장 안쪽의 첫 번째 필터부터, 마지막의 6번째까지 3x3 크기의 필터가 6개가 존재한다고 보면 된다.

- 6개의 output filter가 존재하므로 bias또한 6개

print(list(module.named_buffers()))

- name_buffers()는 학습에 사용되지 않는 정보를 저장한다. 현재는 module에 모든 filter를 사용할 예정이므로, 비어있다.

- 이 named_buffers()는 뒤에가서 Pruning을 진행하며 어떤 용도로 사용되는지 알 수 있다.

- name_buffers() is storing information that is not used  for training time. Currently, as we are using all filters, it remains empty.

- The purpose of this named_buffer() function become evident.

Random Pruning a Module - weight part.

- LeNet의 1번째 conv layer만 pruning을 할 예정이며, torch.nn.utils.prune을 통해 30% 비율의 Random pruning 하는 과정을 보여줄 예정이다.

1. torch.nn.utils.prune 내의 pruning 방식 중 하나 찾아 모듈과 파라미터 지정.

2. 매개변수 지정. 

prune.random_unstructured(module, name="weight", amount=0.3)

- 위의 명령어와 같이 module변수의 LeNet에 대해 0.3 (30%)만큼 "weight" 부분에 대해 Random pruning을 시도한다.

- Therefore, we can perform random weight pruning process on a portion of the LeNet architecture with 30% ratio.

- Random pruning을 하게 되면 우측과 같이 weight라는 이름의 변수가 weight_orig라는 이름으로 바뀐 것을 볼 수 있다.

- bias의 경우 Random pruning을 하지 않았기 때문에 변화가 없다.

- 또한 전체적으로 값의 변화가 없는 것을 확인할 수 있다. (아직 적용이 안된 것을 볼 수 있다)

print(list(module.named_buffers()))

- 그리고 Random Pruning을 진행 후 named_buffers()를 찍었을 때, 어떤 부분에 대해서 Pruning을 진행할 것인지(0) 혹은 그대로 남겨둘 것인지(0)을 통해 기록해두었다. 연산과정에서 0으로 처리되어 사용을 안하면 되므로 0으로 처리해준다.

- 또한 30% 정도를 Random으로 미분하였는데, 이 실제로 Pruning이 약간 더 많이 되긴했지만 30%에 유사하게 Weight의 mask를 0으로 만들어 준 것을 볼 수 있다.

- 우측의 그림은 module.weight 명령어를 통해 weight만 찍어낸 모습이다. named_paramters()는 bias까지 출력하므로 다르다는 것을 알 수 있으며, module.weight에서는 0으로 masking된 부분에 대해 0으로 업데이트 되며, grad_fn = MulBackward로 된 것을 보아 자동미분이 된 것을 알 수 있다. 

- 즉 mask가 끼워진 상태로 학습이 된다면 자동 미분을 통해 weight는 0으로 고정된다는 것을 알 수 있다.

- 따라서 pruning을 수행할 때에는 학습하는 파라미터가 아닌, model의 속성 값(학습 되지 않는 값)으로 정의 된다.

- 이렇게 pruning을 진행한 경우 hook을 얻을 수 있게 되는데, hook이란 순전파, 역전파 과정에서 사용자 정의 동작을 넣을 수 있는 매커니즘이다. 따라서 이 conv1에 대한 pruning을 수행하였으며, 그 후에 conv1에 대한 hook을 얻어 pruning된 weight값에 대한 정보를 얻어 forward pass에 사용할 수 있다.

Pruning a Module - bias part.

prune.l1_unstructured(module, name="bias", amount=3)

- 이번에는 bias를 pruning 해보자. 이번에는 Random하게 하는 것이 아닌, L1 norm을 기준으로 한다.

- 위의 weight와 마찬가지로 bias가 bias_orig로 바뀐 것을 볼 수 있다.

print(list(module.named_parameters()))

그 다음 named_buffers() 를 찍어서 mask를 확인해보면, 아래에 잘 뜨는 것을 볼 수 있고, 실제로도 값이 가장 작은 bias를 제외하고 3개가 선택되어 masking 된 것을 볼 수 있다.

print(list(module.named_buffers()))

- 이제 pruning된 bias를 출력하고, hook이 추가된 것을 볼 수 있다.

- hook에 대해 짧게 설명하면, pruning 혹은 학습과 관련된 작업을 할 때, 추가적인 설정을 통해 기능을 넣어주는 것이다.

 

Iterative pruning은 다음 포스팅에서 다루겠다.

 

Pytorch Tutorial보다 친절한 Pytorch Pruning Tutorial (2)

Pytorch Pruning 1편 보러가기. Pytorch Tutorial보다 친절한 Pytorch Pruning Tutorial (1) Colab 자료 - https://colab.research.google.com/github/pytorch/tutorials/blob/gh-pages/_downloads/f40ae04715cdb214ecba048c12f8dddf/pruning_tutorial.ipynb#scro

187cm.tistory.com

 

반응형