zoukankan      html  css  js  c++  java
  • 推荐系统(五)

    (原创文章,转载请注明出处!)

    协同过滤算法(Collaborative Filtering)基于的根据是,类似的人会喜好类似的物品。如果将用户进行聚类,当来了新用户,将新用户归到相应的类,用类的评分结果来形成新用户的推荐结果。在文章 Collaborative Filtering Based on Iterative Principal Component Analysis 中给出了一种使用主成分分解技术对评分数据进行分解,然后对用户进行聚类的方法,算法的流程如下:

    假设有M个用户对n个物品进行了评价,形成了 M x n的评分矩阵A。

    1. 对矩阵A中的缺失值aij(用户没有对相应的物品进行评价)进行填充:使用i行和j列的平均值。

    2. 对矩阵A进行均值normalization :将每个评价减去其对应的列(物品)平均值。

    3. 对矩阵A进行SVD分解 :A = U∑VT,选择前k个最大的奇异值,通过UkkVkT = A',得到原矩阵A的近似矩阵A'

    4. 将矩阵A中最初的缺失值,使用A'中相应位置的值替代

    5. 重复2、3、4直到收敛。收敛条件:相邻两次(l-1与l)迭代的缺失值的估计值的平方和之差与第(l-1)次的缺失值的估计值的平方和的比值小于某个常数(比如:10-10

    (对最终的评分还需要进行均值normalization的逆操作,即:加上物品平均值)

    通过以上的过程就将已有用户缺失的评分值预测出来。此外还通过上述的步骤的到主成分:Ukk,一个M x k的矩阵,可以将这个矩阵作为训练数据使用K均值聚类来对用户进行聚类(也可以采用其他的聚类方法,如:RRC

    聚类的类别数量选择条件:|Sk - Sk+1| / S2≤ ε , ε是给定的常数,比如:0.001

                                    Sk是k类的情况下,聚类完成后,各个样本点到各自的聚类中心的距离平方和

                                    Sk+1是k+1类的情况下,聚类完成后,各个样本点到各自的聚类中心的距离平方和

                                    S2是2类的情况下,聚类完成后,各个样本点到各自的聚类中心的距离平方和

    当来了一个新用户:

    1. 选择最后的m-1行,形成一个(m-1) x  n的子矩阵; (m < M)

    2. 对一个新用户,将其对n个物品的评价向量添加到子矩阵的最后一行,形成m x n矩阵B

    3. 对矩阵B,使用上述的迭代算法,将最后一行中的缺失值填上

    4. 然后,计算出最后一行的PC

    5. 计算最后一行PC到各个聚类中心的距离,决定新用户属于哪个类

    6. 利用所选择类的平均值,来确实最后一行的缺失值,并形成最终的推荐结果

    算法实现:

    总体上来看,整个算法需要做的事情有:

    1. 通过迭代的SVD分解,将已存在的用户对物品的评价缺失值,给补充上,并且计算出主成分

    2. 通过一个聚类算法(K均值聚类),以原始评分数据的主成分为输入,进行用户聚类

    3. 对新用户,使用迭代的SVD分解将新用户的缺失评分补充上,并计算新用户评分矩阵的主成分

    4. 通过计算新用户的主成分与各聚类中心的距离,将新用户归类,使用类的评均值,作为新用户缺失评分的最终估计值

    5. 寻找新用户缺失评分的Top-n,作为对新用户的推荐结果

    R代码如下:

      1 ## normalize a vector with mean normanization ( x-u )
      2 ## substract column mean from each element
      3 ## Args :
      4 ##     x - a matrix
      5 ## Returns :
      6 ##     a list contains, mean of each colum, 
      7 ##                      normalized x
      8 meanNormalization <- function(x)
      9 {   
     10     meanOfcol <- numeric(dim(x)[2])
     11     sdOfcol <- numeric(dim(x)[2])
     12     for (i in 1:dim(x)[2]) {
     13         t <- x[,i]
     14         idx <- which(t != 0)  
     15         if (length(idx) < 1) {
     16             meanOfcol[i] <- NA
     17             next
     18         }
     19         meanOfcol[i] <- mean(t[idx])
     20         x[idx,i] <- t[idx] - mean(t[idx]) # mean normalization
     21     }
     22     
     23     return ( list(meanOfcol = meanOfcol, xNormalized=x) )
     24 }
     25 ## Args :
     26 ##     x  -  a vector
     27 ##     u  -  a real number, mean value
     28 ## Returns : 
     29 ##     a vector
     30 meanNormalizationInverse <- function(x, u)
     31 {
     32     return (x + u)
     33 }
     34 
     35 ## iterate with SVD to get the Principle Component of users and fill the missing rating
     36 ## Args :
     37 ##      x  -  a matrix, contain all rating reslut. 
     38 ##            Each row is the rating made by  one user, each colum is the rating of one item.
     39 ##            If a movie hasn't been rated by a user, the corresponding postion in the matrix is NA.
     40 ##      pc_threshold  -  threshold to choose the principal components
     41 ##      iterate_threshold  -  svd iteration threshold
     42 ## Returns :
     43 ##      a list, contains : a matrix, principle component of users;
     44 ##                         a matrix, contains all rating between all users and all items, no missing value .
     45 iterateSVD <- function( x, pc_threshold = 0.9, iterate_threshold = 10e-10 )
     46 {    
     47     A <- x
     48     ## fill the missing value with the average of ith row and jth colum
     49     A[which(is.na(A))] <- 0
     50     meanOfColumn <- colMeans(A)
     51     meanOfRow <- rowMeans(A)
     52     idx <- which(is.na(x))
     53     for (i in idx) {
     54         i <- i %% dim(x)[1]   # remainder
     55         j <- j %/% dim(x)[1] + 1
     56         if (i == 0) {
     57             i <- 12
     58         }
     59         A[i,j] <- mean( c(meanOfRow[i], meanOfColumn[j]) )
     60     }
     61     
     62     ## normalize the data by column
     63     normlizedResult <- meanNormalization( A )
     64     A <-  normlizedResult$xNormalized 
     65     
     66     ## iterate util convergence
     67     lastSquareSumLast <- 0.001 * iterate_threshold  # initialized by a small enough value
     68     which( TRUE ) {
     69         # svd decomposition
     70         svd_A <- svd(A)
     71         
     72         # find the top-k singular value
     73         numTopSV <- 0
     74         for(sv in svd_A$d) {
     75             numTopSV <- numTopSV + 1
     76             if ( (sum(svd_A$d[1:numTopSV]) / sum(svd_A$d)) >= pc_threshold ) {
     77                 break
     78             }
     79         }
     80         
     81         # approximation of A
     82         A_appro <- svd_A$u[,1:numTopSV] %*% diag(svd_A$d[1:numTopSV]) %*% t(svd_A$v[,1:numTopSV])
     83         
     84         # filling the missing value with the corresponding value in A_appro
     85         A[which(is.na(x))] <-  A_appro[which(is.na(x))]
     86         
     87         # checking convergence, or not
     88         if ( ( (sum(A_appro[which(is.na(x))]^2) - lastSquareSumLast) / lastSquareSumLast )
     89              < iterate_threshold ) {
     90             break
     91         }
     92         
     93         lastSquareSumLast <- sum(A_appro[which(is.na(x))]^2)
     94     }
     95     
     96     ## calculate the principle component of user
     97     pcOfUsers <- A %*% svd_A$v[,1:numTopSV]
     98     
     99     normalizedA <- A
    100     
    101     ## reverse the normalization 
    102     for (i in 1:dim(A)[2]) {
    103         A[,i] <- meanNormalizationInverse(A[,i], normlizedResult$meanOfcol)
    104     }
    105     
    106     return ( list( pcOfUsers = pcOfUsers, fullRatingResult = A) )
    107 }
    108 
    109 ## cluster the users with k-means cluster method
    110 ## Args :
    111 ##     pc  -  a matrix, principle component of users, each row is one user
    112 ##     clusterThreshold  -  a constant, which is used to choose the 
    113 ##                          best user cluster number
    114 ## Returns :
    115 ##     a list, contains : user k-means cluster object
    116 ##                        user cluster number
    117 clusterUser <- function(pc, clusterThreshold = 0.001)
    118 {
    119     clusterNum <- 2
    120     uc2 <- kmeans(x, centers = clusterNum)
    121     lastKM <- uc2
    122     
    123     ## to make the best choice of cluster number
    124     ## iterate with increasing cluster number until convergence
    125     while( TRUE ) {
    126         clusterNum <- clusterNum + 1
    127         uci <- kmeans(x, centers = clusterNum)
    128         
    129         # check convergence 
    130         if ( ( abs(lastKM$tot.withinss - uci$tot.withinss) / uc2$tot.withinss )
    131              >=  clusterThreshold )   {
    132             break
    133         }
    134         lastKM <- uci
    135     }
    136     
    137     return ( list(userCluster = uci, clusterNum = clusterNum) )
    138 }
    139 
    140 ## get the recomendation list to new user
    141 ## Args :
    142 ##     ur  -  a vector, rating of items from a new user
    143 ##     x  -  a matrix, contain all rating reslut. 
    144 ##           Each row is the rating made by  one user, each colum is the rating of one item.
    145 ##           If a movie hasn't been rated by a user, the corresponding postion in the matrix is NA.
    146 ##     m  -  a integer, number of old users that will be used to fill the
    147 ##                      missing value of vector ur with iterate svd method
    148 ##     topn  -  a integer, how many items will be recommended to the new user.
    149 ## Returns :
    150 ##     a list, contains recommendation result
    151 recommendToNewUser <- function(ur, x, m, topn)
    152 {
    153     iterSVD_x <- iterateSVD(x)
    154     cluster_x <- clusterUser( iterSVD_x$pcOfUsers )
    155     
    156     A <- iterSVD_x$fullRatingResult    
    157     B <- rbind(A[(dim(A)[1] - m + 1) : dim(A)[1],], ur)
    158     
    159     iterSVD_B <- iterateSVD(B)
    160     pc_ur <- iterSVD_B$pcOfUsers[m+1, ] # the principle component of new user
    161     
    162     ## choose the cluster of new user
    163     clusterCenter <- cluster_x$userCluster$centers
    164     distToCC <- numeric(cluster_x$clusterNum)
    165     for (i in 1:cluster_x$clusterNum) {
    166         distToCC[1] <- sqrt( sum( (pc_ur - clusterCenter[i, ]) ^2 ) )
    167     }
    168     # pick the minimum one
    169     clusterOfNU <- which( distToCC == min(distToCC) )
    170     
    171     ## calculate the average value to fill the missing value in ur
    172     idx <- which( cluster_x$cluster == clusterOfNU)
    173     ratingOfNUCluster <- A[idx,]
    174     idx_missing <- which(is.na(ur))
    175     for (j in idx_missing) {
    176         ur[j] <- mean( ratingOfNUCluster[,j] )
    177     }
    178     
    179     ## pick the top-n recommendation items
    180     pickTopn <- numeric( length(ur) )
    181     pickTopn <- ur[idx_missing]
    182     topnIdx <- apply( matrix(pickTopn,nrow=1), MARGIN=1, 
    183                      FUN=function(x) head(  order(x, decreasing=TRUE, na.last=TRUE), topn )  )
    184     topnIdx <- as.vector(topnIdx)
    185     recommendList <- list(ratingResult = pickTopn[topnIdx], topnIndex = topnIdx)
    186     return( recommendList )
    187 }
  • 相关阅读:
    Alice and Bob 要用到辗转相减
    Java经典设计模式
    设计模式---创建类---建造者模式
    luogu4267 TamingtheHerd (dp)
    nowcoder172C 保护 (倍增lca+dfs序+主席树)
    nowcoder172A 中位数 (二分答案)
    bzoj4985 评分 (二分答案+dp)
    luogu4269 Snow Boots G (并查集)
    luogu4268 Directory Traversal (dfs)
    bzoj1001/luogu4001 狼抓兔子 (最小割/平面图最小割转对偶图最短路)
  • 原文地址:https://www.cnblogs.com/activeshj/p/4005618.html
Copyright © 2011-2022 走看看