協同過濾推薦算法


Collaborative Filtering Recommendation

向量之間的相似度

度量向量之間的相似度方法很多了,你可以用距離(各種距離)的倒數,向量夾角,Pearson相關系數等。

皮爾森相關系數計算公式如下:

\begin{equation}\rho_{X,Y}=\frac{cov(X,Y)}{\sigma_{x}\sigma_{y}}=\frac{E((X-\mu_x)(Y-\mu_y))}{\sigma_{x}\sigma_{y}}\end{equation}

分子是協方差,分母是兩個變量標准差的乘積。顯然要求X和Y的標准差都不能為0。

因為$\mu_{X}=E(X),\sigma^2_{X}=E(X-\mu_X)^2=E(X^2)-E^2(X)$所以皮爾森相關系數計算公式還可以寫成:

\begin{equation}\rho_{X,Y}=\frac{E(XY)-E(X)E(Y)}{\sqrt{E(X^2)-E^2(X)}\sqrt{E(Y^2)-E^2(Y)}}\end{equation}

當兩個變量的線性關系增強時,相關系數趨於1或-1。

Pearson相關系數有個特點,它在計算兩個數列的相似度時忽略其平均值的差異。比如說有的用戶對商品評分普遍偏低,有的用戶評分普遍偏高,而實際上他們具有相同的愛好,他們的Pearson相關系數會比較高。用戶1對某一個商品的評分是X=(1,2,3),用戶2對這三個商品的評分是Y=(4,5,6),則X和Y的Pearson相關系數是0.865,相關性還是挺高的。

 

  iterm1 ………… itemn
user1 R11   R1n
……   Rij  
userm Rm1   Rmn

用戶評分數據矩陣

基於用戶的協同過濾

step1.如果用戶i對項目j沒有評過分,就找到與用戶i最相似的K個鄰居(采用Pearson相關系數)

step2.然后用這K個鄰居對項目j的評分的加權平均來預測用戶i對項目j的評分。

$U_1=(r_{1,1},r_{1,2}...r_{1,n})$

$U_2=(r_{2,1},r_{2,2}...r_{2,n})$

要預測用戶u對商品i的評分$r_{u,i}$

用戶u對所有商品的平均得分為$\bar{r_u}$

用戶x評分的商品集合為$I_x$,用戶y評分的商品集合為$I_y$,其並集為$I_{xy}$

采用Pearson相關系數用戶x和y的相似度:

\begin{equation}sim(x,y)=\frac{\sum_{i\in{I_{xy}}}{(r_{x,i}-\bar{r_x})(r_{y,i}-\bar{r_y})}}{\sqrt{\sum_{i\in{I_{xy}}}{(r_{x,i}-\bar{r_x})^2}\sum_{i\in{I_{xy}}}{(r_{y,i}-\bar{r_y})^2}}}\end{equation}

則\begin{equation}r_{u,i}=\bar{r_u}+z\sum_{u'\in{U}}{sim(u,u')(r_{u',i}-\bar{r_{u'}})}\end{equation}

其中U是用戶u的近鄰,z是歸一化因子,$z=\frac{1}{\sum_{u'\in{U}}{sim(u,u')}}$

 這各預測方法充分考慮了用戶一向的評分習慣是偏高還是偏低,因為用戶u的近鄰對u產生影響時已經減去了各自的平均值。

計算用戶$U_1$和$U_2$的相似度時並不是去拿原始的評分向量去計算,而是只關注他們的評交集$I_{x,y}$,這是因為一個用戶只對很少的物品有過評分,這樣用戶評分向量是個高度稀疏的向量,采用Pearson相關系數計算兩個用戶的相似度時很不准。

基於物品的協同過濾

step1.如果用戶i對項目j沒有評過分,就把$r_{i,j}$置為0。找到與物品j最相似的k個近鄰(采用余弦距離)

step2.然后用這K個鄰居對項目j的評分的加權平均來預測用戶i對項目j的評分。

$I_1=(r_{1,1},r_{2,1}...r_{m,1})$

$I_2=(r_{1,2},r_{2,2}...r_{m,2})$

每一項減去各個用戶評分的均值:

$I_1=(r_{1,1}-\bar{r_1},r_{2,1}-\bar{r_2}...r_{m,1}-\bar{r_m})$

$I_2=(r_{1,2}-\bar{r_1},r_{2,2}-\bar{r_2}...r_{m,2}-\bar{r_m})$

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

\begin{equation}sim(i,j)=\frac{\sum_x{(r_{x,i}-\bar{r_x})(r_{x,j}-\bar{r_x})}}{\sqrt{\sum_x{(r_{x,i}-\bar{r_x})^2}\sum_x{(r_{x,j}-\bar{r_x})^2}}}\end{equation}

預測用戶u對商品i的評分:

\begin{equation}r_{u,i}=\frac{\sum_{i'\in{N}}{sim(i,i')r_{u,i'}}}{\sum_{i'\in{N}}{sim(i,i')}}\end{equation}

其中N是商品i的近鄰。

由於物品之間的相似度比較穩定,可以離線先算好,定期更新即可。在電商行業這種算法用的比較多。

混合協同過濾

所謂的混合算法,主體思路還是基於用戶的協同過濾,只是在計算兩個用戶的相似度時又嵌套了item-based CF思想。

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

1.用戶i參與評分的項目集合為$I_i$,用戶j參與評分的項目集合為$I_j$,找到它們的並集$U_{ij}=I_i\cup{I_j}$

2.在集合$U_{ij}$中用戶i未評分的項目是$N_i=U_{ij}-I_i$,采用item-based CF方法重新估計用戶i對$N_i$中每個項目的評分。

3.這樣用戶i和j對$U_{ij}$的評分就都是非0值了,在此情況下計算他們的相似度。

示例代碼:

  1 #include<iostream>
  2 #include<queue>
  3 #include<cmath>
  4 #include<cassert>
  5 #include<cstdlib>
  6 #include<fstream>
  7 #include<sstream>
  8 #include<vector>
  9 #include<algorithm>
 10 
 11 using namespace std;
 12 
 13 const int ITERM_SIZE=1682;
 14 const int USER_SIZE=943;
 15 const int V=15;        //ITERM的最近鄰居數
 16 const int S=10;        //USER的最近鄰居數
 17 
 18 struct MyPair{
 19     int id;
 20     double value;
 21     MyPair(int i=0,double v=0):id(i),value(v){}
 22 };
 23 
 24 struct cmp{
 25     bool operator() (const MyPair & obj1,const MyPair & obj2)const{
 26         return obj1.value < obj2.value;
 27     }
 28 };
 29 
 30 double rate[USER_SIZE][ITERM_SIZE];    //評分矩陣
 31 MyPair nbi[ITERM_SIZE][V];            //存放每個ITERM的最近鄰居
 32 MyPair nbu[USER_SIZE][S];            //存放每個USER的最近鄰居
 33 double rate_avg[USER_SIZE];            //每個用戶的平均評分
 34 
 35 //從文件中讀入評分矩陣
 36 int readRate(string filename){
 37     ifstream ifs;
 38     ifs.open(filename.c_str());
 39     if(!ifs){
 40         cerr<<"error:unable to open input file "<<filename<<endl;
 41         return -1;
 42     }
 43     string line;
 44     while(getline(ifs,line)){
 45         string str1,str2,str3;
 46         istringstream strstm(line);
 47         strstm>>str1>>str2>>str3;
 48         int userid=atoi(str1.c_str());
 49         int itermid=atoi(str2.c_str());
 50         double rating=atof(str3.c_str());
 51         rate[userid-1][itermid-1]=rating;
 52         line.clear();
 53     }
 54     ifs.close();
 55     return 0;
 56 }
 57 
 58 //計算每個用戶的平均評分
 59 void getAvgRate(){
 60     for(int i=0;i<USER_SIZE;++i){
 61         double sum=0;
 62         for(int j=0;j<ITERM_SIZE;++j)
 63             sum+=rate[i][j];
 64         rate_avg[i]=sum/ITERM_SIZE;
 65     }
 66 }
 67 
 68 //計算兩個向量的皮爾森相關系數
 69 double getSim(const vector<double> &vec1,const vector<double> &vec2){
 70     int len=vec1.size();
 71     assert(len==vec2.size());
 72     double sum1=0;
 73     double sum2=0;
 74     double sum1_1=0;
 75     double sum2_2=0;
 76     double sum=0;
 77     for(int i=0;i<len;i++){
 78         sum+=vec1[i]*vec2[i];
 79         sum1+=vec1[i];
 80         sum2+=vec2[i];
 81         sum1_1+=vec1[i]*vec1[i];
 82         sum2_2+=vec2[i]*vec2[i];
 83     }
 84     double ex=sum1/len;
 85     double ey=sum2/len;
 86     double ex2=sum1_1/len;
 87     double ey2=sum2_2/len;
 88     double exy=sum/len;
 89     double sdx=sqrt(ex2-ex*ex);
 90     double sdy=sqrt(ey2-ey*ey);
 91     assert(sdx!=0 && sdy!=0);
 92     double sim=(exy-ex*ey)/(sdx*sdy);
 93     return sim;
 94 }
 95 
 96 //計算每個ITERM的最近鄰
 97 void getNBI(){
 98     for(int i=0;i<ITERM_SIZE;++i){
 99         vector<double> vec1;
100         priority_queue<MyPair,vector<MyPair>,cmp> neighbour;
101         for(int k=0;k<USER_SIZE;k++)
102             vec1.push_back(rate[k][i]);
103         for(int j=0;j<ITERM_SIZE;j++){
104             if(i==j)
105                 continue;
106             vector<double> vec2;
107             for(int k=0;k<USER_SIZE;k++)
108                 vec2.push_back(rate[k][j]);
109             double sim=getSim(vec1,vec2);
110             MyPair p(j,sim);
111             neighbour.push(p);
112         }
113         for(int j=0;j<V;++j){
114             nbi[i][j]=neighbour.top();
115             neighbour.pop();
116         }
117     }
118 }
119 
120 //預測用戶對未評分項目的評分值
121 double getPredict(const vector<double> &user,int index){
122     double sum1=0;
123     double sum2=0;
124     for(int i=0;i<V;++i){
125         int neib_index=nbi[index][i].id;
126         double neib_sim=nbi[index][i].value;
127         sum1+=neib_sim*user[neib_index];
128         sum2+=fabs(neib_sim);
129     }
130     return sum1/sum2;
131 }
132 
133 //計算兩個用戶的相似度
134 double getUserSim(const vector<double> &user1,const vector<double> &user2){
135     vector<double> vec1;
136     vector<double> vec2;
137     int len=user1.size();
138     assert(len==user2.size());
139     for(int i=0;i<len;++i){
140         if(user1[i]!=0 || user2[i]!=0){
141             if(user1[i]!=0)
142                 vec1.push_back(user1[i]);
143             else
144                 vec1.push_back(getPredict(user1,i));
145             if(user2[i]!=0)
146                 vec2.push_back(user2[i]);
147             else
148                 vec2.push_back(getPredict(user2,i));
149         }
150     }
151     return getSim(vec1,vec2);
152 }
153 
154 //計算每個USER的最近鄰
155 void getNBU(){
156     for(int i=0;i<USER_SIZE;++i){
157         vector<double> user1;
158         priority_queue<MyPair,vector<MyPair>,cmp> neighbour;
159         for(int k=0;k<ITERM_SIZE;++k)
160             user1.push_back(rate[i][k]);
161         for(int j=0;j<USER_SIZE;++j){
162             if(j==i)
163                 continue;
164             vector<double> user2;
165             for(int k=0;k<ITERM_SIZE;++k)
166                 user2.push_back(rate[j][k]);
167             double sim=getUserSim(user1,user2);
168             MyPair p(j,sim);
169             neighbour.push(p);
170         }
171         for(int j=0;j<S;++j){
172             nbu[i][j]=neighbour.top();
173             neighbour.pop();
174         }
175     }
176 }
177             
178 //產生推薦,預測某用戶對某項目的評分
179 double predictRate(int user,int iterm){
180     double sum1=0;
181     double sum2=0;
182     for(int i=0;i<S;++i){
183         int neib_index=nbu[user][i].id;
184         double neib_sim=nbu[user][i].value;
185         sum1+=neib_sim*(rate[neib_index][iterm]-rate_avg[neib_index]);
186         sum2+=fabs(neib_sim);
187     }
188     return rate_avg[user]+sum1/sum2;
189 }
190 
191 //測試
192 int main(){
193     string file="/home/orisun/DataSet/movie-lens-100k/u.data";
194     if(readRate(file)!=0){
195         return -1;
196     }
197     getAvgRate();
198     getNBI();
199     getNBU();
200     while(1){
201         cout<<"please input user index and iterm index which you want predict"<<endl;
202         int user,iterm;
203         cin>>user>>iterm;
204         cout<<predictRate(user,iterm)<<endl;
205     }
206     return 0;
207 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM