최근 회사에서 text 유사도를 측정해야하는 일이 있었다.
이것저것 시도하다가 결국 적용하게 된 방법까지 모두 적어보겠다.
정확한 비교를 위해 공통 테스트로 쓸 문장은
"그건 아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어",
"아닌데 어떤 이상한 놈 때문에 기분 다 잡쳤어"
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을 내놓은 것이다.
코사인 유사도는 형태소 분석과 함께 쓰면 쓸만하겠지만… 당장 더 간단한 방법이 필요했다.
그래서 다음에 쓴 방법이 자카드 유사도.
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% 일치하는 문장이라고 나왔다.
생각해보면 공통된 단어의 개수를 전체 단어의 수로 나누어서 유사도를 측정하는 방식이니 이러한 결과는 예상된 일이었다.
결국 마지막으로 선택한 방식은...
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
여기서 나는 string
을 byte
화 한 후 리스트로 만들어
SequenceMatcher로 두 리스트간 유사도를 측정하는 방식을 사용했다.
일반적으로 쓰이는 방식은 아니다. 하지만 효과는 확실했다.
보통은 유클리드 방식을 쓰는 등 좀 더 일반적인 텍스트 유사도 구하는 방식이 있지만... 아무튼 효과는 확실했다.
위 방법들에서 문제가 된 짧은 문장들이나 순서가 뒤바뀐 문장들에서도 오류는 나지 않았다.
게다가 나는 확실한 정답 문장이 있는 상황에서 들어온 문장이 정답과 얼마나 일치하는지 알아야 하는 상황이었기 때문에 이 방식이 더욱 잘 맞았다.
그렇게 최종적으로는 byte방식을 채택하게 되었다.
오늘도 좋은 삽질이었다..
Ultimate Guide To Text Similarity With Python - NewsCatcher
와 너무 좋은 방법 같네요,
저는 번역물을 받으면
번역기로 작업한 문서와 사람이 작업한 문서가 얼마나 유사한지
검사해야 하는 일이 중요해졌어요.
그런 툴이 없어서 고민이에요.
요즘 번역기 작품이 나쁘진 않은데 너무 티가 나잖아요,
근데 번역사가 번역기로 돌리고 그냥 납품해서 문제가 터지면
번역기로 돌렸지만 얼마나 돌렸는지 정확히 알려면
번역기 문서와 사람의 문서가 얼마나 유사한지 알아야 하거든요.
이거 해결해주실 수 있나요?