GetScore
import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class GetScore implements Base { //方法参数,一个是源数据,一个是通过源数据得到的相似度矩阵 //得到预测评分矩阵,先循环行userid,得到每一个userid的K个近邻用户和相似度,再得到目标用户的预测项目 public double[][] getScore(int[][] user_movie_base,double[][] combineMatrix ){ double[][] matrix = new double[PREFROWCOUNT][COLUMNCOUNT];//保存每个用户对评分为0的项目的预测值 //循环userid for (int i = 0; i < PREFROWCOUNT; i++) {//KNEIGHBOUR //得到每一个userid的K个邻近相似度极其userid int[] id = new int[KNEIGHBOUR];//存放K个最近邻userId double[] tempSimilarity = new double[combineMatrix[i].length];//产生一个临时相似度矩阵变量,是为了相似度排序时和userid对应 for (int j = 0; j < tempSimilarity.length; j++) { tempSimilarity[j] = combineMatrix[i][j]; } //Arrays.sort(tempSimilarity);//排序,升序 //int flag = 0;//临时变量 double[] similarity = new double[KNEIGHBOUR];//保存前K个相似度,从大到小 // for (int m = tempSimilarity.length - 1; m >= tempSimilarity.length - KNEIGHBOUR; m--) { // for(int j = 0; j < combineMatrix[i].length; j++) { // if (combineMatrix[i][j] == tempSimilarity[m] && combineMatrix[i][j] != 0.0){ // similarity[flag] = tempSimilarity[m]; // id[flag]=j;//保存前K个相似度的userid // flag++; // } // } // } int[] ids = new int[PREFROWCOUNT];//存放K个邻近项目id for (int h = 0; h < PREFROWCOUNT; h++) { ids[h] = h; } for (int h = 0; h < tempSimilarity.length; h++) { for (int j = 0; j < tempSimilarity.length - 1 - h; j++) { if (tempSimilarity[j] < tempSimilarity[j + 1]) { //如果后一个数小于前一个数交换 double tmp = tempSimilarity[j]; tempSimilarity[j] = tempSimilarity[j + 1]; tempSimilarity[j + 1] = tmp; int temp = ids[j]; ids[j] = ids[j + 1]; ids[j + 1] = temp; } } } for (int h = 0; h < KNEIGHBOUR; h++) { similarity[h] = tempSimilarity[h]; } for (int h = 0; h < KNEIGHBOUR; h++) { id[h] = ids[h]; } //以上代码已经得到一个目标用户的K个相似度userid和相似度结束,并且已经排好顺序,分别是:数组id,和数组similarity //开始计算一个目标用户的推荐产品的预测评分,方法,K个邻近用户的相同商品的加权平均数 Map<Integer, Double> map = new HashMap<Integer, Double>();//存放每件商品的id和商品评分*相似度 Map<Integer, Double> map2 = new HashMap<Integer, Double>();//存放每件商品的id和相似度之和 for (int k = 0; k < KNEIGHBOUR; k++) {//按照k值得大小来循环 // 前k个近邻用户的推荐产品 int user_id = id[k];//数组id中的userid根据相似度大小顺序已经排好,从大到小 int[] items = user_movie_base[user_id];// 获取源数据K个邻近用户userid的所有评分 for (int j = 0; j < COLUMNCOUNT; j++) {//循环每件商品,如果相邻用户对某件商品的评分不为0,而目标用户的评分为0,该商品就为推荐商品 if ((items[j] != 0) && (user_movie_base[i][j] == 0)) { if (map.containsKey(j)) {//如果一件商品的值,已经保存在map集合的键中(键是唯一的,即不会和其他的数值一样),那么键对应的值,就会改变,加上该商品不用用户的相似度 double d = map.get(j); d += similarity[k] * items[j]; map.put(j, d);//修改map中的值 double dd = map2.get(j); dd += similarity[k]; map2.put(j, dd); } else { map.put(j, similarity[k] * items[j]);//如果没有保存一件商品的id,那么开始保存 map2.put(j, similarity[k]); } } } } Set<Integer> set = map.keySet();//循环所有推荐商品 for (Integer key : set) { matrix[i][key] = map.get(key) / map2.get(key); } } return matrix; } }
PearsonCorrelation
import java.util.List; /** * 余弦算法计算相似度 * * @author line */ public class PearsonCorrelation implements Base { // 通过余弦求相邻值,对比两行数据,方法有很多,列举的是余弦方法,也可用皮尔森方法 public double pearsonCorrelation(List<Integer> a, List<Integer> b) {// 返回某两行的相似度值 double sum1 = 0; double sum2 = 0; Object[] a2 = a.toArray(); Object[] b2 = b.toArray(); int aimcha; int usercha; double wei = 0; for (int j = 0; j < a.size(); j++) { aimcha = (Integer) a2[j]; usercha = (Integer) b2[j]; sum1 += aimcha * aimcha; sum2 += usercha * usercha; } for (int i = 0; i < a.size(); i++) { double light = 0; double right = 0; aimcha = (Integer) a2[i]; usercha = (Integer) b2[i]; light = aimcha / Math.sqrt(sum1); right = usercha / Math.sqrt(sum2); wei += light * right; } return wei;//相似度值 } }
ProduceMAE
/** * 计算MSE平均绝对误差 * @author line * */ public class ProduceMAE implements Base{ //求误差 public double[] produceMAE(double[][] m,int[][]test){ double mae= 0.0; double []mm=new double[TESTROWCOUNT ]; for(int i=0;i<TESTROWCOUNT ;i++ ) { double sum_fencha= 0.0; int num=0; for(int j=0;j<PREFROWCOUNT;j++){ if(test[i][j]!=0&& m[i][j]!=0){ sum_fencha+=Math.abs(m[i][j]-(double)test[i][j]);//相差取绝对值 num++; } }if (num==0) mae=0;else mae= sum_fencha/num; mm[i]=mae; } return mm; } }
ProduceMSE
import static java.lang.Math.sqrt; /** * 计算MSE平均绝对误差 * @author line * */ public class ProduceMSE implements Base{ //求误差 //该函数用于计算均方根误差,m为预测数据,test为真实数据 // RMSE : 浮点型 // 均方根误差. public double produceMSE(double[][] m,int[][]test){ //定义一个变量用于存储所有样本的平方误差之和 double the_sum_of_error = 0; double []mm=new double[TESTROWCOUNT ]; for(int i=0;i<TESTROWCOUNT ;i++ ) { for(int j=0;j<test[i].length;j++){ the_sum_of_error += ((double) test[i][j]-m[i][j])*((double) test[i][j]-m[i][j]); } } double RMSE = sqrt(the_sum_of_error/TESTROWCOUNT); return RMSE; } }
ProduceSimilarityMatrix
/** * 产生相似矩阵,通过一个userId找其最近邻userId喜欢的产品,则相似度矩阵为一行n列矩阵, * 若是全部比较一个矩阵所有userId的相关度产生一个n行n列矩阵 * @author line * */ public class ProduceSimilarityMatrix implements Base{ //在计算MAE会用到 public double[][] produceSimilarityMatrix(int[][] preference) { double[][] similarityMatrix = new double[PREFROWCOUNT][PREFROWCOUNT];//行和列都是所有的用户,因为是每一行和每一行相比,所以得到的相似矩阵为正方形 for (int i = 0; i < PREFROWCOUNT; i++) { for (int j = 0; j < PREFROWCOUNT; j++) { if (i == j) { continue; } //数据是两行之间对比,其实只需要填满相似度矩阵的左下方或者右上方即可(减少重复运算) similarityMatrix[i][j] = new ComputeSimilarity().computeSimilarity(preference[i], preference[j]);//参数是从第一行开始,和其他每一行比较相似度 } } return similarityMatrix;//返回相似度矩阵 } //计算某个userId的相似度矩阵,用户之间的相似度是每个用户的每件商品评分的相似度,也就是说相似度矩阵是行是用户列也是用户,是正方形矩阵,对角线上的值都为1 //参数i是输入的userid public double[] produceSimilarityMatrix(int[][] preference,int i) { double[] similarityMatrix = new double[PREFROWCOUNT];//定义一个相似度矩阵,行和列都是所有的用户,因为是每一行和每一行相比,所以得到的相似矩阵为正方形 for (int j = 0; j < PREFROWCOUNT; j++) {//循环和其他userId对比其所有商品 if(j==(i-1)){//不比较同行,i-1是因为数组索引比userid小1 continue;//跳出循环,继续下一次循环 } similarityMatrix[j] = new ComputeSimilarity().computeSimilarity(preference[i-1], preference[j]);//参数是从第一行开始,和其他每一行比较相似度 } return similarityMatrix;//返回相似度矩阵,只有在userid-1行有数据,其他行列数据都为0,因为只是userid-1行和其他行对比 } //根据性别属性,产生用户性别属性相似度 public double[] produceSimilarityMatrixGener(int[] preference,int userId) { double[] similarityMatrix = new double[PREFROWCOUNT];//定义一个相似度矩阵,行和列都是所有的用户,因为是每一行和每一行相比,所以得到的相似矩阵为正方形 for (int j = 0; j < PREFROWCOUNT; j++) {//循环和其他userId对比其所有商品 if(j==(userId-1)){//不比较同行,i-1是因为数组索引比userid小1 continue;//跳出循环,继续下一次循环 } if(preference[j]==preference[userId-1]) similarityMatrix[j] = 1; else similarityMatrix[j] = 0; } return similarityMatrix;//返回相似度矩阵,只有在userid-1行有数据,其他行列数据都为0,因为只是userid-1行和其他行对比 } //基于项目 public double[] produceSimilarityMatrixItems(int[][] preference,int i) { double[] similarityMatrix = new double[COLUMNCOUNT]; for (int j = 0; j < COLUMNCOUNT; j++) { if(j==(i-1)){//不比较同行 continue;//跳出循环,继续下一次循环 } similarityMatrix[j] = new ComputeSimilarity().computeSimilarity(preference[i-1], preference[j]);//参数是从第一行开始,和其他每一行比较相似度 } return similarityMatrix;//返回相似度矩阵,只有在userid-1行有数据,其他行列数据都为0,因为只是userid-1行和其他行对比 } }
ReadFile
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; /** * 读取数据集中的数据 * @author line * */ public class ReadFile implements Base { //从文件中读取数据,以“ ”划分 public int[][] readFile( String fileName) { int[][] user_movie = new int[PREFROWCOUNT][COLUMNCOUNT];//存放数据 try { File file = new File(fileName); FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); String line = ""; while (br.ready()) { line = br.readLine();//按行获取数据 String[] data = line.split("\t");//以“TAB”符来分割每行的四个数据数据获取userid,score,product int[] ddd = new int[4]; for (int j = 0; j < data.length; j++) { ddd[j] = Integer.parseInt(data[j]); } user_movie[ddd[0] - 1][ddd[1] - 1] = ddd[2];//因为数组的索引是从0开始,而商品和用户id是从1开始,故减去1 } } catch (Exception ex) { ex.printStackTrace();//如果方法出现错误,会被抓住,在控制台输出错误原因 } return user_movie; } //从文件中读取数据,以“|”划分 public int[] readFileGener(String fileName) { int[] user_genre_base = new int[PREFROWCOUNT];//存放数据 try { File file = new File(fileName); FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); String line = ""; int i=0; while (br.ready()) { line = br.readLine();//按行获取数据 String[] data = line.split("\\|"); if(data[2].equals("M")){//男性设为1 user_genre_base[i]= 1; }else user_genre_base[i]= 0;//女性 i++; } } catch (Exception ex) { ex.printStackTrace();//如果方法出现错误,会被抓住,在控制台输出错误原因 } return user_genre_base; } //获取items-user矩阵 public int[][] readFileItems( String fileName) { int[][] items_movie = new int[COLUMNCOUNT][PREFROWCOUNT];//存放数据 try { File file = new File(fileName); FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); String line = ""; while (br.ready()) {//矩阵中循环列 line = br.readLine(); String[] data = line.split("\t"); int itemsId = Integer.parseInt(data[1]); int userId = Integer.parseInt(data[0]); items_movie[itemsId-1][userId-1] = Integer.parseInt(data[2]); } } catch (Exception ex) { ex.printStackTrace();//如果方法出现错误,会被抓住,在控制台输出错误原因 } return items_movie; } //获取items-gener矩阵 public int[][] readFileItemsGener( String fileName) { int[][] items_movie = new int[COLUMNCOUNT][ITEMS_GENRE_LINE];//存放数据 try { File file = new File(fileName); FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); String line = ""; while (br.ready()) {//矩阵中循环列 line = br.readLine(); String[] data = line.split("\\|"); int itemsId = Integer.parseInt(data[0]); int j = 0; for (int i = data.length-ITEMS_GENRE_LINE; i < data.length; i++) { items_movie[itemsId-1][j] =Integer.parseInt(data[i]); j++; } } } catch (Exception ex) { ex.printStackTrace();//如果方法出现错误,会被抓住,在控制台输出错误原因 } return items_movie; } }
运行结果图
RMSE计算过程示意图
运行结果示意图
数据集
MovieLens数据集
MovieLens数据集包含多个用户对多部电影的评级数据,也包括电影元数据信息和用户属性信息。
下载地址
http://files.grouplens.org/datasets/movielens/
介绍
下面以ml-100k数据集为例进行介绍:
最主要用的是u.data(评分) | u.item(电影信息) | u.user(用户信息)
下载之后各文件具体含义如下:
各文件含义如下:
allbut.pl --生成训练和测试集的脚本,其中除了n个用户评分之外,所有训练和测试集都在训练数据中。 mku.sh --从u.data数据集生成的所有用户的shell脚本。 u.data – 由943个用户对1682个电影的10000条评分组成。每个用户至少评分20部电影。用户和电影从1号开始连续编号。数据是随机排序的。 标签分隔列表:user id | item id | rating | timestamp u.genre --类型列表。 u.info --u.data数据集中的用户数,电影数和评分数。 u.item --电影信息。 标签分隔列表:movie id | movie title | release date | video release date | IMDb URL | unknown | Action | Adventure | Animation | Children’s | Comedy | Crime | Documentary | Drama | Fantasy | Film-Noir | Horror | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western 最后19个字段是流派,1表示电影是该类型,0表示不是;电影可以同时使用几种流派。 电影id和u.data数据集中的id是一致的。 u.occupation --职业列表。 u.user --用户的人口统计信息。 标签分隔列表:user id | age | gender | occupation | zip code 用户id和u.data数据集中的id是一致的。 u1.base --数据集u1.base / u1.test到u5.base / u5.test都是将u.data数据集按照80% / 20%的比例分割的训练集和测试集。 u1.test u1,…,u5有互不相交的测试集;如果是5次交叉验证,那么你可以在每个训练和测试集中重复实验,平均结果。 u2.base 这些数据集可以通过mku.sh从u.data生成 u2.test u3.base u3.test u4.base u4.test u5.base u5.test ua.base --数据集ua.base, ua.test, ub.base, ub.test将u.data数据集分为训练集和测试集,每个用户在测试集中具有10个评分。 ua.test ua.test和ub.test是不相交的。这些数据集可以通过mku.sh从u.data生成