본문으로 바로가기
  • Node.js 프로젝트는 MVC, Django 프로젝트는 MTV이다.

  • TFIDF 유사도 도출 알고리즘을 Django Filter와 결합하는 데 성공했다!

지난 7일간의 코딩 내역
Python, JavaScript(ES6), TypeScript를 중점으로 공부했다

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를 들여썼는데, 사용하기 간단하다. 이렇게 코드를 입력하면, 문장을 맞춤법에 맞게 변환해준다. 시중에서 구할 수 있는 모듈들 중에서 성능도 훌륭하다.

 

pingpong-ai/chatspace

핑퐁에서 만든 채팅체랑 잘 맞는 띄어쓰기 모델! 🔪😎. Contribute to pingpong-ai/chatspace development by creating an account on GitHub.

github.com

 

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에서 확인할 수 있다.

 

snoop2head/FitCuration_django_web

Exercise Recommendation & Exercise Studio/Center/Gym Recommendation Web Application - snoop2head/FitCuration_django_web

github.com

 

인스타그램 텍스트 데이터로 TFIDF 알고리즘 제작을 만드는 법은 다음 포스트를 참고하면 좋을 것 같다. 

 

인스타그램 텍스트 데이터로 나에게 맞는 운동을 찾는 법: TF-IDF, Word2Vec

I. 인스타그램 게시글 텍스트 데이터를 바탕으로 각 운동 종목을 대표하는 키워드들을 추출했다. 밑의 예시들은 모두 TF-IDF를 이용해서 각 운동을 대표하는 키워드들을 추출해본 것이다. 예를 들어서, 바차타(도..

gaemin.tistory.com

Pingpong ai 기술 블로그는 NLP 공부하는 데에 도움이 됐다. Pingpong이 Chatspace를 개발한 내역은 다음 포스트를 참고하면 좋다.

 

대화체에 유연한 띄어쓰기 모델 만들기

How to make Spacing Model for chatting language

blog.pingpong.us