본문 바로가기
주홍의 프로젝트/연습 프로젝트

사용자기반 협업 필터링 추천시스템 - 애니메이션 추천시스템

by orangecode 2022. 6. 22.
728x90

 

사용자기반 협업 필터링(user_based collaborative filtering)
콘텐츠 기반 협업 필터링(content_based collaborative filtering)

사용자기반 협업 필터링(userbased collaborative filltering)

1. 협업 필터링: 사람들의 행동 기록을 분석하다
‘협업 필터링’이란 특정 집단에서 발생하는 ‘유사한 사용행동’을 파악하여, 비슷한 성향의 사람들에게 아이템을 추천하는 기술이다. 협업 필터링은 성향이 비슷하면, 선호하는 것도 비슷할 것이라는 가정을 전제로 한다. 협업 필터링은 사용자 기반 협업 필터링(User-based CF), 아이템 기반 협업 필터링(Item-based CF)으로 구분된다.

사용자 기반 협업 필터링
나와 성향이 비슷한 사람들이 사용한 아이템을 추천해 주는 방식이다. 예를 들어, 사용자 A가 온라인 몰에서 선크림과 튜브, 그리고 수영복 함께 구매하고, 또 다른 사용자 B는 선크림과 튜브를 구매했다고 가정해 보자. 알고리즘은 구매 목록이 겹치는 이 두 사용자가 유사하다고 판단해, 사용자 B에게 수영복을 추천한다.

 

 

사용데이터 : Anime Recommendations Database from kaggle

 

 

kaggle에서 애니메이션 추천을 위한 데이터셋을 활용하여 추천시스템을 제작하였습니다.

 

- anime.csv

  • anime_id - myanimelist.net's unique id identifying an anime.
  • name - full name of anime.
  • genre - comma separated list of genres for this anime.

type - movie, TV, OVA, etc.

  • episodes - how many episodes in this show. (1 if movie).
  • rating - average rating out of 10 for this anime.
  • members - number of community members

- rating.csv

  • user_id - non identifiable randomly generated user id.
  • anime_id - the anime that this user has rated.
  • rating - rating out of 10 this user has assigned (-1 if the user watched it but didn't assign a rating).

 

data url:

https://www.kaggle.com/datasets/CooperUnion/anime-recommendations-database?select=rating.csv 

 

Anime Recommendations Database

Recommendation data from 76,000 users at myanimelist.net

www.kaggle.com

 

사용자기반 애니메이션 추천 시스템

1. 라이브러리 설정

# 라이브러리 설정
import random
import pandas as pd
import statistics
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.metrics.pairwise import cosine_similarity
import operator

 

2. 데이터 불러오기&확인&시각화

 

# 데이터 불러오기

anime = pd.read_csv('/content/drive/MyDrive/anime/anime.csv')
rating = pd.read_csv('/content/drive/MyDrive/anime/rating.csv')

anime.shape
rating.shape

 

 - anime data

anime 데이터에는 anime_id, 제목, 장르, 시청 타입, 에피소드 개수, 평균 평점, 시청자수가 기록되어 있습니다.

 

- rating data

사용자 평가 데이터는 사용자가 평가한 anime_id, rating 점수를 보여주고 있습니다.

 

rating 점수는 -1 ~ 10까지 나타낼 수 있습니다.

- '-1' : 유저가 시청했으나 점수를 부여하지 않은 경우

- '0~10'은 : 유저가 부여한 점수

 

 

 

 

콘텐츠 평가 유저 수/평가된 콘텐츠 수 확인하기&시각화

# 콘텐츠를 평가한 유저 숫자
rating_per_user = rating.groupby('user_id')['rating'].count()
statistics.mean(rating_per_user.tolist())

# 유저별 평점 개수의 분포
rating_per_user.hist(bins=20, range=(0,1000))

rating 데이터의 평가데이터 중 'rating' 데이터에 중복되는 'user_id'를 묶어 숫자를 세어 줍니다.

-> 각 유저가 평가한 콘텐츠 개수

 

 

# 콘텐츠별 평가된 평점 등급
ratings_per_anime = rating.groupby('anime_id')['rating'].count()
statistics.mean(ratings_per_anime.tolist())

# 콘텐츠별 평가된 빈도
ratings_per_anime.hist(bins=30, range=(0,5000))

rating 데이터의 평가데이터 중 'rating' 데이터에 중복되는 'anime_id'를 묶어 숫자를 세어 줍니다.

-> 각 콘텐츠가 받은 평점의 개수

 

 

 

 

3. 데이터 전처리(결측치 제거 / 유효값 필터링 / 점수 '-1' 제거) - rating

3-1. 점수 '-1' 제거하기 : 공정한 평가 방해(개인적인 생각)

# rating 평점 '-1' -> '0'으로 변경
rating = rating.replace({'rating':-1},0)
rating

# rating 평점 0으로 변경할 경우, 보지 않은 콘텐츠로 처리됨
# 시청한 콘텐츠의 경우 평가하지 않았다면 중간값인 5점으로 처리
rating = rating.replace({'rating':-1},5)
rating

replace() 함수를 이용해 replace({'원하는 열' : 기존 값}, 변경할 값)으로 

'-1' 평점을 '0'으로 변경합니다.

 

'-1'은 콘텐츠는 시청하였으나 평점을 리뷰하지 않은 콘텐츠에게 부여되는 점수입니다.

 

특정유저에게 보지 않은 콘텐츠를 추천할 경우, Rating = 0 값을 보지 않은 콘텐츠로 필터링함.

- > 시청한 콘텐츠를 평가하지 않을 경우 중간값인 5점으로 처리한다.

 

개인적인 생각 -> 5점

제작자 -> -1

 

 

3-2. 유효값 필터링

 - 너무 적게 평가된 콘텐츠 제거

# 너무 적게 평가된 콘텐츠 제거
ratings_per_anime_df = pd.DataFrame(ratings_per_anime)

# 평점 개수가 50개 이하 콘텐츠 제거
filtered_ratings_per_anime_df = ratings_per_anime_df[ratings_per_anime_df.rating >= 50]
print(filtered_ratings_per_anime_df)

# df -> list
popular_anime = filtered_ratings_per_anime_df.index.tolist()

총 콘텐츠 11200개 중 평점개수가 50개 이하인 콘텐츠는 데이터 유효성이 떨어진다고 생각하여 제거합니다.

 

콘텐츠 개수 11200개 -> 5651개

 

 why?

콘텐츠 1개당 평점의 개수평균은 697.3번입니다.

평점 데이터가 적은 콘텐츠는 유사한 user를 탐색하는데 정확도가 떨어질 수 있고, 엉뚱한 콘텐츠를 추천할 수 도 있기 때문입니다.

 

 

 

 

 - 너무 적게 평가한 유저 제거

# 너무 적게 평가한 유저 제거
rating_per_user_df = pd.DataFrame(rating_per_user)

# 평점 데이터 30개 이하일 경우 제거
filtered_rating_per_user_df = rating_per_user_df[rating_per_user_df.rating >= 30]
rating_users = filtered_rating_per_user_df.index.tolist()

총 73515명의 유저 중 평가를 50개 이하인 유저는 데이터 유효성이 떨어진다고 판단하여 제거합니다.

유저 73515명 -> 48366명

필터링된 평가된 콘텐츠와 평점의 개수는 총 7513601개 입니다.

 

 

 

 

 

4. 피벗테이블 만들기

# 피벗테이블 만들기
rating_matrix = filtered_rating.pivot_table(index='user_id', columns='anime_id', values='rating')

# 결측치 제거
rating_matrix = rating_matrix.fillna(0)
rating_matrix.shape

anime_id = columns

user_id = index

values = rating data

 

user별 콘텐츠를 평가한 평점 데이터에 대한 pivot table을 제작합니다.

 

5. 비슷한 성향의 유저 찾기 - 코사인 유사도

def similar_users(user_id, matrix, k=5):
    # 현재 유저에 대한 데이터프레임 만들기
    # matrix의 index = user_id -> 현재 1명 유저에 대한 평가 정보 찾기
    user = matrix[matrix.index == user_id]
    
    # matrix index 값이 user_id와 다른가?
    # 일치하지 않는 값들은 other_users
    other_users = matrix[matrix.index != user_id]
    
    # 대상 user, 다른 유저와의 cosine 유사도 계산 
    # list 변환
    similarities = cosine_similarity(user,other_users)[0].tolist()
    
    # 다른 사용자의 인덱스 목록 생성
    other_users_list = other_users.index.tolist()
    
    # 인덱스/유사도로 이뤄진 딕셔너리 생성
    # dict(zip()) -> {'other_users_list1': similarities, 'other_users_list2': similarities}
    user_similarity = dict(zip(other_users_list, similarities))
    
    # 딕셔너리 정렬
    # key=operator.itemgetter(1) -> 오름차순 정렬 -> reverse -> 내림차순
    user_similarity_sorted = sorted(user_similarity.items(), key=operator.itemgetter(1))
    user_similarity_sorted.reverse()
    
    # 가장 높은 유사도 k개 정렬하기
    top_users_similarities = user_similarity_sorted[:k]
    users = [i[0] for i in top_users_similarities]
    
    return users
    # 현재 유저에 대한 정보 찾기
    user = matrix[matrix.index == user_id]

위에서 제작했던 pivot table의 index(=user_id)와 불러온 user_id가 일치하는지 확인합니다.

    # matrix index 값이 user_id와 다른가?
    other_users = matrix[matrix.index != user_id]

pivot table index와 일치하지 않는 값들은 other users로 지정합니다.

    # 대상 user, 다른 유저와의 cosine 유사도 계산 
    # list 변환
    similarities = cosine_similarity(user,other_users)[0].tolist()
from sklearn.metrics.pairwise import cosine_similarity

cosine_similarity()함수로 대상유저와 다른 유저들 사이의 cosine similarity를 계산합니다.

 

유저의 유사도는 list로 변환시킵니다.

 

    # 다른 사용자의 인덱스 목록 생성
    other_users_list = other_users.index.tolist()
    
    # 인덱스/유사도로 이뤄진 딕셔너리 생성
    # dict(zip()) -> {'other_users_list1': similarities, 'other_users_list2': similarities}
    user_similarity = dict(zip(other_users_list, similarities))

other_users.index(대상 사용자를 제외한 모든 사용자)의 lilst 를 만들고

 

dict(zip())함수를 이용하여 dictionary에 other_user마다 similarity값을 넣습니다.

- other_users = key 값

- similarity = values 값 

    # dict(zip()) -> {'other_users_list1': similarities, 'other_users_list2': similarities}

 

    # 딕셔너리 정렬
    # key=operator.itemgetter(1) -> 오름차순 정렬 -> reverse -> 내림차순
    user_similarity_sorted = sorted(user_similarity.items(), key=operator.itemgetter(1))
    user_similarity_sorted.reverse()
    
    # 가장 높은 유사도 k개 정렬하기
    top_users_similarities = user_similarity_sorted[:k]
    users = [i[0] for i in top_users_similarities]

sorted(user_similarity.items(), key=operator.itemgetter(1))구문은

user_similarity.items()로 dictionary의 key, values 값을 모두 가져오고, 불러오는 기준을 value값을 기준으로 오름차순 정렬하여 가져옵니다. -> 이후 reverse 함수로 내림차순으로 바꾸어 줍니다.

 

가장 높은 유사도 k(지정된 parameter)만큼 가장 높은 유사도를 추출하여 list화 합니다. 

 

for반복문을 사용하여 top_user_similarites를 반복하고 user 리스트로 만들어 return 합니다.

 

 

 

 

 

6. 콘텐츠 추천하기

def recommend_item(user_index, similar_user_indices, matrix, items=10):
    # 유저와 비슷한 유저 가져오기
    similar_users = matrix[matrix.index.isin(similar_user_indices)]
    # 비슷한 유저 평균 계산 # row 계산
    similar_users = similar_users.mean(axis=0)
    # dataframe 변환 : 정렬/필터링 용이
    similar_users_df = pd.DataFrame(similar_users, columns=['user_similarity'])

    # 현재 사용자의 벡터 가져오기 : matrix = rating_matrix(pivot table)
    user_df = matrix[matrix.index == user_index]

    # 현재 사용자의 평가 데이터 정렬
    user_df_transposed = user_df.transpose()

    # 컬럼명 변경 48432 -> rating
    user_df_transposed.columns = ['rating']

    # 미시청 콘텐츠는 rating 0로 바꾸어 준다. remove any rows without a 0 value. Anime not watched yet
    user_df_transposed = user_df_transposed[user_df_transposed['rating']==0]

    # 미시청 콘텐츠 목록리스트 만들기
    animes_unseen = user_df_transposed.index.tolist()

    # 안본 콘텐츠 필터링
    similar_users_df_filtered = similar_users_df[similar_users_df.index.isin(animes_unseen)]

    # 평균값을 기준으로 내림차순 정렬
    similar_users_df_ordered = similar_users_df_filtered.sort_values(by=['user_similarity'], ascending=False)

    # 상위 10개 값 가져오기
    # items = 10
    top_n_anime = similar_users_df_ordered.head(items)
    top_n_anime_indices = top_n_anime.index.tolist()

    # anime dataframe에서 top10값 찾기
    anime_information = anime[anime['anime_id'].isin(top_n_anime_indices)]
    anime_information
    
    return anime_information #items

 

    # 유저와 비슷한 유저 가져오기
    similar_users = matrix[matrix.index.isin(similar_user_indices)]
    # 비슷한 유저 평균 계산 # row 계산
    similar_users = similar_users.mean(axis=0)
    # dataframe 변환 : 정렬/필터링 용이
    similar_users_df = pd.DataFrame(similar_users, columns=['user_similarity'])

matrix = pivot_table

pivot_table에서 similar_user_indices(유사 유저) 를 가져와  평균을 계산합니다.

 

similar users를 dataFrame으로 만들어 정렬/필터링이 쉽도록 변경해줍니다.

 

 # 현재 사용자의 벡터 가져오기 : matrix = rating_matrix(pivot table)
    user_df = matrix[matrix.index == user_index]

    # 현재 사용자의 평가 데이터 정렬
    user_df_transposed = user_df.transpose()

    # 컬럼명 변경 48432 -> rating
    user_df_transposed.columns = ['rating']

    # 미시청 콘텐츠만 가져오기, 시청 콘텐츠 제거
    user_df_transposed = user_df_transposed[user_df_transposed['rating']==0]

    # 미시청 콘텐츠 목록리스트 만들기
    animes_unseen = user_df_transposed.index.tolist()

    # 안본 콘텐츠 필터링
    similar_users_df_filtered = similar_users_df[similar_users_df.index.isin(animes_unseen)]

현재 추천할 사용자의 이름이 pivot_table.index와 일치하는 지 확인합니다.

 

user_df를 transpose()하여 열/행을 바꿔주고 columns를 rating으로 변경하여 user의 평점 데이터를 나타냅니다.

 

미시청 콘텐츠의 평점은 0 이므로 rating == 0인 콘텐츠들이 시청하지 않은 콘텐츠이므로, 미시청 콘텐츠만 가져와 list로 만든다.

 

미시청 콘텐츠만 있음을 isin()함수로 확인하여 similar_users_df_filtered를 만든다.

 

 

    # 평균값을 기준으로 내림차순 정렬
    similar_users_df_ordered = similar_users_df_filtered.sort_values(by=['user_similarity'], ascending=False)
    
    # 상위 10개 값 가져오기
    # items = 10
    top_n_anime = similar_users_df_ordered.head(items)
    top_n_anime_indices = top_n_anime.index.tolist()

    # anime dataframe에서 top10값 찾기
    anime_information = anime[anime['anime_id'].isin(top_n_anime_indices)]
    anime_information
    
    return anime_information #items

user_similarity를 기준으로 내림차순으로 재정렬한다.

 

items(함수 parameter) = 10으로 head(10)개로 상위 10개 값을 가져온다.

내림차순으로 정렬된 유사도는 head(10)으로 가져오면 바로 상위 10개의 값으로 뽑아낼 수 있다.

 

마지막으로 상위 10개의 유사한 콘텐츠가 anime data에 있음을 확인한뒤

추천콘텐츠 top10의 정보를 return 한다.

 

# 추천 콘텐츠 뽑아내기
recommend_content = recommend_item(random_user_id, similar_user_indices, rating_matrix)

print("-- 콘텐츠 추천 TOP 10 --")

# 모든 추천
print(recommend_content)
print("===================================")

# movie_id만 뽑기
print("-- ID --")
print(recommend_content['anime_id'])
print("===================================")

# name만 뽑기
print("-- 제목 --")
print(recommend_content['name'])
print("===================================")

# rating만 뽑기
print("-- 평점 --")
print(recommend_content['name'])
print("===================================")

728x90


참조

코딩월드뉴스(https://www.codingworldnews.com)

https://towardsdatascience.com/build-a-user-based-collaborative-filtering-recommendation-engine-for-anime-92d35921f304

 

Build a user-based collaborative filtering recommendation engine for Anime

Make recommendations based on statistical similarity between users

towardsdatascience.com

 

반응형

댓글