一、计算平均偏差
u - user,用户
i,j - 商品
u(i,j) - 同时评价过i,j商品的用户集合
card - 数目
R(u,j) - 用户u对商品j的评分
二、基础Slope One
上述式子即为得到的评分偏差,接下来提出最简单的slope one预测评分的式子。
三、带权重的Slope One
在计算用户u对商品i进行评估时,用户u对j,k进行过评分,j商品共有2000人同时也对i评分了,而k商品仅有200人,明显使用j商品进行评估更合理,所以这里使用了权重。
四、双极性Slope One
先将用户对某个物品的评分分为like,dislike两类。通过评分是否大于该用户自己的平均评分。
类似地,可以定义对item ii 和 jj 具有相同喜好的用户集合
利用上面的定义,我们可以使用下面的公式为(like或dislike的item)获得新的偏差值:
这样可以计算从item ii 计算得到的预测值:
最终 Bi-Polar Slope One 的预测公式为:
参考: http://www.cnblogs.com/breezedeus/archive/2011/03/11/1981781.html
五、评测指标
这里采用RMSE作为评测指标。计算公式为:
或者
C++实现(权重slope-one实现):
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 #include<fstream> 5 #include<vector> 6 #include<cmath> 7 #include<set> 8 9 using namespace std; 10 11 const int N = 7448; 12 int userMax = 0; 13 int itemMax = 0; 14 int r[N][N]; //记录评分 15 int user_t[N]; //记录测试集的数据 16 int item_t[N]; 17 int rate_t[N]; 18 set<int> test; //记录测试集行号 19 20 void getTest(int m,int n){ //得到测试集的行号 21 while(test.size()<m){ 22 test.insert(rand()%n); 23 } 24 } 25 26 vector<string> split(string str,string pattern) 27 { 28 string::size_type pos; 29 vector<string> result; 30 str+=pattern;//扩展字符串以方便操作 31 int size=str.size(); 32 33 for(int i=0; i<size; i++) 34 { 35 pos=str.find(pattern,i); 36 if(pos<size) 37 { 38 std::string s=str.substr(i,pos-i); 39 result.push_back(s); 40 i=pos+pattern.size()-1; 41 } 42 } 43 return result; 44 } 45 46 int main() 47 { 48 ifstream data("train_small_2.txt"); 49 50 string buffer; 51 vector<string> temp; 52 53 getTest(N/5,N); 54 memset(r,0,sizeof(r)); 55 56 int t = 0; 57 while(!data.eof()){ //数据处理,将一份数据分成训练集与测试集 58 getline(data,buffer); 59 temp = split(buffer," "); 60 int i = atoi(temp[0].c_str()); 61 int j = atoi(temp[1].c_str()); 62 //cout << i << " " << j <<endl; 63 if(test.find(t)!=test.end()){ 64 user_t[t] = i; 65 item_t[t] = j; 66 rate_t[t] = atoi(temp[2].c_str()); 67 }else{ 68 if(i > userMax) userMax = i; 69 if(j > itemMax) itemMax = j; 70 r[i][j] = atoi(temp[2].c_str()); 71 } 72 t++; 73 } 74 set<int>::iterator it = test.begin(); 75 double rmse = 0; 76 int num = 0; 77 for(;it!=test.end();it++){ // uesr_t item_t 78 int t = *it; 79 int count = 0; 80 int sum = 0; 81 for(int i=1;i<=itemMax;i++){//对于user_t[t],找到他评价的所有商品 i 82 if(r[user_t[t]][i]!=0){ 83 int cnt = 0; 84 int dev = 0; 85 for(int j=1;j<=userMax;j++){//找到同时评价item[t]与i的用户 86 if(r[j][item_t[t]]!=0 && r[j][i]!=0){ 87 dev += (-r[j][i]+r[j][item_t[t]]); //求出偏差和 88 cnt++; 89 } 90 } 91 sum += (dev+r[user_t[t]][i]*cnt); //加上对应权重的初始值 92 count += cnt; //算出参与计算的样例总数 93 } 94 } 95 double ans = sum*1.0/count; 96 if(ans>=0 && ans <=5){ //由于样例少会出现不能计算的样例的情况,排除这些 97 rmse += pow((ans - rate_t[t]),2); 98 num++; 99 cout << user_t[t] <<" "<< item_t[t] <<" "<< ans << endl; 100 } 101 } 102 rmse = sqrt(rmse/num); 103 cout << "RMSE:" << rmse << endl; 104 }
运行结果:
RMSE:0.995244
可能数据集小的原因,得到的效果很好..