Apache Mahout之协同过滤原理与实践

Apache Mahout之协同过滤原理与实践

  

    读书时期,选课是令人怀念的,因为自由,学生可以挑选自己喜爱的课程和老师!然而,过程并不是很美好,“系统繁忙,稍后重试!”屡有发生,于是大伙开心地约定今夜不战不休。西门的七彩路,和网吧名一样,我们从门口路过,进的却是右旁的可媛。这里网页同样坚持“系统繁忙,稍后重试!”!去的人多了,也就组了局。痛并快乐着应该如此!那以后,我们这堆人中出了一批又一批的高手,操作极限,走位妖娆,那都不是事儿!

    戏后,一场深思悄然浮现:如果系统可以收罗大量数据,如学生性格特征、性别籍贯、课程成绩、兴趣爱好、喜爱书籍、历史课表等等,然后消化这些数据原料,最后为每个学生呈现一份个性化的定制课表!多么美好!譬如,对于经常清晨借阅或阅读历史书籍的L,就可以推荐G老师上午时间讲授的《清帝漫谈》课程,对L来说,学习效果更佳!

如今,智能推荐无所不在。在亚马逊买过书籍的朋友,可能会注意到,当在网站上买过几次书籍后,下次再次购买一些新书籍时,网站会主动推荐一些你可能感兴趣的书籍,等你来购!

    当然,应用比较广泛的推荐方法之一便是协同过滤(Collaborative Filter,简称CF)。今天,就和大家一起来揭秘她的神秘面纱。

 

一、什么是协同过滤

    协同过滤基于的基本思想:如果用户在过去有相同的偏好(比如他们浏览了相同的网页信息或买过相同的书),那么他们在未来也会有相似的偏好(所谓江山易改,本性难移)。例如,如果用户A和用户B过去都购买过书籍a、b和c,而且用户A最近新买了一本用户B还不知道的书籍d,如表所示:

用户

书籍

A

a

b

c

d

B

a

b

c

?

那么我们基本的逻辑是向B推荐书籍d。而且我们还能看到用户A和用户B可能会成为很好的朋友。当然,如果他们性别相异,年龄相当,我推荐他们考虑建立成恋爱关系(具有相似偏好的人成为恋人的成功率更高,更何况他们看的书籍都那么的一致)。

    向用户B推荐可能感兴趣的书籍涉及从大量的书籍集合中过滤出用户B最可能感兴趣的书籍,而且用户A和用户B的这种关系是一种协同,所以称为协同过滤。

 

二、如何寻找与当前用户具有相似偏好的用户

1、用户相似度度量

    假设数据库中存储了用户-物品-评分的数据alice.txt,格式如下:

1,101,5
1,102,3
1,103,4
1,104,4
2,101,3
2,102,1
2,103,2
2,104,3
2,105,3
3,101,4
3,102,3
3,103,4
3,104,3
3,105,5
4,101,3
4,102,3
4,103,1
4,104,5
4,105,4
5,101,1
5,102,5
5,103,5
5,104,2
5,105,1

第一列表示用户ID={1,2,3,4,5},

第二列表示物品ID={101,102,103,104,105},

第三列表示用户给物品的评分Score={1,2,3,4,5},

例如第一行数据表示的含义是用户1给物品101的评分是5(最高分)。然而,数据库有时只存储了某个用户交易了某个物品,或者浏览过某个物品,或者收藏过某个物品,我们通过这些数据同样可以计算出用户-物品-评分数据。为了清晰,我们将用户-物品-评分数据转换成用户-物品-评分矩阵(本例是5*5大小的矩阵),如下表:

用户/物品

101

102

103

104

105

1

5

3

4

4

2

3

1

2

3

3

3

4

3

4

3

4

4

3

3

1

5

4

5

1

5

5

2

1

    现在的问题是:是否应当向用户1推荐物品105?我们先假设:如果用户1给物品105评5分,那么应当推荐给用户1,如果是1分,那么最好不要推荐!如何预测用户1会给物品105评多少分呢?我们的逻辑是:参考那些与用户1相似的用户给物品105的评分,例如与用户1相似的用户都给105评5分,那么我们预测用户1可能会给物品105评5分,也就当然将物品105推荐给用户1了。所以,我们就需要寻找与用户1具有相似偏好的那些用户,那么究竟如何度量两个用户之间的相似度呢?比如用户1和用户2。事实上,用户对所有物品的评分构成了一个n维向量(矩阵的每一行),例如,用户1对应一个5维向量v1=(5,3,4,4,?),用户2对应一个5维向量v2=(3,1,2,3,3),那么问题就转换成如何度量这两个向量的相似度,大家立即想到的是欧几里得空间距离:

上式表示了用户1和用户2的欧几里得空间距离,注意到用户1对物品105没有评分,计算时我们舍弃没有数据的维数(向量降维),如下计算即可:

 

基于距离越近,用户越相似的思想,用户1和用户2的相似度:

 

其中4表示向量维数,计算结果小数位舍弃。同理可以计算出用户1分别与用户3、4和5的相似度分别为:

 

所以与用户1相似度按从高到低的用户排序依次是:用户3、用户2、用户4和用户5。这里欧几里得相似度只是提供了一种相似度度量方式。是否还有其他的度量方式呢?我们知道,在二维空间中,两个向量v1和向量v2的夹角余弦计算如下:

 

那么,这个余弦值是否可以用来度量两个用户的相似度呢?如图:

 

图中展示了两个向量的三种位置关系:

第一个图表示一般情况:两个向量有个夹角;

第二个图表示两个向量重合的情况;

第三个图表示两个向量垂直的情况。

我们先考虑两个物品,如下:

用户/物品

物品1

物品2

用户1

3

4

用户2

3

4

用户1和用户2对物品的评分一样,我们认为这两个用户是非常相似的,甚至一样,他们对应的向量为v1=(3,4),v2=(3,4),余弦值:

 

这种情况余弦值1确实可以作为用户1和用户2的相似度度量。

    当用户给物品的评分情况如下时:

用户/物品

物品1

物品2

用户1

5

0

用户2

0

5

实际来看,两个用户截然不同,用户1给物品1评5分高分时,用户2却给相同物品评低分,对物品2也是如此,他们对应的向量为v1=(5,0),v2=(0,5),余弦值:

 

这种情况余弦值0也可以作为用户1和用户2的相似度度量。我们的基本逻辑是:当两个向量的夹角越小时,用户相似度越高,反之,用户相似度越低。所以我们可以直接使用余弦值作为用户的相似度度量,称为余弦相似度(记作CosineSimilarity)。我们使用案例数据计算出用户1与其他用户的相似度如下: 

 

    所以与用户1相似度从高到低的用户排序依次是:用户3、用户2、用户4和用户5,与欧几里得相似度值虽然相差甚多,但结果却一致。当然相似度度量还有很多方法,例如皮尔森相关系数,公式如下:

其中P表示所有物品的集合,例如案例中P={101,102,103,104,105},表示用户a给物品p的评分,和分别表示用户a和用户b对所有物品的平均评分,例如用户1给所有物品的平均评分是:

 

那么,用户1和用户2的相似度:

 

用户1与其他用户的相似度为:

 

所以与用户1相似度从高到低的用户排序依次是:用户2、用户3、用户4和用户5。与前面两种度量方式结果不完全一致,这里用户2与用户1更相似一些。如果只选择两个最相似的用户,那么结果却是一致的。还有其他很多相似度度量方法,我们不再一一说明。到这里用户相似度的度量问题已经得到解决。

 

2、用户的最近邻(k-最近邻)

    所谓某个用户的k-最近邻是指与该用户最相似的k个用户(不包括该用户本身)。例如我们前面形成了用户1的4-最近邻 ={用户3,用户2,用户4,用户5}。如果我们只选择两个最相似用户,那么就构成了用户1的2-最近邻 ={用户3,用户2},从2-最近邻来看,前面的三种度量方式,结果是一致的。

 

    我们已经找到了与当前用户相似的那些用户了,接下来就要参考这些用户给物品105的评分来预测当前用户给105的评分了。

三、预测评分

    如何预测用户1给物品105的评分?这就关系到该重视哪些近邻的评分,如何重视?例如通过皮尔森相关系数,计算出的用户1与其他用户的相似度:用户1和用户2、用户3相似度分别为0.85和0.70,相关性最大,与用户4相似度值0,可以认为无关,与用户五的相关系数值是-0.79,是个负值,可以认为两个用户可能偏好截然相反。所以我们应当选择用户2和用户3作为用户1的2-最近邻来评分。下面公式考虑了用户a的N近邻与用户a平均评分的偏差,预测用户a对物品p的评分:

 

其中sim(a,b)表示用户a和用户b的相似度。所以用户1给物品105的预测评分是:

当然,Apache Mahout中并不是这样实现的,它未考虑平均评分,而是采用了如下简化的预测公式:

 

计算出的预测评分为

 

四、推荐

    无论用户1给物品105的预测评分是4.87,还是3.90,都是一个比较高的分数,应当将物品105推荐给用户1。

 

五、代码实现

1、pom.xml

    导入Hadoop Mahout算法库,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.leboop</groupId>
    <artifactId>mahout</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <!-- mahout版本号 -->
        <mahout.version>0.13.0</mahout.version>
    </properties>
    <dependencies>
        <!-- mahout -->
        <dependency>
            <groupId>org.apache.mahout</groupId>
            <artifactId>mahout-integration</artifactId>
            <version>${mahout.version}</version>
        </dependency>
    </dependencies>
</project>

 

 

 

2、推荐程序

    程序中,我们使用皮尔森系数计算用户相似度,如下:

package com.leboop.recommendation;

import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;

import java.io.File;
import java.util.Arrays;
import java.util.List;

/**
 * 推荐思路
 * 1、读取用户-物品-评分数据,转换成推荐数据模型
 * 2、基于用户相似度计算用户N-最近邻
 * 3、使用推荐引擎推荐物品
 */
public class BasedUserRecommendationTest {
    public static void main(String[] args) {
        //用户-物品-评分数据文件
        String filePath = "data\\alice.txt";
        //数据模型
        DataModel dataModel = null;
        try {
            //文件数据转换成数据模型
            dataModel = new FileDataModel(new File(filePath));
            /**
             * 用户相似度定义
             */
            //余弦相似度
//          UserSimilarity userSimilarity= new UncenteredCosineSimilarity(dataModel);
            //欧几里得相似度
//          UserSimilarity userSimilarity= new EuclideanDistanceSimilarity(dataModel);
            //皮尔森相似度
            UserSimilarity userSimilarity = new PearsonCorrelationSimilarity(dataModel);

            //定义用户的2-最近邻
            UserNeighborhood userNeighborhood = new NearestNUserNeighborhood(2, userSimilarity, dataModel);
            //定义推荐引擎
            Recommender recommender = new GenericUserBasedRecommender(dataModel,userNeighborhood, userSimilarity);
            //从数据模型中获取所有用户ID迭代器
            LongPrimitiveIterator usersIterator = dataModel.getUserIDs();
            //通过迭代器遍历所有用户ID
            while (usersIterator.hasNext()) {
              System.out.println("================================================");
                //用户ID
                long userID = usersIterator.nextLong();
                //用户ID
                LongPrimitiveIterator otherusersIterator = dataModel.getUserIDs();
                //遍历用户ID,计算任何两个用户的相似度
                while (otherusersIterator.hasNext()) {
                    Long otherUserID = otherusersIterator.nextLong();
                    System.out.println("用户 " + userID + " 与用户 " + otherUserID + " 的相似度为:"
                            + userSimilarity.userSimilarity(userID, otherUserID));
                }
                //userID的N-最近邻
                long[] userN = userNeighborhood.getUserNeighborhood(userID);
                //用户userID的推荐物品,最多推荐两个
                List<RecommendedItem> recommendedItems = recommender.recommend(userID, 2);
                System.out.println("用户 "+userID + " 的2-最近邻是 "+ Arrays.toString(userN));
                if (recommendedItems.size() > 0) {
                    for (RecommendedItem item : recommendedItems) {
                        System.out.println("推荐的物品"+ item.getItemID()+"预测评分是 "+ item.getValue());
                    }
                } else {
                    System.out.println("无任何物品推荐");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行上述程序,结果如下:

================================================

用户 1 与用户 1 的相似度为:0.9999999999999998

用户 1 与用户 2 的相似度为:0.8528028654224417

用户 1 与用户 3 的相似度为:0.7071067811865475

用户 1 与用户 4 的相似度为:0.0

用户 1 与用户 5 的相似度为:-0.7921180343813393

用户 1 的2-最近邻是 [2, 3]

推荐的物品105预测评分是 3.9065998

================================================

用户 2 与用户 1 的相似度为:0.8528028654224417

用户 2 与用户 2 的相似度为:1.0

用户 2 与用户 3 的相似度为:0.4677071733467446

用户 2 与用户 4 的相似度为:0.4899559349388647

用户 2 与用户 5 的相似度为:-0.9001487972234673

用户 2 的2-最近邻是 [1, 4]

无任何物品推荐

================================================

用户 3 与用户 1 的相似度为:0.7071067811865475

用户 3 与用户 2 的相似度为:0.4677071733467422

用户 3 与用户 3 的相似度为:1.0

用户 3 与用户 4 的相似度为:-0.16116459280507703

用户 3 与用户 5 的相似度为:-0.466569474815843

用户 3 的2-最近邻是 [1, 2]

无任何物品推荐

================================================

用户 4 与用户 1 的相似度为:0.0

用户 4 与用户 2 的相似度为:0.489955934938866

用户 4 与用户 3 的相似度为:-0.16116459280507558

用户 4 与用户 4 的相似度为:1.0

用户 4 与用户 5 的相似度为:-0.6415029025857746

用户 4 的2-最近邻是 [2, 1]

无任何物品推荐

================================================

用户 5 与用户 1 的相似度为:-0.7921180343813393

用户 5 与用户 2 的相似度为:-0.9001487972234682

用户 5 与用户 3 的相似度为:-0.466569474815843

用户 5 与用户 4 的相似度为:-0.6415029025857751

用户 5 与用户 5 的相似度为:1.0

用户 5 的2-最近邻是 [3, 4]

无任何物品推荐

 

Process finished with exit code 0

到这,我们已经成功为用户1推荐了物品105。

 

六、基于物品的协同过滤推荐

1、基本思想

    尽管基于用户的协同过滤的方法已经成功应用在了不同领域,但在有着数以百万计甚至上亿用户和物品的大型电子商务网站(例如亚马逊Amazon)还是会存在很多严峻挑战。这种方法很难做到实时推荐。下面谈谈与基于用户协同过滤类似的另外一种推荐方法——基于物品的协同过滤推荐。

基于物品的协同过滤推荐主要思想是利用物品间相似度,而不是用户间相似度来计算预测值。我们看用户-物品-评分矩阵的某一列,得到物品101对应的向量v1=(5,3,4,3,1)和物品105对应的向量v5=(?,3,5,4,1),舍弃向量的第一个分量,通过余弦相似度计算出他们的相似度如下:

我们通过计算用户1对所有与物品105相似物品的加权评分总和来预测用户1对物品105的评分,公式如下:

N表示物品p的k-最近邻,同用户的k-最近邻类似,实际中需要选择k值,sim(t,p)表示物品t与物品p的相似度,表示用户a给物品t的评分。Apache Mahout实现时,N采用与物品p相似的所有物品。所以,用户1对物品105的预测评分为:

 

 

2、代码实现

    程序中,我们使用物品余弦相似度,如下:

package com.leboop.recommendation;

import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;

import java.io.File;
import java.util.Arrays;
import java.util.List;

/**
 * 推荐思路
 * 1、读取用户-物品-评分数据,转换成推荐数据模型
 * 2、定义物品相似度(余弦相似度、皮尔森相似度等)
 * 3、预测评分
 * 4、使用推荐引擎推荐物品
 */
public class BasedItemRecommendationTest {
    public static void main(String[] args) {
        //用户-物品-评分数据文件
        String filePath = "data\\alice.txt";
        //数据模型
        DataModel dataModel = null;
        try {
            //文件数据转换成数据模型
            dataModel = new FileDataModel(new File(filePath));
            /**
             * 物品相似度定义
             */
            //余弦相似度
            ItemSimilarity itemSimilarity = new UncenteredCosineSimilarity(dataModel);
            //欧几里得相似度
//            ItemSimilarity itemSimilarity= new EuclideanDistanceSimilarity(dataModel);
//            //皮尔森相似度
//            ItemSimilarity itemSimilarity = new PearsonCorrelationSimilarity(dataModel);
            //定义推荐引擎
            Recommender recommender =new GenericItemBasedRecommender(dataModel, itemSimilarity);
            //获取物品迭代器
            LongPrimitiveIterator itemIDIterator = dataModel.getItemIDs();
            //遍历所有物品
            while(itemIDIterator.hasNext()){
                System.out.println("==================================================");
                Long itermID=itemIDIterator.next();
                LongPrimitiveIterator otherItemIDIterator=dataModel.getItemIDs();
                //打印物品相似度
                while (otherItemIDIterator.hasNext()){
                    Long otherItermID=otherItemIDIterator.next();
                    System.out.println("物品 "+itermID+" 与物品 "+otherItermID+" 的相似度为: "+itemSimilarity.itemSimilarity(itermID,otherItermID));
                }
            }
            //获取用户迭代器
            LongPrimitiveIterator userIDIterator =dataModel.getUserIDs();
            //遍历用户
            while(userIDIterator.hasNext()){
                //获取用户
                Long userID=userIDIterator.next();
                //获取用户userID的推荐列表
                List<RecommendedItem> itemList= recommender.recommend(userID,2);
                if(itemList.size()>0){
                    for(RecommendedItem item:itemList){
                        System.out.println("用户 "+userID+" 推荐物品 "+item.getItemID()+",物品评分 "+item.getValue());
                    }
                }else {
                    System.out.println("用户 "+userID+" 无任何物品推荐");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行上述程序,结果如下:

==================================================

物品 101 与物品 101 的相似度为: 0.9999999999999999

物品 101 与物品 102 的相似度为: 0.7802595923450996

物品 101 与物品 103 的相似度为: 0.8197822947299412

物品 101 与物品 104 的相似度为: 0.9433700705169152

物品 101 与物品 105 的相似度为: 0.9941002434954168

==================================================

物品 102 与物品 101 的相似度为: 0.7802595923450996

物品 102 与物品 102 的相似度为: 1.0

物品 102 与物品 103 的相似度为: 0.9420196895802699

物品 102 与物品 104 的相似度为: 0.8479844150302361

物品 102 与物品 105 的相似度为: 0.7388505791113108

==================================================

物品 103 与物品 101 的相似度为: 0.8197822947299412

物品 103 与物品 102 的相似度为: 0.9420196895802699

物品 103 与物品 103 的相似度为: 1.0

物品 103 与物品 104 的相似度为: 0.7840250892042882

物品 103 与物品 105 的相似度为: 0.7226101216384172

==================================================

物品 104 与物品 101 的相似度为: 0.9433700705169152

物品 104 与物品 102 的相似度为: 0.8479844150302361

物品 104 与物品 103 的相似度为: 0.7840250892042882

物品 104 与物品 104 的相似度为: 0.9999999999999999

物品 104 与物品 105 的相似度为: 0.9395584757365169

==================================================

物品 105 与物品 101 的相似度为: 0.9941002434954168

物品 105 与物品 102 的相似度为: 0.7388505791113108

物品 105 与物品 103 的相似度为: 0.7226101216384172

物品 105 与物品 104 的相似度为: 0.9395584757365169

物品 105 与物品 105 的相似度为: 0.9999999999999999

用户 1 推荐物品 105,物品评分 4.0751815

用户 2 无任何物品推荐

用户 3 无任何物品推荐

用户 4 无任何物品推荐

用户 5 无任何物品推荐

 

Process finished with exit code 0

 

七、协同过滤推荐基本步骤

1、基于用户的协同过滤

(1)采集用户与物品之间的关联数据,如浏览、购买或交易记录,形成初始数据;

(2)分析用户与物品的关联数据形成用户-物品-评分数据;

(3)依据用户-物品-评分数据计算所有用户间的相似度;

(4)选择与当前用户最相似的k个用户,也就是用户k-最近邻

(5)将这k个用户加权评分最高且当前用户没有浏览过的n个物品推荐给当前用户。

 

2、基于物品的协同过滤

(1)采集用户与物品之间的关联数据,如浏览、购买或交易记录,形成初始数据;

(2)分析用户与物品的关联数据形成用户-物品-评分数据;

(3)依据用户-物品-评分数据计算所有物品间的相似度;

(4)对当前用户没浏览过的某个物品,选择最相似的k个物品(k-最近邻

(5)基于这k个物品评分预测当前物品评分;

(6)将评分最高的n个物品推荐给当前用户。

 

八、协同过滤之MapReduce

    当用户或者物品数以亿计时,之前的程序是远远不够的,此时,我们需要使用MapReduce来进行协同过滤推荐。

1、数据

    准备用户-物品-评分数据itemdata.data,如下:

 

将数据文件上传至HDFS文件系统/input/mahout-demo/目录下,如图:

 

2、执行协同过滤MapReduce任务

    在命令窗口执行如下命令,

hadoop jar mahout-examples-0.13.0-job.jar

org.apache.mahout.cf.taste.hadoop.item.RecommenderJob

-i /input/mahout-demo/itemdata.data

-o /output/cf/item

-s SIMILARITY_LOGLIKELIHOOD

--tempDir /tmp/cf/item

部分执行过程如图:

 

参数说明:

(1)-i:指定输入数据文件路径

(2)-o:指定最终输出数据文件路径

(3)-s:指定相似度度量方法,这里使用的是对数似然相似度

(4)--tempDir:指定任务执行的中间数据文件保存目录

任务执行结束,推荐结果如下:

 

例如,第4行数据表明:优先推荐物品577给用户1917281441163686119。

posted @ 2018-08-10 11:10  leboop  阅读(4625)  评论(1编辑  收藏  举报