토크나이저 확장

개념정리

자연어 처리(NLP)에서 임베딩은 매우 중요한 역할을 하지만, 모든 NLP 작업에 필수적인 것은 아닙니다. 그리고 임베딩에 필요한 어휘 사전(vocab)은 모델이 다양한 방식으로 전달받거나 가질 수 있습니다.

임베딩의 필요성:

필수적인 경우:

  • 의미적 유사성 계산: 단어 또는 문장의 의미적 유사성을 계산해야 하는 작업(예: 검색 엔진, 추천 시스템)에서는 임베딩이 필수적입니다.

  • 복잡한 의미 이해: 문장의 복잡한 의미를 이해해야 하는 작업(예: 질의응답, 기계 번역)에서도 임베딩은 성능 향상에 크게 기여합니다.

  • 텍스트 분류 및 감성 분석: 텍스트 분류나 감성 분석과 같은 작업에서도 임베딩은 모델이 단어의 의미적 정보를 활용하여 더 나은 결과를 얻도록 돕습니다.

필수적이지 않은 경우:

  • 단순 문자열 처리: 단순한 문자열 처리 작업(예: 특정 문자열 검색, 간단한 텍스트 전처리)에서는 임베딩 없이도 충분히 수행할 수 있습니다.

  • 규칙 기반 시스템: 규칙 기반의 NLP 시스템에서는 임베딩 대신 규칙이나 패턴 매칭을 사용할 수 있습니다.

어휘 사전(vocab)의 전달 및 보유:

  • 모델 내장:
    일부 사전 훈련된 언어 모델(예: BERT, GPT)은 자체적으로 어휘 사전을 가지고 있습니다. 이러한 모델은 토큰화를 수행하고 임베딩을 생성하는 데 필요한 정보를 이미 내장하고 있습니다.

  • 외부 전달:
    사용자가 직접 어휘 사전을 구축하여 모델에 전달할 수 있습니다. 특히, 특정 도메인이나 작업에 특화된 어휘 사전을 사용하는 경우에 유용합니다.
    토크나이저를 통해 어휘 사전을 전달할 수 있습니다. 토크나이저는 텍스트를 토큰으로 분할하고 각 토큰에 해당하는 ID를 생성하는 역할을 하며, 어휘 사전을 포함하고 있습니다.

  • 동적 생성:
    일부 모델은 입력 텍스트를 기반으로 동적으로 어휘 사전을 생성할 수 있습니다. 이러한 방식은 어휘 사전의 크기를 줄이고 메모리 사용량을 최적화하는 데 도움이 됩니다.


0. 토크나이저 확장

1-1. 직접 단어별(토큰별) 추가

base_model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(base_model_name)
model = AutoModel.from_pretrained(base_model_name)

print(f"\n기존 토크나이저 정보:")
print(f"어휘 크기: {len(tokenizer)}")

기존 토크나이저 정보:
어휘 크기: 30522

# 직접 토큰 추가
tokenizer.add_tokens(['1839년', '바그너', '괴테'])
len(tokenizer)

30525 (+3)


1-2. 참고 문서를 단어로 나눈 뒤 추가. (ex: 한국형 토크나이저에서 명사함수) 

KorQuAD 데이터셋에서 데이터를 추출한 뒤에, 연결된 리스트 형태로 만든다.
okt.nouns() 함수를 사용해, 명사만 뽑는다.

import konlpy
from konlpy.tag import Okt

okt = Okt()
okt_nouns = [noun for noun in okt.nouns(all_texts[0]) if len(noun) > 1]
print(len(okt_nouns))

89

리스트로 만들어서, 토큰 추가

okt_nouns = list(set(okt_nouns))

tokenizer.add_tokens(okt_nouns)
len(tokenizer)

30592 (+67)


1-3. 다른 토크나이저를 가져와서 토크나이저에 추가 (ex: 한국형 형태소 토큰화) 

형태소 토큰화를 기존 토크나이저에 추가

# 다른 토크나이저 토큰화 기반 토큰 추가
kor_tokenizer = AutoTokenizer.from_pretrained('klue/bert-base')
kor_tokens = kor_tokenizer.tokenize(all_texts[0])
tokenizer.add_tokens(kor_tokens)
len(tokenizer)

30731 (+139)


2. 모델의 임베딩 어휘사전 리사이징

새로 추가된 임베딩 벡터는 초기에는 무작위 값 또는 0으로 시작하기 때문에, 모델이 새로운 토큰을 잘 처리하려면 추가적인 학습이 필요.

Q: 토크나이저에 새롭게 추가된 토큰이 있는데, 모델을 리사이징 하지 않아 빈공간이 없는 경우, 모델은 새 토큰을 어떻게 처리하는가? <UNK> 벡터로 바꿔버리거나, 오류를 발생시킨다.

origin_model = copy.deepcopy(model)
origin_model.get_input_embeddings().weight.shape

torch.Size([30522, 768])

model.resize_token_embeddings(len(tokenizer))

Embedding(30731, 768, padding_idx=0)

embeddings = model.get_input_embeddings()
embeddings.weight.shape

torch.Size([30731, 768])


토크나이저에 토큰 추가, 모델의 어휘사전 리사이징

def enhance_tokenizer(all_texts, base_tokenizer,
                    base_model, min_morph_len=2):
   """
   토크나이저 개선

   Args:
   - all_texts: 토큰 추출에 사용할 전체 텍스트 리스트
   - base_tokenizer: 기본 토크나이저
   - base_model: 기본 모델
   - min_morph_len: 형태소 토큰의 최소 길이

   Returns:
   - 토큰 추가된 토크나이저
   - 추가된 토큰 리스트
   """

   # Okt 형태소 분석기 초기화
   okt = Okt()

   # KLUE BERT 토크나이저 로드
   klue_tokenizer = AutoTokenizer.from_pretrained('klue/bert-base')

   # 형태소 분석 기반 토큰 추출
   morph_tokens = []
   for text in all_texts:
       nouns = okt.nouns(text)
       morph_tokens += [noun for noun in nouns if len(noun) >= min_morph_len]

   # KLUE BERT 토큰화 기반 토큰 추출
   klue_tokens = []
   for text in all_texts:
       tokens = klue_tokenizer.tokenize(text)
       klue_tokens += [token for token in tokens if token not in base_tokenizer.vocab]

   # 토큰 통합 및 중복 제거
   combined_tokens = list(set(morph_tokens + klue_tokens))

   # 토크나이저에 토큰 추가
   num_added_tokens = base_tokenizer.add_tokens(combined_tokens)
   base_model.resize_token_embeddings(len(base_tokenizer))

   # 결과 출력
   print(f"추가된 토큰 수: {num_added_tokens}")
   print("추가된 토큰 예시:", combined_tokens[:10])

   return base_tokenizer, combined_tokens


추가실습A: 1. 다음 어휘들이 토큰이 있는지 확인

tokenizer.get_vocab()

# 기존 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained("beomi/llama-2-ko-7b")  # 한국어 지원 LLaMA 모델
existing_vocab = set(tokenizer.get_vocab().keys())
print(len(tokenizer), len(existing_vocab))

# 새 토큰 목록
candidate_new_tokens = [
    "자연어처리", "자연어", "처리",
    "기계학습", "기계", "학습",
    "딥러닝", "딥", "러닝",
    "인공지능", "인공", "지능"
]

# 기존 어휘에 없는 토큰만 필터링하는 코드 작성해보기
filtered_new_tokens = [token for token in candidate_new_tokens if token not in existing_vocab]
filtered_new_tokens


추가실습A: 2. 토큰 추가

tokenizer.add_tokens(new_tokens)

# 필터링된 토큰만 추가
num_added_tokens = tokenizer.add_tokens(filtered_new_tokens)
print(len(existing_vocab), len(tokenizer.get_vocab()))


추가실습A: 3. 토큰화 최적화 평가

복사 함수 정리

import copy

origin = [1, 2, 3]  
1. 참조 복사: 주소 공유, 한쪽 수정시 연동  
    new = origin
2. 얕은 복사: 새 객체 선언, 중첩구조에서 내부 연동  
    new = origin.copy()
    new = copy.copy(origin)
3. 깊은 복사: 새 객체 선언, 중첩구조에서 내부연동 안됨  
    new = copy.deepcopy(origin)

def evaluate_token_addition2(tokenizer, candidate_token, test_corpus):
    '''
    toeknizer: 기존 토크나이저
    candidate_token : 새롭게 추가할 토큰 리스트
    test_corpus : 여러 테스트 문장
    '''

    # 토큰 추가 전 토큰화 '길이'
    original_token_count = sum(len(tokenizer.tokenize(text)) for text in test_corpus)

    # 임시 토크나이저에 후보 토큰 추가
    temp_tokenizer = copy.deepcopy(tokenizer)
    temp_tokenizer.add_tokens(candidate_token)

    # 토큰 추가 후 토큰화 길이
    new_token_count = sum(len(temp_tokenizer.tokenize(text)) for text in test_corpus)

    # 토큰화 효율성 향상 비율
    improvement = (original_token_count - new_token_count) / original_token_count

    print(improvement, original_token_count, new_token_count)

test_corpus = ['HD현대오일뱅크/SK에너지 우대 주유소 25~85 포인트 적립.',
               '롯데월드, 서울랜드 자유이용권 본인 50% 할인, 캐리비안베이 입장권 본인 30% 할인',
               '배달의민족, 요기요, 쿠팡이츠 4% 청구할인',
                'G마켓, 옥션, 11번가, 인터파크, 위메프, 티몬 4% 청구할인',
               '카카오T, UT, 택시업종 4% 청구할인']

filtered_new_tokens = ['HD현대오일뱅크', 'SK에너지', '뽥',
                        '롯데월드', '서울랜드', '캐리비안베이',
                        '배달의민족', '요기요', '쿠팡이츠',
                        'G마켓', '옥션', '11번가', '인터파크', '위메프', '티몬'
                        '카카오T', 'UT']

evaluate_token_addition2(tokenizer, filtered_new_tokens, test_corpus)


improvement: 0.2336448598130841 
original_token_count: 107
new_token_count: 82




댓글 쓰기

다음 이전