詳細內容參照Spark官網:http://spark.apache.org/
Spark相關項目:
Spark SQL 、Spark Streaming 、Machine Learning 、GraphX
1、Spark SQL :用Spark編寫的混合SQL查詢,能在分佈式數據集中查詢結構化數據,使得複雜分析算法的查詢更容易。
2、Spark Streaming :Spark Streaming 容易去建立一個可擴展的容錯式流媒體應用,使得流處理應用與批處理工作一樣。
3、Machine Learning:Machine Learning Lib是Apache的可擴展機器學習庫,實現了一些機器學習算法,算法運行效率高,其實現的算法有:
基礎:數據類型、概要統計
分類與迴歸:線性支持向量機(SVM)、邏輯迴歸、線性最小二乘法、套索和嶺迴歸、決策樹、樸素貝葉斯
協同過濾:交替最小二乘法(ALS)
聚類:K-means
最優化:隨即梯度下降法、L-BFGS
4、GraphX:GraphX是對圖標並行計算。
Spark官網例子:
1、Text Search:從日誌文件中搜索錯誤
2、Word Count:統計單詞
3、Estimateing PI:估計PI值
4、Logical Regression:尋找一個超平面,在多維空間中區分點集
Spark兩種工作模式:
1、Spark Shell:運用scala命令交互式分析處理
2、在scala中獨立工作:編寫完scala程序後用工具sbt將其打成jar包,再運行。
Spark的彈性分佈式數據集RDD:
1、並行集合:接收一個已經存在的scala集合,然後進行各種並行計算;2、Hadoop數據集:從Hadoop的文件存儲系統HDFS中獲取數據
Spark對於數據集RDD的兩種操作:轉換和動作:
1、transformations:從現有數據集上創建一個新的數據集,如Map操作就是一種轉換,另外還有filter、flatmap、diatinct、join等操作;
2、Action:在數據集上運行,並返回一個值給驅動程序,如reduce操作就是一種動作,另外還有collect()、count()等
Spark的兩種共享變量(該變量在不同節點任務上被共享):
1、廣播變量:可在內存的所有節點上緩存變量
2、累加器:用於做加法的變量
Spark: Cluster Computing with Working Sets
Spark是一個集羣計算工作集,能對大規模數據進行快速處理,和Hadoop一樣,對大量數據進行分佈並行處理。實際上,spark是對Hadoop的補充,spark在某些工作負載方面更加優越,應用性更廣。它可以像操作本地集合對象一樣輕鬆地操作分佈式數據集,也可以在Hadoop的文件系統HDFS中並行運行。Spark能更好的解決Hadoop中不能實現的迭代計算和交互式計算問題。Spark和Hadoop最大的不同是Spark可以重用存儲在cache中的數據,極大提高運行效率,而Hadoop每次迭代都需要重新從硬盤讀取數據。Spark在迭代機器學習的算法時速度是Hadoop的10倍以上。
一、Spark簡介
Mapreduce編程模型實現他們的可擴展性和容錯性是通過提供一個用戶創建的編程模型,該編程模型操作的是無環數據流圖。在大型應用程序中,無環數據流工作模式效率不高。Spark可以通過多路並行操作重用工作集中的數據。在下列兩個用例中可以看出spark比mapreduce更有效:
1、迭代工作
很多機器學習算法需要在同一數據集上進行多次運算來尋找最優參數,mapreduce每次運行都要從硬盤重新加載這些數據,而spark可以重用這些數據。
2、迭代分析
在數據庫查詢時需要加載有用的數據,spark是把數據緩存到內存,而mapreduce運行程序是從硬盤上加載數據,導致很大的時間延遲。
Spark的一個主要抽象概念是RDD(彈性數據集),RDD是一個元素集合,即spark操作的特殊格式的數據集。RDD劃分到集羣的各個節點上,用戶可以明確的緩存RDD到內存中,並可以重用這些數據集進行並行操作。RDD通過一個稱爲lineage的概念來實現數據集容錯:如果RDD中部分數據丟失,可以在其他RDD中重建這部分數據。
目前有兩種類型的RDD:
1、並行集合(Parallelized Collections):通過調用SparkContext的parallelize方法,在一個已經存在的scala集合上創建的集合,scala集合的對象將會被拷貝,創建出一個可以被並行操作的分佈式數據集,例:val distData=sc.parallelize(data);
2、Hadoop數據集(Hadoop Datasets): Spark從HDFS上創建的分佈式數據集,通過調用SparkContext的textFile方法。例:val distFile=sc.textFile(“hdfs://10.0.2.15:8080/user/input”);
另外還有兩種方式來創建RDD:
3、從已存在的RDD中轉換而來,通過flatMap操作,把類型A數據轉換成類型B數據;
4、RDD的持久化:Spark最重要的一個功能,就是在不同操作間,持久化(或緩存)一個數據集在內存中。當你持久化一個RDD,每一個結點都將把它的計算分塊結果保存在內存中,並在對此數據集(或者衍生出的數據集)進行的其它動作中重用。這將使得後續的動作(Actions)變得更加迅速(通常快10倍)。緩存是用Spark構建迭代算法的關鍵。
二、Spark的編程模型
應用spark時,開發人員編寫一個驅動程序來完成高級應用程序的控制流和發佈各種並行操作,實際上就是對分佈式數據集進行操作。對於RDD的操作,spark提供了兩個抽象概念:轉換(transformation)和動作(action),另外spark有兩種共享變量:廣播變量和累加器。2.1 轉換transformation:
對輸入的數據集進行的格式上的轉換,如表爲一系列transformation操作:
轉換 |
含義 |
map(func) |
返回一個新分佈式數據集,由每一個輸入元素經過func函數轉換後組成 |
filter(func) |
返回一個新數據集,由經過func函數計算後返回值爲true的輸入元素組成 |
flatMap(func) |
類似於map,但是每一個輸入元素可以被映射爲0或多個輸出元素(因此func應該返回一個序列,而不是單一元素) |
mapPartitions(func) |
類似於map,但獨立地在RDD的每一個分塊上運行,因此在類型爲T的RDD上運行時,func的函數類型必須是Iterator[T] => Iterator[U] |
mapPartitionsWithSplit(func) |
類似於mapPartitions, 但func帶有一個整數參數表示分塊的索引值。因此在類型爲T的RDD上運行時,func的函數類型必須是(Int, Iterator[T]) => Iterator[U] |
sample(withReplacement,fraction, seed) |
根據fraction指定的比例,對數據進行採樣,可以選擇是否用隨機數進行替換,seed用於指定隨機數生成器種子 |
union(otherDataset) |
返回一個新的數據集,新數據集是由源數據集和參數數據集聯合而成 |
distinct([numTasks])) |
返回一個包含源數據集中所有不重複元素的新數據集 |
groupByKey([numTasks]) |
在一個(K,V)對的數據集上調用,返回一個(K,Seq[V])對的數據集 |
reduceByKey(func, [numTasks]) |
在一個(K,V)對的數據集上調用時,返回一個(K,V)對的數據集,使用指定的reduce函數,將相同key的值聚合到一起。類似groupByKey,reduce任務個數是可以通過第二個可選參數來配置的 |
sortByKey([ascending], [numTasks]) |
在一個(K,V)對的數據集上調用,K必須實現Ordered接口,返回一個按照Key進行排序的(K,V)對數據集。升序或降序由ascending布爾參數決定 |
join(otherDataset, [numTasks]) |
在類型爲(K,V)和(K,W)類型的數據集上調用時,返回一個相同key對應的所有元素對在一起的(K, (V, W))數據集 |
cogroup(otherDataset, [numTasks]) |
在類型爲(K,V)和(K,W)的數據集上調用,返回一個 (K, Seq[V], Seq[W])元組的數據集。這個操作也可以稱之爲groupwith |
cartesian(otherDataset) |
笛卡爾積,在類型爲 T 和 U 類型的數據集上調用時,返回一個 (T, U)對數據集(兩兩的元素對) |
2.2動作action:
在轉換完的數據集上進行的各種操作,返回結果給驅動程序,其操作有:reduce()、collect()、foreach()等。
如表爲一系列action操作:
動作 |
含義 |
reduce(func) |
通過函數func(接受兩個參數,返回一個參數)聚集數據集中的所有元素。這個功能必須可交換且可關聯的,從而可以正確的被並行執行。 |
collect() |
在驅動程序中,以數組的形式,返回數據集的所有元素。這通常會在使用filter或者其它操作並返回一個足夠小的數據子集後再使用會比較有用。 |
count() |
返回數據集的元素的個數。 |
first() |
返回數據集的第一個元素(類似於take(1)) |
take(n) |
返回一個由數據集的前n個元素組成的數組。注意,這個操作目前並非並行執行,而是由驅動程序計算所有的元素 |
takeSample(withReplacement,num, seed) |
返回一個數組,在數據集中隨機採樣num個元素組成,可以選擇是否用隨機數替換不足的部分,Seed用於指定的隨機數生成器種子 |
saveAsTextFile(path) |
將數據集的元素,以textfile的形式,保存到本地文件系統,HDFS或者任何其它hadoop支持的文件系統。對於每個元素,Spark將會調用toString方法,將它轉換爲文件中的文本行 |
saveAsSequenceFile(path) |
將數據集的元素,以Hadoop sequencefile的格式,保存到指定的目錄下,本地系統,HDFS或者任何其它hadoop支持的文件系統。這個只限於由key-value對組成,並實現了Hadoop的Writable接口,或者隱式的可以轉換爲Writable的RDD。(Spark包括了基本類型的轉換,例如Int,Double,String,等等) |
countByKey() |
對(K,V)類型的RDD有效,返回一個(K,Int)對的Map,表示每一個key對應的元素個數 |
foreach(func) |
在數據集的每一個元素上,運行函數func進行更新。這通常用於邊緣效果,例如更新一個累加器,或者和外部存儲系統進行交互,例如HBase |
2.3 Spark創建兩種共享變量
該變量在不同節點任務上被共享使用:
1、廣播變量:允許程序員保留一個只讀的變量,緩存在每一臺機器上,而非每個任務保存一份拷貝。Spark會嘗試使用一種高效的廣播算法來傳播廣播變量,從而減少通信的代價。
2、累加器:是一種只能通過關聯操作進行“加”操作的變量,因此可以高效被並行支持。它們可以用來實現計數器和求和器。
三、spark分佈式計算模型
Spark通過Mesos進行分佈式計算,spark將RDDs和並行操作函數進行一次轉換,變成標準的job和一系列task,提交給SparkScheduler,SparkScheduler將task提交給MesoMaster,由master分配給不同的workers.最終由worker中的SparkExecutor將分配到的任務一一執行,並返回結果或直接寫入到分佈式文件系統。四、例子
1、text search:從存儲在hdfs中的日誌文件中找出裏面的錯誤信息
val file = spark.textFile("hdfs://...")//創建一個分佈式數據集
val errs = file.filter(_.contains("ERROR"))//RDDs轉換的過程,找出日誌中包含錯誤的行
Val cacheErrs=errs.cache();//RDD持久化
val ones = errs.map(_ => 1)//轉換的過程,將這些行映射成1
val count = ones.reduce(_+_)//動作的過程,將這些行累加起來,worker節點掃描這些1,並將結果發給驅動程序。
2、Logistic Regression(邏輯迴歸):通過迭代一個分類算法來尋找一個超平面w將兩類點分開
val points = spark.textFile(...).map(parsePoint).cache()// 從文件中讀取數據,並緩存在內存中,有利於提高運行效率
var w = Vector.random(D)//給w一個隨機值
for (i <- 1 to ITERATIONS) {//迭代,尋找最優w
val grad = spark.accumulator(new Vector(D))//使用累加器,對各節點上的梯度累加
for (p <- points) { //牛頓迭代法進行迭代計算
val s = (1/(1+exp(-p.y*(w dot p.x)))-1)*p.y
grad += s * p.x}
w -= grad.value}
完整代碼:見後面 六、代碼
3、Spark實現完整的WordCount程序
import spark.SparkContextimport SparkContext._//導入spark開發包
object SparkTest { def main( args: Array[String]) {
if (args.length == 0) {
System.err.println("Usage: SparkTest <host> [<slices>]")
System.exit(1)
}
val spark = new SparkContext(args(0), "SparkTest")//創建SparkContext對象,告訴spark如何訪問spark集羣
val slices = if (args.length > 1) args(1).toInt else 2//定義數據集分爲幾塊
val myFile = spark.textFile("test.txt")//創建一個分佈式數據集
val counts = myFile.flatMap(line => line.split(" ")//文件中每一行單詞以空格分隔,轉換的過程
.map(word => (word, 1))//數據集轉換成(word,1)鍵值對,也是數據集轉換的過程
.reduceByKey(_ + _)//統計單詞個數,動作的過程
counts.saveAsTextFile("out.txt")//輸出文件
}
}
SparkTest.main(args)//設置main函數參數
五、spark未來工作
1、標準化RDDs的屬性和特徵,及Spark的其他抽象概念,使其在其他應用和工作中有更好的適應性;2、減少程序員在存儲和重構RDDs方面的花銷;
3、定義新的關於RDDs轉換方面的操作,如shuffle操作,可以根據給定的鍵值重新分配RDD;
4、對於spark解釋器提供更高水平的交互接口,如跟SQL的交互接口。
六、代碼
1、用Scala語言實現Logistic Regression Classifier的完整代碼:
import java.util.Random
import java.lang.Math
import scala.collection.mutable.HashMap import scala.io.Source
import org.apache.spark.SparkContext //創建SparkContext對象,告訴spark如何訪問spark集羣
import org.apache.spark.rdd.RDD; //RDDs
import org.apache.spark.util.Vector //spark容器
import org.apache.spark.broadcast.Broadcast //廣播變量
import spark.ml.utils.SparserVector
object SparseLR { //定義一個類:LR分類器
val labelNum = 2; // 類別數
val dimNum = 124; // 維度
val iteration = 10; // 迭代次數
val alpha = 0.1 // 迭代步長
val lambda = 0.1
val rand = new Random(42) //取一隨機數
var w = Vector(dimNum, _ => rand.nextDouble) //用隨機數初始化參數 ,w爲一124維的容器變量
//定義一個數據點
case class DataPoint(x: SparserVector, y: Int) //整形y,SparserVector類型的x(124維)
// 解析一個讀入的訓練樣本,構造DataPoint結構
def parsePoint(line: String): DataPoint = { //line爲傳入的訓練樣本數據
var features = new SparserVector(dimNum)
val fields = line.split(" ") //transformation過程,將原RDDs數據集轉換爲以空格分割後的數據集
val y = fields(0).toInt
fields.filter(_.contains(":")).foreach(f => {
val feature = f.split(":")
features.insert(feature(0).toInt, feature(1).toDouble)
})
DataPoint(features, y)
}
//讀樣本文件,構造訓練數據
def genearteDataPoints(filename: String): Array[DataPoint] = {
val dataPoints = Source.fromFile(filename).getLines.map(parsePoint).toArray
dataPoints
}
//定義sigmod函數
def sigmod(x: SparserVector): Double = {
val features = x.elements
val s = features.keySet.map(k => w(k) * features.get(k).get).reduce(_ + _)//權值和訓練數據乘積之和
1 / (1 + Math.exp(-s)) //邏輯函數
}
// train函數,根據樣本訓練參數
def train(sc: SparkContext, dataPoints: RDD[DataPoint]) {
val sampleNum = dataPoints.count //開始迭代
for (i <- 0 until iteration) {
val g = dataPoints.map(p => p.x * (sigmod(p.x) - p.y)).reduce(_ + _) + lambda * w
w -= alpha * g //牛頓-拉斐森(Newton-Raphson)方法進行迭代求解。
println("iteration " + i + ": g = " + g)
println("iteration " + i + ": w = " + w)
}
}
//根據訓練出的參數進行預測
def predict(dataPoints: RDD[DataPoint]): Double = {
val correct = dataPoints.map(p => {
val label = if (sigmod(p.x) > 0.5) 1 else 0
if (label == p.y) 1 else 0
}).reduce(_ + _)
(correct * 1.0) / dataPoints.count
}
//main函數
def main(args: Array[String]): Unit = {
val trainfile = "data/a8a.train";
//val sc = new SparkContext(args(0), "LR")
val sc = new SparkContext("local", "LR")
val trainset = sc.textFile(trainfile, 2).map(parsePoint).cache
train(sc, trainset) //訓練樣本,得到最優參數
val testfile = "data/a8a.test";
val testset = sc.textFile(testfile, 2).map(parsePoint).cache
val accuracy = predict(testset) //測試數據,得到分類結果
println(accuracy)
}
}
2、Spark的LR分類器
package spark.examples
Import scala.io.Source
import java.util.Random
import scala.math.exp
import spark.util.Vector
import spark._
*******************************************************************
object SparkLR {
val N = 10000 // Number of data points
val D = 10 // Numer of dimensions
val R = 0.7 // Scaling factor
val ITERATIONS = 5
val rand = new Random(42)
case class DataPoint(x: Vector, y: Double)
case class DataPoint1(x: Vector)
********************************************************************
def generateData = { //自己構建了訓練數據
def generatePoint(i: Int) = {
val y = if(i % 2 == 0) -1 else 1
val x = Vector(D, _ => rand.nextGaussian + y * R)
DataPoint(x, y)
}
Array.tabulate(N)(generatePoint)
}
*************************************************************************
//fang該部分是對上面的數據結構改進,方便從文件中讀入數據
def generateData = { //自己構建了訓練數據
Val src=Source.fromFile(“/home/jay/file01”)
Val iter=src.getLines()
def generatePoint(i: Int) = {
val y = if(i % 2 == 0) -1 else 1
val x = Vector(D, iter.next().split(“ ”).map(w=>w.toDouble))
DataPoint(x, y)
}
Array.tabulate(N)(generatePoint)
}
def generateData = { //自己構建測試數據
Val src1=Source.fromFile(“/home/jay/file02”)
Val iter1=src1.getLines()
def generatePoint1(i: Int) = {
val x = Vector(D, iter1.next().split(“ ”).map(w=>w.toDouble))
DataPoint1(x)
}
Array.tabulate(N)(generatePoint1)
}
//fang
*******************************************************************************
def main(args: Array[String]) {
Val conf = new SparkConf().setAppName(“SparkLR”)
val sc = new SparkContext(conf)
val numSlices = if (args.length > 1) args(1).toInt else 2
val points = sc.parallelize(generateData, numSlices).cache()
val points1 = sc.parallelize(generateData1, numSlices).cache()
// Initialize w to a random value
var w = Vector(D, _ => 2 * rand.nextDouble - 1)
println("Initial w: " + w)
for (i <- 1 to ITERATIONS) {//運用牛頓迭代法實現LR分類器
println("On iteration " + i)
val gradient = points.map { p =>
(1 / (1 + exp(-p.y * (w dot p.x))) - 1) * p.y * p.x
}.reduce(_ + _)
w -= gradient
}
println("Final w: " + w)
Val y=points1.map{p=>(1/(1+exp(w dot p.x)))}
var Y = Vector(N, y.take(N)
Println(“result: ”, + Y)
System.exit(0)
}
}
訓練數據模型:
xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx yyy
詳細邏輯迴歸分類器參考博客:http://blog.csdn.net/qustqustjay/article/details/46874527