diff --git a/.gitignore b/.gitignore index 99ff88ac..00a5b755 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ doc/doc example/gorgonia/numpy example/gorgonia/gorgonia +examples/model_zoo_executor/model_zoo_executor diff --git a/README.md b/README.md index d74ef293..ca89a1b5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ This is a new version of the API. The tweaked version of Gorgonia have been removed. It is now compatible with the master branch of Gorgonia. Some operators are not yet available though. -Meanwhile, you can use the old version for a demo by fetching a pre-release version of checking out the old version `01b2e2b` +A utility has been added in order to run models from the zoo. +check the `examples` subdirectory. ``` @@ -97,6 +98,8 @@ func Example_gorgonia() { } ``` +A basic implementation can be found in the `examples` subdirectory. + ## Internal ### ONNX protobuf definition diff --git a/RELNOTES.md b/RELNOTES.md index 35b77176..55e308c0 100644 --- a/RELNOTES.md +++ b/RELNOTES.md @@ -2,4 +2,5 @@ This is a new version of the API. The tweaked version of Gorgonia have been removed. It is now compatible with the master branch of Gorgonia. Some operators are not yet available though. -Meanwhile, you can use the old version for a demo by fetching a pre-release version of checking out the old version `01b2e2b` +A utility has been added in order to run models from the zoo. +check the `examples` subdirectory. diff --git a/backend/x/gorgonnx/conv.go b/backend/x/gorgonnx/conv.go index ce3f8b2f..ddd5a266 100644 --- a/backend/x/gorgonnx/conv.go +++ b/backend/x/gorgonnx/conv.go @@ -1,6 +1,8 @@ package gorgonnx import ( + "math" + "github.com/owulveryck/onnx-go" "gorgonia.org/gorgonia" "gorgonia.org/tensor" @@ -16,6 +18,7 @@ func init() { // https://godoc.org/gorgonia.org/gorgonia#Conv2d // test with go test -run=TestONNX/Conv type conv struct { + autopad string pad []int stride []int dilation []int @@ -29,6 +32,40 @@ func (c *conv) apply(g *Graph, n *Node) error { if err != nil { return err } + // autopadding needs to be applied now because it needs to be aware of the shape of the nodes + switch c.autopad { + case "NOTSET": + case "": + case "SAME_UPPER": + outputHeight := int( + math.Ceil( + float64(children[0].gorgoniaNode.Shape()[2]) / + float64(c.stride[0]))) + outputWidth := int( + math.Ceil( + float64(children[0].gorgoniaNode.Shape()[3]) / + float64(c.stride[1]))) + c.pad[0] = int( + math.Max( + float64((outputHeight-1)*c.stride[0]+ + c.kernelShape[0]- + children[0].gorgoniaNode.Shape()[2]), + float64(0))) / + 2 + c.pad[1] = int( + math.Max( + float64((outputWidth-1)*c.stride[1]+ + c.kernelShape[1]- + children[0].gorgoniaNode.Shape()[3]), + float64(0))) / + 2 + + default: + return &onnx.ErrNotImplemented{ + Operator: "Conv", + Message: "auto_pad " + c.autopad + " not implemented", + } + } n.gorgoniaNode, err = gorgonia.Conv2d( children[0].gorgoniaNode, children[1].gorgoniaNode, @@ -41,10 +78,13 @@ func (c *conv) apply(g *Graph, n *Node) error { func (c *conv) init(o onnx.Operation) error { autoPad, ok := o.Attributes["auto_pad"] - if ok && autoPad.(string) != "NOTSET" { - return &onnx.ErrNotImplemented{ - Operator: "Conv", - Message: "auto_pad " + autoPad.(string) + " not implemented", + if ok { + switch autoPad.(string) { + case "NOTSET": + case "VALID": + c.pad = []int{0, 0} + default: + c.autopad = autoPad.(string) } } kernelShape, ok := o.Attributes["kernel_shape"] diff --git a/examples/model_zoo_executor/README.md b/examples/model_zoo_executor/README.md new file mode 100644 index 00000000..b5bec8eb --- /dev/null +++ b/examples/model_zoo_executor/README.md @@ -0,0 +1,16 @@ +# About + +This is a simple utility that runs a model from the model zoo thanks to the Gorgonia backend + +## Example + +Download a pre-trained [model from the zoo](https://github.com/onnx/models) (for now, only [MNIST](https://github.com/onnx/models/tree/master/mnist) is known to work) + +then smply run: + +`go run main.go -model /tmp/mnist/model.onnx -input /tmp/mnist/test_data_set_0/input_0.pb -output /tmp/mnist/test_data_set_0/output_0.pb` + +The utility evaluates the model and check if the computed output is equal to the expected output (within a delta of 5e-3). +If the result is ok, it displays the result: + +`[975.67035 -618.7244 6574.5684 668.0278 -917.27057 -1671.6357 -1952.7606 -61.54949 -777.17645 -1439.5311]` diff --git a/examples/model_zoo_executor/main.go b/examples/model_zoo_executor/main.go new file mode 100644 index 00000000..781763a5 --- /dev/null +++ b/examples/model_zoo_executor/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/owulveryck/onnx-go" + "github.com/owulveryck/onnx-go/backend/x/gorgonnx" + "github.com/stretchr/testify/assert" +) + +func main() { + model := flag.String("model", "model.onnx", "path to the model file") + input := flag.String("input", "test_data_set_0/input_0.pb", "path to the input file") + output := flag.String("output", "test_data_set_0/output_0.pb", "path to the output file") + h := flag.Bool("h", false, "help") + flag.Parse() + if *h { + flag.Usage() + os.Exit(0) + } + for _, f := range []string{*model, *input, *output} { + if _, err := os.Stat(f); err != nil && os.IsNotExist(err) { + log.Fatalf("%v does not exist", f) + } + } + // Create a backend receiver + backend := gorgonnx.NewGraph() + // Create a model and set the execution backend + m := onnx.NewModel(backend) + + // read the onnx model + b, err := ioutil.ReadFile(*model) + if err != nil { + log.Fatal(err) + } + // Decode it into the model + err = m.UnmarshalBinary(b) + if err != nil { + log.Fatal(err) + } + // Set the first input, the number depends of the model + // TODO + b, err = ioutil.ReadFile(*input) + if err != nil { + log.Fatal(err) + } + inputT, err := onnx.NewTensor(b) + if err != nil { + log.Fatal(err) + } + m.SetInput(0, inputT) + err = backend.Run() + if err != nil { + log.Fatal(err) + } + b, err = ioutil.ReadFile(*output) + if err != nil { + log.Fatal(err) + } + outputT, err := onnx.NewTensor(b) + if err != nil { + log.Fatal(err) + } + computedOutputT, err := m.GetOutputTensors() + if err != nil { + log.Fatal(err) + } + assert.InDeltaSlice(&testingT{}, outputT.Data(), computedOutputT[0].Data(), 5e-3, "the two tensors should be equal.") + fmt.Println(computedOutputT[0].Data()) +} + +type testingT struct{} + +func (t *testingT) Errorf(format string, args ...interface{}) { + log.Fatalf(format, args...) +}