2025년, 코딩은 선택이 아닌 필수!

2025년 모든 학교에서 코딩이 시작 됩니다. 먼저 준비하는 사람만이 기술을 선도해 갑니다~

머신러닝/12. 딥러닝챗봇

[딥러닝 챗봇] 텍스트 유사도

파아란기쁨1 2022. 6. 20. 10:20
반응형

1. 개요

 

자연어 처리에서 문장 간의 의미가 얼마나 유사한지 계산하는 일은 매우 중요하다. 우리는 두개의 문장에 동일한 단어나 의비상 비슷한 단어들이 얼마나 분포되어 있는지 직감적으로 판단하게 된다.

컴퓨터도 동일한 방법으로 두 문장의 유사도를 계산 할 수 있다.

이 때 문장은 단어들의 묶음이기 때문에 하나의 벡터로 묶어서 문장간의 유사도를 계산 할 수 있다.

 

챗봇 개발에는 여러가지 방법론이 있는데 여기서는 특정분야에 적용되는 FAQ에 응대하는 Q&A챗봇 개발을 살펴 본다.

 

2. n-gram 유사도

n-gram은 주어진 문장에서 n개의 연속적인 단어 시퀀스를 의미하며 이웃한 단어의 출현 횟수를 통계적으로 표현해서 텍스트 유사도를 계산하는 방법이다.

 

예)  An adorable little boy is spreading smiles 문장을 n-gram으로 전부 구해 보면 다음과 같다.

n=1) unigrams : an, adorable, little, boy, is, spreading, smiles

n=2) bigrams : an adorable, adorable little, little boy, boy is, is spreading, spreading smiles

n=3) trigrams : an adorable little, adorable little boy, little boy is, boy is spreading, is spreading smiles

n=4) 4-grams : an adorable little boy, adorable little boy is, little boy is spreading, boy is spreading smiles

 

n-gram을 이용해 문장 간의 유사도를 계산해 봅시다. 해당 문장을 n-gram으로 토큰을 분리한 후 단어 문서행렬(TDM)을 만든다.이후 두 문장을 서로 비교해 동일한 단어의 출현 빈도를 확률로 계산해 유사도를 구할 수 있다.유사도=(A 문장에서 B의 출현빈도)/(A의 토큰 수)=tf(A,B)/tokens(A)

 

위의 문장과  "An adorable little boy is spreading" 의 유사도를 구하는 경우를 살펴 보자.

  an adorable adorable little little boy boy is is spreading spreading smiles  
A 1 1 1 1 1 1 6(tokens(A))
B 1 1 1 1 1 0 5(tf(A,B)

n-gram 유사도 계산- 실습 전 konlp 설치

%%bash
apt-get update
apt-get install g++ openjdk-8-jdk python-dev python3-dev
pip3 install JPype1
pip3 install konlpy
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) #2-gram 방식으로 추출
doc2 = word_ngram(bow2,2)
doc3 = word_ngram(bow3,2)

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

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

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

결과

1번문장과 2번문장의 유사도는 0.57 

3번문장과 1번문장의 유사도는 0 이 나오는 것을 확인 할 수 있다.

 

n-gram은 문장에 존재하는 모든 단어의 출현 빈도를 확인하는 것이 아니라 연속되는 문장에서 일부단어만 확인하다 보니 전체문장을 고려한 언어 모델 보다 정확도가 떨어질 수 있다.

 

3. 코사인 유사도

단어나 문장을 벡터로 표현할 수 있다면 벡터간 거리나 각도를 이용해서 유사성을 판단 할 수 있다.

코사인유사도는 두 벡터 간 코사인 각도를 이용해 유사도를 측정하는 방법이다.

코사인 유사도는 벡터의 크기가 중요하지 않을때 그 거리를 측정하기 위해 사용한다.

예를 들어 단어의 출현빈도를 통해 유사도 계산을 한다면 동일한 단어가 많이 포함되어 있을 수록 벡터의 크기가 커진다.

코사인 유사도는 벡터의 크기와 상관없이 결과가 안정적이다.

코사인 유사도를 이용해 아래 두 문장의 유사도를 계산하는 과정을 살펴 보자.

A: '6월에 뉴턴은 선생님의 제안으로 트리티니에 입학했다.'

B: '6월에 뉴턴은 선생님의 제안으로 대학교에 입학했다.'

 

  6월 뉴턴 선생님 제안 트리티니 대학 입학
A 1 1 1 1 1 0 1
B 1 1 1 1 0 1 1

위의 수식은 다음과 같이 계산 된다.

A=[1,1,1,1,1,0,1]

B=[1,1,1,1,0,1,1]

코사인 유사도의 분자를 계산 하기 위해 내적을 연산 한다.

AºB=(1*1)+(1*1)+(1*1)+(1*1)+(1*0)+(0*1)+(1*1) = 5

코사인 유사도의 분모는 두 벡터의 크기의 곲이다.

||A||||B|| = √(12+12+12+12+12+02+12) * √(12+12+12+12+02+12+12)=6

 

따라서 5/6 = 0.83333 으로 나온다.

 

다음은 3개 문장의 코사인 유사도를 계산하는 예제를 살펴 보자.

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

#코사인 유사도 계산
def cos_sim(vec1,vec2):
  return dot(vec1,vec2)/(norm(vec1)*norm(vec2))

#TDM 만들기
def make_term_doc_mat(senetence_bow,word_dics):
  freq_mat={}
  for word in word_dics:
    freq_mat[word]=0 #단어의 개수를 세기 위해 먼저 0으로 초기화
  
  for word in word_dics:
    if word in senetence_bow:
      freq_mat[word]+=1 #문장에 해당 단어가 포함 되어 있으면 1을 더해줌
  return freq_mat

#단어 벡터 만들기
def make_vector(tdm):
  vec=[]
  for key in tdm:
    vec.append(tdm[key]) #tdm 의 값을 벡터로 만든다.
  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)

코사인 유사도를 계산하는 return dot(vec1,vec2)/(norm(vec1)*norm(vec2)) 에서 dot는 넘파이에서 제공하는 내적을 계산하는 함수이며 norm 은 벡터의 크기를 계산하는 함수이다.

여기서 코사인 유사도에서는 L2노름(유클리드노름)을 주로 사용한다.

 

결과를 살펴 보면 doc1과 doc2는 77프로, doc3과 doc1은 18프로 인것을 확인 할 수 있다.

 

 

 

 

 

참고]

한빛미디어 - 처음배우는 딥러닝 챗봇

 

 

반응형