본문 바로가기

회고록(TIL&WIL)

TIL 2022.06.07 TODAY_LUNCH (사용자 기반 협업 필터링 추천시스템)

1. DB에 저장된 restaurant 데이터와 star 데이터를 csv화

django 프로젝트 내에서 App이 아닌 파이썬 파일을 실행하기 위해서 환경변수 세팅이 필요하다.

실절적으로 필요한 코드는 2줄이나

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "프로젝트명.settings")

django.setup()

외부 터미널에서 실행 할 수 있도록 경로 세팅을 위해 코드를 추가한 것

 

recommandation/to_csv.py

import os
import django
import sys

#환경변수 세팅(뒷부분은 프로젝트명.settings로 설정한다.) 모델을 불러오는 코드 보다 위에 있어야한다
print('==추가한 Path:', os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__)))) # path 추가
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "today_lunch.settings")
django.setup()

from star.models import star
from restaurant.models import Restaurant
import pandas as pd

restaurant_data = Restaurant.objects.all()
star_data = star.objects.all()

df1 = pd.DataFrame(columns=['restaurant_id',
                            'restaurant_name',
                            'restaurant_address',
                            'restaurant_category_id',
                            'restaurant_image'])
df2 = pd.DataFrame(columns=['user_id',
                            'restaurant_id',
                            'star_score',
                            'star_date'])

for res in restaurant_data:
    df1 = df1.append({
        'restaurant_id': res.id,
        'restaurant_name': res.restaurant_name,
        'restaurant_address': res.restaurant_address,
        'restaurant_category_id': res.restaurant_category_id,
        'restaurant_image': res.restaurant_image,
    }, ignore_index=True)
for star in star_data:
    df2 = df2.append({
        'user_id': star.star_user_id,
        'restaurant_id': star.star_restaurant_id,
        'star_score': star.star_score,
        'star_date': star.star_date,
    }, ignore_index=True)

df1.to_csv('res_info.csv', index=False)
print('res_info.csv create success')
df2.to_csv('stars.csv', index=False)
print('stars.csv create success')

2.  사용자 기반 필터링을 통해 추천시스템 구현

우선 앞서 생성한 csv 파일들을 로드해올 때 recommand.py만 실행할 경우 경로를 단순히 res_info.csv로만 지정해줘도 되나 restaurant APP의 views.py 에서 함수 호출을 통해서 실행 할 수 있도록 하기 위해선 경로 값을 지정해줘야만 했다.

함수 호출 시 로그인 유저의 id 값을 매개변수로 받아오도록 지정한 하였고 현재 로그인한 유저기준으로 사용자 기반 필터링을 통해서 자기와 비슷한 유저를 골라서 해당 유저가 매긴 음식점 중 높은 순서대로 리스트를 뽑아 return 하도록 지정함

 

recommandation/recommand.py

import pandas as pd

def recommandation(login_user_id):
    restaurants = pd.read_csv('recommandation/res_info.csv')
    stars = pd.read_csv('recommandation/stars.csv')

    # 데이터프레임을 출력했을때 더 많은 열이 보이도록 함
    pd.set_option('display.max_columns', 10)
    pd.set_option('display.width', 300)
    # restaurant_id를 기준으로 restaurants 와 starts 를 결합함
    restaurant_stars = pd.merge(restaurants, stars, on='restaurant_id')
    # print(restaurant_stars)

    # user별로 restaurant에 부여한 star 값을 볼 수 있도록 pivot table 사용
    restaurant_user = restaurant_stars.pivot_table('star_score', index='user_id', columns='restaurant_name')

    # 평점을 부여안한 영화는 그냥 0이라고 부여
    restaurant_user = restaurant_user.fillna(0)
    # print(restaurant_user)

    from sklearn.metrics.pairwise import cosine_similarity

    # 유저와 유저 간의 코사인 유사도를 구함
    user_based_collab = cosine_similarity(restaurant_user, restaurant_user)
    # print(user_based_collab)

    # 위는 그냥 numpy 행렬이니까, 이를 데이터프레임으로 변환
    user_based_collab = pd.DataFrame(user_based_collab, index=restaurant_user.index, columns=restaurant_user.index)
    # print(user_based_collab)

    # 1번 유저와 비슷한 유저를 내림차순으로 정렬한 후에, 상위 10개만 뽑음
    print(user_based_collab[login_user_id].sort_values(ascending=False)[:10])

    # 상위 유저 중 첫번째 유저를 뽑고,
    user = user_based_collab[login_user_id].sort_values(ascending=False)[:10].index[1]

    # 해당 유저가 좋아했던 음식점를 평점 내림차순으로 출력
    result = restaurant_user.query(f"user_id == {user}").sort_values(ascending=False, by=user, axis=1)
    # print(result)

    result_list = []
    for re in result:
        result_list.append(re)

    # print(result_list)
    return result_list

3. 메인페이지에 추천리스트 출력

앞서 만든 파이썬 파일들 import 해온 뒤 출력할 추천리스트들 다듬어서 main페이지 render하기

 

restaurant/views.py

# 앞서 만든 함수 호출 하기 위해서 import
from recommandation.recommand import recommandation

def main_view(request):
    if request.method == 'GET':
        # 현재 로그인 유저 정보 가져오기
        current_user = request.user

        # 사용자 기반 추천 시스템 필터링 거쳐 가장 비슷한 유저가 가본 음식점 중 평점 높은 순으로 리스트 가져옴
        reco = recommandation(current_user.id)

        # 내가 가본 음식점들 골라 내기
        my_diary = star.objects.filter(star_user=current_user.id)
        visited_restaurant = []
        for diary in my_diary:
            visited_restaurant.append(diary.star_restaurant.restaurant_name)

        # 추천리스트에서 내가 가본 음식점들 빼고 TOP 5개만 저장
        reco_list = list(set(reco) - set(visited_restaurant))[0:5]
        print(reco_list)

	# TOP5 레스토랑의 이름으로 DB에서 검색해서 해당 object 받아와 리스트에 저장
        recos = []
        for re in reco_list:
            recos.append(Restaurant.objects.get(restaurant_name=re))

        return render(request, 'main/main.html', {'recos': recos})

4. main.html에 출력

main/main.html

<div class="card-box">
    {% for re in recos %}
    <div class="card">
        <img src="/{{ re.restaurant_image }}" class="card-img-top" alt="...">
        <div class="card-body">
            <h5 class="card-title">{{ re.restaurant_name }}</h5>
            <p class="card-text">{{ re.restaurant_category }}</p>
            <a href="#" class="btn btn-primary">상세보기</a>
        </div>
    </div>
    {% endfor %}
</div>

위처럼 코드를 완성한 후 아무래도 계속해서 to_csv 파일을 실행해 주면서 동기화를 해야하는 방식으로 진행을 하다보니, 데이터가 쌓일 수록 페이지를 불러오는데 시간이 너무 많이 드는 것을 발견했다.

 

pandas dataframe에서 제공하는 append함수 자체가 무거워서 사용을 하지 않는 것이 좋다는 경고창이 계속 떠서 선민님에게 여쭤봐서 혹시 빠르게 할 방법이 있을까 싶어 같이 논의해서 인터넷에 찾아보니 django에서 DB데이터를 조회하였을 때 return받는 object를 그대로 values() 함수로 호출해서 DataFrame에 저장할 수 있는 방법이 있었다!

def recommandation(login_user_id):
    # 현재 DB에 맞게 동기화
    restaurants = pd.DataFrame(Restaurant.objects.all().values())
    stars = pd.DataFrame(Star.objects.all().values())
	
    # 테이블에 맞게 컬럼명 조정
    restaurants.columns = ['restaurant_id', 'restaurant_name', 'restaurant_address', 'restaurant_image', 'restaurant_category_id']
    stars.columns = ['star_id', 'star_score', 'star_date', 'restaurant_id', 'user_id']

    print(restaurants)
    print(stars)
	...

위에서 작성한 to_csv.py 파일이 단 4줄로 끝이 났다!

코드도 10배로 줄어서 함수호출형태가 아닌 그냥 recommandation 함수안에서 실행하도록 만들었고

속도도 이전에는 1.35초가 걸렸었는데 변경 후에는 아예 초단위가 아닌 밀리초단위로 265밀리초 밖에 걸리지않았다.