Spark快速大数据分析

第三章 RDD编程

Driver(main函数):定义了RDD以及算子操作

3.1 RDD基础

transformation(转换)算子操作:RDD[A]->RDD[B]

action(立即)算子操作:RDD[A]->result //立即提交job执行,并返回result的过程

延迟执行:transformation算子操作不会立即执行,而是在需要的时候执行

3.2基本RDD操作

1)伪集合操作:并集(union+distinct算子实现),distinct算子属于shuffle操作

交集(intersection算子实现)shuffle操作

差集(subtract算子实现)只存在RDD[A]中不存在RDD[B]中的元素组成,shuffle操作

笛卡尔积(cartesian算子实现)新元素由一个来自RDD[A]中的元素与一个来自RDD[B]中的元素组合形成,开销很大

 

2)平均值计算(aggregate实现):

val result = input.aggregate((0, 0))(//(0,0)表示期望返回值result的第一个元素类型初始值以及第二个元素类型的初始值

                    (acc, value) => (acc._1 + value, acc._2 + 1),//第一个函数将统计每个累加器中的总和与总数

                    (acc1, acc2) => (acc1._1 + acc2._1, acc1._2 + acc2._2))//第二个函数将多个累加器合并

      val avg =result._1 / result._2.toDouble

3.3 缓存

当内存使用量达到上限时,Spark自动采用LRU策略(最近最少未使用)替换,如果时Memory_And_Disk_* 替换的数据分区Partition会写入磁盘

 

第四章 键值对操作

4.1 创建PairRDD
mapToPair:输入一个元素,返回一个Tuple<keyType,valueType>

 

4.2 PairRDD操作

计算平均值:rdd.mapValues(x=> (x, 1)).reduceByKey((x, y) => (x._1 + y._1, x._2 + y._2))

 

利用combineByKey 计算平均值

val result = input.combineByKey(

       (v) =>(v, 1),

       (acc: (Int,Int), v) => (acc._1 + v, acc._2 + 1),

       (acc1: (Int,Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)

       ).map{ case(key, value) => (key, value._1 / value._2.toFloat) }

      result.collectAsMap().map(println(_))

combineByKey()的基本过程:

1)        遍历RDD中的每个分区

2)        当分区中第一次出现某个key时,创建与该key对应的累加器

3)        当再次与到该key时,利用累加器记录value的总和以及key的总频次

4)        合并多个分区相同key对应的多个累加器记录值,第一个元素表示整个RDD中每个key对应的value总和以及出现频次

 

4.3 并行度优化

并行度分为执行并行度和数据并行度,分别从local[4]和parallise(4)体现

 

在Spark运行期间也能动态改变并行度:

1)        repartitions()改变数据并行度

2)        可以首先rdd.partitions.size()查看RDD的分区数,然后在调用coalesce()

 

4.4 左外连接与右外连接

storeAddress.leftOuterJoin(storeRating) ==//storeAddress为源RDD,storeRating为第二个RDD

    {(Store("Ritual"),("1026 Valencia St",Some(4.9))),

      (Store("Starbucks"),("Seattle",None)),

      (Store("Philz"),("748 Van Ness Ave",Some(4.8))),

      (Store("Philz"),("3101 24th St",Some(4.8)))}

    storeAddress.rightOuterJoin(storeRating) ==// storeRating为源RDD,storeAddress为第二个RDD

    {(Store("Ritual"),(Some("1026 Valencia St"),4.9)),

      (Store("Philz"),(Some("748 Van Ness Ave"),4.8)),

      (Store("Philz"), (Some("3101 24th St"),4.8))}

 

4.5 排序

  val input:RDD[(Int, Venue)] = ...

     implicit val sortIntegersByString = newOrdering[Int] {

       override defcompare(a: Int, b: Int) = a.toString.compare(b.toString)

     }

    rdd.sortByKey()

//上述采用了Scala的隐式变量,即当调用rdd.sortByKey()时,实际上传入了sortIntegersByString参数作为排序规则

4.6 数据分区

Spark可以保证同一组的key被放在同一个节点,即key的哈希值%100相同的放在一个节点或者采用范围分区法,将key在同一范围的记录放在一个节点,Join操作原理是首先对两个RDD中的每个记录的key进行哈希值计算,然后将hash值相同的记录发送给同一个节点,最后在该节点上完成相同key不同value记录的连接

val sc = new SparkContext(...)
valuserData = sc.sequenceFile[UserID, UserInfo]("hdfs://...").persist()

// 周期性调用函数来处理过去五分钟产生的事件日志
// 假设这是一个包含(UserID, LinkInfo)对的SequenceFile defprocessNewLogs(logFileName: String) {

       val events =sc.sequenceFile[UserID, LinkInfo](logFileName)

       val joined =userData.join(events)// RDD of (UserID, (UserInfo, LinkInfo)) pairs

       valoffTopicVisits = joined.filter {

         case(userId, (userInfo, linkInfo)) => // Expand the tuple into its components

          !userInfo.topics.contains(linkInfo.topic)

}.count()

      println("Number of visits to non-subscribed topics: " +offTopicVisits)

}

由于join操作会对userData和events进行混洗操作,因此网络开销较大

 

改进措施采用userData采用partitionBy()告诉spark其预先使用hash分区,因此当后面在调用join时,只会对events进行混洗

val sc = new SparkContext(...)

     val userData =sc.sequenceFile[UserID, UserInfo]("hdfs://...")

.partitionBy(new HashPartitioner(100))// 构造100个分区 .persist() //理想情况下100应为集群中的总核数

分区策略:sortByKey() 和groupByKey() 会分别生成范围分区的 RDD 和哈希分区的 RDD

1)查看分区方式

rdd.partitioner().isDefine判断是否有值

rdd.partitioner().get()返回Partitioner对象(可能是RangePartitioner或者HashPartitioner)

2)不发生跨节点的数据混洗情况

如果两个RDD采用相同的分区策略,且缓存在用一个节点或者其中一个RDD还未计算,则不会发生垮节点的数据混洗

大部分二元算子操作有默认的分区方式,输出数据的分区方式取决于父 RDD 的分区方式

3)PageRank实现

// 假设相邻页面列表以Spark objectFile的形式存储
val links =sc.objectFile[(String, Seq[String])]("links")

                  .partitionBy(new HashPartitioner(100))

                  .persist()

// 将每个页面的排序值初始化为1.0;由于使用mapValues,生成的RDD // 的分区方式会和"links"的一样
var ranks =links.mapValues(v => 1.0)

// 运行10轮PageRank迭代 for(i <- 0 until 10) {

       valcontributions = links.join(ranks).flatMap {

         case(pageId, (links, rank)) =>

          links.map(dest => (dest, rank / links.size))

       }

       ranks =contributions.reduceByKey((x, y) => x + y).mapValues(v => 0.15 + 0.85*v)

     }

// 写出最终排名 ranks.saveAsTextFile("ranks")

4)自定义分区方式

class DomainNamePartitioner(numParts: Int) extendsPartitioner {

       override defnumPartitions: Int = numParts

       override defgetPartition(key: Any): Int = {

         val domain= new Java.net.URL(key.toString).getHost()

         val code =(domain.hashCode % numPartitions)

         if(code< 0) {

code + numPartitions // 使其非负 }else{

code }

}
// 用来让Spark区分分区函数对象的Java equals方法


override def equals(other: Any):Boolean = other match {

         case dnp:DomainNamePartitioner =>

          dnp.numPartitions == numPartitions

5)         case _=>

6)           false

7)} }

 

第五章 数据读取与保存

5.1文本文件读取

如果读取同一个目录下的多个小文件,且需要知道数据来自哪个文件时,调用sc.wholeTextFiles(),该方法会返回一个pairRDD

其中key为文件名

val input = sc.wholeTextFiles("file://home/holden/salesFiles")

     val result =input.mapValues{y => //求每行数据的平均值

       val nums =y.split(" ").map(x => x.toDouble)

       nums.sum /nums.size.toDouble

     }

 

5.2 JSON文件

1)JSON文件读取

case classPerson(name: String, lovesPandas: Boolean) // 必须是顶级类
...
// 将其解析为特定的caseclass。使用flatMap,通过在遇到问题时返回空列表(None)// 来处理错误,而在没有问题时返回包含一个元素的列表(Some(_))


val result= input.flatMap(record => {

      try {

        Some(mapper.readValue(record, classOf[Person]))

      } catch {

        case e: Exception => None

}})

2)JSON文件写入

result.filter(p=> P.lovesPandas).map(mapper.writeValueAsString(_))

      .saveAsTextFile(outputFile)

5.3 CSV文件

1)CSV文件读取

     importJava.io.StringReader

    import au.com.bytecode.opencsv.CSVReader

    ...

    val input = sc.textFile(inputFile)

    val result = input.map{ line =>

      val reader = new CSVReader(new StringReader(line));

      reader.readNext();

}

2)完整读取CSV文件:如果字段中含有换行符,则需要完整读取每个文件,然后解析各段

     case class Person(name: String,favoriteAnimal: String)

    val input = sc.wholeTextFiles(inputFile)

    val result = input.flatMap{ case (_, txt) =>

      val reader = new CSVReader(new StringReader(txt));

      reader.readAll().map(x => Person(x(0), x(1)))

    }

3)      CSV文件写入

    pandaLovers.map(person=> List(person.name, person.favoriteAnimal).toArray)

    .mapPartitions{people =>

      val stringWriter = new StringWriter();

      val csvWriter = new CSVWriter(stringWriter);

      csvWriter.writeAll(people.toList)

      Iterator(stringWriter.toString)

}.saveAsTextFile(outFile)

 

5.4 SequenceFile文件(序列化的key-value数据文件)

1)读取

 

val data =sc.sequenceFile(inFile, classOf[Text], classOf[IntWritable]).

      map{case (x, y) => (x.toString, y.get())}

inFile:sequenceFile文件地址

classOf[Text]:keyType 必须是Writable子类

classOf[IntWritable]:valueType 必须是Writable子类

读取sequenceFile中的自定义Key or Value时,需要首先自定义对应序列化的类即继承Writable接口

 

2)写入

val data =sc.parallelize(List(("Panda", 3), ("Kay", 6),("Snail", 2)))//创建PairRDD data.saveAsSequenceFile(outputFile) //写入

 

5.5 对象文件

1)读取

sc.objectFile()  //读取对象文件(通过java序列化)得到RDD[Object]

2)写入

RDD[Object].saveAsObjectFile()

 

5.6 Hadoop输入输出格式

1)旧API读取key-value文本文件,’\t’制表符分割,参数分别为KeyType,ValueType,InputFormat

val input = sc.hadoopFile[Text, Text, KeyValueTextInputFormat](inputFile).map{

       case (x, y)=> (x.toString, y.toString)

}

2)新API读取

val input = sc.newAPIHadoopFile(inputFile,classOf[LzoJsonInputFormat],

classOf[LongWritable],classOf[MapWritable], conf)

读取LZO压缩的JSON数据,分别为输入文件,InputFormat,KeyType,ValueType,conf

 

4)        新API写入

      result.saveAsHadoopFile(fileName, Text.class, IntWritable.class,

SequenceFileOutputFormat.class);

输出文件名,keyType,valueType,OutputFormat

 

5.7 压缩格式

gzip,lzo(性能最好)在hadoop.conf中指定压缩格式,snappy,对于spark读取压缩数据源,最好使用newHadoopAPT设置压缩格式

 

5.8 Apache Hive

1)读取hive数据

val hiveCtx= new org.apache.spark.sql.hive.HiveContext(sc)

val rows =hiveCtx.sql("SELECT name, age FROM users")

valfirstRow = rows.first()
println(firstRow.getString(0)) // 字段0是name字段

2)读取JSON数据

val tweets =hiveCtx.jsonFile("tweets.json")

tweets.registerTempTable("tweets")

val results = hiveCtx.sql("SELECTuser.name, text FROM tweets")

 

5.9 JDBC读取

def createConnection() = {Class.forName("com.mysql.jdbc.Driver").newInstance();

DriverManager.getConnection("jdbc:mysql://localhost/test?user=holden");

    }

    def extractValues(r: ResultSet) = {

      (r.getInt(1), r.getString(2))

}

    val data = new JdbcRDD(sc,

      createConnection, "SELECT * FROM panda WHERE ? <= id AND id<= ?",

      lowerBound = 1, upperBound = 3, numPartitions = 2, mapRow =extractValues)

    println(data.collect().toList)

 

5.10 HBase访问

val conf =HBaseConfiguration.create() conf.set(TableInputFormat.INPUT_TABLE,"tablename") // 扫描哪张表

val rdd=sc.newAPIHadoopRDD(conf,classOf[TableInputFormat],classOf[ImmutableBytesWritable],classOf[Result])

TableInputFormat包含多个可以用来优化对 HBase 的读取的设置项,比如将扫描限制到一部分列中,以及限制扫描的时间范围

 

第六章 Spark编程进阶

6.1 累加器

1)在Driver端创建累加器并赋予初值

val blankLines =sc.accumulator(0).

2)在Executor端利用累加器统计计数

blankLines += 1

3)在Driver端汇总累加器总和

blankLines.value

val sc =new SparkContext(...)

val file =sc.textFile("file.txt")


valblankLines = sc.accumulator(0) // 创建Accumulator[Int]并初始化为0 val callSigns =file.flatMap(line => {

if (line =="") {
blankLines += 1 // 累加器加1

}

      line.split(" ")

    })

    callSigns.saveAsTextFile("output.txt")

    println("Blank lines: " + blankLines.value)

 

Spark慢任务处理:一旦spark检测到错误或者慢任务,它会重新在其他节点启动相同的任务,最先执行完任务的结果作为处理结果

 

尽量把累加器放在Action操作中执行,能保证每个任务对各累加器的修改只应用一次

 

 

6.2 广播变量

1)在Driver端为较大数据集创建广播变量

val signPrefixes =sc.broadcast(loadCallSignTable())

2)在Executor中的每个任务都能共享此广播变量

signPrefixes.value

3)这是由于广播变量只会发送给每个Executor一次,对于广播变量的优化主要在于选择合适的序列化器spark.serializer,一般采用kryo

对于基于分区的操作,可以采用mapPartitions()算子对每个Partition进行处理前,创建一次连接或者映射器之类的

 

6.3 与外部程序间的管道pipe

valdistScript = "./src/R/finddistance.R"

valdistScriptName = "finddistance.R"

sc.addFile(distScript)//在Spark上下文中添加脚本文件,使得每个工作节点都能下载文件(当action操作时,文件被下载)

val distances = contactsContactLists.values.flatMap(x=> x.map(y =>

s"$y.contactlay,$y.contactlong,$y.mylat,$y.mylong")).pipe(Seq(

SparkFiles.get(distScriptName)))//通过pipe调用脚本程序,对RDD数据进行处理

println(distances.collect().toList)

 

6.4 数值型RDD的统计信息

valdistanceDouble = distance.map(string =>string.toDouble)


val stats =distanceDoubles.stats() //创建StatsCounter对象


val stddev= stats.stdev


val mean =stats.mean


valreasonableDistances = distanceDoubles.filter(x => math.abs(x-mean) < 3 *stddev) println(reasonableDistance.collect().toList)

 

第七章 在集群上运行Spark

Spark 内部的公 平调度器(Fair Scheduler)会让长期运行的应用(Spark SQL的JDBC服务器)定义调度任务的优先级队列

设置配置属性 spark.deploy.spreadOut 为 false 来要求Spark 把执行器进程合并到尽量少的工作节

 

第八章 Spark调优与调试

(1)       每个物理步骤(Stage)的数据来自于数据存储,或者缓存RDD,或者shuffled RDD

(2)       执行必要的算子操作转换RDD

(3)       将输出写入数据混洗文件中,写入外部存储或者驱动器程序

 

http://localhost:4040页面可以显示Spark工作状态

1)      作业页面:找到慢任务,以及解决数据倾斜

2)      存储页面:哪些RDD被缓存,缓存媒介,缓存量

3)      执行页面:每个执行器的资源占有情况,找到开销大的用户代码

4)      环境页面:用来调试Spark配置项

 

8.1 性能调优

1)并行度

在数据混洗操作时,使用参数的方式为混洗后的RDD指定并行度

对任意RDD采用重新分区来获取更多或者更少的分区数,如果确定减少分区数,可以采用coalesce()操作,比repartition()更高效

2)序列化

Spark默认使用Java序列化机制,但压缩时间不高且压缩比不高,因此spark提供了kryo压缩机制,需要注册

val conf =new SparkConf()

conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer")

conf.set("spark.kryo.registrationRequired","true") conf.registerKryoClasses(Array(classOf[MyClass],classOf[MyOtherClass]))

3)内存管理

默认情况下,Spark会使用60%的空间存储RDD(spark.storage.memoryFractio 指定缓存RDD的内存占整个堆空间的比例大小,超过部分则会被移除内存),20%的空间存储数据混洗后的数据(spark.shuffle.memoryFraction限定混洗的内存占整个内存比例),20%留给用户程序(剩余内存用于用户程序申请)

5)      硬件供给

每个执行器所占内存,每个执行器所占核数,执行器节点总数,存储临时数据的本地磁盘数量(存储混洗操作后的中间数据或者溢写到磁盘的RDD数据)

 

第九章 Spark SQL

// 创建Spark SQL的HiveContext

val sc =new SparkContext(...)

  valhiveCtx = new HiveContext(sc)

 
// 导入隐式转换支持


importhiveCtx._

核心在于创建SchemaRDD(DataFrame),即包含数据信息又包含属性信息

val input =hiveCtx.jsonFile(inputFile)
// 注册输入的SchemaRDD
input.registerTempTable("tweets")
// 依据retweetCount(转发计数)选出推文
val topTweets = hiveCtx.sql("SELECT text, retweetCount FROM

tweets ORDER BY retweetCount LIMIT10")

 

hiveCtx.cacheTable("tweets") 方法  //Spark SQL缓存数据表,使用列式存储格式在内存中保存数据,只存在于驱动器程序的生命周期

 

1    ApacheHive

要把 SparkSQL 连接到已经部署好的 Hive 上 ,把你 的hive-site.xml 文件复制到 Spark 的./conf/ 目录下即可

    import org.apache.spark.sql.hive.HiveContext

    val hiveCtx = new HiveContext(sc)

    val rows = hiveCtx.sql("SELECT key, value FROM mytable")//从Hive中读取数据

    val keys = rows.map(row => row.getInt(0))

2 Parquet(列式存储格式)

HiveContext.parquetFile()//读取parquet文件

2    JSON

val input = hiveCtx.jsonFile(inputFile)

3    基于RDD

case class HappyPerson(handle: String,favouriteBeverage: String)
...
// 创建了一个人的对象,并且把它转成SchemaRDD
val happyPeopleRDD =sc.parallelize(List(HappyPerson("holden", "coffee"))) // 注意:此处发生了隐式转换

// 该转换等价于sqlCtx.createSchemaRDD(happyPeopleRDD)happyPeopleRDD.registerTempTable("happy_people")

4    JDBC/ODBC

JDBC服务器(类似于hiveserver2)需要通过sbin/start-thriftserver.sh启动,默认情况下在端口10000监听

启动Beeline创建数据表

> CREATE TABLE IF NOT EXISTS mytable(key INT, value STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY‘,’;

> LOAD DATA LOCALINPATH‘learning-spark-examples/files/int_string.csv’ INTO TABLE mytable;

 

JDBC服务器是一个单驱动程序,使不同程序之间的共享成为可能

5 Spark SQLUDF

registerFunction("strLenScala",(_: String).length)

valtweetLength = hiveCtx.sql("SELECT strLenScala('tweet') FROM tweets LIMIT10")

 

 

Spark SQL性能调优

 

第十章 Spark Streaming

Spark Streaming处理流程

1)      创建Spark Streaming Context,ssc

2)      读取数据源,创建DStream(数据流)

3)      按时间间隔分批次,封装多个RDD,一个Job对应一个RDD

4)      由ssc向sc提交Job,sc通过DAGScheduler分割Job为TaskSet,sc通过TaskScheduler调度Task到指定Executor执行任务,得到结果写入外部存储

Spark Streaming容错性:Spark Streaming为每个输入源创建一个接收器,不断从输入源接收数据并保存为RDD,并将接收的数据保存在另外一个Executor中,能容忍一个节点故障

 

Spark Streaming Checkpoint:把状态阶段性地保存在可靠文件系统中,每处理5~10个批处理就保存一次,恢复数据时只需要回溯到上一个检查点

 

Spark Streaming 算子操作分为有状态Transformation操作和无状态Transformation操作

1)      无状态:每个批次的处理不依赖于之前的批次数据。作用于DStream中的每个RDD

val accessLogDStream = logData.map(line=> ApacheAccessLog.parseFromLogLine(line))

val ipDStream =accessLogsDStream.map(entry => (entry.getIpAddress(), val ipCountsDStream =ipDStream.reduceByKey((x, y) => x + y)

DStream与DStream之间能连接工作join(),cogroup()等等,但原理是分别作用于两个DStream的同一时间的批次

 

除此之外可以调用transform()算子,DStream中的每个RDD都要执行

val outlierDStream = accessLogsDStream.transform { rdd =>

      extractOutliers(rdd)

}

2)      有状态:拿之前批次数据或者中间数据来计算当前批次数据

       通过ssc.checkpoint("hdfs://...") 打开检查点

 

主要分为滑动窗口和updateStateByKey()

滑动窗口:整合多个批次的结果,计算出整个窗口的结果;窗口时长(决定一次处理多少个批次数据)和滑动步长(决定每隔多长时间执行一次窗口结果)均为批次间隔的整数倍

重复区间(previous window的值 - oldRDD的值) =》 也就是中间重复部分, 再加上newRDD的值, 这样的话得到的结果就是10秒到25秒这个时间区间的值

val ipDStream = accessLogsDStream.map(logEntry =>(logEntry.getIpAddress(), 1))

     valipCountDStream = ipDStream.reduceByKeyAndWindow(

{(x, y) => x + y},

{(x, y) => x - y},

Seconds(30),

Seconds(10))

 

需要DStream跨批次维护状态,要使用updateStateByKey()提供了一个update(events,oldState)函数,接收与某键相关的事件以及该键之前的状态,返回该键对应的新状态

events:当前批次收到的事件列表

oldState:该键对应的之前状态,放入Option中

newState:返回的新状态,Option

其函数结果会返回新的DStream,每个时间间隔由对应的键值(键-状态)组成

def updateRunningSum(values: Seq[Long], state:Option[Long]) = {

      Some(state.getOrElse(0L) + values.size)

}

val responseCodeDStream =accessLogsDStream.map(log => (log.getResponseCode(), 1L)) valresponseCodeCountDStream =responseCodeDStream.updateStateByKey(updateRunningSum _)

 

print()调试接口每次从DStream的每个批次RDD中取前10个元素输出、

 

ipAddressRequestCount.saveAsTextFiles("outputDir","txt")

将DStream中的每个批次结果保存在outputDir下的子目录中,每个文件名为时间+txt

将DStream保存为HDFS中的SequenceFile

  valwritableIpAddressRequestCount = ipAddressRequestCount.map {

       (ip, count)=> (new Text(ip), new LongWritable(count)) }

    writableIpAddressRequestCount.saveAsHadoopFiles[

      SequenceFileOutputFormat[Text, LongWritable]]("outputDir","txt")

 

DStream存储在JDBC中

ipAddressRequestCount.foreachRDD { rdd =>

      rdd.foreachPartition { partition =>

// 打开 存 的 数据 的 partition.foreach{ item =>

// 用 item存 中 }

// }

}

Spark Streaming 数据容错

def createStreamingContext() = {

       ...

val sc = new SparkContext(conf)
// 1 作 次大 StreamingContext


val ssc = new StreamingContext(sc,Seconds(1))

ssc.checkpoint(checkpointDir)

     }

     ...

val ssc =StreamingContext.getOrCreate(checkpointDir,createStreamingContext_)//如果驱动器程序故障则从checkpoint重构,否则需要重建

 

在提交驱动器程序时使用—supervise标记来让Spark重启失败的驱动器程序以及需要—deploy-mode cluster保证驱动器程序在集群中运行

 

使用可靠数据源是保证Executor容错的最好方式

 

影响Spark Streaming的性能指标:

1)批次和窗口大小

500ms是较好的批次大小

2)并行度

增加接收器数目,使用repartitions()重新分区,提高聚合计算的并行度

3)垃圾回收和内存使用

打开并发标志-清除收集器

spark-submit --confspark.executor.extraJavaOptions=-XX:+UseConcMarkSweepGC App.jar

设置 spark.cleaner.ttl,Spark会显示移除超过给定时间范围内的老RDD
使用Kryo序列化机制

 

第十一章 Spark Mllib

1)逻辑回归+梯度下降法(损失函数,避免过拟合)的垃圾邮件分类器

val spam = sc.textFile("spam.txt")

    val normal =sc.textFile("normal.txt")

// HashingTF 邮件 本 10000 特 的 量
val tf = newHashingTF(numFeatures = 10000)
// 邮件 分 , 特 
val spamFeatures =spam.map(email => tf.transform(email.split(" "))) valnormalFeatures = normal.map(email => tf.transform(email.split("")))

// LabeledPoint数据集分 存 邮件 和 邮件 的 子
valpositiveExamples = spamFeatures.map(features => LabeledPoint(1, features))val negativeExamples = normalFeatures.map(features => LabeledPoint(0,features)) val trainingData = positiveExamples.union(negativeExamples)trainingData.cache() // 辑 迭代算法,所 存 数据RDD

// 用SGD算法运行 辑 
val model =new LogisticRegressionWithSGD().run(trainingData)

// 邮件 和 邮件 的 子分 进行 val posTest =tf.transform(

      "O M GGET cheap stuff by sending money to ...".split(" "))

    val negTest =tf.transform(

      "Hi Dad,I started studying Spark the other ...".split(" "))

   println("Prediction for positive test example: " +model.predict(posTest))

   println("Prediction for negative test example: " +model.predict(negTest))

 

典型算法:

1.特征提取

1)       TF-IDF

TF-IDF的算法组成为HashTF(统计每个单词在文档中的词频)+IDF(一个单词在文档库中出现的逆频繁程度)

HashTF:利用单词对所需长度S的取模求哈希值,从而将单词映射到0~S-1之间的数字上

IDF:逆文档频率

val conf = newSparkConf().setAppName("TfIdfTest")

    valsc = new SparkContext(conf)

 

   // Load documents (one per line).

    valdocuments: RDD[Seq[String]] = sc.textFile("...").map(_.split(" ").toSeq)

 

   val hashingTF = new HashingTF()

    valtf: RDD[Vector] = hashingTF.transform(documents)

 

   tf.cache()

   val idf = new IDF().fit(tf)

val tfidf: RDD[Vector] = idf.transform(tf) //每个单词对每个文档的权重

 

2)       缩放

需要考虑特征向量中各元素的幅值,并在特征缩放调整为平等对待时表现最好(即控制特征平均值与标准差)

 val vectors=data.map(p=>p.features)

 //调用初始化一个StandardScaler对象,具体使用方法查看Spark api

    valscaler=new StandardScaler(withMean = true,withStd = true).fit(vectors)

 //重新标准化特征变量

    valscalerData=data.map(point=>

   LabeledPoint(point.label,scaler.transform(point.features))

)

 

3)       正规化

val data = MLUtils.loadLibSVMFile(sc,"data/mllib/sample_libsvm_data.txt")

val normalizer1 = new Normalizer()

val normalizer2 = new Normalizer(p = Double.PositiveInfinity)

// Each sample in data1 will be normalized using $L^2$ norm.

val data1 = data.map(x => (x.label, normalizer1.transform(x.features)))

// Each sample in data2 will be normalized using $L^\infty$ norm.

val data2 = data.map(x => (x.label, normalizer2.transform(x.features)))

 

4)       Word2Vec模型,将每个单词转换成向量

val word2vec = new Word2Vec()

word2vec.setSeed(42) // we do this togenerate the same results each time

val word2vecModel = word2vec.fit(tokens)

word2vecModel.findSynonyms("hockey",20).foreach(println) //查看与hockey单词相似的单词

 

2. 统计数据

1)统计综述(Statistics.colStats)
   val observations: RDD[Vector] = ... // an RDD of Vectors
 
// Compute column summary statistics.
val summary: MultivariateStatisticalSummary = Statistics.colStats(observations)
println(summary.mean) // 向量中每列的平均值
println(summary.variance) // 每列的方差
println(summary.numNonzeros) // 每列的非0个数

2)由向量组成的RDD列间相关矩阵(皮尔森相关或者斯皮尔森相关)

val seriesX: RDD[Double] = sc.parallelize(Array(1, 2, 3, 3, 5))  // a series
// must have the same number of partitions and cardinality as seriesX
val seriesY: RDD[Double] = sc.parallelize(Array(11, 22, 33, 33, 555))
 
// compute the correlation using Pearson's method. Enter "spearman" for Spearman's method. If a
// method is not specified, Pearson's method will be used by default.
val correlation: Double = Statistics.corr(seriesX, seriesY, "pearson")

.   3)皮尔森独立性测试 Statistics.chiSqTest(rdd) 


.    rdd是有一组LabeledPoint组成,返回ChiSqTestResult结果,有p值,测试统计,以及每个特征的自由度

.   3.分类与回归

朴素贝叶斯分类器是多分类,预测结果在0~S-1之间

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值