Privacy on Beam을 사용한 비공개 통계 계산

집계 통계에서는 통계를 구성하는 데이터 소유자의 정보가 유출되지 않는다고 생각할 수 있습니다. 하지만 공격자는 여러 가지 방법으로 집계 통계의 데이터 세트에서 민감한 정보를 습득할 수 있습니다.

각 개인의 개인 정보 보호를 보장하기 위해 Privacy on Beam에서 개인 정보 차등 보호 집계를 사용하여 비공개 통계를 생성하는 방법을 알아봅니다. Privacy on Beam은 Apache Beam에서 작동하는 개인 정보 차등 보호 프레임워크입니다.

'비공개'의 의미는 무엇인가요?

이 Codelab 전반에서 '비공개'라는 단어는 데이터 속의 비공개 개인 정보를 유출하지 않는 방식으로 출력이 생성된다는 의미로 사용됩니다. 이 작업은 익명처리의 강력한 개인 정보 보호 개념인 개인 정보 차등 보호를 사용해 가능합니다. 익명처리는 사용자 개인 정보 보호를 위해 여러 사용자의 데이터를 집계하는 과정입니다. 모든 익명처리 방법에서 집계가 사용되지만, 모든 집계 방법에서 익명처리가 이루어지지는 않습니다. 그에 반해 개인 정보 차등 보호는 정보 유출과 개인 정보 보호와 관련해 측정 가능한 보증을 제공합니다.

개인 정보 차등 보호를 더 잘 이해하기 위해 간단한 예를 살펴보겠습니다.

이 막대 그래프는 특정한 날 저녁에 한 작은 레스토랑이 얼마나 분주한지를 나타낸 것입니다. 저녁 7시에는 손님이 많고, 새벽 1시에는 레스토랑이 완전히 비어 있습니다.

a43dbf3e2c6de596.png

유용해 보입니다.

여기에 주목할 점이 있습니다. 새 손님이 오면 이 사실이 막대 그래프에 바로 표시됩니다. 차트를 보겠습니다. 새 손님이 있으며 거의 새벽 1시에 왔다는 것이 명확히 드러납니다.

bda96729e700a9dd.png

이는 개인 정보 보호 관점에서는 좋지가 않습니다. 완전히 익명처리된 통계에서는 개별 기여도가 드러나서는 안 됩니다. 이 두 차트를 나란히 두면 더 분명합니다. 주황색 막대 그래프에 새벽 1시에 들어온 추가 손님이 한 명 있음을 알 수 있습니다.

d562ddf799288894.png

다시 한번 말하지만 그다지 좋지 않습니다. 어떻게 해야 할까요?

임의 노이즈를 추가하여 막대 그래프의 정확성을 떨어뜨려 보겠습니다.

아래의 두 막대 그래프를 살펴보세요. 두 그래프는 완전히 정확하지는 않지만 여전히 유용하며 개별 기여도를 드러내지 않습니다. 좋습니다.

838a0293cd4fcfe3.gif

개인 정보 차등 보호는 적절한 양의 임의 노이즈를 추가하여 개별 기여도를 마스크하는 것입니다.

지금 이 분석은 지나치게 단순화된 형태입니다. 올바른 개인 정보 차등 보호 구현 시에는 더 많은 요소가 관련되고 전혀 예상치 못한 구현 세부 사항도 발생합니다. 암호화와 마찬가지로 개인 정보 차등 보호를 자체적으로 구현하는 것은 좋은 생각이 아닐 수 있습니다. 자체 솔루션을 구현하는 대신 Privacy on Beam을 사용하면 됩니다. 자체적으로 개인 정보 차등 보호를 실행하지 마세요.

이 Codelab에서는 Privacy on Beam을 사용하여 개인 정보 차등 보호 분석을 실행하는 방법을 설명합니다.

이 문서에서 모든 관련 코드와 그래프를 찾을 수 있으므로 Codelab을 따르기 위해 Beam에서 Privacy를 다운로드 할 필요가 없습니다. 그러나 코드로 플레이하기 위해 다운로드하거나, 직접 실행하거나 나중에 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/ 디렉터리에 있습니다.

이 Codelab 및 데이터 세트의 코드는 privacy-on-beam/codelab/ 디렉터리에 있습니다.

또한 컴퓨터에 Bazel을 설치해야 합니다. Bazel 웹사이트에서 사용 중인 운영체제에 관한 설치 안내를 찾으세요.

여러분이 레스토랑 주인이고 레스토랑에 관한 일부 통계를 공유(예: 붐비는 방문 시간 공개)하려고 한다고 가정해 보겠습니다. 다행히 개인 정보 차등 보호와 익명처리를 알고 있으므로 개별 방문객의 정보를 유출하지 않는 방식으로 공유하고자 합니다.

이 예의 코드는 codelab/count.go에 있습니다.

먼저 특정 월요일의 레스토랑 방문 기록이 담긴 모의 데이터 세트를 로드해 보겠습니다. 이에 관한 코드는 이 Codelab의 목적을 감안하면 흥미롭지 않지만 codelab/main.go, codelab/utils.gocodelab/visit.go에서 관련 코드를 확인할 수 있습니다.

방문객 ID

입장 시간

체류 시간(분)

지출 금액(유로)

1

오전 9:30:00

26

24

2

오전 11:54:00

53

17

3

오후 1:05:00

81

33

먼저 Beam을 사용하여 아래 코드 샘플로 레스토랑 방문 시간을 비공개 막대 그래프로 생성해 보겠습니다. Scope는 파이프라인을 표현한 것이며, 데이터에 관한 새로운 연산이 Scope에 각각 추가됩니다. CountVisitsPerHourScope와 방문 모음(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()
}

이를 통해 현재 디렉터리에 멋진 막대 그래프가 count.png 형태로 생성(bazel run codelab -- --example="count" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/count.csv --output_chart_file=$(pwd)/count.png 실행)되었습니다.

a179766795d4e64a.png

다음 단계는 파이프라인과 막대 그래프를 비공개로 변환하는 것입니다. 이를 위해 다음과 같이 합니다.

먼저 PCollection<V>에서 MakePrivateFromStruct를 호출하여 PrivatePCollection<V>을 가져옵니다. 입력 PCollection은 구조체 모음이어야 합니다. PrivacySpecidFieldPathMakePrivateFromStruct에 관한 입력으로 입력해야 합니다.

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

PrivacySpec은 데이터 익명처리에 사용할 개인 정보 차등 보호 매개변수(epsilon 및 delta)를 보유한 구조체입니다. 지금은 이 두 매개변수에 관해 걱정하지 않아도 됩니다. 자세한 내용을 알고 싶다면 이후 선택적 섹션을 참고하세요.

idFieldPath는 구조체(이 예에서는 Visit) 내의 사용자 식별자 필드 경로입니다. 여기에서 방문객의 사용자 식별자는 VisitVisitorID 필드입니다.

그런 다음 stats.Count() 대신 pbeam.Count()를 호출합니다. pbeam.Count()는 출력 정확성에 영향을 주는 MaxValue 같은 매개변수를 보유한 CountParams 구조체를 입력으로 취합니다.

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

축하합니다. 첫 번째 개인 정보 차등 보호 통계를 계산했습니다.

코드를 실행할 때 표시되는 막대 그래프는 이 그래프와 다를 수 있습니다. 괜찮습니다. 개인 정보 차등 보호에 적용된 노이즈 때문에 코드를 실행할 때마다 다른 막대 그래프가 표시됩니다. 하지만 앞서 얻은 원래 비공개 막대 그래프와 거의 비슷합니다.

개인 정보 보호를 보장하려면 파이프라인을 여러 번 다시 실행(예: 더 보기 좋은 막대 그래프를 얻기 위한 목적)하지 않는 것이 중요합니다. 파이프라인을 다시 실행해서는 안 되는 이유는 '여러 통계 계산' 섹션에 설명되어 있습니다.

이전 섹션에서 일부 파티션(즉, 시간)에 관한 모든 방문(데이터)이 누락되었음을 알아챘을 것입니다.

d7fbc5d86d91e54a.png

이는 파티션 선택 및 임곗값 설정 때문입니다. 출력 파티션의 존재 여부가 사용자 데이터 자체의 영향을 받는 경우 이는 개인 정보 차등 보호를 보장하기 위한 중요한 단계입니다. 이 사례에서는 출력에 파티션이 있는 것만으로 데이터의 개별 사용자 존재가 유출될 수 있습니다(이 사례가 개인 정보 보호에 위반되는 이유는 이 블로그 게시물 참고). 이를 방지하기 위해 Privacy on Beam은 사용자 수가 충분히 있는 파티션만 유지합니다.

출력 파티션 목록이 비공개 사용자 데이터의 영향을 받지 않는 경우(즉, 목록이 공개 정보인 경우) 이 파티션 선택 단계가 필요하지 않습니다. 사실상 지금의 레스토랑 예가 이에 해당합니다. 레스토랑의 영업시간(9시~21시)을 알고 있기 때문입니다.

이 예의 코드는 codelab/public_partitions.go에 있습니다.

간단히 9~21시(경계 불포함)로 구성된 PCollection을 만들어 CountParamsPublicPartitions 필드에 입력해 보겠습니다.

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
}

참고로, 위 경우처럼 공개 파티션과 라플라스 노이즈(기본값)를 사용하는 경우 delta를 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이 나와 있습니다.

공개 파티션을 사용하면 더 많은 파티션을 유지할 수 있고, 파티션 선택 시 개인 정보 예산(privacy budget, epsilon 및 delta)을 사용하지 않아 공개 파티션을 사용하지 않을 때보다 각 파티션에 1.5배 정도 더 많은 노이즈가 추가됩니다. 이전 실행과 비교해 원시 개수와 비공개 개수의 차이가 거의 없는 이유도 이 때문입니다.

공개 파티션 사용 시 염두에 두어야 할 두 가지 중요 사항이 있습니다.

  1. 원시 데이터에서 파티션 목록을 파생할 때 주의하세요. 이 작업을 개인 정보 차등 보호 방식으로 진행하지 않으면(예: 단순히 사용자 데이터의 모든 파티션 목록을 읽어옴) 파이프라인에서 더 이상 개인 정보 차등 보호를 보장하지 않습니다. 개인 정보 차등 보호 방식으로 진행하는 방법은 아래 고급 섹션을 참고하세요.
  2. 일부 공개 파티션에 데이터(예: 방문객)가 없는 경우 개인 정보 차등 보호를 유지하기 위해 그러한 파티션에도 노이즈가 적용됩니다. 예를 들어 (9~21시간 대신) 0~24시간을 사용할 경우 모든 시간에 노이즈가 적용되어 방문객이 없더라도 방문객이 표시될 수 있습니다.

(고급) 데이터에서 파티션 파생

동일한 파이프라인에서 동일한 비공개 출력 파티션 목록을 사용해 여러 집계를 실행하는 경우, SelectPartitions()을 사용하고 파티션을 각 집계에 PublicPartition 입력으로 제공하면 파티션 목록을 한 번에 파생할 수 있습니다. 이 방식은 개인 정보 보호 측면에서도 안전할 뿐만 아니라, 전체 파이프라인에서 파티션 선택에 비공개 예산을 한 번만 사용하므로 노이즈를 줄일 수도 있습니다.

지금까지 개인 정보 차등 보호 방식으로 개수를 계산하는 방법을 알아보았습니다. 이제는 평균을 계산하는 방법을 살펴보겠습니다. 더 구체적으로 말하자면 이제는 방문객의 평균 체류 시간을 계산할 것입니다.

이 예의 코드는 codelab/mean.go에 있습니다.

일반적으로 체류 시간의 비공개 평균을 계산할 경우에는 방문객의 유입 PCollectionPCollection<K,V>로 변환하는 사전 처리 단계에서 stats.MeanPerKey()를 사용합니다. 여기서 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
}

이를 통해 현재 디렉터리에 멋진 막대 그래프가 mean.png 형태로 생성(bazel run codelab -- --example="mean" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/mean.csv --output_chart_file=$(pwd)/mean.png 실행)되었습니다.

bc2df28bf94b3721.png

이를 개인 정보 차등 보호로 만들기 위해 다시 PCollectionPrivatePCollection으로 변환하고 stats.MeanPerKey()pbeam.MeanPerKey()로 바꿉니다. Count처럼 MeanParams에는 정확성에 영향을 주는 MinValueMaxValue 같은 일부 매개변수가 있습니다. MinValueMaxValue는 각 사용자가 각 키에 미치는 기여도와 관련해 정해둔 한계를 나타냅니다.

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

다시 말씀드리지만 이는 개인 정보 차등 보호 연산이기 때문에 개수 계산처럼 실행할 때마다 다른 결과가 발생합니다. 하지만 개인 정보 차등 보호로 계산한 체류 시간이 실제 결과와 크게 동떨어지지 않음을 알 수 있습니다.

살펴볼 수 있는 또 다른 흥미로운 통계는 하루 동안의 시간별 수익입니다.

이 예의 코드는 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
}

이를 통해 현재 디렉터리에 멋진 막대 그래프가 sum.png 형태로 생성(bazel run codelab -- --example="sum" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/sum.csv --output_chart_file=$(pwd)/sum.png 실행)되었습니다.

548619173fad0c9a.png

이를 개인 정보 차등 보호로 만들기 위해 다시 PCollectionPrivatePCollection으로 변환하고 stats.SumPerKey()pbeam.SumPerKey()로 바꿉니다. CountMeanPerKey처럼 정확성에 영향을 주는 MinValueMaxValue 같은 일부 매개변수를 보유한 SumParams가 있습니다.

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

이 경우 MinValueMaxValue는 각 방문객이 지출한 금액에 설정한 한계를 나타냅니다. 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

다시 말씀드리지만 이는 개인 정보 차등 보호 연산이기 때문에 개수와 평균처럼 실행할 때마다 다른 결과가 발생합니다. 하지만 개인 정보 차등 보호 결과가 시간당 실제 수익에 거의 근접함을 알 수 있습니다.

대부분의 경우 동일한 기본 데이터로 여러 통계를 계산하고 싶어할 것입니다. 여기서 개수, 평균, 총계로 한 것처럼 말입니다. 이 경우 일반적으로 단일 Beam 파이프라인과 단일 바이너리에서 하는 것이 더 깔끔하고 쉽습니다. Privacy on Beam에서도 이 작업을 할 수 있습니다. 단일 파이프라인을 작성하여 변환과 계산을 실행하고 전체 파이프라인에 단일 PrivacySpec을 사용하면 됩니다.

이 작업에는 단일 PrivacySpec을 사용하는 것이 더 편할 뿐만 아니라 개인 정보 보호 관점에서도 더 좋습니다. PrivacySpec에 제공한 epsilon 및 delta 매개변수를 기억한다면 이는 일종의 개인 정보 보호 예산입니다. 기본 데이터의 사용자 개인 정보 보호가 누출된 정도를 측정한 값입니다.

개인 정보 보호 예산에 관해 기억할 중요한 점은 축적된다는 것입니다. 즉, 특정 엡실론(ε)과 델타(δ)로 파이프라인을 한 번에 실행하면 (ε,δ) 예산을 쓰는 것입니다. 한 번 더 실행하면 (2ε, 2δ)의 총 예산을 쓰는 것입니다. 마찬가지로 (ε,δ)의 PrivacySpec(및 연속적인 개인 정보 보호 예산)으로 여러 통계를 계산하면 (2ε, 2δ)의 총 예산을 쓰게 됩니다. 이는 개인 정보 보호 보장이 떨어진다는 의미입니다.

이를 방지하려면 동일한 기본 데이터로 여러 통계를 계산하고자 할 때는 사용할 총 예산에 단일 PrivacySpec을 사용해야 합니다. 그런 다음 집계마다 쓸 epsilon과 delta를 지정해야 합니다. 이렇게 하면 결국 전체적인 개인 정보 보호가 동일하게 보장됩니다. 그런데 특정 집계의 epsilon과 delta가 높을수록 정확성도 높아집니다.

실제 작동을 보려면 이전에 단일 파이프라인에서 개별적으로 계산한 세 가지 통계(개수, 평균, 합계)를 계산하면 됩니다.

이 예의 코드는 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{
        // 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{
        // 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{
        // 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
}

이 Codelab에는 상당수의 매개변수(epsilon, delta, maxPartitionsContributed 등)가 언급되어 있습니다. 이러한 매개변수는 대략적으로 두 개의 카테고리 즉, 개인 정보 보호 매개변수와 유용성 매개변수로 나눌 수 있습니다.

개인 정보 보호 매개변수

epsilon과 delta는 개인 정보 차등 보호를 통한 개인 정보 보호를 수량화하는 매개변수입니다. 더 정확하게는 epsilon과 delta는 잠재 공격자가 익명처리된 출력을 보고 기본 데이터 정보를 얼마만큼 얻는지 측정한 값입니다. epsilon과 delta가 높을수록 공격자가 기본 데이터 정보를 더 많이 얻게 되므로 이는 개인 정보 보호에 위험이 됩니다.

반면에 epsilon과 delta가 낮을수록 익명처리할 출력에 더 많은 노이즈를 추가해야 하고 파티션을 익명처리된 출력에 유지하기 위해 각 파티션에 더 많은 순 사용자가 있어야 합니다. 따라서 여기에는 사용성과 개인 정보 보호 간에 충돌이 생깁니다.

Beam on Privacy에서 PrivacySpec에 총 개인 정보 보호 예산을 지정할 경우 익명처리된 출력에서 원하는 개인 정보 보호를 보장받을 수 있을지 고민해야 합니다. 주의: 개인 정보 보호를 보장받으려면 각 집계에 별도의 PrivacySpec을 사용하거나 파이프라인을 여러 번 실행하여 예산을 과도하게 사용하지 않는 방법에 관한 Codelab 지침을 따라야 합니다.

개인 정보 차등 보호와 개인 정보 보호 매개변수 의미에 관한 자세한 내용은 문서를 참고하세요.

유용성 매개변수

Privacy on Beam 사용 방법의 지침을 올바로 따르는 한 개인 정보 보호 보장에는 영향을 주지 않지만, 정확성에 영향을 주어 결과적으로 출력의 유용성에 영향을 미치는 매개변수입니다. 이 매개변수는 각 집계의 Params 구조체(예: CountParams, SumParams 등)에 제공되고, 추가되는 노이즈를 조정하는 데 사용됩니다.

Params에 제공되고 모든 집계에 적용 가능한 유용성 매개변수는 MaxPartitionsContributed입니다. 파티션은 Privacy On Beam 집계 연산(예: Count, SumPerKey 등)에서 출력된 PCollection의 키입니다. 따라서 MaxPartitionsContributed는 사용자가 출력에 기여할 수 있는 키 값 개수의 한계를 설정합니다. 사용자가 기본 데이터에서 MaxPartitionsContributed 키보다 더 많이 기여하면 사용자가 정확히 MaxPartitionsContributed 키에 기여하도록 기여가 일부 삭제됩니다.

MaxPartitionsContributed와 유사하게 대부분의 집계에는 MaxContributionsPerPartition 매개변수가 있습니다. 이 매개변수는 Params 구조체에 제공되고 집계마다 별도의 값이 있을 수 있습니다. MaxPartitionsContributed와 달리 MaxContributionsPerPartition는 각 키에 관한 사용자의 기여의 한계를 설정합니다. 즉, 사용자는 각 키에 MaxContributionsPerPartition 값만 기여할 수 있습니다.

출력에 추가되는 노이즈는 MaxPartitionsContributedMaxContributionsPerPartition에 의해 조정됩니다. 따라서 여기에 충돌이 발생합니다. 즉, MaxPartitionsContributedMaxContributionsPerPartition가 모두 크면 데이터가 더 많이 유지되지만 결국 노이즈가 더 많은 결과가 발생합니다.

일부 집계에는 MinValueMaxValue가 필요합니다. 이 매개변수는 각 사용자 기여에 관한 경계를 설정합니다. 사용자가 MinValue보다 낮은 값을 기여하면 그 값이 MinValue로 고정됩니다. 마찬가지로 사용자가 MaxValue보다 큰 값을 기여하면 그 값이 MaxValue로 고정됩니다. 즉, 원래 값을 더 많이 유지하려면 더 큰 한계를 지정해야 합니다. MaxPartitionsContributedMaxContributionsPerPartition과 마찬가지로 노이즈는 한계 크기에 따라 조정됩니다. 따라서 한계가 클수록 데이터가 더 많이 유지되지만 결국 노이즈가 더 많은 결과가 발생합니다.

마지막으로 설명할 매개변수는 NoiseKind입니다. Privacy On Beam에서는 GaussianNoiseLaplaceNoise라는 2가지 노이즈 메커니즘이 지원됩니다. 둘 다 장단점이 있지만 라플라스 분포가 낮은 기여도 한계로 사용성이 더 낫기 때문에 Privacy On Beam에서는 기본적으로 라플라스 분포를 사용합니다. 하지만 가우스 분포 노이즈를 사용하려면 Paramspbeam.GaussianNoise{} 변수에 제공하면 됩니다.

잘하셨습니다. Privacy on Beam Codelab을 완료했습니다. 개인 정보 차등 보호와 Privacy on Beam과 관련해 다음과 같은 다양한 내용을 살펴봤습니다.

  • MakePrivateFromStruct를 호출하여 PCollectionPrivatePCollection로 변환.
  • Count를 사용하여 개인 정보 차등 보호 개수 계산.
  • MeanPerKey를 사용하여 개인 정보 차등 보호 평균 계산.
  • SumPerKey를 사용하여 개인 정보 차등 보호 총계 계산.
  • 단일 파이프라인에서 단일 PrivacySpec으로 여러 통계 계산.
  • (선택사항) PrivacySpec 및 집계 매개변수(CountParams, MeanParams, SumParams) 맞춤설정.

하지만 이 외에도 Privacy on Beam에서 가능한(예 : 분위수, 고유 한 값 계산) 집계가 더 많이 있습니다. GitHub 저장소 또는 godoc에서 그에 관해 자세히 살펴볼 수 있습니다.

시간이 된다면 설문조사를 작성해 Codelab에 관한 의견을 보내주세요.