본문 바로가기
FullStack/50. ML

[BOOK] 처음 배우는 딥러닝 챗봇 #3 텍스트 유사도

by nakanara 2023. 1. 26.
반응형

텍스트 유사도

자연어 처리에서 문장 간의 의미가 얼마나 유사한지 계산은 매우 중요하다. 사람은 두 개의 문장에 동일한 단어나 의미상 비슷한 단어의 분포를 직감적으로 파악하지만, 컴퓨터의 경우에는 공식을 통해 두 문장 간의 유사도를 계산할 수 있음

n-gram 유사도

n-gram은 주어진 문장에서 n개의 연속적인 단어 시퀀스(단어 나열)를 기준으로 의미 파악, n-gram은 문장에서 n개의 단어를 토큰으로 사용, 이웃한 단어의 출현 횟수를 통계적으로 표현해 텍스트의 유사도를 계산

n-gram

n-gram은 문장을 토큰으로 분리한 후, 단어 문서 행렬(Term - Docuemnt Matrix TDM)을 생성하여 두 문장을 서로 비교해 동일한 단어의 출현 빈도를 확률로 계산해 유사도를 구할 수 있다.

- tf(term frequency): 두 문장 A와 B에서 동일한 토큰의 출현 빈도
- tokens는 문장에서 전체 토큰 수(n-gram으로 분리된 단어)를 의미

결과가 1.0에 가까울수록 B는 A와 유사하다고 볼 수 있다.

from konlpy.tag import Komoran

# 어절 단위 n-gram - 투플형태로 잘라서 저장
def word_ngram(bow, num_gram):
    text = tuple(bow)
    ngrams = [text[x:x + num_gram] for x in range(0, len(text))]
    return tuple(ngrams)

# 유사도 계산
def similarity(doc1, doc2):
    cnt = 0
    for token in doc1:
        if token in doc2:
            cnt = cnt + 1
    return cnt/len(doc1)

# 문장 정의
sentence1 = '6월에 뉴턴은 선생님의 제안으로 트리니티에 입학했다.'
sentence2 = '6월에 뉴턴은 선생님의 제안으로 대학교에 입학했다.'
sentence3 = '나는 맛있는 밥을 뉴턴 선생님과 먹었다.'

# 형태소 분석기에서 명사(단어) 추출 - 리스트 형태로 추출
komoran = Komoran()
bow1 = komoran.nouns(sentence1)
bow2 = komoran.nouns(sentence2)
bow3 = komoran.nouns(sentence3)

# 단어 n-gram 토큰 추출
doc1 = word_ngram(bow1, 2)
doc2 = word_ngram(bow2, 2)
doc3 = word_ngram(bow3, 2)

# 추출된 n-gram 토큰 출력
print(doc1)
print(doc2)

# 유사도 계산
r1 = similarity(doc1, doc2) # 유사도 0.66
r2 = similarity(doc3, doc1) # 유사도 0

# 계산된 유사도 출력
print(r1)
print(r2)

n-gram은 문장에 존재하는 모든 단어의 출현 빈도를 확인하는 것이 아닌 연속되는 문장에서 일부 단어(n으로 설정된 개수)만 확인하다 보니 전체 문장을 고려한 언어 모델보다 정확도가 떨어질 수 있다.
n-gram의 n 값에 따라 n을 크게 잡을수록 비교 문장의 토큰과 비교할 때 카운트를 놓칠 확률이 증가
n을 적게 잡으면 카운트 확률을 높아지지만 문맥을 파악하는 정확도는 떨어진다.
n-gram 모델에서 n은 매우 중요 보통 1~5 사이 사용

코사인 유사도(cosine similarity)

단어나 문장을 벡터로 표현할 수 있다면 벡터 간 거리나 각도를 이용하여 유사성을 파악할 수 있다. 벡터 간 거리를 구하는 방법은 다양하며 그중 코사인 유사도가 하나의 방식

코사인 유사도는 두 벡터 간 코사인 각도를 이용해 유사도를 측정하는 방법, 일반적으로 코사인 유사도는 벡터의 크기(출현 빈도)가 중요하지 않을 때 그 거리를 측정하기 위해 사용하며, 벡터의 크기에 상관없이 결과가 안정적

  • 벡터의 크기는 단어의 출현 빈도를 통해 유사도를 계산한다면 동일한 단어가 많이 포함될수록 벡터의 크기가 증가

코사인은 -1 ~ 1의 값을 가지며, 두 벡터의 방향이 완전 동일하다면 1, 반대의 경우 -1, 두 벡터가 직각을 이루면 0의 값을 가짐. 즉 두 벡터의 방향 즉 1에 가까울수록 유사

Cosine Similarity

from konlpy.tag import Komoran  
import numpy as np  
from numpy import dot  
from numpy.linalg import norm

# 코사인 유사도 계산
# dot 인자로 들어온 2개의 넘파이 배열의 내적곱
# norm 벡터 계산 - L2 노름(유클리드 노름) 
def cos_sim(vec1, vec2):  
    return dot(vec1, vec2) / (norm(vec1) * norm(vec2))

# TDM 만들기
def make_term_doc_mat(sentence_bow, word_dics):  
    freq\_mat = {}

    for word in word_dics:
        freq_mat[word] = 0

    for word in word_dics: 
        if word in sentence_bow:
            freq_mat[word] += 1

    return freq_mat

# 단어 벡터 만들기

def make_vector(tdm):  
    vec = []  
    for key in tdm:  
        vec.append(tdm[key])  
    return vec

# 문장 정의

sentence1 = '6월에 뉴턴은 선생님의 제안으로 트리니티에 입학했다.'  
sentence2 = '6월에 뉴턴은 선생님의 제안으로 대학교에 입학했다.'  
sentence3 = '나는 맛있는 밥을 뉴턴 선생님과 함께 먹었다.'

# 형태소 분석기에서 명사(단어) 추출 - 리스트 형태로 추출

komoran = Komoran()  
bow1 = komoran.nouns(sentence1)  
bow2 = komoran.nouns(sentence2)  
bow3 = komoran.nouns(sentence3)

# 단어 묶음 리스트를 하나로 합침

bow = bow1 + bow2 + bow3

# 단어 묶음에서 중복을 제거하여 단어 사전 구축

word_dics = []  
for token in bow:  
    if token not in word_dics:  
        word_dics.append(token)

# 문장별 단어 문서 행렬 계산
freq_list1 = make_term_doc_mat(bow1, word_dics)
freq_list2 = make_term_doc_mat(bow2, word_dics)
freq_list3 = make_term_doc_mat(bow3, word_dics)

print(freq_list1)
print(freq_list2)
print(freq_list3)


# 문장 벡터 생성
doc1 = np.array(make_vector(freq_list1))
doc2 = np.array(make_vector(freq_list2))
doc3 = np.array(make_vector(freq_list3))

# 코사인 유사도 계산
r1 = cos_sim(doc1, doc2)
r2 = cos_sim(doc3, doc1)

print(r1)
print(r2)

유클리드 노름(norm)

참고

반응형