【问题标题】:Howto Create Recommendations with a Incremental SVD Recommender System如何使用增量 SVD 推荐系统创建推荐
【发布时间】:2012-01-13 08:03:40
【问题描述】:

我正在测试一个根据 Simon Funk 算法构建的推荐系统。 (作者及时开发。http://www.timelydevelopment.com/demos/NetflixPrize.aspx

问题是,所有增量 SVD 算法都试图预测 user_id 和 movie_id 的评分。但在实际系统中,这应该为活动用户生成一个新项目列表。 我看到有些人在 Incremental SVD 之后使用了 kNN,但是如果我没有遗漏一些东西,如果我在通过 Incremental SVD 创建模型之后使用 kNN,我会失去所有的性能提升。

有人对增量 SVD/Simon Funk 方法有任何经验,请告诉我如何生成新推荐项目列表?

【问题讨论】:

    标签: recommendation-engine collaborative-filtering svd knn


    【解决方案1】:

    推荐电影的制作方式:

    1. 列出未看过的电影
    2. 将他们的特征向量乘以用户的特征向量。
    3. 按结果降序排序,并选择排名靠前的电影。

    对于理论:假设只有两个维度(喜剧和戏剧)。如果我喜欢喜剧,但讨厌戏剧,我的特征向量是[1.0, 0.0]。如果您将我与以下电影进行比较:

    Comedy:  [1.0, 0.0] x [1.0, 0.0] = 1
    Dramedy: [0.5, 0.5] x [1.0, 0.0] = 0.5
    Drama:   [0.0, 1.0] x [1.0, 0,0] = 0 
    

    【讨论】:

      【解决方案2】:

      这是一个基于 Yelp Netflix 代码的简单 Python 代码。如果您安装 Numba,它将以 C 速度运行。

      data_loader.py

      import os
      import numpy as np
      from scipy import sparse
      
      class DataLoader:
          def __init__(self):
              pass
      
          @staticmethod
          def create_review_matrix(file_path):
              data = np.array([[int(tok) for tok in line.split('\t')[:3]]
                               for line in open(file_path)])
      
              ij = data[:, :2]
              ij -= 1
              values = data[:, 2]
              review_matrix = sparse.csc_matrix((values, ij.T)).astype(float)
              return review_matrix
      
      movielens_file_path = '%s/Downloads/ml-100k/u1.base' % os.environ['HOME']
      
      my_reviews = DataLoader.create_review_matrix(movielens_file_path)
      
      user_reviews = my_reviews[8]
      user_reviews = user_reviews.toarray().ravel()
      user_rated_movies,  = np.where(user_reviews > 0)
      user_ratings = user_reviews[user_rated_movies]
      
      movie_reviews = my_reviews[:, 201]
      movie_reviews = movie_reviews.toarray().ravel()
      movie_rated_users,  = np.where(movie_reviews > 0)
      movie_ratings = movie_reviews[movie_rated_users]
      
      user_pseudo_average_ratings = {}
      user_pseudo_average_ratings[8] = np.mean(user_ratings)
      user_pseudo_average_ratings[9] = np.mean(user_ratings)
      user_pseudo_average_ratings[10] = np.mean(user_ratings)
      users, movies = my_reviews.nonzero()
      
      users_matrix = np.empty((3, 3))
      users_matrix[:] = 0.1
      
      movies_matrix = np.empty((3, 3))
      movies_matrix[:] = 0.1
      
      result = users_matrix[0] * movies_matrix[0]
      otro = movies_matrix[:, 2]
      otro[2] = 8
      

      funk.py

      # Requires Movielens 100k data 
      import numpy as np, time, sys
      from data_loader import DataLoader
      from numba import jit
      import os
      
      def get_user_ratings(user_id, review_matrix):
          """
          Returns a numpy array with the ratings that user_id has made
      
          :rtype : numpy array
          :param user_id: the id of the user
          :return: a numpy array with the ratings that user_id has made
          """
          user_reviews = review_matrix[user_id]
          user_reviews = user_reviews.toarray().ravel()
          user_rated_movies, = np.where(user_reviews > 0)
          user_ratings = user_reviews[user_rated_movies]
          return user_ratings
      
      def get_movie_ratings(movie_id, review_matrix):
          """
          Returns a numpy array with the ratings that movie_id has received
      
          :rtype : numpy array
          :param movie_id: the id of the movie
          :return: a numpy array with the ratings that movie_id has received
          """
          movie_reviews = review_matrix[:, movie_id]
          movie_reviews = movie_reviews.toarray().ravel()
          movie_rated_users, = np.where(movie_reviews > 0)
          movie_ratings = movie_reviews[movie_rated_users]
          return movie_ratings
      
      def create_user_feature_matrix(review_matrix, NUM_FEATURES, FEATURE_INIT_VALUE):
          """
          Creates a user feature matrix of size NUM_FEATURES X NUM_USERS
          with all cells initialized to FEATURE_INIT_VALUE
      
          :rtype : numpy matrix
          :return: a matrix of size NUM_FEATURES X NUM_USERS
          with all cells initialized to FEATURE_INIT_VALUE
          """
          num_users = review_matrix.shape[0]
          user_feature_matrix = np.empty((NUM_FEATURES, num_users))
          user_feature_matrix[:] = FEATURE_INIT_VALUE
          return user_feature_matrix
      
      def create_movie_feature_matrix(review_matrix, NUM_FEATURES, FEATURE_INIT_VALUE):
          """
          Creates a user feature matrix of size NUM_FEATURES X NUM_MOVIES
          with all cells initialized to FEATURE_INIT_VALUE
      
          :rtype : numpy matrix
          :return: a matrix of size NUM_FEATURES X NUM_MOVIES
          with all cells initialized to FEATURE_INIT_VALUE
          """
          num_movies = review_matrix.shape[1]
          movie_feature_matrix = np.empty((NUM_FEATURES, num_movies))
          movie_feature_matrix[:] = FEATURE_INIT_VALUE
          return movie_feature_matrix
      
      @jit(nopython=True)
      def predict_rating(user_id, movie_id, user_feature_matrix, movie_feature_matrix):
          """
          Makes a prediction of the rating that user_id will give to movie_id if
          he/she sees it
      
          :rtype : float
          :param user_id: the id of the user
          :param movie_id: the id of the movie
          :return: a float in the range [1, 5] with the predicted rating for
          movie_id by user_id
          """
          rating = 1.
          for f in range(user_feature_matrix.shape[0]):
              rating += user_feature_matrix[f, user_id] * movie_feature_matrix[f, movie_id]
      
          # We trim the ratings in case they go above or below the stars range
          if rating > 5: rating = 5
          elif rating < 1: rating = 1
          return rating
      
      @jit(nopython=True)
      def sgd_inner(feature, A_row, A_col, A_data, user_feature_matrix, movie_feature_matrix, NUM_FEATURES):
          K = 0.015
          LEARNING_RATE = 0.001
          squared_error = 0
          for k in range(len(A_data)):
              user_id = A_row[k]
              movie_id = A_col[k]
              rating = A_data[k]
              p = predict_rating(user_id, movie_id, user_feature_matrix, movie_feature_matrix)
              err = rating - p
      
              squared_error += err ** 2
      
              user_feature_value = user_feature_matrix[feature, user_id]
              movie_feature_value = movie_feature_matrix[feature, movie_id]
              #for j in range(NUM_FEATURES):
              user_feature_matrix[feature, user_id] += \
                  LEARNING_RATE * (err * movie_feature_value - K * user_feature_value)
              movie_feature_matrix[feature, movie_id] += \
                  LEARNING_RATE * (err * user_feature_value - K * movie_feature_value)
      
          return squared_error
      
      def calculate_features(A_row, A_col, A_data, user_feature_matrix, movie_feature_matrix, NUM_FEATURES):
          """
          Iterates through all the ratings in search for the best features that
          minimize the error between the predictions and the real ratings.
          This is the main function in Simon Funk SVD algorithm
      
          :rtype : void
          """
          MIN_IMPROVEMENT = 0.0001
          MIN_ITERATIONS = 100
          rmse = 0
          last_rmse = 0
          print len(A_data)
          num_ratings = len(A_data)
          for feature in xrange(NUM_FEATURES):
              iter = 0
              while (iter < MIN_ITERATIONS) or  (rmse < last_rmse - MIN_IMPROVEMENT):
                  last_rmse = rmse
                  squared_error = sgd_inner(feature, A_row, A_col, A_data, user_feature_matrix, movie_feature_matrix, NUM_FEATURES)
                  rmse = (squared_error / num_ratings) ** 0.5
                  iter += 1
              print ('Squared error = %f' % squared_error)
              print ('RMSE = %f' % rmse)
              print ('Feature = %d' % feature)
          return last_rmse
      
      
      LAMBDA = 0.02
      FEATURE_INIT_VALUE = 0.1
      NUM_FEATURES = 20
      
      movielens_file_path = '%s/Downloads/ml-100k/u1.base' % os.environ['HOME']
      
      A = DataLoader.create_review_matrix(movielens_file_path)
      from scipy.io import mmread, mmwrite
      mmwrite('./data/A', A)
      
      user_feature_matrix = create_user_feature_matrix(A, NUM_FEATURES, FEATURE_INIT_VALUE)
      movie_feature_matrix = create_movie_feature_matrix(A, NUM_FEATURES, FEATURE_INIT_VALUE)
      
      users, movies = A.nonzero()
      A = A.tocoo()
      
      rmse = calculate_features(A.row, A.col, A.data, user_feature_matrix, movie_feature_matrix, NUM_FEATURES )
      print 'rmse', rmse
      

      【讨论】:

        【解决方案3】:

        我认为这是一个大问题,因为有很多推荐方法我认为可以称为“增量 SVD”。回答您的具体问题:kNN 在投影项目空间上运行,而不是在原始空间上运行,所以应该很快。

        【讨论】:

        • 实际上我试图通过提到它是Simon Funk的算法并指定相关的c ++源代码来减少空间。如果您熟悉该算法,Funk 使用特征而不是所有矩阵。我最初的想法是,kNN 可以在 users*k 上使用(其中 k 是特征数,比如 2),但这仍然可能会花费一些,想想数百万用户。
        • 我想说的是,假设我在矩阵分解后有 1M*50k 矩阵,创建相似性并为活跃用户推荐新项目将花费:在 1M 用户中为 2 个功能找到余弦相似性,然后在 k 个最相似的用户中找到新项目。这就是我的想法,想知道是否有更好/有效的方法来做到这一点。
        • 是的,这就是我在回答中所描述的:您在缩小(投影)空间中操作。您正在描述基于用户特征矩阵的基于用户的推荐器,这是有效的。
        • 感谢 Sean 的确认。经过快速实现和测试,用户大小:~2K,特征:2..8,对于一个活跃用户,超过 1K 的其他用户的相似度在 0.99 以上。 (使用基于余弦和不同 k 大小)我认为这不正常,因为用户并没有那么相同。使用 Funk 的方法,第一个特征没有得到很好的训练(根据 RMSE),所以我怀疑第一个特征训练不足。有什么想法吗?
        • 你的投影可能有问题,可能会检查原始矩阵的重建质量。如果您没有使用居中余弦度量,那么如果您的数据都是正数且很大,那么您将在 1 附近获得一堆相似度。
        【解决方案4】:

        假设您有 n 个用户和 m 个项目。在增量 SVD 之后,你有 k 个训练好的特征。要获得给定用户的新商品,请将 1xk 用户特征向量和 kxm 商品特征矩阵相乘。您最终得到该用户的每个项目的 m 个评分。然后对它们进行排序,删除他们已经看过的,并显示一些新的。

        【讨论】:

          猜你喜欢
          • 2017-12-07
          • 2014-06-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-19
          • 1970-01-01
          • 2011-09-12
          相关资源
          最近更新 更多