1. huggingface의 pretrained 모델
1.1 원본 소스 링크 github
- 링크
- 복잡하고…뭐가 굉장히 많습니다.
- 중요한 부분만 살펴볼까요!
1.2 우리가 호출하는 모델의 정체는?
-
먼저 우리는 huggingface의 pretrained 모델을 불러올 때 아래와 같이 사용합니다.
mymodel = RobertaForSequenceClassification.from_pretrained('원하는 pretrained 모델 이름')
-
굉장히 간단하다. 하지만 그만큼 우리가 custom할 수 있는게 많이 없습니다. 아니, 어떻게 접근해야할지 감이 오지 않는 다고 하는 것이 맞을 것 같습니다. 하지만, mymodel을 출력해보면 뭔가 있숙한 구조가 나오는 것을 알 수 있습니다(너무 길어서 중간에 생략을 좀 했습니다.). 일반적으로 pytorch 모델을 설계하고 출력하면 나오는 형태와 유사합니다. 이제 막 custom해볼 수 있을 것 같은 용기가 솟아나는 순간입니다.
print(mymodel) RobertaForSequenceClassification( (roberta): RobertaModel( (embeddings): RobertaEmbeddings( (word_embeddings): Embedding(32000, 768, padding_idx=1) (position_embeddings): Embedding(514, 768, padding_idx=1) (token_type_embeddings): Embedding(1, 768) (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) (encoder): RobertaEncoder( (layer): ModuleList( (0): RobertaLayer( (attention): RobertaAttention( (self): RobertaSelfAttention( (query): Linear(in_features=768, out_features=768, bias=True) (key): Linear(in_features=768, out_features=768, bias=True) (value): Linear(in_features=768, out_features=768, bias=True) (dropout): Dropout(p=0.1, inplace=False) ) (output): RobertaSelfOutput( (dense): Linear(in_features=768, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) (intermediate): RobertaIntermediate( (dense): Linear(in_features=768, out_features=3072, bias=True) ) (output): RobertaOutput( (dense): Linear(in_features=3072, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) ... (생략) (11): RobertaLayer( (attention): RobertaAttention( (self): RobertaSelfAttention( (query): Linear(in_features=768, out_features=768, bias=True) (key): Linear(in_features=768, out_features=768, bias=True) (value): Linear(in_features=768, out_features=768, bias=True) (dropout): Dropout(p=0.1, inplace=False) ) (output): RobertaSelfOutput( (dense): Linear(in_features=768, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) (intermediate): RobertaIntermediate( (dense): Linear(in_features=768, out_features=3072, bias=True) ) (output): RobertaOutput( (dense): Linear(in_features=3072, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) ) ) ) (classifier): RobertaClassificationHead( (dense): Linear(in_features=768, out_features=768, bias=True) (dropout): Dropout(p=0.1, inplace=False) (out_proj): Linear(in_features=768, out_features=30, bias=True) ) )
2. 원본 소스 분석
2.1 RobertaForSequenceClassification forward 함수 살펴보기
- 이제는 이 모델이 어떤 출력을 내보낼지 예상할 수 있습니다. 이 모델은 classification을 위한 모델로, 마지막 classifier에서 클래수 개수 만큼의 logits를 출력으로 돌려줍니다. 하지만 조금더 살펴보아야 할 점이 있습니다. forward의 정의를 한번 더 살펴 보도록 해요!
-
((loss,) + output) if loss is not None else output 이 최종 ouput입니다. loss는 사실 왜 벌써 구하는지 모르겠다. 이건 전체 pipe line을 뜯어봐야 알 것 같습니다.
class RobertaForSequenceClassification(RobertaPreTrainedModel): _keys_to_ignore_on_load_missing = [r"position_ids"] def __init__(self, config): super().__init__(config) self.num_labels = config.num_labels self.config = config self.roberta = RobertaModel(config, add_pooling_layer=False) self.classifier = RobertaClassificationHead(config) self.init_weights() @add_start_docstrings_to_model_forward(ROBERTA_INPUTS_DOCSTRING.format("batch_size, sequence_length")) @add_code_sample_docstrings( tokenizer_class=_TOKENIZER_FOR_DOC, checkpoint=_CHECKPOINT_FOR_DOC, output_type=SequenceClassifierOutput, config_class=_CONFIG_FOR_DOC, ) def forward( self, input_ids=None, attention_mask=None, token_type_ids=None, position_ids=None, head_mask=None, inputs_embeds=None, labels=None, output_attentions=None, output_hidden_states=None, return_dict=None, ): r""" labels (:obj:`torch.LongTensor` of shape :obj:`(batch_size,)`, `optional`): Labels for computing the sequence classification/regression loss. Indices should be in :obj:`[0, ..., config.num_labels - 1]`. If :obj:`config.num_labels == 1` a regression loss is computed (Mean-Square loss), If :obj:`config.num_labels > 1` a classification loss is computed (Cross-Entropy). """ return_dict = return_dict if return_dict is not None else self.config.use_return_dict outputs = self.roberta( input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids, position_ids=position_ids, head_mask=head_mask, inputs_embeds=inputs_embeds, output_attentions=output_attentions, output_hidden_states=output_hidden_states, return_dict=return_dict, ) sequence_output = outputs[0] logits = self.classifier(sequence_output) loss = None if labels is not None: if self.config.problem_type is None: if self.num_labels == 1: self.config.problem_type = "regression" elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int): self.config.problem_type = "single_label_classification" else: self.config.problem_type = "multi_label_classification" if self.config.problem_type == "regression": loss_fct = MSELoss() if self.num_labels == 1: loss = loss_fct(logits.squeeze(), labels.squeeze()) else: loss = loss_fct(logits, labels) elif self.config.problem_type == "single_label_classification": loss_fct = CrossEntropyLoss() loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) elif self.config.problem_type == "multi_label_classification": loss_fct = BCEWithLogitsLoss() loss = loss_fct(logits, labels) if not return_dict: output = (logits,) + outputs[2:] return ((loss,) + output) if loss is not None else output return SequenceClassifierOutput( loss=loss, logits=logits, hidden_states=outputs.hidden_states, attentions=outputs.attentions, )
-
output의 0번째 index는 [CLS] token의 임베딩 벡터입니다. 이것을 classifier에 전달해 logits를 계산합니다. 즉, RobertaForSequenceClassification 모델의 forward 계산의 첫 인덱스에 들어있는 정보는 logits 입니다. (각 클래스일 확률 값)
sequence_output = outputs[0] logits = self.classifier(sequence_output)
- 이제 outputs[2:]의 의미를 알아보도록 해요! 이 친구는 self.roberta 안에 숨어있습니다. 그리고 self.roberta는 RobertaModel class를 의미합니다.
-
RobertaModel forward는 역시 너무깁니다. 일부분만 가져와 보자면, encoder_ouputs의 첫번째 인덱스의 값과, sequence_output을 self.pooler에 적용한 값을 튜플로 묶고, 그 이후에 encoder_outputs를 더해주고 있습니다. 무슨 의미인지 아직 감이 오지 않습니다.
embedding_output = self.embeddings( input_ids=input_ids, position_ids=position_ids, token_type_ids=token_type_ids, inputs_embeds=inputs_embeds, past_key_values_length=past_key_values_length, ) encoder_outputs = self.encoder( embedding_output, attention_mask=extended_attention_mask, head_mask=head_mask, encoder_hidden_states=encoder_hidden_states, encoder_attention_mask=encoder_extended_attention_mask, past_key_values=past_key_values, use_cache=use_cache, output_attentions=output_attentions, output_hidden_states=output_hidden_states, return_dict=return_dict, ) sequence_output = encoder_outputs[0] pooled_output = self.pooler(sequence_output) if self.pooler is not None else None if not return_dict: return (sequence_output, pooled_output) + encoder_outputs[1:]
- 차근차근 다가가 보면, 우선 우리는 encoder의 입력인 embedding_output을 이해해야 합니다.
- position_ids = self.create_position_ids_from_inputs_embeds(inputs_embeds)로 포지션 ids도 자동으로 만들어주고
- token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=self.position_ids.device)도 자동으로 만들어 줍니다.
- inputs_embeds = self.word_embeddings(input_ids) 로 단어 임베딩 벡터를 만들고,
- token_type_embeddings = self.token_type_embeddings(token_type_ids) 문장 순서 임베딩 벡터를 만들고
- embeddings = inputs_embeds + token_type_embeddings 두개를 더해줍니다.
- position_embeddings는 default가 absolute로 설정되어 있는 것 같다. 이것도 embeddings에 더해줍니다.
- Layer norm과 dropout을 거쳐 반환을 합니다.
- 즉, embedding_output은 transformer 구조에 들어가기 위해 token을 벡터로 바꾼것!!
def forward( self, input_ids=None, token_type_ids=None, position_ids=None, inputs_embeds=None, past_key_values_length=0 ): if position_ids is None: if input_ids is not None: # Create the position ids from the input token ids. Any padded tokens remain padded. position_ids = create_position_ids_from_input_ids(input_ids, self.padding_idx, past_key_values_length) else: position_ids = self.create_position_ids_from_inputs_embeds(inputs_embeds) if input_ids is not None: input_shape = input_ids.size() else: input_shape = inputs_embeds.size()[:-1] seq_length = input_shape[1] # Setting the token_type_ids to the registered buffer in constructor where it is all zeros, which usually occurs # when its auto-generated, registered buffer helps users when tracing the model without passing token_type_ids, solves # issue #5664 if token_type_ids is None: if hasattr(self, "token_type_ids"): buffered_token_type_ids = self.token_type_ids[:, :seq_length] buffered_token_type_ids_expanded = buffered_token_type_ids.expand(input_shape[0], seq_length) token_type_ids = buffered_token_type_ids_expanded else: token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=self.position_ids.device) if inputs_embeds is None: inputs_embeds = self.word_embeddings(input_ids) token_type_embeddings = self.token_type_embeddings(token_type_ids) embeddings = inputs_embeds + token_type_embeddings if self.position_embedding_type == "absolute": position_embeddings = self.position_embeddings(position_ids) embeddings += position_embeddings embeddings = self.LayerNorm(embeddings) embeddings = self.dropout(embeddings) return embeddings
- 이제 encoder입니다! 매우 깁니다… 계산과정은 복잡하니 return값만 확인해보겠습니다!
- hidden_states: 일단 입력으로 받고 시작하는데, 이게 방금 살펴본 embedding입니다. 이 친구를 robertlayer에 지정된 layer들에 넣어 layer_outputs을 얻습니다. 그리고 0번 인덱스를 다시 hidden_states에 할당해줍니다.
- 0번이 embedding vector 값
- 1번이 self_attentions 값
- 2번이 cross_attentions 값 이 되는 것 같습니다.
- 이것들을 반환해줍니다.
return tuple( v for v in [ hidden_states, next_decoder_cache, all_hidden_states, all_self_attentions, all_cross_attentions, ] if v is not None )
- hidden_states: 일단 입력으로 받고 시작하는데, 이게 방금 살펴본 embedding입니다. 이 친구를 robertlayer에 지정된 layer들에 넣어 layer_outputs을 얻습니다. 그리고 0번 인덱스를 다시 hidden_states에 할당해줍니다.
- (sequence_output, pooled_output) + encoder_outputs[1:] 로 돌아와서 보면, encoder_outputs[1:]는 next_decoder_cache, all_hidden_states, all_self_attentions, all_cross_attention를 의미합니다.
- 다시 정리해보자면, RobertaModel class의 output은 (sequence_output, pooled_output) + encoder_outputs[1:] 입니다.
- 즉, classification 클래스에서, outputs = [embedding vector, pooled_output, next_decoder_cache, all_hidden_states, all_self_attentions, all_cross_attention] 가 됩니다.
- 최종 output은 아래와 같기 때문에, embedding vector로 logits를 구하고, next_decoder_cache, all_hidden_states, all_self_attentions, all_cross_attention를 튜플로 붙인 형태가 됩니다. 거기에 loss까지 계산이 됬으면
- loss, logits, next_decoder_cache, all_hidden_states, all_self_attentions, all_cross_attention 가 최종 형태가 됩니다.
output = (logits,) + outputs[2:] return ((loss,) + output) if loss is not None else output
2.2 최종 형태의 의미
- loss, logits, next_decoder_cache, all_hidden_states, all_self_attentions, all_cross_attention 가 최종형태입니다. 왜, output의 첫번째 인덱스가 각 클래스일 확률인지를 이해할 수 있었습니다.
- 물론 이외에 더 복잡하고 연관된 사항들이 많기 때문에 아직은 갈길이 멀다고 생각합니다. 다음번 포스팀에서는 조금더 세세한 의미를 알아볼 수 있도록 노력해보겠습니다!
# 실제 출력
SequenceClassifierOutput(logits=tensor([[ 0.0800, 0.1805, 0.0620, ..., -0.1257, -0.1650, -0.3279],
[ 0.1403, 0.1399, 0.0256, ..., -0.1489, -0.0258, -0.4063],
[ 0.1422, 0.1240, 0.1042, ..., 0.0295, -0.2008, -0.4127],
...,
[-0.0334, 0.2673, -0.0113, ..., -0.0961, -0.0519, -0.4188],
[ 0.0981, 0.1145, -0.0784, ..., -0.0449, -0.1429, -0.3333],
[ 0.0175, 0.2102, -0.0617, ..., -0.1251, -0.0935, -0.4533]],
device='cuda:0', grad_fn=<AddmmBackward>), hidden_states=None, attentions=None)