Study/머신러닝

PyTorch 전이학습

김 도경 2024. 12. 17. 11:50
Pretrained Model
  • 대규모 데이터셋을 기반으로 학습된 모델로, 학습한 task에 대한 일반적인 지식을 갖고 있음
  • GPT, PALM, Stable-Diffusion

전이학습
  • 사전 학습된 모델 (pretrained model)의 지식을 다른 task에 활용하는 것
  • 모델이 이미 학습한 일반적인 지식을 기반으로 더 빠르고 효과적이게 새로운 지식을 학습 가능

전이 학습 종류

  • Fine-Tuning
    - 전이 학습의 한 방법
    - Pretrained model을 그대로 혹은 layers를 추가한 후 새로운 작업에 맞는 데이터로 모델을 추가로 더 훈련시키는 방법
  • Domain Adaptation
    - 전이 학습의 한 방법
    - A 라는 도메인에서 학습한 모델을 B라는 도메인으로 전이하여 도메인 간의 차이를 극복하는 것이 목적
  • Multi-task learning
    : 하나의 모델을 사용하여 여러 개의 관련된 작업을 동시에 학습하면서 공통으로 사용되는 특징을 공유하는 학습 방식
  • Zero-shot learning
    : 기존에 학습되지 않은 새로운 클래스나 작업에 대해 예측을 수행하는 기술 (e.g. CLIP)
  • One/few-shot learning
    : 하나 또는 몇 개의 훈련 예시를 기반으로 결과를 예측하는 학습 방식

전이 학습 전략

  • 도메인이 비슷할 때, dataset 크기에 따른 전략
    - 비교적 작을 때 : 마지막 classifier만 추가 학습 (나머지 freeze)
        - 데이터셋의 크기가 비교적 작기 때문에, 기존의 학습한 일반적인 지식을 전달하는데 집중
    - 비교적 클 때 : classifier 뿐만 아니라, 다른 일부 layers도 추가 학습
        - 기존의 학습한 일반적인 지식을 유지하며, 몇 개의 layer만을 추가 학습시켜 specific한 새로운 데이터셋에 대한 지식을 학습
  • 도메인이 매우 다를 때, dataset 크기에 따른 전략
    - 꽤 클 때 : 꽤 많은 layers를 학습해야 함.
        - 도메인이 매우 다를 때, pretrained model이 이미 가지고 있는 지식을 꽤 많이 수정해야 하기 때문
  • Learning rate 전략
    - Pretrained model의 일반적인 지식을 크게 업데이트 하지 않기 위해 작은 learning rate으로 학습
        - 단, 마지막 FC Layer 만 업데이트 하는 경우 learning rate에 성능이 크게 영향을 받지 않음
Pretrained Model Community
  • 필요성
    - 최근 대규모 데이터셋을 사전 학습한 모델들이 발전
    - 사전 학습된 대규모 모델을 쉽게 커스터마이징 (customizing) 해서 활용하고자 하는 수요 증대
    - 이를 위한, pretrained model community 의 필요성 대두

  • Timm for CV
    - Timm (Pytorch Image Model)은 computer vision (CV) 분야에서 사용하는 사전 학습 모델 라이브러리
    - 2021년 paper with codes 에서 가장 인기있는 라이브러리 선정
    - 현재 모델은 사전 학습된 가중치가 있는 모델은 총 1,163개 제공 (2023.7월 기준)
    - Official Site : https://github.com/huggingface/pytorch-image-models 

    - Timm 라이브러리 사용법
        - timm.list_models() 로 제공되는 모델 리스트를 볼 수 있음
        - timm.create_model(“model 이름”, pretrained = True) 로 사전 학습된 모델의 weight를 불러올 수 있음

  • Hugging Face for NLP, CV
    - 오픈 소스 라이브러리 및 여러 도구 (demo, dataset)를 지원하는 커뮤니티
    - 초기엔 natural language processing (NLP) 위주였지만, 최근 computer vision, multi-modal, audio 등 다양한 분야의 pretrained 모델 라이브러리 제공
    - 243,069 개의 모델과 46,033 개의 데이터셋 제공 (2023.07 기준)
    - Official site : https://huggingface.co

    - Hugging Face 사용법
        - 다양한 라이브러리와 다양한 모델이 있어 사용하고자 하는 모델을 찾아야함 (Hugging Face Docs : https://huggingface.co/docs)
        - 찾은 모델의 예시를 참고하여 pip로 설치 (e.g. pip install transformers)

 

timm을 활용한 pretrained model 사용법

 

- timm으로 pretrained model 불러오기

import timm # timm 라이브러리 불러오기

timm.list_models() # timm이 지원하는 모든 모델 리스트
timm.list_models('resnet*') # 쿼리를 통해 모델을 검색할 수 있음.
timm.list_models('resnet50', pretrained=True) # resnet 모델 중 pretrained weight 가 있는 모델 리스트
model = timm.create_model('resnet50', pretrained=True) # resnet50을 imagenet으로 pretrain한 모델 불러오기, 첫번째 모델로 불러옴.
model.default_cfg # resnet50 모델의 기본 정보

model # 모델의 아키텍쳐

model2 = timm.create_model('resnet50', pretrained = True, num_classes = 10) # 마지막 output class 개수 10개로 조정
model2 # num class 를 임의로 조정하면 fc layer 의 weight가 초기화됨

model3 = timm.create_model('resnet50', pretrained = True, num_classes = 10) # 비교를 위한 모델 생성
model2.fc.weight == model3.fc.weight # fc layer weight 비교 => fc layer의 weight는 초기화되는 것을 확인

model2.conv1.weight == model3.conv1.weight # fc layer 이전의 weight는 동일함


- timm을 활용한 전이 학습 실습

# 데이터 불러오기
cifar_transform = T.Compose([
    T.ToTensor(), # 텐서 형식으로 변환
])
download_root = './CIFAR10_DATASET'

trainval_dataset = CIFAR10(download_root, transform=cifar_transform, train=True, download=True) # train dataset 다운로드
test_dataset = CIFAR10(download_root, transform=cifar_transform, train=False, download=True) # test dataset 다운로드

train_num, valid_num = int(len(trainval_dataset) * 0.8), int(len(trainval_dataset) * 0.2) # 8 : 2 = train : valid
print("Train dataset 개수 : ",train_num)
print("Validation dataset 개수 : ",valid_num)
train_dataset,val_dataset = torch.utils.data.random_split(trainval_dataset, [train_num, valid_num]) # train - valid set 나누기

BATCH_SIZE = 64 # 배치사이즈 설정
# 데이터로더 설정
train_dataloader = torch.utils.data.DataLoader(dataset=train_dataset,
                                          batch_size=BATCH_SIZE,
                                          shuffle=True,
                                          drop_last=False, num_workers = 8) # train dataloader 구성
val_dataloader = torch.utils.data.DataLoader(dataset=val_dataset,
                                          batch_size=BATCH_SIZE,
                                          shuffle=False,
                                          drop_last=False, num_workers = 8) # valid dataloader 구성
test_dataloader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=BATCH_SIZE,
                                          shuffle=False,
                                          drop_last=False, num_workers = 8) # test dataloader 구성

 

- pretrained 된 모델로 추론하기

# pretrained model 불러오기 (resnet50 불러오기)
device = 'cuda:0' # gpu 설정
model = timm.create_model('resnet50', pretrained=True, num_classes = 10).to(device) # 10개의 클래스 예측

# 이미지 하나 추론하기
img, label = train_dataset[0]
img = img.unsqueeze(0) # 배치 추가

model.eval() # evaluation 상태로 만듦 (freeze)
preds = model(img.to(device)) # model inference, image 도 gpu에 올리기
pred_label = torch.argmax(preds).item() # 가장 큰 값의 index 반환
print(f'True Label : {label} \nPredict Label : {pred_label}')

 

- timm을 이용하여 fine tuning 하기

# training 코드, evaluation 코드, training_loop 코드
def training(model, dataloader, train_dataset, criterion, optimizer, device, epoch, num_epochs):
  model.train()  # 모델을 학습 모드로 설정
  train_loss = 0.0
  train_accuracy = 0

  tbar = tqdm(dataloader)
  for images, labels in tbar:
      images = images.to(device)
      labels = labels.to(device)

      # 순전파
      outputs = model(images)
      loss = criterion(outputs, labels)

      # 역전파 및 가중치 업데이트
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()

      # 손실과 정확도 계산
      train_loss += loss.item()
      # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
      _, predicted = torch.max(outputs, 1)
      train_accuracy += (predicted == labels).sum().item()

      # tqdm의 진행바에 표시될 설명 텍스트를 설정
      tbar.set_description(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {loss.item():.4f}")

  # 에폭별 학습 결과 출력
  train_loss = train_loss / len(dataloader)
  train_accuracy = train_accuracy / len(train_dataset)

  return model, train_loss, train_accuracy

def evaluation(model, dataloader, val_dataset, criterion, device, epoch, num_epochs):
  model.eval()  # 모델을 평가 모드로 설정
  valid_loss = 0.0
  valid_accuracy = 0

  with torch.no_grad(): # model의 업데이트 막기
      tbar = tqdm(dataloader)
      for images, labels in tbar:
          images = images.to(device)
          labels = labels.to(device)

          # 순전파
          outputs = model(images)
          loss = criterion(outputs, labels)

          # 손실과 정확도 계산
          valid_loss += loss.item()
          # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
          _, predicted = torch.max(outputs, 1)
          valid_accuracy += (predicted == labels).sum().item()

          # tqdm의 진행바에 표시될 설명 텍스트를 설정
          tbar.set_description(f"Epoch [{epoch+1}/{num_epochs}], Valid Loss: {loss.item():.4f}")

  valid_loss = valid_loss / len(dataloader)
  valid_accuracy = valid_accuracy / len(val_dataset)

  return model, valid_loss, valid_accuracy


def training_loop(model, train_dataloader, valid_dataloader, train_dataset, val_dataset, criterion, optimizer, device, num_epochs, patience, model_name):
    best_valid_loss = float('inf')  # 가장 좋은 validation loss를 저장
    early_stop_counter = 0  # 카운터
    valid_max_accuracy = -1

    for epoch in range(num_epochs):
        model, train_loss, train_accuracy = training(model, train_dataloader, train_dataset, criterion, optimizer, device, epoch, num_epochs)
        model, valid_loss, valid_accuracy = evaluation(model, valid_dataloader, val_dataset, criterion, device, epoch, num_epochs)

        if valid_accuracy > valid_max_accuracy:
          valid_max_accuracy = valid_accuracy

        # validation loss가 감소하면 모델 저장 및 카운터 리셋
        if valid_loss < best_valid_loss:
            best_valid_loss = valid_loss
            torch.save(model.state_dict(), f"./model_{model_name}.pt")
            early_stop_counter = 0

        # validation loss가 증가하거나 같으면 카운터 증가
        else:
            early_stop_counter += 1

        print(f"Epoch [{epoch + 1}/{num_epochs}], Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.4f} Valid Loss: {valid_loss:.4f}, Valid Accuracy: {valid_accuracy:.4f}")

        # 조기 종료 카운터가 설정한 patience를 초과하면 학습 종료
        if early_stop_counter >= patience:
            print("Early stopping")
            break

    return model, valid_max_accuracy
# 모델 전체 fine tuning
num_epochs = 100
patience = 3
scores = dict()
model_name = 'exp1'

lr = 1e-3
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, val_dataloader, train_dataset, val_dataset, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy
model.load_state_dict(torch.load("./model_exp1.pt")) # 모델 불러오기
model = model.to(device)
model.eval()
total_labels = []
total_preds = []
with torch.no_grad():
    for images, labels in tqdm(test_dataloader):
        images = images.to(device)
        labels = labels

        outputs = model(images)
        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(outputs.data, 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
full_model_tuning_acc = accuracy_score(total_labels, total_preds) # 정확도 계산
print("Full Fine tuning model accuracy : ",full_model_tuning_acc) # 전체 모델을 fine tuning 한 것이 점수

 

# 마지막 layer 만 fine tuning
num_epochs = 100
patience = 3
scores = dict()
model_name = 'exp2'

model = timm.create_model('resnet50', pretrained=True, num_classes= 10).to(device)

for para in model.parameters(): # 모든 layer freeze 하기
    para.requires_grad = False
for para in model.fc.parameters(): # fc layer 만 학습하기
    para.requires_grad = True

lr = 1e-3
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, val_dataloader, train_dataset, val_dataset, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy
model.load_state_dict(torch.load("./model_exp2.pt")) # 모델 불러오기
model = model.to(device)
model.eval()
total_labels = []
total_preds = []
with torch.no_grad():
    for images, labels in tqdm(test_dataloader):
        images = images.to(device)
        labels = labels

        outputs = model(images)
        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(outputs.data, 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
fc_tuning_acc = accuracy_score(total_labels, total_preds) # 정확도 계산
print("Only FC Layer Fine tuning model accuracy : ",fc_tuning_acc) # 전체 layer를 fine tuning 한 것보다 점수가 낮음
# learning rate 에 따른 결과 비교

model3 = timm.create_model('resnet50', pretrained=True, num_classes= 10).to(device)
num_epochs = 100
patience = 3
scores = dict()
model_name = 'exp3'

lr = 1e-1 # learning rate 높게 설정
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model3.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model3, train_dataloader, val_dataloader, train_dataset, val_dataset, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy

model3.load_state_dict(torch.load("./model_exp3.pt")) # 모델 불러오기
model3 = model3.to(device)
model3.eval()
total_labels = []
total_preds = []
with torch.no_grad():
    for images, labels in tqdm(test_dataloader):
        images = images.to(device)
        labels = labels

        outputs = model3(images)
        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(outputs.data, 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
big_lr_acc = accuracy_score(total_labels, total_preds)
print("Large learning rate model accuracy : ",big_lr_acc)
# learning rate 에 따른 결과 비교

model4 = timm.create_model('resnet50', pretrained=True, num_classes= 10).to(device)
num_epochs = 100
patience = 3
scores = dict()
model_name = 'exp4'

lr = 1e-5 # 기존보다 더 작게 설정
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model4.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model4, train_dataloader, val_dataloader, train_dataset, val_dataset, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy

model4.load_state_dict(torch.load("./model_exp4.pt")) # 모델 불러오기
model4 = model4.to(device)
model4.eval()
total_labels = []
total_preds = []
with torch.no_grad():
    for images, labels in tqdm(test_dataloader):
        images = images.to(device)
        labels = labels

        outputs = model4(images)
        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(outputs.data, 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
small_lr_acc = accuracy_score(total_labels, total_preds)
print("Small learning rate model accuracy : ",small_lr_acc) # learning rate를 작게 설정했을 때가 더욱 정확도가 높음

 

Hugging Face를 통한 전이학습

 

- Hugging Face로 pretrained model 불러오기

from transformers import BertForSequenceClassification
BertForSequenceClassification.from_pretrained("bert-base-cased") # BERT로 분류기를 사용하는 모델 불러오기.

# data 불러오기
data = pd.read_csv('IMDB Dataset.csv')
print(data.shape)
data.head()

dic = {'positive':0, 'negative':1} # positive 면 0으로, negative면 1로 변환
data['sentiment'] = data['sentiment'].map(dic)

# data 8:1:1 로 나누기
train, test = train_test_split(data, test_size = .2, random_state = 42)
val, test = train_test_split(test, test_size = .5, random_state = 42)

print("Train 개수: ", len(train))
print("Validation 개수: ", len(val))
print("Test 개수: ", len(test))

train.reset_index(drop=True, inplace=True) # index 재정렬
val.reset_index(drop=True, inplace=True) # index 재정렬
test.reset_index(drop=True, inplace=True) # index 재정렬

 

- BERT를 훈련시키기 위한 모델 전처리

train['review'] = train['review'].apply(lambda x: f'[CLS] {x} [SEP]') # 문장의 앞뒤에 [CLS]와 [SEP] 삽입
val['review'] = val['review'].apply(lambda x: f'[CLS] {x} [SEP]') # 문장의 앞뒤에 [CLS]와 [SEP] 삽입
test['review'] = test['review'].apply(lambda x: f'[CLS] {x} [SEP]') # 문장의 앞뒤에 [CLS]와 [SEP] 삽입

train.head()

# 각 문장들만 추출
train_sentences = train['review'].values
val_sentences = val['review'].values
test_sentences = test['review'].values

# 정답값 추출
train_label = train['sentiment'].values
val_label = val['sentiment'].values
test_label = test['sentiment'].values

 

- HuggingFace Tokenizer

# BERT의 tokenizer로 문장을 토큰으로 분리
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-cased') # 기존에 학습된 BERT tokenizer 불러오기

tokenizer.tokenize(train_sentences[0])[:10] # BERT tokenizer 결과

train_tokenized_texts = list(map(lambda x: tokenizer.tokenize(x), train_sentences))
val_tokenized_texts = list(map(lambda x: tokenizer.tokenize(x), val_sentences))
test_tokenized_texts = list(map(lambda x: tokenizer.tokenize(x), test_sentences))

# 입력 토큰의 최대 시퀀스 길이
MAX_LEN = 128

# 토큰을 숫자 인덱스로 변환
train_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), train_tokenized_texts)) # convert_tokens_to_ids로 정수 형태로 변환해주기
val_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), val_tokenized_texts))
test_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), test_tokenized_texts))

# 입력 토큰의 최대 시퀀스 길이
MAX_LEN = 128

# 토큰을 숫자 인덱스로 변환
train_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), train_tokenized_texts)) # convert_tokens_to_ids로 정수 형태로 변환해주기
val_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), val_tokenized_texts))
test_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), test_tokenized_texts))
# 입력 토큰의 최대 시퀀스 길이
MAX_LEN = 128

# 토큰을 숫자 인덱스로 변환
train_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), train_tokenized_texts)) # convert_tokens_to_ids로 정수 형태로 변환해주기
val_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), val_tokenized_texts))
test_input_ids = list(map(lambda x: tokenizer.convert_tokens_to_ids(x), test_tokenized_texts))

train_input_ids[0]

 

- Mask 만들기

# 마스크 만들기
train_masks = train_input_ids > 0 # 패딩이 아닌 부분은 0보다 큰 값이 있으므로 flag를 통해서 마스크를 구성할 수 있습니다.
val_masks = val_input_ids > 0
test_masks = test_input_ids > 0

train_masks[0]

 

- Database 만들기

# 모두 tensor로 변환
train_inputs = torch.tensor(train_input_ids) # train set의 input token id들
train_labels = torch.tensor(train_label) # train set의 label들
train_masks = torch.tensor(train_masks) # train set의 mask

validation_inputs = torch.tensor(val_input_ids) # valid set의 input token id들
validation_labels = torch.tensor(val_label) # valid set의 label들
validation_masks = torch.tensor(val_masks) # valid set의 mask

test_inputs = torch.tensor(test_input_ids) # test set의 input token id들
test_labels = torch.tensor(test_label) # test set의 label들
test_masks = torch.tensor(test_masks) # test set의 mask

class EmotionData(torch.utils.data.Dataset): # custom 데이터셋 구성
    def __init__(self, inputs, masks, labels):
        self.inputs = inputs
        self.masks = masks
        self.labels = labels

    def __len__(self):
        return len(self.inputs)

    def __getitem__(self,idx):
        inputs_value = self.inputs[idx]
        masks_value = self.masks[idx]
        labels_value = self.labels[idx]
        return inputs_value, masks_value, labels_value
train_dataset = EmotionData(train_inputs, train_masks, train_labels)
valid_dataset = EmotionData(validation_inputs, validation_masks, validation_labels)
test_dataset = EmotionData(test_inputs, test_masks, test_labels)

BATCH_SIZE = 32
train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size = BATCH_SIZE, shuffle = True, drop_last = False, num_workers = 8)
valid_dataloader = torch.utils.data.DataLoader(valid_dataset, batch_size = BATCH_SIZE, shuffle = False, drop_last = False, num_workers = 8)
test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size = BATCH_SIZE, shuffle = False, drop_last = False, num_workers = 8)

 

- BERT 모델 불러오기

model = BertForSequenceClassification.from_pretrained("bert-base-cased").to(device) # pretrained bert 모델 불러오기

# training 코드, evaluation 코드, training_loop 코드
def training(model, dataloader, train_dataset, optimizer, device, epoch, num_epochs):
    model.train()  # 모델을 학습 모드로 설정
    train_loss = 0.0
    train_accuracy = 0

    tbar = tqdm(dataloader)
    for batch in tbar:
        input_ = batch[0].to(device)
        mask = batch[1].to(device)
        labels = batch[2].to(device)

        # 순전파
        output = model(input_,
                        attention_mask= mask,
                        labels=labels)

        loss = output['loss'] # 얘 확인

        # 역전파 및 가중치 업데이트
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 손실과 정확도 계산
        train_loss += loss.item()
        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(output['logits'], 1)
        train_accuracy += (predicted == labels).sum().item()

        # tqdm의 진행바에 표시될 설명 텍스트를 설정
        tbar.set_description(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {loss.item():.4f}")

    # 에폭별 학습 결과 출력
    train_loss = train_loss / len(dataloader)
    train_accuracy = train_accuracy / len(train_dataset)

    return model, train_loss, train_accuracy

def evaluation(model, dataloader, val_dataset, device, epoch, num_epochs):
    model.eval()  # 모델을 평가 모드로 설정
    valid_accuracy = 0

    with torch.no_grad(): # model의 업데이트 막기
        tbar = tqdm(dataloader)
        for batch in tbar:
            input_ = batch[0].to(device)
            mask = batch[1].to(device)
            labels = batch[2].to(device)
            # 순전파
            output = model(input_,
                            attention_mask= mask,
                            labels=labels)

            # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
            _, predicted = torch.max(output['logits'], 1)
            valid_accuracy += (predicted == labels).sum().item()

            # tqdm의 진행바에 표시될 설명 텍스트를 설정
            tbar.set_description(f"Epoch [{epoch+1}/{num_epochs}]")

    valid_accuracy = valid_accuracy / len(val_dataset)

    return model, valid_accuracy


def training_loop(model, train_dataloader, valid_dataloader, train_dataset, val_dataset, optimizer, device, num_epochs, model_name):
    best_valid_loss = float('inf')  # 가장 좋은 validation loss를 저장
    valid_max_accuracy = -1

    for epoch in range(num_epochs):
        model, train_loss, train_accuracy = training(model, train_dataloader, train_dataset, optimizer, device, epoch, num_epochs)
        model, valid_accuracy = evaluation(model, valid_dataloader, val_dataset, device, epoch, num_epochs)

        if valid_accuracy > valid_max_accuracy:
            valid_max_accuracy = valid_accuracy
            torch.save(model.state_dict(), f"./model_{model_name}.pt")

        print(f"Epoch [{epoch + 1}/{num_epochs}], Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}, Valid Accuracy: {valid_accuracy:.4f}")

    return model, valid_max_accuracy
# 모델 전체 fine tuning
num_epochs = 2
model_name = 'bert1'
lr = 1e-5
optimizer = optim.Adam(model.parameters(), lr=lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, train_dataset, valid_dataset, optimizer, device, num_epochs, model_name)
print('Valid max accuracy : ', valid_max_accuracy)

 

model.load_state_dict(torch.load("./model_bert1.pt")) # 모델 불러오기
model = model.to(device)
model.eval()
total_labels = []
total_preds = []
total_probs = []
with torch.no_grad():
    for batch in tqdm(test_dataloader):
        input_ = batch[0].to(device)
        mask = batch[1].to(device)
        labels = batch[2].to(device)
        output = model(input_,
                attention_mask= mask,
                labels=labels)


        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(output['logits'], 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())
        total_probs.append(output['logits'].detach().cpu().numpy())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
total_probs = np.concatenate(total_probs, axis= 0)
acc = accuracy_score(total_labels, total_preds)
print("Full fine tuning model accuracy : ",acc)

model =  BertForSequenceClassification.from_pretrained("bert-base-cased").to(device)
for para in model.parameters(): # 모든 layer freeze 하기
    para.requires_grad = False
for name, param in model.named_parameters(): # fc layer 만 학습하기
    if name in 'classifier.weight':
        param.requires_grad = True

num_epochs = 2
model_name = 'bert2'

optimizer = optim.Adam(model.parameters(), lr=lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, train_dataset, valid_dataset, optimizer, device, num_epochs, model_name)
print('Valid max accuracy : ', valid_max_accuracy)
model.load_state_dict(torch.load("./model_bert2.pt")) # 모델 불러오기
model = model.to(device)
model.eval()
total_labels = []
total_preds = []
total_probs = []
with torch.no_grad():
    for batch in tqdm(test_dataloader):
        input_ = batch[0].to(device)
        mask = batch[1].to(device)
        labels = batch[2].to(device)
        output = model(input_,
                attention_mask= mask,
                labels=labels)


        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(output['logits'], 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())
        total_probs.append(output['logits'].detach().cpu().numpy())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
total_probs = np.concatenate(total_probs, axis= 0)
acc = accuracy_score(total_labels, total_preds)
print("Only FC layer tunning model accuracy : ", acc)

# learning rate 를 기존보다 높게 설정
model =  BertForSequenceClassification.from_pretrained("bert-base-cased").to(device)
num_epochs = 2
model_name = 'bert3'
lr = 1e-4
optimizer = optim.Adam(model.parameters(), lr=lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, train_dataset, valid_dataset, optimizer, device, num_epochs, model_name)
print('Valid max accuracy : ', valid_max_accuracy)
model.load_state_dict(torch.load("./model_bert3.pt")) # 모델 불러오기
model = model.to(device)
model.eval()
total_labels = []
total_preds = []
total_probs = []
with torch.no_grad():
    for batch in tqdm(test_dataloader):
        input_ = batch[0].to(device)
        mask = batch[1].to(device)
        labels = batch[2].to(device)
        output = model(input_,
                attention_mask= mask,
                labels=labels)


        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(output['logits'], 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())
        total_probs.append(output['logits'].detach().cpu().numpy())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
total_probs = np.concatenate(total_probs, axis= 0)
acc = accuracy_score(total_labels, total_preds)
print("Larger learning rate accuracy : ", acc)

'Study > 머신러닝' 카테고리의 다른 글

PyTorch Hydra  (0) 2024.12.17
PyTorch Lightning  (0) 2024.12.17
딥러닝과 PyTorch  (0) 2024.12.17
텐서 조작, Tensor Manipulation(with PyTorch)  (0) 2024.12.16
Pytorch  (0) 2024.12.16