"Attention Is All You Need"는 2017년에 발표된 논문으로, 자연어 처리(NLP)에서 혁신적인 Transformer 모델을 소개합니다. 이 모델은 기존의 RNN(recurrent neural networks)과 CNN(convolutional neural networks)과 달리, 전적으로 어텐션 메커니즘에 의존합니다. 어텐션 메커니즘은 입력 시퀀스의 모든 부분을 동시에 고려하여 병렬 처리를 가능하게 하고, 문맥을 더 잘 파악할 수 있게 합니다.
Transformer 모델은 크게 인코더와 디코더로 구성되어 있습니다. 인코더는 입력 시퀀스를 받아 어텐션 메커니즘을 통해 각 단어의 중요도를 계산하고, 디코더는 이 정보를 바탕으로 출력 시퀀스를 생성합니다. 이 모델의 핵심 구성 요소는 셀프 어텐션(self-attention) 메커니즘과 다중-헤드 어텐션(multi-head attention)입니다. 셀프 어텐션은 시퀀스 내의 각 단어가 다른 단어들과의 관계를 고려하게 하며, 다중-헤드 어텐션은 다양한 시각에서 이 관계를 분석할 수 있게 합니다.
Hugging Face Transformers는 자연어 처리(NLP) 작업을 위한 강력한 라이브러리로, 다양한 사전 훈련된 모델을 제공하여 연구자와 개발자가 쉽게 사용할 수 있게 합니다. 이 라이브러리는 PyTorch와 TensorFlow를 지원하며, BERT, GPT-2, T5, RoBERTa 등의 다양한 Transformer 기반 모델을 포함하고 있습니다.
Huggingface Transformer는 많은 기능을 제공하므로 모든 것을 소개하지는 못합니다. 여기서는 크게 Transformer 이론 해설, Pytorch로 구현, Huggingface Transformer 라이브러리와 pytorch로 Text Classification을 구성했습니다.
ChatGPT를 만드는 데 사용되는 데이터 세트는 **570GB이다. 반면, 여기서는 시각적으로 수치 계산을 수행하기 위해 매우 작은 데이터 세트를 사용한다.
세 문장으로만 구성된 전체 데이터 세트
전체 데이터 세트에는 단 세 개의 문장만 포함되어 있으며, 모두 TV 프로그램에서 가져온 대화이다.. 데이터 세트는 정리되었지만 ChatGPT 생성과 같은 실제 시나리오에서는 570GB의 데이터 세트를 정리하는 데 상당한 노력이 필요하다.
2단계: 어휘 크기 찾기
어휘 크기는 데이터 세트의 총 고유 단어 수를 결정한다. 아래 공식을 사용하여 계산할 수 있으며, 여기서 N은 데이터 세트의 총 단어 수이다.
vocab_size 공식에서 N은 총 단어 수이다.
N을 찾으려면 데이터 집합을 개별 단어로 쪼개야한다.
변수 N 계산
N을 구한 후 집합 연산을 수행하여 중복을 제거한 다음 고유 단어를 계산하여 어휘 크기를 결정할 수 있다.
어휘 크기 찾기
따라서 데이터 세트에 23개의 고유 단어가 있으므로 어휘 크기는 23개이다.
3단계 - 인코딩
이제 각 고유 단어에 고유 번호를 할당한다.
고유 단어 인코딩
하나의 토큰을 하나의 단어로 간주하고 숫자를 할당했듯이, ChatGPT는 이 공식을 사용하여 단어의 일부를 하나의 토큰으로 간주한다.: 1 토큰 = 0.75 단어
전체 데이터 세트를 인코딩한 후에는 입력을 선택하고 트랜스포머 아키텍처로 작업을 시작할 차례이다.
4단계 - 임베딩 계산
말뭉치(Corpus)에서 트랜스포머 아키텍처에서 처리할 문장을 선택해 보자.
Transformer 입력 문장
입력을 선택했으니 그에 맞는 임베딩 벡터(Embedding Vector)를 찾아야 한다. 논문에서는 각 입력 단어에 대해 512차원 임베딩 벡터를 사용한다.
원본 용지는 512 차원 벡터를 사용합니다.
이 경우 계산이 어떻게 진행되는지 시각화하기 위해 더 작은 차원의 임베딩 벡터로 작업해야 한다. 따라서 임베딩 벡터에 6 차원을 사용하겠다.
입력 벡터 임베딩
임베딩 벡터의 값은 0과 1 사이이며 처음에는 무작위로 채워진다.나중에 트랜스포머가 단어 사이의 의미를 이해하기 시작하면 이 값은 업데이트된다.
5단계 - 위치 임베딩 계산하기
이제 입력에 대한 위치 임베딩(Positional Embedding)을 찾아야 한다.각 단어에 대한 임베딩 벡터의 ith 값의 위치에 따라 위치 임베딩을 위한 두 가지 공식이 있다.
위치 임베딩 공식
아시다시피, 입력 문장은 "왕좌의 게임을 할 때 "이고 시작 단어는 "언제 "이며 시작 인덱스(POS) 값은 0이고 차원(d)은 6이다.. 0에서 5까지의i 대해 입력 문장의 첫 번째 단어에 대한 위치 임베딩을 계산한다.
단어의 위치 임베딩: When
마찬가지로 입력 문장의 모든 단어에 대해 위치 임베딩을 계산할 수 있다.
입력값의 위치 임베딩 계산하기 (계산된 값은 반올림 됨)
6단계 - 위치 및 단어 임베딩 연결하기
위치 임베딩을 계산한 후에는 단어 임베딩과 위치 임베딩을 추가한다.
연결 단계
두 행렬 (단어 임베딩 행렬과위치 임베딩 행렬)을 결합한 결과 행렬이 인코더 부분의 입력으로 간주된다.
7단계 - 멀티 헤드 어텐션
다중 헤드 어텐션(Multi-head Attention)은 여러 개의 단일 헤드 어텐션으로 구성된다. 얼마나 많은 단일 헤드를 결합할 것인지는 모델에 따라 다르다. 예를 들어 Meta의 LLaMA LLM은 인코더 아키텍처에 32개의 싱글 헤드를 사용했다. 아래는 단일 헤드 어텐션이 어떻게 생겼는지 보여주는 그림 다이어그램.
트랜스포머의 싱글 헤드 어텐션
쿼리(Query), 키(Key), 값(Value)의 세 가지 입력이 있다. 이러한 각 행렬은 앞서 계산한 동일한 행렬의 조옮김 행렬과 다른 가중치 행렬 집합을 곱하여 단어 임베딩 및 위치 임베딩 행렬을 추가하여 얻는다.
쿼리 행렬을 계산하기 위해 가중치 행렬 집합의 행 수는 전치 행렬의 열 수와 같아야 하지만 가중치 행렬의 열은 어떤 것이든 상관없습니다(예를 들어 가중치 행렬의 열이 4개가정해 보면). 가중치 행렬의 값은 0과 1 사이의 임의의 값이며, 나중에 트랜스포머가 단어의 의미를 학습하기 시작하면 업데이트된다.
쿼리 행렬 계산
마찬가지로 동일한 절차를 사용하여 키 행렬과 값 행렬을 계산할 수 있지만, 가중치 행렬의 값은 두 행렬에 대해 서로 달라야 한다.
키 및 값 행렬 계산
따라서 행렬을 곱하면 결과 쿼리, 키, 값을 얻을 수 있다:
쿼리, 키, 값 행렬
이제 세 개의 행렬이 모두 준비되었으므로 단일 헤드 주의도 계산을 단계별로 시작해 보자.
쿼리와 키 사이의 행렬 곱셈
결과 행렬의 크기를 조정하려면 임베딩 벡터의 차원인 6을 재사용해야 한다.
결과 행렬을 차원 5로 스케일링한다.
마스킹의 다음 단계는선택 사항이며 계산하지 않습니다. 마스킹은 모델에게 문장에서 다른 단어의 중요성을 파악하는 동안 특정 시점 이전에 일어난 일에만 집중하고 미래를 들여다보지 말라고 말하는 것과 같다. 이는 모델이 앞을 내다보며 속임수를 쓰지 않고 단계적으로 사물을 이해하는 데 도움이 된다.
이제 스케일링된 결과 행렬에 소프트맥스 연산을 적용배보자.
결과 매트릭스에 소프트맥스 적용하기
최종 곱셈 단계를 수행하여 단일 헤드 주의에서 결과 행렬을 얻는다.
싱글 헤드 어텐션의 최종 매트릭스 계산하기
앞서 말씀드린 것처럼 단일 헤드 어텐션은 단일 헤드 어텐션으로 계산한 반면, 멀티 헤드 어텐션은 여러 개의 단일 헤드 어텐션으로 구성된다. 아래는 그 모습을 시각화:
트랜스포머의 멀티 헤드 어텐션
각 싱글 헤드 어텐션에는 쿼리, 키, 값의 세 가지 입력이 있으며, 세 가지 입력에는 각각 다른 가중치 세트가 있다. 모든 싱글 헤드 어텐션이 결과 행렬을 출력하면 모두 연결되고, 최종 연결 행렬은 다시 한 번 무작위 값으로 초기화된 가중치 행렬 세트를 곱하여 선형적으로 변환되며, 나중에 트랜스포머가 훈련을 시작할 때 업데이트된다.
우리의 경우 단일 헤드 어텐션을 고려하고 있지만 다중 헤드 어텐션으로 작업하는 경우 이렇게 보인다.
싱글 헤드 어텐션 vs. 멀티 헤드 언텐션
단일 헤드 어텐션이든 다중 헤드 어텐션, 어떤 경우든 결과 행렬은 가중치 행렬 세트를 곱하여 다시 한 번 선형적으로 변환해야 한다.
단일 헤드 어텐션 행렬 정규화
다음 단계에서는 (단어 임베딩+ 위치 임베딩) 행렬로 정규화된 결과 행렬을 더할 것이므로 선형 가중치 집합 행렬의 열 수는 앞서 계산한 (단어 임베딩 + 위치 임베딩) 행렬의 열 수와 같아야 한다.
멀티 헤드 어텐션 출력 매트릭스
멀티 헤드 어텐션에 대한 결과 행렬을 계산했으므로 이제 단계를 추가하고 정규화하는 작업을 진행한다.
8단계 - 추가 및 정규화
멀티 헤드 어텐션에서 결과 행렬을 얻었으면 이를 원래 행렬에 추가해보자.
행렬을 추가하여 더하기 및 표준화 단계 수행하기
위의 행렬을 정규화하려면 각 행의 평균과 표준 편차를 행 단위로 계산해야 한다.
MEAND 및 STD 계산
행렬의 각 값에서 해당 행 평균을 뺀 다음 해당 표준 편차로 나눈다.
결과 행렬 정규화
작은 오차 값을 추가하면 분모가 0이 되는 것을 방지하고 전체 항이 무한대가 되는 것을 방지할 수 있다.
9단계 - 피드 포워드 네트워크
행렬을 정규화한 후에는 피드포워드 네트워크를 통해 처리된다. 여기서는 하나의 선형 레이어와 하나의 ReLU 활성화 함수 레이어만 포함된 매우 기본적인 네트워크를 사용한다. 이것이 시각적으로 보이는 모습:
피드 포워드 네트워크 비교
먼저, 마지막으로 계산한 행렬에 트랜스포머가 학습을 시작할 때 업데이트될 임의의 가중치 행렬 세트를 곱하고 그 결과 행렬을 임의의 값이 포함된 바이어스 행렬에 추가하여 선형 레이어를 계산한다.
선형 레이어 계산
선형 레이어를 계산한 후에는 이를 ReLU 레이어에 전달하고 해당 공식을 사용한다.
ReLU 레이어 계산
10단계 - 다시 추가 및 정규화
피드 포워드 네트워크에서 결과 행렬을 얻으면 이전 더하기 및 표준화 단계에서 얻은 행렬에 더한 다음 행 현명한 평균과 표준 편차를 사용하여 정규화해야 한다.
피드 포워드 네트워크 후 추가 및 규제
이 더하기 및 규제(Regularization) 단계의 출력 행렬은 디코더 부분에 존재하는 다중 헤드 주의 메커니즘 중 하나에서 쿼리 및 키 행렬로 사용되며, 더하기 및 규범에서 디코더 섹션으로 바깥쪽으로 추적하면 쉽게 이해할 수 있다.
11단계 - 디코더 부분
지금까지 인코더 부분( )을 계산했는데, 데이터 세트 인코딩부터 피드포워드 네트워크를 통한 행렬 전달까지 수행한 모든 단계가 고유하다. 이는 이전에 계산한 적이 없다는 뜻으로 트랜스포머의 나머지 아키텍처인 디코더 부분의모든 단계가 비슷한 종류의 행렬 곱셈을 포함하게 될 것이다.
향후 단계 디코더
디코더의 대부분은 인코더에서 이미 수행한 계산과 유사한 계산을 포함하므로 디코더의 입력과 출력의 계산에만 집중하면 된다.
값 행렬은 첫 번째 더하기 및 표준화 단계 이후 디코더에서 가져온다.
디코더에 대한 두 번째 입력은 예측된 텍스트입니다. 기억하시겠지만, 인코더에 대한 입력은 왕좌의 게임을 할 때이므로 디코더에 대한 입력은 예측된 텍스트(이 경우 승리 또는 사망 )이다.
하지만 예측된 입력 텍스트는 트랜스포머가 어디서 시작하고 어디서 끝나는지 알 수 있도록 표준 토큰 래핑을 따라야 한다.
인코더와 디코더의 입력 비교
여기서 <start><end> 새로 도입되는 두 개의 토큰입니다. 또한 디코더는 한 번에 하나의 토큰을 입력으로 받습니다. 즉, <start> 입력으로 제공되며, 사용자 이에 대해 예측된 텍스트여야 한다.
디코더 입력 <시작> 단어
이미 알고 있듯이 이러한 임베딩은 임의의 값으로 채워지며, 나중에 학습 프로세스 중에 업데이트된다.
앞서 인코더 부분에서 계산한 것과 동일한 방식으로 나머지 블록을 계산한다.
디코더 계산
자세한 내용을 살펴보기 전에 간단한 수학적 예시를 통해 마스크드 멀티 헤드 주의가 무엇인지 알아보자.
12단계 - 마스크 멀티 헤드 어텐션 이해
트랜스포머에서 마스크드 멀티 헤드 어텐션은 모델이 문장의 여러 부분에 집중할 때 사용하는 스포트라이트와 같다. 이 기능이 특별한 이유는 모델이 문장의 뒷부분에 나오는 단어를 보고 속임수를 쓰지 못하도록 하기 때문이다. 이는 모델이 단계별로 문장을 이해하고 생성하는 데 도움이 되며, 이는 말하기나 다른 언어로 단어 번역과 같은 작업에서 중요하다
각 행이 시퀀스의 위치를 나타내고 각 열이 피처를 나타내는 다음과 같은 입력 행렬이 있다고 가정해 보자:
마스크형 멀티 헤드 어텐션용 인퍼 매트릭스
이제 두 개의 헤드를 가진 마스크드 멀티 헤드 주의 컴포넌트를 이해해 보겠다:
선형 투영(쿼리, 키, 값): 각 헤드에 대한 선형 투영을 가정합니다: **헤드 1: Wq1,Wk1,Wv1 및 헤드 2: Wq2,Wk2,Wv2
주의력 점수를 계산합니다: 각 헤드에 대해 쿼리와 키의 도트 곱을 사용하여 주의 점수를 계산하고 마스크를 적용하여 향후 위치에 참석하지 못하도록 한다.
소프트맥스 적용: 소프트맥스 함수를 적용하여 관심도 가중치를 얻는다.
가중치 합산(값): 관심도 가중치에 값을 곱하여 각 헤드에 대한 가중치 합계를 얻는다.
연결 및 선형 변환: 두 헤드의 출력을 연결하고 선형 변환을 적용한다.
간단한 계산을 해 보겠습니다:
두 가지 조건을 가정합니다.
Wq1= Wk1= Wv1= Wq2= Wk2= Wv2= I, 아이덴티티 행렬이다..
Q=K=V=입력 행렬
마스크 멀티 헤드 어텐션 (헤드 2개)
연결 단계에서는 두 주의 헤드의 출력을 하나의 정보 집합으로 결합한다. 두 명의 친구가 각각 어떤 문제에 대해 조언을 해준다고 가정해 보자. 두 친구의 조언을 연결한다는 것은 두 친구의 조언을 모두 합쳐서 두 친구가 제안하는 내용을 보다 완벽하게 파악하는 것을 의미한다. 트랜스포머 모델의 맥락에서 이 단계는 입력 데이터의 다양한 측면을 여러 관점에서 포착하여 모델이 추가 처리에 사용할 수 있는 더 풍부한 표현에 기여한다.
13단계 - 예상 단어 계산하기
디코더의 마지막 덧셈 및 표준 블록의 출력 행렬은 입력 행렬과 동일한 수의 행을 포함해야 하며 열 수는 임의의 수일 수 있다.. 여기서는 6으로 작업하자.
디코더의 출력 추가 및 표준화
디코더의 마지막 덧셈 및 표준 블록 결과 행렬은 데이터 세트(말뭉치)에서 각 고유 단어의 예측 확률을 찾기 위해 선형 레이어와 일치시키기 위해 평탄화되어야 한다.
마지막 더하기 및 표준화 블록 행렬을 평평하게 만든다.
이 평평한 레이어는 선형 레이어를 통과하여 데이터 세트에 있는 각 고유 단어의 로짓 (점수)을 계산한다.
Logits 계산
로그를 얻으면 소프트맥스 함수를 사용하여 정규화하여 가장 높은 확률을 포함하는 단어를 찾을 수 있다.
예상 단어 찾기
따라서 계산에 따르면 디코더에서 예상되는 단어는 you.
디코더의 최종 출력
이 예측된 단어 you 디코더의 입력 단어로 취급되며, 이 프로세스는 <end> 토큰이 예측될 때까지 계속된다.
중요 사항
위의 예는 파이썬과 같은 프로그래밍 언어를 사용해야만 시각화할 수 있는 에포크나 기타 중요한 매개변수를 포함하지 않기 때문에 매우 간단하다.
이 매트릭스 접근 방식을 사용하면 평가나 테스트는 시각적으로 볼 수 없는 반면, 훈련까지의 과정만 보여 준다.
마스킹된 다중 헤드 어텐션은 트랜스포머가 미래를 보지 못하도록 하여 모델 과적합을 방지하는 데 사용할 수 있다.
II. Transformers: Pytorch 구현
import torch
import torch.nn as nn
import torch.optim as optim
import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random
import re
from torch.utils.data import DataLoader, Dataset
1. Transformers 기본 구조
d_model: 트랜스포머의 인코더와 디코더에서의 정해진 입력과 출력의 크기 (default=512)
num_encoder_layers: 트랜스포머 모델에서 인코더가 총 몇 층으로 구성되었는지를 의미 (default=6)
num_decoder_layers: 트랜스포머 모델에서 디코더가 총 몇 층으로 구성되었는지를 의미 (default=6)
nhead: 멀티헤드 어텐션 모델의 헤드 수, 어텐션을 사용할 때 여러 개로 분할해서 병렬로 어텐션을 수행하고 결과값을 다시 하나로 합치는 방식에서 병렬의 수 (default=8)
dim_feedforward: feedforward network model 의 차원, 피드 포워드 신경망의 은닉층의 크기(default=2048).
시퀀스 형태의 크기가 8인 모든 문장을 만들고 크기가 16인 배치로 무작위로 구성하는 가상의 데이터셋 생성
1, 1, 1, 1, 1, 1, 1, 1 → 1, 1, 1, 1, 1, 1, 1, 1
0, 0, 0, 0, 0, 0, 0, 0 → 0, 0, 0, 0, 0, 0, 0, 0
1, 0, 1, 0, 1, 0, 1, 0 → 1, 0, 1, 0, 1, 0, 1, 0
0, 1, 0, 1, 0, 1, 0, 1 → 0, 1, 0, 1, 0, 1, 0, 1
def generate_random_data(n):
SOS_token = np.array([2])
EOS_token = np.array([3])
length = 8
data = []
# 1,1,1,1,1,1 -> 1,1,1,1,1
for i in range(n // 3):
X = np.concatenate((SOS_token, np.ones(length), EOS_token))
y = np.concatenate((SOS_token, np.ones(length), EOS_token))
data.append([X, y])
# 0,0,0,0 -> 0,0,0,0
for i in range(n // 3):
X = np.concatenate((SOS_token, np.zeros(length), EOS_token))
y = np.concatenate((SOS_token, np.zeros(length), EOS_token))
data.append([X, y])
# 1,0,1,0 -> 1,0,1,0,1
for i in range(n // 3):
X = np.zeros(length)
start = random.randint(0, 1)
X[start::2] = 1
y = np.zeros(length)
if X[-1] == 0:
y[::2] = 1
else:
y[1::2] = 1
X = np.concatenate((SOS_token, X, EOS_token))
y = np.concatenate((SOS_token, y, EOS_token))
data.append([X, y])
np.random.shuffle(data)
return data
#크기가 16인 배치 형태로 만들어 줍니다.
def batchify_data(data, batch_size=16, padding=False, padding_token=-1):
batches = []
for idx in range(0, len(data), batch_size):
# batch_size 크기가 아닌 경우 마지막 비트를 얻지 않도록 합니다.
if idx + batch_size < len(data):
# 여기서 배치의 최대 길이를 가져와 PAD 토큰으로 길이를 정규화해야 합니다.
if padding:
max_batch_length = 0
# batch에서 가장 긴 문장 가져오기
for seq in data[idx : idx + batch_size]:
if len(seq) > max_batch_length:
max_batch_length = len(seq)
# 최대 길이에 도달할 때까지 X 패딩 토큰을 추가합니다.
for seq_idx in range(batch_size):
remaining_length = max_bath_length - len(data[idx + seq_idx])
data[idx + seq_idx] += [padding_token] * remaining_length
batches.append(np.array(data[idx : idx + batch_size]).astype(np.int64))
print(f"{len(batches)} batches of size {batch_size}")
return batches
train_data = generate_random_data(9000)
val_data = generate_random_data(3000)
train_dataloader = batchify_data(train_data)
val_dataloader = batchify_data(val_data)
{'sentence1': "Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",
'sentence2': "Yucaipa bought Dominick 's in 1995 for $ 693 million and sold it to Safeway for $ 1.8 billion in 1998 .",
'label': 0,
'idx': 1}
from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
[len(x) for x in samples["input_ids"]]
[50, 59, 47, 67, 59, 50, 62, 32]
3. Train
3.1 TrainingArguments 정의
from transformers import TrainingArguments
training_args = TrainingArguments("test-trainer")
3.2 Sequence Classification PLM
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(
checkpoint,
num_labels=2
)
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at bert-base-uncased and are newly initialized: ['classifier.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(
checkpoint,
num_labels=2
)
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at bert-base-uncased and are newly initialized: ['classifier.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
from transformers import AdamW
optimizer = AdamW(
model.parameters(),
lr=5e-5
)
/home/kubwa/anaconda3/envs/pytorch/lib/python3.11/site-packages/transformers/optimization.py:588: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning
warnings.warn(
from tqdm.auto import tqdm
progress_bar = tqdm(range(num_training_steps))
model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
0%| | 0/87 [00:00<?, ?it/s]
6. Metrics
from datasets import load_metric
metric = load_metric("glue", "mrpc")
model.eval()
for batch in eval_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
logits = outputs.logits
predictions = torch.argmax(logits, dim=-1)
metric.add_batch(predictions=predictions, references=batch["labels"])
metric.compute()
/home/kubwa/anaconda3/envs/pytorch/lib/python3.11/site-packages/datasets/load.py:756: FutureWarning: The repository for glue contains custom code which must be executed to correctly load the metric. You can inspect the repository content at https://raw.githubusercontent.com/huggingface/datasets/2.18.0/metrics/glue/glue.py
You can avoid this message in future by passing the argument `trust_remote_code=True`.
Passing `trust_remote_code=True` will be mandatory to load this metric from the next major release of `datasets`.
warnings.warn(
{'accuracy': 0.8186274509803921, 'f1': 0.864963503649635}
7. 전체 코드
from datasets import load_dataset, load_metric
from transformers import AutoTokenizer, DataCollatorWithPadding, AutoModelForSequenceClassification, AdamW, get_scheduler
import torch
from torch.utils.data import DataLoader
from tqdm.auto import tqdm
# 데이터 셋 적재
raw_datasets = load_dataset("glue", "mrpc")
# 사전학습 언어모델 checkpoint 이름 지정
checkpoint = "bert-base-uncased"
# 지정된 사전학습 언어모델에서 토크나이저 인스턴스화
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
# 토크나이저 함수 사용자 정의화 (sentence1, sentence2 컬럼에 대해서만 토크나이징 수행)
def tokenize_function(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
# 토크나이징 수행
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
# 배치(batch)별 패딩(padding)을 위한 data collator 정의
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
# 불필요한 입력 컬럼을 제거하고 사전학습 언어모델에 필요한 입력만 남김.
tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"])
# 데이터셋의 label 컬럼명을 labels로 변경
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
# 데이터셋의 유형을 PyTorch tensor로 변경
tokenized_datasets.set_format("torch")
# 변경된 컬럼 출력
print(tokenized_datasets["train"].column_names)
# 각 종류별 데이터 로더 생성
train_dataloader = DataLoader(
tokenized_datasets["train"],
shuffle=True,
batch_size=256,
collate_fn=data_collator
)
eval_dataloader = DataLoader(
tokenized_datasets["validation"],
shuffle=True,
batch_size=256,
collate_fn=data_collator
)
# 사전학습 언어모델 인스턴스화
model = AutoModelForSequenceClassification.from_pretrained(
checkpoint,
num_labels=2
)
# 최적화 함수 정의
optimizer = AdamW(
model.parameters(),
lr=5e-5
)
# 에포크 개수 설정
num_epochs = 30
# 학습 스텝 수 계산
num_training_steps = num_epochs * len(train_dataloader)
# 학습 스케쥴러 설정
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps
)
# GPU로 모델을 이동
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
# 진행 상황바 정의
progress_bar = tqdm(range(num_training_steps))
# 모델을 학습 모드로 전환
model.train()
# 학습 루프 시작
for epoch in range(num_epochs):
for batch in train_dataloader:
# 현재 배치 중에서 입력값을 모두 GPU로 이동.
batch = {k: v.to(device) for k, v in batch.items()}
# 모델 실행
outputs = model(**batch)
# 손실값 가져오기
loss = outputs.loss
# 역전파 수행
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
# 평가 메트릭 가져오기
metric = load_metric("glue", "mrpc")
# 모델을 평가 모드로 전환
model.eval()
for batch in eval_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
logits = outputs.logits
predictions = torch.argmax(logits, dim=-1)
metric.add_batch(
predictions=predictions,
references=batch["labels"]
)
# 평가 결과 계산 및 출력
metric.compute()