본문 바로가기
정리/텐서플로와 머신러닝으로 시작하는 자연어처리

Chap03. 자연어처리 개요_텍스트 분류 및 유사도

by 스꼬맹이브로 2021. 7. 7.
728x90
반응형
SMALL

오늘은 자연어 처리 문제 중 가장 대표적이고 많이 하는 텍스트 분류와 텍스트끼리 얼마나 비슷한지를 계산하는 유사도에 대하여 알아보도록 하자.

 

[ 텍스트 분류 ]

텍스트 분류(Text Classification)는 자연어 처리 기술을 활용해 특정 텍스트를 사람들이 정한 몇 가지 범주(Class) 중 어느 범주에 속하는지 분류하는 문제다. 

분류해야 할 범주의 수에 따라 문제를 구분하기도 하는데, 분류 개수가 2가지일 때는 이진 분류(Binary classification) 문제라 하며, 3가지 이상일 경우 다중 분류(Multi classification) 문제라 한다.

 

텍스트 분류 문제를 해결하는 방법은 크게 지도학습과 비지도학습으로 나뉘며, 각각의 방법에 대하여 알아보자.

 

▶ 지도학습을 통한 텍스트 분류

지도학습을 통해 텍스트 분류를 하는 전체적인 방법은 다음 그림과 같다.

텍스트 분류 과정

위 그림과 같이 지도학습은 글에 대해 각각 속한 범주에 대한 레이블이 이미 주어져 있다.

따라서 주어진 범주로 글들을 모두 학습한 후에 학습한 결과를 이용해 새로운 글의 범주를 예측하는 방법이다.

 

지도학습의 모델의 종류는 매우 다양하지만 대표적으로는 다음과 같다.

  • 나이브 베이즈 분류(Naive Bayes Classifier)
  • 서포트 벡터 머신(Support Vector Machine)
  • 신경망(Neural Network)
  • 선형 분류(Linear Classifier)
  • 로지스틱 분류(Logistic Classifier)
  • 랜덤 포레스트(Random Forest)

이 밖에도 수많은 모델이 존재하며 주어진 문제에 대한 적합한 모델은 정해져 있지 않으므로 항상 다양한 모델을 시도해보아야 한다.

 

▶ 비지도학습을 통한 텍스트 분류

비지도학습은 지도학습과는 데이터에 대한 레이블이 존재하지 않기 때문에 데이터의 특성을 찾아내서 적당한 범주를 만들어 각 데이터를 나누며 학습한다.

대표적으로 k-평균 군집화를 예로 들 수 있는데, 각 문장에 대하여 벡터화한 뒤 좌표축에 표현하여 군집으로 나눈다.

여기서 사용자가 몇 개의 군집으로 나눌 것인지, 즉 k 값을 설정하면 데이터에 특성에 따라 군집화를 수행하게 된다.

k-평균 군집화

이처럼 비지도학습을 통한 분류는 어떤 특정한 분류가 있는 것이 아니라 데이터의 특성에 따라 비슷한 데이터끼리 묶어주는 개념이다. 때문에 비지도학습을 통한 텍스트 분류는 텍스트 군집화라고도 불린다.

 

비지도학습의 모델의 종류는 다음과 같다.

  • k-평균 군집화(K-means Clustering)
  • 계층적 군집화(Hierarchical Clustering)

 

지도학습과 비지도학습 중 어떤 방법을 사용할지 결정하는 데 기여하는 가장 큰 기준은 데이터에 정답 레이블이 있느냐 없느냐이다. 그리고 일반적인 분류의 경우에는 지도학습을 사용하지만 정확한 범주가 없고 단순히 군집화만 할 경우에는 비지도학습을 사용할 수 있다.

 

[ 텍스트 유사도 ]

텍스트 유사도란 위에서 말했듯이 말 그대로 텍스트가 얼마나 유사한지를 표현하는 방식 중 하나다.

유사도를 계산하는 방법은 단순히 같은 단어의 개수를 사용하는 방법부터 형태소로 나누어 형태소를 비교하는 방법, 자소 단위로 나누어 단어를 비교하는 방법 등 다양한 방법이 존재한다.

그 중에서도 딥러닝을 기반으로 텍스트의 유사도를 측정하는 방식에 대하여 공부해보자.

딥러닝을 기반으로 텍스트의 유사도를 측정하는 방식은 단어, 형태소, 유사도의 종류에 상관없이 텍스트를 벡터화하여 각 문장 간의 유사도를 측정하는 방식이다.

이 방식은 대표적으로 4가지 방식(자카드 유사도, 유클리디언 유사도, 맨하탄 유사도, 코사인 유사도)이 있으며 각각에 대하여 알아보자.

 

우선 각 유사도를 측정하기 위해 필요한 예시 문장은 다음과 같다.

 - 휴일인 오늘도 서쪽을 중심으로 폭염이 이어졌는데요. 내일은 반가운 비 소식이 있습니다.

 - 폭염을 피해서 휴일에 놀러왔다가 갑작스러운 비로 인해 망연자실하고 있습니다.

 

이 두 문장에 대해 벡터화한다. 여기서는 TF-IDF를 통해 벡터화한다.

from sklearn.feature_extraction.text import TfidfVectorizer

sent = ("휴일 인 오늘 도 서쪽 을 중심 으로 폭염 이 이어졌는데요, 내일 은 반가운 비 소식 이 있습니다.", 
        "폭염 을 피해서 휴일 에 놀러왔다가 갑작스런 비 로 인해 망연자실 하고 있습니다.") 
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(sent) #문장 벡터화 진행

idf = tfidf_vectorizer.idf_
print(dict(zip(tfidf_vectorizer.get_feature_names(), idf)))

 결과 :

{'갑작스런': 1.4054651081081644, '내일': 1.4054651081081644, '놀러왔다가': 1.4054651081081644, '망연자실': 1.4054651081081644, '반가운': 1.4054651081081644, '서쪽': 1.4054651081081644, '소식': 1.4054651081081644, '오늘': 1.4054651081081644, '으로': 1.4054651081081644, '이어졌는데요': 1.4054651081081644, '인해': 1.4054651081081644, '있습니다': 1.0, '중심': 1.4054651081081644, '폭염': 1.0, '피해서': 1.4054651081081644, '하고': 1.4054651081081644, '휴일': 1.0}

 

위에서 얻어진 결과값은 4개의 유사도 측정 방법 중 자카드 유사도를 제외한 3개의 방식에 사용할 것이다.

 

▶ 자카드 유사도

자카드 유사도(Jaccard Similarity)는 자카드 지수라고도 말하며 두 문장을 각각 단어의 집합으로 만든 뒤 두 집합을 통해 유사도를 측정하는 방식이다.

유사도를 측정하는 방법은 두 집합의 교집합(공통된 단어의 개수)을 두 집합의 합집합(전체 단어의 수)으로 나눈다.

결괏값은 공통의 원소의 개수에 따라 0과 1 사이의 값이 나올 것이며, 1에 가까울수록 유사도가 높다는 의미를 갖는다.

다음은 자카드 유사도를 나타내는 수식이며 A와 B는 각 문장을 의미하고 token은 각 단어를 의미한다.

자카드 유사도 공식

그렇다면 위의 예시를 사용해 자카드 유사도를 측정해보자.

유사도를 측정할 때 단어에서 조사는 따로 구분해서 사용하기 때문에 두 문장 A와 B는 다음과 같이 정의될 것이다.

정의된 A, B를 벤 다이어그램으로 표현하면 다음과 같다.

벤 다이어그램에서 보면 교집합 갯수는 5개, 합집합 개수는 25개이다.

이를 자카드 공식에 대입해보면 5/25 = 0.2이므로 자카드 유사도는 0.2가 된다.

다음 계산을 코드 형태로 변환하면 다음과 같다.

sent = ("휴일 인 오늘 도 서쪽 을 중심 으로 폭염 이 이어졌는데요, 내일 은 반가운 비 소식 이 있습니다.", 
        "폭염 을 피해서 휴일 에 놀러왔다가 갑작스런 비 로 인해 망연자실 하고 있습니다.") 
        
tokenized_doc1 = set(sent[0].split(' '))
tokenized_doc2 = set(sent[1].split(' '))
print("문장 1의 집합 = ", tokenized_doc1)
print("문장 2의 집합 = ", tokenized_doc2)

union = set(tokenized_doc1).union(set(tokenized_doc2))
print("합집합 = ", union)

intersection = set(tokenized_doc1).intersection(set(tokenized_doc2))
print("교집합 = ", intersection)

jaccardScore = len(intersection)/len(union)

print("자카드 유사도 = ", jaccardScore)

결과 : 

문장 1의 집합 = {'오늘', '중심', '서쪽', '휴일', '이', '도', '을', '폭염', '소식', '이어졌는데요,', '있습니다.', '으로', '내일', '인', '비', '반가운', '은'}

문장 2의 집합 = {'놀러왔다가', '망연자실', '로', '휴일', '하고', '피해서', '갑작스런', '을', '폭염', '있습니다.', '에', '비', '인해'} 합집합 = {'놀러왔다가', '망연자실', '서쪽', '도', '을', '소식', '이어졌는데요,', '에', '으로', '내일', '인해', '은', '오늘', '이', '휴일', '인', '로', '하고', '피해서', '폭염', '있습니다.', '갑작스런', '비', '중심', '반가운'}

교집합 = {'휴일', '을', '폭염', '있습니다.', '비'}

자카드 유사도 = 0.2

 

▶ 코사인 유사도

코사인 유사도는 두 개의 벡터값에서 코사인 각도를 구하는 방법이다.

코사인 유사도 값은 -1과 1사이의 값을 가지고 1에 가까울수록 유사하다는 것을 의미한다.

코사인 유사도는 유사도를 계한할 때 가장 널리 쓰이는 방법 중 하나로, 다른 유사도 접근법에 비해 일반적으로 성능이 좋다. 그 이유는 단순히 좌표 상의 거리를 구하는 다른 유사도 측정 방법에 비해 두 벡터 간의 각도를 구하는 것이기 때문에 방향성의 개념이 더해지기 때문이다.

즉, 두 문장이 유사하다면 같은 방향을 가지게 되고, 유사하지 않을수록 직교로 표현될 것이다.

먼저 코사인 유사도 공식은 다음과 같다.

이제 앞에서 구한 TF-IDF로 벡터화한 문장을 사용해 코사인 유사도를 구해보자.

코사인 유사도의 경우 직접 함수를 구현할 필요 없이 사이킷런에서 유사도 측정을 위한 함수를 제공한다.

from sklearn.metrics.pairwise import cosine_similarity

# 코사인 유사도를 구해보자
cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])

결과 : 

array([[0.17952266]])

 

코사인 유사도에서 문장 A와 문장 B의 유사도는 0.1796으로 산출된다.

 

▶ 유클리디언 유사도

유클리디언 유사도는 가장 기본적인 거리를 측정하는 유사도 공식이며, 공식은 다음과 같다.

유클리디언 유사도 공식

여기서 구하는 거리는 유클리디언 거리(Euclidean Distance) 혹은 L2 거리(L2-Distance)라고 불리며, n차원 공간에서 두 점 사이의 최단 거리를 구하는 접근법이다.

일반적으로 중고등학교 때 배운 두 점 사이의 거리를 구하는 방식이 유클리디언 거리를 뜻한다.

이 공식을 사용해서 유사도를 구할 수 있는데, 사이킷런에서 유사도를 계산하는 함수를 제공하기 때문에 유클리디언 거리 측정 함수를 사용하기만 하면 된다.

위에서 구한 벡터를 통해 유클리디언 유사도를 구해보자.

from sklearn.metrics.pairwise import euclidean_distances

euclidean_distances(tfidf_matrix[0:1], tfidf_matrix[1:2])

결과 : 

array([[1.28099753]])

 

유클리디언 유사도 값이 1.28로 1보다 큰 값이 나왔다. 이는 단순히 두 점 사이의 거리를 뜻하기 때문에 값에 제한이 없으며 크기는 계속해서 커질 수 있다. 이렇게 제한이 없는 유사도는 사용하기 어렵기 때문에 벡터 값을 0에서 1사이의 값을 갖도록 정규화를 하여 유클리디언 유사도를 측정하여 사용한다. 

여기서는 L1 정규화 방법(L1-Normalization)을 사용하여 정규화를 수행한다.

import numpy as np
from sklearn.metrics.pairwise import euclidean_distances

def l1_normalize(v):
    norm = np.sum(v)
    return v / norm

tfidf_norm_l1 = l1_normalize(tfidf_matrix)
euclidean_distances(tfidf_norm_l1[0:1], tfidf_norm_l1[1:2])

결과 : 

array([[0.20491229]])

 

정규화 과정을 거친 후 유클리디언 유사도의 값은 0.2의 값이 나온 것을 확인할 수 있다.

 

▶ 맨하탄 유사도

맨하탄 유사도(Manhattan Similarity)는 L1거리(L1-Distance)라고도 하며 맨하탄 거리를 통해 유사도를 측정하는 방법이다.

맨하탄 거리란 사각형 격자로 이루어진 지도에서 출발점과 도착점까지를 가로지르지 않고 갈 수 있는 최단 거리를 구하는 공식이다.

출처 : 위키백과(맨해튼 거리)

위의 그림을 봤을 때 유클리디언 거리는 초록색 선을 의미한다.

하지만 초록색 선은 도로와 도로 사이에 아무런 장애물이 없을 때에만 가능하기 때문에 현실성이 없다. 

위의 그림에서 가장 적합한 맨하탄 거리는 파란색을 의미하며, 도로를 감안해서 가장 짧은 거리의 길이라고 생각하면 된다.

다음은 맨하탄 거리를 구하는 공식이다.

맨하탄 유사도 또한 유클리디언 유사도와 마찬가지로 계속해서 커질 수 있기 때문에 L1 정규화 방법을 사용하여 정규화 과정을 거친 뒤 유사도를 측정한다.

from sklearn.metrics.pairwise import manhattan_distances

manhattan_distances(tfidf_norm_l1[0:1], tfidf_norm_l1[1:2])

결과 :

array([[0.77865927]])

 

맨하탄 유사도의 값은 0.78의 값이 나온 것을 확인할 수 있다.

 

4가지의 유사도의 결괏값을 비교하였을 때 맨하탄 유사도로 측정했을 때 유사도가 가장 높게 나왔다.

즉, 측정 방법에 따라 유사도가 크게 달라질 수 있으므로 의도하고자 하는 방향에 맞는 유사도 측정 방법을 고르는 것이 매우 중요하다.

 

이렇게 텍스트 분류와 텍스트 유사도에 대한 이론적인 부분에 대하여 살펴보았다.

다음 포스팅에서는 구현과 함께 조금 더 깊게 공부해보도록 하자.

728x90
반응형
LIST