.gradient.grad_helper

class experimental_experiment.gradient.grad_helper.DerivativeOptions(*values)[source]

Options defining how to build the onnx graph of the gradients.

  • Zero: default option, all options are disabled

  • KeepYieldOp: keeps the operator YieldOp in the graph, see @see fn onnx_derivative

  • KeepOutputs: keeps the output of the original graph

  • FillGrad: does not add any output to specify the gradient of the output but assumes it is one

  • Loss: the function assumes the loss was added to the graph

experimental_experiment.gradient.grad_helper.onnx_derivative(onx: ~onnx.onnx_ml_pb2.ModelProto, weights: ~typing.List[str] | None = None, inputs: ~typing.List[str] | None = None, options: ~experimental_experiment.gradient.grad_helper.DerivativeOptions = <DerivativeOptions.Zero: 0>, loss: str | None = None, label: str | None = None, path_name: str | None = None, verbose: int = 0) ModelProto[source]

Builds the gradient for an onnx graph.

Parameters:
  • onx – onnx graph

  • weights – gradient against those weights, None for all real weights

  • inputs – gradient against inputs, None for all real inputs

  • options – options of type @see cl DerivativeOptions

  • loss – loss output in case a loss was added in the graph, options must be equal to DerivativeOptions.Loss

  • label – if loss is specified, then the label must be specified as well

  • path_name – if options equal to DerivativeOptions.Loss, the gradient is saved to that path

  • verbose – verbosity

Returns:

onnx graph

The function calls OrtModuleGraphBuilderConfiguration from onnxruntime-training. This graph is meant to be used with OrtGradientForwardBackward and includes operator YieldOp. That’s the graph looks this way:

import numpy as np
from onnx.defs import onnx_opset_version
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx.algebra.onnx_ops import OnnxAdd
from experimental_experiment.doc import to_dot
from experimental_experiment.gradient.grad_helper import (
    onnx_derivative,
    DerivativeOptions,
)

opv = onnx_opset_version() - 2

node = OnnxAdd(
    "X",
    np.array([1], dtype=np.float32),
    op_version=opv,
    output_names=["Y"]
)
onx = node.to_onnx(
    {"X": FloatTensorType([None, 10])},
    {"Y": FloatTensorType([None, 10])},
    target_opset=opv,
)
try:
    new_onx = onnx_derivative(onx, options=DerivativeOptions.KeepYieldOp)
except ImportError as e:
    print("onnxruntime-training is not installed", e)
    new_onx = None
if new_onx:
    print("DOT-SECTION", to_dot(new_onx))

These operators are the outputs of the initial graph and must be replaced by the gradient of these outputs to compute the gradient of the weights and the inputs. After they are replaced, it looks this way:

import numpy as np
from onnx.defs import onnx_opset_version
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx.algebra.onnx_ops import OnnxAdd
from experimental_experiment.doc import to_dot
from experimental_experiment.gradient.grad_helper import (
    onnx_derivative,
    DerivativeOptions,
)

opv = onnx_opset_version() - 2

node = OnnxAdd(
    "X",
    np.array([1], dtype=np.float32),
    op_version=opv,
    output_names=["Y"]
)
onx = node.to_onnx(
    {"X": FloatTensorType([None, 10])},
    {"Y": FloatTensorType([None, 10])},
    target_opset=opv,
)
try:
    new_onx = onnx_derivative(onx, options=DerivativeOptions.Zero)
except ImportError as e:
    print("onnxruntime-training is not installed", e)
    new_onx = None
if new_onx:
    print("DOT-SECTION", to_dot(new_onx))

The user can still compute the outputs.

import numpy as np
from onnx.defs import onnx_opset_version
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx.algebra.onnx_ops import OnnxAdd
from experimental_experiment.doc import to_dot
from experimental_experiment.gradient.grad_helper import (
    onnx_derivative,
    DerivativeOptions,
)

opv = onnx_opset_version() - 2

node = OnnxAdd(
    "X",
    np.array([1], dtype=np.float32),
    op_version=opv,
    output_names=["Y"]
)
onx = node.to_onnx(
    {"X": FloatTensorType([None, 10])},
    {"Y": FloatTensorType([None, 10])},
    target_opset=opv,
)
try:
    new_onx = onnx_derivative(onx, options=DerivativeOptions.KeepOutputs)
except ImportError as e:
    print("onnxruntime-training is not installed", e)
    new_onx = None
if new_onx:
    print("DOT-SECTION", to_dot(new_onx))

The input gradient can be filled with a constant matrix filled with one and with the expected shape.

import numpy as np
from onnx.defs import onnx_opset_version
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx.algebra.onnx_ops import OnnxAdd
from experimental_experiment.doc import to_dot
from experimental_experiment.gradient.grad_helper import (
    onnx_derivative,
    DerivativeOptions,
)

opv = onnx_opset_version() - 2

node = OnnxAdd(
    "X",
    np.array([1], dtype=np.float32),
    op_version=opv,
    output_names=["Y"]
)
onx = node.to_onnx(
    {"X": FloatTensorType([None, 10])},
    {"Y": FloatTensorType([None, 10])},
    target_opset=opv,
)
try:
    new_onx = onnx_derivative(
        onx, options=DerivativeOptions.KeepOutputs | DerivativeOptions.FillGrad
    )
except ImportError as e:
    print("onnxruntime-training is not installed", e)
    new_onx = None
if new_onx:
    print("DOT-SECTION", to_dot(new_onx))
experimental_experiment.gradient.grad_helper.random_feed(inputs, batch: int = 10, empty_dimension: int = 1) Dict[str, ndarray][source]

Creates a dictionary of random inputs.

Parameters:
  • batch – dimension to use as batch dimension if unknown

  • empty_dimension – if a dimension is null, replaces it by this value

Returns:

dictionary