18  Lab 18: Ethical AI: The Rights of the Mind

Open In Colab

18.1 Learning Objectives

  1. Understand core concepts from Chapter 18
  2. Implement key privacy-preserving algorithms and techniques
  3. Apply methods to practical neural data protection problems
  4. Analyze results and draw insights about privacy-utility tradeoffs
  5. Connect to broader NeuroAI themes

18.2 Prerequisites

  • Reading: Chapter 18: Ethical AI
  • Libraries: NumPy, Matplotlib, hashlib, uuid
  • Concepts: Fairness and responsibility, differential privacy, de-identification

18.3 Setup

import numpy as np
import matplotlib.pyplot as plt
import hashlib
import uuid

np.random.seed(42)
plt.rcParams['figure.figsize'] = (12, 8)

18.4 Part 1: Neural Data Protection Pipeline

This section implements a comprehensive privacy-preserving pipeline for neural data, demonstrating the three key steps: consent verification, de-identification, and differential privacy.

18.4.1 Exercise 1: Complete Neural Data Protection

Run this cell to implement the full neural data protection pipeline from Chapter 18.

def demonstrate_neural_data_protection(neural_data, metadata, consent_given=True, purpose="medical_diagnosis"):
    """
    A simplified demonstration of key privacy-preserving steps for neural data.

    This function implements the three core privacy protection steps:
    1. Consent and purpose verification
    2. De-identification using Safe Harbor method
    3. Differential privacy with Laplace mechanism
    """
    # 1. Verify Consent and Purpose
    if not consent_given:
        raise PermissionError("Consent not given for data processing.")

    authorized_purposes = ["medical_diagnosis", "approved_research"]
    if purpose not in authorized_purposes:
        raise ValueError(f"Purpose '{purpose}' is not authorized.")

    print(f"Step 1: Consent and purpose verified for '{purpose}'.")

    # 2. De-identify the data using the "Safe Harbor" method
    # Remove direct personal identifiers from metadata
    deidentified_metadata = metadata.copy()
    phi_fields = ["name", "mrn", "dob", "address", "ssn"]
    for field in phi_fields:
        if field in deidentified_metadata:
            del deidentified_metadata[field]

    # Create a new, irreversible research ID
    if "patient_id" in deidentified_metadata:
        salt = uuid.uuid4().hex
        hash_obj = hashlib.sha256((str(deidentified_metadata["patient_id"]) + salt).encode())
        deidentified_metadata["research_id"] = hash_obj.hexdigest()
        del deidentified_metadata["patient_id"]

    print("Step 2: Metadata de-identified. Personal identifiers removed.")

    # 3. Apply Differential Privacy to the neural data itself
    # This adds mathematical noise to ensure an individual's data cannot be re-identified
    epsilon = 0.1  # Privacy budget (lower is more private)
    sensitivity = np.max(neural_data) - np.min(neural_data)
    scale = sensitivity / epsilon
    noise = np.random.laplace(0, scale, neural_data.shape)
    private_data = neural_data + noise

    print(f"Step 3: Differential privacy applied with epsilon={epsilon}.")

    return private_data, deidentified_metadata


# Example Usage
# Raw data
raw_eeg_data = np.random.randn(10, 128) # 10 seconds of 128-channel EEG
patient_metadata = {
    "name": "Jane Doe", "mrn": "12345", "patient_id": 9876, "age": 45,
    "diagnosis": "Epilepsy"
}

# Apply protection
private_eeg, private_meta = demonstrate_neural_data_protection(raw_eeg_data, patient_metadata)
print("Final Private Metadata:", private_meta)

Discussion Questions: 1. Why is consent verification the first step in the pipeline? 2. What is the purpose of the salt in the SHA-256 hash? 3. How does the epsilon parameter affect the privacy-utility tradeoff?

18.5 Part 2: Differential Privacy Deep Dive

This section explores differential privacy in more detail, focusing on the mathematical foundations of adding Laplace noise to neural data.

18.5.1 Exercise 2: Differential Privacy Implementation

Run this cell to implement the core differential privacy mechanism.

def compute_global_sensitivity(neural_data):
    """
    Compute the global sensitivity of neural data.

    Sensitivity is the maximum change that a single individual's data
    can have on the output of a query.
    """
    return np.max(neural_data) - np.min(neural_data)


def add_differential_privacy(neural_data, epsilon=0.1):
    """
    Add Laplace noise to neural data to achieve differential privacy.

    Args:
        neural_data: Raw EEG/fMRI/spike train data
        epsilon: Privacy budget (smaller = more private)

    Returns:
        Privatized neural data
    """
    sensitivity = compute_global_sensitivity(neural_data)
    scale = sensitivity / epsilon
    noise = np.random.laplace(0, scale, neural_data.shape)
    return neural_data + noise


# Test the function with different epsilon values
print("Testing differential privacy with different epsilon values:\n")

epsilons = [0.01, 0.1, 1.0, 10.0]
test_data = np.random.randn(5, 10)

for eps in epsilons:
    private_data = add_differential_privacy(test_data, epsilon=eps)
    noise_added = np.mean(np.abs(private_data - test_data))
    print(f"Epsilon = {eps:5.2f}: Mean absolute noise = {noise_added:.4f}")

18.5.2 Exercise 3: Epsilon Tradeoff Analysis

Visualize the privacy-utility tradeoff by varying epsilon.

def analyze_epsilon_tradeoff(neural_data, epsilon_values=None):
    """
    Analyze the tradeoff between privacy and data utility.

    Args:
        neural_data: Raw neural data
        epsilon_values: List of epsilon values to test

    Returns:
        Dictionary with analysis results
    """
    if epsilon_values is None:
        epsilon_values = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0]

    results = {'epsilon': [], 'noise_magnitude': [], 'snr': []}

    signal_power = np.mean(neural_data ** 2)

    for eps in epsilon_values:
        private_data = add_differential_privacy(neural_data, epsilon=eps)
        noise = private_data - neural_data

        noise_magnitude = np.mean(np.abs(noise))
        noise_power = np.mean(noise ** 2)
        snr = signal_power / noise_power if noise_power > 0 else float('inf')

        results['epsilon'].append(eps)
        results['noise_magnitude'].append(noise_magnitude)
        results['snr'].append(snr)

    return results


# Generate test data and analyze
test_eeg = np.random.randn(100, 64)  # 100 samples, 64 channels
results = analyze_epsilon_tradeoff(test_eeg)

# Plot the results
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Plot 1: Noise magnitude vs epsilon
axes[0].semilogx(results['epsilon'], results['noise_magnitude'], 'b-o', linewidth=2)
axes[0].set_xlabel('Epsilon (Privacy Budget)', fontsize=12)
axes[0].set_ylabel('Mean Absolute Noise', fontsize=12)
axes[0].set_title('Noise Magnitude vs. Privacy Budget', fontsize=14)
axes[0].grid(True, alpha=0.3)

# Plot 2: Signal-to-Noise Ratio vs epsilon
axes[1].semilogx(results['epsilon'], results['snr'], 'r-o', linewidth=2)
axes[1].set_xlabel('Epsilon (Privacy Budget)', fontsize=12)
axes[1].set_ylabel('Signal-to-Noise Ratio (dB)', fontsize=12)
axes[1].set_title('Data Utility vs. Privacy Budget', fontsize=14)
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("\nKey Insight: Lower epsilon = stronger privacy but more noise (lower utility)")

18.6 Part 3: Hands-On Implementation

Practical exercises exploring fairness and responsibility in NeuroAI.

18.6.1 Exercise 4: Bias Audit Simulation

Create a synthetic dataset and simulate bias in neural diagnostic outcomes.

def simulate_bias_audit(n_samples=1000, bias_factor=0.2):
    """
    Simulate a bias audit for a neural diagnostic model.

    Args:
        n_samples: Total number of samples
        bias_factor: How much bias to introduce (0 = no bias)

    Returns:
        Dictionary with bias audit results
    """
    # Create two demographic groups (e.g., Group A and Group B)
    n_group_a = int(n_samples * 0.7)  # 70% in majority group
    n_group_b = n_samples - n_group_a

    # Generate true labels (balanced)
    y_true_a = np.random.randint(0, 2, n_group_a)
    y_true_b = np.random.randint(0, 2, n_group_b)

    # Generate predictions with bias against Group B
    accuracy_a = 0.90
    accuracy_b = 0.90 - bias_factor  # Lower accuracy for minority group

    y_pred_a = np.where(np.random.random(n_group_a) < accuracy_a,
                         y_true_a, 1 - y_true_a)
    y_pred_b = np.where(np.random.random(n_group_b) < accuracy_b,
                         y_true_b, 1 - y_true_b)

    # Calculate metrics
    def calc_metrics(y_true, y_pred):
        tp = np.sum((y_true == 1) & (y_pred == 1))
        tn = np.sum((y_true == 0) & (y_pred == 0))
        fp = np.sum((y_true == 0) & (y_pred == 1))
        fn = np.sum((y_true == 1) & (y_pred == 0))

        accuracy = (tp + tn) / (tp + tn + fp + fn)
        fpr = fp / (fp + tn) if (fp + tn) > 0 else 0
        fnr = fn / (fn + tp) if (fn + tp) > 0 else 0

        return {'accuracy': accuracy, 'fpr': fpr, 'fnr': fnr}

    metrics_a = calc_metrics(y_true_a, y_pred_a)
    metrics_b = calc_metrics(y_true_b, y_pred_b)

    return {
        'Group A (Majority)': metrics_a,
        'Group B (Minority)': metrics_b,
        'Accuracy Gap': metrics_a['accuracy'] - metrics_b['accuracy']
    }


# Run bias audit simulation
print("=== Bias Audit Simulation ===\n")
results = simulate_bias_audit(bias_factor=0.15)
for group, metrics in results.items():
    if isinstance(metrics, dict):
        print(f"{group}:")
        print(f"  Accuracy: {metrics['accuracy']:.3f}")
        print(f"  False Positive Rate: {metrics['fpr']:.3f}")
        print(f"  False Negative Rate: {metrics['fnr']:.3f}\n")
    else:
        print(f"{group}: {metrics:.3f}")

18.7 Exercises

18.7.1 Exercise 1

Implement the basic differential privacy algorithm and test with different epsilon values.

18.7.2 Exercise 2

Explore parameter variations - how does the noise scale change with different data sensitivities?

18.7.3 Exercise 3

Apply to real or simulated neural data and analyze the privacy-utility tradeoff.

18.7.4 Exercise 4

Compare different approaches: differential privacy vs. k-anonymity vs. federated learning.

18.7.5 Exercise 5

Discuss NeuroAI connections - how do these privacy techniques apply to brain-computer interfaces?


18.8 Challenge Problems

18.8.1 Challenge 1: Advanced Privacy Techniques

Implement a federated learning simulation where multiple “hospitals” collaboratively train a model without sharing raw data.

18.8.2 Challenge 2: Reproduce Research Findings

Implement the complete bias detection and mitigation pipeline from the chapter’s Figure 18.2.

18.8.3 Challenge 3: Novel Application

Design a privacy-preserving neural data sharing system that combines differential privacy with secure multi-party computation.


Open In Colab

18.9 Discussion Questions

18.9.1 Question 1

How does neural data privacy differ from traditional data privacy? What makes brain data unique?

18.9.2 Question 2

What are the AI implications of privacy-preserving techniques? How do they affect model performance?

18.9.3 Question 3

Future research directions - how might advances in quantum computing affect privacy guarantees?


18.10 Summary

Explored fairness and responsibility through theory and practice, implementing: - Complete neural data protection pipeline - Differential privacy with Laplace mechanism - Bias audit simulation - Consent management system

Key Takeaways: - Core privacy concepts mastered (consent, de-identification, differential privacy) - Practical implementation skills developed - NeuroAI connections understood (ethics, privacy, bias mitigation) - Privacy-utility tradeoffs analyzed and visualized