Files
wehub-resource-sync 5cbd3f29e3
Fuzz / Run fuzz harnesses (${{ github.event_name == 'schedule' && 'nightly' || 'smoke' }}) (push) Has been cancelled
Create Releases / call-mac (push) Has been cancelled
Create Releases / call-linux (push) Has been cancelled
Create Releases / call-sdist (push) Has been cancelled
Create Releases / call-win (push) Has been cancelled
Create Releases / call-pyodide (push) Has been cancelled
Windows_No_Exception_CI / build (x64, 3.10) (push) Has been cancelled
Check URLs / build (push) Has been cancelled
Create Releases / Attest CI build artifacts (push) Has been cancelled
Create Releases / Check for Publish release build to pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Check for Publish release build to test.pypi (rc-candidates) (push) Has been cancelled
Create Releases / Publish release build to test.pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish release build to pypi (push) Has been cancelled
Create Releases / test source distribution (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
Lint / Validate SBOM (push) Has been cancelled
Lint / Enforce style (push) Has been cancelled
CI / Test windows-2022, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=1, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=1, onnx_ml=1, autogen=1 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=0, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
Pixi CI / Install and lint (ubuntu-24.04-arm) (push) Has been cancelled
Pixi CI / Install and lint (windows-2022) (push) Has been cancelled
Pixi CI / Xcode generator build (push) Has been cancelled
Pixi CI / Install and test (macos-latest, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, default) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, default) (push) Has been cancelled
Pixi CI / Install and test (macos-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, oldies) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Generate and publish ONNX docs / build (push) Has been cancelled
Generate and publish ONNX docs / deploy (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:41:19 +08:00

269 lines
9.2 KiB
Python

from __future__ import annotations
from enum import IntEnum
from typing import TYPE_CHECKING
import numpy as np
from onnx.reference.ops.aionnxml._op_run_aionnxml import OpRunAiOnnxMl
if TYPE_CHECKING:
from collections.abc import Callable
class AggregationFunction(IntEnum):
AVERAGE = 0
SUM = 1
MIN = 2
MAX = 3
class PostTransform(IntEnum):
NONE = 0
SOFTMAX = 1
LOGISTIC = 2
SOFTMAX_ZERO = 3
PROBIT = 4
class Mode(IntEnum):
LEQ = 0
LT = 1
GTE = 2
GT = 3
EQ = 4
NEQ = 5
MEMBER = 6
class Leaf:
def __init__(self, weight: float, target_id: int) -> None:
self.weight = weight
self.target_id = target_id
# Produce the weight and target index
def predict(self, x: np.ndarray) -> np.ndarray: # noqa: ARG002
return np.array([self.weight, self.target_id])
def _print(self, prefix: list, indent: int = 0) -> None:
prefix.append(
" " * indent + f"Leaf WEIGHT: {self.weight}, TARGET: {self.target_id}\n"
)
def __repr__(self) -> str:
prefix = []
self._print(prefix)
return "".join(prefix)
class Node:
compare: Callable[[float, float | set[float]], bool]
true_branch: Node | Leaf
false_branch: Node | Leaf
feature: int
def __init__(
self,
mode: Mode,
value: float | set[float],
feature: int,
missing_tracks_true: bool,
) -> None:
if mode == Mode.LEQ:
self.compare = lambda x: (
x[feature].item() <= value
or (missing_tracks_true and np.isnan(x[feature].item()))
)
elif mode == Mode.LT:
self.compare = lambda x: (
x[feature].item() < value
or (missing_tracks_true and np.isnan(x[feature].item()))
)
elif mode == Mode.GTE:
self.compare = lambda x: (
x[feature].item() >= value
or (missing_tracks_true and np.isnan(x[feature].item()))
)
elif mode == Mode.GT:
self.compare = lambda x: (
x[feature].item() > value
or (missing_tracks_true and np.isnan(x[feature].item()))
)
elif mode == Mode.EQ:
self.compare = lambda x: (
x[feature].item() == value
or (missing_tracks_true and np.isnan(x[feature].item()))
)
elif mode == Mode.NEQ:
self.compare = lambda x: (
x[feature].item() != value
or (missing_tracks_true and np.isnan(x[feature].item()))
)
elif mode == Mode.MEMBER:
self.compare = lambda x: (
x[feature].item() in value
or (missing_tracks_true and np.isnan(x[feature].item()))
)
self.mode = mode
self.value = value
self.feature = feature
def predict(self, x: np.ndarray) -> float:
if self.compare(x):
return self.true_branch.predict(x)
return self.false_branch.predict(x)
def _print(self, prefix: list, indent: int = 0) -> None:
prefix.append(
" " * indent
+ f"Node CMP: {self.mode}, SPLIT: {self.value}, FEATURE: {self.feature}\n"
)
self.true_branch._print(prefix, indent + 1)
self.false_branch._print(prefix, indent + 1)
def __repr__(self) -> str:
prefix = []
self._print(prefix)
return "".join(prefix)
class TreeEnsemble(OpRunAiOnnxMl):
def _run(
self,
X,
nodes_splits,
nodes_featureids,
nodes_modes,
nodes_truenodeids,
nodes_falsenodeids,
nodes_trueleafs,
nodes_falseleafs,
leaf_targetids,
leaf_weights,
tree_roots,
post_transform=PostTransform.NONE, # noqa: ARG002
aggregate_function=AggregationFunction.SUM,
nodes_hitrates=None, # noqa: ARG002
nodes_missing_value_tracks_true=None,
membership_values=None,
n_targets=None,
):
if membership_values is None:
# assert that no set membership ever appears
if any(mode == Mode.MEMBER for mode in nodes_modes):
raise ValueError(
"Cannot have set membership node without specifying set members"
)
elif np.isnan(membership_values).sum() != sum(
int(mode == Mode.MEMBER) for mode in nodes_modes
):
raise ValueError(
"Must specify membership values for all set membership nodes"
)
# Build each tree in the ensemble. Note that the tree structure is implicitly defined by following the true and false indices in
# `nodes_truenodeids` and `nodes_falsenodeids` to the leaves of each tree.
set_membership_iter = (
iter(membership_values) if membership_values is not None else None
)
def build_node(current_node_index, is_leaf) -> Node | Leaf:
if is_leaf:
return Leaf(
leaf_weights[current_node_index], leaf_targetids[current_node_index]
)
if nodes_modes[current_node_index] == Mode.MEMBER:
# parse next sequence of set members
set_members = set()
while (set_member := next(set_membership_iter)) and not np.isnan(
set_member
):
set_members.add(set_member)
node = Node(
nodes_modes[current_node_index],
set_members,
nodes_featureids[current_node_index],
(
nodes_missing_value_tracks_true[current_node_index]
if nodes_missing_value_tracks_true is not None
else False
),
)
else:
node = Node(
nodes_modes[current_node_index],
nodes_splits[current_node_index],
nodes_featureids[current_node_index],
(
nodes_missing_value_tracks_true[current_node_index]
if nodes_missing_value_tracks_true is not None
else False
),
)
# recurse true and false branches
node.true_branch = build_node(
nodes_truenodeids[current_node_index],
nodes_trueleafs[current_node_index],
)
node.false_branch = build_node(
nodes_falsenodeids[current_node_index],
nodes_falseleafs[current_node_index],
)
return node
trees = []
for root_index in tree_roots:
# degenerate case (tree == leaf)
is_leaf = (
nodes_trueleafs[root_index]
and nodes_falseleafs[root_index]
and nodes_truenodeids[root_index] == nodes_falsenodeids[root_index]
)
trees.append(build_node(root_index, is_leaf))
# predict each sample through tree
raw_values = [
np.apply_along_axis(tree.predict, axis=1, arr=X) for tree in trees
]
weights, target_ids = zip(
*[np.split(x, 2, axis=1) for x in raw_values], strict=False
)
weights = np.concatenate(weights, axis=1)
target_ids = np.concatenate(target_ids, axis=1).astype(np.int64)
if aggregate_function in (
AggregationFunction.SUM,
AggregationFunction.AVERAGE,
):
result = np.zeros((len(X), n_targets), dtype=X.dtype)
elif aggregate_function == AggregationFunction.MIN:
result = np.full((len(X), n_targets), np.finfo(X.dtype).max)
elif aggregate_function == AggregationFunction.MAX:
result = np.full((len(X), n_targets), np.finfo(X.dtype).min)
else:
raise NotImplementedError(
f"aggregate_transform={aggregate_function!r} not supported yet."
)
for batch_num, (w, t) in enumerate(zip(weights, target_ids, strict=False)):
weight = w.reshape(-1)
target_id = t.reshape(-1)
if aggregate_function == AggregationFunction.SUM:
for value, tid in zip(weight, target_id, strict=False):
result[batch_num, tid] += value
elif aggregate_function == AggregationFunction.AVERAGE:
for value, tid in zip(weight, target_id, strict=False):
result[batch_num, tid] += value / len(trees)
elif aggregate_function == AggregationFunction.MIN:
for value, tid in zip(weight, target_id, strict=False):
result[batch_num, tid] = min(result[batch_num, tid], value)
elif aggregate_function == AggregationFunction.MAX:
for value, tid in zip(weight, target_id, strict=False):
result[batch_num, tid] = max(result[batch_num, tid], value)
else:
raise NotImplementedError(
f"aggregate_transform={aggregate_function!r} not supported yet."
)
return (result,)