MXNet Scala Symbolic API¶
Topics:
- How to Compose Symbols introduces operator overloading of symbols.
- Symbol Attributes describes how to attach attributes to symbols.
- Serialization explains how to save and load symbols.
- Executing Symbols explains how to evaluate the symbols with data.
- Execution API Reference documents the execution APIs.
- Multiple Outputs explains how to configure multiple outputs.
- Symbol Creation API Reference documents functions.
We also highly encourage you to read Symbolic Configuration and Execution in Pictures.
How to Compose Symbols¶
The symbolic API provides a way to configure computation graphs. You can configure the graphs either at the level of neural network layer operations or as fine-grained operations.
The following example configures a two-layer neural network.
scala> import org.apache.mxnet._
scala> val data = Symbol.Variable("data")
scala> val fc1 = Symbol.api.FullyConnected(Some(data), num_hidden = 128, name = "fc1")
scala> val act1 = Symbol.api.Activation(Some(fc1), "relu", "relu1")
scala> val fc2 = Symbol.api.FullyConnected(some(act1), num_hidden = 64, name = "fc2")
scala> val net = Symbol.api.SoftmaxOutput(Some(fc2), name = "out")
scala> :type net
org.apache.mxnet.Symbol
The basic arithmetic operators (plus, minus, div, multiplication) are overloaded for element-wise operations of symbols.
The following example creates a computation graph that adds two inputs together.
scala> import org.apache.mxnet._
scala> val a = Symbol.Variable("a")
scala> val b = Symbol.Variable("b")
scala> val c = a + b
Symbol Attributes¶
You can add an attribute to a symbol by providing an attribute dictionary when you create a symbol.
val data = Symbol.Variable("data", Map("mood"-> "angry"))
val op = Symbol.api.Convolution(Some(data), kernel = Shape(1, 1), num_filter = 1, attr = Map("mood" -> "so so"))
For proper communication with the C++ backend, both the key and values of the attribute dictionary should be strings. To retrieve the attributes, use attr(key)
:
data.attr("mood")
res6: Option[String] = Some(angry)
To attach attributes, you can use AttrScope
. AttrScope
automatically adds the specified attributes to all of the symbols created within that scope. The user can also inherit this object to change naming behavior. For example:
val (data, gdata) =
AttrScope(Map("group" -> "4", "data" -> "great")).withScope {
val data = Symbol.Variable("data", attr = Map("dtype" -> "data", "group" -> "1"))
val gdata = Symbol.Variable("data2")
(data, gdata)
}
assert(gdata.attr("group").get === "4")
assert(data.attr("group").get === "1")
val exceedScopeData = Symbol.Variable("data3")
assert(exceedScopeData.attr("group") === None, "No group attr in global attr scope")
Serialization¶
There are two ways to save and load the symbols. You can use the mxnet.Symbol.save
and mxnet.Symbol.load
functions to serialize the Symbol
objects.
The advantage of using save
and load
functions is that it is language agnostic and cloud friendly.
The symbol is saved in JSON format. You can also get a JSON string directly using mxnet.Symbol.toJson
.
Refer to API documentation for more details.
The following example shows how to save a symbol to an S3 bucket, load it back, and compare two symbols using a JSON string.
scala> import org.apache.mxnet._
scala> val a = Symbol.Variable("a")
scala> val b = Symbol.Variable("b")
scala> val c = a + b
scala> c.save("s3://my-bucket/symbol-c.json")
scala> val c2 = Symbol.load("s3://my-bucket/symbol-c.json")
scala> c.toJson == c2.toJson
Boolean = true
Executing Symbols¶
After you have assembled a set of symbols into a computation graph, the MXNet engine can evaluate them.
If you are training a neural network, this is typically
handled by the high-level Model class and the [fit()
] function.
For neural networks used in “feed-forward”, “prediction”, or “inference” mode (all terms for the same thing: running a trained network), the input arguments are the input data, and the weights of the neural network that were learned during training.
To manually execute a set of symbols, you need to create an [Executor
] object,
which is typically constructed by calling the [simpleBind(
] method on a symbol.
Multiple Outputs¶
To group the symbols together, use the mxnet.symbol.Group function.
scala> import org.apache.mxnet._
scala> val data = Symbol.Variable("data")
scala> val fc1 = Symbol.api.FullyConnected(Some(data), num_hidden = 128, name = "fc1")
scala> val act1 = Symbol.api.Activation(Some(fc1), "relu", "relu1")
scala> val fc2 = Symbol.api.FullyConnected(Some(act1), num_hidden = 64, name = "fc2")
scala> val net = Symbol.api.SoftmaxOutput(Some(fc2), name = "out")
scala> val group = Symbol.Group(fc1, net)
scala> group.listOutputs()
IndexedSeq[String] = ArrayBuffer(fc1_output, out_output)
After you get the group
, you can bind on group
instead.
The resulting executor will have two outputs, one for fc1_output and one for softmax_output.
Next Steps¶
- See IO Data Loading API for parsing and loading data.
- See NDArray API for vector/matrix/tensor operations.
- See KVStore API for multi-GPU and multi-host distributed training.