From 7d498f68dc548ed824e6a4da676eef9a48b721b2 Mon Sep 17 00:00:00 2001 From: Gilang Ramadhan Ilhami Date: Tue, 17 May 2022 22:38:18 +0700 Subject: [PATCH] SKLearn Pipeine use LaplaceDistribution Class from Google DP (#408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added machine learning and mechanisms directory * Added files for utils Files include: - acountant.py (for BudgetAccountant purposes) - utils.py - validation.py * Directory change for machine learning & mechanism Anything related to machine learning and mechanism is located in the "ml" directory * Added function to check epsilon delta in validation.py * Added function to check min max bounds in validation.py * Added function to clip 2-d array to given maximum norm in validation.py * Added function to clip 2-d array to given bounds in validation.py * Added gunction to set global seed in utils.py * Added function to copy docstring in ml/util/utils.py * Added subclass Budget for privacy budgets epsilon delta * Add class BudgetError for exceeded privacy budget error * Added class for privacy leakage warning * Added class for incompatible arguments PyDP * Added function for warning incompitable argument * Added class for budget accountant The class added main goal is to allocate privacy budget * Added file as base for mechanism and add base class * Added methods for base class * Added class for truncated and folded mechanism * Added files for laplace & geometric mechanism * Added class as main laplace mechanism * Added class for truncated laplacian * Added class for folder laplacian * Added class for main geometric mechanism * Added class for truncated geometric * Added file for naive bayes model with model class * FIX: Move ml directory to src/pydp directory * FEAT: Added LaplaceBoundedDomain class in laplce mechanism file * Added folder that contains machine learning capabilities * Apply Python and C++ styling * Added type:ignored on unresolved imports * Python formatting with Black after adding type:ignore * Fix mypy test errors in ml directory * Fix apply Python format with blac * Add source link to ml directory files * Removed extra special characters * Added example notebook for Naive Bayes implementation * Added Jupyter Notebook for PyDP NB comparison * Moved matplotlib as dev dependincy * change setup msbuild to version 1.0.2 * Test remove python 3.5 in Windows workflow * Add back python 3.5 for Windows build * Remove python-dp in docs/requirements.txt * Change version to cehck build doc action * conf.py in /docs append pydp build path * Change ubuntu version for build docs action * Change Python to 3.6 in build docs action * Revert back ubuntu and Python versions * Add pre-build-command in sphinx-action@master * Fix pre-build-command syntax * Change pre-build-comman for sphinx-action * pre-build-command install software-properties-common * Fix pre-build-command * Fix pre-build-command * Fix pre-build-command * Change ubuntu version for build doc action * Change Python Version to 3.8 * Removed test limited to dev branch. This test would run on all Pull requests now rather than just PR to dev. Other than that, tests are to be triggered if there's a change in ipynb too. * Wrapper for numerical mechanism class [Continuation from #372] (#380) * initial addition of partition selection * corrected instantiation to 'builder.Build' instead * build works, TODO: deal with passing Laplace/Gaussian mechanims builders * post-review changes, moved partition selection python related code to own submodule * fixed some formatting * Added documentation * post formatting * added tests for partition selection * moved imports for patition_selection * clarified TODO dependency * attempt to resolve linting issues * * Added exports in algorithms.partition_selection * Replaced the Create*PartitionStrategy functions with a template function and instantiantions * revert a to latest stable commit * restore the correct commit for google-dp submodule * added python bindings for numerical mechanisms * added some python files * fixed prereqs_linux.sh script * saving changes * build + tests work * added docs for numerical mechanisms * Fix Bazel build Bazel failed to build since the name of the workspace was inconsistent with the DP Library. This commit also fixes the order in which we install dependencies of the DP Lib. Additionally, on Linux we used the wrong compiler flags, which is now fixed in the .bazelrc. * Revert "Fix Bazel build" * Update README.md (#385) * Update README.md Changed "Currently supports Linux and macOS (Windows support coming soon)" to "Compatible with all three types of Operating Systems - Linux, macOS, and Windows" * Update README.md Changed "Currently supports Linux and macOS (Windows support coming soon)" to "Compatible with all three types of Operating Systems - Linux, macOS, and Windows" * sha upgrade and added temporary fix for bazel reference * upgraded SHA * removal of functions and dependency in accordance with SHA upgrade * fixed build file reference issue for test cases * build file reference fix * Made the system release ready * Fixed the ubuntu version for google collab * fixed the BUILD file naming * c++ styling * fixed the RC version * Attempt to trigger the find replace in BUILD file * added find replace in version scripts * updated the release number to 1.1.1 * Fixed the bazel build absolute reference * removed the temporary fix in the build actions * fix * fix * Removing unused code (#400) * clean-ups * tiny fix * format * format c++ * Add support for categorical features for Laplace mechanism * Clean-ups (#401) Removing unused code and using c++ style guidelines * Improve Laplace Demo Notebook and add minimal README (#404) * Improve Laplace Demo NB and add minimal README * Improve the Conclusion Section * Add back the old conclusion * Removed partial privacy budget + Update Google C++ DP library to the latest commit and fix compilation errors (#405) * update * update * remove unintended changed * tests * FIX: Change Google DP Commit Hash * FEAT: SKLearn Laplace Mechanism use Google DP Modifie the `LaplaceMechanism`class in `/src/pydp/ml/mechanisms/sklearn_pipeline.py` so that it uses `LaplaceDistribution` class from Google DP` * Fix typo in build-docs.yml * FIX: Stling pythong and cpp * FIX: Styling python and cpp * FIX: Typo in build-docs.yml * FIX: Changed clang-format version * FIX: Try clang-format 12 * Update to the latest version of Google building block library (#415) * remove privacy budget * fixes * fixes * tests * FIX: Change clang format action version * Reset HEAD Co-authored-by: Chinmay Shah Co-authored-by: Lev Zlotnik <46742999+levzlotnik@users.noreply.github.com> Co-authored-by: Christoph Dibak Co-authored-by: Abin (אבין ברגיס) <36173893+Spartan-119@users.noreply.github.com> Co-authored-by: FIRhinmay Co-authored-by: Vadym Doroshenko Co-authored-by: dvadym <53558779+dvadym@users.noreply.github.com> Co-authored-by: Saurav Maheshkar --- .github/workflows/build-docs.yml | 7 +- .github/workflows/publish.yml | 330 ++++++++-------- .github/workflows/tests.yml | 373 +++++++++--------- .github/workflows/versions.yml | 2 +- Dockerfile | 160 ++++---- README.md | 3 +- WORKSPACE | 18 +- docs/conf.py | 2 + docs/pydp.rst | 13 +- docs/requirements.txt | 1 - .../SKLearn_Pipeline_Laplace_Mechanism.ipynb | 111 +++++- examples/laplace_demo/README.md | 3 + examples/laplace_demo/laplace.ipynb | 122 +++--- prereqs_linux.sh | 12 +- prereqs_mac.sh | 125 +++--- setup.cfg | 2 +- setup.py | 2 +- src/bindings/BUILD | 62 +-- .../PyDP/algorithms/bounded_functions.cpp | 3 - src/bindings/PyDP/algorithms/count.cpp | 2 - .../PyDP/algorithms/distributions.cpp | 1 - .../PyDP/algorithms/order_statistics.cpp | 2 - src/bindings/PyDP/algorithms/rand.cpp | 1 - src/bindings/PyDP/algorithms/util.cpp | 79 ++-- src/bindings/PyDP/base/logging.cpp | 3 +- src/bindings/PyDP/base/status.cpp | 225 ++++++----- src/bindings/PyDP/bindings.cpp | 6 + src/bindings/PyDP/mechanisms/mechanism.cpp | 154 +++++++- src/bindings/PyDP/proto/proto.cpp | 12 +- .../PyDP/pydp_lib/algorithm_builder.hpp | 17 +- src/bindings/PyDP/pydp_lib/casting.hpp | 13 - src/bindings/PyDP/pydp_lib/helper_class.hpp | 46 --- src/pydp/__init__.py | 2 +- src/pydp/algorithms/__init__.py | 3 +- src/pydp/algorithms/_algorithm.py | 22 +- src/pydp/algorithms/numerical_mechanisms.py | 6 + src/pydp/ml/mechanisms/sklearn_pipeline.py | 193 ++++++--- tests/algorithms/conftest.py | 60 --- .../test_bounded_mean_int64_data.bin | 2 - tests/algorithms/test_count.py | 3 - tests/algorithms/test_numerical_mechanisms.py | 84 ++++ tests/algorithms/test_order_statistics.py | 13 +- tests/algorithms/test_partition_selection.py | 1 - third_party/differential-privacy | 2 +- 44 files changed, 1279 insertions(+), 1024 deletions(-) create mode 100644 examples/laplace_demo/README.md delete mode 100644 src/bindings/PyDP/pydp_lib/casting.hpp delete mode 100644 src/bindings/PyDP/pydp_lib/helper_class.hpp create mode 100644 src/pydp/algorithms/numerical_mechanisms.py delete mode 100644 tests/algorithms/conftest.py delete mode 100644 tests/algorithms/test_bounded_mean/test_bounded_mean_int64_data.bin create mode 100644 tests/algorithms/test_numerical_mechanisms.py diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 2d197a7e..69765484 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -11,8 +11,8 @@ jobs: fail-fast: false max-parallel: 1 matrix: - os: [ubuntu-latest] - python-version: [3.9] + os: [ubuntu-18.04] + python-version: [3.8] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -91,6 +91,7 @@ jobs: - uses: ammaraskar/sphinx-action@master with: docs-folder: "docs/" + # pre-build-command: "apt-get -y update && apt-get -y install gcc" - name: Commit documentation changes run: | @@ -109,4 +110,4 @@ jobs: branch: gh-pages directory: gh-pages force: true - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4f35c915..0dcc8708 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,165 +1,165 @@ -name: Publish PyDP - -on: - release: - types: [published] - -jobs: - deploy: - strategy: - fail-fast: false - max-parallel: 12 - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Setup msbuild Windows - if: runner.os == 'Windows' - uses: microsoft/setup-msbuild@v1.0.2 - - - name: Fix Paths Windows - # Make sure that tar.exe from Git is used not from Windows - if: runner.os == 'Windows' - run: | - @("C:\Program Files\Git\usr\bin") + (Get-Content $env:GITHUB_PATH) | Set-Content $env:GITHUB_PATH -Encoding utf8 - - - name: Cache Bazel Unix - # Not working on Windows: https://github.com/actions/cache/issues/576 - if: runner.os != 'Windows' - uses: actions/cache@v2.1.5 - with: - path: | - ./bazel-PyDP - ./bazel-bin - ./bazel-cache - ./bazel-out - key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} - - - name: Update environment variables Windows - if: runner.os == 'Windows' - # See: - # - On Bazel cache: https://docs.bazel.build/versions/master/output_directories.html - run: | - echo "BAZEL_CACHE_DIR=$env:USERPROFILE\_bazel_$env:USERNAME" >> $env:GITHUB_ENV - - - name: Cache Bazel Windows - if: runner.os == 'Windows' - # https://stackoverflow.com/questions/66870002/github-actions-cache-maven-m2-repository-on-windows-environment-c-users-run - uses: actions/cache@v2.1.5 - with: - path: | - ./bazel-cache - key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} - - - name: Build Google DP Unix - if: runner.os != 'Windows' - timeout-minutes: 20 - run: | - PYTHONHOME=$(which python) - PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);") - BAZEL_CONFIG_OS=$(python -c "print('${{ matrix.os }}'.split('-')[0].lower().replace('ubuntu', 'linux'))") - echo "Running: ${{ matrix.os }}" - echo "Using BAZEL_CONFIG_OS: $BAZEL_CONFIG_OS" - bazel --output_base ./bazel-cache build src/python:pydp \ - --config $BAZEL_CONFIG_OS \ - --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME \ - --action_env=PYTHON_LIB_PATH=$PYTHONPATH - cp -f ./bazel-bin/src/bindings/_pydp.so ./src/pydp - - - name: Build Google DP Windows - if: runner.os == 'Windows' - timeout-minutes: 20 - run: | - $PYTHONHOME=$(python -c 'import sys; print(sys.executable);').replace('\', '/') - $PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);").replace('\', '/') - echo "PYTHONHOME=$PYTHONHOME" - echo "PYTHONPATH=$PYTHONPATH" - echo "Running: ${{ matrix.os }}" - bazel.exe --output_base ./bazel-cache build src/python:pydp --config windows --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME --action_env=PYTHON_LIB_PATH=$PYTHONPATH - copy ./bazel-bin/src/bindings/_pydp.so ./src/pydp/_pydp.pyd - - - name: Upgrade pip - run: | - pip install --upgrade --user pip - - - name: Install Poetry - run: | - pip install poetry - - - name: Get poetry cache dir - id: poetry-cache - run: | - echo "::set-output name=dir::$(poetry config cache-dir)" - - - name: poetry cache - uses: actions/cache@v2 - with: - path: ${{ steps.poetry-cache.outputs.dir }} - key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-pip-py${{ matrix.python-version }}- - - - name: Install dependencies - run: | - poetry install - - - name: Build PyDP macOS - if: runner.os == 'macOS' - run: | - poetry run python setup.py build bdist_wheel --plat-name macosx_10_14_x86_64 - - - name: Build PyDP Linux / Windows - if: runner.os != 'macOS' - run: | - poetry run python setup.py build bdist_wheel - - - name: Install Wheel Unix - if: runner.os != 'Windows' - run: | - pip install `find -L ./ -name "*.whl"` - - - name: Install Wheel Windows - if: runner.os == 'Windows' - run: | - Get-ChildItem -Path ./ -Filter "*.whl" -Recurse -File | foreach {pip install $_.FullName} - - - name: Import Package - run: | - python -c "import pydp; print(pydp.__version__)" - - - name: Run Pytest - run: | - poetry run pytest tests -n auto - - - name: Check Wheel Unix - if: runner.os != 'Windows' - run: | - poetry run twine check `find -L ./ -name "*.whl"` - - - name: Check Wheel Windows - if: runner.os == 'Windows' - run: | - Get-ChildItem -Path ./ -Filter "*.whl" -Recurse -File | foreach {poetry run twine check $_.FullName} - - - name: Renaming wheel - if: runner.os == 'Linux' - run: | - find . -name '*linux*.whl' -type f -exec bash -c 'mv "$1" "${1/linux/manylinux1}"' -- {} \; - - - name: Publishing the wheel - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TOKEN }} - run: | - poetry run twine upload --skip-existing dist/*.whl +name: Publish PyDP + +on: + release: + types: [published] + +jobs: + deploy: + strategy: + fail-fast: false + max-parallel: 12 + matrix: + os: [ubuntu-18.04, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Setup msbuild Windows + if: runner.os == 'Windows' + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Fix Paths Windows + # Make sure that tar.exe from Git is used not from Windows + if: runner.os == 'Windows' + run: | + @("C:\Program Files\Git\usr\bin") + (Get-Content $env:GITHUB_PATH) | Set-Content $env:GITHUB_PATH -Encoding utf8 + + - name: Cache Bazel Unix + # Not working on Windows: https://github.com/actions/cache/issues/576 + if: runner.os != 'Windows' + uses: actions/cache@v2.1.5 + with: + path: | + ./bazel-PyDP + ./bazel-bin + ./bazel-cache + ./bazel-out + key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} + + - name: Update environment variables Windows + if: runner.os == 'Windows' + # See: + # - On Bazel cache: https://docs.bazel.build/versions/master/output_directories.html + run: | + echo "BAZEL_CACHE_DIR=$env:USERPROFILE\_bazel_$env:USERNAME" >> $env:GITHUB_ENV + + - name: Cache Bazel Windows + if: runner.os == 'Windows' + # https://stackoverflow.com/questions/66870002/github-actions-cache-maven-m2-repository-on-windows-environment-c-users-run + uses: actions/cache@v2.1.5 + with: + path: | + ./bazel-cache + key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} + + - name: Build Google DP Unix + if: runner.os != 'Windows' + timeout-minutes: 20 + run: | + PYTHONHOME=$(which python) + PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);") + BAZEL_CONFIG_OS=$(python -c "print('${{ matrix.os }}'.split('-')[0].lower().replace('ubuntu', 'linux'))") + echo "Running: ${{ matrix.os }}" + echo "Using BAZEL_CONFIG_OS: $BAZEL_CONFIG_OS" + bazel --output_base ./bazel-cache build src/python:pydp \ + --config $BAZEL_CONFIG_OS \ + --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME \ + --action_env=PYTHON_LIB_PATH=$PYTHONPATH + cp -f ./bazel-bin/src/bindings/_pydp.so ./src/pydp + + - name: Build Google DP Windows + if: runner.os == 'Windows' + timeout-minutes: 20 + run: | + $PYTHONHOME=$(python -c 'import sys; print(sys.executable);').replace('\', '/') + $PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);").replace('\', '/') + echo "PYTHONHOME=$PYTHONHOME" + echo "PYTHONPATH=$PYTHONPATH" + echo "Running: ${{ matrix.os }}" + bazel.exe --output_base ./bazel-cache build src/python:pydp --config windows --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME --action_env=PYTHON_LIB_PATH=$PYTHONPATH + copy ./bazel-bin/src/bindings/_pydp.so ./src/pydp/_pydp.pyd + + - name: Upgrade pip + run: | + pip install --upgrade --user pip + + - name: Install Poetry + run: | + pip install poetry + + - name: Get poetry cache dir + id: poetry-cache + run: | + echo "::set-output name=dir::$(poetry config cache-dir)" + + - name: poetry cache + uses: actions/cache@v2 + with: + path: ${{ steps.poetry-cache.outputs.dir }} + key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip-py${{ matrix.python-version }}- + + - name: Install dependencies + run: | + poetry install + + - name: Build PyDP macOS + if: runner.os == 'macOS' + run: | + poetry run python setup.py build bdist_wheel --plat-name macosx_10_14_x86_64 + + - name: Build PyDP Linux / Windows + if: runner.os != 'macOS' + run: | + poetry run python setup.py build bdist_wheel + + - name: Install Wheel Unix + if: runner.os != 'Windows' + run: | + pip install `find -L ./ -name "*.whl"` + + - name: Install Wheel Windows + if: runner.os == 'Windows' + run: | + Get-ChildItem -Path ./ -Filter "*.whl" -Recurse -File | foreach {pip install $_.FullName} + + - name: Import Package + run: | + python -c "import pydp; print(pydp.__version__)" + + - name: Run Pytest + run: | + poetry run pytest tests -n auto + + - name: Check Wheel Unix + if: runner.os != 'Windows' + run: | + poetry run twine check `find -L ./ -name "*.whl"` + + - name: Check Wheel Windows + if: runner.os == 'Windows' + run: | + Get-ChildItem -Path ./ -Filter "*.whl" -Recurse -File | foreach {poetry run twine check $_.FullName} + + - name: Renaming wheel + if: runner.os == 'Linux' + run: | + find . -name '*linux*.whl' -type f -exec bash -c 'mv "$1" "${1/linux/manylinux1}"' -- {} \; + + - name: Publishing the wheel + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TOKEN }} + run: | + poetry run twine upload --skip-existing dist/*.whl diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index df3f46cb..bde7b05b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,187 +1,186 @@ -name: Tests - -on: - pull_request: - branches: - - dev - paths: - - "*.bazel" - - "*.cpp" - - "*.c" - - "*.cc" - - "*.hpp" - - "*.h" - - "*.py" - - "*.go" - - "*.mod" - - "*.toml" - - "*.txt" - - "setup.cfg" - - ".github/workflows/*.yml" - -jobs: - linting: - runs-on: ubuntu-latest - strategy: - max-parallel: 1 - matrix: - python-version: [3.9] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install python dependencies - run: | - pip install black mypy - - name: Black - run: | - black . - - name: MyPY - run: | - mypy src tests - - name: Run clang-format style check for C/C++ programs. - uses: jidicula/clang-format-action@v3.3.0 - with: - clang-format-version: "11" - check-path: "/src/bindings/" - fallback-style: "Google" # optional - - build: - runs-on: ${{ matrix.os }} - needs: [linting] - strategy: - fail-fast: false - max-parallel: 3 - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.9] - - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Setup msbuild Windows - if: runner.os == 'Windows' - uses: microsoft/setup-msbuild@v1.0.2 - - - name: Fix Paths Windows - # Make sure that tar.exe from Git is used not from Windows - if: runner.os == 'Windows' - run: | - @("C:\Program Files\Git\usr\bin") + (Get-Content $env:GITHUB_PATH) | Set-Content $env:GITHUB_PATH -Encoding utf8 - - - name: Cache Bazel Unix - # Not working on Windows: https://github.com/actions/cache/issues/576 - if: runner.os != 'Windows' - uses: actions/cache@v2.1.5 - with: - path: | - ./bazel-PyDP - ./bazel-bin - ./bazel-cache - ./bazel-out - key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} - - - name: Update environment variables Windows - if: runner.os == 'Windows' - # See: - # - On Bazel cache: https://docs.bazel.build/versions/master/output_directories.html - run: | - echo "BAZEL_CACHE_DIR=$env:USERPROFILE\_bazel_$env:USERNAME" >> $env:GITHUB_ENV - - - name: Cache Bazel Windows - if: runner.os == 'Windows' - # https://stackoverflow.com/questions/66870002/github-actions-cache-maven-m2-repository-on-windows-environment-c-users-run - uses: actions/cache@v2.1.5 - with: - path: | - ./bazel-cache - key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} - - - name: Build Google DP Unix - if: runner.os != 'Windows' - timeout-minutes: 20 - run: | - PYTHONHOME=$(which python) - PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);") - BAZEL_CONFIG_OS=$(python -c "print('${{ matrix.os }}'.split('-')[0].lower().replace('ubuntu', 'linux'))") - echo "Running: ${{ matrix.os }}" - echo "Using BAZEL_CONFIG_OS: $BAZEL_CONFIG_OS" - bazel --output_base ./bazel-cache build src/python:pydp \ - --config $BAZEL_CONFIG_OS \ - --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME \ - --action_env=PYTHON_LIB_PATH=$PYTHONPATH - cp -f ./bazel-bin/src/bindings/_pydp.so ./src/pydp - - - name: Build Google DP Windows - if: runner.os == 'Windows' - timeout-minutes: 20 - run: | - $PYTHONHOME=$(python -c 'import sys; print(sys.executable);').replace('\', '/') - $PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);").replace('\', '/') - echo "PYTHONHOME=$PYTHONHOME" - echo "PYTHONPATH=$PYTHONPATH" - echo "Running: ${{ matrix.os }}" - bazel.exe --output_base ./bazel-cache build src/python:pydp --config windows --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME --action_env=PYTHON_LIB_PATH=$PYTHONPATH - copy ./bazel-bin/src/bindings/_pydp.so ./src/pydp/_pydp.pyd - - - name: Upgrade pip - run: | - pip install --upgrade --user pip - - - name: Install Poetry - run: | - pip install poetry - - - name: Get poetry cache dir - id: poetry-cache - run: | - echo "::set-output name=dir::$(poetry config cache-dir)" - - - name: poetry cache - uses: actions/cache@v2 - with: - path: ${{ steps.poetry-cache.outputs.dir }} - key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-pip-py${{ matrix.python-version }}- - - - name: Install dependencies - run: | - poetry install - - - name: Build PyDP macOS - if: runner.os == 'macOS' - run: | - poetry run python setup.py build bdist_wheel --plat-name macosx_10_14_x86_64 - - - name: Build PyDP Linux / Windows - if: runner.os != 'macOS' - run: | - poetry run python setup.py build bdist_wheel - - - name: Install Wheel Unix - if: runner.os != 'Windows' - run: | - pip install `find -L ./ -name "*.whl"` - - - name: Install Wheel Windows - if: runner.os == 'Windows' - run: | - Get-ChildItem -Path ./ -Filter "*.whl" -Recurse -File | foreach {pip install $_.FullName} - - - name: Import Package - run: | - python -c "import pydp; print(pydp.__version__)" - - - name: Run Pytest - run: | - poetry run pytest tests -n auto +name: Tests + +on: + pull_request: + paths: + - "*.bazel" + - "*.cpp" + - "*.c" + - "*.cc" + - "*.hpp" + - "*.h" + - "*.py" + - "*.ipynb" + - "*.go" + - "*.mod" + - "*.toml" + - "*.txt" + - "setup.cfg" + - ".github/workflows/*.yml" + +jobs: + linting: + runs-on: ubuntu-latest + strategy: + max-parallel: 1 + matrix: + python-version: [3.9] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install python dependencies + run: | + pip install black mypy + - name: Black + run: | + black . + - name: MyPY + run: | + mypy src tests + - name: Run clang-format style check for C/C++ programs. + uses: jidicula/clang-format-action@v4.5.0 + with: + clang-format-version: "11" + check-path: "/src/bindings/" + fallback-style: "Google" # optional + + build: + runs-on: ${{ matrix.os }} + needs: [linting] + strategy: + fail-fast: false + max-parallel: 3 + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.9] + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Setup msbuild Windows + if: runner.os == 'Windows' + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Fix Paths Windows + # Make sure that tar.exe from Git is used not from Windows + if: runner.os == 'Windows' + run: | + @("C:\Program Files\Git\usr\bin") + (Get-Content $env:GITHUB_PATH) | Set-Content $env:GITHUB_PATH -Encoding utf8 + + - name: Cache Bazel Unix + # Not working on Windows: https://github.com/actions/cache/issues/576 + if: runner.os != 'Windows' + uses: actions/cache@v2.1.5 + with: + path: | + ./bazel-PyDP + ./bazel-bin + ./bazel-cache + ./bazel-out + key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} + + - name: Update environment variables Windows + if: runner.os == 'Windows' + # See: + # - On Bazel cache: https://docs.bazel.build/versions/master/output_directories.html + run: | + echo "BAZEL_CACHE_DIR=$env:USERPROFILE\_bazel_$env:USERNAME" >> $env:GITHUB_ENV + + - name: Cache Bazel Windows + if: runner.os == 'Windows' + # https://stackoverflow.com/questions/66870002/github-actions-cache-maven-m2-repository-on-windows-environment-c-users-run + uses: actions/cache@v2.1.5 + with: + path: | + ./bazel-cache + key: ${{ runner.os }}-bazel-${{ hashFiles('WORKSPACE') }} + + - name: Build Google DP Unix + if: runner.os != 'Windows' + timeout-minutes: 20 + run: | + PYTHONHOME=$(which python) + PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);") + BAZEL_CONFIG_OS=$(python -c "print('${{ matrix.os }}'.split('-')[0].lower().replace('ubuntu', 'linux'))") + echo "Running: ${{ matrix.os }}" + echo "Using BAZEL_CONFIG_OS: $BAZEL_CONFIG_OS" + bazel --output_base ./bazel-cache build src/python:pydp \ + --config $BAZEL_CONFIG_OS \ + --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME \ + --action_env=PYTHON_LIB_PATH=$PYTHONPATH + cp -f ./bazel-bin/src/bindings/_pydp.so ./src/pydp + + - name: Build Google DP Windows + if: runner.os == 'Windows' + timeout-minutes: 20 + run: | + $PYTHONHOME=$(python -c 'import sys; print(sys.executable);').replace('\', '/') + $PYTHONPATH=$(python -c "import sys; print([x for x in sys.path if 'site-packages' in x][0]);").replace('\', '/') + echo "PYTHONHOME=$PYTHONHOME" + echo "PYTHONPATH=$PYTHONPATH" + echo "Running: ${{ matrix.os }}" + bazel.exe --output_base ./bazel-cache build src/python:pydp --config windows --verbose_failures --action_env=PYTHON_BIN_PATH=$PYTHONHOME --action_env=PYTHON_LIB_PATH=$PYTHONPATH + copy ./bazel-bin/src/bindings/_pydp.so ./src/pydp/_pydp.pyd + + - name: Upgrade pip + run: | + pip install --upgrade --user pip + + - name: Install Poetry + run: | + pip install poetry + + - name: Get poetry cache dir + id: poetry-cache + run: | + echo "::set-output name=dir::$(poetry config cache-dir)" + + - name: poetry cache + uses: actions/cache@v2 + with: + path: ${{ steps.poetry-cache.outputs.dir }} + key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip-py${{ matrix.python-version }}- + + - name: Install dependencies + run: | + poetry install + + - name: Build PyDP macOS + if: runner.os == 'macOS' + run: | + poetry run python setup.py build bdist_wheel --plat-name macosx_10_14_x86_64 + + - name: Build PyDP Linux / Windows + if: runner.os != 'macOS' + run: | + poetry run python setup.py build bdist_wheel + + - name: Install Wheel Unix + if: runner.os != 'Windows' + run: | + pip install `find -L ./ -name "*.whl"` + + - name: Install Wheel Windows + if: runner.os == 'Windows' + run: | + Get-ChildItem -Path ./ -Filter "*.whl" -Recurse -File | foreach {pip install $_.FullName} + + - name: Import Package + run: | + python -c "import pydp; print(pydp.__version__)" + + - name: Run Pytest + run: | + poetry run pytest tests -n auto diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index 7b85e1bf..6d236aa4 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -41,7 +41,7 @@ jobs: - name: Run clang-format style check for C/C++ programs. uses: jidicula/clang-format-action@v3.3.0 with: - clang-format-version: "11" + clang-format-version: "12" check-path: "/src/bindings/" fallback-style: "Google" # optional diff --git a/Dockerfile b/Dockerfile index 6282ed88..6b31cc13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,79 +1,81 @@ -# Pull base image -ARG PYTHON_VERSION=3.9 -FROM python:${PYTHON_VERSION}-slim-buster - -# must be redefined after FROM -ARG PYTHON_VERSION=$PYTHON_VERSION -ARG BAZELISK_VERSION=v1.8.1 -ARG BAZELISK_BINARY=bazelisk-linux-amd64 -ARG BAZELISK_DOWNLOAD_URL=https://github.com/bazelbuild/bazelisk/releases/download/ - -# Set environment variables -ENV HOME=/root -ENV PROJECT_DIR="${HOME}/PyDP" -ENV PATH="/root/bin:${PATH}" -ENV DP_SHA="78d3fb8f63ea904ea6449a8276b9070254c650ec" - -# Define working directory -WORKDIR ${HOME} - -# Install apt-get packages -RUN apt-get update && \ - apt-get -y install \ - sudo \ - wget \ - zip \ - git \ - software-properties-common \ - gcc \ - g++ \ - clang-format \ - build-essential \ - python3-distutils \ - pkg-config \ - zlib1g-dev - -# Download and Install Bazelisk -RUN wget "${BAZELISK_DOWNLOAD_URL}/${BAZELISK_VERSION}/${BAZELISK_BINARY}" && \ - chmod +x ${BAZELISK_BINARY} - -RUN ./${BAZELISK_BINARY} --version - -# Update pip and setuptools and install poetry -RUN pip install --upgrade pip setuptools wheel && \ - pip install poetry - -# Change working dir -WORKDIR ${PROJECT_DIR} - -# Copy local source over -COPY . ${PROJECT_DIR} - -# Get google dp dependency -RUN mkdir -p third_party && \ - cd third_party && \ - git clone https://github.com/google/differential-privacy.git && \ - cd differential-privacy && \ - git checkout ${DP_SHA} - -# Remove unused java code -RUN rm -rf third_party/differential-privacy/java && \ - rm -rf third_party/differential-privacy/examples/java - -# This makes poetry's virtual environment in the project dir -RUN poetry config virtualenvs.in-project true - -# Build the bindings using Bazel and create a python wheel -RUN poetry env use ${PYTHON_VERSION} && \ - ${HOME}/${BAZELISK_BINARY} build --config linux src/python:pydp --verbose_failures - -RUN cp -f ./bazel-bin/src/bindings/_pydp.so ./pydp && \ - rm -rf dist/ && \ - poetry run python setup.py bdist_wheel && \ - poetry add dist/*.whl - -# This `activates` the virtual env -ENV VIRTUAL_ENV=$PROJECT_DIR/.venv -ENV PATH="$VIRTUAL_ENV/bin:$PATH" -# Default entrypoint -CMD ["/bin/bash"] +# Pull base image +ARG PYTHON_VERSION=3.9 +FROM python:${PYTHON_VERSION}-slim-buster + +# must be redefined after FROM +ARG PYTHON_VERSION=$PYTHON_VERSION +ARG BAZELISK_VERSION=v1.8.1 +ARG BAZELISK_BINARY=bazelisk-linux-amd64 +ARG BAZELISK_DOWNLOAD_URL=https://github.com/bazelbuild/bazelisk/releases/download/ + +# Set environment variables +ENV HOME=/root +ENV PROJECT_DIR="${HOME}/PyDP" +ENV PATH="/root/bin:${PATH}" +ENV DP_SHA="e224a8635728026fb3aa9409ab3a98b9a3f5566a" + +# Define working directory +WORKDIR ${HOME} + +# Install apt-get packages +RUN apt-get update && \ + apt-get -y install \ + sudo \ + wget \ + zip \ + git \ + software-properties-common \ + gcc \ + g++ \ + clang-format \ + build-essential \ + python3-distutils \ + pkg-config \ + zlib1g-dev + +# Download and Install Bazelisk +RUN wget "${BAZELISK_DOWNLOAD_URL}/${BAZELISK_VERSION}/${BAZELISK_BINARY}" && \ + chmod +x ${BAZELISK_BINARY} + +RUN ./${BAZELISK_BINARY} --version + +# Update pip and setuptools and install poetry +RUN pip install --upgrade pip setuptools wheel && \ + pip install poetry + +# Change working dir +WORKDIR ${PROJECT_DIR} + +# Copy local source over +COPY . ${PROJECT_DIR} + +# Get google dp dependency +RUN mkdir -p third_party && \ + cd third_party && \ + git clone https://github.com/google/differential-privacy.git && \ + cd differential-privacy && \ + git checkout ${DP_SHA} + +# Remove unused java code +RUN rm -rf third_party/differential-privacy/java && \ + rm -rf third_party/differential-privacy/examples/java + +RUN sed -i -e 's/@com_google_cc_differential_privacy//g' third_party/differential-privacy/cc/algorithms/BUILD + +# This makes poetry's virtual environment in the project dir +RUN poetry config virtualenvs.in-project true + +# Build the bindings using Bazel and create a python wheel +RUN poetry env use ${PYTHON_VERSION} && \ + ${HOME}/${BAZELISK_BINARY} build --config linux src/python:pydp --verbose_failures + +RUN cp -f ./bazel-bin/src/bindings/_pydp.so ./pydp && \ + rm -rf dist/ && \ + poetry run python setup.py bdist_wheel && \ + poetry add dist/*.whl + +# This `activates` the virtual env +ENV VIRTUAL_ENV=$PROJECT_DIR/.venv +ENV PATH="$VIRTUAL_ENV/bin:$PATH" +# Default entrypoint +CMD ["/bin/bash"] diff --git a/README.md b/README.md index f4c22ff7..b5f8a0a7 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,7 @@ guarantee and accuracy of your model written in Python. BoundedSum, Max, Count Above, Percentile, Min, Median, etc. - All the computation methods mentioned above use Laplace noise only (other noise mechanisms will be added soon! :smiley:) -- :fire: Currently supports Linux and macOS (Windows support coming soon -:smiley:) +- :fire: Compatible with all three types of Operating Systems - Linux, macOS, and Windows :smiley: - :star: Use Python 3.x. ## Installation diff --git a/WORKSPACE b/WORKSPACE index d142db04..2d875841 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -49,20 +49,22 @@ http_archive( # Google DP library and it's dependencies local_repository( - name = "google_dp", - path = "third_party/differential-privacy/cc", + name = "com_google_differential_privacy", + path = "third_party/differential-privacy", ) -load("@google_dp//:cc_differential_privacy_deps.bzl", "cc_differential_privacy_deps") +load("@com_google_differential_privacy//:differential_privacy_deps.bzl", "differential_privacy_deps") + +differential_privacy_deps() -cc_differential_privacy_deps() # Google DP library and it's dependencies local_repository( - name = "com_google_differential_privacy", - path = "third_party/differential-privacy", + name = "com_google_cc_differential_privacy", + path = "third_party/differential-privacy/cc", ) -load("@com_google_differential_privacy//:differential_privacy_deps.bzl", "differential_privacy_deps") +load("@com_google_cc_differential_privacy//:cc_differential_privacy_deps.bzl", "cc_differential_privacy_deps") + +cc_differential_privacy_deps() -differential_privacy_deps() diff --git a/docs/conf.py b/docs/conf.py index 3832ef9f..17a254cb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,8 @@ import os import sys +sys.path.insert(0, "../src") + # pydp absolute import pydp diff --git a/docs/pydp.rst b/docs/pydp.rst index 205b3131..ab9de0fc 100644 --- a/docs/pydp.rst +++ b/docs/pydp.rst @@ -26,6 +26,17 @@ Algorithms :inherited-members: +Numerical Mechanisms +#################### +.. currentmodule:: pydp.algorithms.numerical_mechanisms +.. autoclass:: NumericalMechanism + :members: +.. autoclass:: LaplaceMechanism + :members: + :show-inheritance: +.. autoclass:: GaussianMechanism + :members: + :show-inheritance: Distributions ############# @@ -52,4 +63,4 @@ Partition Selection .. currentmodule:: pydp.algorithms.partition_selection .. autoclass:: PartitionSelectionStrategy :members: -.. autofunction:: create_partition_strategy \ No newline at end of file +.. autofunction:: create_partition_strategy diff --git a/docs/requirements.txt b/docs/requirements.txt index dfea7cb4..c2c6f6a6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,2 @@ -python-dp sphinx>=3.2.1 sphinx-rtd-theme diff --git a/examples/SKLearn_Pipeline/SKLearn_Pipeline_Laplace_Mechanism.ipynb b/examples/SKLearn_Pipeline/SKLearn_Pipeline_Laplace_Mechanism.ipynb index 78cc4097..b870b6e7 100644 --- a/examples/SKLearn_Pipeline/SKLearn_Pipeline_Laplace_Mechanism.ipynb +++ b/examples/SKLearn_Pipeline/SKLearn_Pipeline_Laplace_Mechanism.ipynb @@ -19,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "id": "3a391b28", "metadata": {}, "outputs": [], @@ -44,17 +44,17 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "71ca131d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.48" + "0.84" ] }, - "execution_count": 4, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -84,17 +84,17 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "46d88b53", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.52" + "0.48" ] }, - "execution_count": 6, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -127,17 +127,17 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "93af586e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.64" + "0.52" ] }, - "execution_count": 7, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -173,17 +173,17 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "eda5ef9c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.36" + "0.6" ] }, - "execution_count": 8, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -212,10 +212,93 @@ "pipe.score(X_test, y_test)" ] }, + { + "cell_type": "markdown", + "id": "ed11410c", + "metadata": {}, + "source": [ + "## Categorical Feature Support" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e5159900", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.48" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import random\n", + "\n", + "# Helper function to inject nominal value to dataset\n", + "def create_cat_data(idxs, X, cat_data = [0,1, 2, 3, 4, 5]):\n", + " \n", + " X = X.copy()\n", + " \n", + " for idx in idxs:\n", + " for i in range(len(X[:,idx])):\n", + " num = random.choice(cat_data)\n", + " X[i,idx] = num\n", + " \n", + " return X\n", + "\n", + "# DUMMY DATASET\n", + "\n", + "# Create random dataset\n", + "X, y = make_classification(random_state=0)\n", + "\n", + "# Indecies for caegorical data\n", + "cat_feat_idxs = [0, 19]\n", + "\n", + "# Inject nominal data\n", + "X = create_cat_data(cat_feat_idxs, X)\n", + "\n", + "# Split training test set\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y,\n", + " random_state=0)\n", + "\n", + "# Sensitivity function for numeric features\n", + "calculate_sensitivity = lambda x: (max(x) - min(x))/ (len(x) + 1)\n", + "\n", + "# Sensitivity function for categorical features\n", + "calculate_sensitivity_cat = lambda x: abs(sum(x)/len(x))\n", + "\n", + "# Set laplace mechanism with categoical support\n", + "laplace = LaplaceMechanism(\n", + " epsilon=0.1, \n", + " sensitivity=calculate_sensitivity,\n", + " cat_sensitivity=calculate_sensitivity_cat,\n", + " cat_feat_idxs=cat_feat_idxs\n", + ")\n", + "\n", + "# Initialize scaler and naive bayes extimator\n", + "scaler = StandardScaler()\n", + "nb = GaussianNB()\n", + "\n", + "# Create the pipeline\n", + "pipe = Pipeline([('scaler', scaler), ('laplace', laplace), ('nb', nb)])\n", + "\n", + "# Train Naive Bayes model with Local DP\n", + "pipe.fit(X_train, y_train)\n", + "\n", + "# Get model score\n", + "pipe.score(X_test, y_test)" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "fdb818d4", + "id": "dce26029", "metadata": {}, "outputs": [], "source": [] diff --git a/examples/laplace_demo/README.md b/examples/laplace_demo/README.md new file mode 100644 index 00000000..f9cd6814 --- /dev/null +++ b/examples/laplace_demo/README.md @@ -0,0 +1,3 @@ +# Introduction to Laplace Distribution + +This example walksthrough why it is required to add noise to make data private. It motivates for the use of Laplace distribution's to make it easy to satisfy ε-differential privacy by setting the b parameter to 1/ε. \ No newline at end of file diff --git a/examples/laplace_demo/laplace.ipynb b/examples/laplace_demo/laplace.ipynb index 5b7752e4..84acdd48 100644 --- a/examples/laplace_demo/laplace.ipynb +++ b/examples/laplace_demo/laplace.ipynb @@ -1,21 +1,21 @@ { "cells": [ { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "y4gpTCeMz3-T" - }, - "outputs": [], + "cell_type": "markdown", + "metadata": {}, "source": [ - "%matplotlib inline" + "## Table of Content 🧾\n", + "\n", + "1. [Privacy 🔐 and Noise 📢](#Privacy-%F0%9F%94%90-and-Noise-📢)\n", + "2. [Exponential Distribution 📈](#Exponential-Distribution-📈)\n", + "3. [Laplace Distribution ⭐️](#Laplace-Distribution-⭐%EF%B8%8F)\n", + "4. [Why Laplace? 🧐](#Why-Laplace?-🧐)\n", + "5. [Conclusion 🔚](#Conclusion-🔚)" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "colab": {}, "colab_type": "code", @@ -34,6 +34,8 @@ }, "outputs": [], "source": [ + "# Import Necessary Packages\n", + "%matplotlib inline\n", "import numpy as np\n", "import matplotlib.pyplot as plt" ] @@ -45,7 +47,8 @@ "id": "84VsFE-Hz3-a" }, "source": [ - "## Privacy and Noise" + "## Privacy 🔐 and Noise 📢\n", + "---" ] }, { @@ -61,7 +64,7 @@ "\n", "But how exactly do you choose that random number? More technically, from what distribution will it be drawn?\n", "\n", - "The most common choice is the [Laplace distribution](https://en.wikipedia.org/wiki/Laplace_distribution) because it works well with the privacy parameter ε. This notebook is dedicated to exploring the Laplace distribution: what it is and why it's used for differential privacy." + "The most common choice is the [**Laplace distribution**](https://en.wikipedia.org/wiki/Laplace_distribution) because it works well with the privacy parameter ε. This notebook is dedicated to exploring the Laplace distribution: what it is and why it's used for differential privacy." ] }, { @@ -71,7 +74,8 @@ "id": "gU9zQELHz3-b" }, "source": [ - "## Exponential Distribution" + "## Exponential Distribution 📈\n", + "---" ] }, { @@ -81,7 +85,7 @@ "id": "gSRaoBfsz3-d" }, "source": [ - "The Laplace distribution can be thought of as two exponential distributions back-to-back so let's start by looking at the exponential distribution. It can be defined as" + "The **Laplace distribution** can be thought of as two exponential distributions back-to-back so let's start by looking at the exponential distribution. It can be defined as" ] }, { @@ -91,7 +95,7 @@ "id": "2q1etBbNz3-e" }, "source": [ - "$ f(x) = e^{-x} $" + "$$\\large f(x) = e^{-x} $$" ] }, { @@ -101,12 +105,16 @@ "id": "nPnfuntnz3-f" }, "source": [ - "When x is zero, f(x) is one, and the bigger x gets the closer to zero the output will be. Typically this is only defined for x >= 0. It looks like this:" + "It's behaviour, can be summarized as follows:- \n", + "* When $x$ is zero, $f(x)$ is one\n", + "* the bigger $x$ gets the closer to zero the output will be. \n", + "\n", + "Typically this is only defined for $x >= 0$. Let's use matplotlib to see how it looks:" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -130,7 +138,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -153,7 +161,8 @@ "id": "08lL-b5Az3-j" }, "source": [ - "## Laplace" + "## Laplace Distribution ⭐️\n", + "---" ] }, { @@ -168,7 +177,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "colab": {}, "colab_type": "code", @@ -193,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -217,7 +226,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -244,7 +253,7 @@ "\n", "What if you wanted to adjust the distribution to make it more or less likely to report something close to the real result? You can do that by introducing a scaling parameter.\n", "\n", - "The Laplace is typically defined with two additional parameters, μ and b:" + "The Laplace is typically defined with two additional parameters, $\\mu$ and $b$:" ] }, { @@ -254,7 +263,7 @@ "id": "60blurVhz3-r" }, "source": [ - "$f(x\\mid \\mu ,b)={\\frac {1}{2b}}\\exp \\left(-{\\frac {|x-\\mu |}{b}}\\right)\\,\\!$" + "$$\\large f(x\\mid \\mu ,b)={\\frac {1}{2b}}\\exp \\left(-{\\frac {|x-\\mu |}{b}}\\right)\\,\\!$$" ] }, { @@ -264,16 +273,16 @@ "id": "qTIoqLgEz3-s" }, "source": [ - "μ is the mean, in other words the center, or the pointy bit. In the context of differential privacy, this would be the real result from the database, i.e., what you would report if you added zero noise.\n", + "$\\mu$ is the **mean**, in other words the center, or the \"pointy\" bit. In the context of differential privacy, this would be the real result from the database, i.e., what you would report if you added zero noise.\n", "\n", - "b is the scaling parameter. Increasing it \"flattens out\" the graph so that the tails account for relatively more of the total. Increasing it gives you more privacy, because by flattening out the graph you make it more likely to choose a high value for the noise, which means an attacker can be less certain that the reported result is close to the true result.\n", + "$b$ is the **scaling parameter**. Increasing it \"flattens out\" the graph so that the tails account for relatively more of the total. Increasing it gives you more privacy, because by flattening out the graph you make it more likely to choose a high value for the noise, which means an attacker can be less certain that the reported result is close to the true result.\n", "\n", "You can try different values for μ and b and see how they affect the graph below." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "colab": {}, "colab_type": "code", @@ -302,7 +311,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -326,7 +335,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -351,7 +360,8 @@ "id": "KdsSS8asz3-x" }, "source": [ - "## Why Laplace?" + "## Why Laplace? 🧐\n", + "---" ] }, { @@ -372,7 +382,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "colab": {}, "colab_type": "code", @@ -407,7 +417,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -431,7 +441,17 @@ "outputs": [ { "data": { - "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", "text/plain": [ "
" ] @@ -466,7 +486,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -494,7 +514,7 @@ "(0.25, 0.15163266492815836)" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -517,7 +537,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -545,7 +565,7 @@ "(0.25, 0.25)" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -571,7 +591,8 @@ "id": "fzgGonHLz3-_" }, "source": [ - "## Conclusion" + "## Conclusion 🔚\n", + "---" ] }, { @@ -581,25 +602,12 @@ "id": "7fmk6wcLz3-_" }, "source": [ - "To recap:\n", - "\n", - "* You make data private by adding noise.\n", + "To recap, in this example you learned about:\n", "\n", - "* The Laplace distribution is constructed by stitching together two exponential distributions.\n", - "\n", - "* The Laplace distribution makes it easy for you to satisfy the requirement of ε differential privacy by setting the b parameter to 1/ε." + "* How adding random noise to your data can help preserve it's privacy i.e. you make data private by adding noise.\n", + "* Exponential and Laplace Distributions. Remember, the Laplace distribution is constructed by stitching together two exponential distributions. Moreover, we learnt how changing certain parameters of the distribution can help give you more privacy.\n", + "* Why Laplace Distribution is an ideal choice for adding noise to the data because it makes it easy for you to satisfy the requirement of ε differential privacy by setting the b parameter to 1/ε." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "XM_K2Xkyz3_A" - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -609,7 +617,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -623,7 +631,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.9.6" } }, "nbformat": 4, diff --git a/prereqs_linux.sh b/prereqs_linux.sh index adbf9dc7..c35da3cd 100755 --- a/prereqs_linux.sh +++ b/prereqs_linux.sh @@ -20,7 +20,7 @@ if command -v python3 &>/dev/null; then elif command python --version | grep -q 'Python 3'; then echo "Python 3 already installed" else - echo "Installing Python 3 is not installed" + echo "Installing Python 3 is not installed" sudo add-apt-repository ppa:deadsnakes/ppa sudo apt-get update sudo apt-get install python3.6 @@ -60,10 +60,16 @@ git submodule update --init --recursive # checkout out to particular commit -cd third_party/differential-privacy && \ +cd third_party/differential-privacy && git checkout bf0abf446b2f9d625a824bf14a7e3a6b6ac2a3e4 && \ cd - # renaming workspace.bazel to workspace mv third_party/differential-privacy/cc/WORKSPACE.bazel third_party/differential-privacy/cc/WORKSPACE # Removing the java part -rm -rf third_party/differential-privacy/java third_party/differential-privacy/examples/java \ No newline at end of file +rm -rf third_party/differential-privacy/java third_party/differential-privacy/examples/java + +# Removing the Go part +rm -rf third_party/differential-privacy/go third_party/differential-privacy/examples/go + +# Removing the Privacy on Beam +rm -rf third_party/differential-privacy/privacy-on-beam diff --git a/prereqs_mac.sh b/prereqs_mac.sh index ad42704f..e7d17f88 100755 --- a/prereqs_mac.sh +++ b/prereqs_mac.sh @@ -1,61 +1,64 @@ -#!/bin/bash - -# homebrew -which -s brew -if [[ $? != 0 ]] ; then - # Install Homebrew - echo "Downloading and installing homebrew" - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" -else - brew update -fi - -# python 3 -echo "Checking for python3 installation" -if command -v python3 &>/dev/null; then - echo "Python 3 already installed" -else - echo "Downloading and installing Python3 using homebrew" - brew install python3 -fi - -# bazel -if command -v bazel &>/dev/null; then - echo "Bazel already installed" -else - echo "Downloading and installing Bazel using homebrew" - brew tap bazelbuild/tap - brew install bazelbuild/tap/bazel -fi - -# clang-format -if command -v clang-format &>/dev/null; then - echo "clang-format already installed" -else - echo "installing clang-format" - brew install clang-format -fi - -# poetry -echo "Checking for poetry" -if python3 -c "import poetry" &> /dev/null; then - echo "poetry is already installed" -else - echo "installing poetry" - pip3 install poetry -fi - -# Downloading the Google DP library -git submodule update --init --recursive - -# checkout out to particular commit -cd third_party/differential-privacy && git checkout 2b320f8c03ba97215e3de7f7782eb5b8fd0b2354 && \ -cd - -# renaming workspace.bazel to workspace -mv third_party/differential-privacy/cc/WORKSPACE.bazel third_party/differential-privacy/cc/WORKSPACE - -# Removing the java part -rm -rf third_party/differential-privacy/java third_party/differential-privacy/examples/java - -# Removing the Go part -rm -rf third_party/differential-privacy/go third_party/differential-privacy/examples/go \ No newline at end of file +#!/bin/bash + +# homebrew +which -s brew +if [[ $? != 0 ]] ; then + # Install Homebrew + echo "Downloading and installing homebrew" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" +else + brew update +fi + +# python 3 +echo "Checking for python3 installation" +if command -v python3 &>/dev/null; then + echo "Python 3 already installed" +else + echo "Downloading and installing Python3 using homebrew" + brew install python3 +fi + +# bazel +if command -v bazel &>/dev/null; then + echo "Bazel already installed" +else + echo "Downloading and installing Bazel using homebrew" + brew tap bazelbuild/tap + brew install bazelbuild/tap/bazel +fi + +# clang-format +if command -v clang-format &>/dev/null; then + echo "clang-format already installed" +else + echo "installing clang-format" + brew install clang-format +fi + +# poetry +echo "Checking for poetry" +if python3 -c "import poetry" &> /dev/null; then + echo "poetry is already installed" +else + echo "installing poetry" + pip3 install poetry +fi + +# Downloading the Google DP library +git submodule update --init --recursive + +# checkout out to particular commit +cd third_party/differential-privacy && git checkout e224a8635728026fb3aa9409ab3a98b9a3f5566a && \ +cd - +# renaming workspace.bazel to workspace +mv third_party/differential-privacy/cc/WORKSPACE.bazel third_party/differential-privacy/cc/WORKSPACE + +# Removing the java part +rm -rf third_party/differential-privacy/java third_party/differential-privacy/examples/java + +# Removing the Go part +rm -rf third_party/differential-privacy/go third_party/differential-privacy/examples/go + +# Removing the Privacy on Beam part +rm -rf third_party/differential-privacy/privacy-on-beam \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 38a54239..1148c211 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.1.0 +current_version = 1.1.1 commit = True tag = True diff --git a/setup.py b/setup.py index 304bcdac..6eccca89 100755 --- a/setup.py +++ b/setup.py @@ -60,6 +60,6 @@ def read(fname): python_requires=">=3.6", test_suite="tests", url="https://github.com/OpenMined/PyDP", - version="1.1.0", + version="1.1.1", zip_safe=False, ) diff --git a/src/bindings/BUILD b/src/bindings/BUILD index ff549e23..4afd54dc 100644 --- a/src/bindings/BUILD +++ b/src/bindings/BUILD @@ -1,30 +1,32 @@ -load("@pybind11_bazel//:build_defs.bzl", "pybind_extension") - -pybind_extension( - name = "_pydp", - srcs = glob([ - "PyDP/*.cpp", - "PyDP/base/*.cpp", - "PyDP/algorithms/*.cpp", - "PyDP/pydp_lib/*.hpp", - "PyDP/proto/*.cpp" - ]), - - visibility = ["//src/python:__pkg__"], - deps = [ - "@google_dp//base:percentile", - "@google_dp//base:logging", - "@google_dp//base:statusor_internals", - "@google_dp//base:status", - "@google_dp//base:canonical_errors", - "@google_dp//base:statusor", - "@google_dp//algorithms:algorithm", - "@google_dp//algorithms:bounded-mean", - "@google_dp//algorithms:bounded-sum", - "@google_dp//algorithms:bounded-standard-deviation", - "@google_dp//algorithms:partition-selection", - "@google_dp//algorithms:count", - "@google_dp//algorithms:order-statistics", - "@google_dp//proto:util-lib" - ], -) +load("@pybind11_bazel//:build_defs.bzl", "pybind_extension") + +pybind_extension( + name = "_pydp", + srcs = glob([ + "PyDP/*.cpp", + "PyDP/base/*.cpp", + "PyDP/algorithms/*.cpp", + "PyDP/pydp_lib/*.hpp", + "PyDP/proto/*.cpp", + "PyDP/mechanisms/*.cpp" + ]), + + visibility = ["//src/python:__pkg__"], + deps = [ + "@com_google_cc_differential_privacy//base:percentile", + "@com_google_cc_differential_privacy//base:logging", + "@com_google_cc_differential_privacy//base:statusor_internals", + "@com_google_cc_differential_privacy//base:status", + # "@com_google_cc_differential_privacy//base:canonical_errors", + "@com_google_cc_differential_privacy//base:statusor", + "@com_google_cc_differential_privacy//algorithms:algorithm", + "@com_google_cc_differential_privacy//algorithms:bounded-mean", + "@com_google_cc_differential_privacy//algorithms:bounded-sum", + "@com_google_cc_differential_privacy//algorithms:bounded-standard-deviation", + "@com_google_cc_differential_privacy//algorithms:partition-selection", + "@com_google_cc_differential_privacy//algorithms:numerical-mechanisms", + "@com_google_cc_differential_privacy//algorithms:count", + "@com_google_cc_differential_privacy//algorithms:order-statistics", + "@com_google_cc_differential_privacy//proto:util-lib" + ], +) diff --git a/src/bindings/PyDP/algorithms/bounded_functions.cpp b/src/bindings/PyDP/algorithms/bounded_functions.cpp index 60e4aa42..f52dc0a0 100644 --- a/src/bindings/PyDP/algorithms/bounded_functions.cpp +++ b/src/bindings/PyDP/algorithms/bounded_functions.cpp @@ -12,9 +12,6 @@ #include "algorithms/bounded-variance.h" #include "../pydp_lib/algorithm_builder.hpp" -#include "../pydp_lib/casting.hpp" // our caster helper library - -using namespace std; namespace py = pybind11; namespace dp = differential_privacy; diff --git a/src/bindings/PyDP/algorithms/count.cpp b/src/bindings/PyDP/algorithms/count.cpp index c85eba17..7ebca108 100644 --- a/src/bindings/PyDP/algorithms/count.cpp +++ b/src/bindings/PyDP/algorithms/count.cpp @@ -7,8 +7,6 @@ #include "../pydp_lib/algorithm_builder.hpp" -using namespace std; - namespace py = pybind11; namespace dp = differential_privacy; diff --git a/src/bindings/PyDP/algorithms/distributions.cpp b/src/bindings/PyDP/algorithms/distributions.cpp index e717a165..d126f547 100644 --- a/src/bindings/PyDP/algorithms/distributions.cpp +++ b/src/bindings/PyDP/algorithms/distributions.cpp @@ -3,7 +3,6 @@ #include "algorithms/distributions.h" -using namespace std; namespace py = pybind11; namespace dpi = differential_privacy::internal; diff --git a/src/bindings/PyDP/algorithms/order_statistics.cpp b/src/bindings/PyDP/algorithms/order_statistics.cpp index 3861a8fd..6c29636e 100644 --- a/src/bindings/PyDP/algorithms/order_statistics.cpp +++ b/src/bindings/PyDP/algorithms/order_statistics.cpp @@ -7,8 +7,6 @@ #include "../pydp_lib/algorithm_builder.hpp" -using namespace std; - namespace py = pybind11; namespace dp = differential_privacy; diff --git a/src/bindings/PyDP/algorithms/rand.cpp b/src/bindings/PyDP/algorithms/rand.cpp index bb2bf8ec..2a27060c 100644 --- a/src/bindings/PyDP/algorithms/rand.cpp +++ b/src/bindings/PyDP/algorithms/rand.cpp @@ -1,6 +1,5 @@ // Provides bindings for rand #include "algorithms/rand.h" -#include "../pydp_lib/casting.hpp" #include "pybind11/pybind11.h" namespace py = pybind11; diff --git a/src/bindings/PyDP/algorithms/util.cpp b/src/bindings/PyDP/algorithms/util.cpp index 45394d42..bf27832e 100644 --- a/src/bindings/PyDP/algorithms/util.cpp +++ b/src/bindings/PyDP/algorithms/util.cpp @@ -1,42 +1,39 @@ -// Provides bindings for Util - -#include "pybind11/pybind11.h" -#include "pybind11/stl.h" - -#include "algorithms/util.h" - -namespace py = pybind11; -namespace dp = differential_privacy; - -void init_algorithms_util(py::module& m) { - m.attr("__module__") = "pydp"; - m.def( - "xor_strings", &dp::XorStrings, - R"pbdoc(Character-wise XOR of two strings. In case of differing string lengths, operation will be performed by the repeated concatenation of the smaler string till it is of the same length as the longer before the performance of the XOR operation.)pbdoc"); - m.def("default_epsilon", &dp::DefaultEpsilon); // deprecated, default epsilon value - m.def( - "get_next_power_of_two", &dp::GetNextPowerOfTwo, - R"pbdoc(Outputs value of a power of two that is greater than and closest to the given numerical input.)pbdoc"); - m.def( - "qnorm", &dp::Qnorm, - R"pbdoc(Quantile function of normal distribution, inverse of the cumulative distribution function.)pbdoc"); - m.def( - "mean", &dp::Mean, - R"pbdoc(Calculation of the mean of given set of numbers for a double int data type.)pbdoc"); - m.def( - "mean", &dp::Mean, - R"pbdoc(Calculation of the mean of given set of numbers for an int data type.)pbdoc"); - m.def("variance", &dp::Variance, - R"pbdoc(Calculate variance for a set of values.)pbdoc"); - m.def("standard_deviation", &dp::StandardDev, - R"pbdoc(Standard Deviation, the square root of variance.)pbdoc"); - m.def("order_statistics", &dp::OrderStatistic, - R"pbdoc(Sample values placed in ascending order.)pbdoc"); - m.def("correlation", &dp::Correlation, - R"pbdoc(Returns linear correlation coefficient.)pbdoc"); - m.def( - "vector_filter", &dp::VectorFilter, - R"pbdoc(Filtering a vector using a logical operatio with only values selected using true output in their positions.)pbdoc"); - m.def("vector_to_string", &dp::VectorToString, - R"pbdoc(Conversion of a vector to a string data type.)pbdoc"); +// Provides bindings for Util + +#include "pybind11/pybind11.h" +#include "pybind11/stl.h" + +#include "algorithms/util.h" + +namespace py = pybind11; +namespace dp = differential_privacy; + +void init_algorithms_util(py::module& m) { + m.attr("__module__") = "pydp"; + m.def("default_epsilon", &dp::DefaultEpsilon); // deprecated, default epsilon value + m.def( + "get_next_power_of_two", &dp::GetNextPowerOfTwo, + R"pbdoc(Outputs value of a power of two that is greater than and closest to the given numerical input.)pbdoc"); + m.def( + "qnorm", &dp::Qnorm, + R"pbdoc(Quantile function of normal distribution, inverse of the cumulative distribution function.)pbdoc"); + m.def( + "mean", &dp::Mean, + R"pbdoc(Calculation of the mean of given set of numbers for a double int data type.)pbdoc"); + m.def( + "mean", &dp::Mean, + R"pbdoc(Calculation of the mean of given set of numbers for an int data type.)pbdoc"); + m.def("variance", &dp::Variance, + R"pbdoc(Calculate variance for a set of values.)pbdoc"); + m.def("standard_deviation", &dp::StandardDev, + R"pbdoc(Standard Deviation, the square root of variance.)pbdoc"); + m.def("order_statistics", &dp::OrderStatistic, + R"pbdoc(Sample values placed in ascending order.)pbdoc"); + m.def("correlation", &dp::Correlation, + R"pbdoc(Returns linear correlation coefficient.)pbdoc"); + m.def( + "vector_filter", &dp::VectorFilter, + R"pbdoc(Filtering a vector using a logical operatio with only values selected using true output in their positions.)pbdoc"); + m.def("vector_to_string", &dp::VectorToString, + R"pbdoc(Conversion of a vector to a string data type.)pbdoc"); } \ No newline at end of file diff --git a/src/bindings/PyDP/base/logging.cpp b/src/bindings/PyDP/base/logging.cpp index 7d0b4bcc..8ede4b2a 100644 --- a/src/bindings/PyDP/base/logging.cpp +++ b/src/bindings/PyDP/base/logging.cpp @@ -5,8 +5,7 @@ #include "pybind11/pybind11.h" -#include "../pydp_lib/casting.hpp" // our caster helper library -#include "base/logging.h" // the header file associated with logging.cc +#include "base/logging.h" // the header file associated with logging.cc namespace py = pybind11; namespace dpb = differential_privacy::base; diff --git a/src/bindings/PyDP/base/status.cpp b/src/bindings/PyDP/base/status.cpp index b5196a1b..b1724e5e 100644 --- a/src/bindings/PyDP/base/status.cpp +++ b/src/bindings/PyDP/base/status.cpp @@ -1,115 +1,110 @@ -// Provides bindings for base/status and related - -#include - -#include "../pydp_lib/casting.hpp" // our caster helper library -#include "pybind11/operators.h" // for overloading the operators -#include "pybind11/pybind11.h" -#include "pybind11/stl.h" - -#include "base/canonical_errors.h" // the header file associated with status.cc -#include "base/status.h" // the header file associated with status.cc -#include "base/statusor.h" //header file associated with statusor.cc -// #include "differential_privacy/proto/data.pb.h" // for Output type - -using namespace std; - -namespace py = pybind11; -using namespace py::literals; -namespace dp = differential_privacy; -namespace dpb = differential_privacy::base; - -template -void declareStatusOr(py::module &m, string const &suffix) { - py::class_> cls(m, ("StatusOr" + suffix).c_str()); - cls.def(py::init<>()); - cls.def(py::init(), "value"_a); - cls.def(py::init(), "status"_a); - cls.def("ok", &dpb::StatusOr::ok); - // cls.def("value_or_die", &dpb::StatusOr::ValueOrDie); - // cls.def(py::self == dpbase::Status()); -} - -template -void declareStatusOr2(py::module &m, string const &suffix) { - py::class_> cls(m, ("StatusOr" + suffix).c_str()); - cls.def(py::init<>()); - // cls.def(py::init(), "value"_a); - cls.def(py::init(), "status"_a); - cls.def("ok", &dpb::StatusOr::ok); - // cls.def("value_or_die", &dpb::StatusOr::ValueOrDie); - // cls.def(py::self == dpbase::Status()); -} - -void init_base_status(py::module &m) { - // Creating the Status class - py::class_ status(m, "Status"); - status.attr("__module__") = "pydp"; - // Status class (we can now build functions and enuums from this class) - status.def(py::init()) - .def("__repr__", &dpb::Status::ToString, "String representation of status") - .def("set_payload", &dpb::Status::SetPayload, "Sets the status payload") - .def("get_payload", &dpb::Status::GetPayload, "Returns the status payload") - .def("erase_payload", &dpb::Status::ErasePayload, "Erases at target url"); - - // scoped enumerator for Status Code - py::enum_(status, "StatusCode", py::arithmetic()) - .value("kOk", dpb::StatusCode::kOk) - .value("kCancelled", dpb::StatusCode::kCancelled) - .value("kUnknown", dpb::StatusCode::kUnknown) - .value("kInvalidArgument", dpb::StatusCode::kInvalidArgument) - .value("kDeadlineExceeded", dpb::StatusCode::kDeadlineExceeded) - .value("kNotFound", dpb::StatusCode::kNotFound) - .value("kAlreadyExists", dpb::StatusCode::kAlreadyExists) - .value("kPermissionDenied", dpb::StatusCode::kPermissionDenied) - .value("kResourceExhausted", dpb::StatusCode::kResourceExhausted) - .value("kFailedPrecondition", dpb::StatusCode::kFailedPrecondition) - .value("kAborted", dpb::StatusCode::kAborted) - .value("kOutOfRange", dpb::StatusCode::kOutOfRange) - .value("kUnimplemented", dpb::StatusCode::kUnimplemented) - .value("kInternal", dpb::StatusCode::kInternal) - .value("kUnavailable", dpb::StatusCode::kUnavailable) - .value("kDataLoss", dpb::StatusCode::kDataLoss) - .value("kUnauthenticated", dpb::StatusCode::kUnauthenticated) - .value("kDoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_", - dpb::StatusCode:: - kDoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_); - - // converts a status code to a nice string - // status.def("status_code_to_string", &dpb::StatusCodeToString, - // "converts status code to string"); - - // canonical errors - status.def("aborted_error", &dpb::AbortedError); - status.def("aborted_error", &dpb::AbortedError); - status.def("already_exists_error", &dpb::AlreadyExistsError); - status.def("data_loss_error", &dpb::DataLossError); - status.def("deadline_exceeded_error", &dpb::DeadlineExceededError); - status.def("failed_precondition_error", &dpb::FailedPreconditionError); - status.def("internal_error", &dpb::InternalError); - status.def("invalid_argument_error", &dpb::InvalidArgumentError); - status.def("not_found_error", &dpb::NotFoundError); - status.def("out_of_range_error", &dpb::OutOfRangeError); - status.def("permission_denied_error", &dpb::PermissionDeniedError); - status.def("resource_exhausted_error", &dpb::ResourceExhaustedError); - status.def("unauthenticated_error", &dpb::UnauthenticatedError); - status.def("unavailable_error", &dpb::UnavailableError); - status.def("unimplemented_error", &dpb::UnimplementedError); - status.def("unknown_error", &dpb::UnknownError); - - // from statusor - m.def("handle_invalid_status_ctor_arg", - &dpb::statusor_internal::Helper::HandleInvalidStatusCtorArg); - m.def("crash", &dpb::statusor_internal::Helper::Crash, "Crash helper function"); - - declareStatusOr(m, "D"); - // declareStatusOr(m, "O"); - - // declareStatusOr2 is only a little different from declareStatusOr - // (see above in this file). - // Using declareStatusOr (without "2" at the end) below results in this error: - // external/google_dp/differential_privacy/base/statusor_internals.h:104:60: - // error: use of deleted function - // 'differential_privacy::BoundedMean::BoundedMean(differential_privacy::BoundedMean&&)' - // declareStatusOr2>(m, "BoundedMeantInt"); -} +// Provides bindings for base/status and related + +#include + +#include "pybind11/operators.h" // for overloading the operators +#include "pybind11/pybind11.h" +#include "pybind11/stl.h" + +#include "base/status.h" // the header file associated with status.cc +#include "base/statusor.h" //header file associated with statusor.cc + +namespace py = pybind11; +using namespace py::literals; +namespace dp = differential_privacy; +namespace dpb = differential_privacy::base; + +template +void declareStatusOr(py::module &m, std::string const &suffix) { + py::class_> cls(m, ("StatusOr" + suffix).c_str()); + cls.def(py::init<>()); + cls.def(py::init(), "value"_a); + cls.def(py::init(), "status"_a); + cls.def("ok", &dpb::StatusOr::ok); + // cls.def("value_or_die", &dpb::StatusOr::ValueOrDie); + // cls.def(py::self == dpbase::Status()); +} + +template +void declareStatusOr2(py::module &m, std::string const &suffix) { + py::class_> cls(m, ("StatusOr" + suffix).c_str()); + cls.def(py::init<>()); + // cls.def(py::init(), "value"_a); + cls.def(py::init(), "status"_a); + cls.def("ok", &dpb::StatusOr::ok); + // cls.def("value_or_die", &dpb::StatusOr::ValueOrDie); + // cls.def(py::self == dpbase::Status()); +} + +void init_base_status(py::module &m) { + // Creating the Status class + py::class_ status(m, "Status"); + status.attr("__module__") = "pydp"; + // Status class (we can now build functions and enuums from this class) + status.def(py::init()) + .def("__repr__", &dpb::Status::ToString, "String representation of status") + .def("set_payload", &dpb::Status::SetPayload, "Sets the status payload") + .def("get_payload", &dpb::Status::GetPayload, "Returns the status payload") + .def("erase_payload", &dpb::Status::ErasePayload, "Erases at target url"); + + // scoped enumerator for Status Code + py::enum_(status, "StatusCode", py::arithmetic()) + .value("kOk", dpb::StatusCode::kOk) + .value("kCancelled", dpb::StatusCode::kCancelled) + .value("kUnknown", dpb::StatusCode::kUnknown) + .value("kInvalidArgument", dpb::StatusCode::kInvalidArgument) + .value("kDeadlineExceeded", dpb::StatusCode::kDeadlineExceeded) + .value("kNotFound", dpb::StatusCode::kNotFound) + .value("kAlreadyExists", dpb::StatusCode::kAlreadyExists) + .value("kPermissionDenied", dpb::StatusCode::kPermissionDenied) + .value("kResourceExhausted", dpb::StatusCode::kResourceExhausted) + .value("kFailedPrecondition", dpb::StatusCode::kFailedPrecondition) + .value("kAborted", dpb::StatusCode::kAborted) + .value("kOutOfRange", dpb::StatusCode::kOutOfRange) + .value("kUnimplemented", dpb::StatusCode::kUnimplemented) + .value("kInternal", dpb::StatusCode::kInternal) + .value("kUnavailable", dpb::StatusCode::kUnavailable) + .value("kDataLoss", dpb::StatusCode::kDataLoss) + .value("kUnauthenticated", dpb::StatusCode::kUnauthenticated) + .value("kDoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_", + dpb::StatusCode:: + kDoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_); + + // converts a status code to a nice string + // status.def("status_code_to_string", &dpb::StatusCodeToString, + // "converts status code to string"); + + // canonical errors + status.def("aborted_error", &dpb::AbortedError); + status.def("aborted_error", &dpb::AbortedError); + status.def("already_exists_error", &dpb::AlreadyExistsError); + status.def("data_loss_error", &dpb::DataLossError); + status.def("deadline_exceeded_error", &dpb::DeadlineExceededError); + status.def("failed_precondition_error", &dpb::FailedPreconditionError); + status.def("internal_error", &dpb::InternalError); + status.def("invalid_argument_error", &dpb::InvalidArgumentError); + status.def("not_found_error", &dpb::NotFoundError); + status.def("out_of_range_error", &dpb::OutOfRangeError); + status.def("permission_denied_error", &dpb::PermissionDeniedError); + status.def("resource_exhausted_error", &dpb::ResourceExhaustedError); + status.def("unauthenticated_error", &dpb::UnauthenticatedError); + status.def("unavailable_error", &dpb::UnavailableError); + status.def("unimplemented_error", &dpb::UnimplementedError); + status.def("unknown_error", &dpb::UnknownError); + + // from statusor + m.def("handle_invalid_status_ctor_arg", + &dpb::statusor_internal::Helper::HandleInvalidStatusCtorArg); + m.def("crash", &dpb::statusor_internal::Helper::Crash, "Crash helper function"); + + declareStatusOr(m, "D"); + // declareStatusOr(m, "O"); + + // declareStatusOr2 is only a little different from declareStatusOr + // (see above in this file). + // Using declareStatusOr (without "2" at the end) below results in this error: + // external/google_dp/differential_privacy/base/statusor_internals.h:104:60: + // error: use of deleted function + // 'differential_privacy::BoundedMean::BoundedMean(differential_privacy::BoundedMean&&)' + // declareStatusOr2>(m, "BoundedMeantInt"); +} diff --git a/src/bindings/PyDP/bindings.cpp b/src/bindings/PyDP/bindings.cpp index a2337f04..05369386 100644 --- a/src/bindings/PyDP/bindings.cpp +++ b/src/bindings/PyDP/bindings.cpp @@ -29,6 +29,9 @@ void init_algorithms_rand(py::module &); // proto void init_proto(py::module &); +// numerical mechanisms +void init_mechanisms_mechanism(py::module &); + PYBIND11_MODULE(_pydp, m) { m.doc() = "Google Differential Privacy python extension"; @@ -52,6 +55,9 @@ PYBIND11_MODULE(_pydp, m) { init_algorithms_rand(mutil); init_algorithms_util(mutil); + auto mnumericalmechanisms = m.def_submodule("_mechanisms", "Numerical Mechanisms."); + init_mechanisms_mechanism(mnumericalmechanisms); + // Proto // TODO: Delete if it is not necessary (we no longer return StatusOr to the user) init_proto(m); diff --git a/src/bindings/PyDP/mechanisms/mechanism.cpp b/src/bindings/PyDP/mechanisms/mechanism.cpp index d1f7c442..4e773a99 100644 --- a/src/bindings/PyDP/mechanisms/mechanism.cpp +++ b/src/bindings/PyDP/mechanisms/mechanism.cpp @@ -1 +1,153 @@ -#include "mechanism.h" \ No newline at end of file +#include +#include "pybind11/complex.h" +#include "pybind11/functional.h" +#include "pybind11/stl.h" + +#include "algorithms/numerical-mechanisms.h" + +#include "../pydp_lib/algorithm_builder.hpp" + +namespace py = pybind11; +namespace dp = differential_privacy; + +class ConfidenceIntervalBinder { + public: + static void DeclareIn(py::module& m) { + py::class_ confidence_interval(m, "ConfidenceInterval"); + confidence_interval.attr("__module__") = "pydp"; + confidence_interval + .def_property("lower_bound", &dp::ConfidenceInterval::lower_bound, + &dp::ConfidenceInterval::set_lower_bound) + .def_property("upper_bound", &dp::ConfidenceInterval::upper_bound, + &dp::ConfidenceInterval::set_upper_bound) + .def_property("confidence_level", &dp::ConfidenceInterval::confidence_level, + &dp::ConfidenceInterval::set_confidence_level); + } +}; + +template +py::class_& DefPyAddNoise( + py::class_& pyclass) { + using FunctorType = T (dp::NumericalMechanism::*)(T); + return pyclass.def("add_noise", + static_cast(&dp::NumericalMechanism::AddNoise), + py::arg("result")); +} + +template +std::unique_ptr downcast_unique_ptr(std::unique_ptr u_ptr) { + static_assert(std::is_base_of::value, "Illegal downcast."); + T* ptr = dynamic_cast(u_ptr.release()); + return std::unique_ptr(ptr); +} + +class NumericalMechanismBinder { + public: + static void DeclareIn(py::module& m) { + py::class_ numerical_mech(m, "NumericalMechanism", + R"pbdoc( + Base class for all (Ɛ, 𝛿)-differenially private additive noise numerical mechanisms. + )pbdoc"); + numerical_mech.attr("__module__") = "pydp"; + DefPyAddNoise(numerical_mech); + DefPyAddNoise(numerical_mech); + DefPyAddNoise(numerical_mech); + numerical_mech + .def("noised_value_above_threshold", + &dp::NumericalMechanism::NoisedValueAboveThreshold, + R"pbdoc( + Quickly determines if `result` with added noise is above certain `threshold`. + )pbdoc") + .def("memory_used", &dp::NumericalMechanism::MemoryUsed) + .def( + "noise_confidence_interval", + [](dp::NumericalMechanism& self, double cl, + double nr) -> dp::ConfidenceInterval { + auto result = self.NoiseConfidenceInterval(cl, nr); + return result.ValueOrDie(); + }, + py::arg("confidence_level"), py::arg("noised_result"), + R"pbdoc( + Returns the confidence interval of the specified confidence level of the + noise that AddNoise() would add with the specified privacy budget. + If the returned value is , then the noise added has a confidence_level + chance of being in the domain [x,y] + )pbdoc") + .def_property_readonly("epsilon", &dp::NumericalMechanism::GetEpsilon, + "The Ɛ of the numerical mechanism"); + } +}; + +class LaplaceMechanismBinder { + public: + static std::unique_ptr build(double epsilon, + double l1_sensitivity) { + dp::LaplaceMechanism::Builder builder; + builder.SetEpsilon(epsilon); + builder.SetSensitivity(l1_sensitivity); + builder.SetL1Sensitivity(l1_sensitivity); + return downcast_unique_ptr( + builder.Build().value()); + } + + static void DeclareIn(py::module& m) { + py::class_ lap_mech( + m, "LaplaceMechanism"); + lap_mech.attr("__module__") = "pydp"; + lap_mech + .def(py::init([](double epsilon, double sensitivity) { + return build(epsilon, sensitivity); + }), + py::arg("epsilon"), py::arg("sensitivity") = 1.0) + .def_property_readonly("sensitivity", &dp::LaplaceMechanism::GetSensitivity, + "The L1 sensitivity of the query.") + .def_property_readonly("diversity", &dp::LaplaceMechanism::GetDiversity, + "The diversity of the Laplace mechanism."); + } +}; + +class GaussianMechanismBinder { + public: + static std::unique_ptr build(double epsilon, double delta, + double l2_sensitivity) { + dp::GaussianMechanism::Builder builder; + builder.SetEpsilon(epsilon); + builder.SetDelta(delta); + builder.SetL2Sensitivity(l2_sensitivity); + return downcast_unique_ptr( + builder.Build().value()); + }; + + static void DeclareIn(py::module& m) { + py::class_ gaus_mech( + m, "GaussianMechanism"); + gaus_mech.attr("__module__") = "pydp"; + gaus_mech + .def(py::init([](double epsilon, double delta, double l2_sensitivity) { + return build(epsilon, delta, l2_sensitivity); + }), + py::arg("epsilon"), py::arg("delta"), py::arg("sensitivity") = 1.0) + .def_property_readonly("delta", &dp::GaussianMechanism::GetDelta, + "The 𝛿 of the Gaussian mechanism.") + .def_property_readonly("std", + [](const dp::GaussianMechanism& self) { + return dp::GaussianMechanism::CalculateStddev( + self.GetEpsilon(), self.GetDelta(), + self.GetL2Sensitivity()); + }, + R"pbdoc( + The standard deviation parameter of the + Gaussian mechanism underlying distribution. + )pbdoc") + .def_property_readonly("l2_sensitivity", + &dp::GaussianMechanism::GetL2Sensitivity, + "The L2 sensitivity of the query."); + } +}; + +void init_mechanisms_mechanism(py::module& m) { + ConfidenceIntervalBinder::DeclareIn(m); + NumericalMechanismBinder::DeclareIn(m); + LaplaceMechanismBinder::DeclareIn(m); + GaussianMechanismBinder::DeclareIn(m); +} diff --git a/src/bindings/PyDP/proto/proto.cpp b/src/bindings/PyDP/proto/proto.cpp index f0cbecb3..0ca7faee 100644 --- a/src/bindings/PyDP/proto/proto.cpp +++ b/src/bindings/PyDP/proto/proto.cpp @@ -1,17 +1,12 @@ #include #include -#include "../pydp_lib/casting.hpp" // our caster helper library #include "pybind11/pybind11.h" +#include "proto/data.pb.h" #include "proto/summary.pb.h" -#include "proto/util.h" // the header file associated with status.cc - -using namespace std; namespace py = pybind11; -using namespace py::literals; - namespace dp = differential_privacy; void init_proto(py::module &m) { @@ -23,11 +18,12 @@ void init_proto(py::module &m) { .def(py::init()) .def("save", [](dp::Summary &pythis, std::string &filename) { - fstream output(filename, ios::out | ios::trunc | ios::binary); + std::fstream output(filename, + std::ios::out | std::ios::trunc | std::ios::binary); pythis.SerializeToOstream(&output); }) .def("load", [](dp::Summary &pythis, std::string &filename) { - fstream input(filename, ios::in | ios::binary); + std::fstream input(filename, std::ios::in | std::ios::binary); pythis.ParseFromIstream(&input); }); } diff --git a/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp b/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp index bc3db83f..7e61c6cc 100644 --- a/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp +++ b/src/bindings/PyDP/pydp_lib/algorithm_builder.hpp @@ -146,7 +146,7 @@ class AlgorithmBuilder { py::arg("linf_sensitivity") = 1); } - // // No bounds constructor + // No bounds constructor pyself.def(py::init([this](double epsilon, double delta, int l0_sensitivity, int linf_sensitivity) { return this->build(epsilon, delta, std::nullopt /*percentile*/, @@ -161,8 +161,6 @@ class AlgorithmBuilder { pyself.def_property_readonly("epsilon", &Algorithm::GetEpsilon); pyself.def_property_readonly("delta", &Algorithm::GetDelta); - pyself.def("privacy_budget_left", &Algorithm::RemainingPrivacyBudget); - pyself.def("memory_used", &Algorithm::MemoryUsed); // Input data @@ -203,10 +201,6 @@ class AlgorithmBuilder { }); pyself.def("partial_result", [](Algorithm& pythis, double privacy_budget) { - if (privacy_budget > pythis.RemainingPrivacyBudget()) { - throw std::runtime_error("Privacy budget requeted exceeds set privacy budget"); - } - auto result = pythis.PartialResult(privacy_budget); if (!result.ok()) { @@ -221,13 +215,8 @@ class AlgorithmBuilder { return dp::GetValue(result.ValueOrDie()); }); - pyself.def("partial_result", [](Algorithm& pythis, double privacy_budget, - double noise_interval_level) { - if (privacy_budget > pythis.RemainingPrivacyBudget()) { - throw std::runtime_error("Privacy budget requeted exceeds set privacy budget"); - } - - auto result = pythis.PartialResult(privacy_budget, noise_interval_level); + pyself.def("partial_result", [](Algorithm& pythis, double noise_interval_level) { + auto result = pythis.PartialResult(noise_interval_level); if (!result.ok()) { throw std::runtime_error(result.status().ToString()); diff --git a/src/bindings/PyDP/pydp_lib/casting.hpp b/src/bindings/PyDP/pydp_lib/casting.hpp deleted file mode 100644 index fde072ec..00000000 --- a/src/bindings/PyDP/pydp_lib/casting.hpp +++ /dev/null @@ -1,13 +0,0 @@ -// Convenient place to store type casters that are used through out project -// This teaches pybind11 to cast types provided in the absl library - -#include "absl/strings/string_view.h" -#include "absl/types/optional.h" -#include "pybind11/stl.h" - -namespace pybind11 { -namespace detail { -// incase we use this - -} // namespace detail -} // namespace pybind11 diff --git a/src/bindings/PyDP/pydp_lib/helper_class.hpp b/src/bindings/PyDP/pydp_lib/helper_class.hpp deleted file mode 100644 index ac90c75c..00000000 --- a/src/bindings/PyDP/pydp_lib/helper_class.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "../../c/c_api.h" -#include "pybind11/complex.h" -#include "pybind11/functional.h" -#include "pybind11/pybind11.h" -#include "pybind11/stl.h" - -PYBIND11_MAKE_OPAQUE(BoundedFunctionHelperObject); - -using namespace std; - -namespace py = pybind11; - -class Dummy { - public: - Dummy(double epsilon, int lower_bound, int upper_bound) { - obj = NewBoundedFunctionObject(epsilon, lower_bound, upper_bound); - } - - Dummy(double epsilon) { - obj = NewBoundedFunctionObject1(epsilon); - } - - void set_l0_sensitivity(int _l0_sensitivity) { - set_l0_sensitivity_(_l0_sensitivity); - } - int get_l0_sensitivity() { - return get_l0_sensitivity_(); - } - - void set_linf_sensitivity(int _linf_sensitivity) { - set_linf_sensitivity_(_linf_sensitivity); - } - int get_linf_sensitivity() { - return get_linf_sensitivity_(); - } - - virtual double Result(py::list) {} - - virtual double Result(py::list, double) {} - - ~Dummy() { - DeleteBoundedFunctionObject(obj); - } - - BoundedFunctionHelperObject* obj; -}; \ No newline at end of file diff --git a/src/pydp/__init__.py b/src/pydp/__init__.py index a31556f7..04ed654f 100644 --- a/src/pydp/__init__.py +++ b/src/pydp/__init__.py @@ -7,4 +7,4 @@ from pydp import util from pydp import ml -__version__ = "1.1.0" +__version__ = "1.1.1" diff --git a/src/pydp/algorithms/__init__.py b/src/pydp/algorithms/__init__.py index 4ba19e15..69b31576 100644 --- a/src/pydp/algorithms/__init__.py +++ b/src/pydp/algorithms/__init__.py @@ -1,5 +1,6 @@ # pydp relative from . import laplacian from . import partition_selection +from . import numerical_mechanisms -__all__ = ["laplacian", "partition_selection"] +__all__ = ["laplacian", "partition_selection", "numerical_mechanisms"] diff --git a/src/pydp/algorithms/_algorithm.py b/src/pydp/algorithms/_algorithm.py index 0687fb7a..6a982363 100644 --- a/src/pydp/algorithms/_algorithm.py +++ b/src/pydp/algorithms/_algorithm.py @@ -72,12 +72,6 @@ def linf_sensitivity(self) -> float: """ return self._linf_sensitivity - def privacy_budget_left(self) -> float: - """ - Returns the remaining privacy budget. - """ - return self.__algorithm.privacy_budget_left() - def memory_used(self) -> float: """ Returns the memory currently used by the algorithm in bytes. @@ -112,29 +106,17 @@ def quick_result(self, data: List[Union[int, float]]) -> Union[int, float]: def result( self, - privacy_budget: Union[float, None] = None, noise_interval_level: Union[float, None] = None, ) -> Union[int, float]: """ Gets the algorithm result. - The default call consumes the remaining privacy budget. - - When `privacy_budget` (defined on [0,1]) is set, it consumes only the `privacy_budget` amount of budget. - `noise_interval_level` provides the confidence level of the noise confidence interval, which may be included in the algorithm output. """ - if self.privacy_budget_left() == 0: - raise RuntimeError( - "Privacy Budget left is already 0, you can't do any more operations" - ) - - if privacy_budget is None: + if noise_interval_level is None: return self.__algorithm.partial_result() - elif noise_interval_level is None: - return self.__algorithm.partial_result(privacy_budget) else: - return self.__algorithm.partial_result(privacy_budget, noise_interval_level) + return self.__algorithm.partial_result(noise_interval_level) def reset(self) -> None: """ diff --git a/src/pydp/algorithms/numerical_mechanisms.py b/src/pydp/algorithms/numerical_mechanisms.py new file mode 100644 index 00000000..487f4d36 --- /dev/null +++ b/src/pydp/algorithms/numerical_mechanisms.py @@ -0,0 +1,6 @@ +from .._pydp._mechanisms import ( + NumericalMechanism, # type: ignore + GaussianMechanism, # type: ignore + LaplaceMechanism, # type: ignore + ConfidenceInterval, # type: ignore +) diff --git a/src/pydp/ml/mechanisms/sklearn_pipeline.py b/src/pydp/ml/mechanisms/sklearn_pipeline.py index cfd45aa6..2ecc58ab 100644 --- a/src/pydp/ml/mechanisms/sklearn_pipeline.py +++ b/src/pydp/ml/mechanisms/sklearn_pipeline.py @@ -1,135 +1,202 @@ import numbers -from ..util.accountant import BudgetAccountant -from .laplace import Laplace +from pydp.distributions import LaplaceDistribution # type: ignore from sklearn.base import BaseEstimator, TransformerMixin import numpy as np + class LaplaceMechanism(BaseEstimator, TransformerMixin): """ - An SKLearn Pipeline operator for applying differentially private noise - addition using the laplace mechanism. - Paper link: https://link.springer.com/content/pdf/10.1007/11681878_14.pdf + An SKLearn Pipeline operator for applying differentially private noise + addition using the laplace mechanism. + Paper link: https://link.springer.com/content/pdf/10.1007/11681878_14.pdf """ - - def __init__(self, epsilon=1.0, sensitivity=1, accountant=None): - + + def __init__( + self, + epsilon=1.0, + sensitivity=1, + scale=None, + cat_feat_idxs=None, + cat_sensitivity=None, + ): + """ Checks that all parameters of the mechanism have been initialised correctly, and that the mechanism is ready to be used. - + Parameters ---------- epsilon : float or int The value of epsilon for achieving :math:`\epsilon`-differential privacy with the mechanism. Must have `epsilon > 0`. - + sensitivity : float or int The sensitivity of the mechanism. Must satisfy `sensitivity` > 0. - - accountant : BudgetAccountant, optional - Accountant to keep track of privacy budget. - - + + scale : float or int, optional + + cat_feat_idxs : list or None, optional + List of integers identifying indicies of categorical features. + + cat_feat_idxs : list or None, optional + List of integers identifying indicies of categorical features. + + cat_feat_idxs : list or None, optional + List of integers identifying indicies of categorical features. + + cat_feat_idxs : list or None, optional + List of integers identifying indicies of categorical features. + + Attributes ------- epsilon Privacy budget to calculate noise. - + sensitivty Sensitivity of the mechanism to calculate noise. - - accountant - Accountant to keep track of privacy budget. - + + scale + + + cat_feat_idxs + List of indicies that identifies categorical features. + + cat_sensitivty + Sensitivity of the mechanism to calculate noise for categorical data. + Raises ------ TypeError If epsilon is not a number, or sensitivity is not a number or a callable. - + ValueError If epsilon less than 0, or sensitivty is a number but less than 0. """ - + if not isinstance(epsilon, numbers.Number): raise TypeError(f"Epsilon must be a number. Got type {type(epsilon)}.") if epsilon <= 0: raise ValueError("Epsilon must be at least larger than 0.") - + self.epsilon = epsilon - + if not isinstance(sensitivity, numbers.Number): if not callable(sensitivity): - raise TypeError(f"Sensitivity must be a number or callable. Got type {type(sensitivity)}.") + raise TypeError( + f"Sensitivity must be a number or callable. Got type {type(sensitivity)}." + ) - if isinstance(sensitivity, numbers.Number) and sensitivity <= 0: raise ValueError("Sensitivity must be at least larger than 0.") - + self.sensitivity = sensitivity - self.accountant = BudgetAccountant.load_default(accountant) - - self.laplace = None # If sensitivity is callable, set lapalace to None - - if not callable(sensitivity): - self.laplace = Laplace() - + + if scale is not None: + if not isinstance(scale, numbers.Number): + raise TypeError( + f"Sensitivity must be a int or float. Got type {type(sensitivity)}." + ) + + self.scale = scale + + if (cat_feat_idxs is not None and cat_sensitivity is None) or ( + cat_feat_idxs is None and cat_sensitivity is not None + ): + raise ValueError( + "cat_feat_idxs cannot be None if cat_sensitivity, and vice versa." + ) + + self.categorical_exists = ( + cat_feat_idxs is not None and cat_sensitivity is not None + ) + + if self.categorical_exists: + + if not isinstance(sensitivity, numbers.Number): + if not callable(sensitivity): + raise TypeError( + f"Sensitivity must be a number or callable. Got type {type(sensitivity)}." + ) + + if isinstance(sensitivity, numbers.Number) and sensitivity <= 0: + raise ValueError("Sensitivity must be at least larger than 0.") + + if len(cat_feat_idxs) == 0: + raise ValueError( + "At least 1 categorical feature index must be provided." + ) + + self.cat_feat_idxs = cat_feat_idxs + self.cat_sensitivity = cat_sensitivity + def sensitivity_calculation(self, X): """ - Perform local differential privacy by adding noise using Laplace mechanismto the dataset if the sensitivity + Perform local differential privacy by adding noise using Laplace mechanismto the dataset if the sensitivity provided if a callable. - - + + Parameters ---------- X : numpy.array Datset in the form of a 2-dimensional numpy array. - + Returns ------ X : numpy.array Original parameter X with differentially private noise added. """ - + n_feature = X.shape[-1] n_data = X.shape[0] - for data_idx in range(n_data): - self.accountant.check(self.epsilon, 0) for feature_idx in range(n_feature): - + # Array with data point data_idx removed for feature_idx - feature = np.concatenate((X[:data_idx,feature_idx],X[data_idx + 1:,feature_idx])) - + feature = np.concatenate( + (X[:data_idx, feature_idx], X[data_idx + 1 :, feature_idx]) + ) + # Calculate sensitivity - sensitivity_ = self.sensitivity(feature) - + if self.categorical_exists and feature_idx in cat_feat_idxs: + if isinstance(self.cat_sensitivity, numbers.Number): + sensitivity_ = self.cat_sensitivity + print(sensitivity_) + else: + sensitivity_ = self.cat_sensitivity(feature) + print(sensitivity_) + + else: + if isinstance(self.sensitivity, numbers.Number): + sensitivity_ = self.sensitivity + else: + sensitivity_ = self.sensitivity(feature) + # Initialized Laplace mechanism instance - laplace = Laplace().set_epsilon(self.epsilon).set_sensitivity(sensitivity_) - + laplace = LaplaceDistribution( + epsilon=float(self.epsilon), sensitivity=float(sensitivity_) + ) + # Add noise to the data point that was removed - noised_value = laplace.randomise(X[data_idx,feature_idx]) - + if self.scale is not None: + noised_value = X[data_idx, feature_idx] - laplace.sample( + scale=float(self.scale) + ) + else: + noised_value = X[data_idx, feature_idx] - laplace.sample() + # Replaced data point in the dataset with noised version - X[data_idx,feature_idx] = noised_value - - self.accountant.spend(self.epsilon, 0) + X[data_idx, feature_idx] = noised_value return X - - + def fit(self, X, y=None): return self def transform(self, X, y=None): - if self.laplace is not None: - self.laplace.set_epsilon(self.epsilon).set_sensitivity(self.sensitivity) - vector_randomise = np.vectorize(self.laplace.randomise) - noised_array = vector_randomise(X) - return noised_array - else: - X = self.sensitivity_calculation( X) - return X \ No newline at end of file + X = self.sensitivity_calculation(X) + return X diff --git a/tests/algorithms/conftest.py b/tests/algorithms/conftest.py deleted file mode 100644 index aaad16c9..00000000 --- a/tests/algorithms/conftest.py +++ /dev/null @@ -1,60 +0,0 @@ -# stdlib -from itertools import accumulate -import math -from typing import List - - -def skew(samples: List[float], mu: float, sigma: float): - """Unfortunately this is implemented in third_party/differential-privacy/cc/algorithms/distributions_test.cc - and we don't want to pull the test files in. I'm assuming it'll be moved to - third_party/differential-privacy/cc/algorithms/util.h If they (upstream) move it we can use it. - Until then this should suffice. #FIXME: when possible we can fix this. - """ - skew = list( - accumulate(samples, lambda lhs, rhs: lhs + (rhs - mu) * (rhs - mu) * (rhs - mu)) - )[-1] - return skew / (len(samples) * sigma * sigma * sigma) - - -def kurtosis(samples: List[float], mu: float, var: float): - """Unfortunately this is implemented in third_party/differential-privacy/cc/algorithms/distributions_test.cc - and we don't want to pull the test files in. I'm assuming it'll be moved to - third_party/differential-privacy/cc/algorithms/util.h If they (upstream) move it we can use it. - Until then this should suffice. #FIXME: when possible we can fix this. - """ - kurt = list( - accumulate(samples, lambda lhs, rhs: lhs + ((rhs - mu) * (rhs - mu)) ** 2) - )[-1] - n = len(samples) - kurt = (n + 1) * kurt / (n * var * var) - kurt -= 3 * (n - 1) - kurt *= (n - 1) / (n - 2) / (n - 3) - return kurt - - -def percentile(N, percent, key=lambda x: x): - """ - Find the percentile of a list of values. - @parameter N - is a list of values. Note N MUST BE already sorted. - @parameter percent - a float value from 0.0 to 1.0. - @parameter key - optional key function to compute value from each element of N. - @return - the percentile of the values - """ - if not N: - return None - k = (len(N) - 1) * percent - f = math.floor(k) - c = math.ceil(k) - if f == c: - return key(N[int(k)]) - d0 = key(N[int(f)]) * (c - k) - d1 = key(N[int(c)]) * (k - f) - return d0 + d1 - - -# From what I understand @openmined/dp-research are going to look at validating correctness -# Until then we can use this to assert on floating point numbers. -# FIXME: When possible we should add 'correctness' tests. -expect_near = lambda expected, actual, tol: ( - expected + tol >= actual and expected - tol <= actual -) diff --git a/tests/algorithms/test_bounded_mean/test_bounded_mean_int64_data.bin b/tests/algorithms/test_bounded_mean/test_bounded_mean_int64_data.bin deleted file mode 100644 index 74ec56e4..00000000 --- a/tests/algorithms/test_bounded_mean/test_bounded_mean_int64_data.bin +++ /dev/null @@ -1,2 +0,0 @@ -M -;type.googleapis.com/differential_privacy.BoundedMeanSummary�ʵ��� \ No newline at end of file diff --git a/tests/algorithms/test_count.py b/tests/algorithms/test_count.py index ccc0f58a..d425991a 100644 --- a/tests/algorithms/test_count.py +++ b/tests/algorithms/test_count.py @@ -62,9 +62,6 @@ def test_count_datatypes(self): assert isinstance(mem, int) par = count.result() assert isinstance(par, int) - # TODO - # par2 = count.partial_result(1.0) - # assert isinstance(par2, int) res = count.quick_result([2]) assert isinstance(res, int) diff --git a/tests/algorithms/test_numerical_mechanisms.py b/tests/algorithms/test_numerical_mechanisms.py new file mode 100644 index 00000000..5cba64d6 --- /dev/null +++ b/tests/algorithms/test_numerical_mechanisms.py @@ -0,0 +1,84 @@ +import numpy as np +import pytest +import pydp.algorithms.numerical_mechanisms as num_mech +from scipy.special import erfinv + + +REL_ERR_TOL = 1e-5 + + +def assert_almost_eq(val_true, val_pred): + return np.abs((val_true - val_pred) / val_true) < REL_ERR_TOL + + +def test_basic(): + num_mech_methods = { + "add_noise", + "noised_value_above_threshold", + "memory_used", + "noise_confidence_interval", + "epsilon", + } + assert num_mech_methods.issubset(set(dir(num_mech.NumericalMechanism))) + epsilon, delta, sensitivity = 1, 1e-7, 5.0 + with pytest.raises(TypeError): + # This is a abstract class, it cannot be instantiated! + obj = num_mech.NumericalMechanism(epsilon, delta) + obj = num_mech.LaplaceMechanism(epsilon, sensitivity) + assert num_mech_methods.issubset(set(dir(obj))) + assert { + "memory_used", + "sensitivity", + "diversity", + }.issubset(set(dir(obj))) + obj = num_mech.GaussianMechanism(epsilon, delta, sensitivity) + assert num_mech_methods.issubset(set(dir(obj))) + assert { + "memory_used", + "l2_sensitivity", + "std", + "delta", + }.issubset(set(dir(obj))) + + +def test_laplace_mechanism(): + epsilon, sensitivity = 1, 3.0 + laplace = num_mech.LaplaceMechanism(epsilon, sensitivity) + value = 0 + value = laplace.add_noise(value) + assert type(value) is int + value = 0.0 + value = laplace.add_noise(value) + assert type(value) is float + conf_level = 0.5 + priv_budg = 0.1 + interval = laplace.noise_confidence_interval(0.5, value) + assert type(interval) is num_mech.ConfidenceInterval + bound = laplace.diversity * np.log(1 - conf_level) / priv_budg + lower_bound, upper_bound = value - bound, value + bound + assert_almost_eq(lower_bound, interval.lower_bound) + assert_almost_eq(upper_bound, interval.upper_bound) + assert conf_level == interval.confidence_level + + +def test_gaussian_mechanism(): + epsilon, delta, l2_sensitivity = 1, 1e-5, 3.0 + gaussian = num_mech.GaussianMechanism(epsilon, delta, l2_sensitivity) + value = 0 + value = gaussian.add_noise(value) + assert type(value) is int + value = 0.0 + value = gaussian.add_noise(value) + assert type(value) is float + conf_level = 0.5 + priv_budg = 0.1 + interval = gaussian.noise_confidence_interval(0.5, value) + local_gaussian = num_mech.GaussianMechanism( + priv_budg * epsilon, priv_budg * delta, l2_sensitivity + ) + assert type(interval) is num_mech.ConfidenceInterval + bound = erfinv(-conf_level) * local_gaussian.std * (2 ** 0.5) + lower_bound, upper_bound = value - bound, value + bound + assert_almost_eq(lower_bound, interval.lower_bound) + assert_almost_eq(upper_bound, interval.upper_bound) + assert conf_level == interval.confidence_level diff --git a/tests/algorithms/test_order_statistics.py b/tests/algorithms/test_order_statistics.py index fbb78307..4e5f8509 100644 --- a/tests/algorithms/test_order_statistics.py +++ b/tests/algorithms/test_order_statistics.py @@ -1,12 +1,7 @@ -# stdlib -import math - # verify with actual value import statistics # third party -from conftest import expect_near -from conftest import percentile import pytest # pydp absolute @@ -32,8 +27,8 @@ def test_min(data, dtype): minn = dp.algorithms.laplacian.Min( dtype=dtype, epsilon=1.0, lower_bound=0, upper_bound=200 ) - assert expect_near(min(data), minn.quick_result(data), 10) - assert expect_near(0, minn.quick_result(data), 10) + assert minn.quick_result(data) == pytest.approx(min(data), abs=10) + assert minn.quick_result(data) == pytest.approx(0, abs=10) @pytest.mark.parametrize("dtype, data", [("int", data_ints), ("float", data_floats)]) @@ -43,7 +38,7 @@ def test_max(data, dtype): ) assert 190 < maxx.quick_result(data) < 210 - assert expect_near(max(data), maxx.quick_result(data), 10) + assert maxx.quick_result(data) == pytest.approx(max(data), abs=10) @pytest.mark.parametrize("dtype, data", [("int", data_ints), ("float", data_floats)]) @@ -53,7 +48,7 @@ def test_median(data, dtype): dtype=dtype, epsilon=1.0, lower_bound=0, upper_bound=200 ) - assert expect_near(statistics.median(data), median.quick_result(data), 20) + assert median.quick_result(data) == pytest.approx(statistics.median(data), abs=20) @pytest.mark.parametrize("dtype", ["int", "float"]) diff --git a/tests/algorithms/test_partition_selection.py b/tests/algorithms/test_partition_selection.py index 1efc3053..2d3ee208 100644 --- a/tests/algorithms/test_partition_selection.py +++ b/tests/algorithms/test_partition_selection.py @@ -1,6 +1,5 @@ import numpy as np import pytest -import pydp as dp from pydp.algorithms.partition_selection import create_partition_strategy # TODO - wait for NumericalMechanism implementation to use those for testing Laplace/Gaussian Partition Selection. diff --git a/third_party/differential-privacy b/third_party/differential-privacy index 78d3fb8f..fc4f2abd 160000 --- a/third_party/differential-privacy +++ b/third_party/differential-privacy @@ -1 +1 @@ -Subproject commit 78d3fb8f63ea904ea6449a8276b9070254c650ec +Subproject commit fc4f2abda5052f654539fc1282ed64a827465a70