Docs

0%

Loading...

Static preview:

Tensor

The tensor.Tensor represents n-dimensional data of various types, providing similar functionality to the widely used NumPy libraries in Python, and the commercial MATLAB framework.

The Goal math mode operates on tensor data exclusively: see documentation there for convenient shortcut expressions for common tensor operations. This page documents the underlying Go language implementation of tensors. See tensor for the Go API docs, tensor math for basic math operations that can be performed on tensors, and stats for statistics functions operating on tensor data.

A tensor can be constructed from a Go slice, and accessed using a 1D index into that slice:

Note that the type of the tensor is inferred from the values, using standard Go rules, so you would need to add a decimal to obtain floating-point numbers instead of ints:

You can reshape the tensor by setting the number of values along any number of dimensions, preserving any values that are compatible with the new shape, and access values using n-dimensional indexes:

The dimensions are organized in row major format (same as NumPy), so the number of rows comes first, then columns; the last dimension (i.e., columns in this case) is the innermost dimension, so that each column represents a contiguous array of values in memory, while rows are not contiguous.

You can create a tensor with a specified shape, and fill it with a single value:

Note the detailed formatting available from the standard stringer String() method on any tensor, providing the shape sizes on the first line, with dimensional indexes for the values.

A given tensor can hold any standard Go value type, including int, float32 and float64, and string values (using Go generics for the numerical types), and it provides accessor methods for the following “core” types:

  • Float methods set and return float64 values.
  • Int methods set and return int values.
  • String methods set and return string values.

For example, you can directly get a string representation of any value:

Setting values

To set a value, you typically use a type-specific method most appropriate for the underlying data type:

There are also Value, Value1D, and Set, Set1D methods that use Generics to operate on the actual underlying data type:

Views and values

The abstract tensor.Tensor interface is implemented (and extended) by the concrete tensor.Values types, which are what we’ve been getting in the above examples, and directly manage an underlying Go slice of values. These can be reshaped and appended to, like a Go slice.

In addition, there are various View types that wrap other tensors and provide more flexible ways of accessing the tensor values, and provide all of the same core functionality present in NumPy.

Sliced

First, this is the starting Values tensor, as a 3x4 matrix:

Using the tensor.Reslice function, you can extract any subset from this 2D matrix, for example the values in a given row or column:

Note that the column values got turned into a 1D tensor in this process – to keep it as a column vector (2D with 1 column and 3 rows), you need to add an extra “blank” dimension, which can be done using the tensor.NewAxis value:

You can also specify sub-ranges along each dimension, or even reorder the values, by using a tensor.Slice element that has Start, Stop and Step values, like those of a standard Go for loop expression, with sensible default behavior for zero values:

You can use tensor.Ellipsis to specify FullAxis for all the dimensions up to those specified, to flexibly focus on the innermost dimensions:

As in NumPy (and standard Go slices), the tensor.Sliced view wraps the original source tensor, so that if you change a value in that original source, the value automatically changes in the view as well. Use the AsValues() method on a view to get a new concrete tensor.Values representation of the view (equivalent to the NumPy copy function).

Masked by booleans

You can apply a boolean mask to a tensor, to extract arbitrary values where the boolean value is true:

Note that missing values are encoded as NaN, which allows the resulting tensor.Masked view to retain the shape of the original, and all of the other math functions operating on tensors properly treat NaN as a missing value that is ignored. You can also get the concrete values as shown, but this reduces the shape to 1D by default.

Indexes

You can extract arbitrary values from a tensor using a list of indexes (as a tensor), where the shape of that list then determines the shape of the resulting view:

You can also feed Masked indexes into the tensor.Indexed view to get a reshaped view:

Differences from NumPy

NumPy is somewhat confusing with respect to the distinction between basic indexing (using a single index or sliced ranges of indexes along each dimension) versus advanced indexing (using an array of indexes or bools). Basic indexing returns a view into the original data (where changes to the view directly affect the underlying type), while advanced indexing returns a copy.

However, rather confusingly (per this stack overflow question), you can do direct assignment through advanced indexing (more on this below):

In the tensor package, all of the View types (tensor.Sliced, tensor.Reshaped, tensor.Masked, and tensor.Indexed) are unambiguously wrappers around a source tensor, and their values change when the source changes. Use .AsValues() to break that connection and get the view as a new set of concrete values.

Row, Cell access

The tensor.RowMajor interface provides a convenient set of methods to access tensors where the first, outermost dimension is a row, and there may be multiple remaining dimensions after that. All concrete tensor.Values tensors implement this interface.

For example, you can easily get a SubSpace tensor that contains the values within a given row, and set values within a row tensor using a flat 1D “cell” index that applies to the values within a row:

Tensor pages