주제
1. 임베딩(Embedding)
2. 단어 임베딩
3. 문장 임베딩
4. 감정 분석(Sentiment Analysis)
5 . 감정 분석 실습
1. 임베딩(Embedding)
- 컴퓨터가 알아볼 수 있는 숫자의 형태로 변형하는 과정을 뜻함
- 즉, 텍스트 데이터를 벡터로 변환하는 기술
- 임베딩의 결과는 벡터이므로 벡터가 존재하는 벡터 공간에 표현이 가능
- 좋은 임베딩은 비슷한 의미의 단어들이 비슷한 공간에 존재하게 됨
1.1 인코딩(Encoding) vs. 임베딩(Embedding)
- 인코딩 : 데이터를 표준화한 형식으로 변환 목적. 본질적으로 의미가 변하지 않으며 변환하는데 중점을 둠
- 임베딩 : 머신 러닝 모델이 처리하기 쉬운 형태로 변환하는 것이 목적.
1.2 원핫 인코딩(One-Hot Encoding)
- 특정 단어를 표현하는 위치만 1이고 나머지의 위치는 0으로 구성하는 임베딩 방법
- 단어의 수만큼의 크기를 갖는 벡터가 생성됨
- 직관적으로 쉽게 단어를 변환
- 한계 :
- 차원의 저주
- 하나의 단어를 표현하는 벡터의 크기는 전체 단어 수와 같기 때문에 차원이 크면 효율성과 계산 복잡도가 증가함
- 의미 부재
- 의미적으로 비슷한 단어끼리 비슷한 공간에 존재하지 않을 수 있음
- 정보의 희소성
- 특정 단어의 위치만 1이므로 중요한 정보가 매우 희소함(0이 많음)
- 차원의 저주
1.3 분산 표현(Distribution Representation)
- 정수가 아닌 실수로 이루어진 벡터로 임베딩 진행
- 실수로 단어를 변경하면서 데이터의 의미를 여러 특성에 걸쳐 분산시켜 표현함
- 다양한 의미와 문맥적 특성을 풍부하게 포착 가능함
- 임베딩이 잘 된다면 비슷한 의미를 갖는 단어들은 비슷한 분포를 가지게 됨
- 벡터 공간에 표현하면 비슷한 공간에 표현
- 수의 집합이므로 의미 차원에서 연산이 가능함
2. 단어 임베딩
- 단어에 원핫 인코딩을 적용하면 문장을 단어의 형태로 분해한다(tokenization)
- 고유한 단어 집합 생성하고, 고유 단어에 독립된 인덱스를 부여함
- 나머지 자리는 0으로 채우고 벡터를 생성
2.1 학습 기반 임베딩
1. 분포 가설(Distribution Hypothesis)
- 분포 표현의 이론적 기반이 되는 가설로, "단어의 의미는 그 단어가 나타나는 문맥에 의해서 결정된다"라는 아이디어가 중심
- 최신 임베딩 기법은 이러한 가설을 기반으로 연구가 됨
- 특정 단어의 의미를 숫자 벡터로 표현하기 위해 문맥과 주변 단어를 이용해 학습 진행
2. Word2Vec
- 문장 위를 움직이는 슬라이딩 윈도우를 만든다
- 해당 윈도우는 이동하면서 동일한 개수의 단어를 포함, 포함된 단어들 사이의 연산을 진행해 각 단어들을 임베딩함
- 두 가지 방법으로 변환 :
- CBOW : 이웃한 단어들로 가운데 단어가 무엇인지 예측하는 과정에서 임베딩을 진행
- Skip-gram : 가운데 단어로 이웃한 단어들을 예측하는 과정으로 임베딩 진행

3. GloVe(Global Vectors for Word Representation)
- 전체 글에 단어 간 공동 출현 통계를 이용해 각 단어의 의미를 벡터로 표현하고 각 단어 쌍이 얼마나 자주 함께 나타나는지를 기록
- 이것을 공동출현행렬이라고 함
- 공동으로 자주 출현하는 단어들을 벡터 공간 내 비슷한 위치에 존재하도록 임베딩
4. 그 외 딥러닝을 활용한 학습 기반 단어 임베딩
- BERT : 단어 별 중요도 기반의 모듈을 활용하여 문장 내적/외적 관계를 바탕으로 임베딩
- CLIP : 이미지를 설명하는 글에서 이미지와 텍스트의 공동 의미를 임베딩에 활용
2.2 단어 임베딩 실습
- 사용할 문장
sentence = "사과는 맛있다 바나나는 맛있다"
- 문장을 단어로 분리 (띄어쓰기 단위를 사용)
# 문장을 단어로 분리
# 띄어쓰기 단위를 사용
words = sentence.split()
print(words)
- 단어 배열 생성
# 단어 배열을 NumPy 배열로 변환
words_array = np.array(words).reshape(-1, 1)
print(words_array)
- 원핫 인코딩 적용
# 원핫 인코딩 적용!
one_hot_encoded = encoder.fit_transform(words_array)
print(one_hot_encoded)
- Word2Vec 적용
from gensim.models import KeyedVectors
from gensim.downloader import load
def use_word2vec(word, model):
try:
word_vector = model[word]
return word_vector
except KeyError:
return "단어가 모델의 어휘에 없습니다."
- 기본 Word2Vec 모델 불러오기(300단어)
# 제공하는 기본 Word2Vec 모델 불러오기
# 약 10~12분 정도 소요
word2vec_model = load('word2vec-google-news-300')
- 코사인 유사도를 이용하여 단어의 유사도를 확인할 함수 생성
from scipy.spatial.distance import cosine
def word_similarity(word1, word2, model):
try:
vector1 = model[word1]
vector2 = model[word2]
similarity = 1 - cosine(vector1, vector2) # 코사인 유사도 계산
return similarity
except KeyError as e:
return str(e)
def most_similar(word, model, topn=5):
try:
similar_words = model.most_similar(word, topn=topn) # 가장 유사한 단어 찾기
return similar_words
except KeyError as e:
return str(e)
- 두 단어 사이의 유사도 확인
# 두 단어 사이의 유사도 확인
print('football & basketball 유사도 : ' , word_similarity('football', 'basketball', word2vec_model))
print('football & airplane 유사도 : ' , word_similarity('football', 'airplane', word2vec_model))

- 특정 단어와 가장 유사한 단어 찾기
- soccer, fooball, Football, basketball, footbal
# 특정 단어와 가장 유사한 단어 보여주기
print('football과 가장 유사한 단어 5개는 : ' , most_similar('football', word2vec_model, topn=5))
- GloVe 적용
def use_glove(word, model):
try:
word_vector = model[word]
return word_vector
except KeyError:
return "단어가 모델의 어휘에 없습니다."
# 제공하는 GloVe 모델 불러오기
# 약 5분정도 소요
glove_model = load('glove-wiki-gigaword-300')
# 두 단어 사이의 유사도 확인 (GloVe)
print('football & basketball 유사도 : ' , word_similarity('football', 'basketball', glove_model))
print('football & airplane 유사도 : ' , word_similarity('football', 'airplane', glove_model))

3. 문장 임베딩
3.1 단어 임베딩과 문장 임베딩 차이
- 문장 임베딩 : 문장 자체를 숫자의 형태로 변환
- 단어를 넘어 문장 자체가 갖고 있는 의미를 벡터로 표현함
- 단어 임베딩과 문장 임베딩은 서로 다른 목적과 사용 사례를 기반으로 개발
3.2 문장에 원핫 인코딩 적용
- 각 단어에 독립된 인덱싱 진행
- 문장에 소속된 각 단어를 해당 단어의 인덱스 위치에 1을 부여하고 나머지를 0으로 채움
- 예시) 사과는 = 0, 바나나는 = 1, 맛있다 = 2
- 사과는 맛있다 → [1, 0, 1]
- 바나나는 맛있다 → [0, 1, 1]
3.3 단어 임베딩을 활용한 문장 임베딩
- 각 단어들의 임베딩을 이용해 문장의 임베딩을 생성
- 각 단어의 임베딩의 평균을 활용
- TF-IDF를 활용한 단어 가중치를 적용
- TF-IDF : 문장 내 단어의 중요도를 나타내는 척도
- 가중된 값들을 활용해 평균 값 활용
- 문장 임베딩은 고차원적인 연산이 필요함 → 딥러닝 기법을 활용하는 과정에서 발전이 됨
- 딥러닝 모델이 단어를 임베딩하는 과정에서 문장을 구성하는 각 단어에서 정보를 공급받아 전체 문장의 의미를 담는 벡터를 생성한다
3.4 문장 임베딩 실습
- CountVectorizer 사용
- 텍스트 데이터를 단어의 빈도로 변환하는 객체
- binary = True이면 빈도가 2 이상이어도 1로 표현함
- binary = False이면 BoW(Bag of Words)로 사용. 즉, 빈도만큼 표현함
from sklearn.feature_extraction.text import CountVectorizer
# 문장 리스트
sentences = ["사과는 맛있다",
"바나나는 맛있다",
"딸기는 맛있다",
"김치는 맵다",
"짜장면은 달다",
"소고기는 좋다"]
vectorizer = CountVectorizer(binary=True)
vectorizer.fit(sentences)
# 단어-인덱스 매핑
word_to_index = vectorizer.vocabulary_
print("단어-인덱스 매핑:", word_to_index)

- 문장을 벡터로 변환
# 문장을 벡터로 변환
input_sentence = ["소고기는 맛있다"]
# input_sentence = ["소고기는 소고기는 소고기는 맛있다"]
sentence_vectors = vectorizer.transform(input_sentence).toarray()
print(f"입력 문장 '{input_sentence[0]}'의 원핫 임베딩 벡터 :", sentence_vectors[0])

- CountVectorizer의 binary 인자를 False로 두면 :
# input_sentence = ["소고기는 맛있다"]
input_sentence = ["소고기는 소고기는 소고기는 맛있다"]
sentence_vectors = vectorizer.transform(input_sentence).toarray()
print(f"입력 문장 '{input_sentence[0]}'의 원핫 임베딩 벡터 :", sentence_vectors[0])

4. 감정 분석(Sentiment Analysis)
- 텍스트에서 작성자의 감정 상태나 태도를 파악하고 분류하는 과정
- 텍스트 마이닝과 자연어 처리(NLP) 분야에 속함
- 일반적인 기본 범주 :
- 긍정적(Positive)
- 부정적(Negative)
- 중립적(Neutral)
4.1 감정 분석의 응용
- 소셜 미디어 감성 모니터링
- 플랫폼 내 게시글 분석해 대중의 감정과 태도 파악
- 특정 사건, 제품, 브랜드, 정치적 이슈 등에 대한 대중의 반응을 모니터링
- 마케팅 효과를 분석
- 고객 서비스 분석 및 소비자 인사이트
- 고객 서비스 대화, 콜 센터 통화 내용, 이메일 등 분석으로 고객의 만족 파악
- 고객의 불만을 해결하기 위한 인사이트 제공
- 헬스 케어 및 의료
- 환자의 감정 상태 분석
- 우울증, 불안 장애 등 조기 징후 파악
4.2 텍스트 데이터 전처리
- 전처리 과정 : Tokenize → Stop Words 제거 → Stemming
- Tokenize
- 원문 글을 분석에 사용할 기본 개념 단위로 분리하는 과정
- 토큰의 정의는 문제에 따라 사용자가 정의하기 나름
- 이 단위가 분석 과정의 가장 작은 의미 단위가 됨
- Stop Words 제거
- 개념 단위로 나뉜 개체에서 의미가 없는 개체를 제거하는 과정
- 일반적으로 stop words는 미리 사전 정의하고 해당 단어가 나오면 제거하는 형태
- 한글에서는 '그리고', '아', '내가' 등
- 영어의 경우 'the', 'a', 'and' 등
- Stemming
- 단어 기반의 토큰을 사용하는 경우 특정 단어를 그것의 기본 형태(어간)으로 축소하는 과정
- 접두사나 접미사를 제거하여 단어의 기본 줄기(stem)를 찾는 과정
- Running → Run
- Runner → Run
- 문맥을 고려하지 않아 잘못된 결과를 반환할 수 있음
- Lemmatization
- 단어를 그 의미론적 기본 형태(Lemma)로 변환하는 과정
- 특정 단어의 품사와 문맥을 고려해 정확한 기본 형태를 찾아냄
- Stemming에 비해 복잡하고 시간이 오래 걸리지만 정확도가 좋음
- 딥러닝 기반 전처리
- 전통적인 방식에서 중요하게 생각했던 처리 과정을 일부 변형하여 사용
5 . 감정 분석 실습
- 사용 데이터 : 케글의 Yelp 리뷰 데이터(데이터셋)
- 레스토랑 리뷰 등록 플랫폼으로, 데이터 상 만족도가 1이면 만족 / 0이면 불만족
- 분류 클래스의 비율은 1:1로 동일 (총 1000개의 데이터)
- 문제 정의 : 사용자의 텍스트 리뷰를 바탕으로 그들의 감정 상태를 예측
전처리
- 데이터 탐색

- 전처리 함수 생성 (모두 소문자로 변환, 표현식 제거, 띄어쓰기로 tokenization, stop words 제거)
# 최종 함수로 표현
import nltk
from nltk.corpus import stopwords
nltk.download('stopwords')
stop_words = set(stopwords.words('english'))
def preprocessing(text) :
text = text.lower()
text = re.sub(r'\W', ' ', text)
text = text.split()
text = [t for t in text if t not in stop_words]
return text
- 전체 리뷰 전처리
# 기존 데이터 프레임 업데이트
# apply 함수 사용
data['preprocessed'] = data['text'].apply(preprocessing)
data.head()

문장의 임베딩 구하기
- GloVe 적용
from gensim.downloader import load
# 너무 큰 차원은 시간이 오려걸려 100 차원으로 로드 진행
# 약 2분 소요
glove = load('glove-wiki-gigaword-100')
preprocessed_word = data['preprocessed'][0]

- TF-IDF 적용
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform([' '.join(doc) for doc in data['preprocessed']]) # 모든 문장을 모아 하나의 큰 문서 덩어리를 생성
tfidf_feature_names = vectorizer.get_feature_names_out()
- wow가 어느 위치에 있는지 확인
import numpy as np
np.where(tfidf_feature_names == 'wow')[0][0]

- 각 토큰의 TF-IDF 확인
for word in preprocessed_word :
doc_idx = 0 # 0 : 원래 문서의 0번째 문장이므로
word_idx = np.where(tfidf_feature_names == word)[0][0] # 단어 집합으로부터 word의 위치를 찾아냄
value = tfidf_matrix.toarray()[doc_idx][word_idx]
# toarray() : tf-idf matrix가 0이 많은 sparse matrix여서 이를 원래 0과 숫자값으로 이루어진 형태로 변환
print(f'{word}의 tf-idf 값 : {value:.4f}')

- 위 과정을 하나의 함수로 표현 :
import numpy as np
def sentence_embedding(tfidf_matrix, tfidf_feature_names, doc, doc_idx):
embeddings = []
for word in doc:
# GloVe에서 학습한 데이터와 tf-idf에서 학습한 데이터만 취급
# 그렇지 않은 데이터는 무시됨
if word in glove and word in tfidf_feature_names:
# 여기서는 transform을 쓰지 않고 이미 학습한 matrix에서 indexing으로 가져옴
# 속도 효율성이 높음
# 만약 처음 보는 문장에 대해서 TF-IDF를 한다면 transform이 필수!
word_idx = np.where(tfidf_feature_names==word)[0][0]
tfidf_weight = tfidf_matrix.toarray()[doc_idx, word_idx]
embeddings.append(glove[word] * tfidf_weight)
return np.mean(embeddings, axis=0) if embeddings else np.zeros(100) # GloVe 차원에 맞춰 조정
- 문장 임베딩 진행하여 임베딩 값을 새로운 열로 저장
# 문장 임베딩 값을 새로운 열로 저장
# 여러 입력을 넣어주기 위해 lambda 함수를 활용하고
# 특정 행을 의미하는 row를 이용해 행 번호를 넣어줌 (row.name)
data['sentence_emb'] = data.apply(lambda row: sentence_embedding(tfidf_matrix,
tfidf_feature_names,
row['preprocessed'],
row.name), axis=1)
data.head()

로지스틱 회귀 모델 학습 및 평가
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X = np.stack(data['sentence_emb'].values)
y = np.stack(data['sentiment'].values)
# 훈련 데이터와 테스트 데이터로 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 로지스틱 회귀 모델 생성 및 학습
model = LogisticRegression()
model.fit(X_train, y_train)
# 학습 결과 확인을 위해 검증 데이터 추론 진행
predictions = model.predict(X_test)
- 성능 확인

- 정확도 등의 평가 지표 확인

※ TF-IDF 란?
- 각 단어마다 중요도를 나타내는 수치값
- TF 파트와 IDF 파트의 곱으로 계산
- TF(Term Frequency, 단어의 빈도) :
- 특정 단어가 문서 내에 얼마나 자주 등장하는지를 나타내는 빈도

- IDF(Inverse Document Frequency, 역 문서 빈도) :
- 특정 단어가 얼마나 여러 문서에서 등장하는지
- 모든 문서에 자주 등장한 단어 : 중요도가 낮음
- 특정 문서에서만 자주 등장한 단어 : 중요도가 높음

- D는 전체 문서를 의미
- TF-IDF 계산은 TF와 IDF값을 곱해서 산출
- 이 값은 문서 d 안에서 단어 t가 갖는 상대적 중요도
- 높은 TF-IDF 값을 갖는 단어는 해당 문서에서 더 많은 정보를 제공
- 전체 문서 집합 D보다 의미 있는 특징을 갖고 있음
'Data Science > TIL (Today I Learned)' 카테고리의 다른 글
| 프로그래머스 데이터분석 데브코스 1기 - 64일차 (0) | 2024.02.22 |
|---|---|
| 프로그래머스 데이터분석 데브코스 1기 - 63일차 (0) | 2024.02.21 |
| 프로그래머스 데이터분석 데브코스 1기 - 61일차 (0) | 2024.02.19 |
| 프로그래머스 데이터분석 데브코스 1기 - 60일차 (2) | 2024.02.16 |
| 프로그래머스 데이터분석 데브코스 1기 - 59일차 (1) | 2024.02.15 |