Developer's Development

3.3.3 [NLP] 자연어 데이터 준비(자연어 임베딩) 본문

LLM

3.3.3 [NLP] 자연어 데이터 준비(자연어 임베딩)

mylee 2025. 8. 21. 21:07
자연어 임베딩 (Embedding)

 

단어를 고정된 크기의 실수 벡터로 표현하는 방법이다. 즉, 텍스트 데이터를 수치 데이터(벡터)로 변환하여 컴퓨터로 처리할 수 있도록 만드는 기술이다. 임베딩은 단어, 문장, 문서 간의 관계를 벡터 공간에서 나타내며 의미와 문법 정보를 함축한다. 이를 통해 단어 간의 유사도와 의미적 관계를 수치화할 수 있으며, 딥러닝 모델의 입력으로 사용된다.

 

👉🏻 역할

1. 단어/문장 간 관련도 계산

임베딩은 단어 또는 문장을 벡터로 표현하여 벡터 간의 코사인 유사도, 유클리드 거리 등을 계산해 관련성을 측정할 수 있게 한다.

이를 통해 단어 간 의미적 유사성을 비교할 수 있다.

 

2. 의미/문법 정보 함축

임베딩은 단어의 문맥적 의미와 문법 정보를 벡터 공간에 반영한다.

예를 들어, "king"과 "queen"은 의미적으로 가까운 벡터로, "king - man + woman"은 "queen"과 유사한 벡터를 나타낸다.

 

  • 벡터화 (Vectorization)

데이터를 고정된 차원의 벡터로 변환하는 과정이다. 텍스트, 이미지, 오디오 등의 비정형 데이터를 수치화하여 머신러닝 및 딥러닝 모델이 이해할 수 있도록 한다. 데이터의 특징을 압축하고, 연산을 최적화하며, 유사도 계산을 가능하게 만든다.

 

👉🏻 필요성

기계 학습 모델은 숫자 데이터를 입력으로 받기 때문에 텍스트 등의 비정형 데이터를 변환해야 함

벡터화된 데이터는 수학적 연산이 가능하여, 패턴 분석 및 예측 모델 학습에 적합

벡터화 방식에 따라 모델 성능이 크게 달라질 수 있음

 

👉🏻 종류

- 수동 벡터화 (Feature Engineering 기반): 사람이 직접 특징을 정의하고 수치화 하는 방식

- 자동 벡터화 (딥러닝 기반): 모델이 데이터에서 직접 특징을 추출하여 벡터로 변환하는 방식

 

👉🏻 한계

고차원 벡터의 경우 계산 비용이 증가할 수 있음 (차원의 저주 문제)

적절한 벡터화 방식을 선택하지 않으면 정보 손실이 발생할 수 있음

특정 벡터화 방식이 모든 문제에 적합한 것은 아니므로, 데이터 특성과 목표에 맞는 방법을 선택해야 함

 

  • 벡터화 방식 (임베딩 기법)

👉🏻 형태소 벡터화

형태소 분석은 단어의 기본 형태(어근)로 분리하여 텍스트를 벡터화하며, 이를 통해 문장의 구조적 정보를 반영할 수 있다.

 

👉🏻 Bag of Words (BoW)

텍스트에서 각 단어의 출현 빈도를 벡터로 표현하며, 단어 순서는 고려하지 않는다.

 

👉🏻 TF-IDF

단어의 출현 빈도(TF)와 문서 내 희소성(IDF)을 결합하여 단어의 중요도를 나타낸다.

 

👉🏻 Word2Vec

1. 단어를 고차원 벡터로 임베딩(= 고차원 벡터 공간에 매핑)하는 방법으로, 의미적 유사성을 반영한다.

2. CBOW와 Skip-gram 두 가지 모델이 있다.

  - CBOW(Continuous Bag of Words) : 주변 단어를 이용해 중심 단어를 예측

  - Skip-gram : 중심 단어를 이용해 주변 단어를 예측

3. 특징

  - 단어의 의미적 유사도를 벡터의 거리로 표현한다.

  - 연산을 통해 의미적 관계를 추론할 수 있다.

 

⭐ Word Embedding

단어를 고정된 차원의 벡터로 변환하는 기술로, 단어 간의 의미적 유사성을 반영하도록 학습된 벡터를 말한다.

의미적 유사성 반영, 밀집 벡터, 문맥 정보 반영, 학습 기반 벡터

 

👉🏻 Glove (Global Vectors for Word Representation)

단어 간 공기 행렬(= 동시 등장 행렬, co-occurrence matrix)을 사용해 임베딩(= 단어 벡터)을 학습한다.

전역적인 통계 정보를 반영하여 임베딩을 생성한다.

Word2Vec보다 대규모 데이터 학습에 적합하다.

 

👉🏻 Doc2Vec

문서 단위로 벡터를 생성하며, 문서 유사도 계산에 사용된다. Word2Vec의 확장 모델이다.

 

👉🏻 LDA

문서의 주제를 발견하는 데 사용되며, 단어가 특정 주제에 속할 확률을 기반으로 문서를 임베딩한다.

 

👉🏻 FastText

단어 자체뿐만 아니라 n-그램(문자 단위의 서브워드) 정보를 활용하여 임베딩을 학습한다.

단어를 문자 n-그램의 조합으로 표현하고, 회귀 단어와 신조어에 대한 처리 능력을 향상시킨다.

형태소 정보를 반영하여 유사 단어의 임베딩 유사도를 높인다.

OOV(Out-Of-Vocabulary) 문제를 완화한다.

 

 

실습 (Word Embedding)

 

Embedding Vector 시각화 Wevi: https://ronxin.github.io/wevi/

 

wevi

Training data (context|target): Presets: Update and Restart Update Learning Rate Next 20 100 500 PCA

ronxin.github.io

!pip install gensim

 

👉🏻 데이터 로드

import gdown

url = 'https://drive.google.com/uc?id=1DCgLPJsfyLGZ99lB-aF8EvpKIWSZYgp4'
output = 'ted_en.xml'

gdown.download(url, output)
# 전처리에 사용할 라이브러리 import
from lxml import etree	# xml 데이터를 tree 구조로 파싱 가능
import re
from nltk.tokenize import word_tokenize, sent_tokenize
from nltk.corpus import stopwords

f = open('ted_en.xml', 'r', encoding='utf-8')
xml = etree.parse(f)	# xml 파싱

contents = xml.xpath('//content/text()')	# content 태그 하위의 text를 가져옴
print(len(contents))						# 2085

# contents[:3]	# 3개 정도만 확인 1차) /n 발견!, 2차) (Laghter) 발견!

corpus = '\n'.join(contents)

# 정규표현식을 이용해 (Laghter), (Applause) 등 제거
corpus = re.sub(r'\([^)]*\)', '', corpus)
print(corpus)
sentences = sent_tokenize(corpus)

preprocessed_sentences = []
en_stopwords = stopwords.words('english')

for sentence in sentences:
    sentence = sentence.lower()
    re.sub(r'[^a-z0-9]', ' ', sentence)     # 특수문자 제거
    tokens = word_tokenize(sentence)
    tokens = [token for token in tokens if token not in en_stopwords]
    preprocessed_sentences.append(tokens)

preprocessed_sentences[:5]
# 데이터 전처리 (토큰화, 대소문자 정규화, 불용어 처리)
sentences = sent_tokenize(corpus)

preprocessed_sentences = []
en_stopwords = stopwords.words('english')

for sentence in sentences:
    sentence = sentence.lower()
    sentence = re.sub(r'[^a-z0-9]', ' ', sentence)
    tokens = word_tokenize(sentence)
    tokens = [token for token in tokens if token not in en_stopwords]
    preprocessed_sentences.append(tokens)

preprocessed_sentences[:5]

 

👉🏻 Embedding 모델 학습

from gensim.models import Word2Vec

model = Word2Vec(
    sentences=preprocessed_sentences,   # corpus
    vector_size=100,                    # 임베딩 벡터 차원
    sg=0,                               # 학습 알고리즘 (0:CBOW, 1:Skip-gram)
    window=5,                           # 주변 단어 수 (앞뒤로 n개 사용)
    min_count=5                         # 최소 빈도
)

model.wv.vectors.shape	# (21462, 100)
import pandas as pd

pd.DataFrame(model.wv.vectors, index=model.wv.index_to_key).head(10)
# 학습된 단어 임베딩 저장
model.wv.save_word2vec_format('ted_en_w2v')
# 임베딩 모델 로드
from gensim.models import KeyedVectors

load_model = KeyedVectors.load_word2vec_format('ted_en_w2v')

 

👉🏻 유사도 계산

# model: Word2Vec
model.wv.most_similar('man')
# model.wv.most_similar('asdfasdfasdfasdf')     # 임베딩 벡터에 없는 단어로 조회 시 KeyError 발생

# load_model: KeyedVectors == Word2Vec.wv
load_model.most_similar('man')

model.wv.similarity('man', 'husband')	# 0.72449714

model.wv['man']

 

  • 한국어 Word Embedding

NSMC (Naver Sentimente Movie Corpus)

import urllib.request

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

ratings_df = pd.read_csv('naver_movie_ratings.txt', sep='\t')
ratings_df.isnull().sum()	# document 결측치 8개 확인!
ratings_df = ratings_df.dropna(how='any')

ratings_df['document'][200:300]		# 한국어 아닌거 제외하고싶당
ratings_df['document'] = ratings_df['document'].replace(r'[^0-9가-힣ㄱ-ㅎㅏ-ㅣ\s]', '', regex=True)
from konlpy.tag import Okt
from tqdm import tqdm		# 진행상황을 시각화해줌

okt = Okt()
ko_stopwords = ['은', '는', '이', '가', '을', '를', '과', '와', '들', '도',
                '부터', '까지', '에', '나', '너', '그', '걔', '얘']

preprocessed_data = []
for sentence in tqdm(ratings_df['document']):
    tokens = okt.morphs(sentence, stem=True)
    tokens = [token for token in tokens if token not in ko_stopwords]
    preprocessed_data.append(tokens)
# 100%|██████████| 199992/199992 [08:28<00:00, 393.04it/s]
model = Word2Vec(
    sentences=preprocessed_data,
    vector_size=100,
    window=5,
    min_count=5,
    sg=0
)

model.wv.vectors.shape	# (16841, 100)
# model.wv.most_similar('극장')
# model.wv.similarity('김혜수', '원빈') 	# 0.7855145

# 모델 저장
model.wv.save_word2vec_format('naver_movie_ratings_w2v')

 

👉🏻 임베딩 시각화

 

아래 URL 접속해서 생성된 파일들 넣고 확인하기

https://projector.tensorflow.org/

 

Embedding projector - visualization of high-dimensional data

Visualize high dimensional data.

projector.tensorflow.org

# metadata.tsv(vector에 대응되는 단어 리스트), tensor.tsv(단어 벡터값) 파일 생성
!python -m gensim.scripts.word2vec2tensor --input naver_movie_ratings_w2v --output naver_movie_ratings_w2v

신기방기

 

  • 사전 훈련된 임베딩
import gdown

url = 'https://drive.google.com/uc?id=11MWLNUBLOJWpJePTbOJwCtcgEryPGKGj'
output = 'GoogleNews_vecs.bin.gz'

gdown.download(url, output)
from gensim.models import KeyedVectors

google_news_wv = KeyedVectors.load_word2vec_format('GoogleNews_vecs.bin.gz', binary=True)
google_news_wv.vectors.shape					# (3000000, 300)
google_news_wv.similarity('man', 'husband')		# 0.34499747 /아까보다 작당
google_news_wv.most_similar('man', topn=5)		# 유사도가 높은 상위 n개만 가져옴

# 두 리스트 간의 평균 유사도 계산
google_news_wv.n_similarity(['king', 'queen'], ['man', 'woman'])	# 0.24791394

google_news_wv.similar_by_word('man', topn=5)

# 단어가 임베딩에 존재하는지 확인
google_news_wv.has_index_for('man')				# True

 

 

실습 (FastText)
  • gensim FastText
# 데이터 오픈 및 전처리
f = open('ted_en.xml', 'r', encoding='utf-8')
xml = etree.parse(f)

contents = xml.xpath('//content/text()')
corpus = '\n'.join(contents)
corpus = re.sub(r'\([^)]*\)', '', corpus)

sentences = sent_tokenize(corpus)

preprocessed_sentences = []
en_stopwords = stopwords.words('english')

for sentence in sentences:
    sentence = sentence.lower()
    re.sub(r'[^a-z0-9]', ' ', sentence)
    tokens = word_tokenize(sentence)
    tokens = [token for token in tokens if token not in en_stopwords]
    preprocessed_sentences.append(tokens)
from gensim.models import Word2Vec

w2v_model = Word2Vec(
    sentences=preprocessed_sentences,
    vector_size=100,
    window=5,
    min_count=5,
    sg=0
)

w2v_model.wv.vectors.shape

w2v_model.wv.most_similar('father')		# 유사도 높은 단어 확인
# w2v_model.wv.most_similar('luckfather') 
# KeyError: "Key 'luckfather' not present in vocabulary" == OOV 이슈
fasttext_model = FastText(
    sentences=preprocessed_sentences,
    vector_size=100,
    window=5,
    min_count=5,
    sg=0
)

fasttext_model.wv.vectors.shape		# (22025, 100)

fasttext_model.wv.most_similar('father')
fasttext_model.wv.most_similar('luckfather')	# OOV 이슈 발생하지 않음

# OOV 단어도 subword 기반으로 검색해 vector 반환
fasttext_model.wv['luckyfather']

 

  • fasttext 패키지 활용
!pip install fasttext-wheel
import fasttext

model = fasttext.train_unsupervised(
    'naver_movie_ratings.txt',
    model='skipgram',
    minCount=1,
    dim=100,
    minn=3,      # subword 최소 ngram
    maxn=5       # sumword 최대 ngram
)

model.get_word_vector('극장')		# '극장'이라는 단어의 벡터 값들 반환
model.get_subwords('영화관')
"""
(['영화관', '<영화', '<영화관', '<영화관>', '영화관', '영화관>', '화관>'],
 array([   2062, 1921845, 1442415, 1378913, 2245977, 1515139, 1352938]))
"""