Obliczanie statystyk prywatnych z zastosowaniem funkcji Privacy on Beam

1. Wprowadzenie

Może się wydawać, że statystyki zbiorcze nie wyciekują żadnych informacji o osobach, z których dane te są skonstruowane. Istnieje jednak wiele sposobów, dzięki którym atakujący może poznać poufne informacje o osobach w zbiorze danych ze statystyk zbiorczych.

Ochrona , dowiesz się, jak tworzyć statystyki prywatne za pomocą różnicowych agregacji prywatnych na stronie Privacy on Beam. Privacy on Beam to platforma prywatności różnicowej, która działa z Apache Beam.

Co rozumiemy przez „prywatny”?

Użycie słowa „prywatny” w obrębie tego ćwiczenia w Codelabs mamy na myśli, że wyniki są generowane w sposób, który nie powoduje wycieku żadnych prywatnych informacji o poszczególnych osobach. W tym celu wykorzystano prywatność różnicową – mocną pojęcie ochrony prywatności, która polega na anonimizacji. Anonimizacja to proces agregacji danych od wielu użytkowników w celu ochrony ich prywatności. Wszystkie metody anonimizacji korzystają z agregacji, ale nie wszystkie metody agregacji zapewniają anonimizację. Prywatność różnicowa zapewnia natomiast wymierne gwarancje w zakresie wycieku informacji i ochrony prywatności.

2. Omówienie prywatności różnicowej

Aby lepiej zrozumieć zasadę prywatności różnicowej, przyjrzyjmy się prostemu przykładowi.

Ten wykres słupkowy pokazuje natężenie ruchu w małej restauracji w pewne konkretne wieczory. Wielu gości przychodzi o 19:00, a o 1:00 restauracja jest całkowicie pusta:

a43dbf3e2c6de596.png

To wygląda na przydatne!

Jest jakiś haczyk. Gdy przychodzi nowy gość, natychmiast pokazuje to wykres słupkowy. Spójrz na wykres: wyraźnie widać, że pojawił się nowy gość i że przybył około 1:00:

bda96729e700a9dd.png

Nie jest to dobre z perspektywy prywatności. Naprawdę zanonimizowane statystyki nie powinny ujawniać poszczególnych wkładów. Zestawienie tych 2 wykresów obok siebie sprawia, że jest to jeszcze bardziej widoczne – pomarańczowy wykres słupkowy ma jednego dodatkowego gościa, który przybył o 1: 00:

d562ddf799288894.png

Powtórzę: nie jest to satysfakcjonujące. Co robimy?

Wykresy słupkowe będą nieco mniej dokładne, jeśli dodasz do nich losowy szum.

Spójrz na 2 wykresy słupkowe poniżej. Chociaż nie są one całkowicie dokładne, w dalszym ciągu są przydatne i nie ujawniają informacji o poszczególnych osobach. Super!

838a0293cd4fcfe3.gif

Prywatność różnicowa to dodawanie odpowiedniej ilości losowego szumu do maskowania poszczególnych treści.

Nasza analiza była nieco zbyt uproszczona. Prawidłowe wdrożenie prywatności różnicowej jest bardziej złożone i obejmuje szereg nieoczekiwanych niuansów. Podobnie jak w przypadku kryptografii, opracowanie własnego wdrożenia prywatności różnicowej może nie być dobrym pomysłem. Zamiast wdrażać własne rozwiązanie, możesz korzystać z narzędzia „Privacy on Beam”. Nie stosuj swojej prywatności różnicowej!

W tym ćwiczeniu w Codelabs dowiesz się, jak za pomocą funkcji „Privacy on Beam” przeprowadzać analizę prywatną różnicową.

3. Pobieranie plików Privacy on Beam

Aby wykonać ćwiczenia z programowania, nie musisz pobierać strony Privacy on Beam. Cały odpowiedni kod i wykresy znajdziesz w tym dokumencie. Jeśli jednak zechcesz pobrać kod, żeby pobawić się kodem, uruchomić go samodzielnie lub później skorzystać z narzędzia Privacy on Beam, wykonaj czynności opisane poniżej.

Pamiętaj, że to ćwiczenie w Codelabs dotyczy biblioteki w wersji 1.1.0.

Najpierw pobierz aplikację Privacy on Beam:

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

Możesz też skopiować repozytorium GitHub:

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

Usługa Privacy on Beam znajduje się w katalogu privacy-on-beam/ najwyższego poziomu.

Kod tego ćwiczenia w Codelabs i zbiór danych znajduje się w katalogu privacy-on-beam/codelab/.

Na komputerze musi być też zainstalowany Bazel. Instrukcje instalacji dla swojego systemu operacyjnego znajdziesz na stronie Bazel.

4. Obliczanie liczby wizyt na godzinę

Wyobraź sobie, że jesteś właścicielem restauracji i chcesz udostępnić statystyki dotyczące swojej restauracji, np. ujawnić popularne godziny wizyt. Na szczęście wiesz już o prywatności różnicowej i anonimizacji, więc chcesz, by udało Ci się zrobić to w sposób, który pozwoli uniknąć ujawnienia informacji o poszczególnych użytkownikach.

Kod w tym przykładzie znajduje się w języku: codelab/count.go.

Zacznijmy od wczytania przykładowego zbioru danych zawierającego wizyty w Twojej restauracji w konkretny poniedziałek. Kod tego ćwiczenia nie jest odpowiedni na potrzeby tego ćwiczenia z programowania, ale możesz go zobaczyć w codelab/main.go, codelab/utils.go i codelab/visit.go.

Identyfikator użytkownika

Wprowadzono godzinę

Spędzony czas (w minutach)

Wydane pieniądze (euro)

1

9:30:00

26

24

2

11:54:00

53

17

3

13:05:00

81

33

Najpierw przygotuj nieprywatny wykres słupkowy czasu wizyt w Twojej restauracji, używając Beam w przykładowym kodzie poniżej. Element Scope reprezentuje potok, a każda nowa operacja wykonywana na danych jest dodawana do interfejsu Scope. Funkcja CountVisitsPerHour pobiera Scope i zbiór wizyt, które w Beam są wyświetlane jako PCollection. Wyodrębnia godzinę każdej wizyty, stosując do kolekcji funkcję extractVisitHour. Następnie zlicza wystąpienia każdej godziny i zwraca ją.

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

Powoduje to wygenerowanie ładnego wykresu słupkowego (po uruchomieniu programu bazel run codelab -- --example="count" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/count.csv --output_chart_file=$(pwd)/count.png) w bieżącym katalogu jako count.png:

a179766795d4e64a.png

Następnym krokiem jest przekształcenie potoku i wykresu słupkowego na prywatny. Robimy to w następujący sposób.

Najpierw zadzwoń pod numer MakePrivateFromStruct (PCollection<V>), aby otrzymać PrivatePCollection<V>. Dane wejściowe PCollection muszą być zbiorem elementów struct. Musisz wpisać PrivacySpec i idFieldPath jako dane wejściowe dla MakePrivateFromStruct.

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

PrivacySpec to struktura zawierająca parametry prywatności różnicowej (epsilon i delta), których chcemy używać do anonimizacji danych. (Na razie nie musisz się nimi przejmować – w dalszej części strony znajdziesz opcjonalną sekcję, w której znajdziesz więcej informacji na ten temat).

idFieldPath to ścieżka pola identyfikatora użytkownika w elemencie struct (w tym przypadku Visit). W tym przypadku identyfikatorem użytkownika jest pole VisitorID Visit.

Następnie nazywamy pbeam.Count() zamiast stats.Count(), a pbeam.Count() pobiera jako dane wejściowe strukturę CountParams z parametrami takimi jak MaxValue, które wpływają na dokładność danych wyjściowych.

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

Podobnie funkcja MaxPartitionsContributed ogranicza liczbę różnych godzin wizyt, które użytkownik może wziąć pod uwagę. Oczekujemy, że będą odwiedzać restaurację nie częściej niż raz dziennie (lub nie jest to dla nas obojętne, jeśli odwiedzają ją wiele razy w ciągu dnia), dlatego też ustawiamy dla niej wartość 1. Omówimy je bardziej szczegółowo w sekcji opcjonalnej.

MaxValue ogranicza liczbę udziałów w zliczanych wartościach przez pojedynczego użytkownika. W tym konkretnym przypadku liczone są godziny wizyt. Oczekujemy, że użytkownik odwiedzi restaurację tylko raz (lub nie jest to dla nas bardzo ważne, więc ustawiamy ten parametr na wartość 1.

Ostatecznie Twój kod będzie wyglądał tak:

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
}

Podobny wykres słupkowy (count_dp.png) przedstawia statystyki prywatne o charakterze różnicowym (poprzednie polecenie uruchamiało zarówno potoki nieprywatne, jak i prywatne):

d6a0ace1acd3c760.png

Gratulacje! Udało Ci się obliczyć pierwszą statystykę dotyczącą prywatności różnicowej!

Wykres słupkowy, który widzisz po uruchomieniu kodu, może się różnić od tego. To świetnie. Ze względu na szum w prywatności różnicowej przy każdym uruchomieniu kodu jest wyświetlany inny wykres słupkowy, ale można zauważyć, że są one mniej lub bardziej podobne do oryginalnego, nieprywatnego wykresu słupkowego, który mieliśmy.

Pamiętaj, że bardzo ważne jest, aby w ramach gwarancji prywatności proces nie był powtarzany wielokrotnie (np. w celu uzyskania lepiej wyglądającego wykresu słupkowego). Powód, dla którego nie należy ponownie uruchamiać potoków, został objaśniony w sekcji „Obliczanie wielu statystyk” .

5. Używanie partycji publicznych

W poprzedniej sekcji można było zauważyć, że pominęliśmy wszystkie odwiedziny (dane) w niektórych partycjach, tj. o godz.

d7fbc5d86d91e54a.png

Wynika to z wyboru/progu partycji. To ważny krok w celu zapewnienia prywatności różnicowej, gdy istnienie partycji wyjściowych zależy od samych danych użytkownika. W takim przypadku samo istnienie partycji w danych wyjściowych może ujawnić istnienie w danych konkretnego użytkownika (wyjaśnienie, dlaczego tak narusza prywatność, znajduje się w tym poście na blogu). Aby temu zapobiec, funkcja Privacy on Beam przechowuje tylko partycje, w których jest wystarczająca liczba użytkowników.

Gdy lista partycji wyjściowych nie zależy od prywatnych danych użytkownika, tj. są to informacje publiczne, ten krok wyboru partycji nie jest potrzebny. Tak właśnie jest w przypadku restauracji – znamy jej godziny pracy (od 9:00 do 21:00).

Kod w tym przykładzie znajduje się w języku: codelab/public_partitions.go.

Utworzymy po prostu kolekcję PCollection zawierającą godziny od 9 do 21 (wyłącznie) i wpiszesz ją w polu PublicPartitions wartości 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
}

Pamiętaj, że możesz ustawić wartość delta na 0, jeśli w takiej sytuacji używasz partycji publicznych i Laplace Noise (domyślnie).

Gdy uruchamiamy potok z partycjami publicznymi (z 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), uzyskujemy (public_partitions_dp.png):

7c950fbe99fec60a.png

Jak widać, teraz zachowujemy partycje 9, 10 i 16, które wcześniej były mniejsze, bez partycji publicznych.

Użycie partycji publicznych nie tylko pozwala zachować więcej partycji, ale zapewnia też mniej więcej o połowę mniej szumu do każdej partycji niż w przypadku rezygnacji z partycji publicznych ze względu na brak wydatków z budżetu na prywatność, tj. wartości ypsilon delta przy wyborze partycji. Dlatego różnica między wartościami nieprzetworzonymi a prywatnymi jest nieco mniejsza w porównaniu z poprzednim uruchomieniem.

Podczas korzystania z partycji publicznych musisz pamiętać o 2 ważnych kwestiach:

  1. Zachowaj ostrożność podczas pobierania listy partycji z nieprzetworzonych danych: jeśli nie zrobisz tego w sposób zapewniający prywatność różnicową, np. tylko odczytanie listy wszystkich partycji w danych użytkownika, potok nie zapewnia już gwarancji prywatności różnicowej. Zapoznaj się poniżej z sekcją dla zaawansowanych, aby dowiedzieć się, jak to zrobić z zachowaniem prywatności różnicowej.
  2. Jeśli nie ma danych (np. o wizytach) w przypadku niektórych partycji publicznych, zostanie do nich zastosowany szum, aby zachować prywatność różnicową. Jeśli np. zarejestrowaliśmy godziny z zakresu od 0 do 24 (a nie od 9 do 21), wszystkie godziny zostałyby zaszumione i mogą pokazywać niektóre wizyty, jeśli ich nie ma.

(Zaawansowane) Pobieranie partycji z danych

Jeśli w tym samym potoku korzystasz z kilku agregacji z tą samą listą niepublicznych partycji danych wyjściowych, możesz uzyskać listę partycji raz za pomocą funkcji SelectPartitions() i podając partycje do każdej agregacji jako dane wejściowe PublicPartition. Jest to nie tylko bezpieczne z perspektywy prywatności, ale także pozwala ograniczyć ilość szumu dzięki wykorzystaniu budżetu na potrzeby prywatności przy wyborze partycji tylko raz w całym potoku.

6. Obliczanie średniej długości pobytu

Wiemy już, jak zliczać różne rzeczy z zachowaniem poufności. Teraz przyjrzyjmy się sposobom obliczania. Mówiąc dokładniej, będziemy teraz obliczać średnią długość pobytu użytkowników.

Kod w tym przykładzie znajduje się w języku: codelab/mean.go.

Normalnie do obliczenia średniej czasu trwania pobytu, która nie jest prywatna, używamy funkcji stats.MeanPerKey() z etapem wstępnego przetwarzania danych, który konwertuje PCollection wizyt na PCollection<K,V>, gdzie K to godzina wizyty, a V – czas spędzony przez użytkownika w restauracji.

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
}

Powoduje to wygenerowanie ładnego wykresu słupkowego (po uruchomieniu programu bazel run codelab -- --example="mean" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/mean.csv --output_chart_file=$(pwd)/mean.png) w bieżącym katalogu jako mean.png:

bc2df28bf94b3721.png

Aby uczynić te dane jako prywatne, ponownie przekształcamy PCollection w PrivatePCollection i zastępujemy stats.MeanPerKey() elementem pbeam.MeanPerKey(). Podobnie jak w przypadku parametru Count, mamy także MeanParams zawierające niektóre parametry, np. MinValue i MaxValue, które wpływają na dokładność. MinValue i MaxValue określają granice udziału poszczególnych użytkowników w poszczególnych kluczach.

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

W tym przypadku każdy klucz reprezentuje godzinę, a wartości to czas spędzony przez użytkowników. Ustawiliśmy MinValue na 0, ponieważ według naszych założeń użytkownicy spędziją w restauracji mniej niż 0 minut. Ustawiamy MaxValue na 60, co oznacza, że jeśli użytkownik spędza ponad 60 minut, działamy tak, jakby trwał 60 minut.

Ostatecznie Twój kod będzie wyglądał tak:

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
}

Podobny wykres słupkowy (mean_dp.png) przedstawia statystyki prywatne o charakterze różnicowym (poprzednie polecenie uruchamiało zarówno potoki nieprywatne, jak i prywatne):

e8ac6a9bf9792287.png

Podobnie jak w przypadku liczenia, ponieważ jest to różniczkowa operacja prywatna, więc za każdym razem, gdy ją uruchomisz, otrzymasz inne wyniki. Widać jednak, że różnice w długości pobytów w przypadku prywatności różnią się od rzeczywistych wyników.

7. Obliczanie przychodów na godzinę

Kolejną ciekawą statystyką, którą możemy zbadać, są przychody na godzinę w ciągu dnia.

Kod w tym przykładzie znajduje się w języku: codelab/sum.go.

Tutaj znowu zaczniemy od wersji nieprywatnej. Dzięki wstępnemu przetwarzaniu danych z naszego przykładowego zbioru danych możemy utworzyć PCollection<K,V>, w którym K oznacza godzinę wizyty, a V oznacza kwotę wydaną przez użytkownika w restauracji. Aby obliczyć na godzinę przychody nieprywatne, możemy po prostu zsumować wszystkie wydane pieniądze, dzwoniąc do 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
}

Powoduje to wygenerowanie ładnego wykresu słupkowego (po uruchomieniu programu bazel run codelab -- --example="sum" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/sum.csv --output_chart_file=$(pwd)/sum.png) w bieżącym katalogu jako sum.png:

548619173fad0c9a.png

Aby uczynić te dane jako prywatne, ponownie przekształcamy PCollection w PrivatePCollection i zastępujemy stats.SumPerKey() elementem pbeam.SumPerKey(). Podobnie jak w przypadku Count i MeanPerKey, mamy w elemencie SumParams niektóre parametry, np. MinValue i MaxValue, które wpływają na dokładność.

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

W tym przypadku MinValue i MaxValue określają progi kwoty, jaką może wydać każdy użytkownik. Ustawiliśmy MinValue na 0, ponieważ spodziewamy się, że klienci wydadzą w restauracji mniej niż 0 euro. Ustawiamy MaxValue na 40, co oznacza, że jeśli użytkownik wyda więcej niż 40 euro, działamy tak, jakby klient wydał 40 euro.

Ostatecznie kod będzie wyglądał tak:

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
}

Podobny wykres słupkowy (sum_dp.png) przedstawia statystyki prywatne o charakterze różnicowym (poprzednie polecenie uruchamiało zarówno potoki nieprywatne, jak i prywatne):

46c375e874f3e7c4.png

Podobnie jak w przypadku liczby i średniej, ponieważ jest to różniczkowa operacja prywatna, przy każdym uruchomieniu otrzymasz inne wyniki. Jak widać, jednak zróżnicowany wynik prywatny jest bardzo zbliżony do rzeczywistych przychodów na godzinę.

8. Obliczanie wielu statystyk

Najczęściej być może zainteresuje Cię obliczanie wielu statystyk na podstawie tych samych danych bazowych, podobnie jak w przypadku metod liczenia, średniej i sumy. Zwykle jest to bardziej przejrzyste i łatwiejsze do wykonania w pojedynczym potoku Beam oraz w jednym pliku binarnym. To samo możesz zrobić dzięki sekcji Privacy on Beam. Możesz napisać jeden potok do uruchamiania przekształceń i obliczeń oraz używać jednego PrivacySpec dla całego potoku.

Nie tylko wygodniej jest robić to za pomocą jednego urządzenia PrivacySpec, ale też lepiej dba o prywatność. Jeśli pamiętasz parametry ypsilon i delta, które dostarczamy funkcji PrivacySpec, reprezentują one tak zwane budżety prywatności, które określają, jaka część prywatności użytkowników w danych bazowych jest ujawniana.

Warto pamiętać, że budżet na potrzeby prywatności jest sumujący. Jeśli uruchomisz potok z konkretną wartością ypsilon zer i delta, wydajesz budżet ( , ). Jeśli uruchomisz ją po raz drugi, łączny budżet wyniesie Podobnie, jeśli obliczyć wiele statystyk z parametrem PrivacySpec (i następującym przez nas budżetem na potrzeby prywatności) w wysokości ( , ,), łączny budżet wyniesie (2zer, 2). Oznacza to, że naruszasz gwarancje prywatności.

Aby to obejść, gdy chcesz obliczyć wiele statystyk na podstawie tych samych danych bazowych, musisz użyć jednego elementu PrivacySpec z określonym łącznym budżetem. Następnie musisz określić wartości ypsilon i delta, które chcesz stosować w przypadku każdej agregacji. Ostatecznie zyskujecie taką samą ogólną gwarancję prywatności. ale im wyższa wartość ypsilon i delta danej agregacji, tym większa dokładność.

Aby zobaczyć, jak to działa, możemy obliczyć 3 statystyki (liczbę, średnią i sumę), które obliczyliśmy wcześniej w jednym potoku.

Kod w tym przykładzie znajduje się w języku: codelab/multiple.go. Zwróć uwagę, że całkowite środki (e, il) dzielimy po równo między te 3 agregacje:

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. (Opcjonalnie) Dostosowywanie parametrów prywatności różnicowej

W tym ćwiczeniu w programie zauważyliście sporo parametrów: epsilon, delta, maxPartitionsContributiond itp. Podzielimy je z przybliżenia na 2 kategorie: Parametry prywatności oraz Parametry użytkowe.

Parametry prywatności

Epsilon i delta to parametry, które pozwalają ocenić prywatność zapewnianą przez nas za pomocą prywatności różnicowej. Dokładniej rzecz ujmując, wartości ypsilon i delta określają, ile informacji na temat danych źródłowych zdobywa potencjalny atakujący na podstawie zanonimizowanych danych wyjściowych. Im wyższa wartość ypsilon i delta, tym więcej informacji na temat danych źródłowych uzyskuje atakujący, co stanowi zagrożenie dla prywatności.

Z drugiej strony im niższe wartości ypsilon i delta, tym więcej szumu musisz dodać do danych wyjściowych, aby zachować anonimowość, i większą liczbę unikalnych użytkowników na każdej partycji, aby zachować ją w zanonimizowanych danych wyjściowych. Chodzi tu o kompromis między użytecznością a prywatnością.

Gdy w narzędziu Privacy on Beam określasz całkowity budżet na potrzeby prywatności w PrivacySpec, musisz zadbać o gwarancje prywatności, jakich oczekujesz w zanonimizowanych danych wyjściowych. Jeśli jednak chcesz zagwarantować sobie prywatność, postępuj zgodnie ze wskazówkami z tego ćwiczenia z programowania, aby nie nadużywać budżetu. W tym celu użyj osobnego PrivacySpec do każdej agregacji lub kilkukrotnego uruchamiania potoku.

Więcej informacji o prywatności różnicowej i ich znaczeniu znajdziesz w literaturze.

Parametry narzędzia

Są to parametry, które nie mają wpływu na gwarancje prywatności (pod warunkiem, że są zgodne z zaleceniami dotyczącymi korzystania z funkcji Privacy on Beam), ale wpływają na dokładność, a w konsekwencji na użyteczność danych wyjściowych. Są one podawane w elementach struct Params każdej agregacji, np. CountParams, SumParams itp. Te parametry służą do skalowania dodawanego szumu.

Parametr narzędzia podany w zasadzie Params i ma zastosowanie do wszystkich agregacji to MaxPartitionsContributed. Partycja odpowiada kluczowi obiektu PCollection wygenerowanym przez operację agregacji „Privacy On Beam”, np. Count, SumPerKey itp. W związku z tym MaxPartitionsContributed określa, do ilu unikalnych wartości kluczy użytkownik może mieć udział w danych wyjściowych. Jeśli użytkownik przekaże więcej niż MaxPartitionsContributed kluczy w danych bazowych, niektóre z jego wkładu zostaną pominięte, dzięki czemu będzie mieć udział dokładnie MaxPartitionsContributed kluczy.

Podobnie jak w przypadku parametru MaxPartitionsContributed większość agregacji ma parametr MaxContributionsPerPartition. Są one podawane w elementach struct Params, a każda agregacja może mieć osobną wartość. Zamiast zasady MaxPartitionsContributed zasada MaxContributionsPerPartition ogranicza dane użytkownika dotyczące każdego klucza. Oznacza to, że użytkownik może przesłać tylko wartość MaxContributionsPerPartition dla każdego klucza.

Szum dodawany do danych wyjściowych jest skalowany według atrybutów MaxPartitionsContributed i MaxContributionsPerPartition, dlatego występuje tu kompromis: im więcej parametrów MaxPartitionsContributed, jak i MaxContributionsPerPartition. Oznacza to, że zachowasz więcej danych, ale uzyskasz bardziej zaszumione wyniki.

Niektóre agregacje wymagają MinValue i MaxValue. Określają one limity aktywności każdego użytkownika. Jeśli użytkownik przekaże wartość niższą niż MinValue, zostanie ona ograniczona do MinValue. Jeśli użytkownik przekaże wartość większą niż MaxValue, zostanie ona ograniczona do MaxValue. Oznacza to, że aby zachować więcej pierwotnych wartości, musisz ustalić większe progi. Podobnie jak w przypadku MaxPartitionsContributed i MaxContributionsPerPartition szum jest skalowany według rozmiaru granic, więc większe granice oznaczają, że zachowasz więcej danych, ale uzyskasz bardziej zaszumione wyniki.

Ostatni parametr, który będziemy omawiać, to NoiseKind. W ramach funkcji Privacy On Beam obsługujemy 2 różne mechanizmy szumu: GaussianNoise i LaplaceNoise. Oba mają zalety i wady, ale rozkład Laplace'a zapewnia większą przydatność przy niskim poziomie udziału w konwersji, dlatego funkcja Privacy On Beam używa go domyślnie. Jeśli jednak chcesz użyć szumu rozkładu Gaussa, możesz podać parametr Params ze zmienną pbeam.GaussianNoise{}.

10. Podsumowanie

Brawo! Ćwiczenie z programowania dotyczące prywatności w Beam zostało ukończone. Wiesz już sporo o prywatności różnicowej i prywatności w Beam:

  • Aby zmienić PCollection w PrivatePCollection, zadzwoń pod numer MakePrivateFromStruct.
  • Użycie funkcji Count do obliczania różnych liczb prywatnych.
  • Użycie MeanPerKey do obliczania różnych sposobów prywatności.
  • Użycie funkcji SumPerKey do obliczania różnic prywatnych sum.
  • Obliczanie wielu statystyk za pomocą jednego obiektu PrivacySpec w jednym potoku.
  • (Opcjonalnie) Dostosowanie pola PrivacySpec i parametrów agregacji (CountParams, MeanParams, SumParams).

Prywatność w Beam umożliwia jednak znacznie więcej agregacji (np.kwantyle czy zliczanie odrębnych wartości). Więcej informacji na ten temat znajdziesz w repozytorium GitHub lub w godoc.

Jeśli masz czas, prześlij nam swoją opinię o ćwiczeniach z programowania, wypełniając ankietę.