HAN_PY 2020. 10. 19. 19:27
반응형

0. 들어가면서

  텍스트 데이터를 학습한 모델의 크기는 단어의 개수에 영향을 받는다. RNN에서는 단어 개수에 비례하여 계산비용이 증가하기 때문에 word embedding 벡터의 종류가 제한이 생기는 문제가 발생한다. 이러한 문제를 해결하기 위해 단어를 한정적인 유닛으로 표현하는 Word Picece Model(WPM)이 생겼다. WPM은 언어에 상관 없이 모두 적용 할 수 있어서 특정 언어의 토크나이저를 만들지 않아도 되지만, 그렇다고 모든 데이터 분석에 적합한 것은 아니다.

 

 

 수백만개의 단어를 포함하는 데이터를 표현하기 위해서는 bag of words model에서는 단어 개수 만큼의 embedding vector를 학습하기 때문에 단어의 개수가 많을 수록 차원이 커지고 모델이 무거워진다. 이러한 문제를 해결하기 위해서는 제한된 개수의 단어를 이용해야 한다. 그러나 자주 사용되지 않는 많은 단어들을 무시하면, 해석에 문제가 발생한다.

 

 

 용어 정리를 하고 가려고 한다. 단어와 units의 차이를 알아보자. 영어에서는 알파벳을 유닛으로 이용한다. 그리고 그 알파벳이 모여 단어가 된다. 따라서 하나의 유닛은 뜻이 없어 모호성을 지닌다. 따라서 모호성을 줄어 들기 위해서는 단어를 유닛으로 이용하는 것이다. 이러한 문제는 토크나이징으로 모호성이 적은 최소한의 유닛을 만들 수 있다.

 

공연은 끝났어 -> ['공연-' + '-은' + '끝-' + '-났어']
공연을 끝냈어 -> ['공연-' + '-을' + '끝-' + '-냈어']
개막을 해냈어 -> ['개막-' + '-을' + '해-' + '-냈어']

 

그러나 위처럼 토크나이징을 하려면 해당언어의 언어학적 지식과 학습데이터가 필요하다.

 

 

1. Word Piece Model tokenizer

 WPM은 단어의 출현 빈도수를 이용하는 토크나이저이다. 예시 중 한 문장을 하겨와서 비교해 보자.

  • Word : Jet makers feud over seat width with big orders at stake
  • Wordpieces: _J et _makers _fe ud _over _seat _width _with _big _orders _at _stake

bert 논문에 적힌 내용을 보면 makers, over은 모두 자주 이용되기 때문에 그 자체를 units으로 이용하지만, Jet는 자주 등장하지 않기 때문에 subword units인 'J'와 'et'로 나눠진다. 그리고 단어가 시작되는 곳에 '_'를 붙여준다. 그러면 ‘Jet’ 은 ‘_Jet’ 이 되어 ‘_J et’ 으로 나뉘어지고 makers 는 ‘_makers’ 가 된다.

 만약 복원을 한다면 띄어쓰기 기준으로 나눠진 tokens를 concatenation 한 뒤, 다시 나눠 tokenize하거나 _ 를 빈 칸으로 치환하여 문장을 목원하면 된다.

def recover(tokens):
    sent = ''.join(tokens)
    sent = sent.replace('_', ' ')
    return sent

 

 

2. Byte pair encoding

Byte pair encoding는 데이터 압축 방법으로 빈도수가 많은 최장길이의 substring을 하나의 unit으로 만들어 bit를 아낀다. 토크나이저 입장에서는 많이 쓰이는 subwords 그 자체를 unit으로 만들고 자주 등장하지 않는 단어를 subword unit으로 만든다. 즉 WPM은 각 언어의 지식이 없이도 빈번히 등장하는 substring을 단어로 학습하고, 자주 등장하지 않는 단어는 최대한 의미보존을 할 수 있는 최소한의 unit으로 표현한다. 한국어로 치면 복합명사를 단일면사로 만들고 어절을 명사와 조사로 바꾼다고 생각하면 된다. 

 

 코드를 작성해 보자. 아래에 들어갈 vocab 은 low, lower, newest, widest 이고, 맨 뒤에 특수기호 ‘/w’ 를 넣은 뒤, 한글자 단위로 모두 띄어 초기화를 한 상태이다. Character 는 기본 subword units 디고 for loop 에서 빈도수가 가장 많은 bigram 을 찾는다. 이 bigram 을 하나의 unit 으로 merge 하고 이 과정을 num_merges 만큼 반복한다. vocab 의 value 는 빈도수를 나타내고 ‘low’ 가 5 번, ‘lower’ 가 2 번 등장한다.

 

import re, collections


def get_stats(vocab):
    pairs = collections.defaultdict(int)  # int는 0을 대신해서 값 안들어 오면 0을 넘김
    for word, freq in vocab.items(): # .items()는 딕셔너리에서 key와 value를 쌍으로 얻을 수 있다.
        symbols = word.split()   # 괄호 안에 아무 값도 넣어 주지 않으면 공백(스페이스, 탭, 엔터 등)을 기준으로 문자열을 나누어 준다.
        for i in range(len(symbols) - 1):
            pairs[symbols[i], symbols[i + 1]] += freq # symblos[i] 가 알파벳 하나다. 알파벳을 2개씩 묶는 과정이다.
    return pairs


def merge_vocab(pair, v_in):
    v_out = {}
    bigram = re.escape(' '.join(pair))
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
    for word in v_in:
        w_out = p.sub(''.join(pair), word)
        v_out[w_out] = v_in[word]
    return v_out


vocab = {'l o w </w>': 5,
         'l o w e r </w>': 2,
         'n e w e s t </w>': 6,
         'w i d e s t </w>': 3
         }

num_merges = 10

for i in range(num_merges):
    pairs = get_stats(vocab) # 저장 값은 두글자씩의 빈도수 ex)<class 'int'>, {('l', 'o'): 7, ('o', 'w'): 7, ('w', '</w>'): 5, ('w', 'e'): 8,...
    # print(pairs)
    best = max(pairs, key=pairs.get)  # pairs 딕셔너지 중에 value가 가장 높은 key 값이 best로 저장된다. ex) ('e', 's')
    # print(best)
    vocab = merge_vocab(best, vocab) # ex) {'l o w </w>': 5, 'l o w e r </w>': 2, 'n e w es t </w>': 6, 'w i d es t </w>': 3}
    # print(vocab)

print(vocab) # {'low</w>': 5, 'low e r </w>': 2, 'newest</w>': 6, 'wi d est</w>': 3}

 위의 코드를 보면 num_merges를 10번 반복한다. 즉, 10번의 병합을 거친 vocab은 아래와 같다.

vocab = {
         'low</w>': 5, 
         'low e r </w>': 2, 
         'newest</w>': 6, 
         'wi d est</w>': 3
        }

 

띄어쓰기 기준으로 subword units를 나누면 다음과 같다. 총 9개의 units로 네 단어가 표현 된다.

{'low</w>': 5,
 'low': 2,
 'e': 2,
 'r': 2,
 '</w>': 2,
 'newest</w>': 6,
 'wi': 3,
 'd': 3,
 'est</w>': 3}

 

 

 

 

참고 블로그

lovit.github.io/nlp/2018/04/02/wpm/

 

Word Piece Model (a.k.a sentencepiece)

토크나이징 (tokenizing) 은 문장을 토큰으로 나누는 과정입니다. 텍스트 데이터를 학습한 모델의 크기는 단어의 개수에 영향을 받습니다. 특히 Google neural machine translator (GNMT) 와 같은 Recurrent Neural N

lovit.github.io

 

반응형