Skip to content

Commit b657691

Browse files
committed
adding breeze vector support
1 parent cfc099a commit b657691

File tree

7 files changed

+250
-388
lines changed

7 files changed

+250
-388
lines changed

build.sbt

+19-12
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,32 @@ scalaVersion := "2.10.4"
1212

1313
parallelExecution in Test := false
1414

15-
libraryDependencies ++= Seq(
16-
"org.slf4j" % "slf4j-api" % "1.7.2",
17-
"org.slf4j" % "slf4j-log4j12" % "1.7.2",
18-
"org.scalatest" %% "scalatest" % "1.9.1" % "test",
19-
"org.apache.spark" % "spark-core_2.10" % "1.1.1",
20-
"org.apache.commons" % "commons-compress" % "1.7",
21-
"commons-io" % "commons-io" % "2.4",
22-
"org.jblas" % "jblas" % "1.2.3"
23-
)
15+
{
16+
val excludeHadoop = ExclusionRule(organization = "org.apache.hadoop")
17+
libraryDependencies ++= Seq(
18+
"org.slf4j" % "slf4j-api" % "1.7.2",
19+
"org.slf4j" % "slf4j-log4j12" % "1.7.2",
20+
"org.scalatest" %% "scalatest" % "1.9.1" % "test",
21+
"org.apache.spark" % "spark-core_2.10" % "1.3.1" excludeAll(excludeHadoop),
22+
"org.apache.spark" % "spark-mllib_2.10" % "1.3.1" excludeAll(excludeHadoop),
23+
"org.apache.spark" % "spark-sql_2.10" % "1.3.1" excludeAll(excludeHadoop),
24+
"org.apache.commons" % "commons-compress" % "1.7",
25+
"commons-io" % "commons-io" % "2.4",
26+
"org.scalanlp" % "breeze_2.10" % "0.11.2",
27+
"com.github.fommil.netlib" % "all" % "1.1.2" pomOnly(),
28+
"com.github.scopt" %% "scopt" % "3.3.0"
29+
)
30+
}
2431

2532
{
2633
val defaultHadoopVersion = "1.0.4"
2734
val hadoopVersion =
28-
scala.util.Properties.envOrElse("SPARK_HADOOP_VERSION",
29-
defaultHadoopVersion)
35+
scala.util.Properties.envOrElse("SPARK_HADOOP_VERSION", defaultHadoopVersion)
3036
libraryDependencies += "org.apache.hadoop" % "hadoop-client" % hadoopVersion
3137
}
3238

3339
resolvers ++= Seq(
40+
"Local Maven Repository" at Path.userHome.asFile.toURI.toURL + ".m2/repository",
3441
"Typesafe" at "http://repo.typesafe.com/typesafe/releases",
3542
"Spray" at "http://repo.spray.cc"
3643
)
@@ -48,4 +55,4 @@ mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>
4855
}
4956
}
5057

51-
test in assembly := {}
58+
test in assembly := {}

src/main/scala/driver.scala

+22-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package distopt
33
import org.apache.spark.{SparkContext, SparkConf}
44
import distopt.utils._
55
import distopt.solvers._
6+
import breeze.linalg.DenseVector
7+
68

79
object driver {
810

@@ -25,12 +27,13 @@ object driver {
2527
var chkptIter = options.getOrElse("chkptIter","100").toInt
2628
val testFile = options.getOrElse("testFile", "")
2729
val justCoCoA = options.getOrElse("justCoCoA", "true").toBoolean // set to false to compare different methods
28-
30+
2931
// algorithm-specific inputs
3032
val lambda = options.getOrElse("lambda", "0.01").toDouble // regularization parameter
3133
val numRounds = options.getOrElse("numRounds", "200").toInt // number of outer iterations, called T in the paper
3234
val localIterFrac = options.getOrElse("localIterFrac","1.0").toDouble; // fraction of local points to be processed per round, H = localIterFrac * n
33-
val beta = options.getOrElse("beta","1.0").toDouble; // scaling parameter when combining the updates of the workers (1=averaging)
35+
val beta = options.getOrElse("beta","1.0").toDouble; // scaling parameter when combining the updates of the workers (1=averaging for CoCoA)
36+
val gamma = options.getOrElse("gamma","1.0").toDouble; // aggregation parameter for CoCoA+ (1=adding, 1/K=averaging)
3437
val debugIter = options.getOrElse("debugIter","10").toInt // set to -1 to turn off debugging output
3538
val seed = options.getOrElse("seed","0").toInt // set seed for debug purposes
3639

@@ -41,7 +44,8 @@ object driver {
4144
println("testfile: " + testFile); println("justCoCoA " + justCoCoA);
4245
println("lambda: " + lambda); println("numRounds: " + numRounds);
4346
println("localIterFrac:" + localIterFrac); println("beta " + beta);
44-
println("debugIter " + debugIter); println("seed " + seed);
47+
println("gamma " + beta); println("debugIter " + debugIter);
48+
println("seed " + seed);
4549

4650
// start spark context
4751
val conf = new SparkConf().setMaster(master)
@@ -68,29 +72,36 @@ object driver {
6872

6973
// for the primal-dual algorithms to run correctly, the initial primal vector has to be zero
7074
// (corresponding to dual alphas being zero)
71-
val wInit = Array.fill(numFeatures)(0.0)
75+
val wInit = DenseVector.zeros[Double](numFeatures)
76+
77+
// set to solve hingeloss SVM
78+
val loss = OptUtils.hingeLoss _
79+
val params = Params(loss, n, wInit, numRounds, localIters, lambda, beta, gamma)
80+
val debug = DebugParams(testData, debugIter, seed, chkptIter)
81+
7282

7383
// run CoCoA+
74-
val (finalwCoCoAPlus, finalalphaCoCoAPlus) = CoCoA.runCoCoA(sc, data, n, wInit, numRounds, localIters, lambda, beta, chkptIter, testData, debugIter, seed, true)
84+
val (finalwCoCoAPlus, finalalphaCoCoAPlus) = CoCoA.runCoCoA(data, params, debug, plus=true)
7585
OptUtils.printSummaryStatsPrimalDual("CoCoA+", data, finalwCoCoAPlus, finalalphaCoCoAPlus, lambda, testData)
7686

7787
// run CoCoA
78-
val (finalwCoCoA, finalalphaCoCoA) = CoCoA.runCoCoA(sc, data, n, wInit, numRounds, localIters, lambda, beta, chkptIter, testData, debugIter, seed, false)
88+
val (finalwCoCoA, finalalphaCoCoA) = CoCoA.runCoCoA(data, params, debug, plus=false)
7989
OptUtils.printSummaryStatsPrimalDual("CoCoA", data, finalwCoCoA, finalalphaCoCoA, lambda, testData)
8090

91+
8192
// optionally run other methods for comparison
8293
if(!justCoCoA) {
8394

8495
// run Mini-batch CD
85-
val (finalwMbCD, finalalphaMbCD) = MinibatchCD.runMbCD(sc, data, n, wInit, numRounds, localIters, lambda, beta, chkptIter, testData, debugIter, seed)
86-
OptUtils.printSummaryStatsPrimalDual("Mini-batch CD", data, finalwMbCD, finalalphaMbCD, lambda, testData)
96+
val (finalwMbCD, finalalphaMbCD) = MinibatchCD.runMbCD(data, params, debug)
97+
OptUtils.printSummaryStatsPrimalDual("Mini-batch CD", data, finalwMbCD, finalalphaMbCD, lambda, testData)
8798

8899
// run Mini-batch SGD
89-
val finalwMbSGD = SGD.runSGD(sc, data, n, wInit, numRounds, localIters, lambda, local=false, beta, chkptIter, testData, debugIter, seed)
100+
val finalwMbSGD = SGD.runSGD(data, params, debug, local=false)
90101
OptUtils.printSummaryStats("Mini-batch SGD", data, finalwMbSGD, lambda, testData)
91102

92-
// run Local SGD
93-
val finalwLocalSGD = SGD.runSGD(sc, data, n, wInit, numRounds, localIters, lambda, local=true, beta, chkptIter, testData, debugIter, seed)
103+
// run Local-SGD
104+
val finalwLocalSGD = SGD.runSGD(data, params, debug, local=true)
94105
OptUtils.printSummaryStats("Local SGD", data, finalwLocalSGD, lambda, testData)
95106

96107
}

src/main/scala/solvers/CoCoA.scala

+58-72
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,61 @@ package distopt.solvers
22

33
import org.apache.spark.SparkContext
44
import org.apache.spark.rdd.RDD
5-
import distopt.utils.Implicits._
65
import distopt.utils._
6+
import breeze.linalg.{Vector, NumericOps, DenseVector, SparseVector}
7+
78

89
object CoCoA {
910

1011
/**
11-
* CoCoA - Communication-efficient distributed dual Coordinate Ascent.
12+
* CoCoA/CoCoA+ - Communication-efficient distributed dual Coordinate Ascent.
1213
* Using LocalSDCA as the local dual method. Here implemented for standard
1314
* hinge-loss SVM. For other objectives, adjust localSDCA accordingly.
1415
*
15-
* @param sc
1616
* @param data RDD of all data examples
17-
* @param wInit initial weight vector (has to be zero)
18-
* @param numRounds number of outer iterations T in the paper
19-
* @param localIters number of inner localSDCA iterations, H in the paper
20-
* @param lambda the regularization parameter
21-
* @param beta scaling parameter. beta=1 gives averaging, beta=K=data.partitions.size gives (aggressive) adding
22-
* @param chkptIter checkpointing the resulting RDDs from time to time, to ensure persistence and shorter dependencies
23-
* @param testData
24-
* @param debugIter
25-
* @param seed
17+
* @param params Algorithmic parameters
18+
* @param debug Systems/debugging parameters
19+
* @param plus Whether to use the CoCoA+ framework (plus=true) or CoCoA (plus=false)
2620
* @return
2721
*/
2822
def runCoCoA(
29-
sc: SparkContext,
30-
data: RDD[SparseClassificationPoint],
31-
n: Int,
32-
wInit: Array[Double],
33-
numRounds: Int,
34-
localIters: Int,
35-
lambda: Double,
36-
beta: Double,
37-
chkptIter: Int,
38-
testData: RDD[SparseClassificationPoint],
39-
debugIter: Int,
40-
seed: Int,
41-
plus: Boolean) : (Array[Double], RDD[Array[Double]]) = {
23+
data: RDD[LabeledPoint],
24+
params: Params,
25+
debug: DebugParams,
26+
plus: Boolean) : (Vector[Double], RDD[Vector[Double]]) = {
4227

4328
val parts = data.partitions.size // number of partitions of the data, K in the paper
4429
val alg = if (plus) "CoCoA+" else "CoCoA"
45-
println("\nRunning "+alg+" on "+n+" data examples, distributed over "+parts+" workers")
30+
println("\nRunning "+alg+" on "+params.n+" data examples, distributed over "+parts+" workers")
4631

4732
// initialize alpha, w
4833
var alphaVars = data.map(x => 0.0).cache()
49-
var alpha = alphaVars.mapPartitions(x => Iterator(x.toArray))
34+
var alpha = alphaVars.mapPartitions(x => Iterator(Vector(x.toArray)))
5035
var dataArr = data.mapPartitions(x => Iterator(x.toArray))
51-
var w = wInit
52-
var scaling = if (plus) beta else 1.0/parts
36+
var w = params.wInit.copy
37+
var scaling = if (plus) params.gamma else params.beta/parts
5338

54-
for(t <- 1 to numRounds){
39+
for(t <- 1 to params.numRounds) {
5540

5641
// zip alpha with data
5742
val zipData = alpha.zip(dataArr)
5843

5944
// find updates to alpha, w
60-
val updates = zipData.mapPartitions(partitionUpdate(_,w,localIters,lambda,n,scaling,seed+t,plus,parts*beta),preservesPartitioning=true).persist()
45+
val updates = zipData.mapPartitions(partitionUpdate(_, w, params.localIters, params.lambda, params.n, scaling, debug.seed + t, plus, parts * params.gamma), preservesPartitioning = true).persist()
6146
alpha = updates.map(kv => kv._2)
62-
val primalUpdates = updates.map(kv => kv._1).reduce(_ plus _)
63-
if (plus) {
64-
w = primalUpdates.plus(w)
65-
} else {
66-
w = primalUpdates.times(scaling).plus(w)
67-
}
47+
val primalUpdates = updates.map(kv => kv._1).reduce(_ + _)
48+
w += (primalUpdates * scaling)
6849

6950
// optionally calculate errors
70-
if (debugIter>0 && t % debugIter == 0) {
51+
if (debug.debugIter > 0 && t % debug.debugIter == 0) {
7152
println("Iteration: " + t)
72-
println("primal objective: " + OptUtils.computePrimalObjective(data, w, lambda))
73-
println("primal-dual gap: " + OptUtils.computeDualityGap(data, w, alpha, lambda))
74-
if (testData != null) { println("test error: " + OptUtils.computeClassificationError(testData, w)) }
53+
println("primal objective: " + OptUtils.computePrimalObjective(data, w, params.lambda))
54+
println("primal-dual gap: " + OptUtils.computeDualityGap(data, w, alpha, params.lambda))
55+
if (debug.testData != null) { println("test error: " + OptUtils.computeClassificationError(debug.testData, w)) }
7556
}
7657

7758
// optionally checkpoint RDDs
78-
if(t % chkptIter == 0){
59+
if(t % debug.chkptIter == 0){
7960
zipData.checkpoint()
8061
alpha.checkpoint()
8162
}
@@ -84,6 +65,7 @@ object CoCoA {
8465
return (w, alpha)
8566
}
8667

68+
8769
/**
8870
* Performs one round of local updates using a given local dual algorithm,
8971
* here locaSDCA. Will perform localIters many updates per worker.
@@ -93,35 +75,35 @@ object CoCoA {
9375
* @param localIters
9476
* @param lambda
9577
* @param n
96-
* @param scaling this is the scaling factor beta/K in the paper
78+
* @param scaling This is either gamma for CoCoA+ or beta/K for CoCoA
9779
* @param seed
80+
* @param plus
81+
* @param sigma sigma' in the CoCoA+ paper
9882
* @return
9983
*/
10084
private def partitionUpdate(
101-
zipData: Iterator[(Array[Double],Array[SparseClassificationPoint])],//((Int, Double), SparseClassificationPoint)],
102-
wInit: Array[Double],
85+
zipData: Iterator[(Vector[Double],Array[LabeledPoint])],//((Int, Double), SparseClassificationPoint)],
86+
wInit: Vector[Double],
10387
localIters: Int,
10488
lambda: Double,
10589
n: Int,
10690
scaling: Double,
10791
seed: Int,
10892
plus: Boolean,
109-
sigma: Double): Iterator[(Array[Double], Array[Double])] = {
93+
sigma: Double): Iterator[(Vector[Double], Vector[Double])] = {
11094

11195
val zipPair = zipData.next()
11296
val localData = zipPair._2
11397
var alpha = zipPair._1
114-
val alphaOld = alpha.clone
98+
val alphaOld = alpha.copy
99+
115100
val (deltaAlpha, deltaW) = localSDCA(localData, wInit, localIters, lambda, n, alpha, alphaOld, seed, plus, sigma)
116-
117-
if (plus) {
118-
alpha = alphaOld.plus(deltaAlpha)
119-
} else {
120-
alpha = alphaOld.plus(deltaAlpha.times(scaling))
121-
}
101+
alpha = alphaOld + (deltaAlpha * scaling)
102+
122103
return Iterator((deltaW, alpha))
123104
}
124105

106+
125107
/**
126108
* This is an implementation of LocalDualMethod, here LocalSDCA (coordinate ascent),
127109
* with taking the information of the other workers into account, by respecting the
@@ -132,32 +114,35 @@ object CoCoA {
132114
* regularization parameter C = 1.0/(lambda*numExamples), and re-scaling
133115
* the alpha variables with 1/C.
134116
*
135-
* @param localData the local data examples
117+
* @param localData The local data examples
136118
* @param wInit
137-
* @param localIters number of local coordinates to update
119+
* @param localIters Number of local coordinates to update
138120
* @param lambda
139-
* @param n global number of points (needed for the primal-dual correspondence)
121+
* @param n Global number of points (needed for the primal-dual correspondence)
140122
* @param alpha
141123
* @param alphaOld
142124
* @param seed
143-
* @return deltaAlpha and deltaW, summarizing the performed local changes, see paper
125+
* @param plus
126+
* @param sigma
127+
* @param plus
128+
* @return (deltaAlpha, deltaW) Summarizing the performed local changes
144129
*/
145130
def localSDCA(
146-
localData: Array[SparseClassificationPoint],
147-
wInit: Array[Double],
131+
localData: Array[LabeledPoint],
132+
wInit: Vector[Double],
148133
localIters: Int,
149134
lambda: Double,
150135
n: Int,
151-
alpha: Array[Double],
152-
alphaOld: Array[Double],
136+
alpha: Vector[Double],
137+
alphaOld: Vector[Double],
153138
seed: Int,
154139
plus: Boolean,
155-
sigma: Double): (Array[Double], Array[Double]) = {
140+
sigma: Double): (Vector[Double], Vector[Double]) = {
156141

157142
var w = wInit
158143
val nLocal = localData.length
159144
var r = new scala.util.Random(seed)
160-
var deltaW = Array.fill(wInit.length)(0.0)
145+
var deltaW = DenseVector.zeros[Double](wInit.length)
161146

162147
// perform local udpates
163148
for (i <- 1 to localIters) {
@@ -171,37 +156,38 @@ object CoCoA {
171156
// compute hinge loss gradient
172157
val grad = {
173158
if (plus) {
174-
(y*(x.dot(w)+sigma*x.dot(deltaW)) - 1.0)*(lambda*n)
159+
(y * (x.dot(w) + (sigma * x.dot(deltaW))) - 1.0) * (lambda * n)
175160
} else {
176-
(y*(x.dot(w)) - 1.0)*(lambda*n)
161+
(y * (x.dot(w)) - 1.0) * (lambda * n)
177162
}
178163
}
179164

180165
// compute projected gradient
181166
var proj_grad = grad
182167
if (alpha(idx) <= 0.0)
183-
proj_grad = Math.min(grad,0)
168+
proj_grad = Math.min(grad, 0)
184169
else if (alpha(idx) >= 1.0)
185-
proj_grad = Math.max(grad,0)
170+
proj_grad = Math.max(grad, 0)
186171

187172
if (Math.abs(proj_grad) != 0.0 ) {
188-
val qii = if (plus) x.dot(x)*sigma else x.dot(x)
173+
val xnorm = Math.pow(x.norm(2), 2)
174+
val qii = if (plus) xnorm * sigma else xnorm
189175
var newAlpha = 1.0
190176
if (qii != 0.0) {
191177
newAlpha = Math.min(Math.max((alpha(idx) - (grad / qii)), 0.0), 1.0)
192178
}
193179

194180
// update primal and dual variables
195-
val update = x.times( y*(newAlpha-alpha(idx))/(lambda*n) )
181+
val update = x * (y * (newAlpha - alpha(idx)) / (lambda * n))
196182
if (!plus) {
197-
w = update.plus(w)
183+
w = w + update
198184
}
199-
deltaW = update.plus(deltaW)
185+
deltaW += update
200186
alpha(idx) = newAlpha
201187
}
202188
}
203189

204-
val deltaAlpha = (alphaOld.times(-1.0)).plus(alpha)
190+
val deltaAlpha = alpha - alphaOld
205191
return (deltaAlpha, deltaW)
206192
}
207193

0 commit comments

Comments
 (0)