Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
10-05 13:28
관리 메뉴

SJ_Koding

Pytorch, 이미지 분류 코드 자세히 이해하기 (5편) - Training/valid/test 上편 본문

PyTorch Code/Pytorch

Pytorch, 이미지 분류 코드 자세히 이해하기 (5편) - Training/valid/test 上편

성지코딩 2023. 12. 10. 21:44

(가독성과 필력을 위해 문체를 바꾸겠습니다.)
Data Augmentation, ResNet9까지 살펴 보았다.

2023.11.08 - [Deep Learning/Pytorch] - Pytorch, 이미지 분류 코드 자세히 이해하기 (4편) - ResNet9 모델

 

Pytorch, 이미지 분류 코드 자세히 이해하기 (4편) - ResNet9 모델

2023.11.08 - [Deep Learning/Pytorch] - Pytorch, 이미지 분류 코드 자세히 이해하기 (3편) - AutoAugment Pytorch, 이미지 분류 코드 자세히 이해하기 (3편) - AutoAugment 2023.11.07 - [Deep Learning/Pytorch] - Pytorch, 이미지 분

sjkoding.tistory.com

이제 본격적인 학습 준비가 되어있다.

먼저 모델 학습 및 검증, 추론이 이루어지는 함수 부터 선언한다.

def run_model(model, loader, loss_fn=None, optimizer=None, is_training=False, is_test = False):

    preds = []

    if is_training:
        model.train()
    else:
        model.eval()

    running_loss = 0.0

    if is_test:
        model.eval()
        for data in tqdm(loader):
            data = data.to('cuda')
            outputs = model(data)
            predicted = torch.argmax(outputs, dim=1).detach().cpu().tolist()
            preds.extend(predicted)
        return preds

    for data, target in tqdm(loader):

        data = data.to('cuda')
        target = target.to('cuda')

        if is_training:
            optimizer.zero_grad()

        outputs = model(data)
        total_loss = loss_fn(outputs, target)
        predicted = torch.argmax(outputs, dim=1).detach().cpu().tolist()
        preds.extend(predicted)
        if is_training:
            total_loss.backward()
            optimizer.step()

        running_loss += total_loss.item()

    acc_score = accuracy_score(target.detach().cpu().numpy(), predicted)

    return running_loss / len(loader), acc_score

매개변수가 조금 복잡한데, 이는 코드의 반복작성을 줄이고자 train과정, valid과정(파라메터 업데이트 x), test과정(라벨이 없음)을 나누기 위한 매개변수이다. 한 줄 한 줄 차근히 살펴보자.

def run_model(model, loader, loss_fn=None, optimizer=None, is_training=False, is_test = False):

    preds = []

    if is_training:
        model.train()
    else:
        model.eval()

    running_loss = 0.0

 

매개변수의 model, loader, loss_fn, optimizer는 변수명 그대로 값을 불러오고, is_training은 True일 시 학습, False일 시 검증.
is_test는 True이면 Test로 구분한다. (지금 생각해보니 model_mode라는 이름으로 1, 2, 3으로 지정할 걸 그랬다.)

preds 변수는 예측 값들을 담아두는 list이고 매개변수에 따라 model의 모드를 지정한다.

model.train()으로 지정할 경우:
train시 사용하는 모드로 Dropout, BatchNorm등이 활성화 되며 모델의 파라메터가 업데이트 된다.

model.eval()으로 지정할 경우:
validate이나 test시 사용되는 모드로 Dropout이 비활성화 되어 모든 뉴런이 활성화 상태가 된다. 또한 BatchNorm은 학습과정에서 계산된 평균 및 분산을 사용하여 입력을 정규화 한다. 이 경우에는 모델의 파라메터가 업데이트 되지 않는다.

 

   if is_test:
        model.eval()
        for data in tqdm(loader):
            data = data.to('cuda')
            outputs = model(data)
            predicted = torch.argmax(outputs, dim=1).detach().cpu().tolist()
            preds.extend(predicted)
        return preds

테스트시 사용되는 코드이다. 경진대회에서 test셋은 타겟값이 대부분 주어지지 않으므로 타겟값을 불러오지 않아야 한다. 이를 위해 loader로 부터 image데이터만 불러온다. tqdm은 반복의 진행률을 시각화 할 수 있는 도구로, 추후 자세한 포스팅으로 다루겠다.

data = data.to('cuda')
굉장히 중요한 코드이다. data(이미지)를 GPU 메모리로 이동시키는 작업을 수확류행한다. GPU는 병렬처리에 최적화 되어 있어 훨씬 빠르게 계산되어 거의 필수적인 과정이다. 이때 주의해야할 사항으로, 모델과 데이터가 모두 같은 장치에 있어야 한다는 점이다. 만약 모델이 GPU장치에 있지만, 인풋 데이터가 CPU에 있다면 에러가 발생한다. 프로젝트를 수행하면서 해당 에러는 한 번 쯤은 반드시 겪는다.

outputs = model(data)
data를 model에 입력시키고, model을 통과하고 난 후의 값을 outputs로 가져온다. 이때 outputs의 개수는 배치 개수와 동일하다.
즉, 추정값이다. test셋에서는 단순 추론결과를 내놓지만 train과정에서는 loss를 계산하기 위한 데이터로 쓰인다.

predicted = torch.argmax(outputs, dim=1).detech().cpu().tolist()
np.argmax와 동일한 원리로 tensor데이터에 대해 가장 큰 값의 인덱스를 가져오는데, dim=1이므로 softmax를 통과한 30개의 값(클래스 개수) 중, 가장 높은 값 (확률이 가장 높은 클래스)의 인덱스를 가져오게된다. 이 인덱스가 곧 추정된 클래스인 것이다. 즉 배치사이즈가 32라면 outputs는 (32, 30)이고 argmax(…, dim=1)을 수행하면 (32, 1)의 shape이 된다. 

그 다음으로 detech().cpu().tolist()를 수행하는데, 하나 하나 분리해서 살펴보자.
detech(): 기존 텐서로 부터 새로운 텐서를 생성하지만, 계산에서는 분리된다. 즉, 기존 텐서의 역전파 과정에 영향을 미치지 않으며 깊은복사로 생각하면 편하다.
cpu(): to('cuda')로 GPU장치에 옮겨졌던 데이터를 다시 CPU로 이동시킨다. 주로 NumPy와 같은 다른 라이브러리와 호환되는 형태로 변환할 때 필요하다. (그래프 시각화, loss 출력 등)
tolist(): Tensor데이터를 Python의 list로 변환한다.

for data, target in tqdm(loader):

        data = data.to('cuda')
        target = target.to('cuda')

        if is_training:
            optimizer.zero_grad()

        outputs = model(data)
        total_loss = loss_fn(outputs, target)
        predicted = torch.argmax(outputs, dim=1).detach().cpu().tolist()
        preds.extend(predicted)
        if is_training:
            total_loss.backward()
            optimizer.step()

        running_loss += total_loss.item()

    acc_score = accuracy_score(target.detach().cpu().numpy(), predicted)

    return running_loss / len(loader), acc_score

라벨이 포함되어 있는 train 및 valid 과정을 처리하는 코드이다. 이미 설명한 코드는 건너 뛴다.

target = target.to('cuda')를 하는 이유:
이미 GPU장치에 있는 모델의 추정값은 동일하게 GPU장치에 위치한다. 즉, outputs는 자동으로 GPU장치에 위치한다. loss를 구하기 위해서 target값과 outputs값을 비교해야하는데 아까 말했던 것 처럼 같은 장치에 위치해있어야 서로 연산이 된다고 했다. 즉, outputs와 target값을 연산하기 위해 target을 GPU로 옮겨주어야 한다.

일반적으로 학습은 다음 단계로 수행되며 아래 3가지는 필수이다.
1. optimizer.zero_grad() : gradient초기화
2. loss.backward() : gradient 계산
3. optimizer.step() : 파라메터 업데이트

optimizer.zero_grad()

정말 중요한 문장 중 하나.
필수적으로 알아 두어야 한다. PyTorch에서는 기본적으로 gradient가 누적이 되며 이는 .backward()함수가 호출될 때 마다 gradient값이 이전 값에 추가되어 저장된다는 뜻이다. 일반적인 학습 과정에서는 각 미니배치에 대한 독립적인 gradient 업데이트가 필요합니다. 다른 말로, 이전 단계의 gradient가 현재 gradient에 누적되어, 잘못된 업데이트를 초래한다는 것이다.
optimizer.zero_grad()는 연결된 optimizer의 모든 파라메터에 대한 gradient를 0으로 설정하는 기능을 수행한다.

loss_fn은 下편에서 다룰 loss function에서 자세히 다룬다.

loss.backward()
이것도 필수적으로 알아 두어야 한다
. 이는 주어진 손실 텐서(loss)에 대한 그라디언트를 계산한다. 즉, 손실함수의 값에 대해 모델의 각 가중치에 대한 편미분 값을 계산한다. 해당 과정은 네트워크의 가중치를 어떻게 조정해야 손실을 줄일 수 있는지 결정하는데 필요한 정보이다.
역전파는 각 층의 가중치에 대한 오차의 영향을 계산하며, backward()는 이 자동 미분 과정을 수행한다. backward()로 계산된 gradient에 기반하여 optimizer.step()을 통해 가중치를 조정하게 된다.

optimzer.step()
정해진 optimizer(예: SGD, Adam, RMSProp 등) 알고리즘에 따라 loss.backward()로 부터 구해진 gradient를 기반으로 가중치 업데이트를 수행한다. 따라서 순서도 backward()가 선행되어야 한다. optimizer함수에 따라 사용되는 파라메터가 달라지고, 해당 파라메터를 통해 가중치의 업데이트를 조절한다.

이후 sklearn.metrics의 정의 되어있는 metric을 사용하여 성능을 확인한다. 주로 (target, predict) 순으로 매개변수를 전달 받는다.

- 下편에서 loss function과 optimizer를 세부적으로 다루고, 위에서 선언한 함수를 어떻게 사용하는지 알아본다.