3. 토크나이저와 토큰 샘플링 방법

LLM 시대, LangChain(랭체인)으로 배우는 AI 소프트웨어 개발

by AI개발자
gaebalai-blog_ai-v3-1.jpg

(1) 토크나이저

토크나이저는 텍스트와 토큰ID 시퀀스를 상호 변환하는 소프트웨어 모듈입니다. 텍스트를 토큰ID 시퀀스로 변환하는 과정을 토큰화(tokenization) 또는 인코딩(encoding)처리하고 하며, 반대로 토큰ID 시퀀스를 텍스트로 변환하는 과정을 디코딩(decoding)처리라고 합니다.


이 글을 집필하던 시점의 주요 LLM에서 사용되는 토크나이저는 서브워드를 토큰으로 사용합니다. 서브워드는 단어를 더 작은 단위로 분해한 것입니다. 이러한 서브워드 분할에는 BPE(Byte Pair Encoding) 및 파생 알고리즘이 활용됩니다. 이들 토큰화 알고리즘은 다국어 코퍼스에서 미리 학습된 공통 어휘를 사용하여 텍스트를 일련의 토큰으로 분할합니다. 여기서 어휘는 모든 토큰의 집합을 의미하며, 코퍼스는 모델학습에 사용되는 텍스트 데이터 집합을 말합니다. 다국어 코퍼스에는 단일 언어뿐만 아니라, 영어, 한국어등 다양한 언어의 텍스트 데이터가 포함됩니다.


이 강좌에서는 기본적으로 BPE에 대해 설명합니다. BPE는 어휘표를 사용하여 인코딩 및 디코딩 처리를 수행하며, 이 어휘표는 Transformer의 학습과는 독립적으로 구축합니다. 여기서는 순새돌 디코딩 처리, 인코딩 처리, 그리고 어휘표 학습에 대해 설명합니다. 우선 어휘표의 역할을 이해하기 위해 디코딩 처리부터 살펴봅시다.


① 디코딩

디코딩은 토큰ID의 리스트를 텍스트로 변환하는 처리입니다. 토크나이저는 사전학습을 통해 얻은 어휘표를 사용하여 토큰 ID를 서브워드 단위의 텍스트로 변환합니다. 어휘표에서는 각 서브워드에 대해 고유한 토큰ID가 할당됩니다. 아래 표는 디코딩에 사용되는 어휘표의 예시를 보여줍니다.


디코딩에 사용되는 어휘표 예시

llm-langchain43-2.png

위 표에서는 각 서브워드에 고유한 토큰ID가 할당되어 있음을 확인할 수 있습니다. 토큰ID는 어휘표(배열)의 인덱스에 대응됩니다. 예를 들어, 토큰ID 1에는 "안"이라는 서브워드가, "토큰ID 2에는 "녕"이라는 서브워드가 대응됩니다. 디코딩 처리에는 입력된 각 토큰에 대해 어휘표를 참조하여 토큰 시퀀스를 텍스트로 변환합니다.


예를 들어, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0]이라는 토큰 시퀀스가 주어진 경우를 생각해 봅시다. 첫번째 토큰ID가 1이므로 표에서 '안'이 대응됨을 알 수 있습니다. 다음 토큰 ID가 2이면 마찬가지로 '녕'가 대응됩니다. 이러한 과정을 반복하면, 최종적으로 "안녕하세요!Hello world.<|end of text|>"라는 텍스트가 얻어집니다.


표는 디코딩 절차를 Python 프로그램으로 구현한 예시입니다. 표에서는 어휘표를 서브워드의 리스트(배열)인 vocab으로 구현하고 있으며, 토큰ID는 vocab의 인덱스에 대응합니다. 따라서, vocab[id]를 사용하면 해당 토큰 ID에 대응하는 서브워드를 직접 가져올 수 있습니다.


디코딩 절차(배열 버전)

llm-langchain43-3.png


② 인코딩

인코딩은 텍스트를 토큰ID 리스트로 변환하는 처리입ㅂ니다. 즉, 디코딩의 반대작업에 해당합니다. BPE방식의 토크나이저는 사전학습을 통해 얻은 어휘표를 이용해 입력 텍스트를 서브워드 단위의 토큰으로 분할하고, 각 토큰에 대응하는 ID를 출력합니다. 이 때, 토크나이저는 텍스트의 처음부터 순차적으로 가장 긴 일치하는 서브워드를 토큰ID로 변환해 나갑니다.


여기서는 간소화된 예로서 아래 표의 어휘표를 가정하고 "안녕하세요!"라는 입력 텍스트에 대한 인코딩 처리를 살펴봅시다.


입력텍스트의 시작에 매칭되는 서브워드로 아래 표에는 "안녕하세요"와 "안녕"이 존재합니다. 이 중 가장 긴 일치는 "안녕하세요"이므로, 토크나이저는 "안녕하세요"를 어휘표에 따라 1로 변환합니다. 그 다음 남은 부분 "세계!"에 대해서도 어휘표를 이용한 가장 긴 일치 변환을 적용하면 "세계"는 2로, "!"는 3으로 변환됩니다. 마지막으로 텍스트이 끝을 나타내는 특수 토큰 <|end of text|>에 해당한는 0을 추가합니다.


인코딩에 사용되는 어휘표의 예시

llm-langchain43-4.png

이와 같이 최장 일치 방식으로 변환을 효율적으로 수행하기 위해 토크나이저 구현에서는 어휘표에 '트라이 트리(trie tree)'라는 데이터구조를 사용할 수 있습니다. 트라이 트리의 각 노드는 문자를 나타내며, 루트 노드부터 리프까지의 경로가 서브워드를 표현합니다. 예를 들어, 위 표의 어휘표는 아래 그림으로 나타냅니다. "안녕하세요세계!"가 입력된 경우, 트리의 루트부터 순서대로 "안", "녕", "하", "세", "요"를 따라가고 리프인 "요"의 ID를 확인함으로써 "안녕하세요"의 ID가 1임을 알 수 있습니다.


llm-langchain44.png 트라이트리에 의한 어휘표의 예시

① 트라이 트리 생성

트라이트리를 생성하려면, 어휘표에 있는 각 서브워드를 하나씩 순회하면서, 각 문자들을 노드로 하여 트리에 추가합니다. 이 처리를 Python 프로그램으로 구현하면 위 표와 같이 작성할 수 있습니다.


트라이 트리 생성

llm-langchain45.png


TriNode 클래스: 각 노드를 표현하는 클래스입ㅂ니다. children 필드는 자식노드들을 저장하며, is_end는 해당 노드가 서브워드의 끝에 해당하는지 여부를 나타냅니다. 또한, token_id는 해당 서브워드에 대응하는 토큰ID를 저장합니다.

create_trie_tree 함수: 이 함수는 먼저 루트 노드를 생성합니다. 이후 어휘표에 있는 각 서브워드에 대해 다음 작업을 수행합니다. 서브워드의 각 문자에 대해 현재 노드의 자식노드에 추가합니다. 만약 자식 노드가 존재하지 않으면 새 노드를 생성합니다. 서브워드의 마지막 문자에 도달하면, 현재 노드의 is_end플래그를 True로 설정하고, token_id에 해당 서브워드에 대응하는 토큰ID를 할당합ㅂ니다.


이와 같이 모든 서브워드에 대해 위의 처리를 수행하면 트라이 트리가 완성됩니다.


② 트라이 트리를 이용한 인코딩

트라이트리를 이용한 인코딩 처리를 Python프로그램으로 구현하면 아래 코드와 같이 나타낼 수 있습니다.


트라이트리를 이용한 인코딩 예시

llm-langchain45-1.png

이 함수에서는 다음과 같은 처리를 수행합니다.


텍스트의 각 문자에 대해 현재 노드의 자식노드에 해당 문자가 존재하는지 확인합니다.

자식 노드에 존재하면 현재 노드를 해당 자식 노드로 업데이트합니다.

현재 노드가 서브워드 끝을 나타내는 경우 (is_end가 True인 경우), 그 서브워드에 대응하는 토큰ID를 token_ids에 추가하고, start_index를 업데이트한 후 현재 노드를 루트 노드로 되돌립니다.

자식노드에 해당 문자가 존재하지 않는 경우, 현재 노드가 루트 노드가 아니라면 해당 노드의 토큰 ID를 token_ids에 추가하고, start_index를 업데이트한 후 현재 노드를 루트 노드로 되돌립니다.

텍스트의 끝까지 처리가 완료된 후, 현재 노드가 루트 노드가 아니라면 그 노드의 토큰ID를 token_ids에 추가합니다.


이러한 처리를 통해, 텍스트를 최장 일치 방식의 섭브워드로 분할하고 대응하는 토큰 ID의 리스트를 얻을 수 있습니다.


③ 어휘표의 사전학습

BPE 어휘표는 대규모 코퍼스에 대해 사전학습을 통해 생성됩니다. 이 학습은 Transformer학습과 별도로 수행됩니다. 여기서는 Sennrich등의 논문을 기반으로 BPE어휘표 학습 절차를 설명합니다. 어휘 생성 과정에서는, 단어나 문장을 공백으로 구분한 것을 키로 해당 단어나 문장의 발생빈도를 값으로 하는 사전을 입력으로 사용합니다. 이 사전은 전처리를 통해 코퍼스에ㅔ서 생성된 것으로 가정합니다. 예를 들어, 입력 사전은 다음과 같이 구성될 수 있습니다.

llm-langchain45-2.png

여기서는 이 사전이 "안녕하세요세계.안녕하세요세계."라는 코퍼스에서 만들어졌다고 가정하지만, 실제로는 훨씬 방대한 코퍼스가 사용됩니다. 어휘표를 생성하는 알고리즘의 Python 구현은 아래 예제에 나와있습니다. 어휘표 생성은 bpe함수로 수행됩니다.

llm-langchain45-3.png

이 코드는 Byte Pair Encoding(BPE)알고리즘을 사용하여 어휘표를 학습하는 과정을 구현합니다. 이 알고리즘의 입력인 아휘사전 vocab은 공뱁ㄱ으로 구분된 서브워드(문자 시퀀스)와 그들의 출현빈도를 저장합니다. 초기 상태에서는 어휘사전에는 공백으로 구분된 문자열들이 저장되어 있습니다. 예를 들면 다음과 같은 형태입니다.

llm-langchain45-4.png

여기서 각 키는 공백으로 구분된 서브워드로 구성된 문자열이며, 값은 그 문자열의 출현빈도입니다.


get_stats 함수: 어휘사전 vocab을 입력으로 받아, 서브워드 쌍과 그 출현횟수를 담은 사전을 반환합니다.

merge_vocab 함수: 병합할 서브워드 쌍 pair와 현재 어휘사전 vocab을 입ㅂ력으로 받아, 병합된 서브워드와 그 출현횟수를 담은 새로운 어휘사전을 반환합니다. 어휘 사전 내 각 항목(공백으로 구분된 서브워드 시퀀스)에 대해 지정된 쌍을 정규표현식으로 검색하여 병합된 서브워드로 대체하ㅏㅂ니다.

bpe함수: 초기 어휘사전 vocab과 병합 횟수 num_merges를 입력으로 받아 어휘표를 반환합니다. 이 함수는 num_merges회에 걸쳐 재빈도 높은 서브워드쌍을 병합하면서 어휘사전을 갱신합니다. 최종적으로 어휘사전 내의 섭브워드를 모아 리스트 형태로 반환합니다.


예제 마지막 부분에는 동작 확인용 코드가 포함되어 이씃ㅂㅂ니다. 초기 사전부터 시작하여 0회에서 10횎싸지의 병합을 수행하고, 병합횟수마다 어휘표를 출력합니다. (이 부분은 각 반복마다 처리하므로 성능측면에서 개선의 여지가 있습니다)

프로그램 실행시, 각 행은 병합횟수0회부터 10회까지 얻어진 어휘표를 나타내며, 서서히 서브워드들이 병합되는 과정을 확인할 수 있습니다. 병합되는 순서는 출현빈도가 높은 서브워드부터 진행됩니다. 예를 들어, "안"은 "안녕하세요"와 "안녕하세요" 두 문장에서 접두사로 2회 나타나므로, 1회차(2행)에서 병합됩니다. 2회차 병합에서는 "요세"가 병합됩니다. 여기서 "요세"와 같이 2회 공기하는 "세계"가 병합되어도 좋을 것 같지만, "요세"가 먼저 나타납니다. 보다 현실적인 코퍼스에서는 아마도 "세계"가 먼저 병합될 가능성이 높습니다.



(2) 토큰 샘플링 기법

앞서 설명했던 Transformer가 입력된 토큰ID 시퀀스로부터 다음 토큰ID를 예측하고, 선택된 토큰ID를 현재 입력에 추가하면서 종료 토큰이 생성될 때까지 응답 메시지를 생성하는 과정을 설명했습니다. 이 과정에서는 확률 분포를 조정하기 위한 파라미터들을 사용하여 생성되는 응답 메시지의 다양성과 확정성을 조절할 수 있습니다. 특히 현재 주류 LLM에서는 다음과 같은 파라미터들이 자주 사용됩니다.


temperature

top-k

top-p


temperature는 사실상 대부분의 LLM에서 지원되는 파라미터입니다. 반면 top-k와 top-p는 각 LLM이 채택한 샘플링 기법에 따라 사용됩니다. 자주 사용되는 샘플링 기법으로는 top-k샘플링과 top-p샘플링(또는 nucleus smapling)이 있으며, 각각 top-K와 top-p 파라미터를 활용합니다.

이번에는 이러한 파라미터들이 어떻게 사용되지는지에 대해 설명합니다.


① temperature

temperature는 확률 분포를 조정하기 위한 파라미터입니다. 먼저 temperature가 확률 분포에 어떤 영향을 미치는지 살펴본 후, 그 계산방식에 대해 설명하겠습니다.


▣ 확률분포의 조정

낮은 temperature 값: 낮은 temperature를 설정하면 확률 분포가 뾰족해져, 높은 확률을 가진 토큰이 선택될 가능성이 더 높아집니다.

높은 temperature 값: 높은 temperature를 설정하면 확률 분포가 평탄해져, 낮은 확률의 토큰도 선택될 가능성이 커집니다.

temperature = 1: 이 경우 원래 확률분포가 그대로 사용됩니다.


예를 들어, 언어모델이 "내가 좋아하는 과일은 ...."이라는 문장의 계속을 생성하고 있다고 가정해 봅시다. 토큰과 그 확률분포가 다음과 같이 주어졌다고 합시다.


사과: 0.4

딸기: 0.3

바나나: 0.2

귤: 0.1


여기에 temperature값으로 1, 0.5, 2를 적용한 경우의 확률분포는 아래 그림과 같이 나타납니다.


llm-langchain45 (1).png temperature 효과

위 그림의 왼쪽은 temperature가 1인 경우의 확률 분포를 보여줍니다. 이 경우, 원래 확률 분포가 그대로 사용됩니다. 가운데 temperature가 0.5인 경우 확률분포로 확률 분포가 뾰족해져서 높은 확률을 가진 토큰이 선택되기 쉬워지는 모습을 나타냅니다.


▣ 계산 방법

LLM API 내부에서는 주어진 문맥을 바탕으로 다음 토큰의 확률 분포 P(xi|x1|i-1)를 계산합니다. 여기서 xi는 i번째 토큰, x1|i-1은 그 이전에 생선된 토큰 시퀀스를 나타냅니다. 이 확률분포는 각 토큰이 다음에 등장할 가능성을 의미하며, 언어모델 API내부에서는 이 확률 분포를 조정하기 위해 temperature파라미터 T를 사용합니다. T는 양의 실수로 일반적으로 0부터 1사이의 값이 쓰입니다.

llm-langchain45-2 (1).png

▣ 계산예시

다음과 같은 확률 분포가 있다고 가정해 봅시다.


사과: 0.4

딸기: 0.3

바나나: 0.2

귤: 0.1


temperature=1인 경우, 언어모델은 이 확률 분포를 그대로 사용하여 다음 토큰을 선택합니다. 그렇다면 temperature=0.5일 때는 어떻게 될까요? 이 경우, 확률 분포는 다음과 같이 스케일링합니다.

llm-langchain45-3 (1).png

이렇게 스케일링된 확률값을 정규화하면 다음과 같습니다.

즉, temperature가 0.5일 때는 원래 확률이 가장 높았던 '사과'의 선택확률이 더욱 커지게 됩니다. 다시 말해, temperature값이 낮을수록 고확률 토큰이 더욱 강조됩니다. 반대로 temperature=2인 경우도 살펴보겠습니다. 확률 분포는 다음과 같이 스케일링됩니다.

llm-langchain45-5.png

이를 정규화하면,

이처럼 temperature가 커지면, 고확률 토큰과 저확률 토큰 간의 격차가 줄어들어, 보다 댜양한 토큰이 선택될 가능성이 커집니다.


② top-k

top-k는 top-k 샘플링에서 사용되는 파라미터입니다. top-k 샘플링에서는 확률이 높은 상위 k개의 토큰 중에서 다음 토큰을 선택합니다. top-k값이 클수록 선택가능한 토큰의 다양성이 증가합니다. 예를 들어, 다양한 응답ㅂ을 생성하고자 하는 대화에서는 높은 top-k값을 설정할 수 있습니다. 반대로 top-k값이 작으면 다양성이 줄어들어, 일관성있는 지시서나 절차서를 생성할 때 적합합니다.

예를 들어, 언어모델이 "내가 좋아하는 과일은 ...."이라는 문장을 생성하고 있고 토큰과 그 확률 분포가 다음과 같다고 가정해 봅시다.


사과: 0.4

딸기: 0.3

바나나: 0.2

귤: 0.1

llm-langchain46.png top-k 효과

위 그림은 top-k의 효과를 보여줍니다. 만약, top-k가 2로 설정되면, 언어모델은 확률이 높은 상위 2개의 토큰('사과'와 '딸기') 중에서 다음 토큰을 선택하게 되어, '바나나'와 '귤'은 선택될 가능성이 사라집니다.

반면에 top-k가 3으로 설정되면 상위 3개의 토큰('사과', '딸기', '바나나') 주엥서 선택하게 되어 '귤'은 선택될 가능성이 없게 됩니다. 그리고 top-k가 4로 설정되면 원래 선택지가 4개이므로 모든 토큰이 선택될 수 있습니다.


이처럼 top-k값을 적절히 설정함으로써 생성되는 텍스트의 품질을 제어할 수 있습니다. top-k값을 작게 설정하면 확률이 높은 토큰만 선택되므로 일관성있는 텍스트가 생성되고, 반대로 top-k값을 크게 설정하면 보다 댜앙한 토큰이 선택되어 창의적인 텍스트가 생성됩니다.


예를 들어, top-k값을 1처럼 작게 설정하여 뉴스 기사를 생성하면 언어모델은 항상 가장 확률이 높은 토큰을 선택하므로 사실에 깁반한 일관성있는 기사가 생성됩니다. 반면에 top-k값을 50과 같이 크게 설정하여 창의적인 이야기를 생성하면 상위 50개의 토큰 중에서 무작위로 선택하므로 예측할 수 없고 다양성이 풍부한 이야기가 생성됩니다.


③ top-p (Nucleus Sampling)

top-p는 top-p 샘플링 (Nucleus Sampling)에서 사용되는 파라미터입니다. top-p샘플링에서는 확률의 누적값이 임계치 top-p이상이 될 때까지의 토큰들을 선택지로 사용합니다.

예를 들어, 언어모델이 "내가 좋아하는 과일은 ...."이라는 문장의 계속을 생성하고 있고, 다음 토큰의 확률 분포가 다음과 같다고 가정합시다.


사과: 0.4

딸기: 0.3

바나나: 0.2

귤: 0.1


만약, top-p가 0.7로 설정되어 있다면, 언어모델은 확률의 누적값이 0.7이상이 될 때까지 토큰들을 선택지로 사용합니다. 이 예시에서는 우선 '사과'를 선택지에 포함시켜 누적확률이 0.4가 되고, 다음으로 '딸기'를 추가하여 누적확률이나 0.4 + 0.3 = 0.7가 됩니다. 이 시점에서 누적 확률이 0.7이상이 되었으므로, '사과'와 '딸기'가 다음 토큰의 선택지로 사용되며, '바나나'과 '귤'은 선택지에서 제외됩니다.


top-p값을 작게 설정하면 확률이 높은 토큰만 선택지에 포함되어 일관성있는 텍스트가 생성되고, 반대로 top-p값을 크게 설정하면 보다 다양한 토큰이 선택되어 창의적인 텍스트가 생성됩니다. 예를 들어, top-p를 0.5와 같이 작게 설정하여 뉴스 기사를 생성하면 누적 확률이 0.5이상이 될 때까지의 토큰만 선택지로 사용되므로 사실에 기반한 일관성있는 기사가 생성될 것으로 기대됩니다. 반면에 top-p를 0.95와 같이 크게 설정하여 창의적인 이야기를 생성하면 누적 확률이 0.95이상이 될때까지 토큰이 선택지에 포함되어 예측할 수 없고, 다양성이 푸우한 이야기가 생성될 것으로 예상됩니다.



©2024-2025 GAEBAL AI, Hand-crafted & made with Damon Jaewoo Kim.

GAEBAL AI 개발사: https://gaebalai.com

AI 강의 및 개발, 컨설팅 문의: https://talk.naver.com/ct/w5umt5


keyword
이전 03화2. Transformer의 구조