[python] Text 유사도를 측정하는 여러 방법

haremeat·2022년 7월 19일
1

Python

목록 보기
3/5
post-thumbnail

최근 회사에서 text 유사도를 측정해야하는 일이 있었다.
이것저것 시도하다가 결국 적용하게 된 방법까지 모두 적어보겠다.

정확한 비교를 위해 공통 테스트로 쓸 문장은

"그건 아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어",
"아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어"

1. 코사인 유사도(Cosine Similarity)

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

sentences = ("그건 아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어", 
"아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어")

tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(sentences)
cos_similar = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])
print("코사인 유사도 측정")
print(cos_similar[0][0])

위 예시 문장을 기준으로 나오는 결과값은

0.8673636849609287

텍스트를 TF-IDF를 통해 벡터화한 후 유사도를 두 벡터 사이의 각도의 코사인으로 계산하는 방식. 두 벡터가 같은 방향을 가리키는지에 따라 유사도를 측정한다.
처음엔 유사도 출력에 이 방식을 사용했지만 금방 관뒀다.
이유는

"아 짜증나"
"아 자증"

위 두 문장의 유사도를 0.0이라고 내놓았기 때문이다.
위 문장에서 유사도가 0이 나오는 이유는 코사인 유사도는 형태소 분석을 못하기 때문이다.

띄어쓰기를 기준으로 테스트하기 때문에 실제로 겹친다고 판단한 건 “아” 하나였고 -1 ~ 1 사이의 중간값인 0을 내놓은 것이다.

코사인 유사도는 형태소 분석과 함께 쓰면 쓸만하겠지만… 당장 더 간단한 방법이 필요했다.
그래서 다음에 쓴 방법이 자카드 유사도.

2. 자카드 유사도(Jaccard Similarity)

answer_string = "그건 아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어"
input_string = "아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어"

intersection_cardinality = len(set.intersection(*[set(answer_string), set(input_string)]))
union_cardinality = len(set.union(*[set(answer_string), set(input_string)]))
similar = intersection_cardinality / float(union_cardinality)

print(similar)

위 예시 문장을 기준으로 나오는 결과값은

0.9

각 텍스트 데이터 개체를 집합으로 만든 뒤 두 집합의 교집합 크기를 합집합의 크기로 나눈 값으로 유사도를 구하는 방식.
자카드는 위에서 문제가 된 "아 짜증나", "아 자증" 두 개의 문장에서 문제없이 유사도를 잘 측정해냈다. 이때 나온 결과값은 0.5 "아"와 "증"이 겹치므로 아주 정확한 값이라 볼 수 있다.
하지만 자카드에도 단점은 있었다...

"성현이 너 지은이랑 데이트한거 아니였어? 싸웠어?"
"싸웠어? 아니였어? 데이트한거 지은이랑 너 성현이"

이런 식으로 문장의 순서만 뒤집어놓아도 자카드 유사도 측정으로는 100% 일치하는 문장이라고 나왔다.
생각해보면 공통된 단어의 개수를 전체 단어의 수로 나누어서 유사도를 측정하는 방식이니 이러한 결과는 예상된 일이었다.

결국 마지막으로 선택한 방식은...

3. SequenceMatcher 이용

import difflib

answer_string = "그건 아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어"
input_string = "아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어"

answer_bytes = bytes(answer_string, 'utf-8')
input_bytes = bytes(input_string, 'utf-8')
answer_bytes_list = list(answer_bytes)
input_bytes_list = list(input_bytes)

sm = difflib.SequenceMatcher(None, answer_bytes_list, input_bytes_list)
similar = sm.ratio()

print(similar)

위 예시 문장을 기준으로 나오는 결과값은

0.9457364341085271

여기서 나는 stringbyte화 한 후 리스트로 만들어
SequenceMatcher로 두 리스트간 유사도를 측정하는 방식을 사용했다.
일반적으로 쓰이는 방식은 아니다. 하지만 효과는 확실했다.
보통은 유클리드 방식을 쓰는 등 좀 더 일반적인 텍스트 유사도 구하는 방식이 있지만... 아무튼 효과는 확실했다.

위 방법들에서 문제가 된 짧은 문장들이나 순서가 뒤바뀐 문장들에서도 오류는 나지 않았다.
게다가 나는 확실한 정답 문장이 있는 상황에서 들어온 문장이 정답과 얼마나 일치하는지 알아야 하는 상황이었기 때문에 이 방식이 더욱 잘 맞았다.

그렇게 최종적으로는 byte방식을 채택하게 되었다.

오늘도 좋은 삽질이었다..

Ref

Ultimate Guide To Text Similarity With Python - NewsCatcher

[ NLP ] 텍스트 유사도 ( 자카드 유사도, 코사인 유사도 )

점프 투 파이썬

profile
버그와 함께하는 삶

0개의 댓글