개념정리
자연어 처리(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
Tags:
AI개발_교육