Source code for heavyedge_features.iproj

"""Information projection to allowed classes."""

import cvxpy as cp
import numpy as np

__all__ = [
    "signed_iproj",
]


[docs] def signed_iproj(p, target_indices): """Signed information projection distance to allowed classes. Parameters ---------- p : array-like, shape (K,) Probability distribution over K classes. target_indices : list of int List of target class indices to project onto. Returns ------- signed_distance : float Signed information projection distance to the allowed classes. projected_distribution : array-like, shape (K,) Probability distribution after projection. Examples -------- >>> import numpy as np >>> from heavyedge_features.iproj import signed_iproj >>> p = np.array([0.1, 0.7, 0.2]) >>> target_indices = [0, 2] >>> dist, q = signed_iproj(p, target_indices) >>> dist np.float64(0.164...) >>> q array([0.117..., 0.441..., 0.441...]) """ p = np.maximum(p, 1e-12) # Avoid log(0) p = p / p.sum() dists = [] qs = [] for target_index in target_indices: dist, q = _class_dist(p, target_index) dists.append(dist) qs.append(q) idx = np.argmin(dists) min_dist = dists[idx] mindist_q = qs[idx] if np.argmax(p) in target_indices: sign = -1 # signed distance else: sign = 1 signed_distance, projected_distribution = sign * min_dist, mindist_q return signed_distance, projected_distribution
def _class_dist(p, i): K = len(p) dists = [] qs = [] for j in range(K): if j == i: continue dist, q = _class_dist_ij(p, i, j) dists.append(dist) qs.append(q) idx = np.argmin(dists) return dists[idx], qs[idx] def _class_dist_ij(p, i, j): K = len(p) q = cp.Variable(K, nonneg=True) constraints = [cp.sum(q) == 1] # Constraints: q[i] == q[j], q[i] >= q[other] for k in range(K): if k == i: continue elif k == j: constraints.append(q[i] == q[k]) else: constraints.append(q[i] >= q[k]) objective = cp.Minimize(cp.sum(cp.kl_div(q, p))) return cp.Problem(objective, constraints).solve(), q.value