[英]SBT: How to package an instance of a class as a JAR?
我的代码基本上是这样的:
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
执行此操作, sbt assembly
大型模型类或数据文件签入到我的版本控制中?
您应该将训练产生的数据序列化到自己的文件中。 然后,您可以将此数据文件打包到JAR中。 您的生产代码打开文件并读取它而不是运行训练算法。
步骤如下。
在构建的资源生成阶段:
\n Compile中的resourceGenerators + = Def.task {\n val classifier = new FoodTrainer(s3Dir).train()\n val contents = FoodClassifier.serialize(分类器)\n val file =(编译中的resourceManaged).value /“mypackage”/“food-classifier.model”\n IO.write(文件,内容)\n SEQ(文件)\n } .taskValue\n
jar
文件中,并且不会出现在源树中。 \n object FoodClassifierModel {\n lazy val classifier = readResource(“/ mypackage / food-classifier.model”)\n def readResource(resourceName:String):FoodClassifier = {\n val stream = getClass.getResourceAsStream(resourceName)\n val lines = scala.io.Source.fromInputStream(stream).getLines\n val contents = lines.mkString(“\\ n”)\n FoodClassifier.parse(内容)\n }\n }\n 对象FoodClassifier {\n def parse(content:String):FoodClassifier\n def serialize(classfier:FoodClassifier):String\n }\n
当然,由于您的数据相当大,您需要使用流式序列化器和解析器来不会超载Java堆空间。 以上只是展示了如何在构建时打包资源。
见http://www.scala-sbt.org/1.x/docs/Howto-Generating-Files.html
好的,我设法做到了:
将食物训练模块分成两个独立的SBT子模块: food-trainer
和food-model
。 前者仅在编译时调用以创建模型并序列化为后者的生成资源 。 后者用作简单的工厂对象,用于从序列化版本实例化模型。 每个下游项目仅依赖于该food-model
子模块。
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) } }
添加编译时任务以在build.sbt
生成food trainer模块:
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") }
现在在你的food-model
模块中,你有这样的东西:
object FoodModel { lazy val model: FoodModel = new ObjectInputStream(getClass.getResourceAsStream("/model.bin").readObject().asInstanceOf[FoodModel]) }
现在,每个下游项目仅依赖于food-model
并且只使用FoodModel.model
。 我们得益于:
FoodTrainer
和FoodModel
软件包分离到他们自己的JAR中(我们现在FoodModel
在内部部署它们) - 相反,我们只是将它们保存在同一个项目中,但不同的子模块会被打包到一个JAR中。 这是一个想法,将您的模型放在一个资源文件夹中,该文件夹被添加到jar程序集中。 我认为如果它在该文件夹中,所有罐子都会与您的模型一起分发。 Lmk怎么样,欢呼!
检查一下从资源中读取:
https://www.mkyong.com/java/java-read-a-file-from-resources-folder/
它是用Java编写的,但你仍然可以在Scala中使用api。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.