Unity/Barracuda & Sentis

[Unity Sentis] Unity에서 Tokenizer 구현하기 - 샘플 조사

pnltoen 2025. 4. 21.
반응형

Tokenizer

Unity technologies


서문

Hugging Face에 보면 Sentis에서 MiniLM을 사용할 수 있도록 샘플데모가 공개되어 있다.

 

unity/sentis-MiniLM-v6 · Hugging Face

This model is not currently available via any of the supported Inference Providers. The model cannot be deployed to the HF Inference API: The HF Inference API does not support sentence-similarity models for unity-sentis library.

huggingface.co

 

이 중 Tokenizer와 관련된 내용 몇개를 적어보려고 한다.

 

Tokenizer란, LLM에서 말뭉치 데이터의 효율적인 분류를 위해 사용한다. 쉽게 데이터의 전처리 과정이라고 생각하면 되는데 그 예로 Sentis/MiniLM의 Sample inputs을 보면 다음과 같이 2개의 문장을 확인할 수 있다.

 

 

이 중 string1을 어떻게 나누는게 좋을까 라는 방법에서 Tokenizer의 개념이 정립되었다.

{t, h, i, s,  , i, s,  , a,  , h, a, p, p, y,  , p, e, r, s, o, n}와 같이 단어를 나누게 된다. 이렇게 모든 문장을 나누게 된다면 각 단어 및 알파벳을 모두 기록해야 하기 때문에 메모리가 증가하고 일관성 있는 패턴을 학습하기 어렵다. 가장 중요한 부분으로는 OOV (Out of Vocabulary) 와 Typo 문제에서 자유로을 수 없다.

 

따라서 Sub-word 방식의 Tokenizer 방식을 선호하게 되는데 일반적인 방법이 BPE(Byte Pair Encoding) 그리고 Word Piece 방식이다.

 

두 방식 모두 장단점이 있지만 이번 포스팅에서는 Word Piece 방식을 Pytorch에서 모델을 학습하고 어떻게 유니티에서 읽어 올 수 있는지 MiniLM의 GetTokens 함수를 볼 예정이다.

 

본문

List<int> GetTokens(string text)
    {
        //split over whitespace
        string[] words = text.ToLower().Split(null);

        var ids = new List<int>
        {
            START_TOKEN
        };

        string s = "";

        foreach (var word in words)
        {
            int start = 0;
            for(int i = word.Length; i >= 0;i--)
            {
                string subword = start == 0 ? word.Substring(start, i) : "##" + word.Substring(start, i-start);
                int index = System.Array.IndexOf(tokens, subword);
                if (index >= 0)
                {
                    ids.Add(index);
                    s += subword + " ";
                    if (i == word.Length) break;
                    start = i;
                    i = word.Length + 1;
                }
            }
        }

        ids.Add(END_TOKEN);

        Debug.Log("Tokenized sentece = " + s);

        return ids;
    }

 

전체 코드는 Hugging Face의 코드에서 확인할 수 있다.

 

MiniLMv6.cs · unity/sentis-MiniLM-v6 at main

(input1, input2) => Functional.ReduceSum(input1 * input2, 1),

huggingface.co

 

기본적으로 유니티는 Vocab 파일을 StreamingAssets에 포함하고 이는 tokens에 저장된다. Wordpiece를 고려하지 않고 위의 코드만 생각해보자 위의 코드는 substring 방식으로 어떠한 단어 예: Unity가 들어왔을 때 뒤에서 부터 슬라이싱 하는 방식으로 작동한다.

  • Unity -> tokens에 있는지 확인 있으면 등록 없으면 우측에서 부터 하나씩 줄임
  • Unit -> tokens에 있는지 확인 있으면 등록 없으면 하나 더 줄임
  • Uni -> 반복
  • Un -> 반복
  • U -> 반복

왜 우측부터 자를까? 좌측부터 자르면 안되나...? 여기서 부터 예측인데 일반적으로 단어는 앞에 의미를 갖고 있는 경우가 많다.

예를 들어서 영어는 형태가 되게 다양한데 play, played, playful, playing 등등 근데 여기서 생각해봤을 때 ed, ful, ing가 중요할까 아니면 play가 중요할까? 당연히 답은 의미를 가진 play를 남기는 것이 중요할 것이다. 따라서 앞을 남기는 방식 = 뒤를 자르는 방식으로 구현한게 아닐까 생각한다.

 

이쯤에서 다시 Wordpiece에 대해 생각해보자 Wordpiece는 학습된 Vocab을 기준으로 tokenizer를 진행하였는데 이렇게 우측에서 부터 자르는 방법이 정확하게 100% 동일한 결과를 얻을 수 있을까? 당연히 없다... 하지만 최적화의 관점으로 봤을 때 일반적으로 좌측의 단어의 의미가 있는 경우가 많으니 이렇게 해도 어느정도 비슷한 경향의 토크나이저를 만들 수는 있다.

 

하지만 전반적으로 부정확한 tokenizer는 추 후 모델을 실행할 때 부정확한 결과를 기인하게 됨으로... tokenizer를 직접 만들어 보는 것이 좋을 것 같다.

반응형

댓글