'제로베이스'에서 자연어처리(Natural Language Processing, NLP) 부분 강의를 듣고 있습니다. 자연어처리는 텍스트 데이터를 분석하여 유용한 정보를 추출하고, 이를 다양한 응용 분야에 활용하는 것을 목표로 합니다.
데이터를 다루는데 조금 익숙해졌다고 생각했지만, 자연어처리는 이전까지 배워왔던 정형 데이터 분석과는 많이 달라서 새롭게 배워 나가는 과정에서 많은 주의를 기울이고 있습니다.
오늘 포스트에서는 자연어 처리의 중요한 주제 중 하나인 '문장의 유사성 판단'에 대해 이야기하려고 합니다. 여기서는 특히 문장 유사성을 판단하기 위한 접근법과 그 중에서도 특히 중요한 코사인 유사도에 대해 정리해보도록 하겠습니다.
문장 유사성이란 두 문장이 얼마나 비슷한 내용이나 의미를 담고 있는지를 측정하는 것입니다. 이는 문서 분류, 검색 엔진 최적화, 추천 시스템 등 다양한 자연어 처리 응용 분야에서 사용됩니다.
자연어 처리에서 문장의 유사성을 측정하기 위해서는 먼저 텍스트 데이터를 컴퓨터가 이해할 수 있는 수치형 데이터로 변환하는 과정, 즉 '벡터화'가 필요합니다. 벡터화는 텍스트를 고정된 길이의 숫자 배열로 변환하는 것을 의미합니다. 이 과정을 통해 컴퓨터는 단어의 빈도나 문맥 등의 정보를 기반으로 문장을 수학적으로 분석할 수 있습니다.
대표적인 벡터화 방법으로는 CountVectorizer
와 TF-IDF
(Term Frequency-Inverse Document Frequency)가 있습니다. CountVectorizer
는 문장에서 각 단어가 등장하는 횟수를 카운트하여 벡터를 생성합니다. 반면, TF-IDF
는 각 단어의 빈도와 그 단어가 드문 정도를 함께 고려하여 벡터를 만듭니다. 이는 많이 사용되는 일반적인 단어보다 특정 문맥에서만 자주 나타나는 단어에 더 큰 가중치를 주는 방식입니다.
이러한 벡터화 과정을 통해 텍스트 데이터는 컴퓨터가 처리할 수 있는 형태로 변환되며, 이를 기반으로 문장 간의 유사성을 계산할 수 있게 됩니다.
문장 유사성을 판단하기 위해 유클리디안 거리를 사용할 수 있습니다. 유클리디안 거리는 두 점(또는 두 벡터) 사이의 가장 짧은 직선 거리를 측정하는 방법으로, 간단하게 말해 두 문장이 얼마나 '멀리' 떨어져 있는지를 계산합니다.
예를 들어, 벡터화 과정을 통해 변환된 두 문장의 벡터가 있을 때, 이 두 벡터 사이의 거리를 계산하여 문장의 유사성을 평가할 수 있습니다.
문장 유사성 측정을 위해, 네이버 API을 이용해서 '지식인'에서 '롤'로 100개의 질문을 수집했습니다.
['스네어 롤 연습하기 좋은 클래식 스네어드럼 추천좀 해주세요 버즈롤(프레스롤) 기준으로 스네어 와이어의 상태에 따라서 롤이 잘되기도 하고 어렵기도 하고 그래요... 어떤 스네어든...스네어 와이어를 살짝 풀어 놓으면 대충 쳐도 좋은... ',
'... 롤 하고 싶은 고3 입니다.노트북으로 할려고 하는데 접속이 안됩니다. 1.추천 vpn이 있나요? 2.vpn으로 롤 하면 롤... 차단당하거나 그러진 않아서 지금도 문제없이 잘 하고 있는데 롤은 vpn 걸리면 정지먹는거로 알고 있긴 해서..... ',
'롤 현재 패스 시즌 몇인가요? 롤은 시즌단위로 패스를 내지 않고 1년에 6~7차례 정도 랜덤하게 패스를 냅니다. 현재... 롤토체스를 물어보시는 것이라면 롤토체스는 시즌별로 패스를 운영하므로 9시즌 패스입니다. ',
'노트북으로 롤을 하려고 하는데 돈이 없습니다. 롤을 하면서 렉이 잘 걸리지 않는 노트북 중 가장 저렴한 노트북... ^^ ️ 롤 노트북 질문 주셨는데요. 롤 노트북 추천드리자면 그나마 HP 노트북 15 모델이 딱 그정도 될 것 같아요... ',
'3060TI 5600X, 16gb를 사용하고 있고 롤 시작하면 모니터 최대... 모니터에 롤을 켜놓는데 뭐 노래를 바꾸려고 보조모니터쪽을 움직였다 다시 롤로 오면 프레임이 120프레임... 롤 화면설정은 전체화면을 사용하고 있습니다. 롤 테두리... ',...
각 문장을 벡터로 변환하고 유클리디안 거리를 이용해 유사성을 측정하도록 하겠습니다.
먼저, CountVectorizer와 Okt 형태소 분석기를 사용하여 텍스트 데이터를 벡터로 변환합니다. 각 문장을 형태소로 분석(morphs)한 후, 이를 다시 공백으로 구분된 문자열로 결합하여 벡터화를 위한 준비를 합니다.
from sklearn.feature_extraction.text import CountVectorizer
from konlpy.tag import Okt
t = Okt()
vectorizer = CountVectorizer(min_df=1)
contents_tokens = [t.morphs(doc) for doc in contents]
contents_for_vectorize = []
for content in contents_tokens:
sentence = ''
for word in content:
sentence = sentence + ' ' + word
contents_for_vectorize.append(sentence)
X = vectorizer.fit_transform(contents_for_vectorize)
테스트하고자 하는 새로운 문장도 동일한 방법으로 벡터화합니다. 이렇게 하면 새로운 문장을 기존 데이터 세트와 동일한 형식으로 변환할 수 있습니다.
new_post = ['롤 티어 올리는 법 알려주세요']
new_post_token = [t.morphs(raw) for raw in new_post]
new_post_token_for_vectorize = []
for content in new_post_token:
sentence = ''
for word in content:
sentence = sentence + ' ' + word
new_post_token_for_vectorize.append(sentence)
new_post_vec = vectorizer.transform(new_post_token_for_vectorize)
new_post_vec.toarray()
각 문장 간의 유클리디안 거리를 계산하여 유사성을 측정합니다. 유클리디안 거리는 두 벡터 간의 직선 거리를 측정하는 방법입니다.
import scipy as sp
def dist_raw(v1, v2):
delta = v1 - v2
return sp.linalg.norm(delta.toarray())
dist = [dist_raw(new_post_vec, each) for each in X]
계산된 거리 중 가장 짧은 거리를 가진 문장을 찾아, 가장 유사한 문장으로 판단합니다.
print('Best post is {}'.format(dist.index(min(dist))), 'with distance {}'.format(min(dist)))
print('Test post is -->{}'.format(new_post))
print('Best dist post is --> {}'.format(contents[dist.index(min(dist))]))
>Output
Best post is 84 with distance 5.0
Test post is -->['롤 티어 올리는 법 알려주세요']
Best dist post is --> ... 지금 롤 하고있는데 이렇게 왔어요ㅠ 롤할때 연락 잘 볼수있져?? 저번에 피파하고있을때도 봤는데.. 잘모르겠네요 제가 롤 자주하는데 롤할때는 폰 안봅니다. 왜냐면 자기 점수 걸고 게임하는거라 무조건 이겨야되요....
테스트 문장은 "롤 티어 올리는 법"에 대한 질문이었습니다. 그러나 유클리디안 거리를 사용하여 유사성을 측정한 결과, 가장 유사한 문장으로 선택된 것은 "롤 할 때 연락이 잘 되느냐"에 대한 질문이었습니다. 이는 테스트 문장과는 상당히 다른 주제를 다루고 있어, 유사한 질문이라고 판단하기 어려웠습니다.
이러한 결과를 통해 문장 유사성 측정에 유클리디안 거리만을 사용하는 데는 한계가 있음을 알게 되었습니다. 유클리디안 거리 방식은 문장의 길이나 단어의 빈도수 차이에 매우 민감하게 반응합니다. 실제 문장의 의미나 내용보다는 형식적인 차이에 더 큰 영향을 받는다는 단점이 있습니다.
이 경험을 바탕으로 더 나은 방법을 모색하던 중, 코사인 유사도(Cosine Similarity)라는 개념을 알게 되었습니다. 이 방법은 문장의 길이나 단어 빈도수의 영향을 덜 받으면서 문장 간의 의미적 유사성을 보다 정확하게 평가할 수 있는 장점을 가지고 있습니다.
유클리디안 거리의 한계를 깨닫고 더 나은 대안을 찾던 중, 코사인 유사도(Cosine Similarity)에 대해 알게 되었습니다. 코사인 유사도는 두 벡터 간의 코사인 각도를 사용하여 유사성을 측정하는 방법으로, 문장의 길이나 단어 빈도수의 영향을 덜 받습니다. 이는 문장의 구조적 차이보다는 내용의 유사성에 더 집중할 수 있게 해줍니다.
코사인 유사도는 벡터의 방향성에 초점을 맞추며, 벡터의 크기(문장의 길이)는 고려하지 않습니다. 이 방법은 텍스트 데이터에서 문장이나 문서의 의미적 유사성을 파악하는 데 특히 유용합니다. 같은 단어를 사용하더라도 서로 다른 문맥에서 사용될 때, 코사인 유사도는 이러한 차이를 잘 포착할 수 있습니다.
코사인 유사도의 계산은 다음과 같이 수행됩니다:
from sklearn.metrics.pairwise import cosine_similarity
# 벡터화된 문장들 간의 코사인 유사도 계산
cosine_similarities = cosine_similarity(new_post_vec, X)
# 가장 유사한 문장 찾기
most_similar_doc_index = cosine_similarities.argmax()
most_similar_doc = contents[most_similar_doc_index]
print("가장 유사한 문장:", most_similar_doc)
가장 유사한 문장: 롤 티어 올리고싶은데 대리를 맡기고 싶진 않고 그냥 티어... 혹시 롤 정지사유인가요? 정지사유면 안할려거용 안녕하십니까! 답변드려요. 롤 대리기사와 듀오를 하고 싶다는... 혹시 롤에서 정지 사유가 있을까요? 만약 정지 사유가 있다면...
이 코드는 새로운 문장과 기존 문장들 사이의 코사인 유사도를 계산하여 가장 유사한 문장을 찾아냅니다.
가장 유사하다고 찾은 문장은 테스트 문장인 "롤 티어 올리는 법"과 비슷하게 롤 티어를 올리는 방법과 관련한 이야기를 하고 있습니다. 코사인 유사도를 사용하는 방식은 유클리디안 거리 방식에 비해 훨씬 더 의미론적으로 유사한 문장을 찾아내는 데 효과적이었습니다.
유클리디안 거리와 코사인 유사도는 모두 문장 유사성을 측정하는 데 사용되지만, 그 접근 방식에는 차이가 있습니다.
유클리디안 거리는 두 벡터 간의 '직선 거리'를 측정합니다. 이 방법은 각 단어의 출현 빈도를 기반으로 하며, 결과적으로 두 문장이 공간상에서 얼마나 떨어져 있는지를 나타냅니다. 그러나 이 방식은 문장의 길이나 단어 빈도수에 크게 영향을 받으며, 때로는 의미론적 유사성을 정확하게 포착하지 못하는 단점이 있습니다.
반면에, 코사인 유사도는 두 벡터 간의 각도를 측정하여 유사성을 평가합니다. 이 방식은 문장의 길이나 단어 빈도수와는 독립적으로 작동합니다. 즉, 두 문장이 비슷한 방향을 향하고 있는지, 즉 비슷한 주제나 내용을 담고 있는지를 파악하는 데 초점을 맞춥니다. 이는 문장의 의미적 내용에 더 집중하여, 실제 문장의 내용이 유사할 경우 더 높은 점수를 부여합니다.
예를 들어, "롤 게임에서 승리하는 방법"과 "롤 티어 올리는 전략"이라는 두 문장은 서로 다른 단어를 사용할 수 있지만, 코사인 유사도는 이들이 비슷한 주제를 다루고 있다는 것을 감지합니다. 반면, 유클리디안 거리는 단어의 사용 빈도에 더 크게 영향을 받기 때문에, 이 두 문장이 서로 멀게 측정될 수 있습니다.
따라서, 텍스트 분석에서 의미적 유사성을 보다 정확하게 파악하기 위해서는 코사인 유사도가 더 적합한 선택이 될 수 있습니다. 이 방법은 텍스트의 구조적인 차이에 영향을 받지 않고, 실제 문장의 의미와 내용을 중심으로 유사성을 판단합니다.
이 포스트를 통해 문장 유사성을 판단하는 두 가지 주요 방법, 유클리디안 거리와 코사인 유사도에 대해 정리해 보았습니다. 유클리디안 거리는 간단한 접근법이지만, 문장의 길이나 단어 빈도수의 영향을 많이 받는다는 한계를 가지고 있습니다. 반면, 코사인 유사도는 이러한 요소들의 영향을 줄이면서 문장 간의 의미적 유사성을 보다 정확하게 측정할 수 있는 방법이란 것을 알 수 있었습니다.