관리 메뉴

Bull

[DL] Transformer 설명 유튜브 영상 번역본 본문

Artificial Intelligence/Deep Learning

[DL] Transformer 설명 유튜브 영상 번역본

Bull_ 2025. 4. 3. 13:22

https://www.youtube.com/watch?v=zxQyTK8quyY

StatQuest
안녕하세요, 저는 조쉬 스타머(Josh Starmer)이고, 스탯퀘스트(StatQuest)에 오신 것을 환영합니다!
오늘은 트랜스포머(Transformer) 신경망에 대해 이야기해 보겠습니다.
이해하기 쉽게 설명할 거예요.

트랜스포머는 클라우드에서 라이팅(Lightning)과 함께 직접 만들어보면 더 재밌습니다. 빰!

요즘 사람들은 챗GPT(ChatGPT)에 엄청 열광하고 있습니다.
예를 들어, 우리의 친구 스탯스쿼치(Statsquatch)가 챗GPT에다가 “스탯퀘스트 스타일로 멋진 노래를 써 줘” 같은 걸 입력해볼 수 있죠.

“트랜슬레이션, 이건 트랜스포머로 한 거야!”

아무튼 챗GPT가 어떻게 작동하는지 이야기할 게 정말 많지만, 근본적으로 챗GPT는 트랜스포머(Transformer)라고 하는 것에 기반합니다.

그래서 오늘 스탯퀘스트에서는 트랜스포머가 어떻게 작동하는지 단계를 밟아가며 보여드리겠습니다.
특히 영어 문장을 스페인어로 간단히 번역하는 과정을 통해 설명해볼 거예요.
영어 문장 “Let’s go” → 스페인어 “Vamos”

입력 단어와 출력 단어를 숫자로 바꾸기

우선, 트랜스포머는 신경망(neural network)의 한 종류이고, 일반적으로 입력값을 숫자로만 받습니다.
그러므로 입력 단어와 출력 단어를 어떻게 숫자로 바꿀지 먼저 알아봐야 합니다.

단어를 숫자로 바꾸는 방법은 여러 가지가 있지만, 신경망에서 가장 자주 쓰이는 방법 중 하나는 워드 임베딩(word embedding)입니다.
워드 임베딩의 핵심 아이디어는, 사용하고 싶은 어휘(vocabulary)의 각 단어(혹은 단어 조각이나 기호)에 대해 입력 노드를 하나씩 두는 비교적 간단한 신경망을 이용한다는 것입니다.

여기서는 아주 간단한 예시 어휘를 가지고 있다고 해봅시다.
• 짧은 구문(“Let’s”, “go”, “to”, “go”) 등을 표현할 단어(또는 토큰)
• 그리고 EOS(문장 종료, end of sentence 혹은 end of sequence) 심볼

이처럼 어휘에 단어, 단어 조각, 심볼 등이 섞여 있을 수 있으므로, 각각을 “토큰(token)”이라고 부르겠습니다.

이렇게 마련된 입력 노드들은 그다음에 활성화 함수(activation function)로 연결되는데, 이 예시에서는 활성화 함수가 2개라고 합시다.
각 연결은 “가중치(weight)”라는 값을 곱해주게 됩니다.

조쉬, 이 숫자들은 어디서 온 거예요?
좋은 질문이네요, 스쿼치. 잠시 후에 답변해 드릴게요.

일단은 “Let’s”라는 단어를 숫자로 어떻게 변환하는지 보겠습니다.
• 먼저 “Let’s”에 해당하는 입력 노드에 1을 넣고, 나머지 모든 입력 노드에는 0을 넣습니다.
• 그런 다음, 각각의 입력 노드에서 활성화 함수로 가는 연결 가중치를 곱해 줍니다.

예를 들어, “Let’s”의 입력값이 1이므로, 1.87 × 1 = 1.87이 왼쪽 활성화 함수로 전달되고, 0.09 × 1 = 0.09가 오른쪽 활성화 함수로 전달됩니다.

반면에 “to”라는 입력 노드 값이 0이라면, -1.45 × 0 = 0이 되어서 왼쪽 활성화 함수로 전달되는 값은 0이 되고, 1.50 × 0 = 0이 되어 오른쪽 활성화 함수로 전달되는 값도 0이 됩니다.

즉, 입력 노드 값이 0이면, 그 단어(혹은 토큰)에서는 모두 0만 활성화 함수로 보내게 됩니다.
이 예시에서는 “to”, “go”, “EOS” 모두 0이므로, 활성화 함수로 가는 값은 다 0이고, 오직 “Let’s”만 1을 가지고 있으니 1.87과 0.09라는 값만 활성화 함수들로 넘어가게 됩니다.

그리고 이 예시에서 활성화 함수는 단순히 “정체(identity) 함수”라고 가정합니다. 즉, 들어간 값(x축)이 그대로 결과값(y축)이 됩니다.
• 왼쪽 활성화 함수의 입력이 1.87이면 출력도 1.87
• 오른쪽 활성화 함수의 입력이 0.09면 출력도 0.09

이렇게 해서 나온 출력값(1.87, 0.09)이 곧 “Let’s”를 표현하는 숫자가 됩니다. 빰!

마찬가지로 “go”라는 단어를 숫자로 바꾸려면, “go” 입력 노드에 1, 나머지에는 0을 넣고 같은 과정을 거칩니다.
• 그러면 결과적으로 (-0.78, 0.27)이라는 숫자 쌍이 “go”를 표현하게 됩니다.

그렇게 해서 우리는 “Let’s go”라는 입력 구문을 숫자로 변환했습니다. 빰!

참고로 워드 임베딩에 대해서 더 자세히 알고 싶으면 관련 스탯퀘스트 영상을 확인하세요.

여기서 두 가지 짚고 넘어갈 점이 있습니다.
1. 같은 워드 임베딩 네트워크를 단어마다 재사용한다.
• 예컨대 “Let’s”에 대해 썼던 같은 네트워크(가중치)를 “go”에도 그대로 씁니다.
• 문장의 길이가 어떻든, 같은 워드 임베딩 네트워크를 단어마다 복붙해서 사용할 수 있으므로, 다양한 길이의 입력 문장에 대응할 수 있게 됩니다.
2. 이 네트워크에 있는 모든 가중치는 역전파(backpropagation)로 결정된다.
• 역전파는 예를 들어, 데이터에 선형 회귀선을 그릴 때 기울기와 절편을 무작위로 시작해서, 데이터를 잘 맞추도록 조금씩 조정해가는 과정과 비슷합니다.
• 마찬가지로 신경망에서 각 가중치는 처음 무작위로 설정되고, 영어 문장과 그에 해당하는 스페인어 번역 예시들을 통해 학습하면서 최적화됩니다.
• 가중치를 최적화하는 과정을 통틀어 “학습(training)”이라 부릅니다.

역전파와 학습에 대해서 더 알고 싶다면 스탯퀘스트 영상들을 참고하세요.

워드 임베딩 네트워크 그림이 화면을 다 차지하니, 이제 그것들을 축소해서 구석으로 보낼게요. 좋아요!

단어 순서(Position)를 어떻게 반영할까?

다음으로 단어 순서를 고려해야 합니다.
예를 들어 “Squatch eats pizza(스쿼치는 피자를 먹는다)”라는 문장과 “Pizza eats Squatch(피자가 스쿼치를 먹는다)”는 단어는 같지만 의미가 전혀 다릅니다.

스쿼치: “피자를 먹다니 맛있겠다!”
만약 “Pizza eats Squatch”라면? “어우, 끔찍해!”

이처럼 단어 순서는 문맥과 의미를 살리는 데 매우 중요합니다.
그래서 트랜스포머가 단어 순서를 표현하는 방식인 포지셔널 인코딩(positional encoding)을 살펴보겠습니다.

먼저 “Squatch eats pizza”라는 문장에 포지셔널 인코딩을 적용하는 방법을 예로 들어봅시다.
(참고로 여기서는 또 다른 간단한 예시 어휘를 쓰고, 단어마다 4차원 임베딩 값을 만든다고 가정하겠습니다. 실제로는 수백~수천 차원을 쓰는 경우도 흔합니다.)

포지셔널 인코딩의 한 예시
1. “Squatch”, “eats”, “pizza” 각각을 워드 임베딩으로 숫자화한다. 여기서는 각 단어당 4개의 값(4차원)이 생깁니다.
2. 여기에 단어 순서를 나타내는 숫자들을 더해주는데, 이 숫자는 서로 다른 주기의 사인·코사인(sine, cosine) 파형에서 뽑아옵니다.
• 주파수가 서로 다른 사인·코사인 곡선을 여러 개 겹쳐서 사용하면, 각 단어 위치마다 유일한 값의 조합을 얻을 수 있기 때문입니다.
• 예시로, 첫 번째 임베딩 값에는 녹색 사인곡선의 y좌표를, 두 번째 임베딩 값에는 주황색 곡선의 y좌표를, 세 번째 임베딩 값에는 파란색 곡선의 y좌표를, 네 번째 임베딩 값에는 빨간색 곡선의 y좌표를 사용하는 식입니다.
3. 그런 식으로 첫 번째 단어(“Squatch”)는 녹색 곡선(첫 번째 임베딩 위치)에 해당하는 y좌표가 0, 주황색 곡선(두 번째 임베딩 위치)에 해당하는 y좌표가 1, … 이런 식으로 단어마다 해당 x좌표를 찾아서 y값을 읽어옵니다.
4. 이렇게 얻은 각 위치별 사인·코사인 값들은 반복되는 형태지만, 임베딩 차원이 많아질수록 주기가 서로 달라 유니크한 시퀀스를 만들어냅니다.

결국 이렇게 얻은 위치 값(포지셔널 값)을 원래 워드 임베딩 값에 “더해(add)”서 최종적인 단어의 포지셔널 임베딩을 만듭니다.

만약 단어 순서를 뒤집어 “Pizza eats Squatch”라 하면, 임베딩 값은 단어 위치가 바뀌므로 서로 바뀌고, 포지셔널 값은 여전히 1번째, 2번째, 3번째 단어에 대응하는 값들을 더해주므로 결과적으로 위치가 반영된 임베딩이 달라집니다.

이로써 트랜스포머는 단어 순서를 추적할 수 있게 됩니다. 빰!

다시 간단한 예시로 돌아와서

우리의 간단한 영어 문장 “Let’s go”로 돌아가 봅시다.
• 워드 임베딩으로 변환한 뒤,
• 각 단어에 위치값을 더해줍니다.

예컨대 첫 번째 단어 “Let’s”의 첫 번째 임베딩에는 0, 두 번째 임베딩에는 1을 더한다고 합시다(여기서는 2차원 예시).
두 번째 단어 “go”의 첫 번째 임베딩에는 -0.9, 두 번째 임베딩에는 0.4 같은 값을 더해서 최종 임베딩을 구하면 됩니다.

이제 사인·코사인과 ‘+’ 기호만 남겨 놓고 수식을 단순화해 표시하겠습니다.

단어 간 관계(문맥)를 어떻게 파악할까? – Self-Attention(자기어텐션)

이제 단어들의 위치를 추적할 수 있게 되었지만, “it” 같은 대명사가 어떤 단어를 가리키는지(“pizza”를 가리키는지, “oven”을 가리키는지 등) 같은 맥락(Context) 정보를 파악해야 합니다.
“it tasted good”에서 “it”이 “pizza”를 의미하는지, 아니면 “oven”을 의미하는지에 따라 문장 해석이 완전히 달라지니까요.

트랜스포머에는 이를 위한 Self-Attention(자기어텐션) 메커니즘이 있습니다.
셀프 어텐션의 개념은 간단히 말해, 각 단어가 문장 내 다른 모든 단어들과 얼마나 유사한지를 확인하는 것입니다.
• 그렇게 계산한 유사도를 이용해, 특정 단어가 어떤 단어와 더 밀접하게 연관되어 있는지를 반영해 각 단어를 다시 인코딩합니다.
• 예를 들어 “it”이 “pizza”와 높은 유사도를 가지면, “it”이라는 단어를 표현할 때 “pizza” 정보를 더 많이 반영하게 됩니다.

Self-Attention의 구체적 작동 방식

다시 “Let’s go”에 포지셔널 인코딩까지 마친 상태로 돌아옵시다.
1. 먼저 “Let’s”에 대해 “쿼리(query)” 값을 계산합니다.
• 지금까지 “Let’s”를 나타내던 2차원 값을 한 번 더 가중치와 곱하고 더하는 과정을 거쳐, 또 다른 2차원 쿼리 값을 얻습니다.
스쿼치: “그냥 원래 값 그대로 쓰면 안 돼요?”
조쉬: “잠깐만 기다려 주세요, 조금 있다가 그 이유를 설명할게요.”

이렇게 해서 (예: -1.0, 3.7) 같은 “쿼리” 벡터(2차원) 하나가 나옵니다.
2. 그리고 “키(key)” 값을 계산해야 합니다.
• “Let’s”용 키, “go”용 키를 각각 만듭니다(마찬가지로 2차원씩).
• Self-Attention은 “쿼리와 키의 내적(dot product)”을 통해 유사도를 구합니다.
• 예컨대 “쿼리(Let’s)”와 “키(Let’s)”의 내적을 구하면 값이 크면 유사도가 높고, “쿼리(Let’s)”와 “키(go)”의 내적을 구해 작으면 유사도가 낮음을 의미합니다.
3. “소프트맥스(softmax)” 함수에 유사도들을 넣어, 0~1 사이로 정상화(정규화)합니다. 그리고 모든 값의 합이 1이 되도록 합니다.
• “Let’s”가 자기 자신과 매우 유사하다면, self-attention 시에 “Let’s” 자체 정보를 많이 반영하고, “go” 정보는 적게 반영하게 됩니다.
4. 마지막으로 “밸류(value)” 벡터를 만듭니다(“Let’s”와 “go” 각각). 그리고 소프트맥스로 결정된 가중치(예: “Let’s”=1.0, “go”=0.0)에 따라 벡터를 더해 줍니다.
• 그 결과가 “Let’s”의 Self-Attention 결과 벡터가 됩니다.

마찬가지로 “go”도 쿼리를 계산하고, 키들과 내적을 구해 소프트맥스, 밸류를 가중합하여 “go”의 Self-Attention 벡터를 얻습니다.

여기서 주목할 점은, 쿼리·키·밸류를 계산할 때 사용되는 가중치가 단어별로 달라지지 않고, 같은 세트가 재사용된다는 겁니다(“Let’s”이든 “go”든).
이 덕분에 트랜스포머는 단어 수에 유연하게 대응하며, GPU 같은 병렬 계산을 효율적으로 활용할 수 있습니다.

왜 굳이 쿼리·키·밸류를 새로 계산할까?

스쿼치: “애초에 2차원 값을 그대로 쓰면 되잖아요?”

좋은 질문입니다! 이유는 다음과 같습니다.
• Self-Attention 값에는 문장 안 다른 단어들과의 관계(어텐션)가 반영됩니다.
• 각 레이어(layer)마다 다른 관점, 다른 방식으로 단어간 관계를 포착할 수 있게 쿼리·키·밸류를 달리 계산할 수 있습니다.
• 원조 트랜스포머 논문에서는 Self-Attention 층을 8개나 겹쳤고, 이를 멀티헤드(Multi-head) 어텐션이라고 불렀습니다(왜 8개인지 정확한 이유를 모르겠지만 그들이 그렇게 했습니다).

Residual Connection(잔차 연결)

Self-Attention을 거치면 단어마다 새로운 벡터가 생깁니다. 하지만 원래의 임베딩 값(단어 의미와 포지션 정보)도 유지할 필요가 있습니다.
그래서 트랜스포머에는 “Residual Connection(잔차 연결)”이라는 것이 있어서, Self-Attention 결과와 원래 입력(포지션+임베딩) 값을 더해 줍니다.
• 이렇게 하면 Self-Attention 레이어는 “단어 간 관계”만 신경 쓰면 되고, 원래 임베딩 정보는 직접 보존할 필요가 없으니 학습이 더 수월해집니다.

결과적으로 우리는 다음을 얻습니다.
1. 임베딩 + 포지셔널 인코딩
2. Self-Attention 계산
3. 두 값을 더해 최종 인코딩
이것이 간단한 트랜스포머 인코더의 전부입니다. 빰!

원본 트랜스포머 논문에서는 보통 “워드 임베딩 → 포지션 인코딩 → 멀티헤드(Self-Attention 여러 개) → 잔차 연결 + 정규화” 등의 과정을 여러 층 겹쳐서 복잡한 문장을 다룰 수 있게 했습니다.

디코더(Decoder)로 넘어가기 – 영어 “Let’s go” → 스페인어 “Vamos”

이제 인코더가 영어 문장을 인코딩했으니, 이를 바탕으로 스페인어를 생성(디코딩)해야 합니다.
트랜스포머의 두 번째 큰 구성요소가 바로 디코더(Decoder)입니다.

디코더에도 마찬가지로 워드 임베딩이 있습니다. 다만 이번에는 출력 언어(스페인어) 어휘에 맞춰져 있습니다.
우리 예시에선 스페인어 단어 “ir, vamos, e, EOS” 정도만 있다고 가정해 봅시다.

그리고 디코더를 시작할 때는, 보통 첫 입력으로 EOS 토큰(또는 SOS 토큰)을 사용합니다.
• 여기서는 EOS를 시작 토큰으로 쓰는 방법을 보여주지만, SOS를 쓰는 경우도 있습니다.

예시에서 EOS 토큰을 디코더 워드 임베딩의 입력으로 넣으면, 2차원 출력이 나온다고 합시다(예: 2.70, -1.34).
이제 디코더에서도 똑같이 포지셔널 인코딩을 더해줍니다.
• EOS가 “첫 번째” 위치에 있으니 (0, 1) 정도의 위치값을 더한다고 하면, 결과적으로 (2.70, -0.34)가 됩니다(예시 수치).

디코더에도 Self-Attention 레이어가 있습니다.
• 왜냐하면 스페인어로 단어를 하나씩 생성하다 보면, 이미 생성한 단어들과의 관계를 파악해야 하기 때문입니다(긴 문장일 때 중요).

현재는 첫 단어로 EOS만 있으므로, 쿼리·키·밸류를 계산하고 Self-Attention 결과를 얻으면 됩니다.
• 가중치는 인코더 때와 다를 수 있습니다(디코더 전용).

그렇게 해서 Self-Attention 결과 벡터가 나오고, Residual Connection으로 원래 포지셔널 임베딩을 더해줍니다.

Encoder-Decoder Attention

이제부터가 핵심입니다. 스페인어 디코더가 영어 인코더 내용을 어떻게 참조하느냐를 살펴봅시다.
“Don’t eat the delicious looking and smelling pizza” 같은 문장이 입력됐을 때, “don’t”를 놓치면 정반대 의미가 되겠죠.
• “먹지 마!” → “먹어라!”가 되어 버리면 큰일입니다.

그래서 트랜스포머는 Encoder-Decoder Attention(인코더-디코더 어텐션)을 통해, 디코더가 입력 문장의 중요한 단어에 집중(어텐션)하게 만듭니다.
• 즉, 디코더에서 쿼리를 만들고, 인코더 쪽 단어들에 대한 키와 밸류를 참고하여, 어떤 단어가 중요한지 가중합해 반영합니다.

구체적으로는,
1. 디코더 EOS 토큰으로 쿼리를 만든다(2차원).
2. 인코더에 있는 각 단어(“Let’s”, “go”)마다 키를 만든다(각 2차원).
3. 쿼리·키 내적을 통해 유사도, 소프트맥스 계산 → 어느 단어를 얼마나 참고할지 결정
4. 인코더 단어별 밸류를 만든 뒤, 소프트맥스 가중치로 가중합 → Encoder-Decoder Attention 결과
5. Residual Connection으로 이전 단계 결과와 더한다.

최종 출력 단어를 고르는 Fully Connected Layer

마지막으로, 디코더에서 얻은 2차원 벡터를 실제 스페인어 단어(토큰) 중 하나를 고르는 단계가 필요합니다.
이를 위해 완전 연결층(Fully Connected Layer)을 사용합니다.
• 입력 노드는 (2차원) → 출력 노드는 스페인어 어휘(4개) → 가중치와 바이어스 연산을 거쳐 4차원 출력
• 마지막으로 소프트맥스 함수를 한 번 더 사용하여, (ir, vamos, e, EOS) 중 하나를 확률적으로 선택합니다.

우리 예시에서는 첫 출력으로 “vamos”가 선택되었습니다!
• “Let’s go”의 첫 스페인어 단어가 “vamos”!
하지만 문장이 끝났는지 아닌지를 결정하려면 계속 단어를 뽑아봐야 하죠.

“vamos”를 다시 디코더에 넣어 워드 임베딩 → 포지셔널 인코딩 → Self-Attention → Residual → Encoder-Decoder Attention → Residual → Fully Connected + Softmax 과정을 거칩니다.
• 그 결과, 두 번째 단어로 “EOS”를 뽑았습니다.
• 이제 문장이 끝났으니 디코딩 종료.

결국 “Let’s go” → “vamos” (그리고 EOS로 종료)
완벽하게 번역했네요! 트리플 빰!!

요약

트랜스포머의 핵심 구조를 간단히 요약하면 다음과 같습니다.
1. Word Embedding: 단어(토큰)를 숫자 벡터로 변환
2. Positional Encoding: 단어 순서를 반영하기 위해 사인·코사인 등을 사용해 위치 정보를 벡터에 추가
3. Self-Attention: 각 단어가 문장 내 다른 단어들과 얼마나 관련 있는지 계산해, 단어 벡터들을 문맥 정보로 재인코딩
4. Encoder-Decoder Attention: 디코더가 인코더의 단어들 중 중요한 단어에 집중할 수 있도록 유사도를 계산해 반영
5. Residual Connections: 각 레이어의 출력을 원래 입력에 더해 줌으로써 학습을 안정적으로 함
6. Fully Connected Layer + Softmax: 디코더가 최종적으로 어떤 단어(혹은 토큰)를 출력할지 결정

이 구조가 문장 길이에 관계없이 빠르게 병렬 학습이 가능한 트랜스포머의 기본 아이디어입니다.

추가 기능

지금까지 매우 간단한 버전만 다뤘습니다만, 실제로는 아래와 같은 추가 요소들이 들어갈 수 있습니다.
• Layer Normalization: 각 단계마다(포지셔널 인코딩 후, Self-Attention 후 등) 출력 벡터들을 정규화해, 긴 문장이나 큰 어휘에서도 학습이 안정되도록 합니다.
• Scaled Dot-Product: 원 논문에서는 “쿼리·키 내적값을 벡터 차원의 제곱근으로 나누기”를 추가하여, 긴 문장일수록 발생할 수 있는 문제를 방지했습니다.
• Feed-Forward Network: 인코더와 디코더 각각의 Self-Attention 뒤에 여러 층 레이어를 추가해, 파라미터 수를 늘려 더 복잡한 패턴을 학습할 수 있도록 합니다.

이 모든 것을 종합해서 만들어진 것이 “Attention is all you need”라는 유명한 트랜스포머 구조이고, 이후 챗GPT 같은 거대 언어 모델들도 이것을 확장하여 구현된 것입니다.

마무리 – 스탯퀘스트 홍보

統계나 머신러닝을 오프라인에서 복습하고 싶다면, 스탯퀘스트 PDF 스터디 가이드나 제 책 『The Stat Quest Illustrated Guide to Machine Learning』을 statquest.org에서 확인해 보세요. 누구에게나 유용한 자료가 있을 겁니다!

호오! 드디어 또 하나의 신나는 스탯퀘스트를 마쳤습니다.
이 스탯퀘스트가 마음에 드셨다면 구독 버튼을 눌러주세요.
그리고 스탯퀘스트를 후원하고 싶다면 Patreon 후원, 채널 멤버십, 제 노래 구매, 티셔츠나 후드티 구매, 혹은 단순 기부 등 여러 방법이 있습니다(링크는 아래 설명에).
그럼 다음 시간까지,

Quest on!