diff --git a/CHANGES.md b/CHANGES.md index 968d9cf..ab1c666 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ ++ **0.6.2** - Shade additional package internal to EJML ++ **0.6.1** - Shade EJML ++ **0.6.0** - Calculate with EJML rather than Breeze ++ **0.6.0** - Use sum instead of concat + **0.5.0** - Support Linux on aarch64 + **0.5.0** - Isolate dependencies on models to the apps subproject + **0.4.0** - Account for maxTokens diff --git a/apps/build.sbt b/apps/build.sbt index 4a917aa..bdca081 100644 --- a/apps/build.sbt +++ b/apps/build.sbt @@ -7,8 +7,9 @@ resolvers ++= Seq( libraryDependencies ++= { Seq( - "org.clulab" % "roberta-onnx-model" % "0.1.0", - "org.clulab" % "deberta-onnx-model" % "0.1.0", + "org.clulab" % "deberta-onnx-model" % "0.2.0", + "org.clulab" % "electra-onnx-model" % "0.2.0", + "org.clulab" % "roberta-onnx-model" % "0.2.0", "org.scalatest" %% "scalatest" % "3.2.15" % "test" ) } diff --git a/encoder/src/main/scala-2.12/org/clulab/scala_transformers/encoder/apps/BlasInstanceApp.scala b/apps/src/main/scala-2.12/org/clulab/scala_transformers/apps/BlasInstanceApp.scala.txt similarity index 91% rename from encoder/src/main/scala-2.12/org/clulab/scala_transformers/encoder/apps/BlasInstanceApp.scala rename to apps/src/main/scala-2.12/org/clulab/scala_transformers/apps/BlasInstanceApp.scala.txt index 88d344f..00104ab 100644 --- a/encoder/src/main/scala-2.12/org/clulab/scala_transformers/encoder/apps/BlasInstanceApp.scala +++ b/apps/src/main/scala-2.12/org/clulab/scala_transformers/apps/BlasInstanceApp.scala.txt @@ -1,4 +1,4 @@ -package org.clulab.scala_transformers.encoder.apps +package org.clulab.scala_transformers.apps import dev.ludovic.netlib.blas.{BLAS, JavaBLAS, NativeBLAS} diff --git a/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromFileApp.scala b/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromFileApp.scala index 8bb54e9..797cf0c 100644 --- a/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromFileApp.scala +++ b/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromFileApp.scala @@ -5,7 +5,12 @@ import org.clulab.scala_transformers.tokenizer.LongTokenization import org.clulab.scala_transformers.tokenizer.jni.ScalaJniTokenizer object LoadExampleFromFileApp extends App { - val baseName = args.lift(0).getOrElse("../tcmodel") + // Choose one of these. + val defaultBaseName = "../models/microsoft_deberta_v3_base_mtl/avg_export" + // val defaultBaseName = "../models/google_electra_small_discriminator_mtl/avg_export" + // val defaultBaseName = "../models/roberta_base_mtl/avg_export" + + val baseName = args.lift(0).getOrElse(defaultBaseName) val tokenClassifierLayout = new TokenClassifierLayout(baseName) val tokenClassifierFactory = new TokenClassifierFactoryFromFiles(tokenClassifierLayout) val words = Array("EU", "rejects", "German", "call", "to", "boycott", "British", "lamb", ".") diff --git a/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromResourceApp.scala b/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromResourceApp.scala index 11ee9e0..8ee06f2 100644 --- a/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromResourceApp.scala +++ b/apps/src/main/scala/org/clulab/scala_transformers/apps/LoadExampleFromResourceApp.scala @@ -5,7 +5,11 @@ import org.clulab.scala_transformers.tokenizer.LongTokenization import org.clulab.scala_transformers.tokenizer.jni.ScalaJniTokenizer object LoadExampleFromResourceApp extends App { - val baseName = "/org/clulab/scala_transformers/models/roberta_base_mtl/avg_export" + // Choose one of these. + val baseName = "/org/clulab/scala_transformers/models/microsoft_deberta_v3_base_mtl/avg_export" + // val baseName = "/org/clulab/scala_transformers/models/google_electra_small_discriminator_mtl/avg_export" + // val baseName = "/org/clulab/scala_transformers/models/roberta_base_mtl/avg_export" + val tokenClassifierLayout = new TokenClassifierLayout(baseName) val tokenClassifierFactory = new TokenClassifierFactoryFromResources(tokenClassifierLayout) val words = Array("EU", "rejects", "German", "call", "to", "boycott", "British", "lamb", ".") diff --git a/apps/src/main/scala/org/clulab/scala_transformers/apps/TokenClassifierExampleApp.scala b/apps/src/main/scala/org/clulab/scala_transformers/apps/TokenClassifierExampleApp.scala index 445ce6a..ee3860b 100644 --- a/apps/src/main/scala/org/clulab/scala_transformers/apps/TokenClassifierExampleApp.scala +++ b/apps/src/main/scala/org/clulab/scala_transformers/apps/TokenClassifierExampleApp.scala @@ -2,17 +2,14 @@ package org.clulab.scala_transformers.apps import org.clulab.scala_transformers.encoder.TokenClassifier -/* -import java.io.File - -import org.clulab.scala_transformers.tokenizer.jni.ScalaJniTokenizer -import org.clulab.scala_transformers.tokenizer.LongTokenization -*/ - object TokenClassifierExampleApp extends App { - //val tokenClassifier = TokenClassifier.fromFiles("../../scala-transformers-models/roberta-base-mtl/avg_export") - val tokenClassifier = TokenClassifier.fromFiles("../microsoft-deberta-v3-base-mtl/avg_export") - //val tokenClassifier = TokenClassifier.fromResources("/org/clulab/scala_transformers/models/microsoft_deberta_v3_base_mtl/avg_export") + // Choose one of these. + val tokenClassifier = TokenClassifier.fromFiles("../models/microsoft_deberta_v3_base_mtl/avg_export") + // val tokenClassifier = TokenClassifier.fromResources("/org/clulab/scala_transformers/models/microsoft_deberta_v3_base_mtl/avg_export") + // val tokenClassifier = TokenClassifier.fromFiles("../models/google_electra_small_discriminator_mtl/avg_export") + // val tokenClassifier = TokenClassifier.fromResources("/org/clulab/scala_transformers/models/google_electra_small_discriminator_mtl/avg_export") + // val tokenClassifier = TokenClassifier.fromFiles("../models/roberta_base_mtl/avg_export") + // val tokenClassifier = TokenClassifier.fromResources("/org/clulab/scala_transformers/models/roberta_base_mtl/avg_export") //val words = Seq("EU", "rejects", "German", "call", "to", "boycott", "British", "lamb", ".") val words = Seq("John", "Doe", "went", "to", "China", ".") diff --git a/apps/src/main/scala/org/clulab/scala_transformers/apps/TokenClassifierTimerApp.scala b/apps/src/main/scala/org/clulab/scala_transformers/apps/TokenClassifierTimerApp.scala new file mode 100644 index 0000000..1398c8f --- /dev/null +++ b/apps/src/main/scala/org/clulab/scala_transformers/apps/TokenClassifierTimerApp.scala @@ -0,0 +1,130 @@ +package org.clulab.scala_transformers.apps + +import org.clulab.scala_transformers.common.Timers +import org.clulab.scala_transformers.encoder.{EncoderMaxTokensRuntimeException, TokenClassifier} +import org.clulab.scala_transformers.tokenizer.LongTokenization + +import scala.io.Source + +object TokenClassifierTimerApp extends App { + + class TimedTokenClassifier(tokenClassifier: TokenClassifier) extends TokenClassifier( + tokenClassifier.encoder, tokenClassifier.maxTokens, tokenClassifier.tasks, tokenClassifier.tokenizer + ) { + val tokenizeTimer = Timers.getOrNew("Tokenizer") + val forwardTimer = Timers.getOrNew("Encoder.forward") + val predictTimers = tokenClassifier.tasks.indices.map { index => + val name = tasks(index).name + + Timers.getOrNew(s"Encoder.predict $index\t$name") + } + + // NOTE: This should be copied from the base class and then instrumented with timers. + override def predictWithScores(words: Seq[String], headTaskName: String = "Deps Head"): Array[Array[Array[(String, Float)]]] = { + // This condition must be met in order for allLabels to be filled properly without nulls. + // The condition is not checked at runtime! + // if (tasks.exists(_.dual)) + // require(tasks.count(task => !task.dual && task.name == headTaskName) == 1) + + // tokenize to subword tokens + val tokenization = tokenizeTimer.time { + LongTokenization(tokenizer.tokenize(words.toArray)) + } + val inputIds = tokenization.tokenIds + val wordIds = tokenization.wordIds + val tokens = tokenization.tokens + + if (inputIds.length > maxTokens) { + throw new EncoderMaxTokensRuntimeException(s"Encoder error: the following text contains more tokens than the maximum number accepted by this encoder ($maxTokens): ${tokens.mkString(", ")}") + } + + // run the sentence through the transformer encoder + val encOutput = forwardTimer.time { + encoder.forward(inputIds) + } + + // outputs for all tasks stored here: task x tokens in sentence x scores per token + val allLabels = new Array[Array[Array[(String, Float)]]](tasks.length) + // all heads predicted for every token + // dimensions: token x heads + var heads: Option[Array[Array[Int]]] = None + + // now generate token label predictions for all primary tasks (not dual!) + for (i <- tasks.indices) { + if (!tasks(i).dual) { + val tokenLabels = predictTimers(i).time { + tasks(i).predictWithScores(encOutput, None, None) + } + val wordLabels = TokenClassifier.mapTokenLabelsAndScoresToWords(tokenLabels, tokenization.wordIds) + allLabels(i) = wordLabels + + // if this is the task that predicts head positions, then save them for the dual tasks + // we save all the heads predicted for each token + if (tasks(i).name == headTaskName) { + heads = Some(tokenLabels.map(_.map(_._1.toInt))) + } + } + } + + // generate outputs for the dual tasks, if heads were predicted by one of the primary tasks + // the dual task(s) must be aligned with the heads. + // that is, we predict the top label for each of the head candidates + if (heads.isDefined) { + //println("Tokens: " + tokens.mkString(", ")) + //println("Heads:\n\t" + heads.get.map(_.slice(0, 3).mkString(", ")).mkString("\n\t")) + //println("Masks: " + TokenClassifier.mkTokenMask(wordIds).mkString(", ")) + val masks = Some(TokenClassifier.mkTokenMask(wordIds)) + + for (i <- tasks.indices) { + if (tasks(i).dual) { + val tokenLabels = predictTimers(i).time { + tasks(i).predictWithScores(encOutput, heads, masks) + } + val wordLabels = TokenClassifier.mapTokenLabelsAndScoresToWords(tokenLabels, tokenization.wordIds) + allLabels(i) = wordLabels + } + } + } + + allLabels + } + } + + val verbose = false + val fileName = args.lift(0).getOrElse("../corpora/sentences/sentences.txt") + // Choose one of these. + val untimedTokenClassifier = TokenClassifier.fromFiles("../models/microsoft_deberta_v3_base_mtl/avg_export") + // val untimedTokenClassifier = TokenClassifier.fromFiles("../models/google_electra_small_discriminator_mtl/avg_export") + // val untimedTokenClassifier = TokenClassifier.fromFiles("../models/roberta_base_mtl/avg_export") + + val tokenClassifier = new TimedTokenClassifier(untimedTokenClassifier) + val lines = { + val source = Source.fromFile(fileName) + val lines = source.getLines().take(100).toArray + + source.close + lines + } + val elapsedTimer = Timers.getOrNew("Elapsed") + + elapsedTimer.time { + lines.zipWithIndex/*.par*/.foreach { case (line, index) => + println(s"$index $line") + if (index != 1382) { + val words = line.split(" ").toSeq + val allLabelSeqs = tokenClassifier.predictWithScores(words) + + if (verbose) { + println(s"Words: ${words.mkString(", ")}") + for (layer <- allLabelSeqs) { + val words = layer.map(_.head) // Collapse the next layer by just taking the head. + val wordLabels = words.map(_._1) + + println(s"Labels: ${wordLabels.mkString(", ")}") + } + } + } + } + } + Timers.summarize() +} diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/timer/Timer.scala b/common/src/main/scala/org/clulab/scala_transformers/common/Timer.scala similarity index 97% rename from encoder/src/test/scala/org/clulab/scala_transformers/encoder/timer/Timer.scala rename to common/src/main/scala/org/clulab/scala_transformers/common/Timer.scala index 19381d7..304b73d 100644 --- a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/timer/Timer.scala +++ b/common/src/main/scala/org/clulab/scala_transformers/common/Timer.scala @@ -1,4 +1,4 @@ -package org.clulab.scala_transformers.encoder.timer +package org.clulab.scala_transformers.common import scala.collection.mutable.{HashMap => MutableHashMap} diff --git a/encoder/build.sbt b/encoder/build.sbt index abaa5b4..4603e2f 100644 --- a/encoder/build.sbt +++ b/encoder/build.sbt @@ -10,9 +10,16 @@ libraryDependencies ++= { case Some((2, minor)) if minor < 12 => "1.0" case _ => "2.1.0" } + val ejmlVersion = "0.41" // Use this older version for Java 8. Seq( - "org.scalanlp" %% "breeze" % breezeVersion, + // Choose one of these. + /// "org.apache.commons" % "commons-math3" % "3.6.1", + "org.ejml" % "ejml-core" % ejmlVersion, + "org.ejml" % "ejml-fdense" % ejmlVersion, + "org.ejml" % "ejml-simple" % ejmlVersion, + // "org.scalanlp" %% "breeze" % breezeVersion, + "com.microsoft.onnxruntime" % "onnxruntime" % "1.13.1", "org.slf4j" % "slf4j-api" % "1.7.10" ) @@ -21,3 +28,15 @@ libraryDependencies ++= { fork := true // assembly / mainClass := Some("com.keithalcock.tokenizer.scalapy.apps.ExampleApp") + +enablePlugins(ShadingPlugin) +shadedDependencies ++= Set( + "org.ejml" % "ejml-core" % "", + "org.ejml" % "ejml-fdense" % "", + "org.ejml" % "ejml-simple" % "" +) +shadingRules ++= Seq( + ShadingRule.moveUnder("org.ejml", "org.clulab.shaded"), + ShadingRule.moveUnder("pabeles.concurrency", "org.clulab.shaded") +) +validNamespaces ++= Set("org", "org.clulab") diff --git a/encoder/src/main/python/averaging_trainer.py b/encoder/src/main/python/averaging_trainer.py index 3193eca..6990431 100644 --- a/encoder/src/main/python/averaging_trainer.py +++ b/encoder/src/main/python/averaging_trainer.py @@ -126,7 +126,7 @@ def print_some_params(self, model: TokenClassificationModel, msg: str) -> None: ShortTaskDef("Chunking", "chunking/", "train.txt", "test.txt", "test.txt"), #ShortTaskDef("Deps Head", "deps-wsj/", "train.heads", "dev.heads", "test.heads"), #ShortTaskDef("Deps Label", "deps-wsj/", "train.labels", "dev.labels", "test.labels", dual_mode=True) - ShortTaskDef("Deps Head", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.heads", "dev.heads", "test.heads"), - ShortTaskDef("Deps Label", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.labels", "dev.labels", "test.labels", dual_mode=True) + ShortTaskDef("Deps Head", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.heads", "test.heads", "test.heads"), + ShortTaskDef("Deps Label", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.labels", "test.labels", "test.labels", dual_mode=True) ]) AveragingTrainer(tokenizer).train(tasks) diff --git a/encoder/src/main/python/clu_trainer.py b/encoder/src/main/python/clu_trainer.py index 21d5921..d1073fe 100644 --- a/encoder/src/main/python/clu_trainer.py +++ b/encoder/src/main/python/clu_trainer.py @@ -90,8 +90,8 @@ def compute_metrics(self, eval_pred: EvalPrediction) -> Dict[str, float]: ShortTaskDef("NER", "conll-ner/", "train.txt", "dev.txt", "test.txt"), ShortTaskDef("POS", "pos/", "train.txt", "dev.txt", "test.txt"), ShortTaskDef("Chunking", "chunking/", "train.txt", "test.txt", "test.txt"), # this dataset has no dev - ShortTaskDef("Deps Head", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.heads", "dev.heads", "test.heads"), - ShortTaskDef("Deps Label", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.labels", "dev.labels", "test.labels", dual_mode=True) + ShortTaskDef("Deps Head", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.heads", "test.heads", "test.heads"), # dev is included in train + ShortTaskDef("Deps Label", "deps-combined/", "wsjtrain-wsjdev-geniatrain-geniadev.labels", "test.labels", "test.labels", dual_mode=True) # dev is included in train #ShortTaskDef("Deps Head", "deps-wsj/", "train.heads", "dev.heads", "test.heads"), #ShortTaskDef("Deps Label", "deps-wsj/", "train.labels", "dev.labels", "test.labels", dual_mode=True) ]) diff --git a/encoder/src/main/python/token_classifier.py b/encoder/src/main/python/token_classifier.py index 61963bb..e7dae3b 100644 --- a/encoder/src/main/python/token_classifier.py +++ b/encoder/src/main/python/token_classifier.py @@ -223,7 +223,7 @@ def __init__(self, hidden_size: int, num_labels: int, task_id, dual_mode: bool=F self.dropout = nn.Dropout(dropout_p) self.dual_mode = dual_mode self.classifier = nn.Linear( - hidden_size if not self.dual_mode else hidden_size * 2, + hidden_size, # if not self.dual_mode else hidden_size * 2, # USE SUM num_labels ) self.num_labels = num_labels @@ -248,8 +248,11 @@ def concatenate(self, sequence_output, head_positions): head_states = sequence_output[torch.arange(sequence_output.shape[0]).unsqueeze(1), long_head_positions] #print(f"head_states.size = {head_states.size()}") # Concatenate the hidden states from modifier + head. - modifier_head_states = torch.cat([sequence_output, head_states], dim=2) + #modifier_head_states = torch.cat([sequence_output, head_states], dim=2) + modifier_head_states = torch.add(sequence_output, head_states) # USE SUM #print(f"modifier_head_states.size = {modifier_head_states.size()}") + #print("EXIT") + #exit(1) return modifier_head_states def forward(self, sequence_output, pooled_output, head_positions, labels=None, attention_mask=None, **kwargs): diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeExamples.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeExamples.scala.txt similarity index 77% rename from encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeExamples.scala rename to encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeExamples.scala.txt index b0e57a0..f65454e 100644 --- a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeExamples.scala +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeExamples.scala.txt @@ -1,10 +1,10 @@ package org.clulab.scala_transformers.encoder -import breeze.linalg._ -import BreezeUtils._ +import breeze.linalg._ +import org.clulab.scala_transformers.encoder.math.BreezeMath object BreezeExamples extends App { - val m = mkRowMatrix[Float](Array(Array(1f, 2f), Array(3f, 4f))) + val m = BreezeMath.mkMatrixFromRows(Array(Array(1f, 2f), Array(3f, 4f))) println(m) println("Row 0: " + m(0, ::)) diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeUtils.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeUtils.scala deleted file mode 100644 index e500a2e..0000000 --- a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/BreezeUtils.scala +++ /dev/null @@ -1,33 +0,0 @@ -package org.clulab.scala_transformers.encoder - -import breeze.linalg.DenseMatrix - -import scala.reflect.ClassTag - -object BreezeUtils { - /** - * Constructs a dense matrix by rows - * - * @param inputs Input values; first dimension are rows; second are columns - */ - def mkRowMatrix[T: ClassTag](inputs: Array[Array[T]]): DenseMatrix[T] = { - val rows = inputs.length - val cols = inputs.head.length - val dm = new DenseMatrix[T](rows, cols) - - for (i <- 0 until rows) - for (j <- 0 until cols) - dm(i, j) = inputs(i)(j) - dm - } - - def arrayConcat[T: ClassTag](arrays: Array[Array[T]]): Array[T] = { - val indivLen = arrays.head.length - val overallLen = arrays.length * indivLen - val output = new Array[T](overallLen) - - for (i <- arrays.indices) - System.arraycopy(arrays(i), 0, output, i * indivLen, indivLen) - output - } -} diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/Encoder.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/Encoder.scala index aecf357..19fa2d1 100644 --- a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/Encoder.scala +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/Encoder.scala @@ -1,7 +1,7 @@ package org.clulab.scala_transformers.encoder import ai.onnxruntime.{OnnxTensor, OrtEnvironment, OrtSession} -import breeze.linalg.DenseMatrix +import org.clulab.scala_transformers.encoder.math.Mathematics.{Math, MathMatrix} import java.io.DataInputStream import java.util.{HashMap => JHashMap} @@ -13,12 +13,12 @@ class Encoder(val encoderEnvironment: OrtEnvironment, val encoderSession: OrtSes * @param batchInputIds First dimension is batch size (1 for a single sentence); second is sentence size * @return Hidden states for the whole batch. The matrix dimension: rows = sentence size; columns = hidden state size */ - def forward(batchInputIds: Array[Array[Long]]): Array[DenseMatrix[Float]] = { + def forward(batchInputIds: Array[Array[Long]]): Array[MathMatrix] = { val inputs = new JHashMap[String, OnnxTensor]() inputs.put("token_ids", OnnxTensor.createTensor(encoderEnvironment, batchInputIds)) - val encoderOutput = encoderSession.run(inputs).get(0).getValue.asInstanceOf[Array[Array[Array[Float]]]] - val outputs = encoderOutput.map(BreezeUtils.mkRowMatrix(_)) + val result: OrtSession.Result = encoderSession.run(inputs) + val outputs = Math.fromResult(result) outputs } @@ -27,7 +27,7 @@ class Encoder(val encoderEnvironment: OrtEnvironment, val encoderSession: OrtSes * @param inputIds Array of token ids for this sentence * @return Hidden states for this sentence. The matrix dimension: rows = sentence size; columns = hidden state size */ - def forward(inputIds: Array[Long]): DenseMatrix[Float] = { + def forward(inputIds: Array[Long]): MathMatrix = { val batchInputIds = Array(inputIds) forward(batchInputIds).head } diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayer.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayer.scala index 22ee11b..665ed51 100644 --- a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayer.scala +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayer.scala @@ -1,21 +1,18 @@ package org.clulab.scala_transformers.encoder -import breeze.linalg.`*` -import breeze.linalg.argmax -import breeze.linalg.DenseMatrix -import breeze.linalg.DenseVector +import org.clulab.scala_transformers.encoder.math.Mathematics.{MathMatrix, MathColVector, Math} /** Implements one linear layer */ class LinearLayer( val name: String, val dual: Boolean, - val weights: DenseMatrix[Float], // dimensions (hidden state size x labels size) - val biasesOpt: Option[DenseVector[Float]], // column vector with length = labels size + val weights: MathMatrix, // dimensions (hidden state size x labels size) + val biasesOpt: Option[MathColVector], // column vector with length = labels size val labelsOpt: Option[Array[String]] ) { /** Forward pass for a single sentence */ - def forward(inputSentence: DenseMatrix[Float]): DenseMatrix[Float] = { + def forward(inputSentence: MathMatrix): MathMatrix = { val batch = Array(inputSentence) forward(batch).head } @@ -25,19 +22,19 @@ class LinearLayer( * @param inputBatch Each matrix in the batch has dimensions (sentence size x hidden state size) * @return Each output matrix has dimensions (sentence size x labels size) */ - def forward(inputBatch: Array[DenseMatrix[Float]]): Array[DenseMatrix[Float]] = { + def forward(inputBatch: Array[MathMatrix]): Array[MathMatrix] = { inputBatch.map { input => //println("INPUT:\n" + input) - val output = input * weights + val output = Math.mul(input, weights) //println("OUTPUT before bias:\n" + output) - for (b <- biasesOpt) output(*, ::) :+= b + for (b <- biasesOpt) Math.inplaceMatrixAddition(output, b) //println("OUTPUT after bias:\n" + output) output } } /** Predict the top label per token */ - def predict(inputSentence: DenseMatrix[Float], + def predict(inputSentence: MathMatrix, heads: Option[Array[Int]], masks: Option[Array[Boolean]]): Array[String] = { val batchSentences = Array(inputSentence) @@ -47,7 +44,7 @@ class LinearLayer( } /** Predict the top label for each token in each sentence in the batch */ - def predict(inputBatch: Array[DenseMatrix[Float]], + def predict(inputBatch: Array[MathMatrix], batchHeads: Option[Array[Array[Int]]], batchMasks: Option[Array[Array[Boolean]]]): Array[Array[String]] = { if (dual) predictDual(inputBatch, batchHeads, batchMasks) @@ -55,7 +52,7 @@ class LinearLayer( } /** Predict all labels and their scores per token */ - def predictWithScores(inputSentence: DenseMatrix[Float], + def predictWithScores(inputSentence: MathMatrix, heads: Option[Array[Array[Int]]], masks: Option[Array[Boolean]]): Array[Array[(String, Float)]] = { val batchSentences = Array(inputSentence) @@ -65,7 +62,7 @@ class LinearLayer( } /** Predict all labels and their scores per token in each sentence in the batch */ - def predictWithScores(inputBatch: Array[DenseMatrix[Float]], + def predictWithScores(inputBatch: Array[MathMatrix], batchHeads: Option[Array[Array[Array[Int]]]], batchMasks: Option[Array[Array[Boolean]]]): Array[Array[Array[(String, Float)]]] = { if (dual) predictDualWithScores(inputBatch, batchHeads, batchMasks) @@ -73,30 +70,28 @@ class LinearLayer( } def concatenateModifiersAndHeads( - sentenceHiddenStates: DenseMatrix[Float], - headRelativePositions: Array[Int]): DenseMatrix[Float] = { + sentenceHiddenStates: MathMatrix, + headRelativePositions: Array[Int]): MathMatrix = { // this matrix concatenates the hidden states of modifier + corresponding head // rows = number of tokens in the sentence; cols = hidden state size x 2 - val concatMatrix = DenseMatrix.zeros[Float](rows = sentenceHiddenStates.rows, cols = 2 * sentenceHiddenStates.cols) + val concatMatrix = Math.zeros(rows = Math.rows(sentenceHiddenStates), cols = Math.cols(sentenceHiddenStates)) // traverse all modifiers - for(i <- 0 until sentenceHiddenStates.rows) { - val modHiddenState = sentenceHiddenStates(i, ::) + for(i <- 0 until Math.rows(sentenceHiddenStates)) { + val modHiddenState = Math.row(sentenceHiddenStates, i) // what is the absolute position of the head token in the sentence? val rawHeadAbsPos = i + headRelativePositions(i) val headAbsolutePosition = - if(rawHeadAbsPos >= 0 && rawHeadAbsPos < sentenceHiddenStates.rows) rawHeadAbsPos + if(rawHeadAbsPos >= 0 && rawHeadAbsPos < Math.rows(sentenceHiddenStates)) rawHeadAbsPos else i // if the absolute position is invalid (e.g., root node or incorrect prediction) duplicate the mod embedding - val headHiddenState = sentenceHiddenStates(headAbsolutePosition, ::) - - // vector concatenation in Breeze operates over vertical vectors, hence the transposing here - val concatState = DenseVector.vertcat(modHiddenState.t, headHiddenState.t).t - + val headHiddenState = Math.row(sentenceHiddenStates, headAbsolutePosition) // row i in the concatenated matrix contains the embedding of modifier i and its head - concatMatrix(i, ::) :+= concatState + Math.inplaceMatrixAddition(concatMatrix, i, modHiddenState) + Math.inplaceMatrixAddition(concatMatrix, i, headHiddenState) } + //println(s"concatMatrix size ${concatMatrix.rows} x ${concatMatrix.cols}") concatMatrix } @@ -105,34 +100,31 @@ class LinearLayer( * */ def concatenateModifierAndHead( - sentenceHiddenStates: DenseMatrix[Float], - modifierAbsolutePosition: Int, - headRelativePosition: Int): DenseMatrix[Float] = { + sentenceHiddenStates: MathMatrix, + modifierAbsolutePosition: Int, + headRelativePosition: Int): MathMatrix = { // this matrix concatenates the hidden states of modifier + corresponding head // rows = 1; cols = hidden state size x 2 - val concatMatrix = DenseMatrix.zeros[Float](rows = 1, cols = 2 * sentenceHiddenStates.cols) - + val concatMatrix = Math.zeros(rows = 1, cols = Math.cols(sentenceHiddenStates)) // embedding of the modifier - val modHiddenState = sentenceHiddenStates(modifierAbsolutePosition, ::) + val modHiddenState = Math.row(sentenceHiddenStates, modifierAbsolutePosition) // embedding of the head val rawHeadAbsPos = modifierAbsolutePosition + headRelativePosition val headAbsolutePosition = - if(rawHeadAbsPos >= 0 && rawHeadAbsPos < sentenceHiddenStates.rows) rawHeadAbsPos + if (rawHeadAbsPos >= 0 && rawHeadAbsPos < Math.rows(sentenceHiddenStates)) rawHeadAbsPos else modifierAbsolutePosition // if the absolute position is invalid (e.g., root node or incorrect prediction) duplicate the mod embedding - val headHiddenState = sentenceHiddenStates(headAbsolutePosition, ::) - - // concatenation of the modifier and head embeddings - // vector concatenation in Breeze operates over vertical vectors, hence the transposing here - val concatState = DenseVector.vertcat(modHiddenState.t, headHiddenState.t).t - concatMatrix(0, ::) :+= concatState + val headHiddenState = Math.row(sentenceHiddenStates, headAbsolutePosition) + //println(s"concatMatrix size ${concatMatrix.rows} x ${concatMatrix.cols}") + Math.inplaceMatrixAddition(concatMatrix, 0, modHiddenState) + Math.inplaceMatrixAddition(concatMatrix, 0, headHiddenState) concatMatrix } /** Predict the top label for each combination of modifier token and corresponding head token */ - def predictDual(inputBatch: Array[DenseMatrix[Float]], + def predictDual(inputBatch: Array[MathMatrix], batchHeads: Option[Array[Array[Int]]] = None, batchMasks: Option[Array[Array[Boolean]]] = None): Array[Array[String]] = { assert(batchHeads.isDefined) @@ -146,9 +138,9 @@ class LinearLayer( // get the logits for the current sentence produced by this linear layer val logitsPerSentence = forward(Array(concatInput))(0) // one token per row; pick argmax per token - val bestLabels = Range(0, logitsPerSentence.rows).map { i => - val row = logitsPerSentence(i, ::) // picks line i from a 2D matrix - val bestIndex = argmax(row.t) + val bestLabels = Range(0, Math.rows(logitsPerSentence)).map { i => + val row = Math.row(logitsPerSentence, i) // picks line i from a 2D matrix + val bestIndex = Math.argmax(row) indexToLabel(bestIndex) } @@ -163,7 +155,7 @@ class LinearLayer( // out dimensions: sentence in batch x token in sentence x label/score per token // batchHeads dimensions: sentence in batch x token in sentence x heads per token // labels are sorted in descending order of their scores - def predictDualWithScores(inputBatch: Array[DenseMatrix[Float]], + def predictDualWithScores(inputBatch: Array[MathMatrix], batchHeads: Option[Array[Array[Array[Int]]]] = None, batchMasks: Option[Array[Array[Boolean]]] = None): Array[Array[Array[(String, Float)]]] = { assert(batchHeads.isDefined) @@ -181,9 +173,9 @@ class LinearLayer( val concatInput = concatenateModifierAndHead(input, modifierAbsolutePosition, headRelativePosition) // get the logits for the current pair of modifier and head val logitsPerSentence = forward(Array(concatInput))(0) - val labelScores = logitsPerSentence(0, ::) - val bestIndex = argmax(labelScores.t) - val bestScore = labelScores(bestIndex) + val labelScores = Math.row(logitsPerSentence, 0) + val bestIndex = Math.argmax(labelScores) + val bestScore = Math.get(labelScores, bestIndex) val bestLabel = indexToLabel(bestIndex) // println(s"Top prediction for mod $modifierAbsolutePosition and relative head $headRelativePosition is $bestLabel with score $bestScore") @@ -195,15 +187,15 @@ class LinearLayer( outputBatch } - def predictPrimal(inputBatch: Array[DenseMatrix[Float]]): Array[Array[String]] = { + def predictPrimal(inputBatch: Array[MathMatrix]): Array[Array[String]] = { val labels = labelsOpt.getOrElse(throw new RuntimeException("ERROR: can't predict without labels!")) // predict best label per (subword) token val logits = forward(inputBatch) val outputBatch = logits.map { logitsPerSentence => // one token per row; pick argmax per token - val bestLabels = Range(0, logitsPerSentence.rows).map { i => - val row = logitsPerSentence(i, ::) // picks line i from a 2D matrix - val bestIndex = argmax(row.t) + val bestLabels = Range(0, Math.rows(logitsPerSentence)).map { i => + val row = Math.row(logitsPerSentence, i) // picks line i from a 2D matrix + val bestIndex = Math.argmax(row) labels(bestIndex) } @@ -216,15 +208,15 @@ class LinearLayer( // out dimensions: sentence in batch x token in sentence x label/score per token // labels are sorted in descending order of their scores - def predictPrimalWithScores(inputBatch: Array[DenseMatrix[Float]]): Array[Array[Array[(String, Float)]]] = { + def predictPrimalWithScores(inputBatch: Array[MathMatrix]): Array[Array[Array[(String, Float)]]] = { val labels = labelsOpt.getOrElse(throw new RuntimeException("ERROR: can't predict without labels!")) // predict best label per (subword) token val logits = forward(inputBatch) val outputBatch = logits.map { logitsPerSentence => // one token per row; store scores for all labels for this token - val allLabels = Range(0, logitsPerSentence.rows).map { i => + val allLabels = Range(0, Math.rows(logitsPerSentence)).map { i => // picks line i from a 2D matrix and converts it to Array - val scores = logitsPerSentence(i, ::).t.toArray + val scores = Math.toArray(Math.row(logitsPerSentence, i)) val labelsAndScores = labels.zip(scores) // keep scores in descending order (largest first) diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayerFactory.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayerFactory.scala index 2136d49..4e71d18 100644 --- a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayerFactory.scala +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/LinearLayerFactory.scala @@ -1,6 +1,6 @@ package org.clulab.scala_transformers.encoder -import breeze.linalg.{DenseMatrix, DenseVector} +import org.clulab.scala_transformers.encoder.math.Mathematics.{Math, MathMatrix, MathColVector} import org.slf4j.{Logger, LoggerFactory} import scala.io.Source @@ -15,26 +15,26 @@ abstract class LinearLayerFactory(val linearLayerLayout: LinearLayerLayout) exte def dual: Boolean = sourceBoolean(newSource(linearLayerLayout.dual)) - def getWeights: DenseMatrix[Float] = { + def getWeights: MathMatrix = { val place = linearLayerLayout.weights if (!exists(place)) throw new RuntimeException(s"ERROR: you need at least a weights file for linear layer $name!") val values = sourceFloatMatrix(newSource(place)) // dimensions: rows = hidden state size, columns = labels' count - val weights = BreezeUtils.mkRowMatrix(values).t + val weights = Math.mkMatrixFromCols(values) - logger.info(s"Found weights with dimension ${weights.rows} x ${weights.cols}") + logger.info(s"Found weights with dimension ${Math.rows(weights)} x ${Math.cols(weights)}") weights } - def getBiasesOpt: Option[DenseVector[Float]] = { + def getBiasesOpt: Option[MathColVector] = { val place = linearLayerLayout.biases if (exists(place)) { val values = sourceFloatVector(newSource(place)) // the bias is a column vector - val biases = DenseVector(values) + val biases = Math.mkColVector(values) - logger.info(s"Found biases with dimension ${biases.length}") + logger.info(s"Found biases with dimension ${Math.length(biases)}") Some(biases) } else None diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/BreezeMath.scala.txt b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/BreezeMath.scala.txt new file mode 100644 index 0000000..5c25693 --- /dev/null +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/BreezeMath.scala.txt @@ -0,0 +1,97 @@ +package org.clulab.scala_transformers.encoder.math + +import ai.onnxruntime.OrtSession.Result +import breeze.linalg.{DenseMatrix, DenseVector, Transpose, `*`, argmax => BreezeArgmax} + +object BreezeMath extends Math { + type MathValue = Float + type MathRowMatrix = DenseMatrix[MathValue] + type MathColVector = DenseVector[MathValue] + type MathRowVector = Transpose[DenseVector[MathValue]] + + def fromResult(result: Result): Array[MathRowMatrix] = { + val arrays = result.get(0).getValue.asInstanceOf[Array[Array[Array[Float]]]] + val outputs = arrays.map(mkMatrixFromRows(_)) + + outputs + } + + def argmax(rowVector: MathRowVector): Int = { + BreezeArgmax(rowVector) + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, colVector: MathColVector): Unit = { + matrix(*, ::) :+= colVector + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, rowIndex: Int, rowVector: MathRowVector): Unit = { + matrix(rowIndex, ::) :+= rowVector + } + + def mul(leftMatrix: MathRowMatrix, rightMatrix: MathRowMatrix): MathRowMatrix = { +// println(s"${leftMatrix.rows} * ${leftMatrix.cols} x ${rightMatrix.rows} * ${leftMatrix.cols}") + leftMatrix * rightMatrix + } + + def rows(matrix: MathRowMatrix): Int = { + matrix.rows + } + + def cols(matrix: MathRowMatrix): Int = { + matrix.cols + } + + def length(colVector: MathColVector): Int = { + colVector.length + } + + def vertcat(leftColVector: MathColVector, rightColVector: MathColVector): MathColVector = { + DenseVector.vertcat(leftColVector, rightColVector) + } + + def zeros(rows: Int, cols: Int): MathRowMatrix = { + DenseMatrix.zeros[Float](rows, cols) + } + + def row(matrix: MathRowMatrix, index: Int): MathRowVector = { + matrix(index, ::) + } + + def horcat(leftRowVector: MathRowVector, rightRowVector: MathRowVector): MathRowVector = { + DenseVector.vertcat(leftRowVector.t, rightRowVector.t).t + } + + def toArray(rowVector: MathRowVector): Array[MathValue] = { + rowVector.t.toArray + } + + def get(rowVector: MathRowVector, index: Int): MathValue = { + rowVector(index) + } + + def mkMatrixFromRows(values: Array[Array[MathValue]]): MathRowMatrix = { + val rows = values.length + val cols = values.head.length + val denseMatrix = new DenseMatrix[Float](rows, cols) + + for (row <- 0 until rows) + for (col <- 0 until cols) + denseMatrix(row, col) = values(row)(col) + denseMatrix + } + + def mkMatrixFromCols(values: Array[Array[MathValue]]): MathRowMatrix = { + val rows = values.length + val cols = values.head.length + val denseMatrix = new DenseMatrix[Float](cols, rows) + + for (row <- 0 until rows) + for (col <- 0 until cols) + denseMatrix(col, row) = values(row)(col) + denseMatrix + } + + def mkColVector(values: Array[MathValue]): MathColVector = { + DenseVector(values) + } +} diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/CluMath.scala.txt b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/CluMath.scala.txt new file mode 100644 index 0000000..69ecd8f --- /dev/null +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/CluMath.scala.txt @@ -0,0 +1,166 @@ +package org.clulab.scala_transformers.encoder.math + +import ai.onnxruntime.OrtSession.Result + +object CluMath extends Math { + + // The use of a template seems to prevent the generation of an apply method + // so that the construction of CluMatrix requires new. However, the + // arguments are automatically converted into accessible vals. + case class CluMatrix[T](rowCount: Int, colCount: Int, data: Array[Array[T]]) + + sealed trait Orientation + + final class RowOrientation extends Orientation + final class ColOrientation extends Orientation + + // This <: prevents other kinds of CluVectors from being imagined. + // The U isn't used, but it does result in the CluRowMatrix and + // CluColMatrix being different types and not interchangeable. + case class CluVector[U <: Orientation, T](count: Int, data: Array[T]) + + type ColVector[T] = CluVector[ColOrientation, T] + type RowVector[T] = CluVector[RowOrientation, T] + + type MathValue = Float + + type CluRowMatrix = CluMatrix[MathValue] + type CluColVector = ColVector[MathValue] + type CluRowVector = RowVector[MathValue] + + type MathRowMatrix = CluRowMatrix + type MathColVector = CluColVector + type MathRowVector = CluRowVector + + def fromResult(result: Result): Array[MathRowMatrix] = { + val array = result.get(0).getValue.asInstanceOf[Array[Array[Array[Float]]]] + val outputs = array.map { array2d => + val rows = array2d.length + val cols = array2d.head.length + val matrix = new CluRowMatrix(rows, cols, array2d) + + matrix + } + + outputs + } + + def argmax(rowVector: MathRowVector): Int = { + val max = rowVector.data.max + val index = rowVector.data.indexOf(max) + + index + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, colVector: MathColVector): Unit = { + val colData = colVector.data + + matrix.data.foreach { row => + row.indices.foreach { colIndex => + row(colIndex) += colData(colIndex) + } + } + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, rowIndex: Int, rowVector: MathRowVector): Unit = { + val matrixData = matrix.data(rowIndex) + val vectorData = rowVector.data + + matrixData.indices.foreach { colIndex => + matrixData(colIndex) += vectorData(colIndex) + } + } + + def mul(leftMatrix: MathRowMatrix, rightMatrix: MathRowMatrix): MathRowMatrix = { + require(leftMatrix.colCount == rightMatrix.rowCount) + val rowCount = leftMatrix.rowCount + val colCount = rightMatrix.colCount + val leftData = leftMatrix.data + val rightData = rightMatrix.data + val data = Array.tabulate[MathValue](rowCount, colCount) { (rowIndex, colIndex) => + val leftRowData = leftData(rowIndex) + val sum = leftRowData.indices.foldLeft(0f) { case (sum, index) => + sum + leftRowData(index) * rightData(index)(colIndex) + } + + sum + } + + new CluRowMatrix(rowCount, colCount, data) + } + + def rows(matrix: MathRowMatrix): Int = { + matrix.rowCount + } + + def cols(matrix: MathRowMatrix): Int = { + matrix.colCount + } + + def length(colVector: MathColVector): Int = { + colVector.count + } + + def vertcat(leftColVector: MathColVector, rightColVector: MathColVector): MathColVector = { + val data = { + val data = new Array[MathValue](leftColVector.count + rightColVector.count) + + leftColVector.data.copyToArray(data, 0) + rightColVector.data.copyToArray(data, leftColVector.count) + data + } + + new CluColVector(data.length, data) + } + + def zeros(rows: Int, cols: Int): MathRowMatrix = { + val data = Array.fill[Array[MathValue]](rows) { new Array(cols) } + + new CluRowMatrix(rows, cols, data) + } + + def row(matrix: MathRowMatrix, index: Int): MathRowVector = { + new CluRowVector(matrix.colCount, matrix.data(index).clone) + } + + def horcat(leftRowVector: MathRowVector, rightRowVector: MathRowVector): MathRowVector = { + val data = { + val data = new Array[MathValue](leftRowVector.count + rightRowVector.count) + + leftRowVector.data.copyToArray(data, 0) + rightRowVector.data.copyToArray(data, leftRowVector.count) + data + } + + new CluRowVector(data.length, data) + } + + def toArray(rowVector: MathRowVector): Array[MathValue] = { + rowVector.data + } + + def get(rowVector: MathRowVector, index: Int): MathValue = { + rowVector.data(index) + } + + def mkMatrixFromRows(values: Array[Array[MathValue]]): MathRowMatrix = { + new CluRowMatrix(values.length, values.head.length, values) + } + + def mkMatrixFromCols(values: Array[Array[MathValue]]): MathRowMatrix = { + val rows = values.length + val cols = values.head.length + val data = Array.fill[Array[MathValue]](cols) { new Array[MathValue](rows) } + + values.indices.foreach { rowIndex => + values(rowIndex).indices.foreach { colIndex => + data(colIndex)(rowIndex) = values(rowIndex)(colIndex) + } + } + new CluRowMatrix(cols, rows, data) + } + + def mkColVector(values: Array[MathValue]): MathColVector = { + new CluColVector(values.length, values) + } +} diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/CommonsMath.scala.txt b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/CommonsMath.scala.txt new file mode 100644 index 0000000..b202edc --- /dev/null +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/CommonsMath.scala.txt @@ -0,0 +1,124 @@ +package org.clulab.scala_transformers.encoder.math + +import ai.onnxruntime.OrtSession.Result +import org.apache.commons.math3.linear.{Array2DRowRealMatrix, ArrayRealVector, DefaultRealMatrixChangingVisitor} + +object CommonsMath extends Math { + type MathValue = Float + type MathRowMatrix = Array2DRowRealMatrix + type MathColVector = ArrayRealVector + type MathRowVector = ArrayRealVector + + def fromResult(result: Result): Array[MathRowMatrix] = { + val array = result.get(0).getValue.asInstanceOf[Array[Array[Array[Float]]]] + val outputs = array.map { array2d => + val rows = array2d.length + val cols = array2d.head.length + val matrix = new Array2DRowRealMatrix(rows, cols) + + for (row <- 0 until rows) + for (col <- 0 until cols) + matrix.setEntry(row, col, array2d(row)(col).toDouble) + matrix + } + + outputs + } + + def argmax(rowVector: MathRowVector): Int = { + rowVector.getMaxIndex + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, colVector: MathColVector): Unit = { + val visitor = new DefaultRealMatrixChangingVisitor { + override def visit(row: Int, column: Int, value: Double): Double = { + value + colVector.getEntry(column) + } + } + + matrix.walkInRowOrder(visitor, 0, matrix.getRowDimension - 1, 0, matrix.getColumnDimension - 1) + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, rowIndex: Int, rowVector: MathRowVector): Unit = { + val visitor = new DefaultRealMatrixChangingVisitor { + override def visit(row: Int, column: Int, value: Double): Double = { + value + rowVector.getEntry(column) + } + } + + matrix.walkInRowOrder(visitor, rowIndex, rowIndex, 0, matrix.getColumnDimension - 1) + } + + def mul(leftMatrix: MathRowMatrix, rightMatrix: MathRowMatrix): MathRowMatrix = { + leftMatrix.multiply(rightMatrix) + } + + def rows(matrix: MathRowMatrix): Int = { + matrix.getRowDimension + } + + def cols(matrix: MathRowMatrix): Int = { + matrix.getColumnDimension + } + + def length(colVector: MathColVector): Int = { + colVector.getDimension + } + + def vertcat(leftColVector: MathColVector, rightColVector: MathColVector): MathColVector = { + new ArrayRealVector(leftColVector, rightColVector) + } + + def zeros(rows: Int, cols: Int): MathRowMatrix = { + new Array2DRowRealMatrix(rows, cols) + } + + def row(matrix: MathRowMatrix, index: Int): MathRowVector = { + new ArrayRealVector(matrix.getRow(index)) + } + + def horcat(leftRowVector: MathRowVector, rightRowVector: MathRowVector): MathRowVector = { + new ArrayRealVector(leftRowVector, rightRowVector) + } + + def toArray(rowVector: MathRowVector): Array[MathValue] = { + val doubleArray = rowVector.toArray + val array = doubleArray.map(_.toFloat) + + array + } + + def get(rowVector: MathRowVector, index: Int): MathValue = { + rowVector.getEntry(index).toFloat + } + + def mkMatrixFromRows(values: Array[Array[Float]]): MathRowMatrix = { + val rows = values.length + val cols = values.head.length + val matrix = new Array2DRowRealMatrix(rows, cols) + + for (row <- 0 until rows) + for (col <- 0 until cols) + matrix.setEntry(row, col, values(row)(col).toDouble) + matrix + } + + def mkMatrixFromCols(values: Array[Array[Float]]): MathRowMatrix = { + val rows = values.length + val cols = values.head.length + val matrix = new Array2DRowRealMatrix(cols, rows) + + for (row <- 0 until rows) + for (col <- 0 until cols) + matrix.setEntry(col, row, values(row)(col).toDouble) + matrix + } + + // How do we keep track that this is a column? + def mkColVector(values: Array[Float]): MathColVector = { + val doubles = Array.tabulate[Double](values.length) { index => + values(index).toDouble + } + new ArrayRealVector(doubles) + } +} diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/EjmlMath.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/EjmlMath.scala new file mode 100644 index 0000000..4348b0a --- /dev/null +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/EjmlMath.scala @@ -0,0 +1,152 @@ +package org.clulab.scala_transformers.encoder.math + +import ai.onnxruntime.OrtSession.Result +import org.ejml.data.FMatrixRMaj +import org.ejml.simple.SimpleMatrix + +object EjmlMath extends Math { + type MathValue = Float + type MathRowMatrix = FMatrixRMaj + type MathColVector = FMatrixRMaj + type MathRowVector = FMatrixRMaj + + protected def isRowVector(rowVector: MathRowVector): Boolean = rowVector.getNumRows == 1 + + protected def isColVector(colVector: MathColVector): Boolean = colVector.getNumCols == 1 + + def fromResult(result: Result): Array[MathRowMatrix] = { + val array = result.get(0).getValue.asInstanceOf[Array[Array[Array[Float]]]] + val outputs = array.map(new FMatrixRMaj(_)) + + outputs + } + + def argmax(rowVector: MathRowVector): Int = { + assert(isRowVector(rowVector)) + + var maxIndex = 0 + var maxValue = rowVector.get(maxIndex) + + 1.until(rowVector.getNumCols).foreach { index => + val value = rowVector.get(index) + + if (value > maxValue) { + maxValue = value + maxIndex = index + } + } + + maxIndex + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, colVector: MathColVector): Unit = { + assert(isColVector(colVector)) + 0.until(matrix.getNumRows) foreach { row => + 0.until(matrix.getNumCols) foreach { col => + val oldVal = matrix.get(row, col) + val newVal = oldVal + colVector.get(col, 0) + + matrix.set(row, col, newVal) + } + } + } + + def inplaceMatrixAddition(matrix: MathRowMatrix, rowIndex: Int, rowVector: MathRowVector): Unit = { + assert(isRowVector(rowVector)) + 0.until(matrix.getNumCols) foreach { col => + val oldVal = matrix.get(rowIndex, col) + val newVal = oldVal + rowVector.get(0, col) + + matrix.set(rowIndex, col, newVal) + } + } + + def mul(leftMatrix: MathRowMatrix, rightMatrix: MathRowMatrix): MathRowMatrix = { + val leftSimple = SimpleMatrix.wrap(leftMatrix) + val rightSimple = SimpleMatrix.wrap(rightMatrix) + val product = leftSimple.mult(rightSimple) + val matrix = product.getMatrix.asInstanceOf[FMatrixRMaj] + + matrix + } + + def rows(matrix: MathRowMatrix): Int = { + matrix.getNumRows + } + + def cols(matrix: MathRowMatrix): Int = { + matrix.getNumCols + } + + def length(colVector: MathColVector): Int = { + assert(isColVector(colVector)) + colVector.getNumRows + } + + def vertcat(leftColVector: MathColVector, rightColVector: MathColVector): MathColVector = { + assert(isColVector(leftColVector)) + assert(isColVector(rightColVector)) + val leftSimple = SimpleMatrix.wrap(leftColVector) + val rightSimple = SimpleMatrix.wrap(rightColVector) + val result = leftSimple.concatRows(rightSimple).getMatrix[FMatrixRMaj] + + assert(isColVector(result)) + result + } + + def zeros(rows: Int, cols: Int): MathRowMatrix = { + new FMatrixRMaj(rows, cols) + } + + def row(matrix: MathRowMatrix, index: Int): MathRowVector = { + val result = SimpleMatrix.wrap(matrix).rows(index, index + 1).getMatrix[FMatrixRMaj] + + assert(isRowVector(result)) + result + } + + def horcat(leftRowVector: MathRowVector, rightRowVector: MathRowVector): MathRowVector = { + assert(isRowVector(leftRowVector)) + assert(isRowVector(rightRowVector)) + val leftSimple = SimpleMatrix.wrap(leftRowVector) + val rightSimple = SimpleMatrix.wrap(rightRowVector) + val result = leftSimple.concatColumns(rightSimple).getMatrix[FMatrixRMaj] + + assert(isRowVector(result)) + result + } + + def toArray(rowVector: MathRowVector): Array[MathValue] = { + assert(isRowVector(rowVector)) + val result = rowVector.getData + + result + } + + def get(rowVector: MathRowVector, index: Int): MathValue = { + assert(isRowVector(rowVector)) + rowVector.get(index) + } + + def mkMatrixFromRows(values: Array[Array[MathValue]]): MathRowMatrix = { + new FMatrixRMaj(values) + } + + def mkMatrixFromCols(values: Array[Array[MathValue]]): MathRowMatrix = { + val rows = values.length + val cols = values.head.length + val matrix = new FMatrixRMaj(cols, rows) + + for (row <- 0 until rows) + for (col <- 0 until cols) + matrix.set(col, row, values(row)(col)) + matrix + } + + def mkColVector(values: Array[MathValue]): MathColVector = { + val result = new FMatrixRMaj(values) + + assert(isColVector(result)) + result + } +} diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/Math.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/Math.scala new file mode 100644 index 0000000..c968f98 --- /dev/null +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/Math.scala @@ -0,0 +1,31 @@ +package org.clulab.scala_transformers.encoder.math + +import ai.onnxruntime.OrtSession.Result + +trait Math { + type MathValue + type MathRowMatrix + type MathColVector + type MathRowVector + + def fromResult(result: Result): Array[MathRowMatrix] + def argmax(rowVector: MathRowVector): Int + def inplaceMatrixAddition(matrix: MathRowMatrix, colVector: MathColVector): Unit + def inplaceMatrixAddition(matrix: MathRowMatrix, rowIndex: Int, rowVector: MathRowVector): Unit +// def rowVectorAddition(leftRowVector: MathRowVector, rightRowVector: MathRowVector): MathRowVector + def mul(leftMatrix: MathRowMatrix, rightMatrix: MathRowMatrix): MathRowMatrix + def rows(matrix: MathRowMatrix): Int + def cols(matrix: MathRowMatrix): Int + def length(colVector: MathColVector): Int + def vertcat(leftColVector: MathColVector, rightColVector: MathColVector): MathColVector + def zeros(rows: Int, cols: Int): MathRowMatrix + def row(matrix: MathRowMatrix, index: Int): MathRowVector + def horcat(leftRowVector: MathRowVector, rightRowVector: MathRowVector): MathRowVector + def toArray(rowVector: MathRowVector): Array[MathValue] + def get(rowVector: MathRowVector, index: Int): MathValue + def mkMatrixFromRows(values: Array[Array[MathValue]]): MathRowMatrix + // For this, the array is specified in column-major order, + // but it should be converted to the normal representation. + def mkMatrixFromCols(values: Array[Array[MathValue]]): MathRowMatrix + def mkColVector(values: Array[MathValue]): MathColVector +} diff --git a/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/Mathematics.scala b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/Mathematics.scala new file mode 100644 index 0000000..0aeb62d --- /dev/null +++ b/encoder/src/main/scala/org/clulab/scala_transformers/encoder/math/Mathematics.scala @@ -0,0 +1,13 @@ +package org.clulab.scala_transformers.encoder.math + +object Mathematics { + // Pick one of these. + // val Math = BreezeMath + val Math = EjmlMath + // val Math = CommonsMath + // val Math = CluMath + + type MathMatrix = Math.MathRowMatrix + type MathColVector = Math.MathColVector + type MathRowVector = Math.MathRowVector +} diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/TestBreeze.scala b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/TestBreeze.scala.txt similarity index 100% rename from encoder/src/test/scala/org/clulab/scala_transformers/encoder/TestBreeze.scala rename to encoder/src/test/scala/org/clulab/scala_transformers/encoder/TestBreeze.scala.txt diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/BreezeMathTest.scala.txt b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/BreezeMathTest.scala.txt new file mode 100644 index 0000000..a0dc6a7 --- /dev/null +++ b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/BreezeMathTest.scala.txt @@ -0,0 +1,242 @@ +package org.clulab.scala_transformers.encoder.math + +import org.clulab.transformers.test.Test + +class BreezeMathTest extends Test { + + def mkRowVector(values: Array[Float]): BreezeMath.MathRowVector = { + BreezeMath.mkColVector(values).t + } + + behavior of "BreezeMath" + + it should "argmax" in { + val values = Array(1f, 3f, 2f) + val vector = mkRowVector(values) + val expectedResult = 1 + val actualResult = BreezeMath.argmax(vector) + + actualResult should be (expectedResult) + } + + it should "inplaceMatrixAddition2" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = BreezeMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = BreezeMath.mkColVector(vectorValues) + val expectedResult = Array( + Array(2f, 4f, 6f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + BreezeMath.inplaceMatrixAddition(matrix, vector) + expectedResult.zipWithIndex.foreach { case (expecteedValues, rowIndex) => + expecteedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult(rowIndex, colIndex) should be (expectedValue) + } + } + } + + it should "inplaceMatrixAddition3" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = BreezeMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = Array( + Array(1f, 2f, 3f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + BreezeMath.inplaceMatrixAddition(matrix, 1, vector) + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult(rowIndex, colIndex) should be(expectedValue) + } + } + } + + it should "mul" in { + val leftMatrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val leftMatrix = BreezeMath.mkMatrixFromRows(leftMatrixValues) + val rightMatrixValues = Array( + Array(1f, 2f), + Array(3f, 2f), + Array(4f, 6f) + ) + val rightMatrix = BreezeMath.mkMatrixFromRows(rightMatrixValues) + val expectedResult = Array( + Array(19f, 24f), + Array(38f, 48f) + ) + val actualResult = BreezeMath.mul(leftMatrix, rightMatrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult(rowIndex, colIndex) should be(expectedValue) + } + } + } + + it should "rows" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = BreezeMath.mkMatrixFromRows(matrixValues) + val expectedResult = 2 + val actualResult = BreezeMath.rows(matrix) + + actualResult should be (expectedResult) + } + + it should "cols" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = BreezeMath.mkMatrixFromRows(matrixValues) + val expectedResult = 3 + val actualResult = BreezeMath.cols(matrix) + + actualResult should be(expectedResult) + } + + it should "length" in { + val values = Array(1f, 2f, 3f) + val vector = BreezeMath.mkColVector(values) + val expectedResult = 3 + val actualResult = BreezeMath.length(vector) + + actualResult should be(expectedResult) + } + + it should "vertcat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = BreezeMath.mkColVector(leftVectorValues) + val rightVector = BreezeMath.mkColVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = BreezeMath.vertcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be(expectedValue) + } + } + + it should "zeros" in { + val matrixValues = Array( + Array(0f, 0f, 0f), + Array(0f, 0f, 0f) + ) + val expectedResult = matrixValues + val actualResult = BreezeMath.zeros(2, 3) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult(rowIndex, colIndex) should be (expectedValue) + } + } + } + + it should "row" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = BreezeMath.mkMatrixFromRows(matrixValues) + val expectedResult = Array(2f, 4f, 6f) + val actualResult = BreezeMath.row(matrix, 1) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be(expectedValue) + } + } + + it should "cat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = mkRowVector(leftVectorValues) + val rightVector = mkRowVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = BreezeMath.horcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be(expectedValue) + } + } + + it should "toArray" in { + val values = Array(1f, 2f, 3f) + val vector = mkRowVector(values) + val expectedResult = values + val actualResult = BreezeMath.toArray(vector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be (expectedValue) + } + } + + it should "get" in { + val values = Array(1f, 2f, 3f) + val vector = mkRowVector(values) + val expectedResult = 2f + val actualResult = BreezeMath.get(vector, 1) + + actualResult should be (expectedResult) + } + + it should "mkRowMatrix" in { + val matrix = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = matrix + val actualResult = BreezeMath.mkMatrixFromRows(matrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult(rowIndex, colIndex) should be (expectedValue) + } + } + } + + it should "mkColMatrix" in { + val matrix = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = Array( + Array(1f, 2f), + Array(2f, 4f), + Array(3f, 6f) + ) + val actualResult = BreezeMath.mkMatrixFromCols(matrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult(rowIndex, colIndex) should be (expectedValue) + } + } + } + + it should "mkVector" in { + val vectorValues = Array(1f, 2f, 3f) + val expectedResult = vectorValues + val actualResult = BreezeMath.mkColVector(vectorValues) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be (expectedValue) + } + } +} diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/CluMathTest.scala.txt b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/CluMathTest.scala.txt new file mode 100644 index 0000000..4c1e27c --- /dev/null +++ b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/CluMathTest.scala.txt @@ -0,0 +1,242 @@ +package org.clulab.scala_transformers.encoder.math + +import org.clulab.transformers.test.Test + +class CluMathTest extends Test { + + def mkRowVector(values: Array[Float]): CluMath.CluRowVector = { + new CluMath.CluRowVector(values.length, values) + } + + behavior of "CluMath" + + it should "argmax" in { + val values = Array(1f, 3f, 2f) + val vector = mkRowVector(values) + val expectedResult = 1 + val actualResult = CluMath.argmax(vector) + + actualResult should be (expectedResult) + } + + it should "inplaceMatrixAddition2" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CluMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = CluMath.mkColVector(vectorValues) + val expectedResult = Array( + Array(2f, 4f, 6f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + CluMath.inplaceMatrixAddition(matrix, vector) + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.data(rowIndex)(colIndex) should be (expectedValue) + } + } + } + + it should "inplaceMatrixAddition3" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CluMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = Array( + Array(1f, 2f, 3f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + CluMath.inplaceMatrixAddition(matrix, 1, vector) + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.data(rowIndex)(colIndex) should be(expectedValue) + } + } + } + + it should "mul" in { + val leftMatrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val leftMatrix = CluMath.mkMatrixFromRows(leftMatrixValues) + val rightMatrixValues = Array( + Array(1f, 2f), + Array(3f, 2f), + Array(4f, 6f) + ) + val rightMatrix = CluMath.mkMatrixFromRows(rightMatrixValues) + val expectedResult = Array( + Array(19f, 24f), + Array(38f, 48f) + ) + val actualResult = CluMath.mul(leftMatrix, rightMatrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.data(rowIndex)(colIndex) should be(expectedValue) + } + } + } + + it should "rows" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CluMath.mkMatrixFromRows(matrixValues) + val expectedResult = 2 + val actualResult = CluMath.rows(matrix) + + actualResult should be (expectedResult) + } + + it should "cols" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CluMath.mkMatrixFromRows(matrixValues) + val expectedResult = 3 + val actualResult = CluMath.cols(matrix) + + actualResult should be(expectedResult) + } + + it should "length" in { + val values = Array(1f, 2f, 3f) + val vector = CluMath.mkColVector(values) + val expectedResult = 3 + val actualResult = CluMath.length(vector) + + actualResult should be(expectedResult) + } + + it should "vertcat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = CluMath.mkColVector(leftVectorValues) + val rightVector = CluMath.mkColVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = CluMath.vertcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.data(index) should be(expectedValue) + } + } + + it should "zeros" in { + val matrixValues = Array( + Array(0f, 0f, 0f), + Array(0f, 0f, 0f) + ) + val expectedResult = matrixValues + val actualResult = CluMath.zeros(2, 3) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.data(rowIndex)(colIndex) should be (expectedValue) + } + } + } + + it should "row" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CluMath.mkMatrixFromRows(matrixValues) + val expectedResult = Array(2f, 4f, 6f) + val actualResult = CluMath.row(matrix, 1) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.data(index) should be(expectedValue) + } + } + + it should "cat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = mkRowVector(leftVectorValues) + val rightVector = mkRowVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = CluMath.horcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.data(index) should be(expectedValue) + } + } + + it should "toArray" in { + val values = Array(1f, 2f, 3f) + val vector = mkRowVector(values) + val expectedResult = values + val actualResult = CluMath.toArray(vector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be (expectedValue) + } + } + + it should "get" in { + val values = Array(1f, 2f, 3f) + val vector = mkRowVector(values) + val expectedResult = 2f + val actualResult = CluMath.get(vector, 1) + + actualResult should be (expectedResult) + } + + it should "mkRowMatrix" in { + val matrix = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = matrix + val actualResult = CluMath.mkMatrixFromRows(matrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.data(rowIndex)(colIndex) should be (expectedValue) + } + } + } + + it should "mkColMatrix" in { + val matrix = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = Array( + Array(1f, 2f), + Array(2f, 4f), + Array(3f, 6f) + ) + val actualResult = CluMath.mkMatrixFromCols(matrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.data(rowIndex)(colIndex) should be (expectedValue) + } + } + } + + it should "mkVector" in { + val vectorValues = Array(1f, 2f, 3f) + val expectedResult = vectorValues + val actualResult = CluMath.mkColVector(expectedResult) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.data(index) should be (expectedValue) + } + } +} diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/CommonsMathTest.scala.txt b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/CommonsMathTest.scala.txt new file mode 100644 index 0000000..db94bfc --- /dev/null +++ b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/CommonsMathTest.scala.txt @@ -0,0 +1,243 @@ +package org.clulab.scala_transformers.encoder.math + +import org.apache.commons.math3.linear.ArrayRealVector +import org.clulab.transformers.test.Test + +class CommonsMathTest extends Test { + + def mkRowVector(values: Array[Float]): CommonsMath.MathColVector = { + CommonsMath.mkColVector(values) + } + + behavior of "CommonsMath" + + it should "argmax" in { + val vectorValues = Array(1f, 3f, 2f) + val vector = new ArrayRealVector(vectorValues.map(_.toDouble)) + val expectedResult = 1 + val actualResult = CommonsMath.argmax(vector) + + actualResult should be (expectedResult) + } + + it should "inplaceMatrixAddition2" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CommonsMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = CommonsMath.mkColVector(vectorValues) + val expectedResult = Array( + Array(2f, 4f, 6f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + CommonsMath.inplaceMatrixAddition(matrix, vector) + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.getEntry(rowIndex, colIndex).toFloat should be (expectedValue) + } + } + } + + it should "inplaceMatrixAddition3" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CommonsMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = Array( + Array(1f, 2f, 3f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + CommonsMath.inplaceMatrixAddition(matrix, 1, vector) + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.getEntry(rowIndex, colIndex).toFloat should be(expectedValue) + } + } + } + + it should "mul" in { + val leftMatrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val leftMatrix = CommonsMath.mkMatrixFromRows(leftMatrixValues) + val rightMatrixValues = Array( + Array(1f, 2f), + Array(3f, 2f), + Array(4f, 6f) + ) + val rightMatrix = CommonsMath.mkMatrixFromRows(rightMatrixValues) + val expectedResult = Array( + Array(19f, 24f), + Array(38f, 48f) + ) + val actualResult = CommonsMath.mul(leftMatrix, rightMatrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.getEntry(rowIndex, colIndex).toFloat should be(expectedValue) + } + } + } + + it should "rows" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CommonsMath.mkMatrixFromRows(matrixValues) + val expectedResult = 2 + val actualResult = CommonsMath.rows(matrix) + + actualResult should be (expectedResult) + } + + it should "cols" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CommonsMath.mkMatrixFromRows(matrixValues) + val expectedResult = 3 + val actualResult = CommonsMath.cols(matrix) + + actualResult should be(expectedResult) + } + + it should "length" in { + val vectorValues = Array(1f, 2f, 3f) + val vector = CommonsMath.mkColVector(vectorValues) + val expectedResult = 3 + val actualResult = CommonsMath.length(vector) + + actualResult should be(expectedResult) + } + + it should "vertcat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = CommonsMath.mkColVector(leftVectorValues) + val rightVector = CommonsMath.mkColVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = CommonsMath.vertcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.getEntry(index).toFloat should be(expectedValue) + } + } + + it should "zeros" in { + val matrixValues = Array( + Array(0f, 0f, 0f), + Array(0f, 0f, 0f) + ) + val expectedResult = matrixValues + val actualResult = CommonsMath.zeros(2, 3) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.getEntry(rowIndex, colIndex).toFloat should be (expectedValue) + } + } + } + + it should "row" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = CommonsMath.mkMatrixFromRows(matrixValues) + val expectedResult = Array(2f, 4f, 6f) + val actualResult = CommonsMath.row(matrix, 1) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.getEntry(index).toFloat should be(expectedValue) + } + } + + it should "cat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = mkRowVector(leftVectorValues) + val rightVector = mkRowVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = CommonsMath.horcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.getEntry(index).toFloat should be(expectedValue) + } + } + + it should "toArray" in { + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = vectorValues + val actualResult = CommonsMath.toArray(vector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be (expectedValue) + } + } + + it should "get" in { + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = 2f + val actualResult = CommonsMath.get(vector, 1).toFloat + + actualResult should be (expectedResult) + } + + it should "mkRowMatrix" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = matrixValues + val actualResult = CommonsMath.mkMatrixFromRows(matrixValues) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.getEntry(rowIndex, colIndex).toFloat should be (expectedValue) + } + } + } + + it should "mkColMatrix" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = Array( + Array(1f, 2f), + Array(2f, 4f), + Array(3f, 6f) + ) + val actualResult = CommonsMath.mkMatrixFromCols(matrixValues) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.getEntry(rowIndex, colIndex).toFloat should be(expectedValue) + } + } + } + + it should "mkVector" in { + val vectorValues = Array(1f, 2f, 3f) + val expectedResult = vectorValues + val actualResult = CommonsMath.mkColVector(expectedResult) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.getEntry(index).toFloat should be (expectedValue) + } + } +} diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/EjmlMathTest.scala b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/EjmlMathTest.scala new file mode 100644 index 0000000..b390138 --- /dev/null +++ b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/EjmlMathTest.scala @@ -0,0 +1,247 @@ +package org.clulab.scala_transformers.encoder.math + +import org.clulab.transformers.test.Test +import org.ejml.data.FMatrixRMaj +import org.ejml.simple.SimpleMatrix + +class EjmlMathTest extends Test { + + def mkRowVector(values: Array[Float]): EjmlMath.MathRowVector = { + val matrix = new FMatrixRMaj(values) + val result = SimpleMatrix.wrap(matrix).transpose().getMatrix[FMatrixRMaj] + + result + } + + behavior of "EjmlMath" + + it should "argmax" in { + val vectorValues = Array(1f, 3f, 2f) + val vector = mkRowVector(vectorValues) + val expectedResult = 1 + val actualResult = EjmlMath.argmax(vector) + + actualResult should be (expectedResult) + } + + it should "inplaceMatrixAddition2" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = EjmlMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = EjmlMath.mkColVector(vectorValues) + val expectedResult = Array( + Array(2f, 4f, 6f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + EjmlMath.inplaceMatrixAddition(matrix, vector) + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.get(rowIndex, colIndex) should be (expectedValue) + } + } + } + + it should "inplaceMatrixAddition3" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = EjmlMath.mkMatrixFromRows(matrixValues) + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = Array( + Array(1f, 2f, 3f), + Array(3f, 6f, 9f) + ) + val actualResult = matrix + + EjmlMath.inplaceMatrixAddition(matrix, 1, vector) + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.get(rowIndex, colIndex) should be(expectedValue) + } + } + } + + it should "mul" in { + val leftMatrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val leftMatrix = EjmlMath.mkMatrixFromRows(leftMatrixValues) + val rightMatrixValues = Array( + Array(1f, 2f), + Array(3f, 2f), + Array(4f, 6f) + ) + val rightMatrix = EjmlMath.mkMatrixFromRows(rightMatrixValues) + val expectedResult = Array( + Array(19f, 24f), + Array(38f, 48f) + ) + val actualResult = EjmlMath.mul(leftMatrix, rightMatrix) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.get(rowIndex, colIndex) should be(expectedValue) + } + } + } + + it should "rows" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = EjmlMath.mkMatrixFromRows(matrixValues) + val expectedResult = 2 + val actualResult = EjmlMath.rows(matrix) + + actualResult should be (expectedResult) + } + + it should "cols" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = EjmlMath.mkMatrixFromRows(matrixValues) + val expectedResult = 3 + val actualResult = EjmlMath.cols(matrix) + + actualResult should be(expectedResult) + } + + it should "length" in { + val vectorValues = Array(1f, 2f, 3f) + val vector = EjmlMath.mkColVector(vectorValues) + val expectedResult = 3 + val actualResult = EjmlMath.length(vector) + + actualResult should be(expectedResult) + } + + it should "vertcat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = EjmlMath.mkColVector(leftVectorValues) + val rightVector = EjmlMath.mkColVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = EjmlMath.vertcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.get(index) should be(expectedValue) + } + } + + it should "zeros" in { + val matrixValues = Array( + Array(0f, 0f, 0f), + Array(0f, 0f, 0f) + ) + val expectedResult = matrixValues + val actualResult = EjmlMath.zeros(2, 3) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.get(rowIndex, colIndex) should be (expectedValue) + } + } + } + + it should "row" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val matrix = EjmlMath.mkMatrixFromRows(matrixValues) + val expectedResult = Array(2f, 4f, 6f) + val actualResult = EjmlMath.row(matrix, 1) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.get(index) should be(expectedValue) + } + } + + it should "cat" in { + val leftVectorValues = Array(1f, 2f, 3f) + val rightVectorValues = Array(2f, 4f, 6f) + val leftVector = mkRowVector(leftVectorValues) + val rightVector = mkRowVector(rightVectorValues) + val expectedResult = Array(1f, 2f, 3f, 2f, 4f, 6f) + val actualResult = EjmlMath.horcat(leftVector, rightVector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.get(index) should be(expectedValue) + } + } + + it should "toArray" in { + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = vectorValues + val actualResult = EjmlMath.toArray(vector) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult(index) should be (expectedValue) + } + } + + it should "get" in { + val vectorValues = Array(1f, 2f, 3f) + val vector = mkRowVector(vectorValues) + val expectedResult = 2f + val actualResult = EjmlMath.get(vector, 1) + + actualResult should be (expectedResult) + } + + it should "mkRowMatrix" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = matrixValues + val actualResult = EjmlMath.mkMatrixFromRows(matrixValues) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.get(rowIndex, colIndex) should be (expectedValue) + } + } + } + + it should "mkColMatrix" in { + val matrixValues = Array( + Array(1f, 2f, 3f), + Array(2f, 4f, 6f) + ) + val expectedResult = Array( + Array(1f, 2f), + Array(2f, 4f), + Array(3f, 6f) + ) + val actualResult = EjmlMath.mkMatrixFromCols(matrixValues) + + expectedResult.zipWithIndex.foreach { case (expectedValues, rowIndex) => + expectedValues.zipWithIndex.foreach { case (expectedValue, colIndex) => + actualResult.get(rowIndex, colIndex) should be(expectedValue) + } + } + } + + it should "mkVector" in { + val vectorValues = Array(1f, 2f, 3f) + val expectedResult = vectorValues + val actualResult = EjmlMath.mkColVector(expectedResult) + + expectedResult.zipWithIndex.foreach { case (expectedValue, index) => + actualResult.get(index) should be (expectedValue) + } + } +} diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/MathTest.scala.txt b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/MathTest.scala.txt new file mode 100644 index 0000000..4f63bfb --- /dev/null +++ b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/math/MathTest.scala.txt @@ -0,0 +1,72 @@ +package org.clulab.scala_transformers.encoder.math + +import org.clulab.transformers.test.Test + +class MathTest extends Test { + + behavior of "Math" + + it should "argmax" in { + + } + + it should "inplaceMatrixAddition2" in { + + } + + it should "inplaceMatrixAddition3" in { + + } + + it should "mul" in { + + } + + it should "rows" in { + + } + + it should "cols" in { + + } + + it should "length" in { + + } + + it should "t" in { + + } + + it should "vertcat" in { + + } + + it should "zeros" in { + + } + + it should "row" in { + + } + + it should "cat" in { + + } + + it should "toArray" in { + + } + + it should "get" in { + + } + + it should "mkRowMatrix" in { + + } + + it should "mkVector" in { + + } +} diff --git a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/timer/TokenClassifierTimerApp.scala b/encoder/src/test/scala/org/clulab/scala_transformers/encoder/timer/TokenClassifierTimerApp.scala deleted file mode 100644 index ebe0d72..0000000 --- a/encoder/src/test/scala/org/clulab/scala_transformers/encoder/timer/TokenClassifierTimerApp.scala +++ /dev/null @@ -1,108 +0,0 @@ -package org.clulab.scala_transformers.encoder.timer - -import org.clulab.scala_transformers.encoder.TokenClassifier -import org.clulab.scala_transformers.tokenizer.LongTokenization - -import scala.io.Source - -object TokenClassifierTimerApp extends App { - val fileName = args.lift(0).getOrElse("../sentences.txt") - - class TimedTokenClassifier(tokenClassifier: TokenClassifier) extends TokenClassifier( - tokenClassifier.encoder, tokenClassifier.maxTokens, tokenClassifier.tasks, tokenClassifier.tokenizer - ) { - val tokenizeTimer = Timers.getOrNew("Tokenizer") - val forwardTimer = Timers.getOrNew("Encoder.forward") - val predictTimers = tokenClassifier.tasks.indices.map { index => - Timers.getOrNew(s"Encoder.predict $index") - } - - override def predict(words: Seq[String], headTaskName: String = "Deps Head"): Array[Array[String]] = { - val headTaskIndexOpt = { - val headTaskIndex = tasks.indexWhere { task => task.name == headTaskName && !task.dual } - - if (headTaskIndex >= 0) Some(headTaskIndex) - else None - } - - val tokenization = tokenizeTimer.time { - LongTokenization(tokenizer.tokenize(words.toArray)) - } - - val encOutput = forwardTimer.time { - val encOutput = encoder.forward(tokenization.tokenIds) - - encOutput - } - - val notDualTokenAndWordLabels = tasks.zipWithIndex.map { case (task, index) => - if (!task.dual) { - val tokenAndWordLabels = predictTimers(index).time { - val tokenLabels = task.predict(encOutput, None, None) - val wordLabels = TokenClassifier.mapTokenLabelsToWords(tokenLabels, tokenization.wordIds) - - (tokenLabels, wordLabels) - } - - Some(tokenAndWordLabels) - } - else None - } - - val dualTokenAndWordLabels = if (headTaskIndexOpt.isDefined) { - val headsOpt = Some(notDualTokenAndWordLabels(headTaskIndexOpt.get).get._1.map { sth => sth.toInt }) - val masksOpt = Some(TokenClassifier.mkTokenMask(tokenization.wordIds)) - val dualTokenAndWordLabels = tasks.zipWithIndex.map { case (task, index) => - if (task.dual) { - val tokenAndWordLabels = predictTimers(index).time { - val tokenLabels = task.predict(encOutput, headsOpt, masksOpt) - val wordLabels = TokenClassifier.mapTokenLabelsToWords(tokenLabels, tokenization.wordIds) - - (tokenLabels, wordLabels) - } - - Some(tokenAndWordLabels) - } - else None - } - - dualTokenAndWordLabels - } - else - tasks.map { _ => None: Option[(Array[String], Array[String])] } - - val wordLabels = notDualTokenAndWordLabels.zip(dualTokenAndWordLabels).map { case (notDualTokenAndWordLabels, dualTokenAndWordLabels) => - if (notDualTokenAndWordLabels.isDefined) notDualTokenAndWordLabels.get._2 - else if (dualTokenAndWordLabels.isDefined) dualTokenAndWordLabels.get._2 - else throw new RuntimeException("Some task was unpredicted.") - } - - wordLabels - } - } - - val tokenClassifier = new TimedTokenClassifier(TokenClassifier.fromFiles("../roberta-base-mtl/avg_export")) - val lines = { - val source = Source.fromFile(fileName) - val lines = source.getLines().toArray - - source.close - lines - } - val elapsedTimer = Timers.getOrNew("Elapsed") - - elapsedTimer.time { - lines.zipWithIndex/*.par*/.foreach { case (line, index) => - println(s"$index $line") - if (index != 1382) { - val words = line.split(" ").toSeq - - // println(s"Words: ${words.mkString(", ")}") - val allLabels = tokenClassifier.predict(words) - // for (labels <- allLabels) - // println(s"Labels: ${labels.mkString(", ")}") - } - } - } - Timers.summarize() -} diff --git a/project/plugins.sbt b/project/plugins.sbt index a4987ee..a650594 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,4 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2-1") // up to 1.1.2-1 * addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3") // up to 3.9.6 * addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.13") // up to 1.0.13 +addSbtPlugin("io.get-coursier" % "sbt-shading" % "2.1.3") // up to 2.1.3 // * Held back out of an abundance of caution. diff --git a/version.sbt b/version.sbt index 7338ce7..a3c3a82 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.5.1-SNAPSHOT" +version in ThisBuild := "0.6.2"