SBT:如何将 class 的实例打包为 JAR?
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 组装 (sbt assembly
) 时,调用 val classifier = new FoodTrainer(s3Dir).train()
并发布 JAR,其中 classifier
实例立即可供下游库用户使用。
最简单的方法是什么?这方面有哪些既定范例?我知道在 ML 项目中发布训练有素的模型是一个相当常见的习惯用法,例如http://nlp.stanford.edu/software/stanford-corenlp-models-current.jar
如何使用 sbt assembly
执行此操作,而不必将大型模型 class 或数据文件签入我的版本控制?
这是一个想法,将您的模型放入一个资源文件夹中,然后将其添加到 jar 程序集中。我认为如果模型在该文件夹中,所有 jar 都会与您的模型一起分发。看看进展如何,干杯!
查看此资源以阅读资源:
https://www.mkyong.com/java/java-read-a-file-from-resources-folder/
它在 Java 中,但您仍然可以在 Scala 中使用 api。
您应该将训练产生的数据序列化到它自己的文件中。然后,您可以将此数据文件打包到您的 JAR 中。您的生产代码打开文件并读取它而不是 运行 训练算法。
步骤如下
在构建的资源生成阶段:
- 在构建的资源生成阶段生成模型。
- 将模型的内容序列化为托管资源文件夹中的文件。
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
- 资源将自动包含在
jar
文件中,不会出现在源代码树中。
- 要加载模型,只需添加读取资源和解析模型的代码。
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
}
当然,由于您的数据相当大,您需要使用流序列化器和解析器来避免 java 堆 space 过载。以上只是构建时如何打包资源
见http://www.scala-sbt.org/1.x/docs/Howto-Generating-Files.html
好的,我做到了:
将食物训练器模块分成 2 个独立的 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
:
添加一个编译时任务来生成食物训练器模块
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
。我们受益于:
- 这是在运行时从 JAR 中快速静态加载的
打包资源
- 无需在运行时训练模型(非常
贵)
- 无需在您的版本中签入模型
控制(同样二进制模型非常大) - 它只被打包到你的
罐子
- 不需要分开
FoodTrainer
和FoodModel
将它们打包到它们自己的 JAR 中(我们现在很头疼在内部部署它们)——相反,我们只是将它们保持在相同的位置
项目但不同的子模块被打包到一个 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 组装 (sbt assembly
) 时,调用 val classifier = new FoodTrainer(s3Dir).train()
并发布 JAR,其中 classifier
实例立即可供下游库用户使用。
最简单的方法是什么?这方面有哪些既定范例?我知道在 ML 项目中发布训练有素的模型是一个相当常见的习惯用法,例如http://nlp.stanford.edu/software/stanford-corenlp-models-current.jar
如何使用 sbt assembly
执行此操作,而不必将大型模型 class 或数据文件签入我的版本控制?
这是一个想法,将您的模型放入一个资源文件夹中,然后将其添加到 jar 程序集中。我认为如果模型在该文件夹中,所有 jar 都会与您的模型一起分发。看看进展如何,干杯!
查看此资源以阅读资源:
https://www.mkyong.com/java/java-read-a-file-from-resources-folder/
它在 Java 中,但您仍然可以在 Scala 中使用 api。
您应该将训练产生的数据序列化到它自己的文件中。然后,您可以将此数据文件打包到您的 JAR 中。您的生产代码打开文件并读取它而不是 运行 训练算法。
步骤如下
在构建的资源生成阶段:
- 在构建的资源生成阶段生成模型。
- 将模型的内容序列化为托管资源文件夹中的文件。
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
- 资源将自动包含在
jar
文件中,不会出现在源代码树中。 - 要加载模型,只需添加读取资源和解析模型的代码。
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 }
当然,由于您的数据相当大,您需要使用流序列化器和解析器来避免 java 堆 space 过载。以上只是构建时如何打包资源
见http://www.scala-sbt.org/1.x/docs/Howto-Generating-Files.html
好的,我做到了:
将食物训练器模块分成 2 个独立的 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
: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
。我们受益于:
- 这是在运行时从 JAR 中快速静态加载的 打包资源
- 无需在运行时训练模型(非常 贵)
- 无需在您的版本中签入模型 控制(同样二进制模型非常大) - 它只被打包到你的 罐子
- 不需要分开
FoodTrainer
和FoodModel
将它们打包到它们自己的 JAR 中(我们现在很头疼在内部部署它们)——相反,我们只是将它们保持在相同的位置 项目但不同的子模块被打包到一个 JAR 中。