Skip to content

Commit

Permalink
C++DP Lib: Set stddev directly in Gaussian mechanism
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 504483071
Change-Id: Ie4a40fadaa94921554e858e368b103eb5dd787c2
GitOrigin-RevId: 932d7ff43c77a0cc2035ad1a1eb11005341a9436
  • Loading branch information
Differential Privacy Team authored and MashaTelyatnikova committed Jan 31, 2023
1 parent f2e31b8 commit 2a1e554
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 2 deletions.
25 changes: 23 additions & 2 deletions cc/algorithms/numerical-mechanisms.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,24 @@ GaussianMechanism::Builder::Build() {
ASSIGN_OR_RETURN(std::unique_ptr<internal::GaussianDistribution> distro,
builder.SetStddev(1).Build());

if (stddev_.has_value()) {
if (GetEpsilon().has_value() || GetDelta().has_value() ||
GetL0Sensitivity().has_value() || GetLInfSensitivity().has_value() ||
l2_sensitivity_.has_value()) {
return absl::InvalidArgumentError(
"If standard deviation is set directly it must be the only "
"parameter.");
}
if (!std::isfinite(stddev_.value()) || stddev_.value() < 0) {
return absl::InvalidArgumentError(
"Standard deviation must be finite and positive.");
}
std::unique_ptr<NumericalMechanism> result =
absl::make_unique<GaussianMechanism>(stddev_.value(),
std::move(distro));
return result;
} // Else construct from DP parameters.

absl::optional<double> epsilon = GetEpsilon();
RETURN_IF_ERROR(ValidateIsFiniteAndPositive(epsilon, "Epsilon"));
RETURN_IF_ERROR(DeltaIsSetAndValid());
Expand Down Expand Up @@ -337,12 +355,15 @@ double GaussianMechanism::CalculateStddev(double epsilon, double delta,
}

double GaussianMechanism::CalculateStddev() const {
if (stddev_.has_value()) {
return stddev_.value();
}

return CalculateStddev(GetEpsilon(), delta_, l2_sensitivity_);
}

double GaussianMechanism::AddDoubleNoise(double result) {
double stddev = CalculateStddev(GetEpsilon(), delta_, l2_sensitivity_);
double stddev = CalculateStddev();
double sample = standard_gaussian_->Sample(stddev);

return RoundToNearestMultiple(result,
Expand All @@ -351,7 +372,7 @@ double GaussianMechanism::AddDoubleNoise(double result) {
}

int64_t GaussianMechanism::AddInt64Noise(int64_t result) {
double stddev = CalculateStddev(GetEpsilon(), delta_, l2_sensitivity_);
double stddev = CalculateStddev();
double sample = standard_gaussian_->Sample(stddev);

SafeOpResult<int64_t> noise_cast_result =
Expand Down
26 changes: 26 additions & 0 deletions cc/algorithms/numerical-mechanisms.h
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,20 @@ class GaussianMechanism : public NumericalMechanism {
return *this;
}

// Allows users to set the noise standard deviation directly, skipping
// the calculations based on differential privacy parameters. Only use this
// if you know what you're doing - the code will apply the exact amount of
// noise you specify, which might not be enough to achieve your desired
// privacy guarantee.
//
// Either this, or the differential privacy parameters (epsilon and delta)
// should be specified. In case both are specified, this method will return
// an error.
Builder& SetStandardDeviation(double stddev) {
stddev_ = stddev;
return *this;
}

absl::StatusOr<std::unique_ptr<NumericalMechanism>> Build() override;

std::unique_ptr<NumericalMechanismBuilder> Clone() const override {
Expand All @@ -326,6 +340,7 @@ class GaussianMechanism : public NumericalMechanism {

protected:
absl::optional<double> l2_sensitivity_;
absl::optional<double> stddev_;

private:
// Returns the l2 sensitivity when it has been set or returns an upper bound
Expand All @@ -348,6 +363,16 @@ class GaussianMechanism : public NumericalMechanism {
standard_gaussian_ = std::move(standard_gaussian);
}

ABSL_DEPRECATED(
"Use GaussianMechanism::Builder instead of GaussianMechanism "
"constructor.")
GaussianMechanism(
double stddev,
std::unique_ptr<internal::GaussianDistribution> standard_gaussian)
: NumericalMechanism(0), delta_(0), l2_sensitivity_(0), stddev_(stddev) {
standard_gaussian_ = std::move(standard_gaussian);
}

// Deserialize the GaussianMechanism from a proto.
static absl::StatusOr<std::unique_ptr<NumericalMechanism>> Deserialize(
const serialization::GaussianMechanism& proto);
Expand Down Expand Up @@ -430,6 +455,7 @@ class GaussianMechanism : public NumericalMechanism {
const double delta_;
const double l2_sensitivity_;
std::unique_ptr<internal::GaussianDistribution> standard_gaussian_;
absl::optional<double> stddev_;
};

// Mechanism builder that returns the mechanism with minimum variance for given
Expand Down
56 changes: 56 additions & 0 deletions cc/algorithms/numerical-mechanisms_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,49 @@ TEST(NumericalMechanismsTest, GaussianBuilderFailsCalculatedL2SensitivityZero) {
"^The calculated L2 sensitivity must be positive and finite.*"));
}

TEST(NumericalMechanismsTest, GaussianBuilderFailsWithStddevAndOtherParams) {
GaussianMechanism::Builder test_builder;
auto failed_build =
test_builder.SetStandardDeviation(1).SetEpsilon(1).Build();
EXPECT_THAT(failed_build.status().code(),
Eq(absl::StatusCode::kInvalidArgument));
std::string message(failed_build.status().message());
EXPECT_THAT(message, MatchesRegex("^If standard deviation is set directly it "
"must be the only parameter.*"));

auto failed_build2 =
test_builder.SetStandardDeviation(1).SetDelta(0.1).Build();
EXPECT_THAT(failed_build2.status().code(),
Eq(absl::StatusCode::kInvalidArgument));
message = failed_build2.status().message();
EXPECT_THAT(message, MatchesRegex("^If standard deviation is set directly it "
"must be the only parameter.*"));

auto failed_build3 =
test_builder.SetStandardDeviation(1).SetL0Sensitivity(1).Build();
EXPECT_THAT(failed_build3.status().code(),
Eq(absl::StatusCode::kInvalidArgument));
message = failed_build3.status().message();
EXPECT_THAT(message, MatchesRegex("^If standard deviation is set directly it "
"must be the only parameter.*"));

auto failed_build4 =
test_builder.SetStandardDeviation(1).SetLInfSensitivity(1).Build();
EXPECT_THAT(failed_build4.status().code(),
Eq(absl::StatusCode::kInvalidArgument));
message = failed_build4.status().message();
EXPECT_THAT(message, MatchesRegex("^If standard deviation is set directly it "
"must be the only parameter.*"));

auto failed_build5 =
test_builder.SetStandardDeviation(1).SetL2Sensitivity(1).Build();
EXPECT_THAT(failed_build5.status().code(),
Eq(absl::StatusCode::kInvalidArgument));
message = failed_build5.status().message();
EXPECT_THAT(message, MatchesRegex("^If standard deviation is set directly it "
"must be the only parameter.*"));
}

TEST(NumericalMechanismsTest, GaussianMechanismAddsNoise) {
GaussianMechanism mechanism(1.0, 0.5, 1.0);

Expand Down Expand Up @@ -1033,6 +1076,19 @@ TEST(NumericalMechanismsTest, GaussianVarianceReturnsWallysResult) {
EXPECT_NEAR(mechanism->get()->GetVariance(), std::pow(17.922, 2), 0.5);
}

TEST(NumericalMechanismsTest, GaussianSetStddev) {
absl::StatusOr<std::unique_ptr<NumericalMechanism>> mechanism =
GaussianMechanism::Builder().SetStandardDeviation(3.5).Build();
ASSERT_OK(mechanism.status());

std::vector<double> samples;
for (int i = 0; i < kNumSamples; ++i) {
samples.push_back((*mechanism)->AddNoise(0));
}

EXPECT_NEAR(StandardDev(samples), 3.5, 0.1);
}

TEST(NumericalMechanismsTest, LaplaceMechanismSerialization) {
const double epsilon = std::log(3);
const double l0 = 2;
Expand Down

0 comments on commit 2a1e554

Please sign in to comment.