이 서평은 출판사로부터 도서를 지원받아 작성하였습니다.
쉬운 책은 아니다. 실무형 책이기 때문에 쉽고 재밌게 원리부터 차근차근을 기대하기란 어렵다. 만약 그 점을 기대한다면 책에 실망할 확률이 높다. 그렇다 해도 대략적으로 용어나 단어에 익숙해진다고 생각하고 '대충' 보는 것도 괜찮다.
ML 리서처도 아니고 ML 엔지니어도 아니라면 굳이 이 책에 있는 개념들과 원리를 모두 하나하나 깊이 있게 알 필요는 없기 때문이다. 오히려 그보다도 대충 어떤 라이브러리가 쓰이는지, 어떤 용어가 쓰이는지 잘은 몰라도 '아 저런 단어가 쓰이는구나. 경량화라는 게 저런 의미구나'라는 정도만 이해해도 무방하다고 생각한다. (반대의 경우인 ML 리서처나 ML 엔지니어의 경우 얘기가 다르겠지만 말이다. 반대의 경우 이 책에서 나오는 설명과 코드 수준을 넘어서 원리까지 이해하고 최신 트렌드도 따라가야 할 것이다)
이렇게 서술하는 이유는 이 글을 서술하는 나부터 잘 모르겠기 때문이다. 이 쪽 분야에서 일하는 사람도 아니고 전문적인 교육을 받은 것도 아니다보니 알아듣는 말도 아주 조금은 있지만, 많은 경우 알아먹지 못한 게 사실이다.
그래도 좋았던 건 저자가 설명해주는 개념들과 역사를 훑어보니 어떤 걸 유튜브에 검색해서 하나하나 재미삼아 알아보면 좋을지 보이긴 했다는 거다. 저자의 말대로 이미 개인의 수준을 넘어선 토크나이저를 사전학습 시키는 걸 할 것도 아니고, 아키텍처를 직접 구축할 것도 아니고 그럴 실력도 안 되니 그저 하나하나 등산한다 생각하며 아주 조금씩 관심을 가져보는 게 그나마 할 수 있는 최선이 아닐까.
밑줄긋기
p.11
허깅페이스는 초기에 자연어 처리를 기반으로 성장하였습니다. 오픈소스 라이브러리 Transformers는 초기에 자연어 처리 관련 모델만을 지원했습니다. 서서히 규모가 커지며 모델뿐 아니라 학습 및 서빙을 위한 구현체가 등장했고 오늘날에 와서는 이미지 및 오디오 모델까지 다양한 인공지능 분야를 지원하는 전천후 라이브러리로 거듭났습니다. Transformers라이브러리는 원래 pytorch-pretrained-bert라고 불렸고 이후 pytorch-transfomers, Transformers 순으로 이름이 변경되었습니다. 파이토치, 텐서플로, 구글 잭스 라이브러리와 호환되며 BERT와 GPT 등의 언어모델이 등장하면서 점차 성장하였습니다.
허깅페이스가 엄청난 인기를 끌어 크게 성장하게 된 계기는 바로 구글이 한 논문을 발표한 사건 때문입니다. 구글 브레인은 2017년 [Attention is All You Need] 논문을 발표합니다. 바로 오늘날 자연어 처리를 넘어 전 분야에서 광범위하게 쓰이는 셀프 어텐션(self-attention) 기반 모델인 트랜스포머(Transfomer)를 발표한 사건입니다.
2018년 트랜스포머 인코더(encoder) 기반인 구글에서 발표한 BERT 모델과 트랜스포머 디코더(decoder) 기반인 GPT 모델이 오픈소스로 공개되었고 BERT와 GPT를 시작으로 하여 사전학습 모델을 활용하는 것이 머신러닝 트렌드가 되며 사전학습한 모델을 활용하는 것이 머신러닝 트렌드가 되며 사전학습한 모델을 공유하는 라이브러리인 Transfomers가 주목받게 되었습니다. 이로 인해 개발자 사이에 폭발적인 인기를 끌게 되고 이 시기가 허깅페이스 성장의 기점이 됩니다.
p.26
허깅페이스 Datasets는 자연어 처리, 음성 처리, 컴퓨터 비전 작업을 위한 데이터셋을 공유하는 라이브러리입니다. 한 줄의 코드로 공개되어 있는 데이터셋을 불러오고 딥러닝 모델에 빠르게 활용할 수 있습니다. 참고로 허깅페이스는 모든 사람이 볼 수 있는 공개된 커뮤니티이기 때문에 보안 이슈에서 자유로운 뉴스나 위키피디아 등에서 쉽게 얻을 수 있는 일반적인 데이터를 공유하는 경우가 많습니다. 그래서 실제 자신만의 모델을 구축하기 위해 미세 조정을 진행할 때는 원하는 다운스트림(downstream) 태스크에 맞는 자체적으로 구축한 데이터를 활용하여 학습을 진행합니다.
p.27
한국어 자연어 이해 평가(Korean Language Understanding Evaluation, KLUE) 데이터셋을 활용해 보겠습니다. KLUE는 언어 모델의 한국어 능력을 평가하기 위한 데이터셋으로 총 여덟 가지 다양한 태스크로 구성되며 각 태스크는 다음과 같습니다.
1. 의존 구문 분석(Dependency Parsing, DP)
2. 기계 독해 이해(Machine Reading Comprehension, MRC)
3. 개체명 인식(Named Entity Recognition, NER)
4. 자연어 추론(Natural Language Inference, NLI)
5. 관계 추출 (Relation Extraction, RE)
6. 문장 유사도 비교 (Semantic Textual Similarity, STS)
7. 주제 분류 (Topic Classification, TC a.k.a YNAT)
8. 대화 상태 추적 (Dialogue State Tracking, DST a.k.a WoS)
p.37
이전 머신러닝과 과거 초기 신경망 모델에서는 형태소(명사, 조사, 접사, 어미 등 뜻을 가진 가장 작은 말 단위)를 기준으로 자연어를 토큰화했으며 이를 벡터화(vectorize) 또는 임베딩(embedding)하여 사용하였습니다. 그러나 형태소를 기반으로 토큰화하면 모든 형태소에 대한 단어사전(vocabulary)을 구축할 수 없기 때문에 지속적으로 업데이트해야 한다는 단점이 있습니다.
이런 방식을 보완하기 위해 최근에는 서브워드(subword - 단어보다 더 작은 의미 단위) 기반 토크나이저가 등장했습니다. 서브워드 토크나이저는 기존 형태소 기반 토크나이저보다 언어 간 독립성을 보장하고 사전 어휘 이외의 자연어(Out Of Vocabulary, OOV) 문제를 해결할 수 있으며, 신조어 처리 등 다양한 변수에 대응하기가 용이하다는 등 여러 장점이 있기에 최근 언어 모델에 쓰이는 토크나이저 방식입니다. 토큰화 방법은 주로 파일 압축 알고리즘을 토대로 구현된 바이트 페어 인코딩(Byte Pair Encoding, BPE), 센텐스피스(SentencePiece), 워드피스(WordPiece) 등 방식을 사용합니다. Transformers는 자주 사용되는 서브워드 토크나이저를 모아 구현했으며 파이썬에서 사용할 수 있는 라이브러리로 토크나이저 클래스를 지원합니다. 단, 예외로 SentencePiece는 외부 라이브러리를 따로 설치해 이용합니다. 모델과 함께 학습된 토크나이저를 손쉽게 다운로드할 수 있고 이를 활용해 자연어를 해당 모델이 인식할 수 있도록 빠르게 전처리할 수도 있습니다.
p.38
사전학습한 모델은 각각 단어사전이 존재합니다. 따라서 각 모델에 맞게 학습된 토크나이저를 필요로 하기에 모든 토크나이저 역시 PretrainedTokenizer를 상속받으며 사용하려는 사전 학습 모델과 동일한 repo_id로 호출해야 합니다.
p.40
토크나이저는 일반적인 토큰과는 다른 특수 토큰(special token)을 가집니다. 이는 컴퓨터가 인식하기 위한 말 그대로 특별한 토큰이며 special_tokens_map으로 해당 토크나이저의 특수 토큰을 확인할 수 있습니다. 대표적인 특수 토큰으로 각각 begins of sentence와 end of sentence를 뜻하는 <bos>, <eos> 토큰, 비슷한 역할인 [CLS], [SEP] 토큰 등이 있습니다.
p.41
일반적으로 두 개의 번호 기호(##)는 앞 토큰과 공백 없이 이어짐을 의미합니다. SentencePiece 토크나이저는 기본으로 ## 표시를 붙여서 이어짐을 표시하고 BPE 토크나이저는 토큰 앞부분에 밑줄 문자(언더바, _)를 붙여서 사용합니다.
p.42
대부분 인코더 기반 모델은 [CLS], [SEP]처럼 대문자를 대괄호로 감싸고 디코더 기반 모델에서는 <bos>, <eos>와 같이 소문자를 홀화살괄호로 감싸 사용합니다. 인코더-디코더 기반 모델은 두 가지 방법 중 하나로 혼용되는 편입니다. 대부분 해당 형식을 따르지만 꼭 정해진 것이 아니기 때문에 모델별로 특수 토큰을 확인하는 것이 가장 확실한 방법입니다.
p.45
DataCollator는 데이터셋 요소 목록을 입력으로 사용하여 배치를 형성하는 객체입니다. 학습할 때, 모델에 데이터를 입력하기 전 추가 처리를 진행하기도 합니다. 학습할 때 미니배치 단위로 데이터에 어떤 작업을 진행해야 할 때 많이 쓰이는데, 이를 처리하는 것을 콜레이터(Collator)라고 합니다. 이러한 요소는 train_dataset과 eval_dataset 모든 요소와 동일한 유형으로 사용합니다.
p.55
Trainer는 학습을 전체 관리하는 클래스입니다. TrainingArguments는 Trainer에서 사용하는 파라미터를 관리하는 데이터 클래스입니다. 또한 파이토치의 완전한 학습을 위한 API를 제공합니다. 한 가지 유의해야 할 점이라면 Trainer는 파이토치에서만 사용할 수 있습니다. 과거에는 텐서플로에서도 사용할 수 있는 TFTrainer가 있었지만 텐서플로는 자체 추상화 수준이 매우 높기에 사용하지 않게 되었습니다.
p.58
Pipeline을 활용하면 굉장히 쉽게 추론을 사용할 수 있습니다. Pipeline은 모델과 태스크를 입력하거나, 혹은 특정 태스크로 학습된 모델을 입력하면 전처리부터 결과를 내는 모든 과정을 한 번에 처리해주는 강력한 기능입니다. 모델을 다 학습했다 하더라도 원본 텍스트 데이터를 입력하여 모델이 추론하도록 사용하기 위해서는 모델과 토크나이저를 불러와서 기울기를 제거하거나 디바이스를 설정하고 텍스트 인코딩과 모델 디바이스를 설정한 후, 결과를 추론하고 보기 쉽게 결과를 후처리해야 비로소 사람이 편하게 식별 가능한 결과를 얻을 수 있습니다. 꽤나 번거로운 작업입니다. Pipeline은 이런 문제를 간단히 해결합니다. 원본 텍스트 데이터를 그대로 입력하면 서너 줄의 코드만으로 딕셔너리에 정리된 모델 추론 결과를 얻을 수 있습니다.
p.69
OOM 대처법
배치 사이즈는 늘리고 싶은데 메모리 공간이 부족한 경우 가중치 누적(Gradient Accumulation)을 활용할 수 있습니다. 배치 데이터는 batch_size개 데이털르 한 번에 입력받아 순전파(forward propagation)를 거치고 곧바로 역전파(backward propagation)도 배치 크기만큼 한 번에 진행합니다. 반면 가중치 누적을 사용하면 순전파 연산은 batch_size개만큼 한 번에 연산을 진행하지만 이를 바로 역전파에 업데이트하지 않습니다. gradient-accumulation값만큼 순전파를 진행한 후 실행 결과를 통합해 한 번에 역전파 및 업데이트를 진행합니다. 즉, 실질적인 batch_size 크기가 batch_size * gradient_accumulation이 됩니다. 이 방법은 GPU 메모리가 작을 때 유용하게 사용할 수 있으나 속도가 느릴 수 있다는 단점이 있습니다.
p.88
챗GPT와 다수의 오픈소스 생성형 언어 모델이 떠오른 2023년부터 대규모 언어 모델(LLM)이 생성형 언어 모델의 주류가 되었습니다. 개인이 토크나이저부터 사전학습을 구축하는 사례는 흔히 찾기 어려우며 모델 및 토크나이저 사전학습을 위한 데이터를 구축하는 일 자체가 개인으로서는 불가능한 수준에 도달했습니다. 그럼에도 불구하고 해당 내용을 작성하는 이유는 토크나이저 또한 언어 모델의 일부로써 필수적인 요소로 기능하기 때문입니다. 소속된 직장이나 연구실 등 어떤 곳에서나 대규모 학습이 필요할 때 도움이 될 수 있길 바랍니다.
지금까지 Transformers 라이브러리를 이용해 모델과 함께 사전학습된 토크나이저를 불러와 활용했습니다. 사전학습된 모델을 미세조정하는 과정에서는 모델과 함께 학습된 토크나이저를 그대로 사용하면 되지만, 사전학습 단계부터 직접 진행해야 하는 경우에는 토크나이저 또한 함께 학습해야 했습니다. Tokenizers 라이브러리는 그러한 상황에서 간단한 코드 몇 줄로 서브워드 토크나이저를 학습할 수 있도록 하며, 이를 Transformers 라이브러리에서도 사용할 수 있도록 변환하는 등 다양한 기능을 제공합니다. Tokenizers 라이브러리의 또 다른 특징은 러스트(Rust) 언어로 구현되었다는 점입니다. 파이썬이란 언어는 쉬운 문법과 넓은 확장성으로 정말 많은 분야에서 사용됩니다. 굉장히 강력한 장점이지만 모두가 인정하는 한 가지 단점이 존재하는데, 바로 속도가 느리다는 점입니다. 허깅페이스에서는 이 점에 집중하여 매우 빠른 저수준 언어 러스트를 사용해 빠른 속도로 토큰화를 진행할 수 있돌고 처리했습니다.
p.109
트랜스포머 모델이 등장한 이후 대부분의 모델이 트랜스포머 모델 인코더나 디코더 중 하나만 사용하거나 둘 다 사용하는 형태로 구성되기 시작합니다. 2018년 6월, 트랜스포머 디코더 구조를 사용한 최초의 사전학습 모델인 GPT가 제안되었습니다. GPT 모델은 대규모 데이터로 미리 학습된 최초의 트랜스포머 기반 모델입니다.
p.111
인코더 기반 모델은 트랜스포머 모델의 인코더 부분만 사용하는 모델입니다. 인코더 기반 모델은 대부분 문장 분류나 토큰 분류와 같은 비교적 연산이 단순한 태스크에 사용됩니다. 사전학습을 진행한 후 임베딩 레이어와 트랜스포머 인코더 부분만을 저장하며 태스크에 따라 모양에 맞는 헤더를 부착해 미세조정합니다. 예를 들어, 문장 분류에서는 한 토큰의 벡터 정보만 가져다 선형 레이어를 부착해 사용하고 토큰 분류에서는 토큰마다 완전 연결층을 연결해 토큰별로 어떤 품사인지 분석하는 방식 등이 존재합니다.
인코더 기반 모델은 완성된 문장을 입력받아 이를 이해하는 것을 목적으로 하기에 이를 자연어 이해라고도 부릅니다. 주로 문장 분류 등 비교적 간단한 작업에서 사용합니다. 인코더 모델에는 BERT, RoBERTa, XLM, ELECTRA 등이 있습니다. BERT 이후 제안된 RoBERTa는 BERT 사전학습 전략을 보완하여 전처리 중 최초 한 번이 아닌 에포크마다 토큰을 무작위로 마스크하는 동적 마스킹을 제안해 기존 방법론을 개선하였습니다. 이후에는 대규모 모델을 학습하는 대신 달느 전략으로 압축 기술인 지식 증류를 사용하여 성능을 거의 유지하면서 크기를 줄인 DistilBERT가 출시되었고, 큰 어휘 임베딩을 두 개의 작은 행렬로 분리하고 레이어악 파라미터를 공유하도록 학습 효율성에 초점을 맞춘 ALBERT, 단어와 위치가 두 개 벡터로 인코딩되어 분리되는 어텐션 메커니즘이 추가된 DeBERTa 등이 제안되었습니다.
p.112-113
- 토큰 임베딩(token embeddings) : 우리가 알고 있는 기본 임베딩 층입니다. 각 토큰은 정해진 크기의 실수 벡터로 변환됩니다. 기본 크기 BERT-Base 모델은 768의 길이를 가집니다.
- 세그먼트 임베딩(segment embeddings): BERT 모델은 사전학습을 진행할 때 두 문장을 입력받아 예측하는 방법이 포함됩니다. 세그먼트 임베딩은 입력된 두 문장을 구분하기 위해 사용하는 임베딩으로, 각 문장에 각기 다른 벡터값을 적용합니다. Transformers 라이브러리에서는 token_type_ids라는 이름으로 사용됩니다. 단, 모델에 따라 사용하지 않는 경우도 있습니다.
- 포지션 임베딩(position embeddings): 문장 순서 정보를 인식시키기 위해 사용됩니다. 순서대로 모델에 입력되는 순환신경망(RNN)과 달리 셀프 어텐션 방법은 여러 토큰을 동시에 연산하기에 모델이 토큰 순서 정보를 이해할 수가 없습니다. 이를 위해 사용되는 방법으로, 토큰 위치에 따라 다른 벡터값을 추가해 순서정보를 인식시킵니다.
한 가지 유의할 점은 기본 트랜스포머 모델은 위치 인코딩을 사용한다는 것입니다. 위치 임베딩이 위치에 따른 임베딩 값을 학습 간으한 파라미터로 처리하는 반면 위치 인코딩의 임베딩 값은 변화하지 않는 상수 값입니다. BERT 이후의 모델들은 위치 임베딩을 기반으로 위치 정보를 전달하지만 위치 정보를 입력하는 부분은 모델마다 상이합니다. 각 모델의 차이를 자세하게 알고 싶다면 위치 정보를 입력하는 부분을 유심히 살펴보기 바랍니다.
BERT 모델은 사전학습을 위해 다음 문장 예측(Next Sentence Prediction, NSP)과 마스킹된 언어 모델 (Masked Language Model, Masked LM)이라는 두 가지 방법을 제안합니다. 그 중 NSP는 모델에 두 문장 A 와 B가 주어졌을 때 어떤 문장이 앞선 문장인지를 예측하는 이진 분류 태스크입니다. 두 문장의 구분을 위해 앞서 언급한 세그먼트 임베딩을 사용합니다. Masked LM은 앞서 간략히 설명했듯 입력 토큰 일부를 [MASK] 토큰으로 치환하여 입력한 후, 바뀐 토큰이 원래 무엇인지를 추론하는 태스크입니다. 해당 치환 과정을 마스킹 혹은 오염(corruption)이라 표현하며, BERT 논문에서는 마스킹이란 표현을 사용합니다. 잘 쓰이지 않는 NSP와 달리 꽤 오랜 기간 사용되었던 학습 방법입니다.
p.113
문장 분류 태스크는 2차원 문장 임베딩에서 여러 클래스 중 하나만을 고르는 1차원 확률분포를 반환합니다. 이를 위해 모델은 고차원 데이터를 저차원 데이터로 압축하는 풀링(pooling) 작업을 진행합니다. 풀링에는 여러 가지 방법이 있는데 단순히 특정 차원축의 모든 벡터값을 합하거나(reduce-sum) 혹은 평균(reduce-mean)내어 사용하는 등의 방법이 있습니다.
트랜스포머 인코더 모델은 문장의 모든 정보를 하나의 토큰 벡터에 저장하도록 학습하며 해당 토큰이 바로 문장 가장 앞에 자리한 [CLS] 토큰입니다. [CLS] 토큰은 Classification을 뜻하며 문장의 맨 앞에 삽입되어 분류 태스크에 사용되거나 문장 시작(Begin Of Sentence, BOS)을 알리는 토큰으로 사용되고, 나머지 태스크에서는 대부분 무시됩니다. 전술한 바와 같이 분류 태스크에 한하여 문장 전체 정보를 [CLS] 토큰에 담도록 학습하며, [CLS] 토큰 벡터를 입력으로 받는 순방향 신경망(FeedForward Neural Network, FFNN)을 추가로 부착해 미세조정을 진행합니다.
p.136
질의응답(Question-Answering, QA) 태스크는 모델이 질문에 대해 답변을 하게 만드는 태스크로 주어진 문장 텍스트와 질문을 모델에 입력해 그에 대한 답변을 하도록 합니다. 또한, 컴퓨터가 질문에 대한 답변을 하도록 하는 기계 독해 이해(Machine Reading Comprehension, MRC) 작업 하위 카테고리로 분류할 수 있으며 주어진 정보를 기반으로 답변을 하는 특성으로 인해 정보 검색, 대화형 시스템, 지식 기반 시스템 등 다양한 응용 분야에 유용하게 활용될 수 있습니다.
질문에 대한 답변을 만드는 과정은 추출(extractive)과 생성(generate) 두 가지로 나뉩니다. 추출 질의 응답은 질문에 대한 답변을 입력된 콘텍스트에서 말 그대로 추출해내는 방식이고 생성 질의 응답은 문제에 대한 답을 입력 콘텍스트를 참고하여 새로 작성하는 방법입니다.
- 추출: 주어진 콘텍스트에서 답변을 추출합니다.
- 생성: 질문에 정확하게 답하는 맥락에서 답을 생성합니다.
일반적으로 BERT와 같은 인코더 기반 모델은 추출 방법처럼 사실을 기반으로 답변하는 데에는 강한 모습을 보이지만 개방형 질문에 대해 왜 그런지 답하는 문장 생성 기반 작업에서는 비교적 취약한 모습을 보입니다. T5와 같은 인코더-디코더 모델, 혹은 GPT 같은 디코더 기반 모델은 인코더 모델에 비해 해당 작업에 보다 더 좋은 결과를 추출합니다.
p.142-143
디코더 기반 모델
트랜스포머 아키텍처에는 인코더만으로 구성된 모델뿐만 아니라 디코더만으로 구성된 모델도 존재하며, 오히려 인코더 기반 모델보다 디코더 기반 모델에 훨씬 더 다양한 종류의 모델이 속해 있습니다. 특히 GPT 모델은 디코더 모델의 대표 예시로, 현시점에 가장 영향력 있는 인공지능 기업이라 할 수 있는 오픈AI에서 고안되었습니다. GPT 모델은 BERT 모델과는 다르게 디코더 구조로 이루어져 있으며 이후에 대규모 언어모델(LLM) 시대를 이끈 챗GPT 모델의 기반이 되었습니다. 또한 인코더 기반 BERT와 디코더 기반 GPT는 트랜스포머 각각의 구조를 활용하여 수많은 거대 데이터를 학습하여 사용하는 사전학습 모델 개념이 시작된 모델이기도 합니다.
이번 섹션에서는 디코더 기반 모델에 대해서 알아보려고 합니다. 디코더 기반 모델은 문장 앞부분 일부만을 입력받아 이를 이어서 작성하는 형태이며 이를 자연어 생성이라고도 말합니다. 주로 문장 자동완성 등 복잡한 작업에서 사용하고 GPT-1~4, PaLM, BLOOM, MT-NLG, LaMDA, LLaMA 등이 있습니다.
디코더 기반 모델의 대표인 GPT 모델은 디코더로 구성되어 입력 시퀀스에서 이전 단어를 기반으로 다음 단어를 예측하는 방식으로 학습합니다. 이런 방식으로 GPT 모델은 문맥을 이해하고 문장을 생성하며 이를 통해 대화 응답, 텍스트 생성, 내용 요약 등 다양한 자연어 처리 작업을 수행합니다. GPT-1은 2018년에 처음 발표되었고 영어 위키피디아와 뉴스 기사로 학습하여 파라미터 수 약 1.17억 개(117M)로 시작하였습니다. 이후 학습하는 데이터 크기와 모델 파라미터 개수, 즉 모델의 크기가 커질수록 성능이 대폭 향상된다는 것이 정설로 자리잡으며 인터넷에 있는 대용량 웹페이지 텍스트를 학습시키게 되었고 GPT-2는 파라미터 수 약 15억 개(1.5B), GPT-3는 파라미터 수 약 1,750억 개(1.75T)로 발전해왔습니다. GPT도 구체적으로 보면 내부 언어 이해가 어떤 방식으로 진행되는지에 대해 설명이 모호한 점이 있습니다. 하지만 데이터와 모델의 크기가 커짐에 따라 추가 미세조정을 하지 않아도 명령문(Prompt)과 샘플 데이터 몇 개(few-shot) 혹은 샘플 데이터 없이(zero-shot) 추론할 수 있는 방향으로 발전을 거듭하고 있습니다.
기본 구조
디코더 기반 모델은 이름 그대로 트랜스포머 모델 디코더 부분만을 분리해 사용하는 구조입니다. 셀프 어텐션, 인코더 참조 어텐션, FFNN으로 이루어진 기존의 트랜스포머 디코더에서 인코더 참조 부분을 제거하여 두 단계로 구성됩니다. 자신을 참조하는 셀프 어텐션을 거치고 인코더와 동일하게 FFNN 층을 거칩니다. 이를 여러 개 레이어로 층층이 쌓은 형태의 모델이 바로 디코더 기반 모델입니다. 인코더 기반 모델의 주요 태스크는 완성된 문장을 분석하는 것이기에 이미 완성된 문장이 입력된다는 전제하에 문장의 시작부터 끝까지 한 번에 분석을 진행합니다. 이에 반해 디코더 기반 모델의 주요 태스크는 미완성의 문장을 이어서 작성하는 생성 태스크입니다. 미완성 상태로 작성 중인 문장을 실시간으로 확인하며 직접 이어 나가야 하기에 문장 일부만으로 자연스럽게 다음 단어를 예측하는 단방향 형태로 분석을 진행합니다.
p.144
인과적 언어 모델(Causal LM)은 디코더 기반 언어 모델의 시작과 끝이라고 할 수 있는 생성 태스크입니다. 생성 태스크는 앞서 디코더 기반 모델을 설명했던 바와 같이 주어진 문장을 토대로 다음에 쓰일 토큰을 예측하는 모델입니다. 완성되지 않은 문장을 이어나갈 수도 있고 완전한 문장을 입력받더라도 같은 주제로 계속해서 유연하게 답을 작성할 수도 있습니다. 심지어 정해진 양식에 따라 대규모 데이터셋을 활용하여 학습한다면 모델과 자유롭게 대화할 수 있는 챗봇이 될 수도 있습니다. 그야말로 무궁무진한 가능성을 지닌 태스크라고 할 수 있습니다. 허깅페이스에서는 이전 토큰에 따라서 다음 토큰이 결정되기에 이를 인과적 생성(Causal Generation)이라 부릅니다.
p.157
인코더 기반 모델과 디코더 기반 모델의 가장 큰 차이는 입력 방식입니다. 이는 모델 구조에도 영향을 끼치는 문제인데, 인코더 기반 모델은 이미 완성된 문장을 입력받는 구조이므로 어텐션 과정에서 모든 토큰이 다른 모든 토큰에 영향을 끼칠 수 있습니다. 그에 반해 디코더 기반 모델은 완성되지 않은 문장을 입력받는 것을 전제로 하며 이로 인해 다음에 출력된 토큰은 이전 순서 토큰에 영향을 줄 수 없습니다. 인코더 기반 모델은 문장을 양방향으로 분석하며 디코더 기반 모델은 단방향으로 분석합니다.
분석 방법의 차이로 인해 디코더 기반 모델로 문장 분류와 같은 태스크를 수행할 때 인코더 모델과 동일하게 처리하면 애로사항이 발생합니다. 디코더 모델은 이전 토큰의 영향을 받는 가장 마지막으로 입력된 토큰 벡터만을 가지고 추론을 진행해야 하는데, 문제는 모델에 입력되는 문장 길이가 모두 달라 여러 개를 동시에 처리하기가 어렵다는 것입니다. 이를 해결하기 위해 패딩 토큰을 사용해 배치로 입력되는 데이터는 길이를 맞추어야 합니다.
p.160-161
인코더-디코더 기반 모델
BERT와 같은 인코더 기반 모델, GPT와 같은 디코더 기반 모델을 보면 한 가지 의문이 생길 수 있습니다. 기초가 되는 트랜스포머 모델은 인코더-디코더 기반 모델인데 굳이 인코더나 디코더 둘 중 하나의 구조인 모델을 사용할 이유가 있을까? 기본 트랜스포머 구조와 같이 둘 다 사용하면 안 되는 것일까? 답은 '상관없다'입니다. 인코더-디코더 기반의 기본 형태를 유지한 채로 높은 성능을 낼 수 있는 모델도 존재하며 이를 대표하는 모델이 있습니다. 이번 섹션에서는 트랜스포머 인코더-디코더 기반 모델에 대해 알아보겠습니다.
기본 구조
트랜스포머 모델의 구조와 동일하게 인코더-디코더 모델에서도 셀프 어텐션 기반 인코더와 디코더가 사용됩니다. 다른 점이라면 기본 트랜스포머 성능을 높이기 위해 더 많은 수의 학습 파라미터를 사용한 것, 그리고 다양한 태스크에서 효과적인 성능을 낼 수 있도록 다른 활성화 함수 등 추가 방법론을 도입한 것입니다.
인코더-디코더 기반 모델은 완성된 문장을 입력받아 입력과는 완전히 다른 새로운 문장을 생성하는 것을 목적으로 합니다. 디코더 기반 모델의 자연어 생성과 비슷하지만 입력된 문장을 이어 나가는 디코더 기반 모델과는 달리 완전히 새로운 문장을 작성한다는 차이가 있습니다.
인코더-디코더 모델은 주로 번역 등 태스크에서 사용합니다. 대표 모델로 BART, T5, Marian 등이 있습니다.
BART 모델은 Bidirectional Auto-Regressive Transformer, 즉 양방향 자동 회귀 트랜스포머의 약자로 말 그대로 GPT 같은 생성 태스크를 진행할 때 입력을 BERT 방식과 같이 양방향으로 분석합니다. NSP와 Masked LM을 사용한 BERT 모델, Masked LM과 비지도 학습을 사용한 GPT 모델과 달리 BART 모델은 원본 데이터를 여러 가지 방법으로 오염시키고 이를 복구해 다시 생성하는 방식으로 학습을 진행합니다. 데이터를 오염시키는 방법은 다음과 같습니다.
- 토큰 마스킹(Token Masking): BERT에서도 사용했던 일반적인 Masked LM과 동일합니다.
- 토큰 삭제(Token Deletion): 랜덤한 토큰을 삭제하고 이를 복구합니다. 마스킹 방법은 특정 토큰을 [mask] 토큰으로 변경했던 반면, Deletion 방법은 말 그대로 특정 토큰을 랜덤으로 삭제하기 때문에 어떤 위치의 토큰이 사라졌는지 알 수가 없습니다.
- 텍스트 채우기(Text Infilling): 입력 문장 중, 연속되는 토큰 몇 개를 묶어 토큰 뭉치(text span)를 생성해 이 범위를 [mask] 토큰으로 치환합니다. 이때, 토큰 뭉치 길이는 포아송 분포를 따르며 길이가 0일 수도 2 이상일 수도 있습니다. 길이가 0인 경우 정상 문장에서 [mask] 토큰만 생성되고 2 이상인 경우 여러 토큰이 하나의 [mask] 토큰으로 바뀌게 됩니다. 따라서 모델이 범위에서 누락된 토큰 수에 대해서도 학습할 수 있도록 합니다.
- 문장 순서 바꾸기(Sentence Permutation): 입력 문서를 문장 단위로 분할한 후, 문장의 순서를 무작위로 섞어버립니다.
- 문서 회전(Document Rotation): 입력 문장 중, 토큰 하나를 무작위로 정해 해당 토큰이 문장의 시작이 되도록 해당 문장 토큰을 밀어냅니다. 시작 토큰 앞에 있던 토큰은 문장 맨 뒤로 이동합니다.
여러 가지 방법으로 데이터를 오염시킨 후 모델이 이를 복원하는 방식으로 사전학습을 진행하여 낮은 품질의 데이터에서도 효과적으로 문장을 생성할 수 있도록 진행합니다.
p.162
인코더-디코더 기반 모델은 트랜스포머 모델을 기반으로 합니다. 트랜스포머 모델이 번역을 목적으로 고안된 만큼 해당 계열 모델 역시 주된 태스크는 생성, 그 중에서도 번역과 같이 새로운 문장을 작성하는 것입니다. 어떤 문장이 주어졌을 때 해당 문장을 기반으로 새로운 문장을 작성하는 태스크를 허깅페이스에서는 조건부 생성(Conditional Generation)이라는 이름으로 부릅니다. 디코더 기반 모델의 인과적 생성과 같이 일반적인 상황에서 다수 개발자가 따로 구분하지 않고 생성이라 부르기 때문에 여기에서도 '생성'이라는 명칭을 사용하겠습니다.
인코더-디코더 기반 모델에서의 생성은 디코더 기반 모델 생성과는 달리 입력 문장과 직접 연결되는 방식이 아닙니다. 다시 말해 이어서 쓰는 게 아니라 질문에 대한 답변에 좀 더 가깝다고 할 수 있습니다. 일반적으로는 입력 문장을 다른 언어로 번역하는 기계 번역이나 긴 문서를 짧은 문장으로 요약하는 요약 태스크에 주로 사용합니다.
p.214
2020년 GPT-3를 기준으로 이후에 제안된 모델의 파라미터 개수는 이전에 비해 기하급수적으로 증가했고 최근 대규모 언어 모델이라고 불리는 LLaMA, Alpaca, Mistral, Gemma 등 모델은 파라미터 수가 최소 20억 개(2B)에서 시작하여 많게는 4,050억 개 (405B)라는 어마어마한 크기를 가지고 있습니다.
모델 크기와 성능이 선형적인 관계를 가진다는 트랜스포머 기반 모델 특성상 기반이 되는 모델 아키텍처 자체가 바뀌지 않는 이상 크기 증가는 필연적인 일이었습니다. 또한 아무리 제로샷(Zero-shot) 러닝, 퓨샷(Few-shot) 러닝, 프롬프트 엔지니어링 등 기법이 대두되고 발전해도 사전학습된 모델을 사용하는 데이터 도메인에 맞춰진 미세조정이 확연히 높은 성능을 보이는 것이 명백한 사실입니다. 모델 크기가 증가함에 따라 이를 학습하고 추론할 때 연산량과 사용되는 자원량 또한 무시하지 못할 정도로 커지게 되었고 이는 곧 하드웨어에 가해지는 부담으로 직결되었습니다. 모델은 점차 발전하는데 정작 이를 활용하지 못하는 상황이 발생하게 되자, 연구 방향도 "어떻게 성능을 끌어올릴 수 있는가"와 더불어 "어떻게 모델을 효율성 높게 사용할 수 있는가"에 초점을 맞춰 모델 크기 최적화, 모델 경량화라는 주제에도 집중하는 결과를 낳았습니다.
p.215
PEFT(Parameter-Efficient Fine-Tuning)는 의미 그대로 파라미터를 미세조정하는 효율을 높이는 방법론이자 동명의 라이브러리입니다. 사전학습된 대규모 모델의 파라미터를 좀 더 효율적으로 적용하기 위한 개념입니다. 사전학습된 모델을 미세조정하는 것은 굉장한 시간과 비용을 발생시킵니다. PEFT 방식을 활용하면 사전학습 모델의 모든 파라미터가 아닌 소수 추가 파라미터만으로 다양한 다운스트림 태스크를 진행합니다.
좀 더 자세히 설명하자면 사전학습 모델 대부분의 파라미터를 동결(freeze)시켜 업데이트가 되는 것을 막고 소량의 추가 파라미터만 학습하여 저장공간과 계산 비용을 대폭 줄여 미세조정과 유사한 성능을 내도록 하는 것이 PEFT의 목표입니다. PEFT 방식을 활용하면 모델 전체 파라미터를 미세조정할 때 발생하는 치명적인 망각(Catastrophic Forgetting) 문제를 피할 수 있고 학습할 데이터가 적은 경우에는 전체 파라미터 미세조정보다 더 나은 성능을 보였습니다. 또한, 이러한 방식을 통해 한정된 컴퓨팅 자원에서 활용하기 어려운 비교적 큰 크기의 LLM을 적은 계산 비용으로 학습하고 저장할 수 있게 됩니다. 결론적으로 PEFT 방식을 활용하여 미세조정을 한 경우 학습한 파라미터는 단 몇 MB에 불과하지만 전체 미세조정에 필적하는 성능을 얻을 수 있습니다.
p.230
양자화(Quantization) 방법은 모델 경량화 기법 중 하나로 모델 가중치의 데이터 타입을 용량이 적은 타입으로 변경하여 모델 메모리 사용을 줄이고 추론 속도를 가속화하는 데 큰 영향을 미치는 유용한 기술입니다. 너무 큰 규모 모델은 미세조정뿐 아니라 추론마저도 자원이 부족한 환경에서는 쉬운 일이 아닙니다. 양자화 기술을 사용하여 한정된 자원 환경에서도 큰 규모 모델을 사용할 수 있도록 모델 자체의 용량을 줄일 수 있습니다.
양자화란 많은 비트를 사용하는 데이터 타입을 적은 비트를 사용하는 데이터 타입으로 변경시키는 것을 말합니다. 예를 들어, 대부분 모델의 파라미터는 32비트 부동소수점(float32)으로 저장되는데 이 파라미터 타입을 16비트 부동소수점(flaot16)로 변경할 수 있습니다. 해당 방법을 사용하면 모델이 메모리상 차지하는 용량을 줄일 수 있을 뿐 아니라 연산 속도도 빨라질 수 있기에 추론 속도의 개선을 기대할 수 있습니다. 단, 32비트 파라미터를 16비트로 바꾸는 작업이기에 당연히 정보 손실이 일어나며 이로 인해 성능이 저하되므로 성능과 가용 자원 사이에서 적절한 중간점을 찾아야 합니다.
일반적으로 모델을 양자화하면 추가 다운스트림 태스크를 수행하지 않습니다. 그러나 PEFT 어댑터를 양자화 기술과 동시에 사용하면 이를 좀 더 유연하게 관리할 수 있으며 큰 규모 모델을 효율적으로 학습할 수 있습니다.
p.237
fp16과 bf16 모두 실수를 표현하는 16비트 데이터 타입입니다. 부동소수점 실수를 사용할 때는 메모리를 부호(+-), 지수부(exponent), 가수부(mantissa) 세 가지로 나누어 사용합니다.
32비트를 사용하는 fp32는 부호 1, 지수부 8, 가수부 23비트로 이루어집니다. 16비트인 fp16의 경우 부호 1, 지수부 5, 가수부 10비트로 나뉩니다. 지수부가 줄어들었으므로 값의 범위 또한 좁아지게 됩니다. 표현력이 떨어지므로 그만큼 성능이 줄어들게 됩니다.
bf16은 동일하게 16비트로 이루어졌지만 비트 1, 지수부 8, 가수부 7로 이루어집니다. fp16보다 지수부가 많기 때문에 더 넓은 범위의 값을 가질 수 있지만, 가수부가 적기 때문에 세부적인 정밀도가 떨어지게 됩니다.
fp16과 bf16 모두 32비트의 값을 압축하는 형태이기에 정보 손실은 필연적입니다. 다만 인공신경망 모델은 가수부보단 지수부의 크기로 인한 범위에 더욱 민감하기에 일반적으로 bf16 타입을 많이 사용하는 추세입니다.