■ JITHub 개발일지 66일차
□ TIL(Today I Learned) ::
프로젝트 조회수, Permission, Dataframe, 머신러닝
1) 게시글 조회수 생성
조회수 생성은 아래와 같은 코드로 간단하게 적용할 수 있었다.
class Place(models.Model):
...
hit = models.PositiveIntegerField('조회수', default=0)
...
@property
def hit_count(self):
self.hit +=1
self.save()
- 실제 코드를 실행할 때에는 views.py의 함수에서 위의 hit_count를 호출해와야 동작이 되는데 이를 호출해오지 않아 hit count가 제대로 되지 않았던 문제가 있었다.
- 아래 코드처럼 place.hit_count를 추가해주면서 문제를 해결할 수 있었다.
#views.py
class PlaceDetailView(APIView):
permission_classes = [IsAdminOrOntherReadOnly]
#맛집 상세 페이지
@swagger_auto_schema(operation_summary="맛집 상세 페이지",
responses={200 : '성공', 404 : '찾을 수 없음', 500 : '서버 에러'})
def get(self, request, place_id):
place = get_object_or_404(Place, id=place_id)
place.hit_count
serializer = PlaceSerializer(place)
return Response(serializer.data, status=status.HTTP_200_OK)
2) 장소(Place) DATA CRUD 생성(Admin권한으로만 Create, Update, Delete 접근)
- 장소 데이터는 Admin권한을 가질 경우에만 Create, Update, Delete에 접근할 수 있도록 하려 한다.
- 아래와 같이 permissions.py를 지정해준다. 여기서 IsAdmin 클래스의 has_permission 함수를 호출해와서 사용하게 된다.
# permissions.py
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import APIException
from rest_framework import status
class GenericAPIException(APIException):
def __init__(self, status_code, detail=None, code=None):
self.status_code=status_code
super().__init__(detail=detail, code=code)
class IsAdmin(BasePermission):
"""
admin 사용자는 모든request 가능,
비로그인, 로그인한 사람은 조회만 가능
"""
SAFE_METHODS = ('GET', )
message = '접근 권한이 없습니다.'
def has_permission(self, request, view):
user = request.user
if request.method in self.SAFE_METHODS:
return True
if not user.is_authenticated or user.is_anonymous:
response = {
'detail': "관리자만 접근이 가능합니다."
}
raise GenericAPIException(status_code=status.HTTP_403_FORBIDDEN, detail=response)
if user.is_admin and user.is_authenticated:
return True
if (user.is_anonymous or user.is_authenticated) and request.method in self.SAFE_METHODS:
return True
return False
- View에서 아래와 같이 permission_classes = [IsAdmin]과 같이 적어주면 함수를 호출할 수 있다.
(※ 여기서 문제가 되었던 부분은 pwermissions_classes 라고 's' 한글자 Typo 때문에 실행이 안되는 문제가 있었다.)
##### 장소 #####
class PlaceListView(APIView):
permission_classes = [IsAdmin]
#맛집 리스트
def get(self, request):
place = Place.objects.all()
serializer = PlaceSerializer(place, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
#맛집 생성
def post(self, request):
serializer = PlaceCreateSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
3) 장소 선택 데이터 GET
- 유저가 초기에는 경험데이터(리뷰 데이터)가 없기 때문에 비로그인 접속자나, 신규 회원에게는 선호도를 받고자 했다.
- 장소 데이터는 직접 정한 카테고리를 이용하기 위해 FOOD_CATEGORY를 만들어주고, 튜플 안을 돌면서 카테고리 이름을 가져오기 위해 for문과 Place.objects.filter를 사용했다. order_by는 기존에 갖고 있던 네이버 평점데이터 'rating'을 받아오기 위해 추가해주었다. 각각의 pick은 place에 넣어주고 결과값을 serializer를 통해 Response로 전달해준다.
FOOD_CATEGORY = (
('F1', '분식'),
('F2', '한식'),
('F3', '돼지고기구이'),
('F4', '치킨,닭강정'),
('F5', '햄버거'),
('F6', '피자'),
('F7', '중식당'),
('F8', '일식당'),
('F9', '양식'),
('F10', '태국음식'),
('F11', '인도음식'),
('F12', '베트남음식'),
)
##### 취향 선택 #####
class PlaceSelectView(APIView):
permission_class = [IsAuthenticated]
#맛집 취향 선택
def get(self, request):
place = []
for i in range(len(FOOD_CATEGORY)):
pick = Place.objects.filter(category=FOOD_CATEGORY[i][1]).order_by('-rating')[0]
place.append(pick)
serializer = PlaceSelectSerializer(place, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
4) 머신러닝 장소 추천 기능
- 처음에 DB에서 데이터를 가져와서 데이터프레임으로 어떻게 만들지 고민을 많이했다. 생각보다 코드는 간단히 사용할 수 있었다. 앞으로도 필요하다면 이 코드를 참고하면 좋을 듯 하다.
pd.DataFrame(list(Place.objects.values()))
- dataframe의 칼럼 아이디도 아래와 같은 코드로 간단히 변경이 가능하다.
#Dataframe의 칼럼 아이디를 변경 {<기존>:<변경>}
places.rename(columns={'id':'place_id'}, inplace=True)
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import CountVectorizer
import json
import sys
import os
import django
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gaggamagga.settings')
django.setup()
from reviews.models import Review
from places.models import Place
#DB에서 데이터를 가져와서 Dataframe으로 만들어주기
places = pd.DataFrame(list(Place.objects.values()))
reviews = pd.DataFrame(list(Review.objects.values()))
#Dataframe의 칼럼 아이디를 변경 {<기존>:<변경>}
places.rename(columns={'id':'place_id'}, inplace=True)
# print(reviews.head)
# print(places.head)
#places, reviews 두 개의 dataframe 병합
place_ratings = pd.merge(places, reviews, on='place_id')
# print(place_ratings.head)
# user별로 Place에 부여한 rating을 볼 수 있도록 pivot table사용
title_user = place_ratings.pivot_table('rating_cnt', index='author_id', columns='place_id')
#rating을 부여안한 장소는 3으로 부여
title_user = title_user.fillna(3)
# print(title_user.head)
# User간 Cosine Similarity를 numpy 행렬로 변환
user_sim_np = cosine_similarity(title_user, title_user)
user_sim_df = pd.DataFrame(user_sim_np, index=title_user.index, columns=title_user.index)
print(user_sim_df.head)
# 1번 유저와 비슷한 유저를 내림차순으로 정렬 후 상위 10개만 뽑음
print(user_sim_df[1].sort_values(ascending=False)[:10])
# 1번 유저와 가장 비슷한 유저를 뽑고
user = user_sim_df[1].sort_values(ascending=False)[:10].index[1]
# 가장 비슷한 유저가 평점을 높이 준 장소를 내림차순으로 정렬
result = title_user.query(f"author_id == {user}").sort_values(ascending=False, by=user, axis=1).transpose()[:10]
print(result)
- 위의 머신러닝 테스트 코드를 작성한 후 다른 파일에서 호출해서 사용하기 위해 아래와 같이 코드를 변경하였다.
- 필요한 정보로는 user_id, picked_place_id를 받아온다.
- dataframe을 수정해주기 위해 df.loc[x] = np.nan(x행을 신규로 생성하여 NaN값으로 만들어주기), df.loc[x, y] = z(x행 y열의 값을 z로 삽입하기) 등 데이터프레임 관련 함수들을 추가로 알아봐야 할 필요가 있었다.
# rcm_places.py (1207_1차 완료 코드)
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import CountVectorizer
import json
import sys
import os
import django
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gaggamagga.settings')
django.setup()
from reviews.models import Review
from places.models import Place
def rcm_place(user_id, picked_place_id):
places = pd.DataFrame(list(Place.objects.values()))
reviews = pd.DataFrame(list(Review.objects.values()))
places.rename(columns={'id':'place_id'}, inplace=True)
place_ratings = pd.merge(places, reviews, on='place_id')
review_user = place_ratings.pivot_table('rating_cnt', index='author_id', columns='place_id')
review_user = review_user.fillna(0)
review_user.loc[user_id] = np.nan
review_user = review_user.fillna(0)
review_user.loc[user_id, picked_place_id] = 5
print(review_user)
user_sim_np = cosine_similarity(review_user, review_user)
user_sim_df = pd.DataFrame(user_sim_np, index=review_user.index, columns=review_user.index)
print(user_sim_df.head)
print(user_sim_df[user_id].sort_values(ascending=False)[:])
picked_user = user_sim_df[user_id].sort_values(ascending=False)[:].index[1]
result = review_user.query(f"author_id == {picked_user}").sort_values(ascending=False, by=picked_user, axis=1)
result_list = []
for column in result:
result_list.append(column)
return result_list
- 관련함수들이 너무나 많아 기억하기 쉽지않을 것 같아 아래와 같이 정리된 자료가 있어 스크랩해 정리했다.
(*참고 링크)
import pandas as pd
#빈 DataFrame 생성
df1 = pd.DataFrame(columns=range(5))
#행 추가
df1.loc[0]=[1,2,3,4,5]
#열 길이 같은 DataFrame 행 병합
df2 = pd.DataFrame(columns=range(5))
df2.loc[0]=[11,12,13,14,15]
df1 = df1.append(df2)
#행 길이 같은 DataFrame 열 병합
df3 = pd.DataFrame([[3],[4]],columns=range(5,6))
df1 = df1.join((df3))
#행 이름 설정
df1.index = range(1,3)
#열 이름 설정
df1.columns =range(1,7)
#특정 행 이름 바꾸기
df1.rename(index={2:4},inplace=True)
#특정 열 이름 바꾸기
df1.rename(columns={6:7},inplace=True)
#빈 값으로 데이터 넣기
df1.loc[2]=[3,5,6,7,9,2]
import numpy as np
df1[6] = np.nan
df1.loc[3]=np.nan
#행 순서 바꾸기
df1 = df1.reindex(index=[1,2,3,4])
#열 순서 바꾸기
df1=df1[list(range(1,8))]#df1[[1,2,3,4,5,6,7]]
#특정값 변경
df.loc[2, 'A'] = 3000
5) 여러 개의 필터 조건 넣기
- <model_name>.objects.filter는 일반적으로 하나의 조건을 가져온다고 알고있었는데...
- 아래와 같이 filter(id__in=<리스트>)를 넣으면 두 개 이상의 조건대로 모델에서 데이터를 가져올 수 있다는 사실을 새로 알게 되었다.
#places/views.py
class PlaceListView(APIView):
permission_classes = [IsAdminOrOntherReadOnly]
#맛집 전체 리스트
@swagger_auto_schema(operation_summary="맛집 전체 리스트",
responses={200 : '성공', 500 : '서버 에러'})
#맛집 리스트
def get(self, request, place_id):
place_list = rcm_place(user_id = request.user.id, picked_place_id=place_id)
place = Place.objects.filter(id__in=place_list)
serializer = PlaceSerializer(place, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
'DataScience > 머신러닝' 카테고리의 다른 글
머신러닝 :: 개요, 머신러닝 딥러닝 차이 (0) | 2023.03.20 |
---|---|
머신러닝 :: 머신러닝 추천 서비스 구현_데이터 필터링, 쿼리셋 다루기 _TIL67 (1) | 2022.12.08 |
파이썬 웹 프로그래밍 :: 10월 셋째주 WIL #07 (0) | 2022.10.20 |
머신러닝 :: 딥러닝 MNIST 실습_과일 종류 예측 (0) | 2022.10.14 |
머신러닝 :: 딥러닝 MNIST 실습_캐글 Sign Language 데이터셋(2)_CNN을 이용한 풀이 (0) | 2022.10.14 |
댓글