-
[자연어처리] N-gram으로 아이오아이 너무너무너무 언어모델 학습하기. 다음에 올 가사 생성하기자연어처리 공부 2024. 1. 3. 00:01
▶︎ N-gram 이란?
→ N개의 단어를 묶어 독립된 피처로 생각하여 표현하는 방법
→ N개의 연속된 단어 시퀀스를 파악하여 다음 단어를 예측하는 언어 모델입니다.
→ P(맛있다 | 빨간, 사과는) = n(빨간, 사과는, 맛있다) / n(빨간, 사과는)
: "빨간", "사과는" 다음에 맛있다가 나올 확률은 "빨간", "사과는", "맛있다" 가 나온 횟수를 "빨간", "사과는" 이 나온 횟수로 나눈 조건부 확률 형태로 나타낼 수 있다
▶︎ N-gram의 장점은?
→ 단어의 등장횟수를 카운트하는 BOW나 해당 문서에서 단어의 중요성을 수치화한 TF-IDF에서 할 수 없는 순서를 유지시킬 수 있으며,
→ 붙어 있을 때 또다른 의미를 갖는 단어들을 유지시킬 수 있으며
→ 주변 단어들 간의 관계를 파악하여 문맥을 이해하는데 도움이 된다
▶︎ "I like studying natural language processing" 으로 예시를 들어보겠습니다
from nltk import ngrams from nltk.tokenize import word_tokenize text = 'I like studying natural language processing' # 문장을 단어로 토큰화 tokens = word_tokenize(text) # bi-gram uni_grams = list(ngrams(tokens, 1)) bi_grams = list(ngrams(tokens, 2)) tri_grams = list(ngrams(tokens, 3)) # 결과 출력 print(uni_grams) print(bi_grams) print(tri_grams) # Result [('I',), ('like',), ('studying',), ('natural',), ('language',), ('processing',)] [('I', 'like'), ('like', 'studying'), ('studying', 'natural'), ('natural', 'language'), ('language', 'processing')] [('I', 'like', 'studying'), ('like', 'studying', 'natural'), ('studying', 'natural', 'language'), ('natural', 'language', 'processing')]
▶︎ N-gram의 단점은?
→ 순서를 유지할 수는 있지만 그룹된 단어끼리만 학습되기 때문에 문맥 파악에 제한이 있다.
→ 그렇다고 N을 높이면 더 긴 연속된 단어 시퀀스를 고려할 수 있지만 피처의 개수가 기하급수적으로 증가하며, 벡터가 sparse 해지며 모델이 복잡해집니다.
→ 또한 N이 커지거나 특이한 조합일수록 등장할 빈도가 0에 가깝기 때문에 확률을 구할 수 없는 문제가 발생합니다.
→ 그래서 확률통계가 아닌 딥러닝 기반의 고급 언어 모델로 진화하였고, RNN이나 Transformer 아키텍처를 사용한 모델이 등장하였습니다.
▶︎ 그래도 N-gram은 간단하고 기본적인 언어 모델이기 때문에 재미난 예시를 들어보려고 합니다. "너무"라는 단어가 너무 많이 들어간, 중독성을 자랑하는 아이오아이의 너무너무너무 라는 노래로 예제를 만들어보았습니다.
1. 노래 가사 불러오기
if os.path.isfile('/content/너무너무너무.txt'): with io.open('/content/너무너무너무.txt', encoding='utf8') as fin: text = fin.read()
2. sent_tokenize 문장 단위로 나누고 word_tokenize 문자 단위로 나누어 토큰화합니다.
from nltk import word_tokenize, sent_tokenize tokenized_text = [list(map(str, word_tokenize(sent))) for sent in sent_tokenize(text)]
3. 문장 앞뒤에 <s>, </s> 를 패딩한 후, 3-gram 언어 모델을 훈련하기 위한 파이프라인을 만듭니다.
n = 3 train_data, padded_sents = padded_everygram_pipeline(n, tokenized_text)
4. Maximum Likelihood Estimation (MLE) 언어 모델을 만듭니다.
from nltk.lm import MLE model = MLE(n) print(len(model.vocab)) model.fit(train_data, padded_sents) print(model.vocab)
5. vocab 사전에 없는 단어는 <UNK> 로 처리되는 것을 볼 수 있습니다.
# 자꾸자꾸자꾸 떠오르면 그때 불러줘? 가 원래 가사입니다. print(model.vocab.lookup('자꾸자꾸자꾸 떠오르면 그때 불러줄래?'.split()))
6. "너무너무너무 너무너무너무" 뒤에 "너무너무너무" 가 나올 확률은 71.6%, "좋아하면"이 나올 확률은 23.5% 인 것을 확인 할 수 있습니다. 노래가 "너무너무너무" 단순하고 재밌네요 ^^
model.counts[['너무너무너무']]['너무너무너무'] # i.e. Count('너무너무너무'|'너무너무너무') 17 model.counts[['너무너무너무', '너무너무너무']]['좋아하면'] # i.e. Count('좋아하면'|'너무너무너무 너무너무너무') 4 model.score('좋아하면', '너무너무너무 너무너무너무'.split()) # P('좋아하면'|'너무너무너무 너무너무너무') 0.235 == 4/17
model.counts[['너무너무너무']]['너무너무너무'] # i.e. Count('너무너무너무'|'너무너무너무') 17 model.counts[['너무너무너무', '너무너무너무']]['너무너무너무'] # i.e. Count('너무너무너무'|'너무너무너무 너무너무너무') 12 model.score('너무너무너무', '너무너무너무 너무너무너무'.split()) # P('너무너무너무'|'너무너무너무 너무너무너무') 0.716 == 12/17
▶︎ 학습된 N-gram 모델로 새로운 텍스트를 생성해보겠습니다.
from nltk.tokenize.treebank import TreebankWordDetokenizer detokenize = TreebankWordDetokenizer().detokenize def generate_sentence(model, num_words, random_seed=42): content = [] for token in model.generate(num_words, random_seed=random_seed): if token == '<s>': continue if token == '</s>': break content.append(token) return detokenize(content)
생성된 가사 비교해보기 (원래 <s>, </s> 가 껴있어서 따로 함수를 만든건데 사실 "너무너무너무" 가사가 단순해서 학습이 너무 잘되어있어서 비슷한? 똑같은? 결과가 나옵니다. )
# 28개의 글자로 가사 생성하기 print(model.generate(28, random_seed=0))
# 28개의 문자로 가사를 생성할건데, <s> 부터 시작이고 </s>까지만 가사로 인정할 것입니다. 그래서 28보다 작을 수 있어요 generate_sentence(model, 28, random_seed=0)
'자연어처리 공부' 카테고리의 다른 글
[ChatGPT] 왜 프롬프트 지니를 써야할까? (0) 2024.01.09 [자연어처리] Transformer 쉽게 풀어서 정리해드립니다. (0) 2024.01.05 [자연어처리] Text-CNN 구현하기 (코드 위주) (0) 2024.01.02 [자연어처리] 문자를 숫자로 변환하는 방법 (BOW, TF-IDF, Word-Embedding) (0) 2024.01.02 [자연어처리] Word2Vec 의 모든 것 (1) 2023.12.26