본문 바로가기
카테고리 없음

NLP(4)- 아이템 기반 추천 실습

by 왕방개 2024. 3. 18.

Data: TMDB 데이터를 활용해서 분석

1.데이터 불러오기

import pandas as pd


movies = pd.read_csv("C:\\Users\\User\\Desktop\\데이터\\python_machine_learning-main\\python_machine_learning-main\\data\\movielens\\movies.csv")

ratings =pd.read_csv("C:\\Users\\User\\Desktop\\데이터\\python_machine_learning-main\\python_machine_learning-main\\data\\movielens\\ratings.csv")

print(ratings.info())

print(movies.info())

 

2.데이터 전처리 => ratings에 timestamp 는 데이터 자체를 안씀

ratings = ratings[['userId','movieId','rating']]
ratings.head()

 

3.추천시스템 데이터 형태로 변경

#추천시스템 데이터 형태
#유저 id 별로 상품 id를 펼치거나 상품 id별로  유저 id 가 펼쳐지는 형태
#userId 를 index로 해서 movieId별로 폄점확인할 수 있도록 변경

ratings_matrix = ratings.pivot_table('rating',index='userId',columns='movieId')
ratings_matrix.head()

 

=>컬럼에 데이터 추가

#컬럼이 영화 제목이 아니라 movieId 를 출력 - movieID 를 영화제목으로 변경
ratings_movies = pd.merge(ratings,movies,on='movieId')
ratings_movies.head()
ratings_matrix = ratings_movies.pivot_table('rating',index ='userId',columns ='title')
ratings_matrix.head()

 

=>결측치 0으로 채우기

#결측치를 0으로 채우기

ratings_matrix = ratings_matrix.fillna(0)
ratings_matrix.head()

 

4.코사인 유사도 계산

#코사인 유사도

from sklearn.metrics.pairwise import cosine_similarity

item_sim = cosine_similarity(ratings_matrix_T,ratings_matrix_T)
print(item_sim)
# 컬럼과 인덱스에 영화 제목 붙이기

item_sim_df = pd.DataFrame(data=item_sim, index=ratings_matrix.columns,
                          columns = ratings_matrix.columns)

item_sim_df.head()

 

5.아이템 기반 최근접 이웃 협업 필터링으로 개인화된 영화 추천

=>아이템 기반의 영화 유사도 데이터는 모든 사용자의 평점을 기준으로 영화의 유사도를 생성했고, 이를 이용해서 영화를 추천할 수 는 있지만 이는 개인의 취향을 전혀 반영하지 않음

=>개인화된 영화 추천은 유저가 아직 관람하지 않은 영화를 추천해야 합니다

=>아직 관람하지 않은 영화에 대해서 아이템 유사도 와 기존에 관람한 영화의 평점 데이터를 기반으로 해서 모든 영화의 평점을 예측하고 그 중에서 높은 예측 평점을 가진 영화를 추천

=>계산식: 사용자가 본 영화에 대한 실제 평점과 다른 모든 영화의 코사인 유사도를 내적 곱을 하고 그 값을 전체 합으로 나눔

#사용자 별로 평점을 예측해주는 함수

def predict_ratings(ratings_arr, item_sim_arr):
    ratings_pred = ratings_arr.dot(item_sim_arr)/np.array([np.abs(item_sim_arr).sum(axis=1)])
    return ratings_pred
#예측 평점 확
import numpy as np
ratings_pred = predict_ratings(ratings_matrix.values,item_sim_df.values)
rating_pred_matrix = pd.DataFrame(data=ratings_pred,
                                 index = ratings_matrix.index,
                                 columns = ratings_matrix.columns)

rating_pred_matrix.head()

 

6.추천 수정

=>현재는 모든 영화와의 유사도를 이용해서 평점을 예측했는데 모든 영화보다는 유저가 본 영화 중 유사도가 가장 높은 영화 몇 개를 이용해서 예측하는 것이 더 나을 가능성이 높음

#실제 데이터와 예측한 데이터와의 차이 확인

from sklearn.metrics import mean_squared_error

def get_mse (pred,actual):
    pred = pred[actual.nonzero()].flatten()
    actual = actual[actual.nonzero()].flatten()
    return mean_squared_error(pred,actual)


print("현재 MSE:",get_mse(ratings_pred,ratings_matrix.values))

# 유사도가 높은 영화 와의 유사도 값만을 이용해서 예측함수를 새로 생성
def predict_rating_topsim(ratings_arr, item_sim_arr, n=20):
    # 사용자-아이템 평점 행렬 크기만큼 0으로 채운 예측 행렬 초기화
    pred = np.zeros(ratings_arr.shape)

    # 사용자-아이템 평점 행렬의 열 크기만큼 Loop 수행. 
    for col in range(ratings_arr.shape[1]):
        # 유사도 행렬에서 유사도가 큰 순으로 n개 데이터 행렬의 index 반환
        top_n_items = [np.argsort(item_sim_arr[:, col])[:-n-1:-1]]
        # 개인화된 예측 평점을 계산
        for row in range(ratings_arr.shape[0]):
            pred[row, col] = item_sim_arr[col, :][top_n_items].dot(ratings_arr[row, :][top_n_items].T) 
            pred[row, col] /= np.sum(np.abs(item_sim_arr[col, :][top_n_items]))        
    return pred
ratings_pred = predict_rating_topsim(ratings_matrix.values, item_sim_df.values,n=20)

print("MSE:",get_mse(ratings_pred,ratings_matrix.values))

이전에는 9점대였는데 3점대로 개선됨

아이템이나 유저를 가지고 예측을 할 때 모든 데이터를 사용하는 것 보다는 유사도가 높은 데이터 몇개를 이용해서 예측을 하는 것이 성능이 좋은 경우가 많습니다

=>개선된 데이터로 데이터 프레임 재생성

# 개선된 데이터로 데이터 프레임을 생성
rating_pred_matrix = pd.DataFrame(data=ratings_pred,
                                 index = ratings_matrix.index,
                                 columns = ratings_matrix.columns)

rating_pred_matrix.head()

 

#한명의 유저를 선택해서 예측 평점이 높은 데이터를 확인
user_rating_id = ratings_matrix.loc[9,:]
user_rating_id [user_rating_id >0].sort_values(ascending=False)[:10]

#유저가 보지 않은 영화 목록을 리턴하는 함수
def get_unseen_movies(ratings_matrix, userId):
    # userId로 입력받은 사용자의 모든 영화정보 추출하여 Series로 반환함. 
    # 반환된 user_rating 은 영화명(title)을 index로 가지는 Series 객체임. 
    user_rating = ratings_matrix.loc[userId,:]
    
    # user_rating이 0보다 크면 기존에 관람한 영화임. 대상 index를 추출하여 list 객체로 만듬
    already_seen = user_rating[ user_rating > 0].index.tolist()
    
    # 모든 영화명을 list 객체로 만듬. 
    movies_list = ratings_matrix.columns.tolist()
    
    # list comprehension으로 already_seen에 해당하는 movie는 movies_list에서 제외함. 
    unseen_list = [ movie for movie in movies_list if movie not in already_seen]
    
    return unseen_list

 

#유저가 보지 않은 영화 목록에서 예측 평점이 높은 영화제목을 리턴하는 함수
def recomm_movie_by_userid(pred_df, userId, unseen_list, top_n=10):
    # 예측 평점 DataFrame에서 사용자id index와 unseen_list로 들어온 영화명 컬럼을 추출하여
    # 가장 예측 평점이 높은 순으로 정렬함. 
    recomm_movies = pred_df.loc[userId, unseen_list].sort_values(ascending=False)[:top_n]
    return recomm_movies
# 사용자가 관람하지 않는 영화명 추출   
unseen_list = get_unseen_movies(ratings_matrix, 9)

# 아이템 기반의 인접 이웃 협업 필터링으로 영화 추천 
recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)

# 평점 데이타를 DataFrame으로 생성. 
recomm_movies = pd.DataFrame(data=recomm_movies.values,index=recomm_movies.index,columns=['pred_score'])
recomm_movies

 

7.작업 과정

 

=>각 영화간의 유사도를 측정

 

=>유저가 매긴 평점을 기반으로 해서 유사도가 높은 20개의 영화를 추출해서 그 영화들의 평점을 가지고 보지 않은 영화의 평점을 예측

 

2.행렬 분해

1)개요

=>하나의 행렬을 특정한 구조를 가진 2개 이상의 행렬의 곱으로 표현하는 것

=>선형 방정식의 해를 구하거나 행렬 계산을 효율적으로 수행하기 위해서 사용

데이터 전처리에는 None값을 채우는 용도로 사용하기도 합니다.

 

2)알고리즘

=>SVD (Singular Value Decomposition) -특이값 분해

=>NMF (Non-Negative Matrix Factorization) - 음수 미포함 행렬 분해

=>SGD(Stochastic Gradient Descent) -경사 하강법

=>ALS ( Alternatiing Least Square)

 

3)SVD 

=>N*M 크기의 행렬 A를 3개의 행렬 곱으로 나타내는 것

 

A(M*N) = U(M*N)

 

=>제약 조건

-U와 V는 정방 행령(행과 열의 개수가 같은 행렬)

-U와 V는 직교 행렬(자신의 역행렬 과 곱을 하면 단위 행렬이 만들어지는 행령)

-행렬에 복소수가 포함되어 있더라도 특이값은 복소수나 음수가 될 수 없음

from numpy.linalg import svd

A = np.array([[3,-1],[1,3],[1,0]])

#세번째 행렬은 전치된 상태로 나오기 때문에 일반적으로 변수명을 ㅁ나들 떄  T를 추가

U , S, VT = svd(A)

print(U) #행이 3개이므로 3*3 행령
print(VT) #열이 2개이므로 2*2 행령
print(S) # 2개의 값

 

#print(U @ S @ VT)

#행렬의 곱을 해서 원본 복원하고자 하는 경우는 S 를 U와 연산을 할 수 있는 구조로 변환

temp = np.diag(S,1)
print(temp)
temp = np.diag(S,1)[:,1:]
print(U@temp@VT)

원래 데이터로 복원할려고 할 떄는 0인 자리의 값이 채워지는 형태로 복원이 됩니다.

 

4)NMF - 음수 미포함 행렬 분해

=>음수를 포함하지 않는 행렬 V 를 음수를 포함하지 않는 행렬 W와 H의 곱으로 분해

 

X= WH

W 는 X 와 동일한 모양으로 생성

H 는 열의 개수 만큼의 행을 가지게 되고 데이터의 차원만큼 열을 소유

 

=>W는 가중치 행렬(Weight Matrix)  이라고 하고 H는 특성 행렬( Feature Matrix ) 이라고 합니다.

=>W는 카테고리에 특성의 관계를 나타내는 것이고, 특성 행렬은 원래의 특성에 대비한 새로운 특성에 대한 관계를 나타냄

=>이걸 활용해 차원 축소나 새로운 차원을 생성할 때도 이용

 

5)SGD -경사 하강법

=>GPU를 사용하는 것이 가능하고 결측치에 상관없이 사용 가능

=>이전 2가지 방법은 결측치가 있으면 0으로 채워서 수행해야 합니다

 

6)ALS(Alternating Least Square) 

=>경사 하강법은 2개의 동시에 변경하면서 최적의 값을 찾아가지만 ALS 는 하나의 행렬은 고정하고 다른 하나의 행렬만 변경하면 최적의 값을 찾아가는 방식

 

=>결측치가 있으면 안되기 떄문에 결측치가 있으면 0으로 채워서 사용해야 합니다

 

7)사용

=>행렬 분해에는 SVD가 자주 사용되지만 사용자 - 평점 데이터를 가지고 행렬 분해를 하고자 하는 경우에는 None값이 너무 많기 때문에 SGD 나 ALS 를 주로 이용

 

=>행렬 분해를 이용해서 시청하지 않은 또는 구매하지 않은 상품에 대한 점수를 예측하는 방식을 잠재 요인 협업 추천이라고 합니다.

넷플릭스가 영화 추천에 사용하면서 유명해진 방식

 

=>행렬 분해 알고리즘을 이용해서 추천 시스템을 구축해주는 패키지중의 하나는 scikit-surprise 가 있고 그 이외에 딥러닝을 이용하는 방식들도 있습니다

- scikit-surprise 패키ㄴ지는 Dataset 클래스를 이용해서만 데이터 로딩이 가능하고 데이터가 반드시 userid, itemid, rating의 형태를 갖는 row 로 구성되어 있어야 합니다

일반 파일로 만들어진 데이터가 있다면 row 형태를 맞추고 surprise.Reader 라는 클래스를 이용해서 Dataset 의 형태로 변경을 해야 가능

 

=>요즘은 이방식들을 잘 사용하지 않고 lightFM 같은 딥러닝 모델들을 이용하는 경우가 많dma 

추천시스템을 만들고자 하는 경우는 여러 모델을 사용해보는 것이 좋습니다.

일반적으로 추천 시스템은 평가를 하기가 어렵기 때문에 여러 모델을 사용해서 추천 목록을 만드는 것이 좋습니다.