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

1. Введение

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

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

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

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

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

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

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

a43dbf3e2c6de596.png

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

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

bda96729e700a9dd.png

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

d562ddf799288894.png

Опять же, это не здорово. Что нам делать?

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

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

838a0293cd4fcfe3.gif

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

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

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

3. Загрузка конфиденциальности на 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

53

17

3

13:05:00

81

33

Сначала вы создадите частную гистограмму времени посещения вашего ресторана с помощью Beam в приведенном ниже примере кода. Scope — это представление конвейера, и каждая новая операция, которую мы выполняем с данными, добавляется в Scope . CountVisitsPerHour принимает Scope и коллекцию посещений, которая представлена ​​в виде PCollection в Beam. Он извлекает час каждого посещения, применяя к коллекции функцию 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 должен быть коллекцией структур. Нам нужно ввести PrivacySpec и idFieldPath в качестве входных данных для MakePrivateFromStruct .

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

PrivacySpec — это структура, содержащая дифференциальные параметры конфиденциальности (эпсилон и дельта), которые мы хотим использовать для анонимизации данных. (На данный момент вам не нужно беспокоиться о них, позже у нас есть дополнительный раздел, если вы захотите узнать о них больше.)

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 .

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

Вы видели довольно много параметров, упомянутых в этой кодовой лаборатории: epsilon, delta, 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. Вы много узнали о дифференциальной конфиденциальности и конфиденциальности на Beam:

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

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

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