[NLP] 텍스트 벡터화 : 워드 임베딩(Word embedding) 실습

2023. 11. 3. 17:03ML&DL/NLP

워드 임베딩

 

- 인공 신경망을 이용하여 단어의 벡터값을 얻는 방법

- 학습 후에는 각 단어 벡터 간의 유사도를 계산 할 수 있다.

 

1) 랜덤 초기화 임베딩

- 초기에 모든 단어의 임베딩 벡터값은 랜덤 초기화 됨.

- 오차를 구하는 과정(역전파 하는 과정)에서 embedding table을 학습

 

2) 사전 훈련된 임베딩 (Pre-trained Word Embedding)

- 방대한 양의 텍스트 데이터로 이미 훈련되어있는 임베딩 벡터값을 사용하는 것 

- 대표적인 알고리즘으로 word2Vec, FastText, GloVe가 존재함

 

 

💻   실습

Word2Vec (영어)

 

라이브러리 불러오기

import gensim
gensim.__version__
import re
from lxml import etree
import urllib.request
import zipfile
import nltk
from nltk.tokenize import word_tokenize, sent_tokenize
nltk.download('punkt')

 

 

데이터 불러오기 

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')
target_text = etree.parse(targetXML)

# xml 파일로부터 <content>와 </content> 사이의 내용만 가져온다.
parse_text = '\n'.join(target_text.xpath('//content/text()'))

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

 

- 문장 토큰화

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

 

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

 

- 단어 토큰화

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

 

 

- Word2Vec 훈련

* 하이퍼파라미터값

   vector_size = 워드 벡터의 특징 값. 즉, 임베딩 된 벡터의 차원.
   window = 컨텍스트 윈도우 크기
   min_count = 단어 최소 빈도 수 제한 (빈도가 적은 단어들은 학습하지 않는다.)
   workers = 학습을 위한 프로세스 수
   sg = 0은 CBOW, 1은 Skip-gram.

from gensim.models import Word2Vec
model = Word2Vec(sentences=result, vector_size=100, window=5, min_count=5, workers=4, sg=0)

 

- 훈련된 모델 테스트 

* man 과 유사한 단어

model_result = model.wv.most_similar("man")
print(model_result)

 

[('woman', 0.8469663262367249), ('guy', 0.7982245087623596), ('boy', 0.7504032850265503), ('lady', 0.7486523389816284), ('girl', 0.7446972131729126), ('soldier', 0.7413182854652405), ('kid', 0.7310553193092346), ('gentleman', 0.7181988954544067), ('david', 0.6683082580566406), ('friend', 0.6598968505859375)]

 

=> 1과 가까울수록 유사도가 높은 것

 

model.wv["man"]

 

- 훈련된 모델 저장 & 테스트

from gensim.models import KeyedVectors
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)

 

Word2Vec (한국어)

라이브러리 불러오기

!pip install konlpy
!pip install mecab-python
!bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)
import urllib.request
from konlpy.tag import Mecab
from gensim.models.word2vec import Word2Vec
import pandas as pd
import matplotlib.pyplot as plt

 

데이터 불러오기 

urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings.txt", filename="ratings.txt")

 

train_data = pd.read_table('ratings.txt')

 

데이터 확인

train_data[:5] # 상위 5개 출력

데이터 전처리

train_data = train_data.dropna(how = 'any') # Null 값이 존재하는 행 제거
print(train_data.isnull().values.any()) # Null 값이 존재하는지 확인
# 정규 표현식을 통한 한글 외 문자 제거
train_data['document'] = train_data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","")

 

- 형태소 토큰화

* 의미가 중요하지 않은 단어는 불용어 처리하여 제거 해줌

* mecab 사용

# 불용어 정의
stopwords = ['도', '는', '다', '의', '가', '이', '은', '한', '에', '하', '고', '을', '를', '인', '듯', '과', '와', '네', '들', '듯', '지', '임', '게']
# 형태소 분석기 mecab을 사용한 토큰화 작업 (다소 시간 소요)
mecab = Mecab()
tokenized_data = []
for sentence in train_data['document']:
    temp_X = mecab.morphs(sentence) # 토큰화
    temp_X = [word for word in temp_X if not word in stopwords] # 불용어 제거
    tokenized_data.append(temp_X)

 

- Word2Vec 훈련

from gensim.models import Word2Vec
model = Word2Vec(sentences = tokenized_data, vector_size = 100, window = 5, min_count = 5, workers = 4, sg = 0)

 

- 임베딩 매트릭스 확인

# 완성된 임베딩 매트릭스의 크기 확인
model.wv.vectors.shape

 

- 훈련된 Word2Vec 모델 테스트

print(model.wv.most_similar("최민식"))

[('한석규', 0.8762632012367249), ('안성기', 0.8671063780784607), ('송강호', 0.8488789796829224), ('박신양', 0.8463734984397888), ('드니로', 0.84193354845047), ('김혜수', 0.8415705561637878), ('신들린', 0.8410726189613342), ('설경구', 0.8391404151916504), ('채민서', 0.8342630863189697), ('조진웅', 0.8342404961585999)]

print(model.wv.most_similar("히어로"))

[('호러', 0.8413481116294861), ('슬래셔', 0.8230418562889099), ('하이틴', 0.7898564338684082), ('정통', 0.7874332070350647), ('최고봉', 0.7855408787727356), ('패러디', 0.782534658908844), ('고어', 0.7777903079986572), ('로코', 0.7744789123535156), ('무비', 0.7713674902915955), ('블록버스터', 0.7706264853477478)]

 

- 모델 저장

from gensim.models import KeyedVectors
model.wv.save_word2vec_format('kor_w2v') # 모델 저장
loaded_model = KeyedVectors.load_word2vec_format("eng_w2v") # Word2Vec 모델 로드

 

 

Word2Vec의 문제점 살펴보기

* OOV 문제(Out-Of-Vocabulary Problem) : Vocabulary에 존재하지 않는 단어가 등장하는 문제

 

-> memory 의 유사도는 구할 수 있지만, 이와 비슷한 memorry(vocabulary에 존재 x)의 유사도를 구할 수 없다는 문제점 발생

 

 

FastText

* Word2Vec의 OOV 문제를 해결할 수 있음

 

- FastText 훈련

from gensim.models import FastText
fasttext_model = FastText(result, vector_size=100, window=5, min_count=5, workers=4, sg=1)

 

 

-> Word2Vec에서 구할 수 없었던 'memorry'의 벡터 간 유사도를 출력 가능한 것을 볼 수 있음.