안녕하세요. @anpigon 입니다.
이번에는 유사한 게시물을 찾아내는 방법을 공부하였습니다. 게시물을 벡터로 계산하고, 벡터 간의 거리를 구하는 방법으로 유사도를 분석합니다. 이 기술을 이용하면 인터레스팀 서비스처럼 관련 글을 찾아낼 수도 있습니다. 저는 이 기술을 사용해서 불펌러(어뷰징 계정)들을 찾아내고 싶네요.
이번 예제는 “Building Machine Learning Systems with Python - Second Edition” 서적을 참고하였습니다.
이 책의 54 페이지에 있는 내용입니다.
scikit-learn
**scikit-learn**는 데이터 마이닝 및 데이터 분석을 위한 파이썬 라이브러리이다. NumPy, SciPy 및 matplotlib를 기반으로 제작되었다. 그리고 상업적으로 사용 가능한 오픈 소스(BSD) 라이센스를 가지고 있다.
scikit-learn 사용 환경은 다음과 같다. 아쉽게도 최신 버전인 파이썬3.7에서는 동작하지 않는다.
1 | Python (>= 2.7 or >= 3.3), |
설치하기
1 | pip install -U scikit-learn |
카운트 벡터 생성하기
게시물에 등장하는 단어를 세어 하나의 카운트 벡터로 생성한다. 그리고 해당 컨텐츠와 다른 컨텐츠 사이의 벡터 거리를 계산하여 컨텐츠 사이의 유사도를 파악한다. 하지만 우리는 단어를 세고 그 카운트를 벡터로 나타내는 코드를 작성할 필요가 없다. 그냥 SciKit의 “countVectorizer” 함수를 사용하면 된다.
scikit-learn에서 텍스트를 벡터화할 수 있는 CountVectorizer
함수를 import한다.
1 | from sklearn.feature_extraction.text import CountVectorizer |
CountVectorizer
의 매개변수를 간단하게 설명하면, min_df
의 값은 해당 값보다 낮은 빈도수의 단어는 모두 삭제된다. max_df
도 비슷한 방식으로 작동한다. 아래와 같이 vectorizer
를 출력하면 SciKit이 제공하는 다른 매개 변수와 기본값을 볼 수 있다.
다음 데이터 세트를 사용하여 벡터간 거리를 계산해보자.
1 | posts = [ |
fit_transform()
함수를 사용하여 벡터화 작업을 수행한다. 결과를 확인해보면, 19개 단어로 구성된 5개의 게시물이 벡터화되었다.
1 | X_train = vectorizer.fit_transform(posts) |
feature_names을 출력해보자. vectorizer가 19개의 단어를 감지했다는 것을 확인할 수 있다. 그러나 **“글은”**과 “글이다” 처럼 동일한 단어가 따로 구분되어 잡힌게 찜찜하지만 일단은 그대로 진행해본다.
1 | print(vectorizer.get_feature_names()) |
이제 새로운 게시물이 주어졌을때 해당 게시물이 어느 게시물과 거리가 가까운지 살펴보자. 거리가 가까운 게시물이 가장 유사한 게시물이라고 보면 된다.
새 게시물을 벡터화하고 카운터 벡터를 생성하자.
1 | new_post = "이미징 데이터베이스는" |
책 내용에 따르면 ‘transform’ 함수에 의해 반환되는 카운트 벡터는 희소(sparse)하다는 것에 주의해야 한다. 이것은 각 벡터가 각 단어에 대한 카운트 값을 가지고 있지 않기 때문에 대부분은 0에 가까운 값을 가진다는 것을 의미한다. 대신, 더 효율적인 메모리 구현체인 'coo_matrix’를 사용한다. 예를 들어 새 게시물에는 실제로 두가지 요소만 포함하고 있다.
사실 저는 이 내용을 이해하지 못했습니다. 영어를 제대로 번역한게 맞는지도 모르겠어요. 이 부분에 대해서는 쉽게 설명하지 못해서 죄송합니다.ㅠ
벡터화된 새 게시물의 new_post_vec
를 출력해보자.
1 | print(new_post_vec) |
출력 결과를 설명하면 새로운 게시물의 벡터가 7번째와 12번째에 위치하고 있다는 의미이다.
toarry()
멤버를 통해 전체 ndarray
를 출력해보자.
1 | print(new_post_vec.toarray()) |
하지만 책이 설명하는 것과 동일한 결과가 나오지 않았다. 아마도 한글은 형태소 분석을 해야 원하는 결과가 나올 것 같다.
형태소 분석기를 사용하여 형태소를 분석하자. 그리고 형태소 분석된 게시물을 다시 벡터화를 해보자.
1 | from konlpy.tag import Mecab |
형태소 분석된 게시물을 벡터화한다.
1 | X_train = vectorizer.fit_transform(posts_tokens) |
그리고 새로운 게시물도 형태소 분석을 한다.
1 | new_post = "이미징 데이터베이스는 저장한다." |
새로운 게시물을 벡터화하고 new_post_vec
를 출력해본다.
1 | new_post_vec = vectorizer.transform([new_post_tokens]) |
ndarray
도 출력해본다. 새로운 게시물의 벡터가 5번째, 11번째, 14번째, 17번째에 위치한다는 예상했던 결과가 나왔다.
1 | print(new_post_vec.toarray()) |
벡터 사이의 거리 구하기
이제 새로운 게시물(new_post_vec)과 기존 게시물(posts)들 각각에 대해 거리를 계산해보자. 두 벡터 사이의 유클리드 거리를 계산하여 유사성을 측정한다.
우선 거리를 구하는 dist_raw()
함수 정의한다. np.linalg.norm()
함수를 사용하면 최단 거리를 계산할 수 있다.
1 | import numpy as np |
dist_raw()
함수를 사용하여 이전 게시물과 기존 게시물의 거리를 계산하여 출력한다. 그리고 유사도가 가장 높은 게시물을 찾아보자.
1 | import sys |
3번 게시물이 새로운 게시물과 거리가 가장 가깝다는 결과가 나왔다. 그러나 3번, 4번 게시물을 보면 이상한 점이 있다. 4번 게시물은 3번 게시물을 복사&붙여넣기 한 것이다. 따라서 3번 게시물처럼 4번 게시물도 새로운 게시물과도 유사하다는 결과가 나와야 한다.
해당 벡터의 feature를 출력해보면 그 이유를 알 수 있다.
이를 교정하기 위해서는 카운트 벡터를 정규화해야한다. 위에서 정의한 거리 구하는 함수 dist_raw()
를 수정함으로써 문제를 간단히 해결할 수 있다.
카운터 벡터 정규화하기
원시 벡터(raw vectors)가 아닌 정규화된 벡터 거리를 계산하기 위한 함수 dist_norm()
를 정의한다. 이 함수는 각 벡터의 norm을 나눈 후에 거리를 계산한다.
1 | def dist_norm(v1, v2): |
이 함수를 사용하여 유사도를 측정한 결과를 출력해보자.
1 | === Post 0 with dist = 3.32: 이 글은 머신러닝에 관한 글이다. 사실은 재미없는 내용을 포함하고 있다. |
이제 원하는 결과가 나왔다. 3번 게시물과 4번 게시물도 동일한 결과가 나왔다.
여기까지 읽어주셔서 감사합니다.