108 lines
3.2 KiB
Python
108 lines
3.2 KiB
Python
"""
|
|
Internal helper utilities shared by quantification methods.
|
|
"""
|
|
|
|
import numpy as np
|
|
from sklearn.metrics import confusion_matrix
|
|
from sklearn.preprocessing import LabelEncoder
|
|
|
|
|
|
def _get_abstention_calibrators():
|
|
try:
|
|
from abstention.calibration import NoBiasVectorScaling, TempScaling, VectorScaling
|
|
except ImportError as exc:
|
|
raise ImportError(
|
|
"Posterior calibration for EMQ requires the optional 'abstention' package."
|
|
) from exc
|
|
return {
|
|
'nbvs': NoBiasVectorScaling(),
|
|
'bcts': TempScaling(bias_positions='all'),
|
|
'ts': TempScaling(),
|
|
'vs': VectorScaling(),
|
|
}
|
|
|
|
|
|
def _get_cvxpy():
|
|
try:
|
|
import cvxpy as cp
|
|
except ImportError as exc:
|
|
raise ImportError(
|
|
"RLLS requires the optional 'cvxpy' package."
|
|
) from exc
|
|
return cp
|
|
|
|
|
|
def _labels_to_indices(labels, classes):
|
|
encoder = LabelEncoder().fit(classes)
|
|
return encoder.transform(labels)
|
|
|
|
|
|
def _rlls_check_mode(mode):
|
|
valid = {'soft', 'hard'}
|
|
if mode not in valid:
|
|
raise ValueError(f'unknown mode {mode!r}; valid ones are {valid}')
|
|
return mode
|
|
|
|
|
|
def _rlls_joint_distribution(posteriors, labels, classes, mode='soft'):
|
|
mode = _rlls_check_mode(mode)
|
|
posteriors = np.asarray(posteriors, dtype=float)
|
|
labels = np.asarray(labels)
|
|
n_samples, n_classes = posteriors.shape
|
|
assert n_classes == len(classes), 'wrong number of posterior columns'
|
|
|
|
if mode == 'hard':
|
|
pred = np.argmax(posteriors, axis=1)
|
|
encoded_labels = _labels_to_indices(labels, classes)
|
|
joint = confusion_matrix(encoded_labels, pred, labels=np.arange(n_classes)).T.astype(float)
|
|
return joint / n_samples
|
|
|
|
joint = np.zeros((n_classes, n_classes), dtype=float)
|
|
for class_index, class_ in enumerate(classes):
|
|
idx = labels == class_
|
|
if idx.any():
|
|
joint[:, class_index] = posteriors[idx].sum(axis=0)
|
|
return joint / n_samples
|
|
|
|
|
|
def _rlls_predicted_marginal(posteriors, mode='soft'):
|
|
mode = _rlls_check_mode(mode)
|
|
posteriors = np.asarray(posteriors, dtype=float)
|
|
if mode == 'soft':
|
|
return posteriors.mean(axis=0)
|
|
|
|
pred = np.argmax(posteriors, axis=1)
|
|
counts = np.bincount(pred, minlength=posteriors.shape[1]).astype(float)
|
|
return counts / counts.sum()
|
|
|
|
|
|
def _rlls_compute_3deltaC(n_classes, n_train, delta):
|
|
return 3 * (
|
|
2 * np.log(2 * n_classes / delta) / (3 * n_train)
|
|
+ np.sqrt(2 * np.log(2 * n_classes / delta) / n_train)
|
|
)
|
|
|
|
|
|
def _rlls_compute_weights(C_zy, qz, pz, rho, clip=False):
|
|
cp = _get_cvxpy()
|
|
|
|
n_classes = C_zy.shape[0]
|
|
theta = cp.Variable(n_classes)
|
|
b = qz - pz
|
|
objective = cp.Minimize(cp.pnorm(C_zy @ theta - b) + rho * cp.pnorm(theta))
|
|
constraints = [-1 <= theta]
|
|
problem = cp.Problem(objective, constraints)
|
|
|
|
try:
|
|
problem.solve(verbose=False, solver=cp.SCS)
|
|
except cp.error.SolverError:
|
|
problem.solve(verbose=False, solver=cp.SCS, use_indirect=True)
|
|
|
|
if theta.value is None:
|
|
raise RuntimeError('RLLS optimization failed to produce a solution')
|
|
|
|
w = 1 + np.asarray(theta.value, dtype=float)
|
|
if clip and np.any(w < 0):
|
|
w = np.clip(w, 0, None)
|
|
return w
|