第三章 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之间