SBT:如何将类的实例打包为JAR?

时间:2017-11-08 16:21:24

标签: java scala jar sbt sbt-assembly

我的代码基本上是这样的:

class FoodTrainer(images: S3Path) { // data is >100GB file living in S3
  def train(): FoodClassifier       // Very expensive - takes ~5 hours!
}

class FoodClassifier {          // Light-weight API class
  def isHotDog(input: Image): Boolean
}

我希望在JAR-assembly(sbt assembly)时间,调用val classifier = new FoodTrainer(s3Dir).train()并发布具有classifier实例的JAR,该实例可立即供下游库用户使用。

最简单的方法是什么?对此有哪些既定范式?我知道它在ML项目中是一个相当普遍的成语,用于发布经过训练的模型,例如http://nlp.stanford.edu/software/stanford-corenlp-models-current.jar

如何使用sbt assembly执行此操作,而不必将大型模型类或数据文件签入我的版本控制中?

4 个答案:

答案 0 :(得分:3)

您应该将训练产生的数据序列化到自己的文件中。然后,您可以将此数据文件打包到JAR中。您的生产代码打开文件并读取它而不是运行训练算法。

答案 1 :(得分:3)

步骤如下。

在构建的资源生成阶段:

  1. 在构建的资源生成阶段生成模型。
  2. 将模型的内容序列化到托管资源文件夹中的文件。
    resourceGenerators in Compile += Def.task {
      val classifier = new FoodTrainer(s3Dir).train()
      val contents = FoodClassifier.serialize(classifier)
      val file = (resourceManaged in Compile).value / "mypackage" / "food-classifier.model"
      IO.write(file, contents)
      Seq(file)
    }.taskValue
    
  3. 该资源将自动包含在jar文件中,并且不会出现在源树中。
  4. 要加载模型,只需添加读取资源的代码并解析模型。
    object FoodClassifierModel {
      lazy val classifier = readResource("/mypackage/food-classifier.model")
      def readResource(resourceName: String): FoodClassifier = {
        val stream = getClass.getResourceAsStream(resourceName)
        val lines = scala.io.Source.fromInputStream( stream ).getLines
        val contents = lines.mkString("\n")
        FoodClassifier.parse(contents)
      }
    }
    object FoodClassifier {
      def parse(content: String): FoodClassifier
      def serialize(classfier: FoodClassifier): String
    }
    
  5. 当然,由于您的数据相当大,您需要使用流式序列化器和解析器来不会超载Java堆空间。以上只是展示了如何在构建时打包资源。

    请参阅http://www.scala-sbt.org/1.x/docs/Howto-Generating-Files.html

答案 2 :(得分:0)

好的,我设法做到了:

  1. 将食物训练师模块分成两个独立的SBT子模块:food-trainerfood-model。前者仅在编译时调用以创建模型并序列化为后者的生成资源。后者用作简单的工厂对象,用于从序列化版本实例化模型。每个下游项目仅依赖于此food-model子模块。

  2. food-trainer包含所有代码的大部分内容,并且有一个可以序列化FoodModel的主要方法:

    object FoodTrainer {
      def main(args Array[String]): Unit = {
        val input = args(0)
        val outputDir = args(1)
        val model: FoodModel = new FoodTrainer(input).train() 
        val out = new ObjectOutputStream(new File(outputDir + "/model.bin"))
        out.writeObject(model)
      }
    }
    
  3. build.sbt中添加编译时任务以生成食物训练模块:

    lazy val foodTrainer = (project in file("food-trainer"))
    
    lazy val foodModel = (project in file("food-model"))
      .dependsOn(foodTrainer)
      .settings(    
         resourceGenerators in Compile += Def.task {
           val log = streams.value.log
           val dest = (resourceManaged in Compile).value   
           IO.createDirectory(dest)
           runModuleMain(
             cmd = s"com.foo.bar.FoodTrainer $pathToImages ${dest.getAbsolutePath}",
             cp = (fullClasspath in Runtime in foodTrainer).value.files,
             log = log
           )             
          Seq(dest / "model.bin")
        }
    
    def runModuleMain(cmd: String, cp: Seq[File], log: Logger): Unit = {
      log.info(s"Running $cmd")
      val opt = ForkOptions(bootJars = cp, outputStrategy = Some(LoggedOutput(log)))
      val res = Fork.scala(config = opt, arguments = cmd.split(' '))
      require(res == 0, s"$cmd exited with code $res")
    }
    
  4. 现在,在food-model模块中,你有类似的内容:

    object FoodModel {
      lazy val model: FoodModel =
        new ObjectInputStream(getClass.getResourceAsStream("/model.bin").readObject().asInstanceOf[FoodModel])
    }
    
  5. 现在,每个下游项目仅依赖于food-model,只使用FoodModel.model。我们得益于:

    1. 这是在运行时从JAR快速静态加载的 打包资源
    2. 无需在运行时训练模型(非常 昂贵的)
    3. 无需在您的版本中签入模型 控制(再次二进制模型非常大) - 它只包装到你的 JAR
    4. 无需将FoodTrainerFoodModel分开 将软件包打包到他们自己的JAR中(我们现在很难在内部部署它们) - 相反,我们只是将它们保持在同一个状态 项目,但不同的子模块被打包到一个JAR。

答案 3 :(得分:-1)

这是一个想法,将您的模型放在一个资源文件夹中,该文件夹被添加到jar程序集中。我认为如果它在该文件夹中,所有罐子都会与您的模型一起分发。 Lmk怎么样,欢呼!

检查此信息以便从资源中读取:

https://www.mkyong.com/java/java-read-a-file-from-resources-folder/

它在Java中,但你仍然可以在Scala中使用api。