1. 程式人生 > >SlopeOne推薦演算法實現(C++)

SlopeOne推薦演算法實現(C++)

SlopeOne演算法是一個非常簡單的協同過濾演算法,主要思想如下:如果使用者u對物品j打過分,現在要對物品i打分,那麼只需要計算出在同時對物品i和j打分的這種人中,他們的分數之差平均是多少,那麼我們就可以根據這個分數之差來計算使用者u對物品i的打分了,當然,這樣的物品j也有很多個,那有的物品和j共同打分的人少,有的物品和j共同打分的人多,那麼顯而易見,共同打分多的那個物品在評分時所佔的比重應該大一些。

如上就是簡單的SlopeOne演算法的主要思想,用維基百科上的一張圖來表示(一看就懂):


途中使用者B要對物品J進行評分,那麼這時候發現物品i被使用者B打為2分,而同時發現使用者A同時評價了物品i和物品j,且物品i比物品j少了0.5分,那麼由此看來,使用者B給物品j打得分應該就是比給物品i打的分高0.5分,故是2.5分。

由於思想是如此簡單,故我們就來實踐一把,當然這裡就是最最樸素的實現,只是為了檢測下演算法效果如何。。。資料集還是如上篇部落格一樣,用的是movielens裡面的小資料集,其中有1000使用者對2000物品的評分,80%用來訓練,20%用來測試。

具體程式碼如下:

#include <iostream>
#include <string>
#include <fstream>
#include <math.h>
using namespace std;
const int USERMAX = 1000;
const int ITEMMAX = 2000;
double rating[USERMAX][ITEMMAX];
int I[USERMAX][ITEMMAX];//indicate if the item is rated
double mean;

double predict(int u, int l)
{
	double total = 0;
	double totalCnt = 0;
	for (int i = 0; i < ITEMMAX; i++)
	{
		if (l != i&&I[u][i])
		{
			double dev = 0;
			int cnt = 0;
			for (int j = 0; j < USERMAX; j++)
			{
				if (I[j][l] && I[j][i])
				{
					dev += rating[j][i]-rating[j][l];
					cnt++;
				}
			}
			if (cnt)
			{
				dev /= cnt;
				total += (rating[u][i] - dev)*cnt;
				totalCnt += cnt;
			}
		}
	}
	if (totalCnt == 0)
		return mean;
	return total / totalCnt;
}
double calMean()
{
	double total = 0;
	int cnt = 0;
	for (int i = 0; i < USERMAX; i++)
		for (int j = 0; j < ITEMMAX; j++)
		{
			total += I[i][j] * rating[i][j];
			cnt += I[i][j];
		}
	return total / cnt;
}

void train()
{
	//read rating matrix
	memset(rating, 0, sizeof(rating));
	memset(I, 0, sizeof(I));
	ifstream in("ua.base");
	if (!in)
	{
		cout << "file not exist" << endl;
		exit(1);
	}
	int userId, itemId, rate;
	string timeStamp;
	while (in >> userId >> itemId >> rate >> timeStamp)
	{
		rating[userId][itemId] = rate;
		I[userId][itemId] = 1;
	}	
	mean = calMean();
}

void test()
{
	ifstream in("ua.test");
	if (!in)
	{
		cout << "file not exist" << endl;
		exit(1);
	}
	int userId, itemId, rate;
	string timeStamp;
	double total = 0;
	double cnt = 0;
	while (in >> userId >> itemId >> rate >> timeStamp)
	{
		double r = predict(userId, itemId);
		cout << "true: " << rate << " predict: " << r << endl;
		total += (r - rate)*(r - rate);
		cnt += 1;
		//cout << total << endl;
	}
	cout << "test rmse is " << pow(total / cnt, 0.5) << endl;
}
int main()
{
	train();
	test();
	return 0;
}

實驗結果如下:


在測試集上的rmse達到了0.96,而之前一篇部落格實現的svd通過複雜的梯度下降來求最優解也就0.95左右,故SlopeOne演算法是非常簡單有效的,維基百科裡說是最簡潔的協同過濾了,但是我個人覺得類似knn的協同過濾更加好懂啊(只不過在計算使用者相似度等方面麻煩了點)