放牧代码和思想
专注自然语言处理、机器学习算法
    愛しさ 優しさ すべて投げ出してもいい

3.2 推荐引擎是怎么工作的

目录

3.2.1 基于相似用户的推荐

计算用户相似度

3.2.2 基于相似条目的推荐

3.2.3 基于内容的推荐

样例设置

基于内容的相似度的要点

三类基于内容的推荐引擎

       推荐引擎可以分为两类:

       1、协同过滤CF引擎,基于用户相似度或基于条目。

       2、基于内容,内容指的分析问卷调查等。

3.2.1 基于相似用户的推荐

       找到与用户相似的用户,将他们的评分乘以相似度得出加权评分,这是基本原理。

package com.hankcs;

import iweb2.ch3.collaborative.data.BaseDataset;
import iweb2.ch3.collaborative.data.MusicData;
import iweb2.ch3.collaborative.data.MusicUser;
import iweb2.ch3.collaborative.recommender.Delphi;
import iweb2.ch3.collaborative.similarity.RecommendationType;

public class ch3_2_UserBasedSimilarity
{
    public static void main(String[] args) throws Exception
    {
// The static method createDataset will create random ratings
// for all the users, pick 75% of all the songs and assign ratings.
//
// Users whose name starts from A to D should have ratings between 3 and 5
// Users whose name starts from E to Z should have ratings between 1 and 3
//      创建测试数据
        BaseDataset ds = MusicData.createDataset();

//
// Serialize the dataset, so that we can load it in the next script
//
        // 序列化测试数据
        ds.save("C:/iWeb2/deploy/data/ch3_2_dataset.ser");

//
// use Delphi with USER_BASED similarity
//
        // 创建基于用户的推荐系统
        Delphi delphi = new Delphi(ds, RecommendationType.USER_BASED);
        // 输出详情
        delphi.setVerbose(true);

//
// Show me users like X (top 5)
//
        //  找到Bob和John的相似用户
        MusicUser mu1 = (MusicUser) ds.pickUser("Bob");
        delphi.findSimilarUsers(mu1);

        MusicUser mu2 = (MusicUser) ds.pickUser("John");
        delphi.findSimilarUsers(mu2);

//
// Show me recommendations for user X (top 5)
//
        delphi.recommend(mu1);

//------------------------------------------------------------------------
//BaseDataset ds = MusicData.createDataset();
//ds.save("C:/iWeb2/deploy/data/ch3_2_dataset.ser");
//Delphi delphi = new Delphi(ds,RecommendationType.USER_BASED);
//delphi.setVerbose(true);
//MusicUser mu1 = ds.pickUser("Bob");
//delphi.findSimilarUsers(mu1);
//MusicUser mu2 = ds.pickUser("John");
//delphi.findSimilarUsers(mu2);
//delphi.recommend(mu1);
//------------------------------------------------------------------------
    }
}

计算用户相似度

       相似性使用Jacard度量,两个用户评分相同的次数 / 他们评分条目的并集的元素个数。

iweb2/ch3/collaborative/similarity/UserBasedSimilarity.java

protected void calculate(Dataset dataSet)
    {

        int nUsers = dataSet.getUserCount();        // 定义相似度矩阵的规模
        int nRatingValues = 5;                      // 定义评分计算矩阵的规模

        similarityValues = new double[nUsers][nUsers];

        if (keepRatingCountMatrix)
        {
            ratingCountMatrix = new RatingCountMatrix[nUsers][nUsers];
        }

        // if we want to use mapping from userId to index then generate 
        // index for every userId
        // 如果需要从用户Id到下标索引的映射,则为每个用户生成索引
        if (useObjIdToIndexMapping)
        {
            for (User u : dataSet.getUsers())
            {
                idMapping.getIndex(String.valueOf(u.getId()));
            }
        }

        for (int u = 0; u < nUsers; u++)
        {

            int userAId = getObjIdFromIndex(u);
            User userA = dataSet.getUser(userAId);

            for (int v = u + 1; v < nUsers; v++)
            {
                // 计算相似度矩阵
                //每个user只与id比他大的计算一次,保证不重不漏
                int userBId = getObjIdFromIndex(v);
                User userB = dataSet.getUser(userBId);

                // 两个用户评分的一致性矩阵
                RatingCountMatrix rcm = new RatingCountMatrix(userA, userB, nRatingValues);

                int totalCount = rcm.getTotalCount();
                int agreementCount = rcm.getAgreementCount();

                if (agreementCount > 0)
                {
                    // Jaccard相似度,交集除以并集
                    similarityValues[u][v] = (double) agreementCount / (double) totalCount;
                }
                else
                {
                    similarityValues[u][v] = 0.0;
                }

                // For large datasets
                if (keepRatingCountMatrix)
                {
                    ratingCountMatrix[u][v] = rcm;
                }
            }

            // for u == v assign 1. 
            // RatingCountMatrix wasn't created for this case
            similarityValues[u][u] = 1.0;
        }
    }

Delphi 内部工作机制

基于用户间相似度预测评分

某个条目的用户A预测评分 = AX的相似度 * X对该条目的评分 / AX的相似度 X为除了A之外的任意用户)

iweb2/ch3/collaborative/recommender/Delphi.java

public List<PredictedItemRating> recommend(User user, int topN)
    {

        List<PredictedItemRating> recommendations = new ArrayList<PredictedItemRating>();

        double maxRating = -1.0d;
        // 遍历所有条目
        for (Item item : dataSet.getItems())
        {

            // only consider items that user hasn't rated yet or doesn't own the content
            // 只考虑该用户还未评分的条目
            if (!skipItem(user, item))
            {
                // 只考虑该用户还未评分的条目
                double predictedRating = predictRating(user, item); // 为这名用户预测评分

                if (maxRating < predictedRating)
                {
                    maxRating = predictedRating;
                }

                if (!Double.isNaN(predictedRating))
                {
                    // 把候选推荐的预测分值累加起来
                    recommendations.add(new PredictedItemRating(user.getId(),
                                                                item.getId(), predictedRating));
                }
            }
            else
            {
                if (verbose)
                {
                    System.out.println("Skipping item:" + item.getName());
                }
            }
        }

        this.maxPredictedRating.put(user.getId(), maxRating);

        List<PredictedItemRating> topNRecommendations =
                // 对预测分值排序,返回大小不大于topN的列表
                PredictedItemRating.getTopNRecommendations(recommendations, topN);

        if (verbose)
        {
            PredictedItemRating.printUserRecommendations(user, dataSet, topNRecommendations);
        }

        return topNRecommendations;
    }

3.2.2 基于相似条目的推荐

       基于条目和基于用户类似,只是将用户相似度换成条目相似度。

       相似性依然使用Jacard度量,两个条目被评分相同的次数 / 被评分次数

某个用户的条目A预测评分 = AX的相似度 * 该用户对A的评分 / AX的相似度 X为除了A之外的任意条目)

package com.hankcs;

import iweb2.ch3.collaborative.data.BaseDataset;
import iweb2.ch3.collaborative.data.MusicData;
import iweb2.ch3.collaborative.data.MusicItem;
import iweb2.ch3.collaborative.data.MusicUser;
import iweb2.ch3.collaborative.recommender.Delphi;
import iweb2.ch3.collaborative.similarity.RecommendationType;

public class ch3_3_ItemBasedSimilarity
{
    public static void main(String[] args) throws Exception
    {
//
// Load the dataset that we created before
//
        // 载入上次的数据库
        BaseDataset ds = BaseDataset.load("C:/iWeb2/deploy/data/ch3_2_dataset.ser");

//
// use Delphi with ITEM_BASED similarity
//
        // 创建基于条目的推荐引擎
        Delphi delphi = new Delphi(ds,RecommendationType.ITEM_BASED);
        delphi.setVerbose(true);

//
// Show me recommendations for user X (top 5)
//
        // 给Bob推荐一些条目
        MusicUser mu1 = (MusicUser) ds.pickUser("Bob");
        delphi.recommend(mu1);

//
// Show me items like X (top 5)
//
        // 找出与这个条目相似的条目
        MusicItem mi = (MusicItem) ds.pickItem("La Bamba");
        delphi.findSimilarItems(mi);
    }
}

此时对相似度的计算不同:

protected void calculate(Dataset dataSet)
    {
        // 定义相似度矩阵的规模
        int nItems = dataSet.getItemCount();
        // 定义评分计数矩阵的规模
        int nRatingValues = 5;

        similarityValues = new double[nItems][nItems];

        if (keepRatingCountMatrix)
        {
            ratingCountMatrix = new RatingCountMatrix[nItems][nItems];
        }

        // if we want to use mapping from itemId to index then generate 
        // index for every itemId
        // 如果需要生成从条目id到下标索引的映射,则为每个条目生成索引
        if (useObjIdToIndexMapping)
        {
            for (Item item : dataSet.getItems())
            {
                idMapping.getIndex(String.valueOf(item.getId()));
            }
        }

        // By using these variables we reduce the number of method calls
        // inside the double loop.
        int totalCount = 0;
        int agreementCount = 0;

        for (int u = 0; u < nItems; u++)
        {

            int itemAId = getObjIdFromIndex(u);
            Item itemA = dataSet.getItem(itemAId);

            // we only need to calculate elements above the main diagonal.
            // 只需要计算主对角线上方的元素
            for (int v = u + 1; v < nItems; v++)
            {

                int itemBId = getObjIdFromIndex(v);

                Item itemB = dataSet.getItem(itemBId);

                RatingCountMatrix rcm = new RatingCountMatrix(itemA, itemB, nRatingValues);

                // 所有打分次数
                totalCount = rcm.getTotalCount();
                // 打分相同的次数
                agreementCount = rcm.getAgreementCount();

                if (agreementCount > 0)
                {
                    similarityValues[u][v] = (double) agreementCount / (double) totalCount;
                }
                else
                {
                    similarityValues[u][v] = 0.0;
                }

                if (keepRatingCountMatrix)
                {
                    ratingCountMatrix[u][v] = rcm;
                }
            }

            // for u == v assign 1
            // 对于uv相同的情况赋值1
            similarityValues[u][u] = 1.0;

        }
    }

3.2.3 基于内容的推荐

       在没有评分数据的情况下判断两个文档有多接近。

package com.hankcs;

import iweb2.ch3.collaborative.data.BaseDataset;
import iweb2.ch3.collaborative.data.MusicData;
import iweb2.ch3.collaborative.data.MusicItem;
import iweb2.ch3.collaborative.data.MusicUser;
import iweb2.ch3.collaborative.recommender.Delphi;
import iweb2.ch3.collaborative.similarity.RecommendationType;

public class ch3_3_ItemBasedSimilarity
{
    public static void main(String[] args) throws Exception
    {
//
// Load the dataset that we created before
//
        // 载入上次的数据库
        BaseDataset ds = BaseDataset.load("C:/iWeb2/deploy/data/ch3_2_dataset.ser");

//
// use Delphi with ITEM_BASED similarity
//
        // 创建基于条目的推荐引擎
        Delphi delphi = new Delphi(ds,RecommendationType.ITEM_BASED);
        delphi.setVerbose(true);

//
// Show me recommendations for user X (top 5)
//
        // 给Bob推荐一些条目
        MusicUser mu1 = (MusicUser) ds.pickUser("Bob");
        delphi.recommend(mu1);

//
// Show me items like X (top 5)
//
        // 找出与这个条目相似的条目
        MusicItem mi = (MusicItem) ds.pickItem("La Bamba");
        delphi.findSimilarItems(mi);
    }
}

相似度计算:

  /**
     * 基于文本计算用户相似度
     */
    protected void calculate(Dataset dataSet)
    {

        int nUsers = dataSet.getUserCount();

        similarityValues = new double[nUsers][nUsers];

        // if we want to use mapping from userId to index then generate 
        // index for every userId
        if (useObjIdToIndexMapping)
        {
            for (User u : dataSet.getUsers())
            {
                idMapping.getIndex(String.valueOf(u.getId()));
            }
        }

        // 生成余弦相似性度量
        CosineSimilarityMeasure cosineMeasure = new CosineSimilarityMeasure();
        String[] allTerms = dataSet.getAllTerms();

        for (int u = 0; u < nUsers; u++)
        {
            int userAId = getObjIdFromIndex(u);
            User userA = dataSet.getUser(userAId);

            for (int v = u + 1; v < nUsers; v++)
            {

                int userBId = getObjIdFromIndex(v);
                User userB = dataSet.getUser(userBId);

                double similarity = 0.0;

                for (Content userAContent : userA.getUserContent()) // 对用户A的所有评分条目进行迭代
                {

                    double bestCosineSimValue = 0.0;

                    for (Content userBContent : userB.getUserContent()) // 对用户B的所有评分条目进行迭代
                    {
                        double cosineSimValue = cosineMeasure.calculate(
                                userAContent.getTermVector(allTerms),
                                userBContent.getTermVector(allTerms));
                        bestCosineSimValue = Math.max(bestCosineSimValue,
                                                      cosineSimValue);
                    }

                    similarity += bestCosineSimValue;   // 累加所有文档的最佳相似度
                }
                //System.out.println("Similarity user[" + u + "][" + v + "]=" + similarity);
                similarityValues[u][v] = similarity / userA.getUserContent().size();    // 通过简单的取平均得到相似度
            }

            // for u == v assign 1. 
            similarityValues[u][u] = 1.0;
        }
    }

样例设置

       用户名A-D的用户偏好bzsport

       其他用户偏好usaworld

基于内容的相似度的要点

       找出每个文档前N个出现最频繁的单词,求并集。该并集大小为M,则每个文档可以表示为一个1 * M的向量。

       文档AB的相似度等于 两个向量的点积 / 两个向量的长度的积

package iweb2.ch3.collaborative.similarity;

/**
 * 计算词向量间的余弦相似度
 */
public class CosineSimilarityMeasure
{

    public double calculate(double[] v1, double[] v2)
    {
        double a = getDotProduct(v1, v2);
        double b = getNorm(v1) * getNorm(v2);   // 标准化两个向量并计算它们的乘积
        return a / b;                           // 得到余弦相似度
    }

    private double getDotProduct(double[] v1, double[] v2)
    {
        double sum = 0.0;
        for (int i = 0, n = v1.length; i < n; i++)
        {
            sum += v1[i] * v2[i];
        }
        return sum;
    }

    /**
     * 计算一个向量的欧几里得模
     * @param v
     * @return
     */
    private double getNorm(double[] v)
    {
        double sum = 0.0;
        for (int i = 0, n = v.length; i < n; i++)
        {
            sum += v[i] * v[i];
        }
        return Math.sqrt(sum);
    }
}

三类基于内容的推荐引擎

       相似用户的寻找算法是成功的,相似条目的寻找则稍有模糊。这是由自然语言处理(NLP)制约的。两者杂交形成的用户条目推荐器则是一个折中。

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » 3.2 推荐引擎是怎么工作的

评论 1

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    博主你好,我有一个异议。“相似性使用Jacard度量,两个用户评分相同的次数 / 他们评分条目的并集的元素个数。”我觉得这里除以“他们评分条目的并集”不太科学,应该是除以“他们都做出评价的条目”。这是我的主要异议。另外,单纯的“评分相同的次数”是不是不如“1-评分之差/总分”?

    nickname7年前 (2018-03-14)回复

我的作品

HanLP自然语言处理包《自然语言处理入门》