Moose - 3MoosePC with secret sharing; secure against semi-honest adversaries; focused on data processing and machine learning.
Moose 是一个安全的分布式数据流框架,由编译器、运行时、Python eDSL 和绑定组成。它适用于但不限于加密机器学习和数据处理。它已经准备好生产并且主要用 Rust 编写。
计算使用 eDSL 或通过 Rust API 编程来表达。数据流图中的每个操作都固定到一个位置,该位置代表物理机器或几种虚拟执行单元之一。Moose 目前包括对更简单的机器学习模型的支持,以及由安全多方计算 (MPC) 以复制秘密共享的形式支持的虚拟布局。请参阅docs.rs、examples、tutorials或我们的白皮书了解更多详细信息。
API 版本文档: https://docs.rs/moose/latest/moose/
setup:
moose针对所有计算都在位环MP-SPDZ中完成的三方使用算术复制秘密共享协议进行 基准测试。128Moose 默认使用复制的秘密共享运行,而在其中 MP-SPDZ可以作为replicated-ring-party.x可执行文件找到。
所有测试都使用 4 台机器 - 一台充当协调器,即编译并将计算发送给其他机器,而其余三台机器执行协议
使用的软件版本是moosev0.2.2 和MP-SPDZv0.3.2。
https://github.com/tf-encrypted/moose/blob/main/DEVELOP.md
pip install moose-python
cargo install moose
Debian/Ubuntu:sudo apt install libopenblas-dev
苹果系统:homebrew install openblas
https://hub.docker.com/r/tfencrypted/moose
import numpy as np
import pymoose as pm
alice = pm.host_placement("alice")
bob = pm.host_placement("bob")
carole = pm.host_placement("carole")
replicated = pm.replicated_placement("rep", players=[alice, bob, carole])
@pm.computation
def simple_computation(
x: pm.Argument(placement=alice, vtype=pm.TensorType(pm.float64)),
y: pm.Argument(placement=bob, vtype=pm.TensorType(pm.float64)),
):
with alice:
x = pm.cast(x, dtype=pm.fixed(14, 23))
with bob:
y = pm.cast(y, dtype=pm.fixed(14, 23))
with replicated:
z = pm.add(x, y)
with carole:
v = pm.cast(z, dtype=pm.float64)
return v
# 定义端口
runtime = pm.GrpcMooseRuntime({
alice: "localhost:50000",
bob: "localhost:50001",
carole: "localhost:50002",
})
runtime.set_default()
result = my_computation(
x=np.array([1.0, 2.0], dtype=np.float64),
y=np.array([3.0, 4.0], dtype=np.float64),
)
print(result)
线性回归
"""Example of linear regression training with label-split data."""
import argparse
import logging
import unittest
import numpy as np
import pytest
from absl.testing import parameterized
import pymoose as pm
from pymoose.logger import get_logger
FIXED = pm.fixed(8, 27)
# Rust compiler currently supports only limited set of alternative precisions:
# FIXED = pm.fixed(14, 23)
def generate_data(seed, n_instances, n_features, coeff=3, shift=10):
rng = np.random.default_rng()
x_data = rng.normal(size=(n_instances, n_features))
y_data = np.dot(x_data, np.ones(n_features) * coeff) + shift
return x_data, y_data
def mse(y_pred, y_true):
return pm.mean(pm.square(pm.sub(y_pred, y_true)), axis=0)
def mape(y_pred, y_true, y_true_inv):
return pm.abs(pm.mul(pm.sub(y_pred, y_true), y_true_inv))
def ss_res(y_pred, y_true):
squared_residuals = pm.square(pm.sub(y_true, y_pred))
return pm.sum(squared_residuals, axis=0)
def ss_tot(y_true):
y_mean = pm.mean(y_true)
squared_deviations = pm.square(pm.sub(y_true, y_mean))
return pm.sum(squared_deviations, axis=0)
def r_squared(ss_res, ss_tot):
residuals_ratio = pm.div(ss_res, ss_tot)
return pm.sub(pm.constant(1.0, dtype=pm.float64), residuals_ratio)
class LinearRegressionExample(parameterized.TestCase):
def _build_linear_regression_example(self, metric_name="mse"):
x_owner = pm.host_placement(name="x-owner")
y_owner = pm.host_placement(name="y-owner")
model_owner = pm.host_placement(name="model-owner")
replicated_plc = pm.replicated_placement(
players=[x_owner, y_owner, model_owner], name="replicated-plc"
)
@pm.computation
def my_comp(
x_uri: pm.Argument(placement=x_owner, vtype=pm.StringType()),
y_uri: pm.Argument(placement=y_owner, vtype=pm.StringType()),
w_uri: pm.Argument(placement=model_owner, vtype=pm.StringType()),
metric_uri: pm.Argument(placement=model_owner, vtype=pm.StringType()),
rsquared_uri: pm.Argument(placement=model_owner, vtype=pm.StringType()),
):
with x_owner:
X = pm.atleast_2d(
pm.load(x_uri, dtype=pm.float64), to_column_vector=True
)
# NOTE: what would be most natural to do is this:
# bias_shape = (slice(shape(X), begin=0, end=1), 1)
# bias = ones(bias_shape, dtype=float)
# but this raises an issue about accomodating python native values in
# the ASTTracer, something we've discussed and temporarily tabled in
# the past. For now, we've decided to implement squeeze and unsqueeze
# ops instead.
# But we have a feeling this issue will continue to come up!
bias_shape = pm.shape(X)[0:1]
bias = pm.ones(bias_shape, dtype=pm.float64)
reshaped_bias = pm.expand_dims(bias, 1)
X_b = pm.concatenate([reshaped_bias, X], axis=1)
A = pm.inverse(pm.dot(pm.transpose(X_b), X_b))
B = pm.dot(A, pm.transpose(X_b))
X_b = pm.cast(X_b, dtype=FIXED)
B = pm.cast(B, dtype=FIXED)
with y_owner:
y_true = pm.atleast_2d(
pm.load(y_uri, dtype=pm.float64), to_column_vector=True
)
if metric_name == "mape":
y_true_inv = pm.cast(
pm.div(pm.constant(1.0, dtype=pm.float64), y_true),
dtype=FIXED,
)
totals_ss = ss_tot(y_true)
y_true = pm.cast(y_true, dtype=FIXED)
with replicated_plc:
w = pm.dot(B, y_true)
y_pred = pm.dot(X_b, w)
if metric_name == "mape":
metric_result = mape(y_pred, y_true, y_true_inv)
else:
metric_result = mse(y_pred, y_true)
residuals_ss = ss_res(y_pred, y_true)
with model_owner:
residuals_ss = pm.cast(residuals_ss, dtype=pm.float64)
rsquared_result = r_squared(residuals_ss, totals_ss)
with model_owner:
w = pm.cast(w, dtype=pm.float64)
metric_result = pm.cast(metric_result, dtype=pm.float64)
res = (
pm.save(w_uri, w),
pm.save(metric_uri, metric_result),
pm.save(rsquared_uri, rsquared_result),
)
return res
return my_comp, (x_owner, y_owner, model_owner, replicated_plc)
def _linear_regression_eval(self, metric_name):
linear_comp, _ = self._build_linear_regression_example(metric_name)
x_data, y_data = generate_data(seed=42, n_instances=10, n_features=1)
executors_storage = {
"x-owner": {"x_data": x_data},
"y-owner": {"y_data": y_data},
}
runtime = pm.LocalMooseRuntime(
["x-owner", "y-owner", "model-owner"],
storage_mapping=executors_storage,
)
_ = runtime.evaluate_computation(
computation=linear_comp,
arguments={
"x_uri": "x_data",
"y_uri": "y_data",
"w_uri": "regression_weights",
"metric_uri": "metric_result",
"rsquared_uri": "rsquared_result",
},
)
print(
"Done: \n",
runtime.read_value_from_storage("model-owner", "regression_weights"),
)
def test_linear_regression_mse(self):
self._linear_regression_eval("mse")
@pytest.mark.slow
def test_linear_regression_mape(self):
self._linear_regression_eval("mape")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run example")
parser.add_argument("--verbose", action="store_true")
args = parser.parse_args()
if args.verbose:
get_logger().setLevel(level=logging.DEBUG)
unittest.main()