인프런 커뮤니티 질문&답변

nathan님의 프로필 이미지
nathan

작성한 질문수

Python을 이용한 개인화 추천시스템 | 추천알고리즘 | 추천인공지능

4장 SGD를 사용한 MF 기본 알고리즘 오류

해결된 질문

작성

·

327

·

수정됨

0

안녕하세요

좋은 강의 감사합니다.

 

4장 SGD를 사용한 MF 기본 알고리즘 오류 에서

pivot 테이블을 만드는 과정에서 오류가 생겨 질문드립니다.

아래와 같은 오류가 생기는데 어떤 이유인지요? 코드가 잘못된 것 같지는 않습니다.

pivot 대신 pivot_table 을 쓰니 또 되네요. pivot_table 은 값이 중복인 경우에 첫 값을 쓴다고 되어있는데 올바른 해결책이 맞나요?

 

R_temp = ratings.pivot(index = 'user_id',
                       columns = 'movie_id',
                       values = 'rating').fillna(0)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-10-bcd5f752ec01> in <cell line: 1>()
----> 1 R_temp = ratings.pivot(index = 'user_id',
      2                        columns = 'movie_id',
      3                        values = 'rating').fillna(0)

7 frames
/usr/local/lib/python3.10/dist-packages/pandas/core/reshape/reshape.py in _make_selectors(self)
    187 
    188         if mask.sum() < len(self.index):
--> 189             raise ValueError("Index contains duplicate entries, cannot reshape")
    190 
    191         self.group_index = comp_index

ValueError: Index contains duplicate entries, cannot reshape

 

추가로 질문드립니다. pivot_table을 사용해도 rmse가 계속 nan이 나오네요

저는 코드가 잘못된 부분을 찾기가 힘든데 도움 부탁드립니다.

prediction = self.b + self.b_u[i] + self.b_d[j] + self.P[i, :].dot(self.Q[j, :].T) 으로 바꾸어도 같은 오류가 납니다.

class MF():
    def __init__(self, ratings, hyper_params):
        self.R = np.array(ratings)
        self.num_users, self.num_items = np.shape(self.R)
        self.K = hyper_params['K'] # feature의 개수
        self.alpha = hyper_params['alpha'] # learning rate
        self.beta = hyper_params['beta'] # regularization coef
        self.iterations = hyper_params['iterations'] # SGD 반복 횟수
        self.verbose = hyper_params['verbose'] # 출력 여부

    def rmse(self):
        xs, ys = self.R.nonzero() # R에서 0이 아닌 요소의 index
        self.predictions = []
        self.errors = []
        
        for x,y in zip(xs, ys):
            prediction = self.get_prediction(x,y) # 평점 예측치 함수
            self.predictions.append(prediction)
            self.errors.append(self.R[x,y] - prediction)
        self.predictions = np.array(self.predictions)
        self.errors = np.array(self.errors)

        return np.sqrt(np.mean(self.errors**2))

    def train(self):
        # P, Q 의 값 초기화
        self.P = np.random.normal(scale = 1. / self.K, # 표준편차 설정, mean은 설정 안하면 자동으로 0
                                  size = (self.num_users, self.K))
        self.Q = np.random.normal(scale = 1. / self.K, 
                                  size = (self.num_users, self.K))

        self.b_u = np.zeros(self.num_users) # 사용자 평가경향 초기화
        self.b_d = np.zeros(self.num_items)
        self.b = np.mean(self.R[self.R.nonzero()])  # 평점의 전체 평균, 0이 아닌 것에 대해서만 평균을 낸다.

        rows, columns = self.R.nonzero() # 평점이 있는 요소들의 idx만 가져오겠다는 뜻 --> 나중에 SGD를 적용하기 위해서
        self.samples = [(i,j, self.R[i,j]) for i,j in zip(rows, columns)]

        training_process = [] # SGD 실행될때마다 RMSE 기록
        for i in range(self.iterations):
            np.random.shuffle(self.samples) # 다양한 시작점 지정
            self.sgd()
            rmse = self.rmse()
            training_process.append((i+1, rmse))
            if self.verbose:
                if (i+1) % 10 == 0:
                    print('Iteration : %d ; train RMSE = %.4f'%(i+1, rmse))

        return training_process

    def get_prediction(self, i,j): # i 유저의 j 번째 아이템에 대한 예측치
        # 전체 평점 + 유저의 경향성 + 아이템의 경향성 + i번째 유저의 j번째 아이템에 대한 예측
        prediction = self.b + self.b_u[i] + self.b_d[j] + self.P[i, :].dot(self.Q[j, ].T)
        return prediction

    def sgd(self):
        for i,j,r in self.samples: # i,j는 인덱스, r은 평점
            prediction = self.get_prediction(i,j)
            e = (r-prediction) # 오차

            # 사용자의 평가경향 업데이트, 4장 슬라이드 10페이지 식
            self.b_u[i] += self.alpha * (e - (self.beta * self.b_u[i]))
            self.b_d[j] += self.alpha * (e - (self.beta * self.b_d[j]))

            self.P[i,:] += self.alpha * ((e * self.Q[j,:]) - (self.beta * self.P[i,:]))
            self.Q[j,:] += self.alpha * ((e * self.P[i,:]) - (self.beta * self.Q[j,:]))

# 원코드는 pivot_table 대신 pivot 사용
R_temp = ratings.pivot_table(index = 'user_id',
                       columns = 'movie_id',
                       values = 'rating').fillna(0)
hyper_params = {
    'K':30,
    'alpha' : 0.001,
    'beta' : 0.02,
    'iterations' : 100,
    'verbose' : True
}

mf = MF(R_temp, hyper_params)

train_process = mf.train()

 

get_prediction 함수에서 아래를 해보니까, 처음에는 조금 숫자가 나오다가 계속 nan이 나오는데 여기에서 문제가 있는것 같습니다. 근데 원인을 모르겠어요;

        print(self.b)
        print(self.b_u[i])
        print(self.b_d[j])
        print(self.P[i, :].dot(self.Q[j, ].T))

 

답변 1

0

거친코딩님의 프로필 이미지
거친코딩
지식공유자

안녕하세요.

일단 전달주신 부분으로만으로 확인했을 때는 크게 오류가 없어보이는데,

강의에 대한 원본 코드를 드려보겠습니다.

 

혹시 전달 드린 코드 보시고,

코드 오류가 없는지 확인 부탁드려도 괜찮을까요 ? :)

 

import os
import numpy as np
import pandas as pd

base_src = 'drive/MyDrive/RecoSys/Data'
u_data_src = os.path.join(base_src,'u.data')
r_cols = ['user_id','movie_id','rating','timestamp']
ratings = pd.read_csv(u_data_src,
        sep = '\t',
        names = r_cols,
        encoding='latin-1')
# timestamp 제거 
ratings = ratings[['user_id','movie_id','rating']].astype(int)


# MF class
# MF라는 이름의 클래스는 MF를 위한 데이터와 메소드를 가진 클래스이다.
class MF():
  # 클래스가 생성될 때 실행되는 초기화 함수이다.
  def __init__(self,ratings,hyper_params):
    # DataFrame 형식으로 전달된 평점(ratings)을 numpy array 형태로 바꿔서 클래스 변수인 self.R에 저장
    self.R = np.array(ratings)
    # 사용자 수(num_users)와 아이템 수(num_iterms)를 받아온다.
    self.num_users,self.num_items = np.shape(self.R)
    # 아래는 MF weight 조절을 위한 하이퍼파라미터이다.
    # K : 잠재요인(latent factor)의 수
    self.K = hyper_params['K']
    # alpha : 학습률
    self.alpha = hyper_params['alpha']
    # beta : 정규화 계수
    self.beta = hyper_params['beta']
    # iterations : SGD의 계산을 할 때의 반복 횟수
    self.iterations = hyper_params['iterations']
    # verbose : SGD의 학습 과정을 중간중간에 출력할 것인지에 대한 여부
    self.verbose = hyper_params['verbose']

  # 현재의 P,Q를 가지고 Root Mean Squared Error(RMSE) 계산하는 함수이다.
  def rmse(self):
    # self.R에서 평점이 있는(0이 아닌) 요소의 인덱스를 가져온다.
    xs, ys = self.R.nonzero()
    # prediction과 error를 담을 리스트 변수 초기화
    self.predictions = []
    self.errors = []
    # 평점이 있는 요소(사용자 x, 아이템 y) 각각에 대해서 아래의 코드를 실행한다.
    for x,y in zip(xs,ys):
      # 사용자 x, 아이템 y에 대해서 평점 예측치를 get_prediction() 함수를 사용해서 계산한다.
      prediction = self.get_prediction(x,y)
      # 예측값을 예측값 리스트에 추가한다.
      self.predictions.append(prediction)
      # 실제값(R)과 예측값의 차이(errors) 계산해서 오차값 리스트에 추가한다.
      self.errors.append(self.R[x,y] - prediction)
    # 예측값 리스트와 오차값 리스트를 numpy array형태로 변환한다.
    self.predictions = np.array(self.predictions)
    self.errors = np.array(self.errors)
    # error를 활용해서 RMSE 도출
    return np.sqrt(np.mean(self.errors**2))

  # 정해진 반복 횟수만큼 P(사용자 요인), Q(아이템 요인), bu(사용자 평가경향),bd(아이템 평가경향)을 업데이트하는 함수
  def train(self):
    # Initializing user-feature and movie-feature matrix
    # P행렬 정규분포(평균=0, 표준편차=1/K)로 난수 초기화
    self.P = np.random.normal(scale=1./self.K,
                              size=(self.num_users,self.K))
    # Q행렬 정규분포(평균=0, 표준편차=1/K)로 난수 초기화
    self.Q = np.random.normal(scale=1./self.K,
                              size=(self.num_items,self.K))
   
    # Initializing the bias terms
    # 사용자 평가경향 0으로 초기화
    self.b_u = np.zeros(self.num_users)
    # 아이템 평가경향 0으로 초기화
    self.b_d = np.zeros(self.num_items)
    # 평점의 전체 평균으로 b로 할당
    self.b = np.mean(self.R[self.R.nonzero()])

    # List of training samples
    # 평점행렬 R 중에서 평점이 있는 요소의 인덱스를 가져온다.
    rows, columns = self.R.nonzero()
    # SGD를 적용할 대상, 즉 평점이 있는 요소의 인덱스와 평점을 리스트로 만들어서 samples에 저장한다.
    self.samples = [(i,j,self.R[i,j]) for i,j in zip(rows,columns)]

    # Stochastic Gradient Descent for given number of iterations
    # training_process 리스트에 SGD가 한 번 실행될 때마다 RMSE가 얼마나 개선되는지 기록
    training_process = []
    for i in range(self.iterations):
      # 기계학습 알고리즘과 비슷하게 SGD를 어디서 시작하느냐에 따라서 수렴의 속도가 달라질 수 있기 때문에
      # 매 반복마다 다양한 시작점에서 출발하기 위해 samples를 임의로 섞는다.
      np.random.shuffle(self.samples)
      # SGD를 실행하는 함수 호출
      self.sgd()
      # SGD로 P,Q,bu,bd가 업데이트되었으므로 이에 따른 새로운 RMSE를 계산
      rmse = self.rmse()
      # 결과 저장
      training_process.append((i+1,rmse))
      # verbose가 True일 때 10회 반복마다 중간 결과 출력
      if self.verbose:
        if (i+1) % 10 == 0:
          print("Iteration : %d ; Train RMSE = %.4f"%(i+1,rmse))
    # 최종 결과 반환
    return training_process
 
 # Rating prediction for user i and item j
 # 평점 예측값을 구하는 함수이다.
  def get_prediction(self,i,j):
    # 사용자 i, 아이템 j에 대한 평점 예측치를 앞에서 배웠던 식을 이용해서 구한다.
    prediction = self.b + self.b_u[i] + self.b_d[j] + self.P[i,:].dot(self.Q[j,:].T)
    return prediction
 
  # Stochastic gradient descent to get optimized P and Q matrix
  # 실제로 최적의 P, Q, b_u, b_d를 구하기 위한 SGD 실행 함수
  def sgd(self):
    for i,j,r in self.samples:
      # 사용자 i, 아이템 j에 대한 평점 예측치 계산
      prediction = self.get_prediction(i,j)
      # 실제 평점과 비교한 오차 계산
      e = (r - prediction)

      # 사용자 평가 경향 계산 및 업데이트
      self.b_u[i] += self.alpha * (e - (self.beta * self.b_u[i]))
      # 아이템 평가 경향 계산 및 업데이트
      self.b_d[j] += self.alpha * (e - (self.beta * self.b_d[j]))

      # P 행렬 계산 및 업데이트
      self.P[i,:] += self.alpha * ((e * self.Q[j,:]) - (self.beta * self.P[i,:]))
      # Q 행렬 계산 및 업데이트
      self.Q[j,:] += self.alpha * ((e * self.P[i,:]) - (self.beta * self.Q[j,:]))
 

# 전체 데이터 사용 MF
# DataFrame 형식으로 된 ratings 데이터를 full matrix로 변환
R_temp = ratings.pivot(index='user_id',
                       columns='movie_id',
                       values='rating').fillna(0)

# 모델 MF 계산에 필요한 하이퍼파라미터 정의                       
hyper_params = {
    "K":30,
    "alpha":0.001,
    "beta":0.02,
    "iterations":100,
    "verbose":True
}
# MF 클래스 생성
mf = MF(R_temp,hyper_params)
# MF 모델 학습
train_process = mf.train()
nathan님의 프로필 이미지
nathan

작성한 질문수

질문하기