Skip to content
This repository was archived by the owner on Feb 19, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/scala/epic/logo/ArgmaxInferencer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package epic.logo

trait ArgmaxInferencer[T, Y, W] {

def argmax(weights : Weights[W], instance : T) : (Y, W, Double)

}
28 changes: 28 additions & 0 deletions src/main/scala/epic/logo/CompoundIterationCallback.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package epic.logo

class CompoundIterationCallback[T, W](val callbacks : Iterable[IterationCallback[T, W]]) extends IterationCallback[T, W] {

override def startIteration(iter : Int, weights : Weights[W]) : Unit = {
callbacks.foreach(_.startIteration(iter, weights))
}

override def startMinibatch(iter : Int, weights : Weights[W], miniBatch : Array[MinibatchInput[T, W]]) : Unit = {
callbacks.foreach(_.startMinibatch(iter, weights, miniBatch))
}
override def endMinibatch(iter : Int, weights : Weights[W], miniBatch : Array[MinibatchOutput[T, W]]) : Unit = {
callbacks.foreach(_.endMinibatch(iter, weights, miniBatch))
}

override def endIteration(iter : Int, weights : Weights[W]) : Unit = {
callbacks.foreach(_.endIteration(iter, weights))
}

override def objectiveValCheck(primal : Double, dual : Double, iter : Int, w : Weights[W]) : Unit = {
callbacks.foreach(_.objectiveValCheck(primal, dual, iter, w))
}

override def converged(weights : Weights[W], data : Seq[Instance[T, W]], iter : Int, numNewConstraints : Int) : Boolean = {
callbacks.forall(_.converged(weights, data, iter, numNewConstraints))
}

}
7 changes: 7 additions & 0 deletions src/main/scala/epic/logo/ConvergenceChecker.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package epic.logo

trait ConvergenceChecker[W] {

def converged(weights : Weights[W], data : Seq[Instance[_, W]], iter : Int, numNewConstraints : Int) : Boolean

}
7 changes: 7 additions & 0 deletions src/main/scala/epic/logo/Decoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package epic.logo

trait Decoder[T, W] {

def decode(weights : Weights[W], instance : T) : (W, Double)

}
7 changes: 7 additions & 0 deletions src/main/scala/epic/logo/ExpectationInferencer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package epic.logo

trait ExpectationInferencer[T, W] {

def expectations(weights : Weights[W], instance : T) : (W, Double)

}
11 changes: 11 additions & 0 deletions src/main/scala/epic/logo/FixedIterationConvergenceChecker.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package epic.logo

import scala.collection.Seq

class FixedIterationConvergenceChecker[W](val maxNumIters : Int) extends ConvergenceChecker[W] {

def converged(weights : Weights[W], data : Seq[Instance[_, W]], iter : Int, numNewConstraints : Int) : Boolean = {
iter >= maxNumIters
}

}
31 changes: 31 additions & 0 deletions src/main/scala/epic/logo/FixedStepSizeUpdater.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package epic.logo
import scala.collection.mutable.Buffer
import scala.runtime.DoubleRef
import breeze.linalg.norm
import breeze.math.MutableInnerProductModule

class FixedStepSizeUpdater[W](stepSize : Int => Double, C : Double)(implicit space: MutableInnerProductModule[W, Double]) extends Updater[W] {
import space._

def update(instance: Instance[_, W],
w: Weights[W], n: Int, iter: Int): Boolean = {
import instance._
assert(constraints.length == 2)
val (df, _) = constraints(0)
if (norm(df) == 0.0) return false

val eta = stepSize(iter)
if (C == Double.PositiveInfinity) {
w += df * eta
} else {
w *= (1.0 - eta)
w += df * (eta * C / n)
}
return true
}

def currentSlack(i : Instance[_, W], w : Weights[W]) : Double = {
throw new UnsupportedOperationException(this.getClass().getName() + " should be only be used in online mode.")
}

}
11 changes: 11 additions & 0 deletions src/main/scala/epic/logo/Instance.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package epic.logo

import scala.collection.mutable.ArrayBuffer

/**
* This class is mutable for efficiency purposes.
*/
case class Instance[T, W](var x: T,
var alphas: ArrayBuffer[Double] = ArrayBuffer[Double](),
var slack: Double = 0.0,
var constraints: ArrayBuffer[(W, Double)] = ArrayBuffer[(W, Double)]())
17 changes: 17 additions & 0 deletions src/main/scala/epic/logo/IterationCallback.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package epic.logo

trait IterationCallback[T, W] {

def startIteration(iter : Int, weights : Weights[W]) : Unit = {}

def startMinibatch(iter : Int, weights : Weights[W], miniBatch : Array[MinibatchInput[T, W]]) : Unit = {}
def endMinibatch(iter : Int, weights : Weights[W], miniBatch : Array[MinibatchOutput[T, W]]) : Unit = {}
def endIteration(iter : Int, weights : Weights[W]) : Unit = {}

def objectiveValCheck(primal : Double, dual : Double, iter : Int, weights : Weights[W]) : Unit = {}

def converged(weights : Weights[W], data : Seq[Instance[T, W]], iter : Int, numNewConstraints : Int) : Boolean = false

}

case class NullIterationCallback[T, W]() extends IterationCallback[T, W]
39 changes: 39 additions & 0 deletions src/main/scala/epic/logo/L1Objective.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package epic.logo

import breeze.linalg._
import breeze.math.MutableInnerProductModule

object L1Objective {
def slack[W](i : Instance[_, W], w : Weights[W])(implicit space: MutableInnerProductModule[W, Double]) = {
import space._
if (i.constraints.isEmpty)
Double.NegativeInfinity
else
i.constraints.map { case (df, l) => l - w * df }.max
}

}

class L1Objective[W](val C : Double)(implicit space: MutableInnerProductModule[W, Double]) extends ObjectiveFunction[W] {
import space._

def calculatePrimalAndDual(w : Weights[W], data : Seq[Instance[_, W]]): (Double, Double) = {
w.checkNorm
val calc_w = new Weights(space.zeroLike(w.underlying))
data.flatMap(instance => instance.alphas zip instance.constraints).map { case (alpha, (df, l)) => df * alpha }.foreach(fv => {
calc_w += fv
})
assert(data.forall(instance => NumUtils.approxEquals(C, instance.alphas.sum, 1e-5)))
assert(w.approxEquals(calc_w))
val primal = {
val slackSum = data.map(i => L1Objective.slack(i, w)).sum
0.5 * (w.`^2`) + C * slackSum
}
val dual = {
val lossSum = data.flatMap(instance => instance.alphas zip instance.constraints).map { case (alpha, (df, l)) => alpha * l }.sum
-0.5 * (w.`^2`) + lossSum
}
(primal, dual)
}

}
55 changes: 55 additions & 0 deletions src/main/scala/epic/logo/L1Updater.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package epic.logo

import scala.collection.mutable.Buffer
import scala.runtime.DoubleRef
import scala.util.Random
import breeze.math.MutableInnerProductModule

class L1Updater[W](C : Double)(implicit space: MutableInnerProductModule[W, Double]) extends Updater[W] {
val shuffleRand = new Random()
import space._

def update(instance: Instance[_, W], w : Weights[W], n : Int, iter : Int) : Boolean = {
import instance._
if (constraints.length == 1) {
val alpha0 = alphas(0)
if (alpha0 != C) {
val eta = C - alpha0
val (df, l) = constraints(0)
if (eta != 0.0) {
w += df * eta
alphas(0) += eta
return true
}
}
return false
} else {
var anyChange = false
val indexed = constraints.zipWithIndex
val ((df1, l1), s1) = indexed.maxBy { case ((df, l), s) => l - w * df }

for (((df2, l2), s2) <- shuffleRand.shuffle(indexed) if s2 != s1 && alphas(s2) > 0.0) {
val diff = df2 - df1
val num = (l2 - l1) - w * diff
if (num != 0.0) {
val denom = diff dot diff
if (denom != 0.0) {
val eta = clip(num / denom, -alphas(s2), +alphas(s1))
if (eta != 0.0) {
alphas(s2) += eta
alphas(s1) -= eta
w += diff * eta
anyChange = true
}
}
}

}
return anyChange
}
}

def currentSlack(i : Instance[_, W], w : Weights[W]) = {
L1Objective.slack(i, w)
}
}
24 changes: 24 additions & 0 deletions src/main/scala/epic/logo/L2Objective.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package epic.logo

import breeze.math.MutableInnerProductModule

class L2Objective[W](val C: Double)(implicit space: MutableInnerProductModule[W, Double])
extends ObjectiveFunction[W] {

def calculatePrimalAndDual(w: Weights[W], data: Seq[Instance[_, W]]): (Double, Double) = {
val primal = {
val slackSum = data.map(instance => instance.slack * instance.slack).sum
0.5 * (w.`^2`) + C * slackSum
}
val dual = {
val lossSum = data.flatMap(instance => instance.alphas zip instance.constraints).map {
case (alpha, (df, l)) => alpha * l
}.sum
C * lossSum - primal
}
(primal, dual)
}



}
38 changes: 38 additions & 0 deletions src/main/scala/epic/logo/L2Updater.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package epic.logo

import scala.collection.mutable.Buffer
import scala.runtime.DoubleRef
import breeze.math.MutableInnerProductModule

class L2Updater[W](C : Double)(implicit space: MutableInnerProductModule[W, Double]) extends Updater[W] {
import space._

def update(instance: Instance[_, W],
w: Weights[W], n: Int, iter: Int): Boolean = {
import instance._
for (((df, l), s) <- constraints.zipWithIndex) {
val wTdf = w * df
val num = l - wTdf - slack
if (num != 0.0) {
val denom = (1.0 / C) + (df dot df)
if (denom != 0.0) {
val eta = clip(num / denom, -alphas(s), Double.PositiveInfinity)
if (eta != 0.0) {
alphas(s) += eta
slack += eta / C
w += df * eta
return true

}
}
}
}
return false

}

def currentSlack(i : Instance[_, W], w : Weights[W]) = {
i.slack
}

}
16 changes: 16 additions & 0 deletions src/main/scala/epic/logo/LogLikelihoodDecoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package epic.logo

import breeze.math.MutableInnerProductModule

class LogLikelihoodDecoder[T, W](
val inferencer: OracleInferencer[T, _, W], val summer: ExpectationInferencer[T, W])(
implicit space: MutableInnerProductModule[W, Double]) extends Decoder[T, W] {
import space._

def decode(weights : Weights[W], instance : T) : (W, Double) = {
val (y_*, f_*, l_*) = inferencer.oracle(weights, instance)
val (f, l) = summer.expectations(weights, instance)
((f_* - f), l - l_*)
}

}
16 changes: 16 additions & 0 deletions src/main/scala/epic/logo/LogoOpts.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package epic.logo


case class LogoOpts(constraintEpsilon: Double = 1e-3,

miniBatchSize: Int = 1,

numInnerOptimizationLoops: Int = 10,

numOuterOptimizationLoops: Int = 10,

shuffleMinibatches: Boolean = false,

shuffleSeed: Int = -1,

convergenceTolerance: Double = 1e-3)
9 changes: 9 additions & 0 deletions src/main/scala/epic/logo/LossAugmentedArgmaxInferencer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package epic.logo

trait LossAugmentedArgmaxInferencer[T, Y, W] extends ArgmaxInferencer[T, Y, W] {

def argmax(weights : Weights[W], instance : T) : (Y, W, Double) = lossAugmentedArgmax(weights, instance, 1.0, 0.0)

def lossAugmentedArgmax(weights : Weights[W], instance : T, weightsWeight : Double, lossWeight : Double) : (Y, W, Double)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package epic.logo

trait LossAugmentedExpectationInferencer[T, W] extends ExpectationInferencer[T, W] {

def expectations(weights : Weights[W], instance : T) : (W, Double) = lossAugmentedExpectations(weights, instance, 1.0, 0.0)

def lossAugmentedExpectations(weights : Weights[W], instance : T, weightsWeight : Double, lossWeight : Double) : (W, Double)

}
14 changes: 14 additions & 0 deletions src/main/scala/epic/logo/LossAugmentedMaxMarginDecoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package epic.logo

import breeze.math.MutableInnerProductModule

class LossAugmentedMaxMarginDecoder[T, W](val oracleInferencer: OracleInferencer[T, _, W], val argmaxer: LossAugmentedArgmaxInferencer[T, _, W])(implicit space: MutableInnerProductModule[W, Double]) extends Decoder[T, W] {
import space._

def decode(weights: Weights[W], instance: T): (W, Double) = {
val (y_*, f_*, l_*) = oracleInferencer.oracle(weights, instance)
val (y, f, l) = argmaxer.lossAugmentedArgmax(weights, instance, 1.0, 1.0)
(f_* - f, l - l_*)
}

}
14 changes: 14 additions & 0 deletions src/main/scala/epic/logo/MaxMarginDecoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package epic.logo

import breeze.math.MutableInnerProductModule

class MaxMarginDecoder[T, W](val oracleInferencer : OracleInferencer[T, _, W], val argmaxer : ArgmaxInferencer[T, _, W])(implicit space: MutableInnerProductModule[W, Double]) extends Decoder[T, W] {
import space._

def decode(weights : Weights[W], instance : T) : (W, Double) = {
val (y_*, f_*, l_*) = oracleInferencer.oracle(weights, instance)
val (y, f, l) = argmaxer.argmax(weights, instance)
(f_* - f, l - l_*)
}

}
13 changes: 13 additions & 0 deletions src/main/scala/epic/logo/MaxMarginRankingDecoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package epic.logo

import breeze.math.MutableInnerProductModule

class MaxMarginRankingDecoder[T, W](val inferencer : LossAugmentedArgmaxInferencer[T, _, W], val gamma : Double = 0.0)(implicit space: MutableInnerProductModule[W, Double]) extends Decoder[T, W] {
import space._
def decode(weights: Weights[W], instance: T): (W, Double) = {
val (y_min, df_min, l_min) = inferencer.lossAugmentedArgmax(weights, instance, -1.0, -(1.0 + gamma))
val (y_max, df_max, l_max) = inferencer.lossAugmentedArgmax(weights, instance, 1.0, 1.0)
(df_min - df_max, l_max - (1.0 + gamma) * l_min)
}

}
5 changes: 5 additions & 0 deletions src/main/scala/epic/logo/MinibatchInput.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package epic.logo

case class MinibatchInput[T, W](val instance : Instance[T, W], val instanceNum : Int)

case class MinibatchOutput[T, W](val instance : Instance[T, W], val instanceNum : Int, val df : W, val loss : Double)
Loading