3. 텍스트유사도
- 챗봇엔진에서 입력된 문장에서 챗봇시스템에서 답변이 얼마나 유사한지에 따라 적절한 답변이 가능하다.
3.1. n-gramp 유사도
- n-gram은 문장에서 n개의 연속적인 단어의 시퀀스를 의미한다.
- n-gram은 문장에서 n개의 단어를 token으로 사용한다.
- 이웃한 단어의 출현횟수를 통계적으로 표현해서 텍스트의 유사도를 계산하는 방법이다.
- 참고 : https://uumini.tistory.com/69
문장간 유사도 계산
- 문장을 n-gram으로 토큰을 분리한 후 단어문서행렬(TDM, Term Document Matrix)를 만든다.
- 이후, 두 문장을 비교해서 동일단어의 출현빈도를 확률로 계산해 유사도를 구할 수 있다.
- A, B 두 문장이 있을 때 B가 A와 얼마나 유사한지 확률을 구하는 공식n-gram유사도
- tf(term frequent)는 두 문장 A와 B에서 동일한 토큰의 출현빈도를 의미
- tokens는 해당 문장에서 전체 토큰수를 의미
- 여기서 토큰이란 n-gram으로 분리된 단어 즉, 기준문장 A의 전체 토큰중에서 A와 B이 동일톤큰의 확률표현식
- 1.0에 가까울 수록 B가 A에 유사하다고 볼 수가 있다.
-
𝑠𝑖𝑚𝑢𝑙𝑎𝑟𝑖𝑡𝑦=𝑡𝑓(𝐴,𝐵)𝑡𝑜𝑘𝑒𝑛𝑠(𝐴)
2-gram을 이용한 예제
- A : 6월에 뉴턴은 선생님의 제안으로 트리니티에 입학했다.
- B : 6월에 뉴턴은 선생님의 제안으로 대학교에 입학했다.(6월, 뉴턴)(뉴턴,선생님)(선생님, 제안)(제안, 트리니티)(트리니티, 입학)(입학)
A 1 1 1 1 1 1 6(tokens(A) B 1 1 1 0 0 1 4(tf(A, B)
- 4/6 = 0.66 즉, 2개의 문장 B와 A는 66%의 유사도가 있다.
a = '6월에 뉴턴은 선생님의 제안으로 트리니티에 입학했다.'
b = '6월에 뉴턴은 선생님의 제안으로 대학교에 입학했다.'
c = '나는 맛있는 밥을 뉴턴 선생님과 함께 먹었습니다.'
from konlpy.tag import Komoran
# 1. 어절단위 n-gram
def word_ngram(bow, n_gram):
text = tuple(bow)
ngrams = [text[x:x+n_gram] for x in range(0, len(text))]
return tuple(ngrams)
# 2. 음절 n-gram
def phoneme_ngram(bow, n_gram):
sentence = ' '.join(bow)
text = tuple(sentence)
ngrams = [text[x:x+n_gram] for x in range(0, len(text))]
return tuple(ngrams)
# 3. 유사도계산
def similarity(stn1, stn2):
cnt = 0
for token in stn1:
if token in stn2:
cnt = cnt + 1
return cnt/len(stn1)
위의 코드는 한국어 텍스트에서 어절 단위와 음절 단위의 n-gram을 생성하고, 생성된 n-gram을 사용하여 두 문장 간의 유사도를 계산하는 함수들을 정의한다.
from konlpy.tag import Komoran: Konlpy 라이브러리에서 Komoran 클래스를 가져온다. 이를 통해 한국어 텍스트를 형태소 분석할 수 있다.
def word_ngram(bow, n_gram): 어절 단위 n-gram을 생성하는 함수를 정의한다. 매개변수로는 Bag of Words(BOW)와 n-gram의 크기(n_gram)를 받는다.
text = tuple(bow): BOW를 튜플 형태로 변환한다.
ngrams = [text[x:x+n_gram] for x in range(0, len(text))]: BOW에서 n-gram을 생성한다.
return tuple(ngrams): 생성된 n-gram들을 튜플 형태로 반환한다.
def phoneme_ngram(bow, n_gram): 음절 단위 n-gram을 생성하는 함수를 정의한다. 매개변수와 동작은 word_ngram 함수와 동일하다.
def similarity(stn1, stn2): 두 문장 간의 유사도를 계산하는 함수를 정의한다. 매개변수로 두 개의 문장(stn1, stn2)을 받는다.
cnt = 0: 초기 카운터를 0으로 설정
for token in stn1: 첫 번째 문장을 순회한다.
if token in stn2: 현재 토큰이 두 번째 문장에 있는지 확인한다.
cnt = cnt + 1: 현재 토큰이 두 번째 문장에 존재한다면 카운터를 1 증가시킨다.
return cnt/len(stn1): 첫 번째 문장의 길이로 나누어 유사도를 계산하고 반환한다.
komoran = Komoran()
bow1 = komoran.nouns(a)
bow2 = komoran.nouns(b)
bow3 = komoran.nouns(c)
print(bow1)
print(bow2)
print(bow3)
['6월', '뉴턴', '선생님', '제안', '트리니티', '입학']
['6월', '뉴턴', '선생님', '제안', '대학교', '입학']
['밥', '뉴턴', '선생', '님과 함께']
한국어 텍스트에서 명사만을 추출하여 BOW(Bag of Words)를 생성하는 과정을 보여준다.
stn1 = word_ngram(bow1, 2)
stn2 = word_ngram(bow2, 2)
stn3 = word_ngram(bow3, 2)
print(stn1)
print(stn2)
print(stn3)
(('6월', '뉴턴'), ('뉴턴', '선생님'), ('선생님', '제안'), ('제안', '트리니티'), ('트리니티', '입학'), ('입학',))
(('6월', '뉴턴'), ('뉴턴', '선생님'), ('선생님', '제안'), ('제안', '대학교'), ('대학교', '입학'), ('입학',))
(('밥', '뉴턴'), ('뉴턴', '선생'), ('선생', '님과 함께'), ('님과 함께',))
각각의 BOW를 이용하여 어절 단위의 2-gram을 생성하는 과정을 보여준다.
stn1 = word_ngram(bow1, 2): 첫 번째 BOW(bow1)를 이용하여 어절 단위의 2-gram( 두 개의 연속된 토큰 ex: "나는", "는학교에", "학교에간다" )을 생성한다. word_ngram 함수를 호출하고, 첫 번째 인자로 BOW를, 두 번째 인자로 n-gram의 크기를 전달한다.
r1 = similarity(stn1, stn2)
r2 = similarity(stn1, stn3)
print(f'a와 b문장의 유사도 = {r1:.3f}')
print(f'a와 c문장의 유사도 = {r2:.3f}')
# 결과분석
# n-gram은 문장에서 나타나는 단어의 빈도수를 계산하는 것이 아니라 연속된 문장에서 n개의
# 단어의 유사도를 계산한다.
# n-grams의 모델에서 n개의 값의 설정은 매우 중요하다. 보통 2~5사이의 값을 사용한다.
a와 b문장의 유사도 = 0.667
a와 c문장의 유사도 = 0.000
두 개의 문장 간의 유사도를 측정하는 데에 앞서 생성된 2-gram을 사용하는 예시이다.
r1 = similarity(stn1, stn2): similarity 함수를 호출하여 첫 번째 문장(stn1)과 두 번째 문장(stn2) 간의 유사도를 계산한다.
# tri-grams 적용
stn1 = word_ngram(bow1, 3)
stn2 = word_ngram(bow2, 3)
stn3 = word_ngram(bow3, 3)
print(stn1)
print(stn2)
print(stn3)
r1 = similarity(stn1, stn2)
r2 = similarity(stn1, stn3)
print(f'a와 b문장의 유사도 = {r1:.3f}')
print(f'a와 c문장의 유사도 = {r2:.3f}')
(('6월', '뉴턴', '선생님'), ('뉴턴', '선생님', '제안'), ('선생님', '제안', '트리니티'), ('제안', '트리니티', '입학'), ('트리니티', '입학'), ('입학',))
(('6월', '뉴턴', '선생님'), ('뉴턴', '선생님', '제안'), ('선생님', '제안', '대학교'), ('제안', '대학교', '입학'), ('대학교', '입학'), ('입학',))
(('밥', '뉴턴', '선생'), ('뉴턴', '선생', '님과 함께'), ('선생', '님과 함께'), ('님과 함께',))
a와 b문장의 유사도 = 0.500
a와 c문장의 유사도 = 0.000
3.2 코사인 유사도
- 단어나 문장을 벡터로 표현할 수 있다면 벡터간의 거리나 각도를 이용해서 유사도을 파악할 수 있다.
- 벡터간의 거리를 구하는 방법은 다양하지만 코사인유사오(cosine similarity)를 사용
- 코사인 유사도는 두 벡터간 코사인각도를 이용해서 유사도를 측정하는 방법이다.
- 일반적으로 벡터의 크기가 중요하지 않을 때 그 거리를 측정하기 위해 사용한다.
- 예를 들어 단어의 출현빈도를 통해 유사도를 계산한다면 동일단어가 많이 포함될 수록 벡터크기가 커진다.
- 이때 코사인유사도는 벡터의 크기와 상관없이 결과가 안정적이다.
- 코사인유사도는 다양한 차원에서 적용이 가능해서 실무에 많이 사용한다.코사인유사도𝑠𝑖𝑚𝑖𝑙𝑎𝑟𝑖𝑡𝑦=𝑐𝑜𝑠Θ=𝐴⋅𝐵‖𝐴‖‖𝐵‖=∑𝑖=1𝑛𝐴𝑖⋅𝐵𝑖∑𝑖=1𝑛(𝐴𝑖)2⋅∑𝑖=1𝑛(𝐵𝑖)2
- A : 6월에 뉴턴은 선생님의 제안으로 트리니티에 입학했다.
- B : 6월에 뉴턴은 선생님의 제안으로 대학교에 입학했다.
- 단어문서행렬표현(명사만 추출)문장6월뉴턴선생님제안트리니티입학대학
A 1 1 1 1 1 1 0 B 1 1 1 1 0 1 1 - A = [1,1,1,1,1,1,0]
- B = [1,1,1,1,0,1,1]
- 𝐴⋅𝐵=∑𝑖=1𝑛𝐴𝑖⋅𝐵𝑖 =(1𝑥1)+(1𝑥1)+(1𝑥1)+(1𝑥1)+(1𝑥0)+(1𝑥1)+(0𝑥1) =1+1+1+1+0+1+0 =5
- ‖𝐴‖‖𝐵‖=∑𝑖=1𝑛(𝐴𝑖)2⋅∑𝑖=1𝑛(𝐵𝑖)2 =12+12+12+12+12+12+10𝑥12+12+12+12+02+12+12 =6𝑥6 =36 =6
코사인유사도에서는 주로 L2norm(유클리드노름)을 주로 사용
유클리드노름유클리드노름:𝑙2𝑛𝑜𝑟𝑚=∑|𝑥𝑖|2
수학] 코사인 개념 설명 그래프, Cos Cosine 의미 개념도 Graph 차트
삼각함수에서 코사인이란, 아래 그래프에서 빨간 수평선입니다. 빨간 수평선의 길이를 코사인 값이라고 합니다. 각도를 나타내는 파란 직선이 회색 원주와 만나는 지점과, y축을 수평으로 이어
mwultong.blogspot.com
# 코사인유사도계산
from konlpy.tag import Komoran
import numpy as np
from numpy import dot
from numpy.linalg import norm
# 코사인유사도계산함수
def cos_sim(A, B):
return dot(A, B) / (norm(A) * norm(B))
# TDM(Term Document Matrix) : 단어문서행렬
# 비교할 문장에서 추출한 단어를 기준으로 문장에 해당하는 단어들이 얼마나 포함되어
# 있는지를 나타내는 행렬
# 단언문서행렬생성함수
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
# TDM에서 표현된 토큰들의 출현빈도수를 벡터로 변환함수 - 단어벡터생성
def make_vector(tdm):
vec = []
for key in tdm:
vec.append(tdm[key])
return vec
a = '6월에 뉴턴은 선생님의 제안으로 트리니티에 입학했다.'
b = '6월에 뉴턴은 선생님의 제안으로 대학교에 입학했다.'
c = '나는 맛있는 밥을 뉴턴 선생님과 함께 먹었습니다.'
komoran = Komoran()
bow1 = komoran.nouns(a)
bow2 = komoran.nouns(b)
bow3 = komoran.nouns(c)
print(bow1)
print(bow2)
print(bow3)
['6월', '뉴턴', '선생님', '제안', '트리니티', '입학']
['6월', '뉴턴', '선생님', '제안', '대학교', '입학']
['밥', '뉴턴', '선생', '님과 함께']
# 단어묶음 리스트를 한 개의 리스트로 합치기
bow = bow1 + bow2 + bow3
print(len(bow), bow, '\n')
# 중복제거후 새로운 단어리스트를 생성
word_dics = []
for token in bow:
if token not in word_dics:
word_dics.append(token)
print(len(word_dics), word_dics)
print(len(np.unique(word_dics)), np.unique(word_dics))
print()
# 문장별 단어문서행렬 생성
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)
16 ['6월', '뉴턴', '선생님', '제안', '트리니티', '입학', '6월', '뉴턴', '선생님', '제안', '대학교', '입학', '밥', '뉴턴', '선생', '님과 함께']
10 ['6월', '뉴턴', '선생님', '제안', '트리니티', '입학', '대학교', '밥', '선생', '님과 함께']
10 ['6월' '뉴턴' '님과 함께' '대학교' '밥' '선생' '선생님' '입학' '제안' '트리니티']
{'6월': 1, '뉴턴': 1, '선생님': 1, '제안': 1, '트리니티': 1, '입학': 1, '대학교': 0, '밥': 0, '선생': 0, '님과 함께': 0}
{'6월': 1, '뉴턴': 1, '선생님': 1, '제안': 1, '트리니티': 0, '입학': 1, '대학교': 1, '밥': 0, '선생': 0, '님과 함께': 0}
{'6월': 0, '뉴턴': 1, '선생님': 0, '제안': 0, '트리니티': 0, '입학': 0, '대학교': 0, '밥': 1, '선생': 1, '님과 함께': 1}
# 각 문장별로 벡터를 생성해서 넘파이배열로 변환후 전달 -> 행렬연산을 하기 위해 변환
doc1 = np.array(make_vector(freq_list1))
doc2 = np.array(make_vector(freq_list2))
doc3 = np.array(make_vector(freq_list3))
print(doc1)
print(doc2)
print(doc3)
[1 1 1 1 1 1 0 0 0 0]
[1 1 1 1 0 1 1 0 0 0]
[0 1 0 0 0 0 0 1 1 1]
# 코사인유사도 계산
r1 = cos_sim(doc1, doc2)
r2 = cos_sim(doc1, doc3)
print(f'a와 b문장의 유사도 = {r1:.3f}')
print(f'a와 c문장의 유사도 = {r2:.3f}')
# 결과분석
# n-gram(64%)보다 코사인유사도(83%)방식의 정확도가 높다.
a와 b문장의 유사도 = 0.833
a와 c문장의 유사도 = 0.204
'Python' 카테고리의 다른 글
[Python]챗봇_04_딥러닝모델(CNN 모델) (7) | 2024.04.26 |
---|---|
[Python]챗봇_04_딥러닝모델(keras) (1) | 2024.04.26 |
[Python]챗봇_02_임베딩 (0) | 2024.04.25 |
[Python]챗봇_01_토크나이징 (1) | 2024.04.25 |
[Python]데이터베이스_DataBase (0) | 2024.04.24 |