SKNetworks Family AI 캠프 13기 11주차 회고

¿curious_jin¿·2025년 6월 16일

Family AI 캠프 SKN 13기

목록 보기
11/17

자연어에서 문맥의 정보를 처리하기위해 도입한 RNN은 그 구조 자체가 하나의 Layer를 계속해서 순환하여 사용하는 구조적인 문제로 데이터의 병렬 처리가 불가능하며 시간에 따른 장기 기억 손실 문제를 해결할 수 없었다.
LSTM이나 그 구조를 개선한 GRU의 경우에도 위 문제를 해결할 수 없었고.... 그래서 나온 것이 각 경우에 따라 별도의 중요도를 매긴다는 Attention 기법 이다.
이 기법만 똑 떼어온 것이 transformer, 오늘은 이 transformer를 다루는 Hugging Face에 대해 알아보자.

Hugging Face의 transformers 냅다 가져다 쓰기

Hugging Face에서는 transformers 모델에 관련된 라이브러리를 제공해준다. 사전 학습된 모델, 그 모델의 구조나 토크나이저, 혹은 직접 모델을 Fine Tuningtrainer 객체까지 이 라이브러리에 담겨있다.

from transformers import pipeline
pipe = pipeline(
	task='text-classification',
    # model=''
>>> No model was supplied, defaulted to distilbert/distilbert-base-uncased-finetuned-sst-2-english ~~
pipe.model
>>> DistilBertForSequenceClassification(~)
pipi.tokenizer
>>> DistilBertTokenizerFast(~)

모델의 특정 task 만을 지정해주었더니 알아서 기본 모델과 토크나이저를 모두 불러오는 모습이다... 너무 편한데?
실제로 사용도 해보자.

result = pipe('I am bored... getting tired')
result
>>> [{'label': 'NEGATIVE', 'score': 0.9998125433921814}]

Hugging Face에서는 이와 같이 텍스트 생성, 텍스트 요약, 번역 등 약 50개 가량의 task에 대해 백 만개가 넘는 모델을 제공해준다.

조금 더 하위레벨에서 이를 구경해보자.

Hugging Face의 AutoClass

자연어, 음성, 사진이나 비디오 등 비정형 데이터는 전처리기를 거쳐 수치화된 후 Embedding Vector로 변환, 이 값을 통해 각 모델은 Feature 를 추출해내며 이 Feature는 최종 Classifier를 거쳐 우리가 원하는 출력값으로 변환되게 된다.

Hugging Face에서는 이 일련의 과정들에 대해 AutoTokenizer, AutoProcessor, AutoImageProcessor, AutoFeatureExtractor 등 비정형 데이터 타입에 맞는 전처리기를 따로 불러올 수 있도록 해주며 AutoModel을 통해 전처리된 데이터의 Feature 를 추출하는 모델도 불러올 수 있도록 해준다.

AutoClass.from_pretrained( )

위 메소드는 Hugging Face에 존재하는 이미 사전학습이 완료된 전처리기와 모델을 가져오는 것이다. pipeline 과의 차이점이라고 한다면 이들을 따로 가져온다는 점....?

from transformers AutoTokenizer AutoModel
model_id = 'bert-base-uncased'
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id)
type(tokenizer)
>>> transformers.models.bert.tokenization_bert_fast.BertTokenizerFast
type(model)
>>> transformers.models.bert.modeling_bert.BertModel

참고로 Tokenizer 의 ~Fast 는 Rust 언어로 작성되어 더 빠른 전처리를 해주는 아이라고 한다.

그럼 이제 위 pipeline 에서 했던 작업을 step by step 으로 진행해보자.

# 토큰화
token_list = tokenizer(
	'I am bored... getting tired',
    return_tensors='pt'
)
token_list
>>> {'input_ids': [101, 1045, 2572, 11471, 1012, 1012, 1012, 2893, 5458, 102], 'token_type_ids': ~, 'attention_mask': ~}

# 임베딩 벡터
model.embeddings
>>> BertEmbeddings(
  (word_embeddings): Embedding(30522, 768, padding_idx=0)
  (position_embeddings): Embedding(512, 768)
  (token_type_embeddings): Embedding(2, 768)
  (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
  (dropout): Dropout(p=0.1, inplace=False)
)
model.config.vocab_size
>>> 30522
model.config.hidden_size
>>> 768
model.config.max_position_embeddings
>>> 512

# Feature Extraction
context_vector = model(**token_list)
help(context_vector)
>>> ~~ Base class for model's outputs that also contains a pooling of the last hidden states. ~~

help(model) 을 쳐보니 modeltorch.tensor 를 입력으로 받는다기에 return_tensors 변수를 설정해주었다.

tokenizer의 출력으로 나온 input_ids 는 각 토큰의 id, token_type_ids 는 각 토큰이 몇 번 째 문장인지, attention_mask 는 해당 토큰이 [PAD] 토큰인지를 알려주는 역할이다. 모두 함께 model 에 전달해주어야 올바른 출력이 나오므로 **<dict> 를 통해 전달해주었다.

AutoModel 로 생성된 객체는 내부에 model.embeddings 로 임베딩 벡터를 가지고 있다. 전체 어휘 사전 30522 개를 768 개의 차원을 가진 임베딩 벡터로 바꿈을 확인할 수 있었다. Attention 기법을 활용하기 위해서는 각 토큰이 문장에서 어디에 위치하는지에 대한 정보 역시 필요하므로 이를 position_embeddings 를 통해 별도로 계산함도 알 수 있었다.

context_vector.last_hidden_state.shape
>>> torch.Size([1, 10, 768])
context_vector.pooler_output.shape
torch.Size([1, 768])

이렇게 출력된 값은 Classifier 에 들어가는 last_hidden_state , 문장 전체의 의미를 압축한 벡터 표현인 pooler_output 값을 기본적으로 가지며 추가 설정에 따라 각 hidden_state, attention value 등의 정보를 담을 수 있다.

AutoClass.from_config( )

위에서 사전학습이 완료된 모델을 가져다 썼다면 이 메소드는 학습이 이루어지지 않은 랜덤하게 초기화된 가중치를 가진 모델을 불러오는 메소드이다. 단! 해당 모델의 구조나 하이퍼 파라미터만을 반영.

# 설정 불러오기 
from transformers import AutoConfig
model_id = 'bert-base-uncased'
model_class = 'bert'
config = AutoConfig.from_pretrained(model_id)
config
>>> BertConfig {
   "architectures": [
     "BertForMaskedLM"
   ],
   "attention_probs_dropout_prob": 0.1,
   "classifier_dropout": null,
   "gradient_checkpointing": false,
   "hidden_act": "gelu",
   "hidden_dropout_prob": 0.1,
   "hidden_size": 768,
   "initializer_range": 0.02,
   "intermediate_size": 3072,
   "layer_norm_eps": 1e-12,
   "max_position_embeddings": 512,
   "model_type": "bert",
   "num_attention_heads": 12,
   "num_hidden_layers": 12,
   "pad_token_id": 0,
   "position_embedding_type": "absolute",
   "transformers_version": "4.52.4",
   "type_vocab_size": 2,
   "use_cache": true,
   "vocab_size": 30522
 }
 config2 = AutoConfig.for_model(model_class)
 config2
 >>>  BertConfig ~~~ 동일한 값 

AutoConfig 를 통해 가져온 값은 hidden_size, num_hidden_layers, vocab_size 등과 같이 모델의 전체적인 구조를 알려주는 설정들이었다. 이를 통해 모델을 만들어보자

from transformers import AutoModel
model = AutoModel.from_config(config)
model
>>> BertModel(
  (embeddings): BertEmbeddings( ~ )
  (encoder): BertEncoder( ~ )
  (pooler): BertPooler( ~ )
  )

이렇게 만들어진 모델은 학습이 하나도 되어있지 않은 상태이므로 이 친구에게 무언가 일을 맡겼다가는 엉뚱한 결과만을 받게 될 것이다... !

추출된 Feature 를 해석하는 Classifier 까지 붙은 버전에 대한 내용과 AutoClass.from_pretrained( ) 를 통해 가져온 모델을 다른 datasets 를 이용해 파인 튜닝하는 과정에 대한 내용은 후에 추가하겠다.

그래서?

실제 task 를 해결하는 단계까지 먼 걸음이었다.

거기에서 오는 호기심과 즐거움은 이전에 비해 훨씬 커졌지만 동시에 GPU 의 필요성을 느끼게 된 한 주이기도 하다. 텍스트 생성 너무 오래 걸려....

최종 프로젝트까지 얼마 남지 않았다. 화이팅 !

profile
궁금증이 많은 아이

0개의 댓글