我的代码基本上是这样的:
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
执行此操作,而不必将大型模型类或数据文件签入我的版本控制中?
答案 0 :(得分:3)
您应该将训练产生的数据序列化到自己的文件中。然后,您可以将此数据文件打包到JAR中。您的生产代码打开文件并读取它而不是运行训练算法。
答案 1 :(得分:3)
步骤如下。
在构建的资源生成阶段:
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堆空间。以上只是展示了如何在构建时打包资源。
请参阅http://www.scala-sbt.org/1.x/docs/Howto-Generating-Files.html
答案 2 :(得分:0)
好的,我设法做到了:
将食物训练师模块分成两个独立的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
。我们得益于:
FoodTrainer
和FoodModel
分开
将软件包打包到他们自己的JAR中(我们现在很难在内部部署它们) - 相反,我们只是将它们保持在同一个状态
项目,但不同的子模块被打包到一个JAR。答案 3 :(得分:-1)
这是一个想法,将您的模型放在一个资源文件夹中,该文件夹被添加到jar程序集中。我认为如果它在该文件夹中,所有罐子都会与您的模型一起分发。 Lmk怎么样,欢呼!
检查此信息以便从资源中读取:
https://www.mkyong.com/java/java-read-a-file-from-resources-folder/
它在Java中,但你仍然可以在Scala中使用api。