Computing Private Statistics with Privacy on Beam

1. Einführung

Sie könnten denken, dass aggregierte Statistiken keine Informationen über die Personen herausgeben, aus deren Daten die Statistiken bestehen. Es gibt jedoch viele Möglichkeiten, wie Angreifer vertrauliche Informationen zu Personen in einem Dataset aus aggregierten Statistiken gewinnen können.

Zum Schutz der zum Datenschutz, erfahren Sie, wie Sie private Statistiken mit differenziellen privaten Aggregationen von Privacy in Beam erstellen. Privacy on Beam ist ein Differential Privacy-Framework, das mit Apache Beam kompatibel ist.

Was bedeutet „privat“?

Bei Verwendung des Wortes „privat“ in diesem Codelab meinen wir, dass die Ausgabe so erstellt wird, dass keine privaten Informationen zu den Personen in den Daten offengelegt werden. Das können wir mit Differential Privacy erreichen, einem strikten Datenschutzkonzept der Anonymisierung. Bei der Anonymisierung werden Daten von mehreren Nutzern aggregiert, um die Privatsphäre der Nutzer zu schützen. Bei allen Anonymisierungsmethoden wird die Aggregation verwendet, aber nicht bei allen wird die Anonymisierung erreicht. Differential Privacy hingegen bietet messbare Garantien in Bezug auf den Verlust von Informationen und den Datenschutz.

2. Differential Privacy – Übersicht

Zum besseren Verständnis des Differential Privacy sehen wir uns ein einfaches Beispiel an.

Dieses Balkendiagramm zeigt die Auslastung eines kleinen Restaurants an einem bestimmten Abend. Viele Gäste kommen um 19:00 Uhr und das Restaurant ist um 1:00 Uhr komplett leer:

a43dbf3e2c6de596.png

Das sieht hilfreich aus!

Es gibt einen Haken. Wenn ein neuer Gast eintrifft, lässt sich dies sofort im Balkendiagramm ablesen. Sehen Sie sich das Diagramm an: Es ist klar, dass es einen neuen Gast gibt, der ungefähr um 1:00 Uhr morgens eingetroffen ist:

bda96729e700a9dd.png

Aus datenschutzrechtlicher Sicht ist das nicht gerade toll. Eine vollständig anonymisierte Statistik sollte keine individuellen Beiträge preisgeben. Wenn Sie diese beiden Diagramme nebeneinander platzieren, wird dies noch deutlicher: Das orange Balkendiagramm hat einen zusätzlichen Gast, der um ca. 1:00 Uhr eingetroffen ist:

d562ddf799288894.png

Auch das ist nicht gut. Was tun wir?

Durch das Hinzufügen von zufälligem Rauschen werden Balkendiagramme ein wenig ungenauer.

Sehen Sie sich die beiden Balkendiagramme unten an. Sie sind zwar nicht überhaupt genau, aber dennoch nützlich. Sie geben keine individuellen Beiträge preis. Sehr gut!

838a0293cd4fcfe3.gif

Differential Privacy fügt das richtige Maß an zufälligem Rauschen hinzu, um einzelne Beiträge zu maskieren.

Unsere Analyse war etwas vereinfacht. Die korrekte Implementierung von Differential Privacy ist aufwendiger und es gibt eine Reihe von recht unerwarteten Feinheiten. Ähnlich wie bei der Kryptografie ist es möglicherweise keine gute Idee, eine eigene Implementierung von Differential Privacy zu erstellen. Sie können Privacy on Beam verwenden, anstatt eine eigene Lösung zu implementieren. Verdrehe nicht deinen eigenen Differential Privacy!

In diesem Codelab zeigen wir Ihnen, wie Sie mit Privacy in Beam differenzielle private Analysen durchführen.

3. Privacy on Beam wird heruntergeladen

Sie müssen Privacy on Beam nicht herunterladen, um dem Codelab folgen zu können, da der gesamte relevante Code und die Grafiken in diesem Dokument enthalten sind. Wenn Sie den Code jedoch herunterladen möchten, ihn selbst ausführen oder später Privacy in Beam verwenden, können Sie die folgenden Schritte ausführen.

Dieses Codelab bezieht sich auf Version 1.1.0 der Bibliothek.

Laden Sie zuerst Privacy on Beam herunter:

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

Alternativ können Sie das GitHub-Repository klonen:

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

Privacy bei Beam befindet sich im Verzeichnis privacy-on-beam/ der obersten Ebene.

Der Code für dieses Codelab und das Dataset befindet sich im Verzeichnis privacy-on-beam/codelab/.

Außerdem muss auf Ihrem Computer Bazel installiert sein. Die Installationsanleitung für Ihr Betriebssystem finden Sie auf der Bazel-Website.

4. Besuche pro Stunde berechnen

Angenommen, Sie sind Restaurantbesitzer und möchten einige Statistiken zu Ihrem Restaurant teilen, z. B. Stoßzeiten. Glücklicherweise kennen Sie Differential Privacy und Anonymisierung. Sie sollten dies also so tun, dass keine Informationen über einzelne Besucher offengelegt werden.

Der Code für dieses Beispiel befindet sich in codelab/count.go.

Beginnen wir mit dem Laden eines simulierten Datasets, das die Besuche in Ihrem Restaurant an einem bestimmten Montag enthält. Der entsprechende Code ist für dieses Codelab nicht relevant. Sie können sich den entsprechenden Code aber in codelab/main.go, codelab/utils.go und codelab/visit.go ansehen.

Besucher-ID

Eingegebene Uhrzeit

Verbrachte Zeit (Min.)

Ausgegebener Geldbetrag (Euro)

1

9:30:00 Uhr

26

24

2

11:54:00 Uhr

53

17

3

13:05:00

81

33

Im folgenden Codebeispiel erstellen Sie zuerst mit Beam ein nicht privates Balkendiagramm der Besuchszeiten in Ihrem Restaurant. Scope ist eine Darstellung der Pipeline. Jeder neue Vorgang, den wir an den Daten ausführen, wird dem Scope hinzugefügt. CountVisitsPerHour benötigt eine Scope und eine Sammlung von Besuchen, die in Beam als PCollection dargestellt werden. Die Stunde jedes Besuchs wird extrahiert, indem die Funktion extractVisitHour auf die Sammlung angewendet wird. Dann zählt sie die Vorkommen jeder Stunde und gibt sie zurück.

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()
}

Dadurch wird (durch Ausführen von bazel run codelab -- --example="count" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/count.csv --output_chart_file=$(pwd)/count.png) im aktuellen Verzeichnis als count.png ein schönes Balkendiagramm erzeugt:

a179766795d4e64a.png

Der nächste Schritt besteht darin, Ihre Pipeline und Ihr Balkendiagramm in eine private zu konvertieren. Dazu gehen wir folgendermaßen vor:

Rufe zuerst MakePrivateFromStruct auf einem PCollection<V> auf, um ein PrivatePCollection<V> zu erhalten. Die Eingabe-PCollection muss eine Sammlung von Strukturen sein. Wir müssen PrivacySpec und idFieldPath als Eingabe für MakePrivateFromStruct eingeben.

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

PrivacySpec ist eine Struktur mit den Differential Privacy-Parametern (Epsilon und Delta), die zur Anonymisierung der Daten verwendet werden sollen. Vorerst brauchen Sie sich darüber keine Gedanken zu machen. Später können Sie in einem optionalen Abschnitt mehr darüber erfahren.

idFieldPath ist der Pfad zum Feld für die Nutzerkennung innerhalb der Struktur (in unserem Fall Visit). In diesem Fall ist die Nutzer-ID der Besucher das Feld VisitorID von Visit.

Dann wird pbeam.Count() statt stats.Count() aufgerufen. pbeam.Count() übernimmt als Eingabe eine CountParams-Struktur mit Parametern wie MaxValue, die sich auf die Genauigkeit der Ausgabe auswirken.

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,
})

Entsprechend begrenzt MaxPartitionsContributed, wie viele verschiedene Besuchszeiten ein Nutzer beitragen kann. Wir erwarten, dass sie das Restaurant höchstens einmal am Tag besuchen (oder es ist uns egal, ob sie es im Laufe des Tages mehrmals besuchen), also haben wir den Wert ebenfalls auf 1 gesetzt. Wir werden in einem optionalen Abschnitt ausführlicher auf diese Parameter eingehen.

MaxValue gibt an, wie oft ein einzelner Nutzer zu den von uns gezählten Werten beitragen kann. In diesem speziellen Fall sind die gezählten Werte die Besuchszeiten und wir erwarten, dass ein Nutzer das Restaurant nur einmal besucht (oder es ist uns egal, ob er es mehrmals pro Stunde besucht), also setzen wir diesen Parameter auf 1.

Am Ende sieht Ihr Code so aus:

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
}

Wir sehen ein ähnliches Balkendiagramm (count_dp.png) für die differenzierbaren Statistik (der vorherige Befehl führt sowohl die nicht privaten als auch die privaten Pipelines aus):

d6a0ace1acd3c760.png

Glückwunsch! Sie haben Ihre erste differenzielle Statistik berechnet!

Das Balkendiagramm, das Sie beim Ausführen des Codes erhalten, kann sich von diesem unterscheiden. Kein Problem. Aufgrund des Differential Privacy wird jedes Mal ein anderes Balkendiagramm angezeigt, wenn Sie den Code ausführen. Sie können jedoch sehen, dass diese dem ursprünglichen nicht privaten Balkendiagramm mehr oder weniger ähnlich sind.

Beachten Sie, dass es im Rahmen der Datenschutzgarantien sehr wichtig ist, die Pipeline nicht mehrmals auszuführen (z. B. um ein besseres Balkendiagramm zu erhalten). Warum Sie Ihre Pipelines nicht erneut ausführen sollten, wird im Abschnitt zur Berechnung mehrerer Statistiken erklärt. .

5. Öffentliche Partitionen verwenden

Im vorherigen Abschnitt haben Sie vielleicht bemerkt, dass alle Besuche (Daten) für einige Partitionen, d.h. Stunden, unterbrochen wurden.

d7fbc5d86d91e54a.png

Das liegt an der Partitionsauswahl bzw. -grenzwertung. Dies ist ein wichtiger Schritt, um Differential Privacy zu gewährleisten, wenn die Existenz von Ausgabepartitionen von den Nutzerdaten selbst abhängt. In diesem Fall kann die bloße Existenz einer Partition in der Ausgabe die Existenz eines einzelnen Nutzers in den Daten verlieren. Eine Erklärung, warum dies gegen den Datenschutz verstößt, finden Sie in diesem Blogpost. Um dies zu verhindern, behält Privacy in Beam nur Partitionen mit einer ausreichenden Anzahl von Nutzern bei.

Wenn die Liste der Ausgabepartitionen nicht von privaten Nutzerdaten abhängt, sondern um öffentliche Informationen handelt, ist dieser Schritt zur Partitionsauswahl nicht erforderlich. Dies gilt tatsächlich für unser Restaurantbeispiel: Wir kennen die Arbeitszeiten des Restaurants (9:00 bis 21:00 Uhr).

Der Code für dieses Beispiel befindet sich in codelab/public_partitions.go.

Wir erstellen einfach eine Kollektion von Stunden zwischen 9 und 21 (exklusiv) und geben sie in das Feld PublicPartitions von CountParams ein:

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
}

Beachten Sie, dass es möglich ist, Delta auf 0 zu setzen, wenn Sie öffentliche Partitionen und Laplace Noise (Standardeinstellung) verwenden, wie oben dargestellt.

Wenn wir die Pipeline mit öffentlichen Partitionen (mit 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) ausführen, erhalten wir (public_partitions_dp.png):

7c950fbe99fec60a.png

Wie Sie sehen, behalten wir die zuvor gelöschten Partitionen 9, 10 und 16 ohne öffentliche Partitionen bei.

Mit öffentlichen Partitionen können Sie nicht nur mehr Partitionen behalten, sondern jede Partition wird auch etwa halb so viel Rauschen erzeugt als mit öffentlichen Partitionen, da kein Budget für den Datenschutz (Epsilon & Delta bei der Partitionsauswahl. Aus diesem Grund ist der Unterschied zwischen den Zählungen der Rohdaten und privaten Daten im Vergleich zur vorherigen Ausführung geringfügig geringer.

Es gibt zwei wichtige Dinge, die Sie bei der Verwendung öffentlicher Partitionen beachten sollten:

  1. Gehen Sie mit Bedacht vor, wenn Sie die Liste der Partitionen aus Rohdaten ableiten: Wenn Sie dies nicht auf unterschiedlich vertrauliche Weise tun, z.B. einfach die Liste aller Partitionen in den Nutzerdaten liest, bietet Ihre Pipeline keine Differential Privacy-Garantien mehr. Im folgenden Abschnitt für Fortgeschrittene wird beschrieben, wie Sie dies auf differenzielle Art und Weise tun können.
  2. Wenn für einige der öffentlichen Partitionen keine Daten (z.B. Besuche) vorhanden sind, werden diese Partitionen verrauscht, um die Differential Privacy zu wahren. Wenn wir beispielsweise die Stunden zwischen 0 und 24 (anstelle von 9 und 21) angegeben haben, werden alle Stunden verwendet und es werden möglicherweise Besuche angezeigt, wenn keine vorhanden sind.

(Erweitert) Partitionen aus Daten ableiten

Wenn Sie mehrere Aggregationen mit derselben Liste nicht öffentlicher Ausgabepartitionen in derselben Pipeline ausführen, können Sie die Liste der Partitionen einmal mit SelectPartitions() ableiten und die Partitionen jeder Aggregation als PublicPartition-Eingabe bereitstellen. Dies ist nicht nur aus datenschutzrechtlicher Sicht auf sichere Weise, sondern auch weniger störend, da das Privacy-Budget bei der Partitionsauswahl nur einmal für die gesamte Pipeline verwendet wird.

6. Durchschnittliche Aufenthaltsdauer berechnen

Nachdem wir nun wissen, wie Dinge auf differenzielle Weise gezählt werden, sehen wir uns die Berechnungsmittel an. Genauer gesagt, berechnen wir jetzt die durchschnittliche Aufenthaltsdauer von Besuchern.

Der Code für dieses Beispiel befindet sich in codelab/mean.go.

Normalerweise würden wir zur Berechnung eines nicht privaten Mittelwerts der Aufenthaltsdauer stats.MeanPerKey() mit einem Vorverarbeitungsschritt verwenden, der die eingehenden PCollection der Besuche in einen PCollection<K,V> umwandelt, wobei K die Besuchszeit und V die Zeit ist, die der Besucher im Restaurant verbracht hat.

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
}

Dadurch wird (durch Ausführen von bazel run codelab -- --example="mean" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/mean.csv --output_chart_file=$(pwd)/mean.png) im aktuellen Verzeichnis als mean.png ein schönes Balkendiagramm erzeugt:

bc2df28bf94b3721.png

Um diesen differenziellen privat zu machen, wandeln wir PCollection noch einmal in PrivatePCollection um und ersetzen stats.MeanPerKey() durch pbeam.MeanPerKey(). Ähnlich wie „Count“ gibt es MeanParams, die einige Parameter enthalten, wie z. B. MinValue und MaxValue, die sich auf die Genauigkeit auswirken. MinValue und MaxValue stellen die Grenzen für den Beitrag jedes Nutzers zu jedem Schlüssel dar.

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 diesem Fall steht jeder Schlüssel für eine Stunde und die Werte stehen für die Zeit, die Besucher verbracht haben. Wir haben MinValue auf 0 gesetzt, da wir nicht erwarten, dass Besucher weniger als 0 Minuten im Restaurant verbringen. MaxValue wurde auf 60 Minuten festgelegt. Wenn ein Besucher also mehr als 60 Minuten verbringt, wird so reagiert, als ob er 60 Minuten verbracht hätte.

Am Ende sieht Ihr Code so aus:

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
}

Wir sehen ein ähnliches Balkendiagramm (mean_dp.png) für die differenzierbaren Statistik (der vorherige Befehl führt sowohl die nicht privaten als auch die privaten Pipelines aus):

e8ac6a9bf9792287.png

Ähnlich wie bei der Zählung erhalten wir bei jeder Ausführung unterschiedliche Ergebnisse, da es sich um einen unterschiedlich privaten Vorgang handelt. Sie sehen jedoch, dass die unterschiedlichen privaten Aufenthaltsdauern nicht weit vom tatsächlichen Ergebnis abweichen.

7. Umsatz pro Stunde berechnen

Eine weitere interessante Statistik, die wir uns ansehen könnten, ist der Umsatz pro Stunde im Tagesverlauf.

Der Code für dieses Beispiel befindet sich in codelab/sum.go.

Wir beginnen wieder mit der nicht privaten Version. Mit einer Vorverarbeitung unseres simulierten Datasets können wir eine PCollection<K,V> erstellen, wobei K die Besuchszeit und V das Geld ist, das der Besucher im Restaurant ausgegeben hat: Um einen nicht privaten Umsatz pro Stunde zu berechnen, können wir einfach alle Geldbeträge addieren, die Besucher ausgegeben haben, indem wir stats.SumPerKey() aufrufen:

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
}

Dadurch wird (durch Ausführen von bazel run codelab -- --example="sum" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/sum.csv --output_chart_file=$(pwd)/sum.png) im aktuellen Verzeichnis als sum.png ein schönes Balkendiagramm erzeugt:

548619173fad0c9a.png

Um diesen Unterschied als privat zu kennzeichnen, wandeln wir PCollection noch einmal in PrivatePCollection um und ersetzen stats.SumPerKey() durch pbeam.SumPerKey(). Ähnlich wie Count und MeanPerKey gibt es SumParams, die einige Parameter enthalten, wie z. B. MinValue und MaxValue, die sich auf die Genauigkeit auswirken.

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 diesem Fall stellen MinValue und MaxValue die Grenzen dar, die wir für das Geld haben, das jeder Besucher ausgibt. Wir haben MinValue auf 0 gesetzt, da wir nicht erwarten, dass Besucher weniger als 0 € im Restaurant ausgeben. Wir haben MaxValue auf 40 festgelegt. Das bedeutet, wenn ein Besucher mehr als 40 € ausgibt, gehen wir so vor, als hätte dieser Nutzer 40 € ausgegeben.

Am Ende sieht der Code so aus:

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
}

Wir sehen ein ähnliches Balkendiagramm (sum_dp.png) für die differenzierbaren Statistik (der vorherige Befehl führt sowohl die nicht privaten als auch die privaten Pipelines aus):

46c375e874f3e7c4.png

Auch hier erhalten wir ähnlich wie "count" und " mean" bei jeder Ausführung ein anderes Ergebnis, da es sich um einen differenziellen privaten Vorgang handelt. Wie Sie jedoch sehen, ist das anders als private Ergebnis sehr nahe an den tatsächlichen Umsätzen pro Stunde.

8. Mehrere Statistiken berechnen

Meistens möchten Sie möglicherweise mehrere Statistiken über dieselben zugrunde liegenden Daten berechnen, ähnlich wie bei der Berechnung von Anzahl, Mittelwert und Summe. In der Regel ist es einfacher und übersichtlicher, dies in einer einzelnen Beam-Pipeline und in einem einzigen Binärprogramm zu tun. Sie können dies auch mit Privacy in Beam tun. Sie können eine einzelne Pipeline schreiben, um Ihre Transformationen und Berechnungen auszuführen, und eine einzelne PrivacySpec für die gesamte Pipeline verwenden.

Mit einem einzigen PrivacySpec ist dies nicht nur einfacher, sondern auch im Hinblick auf den Datenschutz besser. Erinnern Sie sich an die Epsilon- und Delta-Parameter, die wir für PrivacySpec bereitstellen? Diese stellen ein sogenanntes Datenschutzbudget dar. Damit wird gemessen, wie viel Datenschutz der Nutzer in den zugrunde liegenden Daten gestohlen wird.

Ein wichtiger Punkt beim Privacy-Budget ist, dass es additiv ist: Wenn Sie eine Pipeline mit einem bestimmten Epsilon-{4/} und Delta Delta nur einmal ausführen, geben Sie ein (Seiten-, Delta)-Budget aus. Bei einer zweiten Ausführung würden Sie insgesamt ein Gesamtbudget in Höhe von (2◆, 2DBM) ausgeben. Das Gleiche gilt, wenn Sie mehrere Statistiken mit einem PrivacySpec (und nacheinander ein Datenschutzbudget) von (Fall, ?) berechnen, beträgt das Gesamtbudget (2, 2, 2). Das bedeutet, dass Sie die Datenschutzgarantien beeinträchtigen.

Um dies zu umgehen, sollten Sie eine einzelne PrivacySpec mit dem Gesamtbudget verwenden, wenn Sie mehrere Statistiken über dieselben zugrunde liegenden Daten berechnen möchten. Dann müssen Sie das Epsilon und das Delta angeben, das Sie für jede Aggregation verwenden möchten. Letztendlich haben Sie die gleiche allgemeine Datenschutzgarantie. aber je höher Epsilon und Delta eine bestimmte Aggregation hat, desto höher ist die Genauigkeit.

Um dies in Aktion zu sehen, können wir die drei Statistiken (Anzahl, Mittelwert und Summe), die wir zuvor separat berechnet haben, in einer einzelnen Pipeline berechnen.

Der Code für dieses Beispiel befindet sich in codelab/multiple.go. Beachten Sie, wie wir das Gesamtbudget (effektiv, ?) gleichmäßig auf die drei Aggregationen aufteilen:

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. (Optional) Differential Privacy-Parameter optimieren

Sie haben in diesem Codelab einige Parameter gesehen, die in diesem Codelab erwähnt werden: Epsilon, Delta, maxPartitionsContributord usw. Wir können sie grob in zwei Kategorien unterteilen: Datenschutzparameter und Dienstprogrammparameter.

Datenschutzparameter

Epsilon und Delta sind die Parameter, die den Datenschutz, den wir durch die Verwendung von Differential Privacy bieten, quantifizieren. Genauer gesagt sind Epsilon und Delta ein Maß dafür, wie viele Informationen ein potenzieller Angreifer über die zugrunde liegenden Daten erhält, wenn er die anonymisierte Ausgabe betrachtet. Je höher Epsilon und Delta sind, desto mehr Informationen erhält der Angreifer über die zugrunde liegenden Daten, was ein Datenschutzrisiko darstellt.

Auf der anderen Seite gilt: Je niedriger das Epsilon und Delta, desto mehr Rauschen müssen Sie der Ausgabe hinzufügen, um anonym zu bleiben, und umso mehr einzelne Nutzer müssen Sie in jeder Partition haben, um diese Partition in der anonymisierten Ausgabe beizubehalten. Hier gibt es also einen Kompromiss zwischen Nutzwert und Datenschutz.

Wenn Sie in „Privacy on Beam“ das Gesamtbudget für den Datenschutz in den PrivacySpec angeben, müssen Sie sich Gedanken um die Datenschutzgarantien machen, die Sie in Ihrer anonymisierten Ausgabe wünschen. Wenn Sie möchten, dass Ihre Datenschutzgarantien bestehen, müssen Sie jedoch den Ratschlägen in diesem Codelab folgen, um Ihr Budget nicht zu überlasten. Erstellen Sie dazu eine separate PrivacySpec für jede Aggregation oder führen Sie die Pipeline mehrmals aus.

Weitere Informationen zum Differential Privacy und zur Bedeutung der Datenschutzparameter finden Sie in der Literatur.

Dienstprogrammparameter

Diese Parameter wirken sich nicht auf die Datenschutzgarantien aus, solange die Hinweise zur Verwendung von Privacy in Beam korrekt befolgt werden. Sie wirken sich jedoch auf die Genauigkeit und folglich auf den Nutzen der Ausgabe aus. Sie werden in den Params-Strukturen der einzelnen Aggregationen bereitgestellt, z.B. CountParams, SumParams usw. Diese Parameter werden verwendet, um das hinzuzufügende Rauschen zu skalieren.

Ein Dienstprogrammparameter in Params, der für alle Aggregationen anwendbar ist, ist MaxPartitionsContributed. Eine Partition entspricht einem Schlüssel der PCollection, die von einem Privacy On Beam-Aggregationsvorgang ausgegeben wird, z.B. Count, SumPerKey usw. MaxPartitionsContributed begrenzt also, zu wie vielen unterschiedlichen Schlüsselwerten ein Nutzer in der Ausgabe beitragen kann. Wenn ein Nutzer zu mehr als MaxPartitionsContributed Schlüsseln in den zugrunde liegenden Daten beiträgt, werden einige seiner Beiträge verworfen, sodass er zu genau MaxPartitionsContributed Schlüsseln beiträgt.

Ähnlich wie bei MaxPartitionsContributed haben die meisten Aggregationen einen MaxContributionsPerPartition-Parameter. Sie werden in den Params-Strukturen bereitgestellt und jede Aggregation kann eigene Werte haben. Im Gegensatz zu MaxPartitionsContributed begrenzt MaxContributionsPerPartition den Beitrag eines Nutzers für jeden Schlüssel. Das heißt, ein Nutzer kann für jeden Schlüssel nur MaxContributionsPerPartition-Werte hinzufügen.

Das der Ausgabe hinzugefügte Rauschen wird um MaxPartitionsContributed und MaxContributionsPerPartition skaliert, daher gibt es hier einen Kompromiss: Ein größerer MaxPartitionsContributed und MaxContributionsPerPartition bedeuten beide, dass Sie mehr Daten behalten, aber das Ergebnis ungenauer sind.

Für einige Aggregationen sind MinValue und MaxValue erforderlich. Diese geben die Grenzen für Beiträge jedes Nutzers an. Wenn ein Nutzer einen Wert unter MinValue beisteuert, wird dieser Wert auf maximal MinValue beschränkt. Wenn ein Nutzer einen Wert beisteuert, der größer als MaxValue ist, wird dieser Wert auf MaxValue reduziert. Dies bedeutet, dass Sie größere Grenzen angeben müssen, um mehr der ursprünglichen Werte beizubehalten. Ähnlich wie bei MaxPartitionsContributed und MaxContributionsPerPartition wird das Rauschen anhand der Größe der Grenzen skaliert. Größere Grenzen bedeuten also, dass Sie mehr Daten behalten, aber das Ergebnis verrauschter haben.

Der letzte Parameter, um den es hier geht, ist NoiseKind. In Privacy On Beam werden zwei verschiedene Rauschmechanismen unterstützt: GaussianNoise und LaplaceNoise. Beide haben Vor- und Nachteile, aber die Laplace-Distribution bietet einen besseren Nutzen bei geringen Beitragsgrenzen, weshalb Privacy On Beam sie standardmäßig verwendet. Wenn Sie jedoch ein Gaußsches Verteilungsrauschen verwenden möchten, können Sie Params mit einer pbeam.GaussianNoise{}-Variablen angeben.

10. Zusammenfassung

Gut gemacht! Du hast das Codelab „Privacy on Beam“ abgeschlossen. Sie haben viel über Differential Privacy und Privacy auf Beam gelernt:

  • PCollection durch Aufrufen von MakePrivateFromStruct in PrivatePCollection umwandeln.
  • Verwendung von Count zur Berechnung von differenziellen privaten Zählungen.
  • Verwendung von MeanPerKey zur Berechnung von differenziellen privaten Mitteln.
  • Mithilfe von SumPerKey differenziell private Summen berechnen.
  • Mehrere Statistiken mit einer einzigen PrivacySpec in einer einzelnen Pipeline berechnen.
  • (Optional) PrivacySpec und Aggregationsparameter (CountParams, MeanParams, SumParams) anpassen

Privacy on Beam bietet aber noch viele weitere Aggregationsfunktionen (z. B. Quantile, Zählung einzelner Werte). Weitere Informationen dazu finden Sie im GitHub-Repository oder im godoc.

Wenn du etwas Zeit hast, kannst du uns gern Feedback zum Codelab geben, indem du an einer Umfrage teilnimmst.