본문 바로가기
IT 도서/Pytorch로 시작하는 딥 러닝 입문

08. 단어의 표현 방법

by 이민우 2021. 3. 2.
728x90
반응형

wikidocs.net/60851

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

*해당 글은 학습을 목적으로 위의 포스팅 내용 중 일부 내용만을 요약하여 작성한 포스팅입니다.

 상세한 내용 및 전체 내용 확인은 위의 링크에서 확인하실 수 있습니다.

 

원-핫 인코딩 (One-hot encoding)

  • 컴퓨터가 이해할 수 있도록 하기 위해 자연어 처리는 문자를 숫자로 바꾸는 기법들이 필요하다.
  • 원 핫 인코딩은 이러한 기법 중 가장 기본적인 방법이다.
  • 원핫 인코딩은 단어 집합의 크기를 벡터의 차원으로 하고, 표현하고자 하는 단어의 인덱스에는 1을 부여하고 나머지에는 0을 부여하는 희소 표현 방식이다.
  • 이러한 작업을 위해 단어 집합을 만들어 각 단어에 고유한 정수값을 부여해야 한다.
  • 이렇게 표현된 벡터를 원 핫 벡터라고 한다.
  • 원핫 인코딩은 단어의 개수가 늘어날수록 벡터를 저장하기 위한 공간이 계속 늘어난다.
  • 즉, 벡터의 차원이 계속 늘어난다. 이는 저장 공간 측면에서 매우 비효율적인 표현 방법이다.
  • 또한 원 핫 벡터는 단어의 유사도를 표현하지 못하는데, 확률을 나타내주는 softmax와 달리 원 핫 벡터는 자신을 제외한 다른 모든 단어는 0으로 표현하기 때문이다.

 

 

워드 임베딩 (Word Embedding)

  • 단어를 벡터로 표현하는 방법.
  • 워드 임베딩은 단어를 밀집 표현으로 변환하는 방법이다.
  • 워드 임베딩의 결과로 나온 벡터는 임베딩 벡터라고 부른다.
  • 워드 임베딩의 방법론으로는 LSA, Word2Vec, FastText, Glove 등이 있다.
  • 파이토치에서도 nn.embedding()을 제공하는데, 앞서 언급한 방법들을 사용하지는 않지만 단어를 랜덤한 값을 가지는 밀집 벡터로 변환한 뒤 가중치를 학습한느 것과 같은 방식으로 단어 벡터를 학습한다.

 

*희소 표현 (Sparse Representation)

  • 앞서 원핫 인코딩을 통해 나온 벡터 표현 방법이다.
  • 벡터 혹은 행렬의 대부분의 값이 0으로 표현된 벡터 혹은 행렬이다.
  • 희소 행렬은 단어의 개수가 늘어나면 벡터의 차원이 한없이 커지는 문제를 가지고 있다. 이는 공간적 낭비를 유발하고, 단어 간 유사도를 표현할 수 없는 단점이 있다.

 

*밀집 표현 (Dense Representation)

  • 희소 표현과 반대되는 표현으로, 벡터의 차원을 단어 집합의 크기로 상정하지 않는 방법이다.
  • 사용자가 설정한 값으로 모든 단어의 벡터 표현의 차원을 맞춘다.
  • 이 과정에서 0, 1만 가지는 것이 아니라 실수값을 가지게 된다.

https://wikidocs.net/60852

 

 

 

 

워드투벡터 (Word2Vec)

  • 단어 간 유사도를 반영할 수 있도록 단어의 의미를 벡터화할 수 있는 방법
  • 분산 표현으로 단어를 표현하여 단어 간 유사도를 계산할 수 있다.

 

*분산 표현

  • 분포 가설 가정 하에 비슷한 위치에서 등장하는 단어들은 비슷한 의미를 가진다는 가정.
  • 분산 표현은 분포 가설을 이용해 단어들의 셋을 학습하고, 벡터에 단어의 의미를 여러 차원에 분산하여 표현한다.
  • 이렇게 표현된 벡터는 원핫 벡터처럼 벡터의 차원이 단어 집합의 크기일 필요가 없고, 벡터의 차원이 상대적으로 저차원으로 줄어든다.
  • 또한 저차원에 단어의 의미를 여러 차원에 분산하여 표현함으로써 단어 간 유사도를 계산할 수 있다.

 

  • Word2Vec을 하는 방법은 CBOW, Skip-Gram 방법이 있다.
  • gensim 패키지를 이용하면 쉽게 변환이 가능하다.

 

 

CBOW (Continuous Bag of Words)

  • 주변에 있는 단어를 활용해 중간에 있는 단어를 예측하는 방법.
  • 중심 단어 예측을 위해 앞 뒤로 몇 개의 단어를 볼 지, 윈도우를 결정한다.
  • 윈도우 크기를 정했다면 윈도우를 계속 움직여 주변 단어와 중심 단어 선택을 바꿔가며 학습을 위한 데이터 셋을 만드는데, 이러한 방법을 슬라이딩 윈도우라고 한다.

 

 

Skip-Gram

  • 중심 단어를 토대로 주변 단어를 예측하는 방법
  • CBOW와 메커니즘 자체는 거의 동일하다.

 

 

네거티브 샘플링 (Negative Sampling)

  • 대체적으로 Word2Vec을 사용할 때 SGNS (Skip-gram with negative sampling)을 주로 활용한다.
  • 즉 스킵그램을 활용하며 네거티브 샘플링을 함께 활용한다.
  • Word2vec에는 한 가지 문제가 있는데, 바로 속도이다. 단어 집합의 크기가 수백만에 달한다면 굉장히 무거운 작업이기 때문이다.
  • 그런데 굳이 상관이 없는 다른 단어까지 소프트맥스와 역전파를 수행해야 할까? 네거티브 샘플링은 랜덤으로 선택된 주변 단어가 아닌 상관없는 단어들을 일부만 가져오는 방법이다.
  • 그렇게 훨씬 작은 단어 집합을 만들고 마지막 단계를 이진 분류 문제로 바꾸어 주변 단어들을 긍정으로 두고 랜덤으로 샘플링 된 단어들을 부정으로 둔 다음 이진 분류 문제를 수행하는 것.
  • 기존의 다중 클래스 분류를 이진 분류로 바꾸어 연산량에 있어 훨씬 효율적이다.

 

 

 

 

Glove

  • 카운트 기반과 예측 기반을 모두 사용하는 단어 임베딩 방법론
  • Word2Vec과 더불어 워드 임베딩 분야에서 자주 사용되는 방법.
  • 각 입력-출력 단어쌍에 대해 학습 데이터에서 주 단어가 한 윈도우 내에서 총 몇 번 동시에 등장했는지 사전에 미리 계산을 하고 입력 워드의 임베딩 벡터 Ui와 출력 워드의 임베딩 벡터 Uj의 내적값이 한 윈도우 안에서 두 단어가 동시에 나타난 횟수인 Pij에 가까워 질 수 있도록 한다.
  • Word2Vec은 특정한 입출력 단어쌍이 자주 등장한 경우 여러 번에 걸쳐 학습함으로써 내적값이 커짐으로써 학습이 빈번하기 될수록 더 연관을 주는 학습 방식이다.
  • 그에 비해 Glove는 애초에 어떤 단어쌍이 동시에 등장한 횟수를 미리 계산하고 이에 대한 로그값을 두 단어간의  내적값과 큰 차이가 없도록 하는 방식으로, 중복된 계산을 방지한다.
  • 이러한 계산상의 차이로 인해 학습이 빠르며 적은 데이터 셋에 대해서도 잘 동작한다.
  • 또한 단어 간의 문법적인 의미와 관계들까지 효과적으로 학습히 가능하다.

 

 

파이토치의 nn.Embedding()

  • 파이토치에서 임베딩 벡터를 사용하는 방법.
  • 임베딩 층을 만들어 훈련 데이터로부터 처음부터 임베딩 벡터를 학습하는 방법이다.
  • 입력으로 정수 인코딩이 된 시퀀스를 받아 밀집 벡터로 변환해준다.
  • 파라미터로는 단어의 수, 변환하려는 임베딩 차원, 패딩 인덱스를 받는다.

 

 

사전 학습된 워드 임베딩

  • 훈련 데이터가 적다면 nn.Embedding()으로 임베딩 벡터를 만드는 것이 쉽지 않다.
  • 그래서 Word2Vec이나 Glove 등으로 학습된 임베딩 벡터를 사용함으로써 성능을 개선할 수 있다.

 

 

 

원 핫 인코딩

from konlpy.tag import Okt  

okt = Okt()  
token = okt.morphs("나는 남자 아이이고, 너는 여자 아이이다.")  
print(token) #['나', '는', '남자', '아이', '이고', ',', '너', '는', '여자', '아이', '이다', '.']

#word2vec
word2index = {}
for voca in token:
     if voca not in word2index.keys():
       word2index[voca] = len(word2index)
print(word2index) #{'나': 0, '는': 1, '남자': 2, '아이': 3, '이고': 4, ',': 5, '너': 6, '여자': 7, '이다': 8, '.': 9}


#원 핫 인코딩
def one_hot_encoding(word, word2index):
       one_hot_vector = [0]*(len(word2index))
       index = word2index[word]
       one_hot_vector[index] = 1
       return one_hot_vector

one_hot_encoding("남자",word2index) #[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

 

 

Word2Vec

import nltk
nltk.download('punkt')

import urllib.request
import zipfile
from lxml import etree
import re
from nltk.tokenize import word_tokenize, sent_tokenize

# 데이터 다운로드
urllib.request.urlretrieve("https://raw.githubusercontent.com/GaoleMeng/RNN-and-FFNN-textClassification/master/ted_en-20160408.xml", filename="ted_en-20160408.xml")



#전처리
targetXML=open('ted_en-20160408.xml', 'r', encoding='UTF8')
# 저자의 경우 윈도우 바탕화면에서 작업하여서 'C:\Users\USER\Desktop\ted_en-20160408.xml'이 해당 파일의 경로.  
target_text = etree.parse(targetXML)
parse_text = '\n'.join(target_text.xpath('//content/text()'))
# xml 파일로부터 <content>와 </content> 사이의 내용만 가져온다.

content_text = re.sub(r'\([^)]*\)', '', parse_text)
# 정규 표현식의 sub 모듈을 통해 content 중간에 등장하는 (Audio), (Laughter) 등의 배경음 부분을 제거.
# 해당 코드는 괄호로 구성된 내용을 제거.

sent_text = sent_tokenize(content_text)
# 입력 코퍼스에 대해서 NLTK를 이용하여 문장 토큰화를 수행.

normalized_text = []
for string in sent_text:
     tokens = re.sub(r"[^a-z0-9]+", " ", string.lower())
     normalized_text.append(tokens)
# 각 문장에 대해서 구두점을 제거하고, 대문자를 소문자로 변환.

result = []
result = [word_tokenize(sentence) for sentence in normalized_text]
# 각 문장에 대해서 NLTK를 이용하여 단어 토큰화를 수행.




#Word2Vec 훈련 : gensim 패키지 사용시 쉽게 구현이 가능.
from gensim.models import Word2Vec, KeyedVectors
model = Word2Vec(sentences=result, 
                 size=100,      #워드 벡터의 특징 값. 임베딩 된 벡터의 차원
                 window=5,      #컨텍스트 윈도우 크기
                 min_count=5,   #단어 최소 빈도 수 제한
                 workers=4,     #학습을 위한 프로세스 수
                 sg=0)          #0은 cbow, 1은 skip-gram





#Word2Vec 모델 저장 및 로드
model.wv.save_word2vec_format('./eng_w2v') # 모델 저장
loaded_model = KeyedVectors.load_word2vec_format("eng_w2v") # 모델 로드

#유사한 단어 출력
model_result = loaded_model.most_similar("man")
print(model_result)

 

 

Glove

from glove import Corpus, Glove

corpus = Corpus() 
corpus.fit(result, window=5)
# 훈련 데이터로부터 GloVe에서 사용할 동시 등장 행렬 생성

glove = Glove(no_components=100, learning_rate=0.05)
glove.fit(corpus.matrix, epochs=20, no_threads=4, verbose=True)
glove.add_dictionary(corpus.dictionary)
# 학습에 이용할 쓰레드의 개수는 4로 설정, 에포크는 20.

#입력 단어와 유사한 단어 출력
model_result1=glove.most_similar("man")
print(model_result1)

 

 

사전 훈련된 워드 임베딩

from torchtext import data, datasets

#필드 선언
TEXT = data.Field(sequential=True, batch_first=True, lower=True)
LABEL = data.Field(sequential=False, batch_first=True)

#학습-테스트 셋 분리
trainset, testset = datasets.IMDB.splits(TEXT, LABEL)


#사전 훈련된 Word2Vec 모델 확인
from gensim.models import KeyedVectors
word2vec_model = KeyedVectors.load_word2vec_format('eng_w2v') #누군가 만든 모델이 아니라 위에 영어 파트에서 만든 거.


#사전 훈련된 Word2Vec을 초기 임베딩으로 사용
import torch
import torch.nn as nn
from torchtext.vocab import Vectors
vectors = Vectors(name="eng_w2v") # 사전 훈련된 Word2Vec 모델을 vectors에 저장
TEXT.build_vocab(trainset, vectors=vectors, max_size=10000, min_freq=10) # Word2Vec 모델을 임베딩 벡터값으로 초기화


#토치 텍스트에서 제공하는 사전 훈련된 워드 임베딩
'''
fasttext.en.300d
fasttext.simple.300d
glove.42B.300d
glove.840B.300d
glove.twitter.27B.25d
glove.twitter.27B.50d
glove.twitter.27B.100d
glove.twitter.27B.200d
glove.6B.50d
glove.6B.100d
glove.6B.200d
glove.6B.300d
'''

from torchtext.vocab import GloVe

TEXT.build_vocab(trainset, vectors=GloVe(name='6B', dim=300), max_size=10000, min_freq=10) #glove.6B.300d
LABEL.build_vocab(trainset)

embedding_layer = nn.Embedding.from_pretrained(TEXT.vocab.vectors, freeze=False)
embedding_layer(torch.LongTensor([10])) # 단어 this의 임베딩 벡터값
728x90
반응형