协同过滤推荐算法-----向量之间的相似度

Collaborative Filtering Recommendation

度量向量之间的相似度方法很多了,你可以用距离(各种距离)的倒数,向量夹角,Pearson相关系数等。

皮尔森相关系数计算公式如下:

ρX,Y=cov(X,Y)σxσy=E((Xμx)(Yμy))σxσy(1)

分子是协方差,分子是两个变量标准差的乘积。显然要求X和Y的标准差都不能为0。

因为 μX=E(X),σ2X=E(XμX)2=E(X2)E2(X) 所以皮尔森相关系数计算公式还可以写成:

ρX,Y=E(XY)E(X)E(Y)E(X2)E2(X)E(Y2)E2(Y)(2)

当两个变量的线性关系增强时,相关系数趋于1或-1。

Pearson相关系数有个特点,它在计算两个数列的相似度时忽略其平均值的差异。比如说有的用户对商品评分普遍偏低,有的用户评分普遍偏高,而实际上他们具有相同的爱好,他们的Pearson相关系数会比较高。用户1对某一个商品的评分是X=(1,2,3),用户2对这三个商品的评分是Y=(4,5,6),则X和Y的Pearson相关系数是0.865,相关性还是挺高的。

 

 iterm1…………itemn
user1R11 R1n
…… Rij 
usermRm1 Rmn

用户评分数据矩阵

基于用户的协同过滤

step1.如果用户i对项目j没有评过分,就找到与用户i最相似的K个邻居(采用Pearson相关系数)

step2.然后用这K个邻居对项目j的评分的加权平均来预测用户i对项目j的评分。

U1=(r1,1,r1,2...r1,n)

U2=(r2,1,r2,2...r2,n)

要预测用户u对商品i的评分 ru,i

用户u对所有商品的平均得分为 ru¯

用户x评分的商品集合为 Ix ,用户y评分的商品集合为 Iy ,其并集为 Ixy

采用Pearson相关系数用户x和y的相似度:

sim(x,y)=iIxy(rx,irx¯)(ry,iry¯)iIxy(rx,irx¯)2iIxy(ry,iry¯)2(3)

ru,i=ru¯+zuUsim(u,u)(ru,iru¯)(4)

其中U是用户u的近邻,z是归一化因子, z=1uUsim(u,u)

 这各预测方法充分考虑了用户一向的评分习惯是偏高还是偏低,因为用户u的近邻对u产生影响时已经减去了各自的平均值。

计算用户 U1 U2 的相似度时并不是去拿原始的评分向量去计算,而是只关注他们的评交集 Ix,y ,这是因为一个用户只对很少的物品有过评分,这样用户评分向量是个高度稀疏的向量,采用Pearson相关系数计算两个用户的相似度时很不准。

基于物品的协同过滤

step1.如果用户i对项目j没有评过分,就把 ri,j 置为0。找到与物品j最相似的k个近邻(采用余弦距离)

step2.然后用这K个邻居对项目j的评分的加权平均来预测用户i对项目j的评分。

I1=(r1,1,r2,1...rm,1)

I2=(r1,2,r2,2...rm,2)

每一项减去各个用户评分的均值:

I1=(r1,1r1¯,r2,1r2¯...rm,1rm¯)

I2=(r1,2r1¯,r2,2r2¯...rm,2rm¯)

商品i和j之间的相似度采用余弦计算:

sim(i,j)=x(rx,irx¯)(rx,jrx¯)x(rx,irx¯)2x(rx,jrx¯)2(5)

预测用户u对商品i的评分:

ru,i=iNsim(i,i)ru,iiNsim(i,i)(6)

其中N是商品i的近邻。

由于物品之间的相似度比较稳定,可以离线先算好,定期更新即可。在电商行业这种算法用的比较多。

混合协同过滤

所谓的混合算法,主体思路还是基于用户的协同过滤,只是在计算两个用户的相似度时又嵌套了item-based CF思想。

度量用户i和用户j相似度更好的方法是:

1.用户i参与评分的项目集合为 Ii ,用户j参与评分的项目集合为 Ij ,找到它们的并集 Uij=IiIj

2.在集合 Uij 中用户i未评分的项目是 Ni=UijIi ,采用item-based CF方法重新估计用户i对 Ni 中每个项目的评分。

3.这样用户i和j对 Uij 的评分就都是非0值了,在此情况下计算他们的相似度。

示例代码:

#include<iostream>
#include<queue>
#include<cmath>
#include<cassert>
#include<cstdlib>
#include<fstream>
#include<sstream>
#include<vector>
#include<algorithm>

using namespace std;

const int ITERM_SIZE=1682;
const int USER_SIZE=943;
const int V=15;        //ITERM的最近邻居数
const int S=10;        //USER的最近邻居数

struct MyPair{
    int id;
    double value;
    MyPair(int i=0,double v=0):id(i),value(v){}
};

struct cmp{
    bool operator() (const MyPair & obj1,const MyPair & obj2)const{
        return obj1.value < obj2.value;
    }
};

double rate[USER_SIZE][ITERM_SIZE];    //评分矩阵
MyPair nbi[ITERM_SIZE][V];            //存放每个ITERM的最近邻居
MyPair nbu[USER_SIZE][S];            //存放每个USER的最近邻居
double rate_avg[USER_SIZE];            //每个用户的平均评分

//从文件中读入评分矩阵
int readRate(string filename){
    ifstream ifs;
    ifs.open(filename.c_str());
    if(!ifs){
        cerr<<"error:unable to open input file "<<filename<<endl;
        return -1;
    }
    string line;
    while(getline(ifs,line)){
        string str1,str2,str3;
        istringstream strstm(line);
        strstm>>str1>>str2>>str3;
        int userid=atoi(str1.c_str());
        int itermid=atoi(str2.c_str());
        double rating=atof(str3.c_str());
        rate[userid-1][itermid-1]=rating;
        line.clear();
    }
    ifs.close();
    return 0;
}

//计算每个用户的平均评分
void getAvgRate(){
    for(int i=0;i<USER_SIZE;++i){
        double sum=0;
        for(int j=0;j<ITERM_SIZE;++j)
            sum+=rate[i][j];
        rate_avg[i]=sum/ITERM_SIZE;
    }
}

//计算两个向量的皮尔森相关系数
double getSim(const vector<double> &vec1,const vector<double> &vec2){
    int len=vec1.size();
    assert(len==vec2.size());
    double sum1=0;
    double sum2=0;
    double sum1_1=0;
    double sum2_2=0;
    double sum=0;
    for(int i=0;i<len;i++){
        sum+=vec1[i]*vec2[i];
        sum1+=vec1[i];
        sum2+=vec2[i];
        sum1_1+=vec1[i]*vec1[i];
        sum2_2+=vec2[i]*vec2[i];
    }
    double ex=sum1/len;
    double ey=sum2/len;
    double ex2=sum1_1/len;
    double ey2=sum2_2/len;
    double exy=sum/len;
    double sdx=sqrt(ex2-ex*ex);
    double sdy=sqrt(ey2-ey*ey);
    assert(sdx!=0 && sdy!=0);
    double sim=(exy-ex*ey)/(sdx*sdy);
    return sim;
}

//计算每个ITERM的最近邻
void getNBI(){
    for(int i=0;i<ITERM_SIZE;++i){
        vector<double> vec1;
        priority_queue<MyPair,vector<MyPair>,cmp> neighbour;
        for(int k=0;k<USER_SIZE;k++)
            vec1.push_back(rate[k][i]);
        for(int j=0;j<ITERM_SIZE;j++){
            if(i==j)
                continue;
            vector<double> vec2;
            for(int k=0;k<USER_SIZE;k++)
                vec2.push_back(rate[k][j]);
            double sim=getSim(vec1,vec2);
            MyPair p(j,sim);
            neighbour.push(p);
        }
        for(int j=0;j<V;++j){
            nbi[i][j]=neighbour.top();
            neighbour.pop();
        }
    }
}

//预测用户对未评分项目的评分值
double getPredict(const vector<double> &user,int index){
    double sum1=0;
    double sum2=0;
    for(int i=0;i<V;++i){
        int neib_index=nbi[index][i].id;
        double neib_sim=nbi[index][i].value;
        sum1+=neib_sim*user[neib_index];
        sum2+=fabs(neib_sim);
    }
    return sum1/sum2;
}

//计算两个用户的相似度
double getUserSim(const vector<double> &user1,const vector<double> &user2){
    vector<double> vec1;
    vector<double> vec2;
    int len=user1.size();
    assert(len==user2.size());
    for(int i=0;i<len;++i){
        if(user1[i]!=0 || user2[i]!=0){
            if(user1[i]!=0)
                vec1.push_back(user1[i]);
            else
                vec1.push_back(getPredict(user1,i));
            if(user2[i]!=0)
                vec2.push_back(user2[i]);
            else
                vec2.push_back(getPredict(user2,i));
        }
    }
    return getSim(vec1,vec2);
}

//计算每个USER的最近邻
void getNBU(){
    for(int i=0;i<USER_SIZE;++i){
        vector<double> user1;
        priority_queue<MyPair,vector<MyPair>,cmp> neighbour;
        for(int k=0;k<ITERM_SIZE;++k)
            user1.push_back(rate[i][k]);
        for(int j=0;j<USER_SIZE;++j){
            if(j==i)
                continue;
            vector<double> user2;
            for(int k=0;k<ITERM_SIZE;++k)
                user2.push_back(rate[j][k]);
            double sim=getUserSim(user1,user2);
            MyPair p(j,sim);
            neighbour.push(p);
        }
        for(int j=0;j<S;++j){
            nbu[i][j]=neighbour.top();
            neighbour.pop();
        }
    }
}
            
//产生推荐,预测某用户对某项目的评分
double predictRate(int user,int iterm){
    double sum1=0;
    double sum2=0;
    for(int i=0;i<S;++i){
        int neib_index=nbu[user][i].id;
        double neib_sim=nbu[user][i].value;
        sum1+=neib_sim*(rate[neib_index][iterm]-rate_avg[neib_index]);
        sum2+=fabs(neib_sim);
    }
    return rate_avg[user]+sum1/sum2;
}

//测试
int main(){
    string file="/home/orisun/DataSet/movie-lens-100k/u.data";
    if(readRate(file)!=0){
        return -1;
    }
    getAvgRate();
    getNBI();
    getNBU();
    while(1){
        cout<<"please input user index and iterm index which you want predict"<<endl;
        int user,iterm;
        cin>>user>>iterm;
        cout<<predictRate(user,iterm)<<endl;
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值