soyeonland

Pruning Tutorial 본문

Study/Code Review

Pruning Tutorial

soyeonland 2020. 3. 22. 13:37

1. model 생성

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

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

class LeNet(nn.Module):
  def __init__(self):
    super(LeNet, self).__init__()
    self.conv1 = nn.Conv2d(1, 6, kernel_size=3)
    self.conv2 = nn.Conv2d(6, 16, kernel_size=3)
    self.fc1 = nn.Linear(16 * 5 *5, 120)
    self.fc2 = nn.Linear(10, 84)
    self.fc3 = nn.Linear(84,10)
  
  def forward(self, x):
    x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))
    x = F.max_pool2d(F.relu(self.conv2(x)), 2)
    x = x.view(-1, int(x.nelement()/x.shape[0]))
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)
    return x

 

2. weight 접근

 

conv1의 weight들 출력

 

model = LeNet().to(device = device)
module = model.conv1
print(list(module.named_parameters()))
print(list(module.named_buffers()))

 

=>

[('weight', Parameter containing:
tensor([[[[-0.2511, -0.2876,  0.2079],
          [ 0.0291, -0.1426,  0.1376],
          [-0.2901, -0.0334,  0.2352]]],


        [[[-0.0660, -0.1610,  0.0728],
          [ 0.0041, -0.3127,  0.1384],
          [ 0.3017,  0.1271,  0.3223]]],


        [[[ 0.2340, -0.1247, -0.3186],
          [ 0.3174, -0.2277, -0.0598],
          [ 0.1046,  0.1306,  0.1649]]],


        [[[ 0.0397,  0.2533,  0.0615],
          [ 0.0200,  0.1114,  0.0139],
          [-0.0923, -0.1025,  0.2250]]],


        [[[-0.3203, -0.1708, -0.0952],
          [-0.0196, -0.3311, -0.1986],
          [ 0.3165, -0.1322,  0.1062]]],


        [[[-0.2047, -0.0777, -0.0955],
          [-0.2469, -0.0815, -0.1437],
          [ 0.1568, -0.1811,  0.2343]]]], device='cuda:0', requires_grad=True)), ('bias', Parameter containing:
tensor([-0.1787, -0.0440,  0.0534, -0.2793,  0.1707, -0.3073], device='cuda:0',
       requires_grad=True))]
[]

 

list 없애보기

print(module.named_parameters())

=> list가 없으면 저장되 있는 위치가 불러와 진다.

<generator object Module.named_parameters at 0x7ff0fcdae780>

 

Weight만 꺼내보기

print(list(module.named_parameters())[0])

=> list에 'weight'와 'bias'가 tuple 형태로 저장되어 있다. 'weight'는 list index 0 에 해당

('weight', Parameter containing:
tensor([[[[-0.2511, -0.2876,  0.2079],
          [ 0.0291, -0.1426,  0.1376],
          [-0.2901, -0.0334,  0.2352]]],


        [[[-0.0660, -0.1610,  0.0728],
          [ 0.0041, -0.3127,  0.1384],
          [ 0.3017,  0.1271,  0.3223]]],


        [[[ 0.2340, -0.1247, -0.3186],
          [ 0.3174, -0.2277, -0.0598],
          [ 0.1046,  0.1306,  0.1649]]],


        [[[ 0.0397,  0.2533,  0.0615],
          [ 0.0200,  0.1114,  0.0139],
          [-0.0923, -0.1025,  0.2250]]],


        [[[-0.3203, -0.1708, -0.0952],
          [-0.0196, -0.3311, -0.1986],
          [ 0.3165, -0.1322,  0.1062]]],


        [[[-0.2047, -0.0777, -0.0955],
          [-0.2469, -0.0815, -0.1437],
          [ 0.1568, -0.1811,  0.2343]]]], device='cuda:0', requires_grad=True))

 

type

print(type(list(module.named_parameters())[0]))

=> 튜플형태로 변수들이 저장되어 있다.

<class 'tuple'>

 

Weight 임의로 수정해보기

 

print(list(module.named_parameters())[0][1])

=> weight에서 value 값만 불러오기 (참고: [0][0]은 'weight' 출력)

Parameter containing:
tensor([[[[-0.2511, -0.2876,  0.2079],
          [ 0.0291, -0.1426,  0.1376],
          [-0.2901, -0.0334,  0.2352]]],


        [[[-0.0660, -0.1610,  0.0728],
          [ 0.0041, -0.3127,  0.1384],
          [ 0.3017,  0.1271,  0.3223]]],


        [[[ 0.2340, -0.1247, -0.3186],
          [ 0.3174, -0.2277, -0.0598],
          [ 0.1046,  0.1306,  0.1649]]],


        [[[ 0.0397,  0.2533,  0.0615],
          [ 0.0200,  0.1114,  0.0139],
          [-0.0923, -0.1025,  0.2250]]],


        [[[-0.3203, -0.1708, -0.0952],
          [-0.0196, -0.3311, -0.1986],
          [ 0.3165, -0.1322,  0.1062]]],


        [[[-0.2047, -0.0777, -0.0955],
          [-0.2469, -0.0815, -0.1437],
          [ 0.1568, -0.1811,  0.2343]]]], device='cuda:0', requires_grad=True)

 

print(type(list(module.named_parameters())[0][1]))

=> Type: parameter

<class 'torch.nn.parameter.Parameter'>

 

☞ 

둘러보기

print((list(module.named_parameters())[0][1].shape))

=> Parameter의 shape (LeNet의 conv1 의 filter value라고 볼 수 있다. 3*3 filter가 6개)

torch.Size([6, 1, 3, 3])

 

print((list(module.named_parameters())[0][1][0]))

=> weight의 첫 번째 channel 의 filter (module.parameters() 쓰면 더 쉽게 접근 가능)

tensor([[[-0.2511, -0.2876,  0.2079],
         [ 0.0291, -0.1426,  0.1376],
         [-0.2901, -0.0334,  0.2352]]], device='cuda:0',
       grad_fn=<SelectBackward>)

 

list(module.named_parameters())[0][1][0][0,1]=1
print((list(module.named_parameters())[0][1][0]))

=> Wegiht 수정하기

tensor([[[-0.2511, -0.2876,  0.2079],
         [ 1.0000,  1.0000,  1.0000],
         [-0.2901, -0.0334,  0.2352]]], device='cuda:0',
       grad_fn=<SelectBackward>)

grad_fn이 True에서 Select BackWard로 수정되었음

 

(힘들어서 함수랑 출력값을 동시에 작성)

print(list(module.named_parameters()))

[('weight', Parameter containing:
tensor([[[[-0.2511, -0.2876,  0.2079],
          [ 1.0000,  1.0000,  1.0000],
          [-0.2901, -0.0334,  0.2352]]],


        [[[-0.0660, -0.1610,  0.0728],
          [ 0.0041, -0.3127,  0.1384],
          [ 0.3017,  0.1271,  0.3223]]],


        [[[ 0.2340, -0.1247, -0.3186],
          [ 0.3174, -0.2277, -0.0598],
          [ 0.1046,  0.1306,  0.1649]]],


        [[[ 0.0397,  0.2533,  0.0615],
          [ 0.0200,  0.1114,  0.0139],
          [-0.0923, -0.1025,  0.2250]]],


        [[[-0.3203, -0.1708, -0.0952],
          [-0.0196, -0.3311, -0.1986],
          [ 0.3165, -0.1322,  0.1062]]],


        [[[-0.2047, -0.0777, -0.0955],
          [-0.2469, -0.0815, -0.1437],
          [ 0.1568, -0.1811,  0.2343]]]], device='cuda:0',
       grad_fn=<CopySlices>)), ('bias', Parameter containing:
tensor([-0.1787, -0.0440,  0.0534, -0.2793,  0.1707, -0.3073], device='cuda:0',
       requires_grad=True))]

전체 결과, grad_fn이 CopySlices로 바꼈음

 

*grad_fn에 대해서 공부할 필요성이 있어보임.

 

3. prune 함수

pruning이 되면

weight_orig에 원본 weigh들을 저장해둔다.

bias는 prune되지 않아서 변화가 없다.

 

amount = 0.3 만큼 weigh들을 없앤다.

 

prune.random_unstructured(module,name='weight', amount=0.3)
print(list(module.named_parameters()))

[('bias', Parameter containing:
tensor([-0.2179,  0.3039,  0.0210,  0.2159, -0.0538,  0.1727], device='cuda:0',
       requires_grad=True)), ('weight_orig', Parameter containing:
tensor([[[[ 0.0553, -0.2966,  0.3259],
          [-0.0673, -0.1419,  0.0502],
          [-0.1520,  0.2030,  0.1565]]],


        [[[ 0.2300,  0.1853,  0.1511],
          [-0.2664,  0.0907,  0.0880],
          [ 0.2731,  0.1323, -0.2508]]],


        [[[ 0.0267,  0.2257, -0.1429],
          [ 0.2152, -0.3155, -0.0194],
          [-0.2857, -0.2567,  0.1992]]],


        [[[-0.1144,  0.2800, -0.0774],
          [-0.0859, -0.1653, -0.3080],
          [ 0.0489,  0.1629,  0.2696]]],


        [[[-0.0107, -0.2813, -0.0735],
          [ 0.2105,  0.1150,  0.0696],
          [-0.1975, -0.0789, -0.0035]]],


        [[[ 0.1801,  0.2752,  0.1583],
          [-0.1039,  0.2094, -0.2401],
          [-0.0591,  0.0956, -0.1902]]]], device='cuda:0', requires_grad=True))]

 

funtion: moudle.named_buffers()

pruning 이 일어나면 'weight_mask' 의 name으로 mask를 저장해둔다.

 

# 전체 파라미터 갯수: 9*6=54

#0 : 13

#0/parametrs : 13/54 = 0.24

print(list(module.named_buffers()))

[('weight_mask', tensor([[[[1., 1., 0.],
          [1., 0., 1.],
          [1., 1., 1.]]],


        [[[0., 1., 1.],
          [1., 0., 1.],
          [1., 1., 1.]]],


        [[[1., 0., 0.],
          [1., 0., 1.],
          [1., 0., 1.]]],


        [[[1., 1., 1.],
          [1., 0., 0.],
          [1., 1., 1.]]],


        [[[1., 1., 1.],
          [0., 0., 1.],
          [1., 0., 0.]]],


        [[[1., 1., 1.],
          [0., 1., 0.],
          [1., 1., 1.]]]], device='cuda:0'))]

forward가 변형없이 지나가기 위해서는 weight atrribute가 존재해야 한다.

torch.nn.utils.prune 이 mask과 original weight들을 합쳐서 pruned 된 weight 들을 생성한다.

그리고 weight 특성에 저장한다. 이때 주의할 점은 더이상 module 의 parameter가 아닌 단순 attribute이다.

 

print(module.weight)

tensor([[[[ 0.0553, -0.2966,  0.0000],
          [-0.0673, -0.0000,  0.0502],
          [-0.1520,  0.2030,  0.1565]]],


        [[[ 0.0000,  0.1853,  0.1511],
          [-0.2664,  0.0000,  0.0880],
          [ 0.2731,  0.1323, -0.2508]]],


        [[[ 0.0267,  0.0000, -0.0000],
          [ 0.2152, -0.0000, -0.0194],
          [-0.2857, -0.0000,  0.1992]]],


        [[[-0.1144,  0.2800, -0.0774],
          [-0.0859, -0.0000, -0.0000],
          [ 0.0489,  0.1629,  0.2696]]],


        [[[-0.0107, -0.2813, -0.0735],
          [ 0.0000,  0.0000,  0.0696],
          [-0.1975, -0.0000, -0.0000]]],


        [[[ 0.1801,  0.2752,  0.1583],
          [-0.0000,  0.2094, -0.0000],
          [-0.0591,  0.0956, -0.1902]]]], device='cuda:0',
       grad_fn=<MulBackward0>)

마지막으로 pruning은 Pytorch의 forward_pre_hooks 라는 함수를 사용해서 pruning이 모든 forward pass에 적용된다.

구체적으로 module이 pruning이 되면, 우리는 pruning과 관련있는 parameter 각각에 대해서 forward_pre_hook 을 얻게된다.

이러한 상황안에서, 우리는 여태까지 weight라고 이름 붙여진 original parameter만 pruning을 해왔기 때문에 하나의 hook만 존재한다.

 

-특징: _forward_pre_hooks는 pruning 된 변수들을 orderdict로 가지고 있음

print(module._forward_pre_hooks)
OrderedDict([(0, <torch.nn.utils.prune.RandomUnstructured object at 0x7fae41905a90>)])

for name, w in model.named_parameters():
  print(name)
  
conv1.bias
conv1.weight_orig
conv2.weight
conv2.bias
fc1.weight
fc1.bias
fc2.weight
fc2.bias
fc3.weight
fc3.bias

<정리>

function

list(feature.named_parameters())

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

 

 

 

[참조]https://pytorch.org/tutorials/intermediate/pruning_tutorial.html