LibRec 学习笔记(七):代码走读 SBPR 算法

以下是本人在学习 LibRec 库时,仔细走读的 SBPR 算法的笔记,文件名是 SBPRRecommender.java,有需要的同学可以往下看。

一、SBPR 源码走读注释

package net.librec.recommender.context.ranking;
import com.google.common.cache.LoadingCache;
import net.librec.annotation.ModelData;
import net.librec.common.LibrecException;
import net.librec.math.algorithm.Maths;
import net.librec.math.algorithm.Randoms;
import net.librec.math.structure.VectorBasedDenseVector;
import net.librec.recommender.SocialRecommender;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
 * Social Bayesian Personalized Ranking (SBPR)
 * Zhao et al., <strong>Leveraging Social Connections to Improve Personalized Ranking for Collaborative
 * Filtering</strong>, CIKM 2014.
 * @author guoguibing and Keqiang Wang
 */
@ModelData({"isRanking", "sbpr", "userFactors", "itemFactors", "itemBiases"})
public class SBPRRecommender extends SocialRecommender {//继承的是SocialRecommender类  
	private VectorBasedDenseVector itemBiases;//物品偏置向量
    protected float regBias;//偏置正则化项
    protected LoadingCache<Integer, List<Integer>> userItemsCache;//加载user-items cache, item-users cache缓存
    protected static String cacheSpec;//Guava cache configuration???
    private List<List<Integer>> userSocialItemsSetList;//存放用户信任的人打分的物品

    @Override//覆盖SocialRecommender中的setup方法
    public void setup() throws LibrecException {
        super.setup();//关于这个SocialRecommender中的setup方法里有什么,详见问题1
        regBias = conf.getFloat("rec.bias.regularization", 0.01f);//偏置正则化项,默认值为0.01
        cacheSpec = conf.get("guava.cache.spec", "maximumSize=5000,expireAfterAccess=50m");//设置内存缓存模块

        itemBiases = new VectorBasedDenseVector(numItems);//初始化物品偏置向量,初始化的值大小在0,1直接随机分配
        itemBiases.init();
        userItemsCache = trainMatrix.rowColumnsCache(cacheSpec);//rowColumnsCache这个方法已经被弃用,但是还是可以正常。这句代码的作用是得到一个用户-物品的缓存模块。        
        userSocialItemsSetList = new ArrayList<>(numUsers);// 找到信任的人打过分的物品,设置为列表中的列表
        for (int userIdx = 0; userIdx < numUsers; userIdx++) {
            userSocialItemsSetList.add(new ArrayList<Integer>());
        }
        //循环遍历每一个用户
        for (int userIdx = 0; userIdx < numUsers; userIdx++) {
        	//找到用户自己打过分的物品,如果没有,则跳过这个用户
            List<Integer> uRatedItems = null;
            try {
                uRatedItems = userItemsCache.get(userIdx);
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            if (uRatedItems.size() == 0)
                continue; // no rated items
            //找到仅仅被用户信任的人打过分的物品(其中要去除自己打过分的物品)
            int[] trustedUsers = socialMatrix.row(userIdx).getIndices();//从社交矩阵中获取用户信任的 人
            List<Integer> items = new ArrayList<>();
            for (int trustedUserIdx : trustedUsers) {//循环遍历,找到仅仅被用户信任的人打过分的物品(其中要去除自己打过分的物品)
                List<Integer> trustedRatedItems = null;
                try {
                    trustedRatedItems = userItemsCache.get(trustedUserIdx);//该行代码的意思是炸弹该用户打过分的物品
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                for (int trustedRatedItemIdx : trustedRatedItems) {//去除用户本身打过分的物品
                    // v's rated items
                    if (!uRatedItems.contains(trustedRatedItemIdx) && !items.contains(trustedRatedItemIdx)) // if not rated by user u and not already added to item list
                        items.add(trustedRatedItemIdx);
                }
            }
            userSocialItemsSetList.set(userIdx, items);//items里面保存的是该userIdx的朋友们打过分而自己没有打过分的物品集合
        }
    }

    @Override
    protected void trainModel() throws LibrecException {
        int maxSample = trainMatrix.size();//设置最大的sample次数为训练集中所有打分个数的总值,因为不可能不满足条件而一直sample下去        
        /**
         * 开始进行每一次迭代,内容包括sample、更新参数值,这里设置的sample次数是训练集中有多少个打分,也即是有多少个posItem,就sample多少次,而不是每一个用户sample一次。*/    
        for (int iter = 1; iter <= numIterations; iter++) {            
        	loss = 0.0d;//设置每次迭代初始loss值为0
            for (int sample = 0; sample < maxSample; sample++) {           
            	// uniformly 选择 userIdx, posItemIdx, k, negItemIdx)
                int userIdx, posItemIdx, negItemIdx;                
                // 从所有用户里面随机选择一个用户出来,记为userIdx,当然这个用户得有打过分的物品
                List<Integer> ratedItems = null;
                do {
                    userIdx = Randoms.uniform(numUsers);
                    try {
                        ratedItems = userItemsCache.get(userIdx);
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                } while (ratedItems.size() == 0);

                //从选择出的用户userIdx所打分的物品中随机选择一个物品,记为posItemIdx
                posItemIdx = Randoms.random(ratedItems);
                
                //得到用户userIdx对物品posItemIdx的预测分数值
                double posPredictRating = predict(userIdx, posItemIdx);

                //得到用户userIdx信任的朋友们的打分物品,具体userSocialItemsSetList的值已经在setup()函数中得到
                List<Integer> socialItemsList = userSocialItemsSetList.get(userIdx);

                //得到用户userIdx以及他信任的朋友们都没有打过分的物品,记为negItemIdx
                do {
                    negItemIdx = Randoms.uniform(numItems);
                } while (ratedItems.contains(negItemIdx) || socialItemsList.contains(negItemIdx));
                
                //得到用户userIdx对物品negItemIdx的预测分数值
                double negPredictRating = predict(userIdx, negItemIdx);

                if (socialItemsList.size() > 0) {//如果用户的朋友们有打分的物品,则进行SBPR方式进行更新,如果没有,则进行BPR方式更新,下面的代码块是进行SBPR的更新
                	//随机从用户userIdx信任的朋友们打过分的物品,记为socialItemIdx,本身已经剔除用户与朋友都打过分的物品
                    int socialItemIdx = Randoms.random(socialItemsList);
                    
                    //得到用户userIdx对物品socialItemIdx的预测分数值
                    double socialPredictRating = predict(userIdx, socialItemIdx);

                    /**以下几行代码的目的是得到socialWeight,根据论文所写,使用这个参数是用来控制每个采样训练对目标的贡献,
                     * 较大的socialWeight值表示用户u可能更喜欢这个物品,因为他的许多朋友们在这件物品上都表现出了他们的偏好。
                     * 只要有用户的朋友对这个socialItemIdx物品进行过打分,就socialWeight加一。
                     */
                    int[] trustedUserIdices = socialMatrix.row(userIdx).getIndices();//从社交矩阵中获取用户userIdx信任的 人
                    double socialWeight = 0;
                    for (int trustedUserIdx : trustedUserIdices) {//遍历userIdx所有信任的朋友

                        int[] indices = trainMatrix.row(trustedUserIdx).getIndices();//得到朋友trustedUserIdx所有打过分物品的下标值集合
                        int socialRating = Arrays.binarySearch(indices, socialItemIdx);//使用二分查找去找到朋友trustedUserIdx是否对socialItemIdx这个物品
                        //返回的是下标值,所以如果下标值大于0,说明找到了,没有找到就会返回0
                        if (socialRating > 0)
                            socialWeight += 1;
                    }
                    //目标函数使用(1+socialWeight)的倒数来控制用户userIdx对物品posItemIdx和物品socialItemIdx之间的偏好差异
                    double posSocialDiffValue = (posPredictRating - socialPredictRating) / (1 + socialWeight);
                    double socialNegDiffValue = socialPredictRating - negPredictRating;
                    
                    //Maths.logistic是sigmoid函数,下面这行代码得到的是loss值(以及下面的要加上正则化项)
                    double error = -Math.log(Maths.logistic(posSocialDiffValue)) - Math.log(Maths.logistic(socialNegDiffValue));
                    loss += error;

                    double posSocialGradient = Maths.logistic(-posSocialDiffValue), socialNegGradient = Maths.logistic(-socialNegDiffValue);

                    // update bi, bk, bj
                    double posItemBiasValue = itemBiases.get(posItemIdx);
                    itemBiases.plus(posItemIdx, learnRate * (posSocialGradient / (1 + socialWeight) - regBias * posItemBiasValue));
                    loss += regBias * posItemBiasValue * posItemBiasValue;

                    double socialItemBiasValue = itemBiases.get(socialItemIdx);
                    itemBiases.plus(socialItemIdx, learnRate * (-posSocialGradient / (1 + socialWeight) + socialNegGradient - regBias * socialItemBiasValue));
                    loss += regBias * socialItemBiasValue * socialItemBiasValue;

                    double negItemBiasValue = itemBiases.get(negItemIdx);
                    itemBiases.plus(negItemIdx, learnRate * (-socialNegGradient - regBias * negItemBiasValue));
                    loss += regBias * negItemBiasValue * negItemBiasValue;

                    // update P, Q
                    for (int factorIdx = 0; factorIdx < numFactors; factorIdx++) {
                        double userFactorValue = userFactors.get(userIdx, factorIdx);
                        double posItemFactorValue = itemFactors.get(posItemIdx, factorIdx);
                        double socialItemFactorValue = itemFactors.get(socialItemIdx, factorIdx);
                        double negItemFactorValue = itemFactors.get(negItemIdx, factorIdx);

                        double delta_puf = posSocialGradient * (posItemFactorValue - socialItemFactorValue) / (1 + socialWeight)
                                + socialNegGradient * (socialItemFactorValue - negItemFactorValue);
                        
                        userFactors.plus(userIdx, factorIdx, learnRate * (delta_puf - regUser * userFactorValue));

                        itemFactors.plus(posItemIdx, factorIdx, learnRate * (posSocialGradient * userFactorValue / (1 + socialWeight)
                                - regItem * posItemFactorValue));

                        double delta_qkf = posSocialGradient * (-userFactorValue / (1 + socialWeight)) + socialNegGradient * userFactorValue;
                        
                        itemFactors.plus(socialItemIdx, factorIdx, learnRate * (delta_qkf - regItem * socialItemFactorValue));

                        itemFactors.plus(negItemIdx, factorIdx, learnRate * (socialNegGradient * (-userFactorValue) -
                                regItem * negItemFactorValue));

                        loss += regUser * userFactorValue * userFactorValue + regItem * posItemFactorValue * posItemFactorValue +
                                regItem * negItemFactorValue * negItemFactorValue + regItem * socialItemFactorValue * socialItemFactorValue;
                    }
                } else {
                	//如果用户的朋友们有打分的物品,则进行SBPR方式进行更新,如果没有,则进行BPR方式更新,下面的代码块是进行BPR方式的更新   
                    double posNegDiffValue = posPredictRating - negPredictRating;
                    loss += posNegDiffValue;

                    double posNegGradient = Maths.logistic(-posNegDiffValue);

                    // update bi, bj
                    double posItemBiasValue = itemBiases.get(posItemIdx);
                    itemBiases.plus(posItemIdx, learnRate * (posNegGradient - regBias * posItemBiasValue));
                    loss += regBias * posItemBiasValue * posItemBiasValue;

                    double negItemBiasValue = itemBiases.get(negItemIdx);
                    itemBiases.plus(negItemIdx, learnRate * (-posNegGradient - regBias * negItemBiasValue));
                    loss += regBias * negItemBiasValue * negItemBiasValue;

                    // update user factors, item factors
                    for (int factorIdx = 0; factorIdx < numFactors; factorIdx++) {
                        double userFactorValue = userFactors.get(userIdx, factorIdx);
                        double posItemFactorValue = itemFactors.get(posItemIdx, factorIdx);
                        double negItemFactorValue = itemFactors.get(negItemIdx, factorIdx);

                        userFactors.plus(userIdx, factorIdx, learnRate * (posNegGradient * (posItemFactorValue - negItemFactorValue) - regUser * userFactorValue));
                        itemFactors.plus(posItemIdx, factorIdx, learnRate * (posNegGradient * userFactorValue - regItem * posItemFactorValue));
                        itemFactors.plus(negItemIdx, factorIdx, learnRate * (posNegGradient * (-userFactorValue) - regItem * negItemFactorValue));

                        loss += regUser * userFactorValue * userFactorValue + regItem * posItemFactorValue * posItemFactorValue +
                                regItem * negItemFactorValue * negItemFactorValue;
                    }
                }
            }
            //判断是否收敛,输出相关信息,详见下面判断是否收敛的解释
            if (isConverged(iter) && earlyStop) {//两个都是真才是真,收敛+早停,则停止迭代
                break;
            }
            //在每一次迭代之后,更新现在的学习率,详见下面学习率的解释
            updateLRate(iter);
        }
    }
    //预测具体的userIdx和itemIdx的分数,详细见下面的解释
    protected double predict(int userIdx, int itemIdx) throws LibrecException {
        double predictRating = itemBiases.get(itemIdx) + userFactors.row(userIdx).dot(itemFactors.row(itemIdx));//这个预测分数值还加入了每一个物品的偏置项值,详细看解释3
        return predictRating;
    }
}

二、SBPR 源码(部分代码)详细解释

解释1、SBPRRecommender.java 中的 setup() 方法

由于 SBPRRecommender 类继承的是 SocialRecommender类,所以下面是SocialRecommender.java 中的 setup() 方法。

public void setup() throws LibrecException {
        super.setup(); //详见下面解释
        regSocial = conf.getFloat("rec.social.regularization", 0.01f);//社交正则化项,默认值为0.01
        socialMatrix = ((SocialDataAppender) getDataModel().getDataAppender()).getUserAppender();// 返回的是一个用户社交矩阵,该社交矩阵读取的路径是配置文件的“data.appender.path”
    }

而 SocialRecommender 类继承的是 MatrixFactorizationRecommender 类,所以下面是 MatrixFactorizationRecommender.java 中的 setup()方法。

protected void setup() throws LibrecException{
        super.setup();//详见下面解释
        numIterations = conf.getInt("rec.iterator.maximum", 100); //设置最大迭代次数,默认值是100
        learnRate = conf.getFloat("rec.iterator.learnrate", 0.01f);//设置学习率,默认值是0.01
        maxLearnRate = conf.getFloat("rec.iterator.learnrate.maximum", 1000.0f);//设置最大学习率,默认值是1000

        regUser = conf.getFloat("rec.user.regularization", 0.01f);//设置用户正则化项,默认值是0.01
        regItem = conf.getFloat("rec.item.regularization", 0.01f);//设置物品正则化项,默认值是0.01

        numFactors = conf.getInt("rec.factor.number", 10);//设置隐因子大小,默认值是10
        isBoldDriver = conf.getBoolean("rec.learnrate.bolddriver", false);//设置是否在每次迭代中调整学习率,默认值是false
        decay = conf.getFloat("rec.learnrate.decay", 1.0f);//设置每次迭代中调整学习率的大小,默认值是1

        userFactors = new DenseMatrix(numUsers, numFactors//定义用户隐因子矩阵,大小是(用户数量*隐因子大小)
        itemFactors = new DenseMatrix(numItems, numFactors);//定义物品隐因子矩阵,大小是(物品数量*隐因子大小)

        initMean = 0.0f;//设置初始平均值为0.0
        initStd = 0.001f;//设置初始标准差0.001

        // initialize factors
        userFactors.init(initMean, initStd);//使用初始平均值和初始标准差初始化用户隐因子矩阵
        itemFactors.init(initMean, initStd);//使用初始平均值和初始标准差初始化物品隐因子矩阵
    }

而 MatrixFactorizationRecommender 类继承的是 MatrixRecommender 类,所以下面是 MatrixRecommender.java 中的 setup()方法。

protected void setup() throws LibrecException{
        super.setup();//详见下面的解释
        trainMatrix = (SequentialAccessSparseMatrix) getDataModel().getTrainDataSet();//根据训练集建立一个序列化稀疏矩阵
        testMatrix = (SequentialAccessSparseMatrix) getDataModel().getTestDataSet();//根据测试集建立一个序列化稀疏矩阵
        validMatrix = (SequentialAccessSparseMatrix) getDataModel().getValidDataSet();//根据验证集建立一个序列化稀疏矩阵

        numUsers = trainMatrix.rowSize();//得到训练集中的用户数量
        numItems = trainMatrix.columnSize();//得到训练集中的用户数量
        numRates = trainMatrix.size();//得到训练集中的所有非0值的数字大小,也即训练集中的评分数量
        Set<Double> ratingSet = new HashSet<>();//设置一个哈希集合
        for (MatrixEntry matrixEntry : trainMatrix) {//MatrixEntry是矩阵中的每一个值
            ratingSet.add(matrixEntry.get());//把训练集中的所有分数放入到这个哈希集合中
        }
        ratingScale = new ArrayList<>(ratingSet);
        Collections.sort(ratingScale);
        maxRate = Collections.max(ratingScale);//得到所有打分数的最大值
        minRate = Collections.min(ratingScale);//得到所有打分数的最小值
        if (minRate == maxRate) {
            minRate = 0;
        }
        globalMean = trainMatrix.mean();//获取训练集所有打分的平均值

        //下面的设置是 关于 AUC、Novelty、NDCG、Entropy几个评估值的特殊设置
        int[] numDroppedItemsArray = new int[numUsers]; // for AUCEvaluator
        int maxNumTestItemsByUser = 0; //for idcg
        for (int userIdx = 0; userIdx < numUsers; ++userIdx) {
            numDroppedItemsArray[userIdx] = numItems - trainMatrix.row(userIdx).getNumEntries();
            int numTestItemsByUser = testMatrix.row(userIdx).getNumEntries();
            maxNumTestItemsByUser = maxNumTestItemsByUser < numTestItemsByUser ? numTestItemsByUser : maxNumTestItemsByUser;
        }

        int[] itemPurchasedCount = new int[numItems]; // for NoveltyEvaluator
        for (int itemIdx = 0; itemIdx < numItems; ++itemIdx) {
            itemPurchasedCount[itemIdx] = trainMatrix.column(itemIdx).getNumEntries()
                    + testMatrix.column(itemIdx).getNumEntries();
        }
        conf.setInts("rec.eval.auc.dropped.num", numDroppedItemsArray);
        conf.setInt("rec.eval.key.test.max.num", maxNumTestItemsByUser); //for nDCGEvaluator
        conf.setInt("rec.eval.item.num", testMatrix.columnSize()); // for EntropyEvaluator
        conf.setInts("rec.eval.item.purchase.num", itemPurchasedCount); // for NoveltyEvaluator
    }

而 MatrixRecommender 类继承的是 AbstractRecommender 类,所以下面是 AbstractRecommender .java 中的 setup()方法。

 protected void setup() throws LibrecException {
        conf = context.getConf();//通过 RecommenderContext类获取所有的配置项到 conf 变量中,而这些变量就是 
        //librec-default.properties和具体算法中的配置项,比如sbpr-test.properties
        isRanking = conf.getBoolean("rec.recommender.isranking");//获取是否进行排序配置项,比如topN任务这一项都有
        if (isRanking) {
            topN = conf.getInt("rec.recommender.ranking.topn", 10);//TopN值,默认是10
            if (this.topN <= 0) {
                throw new IndexOutOfBoundsException("rec.recommender.ranking.topn should be more than 0!");
            }
        }
        earlyStop = conf.getBoolean("rec.recommender.earlystop", false);//是否进行早停策略,默认值是false
        verbose = conf.getBoolean("rec.recommender.verbose", true);//是否输出打印信息,就是控制台输出的那些信息,默认值是true
        userMappingData = getDataModel().getUserMappingData();//得到用户隐射数据(具体用途,暂时不详)
        itemMappingData = getDataModel().getItemMappingData();//得到物品隐射数据(具体用途,暂时不详)
        if (verbose) {//如果可以输出打印消息,则设置进度条的大小
            progressBar = new ProgressBar(100, 100);
        }
    }

以上是 SBPR 算法中调用的 super.setup() 方法中包含的所有操作,这些操作可以概括为从 conf 配置项中得到输入的值,进行成员变量的初始化

值得注意的是,如果具体算法还需要配置额外的参数,可以直接重写这个方法,但记得要调用 super.setup()方法,保证算法的基本参数得到初始化。就像 SBPR 中还设置了很多参数。

解释2:SBPRRecommender.java 中的 trainModel() 方法

1、论文中提到的 S_uk 系数

根据论文所写,使用这个参数是用来控制每个采样训练对目标的贡献,这里的代码里用 socialWeight 表示,论文里面提出较大的 socialWeight 值表示用户 u 可能更喜欢这个物品,因为他的许多朋友们在这件物品上都表现出了他们的偏好。 所以只要有用户的朋友对这个 socialItemIdx 物品进行过打分,socialWeight 这个值就加一。
在这里插入图片描述

2、论文中提到的 Sample

根据论文中的训练过程的伪代码示意图,外层循环是迭代次数,内层循环是每一次迭代的 Sample 次数,并且 Sample 次数等于训练集中的正例个数。和这里的代码是符合的(以前我复现 SBPR 时设置的 Sample次数是用户数!!!)
在这里插入图片描述
以下是每一次 Sample 的步骤:

  1. 在每一次 sample 循环中,首先我们从所有用户里面随机选择一个用户出来,记为 u,当然这个用户 u 得有打过分的物品;
  2. 从用户 u 所打分的物品中随机选择一个物品,记为物品 i;
  3. 从用户 u 信任的朋友们打过分的物品中随机选择一个物品,记为物品 k (当然在用户 u 打过分的物品中不包括物品 k );
  4. 从用户 u 和用户 u 信任的朋友都没有打过分的物品中,随机选择一个物品,记为物品 j;

3、更新参数

我们这里需要更新的参数包括1)用户隐因子矩阵、2)物品隐因子矩阵、3)物品偏置项向量。(代码在上面有,至于具体的推导公式,有空再补上来)
在这里插入图片描述

4、判断是否收敛

下面是 isConverged(iter) 方法的细节:

    protected boolean isConverged(int iter) throws LibrecException {
        float delta_loss = (float) (lastLoss - loss);
        // 如果verbose为真,输出信息
        if (verbose) {
            String recName = getClass().getSimpleName();
            String info = recName + " iter " + iter + ": loss = " + loss + ", delta_loss = " + delta_loss;
            LOG.info(info);
        }
        //判断是否有异常
        if (Double.isNaN(loss) || Double.isInfinite(loss)) {
        	//LOG.error("Loss = NaN or Infinity: current settings does not fit the recommender! Change the settings and try again!");
            throw new LibrecException("Loss = NaN or Infinity: current settings does not fit the recommender! Change the settings and try again!");
        }
        //判断是否收敛
        return Math.abs(delta_loss) < 1e-5;
    }

5、学习率以及 loss 的更新

下面是 updateLRate(iter) 方法的细节:

//在每一次迭代之后,根据配置项决定是否更新学习率
    protected void updateLRate(int iter) {
    	//如果学习率小于0,直接结束
        if (learnRate < 0.0) {
            lastLoss = loss;
            return;
        }
        //如果参数isBoldDriver(是否调整学习率)为真,并且迭代次数大于1,则更新学习率
        if (isBoldDriver && iter > 1) {
            learnRate = Math.abs(lastLoss) > Math.abs(loss) ? learnRate * 1.05f : learnRate * 0.5f;
        } else if (decay > 0 && decay < 1) {
            learnRate *= decay;
        }
        //限制更新的最大学习率为maxLearnRate,配置项里默认值是1000
        // limit to max-learn-rate after update
        if (maxLearnRate > 0 && learnRate > maxLearnRate) {
            learnRate = maxLearnRate;
        }
        //更新loss值
        lastLoss = loss;
    }

6、值得注意

如果用户 u 没有朋友,或者用户 u 和朋友没有共同都没有打过分的物品,则SBPR 训练降为 BPR 训练。
在这里插入图片描述

解释3:SBPRRecommender.java 中的 predict() 方法

以下是 SBPRRecommender.java 中的 predict() 方法,这里直接重写了基类的 predict 方法,一般预测用户对某一个物品具体的分数值的代码是double predictRating = userFactors.row(userIdx).dot(itemFactors.row(itemIdx)),而这里的 predict 方法加了具体物品的偏置项

     //预测具体的userIdx和itemIdx的分数
    protected double predict(int userIdx, int itemIdx) throws LibrecException {
        double predictRating = itemBiases.get(itemIdx) + userFactors.row(userIdx).dot(itemFactors.row(itemIdx));//这个预测分数值还加入了每一个物品的偏置项值,详细看解释3
        return predictRating;
    }

因为原文里是这样写的
在这里插入图片描述
在这里插入图片描述

以上,完(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤


以上是本人走读 SBPR 算法的笔记,希望对研究 SBPR 代码如何写的你有帮助
ヾ(◍°∇°◍)ノ゙

本人目前刚开始学习使用 LibRec,欢迎同伴一起交流进步,哪里有写的不对的地方,欢迎评论指正呀!ヾ(◍°∇°◍)ノ゙

如果这篇博客帮助了您,可以请我吃包5毛钱的辣条吗?(下面为微信收款码)或者点个赞也行呀!您小小的鼓励会是我持续更新的动力!ヾ(◍°∇°◍)ノ゙
在这里插入图片描述

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值