[NLP Project] subword tokneizer(1)

주혜린·2023년 8월 23일
0

[NLP Project]

목록 보기
2/3

subword tokenizer

  • subword tokenizer: 하나의 단어를 여러 개의 하위 단위로 나누는 방법

[Byte Pair Encoding(BPE)]

  • 본래 데이터를 압축하는 알고리즘으로 데이터에서 가장 많이 등장한 문자열을 병합해서 압축하는 기법이다.
[aaabdaaabac]
위와 같은 문자열이 주어졌다고 가정하면, 단어에 등장한 글자로 사전을 구성하면 (a, b, c, d)가 된다.
이제 가장 많이 등장하는 연속하는 두 글자를 하나로 병합합하면, 여기서는 aa가 동시에 많이 등장하므로 이를 Z로 치환할 수 있다.

[ZabdZabac]
이제 글자 사전은 (a, b, c, d, Z)가 된다.
이 문자열에서 다시 ab를 Y로 압축할 수 있다.

[ZYdZYac]
사전에는 Y가 추가되어서 (a, b, c, d, Z, Y)가 된다.
마지막으로 ZY를 묶어서 X로 치환하면 아래와 같이 압축할 수 있다.

[XdXac]
  • BPE의 가장 큰 장점은 분석 대상 언어에 대한 사전 지식이 필요 없다. 그저 말뭉치 데이터만 주어지면 자주 나타나는 문자열을 토큰으로 분석하기 때문에 어떤 언어에도 적용이 가능하다.

  • 학습 순서

    1. 먼저 주어진 문장을 사용해 단어 사전을 만들어 보았다.
      corpus = [
        "이번에는 자연어 처리에 대해 공부했다",
        "자연어 처리에는 다양한 기법들이 있다",
        "그 중에서도 이번에는 서브워드 토큰화를 해보았다",
        "서브워드 토큰화는 어떤 언어에도 적용할 수 있다는 장점이 있다"
      ]
      띄어쓰기를 기준으로 나눠준 후 사전을 만든다.
    
      words = []
      for sentence in corpus:
      	words.extend(sentence.split(" "))

      vocabs = set()
	  for word in words:
      	for c in word:
        	vocabs.add(c)

  1. 단어 사전을 이용하여 각 단어들을 토큰화 하는 함수를 만든다.

    import re
    
      def build_pattern(vocabs):
        sorted_vocabs = sorted(vocabs, key=lambda x: len(x), reverse=True)
        pattern = re.compile(r"|".join(sorted_vocabs))
        return pattern

  2. 함께 자주 등장하는 쌍을 찾는다.

    from collections import Counter
    
    def count_pairs(words, pattern):
        c = Counter()
        for word in words:
            tokens = pattern.findall(word)
            for i in range(len(tokens)-1):
                c[f"{tokens[i]}{tokens[i+1]}"] += 1
        return c

  3. 원하는 단어수를 만족할 때까지 반복한 뒤 만들어진 사전을 이용하여 토큰화 한다.
    학습과정에서 사용하지 않은 단어가 들어간 문장을 위의 사전을 이용해 토큰화하면 아래와 같은 결과가 나온다.

    new_sentence = "다음에는 어떤 토큰화 기법에 대해서 공부할지 기대된다"

[huggingface tokenizers]

  • huggingface 회사가 제공하는 subword tokenizer의 구현체이다.

[huggingface 사이트]
https://huggingface.co/docs/tokenizers/index

  • 토큰화를 하기 전에 대문자를 소문자로 변경해주고, 한글, 영어, 물음표, 느낌표, 마침표, 쉼표, 작은 따옴표, 공백을 제외한 나머지 요소를 제거해준다.
import re

def preprocess_text(text):
    text = text.lower()
    text = re.sub(r"[^ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z!?.,\' ]", "", text)
    text = re.sub(r"\s+", " ", text)
    text = text.strip()
    return text
  • WordPiece를 기반으로 subword tokenizer를 해주는 trainer를 만든다.
    이때 WordPiece는 subword tokenizer 기법 중 하나이며, 주로 자연어 처리 모델인 BERT와 같은 사전 학습된 모델에서 사용되는 토큰화 방법 중 하나이다.
import tokenizers
from tokenizers.trainers import WordPieceTrainer

trainer = WordPieceTrainer(
    vocab_size=10000,
    min_frequency=50,
    special_tokens=["[PAD]", "[UNK]", "[SOS]", "[EOS]"]
)

--> vocab_size: 훈련된 모델이 포함할 최종 하위 단위 토큰의 개수를 결정한다. 이 값이 클수록 더 세밀한 토큰화가 이루어지지만, 모델의 크기와 처리 시간이 증가할 수 있다. 보통 훈련 데이터에서의 고유한 단어 수를 기준으로 설정한다.
min_frequency: 훈련 데이터에서 최소한으로 등장하는 빈도수를 나타낸다. 이 값보다 빈도수가 낮은 하위 단위 토큰은 무시된다. 이를 통해 희귀한 단어나 노이즈를 줄일 수 있다.
--> [PAD]: padding의 약자로 문장 간에 길이를 맞춰주기 위해 일부러 채워넣은 토큰을 의미
[UNK]: unknown의 약자로 인식하지 못한 토큰을 나타냄
[SOS]: start of sentence의 약자로 문장의 시작을 표시
[EOS]: end of sentence의 약자로 문장의 끝을 표시

  • 마지막은 tokenizer 객체를 만들어주고 trainer를 이용해서 학습시키는 단계이다. 데이터프레임에서 배치 단위로 데이터를 추출하는 반복자(iterator)를 정의하는 함수로, 주어진 데이터프레임에서 지정한 배치 크기만큼의 데이터를 추출하여 순차적으로 처리할 수 있다.
def batch_iterator(df, batch_size=1000):
    for i in range(0, len(df), batch_size):
        batch_df = df.iloc[i:i+batch_size]
        yield batch_df["document"]

--> batch_size: 보통 batch_size는 2의 제곱수로 설정하는 것이 일반적이다.
이는 하드웨어 가속기(GPU, TPU)의 연산 최적화 및 메모리 할당과 관련된 이유가 있다.
하지만 데이터의 크기, 하드웨어의 성능, 모델의 복잡도 등에 따라 조정이 필요할 수 있다.
실험을 통해 다양한 batch_size를 시도하여 모델의 성능과 훈련 시간을 평가하며 적절한 값을 찾는 것이 좋다.

--> yield: Python에서 제너레이터를 생성하는데 사용되는 함수이다.
제너레이터는 이터레이터(순회 가능한 객체)를 간편하게 만들어주는 방법 중 하나로, 데이터를 한 번에 모두 메모리에 로드하지 않고 필요한 시점에 데이터를 생성하거나 반환할 수 있도록 도와준다.

from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.pre_tokenizers import Whitespace

tokenizer = Tokenizer(WordPiece())
tokenizer.pre_tokenizer = Whitespace()
tokenizer.train_from_iterator(batch_iterator(train_df), trainer=trainer)

--> Whitespace(): 이것은 pre_tokenizer로 사용되는 것으로, 입력 텍스트를 공백 문자를 기준으로 분리하여 초기 토큰화를 수행한다.
예를 들어, 문장 "Hello, world!"는 "Hello,"와 "world!"로 분리된다.

  • 아래 코드를 사용하면 tokenizer가 학습한 vocabs를 확인할 수 있다.
vocabs = tokenizer.get_vocab()
sorted_vocabs = sorted(vocabs.items(), key=lambda x: x[1])
profile
💻🐜💡

0개의 댓글