09-Spark基础+PySpark案例

一、配置Scala

Spark是用Scala开发的,所以进行Spark开发的语言就是Scala,所以先用IDEA安装Scala插件。

1、安装Scala插件

在IDEA中,File—Settings—Plugins找到Scala,Install。

安装完会提示重启IDEA,重启即可。

安装完之后,点击File—Project Structure—Global Libraries

点击Download,

加载一段时间后会要求选择Scala版本,我们选个老一点的2.12.11,然后就会自动下载。

2、创建Maven工程

创建Maven工程:

 由于Spark的学习包括很多模块,所以我们可以把spark项目当做一个父项目,先把原来的src文件夹删掉,新建一个个模块,作为不同模块的子项目。

新建一个Module,

在src/main/java中新建一个包: com.wolf.bigdata.spark.core

在包里新建一个Scala类Test,类型选object

写入测试内容:

package com.wolf.bigdata.spark.core

object Test {

  def main(args: Array[String]): Unit = {
    
    println("hello spark")
  }

}

运行输出结果:

说明Scala配置成功。 

二、WordCount案例

1、案例分析

2、Spark环境

说明:这里暂时先用Maven进行配置,以后再进行环境搭建

在spark.core包中新建一个包叫wc

在包里新建一个Scala-class叫Scala01_WordCount,类型为object

Spark编程一般可以分为三大步骤:

// 1、建立和Spark框架的连接

// 2、执行业务操作

// 3、关闭连接

2.1 添加Spark依赖关系

在pom.xml中添加如下依赖:

    <dependencies>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>
    </dependencies>

为了实现后续功能,原则上还要添加如下两个依赖:

        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-mllib_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>

还有一些其他依赖,比如spark-hive、spark-streaming,暂时用不到,就先不添加了。 

3、功能实现

在spark根目录下创建文件夹datas,用来存放要读取的文件

编写Scala01_WordCount代码:

package com.wolf.bigdata.spark.core.wc

import org.apache.spark.{SparkConf, SparkContext}

object Scala01_WordCount {

  def main(args: Array[String]): Unit = {

    // 1、建立和Spark框架的连接
    val sparConf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(sparConf)

    // 2、执行业务操作
    // (1) read file(s), get lines
    val lines = sc.textFile("datas")

    // (2) split lines, get words
    // flatMap
    val words = lines.flatMap(_.split(" "))
    // (3) group words for counting    eg: "hello world"-> (hello), (world)
    val wordGroup = words.groupBy(word => word)
    // (4) convert data after grouping [convert (a,a,a) to (a,3)]
    val wordToCount = wordGroup.map{
      case(word,list)=>{
        (word,list.size)
      }
    }
    // (5) show result in cmd
    val array = wordToCount.collect()
    array.foreach(println)
    // 3、关闭连接
    sc.stop()

  }
}

运行,显示结果:

4、另一种实现方式

package com.wolf.bigdata.spark.core.wc

import org.apache.spark.{SparkConf, SparkContext}

object Scala01_WordCount_another {

  def main(args: Array[String]): Unit = {

    // 1、建立和Spark框架的连接
    val sparConf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(sparConf)

    // 2、执行业务操作
    // (1) read file(s), get lines
    val lines = sc.textFile("datas")

    // (2) split lines, get words
    // flatMap
    val words = lines.flatMap(_.split(" "))
    // (3) convert word->(word,1)
    val wordToOne = words.map(
      word => (word, 1)
    )
    // (4) group by the "word" in (word,1)   (because t._1 means the first num in the tuple)
    val wordGroup = wordToOne.groupBy(t => t._1)

    val wordToCount = wordGroup.map{
      case(word,list)=>{
        list.reduce(
          (t1, t2) => {
            (t1._1, t1._2 + t2._2)
          }
        )
      }
    }
    // (5) show result in cmd
    val array = wordToCount.collect()
    array.foreach(println)
    // 3、关闭连接
    sc.stop()

  }
}

5、利用Spark进行实现

Spark框架提供了更多的功能,比如,可以将分组和聚合使用一个方法进行实现

package com.wolf.bigdata.spark.core.wc

import org.apache.spark.{SparkConf, SparkContext}

object Scala03_WordCount_Spark {

  def main(args: Array[String]): Unit = {

    // 1、建立和Spark框架的连接
    val sparConf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(sparConf)

    // 2、执行业务操作
    // (1) read file(s), get lines
    val lines = sc.textFile("datas")

    // (2) split lines, get words
    // flatMap
    val words = lines.flatMap(_.split(" "))
    // (3) convert word->(word,1)
    val wordToOne = words.map(
      word => (word, 1)
    )
    
    // (4) Spark-> reduceByKey
    val wordToCount = wordToOne.reduceByKey(_ + _)
    
    // (5) show result in cmd
    val array = wordToCount.collect()
    array.foreach(println)
    // 3、关闭连接
    sc.stop()

  }
}

6、编写日志输出格式化文件

在resources中添加log4j.properties文件,写入内容如下

log4j.rootCategory=ERROR, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd 
HH:mm:ss} %p %c{1}: %m%n
# Set the default spark-shell log level to ERROR. When running the spark-shell,
the
# log level for this class is used to overwrite the root logger's log level, so
that
# the user can have different defaults for the shell and regular Spark apps.
log4j.logger.org.apache.spark.repl.Main=ERROR
# Settings to quiet third party logs that are too verbose
log4j.logger.org.spark_project.jetty=ERROR
log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=ERROR
log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=ERROR
log4j.logger.org.apache.parquet=ERROR
log4j.logger.parquet=ERROR
# SPARK-9183: Settings to avoid annoying messages when looking up nonexistent
UDFs in SparkSQL with Hive support
log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=FATAL
log4j.logger.org.apache.hadoop.hive.ql.exec.FunctionRegistry=ERROR

再次运行,只会打印错误的日志,正确的日志不会打印出来。(比赛的时候不要写这个)

三、Spark运行环境

1、Local模式

Local模式是不需要其他任何节点资源就可以在本地执行 Spark 代码的环境,一般用于教学,调试,演示等,之前在 IDEA 中运行代码的环境我们称之为开发环境,不太一样。

1.1 解压缩文件

tar -zxvf spark-2.4.5-bin-hadoop2.6.tgz -C /opt/module

重命名

cd /opt/module/
mv spark-2.4.5-bin-hadoop2.6 spark-local

1.2 启动local环境

进入解压后的路径,执行以下命令

bin/spark-shell 

启动成功后,可以输入网址进行 Web UI 监控页面访问

http://hadoop102:4040

 

 1.3 命令行工具简单使用

在解压缩文件夹下的 data 目录中,添加 word.txt 文件。在命令行工具中执行如下代码指 令(和 IDEA 中代码简化版一致)

sc.textFile("data/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect

1.4 退出local模式

按键 Ctrl+C 或输入 Scala 指令:

:quit

1.5 提交应用

2、Standalone模式

local 本地模式毕竟只是用来进行练习演示的,真实工作中还是要将应用提交到对应的 集群中去执行,这里我们来看看只使用 Spark 自身节点运行的集群模式,也就是我们所谓的 独立部署(Standalone)模式。Spark 的 Standalone 模式体现了经典的 master-slave 模式。

集群规划: 

 2.1 解压缩文件

tar -zxvf spark-2.4.5-bin-hadoop2.6.tgz -C /opt/module

重命名

cd /opt/module/
mv spark-2.4.5-bin-hadoop2.6 spark-standalone

2.2 修改配置文件

(1)进入解压缩后路径的 conf 目录,修改 slaves.template 文件名为 slaves

mv slaves.template slaves

(2)修改 slaves 文件,添加 work 节点

vim slaves
hadoop102
hadoop103
hadoop104

(3)修改 spark-env.sh.template 文件名为 spark-env.sh

mv spark-env.sh.template spark-env.sh

(4)修改 spark-env.sh 文件,添加 JAVA_HOME 环境变量和集群对应的 master 节点

export JAVA_HOME=/opt/module/jdk1.8.0_171
SPARK_MASTER_HOST=hadoop102
SPARK_MASTER_PORT=7077

注:这里的7077在Hadoop3中对应着8020

(5)分发spark-standalone目录到103和104集群上

scp -r /opt/module/spark-standalone/ wolf@hadoop103:/opt/module/
scp -r /opt/module/spark-standalone/ wolf@hadoop104:/opt/module/

2.3 启动集群

sbin/start-all.sh

查看三台服务器的运行进程:

查看 Master 资源监控 Web UI 界面:

http://hadoop102:8080

 2.4 提交应用

2.5 配置历史服务

由于 spark-shell 停止掉后,集群监控 linux1:4040 页面就看不到历史任务的运行情况,所以开发时都配置历史服务器记录任务运行情况。 

(1)修改 spark-defaults.conf.template 文件名为 spark-defaults.conf

mv spark-defaults.conf.template spark-defaults.conf

(2)修改 spark-default.conf 文件,配置日志存储路径

spark.eventLog.enabled true
spark.eventLog.dir hdfs://hadoop102:9000/directory

注意:需要启动 hadoop 集群,HDFS 上的 directory 目录需要提前存在。

hadoop fs -mkdir /directory

(3)修改 spark-env.sh 文件, 添加日志配置

export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080 
-Dspark.history.fs.logDirectory=hdfs://hadoop102:9000/directory 
-Dspark.history.retainedApplications=30"

(4)分发文件到103和104上

scp -r /opt/module/spark-standalone/conf wolf@hadoop103:/opt/module/spark-standalone/
scp -r /opt/module/spark-standalone/conf wolf@hadoop104:/opt/module/spark-standalone/

(5)重新启动Spark集群和历史服务

在Spark目录中

sbin/start-all.sh
sbin/start-history-server.sh

(6)重新执行任务

(7)可以去web端查看历史任务

http://hadoop102:18080

2.6 配置高可用(HA)

这一部分需要用到Zookeeper,我们以后再来看。

3、Yarn模式

独立部署(Standalone)模式由 Spark 自身提供计算资源,无需其他框架提供资源。这种方式降低了和其他第三方资源框架的耦合性,独立性非常强。但是也要记住,Spark 主要是计算框架,而不是资源调度框架,所以本身提供的资源调度并不是它的强项,所以还是和其他专业的资源调度框架集成会更靠谱一些。(貌似不是考点,先跳过了)

 四、Spark运行架构

注意:该部分是纯理论部分

 由上图可以看出,对于 Spark 框架有两个核心组件:

1、Driver

2、Executor

 

3、Master&Worker

 4、ApplicationMaster

 

五、Spark-Core编程

 中间的学习部分看不完了,先从案例入手。

案例一:各省份广告点击数前三名

1、数据准备

pri_user_adv.log:时间戳 省份 城市 用户 广告 ,字段以空格分隔,放在了代码根目录下的datas文件夹中。

2、需求描述

统计出每个省份、每个广告被点击数量排行的Top3

3、需求分析

4、功能实现

代码思路

具体实现

package com.wolf.bigdata.spark.core.rdd

import org.apache.spark.{SparkConf, SparkContext}

object Spark_RDD_Req {

  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkConf)

    // 1.get data
    // raw data:time province city user ad.
    val dataRDD = sc.textFile("datas/pri_user_adv.log")

    // 2.process data
    // from raw data to : ((province,ad),1)
    val mapRDD = dataRDD.map(
      line => {
        val datas = line.split(" ")
        ((datas(1) , datas(4)),1)
      }
    )

    // 3.reduce(sum)
    // from new data to : ((province,ad),sum)
    val reduceRDD = mapRDD.reduceByKey(_+_)

    // 4.convert result
    // from ((province,ad),sum) to (province,(ad,sum))
    val newMapRDD = reduceRDD.map{
      case((prv,ad),sum)=>{
        (prv,(ad,sum))
      }
    }
    // 5. group by province
    // (provinceA,[(adA,sumA),(adB,sumB)...])
    val groupRDD = newMapRDD.groupByKey()
    // 6. sort in group
    // according num , pick out 3
    val resultRDD = groupRDD.mapValues(
      iter=>{
        iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
      }
    )
    // 7. print in cmd
    resultRDD.collect().foreach(println)

    sc.stop()
  }

}

运行结果

案例二:电商统计用户行为信息

 1、数据准备

 

 2、需求描述

按照每个品类的点击、下单、支付的量来统计热门品类。 

 鞋 点击数 下单数 支付数

衣服 点击数 下单数 支付数

电脑 点击数 下单数 支付数 

本项目需求优化为:先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下 单数;下单数再相同,就比较支付数。 

3、需求分析 

实现方案一

分别统计每个品类点击的次数,下单的次数和支付的次数:

(品类,点击总数)(品类,下单总数)(品类,支付总数) 

实现方案二

一次性统计每个品类点击的次数,下单的次数和支付的次数:

(品类,(点击总数,下单总数,支付总数)) 

实现方案三

使用累加器的方式聚合数据 

4、具体实现

方案一的代码:

package com.wolf.bigdata.spark.core.req

import org.apache.spark.{SparkConf, SparkContext}

object Spark01_Top10CateMethod1 {
  def main(args: Array[String]): Unit = {

    val sparConf = new SparkConf().setMaster("local[*]").setAppName("Top10CateMethod1")
    val sc = new SparkContext(sparConf)

    // 1.get raw data
    val actionRDD = sc.textFile("datas/user_visit_action.txt")

    // 2.get click count (ID,click_count)
    val clickActionRDD = actionRDD.filter(
      action => {
        val datas = action.split("_") //each action is divided by "_"
        datas(6) != "-1"
      }
    )
    val clickCountRDD = clickActionRDD.map(
      action => {
        val datas = action.split(("_"))
        (datas(6), 1)
      }
    ).reduceByKey(_ + _)
    // 3.get order count (ID,order_count)
    val orderActionRDD = actionRDD.filter(
      action => {
        val datas = action.split("_") //each action is divided by "_"
        datas(8) != "null"
      }
    ) // orderid=>1,2,3 ,but we want [(1,1),(2,1),(3,1)] ,so flat it
    val orderCountRDD = orderActionRDD.flatMap(
      action => {
        val datas = action.split("_")
        val cid = datas(8)
        val cids = cid.split(",")
        cids.map(id => (id, 1))
      }
    ).reduceByKey(_ + _) // [(1,num1),(2,num2)..]

    // 4. get pay count (ID,pay_count)
    val payActionRDD = actionRDD.filter(
      action => {
        val datas = action.split("_") //each action is divided by "_"
        datas(10) != "null"
      }
    ) // payid=>1,2,3 ,but we want [(1,1),(2,1),(3,1)] ,so flat it
    val payCountRDD = payActionRDD.flatMap(
      action => {
        val datas = action.split("_")
        val cid = datas(10)
        val cids = cid.split(",")
        cids.map(id => (id, 1))
      }
    ).reduceByKey(_ + _) // [(1,num1),(2,num2)..]
    // 5. sort , get top 10 ***
    // click_count > order_count > pay_count
    // tuple sort:firstly compare the first, and then compare the second...
    // from [(ID,click_count),(ID,order_count),(ID,pay_count)] to (ID,(click_count,order_count,pay_count))
    // join(x) zip(x) leftOuterJoin(x) cogroup(v)
    // cogroup = connect + group
    val cogroupRDD = clickCountRDD.cogroup(orderCountRDD, payCountRDD)
    val analysisRDD = cogroupRDD.mapValues{
      case(clickIter,orderIter,payIter)=>{
        var clickCnt = 0
        val iter1 = clickIter.iterator
        if(iter1.hasNext){
          clickCnt = iter1.next()
        }
        var orderCnt = 0
        val iter2 = orderIter.iterator
        if (iter2.hasNext) {
          orderCnt = iter2.next()
        }
        var payCnt = 0
        val iter3 = payIter.iterator
        if (iter3.hasNext) {
          payCnt = iter3.next()
        }
        (clickCnt,orderCnt,payCnt)
      }
    }

    val resultRDD = analysisRDD.sortBy(_._2, false).take(10)

    // 6. collect result ,and print in cmd
    resultRDD.foreach(println)

  }
}

运行结果

 方案一有两个问题:

1、actionRDD被重复使用多次

2、cogroup方法可能存在Shuffle,影响性能

问题1的解决方法:把actionRDD放在缓存中

代码实现:

 actionRDD.cache()

问题2的解决方法:

当前数据的样子是这样的:(ID,click_count),(ID,order_count),(ID,pay_count)

我们可以不使用cogroup,直接把数据变成:(ID,(click_count,0,0)),(ID,(0,order_count,0)),(ID,(0,0,pay_count)),然后聚合,这样就不需要用cogroup了。

改进后的方案一代码:

package com.wolf.bigdata.spark.core.req

import org.apache.spark.{SparkConf, SparkContext}

object Spark01_Top10CateMethod2 {
  def main(args: Array[String]): Unit = {

    val sparConf = new SparkConf().setMaster("local[*]").setAppName("Top10CateMethod1")
    val sc = new SparkContext(sparConf)

    // 1.get raw data
    val actionRDD = sc.textFile("datas/user_visit_action.txt")
    actionRDD.cache()
    // 2.get click count (ID,click_count)
    val clickActionRDD = actionRDD.filter(
      action => {
        val datas = action.split("_") //each action is divided by "_"
        datas(6) != "-1"
      }
    )
    val clickCountRDD = clickActionRDD.map(
      action => {
        val datas = action.split(("_"))
        (datas(6), 1)
      }
    ).reduceByKey(_ + _)
    // 3.get order count (ID,order_count)
    val orderActionRDD = actionRDD.filter(
      action => {
        val datas = action.split("_") //each action is divided by "_"
        datas(8) != "null"
      }
    ) // orderid=>1,2,3 ,but we want [(1,1),(2,1),(3,1)] ,so flat it
    val orderCountRDD = orderActionRDD.flatMap(
      action => {
        val datas = action.split("_")
        val cid = datas(8)
        val cids = cid.split(",")
        cids.map(id => (id, 1))
      }
    ).reduceByKey(_ + _) // [(1,num1),(2,num2)..]

    // 4. get pay count (ID,pay_count)
    val payActionRDD = actionRDD.filter(
      action => {
        val datas = action.split("_") //each action is divided by "_"
        datas(10) != "null"
      }
    ) // payid=>1,2,3 ,but we want [(1,1),(2,1),(3,1)] ,so flat it
    val payCountRDD = payActionRDD.flatMap(
      action => {
        val datas = action.split("_")
        val cid = datas(10)
        val cids = cid.split(",")
        cids.map(id => (id, 1))
      }
    ).reduceByKey(_ + _) // [(1,num1),(2,num2)..]
    // 5. sort , get top 10 ***
    // click_count > order_count > pay_count
    // tuple sort:firstly compare the first, and then compare the second...
    // from [(ID,click_count),(ID,order_count),(ID,pay_count)] to
    // (ID,(click_count,0,0)),(ID,(0,order_count,0)),(ID,(0,0,pay_count))
    // then reduce
    val rdd1 = clickCountRDD.map {
      case (cid, cnt) => {
        (cid, (cnt, 0, 0))
      }
    }
    val rdd2 = orderCountRDD.map {
      case (cid, cnt) => {
        (cid, (0, cnt, 0))
      }
    }
    val rdd3 = payCountRDD.map {
      case (cid, cnt) => {
        (cid, (0, 0, cnt))
      }
    }
    // union together and then reduce
    val sourceRDD = rdd1.union(rdd2).union(rdd3)
    // reduce
    val analysisRDD = sourceRDD.reduceByKey(
      (t1, t2) => {
        (t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3)
      }
    )
    val resultRDD = analysisRDD.sortBy(_._2, false).take(10)

    // 6. collect result ,and print in cmd
    resultRDD.foreach(println)

  }
}

运行结果:

改进后仍存在的问题:存在大量的Shuffle操作(reduceByKey)

其实,我们可以改变思路,进而优化代码:

方案二的代码:

package com.wolf.bigdata.spark.core.req

import org.apache.spark.{SparkConf, SparkContext}

object Spark01_Top10CateMethod3 {
  def main(args: Array[String]): Unit = {

    val sparConf = new SparkConf().setMaster("local[*]").setAppName("Top10CateMethod1")
    val sc = new SparkContext(sparConf)

    // 1.get raw data
    val actionRDD = sc.textFile("datas/user_visit_action.txt")

    // 2.convert data structure
    // if click: (ID,(1,0,0))
    // if order: (ID,(0,1,0))
    // if pay: (ID,(0,0,1))
    val flatRDD = actionRDD.flatMap(
      action => {
        val datas = action.split("_")
        if (datas(6) != "-1") {
          // click
          List((datas(6), (1, 0, 0)))
        }
        else if (datas(8) != "null") {
          // order
          val ids = datas(8).split(",")
          ids.map(id => (id, (0, 1, 0)))
        }
        else if (datas(10) != "null") {
          //pay
          val ids = datas(10).split(",")
          ids.map(id => (id, (0, 0, 1)))
        }
        else {
          Nil
        }
      }
    )

    // 3. reduce the same ID
    // (ID,(cnt1,cnt2,cnt3)
    val analysisRDD = flatRDD.reduceByKey(
      (t1, t2) => {
        (t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3)
      }
    )

    // 4.sort descending,Top 10
    val resultRDD = analysisRDD.sortBy(_._2, false).take(10)

    // 5. collect result ,and print in cmd
    resultRDD.foreach(println)

  }
}

这样只需要一个reduceByKey,也即只需要一个Shuffle操作

运行结果

方案三用到了自定义累加器,以后再学习。

六、Spark MLlib

这里不做具体学习,只是写一点案例的代码。

Spark MLlib

引入依赖

在pom中引入如下依赖

        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-mllib_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>

代码

package com.wolf.bigdata.spark.core.req
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.{Row, DataFrame}
import org.apache.spark.sql.types.{StructType, StructField, IntegerType}
import org.apache.spark.ml.recommendation.ALS

object Test01 {
  def main(args: Array[String]): Unit = {
    // 创建SparkSession: 首先,创建一个SparkSession,这是与Spark进行交互的入口点。
    val spark = SparkSession.builder()
      .appName("MovieRecommendation")
      .master("local[*]") // 这里设置本地模式,[*] 表示使用所有 CPU 核心
      .getOrCreate()
    // 读取数据: 使用Spark DataFrame API读取用户-电影评分数据。
    val data = spark.read
      .option("header", "true")
      .option("inferSchema", "true")
      .csv("datas/capter5_2ml.csv")
    // 创建ALS模型: 配置ALS模型的参数

    val als = new ALS()
      .setMaxIter(5)
      .setRegParam(0.01)
      .setUserCol("userId")
      .setItemCol("movieId")
      .setRatingCol("rating")
    // 训练模型: 使用训练数据对ALS模型进行训练
    val model = als.fit(data)
    // 生成用户推荐: 使用recommendForUserSubset方法为指定用户生成电影推荐。
    // 请替换user_ids为你要生成推荐的用户ID列表
    val user_ids = Seq(1, 2, 3)
//    val user_df = user_ids.toDF("userId")
    val userRows = user_ids.map(id => Row(id))
    val userSchema = List(StructField("userId", IntegerType, nullable = false))
    val user_df = spark.createDataFrame(spark.sparkContext.parallelize(userRows), StructType(userSchema))
    val user_recommendations = model.recommendForUserSubset(user_df, 10)
    // 生成电影推荐: 使用recommendForItemSubset方法为指定电影生成用户推荐。
    // 请替换movie_ids为你要生成推荐的电影ID列表
    val movie_ids = Seq(1, 2, 3, 4, 5)
//    val movie_df = movie_ids.toDF("movieId")
    val movieRows = movie_ids.map(id => Row(id))
    val movieSchema = List(StructField("movieId", IntegerType, nullable = false))
    val movie_df = spark.createDataFrame(spark.sparkContext.parallelize(movieRows), StructType(movieSchema))
    val movie_recommendations = model.recommendForItemSubset(movie_df, 10)
    // 结果输出: 将生成的推荐结果输出,例如,打印到控制台。
    // 输出用户推荐
//    user_recommendations.show()
    user_recommendations.take(10).foreach(println)
    // 输出电影推荐
//    movie_recommendations.show()
    movie_recommendations.take(10).foreach(println)

    // 关闭SparkSession: 最后,确保关闭SparkSession以释放资源。
    spark.stop()

  }

}

案例

使用Spark MLlib中的使用ALS算法给每个用户推荐某个商品。

注意:对原始数据添加列名,方便后续操作

要求

1、创建SparkSession

 首先,创建一个SparkSession,这是与Spark进行交互的入口点。

import org.apache.spark.sql.SparkSession

val spark = SparkSession.builder()
      .appName("MovieRecommendation")
      .master("local[*]") // 这里设置本地模式,[*] 表示使用所有 CPU 核心
      .getOrCreate()

2、读取数据

使用Spark DataFrame API读取用户-电影评分数据。

    val data = spark.read
      .option("header", "true")
      .option("inferSchema", "true")
      .csv("datas/capter5_2ml.csv")

3、创建ALS模型并配置ALS模型的参数

import org.apache.spark.ml.recommendation.ALS
 
   val als = new ALS()
      .setMaxIter(5)
      .setRegParam(0.01)
      .setUserCol("userId")
      .setItemCol("movieId")
      .setRatingCol("rating")

4、训练模型

使用训练数据对ALS模型进行训练

val model = als.fit(data)

5、生成用户推荐

使用recommendForUserSubset方法为指定用户生成电影推荐。

import org.apache.spark.sql.{Row, DataFrame}
import org.apache.spark.sql.types.{StructType, StructField, IntegerType}

    val user_ids = Seq(1, 2, 3)
    val userRows = user_ids.map(id => Row(id))
    val userSchema = List(StructField("userId", IntegerType, nullable = false))
    val user_df = spark.createDataFrame(spark.sparkContext.parallelize(userRows), StructType(userSchema))
    val user_recommendations = model.recommendForUserSubset(user_df, 10)

6、生成电影推荐

使用recommendForItemSubset方法为指定电影生成用户推荐。

    val movie_ids = Seq(1, 2, 3, 4, 5)
    val movieRows = movie_ids.map(id => Row(id))
    val movieSchema = List(StructField("movieId", IntegerType, nullable = false))
    val movie_df = spark.createDataFrame(spark.sparkContext.parallelize(movieRows), StructType(movieSchema))
    val movie_recommendations = model.recommendForItemSubset(movie_df, 10)

7、结果输出

将生成的推荐结果输出,例如,打印到控制台。

    // 输出用户推荐
//    user_recommendations.show()
    user_recommendations.take(10).foreach(println)
    // 输出电影推荐
//    movie_recommendations.show()
    movie_recommendations.take(10).foreach(println)

8、关闭Session

spark.stop()

完整代码

package com.wolf.bigdata.spark.core.req
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.{Row, DataFrame}
import org.apache.spark.sql.types.{StructType, StructField, IntegerType}
import org.apache.spark.ml.recommendation.ALS

object Test01 {
  def main(args: Array[String]): Unit = {
    // 创建SparkSession: 首先,创建一个SparkSession,这是与Spark进行交互的入口点。
    val spark = SparkSession.builder()
      .appName("MovieRecommendation")
      .master("local[*]") // 这里设置本地模式,[*] 表示使用所有 CPU 核心
      .getOrCreate()
    // 读取数据: 使用Spark DataFrame API读取用户-电影评分数据。
    val data = spark.read
      .option("header", "true")
      .option("inferSchema", "true")
      .csv("datas/capter5_2ml.csv")
    // 创建ALS模型: 配置ALS模型的参数

    val als = new ALS()
      .setMaxIter(5)
      .setRegParam(0.01)
      .setUserCol("userId")
      .setItemCol("movieId")
      .setRatingCol("rating")
    // 训练模型: 使用训练数据对ALS模型进行训练
    val model = als.fit(data)
    // 生成用户推荐: 使用recommendForUserSubset方法为指定用户生成电影推荐。
    // 请替换user_ids为你要生成推荐的用户ID列表
    val user_ids = Seq(1, 2, 3)
//    val user_df = user_ids.toDF("userId")
    val userRows = user_ids.map(id => Row(id))
    val userSchema = List(StructField("userId", IntegerType, nullable = false))
    val user_df = spark.createDataFrame(spark.sparkContext.parallelize(userRows), StructType(userSchema))
    val user_recommendations = model.recommendForUserSubset(user_df, 10)
    // 生成电影推荐: 使用recommendForItemSubset方法为指定电影生成用户推荐。
    // 请替换movie_ids为你要生成推荐的电影ID列表
    val movie_ids = Seq(1, 2, 3, 4, 5)
//    val movie_df = movie_ids.toDF("movieId")
    val movieRows = movie_ids.map(id => Row(id))
    val movieSchema = List(StructField("movieId", IntegerType, nullable = false))
    val movie_df = spark.createDataFrame(spark.sparkContext.parallelize(movieRows), StructType(movieSchema))
    val movie_recommendations = model.recommendForItemSubset(movie_df, 10)
    // 结果输出: 将生成的推荐结果输出,例如,打印到控制台。
    // 输出用户推荐
//    user_recommendations.show()
    user_recommendations.take(10).foreach(println)
    // 输出电影推荐
//    movie_recommendations.show()
    movie_recommendations.take(10).foreach(println)

    // 关闭SparkSession: 最后,确保关闭SparkSession以释放资源。
    spark.stop()

  }

}

 结果截图

PySpark

PySpark安装

pip install pyspark

or

pip install -i https://pypi.tuna/tsinghua.edu.cn/simple pyspark

Numpy

pip install numpy -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

案例

使用Spark MLlib中的使用ALS算法给每个用户推荐某个商品。

 注意:对原始数据添加列名,方便后续操作

要求

0、创建Session,读取数据

from pyspark.sql import SparkSession
# 创建SparkSession
spark = SparkSession.builder.appName("MovieRecommendation").getOrCreate()

# 读取数据
data = spark.read.csv("capter5_2ml.csv", header=True, inferSchema=True)

 ①设置迭代次数为5次,惩罚系数为0.01,得到评分的矩阵形式(2分)。

from pyspark.ml.recommendation import ALS
als = ALS(maxIter=5, regParam=0.01, userCol="userId", itemCol="movieId", ratingCol="rating")

②对模型进行拟合,训练出合适的模型(2分)。

model = als.fit(data)

③为一组指定的用户生成十大电影推荐(4分)。

user_ids = [1]
# 创建一个DataFrame来存储指定用户的ID
user_df = spark.createDataFrame([Row(userId=uid) for uid in user_ids])
# 使用ALS模型生成前十名用户的电影推荐
user_recommendations = model.recommendForUserSubset(user_df, 10)

④生成前十名用户推荐的一组指定的电影(4分)。

# 创建一个DataFrame包含前十名用户的ID
user_ids_top10 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
top_users_df = spark.createDataFrame([Row(userId=uid) for uid in user_ids_top10])

# 使用ALS模型生成前十名用户的电影推荐
top_users_recommendations = model.recommendForUserSubset(top_users_df, 10)

⑤对结果进行正确输出(1分)。

from pyspark.sql import Row
# 输出一组用户推荐
for row in user_recommendations.collect():
    user_id = row.userId
    recommendations = [(r.movieId, r.rating) for r in row.recommendations]
    print(f"User {user_id} recommendations: {recommendations}")

# 输出前十名用户的电影推荐
for row in top_users_recommendations.collect():
    user_id = row.userId
    recommendations = [(r.movieId, r.rating) for r in row.recommendations]
    print(f"User {user_id} recommendations: {recommendations}")

关闭Session

spark.stop()

建模过程,建模思路描述(2分):

完整代码

from pyspark.sql import SparkSession
from pyspark.sql import Row
from pyspark.ml.recommendation import ALS

# 创建SparkSession
spark = SparkSession.builder.appName("MovieRecommendation").getOrCreate()

# 读取数据
data = spark.read.csv("capter5_2ml.csv", header=True, inferSchema=True)

# ① 设置迭代次数为5次,惩罚系数为0.01,得到评分的矩阵形式
als = ALS(maxIter=5, regParam=0.01, userCol="userId", itemCol="movieId", ratingCol="rating")

# ② 对模型进行拟合,训练出合适的模型
model = als.fit(data)

# ③ 为一组指定的用户生成十大电影推荐
# 请替换user_ids为你要生成推荐的用户ID列表
# user_ids = [1, 217, 381]
user_ids = [1]
# 创建一个DataFrame来存储用户ID
user_df = spark.createDataFrame([Row(userId=uid) for uid in user_ids])

user_recommendations = model.recommendForUserSubset(user_df, 10)

#
# # 请替换movie_ids为你要生成推荐的电影ID列表
# movie_ids = [1, 3, 6, 1023, 1024]
#
# # 创建一个DataFrame来存储电影ID
# movie_df = spark.createDataFrame([Row(movieId=mid) for mid in movie_ids])
#
# movie_recommendations = model.recommendForItemSubset(movie_df, 10)

# ④ 生成前十名用户推荐的一组指定的电影
# 创建一个DataFrame包含前十名用户的ID
user_ids_top10 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
top_users_df = spark.createDataFrame([Row(userId=uid) for uid in user_ids_top10])

# 使用ALS模型生成前十名用户的电影推荐
top_users_recommendations = model.recommendForUserSubset(top_users_df, 10)

# ⑤ 对结果进行正确输出
# 输出1Group用户推荐
for row in user_recommendations.collect():
    user_id = row.userId
    recommendations = [(r.movieId, r.rating) for r in row.recommendations]
    print(f"User {user_id} recommendations: {recommendations}")

# # 输出电影推荐
# for row in movie_recommendations.collect():
#     movie_id = row.movieId
#     recommendations = [(r.userId, r.rating) for r in row.recommendations]
#     print(f"Movie {movie_id} recommendations: {recommendations}")

# 输出前十名用户的电影推荐
for row in top_users_recommendations.collect():
    user_id = row.userId
    recommendations = [(r.movieId, r.rating) for r in row.recommendations]
    print(f"User {user_id} recommendations: {recommendations}")
# 关闭SparkSession
spark.stop()

结果截图:

问题:

Q:为什么数据中的rating最高分只有五分,但是预测出的分数有很多都超过了五分?

A(GPT生成):

在协同过滤推荐系统中,ALS(交替最小二乘法)模型的预测评分可以超过原始数据中的评分范围。这是因为ALS模型是一种基于矩阵分解的模型,它试图学习用户和物品的潜在特征向量,以最小化预测评分与实际评分之间的均方误差。这意味着模型可以生成比原始评分范围更广的预测值。

在你的情况下,原始数据的评分范围为1到5分,但ALS模型的预测评分可以超过这个范围。这并不一定是一个问题,因为ALS模型的目标是提供一组推荐,而不仅仅是复制现有的评分。模型可能会认为某些用户对某些电影的兴趣更高,因此给出了更高的预测评分。

通常,当使用ALS模型时,我们关心的是生成有序的推荐列表,而不是特定的评分值。你可以根据预测评分的高低来排序推荐列表,并向用户推荐最高评分的物品。当然,你可以选择对生成的预测评分进行某种标准化,以确保它们在1到5分的范围内,但这通常不是必要的。

一些其他问题

JAVA_HOME问题

如果PySpark没有自动检测到JAVA_HOME:

import os

os.environ['JAVA_HOME'] = ""

Linux下Python安装

下载、解压

安装依赖

yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel gcc make

./configure --prefix=/opt/module/Python-3.9.18
make
make install
// 建立软链接
sudo ln -s /opt/module/Python-3.9.18/bin/python3.9 /usr/bin/python3
sudo ln -s /opt/module/Python-3.9.18/bin/pip3.9 /usr/bin/pip3

// 配置环境变量
vi ~/.bash_profile

#配置python
export PYTHON_HOME=/opt/module/Python-3.9.18
export PATH=$PYTHON_HOME/bin:$PATH
// 激活环境变量
source ~/.bash_profile

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值