日撸代码300行:第69-70天(矩阵分解)

 代码来自闵老师”日撸 Java 三百行(61-70天)

日撸 Java 三百行(61-70天,决策树与集成学习)_闵帆的博客-CSDN博客

理解推荐系统中的矩阵分解参考了:推荐系统中的矩阵分解技术 - 知乎 (zhihu.com)

预测是通过分解后的用户和项目子矩阵相应位置相乘,得到的分数就是推荐的打分。

updateNoRegular()方法是子矩阵更新,是核心代码。这里对应的是上文提到的知乎参考文章中的“随机梯度下降法”。更新的公式如下:

日撸代码300行:第69-70天(矩阵分解)_第1张图片

 rsme()和mae()两个方法分别是计算均方误差和绝对误差。

Triple类是三重矩阵的数据结构,用于存储不同类型的三种数据类型。

package machinelearning.recommendersystem;

import java.util.Random;
import java.io.*;

/**
 * Matrix factorization for recommender systems.
 * 
 * @author WX873
 */
public class MatrixFactorization {
	/**
	 * Used to generate random numbers.
	 */
	Random rand = new Random();
	
	/**
	 * Number of users.
	 */
	int numUsers;
	
	/**
	 * Number of items.
	 */
	int numItems;
	
	/**
	 * Number of ratings.
	 */
	int numRatings;
	
	/**
	 * Training data.
	 */
	Triple[] dataset;
	
	/**
	 * A parameter for controlling learning regular.
	 */
	double alpha;
	
	/**
	 * A parameter for controlling the learning speed.
	 */
	double lambda;
	
	/**
	 * The low rank of the small matrices.
	 */
	int rank;
	
	/**
	 * The user matrix U.
	 */
	double[][] userSubspace;
	
	/**
	 * The item matrix V.
	 */
	double[][] itemSubspace;
	
	/**
	 * The lower bound of the rating value.
	 */
	double ratingLowerBound;
	
	/**
	 * The upper bound of the rating value.
	 */
	double ratingUpperBound;
	
	/**
	 * *********************************************************************
	 * The first constructor.
	 * 
	 * @param paraFilename   The data filename.
	 * @param paraNumUsers   The number of users.
	 * @param paraNumItems   The number of items.
	 * @param paraNumRatings   The number of ratings.
	 * @param paraRatingLowerBound
	 * @param paraRatingUpperBound
	 * *********************************************************************
	 */
	public MatrixFactorization(String paraFilename, int paraNumUsers, int paraNumItems, 
			int paraNumRatings, double paraRatingLowerBound, double paraRatingUpperBound) {
		// TODO Auto-generated constructor stub
		numUsers = paraNumUsers;
		numItems = paraNumItems;
		numRatings = paraNumRatings;
		ratingLowerBound = paraRatingLowerBound;
		ratingUpperBound = paraRatingUpperBound;
		
		try {
			readData(paraFilename, paraNumUsers, paraNumItems, paraNumRatings);
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println("File " + paraFilename + " cannot be read! " + e);
			System.exit(0);
		}//of try
	}//of the first constructor
	
	/**
	 * **********************************************************************
	 *  Set parameters.
	 *  
	 * @param paraRank  The given rank.
	 * @param paraAlpha
	 * @param paraLambda
	 * **********************************************************************
	 */
	public void setParameters(int paraRank, double paraAlpha, double paraLambda) {
		rank = paraRank;
		alpha = paraAlpha;
		lambda = paraLambda;
	}//of setParameters
	
	/**
	 * ************************************************************
	 * Read the data from the file.
	 * 
	 * @param paraFilename
	 * @param paraNumUsers  number of users
	 * @param paraNumItems  number of items
	 * @param paraNumRatings  number of ratings
	 * @throws IOException
	 * ************************************************************
	 */
	public void readData(String paraFilename, int paraNumUsers, int paraNumItems, int paraNumRatings) throws IOException {
		File tempFile = new File(paraFilename);
		if (!tempFile.exists()) {
			System.out.println("File " + paraFilename + " does not exists.");
			System.exit(0);
		}//of if
		BufferedReader tempBufferReader = new BufferedReader(new FileReader(tempFile));
		
		//Allocate space.
		dataset = new Triple[paraNumRatings];
		String tempString;
		String[] tempStringArray;
		
		for (int i = 0; i < paraNumRatings; i++) {
			tempString = tempBufferReader.readLine();
			tempStringArray = tempString.split(",");
			dataset[i] = new Triple(Integer.parseInt(tempStringArray[0]), 
					Integer.parseInt(tempStringArray[1]), Double.parseDouble(tempStringArray[2]));
		}//of for i
		tempBufferReader.close();
	}//of readData
	
	/**
	 * *******************************************************
	 * Initialize subspaces. Each value is in [0, 1].
	 * *******************************************************
	 */
	void initializeSubspaces() {
		
		userSubspace = new double[numUsers][rank];
		for (int i = 0; i < numUsers; i++) {
			for (int j = 0; j < rank; j++) {
				userSubspace[i][j] = rand.nextDouble();
			}//of for j
		}//of for i
		
		itemSubspace = new double[numItems][rank];
		for (int i = 0; i < numItems; i++) {
			for (int j = 0; j < rank; j++) {
				itemSubspace[i][j] = rand.nextDouble();
			}//of for j
		}//of for i
		
	}//of initializeSubspaces
	
	/**
	 * ***************************************************
	 * Predict the rating of the user to the item
	 * @param paraUser  The user index.
	 * @param paraItem  The item index.
	 * @return
	 * ***************************************************
	 */
	public double predict(int paraUser, int paraItem) {
		double resultValue = 0;
		
		for (int i = 0; i < rank; i++) {
			// The row vector of an user and the column vector of an item
			resultValue += userSubspace[paraUser][i] * itemSubspace[paraItem][i];
		}//of for i
		
		return resultValue;
	}//of predict
	
	/**
	 * ******************************************************
	 * Train.
	 * @param paraRounds  The number of rounds.
	 * *****************************************************
	 */
	public void train(int paraRounds) {
		initializeSubspaces();
		
		for (int i = 0; i < paraRounds; i++) {
			updateNoRegular();
			if (i % 50 == 0) {
				// Show the process
				System.out.println("Round " + i);
				System.out.println("MAE: " + mae());
			}//of if
		}//of for i
	}//of train
	
	/**
	 * **************************************************
	 * Update sub-spaces using the training data.
	 * **************************************************
	 */
	public void updateNoRegular() {
		for (int i = 0; i < numRatings; i++) {
			int tempUserId = dataset[i].user;
			int tempItemId = dataset[i].item;
			double tempRate = dataset[i].rating;
			
			double tempResidual = tempRate - predict(tempUserId, tempItemId); // Residual
			//这里用的是随机梯度下降法
			// Update user subspace
			double tempValue = 0;
			for (int j = 0; j < rank; j++) {
				tempValue = 2 * tempResidual * itemSubspace[tempItemId][j];
				userSubspace[tempUserId][j] += alpha * tempValue;
			}//of for j
			
			// Update item subspace
			for (int j = 0; j < rank; j++) {
				tempValue = 2 * tempResidual * userSubspace[tempUserId][j];
				itemSubspace[tempItemId][j] += alpha * tempValue;
			}//of for j
		}//of for i
	}//of updateNoRegular
	
	/**
	 * ************************************************
	 * Compute the RSME.
	 * 
	 * @return   RSME of the current factorization.
	 * ************************************************
	 */
	public double rsme() {
		double resultRsme = 0;
		int tempTestCount = 0;
		
		for (int i = 0; i < numRatings; i++) {
			int tempUserIndex = dataset[i].user;
			int tempItemIndex = dataset[i].item;
			double tempRate = dataset[i].rating;
			
			double tempPrediction = predict(tempUserIndex, tempItemIndex);
			
			if (tempPrediction < ratingLowerBound) {
				tempPrediction = ratingLowerBound;
			} else if (tempPrediction > ratingUpperBound) {
				tempPrediction = ratingUpperBound;
			}//of if
			
			double tempError = tempRate - tempPrediction;
			resultRsme += tempError * tempError;
			tempTestCount++;
		}//of for i
		
		return Math.sqrt(resultRsme / tempTestCount);
	}//of rsme
	
	/**
	 * *********************************************************
	 * Compute the MAE.
	 * 
	 * @return  MAE of the current factorization.
	 * *********************************************************
	 */
	public double mae() {
		double resultMae = 0;
		int tempTestCount = 0;
		for (int i = 0; i < numRatings; i++) {
			int tempUserIndex = dataset[i].user;
			int tempItemIndex = dataset[i].item;
			double tempRate = dataset[i].rating;
			
			double tempPrediction = predict(tempUserIndex, tempItemIndex);
			
			if (tempPrediction < ratingLowerBound) {
				tempPrediction = ratingLowerBound;
			} else if (tempPrediction > ratingUpperBound) {
				tempPrediction = ratingUpperBound;
			}//of if
			
			double tempError = tempRate - tempPrediction;
			resultMae += Math.abs(tempError);
			tempTestCount++;
		}//of for i
		
		return resultMae / tempTestCount;
	}//of mae
	
	
	public static void testTrainingTesting(String paraFilename, int paraNumUsers, int paraNumItems,
			int paraNumRatings, double paraRatingLowerBound, double paraRatingUpperBound,int paraRounds) {
		try {
			// Step 1. read the training and testing data.
			MatrixFactorization tempMF = new MatrixFactorization(paraFilename, paraNumUsers, 
					paraNumItems, paraNumRatings, paraRatingLowerBound, paraRatingUpperBound);
			
			tempMF.setParameters(5, 0.0001, 0.005);
						
			// Step 2. update and predict
			System.out.println("Begin Training ! ! !");
			tempMF.train(paraRounds);
			
			double tempMAE = tempMF.mae();
			double tempRSME = tempMF.rsme();
			System.out.println("Finally, MAE = " + tempMAE + ", RSME = " + tempRSME);
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}//of try
	}//of testTrainingTesting
	
	/**
	 * ***********************************************
	 * The entrance of the program.
	 * ***********************************************
	 */
	public static void main(String args[]) {
		testTrainingTesting("E:/Datasets/UCIdatasets/temp/movielens-943u1682m.txt", 943, 1682, 10000, 1, 5, 2000);
	}//of main
	
	
	/**
	 * The class of triple matrix.
	 * @author WX873
	 */
	public class Triple {
		public int user;
		public int item;
		public double rating;
		
		/**
		 * ************************************************
		 * The constructor.
		 * ************************************************
		 */
		public Triple() {
			// TODO Auto-generated constructor stub
			user = -1;
			item = -1;
			rating = -1;
		}//of the first constructor
		
		/**
		 * ************************************************
		 * The constructor.
		 * ************************************************
		 */
		public Triple(int paraUser, int paraItem, double paraRating) {
			user = paraUser;
			item = paraItem;
			rating = paraRating;
		}//of the second constructor
		
		/**
		 * ************************************************
		 * Show me..
		 * ************************************************
		 */
		public String toString() {
			return "" + user + ", " + item + ", " + rating;
		}//of toString
	}// Of class Triple
	
}//of MatrixFactorization

你可能感兴趣的:(矩阵,机器学习,java)