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 }