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

334 lines
11 KiB
Python

# Copyright (c) ONNX Project Contributors
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import numpy as np
from onnx.reference.ops.aionnxml._common_classifier import (
compute_logistic,
compute_probit,
compute_softmax_zero,
logistic,
softmax,
softmax_zero,
)
from onnx.reference.ops.aionnxml._op_run_aionnxml import OpRunAiOnnxMl
from onnx.reference.ops.aionnxml.op_svm_helper import SVMCommon
def multiclass_probability(k, R):
max_iter = max(100, k)
Q = np.empty((k, k), dtype=R.dtype)
Qp = np.empty((k,), dtype=R.dtype)
P = np.empty((k,), dtype=R.dtype)
eps = 0.005 / k
for t in range(k):
P[t] = 1.0 / k
Q[t, t] = (R[:t, t] ** 2).sum()
Q[t, :t] = Q[:t, t]
Q[t, t] += (R[t + 1 :, t] ** 2).sum()
Q[t, t + 1 :] = -R[t + 1 :, t] @ R[t, t + 1 :]
for _ in range(max_iter):
# stopping condition, recalculate QP,pQP for numerical accuracy
Qp[:] = Q @ P
pQp = (P * Qp).sum()
max_error = 0
for t in range(k):
error = np.abs(Qp[t] - pQp)
max_error = max(error, max_error)
if max_error < eps:
break
for t in range(k):
diff = (-Qp[t] + pQp) / Q[t, t]
P[t] += diff
pQp = (pQp + diff * (diff * Q[t, t] + 2 * Qp[t])) / (1 + diff) ** 2
P /= 1 + diff
Qp[:] = (Qp + diff * Q[t, :]) / (1 + diff)
return P
def sigmoid_probability(score, proba, probb):
# ref: https://github.com/arnaudsj/libsvm/blob/eaaefac5ebd32d0e07902e1ae740e038eaaf0826/svm.cpp#L1818
val = score * proba + probb
return 1 - compute_logistic(val)
def write_scores(n_classes, scores, post_transform, add_second_class): # noqa: PLR0911
if n_classes >= 2:
if post_transform == "PROBIT":
res = [compute_probit(score) for score in scores]
return np.array(res, dtype=scores.dtype)
if post_transform == "LOGISTIC":
return logistic(scores)
if post_transform == "SOFTMAX":
return softmax(scores)
if post_transform == "SOFTMAX_ZERO":
return compute_softmax_zero(scores)
return scores
if n_classes == 1:
if post_transform == "PROBIT":
return np.array([compute_probit(scores[0])], dtype=scores.dtype)
if add_second_class in (0, 1):
return np.array([1 - scores[0], scores[0]], dtype=scores.dtype)
if add_second_class in (2, 3):
if post_transform == "LOGISTIC":
return np.array(
[logistic(-scores[0]), logistic(scores[0])], dtype=scores.dtype
)
if post_transform == "SOFTMAX":
return softmax(np.array([-scores[0], scores[0]], dtype=scores.dtype))
if post_transform == "SOFTMAX_ZERO":
return softmax_zero(
np.array([-scores[0], scores[0]], dtype=scores.dtype)
)
if post_transform == "PROBIT":
raise RuntimeError(
f"post_transform={post_transform!r} not applicable here."
)
return np.array([-scores[0], scores[0]], dtype=scores.dtype)
return np.array([scores[0]], dtype=scores.dtype)
raise NotImplementedError(f"n_classes={n_classes} not supported.")
def set_score_svm(
max_weight,
maxclass,
has_proba,
weights_are_all_positive_,
classlabels,
posclass,
negclass,
):
write_additional_scores = -1
if len(classlabels) == 2:
write_additional_scores = 2
if not has_proba:
if weights_are_all_positive_ and max_weight >= 0.5:
return classlabels[1], write_additional_scores
if max_weight > 0 and not weights_are_all_positive_:
return classlabels[maxclass], write_additional_scores
return classlabels[maxclass], write_additional_scores
if max_weight > 0:
return posclass, write_additional_scores
return negclass, write_additional_scores
class SVMClassifier(OpRunAiOnnxMl):
def _run_linear(self, X, coefs, class_count_, kernel_type_):
scores = []
for j in range(class_count_):
d = self._svm.kernel_dot(X, coefs[j], kernel_type_)
score = self._svm.atts.rho[0] + d
scores.append(score)
return np.array(scores, dtype=X.dtype)
def _run_svm(
self, X, sv, vector_count_, kernel_type_, class_count_, starting_vector_, coefs
):
evals = 0
kernels_list = [
self._svm.kernel_dot(X, sv[j], kernel_type_) for j in range(vector_count_)
]
kernels = np.array(kernels_list)
votes = np.zeros((class_count_,), dtype=X.dtype)
scores = []
for i in range(class_count_):
si_i = starting_vector_[i]
class_i_sc = self._svm.atts.vectors_per_class[i]
for j in range(i + 1, class_count_):
si_j = starting_vector_[j]
class_j_sc = self._svm.atts.vectors_per_class[j]
s1 = np.dot(
coefs[j - 1, si_i : si_i + class_i_sc],
kernels[si_i : si_i + class_i_sc],
)
s2 = np.dot(
coefs[i, si_j : si_j + class_j_sc],
kernels[si_j : si_j + class_j_sc],
)
s = self._svm.atts.rho[evals] + s1 + s2
scores.append(s)
if s > 0:
votes[i] += 1
else:
votes[j] += 1
evals += 1
return votes, np.array(scores, dtype=X.dtype)
def _probabilities(self, scores, class_count_):
probsp2 = np.zeros((class_count_, class_count_), dtype=scores.dtype)
index = 0
for i in range(class_count_):
for j in range(i + 1, class_count_):
val1 = sigmoid_probability(
scores[index],
self._svm.atts.prob_a[index],
self._svm.atts.prob_b[index],
)
val2 = max(val1, 1.0e-7)
val2 = min(val2, (1 - 1.0e-7))
probsp2[i, j] = val2
probsp2[j, i] = 1 - val2
index += 1
return multiclass_probability(class_count_, probsp2)
def _compute_final_scores(
self, votes, scores, weights_are_all_positive_, has_proba, classlabels_ints
):
max_weight = 0
if votes is not None and len(votes) > 0:
max_class = np.argmax(votes)
max_weight = votes[max_class]
else:
max_class = np.argmax(scores)
max_weight = scores[max_class]
write_additional_scores = -1
if self._svm.atts.rho.size == 1:
label, write_additional_scores = set_score_svm(
max_weight,
max_class,
has_proba,
weights_are_all_positive_,
classlabels_ints,
1,
0,
)
elif classlabels_ints is not None and len(classlabels_ints) > 0:
label = classlabels_ints[max_class]
else:
label = max_class
new_scores = write_scores(
scores.size,
scores,
self._svm.atts.post_transform,
write_additional_scores,
)
return label, new_scores
def _run(
self,
X,
classlabels_ints=None,
classlabels_strings=None,
coefficients=None,
kernel_params=None,
kernel_type=None,
post_transform=None,
prob_a=None,
prob_b=None,
rho=None,
support_vectors=None,
vectors_per_class=None,
):
svm = SVMCommon(
coefficients=coefficients,
kernel_params=kernel_params,
kernel_type=kernel_type,
post_transform=post_transform,
prob_a=prob_a,
prob_b=prob_b,
rho=rho,
support_vectors=support_vectors,
vectors_per_class=vectors_per_class,
)
# unused unless for debugging purposes
self._svm = svm
vector_count_ = 0
class_count_ = max(len(classlabels_ints or classlabels_strings or []), 1)
starting_vector_ = []
if svm.atts.vectors_per_class is not None:
for vc in svm.atts.vectors_per_class:
starting_vector_.append(vector_count_)
vector_count_ += vc
if vector_count_ > 0:
# length of each support vector
mode = "SVM_SVC"
sv = svm.atts.support_vectors.reshape((vector_count_, -1))
kernel_type_ = svm.atts.kernel_type
coefs = svm.atts.coefficients.reshape((-1, vector_count_))
else:
# liblinear mode
mode = "SVM_LINEAR"
kernel_type_ = "LINEAR"
coefs = svm.atts.coefficients.reshape((class_count_, -1))
weights_are_all_positive_ = min(svm.atts.coefficients) >= 0
# SVM part
if vector_count_ == 0 and mode == "SVM_LINEAR":
res = np.empty((X.shape[0], class_count_), dtype=X.dtype)
for n in range(X.shape[0]):
scores = self._run_linear(X[n], coefs, class_count_, kernel_type_)
res[n, :] = scores
votes = None
else:
res = np.empty(
(X.shape[0], class_count_ * (class_count_ - 1) // 2), dtype=X.dtype
)
votes = np.empty((X.shape[0], class_count_), dtype=X.dtype)
for n in range(X.shape[0]):
vote, scores = self._run_svm(
X[n],
sv,
vector_count_,
kernel_type_,
class_count_,
starting_vector_,
coefs,
)
res[n, :] = scores
votes[n, :] = vote
# proba
if (
svm.atts.prob_a is not None
and len(svm.atts.prob_a) > 0
and mode == "SVM_SVC"
):
scores = np.empty((res.shape[0], class_count_), dtype=X.dtype)
for n in range(scores.shape[0]):
s = self._probabilities(res[n], class_count_)
scores[n, :] = s
has_proba = True
else:
scores = res
has_proba = False
# finalization
final_scores = None
labels = []
for n in range(scores.shape[0]):
label, new_scores = self._compute_final_scores(
None if votes is None else votes[n],
scores[n],
weights_are_all_positive_,
has_proba,
classlabels_ints,
)
if final_scores is None:
final_scores = np.empty((X.shape[0], new_scores.size), dtype=X.dtype)
final_scores[n, :] = new_scores
labels.append(label)
# labels
if classlabels_strings is not None and len(classlabels_strings) > 0:
return (np.array([classlabels_strings[i] for i in labels]), final_scores)
return (np.array(labels, dtype=np.int64), final_scores)