การคำนวณสถิติส่วนตัวด้วยความเป็นส่วนตัวบนบีม

1. บทนำ

คุณอาจคิดว่าสถิติรวมไม่ได้รั่วไหลข้อมูลใดๆ เกี่ยวกับบุคคลที่มีข้อมูลที่ประกอบเป็นสถิติ อย่างไรก็ตาม ผู้โจมตีสามารถเรียนรู้ข้อมูลที่ละเอียดอ่อนเกี่ยวกับบุคคลในชุดข้อมูลจากสถิติรวมได้หลายวิธี

คุณจะได้เรียนรู้วิธีสร้างสถิติส่วนตัวโดยใช้การรวมข้อมูลแบบ Differential Privacy จาก Privacy on Beam เพื่อปกป้องความเป็นส่วนตัวของบุคคล ความเป็นส่วนตัวใน Beam คือเฟรมเวิร์กความเป็นส่วนตัวเชิงอนุพันธ์ที่ทำงานร่วมกับ Apache Beam

"ส่วนตัว" ในที่นี้หมายถึงอะไร

เมื่อใช้คำว่า "ส่วนตัว" ใน Codelab นี้ เราหมายถึงการสร้างเอาต์พุตในลักษณะที่ไม่รั่วไหลข้อมูลส่วนตัวเกี่ยวกับบุคคลในข้อมูล เราทำได้โดยใช้ Differential Privacy ซึ่งเป็นแนวคิดด้านความเป็นส่วนตัวที่เข้มงวดในการลบข้อมูลระบุตัวบุคคล การลบข้อมูลระบุตัวบุคคลคือกระบวนการรวบรวมข้อมูลของผู้ใช้หลายรายเพื่อปกป้องความเป็นส่วนตัวของผู้ใช้ วิธีการลบข้อมูลระบุตัวบุคคลทั้งหมดใช้การรวม แต่ไม่ใช่ทุกวิธีการรวมที่จะลบข้อมูลระบุตัวบุคคลได้ ในทางกลับกัน ความเป็นส่วนตัวเชิงอนุพันธ์ให้การรับประกันที่วัดผลได้เกี่ยวกับการรั่วไหลของข้อมูลและความเป็นส่วนตัว

2. ภาพรวมของ Differential Privacy

มาดูตัวอย่างง่ายๆ เพื่อให้เข้าใจความเป็นส่วนตัวเชิงอนุพันธ์ได้ดียิ่งขึ้น

แผนภูมิแท่งนี้แสดงความหนาแน่นของร้านอาหารขนาดเล็กในเย็นวันหนึ่ง แขกจำนวนมากมาที่ร้านอาหารตอน 19:00 น. และไม่มีแขกเลยตอน 01:00 น.

a43dbf3e2c6de596.png

This looks useful!

แต่ก็มีข้อจำกัด เมื่อมีแขกใหม่มาถึง แผนภูมิแท่งจะแสดงข้อมูลนี้ทันที ดูในแผนภูมิ คุณจะเห็นว่ามีแขกใหม่และแขกรายนี้มาถึงประมาณ 01:00 น.

bda96729e700a9dd.png

ซึ่งไม่ดีนักในมุมมองด้านความเป็นส่วนตัว สถิติที่ปกปิดข้อมูลระบุตัวบุคคลอย่างแท้จริงไม่ควรเปิดเผยการมีส่วนร่วมของแต่ละบุคคล การวางแผนภูมิทั้ง 2 รายการไว้ข้างกันจะทำให้เห็นชัดเจนยิ่งขึ้น กราฟแท่งสีส้มมีแขกพิเศษ 1 คนที่มาถึงเวลาประมาณ 01:00 น.

d562ddf799288894.png

ซึ่งก็ไม่ดีอีกเช่นกัน เราทำอะไร

เราจะทำให้แผนภูมิแท่งมีความแม่นยำน้อยลงเล็กน้อยด้วยการเพิ่มสัญญาณรบกวนแบบสุ่ม

ดูแผนภูมิแท่ง 2 รายการด้านล่าง แม้จะไม่แม่นยำทั้งหมด แต่ก็ยังมีประโยชน์และไม่เปิดเผยการมีส่วนร่วมของแต่ละบุคคล เยี่ยมไปเลย

838a0293cd4fcfe3.gif

Differential Privacy จะเพิ่มสัญญาณรบกวนแบบสุ่มในปริมาณที่เหมาะสมเพื่อปกปิดข้อมูลที่แต่ละคนมีส่วนร่วม

การวิเคราะห์ของเราค่อนข้างง่ายเกินไป การใช้ความเป็นส่วนตัวเชิงแตกต่างอย่างเหมาะสมนั้นมีความซับซ้อนมากขึ้นและมีรายละเอียดการใช้งานที่คาดไม่ถึงอยู่หลายประการ การสร้างการใช้งานความเป็นส่วนตัวเชิงแตกต่างของคุณเองอาจไม่ใช่ความคิดที่ดี เช่นเดียวกับการเข้ารหัส คุณใช้ความเป็นส่วนตัวใน Beam แทนการใช้โซลูชันของคุณเองได้ อย่าใช้ Differential Privacy ของคุณเอง

ในโค้ดแล็บนี้ เราจะแสดงวิธีวิเคราะห์แบบส่วนตัวเชิงอนุพันธ์โดยใช้ความเป็นส่วนตัวใน Beam

3. การดาวน์โหลดความเป็นส่วนตัวใน Beam

คุณไม่จำเป็นต้องดาวน์โหลด Privacy on Beam เพื่อทำตาม Codelab เนื่องจากโค้ดและกราฟที่เกี่ยวข้องทั้งหมดอยู่ในเอกสารนี้ อย่างไรก็ตาม หากต้องการดาวน์โหลดเพื่อเล่นกับโค้ด รันโค้ดด้วยตนเอง หรือใช้ความเป็นส่วนตัวใน Beam ในภายหลัง คุณสามารถทำได้โดยทำตามขั้นตอนด้านล่าง

โปรดทราบว่า Codelab นี้มีไว้สำหรับไลบรารีเวอร์ชัน 1.1.0

ก่อนอื่น ให้ดาวน์โหลดส่วนขยายความเป็นส่วนตัวบน 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

ความเป็นส่วนตัวใน Beam อยู่ในไดเรกทอรีระดับบนสุด privacy-on-beam/

โค้ดสำหรับโค้ดแล็บนี้และชุดข้อมูลอยู่ในไดเรกทอรี privacy-on-beam/codelab/

นอกจากนี้ คุณต้องติดตั้ง Bazel ในคอมพิวเตอร์ด้วย ดูวิธีการติดตั้งสำหรับระบบปฏิบัติการของคุณได้ในเว็บไซต์ Bazel

4. การคำนวณการเข้าชมต่อชั่วโมง

สมมติว่าคุณเป็นเจ้าของร้านอาหารและต้องการแชร์สถิติบางอย่างเกี่ยวกับร้านอาหารของคุณ เช่น การเปิดเผยเวลาที่ผู้คนนิยมมาใช้บริการ โชคดีที่คุณทราบเกี่ยวกับ Differential Privacy และการลบข้อมูลระบุตัวบุคคลออก จึงต้องการดำเนินการในลักษณะที่ไม่รั่วไหลข้อมูลเกี่ยวกับผู้เข้าชมแต่ละราย

โค้ดสำหรับตัวอย่างนี้อยู่ใน codelab/count.go

มาเริ่มกันด้วยการโหลดชุดข้อมูลจำลองที่มีการเข้าชมร้านอาหารในวันจันทร์ที่เฉพาะเจาะจง โค้ดสำหรับฟีเจอร์นี้ไม่เกี่ยวข้องกับวัตถุประสงค์ของ Codelab นี้ แต่คุณสามารถดูโค้ดดังกล่าวได้ใน codelab/main.go, codelab/utils.go และ codelab/visit.go

รหัสผู้เข้าชม

เวลาที่ป้อน

เวลาที่ใช้ (นาที)

จำนวนเงินที่ใช้จ่าย (ยูโร)

1

09: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 คือโครงสร้างที่เก็บพารามิเตอร์ Differential Privacy (epsilon และ delta) ที่เราต้องการใช้เพื่อลบข้อมูลระบุตัวบุคคล (คุณไม่ต้องกังวลเกี่ยวกับเรื่องนี้ในตอนนี้ เรามีส่วนที่ไม่บังคับในภายหลังหากคุณต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องดังกล่าว)

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 ครั้ง (หรือเราไม่สนใจหากผู้ใช้ไปที่ร้านอาหารหลายครั้งในระหว่างวัน) ดังนั้นเราจึงตั้งค่าเป็น 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

ซึ่งเป็นผลมาจากการเลือกพาร์ติชัน/การกำหนดเกณฑ์ ซึ่งเป็นขั้นตอนสำคัญในการรับประกันความเป็นส่วนตัวเชิงแตกต่างเมื่อการมีอยู่ของพาร์ติชันเอาต์พุตขึ้นอยู่กับข้อมูลผู้ใช้เอง ในกรณีนี้ การมีพาร์ติชันในเอาต์พุตอาจทำให้ข้อมูลรั่วไหลว่ามีผู้ใช้แต่ละรายอยู่ในข้อมูล (ดูบล็อกโพสต์นี้เพื่อดูคำอธิบายว่าเหตุใดจึงละเมิดความเป็นส่วนตัว) เพื่อป้องกันไม่ให้เกิดเหตุการณ์นี้ ความเป็นส่วนตัวใน 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 ได้หากใช้การแบ่งพาร์ติชันแบบสาธารณะและ Laplace Noise (ค่าเริ่มต้น) ดังเช่นกรณีข้างต้น

เมื่อเรียกใช้ไปป์ไลน์ที่มีการแบ่งพาร์ติชันแบบสาธารณะ (มี 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 ที่เราเคยทิ้งไปโดยไม่มีพาร์ติชันสาธารณะ

การใช้พาร์ติชันสาธารณะไม่เพียงช่วยให้คุณเก็บพาร์ติชันได้มากขึ้น แต่ยังเพิ่มสัญญาณรบกวนในแต่ละพาร์ติชันประมาณครึ่งหนึ่งเมื่อเทียบกับการไม่ใช้พาร์ติชันสาธารณะเนื่องจากไม่ได้ใช้งบประมาณด้านความเป็นส่วนตัว เช่น เอปซิลอนและเดลต้า ในการเลือกพาร์ติชัน ด้วยเหตุนี้ ความแตกต่างระหว่างจำนวนดิบกับจำนวนส่วนตัวจึงน้อยกว่าการเรียกใช้ครั้งก่อนเล็กน้อย

เมื่อใช้พาร์ติชันสาธารณะ คุณควรคำนึงถึงสิ่งสำคัญ 2 ประการต่อไปนี้

  1. โปรดระมัดระวังเมื่อได้รายการพาร์ติชันจากข้อมูลดิบ หากคุณไม่ได้ดำเนินการนี้ในลักษณะที่ใช้ Differential Privacy เช่น เพียงแค่อ่านรายการพาร์ติชันทั้งหมดในข้อมูลผู้ใช้ ไปป์ไลน์จะไม่รับประกัน Differential Privacy อีกต่อไป ดูส่วนขั้นสูงด้านล่างเกี่ยวกับวิธีดำเนินการนี้แบบเป็นส่วนตัวเชิงอนุพันธ์
  2. หากไม่มีข้อมูล (เช่น การเข้าชม) สําหรับพาร์ติชันสาธารณะบางส่วน ระบบจะเพิ่มสัญญาณรบกวนลงในพาร์ติชันเหล่านั้นเพื่อรักษา Differential Privacy ตัวอย่างเช่น หากเราใช้ชั่วโมงระหว่าง 0 ถึง 24 (แทนที่จะเป็น 9 ถึง 21) ทุกชั่วโมงจะมีการเพิ่มสัญญาณรบกวน และอาจแสดงการเข้าชมบางอย่างเมื่อไม่มีการเข้าชม

(ขั้นสูง) การได้พาร์ติชันจากข้อมูล

หากคุณเรียกใช้การรวบรวมหลายรายการที่มีรายการพาร์ติชันเอาต์พุตที่ไม่ใช่แบบสาธารณะเดียวกันในไปป์ไลน์เดียวกัน คุณสามารถรับรายการพาร์ติชันได้ครั้งเดียวโดยใช้ SelectPartitions() และระบุพาร์ติชันให้กับการรวบรวมแต่ละรายการเป็นอินพุต PublicPartition วิธีนี้ไม่เพียงปลอดภัยในแง่ของความเป็นส่วนตัว แต่ยังช่วยลดสัญญาณรบกวนเนื่องจากการใช้ Privacy Budget ในการเลือกพาร์ติชันเพียงครั้งเดียวสำหรับทั้งไปป์ไลน์

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. การคำนวณสถิติหลายรายการ

ส่วนใหญ่แล้ว คุณอาจสนใจที่จะคำนวณสถิติหลายรายการจากข้อมูลพื้นฐานเดียวกัน ซึ่งคล้ายกับที่คุณทำกับ COUNT, MEAN และ SUM โดยปกติแล้วการดำเนินการนี้ในไปป์ไลน์ Beam เดียวและในไบนารีเดียวจะสะอาดและง่ายกว่า คุณทำเช่นนี้ได้ด้วยความเป็นส่วนตัวใน Beam คุณสามารถเขียนไปป์ไลน์เดียวเพื่อเรียกใช้การเปลี่ยนรูปแบบและการคำนวณ และใช้ PrivacySpec เดียวสำหรับทั้งไปป์ไลน์

การทำเช่นนี้ด้วย PrivacySpec เดียวไม่เพียงสะดวกกว่า แต่ยังดีกว่าในแง่ของความเป็นส่วนตัวด้วย หากคุณจำพารามิเตอร์ epsilon และ delta ที่เราจัดหาให้แก่ PrivacySpec ได้ พารามิเตอร์ดังกล่าวจะแสดงถึงสิ่งที่เรียกว่างบประมาณความเป็นส่วนตัว ซึ่งเป็นตัววัดปริมาณความเป็นส่วนตัวของผู้ใช้ในข้อมูลพื้นฐานที่คุณกำลังรั่วไหล

สิ่งสำคัญที่ควรทราบเกี่ยวกับงบประมาณความเป็นส่วนตัวคือ งบประมาณนี้จะเพิ่มขึ้นเรื่อยๆ หากคุณเรียกใช้ไปป์ไลน์ที่มีค่า epsilon ε และ delta δ เฉพาะครั้งเดียว คุณจะใช้จ่ายงบประมาณ (ε,δ) หากเรียกใช้เป็นครั้งที่ 2 คุณจะใช้งบประมาณทั้งหมด (2ε, 2δ) ในทำนองเดียวกัน หากคุณคำนวณสถิติหลายรายการด้วย PrivacySpec (และงบประมาณความเป็นส่วนตัวต่อเนื่อง) ของ (ε,δ) คุณจะใช้จ่ายงบประมาณทั้งหมด (2ε, 2δ) ซึ่งหมายความว่าคุณกำลังลดระดับการรับประกันความเป็นส่วนตัว

หากต้องการหลีกเลี่ยงปัญหานี้ เมื่อต้องการคำนวณสถิติหลายรายการจากข้อมูลพื้นฐานเดียวกัน คุณควรใช้ PrivacySpec รายการเดียวที่มีงบประมาณรวมที่ต้องการใช้ จากนั้นคุณจะต้องระบุค่า epsilon และ delta ที่ต้องการใช้สำหรับการรวมแต่ละครั้ง ท้ายที่สุดแล้ว คุณจะได้รับการรับประกันความเป็นส่วนตัวโดยรวมเหมือนเดิม แต่ยิ่งการรวบรวมข้อมูลหนึ่งๆ มีค่าเอปซิลอนและเดลต้าสูงเท่าใด ความแม่นยำก็จะยิ่งสูงขึ้นเท่านั้น

หากต้องการดูการทำงานนี้ เราสามารถคำนวณสถิติ 3 รายการ (จำนวน ค่าเฉลี่ย และผลรวม) ที่เราคำนวณแยกกันก่อนหน้านี้ในไปป์ไลน์เดียว

โค้ดสำหรับตัวอย่างนี้อยู่ใน codelab/multiple.go โปรดสังเกตว่าเราแบ่งงบประมาณ (ε,δ) ทั้งหมดเท่าๆ กันระหว่างการรวม 3 รายการดังนี้

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 เป็นต้น เราสามารถแบ่งพารามิเตอร์เหล่านี้ออกเป็น 2 หมวดหมู่โดยประมาณ ได้แก่ พารามิเตอร์ความเป็นส่วนตัวและพารามิเตอร์ยูทิลิตี

พารามิเตอร์ความเป็นส่วนตัว

Epsilon และ Delta คือพารามิเตอร์ที่ระบุความเป็นส่วนตัวที่เรามอบให้โดยใช้ Differential Privacy กล่าวอย่างเจาะจงคือ เอปซิลอนและเดลต้าเป็นตัววัดปริมาณข้อมูลที่ผู้โจมตีที่อาจเกิดขึ้นได้รับเกี่ยวกับข้อมูลพื้นฐานโดยดูที่เอาต์พุตที่ไม่ระบุตัวตน ยิ่งค่า epsilon และ delta สูงมากเท่าใด ผู้โจมตีก็จะได้รับข้อมูลเกี่ยวกับข้อมูลพื้นฐานมากขึ้นเท่านั้น ซึ่งเป็นความเสี่ยงด้านความเป็นส่วนตัว

ในทางกลับกัน ยิ่งค่า epsilon และ delta ต่ำเท่าใด คุณก็ยิ่งต้องเพิ่มสัญญาณรบกวนลงในเอาต์พุตเพื่อให้ไม่ระบุตัวตน และคุณต้องมีผู้ใช้ที่ไม่ซ้ำกันจำนวนมากขึ้นในแต่ละพาร์ติชันเพื่อให้พาร์ติชันนั้นอยู่ในเอาต์พุตที่ไม่ระบุตัวตน ดังนั้นจึงต้องเลือกระหว่างยูทิลิตีและความเป็นส่วนตัว

ใน Privacy on Beam คุณต้องกังวลเกี่ยวกับหลักประกันความเป็นส่วนตัวที่ต้องการในเอาต์พุตที่ไม่มีการระบุตัวบุคคลเมื่อระบุงบประมาณความเป็นส่วนตัวทั้งหมดใน PrivacySpec ข้อควรระวังคือหากต้องการให้การรับประกันความเป็นส่วนตัวมีผล คุณต้องทำตามคำแนะนำใน Codelab นี้เกี่ยวกับการไม่ใช้จ่ายงบประมาณมากเกินไปโดยการมี PrivacySpec แยกต่างหากสำหรับการรวมแต่ละครั้ง หรือเรียกใช้ไปป์ไลน์หลายครั้ง

ดูข้อมูลเพิ่มเติมเกี่ยวกับความเป็นส่วนตัวเชิงอนุพันธ์และความหมายของพารามิเตอร์ความเป็นส่วนตัวได้ในเอกสาร

พารามิเตอร์ยูทิลิตี

พารามิเตอร์เหล่านี้ไม่ส่งผลต่อการรับประกันความเป็นส่วนตัว (ตราบใดที่ปฏิบัติตามคำแนะนำเกี่ยวกับวิธีใช้ความเป็นส่วนตัวใน Beam อย่างถูกต้อง) แต่จะส่งผลต่อความแม่นยำ และส่งผลต่อประโยชน์ของเอาต์พุต โดยจะระบุไว้ใน Params โครงสร้างของการรวมแต่ละรายการ เช่น CountParams, SumParams เป็นต้น พารามิเตอร์เหล่านี้ใช้เพื่อปรับขนาดสัญญาณรบกวนที่จะเพิ่ม

พารามิเตอร์ยูทิลิตีที่ระบุใน Params และใช้ได้กับการรวมทั้งหมดคือ MaxPartitionsContributed พาร์ติชันสอดคล้องกับคีย์ของ PCollection ที่เอาต์พุตโดยการดำเนินการรวบรวมข้อมูลความเป็นส่วนตัวใน Beam เช่น Count, SumPerKey เป็นต้น ดังนั้น MaxPartitionsContributed จะจำกัดจำนวนค่าคีย์ที่ไม่ซ้ำกันที่ผู้ใช้สามารถมีส่วนร่วมในเอาต์พุตได้ หากผู้ใช้มีส่วนร่วมในคีย์มากกว่า MaxPartitionsContributed คีย์ในข้อมูลพื้นฐาน ระบบจะทิ้งการมีส่วนร่วมบางอย่างของผู้ใช้เพื่อให้ผู้ใช้มีส่วนร่วมในคีย์ MaxPartitionsContributed คีย์เท่านั้น

การรวมส่วนใหญ่มีพารามิเตอร์ MaxContributionsPerPartition เช่นเดียวกับ MaxPartitionsContributed โดยจะระบุไว้ในโครงสร้าง Params และการรวบรวมแต่ละรายการอาจมีค่าแยกกันสำหรับรายการดังกล่าว MaxContributionsPerPartition จะจำกัดการมีส่วนร่วมของผู้ใช้สำหรับแต่ละคีย์ ซึ่งแตกต่างจาก MaxPartitionsContributed กล่าวคือ ผู้ใช้จะระบุค่าได้เพียง MaxContributionsPerPartition ค่าสำหรับแต่ละคีย์

ระบบจะปรับขนาดสัญญาณรบกวนที่เพิ่มลงในเอาต์พุตตาม MaxPartitionsContributed และ MaxContributionsPerPartition ดังนั้นจึงต้องมีการแลกเปลี่ยนกันในที่นี้ กล่าวคือ MaxPartitionsContributed และ MaxContributionsPerPartition ที่ใหญ่ขึ้นหมายความว่าคุณจะเก็บข้อมูลได้มากขึ้น แต่ผลลัพธ์ที่ได้จะมีสัญญาณรบกวนมากขึ้น

การรวมบางอย่างต้องใช้ MinValue และ MaxValue ซึ่งจะระบุขอบเขตของการมีส่วนร่วมของผู้ใช้แต่ละราย หากผู้ใช้มีส่วนร่วมด้วยมูลค่าที่ต่ำกว่า MinValue ระบบจะบีบมูลค่านั้นให้สูงถึง MinValue ในทำนองเดียวกัน หากผู้ใช้มีส่วนร่วมด้วยค่าที่มากกว่า MaxValue ระบบจะจำกัดค่านั้นไว้ที่ MaxValue ซึ่งหมายความว่าหากต้องการคงค่าเดิมไว้มากขึ้น คุณจะต้องระบุขอบเขตที่ใหญ่ขึ้น เช่นเดียวกับ MaxPartitionsContributed และ MaxContributionsPerPartition ระบบจะปรับขนาดสัญญาณรบกวนตามขนาดของขอบเขต ดังนั้นขอบเขตที่ใหญ่ขึ้นหมายความว่าคุณจะเก็บข้อมูลได้มากขึ้น แต่ผลลัพธ์ที่ได้จะมีสัญญาณรบกวนมากขึ้น

พารามิเตอร์สุดท้ายที่เราจะพูดถึงคือ NoiseKind เราสนับสนุนกลไกการสุ่มเสียง 2 แบบใน Privacy On Beam ได้แก่ GaussianNoise และ LaplaceNoise ทั้ง 2 แบบมีข้อดีและข้อเสีย แต่การแจกแจงแบบลาปลาซให้ค่าสาธารณูปโภคที่ดีกว่าเมื่อมีขอบเขตการมีส่วนร่วมต่ำ ด้วยเหตุนี้ Privacy On Beam จึงใช้การแจกแจงแบบลาปลาซเป็นค่าเริ่มต้น อย่างไรก็ตาม หากต้องการใช้สัญญาณรบกวนแบบการกระจายแบบเกาส์ คุณสามารถระบุ Params ด้วยตัวแปร pbeam.GaussianNoise{} ได้

10. สรุป

เก่งมาก คุณทำ Codelab ความเป็นส่วนตัวใน Beam เสร็จแล้ว คุณได้เรียนรู้มากมายเกี่ยวกับความเป็นส่วนตัวเชิงอนุพันธ์และความเป็นส่วนตัวใน Beam

  • เปลี่ยน PCollection เป็น PrivatePCollection โดยการเรียกใช้ MakePrivateFromStruct
  • การใช้ Count เพื่อคำนวณจำนวนแบบส่วนตัวเชิงอนุพันธ์
  • การใช้ MeanPerKey เพื่อคำนวณค่าเฉลี่ยแบบส่วนตัวเชิงอนุพันธ์
  • การใช้ SumPerKey เพื่อคำนวณผลรวมแบบส่วนตัวเชิงอนุพันธ์
  • การคำนวณสถิติหลายรายการด้วย PrivacySpec รายการเดียวในไปป์ไลน์เดียว
  • (ไม่บังคับ) การปรับแต่งพารามิเตอร์ PrivacySpec และการรวม (CountParams, MeanParams, SumParams)

แต่คุณยังทำการรวมข้อมูลอื่นๆ ได้อีกมากมาย (เช่น ควอนไทล์ การนับค่าที่ไม่ซ้ำ) ด้วยความเป็นส่วนตัวใน Beam ดูข้อมูลเพิ่มเติมได้ในที่เก็บ GitHub หรือ godoc

หากมีเวลา โปรดส่งความคิดเห็นเกี่ยวกับโค้ดแล็บให้เราทราบโดยกรอกแบบสำรวจ