ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [자연어처리] 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은 간단하고 기본적인 언어 모델이기 때문에 재미난 예시를 들어보려고 합니다. "너무"라는 단어가 너무 많이 들어간, 중독성을 자랑하는 아이오아이의 너무너무너무 라는 노래로 예제를 만들어보았습니다.

    너무너무너무.txt
    0.00MB

     

    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)

     

    참고 출처 : https://m.blog.naver.com/koys007/221426162892

Designed by Tistory.