|
| 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 | +} |
0 commit comments