과제/팀과제

[Django] 최종 프로젝트 : 지금은 전시상황!(18) - 추천 시스템 리팩토링2

마이구미+ 2023. 7. 31. 15:09

<이전 코드/>

import psycopg2, os
import pandas as pd
import numpy as np
import datetime
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity


def pre_processing():
    # 데이터베이스 연결
    con = psycopg2.connect(
        host=os.environ.get("DB_HOST"),
        dbname=os.environ.get("DB_NAME"),
        user=os.environ.get("DB_USER"),
        password=os.environ.get("DB_PASSWORD"),
        port=os.environ.get("DB_PORT"),
    )
    cur = con.cursor()
    cur.execute(
        "SELECT id, info_name, start_date, end_date, svstatus From exhibitions_exhibition"
    )
    cols = [column[0] for column in cur.description]

    # 데이터프레임 생성
    exhibition_df = pd.DataFrame.from_records(data=cur.fetchall(), columns=cols)

    # 데이터베이스 연결 종료
    con.close()

    # info_name별 유사도 측정
    count_vect = CountVectorizer(min_df=0, ngram_range=(1, 2))
    info_name_mat = count_vect.fit_transform(exhibition_df["info_name"])

    # 유사도 행렬 생성
    info_name_sim = cosine_similarity(info_name_mat, info_name_mat)

    return exhibition_df, info_name_sim


exhibition_df, info_name_sim = pre_processing()


# 비슷한 전시 추천 시스템 함수
def recommendation(id, top_n=5):
    # 전역변수를 참조하도록 지정함
    global exhibition_df
    global info_name_sim

    try:
        # 입력한 정보의 index
        target = exhibition_df[exhibition_df["id"] == id]
        target_index = target.index.values

        # 데이터프레임에 입력한 정보의 유사도 추가
        exhibition_df["similarity"] = info_name_sim[target_index, :].reshape(-1, 1)

        # start_date가 오늘 날짜 이전이거나 오늘이고, end_date가 오늘 날짜거나 오늘 날짜 이후인 데이터만 추출하기
        today = datetime.date.today()
        period = (exhibition_df["start_date"] <= today) & (
            exhibition_df["end_date"] >= today
        )
        filterd_exhibition_df = exhibition_df.loc[period].sort_values(
            by=["end_date"], ascending=True
        )

        # 날짜 필터링 된 데이터프레임의 유사도 내림차순 정렬
        temp = filterd_exhibition_df.sort_values(by=["similarity"], ascending=False)
        # 자기 자신 제거
        temp = temp[temp.index.values != target_index]
        # svstatus가 접수중인 데이터만 추출
        temp = temp[temp["svstatus"] == "접수중"]

        # 데이터프레임 기준 최종 유사도 Top5 index 리스트
        final_index = temp.index.values[:top_n]

    except ValueError:
        exhibition_df, info_name_sim = pre_processing()
        return recommendation(id, top_n)

    # 최종 유사도 Top5의 데이터프레임
    raw_exhibitions = filterd_exhibition_df.loc[final_index]

    # 최종 유사도 Top5의 데이터베이스 상 id 리스트
    ml_recommend_exhibitions_id_list = np.array(raw_exhibitions[["id"]]["id"].tolist())

    return ml_recommend_exhibitions_id_list

<문제점/>

  • 이전 리팩토링으로 함수 실행시간은 확 단축돼서 효율은 좋아졌으나 오늘 소셜 로그인 리팩토링 하다보니 추천 함수에도 try-except문을 남발했다는 걸 깨달았다
  • 그래서 추천 함수의 try-except문도 고쳐보고자 한다
  • 모르는 사람이 봤을 때 소셜 로그인보다 추천 함수를 더 못 알아볼 것 같다
  • ValueError가 나오면 except문이 실행되는데 ValueError가 왜 나오는지 그 에러를 직접 마주하지 않고서는 알 수가 없다
  • 그리고 내가 알고 있는 원인과 다른 원인으로 ValueError가 발생하게 될 수도 있는데 예외 처리를 해버리면 원인을 찾기 어려울 테니 아무래도 try-except문보다는 다른 방식으로 예외 처리해야 할 것이다

<수정한 코드/>

  • 내가 찾은 방법은 전역 변수를 이용해 새 데이터의 추가 유무를 판단하는 것이다
  • 프론트 작업할 때도 썼던 방법인데 변수에 상태를 담아 그 상태에 따라 로직을 달리하면 된다
# exhibitions/recommend_ml.py

data_updated = False


def set_data_updated():
    global data_updated
    data_updated = True
  • 우선 recommend_ml.py 파일 전역에 data_updated 변수를 선언하고, 그 변수의 상태를 바꿔주는 set_data_updated() 함수를 정의한다
  • 이 함수를 views.py에서 새 데이터가 추가되었을 때 호출함으로써 사용된다
# exhibitions/views.py

from exhibitions.recommend_ml import recommendation, set_data_updated

class ExhibitionView(APIView):
    ...

    def post(self, request):  # 전시회 작성
        serializer = ExhibitionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(user=request.user)
            set_data_updated()	# 여기 추가!
            return Response(
                {"message": "게시글이 등록되었습니다.", "data": serializer.data},
                status=status.HTTP_201_CREATED,
            )
        else:
            return Response(
                {"message": "요청이 올바르지 않습니다."}, status=status.HTTP_400_BAD_REQUEST
            )
  • 위 코드 12번째 줄에서 set_data_updated 함수를 호출하면서 data_updated 변수는 True로 바뀐다
# exhibitions/recommend_ml.py

def pre_processing():
    # 데이터베이스 연결
    ...

    # 데이터 프레임 작업 후에는 data_updated 변수를 False로 바꿔줌
    global data_updated
    data_updated = False

    return exhibition_df, info_name_sim
  • DB에 연결해 데이터를 가져와서 데이터프레임에 담고, 유사도 행렬을 생성하는 pre_processing() 함수에서는 마지막 부분에 data_updated 변수의 상태를 False로 바꿔준다
  • 업데이트 된 데이터를 반영해서 데이터프레임을 새로 가져왔으니 False로 바꿔주는 것이다
  • 이 작업이 없다면 한 번 새 데이터가 추가된 이후 계속 True 상태로 있기 때문에 recommendation() 함수에서 매번 pre_processing() 함수를 호출할 것이다
  • 그러면 매번 데이터베이스에 접속하던 그 코드와 다를 바가 없게 된다!!
# exhibitions/recommend_ml.py

# 비슷한 전시 추천 시스템 함수
def recommendation(id, top_n=5):
    # 전역변수를 참조하도록 지정함
    global exhibition_df
    global info_name_sim
    global data_updated

    if data_updated:
        exhibition_df, info_name_sim = pre_processing()
        
    ...
  • 이렇게 recommendation() 함수에서 data_updated가 True이면 pre_processing() 함수를 다시 호출하고, False면 원래 값을 이용해서 전시 데이터를 추천한다