Files
unity-application/Packages/com.unity.barracuda/Runtime/Core/ModelBuilder.cs
2023-03-18 19:53:17 +00:00

2407 lines
100 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Assertions;
namespace Unity.Barracuda
{
/// <summary>
/// Class responsible for run-time model building from Neural Net primitives.
/// </summary>
public class ModelBuilder
{
readonly Model m_Model;
/// <summary>
/// Model under construction
/// </summary>
public Model model => m_Model;
/// <summary>
/// Create a model builder helper to construct the underlying Model.
/// </summary>
/// <param name="model">base model to continue building on</param>
public ModelBuilder(Model model = null)
{
if (model == null)
model = new Model();
m_Model = model;
}
/// <summary>
/// Add an input to the model
/// </summary>
/// <param name="name">input name</param>
/// <param name="shape">input shape</param>
/// <param name="rank">input rank</param>
/// <returns>Input instance</returns>
public Model.Input Input(string name, Int32[] shape, int rank)
{
m_Model.inputs.Add(new Model.Input {name = name, shape = shape, rank = rank});
return m_Model.inputs.Last();
}
/// <summary>
/// Add an input to the model
/// </summary>
/// <param name="name">input name</param>
/// <param name="shape">input shape</param>
/// <returns>Input instance</returns>
public Model.Input Input(string name, TensorShape shape)
{
m_Model.inputs.Add(new Model.Input {name = name, shape = shape.ToArray()});
return m_Model.inputs.Last();
}
/// <summary>
/// Add an input to the model
/// </summary>
/// <param name="name">input name</param>
/// <param name="batch">input batch size</param>
/// <param name="channels">input channel count</param>
/// <returns>Input instance</returns>
public Model.Input Input(string name, Int32 batch, Int32 channels)
{
m_Model.inputs.Add(new Model.Input {name = name, shape = new []{batch, 1, 1, channels}, rank = 2});
return m_Model.inputs.Last();
}
/// <summary>
/// Add an input to the model
/// </summary>
/// <param name="name">input name</param>
/// <param name="batch">input batch size</param>
/// <param name="height">input height</param>
/// <param name="width">input width</param>
/// <param name="channels">input channel count</param>
/// <returns>Input instance</returns>
public Model.Input Input(string name, Int32 batch, Int32 height, Int32 width, Int32 channels)
{
m_Model.inputs.Add(new Model.Input {name = name, shape = new []{batch, height, width, channels}, rank = 4});
return m_Model.inputs.Last();
}
/// <summary>
/// Add an output to the model
/// </summary>
/// <param name="input">reference object, could be `string`, `Layer` or `Model.Input`</param>
/// <returns>Output instance</returns>
public string Output(object input)
{
var name = ResolveInput(input);
if (!m_Model.outputs.Contains(name))
m_Model.outputs.Add(name);
return name;
}
/// <summary>
/// Add memory to the model
/// </summary>
/// <param name="input">reference input object, could be `string`, `Layer` or `Model.Input`</param>
/// <param name="output">reference output object, could be `string`, `Layer` or `Model.Input`</param>
/// <param name="shape">memory shape</param>
/// <returns>Memory instance</returns>
public Model.Memory Memory(object input, object output, TensorShape shape)
{
m_Model.memories.Add(new Model.Memory {
shape = shape,
input = ResolveInput(input),
output = ResolveInput(output)});
return m_Model.memories.Last();
}
private string ResolveInput(object input)
{
if (input == null)
return null;
if (input is string)
return input as string;
if (input is Layer)
return (input as Layer).name;
if (input is Model.Input)
return ((Model.Input)input).name;
throw new ArgumentException($"Unsupported input type: {input.GetType()}");
}
/// <summary>
/// Allow to load a tensor from constants.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="tensor">data Tensor</param>
/// <param name="insertionIndex">insertion index in Layer list</param>
/// <param name="rank">constant rank</param>
/// <returns>created Layer instance</returns>
public Layer Const(string name, Tensor tensor, int insertionIndex = -1, int rank = -1)
{
Layer layer = new Layer(name, Layer.Type.Load);
if (rank >= 0)
layer.axis = rank;
layer.datasets = new Layer.DataSet[1];
layer.datasets[0].name = name;
layer.datasets[0].shape = tensor.shape;
layer.datasets[0].itemSizeInBytes = 4;//TODO fp16
layer.datasets[0].length = tensor.shape.length;
layer.datasets[0].offset = 0;
layer.weights = new BarracudaArray(tensor.shape.length, tensor.dataType);
tensor.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, 0);
if (insertionIndex < 0 || insertionIndex >= m_Model.layers.Count)
m_Model.layers.Add(layer);
else
m_Model.layers.Insert(insertionIndex, layer);
return layer;
}
/// <summary>
/// Apply per channel scale and bias.
/// Scale and bias should be tensors of shape [1,1,1, input.shape[C]]
///
/// Output shape is same as input.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="scale">scale data Tensor</param>
/// <param name="bias">bias data Tensor</param>
/// <returns>created Layer instance</returns>
public Layer ScaleBias(string name, object input, Tensor scale, Tensor bias)
{
Layer layer = new Layer(name,Layer.Type.ScaleBias);
layer.inputs = new [] {ResolveInput(input)};
layer.datasets = new Layer.DataSet[2];
layer.datasets[0].name = $"{name}/S";
layer.datasets[0].shape = scale.shape;
layer.datasets[0].itemSizeInBytes = 4;
layer.datasets[0].length = scale.shape.length;
layer.datasets[0].offset = 0;
layer.datasets[1].name = $"{name}/B";
layer.datasets[1].shape = bias.shape;
layer.datasets[1].itemSizeInBytes = 4;
layer.datasets[1].length = bias.shape.length;
layer.datasets[1].offset = scale.shape.length;
Assert.AreEqual(scale.dataType, bias.dataType);
layer.weights = new BarracudaArray(scale.shape.length + bias.shape.length, scale.dataType);
scale.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, 0);
bias.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, layer.datasets[1].offset);
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Apply Local Response Normalization as described in the AlexNet paper
/// https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf
/// It normalizes over local input regions, local region being defined across channels.
///
/// For an element X[n, h, w, c] in a tensor of shape (N x H x W x C), its region is X[n, h, w, cRange]
/// with cRange = [max(0, c - floor((size - 1) / 2)), min(C - 1, c + ceil((size - 1) / 2)].
///
/// y = x / Pow( bias + alpha * sum( xOverLocalRange ^ 2 ) / size, beta)
///
/// Output shape is same as input.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="alpha">alpha</param>
/// <param name="beta">beta</param>
/// <param name="bias">bias</param>
/// <param name="size">size</param>
/// <returns>created Layer instance</returns>
public Layer LRN(string name, object input, float alpha, float beta, float bias, int size)
{
Layer layer = new Layer(name, Layer.Type.LRN);
layer.inputs = new [] {ResolveInput(input)};
layer.alpha = alpha;
layer.beta = beta;
layer.datasets = new Layer.DataSet[1];
layer.datasets[0].name = $"{name}/B";
layer.datasets[0].shape = new TensorShape(1,1,1,1);
layer.datasets[0].itemSizeInBytes = 4;
layer.datasets[0].length = 1;
layer.datasets[0].offset = 0;
layer.weights = new BarracudaArray(1);
layer.weights[0] = bias;
layer.pool = new int[1];
layer.pool[0] = size;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Takes a tensor as input and outputs a tensor containing the shape of the input tensor.
/// Optionally, if an axis is specified, then it will return only that part of the shape.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="axis">axis</param>
/// <returns>created Layer instance</returns>
public Layer Shape(string name, object input, int axis = -1)
{
var layer = new Layer(name, Layer.Type.Shape);
layer.inputs = new [] { ResolveInput(input) };
layer.axis = axis; // If positive, then this will return the specific axis of the shape
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Carries out instance normalization as described in the paper https://arxiv.org/abs/1607.08022
/// y = scale * (x - mean) / sqrt(variance + epsilon) + bias, where mean and variance are computed per instance per channel.
/// Scale and bias should be tensors of shape [1,1,1, input.shape[C]]
///
/// Output shape is same as input.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="scale">scale</param>
/// <param name="bias">bias</param>
/// <param name="epsilon">epsilon</param>
/// <returns>created Layer instance</returns>
public Layer Normalization(string name, object input, Tensor scale, Tensor bias, float epsilon = 1e-5f)
{
Layer layer = new Layer(name, Layer.Type.Normalization);
layer.inputs = new [] {ResolveInput(input)};
layer.datasets = new Layer.DataSet[2];
layer.datasets[0].name = $"{name}/S";
layer.datasets[0].shape = scale.shape;
layer.datasets[0].itemSizeInBytes = 4;
layer.datasets[0].length = scale.shape.length;
layer.datasets[0].offset = 0;
layer.datasets[1].name = $"{name}/B";
layer.datasets[1].shape = bias.shape;
layer.datasets[1].itemSizeInBytes = 4;
layer.datasets[1].length = bias.shape.length;
layer.datasets[1].offset = scale.shape.length;
Assert.AreEqual(scale.dataType, bias.dataType);
layer.weights = new BarracudaArray(scale.shape.length + bias.shape.length, scale.dataType);
layer.beta = epsilon;
scale.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, 0);
bias.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, layer.datasets[1].offset);
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Apply a densely connected layer (aka general matrix multiplication or GEMM)
/// Bias should be a tensor with (batch == input.shape[H] * input.shape[W] * input.shape[C]) and only one other dimensions of size > 1
/// Weight should be a tensor with (batch == 1) and (height * width * channels == bias.shape[B] * )
///
/// Output shape is [input.shape[B], 1, 1, Weight.shape[H]*Weight.shape[W]*Weight.shape[C]]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="weight">weight data Tensor</param>
/// <param name="bias">bias data Tensor</param>
/// <returns>created Layer instance</returns>
public Layer Dense(string name, object input, Tensor weight, Tensor bias)
{
Layer layer = new Layer(name, Layer.Type.Dense);
layer.inputs = new [] {ResolveInput(input)};
layer.datasets = new Layer.DataSet[2];
layer.datasets[0].name = $"{name}/W";
layer.datasets[0].shape = weight.shape;
layer.datasets[0].itemSizeInBytes = 4;
layer.datasets[0].length = weight.shape.length;
layer.datasets[0].offset = 0;
layer.datasets[1].name = $"{name}/B";
layer.datasets[1].shape = bias.shape;
layer.datasets[1].itemSizeInBytes = 4;
layer.datasets[1].length = bias.shape.length;
layer.datasets[1].offset = weight.shape.length;
Assert.AreEqual(weight.dataType, bias.dataType);
layer.weights = new BarracudaArray(weight.shape.length + bias.shape.length, weight.dataType);
weight.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, 0);
bias.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, layer.datasets[1].offset);
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Rank 3 `Dense` layer
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="weight">weight data Tensor</param>
/// <param name="bias">bias data Tensor</param>
/// <returns>created Layer instance</returns>
public Layer Dense3(string name, object input, Tensor weight, Tensor bias)
{
Layer layer = new Layer(name, Layer.Type.Dense3);
layer.inputs = new[] { ResolveInput(input) };
layer.datasets = new Layer.DataSet[2];
layer.datasets[0].name = $"{name}/W";
layer.datasets[0].shape = weight.shape;
layer.datasets[0].itemSizeInBytes = 4;
layer.datasets[0].length = weight.shape.length;
layer.datasets[0].offset = 0;
layer.datasets[1].name = $"{name}/B";
layer.datasets[1].shape = bias.shape;
layer.datasets[1].itemSizeInBytes = 4;
layer.datasets[1].length = bias.shape.length;
layer.datasets[1].offset = weight.shape.length;
Assert.AreEqual(weight.dataType, bias.dataType);
layer.weights = new BarracudaArray(weight.shape.length + bias.shape.length, weight.dataType);
weight.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, 0);
bias.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, layer.datasets[1].offset);
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Applies matrix multiplication between A and B
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">first input node</param>
/// <param name="input1">second input node</param>
/// <returns>created Layer instance</returns>
public Layer MatMul(string name, object input0, object input1)
{
var inputs = new[] { input0, input1 };
Layer layer = new Layer(name, Layer.Type.MatMul);
layer.inputs = inputs.Select(i => ResolveInput(i)).ToArray();
m_Model.layers.Add(layer);
return layer;
}
private Layer Conv(string name, Layer.Type convType, object input, Int32[] stride, Int32[] pad, Int32[] outputPad, Tensor kernel, Tensor bias)
{
Layer layer = new Layer(name, convType);
layer.pad = pad;
layer.stride = stride;
layer.pool = outputPad;
layer.inputs = new [] {ResolveInput(input)};
layer.datasets = new Layer.DataSet[2];
layer.datasets[0].name = $"{name}/K";
layer.datasets[0].shape = kernel.shape;
layer.datasets[0].itemSizeInBytes = 4;
layer.datasets[0].length = kernel.shape.length;
layer.datasets[0].offset = 0;
layer.datasets[1].name = $"{name}/B";
layer.datasets[1].shape = bias.shape;
layer.datasets[1].itemSizeInBytes = 4;
layer.datasets[1].length = bias.shape.length;
layer.datasets[1].offset = kernel.shape.length;
Assert.AreEqual(kernel.dataType, bias.dataType);
layer.weights = new BarracudaArray(kernel.shape.length + bias.shape.length, kernel.dataType);
kernel.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, 0);
bias.ToReadOnlyArray().CopyToBarracudaArray(layer.weights, layer.datasets[1].offset);
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Apply a spatial 2D convolution on H and W.
/// Stride should be of size 2 and format is [W, H].
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
/// Kernel should be a tensor of shape [kernelHeight, kernelWidth, kernelDepth, kernelCount]
/// Bias should be a tensor with (batch == 1) and (height * width * channels == kernelCount)
///
/// Output batch is same as input.
/// Output channel is kernel.kernelCount.
/// output.shape[H,W] = (input.shape[H,W] + pad[1,0] + pad[3,2] - kernel.shape[1,0]) / stride[1,0] + 1.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="stride">stride</param>
/// <param name="pad">padding</param>
/// <param name="kernel">kernel weight data Tensor</param>
/// <param name="bias">bias data Tensor</param>
/// <returns>created Layer instance</returns>
public Layer Conv2D(string name, object input, Int32[] stride, Int32[] pad, Tensor kernel, Tensor bias)
{
return Conv(name, Layer.Type.Conv2D, input, stride, pad, new int[0], kernel, bias);
}
/// <summary>
/// Apply a spatial 3D convolution on H, W and D.
/// Stride should be of size 3 and format is [W, H, D].
/// Pad should be of size 6 and format is [pre W, pre H, pre D, post W, post H, post D].
/// Kernel should be a tensor of shape [kernelSpatialHeight, kernelSpatialWidth, kernelSpatialDepth, kernelDepth, kernelCount]
/// Bias should be a tensor with (batch == 1) and (height * width * channels == kernelCount)
///
/// Output batch is same as input.
/// Output channel is kernel.kernelCount.
/// output.shape[D,H,W] = (input.shape[D,H,W] + pad[2,1,0] + pad[5,4,3] - kernel.shape[2,1,0]) / stride[2,1,0] + 1.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="stride">stride</param>
/// <param name="pad">padding</param>
/// <param name="kernel">kernel weight data Tensor</param>
/// <param name="bias">bias data Tensor</param>
/// <returns>created Layer instance</returns>
public Layer Conv3D(string name, object input, Int32[] stride, Int32[] pad, Tensor kernel, Tensor bias)
{
return Conv(name, Layer.Type.Conv3D, input, stride, pad, new int[0], kernel, bias);
}
/// <summary>
/// Apply a spatial 2D depthwise convolution on H and W.
/// Stride should be of size 2 and format is [W, H].
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
/// Kernel should be a tensor of shape [kernelHeight, kernelWidth, kernelDepth, kernelCount]
/// Thus input must have a channel dimension of 1
/// Bias should be a tensor with (batch == 1) and (height * width * channels == kernelCount)
///
/// Output batch is same as input.
/// Output channel is kernel.shape[3].
/// output.shape[H,W] = (input.shape[H,W] + pad[1,0] + pad[3,2] - kernel.shape[1,0]) / stride[1,0] + 1.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="stride">stride</param>
/// <param name="pad">padding</param>
/// <param name="kernel">kernel weight data Tensor</param>
/// <param name="bias">bias data Tensor</param>
/// <returns>created Layer instance</returns>
public Layer DepthwiseConv2D(string name, object input, Int32[] stride, Int32[] pad, Tensor kernel, Tensor bias)
{
return Conv(name, Layer.Type.DepthwiseConv2D, input, stride, pad, new int[0], kernel, bias);
}
/// <summary>
/// Apply a spatial 2D transposed convolution on H and W.
/// Stride should be of size 2 and format is [W, H].
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
/// Kernel should be a tensor of rank 4 of dimensions [kernelHeight, kernelWidth, kernelDepth, kernelCount]
/// Bias should be a tensor with (batch == 1) and (height * width * channels == kernelCount)
/// OutputPad should be of length 0 or 2, format is [W, H].
/// If OutputPad length is 0 it will be defaulted to:
/// OutputPad[W,H] = (input.shape[W,H] * stride[0,1] + pad[0,1] + pad[2,3] - [kernelWidth, kernelHeight]) % stride[0,1]
///
/// Output batch is same as input.
/// Output channel is kernel.shape[3].
/// output.shape[H,W] = (input.shape[H,W]-1) * stride[0,1] - (pad[1,0] + pad[3,2]) + [kernelWidth, kernelHeight] + OutputPad[W,H]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="stride">stride</param>
/// <param name="pad">padding</param>
/// <param name="outputPad">output padding</param>
/// <param name="kernel">kernel weight data Tensor</param>
/// <param name="bias">bias data Tensor</param>
/// <returns>created Layer instance</returns>
public Layer Conv2DTrans(string name, object input, Int32[] stride, Int32[] pad, Int32[] outputPad, Tensor kernel, Tensor bias)
{
return Conv(name, Layer.Type.Conv2DTrans, input, stride, pad, outputPad, kernel, bias);
}
private Layer Pool(Layer.Type type, string name, object input, Int32[] pool, Int32[] stride, Int32[] pad)
{
Layer layer = new Layer(name, type);
layer.pad = pad;
layer.stride = stride;
layer.pool = pool;
layer.inputs = new [] {ResolveInput(input)};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Apply 'average' pooling by downscaling H and W dimension according to `pool`, `stride` and `pad`.
/// Pool and stride should be of size 2 and format is [W, H].
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
///
/// Output batch and channels dimensions the same as input.
/// output.shape[H,W] = (input.shape[H,W] + pad[1,0] + pad[3,2] - pool[1,0]) / stride[1,0] + 1.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="pool">pooling</param>
/// <param name="stride">stride</param>
/// <param name="pad">padding</param>
/// <returns>created Layer instance</returns>
public Layer AvgPool2D(string name, object input, Int32[] pool, Int32[] stride, Int32[] pad)
{
return Pool(Layer.Type.AvgPool2D, name, input, pool, stride, pad);
}
/// <summary>
/// Apply 'max' pooling by downscaling H and W dimension according to `pool`, `stride` and `pad`.
/// Pool and stride should be of size 2 and format is [W, H].
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
///
/// Output batch and channels dimensions the same as input.
/// output.shape[H,W] = (input.shape[H,W] + pad[1,0] + pad[3,2] - pool[1,0]) / stride[1,0] + 1.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="pool">pooling</param>
/// <param name="stride">stride</param>
/// <param name="pad">padding</param>
/// <returns>created Layer instance</returns>
public Layer MaxPool2D(string name, object input, Int32[] pool, Int32[] stride, Int32[] pad)
{
return Pool(Layer.Type.MaxPool2D, name, input, pool, stride, pad);
}
/// <summary>
/// Apply 'average' pooling by downscaling H and W dimension to [1,1]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer GlobalAvgPool2D(string name, object input)
{
return Pool(Layer.Type.GlobalAvgPool2D, name, input, new int[0], new int[0], new int[0]);
}
/// <summary>
/// Apply 'max' pooling by downscaling H and W dimension to [1,1]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer GlobalMaxPool2D(string name, object input)
{
return Pool(Layer.Type.GlobalMaxPool2D, name, input, new int[0], new int[0], new int[0]);
}
/// <summary>
/// Upsample the input tensor by scaling W and H by upsample[0] and upsample[1] respectively.
/// `bilinear` allow to choose between nearest neighbor or bilinear upsampling.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="upsample">upsampling</param>
/// <param name="bilinear">use bilinear</param>
/// <returns>created Layer instance</returns>
public Layer Upsample2D(string name, object input, Int32[] upsample, bool bilinear)
{
Layer layer = new Layer(name, Layer.Type.Upsample2D);
layer.pool = upsample;
layer.axis = bilinear ? 1: -1;
layer.inputs = new [] {ResolveInput(input)};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Upsample the input tensor
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="source">source input node</param>
/// <param name="scale">scale input node</param>
/// <param name="bilinear">use bilinear</param>
/// <returns>created Layer instance</returns>
public Layer Upsample2D(string name, object source, object scale, bool bilinear)
{
Layer layer = new Layer(name, Layer.Type.Upsample2D);
layer.axis = bilinear ? 1: -1;
layer.inputs = new[] { ResolveInput(source), ResolveInput(scale) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Upsample the input tensor by scaling W,H and D by upsample[0], upsample[1] and upsample[2] respectively.
/// `trilinear` allow to choose between nearest neighbor or trilinear upsampling.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="upsample">scaling factors array [W,H,D]</param>
/// <param name="trilinear">trilinear flag</param>
/// <returns>created Layer instance</returns>
public Layer Upsample3D(string name, object input, Int32[] upsample, bool trilinear)
{
Layer layer = new Layer(name, Layer.Type.Upsample3D);
layer.pool = upsample;
layer.axis = trilinear ? 1: -1;
layer.inputs = new [] {ResolveInput(input)};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Upsample the input tensor by scaling W,H and D by scale[0], scale[1] and scale[2] respectively.
/// `trilinear` allow to choose between nearest neighbor or trilinear upsampling.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="source">input node</param>
/// <param name="scale">scale Tensor</param>
/// <param name="trilinear">trilinear flag</param>
/// <returns>created Layer instance</returns>
public Layer Upsample3D(string name, object source, object scale, bool trilinear)
{
Layer layer = new Layer(name, Layer.Type.Upsample3D);
layer.axis = trilinear ? 1: -1;
layer.inputs = new[] { ResolveInput(source), ResolveInput(scale) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Resample2D scales the input tensor to the given resolution (W=size[0], H=size[1]).
/// `bilinear` allows to choose between nearest neighbour or bilinear sampling.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="size">size</param>
/// <param name="bilinear">use bilinear</param>
/// <returns>created Layer instance</returns>
public Layer Resample2D(string name, object input, Int32[] size, bool bilinear)
{
Layer layer = new Layer(name, Layer.Type.Resample2D);
layer.pool = size;
layer.axis = bilinear ? 1 : -1;
layer.inputs = new[] { ResolveInput(input) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Resample2D scales the input tensor to the given resolution (W=size[0], H=size[1]).
/// `bilinear` allows to choose between nearest neighbour or bilinear sampling.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="size">size tensor</param>
/// <param name="bilinear">use bilinear</param>
/// <returns>created Layer instance</returns>
internal Layer Resample2D(string name, object input, object size, bool bilinear)
{
Layer layer = new Layer(name, Layer.Type.Resample2D);
layer.axis = bilinear ? 1 : -1;
layer.inputs = new[] { ResolveInput(input), ResolveInput(size) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// DepthToSpace rearranges (permutes) data from depth into blocks of
/// spatial data. This is the reverse transformation of SpaceToDepth.
/// More specifically, this op outputs a copy of the input tensor where
/// values from the depth dimension are moved in spatial blocks to the
/// height and width dimensions. By default, mode = DCR. In the DCR mode,
/// elements along the depth dimension from the input tensor are rearranged
/// in the following order: depth, column, and then row.
/// In the CRD mode, elements along the depth dimension from the input
/// tensor are rearranged in the following order: column, row, and depth.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="source">input node</param>
/// <param name="blocksize">block size</param>
/// <param name="mode">mode, see `Layer.DepthToSpaceMode`</param>
/// <returns>created Layer instance</returns>
public Layer DepthToSpace(string name, object source, int blocksize, string mode)
{
Layer layer = new Layer(name, Layer.Type.DepthToSpace);
layer.pool = new int[] { blocksize, blocksize };
layer.axis = (int)(Layer.DepthToSpaceMode)Enum.Parse(typeof(Layer.DepthToSpaceMode), mode);
layer.inputs = new[] { ResolveInput(source) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// SpaceToDepth rearranges blocks of [blocksize, blocksize] spatial data into depth.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="source">input node</param>
/// <param name="blocksize">block size</param>
/// <returns>created Layer instance</returns>
public Layer SpaceToDepth(string name, object source, int blocksize)
{
Layer layer = new Layer(name, Layer.Type.SpaceToDepth);
layer.pool = new int[] { blocksize, blocksize };
layer.inputs = new[] { ResolveInput(source) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Apply symbolic shape to input tensor. Symbolic shape can have up to one dimension specified as unknown (value -1).
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="shape">shape</param>
/// <param name="rank">rank</param>
/// <returns>created Layer instance</returns>
public Layer Reshape(string name, object input, int[] shape, int rank = -1)
{
Layer layer = new Layer(name, Layer.Type.Reshape);
layer.pool = shape;
if (rank >= 0)
layer.pad = new[] { rank };
layer.inputs = new [] {ResolveInput(input)};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Creates a constant tensor populated with `value` as the same shape of `input`.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="value">value</param>
/// <returns>created Layer instance</returns>
public Layer ConstantOfShape(string name, object input, float value)
{
Layer layer = new Layer(name, Layer.Type.ConstantOfShape);
layer.inputs = new[] { ResolveInput(input) };
layer.alpha = value;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Apply shape to the input tensor. Number of elements in the shape must match number of elements in input tensor.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="shape">shape</param>
/// <returns>created Layer instance</returns>
public Layer Reshape(string name, object input, TensorShape shape)
{
return Reshape(name, input, shape.ToArray());
}
/// <summary>
/// Return a tensor of the shape given as tensor.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="shape">shape</param>
/// <returns>created Layer instance</returns>
public Layer Reshape(string name, object input, object shape)
{
Layer layer = new Layer(name, Layer.Type.Reshape);
layer.inputs = new [] {ResolveInput(input), ResolveInput(shape)};
layer.axis = 1; // Use tensor value as the shape; -1 is legacy for using the shape of input tensor
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Broadcast the input tensor following the given shape and similar to
/// numpy.array(input) * numpy.ones(shape). Two corresponding dimension
/// must have the same value, or the input dimension is 1.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="shape">shape</param>
/// <returns>created Layer instance</returns>
public Layer Expand(string name, object input, int[] shape)
{
Layer layer = new Layer(name, Layer.Type.Expand);
layer.inputs = new[] { ResolveInput(input) };
layer.pool = shape;
m_Model.layers.Add(layer);
return layer;
}
internal Layer Expand(string name, object input, object shape)
{
Layer layer = new Layer(name, Layer.Type.Expand);
layer.inputs = new[] { ResolveInput(input), ResolveInput(shape) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// From a Tensor of shape [S,R,N,T,D,H,W,C] return a tensor of shape [S,R,N,1,1,1,1,T*D*H*W*C]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Flatten(string name, object input)
{
Layer layer = new Layer(name, Layer.Type.Flatten);
layer.inputs = new [] {ResolveInput(input)};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Concatenate a list of tensors into a single tensor. All input tensors must have the same shape, except for the axis to concatenate on.
/// If axisIs8D==true axis rank is from [S,R,N,T,D,H,W,C] overwise from [N,H,W,C]
/// `axis` must be superior to -4
/// `axis` must be inferior to 8 when axisIs8D==true or inferior to 4 if axisIs8D==false
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input node</param>
/// <param name="axis">axis</param>
/// <param name="axisIs8D">is axis 8D</param>
/// <returns>created Layer instance</returns>
public Layer Concat(string name, object[] inputs, int axis = -1, bool axisIs8D=false)
{
Layer layer = new Layer(name, Layer.Type.Concat);
layer.axis = axisIs8D?axis:TensorExtensions.Convert4DTo8DAxis(axis);
layer.inputs = inputs.Select(i => ResolveInput(i)).ToArray();
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Produces a slice of the input tensor along all axes.
/// The following rules apply:
/// begin=0, end=0, stride=1: copy the full range of elements from the given axis
/// begin=A, end=B, stride=1: copy the range [A, B) (excluding the Bth element) from the given axis
/// begin=A, end=B, stride=I: copy every Ith element in the range [A, B) from the given axis
/// begin=N, end=N, stride=0: shrink axis to a single Nth element
/// output.shape[*] = (ends[*] - starts[*]) / max(1, stride[*])
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="starts">starts</param>
/// <param name="ends">ends</param>
/// <param name="strides">strides</param>
/// <returns>created Layer instance</returns>
public Layer StridedSlice(string name, object input, int[] starts, int[] ends, int[] strides)
{
Layer layer = new Layer(name, Layer.Type.StridedSlice);
layer.inputs = new [] {ResolveInput(input)};
layer.pad = starts;
layer.pool = ends;
layer.stride = strides;
m_Model.layers.Add(layer);
return layer;
}
internal Layer StridedSlice(string name, object input, int[] starts, int[] ends, int[] strides, int[] axes)
{
Layer layer = new Layer(name, Layer.Type.StridedSlice);
layer.inputs = new[] { ResolveInput(input) };
layer.pad = starts;
layer.pool = ends;
layer.stride = strides;
layer.axes = axes;
m_Model.layers.Add(layer);
return layer;
}
internal Layer StridedSlice(string name, object input, object starts, object ends, object strides, object axes)
{
Layer layer = new Layer(name, Layer.Type.StridedSlice);
List<string> inputs = new List<string> { ResolveInput(input), ResolveInput(starts), ResolveInput(ends) };
if (strides != null)
inputs.Add(ResolveInput(strides));
if (axes != null)
inputs.Add(ResolveInput(axes));
layer.inputs = inputs.ToArray();
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Constructs a tensor by repeating the input tensor the number of times given by repeats
/// For example input = [[1, 2], [3, 4]], repeats = [1, 2], Tile(input, repeats) = [[1, 2, 1, 2], [3, 4, 3, 4]]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="repeats">tile repeats</param>
/// <returns>created Layer instance</returns>
public Layer Tile(string name, object input, int[] repeats)
{
Layer layer = new Layer(name, Layer.Type.Tile);
layer.inputs = new[] { ResolveInput(input) };
layer.pool = repeats;
m_Model.layers.Add(layer);
return layer;
}
internal Layer Tile(string name, object input, object repeats)
{
Layer layer = new Layer(name, Layer.Type.Tile);
layer.inputs = new[] { ResolveInput(input), ResolveInput(repeats) };
//layer.pool = repeats;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Make a shallow copy of the input tensor.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Copy(string name, object input)
{
Layer layer = new Layer(name, Layer.Type.Nop);
layer.inputs = new [] {ResolveInput(input)};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Maps integer to one-hot vector of length equal to depth.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="depth">depth</param>
/// <param name="on">on value</param>
/// <param name="off">off value</param>
/// <returns>created Layer instance</returns>
public Layer OneHot(string name, object input, int depth, int on, int off)
{
Layer layer = new Layer(name, Layer.Type.OneHot);
layer.inputs = new [] {ResolveInput(input)};
layer.pool = new int[] { depth };
layer.alpha = on;
layer.beta = off;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Performs RoiAlign as described in the Mask R-CNN paper
/// </summary>
/// <param name="x">input</param>
/// <param name="roi">rois</param>
/// <param name="indices">batch indices</param>
/// <param name="outputHeight">outputHeight</param>
/// <param name="outputWidth">outputWidth</param>
/// <param name="samplingRatio">samplingRatio</param>
/// <param name="spatialScale">spatialScale</param>
/// <returns>output Tensor</returns>
public Layer RoiAlign(string name, object input, object rois, object batchIndices, int outputHeight, int outputWidth, int samplingRatio, float spatialScale)
{
Layer layer = new Layer(name, Layer.Type.RoiAlign);
layer.inputs = new[] { ResolveInput(input), ResolveInput(rois), ResolveInput(batchIndices) };
layer.pool = new int[] { outputHeight, outputWidth };
layer.axis = samplingRatio;
layer.alpha = spatialScale;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Retrieve the indices for top-K largest or smallest elements along a specified axis.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="k">k</param>
/// <param name="axis">axis</param>
/// <param name="largest">largest</param>
/// <param name="sorted">sorted</param>
/// <returns>created Layer instance</returns>
public Layer TopKIndices(string name, object input, object k, int axis, bool largest, bool sorted)
{
var layer = new Layer(name, Layer.Type.TopKIndices);
layer.inputs = new [] {ResolveInput(input), ResolveInput(k)};
layer.axis = axis;
layer.pad = new [] { largest ? 1 : 0, sorted ? 1 : 0 };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Given the indices for top-K largest or smallest elements along a specified axis, return the values
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="indices">indices node</param>
/// <param name="axis">axis</param>
/// <returns>created Layer instance</returns>
public Layer TopKValues(string name, object input, object indices, int axis)
{
var layer = new Layer(name, Layer.Type.TopKValues);
layer.inputs = new [] {ResolveInput(input), ResolveInput(indices)};
layer.axis = axis;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Returns the indices of the elements that are non-zero
/// For example an input tensor of shape(1,2,3,1):
/// [0, 2, 3],
/// [4, 1, 0]
///
/// Would return a tensor of shape(2, 1, 1, 4)
/// N = 2 as the rank of input tensor is 2.
/// C = 4 as there exist 3 non zero value in input tensor.
/// [0, 0, 1, 1],
/// [1, 2, 0, 1]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer NonZero(string name, object input)
{
var layer = new Layer(name, Layer.Type.NonZero);
layer.inputs = new [] {ResolveInput(input) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Transpose
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="permutations">list of axis permutations</param>
/// <returns>created Layer instance</returns>
public Layer Transpose(string name, object input, int[] permutations)
{
Layer layer = new Layer(name, Layer.Type.Transpose);
layer.inputs = new[] { ResolveInput(input) };
layer.pool = permutations;
m_Model.layers.Add(layer);
return layer;
}
internal Layer Squeeze(string name, object input, int[] axes)
{
Layer layer = new Layer(name, Layer.Type.Squeeze);
layer.inputs = new[] { ResolveInput(input) };
layer.pool = axes;
m_Model.layers.Add(layer);
return layer;
}
internal Layer Squeeze(string name, object input, object axes)
{
Layer layer = new Layer(name, Layer.Type.Squeeze);
layer.inputs = new[] { ResolveInput(input), ResolveInput(axes) };
m_Model.layers.Add(layer);
return layer;
}
internal Layer Unsqueeze(string name, object input, int[] axes)
{
Layer layer = new Layer(name, Layer.Type.Unsqueeze);
layer.inputs = new[] { ResolveInput(input) };
layer.pool = axes;
m_Model.layers.Add(layer);
return layer;
}
internal Layer Unsqueeze(string name, object input, object axes)
{
Layer layer = new Layer(name, Layer.Type.Unsqueeze);
layer.inputs = new[] { ResolveInput(input), ResolveInput(axes) };
m_Model.layers.Add(layer);
return layer;
}
private Layer Activation(Layer.Activation activation, string name, object input)
{
Layer layer = new Layer(name, activation);
layer.inputs = new [] {ResolveInput(input)};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// No-op layer
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="rank">input rank</param>
/// <returns>created Layer instance</returns>
public Layer Identity(string name, object input, int rank = -1)
{
Layer identity = Activation(Layer.Activation.None, name, input);
if (rank > 0)
identity.pad = new[] { rank };
return identity;
}
/// <summary>
/// Element-wise `Relu` activation function: f(x) = max(0, x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Relu(string name, object input)
{
return Activation(Layer.Activation.Relu, name, input);
}
/// <summary>
/// Element-wise `Pow` activation function: f(x) = pow(x, alpha)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="alpha">power input will be raised to</param>
/// <returns>created Layer instance</returns>
public Layer Pow(string name, object input, float alpha)
{
Layer layer = Activation(Layer.Activation.Pow, name, input);
layer.alpha = alpha;
return layer;
}
/// <summary>
/// Return the Softmax (normalized exponential) values of the input along provided axis.
/// Thus output will be of shape of the input.
/// If axisIs8D==true axis rank is from [S,R,N,T,D,H,W,C] otherwise from [N,H,W,C]
/// `axis` must be superior to -4
/// `axis` must be inferior to 8 when axisIs8D==true or inferior to 4 if axisIs8D==false
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="axis">axis</param>
/// <param name="axisIs8D">is axis 8D</param>
/// <returns>created Layer instance</returns>
public Layer Softmax(string name, object input, int axis=3, bool axisIs8D=false)
{
Layer layer = Activation(Layer.Activation.Softmax, name, input);
layer.axis = axisIs8D ? axis : TensorExtensions.Convert4DTo8DAxis(axis);
return layer;
}
/// <summary>
/// Return the logSoftmax (log of normalized exponential) values of the input along flatWidth of the input tensor.
/// Thus output will be of shape of the input.
/// If axisIs8D==true axis rank is from [S,R,N,T,D,H,W,C] otherwise from [N,H,W,C]
/// `axis` must be superior to -4
/// `axis` must be inferior to 8 when axisIs8D==true or inferior to 4 if axisIs8D==false
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="axis">axis</param>
/// <param name="axisIs8D">is axis 8D</param>
/// <returns>created Layer instance</returns>
public Layer LogSoftmax(string name, object input, int axis=3, bool axisIs8D=false)
{
Layer layer = Activation(Layer.Activation.LogSoftmax, name, input);
layer.axis = axisIs8D ? axis : TensorExtensions.Convert4DTo8DAxis(axis);
return layer;
}
/// <summary>
/// Element-wise `Sqrt` activation function
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Sqrt(string name, object input)
{
return Activation(Layer.Activation.Sqrt, name, input);
}
/// <summary>
/// Element-wise `Tanh` activation function: f(x) = (1 - e^{-2x})/(1 + e^{-2x})
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Tanh(string name, object input)
{
return Activation(Layer.Activation.Tanh, name, input);
}
/// <summary>
/// Element-wise `Softplus` activation function: f(x) = ln(e^{x} + 1)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Softplus(string name, object input)
{
return Activation(Layer.Activation.Softplus, name, input);
}
/// <summary>
/// Element-wise `Sigmoid` activation function: f(x) = 1/(1 + e^{-x})
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Sigmoid(string name, object input)
{
return Activation(Layer.Activation.Sigmoid, name, input);
}
/// <summary>
/// Element-wise `HardSigmoid` activation function: f(x) = maX(0, min(1, a * x + b))
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="alpha">alpha</param>
/// <param name="beta">beta</param>
/// <returns>created Layer instance</returns>
public Layer HardSigmoid(string name, object input, float alpha = 0.2f, float beta = 0.5f)
{
Layer layer = new Layer(name, Layer.Activation.HardSigmoid);
layer.inputs = new[] { ResolveInput(input) };
layer.alpha = alpha;
layer.beta = beta;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Element-wise `Elu` activation function: f(x) = x if x >= 0 else alpha*(e^x - 1)
/// alpha default is 1.0
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="alpha">alpha</param>
/// <returns>created Layer instance</returns>
public Layer Elu(string name, object input, float alpha = 1.0f)
{
var layer = Activation(Layer.Activation.Elu, name, input);
layer.alpha = alpha;
return layer;
}
/// <summary>
/// Element-wise `Relu6` activation function. f(x) = min(max(x, 0), 6)
/// see http://www.cs.utoronto.ca/~kriz/conv-cifar10-aug2010.pdf
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Relu6(string name, object input)
{
return Activation(Layer.Activation.Relu6, name, input);
}
/// <summary>
/// Element-wise `LeakyRelu` activation function: f(x) = x if x >= 0 else alpha * x
/// alpha default is 0.01
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="alpha">alpha</param>
/// <returns>created Layer instance</returns>
public Layer LeakyRelu(string name, object input, float alpha = 0.01f)
{
var layer = Activation(Layer.Activation.LeakyRelu, name, input);
layer.alpha = alpha;
return layer;
}
/// <summary>
/// Element-wise `Selu` activation function: f(x) = gamma * x if x >= 0 else (alpha * e^x - alpha)
/// alpha default is 1.67326
/// gamma default is 1.0507
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="alpha">alpha</param>
/// <param name="gamma">gamma</param>
/// <returns>created Layer instance</returns>
public Layer Selu(string name, object input, float alpha = 1.67326f, float gamma = 1.0507f)
{
var layer = Activation(Layer.Activation.Selu, name, input);
layer.alpha = alpha;
layer.beta = gamma;
return layer;
}
/// <summary>
/// Element-wise `PRelu` activation function: f(x) = x if x >= 0 else slope * x
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="slope">slope input node</param>
/// <returns>created Layer instance</returns>
public Layer PRelu(string name, object input, object slope)
{
object[] inputs = new [] {input, slope};
Layer layer = new Layer(name, Layer.Activation.PRelu);
layer.inputs = inputs.Select(i => ResolveInput(i)).ToArray();
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Element-wise `Swish` activation function. f(x) = sigmoid(x) * x = x/(1 + e^{-x})
/// see https://arxiv.org/abs/1710.05941
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Swish(string name, object input)
{
return Activation(Layer.Activation.Swish, name, input);
}
/// <summary>
/// Element-wise `Clip` function that limits values within an interval: f(x, xmin, xmax) = min(max(x, xmin), xmax)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="min">min</param>
/// <param name="max">max</param>
/// <returns>created Layer instance</returns>
public Layer Clip(string name, object input, float min, float max)
{
var layer = Activation(Layer.Activation.Clip, name, input);
layer.alpha = min;
layer.beta = max;
return layer;
}
/// <summary>
/// Element-wise `Exp` function that calculates exponential of the input: f(x) = e^{x}
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Exp(string name, object input)
{
return Activation(Layer.Activation.Exp, name, input);
}
/// <summary>
/// Element-wise `Log` function that calculates the natural log of the input: f(x) = log(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Log(string name, object input)
{
return Activation(Layer.Activation.Log, name, input);
}
/// <summary>
/// Element-wise function that flips the sign of the input: f(x) = -x
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Neg(string name, object input)
{
return Activation(Layer.Activation.Neg, name, input);
}
/// <summary>
/// Element-wise function that calculates reciprocal of the input: f(x) = 1/x
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Reciprocal(string name, object input)
{
return Activation(Layer.Activation.Reciprocal, name, input);
}
/// <summary>
/// Element-wise function that calculates absolute values of the input: f(x) = abs(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Abs(string name, object input)
{
return Activation(Layer.Activation.Abs, name, input);
}
/// <summary>
/// Element-wise function that produces rounding towards the greatest integer less than or equal to the input value: f(x) = ceil(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Ceil(string name, object input)
{
return Activation(Layer.Activation.Ceil, name, input);
}
/// <summary>
/// Element-wise function that produces rounding towards least integer greater than or equal to the input value: f(x) = floor(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Floor(string name, object input)
{
return Activation(Layer.Activation.Floor, name, input);
}
/// <summary>
/// Element-wise function that produces rounding of the input value: f(x) = round(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Round(string name, object input)
{
return Activation(Layer.Activation.Round, name, input);
}
/// <summary>
/// Element-wise `Acos` activation function: f(x) = acos(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Acos(string name, object input)
{
return Activation(Layer.Activation.Acos, name, input);
}
/// <summary>
/// Element-wise `Acosh` activation function: f(x) = acosh(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Acosh(string name, object input)
{
return Activation(Layer.Activation.Acosh, name, input);
}
/// <summary>
/// Element-wise `Asin` activation function: f(x) = asin(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Asin(string name, object input)
{
return Activation(Layer.Activation.Asin, name, input);
}
/// <summary>
/// Element-wise `Asinh` activation function: f(x) = asinh(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Asinh(string name, object input)
{
return Activation(Layer.Activation.Asinh, name, input);
}
/// <summary>
/// Element-wise `Atan` activation function: f(x) = atan(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Atan(string name, object input)
{
return Activation(Layer.Activation.Atan, name, input);
}
/// <summary>
/// Element-wise `Atanh` activation function: f(x) = atanh(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Atanh(string name, object input)
{
return Activation(Layer.Activation.Atanh, name, input);
}
/// <summary>
/// Element-wise `Cos` activation function: f(x) = cos(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Cos(string name, object input)
{
return Activation(Layer.Activation.Cos, name, input);
}
/// <summary>
/// Element-wise `Cosh` activation function: f(x) = cosh(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Cosh(string name, object input)
{
return Activation(Layer.Activation.Cosh, name, input);
}
/// <summary>
/// Element-wise `Sin` activation function: f(x) = sin(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Sin(string name, object input)
{
return Activation(Layer.Activation.Sin, name, input);
}
/// <summary>
/// Element-wise `Sinh` activation function: f(x) = sinh(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Sinh(string name, object input)
{
return Activation(Layer.Activation.Sinh, name, input);
}
/// <summary>
/// Element-wise `Tan` activation function: f(x) = tan(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Tan(string name, object input)
{
return Activation(Layer.Activation.Tan, name, input);
}
/// <summary>
/// Element-wise `Erf` activation function: f(x) = erf(x)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Erf(string name, object input)
{
return Activation(Layer.Activation.Erf, name, input);
}
private Layer Broadcast(Layer.Type type, string name, object[] inputs)
{
Layer layer = new Layer(name, type);
layer.inputs = inputs.Select(i => ResolveInput(i)).ToArray();
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Element-wise `add` of each of the input tensors with multidimensional broadcasting support.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Add(string name, object[] inputs)
{
return Broadcast(Layer.Type.Add, name, inputs);
}
/// <summary>
/// Element-wise `sub` of each of the input tensors with multidimensional broadcasting support.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Sub(string name, object[] inputs)
{
return Broadcast(Layer.Type.Sub, name, inputs);
}
/// <summary>
/// Element-wise multiplication of each of the input tensors with multidimensional broadcasting support.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Mul(string name, object[] inputs)
{
return Broadcast(Layer.Type.Mul, name, inputs);
}
/// <summary>
/// Element-wise division of each of the input tensors with multidimensional broadcasting support.
/// First element is divided by the 2nd, then result is divided by the third one and so on.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Div(string name, object[] inputs)
{
return Broadcast(Layer.Type.Div, name, inputs);
}
/// <summary>
/// Element-wise pow of each of the input tensors with multidimensional broadcasting support.
/// First element get raised to the pow of the 2nd, then result is raised to the pow of the third one and so on.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Pow(string name, object[] inputs)
{
return Broadcast(Layer.Type.Pow, name, inputs);
}
/// <summary>
/// Element-wise `min` of each of the input tensors with multidimensional broadcasting support.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Min(string name, object[] inputs)
{
return Broadcast(Layer.Type.Min, name, inputs);
}
/// <summary>
/// Element-wise `max` of each of the input tensors with multidimensional broadcasting support.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Max(string name, object[] inputs)
{
return Broadcast(Layer.Type.Max, name, inputs);
}
/// <summary>
/// Element-wise `mean` of each of the input tensors with multidimensional broadcasting support.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="inputs">input nodes</param>
/// <returns>created Layer instance</returns>
public Layer Mean(string name, object[] inputs)
{
return Broadcast(Layer.Type.Mean, name, inputs);
}
/// <summary>
/// Performs a `greater` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer Greater(string name, object input0, object input1)
{
return Broadcast(Layer.Type.Greater, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `greaterEqual` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer GreaterEqual(string name, object input0, object input1)
{
return Broadcast(Layer.Type.GreaterEqual, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `less` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer Less(string name, object input0, object input1)
{
return Broadcast(Layer.Type.Less, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `less equal` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer LessEqual(string name, object input0, object input1)
{
return Broadcast(Layer.Type.LessEqual, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `equal` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer Equal(string name, object input0, object input1)
{
return Broadcast(Layer.Type.Equal, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `and` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// Input is consider false if 0.0 elementwise true otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer LogicalAnd(string name, object input0, object input1)
{
return Broadcast(Layer.Type.LogicalAnd, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `or` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// Input is consider false if 0.0 elementwise true otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer LogicalOr(string name, object input0, object input1)
{
return Broadcast(Layer.Type.LogicalOr, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `xor` logical operation elementwise on the input tensors with multidimensional broadcasting support.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// Input is consider false if 0.0 elementwise true otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input0">left input node</param>
/// <param name="input1">right input node</param>
/// <returns>created Layer instance</returns>
public Layer LogicalXor(string name, object input0, object input1)
{
return Broadcast(Layer.Type.LogicalXor, name, new [] {input0, input1});
}
/// <summary>
/// Performs a `not` logical operation elementwise on the input tensor.
/// Return 1.0 elementwise if condition is true 0.0 otherwise.
/// Input is consider false if 0.0 elementwise true otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer LogicalNot(string name, object input)
{
Layer layer = new Layer(name, Layer.Type.LogicalNot);
layer.inputs = new[] { ResolveInput(input) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Performs a `sign` operation elementwise on the input tensor.
/// Return 1.0 elementwise if x > 0 else -1.0 if x < 0 else 0.0
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <returns>created Layer instance</returns>
public Layer Sign(string name, object input)
{
Layer layer = new Layer(name, Layer.Type.Sign);
layer.inputs = new[] { ResolveInput(input) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Return elements, either from X or Y, depending on condition (with broadcasting support, based on the shape of the condition)
/// Return X elementwise if condition is true Y otherwise.
/// Input is consider false if 0.0 elementwise true otherwise.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="condition">condition</param>
/// <param name="input1">first input</param>
/// <param name="input2">second input</param>
/// <returns>created Layer instance</returns>
public Layer Where(string name, object condition, object input1, object input2)
{
Layer layer = new Layer(name, Layer.Type.Where);
layer.inputs = new[] { ResolveInput(condition), ResolveInput(input1), ResolveInput(input2) };
m_Model.layers.Add(layer);
return layer;
}
// Generic-ONNX style pad
internal Layer Pad(string name, object input, object pad, object value, Layer.PadMode mode, Layer.AutoPad autoPadMode)
{
Layer layer = new Layer(name, Layer.Type.Pad);
var valuestring = ResolveInput(value);
if (string.IsNullOrEmpty(valuestring))
{
layer.inputs = new[] { ResolveInput(input), ResolveInput(pad) };
layer.beta = 0.0f;
}
else
layer.inputs = new[] { ResolveInput(input), ResolveInput(pad), ResolveInput(value) };
layer.axis = (int)mode;
layer.pool = new[] { (int)autoPadMode };
m_Model.layers.Add(layer);
return layer;
}
internal Layer Pad(string name, object input, Int32[] pad, float constantValue, Layer.PadMode mode, Layer.AutoPad autoPadMode)
{
Layer layer = new Layer(name, Layer.Type.Pad);
layer.inputs = new[] { ResolveInput(input) };
layer.beta = constantValue;
layer.pad = pad;
layer.axis = (int)mode;
layer.pool = new[] { (int)autoPadMode };
m_Model.layers.Add(layer);
return layer;
}
// known Layer.Type
internal Layer Pad(Layer.Type type, string name, object input, Int32[] pad, float constantValue = 0.0f)
{
Layer layer = new Layer(name, type);
layer.inputs = new[] { ResolveInput(input) };
layer.beta = constantValue;
layer.pad = pad;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Pads H and W dimension with a given constant value (default to 0).
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
/// If pad contain negative values H and W dimensions will be cropped instead.
///
/// For example a tensor of shape(1,2,3,1)
/// [1, 2, 3],
/// [4, 5, 6]
///
/// With pad [2, 1, 2, 1]
///
/// Result in a tensor of shape(1,4,7,1)
/// [0, 0, 0, 0, 0, 0, 0],
/// [0, 0, 1, 2, 3, 0, 0],
/// [0, 0, 4, 5, 6, 0, 0],
/// [0, 0, 0, 0, 0, 0, 0]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="pad">padding</param>
/// <param name="constantValue">border constant value</param>
/// <returns>created Layer instance</returns>
public Layer Border2D(string name, object input, Int32[] pad, float constantValue = 0.0f)
{
return Pad(Layer.Type.Border2D, name, input, pad, constantValue);
}
/// <summary>
/// Pads D,H and W dimension with a given constant value (default to 0).
/// Pad should be of size 6 and format is [pre W, pre H, pre D, post W, post H, post D].
/// If pad contain negative values H and W dimensions will be cropped instead.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="pad">padding</param>
/// <param name="constantValue">constant value to use for border</param>
/// <returns>created Layer instance</returns>
public Layer Border3D(string name, object input, Int32[] pad, float constantValue = 0.0f)
{
return Pad(Layer.Type.Border3D, name, input, pad, constantValue);
}
/// <summary>
/// Pads H and W dimension by repeating the edge values of the input.
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
///
/// For example a tensor of shape(1,2,3,1):
/// [1, 2, 3],
/// [4, 5, 6]
///
/// With pad [2, 1, 2, 1]
///
/// Result in a tensor of shape(1,4,7,1)
/// [1, 1, 1, 2, 3, 3, 3],
/// [1, 1, 1, 2, 3, 3, 3],
/// [4, 4, 4, 5, 6, 6, 6],
/// [4, 4, 4, 5, 6, 6, 6]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="pad">padding</param>
/// <returns>created Layer instance</returns>
public Layer Pad2DEdge(string name, object input, Int32[] pad)
{
return Pad(Layer.Type.Pad2DEdge, name, input, pad);
}
/// <summary>
/// Pads H and W dimension by mirroring on the first and last values along those axis.
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
///
/// For example a tensor of shape(1,2,3,1):
/// [1, 2, 3],
/// [4, 5, 6]
///
/// With pad [2, 1, 2, 1]
///
/// Result in a tensor of shape(1,4,7,1)
/// [6, 5, 4, 5, 6, 5, 4],
/// [3, 2, 1, 2, 3, 2, 1],
/// [6, 5, 4, 5, 6, 5, 4],
/// [3, 2, 1, 2, 3, 2, 1]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="pad">padding</param>
/// <returns>created Layer instance</returns>
public Layer Pad2DReflect(string name, object input, Int32[] pad)
{
return Pad(Layer.Type.Pad2DReflect, name, input, pad);
}
/// <summary>
/// Pads H and W dimension with symmetric replication along those axis.
/// Pad should be of size 4 and format is [pre W, pre H, post W, post H].
///
/// For example a tensor of shape(1,2,3,1):
/// [1, 2, 3],
/// [4, 5, 6]
///
/// With pad [2, 1, 2, 1]
///
/// Result in a tensor of shape(1,4,7,1)
/// [2, 1, 1, 2, 3, 3, 2],
/// [2, 1, 1, 2, 3, 3, 2],
/// [5, 4, 4, 5, 6, 6, 5],
/// [5, 4, 4, 5, 6, 6, 5]
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="pad">padding</param>
/// <returns>created Layer instance</returns>
public Layer Pad2DSymmetric(string name, object input, Int32[] pad)
{
return Pad(Layer.Type.Pad2DSymmetric, name, input, pad);
}
/// <summary>
/// Generates a Tensor with random values drawn from a normal distribution.
/// The shape of the tensor is specified by input tensor
/// The normal distribution is specified by mean and scale
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="mean">mean</param>
/// <param name="scale">scale</param>
/// <param name="seed">seed</param>
/// <returns>created Layer instance</returns>
public Layer RandomNormal(string name, object input, float mean, float scale, float seed)
{
Assert.IsFalse(input is TensorShape); // TensorShape must be handled by separate RandomNormal(name, shape...) implementation
Layer layer = new Layer(name, Layer.Type.RandomNormal);
layer.inputs = new[] { ResolveInput(input) };
layer.alpha = scale;
layer.beta = mean;
layer.pad = new int[1] {(int)seed};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Generates a Tensor with random values drawn from a normal distribution.
/// The shape of the tensor is specified by scale
/// The normal distribution is specified by mean and scale
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="shape">shape</param>
/// <param name="mean">mean</param>
/// <param name="scale">scale</param>
/// <param name="seed">seed</param>
/// <returns>created Layer instance</returns>
public Layer RandomNormal(string name, TensorShape shape, float mean, float scale, float seed)
{
Layer layer = new Layer(name, Layer.Type.RandomNormal);
layer.alpha = scale;
layer.beta = mean;
layer.pad = new int[1] {(int)seed};
layer.pool = shape.ToArray();
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Generates a Tensor with random values drawn from a uniform distribution.
/// The shape of the tensor is specified by input tensor
/// The uniform distribution scale is specified by min and max range
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="min">min</param>
/// <param name="max">max</param>
/// <param name="seed">seed</param>
/// <returns>created Layer instance</returns>
public Layer RandomUniform(string name, object input, float min, float max, float seed)
{
Assert.IsFalse(input is TensorShape); // TensorShape must be handled by separate RandomUniform(name, shape...) implementation
Layer layer = new Layer(name, Layer.Type.RandomUniform);
layer.inputs = new[] { ResolveInput(input) };
layer.alpha = (max-min);
layer.beta = min;
layer.pad = new int[1] {(int)seed};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Generates a Tensor with random values drawn from a uniform distribution.
/// The shape of the tensor is specified by shape
/// The uniform distribution scale is specified by min and max range
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="shape">shape</param>
/// <param name="min">min</param>
/// <param name="max">max</param>
/// <param name="seed">seed</param>
/// <returns>created Layer instance</returns>
public Layer RandomUniform(string name, TensorShape shape, float min, float max, float seed)
{
Layer layer = new Layer(name, Layer.Type.RandomUniform);
layer.alpha = (max-min);
layer.beta = min;
layer.pad = new int[1] {(int)seed};
layer.pool = shape.ToArray();
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Generate a Tensor with random samples drawn from a multinomial distribution according to the probabilities of each of the possible outcomes.
/// Output batch is same as input.
/// Output channel is `numberOfSamplesDrawnPerInputChannel`.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="numberOfSamplesDrawnPerInputChannel">number of samples drawn per input channel</param>
/// <param name="seed">seed</param>
/// <returns>created Layer instance</returns>
public Layer Multinomial(string name, object input, int numberOfSamplesDrawnPerInputChannel, float seed)
{
Layer layer = new Layer(name, Layer.Type.Multinomial);
layer.inputs = new[] { ResolveInput(input) };
layer.pad = new int[1] {(int)seed};
layer.pool = new int[1] {numberOfSamplesDrawnPerInputChannel};
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Computes a reduce operation (max/min/mean/prod/sum) of the input tensor's element along the provided axis
/// If axisIs8D==true axis rank is from [S,R,N,T,D,H,W,C] overwise from [N,H,W,C]
/// `axis` must be superior to -4
/// `axis` must be inferior to 8 when axisIs8D==true or inferior to 4 if axisIs8D==false
/// </summary>
/// <param name="type">operation type</param>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="axis">axis</param>
/// <param name="axisIs8D">is axis 8D</param>
/// <param name="keepDims">is shape rank reduced</param>
/// <returns>created Layer instance</returns>
public Layer Reduce(Layer.Type type, string name, object input, int axis = -1, bool axisIs8D=false, int keepDims = 1)
{
Layer layer = new Layer(name, type);
layer.inputs = new[] { ResolveInput(input) };
layer.axis = axisIs8D?axis:TensorExtensions.Convert4DTo8DAxis(axis);
layer.alpha = keepDims;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Generate a tensor containing a sequence of numbers that begin at `start` and extends by increments of `delta` up to `limit` (exclusive).
/// the number of elements are defined as follows:
/// number_of_elements = max( ceil( (limit - start) / delta ) , 0 )
/// output is calculated as follows:
/// output[i] = start + (i * delta)
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="start">start</param>
/// <param name="limit">limit</param>
/// <param name="delta">delta</param>
/// <returns>created Layer instance</returns>
public Layer Range(string name, object start, object limit, object delta)
{
Layer layer = new Layer(name, Layer.Type.Range);
layer.inputs = new[] { ResolveInput(start), ResolveInput(limit), ResolveInput(delta) };
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Gathers input along provided axis. Swizzling pattern is given by input indices:
/// If axisIs8D==false
/// axis == 0: gatheredData[b, y, x, c] = data[indices[b], y, x, c]
/// axis == 1: gatheredData[b, y, x, c] = data[b, indices[y], x, c]
/// ...
/// Else
/// axis == 0: gatheredData[s, r, n, t, d, y, x, c] = data[indices[s], r, n, t, d, y, x, c]
/// axis == 1: gatheredData[s, r, n, t, d, y, x, c] = data[indices[s], indices[y], n, t, d, y, x, c]
/// ...
/// While in both case
/// axis == -1: gatheredData[..., x, c] = data[...x, indices[c]]
/// `axis` must be superior to -4
/// `axis` must be inferior to 8 when axisIs8D==true or inferior to 4 if axisIs8D==false
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="indices">indices</param>
/// <param name="axis">axis</param>
/// <param name="axisIs8D">is axis 8D</param>
/// <returns>created Layer instance</returns>
public Layer Gather(string name, object input, object indices, int axis = -1, bool axisIs8D=false)
{
object[] inputs = new[] { input, indices };
Layer layer = new Layer(name, Layer.Type.Gather);
layer.inputs = inputs.Select(i => ResolveInput(i)).ToArray();
layer.axis = axisIs8D?axis:TensorExtensions.Convert4DTo8DAxis(axis);
m_Model.layers.Add(layer);
return layer;
}
public Layer ScatterND(string name, object input, object indices, object updates, Layer.ScatterNDReductionMode reductionType)
{
Layer layer = new Layer(name, Layer.Type.ScatterND);
layer.inputs = new[] { ResolveInput(input), ResolveInput(indices), ResolveInput(updates) };
layer.axis = (int)reductionType;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// Filter out boxes that have high intersection-over-union (IOU) overlap with previously selected boxes.
/// Bounding boxes with score less than scoreThreshold are removed.
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="boxes">boxes input node</param>
/// <param name="scores">scores input node</param>
/// <param name="maxOutputBoxesPerClass">max output boxes per class input node</param>
/// <param name="iouThreshold">IOU threshold input node</param>
/// <param name="scoreThreshold">score input node</param>
/// <param name="centerPointBox">center point box</param>
/// <returns>created Layer instance</returns>
public Layer NonMaxSuppression(string name, object boxes, object scores, object maxOutputBoxesPerClass,
object iouThreshold, object scoreThreshold, int centerPointBox)
{
var layer = new Layer(name, Layer.Type.NonMaxSuppression);
if (maxOutputBoxesPerClass is float bpc && iouThreshold is float iou && scoreThreshold is float score)
{
layer.inputs = new[] { ResolveInput(boxes), ResolveInput(scores) };
layer.pool = new[] { (int)bpc };
layer.alpha = iou;
layer.beta = score;
}
else
{
layer.inputs = new []
{
ResolveInput(boxes), ResolveInput(scores), ResolveInput(maxOutputBoxesPerClass),
ResolveInput(iouThreshold), ResolveInput(scoreThreshold)
};
}
layer.axis = centerPointBox;
m_Model.layers.Add(layer);
return layer;
}
/// <summary>
/// LSTM
/// </summary>
/// <param name="name">Layer name</param>
/// <param name="input">input node</param>
/// <param name="outputs">output nodes</param>
/// <param name="w">W data</param>
/// <param name="r">R data</param>
/// <param name="b">B data (optional)</param>
/// <param name="hiddenSize">Number of neurons in the hidden layer</param>
/// <param name="initialHidden">Initial value of the hidden layer (optional)</param>
/// <param name="initialCell">Initial value of the hidden layer (optional)</param>
/// <returns>created Layer instances</returns>
public Layer[] LSTM(string name, object input, string[] outputs, object w, object r, object b, int hiddenSize,
object initialHidden = null, object initialCell = null)
{
Layer layer = new Layer(name, Layer.Type.LSTM);
// LSTM's first output may not be used (Y), but we need to preserve the layer regardless, so any additional outputs get computed
layer.flags |= Layer.Flags.Preserve;
string layerHidden = $"{name}_wm_h";
string layerCell = $"{name}_wm_c";
if (initialHidden == null)
{
// Add memory inputs (if not specified) since they are used as inputs to this layer (will be initialized to 0)
initialHidden = layerHidden;
}
else
{
// We don't support directions (i.e. only forward direction) and have built the implementation around
// removing direction axes from W,R,B to allow for 2D matrix multiplications.
// [num_directions, batch_size, hidden_size] NCH -> [batch_size, hidden_size] CH
initialHidden = Transpose($"{layerHidden}_for_{name}", initialHidden, new[] { 1, 2, 0 });
}
if (initialCell == null)
{
// Add memory inputs (if not specified) since they are used as inputs to this layer (will be initialized to 0)
initialCell = layerCell;
}
else
{
// We don't support directions (i.e. only forward direction) and have built the implementation around
// removing direction axes from W,R,B to allow for 2D matrix multiplications.
// [num_directions, batch_size, hidden_size] NCH -> [batch_size, hidden_size] CH
initialCell = Transpose($"{layerCell}_for_{name}", initialCell, new[] { 1, 2, 0 });
}
m_Model.layers.Add(layer);
Layer stateHidden = Transpose(outputs[1] ?? $"{name}_Y_h", layerHidden, new[] { 2, 0, 1 }); // Y_h
Layer stateCell = Transpose(outputs[2] ?? $"{name}_Y_c", layerCell, new[] { 2, 0, 1 }); // Y_c
// LSTM-node working memory (if no input was specified) and additional outputs
Memory(layerHidden, stateHidden, new TensorShape(-1, 1, 1, hiddenSize));
Memory(layerCell, stateCell, new TensorShape(-1, 1, 1, hiddenSize));
var inputs = new List<string>();
inputs.Add(ResolveInput(input));
if (w is Tensor W && r is Tensor R && b is Tensor B)
{
OpsUtils.BakeConstantWRBIntoLSTMLayer(layer, W, R, B);
}
else
{
// Dynamic input
inputs.Add(ResolveInput(w));
inputs.Add(ResolveInput(r));
inputs.Add(ResolveInput(b));
}
inputs.Add(ResolveInput(initialHidden));
inputs.Add(ResolveInput(initialCell));
layer.inputs = inputs.ToArray();
layer.pool = new[] { hiddenSize };
return new [] { layer, stateHidden, stateCell };
}
}
}