번역본 - 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은 다음 포스팅에서 다루겠다.
'Deep Learning (Computer Vision) > Model Compression and Optimization' 카테고리의 다른 글
TensorRT설치하기 (2) | 2023.08.31 |
---|---|
Pytorch Tutorial보다 친절한 Pytorch Pruning Tutorial (2) (0) | 2023.06.01 |