Tính toán số liệu thống kê riêng tư với chế độ Bảo mật trên tia

1. Giới thiệu

Bạn có thể cho rằng số liệu thống kê tổng hợp không làm lộ bất kỳ thông tin nào về cá nhân có dữ liệu trong số liệu thống kê. Tuy nhiên, có nhiều cách mà kẻ tấn công có thể tìm hiểu thông tin nhạy cảm về các cá nhân trong một tập dữ liệu từ một số liệu thống kê tổng hợp.

Để bảo vệ quyền lợi cá nhân quyền riêng tư, bạn sẽ tìm hiểu cách tạo số liệu thống kê riêng tư bằng cách sử dụng các dữ liệu tổng hợp riêng tư khác biệt từ Quyền riêng tư trên Beam. Quyền riêng tư trên Beam là một khung sự riêng tư biệt lập hoạt động với Apache Beam.

"riêng tư" có nghĩa là gì?

Khi sử dụng từ "riêng tư" xuyên suốt lớp học lập trình này, chúng tôi muốn nói rằng dữ liệu đầu ra được tạo sao cho không làm lộ bất kỳ thông tin riêng tư nào về các cá nhân trong dữ liệu đó. Chúng ta có thể thực hiện điều này nhờ sự riêng tư biệt lập – một khái niệm mạnh mẽ về ẩn danh. Ẩn danh là quá trình tổng hợp dữ liệu từ nhiều người dùng để bảo vệ quyền riêng tư của người dùng. Mọi phương thức ẩn danh đều sử dụng phương thức tổng hợp, nhưng không phải phương thức tổng hợp nào cũng đạt được kết quả ẩn danh. Mặt khác, sự riêng tư biệt lập cung cấp các đảm bảo có thể đo lường được về việc rò rỉ thông tin và quyền riêng tư.

2. Tổng quan về Sự riêng tư biệt lập

Để hiểu rõ hơn về sự riêng tư biệt lập, chúng ta hãy xem một ví dụ đơn giản.

Biểu đồ thanh này thể hiện mức độ đông đúc của một nhà hàng nhỏ vào một buổi tối cụ thể. Rất nhiều khách đến lúc 7 giờ tối, và nhà hàng hoàn toàn trống rỗng lúc 1 giờ sáng:

a43dbf3e2c6de596.png

Tài liệu này có vẻ hữu ích!

Có một vấn đề tiềm ẩn. Khi một vị khách mới đến, thông tin này sẽ ngay lập tức được tiết lộ qua biểu đồ thanh. Xem trên biểu đồ: rõ ràng là có một khách mới và khách này đã đến vào khoảng 1 giờ sáng:

bda96729e700a9dd.png

Điều này không tốt từ góc độ quyền riêng tư. Số liệu thống kê thực sự ẩn danh không được tiết lộ nội dung đóng góp cá nhân. Việc đặt hai biểu đồ cạnh nhau làm cho điều này rõ ràng hơn nữa: biểu đồ thanh màu cam có thêm một khách đã đến vào lúc ~1 giờ sáng:

d562ddf799288894.png

Xin nhắc lại, điều đó không tốt lắm. Chúng tôi nên làm gì?

Chúng tôi sẽ giảm độ chính xác của biểu đồ thanh bằng cách thêm nhiễu ngẫu nhiên!

Hãy xem 2 biểu đồ thanh bên dưới. Mặc dù không hoàn toàn chính xác, nhưng chúng vẫn hữu ích và không thể hiện nội dung đóng góp của từng người. Tuyệt vời!

838a0293cd4fcfe3.gif

Sự riêng tư biệt lập đang bổ sung độ nhiễu ngẫu nhiên phù hợp để che giấu các nội dung đóng góp riêng lẻ.

Bản phân tích của chúng tôi hơi đơn giản hoá. Việc triển khai sự riêng tư biệt lập đúng cách phức tạp hơn và có một số chi tiết triển khai khá bất ngờ. Tương tự như mật mã học, việc tự tạo cách triển khai sự riêng tư biệt lập có thể không phải là một ý tưởng hay. Bạn có thể sử dụng chế độ Quyền riêng tư trên tia thay vì triển khai giải pháp của riêng mình. Đừng sử dụng thuật toán riêng tư biệt lập của riêng bạn!

Trong lớp học lập trình này, chúng ta sẽ tìm hiểu cách phân tích sự riêng tư khác biệt bằng cách sử dụng Quyền riêng tư trên Beam.

3. Đang tải chế độ bảo vệ quyền riêng tư xuống khi chiếu

Bạn không cần tải tính năng Quyền riêng tư trên Beam xuống để có thể theo dõi lớp học lập trình vì tài liệu này có tất cả mã và biểu đồ liên quan. Tuy nhiên, nếu bạn muốn tải xuống để chơi với mã, hãy tự chạy mã hoặc sử dụng Quyền riêng tư trên Beam sau, vui lòng thực hiện việc này bằng cách làm theo các bước bên dưới.

Xin lưu ý rằng lớp học lập trình này dành cho phiên bản thư viện 1.1.0.

Trước tiên, hãy tải Privacy on Beam xuống:

https://github.com/google/differential-privacy/archive/refs/tags/v1.1.0.tar.gz

Hoặc bạn có thể sao chép kho lưu trữ GitHub:

git clone --branch v1.1.0 https://github.com/google/differential-privacy.git

Quyền riêng tư trên Beam nằm trong thư mục privacy-on-beam/ cấp cao nhất.

Mã cho lớp học lập trình này và tập dữ liệu nằm trong thư mục privacy-on-beam/codelab/.

Bạn cũng cần phải cài đặt Bazel trên máy tính của mình. Tìm hướng dẫn cài đặt cho hệ điều hành của bạn trên trang web của Bazel.

4. Tính toán số lượt truy cập mỗi giờ

Giả sử bạn là một chủ nhà hàng và muốn chia sẻ một vài số liệu thống kê về nhà hàng của mình, chẳng hạn như công bố thời gian thường ghé thăm. Rất may là bạn biết về Sự riêng tư biệt lập và Ẩn danh, vì vậy, bạn muốn thực hiện việc này theo cách không làm rò rỉ thông tin về bất kỳ khách truy cập cá nhân nào.

Mã cho ví dụ này nằm trong codelab/count.go.

Hãy bắt đầu bằng việc tải một tập dữ liệu mô phỏng chứa các lượt ghé thăm nhà hàng của bạn vào một ngày thứ Hai cụ thể. Mã của lớp học lập trình này không thú vị, nhưng bạn có thể xem mã trong codelab/main.go, codelab/utils.gocodelab/visit.go.

ID khách truy cập

Thời gian đã nhập

Thời gian sử dụng (phút)

Số tiền đã chi tiêu (euro)

1

9:30:00 SA

26

24

2

11:54:00 SA

53

17

3

1:05:00 chiều

81

33

Trước tiên, bạn sẽ tạo một biểu đồ thanh không riêng tư về số lượt ghé thăm nhà hàng của bạn bằng cách sử dụng Tia trong mã mẫu bên dưới. Scope là đại diện của quy trình và mỗi thao tác mới mà chúng ta thực hiện trên dữ liệu sẽ được thêm vào Scope. CountVisitsPerHour lấy Scope và một tập hợp lượt truy cập được biểu thị dưới dạng PCollection trong Tia. Hàm này trích xuất giờ của mỗi lượt truy cập bằng cách áp dụng hàm extractVisitHour cho tập hợp. Sau đó, Analytics đếm số lần xuất hiện của mỗi giờ và trả về.

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

Thao tác này sẽ tạo ra một biểu đồ thanh đẹp (bằng cách chạy bazel run codelab -- --example="count" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/count.csv --output_chart_file=$(pwd)/count.png) trong thư mục hiện tại dưới dạng count.png:

a179766795d4e64a.png

Bước tiếp theo là chuyển đổi quy trình và biểu đồ thanh thành riêng tư. Chúng ta thực hiện việc này như sau.

Trước tiên, hãy gọi MakePrivateFromStruct trên PCollection<V> để nhận PrivatePCollection<V>. Dữ liệu đầu vào PCollection phải là một tập hợp các cấu trúc. Chúng ta cần nhập PrivacySpecidFieldPath làm dữ liệu đầu vào cho MakePrivateFromStruct.

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

PrivacySpec là một cấu trúc chứa các tham số sự riêng tư biệt lập (epsilon và delta) mà chúng tôi muốn sử dụng để ẩn danh dữ liệu. (Bạn hiện không cần phải lo lắng về các thẻ này, chúng tôi có một mục không bắt buộc sau nếu bạn muốn tìm hiểu thêm về các nội dung này.)

idFieldPath là đường dẫn của trường giá trị nhận dạng người dùng trong cấu trúc (trong trường hợp này là Visit). Ở đây, giá trị nhận dạng người dùng của khách truy cập là trường VisitorID của Visit.

Sau đó, chúng ta gọi pbeam.Count() thay vì stats.Count(), pbeam.Count() làm đầu vào là một cấu trúc CountParams chứa các tham số như MaxValue ảnh hưởng đến độ chính xác của kết quả đầu ra.

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

Tương tự, MaxPartitionsContributed giới hạn số giờ truy cập mà người dùng có thể đóng góp. Chúng ta muốn họ ghé thăm nhà hàng nhiều nhất là 1 lần trong ngày (hoặc chúng ta không quan tâm đến việc họ ghé thăm nhà hàng nhiều lần trong ngày), vì vậy chúng ta cũng đặt thành 1. Chúng ta sẽ nói chi tiết hơn về các tham số này trong phần tuỳ chọn.

MaxValue giới hạn số lần một người dùng có thể đóng góp vào các giá trị mà chúng ta đang đếm. Trong trường hợp cụ thể này, giá trị chúng tôi đang tính là số giờ ghé thăm và chúng tôi dự kiến người dùng chỉ ghé thăm nhà hàng một lần (hoặc chúng tôi không quan tâm nếu họ ghé thăm nhà hàng đó nhiều lần trong một giờ), vì vậy chúng tôi đặt tham số này thành 1.

Cuối cùng, mã của bạn sẽ có dạng như sau:

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
}

Chúng ta thấy biểu đồ thanh tương tự (count_dp.png) cho số liệu thống kê riêng tư khác biệt (lệnh trước chạy cả quy trình không riêng tư và quy trình riêng tư):

d6a0ace1acd3c760.png

Xin chúc mừng! Bạn đã tính toán số liệu thống kê riêng tư khác biệt đầu tiên của mình!

Biểu đồ thanh bạn nhận được khi chạy mã có thể khác với biểu đồ này. Đừng lo. Do nhiễu trong sự riêng tư biệt lập, bạn sẽ nhận được một biểu đồ thanh khác nhau mỗi lần chạy mã, nhưng bạn có thể thấy rằng các biểu đồ này giống hoặc ít giống với biểu đồ thanh không riêng tư ban đầu mà chúng tôi có.

Xin lưu ý rằng việc đảm bảo quyền riêng tư không chạy lại quy trình nhiều lần là rất quan trọng (ví dụ: để có biểu đồ thanh đẹp hơn). Lý do tại sao bạn không nên chạy lại đường ống của mình được giải thích trong "Tính toán nhiều số liệu thống kê" .

5. Sử dụng tính năng phân vùng công khai

Trong phần trước, bạn có thể nhận thấy rằng chúng tôi đã bỏ tất cả lượt truy cập (dữ liệu) cho một số phân vùng, tức là số giờ.

d7fbc5d86d91e54a.png

Điều này là do lựa chọn/ngưỡng phân vùng, một bước quan trọng để đảm bảo đảm bảo sự riêng tư biệt lập khi sự tồn tại của các phân vùng đầu ra phụ thuộc vào chính dữ liệu người dùng. Trong trường hợp này, việc chỉ tồn tại của một phân vùng trong dữ liệu đầu ra có thể làm rò rỉ sự tồn tại của một người dùng riêng lẻ trong dữ liệu (Xem bài đăng trên blog này để biết lý do khiến điều này vi phạm quyền riêng tư). Để tránh trường hợp này, chế độ Quyền riêng tư trên Beam chỉ giữ lại những phân vùng có đủ số lượng người dùng.

Khi danh sách các phân vùng đầu ra không phụ thuộc vào dữ liệu riêng tư của người dùng, tức là chúng là thông tin công khai, chúng ta không cần bước chọn phân vùng này. Đây thực sự là trường hợp ví dụ về nhà hàng của chúng ta: chúng ta biết giờ làm việc của nhà hàng (9.00 đến 21.00).

Mã cho ví dụ này nằm trong codelab/public_partitions.go.

Chúng ta sẽ chỉ cần tạo một PCollection gồm các giờ từ 9 đến 21 (không bao gồm) rồi nhập PCollection vào trường PublicPartitions của 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
}

Lưu ý rằng có thể thiết lập delta thành 0 nếu bạn đang sử dụng phân vùng công khai và Laplace Noise (mặc định), như trường hợp trên.

Khi chạy quy trình với phân vùng công khai (bằng 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), chúng ta sẽ nhận được (public_partitions_dp.png):

7c950fbe99fec60a.png.

Như bạn có thể thấy, bây giờ chúng ta giữ lại các phân vùng 9, 10 và 16 chúng ta trước đây đã bỏ mà không có phân vùng công khai.

Việc sử dụng phân vùng công khai không chỉ cho phép bạn giữ nhiều phân vùng hơn mà còn tăng gần một nửa độ nhiễu cho mỗi phân vùng so với không sử dụng phân vùng công khai do không chi tiêu bất kỳ ngân sách quyền riêng tư nào, tức là epsilon & delta, khi chọn phân vùng. Đó là lý do khiến sự khác biệt giữa số liệu thô và số liệu riêng tư ít hơn một chút so với lần chạy trước.

Có hai điều quan trọng cần lưu ý khi sử dụng phân vùng công khai:

  1. Hãy cẩn thận khi lấy danh sách phân vùng từ dữ liệu thô: nếu bạn không thực hiện việc này theo cách riêng tư khác biệt, ví dụ: chỉ cần đọc danh sách tất cả các phân vùng trong dữ liệu người dùng, thì quy trình của bạn sẽ không còn cung cấp đảm bảo sự riêng tư biệt lập nữa. Hãy xem phần nâng cao bên dưới về cách thực hiện việc này theo cách riêng tư khác biệt.
  2. Nếu không có dữ liệu (ví dụ: lượt truy cập) cho một số phân vùng công khai, thì độ nhiễu sẽ được áp dụng cho các phân vùng đó để bảo toàn sự riêng tư biệt lập. Ví dụ: nếu chúng tôi sử dụng số giờ từ 0 đến 24 (thay vì 9 và 21), thì tất cả giờ sẽ bị nhiễu và có thể hiển thị một số lượt truy cập khi không có giờ nào.

(Nâng cao) Lấy phân vùng từ dữ liệu

Nếu đang chạy nhiều bảng tổng hợp có cùng danh sách phân vùng đầu ra không công khai trong cùng một quy trình, bạn có thể lấy danh sách các phân vùng sau khi sử dụng SelectPartitions() và cung cấp các phân vùng cho mỗi quá trình tổng hợp dưới dạng đầu vào PublicPartition. Việc này không chỉ an toàn về quyền riêng tư mà còn giúp bạn giảm bớt tạp âm do việc sử dụng ngân sách quyền riêng tư chỉ một lần cho toàn bộ quy trình.

6. Tính toán thời gian lưu trú trung bình

Bây giờ, chúng ta đã biết cách tính theo cách riêng tư khác biệt, hãy tìm hiểu về các phương tiện tính toán. Cụ thể hơn, bây giờ chúng tôi sẽ tính toán thời gian lưu trú trung bình của khách truy cập.

Mã cho ví dụ này nằm trong codelab/mean.go.

Thông thường, để tính toán thời gian lưu trú trung bình riêng tư, chúng ta sẽ sử dụng stats.MeanPerKey() với bước xử lý trước. Bước này sẽ chuyển đổi PCollection lượt ghé thăm sắp tới thành PCollection<K,V>, trong đó K là giờ ghé thăm và V là thời gian mà khách ghé thăm nhà hàng.

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
}

Thao tác này sẽ tạo ra một biểu đồ thanh đẹp (bằng cách chạy bazel run codelab -- --example="mean" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/mean.csv --output_chart_file=$(pwd)/mean.png) trong thư mục hiện tại dưới dạng mean.png:

bc2df28bf94b3721.png

Để lớp này ở chế độ riêng tư khác biệt, chúng ta lại chuyển đổi PCollection thành PrivatePCollection và thay thế stats.MeanPerKey() bằng pbeam.MeanPerKey(). Tương tự như Count, chúng ta có MeanParams chứa một số tham số như MinValueMaxValue có ảnh hưởng đến độ chính xác. MinValueMaxValue thể hiện các giới hạn mà chúng ta đặt ra đối với sự đóng góp của mỗi người dùng cho từng khoá.

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

Trong trường hợp này, mỗi khoá đại diện cho một giờ và giá trị là thời gian mà khách truy cập đã bỏ ra. Chúng tôi đặt MinValue thành 0 vì chúng tôi dự kiến khách truy cập sẽ không dành ít hơn 0 phút tại nhà hàng. Chúng ta đặt MaxValue thành 60, có nghĩa là nếu khách truy cập dành hơn 60 phút, thì chúng ta sẽ hành động như thể người dùng đó đã dành 60 phút.

Cuối cùng, mã của bạn sẽ có dạng như sau:

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
}

Chúng ta thấy biểu đồ thanh tương tự (mean_dp.png) cho số liệu thống kê riêng tư khác biệt (lệnh trước chạy cả quy trình không riêng tư và quy trình riêng tư):

e8ac6a9bf9792287.png

Tương tự như cách đếm số, vì đây là một hoạt động riêng tư khác biệt, nên chúng ta sẽ nhận được các kết quả khác nhau mỗi lần chạy nó. Tuy nhiên, bạn có thể thấy rằng thời gian lưu trú riêng tư khác biệt không quá xa so với kết quả thực tế.

7. Tính toán doanh thu mỗi giờ

Một thống kê thú vị khác mà chúng ta có thể xem xét là doanh thu mỗi giờ trong suốt cả ngày.

Mã cho ví dụ này nằm trong codelab/sum.go.

Một lần nữa, chúng ta sẽ bắt đầu với phiên bản không riêng tư. Sau khi xử lý trước một số tập dữ liệu mô phỏng, chúng ta có thể tạo PCollection<K,V>, trong đó K là số giờ ghé qua và V là số tiền mà khách đã chi tiêu tại nhà hàng: Để tính doanh thu riêng tư mỗi giờ, chúng ta có thể chỉ cần cộng tất cả số tiền mà khách truy cập đã chi tiêu bằng cách gọi 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
}

Thao tác này sẽ tạo ra một biểu đồ thanh đẹp (bằng cách chạy bazel run codelab -- --example="sum" --input_file=$(pwd)/day_data.csv --output_stats_file=$(pwd)/sum.csv --output_chart_file=$(pwd)/sum.png) trong thư mục hiện tại dưới dạng sum.png:

548619173fad0c9a.png.

Để lớp này ở chế độ riêng tư khác biệt, chúng ta lại chuyển đổi PCollection thành PrivatePCollection và thay thế stats.SumPerKey() bằng pbeam.SumPerKey(). Tương tự như CountMeanPerKey, chúng ta có SumParams chứa một số tham số như MinValueMaxValue ảnh hưởng đến độ chính xác.

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

Trong trường hợp này, MinValueMaxValue thể hiện giới hạn mà chúng ta có đối với số tiền mà mỗi khách truy cập chi tiêu. Chúng tôi đặt MinValue thành 0 vì chúng tôi dự kiến khách truy cập sẽ không chi tiêu ít hơn 0 euro tại nhà hàng. Chúng ta thiết lập MaxValue thành 40, có nghĩa là nếu một khách truy cập chi tiêu nhiều hơn 40 euro, chúng ta sẽ hành động như thể người dùng đó đã chi tiêu 40 euro.

Cuối cùng, mã sẽ có dạng như sau:

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
}

Chúng ta thấy biểu đồ thanh tương tự (sum_dp.png) cho số liệu thống kê riêng tư khác biệt (lệnh trước chạy cả quy trình không riêng tư và quy trình riêng tư):

46c375e874f3e7c4.pngs

Xin nhắc lại, tương tự như count và có nghĩa là, do đây là một hoạt động riêng tư khác biệt, nên chúng ta sẽ nhận được các kết quả khác nhau mỗi lần chạy nó. Tuy nhiên, bạn có thể thấy kết quả riêng tư khác biệt rất gần với doanh thu thực tế mỗi giờ.

8. Tính toán nhiều thống kê

Trong hầu hết trường hợp, bạn có thể quan tâm đến việc tính toán nhiều số liệu thống kê trên cùng một dữ liệu cơ bản, tương tự như những gì bạn đã làm với các chỉ số đếm, trung bình và tổng. Việc này thường gọn gàng và dễ thực hiện hơn trong một đường ống dẫn và trong một tệp nhị phân duy nhất. Bạn cũng có thể thực hiện việc này bằng chế độ Quyền riêng tư trên Truyền phát. Bạn có thể viết một quy trình duy nhất để chạy các phép biến đổi và tính toán, đồng thời sử dụng một PrivacySpec duy nhất cho toàn bộ quy trình đó.

Việc này không chỉ thuận tiện hơn nhờ một PrivacySpec mà còn giúp bảo vệ quyền riêng tư hiệu quả hơn. Nếu bạn còn nhớ các tham số epsilon và delta mà chúng tôi cung cấp cho PrivacySpec, thì chúng đại diện cho ngân sách quyền riêng tư. Đây là thước đo mức độ riêng tư của người dùng trong dữ liệu cơ bản mà bạn đang làm rò rỉ.

Một điều quan trọng cần nhớ về ngân sách quyền riêng tư là ngân sách có tính bổ sung: Nếu bạn chạy một quy trình với một epsilon e và delta cụ thể trong một lần duy nhất, thì tức là bạn sẽ chi tiêu một ngân sách là ( , ) . Nếu bạn chạy lần thứ hai, bạn sẽ đã chi tiêu tổng ngân sách là (2, 2 ). Tương tự như vậy, nếu bạn tính toán nhiều số liệu thống kê với PrivacySpec (và liên tiếp là ngân sách quyền riêng tư) là (">, ;), bạn sẽ đã chi tiêu tổng ngân sách là (2, 2 ̊). Điều này có nghĩa là bạn đang hạ thấp mức độ đảm bảo về quyền riêng tư.

Để tránh né tránh điều này, khi muốn tính toán nhiều số liệu thống kê trên cùng một dữ liệu cơ bản, bạn phải sử dụng một PrivacySpec với tổng ngân sách mà bạn muốn sử dụng. Sau đó, bạn cần chỉ định epsilon và delta mà bạn muốn sử dụng cho mỗi phương thức tổng hợp. Cuối cùng, bạn vẫn nhận được cùng một bảo đảm quyền riêng tư tổng thể; nhưng tổng hợp cụ thể có epsilon và delta càng cao thì độ chính xác càng cao.

Để xem điều này trong thực tế, chúng ta có thể tính toán ba số liệu thống kê (số lượng, giá trị trung bình và tổng) mà chúng tôi đã tính riêng trước đó trong một quy trình.

Mã cho ví dụ này nằm trong codelab/multiple.go. Hãy lưu ý cách chúng tôi chia đều tổng ngân sách (−, ) giữa ba cách tổng hợp:

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. (Không bắt buộc) Chỉnh sửa các tham số Sự riêng tư biệt lập

Bạn đã thấy khá nhiều thông số được đề cập trong lớp học lập trình này: epsilon, delta, maxPartitionsĐóng góp, v.v. Chúng ta có thể chia khái quát các thông số này thành 2 danh mục: Tham số về quyền riêng tư và Tham số tiện ích.

Các tham số về quyền riêng tư

Epsilon và delta là các tham số định lượng sự riêng tư mà chúng tôi đang cung cấp bằng cách sử dụng sự riêng tư biệt lập. Chính xác hơn, epsilon và delta là thước đo lượng thông tin mà kẻ tấn công tiềm ẩn thu được về dữ liệu cơ bản bằng cách xem xét dữ liệu đầu ra ẩn danh. epsilon và delta càng cao thì kẻ tấn công càng thu thập được nhiều thông tin về dữ liệu cơ bản và điều này gây ra rủi ro về quyền riêng tư.

Mặt khác, epsilon và delta càng thấp, bạn càng cần thêm nhiều nhiễu vào đầu ra để ẩn danh, và bạn cần có số lượng người dùng riêng biệt cao hơn trong mỗi phân vùng để giữ phân vùng đó trong đầu ra ẩn danh. Do đó, ở đây có sự đánh đổi giữa sự hữu ích và quyền riêng tư.

Trong phần Quyền riêng tư trên ứng dụng, bạn cần lo lắng về những đảm bảo về quyền riêng tư mà bạn muốn trong dữ liệu đầu ra ẩn danh khi chỉ định tổng ngân sách quyền riêng tư trong PrivacySpec. Lưu ý rằng nếu muốn đảm bảo quyền riêng tư, bạn cần làm theo lời khuyên trong lớp học lập trình này về việc không sử dụng quá ngân sách bằng cách có một PrivacySpec riêng cho mỗi lần tổng hợp hoặc chạy quy trình nhiều lần.

Để biết thêm thông tin về Sự riêng tư biệt lập và ý nghĩa của các tham số về quyền riêng tư, bạn có thể xem văn bản.

Tham số tiện ích

Đây là những tham số không ảnh hưởng đến đảm bảo về quyền riêng tư (miễn là lời khuyên về cách sử dụng Quyền riêng tư trên truyền được tuân thủ đúng cách) nhưng sẽ ảnh hưởng đến độ chính xác và do đó, tính hữu ích của dữ liệu đầu ra. Các tham số này được cung cấp trong cấu trúc Params của mỗi quá trình tổng hợp, ví dụ: CountParams, SumParams, v.v. Các tham số này được dùng để tăng tỷ lệ độ nhiễu đang được thêm vào.

Một tham số hiệu dụng được cung cấp trong Params và có thể áp dụng cho tất cả dữ liệu tổng hợp là MaxPartitionsContributed. Một phân vùng tương ứng với một khoá của PCollection do thao tác tổng hợp Privacy On Stream xuất ra, tức là Count, SumPerKey, v.v. Vì vậy, MaxPartitionsContributed giới hạn số lượng giá trị khoá riêng biệt mà người dùng có thể đóng góp trong dữ liệu đầu ra. Nếu một người dùng đóng góp nhiều hơn MaxPartitionsContributed khoá trong dữ liệu cơ bản, thì một số đóng góp của người này sẽ bị loại bỏ để người đó đóng góp vào đúng MaxPartitionsContributed khoá.

Tương tự như MaxPartitionsContributed, hầu hết các hàm tổng hợp đều có tham số MaxContributionsPerPartition. Các tham số này được cung cấp trong cấu trúc Params và mỗi hàm tổng hợp có thể có các giá trị riêng. Trái ngược với MaxPartitionsContributed, MaxContributionsPerPartition giới hạn sự đóng góp của người dùng cho từng khoá. Nói cách khác, người dùng chỉ có thể đóng góp MaxContributionsPerPartition giá trị cho mỗi khoá.

Độ nhiễu thêm vào đầu ra được điều chỉnh theo MaxPartitionsContributedMaxContributionsPerPartition. Vì vậy, có một sự đánh đổi ở đây: MaxPartitionsContributedMaxContributionsPerPartition lớn hơn đều có nghĩa là bạn giữ được nhiều dữ liệu hơn, nhưng bạn sẽ nhận được kết quả nhiễu hơn.

Một số dữ liệu tổng hợp yêu cầu MinValueMaxValue. Các chính sách này chỉ định giới hạn đối với sự đóng góp của mỗi người dùng. Nếu người dùng đóng góp một giá trị thấp hơn MinValue, thì giá trị đó sẽ được giới hạn ở mức MinValue. Tương tự, nếu người dùng đóng góp một giá trị lớn hơn MaxValue, thì giá trị đó sẽ được giới hạn thành MaxValue. Điều này có nghĩa là để giữ lại nhiều giá trị ban đầu hơn, bạn phải chỉ định các giới hạn lớn hơn. Tương tự như MaxPartitionsContributedMaxContributionsPerPartition, độ nhiễu được điều chỉnh theo kích thước của giới hạn. Vì vậy, giới hạn lớn hơn có nghĩa là bạn sẽ lưu giữ nhiều dữ liệu hơn, nhưng cuối cùng bạn sẽ nhận được kết quả nhiễu hơn.

Thông số cuối cùng chúng ta sẽ nói đến là NoiseKind. Chúng tôi hỗ trợ 2 cơ chế tiếng ồn trong Privacy On Beam: GaussianNoiseLaplaceNoise. Cả hai đều có những ưu điểm và nhược điểm riêng, nhưng tính năng phân phối Laplace mang lại lợi ích tốt hơn với giới hạn đóng góp thấp. Đó là lý do tại sao tính năng Privacy On Beam sử dụng tính năng này theo mặc định. Tuy nhiên, nếu muốn sử dụng độ nhiễu phân phối Gaussian, bạn có thể cung cấp Params với biến pbeam.GaussianNoise{}.

10. Tóm tắt

Tuyệt vời! Bạn đã hoàn thành lớp học lập trình Quyền riêng tư trên chiếu. Bạn đã tìm hiểu nhiều về sự riêng tư biệt lập và Sự riêng tư trên Beam:

  • Chuyển PCollection thành PrivatePCollection bằng cách gọi MakePrivateFromStruct.
  • Sử dụng Count để tính số lượng riêng tư khác biệt.
  • Sử dụng MeanPerKey để tính toán các phương tiện riêng tư khác biệt.
  • Sử dụng SumPerKey để tính các tổng riêng biệt khác nhau.
  • Tính toán nhiều số liệu thống kê bằng một PrivacySpec trong một quy trình.
  • (Không bắt buộc) Tuỳ chỉnh PrivacySpec và các tham số tổng hợp (CountParams, MeanParams, SumParams).

Tuy nhiên, còn nhiều loại dữ liệu tổng hợp khác (ví dụ: số phân vị, đếm các giá trị riêng biệt) mà bạn có thể thực hiện bằng tính năng Quyền riêng tư trên tia! Bạn có thể tìm hiểu thêm về các tệp đó trên kho lưu trữ GitHub hoặc godoc.

Nếu bạn có thời gian, vui lòng gửi ý kiến phản hồi cho chúng tôi về lớp học lập trình này bằng cách điền thông tin vào bản khảo sát.