Spark实战项目之电影推荐

一、Spark知识点

二、项目数据

三、项目代码

import java.util.Arrays;
import java.util.List;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.Optional;
import org.apache.spark.mllib.recommendation.ALS;
import org.apache.spark.mllib.recommendation.MatrixFactorizationModel;
import org.apache.spark.mllib.recommendation.Rating;

import scala.Tuple2;


public class HelloSpark {
	//存放文件的路径,还没有采用hadoop存储
	private static final String PATH_STRING = "/home/hadoop/ml-latest-small_2018/";
	
	//main
	public static void main(String[] args) {
		try {
			HelloSpark helloSpark = new HelloSpark();
			helloSpark.recommendMovies();
		} catch (Exception e) {
			System.out.println("shenme");
		}
	}
	
	//推荐电影
	public void recommendMovies() throws Exception{
		//如果多个spark任务运行在一台机器上,需要APPname对其进行分类
		//setMaster(local[2])就是开启2个线程对该任务进行处理
		SparkConf conf = new SparkConf().setAppName("Movies").setMaster("local[2]");
		//一个程序只需要获得一个配置信息即可
		JavaSparkContext sparkContext = new JavaSparkContext(conf);
		//接下来就是函数的功能,具体功能下面详细解释
		JavaRDD moviesRDD = preProcessMovies(sparkContext, "movies.csv");
		JavaRDD ratRDD = preProcessRating(sparkContext, "ratings.csv");
		JavaRDD rat = prePRocess(sparkContext, ratRDD, moviesRDD);
		MatrixFactorizationModel mod = this.createModel(rat);
		recommend(sparkContext, mod, 23, 10,rat);
		//配置文件必须在程序结束时关闭
		sparkContext.close();
		System.out.println("Application is successed.");
		
	}
	
	//去掉错误的评分数据
	public JavaRDD preProcessRating(JavaSparkContext sContext,String file) throws Exception{
		JavaRDD ratingsJavaRDD = sContext.textFile(PATH_STRING+file).distinct();
		JavaRDD ratingsJavaRDD1 = ratingsJavaRDD.filter(f -> {
			String[] arr = f.split(",");
			//ge shi jian cha
			if (arr.length == 4) {
				try {
					Integer.valueOf(arr[0].trim());
					Double val = Double.valueOf(arr[2].trim());
					// ping fen zui gao 5.0
					if (val< 5.0 && val >= 0) {
						return true;
					}else {
						return false;
					}
				} catch (Exception e) {
					return false;
				}
			}
			return false;
		});
		//qu chu hao miao
		JavaRDD ratingsJavaRDD2 = ratingsJavaRDD1.map(f -> {
			String[] arr = f.split(",");
			return arr[0]+","+arr[1]+","+arr[2];
		});
		return ratingsJavaRDD2;
	}
	
	//去掉错误的电影数据
	public JavaRDD preProcessMovies(JavaSparkContext sContext,String file) throws Exception{
		//quchongfu
		JavaRDD ratingsJavaRDD = sContext.textFile(PATH_STRING+file).distinct();
		JavaRDD ratingsJavaRDD1 = ratingsJavaRDD.filter(f -> {
			String[] arr = f.split(",");
			if (arr.length >= 3) {
				try {
					Integer.valueOf(arr[0].trim());
					return true;
				} catch (Exception e) {
					return false;
				}
			}
			return false;
		});
		return ratingsJavaRDD1;
	}
	
	//去掉用户评价的电影不存在的情况
	public JavaRDD prePRocess(JavaSparkContext sc, JavaRDD rat, JavaRDD movise) throws Exception{
		JavaPairRDD r1 = rat.mapToPair(f -> {
			String[] arr = f.split(",");
			//r1 = 
			return new Tuple2(Integer.valueOf(arr[1].trim()),arr[0]+","+arr[2]);
		});
		JavaPairRDD m1 = movise.mapToPair(f -> {
			String[] arr = f.split(",", 2);
			//m1 = 
			return new Tuple2(Integer.valueOf(arr[0].trim()),arr[1]);
		});
		/*
		 * Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
		 * Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
		 * Optional 类的引入很好的解决空指针异常。
		 */
		/* 
		 * https://www.cnblogs.com/FG123/p/9747901.html
		 * leftOuterJoin:相当于mysql的LEFT JOIN,leftOuterJoin返回数据集左边的全部数据和数据集左边与右边有交集的数据
		 * rightOuterJoin:相当于mysql的RIGHT JOIN,rightOuterJoin返回数据集右边的全部数据和数据集右边与左边有交集的数据
		 * fullOuterJoin:返回左右数据集的全部数据,左右有一边不存在的数据以None填充
		 */
		//rk2 = >
		JavaPairRDD>> rk2 = r1.leftOuterJoin(m1);
		//所有用户评价过的电影数
		System.out.println("rk2 count:"+rk2.count());
		//去掉空的数据
		JavaPairRDD>> rk3 = rk2.filter(f ->{
			if (null == f._2()._1() || f._2()._1().isEmpty()) {
				return false;
			}
			if (!f._2()._2().isPresent()) {
				return false;
			}
			return true;
		});
		JavaRDD readyData = rk3.map(f -> {
			//pid is movieID
			int pid = f._1();
			String[] arr = f._2()._1().split(",");
			//uid is userID
			int uid  = Integer.valueOf(arr[0].trim());
			//r is garde
			double r = Double.valueOf(arr[1].trim());
			return new Rating(uid, pid, r);
		});
		return readyData;
	}
	
	//通过ALS算法对数据进行分类,并形成模型
	public MatrixFactorizationModel createModel(JavaRDD ra) throws Exception{
		int rank = 50;
		int interation = 6;
		double lamda = 0.03;
		System.out.println("ra count:"+ra.count());
		/*
		 * weights: 是一个数组.
		 * 根据weight(权重值)将一个RDD划分成多个RDD,权重越高划分得到的元素较多的几率就越大.
		 * 数组的长度即为划分成RDD的数量.
		 * 需要注意的是weight数组内数据的加和应为1.
		 */
		JavaRDD[] data = ra.randomSplit(new double[]{0.7,0.3});
		JavaRDD training = data[0];
		JavaRDD testing = data[1];
		/*
		 * ALS算法是2008年以来,用的比较多的协同过滤算法.它已经集成到Spark的Mllib库中,使用起来比较方便.
		 */
		/*
		 * 表示矩阵分解结果的模型。
		 * 注意:如果使用构造函数直接创建模型,请注意快速预测需要缓存的用户/产品功能及其关联的分区程序。
		 * param:rank此模型中功能的排名。 param:userFeatures元组的RDD,其中每个元组表示userId以及为此用户计算的功能。 param:productFeatures元组的RDD,其中每个元组代表productId和为此产品计算的特性。
		 */
		MatrixFactorizationModel mod = ALS.train(training.rdd(), rank, interation,lamda,-1);
		//JavaPairRDD.fromJavaRDD将RDD转换成PairRDD,参数为Tuple2
		//mod.predict通过JavaPairRDD的value以及上一句代码定义的算法,进行评级
		JavaRDD predictTesting = mod.predict(JavaPairRDD.fromJavaRDD(testing.map(f ->{
			//f is Rating
			//就是一个评级的过程,根据评级,我们可以获得更接近的效果
			return new Tuple2(f.user(), f.product());
		})));
		
		//从此开始---------------只是为了验证我们对ALS传入的参数是否合适,res最好要小于1.5--------------------
		//将未评级的数据分为<用户和产品,用户评价>,这里的产品就是电影的ID
		JavaPairRDD realityRating = JavaPairRDD.fromJavaRDD(testing.map(f ->{
			return new Tuple2(f.user()+","+f.product(), f.rating());
		}));
		//将评级的数据分为<用户和产品,评级>
		JavaPairRDD predictRating = JavaPairRDD.fromJavaRDD(predictTesting.map(f ->{
			return new Tuple2(f.user()+","+f.product(), f.rating());
		}));
		JavaRDD> val = realityRating.join(predictRating).values();
		//
		double re = val.map(f -> {
			return Math.pow(f._1() - f._2(), 2);
		}).reduce((a,b) -> a+b)/(double)val.count();
		double res = Math.sqrt(re);
		//res:均方差
		System.out.println("res:"+res);
		//到此结尾-----------------------------------------------------------------------------------
		return mod;
	}
	
	//根据用户的ID号给用户推荐电影
	public void recommend(JavaSparkContext sparkContext,MatrixFactorizationModel mod, int uid,int number, JavaRDD rat) throws Exception{
		/*
		 * public Rating [] recommendedProducts(int user,int num)
		 * 向用户推荐产品。
		 * 参数:
		 * user  - 向其推荐产品的用户
		 * num  - 要返回的产品数量。 返回的数字可能小于此数。
		 * 返回:
		 * 评级对象,每个对象包含给定的用户ID,产品ID和评级字段中的“得分”。 每个代表一个推荐产品,它们按分数排序,减少。 
		 * 返回的第一个是预测最强烈推荐给用户的那个。 
		 * 分数是一个不透明的值,表示产品的推荐强度。
		 */
		Rating[] recRatings = mod.recommendProducts(uid, number);
		List lsRecommend = Arrays.asList(recRatings);
		//从此开始-----------------------------将电影ID转换成电影名功能未完成---------------------------------------
		//将list转换成JavaRDD
		JavaRDD recommendRating = sparkContext.parallelize(lsRecommend);
		//将JavaRDD转化成JavaPairRDD
		JavaPairRDD recommendAllRDD = JavaPairRDD.fromJavaRDD(recommendRating.map(f ->{
			return new Tuple2(f.user()+","+f.product(), f.rating());
		}));
		//将未评级JavaRDD转化成JavaPairRDD
		JavaPairRDD ratingRDD = JavaPairRDD.fromJavaRDD(rat.map(f ->{
			return new Tuple2(f.user()+","+f.product(), f.rating());
		}));
		JavaPairRDD>> recommendJoin = recommendAllRDD.leftOuterJoin(ratingRDD);
		JavaRDD recMov = recommendJoin.filter(f ->{
			if (!f._2()._2().isPresent())
				return true;
			return false;
		}).map(f -> {
			String[] arr = f._1().split(",");
			return Integer.valueOf(arr[1]);
		});
		//从此结束-----------------------------将电影ID转换成电影名功能未完成---------------------------------------
		//根据推荐度,推荐出电影ID,由于之前mod是从大到小以此排列的
		for (Rating rating : recRatings) {
			System.out.println("Recommend movie:"+rating.product());
		}
	}
	
}

 

你可能感兴趣的:(学习笔记)