Naver Ai Boostcamp

[Day 38] 빠르게 & 가지치기

잡담연구소 2021. 3. 28. 03:54

📌 빠르게

Further Quetion

1) 이번 강의는 lightweight modeling과 어떤 관계가 있을까? (이 강의를 만든 목적은 무엇일까?)

 

저번 시간에 압축을 통해 공간(메모리)를 줄였다면 , 'acceleration'을 통해 시간을 줄일 수 있다. 

 

- bandwidth는 기간 내 네트워크를 통해 전송되는 데이터의 양

- latency는 어떤 일을 처리(source ->target)할 때 걸리는 시간

- throughput는 일종의 처리율로 기간 내 "성공적으로" 네트워크 전송을 통해 처리된 데이터의 양

 

cpu에서는 병목현상이 일어나고, gpu는 '병렬'로 처리해 throughput을 증가시켜준다. 

 

 

 

2) 두 계산에 차이가 발생하는 이유는 뭘까?(p 4)

 

numpy는 c언어로 구현돼 파이썬보다 더 빠르게 작동한다.

그렇기 때문에 ndarray는 list와 다르게 무조건 같은 타입의 elem들로만 구성된다. 

import numpy as np
import time 
size =1000000

# list excution time 
lst = range(size)
initial_time = time.time()
result_list = [(a*b) for a,b in zip(lst,lst)]
print(round(time.time() - initial_time , 4) ,'seconds')

#numpy 
array = np.arange(size)
initial_time = time.time()
result_array = array*array
print(round(time.time() - initial_time , 4) ,'seconds')

 

 

 

3) Compression과 acceleration의 관계는? (p 22)

Hardware의 성능은 주로 Acceleration(속도)에 초점을 둔다.

software의 highlevel alnguage를 받아 중간 complier 과정을 통해 중간단계의 assembly language로 바꿔준다.

assembler를 기계가 이해할 수 있는 machine language로 받아 hardware에서 연산을 수행한다.

 

 

 

Toy Code

remote fuction은 ray의 프로세스에 의해 비동기적으로 실행된다.

직렬보다는 병렬로 계산할 때 속도가 훨씬 빨리진다.

아래 식의 시간 복잡도는 왼쪽은 N , 오른쪽은 $logN$이 된다.

import time

@ray.remote
def add(x, y):
    time.sleep(1)
    return x + y
start = time.time()
# Aggregate the values slowly. This approach takes O(n) where n is the
# number of values being aggregated. In this case, 7 seconds.
id1 = add.remote(1, 2)
id2 = add.remote(id1, 3)
id3 = add.remote(id2, 4)
id4 = add.remote(id3, 5)
id5 = add.remote(id4, 6)
id6 = add.remote(id5, 7)
id7 = add.remote(id6, 8)
result = ray.get(id7)
print("Vanilla version : {}".format(time.time() - start))

# Vanilla version : 7.029922008514404

start = time.time()
# Aggregate the values in a tree-structured pattern. This approach
# takes O(log(n)). In this case, 3 seconds.
id1 = add.remote(1, 2)
id2 = add.remote(3, 4)
id3 = add.remote(5, 6)
id4 = add.remote(7, 8)
id5 = add.remote(id1, id2)
id6 = add.remote(id3, id4)
id7 = add.remote(id5, id6)
result = ray.get(id7)
print("Advanced version : {}".format(time.time() - start))

#Advanced version : 4.019379138946533

 

 

 

 

📌 가지치기 : pruning

미니코드 : Masking 

import numpy as np
from numpy.ma import array, masked_array,masked_values , masked_outside

vec1 = np.round(np.linspace(0.1,1,5) ,3)
print('vec1: ' ,vec1)
print('array : ' ,array(vec1 , mask = [0,0,0,1,0]))
print('masked array : ' , masked_array(np.array(vec1) , mask = [0,0,0,1,0]))
print('masked_outside : ' , masked_outside(vec1 , 0.2,0.9) , '\n')


# vec1:  [0.1   0.325 0.55  0.775 1.   ]
# array :  [0.1 0.325 0.55 -- 1.0]
# masked array :  [0.1 0.325 0.55 -- 1.0]
# masked_outside :  [-- 0.325 0.55 0.775 --]


vec2 = [0,1,2,3,4,-9999]
print('vec2 : ' , vec2)
print('masked_values : ' , masked_values(vec2 , -9999))
print('masked_values & filled : ', masked_values(vec2, -9999).filled(5555))

# vec2 :  [0, 1, 2, 3, 4, -9999]
# masked_values :  [0 1 2 3 4 --]
# masked_values & filled :  [   0    1    2    3    4 5555]

 

 

 

Further Questoin

1) 이번 강의는 lightweight modeling과 어떤 관계가 있을까? (이 강의를 만든 목적은 무엇일까?)

 

pruning은 딥러닝 경량화 대표 기술 중 하나이다. 

일정 비율만큼 중요하다고 생각 되는 것은 살리고, 중요하지 않다고 생각 되는 것은 죽여버린다.

장점 :  inference 속도도 빨라지고, generalization 효과도 얻을 수 있다. 

단점 :  information loss가 증가하고, 너무 sparse해지면 hardware에서의 최적화가 힘들어진다. 

그림을 보면 dropout과 비슷하게생겼는데 이 둘의 차이점은 

pruning의 경우 한 번 잘라내면 고정되어 inference 시 까지도 계속 없다.

하지만 dropout같은 경우 한 타임동안만 일시적으로 잘라내는 것이고 inference 시에는 잘라내는 것 없이 모두 사용한다.

 

알고리즘은 아래와 같다. 

1. 초기화 

2. 수렴할 때 까지 훈련 

3. 1로 fill된 마스크 준비 

4. W의 score에 mask를 씌어줘 pruning 해준 후 finetunning 을 n번 반복 

pruning을 하면 size와 speed 사이의 tradeoff 이 존재한다. 

size가 작아지면 (number of parameter) 속도는 빨라진다. (number of FLOPs)

그렇다고 성능까지 무조건 떨어지지는 않는다. VGG의 경우 pruned model이 acc도 높은 걸 확인할 수 있다.

아래 논문에서 EfficientNet에 대해서 Pruned model이 표시되어있지 않은데 그 이유는,,,, 그냥 최근에 나와서 pruned model이 없어서라고 한다;

 

  • pruning의 종류 

1) 무엇을 prune 할지 2) 어떻게 prune 할 지 3) 언제 prune할 지 4) 얼마나 자주 prune 할 지 4가지의 척도로 나눠서 생각해볼 수 있다. 

 

  • unstructured pruning과 structured pruning

unstructured pruning은 특정 가중치에 대해 하는 게 아니라 그냥 neuron이나 layer 등 아무거나 연결성이 낮아보이면 삭제하는 것이다. 이러면 sparse해지기 쉽고 hardware에서의 optimize가 어렵다.

반대로 structured pruning은 특정 filter를 삭제한다. filter전체, 혹은 채널 , shape의 일부를 삭제할 수 있다. 

 

  • Iterative pruning

너무 많은 파라미터가 한번에 삭제되면 모델의 성능도 급감하기 때문에, one-shot이 아닌 Iterative pruning을 주로 사용한다고 한다.  pruning과 retrain을 반복해서 loss가 줄어드는 것을 방지할 수 있다. 반복을 통해 조금조금씩 파라미터를 삭제해간다. 

training iteration j, pruning할 비율 p , 최대 loss 손실 비율 k 를 하이퍼파라미터로 설정한다 

1. initial state 로 네트워크를 초기화한다.

2. mask를 node/filter에 적용해준다. 

3. j번만큼 네트워크를 train시킨다.

4. p만큼 node/filter를 pruning해준 후, 마스크를 update해준다. 

위 과정을 val acc가 원레 acc의 k배가 될 때까지 반복한다. 

 

 

 

2) Mask는 언제 사용하는게 유용할까? (p 4)

 

pruning시, node/ filter를 제거할 때 mask를 씌어준다. 

 

 

 

3) Lottery Ticket Hypothesis의 key idea는 무엇인가? (p 28)

 

network가 주어졌을 때, 더 적은 training 횟수 , 더 높은 accuracy, 더 적은 parameters를 가지는 subsnetwork가 존재한다는 가정이다. 

그럴 듯하지만 증명이 안 된 가정에다가, 저런 subnetwork를 찾기위해서는 결국 network부터 시작을 해야한다. 

그래서 그 이후 나온 논문들은 이를 보완하는 형식인데 "Weight rewinding"을 제안한다.

이전 iterative magnitude pruning 방식은 retrain 시 node/filter들을 inital state로 초기화해줬다.

Rewinding은 좀 더 학습된 네트워크로 초기화해주자는 아이디어이다. 

하이퍼파라미터로 정한 k번까지만 train 시킨 후, 이때의 네트워크를 저장해준다. 

이후 수렴할 때 까지 훈련하고 pruning한 후 마스크를 적용해준다.

Retrain 시, 마스크가 적용된 네트워크를 k번 학습하고 저장해놓은 네트워크를 이용하여 초기화해준다. 

 

 

 

Toy Code

pruning을 하면 model의 사이즈가 더 커진다! 왜일까? 

pytorch에서는 직접 0으로 바꾸는 대신 mask 텐서를 추가로 생성해 곱해줌으로써 pruning이 진행된다. 

import torch.nn.utils.prune as prune
prune.random_unstructured(module, name="weight", amount=0.3)

그렇기때문에 원래 모델에 마스크 텐서가 추가 되기 때문에 pruning을 할 수록 모델의 사이즈가 커지게 된다.

마스크는 named_buffers()를 이용하여 확인할 수 있다. 

print(list(module.named_buffers()))

원본 모델의 parameter는 "weight_orig", "bias_orig"과 같은 형태로 보존해준다.

print(list(module.named_parameters()))

# [('bias', Parameter containing:
# tensor([ 0.1874,  0.0468,  0.0102, -0.1306,  0.2191,  0.4025],
#        requires_grad=True)), ('weight_orig', Parameter containing:
# tensor([[[[ 0.1672, -0.1517, -0.1711],
#           [ 0.1280, -0.1407,  0.3215],
#           [-0.1074,  0.2512,  0.2103]]],


#         [[[ 0.0602,  0.2026,  0.0181],
#           [ 0.1411,  0.0426,  0.1177],
#           [-0.0267,  0.1855,  0.0767]]],


#         [[[ 0.3355,  0.0562, -0.5521],
#           [-0.1873,  0.0618, -0.2184],
#           [-0.0807,  0.2019,  0.4228]]],


#         [[[ 0.3663, -0.0216,  0.2986],
#           [ 0.1475,  0.4550,  0.3350],
#           [-0.6378, -0.6662, -0.2658]]],


#         [[[ 0.2871,  0.1041,  0.2666],
#           [ 0.0158,  0.4709, -0.2112],
#           [ 0.4000,  0.0856, -0.3989]]],


#         [[[ 0.4908,  0.0509,  0.2348],
#           [-0.3349, -0.2446, -0.7014],
#           [-0.6735, -0.3921, -0.2518]]]], requires_grad=True))]

마지막에 mask를 제거하고 pruned weight 를 영구히 적용시켜주면 모델의 사이즈와 소요시간 또한 원래 사이즈대로 돌아오는 것을 확인할 수 있다. 하지만 acc는 낮아진 상태를 유지한다. 

'Naver Ai Boostcamp' 카테고리의 다른 글

[03/30] Data Feeding  (0) 2021.03.31
[03/29] P Stage Start !  (0) 2021.03.30
[Day 37] 모델의 시공간 & 알뜰히  (0) 2021.03.27
[DAY 36] OMG 왜 안했누  (0) 2021.03.26
[DAY 35] Multi-modal & 3D Understanding  (0) 2021.03.25