17년도 Attention is all you need 논문 이후로 대 Transformer 구조의 시대에 살고 있다.
NLP 의 역사를 돌아보면 슬슬 새로운 Architecture 가 나올 타이밍이긴 하지만 아직은 대 Transformer 구조의 시대가 지속되고 있다고 해도 과언이 아니다. (요즘에는 디퓨전 모델과 같은 방식으로 텍스트를 생성하는 방식이 뜨고 있는 것 같긴 하지만 필자 생각에는 이 또한 Transformer 구조다.)
생성형 모델의 가치가 엄청나게 높아진 요즘 생성형 모델의 근간이 되는 Decoder 모델의 아버지이자 어머니인 Transformer 모델의 구조는 어땠는지를 확인해봤다.
본 포스팅은 실무자를 위한 내용으로 Transformer 구조를 상세하게 다루었다.
목차
1. TransformerEncoder 구조
2. TransformerDecoder 구조
3. 저는 Transformer 모델을 이렇게 활용할 겁니다.
1. TransformerEncoder 구조
Encoder part 는 상삼각 행렬을 적용하지 않는다. (모든 정보를 embedding 후 연산하여 Decoder 에 전달)
상삼각 행렬은 오직 Decoder part 에서만 적용함을 주의하기 바란다.
코드를 확인하여 구조를 살펴보면 아래와 같다.
특징은 다음과 같다.
- src(=x) 를 Multi-head Attention 에 넣고 나온 Output 값을 src 에 더해준다.
- src(=x) 는 ‘이전 Layer Output’ 또는’Embedding input 을 다 더한 값’
- __sa_block method(Multi-head Attention)에서는 Query, Key, Value 모두 동일한 값이다. (src 로 동일)
- 1번의 Output 값을 __ff_block method(Linear)에 넣어주는 연산을 한다.
- 2개의 nn.linear 를 통해 연산 진행
- 2번의 Output 값과 1번의 Output 값을 더해준다.
- 이 값이 TransformerEncoderLayer 의 final Output 이 됨
상세한 내용 확인을 위해 아래 코드를 참고하기 바란다. (출처: nn.Transformer github)
class TransformerEncoderLayer(Module):
...
def forward(...
...
)
...
# see Fig. 1 of https://arxiv.org/pdf/2002.04745v1.pdf
x = src
if self.norm_first:
x = x + self._sa_block(
self.norm1(x), src_mask, src_key_padding_mask, is_causal=is_causal
)
x = x + self._ff_block(self.norm2(x))
else:
x = self.norm1(
x
+ self._sa_block(x, src_mask, src_key_padding_mask, is_causal=is_causal)
)
x = self.norm2(x + self._ff_block(x))
return x
# self-attention block
def _sa_block(
self,
x: Tensor,
attn_mask: Optional[Tensor],
key_padding_mask: Optional[Tensor],
is_causal: bool = False,
) -> Tensor:
x = self.self_attn(
x,
x,
x,
attn_mask=attn_mask,
key_padding_mask=key_padding_mask,
need_weights=False,
is_causal=is_causal,
)[0]
return self.dropout1(x)
# feed forward block
def _ff_block(self, x: Tensor) -> Tensor:
x = self.linear2(self.dropout(self.activation(self.linear1(x))))
return self.dropout2(x)
2. TransformerDecoder 구조
Decoder part 에서는 상삼각 행렬을 사용하여 다음 token 을 예측하기 위해 Decoder input 의 이전 token 들만 참고한다.
또한 Encoder part 에서 계산된 representation 을 사용한다.
코드를 확인하여 구조를 살펴보면 아래와 같다.
- tgt(=x) 를 Multi-head Attention 에 넣고 나온 Output 값을 tgt 에 더해준다.
- tgt(=x) 는 ‘이전 Layer Output’ 또는’Embedding input 을 다 더한 값’
- __sa_block method(Multi-head Attention)에서는 Query, Key, Value 모두 동일한 값이다. (tgt 로 동일)
- 1번의 Output 값을 Query 로, Encoder Output 의 값을 Key, Value 로 지정하여 __mha_block method(Multi-head Attention)에 넣고 나온 Output 값을 1번의 tgt 와 더해준다.
- Query: 1번의 Output
- Key, Value: Last Encoder Layer’s Output
- 2번의 Output 값을 __ff_block method(Linear)에 넣어주는 연산을 한다.
- 2개의 nn.linear 를 통해 연산 진행
- 3번의 Output 값과 2번의 Output 값을 더해준다.
- 이 값이 TransformerDecoderLayer 의 final Output 이 됨
상세한 내용 확인을 위해 아래 코드를 참고하기 바란다. (출처: nn.Transformer github)
class TransformerDecoderLayer(Module):
...
def forward(...
...
)
...
# see Fig. 1 of https://arxiv.org/pdf/2002.04745v1.pdf
x = tgt
if self.norm_first:
x = x + self._sa_block(
self.norm1(x), tgt_mask, tgt_key_padding_mask, tgt_is_causal
)
x = x + self._mha_block(
self.norm2(x),
memory,
memory_mask,
memory_key_padding_mask,
memory_is_causal,
)
x = x + self._ff_block(self.norm3(x))
else:
x = self.norm1(
x + self._sa_block(x, tgt_mask, tgt_key_padding_mask, tgt_is_causal)
)
x = self.norm2(
x
+ self._mha_block(
x, memory, memory_mask, memory_key_padding_mask, memory_is_causal
)
)
x = self.norm3(x + self._ff_block(x))
return x
# self-attention block
def _sa_block(
self,
x: Tensor,
attn_mask: Optional[Tensor],
key_padding_mask: Optional[Tensor],
is_causal: bool = False,
) -> Tensor:
x = self.self_attn(
x,
x,
x,
attn_mask=attn_mask,
key_padding_mask=key_padding_mask,
is_causal=is_causal,
need_weights=False,
)[0]
return self.dropout1(x)
# multihead attention block
def _mha_block(
self,
x: Tensor,
mem: Tensor,
attn_mask: Optional[Tensor],
key_padding_mask: Optional[Tensor],
is_causal: bool = False,
) -> Tensor:
x = self.multihead_attn(
x,
mem,
mem,
attn_mask=attn_mask,
key_padding_mask=key_padding_mask,
is_causal=is_causal,
need_weights=False,
)[0]
return self.dropout2(x)
# feed forward block
def _ff_block(self, x: Tensor) -> Tensor:
x = self.linear2(self.dropout(self.activation(self.linear1(x))))
return self.dropout3(x)
3. 저는 Transformer 모델을 이렇게 활용할 겁니다.
필자는 현재 교육 Domain 에서 데이터 사이언티스트로 일하고 있다.
교육 Domain 서비스에서도 딥러닝 모델 활용에 대한 요구사항이 많이 발생한다.
이를 위해 서비스의 요구사항을 명확히 파악하고 해당 요구사항을 충족시키기 위한 개발 Process 설계가 중요하다.
이 관점에서 필자는 재작년부터 이런 생각을 많이 하고 업무에 적용한다.
기술은 서비스에서 필요한 요구사항을 어떻게 해결할 수 있느냐? 의 관점에서 바라봐야 비로소 가치가 생긴다.
서비스의 요구사항을 제대로 파악하지 않고 기술력만을 주장하는 건 마치 손님의 취향을 고려하지 않고 요리를 하는 주방장과도 같다고 생각한다. 고수를 싫어하는 손님한테 고수 요리를 주는 거다.
이런 관점으로 교육 Domain 에 Transformer 모델의 구조는 많은 걸 할 수 있는 근간이 된다.
필자는 현재 진행하고 있는 프로젝트에서 Transformer 모델 구조를 참고하여 서비스 적용을 위한 모델을 개발할 계획을 가지고 있다.
예를들어 학생의 성취도를 진단하는 방법, 학생에게 적절한 문제 또는 강의를 추천하는 방법 등에 활용할 수 있을 거다.
이는 이미 뤼이드에서 SAINT+ 모델을 통해 사용한 바 있다.
필자는 이를 자사의 데이터와 서비스 목적에 맞게 커스터마이징할 예정이다.
굳이 따지자면 SAINT+ 모델과 근간이 다르다. 자사 고유의 모델이 될 것이다.
기회가 되면 해외 학회에 논문을 투고하고 추후에 필자의 논문을 소개하도록 하겠다.
마무리,,
필자의 근간은 NLP 연구원이다.
요즘 하는 생각은 하나의 분야에 깊은 이해가 있으면 다른 분야를 학습하고 새로운 무언가를 만드는데 도움이 많이 된다는 것을 느끼고 체감하곤 한다.
NLP 에 대한 깊은 이해가 있기 때문에 다른 분야인 학생의 성취도 측정과 추천을 빠르게 F/U 할 수 있었고, NLP 모델의 구조를 바탕으로 새로운 무언가를 설계할 수 있다.
한 가지 분야에 전문가가 된다는 것은 해당 분야를 다른 분야에 적용하여 새로운 무언가를 생산해낼 수 있는 잠재력이 있다는 뜻이 아닐까.
앞으로 무엇을 공부하고 업무에 적용해야 할지 잠깐 고민이 필요할 것 같다.
최신 트렌드가 되는 기술들을 F/U 하는 것도 좋지만 필자가 하고 있는 프로젝트와 관련된 연구들을 F/U 하는게 더 좋지 않을까? 라는 생각도 한다.
아무튼... 프로젝트 성공적으로 잘 끝나기를 기도하며 열심히 해야겠다는 다짐을 한다.
'Python > 패키지 훓어보기' 카테고리의 다른 글
[LLaMA-Factory] LoRA Adapter 확인 (0) | 2025.02.27 |
---|---|
[LLaMA-Factory] Tokenizer padding_side 확인 (0) | 2025.02.22 |
[LLaMA-Factory] PT&SFT 학습 데이터는 어떻게 만들어지는가? (1) | 2025.01.22 |
댓글