Вычисление частной статистики с конфиденциальностью на Beam

1. Введение

Можно подумать, что сводная статистика не раскрывает никакой информации об отдельных лицах, на основе которых она составлена. Однако существует множество способов, с помощью которых злоумышленник может получить конфиденциальную информацию об отдельных лицах в наборе данных из сводной статистики.

Для защиты конфиденциальности отдельных лиц вы узнаете, как создавать конфиденциальную статистику с использованием агрегированных данных с дифференциальной конфиденциальностью от Privacy on Beam. Privacy on Beam — это платформа для обеспечения дифференциальной конфиденциальности, работающая с Apache Beam .

Что мы подразумеваем под словом «частный»?

В данном примере использования слова «приватный» мы подразумеваем, что результат создается таким образом, чтобы не раскрывать никакой личной информации об отдельных лицах, содержащихся в данных. Этого можно добиться с помощью дифференциальной конфиденциальности — концепции анонимизации, обеспечивающей высокую степень защиты конфиденциальности. Анонимизация — это процесс агрегирования данных от нескольких пользователей для защиты их конфиденциальности. Все методы анонимизации используют агрегирование, но не все методы агрегирования обеспечивают полную анонимизацию. Дифференциальная конфиденциальность, с другой стороны, предоставляет измеримые гарантии в отношении утечки информации и конфиденциальности.

2. Обзор дифференциальной конфиденциальности

Чтобы лучше понять дифференциальную конфиденциальность, рассмотрим простой пример.

Эта гистограмма показывает загруженность небольшого ресторана в один конкретный вечер. В 19:00 приходит много посетителей, а в 1:00 ночи ресторан совершенно пуст:

a43dbf3e2c6de596.png

Это выглядит полезным!

Есть один нюанс. Когда прибывает новый гость , этот факт немедленно отображается на гистограмме. Посмотрите на диаграмму: ясно, что появился новый гость, и что он прибыл примерно в час ночи.

bda96729e700a9dd.png

С точки зрения конфиденциальности это не очень хорошо. По-настоящему анонимизированная статистика не должна раскрывать индивидуальные данные. Если сравнить эти две диаграммы, это становится еще более очевидным: на оранжевой столбчатой ​​диаграмме появился еще один гость, прибывший примерно в час ночи.

d562ddf799288894.png

Опять же, это не очень хорошо. Что же нам делать?

Мы немного снизим точность столбчатых диаграмм, добавив случайный шум!

Посмотрите на две столбчатые диаграммы ниже. Хотя они и не совсем точны, они всё же полезны и не раскрывают индивидуальный вклад. Отлично!

838a0293cd4fcfe3.gif

Дифференциальная конфиденциальность — это добавление необходимого количества случайного шума для маскировки индивидуального вклада .

Наш анализ был несколько упрощен. Правильная реализация дифференциальной конфиденциальности — более сложная задача, сопряженная с рядом довольно неожиданных нюансов. Как и в криптографии, создание собственной реализации дифференциальной конфиденциальности может быть не лучшей идеей. Вместо реализации собственного решения можно использовать Privacy on Beam. Не стоит разрабатывать собственную реализацию дифференциальной конфиденциальности!

В этом практическом занятии мы покажем, как выполнить анализ с дифференциальной приватностью с помощью функции Privacy on Beam.

3. Загрузка Privacy on Beam

Для выполнения этого практического задания вам не нужно скачивать Privacy on Beam, поскольку весь необходимый код и графики можно найти в этом документе. Однако, если вы хотите скачать Privacy on Beam, чтобы поэкспериментировать с кодом, запустить его самостоятельно или использовать его позже, вы можете сделать это, следуя инструкциям ниже.

Обратите внимание, что данное практическое занятие предназначено для версии 1.1.0 библиотеки.

Сначала скачайте Privacy on Beam:

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

Или вы можете клонировать репозиторий на GitHub:

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

Сайт Privacy on Beam находится в корневом каталоге privacy-on-beam/ .

Код для этого практического занятия и набор данных находятся в каталоге privacy-on-beam/codelab/ .

Вам также необходимо установить Bazel на свой компьютер. Инструкции по установке для вашей операционной системы можно найти на веб-сайте Bazel .

4. Расчет количества посещений в час.

Представьте, что вы владелец ресторана и хотите поделиться некоторыми статистическими данными о своем заведении, например, указать наиболее популярное время посещения. К счастью, вы знаете о дифференциальной конфиденциальности и анонимизации, поэтому хотите сделать это таким образом, чтобы не допустить утечки информации об отдельных посетителях.

Код для этого примера находится в codelab/count.go .

Начнём с загрузки фиктивного набора данных, содержащего информацию о посещениях вашего ресторана в определённый понедельник. Код для этого не представляет интереса для целей данного практического занятия, но вы можете ознакомиться с ним в codelab/main.go , codelab/utils.go и codelab/visit.go .

Идентификатор посетителя

Введенное время

Затраченное время (мин)

Потраченные средства (евро)

1

9:30:00 утра

26

24

2

11:54:00 AM

53

17

3

13:05:00

81

33

Сначала вы создадите не приватную столбчатую диаграмму времени посещений вашего ресторана, используя Beam, как показано в приведенном ниже примере кода. Scope представляет собой конвейер обработки данных, и каждая новая операция, выполняемая с данными, добавляется в Scope . CountVisitsPerHour принимает Scope и коллекцию посещений, которая представлена ​​в Beam как PCollection . Она извлекает час каждого посещения, применяя функцию extractVisitHour к коллекции. Затем она подсчитывает количество посещений каждого часа и возвращает результат.

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

В результате выполнения bazel run codelab -- --example="count" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/count.csv --output_chart_file=$(pwd)/count.png в текущем каталоге будет получен файл count.png :

a179766795d4e64a.png

Следующий шаг — преобразовать ваш конвейер обработки данных и гистограмму в закрытый файл. Мы делаем это следующим образом.

Сначала вызовите MakePrivateFromStruct для PCollection<V> , чтобы получить объект PrivatePCollection<V> . Входной PCollection должен представлять собой коллекцию структур. Нам необходимо передать в качестве входных данных для метода MakePrivateFromStruct объект PrivacySpec и idFieldPath .

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

PrivacySpec is a struct that holds the differential privacy parameters (epsilon and delta) we want to use to anonymize the data. (You don't need to worry about them for now, we have an optional section later if you would like to learn more about those.)

idFieldPath — это путь к полю идентификатора пользователя внутри структуры (в нашем случае Visit ). Здесь идентификатором пользователя для посетителей является поле VisitorID структуры Visit .

Затем мы вызываем pbeam.Count() вместо stats.Count() , при этом pbeam.Count() принимает на вход структуру CountParams , содержащую такие параметры, как MaxValue , влияющие на точность выходных данных.

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

Аналогично, MaxPartitionsContributed ограничивает количество часов посещения, которые может указать пользователь. Мы ожидаем, что он будет посещать ресторан не чаще одного раза в день (или нам все равно, посещает ли он его несколько раз в течение дня), поэтому мы устанавливаем его значение равным 1. Подробнее об этих параметрах мы поговорим в необязательном разделе.

Параметр MaxValue ограничивает количество посещений одним пользователем, которое учитывается в подсчете. В данном конкретном случае подсчитываются часы посещений, и мы ожидаем, что пользователь посетит ресторан только один раз (или нам все равно, посетит ли он его несколько раз в час), поэтому мы устанавливаем этот параметр равным 1.

В итоге ваш код будет выглядеть так:

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
}

Мы видим аналогичную гистограмму ( count_dp.png ) для статистики дифференциальной приватности (предыдущая команда запускает как не приватный, так и приватный конвейеры):

d6a0ace1acd3c760.png

Поздравляем! Вы рассчитали свою первую статистику дифференциальной приватности!

Гистограмма, которую вы получите при запуске кода, может отличаться от этой. Это нормально. Из-за шума в дифференциальной конфиденциальности вы будете получать разные гистограммы при каждом запуске кода, но вы можете видеть, что они более или менее похожи на исходную гистограмму для неконфиденциальной среды, которую мы имели.

Обратите внимание, что для обеспечения конфиденциальности крайне важно не запускать конвейер обработки данных несколько раз (например, для получения более качественной гистограммы). Причина, по которой не следует повторно запускать конвейеры обработки данных, объяснена в разделе «Вычисление множественных статистических данных».

5. Использование общедоступных разделов

В предыдущем разделе вы могли заметить, что мы удалили все данные о посещениях для некоторых разделов, то есть для часов.

d7fbc5d86d91e54a.png

Это связано с выбором/пороговым значением разделов, важным шагом для обеспечения гарантий дифференцированной конфиденциальности, когда существование выходных разделов зависит от самих пользовательских данных. В этом случае само существование раздела в выходных данных может раскрыть информацию о наличии отдельного пользователя в данных (см. эту статью в блоге для объяснения того, почему это нарушает конфиденциальность). Чтобы предотвратить это, Privacy on Beam сохраняет только те разделы, в которых содержится достаточное количество пользователей.

Когда список выходных разделов не зависит от личных данных пользователя, то есть является общедоступной информацией, нам не нужен этот этап выбора разделов. Именно так обстоит дело в нашем примере с рестораном: мы знаем часы работы ресторана (с 9:00 до 21:00).

Код для этого примера находится в codelab/public_partitions.go .

Мы просто создадим коллекцию часов в промежутке с 9 до 21 (исключая этот период) и вставим её в поле PublicPartitions переменной 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
}

Обратите внимание, что при использовании общедоступных разделов и шума Лапласа (по умолчанию), как в приведенном выше примере, можно установить значение дельта равным 0.

Когда мы запускаем конвейер с публичными разделами (с помощью 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 "), мы получаем ( public_partitions_dp.png ):

7c950fbe99fec60a.png

Как видите, теперь мы сохранили разделы 9, 10 и 16, которые ранее были удалены без публичных разделов.

Использование общедоступных разделов не только позволяет хранить больше разделов, но и добавляет примерно вдвое меньше шума к каждому разделу по сравнению с вариантом без общедоступных разделов, поскольку не расходуется бюджет конфиденциальности, то есть эпсилон и дельта, на выбор разделов. Именно поэтому разница между исходными и приватными значениями немного меньше по сравнению с предыдущим запуском.

При использовании общедоступных разделов следует помнить о двух важных вещах:

  1. Будьте осторожны при получении списка разделов из необработанных данных: если вы не сделаете это способом с обеспечением дифференциальной конфиденциальности, например, просто прочитав список всех разделов в пользовательских данных, ваш конвейер обработки данных перестанет гарантировать дифференциальную конфиденциальность. См. раздел «Расширенные возможности» ниже, как это сделать способом с обеспечением дифференциальной конфиденциальности.
  2. Если для некоторых общедоступных разделов отсутствуют данные (например, о посещениях), к этим разделам будет применено шумовое ограничение для сохранения дифференциальной конфиденциальности. Например, если мы будем использовать часы от 0 до 24 (вместо от 9 до 21), все часы будут подвергнуты шумовому ограничению, и могут отображаться данные о посещениях, которых на самом деле нет.

(Продвинутый уровень) Выведение разделов из данных

Если вы выполняете несколько агрегаций с одним и тем же списком непубличных выходных разделов в одном и том же конвейере, вы можете получить список разделов один раз, используя SelectPartitions() и передавая разделы каждой агрегации в качестве входных данных PublicPartition . Это не только безопасно с точки зрения конфиденциальности, но и позволяет уменьшить информационный шум, поскольку бюджет конфиденциальности при выборе разделов используется только один раз для всего конвейера.

6. Расчет средней продолжительности пребывания.

Теперь, когда мы знаем, как подсчитывать данные с учетом дифференцированной приватности, давайте рассмотрим вычисление средних значений. В частности, мы вычислим среднюю продолжительность пребывания посетителей.

Код для этого примера находится в codelab/mean.go .

Обычно для вычисления среднего значения продолжительности пребывания без приватности мы используем stats.MeanPerKey() с этапом предварительной обработки, который преобразует входящий PCollection с данными о посещениях в объект PCollection<K,V> где K — время посещения в часах, а V — время, проведенное посетителем в ресторане.

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
}

В результате выполнения bazel run codelab -- --example="mean" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/mean.csv --output_chart_file=$(pwd)/mean.png в текущем каталоге будет получен файл mean.png :

bc2df28bf94b3721.png

Чтобы обеспечить дифференциальную приватность, мы снова преобразуем наш PCollection в PrivatePCollection и заменяем stats.MeanPerKey() на pbeam.MeanPerKey() . Аналогично Count , у нас есть MeanParams , которые содержат некоторые параметры, такие как MinValue и MaxValue , влияющие на точность. MinValue и MaxValue представляют собой границы, установленные для вклада каждого пользователя в каждый ключ.

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

В данном случае каждый ключ обозначает час, а значения — время, проведенное посетителями. Мы устанавливаем MinValue равным 0, поскольку не ожидаем, что посетители проведут в ресторане меньше 0 минут. MaxValue мы устанавливаем равным 60, что означает, что если посетитель проводит в ресторане более 60 минут, мы будем действовать так, как если бы он провел там 60 минут.

В итоге ваш код будет выглядеть так:

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
}

Мы видим аналогичную столбчатую диаграмму ( mean_dp.png ) для статистики дифференциальной приватности (предыдущая команда запускает как не приватный, так и приватный конвейеры):

e8ac6a9bf9792287.png

Опять же, как и в случае с подсчетом, поскольку это операция с дифференциальной приватностью, мы будем получать разные результаты при каждом ее запуске. Но вы можете видеть, что значения продолжительности пребывания с дифференциальной приватностью не сильно отличаются от фактического результата.

7. Расчет почасовой выручки.

Ещё один интересный статистический показатель, который мы могли бы рассмотреть, — это выручка в час за день.

Код для этого примера находится в codelab/sum.go .

Начнём с не приватной версии. После предварительной обработки нашего фиктивного набора данных мы можем создать PCollection<K,V> где K — время посещения, а V — сумма, потраченная посетителем в ресторане: Чтобы рассчитать доход за час без приватности, мы можем просто суммировать все потраченные посетителями деньги, вызвав 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
}

В результате выполнения bazel run codelab -- --example="sum" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/sum.csv --output_chart_file=$(pwd)/sum.png в текущем каталоге будет получена красивая столбчатая диаграмма в формате sum.png :

548619173fad0c9a.png

Чтобы обеспечить дифференциальную приватность, мы снова преобразуем наш PCollection в PrivatePCollection и заменяем stats.SumPerKey() на pbeam.SumPerKey() . Аналогично Count и MeanPerKey , у нас есть SumParams , которые содержат некоторые параметры, такие как MinValue и MaxValue , влияющие на точность.

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

В данном случае MinValue и MaxValue представляют собой границы суммы, которую тратит каждый посетитель. Мы устанавливаем MinValue равным 0, потому что не ожидаем, что посетители потратят в ресторане меньше 0 евро. MaxValue мы устанавливаем равным 40, что означает, что если посетитель потратит больше 40 евро, мы будем действовать так, как если бы этот пользователь потратил 40 евро.

В итоге код будет выглядеть так:

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
}

Мы видим аналогичную гистограмму ( sum_dp.png ) для статистики дифференциальной приватности (предыдущая команда запускает как не приватный, так и приватный конвейеры):

46c375e874f3e7c4.png

Опять же, как и в случае с подсчетом и средним значением, поскольку это операция с дифференциальной приватностью, мы будем получать разные результаты при каждом ее запуске. Но вы можете видеть, что результат с дифференциальной приватностью очень близок к фактической выручке в час.

8. Вычисление множественных статистических показателей

В большинстве случаев вас может интересовать вычисление нескольких статистических показателей для одних и тех же базовых данных, аналогично тому, как вы это делали с подсчетом, средним значением и суммой. Обычно это проще и удобнее сделать в рамках одного конвейера Beam и в одном бинарном файле. Это можно сделать и с помощью Privacy on Beam. Вы можете написать один конвейер для выполнения преобразований и вычислений и использовать один PrivacySpec для всего конвейера.

Использование единого PrivacySpec не только удобнее, но и обеспечивает более высокий уровень конфиденциальности. Если вы помните параметры эпсилон и дельта, которые мы передаем в PrivacySpec , они представляют собой так называемый бюджет конфиденциальности , который измеряет, насколько сильно происходит утечка конфиденциальной информации о пользователях из исходных данных.

Важно помнить, что бюджет конфиденциальности является аддитивным: если вы запускаете конвейер с определенным значением эпсилон ε и дельта δ один раз, вы расходуете бюджет (ε,δ). Если вы запустите его второй раз, вы израсходуете общий бюджет (2ε, 2δ). Аналогично, если вы вычисляете несколько статистических показателей с PrivacySpec (и, соответственно, бюджетом конфиденциальности) равным (ε,δ), вы израсходуете общий бюджет (2ε, 2δ). Это означает, что вы ухудшаете гарантии конфиденциальности.

Чтобы обойти это ограничение, при вычислении нескольких статистических показателей для одних и тех же базовых данных необходимо использовать один PrivacySpec с общим бюджетом, который вы хотите использовать. Затем вам нужно будет указать значения эпсилон и дельта, которые вы хотите использовать для каждой агрегации. В итоге вы получите одинаковую общую гарантию конфиденциальности; но чем выше значения эпсилон и дельта для конкретной агрегации, тем выше будет ее точность.

Чтобы увидеть это в действии, мы можем вычислить три статистических показателя (количество, среднее значение и сумму), которые мы вычисляли отдельно ранее, в рамках единого алгоритма.

Код для этого примера находится в codelab/multiple.go . Обратите внимание, как мы делим общий бюджет (ε,δ) поровну между тремя агрегациями:

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. (Необязательно) Настройка параметров дифференциальной конфиденциальности

В этом практическом занятии вы уже видели довольно много параметров: эпсилон, дельта, maxPartitionsContributed и т. д. Их можно условно разделить на две категории: параметры конфиденциальности и параметры полезности.

Параметры конфиденциальности

Эпсилон и дельта — это параметры, которые количественно оценивают уровень конфиденциальности, обеспечиваемый использованием дифференциальной конфиденциальности. Точнее, эпсилон и дельта — это показатели того, сколько информации потенциальный злоумышленник получает об исходных данных, анализируя анонимизированные выходные данные. Чем выше значения эпсилон и дельта, тем больше информации получает злоумышленник об исходных данных, что представляет собой риск для конфиденциальности.

С другой стороны, чем ниже значения эпсилон и дельта, тем больше шума необходимо добавить в выходные данные для обеспечения анонимности, и тем большее количество уникальных пользователей должно быть в каждом разделе, чтобы сохранить анонимность этого раздела в выходных данных. Таким образом, здесь возникает компромисс между полезностью и конфиденциальностью.

В Privacy on Beam вам необходимо позаботиться о гарантиях конфиденциальности, которые вы хотите обеспечить в анонимизированных выходных данных, когда вы указываете общий бюджет конфиденциальности в PrivacySpec . Однако, если вы хотите, чтобы ваши гарантии конфиденциальности были соблюдены, вам необходимо следовать рекомендациям из этого примера, чтобы не превышать бюджет, используя отдельный PrivacySpec для каждой агрегации или запуская конвейер несколько раз.

Для получения более подробной информации о дифференциальной конфиденциальности и значении параметров конфиденциальности вы можете ознакомиться с соответствующей литературой .

Параметры полезности

Эти параметры не влияют на гарантии конфиденциальности (при условии соблюдения рекомендаций по использованию Privacy on Beam), но влияют на точность и, следовательно, на полезность выходных данных. Они предоставляются в структурах Params каждой агрегации, например, CountParams , SumParams и т. д. Эти параметры используются для масштабирования добавляемого шума.

Вспомогательный параметр, предоставляемый в Params и применимый ко всем агрегациям, — это MaxPartitionsContributed . Раздел соответствует ключу PCollection, полученному в результате операции агрегации Privacy On Beam, например, Count , SumPerKey и т. д. Таким образом, MaxPartitionsContributed ограничивает количество различных значений ключей, к которым пользователь может добавить данные в выходных данных. Если пользователь добавляет данные к более чем MaxPartitionsContributed ключам в исходных данных, часть его добавлений будет отброшена, так что он будет добавлять данные ровно к MaxPartitionsContributed ключам.

Подобно параметру MaxPartitionsContributed , большинство агрегаций имеют параметр MaxContributionsPerPartition . Они предоставляются в структурах Params , и каждая агрегация может иметь для них отдельные значения. В отличие от MaxPartitionsContributed , MaxContributionsPerPartition ограничивает вклад пользователя для каждого ключа. Другими словами, пользователь может внести только значения MaxContributionsPerPartition для каждого ключа.

Добавляемый к выходным данным шум масштабируется с помощью MaxPartitionsContributed и MaxContributionsPerPartition , поэтому здесь есть компромисс: чем больше значения MaxPartitionsContributed и MaxContributionsPerPartition , тем больше данных сохраняется, но в итоге получается более зашумленный результат.

Для некоторых агрегаций требуются MinValue и MaxValue . Они определяют границы вклада каждого пользователя. Если пользователь вносит значение меньше MinValue , это значение будет ограничено значением MinValue . Аналогично, если пользователь вносит значение больше MaxValue , это значение будет ограничено значением MaxValue . Это означает, что для сохранения большего количества исходных значений необходимо указывать более широкие границы. Подобно MaxPartitionsContributed и MaxContributionsPerPartition , уровень шума масштабируется размером границ, поэтому более широкие границы означают сохранение большего количества данных, но в итоге вы получите более зашумленный результат.

Последний параметр, о котором мы поговорим, — это NoiseKind . В Privacy On Beam мы поддерживаем два разных механизма шумоподавления: GaussianNoise и LaplaceNoise . Оба имеют свои преимущества и недостатки, но распределение Лапласа обеспечивает лучшую полезность при низких границах вклада, поэтому Privacy On Beam использует его по умолчанию. Однако, если вы хотите использовать шум с гауссовым распределением, вы можете передать параметру Params переменную pbeam.GaussianNoise{} .

10. Резюме

Отличная работа! Вы завершили практическое занятие по Privacy on Beam. Вы много узнали о дифференциальной конфиденциальности и Privacy on Beam:

  • Преобразование вашей PCollection в коллекцию PrivatePCollection путем вызова функции MakePrivateFromStruct .
  • Использование функции Count для вычисления количества случаев дифференциальной приватности.
  • Использование MeanPerKey для вычисления средних значений с разной степенью приватности.
  • Использование SumPerKey для вычисления сумм с различной степенью конфиденциальности.
  • Вычисление множества статистических показателей с помощью одного PrivacySpec в рамках одного конвейера обработки данных.
  • (Необязательно) Настройка параметров PrivacySpec и параметров агрегирования ( CountParams, MeanParams, SumParams ).

Но с помощью Privacy on Beam можно выполнить гораздо больше агрегаций (например, квантили, подсчет уникальных значений)! Подробнее о них можно узнать в репозитории GitHub или в документации godoc .

Если у вас есть время, пожалуйста, оставьте свой отзыв о практическом занятии, заполнив опрос .