From 1cdef2e8b2440f00a13142d0ccc2960d14ec4539 Mon Sep 17 00:00:00 2001 From: Xin Qiu Date: Mon, 9 Sep 2019 09:38:44 +0800 Subject: [PATCH] Save Keras-like model to pure keras or tensorflow protobuf. (#1600) * checkpoint * some update * refine api * some update * fix build fail * meet code review * style check * fix typo * fix style check --- layers/Dense.scala | 18 ++++++++++++++++++ layers/Dropout.scala | 11 ++++++++++- layers/LSTM.scala | 35 ++++++++++++++++++++++++++++++++++ layers/Permute.scala | 7 +++++++ layers/Reshape.scala | 7 +++++++ layers/utils/KerasUtils.scala | 28 ++++++++++++++++++++++++++- models/Topology.scala | 36 +++++++++++++++++++++++++++++++++++ 7 files changed, 140 insertions(+), 2 deletions(-) diff --git a/layers/Dense.scala b/layers/Dense.scala index 03c57eda8f7..177b7c85454 100644 --- a/layers/Dense.scala +++ b/layers/Dense.scala @@ -59,6 +59,24 @@ class Dense[T: ClassTag]( override val inputShape: Shape = null)(implicit ev: TensorNumeric[T]) extends BigDLDense[T](outputDim, init, activation, wRegularizer, bRegularizer, bias, inputShape) with Net { + + override private[zoo] def toKeras2(dir: String): String = { + val params = Net.inputShapeToString(inputShape) ++ + Net.activationToString(activation) ++ + Net.param(getName()) ++ + Net.param(bias, "use_bias") ++ + Net.param(outputDim, "units") + Net.kerasDef(this, params) + } + + override private[zoo] def getKerasWeights(): Array[Tensor[Float]] = { + val weights = this.parameters()._1 + val kWeights = Array.tabulate(weights.length)(_ => Tensor[Float]()) + weights(0) = weights(0).t().contiguous() + weights(0).cast[Float](kWeights(0).resizeAs(weights(0))) + weights(1).cast[Float](kWeights(1).resizeAs(weights(1))) + kWeights + } } object Dense { diff --git a/layers/Dropout.scala b/layers/Dropout.scala index 702483860cd..57b6a1429e0 100644 --- a/layers/Dropout.scala +++ b/layers/Dropout.scala @@ -36,7 +36,16 @@ class Dropout[T: ClassTag]( override val p: Double, override val inputShape: Shape = null) (implicit ev: TensorNumeric[T]) - extends com.intel.analytics.bigdl.nn.keras.Dropout[T](p, inputShape) with Net {} + extends com.intel.analytics.bigdl.nn.keras.Dropout[T](p, inputShape) with Net { + + override private[zoo] def toKeras2(dir: String): String = { + val params = Net.inputShapeToString(inputShape) ++ + Net.param(getName()) ++ + Net.param(p, "rate") + Net.kerasDef(this, params) + } + +} object Dropout { def apply[@specialized(Float, Double) T: ClassTag]( diff --git a/layers/LSTM.scala b/layers/LSTM.scala index af35e9a7859..00bacfd22ee 100644 --- a/layers/LSTM.scala +++ b/layers/LSTM.scala @@ -78,6 +78,41 @@ class LSTM[T: ClassTag]( uRegularizer = uRegularizer, bRegularizer = bRegularizer) } + + override private[zoo] def toKeras2(dir: String): String = { + val params = Net.inputShapeToString(inputShape) ++ + Net.activationToString(activation) ++ + Net.activationToString(innerActivation, "recurrent_activation") ++ + Net.param(returnSeq, "return_sequences") ++ + Net.param(outputDimension, "units") + Net.param(getName()) + Net.kerasDef(this, params) + } + + override private[zoo] def getKerasWeights(): Array[Tensor[Float]] = { + val weights = this.parameters()._1 + val kWeights = Array.tabulate(weights.length)(_ => Tensor[Float]()) + weights(0) = weights(0).t().contiguous() + weights(2) = weights(2).t().contiguous() + weights(0).cast[Float](kWeights(0).resizeAs(weights(0))) + weights(2).cast[Float](kWeights(1).resizeAs(weights(2))) + weights(1).cast[Float](kWeights(2).resizeAs(weights(1))) + // map to keras's weight + switch(kWeights(0), 2) + switch(kWeights(1), 2) + switch(kWeights(2), 1) + + kWeights + } + + private def switch(t: Tensor[Float], dim: Int): Unit = { + val tmpWeight = t.narrow(dim, 1, outputDimension).clone() + tmpWeight.copy(t.narrow(dim, 1 + outputDimension, outputDimension)) + t.narrow(dim, 1 + outputDimension, outputDimension) + .copy(t.narrow(dim, 2 * outputDimension + 1, outputDimension)) + t.narrow(dim, 2 * outputDimension + 1, outputDimension).copy(tmpWeight) + } + } object LSTM { diff --git a/layers/Permute.scala b/layers/Permute.scala index b0d634176f5..95572521551 100644 --- a/layers/Permute.scala +++ b/layers/Permute.scala @@ -40,6 +40,13 @@ class Permute[T: ClassTag]( override val inputShape: Shape = null)(implicit ev: TensorNumeric[T]) extends BigDLPermute[T]( dims, inputShape) with Net { + + override private[zoo] def toKeras2(dir: String): String = { + val params = Net.inputShapeToString(inputShape) ++ + Net.param(getName()) ++ + Net.arrayToString(dims, "dims") + Net.kerasDef(this, params) + } } object Permute { diff --git a/layers/Reshape.scala b/layers/Reshape.scala index 96d229445f3..a1e97198c74 100644 --- a/layers/Reshape.scala +++ b/layers/Reshape.scala @@ -98,6 +98,13 @@ class Reshape[T: ClassTag]( } layer.asInstanceOf[AbstractModule[Tensor[T], Tensor[T], T]] } + + override private[zoo] def toKeras2(dir: String): String = { + val params = Net.inputShapeToString(inputShape) ++ + Net.param(getName()) ++ + Net.arrayToString(targetShape, "target_shape") + Net.kerasDef(this, params) + } } object Reshape { diff --git a/layers/utils/KerasUtils.scala b/layers/utils/KerasUtils.scala index 8c98f73f8c8..098298c161b 100644 --- a/layers/utils/KerasUtils.scala +++ b/layers/utils/KerasUtils.scala @@ -20,7 +20,7 @@ import com.intel.analytics.bigdl.Criterion import com.intel.analytics.bigdl.nn.Graph.ModuleNode import com.intel.analytics.bigdl.nn._ import com.intel.analytics.bigdl.nn.keras.{KerasIdentityWrapper, KerasLayer, KerasLayerWrapper, Sequential => KSequential, SoftMax => KSoftMax} -import com.intel.analytics.bigdl.nn.abstractnn.{AbstractModule, Activity, DataFormat} +import com.intel.analytics.bigdl.nn.abstractnn.{AbstractModule, Activity, DataFormat, TensorModule} import com.intel.analytics.bigdl.optim._ import com.intel.analytics.bigdl.tensor.Tensor import com.intel.analytics.bigdl.tensor.TensorNumericMath.TensorNumeric @@ -84,6 +84,32 @@ object KerasUtils { } } + def getActivationName[T: ClassTag](activation: AbstractModule[_, _, T]): String = { + if (activation == null) { + throw new IllegalArgumentException("activation is null") + } else { + activation match { + case _: Tanh[T] => "tanh" + case _: Sigmoid[T] => "sigmoid" + case _: ReLU[T] => "relu" + case _: com.intel.analytics.bigdl.nn.SoftMax[T] => "softmax" + case _: SoftPlus[T] => "softplus" + case _: SoftSign[T] => "softsign" + case _: HardSigmoid[T] => "hard_sigmoid" + case _: ReLU6[T] => "relu6" + case _: TanhShrink[T] => "tanh_shrink" + case _: SoftMin[T] => "softmin" + case _: LogSigmoid[T] => "log_sigmoid" + case _: LogSoftMax[T] => "log_softmax" + case _: Identity[T] => "linear" + case _: com.intel.analytics.zoo.pipeline.api.keras.layers.SoftMax[T] => "softmax" + case _ => throw new IllegalArgumentException("unkown activation" + + activation.getClass.getName) + } + } + + } + def getTorchActivation[T : ClassTag] (activation: String) (implicit ev: TensorNumeric[T]): AbstractModule[Tensor[T], Tensor[T], T] = { if (activation == null) null diff --git a/models/Topology.scala b/models/Topology.scala index dd360a4c584..ed4b64e259f 100644 --- a/models/Topology.scala +++ b/models/Topology.scala @@ -547,6 +547,29 @@ abstract class KerasNet[T](implicit val tag: ClassTag[T], implicit val ev: Tenso def toModel(): Model[T] + + /** + * Save model to keras2 h5 file. Only for inference + * @param filePath path to save model. + * @param python python path, need analytics-zoo and tensorflow installed. + */ + def saveToKeras2[T: ClassTag]( + filePath: String, + python: String = "python")(implicit ev: TensorNumeric[T]): Unit = { + Net.saveToKeras2[T](this, filePath, python) + } + + /** + * Save model to tensorflow protobuf. Only for inference. + * @param dir directory to save model. + * @param python python path, need analytics-zoo and tensorflow installed. + */ + def saveToTf[T: ClassTag]( + dir: String, + python: String = "python")(implicit ev: TensorNumeric[T]): Unit = { + Net.saveToTf[T](this, dir, python) + } + /** * Print out the summary information of an Analytics Zoo Keras Model. * @@ -892,6 +915,19 @@ class Sequential[T: ClassTag] private () val graph = this.toModel() graph.summary(lineLength, positions) } + + override private[zoo] def getKerasWeights(): Array[Tensor[Float]] = { + val weights = new ArrayBuffer[Tensor[Float]]() + modules(0).asInstanceOf[TSequential[T]].modules.foreach(m => { + val params = m.asInstanceOf[Net].getKerasWeights() + if (params != null) { + params.foreach{p => + weights += p + } + } + }) + weights.toArray + } } object Sequential extends KerasLayerSerializable {