Rate Distortion Theory
Note
We use \(p\) to denote fixed probability distributions, and \(q\) to denote probability distributions that are optimized.
Rate-distortion theory [CT06] is a framework for studying optimal lossy compression. Given a distribution \(p(x)\), we wish to find \(q(\hat{x}|x)\) which compresses \(X\) as much as possible while limiting the amount of user-defined distortion, \(d(x, \hat{x})\). The minimum rate (effectively, code book size) at which \(X\) can be compressed while maintaining a fixed distortion is known as the rate-distortion curve:
By introducing a Lagrange multiplier, we can transform this constrained optimization into an unconstrained one:
where minimizing at each \(\beta\) produces a point on the curve.
RDCurve minimizes \(\mathcal{L}\) with the Blahut-Arimoto algorithm
[Ari72, Bla72], which alternates
\(q(\hat{x}|x) \propto q(\hat{x}) 2^{-\beta d(x, \hat{x})}\) with
\(q(\hat{x}) = \sum_x p(x) q(\hat{x}|x)\) (see also [CT06],
Section 10.8). When the distortion does not depend on \(q\) (as for the
Hamming distortion) the problem is convex, so two deterministic starts suffice,
and the iteration stops once Blahut’s upper and lower bounds on
\(\min \mathcal{L}\) agree. With \(c(x) = \sum_{\hat{x}} q(\hat{x}) 2^{-\beta d(x, \hat{x})}\),
those bounds differ by
\(\log_2 \max_{\hat{x}} \sum_x p(x) 2^{-\beta d(x, \hat{x})} / c(x)\).
Distortions that depend on \(q\) (residual entropy, the information
bottleneck) are not convex and keep multiple random restarts.
Example
It is known that under the Hamming distortion (\(d(x, \hat{x}) = \left[ x \neq \hat{x} \right]\)) the rate-distortion function for a biased coin has the following solution: \(R(D) = \H{p} - \H{D}\):
In [1]: from dit.rate_distortion import RDCurve
In [2]: d = dit.Distribution(['0', '1'], [1/2, 1/2])
In [3]: RDCurve(d, beta_num=26).plot();
Distortion Measures
RDCurve takes its distortion through the distortion argument. Three are
provided in dit.rate_distortion:
hamming(the default): \(d(x, \hat{x}) = \left[ x \neq \hat{x} \right]\).residual_entropy: \(d(x, \hat{x}) = -\log_2 q(x | \hat{x}) - \log_2 q(\hat{x} | x)\), whose average is the residual entropy of \(X\hat{X}\).maximum_correlation: penalizes the maximum correlation between \(X\) and \(\hat{X}\).
Each is a named tuple of a display name, a distortion matrix (when one
exists), and the optimizer class that handles it. Note that the
hamming distortion here is unrelated to the Hamming code
dit.coding.hamming().
from dit.rate_distortion import RDCurve, residual_entropy
RDCurve(d, distortion=residual_entropy)
Information Bottleneck
The information bottleneck [TPB00] is a form of rate-distortion where the distortion measure is given by:
where \(D\) is an arbitrary divergence measure, and \(\hat{X} - X - Y\) form a Markov chain. Traditionally, \(D\) is the Kullback-Leibler Divergence, in which case the average distortion takes a particular form:
Since \(\I{X : Y}\) is constant over \(q(\hat{x} | x)\), it can be removed from the optimization. Furthermore,
where the final equality is due to the Markov chain. Due to all this, Information Bottleneck utilizes a “relevance” term, \(\I{\hat{X} : Y}\), which replaces the average distortion in the Lagrangian:
Though \(\I{X : Y | \hat{X}}\) is the most simplified form of the average distortion, it is faster to compute \(\I{\hat{X} : Y}\) during optimization.
Variants and Algorithms
The standard information bottleneck uses \(\I{X : \hat{X}}\) as the compression term. The generalized information bottleneck replaces this with
so that \(\alpha = 1\) recovers the standard bottleneck, while \(\alpha = 0\) gives the deterministic information bottleneck [SS16]. The deterministic endpoint minimizes
up to the constant \(\beta\I{X : Y}\), and its optima are hard clusterings of \(X\).
IBCurve supports these variants through variant='ib',
variant='gib', and variant='dib'. The legacy alpha argument is still
accepted: alpha=1 is standard IB, alpha=0 is DIB, and intermediate
values are generalized IB. The default method='sp' uses the generic
optimizer. method='ba' uses the Blahut-Arimoto-style finite-alphabet
iteration for the standard, unconditional IB. method='sequential' and
method='agglomerative' use finite-alphabet hard-clustering algorithms for
the unconditional DIB; the latter follows the information-based clustering
viewpoint of building a hierarchy by merging clusters
[SATkavcikB05].
For deterministic prediction tasks, where \(Y\) is a function of \(X\), sweeps over the IB Lagrangian can miss portions of the IB curve and can include trivial solutions. Interpret kinks and beta sweeps in those cases with care [KTVK18].
Example
Consider this distribution:
In [4]: d = dit.Distribution(['00', '02', '12', '21', '22'], [1/5]*5)
There are effectively three features that the first index, \(X\), has regarding the second index, \(Y\). We can find them using the standard information bottleneck:
In [5]: from dit.rate_distortion import IBCurve
In [6]: IBCurve(d, beta_num=26).plot();
We can also find them utilizing the total variation:
In [7]: from dit.divergences.pmf import variational_distance
In [8]: IBCurve(d, divergence=variational_distance).plot();
Note
The spiky behavior at low \(\beta\) values is due to numerical imprecision.
Passing divergence makes IBCurve use
InformationBottleneckDivergence, the bottleneck whose distortion is
\(D[p(Y | x) \mid\mid q(Y | \hat{x})]\) for an arbitrary divergence
\(D\) operating on probability vectors (see dit.divergences.pmf).
The deterministic information bottleneck can be computed with a hard-clustering solver:
In [9]: IBCurve(d, variant='dib', method='sequential', beta_num=26).plot();
Privacy Funnel
The privacy funnel [MSFMedard14] is the information bottleneck turned around. Given private data \(S\) and public data \(X\), we release \(T\), a randomized function of \(X\) (so that \(S - X - T\) form a Markov chain), which is informative about \(X\) but reveals as little as possible about \(S\):
The leakage \(\I{S : T}\) and utility \(\I{X : T}\) are both convex in \(q(t|x)\), so their difference is not, and the privacy funnel is a non-convex problem. Its Lagrangian is
Since \(\I{S : T} \leq \I{X : T}\), every \(\beta \geq 1\) is minimized by releasing \(T = X\), so the Lagrangian is swept over \(\beta \in [0, 1]\).
For a fixed \(p(s, x)\), the pairs \((\I{X : T}, \I{S : T})\) over
all releases \(T\) form a convex set. Its upper boundary is the information
bottleneck curve of \(X\) with respect to \(S\), and its lower boundary
is the privacy funnel curve [AC20]. So the privacy funnel
curve is convex and increasing. Since it often has a narrow range of slopes, a
sweep over \(\beta\) resolves few of its points, and PFCurve sweeps the
rate \(R\) instead. The size of \(T\) is bounded by \(|X| + 1\);
unlike the information bottleneck, \(|X|\) does not suffice in general
[AC20].
greedy_privacy_funnel implements the greedy algorithm of
[MSFMedard14]: starting from \(T = X\), it repeatedly
merges the two symbols of \(T\) whose merger most reduces the leakage while
the utility is at least \(R\). Each step is locally optimal, but the result
need not be optimal overall.
Perfect Privacy
A release with non-zero utility and zero leakage exists if and only if the vectors \(p(S | x)\) are linearly dependent [CMMedard15]; in particular, whenever \(|X| > |S|\). The largest utility of such a release,
is computed exactly by perfect_privacy_utility. Every such \(T\)
writes \(p(X)\) as a mixture of distributions \(p(X | t)\) under which
\(S\) is distributed as \(p(S)\). These form a polytope, and since
entropy is concave, the optimal mixture uses only its vertices; the mixing
weights then solve a linear program [RGunduz21].
Example
Let \(S\) be binary and \(X\) ternary:
In [10]: from dit.rate_distortion import admits_perfect_privacy, perfect_privacy_utility, privacy_funnel_rate
In [11]: d = dit.Distribution(['00', '01', '12', '10'], [0.3, 0.2, 0.3, 0.2])
In [12]: admits_perfect_privacy(d)
Out[12]: True
In [13]: perfect_privacy_utility(d)
Out[13]: 0.6954618442383221
In [14]: privacy_funnel_rate(d, 1.2)
Out[14]: 0.22904935197227727
BottleneckRegionPlotter computes both curves and plots the region between
them:
In [15]: from dit.rate_distortion.plotting import BottleneckRegionPlotter
In [16]: BottleneckRegionPlotter.from_dist(d).plot();
See Also
The The Gray-Wyner Network extends rate-distortion ideas to a one-encoder, many-decoder network, and recovers the common informations as operating points.
APIs
- class RDCurve(dist, rv=None, crvs=None, beta_min=0, beta_max=10, beta_num=101, alpha=1.0, distortion=('Hamming', <function hamming_distortion>, <class 'dit.rate_distortion.rate_distortion.RateDistortionHamming'>), method=None)[source]
Compute a rate-distortion curve.
- class IBCurve(dist, rvs=None, crvs=None, beta_min=0.0, beta_max=15.0, beta_num=101, alpha=1.0, method='sp', divergence=None, variant=None, bound=None)[source]
Compute an information bottleneck curve.
- class InformationBottleneck(dist, beta, alpha=1.0, rvs=None, crvs=None, bound=None)[source]
Base optimizer for information bottleneck type calculations.
- class GeneralizedInformationBottleneck(dist, beta, alpha=1.0, rvs=None, crvs=None, bound=None)[source]
The generalized information bottleneck family.
The parameter
alphainterpolates between the standard information bottleneck (alpha=1) and the deterministic information bottleneck (alpha=0).
- class DeterministicInformationBottleneck(dist, beta, alpha=0.0, rvs=None, crvs=None, bound=None)[source]
The deterministic information bottleneck objective.
This is the
alpha=0endpoint of the generalized information bottleneck, using \(H(T | Z)\) as the compression term.
- class InformationBottleneckDivergence(dist, beta, alpha=1.0, divergence=<function relative_entropy_pmf>, rvs=None, crvs=None, bound=None)[source]
A generalized information bottleneck which uses a distortion equal to D( p(Y|x) || q(Y|t) ) for an arbitrary divergence measure D.
- class PFCurve(dist, rvs=None, crvs=None, rate_num=26, method='sp', bound=None)[source]
Compute a privacy funnel curve [MSFMedard14]: the least leakage
I[S : T | Z]of a release T of X as a function of its utilityI[X : T | Z]. It is the lower boundary of the region whose upper boundary is the information bottleneck curve of X with respect to S [AC20].The curve is swept over utility rates rather than Lagrange multipliers: it is convex, often with a narrow range of slopes, so a sweep over the multiplier would resolve only a few of its points.
- class PrivacyFunnel(dist, beta, rvs=None, crvs=None, bound=None)[source]
The privacy funnel Lagrangian [MSFMedard14],
\[\min_{p(t | x, z)} I[S : T | Z] - \beta I[X : T | Z],\]over mappings T of X, so that S - XZ - T. Since
I[S : T | Z] <= I[X : T | Z], everybeta >= 1is minimized by releasingT = X, so the whole curve is traced bybetain [0, 1]. The objective is a difference of convex functions of the mapping, so it is non-convex [AC20].
- class PrivacyFunnelRate(dist, rate, rvs=None, crvs=None, bound=None)[source]
The privacy funnel [MSFMedard14],
\[\mathrm{PF}(R) = \min_{p(t | x, z) : I[X : T | Z] \geq R} I[S : T | Z].\]The size of T is bounded by
|X| + 1; unlike the information bottleneck,|X|does not suffice in general [AC20].
- privacy_funnel(dist, beta, rvs=None, crvs=None, bound=None)
Compute a point on the privacy funnel by minimizing
I[S : T | Z] - beta I[X : T | Z][MSFMedard14].- Parameters:
dist (Distribution) – The distribution of interest.
beta (float) – The non-negative price of utility.
rvs (iterable of iterables, None) – The private variables S and the public variables X. If None, use
dist.rvs.crvs (iterable, None) – The random variables to condition on.
bound (int, None) – The bound on the size of T. If None, use
|X|.
- Returns:
leakage (float) – I[S : T | Z] at the optimum.
utility (float) – I[X : T | Z] at the optimum.
- privacy_funnel_rate(dist, rate, rvs=None, crvs=None, bound=None)
Compute the privacy funnel, the least leakage
I[S : T | Z]of a release T of X with utilityI[X : T | Z] >= rate[MSFMedard14].- Parameters:
dist (Distribution) – The distribution of interest.
rate (float) – The minimum utility, in bits.
rvs (iterable of iterables, None) – The private variables S and the public variables X. If None, use
dist.rvs.crvs (iterable, None) – The random variables to condition on.
bound (int, None) – The bound on the size of T. If None, use
|X| + 1.
- Returns:
pf – The minimal leakage.
- Return type:
- greedy_privacy_funnel(p_sx, rate=0.0)[source]
Greedily merge released symbols for the privacy funnel [MSFMedard14].
Starting from \(T = X\), repeatedly merge the pair of symbols of \(T\) whose merger most reduces the leakage \(I(S : T)\) while keeping the utility \(I(X : T) \geq R\), until no merge is feasible. Each step is locally optimal; the result need not be globally optimal.
- Parameters:
p_sx (np.ndarray) – Joint distribution with axis 0 indexing the private \(S\) and axis 1 indexing the public \(X\).
rate (float) – The minimum utility \(R\), in bits. Must lie in \([0, H(X)]\).
- Returns:
result – The hard privacy mapping found.
- Return type:
- Raises:
ditException – If
rateis outside \([0, H(X)]\).
- class PrivacyFunnelResult(p_t_given_x: ndarray, leakage: float, utility: float, active: int, assignments: ndarray | None = None, merges: tuple[tuple[int, int], ...] = ())[source]
The result of a finite-alphabet privacy funnel optimization.
- p_t_given_x
The privacy mapping \(p(t | x)\).
- Type:
np.ndarray
- leakage
\(I(S : T)\).
- Type:
- utility
\(I(X : T)\).
- Type:
- active
The number of non-empty released states.
- Type:
- assignments
The hard cluster assignment for each \(x\), when available.
- Type:
np.ndarray
- admits_perfect_privacy(dist, rvs=None)[source]
Determine whether X can be released with non-zero utility and zero leakage about S.
This is the case if and only if the vectors p(S | x), over the support of X, are linearly dependent [CMMedard15]; in particular, whenever
|X| > |S|.- Parameters:
dist (Distribution) – The distribution of interest.
rvs (iterable of iterables, None) – The private variables S and the public variables X. If None, use
dist.rvs.
- Returns:
perfect – Whether perfect privacy is possible.
- Return type:
- perfect_privacy_utility(dist, rvs=None, return_mechanism=False, max_candidates=100000)[source]
Compute the maximal utility at perfect privacy,
\[g_0 = \max_{p(t | x) : I(S : T) = 0} I(X : T),\]exactly, by enumerating the vertices of the perfect privacy polytope and solving a linear program [RGunduz21].
- Parameters:
dist (Distribution) – The distribution of interest.
rvs (iterable of iterables, None) – The private variables S and the public variables X. If None, use
dist.rvs.return_mechanism (bool) – If True, also return an optimal mapping p(t | x).
max_candidates (int) – The maximum number of column bases to enumerate.
- Returns:
g0 (float) – The maximal utility, in bits, of a release leaking nothing about S.
p_t_given_x (np.ndarray) – An optimal mapping of shape
(|X|, |T|), ifreturn_mechanism.
- Raises:
ditException – If the polytope has too many candidate bases.
- class BottleneckRegionPlotter(ib_curve=None, pf_curve=None)[source]
Plot the information bottleneck and privacy funnel curves together. For a fixed p(s, x), every release T of X gives a point
(I[X : T | Z], I[S : T | Z]); these points form a convex region whose upper boundary is the information bottleneck curve and whose lower boundary is the privacy funnel curve [AC20].- classmethod from_dist(dist, rvs=None, crvs=None, ib_kwargs=None, pf_kwargs=None)[source]
Compute both curves of a distribution and build a plotter for them.
- Parameters:
dist (Distribution) – The distribution of interest.
rvs (iterable, None) – The private variables S and the public variables X. If None, use [0], [1].
crvs (iterable, None) – The random variables to condition on.
ib_kwargs (dict, None) – Extra keyword arguments for
IBCurve.pf_kwargs (dict, None) – Extra keyword arguments for
PFCurve.
- Returns:
plotter – The plotter.
- Return type:
BottleneckRegionPlotter