محاسبه آمار خصوصی با حریم خصوصی در پرتو

1. مقدمه

ممکن است فکر کنید که آمار انبوه هیچ اطلاعاتی در مورد افرادی که داده‌های آنها از آمار تشکیل شده است، درز نمی‌کند. با این حال، راه‌های زیادی وجود دارد که مهاجم می‌تواند اطلاعات حساس افراد در یک مجموعه داده را از یک آمار مجموع بیاموزد.

برای محافظت از حریم خصوصی افراد، نحوه تولید آمار خصوصی با استفاده از تجمیع خصوصی متفاوت از Privacy on Beam را یاد خواهید گرفت. Privacy on Beam یک چارچوب حریم خصوصی متفاوت است که با Apache Beam کار می کند.

منظور ما از "خصوصی" چیست؟

هنگام استفاده از کلمه "خصوصی" در سراسر این Codelab، منظور ما این است که خروجی به گونه ای تولید می شود که هیچ اطلاعات خصوصی در مورد افراد موجود در داده ها به بیرون درز نمی کند. ما می‌توانیم این کار را با استفاده از حریم خصوصی تفاضلی، یک مفهوم قوی حریم خصوصی از ناشناس‌سازی انجام دهیم. ناشناس سازی فرآیند جمع آوری داده ها بین چندین کاربر برای محافظت از حریم خصوصی کاربر است. همه روش‌های ناشناس‌سازی از تجمیع استفاده می‌کنند، اما همه روش‌های تجمیع به بی‌نام‌سازی نمی‌رسند. از سوی دیگر، حریم خصوصی متفاوت، تضمین های قابل اندازه گیری در مورد نشت اطلاعات و حریم خصوصی ارائه می دهد.

2. بررسی اجمالی حریم خصوصی

برای درک بهتر حریم خصوصی دیفرانسیل، اجازه دهید به یک مثال ساده نگاه کنیم.

این نمودار میله ای شلوغی یک رستوران کوچک را در یک عصر خاص نشان می دهد. تعداد زیادی مهمان ساعت 7 بعدازظهر می آیند و رستوران در ساعت 1 صبح کاملاً خالی است:

a43dbf3e2c6de596.png

این مفید به نظر می رسد!

گرفتاری وجود دارد. هنگامی که یک مهمان جدید وارد می شود ، این واقعیت بلافاصله توسط نمودار میله ای آشکار می شود. به نمودار نگاه کنید: واضح است که یک مهمان جدید وجود دارد و این مهمان تقریباً ساعت 1 صبح آمده است:

bda96729e700a9dd.png

این از منظر حریم خصوصی عالی نیست. یک آمار واقعاً ناشناس نباید مشارکت فردی را نشان دهد. قرار دادن این دو نمودار در کنار هم این موضوع را آشکارتر می کند: نمودار نواری نارنجی یک مهمان اضافی دارد که در ساعت 1 بامداد وارد شده است:

d562ddf799288894.png

باز هم، این عالی نیست. چه کار کنیم؟

با اضافه کردن نویز تصادفی، نمودارهای میله ای را کمی دقیق تر می کنیم!

به دو نمودار میله ای زیر نگاه کنید. اگرچه کاملاً دقیق نیستند، اما هنوز مفید هستند و مشارکت‌های فردی را فاش نمی‌کنند. خوب!

838a0293cd4fcfe3.gif

حریم خصوصی دیفرانسیل اضافه کردن مقدار مناسبی از نویز تصادفی برای پنهان کردن مشارکت های فردی است .

تحلیل ما تا حدودی بیش از حد ساده شده بود. اجرای صحیح حریم خصوصی دیفرانسیل بیشتر دخیل است و دارای تعدادی ظرافت های اجرایی کاملا غیرمنتظره است. مشابه رمزنگاری، ایجاد پیاده‌سازی شخصی از حریم خصوصی متفاوت ممکن است ایده خوبی نباشد. می توانید به جای پیاده سازی راه حل خود از Privacy on Beam استفاده کنید. حریم خصوصی دیفرانسیل خود را رول نکنید!

در این کد لبه، نحوه انجام تجزیه و تحلیل خصوصی متفاوت با استفاده از Privacy on Beam را نشان خواهیم داد.

3. دانلود Privacy on Beam

برای اینکه بتوانید لبه کد را دنبال کنید نیازی به دانلود Privacy on 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 پیدا کنید.

4. محاسبه بازدید در ساعت

تصور کنید که صاحب رستوران هستید و می خواهید آماری از رستوران خود را به اشتراک بگذارید، مانند افشای زمان های بازدید محبوب. خوشبختانه، شما در مورد حفظ حریم خصوصی و ناشناس بودن اطلاعات دارید، بنابراین می‌خواهید این کار را به گونه‌ای انجام دهید که اطلاعات مربوط به هیچ بازدیدکننده‌ای به بیرون درز نکند.

کد این مثال در codelab/count.go است.

بیایید با بارگیری یک مجموعه داده ساختگی شامل بازدید از رستوران شما در یک دوشنبه خاص شروع کنیم. کد مربوط به این مورد برای اهداف این codelab جالب نیست، اما می‌توانید کد آن را در codelab/main.go ، codelab/utils.go و codelab/visit.go بررسی کنید.

شناسه بازدید کننده

زمان وارد شد

زمان صرف شده (دقیقه)

پول خرج شده (یورو)

1

9:30:00 صبح

26

24

2

11:54:00 صبح

53

17

3

1: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 ساختاری است که پارامترهای حریم خصوصی دیفرانسیل (epsilon و delta) را که می‌خواهیم برای ناشناس کردن داده‌ها استفاده کنیم را در خود نگه می‌دارد. (در حال حاضر لازم نیست نگران آنها باشید، اگر مایلید در مورد آنها بیشتر بدانید، بعداً یک بخش اختیاری داریم.)

idFieldPath مسیر فیلد شناسه کاربر در ساختار است ( در مورد ما Visit ). در اینجا، شناسه کاربری بازدیدکنندگان، فیلد VisitorID Visit است.

سپس به جای stats، 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 است.

ما به سادگی یک مجموعه PC از ساعت های بین 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
}

توجه داشته باشید که اگر از پارتیشن‌های عمومی و نویز Laplace (پیش‌فرض) استفاده می‌کنید، می‌توانید دلتا را روی 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. محاسبه آمار چندگانه

بیشتر اوقات، ممکن است علاقه مند به محاسبه آمارهای متعدد بر روی یک داده اساسی باشید، مشابه آنچه که با تعداد، میانگین و مجموع انجام داده اید. معمولاً انجام این کار در یک خط لوله پرتو و در یک باینری واحد تمیزتر و آسانتر است. می توانید این کار را با Privacy on Beam نیز انجام دهید. شما می توانید یک خط لوله برای اجرای تبدیل ها و محاسبات خود بنویسید و از یک PrivacySpec برای کل خط لوله استفاده کنید.

نه تنها انجام این کار با یک PrivacySpec راحت تر است، بلکه از نظر حریم خصوصی نیز بهتر است. اگر پارامترهای epsilon و delta را که ما به 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. (اختیاری) تنظیم پارامترهای Differential Privacy

شما چند پارامتر ذکر شده در این کد را مشاهده کرده اید: epsilon، delta، maxPartitionsContributed، و غیره. ما تقریباً می توانیم آنها را به دو دسته تقسیم کنیم: پارامترهای خصوصی و پارامترهای کاربردی.

پارامترهای حریم خصوصی

اپسیلون و دلتا پارامترهایی هستند که حریم خصوصی را که با استفاده از حریم خصوصی دیفرانسیل ارائه می کنیم، کمیت می کنند. به‌طور دقیق‌تر، اپسیلون و دلتا معیاری هستند که نشان می‌دهد مهاجم بالقوه چه مقدار اطلاعات در مورد داده‌های زیربنایی با نگاه کردن به خروجی ناشناس به دست می‌آورد. هرچه اپسیلون و دلتا بالاتر باشد، مهاجم اطلاعات بیشتری در مورد داده های زیربنایی به دست می آورد که خطر حفظ حریم خصوصی است.

از طرف دیگر، هرچه اپسیلون و دلتا کمتر باشد، نویز بیشتری باید به خروجی اضافه کنید تا ناشناس باشد، و تعداد کاربران منحصربه‌فرد بیشتری را در هر پارتیشن داشته باشید تا آن پارتیشن را در خروجی ناشناس نگه دارید. بنابراین، یک معاوضه بین ابزار و حریم خصوصی در اینجا وجود دارد.

در Privacy on Beam، وقتی کل بودجه حریم خصوصی را در PrivacySpec مشخص می‌کنید، باید نگران تضمین‌های حفظ حریم خصوصی باشید که در خروجی ناشناس خود می‌خواهید. هشدار این است که اگر می‌خواهید ضمانت‌های حفظ حریم خصوصی شما حفظ شود، باید توصیه‌های موجود در این نرم‌افزار را در مورد عدم استفاده بیش از حد از بودجه خود با داشتن PrivacySpec جداگانه برای هر تجمیع یا اجرای چندین بار خط لوله دنبال کنید.

برای اطلاعات بیشتر در مورد Differential Privacy و معنای پارامترهای حریم خصوصی، می توانید نگاهی به ادبیات داشته باشید.

پارامترهای سودمند

اینها پارامترهایی هستند که بر تضمین حریم خصوصی تأثیر نمی‌گذارند (تا زمانی که توصیه‌های مربوط به نحوه استفاده از Privacy on Beam به درستی رعایت شود) اما بر دقت و در نتیجه سودمندی خروجی تأثیر می‌گذارد. آنها در ساختارهای Params هر تجمع ارائه می شوند، به عنوان مثال CountParams ، SumParams ، و غیره. این پارامترها برای مقیاس کردن نویز اضافه شده استفاده می شوند.

یکی از پارامترهای کاربردی ارائه شده در Params و قابل استفاده برای همه ادغام ها MaxPartitionsContributed است. یک پارتیشن مربوط به یک کلید از PCCollection است که توسط یک عملیات جمع‌آوری 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 . هر دو مزایا و معایب خود را دارند، اما توزیع Laplace با محدودیت‌های مشارکت کم، کاربرد بهتری را ارائه می‌دهد، به همین دلیل است که Privacy On Beam به طور پیش‌فرض از آن استفاده می‌کند. با این حال، اگر می‌خواهید از نویز توزیع گاوسی استفاده کنید، می‌توانید به Params یک متغیر pbeam.GaussianNoise{} ارائه دهید.

10. خلاصه

کار عالی! شما Privacy on Beam codelab را تمام کردید. شما در مورد حریم خصوصی دیفرانسیل و Privacy on Beam چیزهای زیادی یاد گرفتید:

  • با تماس با MakePrivateFromStruct PCollection خود را به PrivatePCollection تبدیل کنید.
  • استفاده از Count برای محاسبه شمارش خصوصی متفاوت.
  • استفاده از MeanPerKey برای محاسبه میانگین خصوصی متفاوت.
  • استفاده از SumPerKey برای محاسبه مجموع خصوصی متفاوت.
  • محاسبه آمارهای متعدد با یک PrivacySpec در یک خط لوله واحد.
  • (اختیاری) سفارشی کردن PrivacySpec و پارامترهای تجمع ( CountParams, MeanParams, SumParams ).

اما، تعداد زیادی تجمیع بیشتر وجود دارد (مثلاً چندک ها، شمارش مقادیر متمایز) که می توانید با Privacy on Beam انجام دهید! می‌توانید در مخزن GitHub یا godoc درباره آنها اطلاعات بیشتری کسب کنید.

اگر وقت دارید، لطفاً با پر کردن یک نظرسنجی ، بازخورد خود را در مورد آزمایشگاه کد به ما بدهید.