Calcolo di statistiche private con privacy su Beam

1. Introduzione

Potresti ritenere che le statistiche aggregate non trasmettano alcuna informazione sulle persone di cui sono composte i dati. Tuttavia, ci sono molti modi in cui un aggressore può apprendere informazioni sensibili su individui in un set di dati da una statistica aggregata.

Per proteggere le persone imparerai a generare statistiche private utilizzando aggregazioni private differenziate da Privacy on Beam. Privacy on Beam è un framework di privacy differenziale compatibile con Apache Beam.

Che cosa si intende per "privato"?

Quando si utilizza la parola "privato" in questo Codelab, intendiamo dire che l'output è generato in modo da non divulgare informazioni private sulle persone incluse nei dati. Possiamo farlo avvalendoci della privacy differenziale, un'efficace nozione di anonimizzazione per la privacy. L'anonimizzazione è il processo di aggregazione dei dati di più utenti per proteggere la loro privacy. Tutti i metodi di anonimizzazione utilizzano l'aggregazione, ma non tutti i metodi ottengono l'anonimizzazione. La privacy differenziale, d'altra parte, fornisce garanzie misurabili in merito alla fuga di informazioni e alla privacy.

2. Panoramica della privacy differenziale

Per comprendere meglio la privacy differenziale, esaminiamo un semplice esempio.

Questo grafico a barre mostra l'affollamento di un piccolo ristorante in una particolare serata. Molti ospiti arrivano alle 19 e il ristorante è completamente vuoto all'1:

a43dbf3e2c6de596.png

Sembra utile.

C'è un problema. Quando arriva un nuovo ospite, questo aspetto viene immediatamente rivelato dal grafico a barre. Guarda il grafico: è chiaro che c'è un nuovo ospite, che è arrivato all'incirca alle 01:00:

bda96729e700a9dd.png

Non è un'ottima cosa dal punto di vista della privacy. Una statistica realmente anonimizzata non deve rivelare i contributi individuali. Se metti i due grafici uno accanto all'altro, è ancora più evidente: il grafico a barre arancioni ha un ospite in più che arriva alle 01:00 circa:

d562ddf799288894.png

Non va bene così. Cosa facciamo?

Aggiungiamo rumore casuale per rendere i grafici a barre un po' meno precisi.

Osserva i due grafici a barre qui sotto. Sebbene non siano completamente precisi, sono comunque utili e non rivelano i singoli contributi. Bene!

838a0293cd4fcfe3.gif

La privacy differenziale consiste nell'aggiungere la giusta quantità di rumore casuale per mascherare i singoli contributi.

La nostra analisi era un po' troppo semplificata. L'implementazione corretta della privacy differenziale è più complessa e presenta una serie di sottigliezze di implementazione piuttosto inaspettate. Come nel caso della crittografia, creare una tua implementazione della privacy differenziale potrebbe non essere una grande idea. Puoi utilizzare la privacy su Beam anziché implementare la tua soluzione. Non implementare la tua privacy differenziale.

In questo codelab, mostreremo come eseguire un'analisi differenziata con privato utilizzando Privacy on Beam.

3. Download della privacy su Beam

Non è necessario scaricare Privacy su Beam per poter seguire il codelab, perché tutto il codice e i grafici pertinenti sono disponibili in questo documento. Tuttavia, se desideri scaricarlo per giocare con il codice, eseguirlo autonomamente o utilizzare Privacy su Beam in seguito, puoi farlo seguendo i passaggi riportati di seguito.

Tieni presente che questo codelab è per la versione 1.1.0 della libreria.

Innanzitutto, scarica Privacy on Beam:

https://github.com/google/differential-privacy/archive/refs/tags/v1.1.0.tar.gz

In alternativa, puoi clonare il repository GitHub:

git clone --branch v1.1.0 https://github.com/google/differential-privacy.git

Privacy on Beam si trova nella directory privacy-on-beam/ di primo livello.

Il codice di questo codelab e il set di dati si trova nella directory privacy-on-beam/codelab/.

È inoltre necessario che sul computer sia installato Bazel. Trova le istruzioni di installazione per il tuo sistema operativo sul sito web di Bazel.

4. Visite di computing all'ora

Immagina di essere il proprietario di un ristorante e di voler condividere alcune statistiche sul tuo ristorante, ad esempio la divulgazione di orari di punta. Per fortuna, conosci la privacy differenziale e l'anonimizzazione, quindi è consigliabile farlo in modo da non divulgare informazioni sui singoli visitatori.

Il codice per questo esempio è in codelab/count.go.

Iniziamo caricando un set di dati fittizio contenente le visite al ristorante in un determinato lunedì. Il codice per questa operazione non è interessante ai fini di questo codelab, ma puoi controllare il codice in codelab/main.go, codelab/utils.go e codelab/visit.go.

ID visitatore

Ora inserita

Tempo di utilizzo (min)

Denaro speso (euro)

1

09:30:00

26

24

2

11:54:00

53

17

3

13:05:00

81

33

Per prima cosa devi produrre un grafico a barre non privato delle ore delle visite al tuo ristorante utilizzando Beam nell'esempio di codice riportato di seguito. Scope è una rappresentazione della pipeline e ogni nuova operazione che eseguiamo sui dati viene aggiunta a Scope. CountVisitsPerHour riceve un Scope e una raccolta di visite, rappresentate come PCollection in Beam. Estrae l'ora di ogni visita applicando la funzione extractVisitHour alla raccolta. Quindi conteggia le occorrenze di ogni ora e le restituisce.

func CountVisitsPerHour(s beam.Scope, col beam.PCollection) beam.PCollection {
    s = s.Scope("CountVisitsPerHour")
    visitHours := beam.ParDo(s, extractVisitHourFn, col)
    visitsPerHour := stats.Count(s, visitHours)
    return visitsPerHour
}

func extractVisitHourFn(v Visit) int {
    return v.TimeEntered.Hour()
}

In questo modo viene creato un grafico a barre (eseguendo bazel run codelab -- --example="count" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/count.csv --output_chart_file=$(pwd)/count.png) nella directory corrente come count.png:

a179766795d4e64a.png

Il passaggio successivo consiste nel convertire la pipeline e il grafico a barre in uno privato. Per farlo, procedi nel seguente modo.

Innanzitutto, chiama MakePrivateFromStruct con un PCollection<V> per prendere PrivatePCollection<V>. L'input PCollection deve essere una raccolta di struct. Dobbiamo inserire un PrivacySpec e un idFieldPath come input in MakePrivateFromStruct.

spec := pbeam.NewPrivacySpec(epsilon, delta)
pCol := pbeam.MakePrivateFromStruct(s, col, spec, "VisitorID")

PrivacySpec è uno struct contenente i parametri della privacy differenziale (epsilon e delta) che vogliamo utilizzare per anonimizzare i dati. Per il momento non devi preoccuparti di questi prodotti; in seguito potrai trovare una sezione facoltativa nel caso in cui tu voglia saperne di più.

idFieldPath è il percorso del campo dell'identificatore utente all'interno dello struct (Visit nel nostro caso). In questo caso, l'identificatore utente dei visitatori è il campo VisitorID di Visit.

Quindi chiamiamo pbeam.Count() invece di stats.Count(), pbeam.Count() prende come input uno struct CountParams che contiene parametri come MaxValue che influiscono sulla precisione dell'output.

visitsPerHour := pbeam.Count(s, visitHours, pbeam.CountParams{
    // Visitors can visit the restaurant once (one hour) a day
    MaxPartitionsContributed: 1,
    // Visitors can visit the restaurant once within an hour
    MaxValue:                 1,
})

Allo stesso modo, MaxPartitionsContributed vincola il numero di ore di visita diverse che un utente può contribuire. Ci aspettiamo che queste persone visitino il ristorante al massimo una volta al giorno (o non ci importa se lo visitano più volte nel corso della giornata), quindi abbiamo impostato anche il valore su 1. Parleremo di questi parametri in maggiore dettaglio in una sezione facoltativa.

MaxValue limita il numero di volte in cui un singolo utente può contribuire ai valori che stiamo conteggiando. In questo caso particolare, i valori che conteggiamo sono le ore di visita e ci aspettiamo che un utente visiti il ristorante una sola volta (o non ci importa se lo visita più volte all'ora), quindi impostiamo questo parametro su 1.

Alla fine, il codice sarà simile a questo:

func PrivateCountVisitsPerHour(s beam.Scope, col beam.PCollection) beam.PCollection {
    s = s.Scope("PrivateCountVisitsPerHour")
    // Create a Privacy Spec and convert col into a PrivatePCollection
    spec := pbeam.NewPrivacySpec(epsilon, delta)
    pCol := pbeam.MakePrivateFromStruct(s, col, spec, "VisitorID")

    visitHours := pbeam.ParDo(s, extractVisitHourFn, pCol)
    visitsPerHour := pbeam.Count(s, visitHours, pbeam.CountParams{
        // Visitors can visit the restaurant once (one hour) a day
        MaxPartitionsContributed: 1,
        // Visitors can visit the restaurant once within an hour
        MaxValue:                 1,
    })
    return visitsPerHour
}

Viene visualizzato un grafico a barre simile (count_dp.png) per la statistica differenziata (il comando precedente esegue sia la pipeline non privata che quella privata):

d6a0ace1acd3c760.png

Complimenti! Hai calcolato la tua prima statistica differenziata.

Il grafico a barre visualizzato quando esegui il codice potrebbe essere diverso da questo. Non importa. A causa del rumore nella privacy differenziale, ogni volta che esegui il codice viene visualizzato un grafico a barre diverso, ma puoi notare che sono più o meno simili al grafico a barre non privato originale che avevamo.

Tieni presente che è molto importante che le garanzie di privacy non ripetere la pipeline più volte (ad esempio, al fine di ottenere un grafico a barre più bello). Il motivo per cui non devi eseguire di nuovo le pipeline è spiegato nella sezione "Calcolo di più statistiche" .

5. Utilizzo di partizioni pubbliche

Nella sezione precedente, potresti aver notato che abbiamo perso tutte le visite (dati) per alcune partizioni, ad esempio ore.

d7fbc5d86d91e54a.png

Ciò è dovuto alla selezione/soglia della partizione, un passaggio importante per garantire garanzie di privacy differenziale quando l'esistenza delle partizioni di output dipende dai dati utente stessi. In questo caso, la sola esistenza di una partizione nell'output può rendere nota l'esistenza di un singolo utente nei dati (vedi questo post del blog per una spiegazione del motivo per cui ciò viola la privacy). Per evitare che questo accada, Privacy on Beam conserva solo le partizioni che contengono un numero sufficiente di utenti.

Quando l'elenco delle partizioni di output non dipende da dati utente privati, ovvero sono informazioni pubbliche, questo passaggio di selezione della partizione non è necessario. Questo è effettivamente il caso del nostro esempio del ristorante: conosciamo l'orario di lavoro del ristorante (dalle 9.00 alle 21.00).

Il codice per questo esempio è in codelab/public_partitions.go.

Creeremo semplicemente una PCollection di ore comprese tra 9 e 21 (escluse) e la inseriamo nel campo PublicPartitions di CountParams:

func PrivateCountVisitsPerHourWithPublicPartitions(s beam.Scope,
    col beam.PCollection) beam.PCollection {
    s = s.Scope("PrivateCountVisitsPerHourWithPublicPartitions")
    // Create a Privacy Spec and convert col into a PrivatePCollection
    spec := pbeam.NewPrivacySpec(epsilon, /* delta */ 0)
    pCol := pbeam.MakePrivateFromStruct(s, col, spec, "VisitorID")

    // Create a PCollection of output partitions, i.e. restaurant's work hours
    // (from 9 am till 9pm (exclusive)).
    hours := beam.CreateList(s, [12]int{9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20})

    visitHours := pbeam.ParDo(s, extractVisitHourFn, pCol)
    visitsPerHour := pbeam.Count(s, visitHours, pbeam.CountParams{
        // Visitors can visit the restaurant once (one hour) a day
        MaxPartitionsContributed: 1,
        // Visitors can visit the restaurant once within an hour
        MaxValue:                 1,
        // Visitors only visit during work hours
        PublicPartitions:         hours,
    })
    return visitsPerHour
}

Tieni presente che è possibile impostare il delta su 0 se utilizzi partizioni pubbliche e Laplace rumore (predefinito), come nel caso precedente.

Quando eseguiamo la pipeline con partizioni pubbliche (con bazel run codelab -- --example="public_partitions" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/public_partitions.csv --output_chart_file=$(pwd)/public_partitions.png), si ottiene (public_partitions_dp.png):

7c950fbe99fec60a.png

Come puoi vedere, ora conserviamo le partizioni 9, 10 e 16 eliminate in precedenza senza partizioni pubbliche.

Non solo l'utilizzo di partizioni pubbliche consente di mantenere più partizioni, ma aggiunge anche circa metà del rumore a ciascuna partizione rispetto al non utilizzare partizioni pubbliche a causa di non spendere alcun budget per la privacy, ovvero epsilon e delta, alla selezione della partizione. Questo è il motivo per cui la differenza tra i conteggi non elaborati e privati è leggermente inferiore rispetto all'esecuzione precedente.

Quando si utilizzano le partizioni pubbliche, è importante tenere presente due aspetti importanti:

  1. Fai attenzione quando ricava l'elenco delle partizioni dai dati non elaborati: se non lo fai in modo differenziato, ad esempio semplicemente leggendo l'elenco di tutte le partizioni nei dati utente, la pipeline non fornisce più garanzie di privacy differenziale. Per informazioni su come eseguire questa operazione in modo differenziato, consulta la sezione avanzata riportata di seguito.
  2. Se non sono disponibili dati (ad esempio le visite) per alcune partizioni pubbliche, a queste partizioni verrà applicato del rumore per preservare la privacy differenziale. Ad esempio, se abbiamo utilizzato le ore comprese tra 0 e 24 (anziché le 9 e le 21), tutte le ore saranno disturbate e potrebbero essere mostrate alcune visite quando non ce ne sono.

(Avanzato) Ricavare partizioni dai dati

Se esegui più aggregazioni con lo stesso elenco di partizioni di output non pubbliche nella stessa pipeline, puoi ricavare l'elenco di partizioni una volta utilizzando SelectPartitions() e fornendo le partizioni a ogni aggregazione come input PublicPartition. Questo non solo è sicuro dal punto di vista della privacy, ma ti consente anche di aggiungere meno rumore grazie all'utilizzo del budget di privacy nella selezione della partizione solo una volta per l'intera pipeline.

6. Calcolo della durata media del soggiorno

Ora che sappiamo come contare le cose in modo differenziato, esaminiamo i mezzi di calcolo. In particolare, ora calcoleremo la durata media del soggiorno dei visitatori.

Il codice per questo esempio è in codelab/mean.go.

Di solito, per calcolare una media non privata della durata del soggiorno, utilizziamo stats.MeanPerKey() con una fase di pre-elaborazione che converte il PCollection delle visite in arrivo in un PCollection<K,V>, dove K è l'ora della visita e V è il tempo che il visitatore ha trascorso nel ristorante.

func MeanTimeSpent(s beam.Scope, col beam.PCollection) beam.PCollection {
    s = s.Scope("MeanTimeSpent")
    hourToTimeSpent := beam.ParDo(s, extractVisitHourAndTimeSpentFn, col)
    meanTimeSpent := stats.MeanPerKey(s, hourToTimeSpent)
    return meanTimeSpent
}

func extractVisitHourAndTimeSpentFn(v Visit) (int, int) {
    return v.TimeEntered.Hour(), v.MinutesSpent
}

In questo modo viene creato un grafico a barre (eseguendo bazel run codelab -- --example="mean" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/mean.csv --output_chart_file=$(pwd)/mean.png) nella directory corrente come mean.png:

bc2df28bf94b3721.png

Per rendere questa informazione privata in modo differenziato, convertiamo di nuovo PCollection in PrivatePCollection e sostituiamo stats.MeanPerKey() con pbeam.MeanPerKey(). Come Count, abbiamo MeanParams che contengono alcuni parametri, come MinValue e MaxValue, che influiscono sulla precisione. MinValue e MaxValue rappresentano i limiti che abbiamo per il contributo di ciascun utente a ogni chiave.

meanTimeSpent := pbeam.MeanPerKey(s, hourToTimeSpent, pbeam.MeanParams{
    // Visitors can visit the restaurant once (one hour) a day
    MaxPartitionsContributed:     1,
    // Visitors can visit the restaurant once within an hour
    MaxContributionsPerPartition: 1,
    // Minimum time spent per user (in mins)
    MinValue:                     0,
    // Maximum time spent per user (in mins)
    MaxValue:                     60,
})

In questo caso, ogni chiave rappresenta un'ora e i valori sono il tempo trascorso dai visitatori. Abbiamo impostato MinValue su 0 perché non ci aspettiamo che i visitatori passino meno di 0 minuti al ristorante. Impostiamo il valore MaxValue su 60, il che significa che se un visitatore trascorre più di 60 minuti, agiamo come se l'utente abbia trascorso 60 minuti.

Alla fine, il codice sarà simile a questo:

func PrivateMeanTimeSpent(s beam.Scope, col beam.PCollection) beam.PCollection {
    s = s.Scope("PrivateMeanTimeSpent")
    // Create a Privacy Spec and convert col into a PrivatePCollection
    spec := pbeam.NewPrivacySpec(epsilon, /* delta */ 0)
    pCol := pbeam.MakePrivateFromStruct(s, col, spec, "VisitorID")

    // Create a PCollection of output partitions, i.e. restaurant's work hours
    // (from 9 am till 9pm (exclusive)).
    hours := beam.CreateList(s, [12]int{9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20})

    hourToTimeSpent := pbeam.ParDo(s, extractVisitHourAndTimeSpentFn, pCol)
    meanTimeSpent := pbeam.MeanPerKey(s, hourToTimeSpent, pbeam.MeanParams{
        // Visitors can visit the restaurant once (one hour) a day
        MaxPartitionsContributed:     1,
        // Visitors can visit the restaurant once within an hour
        MaxContributionsPerPartition: 1,
        // Minimum time spent per user (in mins)
        MinValue:                     0,
        // Maximum time spent per user (in mins)
        MaxValue:                     60,
        // Visitors only visit during work hours
        PublicPartitions:             hours,
    })
    return meanTimeSpent
}

Viene visualizzato un grafico a barre simile (mean_dp.png) per la statistica differenziata (il comando precedente esegue sia la pipeline non privata che quella privata):

e8ac6a9bf9792287.png

Come già detto in precedenza, poiché si tratta di un'operazione con privacy differenziata, otterremo risultati diversi ogni volta che la eseguiamo. Tuttavia, puoi vedere che le durate differenziate del soggiorno privato non sono lontane dal risultato effettivo.

7. Entrate di calcolo per ora

Un'altra statistica interessante che potremmo esaminare sono le entrate per ora nel corso della giornata.

Il codice per questo esempio è in codelab/sum.go.

Anche in questo caso, inizieremo con la versione non privata. Effettuando alcune pre-elaborazione sul nostro set di dati fittizio, possiamo creare un PCollection<K,V> in cui K è l'ora di visita e V è il denaro che il visitatore ha speso nel ristorante. Per calcolare le entrate non private per ora, possiamo semplicemente sommare tutti i soldi spesi dai visitatori chiamando il numero stats.SumPerKey():

func RevenuePerHour(s beam.Scope, col beam.PCollection) beam.PCollection {
    s = s.Scope("RevenuePerHour")
    hourToMoneySpent := beam.ParDo(s, extractVisitHourAndMoneySpentFn, col)
    revenues := stats.SumPerKey(s, hourToMoneySpent)
    return revenues
}

func extractVisitHourAndMoneySpentFn(v Visit) (int, int) {
    return v.TimeEntered.Hour(), v.MoneySpent
}

In questo modo viene creato un grafico a barre (eseguendo bazel run codelab -- --example="sum" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/sum.csv --output_chart_file=$(pwd)/sum.png) nella directory corrente come sum.png:

548619173fad0c9a.png

Per rendere questa informazione privata in modo differenziato, convertiamo di nuovo PCollection in PrivatePCollection e sostituiamo stats.SumPerKey() con pbeam.SumPerKey(). Analogamente a Count e MeanPerKey, abbiamo SumParams che contengono alcuni parametri, come MinValue e MaxValue, che influiscono sulla precisione.

revenues := pbeam.SumPerKey(s, hourToMoneySpent, pbeam.SumParams{
    // Visitors can visit the restaurant once (one hour) a day
    MaxPartitionsContributed: 1,
    // Minimum money spent per user (in euros)
    MinValue:                 0,
    // Maximum money spent per user (in euros)
    MaxValue:                 40,
})

In questo caso, MinValue e MaxValue rappresentano i limiti di spesa di ciascun visitatore. Abbiamo impostato MinValue su 0 perché non ci aspettiamo che i visitatori spendano meno di 0 euro al ristorante. Impostiamo il valore MaxValue su 40, il che significa che se un visitatore spende più di 40 euro, agiamo come se l'utente avesse speso 40 euro.

Alla fine, il codice sarà simile a questo:

func PrivateRevenuePerHour(s beam.Scope, col beam.PCollection) beam.PCollection {
    s = s.Scope("PrivateRevenuePerHour")
    // Create a Privacy Spec and convert col into a PrivatePCollection
    spec := pbeam.NewPrivacySpec(epsilon, /* delta */ 0)
    pCol := pbeam.MakePrivateFromStruct(s, col, spec, "VisitorID")

    // Create a PCollection of output partitions, i.e. restaurant's work hours
    // (from 9 am till 9pm (exclusive)).
    hours := beam.CreateList(s, [12]int{9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20})

    hourToMoneySpent := pbeam.ParDo(s, extractVisitHourAndMoneySpentFn, pCol)
    revenues := pbeam.SumPerKey(s, hourToMoneySpent, pbeam.SumParams{
        // Visitors can visit the restaurant once (one hour) a day
        MaxPartitionsContributed: 1,
        // Minimum money spent per user (in euros)
        MinValue:                 0,
        // Maximum money spent per user (in euros)
        MaxValue:                 40,
        // Visitors only visit during work hours
        PublicPartitions:         hours,
    })
    return revenues
}

Viene visualizzato un grafico a barre simile (sum_dp.png) per la statistica differenziata (il comando precedente esegue sia la pipeline non privata che quella privata):

46c375e874f3e7c4.png

Come già detto in precedenza, in modo analogo a conteggio e media, poiché si tratta di un'operazione differentemente privata, otterremo risultati diversi ogni volta che la eseguiamo. Come puoi notare, il risultato differenziato privato è molto simile alle entrate effettive per ora.

8. Calcolo di più statistiche

La maggior parte delle volte potrebbe interessarti calcolare più statistiche sugli stessi dati sottostanti, in modo simile a quanto hai fatto con conteggio, media e somma. Di solito è più semplice e più lineare eseguire questa operazione in una singola pipeline Beam e in un singolo file binario. Puoi farlo anche con Privacy su Beam. Puoi scrivere un'unica pipeline per eseguire trasformazioni e calcoli e utilizzare un singolo PrivacySpec per l'intera pipeline.

Questa operazione non solo è più comoda con un singolo PrivacySpec, ma è anche meglio in termini di privacy. Se ricordi i parametri epsilon e delta che forniamo a PrivacySpec, si tratta di un cosiddetto budget per la privacy, che misura il livello di privacy degli utenti nei dati sottostanti divulgati.

Una cosa importante da ricordare a proposito del budget per la privacy è che è additivo: se esegui una pipeline con una particolare epsilon ↂ e delta ♦ una sola volta, stai spendendo un budget (Π, ). Se lo esegui una seconda volta, avrai speso un budget totale di (2Π, 2elaborazione). Allo stesso modo, se calcoli più statistiche con un PrivacySpec (e consecutivamente un budget di privacy) di (ↂ, ), avrai speso un budget totale di (2Π, 2). Ciò significa che stai riducendo le garanzie sulla privacy.

Per aggirare questo problema, quando vuoi calcolare più statistiche sugli stessi dati sottostanti, devi utilizzare un'unica variabile PrivacySpec con il budget totale che intendi utilizzare. Devi quindi specificare l'epsilon e il delta da utilizzare per ogni aggregazione. Alla fine, otterrai la stessa garanzia di privacy complessiva; ma maggiore è l'epsilon e il delta di una particolare aggregazione, maggiore sarà l'accuratezza.

Per vedere un esempio pratico, possiamo calcolare le tre statistiche (conteggio, media e somma) che abbiamo calcolato separatamente in precedenza in un'unica pipeline.

Il codice per questo esempio è in codelab/multiple.go. Nota come stiamo dividendo equamente il budget totale (Φ, ) tra le tre aggregazioni:

func ComputeCountMeanSum(s beam.Scope, col beam.PCollection) (visitsPerHour, meanTimeSpent, revenues beam.PCollection) {
    s = s.Scope("ComputeCountMeanSum")
    // Create a Privacy Spec and convert col into a PrivatePCollection
    // Budget is shared by count, mean and sum.
    spec := pbeam.NewPrivacySpec(epsilon, /* delta */ 0)
    pCol := pbeam.MakePrivateFromStruct(s, col, spec, "VisitorID")

    // Create a PCollection of output partitions, i.e. restaurant's work hours
    // (from 9 am till 9pm (exclusive)).
    hours := beam.CreateList(s, [12]int{9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20})

    visitHours := pbeam.ParDo(s, extractVisitHourFn, pCol)
    visitsPerHour = pbeam.Count(s, visitHours, pbeam.CountParams{
        Epsilon:                  epsilon / 3,
        Delta:                    0,
        // Visitors can visit the restaurant once (one hour) a day
        MaxPartitionsContributed: 1,
        // Visitors can visit the restaurant once within an hour
        MaxValue:                 1,
        // Visitors only visit during work hours
        PublicPartitions:         hours,
    })

    hourToTimeSpent := pbeam.ParDo(s, extractVisitHourAndTimeSpentFn, pCol)
    meanTimeSpent = pbeam.MeanPerKey(s, hourToTimeSpent, pbeam.MeanParams{
        Epsilon:                      epsilon / 3,
        Delta:                        0,
        // Visitors can visit the restaurant once (one hour) a day
        MaxPartitionsContributed:     1,
        // Visitors can visit the restaurant once within an hour
        MaxContributionsPerPartition: 1,
        // Minimum time spent per user (in mins)
        MinValue:                     0,
        // Maximum time spent per user (in mins)
        MaxValue:                     60,
        // Visitors only visit during work hours
        PublicPartitions:             hours,
    })

    hourToMoneySpent := pbeam.ParDo(s, extractVisitHourAndMoneySpentFn, pCol)
    revenues = pbeam.SumPerKey(s, hourToMoneySpent, pbeam.SumParams{
        Epsilon:                  epsilon / 3,
        Delta:                    0,
        // Visitors can visit the restaurant once (one hour) a day
        MaxPartitionsContributed: 1,
        // Minimum money spent per user (in euros)
        MinValue:                 0,
        // Maximum money spent per user (in euros)
        MaxValue:                 40,
        // Visitors only visit during work hours
        PublicPartitions:         hours,
    })

    return visitsPerHour, meanTimeSpent, revenues
}

9. (Facoltativo) Regolare i parametri della privacy differenziale

Hai visto diversi parametri menzionati in questo codelab: epsilon, delta, maxPartitionsContribuiscid e così via. Possiamo suddividerli approssimativamente in due categorie: Parametri di privacy e Parametri di utilità.

Parametri privacy

Epsilon e delta sono i parametri che quantificano la privacy che forniamo usando la privacy differenziale. Più precisamente, epsilon e delta sono una misura di quante informazioni ottiene un potenziale aggressore sui dati sottostanti esaminando l'output anonimo. Più elevati sono l’epsilon e il delta, maggiori sono le informazioni ottenute dall’aggressore sui dati sottostanti, con un conseguente rischio per la privacy.

D'altra parte, più bassi sono l'epsilon e il delta, maggiore è il rumore da aggiungere all'output per renderlo anonimo e un numero più alto di utenti unici necessari in ogni partizione per mantenere quella partizione nell'output anonimo. Esiste quindi un compromesso tra utilità e privacy.

In Privacy on Beam, devi preoccuparti delle garanzie di privacy che vuoi nei tuoi output anonimizzati quando specifichi il budget di privacy totale in PrivacySpec. Tieni presente che se vuoi che le tue garanzie di privacy rimangano valide, devi seguire i consigli di questo codelab su come non usare in eccesso il budget impostando un PrivacySpec separato per ogni aggregazione o eseguendo la pipeline più volte.

Per ulteriori informazioni sulla privacy differenziale e sul significato dei parametri della privacy, puoi consultare la letteratura.

Parametri utilità

Si tratta di parametri che non influiscono sulle garanzie di privacy (purché vengano rispettati correttamente i consigli su come utilizzare Privacy su Beam), ma influiscono sull'accuratezza e, di conseguenza, sull'utilità dell'output. Sono forniti negli struct Params di ogni aggregazione, ad esempio CountParams, SumParams e così via. Questi parametri vengono utilizzati per scalare il rumore aggiunto.

Un parametro di utilità fornito in Params e applicabile a tutte le aggregazioni è MaxPartitionsContributed. Una partizione corrisponde a una chiave della PCollection emessa da un'operazione di aggregazione Privacy On Beam, ovvero Count, SumPerKey e così via. Pertanto, MaxPartitionsContributed limita il numero di coppie chiave-valore distinte a cui un utente può contribuire nell'output. Se un utente contribuisce a più di MaxPartitionsContributed chiavi nei dati sottostanti, alcuni dei suoi contributi verranno eliminati in modo che possa contribuire esattamente a MaxPartitionsContributed chiavi.

Analogamente a MaxPartitionsContributed, la maggior parte delle aggregazioni ha un parametro MaxContributionsPerPartition. Sono forniti negli struct Params e ogni aggregazione potrebbe avere valori separati. Diversamente da MaxPartitionsContributed, il criterio MaxContributionsPerPartition limita il contributo di un utente per ogni chiave. In altre parole, un utente può contribuire solo con MaxContributionsPerPartition valori per ogni chiave.

Il rumore aggiunto all'output viene scalato da MaxPartitionsContributed e MaxContributionsPerPartition, quindi c'è un compromesso in questo caso: un valore maggiore di MaxPartitionsContributed e MaxContributionsPerPartition comporta entrambi la conservazione di più dati, ma il risultato sarà più rumoroso.

Alcune aggregazioni richiedono MinValue e MaxValue. che specificano i limiti relativi ai contributi di ciascun utente. Se un utente contribuisce con un valore inferiore a MinValue, quel valore verrà bloccato fino a MinValue. Allo stesso modo, se un utente contribuisce con un valore maggiore di MaxValue, quel valore verrà limitato a MaxValue. Ciò significa che per conservare un numero maggiore di valori originali, devi specificare limiti maggiori. Come per MaxPartitionsContributed e MaxContributionsPerPartition, il rumore viene scalato in base alla dimensione dei limiti, quindi limiti più ampi significano conservare più dati, ma il risultato sarà più rumoroso.

L'ultimo parametro di cui parleremo è NoiseKind. Supportiamo due diversi meccanismi per il rumore in Privacy On Beam: GaussianNoise e LaplaceNoise. Entrambi hanno i loro vantaggi e svantaggi, ma la distribuzione di Laplace offre una migliore utilità con bassi limiti di contributo, ecco perché Privacy On Beam lo utilizza per impostazione predefinita. Tuttavia, se vuoi utilizzare un rumore di distribuzione gaussiano, puoi fornire a Params una variabile pbeam.GaussianNoise{}.

10. Riepilogo

Ottimo lavoro. Hai completato il codelab sulla privacy su Beam. Hai imparato molto sulla privacy differenziale e sulla privacy su Beam:

  • Trasforma il tuo PCollection in un PrivatePCollection chiamando il numero MakePrivateFromStruct.
  • Utilizzo di Count per calcolare conteggi di privacy differenziati.
  • Utilizzare MeanPerKey per calcolare una privacy differenziata.
  • Utilizzo di SumPerKey per calcolare somme private differenziate.
  • Calcolo di più statistiche con un singolo PrivacySpec in un'unica pipeline.
  • (Facoltativo) Personalizzazione di PrivacySpec e dei parametri di aggregazione (CountParams, MeanParams, SumParams).

Tuttavia, ci sono molte altre aggregazioni (ad es.quantili, conteggio di valori distinti) che puoi fare con Privacy su Beam. Puoi scoprire di più al riguardo nel repository GitHub o nel godoc.

Se hai tempo, inviaci un feedback sul codelab rispondendo a un sondaggio.