tfgo

package module
v0.0.0-...-0663583 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 9, 2019 License: Apache-2.0 Imports: 5 Imported by: 0

README

tfgo: Tensorflow in Go

GoDoc Build Status

Tensorflow's Go bindings are hard to use: tfgo makes it easy!

No more problems like:

  • Scoping: each new node will have a new and unique name
  • Typing: attributes are automatically converted to a supported type instead of throwing errors at runtime

Also, it uses Method chaining making possible to write pleasant Go code.

Getting started

Prerequisite: https://www.tensorflow.org/install/lang_go

The core data structure of the Tensorflow's Go bindings is the op.Scope struct. tfgo allows creating new *op.Scope that solves the scoping issue mentioned above.

Since we're defining a graph, let's start from its root (empty graph)

root := tg.NewRoot()

We can now place nodes into this graphs and connect them. Let's say we want to multiply a matrix for a column vector and then add another column vector to the result.

Here's the complete source code.

package main

import (
        "fmt"
        tg "github.com/galeone/tfgo"
        tf "github.com/tensorflow/tensorflow/tensorflow/go"
)

func main() {
        root := tg.NewRoot()
        A := tg.NewTensor(root, tg.Const(root, [2][2]int32{{1, 2}, {-1, -2}}))
        x := tg.NewTensor(root, tg.Const(root, [2][1]int64{{10}, {100}}))
        b := tg.NewTensor(root, tg.Const(root, [2][1]int32{{-10}, {10}}))
        Y := A.MatMul(x.Output).Add(b.Output)
        // Please note that Y is just a pointer to A!

        // If we want to create a different node in the graph, we have to clone Y
        // or equivalently A
        Z := A.Clone()
        results := tg.Exec(root, []tf.Output{Y.Output, Z.Output}, nil, &tf.SessionOptions{})
        fmt.Println("Y: ", results[0].Value(), "Z: ", results[1].Value())
        fmt.Println("Y == A", Y == A) // ==> true
        fmt.Println("Z == A", Z == A) // ==> false
}

that produces

Y:  [[200] [-200]] Z:  [[200] [-200]]
Y == A true
Z == A false

The list of the available methods is available on GoDoc: http://godoc.org/github.com/galeone/tfgo

Computer Vision using data flow graph

Tensorflow is rich of methods for performing operations on images. tfgo provides the image package that allows using the Go bindings to perform computer vision tasks in an elegant way.

For instance, it's possible to read an image, compute its directional derivative along the horizontal and vertical directions, compute the gradient and save it.

The code below does that, showing the different results achieved using correlation and convolution operations.

package main

import (
        tg "github.com/galeone/tfgo"
        "github.com/galeone/tfgo/image"
        "github.com/galeone/tfgo/image/filter"
        "github.com/galeone/tfgo/image/padding"
        tf "github.com/tensorflow/tensorflow/tensorflow/go"
        "os"
)

func main() {
        root := tg.NewRoot()
        grayImg := image.Read(root, "/home/pgaleone/airplane.png", 1)
        grayImg = grayImg.Scale(0, 255)

        // Edge detection using sobel filter: convolution
        Gx := grayImg.Clone().Convolve(filter.SobelX(root), image.Stride{X: 1, Y: 1}, padding.SAME)
        Gy := grayImg.Clone().Convolve(filter.SobelY(root), image.Stride{X: 1, Y: 1}, padding.SAME)
        convoluteEdges := image.NewImage(root.SubScope("edge"), Gx.Square().Add(Gy.Square().Value()).Sqrt().Value()).EncodeJPEG()

        Gx = grayImg.Clone().Correlate(filter.SobelX(root), image.Stride{X: 1, Y: 1}, padding.SAME)
        Gy = grayImg.Clone().Correlate(filter.SobelY(root), image.Stride{X: 1, Y: 1}, padding.SAME)
        correlateEdges := image.NewImage(root.SubScope("edge"), Gx.Square().Add(Gy.Square().Value()).Sqrt().Value()).EncodeJPEG()

        results := tg.Exec(root, []tf.Output{convoluteEdges, correlateEdges}, nil, &tf.SessionOptions{})

        file, _ := os.Create("convolved.png")
        file.WriteString(results[0].Value().(string))
        file.Close()

        file, _ = os.Create("correlated.png")
        file.WriteString(results[1].Value().(string))
        file.Close()
}

airplane.png

airplane

convolved.png

convolved

correlated.png

correlated

the list of the available methods is available on GoDoc: http://godoc.org/github.com/galeone/tfgo/image

Train in Python, Serve in Go

Using both DyTB and tfgo we can train, evaluate and export a machine learning model in very few lines of Python and Go code. Below you can find the Python and the Go code. Just dig into the example to understand how to serve a trained model with tfgo.

Python code
import sys
import tensorflow as tf
from dytb.inputs.predefined.MNIST import MNIST
from dytb.models.predefined.LeNetDropout import LeNetDropout
from dytb.train import train

def main():
    """main executes the operations described in the module docstring"""
    lenet = LeNetDropout()
    mnist = MNIST()

    info = train(
        model=lenet,
        dataset=mnist,
        hyperparameters={"epochs": 2},)

    checkpoint_path = info["paths"]["best"]

    with tf.Session() as sess:
        # Define a new model, import the weights from best model trained
        # Change the input structure to use a placeholder
        images = tf.placeholder(tf.float32, shape=(None, 28, 28, 1), name="input_")
        # define in the default graph the model that uses placeholder as input
        _ = lenet.get(images, mnist.num_classes)

        # The best checkpoint path contains just one checkpoint, thus the last is the best
        saver = tf.train.Saver()
        saver.restore(sess, tf.train.latest_checkpoint(checkpoint_path))

        # Create a builder to export the model
        builder = tf.saved_model.builder.SavedModelBuilder("export")
        # Tag the model in order to be capable of restoring it specifying the tag set
        # clear_device=True in order to export a device agnostic graph.
        builder.add_meta_graph_and_variables(sess, ["tag"], clear_devices=True)
        builder.save()

    return 0


if __name__ == '__main__':
    sys.exit(main())
Go code
package main

import (
        "fmt"
        tg "github.com/galeone/tfgo"
        tf "github.com/tensorflow/tensorflow/tensorflow/go"
)

func main() {
        model := tg.LoadModel("test_models/export", []string{"tag"}, nil)

        fakeInput, _ := tf.NewTensor([1][28][28][1]float32{})
        results := model.Exec([]tf.Output{
                model.Op("LeNetDropout/softmax_linear/Identity", 0),
        }, map[tf.Output]*tf.Tensor{
                model.Op("input_", 0): fakeInput,
        })

        predictions := results[0].Value().([][]float32)
        fmt.Println(predictions)
}

Train with tf.estimator, serve in go

tfgo supports two different inputs for the estimator:

  • Pandas DataFrames
  • Numpy Arrays
  • Python Dictionary

You can train you estimator using these three types of feature columns and you'll be able to run the inference using the *model.EstimatorServe method.

Training in Python using estimator and feature columns

An example of supported input is shown in the example: estimator.py.

Estimator serving using Go
package main

import (
	"fmt"

	tg "github.com/galeone/tfgo"
	"github.com/galeone/tfgo/preprocessor"
	"github.com/galeone/tfgo/proto/example"
	tf "github.com/tensorflow/tensorflow/tensorflow/go"
)

func main() {
	model := tg.LoadModel("./static/1", []string{"serve"}, nil)

	// npData:numpy data like in python {"inputs":[6.4,3.2,4.5,1.5]}
	npData := make(map[string][]float32)
	npData["your_input"] = []float32{6.4, 3.2, 4.5, 1.5}
	featureExample := make(map[string]*example.Feature)
	// You need to choose the method of serialization according to your features column's type
	// e.g{"preprocessor.Float32ToFeature","preprocessor.StringToFeature","StringToFeature.Int64ToFeature"}
	featureExample["your_input"] = preprocessor.Float32ToFeature(npData["your_input"])
	seq, err := preprocessor.PythonDictToByteArray(featureExample)
	if err !=nil{
		panic(err)
	}
	newTensor, _ := tf.NewTensor([]string{string(seq)})
	results := model.EstimatorServe([]tf.Output{
		model.Op("dnn/head/predictions/probabilities", 0)}, newTensor)
	fmt.Println(results[0].Value().([][]float32))

	model = tg.LoadModel("test_models/output/2pb/", []string{"serve"}, nil)
	//pdData:pandas DataFrame like in python
	//    A    B    C    D
	//0   a    b    c    d
	//1   e    f    g    h
	// This is an example, just to show you how to deal with the features of string types.
	data := [][]string{{"a", "b", "c", "d"}, {"e", "f", "g", "h"}}
	columnsName := []string{"A", "B", "C", "D"}
	for _, item := range data {
		for index, key := range columnsName {
			featureExample[key] = preprocessor.StringToFeature([]string{item[index]})
		}
		seq, err := preprocessor.PythonDictToByteArray(featureExample)
		if err !=nil{
			panic(err)
		}
		newTensor, _ := tf.NewTensor([]string{string(seq)})
		results := model.EstimatorServe([]tf.Output{
			model.Op("dnn/head/predictions/probabilities", 0)}, newTensor)
		fmt.Println(results[0].Value().([][]float32))
	}
}

Why?

Thinking about computation represented using graphs, describing computing in this way is, in one word, challenging.

Also, tfgo brings GPU computations to Go and allows writing parallel code without worrying about the device that executes it (just place the graph into the device you desire: that's it!)

Contribute

I love contributions. Seriously. Having people that share your same interests and want to face your same challenges it's something awesome.

If you'd like to contribute, just dig in the code and see what can be added or improved. Start a discussion opening an issue and let's talk about it.

Just follow the same design I use into the image package ("override" the same Tensor methods, document the methods, test your changes, ...)

There are a lot of packages that can be added, like the image package. Feel free to work on a brand new package: I'd love to see this kind of contributions!

Documentation

Overview

Package tfgo simplifies the usage of the Tensorflow's go bindings wrapping the most common methods as methods of new and logically separated objects. These objects handle the naming issues (that could happen when describing a tf.Graph) in a transparent way. Also, additional features are added. Why this package is required is explained in this blog post: https://pgaleone.eu/tensorflow/go/2017/05/29/understanding-tensorflow-using-go/

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Batchify

func Batchify(scope *op.Scope, tensors []tf.Output) tf.Output

Batchify creates a batch of tensors, concatenating them along the first dimension

func Cast

func Cast(scope *op.Scope, value tf.Output, dtype tf.DataType) tf.Output

Cast casts value to the specified dtype

func Const

func Const(scope *op.Scope, value interface{}) tf.Output

Const creates a constant value within the specified scope

func Exec

func Exec(scope *op.Scope, tensors []tf.Output, feedDict map[tf.Output]*tf.Tensor, options *tf.SessionOptions) []*tf.Tensor

Exec creates the computation graph from the scope, then executes the operations required to compute each element of tensors. Node in the graph can be overwritten with feedDict. The session options can be specified using the session parameter. Returns the evaluated tensors. Panics on error.

func IsClose

func IsClose(scope *op.Scope, a, b tf.Output, relTol, absTol tf.Output) tf.Output

IsClose defines the isclose operation between a and b. Returns a conditional node that is true when a is close to b. relTol is the relative tolerance absTol is the absolute tolerance

func IsFloat

func IsFloat(dtype tf.DataType) bool

IsFloat returns true if dtype is a tensorfow float type

func IsInteger

func IsInteger(dtype tf.DataType) bool

IsInteger returns true if dtype is a tensorflow integer type

func MaxValue

func MaxValue(dtype tf.DataType) float64

MaxValue returns the maximum value accepted for the dtype

func MinValue

func MinValue(dtype tf.DataType) float64

MinValue returns the minimum representable value for the specified dtype

func NewRoot

func NewRoot() *op.Scope

NewRoot creates a new *op.Scope, empty

func NewScope

func NewScope(root *op.Scope) *op.Scope

NewScope returns a unique scope in the format input_<suffix> where suffix is a counter This function is not thread safe and shouldn't be called in parallel

Types

type Model

type Model struct {
	// contains filtered or unexported fields
}

Model represents a trained model

func ImportModel

func ImportModel(serializedModel, prefix string, options *tf.SessionOptions) (model *Model)

ImportModel creates a new *Model, loading the graph from the serialized representation. This operation creates a session with specified `options` Panics if the model can't be loaded

func LoadModel

func LoadModel(exportDir string, tags []string, options *tf.SessionOptions) (model *Model)

LoadModel creates a new *Model, loading it from the exportDir. The graph loaded is identified by the set of tags specified when exporting it. This operation creates a session with specified `options` Panics if the model can't be loaded

func (*Model) EstimatorServe

func (model *Model) EstimatorServe(tensors []tf.Output, input *tf.Tensor) (results []*tf.Tensor)

EstimatorServe runs the inference on the model, exported as an estimator for serving. The data can be in numpy or pandas format, e.g. Pandas: { "a": 6.4, "b": 3.2, "c": 4.5, "d": 1.5 } Numpy: { "inputs": [6.4, 3.2, 4.5, 1.5] } For pandas you have to wrap your values into an array, e.g: { "a": [6.4], "b": [3.2], ...}. After that, use preprocessor.PythonDictToByteArray to create the correct input for this method.

func (*Model) Exec

func (model *Model) Exec(tensors []tf.Output, feedDict map[tf.Output]*tf.Tensor) (results []*tf.Tensor)

Exec executes the nodes/tensors that must be present in the loaded model feedDict values to feed to placeholders (that must have been saved in the model definition) panics on error

func (*Model) Op

func (model *Model) Op(name string, idx int) tf.Output

Op extracts the output in position idx of the tensor with the specified name from the model graph

type Tensor

type Tensor struct {
	// Root: Each tensor maintains a pointer to the graph root
	Root *op.Scope
	// Path is the current Tensor full path
	Path *op.Scope
	// Output is the Tensor content
	Output tf.Output
}

Tensor is an high level abstraction for the tf.Output structure, associating a scope to the Tensor

func NewTensor

func NewTensor(scope *op.Scope, tfout tf.Output) (tensor *Tensor)

NewTensor creates a *Tensor from a tf.Output Place the cloned tensor within the specified scope

func (*Tensor) Add

func (tensor *Tensor) Add(tfout tf.Output) *Tensor

Add defines the add operation between the tensor and tfout `tfout` dtype is converted to tensor.Dtype() before adding

func (*Tensor) Cast

func (tensor *Tensor) Cast(dtype tf.DataType) *Tensor

Cast casts the current tensor to the requested dtype

func (*Tensor) Check

func (tensor *Tensor) Check()

Check checks if the previous operation caused an error and thus tensor.Path.Err is not nil. If it's not, panics because we're defining the graph in a wrong way

func (*Tensor) Clone

func (tensor *Tensor) Clone() *Tensor

Clone returns a copy of the current tensor in a new scope Clone is used to create a different tensor from the output of an operation. The new node is placed at the same level of the current tensor it can be seen as a twin tensor

func (*Tensor) Dtype

func (tensor *Tensor) Dtype() tf.DataType

Dtype returns the tensor dtype

func (*Tensor) MatMul

func (tensor *Tensor) MatMul(tfout tf.Output) *Tensor

MatMul defines the matrix multiplication operation between the tensor and `tfout`. `tfout` dtype is converted to tensor.Dtype() before multiplying

func (*Tensor) Mul

func (tensor *Tensor) Mul(tfout tf.Output) *Tensor

Mul defines the multiplication operation between the tensor and `tfout`. It's the multiplication element-wise with broadcasting support. `tfout` dtype is converted to tensor.Dtype() before multiplying

func (*Tensor) Pow

func (tensor *Tensor) Pow(y tf.Output) *Tensor

Pow defines the pow operation x^y, where x are the tensor values y dtype is converted to tensor.Dtype() before executing Pow

func (*Tensor) Scope

func (tensor *Tensor) Scope() *op.Scope

Scope returns the scope associated to the tensor

func (*Tensor) Shape32

func (tensor *Tensor) Shape32(firstDimension bool) []int32

Shape32 returns the shape of the tensor as []int32. If firstDimension is true a 4 elements slice is returned. Otherwise a 3 elements slice is returned.

func (*Tensor) Shape64

func (tensor *Tensor) Shape64(firstDimension bool) []int64

Shape64 returns the shape of the tensor as []int64. If firstDimension is true a 4 elements slice is returned. Otherwise a 3 elements slice is returned.

func (*Tensor) Sqrt

func (tensor *Tensor) Sqrt() *Tensor

Sqrt defines the square root operation for the tensor values

func (*Tensor) Square

func (tensor *Tensor) Square() *Tensor

Square defines the square operation for the tensor values

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL