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

1. บทนำ

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

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

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

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

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

มาดูตัวอย่างง่ายๆ เพื่อให้เข้าใจ Differential Privacy ได้ดียิ่งขึ้น

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

a43dbf3e2c6de596.png

ข้อมูลนี้มีประโยชน์

รับได้ เมื่อมีผู้เข้าร่วมใหม่เข้ามา แผนภูมิแท่งจะแสดงข้อเท็จจริงนี้ทันที ดูแผนภูมิ: เห็นได้ชัดว่ามีผู้เข้าร่วมใหม่และแขกผู้นี้มาถึงเวลาประมาณ 1:00 น.

bda96729e700a9dd.png

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

d562ddf799288894.png

นั่นยังไม่ดีเลย เราทำอย่างไร

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

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

838a0293cd4fcfe3.gif

Differential Privacy จะใส่เสียงแบบสุ่มในปริมาณที่เหมาะสมเพื่อปกปิดการมีส่วนร่วมแต่ละรายการ

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

ใน Codelab นี้ เราจะแสดงวิธีทำการวิเคราะห์ Differentiated Private โดยใช้ Privacy บนบีม

3. กำลังดาวน์โหลดความเป็นส่วนตัวบนบีม

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

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

ก่อนอื่น ให้ดาวน์โหลด "ความเป็นส่วนตัวบนบีม" โดยทำดังนี้

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/ ระดับบนสุด

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

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

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

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

โค้ดสำหรับตัวอย่างนี้อยู่ใน 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

13:05:00 น.

81

33

คุณจะต้องสร้างแผนภูมิแท่งแบบไม่เป็นส่วนตัวสำหรับเวลาไปร้านอาหารโดยใช้บีมในตัวอย่างโค้ดด้านล่าง Scope เป็นตัวแทนของไปป์ไลน์ และการดำเนินการใหม่แต่ละครั้งกับข้อมูลจะเพิ่มลงใน Scope CountVisitsPerHour บันทึก Scope และคอลเล็กชันการเข้าชม ซึ่งจะแสดงเป็น PCollection ในบีม โดยจะแยกชั่วโมงของการเข้าชมแต่ละครั้งโดยใช้ฟังก์ชัน extractVisitHour ในคอลเล็กชัน จากนั้นจะนับรายการในทุกๆ ชั่วโมงและแสดงผลอีกครั้ง

func CountVisitsPerHour(s beam.Scope, col beam.PCollection) beam.PCollection {
    s = s.Scope("CountVisitsPerHour")
    visitHours := beam.ParDo(s, extractVisitHourFn, col)
    visitsPerHour := stats.Count(s, visitHours)
    return visitsPerHour
}

func extractVisitHourFn(v Visit) int {
    return v.TimeEntered.Hour()
}

การดำเนินการนี้จะสร้างแผนภูมิแท่งแสดงที่ดี (โดยการเรียกใช้ bazel run codelab -- --example="count" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/count.csv --output_chart_file=$(pwd)/count.png) ในไดเรกทอรีปัจจุบันเป็น count.png:

a179766795d4e64a.png

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

ก่อนอื่น ให้โทรหา MakePrivateFromStruct ที่หมายเลข PCollection<V> เพื่อรับ PrivatePCollection<V> อินพุต PCollection ต้องเป็นคอลเล็กชันของ Struct เราต้องป้อน 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 เช่นกัน เราจะพูดถึงพารามิเตอร์เหล่านี้อย่างละเอียดยิ่งขึ้นในส่วนที่ไม่บังคับ

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

ยินดีด้วย คุณได้คำนวณสถิติส่วนตัวที่แตกต่างกันเป็นครั้งแรก

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

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

5. การใช้พาร์ติชันสาธารณะ

ในส่วนก่อนหน้านี้ คุณอาจสังเกตเห็นว่าเราได้ยกเลิกการเข้าชม (ข้อมูล) ทั้งหมดในบางพาร์ติชัน เช่น จำนวนชั่วโมง

d7fbc5d86d91e54a.png

ทั้งนี้เนื่องจากการเลือก/การกำหนดเกณฑ์พาร์ติชัน ซึ่งเป็นขั้นตอนสำคัญในการรับประกัน Differential Privacy ในเมื่อการมีอยู่ของพาร์ติชันเอาต์พุตขึ้นอยู่กับข้อมูลผู้ใช้ ในกรณีนี้ การมีอยู่ของพาร์ติชันในเอาต์พุตอาจทำให้การมีอยู่ของผู้ใช้แต่ละรายในข้อมูลไม่ได้ (ดูบล็อกโพสต์นี้สำหรับคำอธิบายว่าเหตุใดการดำเนินการนี้จึงละเมิดความเป็นส่วนตัว) เพื่อป้องกันปัญหานี้ Privacy on Beam จะเก็บเฉพาะพาร์ติชันที่มีผู้ใช้มากพอ

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

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

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

  1. โปรดใช้ความระมัดระวังเมื่อดึงข้อมูลรายการพาร์ติชันจากข้อมูลดิบ ดังนี้ เพียงแค่อ่านรายการพาร์ติชันทั้งหมดในข้อมูลผู้ใช้ ไปป์ไลน์ของคุณจะไม่ให้การรับประกัน Differential Privacy อีกต่อไป โปรดดูส่วนขั้นสูงด้านล่างเกี่ยวกับวิธีดำเนินการในลักษณะที่เป็นส่วนตัว
  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() เพื่อสร้างความเป็นส่วนตัวที่แตกต่าง เรามี SumParams ที่เก็บพารามิเตอร์บางอย่าง เช่น MinValue และ MaxValue ที่ส่งผลต่อความแม่นยำ เช่นเดียวกับ Count และ MeanPerKey

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

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

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

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

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

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

โค้ดสำหรับตัวอย่างนี้อยู่ใน codelab/multiple.go โปรดสังเกตวิธีแบ่งงบประมาณทั้งหมด (Negative, {/9}) ระหว่างการรวม 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

คุณได้เห็นพารามิเตอร์สองสามรายการใน Codelab นี้ เช่น epsilon, delta, maxPartitionsContributd ฯลฯ เราสามารถแบ่งพารามิเตอร์ออกเป็น 2 หมวดหมู่คร่าวๆ ได้แก่ พารามิเตอร์ความเป็นส่วนตัว และพารามิเตอร์ยูทิลิตี

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

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

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

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Differential Privacy และความหมายของพารามิเตอร์ความเป็นส่วนตัวได้จากเนื้อหา

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

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

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

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

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

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

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

10. สรุป

เก่งมาก คุณดำเนินการเกี่ยวกับความเป็นส่วนตัวบนบีม Codelab เสร็จแล้ว คุณได้เรียนรู้มากมายเกี่ยวกับ Differential Privacy และ Privacy ในบีม

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

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

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