<이전 코드/>
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면 원래 값을 이용해서 전시 데이터를 추천한다