Skip to content

Latest commit

 

History

History
327 lines (219 loc) · 11.4 KB

File metadata and controls

327 lines (219 loc) · 11.4 KB

Erroneous Examples

Each question below shows an LLM-generated program that attempts to solve the task but is incorrect. Your job is to identify the reason for the error. Pick an answer before expanding the solution.

Use Case

This question set focuses on the following use case:

You are given a dataset of restaurant visits spanning one week. The intended goal is to compute the proportion of visits lasting longer than 60 minutes, using individual-level pure differential privacy with ε = 1.0 to protect visitors' privacy.

The dataset contains the following columns:

  • VisitorId: A unique identifier for each customer.
  • Time entered: The time the customer entered the restaurant.
  • Time spent (minutes): The duration of the customer's visit in minutes.
  • Money spent (euros): The amount of money spent by the customer during their visit.
  • Day: The day of the week, represented as a number from 1 (Monday) to 7 (Sunday).

Q1

Why does the following program fail to compute the proportion of visits lasting longer than 60 minutes with individual-level pure differential privacy (ε = 1.0)?

import pandas as pd
import numpy as np

# DP parameters
epsilon = 1.0
sensitivity = 1.0
epsilon_per_query = epsilon / 2  # Split ε equally

# Load data
df = pd.read_csv("restaurant.csv")

# Group by VisitorId to apply individual-level DP
grouped = df.groupby("VisitorId")

# Bound individual contribution: 1 if any of their visits were > 60 mins
long_visit_flags = grouped["Time spent (minutes)"].apply(lambda x: (x > 60).any()).astype(int)

# Each visitor contributes 1 to the total
total_visits = len(long_visit_flags)
long_visits = long_visit_flags.sum()

# Apply Laplace noise
noisy_total = total_visits + np.random.laplace(scale=sensitivity / epsilon_per_query)
noisy_long = long_visits + np.random.laplace(scale=sensitivity / epsilon_per_query)

# Compute DP proportion
if noisy_total <= 0:
    dp_proportion = 0.0
else:
    dp_proportion = np.clip(noisy_long / noisy_total, 0.0, 1.0)

# Output
print(f"Differentially private proportion of customers with >60 min visits: {dp_proportion:.4f}")

Choices:

  • Only part of the result is private; the rest still leaks information.
  • The program does not fully utilize the epsilon.
  • The sensitivity does not account for the maximum change in the query result when a single privacy unit's data is added or removed.
Answer & Explanation

Answer: The sensitivity does not account for the maximum change in the query result when a single privacy unit's data is added or removed.

The sensitivity should reflect the maximum possible change in the query result when the data of a single privacy unit is added or removed. That is, we should have:

  • sensitivity for total visit: maximum number of visits that one visitor might contribute
  • sensitivity for long visit: maximum number of long visits that one visitor might contribute

Currently, the program sets both sensitivities to 1, but a single visitor may visit the restaurant multiple times within a week. Setting sensitivities to 1 fails to reflect the maximum changes.

There are two ways to fix this:

  1. Query the maximum number of visits per visitor, using some of the privacy budget.
  2. Set data-independent sensitivity values that reflect upper bounds on the possible changes in both query results.

Q2

Why does the following program fail to compute the proportion of visits lasting longer than 60 minutes with individual-level pure differential privacy (ε = 1.0)?

import pandas as pd
import numpy as np

# Load dataset
df = pd.read_csv('restaurant.csv')

# Step 1: Identify long visits
df['LongVisit'] = df['Time spent (minutes)'] > 60

# Step 2: Compute sensitivity: max number of visits by any visitor
visits_per_visitor = df.groupby('VisitorId').size()
sensitivity = visits_per_visitor.max()

# Step 3: True counts
true_count_long = df['LongVisit'].sum()
true_count_total = len(df)

# Step 4: Differential privacy with ε split
epsilon = 1.0
epsilon_long = epsilon_total = epsilon / 2  # Split budget equally

# Add Laplace noise
noisy_count_long = true_count_long + np.random.laplace(loc=0, scale=sensitivity / epsilon_long)
noisy_count_total = true_count_total + np.random.laplace(loc=0, scale=sensitivity / epsilon_total)

# Step 5: Compute private proportion (clip result)
if noisy_count_total <= 0:
    private_proportion = 0
else:
    private_proportion = noisy_count_long / noisy_count_total
    private_proportion = min(max(private_proportion, 0), 1)

# Output
print(f"Private proportion of long visits (>60 minutes): {private_proportion:.4f}")

Choices:

  • The sensitivity does not account for the maximum change in the query result when a single privacy unit's data is added or removed.
  • The sensitivity is set by querying the data without privacy protection.
  • Exceeded privacy budget.
Answer & Explanation

Answer: The sensitivity is set by querying the data without privacy protection.

The sensitivity is set by querying the maximum number of visits a single visitor contributes. Setting hyperparameters by querying the data without privacy protection can leak sensitive information!

A recommended fix is to set the sensitivities using data-independent values.


Q3

Why does the following program fail to compute the proportion of visits lasting longer than 60 minutes with individual-level pure differential privacy (ε = 1.0)?

import pandas as pd
import numpy as np

def load_and_preprocess_data(filepath):
    df = pd.read_csv(filepath)
    # Randomly sample up to 3 visits per visitor
    df_limited = df.groupby('VisitorId', group_keys=False).apply(lambda x: x.sample(min(len(x), 3)))
    return df_limited

def compute_noisy_proportion(df, epsilon=1.0):
    # Count visits over 60 minutes
    count_over_60 = (df['Time spent (minutes)'] > 60).sum()
    total_visits = len(df)

    # Sensitivity is 3 because any individual can contribute at most 3 visits
    sensitivity = 3

    # Apply Laplace noise
    noisy_count = count_over_60 + np.random.laplace(scale=sensitivity / epsilon)

    # Clip to [0, total_visits] to ensure valid proportion
    noisy_count = max(0, min(noisy_count, total_visits))
    dp_proportion = noisy_count / total_visits if total_visits > 0 else 0.0

    return dp_proportion

filepath = 'restaurant.csv'
df = load_and_preprocess_data(filepath)
dp_result = compute_noisy_proportion(df)
print(f"Differentially Private Proportion of Visits > 60 mins: {dp_result:.4f}")

Choices:

  • Only part of the result is private; the rest still leaks sensitive information.
  • The result satisfies differential privacy but may be too noisy.
  • The sensitivity does not account for the maximum change in the query result when a single privacy unit's data is added or removed.
Answer & Explanation

Answer: Only part of the result is private; the rest still leaks sensitive information.

While the result includes both the number of long visits and the total number of visits, only the number of long visits is protected by noise. This means the total number of visits could leak information.

To fix this, split the privacy budget (ε) into two parts: one for adding noise to the number of long visits, and the other for adding noise to the total number of visits.


Q4

Why does the following program fail to compute the proportion of visits lasting longer than 60 minutes with individual-level pure differential privacy (ε = 1.0)?

import pandas as pd
import numpy as np

# Load the dataset
df = pd.read_csv("restaurant.csv")

# Ensure Time spent is numeric
df["Time spent (minutes)"] = pd.to_numeric(df["Time spent (minutes)"], errors="coerce")
df.dropna(subset=["VisitorId", "Time spent (minutes)"], inplace=True)

# Identify visits longer than 60 minutes
df["Long visit"] = df["Time spent (minutes)"] > 60

# Aggregate to individual-level (one record per visitor)
visitor_long_visit = df.groupby("VisitorId")["Long visit"].any().astype(int)

# Calculate true proportion
true_count = visitor_long_visit.sum()
total_visitors = len(visitor_long_visit)
true_proportion = true_count / total_visitors

# Differential Privacy Parameters
epsilon = 1.0
sensitivity = 1.0 / total_visitors  # Sensitivity for proportion

# Apply Laplace noise
noise = np.random.laplace(loc=0.0, scale=sensitivity / epsilon)
noisy_proportion = true_proportion + noise

# Clamp to valid range
noisy_proportion = max(0, min(noisy_proportion, 1))

print(f"Differentially private proportion of visitors with visits > 60 minutes: {noisy_proportion:.4f}")

Choices:

  • Only part of the result is private; the rest still leaks sensitive information.
  • The result satisfies differential privacy but may be too noisy.
  • Exceeded privacy budget.
Answer & Explanation

Answer: The result satisfies differential privacy but may be too noisy.

The program adds noise to the proportion, which is a value between 0 and 1. Because the signal-to-sensitivity ratio is relatively small, the noise overwhelms the result.

To address this, we can instead add noise to the total count and the long visit count. This still ensures differential privacy while yielding more accurate results.


Q5

Why does the following program fail to compute the proportion of visits lasting longer than 60 minutes with individual-level pure differential privacy (ε = 1.0)?

import pandas as pd
import numpy as np
import math

# Parameters for differential privacy
epsilon = 1.0

# Load dataset
df = pd.read_csv('restaurant.csv')

# Binary labeling: 1 if time spent > 60 minutes, 0 otherwise
df['LongVisit'] = (df['Time spent (minutes)'] > 60).astype(int)

# Clip the number of visits per visitor to 4
df['VisitIndex'] = df.groupby('VisitorId').cumcount()
df_clipped = df[df['VisitIndex'] < 4].copy()

# Compute true count of long visits
true_count = df_clipped['LongVisit'].sum()
n = len(df_clipped)

# Sensitivity is 4 because each individual can contribute up to 4 visits
sensitivity = 4

# Add Laplace noise to the numerator (long visits)
noise_numerator = np.random.laplace(loc=0.0, scale=sensitivity/epsilon)
noisy_count = true_count + noise_numerator

# Add Laplace noise to the denominator (total visits)
noise_denominator = np.random.laplace(loc=0.0, scale=sensitivity/epsilon)
noisy_n = n + noise_denominator

# Ensure denominator is positive to avoid division by zero
noisy_n = max(noisy_n, 1)

# Estimate proportion
estimated_proportion = noisy_count / noisy_n

# Clip to [0,1] range
estimated_proportion = min(max(estimated_proportion, 0), 1)

print(f"Estimated proportion of visits > 60 minutes (DP, Laplace mechanism, epsilon={epsilon}): {estimated_proportion:.4f}")

Choices:

  • Only part of the result is private; the rest still leaks sensitive information.
  • The result satisfies differential privacy but may be too noisy.
  • Exceeded privacy budget.
Answer & Explanation

Answer: Exceeded privacy budget.

Both queries use an epsilon of 1.0. Since the groups of visitors used in the two queries are not disjoint, we cannot apply parallel composition. As a result, the overall privacy budget consumed is 1.0 + 1.0 = 2.0, which exceeds the privacy budget constraint.

To fix this, we can split the epsilon into two parts and use an epsilon of 0.5 for each query.