언어 AI란
컴퓨터는 기본적으로 숫자(0과 1)만 이해할 수 있다.
우리가 사용하는 '컴퓨터'와 같은 단어들은 이해하지 못한다.
즉 사람의 언어(텍스트)를 컴퓨터가 이해 가능한 숫자 형태로 바꿔야한다.
이게 임베딩의 기본 개념이다.
BoW(Bag-of-Words)
가장 기본적인 방법으로는 BoW가 있다.
"문장에 어떤 단어가 몇 번 등장하는지 숫자로 세어보면 되지 않을까?"
라는 아이디어에 착안해 2가지 작업을 한다.
토큰화: 문장을 공백 기준으로 잘라서 개별 단어 단위로 쪼개기
벡터화: 쪼갠 단어로 중복 없는 단어 목록을 만든다. 그리고 입력된 텍스트를 기반으로 우리 단어 목록과 일치하는 단어가 몇 번씩 나왔는지 숫자를 세어 표현한다.
세어진 숫자 묶음이 바로 문장 단위의 벡터가 된다.
TF-IDF(단어 빈도-역 문서 빈도)
BoW에는 큰 단점이 있었다.
단어를 그냥 쪼개버리고 카운트하니 순서를 알 수 없는 것이다.
즉 "아빠가 방에 들어가신다"과 "방에 아빠가 들어가신다"는 완전히 동일한 결과가 나온다는 것이다.
또한 의미 생각도 안하고 카운트를 하니 주로 사용되는 "a", "the", "is" 등의 단어가 많이 카운트 되고 핵심 단어들은 구분하지 못한다.
이 문제를 보완하기 위해 TF-IDF 방식이 등장했는데
빈도(TF)만 세지 말고 다른 문서에서 안나오는 희귀한(IDF) 단어는 더 높은 가중치를 주자 라는 개념이다.
즉 '이 문서에서만 유독 자주 등장하는 중요한 단어'를 찾아내서 텍스트의 핵심을 파악하고자 하는 방법이다.
단어는 분류했으니 단어의 의미를 찾아보자
"고양이"와 "강아지"는 서로 다른 단어지만 관련성이 존재한다.
이 관련성을 숫자로 표현하고자 단어의 의미를 파헤치려는 시도가 있었는데 바로 단어 임베딩이다.
단어 임베딩(Word Embedding)
'의미가 비슷하다면 벡터 공간에서 가깝게 위치하도록 하자'라는 아이디어다.
단순히 단어를 숫자화 하는게 아닌 단어의 의미와 관계를 압축하자는 개념이다.
1단계 (BoW): "강아지"는 1, "고양이"는 2, "사과"는 3. 각 단어는 그냥 다른 숫자일 뿐
2단계 (임베딩): "강아지"와 "고양이"의 벡터 좌표는 서로 가깝고, "사과"의 벡터 좌표는 멀리 떨어져 있다.
Word2Vec: 주변 단어를 보면 그 단어를 알 수 있다.
마치 나는 내 주변 친구 5명의 평균이라는 말이 있듯이
증명하기 모호한 단어의 의미를 그 단어 주변에서 찾고자 하는 방식이다.
작동원리는 간단히 하자면 다음과 같다.
학습 준비: 수많은 문장 데이터를 준비한다.
예측 훈련: 문장 속에서 한 단어를 기준으로, 그 주변에 어떤 단어들이 나타날지 예측하는 훈련을 반복한다.
결과물 (임베딩): 훈련이 끝나고 나면 신경망 내부에는 각 단어의 '의미'가 압축된 벡터가 남게 된다.
즉 "강아지 귀여워~" "고양이 귀여워~" 이런 데이터를 수없이 학습 시키다보면 자연스럽게,
"귀여운건?" 다음에 나올 단어로 강아지 아니면 고양이가 나온다.
예측되도록 하는 과정에서 "강아지"와 "고양이"는 비슷한 벡터 좌표에 위치하게 되는 것이다.
코사인 유사도: 단어 벡터 사이의 거리를 재는 법
단어들이 의미를 담은 벡터가 되었다면 두 단어가 얼마나 비슷한지 수학적으로 계산이 가능하다
왜냐면 숫자 좌표 정보니깐
아무튼 이때 가장 널리 쓰이는 방법이 바로 코사인 유사도(Cosine Similarity)다.
벡터는 크기와 방향이다.
dot(A,b)/(norm(A)*norm(B))
파이썬으로 구현되는 이 간단한 연산은 다음과 같이 분류된다.
- 값이 1에 가까울수록: 두 단어의 의미가 매우 비슷함 (같은 방향)
- 값이 0에 가까울수록: 두 단어의 의미 연관성이 거의 없음 (직각 방향)
- 값이 -1에 가까울수록: 두 단어의 의미가 서로 반대됨 (반대 방향)
문장의 '순서'와 '문맥' 이해하기
단어 의미를 판별한건 좋은데 "고양이, 강아지"와 "강아지, 고양이"는 여전히 똑같은 취급이다.
문장에 순서를 아직 고려 못한 것이다.
첫 번째 시도 RNN(순환 신경망)
사람이 책을 읽으면 처음부터 순서대로 읽으니 이처럼 단어를 하나씩 순서대로 처리하고 '기억'했다가 다음 단어를 이해하는데 사용하자! 라는 아이디어로 시작했다.
"나는 점심을 먹는다"라는 문장을 생성함에 있어서
"나는"을 처리한 기억을 가지고 -> "점심을"을 처리하고 -> 이 둘의 기억을 합쳐서 -> "먹는다"를 처리하는 방식이다.
하지만 이 방식에는 치명적인 문제가 있는데, 사람 기억에 한계가 있듯이 AI도 기억량에 한계가 있다는 점이었다.
장기 의존성 문제로 불리는 이 단점은 문장이 길어지면 처음에 읽었던 단어 정보가 뒤에서는 흐릿해지면서 까먹는 문제가 발생한다는 점이다.
또한 단어를 하나씩 순서대로 처리하면서 기억이라는 코스트가 누적된다는 측면에서 학습 속도도 매우 느렸다.
획기적인 개선: 어텐션(Attention) 매커니즘
우리 학습법 같은거 보면 중요한 부분에 밑줄치고, 반복해서 핵심만 기억하는 방식을 쓰는데 이것도 비슷한 방식이다.
중요한 부분에만 '집중(Attention)'해서 다시 보자! 라는 개념이다.
번역을 예로 들자면 특정 단어를 번역할 차례가 오면 원본 문장 전체를 다시 훑어보고 지금 번역할 단어와 가장 관련이 깊은 단어에 높은 점수를 주는 것이다.
지금 번역 단어 - 원본 문장 전체 에 집중해야하는데
아무튼 성능은 올랐지만 매 처리마다 원본 문자에 대한 집중도 계산이 들어가니 느리긴 마찬가지 였다.
현대 AI의 기반 트랜스포머(Transformer)
"어텐션 = 좋다. 그럼 RNN 버리고 어텐션만 쓰자"
트랜스포머는 문장 안에 모든 단어를 한 번에 처리한다.
처리시 Self-Attention이라는 매커니즘으로 문장 내 모든 단어들이 서로에게 질문을 한다.
"이 문장에서 네가 나랑 얼마나 관련이 깊냐?"
중요한건 단어끼리의 상대적인 관계가 생겼다는 것이다.
간장 공장 공장장과 된장 공장 공장장 사이의 상대적인 관계속에서
공장장이라는 단어는 다른 공장장이라는 단어를 보고 "우리는 남이다"라고 외칠 수 있는 것이다.
간장 보고 "마 니 공장장이 뉘고" 하면 "강 공장장"이라고 대답도 할 수 있다.
아까 RNN+Attention 보면 매 처리마다 '지금 번역 단어 - 원본 문장 전체' 만 하더라도 느리다고 했었는데
'원본 문장 전체 - 원본 문장 전체'가 되버린 지금은 더 느릴 것이다.
하지만 상관없다. 모든 단어를 동시에 계산하기 때문에 병렬 처리가 가능해 GPU를 활용해 빠르게 학습이 가능하기 때문이다.
이로써 거리 상관없이 단어 간의 직접 관계를 파악함으로 긴 문장도 정확하게 이해하게 되는 것이다.
"나 지금 애 처리하는데 애랑 관련있는 애 누구임?"에 답할 수 있기 때문이다.
현대 LLM의 두 축, BERT와 GPT
트랜스포머는 크게 **인코더(Encoder)**와 **디코더(Decoder)**로 나뉜다.
분류에 따라서 표현 기반 모델과 생성 기반 모델로 나뉘는데
트랜스포머 아키텍처에 기반한 BERT와 GPT는 여기서 차이가 벌어진다.
BERT: 문맥을 이해하는 독해 전문가(인코더 모델)
BERT는 Bidirectional Encoder Representations from Transformers의 약자로 이름도 참 길다.
트랜스포머 사용한 양반향 인코더 입니다. 라는데
설명좀 들어보면 문맥의 양방향 이해가 가능하다고 한다.
이름에 걸맞게 텍스트 생성이 아닌 문장의 의미를 완벽하게 이해(독해) 하는게 목적이다.
어떤 식이냐면 "나는 은행에 가서 돈을 찾았다"라는 문장에서 "나는"이랑 "돈을 찾았다"까지 동시에 고려한다는데 학습 방식이 특이하다
빈칸 채우기 퀴즈를 사용해서 독해력을 키운다는데 문장에서 무작위 단어를 가리고 '맞춰보세요~'하는 방법이다.
여러분도 맞춰보자.
"나는 탕수육 소스를 [MASK] 먹는 친구의 뺨을 때렸다." -> 정답: 부워
왜냐고? 부워의 발음은 DO WAR다 본인이 자초한 것이다.
아무튼 문장의 숨은 의미를 파악하는데 특화 되있어 감정 분석, 주체 분류, 정보 검색, 질의응답처럼 이해가 중요한 작업에 주로 사용된다.
GPT: 글을 쓰는 창의적인 작가(디코더 모델)
GPT는 Generative Pre-trained Transformer의 약자로 이름에 생성이 들어간다.
트랜스포머의 디코더 구조를 사용해서 문장 이해를 넘어 새로운 문장을 창의적으로 생성하는게 목표다.
생성이란 결국 다음 내용의 예측이기 때문에 GPT의 처리 방식은 자연스럽게 단반향 방식을 따른다.
작가처럼 글을 앞에서부터 순서대로 써 내려가면서 **"다음에 올 단어는 무었을까?"**를 예측하는 훈련만 반복한다.
토크나이저: 단어, 어떻게 쪼갤 것인가
처음에 우리는 컴퓨터가 자연어를 이해하도록 하는 임베딩을 이야기 했다.
임베딩을 하기 위해서는 문장 속 단어를 쪼개는 과정이 필요했는데 이 과정이 토크나이저다.
다만 당시에는 단순히 공백으로 쪼갠다 정도로만 설명을 하고 넘어갔다.
처음에 나온 개념을 왜 갑자기 모델 소개하고 막바지에 하는지 궁금 할 수 있는데
어제 내가 RAG 건들다가 토크나이저 잘못 써서 말아먹었기 때문이다.
시작이 중요한 만큼 마지막에 정리해보고자 한다.
처음 보는 단어는 어떻게 처리할까? (OOV) 문제
처음에 단어 사전 만들고 분류하는 토크나이저는 문제가 하나 있었는데 훈련 데이터에 없는 새로운 단어(신조어, 오타, 전문 용어 등)은 <unk>라는 Unknown 토큰으로 처리해버린다는 것이다.
단어의 의미를 완전히 잃어버린다는 뜻이다.
이 어휘 밖 단어(Out-of-Vocabulary, OOV) 를 해결하기 위해 등장한 것이 서브워드 토큰화다.
서브워드 토큰화: 단어를 의미있는 조각(레고 블록)으로 분해하기
단어가 있다면 해당 단어를 분해 가능한 작은 의미 단위(Subword)로 분해한다는 개념이다.
기존 방식: walking, walked, walks를 모두 다른 단어로 취급
서브워드 방식: walk와 ing, ed, s라는 작은 조각으로 나눈다.
위 방식으로 처리하면 신조어 등 처음보는 단어를 분석해서 아는 조각들로 조합해서 의미를 유추 가능하고
단어도 모든 변형을 저장하는게 아닌 자주 쓰이는 조각들로 어휘집을 구성함으로 크기도 줄어들고, 효율적으로 구성 가능하다는 장점이 있다.
대표적인 방법으로는 BPE(Byte Pair Encoding) 알고리즘이 있는데 텍스트에서 가장 자주 함께 등장하는 글자 쌍을 찾아 하나로 합치는 과정을 반복한다.
"lower"를 [l, o, w, e, r]로 나누고 "lo"가 자주 나오고~ "er"이 자주 나오고~ "low"도 자주 나오고~
하는거 맞춰서 합치고 합치다 보면 low, er로 구분되는거라고 보면 된다.
모델과 토크나이저는 한 팀이다.
영어 데이터로 훈련된 GPT-2 토크나이저한테 한글로 "프로그래밍"라는 단어 던지면 이렇게 나온다
['í', 'Ķ', 'Ħ', 'ë', '¡', 'ľ', 'ê'...]
한국어를 이해 못하니 의미 없는 바이트 조각으로 분해해 버리는 건데
내가 어제 말아먹은 원인이기도 하다.
양식 요리사한데 중식 식재료 던지는거라고 보면 된다.
청경채를 샐러리마냥 다루면 식감 푹 죽어서 뭔지도 모를거 나오는거랑 비슷하다고 보면 된다.
토크나이저의 특수 토큰
아까 모르는 단어는 <unk>라는 Unknown 토큰으로 처리한다고 했는데 토크나이저에서는 모델과 약속된 신호를 추가하는 역할도 한다.
특수 토큰(Pecial Tokens)라고 불리는 이 토큰들은 모델별로 상의하기 때문에 참고용으로 몇개만 명시하겠다.
- [CLS]: BERT가 문장 분류 같은 작업을 할 때, 문장 전체의 의미를 응축해서 담는 역할을 하는 토큰
- [SEP]: 두 문장을 구분하는 역할
- [PAD]: 여러 문장을 한 번에 처리하기 위해 길이를 맞출 때, 비어있는 부분을 채우는 역할