본문 바로가기
자연어 처리(NLP)

4. 워드 임베딩

by 곽정우 2024. 6. 17.

 

1. 워드 임베딩(Word Embedding)

  • 단어를 컴퓨터가 이해하고, 효율적으로 처리할 수 있도록 단어를 벡터화 하는 기술
  • 단어를 밀집 벡터(실수 값으로 이루어진 벡터)의 형태로 표현하는 방법
  • 워드 임베딩 과정을 통해 나온 결과를 임베딩 벡터라고 함
  • 워드 임베딩을 거쳐 잘 표현된 단어 벡터들은 계산이 가능하며, 모델에 입력으로 사용할 수 있음

1-1. 희소 표현(Sparse Representation)

  • 원-핫 인코딩을 통해서 나온 벡터들은 표현하고자 하는 단어의 인덱스의 값만 1이고, 나머지 인덱스에는 전부 0으로 표현되는 벡터 표현 방법에 의해 만들어지는 벡터를 희소 벡터라고 함

1-2. 희소 벡터의 문제점

  • 희소 벡터의 특징은 단어의 개수가 늘어나면 벡터의 차원이 한없이 커진다는 것
  • 원-핫 벡터는 벡터 표현 방식이 매우 단순하여, 단어의 출현 여부만을 벡터에 표시할 수 있음
  • 희소 벡터를 이용하여 문장 혹은 텍스트간 유사도를 계산해보면 원하는 유사도를 얻기 힘듦

1-3. 밀집 표현(Dense Representation)

  • 벡터의 차원이 조밀해졌다는 의미
  • 사용자가 설정한 값으로 모든 단어의 벡터 표현의 차원을 맞추는 표현 방식
  • 고차원의 정보를 저차원의 공간에 압축하여 표현
  • 자연어를 밀집 표현으로 변환하는 인코딩 과정에서 0과 1의 binary 값이 아니라 연속적인 실수 값을 가질 수 있음
  • 적은 차원으로 대상을 표현할 수 있음
  • 더 큰 일반화 능력을 가지고 있음

1-4. 원 핫 벡터와 워드 임베딩의 차이

  • 원 핫 벡터: 고차원, 희소 벡터, 값의 유형이 0과 1
  • 워드 임베딩: 저차원, 밀집 벡터, 실수

1-5. 차원 축소(Dimensionality Reduction)

  • 희소 벡터를 밀집 벡터의 형태로 변환하는 방법
  • 머신러닝에서 많은 피처들로 구성된 고차원의 데이터에서 중요한 피처들만 뽑아 저차원의 데이터(행렬)로 변환하기 위해 사용
  • PCA(Pricipal Component Analysis), 잠재 의미 분석(Latent Semantic Analysis, LSA), 잠재 디리클레 할당(Latent Dirichlet Allocation, LDA), SVD(Singular Value Decomposition, SVD)

 

2. 주요 워드 임베딩 알고리즘

  • 워드 임베딩은 고차원의 단어 공간에서 저차원의 벡터 공간으로 변환하는 방법
  • 변환된 벡터는 단어의 의미적 유사성을 반영하며, 유사한 의미를 가진 단어들은 벡터 공간에서 가깝게 위치
  • 모델이 텍스트 데이터의 의미를 이해하고 학습할 수 있도록함

2-1. Word2Vec

  • 분포 가설 하에 표현한 붓나 표현을 따라는 워드 임베딩 모델
    • 분산 표현(Distributed Representation)
      • 분포 가설: "비슷한 문맥에서 등장하는 단어들은 비슷한 의미를 가진다" 라는 가설
      • 분포 가설의 목표는 단어 주변의 단어들, window 크기에 따라 정의되는 문맥의 의미를 이용해 단어를 벡터로 표현 하는 것
      • 분산 표현으로 표현된 벡터들은 원-핫 벡터처럼 차원이 단어 집합의 크기일 필요가 없으므로, 벡터의 차원이 상대적으로 저차원으로 줄어듦
      • 희소 표현에서는 각각의 차원이 각각의 독립적인 정보를 갖고 있지만, 밀집에서는 하나의 차원이 여러 속성들이 버무려진 정보를 갖고 있음
  • 중심 단어와 주변의 단어들을 사용하여 단어를 예측하는 방식으로 임베딩을 만듦
  • Word2Vec의 학습 방식에는 CBOW(Continuous Bag of Words), Skip-Gram을 사용
    • CBOW
      • 주변에 있는 단어들을 보고 중간에 있는 단어(타켓 단어)를 예측하는 방법
      • 주변 단어(context)는 타겟 단어(target word)의 직전 n개 단어와 직후 n개 단어를 의미하며, 이 범위를 윈도우(window)라고 부르고, n을 window size라고 함
      • 문장 하나에 대해 한 번만 학습을 진행하면 문장이 데이터가 아깝기 때문에 sliding window 방식을 사용하여 하나의 문장을 가지고 여러 개의 학습 데이터 셋을 만듦
    • Skip-Gram
      • 중심 단어에서 주변 단어를 예측
      • 중심 단어를 sliding window 하면서 학습 데이터를 증강
      • 중심 단어를 가지고 주변 단어를 예측하는 방법이기 때문에 window size의 2n개 만큼 학습 데이터가 나옴
    • CBOW vs Skip-gram
      • Skip-gram이 CBOW에 비해 여러 문맥을 고려하기 때문에 Skip-gram의 성능이 일반적으로 더 좋음
      • Skip-gram이 단어당 학습 횟수가 더 많고, 임베딩 조정 기회가 많으므로 더 정교한 임베딩 학습이 가능
    작고 귀여운 강아지 문 앞에 앉아 있다

    • CBOW(window size=2)
      • 귀여운, 강아지 -> 작고
      • 작고, 강아지, 문 -> 귀여운
      • 작고, 귀여운, 문, 앞에 -> 강아지
      • 귀여운, 강아지, 앞에, 앉아있다 -> 문
      • 강아지, 문, 앉아있다 - > 앞에
      • 문, 앞에 -> 앉아있다
    • SKip-gram(window size=2)
      • 작고 -> 귀여운, 강아지
      • 귀여운 -> 작고, 강아지, 문
      • 강아지 -> 작고, 귀여운, 문, 앞에
      • 문 -> 귀여운, 강아지, 앞에, 앉아있다
      • 앞에 -> 강아지, 문, 앉아있다
      • 앉아있다 -> 문, 앞에
  • Word2Vec의 한계점
    • 단어의 형태학적 특성을 반영하지 못함(예 teach, teacher, teachers와 같이 세 단어는 의미적으로 유사한 단어지만 각 단어를 개별단어로 처리)
    • 단어 빈도 수의 영향을 많이 받아 희소 단어를 임베딩하기 어려움
    • OOV(Out Of Vocabulary)의 처리가 어려움
    • 새로운 단어가 등장하면 데이터 전체를 다시 학습 시켜야 함
    • 단어 사전의 크기가 클수록 학습하는데 오래걸림

2-2. FastText

  • Facebook의 AI Research 팀에서 개발한 텍스트 분류 및 단어 벡터 표현 도구
  • 대규모 데이터셋에서 빠르게 작동하도록 설계, 단어 임베딩과 텍스트 분류 모두에 사용할 수 있음
  • 작동 원리
    • <, > 는 단어의 경계를 나타내기 위한 특수 기호
    • 단어를 먼저 <, >로 감싼 후 설정한 n-gram의 값에 따라 앞에서부터 단어를 쪼갬
    • 마지막에 본 단어를 설명하기 위해 <, >으로 감싸진 전체 단어를 하나 추가함
      • 예) "apple" ["a", "ap", "app", "appl", "apple", "p", "pp", "ppl", "ple", "e"
    • 마지막에 본 단어를 설명하기 위해 <, >으로 감싸진 전체 단어를 하나 추가함
  • FastText 장점
    • 오타나 모르는 단어에 대한 대응
    • 단어 집합 내 빈도 수가 적었던 단어에 대한 대응
    • 자연어 코퍼스 내 노이즈에 대응

 

3. 워드 임베딩 구축하기

import pandas as pd
import numpy as np
from sklearn.datasets import fetch_20newsgroups
# 영어 말뭉치 데이터셋
# 영어 말뭉치 데이터셋에서 '헤더', '푸터', '인용문'을 제거한 데이터를 추출

dataset = fetch_20newsgroups(shuffle=True, random_state=2024, remove=('headers','footers','quotes'))
dataset = dataset.data
dataset[0]

len(dataset)

# 컬럼명을 document로 한 데이터프레임을 만들기
news_df = pd.DataFrame({'document': dataset})
news_df

# 데이터셋에 결측값이 있다면 제거하고, 총 데이터셋의 개수를 출력
news_df = news_df.dropna().reset_index(drop=True)
len(news_df)

# 열을 기준으로 중복된 데이터를 제거
processed_news_df = news_df.drop_duplicates(['document']).reset_index(drop=True)
len(processed_news_df)

# 데이터셋의 데이터 중 특수 문자를 제거
processed_news_df['document'] = processed_news_df['document'].str.replace('[^a-zA-Z]', ' ', regex=True)
processed_news_df

# 데이터셋에 길이가 너무 짧은 단어를 제거(단어의 길이가 2이하)
processed_news_df['document'] = processed_news_df['document'].apply(lambda x: ' '.join([token for token in x.split() if len(token) > 2]))
processed_news_df

# 전체 문장이 100자이상이고 단어의 갯수가 3개 이상인 데이터만 필터링
processed_news_df = processed_news_df[processed_news_df.document.apply(lambda x: len(str(x)) >= 100 and len(str(x).split()) >= 3)].reset_index(drop=True)
processed_news_df

# 전체 단어에 대한 소문자 변환
processed_news_df['document'] = processed_news_df['document'].apply(lambda x: x.lower())
processed_news_df

import nltk
from nltk.corpus import stopwords
nltk.download('stopwords')

stop_words = stopwords.words('english')
print(len(stop_words))

# 데이터셋에 불용어를 제외하여 띄어쓰기 단위로 문장을 분리
# [hell, set, spark, jammer, electrically, noisy...]

tokenized_doc = processed_news_df['document'].apply(lambda x: x.split())
tokenized_doc = tokenized_doc.apply(lambda x: [s_word for s_word in x if s_word not in stop_words])
tokenized_doc

len(tokenized_doc)

from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
tokenizer.fit_on_texts(tokenized_doc)

word2idx = tokenizer.word_index
print(word2idx)

 # 인덱스와 values값 순서 변경하고 딕셔너리

idx2word = {value:key for key, value in word2idx.items()} 
print(idx2word)

encoded = tokenizer.texts_to_sequences(tokenized_doc)
print(encoded[0])

vocab_size = len(word2idx)
print(f'단어 사전의 크기: {vocab_size}')

# 텍스트 데이터를 다루는 과정에서 단어 쌍을 생성하는 데 사용
# Skip-gram 모델을 사용하여 단어 쌍을 만들며, Word2Vec 알고리즘의 구성 요소
#주어진 단어를 기준으로 주변 단어를 예측하는 방식으로 단어 벡터를 학습

from tensorflow.keras.preprocessing.sequence import skipgrams
# skipgrams(시퀀스, 사전크기, 윈도우크기)
# 시퀀스: 인코딩한 리스트
# 사전크기: 단어 사전의 크기. 시퀀스에 등장하는 단어의 총 개수
# 중심단어와 주변단어간의 최대 거리

skip_grams = [skipgrams(sample,vocabulary_size=vocab_size, window_size=10) for sample in encoded [:5]]
print(f'전체 샘플 수 : {len(skip_grams)}')

# 부정적 예: 중심 단어와 실제 텍스트에서 함께 등장하지 않은 단어(0)
# 긍정적 예: 실제 텍스트에서 중심 단어와 주변 단어가 함께 등장한 경우(1)]
# pairs: (중심단어, 주변단어) 형태의 단어 쌍 리스트
# labels: 각 단어 쌍의 레이블 리스트로, 해당 단어 쌍이 실제로 등장하는지 여부를 나타냄

pairs, labels = skip_grams[0][0], skip_grams[0][1]

print(pairs)
print(labels)

print(len(pairs))
print(len(labels))

for i in range(5):
    print('({:s} ({:d}), {:s} ({:d})) -> {:d}'.format(
        idx2word[pairs[i][0]], pairs[i][0],
        idx2word[pairs[i][1]], pairs[i][1],
        labels[i]
    ))

training_dataset = [skipgrams(sample, vocabulary_size=vocab_size, window_size=10) for sample in encoded[:5000]]
len(training_dataset)

from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Embedding, Reshape,Activation,Input,Dot
from tensorflow.keras.utils import plot_model
embedding_dim = 100

# 중심 단어를 위한 임베딩 테이블
w_inputs = Input(shape=(1,), dtype='int32')
word_embedding = Embedding(vocab_size, embedding_dim)(w_inputs)

c_inputs = Input(shape=(1,), dtype='int32')
context_embedding = Embedding(vocab_size, embedding_dim)(c_inputs)
dot_product = Dot(axes = 2)([word_embedding, context_embedding])
dot_product = Reshape((1, ), input_shape = (1, 1))(dot_product)
output = Activation('sigmoid')(dot_product)
model = Model(inputs = [w_inputs, c_inputs], outputs = output)
model.summary()

# 이중 분류 문제에서는 (0,1) 손실 함수로서 binary_crossentropy를 사용
model.compile(loss = 'binary_crossentropy', optimizer='adam')

# 모델 정보에 대한 시각화
plot_model(model, to_file='model.png', show_shapes=True, show_layer_names=True)

for epoch in range(100):
  loss=0
  for _,elem in enumerate(training_dataset):
    first_elem = np.array(list(zip(*elem[0]))[0], dtype='int32')
    second_elem = np.array(list(zip(*elem[0]))[1], dtype='int32')
    labels = np.array(elem[1],dtype = 'int32')
    X = [first_elem, second_elem]
    Y = labels
    loss += model.train_on_batch(X,Y)
  print('Epoch:',epoch+1,'Loss:',loss)

# Gens im
# 자연어 처리 작업에서 주로 사용되는 오픈 소스 라이브러리
# 토픽 모델링, 문서 유사도 계산, 단어 임베딩 (Word2Vec, FastText 등)

import gensim
vectors = model.get_weights()[0]
vectors

len(vectors)

f = open('vectors.txt', 'w')
f.write('{} {}\n'.format(vocab_size, embedding_dim))

for word, i in tokenizer.word_index.items():
    f.write('{} {}\n'.format(word, ' '.join(map(str, list(vectors[i-1, :])))))
f.close()
w2v = gensim.models.KeyedVectors.load_word2vec_format('./vectors.txt', binary=False)
w2v.most_similar(positive=['flower'])

과제
AI Hub에 공개데외 있는 한국어 데이터셋을 활용하여 한국어 워드 임베딩을 구축

'자연어 처리(NLP)' 카테고리의 다른 글

6. Rnn 기초  (0) 2024.06.21
5. 워드임베딩 시각화  (0) 2024.06.21
3. 임베딩  (0) 2024.06.17
2. 자연어 처리 프로젝트 진행 순서  (0) 2024.06.17
1. 자연어 처리 개요(Natural Language Processing)  (0) 2024.06.17