{"id":13482450,"url":"https://github.com/max-andr/provably-robust-boosting","last_synced_at":"2025-03-27T13:31:55.329Z","repository":{"id":96388061,"uuid":"185219128","full_name":"max-andr/provably-robust-boosting","owner":"max-andr","description":"Provably Robust Boosted Decision Stumps and Trees against Adversarial Attacks [NeurIPS 2019]","archived":false,"fork":false,"pushed_at":"2020-04-25T20:01:40.000Z","size":7482,"stargazers_count":49,"open_issues_count":0,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-07-01T03:12:14.151Z","etag":null,"topics":["adversarial-attacks","boosted-decision-stumps","boosted-trees","boosting","provable-defense"],"latest_commit_sha":null,"homepage":"http://arxiv.org/abs/1906.03526","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/max-andr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-05-06T15:02:21.000Z","updated_at":"2024-02-10T12:22:51.000Z","dependencies_parsed_at":"2023-07-11T15:15:32.261Z","dependency_job_id":null,"html_url":"https://github.com/max-andr/provably-robust-boosting","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/max-andr%2Fprovably-robust-boosting","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/max-andr%2Fprovably-robust-boosting/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/max-andr%2Fprovably-robust-boosting/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/max-andr%2Fprovably-robust-boosting/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/max-andr","download_url":"https://codeload.github.com/max-andr/provably-robust-boosting/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213388639,"owners_count":15579773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adversarial-attacks","boosted-decision-stumps","boosted-trees","boosting","provable-defense"],"created_at":"2024-07-31T17:01:02.108Z","updated_at":"2024-07-31T17:08:47.032Z","avatar_url":"https://github.com/max-andr.png","language":"Python","readme":"# Provably Robust Boosted Decision Stumps and Trees against Adversarial Attacks \n\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/abstract.png\" width=\"600\"\u003e\u003c/p\u003e\n\n**NeurIPS 2019**\n\n**Maksym Andriushchenko, Matthias Hein**\n\n**University of Tübingen**\n\n**Paper:** [http://arxiv.org/abs/1906.03526](http://arxiv.org/abs/1906.03526)\n\n\nThis repository contains a mini-library for training provably robust boosted decision \nstumps and trees written in `python`. Thus, it is easy to modify and experiment with this code, unlike\nthe code of `xgboost` or `lightgbm` which are implemented in `C++`.\nTo foster reproducible research, we also provide code for all main experiments \nreported in the paper (see `exps.sh`). \nMoreover, we also provide code for all figures shown in the paper, each as a separate Jupyter notebook \n(see folder `notebooks`). \nAll dependencies are collected in `Dockerfile`.\n\n**Models:** All our provably robust models (stumps and trees) are publicly available by \n[this link](https://drive.google.com/open?id=15p2ihucMVfNXEmqZBJYYvPHDeQjBixV6)\nincluding our MNIST, FMNIST, and CIFAR-10 models. \n\n**Version 2.0 of the repository (corresponds to the NeurIPS'19 camera-ready version):**\n- multi-class extension\n- significant speed-up via a parallel tree construction and parallel fitting of stumps to different coordinates\n- fixed memory leak issues due to `numba`\n- improved efficiency of individual tree predictions and certification using `numba`\n\n\n\n## Main idea of the paper\nWe propose provable defenses against adversarial attack for boosted decision stumps and trees.\nHere is the effect of our method on a 2D dataset.\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/toy2d_stumps_trees.png\" width=\"700\"\u003e\u003c/p\u003e\n\n\n## Provably robust training\nWe follow the framework of robust optimization aiming at solving the following min-max problem:\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/general_robust_optimization.png\" width=\"300\"\u003e\u003c/p\u003e\n\nWe first derive the robustness certificates. The certification for boosted stumps is exact:\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/certificate_stumps.png\" width=\"650\"\u003e\u003c/p\u003e\nFor boosted trees, we derive a simple lower bound on the functional margin which, however, \nbecomes tight after robust training.\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/certificate_trees.png\" width=\"550\"\u003e\u003c/p\u003e\n\nThen we integrate these certificates into training which leads to the exact robust loss or to an upper bound on \nthe robust loss for stumps and trees respectively.\n\nHow we minimize these robust losses? Surprisingly, it results in a **convex optimization problem** wrt the parameters of \nthe stumps or trees. We use coordinate descent combined with bisection to solve for w_r and w_l. \nFor more details, see the paper.\n\n\n## Experiments\nExperimental results show the efficiency of the robust training methods for boosted stumps and\nboosted trees:\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/tables_rte_stumps.png\" width=\"650\"\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/tables_rte_trees.png\" width=\"650\"\u003e\u003c/p\u003e\n\nMoreover, although decision trees as weak learners are obviously not suitable for computer vision tasks, our robust \nboosted trees nonetheless show provable robustness **competitive to provably robust CNNs** outperforming almost all \nproposed in the literature approaches:\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/comparison_cnns.png\" width=\"650\"\u003e\u003c/p\u003e\n\n\n\n## Effect of robust training\nThe effect of robust training can be clearly seen based on the splitting thresholds \nthat robust models select\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/thresholds_histograms.png\" width=\"750\"\u003e\u003c/p\u003e\n\n\n## Exact adversarial examples\nUsing our exact certification routine, we can also *efficiently* (without any combinatorial solvers) find provably \nminimal (exact) adversarial examples wrt Linf-norm for boosted stumps:\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/exact_adv_examples.png\" width=\"700\"\u003e\u003c/p\u003e\n\n\n## Interpretability\nOne of the main advantages of boosted trees is their interpretability and transparent decision making.\nUnlike neural networks, tree ensembles can be directly visualized based on which features they actually use \nfor classification. Here is an example of our provably robust boosted trees on MNIST 2 vs 6:\n\u003c!-- ![](images/trees.gif) --\u003e\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/trees.gif\" width=\"500\"\u003e\u003c/p\u003e\n\n\n\n## Code for training provably robust boosted stumps and trees\n\n\n### Simple example\n```python\nimport numpy as np\nimport data\nfrom tree_ensemble import TreeEnsemble\n\nn_trees = 50  # total number of trees in the ensemble\nmodel = 'robust_bound'  # robust tree ensemble\nX_train, y_train, X_test, y_test, eps = data.all_datasets_dict['breast_cancer']()\nX_train, X_test = data.convert_to_float32(X_train), data.convert_to_float32(X_test)\n\n# initialize a tree ensemble with some hyperparameters\nensemble = TreeEnsemble(weak_learner='tree', n_trials_coord=X_train.shape[1], \n                        lr=0.01, min_samples_split=10, min_samples_leaf=5, max_depth=4, \n                        max_weight=1.0, idx_clsf=0)\n# initialize gammas, per-example weights which are recalculated each iteration\ngamma = np.ones(X_train.shape[0])\nfor i in range(1, n_trees + 1):\n    # fit a new tree in order to minimize the robust loss of the whole ensemble\n    weak_learner = ensemble.fit_tree(X_train, y_train, gamma, model, eps, depth=1)\n    margin_prev = ensemble.certify_treewise(X_train, y_train, eps)  # needed for pruning\n    ensemble.add_weak_learner(weak_learner)\n    ensemble.prune_last_tree(X_train, y_train, margin_prev, eps, model)\n    # calculate per-example weights for the next iteration\n    gamma = np.exp(-ensemble.certify_treewise(X_train, y_train, eps))\n    \n    # track generalization and robustness\n    yf_test = y_test * ensemble.predict(X_test)\n    min_yf_test = ensemble.certify_treewise(X_test, y_test, eps)\n    if i == 1 or i % 5 == 0:\n        print('Iteration: {}, test error: {:.2%}, upper bound on robust test error: {:.2%}'.format(\n            i, np.mean(yf_test \u003c 0.0), np.mean(min_yf_test \u003c 0.0)))\n```\n```\nIteration: 1, test error: 2.92%, upper bound on robust test error: 10.95%\nIteration: 5, test error: 2.92%, upper bound on robust test error: 10.95%\nIteration: 10, test error: 2.19%, upper bound on robust test error: 10.22%\nIteration: 15, test error: 2.19%, upper bound on robust test error: 10.22%\nIteration: 20, test error: 2.19%, upper bound on robust test error: 10.22%\nIteration: 25, test error: 2.19%, upper bound on robust test error: 10.22%\nIteration: 30, test error: 1.46%, upper bound on robust test error: 8.03%\nIteration: 35, test error: 1.46%, upper bound on robust test error: 8.03%\nIteration: 40, test error: 1.46%, upper bound on robust test error: 7.30%\nIteration: 45, test error: 1.46%, upper bound on robust test error: 7.30%\nIteration: 50, test error: 0.73%, upper bound on robust test error: 6.57%\n```\n\n\n### Full training\nOne can train robust stumps or trees using `train.py`.  The full list of possible arguments is \navailable by `python train.py --help`. \n\nBoosted stumps models:\n- `python train.py --dataset=mnist_2_6 --weak_learner=stump --model=plain`  \n- `python train.py --dataset=mnist_2_6 --weak_learner=stump --model=at_cube --lr=0.01`  \n- `python train.py --dataset=mnist_2_6 --weak_learner=stump --model=robust_bound`\n- `python train.py --dataset=mnist_2_6 --weak_learner=stump --model=robust_exact`\n\nBoosted trees models:\n- `python train.py --dataset=mnist_2_6 --weak_learner=tree --model=plain --lr=0.2`  \n- `python train.py --dataset=mnist_2_6 --weak_learner=tree --model=at_cube --lr=0.2`  \n- `python train.py --dataset=mnist_2_6 --weak_learner=tree --model=robust_bound --lr=0.2`\n\nNote that Linf epsilons for adversarial attacks are specified for each dataset separately in `data.py`.\n\nSee more examples how to train our models on different datasets and in different settings in `exps.sh`.\n\n\n\n### Evaluation\n`eval.py` and `notebooks/adv_examples.ipynb` show how one can restore a trained model in order to evaluate it (e.g., \ncalculate the robustness bounds or to show the adversarial examples).\n\nIn order to evaluate the boosted tree models using MILP, we refer to [this repository](https://github.com/chenhongge/RobustTrees).\n\n\n### Jupyter notebooks to reproduce the figures\n- `notebooks/toy2d.ipynb` - Figure 1: toy dataset which shows that the usual training is non-robust, while our robust models\ncan robustly classify all training points.\n- `notebooks/minmax_objective.ipynb` - Figure 2: visualization of the min-max objective which is convex wrt its parameters.\n- `notebooks/model_analysis.ipynb` - Figures 3, 8, 9, 10: histograms of splitting thresholds, where we can observe a clear effect of \nrobust training on the choice of the splitting thresholds. Also: Figures 5, 6, 7 show the feature importance plots based\non the number of times some feature was used for a split.\n- `notebooks/robustness_generalization.ipynb` - Figure 4: the robustness vs accuracy trade-off.\n- `notebooks/adv_examples.ipynb` - Figures 11, 12, 13: exact adversarial examples for boosted stumps, \nwhich are much larger in Linf-norm for robust models. \n\n\n### Dependencies\nAll dependencies are collected in `Dockerfile`.\nThe best way to reproduce our environment is to use Docker. Just build the image and then run the container:\n- `docker build -t provably_robust_boosting .`\n- `docker run --name=boost -it -P -p 6001:6001 -t provably_robust_boosting`\n\n\n### Faster training\nRunning the code on small- and middle-scale datasets should be fast, however large-scale datasets \n(especially with many classes) may need some time to train. \nIn case you want to get results faster, there are several options to speed up the training:\n- **Check fewer thresholds**: You can try to subsample the number of thresholds by using the option `--n_bins=k`. \nThis approach comes with no guarantees, but based on our experience it does not sacrifice the robust \naccuracy for a *reasonably chosen* `k`, and can save a lot of computations.\n- **Parallelization over features**: The number of processes for this is determined automatically in \nfunction `get_n_proc(n_ex)` from `utils.py`. You can adapt this number according to the number of features in \nyour dataset, and the number of available CPU cores.\n- **Parallelization over tree construction**: The tree construction is also parallelized, see `def fit_tree(self, X, y, gamma, model, eps, depth)`.\nYou might need to adapt the depth limit under which the tree construction is parallelized, i.e. instead of the default\nvalue of `4` in line `if parallel and depth \u003c= 4:`, you can increase or reduce this number. Again, it depends on your \nhardware, the total depth, and the dataset, so it's hard to give a general advice. We recommend to change this value\nin a way that would maximize the CPU utilization.\n- **Parallelization over thresholds**: This can help if you have a few features, and many thresholds to check. You can \nset `parallel = True` at the top of `robust_boosting.py`, which would turn on the parallelization over thresholds \n(due to `numba` library). However, then you have to disable all other parallelization methods (i.e. over features and over \ntree construction, both are in `tree_ensemble.py`, just set `parallel = False` in `fit_tree()` and `fit_stumps_over_coords()`) \nwhich conflict with `numba`.\n\n## Export the model in XGBoost-compatible format\n```python\nimport json\n\nensembles = []\nfor i_clsf in range(n_classifiers):\n    ensembles.append(TreeEnsemble(weak_learner, 0, 0, 0, 0, 0, 0, 0, 0, 0))\n\nmodel_ova = OneVsAllClassifier(ensembles)\nmodel_ova.load('{}/{}.model.npy'.format(exp_folder, args.model_path), iteration=iter_to_take)\n\nmodel_list_of_dicts = model.dump_model()\nwith open('model.json', 'w') as f:\n    json.dump(model_list_of_dicts, f)\n```\n\n\n## Contact\nDo you have a problem or question regarding the code?\nPlease don't hesitate to open an issue or contact [Maksym Andriushchenko](https://github.com/max-andr) directly.\nWe would also like to hear from you if you find the code useful in some applications.\n\n\n## Citation\n```\n@article{andriushchenko2019provably,\n  title={Provably Robust Boosted Decision Stumps and Trees against Adversarial Attacks},\n  author={Andriushchenko, Maksym and Hein, Matthias},\n  conference={Advances in Neural Information Processing Systems},\n  year={2019}\n}\n```\n","funding_links":[],"categories":["2019"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmax-andr%2Fprovably-robust-boosting","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmax-andr%2Fprovably-robust-boosting","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmax-andr%2Fprovably-robust-boosting/lists"}