1. Giới thiệu
Bạn có thể nghĩ rằng số liệu thống kê tổng hợp không tiết lộ bất kỳ thông tin nào về những cá nhân có dữ liệu tạo nên số liệu thống kê đó. Tuy nhiên, có nhiều cách để 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ừ số liệu thống kê tổng hợp.
Để bảo vệ quyền riêng tư của cá nhân, 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 giá trị tổng hợp có quyền riêng tư biệt lập từ Privacy on 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ư" trong Lớp học lập trình này, chúng tôi muốn nói rằng đầu ra được tạo ra theo cách không làm rò rỉ bất kỳ thông tin riêng tư nào về các cá nhân trong dữ liệu. Chúng tôi có thể làm việc này bằng cách sử dụng sự riêng tư biệt lập, một khái niệm mạnh mẽ về quyền riêng tư đối với việc ẩn danh. Ẩn danh là quá trình tổng hợp dữ liệu của nhiều người dùng để bảo vệ quyền riêng tư của người dùng. Tất cả các phương pháp ẩn danh đều sử dụng tính năng tổng hợp nhưng không phải phương pháp tổng hợp nào cũng đạt được mục đích ẩn danh. Mặt khác, sự riêng tư biệt lập mang đến những đả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, hãy xem một ví dụ đơn giản.
Biểu đồ thanh này cho biết mức độ đông đúc của một nhà hàng nhỏ vào một buổi tối cụ thể. 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:

Thông tin này có vẻ hữu ích!
Có một điểm cần lưu ý. Khi có khách mới đến, biểu đồ thanh sẽ cho thấy ngay lập tức thông tin này. Nhìn vào biểu đồ, bạn có thể thấy rõ rằng có một khách mới và khách này đã đến vào khoảng 1 giờ sáng:

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

Một lần nữa, điều đó không tốt. Chúng ta phải làm gì?
Chúng ta sẽ làm cho biểu đồ thanh kém chính xác hơn một chút 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 những số liệu này vẫn hữu ích và không tiết lộ đóng góp của từng cá nhân. Tuyệt vời!

Sự riêng tư biệt lập là việc thêm một lượng nhiễu ngẫu nhiên phù hợp để che giấu các đóng góp riêng lẻ.
Phân tích của chúng tôi có phần đơn giản hoá quá mức. Việc triển khai sự riêng tư biệt lập đúng cách sẽ phức tạp hơn và có một số điểm tinh tế trong quá trình triển khai khá bất ngờ. Tương tự như mật mã học, việc tạo chế độ triển khai sự riêng tư biệt lập của riêng bạn có thể không phải là một ý tưởng hay. Bạn có thể sử dụng tính năng Quyền riêng tư trên Beam thay vì triển khai giải pháp của riêng mình. Đừng tự triển khai sự riêng tư biệt lập!
Trong lớp học lập trình này, chúng ta sẽ tìm hiểu cách thực hiện phân tích riêng tư vi phân bằng cách sử dụng Quyền riêng tư trên Beam.
3. Tải Quyền riêng tư trên Beam xuống
Bạn không cần tải Privacy on Beam xuống để có thể làm theo lớp học lập trình này vì tất cả mã và biểu đồ có liên quan đều có trong tài liệu này. Tuy nhiên, nếu bạn muốn tải xuống để chơi với mã, tự kích hoạt mã hoặc sử dụng Privacy on Beam sau này, bạn có thể 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 1.1.0 của thư viện.
Trước tiên, hãy tải Quyền riêng tư trên 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 cài đặt Bazel trên máy tính. Tìm hướng dẫn cài đặt cho hệ điều hành của bạn trên trang web Bazel.
4. Tính toán số lượt truy cập mỗi giờ
Giả sử bạn là chủ nhà hàng và muốn chia sẻ một số số liệu thống kê về nhà hàng của mình, chẳng hạn như tiết lộ thời gian khách thường đến. Rất may là bạn biết về Sự riêng tư biệt lập và Quy trình ẩn danh, nên 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 nào.
Mã cho ví dụ này nằm trong codelab/count.go.
Hãy bắt đầu bằng cách 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ã cho phần này không thú vị đối với mục đích của lớp học lập trình này, nhưng bạn có thể xem mã cho phần đó trong codelab/main.go, codelab/utils.go và codelab/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 | 26 | 24 |
2 | 11:54:00 | 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ề thời gian ghé thăm nhà hàng của mình bằng cách sử dụng Beam trong mã mẫu bên dưới. Scope là một biểu 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 một Scope và một tập hợp các lượt truy cập, được biểu thị dưới dạng PCollection trong Beam. Thao tác 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 bộ sưu tập. Sau đó, hàm này sẽ đếm số lần xuất hiện của từng giờ và trả về số lần đó.
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 mắt (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:

Bước tiếp theo là chuyển đổi quy trình và biểu đồ thanh của bạn thành quy trình và biểu đồ thanh riêng tư. Chúng tôi thực hiện việc này như sau.
Trước tiên, hãy gọi MakePrivateFromStruct trên một PCollection<V> để lấy PrivatePCollection<V>. PCollection đầu vào phải là một tập hợp các cấu trúc. Chúng ta cần nhập PrivacySpec và idFieldPath 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 lưu giữ các thông số về sự riêng tư biệt lập (epsilon và delta) mà chúng ta muốn dùng để ẩn danh dữ liệu. (Hiện tại, bạn không cần lo lắng về những điều này. Chúng tôi có một phần không bắt buộc ở phần sau nếu bạn muốn tìm hiểu thêm về những điều đó.)
idFieldPath là đường dẫn của trường giá trị nhận dạng người dùng trong cấu trúc (Visit trong trường hợp này). Trong đó, 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ấy CountParams struct làm dữ liệu đầu vào. Struct này chứa các tham số như MaxValue ảnh hưởng đến độ chính xác của đầ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 khác nhau mà người dùng có thể đóng góp. Chúng tôi dự kiến họ sẽ ghé thăm nhà hàng tối đa một lần mỗi ngày (hoặc chúng tôi không quan tâm nếu họ ghé thăm nhiều lần trong ngày), vì vậy, chúng tôi cũng đặt giá trị này thành 1. Chúng ta sẽ thảo luận chi tiết hơn về các tham số này trong một phần không bắt buộc.
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 tính. Trong trường hợp cụ thể này, các giá trị mà chúng ta đang tính là số giờ ghé thăm và chúng ta kỳ vọng người dùng chỉ ghé thăm nhà hàng một lần (hoặc chúng ta không quan tâm nếu họ ghé thăm nhiều lần mỗi giờ), vì vậy, chúng ta đặ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 một biểu đồ thanh tương tự (count_dp.png) cho số liệu thống kê riêng tư vi phân (lệnh trước chạy cả quy trình không riêng tư và quy trình riêng tư):

Xin chúc mừng! Bạn đã tính toán số liệu thống kê riêng tư vi phân đầu tiên!
Biểu đồ thanh mà bạn nhận được khi chạy mã có thể khác với biểu đồ này. Đừng lo. Do hiện tượng nhiễu liên quan đến sự riêng tư biệt lập, bạn sẽ nhận được một biểu đồ thanh khác nhau mỗi khi chạy mã. Tuy nhiên, bạn có thể thấy rằng các biểu đồ này ít nhiều tương tự như biểu đồ thanh ban đầu không riêng tư mà chúng ta đã có.
Xin lưu ý rằng việc không chạy lại pipeline nhiều lần (ví dụ: để có được biểu đồ thanh đẹp mắt hơn) là rất quan trọng đối với các đảm bảo về quyền riêng tư. Lý do bạn không nên chạy lại các quy trình được giải thích trong phần "Tính toán nhiều số liệu thống kê".
5. Sử dụng phân vùng công khai
Trong phần trước, có thể bạn đã nhận thấy rằng chúng tôi đã loại bỏ tất cả lượt truy cập (dữ liệu) cho một số phân vùng, tức là các giờ.

Điều này là do việc chọn phân vùng/ngưỡng, một bước quan trọng để đả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, chỉ cần có một phân vùng trong đầu ra là có thể làm lộ sự tồn tại của một người dùng riêng lẻ trong dữ liệu (Xem bài đăng này trên blog để biết lý do khiến điều này vi phạm quyền riêng tư). Để ngăn chặn điều này, Privacy on 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 người dùng riêng tư (tức 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 là trường hợp thực tế đối với ví dụ về nhà hàng: chúng ta biết giờ làm việc của nhà hàng (từ 9:00 đến 21:00).
Mã cho ví dụ này nằm trong codelab/public_partitions.go.
Chúng ta chỉ cần tạo một PCollection gồm các giờ từ 9 đến 21 (không bao gồm) và nhập 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
}
Xin lưu ý rằng bạn có thể đặt delta thành 0 nếu đang sử dụng các phân vùng công khai và Nhiễu Laplace (mặc định), như trường hợp ở trên.
Khi chạy quy trình bằng các phân vùng công khai (với 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):

Như bạn có thể thấy, giờ đây, chúng ta sẽ giữ lại các phân vùng 9, 10 và 16 mà trước đây chúng ta đã loại bỏ mà không có phân vùng công khai.
Việc sử dụng các phân vùng công khai không chỉ giúp bạn giữ lại nhiều phân vùng hơn mà còn làm tăng khoảng một nửa lượng nhiễu cho mỗi phân vùng so với việc không sử dụng các 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 và delta) cho việc chọn phân vùng. Đó là lý do khiến sự khác biệt giữa số lượt xem thô và số lượt xem riêng tư thấp 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:
- Hãy cẩn thận khi lấy danh sách các 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ư biệt lập, chẳng hạn như 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 đả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 để biết cách thực hiện việc này theo cách riêng tư vi phân.
- 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 những phân vùng đó để đảm bảo sự riêng tư biệt lập. Ví dụ: nếu chúng ta sử dụng các giờ từ 0 đến 24 (thay vì 9 đến 21), thì tất cả các giờ sẽ bị làm nhiễu và có thể cho thấy một số lượt truy cập khi không có lượt truy cập nào.
(Nâng cao) Tạo phân vùng từ dữ liệu
Nếu đang chạy nhiều hoạt động tổng hợp với cùng một danh sách các 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 phân vùng một lần bằng cách sử dụng SelectPartitions() và cung cấp các phân vùng cho từng hoạt động tổng hợp dưới dạng đầu vào PublicPartition. Không chỉ an toàn về quyền riêng tư, mà giải pháp này còn giúp bạn giảm bớt nhiễu do chỉ sử dụng ngân sách quyền riêng tư cho việc chọn phân vùng một lần cho toàn bộ quy trình.
6. Tính thời gian lưu trú trung bình
Giờ đây, khi đã biết cách đếm số lượng theo cách riêng tư vi phân, hãy xem cách tính giá trị trung bình. Cụ thể hơn, 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 thời gian lưu trú trung bình không riêng tư, chúng ta sẽ sử dụng stats.MeanPerKey() với một bước tiền xử lý giúp chuyển đổi PCollection của các lượt truy cập đến PCollection<K,V>, trong đó K là giờ truy cập và V là thời gian mà khách truy cập đã dành ở 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 mắt (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:

Để đảm bảo quyền riêng tư khác biệt, chúng ta sẽ 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 lưu giữ một số tham số như MinValue và MaxValue ảnh hưởng đến độ chính xác. MinValue và MaxValue biểu thị giới hạn đó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à các giá trị là thời gian mà khách truy cập đã dành ra. Chúng tôi đặt MinValue thành 0 vì chúng tôi không mong đợi khách hàng dành ít hơn 0 phút ở nhà hàng. Chúng tôi đặt MaxValue thành 60, tức là nếu khách truy cập dành hơn 60 phút, chúng tôi sẽ coi như 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 một biểu đồ thanh tương tự (mean_dp.png) cho số liệu thống kê riêng tư vi phân (lệnh trước chạy cả quy trình không riêng tư và quy trình riêng tư):

Tương tự như count, vì đây là một thao tác riêng tư vi phân, nên mỗi lần kích hoạt, chúng ta sẽ nhận được kết quả khác nhau. Tuy nhiên, bạn có thể thấy thời gian lưu trú riêng tư vi phân không khác biệt nhiều so với kết quả thực tế.
7. Tính toán doanh thu mỗi giờ
Một số liệu thống kê thú vị khác mà chúng ta có thể xem xét là doanh thu mỗi giờ trong 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ư. Với một số bước tiền xử lý trên tập dữ liệu mô phỏng, chúng ta có thể tạo một PCollection<K,V> trong đó K là giờ truy cập và V là số tiền mà khách truy cập đã chi tiêu trong nhà hàng: Để tính doanh thu không riêng tư theo giờ, chúng ta 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 mắt (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:

Để đảm bảo quyền riêng tư khác biệt, chúng ta sẽ chuyển đổi PCollection thành PrivatePCollection và thay thế stats.SumPerKey() bằng pbeam.SumPerKey(). Tương tự như Count và MeanPerKey, chúng ta có SumParams lưu giữ một số tham số như MinValue và MaxValue ả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, MinValue và MaxValue biểu thị giới hạn 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 không mong đợi khách truy cập chi tiêu ít hơn 0 EUR tại nhà hàng. Chúng tôi đặt MaxValue thành 40, tức là nếu một khách truy cập chi tiêu hơn 40 EUR, thì chúng tôi sẽ coi như người dùng đó đã chi tiêu 40 EUR.
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 một biểu đồ thanh tương tự (sum_dp.png) cho số liệu thống kê riêng tư vi phân (lệnh trước chạy cả quy trình không riêng tư và quy trình riêng tư):

Tương tự như count và mean, vì đây là một thao tác riêng tư vi sai, nên mỗi lần kích hoạt, chúng ta sẽ nhận được các kết quả khác nhau. Tuy nhiên, bạn có thể thấy kết quả riêng tư hoá 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 số liệu thống kê
Trong hầu hết các trường hợp, bạn có thể 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, tương tự như những gì bạn đã làm với số lượng, giá trị trung bình và tổng. Thường thì việc này sẽ dễ dàng và gọn gàng hơn khi thực hiện trong một quy trình Beam duy nhất và trong một tệp nhị phân duy nhất. Bạn cũng có thể làm việc này bằng tính năng Quyền riêng tư trên Beam. 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 khi chỉ cần một PrivacySpec mà còn đảm bảo quyền riêng tư tốt hơn. Nếu bạ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 một thứ gọi là Hạn mức quyền riêng tư. Đây là thước đo mức độ quyền riêng tư của người dùng trong dữ liệu cơ bản mà bạn đang tiết lộ.
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 này có tính cộng: Nếu chạy một quy trình có epsilon ε và delta δ cụ thể một lần, thì bạn đang chi tiêu ngân sách (ε,δ). Nếu kích hoạt lần thứ hai, bạn sẽ chi tiêu tổng ngân sách là (2ε, 2δ). Tương tự, nếu 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à (ε,δ), thì 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 làm giảm các cam kết đảm bảo quyền riêng tư.
Để khắc phục đ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 nên 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 hoạt động tổng hợp. Cuối cùng, bạn sẽ nhận được sự đảm bảo chung về quyền riêng tư; nhưng một phép tổng hợp cụ thể có epsilon và delta càng cao thì độ chính xác càng cao.
Để xem hoạt động này, chúng ta có thể tính toán 3 số liệu thống kê (số lượng, giá trị trung bình và tổng) mà chúng ta đã tính toán riêng biệt trước đó trong một quy trình duy nhất.
Mã cho ví dụ này nằm trong codelab/multiple.go. Lưu ý cách chúng ta chia đều tổng ngân sách (ε,δ) cho 3 quy trình 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) Điều chỉnh các tham số về Sự riêng tư biệt lập
Bạn đã thấy khá nhiều tham số được đề cập trong lớp học lập trình này: epsilon, delta, maxPartitionsContributed, v.v. Chúng ta có thể chia các tham số này thành 2 danh mục: Tham số về quyền riêng tư và Tham số về tiện ích.
Tham số về quyền riêng tư
Epsilon và delta là các thông số định lượng quyền riêng tư mà chúng tôi cung cấp bằng cách sử dụng sự riêng tư biệt lập. Cụ thể hơn, epsilon và delta là thước đo mức độ 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 đầ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, đây là một rủi ro về quyền riêng tư.
Mặt khác, epsilon và delta càng thấp thì bạn càng cần thêm nhiều nhiễu vào đầu ra để ẩn danh, đồng thời 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. Vì vậy, ở đây có sự đánh đổi giữa tiện ích và quyền riêng tư.
Trong Quyền riêng tư trên Beam, bạn cần lo ngại về những đảm bảo quyền riêng tư mà bạn muốn trong đầu ra ẩn danh khi chỉ định tổng ngân sách quyền riêng tư trong PrivacySpec. Lưu ý là 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á nhiều ngân sách bằng cách có một PrivacySpec riêng cho mỗi hoạt động tổng hợp hoặc chạy pipeline nhiều lần.
Để biết thêm thông tin về Quyền riêng tư vi phân và ý nghĩa của các tham số về quyền riêng tư, bạn có thể xem tài liệu.
Tham số tiện ích
Đây là những tham số không ảnh hưởng đến các đảm bảo về quyền riêng tư (miễn là bạn tuân thủ đúng lời khuyên về cách sử dụng Quyền riêng tư trên Beam) nhưng ảnh hưởng đến độ chính xác và do đó ảnh hưởng đến tính hữu ích của đầu ra. Các tham số này được cung cấp trong các cấu trúc Params của mỗi hoạt động tổng hợp, ví dụ: CountParams, SumParams, v.v. Các tham số này được dùng để điều chỉnh quy mô của nhiễu được thêm vào.
MaxPartitionsContributed là một tham số tiện ích được cung cấp trong Params và áp dụng cho tất cả các hoạt động tổng hợp. Phân vùng tương ứng với một khoá của PCollection do một thao tác tổng hợp Privacy On Beam xuất ra, tức là Count, SumPerKey, v.v. Do đó, 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 đầu ra. Nếu một người dùng đóng góp cho nhiều hơn MaxPartitionsContributed khoá trong dữ liệu cơ bản, thì một số đóng góp của người dùng đó sẽ bị loại bỏ để người dùng đóng góp chính xác MaxPartitionsContributed khoá.
Tương tự như MaxPartitionsContributed, hầu hết các hàm tổng hợp đều có tham số MaxContributionsPerPartition. Chúng được cung cấp trong các cấu trúc Params và mỗi hoạt động tổng hợp có thể có các giá trị riêng biệt cho chúng. Khác với MaxPartitionsContributed, MaxContributionsPerPartition giới hạn mức đó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 được thêm vào đầu ra được điều chỉnh theo MaxPartitionsContributed và MaxContributionsPerPartition, vì vậy, bạn cần cân nhắc ở đây: MaxPartitionsContributed và MaxContributionsPerPartition càng lớn thì bạn càng giữ được nhiều dữ liệu hơn, nhưng kết quả sẽ càng nhiễu.
Một số phép tổng hợp yêu cầu MinValue và MaxValue. Các quy tắc này chỉ định giới hạn cho nội dung đó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ẽ bị giới hạn ở 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 ranh giới lớn hơn. Tương tự như MaxPartitionsContributed và MaxContributionsPerPartition, nhiễu được điều chỉnh theo tỷ lệ kích thước của ranh giới, vì vậy, ranh giới càng lớn thì bạn càng giữ được nhiều dữ liệu hơn, nhưng kết quả sẽ có nhiều nhiễu hơn.
Tham số cuối cùng mà chúng ta sẽ nói đến là NoiseKind. Chúng tôi hỗ trợ 2 cơ chế nhiễu khác nhau trong Privacy On Beam: GaussianNoise và LaplaceNoise. Cả hai đều có ưu và nhược điểm riêng, nhưng phân phối Laplace mang lại phần mềm tiện ích tốt hơn với các giới hạn đóng góp thấp, đó là lý do Privacy On Beam sử dụng phân phối 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 bằng một 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 Beam. Bạn đã tìm hiểu được nhiều thông tin về sự riêng tư biệt lập và Quyền riêng tư trên Beam:
- Chuyển
PCollectionthànhPrivatePCollectionbằng cách gọiMakePrivateFromStruct. - Sử dụng
Countđể tính toán số lượng riêng tư vi phân. - 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 tổng riêng tư vi phân. - Tính toán nhiều số liệu thống kê bằng một
PrivacySpectrong một quy trình duy nhất. - (Không bắt buộc) Tuỳ chỉnh
PrivacySpecvà các thông số tổng hợp (CountParams, MeanParams, SumParams).
Tuy nhiên, bạn có thể thực hiện nhiều hoạt động tổng hợp khác (ví dụ: phân vị, đếm các giá trị riêng biệt) bằng tính năng Quyền riêng tư trên Beam! Bạn có thể tìm hiểu thêm về các hàm này trên kho lưu trữ GitHub hoặc godoc.
Nếu có thời gian, vui lòng điền vào bài khảo sát để gửi ý kiến phản hồi cho chúng tôi về lớp học lập trình này.