pytorch를 이용한 training process 기본기 완벽하게 이해하기

1. training을 위해 필요한 것?

 

training을 위해 dataset, dataloader를 준비해서 data를 효율적으로 뽑아낼 준비를 했고

 

설계한 model과 loss, optimizer, learning rate, scheduler, metric 등을 준비했다면 학습 준비 완료!

 

model을 1번 train하기 위해 필요한 준비물

 

2. model.train()

 

train하기 전에 model을 trainable하게 변경시키는 함수?

 

그런데 분명 model 설계할 때 나는 각종 parameter의 requires_grad=True로 한것같은데 굳이 해야하나???

 

batchnorm이나 dropout같은 것이 training 과정이랑 evaluation 과정에서 다르게 작동해야한다고 알려져있어서

 

train mode와 eval mode를 구분하기 위해 사용하는 것에 의미 있다

 

 

실제로 test과정에서 model.eval()상태와 model.train()상태의 결과가 매우 다름

 

그래서 잊지말고 반드시 구분해서 사용해야함

 

 

3. optimizer.zero_grad()

 

epoch이라는 것은 가지고 있는 전체 데이터를 1회 전부 도는 것

 

for epoch in range(n):으로 training 과정을 전체 감싸는 것이 기본인데

 

중간에 running_loss =0.0으로 loss가 얼마인지 알기 위해 초기화시키고

 

다음 for i,data in enumerate(trainloader,0):으로 trainloader에서 데이터를 뽑아옴

 

data를 input과 label로 나누기 위해 input,label = data

 

그런데 갑자기 중간에 optimizer.zero_grad()??

 

중간에 갑자기 등장한 optimizer.zero_grad()란?

 

 

for epoch in range(2): #loop over the dataset multiple times
    
    running_loss = 0.0
    
    for i,data in enumerate(trainloader,0):
        
        #get the inputs; data is a list of [inputs, labels]
        inputs, labels = data
        
        #zero the parameter gradients
        optimizer.zero_grad()
        
        #forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        #print statistics
        running_loss += loss.item()
        if i % 2000 == 1999: #print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss/2000))
            running_loss = 0.0

print('Finished Training')

 

 

optimizer는 loss가 backward되면서 모델 parameter 내부의 grad가 업데이트 되면서 학습을 진행함

 

optimzier.zero_grad()는 왜 하느냐…?

 

(처음 학습을 시작할 때는 괜찮을 지라도) 2번째 batch부터는 zero_grad가 없으면 이전 단계에서 계산한 grad가 optimizer에 남아있어

 

자세히 설명하면 첫 batch에서 loss로부터 grad를 발생시켜 parameter가 grad를 각각 가짐

 

이제부터 선택의 문제인데

 

1) 두번째 batch부터 이 grad를 초기화하고 두번째 batch에서 나온 grad만 가져갈 것인가?

 

2) 아니면 grad를 초기화하지 않고 그대로 가지고 가서 batch를 계속 돌아가 누적해서 grad를 update할 것인가?

 

일반적으로는 첫번째 경우를 선택함.

 

각각의 minibatch의 loss에서 발생한 grad만 사용하지 일반적으로 이전 단계에서 발생한 loss grad까지 같이 쓰지는 않는다

 

optimizer.zero_grad()는 이렇게 이전 단계에서 발생한 loss grad를 parameter가 가지는데 이것을 각각 초기화시켜주는 역할을 한다.

 

---------------------------------------------------------------------------------------------------------------------

 

optimizer가 어떻게 model의 parameter의 grad를 초기화함??

 

optimizer가 model의 parameter를 input으로 받아서 가능하다

 

다음은 일반적으로 구현된 optimizer의 zero_grad()함수이다.

 

optimizer가 model.parameters()라고 model의 parameter를 input으로 받음

 

parameter group을 iteration해서 마지막에 p.grad.zero_()로 초기화 시키는거 보임?

 

 

4. loss=criterion(outputs,labels)

 

criterion = nn.CrossEntropyLoss()같이 사전에 정의한 loss함수

 

input을 model에 넣어 outputs를 얻고

 

outputs와 input의 정답 labels를 함께 criterion의 input으로 넣으면

 

loss값인 tensor를 계산해준다.

 

-------------------------------------------------------------------------------------------

 

loss인 criterion은 nn.Module을 상속받으니 forward를 정의해야함

 

input부터 시작한 model의 forward함수에 의해 나온 output이 criterion에 들어가면서

 

criterion의 forward로부터 loss가 계산이 되면서 input부터 시작하여 loss까지 하나의 forward chain이 완성

 

input > (model_forward) > output > (criterion_forward) > loss

 

5. loss.backward()

 

loss를 계산한 뒤 loss.backward()를 하면 loss가 grad_fn을 찾아가

 

그 이후 next_function을 계속해서 찾아가면서 여러가지 backward함수를 실행함

 

그러면서 연결된 parameter들에 grad를 전달함

 

loss가 backward를 받으면 grad를 어떻게 전달하고 있을지 디버깅한것

 

loss가 chain rule을 적용하면서 grad가 update되는 parameter를 확인하여 grad를 전달해줬을 것임

 

그러나 loss가 하는 역할은 딱 여기까지임.... grad를 parameter에 전달해주는 역할

 

parameter가 grad 변수에 grad를 전달받았지만

 

우리가 진짜로 원하는 점은 grad를 바탕으로 parameter의 실제값(data)이 update가 되어야함

 

그래야 update된 parameter를 바탕으로 다음 batch를 돌아가면서 loss가 작아질 것임

 

 

6. optimizer.step()

 

loss.backward()에 의해 parameter가 전달받은 grad를 바탕으로 실제 parameter값을 update해주는 것은 optimizer임

 

optimizer는 위에서 보인것처럼 parameter를 input으로 받으므로 parameter를 자유자재로 control할 수 있다

 

 

 

optimizer.step()으로 변경된 parameter의 grad를 바탕으로 parameter의 실제 data값인 parameter값을 update해준다

 

step 함수만 봐서는... 도대체 어딜봐서 업데이트해준다는거지

 

 

7. gradient accumulation

 

매번 batch size로 작업을 하는데 GPU 메모리는 한정적이다

 

minibatch 학습은 batch가 가지는 분포를 바탕으로 조금씩 데이터를 학습해나가는 것

 

때로는 batch size가 중요한 task가 있는데 작은 size로는 분포를 한번에 다 담지 못한다

 

큰 size로 돌고 싶을 때 매회 optimizer.step하여 update하지 말고 몇번 씩 update 과정을 skip한 뒤 step을 한다면?

 

gradient accumulation 코드

 

NUM_ACCUM = 2

optimizer.zero_grad()

for epoch in range(2):
    
    running_loss = 0.0
    
    for i,data in enumerate(train_loader, 0):
        
        inputs,labels = data
        
        outputs = net(inputs)
        
        loss = criterion(outputs,labels)/NUM_ACCUM
        loss.backward()
        
        if i % NUM_ACCUM == 0:
            optimizer.step()
            optimizer.zero_grad()

 

 

loss는 epoch이 돌아가면서 중첩해서 쌓이는 것이 기본값인데

 

NUM_ACCUM마다 지금까지 쌓인 loss를 바탕으로 optimizer가 step을 하는 전략

 

코드를 보면 loss를 num_accum으로 나눠서 구했음

 

num_accum이 지나면 결국엔 criterion값이랑 똑같은데???

 

그리고 나서 update를 한다면???

 

근데 결국에는 skip안할때랑 똑같은데 이게 skip을 조금했으니까 계산이 조금 빠르고 메모리 부담이 적을 수 있나???

TAGS.

Comments