Slope One 協同過濾算法

1 背景介紹

1.1問題描述

        人們在網上收看電影時,常常會給看過的電影打分。從這些電影的打分情況可以發掘出一個用戶的電影收看偏好。通過發掘出的用戶偏好,可以爲用戶做出準確的電影推薦。在這個問題中,我們需要根據用戶之前的電影打分記錄,來預測該用戶對一部未看過的電影的評分情況

1.2協同過濾

        上面描述的是一個典型的協同過濾推薦問題(Collaborative Filtering recommendation)。協同過濾技術簡單來說就是利用某興趣相投,擁有共同經驗的羣體的喜好來推薦使用者感興趣的資訊,個人透過合作的機制給與資訊一定程度的迴應(如上面說的評分),系統將回應記錄下來以達到過濾的目的,進而幫助別人篩選資訊。迴應不侷限於特別感興趣的,特別不感興趣的資訊記錄也相當重要[1]

          目前協同過濾技術分爲三類:

          (1)基於使用者(User-based)的協同過濾

               主要思想是尋找具有相似愛好或者興趣的相鄰使用者,由相鄰使用者對待預測物品的評分得出目標用戶的可能評分。

          (2)基於物品(Item-based)的協同過濾

               由於用戶的數量可能變化較大,User-based協同過濾算法可能在擴展性上有瓶頸。基於物品的協同過濾做出這樣一個基本假設:能夠引起使用者興趣的項目,必定和之前評分高的項目相似。算法以計算項目之間的相似性來代替使用者的相似性。這個想法由BadrulSarwar等人的一篇論文於2001年提出[2]

         (3)基於模型(Model-based)的協同過濾

              上面兩種方法統稱爲Memory-based的協同過濾技術。基於模型的協同過濾技術利用數據挖掘技術(如貝葉斯模型,決策樹等)對數據進行建模,再用得到的模型進行預測。

2 Slope One介紹

               SlopeOne是一系列Item-based協同過濾算法,具有實現簡單高效,而且精確度和其它複雜費時的算法相比不相上下的特點。其想法由DanielLemire等人於2005年提出[3]

2.1  基本原理

            這裏引用原論文[3]上的一個例子來介紹:

                                                                     

          這裏要預測UserBItemJ的打分情況。SlopeOne的一個基本想法是:平均值可以代替兩個未知物體之間的打分差異。由UserAItemIItemJ的打分情況可以看出,ItemI的平均評分要比ItemJ的平均評分低0.5。這樣,SlopeOne方法認定UserBItemI的打分也比ItemJ的打分低0.5。因此會給問號處填上2.5


2.2帶權重的SlopeOne算法

            現在要通過用戶A對物品JK的打分來預測物品L的打分。如果數據集中同時給JL打分的用戶有2000個,而同時給KL打分的用戶只有20個。直觀來看,在對物品L打分時,用戶A對物品J的打分比對K的打分更具參考價值。因此,計算時就需要考慮不同物品間平均分差的權重。

          有n個人對事物A和事物B打分了,R(A->B)表示這n個人對A和對B打分的平均差(A-B,m個人對事物B和事物C打分了,RC->B)表示這m個人對B和對C打分的平均差(C-B),現在某個用戶對A的打分是ra,對C的打分是rc,那麼AB的打分是[4]

                                                           rb= (n * (ra - R(A->B)) + m * (rc + R(B->C)))/(m+n)

3 算法實現

       RateMatrix.java:  從輸入的數據文件train.txt中讀取數據,構造用戶對電影的打分矩陣。
package common;
import java.io.*;
import java.util.*;
public class RateMatrix {

	private  char[][] rateMat = null;
	HashMap<Integer,Integer> users = new HashMap<Integer,Integer>();
	HashMap<Integer,Integer> movies = new HashMap<Integer,Integer>();
	
	private File train = null;
	public RateMatrix(String path)
	{
		train=new File(path);
	}
	
	
	public char [][] getRateMat(){
		computeRateMat();
		return this.rateMat;
	}
	public HashMap<Integer,Integer> getUsers(){
		return this.users;
	}
	public HashMap<Integer,Integer> getMovies(){
		return this.movies;
	}

	/**
	 * get the ID set of users and movies,
	 * conpute the num of each for the rateMat
	 * @throws IOException
	 */
	void computeUser_Movies() throws IOException
	{
		BufferedReader in=new BufferedReader(new FileReader(train));
		SortedSet<Integer> userset=new TreeSet<Integer>();
		SortedSet<Integer> movieset=new TreeSet<Integer>();
		String line;
		while((line=in.readLine())!=null){
			String[] uID_mID=line.split("\\s+");
			int userId=Integer.parseInt(uID_mID[0]);
			int movieId=Integer.parseInt(uID_mID[1]);
			userset.add(userId);
			movieset.add(movieId);
		}
		
		Iterator<Integer> userIter=userset.iterator();
		int i=0;
		while(userIter.hasNext()){
			users.put(userIter.next(), i++);
		}
		
		i=0;
		Iterator<Integer> movieIter=movieset.iterator();
		while(movieIter.hasNext()){
			movies.put(movieIter.next(), i++);
		}
	}
	/**
	 *  compute the rateMatrix
	 */
	void computeRateMat()
	{
		try {
			computeUser_Movies();
			this.rateMat=new char[this.users.size()][this.movies.size()];
			for(int i=0;i<rateMat.length;i++)
			Arrays.fill(this.rateMat[i], '0');
			BufferedReader buf=new BufferedReader(new FileReader(train));
			
			String line;
			while((line=buf.readLine())!=null){
				String[] uID_mID_rate=line.split("\\s+");
				int userID=Integer.parseInt(uID_mID_rate[0]);
				int movieID=Integer.parseInt(uID_mID_rate[1]);
				char rate=uID_mID_rate[2].charAt(0);
				
				// get the index of the userID in vector of users
				int i=this.users.get(userID);
				// get the index of the movieID in vector of movies
				int j=this.movies.get(movieID);
				this.rateMat[i][j]=rate;
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/** for test 
	 *
	 * */
	void ptMatrix()
	{
		int i=this.users.get(187);
		int j=this.movies.get(2373);
		System.out.println(i+" "+j+" "+this.rateMat[i][j]);
	}
	public static void main(String[] args)
	{
		RateMatrix temp=new RateMatrix("train.txt");
		char[][] mat=temp.getRateMat();
		temp.ptMatrix();
	}
}


         SlopeOne.java:SlopeOne 的算法實現,從打分矩陣中計算各個電影之間的評分平均差和電影對出現的頻率,主要方法是buildmDiffs( )      

package SlopeOne;
import java.util.*;

import common.RateMatrix;


class MDiffRate{
	double diff;
	int num;
	public MDiffRate(double diff,int num)
	{
		this.diff = diff;
		this.num = num;
	}
}
public class SlopeOne {
	char[][] rMat = null;
	HashMap<Integer,Integer> userID = null;
	HashMap<Integer,Integer> movieID = null;
	RateMatrix RateMatrix_Fac = null;
	
	MDiffRate[][] mDiffMatrix = null;
	
	public SlopeOne(String path)
	{
		RateMatrix_Fac = new RateMatrix(path);
		this.rMat = RateMatrix_Fac.getRateMat();
		this.userID = RateMatrix_Fac.getUsers();
		this.movieID = RateMatrix_Fac.getMovies();
		this.mDiffMatrix = new MDiffRate[movieID.size()][movieID.size()];
		System.out.println("loading: "+this.userID.size()+" users,"+this.movieID.size()+"movies.");
	}
	
	public char[][] getRMat()
	{
		return this.rMat;
	}
	
	public HashMap<Integer,Integer> getUserID()
	{
		return this.userID;
	}
	
	public HashMap<Integer,Integer> getMovieID()
	{
		return this.movieID;
	}
	
	public MDiffRate[][] getMDiffs()
	{
		return this.mDiffMatrix;
	}
	
	
	
	void buildmDiffs()
	{
		for(int i=0;i<movieID.size();i++)
		{
			for(int j=0;j<i;j++)
			{
				if(j==i) continue;
				int frequency=0;
				double diffs=0;
				for(int z=0;z<userID.size();z++)
				{
					if(rMat[z][i]!='0'&&rMat[z][j]!='0')
					{
						diffs+=(rMat[z][j]-rMat[z][i]);
						frequency++;
					}
				}
				if(frequency>0)   //have common lines
				{
					diffs=diffs/frequency;
					MDiffRate tempdiff1=new MDiffRate(diffs,frequency);
					MDiffRate tempdiff2=new MDiffRate(-diffs,frequency);
					mDiffMatrix[i][j]=tempdiff1;
					mDiffMatrix[j][i]=tempdiff2;
				}
			}
		}
	}
}


        SlopePredict.java用來對test.txt中的數據進行預測。

package SlopeOne;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;

public class SlopePredict {
	
	File testFile = new File("test.txt");
	File outFile = new File("test.rate");
	
	
	SlopeOne slopeModel = null;
	char[][] rateMat = null;
	HashMap<Integer,Integer> userID = null;
	HashMap<Integer,Integer> movieID = null;
	MDiffRate[][] mDiffMatrix = null;
	
	
    SlopePredict(String trainPath)
	{
		slopeModel = new SlopeOne(trainPath);
		slopeModel.buildmDiffs();
		this.rateMat = slopeModel.getRMat();
		this.userID = slopeModel.getUserID();
		this.movieID = slopeModel.getMovieID();
		this.mDiffMatrix = slopeModel.getMDiffs();
	}
    
    double predict(int userIndex,int movieIndex)
    {
    	double frequencysum=0;
    	double weightsum=0;
    	double ans=0;
    	for(int i=0;i<movieID.size();i++)
    	{
    		if(rateMat[userIndex][i]!='0'){
    			if(mDiffMatrix[movieIndex][i]!=null)
    			{
    			       weightsum += (((double)(rateMat[userIndex][i]-'0'))-mDiffMatrix[movieIndex][i].diff)
    						    *mDiffMatrix[movieIndex][i].num;
    				frequencysum += mDiffMatrix[movieIndex][i].num;
    			}
    		}
    	}
    	ans=weightsum/frequencysum;
    	return ans;
    }
    
    void predict() throws IOException
    {
    	BufferedReader in = new BufferedReader(new FileReader(testFile));
    	BufferedWriter out = new BufferedWriter(new FileWriter(outFile));
    	String line = null;
		int userID_temp,movieID_temp;
		while((line=in.readLine()) != null)
		{
			int userIndex = -1, movieIndex = -1;
			String[] temp = line.split("\\s+");
			userID_temp = Integer.parseInt(temp[0]);
			movieID_temp = Integer.parseInt(temp[1]);
			if(userID.containsKey(userID_temp))
			{
				userIndex = userID.get(userID_temp);
			}
			if(movieID.containsKey(movieID_temp))
			{
			    movieIndex = movieID.get(movieID_temp);
			}
			double ans = -555;
			if(userIndex<0||movieIndex<0)	 		//oops! new user! new movie!
			{
				if(userIndex<0&&movieIndex>=0)
				{
					double sum = 0;
					int num = 0;
					for(int i = 0;i<userID.size();i++)
					{
						if(rateMat[i][movieIndex] != '0')
						{
							sum += rateMat[i][movieIndex]-'0';
							num++;
						}
					}
					ans=sum/num;
				}
				if(userIndex >= 0&&movieIndex < 0)
				{
					double sum = 0;
					int num = 0;
					for(int i=0;i<movieID.size();i++)
					{
						if(rateMat[userIndex][i]!='0')
						{
							sum += rateMat[userIndex][i]-'0';
							num++;
						}
					}
					ans=sum/num;
				}
				if(userIndex<0&&movieIndex<0)
				{
					ans = 3;
				}
			}
			else
			{
				ans = predict(userIndex,movieIndex); //the user and the movie exist in the train                                                                        //set
			}
			long res = Math.round(ans);
			if(res<=0) res = 1;
			if(res>=5) res = 5;
			out.write(res+"\n");
			out.flush();
		}
		out.close();
		in.close();
    }
    
	public static void main(String[] args) {
		// TODO Auto-generated method stub
			SlopePredict sp = new SlopePredict("train.txt");
			try {
				sp.predict();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

}

References:

  1. http://zh.wikipedia.org/wiki/Slope_one

  2. SarwarB, Karypis G, Konstan J, et al. Item-based collaborative filteringrecommendation algorithms[C]//Proceedings of the 10th internationalconference on World Wide Web. ACM, 2001: 285-295.

  3. LemireD, Maclachlan A. Slope one predictors for online rating-basedcollaborative filtering[J]. Society for Industrial Mathematics,2005, 5: 471-480.

  4. http://my.oschina.net/liangtee/blog/124987

PS:這是這學期數據挖掘課的一個作業,之前還有幾個作業。突然想起來把這次作業傳博客上,聊作總結。前面幾次         作業我也打算總結出來,不過估計要等考完試加班那段時間了。說出口的話,就要盡力做到。先在這裏說了,督         促自己最後能好好總結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章