Skip to main content

하위 단어(Sub-Word)정보를 통한 단어 벡터(Word Vectors) 강화

단어 임베딩을 위한 다음 단계
Created on March 2|Last edited on January 21
이는 여기에서 볼 수 있는 영어 기사를 번역한 것이다.

서론

 코드 확인하기|  Repository 확인하기

출처:
지능(intelligence)은 추론 능력, 논리 이해 능력 등으로 정의됩니다. 이는 대부분의 경우 딱 들어맞지만, 컴퓨터에 지능을 “가르친다”라는 질문은 논쟁의 여지가 있습니다.
제 생각에, 지식 기반(knowledge base)을 통해 컴퓨터를 가르치는 것은 모조 지능(pseudo-intelligence)에 속한다고 보입니다. 컴퓨터는 언제나 지식 데이터베이스에 설정한 규칙 및 패러다임을 기반으로 작동합니다. 논리를 유추하고 이해하기 위해 그 이상으로 나아가지 않을 것입니다. 즉, 먹을 것을 그 대가로 서커스의 사자가 불붙은 후프를 뛰어 통과하는 법을 배우는 것과 다를 바 없으며, 주위에 충분히 많은 음식이 있다면 이것은 꽤 어려울 수 있습니다.
그러면 명시적으로 모든 것을 말하지 않고 어떻게 컴퓨터에게 지능(intelligence)을 가르치는가에 대한 질문에 답할 수 있을까요? 그러기 위해서, 그 과정을 조금 바꿔보도록 합시다. 지식 기반에서 모든 것을 정의하는 것 대신, n-차원 공간에서 숫자 (벡터)를 통해 객체(object)의 의미를 컴퓨터가 이해시키는 것은 어떨까요?
예를 들어보겠습니다. 저희는 컴퓨터가 전구💡 의 개념을 이해하기를 원한다고 가정해보겠습니다. 여기서 2차원의 공간을 취하겠습니다. 여기서 한 차원은 빛의 강도를 나타내고, 나머지 한 차원은 객체(object)의 크기를 나타냅니다. 이 공간은 컴퓨터가 전구뿐만 아니라 튜브 조명(tube light)도 이해할 수 있게 해줍니다. 객체의 벡터 표현을 통해 컴퓨터는 이제 포인트를 수정(tweak)하고 다양한 객체를 이해합니다. 이 방법은 컴퓨터가 사물을 보다 더 쉽게 이해할 수 있게 하기 위한 방법인가요? 그렇다고 생각하신다면 AI의 대부 중 한 사람인 Geoffrey Hinton과 같은 견해를 가지고 있다고 할 수 있습니다. 그렇다면, “지능(Intelligence)은 벡터다”라는 의견에 대해서는 어떻게 생각하시나요?
벡터를 통해 단어를 표현하는 것은 오랫동안 논의된 개념입니다. Word2VecGloVe에 대한 이전 기사에서 저희는 이 아이디어의 핵심을 다루고 있습니다. 거기서 이 아이디어의 이면에 있는 직관(intuition)뿐만 아니라 임베딩 레이어를 통해 저희 방식을 코딩하는 것에 대해 기술하고 있습니다. 이번 기사는 후속 보고서로써, 여기서 Piotr Bojanowski 등의 논문인Enriching Word Vectors with Subword Information 를 해독해보겠습니다. 이는 Word2Vec의 직속 후속작으로 간주되고 있으며, 여기서 저자는 더 나은 벡터 표현을 위한 개별 단어의 형태론(morphology)를 고찰하고 있습니다.

직관(Intuition)

Word2Vec에 대해 잠시 이야기해보겠습니다. Word2Vec에 대한 제안은 간단하면서도 강력합니다.
단어의 의미는 주위의 단어들에 의존한다.
이것은 단어를 벡터로 인코딩하는 개념을 사용한 두 가지 전략, 즉, Skip-Gram과 CBOW의 관념화로 이어졌습니다. 단어 벡터에 대한 가장 흥미로운 통찰은 바로 잘 훈련된 단어 벡터에 대한 선형 대수가 바로 논리(logic)로 직접 번역된다는 것입니다. 유명한 예시는 바로 King-Man+Woman=Queen의 예입니다. 관심이 많은 독자께서는 Word2VecGloVe 에 관한 기사를 읽어보시기 바랍니다. 이 주제를 더 잘 이해하는 데 도움이 됩니다.
벡터 표현 및 연관 내용(주위 단어)의 도움을 통해, word2vec의 독창적인 아이디어가 도입되었으며, 여기서 N-차원 공간에서 벡터를 통해 단어의 의미를 머신에 알려줄 수 있었습니다. 그러나 저희는 각 단어(each word)를 벡터로 가르치고 있기 때문에, 단어 뒤의 형태론(morphology) 및 어원(etymology)을 절대적으로 무시합니다. 이는 단어가 어떻게 생겨났는지와 어떻게 단어의 하위 단어(subwords)가 다른 단어와 연결될 수 있는지를 보여주는 정보의 중요한 부분을 무시하는 것으로 이어집니다.
박식한 에세이가 쓰이거나 복잡한 아이디어 또는 감정이 표현 될 때, 희귀하고 복잡한 단어가 사용됩니다. 단어 연상 경로(word association route)(Word2Vec, GloVe 군)를 통해 모델을 훈련하려는 경우, 이는 작업 실패로 이어질 수 있습니다. 반면에 이러한 희귀 단어들은 다른 단어의 일부 또는 어원적으로 해독할 수 있는 특징을 통해 구성되었으므로 형태론을 통해 해독될 수 있습니다. 샨스크리트어 또는 샨스크리트의 현대적 파생어인 인도 언어와 같은 일부 언어는 체계적으로 어떠한 경로 음절(route syllable)에서 파생될 수 있으므로 이러한 훈련에 매우 적합합니다.
형태론 이해
다음은 논문에서 발췌한 부분입니다:
예를 들어, 프랑스어나 스페인어에서 대부분의 동사는 40개 이상의 다른 굴절형을 가지고 있는 반면, 핀란드어는 명사의 경우 15개의 격(case)을 가지고 있습니다. 이러한 언어는 훈련 코퍼스(corpus)에서 거의 또는 전혀 발생하지 않는 수많은 단어 형식이 포함되어 있어 좋은 언어 표현을 학습하기가 어렵습니다. 다양한 단어 형성이 규칙을 따르고 있으므로 문자 단위 정보(character level information)를 사용함으로써 형태론적으로 풍부한 언어에 대한 벡터 표현을 개선할 수 있습니다.
논문의 저자는 Skip-Gram 모델의 확장을 제시하고 있습니다. Skip-Gram 모델 이면의 주요 아이디어는 중심/타깃 단어(center/target word)를 고려하여 여러 개의 문맥 단어(multiple context words)를 예측하는 것입니다.
예를 들어, 다음의 문장을 살펴보시기 바랍니다:
  1. The color red pops out” (빨간색이 돋보입니다)
  2. "What is green in color?" (초록색이란 무엇인가요?)
  3. "Shades of the color yellow suits you" (노란색이 잘 어울립니다)
위의 문장에 대해 모델을 훈련 시키는 경우, “color”(색)이라는 단어가 “red”, “green”, “yellow” 단어와 연관되어 있다는 것을 알 수 있습니다. 단어 연상을 통해 단어 ‘color’의 의미를 파악하는 기본 목표를 분명히 달성하게 됩니다.
반면에, 개체(entity)로서의 언어는 훨씬 더 깊고 정의된 의미를 지닙니다. 세상에는 약 7,000여 개의 다양한 인간 언어가 존재하는 것으로 추정되며, 각 언어는 독특한 방언과 정의된 어휘를 갖고 있습니다. 특정 단어가 어떻게 특정 언어에서 어떻게 생겨났는지에 대해 이해하는 것은 단어를 정의된 하위 단어(sub-words)로 세분화하는 것을 의미합니다. 그러나 word2vec에게 이 작업을 수행하게 하는 것은 언어의 미묘한 아름다움을 무시하겠다는 것을 의미합니다. 다음의 예를 보시기 바랍니다:
단어 형태론의 예
단어 'unreadable'(판독 불가능한, 읽기 어려운)은 단어를 구성하는 하위 단어(sub-words)로 세분화할 수 있으며, 따라서 의미를 정의할 수 있습니다. 이 논문은 타깃/중심 단어를 구성 하위 단어(constituent sub-words)로 대체하여 바로 이 정보를 Skip-Gram 모델에 포함하고자 합니다. 이 방식으로, 또한 ‘unreadable’에 속하는 하위 단어(sub-words)를 ‘unstoppable’과 같은 다른 단어와 연결 지을 수 있습니다. 비록 이 두 단어는 다른 의미가 있으나, 공통된 하위 단어(sub-words)를 가지고 있습니다. 이는 이러한 구성은 유사한 아이디어를 가지고 있음을 의미합니다. 이러한 접근법을 유용하게 사용할 수 있는 다른 방식은 단어의 의미가 단어 연상(Word Associations)과 형태론(Morphology)이라는 두 가지 요인에 어떻게 의존하는가에 대한 것입니다. 즉, 사용 예가 거의 없는 경우에도, 드물게 사용되는 단어를 해독할 수 있음을 의미합니다.

목적

Piotr Bojanowski 등이 이 논문에 제시한 방법은 Skip-Gram 모델의 직접 확장입니다. 하위단어(subword) 공간에 대해 살펴보기에 앞서 Skip-Grams에 대해 조금 수정해보도록 하겠습니다.
WW 크기의 단어 어휘(word vocabulary)가 주어졌을 때, 여기서 단어가 단어의 색인(index) w{1,...,W}w\in \{1,...,W\} 인 경우, 목표는 각 단어 w에 대한 벡터 표현을 학습하는 것입니다. 단어 시퀀스 w1,...,wT w_{1},...,w_{T} ​로 표현되는 대형 훈련 코퍼스가 주어진 경우, Skip-Gram 모델의 목적은 다음의 로그 가능도(log-likelihood)를 최대화하는 것입니다:
J(θ)=t=1TcCtlogp(wcwt)J(\theta)=\sum ^{T}_{t=1}\sum _{c\in C_{t}}\log p( w_{c} |w_{t})

여기서 문맥(context) CtC_t ​은 타깃 언어 wtw_t. 주위의 단어 색인(indices of words) 집합입니다.
여기서 로그 가능도(log-likelihood) 함수의 매개변수화(parameterization)에 대한 질문이 발생하는데, 구체적으로는 “이 로그 가능도(log-likelihood)를 최대화하기 위해 무엇을 수정(tweak)해야 하는가?”가 바로 그것입니다. 이에 대한 답은 확률 함수에 있습니다.
p(wcwt)=exp(s(wt,wc))j=1Wexp(s(wt,j))p( w_{c} |w_{t}) =\frac{\exp( s( w_{t} ,w_{c}))}{\sum ^{W}_{j=1}\exp( s( w_{t} ,j))}

이 확률 함수는 사실 softmax 함수입니다. 여기서 s(x,y)s(x,y) 는 벡터 xxyy사이의 유사성을 계산하는 스코어링 함수(scoring function)로 간주됩니다. 로그 가능도(log-likelihood)를 최대화하면서 수정(tweak)되는 parameters는 단어의 벡터 표현입니다. 목적 함수(objective function)는 단어의 더 나은 벡터 표현을 통해 손실이 감소한다는 것입니다.
Softmax 구현을 통해, 우리가 하나의 문맥 단어에만 초점을 맞출 수 있게 하는 더 높은 확률 분포를 실현하게 됩니다.
그러나, 단어 wtw_t ,가 주어진 경우 하나의 문맥 단어(context word) wcw_c 만을 예측한다는 것을 암시하기 때문에 이러한 모델은 저희 사례에는 적용되지 않습니다.
이는 확률 함수를 다르게 프레이밍(frame) 하는 것으로 이어집니다. 이제 문맥 단어(context words) 예측에 관한 문제는 이진 분류(binary classification) 작업으로 간주됩니다. 이는 독립적으로 문맥 단어(context words)의 존재 및 부재를 예측하는 작업이 됩니다. 그림에서의 네거티브 샘플링과 함께 이 이진 분류(binary classification) 작업은 두 가지 종류의 문맥 단어인, 포지티브(positive) 및 네거티브(negative)와 함께 발생합니다. 포지티브 문맥 단어(positive context words)는 타깃 단어와 같은 윈도우(window)에 있는 것이며, 네거티브 문맥 단어(negative context words)는 이 윈도우(window)에 있는 그 외의 단어들입니다.

하위단어 공간(Subword space)

각 단어에 대하여 고유한 벡터 표현을 사용함으로써 Skip-Gram 모델은 단어의 내부 구조를 무시합니다.
이 문제를 해결하기 위해 저자는 다른 스코어링 함수를 제시했습니다. 스코어링 함수에 대해 더 심층적으로 살펴보려면 저자들이 제안한 설정에 대해 먼저 이해해야 합니다. 저자는 각 단어 w를 n-grams의 문자 가방으로 간주했습니다. 또한 다른 문자 시퀀스(character sequences)에서의 접두사 및 접미사의 경계(demarcation)를 고려하여 특별 경계 기호 <와 >을 각 단어의 처음과 끝에 추가합니다. 그리고 단어 w 자체를 단어 n-grams의 집합에 추가합니다. 코드 스니펫을 통해 다음의 내용을 이해해보도록 합시다.
>>>word = "where"
>>>word = f"<{word}>"
>>>n_grams = [word[i:i+3] for i in range(len(word)-2)]+[word]
n_grams
>>>n_grams
['<wh', 'whe', 'her', 'ere', 're>', '<where>']
이제, 스코어링 함수 s(wt,wc)s( w_{t} , w_c) 는 두 개의 벡터를 parameter로 취합니다. 즉, 타깃 및 문맥 벡터를 뜻합니다. 이제 프레임의 하위단어(subwords)와 함께, 스코어링 함수는 타깃 단어의 하위 단어만 포함하도록 수정됩니다. 즉, 스코어는 문맥 벡터와 모든 타깃 n-gram 벡터 사이의 스칼라곱(scalar product)으로 계산됨을 의미합니다.
크기 GG의 n-grams 사전이 주어졌다고 가정합시다. 단어 w가 주어진 경우, w에 나타나는 n-grams의 집합 Gw{1,...,G}G_w \sub \{1,...,G\} t로 나타내봅시다. 저희는 벡터 표현 zg​를 각 n-gram g와 연결합니다. 저희는 n-grams의 벡터 표현의 총합(sum)으로 단어를 표현합니다. 따라서, 다음의 스코어링 함수를 얻을 수 있습니다.
s(w, c)=gGwzgTvcs( w,\ c) = \sum\limits _{g\in G_{w}} z^{T}_{g} v_{c}

여기서 가장 주목해야 할 점은 타깃 단어의 벡터표현을 n-grams의 모든 벡터의 총합(sum)으로 간주한다는 점입니다.
여기서 zg는 타깃 단어에 해당하는 각 n-grams를 나타냅니다. 예를 들어, 타깃 단어가 '<where> '인 경우, zg는 '<wh', 'whe', 'her', 'ere', 're>' 및 '<where>'에 해당하는 벡터일 수 있습니다. 기억하셔야 할 중요 사항은 한 단어에 나타나는 하위 단어(sub-words)는 다른 단어의 경우에도 나타날 수 있다는 것입니다. 이것이 바로 성호 정보 공유(mutual information sharing)가 일어나는 방법입니다. 이 간단한 모델을 통해 아키텍처가 여러 단어에 걸친 하위 단어(sub-words)에 대한 정보를 공유할 수 있습니다.

Code

 Code 확인하기 |  Repositorry 확인하기

이번 섹션에서 저희는 논문의 TensorFlow 구현에 대해서 살펴보겠습니다. 코드는 상당 부분 공식 TensorFlow Word2Vec 가이드의 영향을 받아 작성되었습니다.
코드의 가장 중요한 부분은 바로 데이터 준비(data preparation)입니다. 저희가 취하는 데이터는 캐리지 반환(carriage returns)으로 구분된 수많은 문장(sentences)을 포함하는 텍스트 파일입니다.
# Create a `tf.data` with all the non-negative sentences
>>> text_ds = tf.data.TextLineDataset(path_to_file).filter(lambda x: tf.cast(tf.strings.length(x), bool))

>>> for text in text_ds.take(5):
print(text)

tf.Tensor(b'First Citizen:', shape=(), dtype=string)
tf.Tensor(b'Before we proceed any further, hear me speak.', shape=(), dtype=string)
tf.Tensor(b'All:', shape=(), dtype=string)
tf.Tensor(b'Speak, speak.', shape=(), dtype=string)
tf.Tensor(b'First Citizen:', shape=(), dtype=string)
그런 다음 각 문장을 토큰화하고 표준화(standardize)합니다.
# We create a custom standardization function to lowercase the text and
# remove punctuation.
def custom_standardization(input_data):
lowercase = tf.strings.lower(input_data)
return tf.strings.regex_replace(lowercase,
'[%s]' % re.escape(string.punctuation), '')

# Define the vocabulary size and number of words in a sequence.
vocab_size = 4096
sequence_length = 10

# Use the text vectorization layer to normalize, split, and map strings to
# integers. Set output_sequence_length length to pad all samples to same length.
vectorize_layer = TextVectorization(
standardize=custom_standardization,
max_tokens=vocab_size,
output_mode='int',
output_sequence_length=sequence_length)

# build the vocab
vectorize_layer.adapt(text_ds.batch(1024))
토큰을 얻게 된 후, 이제 학습용 지도 설정(supervised setup)에서 도움이 되는 설정을 생성해야 합니다.
 출처: Word2Vec
를 사용하지 않았으며, 대신 훈련 프로세스에서 더 나은 네거티브 샘플(negative samples)을 포함하도록 프로세스를 사용자 정의합니다. StackOverFlow thread에서 이 논의 사항을 확인하실 수 있습니다.
저희 하위 단어(sub-words)의 경우, 설정이 약간 수정됩니다. 그림에 나타난 바와 같이 초기 위치에서 타깃 워드 색인(index)가 없습니다. 대신, 저희는 타깃 단어의 하위 단어 인덱스(indices)를 갖게 됩니다.
배치크기(batchsize) 1000인 데이터세트의 형태는 다음과 같습니다.
<PrefetchDataset shapes: (((1000, None), (1000, 5, 1)), (1000, 5)), types: ((tf.int32, tf.int64), tf.int64)>
  • (1000, None) - 1000 ngrams.
  • (1000, 5, 1) - 1,000 5-조각(piece) 문맥 단어들(context words).
  • (1000, 5) - 1,000개의 5-조각(piece) 레이블(label)
모델은 다소 간단합니다.
class Word2Vec(Model):
def __init__(self, subword_vocab_size, vocab_size, embedding_dim):
super(Word2Vec, self).__init__()
self.target_embedding = Embedding(subword_vocab_size+1,
embedding_dim,
input_length=None,
name="w2v_embedding",)
self.context_embedding = Embedding(vocab_size+1,
embedding_dim,
input_length=num_ns+1)
self.dots = Dot(axes=(3,1))
self.flatten = Flatten()

def call(self, pair):
target, context = pair
we = tf.math.reduce_sum(self.target_embedding(target),axis=1)
ce = self.context_embedding(context)
dots = self.dots([ce, we])
return self.flatten(dots)
저희는 두 개의 임베딩 레이어를 정의합니다. 그런 다음 문맥(context) 및 타깃 임베딩에 내적(dot product)을 수행하여 스코어를 매깁니다. 그다음, 이 스코어를 평가하고 손실을 역전파(back-propagate)하여 임베딩을 수정(tweak)합니다.

결과

모델의 손실 및 정확도는 여기 나타난 바와 같습니다. 두 메트릭 모두 잘 작동하고 있는 것으로 보입니다.

Run set
0


임베딩 프로젝션(Embedding Projections)

임베딩을 보는 다른 방법은 프로젝터(project)에서 보는 것입니다. TensorFlow는 이를 시각화하는데 유용한 툴을 제공합니다. 임베딩을 위한 vector.tsv 및 metadat.tsv를 생성하고 이를 프로젝터(projector)에 로드할 수 있습니다. 프로젝터(projector)는 PCA와 같은 차원 감소 테크닉(dimensional reduction techniques)을 적용하여 데이터를 비주얼 벡터 공간(visual vector space)에 합치지만, 여전히 중요한 정보를 유지할 수 있습니다. 빠른 액세스를 위해, 저희는 tsv 파일을 wandb 프로젝트에 아티팩트로 업로드 했습니다. 아티팩트를 다운로드하여 사용하시기 바랍니다. 또한 GitHub repository에도 파일을 업로드 했습니다.

문맥(Context)

손실이 크게 안정화된 후, 저희는 저희 문맥 임베딩(context embeddings)가 어떻게 형성되었는지 살펴보기로 했습니다. 탐색 단어로 “for”를 선택했습니다. "the", "a", "of", "with"와 같은 단어가 얼마나 가장 가까이에 나타나 있는지에 주목하시기 바랍니다. 이것은 우리 모델이 두드러진 문법적 의미를 가진 단어를 함께 그룹화하는 방법을 학습했음을 의미합니다.
단어 검색: “for”

다음으로 조금 더 드물게 나타나는 단어인 “secrets”를 선택하겠습니다. 여기서 우리 모델이 "safeguard", "Signal", "strangely"와 같은 단어를 따라가는 것을 확인할 수 있습니다. 저희 데이터는 셰익스피어의 구절에 불과하다는 것을 명심했을 때, 모델은 상대적으로 우수하게 작업을 수행했습니다!
단어 검색: “secrets”

타깃(Target)

타깃 임베딩은 주어진 데이터에서 표현된 모든 하위 단어(sub-words)를 포함하고 있습니다. 우선 “<th”라는 문구를 검색합니다. 하위 언어를 사용하여 다른 단어들을 연결하는 것이 우리의 주목적임을 재요약합니다. "the", "they", "them", "this", "thou", "thy"와 같은 단어들이 “<th” 문구와 가장 가깝다는 것을 알 수 있기 때문에 목적을 크게 달성할 수 있었습니다.
단어 검색: "<th"

단어 “the”를 검색하는 경우 유사한 결과를 확인할 수 있습니다. 유사한 하위 단어(sub-words)를 가진 단어들이 가장 가까운 이웃(nearest neighbors)으로 나타납니다. 따라서 저희 모델을 통해 단어의 형태론(morphology)을 포착했다고 성공적으로 결론을 내릴 수 있겠습니다. 더 많은 의미론적 의미(semantic meaning)를 포착하기 위해 다양한 크기의 n-gram과 함께 실험 및 시도해 볼 수 있습니다.
단어 검색: “the”

결론

이 논문에 설명된 작업은 word2vec의 성공적인 후속작으로서 또한 fastText 기반의 3가지 전제 조건 중 하나가 되었습니다 (효율적인 단어 표현을 생성하기 위해 많은 개념을 사용하기 때문에 자연어 처리(Natural language processing)에서 수행된 작업의 벤치마크로 간주).
이 논문이 단순한 방법을 활용하여 언어표현을 학습하고, 또한 하위 단어(sub-word) 정보를 학습함을 저희 실험에서 확인할 수 있습니다. Skip-Gram 방법의 이상적인 후속작인 이 모델은 다른 기존 모델보다 더 빠르게 훈련되며 하위 단어(sub-word) 정보 및 형태론적 분석(morphological analysis) 없이 인식되는 베이스라인을 상회합니다.
결론적으로, 하위 단어(sub-word) 정보와 함께 작업하는 것은 실제로 컴퓨터가 언어의 힘과 묘미를 최대한으로 활용할 수 있도록 하는데 한 걸음 더 다가설 수 있게 합니다.

저자:
이름 TwitterGitHub
Devjyoti Chakrobarty@Cr0wley_zz@cr0wley-zz
Aritra Roy Gosthipaty@ariG23498@ariG23498

Iterate on AI agents and models faster. Try Weights & Biases today.