From 066bf54774333935d8a6af7ceac7c37197468a8d Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Tue, 7 Oct 2025 13:26:43 +0200 Subject: [PATCH 01/10] Add some support of ONNX through codex --- docs/source/api.rst | 12 ++ docs/source/index.rst | 2 +- docs/source/user/supported.rst | 15 ++ requirements.onnx.txt | 1 + src/gurobi_ml/onnx/__init__.py | 22 ++ src/gurobi_ml/onnx/onnx_model.py | 237 ++++++++++++++++++++++ src/gurobi_ml/registered_predictors.py | 14 ++ tests/test_onnx/__init__.py | 0 tests/test_onnx/test_onnx_exceptions.py | 28 +++ tests/test_onnx/test_onnx_formulations.py | 65 ++++++ tox.ini | 14 +- 11 files changed, 407 insertions(+), 3 deletions(-) create mode 100644 requirements.onnx.txt create mode 100644 src/gurobi_ml/onnx/__init__.py create mode 100644 src/gurobi_ml/onnx/onnx_model.py create mode 100644 tests/test_onnx/__init__.py create mode 100644 tests/test_onnx/test_onnx_exceptions.py create mode 100644 tests/test_onnx/test_onnx_formulations.py diff --git a/docs/source/api.rst b/docs/source/api.rst index a039e31c..e035d5a0 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -68,6 +68,18 @@ PyTorch API sequential +ONNX API +******** + +.. currentmodule:: gurobi_ml.onnx + +.. autosummary:: + :toctree: auto_generated/ + :caption: ONNX API + :template: modeling_object.rst + + onnx_model + XGBoost API *********** diff --git a/docs/source/index.rst b/docs/source/index.rst index 1c179b81..052f48c9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,7 +14,7 @@ Gurobi Machine Learning A Python package to help use *trained* regression models in mathematical optimization models. The package supports a variety of regression models (linear, logistic, neural networks, decision trees,...) trained by -different machine learning frameworks (scikit-learn, LightGBM, XGBoost, Keras and PyTorch). +different machine learning frameworks (scikit-learn, LightGBM, XGBoost, Keras, PyTorch, and ONNX). .. only:: html diff --git a/docs/source/user/supported.rst b/docs/source/user/supported.rst index 1638cb3c..52dfd755 100644 --- a/docs/source/user/supported.rst +++ b/docs/source/user/supported.rst @@ -121,6 +121,21 @@ Currently, only two types of layers are supported: * :external+torch:py:class:`Linear layers `, * :external+torch:py:class:`ReLU layers `. +ONNX +---- + +`ONNX `_ models for sequential multi-layer perceptrons are +supported when composed of `Gemm` (dense) operators and `Relu` activations. + +They can be formulated in a Gurobi model with the function +:py:func:`add_onnx_constr `. + +Currently, only the following are supported: + + * `Gemm` nodes with default attributes (`alpha=1`, `beta=1`) and optional + `transB` attribute, + * `Relu` activations. + XGBoost ------- diff --git a/requirements.onnx.txt b/requirements.onnx.txt new file mode 100644 index 00000000..de4714a3 --- /dev/null +++ b/requirements.onnx.txt @@ -0,0 +1 @@ +onnx>=1.14.0 diff --git a/src/gurobi_ml/onnx/__init__.py b/src/gurobi_ml/onnx/__init__.py new file mode 100644 index 00000000..4dc11ac2 --- /dev/null +++ b/src/gurobi_ml/onnx/__init__.py @@ -0,0 +1,22 @@ +# Copyright © 2023 Gurobi Optimization, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""ONNX support for formulating simple feed-forward neural networks. + +Currently supports sequential MLPs represented with ONNX `Gemm` layers and +`Relu` activations, matching the capabilities of the Keras and PyTorch +converters (Dense/Linear + ReLU).""" + +from .onnx_model import add_onnx_constr as add_onnx_constr # noqa: F401 diff --git a/src/gurobi_ml/onnx/onnx_model.py b/src/gurobi_ml/onnx/onnx_model.py new file mode 100644 index 00000000..5ba87efd --- /dev/null +++ b/src/gurobi_ml/onnx/onnx_model.py @@ -0,0 +1,237 @@ +# Copyright © 2023 Gurobi Optimization, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Module for formulating an ONNX MLP model into a :external+gurobi:py:class:`Model`. + +Supported ONNX models are simple feed-forward networks composed of `Gemm` +nodes (dense layers) and `Relu` activations. This mirrors the Keras and +PyTorch integrations, which currently handle Dense/Linear + ReLU networks. +""" + +from __future__ import annotations + +from typing import List, Optional + +import numpy as np + +try: # Lazy import to avoid hard dependency when not used + import onnx + from onnx import numpy_helper +except Exception: # pragma: no cover - handled by no-deps tests + onnx = None # type: ignore + numpy_helper = None # type: ignore + +from ..exceptions import NoModel, NoSolution +from ..modeling.neuralnet import BaseNNConstr + + +def add_onnx_constr(gp_model, onnx_model, input_vars, output_vars=None, **kwargs): + """Formulate an ONNX MLP model into `gp_model`. + + The formulation predicts the values of `output_vars` using `input_vars` + according to `onnx_model`. + + Parameters + ---------- + gp_model : :external+gurobi:py:class:`Model` + Target Gurobi model where the predictor submodel is added. + onnx_model : onnx.ModelProto + ONNX model, expected to represent a sequential MLP with `Gemm` nodes + and `Relu` activations. + input_vars : mvar_array_like + Decision variables used as input for the model in `gp_model`. + output_vars : mvar_array_like, optional + Decision variables used as output for the model in `gp_model`. + + Warnings + -------- + Only networks composed of `Gemm` and `Relu` nodes are supported. `Gemm` + nodes must use default `alpha=1`, `beta=1`. Attribute `transB` is + supported. + """ + return ONNXNetworkConstr(gp_model, onnx_model, input_vars, output_vars, **kwargs) + + +class _ONNXLayer: + """Internal representation of one dense+activation block.""" + + def __init__(self, W: np.ndarray, b: np.ndarray, activation: str = "identity"): + self.W = W # shape (in, out) + self.b = b # shape (out,) + self.activation = activation # "relu" or "identity" + + +class ONNXNetworkConstr(BaseNNConstr): + """Formulate a supported ONNX MLP model as a Gurobi predictor constraint.""" + + def __init__(self, gp_model, predictor, input_vars, output_vars=None, **kwargs): + if onnx is None: + raise NoModel(predictor, "onnx is not available") + if not isinstance(predictor, onnx.ModelProto): + raise NoModel(predictor, "Expected an onnx.ModelProto model") + + self._layers_spec: List[_ONNXLayer] = self._parse_mlp(predictor) + if not self._layers_spec: + raise NoModel(predictor, "Empty or unsupported ONNX graph") + + super().__init__(gp_model, predictor, input_vars, output_vars, **kwargs) + + def _parse_mlp(self, model: "onnx.ModelProto") -> List[_ONNXLayer]: + """Parse a limited subset of ONNX graphs representing MLPs. + + We support sequences of: Gemm -> (Relu)? -> Gemm -> (Relu)? ... + Gemm attributes allowed: alpha==1, beta==1, transB in {0,1}. + """ + graph = model.graph + + # Map initializer name -> numpy array + init_map = {} + for init in graph.initializer: + arr = numpy_helper.to_array(init) + init_map[init.name] = arr + + # Helper: get attribute value with default + def _get_attr(node, name, default=None): + for a in node.attribute: + if a.name == name: + # attributes can be ints, floats + if a.type == onnx.AttributeProto.INT: + return int(a.i) + if a.type == onnx.AttributeProto.FLOAT: + return float(a.f) + return default + + # Iterate nodes gathering dense layers and relus + layers: List[_ONNXLayer] = [] + pending_activation: Optional[str] = None + + for node in graph.node: + op = node.op_type + if op == "Gemm": + alpha = _get_attr(node, "alpha", 1.0) + beta = _get_attr(node, "beta", 1.0) + transB = _get_attr(node, "transB", 0) + if alpha != 1.0 or beta != 1.0: + raise NoModel( + model, f"Unsupported Gemm attributes alpha={alpha}, beta={beta}" + ) + + # Inputs: A, B, C + if len(node.input) < 2: + raise NoModel(model, "Gemm node missing inputs") + # B and C should be initializers + B_name = node.input[1] + C_name = node.input[2] if len(node.input) > 2 else None + if B_name not in init_map: + raise NoModel(model, "Gemm weights must be an initializer") + W = init_map[B_name] + if transB == 1: + W = W.T # make it shape (in, out) + if C_name is None or C_name not in init_map: + b = np.zeros((W.shape[1],), dtype=W.dtype) + else: + b = init_map[C_name].reshape(-1) + if b.shape[0] != W.shape[1]: + raise NoModel(model, "Gemm bias has wrong shape") + + act = pending_activation or "identity" + layers.append(_ONNXLayer(W=W, b=b, activation=act)) + pending_activation = None + + elif op == "Relu": + # Next linear layer will use relu activation; if we have no + # preceding linear layer, we model it as a pure activation layer + # via _add_activation_layer during _mip_model. + # To keep the implementation simple and in line with Keras/Torch, + # we treat Relu as activation for the preceding affine transform + # when possible; otherwise mark pending_activation to apply to + # the following Dense layer. + if layers and layers[-1].activation == "identity": + layers[-1].activation = "relu" + else: + # No prior dense, remember to insert a standalone activation + # at modeling time by setting a pending flag. + # Here we store a marker layer with zero-sized W to signal + # a standalone activation to the modeler. + layers.append( + _ONNXLayer( + W=np.zeros((0, 0)), b=np.zeros((0,)), activation="relu" + ) + ) + elif op in ("Identity",): + # Ignore + continue + else: + raise NoModel(model, f"Unsupported ONNX op {op}") + + # Validate at least one real dense layer + has_dense = any(layer.W.size > 0 for layer in layers) + if not has_dense: + return [] + return layers + + def _mip_model(self, **kwargs): + _input = self._input + output = None + # Build Gurobi layers according to parsed spec + for i, spec in enumerate(self._layers_spec): + if i == len(self._layers_spec) - 1: + output = self._output + if spec.W.size == 0: + # Standalone activation layer + layer = self._add_activation_layer( + _input, + self.act_dict[spec.activation], + output, + name=f"relu{i}", + **kwargs, + ) + _input = layer.output + else: + layer = self._add_dense_layer( + _input, + spec.W, + spec.b, + self.act_dict[ + spec.activation if spec.activation != "identity" else "identity" + ], + output, + name=f"dense{i}", + **kwargs, + ) + _input = layer.output + if self._output is None: + self._output = layer.output + + def _forward_numpy(self, X: np.ndarray) -> np.ndarray: + out = X + for spec in self._layers_spec: + if spec.W.size == 0: + # Relu activation directly on out + out = np.maximum(out, 0.0) + else: + out = out @ spec.W + spec.b + if spec.activation == "relu": + out = np.maximum(out, 0.0) + return out + + def get_error(self, eps=None): + if self._has_solution: + pred = self._forward_numpy(self.input_values) + r_val = np.abs(pred - self.output_values) + if eps is not None and np.max(r_val) > eps: + print(f"{pred} != {self.output_values}") + return r_val + raise NoSolution() diff --git a/src/gurobi_ml/registered_predictors.py b/src/gurobi_ml/registered_predictors.py index 6f8ded3e..c6818f3f 100644 --- a/src/gurobi_ml/registered_predictors.py +++ b/src/gurobi_ml/registered_predictors.py @@ -102,12 +102,26 @@ def keras_convertors(): return {} +def onnx_convertors(): + """Collect known ONNX objects that can be formulated and the conversion class.""" + if "onnx" in sys.modules: + import onnx # pylint: disable=import-outside-toplevel + + from .onnx import add_onnx_constr # pylint: disable=import-outside-toplevel + + return { + onnx.ModelProto: add_onnx_constr, + } + return {} + + def registered_predictors(): """Return the list of registered predictors.""" convertors = {} convertors |= sklearn_convertors() convertors |= pytorch_convertors() convertors |= keras_convertors() + convertors |= onnx_convertors() convertors |= xgboost_convertors() convertors |= lightgbm_convertors() convertors |= user_predictors() diff --git a/tests/test_onnx/__init__.py b/tests/test_onnx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_onnx/test_onnx_exceptions.py b/tests/test_onnx/test_onnx_exceptions.py new file mode 100644 index 00000000..134d0c33 --- /dev/null +++ b/tests/test_onnx/test_onnx_exceptions.py @@ -0,0 +1,28 @@ +import unittest + +import gurobipy as gp +import numpy as np +import onnx +from onnx import helper, TensorProto + +from gurobi_ml import add_predictor_constr +from gurobi_ml.exceptions import NoModel + + +class TestUnsupportedONNX(unittest.TestCase): + def test_unsupported_op(self): + # Build a simple graph with an unsupported op (Sigmoid) + X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, 4]) + Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, 4]) + node = helper.make_node("Sigmoid", inputs=["X"], outputs=["Y"], name="sigmoid") + graph = helper.make_graph( + nodes=[node], name="BadGraph", inputs=[X], outputs=[Y] + ) + model = helper.make_model(graph) + onnx.checker.check_model(model) + + example = np.zeros((1, 4), dtype=float) + m = gp.Model() + x = m.addMVar(example.shape, lb=0.0, ub=1.0, name="x") + with self.assertRaises(NoModel): + add_predictor_constr(m, model, x) diff --git a/tests/test_onnx/test_onnx_formulations.py b/tests/test_onnx/test_onnx_formulations.py new file mode 100644 index 00000000..c3c04469 --- /dev/null +++ b/tests/test_onnx/test_onnx_formulations.py @@ -0,0 +1,65 @@ +import os + +import numpy as np +import onnx +from joblib import load +from onnx import helper, TensorProto + +from ..fixed_formulation import FixedRegressionModel + + +def build_simple_mlp_onnx( + n_in: int, n_hidden: int, n_out: int, seed: int = 0 +) -> onnx.ModelProto: + rng = np.random.default_rng(seed) + W1 = rng.normal(size=(n_in, n_hidden)).astype(np.float32) + b1 = rng.normal(size=(n_hidden,)).astype(np.float32) + W2 = rng.normal(size=(n_hidden, n_out)).astype(np.float32) + b2 = rng.normal(size=(n_out,)).astype(np.float32) + + X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, n_in]) + Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, n_out]) + + init_W1 = helper.make_tensor( + name="W1", data_type=TensorProto.FLOAT, dims=W1.T.shape, vals=W1.T.flatten() + ) + init_b1 = helper.make_tensor( + name="b1", data_type=TensorProto.FLOAT, dims=b1.shape, vals=b1 + ) + init_W2 = helper.make_tensor( + name="W2", data_type=TensorProto.FLOAT, dims=W2.T.shape, vals=W2.T.flatten() + ) + init_b2 = helper.make_tensor( + name="b2", data_type=TensorProto.FLOAT, dims=b2.shape, vals=b2 + ) + + gemm1 = helper.make_node( + "Gemm", inputs=["X", "W1", "b1"], outputs=["H1"], name="gemm1", transB=1 + ) + relu1 = helper.make_node("Relu", inputs=["H1"], outputs=["A1"], name="relu1") + gemm2 = helper.make_node( + "Gemm", inputs=["A1", "W2", "b2"], outputs=["Y"], name="gemm2", transB=1 + ) + + graph = helper.make_graph( + nodes=[gemm1, relu1, gemm2], + name="SimpleMLP", + inputs=[X], + outputs=[Y], + initializer=[init_W1, init_b1, init_W2, init_b2], + ) + model = helper.make_model(graph) + onnx.checker.check_model(model) + return model + + +class TestONNXModel(FixedRegressionModel): + basedir = os.path.join(os.path.dirname(__file__), "..", "predictors") + + def test_diabetes_onnx_mlp(self): + X = load(os.path.join(self.basedir, "examples_diabetes.joblib")) + n_in = X.shape[1] + model = build_simple_mlp_onnx(n_in=n_in, n_hidden=16, n_out=1, seed=123) + onecase = {"predictor": model, "nonconvex": 0} + self.do_one_case(onecase, X, 5, "all") + self.do_one_case(onecase, X, 6, "pairs") diff --git a/tox.ini b/tox.ini index 4bc0e2f1..738969b8 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ requires = tox-uv tox-gh-actions -envlist = {py310,py311,py312,py313}-{lightgbm,keras,pandas,pytorch,sklearn,xgboost,no_deps,all_deps}-{gurobi11,gurobi12,gurobi13},pre-commit,docs,examples-{gurobi11,gurobi12,gurobi13} +envlist = {py310,py311,py312,py313}-{lightgbm,keras,pandas,pytorch,onnx,sklearn,xgboost,no_deps,all_deps}-{gurobi11,gurobi12,gurobi13},pre-commit,docs,examples-{gurobi11,gurobi12,gurobi13} isolated_build = True [gh-actions] @@ -38,6 +38,7 @@ deps = -r{toxinidir}/requirements.tox.txt -r{toxinidir}/requirements.keras.txt -r{toxinidir}/requirements.pytorch.txt + -r{toxinidir}/requirements.onnx.txt -r{toxinidir}/requirements.sklearn.txt -r{toxinidir}/requirements.pandas.txt -r{toxinidir}/requirements.xgboost.txt @@ -102,6 +103,13 @@ deps = commands = pytest tests/test_pytorch +[testenv:{py310,py311,py312,py313}-onnx-{gurobi11,gurobi12,gurobi13}] +deps = + {[base]deps} + -r{toxinidir}/requirements.onnx.txt +commands = + pytest tests/test_onnx + [testenv:{py310,py311,py312,py313}-sklearn-{gurobi11,gurobi12,gurobi13}] deps = {[base]deps} @@ -144,6 +152,7 @@ deps = {[base]deps} -r{toxinidir}/requirements.keras.txt -r{toxinidir}/requirements.pytorch.txt + -r{toxinidir}/requirements.onnx.txt -r{toxinidir}/requirements.sklearn.txt -r{toxinidir}/requirements.pandas.txt -r{toxinidir}/requirements.xgboost.txt @@ -154,7 +163,8 @@ commands = tests/test_xgboost \ tests/test_keras \ tests/test_pandas \ - tests/test_pytorch + tests/test_pytorch \ + tests/test_onnx [testenv] setenv = From 1559b310fd40d605ee311d211c8b6a7cb01ee5a8 Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Tue, 7 Oct 2025 13:41:59 +0200 Subject: [PATCH 02/10] Add an adversarial notebook for ONNX --- notebooks/adversarial/adversarial_onnx.ipynb | 323 +++++++++++++++++++ src/gurobi_ml/onnx/__init__.py | 2 +- src/gurobi_ml/onnx/onnx_model.py | 11 +- 3 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 notebooks/adversarial/adversarial_onnx.ipynb diff --git a/notebooks/adversarial/adversarial_onnx.ipynb b/notebooks/adversarial/adversarial_onnx.ipynb new file mode 100644 index 00000000..1d88c39d --- /dev/null +++ b/notebooks/adversarial/adversarial_onnx.ipynb @@ -0,0 +1,323 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Adversarial example using ONNX\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import the necessary packages and load data\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import tensorflow as tf\n", + "from tensorflow import keras\n", + "import onnx\n", + "from onnx import helper, TensorProto\n", + "\n", + "import gurobipy as gp\n", + "\n", + "from gurobi_ml import add_predictor_constr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()" + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "We reshape and scale `x_train` and `x_test`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "x_train = tf.reshape(tf.cast(x_train, tf.float32) / 255.0, [-1, 28 * 28])\n", + "x_test = tf.reshape(tf.cast(x_test, tf.float32) / 255.0, [-1, 28 * 28])" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "## Construct and train the neural network\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "nn = tf.keras.models.Sequential(\n", + " [\n", + " tf.keras.layers.InputLayer((28 * 28,)),\n", + " tf.keras.layers.Dense(50, activation=\"relu\"),\n", + " tf.keras.layers.Dense(50, activation=\"relu\"),\n", + " tf.keras.layers.Dense(10), # logits\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "nn.compile(\n", + " optimizer=tf.keras.optimizers.Adam(0.001),\n", + " loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n", + " metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "nn.fit(\n", + " x_train,\n", + " y_train,\n", + " epochs=3,\n", + " validation_data=(x_test, y_test),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "Convert the trained Keras model to an ONNX MLP\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "def keras_dense_layers_to_onnx(model):\n", + " # Extract dense layers weights/bias and activation\n", + " layers = []\n", + " in_dim = None\n", + " for layer in model.layers:\n", + " if isinstance(layer, tf.keras.layers.InputLayer):\n", + " try:\n", + " in_dim = layer.input_shape[-1]\n", + " except Exception:\n", + " pass\n", + " elif isinstance(layer, tf.keras.layers.Dense):\n", + " W, b = layer.get_weights()\n", + " act = layer.get_config().get(\"activation\", \"linear\")\n", + " layers.append((W.astype(np.float32), b.astype(np.float32), act))\n", + "\n", + " # Build ONNX graph from collected layers\n", + " n_in = in_dim or layers[0][0].shape[0]\n", + " X = helper.make_tensor_value_info(\"X\", TensorProto.FLOAT, [None, n_in])\n", + "\n", + " last = \"X\"\n", + " inits = []\n", + " nodes = []\n", + " for i, (W, b, act) in enumerate(layers):\n", + " W_name = f\"W{i + 1}\"\n", + " b_name = f\"b{i + 1}\"\n", + " # Gemm with transB=1 realizes (last @ W + b) when B is W.T\n", + " inits.append(\n", + " helper.make_tensor(W_name, TensorProto.FLOAT, W.T.shape, W.T.flatten())\n", + " )\n", + " inits.append(helper.make_tensor(b_name, TensorProto.FLOAT, b.shape, b))\n", + " out_name = f\"H{i + 1}\"\n", + " nodes.append(\n", + " helper.make_node(\n", + " \"Gemm\",\n", + " inputs=[last, W_name, b_name],\n", + " outputs=[out_name],\n", + " name=f\"gemm{i + 1}\",\n", + " transB=1,\n", + " )\n", + " )\n", + " last = out_name\n", + " if act == \"relu\":\n", + " act_name = f\"A{i + 1}\"\n", + " nodes.append(\n", + " helper.make_node(\n", + " \"Relu\", inputs=[last], outputs=[act_name], name=f\"relu{i + 1}\"\n", + " )\n", + " )\n", + " last = act_name\n", + "\n", + " # Connect final tensor to a named output via Identity\n", + " n_out = layers[-1][1].shape[0]\n", + " nodes.append(\n", + " helper.make_node(\"Identity\", inputs=[last], outputs=[\"Y\"], name=\"output\")\n", + " )\n", + " Y = helper.make_tensor_value_info(\"Y\", TensorProto.FLOAT, [None, n_out])\n", + " graph = helper.make_graph(\n", + " nodes=nodes, name=\"KerasMLP\", inputs=[X], outputs=[Y], initializer=inits\n", + " )\n", + " model = helper.make_model(graph)\n", + " onnx.checker.check_model(model)\n", + " return model\n", + "\n", + "\n", + "onnx_model = keras_dense_layers_to_onnx(nn)" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "## Build optimization model\n", + "\n", + "Now we turn to building the optimization model.\n", + "\n", + "We choose a training example and follow the same steps as the Keras example.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "example = x_train[18, :]\n", + "plt.imshow(tf.reshape(example, [28, 28]), cmap=\"gray\")\n", + "ex_prob = nn.predict(tf.reshape(example, (1, -1)))\n", + "sorted_labels = tf.argsort(ex_prob)[0]\n", + "right_label = sorted_labels[-1]\n", + "wrong_label = sorted_labels[-2]\n", + "print(\n", + " f\"Original classified as {int(right_label)}; target misclassify as {int(wrong_label)}\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "m = gp.Model()\n", + "delta = 5\n", + "\n", + "x = m.addMVar(example.numpy().shape, lb=0.0, ub=1.0, name=\"x\")\n", + "y = m.addMVar(ex_prob.shape, lb=-gp.GRB.INFINITY, name=\"y\")\n", + "\n", + "abs_diff = m.addMVar(example.numpy().shape, lb=0, ub=1, name=\"abs_diff\")\n", + "\n", + "m.setObjective(y[0, wrong_label] - y[0, right_label], gp.GRB.MAXIMIZE)\n", + "\n", + "# Bound on the distance to example in norm-1\n", + "m.addConstr(abs_diff >= x - example.numpy())\n", + "m.addConstr(abs_diff >= -x + example.numpy())\n", + "m.addConstr(abs_diff.sum() <= delta)\n", + "\n", + "pred_constr = add_predictor_constr(m, onnx_model, x, y)\n", + "\n", + "pred_constr.print_stats()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "m.Params.BestBdStop = 0.0\n", + "m.Params.BestObjStop = 0.0\n", + "m.optimize()" + ] + }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "Finally, display the adversarial example if one was found.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "if m.SolCount and m.ObjVal > 0.0:\n", + " plt.imshow(x.X.reshape((28, 28)), cmap=\"gray\")\n", + " label = tf.math.argmax(nn.predict(tf.reshape(x.X, (1, -1))), axis=1)\n", + " print(f\"Solution is classified as {label.numpy()[0]}\")\n", + "else:\n", + " print(\"No counter example exists in neighborhood.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.3" + }, + "license": { + "full_text": "# Copyright © 2025 Gurobi Optimization, LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ==============================================================================" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/gurobi_ml/onnx/__init__.py b/src/gurobi_ml/onnx/__init__.py index 4dc11ac2..cdb83ad7 100644 --- a/src/gurobi_ml/onnx/__init__.py +++ b/src/gurobi_ml/onnx/__init__.py @@ -1,4 +1,4 @@ -# Copyright © 2023 Gurobi Optimization, LLC +# Copyright © 2025 Gurobi Optimization, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/gurobi_ml/onnx/onnx_model.py b/src/gurobi_ml/onnx/onnx_model.py index 5ba87efd..34ba7c13 100644 --- a/src/gurobi_ml/onnx/onnx_model.py +++ b/src/gurobi_ml/onnx/onnx_model.py @@ -1,4 +1,4 @@ -# Copyright © 2023 Gurobi Optimization, LLC +# Copyright © 2025 Gurobi Optimization, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ from __future__ import annotations -from typing import List, Optional import numpy as np @@ -82,13 +81,13 @@ def __init__(self, gp_model, predictor, input_vars, output_vars=None, **kwargs): if not isinstance(predictor, onnx.ModelProto): raise NoModel(predictor, "Expected an onnx.ModelProto model") - self._layers_spec: List[_ONNXLayer] = self._parse_mlp(predictor) + self._layers_spec: list[_ONNXLayer] = self._parse_mlp(predictor) if not self._layers_spec: raise NoModel(predictor, "Empty or unsupported ONNX graph") super().__init__(gp_model, predictor, input_vars, output_vars, **kwargs) - def _parse_mlp(self, model: "onnx.ModelProto") -> List[_ONNXLayer]: + def _parse_mlp(self, model: onnx.ModelProto) -> list[_ONNXLayer]: """Parse a limited subset of ONNX graphs representing MLPs. We support sequences of: Gemm -> (Relu)? -> Gemm -> (Relu)? ... @@ -114,8 +113,8 @@ def _get_attr(node, name, default=None): return default # Iterate nodes gathering dense layers and relus - layers: List[_ONNXLayer] = [] - pending_activation: Optional[str] = None + layers: list[_ONNXLayer] = [] + pending_activation: str | None = None for node in graph.node: op = node.op_type From a7ef2db9324e04ca9219a1d5a2397ddc8dbf33bf Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Mon, 13 Oct 2025 18:30:15 +0100 Subject: [PATCH 03/10] Add more ONNX operation Add MatMult and Plus --- notebooks/adversarial/adversarial_onnx.ipynb | 308 +++++++++---------- notebooks/adversarial/mnist_model.onnx | Bin 0 -> 171418 bytes requirements.onnx.txt | 1 + src/gurobi_ml/onnx/onnx_model.py | 87 +++++- tests/test_onnx/test_onnx_formulations.py | 102 ++++-- 5 files changed, 293 insertions(+), 205 deletions(-) create mode 100644 notebooks/adversarial/mnist_model.onnx diff --git a/notebooks/adversarial/adversarial_onnx.ipynb b/notebooks/adversarial/adversarial_onnx.ipynb index 1d88c39d..72ea0dc7 100644 --- a/notebooks/adversarial/adversarial_onnx.ipynb +++ b/notebooks/adversarial/adversarial_onnx.ipynb @@ -2,268 +2,188 @@ "cells": [ { "cell_type": "markdown", - "id": "0", "metadata": {}, "source": [ "# Adversarial example using ONNX\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Import the necessary packages and load data\n", - "\n" + "\n", + "In this example, we demonstrate finding adversarial examples for a neural network using Gurobi and gurobi-ml's ONNX support.\n", + "\n", + "We load a pre-trained MNIST classifier (stored as an ONNX model) and use optimization to find small perturbations to an input image that cause misclassification.\n", + "\n", + "This example requires:\n", + " - [matplotlib](https://matplotlib.org/)\n", + " - [onnx](https://onnx.ai/)\n", + " - [keras](https://keras.io/) (only for loading MNIST data)" ] }, { "cell_type": "code", "execution_count": null, - "id": "2", "metadata": {}, "outputs": [], "source": [ "from matplotlib import pyplot as plt\n", "import numpy as np\n", - "import tensorflow as tf\n", - "from tensorflow import keras\n", "import onnx\n", - "from onnx import helper, TensorProto\n", + "from tensorflow import keras\n", "\n", "import gurobipy as gp\n", - "\n", "from gurobi_ml import add_predictor_constr" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "3", - "metadata": {}, - "outputs": [], - "source": [ - "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()" - ] - }, { "cell_type": "markdown", - "id": "4", "metadata": {}, "source": [ - "We reshape and scale `x_train` and `x_test`.\n" + "## Load MNIST data\n", + "\n", + "We use Keras only to load the MNIST dataset." ] }, { "cell_type": "code", "execution_count": null, - "id": "5", "metadata": {}, "outputs": [], "source": [ - "x_train = tf.reshape(tf.cast(x_train, tf.float32) / 255.0, [-1, 28 * 28])\n", - "x_test = tf.reshape(tf.cast(x_test, tf.float32) / 255.0, [-1, 28 * 28])" + "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n", + "\n", + "# Reshape and normalize\n", + "x_test = x_test.astype(\"float32\") / 255.0\n", + "x_test_flat = x_test.reshape(-1, 28 * 28)" ] }, { "cell_type": "markdown", - "id": "6", "metadata": {}, "source": [ - "## Construct and train the neural network\n", - "\n" + "## Load pre-trained ONNX model\n", + "\n", + "We load a pre-trained neural network with 2 hidden layers of 50 neurons each and ReLU activations.\n", + "The model was trained on MNIST and converted to ONNX format." ] }, { "cell_type": "code", "execution_count": null, - "id": "7", "metadata": {}, "outputs": [], "source": [ - "nn = tf.keras.models.Sequential(\n", - " [\n", - " tf.keras.layers.InputLayer((28 * 28,)),\n", - " tf.keras.layers.Dense(50, activation=\"relu\"),\n", - " tf.keras.layers.Dense(50, activation=\"relu\"),\n", - " tf.keras.layers.Dense(10), # logits\n", - " ]\n", - ")" + "onnx_model = onnx.load(\"mnist_model.onnx\")\n", + "print(\"ONNX model loaded successfully\")\n", + "print(f\"Model has {len(onnx_model.graph.node)} operations\")" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "8", + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "nn.compile(\n", - " optimizer=tf.keras.optimizers.Adam(0.001),\n", - " loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n", - " metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],\n", - ")" + "## Verify model predictions\n", + "\n", + "Let's verify the model works by making a prediction on a test sample." ] }, { "cell_type": "code", "execution_count": null, - "id": "9", "metadata": {}, "outputs": [], "source": [ - "nn.fit(\n", - " x_train,\n", - " y_train,\n", - " epochs=3,\n", - " validation_data=(x_test, y_test),\n", - ")" + "# Use onnxruntime for inference\n", + "import onnxruntime as ort\n", + "\n", + "session = ort.InferenceSession(onnx_model.SerializeToString())\n", + "input_name = session.get_inputs()[0].name\n", + "\n", + "# Predict on a test sample\n", + "sample_idx = 18\n", + "sample = x_test_flat[sample_idx : sample_idx + 1]\n", + "prediction = session.run(None, {input_name: sample})[0]\n", + "\n", + "print(f\"True label: {y_test[sample_idx]}\")\n", + "print(f\"Predicted: {np.argmax(prediction)}\")\n", + "\n", + "# Display the image\n", + "plt.imshow(x_test[sample_idx], cmap=\"gray\")\n", + "plt.title(f\"True: {y_test[sample_idx]}, Predicted: {np.argmax(prediction)}\")\n", + "plt.axis(\"off\")\n", + "plt.show()" ] }, { "cell_type": "markdown", - "id": "10", "metadata": {}, "source": [ - "Convert the trained Keras model to an ONNX MLP\n" + "## Select an example for adversarial attack\n", + "\n", + "We choose a test example that is correctly classified and define the target misclassification." ] }, { "cell_type": "code", "execution_count": null, - "id": "11", "metadata": {}, "outputs": [], "source": [ - "def keras_dense_layers_to_onnx(model):\n", - " # Extract dense layers weights/bias and activation\n", - " layers = []\n", - " in_dim = None\n", - " for layer in model.layers:\n", - " if isinstance(layer, tf.keras.layers.InputLayer):\n", - " try:\n", - " in_dim = layer.input_shape[-1]\n", - " except Exception:\n", - " pass\n", - " elif isinstance(layer, tf.keras.layers.Dense):\n", - " W, b = layer.get_weights()\n", - " act = layer.get_config().get(\"activation\", \"linear\")\n", - " layers.append((W.astype(np.float32), b.astype(np.float32), act))\n", - "\n", - " # Build ONNX graph from collected layers\n", - " n_in = in_dim or layers[0][0].shape[0]\n", - " X = helper.make_tensor_value_info(\"X\", TensorProto.FLOAT, [None, n_in])\n", + "example = x_test_flat[sample_idx : sample_idx + 1]\n", + "right_label = int(y_test[sample_idx])\n", + "wrong_label = 8\n", "\n", - " last = \"X\"\n", - " inits = []\n", - " nodes = []\n", - " for i, (W, b, act) in enumerate(layers):\n", - " W_name = f\"W{i + 1}\"\n", - " b_name = f\"b{i + 1}\"\n", - " # Gemm with transB=1 realizes (last @ W + b) when B is W.T\n", - " inits.append(\n", - " helper.make_tensor(W_name, TensorProto.FLOAT, W.T.shape, W.T.flatten())\n", - " )\n", - " inits.append(helper.make_tensor(b_name, TensorProto.FLOAT, b.shape, b))\n", - " out_name = f\"H{i + 1}\"\n", - " nodes.append(\n", - " helper.make_node(\n", - " \"Gemm\",\n", - " inputs=[last, W_name, b_name],\n", - " outputs=[out_name],\n", - " name=f\"gemm{i + 1}\",\n", - " transB=1,\n", - " )\n", - " )\n", - " last = out_name\n", - " if act == \"relu\":\n", - " act_name = f\"A{i + 1}\"\n", - " nodes.append(\n", - " helper.make_node(\n", - " \"Relu\", inputs=[last], outputs=[act_name], name=f\"relu{i + 1}\"\n", - " )\n", - " )\n", - " last = act_name\n", - "\n", - " # Connect final tensor to a named output via Identity\n", - " n_out = layers[-1][1].shape[0]\n", - " nodes.append(\n", - " helper.make_node(\"Identity\", inputs=[last], outputs=[\"Y\"], name=\"output\")\n", - " )\n", - " Y = helper.make_tensor_value_info(\"Y\", TensorProto.FLOAT, [None, n_out])\n", - " graph = helper.make_graph(\n", - " nodes=nodes, name=\"KerasMLP\", inputs=[X], outputs=[Y], initializer=inits\n", - " )\n", - " model = helper.make_model(graph)\n", - " onnx.checker.check_model(model)\n", - " return model\n", - "\n", - "\n", - "onnx_model = keras_dense_layers_to_onnx(nn)" + "print(f\"Original label: {right_label}\")\n", + "print(f\"Target (wrong) label: {wrong_label}\")" ] }, { "cell_type": "markdown", - "id": "12", "metadata": {}, "source": [ - "## Build optimization model\n", - "\n", - "Now we turn to building the optimization model.\n", + "## Build the optimization model\n", "\n", - "We choose a training example and follow the same steps as the Keras example.\n" + "We create a Gurobi model to find an adversarial example.\n", + "The objective is to maximize the score difference between the wrong label and correct label,\n", + "subject to the perturbed image being close to the original (measured by L1 distance)." ] }, { "cell_type": "code", "execution_count": null, - "id": "13", - "metadata": {}, - "outputs": [], - "source": [ - "example = x_train[18, :]\n", - "plt.imshow(tf.reshape(example, [28, 28]), cmap=\"gray\")\n", - "ex_prob = nn.predict(tf.reshape(example, (1, -1)))\n", - "sorted_labels = tf.argsort(ex_prob)[0]\n", - "right_label = sorted_labels[-1]\n", - "wrong_label = sorted_labels[-2]\n", - "print(\n", - " f\"Original classified as {int(right_label)}; target misclassify as {int(wrong_label)}\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14", "metadata": {}, "outputs": [], "source": [ "m = gp.Model()\n", - "delta = 5\n", + "delta = 5 # Maximum L1 distance from original image\n", "\n", - "x = m.addMVar(example.numpy().shape, lb=0.0, ub=1.0, name=\"x\")\n", - "y = m.addMVar(ex_prob.shape, lb=-gp.GRB.INFINITY, name=\"y\")\n", - "\n", - "abs_diff = m.addMVar(example.numpy().shape, lb=0, ub=1, name=\"abs_diff\")\n", + "# Decision variables\n", + "x = m.addMVar(example.shape, lb=0.0, ub=1.0, name=\"x\")\n", + "y = m.addMVar((1, 10), lb=-gp.GRB.INFINITY, name=\"y\") # Network output logits\n", + "abs_diff = m.addMVar(example.shape, lb=0, ub=1, name=\"abs_diff\")\n", "\n", + "# Objective: maximize score of wrong label minus score of correct label\n", "m.setObjective(y[0, wrong_label] - y[0, right_label], gp.GRB.MAXIMIZE)\n", "\n", - "# Bound on the distance to example in norm-1\n", - "m.addConstr(abs_diff >= x - example.numpy())\n", - "m.addConstr(abs_diff >= -x + example.numpy())\n", + "# Constraints: bound L1 distance from original\n", + "m.addConstr(abs_diff >= x - example)\n", + "m.addConstr(abs_diff >= -x + example)\n", "m.addConstr(abs_diff.sum() <= delta)\n", "\n", + "# Add neural network constraints\n", "pred_constr = add_predictor_constr(m, onnx_model, x, y)\n", "\n", "pred_constr.print_stats()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solve the optimization problem\n", + "\n", + "We solve the model to find an adversarial example." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "15", "metadata": {}, "outputs": [], "source": [ @@ -274,25 +194,68 @@ }, { "cell_type": "markdown", - "id": "16", "metadata": {}, "source": [ - "Finally, display the adversarial example if one was found.\n" + "## Display the adversarial example\n", + "\n", + "If an adversarial example was found, we display it and verify the misclassification." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pred_constr.get_error()" ] }, { "cell_type": "code", "execution_count": null, - "id": "17", "metadata": {}, "outputs": [], "source": [ - "if m.SolCount and m.ObjVal > 0.0:\n", - " plt.imshow(x.X.reshape((28, 28)), cmap=\"gray\")\n", - " label = tf.math.argmax(nn.predict(tf.reshape(x.X, (1, -1))), axis=1)\n", - " print(f\"Solution is classified as {label.numpy()[0]}\")\n", + "adversarial_image = x.X.reshape(28, 28)\n", + "\n", + "# Verify classification\n", + "adv_flat = x.X.reshape(1, -1).astype(np.float32)\n", + "adv_prediction = session.run(None, {input_name: adv_flat})[0]\n", + "predicted_label = np.argmax(adv_prediction)\n", + "\n", + "# Display original and adversarial images\n", + "fig, axes = plt.subplots(1, 3, figsize=(12, 4))\n", + "\n", + "axes[0].imshow(example.reshape(28, 28), cmap=\"gray\")\n", + "axes[0].set_title(f\"Original (label: {right_label})\")\n", + "axes[0].axis(\"off\")\n", + "\n", + "axes[1].imshow(adversarial_image, cmap=\"gray\")\n", + "axes[1].set_title(f\"Adversarial (classified as: {predicted_label})\")\n", + "axes[1].axis(\"off\")\n", + "\n", + "# Show difference\n", + "diff = np.abs(adversarial_image - example.reshape(28, 28))\n", + "axes[2].imshow(diff, cmap=\"hot\")\n", + "axes[2].set_title(f\"Difference (L1: {diff.sum():.2f})\")\n", + "axes[2].axis(\"off\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()\n", + "if m.ObjVal > 0.0:\n", + " print(\"\\nAdversarial example found!\")\n", + " print(f\"Original label: {right_label}\")\n", + " print(f\"Predicted label: {predicted_label}\")\n", + " print(f\"L1 distance: {diff.sum():.2f}\")\n", "else:\n", - " print(\"No counter example exists in neighborhood.\")" + " print(\"No adversarial example exists within the specified distance bound.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "copyright © 2023 Gurobi Optimization, LLC" ] } ], @@ -312,12 +275,17 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.3" + "version": "3.11.2" }, "license": { - "full_text": "# Copyright © 2025 Gurobi Optimization, LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ==============================================================================" + "full_text": "# Copyright © 2023-2025 Gurobi Optimization, LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ==============================================================================" + }, + "vscode": { + "interpreter": { + "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" + } } }, "nbformat": 4, - "nbformat_minor": 5 + "nbformat_minor": 4 } diff --git a/notebooks/adversarial/mnist_model.onnx b/notebooks/adversarial/mnist_model.onnx new file mode 100644 index 0000000000000000000000000000000000000000..71a89e8267fbb4606669c2723a5584941bacaa89 GIT binary patch literal 171418 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

H`=tr?v$kWEh5)M7OiNt_zpiuMgF z8;MnUBA3bo=#BI$P;hV_9)r@x=E_;?(^vfoT1_uqHekc%(w3qU3l ztI3S$N7C+sk_Lk%NTp|tA zy^!le7r3CzlHR)XjWqmPq!B#S$6u(2PTygh9!85q8GLuHdgkdrHzog|9kJ#59CSIc(P)y__C~2EU-^|@e{d@z^=$~S6 zzgPg29j}8aVJ&Q;`+smtpcSmD|u}Buz#%z)N%vBcFkex z`DqhwlZiq<#toSA`-f3r!euB55qmq#n^c=MfWh#3^lwKdnVwaJ3s1ge+`fl{nvBEf z_Q47^biV+)JNc0;FYlwpjS6IClQ+mt_hKrBWs%@G#7Dj-fDcc;LWwSxa;#=JEclz) zeELq@-}0flo^aHso<=S=ub?wtuBJnhu_*U`1uiyu$xJ;}BWtx}vFYw@Xh?b|8f+Gz z1KQb8Sb*Di6kbJn&#b}bSNyhCnfWQ=z}2#toVY#CTRKHKCuh=ra7OJ%}m5tU(W%pj1N5>u} zLh;d3w02Pez4C&MPM4-27mU%po`M` zsQH;w=-{vp$aAY;k8bLsv)?WQcHy;n@wDrt)o=^4cZooYT1sf*b2C)prjL15YDh#% zkqTKJ!e&3~ps)J_v}ais4xZ8mlBL=7ieU}b$lOU=kJnR;)@d}MI}IAN3sD8vdGv+? z0NXud$co8Ov^}JP%UE-L9wixIYTHE$?a#8~=bfnUk{vL|yaG?rdtAOnf}3kg(1(5> zk?%B9AU5BLO&pa2&YDZ;WPLH`ZPP%SoHx|)j6Kb^)<6O8-?N(nHe-XJCTgKyj2~AN z;n-IX*+W*+$TJV2m}&M@FGU6UN6J&&aRT0Js(|-dJ9>H2kl?0s=(~gz`*JW8Yq!W! zo>(PQy5FB3IBFgQ zJd`VNSNSq}xamLWGvC>Z{ySuv+rcXLZN>Ncbnr+MCa>;K1Ldzd zcEH$U7~=DuOg=mgH3m~buYE21bKH*FZH~ph=LFg5ohO;2@9x1Lh5fKO@jr6v%0Dp9 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

Ih~p8kEpQI!R^E*7(hYcL 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 GvicwOlRL@) literal 0 HcmV?d00001 diff --git a/requirements.onnx.txt b/requirements.onnx.txt index de4714a3..677e4f3d 100644 --- a/requirements.onnx.txt +++ b/requirements.onnx.txt @@ -1 +1,2 @@ onnx>=1.14.0 +onnxruntime>=1.15.0 diff --git a/src/gurobi_ml/onnx/onnx_model.py b/src/gurobi_ml/onnx/onnx_model.py index 34ba7c13..91963c17 100644 --- a/src/gurobi_ml/onnx/onnx_model.py +++ b/src/gurobi_ml/onnx/onnx_model.py @@ -16,8 +16,9 @@ """Module for formulating an ONNX MLP model into a :external+gurobi:py:class:`Model`. Supported ONNX models are simple feed-forward networks composed of `Gemm` -nodes (dense layers) and `Relu` activations. This mirrors the Keras and -PyTorch integrations, which currently handle Dense/Linear + ReLU networks. +nodes (dense layers) or `MatMul`+`Add` sequences, along with `Relu` activations. +This mirrors the Keras and PyTorch integrations, which currently handle +Dense/Linear + ReLU networks. """ from __future__ import annotations @@ -48,7 +49,7 @@ def add_onnx_constr(gp_model, onnx_model, input_vars, output_vars=None, **kwargs Target Gurobi model where the predictor submodel is added. onnx_model : onnx.ModelProto ONNX model, expected to represent a sequential MLP with `Gemm` nodes - and `Relu` activations. + (or `MatMul`+`Add` sequences) and `Relu` activations. input_vars : mvar_array_like Decision variables used as input for the model in `gp_model`. output_vars : mvar_array_like, optional @@ -56,9 +57,9 @@ def add_onnx_constr(gp_model, onnx_model, input_vars, output_vars=None, **kwargs Warnings -------- - Only networks composed of `Gemm` and `Relu` nodes are supported. `Gemm` - nodes must use default `alpha=1`, `beta=1`. Attribute `transB` is - supported. + Only networks composed of `Gemm` (or `MatMul`+`Add`) and `Relu` nodes are + supported. `Gemm` nodes must use default `alpha=1`, `beta=1`. Attribute + `transB` is supported. """ return ONNXNetworkConstr(gp_model, onnx_model, input_vars, output_vars, **kwargs) @@ -90,7 +91,10 @@ def __init__(self, gp_model, predictor, input_vars, output_vars=None, **kwargs): def _parse_mlp(self, model: onnx.ModelProto) -> list[_ONNXLayer]: """Parse a limited subset of ONNX graphs representing MLPs. - We support sequences of: Gemm -> (Relu)? -> Gemm -> (Relu)? ... + We support sequences of: + - Gemm -> (Relu)? -> Gemm -> (Relu)? ... + - MatMul -> Add -> (Relu)? -> MatMul -> Add -> (Relu)? ... + Gemm attributes allowed: alpha==1, beta==1, transB in {0,1}. """ graph = model.graph @@ -112,11 +116,21 @@ def _get_attr(node, name, default=None): return float(a.f) return default + # Build a map from output name to node for easier traversal + output_to_node = {} + for node in graph.node: + for output in node.output: + output_to_node[output] = node + # Iterate nodes gathering dense layers and relus layers: list[_ONNXLayer] = [] pending_activation: str | None = None + processed_indices = set() + + for node_idx, node in enumerate(graph.node): + if node_idx in processed_indices: + continue - for node in graph.node: op = node.op_type if op == "Gemm": alpha = _get_attr(node, "alpha", 1.0) @@ -148,6 +162,60 @@ def _get_attr(node, name, default=None): act = pending_activation or "identity" layers.append(_ONNXLayer(W=W, b=b, activation=act)) pending_activation = None + processed_indices.add(node_idx) + + elif op == "MatMul": + # MatMul should be followed by Add for bias + # Inputs: A, B where B is the weight matrix + if len(node.input) != 2: + raise NoModel(model, "MatMul node should have exactly 2 inputs") + + weight_name = node.input[1] + if weight_name not in init_map: + raise NoModel(model, "MatMul weights must be an initializer") + + W = init_map[weight_name] # shape should be (in, out) + + # Look for an Add node that uses the output of this MatMul + matmul_output = node.output[0] + add_node = None + add_node_idx = None + for next_idx, next_node in enumerate(graph.node): + if next_node.op_type == "Add" and matmul_output in next_node.input: + add_node = next_node + add_node_idx = next_idx + break + + if add_node is not None: + # Find the bias input (the one that's not the MatMul output) + bias_name = None + for inp in add_node.input: + if inp != matmul_output and inp in init_map: + bias_name = inp + break + + if bias_name is not None: + b = init_map[bias_name].reshape(-1) + if b.shape[0] != W.shape[1]: + raise NoModel(model, "Add bias has wrong shape") + else: + b = np.zeros((W.shape[1],), dtype=W.dtype) + + processed_indices.add(add_node_idx) + else: + # MatMul without Add - use zero bias + b = np.zeros((W.shape[1],), dtype=W.dtype) + + act = pending_activation or "identity" + layers.append(_ONNXLayer(W=W, b=b, activation=act)) + pending_activation = None + processed_indices.add(node_idx) + + elif op == "Add": + # Skip if already processed as part of MatMul+Add + if node_idx not in processed_indices: + # Standalone Add node - ignore or warn? + processed_indices.add(node_idx) elif op == "Relu": # Next linear layer will use relu activation; if we have no @@ -169,8 +237,11 @@ def _get_attr(node, name, default=None): W=np.zeros((0, 0)), b=np.zeros((0,)), activation="relu" ) ) + processed_indices.add(node_idx) + elif op in ("Identity",): # Ignore + processed_indices.add(node_idx) continue else: raise NoModel(model, f"Unsupported ONNX op {op}") diff --git a/tests/test_onnx/test_onnx_formulations.py b/tests/test_onnx/test_onnx_formulations.py index c3c04469..3e8296f2 100644 --- a/tests/test_onnx/test_onnx_formulations.py +++ b/tests/test_onnx/test_onnx_formulations.py @@ -9,7 +9,7 @@ def build_simple_mlp_onnx( - n_in: int, n_hidden: int, n_out: int, seed: int = 0 + n_in: int, n_hidden: int, n_out: int, seed: int = 0, use_gemm: bool = True ) -> onnx.ModelProto: rng = np.random.default_rng(seed) W1 = rng.normal(size=(n_in, n_hidden)).astype(np.float32) @@ -20,34 +20,71 @@ def build_simple_mlp_onnx( X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, n_in]) Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, n_out]) - init_W1 = helper.make_tensor( - name="W1", data_type=TensorProto.FLOAT, dims=W1.T.shape, vals=W1.T.flatten() - ) - init_b1 = helper.make_tensor( - name="b1", data_type=TensorProto.FLOAT, dims=b1.shape, vals=b1 - ) - init_W2 = helper.make_tensor( - name="W2", data_type=TensorProto.FLOAT, dims=W2.T.shape, vals=W2.T.flatten() - ) - init_b2 = helper.make_tensor( - name="b2", data_type=TensorProto.FLOAT, dims=b2.shape, vals=b2 - ) + if use_gemm: + # Use Gemm operations + init_W1 = helper.make_tensor( + name="W1", data_type=TensorProto.FLOAT, dims=W1.T.shape, vals=W1.T.flatten() + ) + init_b1 = helper.make_tensor( + name="b1", data_type=TensorProto.FLOAT, dims=b1.shape, vals=b1 + ) + init_W2 = helper.make_tensor( + name="W2", data_type=TensorProto.FLOAT, dims=W2.T.shape, vals=W2.T.flatten() + ) + init_b2 = helper.make_tensor( + name="b2", data_type=TensorProto.FLOAT, dims=b2.shape, vals=b2 + ) - gemm1 = helper.make_node( - "Gemm", inputs=["X", "W1", "b1"], outputs=["H1"], name="gemm1", transB=1 - ) - relu1 = helper.make_node("Relu", inputs=["H1"], outputs=["A1"], name="relu1") - gemm2 = helper.make_node( - "Gemm", inputs=["A1", "W2", "b2"], outputs=["Y"], name="gemm2", transB=1 - ) + gemm1 = helper.make_node( + "Gemm", inputs=["X", "W1", "b1"], outputs=["H1"], name="gemm1", transB=1 + ) + relu1 = helper.make_node("Relu", inputs=["H1"], outputs=["A1"], name="relu1") + gemm2 = helper.make_node( + "Gemm", inputs=["A1", "W2", "b2"], outputs=["Y"], name="gemm2", transB=1 + ) + + graph = helper.make_graph( + nodes=[gemm1, relu1, gemm2], + name="SimpleMLP", + inputs=[X], + outputs=[Y], + initializer=[init_W1, init_b1, init_W2, init_b2], + ) + else: + # Use MatMul + Add operations (tf2onnx style) + init_W1 = helper.make_tensor( + name="W1", data_type=TensorProto.FLOAT, dims=W1.shape, vals=W1.flatten() + ) + init_b1 = helper.make_tensor( + name="b1", data_type=TensorProto.FLOAT, dims=b1.shape, vals=b1 + ) + init_W2 = helper.make_tensor( + name="W2", data_type=TensorProto.FLOAT, dims=W2.shape, vals=W2.flatten() + ) + init_b2 = helper.make_tensor( + name="b2", data_type=TensorProto.FLOAT, dims=b2.shape, vals=b2 + ) + + matmul1 = helper.make_node( + "MatMul", inputs=["X", "W1"], outputs=["MM1"], name="matmul1" + ) + add1 = helper.make_node( + "Add", inputs=["MM1", "b1"], outputs=["H1"], name="add1" + ) + relu1 = helper.make_node("Relu", inputs=["H1"], outputs=["A1"], name="relu1") + matmul2 = helper.make_node( + "MatMul", inputs=["A1", "W2"], outputs=["MM2"], name="matmul2" + ) + add2 = helper.make_node("Add", inputs=["MM2", "b2"], outputs=["Y"], name="add2") + + graph = helper.make_graph( + nodes=[matmul1, add1, relu1, matmul2, add2], + name="SimpleMLP", + inputs=[X], + outputs=[Y], + initializer=[init_W1, init_b1, init_W2, init_b2], + ) - graph = helper.make_graph( - nodes=[gemm1, relu1, gemm2], - name="SimpleMLP", - inputs=[X], - outputs=[Y], - initializer=[init_W1, init_b1, init_W2, init_b2], - ) model = helper.make_model(graph) onnx.checker.check_model(model) return model @@ -63,3 +100,14 @@ def test_diabetes_onnx_mlp(self): onecase = {"predictor": model, "nonconvex": 0} self.do_one_case(onecase, X, 5, "all") self.do_one_case(onecase, X, 6, "pairs") + + def test_diabetes_onnx_mlp_matmul(self): + """Test ONNX models using MatMul+Add pattern (tf2onnx style).""" + X = load(os.path.join(self.basedir, "examples_diabetes.joblib")) + n_in = X.shape[1] + model = build_simple_mlp_onnx( + n_in=n_in, n_hidden=16, n_out=1, seed=123, use_gemm=False + ) + onecase = {"predictor": model, "nonconvex": 0} + self.do_one_case(onecase, X, 5, "all") + self.do_one_case(onecase, X, 6, "pairs") From 717ecc323f1e69418fec7e0f41456556e7299dfb Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Tue, 14 Oct 2025 23:53:01 +0200 Subject: [PATCH 04/10] Review onnx code --- src/gurobi_ml/onnx/onnx_model.py | 35 ++++++++------------------------ 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/src/gurobi_ml/onnx/onnx_model.py b/src/gurobi_ml/onnx/onnx_model.py index 91963c17..56f7b6db 100644 --- a/src/gurobi_ml/onnx/onnx_model.py +++ b/src/gurobi_ml/onnx/onnx_model.py @@ -25,13 +25,8 @@ import numpy as np - -try: # Lazy import to avoid hard dependency when not used - import onnx - from onnx import numpy_helper -except Exception: # pragma: no cover - handled by no-deps tests - onnx = None # type: ignore - numpy_helper = None # type: ignore +import onnx +from onnx import numpy_helper from ..exceptions import NoModel, NoSolution from ..modeling.neuralnet import BaseNNConstr @@ -77,8 +72,6 @@ class ONNXNetworkConstr(BaseNNConstr): """Formulate a supported ONNX MLP model as a Gurobi predictor constraint.""" def __init__(self, gp_model, predictor, input_vars, output_vars=None, **kwargs): - if onnx is None: - raise NoModel(predictor, "onnx is not available") if not isinstance(predictor, onnx.ModelProto): raise NoModel(predictor, "Expected an onnx.ModelProto model") @@ -91,10 +84,7 @@ def __init__(self, gp_model, predictor, input_vars, output_vars=None, **kwargs): def _parse_mlp(self, model: onnx.ModelProto) -> list[_ONNXLayer]: """Parse a limited subset of ONNX graphs representing MLPs. - We support sequences of: - - Gemm -> (Relu)? -> Gemm -> (Relu)? ... - - MatMul -> Add -> (Relu)? -> MatMul -> Add -> (Relu)? ... - + We support sequences of Gemm and MatMul+Add nodes with optional Relu activations. Gemm attributes allowed: alpha==1, beta==1, transB in {0,1}. """ graph = model.graph @@ -285,21 +275,14 @@ def _mip_model(self, **kwargs): if self._output is None: self._output = layer.output - def _forward_numpy(self, X: np.ndarray) -> np.ndarray: - out = X - for spec in self._layers_spec: - if spec.W.size == 0: - # Relu activation directly on out - out = np.maximum(out, 0.0) - else: - out = out @ spec.W + spec.b - if spec.activation == "relu": - out = np.maximum(out, 0.0) - return out - def get_error(self, eps=None): if self._has_solution: - pred = self._forward_numpy(self.input_values) + import onnxruntime as ort + + sess = ort.InferenceSession(self.predictor.SerializeToString()) + input_name = sess.get_inputs()[0].name + pred = sess.run(None, {input_name: self.input_values.astype(np.float32)})[0] + r_val = np.abs(pred - self.output_values) if eps is not None and np.max(r_val) > eps: print(f"{pred} != {self.output_values}") From e18c1a00382da2d7eedc750fc7e4499d1bffaa77 Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Tue, 14 Oct 2025 23:55:00 +0200 Subject: [PATCH 05/10] Add a composite test --- .../test_framework_onnx_equivalence.py | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 tests/test_composite/test_framework_onnx_equivalence.py diff --git a/tests/test_composite/test_framework_onnx_equivalence.py b/tests/test_composite/test_framework_onnx_equivalence.py new file mode 100644 index 00000000..70829623 --- /dev/null +++ b/tests/test_composite/test_framework_onnx_equivalence.py @@ -0,0 +1,322 @@ +"""Composite tests comparing framework models with their ONNX conversions. + +These tests verify that models converted to ONNX produce equivalent results +when used with gurobi-ml. This ensures ONNX conversion and formulation +correctness. + +Note: These tests may be skipped if required dependencies (tf2onnx, etc.) are +not available or if there are version compatibility issues. +""" + +import os +import tempfile +import unittest +import warnings + +import numpy as np +from joblib import load + +try: + import keras + import tensorflow as tf + + KERAS_AVAILABLE = True +except ImportError: + KERAS_AVAILABLE = False + +try: + import tf2onnx + + TF2ONNX_AVAILABLE = True +except ImportError: + TF2ONNX_AVAILABLE = False + +try: + import torch + + TORCH_AVAILABLE = True +except ImportError: + TORCH_AVAILABLE = False + +try: + import onnx + + ONNX_AVAILABLE = True +except ImportError: + ONNX_AVAILABLE = False + +import gurobipy as gp + +from gurobi_ml import add_predictor_constr + + +class TestFrameworkONNXEquivalence(unittest.TestCase): + """Test that framework models produce same results as their ONNX conversions.""" + + basedir = os.path.join(os.path.dirname(__file__), "..", "predictors") + + def setUp(self): + self.rng = np.random.default_rng(42) + + def _solve_with_model(self, model, X_samples): + """Solve a Gurobi model with fixed inputs using a given predictor.""" + with gp.Env(params={"OutputFlag": 0}) as env, gp.Model(env=env) as gpm: + x = gpm.addMVar(X_samples.shape, lb=X_samples - 1e-4, ub=X_samples + 1e-4) + pred_constr = add_predictor_constr(gpm, model, x) + gpm.optimize() + return pred_constr.output.X.copy() + + @unittest.skipIf( + not (KERAS_AVAILABLE and TF2ONNX_AVAILABLE and ONNX_AVAILABLE), + "keras, tf2onnx, or onnx not available", + ) + def test_keras_to_onnx_basic(self): + """Test basic Keras to ONNX conversion produces consistent results.""" + X = load(os.path.join(self.basedir, "examples_diabetes.joblib")) + + # Load Keras model + filename = os.path.join(self.basedir, "diabetes.keras") + keras_model = keras.saving.load_model(filename) + + # Select samples + choice = self.rng.choice(X.shape[0], size=3, replace=False) + samples = X[choice, :] + + # Get Keras direct predictions + keras_direct = keras_model.predict(samples, verbose=0) + + # Get Gurobi-Keras predictions + try: + keras_gurobi = self._solve_with_model(keras_model, samples) + except Exception as e: + self.skipTest(f"Could not formulate Keras model: {e}") + + # Convert to ONNX + with tempfile.NamedTemporaryFile(suffix=".onnx", delete=False) as tmp: + try: + n_features = X.shape[1] + + @tf.function( + input_signature=[ + tf.TensorSpec(shape=(None, n_features), dtype=tf.float32) + ] + ) + def model_fn(x): + return keras_model(x) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + onnx_model_proto, _ = tf2onnx.convert.from_function( + model_fn, + input_signature=[ + tf.TensorSpec(shape=(None, n_features), dtype=tf.float32) + ], + opset=13, + ) + + onnx.save(onnx_model_proto, tmp.name) + onnx_model = onnx.load(tmp.name) + + # Get Gurobi-ONNX predictions + onnx_gurobi = self._solve_with_model(onnx_model, samples) + + # Compare Keras Gurobi vs ONNX Gurobi + max_diff = np.max(np.abs(keras_gurobi - onnx_gurobi)) + + # Use a reasonable tolerance + tolerance = 1e-3 + self.assertLess( + max_diff, + tolerance, + f"Keras and ONNX Gurobi formulations differ by {max_diff:.6e}\n" + f"Keras direct: {keras_direct.flatten()}\n" + f"Keras Gurobi: {keras_gurobi}\n" + f"ONNX Gurobi: {onnx_gurobi}", + ) + + except Exception as e: + # Clean up and skip if there are issues + self.skipTest(f"ONNX conversion failed: {e}") + finally: + if os.path.exists(tmp.name): + os.unlink(tmp.name) + + @unittest.skipIf( + not (TORCH_AVAILABLE and ONNX_AVAILABLE), "torch or onnx not available" + ) + def test_pytorch_to_onnx_basic(self): + """Test basic PyTorch to ONNX conversion produces consistent results.""" + X = load(os.path.join(self.basedir, "examples_diabetes.joblib")) + + # Load PyTorch model + filename = os.path.join(self.basedir, "diabetes__pytorch.pt") + try: + pytorch_model = torch.load(filename, weights_only=False) + pytorch_model.eval() + except Exception as e: + self.skipTest(f"Could not load PyTorch model: {e}") + + # Select samples + choice = self.rng.choice(X.shape[0], size=3, replace=False) + samples = X[choice, :] + + # Get PyTorch direct predictions + with torch.no_grad(): + pytorch_direct = pytorch_model(torch.FloatTensor(samples)).numpy() + + # Get Gurobi-PyTorch predictions + try: + pytorch_gurobi = self._solve_with_model(pytorch_model, samples) + except Exception as e: + self.skipTest(f"Could not formulate PyTorch model: {e}") + + # Convert to ONNX + with tempfile.NamedTemporaryFile(suffix=".onnx", delete=False) as tmp: + try: + n_features = X.shape[1] + dummy_input = torch.randn(1, n_features) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + torch.onnx.export( + pytorch_model, + dummy_input, + tmp.name, + export_params=True, + opset_version=13, + do_constant_folding=True, + input_names=["input"], + output_names=["output"], + ) + + onnx_model = onnx.load(tmp.name) + + # Get Gurobi-ONNX predictions + onnx_gurobi = self._solve_with_model(onnx_model, samples) + + # Compare PyTorch Gurobi vs ONNX Gurobi + max_diff = np.max(np.abs(pytorch_gurobi - onnx_gurobi)) + + # Use a reasonable tolerance + tolerance = 1e-3 + self.assertLess( + max_diff, + tolerance, + f"PyTorch and ONNX Gurobi formulations differ by {max_diff:.6e}\n" + f"PyTorch direct: {pytorch_direct.flatten()}\n" + f"PyTorch Gurobi: {pytorch_gurobi}\n" + f"ONNX Gurobi: {onnx_gurobi}", + ) + + except Exception as e: + self.skipTest(f"ONNX conversion or formulation failed: {e}") + finally: + if os.path.exists(tmp.name): + os.unlink(tmp.name) + + @unittest.skipIf( + not ( + KERAS_AVAILABLE and TORCH_AVAILABLE and TF2ONNX_AVAILABLE and ONNX_AVAILABLE + ), + "Required frameworks not available", + ) + def test_cross_framework_onnx_consistency(self): + """Test that different frameworks produce consistent ONNX formulations. + + This uses the same data with different framework models, converts to ONNX, + and verifies the formulations work correctly. + """ + X = load(os.path.join(self.basedir, "examples_diabetes.joblib")) + + # Use a single sample for simplicity + sample = X[0:1, :] + + results = {} + + # Test Keras -> ONNX + try: + keras_model = keras.saving.load_model( + os.path.join(self.basedir, "diabetes.keras") + ) + keras_pred = keras_model.predict(sample, verbose=0) + results["keras_direct"] = keras_pred.flatten()[0] + + with tempfile.NamedTemporaryFile(suffix=".onnx", delete=False) as tmp: + try: + n_features = X.shape[1] + + @tf.function( + input_signature=[ + tf.TensorSpec(shape=(None, n_features), dtype=tf.float32) + ] + ) + def model_fn(x): + return keras_model(x) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + onnx_model_proto, _ = tf2onnx.convert.from_function( + model_fn, + input_signature=[ + tf.TensorSpec( + shape=(None, n_features), dtype=tf.float32 + ) + ], + opset=13, + ) + + onnx.save(onnx_model_proto, tmp.name) + onnx_model = onnx.load(tmp.name) + + keras_onnx_gurobi = self._solve_with_model(onnx_model, sample) + results["keras_onnx"] = keras_onnx_gurobi[0] + finally: + if os.path.exists(tmp.name): + os.unlink(tmp.name) + except Exception as e: + warnings.warn(f"Keras test skipped: {e}") + + # Test PyTorch -> ONNX + try: + pytorch_model = torch.load( + os.path.join(self.basedir, "diabetes__pytorch.pt"), weights_only=False + ) + pytorch_model.eval() + + with torch.no_grad(): + pytorch_pred = pytorch_model(torch.FloatTensor(sample)).numpy() + results["pytorch_direct"] = pytorch_pred.flatten()[0] + + with tempfile.NamedTemporaryFile(suffix=".onnx", delete=False) as tmp: + try: + n_features = X.shape[1] + dummy_input = torch.randn(1, n_features) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + torch.onnx.export( + pytorch_model, + dummy_input, + tmp.name, + export_params=True, + opset_version=13, + do_constant_folding=True, + ) + + onnx_model = onnx.load(tmp.name) + pytorch_onnx_gurobi = self._solve_with_model(onnx_model, sample) + results["pytorch_onnx"] = pytorch_onnx_gurobi[0] + finally: + if os.path.exists(tmp.name): + os.unlink(tmp.name) + except Exception as e: + warnings.warn(f"PyTorch test skipped: {e}") + + # Verify we got at least some results + if len(results) < 2: + self.skipTest("Not enough frameworks available for comparison") + + # Print results for debugging + print("\nCross-framework results:") + for key, value in results.items(): + print(f" {key}: {value:.4f}") From 9581e59521362b5e080f4b166d0642c4d4a646f8 Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Thu, 16 Oct 2025 15:54:28 +0200 Subject: [PATCH 06/10] Enable composite example in tox --- tox.ini | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 738969b8..be912706 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ requires = tox-uv tox-gh-actions -envlist = {py310,py311,py312,py313}-{lightgbm,keras,pandas,pytorch,onnx,sklearn,xgboost,no_deps,all_deps}-{gurobi11,gurobi12,gurobi13},pre-commit,docs,examples-{gurobi11,gurobi12,gurobi13} +envlist = {py310,py311,py312,py313}-{compositetf,composieto,lightgbm,keras,pandas,pytorch,onnx,sklearn,xgboost,no_deps,all_deps}-{gurobi11,gurobi12,gurobi13},pre-commit,docs,examples-{gurobi11,gurobi12,gurobi13} isolated_build = True [gh-actions] @@ -164,7 +164,24 @@ commands = tests/test_keras \ tests/test_pandas \ tests/test_pytorch \ - tests/test_onnx + tests/test_onnx \ + tests/test_composite + +[testenv:{py310,py311,py312,py313}-compositeto-{gurobi11,gurobi12,gurobi13}] +deps = + {[base]deps} + -r{toxinidir}/requirements.pytorch.txt + -r{toxinidir}/requirements.onnx.txt +commands = + pytest tests/test_composite + +[testenv:{py310,py311,py312,py313}-compositetf-{gurobi11,gurobi12,gurobi13}] +deps = + {[base]deps} + -r{toxinidir}/requirements.keras.txt + -r{toxinidir}/requirements.onnx.txt +commands = + pytest tests/test_composite [testenv] setenv = From a43c42fef45f2910471c8ba129e0d23031b5a26b Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Thu, 30 Oct 2025 10:36:14 +0100 Subject: [PATCH 07/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- notebooks/adversarial/adversarial_onnx.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/adversarial/adversarial_onnx.ipynb b/notebooks/adversarial/adversarial_onnx.ipynb index 72ea0dc7..ffca582e 100644 --- a/notebooks/adversarial/adversarial_onnx.ipynb +++ b/notebooks/adversarial/adversarial_onnx.ipynb @@ -255,7 +255,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "copyright © 2023 Gurobi Optimization, LLC" + "copyright © 2023-2025 Gurobi Optimization, LLC" ] } ], From 97dbbef9522a8197fa06fec28885ec05680f8c36 Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Thu, 30 Oct 2025 10:36:29 +0100 Subject: [PATCH 08/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index be912706..3ea96ca0 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ requires = tox-uv tox-gh-actions -envlist = {py310,py311,py312,py313}-{compositetf,composieto,lightgbm,keras,pandas,pytorch,onnx,sklearn,xgboost,no_deps,all_deps}-{gurobi11,gurobi12,gurobi13},pre-commit,docs,examples-{gurobi11,gurobi12,gurobi13} +envlist = {py310,py311,py312,py313}-{compositetf,compositeto,lightgbm,keras,pandas,pytorch,onnx,sklearn,xgboost,no_deps,all_deps}-{gurobi11,gurobi12,gurobi13},pre-commit,docs,examples-{gurobi11,gurobi12,gurobi13} isolated_build = True [gh-actions] From 069eb8b505096b6b83240958c277caeca1912654 Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Thu, 30 Oct 2025 13:59:10 +0100 Subject: [PATCH 09/10] Set ONNX version in test --- tests/test_onnx/test_onnx_exceptions.py | 3 ++- tests/test_onnx/test_onnx_formulations.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_onnx/test_onnx_exceptions.py b/tests/test_onnx/test_onnx_exceptions.py index 134d0c33..6d1b850c 100644 --- a/tests/test_onnx/test_onnx_exceptions.py +++ b/tests/test_onnx/test_onnx_exceptions.py @@ -18,7 +18,8 @@ def test_unsupported_op(self): graph = helper.make_graph( nodes=[node], name="BadGraph", inputs=[X], outputs=[Y] ) - model = helper.make_model(graph) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 18)]) + model.ir_version = 9 onnx.checker.check_model(model) example = np.zeros((1, 4), dtype=float) diff --git a/tests/test_onnx/test_onnx_formulations.py b/tests/test_onnx/test_onnx_formulations.py index 3e8296f2..98620fb6 100644 --- a/tests/test_onnx/test_onnx_formulations.py +++ b/tests/test_onnx/test_onnx_formulations.py @@ -85,7 +85,8 @@ def build_simple_mlp_onnx( initializer=[init_W1, init_b1, init_W2, init_b2], ) - model = helper.make_model(graph) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 18)]) + model.ir_version = 9 onnx.checker.check_model(model) return model From d068839874a4a7785c63500e926fcbd0f4491c9e Mon Sep 17 00:00:00 2001 From: Pierre Bonami Date: Fri, 7 Nov 2025 12:38:08 +0100 Subject: [PATCH 10/10] Add some detection of non-sequential neural networks --- src/gurobi_ml/onnx/onnx_model.py | 66 +++++++++++++ tests/test_onnx/test_onnx_exceptions.py | 122 ++++++++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/src/gurobi_ml/onnx/onnx_model.py b/src/gurobi_ml/onnx/onnx_model.py index 56f7b6db..4181e7a5 100644 --- a/src/gurobi_ml/onnx/onnx_model.py +++ b/src/gurobi_ml/onnx/onnx_model.py @@ -81,6 +81,69 @@ def __init__(self, gp_model, predictor, input_vars, output_vars=None, **kwargs): super().__init__(gp_model, predictor, input_vars, output_vars, **kwargs) + def _validate_sequential_architecture(self, graph, init_map): + """Validate that the graph has a sequential architecture. + + Raises NoModel if the graph contains: + - Skip connections (same intermediate value used by multiple nodes) + - Residual connections (Add nodes combining non-bias values) + - Non-sequential topology + """ + # Build usage map: which nodes use each tensor + tensor_usage = {} + for node in graph.node: + for inp in node.input: + if inp not in tensor_usage: + tensor_usage[inp] = [] + tensor_usage[inp].append(node.name) + + # Check 1: Input should only be used by one node (first layer) + for graph_input in graph.input: + input_name = graph_input.name + if input_name in tensor_usage and len(tensor_usage[input_name]) > 1: + raise NoModel( + graph, + f"Non-sequential architecture detected: input '{input_name}' is used by multiple nodes {tensor_usage[input_name]}. " + "Skip connections and residual architectures are not supported.", + ) + + # Check 2: Each intermediate node output should be used by at most one node + # (except for the final output which may not be used by any node) + for node in graph.node: + for output in node.output: + if output in tensor_usage and len(tensor_usage[output]) > 1: + raise NoModel( + graph, + f"Non-sequential architecture detected: node '{node.name}' output '{output}' is used by multiple nodes {tensor_usage[output]}. " + "Skip connections and residual architectures are not supported.", + ) + + # Check 3: Add nodes should only be used for bias addition (MatMul+Add pattern) + # Not for combining two computed branches (residual connections) + for node in graph.node: + if node.op_type == "Add": + # An Add is valid if one of its inputs is an initializer (bias) + # and the other is from a MatMul + inputs = list(node.input) + if len(inputs) != 2: + continue + + # Check if this is a MatMul+Add pattern (one input from MatMul, one is initializer) + is_bias_add = False + for inp in inputs: + if inp in init_map: + # One input is a constant (bias) + is_bias_add = True + break + + if not is_bias_add: + # Both inputs are computed values - this is a residual connection + raise NoModel( + graph, + f"Non-sequential architecture detected: Add node '{node.name}' combines two computed values {inputs}. " + "Residual connections are not supported.", + ) + def _parse_mlp(self, model: onnx.ModelProto) -> list[_ONNXLayer]: """Parse a limited subset of ONNX graphs representing MLPs. @@ -106,6 +169,9 @@ def _get_attr(node, name, default=None): return float(a.f) return default + # Validate that the graph is sequential (no skip connections or residual adds) + self._validate_sequential_architecture(graph, init_map) + # Build a map from output name to node for easier traversal output_to_node = {} for node in graph.node: diff --git a/tests/test_onnx/test_onnx_exceptions.py b/tests/test_onnx/test_onnx_exceptions.py index 6d1b850c..98dbb763 100644 --- a/tests/test_onnx/test_onnx_exceptions.py +++ b/tests/test_onnx/test_onnx_exceptions.py @@ -27,3 +27,125 @@ def test_unsupported_op(self): x = m.addMVar(example.shape, lb=0.0, ub=1.0, name="x") with self.assertRaises(NoModel): add_predictor_constr(m, model, x) + + def test_skip_connection_rejected(self): + # Build a model with skip connection: input used by multiple nodes + n_in, n_hidden, n_out = 4, 8, 2 + + W1 = np.random.randn(n_in, n_hidden).astype(np.float32) + b1 = np.random.randn(n_hidden).astype(np.float32) + W2 = np.random.randn(n_hidden, n_out).astype(np.float32) + b2 = np.random.randn(n_out).astype(np.float32) + W_skip = np.random.randn(n_in, n_out).astype(np.float32) + b_skip = np.random.randn(n_out).astype(np.float32) + + X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, n_in]) + Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, n_out]) + + init_W1 = helper.make_tensor( + "W1", TensorProto.FLOAT, W1.T.shape, W1.T.flatten() + ) + init_b1 = helper.make_tensor("b1", TensorProto.FLOAT, b1.shape, b1) + init_W2 = helper.make_tensor( + "W2", TensorProto.FLOAT, W2.T.shape, W2.T.flatten() + ) + init_b2 = helper.make_tensor("b2", TensorProto.FLOAT, b2.shape, b2) + init_W_skip = helper.make_tensor( + "W_skip", TensorProto.FLOAT, W_skip.T.shape, W_skip.T.flatten() + ) + init_b_skip = helper.make_tensor( + "b_skip", TensorProto.FLOAT, b_skip.shape, b_skip + ) + + # Main path + gemm1 = helper.make_node("Gemm", ["X", "W1", "b1"], ["H1"], transB=1) + relu1 = helper.make_node("Relu", ["H1"], ["A1"]) + gemm2 = helper.make_node("Gemm", ["A1", "W2", "b2"], ["branch1"], transB=1) + + # Skip connection path - uses X again! + gemm_skip = helper.make_node( + "Gemm", ["X", "W_skip", "b_skip"], ["branch2"], transB=1 + ) + + # Combine branches (residual add) + add = helper.make_node("Add", ["branch1", "branch2"], ["Y"]) + + graph = helper.make_graph( + [gemm1, relu1, gemm2, gemm_skip, add], + "SkipConnectionMLP", + [X], + [Y], + [init_W1, init_b1, init_W2, init_b2, init_W_skip, init_b_skip], + ) + + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 18)]) + model.ir_version = 9 + onnx.checker.check_model(model) + + m = gp.Model() + x = m.addMVar((n_in,), lb=-1.0, ub=1.0, name="x") + with self.assertRaises(NoModel) as cm: + add_predictor_constr(m, model, x) + + # Verify the error message mentions skip connections + self.assertIn("skip connection", str(cm.exception).lower()) + + def test_residual_connection_rejected(self): + # Build a model with residual connection: intermediate value used by multiple nodes + n_in, n_hidden, n_out = 4, 8, 2 + + W1 = np.random.randn(n_in, n_hidden).astype(np.float32) + b1 = np.random.randn(n_hidden).astype(np.float32) + W2a = np.random.randn(n_hidden, n_out).astype(np.float32) + b2a = np.random.randn(n_out).astype(np.float32) + W2b = np.random.randn(n_hidden, n_out).astype(np.float32) + b2b = np.random.randn(n_out).astype(np.float32) + + X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, n_in]) + Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, n_out]) + + init_W1 = helper.make_tensor( + "W1", TensorProto.FLOAT, W1.T.shape, W1.T.flatten() + ) + init_b1 = helper.make_tensor("b1", TensorProto.FLOAT, b1.shape, b1) + init_W2a = helper.make_tensor( + "W2a", TensorProto.FLOAT, W2a.T.shape, W2a.T.flatten() + ) + init_b2a = helper.make_tensor("b2a", TensorProto.FLOAT, b2a.shape, b2a) + init_W2b = helper.make_tensor( + "W2b", TensorProto.FLOAT, W2b.T.shape, W2b.T.flatten() + ) + init_b2b = helper.make_tensor("b2b", TensorProto.FLOAT, b2b.shape, b2b) + + # Shared layer + gemm1 = helper.make_node("Gemm", ["X", "W1", "b1"], ["H1"], transB=1) + relu1 = helper.make_node("Relu", ["H1"], ["A1"]) + + # Branch 1 - uses A1 + gemm2a = helper.make_node("Gemm", ["A1", "W2a", "b2a"], ["branch1"], transB=1) + + # Branch 2 - also uses A1! + gemm2b = helper.make_node("Gemm", ["A1", "W2b", "b2b"], ["branch2"], transB=1) + + # Combine branches + add = helper.make_node("Add", ["branch1", "branch2"], ["Y"]) + + graph = helper.make_graph( + [gemm1, relu1, gemm2a, gemm2b, add], + "ResidualMLP", + [X], + [Y], + [init_W1, init_b1, init_W2a, init_b2a, init_W2b, init_b2b], + ) + + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 18)]) + model.ir_version = 9 + onnx.checker.check_model(model) + + m = gp.Model() + x = m.addMVar((n_in,), lb=-1.0, ub=1.0, name="x") + with self.assertRaises(NoModel) as cm: + add_predictor_constr(m, model, x) + + # Verify the error message mentions the architecture issue + self.assertIn("non-sequential", str(cm.exception).lower())