Skip to content

Commit 52497e1

Browse files
committed
Adding dropout implementation from ann-dropout branch
1 parent cb03fe0 commit 52497e1

2 files changed

Lines changed: 187 additions & 5 deletions

File tree

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.mllib.ann
19+
20+
import scala.collection.BitSet
21+
22+
import breeze.linalg.{DenseMatrix => BDM}
23+
24+
import org.apache.spark.mllib.linalg.{Vectors, Vector}
25+
import org.apache.spark.util.random.XORShiftRandom
26+
27+
class DropoutTopology(val layers: Array[Layer], val inputDropoutProb: Double,
28+
val layerDropoutProb: Double) extends Topology {
29+
override def getInstance(weights: Vector): TopologyModel =
30+
DropoutModel(this, weights)
31+
32+
override def getInstance(seed: Long): TopologyModel = DropoutModel(this, seed)
33+
}
34+
35+
/* Model of Feed Forward Neural Network with drop-out.
36+
* Implements forward, gradient computation and can return weights in vector format.
37+
* */
38+
class DropoutModel(val layerModels: Array[LayerModel],
39+
val topology: DropoutTopology) extends TopologyModel {
40+
private val rand = new XORShiftRandom(System.nanoTime())
41+
42+
override def forward(data: BDM[Double]): Array[BDM[Double]] = {
43+
val outputs = new Array[BDM[Double]](layerModels.length)
44+
val lastIndex = layerModels.lastIndexWhere(lm => lm.size > 0)
45+
for(i <- 0 until layerModels.length){
46+
outputs(i) = layerModels(i).eval(if (i==0) data else outputs(i - 1))
47+
// use probabilities only on layers with weights except the last one
48+
if (topology.layerDropoutProb > 0 && layerModels(i).size > 0 && i < lastIndex) {
49+
outputs(i) :*= 1.0 - topology.layerDropoutProb
50+
}
51+
}
52+
outputs
53+
}
54+
55+
override def computeGradient(data: BDM[Double], target: BDM[Double], cumGradient: Vector,
56+
realBatchSize: Int): Double = {
57+
// preparing masks
58+
var inputMask: BitSet = null
59+
val layerMasks = new Array[BitSet](layerModels.length)
60+
if (topology.inputDropoutProb > 0) {
61+
inputMask = makeMask(data, topology.inputDropoutProb)
62+
applyMask(data, inputMask)
63+
}
64+
// forward with masks
65+
val lastIndex = layerModels.lastIndexWhere(lm => lm.size > 0)
66+
val outputs = new Array[BDM[Double]](layerModels.length)
67+
for (i <- 0 until layerModels.length){
68+
outputs(i) = layerModels(i).eval(if (i==0) data else outputs(i - 1))
69+
if (i < lastIndex && topology.layerDropoutProb > 0) {
70+
layerMasks(i) = if (layerModels(i).size > 0) {
71+
makeMask(outputs(i), topology.layerDropoutProb)
72+
} else {
73+
if (i==0) inputMask else layerMasks(i - 1)
74+
}
75+
applyMask(outputs(i), layerMasks(i))
76+
}
77+
}
78+
// error depending on output layer
79+
val (newE, newError) = layerModels.last match {
80+
case flm: FunctionalLayerModel => flm.error(outputs.last, target)
81+
case _ =>
82+
throw new UnsupportedOperationException("Non-functional layer not supported at the top")
83+
}
84+
// compute delta with masks
85+
val L = layerModels.length - 1
86+
val deltas = new Array[BDM[Double]](layerModels.length)
87+
deltas(L) = new BDM[Double](0, 0)
88+
deltas(L - 1) = newE
89+
for (i <- (L - 2) to (0, -1)) {
90+
deltas(i) = layerModels(i + 1).prevDelta(deltas(i + 1), outputs(i + 1))
91+
applyMask(deltas(i), layerMasks(i))
92+
}
93+
// compute gradient
94+
val grads = new Array[Array[Double]](layerModels.length)
95+
for (i <- 0 until layerModels.length) {
96+
val input = if (i==0) data else outputs(i - 1)
97+
grads(i) = layerModels(i).grad(deltas(i), input)
98+
}
99+
// update cumGradient
100+
val cumGradientArray = cumGradient.toArray
101+
var offset = 0
102+
// TODO: extract roll
103+
for (i <- 0 until grads.length) {
104+
val gradArray = grads(i)
105+
var k = 0
106+
while (k < gradArray.length) {
107+
cumGradientArray(offset + k) += gradArray(k)
108+
k += 1
109+
}
110+
offset += gradArray.length
111+
}
112+
newError
113+
}
114+
115+
private def makeMask(data: BDM[Double], dropoutProb: Double): BitSet = {
116+
val mask = scala.collection.mutable.BitSet(data.size)
117+
mask(data.size) = false
118+
var k = 0
119+
while (k < data.size) {
120+
if (rand.nextDouble() > dropoutProb) {
121+
mask(k) = true
122+
}
123+
k += 1
124+
}
125+
mask
126+
}
127+
128+
private def applyMask(data: BDM[Double], mask: BitSet): Unit = {
129+
var k = 0
130+
while (k < data.size && mask != null) {
131+
if (mask(k) == false) {
132+
data.data(k) = 0.0
133+
}
134+
k += 1
135+
}
136+
}
137+
138+
override def weights(): Vector = {
139+
// TODO: extract roll
140+
var size = 0
141+
for(i <- 0 until layerModels.length) {
142+
size += layerModels(i).size
143+
}
144+
val array = new Array[Double](size)
145+
var offset = 0
146+
for(i <- 0 until layerModels.length) {
147+
val layerWeights = layerModels(i).weights().toArray
148+
System.arraycopy(layerWeights, 0, array, offset, layerWeights.length)
149+
offset += layerWeights.length
150+
}
151+
Vectors.dense(array)
152+
}
153+
154+
override def predict(data: Vector): Vector = {
155+
val result = forward(data.toBreeze.toDenseVector.toDenseMatrix.t)
156+
Vectors.dense(result.last.toArray)
157+
}
158+
159+
}
160+
161+
// TODO: make a fabric of models (unite with object FeedForwardModel)
162+
object DropoutModel {
163+
def apply(topology: DropoutTopology, weights: Vector): DropoutModel = {
164+
val layers = topology.layers
165+
val layerModels = new Array[LayerModel](layers.length)
166+
var offset = 0
167+
for(i <- 0 until layers.length){
168+
layerModels(i) = layers(i).getInstance(weights, offset)
169+
offset += layerModels(i).size
170+
}
171+
new DropoutModel(layerModels, topology)
172+
}
173+
174+
def apply(topology: DropoutTopology, seed: Long = 11L): DropoutModel = {
175+
val layers = topology.layers
176+
val layerModels = new Array[LayerModel](layers.length)
177+
var offset = 0
178+
for(i <- 0 until layers.length){
179+
layerModels(i) = layers(i).getInstance(seed)
180+
offset += layerModels(i).size
181+
}
182+
new DropoutModel(layerModels, topology)
183+
}
184+
}

mllib/src/main/scala/org/apache/spark/mllib/ann/Layer.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ class FunctionalLayerModel private (val activationFunction: ActivationFunction
305305
}
306306

307307
object FunctionalLayerModel {
308-
def apply(layer: FunctionalLayer) = new FunctionalLayerModel(layer.activationFunction)
308+
def apply(layer: FunctionalLayer): FunctionalLayerModel =
309+
new FunctionalLayerModel(layer.activationFunction)
309310
}
310311

311312
trait Topology extends Serializable{
@@ -423,7 +424,6 @@ class FeedForwardModel(val layerModels: Array[LayerModel],
423424
val result = forward(data.toBreeze.toDenseVector.toDenseMatrix.t)
424425
Vectors.dense(result.last.toArray)
425426
}
426-
427427
}
428428

429429
object FeedForwardModel {
@@ -531,14 +531,13 @@ class FeedForwardTrainer (topology: Topology, val inputSize: Int,
531531
val outputSize: Int) extends Serializable {
532532

533533
// TODO: what if we need to pass random seed?
534-
private var _weights = topology.getInstance(11L).weights()//FeedForwardModel(topology).weights()
534+
private var _weights = topology.getInstance(11L).weights()
535535
private var _batchSize = 1
536536
private var dataStacker = new DataStacker(_batchSize, inputSize, outputSize)
537537
private var _gradient: Gradient = new ANNGradient(topology, dataStacker)
538538
private var _updater: Updater = new ANNUpdater()
539539
private var optimizer: Optimizer = LBFGSOptimizer.setConvergenceTol(1e-4).setNumIterations(100)
540540

541-
542541
def getWeights: Vector = _weights
543542

544543
def setWeights(value: Vector): FeedForwardTrainer = {
@@ -596,7 +595,6 @@ class FeedForwardTrainer (topology: Topology, val inputSize: Int,
596595

597596
def train(data: RDD[(Vector, Vector)]): TopologyModel = {
598597
val newWeights = optimizer.optimize(dataStacker.stack(data), getWeights)
599-
//FeedForwardModel(topology, newWeights)
600598
topology.getInstance(newWeights)
601599
}
602600

0 commit comments

Comments
 (0)