-
Node.js 프로젝트는 MVC, Django 프로젝트는 MTV이다.
-
TFIDF 유사도 도출 알고리즘을 Django Filter와 결합하는 데 성공했다!
Node.js의 MVC 그리고 Django의 MTV
Node.js의 프로젝트는 세 가지 파트로 구성되어 있다.
- Model: Model represents shape of the data.
- View: View is a user interface.
- Controller: Controller handles the user request.
만약 Django의 파일을 Node.js와 1:1 대응을 시켜보자면,
- Django의 models.py는 Model이다
- Django의 templates (.html & .css)는 View이다.
- Django의 views.py, forms.py는 Controller이다.
그래서 Django에서는 MTV라고 한다. Views.py라는 이름이 Node.js의 개념과 부딛쳐서 헷갈렸다.
TFIDF를 기반으로 높은 유사도를 뽑는 알고리즘과 Django Filter의 결합
I. Mecab과 Okt로 검색어에서 특정 형태소(명사, 서술어) 추출하기
import pandas as pd
import numpy as np
from konlpy.tag import Okt, Mecab
class KorTokenizer:
""" Korean Tokenizer Description """
def tokenizer(raw):
# position arguments: 내가 뽑아내고 싶은 형태소들
twitter_pos = ["Noun", "Alpha", "Verb", "Number", "Adverb"]
mecab_pos = ["NNG", "NNP", "VV", "VA", "MAG"]
pos = twitter_pos + mecab_pos
# accessing saved stopwords
stopwords_dataframe = pd.read_csv(
"./exercises/recommender/_searchTool/stopwords_dataframe.csv"
)
stopwords = stopwords_dataframe["stopwords"]
# mecab module
mecab = Mecab()
mecab_list = [
word
for word, tag in mecab.pos(raw, flatten=True)
if len(word) > 1 and tag in pos and word not in stopwords
]
# twitter module
twitter = Okt()
twitter_list = [
word
# normalize 그랰ㅋㅋ -> 그래ㅋㅋ # stemming 바뀌나->바뀌다
for word, tag in twitter.pos(raw, norm=True, stem=True)
if len(word) > 1 and tag in pos and word not in stopwords
]
# combine extracted noun and verb list without overlapping
return set(mecab_list + twitter_list)
예를 들어서 사용자가 "복근에 좋은 운동"을 검색하면, KorTokenizer가 이를 ["복근", "좋다"]의 리스트로 변환하는 것이다.
이 때, "좋은" -> "좋다" 형태로 바꾸는 것은 Okt에서는 stem = True인 argument, Mecab에서는 flatten = True argument로 설정하면 된다.
II. 미리 저장해놓은 TFIDF 모델 불러올 때, 형태소 분석기 덮어씌우기
from scipy import sparse
import pickle
class CustomUnpickler(pickle.Unpickler):
""" Returning custom tokenizer instead of default tokenizer when loading tfidf vectorizer with pickle """
def find_class(self, module, name):
if name == "tokenizer":
return KorTokenizer.tokenizer
return super().find_class(module, name)
TFIDF 모델을 저장할 때, 세 가지로 구성해서 저장해야 한다.
- feature vector을 저장한 .csv 파일
- matrix를 저장한 .npx 파일
- (vocabulary를 내장한) vectorizer을 binary 형태로 저장한 .pkl 파일
여기에서 .pkl 파일인 vectorizer을 불러낼 때 문제가 있다. vectorizer에서 tokenizer을 요구하는데, 우리는 여기서 한국어 형태소 분석기를 써야 한다. 그래서 내장 tokenizer가 아닌, 우리의 한국어 tokenizer로 덮어씌워야 한다. 안 그러면 vocabulary_ error가 발생한다.
III. TFIDFSearch로 검색 어절과 최대한 비슷한 운동 종목 찾아내기
class TFIDFSearch:
""" TFIDF Search Definition """
def tfidf_srch(search_phrase):
# ignore warnings
warnings.filterwarnings(action="ignore", category=UserWarning, module="sklearn")
# load feature vector
features = pd.read_csv("./exercises/recommender/_searchTool/tfidf/features.csv")
features = features.values[0].tolist()
# 검색 문장에서 feature를 뽑아냄. 단, 아직 띄어쓰기 모듈은 적용이 안 되어 있음
srch = [t for t in KorTokenizer.tokenizer(search_phrase) if t in features]
print(srch)
# load matrix
X = sparse.load_npz("./exercises/recommender/_searchTool/tfidf/yourmatrix.npz")
# print(type(X))
# load vectorizer
vectorizer = CustomUnpickler(
open("./exercises/recommender/_searchTool/tfidf/tfidfvectorizer.pkl", "rb")
).load()
# dtm 에서 검색하고자 하는 feature만 뽑아낸다.
srch_dtm = np.asarray(X.toarray())[
:,
[
# vectorize.vocabulary_.get 는 특정 feature 가 dtm 에서 가지고 있는 index값을 리턴한다
vectorizer.vocabulary_.get(i)
for i in srch
],
]
# print(len(srch_dtm))
score = srch_dtm.sum(axis=1)
# exercises' instagram hashtag crawling result data (NOT DJANGO DATA)
df = pd.read_csv(
"./exercises/recommender/_searchTool/200311_djangoDB_matched_instaCrawled.csv"
)
recommended_exercises_web = []
for i in score.argsort()[::-1]:
if score[i] > 0.053:
# see each exercise's matching score
# print((df["exercise_name"].iloc[i], score[i]))
# compare exercise name for instagram crawling and exercise name registered on web
# recommend_exercise_insta = df["exercise_name"].iloc[i]
# recommend_exercise_web = df["exercise_name_web"].iloc[i]
# print(recommend_exercise_insta, recommend_exercise_web)
# add on list
exercise_name_web = df["exercise_name_web"].iloc[i]
# print(type(exercise_name_web))
# print(exercise_name_web)
# remove nan from recommendation result
if isinstance(exercise_name_web, float):
pass
else:
recommended_exercises_web.append(exercise_name_web)
# print(recommended_exercises_web)
# give top 21 values only
return recommended_exercises_web[:20]
IV. Views.py에서 유저가 입력한 검색어를 맞춤법에 맞도록 바꾸기
# importing Korean spell checker
from chatspace import ChatSpace
def spellchecker(text):
spacer = ChatSpace()
texts = [text]
for origin_text in spacer.space_iter(texts):
return origin_text
맞춤법에 맞지 않으면, 형태소가 정상적으로 나뉘어지지 않는다. 예를 들어 "무릎관절에나쁘지않은운동"라는 문장을 형태소분석기 KorTokenizer 클래스에 던져주면, 혼란스러워할 것이다.
그래서 Pingpong ai 팀에서 개발한 chatspace를 들여썼는데, 사용하기 간단하다. 이렇게 코드를 입력하면, 문장을 맞춤법에 맞게 변환해준다. 시중에서 구할 수 있는 모듈들 중에서 성능도 훌륭하다.
V. 드디어! Django views.py에 TFIDFSearch를 결합하기
# my python files for search engine
from django.views.generic import View
from .recommender.tfidf_srch import TFIDFSearch
class SearchView(View):
""" SearchView Definition """
def get(self, request):
search_phrase = request.GET.get("search_phrase")
if name:
# give form with user's GET request
form = forms.SearchForm(request.GET)
if form.is_valid():
filter_args = {}
if name != "모든 운동":
# spelling correction using: https://github.com/pingpong-ai/chatspace
spelled_search_phrase = spellchecker(search_phrase)
# Django default filter search
# filter_args["name__contains"] = name
# TFIDF search
tfidf_recommend_list = TFIDFSearch.tfidf_srch(spelled_search_phrase)
print(tfidf_recommend_list)
# multiple entries as __in
# https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships
filter_args["name__in"] = tfidf_recommend_list
exercises_qs = models.Exercise.objects.filter(**filter_args).order_by(
"-created"
)
- 유저가 던져준 search_phrase를 spellchecker()로 맞춤법에 맞게 spelled_search_phrase로 변환시킨다.
(예시) "복근에좋은운동" -> "복근에 좋은 운동" - TFIDFSearch.tfidf_srch는 검색어를 모델에 입력해서, 최대한 비슷한 운동들 21개를 리스트 형태로 반환한다.
(예시) "복근에 좋은 운동" -> ["폴댄스", "플라잉요가", "플라잉필라테스" ...] - 운동 목록을 갖고 database를 검색할 때는, filter arguments로서 입력할 때 __in을 이용한다. queryset인 exercises_qs를 반환한다
드디어 운동 추천 기능을 검색 엔진 형태로 구현을 했다.
이제 디자인으로 넘어갈 차례이다.
참고자료
프로젝트 전체 코드는 필자의 github에서 확인할 수 있다.
인스타그램 텍스트 데이터로 TFIDF 알고리즘 제작을 만드는 법은 다음 포스트를 참고하면 좋을 것 같다.
Pingpong ai 기술 블로그는 NLP 공부하는 데에 도움이 됐다. Pingpong이 Chatspace를 개발한 내역은 다음 포스트를 참고하면 좋다.
'Project Based Learning > 운동 추천 웹서비스 - FitCuration' 카테고리의 다른 글
Django 학습 3주일 차 - 몸져눕다 (0) | 2020.03.17 |
---|---|
Django 학습 2주일 차 - Node(Express.js)와의 비교 (0) | 2020.02.29 |
Django 학습 1주일 차 - 어떻게 하면 날로 먹을까? (0) | 2020.02.22 |
[Django] Application 폴더를 지워서, migration이 꼬였을 때 (0) | 2020.02.11 |
인스타그램 텍스트 데이터로 나에게 맞는 운동을 찾는 법: TF-IDF, Word2Vec (0) | 2020.02.03 |