{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"tags": [
"remove-cell"
]
},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"plt.style.use('seaborn-whitegrid')\n",
"import pandas as pd\n",
"import numpy as np\n",
"\n",
"adult = pd.read_csv(\"adult_with_pii.csv\")\n",
"def laplace_mech(v, sensitivity, epsilon):\n",
" return v + np.random.laplace(loc=0, scale=sensitivity / epsilon)\n",
"def pct_error(orig, priv):\n",
" return np.abs(orig - priv)/orig * 100.0\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# The Exponential Mechanism\n",
"\n",
"```{admonition} Learning Objectives\n",
"After reading this chapter, you will be able to:\n",
"- Define, implement, and apply the Exponential and Report Noisy Max mechanisms\n",
"- Describe the challenges of applying the Exponential mechanism in practice\n",
"- Describe the advantages of these mechanisms\n",
"```\n",
"\n",
"The fundamental mechanisms we have seen so far (Laplace and Gaussian) are focused on numerical answers, and add noise directly to the answer itself. What if we want to return a precise answer (i.e. no added noise), but still preserve differential privacy? One solution is the exponential mechanism {cite}`mcsherry2007`, which allows selecting the \"best\" element from a set while preserving differential privacy. The analyst defines which element is the \"best\" by specifying a *scoring function* that outputs a score for each element in the set, and also defines the set of things to pick from. The mechanism provides differential privacy by *approximately* maximizing the score of the element it returns - in other words, to satisfy differential privacy, the exponential mechanism sometimes returns an element from the set which does *not* have the highest score.\n",
"\n",
"The exponential mechanism satisfies $\\epsilon$-differential privacy:\n",
"\n",
"1. The analyst selects a set $\\mathcal{R}$ of possible outputs\n",
"2. The analyst specifies a scoring function $u : \\mathcal{D} \\times \\mathcal{R} \\rightarrow \\mathbb{R}$ with global sensitivity $\\Delta u$\n",
"3. The exponential mechanism outputs $r \\in \\mathcal{R}$ with probability proportional to:\n",
"\n",
"\\begin{align}\n",
"\\exp \\Big(\\frac{\\epsilon u(x, r)}{2 \\Delta u} \\Big)\n",
"\\end{align}\n",
"\n",
"The biggest practical difference between the exponential mechanism and the previous mechanisms we've seen (e.g. the Laplace mechanism) is that the output of the exponential mechanism is *always* a member of the set $\\mathcal{R}$. This is extremely useful when selecting an item from a finite set, when a noisy answer would not make sense. For example, we might want to pick a date for a big meeting, which uses each participant's personal calendar to maximize the number of participants without a conflict, while providing differential privacy for the calendars. Adding noise to a date doesn't make much sense: it might turn a Friday into a Saturday, and *increase* the number of conflicts significantly. The exponential mechanism is perfect for problems like this one: it selects a date *without noise*.\n",
"\n",
"The exponential mechanism is interesting for several reasons:\n",
"\n",
"- The privacy cost of the mechanism is just $\\epsilon$, regardless of the size of $\\mathcal{R}$ - more on this next.\n",
"- It works for both finite and infinite sets $\\mathcal{R}$, but it can be really challenging to build a practical implementation which samples from the appropriate probability distribution when $\\mathcal{R}$ is infinite.\n",
"- It represents a \"fundamental mechanism\" of $\\epsilon$-differential privacy: all other $\\epsilon$-differentially private mechanisms can be defined in terms of the exponential mechanism with the appropriate definition of the scoring function $u$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Exponential Mechanism for Finite Sets\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10.683"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"options = adult['Marital Status'].unique()\n",
"\n",
"def score(data, option):\n",
" return data.value_counts()[option]/1000\n",
"\n",
"score(adult['Marital Status'], 'Never-married')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Married-civ-spouse'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def exponential(x, R, u, sensitivity, epsilon):\n",
" # Calculate the score for each element of R\n",
" scores = [u(x, r) for r in R]\n",
" \n",
" # Calculate the probability for each element, based on its score\n",
" probabilities = [np.exp(epsilon * score / (2 * sensitivity)) for score in scores]\n",
" \n",
" # Normalize the probabilties so they sum to 1\n",
" probabilities = probabilities / np.linalg.norm(probabilities, ord=1)\n",
"\n",
" # Choose an element from R based on the probabilities\n",
" return np.random.choice(R, 1, p=probabilities)[0]\n",
"\n",
"exponential(adult['Marital Status'], options, score, 1, 1)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Married-civ-spouse 179\n",
"Never-married 21\n",
"dtype: int64"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"r = [exponential(adult['Marital Status'], options, score, 1, 1) for i in range(200)]\n",
"pd.Series(r).value_counts()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Report Noisy Max\n",
"\n",
"Can we recover the exponential mechanism using the Laplace mechanism? In the case of a finite set $\\mathcal{R}$, the basic idea of the exponential mechanism - to select from a set with differential privacy - suggests a naive implementation in terms of the Laplace mechanism:\n",
"\n",
"1. For each $r \\in \\mathcal{R}$, calculate a *noisy score* $u(x, r) + \\mathsf{Lap}\\left(\\frac{\\Delta u}{\\epsilon}\\right)$\n",
"2. Output the element $r \\in \\mathcal{R}$ with the maximum noisy score\n",
"\n",
"Since the scoring function $u$ is $\\Delta u$ sensitive in $x$, each \"query\" in step 1 satisfies $\\epsilon$-differential privacy. Thus if $\\mathcal{R}$ contains $n$ elements, the above algorithm satisfies $n\\epsilon$-differential privacy by sequential composition.\n",
"\n",
"However, if we used the exponential mechanism, the total cost would be just $\\epsilon$ instead! Why is the exponential mechanism so much better? Because *it releases less information*.\n",
"\n",
"Our analysis of the Laplace-based approach defined above is very pessimistic. The whole set of noisy scores computed in step 1 actually satisfies $n\\epsilon$-differential privacy, and we could release the whole thing. That the output in step 2 satisfies $n\\epsilon$-differential privacy follows from the post-processing property.\n",
"\n",
"But the exponential mechanism releases *only* the identity of the element with the maximum noisy score - *not* the score itself, or the scores of any other element.\n",
"\n",
"The algorithm defined above is often called the *report noisy max* algorithm, and it actually satisfies $\\epsilon$-differential privacy, no matter how large the set $\\mathcal{R}$ is - specifically because it releases *only* the identity of the element with the largest noisy count. The proof can be found in [Dwork and Roth](https://www.cis.upenn.edu/~aaroth/Papers/privacybook.pdf) {cite}`dwork2014`, Claim 3.9.\n",
"\n",
"Report noisy max is easy to implement, and it's easy to see that it produces very similar results to our earlier implementation of the exponential mechanism for finite sets."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Married-civ-spouse'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def report_noisy_max(x, R, u, sensitivity, epsilon):\n",
" # Calculate the score for each element of R\n",
" scores = [u(x, r) for r in R]\n",
"\n",
" # Add noise to each score\n",
" noisy_scores = [laplace_mech(score, sensitivity, epsilon) for score in scores]\n",
"\n",
" # Find the index of the maximum score\n",
" max_idx = np.argmax(noisy_scores)\n",
" \n",
" # Return the element corresponding to that index\n",
" return R[max_idx]\n",
"\n",
"report_noisy_max(adult['Marital Status'], options, score, 1, 1)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"Married-civ-spouse 192\n",
"Never-married 8\n",
"dtype: int64"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"r = [report_noisy_max(adult['Marital Status'], options, score, 1, 1) for i in range(200)]\n",
"pd.Series(r).value_counts()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So the exponential mechanism can be replaced with report noisy max when the set $\\mathcal{R}$ is finite, but what about when it's infinite? We can't easily add Laplace noise to an infinite set of scores. In this context, we have to use the actual exponential mechanism. \n",
"\n",
"In practice, however, using the exponential mechanism for infinite sets is often challenging or impossible. While it's easy to write down the probability density function defined by the mechanism, it's often the case that no efficient algorithm exists for sampling from it. As a result, numerous theoretical papers appeal to the exponential mechanism to show that a differentially private algorithm \"exists\" with certain desirable properties, but many of these algorithms are impossible to use in practice."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Exponential Mechanism as Fundamental Mechanism for Differential Privacy\n",
"\n",
"We've seen that it's not possible to recover the exponential mechanism using the Laplace mechanism plus sequential composition, because we can't capture the fact that the algorithm we designed doesn't release all of the noisy scores. What about the reverse - can we recover the Laplace mechanism from the exponential mechanism? It turns out that we can!\n",
"\n",
"Consider a query $q(x) : \\mathcal{D} \\rightarrow \\mathbb{R}$ with sensitivity $\\Delta q$. We can release an $\\epsilon$-differentially private answer by adding Laplace noise: $F(x) = q(x) + \\mathsf{Lap}(\\Delta q / \\epsilon)$. The probability density function for this differentially private version of $q$ is:\n",
"\n",
"\\begin{align}\n",
"\\mathsf{Pr}[F(x) = r] =& \\frac{1}{2b} \\exp\\Big(- \\frac{\\lvert r - \\mu \\rvert}{b}\\Big)\\\\\n",
"=& \\frac{\\epsilon}{2 \\Delta q} \\exp\\Big(- \\frac{\\epsilon \\lvert r - q(x) \\rvert}{\\Delta q}\\Big)\n",
"\\end{align}\n",
"\n",
"Consider what happens when we set the scoring function for the exponential mechanism to $u(x, r) = -2 \\lvert q(x) - r \\rvert$. The exponential mechanism says that we should sample from the probability distribution proportional to:\n",
"\n",
"\\begin{align}\n",
"\\mathsf{Pr}[F(x) = r] =&\\; \\exp \\Big(\\frac{\\epsilon u(x, r)}{2 \\Delta u} \\Big)\\\\\n",
"&= \\exp \\Big(\\frac{\\epsilon (-2 \\lvert q(x) - r \\rvert)}{2 \\Delta q} \\Big)\\\\\n",
"&= \\exp \\Big(- \\frac{\\epsilon \\lvert r - q(x) \\rvert}{\\Delta q} \\Big)\\\\\n",
"\\end{align}\n",
"\n",
"So it's possible to recover the Laplace mechanism from the exponential mechanism, and we get the same results (up to constant factors - the general analysis for the exponential mechanism is not tight in all cases).\n",
"\n",
"The exponential mechanism is extremely general - it's generally possible to re-define any $\\epsilon$-differentially private mechanism in terms of a carefully chosen definition of the scoring function $u$. If we can analyze the sensitivity of this scoring function, then the proof of differential privacy comes for free.\n",
"\n",
"On the other hand, applying the general analysis of the exponential mechanism sometimes comes at the cost of looser bounds (as in the Laplace example above), and mechanisms defined in terms of the exponential mechanism are often very difficult to implement. The exponential mechanism is often used to prove theoretical lower bounds (by showing that a differentially private algorithm *exists*), but practical algorithms often replicate the same behavior using some other approach (as in the case of report noisy max above)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.10"
}
},
"nbformat": 4,
"nbformat_minor": 2
}