강의/NLP

[NLP] Language Model - Transformer

하기싫지만어떡해해야지 2026. 3. 21. 20:36

이 게시글은 서울대학교 데이터사이언스대학원 조요한 교수님의

거대언어모델과 대화형 인공지능 강의를

학습을 위해 재구성하였습니다


 

 

오늘 배운 내용은 트랜스포머이다

 

 

 

 

 

 

지난 시간에 배운 내용에 대해서 간단하게 복습해보자

 

지난시간에는 word2vec에 대해서 배웠는데

word2vec이란 어떤 단어를 dense vector로 나타내는 것을 말한다

 

워드투벡의 핵심은 어떤 단어에 대해서 그 주위에 나타나는 context 단어를

예측하게 함으로써 그 dense vector가 단어에 관련된 중요한 정보들을

갖고 있도록 학습을 하는 아이디어였다

여기서 등장하는게 representation learning이라는 개념이다

 

어떤 데이터를 이용해서 머신러닝 작업을 할 때

데이터 자체를 벡터로 잘 나타내야 머신러닝에서 좋은 결과를 낼 수가 있을 것이다

그렇다면 우리가 어떻게하면 그 데이터의 좋은 임베딩을 얻어낼 수 있을까?

이게 representation learning의 개념이다

 

기본적인 윈칙은 우리가 벡터에 넣고싶은 기본적인 정보들

예를 들면 품사정보, 성별 등등의 그런 정보들이

벡터에 잘 들어가있어야한다

 

prediction task를 수행할 수 있는 그런 task를 잘 골라서

그걸 가지고 학습을 시키다보면 모델이 이런 중요한 정보들을 잘 담아서

이걸 가지고 학습을 시키다 보면

모델이 이런 중요한 정보들을 잘 담아서 학습을 시킬 것이다

이게 바로 representation learning의 중요한 원칙이다

 

 

 

 

 

 

 

 

seq2seq은 input text를 output text로 변환하는 모델이었다

여기서 중요한 것은 attention mechanism이었는데

각각의 디코딩 스텝에서 우리가 예측을 해야하는데

예측을 할 떄 입력으로 들어오는 input 단어들을 잘 aggregate해서

좋은 context vector를 만들어서 이걸 이용해서 다음 단어를 예측하고자 하는 것이다

그런데 이 때 각각의 단어가 조금씩 서로 다른 중요도를 갖고 있을 것이라는 것이다

 

해서 우리가 어떤 단어에 얼만큼 attention을 줘야하는지

그 weight를 잘 계산을 해서 우리가 context vector를 만들 때

현재 상태에서 중요한 attention weight는 더 많이 집어넣고

덜 중요하면 덜 집어넣고

이런식으로 해서 좋은 context vector를 만들어서 다음 단어를 예측하자는 것이 attention의 핵심이다

 

 

 

 

 

오늘은 트랜스포머에 대해서 알아보자

 

 

 

 

 

 

 

트랜스포머라고하는 것은 아키텍처 이름이고

저 contextualized word embedding은 트랜스포머의 철학이다

 

 

 

 

 

 

 

저번 시간에 질문에서도 한 번 나왔는데

언어에서는 같은 단어이지만 다른 뜻을 가진 단어들이 있다

play같은 것들이 대표적인 예시이다

 

그리고 또 대명사같은 경우는 어떤 문장에 쓰이느냐에 따라

그 대명사가 가리키는 대상이 달라진다

철자가 같다는 이유로 하나의 단어는 하나의 고정된 벡터를 할당하는 것은 너무 strict하다

이상적으로는 어떤 단어의 임베딩이라는게

너무 strict하지 않고 주변 어떤 단어들이 나타나느냐에 따라서

contextualized하게 나타내자는 것이 이것의 핵심이다

 

 

 

 

 

 

트랜스포머는 인코더와 디코더가 결합된 아키텍처이다

인코더 디코더 구조라고 하는데

항상 인코더와 디코더를 같이 쓰는 것은 아니다

 

BERT는 트랜스포머에서 인코더만을 사용하고

GPT나 라마는 트랜스포머에서 디코더부분만을 가져다가 사용한다

 

그리고 인코더와 디코더를 합쳐서 같이 쓰는 모델로는

T5와 BART 등이 있다

 

 

 

 

 

 

 

오늘은 인코더 위주로 배워본다고 한다

 

우선 제일 아랫단에서 input이 들어가게된다

원래는 단어가 토큰은 아닌데

우선 오늘 설명을 할 때는 단어=토큰이라고 가정하고 설명한다

 

이 단어들을 input으로 넣으면 한 번 임베딩 레이어를 거쳐서 벡터로 만든다

벡터로 만들면 그때부터 계속 transformer block들을 여러번 거치면서

이 벡터들을 계속해서 transform을 시켜준다

 

초기 벡터가 있으면 걔를 multi-head attention이란걸 거쳐서

벡터를 transformation을 시키고 feed forward를 거쳐서 계산을 하고

이걸 다음 block에 넣어서 N번의 과정을 거쳐서 계속해서 transform을 시킨다

 

 

 

 

 

 

 

여기서 핵심이 되는 부분이 바로 이 multi-head attention이다

 

 

 

 

 

 

 

 

the cat walks라는 간단한 문장이 있다고 해보자

각각의 토큰별로 initial embedding은 이미 존재한다고 해보자

 

지금 이 self attention에서 하고싶은 것은

각각의 word embedding들을 contextualized를 시키고 싶은 것이다

한 마디로 주변에 나오는 단어들의 정보를 담고싶은 것이다

그렇다면 cat이라는 단어를 어떻게 self attention을 통해서

contextualized를 시킬 수 있을까?

 

가장 쉬운 방법은 cat과 다른 모든 단어들에 대해서

서로 유사도 혹은 relevance가 어느 정도인지 계산해보는 것인데

이 유사도 계산으로는 dot product가 가장 간단하다

그래서 cat과 the, cat과 cat, cat과 walk끼리 dot product를 하면

각각에 대해서 어떤 숫자들이 나오게 된다

이렇게 나온 dot product 값에다가 softmax를 취해서 확률분포로 바꾼다

이 부분을 cat에 대한 각각의 단어에 대한 attention weight라고 볼 수 있다

그래서 이걸 가지고 weighted sum을 수행한다

그래서 cat의 contextualized된 버전이 나오게 되는 것이다

이 방법이 우리가 생각할 수 있는 가장 심플한 방법이다

 

하지만 이런 방법은 문제가 있을 수가 있다

단어의 pair에 대해서 relevance를 계산을 할 수는 잇는데

이 relevance가 dot product 하나로 계산이 되어버린다

두 단어의 관련성이라는 것은 한 가지 측면만 있는 것이 아니다

문법적 측면, 의미적 측면 등등 다양한 측면에서의 관련이 있는데

이 다양한 측면에서의 관련성을 포함시키고 싶었던 것이다

 

이러다가 나오게 된 것이 바로 attention head라는 개념이다

각각의 initial embedding이 있는데

각각의 단어에 대해서 query vector, key vector, value vector

3개의 단어를 각각의 단어에 대해서 계산한다

 

 

 

 

 

 

 

각 단어의 initial word embeddig을 x라고 해보면 차원은 d가 된다

저 x에다가 linear transformation을 시켜주는 것이다

q는 initial word embedding x에 WQ를 곱해준다

그렇게 되면 q는 Dq차원을 가진 쿼리 벡터가 되는 것이다 

이런 방식으로 k, v에 대해서도 동일하게 matrix를 곱해서 변형을 시켜준다

그 다음이 attention score를 계산을 해서 aggregation을 시키는 것이다

 

그렇다면 관련성이라는 것은 어떻게 계산할까?

우리는 지금 cat을 contextualized 시키고 싶은 것이다

cat의 query vector를 가지고 다른 각각의 단어들의

key vector와 dot product를 수행한다

 

cat의 query vector를 가지고 다른 각각의 단어들의 key vector와

dot product를 수행한다

cat의 쿼리벡터와 cat의 key 벡터도 dot product를 수행한다

그 다음에 저 값들을 다 softmax를 취해서 attention weight를 구한다

그런 다음 각각의 value vector들을 곱한 다음 aggregation을 한다

이게 attention head 하나에서 나온 output이 되는 것이다

그럼 이 head는 1*d만큼의 size를 갖게 된다

 

그런데 이게 원래 input vector의 사이즈와 차원이 맞지 않을 수가 있다

그렇게 때문에 linear transformation을 한 번 더 시켜줘서

original vector와 차원을 맞춰준다

이게 Wo matrix가 수행해주는 것이다

원래의 워드임베딩 차원인 d로 복구를 시켜주는 것이다

그래서 최종 contextualized vector로 나오는 것 x'은 

 

이렇게 생기게 된다

 

 

여기서 우리가 왜 query, key, value라는 표현을 쓸까?

우리가 현재 관심있는 질의는 query이다

예시같은 경우는 cat이 질의역할을 한다

 

이 질의와 DB에는 각각의 entry가 얼마나 관련이 있는지를 체크하고 싶은 것이다

여기서 key는 DB의 key에서 따온 것이다

그렇게 해서 query와 key 사이의 유사도를 계산한다

그래서 이 값들의 유사도를 계산했는데

우리가 실제로 쓸 것은 key가 아니고 value 값들이다

그래서 실제로 aggregation을 수행할 때에는 value를 가져와서 쓰는 것이다

 

 

 

 

 

 

 

이제 multi-head attention에 대해서 알아보자

 

아까 단어의 관련성에 여러 가지 측면이 있다고 했다

그래서 각 헤드마다 그 여러 측면의 관련성을 계산해주고 싶은 것이다

 

각각의 단어별로 m개의 head가 있다고 가정해보자

위 ppt에서는 3개가 있다

 

결국에는 각각의 head별로 Wq, Wk, Wv가 다 달라진다

x에다가 Wq1, Wk1, Wv1으로 구현한 것이 첫번째 head이다

이런식으로 head별로 트리플을 따로 가지고

지금은 m=3이다

 

이렇게 head별로 contextualized를 시키게 된다

앞에 했던 방식과 동일하게 contextualized를 시키는데

첫번쨰 head는 첫번째 head끼리만 수행하는 것이다

첫 번째 head가 생각하는 관련성을 가지고 어떤 단어들의 중요도를 계산 한 다음

거기에 비례하게 값이 나오게 될 것이다

head2도 이 과정에 대해서 동일하게 수행한다

각 헤드끼리는 서로 다른 관련성을 계산하고 있기 때문에

서로 다른 값이 나오게 된다

이렇게 해서 각각 head의 output으로 h1, h2, ..., hm까지 나오게 된다

 

그렇다면 얘네들을 concat을 한 다음

거기에다가 Wo를 곱해서 원래 size로 원복시킨다

이런 경우 Wo matrix는 m*dv*d 이렇게 matrix size가 나오게 된다

이렇게 하면 최종 x'는 저렇게 h1, h2, ..., hm concat 한 거에다가 Wo를 곱해주면

1*d인 원래 사이즈로 맞춰지게 된다

이게 바로 multi-head attention이다

 

그런데 지금 이 과정을 각각의 단어에 대해서 반복을 하면 시간이 엄청 오래걸리게 된다

이걸 진짜로 단어 바이 단어로 하면 시간이 너무 오래 걸려서

이걸 더 빠르게 수행하기 위해서 행렬연산을 해준다

input 단어들을 다 따로따로 contextualized 해주는게 아니고

input 안에 총 n개의 토큰이 있다고 하면 (sequence의 길이가 n)

n*d 차원의 matrix가 나오게 되는데

각 토큰들을 stacking 해놓은 행렬 형태가 되는 것이다

 

그래서 i번째 head에 대해서 생각을 해보면 이 x들을

query, key, value 벡터도로 변환을 해야하는데

X 행렬에다가 그냥 W, Qi만 곱하면 결과가 한번에 나오게 된다

key도 마찬가지이다

key vector들을 stacking 해놓은 K matrix를 곱하면

N*N 차원의 matrix가 나오게 되고

얘네들이 query, key, vector들의 pair에 대해서 dot product를 시키면

결과가 한번에 쭈루룩 나오게 된다

이렇게 나오는걸 row wise로 softmax를 취해주면

각각의 단어에 대한 확률분포가 나오게 되는 것이다

이런식으로 수행해주면 attention weight를 한 번에 다 계산할 수가 있게 된다

그 다음 value vector aggregation도 마찬가지다

 

Hi는 i번째 head에 대해서 head i가 내뱉은 전체 토큰에 대한 최종 벡터이다

이걸 H1, H2, ..., Hm에 대해서 계산을 한 다음에 concat해준다

그럼 엄청 큰 matrix가 나오게 된다

거기에 Wo를 한 번에 곱해주면 x'이 stacking이 된 matrix가 한 번에 나오게 된다

그래서 X'는 n*d차원의 행렬이 되는 것이다

 

 

 

 

 

 

 

 

이 내용을 시각화를 해보자

 

각각 그럼 initial embedding이 있고 이걸 matrix로 만들어서 계산한다

Q, K, V를 곱해준다

 

 

 

 

 

 

 

그럼 I를 중심으로 contextualized를 시키고 싶다면

I의 query vector와 다른 단어들의 key vector들을 dot product를 해주고

이 값에 대해서 softamx를 취한다

그럼 I에 대해서 각각의 단어가 어느 정도로 중요한지 확률분포형태가 나오게 된다

 

 

 

 

 

 

여기다가 각각의 value vector를 다 곱해준 뒤

얘네들을 다 더해서 softmax를 취해준다

 

 

 

 

 

 

 

 

 

 

이 과정을 여러 개의 head에 대해서 수행하는 것이다

이래서 최종적으로 Wo를 곱해주면 각각의 단어에 대해서 contextualized된 output이 나오게 된다

 

 

 

 

 

 

 

그렇다면 왜 self attention이 유용할까?

 

우리가 recurrent neural network에서는

앞에 있는 것들이 다 계산이 되어야만 뒤에 있는 것을 계산할 수가 있었다

sequential하게 계산을 하다보니 시간이 오래걸렸다

 

 

 

 

 

 

 

 

하지만 self attention은 병렬적으로 처리가 가능하다

 

예를 들어서 단어를 3개로 처리를 해야한다고 하면

이걸 recurrent로 한다면 the 구하고 cat 구하고 ... 계속해서 sequential하게 해야한다

하지만 self attention으로 수행하면 각각의 GPU 내에서

Q, K, V를 동시에 3개의 단어에 대해서 쭈욱 계산할 수가 있다

병목이 걸리지 않고 기다릴 필요가 없어진다

이렇게 시간이 빠르고 병렬화가 쉽다는 것이 가장 큰 장점이다

 

두번째는 RNN의 가장 큰 단점 중에 하나는

old history를 잊어버린다는 것이었다

 

 

 

 

 

 

 

그러나 self attention 같은 경우는 이러한 문제가 없다

각각의 단어들에 대해서 weight를 다 계산해주기때문에

모든 단어들이 다른 모든 단어들을 보고서 계산을 해주니까

long-term dependency 문제를 잘 해결할 수가 있다

 

 

 

 

 

 

다음으로 feed forward network에 대해서 알아보자

 

 

 

 

 

 

 

 

head의 output 부분이 Win을 거쳤다가 다시 Wout을 통해서 원래 사이즈로 원복이 된다

이 때 ReLU 계열의 activation function이 중간에 들어간다

 

이 ReLU는 각각의 cell 값들을 x라고 한다면

x와 0에 max를 주는 함수이다

그래서 이걸 잘 살펴보면 결과적으로 이 벡터에다가

Win을 곱하고 ReLU를 취하면 0이거나 0보다 큰 cell들이 나오게 된다

0보다 큰 몇 개의 cell들이 activation된 neuron들이다

실제로 language model을 해석하는 분야의 사람들은

0보다 큰 애들을 neuron이라고 부르기도 한다

그래서 ReLU를 곱하면 k가 나오고 k에다가 Wout을 곱해준다

 

이렇게 token의 contextualized embedding이 Win을 붙여서 큰 벡터가 되고

여기에 활성화된 dimension이 생기는데 각각의 dimenstion들이 해석가능한 의미를 가질까?

왜 특정 부분에서만 activation이 일어날까?

 

활성화된 디멘션이 입력으로 들어간 token의 어떤 해석가능한 feature를 의미하는 경우가

상당히 많이 있다는 것이 밝혀져있다

우리 token이 cat이라고 한다면 Win을 곱해서 ReLU를 취하면

활성화된 부분은 예를 들면.. 고양이의 다리가 몇 개인지 정보라던지 ..

포유류인지 아닌지라던지..

아무튼 우리가 생각했을 때 고양이의 feature를 나타내는 경우가 많다

실제로 저기에다가 Wout을 곱하면 0인 애들은 전혀 반영이 되지않고

0보다 큰 row들의 값들만 v에 들어가게 되는 것이다

그래서 실제로 최종 벡터에는 그런 정보들만 포함되게 된다

이러한 내용은 나중에 interpretability 시간에 좀 더 자세하게 배워보도록 하자

 

아무튼 feed forward network에는 Win, Wout이 적용되었고

얘네들은 전부다 trainable paramter이다

 

 

 

 

 

 

 

 

 

또한 각각의 component 위네는 Add & Norm 이런 부분이 있는데

이 부분이 residual connection이랑 layer norm이다

 

 

 

 

 

 

residual connection이란 어떤 layer x가 입력으로 들어가서

output x'를 내뱉으면 이걸 그대로 쓰는게 아니고

입력으로 들어간 x를 더해줘서 최종 x'를 쓰겠다는 것이다

 

이는 attention layer에서도 그렇고

feed forward layer에서도 마찬가지이다

그렇다면 이렇게 했을 때 좋은 점은 무엇일까?

 

output을 만들 때 입력으로 들어갔던 벡터들의 정보가 굉장히 손실이 많이 될 수가 있다

실제로 transform layer도 1개가 아니고 여러번 계속 쌓아서 하기 때문에

이런식으로 하다보면 원래 정보가 거의 안남아있다는 문제가 있었다

그렇게 입력으로 들어갔던 벡터들이 non-linearity를 너무 많이 거쳐서

loss가 되게 울퉁불퉁하게 되는 경우가 많았다

반면에 중간에 output에다가 input의 값을 계속해서 더해주면

이 input의 정보가 꾸준히 위 레이어로 들어가기때문에

loss가 상당히 smooth해지고

아래에 있는 정보가 윗단까지 안정적으로 올라가는 현상이 발생한다

 

back prop을 할 때도 residual connections를 넣어주면

안정적으로 gradient가 업데이트가 되게 된다

 

 

 

 

 

 

 

layer norm은 그럼 무엇인지 살펴보자

 

residual connextion도 추가된 output을 layer norm을 이용해서 가공해서 사용한다는 것이다

x라는게 결국 output으로 나온 벡터인데 

이걸 그대로 쓰는게 아니고 가공을 거쳐 x'로 만들어주는 것이다

 

x'는 그럼 어떻게 계산되냐?

x라는 벡터에다가 쟤네 각각의 값들의 평균을 계산해서 빼고 분산으로 나눠준다

한마디로 standarize를 시키는 것이다

이렇게 standarize를 시키면 0을 중심으로 예측 가능한 범위에서 머물게 된다

그래서 이게 layer normalization이다

 

여기에 gamma와 beta도 접목시켜준다

그냥 standarize만 시키면 얘가 너무 strict해지기 때문에

각각의 dimension 별로 scale의 차이가 반영이 안되니깐

gamma는 같은 크기의 벡터인데 scaling factor이고

각각의 dimension 별로 스케일을 다르게 해주는 것이다

 

Beta는 평균을 0으로 맞춰주는 것도 너무 strict한 것 같아서

같은 크기의 벡터인데 bias vector가 된다

dimension별로 bias를 다르게 주기 위함이다

 

gamma와 beta 모두 trainable parameter이다

 

이렇게 했을 때 좋은 점은

이렇게 normalization을 시키는 근본적인 이유는

각각의 dimension별로 scale이 너무 다를까봐 이다

dimension의 scale이 너무 다르게 되면 학습이 불안정해지기 때문이다

 

만약에 train data들과 test data가 dimension이 너무 다르다는 가정을 해보면

layer norm 없이 학습을 하면 train에 맞게 학습이 되었을 것이다 

하지만 test는 전혀 다르니까 generalization이 되지 않는다

그런데 layer norm을 해주면 generalization이 올라가게 된다

 

또한 vanishing/exploding gradients 문제도 해결해준다

 

 

 

 

 

 

input embedding 부분을 한 번 살펴보자

 

각각의 토큰은 one hot vector로 시작한다

10개의 단어가 있다고 가정을 했을 때 the가 1번 단어

cat이 10번 단어가 된다 

 

여기에 Wemb라는 matrix를 곱해주는데

우리가 원하는 dimension으로 projection을 시켜주는 것이다

위 ppt에서 Wemb는 10*5 matrix가 될 것이다

 

원핫벡터는 사실 벡터의 형식를 갖고있지만 인덱스에 가깝고

저기에 Wemb를 곱해주면 dense embedding이 된다

 

Wemb도 역시 trainable paramters이고

얘도 모델 학습시킬 때 그냥 같이 학습을 시켜버린다

모델이 back prop이 되면서 얘까지도 같이 학습을 한다

 

 

 

 

 

 

 

 

그럼 이 input embedding이 나오면 이걸 바로 쓰는게 아니고

positional encoding이란걸 같이 집어넣는다

 

이건 문장에서 몇 번째 단어에 있는지 정보를 주는 것이다

 

 

 

 

 

 

 

그렇다면 이게 왜 필요할까?

 

사실 self attention 입장에서는 이런 position 정보는 전혀 알 수가 없다

위의 men eat oranges랑

oranges eat men이랑은 전혀 다른 문장임에도 불구하고

self attention은 구분할 수 없다

그래서 우리가 이 positional encoding을 직접 넣어줘야한다

 

word embedding에다가 position embedding을 augment 시켜주는 것이다

 

 

 

 

 

 

 

위 positional encoding은 트랜스포머에서 제안했던 방식이다

learned positional encoding이라고 불린다

 

word embedding을 학습시켰던 것처럼 각각의 포지션별로

임베딩이라는게 있다고 가정하고 우리가 그냥 학습을 시켜버리는 것이다

 

word embedding과 동일한 방식으로 학습을 시키는데

위 ppt에서는 the가 첫번째 토큰이니까 첫번째 자리만 1이고

나머지는 다 0인 원핫 벡터를 우선 만든다

 

Wpos라는 포지션 임베딩 matrix가 있어서

쟤를 곱해주면 모델이 기대했던 input word vector와

동일한 사이즈로 projection이 된다

 

우리가 입력으로 최대 N개의 토큰이 들어오는 것까지 허용을 하겠다고 하면

N차원의 one hot vector를 만들 수가 있다

 

N이 max sequence length이고

이런 방식으로 각 포지션의 임베딩이라는 것을 계산한다

그래서 max sequence size를 미리 정해놔야한다

 

학습을 시킬 것이기 때문에 n*d matrix로 하겠다는 것을 미리 정해놔야하고

결국에 그렇게 학습이 된 position embedding을 쓰려면

input의 길이가 N 안에 들어와야 의미있는 임베딩을 구할 수 있다는 것이다

 

 

 

 

 

 

 

시각화를 시키면 위와 같다

 

첫번째 포지션 embedding은 the와 곱해지고

두번쨰 포지션 임베딩은 cat과 곱해지고 이런식이다

각각을 더해줘서 이게 실제로 transformer block에 들어가게 된다

 

학습관점에서 본다면 이렇게 계산된 값들이

layer들을 따라서 쭈욱 올라가게 될 것이고

back prop을 할 때까지 다시 쟤네까지 내려와서 학습이 되는 것이다

 

 

 

 

 

 

 

이런 과정을 하나의 그림으로 보자

 

 

FFN에서는 Win, Wout

Attention에서는 Query, Key, Value, O

LayerNorm에서는 gamma랑 beta

embedding에서는 emb와 pos

이렇게가 학습가능한 파라미터들이된다

 

 

 

 

 

 

우리가 본 이런 레이어들이 엄청나게 많다

이런 레이어들이 계속해서 stack되게 된다

 

 

 

 

 

 

 

 

 

이렇게 레이어들이 여러 개 stack되게 되면

trainable parameters들이 훨씬 많아지게 될 것이다

 

 

 

 

 

 

 

오늘 강의의 요약이다

 

오늘은 트랜스포머에 대해서 배웠는데

핵심 철학은 contextualized word embedding이었다

 

이를 위해서 구성되는 여러가지 컴포넌트들에 대해서 배워보았다

그럼 오늘 강의 정리는 여기서 마무리-!