Repository: EpistasisLab/tpot Branch: main Commit: 1bca6c6a51a7 Files: 141 Total size: 4.0 MB Directory structure: gitextract_i76kzk_w/ ├── .github/ │ └── workflows/ │ ├── docs.yml │ ├── publish_package.yml │ └── tests.yml ├── .gitignore ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── Tutorial/ │ ├── 1_Using_TPOT.ipynb │ ├── 2_Search_Spaces.ipynb │ ├── 3_Feature_Set_Selector.ipynb │ ├── 4_Genetic_Feature_Selection.ipynb │ ├── 5_GraphPipeline.ipynb │ ├── 6_Symbolic_Regression_and_Classification.ipynb │ ├── 7_dask_parallelization.ipynb │ ├── 8_SH_and_cv_early_pruning.ipynb │ ├── 9_Genetic_Algorithm_Overview.ipynb │ ├── amltk_search_space_parser_example.ipynb │ └── simple_fss.csv ├── docs/ │ ├── archived/ │ │ ├── api.md │ │ ├── citing.md │ │ ├── contributing.md │ │ ├── css/ │ │ │ └── archived.css │ │ ├── examples.md │ │ ├── index.md │ │ ├── installing.md │ │ ├── related.md │ │ ├── releases.md │ │ ├── support.md │ │ └── using.md │ ├── cite.md │ ├── contribute.md │ ├── css/ │ │ └── extra.css │ ├── index.md │ ├── installation.md │ ├── related.md │ ├── requirements_docs.txt │ ├── scripts/ │ │ ├── build_docs_sources.sh │ │ ├── build_mkdocs.sh │ │ └── build_tutorial_toc_not_used.sh │ ├── support.md │ ├── tpot_api/ │ │ ├── classifier.md │ │ ├── estimator.md │ │ └── regressor.md │ └── using.md ├── mkdocs_archived.yml ├── pyproject.toml ├── tox.ini └── tpot/ ├── __init__.py ├── _version.py ├── builtin_modules/ │ ├── __init__.py │ ├── arithmetictransformer.py │ ├── column_one_hot_encoder.py │ ├── estimatortransformer.py │ ├── feature_encoding_frequency_selector.py │ ├── feature_set_selector.py │ ├── feature_transformers.py │ ├── genetic_encoders.py │ ├── imputer.py │ ├── nn.py │ ├── passkbinsdiscretizer.py │ ├── passthrough.py │ ├── tests/ │ │ └── feature_set_selector_tests.py │ └── zero_count.py ├── config/ │ ├── __init__.py │ ├── autoqtl_builtins.py │ ├── classifiers.py │ ├── classifiers_sklearnex.py │ ├── get_configspace.py │ ├── imputers.py │ ├── mdr_configs.py │ ├── regressors.py │ ├── regressors_sklearnex.py │ ├── selectors.py │ ├── special_configs.py │ ├── template_search_spaces.py │ ├── tests/ │ │ ├── __init__.py │ │ └── test_get_configspace.py │ └── transformers.py ├── evolvers/ │ ├── __init__.py │ ├── base_evolver.py │ └── steady_state_evolver.py ├── graphsklearn.py ├── individual.py ├── logbook.py ├── objectives/ │ ├── __init__.py │ ├── average_path_length.py │ ├── complexity.py │ ├── number_of_leaves.py │ ├── number_of_nodes.py │ └── tests/ │ ├── test_complexity_objective.py │ └── test_number_of_nodes.py ├── old_config_utils/ │ ├── __init__.py │ └── old_config_utils.py ├── population.py ├── search_spaces/ │ ├── __init__.py │ ├── base.py │ ├── graph_utils.py │ ├── nodes/ │ │ ├── __init__.py │ │ ├── estimator_node.py │ │ ├── estimator_node_gradual.py │ │ ├── fss_node.py │ │ └── genetic_feature_selection.py │ ├── pipelines/ │ │ ├── __init__.py │ │ ├── choice.py │ │ ├── dynamic_linear.py │ │ ├── dynamicunion.py │ │ ├── graph.py │ │ ├── sequential.py │ │ ├── tests/ │ │ │ └── test_graphspace.py │ │ ├── tree.py │ │ ├── union.py │ │ └── wrapper.py │ ├── tests/ │ │ └── test_search_spaces.py │ └── tuple_index.py ├── selectors/ │ ├── __init__.py │ ├── lexicase_selection.py │ ├── map_elites_selection.py │ ├── max_weighted_average_selector.py │ ├── nsgaii.py │ ├── random_selector.py │ ├── tournament_selection.py │ └── tournament_selection_dominated.py ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── test_estimators.py │ └── test_hello_world.py ├── tpot_estimator/ │ ├── __init__.py │ ├── cross_val_utils.py │ ├── estimator.py │ ├── estimator_utils.py │ ├── steady_state_estimator.py │ ├── templates/ │ │ ├── __init__.py │ │ ├── tpot_autoimputer.py │ │ └── tpottemplates.py │ └── tests/ │ ├── __init__.py │ └── test_estimator_utils.py └── utils/ ├── __init__.py ├── amltk_parser.py ├── eval_utils.py └── utils.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/docs.yml ================================================ name: Docs Build on: push: branches: - main jobs: build_docs: runs-on: ubuntu-latest env: GIT_COMMITTER_NAME: "Doc Build Bot" GIT_COMMITTER_EMAIL: "jay-m-dev@users.noreply.github.com" steps: - name: Checkout code uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.10' - name: Cache dependencies uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements_docs.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Install dependencies run: | pip install --upgrade pip pip install . pip install -r docs/requirements_docs.txt # - name: Convert notebooks to HTML # # if: ${{ github.event_name == 'push' && contains(github.event.head_commit.modified, 'Tutorial/') && contains(github.event.head_commit.modified, '.ipynb') }} # run: | # # jupyter nbconvert --to html --allow-errors --no-input --show-input --template classic --output-dir docs/tutorial Tutorial/*.ipynb # jupyter nbconvert --to html --allow-errors --template classic --output-dir docs/tutorial Tutorial/*.ipynb # - name: Build Tutorial Table of Contents # run: | # bash docs/scripts/build_tutorial_toc.sh - name: Build Documentation sources run: | bash docs/scripts/build_docs_sources.sh - name: Build mkdocs.yml run: | bash docs/scripts/build_mkdocs.sh - name: Checkout gh-pages run: | git fetch origin gh-pages git checkout gh-pages || git checkout --orphan gh-pages git pull origin gh-pages || echo "No remote changes to pull" git checkout main # Switch back before continuing - name: Build and Deploy Latest Docs run: | mike deploy --push --branch gh-pages latest - name: Build and Deploy Archived Docs run: | mike deploy --config-file mkdocs_archived.yml --push --branch gh-pages archived - name: Set Default Version run: | mike set-default latest --push --branch gh-pages - name: Create alias for Latest Docs run: | mike alias latest stable --push --branch gh-pages ================================================ FILE: .github/workflows/publish_package.yml ================================================ name: Publish Package on: release: types: [published] workflow_dispatch: jobs: build-and-publish-pypi: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup Python uses: actions/setup-python@v2 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build package run: python -m build - name: Upload to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: twine upload dist/* ================================================ FILE: .github/workflows/tests.yml ================================================ name: Tests on: - push - pull_request jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] python-version: ['3.10', '3.11', '3.12', '3.13'] 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 dependencies run: | python -m pip install --upgrade pip pip install tox tox-gh-actions - name: Test with tox run: tox ================================================ FILE: .gitignore ================================================ *.pyc .pytest_cache/ TPOT.egg-info TPOT.egg-info *.tar.gz *.pkl *.json joblib/ cache_folder/ dask-worker-space/ .tox/ *.egg-info/ .coverage target/ .venv/ build/* *.egg *.coverage* docs/documentation/ mkdocs.yml dist/ ================================================ FILE: ISSUE_TEMPLATE.md ================================================ [provide general introduction to the issue and why it is relevant to this repository] ## Context of the issue [provide more detailed introduction to the issue itself and why it is relevant] [the remaining entries are only necessary if you are reporting a bug] ## Process to reproduce the issue [ordered list the process to finding and recreating the issue, example below. A minimally reproducible example would be ideal. This refers to the minimum amount of code necessary to reproduce the issue.] 1. User creates TPOT instance 2. User calls TPOT `fit()` function with training data 3. TPOT crashes with a `KeyError` after 5 generations ## Expected result [describe what you would expect to have resulted from this process] ## Current result [describe what you currently experience from this process, and thereby explain the bug] ## Possible fix [not necessary, but suggest fixes or reasons for the bug] ## `name of issue` screenshot [if relevant, include a screenshot] ================================================ FILE: LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: PULL_REQUEST_TEMPLATE.md ================================================ [please review the [Contribution Guidelines](http://epistasislab.github.io/tpot/contributing/) prior to submitting your pull request. go ahead and delete this line if you've already reviewed said guidelines.] ## What does this PR do? ## Where should the reviewer start? ## How should this PR be tested? ## Any background context you want to provide? ## What are the relevant issues? [you can link directly to issues by entering # then the number of the issue] ## Screenshots (if appropriate) ## Questions: - Do the docs need to be updated? - Does this PR add new (Python) dependencies? ================================================ FILE: README.md ================================================ # TPOT

![Tests](https://github.com/EpistasisLab/tpot/actions/workflows/tests.yml/badge.svg) [![PyPI Downloads](https://img.shields.io/pypi/dm/tpot?label=pypi%20downloads)](https://pypi.org/project/TPOT) [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/tpot?label=conda%20downloads)](https://anaconda.org/conda-forge/tpot) TPOT stands for Tree-based Pipeline Optimization Tool. TPOT is a Python Automated Machine Learning tool that optimizes machine learning pipelines using genetic programming. Consider TPOT your Data Science Assistant. ## Contributors TPOT recently went through a major refactoring. The package was rewritten from scratch to improve efficiency and performance, support new features, and fix numerous bugs. New features include genetic feature selection, a significantly expanded and more flexible method of defining search spaces, multi-objective optimization, a more modular framework allowing for easier customization of the evolutionary algorithm, and more. While in development, this new version was referred to as "TPOT2" but we have now merged what was once TPOT2 into the main TPOT package. You can learn more about this new version of TPOT in our GPTP paper titled "TPOT2: A New Graph-Based Implementation of the Tree-Based Pipeline Optimization Tool for Automated Machine Learning." Ribeiro, P. et al. (2024). TPOT2: A New Graph-Based Implementation of the Tree-Based Pipeline Optimization Tool for Automated Machine Learning. In: Winkler, S., Trujillo, L., Ofria, C., Hu, T. (eds) Genetic Programming Theory and Practice XX. Genetic and Evolutionary Computation. Springer, Singapore. https://doi.org/10.1007/978-981-99-8413-8_1 The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (Lead developer - https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors ## License Please see the [repository license](https://github.com/EpistasisLab/tpot/blob/main/LICENSE) for the licensing and usage information for TPOT. Generally, we have licensed TPOT to make it as widely usable as possible. TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . ## Documentation [The documentation webpage can be found here.](https://epistasislab.github.io/tpot/) We also recommend looking at the Tutorials folder for jupyter notebooks with examples and guides. ## Installation TPOT requires a working installation of Python. ### Creating a conda environment (optional) We recommend using conda environments for installing TPOT, though it would work equally well if manually installed without it. [More information on making anaconda environments found here.](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) ``` conda create --name tpotenv python=3.10 conda activate tpotenv ``` ### Packages Used python version >=3.10, <3.14 numpy scipy scikit-learn update_checker tqdm stopit pandas joblib xgboost matplotlib traitlets lightgbm optuna jupyter networkx dask distributed dask-ml dask-jobqueue func_timeout configspace Many of the hyperparameter ranges used in our configspaces were adapted from either the original TPOT package or the AutoSklearn package. ### Note for M1 Mac or other Arm-based CPU users You need to install the lightgbm package directly from conda using the following command before installing TPOT. This is to ensure that you get the version that is compatible with your system. ``` conda install --yes -c conda-forge 'lightgbm>=3.3.3' ``` ### Installing Extra Features with pip If you want to utilize the additional features provided by TPOT along with `scikit-learn` extensions, you can install them using `pip`. The command to install TPOT with these extra features is as follows: ``` pip install tpot[sklearnex] ``` Please note that while these extensions can speed up scikit-learn packages, there are some important considerations: These extensions may not be fully developed and tested on Arm-based CPUs, such as M1 Macs. You might encounter compatibility issues or reduced performance on such systems. We recommend using Python 3.9 when installing these extra features, as it provides better compatibility and stability. ### Developer/Latest Branch Installation ``` pip install -e /path/to/tpotrepo ``` If you downloaded with git pull, then the repository folder will be named TPOT. (Note: this folder is the one that includes setup.py inside of it and not the folder of the same name inside it). If you downloaded as a zip, the folder may be called tpot-main. ## Usage See the Tutorials Folder for more instructions and examples. ### Best Practices #### 1 TPOT uses dask for parallel processing. When Python is parallelized, each module is imported within each processes. Therefore it is important to protect all code within a `if __name__ == "__main__"` when running TPOT from a script. This is not required when running TPOT from a notebook. For example: ``` #my_analysis.py import tpot if __name__ == "__main__": X, y = load_my_data() est = tpot.TPOTClassifier() est.fit(X,y) #rest of analysis ``` #### 2 When designing custom objective functions, avoid the use of global variables. Don't Do: ``` global_X = [[1,2],[4,5]] global_y = [0,1] def foo(est): return my_scorer(est, X=global_X, y=global_y) ``` Instead use a partial ``` from functools import partial def foo_scorer(est, X, y): return my_scorer(est, X, y) if __name__=='__main__': X = [[1,2],[4,5]] y = [0,1] final_scorer = partial(foo_scorer, X=X, y=y) ``` Similarly when using lambda functions. Dont Do: ``` def new_objective(est, a, b) #definition a = 100 b = 20 bad_function = lambda est : new_objective(est=est, a=a, b=b) ``` Do: ``` def new_objective(est, a, b) #definition a = 100 b = 20 good_function = lambda est, a=a, b=b : new_objective(est=est, a=a, b=b) ``` ### Tips TPOT will not check if your data is correctly formatted. It will assume that you have passed in operators that can handle the type of data that was passed in. For instance, if you pass in a pandas dataframe with categorical features and missing data, then you should also include in your configuration operators that can handle those feautures of the data. Alternatively, if you pass in `preprocessing = True`, TPOT will impute missing values, one hot encode categorical features, then standardize the data. (Note that this is currently fitted and transformed on the entire training set before splitting for CV. Later there will be an option to apply per fold, and have the parameters be learnable.) Setting `verbose` to 5 can be helpful during debugging as it will print out the error generated by failing pipelines. ## Contributing to TPOT We welcome you to check the existing issues for bugs or enhancements to work on. If you have an idea for an extension to TPOT, please file a new issue so we can discuss it. ## Citing TPOT If you use TPOT in a scientific publication, please consider citing at least one of the following papers: Hernandez, J. G., Saini, A. K., Ghosh, A., & Moore, J. H. (2025). [The tree-based pipeline optimization tool: Tackling biomedical research problems with genetic programming and automated machine learning](https://www.cell.com/patterns/fulltext/S2666-3899(25)00162-X). Patterns, 6(7). BibTeX entry: ```bibtext @article{hernandez2025tree, title={The tree-based pipeline optimization tool: Tackling biomedical research problems with genetic programming and automated machine learning}, author={Hernandez, Jose Guadalupe and Saini, Anil Kumar and Ghosh, Attri and Moore, Jason H}, journal={Patterns}, volume={6}, number={7}, year={2025}, publisher={Elsevier} } ``` Ribeiro, P., Saini, A., Moran, J., Matsumoto, N., Choi, H., Hernandez, M., & Moore, J. H. (2024). [TPOT2: A New Graph-Based Implementation of the Tree-Based Pipeline Optimization Tool for Automated Machine Learning](https://link.springer.com/chapter/10.1007/978-981-99-8413-8_1). In Genetic programming theory and practice XX (pp. 1-17). Singapore: Springer Nature Singapore. BitTex entry: ```bibtex @incollection{ribeiro2024tpot2, title={TPOT2: A New Graph-Based Implementation of the Tree-Based Pipeline Optimization Tool for Automated Machine Learning}, author={Ribeiro, Pedro and Saini, Anil and Moran, Jay and Matsumoto, Nicholas and Choi, Hyunjun and Hernandez, Miguel and Moore, Jason H}, booktitle={Genetic programming theory and practice XX}, pages={1--17}, year={2024}, publisher={Springer} } ``` Randal S. Olson, Ryan J. Urbanowicz, Peter C. Andrews, Nicole A. Lavender, La Creis Kidd, and Jason H. Moore (2016). [Automating biomedical data science through tree-based pipeline optimization](http://link.springer.com/chapter/10.1007/978-3-319-31204-0_9). *Applications of Evolutionary Computation*, pages 123-137. BibTeX entry: ```bibtex @inbook{Olson2016EvoBio, author={Olson, Randal S. and Urbanowicz, Ryan J. and Andrews, Peter C. and Lavender, Nicole A. and Kidd, La Creis and Moore, Jason H.}, editor={Squillero, Giovanni and Burelli, Paolo}, chapter={Automating Biomedical Data Science Through Tree-Based Pipeline Optimization}, title={Applications of Evolutionary Computation: 19th European Conference, EvoApplications 2016, Porto, Portugal, March 30 -- April 1, 2016, Proceedings, Part I}, year={2016}, publisher={Springer International Publishing}, pages={123--137}, isbn={978-3-319-31204-0}, doi={10.1007/978-3-319-31204-0_9}, url={http://dx.doi.org/10.1007/978-3-319-31204-0_9} } ``` Randal S. Olson, Nathan Bartley, Ryan J. Urbanowicz, and Jason H. Moore (2016). [Evaluation of a Tree-based Pipeline Optimization Tool for Automating Data Science](http://dl.acm.org/citation.cfm?id=2908918). *Proceedings of GECCO 2016*, pages 485-492. BibTeX entry: ```bibtex @inproceedings{OlsonGECCO2016, author = {Olson, Randal S. and Bartley, Nathan and Urbanowicz, Ryan J. and Moore, Jason H.}, title = {Evaluation of a Tree-based Pipeline Optimization Tool for Automating Data Science}, booktitle = {Proceedings of the Genetic and Evolutionary Computation Conference 2016}, series = {GECCO '16}, year = {2016}, isbn = {978-1-4503-4206-3}, location = {Denver, Colorado, USA}, pages = {485--492}, numpages = {8}, url = {http://doi.acm.org/10.1145/2908812.2908918}, doi = {10.1145/2908812.2908918}, acmid = {2908918}, publisher = {ACM}, address = {New York, NY, USA}, } ``` ## Related Papers Trang T. Le, Weixuan Fu and Jason H. Moore (2020). [Scaling tree-based automated machine learning to biomedical big data with a feature set selector](https://academic.oup.com/bioinformatics/article/36/1/250/5511404). *Bioinformatics*.36(1): 250-256. BibTeX entry: ```bibtex @article{le2020scaling, title={Scaling tree-based automated machine learning to biomedical big data with a feature set selector}, author={Le, Trang T and Fu, Weixuan and Moore, Jason H}, journal={Bioinformatics}, volume={36}, number={1}, pages={250--256}, year={2020}, publisher={Oxford University Press} } ``` ## Support for TPOT TPOT was developed in the [Artificial Intelligence Innovation (A2I) Lab](http://epistasis.org/) at Cedars-Sinai with funding from the [NIH](http://www.nih.gov/) under grants U01 AG066833 and R01 LM010098. We are incredibly grateful for the support of the NIH and the Cedars-Sinai during the development of this project. The TPOT logo was designed by Todd Newmuis, who generously donated his time to the project. ================================================ FILE: Tutorial/1_Using_TPOT.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# What to expect from AutoML software\n", "Automated machine learning (AutoML) takes a higher-level approach to machine learning than most practitioners are used to, so we've gathered a handful of guidelines on what to expect when running AutoML software such as TPOT.\n", "\n", "#### AUTOML ALGORITHMS AREN'T INTENDED TO RUN FOR ONLY A FEW MINUTES\n", "Of course, you can run TPOT for only a few minutes, and it will find a reasonably good pipeline for your dataset. However, if you don't run TPOT for long enough, it may not find the best possible pipeline for your dataset. It may not even find any suitable pipeline at all, in which case a RuntimeError('A pipeline has not yet been optimized. Please call fit() first.') will be raised. Often it is worthwhile to run multiple instances of TPOT in parallel for a long time (hours to days) to allow TPOT to thoroughly search the pipeline space for your dataset.\n", "\n", "#### AUTOML ALGORITHMS CAN TAKE A LONG TIME TO FINISH THEIR SEARCH\n", "AutoML algorithms aren't as simple as fitting one model on the dataset; they consider multiple machine learning algorithms (random forests, linear models, SVMs, etc.) in a pipeline with multiple preprocessing steps (missing value imputation, scaling, PCA, feature selection, etc.), the hyperparameters for all of the models and preprocessing steps, and multiple ways to ensemble or stack the algorithms within the pipeline.\n", "\n", "As such, TPOT will take a while to run on larger datasets, but it's important to realize why. With the default TPOT settings (100 generations with 100 population size), TPOT will evaluate 10,000 pipeline configurations before finishing. To put this number into context, think about a grid search of 10,000 hyperparameter combinations for a machine learning algorithm and how long that grid search will take. That is 10,000 model configurations to evaluate with 10-fold cross-validation, which means that roughly 100,000 models are fit and evaluated on the training data in one grid search. That's a time-consuming procedure, even for simpler models like decision trees.\n", "\n", "Typical TPOT runs will take hours to days to finish (unless it's a small dataset), but you can always interrupt the run partway through and see the best results so far. TPOT also provides a warm_start and a periodic_checkpoint_folder parameter that lets you restart a TPOT run from where it left off.\n", "\n", "#### AUTOML ALGORITHMS CAN RECOMMEND DIFFERENT SOLUTIONS FOR THE SAME DATASET\n", "If you're working with a reasonably complex dataset or run TPOT for a short amount of time, different TPOT runs may result in different pipeline recommendations. TPOT's optimization algorithm is stochastic, which means that it uses randomness (in part) to search the possible pipeline space. When two TPOT runs recommend different pipelines, this means that the TPOT runs didn't converge due to lack of time or that multiple pipelines perform more-or-less the same on your dataset.\n", "\n", "This is actually an advantage over fixed grid search techniques: TPOT is meant to be an assistant that gives you ideas on how to solve a particular machine learning problem by exploring pipeline configurations that you might have never considered, then leaves the fine-tuning to more constrained parameter tuning techniques such as grid search or bayesian optimization." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# TPOT with code\n", "\n", "We've designed the TPOT interface to be as similar as possible to scikit-learn.\n", "\n", "TPOT can be imported just like any regular Python module. To import TPOT, type:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Matplotlib is building the font cache; this may take a moment.\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] } ], "source": [ "import tpot\n", "from tpot import TPOTClassifier" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "then create an instance of TPOT as follows:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "classification_optimizer = TPOTClassifier()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's also possible to use TPOT for regression problems with the TPOTRegressor class. Other than the class name, a TPOTRegressor is used the same way as a TPOTClassifier. You can read more about the TPOTClassifier and TPOTRegressor classes in the API documentation." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "from tpot import TPOTRegressor\n", "regression_optimizer = TPOTRegressor()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fitting a TPOT model works exactly like any other sklearn estimator. Some example code with custom TPOT parameters might look like:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 5it [00:32, 6.57s/it]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "auroc_score: 0.9950396825396826\n" ] } ], "source": [ "import sklearn\n", "import sklearn.datasets\n", "import sklearn.metrics\n", "import tpot\n", "\n", "classification_optimizer = TPOTClassifier(search_space=\"linear-light\", max_time_mins=30/60, n_jobs=30, cv=5)\n", "\n", "X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, random_state=1, test_size=0.2)\n", "\n", "classification_optimizer.fit(X_train, y_train)\n", "\n", "auroc_score = sklearn.metrics.roc_auc_score(y_test, classification_optimizer.predict_proba(X_test)[:,1])\n", "print(\"auroc_score: \", auroc_score)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scorers, Objective Functions, and multi objective optimization.\n", "\n", "There are two ways of passing objectives into TPOT. \n", "\n", "1. `scorers`: Scorers are functions that have the signature (estimator, X_test, y_test) and take in estimators that are expected to be fitted to training data. These can be produced with the [sklearn.metrics.make_scorer](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html) function. This function is used to evaluate the test folds during cross validation (defined in the `cv` parameter). These are passed into TPOT via the scorers parameter. This can take in the scorer itself or the string corresponding to a scoring function ([as listed here](https://scikit-learn.org/stable/modules/model_evaluation.html)). TPOT also supports passing in a list of several scorers for multi-objective optimization. For each fold of CV, TPOT only fits the estimator once, then evaluates all provided scorers in a loop.\n", "\n", "2. `other_objective_functions` : Other objective functions in TPOT have the signature (estimator) and returns a float or list of floats. These get passed a single unfitted estimator once, outside of cross validation. The user may choose to fit the pipeline within this objective function as well.\n", "\n", "\n", "\n", "Each scorer and objective function must be accompanied by a list of weights corresponding to the list of objectives, these are `scorers_weights` and `other_objective_function_weights`, respectively. By default, TPOT maximizes objective functions (this can be changed by `bigger_is_better=False`). Positive weights means that TPOT will seek to maximize that objective, and negative weights correspond to minimization. For most selectors (and the default), only the sign matters. The scale of the weight may matter if using a custom selection function for the optimization algorithm. A zero weight means that the score will not have an impact on the selection algorithm.\n", "\n", "Here is an example of using two scorers\n", "\n", " scorers=['roc_auc_ovr',tpot.objectives.complexity_scorer],\n", " scorers_weights=[1,-1],\n", "\n", "\n", "Here is an example with a scorer and a secondary objective function\n", "\n", " scorers=['roc_auc_ovr'],\n", " scorers_weights=[1],\n", " other_objective_functions=[tpot.objectives.number_of_leaves_objective],\n", " other_objective_functions_weights=[-1],\n", "\n", "\n", "TPOT will always automatically name the scorers based on the function name for the columns in the final results dataframe. TPOT will use the function name as the column name for `other_objective_functions`. However, if you would like to specify custom column names, you can set the `objective_function_names` to be a list of names (str) for each value returned by the function in `other_objective_functions`. This can be useful if your additional functions return more than one value per function.\n", "\n", "It is possible to have either the scorer or other_objective_function to return multiple values. In that case, just make sure that the `scorers_weights` and `other_objective_function_weights` are the same length as the number of returned scores.\n", "\n", "\n", "TPOT comes with a few additional built in objective functions you can use. The first table are objectives applied to fitted pipelines, and thus are passee into the `scorers` parameter. The second table are objective functions for the `other_objective_functions` param.\n", "\n", "Scorers:\n", "| Function | Description |\n", "| :--- | :----: |\n", "| tpot.objectives.complexity_scorer | Estimates the number of learned parameters across all classifiers and regressors in the pipelines. Additionally, currently transformers add 1 point and selectors add 0 points (since they don't affect the complexity of the \"final\" predictive pipeline.) |\n", "\n", "Other Objective Functions.\n", "\n", "| Function | Description |\n", "| :--- | :----: |\n", "| tpot.objectives.average_path_length | Computes the average shortest path from all nodes to the root/final estimator (only supported for GraphPipeline) |\n", "| tpot.objectives.number_of_leaves_objective | Calculates the number of leaves (input nodes) in a GraphPipeline |\n", "| tpot.objectives.number_of_nodes_objective | Calculates the number of nodes in a pipeline (whether it is an scikit-learn Pipeline, GraphPipeline, Feature Union, or the previous nested within each other) |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Measuring Model Complexity\n", "\n", "When running TPOT, including a secondary objective that measures model complexity can sometimes be beneficial. More complex models can yield higher performance, but this comes at the cost of interpretability. Simpler models may be more interpretable but often have lower predictive performance. Sometimes, however, vast increases in complexity only marginally improve predictive performance. There may be other simpler and more interpretable pipelines with marginal performance decreases that could be acceptable for the increased interpretability. However, these pipelines are often missed when optimizing purely for performance. By including both performance and complexity as objective functions, TPOT will attempt to optimize the best pipeline for all complexity levels simultaneously. After optimization, the user will be able to see the complexity vs performance tradeoff and decide which pipeline best suits their needs. \n", "\n", "Two methods of measuring complexity to consider would be `tpot.objectives.number_of_nodes_objective` or `tpot.objectives.complexity_scorer`. The number of nodes objective simply calculates the number of steps within a pipeline. This is a simple metric, however it does not differentiate between the complexity of different model types. For example, a simple LogisticRegression counts the same as the much more complex XGBoost. The complexity scorer tries to estimate the number of learned parameters included in the classifiers and regressors of the pipeline. It is challenging and potentially subjective how to exactly quantify and compare complexity between different classes of models. However, this function provides a reasonable heuristic for the evolutionary algorithm that at least separates out qualitatively more or less complex algorithms from one another. While it may be hard to compare the relative complexities of LogisticRegression and XGBoost exactly, for example, both will always be on opposite ends of the complexity values returned by this function. This allows for pareto fronts with LogisticRegression on one side, and XGBoost on the other.\n", "\n", "An example of this analysis is demonstrated in a following section." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Built In Configurations\n", "TPOT can be used to optimize hyperparameters, select models, and optimize pipelines of models including determining the sequence of steps. **Tutorial 2** goes into more detail on how to customize search spaces with custom hyperparameter ranges, model types, and possible pipeline configurations. TPOT also comes with a handful of default operators and parameter configurations that we believe work well for optimizing machine learning pipelines. Below is a list of the current built-in configurations that come with TPOT. These can be passed in as strings to the `search space` parameter of any of the TPOT estimators.\n", "\n", "| String | Description |\n", "| :--- | :----: |\n", "| linear | A linear pipeline with the structure of \"Selector->(transformers+Passthrough)->(classifiers/regressors+Passthrough)->final classifier/regressor.\" For both the transformer and inner estimator layers, TPOT may choose one or more transformers/classifiers, or it may choose none. The inner classifier/regressor layer is optional. |\n", "| linear-light | Same search space as linear, but without the inner classifier/regressor layer and with a reduced set of faster running estimators. |\n", "| graph | TPOT will optimize a pipeline in the shape of a directed acyclic graph. The nodes of the graph can include selectors, scalers, transformers, or classifiers/regressors (inner classifiers/regressors can optionally be not included). This will return a custom GraphPipeline rather than an sklearn Pipeline. More details in Tutorial 6. |\n", "| graph-light | Same as graph search space, but without the inner classifier/regressors and with a reduced set of faster running estimators. |\n", "| mdr |TPOT will search over a series of feature selectors and Multifactor Dimensionality Reduction models to find a series of operators that maximize prediction accuracy. The TPOT MDR configuration is specialized for genome-wide association studies (GWAS), and is described in detail online here.\n", "\n", "Note that TPOT MDR may be slow to run because the feature selection routines are computationally expensive, especially on large datasets. |\n", "\n", "The `linear` and `graph` configurations by default allow for additional stacked classifiers/regressors within the pipeline in addition to the final classifier/regressor. If you would like to disable this, you can manually get the search space without inner classifier/regressors through the function `tpot.config.template_search_spaces.get_template_search_spaces` with `inner_predictios=False`. You can pass the resulting search space into the `search space` param. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import tpot\n", "from tpot.search_spaces.pipelines import SequentialPipeline\n", "from tpot.config import get_search_space\n", "\n", "stc_search_space = SequentialPipeline([\n", " get_search_space(\"selectors\"),\n", " get_search_space(\"all_transformers\"),\n", " get_search_space(\"classifiers\"),\n", "])\n", "\n", "est = tpot.TPOTEstimator(\n", " search_space = stc_search_space,\n", " scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", " classification = True,\n", " cv = 5,\n", " max_eval_time_mins = 10,\n", " early_stop = 2,\n", " verbose = 2,\n", " n_jobs=4,\n", ")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using a built in method" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "est = tpot.TPOTEstimator(\n", " search_space = \"linear\",\n", " scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", " classification = True,\n", " cv = 5,\n", " max_eval_time_mins = 10,\n", " early_stop = 2,\n", " verbose = 2,\n", " n_jobs=4,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The specific hyperparameter ranges used by TPOT can be found in files in the tpot/config folder. The template search spaces listed above are defined in tpot/config/template_search_spaces.py. Search spaces for individual models can be acquired in the tpot/config/get_configspace.py file (`tpot.config.get_search_space`). More details on customizing search spaces can be found in Tutorial 2.\n", "\n", "\n", " `tpot.config.template_search_spaces.get_template_search_spaces`\n", " Returns a search space which can be optimized by TPOT.\n", "\n", " Parameters\n", " ----------\n", " search_space: str or SearchSpace\n", " The default search space to use. If a string, it should be one of the following:\n", " - 'linear': A search space for linear pipelines\n", " - 'linear-light': A search space for linear pipelines with a smaller, faster search space\n", " - 'graph': A search space for graph pipelines\n", " - 'graph-light': A search space for graph pipelines with a smaller, faster search space\n", " - 'mdr': A search space for MDR pipelines\n", " If a SearchSpace object, it should be a valid search space object for TPOT.\n", " \n", " classification: bool, default=True\n", " Whether the problem is a classification problem or a regression problem.\n", "\n", " inner_predictors: bool, default=None\n", " Whether to include additional classifiers/regressors before the final classifier/regressor (allowing for ensembles). \n", " Defaults to False for 'linear-light' and 'graph-light' search spaces, and True otherwise. (Not used for 'mdr' search space)\n", " \n", " cross_val_predict_cv: int, default=None\n", " The number of folds to use for cross_val_predict. \n", " Defaults to 0 for 'linear-light' and 'graph-light' search spaces, and 5 otherwise. (Not used for 'mdr' search space)\n", "\n", " get_search_space_params: dict\n", " Additional parameters to pass to the get_search_space function.\n", "\n", "### cross_val_predict_cv\n", "\n", "Additionally, utilizing `cross_val_predict_cv` may increase performance when training models with inner classifiers/regressors. If this parameter is set, during model training any classifiers or regressors that is not the final predictor will use `sklearn.model_selection.cross_val_predict` to pass out of sample predictions into the following steps of the model. The model will still be fit to the full data which will be used for predictions after training. Training downstream models on out of sample predictions can often prevent overfitting and increase performance. The reason is that this gives downstream models a estimate of how upstream models compare on unseen data. Otherwise, if an upsteam model heavily overfits the data, downsteam models may simply learn to blindly trust the seemingly well-predicting model, propagating the over-fitting through to the end result.\n", "\n", "The downside is that cross_val_predict_cv is significantly more computationally demanding, and may not be necessary for your given dataset. \n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "linear_with_cross_val_predict_sp = tpot.config.template_search_spaces.get_template_search_spaces(search_space=\"linear\", classification=True, inner_predictors=True, cross_val_predict_cv=5)\n", "classification_optimizer = TPOTClassifier(search_space=linear_with_cross_val_predict_sp, max_time_mins=30/60, n_jobs=30, cv=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Terminating Optimization (Early Stopping)\n", "\n", "Note that we use a short time duration for a quick example, but in practice, you may need to run TPOT for a longer duration. By default, TPOT sets a time limit of 1 hour with a max limit of 5 minutes per pipeline. In practice, you may want to increase these values.\n", "\n", "There are three methods of terminating a TPOT run and ending the optimization process. TPOT will terminate as soon as one of the conditions is met.\n", "* `max_time_mins` : (Default, 60 minutes) After this many minutes, TPOT will terminate and return the best pipeline it found so far.\n", "* `early_stop` : The number of generations without seeing an improvement in performance, after which TPOT terminates. Generally, a value of around 5 to 20 is sufficient to be reasonably sure that performance has converged.\n", "* `generations`: The total number of generations of the evolutionary algorithm to run.\n", "\n", "By default, TPOT will run until the time limit is up, with no generation or early stop limits." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Best Practices and tips:\n", "\n", "* When running tpot from an .py script, it is important to protect code with `if __name__==\"__main__\":` . This is because of how TPOT handles parallelization with Python and Dask." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 1it [03:13, 193.20s/it]\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 0 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 1 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 2 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 3 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 4 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 5 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 6 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 7 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 8 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 9 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 10 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 11 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 12 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 13 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 14 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 15 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 16 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 17 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 18 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 19 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 20 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 21 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 22 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 23 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 24 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 25 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 26 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 27 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 28 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 29 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 30 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 31 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 32 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 33 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 34 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 35 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 36 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 37 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 38 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 39 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 40 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 41 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 42 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 43 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 44 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 45 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 46 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 47 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 48 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 49 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 50 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 51 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 52 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 53 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 54 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 55 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 56 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 57 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 58 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_discretization.py:307: UserWarning: Bins whose width are too small (i.e., <= 1e-8) in feature 59 are removed. Consider decreasing the number of bins.\n", " warnings.warn(\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.999621947852182\n" ] } ], "source": [ "from dask.distributed import Client, LocalCluster\n", "import tpot\n", "import sklearn\n", "import sklearn.datasets\n", "import numpy as np\n", "\n", "if __name__==\"__main__\":\n", " scorer = sklearn.metrics.get_scorer('roc_auc_ovo')\n", " X, y = sklearn.datasets.load_digits(return_X_y=True)\n", " X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "\n", " est = tpot.TPOTClassifier(n_jobs=4, max_time_mins=3, verbose=2, early_stop=3)\n", " est.fit(X_train, y_train)\n", "\n", "\n", " print(scorer(est, X_test, y_test))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Example analysis and the Estimator class \n", "\n", "Here we use a toy example dataset included in scikit-learn. We will use the `light` configuration and the `complexity_scorer` to estimate complexity.\n", "\n", "Note, for this toy example, we set a relatively short run time. In practice, we would recommend running TPOT for a longer duration with an `early_stop` value of around 5 to 20 (more details below)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 4it [02:34, 38.64s/it]\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/neural_network/_multilayer_perceptron.py:690: ConvergenceWarning: Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.\n", " warnings.warn(\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.9978289188015632\n" ] } ], "source": [ "from dask.distributed import Client, LocalCluster\n", "import tpot\n", "import sklearn\n", "import sklearn.datasets\n", "import numpy as np\n", "\n", "import tpot.objectives\n", "\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovr')\n", "\n", "X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "\n", "est = tpot.TPOTClassifier(\n", " scorers=[scorer, tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", "\n", " search_space=\"linear\",\n", " n_jobs=4, \n", " max_time_mins=60, \n", " max_eval_time_mins=10,\n", " early_stop=2,\n", " verbose=2,)\n", "est.fit(X_train, y_train)\n", "\n", "print(scorer(est, X_test, y_test))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can access the best pipeline selected by TPOT with the `fitted_pipeline_` attribute. This is the pipeline with the highest cross validation score (on the first scorer, or first objective function if no scorer is provided.)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('minmaxscaler', MinMaxScaler()),\n",
       "                ('selectpercentile',\n",
       "                 SelectPercentile(percentile=68.60012151662)),\n",
       "                ('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                 SkipTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('featureunion-2',\n",
       "                 FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                 SkipTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('mlpclassifier',\n",
       "                 MLPClassifier(activation='identity', alpha=0.0023692590029,\n",
       "                               hidden_layer_sizes=[139, 139],\n",
       "                               learning_rate='invscaling',\n",
       "                               learning_rate_init=0.0004707733364,\n",
       "                               n_iter_no_change=32))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('minmaxscaler', MinMaxScaler()),\n", " ('selectpercentile',\n", " SelectPercentile(percentile=68.60012151662)),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('featureunion-2',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('mlpclassifier',\n", " MLPClassifier(activation='identity', alpha=0.0023692590029,\n", " hidden_layer_sizes=[139, 139],\n", " learning_rate='invscaling',\n", " learning_rate_init=0.0004707733364,\n", " n_iter_no_change=32))])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "best_pipeline = est.fitted_pipeline_\n", "best_pipeline" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,\n", " 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0,\n", " 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1,\n", " 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1,\n", " 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0,\n", " 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1,\n", " 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "best_pipeline.predict(X_test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Saving the Pipeline\n", "\n", "We recommend using dill or pickle to save the instance of the fitted_pipeline_. Note that we do not recommend pickling the TPOT object itself." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "import dill as pickle\n", "with open(\"best_pipeline.pkl\", \"wb\") as f:\n", " pickle.dump(best_pipeline, f)\n", "\n", "#load the pipeline\n", "import dill as pickle\n", "with open(\"best_pipeline.pkl\", \"rb\") as f:\n", " my_loaded_best_pipeline = pickle.load(f)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The evaluated_individuals Dataframe - Further analysis of results\n", "\n", "The `evaluated_individuals` attribute of the tpot estimator object is a Pandas Dataframe containing information about a run. Each row corresponds to an individual pipeline explored by tpot. The dataframe contains the following columns:\n", "\n", "| Column | Description |\n", "| :--- | :----: |\n", "| \\ | The first set of columns will correspond to each objective function. These can either be automatically named by TPOT, or passed in by the user. |\n", "| Parents | This contains a tuple that contains the indexes of the 'parents' of the current pipeline. For example, (29, 42) means that the pipelines in indexes 29 and 42 were utilized to generate that pipeline. |\n", "| Variation_Function | The function applied to the parents to generate the new pipeline |\n", "| Individual | The individual class that represents a specific pipeline and hyperparameter configuration. This class also contains functions for mutation and crossover. To get the sklearn estimator/pipeline object from the individual you can call the `export_pipeline()` function. (as in, `pipe = ind.export_pipeline()`) |\n", "| Generation | The generation where the individual was created. (Note that the higher performing pipelines from previous generations may still be present in the current \"population\" of a given generation if selected.) |\n", "| Submitted Timestamp | Timestamp, in seconds, at which the pipeline was sent to be evaluated. This is the output of time.time(), which is \"Return the time in seconds since the epoch as a floating-point number. \" |\n", "| Completed Timestamp | Timestamp at which the pipeline evaluation completed in the same units as Submitted Timestamp |\n", "| Pareto_Front\t | If you have multiple parameters, this column is True if the pipeline performance fall on the pareto front line. This is the set of pipelines with scores that are strictly better than pipelines not on the line, but not strictly better than one another. |\n", "| Instance | This contains the unfitted pipeline evaluated for this row. (This is the pipeline returned by calling the export_pipeline() function of the individual class) |\n" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['roc_auc_score', 'complexity_scorer']" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#get the score/objective column names generated by TPOT\n", "est.objective_names" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
roc_auc_scorecomplexity_scorerParentsVariation_FunctionIndividualGenerationSubmitted TimestampCompleted TimestampEval ErrorPareto_FrontInstance
0NaNNaNNaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.01.740178e+091.740178e+09INVALIDNaN(MaxAbsScaler(), RFE(estimator=ExtraTreesClass...
1NaNNaNNaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.01.740178e+091.740179e+09INVALIDNaN(RobustScaler(quantile_range=(0.1386847479391,...
2NaNNaNNaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.01.740178e+091.740178e+09INVALIDNaN(RobustScaler(quantile_range=(0.0087917518794,...
3NaNNaNNaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.01.740178e+091.740178e+09INVALIDNaN(Passthrough(), Passthrough(), FeatureUnion(tr...
40.969262241.2NaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.01.740178e+091.740178e+09NoneNaN(RobustScaler(quantile_range=(0.0359502923061,...
....................................
2450.98628044.0(184, 184)ind_crossover<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09NoneNaN(RobustScaler(quantile_range=(0.1428289713161,...
2460.9028459.0(145, 148)ind_mutate , ind_mutate , ind_crossover<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09NoneNaN(MinMaxScaler(), SelectFwe(alpha=0.00184795618...
2470.9928515301.0(155, 133)ind_mutate , ind_mutate , ind_crossover<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09NoneNaN(MaxAbsScaler(), SelectFwe(alpha=0.00212090942...
2480.9923497749.0(152, 152)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09NoneNaN(MinMaxScaler(), SelectFromModel(estimator=Ext...
2490.5152429.0(182, 182)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09NoneNaN(MaxAbsScaler(), VarianceThreshold(threshold=0...
\n", "

250 rows × 11 columns

\n", "
" ], "text/plain": [ " roc_auc_score complexity_scorer Parents \\\n", "0 NaN NaN NaN \n", "1 NaN NaN NaN \n", "2 NaN NaN NaN \n", "3 NaN NaN NaN \n", "4 0.969262 241.2 NaN \n", ".. ... ... ... \n", "245 0.986280 44.0 (184, 184) \n", "246 0.902845 9.0 (145, 148) \n", "247 0.992851 5301.0 (155, 133) \n", "248 0.992349 7749.0 (152, 152) \n", "249 0.515242 9.0 (182, 182) \n", "\n", " Variation_Function \\\n", "0 NaN \n", "1 NaN \n", "2 NaN \n", "3 NaN \n", "4 NaN \n", ".. ... \n", "245 ind_crossover \n", "246 ind_mutate , ind_mutate , ind_crossover \n", "247 ind_mutate , ind_mutate , ind_crossover \n", "248 ind_mutate \n", "249 ind_mutate \n", "\n", " Individual Generation \\\n", "0 " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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aJdO3X/9/OhrqN09U7qf+mWEa/hXjpY4pV+CaCuUhh/9AkB/T8l7M6XHLCXHHUAQsAAgwEycONHS/74WL15snTp1yjp06JA1depUK0uWLFb69Omtw4cPW3/++aeVNm1aa+DAgW6P3bZtmxUeHu42/YknnjDrGzt27E3P1bx5cys6Otpt2ubNm83yr776qtv0bt26mek//fSTc1qBAgXMtPnz57stu3TpUjO9ZMmS1tWrV53TGzdubIWFhVl16tRxWz4+Pt6sy9XFixdv2t7atWtbhQsXdpvm2Iaff/7ZOe3kyZNWZGSk1bVrV+e0vn37muVmzpx503pv3Lhhfn755ZdWmjRprBUrVrjN19dOH7ty5UrrVnQ/s2fPbvb50qVLzulz5swxj9XnT3qM169fb91J/fr1rYiICGvfvn3OaUePHrUyZsxoPf744zets0aNGs79UZ07dzbvlTNnzri9J/TmsGDBAvPYefPmuT136dKl3ZZLjr5H9bH9+vVL9v2l8wYMGOA2vVy5claFChWc9/X11uWmTJnitpy+r5KbnpQ+ty63Z88esz379++3xo0bZ94DOXLksC5cuGBdv37dunLlitvjTp8+bea/8sorzmn6WF1XbGyseR+5ql69ulWqVCnr8uXLzmn6WlepUsUqWrSoc9r06dPNOvRz4ErXp8eyVq1aVmJionP6p59+apafMGHCbffTcYyTu7l+7vQz4vr5sfPeTOkxu91xBxAc6NoHIGDpN9b6za92LdOWJG050NYg/TZcx31o64m2Ruk3045bzpw5pWjRojd1V9Jv1PXb6JTQghZKu4e50pYpNXfuXLfp2qKi314nR7vNuX7Drt9wawuHtgi50unaJVG7FDq4jrNyfAP/xBNPyB9//GHuu9Juf9pq46Cvm7b66LIO2mJQpkwZ8w16Uo5CBNr1SVsOHnroIbfX1dFFLOnr6mrDhg1m7JO2mmmXL4e6deua9SV93VJCWwa0aIK2eGh3RYdcuXLJv//9b9NylrQqnbaguBZW0NdF13PgwIHbvte0i9mUKVOc07SoydatW00L5t3S8UqudJtcj42+7trdrmbNmm6vu7Yo6vv+dq+7Kz3meuz1PaktXNo9U1937d6mLW2OMU762fnnn3/M+01bZjdt2nTTunR8laP7mtLltXCFfua0Ncuxjdraq+//vXv33tRNLilt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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(5,5))\n", "sns.scatterplot(df[df['Pareto_Front']!=1], x='roc_auc_score', y='complexity_scorer', label='other', ax=ax)\n", "sns.scatterplot(df[df['Pareto_Front']==1], x='roc_auc_score', y='complexity_scorer', label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of all pipelines')\n", "#log scale y\n", "ax.set_yscale('log')\n", "plt.show()\n", "\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(10,5))\n", "sns.scatterplot(df[df['Pareto_Front']==1], x='roc_auc_score', y='complexity_scorer', label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of only the Pareto Front')\n", "#log scale y\n", "# ax.set_yscale('log')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
roc_auc_scorecomplexity_scorerParentsVariation_FunctionIndividualGenerationSubmitted TimestampCompleted TimestampEval ErrorPareto_FrontInstance
510.996818582.0(13, 13)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...1.01.740179e+091.740179e+09None1.0(MinMaxScaler(), SelectPercentile(percentile=6...
1330.99623931.0(65, 65)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...2.01.740179e+091.740179e+09None1.0(StandardScaler(), SelectFwe(alpha=0.002276474...
1850.99584330.9(133, 133)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...3.01.740179e+091.740179e+09None1.0(StandardScaler(), SelectFwe(alpha=0.000234016...
2330.99511530.7(185, 185)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09None1.0(StandardScaler(), SelectFwe(alpha=0.000234016...
850.99089426.0(6, 23)ind_crossover<tpot.search_spaces.pipelines.sequential.Seque...1.01.740179e+091.740179e+09None1.0(MaxAbsScaler(), SelectFwe(alpha=0.00114277554...
2280.99008119.0(162, 162)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09None1.0(MaxAbsScaler(), VarianceThreshold(threshold=0...
2150.9886149.0(162, 162)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...4.01.740179e+091.740179e+09None1.0(MaxAbsScaler(), VarianceThreshold(threshold=0...
1210.9825247.0(10, 10)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...2.01.740179e+091.740179e+09None1.0(MaxAbsScaler(), SelectFwe(alpha=0.03019980124...
\n", "
" ], "text/plain": [ " roc_auc_score complexity_scorer Parents Variation_Function \\\n", "51 0.996818 582.0 (13, 13) ind_mutate \n", "133 0.996239 31.0 (65, 65) ind_mutate \n", "185 0.995843 30.9 (133, 133) ind_mutate \n", "233 0.995115 30.7 (185, 185) ind_mutate \n", "85 0.990894 26.0 (6, 23) ind_crossover \n", "228 0.990081 19.0 (162, 162) ind_mutate \n", "215 0.988614 9.0 (162, 162) ind_mutate \n", "121 0.982524 7.0 (10, 10) ind_mutate \n", "\n", " Individual Generation \\\n", "51 #sk-container-id-2 {\n", " /* Definition of color scheme common for light and dark mode */\n", " --sklearn-color-text: black;\n", " --sklearn-color-line: gray;\n", " /* Definition of color scheme for unfitted estimators */\n", " --sklearn-color-unfitted-level-0: #fff5e6;\n", " --sklearn-color-unfitted-level-1: #f6e4d2;\n", " --sklearn-color-unfitted-level-2: #ffe0b3;\n", " --sklearn-color-unfitted-level-3: chocolate;\n", " /* Definition of color scheme for fitted estimators */\n", " --sklearn-color-fitted-level-0: #f0f8ff;\n", " --sklearn-color-fitted-level-1: #d4ebff;\n", " --sklearn-color-fitted-level-2: #b3dbfd;\n", " --sklearn-color-fitted-level-3: cornflowerblue;\n", "\n", " /* Specific color for light theme */\n", " --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n", " --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n", " --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n", " --sklearn-color-icon: #696969;\n", "\n", " @media (prefers-color-scheme: dark) {\n", " /* Redefinition of color scheme for dark theme */\n", " --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n", " --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n", " --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n", " --sklearn-color-icon: #878787;\n", " }\n", "}\n", "\n", "#sk-container-id-2 {\n", " color: var(--sklearn-color-text);\n", "}\n", "\n", "#sk-container-id-2 pre {\n", " padding: 0;\n", "}\n", "\n", "#sk-container-id-2 input.sk-hidden--visually {\n", " border: 0;\n", " clip: rect(1px 1px 1px 1px);\n", " clip: rect(1px, 1px, 1px, 1px);\n", " height: 1px;\n", " margin: -1px;\n", " overflow: hidden;\n", " padding: 0;\n", " position: absolute;\n", " width: 1px;\n", "}\n", "\n", "#sk-container-id-2 div.sk-dashed-wrapped {\n", " border: 1px dashed var(--sklearn-color-line);\n", " margin: 0 0.4em 0.5em 0.4em;\n", " box-sizing: border-box;\n", " padding-bottom: 0.4em;\n", " background-color: var(--sklearn-color-background);\n", "}\n", "\n", "#sk-container-id-2 div.sk-container {\n", " /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n", " but bootstrap.min.css set `[hidden] { display: none !important; }`\n", " so we also need the `!important` here to be able to override the\n", " default hidden behavior on the sphinx rendered scikit-learn.org.\n", " See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n", " display: inline-block !important;\n", " position: relative;\n", "}\n", "\n", "#sk-container-id-2 div.sk-text-repr-fallback {\n", " display: none;\n", "}\n", "\n", "div.sk-parallel-item,\n", "div.sk-serial,\n", "div.sk-item {\n", " /* draw centered vertical line to link estimators */\n", " background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n", " background-size: 2px 100%;\n", " background-repeat: no-repeat;\n", " background-position: center center;\n", "}\n", "\n", "/* Parallel-specific style estimator block */\n", "\n", "#sk-container-id-2 div.sk-parallel-item::after {\n", " content: \"\";\n", " width: 100%;\n", " border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n", " flex-grow: 1;\n", "}\n", "\n", "#sk-container-id-2 div.sk-parallel {\n", " display: flex;\n", " align-items: stretch;\n", " justify-content: center;\n", " background-color: var(--sklearn-color-background);\n", " position: relative;\n", "}\n", "\n", "#sk-container-id-2 div.sk-parallel-item {\n", " display: flex;\n", " flex-direction: column;\n", "}\n", "\n", "#sk-container-id-2 div.sk-parallel-item:first-child::after {\n", " align-self: flex-end;\n", " width: 50%;\n", "}\n", "\n", "#sk-container-id-2 div.sk-parallel-item:last-child::after {\n", " align-self: flex-start;\n", " width: 50%;\n", "}\n", "\n", "#sk-container-id-2 div.sk-parallel-item:only-child::after {\n", " width: 0;\n", "}\n", "\n", "/* Serial-specific style estimator block */\n", "\n", "#sk-container-id-2 div.sk-serial {\n", " display: flex;\n", " flex-direction: column;\n", " align-items: center;\n", " background-color: var(--sklearn-color-background);\n", " padding-right: 1em;\n", " padding-left: 1em;\n", "}\n", "\n", "\n", "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n", "clickable and can be expanded/collapsed.\n", "- Pipeline and ColumnTransformer use this feature and define the default style\n", "- Estimators will overwrite some part of the style using the `sk-estimator` class\n", "*/\n", "\n", "/* Pipeline and ColumnTransformer style (default) */\n", "\n", "#sk-container-id-2 div.sk-toggleable {\n", " /* Default theme specific background. It is overwritten whether we have a\n", " specific estimator or a Pipeline/ColumnTransformer */\n", " background-color: var(--sklearn-color-background);\n", "}\n", "\n", "/* Toggleable label */\n", "#sk-container-id-2 label.sk-toggleable__label {\n", " cursor: pointer;\n", " display: block;\n", " width: 100%;\n", " margin-bottom: 0;\n", " padding: 0.5em;\n", " box-sizing: border-box;\n", " text-align: center;\n", "}\n", "\n", "#sk-container-id-2 label.sk-toggleable__label-arrow:before {\n", " /* Arrow on the left of the label */\n", " content: \"▸\";\n", " float: left;\n", " margin-right: 0.25em;\n", " color: var(--sklearn-color-icon);\n", "}\n", "\n", "#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {\n", " color: var(--sklearn-color-text);\n", "}\n", "\n", "/* Toggleable content - dropdown */\n", "\n", "#sk-container-id-2 div.sk-toggleable__content {\n", " max-height: 0;\n", " max-width: 0;\n", " overflow: hidden;\n", " text-align: left;\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-0);\n", "}\n", "\n", "#sk-container-id-2 div.sk-toggleable__content.fitted {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-0);\n", "}\n", "\n", "#sk-container-id-2 div.sk-toggleable__content pre {\n", " margin: 0.2em;\n", " border-radius: 0.25em;\n", " color: var(--sklearn-color-text);\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-0);\n", "}\n", "\n", "#sk-container-id-2 div.sk-toggleable__content.fitted pre {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-fitted-level-0);\n", "}\n", "\n", "#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n", " /* Expand drop-down */\n", " max-height: 200px;\n", " max-width: 100%;\n", " overflow: auto;\n", "}\n", "\n", "#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n", " content: \"▾\";\n", "}\n", "\n", "/* Pipeline/ColumnTransformer-specific style */\n", "\n", "#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " color: var(--sklearn-color-text);\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "#sk-container-id-2 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "/* Estimator-specific style */\n", "\n", "/* Colorize estimator box */\n", "#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "#sk-container-id-2 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "#sk-container-id-2 div.sk-label label.sk-toggleable__label,\n", "#sk-container-id-2 div.sk-label label {\n", " /* The background is the default theme color */\n", " color: var(--sklearn-color-text-on-default-background);\n", "}\n", "\n", "/* On hover, darken the color of the background */\n", "#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {\n", " color: var(--sklearn-color-text);\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "/* Label box, darken color on hover, fitted */\n", "#sk-container-id-2 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n", " color: var(--sklearn-color-text);\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "/* Estimator label */\n", "\n", "#sk-container-id-2 div.sk-label label {\n", " font-family: monospace;\n", " font-weight: bold;\n", " display: inline-block;\n", " line-height: 1.2em;\n", "}\n", "\n", "#sk-container-id-2 div.sk-label-container {\n", " text-align: center;\n", "}\n", "\n", "/* Estimator-specific */\n", "#sk-container-id-2 div.sk-estimator {\n", " font-family: monospace;\n", " border: 1px dotted var(--sklearn-color-border-box);\n", " border-radius: 0.25em;\n", " box-sizing: border-box;\n", " margin-bottom: 0.5em;\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-0);\n", "}\n", "\n", "#sk-container-id-2 div.sk-estimator.fitted {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-0);\n", "}\n", "\n", "/* on hover */\n", "#sk-container-id-2 div.sk-estimator:hover {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "#sk-container-id-2 div.sk-estimator.fitted:hover {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n", "\n", "/* Common style for \"i\" and \"?\" */\n", "\n", ".sk-estimator-doc-link,\n", "a:link.sk-estimator-doc-link,\n", "a:visited.sk-estimator-doc-link {\n", " float: right;\n", " font-size: smaller;\n", " line-height: 1em;\n", " font-family: monospace;\n", " background-color: var(--sklearn-color-background);\n", " border-radius: 1em;\n", " height: 1em;\n", " width: 1em;\n", " text-decoration: none !important;\n", " margin-left: 1ex;\n", " /* unfitted */\n", " border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n", " color: var(--sklearn-color-unfitted-level-1);\n", "}\n", "\n", ".sk-estimator-doc-link.fitted,\n", "a:link.sk-estimator-doc-link.fitted,\n", "a:visited.sk-estimator-doc-link.fitted {\n", " /* fitted */\n", " border: var(--sklearn-color-fitted-level-1) 1pt solid;\n", " color: var(--sklearn-color-fitted-level-1);\n", "}\n", "\n", "/* On hover */\n", "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n", ".sk-estimator-doc-link:hover,\n", "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n", ".sk-estimator-doc-link:hover {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-3);\n", " color: var(--sklearn-color-background);\n", " text-decoration: none;\n", "}\n", "\n", "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n", ".sk-estimator-doc-link.fitted:hover,\n", "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n", ".sk-estimator-doc-link.fitted:hover {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-3);\n", " color: var(--sklearn-color-background);\n", " text-decoration: none;\n", "}\n", "\n", "/* Span, style for the box shown on hovering the info icon */\n", ".sk-estimator-doc-link span {\n", " display: none;\n", " z-index: 9999;\n", " position: relative;\n", " font-weight: normal;\n", " right: .2ex;\n", " padding: .5ex;\n", " margin: .5ex;\n", " width: min-content;\n", " min-width: 20ex;\n", " max-width: 50ex;\n", " color: var(--sklearn-color-text);\n", " box-shadow: 2pt 2pt 4pt #999;\n", " /* unfitted */\n", " background: var(--sklearn-color-unfitted-level-0);\n", " border: .5pt solid var(--sklearn-color-unfitted-level-3);\n", "}\n", "\n", ".sk-estimator-doc-link.fitted span {\n", " /* fitted */\n", " background: var(--sklearn-color-fitted-level-0);\n", " border: var(--sklearn-color-fitted-level-3);\n", "}\n", "\n", ".sk-estimator-doc-link:hover span {\n", " display: block;\n", "}\n", "\n", "/* \"?\"-specific style due to the `` HTML tag */\n", "\n", "#sk-container-id-2 a.estimator_doc_link {\n", " float: right;\n", " font-size: 1rem;\n", " line-height: 1em;\n", " font-family: monospace;\n", " background-color: var(--sklearn-color-background);\n", " border-radius: 1rem;\n", " height: 1rem;\n", " width: 1rem;\n", " text-decoration: none;\n", " /* unfitted */\n", " color: var(--sklearn-color-unfitted-level-1);\n", " border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n", "}\n", "\n", "#sk-container-id-2 a.estimator_doc_link.fitted {\n", " /* fitted */\n", " border: var(--sklearn-color-fitted-level-1) 1pt solid;\n", " color: var(--sklearn-color-fitted-level-1);\n", "}\n", "\n", "/* On hover */\n", "#sk-container-id-2 a.estimator_doc_link:hover {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-3);\n", " color: var(--sklearn-color-background);\n", " text-decoration: none;\n", "}\n", "\n", "#sk-container-id-2 a.estimator_doc_link.fitted:hover {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-3);\n", "}\n", "
Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),\n",
       "                ('selectfwe', SelectFwe(alpha=0.0301998012478)),\n",
       "                ('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                 SkipTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('featureunion-2',\n",
       "                 FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                 SkipTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('kneighborsclassifier',\n",
       "                 KNeighborsClassifier(n_jobs=1, n_neighbors=2))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),\n", " ('selectfwe', SelectFwe(alpha=0.0301998012478)),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('featureunion-2',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('kneighborsclassifier',\n", " KNeighborsClassifier(n_jobs=1, n_neighbors=2))])" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#access the best performing pipeline with the lowest complexity\n", "\n", "best_pipeline_lowest_complexity = sorted_pareto_front.iloc[-1]['Instance']\n", "best_pipeline_lowest_complexity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plot performance over time + Continuing a run from where it left off\n", "\n", "Plotting performance over time is a good way to assess whether or not the TPOT model has converged. If performance asymptotes over time, there may not be much more performance to be gained by running for a longer period. If the plot looks like it is still improving, it may be worth running TPOT for a longer duration. \n", "\n", "In this case, we can see that performance is near optimal and has slowed, so more time is likely unnecessary." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#get columns where roc_auc_score is not NaN\n", "scores_and_times = df[df['roc_auc_score'].notna()][['roc_auc_score', 'Completed Timestamp']].sort_values('Completed Timestamp', ascending=True).to_numpy()\n", "\n", "#get best score at a given time\n", "best_scores = np.maximum.accumulate(scores_and_times[:,0])\n", "times = scores_and_times[:,1]\n", "times = times - df['Submitted Timestamp'].min()\n", "\n", "fig, ax = plt.subplots(figsize=(10,5))\n", "ax.plot(times, best_scores)\n", "ax.set_xlabel('Time (seconds)')\n", "ax.set_ylabel('Best Score')\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Checkpointing\n", "\n", "There are two ways to resume TPOT. \n", "* If the `warm_start` parameter is set to True, subsequent calls to `fit` will continue training where it left off (The conventional scikit-learn default is to retrain from scratch on subsequent calls to fit). \n", "* If `periodic_checkpoint_folder` is set, TPOT will periodically save its current state to disk. If TPOT is interrupted (job canceled, PC shut off, crashes), you can resume training from where it left off. The checkpoint folder stores a data frame of all evaluated pipelines. This data frame can be loaded and inspected to help diagnose problems when debugging.\n", "\n", "\n", "**Note: TPOT does not clean up the checkpoint files. If the `periodic_checkpoint_folder` parameter is set, training from the last saved point will always continue, even if the input data has changed. A common issue is forgetting to change this folder between experiments and TPOT continuing training from pipelines optimized for another dataset. If you intend to start a run from scratch, you must either remove the parameter, supply an empty folder, or delete the original checkpoint folder.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Common parameters\n", "\n", "Here is a subset of the most common parameters to customize and what they do. See the docs for `TPOTEstimator` or `TPOTEstimatorSteadyState` full documentation of all parameters. \n", "\n", "| Parameter | Type | Description |\n", "|--------------------------------|-----------------------|-----------------------------------------------------------------------------|\n", "| scorers | list, scorer | List of scorers for cross-validation; see |\n", "| scorers_weights | list | Weights applied to scorers during optimization |\n", "| classification | bool | Problem type: True for classification, False for regression |\n", "| cv | int, cross-validator | Cross-validation strategy: int for folds or custom cross-validator |\n", "| max_depth | int | Maximum pipeline depth |\n", "| other_objective_functions | list | Additional objective functions; default: [average_path_length_objective] |\n", "| other_objective_functions_weights | list | Weights for additional objective functions; default: [-1] |\n", "| objective_function_names | list | Names for objective functions; default: None (uses function names) |\n", "| bigger_is_better | bool | Optimization direction: True for maximize, False for minimize |\n", "| generations | int | Number of optimization generations; default: 50 |\n", "| max_time_mins | float | Maximum optimization time (minutes); default: infinite |\n", "| max_eval_time_mins | float | Maximum evaluation time per individual (minutes); default: 300 |\n", "| n_jobs | int | Number of parallel processes; default: 1 |\n", "| memory_limit | str | Memory limit per job; default: \"4GB\" |\n", "| verbose | int | Optimization process verbosity: 0 (none), 1 (progress), 3 (best individual), 4 (warnings), 5+ (full warnings) |\n", "| memory | str, memory object | If supplied, pipeline will cache each transformer after calling fit with joblib.Memory. |\n", "| periodic_checkpoint_folder | str | Folder to save the population to periodically. If None, no periodic saving will be done. If provided, training will resume from this checkpoint.|\n", " \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Preventing Overfitting\n", "\n", "On small datasets, it is not impossible for TPOT to overfit the cross-validation score itself. This can lead to lower-than-expected performance on held-out datasets. TPOT will always return the model with the highest CV score as its final fitted_pipeline. However, if the highest performing model, as evaluated by cross-validation, actually was just overfit to the CV score, it may actually be worse performing compared to other models on the Pareto front.\n", "  * Using a secondary complexity objective and evaluating the entire pareto front may be beneficial. In some cases a lower performing pipeline with lower complexity can actually perform better on held out sets. These can either be evaluated and compared on a held out validation set, or sometimes, if very data limited, simply using a different seed of splitting the CV folds can work as well.\n", "    * TPOT can do this automatically. The `validation_strategy` parameter can be set to re-test the final pareto front on either a held-out validation set (percent of data set by `validation_fraction`) or a different seed for splitting the CV folds. These can be selected by setting `validation_strategy` to \"split\" or \"reshuffled\", respectively.\n", "  * Increasing the number of folds of cross-validation can mitigate this. \n", "  * Nested cross-validation can also be used to estimate the performance of the TPOT optimization algorithm itself.\n", "  * Removing more complex methods from the search space can reduce the chances of overfitting" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Tips and tricks for speeding up TPOT\n", "\n", "TPOT can be a computationally demanding algorithm as it fits thousands of complex machine learning pipelines on potentially large datasets. There are several strategies available for improving run time by reducing the compute needed. \n", "\n", "There are three main strategies implemented in TPOT to reduce redundant work and/or prevent wasting compute on poorly performing pipelines.\n", "\n", "1. TPOT pipelines will often have the exact same components doing the exact same computation (e.g. the first steps of the pipeline remain the same and only the parameters of the final classifier changed.) In these cases, that The first strategy is to simply cache these repeat computations so that they only happen once. More info in the next subsection.\n", "2. Successive Halving. This idea was first tested with TPOT by Parmentier et al. in [\"TPOT-SH: a Faster Optimization Algorithm to Solve the AutoML Problem on Large Datasets\"](https://www.researchgate.net/profile/Laurent-Parmentier-4/publication/339263193_TPOT-SH_A_Faster_Optimization_Algorithm_to_Solve_the_AutoML_Problem_on_Large_Datasets/links/5e5fd8b8a6fdccbeba1c6a56/TPOT-SH-A-Faster-Optimization-Algorithm-to-Solve-the-AutoML-Problem-on-Large-Datasets.pdf). The algorithm operates in two stages. Initially, it trains early generations using a small data subset and a large population size. Later generations then evaluate a smaller set of promising pipelines on larger, or even full, data portions. This approach rapidly identifies top-performing pipeline configurations through initial rough evaluations, followed by more comprehensive assessments. More information on this strategy in Tutorial 8.\n", "3. Most often, we will be evaluating pipelines using cross validation. However, we can often tell within the first few folds whether or not the pipeline is going have a reasonable change of outperforming the previous best pipelines. For example, if the best score so far is .92 AUROC and the average score of the first five folds of our current pipeline is only around .61, we can be reasonably confident that the next five folds are unlikely to this pipeline ahead of the others. We can save a significant amount of compute by not computing the rest of the folds. There are two strategies that TPOT can use to accomplish this (More information on these strategies in Tutorial 8).\n", " 1. Threshold Pruning: Pipelines must achieve a score above a predefined percentile threshold (based on previous pipeline scores) to proceed in each cross-validation (CV) fold.\n", " 2. Selection Pruning: Within each population, only the top N% of pipelines (ranked by performance in the previous CV fold) are selected to evaluate in the next fold.\"\n", " \n", "\n", "## Pipeline caching in TPOT (joblib.Memory)\n", "\n", "With the memory parameter, pipelines can cache the results of each transformer after fitting them. This feature is used to avoid repeated computation by transformers within a pipeline if the parameters and input data are identical to another fitted pipeline during the optimization process. TPOT allows users to specify a custom directory path or joblib.Memory in case they want to re-use the memory cache in future TPOT runs (or a warm_start run).\n", "\n", "There are three methods for enabling memory caching in TPOT:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "from tpot import TPOTClassifier\n", "from tempfile import mkdtemp\n", "from joblib import Memory\n", "from shutil import rmtree\n", "\n", "# Method 1, auto mode: TPOT uses memory caching with a temporary directory and cleans it up upon shutdown\n", "est = TPOTClassifier(memory='auto')\n", "\n", "# Method 2, with a custom directory for memory caching\n", "est = TPOTClassifier(memory='/to/your/path')\n", "\n", "# Method 3, with a Memory object\n", "memory = Memory(location='./to/your/path', verbose=0)\n", "est = TPOTClassifier(memory=memory)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note: TPOT does NOT clean up memory caches if users set a custom directory path or Memory object. We recommend that you clean up the memory caches when you don't need it anymore.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Advanced Parallelization (HPC and multi-node training)\n", "\n", "See Tutorial 7 for more details on parallelization with Dask, including information of using multiple nodes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# FAQ and Debugging\n", "\n", "If you are experiencing issues with TPOT, here are some common issues and how to address them.\n", "\n", "* Performance is lower than expected. What can I do?\n", " * TPOT may have to be run for a longer duration, increase `max_time_mins`, `early_stop`, or `generations`.\n", " * Individual pipelines may need more time to complete fitting; increase `max_eval_time_seconds.`\n", " * The configuration may not include the optimal model types or hyperparameter ranges, explore other included templates, or customize your own search space (see Tutorial 2!)\n", " * Check that `periodic_checkpoint_folder` is set correctly. A common issue is forgetting to change this folder between experiments and TPOT continuing training from pipelines optimized for another dataset.\n", "* TPOT is too slow! It is running forever and never terminating\n", " * Check that at least one of the three termination conditions is set to a reasonable level. These are `max_time_mins`, `early_stop`, or `generations`. Additionally, check that `max_eval_time_seconds` gives enough time for most models to train without being overly long. (Some estimators may take an unreasonably long time to fit; this parameter is intended to prevent them from slowing everything to a halt. In my experience, SVC and SVR tend to be the culprits, so removing them from the search space may also improve run time).\n", " * Set the `memory` parameter to allow TPOT to prevent repeated work when using either scikit-learn pipelines or TPOT GraphPipelines.\n", " * Increase n_jobs to use more processes/CPU power. See Tutorial 7 for advanced Dask usage, including parallelizing across multiple nodes on an HPC.\n", " * Use feature selection, either the build in configuration of sklearn methods (see Tutorial 2), or genetic feature selection (see Tutorials 3 and 5 for two different strategies).\n", " * Use successive halving to reduce computational load (See tutorial 8).\n", "* Many pipelines in the evaluated_individuals data frame have crashed or turned up invalid!\n", " * This is normal and is expected behavior for TPOT. In some cases, TPOT may attempt an invalid hyperparameter combination, resulting in the pipeline not working. Other times, the pipeline configuration itself may be invalid. For example, a selector may not select any features due to its hyperparameter. Another common example is `MultinomialNB` throwing an error because it expects positive values, but a prior transformation yielded a negative value. \n", " * If you used custom search spaces, you can use `ConfigSpace` conditionals to prevent invalid hyperparameters (this may still occur due to how TPOT uses crossover).\n", " * Setting `verbose=5` will print out the full error message for all failed pipelines. This can be useful for debugging whether or not there is something misconfigured in your pipeline, custom search space modules, or something else.\n", "* TPOT is crashing due to memory issues\n", " * Set the `memory_limit` parameter so that n_jobs*memorylimit is less than the available RAM on your machine, plus some wiggle room. This should prevent crashing due to memory concerns.\n", " * Using feature selection may also improve memory usage, as described above.\n", " * Remove modules that create high RAM usage (e.g. multiple PolynomialFeatures or one with high degree).\n", "* Why are my TPOT runs not reproducible when random_state is set?\n", " * Check that `periodic_checkpoint_folder` is set correctly. If this is set to a non-empty folder, TPOT will continue training from the checkpoint rather than start a new run from scratch. For TPOT runs to be reproducible, they have to have the same starting points.\n", " * If using custom search spaces, pass in a fixed `random_state` value into the configspace of the scikit-learn modules that utilize them. TPOT does not check whether estimators do or do not take in a random state value (See Tutorial 2).\n", " * If using the pre-built search spaces provided by TPOT, make sure to pass in `random_state` to `tpot.config.get_configspace` or `tpot.config.template_search_spaces.get_template_search_spaces`. This ensures all estimators that support it get a fixed random_state value. (See Tutorial 2).\n", " * If using custom Node and Pipeline types, ensure all random decisions utilize the rng parameter passed into the mutation/crossover functions.\n", " * If `max_eval_time_mins` is set, TPOT will terminate pipelines that exceed this time limit. If the pipeline evaluation happens to be very similar to the time limit, small random fluctuations in CPU allocation may cause a given pipeline to be evaluated in one run but not another. This slightly different result would throw off the random number generator throughout the rest of the run. Setting `max_eval_time_mins` to None or a higher value may prevent this edge case.\n", " * If using `TPOTEstimatorSteadyState` with `n_jobs`>1, it is also possible that random fluctuations in CPU allocation slightly change the order in which pipelines are evaluated, which will affect the downstream results. `TPOTEstimatorSteadyState` is more reliably reproducible when `n_jobs=1` (This is not an issue for the default `TPOTEstimator`, `TPOTClassifier`, `TPOTRegressor` as they used a batched generational approach where execution order does not impact results).\n", "* TPOT is not using all the CPU cores I expected, given my `n_jobs` setting.\n", " * The default TPOT algorithm uses a generational approach. This means the TPOT will need to evaluate `population_size` (default 50) pipelines before starting the next batch. At the end of each generation, TPOT may leave threads unused while it waits for the last few pipelines to finish evaluating. Some estimators or pipelines can be significantly slower to evaluate than others. This can be addressed in a few ways:\n", " * Decrease `max_eval_time_mins` to cut long-running pipeline evaluations early.\n", " * Remove estimators or hyperparameter configurations that are prone to very slow convergence (which is very often `SVC` or `SVR`).\n", " * Alternatively, `TPOTEstimatorSteadyState` uses a slightly different backend for the evolutionary algorithm that does not utilize the generational approach. Instead, new pipelines are generated and evaluated as soon as the previous one finishes. With this estimator, all cores should be utilized at all times. \n", " * Sometimes, setting n_jobs to a multiple of the number of threads can help minimize the chances of threads being idle while waiting for others to finish" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# More Options\n", "\n", "`tpot.TPOTClassifier` and `tpot.TPOTRegressor` have a simplified set of hyperparameters with default values set for classification and regression problems. Currently, both of these use the standard evolutionary algorithm in the `tpot.TPOTEstimator` class. If you want more control, you can look into either the `tpot.TPOTEstimator` or `tpot.TPOTEstimatorSteadyState` class.\n", "\n", "There are two evolutionary algorithms built into TPOT, which corresponds to two different estimator classes.\n", "\n", "1. The `tpot.TPOTEstimator` uses a standard evolutionary algorithm that evaluates exactly population_size individuals each generation. This is similar to the algorithm in TPOT1. The next generation does not start until the previous is completely finished evaluating. This leads to underutilized CPU time as the cores are waiting for the last individuals to finish training, but may preserve diversity in the population. \n", "\n", "2. The `tpot.TPOTEstimatorSteadyState` differs in that it will generate and evaluate the next individual as soon as an individual finishes the evaluation. The number of individuals being evaluated is determined by the n_jobs parameter. There is no longer a concept of generations. The population_size parameter now refers to the size of the list of evaluated parents. When an individual is evaluated, the selection method updates the list of parents. This allows more efficient utilization when using multiple cores.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### tpot.TPOTEstimatorSteadyState" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Evaluations: : 119it [00:37, 3.21it/s]\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/preprocessing/_data.py:2785: UserWarning: n_quantiles (688) is greater than the total number of samples (426). n_quantiles is set to n_samples.\n", " warnings.warn(\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.9816225907664725\n" ] } ], "source": [ "import tpot\n", "import sklearn\n", "import sklearn.datasets\n", "\n", "\n", "graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " leaf_search_space = tpot.config.get_search_space(\"selectors\"), \n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " max_size = 10,\n", ")\n", "\n", "est = tpot.TPOTEstimatorSteadyState( \n", " search_space = graph_search_space,\n", " scorers=['roc_auc_ovr',tpot.objectives.complexity_scorer],\n", " scorers_weights=[1,-1],\n", "\n", "\n", " classification=True,\n", "\n", " max_eval_time_mins=15,\n", " max_time_mins=30,\n", " early_stop=10, #In TPOTEstimatorSteadyState, since there are no generations, early_stop is the number of pipelines to evaluate before stopping.\n", " n_jobs=30,\n", " verbose=2)\n", "\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovo')\n", "X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fitted_pipeline = est.fitted_pipeline_ # access best pipeline directly\n", "fitted_pipeline.plot()" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
roc_auc_scorecomplexity_scorerParentsVariation_FunctionIndividualSubmitted TimestampCompleted TimestampEval ErrorPareto_FrontInstance
00.84195495.0NaNNaN<tpot.search_spaces.pipelines.graph.GraphPipel...1.740179e+091.740179e+09NoneNaN[('DecisionTreeClassifier_1', 'SelectPercentil...
10.96778189.0NaNNaN<tpot.search_spaces.pipelines.graph.GraphPipel...1.740179e+091.740179e+09NoneNaN[('DecisionTreeClassifier_1', 'SelectFwe_1'), ...
20.97241222.0NaNNaN<tpot.search_spaces.pipelines.graph.GraphPipel...1.740179e+091.740179e+09NoneNaN[('KNeighborsClassifier_1', 'ColumnOneHotEncod...
30.97592654.0NaNNaN<tpot.search_spaces.pipelines.graph.GraphPipel...1.740179e+091.740179e+09NoneNaN[('KNeighborsClassifier_1', 'SelectFwe_1')]
40.96435284.0NaNNaN<tpot.search_spaces.pipelines.graph.GraphPipel...1.740179e+091.740179e+09NoneNaN[('DecisionTreeClassifier_1', 'ZeroCount_1'), ...
\n", "
" ], "text/plain": [ " roc_auc_score complexity_scorer Parents Variation_Function \\\n", "0 0.841954 95.0 NaN NaN \n", "1 0.967781 89.0 NaN NaN \n", "2 0.972412 22.0 NaN NaN \n", "3 0.975926 54.0 NaN NaN \n", "4 0.964352 84.0 NaN NaN \n", "\n", " Individual Submitted Timestamp \\\n", "0 #sk-container-id-1 {\n", " /* Definition of color scheme common for light and dark mode */\n", " --sklearn-color-text: black;\n", " --sklearn-color-line: gray;\n", " /* Definition of color scheme for unfitted estimators */\n", " --sklearn-color-unfitted-level-0: #fff5e6;\n", " --sklearn-color-unfitted-level-1: #f6e4d2;\n", " --sklearn-color-unfitted-level-2: #ffe0b3;\n", " --sklearn-color-unfitted-level-3: chocolate;\n", " /* Definition of color scheme for fitted estimators */\n", " --sklearn-color-fitted-level-0: #f0f8ff;\n", " --sklearn-color-fitted-level-1: #d4ebff;\n", " --sklearn-color-fitted-level-2: #b3dbfd;\n", " --sklearn-color-fitted-level-3: cornflowerblue;\n", "\n", " /* Specific color for light theme */\n", " --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n", " --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n", " --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n", " --sklearn-color-icon: #696969;\n", "\n", " @media (prefers-color-scheme: dark) {\n", " /* Redefinition of color scheme for dark theme */\n", " --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n", " --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n", " --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n", " --sklearn-color-icon: #878787;\n", " }\n", "}\n", "\n", "#sk-container-id-1 {\n", " color: var(--sklearn-color-text);\n", "}\n", "\n", "#sk-container-id-1 pre {\n", " padding: 0;\n", "}\n", "\n", "#sk-container-id-1 input.sk-hidden--visually {\n", " border: 0;\n", " clip: rect(1px 1px 1px 1px);\n", " clip: rect(1px, 1px, 1px, 1px);\n", " height: 1px;\n", " margin: -1px;\n", " overflow: hidden;\n", " padding: 0;\n", " position: absolute;\n", " width: 1px;\n", "}\n", "\n", "#sk-container-id-1 div.sk-dashed-wrapped {\n", " border: 1px dashed var(--sklearn-color-line);\n", " margin: 0 0.4em 0.5em 0.4em;\n", " box-sizing: border-box;\n", " padding-bottom: 0.4em;\n", " background-color: var(--sklearn-color-background);\n", "}\n", "\n", "#sk-container-id-1 div.sk-container {\n", " /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n", " but bootstrap.min.css set `[hidden] { display: none !important; }`\n", " so we also need the `!important` here to be able to override the\n", " default hidden behavior on the sphinx rendered scikit-learn.org.\n", " See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n", " display: inline-block !important;\n", " position: relative;\n", "}\n", "\n", "#sk-container-id-1 div.sk-text-repr-fallback {\n", " display: none;\n", "}\n", "\n", "div.sk-parallel-item,\n", "div.sk-serial,\n", "div.sk-item {\n", " /* draw centered vertical line to link estimators */\n", " background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n", " background-size: 2px 100%;\n", " background-repeat: no-repeat;\n", " background-position: center center;\n", "}\n", "\n", "/* Parallel-specific style estimator block */\n", "\n", "#sk-container-id-1 div.sk-parallel-item::after {\n", " content: \"\";\n", " width: 100%;\n", " border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n", " flex-grow: 1;\n", "}\n", "\n", "#sk-container-id-1 div.sk-parallel {\n", " display: flex;\n", " align-items: stretch;\n", " justify-content: center;\n", " background-color: var(--sklearn-color-background);\n", " position: relative;\n", "}\n", "\n", "#sk-container-id-1 div.sk-parallel-item {\n", " display: flex;\n", " flex-direction: column;\n", "}\n", "\n", "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n", " align-self: flex-end;\n", " width: 50%;\n", "}\n", "\n", "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n", " align-self: flex-start;\n", " width: 50%;\n", "}\n", "\n", "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n", " width: 0;\n", "}\n", "\n", "/* Serial-specific style estimator block */\n", "\n", "#sk-container-id-1 div.sk-serial {\n", " display: flex;\n", " flex-direction: column;\n", " align-items: center;\n", " background-color: var(--sklearn-color-background);\n", " padding-right: 1em;\n", " padding-left: 1em;\n", "}\n", "\n", "\n", "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n", "clickable and can be expanded/collapsed.\n", "- Pipeline and ColumnTransformer use this feature and define the default style\n", "- Estimators will overwrite some part of the style using the `sk-estimator` class\n", "*/\n", "\n", "/* Pipeline and ColumnTransformer style (default) */\n", "\n", "#sk-container-id-1 div.sk-toggleable {\n", " /* Default theme specific background. It is overwritten whether we have a\n", " specific estimator or a Pipeline/ColumnTransformer */\n", " background-color: var(--sklearn-color-background);\n", "}\n", "\n", "/* Toggleable label */\n", "#sk-container-id-1 label.sk-toggleable__label {\n", " cursor: pointer;\n", " display: block;\n", " width: 100%;\n", " margin-bottom: 0;\n", " padding: 0.5em;\n", " box-sizing: border-box;\n", " text-align: center;\n", "}\n", "\n", "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n", " /* Arrow on the left of the label */\n", " content: \"▸\";\n", " float: left;\n", " margin-right: 0.25em;\n", " color: var(--sklearn-color-icon);\n", "}\n", "\n", "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n", " color: var(--sklearn-color-text);\n", "}\n", "\n", "/* Toggleable content - dropdown */\n", "\n", "#sk-container-id-1 div.sk-toggleable__content {\n", " max-height: 0;\n", " max-width: 0;\n", " overflow: hidden;\n", " text-align: left;\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-0);\n", "}\n", "\n", "#sk-container-id-1 div.sk-toggleable__content.fitted {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-0);\n", "}\n", "\n", "#sk-container-id-1 div.sk-toggleable__content pre {\n", " margin: 0.2em;\n", " border-radius: 0.25em;\n", " color: var(--sklearn-color-text);\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-0);\n", "}\n", "\n", "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-fitted-level-0);\n", "}\n", "\n", "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n", " /* Expand drop-down */\n", " max-height: 200px;\n", " max-width: 100%;\n", " overflow: auto;\n", "}\n", "\n", "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n", " content: \"▾\";\n", "}\n", "\n", "/* Pipeline/ColumnTransformer-specific style */\n", "\n", "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " color: var(--sklearn-color-text);\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "/* Estimator-specific style */\n", "\n", "/* Colorize estimator box */\n", "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n", "#sk-container-id-1 div.sk-label label {\n", " /* The background is the default theme color */\n", " color: var(--sklearn-color-text-on-default-background);\n", "}\n", "\n", "/* On hover, darken the color of the background */\n", "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n", " color: var(--sklearn-color-text);\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "/* Label box, darken color on hover, fitted */\n", "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n", " color: var(--sklearn-color-text);\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "/* Estimator label */\n", "\n", "#sk-container-id-1 div.sk-label label {\n", " font-family: monospace;\n", " font-weight: bold;\n", " display: inline-block;\n", " line-height: 1.2em;\n", "}\n", "\n", "#sk-container-id-1 div.sk-label-container {\n", " text-align: center;\n", "}\n", "\n", "/* Estimator-specific */\n", "#sk-container-id-1 div.sk-estimator {\n", " font-family: monospace;\n", " border: 1px dotted var(--sklearn-color-border-box);\n", " border-radius: 0.25em;\n", " box-sizing: border-box;\n", " margin-bottom: 0.5em;\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-0);\n", "}\n", "\n", "#sk-container-id-1 div.sk-estimator.fitted {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-0);\n", "}\n", "\n", "/* on hover */\n", "#sk-container-id-1 div.sk-estimator:hover {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-2);\n", "}\n", "\n", "#sk-container-id-1 div.sk-estimator.fitted:hover {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-2);\n", "}\n", "\n", "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n", "\n", "/* Common style for \"i\" and \"?\" */\n", "\n", ".sk-estimator-doc-link,\n", "a:link.sk-estimator-doc-link,\n", "a:visited.sk-estimator-doc-link {\n", " float: right;\n", " font-size: smaller;\n", " line-height: 1em;\n", " font-family: monospace;\n", " background-color: var(--sklearn-color-background);\n", " border-radius: 1em;\n", " height: 1em;\n", " width: 1em;\n", " text-decoration: none !important;\n", " margin-left: 1ex;\n", " /* unfitted */\n", " border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n", " color: var(--sklearn-color-unfitted-level-1);\n", "}\n", "\n", ".sk-estimator-doc-link.fitted,\n", "a:link.sk-estimator-doc-link.fitted,\n", "a:visited.sk-estimator-doc-link.fitted {\n", " /* fitted */\n", " border: var(--sklearn-color-fitted-level-1) 1pt solid;\n", " color: var(--sklearn-color-fitted-level-1);\n", "}\n", "\n", "/* On hover */\n", "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n", ".sk-estimator-doc-link:hover,\n", "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n", ".sk-estimator-doc-link:hover {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-3);\n", " color: var(--sklearn-color-background);\n", " text-decoration: none;\n", "}\n", "\n", "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n", ".sk-estimator-doc-link.fitted:hover,\n", "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n", ".sk-estimator-doc-link.fitted:hover {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-3);\n", " color: var(--sklearn-color-background);\n", " text-decoration: none;\n", "}\n", "\n", "/* Span, style for the box shown on hovering the info icon */\n", ".sk-estimator-doc-link span {\n", " display: none;\n", " z-index: 9999;\n", " position: relative;\n", " font-weight: normal;\n", " right: .2ex;\n", " padding: .5ex;\n", " margin: .5ex;\n", " width: min-content;\n", " min-width: 20ex;\n", " max-width: 50ex;\n", " color: var(--sklearn-color-text);\n", " box-shadow: 2pt 2pt 4pt #999;\n", " /* unfitted */\n", " background: var(--sklearn-color-unfitted-level-0);\n", " border: .5pt solid var(--sklearn-color-unfitted-level-3);\n", "}\n", "\n", ".sk-estimator-doc-link.fitted span {\n", " /* fitted */\n", " background: var(--sklearn-color-fitted-level-0);\n", " border: var(--sklearn-color-fitted-level-3);\n", "}\n", "\n", ".sk-estimator-doc-link:hover span {\n", " display: block;\n", "}\n", "\n", "/* \"?\"-specific style due to the `` HTML tag */\n", "\n", "#sk-container-id-1 a.estimator_doc_link {\n", " float: right;\n", " font-size: 1rem;\n", " line-height: 1em;\n", " font-family: monospace;\n", " background-color: var(--sklearn-color-background);\n", " border-radius: 1rem;\n", " height: 1rem;\n", " width: 1rem;\n", " text-decoration: none;\n", " /* unfitted */\n", " color: var(--sklearn-color-unfitted-level-1);\n", " border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n", "}\n", "\n", "#sk-container-id-1 a.estimator_doc_link.fitted {\n", " /* fitted */\n", " border: var(--sklearn-color-fitted-level-1) 1pt solid;\n", " color: var(--sklearn-color-fitted-level-1);\n", "}\n", "\n", "/* On hover */\n", "#sk-container-id-1 a.estimator_doc_link:hover {\n", " /* unfitted */\n", " background-color: var(--sklearn-color-unfitted-level-3);\n", " color: var(--sklearn-color-background);\n", " text-decoration: none;\n", "}\n", "\n", "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n", " /* fitted */\n", " background-color: var(--sklearn-color-fitted-level-3);\n", "}\n", "" ], "text/plain": [ "RandomForestClassifier(max_features=0.8874647037836, min_samples_leaf=2,\n", " min_samples_split=5, n_estimators=128)" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from ConfigSpace import ConfigurationSpace\n", "from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal\n", "from sklearn.ensemble import RandomForestClassifier\n", "import tpot\n", "import numpy as np\n", "import sklearn\n", "import sklearn.datasets\n", "\n", "rf_configspace = ConfigurationSpace(\n", " space = {\n", " 'n_estimators': 128, #as recommended by Oshiro et al. (2012\n", " 'max_features': Float(\"max_features\", bounds=(0.01,1), log=True), #log scale like autosklearn?\n", " 'criterion': Categorical(\"criterion\", ['gini', 'entropy']),\n", " 'min_samples_split': Integer(\"min_samples_split\", bounds=(2, 20)),\n", " 'min_samples_leaf': Integer(\"min_samples_leaf\", bounds=(1, 20)),\n", " 'bootstrap': Categorical(\"bootstrap\", [True, False]),\n", " #random_state = 1, # If you want results to be reproducible, you can set a fixed random_state.\n", " }\n", ")\n", "\n", "hyperparameters = dict(rf_configspace.sample_configuration())\n", "print(\"sampled hyperparameters\")\n", "print(hyperparameters)\n", "\n", "rf = RandomForestClassifier(**hyperparameters)\n", "rf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More simply:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled hyperparameters\n", "{'bootstrap': False, 'criterion': 'entropy', 'max_features': 0.8418685817308, 'min_samples_leaf': 5, 'min_samples_split': 2, 'n_estimators': 128}\n" ] }, { "data": { "text/html": [ "
RandomForestClassifier(bootstrap=False, criterion='entropy',\n",
       "                       max_features=0.8418685817308, min_samples_leaf=5,\n",
       "                       n_estimators=128)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "RandomForestClassifier(bootstrap=False, criterion='entropy',\n", " max_features=0.8418685817308, min_samples_leaf=5,\n", " n_estimators=128)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rf_configspace = ConfigurationSpace(\n", " space = {\n", " 'n_estimators': 128, #as recommended by Oshiro et al. (2012\n", " 'max_features':(0.01,1), #not log scaled\n", " 'criterion': ['gini', 'entropy'],\n", " 'min_samples_split': (2, 20),\n", " 'min_samples_leaf': (1, 20),\n", " 'bootstrap': [True, False],\n", " #random_state = 1, # If you want results to be reproducible, you can set a fixed random_state.\n", " }\n", ")\n", "\n", "hyperparameters = dict(rf_configspace.sample_configuration())\n", "print(\"sampled hyperparameters\")\n", "print(hyperparameters)\n", "\n", "rf = RandomForestClassifier(**hyperparameters)\n", "rf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# TPOT Search spaces\n", "\n", "TPOT allows you to create hyperparameter search spaces for individual methods and pipeline structure search spaces. For example, TPOT can create linear pipelines, trees, or graphs. \n", "\n", "TPOT search spaces are found in the `search_spaces` module. There are two primary kinds of search spaces, node and pipeline. Node search spaces specify a single sklearn `BaseEstimator` search space. Pipeline search spaces define the possible structures for a group of node search spaces. These take in node search spaces and produce a pipeline using nodes from that search space. Since sklearn Pipelines are also `BaseEstimator`, pipeline search spaces are also technically node search spaces. This means that pipeline search spaces can take in other pipeline search spaces in order to define more complex structures. The primary differentiating factor between node and pipeline search spaces is that pipeline search spaces must take in another search space as input to feed its individual nodes. Therefore, all search spaces eventually end in a node search space at the lowest level. Note that parameters for pipeline search spaces can differ, some take in only a single search space, some take in a list, or some take in multiple defined parameters.\n", "\n", "## node search spaces\n", "\n", "\n", "| Name | Info |\n", "| :--- | :----: |\n", "| EstimatorNode | Takes in a ConfigSpace along with the class of the method. This node will optimize the hyperparameters for a single method. |\n", "| GeneticFeatureSelectorNode | Uses evolution to optimize a set of features, exports a basic sklearn Selector that simply selects the features chosen by the node. |\n", "| FSSNode | FSS stands for FeatureSetSelector. This node takes in a list of user-defined subsets of features and selects a single predefined subset. Note that TPOT will not create new subsets nor will it select multiple subsets per node. If using a linear pipeline, this node should be set as the first step. In linear pipelines it is recommended that you only use a small number of feature sets. I recommend exploring using FSSNodes in pipelines that allow TPOT to select more than one FSSNode at a time. For example, DynamicUnionPipeline and GraphPipeline are both excellent combos for FSSNode. Use FFSNode inside a DynamicUnionPipeline at the start of linear pipeline to explore optimal combinations of subsets in linear pipelines. Set the leaf_search_space of GraphSearchPipeline TPOT can use multiple feature sets in different ways, for example, with different transformers for different sets. |\n", "\n", "\n", "\n", "## pipeline search spaces\n", "\n", "found in tpot2.search_spaces.pipelines\n", "\n", "WrapperPipeline - This search space is for wrapping a sklearn estimator with a method that takes another estimator and hyperparameters as arguments.\n", " For example, this can be used with sklearn.ensemble.BaggingClassifier or sklearn.ensemble.AdaBoostClassifier.\n", "\n", "\n", "| Name | Info |\n", "| :--- | :----: |\n", "| ChoicePipeline | Takes in a list of search spaces. Will select one node from the search space. |\n", "| SequentialPipeline | Takes in a list of search spaces. will produce a pipeline of Sequential length. Each step in the pipeline will correspond to the the search space provided in the same index. |\n", "| DynamicLinearPipeline | Takes in a single search space. Will produce a linear pipeline of variable length. Each step in the pipeline will be pulled from the search space provided. |\n", "| UnionPipeline | Takes in a list of search spaces. The returned pipeline will include one estimator per search space joined in an sklearn FeatureUnion. Useful for having many steps in one layer. |\n", "| DynamicUnionPipeline | Takes in a single search space. It will pull anywhere from 1 to max_estimators number of estimators from the search space and concatenate them in a FeatureUnion. |\n", "| TreePipeline |Generates a pipeline of variable length. Pipeline will have a tree structure similar to TPOT1. |\n", "| GraphSearchPipeline | Generates a directed acyclic graph of variable size. Search spaces for root, leaf, and inner nodes can be defined separately if desired. |\n", "| WrapperPipeline | This search space is for wrapping a sklearn estimator with a method that takes another estimator and hyperparameters as arguments. For example, this can be used with sklearn.ensemble.BaggingClassifier or sklearn.ensemble.AdaBoostClassifier. |\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Node Search Space Examples\n", "\n", "Node search spaces represent the smallest unit of an sklearn pipeline. All node search spaces create and optimize a single node which exports a single estimator object. For example this could be a KNeighborsClassifier or a FeatureSetSelector.\n", "\n", "### EstimatorNode\n", "\n", "The EstimatorNode represents the hyperparameter search space for a scikit-learn estimator. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "import tpot\n", "from ConfigSpace import ConfigurationSpace\n", "from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal\n", "from sklearn.neighbors import KNeighborsClassifier\n", "\n", "knn_configspace = ConfigurationSpace(\n", " space = {\n", "\n", " 'n_neighbors': Integer(\"n_neighbors\", bounds=(1, 10)),\n", " 'weights': Categorical(\"weights\", ['uniform', 'distance']),\n", " 'p': Integer(\"p\", bounds=(1, 3)),\n", " 'metric': Categorical(\"metric\", ['euclidean', 'minkowski']),\n", " 'n_jobs': 1,\n", " }\n", ")\n", "\n", "\n", "knn_node = tpot.search_spaces.nodes.EstimatorNode(\n", " method = KNeighborsClassifier,\n", " space = knn_configspace,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can sample generate an individual with the generate() function. This individual samples from the search space as well as provides mutation and crossover functions to modify the current sample." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "knn_individual = knn_node.generate()\n", "knn_individual" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled hyperparameters\n", "{'metric': 'minkowski', 'n_jobs': 1, 'n_neighbors': 4, 'p': 1, 'weights': 'uniform'}\n" ] } ], "source": [ "print(\"sampled hyperparameters\")\n", "print(knn_individual.hyperparameters)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All Individual objects have mutation and crossover operators that TPOT uses to optimize the pipelines." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "mutated hyperparameters\n", "{'metric': 'minkowski', 'n_jobs': 1, 'n_neighbors': 6, 'p': 2, 'weights': 'distance'}\n" ] } ], "source": [ "knn_individual.mutate() # mutate the individual\n", "print(\"mutated hyperparameters\")\n", "print(knn_individual.hyperparameters)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In TPOT, crossover only modifies the individual calling the crossover function, the second individual remains the same" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "original hyperparameters for individual 1\n", "{'metric': 'euclidean', 'n_jobs': 1, 'n_neighbors': 8, 'p': 3, 'weights': 'uniform'}\n", "original hyperparameters for individual 2\n", "{'metric': 'minkowski', 'n_jobs': 1, 'n_neighbors': 3, 'p': 2, 'weights': 'distance'}\n", "\n", "post crossover hyperparameters for individual 1\n", "{'metric': 'minkowski', 'n_jobs': 1, 'n_neighbors': 3, 'p': 2, 'weights': 'distance'}\n", "post crossover hyperparameters for individual 2\n", "{'metric': 'minkowski', 'n_jobs': 1, 'n_neighbors': 3, 'p': 2, 'weights': 'distance'}\n" ] } ], "source": [ "knn_individual1 = knn_node.generate()\n", "knn_individual2 = knn_node.generate()\n", "\n", "print(\"original hyperparameters for individual 1\")\n", "print(knn_individual1.hyperparameters)\n", "\n", "print(\"original hyperparameters for individual 2\")\n", "print(knn_individual2.hyperparameters)\n", "\n", "print()\n", "\n", "knn_individual1.crossover(knn_individual2) # crossover the individuals\n", "print(\"post crossover hyperparameters for individual 1\")\n", "print(knn_individual1.hyperparameters)\n", "print(\"post crossover hyperparameters for individual 2\")\n", "print(knn_individual2.hyperparameters)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All search spaces have an export_pipeline function that returns an sklearn `BaseEstimator`" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
KNeighborsClassifier(n_jobs=1, n_neighbors=3, weights='distance')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "KNeighborsClassifier(n_jobs=1, n_neighbors=3, weights='distance')" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est = knn_individual1.export_pipeline()\n", "est" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If a dictionary of parameters is passed instead of of a ConfigSpace object, then the hyperparameters will always be fixed and not learned." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
KNeighborsClassifier(n_neighbors=10)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "KNeighborsClassifier(n_neighbors=10)" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "from ConfigSpace import ConfigurationSpace\n", "from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal\n", "from sklearn.neighbors import KNeighborsClassifier\n", "\n", "space = {\n", "\n", " 'n_neighbors':10,\n", "}\n", "\n", "knn_node = tpot.search_spaces.nodes.EstimatorNode(\n", " method = KNeighborsClassifier,\n", " space = space,\n", ")\n", "\n", "knn_node.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### FSSNode and GeneticFeatureSelectorNode\n", "\n", "Both of these are given their own tutorials. See Tutorial 3 for FFSNode and Tutorial 5 for GeneticFeatureSelectorNode" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pipeline Search Space Examples\n", "\n", "Pipeline search spaces are used to define the structure and restrictions of the pipelines TPOT can search. Unlike Node search spaces, all pipeline search spaces take in other search spaces as inputs. Rather than sample hyperparameters, pipeline search spaces can select models from the input search spaces and organize them within a linear sklearn Pipeline or a TPOT GraphPipeline." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ChoicePipeline\n", "\n", "The simplest pipeline search space is the ChoicePipeline. This takes in a list of search spaces and simply selects and samples from one. In this example, we will construct a search space that takes in several options for a classifier. The resulting search space will then first select a model from KNeighborsClassifier, LogisticRegression or DecisionTreeClassifier, and then select the hyperparameters for the given model." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "from ConfigSpace import ConfigurationSpace\n", "from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal\n", "from sklearn.neighbors import KNeighborsClassifier\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.tree import DecisionTreeClassifier\n", "\n", "knn_configspace = ConfigurationSpace(\n", " space = {\n", "\n", " 'n_neighbors': Integer(\"n_neighbors\", bounds=(1, 10)),\n", " 'weights': Categorical(\"weights\", ['uniform', 'distance']),\n", " 'p': Integer(\"p\", bounds=(1, 3)),\n", " 'metric': Categorical(\"metric\", ['euclidean', 'minkowski']),\n", " 'n_jobs': 1,\n", " }\n", ")\n", "\n", "lr_configspace = ConfigurationSpace(\n", " space = {\n", " 'solver': Categorical(\"solver\", ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga']),\n", " 'penalty': Categorical(\"penalty\", ['l1', 'l2']),\n", " 'dual': Categorical(\"dual\", [True, False]),\n", " 'C': Float(\"C\", bounds=(1e-4, 1e4), log=True),\n", " 'class_weight': Categorical(\"class_weight\", ['balanced']),\n", " 'n_jobs': 1,\n", " 'max_iter': 1000,\n", " }\n", " )\n", "\n", "dt_configspace = ConfigurationSpace(\n", " space = {\n", " 'criterion': Categorical(\"criterion\", ['gini', 'entropy']),\n", " 'max_depth': Integer(\"max_depth\", bounds=(1, 11)),\n", " 'min_samples_split': Integer(\"min_samples_split\", bounds=(2, 21)),\n", " 'min_samples_leaf': Integer(\"min_samples_leaf\", bounds=(1, 21)),\n", " 'max_features': Categorical(\"max_features\", ['sqrt', 'log2']),\n", " 'min_weight_fraction_leaf': 0.0,\n", " }\n", " )\n", "\n", "knn_node = tpot.search_spaces.nodes.EstimatorNode(\n", " method = KNeighborsClassifier,\n", " space = knn_configspace,\n", ")\n", "\n", "lr_node = tpot.search_spaces.nodes.EstimatorNode(\n", " method = LogisticRegression,\n", " space = lr_configspace,\n", ")\n", "\n", "dt_node = tpot.search_spaces.nodes.EstimatorNode(\n", " method = DecisionTreeClassifier,\n", " space = dt_configspace,\n", ")\n", "\n", "classifier_node = tpot.search_spaces.pipelines.ChoicePipeline(\n", " search_spaces=[\n", " knn_node,\n", " lr_node,\n", " dt_node,\n", " ]\n", ")\n", "\n", "\n", "tpot.search_spaces.pipelines.ChoicePipeline(\n", " search_spaces = [\n", " tpot.search_spaces.nodes.EstimatorNode(\n", " method = KNeighborsClassifier,\n", " space = knn_configspace,\n", " ),\n", " tpot.search_spaces.nodes.EstimatorNode(\n", " method = LogisticRegression,\n", " space = lr_configspace,\n", " ),\n", " tpot.search_spaces.nodes.EstimatorNode(\n", " method = DecisionTreeClassifier,\n", " space = dt_configspace,\n", " ),\n", " ]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Search space objects provided by pipeline search spaces work the same as with node search spaces. Note that crossover only works when both individuals have sampled the same method. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
KNeighborsClassifier(n_jobs=1, n_neighbors=3)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "KNeighborsClassifier(n_jobs=1, n_neighbors=3)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "classifier_individual = classifier_node.generate()\n", "\n", "print(\"sampled pipeline\")\n", "classifier_individual.export_pipeline()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "mutated pipeline\n" ] }, { "data": { "text/html": [ "
KNeighborsClassifier(metric='euclidean', n_jobs=1, n_neighbors=9)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "KNeighborsClassifier(metric='euclidean', n_jobs=1, n_neighbors=9)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"mutated pipeline\")\n", "classifier_individual.mutate()\n", "classifier_individual.export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Built in search spaces for EstimatorNode and ChoicePipeline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "TPOT also comes with predefined hyperparameter search spaces. The current search spaces were adapted from a combination of the original TPOT package as well as the search spaces used in [AutoSklearn](https://github.com/automl/auto-sklearn/tree/development/autosklearn/pipeline/components). The helper function `tpot.config.get_search_space` takes in a string or a list of strings, and returns either a EstimatorNode or a ChoicePipeline (including all methods in the list), respectively. \n", "\n", "| String | Corresponding Method |\n", "| --- | ----- |\n", "| SGDClassifier | |\n", "| RandomForestClassifier | |\n", "| ExtraTreesClassifier | |\n", "| GradientBoostingClassifier | |\n", "| MLPClassifier | |\n", "| DecisionTreeClassifier | |\n", "| XGBClassifier | |\n", "| KNeighborsClassifier | |\n", "| SVC | |\n", "| LogisticRegression | |\n", "| LGBMClassifier | |\n", "| LinearSVC | |\n", "| GaussianNB | |\n", "| BernoulliNB | |\n", "| MultinomialNB | |\n", "| ExtraTreesRegressor | |\n", "| RandomForestRegressor | |\n", "| GradientBoostingRegressor | |\n", "| BaggingRegressor | |\n", "| DecisionTreeRegressor | |\n", "| KNeighborsRegressor | |\n", "| XGBRegressor | |\n", "| ZeroCount | |\n", "| ColumnOneHotEncoder | |\n", "| Binarizer | |\n", "| FastICA | |\n", "| FeatureAgglomeration | |\n", "| MaxAbsScaler | |\n", "| MinMaxScaler | |\n", "| Normalizer | |\n", "| Nystroem | |\n", "| PCA | |\n", "| PolynomialFeatures | |\n", "| RBFSampler | |\n", "| RobustScaler | |\n", "| StandardScaler | |\n", "| SelectFwe | |\n", "| SelectPercentile | |\n", "| VarianceThreshold | |\n", "| SGDRegressor | |\n", "| Ridge | |\n", "| Lasso | |\n", "| ElasticNet | |\n", "| Lars | |\n", "| LassoLars | |\n", "| LassoLarsCV | |\n", "| RidgeCV | |\n", "| SVR | |\n", "| LinearSVR | |\n", "| AdaBoostRegressor | |\n", "| ElasticNetCV | |\n", "| AdaBoostClassifier | |\n", "| MLPRegressor | |\n", "| GaussianProcessRegressor | |\n", "| HistGradientBoostingClassifier | |\n", "| HistGradientBoostingRegressor | |\n", "| AddTransformer | |\n", "| mul_neg_1_Transformer | |\n", "| MulTransformer | |\n", "| SafeReciprocalTransformer | |\n", "| EQTransformer | |\n", "| NETransformer | |\n", "| GETransformer | |\n", "| GTTransformer | |\n", "| LETransformer | |\n", "| LTTransformer | |\n", "| MinTransformer | |\n", "| MaxTransformer | |\n", "| ZeroTransformer | |\n", "| OneTransformer | |\n", "| NTransformer | |\n", "| PowerTransformer | |\n", "| QuantileTransformer | |\n", "| ARDRegression | |\n", "| QuadraticDiscriminantAnalysis | |\n", "| PassiveAggressiveClassifier | |\n", "| LinearDiscriminantAnalysis | |\n", "| DominantEncoder | |\n", "| RecessiveEncoder | |\n", "| HeterosisEncoder | |\n", "| UnderDominanceEncoder | |\n", "| OverDominanceEncoder | |\n", "| GaussianProcessClassifier | |\n", "| BaggingClassifier | |\n", "| LGBMRegressor | |\n", "| Passthrough | |\n", "| SkipTransformer | |\n", "| PassKBinsDiscretizer | |\n", "| SimpleImputer | |\n", "| IterativeImputer | |\n", "| KNNImputer | |\n", "| MDR | |\n", "| ContinuousMDR | |\n", "| ReliefF | |\n", "| SURF | |\n", "| SURFstar | |\n", "| MultiSURF | |\n", "| LinearRegression_sklearnex | |\n", "| Ridge_sklearnex | |\n", "| Lasso_sklearnex | |\n", "| ElasticNet_sklearnex | |\n", "| SVR_sklearnex | |\n", "| NuSVR_sklearnex | |\n", "| RandomForestRegressor_sklearnex | |\n", "| KNeighborsRegressor_sklearnex | |\n", "| RandomForestClassifier_sklearnex | |\n", "| KNeighborsClassifier_sklearnex | |\n", "| SVC_sklearnex | |\n", "| NuSVC_sklearnex | |\n", "| LogisticRegression_sklearnex | |\n", "\n", "Some methods require a wrapped estimator. To account for both regression and classification, these have been grouped separately with their own special strings.\n", "\n", "| Wrapper Special String | Notes |\n", "| :--- | :----: |\n", "| RFE_classification | FRE with learned ExtraTreesClassifier |\n", "| RFE_regression | RFE with learned ExtraTreesRegressor |\n", "| SelectFromModel_classification | SelectFromModel with learned ExtraTreesClassifier |\n", "| SelectFromModel_regression | SelectFromModel with learned ExtraTreesRegressor |\n", "| IterativeImputer_learned_estimators | IterativeImputer with learned ExtraTreesRegressor |\n", "\n", "\n", "There are also special strings that include a predefined lists of methods. These will return a ChoicePipeline of the included methods.\n", "\n", "| List Special String | Included methods |\n", "| :--- | :----: |\n", "| \"selectors\" | [\"SelectFwe\", \"SelectPercentile\", \"VarianceThreshold\",] |\n", "| \"selectors_classification\" | [\"SelectFwe\", \"SelectPercentile\", \"VarianceThreshold\", \"RFE_classification\", \"SelectFromModel_classification\"] |\n", "| \"selectors_regression\" | [\"SelectFwe\", \"SelectPercentile\", \"VarianceThreshold\", \"RFE_regression\", \"SelectFromModel_regression\"] |\n", "| \"classifiers\" | [\"LGBMClassifier\", \"BaggingClassifier\", 'AdaBoostClassifier', 'BernoulliNB', 'DecisionTreeClassifier', 'ExtraTreesClassifier', 'GaussianNB', 'HistGradientBoostingClassifier', 'KNeighborsClassifier','LinearDiscriminantAnalysis', 'LogisticRegression', \"LinearSVC\", \"SVC\", 'MLPClassifier', 'MultinomialNB', \"QuadraticDiscriminantAnalysis\", 'RandomForestClassifier', 'SGDClassifier', 'XGBClassifier'] |\n", "| \"regressors\" | [\"LGBMRegressor\", 'AdaBoostRegressor', \"ARDRegression\", 'DecisionTreeRegressor', 'ExtraTreesRegressor', 'HistGradientBoostingRegressor', 'KNeighborsRegressor', 'LinearSVR', \"MLPRegressor\", 'RandomForestRegressor', 'SGDRegressor', 'SVR', 'XGBRegressor'] |\n", "| \"transformers\" | [\"PassKBinsDiscretizer\", \"Binarizer\", \"PCA\", \"ZeroCount\", \"ColumnOneHotEncoder\", \"FastICA\", \"FeatureAgglomeration\", \"Nystroem\", \"RBFSampler\", \"QuantileTransformer\", \"PowerTransformer\"] |\n", "| \"scalers\" | [\"MinMaxScaler\", \"RobustScaler\", \"StandardScaler\", \"MaxAbsScaler\", \"Normalizer\", ] |\n", "| \"all_transformers\" | [\"transformers\", \"scalers\"] |\n", "| \"arithmatic\" | [\"AddTransformer\", \"mul_neg_1_Transformer\", \"MulTransformer\", \"SafeReciprocalTransformer\", \"EQTransformer\", \"NETransformer\", \"GETransformer\", \"GTTransformer\", \"LETransformer\", \"LTTransformer\", \"MinTransformer\", \"MaxTransformer\"] |\n", "| \"imputers\" | [\"SimpleImputer\", \"IterativeImputer\", \"KNNImputer\"] |\n", "| \"skrebate\" | [\"ReliefF\", \"SURF\", \"SURFstar\", \"MultiSURF\"] |\n", "| \"genetic_encoders\" | [\"DominantEncoder\", \"RecessiveEncoder\", \"HeterosisEncoder\", \"UnderDominanceEncoder\", \"OverDominanceEncoder\"] |\n", "| \"classifiers_sklearnex\" | [\"RandomForestClassifier_sklearnex\", \"LogisticRegression_sklearnex\", \"KNeighborsClassifier_sklearnex\", \"SVC_sklearnex\",\"NuSVC_sklearnex\"] |\n", "| \"regressors_sklearnex\" | [\"LinearRegression_sklearnex\", \"Ridge_sklearnex\", \"Lasso_sklearnex\", \"ElasticNet_sklearnex\", \"SVR_sklearnex\", \"NuSVR_sklearnex\", \"RandomForestRegressor_sklearnex\", \"KNeighborsRegressor_sklearnex\"] |\n", "| \"genetic encoders\" | [\"DominantEncoder\", \"RecessiveEncoder\", \"HeterosisEncoder\", \"UnderDominanceEncoder\", \"OverDominanceEncoder\"] |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here are some examples of how to get search spaces using the `get_search_space` function. " ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline 1\n" ] }, { "data": { "text/html": [ "
KNeighborsClassifier(n_jobs=1, n_neighbors=15, p=1, weights='distance')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "KNeighborsClassifier(n_jobs=1, n_neighbors=15, p=1, weights='distance')" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#same pipeline search space as before.\n", "classifier_choice = tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"])\n", "\n", "print(\"sampled pipeline 1\")\n", "classifier_choice.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline 2\n" ] }, { "data": { "text/html": [ "
LogisticRegression(C=5.9018435257131, max_iter=1000, n_jobs=1, solver='saga')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "LogisticRegression(C=5.9018435257131, max_iter=1000, n_jobs=1, solver='saga')" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"sampled pipeline 2\")\n", "classifier_choice.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline 1\n" ] }, { "data": { "text/html": [ "
SGDClassifier(alpha=0.0007786971309, class_weight='balanced',\n",
       "              eta0=0.0209976430718, l1_ratio=0.8571538017043,\n",
       "              learning_rate='constant', loss='modified_huber', n_jobs=1,\n",
       "              penalty='elasticnet')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "SGDClassifier(alpha=0.0007786971309, class_weight='balanced',\n", " eta0=0.0209976430718, l1_ratio=0.8571538017043,\n", " learning_rate='constant', loss='modified_huber', n_jobs=1,\n", " penalty='elasticnet')" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#search space for all classifiers\n", "classifier_choice = tpot.config.get_search_space(\"classifiers\")\n", "\n", "print(\"sampled pipeline 1\")\n", "classifier_choice.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline 2\n" ] }, { "data": { "text/html": [ "
BernoulliNB(alpha=0.0667141454883, fit_prior=False)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "BernoulliNB(alpha=0.0667141454883, fit_prior=False)" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"sampled pipeline 2\")\n", "classifier_choice.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### A note on reproducibility \n", "Many sklearn estimators, like RandomForestClassifier, are stochastic and require a random_state parameter in order to have deterministic results. If you want TPOT runs to be reproducible, it is important that the estimators used by TPOT have a random state set. TPOT will not automatically set this value. This can either be set manually in each search space, or by passing in the random state to the `get_search_space` function. For example: " ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
RandomForestClassifier(bootstrap=False, max_features=0.0234127070363,\n",
       "                       min_samples_leaf=3, min_samples_split=8,\n",
       "                       n_estimators=128, n_jobs=1, random_state=1)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "RandomForestClassifier(bootstrap=False, max_features=0.0234127070363,\n", " min_samples_leaf=3, min_samples_split=8,\n", " n_estimators=128, n_jobs=1, random_state=1)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "reproducible_random_forest = tpot.config.get_search_space(\"RandomForestClassifier\", random_state=1)\n", "reproducible_random_forest.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SequentialPipeline\n", "\n", "SequentialPipelines are of fixed length and sample from a predefined distribution for each step. " ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('variancethreshold',\n",
       "                 VarianceThreshold(threshold=0.00023551581)),\n",
       "                ('pca', PCA(n_components=0.9764631370244)),\n",
       "                ('logisticregression',\n",
       "                 LogisticRegression(C=1.9396611393109, max_iter=1000, n_jobs=1,\n",
       "                                    penalty='l1', solver='saga'))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('variancethreshold',\n", " VarianceThreshold(threshold=0.00023551581)),\n", " ('pca', PCA(n_components=0.9764631370244)),\n", " ('logisticregression',\n", " LogisticRegression(C=1.9396611393109, max_iter=1000, n_jobs=1,\n", " penalty='l1', solver='saga'))])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selector_choicepipeline = tpot.config.get_search_space(\"VarianceThreshold\")\n", "transformer_choicepipeline = tpot.config.get_search_space(\"PCA\")\n", "classifier_choicepipeline = tpot.config.get_search_space(\"LogisticRegression\")\n", "\n", "stc_pipeline = tpot.search_spaces.pipelines.SequentialPipeline([\n", " selector_choicepipeline,\n", " transformer_choicepipeline,\n", " classifier_choicepipeline,\n", "])\n", "\n", "print(\"sampled pipeline\")\n", "stc_pipeline.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is an example of the form Selector-Transformer-Classifier.\n", "\n", "Note that each step in the sequence is a ChoicePipeline this time. Here, the SequentialPipeline can sample from search provided search space in order." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('variancethreshold',\n",
       "                 VarianceThreshold(threshold=0.0004317798946)),\n",
       "                ('kbinsdiscretizer',\n",
       "                 KBinsDiscretizer(encode='onehot-dense', n_bins=77)),\n",
       "                ('lgbmclassifier',\n",
       "                 LGBMClassifier(boosting_type='dart', max_depth=5,\n",
       "                                n_estimators=76, n_jobs=1, num_leaves=192,\n",
       "                                verbose=-1))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('variancethreshold',\n", " VarianceThreshold(threshold=0.0004317798946)),\n", " ('kbinsdiscretizer',\n", " KBinsDiscretizer(encode='onehot-dense', n_bins=77)),\n", " ('lgbmclassifier',\n", " LGBMClassifier(boosting_type='dart', max_depth=5,\n", " n_estimators=76, n_jobs=1, num_leaves=192,\n", " verbose=-1))])" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selector_choicepipeline = tpot.config.get_search_space(\"selectors\")\n", "transformer_choicepipeline = tpot.config.get_search_space(\"transformers\")\n", "classifier_choicepipeline = tpot.config.get_search_space(\"classifiers\")\n", "\n", "stc_pipeline = tpot.search_spaces.pipelines.SequentialPipeline([\n", " selector_choicepipeline,\n", " transformer_choicepipeline,\n", " classifier_choicepipeline,\n", "])\n", "\n", "print(\"sampled pipeline\")\n", "stc_pipeline.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('selectpercentile',\n",
       "                 SelectPercentile(percentile=4.5788544361168)),\n",
       "                ('columnonehotencoder', ColumnOneHotEncoder()),\n",
       "                ('decisiontreeclassifier',\n",
       "                 DecisionTreeClassifier(criterion='entropy', max_depth=10,\n",
       "                                        min_samples_split=13))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('selectpercentile',\n", " SelectPercentile(percentile=4.5788544361168)),\n", " ('columnonehotencoder', ColumnOneHotEncoder()),\n", " ('decisiontreeclassifier',\n", " DecisionTreeClassifier(criterion='entropy', max_depth=10,\n", " min_samples_split=13))])" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"sampled pipeline\")\n", "stc_pipeline.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## DynamicLinearPipeline\n", "\n", "DynamicLinearPipeline takes in a single search space and randomly samples and places estimators in a list without a predefined sequence. DynamicLinearPipeline are most often used when paired with LinearPipeline. A common strategy is to use DynamicLinearPipeline to optimize a series of preprocessing or feature engineering steps, followed by a final classifier or regressor." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('pca-1', PCA(n_components=0.6376571946485)),\n",
       "                ('pca-2', PCA(n_components=0.7836827180307)),\n",
       "                ('quantiletransformer',\n",
       "                 QuantileTransformer(n_quantiles=334,\n",
       "                                     output_distribution='normal'))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('pca-1', PCA(n_components=0.6376571946485)),\n", " ('pca-2', PCA(n_components=0.7836827180307)),\n", " ('quantiletransformer',\n", " QuantileTransformer(n_quantiles=334,\n", " output_distribution='normal'))])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot.config\n", "\n", "\n", "linear_feature_engineering = tpot.search_spaces.pipelines.DynamicLinearPipeline(search_space = tpot.config.get_search_space([\"all_transformers\",\"selectors_classification\"]), max_length=10)\n", "print(\"sampled pipeline\")\n", "linear_feature_engineering.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('selectfwe', SelectFwe(alpha=0.0004164619371)),\n",
       "                ('binarizer', Binarizer(threshold=0.2392693027442)),\n",
       "                ('rbfsampler',\n",
       "                 RBFSampler(gamma=0.3669672326084, n_components=35))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('selectfwe', SelectFwe(alpha=0.0004164619371)),\n", " ('binarizer', Binarizer(threshold=0.2392693027442)),\n", " ('rbfsampler',\n", " RBFSampler(gamma=0.3669672326084, n_components=35))])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"sampled pipeline\")\n", "linear_feature_engineering.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('pipeline',\n",
       "                 Pipeline(steps=[('binarizer',\n",
       "                                  Binarizer(threshold=0.2150677779496)),\n",
       "                                 ('maxabsscaler', MaxAbsScaler()),\n",
       "                                 ('columnonehotencoder',\n",
       "                                  ColumnOneHotEncoder())])),\n",
       "                ('gaussiannb', GaussianNB())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('pipeline',\n", " Pipeline(steps=[('binarizer',\n", " Binarizer(threshold=0.2150677779496)),\n", " ('maxabsscaler', MaxAbsScaler()),\n", " ('columnonehotencoder',\n", " ColumnOneHotEncoder())])),\n", " ('gaussiannb', GaussianNB())])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "full_search_space = tpot.search_spaces.pipelines.SequentialPipeline([\n", " linear_feature_engineering,\n", " tpot.config.get_search_space(\"classifiers\"),\n", "])\n", "\n", "print(\"sampled pipeline\")\n", "full_search_space.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sampled pipeline\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('pipeline',\n",
       "                 Pipeline(steps=[('zerocount', ZeroCount()),\n",
       "                                 ('selectfrommodel',\n",
       "                                  SelectFromModel(estimator=ExtraTreesClassifier(class_weight='balanced',\n",
       "                                                                                 max_features=0.1619832293406,\n",
       "                                                                                 min_samples_leaf=7,\n",
       "                                                                                 min_samples_split=7,\n",
       "                                                                                 n_jobs=1),\n",
       "                                                  threshold=0.6414209870839)),\n",
       "                                 ('variancethreshold',\n",
       "                                  VarianceThreshold(threshold=0.0113542845765))])),\n",
       "                ('multinomialnb', MultinomialNB(alpha=0.0815128367119))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('pipeline',\n", " Pipeline(steps=[('zerocount', ZeroCount()),\n", " ('selectfrommodel',\n", " SelectFromModel(estimator=ExtraTreesClassifier(class_weight='balanced',\n", " max_features=0.1619832293406,\n", " min_samples_leaf=7,\n", " min_samples_split=7,\n", " n_jobs=1),\n", " threshold=0.6414209870839)),\n", " ('variancethreshold',\n", " VarianceThreshold(threshold=0.0113542845765))])),\n", " ('multinomialnb', MultinomialNB(alpha=0.0815128367119))])" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"sampled pipeline\")\n", "full_search_space.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### UnionPipeline\n", "\n", "Union pipelines can be useful when you want to either do multiple transformations in a single layer. Another common strategy is to do a union with a transformer and a passthrough for when you want to keep the original data in addition to the transformation. " ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('pca', PCA(n_components=0.7674007136568)),\n",
       "                               ('passthrough', Passthrough())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('pca', PCA(n_components=0.7674007136568)),\n", " ('passthrough', Passthrough())])" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "transform_and_passthrough = tpot.search_spaces.pipelines.UnionPipeline([\n", " tpot.config.get_search_space(\"transformers\"),\n", " tpot.config.get_search_space(\"Passthrough\"),\n", "])\n", "\n", "transform_and_passthrough.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "UnionPipelines are an excellent tool to expand the capabilities of the linear search spaces." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('selectpercentile',\n",
       "                 SelectPercentile(percentile=29.1049436421441)),\n",
       "                ('featureunion',\n",
       "                 FeatureUnion(transformer_list=[('powertransformer',\n",
       "                                                 PowerTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('extratreesclassifier',\n",
       "                 ExtraTreesClassifier(max_features=0.8376611419015,\n",
       "                                      min_samples_leaf=9, min_samples_split=17,\n",
       "                                      n_jobs=1))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('selectpercentile',\n", " SelectPercentile(percentile=29.1049436421441)),\n", " ('featureunion',\n", " FeatureUnion(transformer_list=[('powertransformer',\n", " PowerTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('extratreesclassifier',\n", " ExtraTreesClassifier(max_features=0.8376611419015,\n", " min_samples_leaf=9, min_samples_split=17,\n", " n_jobs=1))])" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stc_pipeline2 = tpot.search_spaces.pipelines.SequentialPipeline([\n", " tpot.config.get_search_space(\"selectors\"),\n", " transform_and_passthrough,\n", " tpot.config.get_search_space(\"classifiers\"),\n", "])\n", "\n", "stc_pipeline2.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Union pipelines can also be used to create \"branches\" if you are trying to create a tree-like search space. This can be particularly useful when paired with the FeatureSetSelector node (FSSNode) as each branch can learn different feature engineering for different subsets of the features, for example." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featureunion',\n",
       "                 FeatureUnion(transformer_list=[('pipeline-1',\n",
       "                                                 Pipeline(steps=[('selectfwe',\n",
       "                                                                  SelectFwe(alpha=0.0080564930162)),\n",
       "                                                                 ('quantiletransformer',\n",
       "                                                                  QuantileTransformer(n_quantiles=450,\n",
       "                                                                                      output_distribution='normal'))])),\n",
       "                                                ('pipeline-2',\n",
       "                                                 Pipeline(steps=[('variancethreshold',\n",
       "                                                                  VarianceThreshold(threshold=0.155443085484)),\n",
       "                                                                 ('columnonehotencoder...\n",
       "                               feature_types=None, gamma=14.5866790094856,\n",
       "                               grow_policy=None, importance_type=None,\n",
       "                               interaction_constraints=None,\n",
       "                               learning_rate=0.2226908938347, max_bin=None,\n",
       "                               max_cat_threshold=None, max_cat_to_onehot=None,\n",
       "                               max_delta_step=None, max_depth=11,\n",
       "                               max_leaves=None, min_child_weight=3, missing=nan,\n",
       "                               monotone_constraints=None, multi_strategy=None,\n",
       "                               n_estimators=100, n_jobs=1, nthread=1,\n",
       "                               num_parallel_tree=None, ...))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featureunion',\n", " FeatureUnion(transformer_list=[('pipeline-1',\n", " Pipeline(steps=[('selectfwe',\n", " SelectFwe(alpha=0.0080564930162)),\n", " ('quantiletransformer',\n", " QuantileTransformer(n_quantiles=450,\n", " output_distribution='normal'))])),\n", " ('pipeline-2',\n", " Pipeline(steps=[('variancethreshold',\n", " VarianceThreshold(threshold=0.155443085484)),\n", " ('columnonehotencoder...\n", " feature_types=None, gamma=14.5866790094856,\n", " grow_policy=None, importance_type=None,\n", " interaction_constraints=None,\n", " learning_rate=0.2226908938347, max_bin=None,\n", " max_cat_threshold=None, max_cat_to_onehot=None,\n", " max_delta_step=None, max_depth=11,\n", " max_leaves=None, min_child_weight=3, missing=nan,\n", " monotone_constraints=None, multi_strategy=None,\n", " n_estimators=100, n_jobs=1, nthread=1,\n", " num_parallel_tree=None, ...))])" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "st_pipeline = tpot.search_spaces.pipelines.SequentialPipeline([\n", " tpot.config.get_search_space(\"selectors\"),\n", " tpot.config.get_search_space(\"transformers\"),\n", "])\n", "\n", "branched_pipeline = tpot.search_spaces.pipelines.SequentialPipeline([\n", " tpot.search_spaces.pipelines.UnionPipeline([\n", " st_pipeline,\n", " st_pipeline,\n", " ]),\n", " tpot.config.get_search_space(\"classifiers\"),\n", "])\n", "\n", "branched_pipeline.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### DynamicUnionPipeline\n", "\n", "DynamicUnionPipeline works similarly as UnionPipeline. Whereas UnionPipeline is fixed length, with each index corresponding to the search space provided as a list, DynamicUnionPipeline takes in a single search space and will sample 1 or more estimators/pipelines and concatenate them with a FeatureUnion. \n", "\n", "Note that DynamicUnionPipeline will check for pipeline uniqueness, so it will never concatenate two completely identical pipelines. In other words, all steps within the feature union will be unique.\n", "\n", "This can be useful when you want multiple transformers (or in some cases, pipelines), but are not sure how many or which ones." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('fastica', FastICA(n_components=4))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('fastica', FastICA(n_components=4))])" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dynamic_transformers = tpot.search_spaces.pipelines.DynamicUnionPipeline(tpot.config.get_search_space(\"transformers\"), max_estimators=4)\n", "dynamic_transformers.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One good strategy could be to pair this with Passthrough in a feature union so that you output all the transformations along with the original data." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('featureunion',\n",
       "                                FeatureUnion(transformer_list=[('pca',\n",
       "                                                                PCA(n_components=0.9386236966835)),\n",
       "                                                               ('zerocount',\n",
       "                                                                ZeroCount()),\n",
       "                                                               ('featureagglomeration',\n",
       "                                                                FeatureAgglomeration(n_clusters=94,\n",
       "                                                                                     pooling_func=<function max at 0x1048f3470>))])),\n",
       "                               ('passthrough', Passthrough())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('pca',\n", " PCA(n_components=0.9386236966835)),\n", " ('zerocount',\n", " ZeroCount()),\n", " ('featureagglomeration',\n", " FeatureAgglomeration(n_clusters=94,\n", " pooling_func=))])),\n", " ('passthrough', Passthrough())])" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dynamic_transformers_with_passthrough = tpot.search_spaces.pipelines.UnionPipeline([\n", " dynamic_transformers,\n", " tpot.config.get_search_space(\"Passthrough\")],\n", " )\n", "\n", "dynamic_transformers_with_passthrough.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('variancethreshold',\n",
       "                 VarianceThreshold(threshold=0.0003352949622)),\n",
       "                ('featureunion',\n",
       "                 FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                 FeatureUnion(transformer_list=[('featureagglomeration',\n",
       "                                                                                 FeatureAgglomeration(linkage='complete',\n",
       "                                                                                                      metric='cosine',\n",
       "                                                                                                      n_clusters=25)),\n",
       "                                                                                ('columnordinalencoder',\n",
       "                                                                                 ColumnOrdinalEncoder())])),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('mlpclassifier',\n",
       "                 MLPClassifier(activation='identity', alpha=0.000256185492,\n",
       "                               early_stopping=True,\n",
       "                               hidden_layer_sizes=[146, 146, 146],\n",
       "                               learning_rate='invscaling',\n",
       "                               learning_rate_init=0.0006442167601,\n",
       "                               n_iter_no_change=32))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('variancethreshold',\n", " VarianceThreshold(threshold=0.0003352949622)),\n", " ('featureunion',\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('featureagglomeration',\n", " FeatureAgglomeration(linkage='complete',\n", " metric='cosine',\n", " n_clusters=25)),\n", " ('columnordinalencoder',\n", " ColumnOrdinalEncoder())])),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('mlpclassifier',\n", " MLPClassifier(activation='identity', alpha=0.000256185492,\n", " early_stopping=True,\n", " hidden_layer_sizes=[146, 146, 146],\n", " learning_rate='invscaling',\n", " learning_rate_init=0.0006442167601,\n", " n_iter_no_change=32))])" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stc_pipeline3 = tpot.search_spaces.pipelines.SequentialPipeline([\n", " tpot.config.get_search_space(\"selectors\"),\n", " dynamic_transformers_with_passthrough,\n", " tpot.config.get_search_space(\"classifiers\"),\n", "])\n", "\n", "stc_pipeline3.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### WrapperPipeline\n", "\n", "Some sklearn estimators take in other sklearn estimators as a parameter. The wrapper pipeline is used to tune both the original estimators hyperparameters simultaneously with the inner estimators hyperparameters. In fact, the inner estimator in WrapperPipeline can be any search space defined with any of the methods described in this Tutorial.\n", "\n", "The `get_search_space` will automatically create an inner search space for sklearn estimators that do use require an inner estimator. For example \"SelectFromModel_classification\" will return the following search space" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
ExtraTreesClassifier(class_weight='balanced', max_features=0.9851993193336,\n",
       "                     min_samples_leaf=5, min_samples_split=6, n_jobs=1)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "ExtraTreesClassifier(class_weight='balanced', max_features=0.9851993193336,\n", " min_samples_leaf=5, min_samples_split=6, n_jobs=1)" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "SelectFromModel_configspace_part = ConfigurationSpace(\n", " space = {\n", " 'threshold': Float('threshold', bounds=(1e-4, 1.0), log=True),\n", " }\n", " )\n", "\n", "extratrees_estimator_node = tpot.config.get_search_space(\"ExtraTreesClassifier\") #this exports an ExtraTreesClassifier node\n", "extratrees_estimator_node.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
SelectFromModel(estimator=ExtraTreesClassifier(max_features=0.277440186742,\n",
       "                                               min_samples_leaf=9,\n",
       "                                               min_samples_split=17, n_jobs=1),\n",
       "                threshold=0.0032005860778)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "SelectFromModel(estimator=ExtraTreesClassifier(max_features=0.277440186742,\n", " min_samples_leaf=9,\n", " min_samples_split=17, n_jobs=1),\n", " threshold=0.0032005860778)" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from sklearn.ensemble import ExtraTreesClassifier\n", "from sklearn.feature_selection import SelectFromModel\n", "\n", "select_from_model_wrapper_searchspace = tpot.search_spaces.pipelines.WrapperPipeline(\n", " method=SelectFromModel,\n", " space = SelectFromModel_configspace_part,\n", " estimator_search_space= extratrees_estimator_node,\n", " )\n", "\n", "select_from_model_wrapper_searchspace.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### WrapperPipeline strategy for ensembles/inner classifiers and regressors (EstimatorTransformer)\n", "\n", "Sklearn Pipelines only allow classifiers/regressors as the final step. All other steps are expected to implement a transform function. We can get around this by wrapping it in another transformer class that returns the output of predict or predict_proba inside the transform() function.\n", "\n", "To wrap classifiers as transfomers, you can use the following class: `tpot.builtin_modules.EstimatorTransformer`. You can specify whether to pass the outputs of predict, predict_proba, or decision function with the `method` parameter. \n", "\n", "#### cross_val_predict_cv\n", "\n", "An additional consideration is whether or not to use `cross_val_predict_cv`. If this parameter is set, during model training any classifiers or regressors that is not the final predictor will use `sklearn.model_selection.cross_val_predict` to pass out of sample predictions into the following steps of the model. The model will still be fit to the full data which will be used for predictions after training. Training downstream models on out of sample predictions can often prevent overfitting and increase performance. The reason is that this gives downstream models a estimate of how upstream models compare on unseen data. Otherwise, if an upsteam model heavily overfits the data, downsteam models may simply learn to blindly trust the seemingly well-predicting model, propagating the over-fitting through to the end result.\n", "\n", "The downside is that cross_val_predict_cv is significantly more computationally demanding, and may not be necessary for your given dataset. \n", "\n", "Note: This is not necessary for `GraphSearchPipeline` as the exported GraphPipeline estimator does have builtin support for inner/regressors. Instead of using a wrapper, you can set the `cross_val_predict_cv` param when initializing the `GraphSearchPipeline` object." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
EstimatorTransformer(estimator=MLPClassifier(alpha=0.000648285661,\n",
       "                                             hidden_layer_sizes=[380],\n",
       "                                             learning_rate='invscaling',\n",
       "                                             learning_rate_init=0.0008851810314,\n",
       "                                             n_iter_no_change=32))
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "EstimatorTransformer(estimator=MLPClassifier(alpha=0.000648285661,\n", " hidden_layer_sizes=[380],\n", " learning_rate='invscaling',\n", " learning_rate_init=0.0008851810314,\n", " n_iter_no_change=32))" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "classifiers = tpot.config.get_search_space(\"classifiers\")\n", "wrapped_estimators = tpot.search_spaces.pipelines.WrapperPipeline(tpot.builtin_modules.EstimatorTransformer, {}, classifiers)\n", "\n", "est = wrapped_estimators.generate().export_pipeline() #returns an estimator with a transform function\n", "est" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/neural_network/_multilayer_perceptron.py:690: ConvergenceWarning: Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.\n", " warnings.warn(\n" ] }, { "data": { "text/plain": [ "array([[0.34363566, 0.65636434],\n", " [0.14785295, 0.85214705],\n", " [0.45816571, 0.54183429],\n", " [0.81083741, 0.18916259],\n", " [0.56944478, 0.43055522]])" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "X, y = np.random.rand(100, 10), np.random.randint(0, 2, 100)\n", "\n", "est.fit_transform(X, y)[0:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "you can manually set the settings for an estimator the same way you would do it for an EstimatorNode. Here's another example with cross_val_predict and method being used." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0],\n", " [0],\n", " [0],\n", " [0],\n", " [0]])" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "classifiers = tpot.config.get_search_space(\"classifiers\")\n", "wrapped_estimators_cv = tpot.search_spaces.pipelines.WrapperPipeline(tpot.builtin_modules.EstimatorTransformer, {'cross_val_predict_cv':10, 'method':'predict'}, classifiers)\n", "est = wrapped_estimators_cv.generate().export_pipeline() #returns an estimator with a transform function\n", "est.fit_transform(X, y)[0:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These can now be used inside a linear pipeline. This is fairly similar to the default linear pipeline search space." ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('robustscaler',\n",
       "                 RobustScaler(quantile_range=(0.2632669052042,\n",
       "                                              0.892009308738))),\n",
       "                ('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                 FeatureUnion(transformer_list=[('columnonehotencoder',\n",
       "                                                                                 ColumnOneHotEncoder()),\n",
       "                                                                                ('kbinsdiscretizer',\n",
       "                                                                                 KBinsDiscretizer(encode='onehot-dense',\n",
       "                                                                                                  n_bins=58,\n",
       "                                                                                                  strategy='kmeans'))])),\n",
       "                                                ('passthrough',\n",
       "                                                 Passth...\n",
       "                                                                                                      estimator=LogisticRegression(C=334.8557628287718,\n",
       "                                                                                                                                   max_iter=1000,\n",
       "                                                                                                                                   n_jobs=1,\n",
       "                                                                                                                                   solver='saga'),\n",
       "                                                                                                      method='predict')),\n",
       "                                                                                ('estimatortransformer-2',\n",
       "                                                                                 EstimatorTransformer(cross_val_predict_cv=10,\n",
       "                                                                                                      estimator=QuadraticDiscriminantAnalysis(reg_param=0.0011738914966),\n",
       "                                                                                                      method='predict'))])),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('lineardiscriminantanalysis', LinearDiscriminantAnalysis())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('robustscaler',\n", " RobustScaler(quantile_range=(0.2632669052042,\n", " 0.892009308738))),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('columnonehotencoder',\n", " ColumnOneHotEncoder()),\n", " ('kbinsdiscretizer',\n", " KBinsDiscretizer(encode='onehot-dense',\n", " n_bins=58,\n", " strategy='kmeans'))])),\n", " ('passthrough',\n", " Passth...\n", " estimator=LogisticRegression(C=334.8557628287718,\n", " max_iter=1000,\n", " n_jobs=1,\n", " solver='saga'),\n", " method='predict')),\n", " ('estimatortransformer-2',\n", " EstimatorTransformer(cross_val_predict_cv=10,\n", " estimator=QuadraticDiscriminantAnalysis(reg_param=0.0011738914966),\n", " method='predict'))])),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('lineardiscriminantanalysis', LinearDiscriminantAnalysis())])" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dynamic_wrapped_classifiers_with_passthrough = tpot.search_spaces.pipelines.UnionPipeline([\n", " tpot.search_spaces.pipelines.DynamicUnionPipeline(wrapped_estimators_cv, max_estimators=4),\n", " tpot.config.get_search_space(\"Passthrough\")\n", " ])\n", "\n", "stc_pipeline4 = tpot.search_spaces.pipelines.SequentialPipeline([\n", " tpot.config.get_search_space(\"scalers\"),\n", " dynamic_transformers_with_passthrough,\n", " dynamic_wrapped_classifiers_with_passthrough,\n", " tpot.config.get_search_space(\"classifiers\"),\n", "])\n", "\n", "stc_pipeline4.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### GraphSearchPipeline\n", "\n", "The GraphSearchPipeline is a flexible search space without a prior restriction of pipeline structure. With GraphSearchPipeline, TPOT will create a pipeline in the shape of a directed acyclic graph. Throughout the optimization process, TPOT may add/remove nodes, add/remove edges, and performs model selection and hyperparameter tuning for each node.\n", "\n", "The primary parameters for the graph_search_space are the root_search_space, inner_search_space, and leaf_search_space.\n", "\n", "| Parameter | Type | Description |\n", "|------------------------|-------------------------------------|-----------------------------------------------------------------------------------------------------------|\n", "| root_search_space | SklearnIndividualGenerator | The search space for the root node of the graph. This node will be the final estimator in the pipeline. |\n", "| inner_search_space | SklearnIndividualGenerator, optional| The search space for the inner nodes of the graph. If not defined, there will be no inner nodes. |\n", "| leaf_search_space | SklearnIndividualGenerator, optional| The search space for the leaf nodes of the graph. If not defined, the leaf nodes will be drawn from the inner_search_space. |\n", "| crossover_same_depth | bool, optional | If True, crossover will only occur between nodes at the same depth in the graph. If False, crossover will occur between nodes at any depth. |\n", "| cross_val_predict_cv | int, cross-validation generator or an iterable, optional | Determines the cross-validation splitting strategy used in inner classifiers or regressors. |\n", "| method | str, optional | The prediction method to use for the inner classifiers or regressors. If 'auto', it will try to use predict_proba, decision_function, or predict in that order. |\n", "\n", "This search space exports a `tpot.GraphPipeline`. This is similar to a scikit-learn Pipeline, but for directed acyclic graph pipelines. You can learn more about using this module in Tutorial 6." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " leaf_search_space = tpot.config.get_search_space(\"selectors\"), \n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " max_size = 10,\n", ")\n", "\n", "ind = graph_search_space.generate()" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "image/png": "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yKhRo2zLCZ5t9OjR5jXkbNGiRea1BsCz0IoFPIgeAaWbyx47dswR01E6bZtpKxaoLm3Fanv/4MGDjlhERIQ5SzYuLs7W3ABUHSN2gAcdBzVx4kRLUafeeecdijqcN30Nvf3225ZYXl6eec3x+z/gOSjsAA/x1ltvyTfffGOJTZgwQYYOHWpbTvAuw4YNk/Hjx1tiS5cuNa89AJ6BVizgAXbu3CkdOnSQgoICyz5kupmsbkgMuEpOTo5p9+sGxhVCQ0PNucMJCQm25gbg9zFiB7i5srIyufXWWy1FnZozZw5FHVwuKirKvLac6WtPX4P6WgTg3ijsADf32muvyYoVKyyxO+64QwYNGmRbTvBu+trS15izH374QV5//XXbcgJQNbRiATe2detW6dSpkxQVFTli8fHxpi0WHh5ua27wbsePH5f27dvLnj17HLG6devKhg0bpE2bNrbmBuDMGLED3FRpaamZyO5c1OlRYbq3GEUdapq+xubOnWs5nk5fi/qa1NcmAPdEYQe4qRdeeEHWrl1riU2dOlX69u1rW07wLf369ZN7773XEtPX5N/+9jfbcgJwdrRiATe0ceNG6dq1q5SUlDhirVu3Nm2wkJAQW3ODbyksLDTTAbZv3+6IBQUFmQJPV2oDcC+M2AFupri4WMaNG2cp6vz9/WX+/PkUdah1+prT156+Bivoa1NbsvpaBeBeKOwAN/PMM8+YxRHOHnroIenevbttOcG36WtPX4Onjio/++yztuUE4PRoxQJuZM2aNdKzZ0/LfmG6Way2vXRFImAXXTih0wP07NgKAQEBsnr1ahMH4B4o7AA3msvUuXNn2bZtmyMWGBhoirqOHTvamhugdI5nt27dLKtideuT9evXS3BwsK25AfgVrVjATTz++OOWok498cQTFHVwG7qIQl+np+61eGoMgH0YsQPcgO7qr1tLOL8du3TpYtpcugIRcBe6cKJHjx6SlJTkiOled99//7307t3b1twAUNgBbrHDv24bsXv3bkdM59PpjbNt27a25gacTnJyspk24LwqNiEhwSyoCAsLszU3wNfRigVspqsNnYs6pasNKergrvS1+Ze//MUS27VrV6WVswBqHyN2gI2WLl0qV155pSWmq2K1raUrDgF3pSu39RSUVatWVXpNDxgwwLa8AF9HYQfYJDc312xlkpaWZtkMVttZiYmJ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jK254T8YAnIjd999t6WoU6+88gpFHQDAdnovevXVVy2xjIwMc++C+2DEzk3861//klGjRllif/zjH+WLL74wm0ICAGA3LRm09bp48eJK97Drr7/etrzwHxR2buDQoUNmv6CsrCxHLDo62gxvN23a1NbcAABwlp6ebqYN5eTkOGINGjQwGxo3atTI1txAK9Z2WldPnjzZUtSpN998k6IOAOB2mjVrZu5RzvQedscdd1iOv4Q9KOxs9sEHH8hnn31miV133XVy44032pYTAABnc9NNN8m1115riX366afy4Ycf2pYTfkUr1kYMZwMAvGkaUVRUlJlGpKN6sAcjdjbRevq2226zFHVq9uzZFHUAALen96pZs2ZZYnpPmzRpEi1ZG1HY2WTOnDny1VdfWWLaftU2LAAAnmDkyJFyww03WGK6YlbvcbAHrVgbsMkjAMBbZGdnm5as8z6s4eHh5ixZ9mGtfYzY2XAsy5/+9CdLUafeffddijoAgMfRe5few5zpPU7vdXrPQ+2isKtlM2bMqHTW3sSJE2XIkCG25QQAwPnQTYu1kHOm97qZM2falpOvohVbi3bs2CEdO3aUwsJCRywuLs4MV0dERNiaGwAA5yM3N9dMM0pLS3PEQkJCZOPGjZKYmGhrbr6EEbtaUlZWJhMmTLAUdeq9996jqAMAeLzIyEhzT3Om9zy99+k9ELWDwq6WvPzyy7J69WpLbMqUKTJgwADbcgIAwJUGDhwod911lyW2atUqeeWVV2zLydfQiq0FuuFw586dpbi42BFLSEgww9NhYWG25gYAgCvpwgmddrRr1y5HrE6dOrJ+/XqzehY1ixG7GlZSUiLjxo2zFHV+fn4yf/58ijoAgNfRrU7mzZtn7nUV9B44fvx4c09EzaKwq2HPPfec+S3F2f333y+9evWyLScAAGpS7969Zdq0aZZYUlKSPP/887bl5CtoxdYgLei6d+8upaWljlibNm1MPDg42NbcAACoSbpwQqchbdu2zRELDAyUNWvWSKdOnWzNzZsxYldDioqKzLCzc1EXEBBgWrAUdQAAb6dbneg9T+99FfSeqPdGvUeiZlDY1ZCnnnrKHBHm7JFHHpGuXbvalhMAALWpW7du8vDDD1tiunfr008/bVtO3o5WbA348ccfzRw656NUOnToYIafdWUQAAC+QhdO6KDGpk2bHDF/f3+z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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "est1 = ind.export_pipeline()\n", "est1.plot() #GraphPipelines have a helpful plotting function to visualize the pipeline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets add a few more mutations and plot the final pipeline to get a sense of the diversity of pipelines that can be generated with this search space" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "image/png": "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diw0NFTTtRFZC7diySng88lDDz1kNkLqww8/ZFBHFsF27Geffaayc9imNXT+/Hl55JFHVOUsznCSY6pXr5588MEHRtf++ecf9drBHAc5C2bsyCngADwa0BoaNGiQ/PLLL8yw0BUdth8xYoTs3LnT7LaIiAj5/vvvOXfUQeEtD1uvy5YtM3sNwd85kaNjYEdOMRMUW7CGmRT0LMMWbO3atTVdGzn2MPn//e9/ZofuwcPDQ1555RWZOnUq2+c4oNTUVPWakZGRob8WEhKiXjMwio7IkfEViRwaPpfcf//9Zttjn376KYM6uire3t7y9ttvy++//64+KBgqKSlRc0dvvvlmSUtL02yNdGXw2oDXCEMI8h544AFuyZLDY2BHDu2rr76SP/74w+gaqhuHDh2q2ZrIudx6662yd+9e1eDaFCosUS27evVqTdZGV27YsGHqyxCC+AULFmi2JiJr4FYsOayEhATViPjcuXP6a7Vq1VIVbqYZFqKrhSzdzJkz1RemGRjCOU5s286YMUM8PT01WyNdnvT0dPUaYph1ReEMtmRRaEHkiJixI4eEN9YJEyYYBXUwd+5cBnVkEzhXh8AN2bk6deoY3YbPx6+++qpce+21qqUGOQa0ODFtb4OJJOPHj+eWLDksBnbkkObMmSN///230TVMChg4cKBmayLXgOBtz549ZrOIYfPmzWrWrGnFJdkvVM+bjo7DawteY4gcEbdiyeFgcDvmPubl5emvoVcdtk/QkJiosrLG7733njz77LNSXFxc7mi7t956SzU3JvuWlZWltmQxZsxwXjDOVjZu3FjTtRFdLmbsyOHOOSEzZxjUwZdffsmgjioV2pw8+eSTKktX3hxiNMfu0aOHxMXFabI+slxQUJDMmzfPbF7wuHHjzM5TEtk7BnbkUN5//33ZtGmT0bWJEyeqebBEWujSpYvs3r273Ers6Oho6dChg3z33XearI0sh9eQBx980Ojaxo0bzSZVENk7bsWSwzh06JC0b99ejXfSQaZk3759EhAQoOnaiPBSiuKdKVOmSEFBgdntOJCPLB62+Mg+oRgLZyRPnDihv4atdJypbNasmaZrI7IUM3bkEHCGCQecDYM6tJhAHzsGdWQP8O8RDW537NghzZs3N7sdW32dO3eW/fv3a7I+urSqVavK/Pnzja7hNQevPeWdoySyRwzsyCG8+eab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eHhcPD4RERFZF9/fXWTyREXi4+Pl58VLxO/UKel46JAE5uXZZM8d6VlE8vhLr1+/vtUfg4iIiP7D93cXDewgNTVV/li2TDKTkqVZXJxEJieLuxV+a0jNojoGBymx5470rCNH8kRERI6E7+8uGthBcXGxbN68WXZu3iwB6enSOD5B6qWni0dp6RV1nEZzQvSxQckzqmNwkNJR99yJiIgcFd/fXTSw0zl16pRs2bxZTsTGimdentRPTJSwMxlSLTdXvEpKKrxfkYeHZGNWXfUQNUYEHafRnLCng5Y8ExERORO+v7toYKeTmZkp+/btk31RUVKQmytlxcUSkJ8vgRmZ4l1cLO5lpVLq5i6Fnp5yNiRYcnx9xc3TUw0ixmw4jBFxtI7TREREzo7v7y4a2OmUlJRIRkaGpKWlqa/TqalSWFAgJcXF4uHpKd4+PlKjdm2pVauW+goJCXGogb9ERESuiO/vLhrYEREREbkCh+5jR0RERET/YWBHRERE5CQY2BERERE5CQZ2RERERE6CgR0RERGRk2BgR0REROQkGNgREREROQkGdkREREROgoEdERERkZNgYEdERETkJBjYERERETkJBnZEREREToKBHREREZGTYGBHRERE5CQY2BERERE5CQZ2RERERE6CgR0RERGRk2BgR0REROQkGNgREREROQkGdkREREROgoEdERERkZNgYEdERETkJBjYERERETkJBnZEREREToKBHREREZGTYGBHRERE5CQY2BERERGJc/g/XTIGj5FEzGgAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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7zPEo3Xr1toNGzZYenraTz75xGBuX375paWnZTdUVVVp586dq3V2djb4O+DRr18/bXx8vNbaKSsr0958880G78/BwUH77bffWnRua9euFfOQzsvPz0+bkpJi8nOVl5drQ0ND9c6FNTYvL8/k52JsHzbsmEbz+OOPGyzE69evt/S0rIKamhrtvHnzjG7W3bp10x47dkyrFMOiT58+evPz8fHRZmVlWXpqdsXhw4e1ERERRr8vjo6O2tdff11bWVmptWbwXZs2bZrR9/jee+9ZdG74fOVzGjhwoDBITc3ff/9tcK6ZM2ea/DyM7cOGHdMoDh48qFWpVHqLzy233GLpaVkF8Gjec889Rjewq666Spudna1VEps3bzaY54MPPmjpadkdMNxgYMCQM/bdiYyMFAagNVNdXW30hhGPF198UdwQWWpe48aNM5jTvffe2yJzknsv1Wq19tChQyY/D2PbsGHHNBgsZFdeeaXewuPq6qo9deqUpaemeODpkn92usf06dMVG8a+4447DEJk+/bts/S07BKEXhGCNfYdQsgWoVt4v6x5fXnllVeMvr9HHnlEGFmWID8/3yBMisfXX39t8nNhLXVxcTG46bOUYctYJ2zYMQ3m119/NVjc5syZY+lpKZ7ExERt165djeYRIdSk5EU7LS1N6+HhoTfvIUOGWGyTtXdwA/DSSy8JT44xAwh/G6WE85vKhx9+aPS93XXXXRYLOx85csTgOnByctLu3LnT5Od67bXXDN77kiVLTH4exnZhw45pEIWFhdqOHTvqLTadO3fWlpaWWnpqigaFEN7e3gYLNQonVq9erbUGjOUELly40NLTsmv27t2rDQsLM2oAwYsO40ij0WitlQULFhikfOARHR3dIvltDWH58uUG88GamJmZadLzYE0NDg42OE9RUZFJz8PYLmzYMQ3iueeeM1jUfv/9d0tPS9EgVGPMsxIQEKCNi4vTWgvGKvbatWvHFXsWBgYOrktjBhAew4cP1yYnJ2utFRhS8IrJ39fIkSPFjaYlePbZZw3mM2LECJN7ElE1Lz/P7NmzTXoOxnZhw465LAjtyBfY0aNHKzqEaEngKUE1m7HNFhV1GRkZWmuDK/aUC8KBxnLAdJ7hzz//3GpD5/jeubm5GbyvwYMHa3Nycsw+H+QwXnvttQbzeeKJJ0x6Hqyto0aNMgj9Hj9+3KTn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j4WdSUnwOJ0YO/Zgl2oRERFpP63vPTSw44GEwRkZ2DYwHY0d7MrcVXj92wemY3Bmpg5KiIiIdILW9x4a2NHkqVNRk5SErd+N8zhW8gcMMLdj8pQpx/R2iIiIhAOt7z00sGPD45MnT0ZeRgaqOzF2ozOqnE5syczAD6ZMMbdHREREOkfrew8N7HxtPuJTByBrxAh4jnKhJa8va+QIJAwYgEmTJh3V6xYREQlnWt97aGDHWa0zZ82Cu39/bBg18qjtx/N6eH37U/pjxqxZQYf3ioiISMdofe+hgR1xnuqceXNRkZ6O9aNHHfHInj+f18Pr4/Xy+kVERKRraX0PwVmxXamwsBB/+8tf4dy9G+NzcxHrdh+RPXemZxnJ80nngF8RERE5crS+99DAjvbs2YN3Vq7Evl3FGL51KzKKi2HtgrvG1CxPx7CQknvuTM+GciQvIiISSrS+99DAjjweD9atW4fP161DTFkZhhQWIa2sDLbGxg51nGZzQvax4ZFnno5hIWWo7rmLiIiEKq3vPTSw89m9ezc+XbcOBfn5sLvdGLhzJ1LKK9DH5UJEQ8Mhv6/eZjMDfzkbjmNE2HGazQknh+iRZxERkXCi9b2HBnY++/btw+bNm7E5Kwu1Lhe8Hg9i9u9HbMU+9PJ4YPU2otFiRZ3djuqEeNQ4HLDY7WbgL2fDcYxIqHWcFhERCXda33toYOfT0NCAiooKlJaWmo+9e/agrrYWDR4PbHY7ekVF4bh+/ZCcnGw+EhISQmrgr4iISE+k9b2HBnYiIiIiPUFI97ETERERke8psBMREREJEwrsRERERMKEAjsRERGRMKHATkRERCRMKLATERERCRMK7ERERETChAI7ERERkTChwE5EREQkTCiwExEREQkTCuxEREREwoQCOxEREZEwocBOREREJEwosBMREREJEwrsRERERMKEAjsRERGRMKHATkRERCRMKLATERERCRMK7ERERETChAI7ERERkTChwE5EREQkTCiwExEREQkTCuxEREREwoQCOxEREZEwocBOREREJEwosBMREREJEwrsRERERBAe/h+Kxz53y5qllAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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Sy65pLZdu3ZNWskbOXJk7UsvvVSbn59f68mrk8OHD3c697i4OKf3psmTJzvdjyuVcmAU2LWCiooK88Pr6hd34MCBtWvXrnX6nPT09NrDDjus0V/4+Pj42qlTp5rgT0REvEdpaWntBx98UHviiSe6vNh3/AgMDKw9/fTTa2fMmNFoqk5rKiwsNEGo43nHxMTUZmRkNAhujz32WKf7ffHFF616/t5OW7GtUB7OmYJ//vmn022nn3463nrrrQZJs5zn+t///hevvvqq0zgxYi8kJqFyPIsqikREvP89ghW1bIL8119/7fX+7du3x9lnn22KLgYNGuQxrVPYFH/MmDGYNWtWg+PcZv3hhx8wdOhQ82/OnuXsWPtmzyySYZWtrzR0bnGtHVm2JfPnzzcra66uwB544IEGq21MLuUydXR0dKNXbWPGjKnNyspq1a9JRETcj+8HXN264YYbTHpOU7Zq+/fvX/v444/X5ubm1nqCHTt2mPQgx/OMjIysnTt3bv393nzzTaf7nHnmma167t5MgV0L4TJ7cHCw0w9vWFhY7fTp0xvc948//jBbso398vbs2bP2yy+/bLWvRUREWg63W/maz+1XbsPuLcDjdu4JJ5xg3ne4zduaSkpKao8++minc4yIiKidPXt2fRB78sknO93no48+atVz91YK7JoZcwjuuOMOl7983bt3b5BMyqusc845p9Ff1tDQ0NqHHnqotry8vFW/JhERaR0snHj55Zdd5rC5+mBhBgs0fvvtt1ZrncLg8rjjjnPZ1oULGbRx40anHSq27Nq8eXOrnLM3U2DXjIqKimpPOeUUl79sRxxxRO22bdvM/SorK2sfe+wx80Pe2C8nl6VzcnJa+0sSEREPwVScu+++uzYxMbFJQV6PHj1q77nnntrs7OwWP9eysjJTHOJq1+rXX38192EBoOPtY8eOVVHgPlJg10z4i9O3b1+Xv1xXXHFFfSXTN998U9urV6895kywSaWIiIgrXInj+8RFF120xwUC+w92WXjllVdMe5KWwt2mk046yelcQkJCan/66ScTwHG72fH2t99+u37lz1saNrcmBXbN4Mcff3RZ9GC1WmtffPFFc59Vq1aZK5HGfumYXPrss8/WVlVVtfaXIyIiXoLBz3vvvWemEvn5+e01wAsKCqqdOHFi7VdffdUi7zds93Xqqac6nQdz0L///vvarVu31nbo0MEpH+/cc88158r3xpkzZzb7eXoztTtxI34rX3jhBfznP/8x8w/tsWz7008/xfDhw/HII4/giSeecDk7kKXqF198MR5++GHTrVtERGR/5Obm1rdOWb58+V7vz/ecc845B+eff75pndJcdu7caWajT58+vcHxoKAgM1e3oqIC48aNa/TzDz74YGRnZzfb+Xm7NhHYuRoyXVlejprqavhbLAgKCTngIdP8Qb366qvx+uuvO93GMSpffPGFGbPCnnPr1693+RgM+hgYDhs2bL+/VhEREXt8m8/IyDBjzD744APk5eXt9XM47osBHgO9Tp06uf2cqqqqzGN/8sknTr1ZGfBNmTIF06ZNa/TzCwsLERkZ2SLv797GpwO7goICLF68GEszMlBRWopamw3h5eWIzM9HgM0G/9pa1Pj5ocpqRVFMDEpCQuBntSI4LAwDhgzBwIEDXc5a5Xw/Pu7o0aPNStzWrVtN0+G0tDSn+5522mm44447zMfPP//c6FXSo48+ahpMcs6riIhIc+AixLfffmtW8b788ksTYO0J35NOOOEE8/40duxYhISEuO1cOOucM3A//PDDBsetVmv97Y1h42Oeu7vf332BTwZ2GzduxOxZs7AmOxsBZWVIyFmPzvn5iCwtRYDDFqm9KosFRWFh2BQTg5yEbqgKDUWPpCSkjh6Nzp07m/swODvuuONQU1ODLl264MUXX8S1117rchXu1ltvNUvKkydPdtqaJV418HPvvfdeDT0WEZEWxclGH330kVnJmzt37l7vHxERgYkTJ5ogLzU11S1TLhi8XXTRRXjvvfeadH+uHo467DAMGjAAYVVVbn1/9xU+FdjxB4SrZvPT0hCel4eD1+Wga14eLDU1+/xY1f7+2BAXh78TE1ASF4dhqak47LDDkJKSgqVLl9bfjz/Yjt9CXtFceOGFZhmZq3muHH300XjuuefQr1+//fhKRURE3GflypUmwHv33XcbTReyd9BBB5mtWq648e8Hggsf//73v81IzcZwIYTvwanDhiGupAS9c3Nx8I4St72/p6am1q8UejufCew2b96MmTNmoGBDLvpkZyMpN9csxR4oLuVmx8djRVISLO3C8eiTTzYarBH38Dm7r7EZf926dcPTTz+NCRMmeMxMPxEREeJu1K+//mqCPBb8lZaW7vVzmJbEIO+MM84w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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnYAAAHWCAYAAAD6oMSKAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAlQlJREFUeJzt3QdY0+f2B/AvSUAIyIioIIIT3FVxVdHuqS1tbWun3eN2/DtuvR237e1ub/e93e3tsHo7rLW2trXb2ha0DnCDgopsEAhDElYC/+e8Gm7CEmRk8P30yVP4keT3JkJyct73PceroaGhAURERETk9jTOHgARERERdQ0GdkREREQegoEdERERkYdgYEdERETkIRjYEREREXkIBnZEREREHoKBHREREZGHYGBHRERE5CEY2BERERF5CAZ2RERERB6CgR0RERGRh2BgR0REROQhGNgREREReQgGdkREREQegoEdERERkYdgYEdERETkIRjYEREREXkIBnZEREREHoKBHREREZGHYGBHRERE5CEY2BERERF5CAZ2RERERB6CgR0RERGRh2BgR0REROQhGNgREREReQgGdkREREQegoEdERERkYdgYEdERETkIXTOHgAReRar1Qqj0YjCwkJ1KSooQE1VFeqtVmi0WvTx80P/sDAMHDhQXQwGA7RarbOHTUTkEbwaGhoanD0IInJ/paWl2LZtG3YkJ6PaZEKDxYKAqioEGY3wtligaWhAvZcX6nQ6lBsMqPTzg5dOB19/f0yIjcXEiRMREhLi7IdBROTWGNgRUafk5eVhXUICMtLT4W02IyorG+FGI4JMJnhbra3erk6rRbm/P/INBmRFRaJOr8ew6GjEzZmD8PDwHn0MRESegoEdER0Ti8WCxMREbEpMREBxMUZmZmFwcTG09fUdvi+rRoOc0FDsHRKFytBQTIuLQ1xcHHQ6rhYhIuoIBnZE1GEFBQX4dtUqlObkYnR6OqJzc9VUa2fJVG16RAR2R0fDMDgCc+PjERYW1iVjJiLqDRjYEVGHZGZmYuWyZdDn5WNKaioCzeYuP0eFXo+kMWNgHjQIF1yyAEOGDOnycxAReSIGdkTUoaBuxSefoF9mFqanpEB3DNOu7WXRaLBh3FgYo6Jw4WWXMbgjImoH1rEjonZPv0qmzpCZheN37erWoE7I/c/cuQuGrCysXPaZOj8R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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for i in range(0,50):\n", " ind.mutate()\n", " if i%5==0:\n", " est = ind.export_pipeline()\n", " est.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### TreePipeline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "TreePipelines work the same way as GraphPipelines, but they are limited to a tree structure. This is similar to the search space in the original TPOT.\n", "\n", "(This search space is still experimental and currently built off GraphSearchPipeline. It may be rewritten with its own code in the future.)" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "image/png": "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INihxM2ZM0dWr15t1aar/XQzYsCVZWZmykcffSTTpk2Tw4cP27yuZcuWMnHiRLnzzjs5/xhAiaIUixIVExMjLVq0kPT09MK2sLAw2bFjhwQGBjq0b8D10n/POuL82muvmZXettx4440m0PXu3ZvTVADYBSN2KNEVgbptg2WoUwsWLCDUwSWlpaWZVdzTp0+/6IxjS+3bt5eXXnpJevToQaADYFcEO5SYmTNnyoYNG6zaRo0aJbfccovD+gRcD50f+u6778qMGTPMcXi2dOrUyYzQde/enUAHwCEoxaJE6J5drVu3NpPKC9SrV89szspZl3AVZ8+eNXNE9U3K6dOnbV7XtWtXE+j0VwIdAEdixA4lsuXD0KFDrUKdPuwWLVpEqINL0E2033rrLZk9e7acOXPG5nW33nqrWeXauXNnu/YPAGwh2KHY6QrBC8/BfPrppyUyMtJhfQKuRmJioglzb775phmts6Vnz54m0HXo0MGu/QOAK6EUi2KlgU4njuvO+5absWo7h5nDWSUkJMisWbPk7bffvmi/RUt9+vQxga5t27Z27R8AXC1G7FCse3rpKljLUKebsGoJllAHZ3TixAkzf27u3LmSmppq87p+/fqZQNeqVSu79g8ArhXBDsXm5ZdfNkeEWRo3bhyjG3A6x48fl9dff13mzZt30XY8lvNCBwwYYI7+at68ud37CADXg1IsisVvv/1m5tDp8WEFdGPiTZs2ia+vr0P7BhQ4evSo2VT4gw8+MCPMl6InQ9x7770yfvx4adKkid37CABFQbBDsWzaqiWq/fv3F7b5+PjIli1bGOmAUzh06JC8+uqrZnNsy9XalnTawH333ScvvPCCNGzY0O59BIDiQCkWRaYjG5ahrqAsS6iDox08eNCs0l64cKHV3E9L3t7eZnsenTZQv359u/cRAIoTI3Yokl9++cVsymqpXbt2sm7dOvPABBx1RvHUqVPl448/NkfbXYqOKg8fPlyef/55qVOnjt37CAAlgWCH63bu3Dkzjy42NrawTVe/btu2TRo1auTQvqF02rt3r0yZMkU++eQTq/melnTO54MPPijPPfechIWF2b2PAFCSGFLBdXvmmWesQp3ShyqhDvamq7EnT54sn3/+udh6r6pvOh5++GF59tlnJTQ01O59BAB7YMQO12XlypXSo0cPqzY9Vmn16tVmEjpgD3r2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6PT4sAItWrSQTZs2ia+vrzi7o0ePyvTp0+X99983I4+XoidD6Bmu48ePl4iICLv3EQAAXIxgVwKb87Zq1cqcMlHAx8dHtmzZ4vQjWnrcl54SMX/+fKtVvJa0nHzffffJCy+8IA0bNrR7HwEAgG2UYouZjmBZhrqCsqwzh7rY2FizenfhwoVme5ZL8fb2Ntu2aDm5fv36du8jAAC4MkbsitEvv/xiNt+11K5dO1m3bp0JRs4mJiZGpk6dKh9//LE58uxSdLRx+PDh8vzzz0udOnXs3kcAAHD1CHbFRLf/0Hl0OvpVQFe/btu2TRo1aiTOZO/evTJlyhT55JNPrOYBWtK5gA8++KA899xzEhYWZvc+AgCAa+d8w0gu6plnnrEKdUrDkzOFOj3abPLkyfLZZ5+JrTyvYfThhx+WZ599VkJDQ+3eRwAAcP0YsSsGK1eulB49eli1de7cWVavXm0WGziankmrge7LL7+0eY2fn5889thjMnbsWAkJCbFr/wAAQPEg2BXDealNmzaVuLi4wjZ/f3+Jjo6WevXqObRvesLFK6+8It98843Na/S82scff1yeeuopqVq1ql37BwAAihel2CIaM2aMVahTM2bMcGio0/3yNNAtX77c5jXly5eX0aNHm/4HBwfbtX8AAKBkMGJXBMuWLZM777zTqq179+6yatUqcyKDva1fv14mTZpkSsO2VKhQwYS5J598UipVqmTX/gEAgJJFsLtOCQkJpgSr56gWCAwMlB07dth9FemaNWtMoPvpp59sXqMhTsutTzzxhAl3AADA/VCKvU6jRo2yCnVq9uzZdgt1msd1cYYGOt0/zxYts+qCCF0YoeVXAADgvhixuw66Xci9995r1da7d29Tmi3pEqz+df3nP/8xgU43PrZFF0LoliWPPPKIWSABAADcH8HuGsXHx5tD7xMTE63KnLpHXPXq1UvsvvrX9P3335tAt3HjRpvXaR90U+GHHnrIrM4FAAClB6XYawxXI0eOtAp1au7cuSUW6vSeOhKoq1yjoqJsXlezZk1z7NeIESPMJsMAAKD0IdhdgR65lZmZaTbwXbx4sQlZlgYMGCCDBg0qkft+/fXXJtDpBsO21KpVS1544QUZNmyYlClTptj7AQAAXAel2Mv47rvv5L777pP09HS555575Ntvv5WzZ89azWPbuXOnVKlSpdjumZub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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "tree_search_space = tpot.search_spaces.pipelines.TreePipeline(\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " leaf_search_space = tpot.config.get_search_space(\"selectors\"), \n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " max_size = 10,\n", ")\n", "\n", "ind = graph_search_space.generate()\n", "exp = ind.export_pipeline()\n", "exp.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tips and Tricks\n", "\n", "* Two very helpful transformers to use with search spaces are `tpot.buildin_models.Passthrough` and `tpot.builtin_models.SkipTransformer`. \n", " Passthrough will simply pass through the exact inputs it receives into the next step. This is particularly useful inside UnionSearchSpace as it allows for both the transformed data as well as the original data to be passed into the next step.\n", " SkipTransformer will always return nothing. This is helpful when inside a union with Passthrough and an optional second method. For example, if you are unsure of whether or not you will need a transformer, you can have SkipTransformer be one option that will skip the transformation step if selected." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, the FeatureUnion layer will always have at least one transformer selected and will always have one passthrough" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('featureunion',\n",
       "                                FeatureUnion(transformer_list=[('kbinsdiscretizer',\n",
       "                                                                KBinsDiscretizer(encode='onehot-dense',\n",
       "                                                                                 n_bins=80,\n",
       "                                                                                 strategy='kmeans')),\n",
       "                                                               ('fastica',\n",
       "                                                                FastICA(algorithm='deflation',\n",
       "                                                                        n_components=91))])),\n",
       "                               ('passthrough', Passthrough())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('kbinsdiscretizer',\n", " KBinsDiscretizer(encode='onehot-dense',\n", " n_bins=80,\n", " strategy='kmeans')),\n", " ('fastica',\n", " FastICA(algorithm='deflation',\n", " n_components=91))])),\n", " ('passthrough', Passthrough())])" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from tpot.search_spaces.pipelines import *\n", "from tpot.config import get_search_space\n", "\n", "#This FeatureUnion layer will always have at least one transformer selected and will always have one passthrough\n", "transformers_with_passthrough = UnionPipeline([\n", " DynamicUnionPipeline(get_search_space([\"transformers\"])),\n", " get_search_space(\"Passthrough\")\n", " ]\n", " )\n", "\n", "transformers_with_passthrough.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, the FeatureUnion layer will always one passthrough. In addition, it may select one or more transformer, but it may skip transformers altogether and only include a Passthrough. " ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),\n",
       "                               ('passthrough', Passthrough())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),\n", " ('passthrough', Passthrough())])" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "final_transformers_layer =UnionPipeline([\n", " ChoicePipeline([\n", " DynamicUnionPipeline(get_search_space([\"transformers\"])),\n", " get_search_space(\"SkipTransformer\"),\n", " ]),\n", " get_search_space(\"Passthrough\")\n", " ]\n", " )\n", "\n", "final_transformers_layer.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),\n",
       "                               ('passthrough', Passthrough())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),\n", " ('passthrough', Passthrough())])" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "inner_estimators_layer = UnionPipeline([\n", " ChoicePipeline([\n", " DynamicUnionPipeline(wrapped_estimators, max_estimators=4),\n", " get_search_space(\"SkipTransformer\"),\n", " ]),\n", " get_search_space(\"Passthrough\")]\n", " )\n", "\n", "inner_estimators_layer.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('normalizer', Normalizer(norm='l1')),\n",
       "                ('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                 SkipTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('featureunion-2',\n",
       "                 FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                 SkipTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('bernoullinb',\n",
       "                 BernoulliNB(alpha=5.0573782838899, fit_prior=False))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('normalizer', Normalizer(norm='l1')),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('featureunion-2',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('bernoullinb',\n", " BernoulliNB(alpha=5.0573782838899, fit_prior=False))])" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "final_linear_pipeline = SequentialPipeline([\n", " get_search_space(\"scalers\"),\n", " final_transformers_layer,\n", " inner_estimators_layer,\n", " get_search_space(\"classifiers\"),\n", " ])\n", "\n", "final_linear_pipeline.generate().export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Template Search Spaces\n", "\n", "As mentioned in Tutorial 1, TPOT has several buildin search spaces. Here is the same table:\n", "\n", "| String | Description |\n", "| :--- | :----: |\n", "| linear | A linear pipeline with the structure of \"Selector->(transformers+Passthrough)->(classifiers/regressors+Passthrough)->final classifier/regressor.\" For both the transformer and inner estimator layers, TPOT may choose one or more transformers/classifiers, or it may choose none. The inner classifier/regressor layer is optional. |\n", "| linear-light | Same search space as linear, but without the inner classifier/regressor layer and with a reduced set of faster running estimators. |\n", "| graph | TPOT will optimize a pipeline in the shape of a directed acyclic graph. The nodes of the graph can include selectors, scalers, transformers, or classifiers/regressors (inner classifiers/regressors can optionally be not included). This will return a custom GraphPipeline rather than an sklearn Pipeline. More details in Tutorial 6. |\n", "| graph-light | Same as graph search space, but without the inner classifier/regressors and with a reduced set of faster running estimators. |\n", "| mdr |TPOT will search over a series of feature selectors and Multifactor Dimensionality Reduction models to find a series of operators that maximize prediction accuracy. The TPOT MDR configuration is specialized for genome-wide association studies (GWAS), and is described in detail online here. |\n", "\n", "Rather than create your own search space, you can simply pass the string into the `search_space` param. Alternatively, you can access tpot.config.`template_search_spaces.get_template_search_spaces` directly which offers a few more customizable options for each template including `cross_val_predict_cv` and whether or not stacked classifiers/regressors are allowed. Or you can copy the code and customize it manually!\n", "\n", " `tpot.config.template_search_spaces.get_template_search_spaces`\n", " Returns a search space which can be optimized by TPOT.\n", "\n", " Parameters\n", " ----------\n", " search_space: str or SearchSpace\n", " The default search space to use. If a string, it should be one of the following:\n", " - 'linear': A search space for linear pipelines\n", " - 'linear-light': A search space for linear pipelines with a smaller, faster search space\n", " - 'graph': A search space for graph pipelines\n", " - 'graph-light': A search space for graph pipelines with a smaller, faster search space\n", " - 'mdr': A search space for MDR pipelines\n", " If a SearchSpace object, it should be a valid search space object for TPOT.\n", " \n", " classification: bool, default=True\n", " Whether the problem is a classification problem or a regression problem.\n", "\n", " inner_predictors: bool, default=None\n", " Whether to include additional classifiers/regressors before the final classifier/regressor (allowing for ensembles). \n", " Defaults to False for 'linear-light' and 'graph-light' search spaces, and True otherwise. (Not used for 'mdr' search space)\n", " \n", " cross_val_predict_cv: int, default=None\n", " The number of folds to use for cross_val_predict. \n", " Defaults to 0 for 'linear-light' and 'graph-light' search spaces, and 5 otherwise. (Not used for 'mdr' search space)\n", "\n", " get_search_space_params: dict\n", " Additional parameters to pass to the get_search_space function." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('standardscaler', StandardScaler()),\n",
       "                ('rfe',\n",
       "                 RFE(estimator=ExtraTreesClassifier(max_features=0.8009842720563,\n",
       "                                                    min_samples_leaf=4,\n",
       "                                                    min_samples_split=9,\n",
       "                                                    n_jobs=1),\n",
       "                     step=0.4315847507401)),\n",
       "                ('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                 SkipTransformer()),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('featureunion-2',\n",
       "                 FeatureUnion(tran...\n",
       "                                                                                                                                       max_features='sqrt',\n",
       "                                                                                                                                       min_samples_leaf=17,\n",
       "                                                                                                                                       min_samples_split=8))),\n",
       "                                                                                ('estimatortransformer-2',\n",
       "                                                                                 EstimatorTransformer(cross_val_predict_cv=5,\n",
       "                                                                                                      estimator=LGBMClassifier(max_depth=3,\n",
       "                                                                                                                               n_estimators=84,\n",
       "                                                                                                                               n_jobs=1,\n",
       "                                                                                                                               num_leaves=244,\n",
       "                                                                                                                               verbose=-1)))])),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('lineardiscriminantanalysis',\n",
       "                 LinearDiscriminantAnalysis(shrinkage=0.369619691802,\n",
       "                                            solver='eigen'))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('standardscaler', StandardScaler()),\n", " ('rfe',\n", " RFE(estimator=ExtraTreesClassifier(max_features=0.8009842720563,\n", " min_samples_leaf=4,\n", " min_samples_split=9,\n", " n_jobs=1),\n", " step=0.4315847507401)),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('featureunion-2',\n", " FeatureUnion(tran...\n", " max_features='sqrt',\n", " min_samples_leaf=17,\n", " min_samples_split=8))),\n", " ('estimatortransformer-2',\n", " EstimatorTransformer(cross_val_predict_cv=5,\n", " estimator=LGBMClassifier(max_depth=3,\n", " n_estimators=84,\n", " n_jobs=1,\n", " num_leaves=244,\n", " verbose=-1)))])),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('lineardiscriminantanalysis',\n", " LinearDiscriminantAnalysis(shrinkage=0.369619691802,\n", " solver='eigen'))])" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "linear_search_space = tpot.config.template_search_spaces.get_template_search_spaces(\"linear\", inner_predictors=True, cross_val_predict_cv=5)\n", "linear_search_space.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "linear_search_space = tpot.config.template_search_spaces.get_template_search_spaces(\"linear\", inner_predictors=True, cross_val_predict_cv=5)\n", "linear_est = tpot.TPOTEstimator(\n", " search_space = linear_search_space,\n", " scorers=['roc_auc_ovr',tpot.objectives.complexity_scorer],\n", " scorers_weights=[1,-1],\n", " classification=True,\n", " verbose=1,\n", " )\n", "\n", "#alternatively, you can use the template search space to generate a pipeline\n", "linear_est = tpot.TPOTEstimator(\n", " search_space = \"linear\",\n", " scorers=['roc_auc_ovr',tpot.objectives.complexity_scorer],\n", " scorers_weights=[1,-1],\n", " n_jobs=32,\n", " classification=True,\n", " verbose=1,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Optimize Search Space with TPOTEstimator\n", "\n", "Once you have constructed a search space, you can use TPOTEstimator to optimize a pipeline within that space. Simply pass that search space into the `search_space` parameter. Here is a cell where you can select different search spaces that we created in this tutorial." ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": [ "all_search_spaces ={\n", " \"classifiers_only\" : classifier_choice,\n", " \"stc_pipeline\" : stc_pipeline,\n", " \"stc_pipeline2\": stc_pipeline2,\n", " \"stc_pipeline3\": stc_pipeline3,\n", " \"stc_pipeline4\": stc_pipeline4,\n", " \"final_linear_pipeline\": final_linear_pipeline,\n", " \"graph_pipeline\": graph_search_space,\n", "}\n", "\n", "X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.5)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 5it [00:58, 11.77s/it]\n" ] }, { "data": { "text/html": [ "
TPOTEstimator(classification=True, cv=5, early_stop=2, max_time_mins=10,\n",
       "              n_jobs=4,\n",
       "              scorers=['roc_auc_ovr',\n",
       "                       <function complexity_scorer at 0x32f4e0550>],\n",
       "              scorers_weights=[1.0, -1.0],\n",
       "              search_space=<tpot.search_spaces.pipelines.sequential.SequentialPipeline object at 0x32f7692d0>,\n",
       "              verbose=2)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "TPOTEstimator(classification=True, cv=5, early_stop=2, max_time_mins=10,\n", " n_jobs=4,\n", " scorers=['roc_auc_ovr',\n", " ],\n", " scorers_weights=[1.0, -1.0],\n", " search_space=,\n", " verbose=2)" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selected_search_space = all_search_spaces[\"stc_pipeline\"] #change this to select a different search space\n", "\n", "\n", "est = tpot.TPOTEstimator(\n", " scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", " classification = True,\n", " cv = 5,\n", " search_space = selected_search_space,\n", " max_time_mins=10,\n", " max_eval_time_mins = 10,\n", " early_stop = 2,\n", " verbose = 2,\n", " n_jobs=4,\n", ")\n", "\n", "est.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "auroc score 0.9947351959966638\n" ] } ], "source": [ "# score the model\n", "auroc_scorer = sklearn.metrics.get_scorer(\"roc_auc\")\n", "auroc_score = auroc_scorer(est, X_test, y_test)\n", "\n", "print(\"auroc score\", auroc_score)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('selectfwe', SelectFwe(alpha=0.0001569023321)),\n",
       "                ('powertransformer', PowerTransformer()),\n",
       "                ('mlpclassifier',\n",
       "                 MLPClassifier(activation='identity', alpha=0.0008696190619,\n",
       "                               hidden_layer_sizes=[203, 203],\n",
       "                               learning_rate_init=0.0135276110446,\n",
       "                               n_iter_no_change=32))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('selectfwe', SelectFwe(alpha=0.0001569023321)),\n", " ('powertransformer', PowerTransformer()),\n", " ('mlpclassifier',\n", " MLPClassifier(activation='identity', alpha=0.0008696190619,\n", " hidden_layer_sizes=[203, 203],\n", " learning_rate_init=0.0135276110446,\n", " n_iter_no_change=32))])" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#plot the best pipeline\n", "if isinstance(est.fitted_pipeline_, tpot.GraphPipeline):\n", " est.fitted_pipeline_.plot()\n", " \n", "est.fitted_pipeline_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Transformer-only pipelines - imputation optimization example\n", "\n", "Pipelines don't necessarily need to end in a classifier or regressor. Transformer only pipelines are possible as long as you have a custom objective function to match. " ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.04690299241236334" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import sklearn\n", "import sklearn.datasets\n", "import numpy as np\n", "import tpot\n", "\n", "#in practice, cross validation is likely better, but this simple example is fine for demonstration purposes\n", "def rmse_obective(est, X, missing_add=.2, rng=1, fitted=False):\n", " rng = np.random.default_rng(rng)\n", " X_missing = X.copy()\n", " missing_idx = rng.random(X.shape) < missing_add\n", " X_missing[missing_idx] = np.nan\n", " \n", " if not fitted:\n", " est.fit(X_missing)\n", " \n", " X_filled = est.transform(X_missing)\n", " return np.sqrt(np.mean((X_filled[missing_idx] - X[missing_idx])**2))\n", "\n", "from sklearn.impute import SimpleImputer\n", "\n", "X, y = sklearn.datasets.load_diabetes(return_X_y=True)\n", "\n", "imp = SimpleImputer(strategy=\"mean\")\n", "\n", "rmse_obective(imp, X)" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
SimpleImputer()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "SimpleImputer()" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot.search_spaces\n", "from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal\n", "\n", "#set up an imputation search space that includes simple imputer, knn imputer, and iterative imputer (with an optimized ExtraTreesRegressor)\n", "\n", "simple_imputer = tpot.config.get_search_space(\"SimpleImputer\")\n", "knn_imputer = tpot.config.get_search_space(\"KNNImputer\")\n", "\n", "space = ConfigurationSpace({ 'initial_strategy' : Categorical('initial_strategy', \n", " ['mean', 'median', \n", " 'most_frequent', 'constant']),\n", " 'n_nearest_features' : Integer('n_nearest_features', \n", " bounds=(1, X.shape[1])),\n", " 'imputation_order' : Categorical('imputation_order', \n", " ['ascending', 'descending', \n", " 'roman', 'arabic', 'random']),\n", "})\n", "\n", "# This optimizes both the iterative imputer parameters and the ExtraTreesRegressor parameters\n", "iterative_imputer_sp = tpot.search_spaces.pipelines.WrapperPipeline(\n", " method = sklearn.impute.IterativeImputer,\n", " space = space,\n", " estimator_search_space = tpot.config.get_search_space(\"ExtraTreesRegressor\"),\n", ")\n", "#this is equivalent to\n", "# iterative_imputer_sp = tpot.config.get_search_space(\"IterativeImputer_learned_estimators\")\n", "\n", "imputation_search_space = tpot.search_spaces.pipelines.ChoicePipeline(\n", " search_spaces = [simple_imputer, knn_imputer, iterative_imputer_sp],\n", ")\n", "imputation_search_space.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/ketrong/Desktop/tpotvalidation/tpot/tpot/tpot_estimator/estimator.py:535: UserWarning: Labels are not encoded as ints from 0 to N. For compatibility with some classifiers such as sklearn, TPOT has encoded y with the sklearn LabelEncoder. When using pipelines outside the main TPOT estimator class, you can encode the labels with est.label_encoder_\n", " warnings.warn(\"Labels are not encoded as ints from 0 to N. For compatibility with some classifiers such as sklearn, TPOT has encoded y with the sklearn LabelEncoder. When using pipelines outside the main TPOT estimator class, you can encode the labels with est.label_encoder_\")\n", "Generation: : 1it [00:19, 19.42s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 1\n", "Best rmse score: 0.03494378757292814\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 2it [00:35, 17.45s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 2\n", "Best rmse score: 0.03494378757292814\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 3it [00:51, 16.76s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 3\n", "Best rmse score: 0.034787576318641794\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 3it [01:10, 23.47s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 4\n", "Best rmse score: 0.034283600126080886\n", "Early stop\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] }, { "data": { "text/html": [ "
TPOTEstimator(classification=True, early_stop=2, max_eval_time_mins=300,\n",
       "              max_time_mins=10, n_jobs=20, objective_function_names=['rmse'],\n",
       "              other_objective_functions=[functools.partial(<function rmse_obective at 0x33edfd480>, X=array([[ 0.03807591,  0.05068012,  0.06169621, ..., -0.00259226,\n",
       "         0.01990749, -0.01764613],\n",
       "       [-0.00188202, -0.04464164, -0.05147406, ..., -0.03949338,\n",
       "        -...\n",
       "        -0.04688253,  0.01549073],\n",
       "       [-0.04547248, -0.04464164,  0.03906215, ...,  0.02655962,\n",
       "         0.04452873, -0.02593034],\n",
       "       [-0.04547248, -0.04464164, -0.0730303 , ..., -0.03949338,\n",
       "        -0.00422151,  0.00306441]]), missing_add=0.2)],\n",
       "              other_objective_functions_weights=[-1], scorers=[],\n",
       "              scorers_weights=[],\n",
       "              search_space=<tpot.search_spaces.pipelines.choice.ChoicePipeline object at 0x36ff3e770>,\n",
       "              verbose=3)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "TPOTEstimator(classification=True, early_stop=2, max_eval_time_mins=300,\n", " max_time_mins=10, n_jobs=20, objective_function_names=['rmse'],\n", " other_objective_functions=[functools.partial(, X=array([[ 0.03807591, 0.05068012, 0.06169621, ..., -0.00259226,\n", " 0.01990749, -0.01764613],\n", " [-0.00188202, -0.04464164, -0.05147406, ..., -0.03949338,\n", " -...\n", " -0.04688253, 0.01549073],\n", " [-0.04547248, -0.04464164, 0.03906215, ..., 0.02655962,\n", " 0.04452873, -0.02593034],\n", " [-0.04547248, -0.04464164, -0.0730303 , ..., -0.03949338,\n", " -0.00422151, 0.00306441]]), missing_add=0.2)],\n", " other_objective_functions_weights=[-1], scorers=[],\n", " scorers_weights=[],\n", " search_space=,\n", " verbose=3)" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from functools import partial\n", "\n", "final_objective = partial(rmse_obective, X=X, missing_add=.2)\n", "\n", "est = tpot.TPOTEstimator(\n", " scorers = [],\n", " scorers_weights = [],\n", " other_objective_functions = [final_objective],\n", " other_objective_functions_weights = [-1],\n", " objective_function_names = [\"rmse\"],\n", " classification = True,\n", " search_space = imputation_search_space,\n", " max_time_mins=10,\n", " max_eval_time_mins = 60*5,\n", " verbose = 3,\n", " early_stop = 2,\n", " n_jobs=20,\n", ")\n", "\n", "est.fit(X, y=y)" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "final rmse score 0.02796745384428642\n" ] } ], "source": [ "# score the model\n", "rmse_score = final_objective(est, fitted=True)\n", "print(\"final rmse score\", rmse_score)" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
IterativeImputer(estimator=ExtraTreesRegressor(criterion='friedman_mse',\n",
       "                                               max_features=0.6404215718013,\n",
       "                                               min_samples_leaf=2,\n",
       "                                               min_samples_split=10, n_jobs=1),\n",
       "                 imputation_order='arabic', n_nearest_features=9)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "IterativeImputer(estimator=ExtraTreesRegressor(criterion='friedman_mse',\n", " max_features=0.6404215718013,\n", " min_samples_leaf=2,\n", " min_samples_split=10, n_jobs=1),\n", " imputation_order='arabic', n_nearest_features=9)" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est.fitted_pipeline_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Combined Search Space Example" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 25it [10:00, 24.01s/it]\n" ] }, { "data": { "text/html": [ "
TPOTEstimator(classification=True, cv=5, max_eval_time_mins=300,\n",
       "              max_time_mins=10, n_jobs=20, scorers=['roc_auc'],\n",
       "              scorers_weights=[1],\n",
       "              search_space=<tpot.search_spaces.pipelines.sequential.SequentialPipeline object at 0x35798c880>,\n",
       "              verbose=2)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "TPOTEstimator(classification=True, cv=5, max_eval_time_mins=300,\n", " max_time_mins=10, n_jobs=20, scorers=['roc_auc'],\n", " scorers_weights=[1],\n", " search_space=,\n", " verbose=2)" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from tpot.search_spaces.pipelines import *\n", "from tpot.config import get_search_space\n", "\n", "selectors = get_search_space([\"selectors_classification\", \"Passthrough\"])\n", "estimators = get_search_space([\"classifiers\"])\n", "\n", "\n", "# this allows us to wrap the classifiers in the EstimatorTransformer\n", "# this is necessary so that classifiers can be used inside of sklearn pipelines\n", "wrapped_estimators = WrapperPipeline(tpot.builtin_modules.EstimatorTransformer, {}, estimators)\n", "\n", "scalers = get_search_space([\"scalers\",\"Passthrough\"])\n", "\n", "transformers_layer =UnionPipeline([\n", " ChoicePipeline([\n", " DynamicUnionPipeline(get_search_space([\"transformers\"])),\n", " get_search_space(\"SkipTransformer\"),\n", " ]),\n", " get_search_space(\"Passthrough\")\n", " ]\n", " )\n", "\n", "inner_estimators_layer = UnionPipeline([\n", " ChoicePipeline([\n", " DynamicUnionPipeline(wrapped_estimators),\n", " get_search_space(\"SkipTransformer\"),\n", " ]),\n", " get_search_space(\"Passthrough\")]\n", " )\n", "\n", "\n", "search_space = SequentialPipeline(search_spaces=[\n", " scalers,\n", " selectors, \n", " transformers_layer,\n", " inner_estimators_layer,\n", " estimators,\n", " ])\n", "\n", "est = tpot.TPOTEstimator(\n", " scorers = [\"roc_auc\"],\n", " scorers_weights = [1],\n", " classification = True,\n", " cv = 5,\n", " search_space = search_space,\n", " max_time_mins=10,\n", " max_eval_time_mins = 60*5,\n", " verbose = 2,\n", " n_jobs=20,\n", ")\n", "\n", "est.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),\n",
       "                ('selectfwe', SelectFwe(alpha=0.0004883916878)),\n",
       "                ('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                 FeatureUnion(transformer_list=[('powertransformer',\n",
       "                                                                                 PowerTransformer())])),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('featureunion-2',\n",
       "                 FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                 FeatureUnion(transformer_list=[('estimatortransformer',\n",
       "                                                                                 EstimatorTransformer(estimator=LinearDiscriminantAnalysis(shrinkage=0.5801392483719,\n",
       "                                                                                                                                           solver='lsqr')))])),\n",
       "                                                ('passthrough',\n",
       "                                                 Passthrough())])),\n",
       "                ('mlpclassifier',\n",
       "                 MLPClassifier(activation='identity', alpha=0.0310773820788,\n",
       "                               hidden_layer_sizes=[54, 54, 54],\n",
       "                               learning_rate_init=0.0017701050157,\n",
       "                               n_iter_no_change=32))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),\n", " ('selectfwe', SelectFwe(alpha=0.0004883916878)),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('powertransformer',\n", " PowerTransformer())])),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('featureunion-2',\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('estimatortransformer',\n", " EstimatorTransformer(estimator=LinearDiscriminantAnalysis(shrinkage=0.5801392483719,\n", " solver='lsqr')))])),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('mlpclassifier',\n", " MLPClassifier(activation='identity', alpha=0.0310773820788,\n", " hidden_layer_sizes=[54, 54, 54],\n", " learning_rate_init=0.0017701050157,\n", " n_iter_no_change=32))])" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est.fitted_pipeline_" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/3_Feature_Set_Selector.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Genetic Feature Selection nodes in TPOT\n", "\n", "TPOT can use evolutionary algorithms to optimize feature selection simultaneously with pipeline optimization. It includes two node search spaces with different feature selection strategies: FSSNode and GeneticFeatureSelectorNode. \n", "\n", "1. FSSNode - (Feature Set Selector) This node is useful if you have a list of predefined feature sets you want to select from. Each FeatureSetSelector Node will select a single group of features to be passed to the next step in the pipeline. Note that FSSNode does not create its own subset of features and does not mix/match multiple predefined feature sets.\n", "\n", "2. GeneticFeatureSelectorNode—Whereas the FSSNode selects from a predefined list of subsets of features, this node uses evolutionary algorithms to optimize a novel subset of features from scratch. This is useful where there is no predefined grouping of features. \n", "\n", "This tutorial focuses on FSSNode. See Tutorial 5 for more information on GeneticFeatureSelectorNode.\n", "\n", "It may also be beneficial to pair these search spaces with a secondary objective function to minimize complexity. That would encourage TPOT to try to produce the simplest pipeline with the fewest number of features.\n", "\n", "tpot.objectives.number_of_nodes_objective - This can be used as an other_objective_function that counts the number of nodes.\n", "\n", "tpot.objectives.complexity_scorer - This is a scorer that tries to count the total number of learned parameters (number of coefficients, number of nodes in decision trees, etc.).\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Feature Set Selector\n", "\n", "The FeatureSetSelector is a subclass of sklearn.feature_selection.SelectorMixin that simply returns the manually specified columns. The parameter sel_subset specifies the name or index of the column that it selects. The transform function then simply indexes and returns the selected columns. You can also optionally name the group with the name parameter, though this is only for note keeping and does is not used by the class.\n", "\n", "\n", " sel_subset: list or int\n", " If X is a dataframe, items in sel_subset list must correspond to column names\n", " If X is a numpy array, items in sel_subset list must correspond to column indexes\n", " int: index of a single column\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "original DataFrame\n", " a b c d e f\n", "0 0 1 2 3 4 5\n", "1 0 1 2 3 4 5\n", "2 0 1 2 3 4 5\n", "3 0 1 2 3 4 5\n", "4 0 1 2 3 4 5\n", "5 0 1 2 3 4 5\n", "6 0 1 2 3 4 5\n", "7 0 1 2 3 4 5\n", "8 0 1 2 3 4 5\n", "9 0 1 2 3 4 5\n", "Transformed Data\n", "[[0 1 2]\n", " [0 1 2]\n", " [0 1 2]\n", " [0 1 2]\n", " [0 1 2]\n", " [0 1 2]\n", " [0 1 2]\n", " [0 1 2]\n", " [0 1 2]\n", " [0 1 2]]\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] } ], "source": [ "import tpot\n", "import pandas as pd\n", "import numpy as np\n", "#make a dataframe with columns a,b,c,d,e,f\n", "\n", "#numpy array where columns are 1,2,3,4,5,6\n", "data = np.repeat([np.arange(6)],10,0)\n", "\n", "df = pd.DataFrame(data,columns=['a','b','c','d','e','f'])\n", "fss = tpot.builtin_modules.FeatureSetSelector(name='test',sel_subset=['a','b','c'])\n", "\n", "print(\"original DataFrame\")\n", "print(df)\n", "print(\"Transformed Data\")\n", "print(fss.fit_transform(df))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# FSSNode\n", "\n", "The `FSSNode` is a node search space that simply selects one feature set from a list of feature sets. This works identically to the EstimatorNode, but provides a easier interface for defining the feature sets.\n", "\n", "Note that the FSS is only well defined when used as the first step in a pipeline. This is because downstream nodes will receive different transformations of the data such that the original indexes no longer correspond to the same columns in the transformed data.\n", "\n", "The `FSSNode` takes in a single parameter `subsets` which defines the groups of features. There are four ways of defining the subsets. \n", "\n", " subsets : str or list, default=None\n", " Sets the subsets that the FeatureSetSeletor will select from if set as an option in one of the configuration dictionaries. \n", " Features are defined by column names if using a Pandas data frame, or ints corresponding to indexes if using numpy arrays.\n", " - str : If a string, it is assumed to be a path to a csv file with the subsets. \n", " The first column is assumed to be the name of the subset and the remaining columns are the features in the subset.\n", " - list or np.ndarray : If a list or np.ndarray, it is assumed to be a list of subsets (i.e a list of lists).\n", " - dict : A dictionary where keys are the names of the subsets and the values are the list of features.\n", " - int : If an int, it is assumed to be the number of subsets to generate. Each subset will contain one feature.\n", " - None : If None, each column will be treated as a subset. One column will be selected per subset.\n", "\n", "\n", "Lets say you want to have three groups of features, each with three columns each. The following examples are equivalent:\n", "\n", "### str\n", "\n", "sel_subsets=simple_fss.csv\n", "\n", "\n", " \\# simple_fss.csv\n", " group_one, 1,2,3\n", " group_two, 4,5,6\n", " group_three, 7,8,9\n", "\n", "\n", "### dict\n", "\n", "\n", "sel_subsets = { \"group_one\" : [1,2,3],\n", " \"group_two\" : [4,5,6],\n", " \"group_three\" : [7,8,9],\n", " }\n", "\n", "\n", "### list\n", "\n", "\n", "sel_subsets = [[1,2,3],\n", "[4,5,6],\n", "[7,8,9]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Examples\n", "\n", "For these examples, we create a dummy dataset where the first six columns are informative and the rest are uninformative." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
abcdefghijkl
02.315814-3.427720-1.314654-1.508737-0.3009320.0894480.3276510.3290220.8574950.7342380.2572180.652350
1-0.191001-1.3969220.149488-1.730145-0.3949320.5197120.8077620.5098230.8761590.0028060.4498280.671350
20.661264-0.9817370.7038790.730321-2.7504050.3965810.3803020.5326040.8771290.6109190.7801080.625689
31.4459360.3542370.7790401.2880142.3971330.1863240.5441910.4654190.5885350.9195750.5134600.831546
4-0.989027-1.824787-1.4482341.5464421.6437750.1679750.1882380.0241490.5448780.8345030.8778690.278330
\n", "
" ], "text/plain": [ " a b c d e f g \\\n", "0 2.315814 -3.427720 -1.314654 -1.508737 -0.300932 0.089448 0.327651 \n", "1 -0.191001 -1.396922 0.149488 -1.730145 -0.394932 0.519712 0.807762 \n", "2 0.661264 -0.981737 0.703879 0.730321 -2.750405 0.396581 0.380302 \n", "3 1.445936 0.354237 0.779040 1.288014 2.397133 0.186324 0.544191 \n", "4 -0.989027 -1.824787 -1.448234 1.546442 1.643775 0.167975 0.188238 \n", "\n", " h i j k l \n", "0 0.329022 0.857495 0.734238 0.257218 0.652350 \n", "1 0.509823 0.876159 0.002806 0.449828 0.671350 \n", "2 0.532604 0.877129 0.610919 0.780108 0.625689 \n", "3 0.465419 0.588535 0.919575 0.513460 0.831546 \n", "4 0.024149 0.544878 0.834503 0.877869 0.278330 " ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "import numpy as np\n", "import pandas as pd\n", "import tpot\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "import numpy as np\n", "from tpot.search_spaces.nodes import *\n", "from tpot.search_spaces.pipelines import *\n", "from tpot.config import get_search_space\n", "\n", "\n", "X, y = sklearn.datasets.make_classification(n_samples=1000, n_features=6, n_informative=6, n_redundant=0, n_repeated=0, n_classes=2, n_clusters_per_class=2, weights=None, flip_y=0.01, class_sep=1.0, hypercube=True, shift=0.0, scale=1.0, shuffle=True, random_state=None)\n", "X = np.hstack([X, np.random.rand(X.shape[0],6)]) #add six uninformative features\n", "X = pd.DataFrame(X, columns=['a','b','c','d','e','f','g','h','i', 'j', 'k', 'l']) # a, b ,c the rest are uninformative\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "X.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets say that either based on prior knowledge or interest, we know that the features can be grouped as follows" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "subsets = { \"group_one\" : ['a','b','c',],\n", " \"group_two\" : ['d','e','f'],\n", " \"group_three\" : ['g','h','i'],\n", " \"group_four\" : ['j','k','l'],\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can create an FSSNode that will select from this subset. Each node in a pipeline only selects one subset." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "fss_search_space = FSSNode(subsets=subsets)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we randomly sample from this search space, we can see that we get a single selector that selects one of the predefined sets. In this case, it selects groups two, which includes ['d', 'e', 'f']. (A random seed was set in the generate function so that the same group would be selected when rerunning the notebook.)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureSetSelector(name='group_two', sel_subset=['d', 'e', 'f'])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureSetSelector(name='group_two', sel_subset=['d', 'e', 'f'])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fss_selector = fss_search_space.generate(rng=1).export_pipeline()\n", "fss_selector" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
def
1621.315442-1.0392580.194516
168-1.908995-0.953551-1.430472
2140.1811621.022858-2.289700
8952.825765-1.2055201.147791
154-2.3004811.0231730.449162
............
32-1.7930622.209649-0.045031
829-0.2214091.6887500.069356
1760.141471-1.8802941.984397
124-0.3599521.1417582.019301
350.1713120.0793320.178522
\n", "

750 rows × 3 columns

\n", "
" ], "text/plain": [ " d e f\n", "162 1.315442 -1.039258 0.194516\n", "168 -1.908995 -0.953551 -1.430472\n", "214 0.181162 1.022858 -2.289700\n", "895 2.825765 -1.205520 1.147791\n", "154 -2.300481 1.023173 0.449162\n", ".. ... ... ...\n", "32 -1.793062 2.209649 -0.045031\n", "829 -0.221409 1.688750 0.069356\n", "176 0.141471 -1.880294 1.984397\n", "124 -0.359952 1.141758 2.019301\n", "35 0.171312 0.079332 0.178522\n", "\n", "[750 rows x 3 columns]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fss_selector.set_output(transform=\"pandas\") #by default sklearn selectors return numpy arrays. this will make it return pandas dataframes\n", "fss_selector.fit(X_train)\n", "fss_selector.transform(X_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Under the hood, mutation will randomly select another feature set and crossover will swap the feature sets selected by two individuals" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureSetSelector(name='group_two', sel_subset=['d', 'e', 'f'])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureSetSelector(name='group_two', sel_subset=['d', 'e', 'f'])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ind1 = fss_search_space.generate(rng=1)\n", "ind1.export_pipeline()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureSetSelector(name='group_four', sel_subset=['j', 'k', 'l'])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureSetSelector(name='group_four', sel_subset=['j', 'k', 'l'])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ind1.mutate()\n", "ind1.export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now use this when defining our pipelines. \n", "For this first example, we will construct a simple linear pipeline where the first step is a feature set selector, and the second is a classifier" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/ketrong/Desktop/tpotvalidation/tpot/tpot/tpot_estimator/estimator.py:456: UserWarning: Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\n", " warnings.warn(\"Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\")\n", "Generation: 100%|██████████| 5/5 [00:36<00:00, 7.26s/it]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.926166142557652\n" ] } ], "source": [ "\n", "classification_search_space = get_search_space([\"RandomForestClassifier\"])\n", "fss_and_classifier_search_space = SequentialPipeline([fss_search_space, classification_search_space])\n", "\n", "\n", "est = tpot.TPOTEstimator(generations=5, \n", " scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", " n_jobs=32,\n", " classification=True,\n", " search_space = fss_and_classifier_search_space,\n", " verbose=1,\n", " )\n", "\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovr')\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featuresetselector',\n",
       "                 FeatureSetSelector(name='group_one',\n",
       "                                    sel_subset=['a', 'b', 'c'])),\n",
       "                ('randomforestclassifier',\n",
       "                 RandomForestClassifier(max_features=0.30141491087,\n",
       "                                        min_samples_leaf=4,\n",
       "                                        min_samples_split=17, n_estimators=128,\n",
       "                                        n_jobs=1))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featuresetselector',\n", " FeatureSetSelector(name='group_one',\n", " sel_subset=['a', 'b', 'c'])),\n", " ('randomforestclassifier',\n", " RandomForestClassifier(max_features=0.30141491087,\n", " min_samples_leaf=4,\n", " min_samples_split=17, n_estimators=128,\n", " n_jobs=1))])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est.fitted_pipeline_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With this setup TPOT is able to identify one of the subsets used, but the performance is not optimal. In this case we happen to know that multiple feature sets are required. If we want to include multiple features in our pipelines, we will have to modify our search space. There are three options for this.\n", "\n", "1. UnionPipeline - This allows you to have a fixed number of feature sets selected. If you use a UnionPipeline with two FSSNodes, you will always select two feature sets that are simply concatenated together.\n", "2. DynamicUnionPipeline - This space allows multiple FSSNodes to be selected. Unlike UnionPipeline you don't have to specify the number of selected sets, TPOT will identify the number of sets that are optimal. Additionally, with DynamicUnionPipeline, the same feature set cannot be selected twice. Note that while DynamicUnionPipeline can select multiple feature sets, it never mixes two feature sets together.\n", "3. GraphSearchPipeline - When set as the leave_search_space, GraphSearchPipeline can also select multiple FSSNodes which act as an input to the rest of the pipeline." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### UnionPipeline + FSSNode example" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "union_fss_space = UnionPipeline([fss_search_space, fss_search_space])" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('featuresetselector-1',\n",
       "                                FeatureSetSelector(name='group_two',\n",
       "                                                   sel_subset=['d', 'e', 'f'])),\n",
       "                               ('featuresetselector-2',\n",
       "                                FeatureSetSelector(name='group_three',\n",
       "                                                   sel_subset=['g', 'h',\n",
       "                                                               'i']))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('featuresetselector-1',\n", " FeatureSetSelector(name='group_two',\n", " sel_subset=['d', 'e', 'f'])),\n", " ('featuresetselector-2',\n", " FeatureSetSelector(name='group_three',\n", " sel_subset=['g', 'h',\n", " 'i']))])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# this union search space will always select exactly two fss_search_space\n", "selector1 = union_fss_space.generate(rng=1).export_pipeline()\n", "selector1" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
defghi
1621.315442-1.0392580.1945160.7511750.4113400.824754
168-1.908995-0.953551-1.4304720.0726970.8757660.953255
2140.1811621.022858-2.2897000.1352220.3958470.232638
8952.825765-1.2055201.1477910.9259050.4866450.710991
154-2.3004811.0231730.4491620.6451610.1316570.863514
.....................
32-1.7930622.209649-0.0450310.5029470.9946030.280062
829-0.2214091.6887500.0693560.3280660.1023810.492280
1760.141471-1.8802941.9843970.3655500.4658590.974601
124-0.3599521.1417582.0193010.3293800.7186470.365507
350.1713120.0793320.1785220.2157590.5462790.662928
\n", "

750 rows × 6 columns

\n", "
" ], "text/plain": [ " d e f g h i\n", "162 1.315442 -1.039258 0.194516 0.751175 0.411340 0.824754\n", "168 -1.908995 -0.953551 -1.430472 0.072697 0.875766 0.953255\n", "214 0.181162 1.022858 -2.289700 0.135222 0.395847 0.232638\n", "895 2.825765 -1.205520 1.147791 0.925905 0.486645 0.710991\n", "154 -2.300481 1.023173 0.449162 0.645161 0.131657 0.863514\n", ".. ... ... ... ... ... ...\n", "32 -1.793062 2.209649 -0.045031 0.502947 0.994603 0.280062\n", "829 -0.221409 1.688750 0.069356 0.328066 0.102381 0.492280\n", "176 0.141471 -1.880294 1.984397 0.365550 0.465859 0.974601\n", "124 -0.359952 1.141758 2.019301 0.329380 0.718647 0.365507\n", "35 0.171312 0.079332 0.178522 0.215759 0.546279 0.662928\n", "\n", "[750 rows x 6 columns]" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selector1.set_output(transform=\"pandas\") \n", "selector1.fit(X_train)\n", "selector1.transform(X_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### DynamicUnionPipeline + FSSNode example\n", "The dynamic union pipeline may select a variable number of feature sets." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('featuresetselector',\n",
       "                                FeatureSetSelector(name='group_three',\n",
       "                                                   sel_subset=['g', 'h',\n",
       "                                                               'i']))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('featuresetselector',\n", " FeatureSetSelector(name='group_three',\n", " sel_subset=['g', 'h',\n", " 'i']))])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dynamic_fss_space = DynamicUnionPipeline(fss_search_space)\n", "dynamic_fss_space.generate(rng=1).export_pipeline()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
FeatureUnion(transformer_list=[('featuresetselector-1',\n",
       "                                FeatureSetSelector(name='group_one',\n",
       "                                                   sel_subset=['a', 'b', 'c'])),\n",
       "                               ('featuresetselector-2',\n",
       "                                FeatureSetSelector(name='group_four',\n",
       "                                                   sel_subset=['j', 'k',\n",
       "                                                               'l']))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "FeatureUnion(transformer_list=[('featuresetselector-1',\n", " FeatureSetSelector(name='group_one',\n", " sel_subset=['a', 'b', 'c'])),\n", " ('featuresetselector-2',\n", " FeatureSetSelector(name='group_four',\n", " sel_subset=['j', 'k',\n", " 'l']))])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dynamic_fss_space.generate(rng=3).export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### GraphSearchPipeline + FSSNode example\n", "\n", "FSSNodes must be set as the leaf search space as they act as the inputs to the pipeline.\n", "\n", "Here is an example pipeline from this search space that utilizes two feature sets." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " leaf_search_space = fss_search_space,\n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " max_size = 10,\n", ")\n", "\n", "graph_search_space.generate(rng=4).export_pipeline().plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Optimize with TPOT\n", "\n", "For this example, we will optimize the DynamicUnion search space" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/ketrong/Desktop/tpotvalidation/tpot/tpot/tpot_estimator/estimator.py:456: UserWarning: Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\n", " warnings.warn(\"Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\")\n", "Generation: 100%|██████████| 5/5 [00:41<00:00, 8.33s/it]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.9838836477987423\n" ] } ], "source": [ "import tpot\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "import numpy as np\n", "\n", "\n", "final_classification_search_space = SequentialPipeline([dynamic_fss_space, classification_search_space])\n", "\n", "est = tpot.TPOTEstimator(generations=5, \n", " scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", " n_jobs=32,\n", " classification=True,\n", " search_space = final_classification_search_space,\n", " verbose=1,\n", " )\n", "\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovr')\n", "\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see that this pipeline performed slightly better and correctly identified group one and group two as the feature sets used in the generative equation." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featureunion',\n",
       "                 FeatureUnion(transformer_list=[('featuresetselector-1',\n",
       "                                                 FeatureSetSelector(name='group_two',\n",
       "                                                                    sel_subset=['d',\n",
       "                                                                                'e',\n",
       "                                                                                'f'])),\n",
       "                                                ('featuresetselector-2',\n",
       "                                                 FeatureSetSelector(name='group_one',\n",
       "                                                                    sel_subset=['a',\n",
       "                                                                                'b',\n",
       "                                                                                'c']))])),\n",
       "                ('randomforestclassifier',\n",
       "                 RandomForestClassifier(max_features=0.0530704381152,\n",
       "                                        min_samples_leaf=2, min_samples_split=5,\n",
       "                                        n_estimators=128, n_jobs=1))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featureunion',\n", " FeatureUnion(transformer_list=[('featuresetselector-1',\n", " FeatureSetSelector(name='group_two',\n", " sel_subset=['d',\n", " 'e',\n", " 'f'])),\n", " ('featuresetselector-2',\n", " FeatureSetSelector(name='group_one',\n", " sel_subset=['a',\n", " 'b',\n", " 'c']))])),\n", " ('randomforestclassifier',\n", " RandomForestClassifier(max_features=0.0530704381152,\n", " min_samples_leaf=2, min_samples_split=5,\n", " n_estimators=128, n_jobs=1))])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est.fitted_pipeline_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Combining with existing search spaces\n", "\n", "As with all search spaces, FSSNode can be combined with any other search space. \n", "\n", "You can also pair this with the existing prebuilt templates, for example:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featuresetselector',\n",
       "                 FeatureSetSelector(name='group_two',\n",
       "                                    sel_subset=['d', 'e', 'f'])),\n",
       "                ('pipeline',\n",
       "                 Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),\n",
       "                                 ('rfe',\n",
       "                                  RFE(estimator=ExtraTreesClassifier(max_features=0.0390676831531,\n",
       "                                                                     min_samples_leaf=8,\n",
       "                                                                     min_samples_split=14,\n",
       "                                                                     n_jobs=1),\n",
       "                                      step=0.753983388654)),\n",
       "                                 ('featureunion-1',\n",
       "                                  FeatureUnion(transformer_lis...\n",
       "                                  FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                                  SkipTransformer()),\n",
       "                                                                 ('passthrough',\n",
       "                                                                  Passthrough())])),\n",
       "                                 ('histgradientboostingclassifier',\n",
       "                                  HistGradientBoostingClassifier(early_stopping=True,\n",
       "                                                                 l2_regularization=9.1304e-09,\n",
       "                                                                 learning_rate=0.0036310282582,\n",
       "                                                                 max_features=0.238877814721,\n",
       "                                                                 max_leaf_nodes=1696,\n",
       "                                                                 min_samples_leaf=59,\n",
       "                                                                 n_iter_no_change=14,\n",
       "                                                                 tol=0.0001,\n",
       "                                                                 validation_fraction=None))]))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featuresetselector',\n", " FeatureSetSelector(name='group_two',\n", " sel_subset=['d', 'e', 'f'])),\n", " ('pipeline',\n", " Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),\n", " ('rfe',\n", " RFE(estimator=ExtraTreesClassifier(max_features=0.0390676831531,\n", " min_samples_leaf=8,\n", " min_samples_split=14,\n", " n_jobs=1),\n", " step=0.753983388654)),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_lis...\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('histgradientboostingclassifier',\n", " HistGradientBoostingClassifier(early_stopping=True,\n", " l2_regularization=9.1304e-09,\n", " learning_rate=0.0036310282582,\n", " max_features=0.238877814721,\n", " max_leaf_nodes=1696,\n", " min_samples_leaf=59,\n", " n_iter_no_change=14,\n", " tol=0.0001,\n", " validation_fraction=None))]))])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "linear_search_space = tpot.config.template_search_spaces.get_template_search_spaces(\"linear\", classification=True)\n", "fss_and_linear_search_space = SequentialPipeline([fss_search_space, linear_search_space])\n", "\n", "# est = tpot.TPOTEstimator( \n", "# population_size=32,\n", "# generations=10, \n", "# scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", "# scorers_weights=[1.0, -1.0],\n", "# other_objective_functions=[number_of_selected_features],\n", "# other_objective_functions_weights = [-1],\n", "# objective_function_names = [\"Number of selected features\"],\n", "\n", "# n_jobs=32,\n", "# classification=True,\n", "# search_space = fss_and_linear_search_space,\n", "# verbose=2,\n", "# )\n", "\n", "fss_and_linear_search_space.generate(rng=1).export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Getting Fancy\n", "\n", "If you want to get fancy, you can combine more search spaces in order to set up unique preprocessing pipelines per feature set. Here's an example:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "dynamic_transformers = DynamicUnionPipeline(get_search_space(\"all_transformers\"), max_estimators=4)\n", "dynamic_transformers_with_passthrough = tpot.search_spaces.pipelines.UnionPipeline([\n", " dynamic_transformers,\n", " tpot.config.get_search_space(\"Passthrough\")],\n", " )\n", "multi_step_engineering = DynamicLinearPipeline(dynamic_transformers_with_passthrough, max_length=4)\n", "fss_engineering_search_space = SequentialPipeline([fss_search_space, multi_step_engineering])\n", "union_fss_engineering_search_space = DynamicUnionPipeline(fss_engineering_search_space)\n", "\n", "final_fancy_search_space = SequentialPipeline([union_fss_engineering_search_space, classification_search_space])" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featureunion',\n",
       "                 FeatureUnion(transformer_list=[('pipeline-1',\n",
       "                                                 Pipeline(steps=[('featuresetselector',\n",
       "                                                                  FeatureSetSelector(name='group_one',\n",
       "                                                                                     sel_subset=['a',\n",
       "                                                                                                 'b',\n",
       "                                                                                                 'c'])),\n",
       "                                                                 ('pipeline',\n",
       "                                                                  Pipeline(steps=[('featureunion',\n",
       "                                                                                   FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                                                                                   FeatureUnion(transformer_list=[('zerocount',\n",
       "                                                                                                                                                   ZeroCount())])),\n",
       "                                                                                                                  ('passthrough',\n",
       "                                                                                                                   Passth...\n",
       "                                                                                                                                                   KBinsDiscretizer(encode='onehot-dense',\n",
       "                                                                                                                                                                    n_bins=11)),\n",
       "                                                                                                                                                  ('rbfsampler',\n",
       "                                                                                                                                                   RBFSampler(gamma=0.0925899621466,\n",
       "                                                                                                                                                              n_components=17)),\n",
       "                                                                                                                                                  ('maxabsscaler',\n",
       "                                                                                                                                                   MaxAbsScaler())])),\n",
       "                                                                                                                  ('passthrough',\n",
       "                                                                                                                   Passthrough())]))]))]))])),\n",
       "                ('randomforestclassifier',\n",
       "                 RandomForestClassifier(bootstrap=False,\n",
       "                                        class_weight='balanced',\n",
       "                                        max_features=0.8205760841606,\n",
       "                                        min_samples_leaf=16,\n",
       "                                        min_samples_split=11, n_estimators=128,\n",
       "                                        n_jobs=1))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featureunion',\n", " FeatureUnion(transformer_list=[('pipeline-1',\n", " Pipeline(steps=[('featuresetselector',\n", " FeatureSetSelector(name='group_one',\n", " sel_subset=['a',\n", " 'b',\n", " 'c'])),\n", " ('pipeline',\n", " Pipeline(steps=[('featureunion',\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('zerocount',\n", " ZeroCount())])),\n", " ('passthrough',\n", " Passth...\n", " KBinsDiscretizer(encode='onehot-dense',\n", " n_bins=11)),\n", " ('rbfsampler',\n", " RBFSampler(gamma=0.0925899621466,\n", " n_components=17)),\n", " ('maxabsscaler',\n", " MaxAbsScaler())])),\n", " ('passthrough',\n", " Passthrough())]))]))]))])),\n", " ('randomforestclassifier',\n", " RandomForestClassifier(bootstrap=False,\n", " class_weight='balanced',\n", " max_features=0.8205760841606,\n", " min_samples_leaf=16,\n", " min_samples_split=11, n_estimators=128,\n", " n_jobs=1))])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "final_fancy_search_space.generate(rng=3).export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Other examples" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## dictionary" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
def
1621.315442-1.0392580.194516
168-1.908995-0.953551-1.430472
2140.1811621.022858-2.289700
8952.825765-1.2055201.147791
154-2.3004811.0231730.449162
............
32-1.7930622.209649-0.045031
829-0.2214091.6887500.069356
1760.141471-1.8802941.984397
124-0.3599521.1417582.019301
350.1713120.0793320.178522
\n", "

750 rows × 3 columns

\n", "
" ], "text/plain": [ " d e f\n", "162 1.315442 -1.039258 0.194516\n", "168 -1.908995 -0.953551 -1.430472\n", "214 0.181162 1.022858 -2.289700\n", "895 2.825765 -1.205520 1.147791\n", "154 -2.300481 1.023173 0.449162\n", ".. ... ... ...\n", "32 -1.793062 2.209649 -0.045031\n", "829 -0.221409 1.688750 0.069356\n", "176 0.141471 -1.880294 1.984397\n", "124 -0.359952 1.141758 2.019301\n", "35 0.171312 0.079332 0.178522\n", "\n", "[750 rows x 3 columns]" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "import pandas as pd\n", "import numpy as np\n", "from sklearn.linear_model import LogisticRegression\n", "import sklearn\n", "\n", "subsets = { \"group_one\" : ['a','b','c'],\n", " \"group_two\" : ['d','e','f'],\n", " \"group_three\" : ['g','h','i'],\n", " }\n", "\n", "fss_search_space = tpot.search_spaces.nodes.FSSNode(subsets=subsets)\n", "\n", "selector = fss_search_space.generate(rng=1).export_pipeline()\n", "selector.set_output(transform=\"pandas\")\n", "selector.fit(X_train)\n", "selector.transform(X_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## list" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
def
1621.315442-1.0392580.194516
168-1.908995-0.953551-1.430472
2140.1811621.022858-2.289700
8952.825765-1.2055201.147791
154-2.3004811.0231730.449162
............
32-1.7930622.209649-0.045031
829-0.2214091.6887500.069356
1760.141471-1.8802941.984397
124-0.3599521.1417582.019301
350.1713120.0793320.178522
\n", "

750 rows × 3 columns

\n", "
" ], "text/plain": [ " d e f\n", "162 1.315442 -1.039258 0.194516\n", "168 -1.908995 -0.953551 -1.430472\n", "214 0.181162 1.022858 -2.289700\n", "895 2.825765 -1.205520 1.147791\n", "154 -2.300481 1.023173 0.449162\n", ".. ... ... ...\n", "32 -1.793062 2.209649 -0.045031\n", "829 -0.221409 1.688750 0.069356\n", "176 0.141471 -1.880294 1.984397\n", "124 -0.359952 1.141758 2.019301\n", "35 0.171312 0.079332 0.178522\n", "\n", "[750 rows x 3 columns]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "import pandas as pd\n", "import numpy as np\n", "from sklearn.linear_model import LogisticRegression\n", "import sklearn\n", "\n", "subsets = [['a','b','c'],['d','e','f'],['g','h','i']]\n", "\n", "fss_search_space = tpot.search_spaces.nodes.FSSNode(subsets=subsets)\n", "\n", "selector = fss_search_space.generate(rng=1).export_pipeline()\n", "selector.set_output(transform=\"pandas\")\n", "selector.fit(X_train)\n", "selector.transform(X_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## csv file\n", "\n", "note: watch for spaces in the csv file!" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
def
1621.315442-1.0392580.194516
168-1.908995-0.953551-1.430472
2140.1811621.022858-2.289700
8952.825765-1.2055201.147791
154-2.3004811.0231730.449162
............
32-1.7930622.209649-0.045031
829-0.2214091.6887500.069356
1760.141471-1.8802941.984397
124-0.3599521.1417582.019301
350.1713120.0793320.178522
\n", "

750 rows × 3 columns

\n", "
" ], "text/plain": [ " d e f\n", "162 1.315442 -1.039258 0.194516\n", "168 -1.908995 -0.953551 -1.430472\n", "214 0.181162 1.022858 -2.289700\n", "895 2.825765 -1.205520 1.147791\n", "154 -2.300481 1.023173 0.449162\n", ".. ... ... ...\n", "32 -1.793062 2.209649 -0.045031\n", "829 -0.221409 1.688750 0.069356\n", "176 0.141471 -1.880294 1.984397\n", "124 -0.359952 1.141758 2.019301\n", "35 0.171312 0.079332 0.178522\n", "\n", "[750 rows x 3 columns]" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "import pandas as pd\n", "import numpy as np\n", "from sklearn.linear_model import LogisticRegression\n", "import sklearn\n", "\n", "subsets = 'simple_fss.csv'\n", "'''\n", "# simple_fss.csv\n", "one,a,b,c\n", "two,d,e,f\n", "three,g,h,i\n", "'''\n", "\n", "fss_search_space = tpot.search_spaces.nodes.FSSNode(subsets=subsets)\n", "\n", "selector = fss_search_space.generate(rng=1).export_pipeline()\n", "selector.set_output(transform=\"pandas\")\n", "selector.fit(X_train)\n", "selector.transform(X_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All of the above is the same when using numpy data, but the column names are replaced int indexes." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[-0.31748616 2.20805859 -2.21719911 ... 0.5595234 0.80605806\n", " 0.41484993]\n", " [ 2.8673731 1.45905176 -1.11516833 ... 0.74646156 0.95635356\n", " 0.03575697]\n", " [-1.64867116 2.14478724 2.31196119 ... 0.22969172 0.72447325\n", " 0.81842014]\n", " ...\n", " [ 1.17772695 0.7188885 -0.52548496 ... 0.99266968 0.95436462\n", " 0.57430922]\n", " [ 0.14052568 0.15042817 -0.86281564 ... 0.25379746 0.1818071\n", " 0.55993116]\n", " [ 1.37273916 -0.14898886 -0.89938251 ... 0.767549 0.66184827\n", " 0.49174333]]\n" ] } ], "source": [ "import tpot\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "import numpy as np\n", "import pandas as pd\n", "\n", "n_features = 6\n", "X, y = sklearn.datasets.make_classification(n_samples=1000, n_features=n_features, n_informative=6, n_redundant=0, n_repeated=0, n_classes=2, n_clusters_per_class=2, weights=None, flip_y=0.01, class_sep=1.0, hypercube=True, shift=0.0, scale=1.0, shuffle=True, random_state=None)\n", "X = np.hstack([X, np.random.rand(X.shape[0],3)]) #add three uninformative features\n", "\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "print(X)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-0.76235619, -1.97629642, 1.05447979],\n", " [ 2.16944118, -1.55515714, 0.67925075],\n", " [ 1.96557199, 0.13789923, 1.588271 ],\n", " ...,\n", " [ 0.78956322, 2.12535053, 0.63115798],\n", " [-0.80184984, -0.40793866, 1.3880617 ],\n", " [-1.38085267, 1.62568989, -1.42046795]])" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "import pandas as pd\n", "import numpy as np\n", "from sklearn.linear_model import LogisticRegression\n", "import sklearn\n", "\n", "subsets = { \"group_one\" : [0,1,2],\n", " \"group_two\" : [3,4,5],\n", " \"group_three\" : [6,7,8],\n", " }\n", "\n", "fss_search_space = tpot.search_spaces.nodes.FSSNode(subsets=subsets)\n", "selector = fss_search_space.generate(rng=1).export_pipeline()\n", "selector.fit(X_train)\n", "selector.transform(X_train)" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/4_Genetic_Feature_Selection.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# GeneticFeatureSelectorNode\n", "\n", "Whereas the `FSSNode` selects from a predefined list of subsets of features, the `GeneticFeatureSelectorNode` uses evolutionary algorithms to optimize a novel subset of features from scratch. This is useful where there is no predefined grouping of features. \n", "\n", "To initalize the `GeneticFeatureSelectorNode` you simply need to pass in the total number of features (i.e number of columns) in your dataset." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For these examples, we create a dummy dataset where the first six columns are informative and the rest are uninformative." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
abcdefghijkl
00.4310311.8898410.4032350.1303471.245051-3.3565870.2546120.4773690.1451580.6336070.2003730.037735
10.016308-1.035908-1.6251761.8033910.442258-0.8440520.1415070.0246590.7699760.6589900.9719870.570931
23.7694690.209185-1.3030334.0775092.9356031.2434870.0889880.3779350.0190070.9237250.7608950.316752
3-2.5832920.172831-1.531697-0.0787741.6561900.4756520.7415390.1796120.9937590.6241010.2906790.946652
4-0.8335043.209340-0.9287980.3457651.5990570.2428010.3596560.6970360.6430630.1983620.7255300.974992
\n", "
" ], "text/plain": [ " a b c d e f g \\\n", "0 0.431031 1.889841 0.403235 0.130347 1.245051 -3.356587 0.254612 \n", "1 0.016308 -1.035908 -1.625176 1.803391 0.442258 -0.844052 0.141507 \n", "2 3.769469 0.209185 -1.303033 4.077509 2.935603 1.243487 0.088988 \n", "3 -2.583292 0.172831 -1.531697 -0.078774 1.656190 0.475652 0.741539 \n", "4 -0.833504 3.209340 -0.928798 0.345765 1.599057 0.242801 0.359656 \n", "\n", " h i j k l \n", "0 0.477369 0.145158 0.633607 0.200373 0.037735 \n", "1 0.024659 0.769976 0.658990 0.971987 0.570931 \n", "2 0.377935 0.019007 0.923725 0.760895 0.316752 \n", "3 0.179612 0.993759 0.624101 0.290679 0.946652 \n", "4 0.697036 0.643063 0.198362 0.725530 0.974992 " ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tpot\n", "from tpot.search_spaces.nodes import *\n", "from tpot.search_spaces.pipelines import *\n", "import tpot\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "import numpy as np\n", "import pandas as pd\n", "import tpot\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "import numpy as np\n", "from tpot.search_spaces.nodes import *\n", "from tpot.search_spaces.pipelines import *\n", "from tpot.config import get_search_space\n", "\n", "\n", "X, y = sklearn.datasets.make_classification(n_samples=1000, n_features=6, n_informative=6, n_redundant=0, n_repeated=0, n_classes=2, n_clusters_per_class=2, weights=None, flip_y=0.01, class_sep=1.0, hypercube=True, shift=0.0, scale=1.0, shuffle=True, random_state=None)\n", "X = np.hstack([X, np.random.rand(X.shape[0],6)]) #add six uninformative features\n", "X = pd.DataFrame(X, columns=['a','b','c','d','e','f','g','h','i', 'j', 'k', 'l']) # a, b ,c the rest are uninformative\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "X.head()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "gfs_sp = GeneticFeatureSelectorNode(n_features=X.shape[1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each GeneticFeatureSelectorNode will select a new subset of features" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
bj
890.0677350.839366
897-0.1759820.050951
824-0.5031850.826335
3052.7752970.877498
7743.1439690.429360
.........
3101.4025020.506769
3332.3840900.047125
2595.2627630.500726
301.1077170.768569
7573.6065050.557151
\n", "

750 rows × 2 columns

\n", "
" ], "text/plain": [ " b j\n", "89 0.067735 0.839366\n", "897 -0.175982 0.050951\n", "824 -0.503185 0.826335\n", "305 2.775297 0.877498\n", "774 3.143969 0.429360\n", ".. ... ...\n", "310 1.402502 0.506769\n", "333 2.384090 0.047125\n", "259 5.262763 0.500726\n", "30 1.107717 0.768569\n", "757 3.606505 0.557151\n", "\n", "[750 rows x 2 columns]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selector = gfs_sp.generate().export_pipeline()\n", "selector.set_output(transform=\"pandas\") #by default sklearn selectors return numpy arrays. this will make it return pandas dataframes\n", "selector.fit(X_train, y_train)\n", "selector.transform(X_train)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
k
890.179639
8970.430166
8240.354605
3050.949369
7740.499857
......
3100.624468
3330.995309
2590.138835
300.548930
7570.643055
\n", "

750 rows × 1 columns

\n", "
" ], "text/plain": [ " k\n", "89 0.179639\n", "897 0.430166\n", "824 0.354605\n", "305 0.949369\n", "774 0.499857\n", ".. ...\n", "310 0.624468\n", "333 0.995309\n", "259 0.138835\n", "30 0.548930\n", "757 0.643055\n", "\n", "[750 rows x 1 columns]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selector = gfs_sp.generate().export_pipeline()\n", "selector.set_output(transform=\"pandas\") #by default sklearn selectors return numpy arrays. this will make it return pandas dataframes\n", "selector.fit(X_train, y_train)\n", "selector.transform(X_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mutation and crossover can add or remove subsets from the learned feature set." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "selected features: Index(['a', 'j'], dtype='object')\n" ] } ], "source": [ "selector_ind = gfs_sp.generate()\n", "selector = selector_ind.export_pipeline()\n", "selected_features = X.columns[selector.mask]\n", "\n", "print(\"selected features: \", selected_features)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "selected features: Index(['a', 'h', 'j'], dtype='object')\n" ] } ], "source": [ "selector_ind.mutate()\n", "selector = selector_ind.export_pipeline()\n", "selected_features = X.columns[selector.mask]\n", "print(\"selected features: \", selected_features)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Training" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/ketrong/Desktop/tpotvalidation/tpot/tpot/tpot_estimator/estimator.py:456: UserWarning: Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\n", " warnings.warn(\"Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\")\n", "Generation: 100%|██████████| 10/10 [00:53<00:00, 5.33s/it]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.9458645653148825\n" ] } ], "source": [ "import tpot\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "import numpy as np\n", "from tpot.search_spaces.nodes import *\n", "from tpot.search_spaces.pipelines import *\n", "\n", "gfs_sp = GeneticFeatureSelectorNode(n_features=X.shape[1])\n", "classifiers_sp = get_search_space('RandomForestClassifier')\n", "final_classification_search_space = SequentialPipeline([gfs_sp, classifiers_sp])\n", "\n", "est = tpot.TPOTEstimator( population_size=32,\n", " generations=10, \n", " scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", " n_jobs=32,\n", " classification=True,\n", " search_space = final_classification_search_space,\n", " verbose=1,\n", " )\n", "\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovo')\n", "\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('maskselector',\n",
       "                 MaskSelector(mask=array([ True,  True,  True,  True,  True,  True, False, False,  True,\n",
       "       False,  True,  True]))),\n",
       "                ('randomforestclassifier',\n",
       "                 RandomForestClassifier(class_weight='balanced',\n",
       "                                        criterion='entropy',\n",
       "                                        max_features=0.487196536075,\n",
       "                                        min_samples_leaf=5, min_samples_split=3,\n",
       "                                        n_estimators=128, n_jobs=1))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('maskselector',\n", " MaskSelector(mask=array([ True, True, True, True, True, True, False, False, True,\n", " False, True, True]))),\n", " ('randomforestclassifier',\n", " RandomForestClassifier(class_weight='balanced',\n", " criterion='entropy',\n", " max_features=0.487196536075,\n", " min_samples_leaf=5, min_samples_split=3,\n", " n_estimators=128, n_jobs=1))])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est.fitted_pipeline_" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "selected features: Index(['a', 'b', 'c', 'd', 'e', 'f', 'i', 'k', 'l'], dtype='object')\n" ] } ], "source": [ "selected_features = X.columns[est.fitted_pipeline_.steps[0][1].mask]\n", "print(\"selected features: \", selected_features)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Custom objective function to minimize number of selected features\n", "We can create a custom objective function that returns the number of features selected per pipeline. The `other_objective_functions` parameter is for objective functions that do not require fitted pipelines and do not require cross validation. Since we know that the selector instance gets its features from its parameters, not through fitting, we can create an objective for the `other_objective_functions` parameter. \n", "We set the weights to -1 because we would like to minimize the number of features selected. We also give it a name so that we can more easily access it in the `evaluated_individuals` dataframe." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/ketrong/Desktop/tpotvalidation/tpot/tpot/tpot_estimator/estimator.py:456: UserWarning: Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\n", " warnings.warn(\"Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.\")\n", "Generation: 100%|██████████| 10/10 [00:47<00:00, 4.73s/it]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.9414440386956244\n" ] } ], "source": [ "def number_of_selected_features(est):\n", " return sum(est.steps[0][1].mask)\n", "\n", "gfs_sp = GeneticFeatureSelectorNode(n_features=X.shape[1])\n", "classifiers_sp = get_search_space('RandomForestClassifier')\n", "final_classification_search_space = SequentialPipeline([gfs_sp, classifiers_sp])\n", "\n", "est = tpot.TPOTEstimator( \n", " population_size=32,\n", " generations=10, \n", " scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", " scorers_weights=[1.0, -1.0],\n", " other_objective_functions=[number_of_selected_features],\n", " other_objective_functions_weights = [-1],\n", " objective_function_names = [\"Number of selected features\"],\n", "\n", " n_jobs=32,\n", " classification=True,\n", " search_space = final_classification_search_space,\n", " verbose=2,\n", " )\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovo')\n", "\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "selected features: Index(['b', 'c', 'd', 'e', 'f', 'g'], dtype='object')\n" ] } ], "source": [ "selected_features = X.columns[est.fitted_pipeline_.steps[0][1].mask]\n", "print(\"selected features: \", selected_features)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "image/png": "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40RERaNF97hzdl2jRo3kwAHXWdFw8uRJGTFihPTr10+NGz344INFOmdcXJwaX8DzOp+nMGPtvoDpXa85BWzA8WvfHFD1OuHa40Sle7rr6NGjfX0ZZYJen+wikpSUpMap88uuQ1d2r169pEmTJvLpp5/KmjVrVOb3I4884vE5Me506aWXqueywXg5jl11b2uzlreLJDRAYES9aWuPE5HesA4FejXPnz/v60sps3zePY5gixYzusSPHTsmTzzxhGr1Dh48OM9jEUjRnY7HIlAj6xwJXatWrVLJaNWrV3fZ6saNgH3LHXMnkQleoUIF1dUOyDwfMmSItGvXTk0pw5QvjE3bssl1E5/qPsjFp+q1ljfXHieii00QZiKfhi3tP/74QwVoJKLddNNNaq71Dz/8IBUrVszzWGR0T5kyRRYvXuzwy8QcbIwnDxo0yOU5tmzZIm3atFFftgCN/584cWLuY26++WZ54YUXVBnmYyOof/nlly7nfeugoK03w4OLvvJOceLa40QXD8NGB08lqeWJD55OKpFhpPT0dLn//vvVtKTQ0FDp2rWrbN68WX777Tfp0aOHegz2WECL+4477nBoVGEGDxpDGFbEwln20Dq/66671Gd7VFSUamghcdcGj8dn7zvvvCN169ZV5yYNW9qYb30xrrzySpfltoCc33gLJrcXZNSoUerLFO7maYvv8/Jcrj1uv4SpDdceJ9IncROBFw2j+fPnq17N5557Ti00ham5KL/xxhvV2hgIvLa5x4DHo0H0448/qlk9COhdunTJ/cxGowqPX7FihZo18+abb6qhzn379qlAD+gRxTkw9FkcS36WRj5vaZOH/CyZ2LeZdHWap43jJ/o1wxo8ouPa486bhnDtcSJ9EjcxJIjps1jOGUORGH58++23VbDFQlO24IpWOFrT9lNWMfsHw5tY/ArraGCo0ZYntH79etm0aZOaBotyPAY9m0g8/uSTTxy6xLHpEhpheD7SsKVNngkLDJQnl/4qrWvFqMVU7DcMmbZitwrouuHa40TFl7jpjb8bJAFj4Q+0kG2wZDTyfHbv3u12QSjnIItkYtuynegGR24Rhj/tpaamqnPaoGXvamiU/sGgbajUrGxZu+e0+nJlzDUXVmTSDdceJyqdiZvO+0FgzNu2WQoCNoK4q10Q0dq2iYiIKIErNRuDtqGS09zv4qXjLl9E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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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/6eOeQUSDQfV69ffj95DDRXN7uvUFxSBXwiIv8dO9MWIeqsUiPSlgH7PzrGZ3c8sO587gHzCBwARbu7cuT79c/bRRx/59u7d69u+fbvvtdde85UvX94XExPj+/XXX32//PKLr0iRIr7x48cHPfa7777zFS1aNGj5RRdd5PY3c+bMdM/Vp08fX8mSJYOWffPNN277W2+9NWj5fffd55Z//PHHqctq1qzpli1evDho208++cQtP++883zJycmpy3v27OkrVKiQr0uXLkHbt23b1u0r0OHDh9O1t3Pnzr6zzz47aJm/DcuWLUtdtmfPHl/x4sV9Q4cOTV02atQot93ChQvT7ffkyZPu57x583yFCxf2ffbZZ0Hr9d7psStWrPCdil5nxYoV3Ws+cuRI6vK3337bPVbPn/Yz/vLLL31Zufrqq31RUVG+n3/+OXXZzp07faVLl/Z16NAh3T47duyY+nrk3nvvdcfK/v37g44J3fzef/9999j33nsv6LmbNGkStF1GdIzqsaNHj87w+NK6cePGBS1v3ry5r0WLFqn39X5ru/nz5wdtp+Mqo+Vp6bm13YYNG1x7tmzZ4ps1a5Y7BipVquQ7dOiQ7/jx475jx44FPe6PP/5w6/v165e6TI/VvmJjY91xFOiyyy7zNW7c2Hf06NHUZXqv27Vr56tXr17qsn/9619uH/o7CKT96bPs1KmT78SJE6nLn3nmGbf9nDlzMn2d/s84o1vg353+RgL/fnJybGb3M8vscweQfzAsEEC+oW+29Q2xhqWpx0k9DOo10rfmmkeiXhb1WukbbP+tcuXKVq9evXRDnfTNu761zg4VzRANLQukHix55513gpar50XfcmdEQ+4Cv4nXN+HqCVHPUSAt13BGDUf0C5y35f+m/qKLLrLNmze7+4E0ZFC9O35639Q7pG391LPQtGlT9017Wv5iBxo2pR6GBg0aBL2v/uFlad/XQF999ZWbS6XeNQ0X87v88svd/tK+b9mhHgQVZlDPiIY6+lWpUsVuuOEG18OWthqeeloCizfofdF+tm7dmumxpuFp8+fPT12mwinr1q1zPZ2nS/OfAqlNgZ+N3ncN1fvrX/8a9L6r51HHfWbveyB95vrsdUyqJ0xDO/W+a2iceuT8c6b0t/P777+74009uF9//XW6fWm+ln/om2h7FcfQ35x6vfxtVK+wjv+N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27VrV4bba5vBgwdb+/bt7bzzzstwG83RUhr139RzBgAAAAARNecqp6ZNm2b16tWzBg0aWFRUlA0cOND69u3rerwyouGD33//vb322mun3OeIESNc75b/tn379jx8BQAAAADyo5CGq/j4eCtSpIj99ttvQct1v3Llyhk+pkKFCrZo0SJXnGLr1q32448/WqlSpezss89Ot62C19tvv22ffPKJVa9e/ZTtKF68uBs/GXgDAAAAgIgJV+p5atGihS1ZsiRoGJ/ut23bNtPHat5VtWrV7Pjx45aQkGDdunVLXacaHQpWb7zxhn388cdWu3btPH0dAAAAAFA01A1QGfY+ffpYy5YtrXXr1jZ16lTXK6WhftK7d28XojQvSlatWmU7duywZs2auZ9jxoxxgeyBBx4IGgr4yiuv2JtvvumudeWfv6X5VCp+AQAAAAD5Llxdf/31rlT6qFGjXAhSaNJFgf1FLrZt2xY0n+ro0aPuWlebN292wwG7du1q8+bNs7i4uNRtnnvuOffz4osvDnquuXPn2s0333zGXhsAAACAgiPk17kKR1znCgAAAEBEXecKAAAAAPILwhUAAAAAeIBwBQAAAAAeIFwBAAAAgAcIVwAAAADgAcIVAAAAAIQ6XCUnJ9uGDRvs+PHjXrQFAAAAAApWuDp8+LDdcsstVqJECWvUqJG70K8MGjTIJk2a5HUbAQAAACB/hqsRI0bYt99+a59++qlFR0enLu/YsaMtWLDAy/YBAAAAQEQompsHLVq0yIWov/zlL1aoUKHU5erF+vnnn71sHwAAAADk356rvXv3WsWKFdMtP3ToUFDYAgAAAICCIlfhqmXLlvbOO++k3vcHqhdeeMHatm3rXesAAAAAID8PC5wwYYJ16dLF1q9f7yoFTps2zf3++eef29KlS71vJQAAAADkx56rCy64wBW0ULBq3LixffDBB26Y4MqVK61FixbetxIAAAAA8lvPVUpKit1+++328MMP2+zZs/OmVQAAAACQ33uuihUrZgkJCXnTGgAAAAAoSMMCr776aleOHQAAAABwGgUt6tWrZ+PGjbMVK1a4OVYlS5YMWn/33XfnZrcAAAAAELEK+Xw+X04fVLt27VPvsFAh27x5s0WypKQkK1OmjB04cMBiY2ND3RwAAAAAEZANctVztWXLlty2DQAAAADypVzNuQqkjq9cdH4BAAAAQL6S63D1j3/8w13jKiYmxt2aNGli8+bN87Z1AAAAABAhcjUscMqUKe46VwMHDrT27du7ZcuXL7cBAwZYYmKi3XvvvV63EwAAAADyZ0GLsWPHWu/evYOWv/zyyzZmzJiIn5NFQQsAAAAAOc0GuRoWuGvXLmvXrl265VqmdQAAAABQ0OQqXNWtW9def/31dMsXLFjgroEFAAAAAAVNruZcaUjg9ddfb8uWLUudc6ULCi9ZsiTD0AUAAAAA+V2ueq569Ohhq1atsvj4eFu0aJG76ffVq1db9+7dvW8lAAAAAOTHghb5HQUtAAAAAJyRghbvvvuuvf/+++mWa9l7772Xm10CAAAAQETLVbgaPny4nThxIt1ydYJpHQAAAAAUNLkKVxs3brSGDRumW96gQQPbtGmTF+0CAAAAgPwfrjTmcPPmzemWK1iVLFnSi3YBAAAAQP4PV926dbPBgwfbzz//HBSshg4daldddZWX7QMAAACA/BuuHnvsMddDpWGAtWvXdrdzzz3Xypcvb0888YT3rQQAAACA/HgRYQ0L/Pzzz+3DDz+0b7/91mJiYqxJkybWoUMH71sIAAAAAAXpOlf79++3uLg4yw+4zhUAAACAM3Kdq8mTJ9uCBQtS71933XVuSGC1atVcTxYAAAAAFDS5ClczZ860GjVquN81NFA3XTy4S5cudv/993vdRgAAAADIn3Oudu/enRqu3n77bddz1alTJ6tVq5a1adPG6zYCAAAAQP7suSpbtqxt377d/b548WLr2LGj+13Tt06cOOFtCwEAAAAgv/ZcXXPNNXbDDTdYvXr1bN++fW44oKxdu9bq1q3rdRsBAAAAIH+Gq6eeesoNAVTvla55VapUKbd8165dduedd3rdRgAAAAAoOKXYM3L55ZfbCy+8YFWqVLFIQil2AAAAAGekFHt2LVu2zI4cOZKXTwEAAAAAYSFPwxUAAAAAFBSEKwAAAADwAOEKAAAAADxAuAIAAAAADxCuAAAAACDcw9XIkSOtXLlyefkUAAAAABC54WrixIk2Z86cdMu1bPLkyan3R4wYYXFxcafXQgAAAADIr+Fq1qxZ1qBBg3TLGzVqZDNnzvSiXQAAAAAQUYrm5kG7d++2KlWqpFteoUIF27VrlxftApALBw4nW+LBZEs6mmKxMcUsvmSUlSkRFepmAQAAFAi5Clc1atSwFStWWO3atYOWa1nVqlW9ahuAHNi5/4gNS1hnn21MTF3WoV68TerRxKrGxYS0bQAAAAVBrsJV//79bfDgwZaSkmKXXnqpW7ZkyRJ74IEHbOjQoV63EUA2eqzSBitZtjHRhiess+k9m9ODBQAAEI7h6v7777d9+/bZnXfeacnJyW5ZdHS0DRs2zBWxAHBmaShg2mAVGLC0nnAFAAAQhuGqUKFCrirgww8/bD/88IPFxMRYvXr1rHjx4t63EECWNMcqM39msR4AAAAhCld+pUqVslatWnnQDACnIza6WKbrS2exHgAAACEKV5dcconrvTqVjz/++HTaBCCH4ktFueIVGgKYlpZrPQAAAMIwXDVr1izovgpbfPPNN/b9999bnz59vGobgGzSfCpVBVTxisCApWA1uUcT5lsBAACEa7h66qmnMlw+ZswYO3jw4Om2CUAuqNy6qgKqeIXmWGkooHqsCFbIDNdGAwDAO4V8Pp/Pq51t2rTJWrdubb///rtFsqSkJCtTpowdOHDAYmNjQ90cAMgTXBsNAABvs0Fh89DKlStdSXYAQGRfG03rAQDAGRgWeM011wTdV+fXrl277KuvvnLl2QEA4Y1rowEAECbhSt1igQoXLmz169e3cePGWadOnbxqGwAgj3BtNAAAwiRczZ071/uWAADOGK6NBgCA9zydcwUAiKxro2WEa6MBAHAGw9WJEyfsiSeecJUBK1eubOXKlQu6AQAi49poaQMW10YDAOAMDwscO3asvfDCCzZ06FB76KGH7MEHH7RffvnFFi1aZKNGjTqN5gAAzhSujQYAQBj0XM2fP99mz57twlXRokWtZ8+eLmwpWH3xxRc53t+MGTOsVq1arox7mzZtbPXq1afcNiUlxRXOqFOnjtu+adOmtnjx4tPaJwAUVApSdSqWsmZnlXU/CVYAAJzhcLV7925r3Lix+71UqVLuglpyxRVX2DvvvJOjfS1YsMCGDBlio0ePtq+//tqFpc6dO9uePXsy3F49ZbNmzbLp06fb+vXrbcCAAda9e3dbu3ZtrvcJ5Be6NtHPew7a2m1/2M97D3KtIgAAgHAPV9WrV3fXtRL1IH3wwQfu9y+//NKKFy+eo31NmTLF+vfvb3379rWGDRvazJkzrUSJEjZnzpwMt583b56NHDnSunbtameffbbdcccd7vcnn3wy1/sE8oOd+4/YwFfX2mVTllr3Zz+3y55caoNeXeuWAwAAIEzDlXqKlixZ4n4fNGiQu3BwvXr1rHfv3tavX79s7yc5OdnWrFljHTt2/F+DChd291euXJnhY44dO+aG+gWKiYmx5cuXn9Y+k5KSgm5AJFEP1bCEdekuCquLwQ5PWEcPFgAAQLgWtJg0aVLq79dff73VrFnTPv/8cxewrrzyymzvJzEx0VUerFSpUtBy3f/xxx8zfIyG96lnqkOHDq7XTCFv4cKFbj+53efEiRNdkQ4gUqkgQdpgFRiwtJ65NAAAABFwnau//OUvbo5T2mB1+eWXpw4f9Mq0adNciGvQoIFFRUXZwIED3fA/9U7l1ogRI9y8Mf9t+/btnrYZyGtJR1MyXa9KcAAAAIjgiwgvW7bMjhw59XyP+Ph4K1KkiP32229By3Vf18/KSIUKFVzJ90OHDtnWrVtdb5SKamj+VW73qXlisbGxQTcgkpQqnnkndMks1gMAACDMw1VW1PPUokWL1PlbcvLkSXe/bdu2mT5W866qVatmx48ft4SEBOvWrdtp7xOIVFFFClv7uuUzXKflWg8AAIC8FfKvszWcsE+fPtayZUtr3bq1TZ061fVKaaifqEiGQpTmRcmqVatsx44d1qxZM/dzzJgxLjw98MAD2d4nkN/sP5JsfdvXdr+v2LQvKFhp+YEjKmhRMoQtBAAAyP9CHq5UEGPv3r3uAsS6fpZCky4K7C9IsW3btqD5VEePHnXXutq8ebMbDqgy7CrPHhcXl+19AvlNqeLFrOfsVdbvgtrWr31tO3b8pBUvWtjWbt9vd7+61v4z8IJQNxEAACDfK+Tz+Xx5tfPSpUvbt99+mzofKlKoFHuZMmVccQvmXyESqNS6rmmlyoBpdagXb9N7NqdaIAAAQB5nAyZiAPmAgtOkHk1ckAqk+5N7NCFYAQAARPqwwJEjR1q5cuXy8ikA/D9V42Ls8Wub2h+Hki3p6HGLjSlqZUtEWaXY4ItuAwAAIIyGBaq4hOYv9evXL2j5nDlz3FynYcOGWSRjWCAi0c79R2xYwrqgiwmr50o9WgpeAAAACMNhgbNmzXIX8U2rUaNGNnPmzNzsEsBpzrlKG6xEc7CGJ6xz6wEAAJC3chWuVIGvSpUqGV7gd9euXV60C0AOJB5MThesAgOW1gMAACAMw1WNGjVsxYoV6ZZrWdWqVb1oF4AcSDqakun6P7NYDwAAgBAVtOjfv78NHjzYUlJS7NJLL3XLlixZ4i7kO3ToUA+aBSAnYqOLZbq+dBbrAQAAEKJwdf/999u+ffvszjvvtOTk/3+4UXR0tCtkMWLECA+aBSAn4ktFueIVp7rOldYDAAAgjC8ifPDgQfvhhx8sJibG6tWrZ8WLF7f8gGqBiNRqgSpeERiw/Ne5qkK1QAAAgDzPBqd1natSpUqlFrbIL8EKiFQqtz69Z3NXvEJzrDQUUD1WXEAYAAAgjAtanDx50saNG+cSXM2aNd0tLi7OHnnkEbcOQGgoSNWpWMqanVXW/SRYAQAAnDm56rl68MEH7cUXX7RJkyZZ+/bt3bLly5fbmDFj7OjRozZ+/Hiv2wkAAAAA+W/Olcqt62LBV111VdDyN9980xW52LFjh0Uy5lwBAAAAyGk2yNWwwN9//90aNGiQbrmWaR0AAAAAFDS5CldNmza1Z555Jt1yLdM6AAAAAChocjXn6vHHH7euXbvaRx99ZG3btnXLVq5cadu3b7d3333X6zYCAAAAQP7ruUpJSbGxY8e6EHXNNdfY/v373U2/b9iwwS688MK8aSkAAAAA5Keeq2LFitm6devc9a0effTRvGkVAAAAABSEOVc33nijK8UOAAAAADiNOVfHjx+3OXPmuDlXLVq0sJIlSwatnzJlSm52CwAAAAAFK1x9//33dv7557vff/rpp6B1hQoV8qZlAAAAAJDfw9Unn3zifUsAAAAAoKDNuQIAAAAABCNcAQAAAIAHCFcAAAAA4AHCFQAAAAB4gHAFAAAAAB4gXAEAAACABwhXAAAAAOABwhUAAAAAeIBwBQAAAAAeIFwBAAAAgAcIVwAAAADgAcIVAAAAAHiAcAUAAAAAHiBcAQAAAIAHCFcAAAAA4AHCFQAAAAB4gHAFAAAAAB4gXAEAAACABwhXAAAAAOABwhUAAAAAeIBwBQAAAAAeIFwBAAAAgAcIVwAAAADgAcIVAAAAAHiAcAUAAAAAHiBcAQAAAIAHCFcAAAAA4AHCFQAAAAB4gHAFAAAAAB4gXAEAAACABwhXAAAAAOABwhUAAAAAeIBwBQAAAAAeIFwBAAAAgAcIVwAAAADgAcIVAAAAAHiAcAUAAAAAHiBcAQAAAIAHCFcAAAAA4AHCFQAAAAB4gHAFAAAAAB4gXAEAAACABwhXAAAAAOABwhUAAAAAeIBwBQAAAAAeIFwBAAAAgAcIVwAAAADgAcIVAAAAAOSHcDVjxgyrVauWRUdHW5s2bWz16tWZbj916lSrX7++xcTEWI0aNezee++1o0ePpq4/ceKEPfzww1a7dm23TZ06deyRRx4xn893Bl4NAAAAgIKqaCiffMGCBTZkyBCbOXOmC1YKTp07d7YNGzZYxYoV023/yiuv2PDhw23OnDnWrl07++mnn+zmm2+2QoUK2ZQpU9w2kydPtueee85efvlla9SokX311VfWt29fK1OmjN19990heJUAAAAACoJCvhB26ShQtWrVyp555hl3/+TJk643atCgQS5EpTVw4ED74YcfbMmSJanLhg4daqtWrbLly5e7+1dccYVVqlTJXnzxxdRtevTo4Xqx/vnPf2arXUlJSS6MHThwwGJjYz14pQAAAAAiUU6yQciGBSYnJ9uaNWusY8eO/2tM4cLu/sqVKzN8jHqr9Bj/0MHNmzfbu+++a127dg3aRuFLvVry7bffuuDVpUuXU7bl2LFj7k0LvAEAAABARAwLTExMdPOj1MsUSPd//PHHDB9zww03uMddcMEFbg7V8ePHbcCAATZy5MjUbdTjpXDUoEEDK1KkiHuO8ePHW69evU7ZlokTJ9rYsWM9fHUAAAAACpqQF7TIiU8//dQmTJhgzz77rH399de2cOFCe+edd1zBCr/XX3/d5s+f7+ZnaRvNvXriiSfcz1MZMWKE6+bz37Zv336GXhEAAACA/CJkPVfx8fGuZ+m3334LWq77lStXzvAxqgJ400032a233uruN27c2A4dOmS33XabPfjgg25Y4f333+96r/7+97+nbrN161bXO9WnT58M91u8eHF3AwAAAICI67mKioqyFi1aBBWnUEEL3W/btm2Gjzl8+LALUIEU0MRfl+NU22jfAAAAAJAvS7GrDLt6k1q2bGmtW7d2pdjVE6XS6dK7d2+rVq2a63WSK6+80pVcb968uas0uGnTJtebpeX+kKXfNcfqrLPOcqXY165d6x7Tr1+/UL5UAAAAAPlcSMPV9ddfb3v37rVRo0bZ7t27rVmzZrZ48eLUIhfbtm0L6oV66KGH3DWt9HPHjh1WoUKF1DDlN336dBe47rzzTtuzZ49VrVrVbr/9dvccAAAAAJAvr3MVrrjOFQAAAICIuc4VAAAAAOQnhCsAAAAA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X9SuXduFtl69eoXkNQIAAAAoGEJ6natwpYqBcXFxrmogxS0AAACAgivp/10Dd//+/e6yTWHbcxWu/vzzT/eT610BAAAA8GeErMIVPVcZUGGNnTt3WunSpd0wxHBIyvSiIbs4ZpBTHDPIKY4Z5BTHDCL5mFFcUrCqWrVquuJ6adFzlQG9adWrV7dwwvW3kFMcM8gpjhnkFMcMcopjBpF6zGTVYxWRFxEGAAAAgHBFuAIAAAAADxCuwlzx4sVt9OjR7ieQHRwzyCmOGeQUxwxyimMGBeWYoaAFAAAAAHiAnisAAAAA8ADhCgAAAAA8QLgCAAAAAA8QrgAAAADAA4SrMLZs2TK78sor3dWgCxUqZIsWLQp1kxDGJk6caK1atbLSpUtbxYoV7eqrr7YNGzaEulkIY88995w1adIk9QKNbdu2tffeey/UzUKEmDRpkvt/0+DBg0PdFISxMWPGuOMk8NagQYNQNwthbMeOHXbjjTda+fLlLSYmxho3bmxfffWVRQrCVRg7dOiQNW3a1GbMmBHqpiACLF261O666y774osv7MMPP7SUlBTr1KmTO46AjFSvXt2dIK9Zs8b9j+vSSy+1bt262X//+99QNw1h7ssvv7RZs2a5cA5kpVGjRrZr167U2/Lly0PdJISpP/74w9q3b2/FihVzX/atX7/ennzySStbtqxFiqKhbgBOrUuXLu4GZMfixYuD7r/00kuuB0snzh06dAhZuxC+1DMeaPz48a43SwFdJ0NARg4ePGi9evWy2bNn26OPPhrq5iACFC1a1CpXrhzqZiACTJ482WrUqGFz585NXVa7du2Qtimn6LkC8qkDBw64n+XKlQt1UxABTpw4Ya+99prr6dTwQOBU1EN++eWXW8eOHUPdFESIjRs3uikOZ599tgvm27ZtC3WTEKbeeusta9mypV177bXuC+LmzZu7L3IiCT1XQD508uRJNw9CXevnnXdeqJuDMPbdd9+5MHX06FErVaqUvfHGG9awYcNQNwthSgH866+/dsMCgexo06aNG0lRv359NyRw7NixduGFF9r333/v5ggDgTZv3uxGUAwZMsRGjhzp/q25++67LSoqyvr06WORgHAF5NNvlvU/Lsa1Iys64fnmm29cT+e///1v9z8vzd8jYCGt7du32z333OPmdEZHR4e6OYgQgdMbNEdPYatmzZr2+uuv2y233BLStiE8vxxu2bKlTZgwwd1Xz5XOZ2bOnBkx4YphgUA+M3DgQHv77bftk08+cQULgMzo28C6detaixYtXMVJFdGZNm1aqJuFMKT5m3v27LHzzz/fzaHRTUH86aefdr9raCmQlbi4ODvnnHNs06ZNoW4KwlCVKlXSfbl37rnnRtRQUnqugHzC5/PZoEGD3LCuTz/9NOImgCJ8vjU8duxYqJuBMHTZZZe5YaSB+vbt68pqDxs2zIoUKRKytiGyCqL8/PPPdtNNN4W6KQhD7du3T3cZmZ9++sn1dkYKwlWY/wMU+M3Oli1b3PAdFSg466yzQto2hOdQwFdeecXefPNNN4599+7dbnmZMmXcdSKAtEaMGOGG7Ojfkz///NMdPwrm77//fqibhjCkf1fSzuEsWbKkuxYNcztxKvfdd5+rTKqT4507d9ro0aNdEO/Zs2eom4YwdO+991q7du3csMDrrrvOVq9ebc8//7y7RQrCVRjTdWcuueSS1Pua3Ccac6rJoUAgTQCViy++OGi5ypnefPPNIWoVwpmGePXu3dtNMlcI13wIBau//vWvoW4agHzi119/dUFq3759VqFCBbvgggvc5R70O5BWq1at3Agcffk3btw4Nwpn6tSprspkpCjk01giAAAAAMBpoaAFAAAAAHiAcAUAAAAAHiBcAQAAAIAHCFcAAAAA4AHCFQAAAAB4gHAFAAAAAB4gXAEAAACABwhXAAAAAOABwhUAIJ1ffvnFChUqZN98842Fix9//NH+8pe/WHR0tDVr1qxAvOactO3w4cPWo0cPi42Nddvu37//jLYRAEC4AoCwdPPNN7sT5EmTJgUtX7RokVteEI0ePdpKlixpGzZssCVLlli4O9Nh7eWXX7bPPvvMPv/8c9u1a5eVKVPGk/1efPHFNnjwYE/2BQD5HeEKAMKUemgmT55sf/zxh+UXycnJuX7szz//bBdccIHVrFnTypcv72m78gO9P+eee66dd955Vrly5bAL4afz2QNApCBcAUCY6tixoztJnjhx4im3GTNmTLohclOnTrVatWoF9YJdffXVNmHCBKtUqZLFxcXZuHHj7Pjx43b//fdbuXLlrHr16jZ37twMh+K1a9fOBT2dtC9dujRo/ffff29dunSxUqVKuX3fdNNNlpiYGNTrMXDgQNfzER8fb507d87wdZw8edK1Se0oXry4e02LFy9OXa+gsGbNGreNftfrzsi///1va9y4scXExLgApvfw0KFDqetfeOEFF0D0eho0aGDPPvvsKd/b7Lw+tfuxxx6zunXrunafddZZNn78eLeudu3a7mfz5s1dm/VeZLcdq1evdo/T+pYtW9ratWszbaf2/eSTT9qyZcuCnuvYsWN23333WbVq1VyvX5s2bezTTz9Nfdy+ffusZ8+ebn2JEiXce/fqq68GHTv6zKdNm+b2q5t65F566SV3HGXWq+o/NvVa9V7otYiGK956661WoUIFN4Tx0ksvtW+//Tb1cfr9kksusdKlS7v1LVq0sK+++irT1w8A4YJwBQBhqkiRIi4QTZ8+3X799dfT2tfHH39sO3fudCffU6ZMcUPsrrjiCitbtqytWrXKBgwYYLfffnu651H4Gjp0qDu5b9u2rV155ZXuhNx/kqwTY4UAnfwqDP3222923XXXpRuuFhUVZStWrLCZM2dm2D6dvCscPPHEE7Zu3ToXwq666irbuHGjW69hbo0aNXJt0e8KDGlpuYJCv3797IcffnAh4pprrjGfz+fWz58/30aNGuXCj9brvX344Ydd+zKSndc3YsQIN3RT+1m/fr298sorLoT5A5J89NFHrm0LFy7MVjsOHjzoPpuGDRu6QKmQktHrDaR99+/f331Ggc+lYLty5Up77bXX3Pt67bXX2t/+9rfU9/Xo0aMuvLzzzjsuSN52220uQPrbrs9F+9S+tV/datSoYdm1adMmS0hIcO3xD49UG/bs2WPvvfeee33nn3++XXbZZfb777+79b169XIh+8svv3Trhw8fbsWKFcv2cwJASPkAAGGnT58+vm7durnf//KXv/j69evnfn/jjTeUFFK3Gz16tK9p06ZBj33qqad8NWvWDNqX7p84cSJ1Wf369X0XXnhh6v3jx4/7SpYs6Xv11Vfd/S1btrjnmTRpUuo2KSkpvurVq/smT57s7j/yyCO+Tp06BT339u3b3eM2bNjg7l900UW+5s2bZ/l6q1at6hs/fnzQslatWvnuvPPO1Pt6nXq9p7JmzRr33L/88kuG6+vUqeN75ZVXgpbpNbRt2zboNa9duzZbry8pKclXvHhx3+zZszN8vrT7y247Zs2a5StfvrzvyJEjqeufe+65DPcV6J577nHvt9/WrVt9RYoU8e3YsSNou8suu8w3YsSIU+7n8ssv9w0dOjT1vvapfQeaO3eur0yZMkHLMjo2ixUr5tuzZ0/qss8++8wXGxvrO3r0aLr3RK9bSpcu7XvppZdO2T4ACGdFQxvtAABZ0bwr9aBk1XuRGfX6FC78v8EK6l3RML/AXjINo1OPQiD1WvgVLVrUDVFTb4t/+NYnn3zihsxlNP/nnHPOcb+rZyQzSUlJrletffv2Qct1P3C4WFaaNm3qekA0tE09X506dbL/+7//c71zGhqoNt1yyy2uF8ZPQyNPVfghq9enni0Nu9NzZld22qH3t0mTJqnD6NJ+Dtn13Xff2YkTJ1I/Bz+12T9nTevVc/b666/bjh073LwordcQQS9ofpyG/wW+p+qZSztn7siRI+59kSFDhrhhg/PmzXPDOtXTVadOHU/aAwB5jXAFAGGuQ4cOLixoCJrmwARSYPIPe/NLSUlJt4+0w6o0NyajZZpDlF06SdYwQYW/tKpUqZL6u+b6nAkKiB9++KGrlvfBBx+44ZQPPvigG/boDwuzZ892847SPi43r2/z5s05bqP2mdN25JaeS/vU0Lq0+/YHxscff9wN/dM8PYVSfVaaH5dV8YnsHndpP3u1Se9d4LwvP/8cLg2DvOGGG9xQRQ0d1BBWDWvs3r17Dl49AIQG4QoAIoDm9ag4QP369YOWq1dg9+7d7kTXX0zAy9LfX3zxhQt3/t4VnahrHo9orozm06h4hnq1cktFC6pWrermZF100UWpy3W/devWOdqX3gP1eOmmeU3qOXnjjTdcb4ieQ4FIc3qyI6vXV69ePVc4Q2Xh1dOSluaZ+XuHAnsMs2qHCl2o10bzofy9V/occkpzxfTc6o288MILM9xG73G3bt3sxhtvdPcVrn/66Sc33yvwdQS+Bv9x9+eff7qeOH+Ays5xp/dUx6vez8CiK2mpt023e++9182jU7EVwhWASEBBCwCIAOpV0Mn4008/HbRcVeH27t3rKtZpWNWMGTPct/1e0f4UTlQ18K677nJl4VUwQnRfRQh08qviA3r+999/3/r27ZvuZDwrKpyhHqIFCxa461ipiIFO1u+5555s70M9VBripuIT27Ztc0UU9N4orMjYsWNd5UW9hwoQGjank3YV+MhIVq9PwWfYsGH2wAMP2D/+8Q+3XiHoxRdfdI+vWLGiC1/+QhgHDhzIVjvUa6OQqGGDKpLx7rvvukIfOaVwomOmd+/e7r3YsmWLK1Sh51avkD8g+nv7NBxRRU3U1kAKQXpvVSVQlRIVwNTrpt7AkSNHutetQh6qIJgVDfPTEEdVr1Tvovap51YPoz43DQ9UeFfP1tatW13403vv/wwBIOyFetIXACDzghaBBRKioqKCigb4ix3UqFHDFaTo3bu3KwyRtqBF2n1lVKRAj1ExDP9z6XlUeKF169bueRs2bOj7+OOPgx7z008/+bp37+6Li4vzxcTE+Bo0aOAbPHiw7+TJk6d8noyo2MaYMWN81apVc0UQVLzivffeC9omq4IW69ev93Xu3NlXoUIFV2jinHPO8U2fPj1om/nz5/uaNWvmXk/ZsmV9HTp08C1cuDDoNQcWjcjq9andjz76qHvv1O6zzjrLN2HChNTHq9iFPpvChQsHFZvIrB2ycuVK93q1XtslJCTkuKCFJCcn+0aNGuWrVauWa1+VKlXc61m3bp1bv2/fPndslCpVylexYkXfQw895I6hwONFxTtUVEWvX23Q++QvYFG3bl23/IorrvA9//zzWRZbERUCGTRokCtiojbp/enVq5dv27ZtvmPHjvn+/ve/u2V67dpm4MCBQcU9ACCcFdJ/Qh3wAAAAACDSMSwQAAAAADxAuAIAAAAADxCuAAAAAMADhCsAAAAA8ADhCgAAAAA8QLgCAAAAAA8QrgAAAADAA4QrAAAAAPAA4QoAAAAAPEC4AgAAAAAPEK4AAAAAwE7f/wdcDPcsgq50CgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "\n", "df = est.evaluated_individuals\n", "col1 = \"Number of selected features\"\n", "col2 = \"roc_auc_score\"\n", "\n", "# Multiple orange dots show because the pareto front in this case is actually 3D along the auroc score, number of features, and complexity.\n", "\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(5,5))\n", "sns.scatterplot(df[df['Pareto_Front']!=1], x=col1, y=col2, label='other', ax=ax)\n", "sns.scatterplot(df[df['Pareto_Front']==1], x=col1, y=col2, label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of all pipelines')\n", "#log scale y\n", "ax.set_yscale('log')\n", "plt.show()\n", "\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(10,5))\n", "sns.scatterplot(df[df['Pareto_Front']==1], x=col1, y=col2, label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of only the Pareto Front')\n", "#log scale y\n", "# ax.set_yscale('log')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Other Examples\n", "\n", "As with all search spaces, GeneticFeatureSelectorNode can be combined with any other search space. \n", "\n", "You can also pair this with the existing prebuilt templates, for example:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('maskselector',\n",
       "                 MaskSelector(mask=array([False, False,  True, False, False, False, False, False, False,\n",
       "        True, False, False]))),\n",
       "                ('pipeline',\n",
       "                 Pipeline(steps=[('normalizer', Normalizer(norm='l1')),\n",
       "                                 ('selectpercentile',\n",
       "                                  SelectPercentile(percentile=74.2561844719571)),\n",
       "                                 ('featureunion-1',\n",
       "                                  FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                                  FeatureUnion(transformer_list=[('binarizer',\n",
       "                                                                                                  Binarizer(threshold=0.0935770250992))])),\n",
       "                                                                 ('passthrough',\n",
       "                                                                  Passthrough())])),\n",
       "                                 ('featureunion-2',\n",
       "                                  FeatureUnion(transformer_list=[('skiptransformer',\n",
       "                                                                  SkipTransformer()),\n",
       "                                                                 ('passthrough',\n",
       "                                                                  Passthrough())])),\n",
       "                                 ('adaboostclassifier',\n",
       "                                  AdaBoostClassifier(algorithm='SAMME',\n",
       "                                                     learning_rate=0.9665397922726,\n",
       "                                                     n_estimators=320))]))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('maskselector',\n", " MaskSelector(mask=array([False, False, True, False, False, False, False, False, False,\n", " True, False, False]))),\n", " ('pipeline',\n", " Pipeline(steps=[('normalizer', Normalizer(norm='l1')),\n", " ('selectpercentile',\n", " SelectPercentile(percentile=74.2561844719571)),\n", " ('featureunion-1',\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('binarizer',\n", " Binarizer(threshold=0.0935770250992))])),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('featureunion-2',\n", " FeatureUnion(transformer_list=[('skiptransformer',\n", " SkipTransformer()),\n", " ('passthrough',\n", " Passthrough())])),\n", " ('adaboostclassifier',\n", " AdaBoostClassifier(algorithm='SAMME',\n", " learning_rate=0.9665397922726,\n", " n_estimators=320))]))])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "linear_search_space = tpot.config.template_search_spaces.get_template_search_spaces(\"linear\", classification=True)\n", "gfs_and_linear_search_space = SequentialPipeline([gfs_sp, linear_search_space])\n", "\n", "# est = tpot.TPOTEstimator( \n", "# population_size=32,\n", "# generations=10, \n", "# scorers=[\"roc_auc_ovr\", tpot.objectives.complexity_scorer],\n", "# scorers_weights=[1.0, -1.0],\n", "# other_objective_functions=[number_of_selected_features],\n", "# other_objective_functions_weights = [-1],\n", "# objective_function_names = [\"Number of selected features\"],\n", "\n", "# n_jobs=32,\n", "# classification=True,\n", "# search_space = gfs_and_linear_search_space,\n", "# verbose=2,\n", "# )\n", "\n", "gfs_and_linear_search_space.generate(rng=1).export_pipeline()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Getting Fancy\n", "\n", "If you want to get fancy, you can combine more search spaces in order to set up unique preprocessing pipelines per feature set. Here's an example:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featureunion',\n",
       "                 FeatureUnion(transformer_list=[('pipeline',\n",
       "                                                 Pipeline(steps=[('maskselector',\n",
       "                                                                  MaskSelector(mask=array([False,  True, False, False, False, False, False, False,  True,\n",
       "       False, False, False]))),\n",
       "                                                                 ('pipeline',\n",
       "                                                                  Pipeline(steps=[('featureunion-1',\n",
       "                                                                                   FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                                                                                   FeatureUnion(transformer_list=[('robustscaler',\n",
       "                                                                                                                                                   Robu...\n",
       "                                                                                   FeatureUnion(transformer_list=[('featureunion',\n",
       "                                                                                                                   FeatureUnion(transformer_list=[('normalizer',\n",
       "                                                                                                                                                   Normalizer(norm='l1')),\n",
       "                                                                                                                                                  ('nystroem',\n",
       "                                                                                                                                                   Nystroem(gamma=0.5186832611359,\n",
       "                                                                                                                                                            kernel='polynomial',\n",
       "                                                                                                                                                            n_components=3))])),\n",
       "                                                                                                                  ('passthrough',\n",
       "                                                                                                                   Passthrough())]))]))]))])),\n",
       "                ('sgdclassifier',\n",
       "                 SGDClassifier(alpha=0.0024802032445, eta0=0.2824117602653,\n",
       "                               l1_ratio=0.281711265998, loss='modified_huber',\n",
       "                               n_jobs=1, penalty='elasticnet'))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featureunion',\n", " FeatureUnion(transformer_list=[('pipeline',\n", " Pipeline(steps=[('maskselector',\n", " MaskSelector(mask=array([False, True, False, False, False, False, False, False, True,\n", " False, False, False]))),\n", " ('pipeline',\n", " Pipeline(steps=[('featureunion-1',\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('robustscaler',\n", " Robu...\n", " FeatureUnion(transformer_list=[('featureunion',\n", " FeatureUnion(transformer_list=[('normalizer',\n", " Normalizer(norm='l1')),\n", " ('nystroem',\n", " Nystroem(gamma=0.5186832611359,\n", " kernel='polynomial',\n", " n_components=3))])),\n", " ('passthrough',\n", " Passthrough())]))]))]))])),\n", " ('sgdclassifier',\n", " SGDClassifier(alpha=0.0024802032445, eta0=0.2824117602653,\n", " l1_ratio=0.281711265998, loss='modified_huber',\n", " n_jobs=1, penalty='elasticnet'))])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dynamic_transformers = DynamicUnionPipeline(get_search_space(\"all_transformers\"), max_estimators=4)\n", "dynamic_transformers_with_passthrough = tpot.search_spaces.pipelines.UnionPipeline([\n", " dynamic_transformers,\n", " tpot.config.get_search_space(\"Passthrough\")],\n", " )\n", "multi_step_engineering = DynamicLinearPipeline(dynamic_transformers_with_passthrough, max_length=4)\n", "gfs_engineering_search_space = SequentialPipeline([gfs_sp, multi_step_engineering])\n", "union_fss_engineering_search_space = DynamicUnionPipeline(gfs_engineering_search_space)\n", "classification_search_space = get_search_space('classifiers')\n", "\n", "final_fancy_search_space = SequentialPipeline([union_fss_engineering_search_space, classification_search_space])\n", "\n", "final_fancy_search_space.generate(rng=1).export_pipeline()" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/5_GraphPipeline.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# GraphPipeline\n", "\n", "GraphPipelines (`tpot.GraphPipeline`) work similarly to the scikit-learn Pipeline class. Rather than provide a list of steps, in GraphPipeline you provide a directed acyclic graph (`networkx.DiGraph`) of steps using networkx. In GraphPipeline, parents get their inputs from their children (i.e the leafs get the raw inputs (X,y), and the roots are the final classifiers/regressors). \n", "\n", "The label of the nodes can be anything, but must unique per instance of an sklearn estimator. Each node has an attribute called \"instance\" for the instance of the scikit-learn estimator.\n", "\n", "GraphPipeline allows for classifiers and regressors in the middle of the pipeline. In this case, GraphPipeline will will try to use the outputs of predict_proba, decision_function, or predict in that order. If cross_val_predict_cv is set, the downstream models are trained with the output of `sklearn.model_selection.cross_val_predict` (final results are predicted using the models trained on the full data).\n", "\n", "\n", " Parameters\n", " ----------\n", "\n", " graph: networkx.DiGraph\n", " A directed graph where the nodes are sklearn estimators and the edges are the inputs to those estimators.\n", " \n", " cross_val_predict_cv: int, cross-validation generator or an iterable, optional\n", " Determines the cross-validation splitting strategy used in inner classifiers or regressors\n", "\n", " method: str, optional\n", " The prediction method to use for the inner classifiers or regressors. If 'auto', it will try to use predict_proba, decision_function, or predict in that order.\n", "\n", " memory: str or object with the joblib.Memory interface, optional\n", " Used to cache the input and outputs of nodes to prevent refitting or computationally heavy transformations. By default, no caching is performed. If a string is given, it is the path to the caching directory.\n", "\n", " use_label_encoder: bool, optional\n", " If True, the label encoder is used to encode the labels to be 0 to N. If False, the label encoder is not used.\n", " Mainly useful for classifiers (XGBoost) that require labels to be ints from 0 to N.\n", "\n", " Can also be a sklearn.preprocessing.LabelEncoder object. If so, that label encoder is used." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "image/png": "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L5RiAR/04IMPWpI69dhjj5HU+SF9zGfOnGlp27Fjh3mOAPA9jNgBPubbb7+VAQMGWNq0HPf999+blZHwP7pwQsvyx586os8VTh0BfAuJHeBDOAweJ7Nt2zZTnnc9J/icc86R9evXc04w4EMoxQI+RDemdU3q1JNPPklSB2nTpo15Lrj66aefzEbVAHwHI3aAj/jiiy9kyJAhlraLL77YlNtcj5iC/6qsrJRLL71UFi9eXOO5M3jwYNviAuA+JHaADzh8+LA55H3//v3OtqioKFNma9Wqla2xwbPs3r3blOt15XS1Zs2amdWzDRo0sDU2AGePt/GAD7j99tstSZ169tlnSepQw7nnnmueG64yMzPljjvusC0mAO7DiB3g5T755BO56qqrLG2XX365Ka8FBATYFhc8l172tfT61Vdf1XgujRw50ra4AJw9EjvAix08eFDat29v/q0WExMjGzdulObNm9saGzxbRkaG2ePuyJEjzrbGjRubY8fi4+NtjQ3AmaMUC3gpfU92yy23WJI69cILL5DU4Te1aNFC/vrXv1raDhw4YJ5TvN8HvBcjdoCXeu+99+S6666ztF155ZWmnEYJFqdCL/9aep0/f36N59Y111xjW1wAzhyJHeCFdKGEroLV1bDVGjZsaMpoCQkJtsYG75KdnW3K+Xl5ec62uLg4U85PTEy0NTYAp49SLOBl9L3YTTfdZEnq1CuvvEJSh9PWpEkT89xxpUmePsd43w94HxI7wMu8/fbbsmDBAkvbuHHjZOzYsbbFBO929dVXmw9Xn332mcydO9e2mACcGUqxgBfR48J0JePRo0edbTpKpyVYLcUCZyo3N9eU93Nycpxt0dHRpiSrCy0AeAdG7AAvOg5qypQplqROvfHGGyR1OGu6xcnrr79uaSsoKJDJkydTkgW8CIkd4CVeffVV+eabbyxtEydOlOHDh9sWE3zLiBEjZMKECZa2r7/+2jz3AHgHSrGAF9i5c6d06tRJiouLnW26V52WyXRDYsBd8vPzTUlWjxmrFhkZKevWrZPk5GRbYwPw2xixAzycw+GQSZMmWZI69dZbb5HUwe1iY2Nl9uzZlraioiLzHNTpAAA8G4kd4OGef/55WbJkiaXt5ptvlkGDBtkWE3ybPremTZtmafv+++9rnFQBwPNQigU82JYtW6RLly5SWlrqbGvVqpWsX79e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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "score\n", "0.8974358974358974\n" ] } ], "source": [ "from sklearn.svm import SVC\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.datasets import make_classification\n", "from sklearn.model_selection import train_test_split\n", "from sklearn.pipeline import Pipeline\n", "import networkx as nx\n", "from tpot import GraphPipeline\n", "import sklearn.metrics\n", "\n", "X, y = make_classification(random_state=0)\n", "X_train, X_test, y_train, y_test = train_test_split(X, y,\n", " random_state=0)\n", "\n", "\n", "g = nx.DiGraph()\n", "\n", "g.add_node(\"scaler\", instance=StandardScaler())\n", "g.add_node(\"svc\", instance=SVC())\n", "g.add_node(\"LogisticRegression\", instance=LogisticRegression())\n", "g.add_node(\"LogisticRegression2\", instance=LogisticRegression())\n", "\n", "g.add_edge(\"svc\",\"scaler\")\n", "g.add_edge(\"LogisticRegression\", \"scaler\")\n", "g.add_edge(\"LogisticRegression2\", \"LogisticRegression\")\n", "g.add_edge(\"LogisticRegression2\", \"svc\")\n", "\n", "\n", "est = GraphPipeline(g)\n", "est.plot()\n", "est.fit(X_train, y_train)\n", "print(\"score\")\n", "print(sklearn.metrics.roc_auc_score(y_test, est.predict_proba(X_test)[:,1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cross val predict\n", "\n", "Using cross_val_predict_cv can improve performance in some cases. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/png": "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L5RiAR/04IMPWpI69dhjj5HU+SF9zGfOnGlp27Fjh3mOAPA9jNgBPubbb7+VAQMGWNq0HPf999+blZHwP7pwQsvyx586os8VTh0BfAuJHeBDOAweJ7Nt2zZTnnc9J/icc86R9evXc04w4EMoxQI+RDemdU3q1JNPPklSB2nTpo15Lrj66aefzEbVAHwHI3aAj/jiiy9kyJAhlraLL77YlNtcj5iC/6qsrJRLL71UFi9eXOO5M3jwYNviAuA+JHaADzh8+LA55H3//v3OtqioKFNma9Wqla2xwbPs3r3blOt15XS1Zs2amdWzDRo0sDU2AGePt/GAD7j99tstSZ169tlnSepQw7nnnmueG64yMzPljjvusC0mAO7DiB3g5T755BO56qqrLG2XX365Ka8FBATYFhc8l172tfT61Vdf1XgujRw50ra4AJw9EjvAix08eFDat29v/q0WExMjGzdulObNm9saGzxbRkaG2ePuyJEjzrbGjRubY8fi4+NtjQ3AmaMUC3gpfU92yy23WJI69cILL5DU4Te1aNFC/vrXv1raDhw4YJ5TvN8HvBcjdoCXeu+99+S6666ztF155ZWmnEYJFqdCL/9aep0/f36N59Y111xjW1wAzhyJHeCFdKGEroLV1bDVGjZsaMpoCQkJtsYG75KdnW3K+Xl5ec62uLg4U85PTEy0NTYAp49SLOBl9L3YTTfdZEnq1CuvvEJSh9PWpEkT89xxpUmePsd43w94HxI7wMu8/fbbsmDBAkvbuHHjZOzYsbbFBO929dVXmw9Xn332mcydO9e2mACcGUqxgBfR48J0JePRo0edbTpKpyVYLcUCZyo3N9eU93Nycpxt0dHRpiSrCy0AeAdG7AAvOg5qypQplqROvfHGGyR1OGu6xcnrr79uaSsoKJDJkydTkgW8CIkd4CVeffVV+eabbyxtEydOlOHDh9sWE3zLiBEjZMKECZa2r7/+2jz3AHgHSrGAF9i5c6d06tRJiouLnW26V52WyXRDYsBd8vPzTUlWjxmrFhkZKevWrZPk5GRbYwPw2xixAzycw+GQSZMmWZI69dZbb5HUwe1iY2Nl9uzZlraioiLzHNTpAAA8G4kd4OGef/55WbJkiaXt5ptvlkGDBtkWE3ybPremTZtmafv+++9rnFQBwPNQigU82JYtW6RLly5SWlrqbGvVqpWsX79e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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "score\n", "0.9166666666666666\n" ] } ], "source": [ "est = GraphPipeline(g, cross_val_predict_cv=10)\n", "est.plot()\n", "est.fit(X_train, y_train)\n", "print(\"score\")\n", "print(sklearn.metrics.roc_auc_score(y_test, est.predict_proba(X_test)[:,1]))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "You can access individual steps of a GraphPipeline using the label of each node." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
SVC()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "SVC()" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "svc = est.graph.nodes[\"svc\"][\"instance\"]\n", "svc" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/6_Symbolic_Regression_and_Classification.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Symbolic Regression and Classification\n", "\n", "Symbolic Regression and Classification seek to optimize an interpretable algebraic equation. TPOT allows you to combine this approach with classical machine learning operations.\n", "\n", "We can construct a search space for symbolic equations using either the TreePipeline or GraphSearchPipeline as neither have a fixed pipeline structure and instead optimize their own sequences and structure.\n", "\n", "The strategy is to set the leaves to select a single feature (Using FSSNode), have all inner nodes be arithmetic operators, and have the root node be a classifier or regressor.\n", "\n", "Note: This is still experimental. There are lots of opportunities to optimize the optimization process. In the future, symbolic regression/classification may have their own dedicated search space class." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] } ], "source": [ "import tpot\n", "from tpot.search_spaces.pipelines import GraphSearchPipeline\n", "from tpot.search_spaces.nodes import FSSNode\n", "from tpot.config import get_search_space\n", "import sklearn.datasets\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.model_selection import train_test_split\n", "import numpy as np" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Symbolic Classification" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "X, y = sklearn.datasets.make_classification(n_samples=1000, n_features=100, n_informative=6, n_redundant=0, n_repeated=0, n_classes=2, n_clusters_per_class=2, weights=None, flip_y=0.01, class_sep=1.0, hypercube=True, shift=0.0, scale=1.0, shuffle=True, random_state=None)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "symbolic_classification_search_space = GraphSearchPipeline(\n", " root_search_space= get_search_space(\"LogisticRegression\"),\n", " leaf_search_space = FSSNode(subsets=X_train.shape[1]), \n", " inner_search_space = get_search_space([\"arithmatic\"]),\n", " max_size = 20,\n", ")\n", "\n", "#example pipelines randomly sampled\n", "ind = symbolic_classification_search_space.generate(rng=5)\n", "for i in range(3):\n", " ind.mutate(rng=1)\n", "est_example = ind.export_pipeline()\n", "est_example.plot()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 20/20 [00:40<00:00, 2.01s/it]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.71168\n" ] }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "est = tpot.TPOTEstimator( generations=20, \n", " max_time_mins=None,\n", " scorers=['roc_auc_ovr'],\n", " scorers_weights=[1],\n", " other_objective_functions=[tpot.objectives.number_of_nodes_objective],\n", " other_objective_functions_weights=[-1],\n", " n_jobs=32,\n", " classification=True,\n", " search_space = symbolic_classification_search_space,\n", " verbose=1,\n", " )\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovo')\n", "\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))\n", "est.fitted_pipeline_.plot()" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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gUTDQfX59fujY6ihoqn9nPoDRfAfBMTfYyf6w4h6qxSI9EcB/ZyaczO131lqvncAEcIHANnc+++/79N/zr766ivfvn37fNu3b/dNnjzZV7x4cV9MTIzvf//7n++PP/7w5c6d2zds2LCQ1/7000++PHnyhCy//vrr3f7Gjx+f4L06d+7sK1iwYMiyNWvWuO27du0asvzpp592y7/++uvAsooVK7plc+bMCdl24cKFbnmtWrV8cXFxgeUdOnTwRUVF+Vq2bBmyfePGjd2+gh0/fjxBe1u0aOG7+OKLQ5b527Bo0aLAsr179/ry5cvn6927d2DZwIED3XYzZsxIsN9z5865fz/66CNfrly5fIsXLw5Zr2On1y5dutSXFH3OkiVLus984sSJwPIvvvjCvVbvH/87XrFihS8lrVu39kVHR/t+//33wLKdO3f6Chcu7LvuuusS7LNZs2aBzyM9e/Z058rBgwdDzgk9/ObOnete++WXX4a8d506dUK2S4zOUb120KBBiZ5fWjdkyJCQ5Zdffrmvfv36gec63tpu0qRJIdvpvEpseXx6b223ceNG154tW7b43n77bXcOlCpVynfs2DHfmTNnfKdOnQp53V9//eXWP/jgg4Fleq32FRsb686jYDfddJOvdu3avpMnTwaW6VhfffXVvqpVqwaWTZs2ze1DvwfBtD99l82bN/edPXs2sHzs2LFu+4kTJyb7Of3fcWKP4N87/Y4E//6k5dxM7XeW3PcOIHIwLBBAxNBftvUXYg1LU4+TehjUa6S/mmseiXpZ1Gulv2D7H6VLl7aqVasmGOqkv7zrr9apoaIZoqFlwdSDJbNmzQpZrp4X/ZU7MRpyF/yXeP0lXD0h6jkKpuUazqjhiH7B87b8f6m//vrrbfPmze55MA0ZVO+On46beoe0rZ96FurWrev+0h6fv9iBhk2ph6FatWohx9U/vCz+cQ32448/urlU6l3TcDG/Vq1auf3FP26poR4EFWZQz4iGOvqVKVPG7r33XtfDFr8annpagos36LhoP1u3bk32XNPwtEmTJgWWqXDKunXrXE/n+dL8p2BqU/B3o+OuoXo333xzyHFXz6PO++SOezB95/rudU6qJ0xDO3XcNTROPXL+OVP63Tlw4IA739SDu2rVqgT70nwt/9A30fYqjqHfOfV6+duoXmGd/5s2bUowxC4+9QBp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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "df = est.evaluated_individuals\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(5,5))\n", "sns.scatterplot(df[df['Pareto_Front']!=1], y='roc_auc_score', x='number_of_nodes_objective', label='other', ax=ax)\n", "sns.scatterplot(df[df['Pareto_Front']==1], y='roc_auc_score', x='number_of_nodes_objective', label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of all pipelines')\n", "#log scale y\n", "plt.show()\n", "\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(10,5))\n", "sns.scatterplot(df[df['Pareto_Front']==1], y='roc_auc_score', x='number_of_nodes_objective', label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of only the Pareto Front')\n", "#log scale y\n", "# ax.set_yscale('log')\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Symbolic Regression" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 20/20 [00:30<00:00, 1.51s/it]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "-3073.9914754941187\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnYAAAHWCAYAAAD6oMSKAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAuXdJREFUeJzsnQd4U1Ubx9+26S5dFDops2WvVmYBQURZIlv5EBVE3CIiKqi4UBBFQRRFRRAURAEZorIVKbtlF2iB0gUFuvdIe7/nfyA1o+nMzvt7nii9SW5Ocu8953/faSNJkkQMwzAMwzCM2WNr7AEwDMMwDMMwuoGFHcMwDMMwjIXAwo5hGIZhGMZCYGHHMAzDMAxjIbCwYxiGYRiGsRBY2DEMwzAMw1gILOwYhmEYhmEsBBZ2DMMwDMMwFgILO4ZhGIZhGAuBhR3DMAzDMIyFwMKOYRiGYRjGQmBhxzAMwzAMYyGwsGMYhmEYhrEQWNgxDMMwDMNYCCzsGIZhGIZhLAQWdgzDMAzDMBYCCzuGYRiGYRgLgYUdwzAMwzCMhcDCjmEYhmEYxkJgYccwDMMwDGMhsLBjGIZhGIaxEFjYMQzDMAzDWAgs7BiGYRiGYSwEFnYMwzAMwzAWAgs7hmEYhmEYC4GFHcMwDMMwjIXAwo5hGIZhGMZCYGHHMAzDMAxjIciMPQCGYRhLpqysjDIyMujGjRvicSs1lYoLC6m8rIxs7ezI0dmZGvn5ka+vr3h4e3uTnZ2dsYfNMIyZYiNJkmTsQTAMw1gamZmZdOrUKToTHU1F+fkkyeXkVlhIHhkZZC+Xk60kUbmNDZXKZJTt7U15zs5kI5ORk6srdQwLo86dO5OXl5exvwbDMGYGCzuGYRgdcu3aNTp44ADFx8WRfUEBBScmkX9GBnnk55N9WZnW95Xa2VG2qytd9/amxOAmVOriQs1DQiiib1/y9/c36HdgGMZ8YWHHMAyjA+RyOUVGRtKxyEhyS0ujVgmJFJSWRnbl5bXeV5mtLSX7+NClpsGU5+ND3SIiKCIigmQyjp5hGKZqWNgxDMPUk9TUVNq+dStlJqdQm7g4CklJEa7W+gJXbVxgIF0ICSHvoEAaOmIE+fn56WTMDMNYJizsGIZh6kFCQgL9tn49uVy7TuHnz5N7QYHOPyPHxYWi2ralgoAAGvXQeGratKnOP4NhGMuAhR3DMEw9RN3GdeuoYUIidY+JIVkd3K41RW5rS0fat6OM4GAaM2ECizuGYSqF69gxDMPU0f0KS513QiL1PHdOr6IOYP+9zp4j78RE+m39L+LzGYZh1GFhxzAMU4dECcTUwf3aIyZGJ/F0NQGf0+NcDDlfv0Z/bN0qxsEwDKMMCzuGYZhaguxXJEogpk7fljp18HnhMecpIyWFDh48aNDPZhjG9GFhxzAMU8s6dShpguxXfSRK1ASPggJqHRtHRw8coOvXrxtlDAzDmCYs7BiGYWoBig+jTh1KmhiT0JQUMY7IAweMOg6GYUwLFnYMwzC1aBOGjhIoPmyouDpt4PNbJiRSfGysGBfDMAxgYccwDFND0PsVbcLQUcIUaJKWRrKCAjp9+rSxh8IwjInAwo5hGKYGlJWV0ZnoaNH7tS5twvQBxtE0KYlOR0WJ8TEMw7CwYxiGqQEZGRlUlJ9P/hkZZEr4p98eF8bHMAzDwo5hGL3z3nvvUfv27aljx4501113UXx8vNbXLlmyhNq2bUvPPfec1te88847FBQURF26dBGvXbduXZ3GNXXqVLp8+XKNXnvjxg2S5HLyzMuj+vLI6dMUm59Pj505QyNORNPdx45SzyOHxb/xyKlFfTqP/HwxLoyvKmbNmiWOwQcffECmwu+//04dOnQgW1tbOnv2rLGHwzAWgczYA2AYxrJBrbV9+/bRyZMnyd7enpKTk8nV1VXr67/66is6cOAA+fj4VLnf119/nZ5//nm6ePGiEItjx44V+68N3333XY1fC+Hkkp+v07p1P3TsKP6/6cYNii3Ip9ebt1B5vkySyM7Gpsp92JeVkVthoRgfRJI2Vq1aJV4DEVUT4Nq1s7Or0Wvruq/WrVvThg0b6Omnn9bJ5zAMwxY7hmH0DFpfQaQpRBcsbV5eXjRt2jQKDw8XVqRPPvlEPAcr3ZUrV+iee+4RouvWrVs0evRoIdx69epFJ06cqFQcQCgqXJFr1qyhbt26UefOnenll1+ueN33339PnTp1EttfeeUVsa1///4VliKMEZ+P8QwbNoxycnIqXvPSSy/Rk08+SWeOHqXNN2/Q8OgoGhYdRd8lJ1fsf0NqKj0QHUUPREfTgvgrYtu669dp9MkTYtvLFy9QaQ1E4ecJCfRq7EV66NRJev/KZTqZk0PjT52kkSeiaeLpU5RSVFTxujlxsfS/06fona+/pi2//VZRZy8iIkJ8T3xfJFaMGjVKZM6GhYXRH3/8QdHR0dS9e3dhQX300Uep6M4+mzVrJgRz165dae/eveI3mT59urCKPvjgg/Tvv/9Snz59qFWrVnTo0CHxnvz8fHr88cfFb47juWvXrgqr6mOPPUa9e/emF198sdLvGhISQm3atKn2N2EYpuawsGMYRq8MGjSILly4QO3atRMi4fjx42L7ggULKCoqSmSabty4kZKSkujLL7+kgIAAYeWDmxSCavbs2eI9q1evrtSyc+zYMWrevDn5+vrS+fPnacuWLUJ0YL9paWm0fft2OnPmDH322We0f/9+sX3OnDka+0lPTxeC8ty5c0IQffrppxXPQZS+PWcOdfX1paWJifRjx060qUtX+v3WTTqbl0sX8/Np1bUU+qlTZ9oWFkZPBzUR7xvi4yNeh20+9g70Zw2zaRMLi2hNx070TstW1MrFhdZ16kybu4bRlMAgWpaUVPG6pKIi+qFDR3rz3kG0/pdfxDa4pSFG8T0h4CDCfvvtN/L09BRW06FDhwrBtXTpUvG7QBQvW7asYp9NmjQRAhrHDb/JyJEjxe9aUFBAX3zxhfgNcZxw/ABcu8OHDxfHYceOHfTCCy+QdKcUzKVLl+jvv/8Wr2cYxjCwK5ZhGL3SoEEDIRTgjt2zZ48QDL/88gvFxsYKqxzcdHDPQvxBVCize/duIbQUKNdrg7CAIMF+IFwA9n/48GFh4QMQI7AiIY7uoYceEuIGeHt7a4zT0dFRWAfBhAkTVKx948aNo3MnTtCVtDTq5eFJnnesj/f7+FBUdg7BWzrUpxG5y25PqYrnL+Tn0+LEBMqTyym3rIycaugGHdjQmxzuvDZbLqdZsRcpsaiIyiWJPGT/uZv7e3mTva0tBTRwE98VwHIGK5xMJhPuaVjllMnKyqLi4mLq0aOH+HvSpEn08ccfV3xffFcFbm5uNGDAAPFv7AfWUbhy8e+rV6+K7Tt37hSxcvPmzauw4Cni/WDlc3BwqNF3ZhhGN7CwYxhG70BkQNDhAfcerGcQBrCseXh4CAECsVEZsNbh/dpi7DZt2iQsedh3eXm5cJm+/fbbKq/9/PPPazVeGxsb8VDg4uJCtnZ2JFUT76bOnLg4+qZ9e2F1W3PtGqUU33Z5VoeT7X/xaEsSE+hub2962M9fJFy8Hhdb8ZxC/JXb2GLQ4t/9+vUTvWwhtiBQP/zwQxoxYkSNx4zvqix2FUDQKf7GvxXlVfCbb9u2jZo2bVrlvhiGMQzsimUYRq8guUGReQoXHWLaYC2CNcjd3V1Y62CZqwxYi5BMoQDuRXVgZUNMGFy1AwcOpPXr1wsXIrh586bopQoXK7ZnZ2eL7ZWVBoGwhBsX4LWIJVPG0dmZmvr60qHsLMqWl1JJeTntSk+nuzw8qKeHJ/2Rdoty72SzZpWWiv8XlpeRj729eO32W7fq9PvlycvI1+G2oNp0s/LM1xIl4ZuQkEB+fn701FNPCWucevFiWC0h0OA6BT/99JMQg3XlvvvuUxHOcPcyDGM8WNgxDKNX8vLy6JFHHhFJCcjahIXn1VdfFQH5CJx/4oknNESUAsSBIUYLiQB4/dq1ayt9HQL1YQVEHN8bb7whBB7i5JAEARGHz0V8H5IKUCJFER+mTMOGDUXgP8YJ1/GMGTNUnm/k50e2TZrQ802CaSISEk6eEO7X9m5uFOrqSo8GBNLDp0+JciXf3EmqeCE4mEafPEkTz5ym1q51s149GRRE869cEckT9rDMVUKOt1dFtqvi94LY/euvv0SsYmUZskgUwW+Um5tLzzzzDNWVt956Swhm7Au/vyIRpiYgkQPJNLDc3nvvvSpuYIZh6oaNpIhyZRiGsWLgIkayhTZgafzj119p+D/7RYkRU6HUzo5+v7sfDR03rspyJwzDWAdssWMYhqkByLq1kckou4oafMYA48G4MD6GYRgWdgzDMERVWusUmbROrq50vZKMWmNyveHtcVWW6WsqrFy5UrjAlR+m1AGDYSwJdsUyDMPUEMSvndy1iwYfiCQ7HXagqCtltrb0Z58ICrvvPrr77ruNPRyGYUwAttgxDMPUECQllLq4UHI17c5AuVROuXl54oH6c/ogyceH5C4uInGBYRgGsLBjGIapIWiF1jwkhC41DabyKmraSSRRWlo65ebmiIdyYWVdgc+/3DSYmoeGinExDMMAFnYMwzC1IKJvX8rz8aG4wECtr8nLyye5/HYtO1BSXEy6ttnFBgaKcURoKRXDMIx1wsKOYRimFvj7+1O3iAi6EBJCOZV0VigrLxe1+5RxcHSk2vWsqJpsFxe6GBpC3fv0EeNhGIZRwMKOYRimlqDQsVdQIEW1bUtytf6vOTk5JEnKiRU25N6gQa32n5ObK7J08+/0f1UGnxfVri15BwZS79696/wdGIaxTFjYMQzD1BL0rh02YgQVBATQkfbtKuLtSkpLqLCwQKNfqr29fY33jS4OeXm5Yl/Z2VmUnpEhEjEAPgefV+gfQENHjKi0hy7DMNYNCzuGYZg6gH6sox4aTxnBwXSoQ3sqtbWl7OwcldfY2NhSg1pa64qKi1X+Li4uops3b1FeSYn4HHwePhefzzAMow4LO4ZhmDrStGlTGjNhAmU1a077OnagbGcnlech6uzUXLXVUdnrc91caV+nTnTF3Z0GDhkiPpdhGKYyuEAxwzBMPbl8+TItWriQGru5UciFCxQQG0sOdnbUqFHjWidNwBWbX5Bf4Xq9FhpKcW3aUEpGBm394w+ysbGhyMhIatmypV6+C8Mw5g0HaDAMw9ST5cuX0zcrVohkhuJu3Sg1KIjapqZSw6zsWneosLG1FR0l0po0oaRWrSjNzY0ijx2jgwcPUllZmXjN0qVLafHixXr6NgzDmDMs7BiGYepBbGysEFkQXf/++y/FxcXRuLFjqbx7d7pYUEBNk5LIPz2DPPLzyf6OMKuMUjs7ynZ1pYTAAIrz86NCmYxi4+MpcutWSk1NVXmth4eHAb4ZwzDmCLtiGYZh6sHw4cNp+/btFX8jA/bcuXPk4+NDp0+fptNRUVSUn0+SXE5uhYXknpFJDnI52UrlVG5jSyUyGeV4e1GeszPZyGRUWl5OO/bupVOnTgm3rDoDBw6kTZs2kbu7u4G/KcMw5gALO4ZhmDry559/0tChQ1W2zZo1ixYuXFjxNyx5GRkZdOPGDfG4lZpKJUVFVCaXk51MRg5OTtTIz498fX3FA1a/MWPGaP3MrVu30gMPPKDX78UwjPnCwo5hGKYOlJSUUKdOnejixYsV2yDM4JqtjzXt5s2bFBQURKWl/7UkU6ZVq1Z09uxZcnR0rPNnMAxjuXC5E4ZhmDrwxRdfqIg6sGDBgnq7SBs3bkw7duyg0aNH04svvkiffvqpyvOXLl2izz//vF6fwTCM5cIWO4ZhmFoCl2poaKhoH6agW7dudPjwYbKtZd266igvLxctzLBv5fp4sAxykWKGYdRhix3DMEwtefPNN1VEHYAVTdeiDmCf6ha63Nxcmj17ts4/i2EY84ctdgzDMLUgKipKWOeUp85JkybR6tWr9fq5U6ZMoZUrV6psO3r0qBgLwzCMAhZ2DMMwNQTTZd++fUXnBwWurq7CLRoQEKDXz0YtO7h/Ya1T0LNnTzEWfVgKGYYxT3g2YBiGqSE///yziqgDb7zxht5FHUA83VtvvaWyDXF3P/30k94/m2EY84EtdgzDMDUgPz+fWrduTSkpKRXbWrRoIYoROzk5GazESocOHUR3CwX+/v4iOxcJFQzDMGyxYxiGqQEoZaIs6gBKkRhK1AEHBwf67LPPVLZdv36d5s+fb7AxMAxj2rDFjmEYphri4+Opbdu2VFxcXLHt3nvvpZ07d5KNjY3Bx4NuF+h6oSz4YmJiqGXLlgYfC8MwpgVb7BiGYaoBbcKURZ2dnR0tWbLEKKJOYSmUyWQqLtqZM2caZSwMw5gWLOwYhmGqYN++fbRx40aVbc899xy1a9fOaGNq06aN6EqhzJYtW2jXrl1GGxPDMKYBu2IZhmG0IJfLqWvXrqI3q4KGDRuK5AUvLy+jji07O5tCQkLo1q1bFdsgNk+ePEn29vZGHRvDMMaDLXYMwzBaWL58uYqoA/PmzTO6qAMeHh704YcfqmxDnN1XX31ltDExDGN82GLHMAxTCenp6cIilpmZWbGtc+fOovMEYuxMgbKyMurevTtFR0dXbPP09BQFkxs1amTUsTEMYxzYYscwDFMJb7/9toqoA0iYMBVRBzAW9T6yWVlZNHfuXKONiWEY48IWO4ZhGDXOnDlDXbp0ofLy8opt48aNo19++YVMkYkTJ9LatWsr/kaLMVjxYGFkGMa6YGHHMAyjBKbEgQMHimxYBShCfOHCBWratCmZIsnJyaIrRkFBQcW2fv360d9//220kiwMwxgHdsUyDMMo8dtvv6mIOvDaa6+ZrKgDQUFBNHv2bJVt+/fvp19//dVoY2IYxjiwxY5hGOYOhYWFomTI1atXK7Y1adJEWOtcXFzI3MYeHBxM58+fN/mxMwyjO9hixzAMc4dFixapCCPw8ccfm4UwcnZ2FuNXJjExUYyfYRjrgS12DGNmoMRFRkYG3bhxQzxupaZScWEhlZeVka2dHTk6O1MjPz/y9fUVD29vb5PK5DRV9BWnZsjjVVl8IAQfLI6w3jEMY/mwsGMYMwGlN06dOkVnoqOpKD+fJLmc3AoLySMjg+zlcrKVJCq3saFSmYyyvb0pz9mZbGQycnJ1pY5hYSJD0hQK65oq6pmlEHPILEV2rDkdr8oyesePH0/r16+v0/dgGMa8YGHHMCbOtWvX6OCBAxQfF0f2BQUUnJhE/hkZ5JGfT/ZlZVrfV2pnR9murnTd25sSg5tQqYsLNQ8JoYi+fcnf39+g38HUiYyMpD59+qhsmzZtmug8YY7H6/nnn6cvv/xSZds///wjLJAMw1g2LOwYxoT7lEJwHIuMJLe0NGqVkEhBaWlkp2SJqSlltraU7ONDl5oGU56PD3WLiKCIiAiSyWRk7VTWvQHtutAPtjbdG0zpeJlD1wyGYfQDCzuGMUFSU1Np+9atlJmcQm3i4igkJUW47uoLXH9xgYF0ISSEvIMCaeiIEeTn50fWzIoVK2jq1Kkq2xYvXkzTp0836+MFix0sd8p8/fXX9NRTT9V7XAzDmC4s7BjGxEhISKDf1q8nl2vXKfz8eXJXCubXFTkuLhTVti0VBATQqIfGm3SNNn2SnZ1NoaGhdPPmzYptbdu2FbFx9vb2Zn28YEEMCwsTMXcKGjZsKCyRHGvJMJYLlzthGBMCImHjunXkFX+V+p44oReRALBf7N/zarz4PHyuNfLee++piDqFta42os5Ujxfctvgu6i7ad999Vy9jZBjGNGCLHcOYCHDn/bx6NXnGX6Ve587pxJVXE1ffoQ7tKatZc3r40UlW5Za9ePEidejQQVi2FIwYMYK2bNliUcdrzJgxtGnTpoq/EWN3+vRpUcyYYRjLgy12DGMCQFwgRgvuvB4xMQYRCQCf0+NcDDlfv0Z/bN2qInIsnRkzZqh8XwcHB40Cv5ZwvD755BNydHRUSRZ56aWXRM07hmEsDxZ2DGMCIJsSgfeI0ZLVIYuyPuDzwmPOU0ZKCh08eJCsge3bt9Off/6pIfRatWplccerefPmNGvWLJVtu3btom3btulxlAzDGAsWdgxjZFD3DCUykE2prxit6vAoKKDWsXF09MABun79OlkyJSUlQsQpgzpxb7zxhsUer9dff50CAwNVtr388stUXFysx1EyDGMMWNgxjJFBMVvUPUOJDGMSmpIixhF54ABZMp9//rnIDFVmwYIF1KBBA4s9Xq6urrRw4UKVbZcvX6bPPvtMjyNkGMYYsLBjGCOCArLoUIBitoaK09IGPr9lQiLFx8aqFLa1JJDwgExYZXr06EGPPPKIxR+vCRMmiCLHysybN09YIBmGsRxY2DGMERH10goKRIcCU6BJWhrJCgpE1qQlMmfOHMrNzVXZtmTJErK1tbX444Xet/iu+L+C/Px8mj17tp5HyTCMIWFhxzBGAtmJaBCPXqJ1aTulDzCOpklJdDoqSozPkjh27BitXLlSZdtjjz0mLHbWcrzCw8NpypQpKttWr15NR44c0eMoGYYxJCzsGKsGRVy7dOlS8SgsLKz1PtRjl2pKRkYGFeXniwbxynyRmEBDo6NoeHQUjT55gpKKiqrcz7fJSfV6f/fDh1T+9k+/PS6MrypQ/BaJCLoCSQDu7u70xRdf6Px4obTHiy++qLLNzc2N5s+fb3XH68MPPxS/szIvvPAClddCrCofh4kTJ9b4fQzD6B/uAM5YNZ6ennTy5Ml67QPC7tVXX63Ve2BduXHjBklyOXnm5VVsj87JoSPZ2bSlS1eyt7Wl1OJicrar+v7r2+RkejKoSZ3fr45Hfr4YF8bXqFGjKoUCeqyi/ltNgHCoyuWJzM1Bgwbp5Xj99NNPdPjwYZVtb731lsiGtbbj1bhxY3r77bdp5syZKtbMNWvWCAtmTY6XLq4bhmH0A1vsGEaNHTt2UK9evahr164iqF5hlZo2bZpwZbVv314UfQUokZGVlSUsF08//TRdvXqV7rrrrop9vfLKK7Rq1Srx72bNmgnxgv3u3buXfvzxR/rym29o1PHj9OGVK+I1t0pKyEtmLxZ54OfoSB6y2+2t/s3MpPGnTtKDJ6LplYsXqKS8nD69epVy5XIacSKa5l6Kq/X71fkmOYkeijpOS5Yto6VLl1Zs/+CDD6hjx47UqVMnkUmJBvMIuu/du7fo1gAgDPAadHP4+OOPxTb8Htj28MMPi04H2ixs+/fvF1YkvFbXxwu9X5977jmV90CMIjPWWo/X888/r9G1At/17NmzNTpeDMOYLizsGKtGIcrwgDUjLS1NiBIs5CdOnKAWLVrQt99+W1ESIyoqSgTQb9y4kZKSksQCqrBefP3119V+XpMmTcR+g4KCaN/evfT+kCG0LSyMMktLaV9GBkV4etKVwgIaEnWc5l2+TGfuBPpnlJbSd8nJtLpDR9rSNYyaODnRL6mp9HKzZtRAJqOtXcPovVYhtX6/MgcyM4XFaGPnLjT/gRF04MABsdD/8ccf4vc4fvy4CNKHVQdCKSAgQBTI3bp1K6WkpNA777xD//zzj3jdunXrxG8Fzp8/L5IWLly4QM7Ozhq/CbonzJ07t0Y9TOtyvEaOHEk5OTkq+9mwYUPF66zxeN26davSjGG4was7XgC/J0Qzsmx37txZ7e/IMIzhYFcsY9Wou5R+//13sRjCAgRQwHXYsGHi3xAr3333nXDLJScni4UPC39tGDdunPj/nj17hMVo7qVL5FxSQkVl5dTBzY0GeHvT5q5hdCQriw5mZ9Hks2dpSZs2VCKV08WCfBp/+pR4P6w3/b29NfbvJpPV+f0HsjLp74xMOp5zggpjzlGBTEaxsbFCMEyePLmiLZV3JZ8LV97AgQMrnhs7dqx434MPPkihoaHCcqQNWJPwu1S23/oer2XLlgmhooy9vT0NHz6crP14PfDAA8KKhy4cClasWCEslVUdLxAfHy8KHuPz7rvvPjp69Khw8TIMY3xY2DGMEogrgjBQz568cuWKECCHDh0iDw8PIVwqq9qPoHLlIHT117i4uFR8Tr8+fegRb2/qfCVedR82NhTh5SUe3jJ72p2RTn08vai/lzctCA2t9jvU9f3lEtHzwcE02teXTrVoTrm9e9Po0aOFUKgPiu+sDYgCfAYsb7DIoUk93qOevVmX4wULXkxMjMrxQbFeRckPaz9ecNPC4lZaWlphPYUVtDoUXSwg2rt16yZ+YxZ2DGMasCuWYZSA5Wffvn2UkJBQ4XKCdQK1z5BFiTgwWOt2795d8R4IEUWpCSxusILg9Xl5eaInZ2XAunUsKoqy7wiJ9JISullSQlcKCijxTlwTMjljC/IpwNGRuro3oCPZWZRyJ2MyTy6vyJ60s7GhsjvFcuvyfgV9vDzp1xupVFhWRuU2tpSRlUXZ2dl07733CuGkED2K7Et0alDUhOvevbuwaqFQLl63adMm6tu3b40TG/B7I94Nzenhkq2JqKvueEGMKVujAGLLEF/Hx+v28fLx8RG/iTLYB+IWtaE4xgAJG3B3h4SE1OBoMQxjCNhixzBKIKsQsVdjxowRQfjICkQ2Yf/+/UUQfps2bYSrqk+fPhXvQQwTAs779esn4uyQIYuA++DgYK3JAEjAGDVyJL23fj05FxWJ4PmPQkKpWCqn9y5fprw7wqO9qxtN8g8gJzs7mtcqhF64cJ5Ky8uFxemN5i1E7NWoxr6iVEY3Dw8a7+dX6/cr6OflTZcKCkTAft758+R26CA98vjjNHToULF4h4WFCTcm3HzTp0+nJ598kgYMGCCsNojbQqYlfgMIFPwmeD3EmjGOF44PYsaUgSjHGPEd+Hj9d7wgcnHTAmGrAAIbLm68Xx24tp966qmKjFmUT1HvQ8swjPGwkTALMwxjcGAxubhjBw06pFqGwxTY1asntb7/fmGpMkeQIapet+6bb74R4qauWPLxgoVP3UoKNy0EHsMw5gW7YhnGSPj6+lKeszOV2tmRKYHxYFwYnzmCGDFk2SoDi1xN3bvWeLxgYVUu+wKQ5VxZ9izDMKYNu2IZxkhgIbaRySjb1ZV81MpxGBOMB+PStbBLT0/XsCghc1PX7axQeBhJGMqgRypiIQ1xvMpQ2NfGRqUnq6kfL7hVP//8c1HnrmK/2dki9EA581sfx4thGN3Cwo5hjATKUDi5utJ1b2+TEnbXG94eV03Kj9SGhg0b6r1bAWoMwuWqDIrt1jSRoz7HC1EtWdnZFUV9vby8yFkpJs7UjxcSUVDgGYWYlRMlkFCCeD2GYcwDdsUyjJGABaljWBglBjehsipabRkSjCOhSRPqFB5ebwuXoYGwQpKAcvkSFNitay/f2hwvWOnS0tOpsLAAIxGPXAOIdV0fr48++kiUg1H/TTkUm2HMB9NYTRjGSuncuTOVurhQso9Pta+FeMjJzaXcvDwqr8FCi9egvIUo/SHVrMF7ko8PyV1cqi1Qa4qgmwQ6X6i3yaptEenaHi9F7bfS0tutzBTYGECs6/p4oTsF2uQpg7p469ev18n+GYbRPyzsGMaIwF3XPCSELjUNpvIqYrJgMUlPS6O8PAi1HMrKzKx235kZGZSL1+flUlpaOknCkqQdfP7lpsHUPDRUjMucKCgoEH1elWnatCnNmjVLr8eruKSEbqWlUVmZXOV1Nja2opC1PtHX8ZoxY4Yo7KwMfsf8/HydfQbDMPqDhR3DGJmIvn0pz8eH4qqoBQYrnVxJPEBQVAUkHOq6KZDLSyk/r+qFOTYwUIwjQqlGn7nwySefUGJiosY2bb1OdXG8zjVuLBJCJDVrqJ2dTBT+daikBpwu0dfxcnJyokWLFqlsQ1FuuGkZhjF9WNgxjJHx9/enbhERdCEkhHIqab8lLyujfKXisUDRB1QbsP05ODpoiEO4cysj28WFLoaGUPc+fcR4zAkIugULFqhsu/vuu0XRYn3g5+dHOUVFdLppMBW4N1B5zt7eQYg6e5l+89L0fbzQ4xcdLJRByzd9F5xmGKb+sLBjGBMgIiKCvIICKaptW5KrxWahTZaqG9VGtIeqDvcG7nck3m1gWcK+1MHnRbVrS96BgSrlLswFdI5QZKIql+7QR7mRoqIimjhxoui2kJKZSefDw6nsTtKCk5Mz+TRsSHZ6jq0zxPHCb4cOHsoJGfjuunZtMwyje1jYMYwJgGb0w0aMoIKAADrSvl1FvB1crkVF/4kWgKzFmliE0A5K0cReAbI2S+40fAf4HHxeoX8ADR0xQozDnPj33381AvvR7kofyR9IkBg0aBCtW7dOtOHaun07XXNxofM9epCzWwMR56bv2nWGPF5oo/bss89qJKigNy/DMKYLtxRjGBMCzew3rltH3omJ1P1cDGXeuCHi4xTY2thSY19fUQC3JsD1evPmTZU4MIW7EKUyIBIygoNpzIQJItnAnIC4QrcE5dp4EFdxcXGiZp4uwT7Rg/XSpUsq2/GbTX3sMQq6dYt6nIshmRZXt64sdYY+XqhjFxISImIJFUA0oxetud0EMIy1wBY7hjEhsFhj0c5q1pz2dexAOS6qwf8N3N1rLOoA3ILqbluU5bhpa0v7w7qKzzFHUQdWrFihUfD43Xff1bmog1WwZ8+eGqLOzc2NvvrqK5o0ZYr4Hf/t2rXSGEldxdQZ43hBKM+bN09l2+nTp+nbb781yOczDFN72GLHMCbIxYsXafEnn5CvuzuFXLhAAbGx5Ihsy0aNlKLmagYu8Fu3bop6a3DlXQsNpUtt21JAy5b0wKhRIhnA3IAlKTQ0VLhHlV2HEHq6tCStXbuWJk+erJJhDAIDA2n79u2irh1ITU2l7Vu3UmZyCrWJi6OQlBSy1cHUiuOF7FckSiCmDu5XQx8vWEbReQKCTgG6XMCKqevuJAzD1B+2pTOMCYLg/29XrhTB8cXdulFqUBC1S71B3llZZFdLdx+EoKunF8W6ulBSq1aU5uZGkUePUk9bW7MUdeC9995TEXWKfrC6EnW434Wlau7cuRrPde3albZt2ybEnQL8jo9NmUKRkZF0zMmRkv39qGVCIjVJS6v18QJwk6P4MOrUoaQJsl9xLhjD/YkECpyP/fv3r9iWkZFBb7/9Ni1dutTg42EYpmrYYscwJsaZM2eoS5cuFa2xIBomPPQQ+TdqRLKCAmqalET+6RnkkZ9P9mVlWvdTamcnGsSjlyjaTmWVl9O52FiKPHhQWJiQXHHu3DkRQ2VOnD9/XsR5wQKpYOTIkfTbb7/pZP+wzk2bNo1++OEHjeeGDx8ukifghtXGtWvX6GBkJMXHxtbreKGjBIoPR5hICZrx48fTr7/+qiL4YCHt0KGDUcfFMIwqLOwYxoTA5Thw4ECVzEMU2b1w4YKIlYM77HRUFBXl55Mkl5NbYSG5Z2SSg1xOtlI5ldvYUolMRjneXpTn7Ew2MploEI9eou7u7tSjRw8qVcqKhVCB9cmcfp/BgwfTzp07VWr6xcTEaHRLqKuLF/XvKsv8fOGFF+izzz6rcU9W7Ks+xwvi1ZQ6gCCxp02bNqLsiQKcq7t27dJ7NjDDMDWHhR3DmBAbN26ksWPHqmx75513hNtLOeYJrrAbN26Ix63UVCopKqIyuZzsZDJycHKiRn5+5OvrKx6Ig1KIEdR8Q6FZZf78808hlsyBrVu3iuK5ysyZM4c++OCDeu/7ypUrNGzYMCGiK6vp9uKLL9Zpv/U5XqYGzkO4wZXZtGkTjRo1ymhjYhhGFRZ2DGMioMhuu3btVKr7o4E9hIZ6Pbq6ggLFSDqAwFDQunVrYVlycFDtVGFqFBcXiwSJy5cvqzStR6JJVa7RmnD48GEaMWIE3bp1S2U7fne4XvEcc7snL6x2SUlJFduaN28uLKZoRcYwjPHhcicMYyKgP6d6yyb0O9WVqANwx86fP19lG4TRl19+SaYOrGbKog6gf2l9RR3ixgYMGKAh6hDbuH//fhZ1SuBcVLf4xsfH06effmq0MTEMowpb7BjGBECTdVjOYBFR0K9fP/r77791Hr+EpAzUZTt27JiK4EP5isaNG5Mpcv36dWFpzFPqmdurVy+RhVrX3wdTH0TKa6+9pvFcx44d6ffff6fg4OB6jdsSwe+GXryo76fcDQU3CMqZwgzDGAe22DGMCYDYN2VRh36nKN+hj6B0xb7VXbRvvPEGmSqzZ89WEXX4XerTDxYJJE8//XSlou6+++6jAwcOsKjTAn5z9XMzPz+/0t+SYRjDw8KOYYwMRATiuJR58sknRckTfQFr1yOPPKLRySE6OppMjSNHjmiUHkHRYLQTqwvZ2dkiG/ibb77ReA5lTmCpgwWT0Q5q+eEcVeann36igwcPGm1MDMPchl2xDGNEkDHZvXt3FUHl6ekp3KLo56pPUG8N7k1YWxREREQIF5uplK+A2xgi9OjRoxXbUPYlNja2TsWVExMTRebr2bNnNZ6DW3bmzJkm891NHcQkogYihLICiG0IcViFGYYxDnz1MYwRWblypYaVDP1O9S3qFBml6u5XxKz9/PPPZCqsWbNGRdQBdIOoi6hD43rU8VMXdcjm3LBhA73yyiss6mpBo0aNRCkeZY4fP06rVq0y2pgYhmGLHcMYDVg6YPFQzsZEuRNU80dXCEOAYrMoIYIabgoQAI9AeATEG5Pc3FxhUUSXDAX4vSDMaluaZcuWLfS///1PJY4RIFkEtfEg+Ji6xSqiXy66gSj/prCoenh4GHVsDGOtsMWOYYwECr2ql9hASQ9DiTqFtQplVpRJSUmhBQsWkLFB0WFlUQfQ+aE2og73rQj0RwFddVHXtm1bUb+ORV3dwbmKc1aZmzdvij67DMMYB7bYMYwRQNFhlNRQ7neKjgqbN282+FgwBSATdPfu3SqCD1aYZs2akTG4dOmSsCSib6uCIUOG0B9//FHjfeC3nTFjBn3xxRcaz91zzz3C/WpKLbvMGZy7sHwqCz5YVmFxZRjGsLDFjmGMIKQgOJRFHaxQ6pYzQ6FomaXcxgouWsScGYuXX35ZRdTJZDJhraspKI0ycuTISkXd448/LtqosajTHTh3lS2pcNHiHGcYxvCwsGMYAwOr019//aUhZFq2bGm0McE69uyzz2r0rd23b5/Bx7Jjxw7atm2byrbp06eLAs41Aa7kvn370vbt2yt1737//fcm3z7N3GjVqpWGkMN5XhsLK8MwuoFdsQxjQGCF6tChgyhnosDf318kK6CMhzHJzMwUyQnp6ekV2+AuRtYuLGbmEIx/6tQpUc4E4k4ZCDlka06YMEEv42YqT3bB32fOnGEhzTAGhC12DGNAEMivLOoU/U6NLeoAXJPqQe9YlCsr5Ksv0LNWWdSBDz/8sEaiDtahPn36aIi6hg0b0p49e1jU6Rmcw+pJNxDkS5cuNdqYGMYaYYsdwxgIWDJgwYBlQwEyMlGt31QKuqJgclhYGJ0+fbpim7e3txCj+L+hC96Gh4eLOnbV/T5fffUVPf/886KgsTLYH1yy+D9jnILS6OIBgefr62vUsTGMtWAaqwnDWAFz5sxREXUA/U5NRdQBJFBgTMpkZGTQ22+/rffPfvPNN1VEXU1+HwhRdItAfKC6qEOc3aFDh1jUGRAcK/Xzx9T7EDOMpcEWO4YxAMeOHROtw9SzM9F5whQZP348/frrryqCD4WTER+oD06cOCGsc8rTEQoKo/+oNtAKDf1uKysRM3HiRNH71tHRUS/jZaoG57Zyf19kXuMawDFmGEa/sLBjGD0DSxJ6sKIYrgI3Nzfh3qxLayxDkJCQQG3atBFlT5Rrv6HWna7bbmEK6tevHx04cKBim4uLi0goCQoK0urWfuCBB0QLK3XQcgytrrg9mPG4fv26CDtA2RkFvXv3FseYjwvD6BfT8QExjIWydu1aFVEH3nrrLZMVdaBp06b06quvqmzbu3evyCyFaJo6dWqloqqmoFH8lClTRF/c7777TkXUKdzW2kQdCt8iNlH981EUF1Yi7JPFg3FBpjdc68oglhTXAsMw+oUtdgyjR2CxQP21a9euqdT8gjgxdTchWnDBapeUlKQSQ6WIZYNVDc/VNqkCLacgHBXWQIgw5WkI3S6QGYvuF+rs2rWLxo4dK+K2lPH09KTffvuN+vfvX+vvyeiH4uJi4bpHFxEFAQEBwhILizXDMPqBLXYMo0dQqkNZ1AF0UDB1UacQbh9//LHKNuUEBQi/f//9t9b7xXuUXbzq95boYlCZqINlD23F1EVd8+bNRZIEizrTAuf4p59+qrIN14Ip9CFmGEvGMFVHGcYKuXz5skabsPvvv18U0DUHCgsLVRIotBU1RmYqMmdv3LghHrdSU6m4sJDKy8rI1s6OHJ2dqZGfnyh3gUdaWlqV+1y/fj0NHTq0QtxBTCKrsjJB0LNnT9GjtFGjRvX8tow+GD58uOhDvHPnzoptn3zyiXDDt2jRwqhjYxhLhV2xDKMnRo0apZKxie4NKPgL96Y5gGLFiAXUBooGv/766+Tq4EBF+fkkyeXkVlhIHhkZZC+Xk60kUbmNDZXKZJTt7U15zs5kI5NRUWkp7f7nH9ElQr28iQIkm/z888+iuPBjjz1WqcAcN26ciKlzdnbW6fdmdAvc6p06dVLpjTx69GjRso5hGN3Dwo5h9ACyRwcNGqSyDb001V1TpgwK/qIThDpI+ujTuzeFNG9OnjY21Cr1BvlnZJBHfj7Zl5Vp3V+pnR1lu7rSVTdXuuznRwX29hQXH08HDh5UaUOlANY9xGShFIo6EJTo+2pKNQAZ7eDcX7x4sco2dANBpjXDMLqFhR3D6KHfaZcuXSgmJqZiG1yFqL6PIH9z4dy5c6JFV1ZWVkUtO5SsiOjWjXzy8ig4Lo6CM7PIu5aB8FnZ2ZRbVEjpQUGUGBJCaW5uFHnsmMiahFu3KjAGdJl48skn6/XdGMOCcwiFopXd8EisgGg3VB9ihrEW+HaXYXTM119/rSLqAKxL5iTqQPv27cXCC8tj48aNafKjj9I93bpRhwsXKGzvXmqcmEjlxcW13q+8tJTsysvF+7Ef7A/7nTxpkvgcbaA11Z9//smizgzBuY9rQBlkhuNaYRhGt7DFjmF0CCwSsEworFyga9euouo+rE3myNWrV+mnVavI5do1anP8OLkoZaXa2cnItwoxVhk3bt6ksrL/4q1AgbsHnQ8Po2suLvTr5s2UmJio8jzKY8Ci17Fjx3p+G8ZYwBrbrVs3Fde6l5eXKNSNWEqGYXQDW+wYRocg2UBZ1AH0zjRXUYcOFJt+/pmCbtykQecvkGfhf2VKgKODQ6336aD2HgcHR2okl1OX/fupeWYmPTx6NAUHB2vUA4SFhzFfcA0sWbJEI6sanUIYhtEdLOwYRkcgy/Obb75R2TZhwgQRp2aOIKHht/XryTshkXqeO0eovOfj40MNGriTTGYv6tx51MG9DLeci7OL2Id7A3dq2NBb1MSzKyujdocOUXB6Oo0bOVLDLVtd6RXG9Onbty899NBDKtvgjj19+rTRxsQwlgYLO4bRAYhomD59ukoBX5Th+Oijj8gcQWmK7Vu3ksu169QjJkaULlHQwM2NGjdqRJ4enlSXxl02d8Qd9gEXa15ePkl0e//4nLZHjlBAQSGNGDpUxdLZvXt3nXw3xrig6LVyiRpcM7h2OCqIYXQDCzuG0QEbNmygf/75R2Xb7NmzqUmTJmSOREZGUmZyCoWfP08yJbGqD5TFMIDlrk3UcQr09hZZuGFhYaKo7cyZM/U6DsYw4JpAuRpl/v77b9q0aZPRxsQwlgQnTzBMPYEbsW3btioB/+iFisKs5lg8F22f1q5aRW3OnKXWycl6/7xySRIdKySpvCIhA10nkkJD6FKXLvTIlCmiqTxjOaCrCQp1W8o1wzCmBFvsGEYHriX1LE5YmMx1gTp44AC5Ibs3JcUgn2drYyOKHjds6EO+vn4iy9bD3Z3a3bhJDdLTKfLAAYOMgzEcuDZwjagn6qhvYxim9rCwY5h6AEGnHkeHZvRjxowhcwRZivFxcdQqIVElrk7f2NzJsLVT6iSBz2+ZkEjxsbFiXIxlMXbsWLr77rtVts2fP5+SkpKMNiaGsQRY2DFMPXj11VeFW0kBWlyhpIONTV3SCkwjs9e+oICClDoEGJMmaWkkKyjgrEkLBNcIrhXltnC4ll577TWjjothzB0WdgxTR/bv30/r169X2fb000+LhufmWkD2THQ0BScmic4QpgDG0TQpiU5HRVXbbowxPzp37kzTpk1T2bZu3To6wO53hqkzLOwYpg5AZKBEgzKoov/ee+8ZbAzJycnC5duiRQsKDw+ne++9l44ePUrvvPMOBQUFiX61eCCzFFm7ir9RIBjiE/9esGBBxf4yMjKoKD+f/DMy6jWuywUFNOJEND14IpoyS0vr/T3902+PC+OrD6NGjRLHCC5AxnR4//33NdrtvfjiiyzkGaaOcPdlhqkDK1asoJMnT6psg6gzVGskJLOPHDmSnn32Wdq4caPYhvFcuHBB/BvlJJ5//nmV9ygETbNmzUR7LtSQU+b69eskyeXkmZdXr7HtTk+nBxs1pieCgmr0+jJJIrsqXNce+fliXMicbdSoUY3Kpyi79xRAiE+ZMoV++OGHGo2LMQwoeo1rB2JOAdqOrVy5kqZOnWrUsTGMOcLlThimliCQPzQ0VPSFVdC+fXshrGQyw9wr7dq1iz788EPat2+fxnOw2GGxVBd2CiDs0J4Lwg5CFN0xUIPv5ZdfphWLF1PW5StULJXTmMa+FeKs++FDNNrXlw5kZpK3vT193a49udjZ0aqUFFqXep0cbGwpzL0B3dvQh16LvSiEWns3N/qqbTv6MP4KHczKEttea96cIjy9aNONG7QnI52yS+XkYS+j1i6udL24mOKLCulmcTG906oV7UlPp6PZ2WI/9z/+OLW+/35ROBnfr6ioSPzm33//vbBAKn8PdKhAKY3KQL20L774QlgwGdMBxxUW5HPnzlVsg4iPjY3VsOYxDFM17IplmFry7rvvqog6gCBwQ4k6gHpfWAi1ARerwvWq3sJJGbg3hwwZQmfOnCFnBwea1KkT/da1K23tGkY70tOE2AJZcjn19fKi38PCydfBkXam3/7+XyYl0m9dutK2sDB6pVlz8ZqH/fxpWlATIf6wj4TCItrWNYyWtW1Hb8bFUfGd+L0L+fn0dbt29GXbduLvlOIi+qljJ/q4dWt68fx5Gu3rR3+EhVNSURFlxMbR5bg4UVpm7969wqIDF/S3336r8T20iTrGdMG1s3jxYpVtt27dMmhoA8NYCuyKZZhaEBMTIyw+6rFbAwcOJGMCNyvGFhERQYGBgZW6YrXVExs2bJj4d3FhIR2Ni6MlZ8+KosGpJSV0paCA/B0dydXOTljaQAc3N0opui34Ork1oFcuXqQhPj50byVu6KicHHqgUSNRqy7IyYmaOTuLfYK+nl7kpiSG7/byFlY9WO/weZ0bNBDbQ1xcKSs7mzJiYkR2bK9evW6Pt7i4YuzK34MxTxAjivCCzZs3V2xbunQpPfnkk6IAOMMwNYMtdgxTQxC1MGPGDJWgbkdHR1q0aJHBx4KFDqVJFMC1uGzZslrXe3Nxcan4d2pqKu24cIF+7NiJtoWFU08PDyq50w3CXikGDiINcXHgm/bt6ZEAfzqRm0MTz9SuJImTner043AnLg5lMBT/vv15+O3LhbsO4g0ubzxgtVQUtFX+Hoz5gmsJrnUFOOa45jhiiGFqDgs7hqkh27Zto507d6pse+WVV6h58+YGHwsshFlZWSqJAMr19OpCSWkpOclk5GZnR6nFxSIuripg1YOrtrenF73evIX4t0LwKQh3d6ftabfEwpxSVEQJhYXUog4iTCIbEdeImEJ0KAA5OTkUHx9f630xpgvc6+o9gXfs2EHbt2832pgYxtxgYccwNQBuPyQXKBMQEKDRzNxQIOtzy5Ytwm0FYQn35Oeffy6sG+oxdnjURPSFhIaSv5cXDY6OojlxsRTu7lHl6yHi4IZ9IDqKRp88Qc81CdbIbr2voQ81cXKi4Sei6ZnzMfR+SAg5VpKxWh1yOzvyadRIxNShxAvKtfTr169C5NXU1Tdu3Dj6448/RDmYQ4cO1XocjP6ZM2eOuLaUwXmNa5BhmOrhrFiGqQFoG6Yu4n788UeaOHEiWQp79uyhizt20KBDh8nU2NWrp8iKNXYsI2MYcG1NmjRJZdvChQtp1qxZRhsTw5gLbLFjmGpAfbd58+apbEPR3//9739kSfj6+lKeszOV2tmRKYHxYFwYH2Md4IZJkSSjXMgYcaAMw1QNCzuGqQZY6vKUivYiuB9uT3PtB6sNCCcbmYyyXV3JlMB4MK6aCrsePXqouKHxSE9P1/s4Gd33kVUmNzeXZs+ebbQxMYy5wMKOYargyJEjtHr1apVtkydPFi28LA1vb29ycnWl697eZEpcb3h7XBhfTY+ZInNW8TBURxBGd3Tr1k1ca8qsWrVKtM1jGEY7LOwYporWVMptjoC7u7vo+GCJ2NnZUcewMEoMbkJldUhw0AcYR0KTJtQpPFyMj7EucK01uFPPUAGuSVybDMNUjmnM3gxjgqxZs0bDOjB37lyLjvXq3Lkzlbq4ULKPD5kCST4+JHdxEVmwjPXh5+cnrjl1i+xPP/1ktDExjKnDWbEMUwmI50HdNOVgbfyNllXKBVQtkQ2//EJphw/TgONRZKuD6aG4pITkqJHn5FQrq1u5jQ3tuyucfHr1orHjxtV7HIx5UlJSQh06dKC4uLiKbf7+/nTx4kUNax7DMGyxY5hK+eCDDzQy8D777DOLF3Ugom9fyvPxobjAwHrvq7CoiNLT0yg7J1v0/iyrhQstNjBQjCOiT596j4MxX3DN4dpTz1S31JAIhqkvLOwYRo1Lly5pLCRDhw4VD2sA1pBuERF0ISSEcurZqquoqKji3+VSORUU5NfofdkuLnQxNIS69+kjxsNYN2glN2TIEJVtn376KV2+fNloY2IYU4WFHcOogQ4TcP8okMlkGkLP0omIiCCvoECKatuW5PVIpECHDGWKiqrvHoDPi2rXlrwDA0W9QIZRCDlciwpwjaq3H2MYhoUdw2j0pURPWGWmT58u4uusCSygw0aMoIKAADrSvp2Id6sLTo6OKn+XlpZW6Y7F5+DzCv0DaOiIESoLOWPdtGnTRiNLHW31du3aZbQxMYwpwskTDKMkOpAVev78+YptjRs3ptjYWPLwqLpvqqWCXqwb160j78RE6nEuhmQ1iJHDhFKQn48qs+Ti7EypN26QJP33Pk9PL7G9MksdRF1GcDCNmTCBmjZtqvPvw5g32dnZ4ibr5s2bFdvatWsnahXa29sbdWwMYyqwxY5h7vDll1+qiDowf/58qxV1AOIKIiurWXP6t2vXGsXcIUkCyRLZ2Vl0Ky2NHNWsdsVKcXfKMXX7w7qKz2FRx2gD16J60kRMTAx99dVXRhsTw5gabLFjmDtiJCQkRFgEFKC7BOrYqceJWSPIEN6+dStlJqdQm7g4CklJqbQUSlFxMWVkqLbvcnV1o/x85ZZstqI+mc0d1yuyX5EogZg6uF/xHMNoo6ysjLp3707R0dEV2zw9PYVlvVGjRkYdG8OYAizsGIaInnrqKfrmm29UtkVGRnLwvhJyuVz8JsciI8ktLY1aJiRSk7Q0slNyz6alp1NJiWqChIO9A5WU/peMAjwbNaabAQF0uWmwKGmC7Ff81hxTx9QEnId91Mrg4Br++uuvjTYmhjEVWNgxVs+JEyeEdU75Upg4cSL9+OOPRh2XqXLt2jU6GBlJ8bGxJCsooKZJSeSfnkHOmZmUffNGpe+R2cmoyIYo39OTMvz96UarVmTr6UnNQ0NFnTouacLUFlyja9eurfjbxsZGWPG6dOli1HExjLFhYcdYNTj9+/XrRwcOHKjY5uLiItw6gToo0GvJZGZm0unTp+l0VBQV5edTYV4eOWbnkFdONslKSjC5kGRjQ3IHB8ry9KQCV1eSSxLlFxVRys2btHz5cvLy8jL212DMlOTkZGrdujUVFBRUbMO1/PfffwuRxzDWCgs7xqr5+eefacKECSrb5s2bR2+88YbRxmSOMU87d+6kWbNmiT66vj4+5OTgQDI7O5KXlVFRSQndSEujGzduiEd6eroQ1ImJidSkSRNjD58xY3CtvvXWWyrb1q9fT+PHjzfamBjG2LCwY6wW3Onjjh93/gqaN28usuzQ15SpOWPGjKFNmzZV2xpKufAz4qEQF8UwdaWwsFCUO7l69WrFNtwsXLhwQVjeGcYa4XQ/xmr56KOPVEQdWLRoEYu6OgDrW3W4u7ur/P3HH3/ocUSMNeDs7CyuWWWSkpLo448/NtqYGMbYsLBjrLbw7sKFC1W2DRw4kEaOHGm0MZkzL7zwQpXPI+Zp3LhxKtt2795NxcXVtxhjmKoYNWoUDRgwQOOmrSY3GwxjibCwY6wSxIMpN6i3s7OjxYsXc9B1HXn00UeF+6uychPffvstXbx4kd59912V3xeu8H/++cfAI2UsDZxTS5YsUak3CRctrnGGsUZY2DFWB7Lmfv31V5VtzzzzDHXo0MFoY7IEEK/44IMPVmpRQfFnFI9FYVll2B3L6IKOHTuKa1iZX375hfbv32+0MTGMsWBhx1hdkd3p06erbPP29hbWJEb/DBs2TOVvFnaMrnjvvffEtazMiy++KLK2GcaaYGHHWBVwC6L2mjLvv/++xoLA6IehQ4eq/B0XFyceDFNfcA1D3Clz6tQp+u6774w2JoYxBlzuhLEaMjIyKDQ0VNRRU3bhoFo9t7LSXU9Z9S4SaWlp1LBhQ/Hv8vJyCggIEPXsFCC2Ud2KyjB1tciHhYXRmTNnKrbh3MPNAxfDZqwFttgxVsM777yjIurA559/zqLOgCDAXd1qx+5YRlfgWsaNgjK45jnUgrEmWNgxVsHZs2dp2bJlKtvGjh1L/fv3N9qYrBV1YYdklry8PKONh7Es7rnnHho9erTKti+++EIUHmcYa4CFHWPxINrgpZdeUgmiRhFiLmJqHAYNGqRiJUU3ir179xp1TIxl8cknn5Cjo2PF37j2MQdw5BFjDbCwYyyeLVu20J49e1S2ocZVs2bNjDYma8bDw4P69Omjso3dsYwuQWtA9Tp2u3btoq1btxptTAxjKFjYMRYNihC//PLLKtuCgoLotddeM9qYGE137Pbt29mawuiU119/nQIDA1W2YS7gbieMpcPCjrFoPv30U4qPj1fZBhesq6ur0cbEaAo79OxFHCTD6Apc4+ptA69cuUKfffaZ0cbEMIaAhR1jsaSkpNCHH36osg0uwIceeshoY2Ju065dO2ratKnKNnbHMrpmwoQJFBERobJt3rx5dO3aNaONiWH0DQs7xqJdMfn5+So9JVHehPvBGh8cg8rcsQyjjz6yytc85oTZs2cbdVwMo09Y2DEWyaFDh+jHH39U2TZ16lTq2rWr0cbEVN1e7ODBg5SZmWm08TCWSXh4OE2ZMkVl2+rVq+nIkSNGGxPD6BMWdozFge4G6BGpnokJFwxjOgwYMECjJMXOnTuNOibGMkFIhru7u8q2F154QcwVDGNpsLBjLI4ffviBjh8/rrLt7bffpsaNGxttTIwmLi4uQtwpw3F2jD7AtY85QJljx44Jyx3DWBrcK5axKHJyckQ/WOVepG3atKHTp0+Tvb29UcdmDVTXK1YddASA5URBo0aNxD7QeoxhdAkKYXfq1IkuXrxYsc3X15diY2M1rHkMY87w7MlYFO+//76KqAPoHcmizjRRT6C4deuWhrWVYXSBg4ODRqkTzBUffPCB0cbEMPqALXaMxYA77w4dOlBpaWnFtuHDh9O2bduMOi5LB7FxGRkZYpG8fPkyrV+7lpwcHUlma0vy8nLqP2AABQYHC+sIHt7e3mRnZ6diUVW2osBl9s477xjp2zCWDuYE5Qxs3PSdO3eOQkJCjDouhtEVLOwYi4EnbMOCDNZTp07RmehoKsrPJ0kuJ9f8fLK/do1kJSVkU15Okq0tOXh4Uk5Db8pzdiYbmYycXF2pY1gYde7cmby8vGjmzJmikLSCbt260dGjR4363RjLJS4ujtq3b883gIzFwsKOsQj+/PNPDbfeq6++Sh999JHRxmSpoLjrwQMHKD4ujuwLCig4MYn8MzLIIz+fbEtL6caNVJXX+/n6iZi5Ujs7ynZ1peve3pQY3IRKXVyoeUgI2Ts60siRI1Xegzg7WPcYRh9gbkAHGvU5ZPDgwUYbE8PoChZ2jEUERXfs2FG4YhX4+fmJvxs0aGDUsVkScrmcIiMj6VhkJLmlpVGrhEQKSksjO6WSEWXl5VqFnTJltraU7ONDl5oGU27DhrRj3z76+++/hVsXrFq1ih577DEDfTPG2uAkK8aS4eQJxuxBZqWyqAPz589nUadDYEH74fvv6dievdTmzFkacDyKmt68qSLqagPeh/djP23PnqP+4eE0edKkipI0XPaE0SfIgsUcocyFCxfEXMIw5g5b7BizBnfcuPPGHbiC7t27i84TXDJDNyQkJNBv69eTy7XrFH7+PLkXFGh9bU0tdurcsLGho61D6ZqLC/26eTNlZ2eLDFm2njD6AsWJe/TooZKFDcGHGDyuecmYM7zyMWbNG2+8oSLqAHpDsqjTnajbuG4decVfpb4nTlQp6uqDT1kZddm/n5pnZtLDo0eLTiEQ5wyjLzBHoHe0MphL3nzzTaONiWF0Aa9+jNkSFRVF33//vcq2SZMmUc+ePY02Jktzv8JS552QSD3PnSOZHtsv2dnakpONLbU7dIiC09Np3MiRIpidYfRJr1696JFHHlHZ9t1331F0dLTRxsQw9YWFHWOWIIIA/WCVIwlcXV1pwYIFRh2XJSVKbN+6Vbhfe8TEkG0NIzZgBbG1/a9GHf5tU0PrqaOTk/ictkeOUEBBIRXk5IhxMIw+QeY85o6q5haGMSdY2DFmybp16+jgwYMq2+BCCQgIMNqYLAlkv2Ymp4iYutpY6myIhBsVgg4PTw8Psa0moKgxsCsrozZRx8nLyYmTKBi9gzkDIR3q5//69euNNiaGqQ+cPMGYHfn5+dS6dWtKSUmp2NayZUtRjNjxjjhg6lenbu2qVSL7tXVyssE+FxPRjdRUKpduC8nk1q0pPjycnnjmGY3+swyjS4qKikTR4itXrlRsCwoKEpmyytY8hjEH2GLHmB1wtyqLOrBo0SIWdToCxYdRpy5E7TfWN7DsOTr9dwwDYmPFOCIPHDDoOBjrw8nJScwhyiQnJ3OBc8YsYWHHmBXx8fEaFeMHDRpEI0aMMNqYLK1NGDpKoPhwTePqdImjo1PFv/H5gbGxdOXiRTEuhtEnDz74IN17770q2zDXXL161WhjYpi6wMKOMSteeeUVKi4urvgbzeQXL15MNjY1jeRiqgK9X9EmDB0ljMHtOLv/jmXDxESyzcsTHQEYRp9gDsFcgjlF2UU7a9Yso46LYWoLCzvGbNi7dy9t2rRJZdtzzz1H7dq1M9qYLAm08zoTHS16v9a1o0R9QVatg1JRYowj4Eo8nY6Kqmg3xjD6AnF2zz77rMq2DRs20L59+4w2JoapLSzsGLMAZS+mT5+usq1hw4b0zjvvkCUhk8moS5cuFY/CwsJa72PhwoV1+uyMjAwqys8n/4wMle1fJCbQ0OgoGh4dRaNPnqCkoqIq9/NtclK93j8kTrU9nEdyshgXxlcVsLagb3B9uXjxInXt2lX8/p07d6atW7fWe5+M+fDuu++KuUUZzD1ceocxF1jYMWbB8uXL6ezZsyrbPvjgA/Ly8iJLwtPTk06ePFnxcHZ2NoiwgzUM7dkkuZw88/Iqtkfn5NCR7Gza0qUr/R4WTsvatiN32X+uqsr4VimTti7vV3erO2WkU3lpqUrDdl0IO7SUqoymTZvSkSNHxO+/c+dOeuaZZ7immRWBOWXevHkq286cOUPffPON0cbEMLWBhR1j8qSnp9Nbb72lsg2WlKlTp5I1sGPHDlEhH1YkVMlXiJdp06ZReHi4cB998sknYhvqcWVlZQlr09NPPy0Cv++66y6VGMVVq1aJfzdr1oxef/11sV+4uX/88Uf68ptvaNTx4/ThnbIPt0pKyEtmT/Z3igz7OTqSh+y2q/TfzEwaf+okPXgiml65eIFKysvp06tXKVcupxEnomnupbhav1+Bosjx2sxMeu7qVVqybBktXbpURdR37NiROnXqRJ999hl9+eWXokxL7969KxJp1qxZI17ToUOHioQb/B7Y9vDDDwsXfmUWUWRIOjg4VMRYsaizPp588klxbimDOag6qzHDmAIs7BiTZ+7cuRpZkejxqBzkbCkoRBkeEK5paWlClEB4nThxglq0aEHffvttRdkXtFVDwsPGjRspKSlJCB6F1e/rr7+u9vOaNGki9ouaXfv27qX3hwyhbWFhlFlaSvsyMijC05OuFBbQkKjjNO/yZTqTmyvel1FaSt8lJ9PqDh1pS9cwauLkRL+kptLLzZpRA5mMtnYNo/dahdT6/cpJFMcKCuiWXE5fBwbSu/ffTwcOHBBWWxQtxu+B5u1IqnjsscdErCUKzaJoNVynKIcDN/0///wjXoeC1vitwPnz52nOnDmiRpk2i2hMTIwQgBDNy5Yt4+QcKwNzi3ofWYi6t99+22hjYpiaIqvxKxnGCMAFoi5Qxo8fT/369SNLRCHKFPz+++9CvMBiB5ARPGzYMPFviBX0tYQbFTW3IFQg1GrDuHHjxP/37NlDcXFxNPfSJXIuKaGisnLq4OZGA7y9aXPXMDqSlUUHs7No8tmztKRNGyqRyuliQT6NP31KvB/Wtv7e3hr7d5PJ6vR+tBeDsDtcUECnk5OpZPt2KrK3p9jYWCHwJk+eXFG30LuSzz127BgNHDiw4rmxY8eK96GkRWhoqIY1Rh1Y83DuXbp0iR599FEaPHiwsOQx1sPdd98tro9ff/21YttXX31FTz31lLACM4ypwsKOMVngAkPQsnIsFCws6nXsLBl8dwi5lStXqmxHhXy4Hw8dOiRaeEG4KJeBUU7GUP791F/j4uJS8Tn9+vShR7y9qfOVeNV92NhQhJeXeHjL7Gl3Rjr18fSi/l7etCA0tNrvUJf3Q7TBAfqYlxcNdnenK507U0a3bjR69Ggh0OqD4jvXhFatWgmxDUuhskubsQ4w12zbtk245AFuojAn7d69m624jMnCrljGZEFpE/UyA6+99hoFBweTtQBLHX6DhIQE8XdOTo4o0pybm0tubm7k7u4urHVYaJTdSIrSII0bNxaxZ3h9Xl4e7dq1q9LPgXXrWFQUZd8RfuklJXSzpISuFBRQ4p04NAjt2IJ8CnB0pK7uDehIdhal3Fnw8uTyimxXOxsbKrsTl1aX9wNbGxvq5e5B23Nzqai8nCQbG7py9SplZ2eLIrIQugqRqoh7atCggfieoHv37sIKCRc+XodzqW/fvjX6zRMTEyv2jd8Oog7xiIz1gUSaV199VWUbwgA2b95stDExTHWwxY4xSRDUjkB/ZeBmtLZioY0aNRIxdWPGjBFJE6jzhuzP/v37U9u2balNmzZCdPTp06fiPYg5Q3wY3NVwY2NhQoIEBDG2VwZiyUaNHEnvrV9PzkVFItnho5BQKpbK6b3LlynvjlBs7+pGk/wDyMnOjua1CqEXLpyn0vJyYb14o3kLESs3qrGvKG3SzcODxvv51fr9Cvr7+NClgnx6NiWFijIySO7gQG+98w4NHTpUxMuFhYWRvb29cMvCioKA9wEDBghXK+LsEA+F3wCCEr8JXl+TLgJwhSMJBQIZv/eSJUvIx8dHB0eTMUdwM4kbCcSwKpg5cyYNGTKE3fOMSWIjccoXY4Kg3IB6Juz69etFfB2jH2DhurhjBw06dJhMAdQNu3nrpvj34XsH0c64WJEBW118HMPoGsw9yKRWn6NwA8Awpga7YhmTA67F+fPnq2yD5UUR6M/oB19fX8pzdqZSE8k2RnygnZ2M5DIZFTZwE3XskBHLMIYGN5TqrvwPP/xQZF8zjKnBwo4xOeA6LCgoqPhb4Q7jYGX9CzsbmYyyXV2rfJ28rIzKDWToR9mTfE9PkkuSEHbbt2/XaX1E5S4fePTo0UNn+2csB8w96nMQ5ii4aRnG1GBXLGNSIONR/c4Y5QVqUpONqR9IuFi2ZAkFnjhJHSuJRYOYQ529oiIkQ9iQl5cnOTvVvjNGbSgqLqaowAA6FRgoihRjYUVtP0vrOMKYB5iL1DtQREZGisLYDGMqsMWOMRkUpQSUQakJ9fY+jH5AskDHsDBKDG5CZXc6RSgoKy8Tguq2qAMS5eb+13pMX8icnCi5aVOKPnNGJEGgLAs6cTCMMcBchPJCyrz44ota29MxjDFgYceYDMg8i46O1mjIzRmJhgOt2kpdXChZ6TcvLS2lW7fSSC4vVXktXOT6JrlRIyp1dhbdNRRwnB1jzCx1dDRRBhnaijZ9DGMKsCuWMQlQnywkJIRu3bqlUv0fpSdQ0oIxHBt++YXSDh+mAcejqKSoSNSCkyRVi4StjS019PEhe5n+KiaV29jQvrvCKdPHh+YoZR9C6CPezhDCkmHUwY0OboDQmk4B6kWiK4q6NY9hjAHPjIxJ8N5776mIOoB6bSzqDE9E376U5+NDZxv5iOK/6qJOZicjn0aN9CrqQGxgoBjHoPvuU9kOlzBahjGMMcCchLI7yty8eZPef/99o42JYZRhYccYHfQ4VW+4jZ6egwYNMtqYrBlYH25lZ9OZZs2owL2BynMODo7k08iHZHouiZLt4kIXQ0Ooe58+opUXijErw+5Yxpjcf//99MADD6hsQ9bsxYsXjTYmhlHAwo4xKogEmDFjhihGq8DBwYEWLVpk1HFZK/n5+aIfK37/lMxMOh8eTmV3RJyzsws1bNhQuGH1idzWlqLatSXvwMCKbEN0m1CGhR1jbD799FMxVynAHPbyyy8bdUwMA1jYMUYFC/Rff/2lsg2TY8uWLY02Jmvl+vXrohA02nEhQ3nr9u10zcWFzvfoQa7u7uTl6Un6riSIuLoj7dtRoX8ADR0xQhQprkzYHT9+nFJTU/U8GobRTqtWrcRNqfp8xjcdjLHh5AnGaKD3aYcOHSguLq5im7+/v3BnoKE7YzjOnDlDw4YNU+mHCVq0aEFTJk2iwJs3qce5GJLpsawDLHUQdRnBwTRmwgTRgF35XEHSRG5urkoW9eOPP6638TBMdeB8RG9i5ZsM/I3rSdmaxzCGhC12jNFATIqyqAMfffQRizoDg7pwERERGqIORYC///57emTyZMpq1pz+7dqVclxc9BZTtz+sq/gcdVEHsEiqx1zqsgsFw9QFzFULFixQ2Ybs2KVLlxptTAzDFjvGKOAOF3e2yhYYtHM6ePAgl7EwIKii/+yzzwrXq7qlDi6l1q1bVxyv7Vu3UmZyCrWJi6OQlBSy1cHUAdcrsl+RKIGYOrhf/fz8Kn3tihUraOrUqRV/u7u7iwxZzpxmjAmKE/fq1YuOHj2qcm5C4KFNH8MYGhZ2jFGYMmWKcKUpc+TIEerevbvRxmRti9Hs2bNp4cKFGs8hYWHz5s2iGKsyCA5H+6RjkZHklpZGLRMSqUlaGtnVwT2LzhZJPj50uWmwKGmC7Fd8riKmrjKuXbtGgYGBKtv+/vtvuvvuu2v9+QyjSzB39ezZU2OOw80IwxgaFnaMwUENMnUBh1gpdaHH6IfCwkKaNGkSbdy4UeO58ePH0w8//EBOTk5VCqyDkZEUHxtLsoICapqURP7pGeSRn0/2apY/ZUrt7Cjb1ZWuN/SmhCZNSO7iQs1DQymiTx8RW1kTwsLC6MSJExV/z5o1q1JxyjCGBnMYrh0F6GsMKx7K9TCMIWFhxxjcUoR4rsOHD1dsc3NzE7F22lxwjO5AIdURI0YIC4M6sOChF2ZNXeHoSHH69Gk6HRVFRfn5JMnl5FZYSO4ZmeQgl5OtVE7lNrZUIpNRjrcX5Tk7k41MRk6urtQpPJw6deok4vhqw1tvvaXSO7h9+/Z09uzZWu2DYfSVVY7wkry8/3oow0ULKzdEHsMYChZ2jEH58ccfhbVIPWHi1VdfNdqYrAW0QELZkKtXr6psh/vz66+/pieeeKJO+0V8HjpUoM0XHrdSU0UrsjK5nOxkMnJwcqJGfn4i3ggPb29vsqtjgeNDhw5V1LZTgO+jnmzBMMYAc9nrr7+uMedNnDjRaGNirA8WdozBwJ0sgvHhylOuBQWLi6Ojo1HHZuns3btXFB5GT15lEOQNl+y9995L5gBEJMRhenp6xbZly5bRM888Y9RxMQwoLi4WVuTLly9XbAsICBAlnOCZYBhDwOmHjMH48MMPVUQdQM9FFnX6ZdWqVaIFkrqog5ULWcjmIuoALH2DBw9W2cYFYRlTAXMZOlIogzlPvSQKw+gTttgxBgF3sO3atROFZhVAbPz5558cf6IncGnPnTtXJSZNQbdu3USHCXOMa1y7dq2Ka8vZ2VlY8PB/hjGF6w43Hzt37lQRfDExMaKMEMPoG7bYMQbhlVdeURF1iOtavHgxizo9UVRUJMRPZaJu1KhRokyIOYo6xQ2BcoIHsnz/+ecfo46JYRRgTsPcply6By5azIEMYwhY2DF6Z/fu3aIumjIvvPACtWnTxmhjsmRQtBddGtatW6fx3MyZM+nXX38lFz11kDAEDRs21KgZxu5YxpRo27YtPf/88yrbfvvtN9qzZ4/RxsRYD+yKZfRKaWkpdenSRbghFKDwLaqye3p6GnVslgjKxiDz9dKlSyrbYeH64osvLCbJ4IMPPqA333yz4m+4uPCd2QLMmApZWVkUEhIibrQUILHi5MmTVRbiZpj6whY7Rq989dVXKqJOsSizqNM9//77r7BkqYs6ZOP9/vvvFiPqAMSrMleuXBE3CwxjKmCOw1ynzLlz50RpIYbRJ2yxY/QG7lRxx4o7VwVdu3YVnSfqWseM0Z5QMHnyZJU4RoAWXNu3b6fOnTuTJYFpC98NRWEVIBtxxowZRh0Xw6iX50HnCVjpFKAoNyzrCClgGH3AFjtGb6BLgLKoA59//jmLOh0LnPfff18kSqiLOohodJiwNFEH4HJVt9pBwDKMKYG5DnOeescWzI0Moy/YYsfohVOnTom+nmghpmDChAnCssToBgi5adOmqfSnVDBs2DD6+eefLbooKoLRUXRZgb29vSh70qBBA6OOi2HUefjhh2n9+vUqMa/oeYy2egyja1jYMToHp9SAAQNUSlCgxhiqrzdp0sSoY7MUcNcPUYOyJeogGw/lFizdMpqTk0M+Pj4iQUfBpk2bRDkXhjElkpKSRNcdlOZR0L9/f9ERhhN+GF3DrlhG52zYsEGjrhgazLOo0w1IFEC/VHVRhwViyZIltHTpUosXdYp2aH379lXZxmVPGFMEc596D1lcv2jnxzC6hi12jE4pKCgQNZwSExNVWlehAT13Bqg/hw8fphEjRtCtW7dUtqMuHerW4TlrAgkTqM2n3JczOTmZrSCMycFzI2Mo2GLH6JSPP/5YZeICn3zyCU9cOgCFheHiVhd16CCxf/9+qxN1QD2BAn05Ed/JMKYGbr4wFyqTkJCgsY1h6gsLO0ZnQNB99NFHKtsQRzJmzBijjckSgFEdv+v48eNFqzBlOnbsKDJfw8PDyRpB3FLz5s1VtrE7ljFVxo4dS3fffbfKtvnz54sYPIbRFSzsGJ3x6quvqgQHI/MLMV/sFqs7SAx46qmnNOJzwH333UcHDhyg4OBgslZwbiEDWBkWdoypooiDVe91jLmTYXQFCztGJ8AVqJzOD55++mlO568H2dnZQrR8++23Gs+hzAm6SSCBwNpRd8ceOnSI4uPjRZV/9dp+DGNsUFcS168yKE2EzjEMows4eYKpN1xdXT9ubYi6s2fPVhrHiIQBtoT+Z/Hw9vZWcVPDIoIaiq1atRJCD2VRGMZU4K48jD5hix1Tb1asWKEi6sB7773Hoq6OHD9+nHr06KEh6pycnEQpmVdeeYVF3R2wMKKKv/rvoSiMjb65lRVwZhhjghsNzJHKoGDx999/b7QxMZYDW+yYehfKDQ0NFXegCtq3by+EnkwmM+rYzJEtW7aIDh3KsYqgcePGtHXrViH4mP9AkWZ0oKiKhQsX0qxZsww2Joapafxsly5dKCYmRkXwwdPh6elp1LEx5g1b7Jh68e6776qIOoDgYBZ1tQP3V+gWga4J6qIOta9Qv45FnSZHjx6t9jUc58mYImiBh7lSGcyl6pY8hqktbLFj6gzuNLFoIsZOAYQJ2joxNUcul9NLL71EX375pcZzqFuH6vSIWWQ0mT59ukaTdbhlEQbg6+srHpMmTiSb8nIqLysjWzs7cnR2pkZ+fhXPIz6P45oYY4E5c/PmzRV/46b49OnT4oaOYeoCCzumTuC0GTx4MO3cubNim6Ojo6iirl5XjNFObm6uaBBeWYmOxx9/nJYvX04ODg5GGZs5gIQJJJmg56aHh4fIOAzr2JFcnZxIZmNDLnn5FFhaQvZyOdlKEpXb2FCpTEbZ3t6U5+xMNjIZObm6UsewMPFeFtCMMVoEQsQpZ3Dff//99Oeff3IsLVMnWNgxdQLxXg8++KDKtjfeeIPmzZtntDGZGykpKTR8+HCNxBOA33HOnDk8sdcAJEh8/NFH1NDDg1xKS6lJYiJ5XbtGrtnZ5GJrRz5aknhK7ewo29WVrnt7U2JwEyp1caHmISEU0bcv+fv7G/x7MNYLrnUUKlafYx944AGjjYkxX1jYMbWmuLhYJEhcvnxZpUfnxYsXyc3NzahjMxcg5iDqIO6UgXVu1apVIoGCqd6FHRkZScciI8n11i1qfOYseSUmkN2djFjg5OgkXK3VUWZrS8k+PnSpaTDl+fhQt4gIioiI4FhRxiDk5eWJLipoiacApXqQGQ9PCMPUBk6eYGoNgvyVRZ0i85BFXc2A27VPnz4aog5xYXv27GFRVwNSU1Pph++/p2N79lKbM2fpnqhoal9QQDK1+1T7GrqxIQab3rxJA45Hif1hv6u//158DsPoG8yd6u0YYYlWT65gmJrAFjumVly/fl2UN8EdpoLevXuL1lbsNqyeZcuW0QsvvFBRZ00BipVu375d/J+pGjRO/239enK5dp3Cz58n94KCiudKSksoPT2DJKmcZDJ7atSoEdXlrMxxcaGotm2pICCARj00npo2barT78Aw6mBOgJUYGfDKgi82NpZDA5hawRY7plagZ6myqFP0PmRRVzXIHH755Zfpueee0xB1ffv2Fd0RWNTVTNRtXLeOvOKvUt8TJ1REHXCwdyB/Pz/y9w+gxnUUdQD7xf49r8aLz8PnMow+QbcU9QxvzLWIv2OY2sDCjqkxR44codWrV6tsmzx5smgnxmgnPz+fxo4dS5999pnGcxMnTqRdu3Zxl44aALcoLHXeCYnU89w5kqkJZGV0cZuB/fc6e468ExPpt/W/sFuW0TvdunUTc6oyiLmtSb1GhlHArlimRsDK1KtXL5UJBg3o4SZALTCmciAGkNmGNmHqzJ07l9555x22dtYwUQIxdWUx54UlrSpRp/PPtrWl/WFdyb5tW3p0yhROqGD0Pmcg3AWlkBSgOPnBgweFVY9hqoPPEqZGrFmzRuOuEcKERZ12kNGGCVld1KHiPPqXomsHi7qagezXzOQUEVNnSFEH8HnhMecpIyVFLK4Mo0/8/PxE/2N1b8mPP/5otDEx5gVb7JhqwZ0j7iCVXVH4+8yZM1w8Vwtwr8L9mpOTo7IdPSDR27R///5GG5u5gRIQa1etEtmqrZOTjTaOC0FBdLFjB5o4eTIHszN6BcWKO3ToIPrGKsA5h5JSDRo0MOrYGNOHLXZMtXzwwQca8UWIF2NRVznfffcdDRkyREPUoSMHkiRY1NWOgwcOkFtaGoWolYcxNKEpKWIckQcOGHUcjOWDuVU9JhcVCT788EOjjYkxH1jYMVWCWkrqE8zQoUPFg9GMQ5w9ezY9+eSTKv1zQc+ePUUZgzZt2hhtfOZIZmYmxcfFUauERNESzJjg81smJFJ8bKwYF8PoE7TKww2iMp9++qmYkxmmKljYMVWCEh3KPQwROI7JhVGlsLBQ9HxdsGCBxnPjxo0TvUwbN25slLGZM6dOnSL7ggIKSksjU6BJWhrJCgpEk3aG0TeYa5WTdTAXz5w506hjYkwfFnaMVnbs2EHbtm1T2TZ9+nTR+ob5j5s3b9I999xDv/76a6V1/37++WdydnY2ytjMGVg9z0RHU3BikkqbMGMiOlQkJdHpqCgNqyzD6BpY+F988UWNHrI7d+402pgY04eFHVMppaWlNGPGDJVtsDipZ2tZOxcuXKhwsypjZ2dH33zzjWjsbeklCpDZ++yzz6rEAuH7o5SLInv633//1fp+FGXt0qWLeMA6ofj3V199RUX5+eSfkaGzsR7LzqZh0VE09uTJOu/DPz1DjCtDR+NCEdqBAweKLgOvvPKKTvbJWA64ftBBRZmXXnpJzNEMUxmWveIwdebLL7+k8+fPq2yDSPHw8DDamEyNv//+W9T2i4+PV9mO+n5//vmniLWzBry9vYWwVViwNmzYQO3bt694/r333hPdNbQBi8TJkyfFA1nDin8jyUSSy8ldqZ5Xfdl26ya9EBxMG7p0qdHryyqJ6/PIzxfjunHjRq0+W73jiHL5m7fffps+/vjjWu2PsQ4w56onTWBuRntChqkMLnfCaHDr1i3R3io7O7tiW3h4uKhjZ+nWp5qCOnQQbup3zcHBwaLnK0oVWAs+Pj6ig8aDDz4oXNJ49OvXTzwHq93jjz8uSr8MHz6cmjVrJv7evHmzEDRwKymXDsG+0tLS6OrVqzRgwABq6OREGQkJtKVLV3rxwgW6VVJCJVI5PRXUhEY0bkzJRUX0TEwMtXVzpdO5udTa1ZUWt24jrIgfxV+hvRkZ5GBjS0N8fMjX0YEWxMdTAzsZ9fHypDnNW9Cbly7Rxfw8crC1pfdbhVA7Nzf6PCGBkouLKKGwkNq6uVFhWRm52NnRmdw8ypaX0sLQ1rSwpJhu5OXRI488Im54FLUeYX1EHBQscIiPwvdAgWoIXYjVEydOaHXLo8MAah9+8sknBjpyjLmAm6bu3btTdHS0iuBDORR1ax7D8CrNaPDmm2+qiDqABYtFHRHug2BdgThRF3VorQbLlTWJOgXjx4+nX375RdScQ6kGCDRtBAUFCZGDjD+UhtFGYmIijWnblnaE30VOdna0MDSUfuvalX7t3IW+SkqkkjsWsCuFBTQtKIj+DAun9JJSOp6TQ5mlpfRHWprYti0sjCYFBNAYXz+6x9ub3mrZgt5rFUI/Xb9ObnZ2tC0snN5q0ZJei43977MLi2hNx070TstW4u/8sjJh5XshuCk9FXOOHunUmebPm0fr168XQhQWlC1btohyNkj4wDYIfIDn0O8TbnuOtWTqAkIb1PvIYo7m0BimMnilZlSAReHbb79V2QZrTO/evcnaKS4upkmTJgnXojqwVsE1a62Fa3F+wKKLRBFY56pi1KhRFVZgWLS04efrSy3d3Sv+XnUthR6IjqaHTp2i68XFdK24WGxv7uxMrVxchZWunZsrpRQXUQOZjBrY2dHsuFjalZ5GznZ2GvuHAITVD3Rxd6fi8nLKlcvF3wMbegsrnoKB3rd7+Ya6ulIzZ2cKcHamcrmcWrVqRUlJSbRnzx4h6iHuER+IfyvKUqCYd6dOnWr1ezKMOhEREfS///1PZRvieHGTxDDKsLBjVKxRiHdS9s67uLjQRx99RNZOeno6DRo0iH766adKS8Js3LiRXF1dyVqBqIL7FeVeFMJNG46OjhVWiKoyS+GqVdSuO5yVRdE5OcJqBgtcCxeXCoudsgCztbGhcolIZmNDm7p0pfsb+tBfaWk044JqvGh1ONmqCkEH29ut3/BJcO3aSuVUJpcLKza+A+Ln4JpXxAeihzIyyBXXEMPoAszFyudTZXM2w7CwYyqAK+2AWlV9uJACAwPJmoHlBUkS6pmdWNSRZLJo0SIhUqyd5557Tiw8DRvetm7pQiyW3+mlm1dWRp4ye3K0taWYvDy6kJ9f5XvhOoX17Z6GDWl28xZ0vpLX3+XuLpIpwKncXHKysxWWvppQbmNLdkqvRUwd3LK4AVCUwEF2MMPoEoQxoAi6MpiXKiu1xFgvNZvFGIunoKCAZs2apdECy9qLYULojhw5smLBVoDSFFjIuQPHfyDhBg9dYWNrS6V3xFM/Ly9ad/06DYk6TiEurtTeza1aYfdMzDkqgfmOiGY1a67xmon+/vTmpTh6IDpKWP0WhITWeGwlMhk5ODlV/I3kiDfeeEMIPFjvYJVEMkRNrbioDYmkJcRtwp0NVy4WcYZRB3PyihUrVMIYUCYHyUlsHWYAZ8UyAiQEqMeObdq0qVq3miWzbt06kSSh3HkDwIL5+++/i1gqRn8gbu3ijh006JBqjUBQUloqbkbKy8vI1dWNHA3ct3hXr57U+v77hZBjGEOD0A/1WFbM4YrakYx1w65YhhISEmjhwoUq21CyApYqawT3Oh988IEIVFYXdZ07dxbWFBZ1+sfX15fynJ2pVMnNDUGXnpFBaWm3qKAgn4qKikShYG014vQBxoNxYXwMYwxGjx4tygEpgzAIzOUMw65YRrhgsUAqQLzYkiVLRIyTtQEh99RTTwk3mjpwu8JN1qBBA6OMzdqAcLKRySjb1ZXc09IoNy+Piov/O08VSEhkKC/XSzme4pISys7OIqlcIls7W7KxsaUsb2/KLy4W1f/haoVrTFG3ryrgzle38MFle+TIEZ2Pm7FsMDdjjsYNpuKmBnP4q6++KkJEGOuGXbFWDkp0qN/5Pf/887R06VKyNrKysmjMmDG0d+/eShMDFi9erNKQm9EvyDb9dMEC8j9xgppUUdLBwcFRJGzo+jYEE+PtBAjVKTK+Y0c6FRhIS5YtE9ZdJycn8Tp0zWAYQ4K5Gglc6nP63XffbbQxMcaHXbFWjFwuryjJoNwe6t133yVrA23BUItNXdThzvizzz4TQpdFnWGAWEJ8HcIBft+5k64GBlJZJdY4WOjcG7hTQ29vnYu62+Mo1xB1GEdy06YUfeZMRYkJWErU28oxjCHAXO3l5aWyDXN6VWWEGMuHhZ0Vg6r/p0+fVtn2/vvvC3FnTcAV1qNHD43euOgSgAQSuNys0S1taCCUduzYQX369KF7772X9u/fL7o4FNjbU7pShqitrR25u3tQ48a+IjtZX8fGFvXq1OrZpTVpQgUymRiXAnTZUO6NyzCGApZqzNnK4NysqqMLY/mwsLNSMjMzReswZTp27EjTpk0ja8suQ7N5lJpQxs/PTwgLa00gMbSgQ/utnj170uDBg+ngwYMqbZPi4uMpEWVU7GRC0Pk2bkxurq6iGLG+aeTjI+LqAGrqJbVqRbHx8Sot99A+DDF2f/zxBxeKZQwOYoIxdyuD0juY4xnrhIWdlYLUePXabAjGtRZ3Ixbgjz/+mMaNG6eSOAJgfVG0h2L0ewzQXxW/M2pwoSVZZcRdvkyFvr6UE9ZVCDpDWk+RSOTtDVeXDV0LDaU0NzeKVBKeylbfYcOGUbdu3cR3YoHHGArM2Yj/VQZzO5c+sV5Y2FkhZ8+epWXLlqlsQ9KAehKFJccWPvPMMyKDTH0BRtuwyMhIatq0qdHGZ+kgi2/Dhg3UtWtXYRGNjo6u9HXBwcH09ddfC9HUd+BAuhgSSjlGKMDq6OBI5O9PcW3aUOSxY5Samqr1tVFRUeI74bvBGmzIMiyM9YJ4VJRAUQZJFefOnTPamBjjwcLOyoCQQcyYcnAtsvo++eQTsgZycnKEdWj58uUaz6HXJ1yCHh4eRhmbpYNzDuViOnXqJCylynFq6h1PECMUFxcn3EwoCYIG6F5BgRTVti3J9VDWpCrweRfuCqe88nIVNzHK3jRr1qzS9+C7oYAsvivKT3AwO6NvMIcr+jADnHMzZsxg67EVwsLOyoCbCBmH6nXstC1QlkRSUpIIzEeAvjoo7gmxh8bzjO4tpD/++KNwcU+YMEGrFQHtyFA/8OLFi/TEE0+Qg1I3Cbibho0YQQUBAXSkfbuKHrL6Bp+Dzyv0D6Anpk1TaZn2zTffUGxsrBiztlZq+K4PP/wwdejQgX766SfxWzCMPsANkXpbyF27dtHWrVuNNibGOHAdOysCsWTt2rVTKc2AfpQXLlyocU9LcwUusgceeECjMTvucNesWSMsSIxuQd9TiBl08bh06ZLW17Vp00Yk8jz00EPVxniisv7GdevIOzGRepyLIZkeXZ2w1EHUZQQH05gJE4R7Pjc3l/755x9q1aqVGHfFa+VyYZmbN2+euJ60AQGIwHZ0NeGbCEbX5Ofni77DKSkpFdtatGghbjDgmWGsA7bYWRGox6ZebwutxCxd1OGOFVmL6qKuUaNGtG/fPhZ1eujeAVcqFpjJkydrFXWwYkEMIeZz4sSJNUrcgbiCyMpq1pz+7dpVbzF32S4utD+sq/gchahTuF/hylcWdQBjx3fAd8F3wnerDLiX0X8Y70cjd/WWdQxTHzCXq7eHvHLlipj7GeuBLXZWAu7gsNDijk4B3JIo6WHJNdo+//xzEVOofppjYUU8He5mGd1QXFxMK1eupPnz51NiYqLW16Hf7ty5c0WSQV3bgCGBYfvWrZSZnEJt4uIoJCWFbHUwlcH1GhsYSBdDQ8g7MJCGjhghSt/Uej/l5bR582Z67733tMYSKhJEZs+eLQSwcnwUw9QVzHV9+/YVSWDKgg9hAwEBAUYdG2MYWNhZCZMmTRJxTgog5o4fP05hYWFkiSgChytrjYa6dSg8rF6xnakbhYWFwkKHOEVlF5A64eHhQtDBJa6Lmwm4P7F4HYuMJLe0NGqZkEhN0tLIrg7uWXSUSPLxoctNgynPx4e69+kjOpHUt/wPptdt27YJgYdwAG0EBgbS66+/TlOnTmWXGVNvcK6h9I7y8o41YPXq1UYdF2MYWNhZAYcOHRKLlDJYQL799luyRPLy8kSQ/u+//67x3GOPPSaC3pUD85m6UVBQIBJO4PqpqgQIunpA0A0ZMkQv1uFr167RwchIio+NJVlBATVNSiL/9AzyyM8n+yqyUUvt7Cjb1ZWuN/SmhCZNSO7iQs1DQymiTx/y9/fX6Rgxzf75559C4KF8izbwuSjDg0LhLkYo7cJYDpjj4e5XXwtQCJyxbFjYWThwCWFhhXVOAcp5wCzfuHFjsjSwyCMG6sSJExrPYVFFkL4lu54NJZy/+uorUV7h5s2bWl+HEiUohI32YIb4zVFpHy3yTkdFUVF+PklyObkVFpJ7RiY5yOVkK5VTuY0tlchklOPtRXnOzmQjk5GTqyt1Cg8XpUn0bcXFdLt7927R41PZVaYOrk1kOD799NOibRrD1BZcm0jWQYknBbDiofh6XUMgGPOAhZ2Fg5inKVOmqGz79NNPhZvS0kAsE0RdcnKyynZY577//nsR3M7UHSwQKHq6aNEija4l6q5uWOjwf2OIaLjhMzIy6MaNG+JxKzWVSoqKqEwuJzuZjBycnKiRnx/5+vqKB3ojo8OEIcG0+/fff4ubDfxfG+hDO3PmTHruuedE4gbD1AbM9Th/1NcEJPAwlgsLOwtfiENDQ8Xippw0AKuGpZVa+Ouvv0R2K6xJymDRRhA7gomZupGVlSWSUNC2qKr+k7DMvfXWWyIDmak5SGBCI3dY8rQBSyJuxl544QXy9PQ06PgY8wVZ17BEozakAtzMwGPj7u5u1LExegTCjrFMZs2aBdGu8vjzzz8lS+Orr76S7OzsNL5rq1atpIsXLxp7eGZLenq69NZbb0nu7u4av63yY/DgwVJkZKSxh2v24DfEb1nVb+3h4SHNnTtXHBuGqQl//PGHxnmEtYGxXFjYWSgQNPb29ioX8/DhwyVLoqysTJo5c2alC2BERIR069YtYw/RLMHvNnv2bMnNza1KkYHz6ciRI8YersWB3xS/bVW/fYMGDaQ5c+bwOc7UiGHDhqmcP1gbYmNjjT0sRk+wsLNQLP1Czs/Pl0aNGlXpojdhwgSpsLDQ2EM0O1JTU8WdvKura5WiYuTIkVJUVJSxh2vx4DfGb13VscCxevXVV6UbN24Ye7iMCWMNN/rMf7CwsxLTOyZ/S+H69etSt27dKl3o3nzzTam8vNzYQzQrrl27Js2YMUNydnbWKiBsbGykcePGSadOnTL2cK0O/Ob47XEMtB0fHLuXX35ZHEuGqYxXXnnFKkJzGBZ2FkdxcbHUunVrlYvX19dXys7OliyBs2fPSk2bNtWYoGQymbRy5UpjD8+sSEpKkp5//nnJ0dGxSkEHCyh+d8a44BjgWFQl8JycnKQXXnhBHFuGUQZrANYC5fMFawXWDMayYGFnYSxatEhjsrcUwbNr1y4RPF5ZQPmePXuMPTyz4erVq9LTTz8tOTg4aBUItra20qRJk6Tz588be7iMGjgmODY4RtqOH47tM888IyUkJBh7uIwJ8f3332ucK59++qmxh8XoGBZ2FhYjpZ7BCJclkgzMnRUrVgirnPqk1KxZMykmJsbYwzMLLl++LE2dOrXS31HxQHbx5MmTpbi4OGMPl6kGHCMcq8oywpVja5988knpypUrxh4uYwJgLbjrrrtUzhGsGRyjaVmwsLMgnnjiCY2J/dChQ5K5T0TI0Kxs0erRo4cQs0zVIGnm8ccfr1YATJs2jQWAGYJjBvGmHhzPgp2pjIMHD2qcH7jhYywHFnYWwvHjxzVib+CuMWeQ2frQQw9VulCNGTNGKigoMPYQTd5l98gjj1Trsnv22WfZZWcB4BjiWNbExX7hwgVjD5cxIpgX1GNpsYYwlgELOwsAWaCo26ZeBiElJUUyV27evCn17t270sUJGb6W4F7WZ5D9ww8/XG2Q/YsvviglJycbe7iMjsExxbHFMeakGEbbOaJe1ghrCFcUsAxY2FkAP/30k8bEPX/+fMlcgTWhZcuWlbqTli9fbuzhmSwnT54UlkxtizkeXBbDesAxxrGurozN2LFjuYyNFfLBBx9onA9r16419rAYHcC9Ys2c/Px8at26NaWkpFRsa9GiBZ07d46cnJzI3Pjnn39o1KhRGj1J0QD9119/pfvvv99oYzNVoqKiRK/RLVu2aH2Nq6uraCSPhuCNGzc26PgY43Lz5k1atGgRffnll2K+0MbIkSNFr9+wsDCDjo8xDkVFRdS+fXu6cuVKxbbAwEDRVxbzBWPG6EIdMsYDBXnV77o2b94smSOrV6+uNAC8SZMm0unTp409PJPj8OHDGh1GKms99cYbb3DrKUacA2hDhnOiqnOGW8VZD7/99pvG8Ud/aMa8YWFn5tlw6sVlBw0aZHZxEhjv22+/XekiEx4ezm5DNQ4cOCDdf//9VS7OqO2H3zQjI8PYw2VMjPT0dGnu3LmV1oRUfgwePFhkUDKWC+bee++9VyP+Nj4+3thDY+oBCzszZvTo0RoxaOfOnZPMiaKiIo0MLcVjxIgRUl5enrGHaDL8/fff0j333FPlYuzl5SW9//77UlZWlrGHy5g4mZmZ4lzBOVPVOYWF/59//jH2cBk9gQQa9VJIiNVlzBcWdmYKOi2oT8DIhDM3y0G/fv0qXUymT58uyeVyydrBHfXu3bu1/k6Kh4+Pj0iYycnJMfaQGTNsNYVzp2HDhlWeY3fffbe0d+9es/MIMNWDNnTqxxvHmjFPWNiZIaWlpVKHDh1ULkJMyubkdrt06ZIUGhpaaZ2tzz//XLJ2sHj+9ddfWku+KB6NGzeWPv74Yyk3N9fYQ2bMHJxDOJdwTlV1zqEsxo4dO1jgWRBYO9SFfceOHcVaw5gfLOzMkC+++EJjsv36668lcyEyMlJYmNS/A+oqbdu2TbJmsFj+/vvvUvfu3atcXP39/aXFixdL+fn5xh4yY2HgnPrss88kPz+/Ks9BdH7Zvn07CzwLYdmyZRrH+MsvvzT2sJg6wMLOzEhLS9OIiencubPZuC1//vlnjYQPPAICAqTo6GjJWsHiiGzmsLCwKhfToKAgIezRlYNh9Ak6uyxdulScc1Wdk0hw2rJlCws8MwdrSKdOnVSOrbe3twiZYcwLFnZmxnPPPacxsZpDYDMm/Q8//LDShQGTSVJSkmSNoIPGr7/+KsR5VYtncHCwsMoi2YRhDAnOOZx7OAerOkdxDm/YsIG7wph5gpb6ccWaw5gXXKDYjDhz5gx16dKFysvLK7aNHz+e1q9fT6ZMaWkpPfPMM7RixQqN54YMGSLGjwLE1kRZWZkouDxv3jxRTFobKDY9Z84cmjRpEjk4OBh0jOb822ZkZNCNGzfE41ZqKhUXFlJ5WRnZ2tmRo7MzNfLzI19fX/Hw9vYmOzs7Yw/b5CkpKaE1a9bQBx98QPHx8Vpfh6K3KHQ8duxY/l3NEKwpmJsU2Nra0smTJ6ljx45GHRdTc1jYmQk4TAMHDqR9+/ZVbENniQsXLlDTpk3JVMnKyhIT/J49ezSeg9j7/PPPSSaTkbUgl8vp559/FoIOFd61ERISQm+88Qb973//I3t7e4OO0VxBt5JTp07RmehoKsrPJ0kuJ7fCQvLIyCB7uZxsJYnKbWyoVCajbG9vynN2JhuZjJxcXaljWBh17tyZvLy8jP01TB7cqK1du1YIvLi4OK2va9OmDb355pv00EMPWdU1bu4kJCSIY4fOFAruuece2r17N9nY2Bh1bEzNYGFnJmzatInGjBmjsu3tt9+md955h0yVq1ev0rBhwygmJkZlOyaHTz75hGbMmGE1EwUWw59++kkshpcuXdL6Ol4Ma8+1a9fo4IEDFB8XR/YFBRScmET+GRnkkZ9P9mVlWt9XamdH2a6udN3bmxKDm1Cpiws1DwmhiL59yd/f36DfwVxvUmBtx00KbjCru0mZOHEin9NmAtaW9957T2Xbxo0bafTo0UYbE1NzWNiZAYWFhdSuXTshlBQ0adJETKYuLi5kihw7doweeOAB4QpTxtnZWQgc9IO1FvfV6tWr6cMPP6zSfdWhQwfhvoJ4Z/dVzYVFZGQkHYuMJLe0NGqVkEhBaWlkpxSqUFPKbG0p2ceHLjUNpjwfH+oWEUEREREsRGro+t6wYYMQeGfPntX6Og4rMB8KCgrETWZSUlLFtmbNmtH58+fNsge5tcHCzgzAhIlFXxncKSMWwhT57bffxN05BKkyaD6/bds26t69O1k6xcXF9P3339OCBQsoMTFR6+vg/ps7d65owI5YFqZmpKam0vatWykzOYXaxMVRSEqKcLXWF7hq4wID6UJICHkHBdLQESPIz89PJ2O2dBD7u3nzZmHpgUtcGwgdmT17Nj3++OPk6Oho0DEyNQdrzMMPP6yxFsH6ypg2LOxMnOTkZGrdurW4g1LQr18/+vvvv03OjYlT6dNPP6VZs2aJfysDi+P27dvFXZ8lAzH73Xff0UcffUQpKSlaXxceHi4EHayapnYczSEG6Lf168nl2nUKP3+e3JWuDV2R4+JCUW3bUkFAAI16aLxJx7GaGrj2cQMHgRcVFaX1dUFBQfTaa6/R1KlT2Qpkosfx7rvvpn///bdiGzxEiA3GsWNMFxZ2Jg4sXwhUVgCrDiZLZMeamlvsxRdfpK+++krjuXvvvVdkWXl6epKlAuG9fPlyWrhwobAmaaNHjx4ifmXw4MEs6Ooo6jauW0cNExKpe0wMyergdq0pcltbOtK+HWUEB9OYCRNY3NUSLC1//vmnEHhHjhzR+jrEM7766qs0bdo0kw0tsVZOnDghbkKVZQISuhBOw5guLOxMGMQP9enTR2XbU089RV9//TWZErm5uSLYH5O4Ok888YQQe5aa2ZmXlye+H5JBbt68qfV1iNeCoIPIZUFXNyCYf169mjzjr1Kvc+d04nqtiWv2UIf2lNWsOT386CR2y9YBLDG7du0SAg9zmjYQqgFr/9NPP01ubm4GHSOjHaw533zzjcq2AwcOiDmNMU1Y2JlwQDJi0aKjoyu2weIVGxtLjRo1IlNyFSPz9fTp0xrPIWHg9ddft0ghk5OTQ19++SUtWrSI0tPTtb6uf//+wuWK/1vi72BIi/AP339PZTHnqe+JE3q11Gl8tq0t7Q/rSvZt29KjU6ZwQkUdwVKDEJJ3332X/vnnH62v8/HxoZkzZ9Jzzz1ndfUtTZFbt26JzObs7OyKbbDiHT16lOOCTRQ+KibKqlWrVEQdQGkTUxJ1MNPDtagu6hAQjVptCJC2NDGDunywPCBWEBl+2kQdLHNYvFB3cMCAARb3OxgaWHqQKIGYOkOKOoDPC485TxkpKXTw4EGDfrYlgWsA1wLEHa4NXCOVkZaWJuYOXGMI1lcWFIzhwZqjXlYL4UArV6402piYqmGLnQmCiSw0NFTFtYfkA1T/NhWX5u+//y4ypvLz8zXutrds2UK9e/cmSwKdDBYvXkxLliwR1jptIHYOGcyW9v2NXadu7apV1ObMWWqdnGy0cVwICqKLHTvQxMmTuc6djoBQfv/99+mvv/7S+hoPDw+aPn06vfTSS1xA2oh1OJHBj3Inyq5zeJBwfBjTgi12JggsQurxWhAVpiLqvvjiC3rwwQc1RB3E6OHDhy1K1MB6AMscAuexAGkTdchuRYA44gwt6fubAig+jDp1KGliTEJTUsQ4Ig8cMOo4LAlcK7hmcO0MHz5c640u5kRcgyi1gWuSMSxYez777DOVbVijMCcypgcLOxMDRYfRZkuZESNG0KBBg8gU4v5w1/zCCy+o9KtVlGA5dOgQtWzZkiwBFFZGph7cQfPnzxdJEpWBQstwS2zdutUq6vMZo00YOkqg+LAhkiWqAp/fMiGR4mNjxbgY3YFrByVScC2hpqO2JC3E7eKaRJmUqpKVGN1z//33ixtYZeDBqKo1ImMcWNiZGC+//LIIFFeACu2oDWdsYJ1DOxlcyOqgkvzOnTtFM3Vz5/r16+IYNG/enD7++GMNq6QiVmjcuHGiCCtavYWFhRllrNYAfmO0CUNHCVOgSVoayQoKKk0WYuoPriUUOEfYCXpMVxabimsSZYUg8JBkgWuWMQxYi5S7hmCtQmtIxrRgYWdCoICveskQiAxjW8EwccIiB6uUOgiq/eGHH8y+gjyye2GJhKCDy0G9awZABtiECRPozJkz9Msvv1CnTp2MMlZrARbiM9HRovdrXdqE6QOMo2lSEp2OihLjY/QD4rlQ+xLXGq65ygQerlEIDbQqQwxeVQXBGd3QqlUrDSGHNeuPP/4w2pgYTVjYmVBPUfULBgHaiO8yJphYkfmqnqGLmIs1a9aI2mzmnPGJgrfPPPOMEM+IHUQrsMoE3aOPPkoxMTGiWHT79u3JWkBpDxTDVjwqE7zVAetKXRNWivLzyT8jQ2X7F4kJNDQ6ioZHR9Hokycoqaioyv18m5xUr/d3P3xI5W//9NvjwviqAnGxuK7rC6xXPXv2FP2EYdFCVqm1gGsN1xyuPXgGKiuvUVRUJMJXIPBQIqWqFn5M/UGco3o9R6xdujjXGd3Aws5EwMQUFxensg19Ro1Zx2nHjh2iCKVyI2iAzDQUHH3kkUfIXLly5Qo9+eST4g4UBZ8rm5QgaqZMmSJiSGCVRGs3awO1EyEsFA9nZ2eDCDtYwxDnKMnl5KkU3xidk0NHsrNpS5eu9HtYOC1r247cZXZV7utbpUzaurxfHY/8fDEujE+Xwk49blWBq6urqPR/9uxZ+vHHH8U5aW2gIf3q1avFtTh58mSys9M8Zvitly1bJq5pdLGIj483ylgtHaxJWJuUQXasemw4Y0RQ7oQxLtevX5caNGiAyPCKR48ePaSysjKjjWn58uWSnZ2dypjwaNGihXThwgXJXImNjZUef/zxSr+b4mFvby9NmzZNunLlimTtNGzYUGPbX3/9JfXs2VPq0qWLNHHiRKm4uFhsf/LJJ6WwsDCpXbt20scffyy2zZkzR/zWnTt3lp566ikpPj5eCg8Pr9jXzJkzpZUrV4p/N23aVHrttdfEfnfu3Cn+HeznJ7V2cZUeDwiUYvv0lZa2aSsNbugj/q3+WNG+g9SlQQOpraurNKJRI+ls7wjp6aAmkh2R1MbVVXrYz6/W78d2T5ms4jWvNGsmdXBzkwJ8fMQ5omDevHlShw4dpI4dO0qffvqp9MUXX4jzCH8/8MAD4jWrV68Wr2nfvr20cOFCsQ2/B7Y99NBDUuvWraWCgoIqj0d5ebnUqFEjSS6XS9bM5cuXxfmG31jbdYzzbvLkyVJcXJyxh2txYG3q3r27yu+NNQxrGWN8WNiZAJh81CelI0eOGO2CffXVVyudKHv37i3dvHlTMkfOnz8vPfLII5Ktra3WhcDBwUF69tlnpYSEBGMP12RQiDI8nnjiCenWrVvSwIEDKwTIW2+9JUQMSE9PF/8vLS0Vwi8xMVFDHFYn7BT7iomJkbp36yb9MHmyEFQPNmosLW/XXoru2UsKdXGRWjo7S4/6B0gbO3cRzx/u0VPq5eEpne7VW/z9XJMm0twWLTWEWX3e/337DtIj/v7SxYg+0g+TpwgBe+bMGWn79u3SPffcIxUVFan8Dvg+ubm54t/JycnipgjPFRYWSl27dpWOHz8ufg/8xqdOnarR8di0aZM0ePBgHRxZy+Dq1avSM888I65dbdc1rvlJkyaZ9Q2pKXL48GGN33rKlCnGHhYjSRK7Yo3MsWPHNCp4P/bYY0YpnYH4qfHjx1fqOkMv2D179phU54uaAPcVCimjwDPcWJW5u5ycnOjFF18U7lm0CQsODjbKWE3dFfvdd9+JOoXICO3Vq5eIuUOAu8LltW7dOuratauIA4PLDKV7aguyjQHONYQmzN28mUaciKZTubmUWFhIbjIZbe4aRm+2aEmOdrY0+exZiszMpFO5OXSxIJ/Gnz4lXv9nWholF2vGztXn/QeyMunvjEx68OQJmrv5N9FqCS6o3bt3C/egIoGosuxwXOcDBw4Uz+F8Q8Yn+m0q6j/WJBEH5ydK8CxdurTWv6ulgtp2cL9evnxZJD/ht1UH1zzigdu2bSsa2J87d84oY7U0EHuN2GNlsJYdP37caGNibsNND40ILKYQFMqg+TXqphka1IRCvTwUClUHCRwoRGlOfQFRJgNj3rhxo9bXIF4MiROvvPIKdxKoIVgk0RtY/WZEIYpRyxCV6CFcKktEQdyisrhWf42Li0vF5/Tr04ce8famzldUY6VkNjbU08ODeri7k5fMnnZnpFMfTy/q7+VNC0JDq/0OeH+El5d4eNfi/eUS0fPBwTTa15dOtWhOub17ixJACoFWVxTfuSqQqIGi4MuXLxcxZIwqQUFBIsYLrcg++eQT+uqrrzQSfTDf4uYD7Q5xfr755puc2V5PEGuHkk+KOp+KNQ0tAM05qc7cMZ+V2gJBQDQsIMqgHZWhRQbaxODuS13UYRFesWIFffDBB2Yj6hQFTmFN0ibqEIyOAqdXr16lRYsWsairBbDUof8tsokBOnHAYofisbgpcXd3F6VjYMVSgEB3RWkQtCFCizC8HosBknAqA9atY1FRlH1H+N0oKqSEnGw6deMGRScn040bqeJxNjOD/B0cqat7AzqSnUUpdzJc8+TyimxXOxsbKrtT3PhKQYGw/CkWodiCfApwrPr9Cvp4edKvN1KpsKyMym1sKSMrS3RFQM9TCF2FSFVkyyLIHN8TwAIPKyQKG+N1WAz79u1bo98cSQEohI2abffcc0+Nj5U1gmsZ1zSubVg3ca2rg+MOSzNKquB3Vc/4Z2r3e0MgK4ObO6xtjBExti/YWkHsTUBAgEp8QqtWrSridAzFnj17JA8PD41YCXd3d2nXrl2SOcV7DBs2TGucjSK494033hBxYkzdkyeQ2IA4OSQGIPZu3759Yvujjz4qhYaGSvfdd584Ftu2bRPbZ82aJbVt21YkT4BFixZJLVu2lAYMGCCNHTtWJcYO1wUSBBDw/tD48VITLy+phYOD1NrRUVrZpIm0PChIaufoKDWztxeP+9zcpKPhd4kYuJXtO4jEhtYuLiJZYk2HjmL71MAgEVOH5IlNXbqIBIlWLi7igdg9RVydtvcrx+i93ry5eD7Q00tqHRoqpaamirG/9957IuYOv8fixYvFtiVLloiECEXyxA8//FBp8oRyzGFlrFmzRsSQKWId8UhLS9PhUbZccK0jgUc9OU39MXz4cKPFNZs7WLNwPSv/nljbFPGljOGxwX+MKSytuRYQ2uMogwLA6i1b9MmqVatEyQ/lTheKuBUUSzaHem0w+cPlitIs2oBrEK3QUMSUm4ibHpiCEI/3zz//0P79+8X/YdWDdeq+kBDqqWT9q4xGPo0M3kd5V6+e1Pr++4VlkTF9YEVF1xw8YGXVxuDBg2nu3LnCMs3UHKxdCBVQD+GBt4cxPCzsjADikRDMrxxfhD58qOBtiLgEHHJMXvPmzdN4rlu3bqJno6+vL5kyWPzRGHzv3r1aXwMRh84dCKqGuGNMA8TPIYAdx1Ah5irr+4kbi7FDhlDfbdtIpnbzAWxtbMmtQQNyq8Tdpk9K7ezo97v70dBx40TRYMZ8yMrKEskn6C5TVb9fuNcRFoOOO0z1YE2BKEZrSQVIJkJhaRSOZgwLCzsjgLiOzZs3q8SyIdMQWVv6BlXan3jiCVHNvbJxIXO0JsHcxgCnKoQcBB3EgDZ8fHxEQsSzzz5r1ALPzG0QX4fzWyHk/v33X0pPT6/2fTiOTyFD/N9/ySM9XQg5B0dH0avS0dGBZDJ7MkZ4dpq7Ox3o2YMef/ppnWaJ4zdRtwBicawsoYmpH4gNRTYtEi2qOhfvvvtu0V2nf//+nAxQg1jtjh07qrTaw5qCeFLGsLCwMzAIKh80aJDKNrgJcQepb9LS0sSFVlkWH4TQRx99ZJJJEjhFcScIQXfw4EGtr0NgPgKmn3766UqDphnDANf+iRMnVIRcVe4vbaBt0dTHHqP2VxOoU0KCuAEyhaX1TPNmlNKlCz07fXqlHRAY8wEJPOg88/HHH1dqNVaADjzwcmDuZoGnHaxlcHerr3kcsmBYWNgZeMFDJhbM0wpwx49aWKgXpk9QE2zo0KF06dIlle0QcuiRirIfpgZOTTSXhqA7evRolZlZyHJFvKCpWhstmdLSUlG7SiHkEPeoyAatDYGBgcJConigvhv2d3LXLhp8IJLstLTcMiRltrb0Z58ICrvvPjFGxjIoKCigb775Rtzcpqaman0dqgdA4A0ZMoQFnhZXd0hIiDAiKIdUoA4mbswYw8DCzoAgtkO9bh0mEwgSfQKLCUqAqDctR3mKX375RUxSpgROSQTjQtBVVYoAtatef/114VqurDApox8QGwqhrRBysKJiYawtSNJRFnKIxVFfLBEH9d2yZdQ1+gQ1rcKiYiiuNm5MJ8O60tRnn+VEHAsEte9Q4gkCD2V7tBEeHi4EHpLdWOCRxpr21FNPqWxDjUHEOjOGgYWdgcAdDO5kcEejAFX6UZFen+4cxNKhKr56M3JYR5D5CguiKQXVIx4DSR0oMFyVIEAh0scff7yi2j+j38UOtakUQg61FysrPlwdLVu2VBFyOI41YcMvv1Da4cM04HgU2Rpxuiq3saF9d4WTT69eNPZOhwzGMsH5jaoBqFyQmJio9XWYPyHwcONsimEsxgAxdnfddZew0imARwpeI8TNMvqHhZ2BQCA/qqErgwSAmhYprS04rBBImHTUgaBE5ivEnalMBCgYivFW1e4HFh2UiZk0aZLBy1tYW9wRrHAKIQfrHNyttaV169YqQq6u59v169fpp5Urqc2Zs9S6CiuKvrkQFEQXO3agiZMnc1FrKwE3xGhHhrIditZ5lYHsaBTqRUcLjru87SVSzyhGuA8SVhj9w8LOAMD6hP6Zyq2U0L8U7W30NRlNmzaNfvjhB43nhg8fLj4XblhTiDlEex8IOvQW1QYsnZg00eeR4zT0kyGIhBqFkEP3DvXahjUBi5tCxOGGBckPugLjOrZnLw04coTc6+D2rS/ZLi70d88e1H3gQC6BYYXgxgbeD8xV6nHKyrRp00bMVeitbe1zFda49evXV/wNiyZCa0zJS2SpsLDTM/h5BwwYIBYm5R6lEDJNmjTR+echJmnMmDGi7ZM6iHFA9q2x7ygxSaLlDO6Cq5skUUsKk6Sxx2xJ4BzBHbVCyCGDVfmmoyYgrggTtLKQ06ebBULzh++/p7KY89T3xAmSGTCRQm5rS/vDupJ927b06JQpVr9gWzM4DyFWIPBQVLuqm1F4FyZOnGi15wtc2JjDlXv2Yq7A2sRxifqFhZ2egYtx/PjxKtvefffdSl2kuih8jAbt6hMOLqLFixdrJG4YGlgSV69eLeJWqnNrQNBBoLKg0018p6KjAx6oKVfbyx5327A6K4Rcnz59DJ48gGzFn1evIc+r8dTr7DmDxNshru5Qh/aU1aw5PfzoJJ1aIRnzBeEjGzZsEF1vqgsfQQcGhI+g/qK1gbXunXfe0VgT4bJm9AcLOz2COxXcsSgH3yJgHIUcYbXTJQhoHzFiBN26dUtlO8p/wPWK54wZiIwm6fPnz68yELlLly5C0HEgcv0FkHJXh6oWHm3AyoAAaIWQQx0vd3d3MjYJCQm0cd068k5MpB7nYvRquYOl7kj7dpQRHExjJkyocbIHYz3A0v3bb78JgccJX5ogWx6F9w2xBjL/wcJOj6BcB6qW6/tuBft89NFHRVcJZWBd+P3330VqvrGE7XfffSdKB6SkpGh9HQQELJiI/2MTfe1BWQaFkMMDdRFrC6wJqNGF+DEIud69e5tskWeIu9/W/0Iu165R+Pnzeom5Q0xdVLu2VOgfQKMeGs+ijqkSLKNISMOcjxjVqko0oebm1KlTraZEkyG9VsxtWNgZML4AbWnQEktX4gWHDhXTMVGog9YuEHXBwcFkjLu05cuX08KFC6st9gnhix6DLOhqztWrV1WEHFzwtQWLChqdKyxyOBbmdAeN82r71q2UmZxCbeLiKCQlRSeuWbheYwMD6WJoCHkHBtLQESPY/crUak5Gz28IvKpawSGrGl1ykORm6UXVDR1nzrCw0xsTJkwQGZ8K4FpEkHqnTp10loDw3HPP0bfffqvx3H333SfukgztOkOZDJR0Qf/F6trzQNCh0TYLuqrB5Xn58mUVIVeVO1sbWDzwuyuEXLdu3czeJYRAdnS5OBYZSW5padQyIZGapKXVqUMFOkok+fjQ5abBlOfjQ9379BFWS2sNfGfqf93u2rVLWKaqa4M4a9Ys0QbRFCoV6AtDV4awdljYmWENH/TdhGkb/VPVwR0gWoQZss4bymV8+eWXtGjRoiobasNiCfM7N9TWDi5H3MkqC7lr167Vej8NGjQQCQ4KIQd3vKXW/sPvczAykuJjY0lWUEBNk5LIPz2DPPLzyV6pIbk6pXZ2lO3qStcbelNCkyYkd3Gh5qGhFNGnD9epY3R2PSMLFBY8ZYuVOsgonzlzprhZx7VriWANRF9eQ9VytWZY2Bmg6jayB1F1u2HDhvXeP6w1yHw9e/asxnNwy2JyMJRoQhcNtElDCRWU0NAGLHNIiuD6X5rgDhbJDYqsVfz/xo0btd4PKrtjglQIOSSiWJu1CecgMn5PR0VRUX4+SXI5uRUWkntGJjnI5WQrlVO5jS2VyGSU4+1Fec7OZCOTkZOrK3UKDxfWdG4TxugLXNtIsti9e7fW13h7e9OMGTNEaSoPDw+y9O5LmKfQZ5qrH+gWFnYG6JMH8fP888/Xe98IykWCgXrcGuKlfvzxR1EexBCg5+ySJUvEA9ZDbaAHLQQdYrmY/4Q/xIfCGgfrblVWTm3gJkGR6IAHYip5cvzvN8Y5CoGMx63UVCopKqIyuZzsZDJycHKiRn5+5OvrKx5YTPm3YwwFXLMQeH/99ZfW10DUTZ8+XTxwfloKxuqXbm2wsNMhuBPBHQnuTBS0b99eWO/qaz3ZsmWL6Lyg3mwdMRpbt24Vwe/6Bt/r008/Fa7e3Nxcra9DY2wIOsRxWTuIA0NspULIocOD8h1rTYEAUW7PhRICXBKGYcwXtOqDwEOSmzbgloX1DlY8S+izithwWOliYmIqtuF7waMFrwOjIyDsGN3w0ksvQSSrPHbv3l2vfZaXl0ufffaZZGNjo7Hvtm3bSleuXJH0TWpqqjRr1izJ1dVVYwzKj1GjRklRUVGSNVNSUiIdPHhQmj9/vjR48GCpQYMGVf5m2h6BgYHS//73P2n58uXShQsXxHnAMIzlgTlz5MiRVc4HmHtfffVV6caNG5K5s2vXLo3vh7WT0R0s7HRETEyMJJPJNIROfSgtLZWef/75Si/0e+65R8rIyJD0ybVr16QZM2ZIzs7OWiccCM5x48ZJp06dkqyRoqIiaf/+/dL7778v3XvvvZKLi0udhFzTpk2lRx99VFqxYoV06dIlFnIMY2WcPHlSGjt2bKU38YoH5uKXX35ZzM3mjLqQxdqJNZTRDSzsdAAW4fvuu0/lRHV0dJQuX75c533m5uZKw4YNq/Tifvzxx6Xi4mJJXyQlJUkvvPCC+A7aJhhbW1tpwoQJ0tmzZyVroqCgQNq7d6/09ttvS/3795ecnJzqJORatmwpTZkyRfrhhx+kq1evGvtrMQxjImBOxdxalcDDvIM5GnO1OYKbVwcHB5XvhDWUb2h1Aws7HbB161aNC2/OnDl13l9ycrLUpUuXSi/oefPm6e3kh8B45plnNC44dUEHyxLcg9YABPaOHTvE8ezTp0+Vv01VjzZt2khPPfWUtHbtWnF8GYZhquL8+fPSpEmTxJyrbV7BfIQ5OyEhQTI3Zs+erfF9sJYy9YeFnQ5ccbC+KJ+cAQEBQhDU1RyP+KrKLmCIAn0Ay+LUqVM1XMnqpnJYmOLi4iRLJjs7W9q+fbuIZ+nRo0eVv0lVjw4dOkjPPfec9Msvv4gYRYZhmLqAOXfy5MmSnZ2d1vnG3t5eevLJJw0Sc60rsEZirVT3ZGBNZeoHC7t6smDBAo2L7Mcff6zTviAo3NzcNPbXsGFD6d9//9X52GNjY4Vbt7oJY9q0aWY1YdQGxClu2bJFxK2Eh4dXeXes7QGXCSys06dPlzZt2iTdunXL2F+LYRgLAzfgEG+Yk7XNRZjLIQLN5QZ8zZo1Gt8BaypTP1jY1QMEsKoLsV69etXJVbps2bJKRUVISIgQYLo28T/yyCPVmvifffZZszTxVwVE18aNG6UXX3xR6ty5c5VxLFW5o++66y5p5syZwnWg7yQWhmGY2obMwI1r6iEzZWVlUs+ePVXGjjXV3JNDjA0Lu3rw2GOPaVhujh07Vqt9yOVyYS2q7OLs27evlJaWptOg3IcffrjaoFyIHkuJA7t+/bq0fv16IVLbt29fJ7cq3LGYfF577TXpjz/+EO5ahmEYY6JIcqsqgQtzPeZ8U05yO3r0qMa4sbYydYeFXR05fPiwxsmIGLTakJeXp7V+0cSJE3UWa4C4vTFjxlQpXpBGDwsUhJC5T3Y//fSTcB+3bt26TkIOd8IQ1W+88Ya0c+dOcZwYhmFMEVi3YByoqiwVHiilYqplqeA+Vh8v1limbrCwq6P5uHv37ionIQrR1kYU4bVw51V2Ac6dO1cnma/Hjx+XHnzwwWoLX8ISZa6FL+Pj46VVq1aJiaFFixZ1EnK44x0wYID0zjvvSPv27RMlTRiGYcwJzOFI+qqukDyMCaZWSB7roXoxdySvYa1lag8LuzqA2mPqF8vHH39c4/fDLB4cHKyxDwTFQqTUF9zpaKuBpyxEYZEyp0B/iF0EBX/33XcifqSy37AmDxQRHjRokCgdg6QUzsJiGMZSwJyO8kzVdb0ZPny4dOTIEclUWLhwocYYsdYytYd7xdYS9EgNDQ2l1NTUim34+8yZM+Tg4FDt+3fv3k1jxoyhnJwcle3ok7dp0yYaMGBAvZpLv/fee7Rjx44qm0u/9NJLorm0l5cXmTI4NS9evFjRZxWPa9eu1Xo/6LfYp0+fij6r4eHhZG9vr5cxMwzDmAIZGRm0ZMkS8cjOztb6usGDB4ve3r179yZjUlJSQh06dBB9YxX4+flRbGysmMOZWlAHMWjVwG2pfleBMiU1AZamyuqiNW/eXGSq1pW///5bGjhwYJV3Z15eXqLtVVZWlmSqwOx++vRp6YsvvhBtynx9fetkkfP09JQeeOAB6ZNPPhHJLGjNxjAMY41kZmaKuR9rQFXzJtaQf/75x6hj/f333zXGhTWXqR0s7GoB3IDqKeZDhgypkWB5/fXXK72YkG1Zl/g2uCX37Nkj9evXr8qL1cfHR9QFysnJkUwNZARHR0dLn332mYj7QL2+ugg5fMfRo0dLS5YsEYkiHJfBMAyjCrL558+fX+08e/fdd4u1xRjtvfCZgwcPVhkP1lxzqctnKrCwqwUjRoxQOeFgfauuThAC8cePH1/pBQSrVG0D9XHi//XXX1Lv3r2rvDgbN24sLFamlNEJyxlS2xGPiPgOWNbqIuRgycNv+uWXX4p4RRZyDMMwNe/4gDkYa0RV82xERIRop2hogQfvlbpnC2svU3NY2NUQnODqJz7Kg1TFzZs3RcHiyi4aWPBqI0hwccFMrZ6Nq/7w9/eXFi9eLOXn50vGpqSkRDp48KC4S8RdWHXBvNoeaLH2v//9T1q+fLkQ0twommEYpn5gjYC3xM/Pr8r5F9mpCDcy5LxbWW1XrMFMzWBhV0OB0rZtWw2LWFXxarjrqKz8Blq+fPPNNzX+bFxMmzdvlsLCwqq8+IKCgkRsWmFhoWQskF26f/9+Ec9x7733iuzTugi5pk2bSo8++qi0YsUK6dKlSyzkGIZh9AS8RkuXLhVrSFXzMlouYi0yxHyMtbVRo0Yqn481GGsxUz0s7LSwbds2Efc1a9Ys6d1339U4yZEIUVUyQ2WBqu7u7qLgbU2ANW/Dhg2i7VV1Iujrr782SskOTAh79+6V3n77bal///5VVkCv6oHGzyjujNR2tMthGIZhDAvWEKwl1ZWRwpqEtUnfITDffvutxmfDG8VUDwu7Srh48WKVfVRx56LtpF69enWlTZpxsZw5c6ZGCQXr1q2rtv0VrIGwaBnyDgaxGTCHo0ZSnz59quxVWNWjTZs20lNPPSWtXbvWYlqXMQzDWALFxcXCcIFqDVXN41ijfv75Z7Fm6QPsV91T5eHhIUKcmKphYVfDOwXlx4EDBzTeA/M0LFfahGB1TY2RWLBmzZpq22CFhIQIy5YhSnggiwqxFahmjjiLykq11OTRoUMH6bnnnpN++eUXKTU1Ve/jZhiGYeoHjAYomN+qVatqb9R//PFHvaxJWGvVPw/tIpmqsYoCxWVlZaJY440bN8TjVmoqFRcWUnlZGdna2ZGjszM18vMjX19f8Vi5ciW99tprWvc3atQoWrt2LTk5OYm/i4uLaerUqfTjjz9qvPbBBx+kn376iVxdXSvdV2lpqXj+gw8+oEuXLmn9zLZt29Kbb75JDz30ENnZ2ZE+yMzMpH///beiGPCJEyeovLy8VvuwsbGhzp07VxQD7tu3L/n4+OhlvAzDMIx+kcvltH79epo3bx5duHBB6+tCQkLojTfeoP/97386LQCP/a1bt05ljYmOjqYuXbrUaX339vbW2xpqKli0sINQOXXqFJ2Jjqai/HyS5HJyKywkj4wMspfLyVaSqNzGhkplMsr29qY8Z2eykcmooLiY9v77r3ivtordPXr0oJ9//pnc3d2F0Nu/f7/Ga2bMmEEff/xxpScRqmyvXr2aPvzwQ4qPj9f6HVCJG1XB0a1C1ydjWlqaGLdCyJ0+fVp0e6gNtra2FBYWViHk0OHB1DtaMAzDMLUDAmrDhg1C4J09e1br61q0aEFz5syhSZMm1agbU3UkJydT69atqaCgoGIbDAZbtmyp0/ru5OpKHcPChAHCUtcqixR2aDt18MABio+LI/uCAgpOTCL/jAzyyM8n+7Iyre8rtbOjbFdXind1oSv+/lRgb09x8fF04OBBlRZiCho3biwscerCDGLn888/p+eee07jPbDuwSI4f/58SkxM1DoW3I1A0I0cOVLsTxfgOyhEHATduXPnar0PmUxG3bp1o379+gkhFxERIcQtwzAMY/nAi7N582bRvhLCShvBwcE0e/Zsmjx5Mjk6OtbrMyEmsR4q2oz16d2bunToQK5yea3X9+ve3pQY3IRKXVyoeUgIRfTtS/7+/mRJWJSwg8k4MjKSjkVGkltaGrVKSKSgtDSyq6U7EZa+vJJiSg8KosSQEEpzc6PIY8dEL1bctVQFhB7M1sOGDVPZXlRURN999x0tWLCAUlJStL7/rrvuorlz59Lw4cOFybm+dzrKfVbRc6+24I4L1kmFRa5Xr15a3coMwzCMdQDpsG3bNiHwoqKitL4uMDCQXn/9dRGupAhfqi2FhYXCe4V9RXTrRj55edTs8mVqV1JKsjpImDJbW0r28aFLTYMpz8eHukVECCMFDBeWgMUIO1ijtm/dSpnJKdQmLo5CUlKEKbYu3Lx1i+TyUvFvmHKvhYZSXJs2lJKRQVv/+INu3rxZ6fsCAgJo+/btFb5/APPx8uXLhUv2+vXrWj8T4untt98WDZnrKuiuXr2qIuSuXLlS633gwoN4Uwg5jMvZ2blO42EYhmEsG0iIP//8Uwi8I0eOaH0drGKvvvoqTZs2jVxcXGq9vq9ZuZLy09Io5MIFCoiNFet7A7cG1KBBgzqPvdzGhuICA+lCSAh5BwXS0BEjhEXQ3LEIYZeQkEC/rV9PLteuU/j58+Su5IuvC6k3blB5uaplrtDDk2LCutI1Fxf6dfNmDTeqh4cHnTlzhpo0aSL+zsvLo6+++oo++eQTrUIQ4C4Bgu7ee++tlaDDYbt8+bKKkKvKtasNXGAYg0LIwc1aX7M5wzAMY11gTdq1a5cQePCcaQMhTK+88go988wz5ObmVqv1vcWhg2Sfnl7xnA3ZiP3VN/48x8WFotq2pYKAABr10Hhq2rQpmTNmL+xw0DeuW0cNExKpe0wMyWrpdq2MjMxMKioqrPj7tsXKRrhnz/foQYkNG9LPmzZpCCkkS7zzzjv05Zdf0qJFiyhd6QRUp3///sLliv/XRNDhMCEjSTnZAbGEtQV3N0hwUAi58PBwnWYwMQzDMNYL1qq///6b3n33XbFOaaNhw4Y0c+ZMEYuuLU5bfX2XSkro1q1bdyqf3MbJyZm8dZAEIbe1pSPt21FGcDCNmTDBrMWdWQs7mGd/Xr2aPOOvUq9z5+rselUHe8nJzqZSuZxcXVyEBQtWPDwD021Mr14U7+VFa37+WcUaB4GE+LOsrCyt+4ZlDkGgSD6oLkAVyQ3KyQ5VWf604enpKTKIFEIObmJLiSNgGIZhTBesW++//z7t3r1b62uQmQqjyAsvvCDWq+rWd1SqyC/IV9lHw4Y+5KiDDNxyGxs61KE9ZTVrTg8/Osls3bJmK+yQKPHD999TWcx56nvihE4sddpQP5HK7OzoZL+76by8lFauWVNtQgUYMmSIEHSIX6sM7APlRhRCDvXkqrL4aQM14xQZq3h07NhRZ1m1DMMwDFNbkHgIgffXX39pfQ3CmaZPny4esOBpW99h9ICRo1z6b5u9vQM10lG9VLmtLe0P60r2bdvSo1OmmKUhxGyFHcTPsT17acCRI/WOqasOWOAKClU/I9/dnY4OGEB7jx0TIkwbDzzwgBB0iF1TF6YoAKws5LTVzKsKFFxUiDg8UMiYhRzDMAxjahw9elQIvN9//73KcCEh7mxs6J4jRytd32FoUV0vbcjfz6/elSQUZLu40N89e1D3gQOr9a6ZIjJzrVOHkibIftW3qAO4eygsKiJJ3CHgxJHINSdHZOcUd+tGcXFxGnXuULQYnSJQvFfRYeL48eMVQg7Bpbm5ubUeC9K9lYVcaGiozk5mhmEYhtEX3bt3FyVS0DkCAg/18NRBOJO8oIACLlyEP5bK3NzITs1Y4eLiSgUFhVRaWiL+drC31+k66FFQQK1j4+ioo6PoqGFude7M0mK34ZdfKO3wYRpwPEpncXXVId1xl966eZOkO4Gb8MdH33MPHUpLo42bNlW8Fhk6OTk5oraPQsjBFK1cObumIIBTWcihqjcLOYZhGMbcQYFjFB/euHFjRdejsaNHU08fHwrbu1es78h8dXF1FRm0ygKvXJIoPx8hUhK5urqRrY7XxXIbG9p3Vzj59OpFY8eNI3PC7Cx2KB6MjhJdExINJupIYaeTyitEHcDnN7l0idK7dhXxAQrTMAQggkBhpastLVu2VBFy5pyZwzAMwzDaQFuvX3/9VSQKol866uGFNG9OwdHRFes71tz8/DwqyM8nF1cXUbsO4UYQcg1qUC6lruDzWyYk0smGDYXuMKf2YzJzVPhoE4aOEoZGJrOvcMUq8ElKIpeOHcUJqtwvtqairk2bNhUiDr58uFoZhmEYxlpo3749rV27VvSivXT4MDVM1uzOdFvg5VNxcYlIEtS1ha4ymqSl0dmCApHYiDXaXDArYQdLGBr+ojdcbduE6QKbO7V3oN4VBYwxjqCEBArr2FEkQFTn2UZbFGUhh+QHhmEYhrFmsL5fT0yk0Bs3yd/Hh/Jyc6mgEPVkVddUdIWC4UQX5U2qA+t706QkOh0VJeq/1rcQsqHQe/okqlBDjaPsBvqgxsfHC5fllClTRLwYCuSi84EiDXrVqlWikjTqrSFoccSIEUItg4yMDFq8dCnN+G0TPRAdTaNPnqCYvDwyJDiZJDVR6X39Ork6OQnRpw6+BzJ8Nm3aJAorojvFF198QePGjasQdcgUwm+DOnhVZQsxDMMwjL5AaQ+sWYoHerTWloULF9bps7G+F+Xnk39GBsns7EQ4E7TA2tw8ejwxiSYnJdG05GRKLZVXWYLk2+Qklb+/SEygodFRNDw6SmiGpKKiKsfR/fAhlb/902+PC+OrisWLF1NJye1kjvqArlUDBw4UMYXo0GFyFjskDOzbt49OnjwpRAua0iPjZfLkydSpUyfREguJAPj/3r17K9736KOPilZc4LfffhNFfSGIRMVpSaKlbdpSW2dn+iU1lRZejadVHTrWa5xlkkR2tTHr4rVKljnXrCyS2dgIoZam5CLeuXMnDRo06L/P0VLvDj1mV6xYIbpVMAzDMIwxgJjCel0fIOzQE7Y2YG28ceMGSXI5eSoZa07n59Op4iLaGhZGJUVFdK2oiBq6umpkySrzbXIyPRl0u7VndE4OHcnOpi1dupK9rS2lFheTs13t7Fke+fliXBhfo0aNqhR2U6dOJYcaWhJRj6+y0mTQSmgzirhDaCOTE3YoAQJfuKJlVVBQkCgNgjg5+NIV2Z1IGMCjMlA2BCnR69atE1Y/mEYVbthwd3f6PiW5QpwtjI+nYznZVFou0ZNBQTSicWMqKCujVy5epPjCAurcwJ0OZ2fR9rBwOpubS18mJZKDrS1lo9hxh4707uVLFFdQIDTbK82aUYSXFx3OyqJ5Vy6LzBx7Wxva1KUr3bC3pzcuXyakUoBP/P3JKSdXow7doUOHRK26PXv2iOeQYIH+sdpq9yB+AL/ZlStXdHgUGIZhGKZmYkN9/UHs+Oeff07FxcXCi7ZgwQIhXubMmUNnz54VVqoxY8bQk08+KQwyqPvarl07YfF7+umnRcuwLVu2iH19+OGHokTX2LFjRSjS8OHDRQjTa6+9JsqBbfnlF/o+N5d6enjQa82a042iIvKwk5HM1pYc3dyoTYMGFeP6NzOTliYmUHF5OYW4uNCHIaH0RWIi5crlNOJENHVp0IAiPL3IS2YvRB3wU+qDXtn7oQeU+SY5if5KS6OMc2fpcmoqLV++XGxHosfPP/8sNAwMVfg9UIatd+/e1KxZM9q6dSutWbNGiFyEZz322GM0a9Ysunr1qqhtCy8mBDT0we2Wpf+BTlf4beqjA/Ra7gR12vBFocZhuZo0aZL48itXrhSWuMqAKxYni8JiB5YsWSL6pN7dpw+9+8or9Il/AIW6utKK5GTKKC2lWc2b08+p1ylfXkZPBAVRUVkZjTt1ilZ37EgbbqTSzZISeqNFS4rMyqTJZ8/SiV69hbB75nwM/RkWTr6OjrTo6lVq7+ZKg30aiX1OOH2K/goLp6djYujRgAAh8nDCNJDJhAAMKCujYQ0aiJMCQZy/+vvT71eu0LXr1/X1czIMwzCMRdIkKIhmR0RQu+PH6cMbN2iAmxt1dnam51JSRGmTu1xcaZSfH3VvdHuNnnHhAi1v146c7OxoScJVamjvQI8EBAhX6tGetzs85cnl9PDpU8LwA5H3YOPG1LFBgxq9/0BmJu3NSKe3WrSkw6Gh9NGRw7R+/XrRIx7etT/++EOIMLhovb29haCDdoELNSUlRYizY8eOkYuLi9BB3377rQjXatWqlajjB69lVVSmhUzCYgcrFBQp3LGwWkHcYbDKvPjii+J5ZINqazei0J7FhYUiBfmFC+eppLyc8srKaGvX2wWAIzMzKbaggLbcut1PNa9MLnzp0Tm5NC0oSGzDgfVU8s2HubsLUSfen5VJf2ek07Kk2/75wrIySistFa/55OpVulxYIEQf7hfayOzpm7Q0ypLLxckXYG9PV27coFYtWrCwYxiGYZhakp6RQfP/+oscCgupWJIo1NGRerm60rdBQXSysJCiCgvp6bhY+lwmo1KpnC4W5NP406fEe6EH+nt7a+zTTSajzV3D6EhWFh3MzhKGnSVt2lBJDd5/QGiCTDqec4IKY85RgUxGsbGxdODAAWGlg6gDEHXqQNAhTu7/7d1bTJTpHcfx3zAjzACLMLLLaQeKMiCyXcLqbloOm3rRYG2qXZPamLbppjdNU7yg3TsTQ7tN3MT2ypg0pmkTkyZtvdjEpAeim3jDImnYrm4iC4oruhzsysEDI4EBmv+zjOtaWXQFZN75fpJJPPDyvsyQPL88z/P/P4n/sxlKu2737t1uxnKpULfmq2Jtk6MFOnvZsuyxY8fU29vrwppNY9oUr01P2g++GJuyrKur09zCHjXbY+emTj+67JZJj1ZvkS3OvllRoVfWf3aI8KcWn5AMPdDs8PdbalQSDH7ua34aiejVvDydGR/T3nPv6y8v1upb4bDKfVLn5KTeGBpSmx0UnJHxhev+AADg4TZHo2rZuFEbF4olE2z/+rbMTPfK9Qf0ztioGnPz9I28sN6qrFzy+wZ8PrfiZq9wYJ1OP+L1c/NSS2mp9hQU6NzGct2ur9eePXtcQHsSNoO30lY0iViAS2z+syBn04rW5sNafli36cRM3BdV3tjavM3k7du3T2n3lRpbKPxF2Vf0/q1buhyLuQ/qz8PDbsrV9E1Ouj/X5eTonwsFDZ0TE26W7WHsQz8+NHTv74lq26t376o6O1s/i5RqU2amPp6a0sS6gMqCQX0vN9f9sl2ZnlY0P1+X2BsHAMBj679yRbcX+r+Ox+Majcd1dXpagwv/luZL06DmVZyRobqcZ9R1c0KDCxWutuSaqHb1+3z3coBlAxvDjeWNvtjkktcnNObl6sT1Ebd6N+dL09jEhNsrb8Wctp3M9hyaRLWsrVAmjgm1o9NsldJao9nXWVeMpqYmrZYVnbGzst2WlhZ3vJax1ib79+93myxbW1tduxOrMrE16ba2tnvXHT9+XKdPn3ZHcFkD31OnTrmy54xQyB3zkRDy+/WTkuf1x8FB/aqiwoWu7/7nPTd792x6uv5Q84J+UFSsN3o/dOXOtdnPqCA9XcGHzKz9PFLqZv++81634vPzqsnO1m+rNutPQ4OuqsYipa3NW1C0qpuTn/zX/dtzaWlqysrSlfJyvfNAObQF0oGBAV24cEGHDh1a9H2yil8rErFNp7aR0gpJzpw5s0yfAgAAS4tEIrq2sB0pwQLKwYMHXe84m1A5fPiw2z9m47i16rLTkWxlzipCd+7cqQMHDrgTJKyN2ZEjR9yqnK3U2fe2pckdO3a4/fY2tlvBhI3/5petrfrdiRMKTk25Ioa3olE3bfbm5X7dic+6RrI1Wdn6UVGx2xf3m4qo25Y1MzfnnutA+UZFgkG99lyBa23y8vr12ltYqF/397ttW+ZRrk94NS+sS7GYW6m709Oj7M539cPXX3c/ox0XaufAW2GoLctaSzN7P7Zv3+6WWq14wipb7X1KFE/Y19vq5KOoqqpyXUDsPbcijbNnz7riU0+eFWu/YL3t7fpm59lHvsZCmi2z2i/Kudu3XeGDVbYup+mZaf1r2zb9o6fnXtsWW38fHh5OqmNIAABIlvF9tZz6+tdU1dzs9s0lg6Q6ecL6xHWHQprx+7VukZ5wD7J2Jz/+4AMX8KxdSdumimV/Ll8wpNkNG1xZt/Wks6RtjQUJdQAArMz4vhpm/H7dCYWS6pSopAt2vkBAN7OylL+wvLuUnEBAb9ct7wzdg+x57LlsDd02Vy6mvb3d9eu5n01XHz16dEWfDwAAr43vq+Hmwvi+3MFudHT0/2YAbaWvq6srtYKdrc8Hs7I0HA6vqQ9+eMOnz/Wwsuf7NTc3uxcAAPDO+P64rKfdk57ysZik6s9hB/B+9aWXdLU0otk10lrEnmMgEtGLW7cmzQHBAACsJYzvy2dtvHuPoba2VjPWdiQ/X2vBtfx8xTMzV7zhIAAAXsb4nqLBzgoSyqNRXSor/Vzrk6fB7t9fVqryykoKJQAAeAKM7yka7ExDU5Pu5OfrYknJU32OvpIS9xwNjY1P9TkAAPACxvcUDXZFRUV6uaFBH0ajurUKx3M8zM3MTPVWRvVKY6N7HgAA8GQY31M02CXahOQ9X6Lu6mrFV3mjpd2ve0u1wiUlqq+vX9V7AwDgZYzvKRrs7AiTb+/apVhxsbpqtqzaerzdx+53t6hYO3ftcs8BAACWB+N7igY7U1hYqNe+v1djpaXqfKFmxZO9fX+7j93P7mv3BwAAy4vx/ctLqrNiFzMwMKC3//o3ZQ4NaWtPj3JisRVZc7fpWUvy9qHbwccAAGDlML6naLAzIyMj+vvJkxr/eFCbL15UdHBQacvwo9nUrFXH2EZKW3O36dlkTvIAACQTxvcUDXYmHo+ro6ND/+7oUPaNG9o0cFWRGzfkn5v7Uh2nrTmh9bGxkmerjrGNlMm65g4AQLJifE/RYJcwNDSkdzs69FFfnwKxmMquXVPR6JjWT05q3ezsotfN+P3uwF87G86OEbGO09acsCFJS54BAPASxvcUDXYJ4+PjOn/+vM53d2tqclLz8biy795Vzti40uNxpc3Pac6XpulAQLfCeboTCskXCLgDf+1sODtGJNk6TgMA4HWM7yka7BJmZ2c1Njam69evu9cnIyOanprSbDwufyCg9GBQzxYWqqCgwL3C4XBSHfgLAEAqYnxP0WAHAACQCpK6jx0AAAA+Q7ADAADwCIIdAACARxDsAAAAPIJgBwAA4BEEOwAAAI8g2AEAAHgEwQ4AAMAjCHYAAAAeQbADAADwCIIdAACARxDsAAAAPIJgBwAA4BEEOwAAAI8g2AEAAHgEwQ4AAMAjCHYAAAAeQbADAADwCIIdAACARxDsAAAAPIJgBwAA4BEEOwAAAI8g2AEAAHgEwQ4AAMAjCHYAAAAeQbADAADwCIIdAACAvOF/17hyFFTFS6kAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import tpot\n", "import sklearn.datasets\n", "\n", "scorer = sklearn.metrics.get_scorer('neg_mean_squared_error')\n", "X, y = sklearn.datasets.load_diabetes(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " root_search_space= tpot.config.get_search_space(\"SGDRegressor\"),\n", " leaf_search_space = tpot.search_spaces.nodes.FSSNode(subsets=X_train.shape[1]), \n", " inner_search_space = tpot.config.get_search_space([\"arithmatic\"]),\n", " max_size = 10,\n", ")\n", "\n", "est = tpot.TPOTEstimator( generations=20, \n", " max_time_mins=None,\n", " scorers=['neg_mean_squared_error'],\n", " scorers_weights=[1],\n", " other_objective_functions=[tpot.objectives.number_of_nodes_objective],\n", " other_objective_functions_weights=[-1],\n", " n_jobs=32,\n", " classification=False,\n", " search_space = graph_search_space ,\n", " verbose=2,\n", " )\n", "\n", "\n", "\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))\n", "est.fitted_pipeline_.plot()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdQAAAHWCAYAAADO73hnAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAaQJJREFUeJztnQd8VMX2x096SEIPvYUmgvQqUqQpRVFAkac+pYkVULGBhS7YQH2AoihFn/IQVJ6iogJSRTqIUiQQAWkJENIICUnu//Mb3+5/N9m0zZY7u7/v53OzuTN3752dvXvPnDNnzgkwDMMQQgghhJSIwJK9nRBCCCGAApUQQghxARSohBBCiAugQCWEEEJcAAUqIYQQ4gIoUAkhhBAXQIFKCCGEuAAKVEIIIcQFUKASQgghLoAClfgEr7/+utSrV0+CgoKkZcuW3m6O37B69WrV3+Hh4RIQECCXLl0q0fnWr1+vzoNXC8OGDZOYmBgXtDb/a7gDtBlt9/R1ifegQCVuYfHixerhYdnwwL3mmmtk9OjRcu7cOZde64cffpBnn31WOnXqJIsWLZIZM2a49PzEMRcuXJC77rpLSpUqJfPmzZOPP/5YIiMjvd0sQrxGsPcuTfyBqVOnSt26deXKlSuyefNmeffdd+Xbb7+V3377TSIiIlxyjXXr1klgYKB8+OGHEhoa6pJzksLZsWOHpKSkyLRp06RXr16iC127dpX09HSP3yveui7xHBSoxK307dtX2rZtq/5/4IEHpGLFijJ79mz573//K3fffXeJzn358mUllOPj45WW5KoHFfJFYACAc5L8Qb+DcuXKiU5g8AWLib9cl3gOmnyJR+nRo4d6jYuLs5b9+9//ljZt2igBVqFCBfnHP/4hJ0+etHtft27dpGnTprJr1y410ocgff7555U5GWbetLQ0q3kZ5maQlZWltKf69etLWFiYmtPCezIyMuzOjfJbb71Vvv/+eyX80Y733nvPOuf12WefyZQpU6RGjRpSunRpufPOOyUpKUmd54knnpDKlStLVFSUDB8+PM+50TZ8ZhyDNjRp0kRp6bmxtAFafPv27dWDF3PCH330UZ5jMU/55JNPqvfgnDVr1pT7779fzp8/bz0G7Zg0aZI0aNBAHVOrVi1lFs/dvvxYvny59TuJjo6Wf/7zn3Lq1Cm772Po0KHq/3bt2ql+sp0vzM3x48fl0UcflUaNGqlzYmA1ePBg+fPPP8VVWPoQUwCWeV309xdffGF3nKO5TNv764YbblBthGVl/vz5ea7jbN8WdN0DBw5I9+7d1X2N++y1115z+ro//vijdO7cWQ10cF+iz3HfE/dDDZV4lKNHj6pXPFDByy+/LC+99JKai4MGm5CQIHPmzFFCc8+ePXbaD+bsoPFC4OIBX6VKFSUA33//fdm+fbt88MEH6jg8EAHOt2TJEiUAn3rqKdm2bZvMnDlTDh48KF9++aVduw4fPqw05oceekhGjRqlHkIW8B48YMePHy+xsbGqfSEhIUrjSExMlMmTJ8svv/yiBDkewhMnTrS+F8Lzuuuuk9tuu02Cg4Pl66+/VoIlJydHHnvsMbs24Nxo68iRI5WwWrhwoRJSEGw4B0hNTZUuXbqozzBixAhp3bq1EqRfffWV/PXXX0r44dy4HoTzgw8+KI0bN5b9+/fLm2++KX/88YesXLmywO8InwODAwhKfHbMeb/99tuyZcsW63fywgsvqD5C31vM+hi4FGQe/vnnn9V3hwEABCn6BgIFwsRV5v8jR47IkCFD5OGHH1Z9iAENBDecp2666aYC34vvsl+/fupexL2AgdQjjzyiLB/oa1DSvs3vun369JFBgwapa69YsUKee+45adasmbrfi3Pd33//XQ0qmjdvrr4XCF7cV/juiAdAPlRCXM2iRYuQZ9dYs2aNkZCQYJw8edL4z3/+Y1SsWNEoVaqU8ddffxl//vmnERQUZLz88st2792/f78RHBxsV37jjTeq882fPz/PtYYOHWpERkbale3du1cd/8ADD9iVP/3006p83bp11rI6deqostWrV9sd+9NPP6nypk2bGpmZmdbyu+++2wgICDD69u1rd3zHjh3VuWy5fPlynvb27t3bqFevnl2ZpQ0bN260lsXHxxthYWHGU089ZS2bOHGiOu6LL77Ic96cnBz1+vHHHxuBgYHGpk2b7OrRd3jvli1bjPzA56xcubL6zOnp6dbyVatWqffi+rm/4x07duR7voL6YevWrer9H330UZ4+x6vt95u7Xx1h6cPPP//cWpaUlGRUq1bNaNWqVYHXsNxfs2bNspZlZGQYLVu2VP1h+f6L07doD9pelOva9gGuW7VqVeOOO+6wlhX1um+++abax2+OeB6afIlbgbNKpUqVlHkK2glM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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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AAAA8ADBFgAAAAB4gGALAAAAANJzsDVs2DCLiYmxbt26uce///67e5zQ7aOPPgo8L6H1U6dODdn3smXLrEaNGpYlSxYrX768TZo06ZK/PwAAAADRJVUEWytXrrTx48db1apVA8tKlixpu3btCrk9++yzljNnTmvcuHHI8ydOnBiyXfPmzQPrtmzZYk2bNrVbb73V1qxZ44K59u3b24IFCy7pewQAAAAQXTJFugFHjhyxVq1a2ZtvvmnPPfdcYHnGjBmtaNGiIdvOmjXL7r33XhdwBcubN2+8bf3GjRtnZcuWtREjRrjHFStWtC+++MJGjRpljRo18uQ9AQAAAEDEM1udO3d2maf69esnut2qVatcZqpdu3YJ7qNgwYJ27bXX2ttvv20+ny+wbsWKFfH2rSBLy8/n5MmTdujQoZAbAAAAAKSZzJbGVn3//feuG+GFvPXWWy4rdf3114csHzRokN12222WPXt2W7hwoXXq1Mlly7p06eLW796924oUKRLyHD1WAHX8+HHLli1bvNcaOnSo67IIAAAAAGku2Nq+fbt17drVFi1aZFmzZk10WwVFU6ZMsaeffjreuuBl1atXt6NHj9qLL74YCLZSon///tajR4/AYwVmGkMGAAAAAKm+G6G6Be7du9dVCcyUKZO7LV++3F555RV3/+zZs4Ftp0+fbseOHbPWrVtfcL+1a9e2HTt2uK6AorFce/bsCdlGj3Pnzp1gVktUtVDrg28AAAAAkCYyW/Xq1bN169aFLHvkkUfsqquusr59+7oCGcFdCJs1a2aFChW64H41ritfvnwuYJI6derY3LlzQ7ZRNk3LAQAAACDdBVu5cuWyypUrhyzLkSOHFShQIGT5pk2b7LPPPosXMMns2bNdluq6665zXREVRA0ZMsR69eoV2KZjx442ZswY69Onj7Vt29aWLl1q06ZNszlz5nj8DgEAAABEs4iXfr8QVRcsUaKENWzYMN662NhYGzt2rHXv3t1VINSExSNHjrQOHToEtlHZdwVW2mb06NFuXxMmTKDsOwAAAABPxfiC66QjQSqQkSdPHjt48CDjtwAAAIAodigZsUHE59kCAAAAgPSIYAsAAAAAPECwBQAAAAAeINgCAAAAAA8QbAEAAACABwi2AAAAAMADBFsAAAAA4AGCLQAAAADwAMEWAAAAAHiAYAsAAAAAPECwBQAA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3XXwZZddZqkZpd/TqD///NNluBSE1a1bN9LNwX/lz5/fXnzxRWvXrl2kmxK1jhw5YjVq1LDXXnvNnnvuObvmmmvs5ZdfjnSzojaz9Z///MfWrFkT6abgv/r162dffvmlff7555FuCs5Dv95/8sknLghWhguX1h133GFFihSxt956K7CsRYsWLpuiC3xcesePH3c9u/73f//XmjZtGlhes2ZNa9y4sfu/PjWjG2EadfDgwcDFPSJPv0pOnTrVjh496roTInKUAdY/xvrFC5GnC0b9Cnn55Zdbq1atbNu2bZFuUlT7+OOPrVatWi5roh/sqlevbm+++Wakm4X/OnXqlLugb9u2LYFWhKh72pIlS+zXX391j3/44Qf74osv3EU9IuPMmTPuOitr1qwhyxUA69ykdvTnSIPOnTvnfvlSV5DKlStHujlRbd26dS64OnHihOXMmdNmzZpllSpVinSzopYC3u+//z7N9ONO72rXrm2TJk2yK6+80nbt2mXPPvus3XTTTa4riH6lxKX322+/uS5SPXr0cN3R9V1R16jMmTNbmzZtIt28qKdM8IEDB+zhhx+OdFOiOvt76NAhN740Y8aM7iL/+eefdz8WITJy5crlrrU0dq5ixYou8/jBBx/YihUrrHz58pbaEWyl0V/udbGSFqL59E4XkeoipUzj9OnT3cWKunYScF1627dvt65du9qiRYvi/fqFyAj+JVjj5xR8aXDztGnT6GobwR/rlNkaMmSIe6zMlv4/GTduHMFWKqCua/reKBuMyNC/T5MnT7YpU6a4MVv6P14/cOuc8B2JnPfee89lfDU+S0Gwhgs88MADtmrVKkvtCLbSmCeeeML15f7ss89cgQZEln4N9v+qor7D+pV49OjRrjgDLi39g7t37173D7CffpHUd2XMmDF28uRJ9w80Iidv3rx2xRVX2KZNmyLdlKhVrFixeD8G6ZfiGTNmRKxN+D9bt251xWRmzpwZ6aZEtd69e7vs1v333+8eq1qnzo2qQhNsRU65cuXcj9karqHMo/4tu++++1wX9dSOMVtphOqYKNBSN7WlS5e6kqRInb8a66Iel169evVct079Cum/6Rd8df3QfQKt1FG8ZPPmze4/SUSGup/HnTZEY1OUcURkTZw40Y2jCy4AgEvv2LFjliFD6OWx/v/Q/++IvBw5crj/Q1RZdcGCBa4KdGpHZisNdR1USluVWNR3dffu3W65SpFqgCAuvf79+7vuHqVKlXLzP+j8LFu2zH35cenpexF3DKP+UdZcQoxtjIxevXrZnXfe6S7kd+7caQMHDnQXLer6gcjo3r27KwCgboT33nuvffvtt/bGG2+4GyJHF/IKtpQ5YXqEyNK/WRqjpf/b1Y1Q07mMHDnSdWFD5CxYsMAlHjR8Q70jlIHUuDqV5U/t+EanERrQLLfcckvIcv3jzEDayFCXtdatW7uB/wp6NSZF/xhofhQAZjt27HCB1V9//eXmc7rxxhvt66+/dvcRGZqrUT0k9GPRoEGDXC8JTY3A4P/IUvdBVerkgj7yXn31VTepcadOndz/8xqr9dhjj9mAAQMi3bSodvDgQffvlv5fUSVuleNXUBwbG2upHfNsAQAAAIAHGLMFAAAAAB4g2AIAAAAADxBsAQAAAIAHCLYAAAAAwAMEWwAAAADgAYItAAAAAPAAwRYAAAAAeIBgCwAAAAA8QLAFAEiSW265xbp162ap0S+//GLXXXedZc2a1a655pqItaNMmTL28ssvW2qQlLZcivZOmjTJ8ubN6+lrAEBqRbAFAEjzBg4caDly5LANGzbYkiVLIt2cNGPlypX26KOPhm1/CQVv9913n/36669hew0ASEsyRboBAIDodfbsWYuJibEMGS7ut7/Nmzdb06ZNrXTp0mFrWzQoVKiQ56+RLVs2dwOAaERmCwDSYHe+Ll26WJ8+fSx//vxWtGhRe+aZZ9y633//3QUva9asCWx/4MABt2zZsmXusf7q8YIFC6x69eruQvi2226zvXv32rx586xixYqWO3dua9mypR07dizktc+cOWNPPPGE5cmTxwoWLGhPP/20+Xy+wPqTJ09ar1697LLLLnOZptq1awdeN7hL2ccff2yVKlWyLFmy2LZt2xJ9v+fOnbNBgwZZiRIl3PbqJjh//vzAer2XVatWuW10338szsd/jGbOnGm33nqrZc+e3apVq2YrVqwI2W7GjBl29dVXu9dUxmbEiBEh63W87rzzTnf8ypYta5MnT473Wjr27du3d0GNjqmO8w8//BBYr/tqQ65cudz6mjVr2nfffZdo+5PaPjl8+LA98MAD7lzonIwdOzbRTNSF2iuzZ8+2f/zjH67Lpj4Dd999d+BzuXXrVuvevbs7vrrF7UaoDJeWq9tnsFGjRlm5cuUCj3/88Udr3Lix5cyZ04oUKWIPPfSQ7du3L0nHBQBSE4ItAEiD3nnnHXcB/c0339jw4cNdoLFo0aJk7UNByZgxY+yrr76y7du327333usuvKdMmWJz5syxhQsX2quvvhrvdTNlymTffvutjR492kaOHGkTJkwIrFcgpqBl6tSptnbtWvvXv/5lt99+u23cuDGwjQK4F154wT1v/fr1Vrhw4UTbqddRIPHSSy+5fTZq1MiaNWsW2OeuXbtc0NGzZ093X8FeUvzP//yP21aB6RVXXOGCEgWTouBNx+P++++3devWuWOlwFKBg9/DDz/sjtunn35q06dPt9dee80FYMH0/v1BrPZZo0YNq1evnv39999ufatWrVwQqe58Wt+vXz+LjY29YNuT0j558cUXXSC5evVqt++uXbsm+jm5UHv1uVBw1aRJE7dPddm89tpr3ToFr3ov+izqPOgWl45zrVq14gWmeqzg3h/wKcjTDwEKPBVY79mzx71fAEhzfACANOXmm2/23XjjjSHL/vGPf/j69u3r27Jli9JMvtWrVwfW7d+/3y379NNP3WP91ePFixcHthk6dKhbtnnz5sCyxx57zNeoUaOQ161YsaLv3LlzgWV6TS2TrVu3+jJmzOj7448/QtpWr149X//+/d39iRMnutdZs2ZNkt9v8eLFfc8//3y899upU6fA42rVqvkGDhyYpP35j9GECRMCy9avX++W/fzzz+5xy5YtfQ0aNAh5Xu/evX2VKlVy9zds2OC2//bbbwPr9VwtGzVqlHv8+eef+3Lnzu07ceJEyH7KlSvnGz9+vLufK1cu36RJk3zJdaH2SenSpX233357yDb33Xefr3HjxiHbJKe9derU8bVq1eq87Qren5/OeZ48eQKPtV779PMfS/+xHzx4sK9hw4Yh+9i+fbvbRtsCQFpCZgsA0qCqVauGPC5WrFi8rEpy9qGuWupOd/nll4csi7tPVfzzdw+TOnXquAyTxl4pw6K/yl6o+5f/tnz5cjemyi9z5szx2n8+hw4dsp07d9oNN9wQslyPf/75Z7sYwW3Q8RP/+9W+E3pN/3vVemX41O3P76qrrgqpuqfud0eOHLECBQqEHI8tW7YEjkePHj1ct7369evbsGHDQo5TYi7UvuDzE0yPz3fcktJeZQGV6boYysapK+fXX38dyGopg6bj52+HsoXBbfCvS+rxAYDUggIZAJAGxe1qpgBIY5v8hSaCx1GdPn36gvvQ88+3z6TShXrGjBld9zP9DaYLZj+NcQoO2CIl7vuX5LzfpBwPBXHBY9b8/EGZuv+p+5y656nrnqoqqgumfxzUpZSU9oaj0IXGGKqboLqrKnjX38cffzykHRoLp66mcfmDYgBIKwi2ACAdVpfTeBmNeZHgYhkXS2PEgik7UaFCBRdc6fWUVVF26KabbgrL66lIQ/Hixe3LL7+0m2++ObBcj/1jhbygIiF6jWB6rKyd3qsyLRrfpcBSxSJEZec13shP2Zrdu3e7DJgKUZyP9qmbCkto3NjEiRMvGGxdqH1+/uxR8GM9NyFJaa+ygRqn9cgjjyS4XlnL4Mza+Wismgq86P3+9ttvLtsV3A4V/1Ab1BYASMvoRggA6YgyD8oWqEuauoupC99TTz0Vtv2rcqC6vimw+OCDD1wBDRVdEF3o6yK6devWrliCup+pkMbQoUNd5ialevfu7bIcH374oXtdFXpQAOl/XS+o2IaCisGDB7sKeioMomIi/uIbV155pSv88dhjj7kAVEGXugMGZ37UNVDd9po3b+6KjajrnIqRqDCHCj8cP37cFRRRJklV/BQsqVDG+YKh5LTPT/tUARVto0qEH3300XmP24XaK8q86bzrrz5f6joanIFSgPTZZ5/ZH3/8kWj1wHvuucdVSlRGS9UYFVD7de7c2RXkUCCm46Gug6qcqQAvKYEcAKQmBFsAkM68/fbbLuui8UTdunWz5557Lmz7ViClIEFZJV0U68I9eFJcZWW0jYIBBSS6cNcFc6lSpVL8mipzrwBP+6xSpYqrTqfS8cqoeUXZlWnTprkufZUrV7YBAwa4KnuqQBj8XhUkKOOm4EHHIbiyoromzp071+rWresCBQWjyuAosNJ4OGWg/vrrL3e8tE7V9lTu/Nlnnw1L+0THTIGSso76HKh6pKo5JuRC7fWXd1fApuOvEvzqDqiA2k9tUJCmMu6JzeGlUvfqKqjxWQrQg/kzmQqsGjZs6M65Psfqynix87EBwKUWoyoZl/xVAQBAxGkMlLJjysoBAMKPztAAAEQZzXWm7JHmr9IcZQAAb5CPBwBEVHCJ77i3zz//PNn7GzJkyHn3p256aYHaeb73oPd3sd544w3XRVDd8+KWhwcAhA/dCAEAEbVp06bzrrvsssuSXW5cxRV0S4j2pX2mdiowobFxCcmfP7+7AQBSP4ItAAAAAPAA3QgBAAAAwAMEWwAAAADgAYItAAAAAPAAwRYAAAAAeIBgCwAAAAA8QLAFAAAAAB4g2AIAAAAAC7//B0IAi0GNVAPLAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "df = est.evaluated_individuals\n", "df['mean_squared_error'] = -df['mean_squared_error']\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(5,5))\n", "sns.scatterplot(df[df['Pareto_Front']!=1], y='mean_squared_error', x='number_of_nodes_objective', label='other', ax=ax)\n", "sns.scatterplot(df[df['Pareto_Front']==1], y='mean_squared_error', x='number_of_nodes_objective', label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of all pipelines')\n", "#log scale y\n", "ax.set_yscale('log')\n", "plt.show()\n", "\n", "#replace nans in pareto front with 0\n", "fig, ax = plt.subplots(figsize=(10,5))\n", "sns.scatterplot(df[df['Pareto_Front']==1], y='mean_squared_error', x='number_of_nodes_objective', label='Pareto Front', ax=ax)\n", "ax.title.set_text('Performance of only the Pareto Front')\n", "#log scale y\n", "# ax.set_yscale('log')\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/7_dask_parallelization.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Parallelization\n", "\n", "This tutorial covers advanced setups for parallelizing TPOT with Dask. If you just want to parallelize TPOT within a single computer with multiple processes, set the n_jobs parameter to the number of threads you want to use and skip this tutorial. \n", "\n", "TPOT uses Dask for parallelization and defaults to using a dask.distributed.LocalCluster for local parallelization. A user can pass in a custom Dask client or cluster for advanced usage. For example, a multi-node parallelization is possible using the dask-jobqueue package.\n", "\n", "\n", "TPOT can be easily parallelized on a local computer by setting the n_jobs and memory_limit parameters.\n", "\n", "`n_jobs` dictates how many dask workers to launch. In TPOT this corresponds to the number of pipelines to evaluate in parallel.\n", "\n", "`memory_limit` is the amount of RAM to use per worker. " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### TPOT with Python Scripts\n", "\n", "When running tpot from an .py script, it is important to protect code with `if __name__==\"__main__\":`\n", "\n", "This is due to how parallelization is handled in Python. In short, when Python spawns new processes, each new process reimports code from the relevant .py files, including rerunning code. The context under `if __name__==\"__main__\":` ensures the code under it only executed by the main process and only once. More info [here](https://docs.dask.org/en/stable/scheduling.html#standalone-python-scripts)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n", "Generation: : 8it [01:00, 7.57s/it]\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/decomposition/_fastica.py:595: UserWarning: n_components is too large: it will be set to 4\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/linear_model/_sag.py:349: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n", " warnings.warn(\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.997905982905983\n" ] } ], "source": [ "import tpot\n", "import sklearn\n", "import sklearn.datasets\n", "import numpy as np\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovr')\n", "X, y = sklearn.datasets.load_iris(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "\n", "graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " leaf_search_space = tpot.config.get_search_space(\"selectors\"), \n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " max_size = 10,\n", " )\n", "\n", "est = tpot.TPOTEstimator(\n", " scorers = [\"roc_auc_ovr\"],\n", " scorers_weights = [1],\n", " classification = True,\n", " cv = 10,\n", " search_space = graph_search_space,\n", " max_time_mins = 60,\n", " verbose = 2,\n", " n_jobs=16,\n", " memory_limit=\"4GB\"\n", ")\n", "\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Manual Dask Clients and Dashboard\n", "\n", "You can also manually initialize a dask client. This can be useful to gain additional control over the parallelization, debugging, as well as viewing a dashboard of the live performance of TPOT.\n", "\n", "You can find more details in the official [documentation here.](https://docs.dask.org/en/stable/)\n", "\n", "\n", "[Dask Python Tutorial](https://docs.dask.org/en/stable/deploying-python.html)\n", "[Dask Dashboard](https://docs.dask.org/en/stable/dashboard.html)\n", "\n", "\n", "Note that the if a client is passed in manually, TPOT will ignore n_jobs and memory_limit.\n", "If there is no client passed in, TPOT will ignore any global/existing client and create its own." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Initializing a basic dask local cluster" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from dask.distributed import Client, LocalCluster\n", "\n", "n_jobs = 4\n", "memory_limit = \"4GB\"\n", "\n", "cluster = LocalCluster(n_workers=n_jobs, #if no client is passed in and no global client exists, create our own\n", " threads_per_worker=1,\n", " memory_limit=memory_limit)\n", "client = Client(cluster)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Get the link to view the dask Dashboard. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'http://127.0.0.1:8787/status'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "client.dashboard_link" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 8it [01:01, 7.69s/it]\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/linear_model/_sag.py:349: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n", " warnings.warn(\n", "2025-02-21 16:37:55,843 - distributed.worker.state_machine - WARNING - Async instruction for > ended with CancelledError\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.997905982905983\n" ] } ], "source": [ "graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " leaf_search_space = tpot.config.get_search_space(\"selectors\"), \n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " max_size = 10,\n", " )\n", "\n", "est = tpot.TPOTEstimator(\n", " client = client,\n", " scorers = [\"roc_auc_ovr\"],\n", " scorers_weights = [1],\n", " classification = True,\n", " cv = 10,\n", " search_space = graph_search_space,\n", " max_time_mins = 60,\n", " early_stop=10,\n", " verbose = 2,\n", ")\n", "\n", "\n", "# this is equivalent to: \n", "# est = tpot.TPOTClassifier(population_size= 8, generations=5, n_jobs=4, memory_limit=\"4GB\", verbose=1)\n", "est.fit(X_train, y_train)\n", "print(scorer(est, X_test, y_test))\n", "\n", "#It is good to close the client and cluster when you are done with them\n", "client.close()\n", "cluster.close()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Option 2\n", "\n", "You can initialize the cluster and client with a context manager that will automatically close them. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: : 10it [01:00, 6.07s/it]\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/linear_model/_sag.py:349: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n", " warnings.warn(\n", "2025-02-21 16:38:57,976 - distributed.worker.state_machine - WARNING - Async instruction for > ended with CancelledError\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2025-02-21 16:39:01,975 - distributed.nanny - WARNING - Worker process still alive after 4.0 seconds, killing\n" ] } ], "source": [ "from dask.distributed import Client, LocalCluster\n", "import tpot\n", "import sklearn\n", "import sklearn.datasets\n", "import numpy as np\n", "\n", "scorer = sklearn.metrics.get_scorer('roc_auc_ovr')\n", "X, y = sklearn.datasets.load_iris(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", "\n", "n_jobs = 4\n", "memory_limit = \"4GB\"\n", "\n", "with LocalCluster( \n", " n_workers=n_jobs,\n", " threads_per_worker=1,\n", " memory_limit='4GB',\n", ") as cluster, Client(cluster) as client:\n", " graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " leaf_search_space = tpot.config.get_search_space(\"selectors\"), \n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " max_size = 10,\n", " )\n", "\n", " est = tpot.TPOTEstimator(\n", " client = client,\n", " scorers = [\"roc_auc_ovr\"],\n", " scorers_weights = [1],\n", " classification = True,\n", " cv = 5,\n", " search_space = graph_search_space,\n", " max_time_mins = 60,\n", " early_stop=10,\n", " verbose = 2,\n", " )\n", " est.fit(X_train, y_train)\n", " print(scorer(est, X_test, y_test))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Dask multi node parallelization on HPC\n", "\n", "Dask can parallelize across multiple nodes via job queueing systems. This is done using the Dask-Jobqueue package. More information can be found in the official [documentation here.]( https://jobqueue.dask.org/en/latest/)\n", "\n", "To parallelize TPOT with Dask-Jobqueue, simply pass in a client based on a Jobqueue cluster with desired settings into the client parameter. Each job will evaluate a single pipeline.\n", "\n", "Note that TPOT will ignore n_jobs and memory_limit as these should be set inside the Dask cluster. \n", "\n", "\n", "The following example is specific to the Sun Grid Engine. Other supported clusters can be found in the [Dask-Jobqueue documentation here](https://jobqueue.dask.org/en/latest/examples.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sun Grid Engine is not installed. This example requires Sun Grid Engine to be installed.\n" ] } ], "source": [ "from dask.distributed import Client, LocalCluster\n", "import sklearn\n", "import sklearn.datasets\n", "import sklearn.metrics\n", "import sklearn.model_selection\n", "import tpot\n", "from dask_jobqueue import SGECluster # or SLURMCluster, PBSCluster, etc. Replace SGE with your scheduler.\n", "import os\n", "\n", "if os.system(\"which qsub\") != 0:\n", " print(\"Sun Grid Engine is not installed. This example requires Sun Grid Engine to be installed.\")\n", "else:\n", " print(\"Sun Grid Engine is installed.\")\n", "\n", " \n", " cluster = SGECluster(\n", " queue='all.q',\n", " cores=2,\n", " memory=\"50 GB\"\n", "\n", " )\n", "\n", " cluster.adapt(minimum_jobs=10, maximum_jobs=100) # auto-scale between 10 and 100 jobs\n", "\n", " client = Client(cluster)\n", "\n", " scorer = sklearn.metrics.get_scorer('roc_auc_ovr')\n", " X, y = sklearn.datasets.load_digits(return_X_y=True)\n", " X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)\n", "\n", " graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline(\n", " root_search_space= tpot.config.get_search_space([\"KNeighborsClassifier\", \"LogisticRegression\", \"DecisionTreeClassifier\"]),\n", " leaf_search_space = tpot.config.get_search_space(\"selectors\"), \n", " inner_search_space = tpot.config.get_search_space([\"transformers\"]),\n", " max_size = 10,\n", " )\n", "\n", " est = tpot.TPOTEstimator(\n", " client = client,\n", " scorers = [\"roc_auc\"],\n", " scorers_weights = [1],\n", " classification = True,\n", " cv = 10,\n", " search_space = graph_search_space,\n", " max_time_mins = 60,\n", " early_stop=10,\n", " verbose = 2,\n", " )\n", " est.fit(X_train, y_train)\n", " # this is equivalent to: \n", " # est = tpot.TPOTClassifier(population_size= 8, generations=5, n_jobs=4, memory_limit=\"4GB\", verbose=1)\n", " est.fit(X_train, y_train)\n", " print(scorer(est, X_test, y_test))\n", "\n", " #It is good to close the client and cluster when you are done with them\n", " client.close()\n", " cluster.close()" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/8_SH_and_cv_early_pruning.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Strategies for reducing computational load\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial covers two strategies for pruning the computational load of TPOT to decrease run time." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Successive Halving\n", "\n", "This idea was first tested with TPOT by Parmentier et al. in [\"TPOT-SH: a Faster Optimization Algorithm to Solve the AutoML Problem on Large Datasets\"](https://www.researchgate.net/profile/Laurent-Parmentier-4/publication/339263193_TPOT-SH_A_Faster_Optimization_Algorithm_to_Solve_the_AutoML_Problem_on_Large_Datasets/links/5e5fd8b8a6fdccbeba1c6a56/TPOT-SH-A-Faster-Optimization-Algorithm-to-Solve-the-AutoML-Problem-on-Large-Datasets.pdf). The algorithm operates in two stages. Initially, it trains early generations using a small data subset and a large population size. Later generations then evaluate a smaller set of promising pipelines on larger, or even full, data portions. This approach rapidly identifies top-performing pipeline configurations through initial rough evaluations, followed by more comprehensive assessments. More information on this strategy in Tutorial 8.\n", "\n", "In this tutorial, we will cover the following parameters:\n", "\n", "`population_size`\n", "\n", "`initial_population_size`\n", "\n", "`population_scaling`\n", "\n", "`generations_until_end_population`\n", "\n", "`budget_range`\n", "\n", "`generations_until_end_budget`\n", "\n", "`budget_scaling`\n", "\n", "`stepwise_steps`\n", "\n", "Population size is the number of individuals evaluated each generation. Budget refers to the proportion of data to sample. By manipulating these parameters, we can control how quickly the budget increases and how population size changes over time. Most often, this will be used to start the algorithm by evaluating a large number of pipelines on small subsets of the data to quickly narrow now best models, before later getting a better estimate with larger samples on fewer datasets. This can reduce overall computational cost by not spending as much time evaluating poor performing pipelines.\n", "\n", "`population_size` determines the number of individuals to evalaute each generation. Sometimes we may want to evaluate more or fewer individuals in the earlier generations. The `initial_population_size` parameter specifies the starting size of the population. The population size will gradually move from `initial_population_size` to `population_size` over the course of `generations_until_end_population` generations. `population_scaling` dictates how fast that scaling takes place. The interpolation over `generations_until_end_population` is done stepwise with the number of steps specified by `stepwise_steps`.\n", "\n", "The same process goes for the budget scaling. \n", "\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The following cell illustrates how the population size and budget change over time with the given settings. (Note that tpot happens to converge on this dataset fairly quickly, but we turn off early stop to get the full run. )" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] }, { "data": { "image/png": "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QvAAAAAAwBKCBwAAAADuCx72798vL730knTp0kVOnDhh9v3www+yY8eOG3k5AAAAALkxeFi9erXceeedsn79evnmm28kKSnJ7P/1119l9OjR7mgjAAAAAF8MHkaMGCHjx4+X5cuXS0hIiGN/ixYtZN26da5uHwAAAABfDR62bdsmjzzyyFX7S5YsKadOnXJVuwAAAAD4evBQuHBhOXbs2FX7t2zZImXLlnVVuwAAAAD4evDQuXNneeGFF+T48eMSEBAgaWlp8ssvv8jw4cOlW7du7mklAAAAAN8LHl599VWpUaOGlC9f3kyWjoyMlHvuuUcaN25sKjABAAAAyJ2CsvsNOkn6/fffl5iYGDP/QQOIOnXqSLVq1dzTQgAAAAC+GTysWbPGMfKgN7vLly9LbGysGYUAAAAAkPtkO22pefPmUqtWravKsv71119y7733urJtAAAAAHx9hWmdNN2yZUuZN29euv02m81V7QIAAADg68GDVlgaOXKkfPzxxzJw4EAZNmyYI2jQxwAAAADkTtkOHuyBQocOHeS///2vfPXVV9KmTRs5c+aMO9oHAAAAwJfTluy0ylJcXJwJHDSNCQAAAEDule3goXv37pIvXz7HdqlSpWT16tUmeKhQoYKr2wcAAADAV0u1zp0796p9oaGh8uGHH7qqTQAAAAB8NXj47bff5I477pDAwEBz/1pq1qzpqrYBAAAA8LXgoXbt2nL8+HEpWbKkua9VlZzLstq39euVK1fc2V4AAAAA3hw8HDhwQEqUKOG4DwAAAMD/WAoeKlasmOl9AAAAAP4j29WWdGL04sWLHdvPP/+8FC5cWBo3biwHDx50dfsAAAAA+Grw8OqrrzpKtcbGxsr06dNl4sSJUrx4cRk6dKg72ggAAADAF0u1Hj58WKpWrWruL1y4UB599FHp27evNGnSRJo3b+6ONgIAAADwxZGH/Pnzy//+9z9z/8cff5TWrVub+3nz5pULFy64vIGVKlUyVZwy3gYMGGAev3jxorlfrFgx07aOHTtKQkKCy9sBAAB834wZM0zfQvstDRs2lLi4uCyfqxdFM+uDtG3bNkfbDPh08KDBQu/evc3t999/lwceeMDs37Fjh/kwutqGDRvk2LFjjtvy5cvN/scee8x81VSp7777ThYsWGBWuj569Kh06NDB5e0AAAC+bf78+TJs2DAZPXq0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qFFmcp6uUYDspyp99tlnsmjRIrPWgz3vWCeha4qBfu3Vq5d5v+vx13UJBg0aZE70d911l6eb75NGjhxpUjv0vZyYmGiO/6pVq2TZsmUcbxfT97R9/o6dlnbWNQbs+znerjV8+HCzTo+mKmkZVi1nriP1Xbp04f0N+ApPl3vKzd5++21bhQoVbCEhIaZ067p16zzdpFxh5cqVpnRfxlv37t0d5VpHjRpli4iIMCVaW7ZsaduzZ4+nm+2TMjvOeps7d67jORcuXLD179/flBMNCwuzPfLII7Zjx455tN2+rGfPnraKFSua/zdKlChh3r8//vij43GOt3s5l2pVHG/X6tSpk6106dLm/V22bFmzvW/fPsfjHG/A+wXoP54OYAAAAAB4P+Y8AAAAALCE4AEAAACAJQQPAAAAACwheAAAAABgCcEDAAAAAEsIHgAAAABYQvAAAAAAwBKCBwAAAACWEDwAgAWrVq2SgIAAOXPmjKebAgCAx7DCNABk0Lx5c6ldu7ZMmTLFsS8lJUX++usviYiIMEEEAAD+iJEHAH7j8uXLN/y9ISEhUqpUKQIHAIBfI3gA4HKJiYnyxBNPSHh4uJQuXVomT55sruYPGTLEPH7p0iUZPny4lC1b1jynYcOGJi3Ibt68eVK4cGFZtmyZ3HbbbZI/f365//775dixY+l+zuzZs83jefPmlRo1asg777zjeOzPP/80Hf358+dLs2bNzHM+/fRT+d///iddunQxPzssLEzuvPNO+fzzzx3f99RTT8nq1atl6tSp5vv1pq+VWdrS119/LbfffruEhoZKpUqV5K233krXPt336quvSs+ePaVAgQJSoUIFee+999xyzAEAyAkEDwBcbtiwYfLLL7/Iv//9b1m+fLn897//lc2bNzseHzhwoMTGxsoXX3whv/32mzz22GMmONi7d6/jOefPn5c333xTPv74Y1mzZo0cOnTIBBx2GgjExMTIK6+8Irt27TKd9FGjRsmHH36Yri0jRoyQwYMHm+dER0fLxYsXpV69erJ48WLZvn279O3bV7p27SpxcXHm+Ro0NGrUSPr06WOCFb2VL1/+qt9x06ZN8o9//EM6d+4s27ZtkzFjxpifr4GPMw0ooqKiZMuWLdK/f395+umnZc+ePS493gAA5Bid8wAArnLu3DlbcHCwbcGCBY59Z86csYWFhdkGDx5sO3jwoC1Pnjy2+Pj4dN/XsmVL28iRI839uXPn6lws2759+xyPz5gxwxYREeHYrlKliu2zzz5L9xrjxo2zNWrUyNw/cOCAeY0pU6Zct81t27a1Pfvss47tZs2ambY6W7lypXm906dPm+3HH3/c1rp163TPee6552yRkZGO7YoVK9qefPJJx3ZaWpqtZMmStpkzZ163TQAAeKOgnAtTAPiDP/74w8wtaNCggWNfoUKFpHr16ua+XqW/cuWK3Hrrrem+T1OZihUr5tjWlKIqVao4tjX96cSJE+Z+cnKy7N+/X3r16mVGCOxSU1PNz3KmV/2d6c/WUYovv/xS4uPjzURo/dn687JDRzLatWuXbl+TJk3MJGv9GXny5DH7atas6Xhc05503oT99wAAwNcQPADIUUlJSaZjrWk/9g62nc5tsAsODk73mHa87cXh9DXU+++/b+ZLOMv4mjqnwtkbb7xhUpO0k6/zHfRxnYuhQYQ7ZPZ7pKWlueVnAQDgbgQPAFzqlltuMR3mDRs2mAnC6uzZs/L777/LPffcI3Xq1DFX5vXq+913331DP0PLpZYpU8aMcujE7OzQuRg6YvDkk0+abe3Ia9siIyPTVVbSNl6LTtTW18r42jqikjGAAQAgtyB4AOBSWlWoe/fu8txzz0nRokWlZMmSMnr0aAkMDDRX3bVzrR3+bt26mcnEGkycPHlSVqxYYVJ82rZta+nnvPzyy/LMM8+YNCWdbK2pRxs3bpTTp0+bCdtZqVatmnz11Veydu1aKVKkiEyaNEkSEhLSBQ9aJWn9+vWmypKOhujvkdGzzz4r9evXl3HjxkmnTp3MBPDp06enq/gEAEBuQ7UlAC6nHXKtWPTggw9Kq1atzFwAe0lVNXfuXBM8aAdc50K0b98+3UiFFb179zalWvW1NP1Iy7FqpaPKlStf8/teeuklqVu3rqm8pOVjdQ6C/nxnWtVJRw80oChRooSp9JSRvobOm9CKUXfccYep/DR27FhT6hUAgNyKFaYBuJ1OcNZ1FXSkQSc5AwAA30TaEgCX0zUNdu/ebSou6XwHvSKvMlYnAgAAvoXgAYBb6AJvuhiaTj7WRdl0objixYt7ulkAAOAmkLYEAAAAwBImTAMAAACwhOABAAAAgCUEDwAAAAAsIXgAAAAAYAnBAwAAAABLCB4AAAAAWELwAAAAAMASggcAAAAAYsX/AxXq5fhS3Md+AAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "import tpot\n", "\n", "population_size=30\n", "initial_population_size=100\n", "population_scaling = .5\n", "generations_until_end_population = 50\n", "\n", "budget_range = [.3,1]\n", "generations_until_end_budget=50\n", "budget_scaling = .5\n", "stepwise_steps = 5\n", "\n", "#Population and budget use stepwise\n", "fig, ax1 = plt.subplots()\n", "ax2 = ax1.twinx()\n", "\n", "interpolated_values_population = tpot.utils.beta_interpolation(start=initial_population_size, end=population_size, n=generations_until_end_population, n_steps=stepwise_steps, scale=population_scaling)\n", "interpolated_values_budget = tpot.utils.beta_interpolation(start=budget_range[0], end=budget_range[1], n=generations_until_end_budget, n_steps=stepwise_steps, scale=budget_scaling)\n", "ax1.step(list(range(len(interpolated_values_population))), interpolated_values_population, label=f\"population size\")\n", "ax2.step(list(range(len(interpolated_values_budget))), interpolated_values_budget, label=f\"budget\", color='r')\n", "ax1.set_xlabel(\"generation\")\n", "ax1.set_ylabel(\"population size\")\n", "ax2.set_ylabel(\"bugdet\")\n", "\n", "ax1.legend(loc='center left', bbox_to_anchor=(1.1, 0.4))\n", "ax2.legend(loc='center left', bbox_to_anchor=(1.1, 0.3))\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/metrics/_scorer.py:610: FutureWarning: The `needs_threshold` and `needs_proba` parameter are deprecated in version 1.4 and will be removed in 1.6. You can either let `response_method` be `None` or set it to `predict` to preserve the same behaviour.\n", " warnings.warn(\n", "Generation: 2%|▏ | 1/50 [00:20<16:51, 20.64s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 1\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 4%|▍ | 2/50 [00:45<18:39, 23.32s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 2\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 6%|▌ | 3/50 [01:19<22:03, 28.17s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 3\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 8%|▊ | 4/50 [01:57<24:30, 31.97s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 4\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 10%|█ | 5/50 [02:24<22:44, 30.32s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 5\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 12%|█▏ | 6/50 [03:09<25:40, 35.02s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 6\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 14%|█▍ | 7/50 [03:50<26:29, 36.96s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 7\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 16%|█▌ | 8/50 [04:27<26:04, 37.26s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 8\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 18%|█▊ | 9/50 [05:20<28:45, 42.08s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 9\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 20%|██ | 10/50 [06:03<28:09, 42.25s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 10\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 22%|██▏ | 11/50 [07:16<33:43, 51.88s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 11\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 24%|██▍ | 12/50 [08:04<31:55, 50.42s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 12\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 26%|██▌ | 13/50 [09:13<34:35, 56.10s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 13\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 28%|██▊ | 14/50 [10:15<34:49, 58.04s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 14\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 30%|███ | 15/50 [11:36<37:49, 64.85s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 15\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 32%|███▏ | 16/50 [12:59<39:47, 70.21s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 16\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 34%|███▍ | 17/50 [14:05<37:58, 69.05s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 17\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 36%|███▌ | 18/50 [15:23<38:13, 71.66s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 18\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 38%|███▊ | 19/50 [17:03<41:30, 80.33s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 19\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 40%|████ | 20/50 [18:32<41:28, 82.96s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 20\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 42%|████▏ | 21/50 [22:13<1:00:02, 124.23s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 21\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 44%|████▍ | 22/50 [24:54<1:03:11, 135.40s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 22\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 46%|████▌ | 23/50 [27:03<1:00:01, 133.40s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 23\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 48%|████▊ | 24/50 [29:09<56:48, 131.10s/it] " ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 24\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 50%|█████ | 25/50 [31:26<55:27, 133.09s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 25\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 52%|█████▏ | 26/50 [33:27<51:48, 129.50s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 26\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 54%|█████▍ | 27/50 [35:51<51:12, 133.60s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 27\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 56%|█████▌ | 28/50 [38:40<52:54, 144.28s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 28\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 58%|█████▊ | 29/50 [40:49<48:55, 139.80s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 29\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 60%|██████ | 30/50 [43:49<50:36, 151.83s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 30\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 62%|██████▏ | 31/50 [48:19<59:20, 187.37s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 31\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 64%|██████▍ | 32/50 [50:18<50:01, 166.77s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 32\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 66%|██████▌ | 33/50 [52:22<43:38, 154.01s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 33\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 68%|██████▊ | 34/50 [54:38<39:35, 148.46s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 34\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 70%|███████ | 35/50 [57:43<39:52, 159.52s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 35\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 72%|███████▏ | 36/50 [1:00:16<36:44, 157.44s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 36\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 74%|███████▍ | 37/50 [1:04:37<40:51, 188.57s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 37\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 76%|███████▌ | 38/50 [1:27:43<1:49:32, 547.68s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 38\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 78%|███████▊ | 39/50 [1:29:40<1:16:43, 418.49s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 39\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 80%|████████ | 40/50 [1:32:56<58:39, 351.95s/it] " ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 40\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 82%|████████▏ | 41/50 [1:36:55<47:41, 317.92s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 41\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 84%|████████▍ | 42/50 [1:38:54<34:27, 258.41s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 42\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 86%|████████▌ | 43/50 [1:40:59<25:28, 218.30s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 43\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 88%|████████▊ | 44/50 [1:42:38<18:14, 182.39s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 44\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 90%|█████████ | 45/50 [1:44:18<13:08, 157.68s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 45\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 92%|█████████▏| 46/50 [1:46:13<09:39, 144.91s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 46\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 94%|█████████▍| 47/50 [1:48:29<07:06, 142.24s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 47\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 96%|█████████▌| 48/50 [1:50:06<04:17, 128.67s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 48\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 98%|█████████▊| 49/50 [1:52:18<02:09, 129.85s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 49\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 50/50 [1:54:14<00:00, 137.09s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 50\n", "Best roc_auc_score score: 1.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "total time: 6862.724096059799\n", "test score: 0.9917355371900827\n" ] } ], "source": [ "# A Graph pipeline starting with at least one selector as a leaf, potentially followed by a series\n", "# of stacking classifiers or transformers, and ending with a classifier. The graph will have at most 15 nodes and a max depth of 6.\n", "\n", "import tpot\n", "import sklearn\n", "import sklearn.datasets\n", "import numpy as np\n", "import time\n", "import tpot\n", "import pandas as pd\n", "import numpy as np\n", "from sklearn.linear_model import LogisticRegression\n", "import sklearn\n", "\n", "X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, random_state=1)\n", "scorer = sklearn.metrics.make_scorer(sklearn.metrics.roc_auc_score, needs_proba=True, multi_class='ovr')\n", "\n", "\n", "est = tpot.TPOTEstimator(\n", " generations=50,\n", " max_time_mins=None,\n", " scorers=['roc_auc_ovr'],\n", " scorers_weights=[1],\n", " classification=True,\n", " search_space = 'linear',\n", " n_jobs=32,\n", " cv=10,\n", " verbose=3,\n", "\n", " population_size=population_size,\n", " initial_population_size=initial_population_size,\n", " population_scaling = population_scaling,\n", " generations_until_end_population = generations_until_end_population,\n", " \n", " budget_range = budget_range,\n", " generations_until_end_budget=generations_until_end_budget,\n", " )\n", "\n", "\n", "\n", "start = time.time()\n", "est.fit(X_train, y_train)\n", "print(f\"total time: {time.time()-start}\")\n", "\n", "print(\"test score: \", scorer(est, X_test, y_test))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## CV early pruning\n", "\n", "Most often, we will be evaluating pipelines using cross validation. However, we can often tell within the first few folds whether or not the pipeline is going have a reasonable change of outperforming the previous best pipelines. For example, if the best score so far is .92 AUROC and the average score of the first five folds of our current pipeline is only around .61, we can be reasonably confident that the next five folds are unlikely to this pipeline ahead of the others. We can save a significant amount of compute by not computing the rest of the folds. There are two strategies that TPOT can use to accomplish this (More information on these strategies in Tutorial 8).\n", " 1. Threshold Pruning: Pipelines must achieve a score above a predefined percentile threshold (based on previous pipeline scores) to proceed in each cross-validation (CV) fold.\n", " 2. Selection Pruning: Within each population, only the top N% of pipelines (ranked by performance in the previous CV fold) are selected to evaluate in the next fold.\"\n", "\n", "\n", "We can further reduce computational load by terminating the evaluation of individual pipelines early if the first few CV scores are not promising. Note that this is different than early stopping of the full algorithm. In this section we will cover:\n", "\n", "`threshold_evaluation_pruning`\n", "\n", "`threshold_evaluation_scaling`\n", "\n", "`min_history_threshold`\n", "\n", "`selection_evaluation_pruning`\n", "\n", "`selection_evaluation_scaling`\n", "\n", "Threshold early stopping uses previous scores to identify and terminate the cross validation evaluation of poorly performing pipelines. We calculate the percentile scores from the previously evaluated pipelines. A pipeline must reach the given percentile each fold for the next to be evaluated, otherwise the pipeline is discarded.\n", "\n", "The `threshold_evaluation_pruning` parameter is a list that specifies the starting and ending percentiles to use as a threshold for the evaluation early stopping. W The `threshold_evaluation_scaling` parameter is a float that controls the rate at which the threshold moves from the start to end percentile. The `min_history_threshold` parameter specifies the minimum number of previous scores needed before using threshold early stopping. This ensures that the algorithm has enough historical data to make an informed decision about when to stop evaluating pipelines.\n", "\n", "Selection early stopping uses a selection algorithm after each fold to select which algorithms will be evaluated for the next fold. For example, after evaluating 100 individuals on fold 1, we may want to only evaluate the best 50 for the remaining folds.\n", "\n", "The `selection_evaluation_pruning` parameter is a list that specifies the lower and upper percentage of the population size to select each round of CV. This is used to determine which individuals to evaluate in the next generation. The `selection_evaluation_scaling` parameter is a float that controls the rate at which the selection threshold moves from the start to end percentile.\n", "\n", "By manipulating these parameters, we can control how the algorithm selects individuals to evaluate in the next generation and when to stop evaluating pipelines that are not performing well.\n", "\n", "In practice, the values of these parameters will depend on the specific problem and the available computational resources. \n", "\n", "In the following sections, we will show you how to set and manipulate these parameters using Python code in a Jupyter Notebook. We will also provide examples of how these parameters can affect the performance of the algorithm.\n", "\n", "(Note that in these small test cases, you may not notice much or any performance improvements, these are more likely to be more beneficial in real world scenarios with larger datasets and slower evaluating pipelines.)\n", "\n", "**Considerations:**\n", "It is important to be aware of how CV pruning interacts with the evolutionary algorithm. When pipelines are pruned with one of these methods, they are removed from the live population and thus are no longer used to inform the TPOT algorithm. If too many pipelines are pruned, this could reduce the diversity of pipelines per generation, and limit TPOT's ability to learn. Additionally, the pruning methods may interact with how long it takes TPOT to run. If the pruning algorithm removes the slightly less performant but faster running pipelines, TPOT will most likely fill the next generation with only slower running pipelines, thus technically increasing the total runtime. This may be acceptable since more compute is dedicated to the higher performing pipelines." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGwCAYAAACzXI8XAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAJPlJREFUeJzt3QtUVWXex/E/IDcvoGCCJHitkNQULEWtVsrEMI5pOpUtK0vLGTNSSU0mL3kLc0rNSkgXg120Jqers5aaUTlpYIjWWDamZWIiOJlAilxU3vU875zzelJLzwvs/Zzz/ay1l+fsfdj+7UDnx3P1qaurqxMAAAAD+VpdAAAAgLsIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxmoiHu7MmTNSXFwsLVq0EB8fH6vLAQAAF0Etc/fTTz9JVFSU+Pr6em+QUSEmOjra6jIAAIAbDh48KO3atfPeIKNaYhz/IUJCQqwuBwAAXISKigrdEOH4HPfaIOPoTlIhhiADAIBZfm1YCIN9AQCAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYlgYZtavlpEmTpH379hIcHCz9+vWTgoICl50vZ82aJW3bttXXk5KSZO/evVaWDAAAbMTSIHP//ffLpk2b5OWXX5Zdu3bJzTffrMPKoUOH9PVFixbJsmXLJCsrS7Zt2ybNmjWT5ORkqaqqsrJsAABgEz51qtnDAidPntQ7Wr7zzjsyePBg5/mEhARJSUmRefPmSVRUlDzyyCMyZcoUfa28vFwiIiJk1apVMnLkyIvePTM0NFR/LZtGAgDsQH30nqw9LZ4i2N/vVzd3vFQX+/lt2e7Xp06dktOnT0tQUJDLedWFtGXLFtm/f7+UlJToFhoH9Q/q06eP5OXlXTDIVFdX6+Ps/xAAAM/gCQFANR/clpUnuw97zufT7rnJ0jTAmkhhWZBRrTGJiYm65aVr1666peXVV1/VIaVLly46xCjq/NnUc8e188nIyJA5c+Y0eP0AgMYPMX/IypPCA8esLgU2YlmQUdTYmDFjxsjll18ufn5+Eh8fL3feeacUFha6fc/09HRJS0tzaZGJjo6up4oBAFZRLTGeFGLi2obI2j8lSj33yFjWteSVQaZz586yefNmOXHihA4canbSHXfcIZ06dZLIyEj9mtLSUn3eQT3v2bPnBe8ZGBioDwCAZ3XJVNb8X/3bZyRJ0wDrPjztOq7EG1kaZBzUbCR1HDt2TDZu3KhnK3Xs2FGHmdzcXGdwUWFHzV4aP3681SUDgDE8sUtGhRirxmTAXiz9LlChRf2AXXXVVbJv3z6ZOnWqxMbGyn333adTqlpjZv78+XLFFVfoYDNz5kw9k2nYsGFWlg0ARvG0Lpne7VtZ2pUBe7E0yKgpVWpMy/fffy9hYWEyYsQIWbBggfj7++vr06ZN091O48aNk7KyMhkwYIBs2LDhnJlOAICLQ5cMPI1l68g0FtaRAeDtKmtOSdysjZZPkwUa4vObvZYAAICxCDIAAMBYBBkAAGAsOkoBwIvWXwE8DUEGALxo/RXA09C1BAAXwPorgP3RIgMAF4H1VwB7IsgAwEVgSXzAnuhaAgAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMxepOABoEmy0CaAwEGQD1js0WATQWupYA1Ds2WwTQWGiRAdCg2GwRQEMiyABoUGy2CKAh0bUEAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMFYTqwsA4Kqurk5O1p4Wk1XWmF0/AHMQZACbhZg/ZOVJ4YFjVpcCAEagawmwEdUS40khpnf7VhLs72d1GQA8GC0ygE1tn5EkTQPMDgEqxPj4+FhdBgAPRpABbEqFmKYB/IgCwC+hawkAABiLIAMAAIxFkAEAAMYiyAAAAGNZGmROnz4tM2fOlI4dO0pwcLB07txZ5s2bp9fScFCPZ82aJW3bttWvSUpKkr1791pZNgAAsAlLg8yTTz4pmZmZ8txzz8lXX32lny9atEieffZZ52vU82XLlklWVpZs27ZNmjVrJsnJyVJVVWVl6QAAwAYsndv5ySefyNChQ2Xw4MH6eYcOHeTVV1+VTz/91Nkas3TpUpkxY4Z+nfLSSy9JRESEvP322zJy5EgrywcAAN7cItOvXz/Jzc2Vr7/+Wj///PPPZcuWLZKSkqKf79+/X0pKSnR3kkNoaKj06dNH8vLyznvP6upqqaiocDkAAIBnsrRFZvr06TpoxMbGip+fnx4zs2DBAhk1apS+rkKMolpgzqaeO679XEZGhsyZM6cRqgcAAF7dIvP666/L6tWrZc2aNbJjxw558cUX5amnntJ/uis9PV3Ky8udx8GDB+u1ZgAAYB+WtshMnTpVt8o4xrp0795dDhw4oFtVRo8eLZGRkfp8aWmpnrXkoJ737NnzvPcMDAzUBwAA8HyWtshUVlaKr69rCaqL6cyZM/qxmpatwowaR+OguqLU7KXExMRGrxcAANiLpS0yQ4YM0WNiYmJi5Oqrr5adO3fK4sWLZcyYMfq62jV30qRJMn/+fLniiit0sFHrzkRFRcmwYcOsLB0AAHh7kFHrxahg8uCDD8qRI0d0QPnjH/+oF8BzmDZtmpw4cULGjRsnZWVlMmDAANmwYYMEBQVZWToAALABn7qzl9H1QKorSk3ZVgN/Q0JCrC4H+EWVNackbtZG/Xj33GRpGmDp7xoAYPvPb/ZaAgAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjsf45PIbabeNk7WkxWWWN2fUDQGMjyMBjQswfsvKk8MAxq0sBADQiupbgEVRLjCeFmN7tW0mwv5/VZQCA7dEiA4+zfUaSNA0wOwSoEOPj42N1GQBgewQZeBwVYpoG8K0NAN6AriUAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGNZGmQ6dOggPj4+5xwTJkzQ16uqqvTj8PBwad68uYwYMUJKS0utLBkAANiIpUGmoKBADh8+7Dw2bdqkz9922236z8mTJ8u6detk7dq1snnzZikuLpbhw4dbWTIAALCRJlb+5ZdddpnL84ULF0rnzp3lxhtvlPLycsnOzpY1a9bIwIED9fWcnBzp2rWr5OfnS9++fS2qGgAA2IVtxsjU1NTIK6+8ImPGjNHdS4WFhVJbWytJSUnO18TGxkpMTIzk5eVd8D7V1dVSUVHhcgAAAM9kmyDz9ttvS1lZmdx77736eUlJiQQEBEjLli1dXhcREaGvXUhGRoaEhoY6j+jo6AavHQAAeHmQUd1IKSkpEhUV9f+6T3p6uu6WchwHDx6stxoBAIC9WDpGxuHAgQPy/vvvy5tvvuk8FxkZqbubVCvN2a0yataSunYhgYGB+gAAAJ7PFi0yahBvmzZtZPDgwc5zCQkJ4u/vL7m5uc5ze/bskaKiIklMTLSoUgAAYCeWt8icOXNGB5nRo0dLkyb/V44a3zJ27FhJS0uTsLAwCQkJkdTUVB1imLFUv+rq6uRk7WkxWWWN2fUDAAwNMqpLSbWyqNlKP7dkyRLx9fXVC+Gp2UjJycmyfPlyS+r05BDzh6w8KTxwzOpSAAC4ZD516pPMg6np16p1Rw38Va06cFVZc0riZm0UT9G7fStZ+6dEPYUfAOD5n9+Wt8jAPrbPSJKmAX5ismB/P0IMAHgRggycVIhpGsC3BADAHLaYtQQAAOAOggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAADgfUHm448/lrvuuksSExPl0KFD+tzLL78sW7Zsqc/6AAAA6jfIvPHGG5KcnCzBwcGyc+dOqa6u1ufLy8vliSeecOeWAAAAjRNk5s+fL1lZWbJy5Urx9/d3nu/fv7/s2LHDnVsCAAA0TpDZs2eP3HDDDeecDw0NlbKyMnduCQAA0DhBJjIyUvbt23fOeTU+plOnTu7cEgAAoHGCzAMPPCATJ06Ubdu2iY+PjxQXF8vq1atlypQpMn78eHduCQAAcMmaXPqXiEyfPl3OnDkjgwYNksrKSt3NFBgYqINMamqqO7cEAABonCCjWmEee+wxmTp1qu5iOn78uMTFxUnz5s3duR0AAEDjBRmHgIAAHWAAAABsHWSGDx9+0Td988033a0HAACg/oOMmloNAABgZJDJyclp2EoAAAAuEZtGAgAAz2+RiY+Pl9zcXGnVqpX06tVLz1y6ELYpAAAAtgoyQ4cO1WvFOB7/UpABAACwVZCZPXu28/Hjjz/eUPUAAAA07BgZtZ/S0aNHzzmvNoxkryUAAGDrIPPdd9/J6dOnzzlfXV0t33//fX3UBQAAUL8r+7777rvOxxs3bnRZW0YFGzUYuGPHjpdySwAAgMYJMsOGDdN/qoG+o0ePdrnm7+8vHTp0kKefftr9agAAABoqyKgdrxXV6lJQUCCtW7e+lC8HAACwftPI/fv3128VAAAAjbn7tRoPo44jR444W2oc/vrXv7p7WwAAgIYNMnPmzJG5c+dK7969pW3btiyOBwAAzAkyWVlZsmrVKrn77rvrvyIAAICGXEempqZG+vXr586XAgAAWBtk7r//flmzZk29FHDo0CG56667JDw8XIKDg6V79+6yfft25/W6ujqZNWuW7sJS15OSkmTv3r318ncDAAAv7FqqqqqSFStWyPvvvy89evTQa8icbfHixRd1n2PHjkn//v3lpptukvXr18tll12mQ4raYdth0aJFsmzZMnnxxRf1tO+ZM2dKcnKy7N69W4KCgtwpHwAAeHOQ+de//iU9e/bUj7/44guXa5cy8PfJJ5+U6OhoycnJcZ47e2Vg1RqzdOlSmTFjht5xW3nppZckIiJC3n77bRk5cqQ75QMAAG8OMh9++GG9/OVqywPVunLbbbfJ5s2b5fLLL5cHH3xQHnjgAed6NSUlJbo7yUFti9CnTx/Jy8s7b5BR+z2pw6GioqJeagUAAB4yRsZh3759es+lkydPOltQLsW3334rmZmZcsUVV+j7jB8/Xh5++GHdjaSoEKOoFpizqeeOaz+XkZGhw47jUC0+AADAM7kVZI4ePSqDBg2SK6+8Un73u9/J4cOH9fmxY8fKI488ctH3UQvpxcfHyxNPPCG9evWScePG6dYYNb3bXenp6VJeXu48Dh486Pa9AACABwaZyZMn6wG+RUVF0rRpU+f5O+64QzZs2HDR91EzkeLi4lzOde3aVd9XiYyM1H+Wlpa6vEY9d1z7ucDAQAkJCXE5AACAZ3IryLz33nt6oG67du1czqsuogMHDlz0fdSMpT179ric+/rrr6V9+/bOgb8qsKitEM4e87Jt2zZJTEx0p3QAAODtg31PnDjh0hLj8OOPP+oWkUtp2VEL66mupdtvv10+/fRTPa1bHY4ZUJMmTZL58+frkOSYfh0VFSXDhg1zp3QAAODtLTLXX3+9ngbtoAKHGu+i1nxRa8JcrGuvvVbeeustefXVV6Vbt24yb948Pd161KhRztdMmzZNUlNT9fgZ9frjx4/r7ivWkAEAAD51lzrV6L9rx6jBvmqg7gcffCC33HKLfPnll7pFZuvWrdK5c2exC9UVpWYvqYG/jJc5V2XNKYmbtVE/3j03WZoGuL0hOgAAjf757VaLjGo9UWNZBgwYoBeqU11Nw4cPl507d9oqxAAAAM/m9q/fKiU99thj9VsNAADAJXCrRUZtKbB27dpzzqtzjsXsAAAAbBlk1Oq5rVu3Pud8mzZt9AwkAAAA2wYZtWDd2Zs7Oqj1XxyL2QEAANgyyKiWF7UD9s99/vnnEh4eXh91AQAANMxg3zvvvFNv7tiiRQu54YYb9Dm1e/XEiRPPuyO1J1Kz1k/WnhbTVdaY/28AAHgvt4KMWrjuu+++02vJNGnyv7dQC+Ldc889XjFGRoWYP2TlSeGBY1aXAgCAV2vizod4SUmJrFq1Sm8d8Nlnn0lwcLB0797duUeSp1MtMZ4WYnq3byXB/n5WlwEAQMMHmS5duuiVfNX+R+rwZttnJEnTAPMDgAoxaqsJAAA8Osj4+vrq8HL06FGvDzGKCjEs6w8AgEGzlhYuXChTp07Vey4BAABYxa2mBDWot7KyUq655hoJCAjQY2TOpjaPBAAAsGWQWbp0af1XAgAA0BhBZvTo0e58GQAAgPVjZJRvvvlGZsyYoRfHO3LkiD63fv16PZsJAADAtkFGreKr1o3Ztm2bvPnmm3L8+HHnFgWzZ8+u7xoBAADqL8hMnz5dL4a3adMmPdjXYeDAgZKfn+/OLQEAABonyOzatUtuvfXW824m+cMPP7hzSwAAgMYJMi1btpTDhw+fc37nzp1y+eWXu3NLAACAxgkyaofrRx99VO+5pJa1VxtGbt26VaZMmaLXmAEAALBtkFE7XMfGxkp0dLQe6BsXFyfXX3+99OvXT89kAgAAsO06MmqA78qVK2XWrFl6vMyJEyekV69eejNJAACAxuL2bofZ2dmyZMkS2bt3r36uNpCcNGmS3H///fVZHwAAQP0GGdUSs3jxYklNTZXExER9Li8vTyZPnixFRUUyd+5cd24LAADQ8EEmMzNTdy2pVX0dbrnlFunRo4cONwQZAABg28G+tbW10rt373POJyQkyKlTp+qjLgAAgIYJMnfffbdulfm5FStWyKhRo9y5JQAAQOMO9n3vvfekb9+++rnad0mNj1HryKSlpTlfp8bSAAAA2CbIfPHFFxIfH+/cBVtp3bq1PtQ1B7VYHgAAgK2CzIcfflj/lQAAADTGGBkAAAA7IMgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMJalQebxxx8XHx8flyM2NtZ5vaqqSiZMmCDh4eHSvHlzGTFihJSWllpZMgAAsBHLW2SuvvpqOXz4sPPYsmWL89rkyZNl3bp1snbtWtm8ebMUFxfL8OHDLa0XAADYRxPLC2jSRCIjI885X15eLtnZ2bJmzRoZOHCgPpeTkyNdu3aV/Px86du3rwXVAgAAO7G8RWbv3r0SFRUlnTp1klGjRklRUZE+X1hYKLW1tZKUlOR8rep2iomJkby8vAver7q6WioqKlwOAADgmSwNMn369JFVq1bJhg0bJDMzU/bv3y/XX3+9/PTTT1JSUiIBAQHSsmVLl6+JiIjQ1y4kIyNDQkNDnUd0dHQj/EsAAIDXdS2lpKQ4H/fo0UMHm/bt28vrr78uwcHBbt0zPT1d0tLSnM9ViwxhBgAAz2R519LZVOvLlVdeKfv27dPjZmpqaqSsrMzlNWrW0vnG1DgEBgZKSEiIywEAADyTrYLM8ePH5ZtvvpG2bdtKQkKC+Pv7S25urvP6nj179BiaxMRES+sEAAD2YGnX0pQpU2TIkCG6O0lNrZ49e7b4+fnJnXfeqce3jB07VncThYWF6ZaV1NRUHWKYsQQAACwPMt9//70OLUePHpXLLrtMBgwYoKdWq8fKkiVLxNfXVy+Ep2YjJScny/Lly3nnAACA5lNXV1cnHkwN9lWtO2pdmvoaL1NZc0riZm3Uj3fPTZamAZYvxwMAgFd+fttqjAwAAMClIMgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLNsEmYULF4qPj49MmjTJea6qqkomTJgg4eHh0rx5cxkxYoSUlpZaWicAALAPWwSZgoICeeGFF6RHjx4u5ydPnizr1q2TtWvXyubNm6W4uFiGDx9uWZ0AAMBeLA8yx48fl1GjRsnKlSulVatWzvPl5eWSnZ0tixcvloEDB0pCQoLk5OTIJ598Ivn5+ZbWDAAA7MHyIKO6jgYPHixJSUku5wsLC6W2ttblfGxsrMTExEheXt4F71ddXS0VFRUuBwAA8ExNrPzLX3vtNdmxY4fuWvq5kpISCQgIkJYtW7qcj4iI0NcuJCMjQ+bMmdMg9QIAAHuxrEXm4MGDMnHiRFm9erUEBQXV233T09N1t5TjUH8PAADwTJYFGdV1dOTIEYmPj5cmTZroQw3oXbZsmX6sWl5qamqkrKzM5evUrKXIyMgL3jcwMFBCQkJcDgAA4Jks61oaNGiQ7Nq1y+Xcfffdp8fBPProoxIdHS3+/v6Sm5urp10re/bskaKiIklMTLSoagAAYCeWBZkWLVpIt27dXM41a9ZMrxnjOD927FhJS0uTsLAw3bKSmpqqQ0zfvn0tqhoAANiJpYN9f82SJUvE19dXt8io2UjJycmyfPlyq8sCAAA2Yasg89FHH7k8V4OAn3/+eX0AAADYbh0ZAAAAdxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMJalQSYzM1N69OghISEh+khMTJT169c7r1dVVcmECRMkPDxcmjdvLiNGjJDS0lIrSwYAADZiaZBp166dLFy4UAoLC2X79u0ycOBAGTp0qHz55Zf6+uTJk2XdunWydu1a2bx5sxQXF8vw4cOtLBkAANhIEyv/8iFDhrg8X7BggW6lyc/P1yEnOztb1qxZowOOkpOTI127dtXX+/bta1HVAADALmwzRub06dPy2muvyYkTJ3QXk2qlqa2tlaSkJOdrYmNjJSYmRvLy8i54n+rqaqmoqHA5AACAZ7I8yOzatUuPfwkMDJQ//elP8tZbb0lcXJyUlJRIQECAtGzZ0uX1ERER+tqFZGRkSGhoqPOIjo5uhH8FAADwuq4l5aqrrpLPPvtMysvL5e9//7uMHj1aj4dxV3p6uqSlpTmfqxaZ+g4zwf5+sntusvMxAADw0iCjWl26dOmiHyckJEhBQYE888wzcscdd0hNTY2UlZW5tMqoWUuRkZEXvJ9q2VFHQ/Lx8ZGmAZb/pwMAwOtZ3rX0c2fOnNHjXFSo8ff3l9zcXOe1PXv2SFFRkR5DAwAAYGmzguoGSklJ0QN4f/rpJz1D6aOPPpKNGzfq8S1jx47V3URhYWF6nZnU1FQdYpixBAAALA8yR44ckXvuuUcOHz6sg4taHE+FmN/85jf6+pIlS8TX11cvhKdaaZKTk2X58uW8cwAAQPOpq6urEw+mBvuqkKQGE6tWHQAA4Dmf37YbIwMAAHCxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLE8fgtnx8LFaoVAAABgBsfn9q9tQODxQUZtRqlER0dbXQoAAHDjc1xtVeC1ey2dOXNGiouLpUWLFuLj41OvSVGFo4MHD7KHk03wntgL74e98H7YC+/Hr1PxRIWYqKgovYG017bIqH98u3btGuz+6huQb0J74T2xF94Pe+H9sBfej1/2Sy0xDgz2BQAAxiLIAAAAYxFk3BQYGCizZ8/Wf8IeeE/shffDXng/7IX3o/54/GBfAADguWiRAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZNz3//PPSoUMHCQoKkj59+sinn35qdUleKSMjQ6699lq9cnObNm1k2LBhsmfPHqvLwn8tXLhQr6g9adIkq0vxaocOHZK77rpLwsPDJTg4WLp37y7bt2+3uiyvdPr0aZk5c6Z07NhRvxedO3eWefPm/ep+Qrgwgowb/va3v0laWpqeOrdjxw655pprJDk5WY4cOWJ1aV5n8+bNMmHCBMnPz5dNmzZJbW2t3HzzzXLixAmrS/N6BQUF8sILL0iPHj2sLsWrHTt2TPr37y/+/v6yfv162b17tzz99NPSqlUrq0vzSk8++aRkZmbKc889J1999ZV+vmjRInn22WetLs1YTL92g2qBUa0A6hvRsZ+T2jMjNTVVpk+fbnV5Xu0///mPbplRAeeGG26wuhyvdfz4cYmPj5fly5fL/PnzpWfPnrJ06VKry/JK6v9JW7dulY8//tjqUiAiv//97yUiIkKys7Od50aMGKFbZ1555RVLazMVLTKXqKamRgoLCyUpKcllPyf1PC8vz9LaIFJeXq7/DAsLs7oUr6ZayQYPHuzycwJrvPvuu9K7d2+57bbbdMjv1auXrFy50uqyvFa/fv0kNzdXvv76a/38888/ly1btkhKSorVpRnL4zeNrG8//PCD7uNUifps6vm///1vy+rC/7aMqbEYqhm9W7duVpfjtV577TXd5aq6lmC9b7/9VndlqO7wP//5z/p9efjhhyUgIEBGjx5tdXle2UKmdr6OjY0VPz8//XmyYMECGTVqlNWlGYsgA49qBfjiiy/0bzewxsGDB2XixIl6vJIaCA97BHzVIvPEE0/o56pFRv2cZGVlEWQs8Prrr8vq1atlzZo1cvXVV8tnn32mfwGLiori/XATQeYStW7dWqfo0tJSl/PqeWRkpGV1ebuHHnpI/vGPf8g///lPadeundXleC3V7aoGvavxMQ7qN071vqgxZdXV1frnB42nbdu2EhcX53Kua9eu8sYbb1hWkzebOnWqbpUZOXKkfq5mkB04cEDPwCTIuIcxMpdINccmJCToPs6zf+NRzxMTEy2tzRupseoqxLz11lvywQcf6CmNsM6gQYNk165d+rdMx6FaA1SzuXpMiGl8qqv150sSqPEZ7du3t6wmb1ZZWanHVZ5N/VyozxG4hxYZN6i+ZpWc1f+gr7vuOj0bQ033ve+++6wuzSu7k1QT7TvvvKPXkikpKdHnQ0ND9SwANC71Hvx8fFKzZs30+iWMW7LG5MmT9QBT1bV0++236zWvVqxYoQ80viFDhugxMTExMbpraefOnbJ48WIZM2aM1aWZS02/xqV79tln62JiYuoCAgLqrrvuurr8/HyrS/JK6lv4fEdOTo7VpeG/brzxxrqJEydaXYZXW7duXV23bt3qAgMD62JjY+tWrFhhdUleq6KiQv88qM+PoKCguk6dOtU99thjddXV1VaXZizWkQEAAMZijAwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDABbUWt0jhs3TsLCwsTHx0fv0fRLPvroI/26srKyC75m1apV0rJlywaoFoDV2GsJgK1s2LBBBw8VUDp16qR3nAeACyHIALCVb775Rtq2bas3OgSAX0PXEgDbuPfeeyU1NVWKiop0d1GHDh2kurpaHn74YWnTpo0EBQXJgAEDpKCg4Bfvo1p01O7CTZs2lVtvvVWOHj3aaP8GAI2LIAPANp555hmZO3eutGvXTg4fPqwDy7Rp0+SNN96QF198UXbs2CFdunSR5ORk+fHHH897j23btsnYsWPloYce0uNrbrrpJpk/f36j/1sANA6CDADbCA0NlRYtWoifn59ERkbqFpXMzEz5y1/+IikpKRIXFycrV66U4OBgyc7OvmAY+u1vf6sD0JVXXqlbc1TwAeCZCDIAbD1epra2Vvr37+885+/vL9ddd5189dVX5/0adb5Pnz4u5xITExu8VgDWIMgAAABjEWQA2Fbnzp0lICBAtm7d6jynWmjU2BnVzXQ+Xbt21eNkzpafn9/gtQKwBtOvAdhWs2bNZPz48TJ16lS9QJ6aibRo0SKprKzUA3rPR42JUV1RTz31lAwdOlQ2btyo16YB4JlokQFgawsXLpQRI0bI3XffLfHx8bJv3z4dTlq1anXe1/ft21cPCFaDfq+55hp57733ZMaMGY1eN4DG4VOn1gMHAAAwEC0yAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAABBT/Q9yZSFTz9dH2gAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "import tpot\n", "import time\n", "import sklearn\n", "import sklearn.datasets\n", "\n", "threshold_evaluation_pruning = [30, 90]\n", "threshold_evaluation_scaling = .2 #.5\n", "cv = 10\n", "\n", "#Population and budget use stepwise\n", "fig, ax1 = plt.subplots()\n", "\n", "interpolated_values = tpot.utils.beta_interpolation(start=threshold_evaluation_pruning[0], end=threshold_evaluation_pruning[-1], n=cv, n_steps=cv, scale=threshold_evaluation_scaling)\n", "ax1.step(list(range(len(interpolated_values))), interpolated_values, label=f\"threshold\")\n", "ax1.set_xlabel(\"fold\")\n", "ax1.set_ylabel(\"percentile\")\n", "#ax1.legend(loc='center left', bbox_to_anchor=(1.1, 0.4))\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import tpot\n", "from tpot.search_spaces.pipelines import *\n", "from tpot.search_spaces.nodes import *\n", "from tpot.config.get_configspace import get_search_space\n", "import sklearn.model_selection\n", "import sklearn\n", "\n", "\n", "selectors = get_search_space([\"selectors\",\"selectors_classification\", \"Passthrough\"], random_state=42,)\n", "estimators = get_search_space(['XGBClassifier'],random_state=42,)\n", "\n", "scalers = get_search_space([\"scalers\",\"Passthrough\"],random_state=42,)\n", "\n", "transformers_layer =UnionPipeline([\n", " ChoicePipeline([\n", " DynamicUnionPipeline(get_search_space([\"transformers\"], random_state=42,)),\n", " get_search_space(\"SkipTransformer\"),\n", " ]),\n", " get_search_space(\"Passthrough\")\n", " ]\n", " )\n", " \n", "search_space = SequentialPipeline(search_spaces=[\n", " scalers,\n", " selectors, \n", " transformers_layer,\n", " estimators,\n", " ])" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/metrics/_scorer.py:610: FutureWarning: The `needs_threshold` and `needs_proba` parameter are deprecated in version 1.4 and will be removed in 1.6. You can either let `response_method` be `None` or set it to `predict` to preserve the same behaviour.\n", " warnings.warn(\n" ] } ], "source": [ "import matplotlib.pyplot as plt\n", "import tpot\n", "import time\n", "import sklearn\n", "import sklearn.datasets\n", "\n", "scorer = sklearn.metrics.make_scorer(sklearn.metrics.roc_auc_score, needs_proba=True, multi_class='ovr')\n", "\n", "X, y = sklearn.datasets.make_classification(n_samples=5000, n_features=20, n_classes=5, random_state=1, n_informative=15, n_redundant=5, n_repeated=0, n_clusters_per_class=3, class_sep=.8)\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, random_state=1)\n", "\n", "# search_space = tpot.config.template_search_spaces.get_template_search_spaces(\"linear\",inner_predictors=False, random_state=42)\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: 10%|█ | 1/10 [02:42<24:26, 162.98s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 1\n", "Best roc_auc_score score: 0.9212394545585599\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 20%|██ | 2/10 [06:10<25:14, 189.31s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 2\n", "Best roc_auc_score score: 0.921316057689257\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 30%|███ | 3/10 [10:07<24:37, 211.00s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 3\n", "Best roc_auc_score score: 0.9291812014325632\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 40%|████ | 4/10 [16:26<27:43, 277.33s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 4\n", "Best roc_auc_score score: 0.9291812014325632\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 50%|█████ | 5/10 [21:24<23:44, 284.90s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 5\n", "Best roc_auc_score score: 0.9309353469187138\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 60%|██████ | 6/10 [28:02<21:32, 323.19s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 6\n", "Best roc_auc_score score: 0.9328394699598583\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 70%|███████ | 7/10 [36:02<18:43, 374.57s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 7\n", "Best roc_auc_score score: 0.9341963775600117\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 80%|████████ | 8/10 [45:34<14:34, 437.41s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 8\n", "Best roc_auc_score score: 0.9341963775600117\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 90%|█████████ | 9/10 [54:40<07:51, 471.27s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 9\n", "Best roc_auc_score score: 0.9356175936945494\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 10/10 [1:03:45<00:00, 382.55s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 10\n", "Best roc_auc_score score: 0.9371852416832148\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "total time: 3836.4180731773376\n", "test score: 0.9422368174356803\n" ] } ], "source": [ "# no pruning\n", "est = tpot.TPOTEstimator( \n", " generations=10,\n", " max_time_mins=None,\n", " scorers=['roc_auc_ovr'],\n", " scorers_weights=[1],\n", " classification=True,\n", " search_space = search_space,\n", " population_size=100,\n", " n_jobs=32,\n", " cv=cv,\n", " verbose=3,\n", " random_state=42,\n", " )\n", "\n", "\n", "start = time.time()\n", "est.fit(X_train, y_train)\n", "print(f\"total time: {time.time()-start}\")\n", "print(\"test score: \", scorer(est, X_test, y_test))" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: 10%|█ | 1/10 [02:57<26:40, 177.87s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 1\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 20%|██ | 2/10 [03:57<14:24, 108.05s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 2\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 30%|███ | 3/10 [05:58<13:18, 114.13s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 3\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 40%|████ | 4/10 [07:54<11:29, 114.96s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 4\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 50%|█████ | 5/10 [10:43<11:11, 134.34s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 5\n", "Best roc_auc_score score: 0.921316057689257\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 60%|██████ | 6/10 [13:16<09:23, 140.78s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 6\n", "Best roc_auc_score score: 0.921316057689257\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 70%|███████ | 7/10 [15:05<06:31, 130.43s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 7\n", "Best roc_auc_score score: 0.921316057689257\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 80%|████████ | 8/10 [18:01<04:49, 144.72s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 8\n", "Best roc_auc_score score: 0.9255953925256337\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 90%|█████████ | 9/10 [19:53<02:14, 134.59s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 9\n", "Best roc_auc_score score: 0.9255953925256337\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 10/10 [21:24<00:00, 128.50s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 10\n", "Best roc_auc_score score: 0.9255953925256337\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "total time: 1295.825649023056\n", "test score: 0.9320499022897322\n" ] } ], "source": [ "import tpot.config\n", "import tpot.config.template_search_spaces\n", "import tpot.search_spaces\n", "\n", "\n", "\n", "# search_space = tpot.config.get_search_space([\"RandomForestClassifier\"])\n", "\n", "est = tpot.TPOTEstimator( \n", " generations=10,\n", " max_time_mins=None,\n", " scorers=['roc_auc_ovr'],\n", " scorers_weights=[1],\n", " classification=True,\n", " search_space = search_space,\n", " population_size=100,\n", " n_jobs=32,\n", " cv=cv,\n", " verbose=3,\n", " random_state=42,\n", "\n", " threshold_evaluation_pruning = threshold_evaluation_pruning,\n", " threshold_evaluation_scaling = threshold_evaluation_scaling,\n", " )\n", "\n", "\n", "start = time.time()\n", "est.fit(X_train, y_train)\n", "print(f\"total time: {time.time()-start}\")\n", "print(\"test score: \", scorer(est, X_test, y_test))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "import tpot\n", "\n", "selection_evaluation_pruning = [.9, .3]\n", "selection_evaluation_scaling = .2\n", "\n", "#Population and budget use stepwise\n", "fig, ax1 = plt.subplots()\n", "\n", "interpolated_values = tpot.utils.beta_interpolation(start=selection_evaluation_pruning[0], end=selection_evaluation_pruning[-1], n=cv, n_steps=cv, scale=selection_evaluation_scaling)\n", "ax1.step(list(range(len(interpolated_values))), interpolated_values, label=f\"threshold\")\n", "ax1.set_xlabel(\"fold\")\n", "ax1.set_ylabel(\"percent to select\")\n", "#ax1.legend(loc='center left', bbox_to_anchor=(1.1, 0.4))\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: 10%|█ | 1/10 [02:23<21:31, 143.50s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 1\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 20%|██ | 2/10 [04:00<15:30, 116.31s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 2\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 30%|███ | 3/10 [05:42<12:48, 109.73s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 3\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 40%|████ | 4/10 [07:36<11:08, 111.45s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 4\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 50%|█████ | 5/10 [09:12<08:48, 105.72s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 5\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 60%|██████ | 6/10 [11:04<07:11, 107.81s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 6\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 70%|███████ | 7/10 [12:54<05:26, 108.71s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 7\n", "Best roc_auc_score score: 0.9212394545585602\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 80%|████████ | 8/10 [14:45<03:38, 109.49s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 8\n", "Best roc_auc_score score: 0.925549420935039\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 90%|█████████ | 9/10 [16:49<01:54, 114.03s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 9\n", "Best roc_auc_score score: 0.925549420935039\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 10/10 [18:36<00:00, 111.67s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 10\n", "Best roc_auc_score score: 0.925549420935039\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/decomposition/_fastica.py:595: UserWarning: n_components is too large: it will be set to 20\n", " warnings.warn(\n", "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/sklearn/decomposition/_fastica.py:128: ConvergenceWarning: FastICA did not converge. Consider increasing tolerance or the maximum number of iterations.\n", " warnings.warn(\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "total time: 1129.1526980400085\n", "test score: 0.9324219154371735\n" ] } ], "source": [ "est = tpot.TPOTEstimator( \n", " generations=10,\n", " max_time_mins=None,\n", " scorers=['roc_auc_ovr'],\n", " scorers_weights=[1],\n", " classification=True,\n", " search_space = search_space,\n", " population_size=100,\n", " n_jobs=32,\n", " cv=cv,\n", " verbose=3,\n", " random_state=42,\n", "\n", " selection_evaluation_pruning = selection_evaluation_pruning,\n", " selection_evaluation_scaling = selection_evaluation_scaling,\n", " )\n", "\n", "\n", "start = time.time()\n", "est.fit(X_train, y_train)\n", "print(f\"total time: {time.time()-start}\")\n", "print(\"test score: \", scorer(est, X_test, y_test))" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
roc_auc_scoreParentsVariation_FunctionIndividualGenerationroc_auc_score_step_0Submitted TimestampCompleted TimestampEval Errorroc_auc_score_step_1roc_auc_score_step_2roc_auc_score_step_3roc_auc_score_step_4roc_auc_score_step_5roc_auc_score_step_6roc_auc_score_step_7roc_auc_score_step_8roc_auc_score_step_9Pareto_FrontInstance
00.812263NaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.00.8111531.740198e+091.740198e+09None0.7992130.8077100.8135870.7975280.8206920.8276140.8150690.8054470.824616NaN(MinMaxScaler(), RFE(estimator=ExtraTreesClass...
10.848068NaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.00.8464781.740197e+091.740197e+09None0.8398940.8446190.8483210.8469150.8579020.8558750.8276550.8509380.862081NaN(Passthrough(), RFE(estimator=ExtraTreesClassi...
40.831502NaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.00.8172191.740197e+091.740197e+09None0.8278880.8219110.8255580.8300200.8315290.8369550.8446340.8324990.846805NaN(StandardScaler(), VarianceThreshold(threshold...
50.830374NaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.00.8171501.740197e+091.740197e+09None0.8318850.8206940.8248990.8244090.8278610.8339230.8443080.8327980.845818NaN(MinMaxScaler(), SelectFromModel(estimator=Ext...
60.850091NaNNaN<tpot.search_spaces.pipelines.sequential.Seque...0.00.8435241.740197e+091.740197e+09None0.8411760.8406190.8462090.8495610.8543670.8580350.8601650.8451790.862077NaN(Normalizer(norm='max'), SelectFwe(alpha=0.000...
...............................................................
9830.886974(13, 13)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...9.00.8715801.740198e+091.740198e+09None0.8877620.8825040.8608720.8981000.8855230.8935270.9047790.8845570.900537NaN(StandardScaler(), SelectFromModel(estimator=E...
9860.850281(35, 470)ind_crossover<tpot.search_spaces.pipelines.sequential.Seque...9.00.8374931.740198e+091.740198e+09None0.8582890.8441410.8512600.8489090.8530020.8561320.8453560.8478300.860393NaN(StandardScaler(), SelectPercentile(percentile...
9900.878811(866, 866)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...9.00.8758421.740198e+091.740198e+09None0.8625670.8818580.8855390.8743470.8888580.8912050.8821030.8639520.881838NaN(Normalizer(norm='l1'), SelectPercentile(perce...
9910.835669(72, 855)ind_crossover<tpot.search_spaces.pipelines.sequential.Seque...9.00.8383751.740198e+091.740198e+09None0.8445720.8372340.8227990.8188680.8409710.8451220.8163900.8407090.851650NaN(MinMaxScaler(), SelectPercentile(percentile=4...
9920.892459(898, 898)ind_mutate<tpot.search_spaces.pipelines.sequential.Seque...9.00.8819911.740198e+091.740198e+09None0.8939870.8825140.8873940.9022900.8943600.9039440.8846720.8895880.903849NaN(RobustScaler(quantile_range=(0.0911728428421,...
\n", "

326 rows × 20 columns

\n", "
" ], "text/plain": [ " roc_auc_score Parents Variation_Function \\\n", "0 0.812263 NaN NaN \n", "1 0.848068 NaN NaN \n", "4 0.831502 NaN NaN \n", "5 0.830374 NaN NaN \n", "6 0.850091 NaN NaN \n", ".. ... ... ... \n", "983 0.886974 (13, 13) ind_mutate \n", "986 0.850281 (35, 470) ind_crossover \n", "990 0.878811 (866, 866) ind_mutate \n", "991 0.835669 (72, 855) ind_crossover \n", "992 0.892459 (898, 898) ind_mutate \n", "\n", " Individual Generation \\\n", "0 0]" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "All of the above methods can be used independently or simultaneously as done below:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: 10%|█ | 1/10 [01:34<14:09, 94.40s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 1\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 20%|██ | 2/10 [02:26<09:14, 69.36s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 2\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 30%|███ | 3/10 [03:41<08:23, 71.97s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 3\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 40%|████ | 4/10 [04:52<07:09, 71.53s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 4\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 50%|█████ | 5/10 [05:52<05:37, 67.57s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 5\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 60%|██████ | 6/10 [07:13<04:48, 72.10s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 6\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 70%|███████ | 7/10 [08:06<03:17, 65.84s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 7\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 80%|████████ | 8/10 [08:57<02:02, 61.13s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 8\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 90%|█████████ | 9/10 [09:39<00:55, 55.14s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 9\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 10/10 [10:17<00:00, 61.70s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 10\n", "Best roc_auc_score score: 0.8515086951804098\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "total time: 621.607882976532\n", "test score: 0.9084772293865335\n" ] } ], "source": [ "est = tpot.TPOTEstimator( \n", " generations=10,\n", " max_time_mins=None,\n", " scorers=['roc_auc_ovr'],\n", " scorers_weights=[1],\n", " classification=True,\n", " search_space = search_space,\n", " population_size=30,\n", " n_jobs=3,\n", " cv=cv,\n", " verbose=3,\n", "\n", " initial_population_size=initial_population_size,\n", " population_scaling = population_scaling,\n", " generations_until_end_population = generations_until_end_population,\n", " \n", " budget_range = budget_range,\n", " generations_until_end_budget=generations_until_end_budget,\n", " \n", " threshold_evaluation_pruning = threshold_evaluation_pruning,\n", " threshold_evaluation_scaling = threshold_evaluation_scaling,\n", "\n", " selection_evaluation_pruning = selection_evaluation_pruning,\n", " selection_evaluation_scaling = selection_evaluation_scaling,\n", " )\n", "\n", "\n", "start = time.time()\n", "est.fit(X_train, y_train)\n", "print(f\"total time: {time.time()-start}\")\n", "print(\"test score: \", scorer(est, X_test, y_test))" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/9_Genetic_Algorithm_Overview.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Objective functions can optionally take in step, budget, and generations.\n", "\n", "step - The same objective function will be run for #evaluation_early_stop_steps, the current step will be passed into the function as an interger. (This is useful for getting a single fold of cross validation for example).\n", "\n", "budget - A parameter that varies over the course of the generations. Gets passed into the objective function as a float between 0 and 1. If the budget of the previous evaluation is less than the current budget, it will get re-evaluated. Useful for using smaller datasets earlier in training.\n", "\n", "generations - an int corresponding to the current generation number.\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/envs/tpotenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n", "Generation: 100%|██████████| 100/100 [01:43<00:00, 1.03s/it]\n" ] } ], "source": [ "#knapsack problem\n", "import numpy as np\n", "import tpot\n", "import random\n", "import matplotlib.pyplot as plt\n", "from dask.distributed import Client, LocalCluster\n", "\n", "class SubsetSelector(tpot.individual.BaseIndividual):\n", " def __init__( self,\n", " values,\n", " initial_set = None,\n", " k=1, #step size for shuffling\n", " ):\n", "\n", " if isinstance(values, int):\n", " self.values = set(range(0,values))\n", " else:\n", " self.values = set(values)\n", "\n", "\n", " if initial_set is None:\n", " self.subsets = set(random.choices(values, k=k))\n", " else:\n", " self.subsets = set(initial_set)\n", "\n", " self.k = k\n", "\n", " self.mutation_list = [self._mutate_add, self._mutate_remove]\n", " self.crossover_list = [self._crossover_swap]\n", " \n", "\n", " def mutate(self, rng=None):\n", " mutation_list_copy = self.mutation_list.copy()\n", " random.shuffle(mutation_list_copy)\n", " for func in mutation_list_copy:\n", " if func():\n", " return True\n", " return False\n", "\n", " def crossover(self, ind2, rng=None):\n", " crossover_list_copy = self.crossover_list.copy()\n", " random.shuffle(crossover_list_copy)\n", " for func in crossover_list_copy:\n", " if func(ind2):\n", " return True\n", " return False\n", "\n", " def _mutate_add(self,):\n", " not_included = list(self.values.difference(self.subsets))\n", " if len(not_included) > 1:\n", " self.subsets.update(random.sample(not_included, k=min(self.k, len(not_included))))\n", " return True\n", " else:\n", " return False\n", "\n", " def _mutate_remove(self,):\n", " if len(self.subsets) > 1:\n", " self.subsets = self.subsets - set(random.sample(list(self.subsets), k=min(self.k, len(self.subsets)-1) ))\n", "\n", " def _crossover_swap(self, ss2):\n", " diffs = self.subsets.symmetric_difference(ss2.subsets)\n", "\n", " if len(diffs) == 0:\n", " return False\n", " for v in diffs:\n", " self.subsets.discard(v)\n", " ss2.subsets.discard(v)\n", " random.choice([self.subsets, ss2.subsets]).add(v)\n", " \n", " return True\n", "\n", " def unique_id(self):\n", " return str(tuple(sorted(self.subsets)))\n", "\n", "def individual_generator():\n", " while True:\n", " yield SubsetSelector(values=np.arange(len(values)))\n", "\n", "\n", "values = np.random.randint(200,size=100)\n", "weights = np.random.random(200)*10\n", "max_weight = 50\n", "\n", "def simple_objective(ind, **kwargs):\n", " subset = np.array(list(ind.subsets))\n", " if len(subset) == 0:\n", " return 0, 0\n", "\n", " total_weight = np.sum(weights[subset])\n", " total_value = np.sum(values[subset])\n", "\n", " if total_weight > max_weight:\n", " total_value = 0\n", "\n", " return total_value, total_weight\n", "\n", "objective_names = [\"Value\", \"Weight\"]\n", "objective_function_weights = [1,-1]\n", "\n", "\n", "\n", "evolver = tpot.evolvers.BaseEvolver( individual_generator=individual_generator(), \n", " objective_functions=[simple_objective],\n", " objective_function_weights = objective_function_weights,\n", " bigger_is_better = True,\n", " population_size= 100,\n", " objective_names = objective_names,\n", " generations= 100,\n", " n_jobs=32,\n", " verbose = 1,\n", "\n", ")\n", "\n", "evolver.optimize()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "best subset {1, 8, 9, 16, 17, 22, 23, 24, 28, 29, 31, 42, 43, 48, 50, 61, 62, 68, 80, 89, 91, 97, 98}\n", "Best value 3070.0, weight 49.01985602703945\n", "\n", "All results\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Selected IndexValueWeightParentsVariation_FunctionIndividualGenerationSubmitted TimestampCompleted TimestampEval ErrorPareto_Front
0(40,)89.09.883465NaNNaN<__main__.SubsetSelector object at 0x32aa80eb0>0.01.740209e+091.740209e+09NoneNaN
1(45,)116.06.643557NaNNaN<__main__.SubsetSelector object at 0x32aa83b50>0.01.740209e+091.740209e+09NoneNaN
2(52,)172.09.273163NaNNaN<__main__.SubsetSelector object at 0x32aa81210>0.01.740209e+091.740209e+09NoneNaN
3(33,)112.01.594347NaNNaN<__main__.SubsetSelector object at 0x32aa838e0>0.01.740209e+091.740209e+09NoneNaN
4(37,)90.03.273826NaNNaN<__main__.SubsetSelector object at 0x32aa83e50>0.01.740209e+091.740209e+09NoneNaN
....................................
9995(1, 9, 16, 23, 24, 31, 77, 79)998.011.622582((1, 9, 16, 17, 23, 24, 31, 77), (1, 9, 16, 17...ind_mutate<__main__.SubsetSelector object at 0x3a739b010>99.01.740209e+091.740209e+09NoneNaN
9996(1, 8, 9, 16, 22, 23, 24, 28, 29, 31, 48, 49, ...0.051.400433((1, 8, 9, 16, 17, 22, 23, 24, 28, 29, 31, 48,...ind_mutate<__main__.SubsetSelector object at 0x3af9a4460>99.01.740209e+091.740209e+09NoneNaN
9997(1, 4, 8, 9, 16, 17, 23, 24, 31, 49, 68, 77, 8...1728.015.997430((1, 4, 8, 9, 16, 17, 23, 24, 31, 68, 77, 88, ...ind_mutate<__main__.SubsetSelector object at 0x3aa303430>99.01.740209e+091.740209e+09None1.0
9998(8, 9, 17, 23, 24, 25, 31, 51, 77)972.011.991547((8, 9, 17, 23, 24, 31, 77, 88), (8, 9, 17, 23...ind_mutate<__main__.SubsetSelector object at 0x3a7399600>99.01.740209e+091.740209e+09NoneNaN
9999(8, 23, 24, 73, 79)648.012.109013((8, 16, 17, 23, 24), (8, 16, 17, 23, 24))ind_mutate<__main__.SubsetSelector object at 0x3a88d4430>99.01.740209e+091.740209e+09NoneNaN
\n", "

10000 rows × 11 columns

\n", "
" ], "text/plain": [ " Selected Index Value Weight \\\n", "0 (40,) 89.0 9.883465 \n", "1 (45,) 116.0 6.643557 \n", "2 (52,) 172.0 9.273163 \n", "3 (33,) 112.0 1.594347 \n", "4 (37,) 90.0 3.273826 \n", "... ... ... ... \n", "9995 (1, 9, 16, 23, 24, 31, 77, 79) 998.0 11.622582 \n", "9996 (1, 8, 9, 16, 22, 23, 24, 28, 29, 31, 48, 49, ... 0.0 51.400433 \n", "9997 (1, 4, 8, 9, 16, 17, 23, 24, 31, 49, 68, 77, 8... 1728.0 15.997430 \n", "9998 (8, 9, 17, 23, 24, 25, 31, 51, 77) 972.0 11.991547 \n", "9999 (8, 23, 24, 73, 79) 648.0 12.109013 \n", "\n", " Parents Variation_Function \\\n", "0 NaN NaN \n", "1 NaN NaN \n", "2 NaN NaN \n", "3 NaN NaN \n", "4 NaN NaN \n", "... ... ... \n", "9995 ((1, 9, 16, 17, 23, 24, 31, 77), (1, 9, 16, 17... ind_mutate \n", "9996 ((1, 8, 9, 16, 17, 22, 23, 24, 28, 29, 31, 48,... ind_mutate \n", "9997 ((1, 4, 8, 9, 16, 17, 23, 24, 31, 68, 77, 88, ... ind_mutate \n", "9998 ((8, 9, 17, 23, 24, 31, 77, 88), (8, 9, 17, 23... ind_mutate \n", "9999 ((8, 16, 17, 23, 24), (8, 16, 17, 23, 24)) ind_mutate \n", "\n", " Individual Generation \\\n", "0 <__main__.SubsetSelector object at 0x32aa80eb0> 0.0 \n", "1 <__main__.SubsetSelector object at 0x32aa83b50> 0.0 \n", "2 <__main__.SubsetSelector object at 0x32aa81210> 0.0 \n", "3 <__main__.SubsetSelector object at 0x32aa838e0> 0.0 \n", "4 <__main__.SubsetSelector object at 0x32aa83e50> 0.0 \n", "... ... ... \n", "9995 <__main__.SubsetSelector object at 0x3a739b010> 99.0 \n", "9996 <__main__.SubsetSelector object at 0x3af9a4460> 99.0 \n", "9997 <__main__.SubsetSelector object at 0x3aa303430> 99.0 \n", "9998 <__main__.SubsetSelector object at 0x3a7399600> 99.0 \n", "9999 <__main__.SubsetSelector object at 0x3a88d4430> 99.0 \n", "\n", " Submitted Timestamp Completed Timestamp Eval Error Pareto_Front \n", "0 1.740209e+09 1.740209e+09 None NaN \n", "1 1.740209e+09 1.740209e+09 None NaN \n", "2 1.740209e+09 1.740209e+09 None NaN \n", "3 1.740209e+09 1.740209e+09 None NaN \n", "4 1.740209e+09 1.740209e+09 None NaN \n", "... ... ... ... ... \n", "9995 1.740209e+09 1.740209e+09 None NaN \n", "9996 1.740209e+09 1.740209e+09 None NaN \n", "9997 1.740209e+09 1.740209e+09 None 1.0 \n", "9998 1.740209e+09 1.740209e+09 None NaN \n", "9999 1.740209e+09 1.740209e+09 None NaN \n", "\n", "[10000 rows x 11 columns]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "final_population_results = evolver.population.evaluated_individuals\n", "final_population_results.reset_index(inplace=True)\n", "final_population_results = final_population_results.rename(columns = {'index':'Selected Index'})\n", "\n", "best_idx = final_population_results[\"Value\"].idxmax()\n", "best_individual = final_population_results.loc[best_idx]['Individual']\n", "print(\"best subset\", best_individual.subsets)\n", "print(\"Best value {0}, weight {1}\".format(final_population_results.loc[best_idx, \"Value\"],final_population_results.loc[best_idx, \"Weight\"]))\n", "print()\n", "\n", "print(\"All results\")\n", "final_population_results" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from scipy.stats import binned_statistic_2d\n", "\n", "y = final_population_results[\"Value\"]\n", "x = final_population_results[\"Weight\"]\n", "c = final_population_results[\"Generation\"]\n", "\n", "x_bins = np.linspace(0, 100, 100)\n", "y_bins = np.linspace(0, 3000, 100)\n", "\n", "ret = binned_statistic_2d(x, y, c, statistic=np.mean, bins=[x_bins, y_bins])\n", "\n", "fig, ax1 = plt.subplots(1, 1, figsize=(12, 4))\n", "\n", "im = ax1.imshow(ret.statistic.T, origin='lower', extent=(0,100,0,3000), vmin=0, vmax=100, aspect=.03)\n", "ax1.set_xlabel(\"Weight\")\n", "ax1.set_ylabel(\"Value\")\n", "ax1.set_title(\"Binned Average Generation\")\n", "\n", "cbar = fig.colorbar(im,)\n", "cbar.set_label('Generation')\n", "plt.tight_layout()" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/amltk_search_space_parser_example.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "The AMLTK (https://github.com/automl/amltk) provides a framework for developing AutoML systems. One component of this system is the search space definitions. \n", "\n", "TPOT provides a function called tpot.utils.tpot_parser which can convert a search space defined in the AMLTK API into the search space class used by TPOT. This allows users to define a single search space to be used by both algorithms, facilitating better comparisons. Below is an example of a few search spaces defined in AMLTK and how to use them in TPOT.\n", "\n", "Note: this feature is still experimental and not all features present in the AMLTK API are fully supported in TPOT yet. (For example, automated splitting based on categorical vs numeric with amltk.pipeline.Split is not currently implemented in the parser.)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "ModuleNotFoundError", "evalue": "No module named 'amltk'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[1], line 7\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01msklearn\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpreprocessing\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m OneHotEncoder\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01msklearn\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01msvm\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m SVC\n\u001b[0;32m----> 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mamltk\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpipeline\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Choice, Component, Sequential, Split\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mtpot\u001b[39;00m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01msklearn\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpreprocessing\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m FunctionTransformer\n", "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'amltk'" ] } ], "source": [ "from sklearn.compose import make_column_selector\n", "import numpy as np\n", "from sklearn.ensemble import RandomForestClassifier\n", "from sklearn.impute import SimpleImputer\n", "from sklearn.preprocessing import OneHotEncoder\n", "from sklearn.svm import SVC\n", "from amltk.pipeline import Choice, Component, Sequential, Split\n", "import tpot\n", "from sklearn.preprocessing import FunctionTransformer\n", "from sklearn.compose import make_column_transformer\n", "import tpot\n", "import numpy as np\n", "import sklearn\n", "import sklearn.datasets\n", "import pandas as pd\n", "# create dummy pandas dataset with both categorical and numerical columns\n", "X, y = sklearn.datasets.make_classification(n_samples=100, n_features=5, n_informative=3, n_classes=2, random_state=42)\n", "X = pd.DataFrame(X, columns=[f\"num_{i}\" for i in range(5)])\n", "# add 5 categorical columns\n", "for i in range(5):\n", " X[f\"cat_{i}\"] = np.random.choice([\"A\", \"B\", \"C\"], size=100)\n", "y = y.flatten()\n", "# train test split\n", "X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.5)\n", "\n", "# TODO: implement support for this condition\n", "# select_categories = make_column_selector(dtype_include=object)\n", "# select_numerical = make_column_selector(dtype_include=np.number)\n", "\n", "# split_imputation = Split(\n", "# {\n", "# \"categories\": [SimpleImputer(strategy=\"constant\", fill_value=\"missing\"), OneHotEncoder(drop=\"first\")],\n", "# \"numerics\": Component(SimpleImputer, space={\"strategy\": [\"mean\", \"median\"]}),\n", "# },\n", "# config={\"categories\": select_categories, \"numerics\": select_numerical}, #not yet supported\n", "# name=\"feature_preprocessing\",\n", "# )\n", "# split_imputation\n", "\n", "select_categories = make_column_selector(dtype_include=object)\n", "select_numerical = make_column_selector(dtype_include=np.number)\n", "\n", "cat_selector = make_column_transformer((\"passthrough\", select_categories))\n", "num_selector = make_column_transformer((\"passthrough\", select_numerical))\n", "\n", "\n", "split_imputation = Split(\n", " {\n", " \"categories\": [cat_selector,SimpleImputer(strategy=\"constant\", fill_value=\"missing\"), OneHotEncoder(drop=\"first\", sparse_output=False)],\n", " \"numerics\": [num_selector, Component(SimpleImputer, space={\"strategy\": [\"mean\", \"median\"]})],\n", " },\n", " name=\"split_imputation\",\n", ")\n", "split_imputation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
╭─ Sequential(my_pipeline) ───────────────────────────────────────────────────────────────────────────────────────╮\n",
       " ╭─ Split(split_imputation) ───────────────────────────────────────────────────────────────────────────────────╮ \n",
       "  ╭─ Sequential(categories) ─────────────────────────╮ ╭─ Sequential(numerics) ─────────────────────────────╮  \n",
       "   ╭─ Fixed(ColumnTransformer) ───────────────────╮   ╭─ Fixed(ColumnTransformer) ─────────────────────╮   \n",
       "    item ColumnTransformer(transformers=[('pass…     item ColumnTransformer(transformers=[('passth…    \n",
       "         'passthrough',                                   'passthrough',                               \n",
       "                                          <skle…                                           <sklear…    \n",
       "         object at 0x7d354d946290>)])                     object at 0x7d34edf94fa0>)])                 \n",
       "   ╰──────────────────────────────────────────────╯   ╰────────────────────────────────────────────────╯   \n",
       "         \n",
       "   ╭─ Fixed(SimpleImputer) ───────────────────────╮   ╭─ Component(SimpleImputer) ─────────────╮           \n",
       "    item SimpleImputer(fill_value='missing',         item  class SimpleImputer(...)                    \n",
       "         strategy='constant')                        space {'strategy': ['mean', 'median']}            \n",
       "   ╰──────────────────────────────────────────────╯   ╰────────────────────────────────────────╯           \n",
       "     ╰────────────────────────────────────────────────────╯  \n",
       "   ╭─ Fixed(OneHotEncoder) ───────────────────────╮                                                          \n",
       "    item OneHotEncoder(drop='first',                                                                       \n",
       "         sparse_output=False)                                                                              \n",
       "   ╰──────────────────────────────────────────────╯                                                          \n",
       "  ╰──────────────────────────────────────────────────╯                                                         \n",
       " ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \n",
       "  \n",
       " ╭─ Choice(selectors) ─────────────────────────────────────────────────────╮                                     \n",
       "  ╭─ Component(SelectKBest) ─────╮ ╭─ Component(VarianceThreshold) ─────╮                                      \n",
       "   item  class SelectKBest(...)   item  class VarianceThreshold(...)                                       \n",
       "   space {'k': (1, 10)}           space {'threshold': (0.1, 1)}                                            \n",
       "  ╰──────────────────────────────╯ ╰────────────────────────────────────╯                                      \n",
       " ╰─────────────────────────────────────────────────────────────────────────╯                                     \n",
       "  \n",
       " ╭─ Split(transformers) ─────────────────────────────────────────────────────────────────────────────────╮       \n",
       "  ╭─ Sequential(passthrough) ─╮ ╭─ Sequential(polynomial) ────────────────╮ ╭─ Sequential(zerocount) ─╮        \n",
       "   ╭─ Fixed(Passthrough) ─╮    ╭─ Component(PolynomialFeatures) ─────╮   ╭─ Fixed(ZeroCount) ─╮          \n",
       "    item Passthrough()        item  class PolynomialFeatures(...)     item ZeroCount()             \n",
       "   ╰──────────────────────╯     space {'degree': [2, 3]}               ╰────────────────────╯          \n",
       "  ╰───────────────────────────╯  ╰─────────────────────────────────────╯  ╰─────────────────────────╯        \n",
       "                                ╰─────────────────────────────────────────╯                                    \n",
       " ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯       \n",
       "  \n",
       " ╭─ Choice(estimator) ─────────────────────────────────────────────────────────────────────────────────────────╮ \n",
       "  ╭─ Component(RandomForestClassifier) ──────────╮ ╭─ Component(SVC) ────────────────────────────╮             \n",
       "   item   class RandomForestClassifier(...)       item  class SVC(...)                                     \n",
       "   config {'max_depth': 3}                        space {'kernel': ['linear', 'rbf', 'poly']}              \n",
       "   space  {                                      ╰─────────────────────────────────────────────╯             \n",
       "              'n_estimators': (10, 100),                                                                     \n",
       "              'criterion': [                                                                                 \n",
       "                  'gini',                                                                                    \n",
       "                  'log_loss'                                                                                 \n",
       "              ]                                                                                              \n",
       "          }                                                                                                  \n",
       "  ╰──────────────────────────────────────────────╯                                                             \n",
       " ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \n",
       "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
       "
\n" ], "text/plain": [ "\u001b[38;2;126;107;143m╭─\u001b[0m\u001b[38;2;126;107;143m \u001b[0m\u001b[1;38;2;126;107;143mSequential\u001b[0m\u001b[38;2;126;107;143m(\u001b[0m\u001b[3;38;2;126;107;143mmy_pipeline\u001b[0m\u001b[38;2;126;107;143m) \u001b[0m\u001b[38;2;126;107;143m──────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[38;2;126;107;143m─╮\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m╭─\u001b[0m\u001b[38;2;119;125;167m \u001b[0m\u001b[1;38;2;119;125;167mSplit\u001b[0m\u001b[38;2;119;125;167m(\u001b[0m\u001b[3;38;2;119;125;167msplit_imputation\u001b[0m\u001b[38;2;119;125;167m) \u001b[0m\u001b[38;2;119;125;167m──────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[38;2;119;125;167m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m╭─\u001b[0m\u001b[38;2;126;107;143m \u001b[0m\u001b[1;38;2;126;107;143mSequential\u001b[0m\u001b[38;2;126;107;143m(\u001b[0m\u001b[3;38;2;126;107;143mcategories\u001b[0m\u001b[38;2;126;107;143m) \u001b[0m\u001b[38;2;126;107;143m────────────────────────\u001b[0m\u001b[38;2;126;107;143m─╮\u001b[0m \u001b[38;2;126;107;143m╭─\u001b[0m\u001b[38;2;126;107;143m \u001b[0m\u001b[1;38;2;126;107;143mSequential\u001b[0m\u001b[38;2;126;107;143m(\u001b[0m\u001b[3;38;2;126;107;143mnumerics\u001b[0m\u001b[38;2;126;107;143m) \u001b[0m\u001b[38;2;126;107;143m────────────────────────────\u001b[0m\u001b[38;2;126;107;143m─╮\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╭─\u001b[0m\u001b[38;2;86;53;30m \u001b[0m\u001b[1;38;2;86;53;30mFixed\u001b[0m\u001b[38;2;86;53;30m(\u001b[0m\u001b[3;38;2;86;53;30mColumnTransformer\u001b[0m\u001b[38;2;86;53;30m) \u001b[0m\u001b[38;2;86;53;30m──────────────────\u001b[0m\u001b[38;2;86;53;30m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╭─\u001b[0m\u001b[38;2;86;53;30m \u001b[0m\u001b[1;38;2;86;53;30mFixed\u001b[0m\u001b[38;2;86;53;30m(\u001b[0m\u001b[3;38;2;86;53;30mColumnTransformer\u001b[0m\u001b[38;2;86;53;30m) \u001b[0m\u001b[38;2;86;53;30m────────────────────\u001b[0m\u001b[38;2;86;53;30m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39mitem\u001b[0m\u001b[39m \u001b[0m\u001b[1;35mColumnTransformer\u001b[0m\u001b[1;39m(\u001b[0m\u001b[33mtransformers\u001b[0m\u001b[39m=\u001b[0m\u001b[1;39m[\u001b[0m\u001b[1;39m(\u001b[0m\u001b[32m'pass…\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39mitem\u001b[0m\u001b[39m \u001b[0m\u001b[1;35mColumnTransformer\u001b[0m\u001b[1;39m(\u001b[0m\u001b[33mtransformers\u001b[0m\u001b[39m=\u001b[0m\u001b[1;39m[\u001b[0m\u001b[1;39m(\u001b[0m\u001b[32m'passth…\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[32m'passthrough'\u001b[0m\u001b[39m, \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[32m'passthrough'\u001b[0m\u001b[39m, \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[39m \u001b[0m\u001b[1;39m<\u001b[0m\u001b[1;95mskle…\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[39m \u001b[0m\u001b[1;39m<\u001b[0m\u001b[1;95msklear…\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[39mobject at \u001b[0m\u001b[1;36m0x7d354d946290\u001b[0m\u001b[1;39m>\u001b[0m\u001b[1;39m)\u001b[0m\u001b[1;39m]\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[39mobject at \u001b[0m\u001b[1;36m0x7d34edf94fa0\u001b[0m\u001b[1;39m>\u001b[0m\u001b[1;39m)\u001b[0m\u001b[1;39m]\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╰──────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╰────────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[1m ↓ \u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[1m ↓ \u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╭─\u001b[0m\u001b[38;2;86;53;30m \u001b[0m\u001b[1;38;2;86;53;30mFixed\u001b[0m\u001b[38;2;86;53;30m(\u001b[0m\u001b[3;38;2;86;53;30mSimpleImputer\u001b[0m\u001b[38;2;86;53;30m) \u001b[0m\u001b[38;2;86;53;30m──────────────────────\u001b[0m\u001b[38;2;86;53;30m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m╭─\u001b[0m\u001b[38;2;230;175;46m \u001b[0m\u001b[1;38;2;230;175;46mComponent\u001b[0m\u001b[38;2;230;175;46m(\u001b[0m\u001b[3;38;2;230;175;46mSimpleImputer\u001b[0m\u001b[38;2;230;175;46m) \u001b[0m\u001b[38;2;230;175;46m────────────\u001b[0m\u001b[38;2;230;175;46m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39mitem\u001b[0m\u001b[39m \u001b[0m\u001b[1;35mSimpleImputer\u001b[0m\u001b[1;39m(\u001b[0m\u001b[33mfill_value\u001b[0m\u001b[39m=\u001b[0m\u001b[32m'missing'\u001b[0m\u001b[39m, \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mitem \u001b[0m\u001b[39m \u001b[0m\u001b[3;96mclass \u001b[0m\u001b]8;id=178888;https://www.scikit-learn.org/stable/modules/generated/sklearn.impute.SimpleImputer.html\u001b\\\u001b[4;39mSimpleImputer\u001b[0m\u001b]8;;\u001b\\\u001b[1;39m(\u001b[0m\u001b[33m...\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[33mstrategy\u001b[0m\u001b[39m=\u001b[0m\u001b[32m'constant'\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mspace\u001b[0m\u001b[39m \u001b[0m\u001b[1;39m{\u001b[0m\u001b[32m'strategy'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;39m[\u001b[0m\u001b[32m'mean'\u001b[0m\u001b[39m, \u001b[0m\u001b[32m'median'\u001b[0m\u001b[1;39m]\u001b[0m\u001b[1;39m}\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╰──────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m╰────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[1m ↓ \u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m╰────────────────────────────────────────────────────╯\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╭─\u001b[0m\u001b[38;2;86;53;30m \u001b[0m\u001b[1;38;2;86;53;30mFixed\u001b[0m\u001b[38;2;86;53;30m(\u001b[0m\u001b[3;38;2;86;53;30mOneHotEncoder\u001b[0m\u001b[38;2;86;53;30m) \u001b[0m\u001b[38;2;86;53;30m──────────────────────\u001b[0m\u001b[38;2;86;53;30m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39mitem\u001b[0m\u001b[39m \u001b[0m\u001b[1;35mOneHotEncoder\u001b[0m\u001b[1;39m(\u001b[0m\u001b[33mdrop\u001b[0m\u001b[39m=\u001b[0m\u001b[32m'first'\u001b[0m\u001b[39m, \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39m \u001b[0m\u001b[33msparse_output\u001b[0m\u001b[39m=\u001b[0m\u001b[3;91mFalse\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╰──────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m╰──────────────────────────────────────────────────╯\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[1m ↓ \u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m╭─\u001b[0m\u001b[38;2;255;69;0m \u001b[0m\u001b[1;38;2;255;69;0mChoice\u001b[0m\u001b[38;2;255;69;0m(\u001b[0m\u001b[3;38;2;255;69;0mselectors\u001b[0m\u001b[38;2;255;69;0m) \u001b[0m\u001b[38;2;255;69;0m────────────────────────────────────────────────────\u001b[0m\u001b[38;2;255;69;0m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m╭─\u001b[0m\u001b[38;2;230;175;46m \u001b[0m\u001b[1;38;2;230;175;46mComponent\u001b[0m\u001b[38;2;230;175;46m(\u001b[0m\u001b[3;38;2;230;175;46mSelectKBest\u001b[0m\u001b[38;2;230;175;46m) \u001b[0m\u001b[38;2;230;175;46m────\u001b[0m\u001b[38;2;230;175;46m─╮\u001b[0m \u001b[38;2;230;175;46m╭─\u001b[0m\u001b[38;2;230;175;46m \u001b[0m\u001b[1;38;2;230;175;46mComponent\u001b[0m\u001b[38;2;230;175;46m(\u001b[0m\u001b[3;38;2;230;175;46mVarianceThreshold\u001b[0m\u001b[38;2;230;175;46m) \u001b[0m\u001b[38;2;230;175;46m────\u001b[0m\u001b[38;2;230;175;46m─╮\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mitem \u001b[0m\u001b[39m \u001b[0m\u001b[3;96mclass \u001b[0m\u001b]8;id=870666;https://www.scikit-learn.org/stable/modules/generated/sklearn.feature_selection.SelectKBest.html\u001b\\\u001b[4;39mSelectKBest\u001b[0m\u001b]8;;\u001b\\\u001b[1;39m(\u001b[0m\u001b[33m...\u001b[0m\u001b[1;39m)\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mitem \u001b[0m\u001b[39m \u001b[0m\u001b[3;96mclass \u001b[0m\u001b]8;id=23174;https://www.scikit-learn.org/stable/modules/generated/sklearn.feature_selection.VarianceThreshold.html\u001b\\\u001b[4;39mVarianceThreshold\u001b[0m\u001b]8;;\u001b\\\u001b[1;39m(\u001b[0m\u001b[33m...\u001b[0m\u001b[1;39m)\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mspace\u001b[0m\u001b[39m \u001b[0m\u001b[1;39m{\u001b[0m\u001b[32m'k'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;39m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[39m, \u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;39m)\u001b[0m\u001b[1;39m}\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mspace\u001b[0m\u001b[39m \u001b[0m\u001b[1;39m{\u001b[0m\u001b[32m'threshold'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;39m(\u001b[0m\u001b[1;36m0.1\u001b[0m\u001b[39m, \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;39m)\u001b[0m\u001b[1;39m}\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m╰──────────────────────────────╯\u001b[0m \u001b[38;2;230;175;46m╰────────────────────────────────────╯\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m╰─────────────────────────────────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[1m ↓ \u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m╭─\u001b[0m\u001b[38;2;119;125;167m \u001b[0m\u001b[1;38;2;119;125;167mSplit\u001b[0m\u001b[38;2;119;125;167m(\u001b[0m\u001b[3;38;2;119;125;167mtransformers\u001b[0m\u001b[38;2;119;125;167m) \u001b[0m\u001b[38;2;119;125;167m────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[38;2;119;125;167m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m╭─\u001b[0m\u001b[38;2;126;107;143m \u001b[0m\u001b[1;38;2;126;107;143mSequential\u001b[0m\u001b[38;2;126;107;143m(\u001b[0m\u001b[3;38;2;126;107;143mpassthrough\u001b[0m\u001b[38;2;126;107;143m) \u001b[0m\u001b[38;2;126;107;143m─╮\u001b[0m \u001b[38;2;126;107;143m╭─\u001b[0m\u001b[38;2;126;107;143m \u001b[0m\u001b[1;38;2;126;107;143mSequential\u001b[0m\u001b[38;2;126;107;143m(\u001b[0m\u001b[3;38;2;126;107;143mpolynomial\u001b[0m\u001b[38;2;126;107;143m) \u001b[0m\u001b[38;2;126;107;143m───────────────\u001b[0m\u001b[38;2;126;107;143m─╮\u001b[0m \u001b[38;2;126;107;143m╭─\u001b[0m\u001b[38;2;126;107;143m \u001b[0m\u001b[1;38;2;126;107;143mSequential\u001b[0m\u001b[38;2;126;107;143m(\u001b[0m\u001b[3;38;2;126;107;143mzerocount\u001b[0m\u001b[38;2;126;107;143m) \u001b[0m\u001b[38;2;126;107;143m─╮\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╭─\u001b[0m\u001b[38;2;86;53;30m \u001b[0m\u001b[1;38;2;86;53;30mFixed\u001b[0m\u001b[38;2;86;53;30m(\u001b[0m\u001b[3;38;2;86;53;30mPassthrough\u001b[0m\u001b[38;2;86;53;30m) \u001b[0m\u001b[38;2;86;53;30m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m╭─\u001b[0m\u001b[38;2;230;175;46m \u001b[0m\u001b[1;38;2;230;175;46mComponent\u001b[0m\u001b[38;2;230;175;46m(\u001b[0m\u001b[3;38;2;230;175;46mPolynomialFeatures\u001b[0m\u001b[38;2;230;175;46m) \u001b[0m\u001b[38;2;230;175;46m────\u001b[0m\u001b[38;2;230;175;46m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╭─\u001b[0m\u001b[38;2;86;53;30m \u001b[0m\u001b[1;38;2;86;53;30mFixed\u001b[0m\u001b[38;2;86;53;30m(\u001b[0m\u001b[3;38;2;86;53;30mZeroCount\u001b[0m\u001b[38;2;86;53;30m) \u001b[0m\u001b[38;2;86;53;30m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39mitem\u001b[0m\u001b[39m \u001b[0m\u001b[1;35mPassthrough\u001b[0m\u001b[1;39m(\u001b[0m\u001b[1;39m)\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mitem \u001b[0m\u001b[39m \u001b[0m\u001b[3;96mclass \u001b[0m\u001b]8;id=605509;https://www.scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html\u001b\\\u001b[4;39mPolynomialFeatures\u001b[0m\u001b]8;;\u001b\\\u001b[1;39m(\u001b[0m\u001b[33m...\u001b[0m\u001b[1;39m)\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[39mitem\u001b[0m\u001b[39m \u001b[0m\u001b[1;35mZeroCount\u001b[0m\u001b[1;39m(\u001b[0m\u001b[1;39m)\u001b[0m \u001b[38;2;86;53;30m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╰──────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mspace\u001b[0m\u001b[39m \u001b[0m\u001b[1;39m{\u001b[0m\u001b[32m'degree'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;39m[\u001b[0m\u001b[1;36m2\u001b[0m\u001b[39m, \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;39m]\u001b[0m\u001b[1;39m}\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;86;53;30m╰────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m╰───────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;230;175;46m╰─────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;126;107;143m╰─────────────────────────╯\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m╰─────────────────────────────────────────╯\u001b[0m \u001b[38;2;119;125;167m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;119;125;167m╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[1m ↓ \u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m╭─\u001b[0m\u001b[38;2;255;69;0m \u001b[0m\u001b[1;38;2;255;69;0mChoice\u001b[0m\u001b[38;2;255;69;0m(\u001b[0m\u001b[3;38;2;255;69;0mestimator\u001b[0m\u001b[38;2;255;69;0m) \u001b[0m\u001b[38;2;255;69;0m────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[38;2;255;69;0m─╮\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m╭─\u001b[0m\u001b[38;2;230;175;46m \u001b[0m\u001b[1;38;2;230;175;46mComponent\u001b[0m\u001b[38;2;230;175;46m(\u001b[0m\u001b[3;38;2;230;175;46mRandomForestClassifier\u001b[0m\u001b[38;2;230;175;46m) \u001b[0m\u001b[38;2;230;175;46m─────────\u001b[0m\u001b[38;2;230;175;46m─╮\u001b[0m \u001b[38;2;230;175;46m╭─\u001b[0m\u001b[38;2;230;175;46m \u001b[0m\u001b[1;38;2;230;175;46mComponent\u001b[0m\u001b[38;2;230;175;46m(\u001b[0m\u001b[3;38;2;230;175;46mSVC\u001b[0m\u001b[38;2;230;175;46m) \u001b[0m\u001b[38;2;230;175;46m───────────────────────────\u001b[0m\u001b[38;2;230;175;46m─╮\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mitem \u001b[0m\u001b[39m \u001b[0m\u001b[3;96mclass \u001b[0m\u001b]8;id=470078;https://www.scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html\u001b\\\u001b[4;39mRandomForestClassifier\u001b[0m\u001b]8;;\u001b\\\u001b[1;39m(\u001b[0m\u001b[33m...\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mitem \u001b[0m\u001b[39m \u001b[0m\u001b[3;96mclass \u001b[0m\u001b]8;id=315827;https://www.scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html\u001b\\\u001b[4;39mSVC\u001b[0m\u001b]8;;\u001b\\\u001b[1;39m(\u001b[0m\u001b[33m...\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mconfig\u001b[0m\u001b[39m \u001b[0m\u001b[1;39m{\u001b[0m\u001b[32m'max_depth'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;39m}\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mspace\u001b[0m\u001b[39m \u001b[0m\u001b[1;39m{\u001b[0m\u001b[32m'kernel'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;39m[\u001b[0m\u001b[32m'linear'\u001b[0m\u001b[39m, \u001b[0m\u001b[32m'rbf'\u001b[0m\u001b[39m, \u001b[0m\u001b[32m'poly'\u001b[0m\u001b[1;39m]\u001b[0m\u001b[1;39m}\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39mspace \u001b[0m\u001b[39m \u001b[0m\u001b[1;39m{\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;230;175;46m╰─────────────────────────────────────────────╯\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39m \u001b[0m\u001b[39m \u001b[0m\u001b[32m'n_estimators'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;39m(\u001b[0m\u001b[1;36m10\u001b[0m\u001b[39m, \u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;39m)\u001b[0m\u001b[39m, \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39m \u001b[0m\u001b[39m \u001b[0m\u001b[32m'criterion'\u001b[0m\u001b[39m: \u001b[0m\u001b[1;39m[\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39m \u001b[0m\u001b[39m \u001b[0m\u001b[32m'gini'\u001b[0m\u001b[39m, \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39m \u001b[0m\u001b[39m \u001b[0m\u001b[32m'log_loss'\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39m \u001b[0m\u001b[39m \u001b[0m\u001b[1;39m]\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[39m \u001b[0m\u001b[1;39m}\u001b[0m\u001b[39m \u001b[0m \u001b[38;2;230;175;46m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;230;175;46m╰──────────────────────────────────────────────╯\u001b[0m \u001b[38;2;255;69;0m│\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m│\u001b[0m \u001b[38;2;255;69;0m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m \u001b[38;2;126;107;143m│\u001b[0m\n", "\u001b[38;2;126;107;143m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "Sequential(name='my_pipeline', item=None, nodes=(Split(name='split_imputation', item=None, nodes=(Sequential(name='categories', item=None, nodes=(Fixed(name='ColumnTransformer', item=ColumnTransformer(transformers=[('passthrough', 'passthrough',\n", " )]), nodes=(), config=None, space=None, fidelities=None, config_transform=None, meta=None), Fixed(name='SimpleImputer', item=SimpleImputer(fill_value='missing', strategy='constant'), nodes=(), config=None, space=None, fidelities=None, config_transform=None, meta=None), Fixed(name='OneHotEncoder', item=OneHotEncoder(drop='first', sparse_output=False), nodes=(), config=None, space=None, fidelities=None, config_transform=None, meta=None)), config=None, space=None, fidelities=None, config_transform=None, meta=None), Sequential(name='numerics', item=None, nodes=(Fixed(name='ColumnTransformer', item=ColumnTransformer(transformers=[('passthrough', 'passthrough',\n", " )]), nodes=(), config=None, space=None, fidelities=None, config_transform=None, meta=None), Component(name='SimpleImputer', item=, nodes=(), config=None, space={'strategy': ['mean', 'median']}, fidelities=None, config_transform=None, meta=None)), config=None, space=None, fidelities=None, config_transform=None, meta=None)), config=None, space=None, fidelities=None, config_transform=None, meta=None), Choice(name='selectors', item=None, nodes=(Component(name='SelectKBest', item=, nodes=(), config=None, space={'k': (1, 10)}, fidelities=None, config_transform=None, meta=None), Component(name='VarianceThreshold', item=, nodes=(), config=None, space={'threshold': (0.1, 1)}, fidelities=None, config_transform=None, meta=None)), config=None, space=None, fidelities=None, config_transform=None, meta=None), Split(name='transformers', item=None, nodes=(Sequential(name='passthrough', item=None, nodes=(Fixed(name='Passthrough', item=Passthrough(), nodes=(), config=None, space=None, fidelities=None, config_transform=None, meta=None),), config=None, space=None, fidelities=None, config_transform=None, meta=None), Sequential(name='polynomial', item=None, nodes=(Component(name='PolynomialFeatures', item=, nodes=(), config=None, space={'degree': [2, 3]}, fidelities=None, config_transform=None, meta=None),), config=None, space=None, fidelities=None, config_transform=None, meta=None), Sequential(name='zerocount', item=None, nodes=(Fixed(name='ZeroCount', item=ZeroCount(), nodes=(), config=None, space=None, fidelities=None, config_transform=None, meta=None),), config=None, space=None, fidelities=None, config_transform=None, meta=None)), config=None, space=None, fidelities=None, config_transform=None, meta=None), Choice(name='estimator', item=None, nodes=(Component(name='RandomForestClassifier', item=, nodes=(), config={'max_depth': 3}, space={'n_estimators': (10, 100), 'criterion': ['gini', 'log_loss']}, fidelities=None, config_transform=None, meta=None), Component(name='SVC', item=, nodes=(), config=None, space={'kernel': ['linear', 'rbf', 'poly']}, fidelities=None, config_transform=None, meta=None)), config=None, space=None, fidelities=None, config_transform=None, meta=None)), config=None, space=None, fidelities=None, config_transform=None, meta=None)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from tpot.builtin_modules import Passthrough, ZeroCount\n", "from sklearn.preprocessing import PolynomialFeatures\n", "from sklearn.decomposition import PCA\n", "\n", "from sklearn.feature_selection import VarianceThreshold, SelectKBest\n", "\n", "selectors = Choice(\n", " Component(VarianceThreshold, space={\"threshold\": (0.1,1)}),\n", " Component(SelectKBest, space={\"k\": (1, 10)}),\n", " name=\"selectors\",\n", ")\n", "\n", "\n", "transformers = Split(\n", " {\n", " \"passthrough\": Passthrough(),\n", " \"polynomial\": Component(PolynomialFeatures, space={\"degree\": [2, 3]}),\n", " \"zerocount\" : ZeroCount(),\n", " },\n", " # config={\"categories\": select_categories, \"numerics\": select_numerical},\n", " name=\"transformers\",\n", ")\n", "\n", "pipeline = (\n", " Sequential(name=\"my_pipeline\")\n", " >> split_imputation\n", " # >> Component(SimpleImputer, space={\"strategy\": [\"mean\", \"median\"]}) # Choose either mean or median\n", " \n", " >> selectors\n", " >> transformers\n", " >> Choice(\n", " # Our pipeline can choose between two different estimators\n", " Component(\n", " RandomForestClassifier,\n", " space={\"n_estimators\": (10, 100), \"criterion\": [\"gini\", \"log_loss\"]},\n", " config={\"max_depth\": 3},\n", " ),\n", " Component(SVC, space={\"kernel\": [\"linear\", \"rbf\", \"poly\"]}),\n", " name=\"estimator\",\n", " )\n", ")\n", "\n", "# Display the amltk Pipeline\n", "pipeline" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('pipeline-1',\n",
       "                                                 Pipeline(steps=[('columntransformer',\n",
       "                                                                  ColumnTransformer(transformers=[('passthrough',\n",
       "                                                                                                   'passthrough',\n",
       "                                                                                                   <sklearn.compose._column_transformer.make_column_selector object at 0x7d354d946290>)])),\n",
       "                                                                 ('simpleimputer',\n",
       "                                                                  SimpleImputer(fill_value='missing',\n",
       "                                                                                strategy='constant')),\n",
       "                                                                 ('onehotencode...\n",
       "                 VarianceThreshold(threshold=0.6738938110936)),\n",
       "                ('featureunion-2',\n",
       "                 FeatureUnion(transformer_list=[('pipeline-1',\n",
       "                                                 Pipeline(steps=[('passthrough',\n",
       "                                                                  Passthrough())])),\n",
       "                                                ('pipeline-2',\n",
       "                                                 Pipeline(steps=[('polynomialfeatures',\n",
       "                                                                  PolynomialFeatures(degree=3))])),\n",
       "                                                ('pipeline-3',\n",
       "                                                 Pipeline(steps=[('zerocount',\n",
       "                                                                  ZeroCount())]))])),\n",
       "                ('randomforestclassifier',\n",
       "                 RandomForestClassifier(n_estimators=16))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featureunion-1',\n", " FeatureUnion(transformer_list=[('pipeline-1',\n", " Pipeline(steps=[('columntransformer',\n", " ColumnTransformer(transformers=[('passthrough',\n", " 'passthrough',\n", " )])),\n", " ('simpleimputer',\n", " SimpleImputer(fill_value='missing',\n", " strategy='constant')),\n", " ('onehotencode...\n", " VarianceThreshold(threshold=0.6738938110936)),\n", " ('featureunion-2',\n", " FeatureUnion(transformer_list=[('pipeline-1',\n", " Pipeline(steps=[('passthrough',\n", " Passthrough())])),\n", " ('pipeline-2',\n", " Pipeline(steps=[('polynomialfeatures',\n", " PolynomialFeatures(degree=3))])),\n", " ('pipeline-3',\n", " Pipeline(steps=[('zerocount',\n", " ZeroCount())]))])),\n", " ('randomforestclassifier',\n", " RandomForestClassifier(n_estimators=16))])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#convert to tpot search space\n", "tpot_search_space = tpot.utils.tpot_parser(pipeline)\n", "\n", "# sample a pipeline from the tpot search space\n", "tpot_search_space.generate().export_pipeline()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Generation: 50%|█████ | 1/2 [00:02<00:02, 2.60s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 1\n", "Best roc_auc_score score: 0.976\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Generation: 100%|██████████| 2/2 [00:03<00:00, 1.57s/it]\n", "2024-09-09 17:25:40,301 - distributed.scheduler - ERROR - Removing worker 'tcp://127.0.0.1:39897' caused the cluster to lose scattered data, which can't be recovered: {'ndarray-3f2f44921e6e9cc40ef07cfcd8ae90fb', 'DataFrame-5551f84174fd651642ff10eb71e30b22'} (stimulus_id='handle-worker-cleanup-1725927940.3010821')\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generation: 2\n", "Best roc_auc_score score: 0.984\n" ] }, { "data": { "text/html": [ "
TPOTEstimator(classification=True, generations=2, max_eval_time_mins=300,\n",
       "              n_jobs=10, population_size=10, scorers=['roc_auc'],\n",
       "              scorers_weights=[1],\n",
       "              search_space=<tpot.search_spaces.pipelines.sequential.SequentialPipeline object at 0x7d34ec1efbb0>,\n",
       "              verbose=5)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "TPOTEstimator(classification=True, generations=2, max_eval_time_mins=300,\n", " n_jobs=10, population_size=10, scorers=['roc_auc'],\n", " scorers_weights=[1],\n", " search_space=,\n", " verbose=5)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\n", "\n", "\n", "est = tpot.TPOTEstimator(\n", " scorers = [\"roc_auc\"],\n", " scorers_weights = [1],\n", " classification = True,\n", " cv = 5,\n", " search_space = tpot_search_space, #converted search space goes here\n", " population_size= 10,\n", " generations = 2,\n", " max_eval_time_mins = 60*5,\n", " verbose = 5,\n", " n_jobs=10,\n", ")\n", "\n", "est.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('featureunion-1',\n",
       "                 FeatureUnion(transformer_list=[('pipeline-1',\n",
       "                                                 Pipeline(steps=[('columntransformer',\n",
       "                                                                  ColumnTransformer(transformers=[('passthrough',\n",
       "                                                                                                   'passthrough',\n",
       "                                                                                                   <sklearn.compose._column_transformer.make_column_selector object at 0x7d34eb307cd0>)])),\n",
       "                                                                 ('simpleimputer',\n",
       "                                                                  SimpleImputer(fill_value='missing',\n",
       "                                                                                strategy='constant')),\n",
       "                                                                 ('onehotencode...\n",
       "                 VarianceThreshold(threshold=0.1557560591318)),\n",
       "                ('featureunion-2',\n",
       "                 FeatureUnion(transformer_list=[('pipeline-1',\n",
       "                                                 Pipeline(steps=[('passthrough',\n",
       "                                                                  Passthrough())])),\n",
       "                                                ('pipeline-2',\n",
       "                                                 Pipeline(steps=[('polynomialfeatures',\n",
       "                                                                  PolynomialFeatures())])),\n",
       "                                                ('pipeline-3',\n",
       "                                                 Pipeline(steps=[('zerocount',\n",
       "                                                                  ZeroCount())]))])),\n",
       "                ('randomforestclassifier',\n",
       "                 RandomForestClassifier(criterion='log_loss',\n",
       "                                        n_estimators=80))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('featureunion-1',\n", " FeatureUnion(transformer_list=[('pipeline-1',\n", " Pipeline(steps=[('columntransformer',\n", " ColumnTransformer(transformers=[('passthrough',\n", " 'passthrough',\n", " )])),\n", " ('simpleimputer',\n", " SimpleImputer(fill_value='missing',\n", " strategy='constant')),\n", " ('onehotencode...\n", " VarianceThreshold(threshold=0.1557560591318)),\n", " ('featureunion-2',\n", " FeatureUnion(transformer_list=[('pipeline-1',\n", " Pipeline(steps=[('passthrough',\n", " Passthrough())])),\n", " ('pipeline-2',\n", " Pipeline(steps=[('polynomialfeatures',\n", " PolynomialFeatures())])),\n", " ('pipeline-3',\n", " Pipeline(steps=[('zerocount',\n", " ZeroCount())]))])),\n", " ('randomforestclassifier',\n", " RandomForestClassifier(criterion='log_loss',\n", " n_estimators=80))])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est.fitted_pipeline_" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1,\n", " 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0,\n", " 1, 0, 0, 0, 0, 0])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "est.predict(X_test)" ] } ], "metadata": { "kernelspec": { "display_name": "tpotenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: Tutorial/simple_fss.csv ================================================ one,a,b,c two,d,e,f three,g,h,i ================================================ FILE: docs/archived/api.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

# TPOT API ## Classification
class tpot.TPOTClassifier(generations=100, population_size=100,
                          offspring_size=None, mutation_rate=0.9,
                          crossover_rate=0.1,
                          scoring='accuracy', cv=5,
                          subsample=1.0, n_jobs=1,
                          max_time_mins=None, max_eval_time_mins=5,
                          random_state=None, config_dict=None,
                          template=None,
                          warm_start=False,
                          memory=None,
                          use_dask=False,
                          periodic_checkpoint_folder=None,
                          early_stop=None,
                          verbosity=0,
                          disable_update_check=False,
                          log_file=None
                          )
Automated machine learning for supervised classification tasks. The TPOTClassifier performs an intelligent search over machine learning pipelines that can contain supervised classification models, preprocessors, feature selection techniques, and any other estimator or transformer that follows the [scikit-learn API](http://scikit-learn.org/stable/developers/contributing.html#apis-of-scikit-learn-objects). The TPOTClassifier will also search over the hyperparameters of all objects in the pipeline. By default, TPOTClassifier will search over a broad range of supervised classification algorithms, transformers, and their parameters. However, the algorithms, transformers, and hyperparameters that the TPOTClassifier searches over can be fully customized using the `config_dict` parameter. Read more in the [User Guide](using/#tpot-with-code).
Parameters: generations: int or None optional (default=100)
Number of iterations to the run pipeline optimization process. It must be a positive number or None. If None, the parameter max_time_mins must be defined as the runtime limit.

Generally, TPOT will work better when you give it more generations (and therefore time) to optimize the pipeline.

TPOT will evaluate population_size + generations × offspring_size pipelines in total.
population_size: int, optional (default=100)
Number of individuals to retain in the genetic programming population every generation. Must be a positive number.

Generally, TPOT will work better when you give it more individuals with which to optimize the pipeline.
offspring_size: int, optional (default=None)
Number of offspring to produce in each genetic programming generation. Must be a positive number. By default, the number of offspring is equal to the number of population size.
mutation_rate: float, optional (default=0.9)
Mutation rate for the genetic programming algorithm in the range [0.0, 1.0]. This parameter tells the GP algorithm how many pipelines to apply random changes to every generation.

mutation_rate + crossover_rate cannot exceed 1.0.

We recommend using the default parameter unless you understand how the mutation rate affects GP algorithms.
crossover_rate: float, optional (default=0.1)
Crossover rate for the genetic programming algorithm in the range [0.0, 1.0]. This parameter tells the genetic programming algorithm how many pipelines to "breed" every generation.

mutation_rate + crossover_rate cannot exceed 1.0.

We recommend using the default parameter unless you understand how the crossover rate affects GP algorithms.
scoring: string or callable, optional (default='accuracy')
Function used to evaluate the quality of a given pipeline for the classification problem. The following built-in scoring functions can be used:

'accuracy', 'adjusted_rand_score', 'average_precision', 'balanced_accuracy', 'f1', 'f1_macro', 'f1_micro', 'f1_samples', 'f1_weighted', 'neg_log_loss', 'precision' etc. (suffixes apply as with ‘f1’), 'recall' etc. (suffixes apply as with ‘f1’), ‘jaccard’ etc. (suffixes apply as with ‘f1’), 'roc_auc', ‘roc_auc_ovr’, ‘roc_auc_ovo’, ‘roc_auc_ovr_weighted’, ‘roc_auc_ovo_weighted’

If you would like to use a custom scorer, you can pass the callable object/function with signature scorer(estimator, X, y).

See the section on scoring functions for more details.
cv: int, cross-validation generator, or an iterable, optional (default=5)
Cross-validation strategy used when evaluating pipelines.

Possible inputs:
  • integer, to specify the number of folds in an unshuffled StratifiedKFold,
  • An object to be used as a cross-validation generator, or
  • An iterable yielding train/test splits.
subsample: float, optional (default=1.0)
Fraction of training samples that are used during the TPOT optimization process. Must be in the range (0.0, 1.0].

Setting subsample=0.5 tells TPOT to use a random subsample of half of the training data. This subsample will remain the same during the entire pipeline optimization process.
n_jobs: integer, optional (default=1)
Number of processes to use in parallel for evaluating pipelines during the TPOT optimization process.

Setting n_jobs=-1 will use as many cores as available on the computer. For n_jobs below -1, (n_cpus + 1 + n_jobs) are used. Thus for n_jobs = -2, all CPUs but one are used. Beware that using multiple processes on the same machine may cause memory issues for large datasets.
max_time_mins: integer or None, optional (default=None)
How many minutes TPOT has to optimize the pipeline.

If not None, this setting will allow TPOT to run until max_time_mins minutes elapsed and then stop. TPOT will stop earlier if generations is set and all generations are already evaluated.
max_eval_time_mins: float, optional (default=5)
How many minutes TPOT has to evaluate a single pipeline.

Setting this parameter to higher values will allow TPOT to evaluate more complex pipelines, but will also allow TPOT to run longer. Use this parameter to help prevent TPOT from wasting time on evaluating time-consuming pipelines.
random_state: integer or None, optional (default=None)
The seed of the pseudo random number generator used in TPOT.

Use this parameter to make sure that TPOT will give you the same results each time you run it against the same data set with that seed.
config_dict: Python dictionary, string, or None, optional (default=None)
A configuration dictionary for customizing the operators and parameters that TPOT searches in the optimization process.

Possible inputs are:
  • Python dictionary, TPOT will use your custom configuration,
  • string 'TPOT light', TPOT will use a built-in configuration with only fast models and preprocessors, or
  • string 'TPOT MDR', TPOT will use a built-in configuration specialized for genomic studies, or
  • string 'TPOT sparse': TPOT will use a configuration dictionary with a one-hot encoder and the operators normally included in TPOT that also support sparse matrices, or
  • None, TPOT will use the default TPOTClassifier configuration.
See the built-in configurations section for the list of configurations included with TPOT, and the custom configuration section for more information and examples of how to create your own TPOT configurations.
template: string (default=None)
Template of predefined pipeline structure. The option is for specifying a desired structure for the machine learning pipeline evaluated in TPOT.

So far this option only supports linear pipeline structure. Each step in the pipeline should be a main class of operators (Selector, Transformer, Classifier) or a specific operator (e.g. `SelectPercentile`) defined in TPOT operator configuration. If one step is a main class, TPOT will randomly assign all subclass operators (subclasses of [`SelectorMixin`](https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/feature_selection/base.py#L17), [`TransformerMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html), [`ClassifierMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.ClassifierMixin.html) in scikit-learn) to that step. Steps in the template are delimited by "-", e.g. "SelectPercentile-Transformer-Classifier". By default value of template is None, TPOT generates tree-based pipeline randomly. See the template option in tpot section for more details.
warm_start: boolean, optional (default=False)
Flag indicating whether the TPOT instance will reuse the population from previous calls to fit().

Setting warm_start=True can be useful for running TPOT for a short time on a dataset, checking the results, then resuming the TPOT run from where it left off.
memory: a joblib.Memory object or string, optional (default=None)
If supplied, pipeline will cache each transformer after calling fit. This feature is used to avoid computing the fit transformers within a pipeline if the parameters and input data are identical with another fitted pipeline during optimization process. More details about memory caching in scikit-learn documentation

Possible inputs are:
  • String 'auto': TPOT uses memory caching with a temporary directory and cleans it up upon shutdown, or
  • Path of a caching directory, TPOT uses memory caching with the provided directory and TPOT does NOT clean the caching directory up upon shutdown, or
  • Memory object, TPOT uses the instance of joblib.Memory for memory caching and TPOT does NOT clean the caching directory up upon shutdown, or
  • None, TPOT does not use memory caching.
use_dask: boolean, optional (default: False)
Whether to use Dask-ML's pipeline optimiziations. This avoid re-fitting the same estimator on the same split of data multiple times. It will also provide more detailed diagnostics when using Dask's distributed scheduler.

See avoid repeated work for more details.
periodic_checkpoint_folder: path string, optional (default: None)
If supplied, a folder in which TPOT will periodically save pipelines in pareto front so far while optimizing.

Currently once per generation but not more often than once per 30 seconds.

Useful in multiple cases:
  • Sudden death before TPOT could save optimized pipeline
  • Track its progress
  • Grab pipelines while it's still optimizing
early_stop: integer, optional (default: None)
How many generations TPOT checks whether there is no improvement in optimization process.

Ends the optimization process if there is no improvement in the given number of generations.
verbosity: integer, optional (default=0)
How much information TPOT communicates while it's running.

Possible inputs are:
  • 0, TPOT will print nothing,
  • 1, TPOT will print minimal information,
  • 2, TPOT will print more information and provide a progress bar, or
  • 3, TPOT will print everything and provide a progress bar.
disable_update_check: boolean, optional (default=False)
Flag indicating whether the TPOT version checker should be disabled.

The update checker will tell you when a new version of TPOT has been released.
log_file: file-like class (io.TextIOWrapper or io.StringIO) or string, optional (default: None)

Save progress content to a file. If it is a string for the path and file name of the desired output file, TPOT will create the file and write log into it. If it is None, TPOT will output log into sys.stdout
Attributes: fitted_pipeline_: scikit-learn Pipeline object
The best pipeline that TPOT discovered during the pipeline optimization process, fitted on the entire training dataset.
pareto_front_fitted_pipelines_: Python dictionary
Dictionary containing the all pipelines on the TPOT Pareto front, where the key is the string representation of the pipeline and the value is the corresponding pipeline fitted on the entire training dataset.

The TPOT Pareto front provides a trade-off between pipeline complexity (i.e., the number of steps in the pipeline) and the predictive performance of the pipeline.

Note: pareto_front_fitted_pipelines_ is only available when verbosity=3.
evaluated_individuals_: Python dictionary
Dictionary containing all pipelines that were evaluated during the pipeline optimization process, where the key is the string representation of the pipeline and the value is a tuple containing (# of steps in pipeline, accuracy metric for the pipeline).

This attribute is primarily for internal use, but may be useful for looking at the other pipelines that TPOT evaluated.
Example ```Python from tpot import TPOTClassifier from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split digits = load_digits() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, train_size=0.75, test_size=0.25) tpot = TPOTClassifier(generations=5, population_size=50, verbosity=2) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_digits_pipeline.py') ``` Functions
fit(features, classes[, sample_weight, groups]) Run the TPOT optimization process on the given training data.
predict(features) Use the optimized pipeline to predict the classes for a feature set.
predict_proba(features) Use the optimized pipeline to estimate the class probabilities for a feature set.
score(testing_features, testing_classes) Returns the optimized pipeline's score on the given testing data using the user-specified scoring function.
export(output_file_name) Export the optimized pipeline as Python code.
```Python fit(features, classes, sample_weight=None, groups=None) ```
Run the TPOT optimization process on the given training data.

Uses genetic programming to optimize a machine learning pipeline that maximizes the score on the provided features and target. This pipeline optimization procedure uses internal k-fold cross-validaton to avoid overfitting on the provided data. At the end of the pipeline optimization procedure, the best pipeline is then trained on the entire set of provided samples.

Parameters: features: array-like {n_samples, n_features}
Feature matrix

TPOT and all scikit-learn algorithms assume that the features will be numerical and there will be no missing values. As such, when a feature matrix is provided to TPOT, all missing values will automatically be replaced (i.e., imputed) using median value imputation.

If you wish to use a different imputation strategy than median imputation, please make sure to apply imputation to your feature set prior to passing it to TPOT.
classes: array-like {n_samples}
List of class labels for prediction
sample_weight: array-like {n_samples}, optional
Per-sample weights. Higher weights indicate more importance. If specified, sample_weight will be passed to any pipeline element whose fit() function accepts a sample_weight argument. By default, using sample_weight does not affect tpot's scoring functions, which determine preferences between pipelines.
groups: array-like, with shape {n_samples, }, optional
Group labels for the samples used when performing cross-validation.

This parameter should only be used in conjunction with sklearn's Group cross-validation functions, such as sklearn.model_selection.GroupKFold.
Returns: self: object
Returns a copy of the fitted TPOT object
```Python predict(features) ```
Use the optimized pipeline to predict the classes for a feature set.

Parameters: features: array-like {n_samples, n_features}
Feature matrix
Returns: predictions: array-like {n_samples}
Predicted classes for the samples in the feature matrix
```Python predict_proba(features) ```
Use the optimized pipeline to estimate the class probabilities for a feature set.

Note: This function will only work for pipelines whose final classifier supports the predict_proba function. TPOT will raise an error otherwise.

Parameters: features: array-like {n_samples, n_features}
Feature matrix
Returns: predictions: array-like {n_samples, n_classes}
The class probabilities of the input samples
```Python score(testing_features, testing_classes) ```
Returns the optimized pipeline's score on the given testing data using the user-specified scoring function.

The default scoring function for TPOTClassifier is 'accuracy'.

Parameters: testing_features: array-like {n_samples, n_features}
Feature matrix of the testing set
testing_classes: array-like {n_samples}
List of class labels for prediction in the testing set
Returns: accuracy_score: float
The estimated test set accuracy according to the user-specified scoring function.
```Python export(output_file_name, data_file_path) ```
Export the optimized pipeline as Python code.

See the usage documentation for example usage of the export function.

Parameters: output_file_name: string
String containing the path and file name of the desired output file
data_file_path: string
By default, the path of input dataset is 'PATH/TO/DATA/FILE' by default. If data_file_path is another string, the path will be replaced.
Returns: exported_code_string: string
The whole pipeline text as a string should be returned if output_file_name is not specified.
## Regression
class tpot.TPOTRegressor(generations=100, population_size=100,
                         offspring_size=None, mutation_rate=0.9,
                         crossover_rate=0.1,
                         scoring='neg_mean_squared_error', cv=5,
                         subsample=1.0, n_jobs=1,
                         max_time_mins=None, max_eval_time_mins=5,
                         random_state=None, config_dict=None,
                         template=None,
                         warm_start=False,
                         memory=None,
                         use_dask=False,
                         periodic_checkpoint_folder=None,
                         early_stop=None,
                         verbosity=0,
                         disable_update_check=False)
Automated machine learning for supervised regression tasks. The TPOTRegressor performs an intelligent search over machine learning pipelines that can contain supervised regression models, preprocessors, feature selection techniques, and any other estimator or transformer that follows the [scikit-learn API](http://scikit-learn.org/stable/developers/contributing.html#apis-of-scikit-learn-objects). The TPOTRegressor will also search over the hyperparameters of all objects in the pipeline. By default, TPOTRegressor will search over a broad range of supervised regression models, transformers, and their hyperparameters. However, the models, transformers, and parameters that the TPOTRegressor searches over can be fully customized using the `config_dict` parameter. Read more in the [User Guide](using/#tpot-with-code).
Parameters: generations: int or None, optional (default=100)
Number of iterations to the run pipeline optimization process. It must be a positive number or None. If None, the parameter max_time_mins must be defined as the runtime limit.

Generally, TPOT will work better when you give it more generations (and therefore time) to optimize the pipeline.

TPOT will evaluate population_size + generations × offspring_size pipelines in total.
population_size: int, optional (default=100)
Number of individuals to retain in the genetic programming population every generation. Must be a positive number.

Generally, TPOT will work better when you give it more individuals with which to optimize the pipeline.
offspring_size: int, optional (default=None)
Number of offspring to produce in each genetic programming generation. Must be a positive number. By default, the number of offspring is equal to the number of population size.
mutation_rate: float, optional (default=0.9)
Mutation rate for the genetic programming algorithm in the range [0.0, 1.0]. This parameter tells the GP algorithm how many pipelines to apply random changes to every generation.

mutation_rate + crossover_rate cannot exceed 1.0.

We recommend using the default parameter unless you understand how the mutation rate affects GP algorithms.
crossover_rate: float, optional (default=0.1)
Crossover rate for the genetic programming algorithm in the range [0.0, 1.0]. This parameter tells the genetic programming algorithm how many pipelines to "breed" every generation.

mutation_rate + crossover_rate cannot exceed 1.0.

We recommend using the default parameter unless you understand how the crossover rate affects GP algorithms.
scoring: string or callable, optional (default='neg_mean_squared_error')
Function used to evaluate the quality of a given pipeline for the regression problem. The following built-in scoring functions can be used:

'neg_median_absolute_error', 'neg_mean_absolute_error', 'neg_mean_squared_error', 'r2'

Note that we recommend using the neg version of mean squared error and related metrics so TPOT will minimize (instead of maximize) the metric.

If you would like to use a custom scorer, you can pass the callable object/function with signature scorer(estimator, X, y).

See the section on scoring functions for more details.
cv: int, cross-validation generator, or an iterable, optional (default=5)
Cross-validation strategy used when evaluating pipelines.

Possible inputs:
  • integer, to specify the number of folds in an unshuffled KFold,
  • An object to be used as a cross-validation generator, or
  • An iterable yielding train/test splits.
subsample: float, optional (default=1.0)
Fraction of training samples that are used during the TPOT optimization process. Must be in the range (0.0, 1.0].

Setting subsample=0.5 tells TPOT to use a random subsample of half of the training data. This subsample will remain the same during the entire pipeline optimization process.
n_jobs: integer, optional (default=1)
Number of processes to use in parallel for evaluating pipelines during the TPOT optimization process.

Setting n_jobs=-1 will use as many cores as available on the computer. For n_jobs below -1, (n_cpus + 1 + n_jobs) are used. Thus for n_jobs = -2, all CPUs but one are used. Beware that using multiple processes on the same machine may cause memory issues for large datasets
max_time_mins: integer or None, optional (default=None)
How many minutes TPOT has to optimize the pipeline.

If not None, this setting will allow TPOT to run until max_time_mins minutes elapsed and then stop. TPOT will stop earlier if generations is set and all generations are already evaluated.
max_eval_time_mins: float, optional (default=5)
How many minutes TPOT has to evaluate a single pipeline.

Setting this parameter to higher values will allow TPOT to evaluate more complex pipelines, but will also allow TPOT to run longer. Use this parameter to help prevent TPOT from wasting time on evaluating time-consuming pipelines.
random_state: integer or None, optional (default=None)
The seed of the pseudo random number generator used in TPOT.

Use this parameter to make sure that TPOT will give you the same results each time you run it against the same data set with that seed.
config_dict: Python dictionary, string, or None, optional (default=None)
A configuration dictionary for customizing the operators and parameters that TPOT searches in the optimization process.

Possible inputs are:
  • Python dictionary, TPOT will use your custom configuration,
  • string 'TPOT light', TPOT will use a built-in configuration with only fast models and preprocessors, or
  • string 'TPOT MDR', TPOT will use a built-in configuration specialized for genomic studies, or
  • string 'TPOT sparse': TPOT will use a configuration dictionary with a one-hot encoder and the operators normally included in TPOT that also support sparse matrices, or
  • None, TPOT will use the default TPOTRegressor configuration.
See the built-in configurations section for the list of configurations included with TPOT, and the custom configuration section for more information and examples of how to create your own TPOT configurations.
template: string (default=None)
Template of predefined pipeline structure. The option is for specifying a desired structure for the machine learning pipeline evaluated in TPOT.

So far this option only supports linear pipeline structure. Each step in the pipeline should be a main class of operators (Selector, Transformer or Regressor) or a specific operator (e.g. `SelectPercentile`) defined in TPOT operator configuration. If one step is a main class, TPOT will randomly assign all subclass operators (subclasses of [`SelectorMixin`](https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/feature_selection/base.py#L17), [`TransformerMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html) or [`RegressorMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.RegressorMixin.html) in scikit-learn) to that step. Steps in the template are delimited by "-", e.g. "SelectPercentile-Transformer-Regressor". By default value of template is None, TPOT generates tree-based pipeline randomly. See the template option in tpot section for more details.
warm_start: boolean, optional (default=False)
Flag indicating whether the TPOT instance will reuse the population from previous calls to fit().

Setting warm_start=True can be useful for running TPOT for a short time on a dataset, checking the results, then resuming the TPOT run from where it left off.
memory: a joblib.Memory object or string, optional (default=None)
If supplied, pipeline will cache each transformer after calling fit. This feature is used to avoid computing the fit transformers within a pipeline if the parameters and input data are identical with another fitted pipeline during optimization process. More details about memory caching in scikit-learn documentation

Possible inputs are:
  • String 'auto': TPOT uses memory caching with a temporary directory and cleans it up upon shutdown, or
  • Path of a caching directory, TPOT uses memory caching with the provided directory and TPOT does NOT clean the caching directory up upon shutdown, or
  • Memory object, TPOT uses the instance of joblib.Memory for memory caching and TPOT does NOT clean the caching directory up upon shutdown, or
  • None, TPOT does not use memory caching.
use_dask: boolean, optional (default: False)
Whether to use Dask-ML's pipeline optimiziations. This avoid re-fitting the same estimator on the same split of data multiple times. It will also provide more detailed diagnostics when using Dask's distributed scheduler.

See avoid repeated work for more details.
periodic_checkpoint_folder: path string, optional (default: None)
If supplied, a folder in which TPOT will periodically save pipelines in pareto front so far while optimizing.

Currently once per generation but not more often than once per 30 seconds.

Useful in multiple cases:
  • Sudden death before TPOT could save optimized pipeline
  • Track its progress
  • Grab pipelines while it's still optimizing
early_stop: integer, optional (default: None)
How many generations TPOT checks whether there is no improvement in optimization process.

Ends the optimization process if there is no improvement in the given number of generations.
verbosity: integer, optional (default=0)
How much information TPOT communicates while it's running.

Possible inputs are:
  • 0, TPOT will print nothing,
  • 1, TPOT will print minimal information,
  • 2, TPOT will print more information and provide a progress bar, or
  • 3, TPOT will print everything and provide a progress bar.
disable_update_check: boolean, optional (default=False)
Flag indicating whether the TPOT version checker should be disabled.

The update checker will tell you when a new version of TPOT has been released.
Attributes: fitted_pipeline_: scikit-learn Pipeline object
The best pipeline that TPOT discovered during the pipeline optimization process, fitted on the entire training dataset.
pareto_front_fitted_pipelines_: Python dictionary
Dictionary containing the all pipelines on the TPOT Pareto front, where the key is the string representation of the pipeline and the value is the corresponding pipeline fitted on the entire training dataset.

The TPOT Pareto front provides a trade-off between pipeline complexity (i.e., the number of steps in the pipeline) and the predictive performance of the pipeline.

Note: _pareto_front_fitted_pipelines is only available when verbosity=3.
evaluated_individuals_: Python dictionary
Dictionary containing all pipelines that were evaluated during the pipeline optimization process, where the key is the string representation of the pipeline and the value is a tuple containing (# of steps in pipeline, accuracy metric for the pipeline).

This attribute is primarily for internal use, but may be useful for looking at the other pipelines that TPOT evaluated.
Example ```Python from tpot import TPOTRegressor from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split digits = load_boston() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, train_size=0.75, test_size=0.25) tpot = TPOTRegressor(generations=5, population_size=50, verbosity=2) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_boston_pipeline.py') ``` Functions
fit(features, target[, sample_weight, groups]) Run the TPOT optimization process on the given training data.
predict(features) Use the optimized pipeline to predict the target values for a feature set.
score(testing_features, testing_target) Returns the optimized pipeline's score on the given testing data using the user-specified scoring function.
export(output_file_name) Export the optimized pipeline as Python code.
```Python fit(features, target, sample_weight=None, groups=None) ```
Run the TPOT optimization process on the given training data.

Uses genetic programming to optimize a machine learning pipeline that maximizes the score on the provided features and target. This pipeline optimization procedure uses internal k-fold cross-validaton to avoid overfitting on the provided data. At the end of the pipeline optimization procedure, the best pipeline is then trained on the entire set of provided samples.

Parameters: features: array-like {n_samples, n_features}
Feature matrix

TPOT and all scikit-learn algorithms assume that the features will be numerical and there will be no missing values. As such, when a feature matrix is provided to TPOT, all missing values will automatically be replaced (i.e., imputed) using median value imputation.

If you wish to use a different imputation strategy than median imputation, please make sure to apply imputation to your feature set prior to passing it to TPOT.
target: array-like {n_samples}
List of target labels for prediction
sample_weight: array-like {n_samples}, optional
Per-sample weights. Higher weights indicate more importance. If specified, sample_weight will be passed to any pipeline element whose fit() function accepts a sample_weight argument. By default, using sample_weight does not affect tpot's scoring functions, which determine preferences between pipelines.
groups: array-like, with shape {n_samples, }, optional
Group labels for the samples used when performing cross-validation.

This parameter should only be used in conjunction with sklearn's Group cross-validation functions, such as sklearn.model_selection.GroupKFold.
Returns: self: object
Returns a copy of the fitted TPOT object
```Python predict(features) ```
Use the optimized pipeline to predict the target values for a feature set.

Parameters: features: array-like {n_samples, n_features}
Feature matrix
Returns: predictions: array-like {n_samples}
Predicted target values for the samples in the feature matrix
```Python score(testing_features, testing_target) ```
Returns the optimized pipeline's score on the given testing data using the user-specified scoring function.

The default scoring function for TPOTRegressor is 'mean_squared_error'.

Parameters: testing_features: array-like {n_samples, n_features}
Feature matrix of the testing set
testing_target: array-like {n_samples}
List of target labels for prediction in the testing set
Returns: accuracy_score: float
The estimated test set accuracy according to the user-specified scoring function.
```Python export(output_file_name) ```
Export the optimized pipeline as Python code.

See the usage documentation for example usage of the export function.

Parameters: output_file_name: string
String containing the path and file name of the desired output file
data_file_path: string
By default, the path of input dataset is 'PATH/TO/DATA/FILE' by default. If data_file_path is another string, the path will be replaced.
Returns: exported_code_string: string
The whole pipeline text as a string should be returned if output_file_name is not specified.
================================================ FILE: docs/archived/citing.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

# Citing TPOT If you use TPOT in a scientific publication, please consider citing at least one of the following papers: Trang T. Le, Weixuan Fu and Jason H. Moore (2020). [Scaling tree-based automated machine learning to biomedical big data with a feature set selector](https://academic.oup.com/bioinformatics/article/36/1/250/5511404). *Bioinformatics*.36(1): 250-256. BibTeX entry: ```bibtex @article{le2020scaling, title={Scaling tree-based automated machine learning to biomedical big data with a feature set selector}, author={Le, Trang T and Fu, Weixuan and Moore, Jason H}, journal={Bioinformatics}, volume={36}, number={1}, pages={250--256}, year={2020}, publisher={Oxford University Press} } ``` Randal S. Olson, Ryan J. Urbanowicz, Peter C. Andrews, Nicole A. Lavender, La Creis Kidd, and Jason H. Moore (2016). [Automating biomedical data science through tree-based pipeline optimization](http://link.springer.com/chapter/10.1007/978-3-319-31204-0_9). *Applications of Evolutionary Computation*, pages 123-137. BibTeX entry: ```bibtex @inbook{Olson2016EvoBio, author={Olson, Randal S. and Urbanowicz, Ryan J. and Andrews, Peter C. and Lavender, Nicole A. and Kidd, La Creis and Moore, Jason H.}, editor={Squillero, Giovanni and Burelli, Paolo}, chapter={Automating Biomedical Data Science Through Tree-Based Pipeline Optimization}, title={Applications of Evolutionary Computation: 19th European Conference, EvoApplications 2016, Porto, Portugal, March 30 -- April 1, 2016, Proceedings, Part I}, year={2016}, publisher={Springer International Publishing}, pages={123--137}, isbn={978-3-319-31204-0}, doi={10.1007/978-3-319-31204-0_9}, url={http://dx.doi.org/10.1007/978-3-319-31204-0_9} } ``` Evaluation of a Tree-based Pipeline Optimization Tool for Automating Data Science Randal S. Olson, Nathan Bartley, Ryan J. Urbanowicz, and Jason H. Moore (2016). [Evaluation of a Tree-based Pipeline Optimization Tool for Automating Data Science](http://dl.acm.org/citation.cfm?id=2908918). *Proceedings of GECCO 2016*, pages 485-492. BibTeX entry: ```bibtex @inproceedings{OlsonGECCO2016, author = {Olson, Randal S. and Bartley, Nathan and Urbanowicz, Ryan J. and Moore, Jason H.}, title = {Evaluation of a Tree-based Pipeline Optimization Tool for Automating Data Science}, booktitle = {Proceedings of the Genetic and Evolutionary Computation Conference 2016}, series = {GECCO '16}, year = {2016}, isbn = {978-1-4503-4206-3}, location = {Denver, Colorado, USA}, pages = {485--492}, numpages = {8}, url = {http://doi.acm.org/10.1145/2908812.2908918}, doi = {10.1145/2908812.2908918}, acmid = {2908918}, publisher = {ACM}, address = {New York, NY, USA}, } ``` Alternatively, you can cite the repository directly with the following DOI: [DOI](https://zenodo.org/badge/latestdoi/20747/rhiever/tpot) ================================================ FILE: docs/archived/contributing.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

# Contribution Guide We welcome you to [check the existing issues](https://github.com/EpistasisLab/tpot/issues/) for bugs or enhancements to work on. If you have an idea for an extension to TPOT, please [file a new issue](https://github.com/EpistasisLab/tpot/issues/new) so we can discuss it. ## Project layout The latest stable release of TPOT is on the [master branch](https://github.com/EpistasisLab/tpot/tree/master), whereas the latest version of TPOT in development is on the [development branch](https://github.com/EpistasisLab/tpot/tree/development). Make sure you are looking at and working on the correct branch if you're looking to contribute code. In terms of directory structure: * All of TPOT's code sources are in the `tpot` directory * The documentation sources are in the `docs_sources` directory * Images in the documentation are in the `images` directory * Tutorials for TPOT are in the `tutorials` directory * Unit tests for TPOT are in the `tests.py` file Make sure to familiarize yourself with the project layout before making any major contributions, and especially make sure to send all code changes to the `development` branch. ## How to contribute The preferred way to contribute to TPOT is to fork the [main repository](https://github.com/EpistasisLab/tpot/) on GitHub: 1. Fork the [project repository](https://github.com/EpistasisLab/tpot): click on the 'Fork' button near the top of the page. This creates a copy of the code under your account on the GitHub server. 2. Clone this copy to your local disk: $ git clone git@github.com:YourUsername/tpot.git $ cd tpot 3. Create a branch to hold your changes: $ git checkout -b my-contribution 4. Make sure your local environment is setup correctly for development. Installation instructions are almost identical to [the user instructions](installing.md) except that TPOT should *not* be installed. If you have TPOT installed on your computer then make sure you are using a virtual environment that does not have TPOT installed. Furthermore, you should make sure you have installed the `nose` package into your development environment so that you can test changes locally. $ conda install nose 5. Start making changes on your newly created branch, remembering to never work on the ``master`` branch! Work on this copy on your computer using Git to do the version control. 6. Once some changes are saved locally, you can use your tweaked version of TPOT by navigating to the project's base directory and running TPOT directly from the command line: $ python -m tpot.driver or by running script that imports and uses the TPOT module with code similar to `from tpot import TPOTClassifier` 7. To check your changes haven't broken any existing tests and to check new tests you've added pass run the following (note, you must have the `nose` package installed within your dev environment for this to work): $ nosetests -s -v 8. When you're done editing and local testing, run: $ git add modified_files $ git commit to record your changes in Git, then push them to GitHub with: $ git push -u origin my-contribution Finally, go to the web page of your fork of the TPOT repo, and click 'Pull Request' (PR) to send your changes to the maintainers for review. Make sure that you send your PR to the `development` branch, as the `master` branch is reserved for the latest stable release. This will start the CI server to check all the project's unit tests run and send an email to the maintainers. (If any of the above seems like magic to you, then look up the [Git documentation](http://git-scm.com/documentation) on the web.) ## Before submitting your pull request Before you submit a pull request for your contribution, please work through this checklist to make sure that you have done everything necessary so we can efficiently review and accept your changes. If your contribution changes TPOT in any way: * Update the [documentation](https://github.com/EpistasisLab/tpot/tree/master/docs_sources) so all of your changes are reflected there. * Update the [README](https://github.com/EpistasisLab/tpot/blob/master/README.md) if anything there has changed. If your contribution involves any code changes: * Update the [project unit tests](https://github.com/EpistasisLab/tpot/tree/master/tests) to test your code changes. * Make sure that your code is properly commented with [docstrings](https://www.python.org/dev/peps/pep-0257/) and comments explaining your rationale behind non-obvious coding practices. * If your code affected any of the pipeline operators, make sure that the corresponding [export functionality](https://github.com/EpistasisLab/tpot/blob/master/tpot/export_utils.py) reflects those changes. If your contribution requires a new library dependency: * Double-check that the new dependency is easy to install via `pip` or Anaconda and supports both Python 2 and 3. If the dependency requires a complicated installation, then we most likely won't merge your changes because we want to keep TPOT easy to install. * Add the required version of the library to [.travis.yml](https://github.com/EpistasisLab/tpot/blob/master/.travis.yml#L7) * Add a line to pip install the library to [.travis_install.sh](https://github.com/EpistasisLab/tpot/blob/master/ci/.travis_install.sh#L46) * Add a line to print the version of the library to [.travis_install.sh](https://github.com/EpistasisLab/tpot/blob/master/ci/.travis_install.sh#L63) * Similarly add a line to print the version of the library to [.travis_test.sh](https://github.com/EpistasisLab/tpot/blob/master/ci/.travis_test.sh#L13) ## After submitting your pull request After submitting your pull request, [Travis-CI](https://travis-ci.com/) will automatically run unit tests on your changes and make sure that your updated code builds and runs on Python 2 and 3. We also use services that automatically check code quality and test coverage. Check back shortly after submitting your pull request to make sure that your code passes these checks. If any of the checks come back with a red X, then do your best to address the errors. ================================================ FILE: docs/archived/css/archived.css ================================================ .md-grid { max-width: 100%; } ================================================ FILE: docs/archived/examples.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

# Overview The following sections illustrate the usage of TPOT with various datasets, each belonging to a typical class of machine learning tasks. | Dataset | Task | Task class | Dataset description | Jupyter notebook | | ------- | ----------------------- | ---------------------- |:-------------------:|:------------------------------------------------------------------------------------------:| | Iris | flower classification | classification | [link](https://archive.ics.uci.edu/ml/datasets/iris) | [link](https://github.com/EpistasisLab/tpot/blob/master/tutorials/IRIS.ipynb) | | Optical Recognition of Handwritten Digits | digit recognition | (image) classification | [link](https://scikit-learn.org/stable/datasets/index.html#digits-dataset) | [link](https://github.com/EpistasisLab/tpot/blob/master/tutorials/Digits.ipynb) | | Boston | housing prices modeling | regression | [link](https://www.cs.toronto.edu/~delve/data/boston/bostonDetail.html) | N/A | | Titanic | survival analysis | classification | [link](https://www.kaggle.com/c/titanic/data) | [link](https://github.com/EpistasisLab/tpot/blob/master/tutorials/Titanic_Kaggle.ipynb) | | Bank Marketing | subscription prediction | classification | [link](https://archive.ics.uci.edu/ml/datasets/Bank+Marketing) | [link](https://github.com/EpistasisLab/tpot/blob/master/tutorials/Portuguese%20Bank%20Marketing/Portuguese%20Bank%20Marketing%20Strategy.ipynb) | | MAGIC Gamma Telescope | event detection | classification | [link](https://archive.ics.uci.edu/ml/datasets/MAGIC+Gamma+Telescope) | [link](https://github.com/EpistasisLab/tpot/blob/master/tutorials/MAGIC%20Gamma%20Telescope/MAGIC%20Gamma%20Telescope.ipynb) | | cuML Classification Example | random classification problem | classification | [link](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html) | [link](https://github.com/EpistasisLab/tpot/blob/master/tutorials/cuML_Classification_Example.ipynb) | | cuML Regression Example | random regression problem | regression | [link](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_regression.html) | [link](https://github.com/EpistasisLab/tpot/blob/master/tutorials/cuML_Regression_Example.ipynb) | **Notes:** - For details on how the `fit()`, `score()` and `export()` methods work, refer to the [usage documentation](/using/). - Upon re-running the experiments, your resulting pipelines _may_ differ (to some extent) from the ones demonstrated here. ## Iris flower classification The following code illustrates how TPOT can be employed for performing a simple _classification task_ over the Iris dataset. ```Python from tpot import TPOTClassifier from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split import numpy as np iris = load_iris() X_train, X_test, y_train, y_test = train_test_split(iris.data.astype(np.float64), iris.target.astype(np.float64), train_size=0.75, test_size=0.25, random_state=42) tpot = TPOTClassifier(generations=5, population_size=50, verbosity=2, random_state=42) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_iris_pipeline.py') ``` Running this code should discover a pipeline (exported as `tpot_iris_pipeline.py`) that achieves about 97% test accuracy: ```Python import numpy as np import pandas as pd from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.pipeline import make_pipeline from sklearn.preprocessing import Normalizer from tpot.export_utils import set_param_recursive # NOTE: Make sure that the outcome column is labeled 'target' in the data file tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64) features = tpot_data.drop('target', axis=1) training_features, testing_features, training_target, testing_target = \ train_test_split(features, tpot_data['target'], random_state=42) # Average CV score on the training set was: 0.9826086956521738 exported_pipeline = make_pipeline( Normalizer(norm="l2"), KNeighborsClassifier(n_neighbors=5, p=2, weights="distance") ) # Fix random state for all the steps in exported pipeline set_param_recursive(exported_pipeline.steps, 'random_state', 42) exported_pipeline.fit(training_features, training_target) results = exported_pipeline.predict(testing_features) ``` ## Digits dataset Below is a minimal working example with the optical recognition of handwritten digits dataset, which is an _image classification problem_. ```Python from tpot import TPOTClassifier from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split digits = load_digits() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, train_size=0.75, test_size=0.25, random_state=42) tpot = TPOTClassifier(generations=5, population_size=50, verbosity=2, random_state=42) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_digits_pipeline.py') ``` Running this code should discover a pipeline (exported as `tpot_digits_pipeline.py`) that achieves about 98% test accuracy: ```Python import numpy as np import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline, make_union from sklearn.preprocessing import PolynomialFeatures from tpot.builtins import StackingEstimator from tpot.export_utils import set_param_recursive # NOTE: Make sure that the outcome column is labeled 'target' in the data file tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64) features = tpot_data.drop('target', axis=1) training_features, testing_features, training_target, testing_target = \ train_test_split(features, tpot_data['target'], random_state=42) # Average CV score on the training set was: 0.9799428471757372 exported_pipeline = make_pipeline( PolynomialFeatures(degree=2, include_bias=False, interaction_only=False), StackingEstimator(estimator=LogisticRegression(C=0.1, dual=False, penalty="l1")), RandomForestClassifier(bootstrap=True, criterion="entropy", max_features=0.35000000000000003, min_samples_leaf=20, min_samples_split=19, n_estimators=100) ) # Fix random state for all the steps in exported pipeline set_param_recursive(exported_pipeline.steps, 'random_state', 42) exported_pipeline.fit(training_features, training_target) results = exported_pipeline.predict(testing_features) ``` ## Boston housing prices modeling The following code illustrates how TPOT can be employed for performing a _regression task_ over the Boston housing prices dataset. ```Python from tpot import TPOTRegressor from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split housing = load_boston() X_train, X_test, y_train, y_test = train_test_split(housing.data, housing.target, train_size=0.75, test_size=0.25, random_state=42) tpot = TPOTRegressor(generations=5, population_size=50, verbosity=2, random_state=42) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_boston_pipeline.py') ``` Running this code should discover a pipeline (exported as `tpot_boston_pipeline.py`) that achieves at least 10 mean squared error (MSE) on the test set: ```Python import numpy as np import pandas as pd from sklearn.ensemble import ExtraTreesRegressor from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline from sklearn.preprocessing import PolynomialFeatures from tpot.export_utils import set_param_recursive # NOTE: Make sure that the outcome column is labeled 'target' in the data file tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64) features = tpot_data.drop('target', axis=1) training_features, testing_features, training_target, testing_target = \ train_test_split(features, tpot_data['target'], random_state=42) # Average CV score on the training set was: -10.812040755234403 exported_pipeline = make_pipeline( PolynomialFeatures(degree=2, include_bias=False, interaction_only=False), ExtraTreesRegressor(bootstrap=False, max_features=0.5, min_samples_leaf=2, min_samples_split=3, n_estimators=100) ) # Fix random state for all the steps in exported pipeline set_param_recursive(exported_pipeline.steps, 'random_state', 42) exported_pipeline.fit(training_features, training_target) results = exported_pipeline.predict(testing_features) ``` ## Titanic survival analysis To see the TPOT applied the Titanic Kaggle dataset, see the Jupyter notebook [here](https://github.com/EpistasisLab/tpot/blob/master/tutorials/Titanic_Kaggle.ipynb). This example shows how to take a messy dataset and preprocess it such that it can be used in scikit-learn and TPOT. ## Portuguese Bank Marketing The corresponding Jupyter notebook, containing the associated data preprocessing and analysis, can be found [here](https://github.com/EpistasisLab/tpot/blob/master/tutorials/Portuguese%20Bank%20Marketing/Portuguese%20Bank%20Marketing%20Stratergy.ipynb). ## MAGIC Gamma Telescope The corresponding Jupyter notebook, containing the associated data preprocessing and analysis, can be found [here](https://github.com/EpistasisLab/tpot/blob/master/tutorials/MAGIC%20Gamma%20Telescope/MAGIC%20Gamma%20Telescope.ipynb). ## Neural network classifier using TPOT-NN By loading the TPOT-NN configuration dictionary, PyTorch estimators will be included for classification. Users can also create their own NN configuration dictionary that includes `tpot.builtins.PytorchLRClassifier` and/or `tpot.builtins.PytorchMLPClassifier`, or they can specify them using a template string, as shown in the following example: ```Python from tpot import TPOTClassifier from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split X, y = make_blobs(n_samples=100, centers=2, n_features=3, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.75, test_size=0.25) clf = TPOTClassifier(config_dict='TPOT NN', template='Selector-Transformer-PytorchLRClassifier', verbosity=2, population_size=10, generations=10) clf.fit(X_train, y_train) print(clf.score(X_test, y_test)) clf.export('tpot_nn_demo_pipeline.py') ``` This example is somewhat trivial, but it should result in nearly 100% classification accuracy. ================================================ FILE: docs/archived/index.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

Consider TPOT your **Data Science Assistant**. TPOT is a Python Automated Machine Learning tool that optimizes machine learning pipelines using genetic programming.
TPOT Demo

TPOT will automate the most tedious part of machine learning by intelligently exploring thousands of possible pipelines to find the best one for your data.
An example machine learning pipeline An example machine learning pipeline

Once TPOT is finished searching (or you get tired of waiting), it provides you with the Python code for the best pipeline it found so you can tinker with the pipeline from there.
An example TPOT pipeline An example TPOT pipeline

TPOT is built on top of scikit-learn, so all of the code it generates should look familiar... if you're familiar with scikit-learn, anyway. **TPOT is still under active development** and we encourage you to check back on this repository regularly for updates. ================================================ FILE: docs/archived/installing.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

# Installation TPOT is built on top of several existing Python libraries, including: * [NumPy](http://www.numpy.org/) * [SciPy](https://www.scipy.org/) * [scikit-learn](http://www.scikit-learn.org/) * [DEAP](https://github.com/DEAP/deap) * [update_checker](https://github.com/bboe/update_checker) * [tqdm](https://github.com/tqdm/tqdm) * [stopit](https://github.com/glenfant/stopit) * [pandas](http://pandas.pydata.org) * [joblib](https://joblib.readthedocs.io/en/latest/) * [xgboost](https://xgboost.readthedocs.io/en/latest/) Most of the necessary Python packages can be installed via the [Anaconda Python distribution](https://www.anaconda.com/products/individual), which we strongly recommend that you use. **Support for Python 3.4 and below has been officially dropped since version 0.11.0.** You can install TPOT using `pip` or `conda-forge`. ## pip NumPy, SciPy, scikit-learn, pandas, joblib, and PyTorch can be installed in Anaconda via the command: ```Shell conda install numpy scipy scikit-learn pandas joblib pytorch ``` DEAP, update_checker, tqdm, stopit and xgboost can be installed with `pip` via the command: ```Shell pip install deap update_checker tqdm stopit xgboost ``` **Windows users: pip installation may not work on some Windows environments, and it may cause unexpected errors.** If you have issues installing XGBoost, check the [XGBoost installation documentation](http://xgboost.readthedocs.io/en/latest/build.html). If you plan to use [Dask](http://dask.pydata.org/en/latest/) for parallel training, make sure to install [dask[delay] and dask[dataframe]](https://docs.dask.org/en/latest/install.html) and [dask_ml](https://dask-ml.readthedocs.io/en/latest/install.html). **It is noted that dask-ml>=1.7 requires distributed>=2.4.0 and scikit-learn>=0.23.0.** ```Shell pip install dask[delayed] dask[dataframe] dask-ml fsspec>=0.3.3 distributed>=2.10.0 ``` If you plan to use the [TPOT-MDR configuration](https://arxiv.org/abs/1702.01780), make sure to install [scikit-mdr](https://github.com/EpistasisLab/scikit-mdr) and [scikit-rebate](https://github.com/EpistasisLab/scikit-rebate): ```Shell pip install scikit-mdr skrebate ``` To enable support for [PyTorch](https://pytorch.org/)-based neural networks (TPOT-NN), you will need to install PyTorch. TPOT-NN will work with either CPU or GPU PyTorch, but we strongly recommend using a GPU version, if possible, as CPU PyTorch models tend to train very slowly. We recommend following [PyTorch's installation instructions](https://pytorch.org/get-started/locally/) customized for your operating system and Python distribution. Finally to install TPOT itself, run the following command: ```Shell pip install tpot ``` ## conda-forge To install tpot and its core dependencies you can use: ```Shell conda install -c conda-forge tpot ``` To install additional dependencies you can use: ```Shell conda install -c conda-forge tpot xgboost dask dask-ml scikit-mdr skrebate ``` As mentioned above, we recommend following [PyTorch's installation instructions](https://pytorch.org/get-started/locally/) for installing it to enable support for [PyTorch](https://pytorch.org/)-based neural networks (TPOT-NN). ## Installation for using TPOT-cuML configuration With "TPOT cuML" configuration (see built-in configurations), TPOT will search over a restricted configuration using the GPU-accelerated estimators in [RAPIDS cuML](https://github.com/rapidsai/cuml) and [DMLC XGBoost](https://github.com/dmlc/xgboost). **This configuration requires an NVIDIA Pascal architecture or better GPU with [compute capability 6.0+](https://developer.nvidia.com/cuda-gpus), and that the library cuML is installed.** With this configuration, all model training and predicting will be GPU-accelerated. This configuration is particularly useful for medium-sized and larger datasets on which CPU-based estimators are a common bottleneck, and works for both the `TPOTClassifier` and `TPOTRegressor`. Please download this conda environment yml file to install TPOT for using TPOT-cuML configuration. ``` conda env create -f tpot-cuml.yml -n tpot-cuml conda activate tpot-cuml ``` ## Installation problems Please [file a new issue](https://github.com/EpistasisLab/tpot/issues/new) if you run into installation problems. ================================================ FILE: docs/archived/related.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

Other Automated Machine Learning (AutoML) tools and related projects:
Name Language License Description
Auto-WEKA Java GPL-v3 Automated model selection and hyper-parameter tuning for Weka models.
auto-sklearn Python BSD-3-Clause An automated machine learning toolkit and a drop-in replacement for a scikit-learn estimator.
auto_ml Python MIT Automated machine learning for analytics & production. Supports manual feature type declarations.
H2O AutoML Java with Python, Scala & R APIs and web GUI Apache 2.0 Automated: data prep, hyperparameter tuning, random grid search and stacked ensembles in a distributed ML platform.
devol Python MIT Automated deep neural network design via genetic programming.
MLBox Python BSD-3-Clause Accurate hyper-parameter optimization in high-dimensional space with support for distributed computing.
Recipe C GPL-v3 Machine-learning pipeline optimization through genetic programming. Uses grammars to define pipeline structure.
Xcessiv Python Apache 2.0 A web-based application for quick, scalable, and automated hyper-parameter tuning and stacked ensembling in Python.
GAMA Python Apache 2.0 Machine-learning pipeline optimization through asynchronous evaluation based genetic programming.
================================================ FILE: docs/archived/releases.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

# Release Notes ## Version 0.12.0 - Fix numpy compatibility - Dask optimizations - Minor bug fixes ## Version 0.11.7 - Fix compatibility issue with scikit-learn 0.24 and xgboost 1.3.0 - Fix a bug causing that TPOT does not work when classifying more than 50 classes - Add initial support `Resampler` from `imblearn` - Fix minor bugs ## Version 0.11.6 - Fix a bug causing point mutation function does not work properly with using `template` option - Add a new built configuration called "TPOT cuML" which TPOT will search over a restricted configuration using the GPU-accelerated estimators in [RAPIDS cuML](https://github.com/rapidsai/cuml) and [DMLC XGBoost](https://github.com/dmlc/xgboost). **This configuration requires an NVIDIA Pascal architecture or better GPU with [compute capability 6.0+](https://developer.nvidia.com/cuda-gpus), and that the library cuML is installed.** - Add string path support for log/log_file parameter - Fix a bug in version 0.11.5 causing no update in stdout after each generation - Fix minor bugs ## Version 0.11.5 - Make `Pytorch` as an optional dependency - Refine installation documentation ## Version 0.11.4 - Add a new built configuration "TPOT NN" which includes all operators in "Default TPOT" plus additional neural network estimators written in PyTorch (currently `tpot.builtins.PytorchLRClassifier` and `tpot.builtins.PytorchMLPClassifier` for classification tasks only) - Refine `log_file` parameter's behavior ## Version 0.11.3 - Fix a bug in TPOTRegressor in v0.11.2 - Add `-log` option in command line interface to save process log to a file. ## Version 0.11.2 - Fix `early_stop` parameter does not work properly - TPOT built-in `OneHotEncoder` can refit to different datasets - Fix the issue that the attribute `evaluated_individuals_` cannot record correct generation info. - Add a new parameter `log_file` to output logs to a file instead of `sys.stdout` - Fix some code quality issues and mistakes in documentations - Fix minor bugs ## Version 0.11.1 - Fix compatibility issue with scikit-learn v0.22 - `warm_start` now saves both Primitive Sets and evaluated_pipelines_ from previous runs; - Fix the error that TPOT assign wrong fitness scores to non-evaluated pipelines (interrupted by `max_min_mins` or `KeyboardInterrupt`) ; - Fix the bug that mutation operator cannot generate new pipeline when template is not default value and `warm_start` is True; - Fix the bug that `max_time_mins` cannot stop optimization process when search space is limited. - Fix a bug in exported codes when the exported pipeline is only 1 estimator - Fix spelling mistakes in documentations - Fix some code quality issues ## Version 0.11.0 - **Support for Python 3.4 and below has been officially dropped.** Also support for scikit-learn 0.20 or below has been dropped. - The support of a metric function with the signature `score_func(y_true, y_pred)` for `scoring parameter` has been dropped. - Refine `StackingEstimator` for not stacking NaN/Infinity predication probabilities. - Fix a bug that population doesn't persist by `warm_start=True` when `max_time_mins` is not default value. - Now the `random_state` parameter in TPOT is used for pipeline evaluation instead of using a fixed random seed of 42 before. The `set_param_recursive` function has been moved to `export_utils.py` and it can be used in exported codes for setting `random_state` recursively in scikit-learn Pipeline. It is used to set `random_state` in `fitted_pipeline_` attribute and exported pipelines. - TPOT can independently use `generations` and `max_time_mins` to limit the optimization process through using one of the parameters or both. - `.export()` function will return string of exported pipeline if output filename is not specified. - Add [`SGDClassifier`](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDClassifier.html) and [`SGDRegressor`](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDRegressor.html) into TPOT default configs. - Documentation has been updated - Fix minor bugs. ## Version 0.10.2 - **TPOT v0.10.2 is the last version to support Python 2.7 and Python 3.4.** - Minor updates for fixing compatibility issues with the latest version of scikit-learn (version > 0.21) and xgboost (v0.90) - Default value of `template` parameter is changed to `None` instead. - Fix errors in documentation ## Version 0.10.1 - Add `data_file_path` option into `expert` function for replacing `'PATH/TO/DATA/FILE'` to customized dataset path in exported scripts. (Related issue #838) - Change python version in CI tests to 3.7 - Add CI tests for macOS. ## Version 0.10.0 - Add a new `template` option to specify a desired structure for machine learning pipeline in TPOT. Check [TPOT API](https://epistasislab.github.io/tpot/api/) (it will be updated once it is merge to master branch). - Add `FeatureSetSelector` operator into TPOT for feature selection based on *priori* export knowledge. Please check our [preprint paper](https://www.biorxiv.org/content/10.1101/502484v1.article-info) for more details (*Note: it was named `DatasetSelector` in 1st version paper but we will rename to FeatureSetSelector in next version of the paper*) - Refine `n_jobs` parameter to accept value below -1. For n_jobs below -1, (n_cpus + 1 + n_jobs) are used. Thus for n_jobs = -2, all CPUs but one are used. - Now `memory` parameter can create memory cache directory if it does not exist. - Fix minor bugs. ## Version 0.9.6 - Fix a bug causing that `max_time_mins` parameter doesn't work when `use_dask=True` in TPOT 0.9.5 - Now TPOT saves best pareto values best pareto pipeline s in checkpoint folder - TPOT raises `ImportError` if operators in the TPOT configuration are not available when `verbosity>2` - Thank @PGijsbers for the suggestions. Now TPOT can save scores of individuals already evaluated in any generation even the evaluation process of that generation is interrupted/stopped. But it is noted that, in this case, TPOT will raise this **warning message**: `WARNING: TPOT may not provide a good pipeline if TPOT is stopped/interrupted in a early generation.`, because the pipelines in early generation, e.g. 1st generation, are evolved/modified very limited times via evolutionary algorithm. - Fix bugs in configuration of `TPOTRegressor` - Error fixes in documentation ## Version 0.9.5 - **TPOT now supports integration with Dask for parallelization + smart caching**. Big thanks to the Dask dev team for making this happen! - TPOT now supports for imputation/sparse matrices into `predict` and `predict_proba` functions. - `TPOTClassifier` and `TPOTRegressor` now follows scikit-learn estimator API. - We refined scoring parameter in TPOT API for accepting [`Scorer` object](http://jaquesgrobler.github.io/online-sklearn-build/modules/generated/sklearn.metrics.Scorer.html). - We refined parameters in VarianceThreshold and FeatureAgglomeration. - TPOT now supports using memory caching within a Pipeline via an optional `memory` parameter. - We improved documentation of TPOT. ## Version 0.9 * **TPOT now supports sparse matrices** with a new built-in TPOT configuration, "TPOT sparse". We are using a custom OneHotEncoder implementation that supports missing values and continuous features. * We have added an "early stopping" option for stopping the optimization process if no improvement is made within a set number of generations. Look up the `early_stop` parameter to access this functionality. * TPOT now reduces the number of duplicated pipelines between generations, which saves you time during the optimization process. * TPOT now supports custom scoring functions via the command-line mode. * We have added a new optional argument, `periodic_checkpoint_folder`, that allows TPOT to periodically save the best pipeline so far to a local folder during optimization process. * TPOT no longer uses `sklearn.externals.joblib` when `n_jobs=1` to avoid the potential freezing issue [that scikit-learn suffers from](http://scikit-learn.org/stable/faq.html#why-do-i-sometime-get-a-crash-freeze-with-n-jobs-1-under-osx-or-linux). * We have added `pandas` as a dependency to read input datasets instead of `numpy.recfromcsv`. NumPy's `recfromcsv` function is unable to parse datasets with complex data types. * Fixed a bug that `DEFAULT` in the parameter(s) of nested estimator raises `KeyError` when exporting pipelines. * Fixed a bug related to setting `random_state` in nested estimators. The issue would happen with pipeline with `SelectFromModel` (`ExtraTreesClassifier` as nested estimator) or `StackingEstimator` if nested estimator has `random_state` parameter. * Fixed a bug in the missing value imputation function in TPOT to impute along columns instead rows. * Refined input checking for sparse matrices in TPOT. * Refined the TPOT pipeline mutation operator. ## Version 0.8 * **TPOT now detects whether there are missing values in your dataset** and replaces them with the median value of the column. * TPOT now allows you to set a `group` parameter in the `fit` function so you can use the [GroupKFold](http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupKFold.html) cross-validation strategy. * TPOT now allows you to set a subsample ratio of the training instance with the `subsample` parameter. For example, setting `subsample`=0.5 tells TPOT to create a fixed subsample of half of the training data for the pipeline optimization process. This parameter can be useful for speeding up the pipeline optimization process, but may give less accurate performance estimates from cross-validation. * **TPOT now has more [built-in configurations](/using/#built-in-tpot-configurations)**, including TPOT MDR and TPOT light, for both classification and regression problems. * `TPOTClassifier` and `TPOTRegressor` now expose three useful internal attributes, `fitted_pipeline_`, `pareto_front_fitted_pipelines_`, and `evaluated_individuals_`. These attributes are described in the [API documentation](/api/). * Oh, **TPOT now has [thorough API documentation](/api/)**. Check it out! * Fixed a reproducibility issue where setting `random_seed` didn't necessarily result in the same results every time. This bug was present since TPOT v0.7. * Refined input checking in TPOT. * Removed Python 2 uncompliant code. ## Version 0.7 * **TPOT now has multiprocessing support.** TPOT allows you to use multiple processes in parallel to accelerate the pipeline optimization process in TPOT with the `n_jobs` parameter. * TPOT now allows you to **customize the operators and parameters considered during the optimization process**, which can be accomplished with the new `config_dict` parameter. The format of this customized dictionary can be found in the [online documentation](/using/#customizing-tpots-operators-and-parameters), along with a list of [built-in configurations](/using/#built-in-tpot-configurations). * TPOT now allows you to **specify a time limit for evaluating a single pipeline** (default limit is 5 minutes) in optimization process with the `max_eval_time_mins` parameter, so TPOT won't spend hours evaluating overly-complex pipelines. * We tweaked TPOT's underlying evolutionary optimization algorithm to work even better, including using the [mu+lambda algorithm](http://deap.readthedocs.io/en/master/api/algo.html#deap.algorithms.eaMuPlusLambda). This algorithm gives you more control of how many pipelines are generated every iteration with the `offspring_size` parameter. * Refined the default operators and parameters in TPOT, so TPOT 0.7 should work even better than 0.6. * TPOT now supports sample weights in the fitness function if some if your samples are more important to classify correctly than others. The sample weights option works the same as in scikit-learn, e.g., `tpot.fit(x_train, y_train, sample_weights=sample_weights)`. * The default scoring metric in TPOT has been changed from balanced accuracy to accuracy, the same default metric for classification algorithms in scikit-learn. Balanced accuracy can still be used by setting `scoring='balanced_accuracy'` when creating a TPOT instance. ## Version 0.6 * **TPOT now supports regression problems!** We have created two separate `TPOTClassifier` and `TPOTRegressor` classes to support classification and regression problems, respectively. The [command-line interface](/using/#tpot-on-the-command-line) also supports this feature through the `-mode` parameter. * TPOT now allows you to **specify a time limit** for the optimization process with the `max_time_mins` parameter, so you don't need to guess how long TPOT will take any more to recommend a pipeline to you. * Added a new operator that performs feature selection using [ExtraTrees](http://scikit-learn.org/stable/modules/ensemble.html#extremely-randomized-trees) feature importance scores. * **[XGBoost](https://github.com/dmlc/xgboost) has been added as an optional dependency to TPOT.** If you have XGBoost installed, TPOT will automatically detect your installation and use the `XGBoostClassifier` and `XGBoostRegressor` in its pipelines. * TPOT now offers a verbosity level of 3 ("science mode"), which outputs the entire Pareto front instead of only the current best score. This feature may be useful for users looking to make a trade-off between pipeline complexity and score. ## Version 0.5 * Major refactor: Each operator is defined in a separate class file. Hooray for easier-to-maintain code! * TPOT now **exports directly to scikit-learn Pipelines** instead of hacky code. * Internal representation of individuals now uses scikit-learn pipelines. * Parameters for each operator have been optimized so TPOT spends less time exploring useless parameters. * We have removed pandas as a dependency and instead use numpy matrices to store the data. * TPOT now uses **k-fold cross-validation** when evaluating pipelines, with a default k = 3. This k parameter can be tuned when creating a new TPOT instance. * Improved **scoring function support**: Even though TPOT uses balanced accuracy by default, you can now have TPOT use [any of the scoring functions](http://scikit-learn.org/stable/modules/model_evaluation.html#common-cases-predefined-values) that `cross_val_score` supports. * Added the scikit-learn [Normalizer](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html) preprocessor. * [Minor text fixes.](http://knowyourmeme.com/memes/pokemon-go-updates-controversy) ## Version 0.4 In TPOT 0.4, we've made some major changes to the internals of TPOT and added some convenience functions. We've summarized the changes below.
  • Added new sklearn models and preprocessors
    • AdaBoostClassifier
    • BernoulliNB
    • ExtraTreesClassifier
    • GaussianNB
    • MultinomialNB
    • LinearSVC
    • PassiveAggressiveClassifier
    • GradientBoostingClassifier
    • RBFSampler
    • FastICA
    • FeatureAgglomeration
    • Nystroem
  • Added operator that inserts virtual features for the count of features with values of zero
  • Reworked parameterization of TPOT operators
    • Reduced parameter search space with information from a scikit-learn benchmark
    • TPOT no longer generates arbitrary parameter values, but uses a fixed parameter set instead
  • Removed XGBoost as a dependency
    • Too many users were having install issues with XGBoost
    • Replaced with scikit-learn's GradientBoostingClassifier
  • Improved descriptiveness of TPOT command line parameter documentation
  • Removed min/max/avg details during fit() when verbosity > 1
    • Replaced with tqdm progress bar
    • Added tqdm as a dependency
  • Added fit_predict() convenience function
  • Added get_params() function so TPOT can operate in scikit-learn's cross_val_score & related functions
## Version 0.3 * We revised the internal optimization process of TPOT to make it more efficient, in particular in regards to the model parameters that TPOT optimizes over. ## Version 0.2 * TPOT now has the ability to export the optimized pipelines to sklearn code. * Logistic regression, SVM, and k-nearest neighbors classifiers were added as pipeline operators. Previously, TPOT only included decision tree and random forest classifiers. * TPOT can now use arbitrary scoring functions for the optimization process. * TPOT now performs multi-objective Pareto optimization to balance model complexity (i.e., # of pipeline operators) and the score of the pipeline. ## Version 0.1 * First public release of TPOT. * Optimizes pipelines with decision trees and random forest classifiers as the model, and uses a handful of feature preprocessors. ================================================ FILE: docs/archived/support.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

TPOT was developed in the [Computational Genetics Lab](http://epistasis.org/) at the [University of Pennsylvania](https://www.upenn.edu/) with funding from the [NIH](http://www.nih.gov/) under grant R01 AI117694. We are incredibly grateful for the support of the NIH and the University of Pennsylvania during the development of this project. The TPOT logo was designed by Todd Newmuis, who generously donated his time to the project. ================================================ FILE: docs/archived/using.md ================================================
⚠️ Warning

This documentation is for the archived version of TPOT, which is no longer maintained. For the latest version, click here.

# Using TPOT ## What to expect from AutoML software Automated machine learning (AutoML) takes a higher-level approach to machine learning than most practitioners are used to, so we've gathered a handful of guidelines on what to expect when running AutoML software such as TPOT.
AutoML algorithms aren't intended to run for only a few minutes
Of course, you *can* run TPOT for only a few minutes and it will find a reasonably good pipeline for your dataset. However, if you don't run TPOT for long enough, it may not find the best possible pipeline for your dataset. It may even not find any suitable pipeline at all, in which case a `RuntimeError('A pipeline has not yet been optimized. Please call fit() first.')` will be raised. Often it is worthwhile to run multiple instances of TPOT in parallel for a long time (hours to days) to allow TPOT to thoroughly search the pipeline space for your dataset.
AutoML algorithms can take a long time to finish their search
AutoML algorithms aren't as simple as fitting one model on the dataset; they are considering multiple machine learning algorithms (random forests, linear models, SVMs, etc.) in a pipeline with multiple preprocessing steps (missing value imputation, scaling, PCA, feature selection, etc.), the hyperparameters for all of the models and preprocessing steps, as well as multiple ways to ensemble or stack the algorithms within the pipeline. As such, TPOT will take a while to run on larger datasets, but it's important to realize why. With the default TPOT settings (100 generations with 100 population size), TPOT will evaluate 10,000 pipeline configurations before finishing. To put this number into context, think about a grid search of 10,000 hyperparameter combinations for a machine learning algorithm and how long that grid search will take. That is 10,000 model configurations to evaluate with 10-fold cross-validation, which means that roughly 100,000 models are fit and evaluated on the training data in one grid search. That's a time-consuming procedure, even for simpler models like decision trees. Typical TPOT runs will take hours to days to finish (unless it's a small dataset), but you can always interrupt the run partway through and see the best results so far. TPOT also [provides](/tpot/api/) a `warm_start` parameter that lets you restart a TPOT run from where it left off.
AutoML algorithms can recommend different solutions for the same dataset
If you're working with a reasonably complex dataset or run TPOT for a short amount of time, different TPOT runs may result in different pipeline recommendations. TPOT's optimization algorithm is stochastic in nature, which means that it uses randomness (in part) to search the possible pipeline space. When two TPOT runs recommend different pipelines, this means that the TPOT runs didn't converge due to lack of time *or* that multiple pipelines perform more-or-less the same on your dataset. This is actually an advantage over fixed grid search techniques: TPOT is meant to be an assistant that gives you ideas on how to solve a particular machine learning problem by exploring pipeline configurations that you might have never considered, then leaves the fine-tuning to more constrained parameter tuning techniques such as grid search. ## TPOT with code We've taken care to design the TPOT interface to be as similar as possible to scikit-learn. TPOT can be imported just like any regular Python module. To import TPOT, type: ```Python from tpot import TPOTClassifier ``` then create an instance of TPOT as follows: ```Python pipeline_optimizer = TPOTClassifier() ``` It's also possible to use TPOT for regression problems with the `TPOTRegressor` class. Other than the class name, a `TPOTRegressor` is used the same way as a `TPOTClassifier`. You can read more about the `TPOTClassifier` and `TPOTRegressor` classes in the [API documentation](/tpot/api/). Some example code with custom TPOT parameters might look like: ```Python pipeline_optimizer = TPOTClassifier(generations=5, population_size=20, cv=5, random_state=42, verbosity=2) ``` Now TPOT is ready to optimize a pipeline for you. You can tell TPOT to optimize a pipeline based on a data set with the `fit` function: ```Python pipeline_optimizer.fit(X_train, y_train) ``` The `fit` function initializes the genetic programming algorithm to find the highest-scoring pipeline based on average k-fold cross-validation Then, the pipeline is trained on the entire set of provided samples, and the TPOT instance can be used as a fitted model. You can then proceed to evaluate the final pipeline on the testing set with the `score` function: ```Python print(pipeline_optimizer.score(X_test, y_test)) ``` Finally, you can tell TPOT to export the corresponding Python code for the optimized pipeline to a text file with the `export` function: ```Python pipeline_optimizer.export('tpot_exported_pipeline.py') ``` Once this code finishes running, `tpot_exported_pipeline.py` will contain the Python code for the optimized pipeline. Below is a full example script using TPOT to optimize a pipeline, score it, and export the best pipeline to a file. ```Python from tpot import TPOTClassifier from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split digits = load_digits() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, train_size=0.75, test_size=0.25) pipeline_optimizer = TPOTClassifier(generations=5, population_size=20, cv=5, random_state=42, verbosity=2) pipeline_optimizer.fit(X_train, y_train) print(pipeline_optimizer.score(X_test, y_test)) pipeline_optimizer.export('tpot_exported_pipeline.py') ``` Check our [examples](/tpot/examples/) to see TPOT applied to some specific data sets. ## TPOT on the command line To use TPOT via the command line, enter the following command with a path to the data file: ```Shell tpot /path_to/data_file.csv ``` An example command-line call to TPOT may look like: ```Shell tpot data/mnist.csv -is , -target class -o tpot_exported_pipeline.py -g 5 -p 20 -cv 5 -s 42 -v 2 ``` TPOT offers several arguments that can be provided at the command line. To see brief descriptions of these arguments, enter the following command: ```Shell tpot --help ``` Detailed descriptions of the command-line arguments are below.
Argument Parameter Valid values Effect
-is INPUT_SEPARATOR Any string Character used to separate columns in the input file.
-target TARGET_NAME Any string Name of the target column in the input file.
-mode TPOT_MODE ['classification', 'regression'] Whether TPOT is being used for a supervised classification or regression problem.
-o OUTPUT_FILE String path to a file File to export the code for the final optimized pipeline.
-g GENERATIONS Any positive integer or None Number of iterations to run the pipeline optimization process. It must be a positive number or None. If None, the parameter max_time_mins must be defined as the runtime limit. Generally, TPOT will work better when you give it more generations (and therefore time) to optimize the pipeline.

TPOT will evaluate POPULATION_SIZE + GENERATIONS x OFFSPRING_SIZE pipelines in total.
-p POPULATION_SIZE Any positive integer Number of individuals to retain in the GP population every generation. Generally, TPOT will work better when you give it more individuals (and therefore time) to optimize the pipeline.

TPOT will evaluate POPULATION_SIZE + GENERATIONS x OFFSPRING_SIZE pipelines in total.
-os OFFSPRING_SIZE Any positive integer Number of offspring to produce in each GP generation.

By default, OFFSPRING_SIZE = POPULATION_SIZE.
-mr MUTATION_RATE [0.0, 1.0] GP mutation rate in the range [0.0, 1.0]. This tells the GP algorithm how many pipelines to apply random changes to every generation.

We recommend using the default parameter unless you understand how the mutation rate affects GP algorithms.
-xr CROSSOVER_RATE [0.0, 1.0] GP crossover rate in the range [0.0, 1.0]. This tells the GP algorithm how many pipelines to "breed" every generation.

We recommend using the default parameter unless you understand how the crossover rate affects GP algorithms.
-scoring SCORING_FN 'accuracy', 'adjusted_rand_score', 'average_precision', 'balanced_accuracy',
'f1', 'f1_macro', 'f1_micro', 'f1_samples', 'f1_weighted', 'neg_log_loss', 'neg_mean_absolute_error', 'neg_mean_squared_error', 'neg_median_absolute_error', 'precision', 'precision_macro', 'precision_micro', 'precision_samples', 'precision_weighted',
'r2', 'recall', 'recall_macro', 'recall_micro', 'recall_samples', 'recall_weighted', 'roc_auc', 'my_module.scorer_name*'
Function used to evaluate the quality of a given pipeline for the problem. By default, accuracy is used for classification and mean squared error (MSE) is used for regression.

TPOT assumes that any function with "error" or "loss" in the name is meant to be minimized, whereas any other functions will be maximized.

my_module.scorer_name: You can also specify your own function or a full python path to an existing one.

See the section on scoring functions for more details.
-cv CV Any integer > 1 Number of folds to evaluate each pipeline over in k-fold cross-validation during the TPOT optimization process.
-sub SUBSAMPLE (0.0, 1.0] Subsample ratio of the training instance. Setting it to 0.5 means that TPOT randomly collects half of training samples for pipeline optimization process.
-njobs NUM_JOBS Any positive integer or -1 Number of CPUs for evaluating pipelines in parallel during the TPOT optimization process.

Assigning this to -1 will use as many cores as available on the computer. For n_jobs below -1, (n_cpus + 1 + n_jobs) are used. Thus for n_jobs = -2, all CPUs but one are used.
-maxtime MAX_TIME_MINS Any positive integer How many minutes TPOT has to optimize the pipeline.

How many minutes TPOT has to optimize the pipeline.If not None, this setting will allow TPOT to run until max_time_mins minutes elapsed and then stop. TPOT will stop earlier if generationsis set and all generations are already evaluated.
-maxeval MAX_EVAL_MINS Any positive float How many minutes TPOT has to evaluate a single pipeline.

Setting this parameter to higher values will allow TPOT to consider more complex pipelines but will also allow TPOT to run longer.
-s RANDOM_STATE Any positive integer Random number generator seed for reproducibility.

Set this seed if you want your TPOT run to be reproducible with the same seed and data set in the future.
-config CONFIG_FILE String or file path Operators and parameter configurations in TPOT:

  • Path for configuration file: TPOT will use the path to a configuration file for customizing the operators and parameters that TPOT uses in the optimization process
  • string 'TPOT light', TPOT will use a built-in configuration with only fast models and preprocessors
  • string 'TPOT MDR', TPOT will use a built-in configuration specialized for genomic studies
  • string 'TPOT sparse': TPOT will use a configuration dictionary with a one-hot encoder and the operators normally included in TPOT that also support sparse matrices.
See the built-in configurations section for the list of configurations included with TPOT, and the custom configuration section for more information and examples of how to create your own TPOT configurations.
-template TEMPLATE String Template of predefined pipeline structure. The option is for specifying a desired structure for the machine learning pipeline evaluated in TPOT. So far this option only supports linear pipeline structure. Each step in the pipeline should be a main class of operators (Selector, Transformer, Classifier or Regressor) or a specific operator (e.g. `SelectPercentile`) defined in TPOT operator configuration. If one step is a main class, TPOT will randomly assign all subclass operators (subclasses of [`SelectorMixin`](https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/feature_selection/base.py#L17), [`TransformerMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html), [`ClassifierMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.ClassifierMixin.html) or [`RegressorMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.RegressorMixin.html) in scikit-learn) to that step. Steps in the template are delimited by "-", e.g. "SelectPercentile-Transformer-Classifier". By default value of template is None, TPOT generates tree-based pipeline randomly. See the template option in tpot section for more details.
-memory MEMORY String or file path If supplied, pipeline will cache each transformer after calling fit. This feature is used to avoid computing the fit transformers within a pipeline if the parameters and input data are identical with another fitted pipeline during optimization process. Memory caching mode in TPOT:

  • Path for a caching directory: TPOT uses memory caching with the provided directory and TPOT does NOT clean the caching directory up upon shutdown.
  • string 'auto': TPOT uses memory caching with a temporary directory and cleans it up upon shutdown.
-cf CHECKPOINT_FOLDER Folder path If supplied, a folder you created, in which tpot will periodically save pipelines in pareto front so far while optimizing.

This is useful in multiple cases:
  • sudden death before tpot could save an optimized pipeline
  • progress tracking
  • grabbing a pipeline while tpot is working


Example:
mkdir my_checkpoints
-cf ./my_checkpoints
-es EARLY_STOP Any positive integer How many generations TPOT checks whether there is no improvement in optimization process.

End optimization process if there is no improvement in the set number of generations.
-v VERBOSITY {0, 1, 2, 3} How much information TPOT communicates while it is running.

0 = none, 1 = minimal, 2 = high, 3 = all.

A setting of 2 or higher will add a progress bar during the optimization procedure.
-log LOG Folder path Save progress content to a file.
--no-update-check Flag indicating whether the TPOT version checker should be disabled.
--version Show TPOT's version number and exit.
--help Show TPOT's help documentation and exit.
## Scoring functions TPOT makes use of `sklearn.model_selection.cross_val_score` for evaluating pipelines, and as such offers the same support for scoring functions. There are two ways to make use of scoring functions with TPOT: - You can pass in a string to the `scoring` parameter from the list above. Any other strings will cause TPOT to throw an exception. - You can pass the callable object/function with signature `scorer(estimator, X, y)`, where `estimator` is trained estimator to use for scoring, `X` are features that will be passed to `estimator.predict` and `y` are target values for `X`. To do this, you should implement your own function. See the example below for further explanation. ```Python from tpot import TPOTClassifier from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.metrics import make_scorer digits = load_digits() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, train_size=0.75, test_size=0.25) # Make a custom metric function def my_custom_accuracy(y_true, y_pred): return float(sum(y_pred == y_true)) / len(y_true) # Make a custom a scorer from the custom metric function # Note: greater_is_better=False in make_scorer below would mean that the scoring function should be minimized. my_custom_scorer = make_scorer(my_custom_accuracy, greater_is_better=True) tpot = TPOTClassifier(generations=5, population_size=20, verbosity=2, scoring=my_custom_scorer) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_digits_pipeline.py') ``` * **my_module.scorer_name**: You can also use a custom `score_func(y_true, y_pred)` or `scorer(estimator, X, y)` function through the command line by adding the argument `-scoring my_module.scorer` to your command-line call. TPOT will import your module and use the custom scoring function from there. TPOT will include your current working directory when importing the module, so you can place it in the same directory where you are going to run TPOT. Example: `-scoring sklearn.metrics.auc` will use the function auc from sklearn.metrics module. ## Built-in TPOT configurations TPOT comes with a handful of default operators and parameter configurations that we believe work well for optimizing machine learning pipelines. Below is a list of the current built-in configurations that come with TPOT.
Configuration Name Description Operators
Default TPOT TPOT will search over a broad range of preprocessors, feature constructors, feature selectors, models, and parameters to find a series of operators that minimize the error of the model predictions. Some of these operators are complex and may take a long time to run, especially on larger datasets.

Note: This is the default configuration for TPOT. To use this configuration, use the default value (None) for the config_dict parameter.
Classification

Regression
TPOT light TPOT will search over a restricted range of preprocessors, feature constructors, feature selectors, models, and parameters to find a series of operators that minimize the error of the model predictions. Only simpler and fast-running operators will be used in these pipelines, so TPOT light is useful for finding quick and simple pipelines for a classification or regression problem.

This configuration works for both the TPOTClassifier and TPOTRegressor.
Classification

Regression
TPOT MDR TPOT will search over a series of feature selectors and Multifactor Dimensionality Reduction models to find a series of operators that maximize prediction accuracy. The TPOT MDR configuration is specialized for genome-wide association studies (GWAS), and is described in detail online here.

Note that TPOT MDR may be slow to run because the feature selection routines are computationally expensive, especially on large datasets.
Classification

Regression
TPOT sparse TPOT uses a configuration dictionary with a one-hot encoder and the operators normally included in TPOT that also support sparse matrices.

This configuration works for both the TPOTClassifier and TPOTRegressor.
Classification

Regression
TPOT NN TPOT uses the same configuration as "Default TPOT" plus additional neural network estimators written in PyTorch (currently only `tpot.builtins.PytorchLRClassifier` and `tpot.builtins.PytorchMLPClassifier`).

Currently only classification is supported, but future releases will include regression estimators.
Classification
TPOT cuML TPOT will search over a restricted configuration using the GPU-accelerated estimators in RAPIDS cuML and DMLC XGBoost. This configuration requires an NVIDIA Pascal architecture or better GPU with compute capability 6.0+, and that the library cuML is installed. With this configuration, all model training and predicting will be GPU-accelerated.

This configuration is particularly useful for medium-sized and larger datasets on which CPU-based estimators are a common bottleneck, and works for both the TPOTClassifier and TPOTRegressor.
Classification

Regression
To use any of these configurations, simply pass the string name of the configuration to the `config_dict` parameter (or `-config` on the command line). For example, to use the "TPOT light" configuration: ```Python from tpot import TPOTClassifier from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split digits = load_digits() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, train_size=0.75, test_size=0.25) tpot = TPOTClassifier(generations=5, population_size=20, verbosity=2, config_dict='TPOT light') tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_digits_pipeline.py') ``` ## Customizing TPOT's operators and parameters Beyond the default configurations that come with TPOT, in some cases it is useful to limit the algorithms and parameters that TPOT considers. For that reason, we allow users to provide TPOT with a custom configuration for its operators and parameters. The custom TPOT configuration must be in nested dictionary format, where the first level key is the path and name of the operator (e.g., `sklearn.naive_bayes.MultinomialNB`) and the second level key is the corresponding parameter name for that operator (e.g., `fit_prior`). The second level key should point to a list of parameter values for that parameter, e.g., `'fit_prior': [True, False]`. For a simple example, the configuration could be: ```Python tpot_config = { 'sklearn.naive_bayes.GaussianNB': { }, 'sklearn.naive_bayes.BernoulliNB': { 'alpha': [1e-3, 1e-2, 1e-1, 1., 10., 100.], 'fit_prior': [True, False] }, 'sklearn.naive_bayes.MultinomialNB': { 'alpha': [1e-3, 1e-2, 1e-1, 1., 10., 100.], 'fit_prior': [True, False] } } ``` in which case TPOT would only consider pipelines containing `GaussianNB`, `BernoulliNB`, `MultinomialNB`, and tune those algorithm's parameters in the ranges provided. This dictionary can be passed directly within the code to the `TPOTClassifier`/`TPOTRegressor` `config_dict` parameter, described above. For example: ```Python from tpot import TPOTClassifier from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split digits = load_digits() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, train_size=0.75, test_size=0.25) tpot_config = { 'sklearn.naive_bayes.GaussianNB': { }, 'sklearn.naive_bayes.BernoulliNB': { 'alpha': [1e-3, 1e-2, 1e-1, 1., 10., 100.], 'fit_prior': [True, False] }, 'sklearn.naive_bayes.MultinomialNB': { 'alpha': [1e-3, 1e-2, 1e-1, 1., 10., 100.], 'fit_prior': [True, False] } } tpot = TPOTClassifier(generations=5, population_size=20, verbosity=2, config_dict=tpot_config) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test)) tpot.export('tpot_digits_pipeline.py') ``` Command-line users must create a separate `.py` file with the custom configuration and provide the path to the file to the `tpot` call. For example, if the simple example configuration above is saved in `tpot_classifier_config.py`, that configuration could be used on the command line with the command: ``` tpot data/mnist.csv -is , -target class -config tpot_classifier_config.py -g 5 -p 20 -v 2 -o tpot_exported_pipeline.py ``` When using the command-line interface, the configuration file specified in the `-config` parameter *must* name its custom TPOT configuration `tpot_config`. Otherwise, TPOT will not be able to locate the configuration dictionary. For more detailed examples of how to customize TPOT's operator configuration, see the default configurations for [classification](https://github.com/EpistasisLab/tpot/blob/master/tpot/config/classifier.py) and [regression](https://github.com/EpistasisLab/tpot/blob/master/tpot/config/regressor.py) in TPOT's source code. Note that you must have all of the corresponding packages for the operators installed on your computer, otherwise TPOT will not be able to use them. For example, if XGBoost is not installed on your computer, then TPOT will simply not import nor use XGBoost in the pipelines it considers. ## Template option in TPOT Template option provides a way to specify a desired structure for machine learning pipeline, which may reduce TPOT computation time and potentially provide more interpretable results. Current implementation only supports linear pipelines. Below is a simple example to use `template` option. The pipelines generated/evaluated in TPOT will follow this structure: 1st step is a feature selector (a subclass of [`SelectorMixin`](https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/feature_selection/base.py#L17)), 2nd step is a feature transformer (a subclass of [`TransformerMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html)) and 3rd step is a classifier for classification (a subclass of [`ClassifierMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.ClassifierMixin.html)). The last step must be `Classifier` for `TPOTClassifier`'s template but `Regressor` for `TPOTRegressor`. **Note: although `SelectorMixin` is subclass of `TransformerMixin` in scikit-learn, but `Transformer` in this option excludes those subclasses of `SelectorMixin`.** ```Python tpot_obj = TPOTClassifier( template='Selector-Transformer-Classifier' ) ``` If a specific operator, e.g. `SelectPercentile`, is preferred for usage in the 1st step of the pipeline, the template can be defined like 'SelectPercentile-Transformer-Classifier'. ## FeatureSetSelector in TPOT `FeatureSetSelector` is a special new operator in TPOT. This operator enables feature selection based on *priori* expert knowledge. For example, in RNA-seq gene expression analysis, this operator can be used to select one or more gene (feature) set(s) based on GO (Gene Ontology) terms or annotated gene sets Molecular Signatures Database ([MSigDB](http://software.broadinstitute.org/gsea/msigdb/index.jsp)) in the 1st step of pipeline via `template` option above, in order to reduce dimensions and TPOT computation time. This operator requires a dataset list in csv format. In this csv file, there are only three columns: 1st column is feature set names, 2nd column is the total number of features in one set and 3rd column is a list of feature names (if input X is pandas.DataFrame) or indexes (if input X is numpy.ndarray) delimited by ";". Below is an example how to use this operator in TPOT. Please check our [preprint paper](https://www.biorxiv.org/content/10.1101/502484v1.article-info) for more details. ```Python from tpot import TPOTClassifier import numpy as np import pandas as pd from tpot.config import classifier_config_dict test_data = pd.read_csv("https://raw.githubusercontent.com/EpistasisLab/tpot/master/tests/tests.csv") test_X = test_data.drop("class", axis=1) test_y = test_data['class'] # add FeatureSetSelector into tpot configuration classifier_config_dict['tpot.builtins.FeatureSetSelector'] = { 'subset_list': ['https://raw.githubusercontent.com/EpistasisLab/tpot/master/tests/subset_test.csv'], 'sel_subset': [0,1] # select only one feature set, a list of index of subset in the list above #'sel_subset': list(combinations(range(3), 2)) # select two feature sets } tpot = TPOTClassifier(generations=5, population_size=50, verbosity=2, template='FeatureSetSelector-Transformer-Classifier', config_dict=classifier_config_dict) tpot.fit(test_X, test_y) ``` ## Pipeline caching in TPOT With the `memory` parameter, pipelines can cache the results of each transformer after fitting them. This feature is used to avoid repeated computation by transformers within a pipeline if the parameters and input data are identical to another fitted pipeline during optimization process. TPOT allows users to specify a custom directory path or [`joblib.Memory`](https://joblib.readthedocs.io/en/latest/generated/joblib.Memory.html) in case they want to re-use the memory cache in future TPOT runs (or a `warm_start` run). There are three methods for enabling memory caching in TPOT: ```Python from tpot import TPOTClassifier from tempfile import mkdtemp from joblib import Memory from shutil import rmtree # Method 1, auto mode: TPOT uses memory caching with a temporary directory and cleans it up upon shutdown tpot = TPOTClassifier(memory='auto') # Method 2, with a custom directory for memory caching tpot = TPOTClassifier(memory='/to/your/path') # Method 3, with a Memory object cachedir = mkdtemp() # Create a temporary folder memory = Memory(cachedir=cachedir, verbose=0) tpot = TPOTClassifier(memory=memory) # Clear the cache directory when you don't need it anymore rmtree(cachedir) ``` **Note: TPOT does NOT clean up memory caches if users set a custom directory path or Memory object. We recommend that you clean up the memory caches when you don't need it anymore.** ## Crash/freeze issue with n_jobs > 1 under OSX or Linux Internally, TPOT uses [joblib](http://joblib.readthedocs.io/) to fit estimators in parallel. This is the same parallelization framework used by scikit-learn. But it may crash/freeze with n_jobs > 1 under OSX or Linux [as scikit-learn does](http://scikit-learn.org/stable/faq.html#why-do-i-sometime-get-a-crash-freeze-with-n-jobs-1-under-osx-or-linux), especially with large datasets. One solution is to configure Python's `multiprocessing` module to use the `forkserver` start method (instead of the default `fork`) to manage the process pools. You can enable the `forkserver` mode globally for your program by putting the following codes into your main script: ```Python import multiprocessing # other imports, custom code, load data, define model... if __name__ == '__main__': multiprocessing.set_start_method('forkserver') # call scikit-learn utils or tpot utils with n_jobs > 1 here ``` More information about these start methods can be found in the [multiprocessing documentation](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods). ## Parallel Training with Dask For large problems or working on Jupyter notebook, we highly recommend that you can distribute the work on a [Dask](http://dask.pydata.org/en/latest/) cluster. The [dask-examples binder](https://mybinder.org/v2/gh/dask/dask-examples/master?filepath=machine-learning%2Ftpot.ipynb) has a runnable example with a small dask cluster. To use your Dask cluster to fit a TPOT model, specify the ``use_dask`` keyword when you create the TPOT estimator. **Note: if `use_dask=True`, TPOT will use as many cores as available on the your Dask cluster. If `n_jobs` is specified, then it will control the chunk size (10*`n_jobs` if it is less then offspring size) of parallel training.** ```python estimator = TPOTEstimator(use_dask=True, n_jobs=-1) ``` This will use all the workers on your cluster to do the training, and use [Dask-ML's pipeline rewriting](https://dask-ml.readthedocs.io/en/latest/hyper-parameter-search.html#avoid-repeated-work) to avoid re-fitting estimators multiple times on the same set of data. It will also provide fine-grained diagnostics in the [distributed scheduler UI](https://distributed.readthedocs.io/en/latest/web.html). Alternatively, Dask implements a joblib backend. You can instruct TPOT to use the distributed backend during training by specifying a `joblib.parallel_backend`: ```python import joblib import distributed.joblib from dask.distributed import Client # connect to the cluster client = Client('schedueler-address') # create the estimator normally estimator = TPOTClassifier(n_jobs=-1) # perform the fit in this context manager with joblib.parallel_backend("dask"): estimator.fit(X, y) ``` See [dask's distributed joblib integration](https://distributed.readthedocs.io/en/latest/joblib.html) for more. ## Neural Networks in TPOT (`tpot.nn`) Support for neural network models and deep learning is an experimental feature newly added to TPOT. Available neural network architectures are provided by the `tpot.nn` module. Unlike regular `sklearn` estimators, these models need to be written by hand, and must also inherit the appropriate base classes provided by `sklearn` for all of their built-in modules. In other words, they need implement methods like `.fit()`, `fit_transform()`, `get_params()`, etc., as described in detail on [Developing scikit-learn estimators](https://scikit-learn.org/stable/developers/develop.html). ### Telling TPOT to use built-in PyTorch neural network models Mainly due to the issues described below, TPOT won't use its neural network models unless you explicitly tell it to do so. This is done as follows: - Use `import tpot.nn` before instantiating any TPOT estimators. - Use a configuration dictionary that includes one or more `tpot.nn` estimators, either by writing one manually, including one from a file, or by importing the configuration in `tpot/config/classifier_nn.py`. A very simple example that will force TPOT to only use a PyTorch-based logistic regression classifier as its main estimator is as follows: ```python tpot_config = { 'tpot.nn.PytorchLRClassifier': { 'learning_rate': [1e-3, 1e-2, 1e-1, 0.5, 1.] } } ``` - Alternatively, use a template string including `PytorchLRClassifier` or `PytorchMLPClassifier` while loading the TPOT-NN configuration dictionary. Neural network models are notorious for being extremely sensitive to their initialization parameters, so you may need to heavily adjust `tpot.nn` configuration dictionaries in order to attain good performance on your dataset. A simple example of using TPOT-NN is shown in [examples](/tpot/examples/). ### Important caveats - Neural network models (especially when they reach moderately large sizes) take a notoriously large amount of time and computing power to train. You should expect `tpot.nn` neural networks to train several orders of magnitude slower than their `sklearn` alternatives. This can be alleviated somewhat by training the models on computers with CUDA-enabled GPUs. - TPOT will occasionally learn pipelines that stack several `sklearn` estimators. Mathematically, these can be nearly identical to some deep learning models. For example, by stacking several `sklearn.linear_model.LogisticRegression`s, you end up with a very close approximation of a Multilayer Perceptron; one of the simplest and most well known deep learning architectures. TPOT's genetic programming algorithms generally optimize these 'networks' much faster than PyTorch, which typically uses a more brute-force convex optimization approach. - The problem of 'black box' model introspection is one of the most substantial criticisms and challenges of deep learning. This problem persists in `tpot.nn`, whereas TPOT's default estimators often are far easier to introspect. ================================================ FILE: docs/cite.md ================================================ # Citing TPOT If you use TPOT in a scientific publication, please consider citing at least one of the following papers: Hernandez, J. G., Saini, A. K., Ghosh, A., & Moore, J. H. (2025). [The tree-based pipeline optimization tool: Tackling biomedical research problems with genetic programming and automated machine learning](https://www.cell.com/patterns/fulltext/S2666-3899(25)00162-X). Patterns, 6(7). BibTeX entry: ```bibtext @article{hernandez2025tree, title={The tree-based pipeline optimization tool: Tackling biomedical research problems with genetic programming and automated machine learning}, author={Hernandez, Jose Guadalupe and Saini, Anil Kumar and Ghosh, Attri and Moore, Jason H}, journal={Patterns}, volume={6}, number={7}, year={2025}, publisher={Elsevier} } ``` Ribeiro, P., Saini, A., Moran, J., Matsumoto, N., Choi, H., Hernandez, M., & Moore, J. H. (2024). [TPOT2: A New Graph-Based Implementation of the Tree-Based Pipeline Optimization Tool for Automated Machine Learning](https://link.springer.com/chapter/10.1007/978-981-99-8413-8_1). In Genetic programming theory and practice XX (pp. 1-17). Singapore: Springer Nature Singapore. BitTex entry: ```bibtex @incollection{ribeiro2024tpot2, title={TPOT2: A New Graph-Based Implementation of the Tree-Based Pipeline Optimization Tool for Automated Machine Learning}, author={Ribeiro, Pedro and Saini, Anil and Moran, Jay and Matsumoto, Nicholas and Choi, Hyunjun and Hernandez, Miguel and Moore, Jason H}, booktitle={Genetic programming theory and practice XX}, pages={1--17}, year={2024}, publisher={Springer} } ``` Randal S. Olson, Ryan J. Urbanowicz, Peter C. Andrews, Nicole A. Lavender, La Creis Kidd, and Jason H. Moore (2016). [Automating biomedical data science through tree-based pipeline optimization](http://link.springer.com/chapter/10.1007/978-3-319-31204-0_9). *Applications of Evolutionary Computation*, pages 123-137. BibTeX entry: ```bibtex @inbook{Olson2016EvoBio, author={Olson, Randal S. and Urbanowicz, Ryan J. and Andrews, Peter C. and Lavender, Nicole A. and Kidd, La Creis and Moore, Jason H.}, editor={Squillero, Giovanni and Burelli, Paolo}, chapter={Automating Biomedical Data Science Through Tree-Based Pipeline Optimization}, title={Applications of Evolutionary Computation: 19th European Conference, EvoApplications 2016, Porto, Portugal, March 30 -- April 1, 2016, Proceedings, Part I}, year={2016}, publisher={Springer International Publishing}, pages={123--137}, isbn={978-3-319-31204-0}, doi={10.1007/978-3-319-31204-0_9}, url={http://dx.doi.org/10.1007/978-3-319-31204-0_9} } ``` Randal S. Olson, Nathan Bartley, Ryan J. Urbanowicz, and Jason H. Moore (2016). [Evaluation of a Tree-based Pipeline Optimization Tool for Automating Data Science](http://dl.acm.org/citation.cfm?id=2908918). *Proceedings of GECCO 2016*, pages 485-492. BibTeX entry: ```bibtex @inproceedings{OlsonGECCO2016, author = {Olson, Randal S. and Bartley, Nathan and Urbanowicz, Ryan J. and Moore, Jason H.}, title = {Evaluation of a Tree-based Pipeline Optimization Tool for Automating Data Science}, booktitle = {Proceedings of the Genetic and Evolutionary Computation Conference 2016}, series = {GECCO '16}, year = {2016}, isbn = {978-1-4503-4206-3}, location = {Denver, Colorado, USA}, pages = {485--492}, numpages = {8}, url = {http://doi.acm.org/10.1145/2908812.2908918}, doi = {10.1145/2908812.2908918}, acmid = {2908918}, publisher = {ACM}, address = {New York, NY, USA}, } ``` ================================================ FILE: docs/contribute.md ================================================ # Contributing We welcome you to check the existing issues for bugs or enhancements to work on. If you have an idea for an extension to TPOT, please file a new issue so we can discuss it. # Contribution Guide We welcome you to [check the existing issues](https://github.com/EpistasisLab/tpot/issues/) for bugs or enhancements to work on. If you have an idea for an extension to TPOT, please [file a new issue](https://github.com/EpistasisLab/tpot/issues/new) so we can discuss it. ## Project layout Both the latest stable release and the development version of TPOT are on the [main branch](https://github.com/EpistasisLab/tpot/tree/main). Ensure you are working from the correct commit when contributing. In terms of directory structure: * All of TPOT's code sources are in the `tpot` directory * The documentation sources are in the `docs_sources` directory * Images in the documentation are in the `images` directory * Tutorials for TPOT are in the `tutorials` directory * Unit tests for TPOT are in the `tests.py` file Make sure to familiarize yourself with the project layout before making any major contributions, and especially make sure to send all code changes to the `main` branch. ## How to contribute The preferred way to contribute to TPOT is to fork the [main repository](https://github.com/EpistasisLab/tpot/) on GitHub: 1. Fork the [project repository](https://github.com/EpistasisLab/tpot): click on the 'Fork' button near the top of the page. This creates a copy of the code under your account on the GitHub server. 2. Clone this copy to your local disk: $ git clone git@github.com:YourUsername/tpot.git $ cd tpot 3. Create a branch to hold your changes: $ git checkout main $ git pull origin main $ git checkout -b my-contribution 4. Make sure your local environment is setup correctly for development. Set up your local environment following the [Developer Installation instructions](installing.md#developerlatest-branch-installation). Ensure you also install `pytest` (`conda install pytest` or `pip install pytest`) into your development environment so that you can test changes locally. 5. Start making changes on your newly created branch. Work on this copy on your computer using Git to do the version control. 6. Check your changes haven't broken any existing tests and pass all your new tests. Navigate the terminal into the `tpot/tpot/` folder and run the command `pytest` to start all tests. (note, you must have the `pytest` package installed within your dev environment for this to work): $ pytest 7. When you're done editing and local testing, run: $ git add modified_files $ git commit to record your changes in Git, then push them to GitHub with: $ git push -u origin my-contribution Finally, go to the web page of your fork of the TPOT repo, and click 'Pull Request' (PR) to send your changes to the maintainers for review. Make sure that you send your PR to the `main` branch. This will start the CI server to check all the project's unit tests run and send an email to the maintainers. (If any of the above seems like magic to you, then look up the [Git documentation](http://git-scm.com/documentation) on the web.) ## Before submitting your pull request Before you submit a pull request for your contribution, please work through this checklist to make sure that you have done everything necessary so we can efficiently review and accept your changes. If your contribution changes TPOT in any way: * Update the [documentation](https://github.com/EpistasisLab/tpot/tree/main/docs) so all of your changes are reflected there. * Update the [README](https://github.com/EpistasisLab/tpot/blob/main/README.md) if anything there has changed. If your contribution involves any code changes: * Update the [project unit tests](https://github.com/EpistasisLab/tpot/tree/main/tpot/tests) to test your code changes. * Make sure that your code is properly commented with [docstrings](https://www.python.org/dev/peps/pep-0257/) and comments explaining your rationale behind non-obvious coding practices. If your contribution requires a new library dependency: * Double-check that the new dependency is easy to install via `pip` or Anaconda. If the dependency requires a complicated installation, then we most likely won't merge your changes because we want to keep TPOT easy to install. ## After submitting your pull request After submitting your pull request, GitHub will automatically run unit tests on your changes and make sure that your updated code builds and runs. We also use services that automatically check code quality and test coverage. Check back shortly after submitting your pull request to make sure that your code passes these checks. If any of the checks come back with a red X, then do your best to address the errors. ================================================ FILE: docs/css/extra.css ================================================ .md-grid { max-width: 100%; } ================================================ FILE: docs/index.md ================================================ {% include-markdown "../README.md" %} ================================================ FILE: docs/installation.md ================================================ # Installation TPOT requires a working installation of Python. ### Creating a conda environment (optional) We recommend using conda environments for installing TPOT, though it would work equally well if manually installed without it. [More information on making anaconda environments found here.](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) ``` conda create --name tpotenv python=3.13 conda activate tpotenv ``` ### Note for M1 Mac or other Arm-based CPU users You need to install the lightgbm package directly from conda using the following command before installing TPOT. This is to ensure that you get the version that is compatible with your system. ``` conda install --yes -c conda-forge 'lightgbm>=3.3.3' ``` ### Developer/Latest Branch Installation ``` pip install -e /path/to/tpotrepo ``` If you downloaded with git pull, then the repository folder will be named TPOT. (Note: this folder is the one that includes setup.py inside of it and not the folder of the same name inside it). If you downloaded as a zip, the folder may be called tpot-main. ================================================ FILE: docs/related.md ================================================ Other Automated Machine Learning (AutoML) tools and related projects:
Name Language License Description
Auto-WEKA Java GPL-v3 Automated model selection and hyper-parameter tuning for Weka models.
auto-sklearn Python BSD-3-Clause An automated machine learning toolkit and a drop-in replacement for a scikit-learn estimator.
auto_ml Python MIT Automated machine learning for analytics & production. Supports manual feature type declarations.
H2O AutoML Java with Python, Scala & R APIs and web GUI Apache 2.0 Automated: data prep, hyperparameter tuning, random grid search and stacked ensembles in a distributed ML platform.
devol Python MIT Automated deep neural network design via genetic programming.
MLBox Python BSD-3-Clause Accurate hyper-parameter optimization in high-dimensional space with support for distributed computing.
Recipe C GPL-v3 Machine-learning pipeline optimization through genetic programming. Uses grammars to define pipeline structure.
Xcessiv Python Apache 2.0 A web-based application for quick, scalable, and automated hyper-parameter tuning and stacked ensembling in Python.
GAMA Python Apache 2.0 Machine-learning pipeline optimization through asynchronous evaluation based genetic programming.
PyMoo Python Apache 2.0 Multi-objective optimization in Python.
Karoo GP Python MIT A Python based genetic programming application suite with support for symbolic regression and classification.
MABE C++ See here A Python based genetic programming application suite with support for symbolic regression and classification.
SBBFramework Python BSD-2-Clause Python implementation of Symbiotic Bid-Based (SBB) framework for problem decomposition using Genetic Programming (GP).
Tiny GP Python GPL-v3 A minimalistic program implementing Koza-style (tree-based) genetic programming to solve a symbolic regression problem.
Baikal Python BSD-3-Clause A graph-based functional API for building complex scikit-learn pipelines.
skdag Python MIT A more flexible alternative to scikit-learn Pipelines.
d6tflow Python MIT A python library which makes building complex data science workflows easy, fast and intuitive.
================================================ FILE: docs/requirements_docs.txt ================================================ griffe==1.3.1 mike==2.1.3 mkdocs==1.6.1 mkdocs-include-markdown-plugin==6.2.2 mkdocs-jupyter==0.25.0 mkdocs-material==9.5.35 mkdocstrings==0.26.1 mkdocstrings-python==1.11.1 nbconvert==7.16.5 ================================================ FILE: docs/scripts/build_docs_sources.sh ================================================ #!/bin/bash function iterate_files() { local directory="$1" base_dir="docs/documentation" for file in "$directory"/*; do if [ -f "$file" ] && [[ "$file" == *.py ]] && [ "$(basename "$file")" != "__init__.py" ] && \ ! echo "$file" | grep -q "test" && [ "$(basename "$file")" != "graph_utils.py" ]; then directories=$base_dir/$(dirname "$file") file_name=$(basename "$file") md_file=$directories/"${file_name%.*}".md mkdir -p $directories && touch $md_file include_line=$(dirname "$file") include_line="${include_line//\//.}"."${file_name%.*}" echo "::: $include_line" > $md_file elif [ -d "$file" ]; then iterate_files "$file" fi done } iterate_files "tpot" ================================================ FILE: docs/scripts/build_mkdocs.sh ================================================ #!/bin/bash cat > mkdocs.yml <> mkdocs.yml echo " - tpot_api/estimator.md" >> mkdocs.yml echo " - tpot_api/classifier.md" >> mkdocs.yml echo " - tpot_api/regressor.md" >> mkdocs.yml echo " - Examples:" >> mkdocs.yml for file in docs/Tutorial/*.ipynb; do base=$(basename $file .ipynb) echo " - Tutorial/$base.ipynb" >> mkdocs.yml done echo " - Documentation:" >> mkdocs.yml function iterate_source_files() { local directory="$1" for file in "$directory"/*; do if [ -f "$file" ] && [[ "$file" == *.md ]]; then slash_count=$(echo "$file" | grep -o '/' | wc -l) num_spaces=$((slash_count * 2)) spaces=$(printf "%*s" $num_spaces) echo "$spaces- ${file#*/}" >> mkdocs.yml fi done for file in "$directory"/*; do if [ -d "$file" ]; then slash_count=$(echo "$file" | grep -o '/' | wc -l) num_spaces=$((slash_count * 2)) spaces=$(printf "%*s" $num_spaces) last_dir=$(basename "$file") echo "$spaces- $last_dir:" >> mkdocs.yml iterate_source_files "$file" fi done } iterate_source_files "docs/documentation" # make these static instead # for file in docs/*.md; do # base=$(basename $file .md) # if [ "$base" == "index" ]; then # continue # fi # echo " - $base.md" >> mkdocs.yml # done echo " - contribute.md" >> mkdocs.yml echo " - cite.md" >> mkdocs.yml echo " - support.md" >> mkdocs.yml echo " - related.md" >> mkdocs.yml # moved to the top # # test docstring # # echo " - Tutorials:" >> mkdocs.yml # for file in docs/tutorial/*.ipynb; do # base=$(basename $file .ipynb) # echo " - tutorial/$base.ipynb" >> mkdocs.yml # done ================================================ FILE: docs/scripts/build_tutorial_toc_not_used.sh ================================================ #!/bin/bash for file in docs/tutorial/*.html; do base=$(basename "$file" .html) echo "
" > "docs/tutorial/$base.md" done ================================================ FILE: docs/support.md ================================================ # Support TPOT was developed in the [Artificial Intelligence Innovation (A2I) Lab](http://epistasis.org/) at Cedars-Sinai with funding from the [NIH](http://www.nih.gov/) under grants U01 AG066833 and R01 LM010098. We are incredibly grateful for the support of the NIH and the Cedars-Sinai during the development of this project. The TPOT logo was designed by Todd Newmuis, who generously donated his time to the project. ================================================ FILE: docs/tpot_api/classifier.md ================================================ ::: tpot.tpot_estimator.templates.tpottemplates.TPOTClassifier ================================================ FILE: docs/tpot_api/estimator.md ================================================ ::: tpot.tpot_estimator.estimator ================================================ FILE: docs/tpot_api/regressor.md ================================================ ::: tpot.tpot_estimator.templates.tpottemplates.TPOTRegressor ================================================ FILE: docs/using.md ================================================ # Using TPOT See the Tutorials Folder for more instructions and examples. ## Best Practices ### 1 TPOT uses dask for parallel processing. When Python is parallelized, each module is imported within each processes. Therefore it is important to protect all code within a `if __name__ == "__main__"` when running TPOT from a script. This is not required when running TPOT from a notebook. For example: ``` #my_analysis.py import tpot if __name__ == "__main__": X, y = load_my_data() est = tpot.TPOTClassifier() est.fit(X,y) #rest of analysis ``` ### 2 When designing custom objective functions, avoid the use of global variables. Don't Do: ``` global_X = [[1,2],[4,5]] global_y = [0,1] def foo(est): return my_scorer(est, X=global_X, y=global_y) ``` Instead use a partial ``` from functools import partial def foo_scorer(est, X, y): return my_scorer(est, X, y) if __name__=='__main__': X = [[1,2],[4,5]] y = [0,1] final_scorer = partial(foo_scorer, X=X, y=y) ``` Similarly when using lambda functions. Dont Do: ``` def new_objective(est, a, b) #definition a = 100 b = 20 bad_function = lambda est : new_objective(est=est, a=a, b=b) ``` Do: ``` def new_objective(est, a, b) #definition a = 100 b = 20 good_function = lambda est, a=a, b=b : new_objective(est=est, a=a, b=b) ``` ## Tips TPOT will not check if your data is correctly formatted. It will assume that you have passed in operators that can handle the type of data that was passed in. For instance, if you pass in a pandas dataframe with categorical features and missing data, then you should also include in your configuration operators that can handle those feautures of the data. Alternatively, if you pass in `preprocessing = True`, TPOT will impute missing values, one hot encode categorical features, then standardize the data. (Note that this is currently fitted and transformed on the entire training set before splitting for CV. Later there will be an option to apply per fold, and have the parameters be learnable.) Setting `verbose` to 5 can be helpful during debugging as it will print out the error generated by failing pipelines. ================================================ FILE: mkdocs_archived.yml ================================================ site_name: TPOT site_url: http://epistasislab.github.io/tpot site_author: Randal S. Olson site_description: Documentation for TPOT, a Python Automated Machine Learning tool that optimizes machine learning pipelines using genetic programming. repo_url: https://github.com/epistasislab/tpot edit_uri: edit/master/docs/archived/ docs_dir: docs/archived/ site_dir: target/archived_site #theme: readthedocs theme: name: material logo: assets/tpot-logo.jpg favicon: assets/favicon.ico features: - toc.integrate - navigation.top palette: # light mode - scheme: default primary: grey toggle: icon: material/brightness-7 name: Switch to dark mode # dark mode - scheme: slate primary: grey toggle: icon: material/brightness-4 name: Switch to light mode extra: version: provider: mike extra_css: - css/archived.css markdown_extensions: - tables - fenced_code - pymdownx.highlight: anchor_linenums: true - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences plugins: - include-markdown copyright: Developed by Randal S. Olson and others at the University of Pennsylvania nav: - Home: index.md - Installation: installing.md - Using TPOT: using.md - TPOT API: api.md - Examples: examples.md - Contributing: contributing.md - Release Notes: releases.md - Citing TPOT: citing.md - Support: support.md - Related: related.md ================================================ FILE: pyproject.toml ================================================ [build-system] requires = ["setuptools>=61", "setuptools_scm>=7.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "TPOT" description = "Tree-based Pipeline Optimization Tool" readme = "README.md" requires-python = ">=3.10,<3.14" license = { text = "LGPL-3.0" } authors = [ { name = "Pedro Ribeiro" } ] keywords = [ "pipeline optimization", "hyperparameter optimization", "data science", "machine learning", "genetic programming", "evolutionary computation" ] classifiers = [ "Intended Audience :: Science/Research", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering :: Artificial Intelligence" ] dependencies = [ "numpy>=1.26.4", "scipy>=1.3.1", "scikit-learn>=1.6", "update_checker>=0.16", "tqdm>=4.36.1", "stopit>=1.1.1", "pandas>=2.2.0", "joblib>=1.1.1", "xgboost>=3.0.0", "matplotlib>=3.6.2", "traitlets>=5.8.0", "lightgbm>=3.3.3", "optuna>=3.0.5", "networkx>=3.0", "dask>=2024.4.2", "distributed>=2024.4.2", "dask-expr>=1.0.12", "dask-jobqueue>=0.8.5", "func_timeout>=4.3.5", "configspace>=1.1.1", "dill>=0.3.9", "seaborn>=0.13.2", ] dynamic = ["version"] [project.optional-dependencies] skrebate = ["skrebate>=0.3.4"] mdr = ["scikit-mdr>=0.4.4"] sklearnex = ["scikit-learn-intelex>=2023.2.1"] amltk = ["amltk>=1.12.1"] testing = [ "pytest>=6.0", "pytest-cov>=2.0", "mypy>=0.910", "flake8>=3.9", "tox>=3.24" ] [project.urls] Homepage = "https://github.com/EpistasisLab/tpot" [project.scripts] tpot = "tpot:main" [tool.setuptools] packages = ["tpot"] zip-safe = true [tool.setuptools.package-data] tpot = ["py.typed"] [tool.flake8] max-line-length = 120 [tool.setuptools_scm] # setuptools_scm gets the version from Git tags, e.g git tag v1.1.0 # then python -m build embeds the version into the package ================================================ FILE: tox.ini ================================================ [tox] minversion = 3.28.0 # flake8 and mypy outputs severla errors, so we disable them for now # envlist = py310, flake8, mypy envlist = py310, py311, py312, py313 isolated_build = true skip_missing_interpreters = true [gh-actions] python = 3.10: py310 3.11: py311 3.12: py312 3.13: py313 # 3.10: py310, flake8, mypy [testenv] setenv = PYTHONPATH = {toxinidir} extras = testing deps = setuptools>=65.0.0 commands = pytest --basetemp={envtmpdir} [testenv:flake8] basepython = python3.10 deps = flake8 commands = flake8 tpot [testenv:mypy] basepython = python3.10 extras = testing deps = setuptools>=65.0.0 commands = mypy tpot ================================================ FILE: tpot/__init__.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ #TODO: are all the imports in the init files done correctly? #TODO clean up import organization from .individual import BaseIndividual from .graphsklearn import GraphPipeline from .population import Population from . import builtin_modules from . import config from . import search_spaces from . import utils from . import evolvers from . import objectives from . import selectors from . import tpot_estimator from . import old_config_utils from .tpot_estimator import TPOTClassifier, TPOTRegressor, TPOTEstimator, TPOTEstimatorSteadyState from update_checker import update_check from ._version import __version__ update_check("tpot",__version__) ================================================ FILE: tpot/_version.py ================================================ try: from importlib.metadata import version except ImportError: from importlib_metadata import version # for Python < 3.8 __version__ = version("tpot") ================================================ FILE: tpot/builtin_modules/__init__.py ================================================ from .feature_set_selector import FeatureSetSelector from .zero_count import ZeroCount from .column_one_hot_encoder import ColumnOneHotEncoder, ColumnOrdinalEncoder from .arithmetictransformer import ArithmeticTransformer from .arithmetictransformer import AddTransformer, mul_neg_1_Transformer, MulTransformer, SafeReciprocalTransformer, EQTransformer, NETransformer, GETransformer, GTTransformer, LETransformer, LTTransformer, MinTransformer, MaxTransformer, ZeroTransformer, OneTransformer, NTransformer from .passthrough import Passthrough, SkipTransformer from .imputer import ColumnSimpleImputer from .estimatortransformer import EstimatorTransformer from .passkbinsdiscretizer import PassKBinsDiscretizer try: from .nn import PytorchLRClassifier, PytorchMLPClassifier except (ModuleNotFoundError, ImportError): pass # import warnings # warnings.warn("Warning: optional dependency `torch` is not available. - skipping import of NN models.") ================================================ FILE: tpot/builtin_modules/arithmetictransformer.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import random import numpy as np from sklearn.base import TransformerMixin, BaseEstimator #operations are done along axis #TODO potentially we could do operations on every combo (mul would be all possible pairs multiplied with each other) class ArithmeticTransformer(TransformerMixin, BaseEstimator): #functions = ["add", "mul_neg_1", "mul", "safe_reciprocal", "eq","ne","ge","gt","le","lt", "min","max","0","1"] def __init__(self, function,): """ A transformer that applies a function to the input array along axis 1. Parameters ---------- function : str The function to apply to the input array. The following functions are supported: - 'add' : Add all elements along axis 1 - 'mul_neg_1' : Multiply all elements along axis 1 by -1 - 'mul' : Multiply all elements along axis 1 - 'safe_reciprocal' : Take the reciprocal of all elements along axis 1, with a safe division by zero - 'eq' : Check if all elements along axis 1 are equal - 'ne' : Check if all elements along axis 1 are not equal - 'ge' : Check if all elements along axis 1 are greater than or equal to 0 - 'gt' : Check if all elements along axis 1 are greater than 0 - 'le' : Check if all elements along axis 1 are less than or equal to 0 - 'lt' : Check if all elements along axis 1 are less than 0 - 'min' : Take the minimum of all elements along axis 1 - 'max' : Take the maximum of all elements along axis 1 - '0' : Return an array of zeros - '1' : Return an array of ones """ self.function = function def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) if self.function == "add": return np.expand_dims(np.sum(X,1),1) elif self.function == "mul_neg_1": return X*-1 elif self.function == "mul": return np.expand_dims(np.prod(X,1),1) elif self.function == "safe_reciprocal": results = np.divide(1.0, X.astype(float), out=np.zeros_like(X).astype(float), where=X!=0) #TODO remove astypefloat? return results elif self.function == "eq": return np.expand_dims(np.all(X == X[0,:], axis = 1),1).astype(float) elif self.function == "ne": return 1- np.expand_dims(np.all(X == X[0,:], axis = 1),1).astype(float) #TODO these could be "sorted order" elif self.function == "ge": result = X >= 0 return result.astype(float) elif self.function == "gt": result = X > 0 return result.astype(float) elif self.function == "le": result = X <= 0 return result.astype(float) elif self.function == "lt": result = X < 0 return result.astype(float) elif self.function == "min": return np.expand_dims(np.amin(X,1),1) elif self.function == "max": return np.expand_dims(np.amax(X,1),1) elif self.function == "0": return np.zeros((X.shape[0],1)) elif self.function == "1": return np.ones((X.shape[0],1)) def issorted(x, rev=False): if rev: s = sorted(x) s.reverse() if s == x: return True else: if sorted(x) == x: return True return False class AddTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that adds all elements along axis 1. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.expand_dims(np.sum(X,1),1) class mul_neg_1_Transformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that multiplies all elements by -1. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return X*-1 class MulTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that multiplies all elements along axis 1. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.expand_dims(np.prod(X,1),1) class SafeReciprocalTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes the reciprocal of all elements, with a safe division by zero. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.divide(1.0, X.astype(float), out=np.zeros_like(X).astype(float), where=X!=0) #TODO remove astypefloat? class EQTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes checks if all elements in a row are equal. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.expand_dims(np.all(X == X[0,:], axis = 1),1).astype(float) class NETransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes checks if all elements in a row are not equal. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return 1- np.expand_dims(np.all(X == X[0,:], axis = 1),1).astype(float) class GETransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes checks if all elements in a row are greater than or equal to 0. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) result = X >= 0 return result.astype(float) class GTTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes checks if all elements in a row are greater than 0. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) result = X > 0 return result.astype(float) class LETransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes checks if all elements in a row are less than or equal to 0. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) result = X <= 0 return result.astype(float) class LTTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes checks if all elements in a row are less than 0. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) result = X < 0 return result.astype(float) class MinTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes the minimum of all elements in a row. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.expand_dims(np.amin(X,1),1) class MaxTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that takes the maximum of all elements in a row. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.expand_dims(np.amax(X,1),1) class ZeroTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that returns an array of zeros. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.zeros((X.shape[0],1)) class OneTransformer(TransformerMixin, BaseEstimator): def __init__(self): """ A transformer that returns an array of ones. """ pass def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.ones((X.shape[0],1)) class NTransformer(TransformerMixin, BaseEstimator): def __init__(self, n): """ A transformer that returns an array of n. """ self.n = n def fit(self, X, y=None): return self def transform(self, X): transformed_X = np.array(self.transform_helper(np.array(X))) if transformed_X.dtype != float: transformed_X = transformed_X.astype(float) return transformed_X def transform_helper(self, X): X = np.array(X) if len(X.shape) == 1: X = np.expand_dims(X,0) return np.ones((X.shape[0],1))*self.n ================================================ FILE: tpot/builtin_modules/column_one_hot_encoder.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np from scipy import sparse from sklearn.base import TransformerMixin, BaseEstimator from sklearn.utils import check_array from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder import sklearn import pandas as pd from pandas.api.types import is_numeric_dtype def auto_select_categorical_features(X, min_unique=10,): if isinstance(X, pd.DataFrame): return [col for col in X.columns if len(X[col].unique()) < min_unique] else: return [i for i in range(X.shape[1]) if len(np.unique(X[:, i])) < min_unique] def _X_selected(X, selected): """Split X into selected features and other features""" if isinstance(X, pd.DataFrame): X_sel = X[selected] X_not_sel = X.drop(selected, axis=1) else: X_sel = X[:, selected] X_not_sel = np.delete(X, selected, axis=1) return X_sel, X_not_sel class ColumnOneHotEncoder(TransformerMixin, BaseEstimator ): def __init__(self, columns='auto', drop=None, handle_unknown='infrequent_if_exist', sparse_output=False, min_frequency=None,max_categories=None): ''' A wrapper for OneHotEncoder that allows for onehot encoding of specific columns in a DataFrame or np array. Parameters ---------- columns : str, list, default='auto' Determines which columns to onehot encode with sklearn.preprocessing.OneHotEncoder. - 'auto' : Automatically select categorical features based on columns with less than 10 unique values - 'categorical' : Automatically select categorical features - 'numeric' : Automatically select numeric features - 'all' : Select all features - list : A list of columns to select drop, handle_unknown, sparse_output, min_frequency, max_categories : see sklearn.preprocessing.OneHotEncoder ''' self.columns = columns self.drop = drop self.handle_unknown = handle_unknown self.sparse_output = sparse_output self.min_frequency = min_frequency self.max_categories = max_categories def fit(self, X, y=None): """Fit OneHotEncoder to X, then transform X. Equivalent to self.fit(X).transform(X), but more convenient and more efficient. See fit for the parameters, transform for the return value. Parameters ---------- X : array-like or sparse matrix, shape=(n_samples, n_features) Dense array or sparse matrix. y: array-like {n_samples,} (Optional, ignored) Feature labels """ if (self.columns == "categorical" or self.columns == "numeric") and not isinstance(X, pd.DataFrame): raise ValueError(f"Invalid value for columns: {self.columns}. " "Only 'all' or is supported for np arrays") if self.columns == "categorical": self.columns_ = list(X.select_dtypes(exclude='number').columns) elif self.columns == "numeric": self.columns_ = [col for col in X.columns if is_numeric_dtype(X[col])] elif self.columns == "auto": self.columns_ = auto_select_categorical_features(X) elif self.columns == "all": if isinstance(X, pd.DataFrame): self.columns_ = X.columns else: self.columns_ = list(range(X.shape[1])) elif isinstance(self.columns, list): self.columns_ = self.columns else: raise ValueError(f"Invalid value for columns: {self.columns}") if len(self.columns_) == 0: return self self.enc = sklearn.preprocessing.OneHotEncoder( categories='auto', drop = self.drop, handle_unknown = self.handle_unknown, sparse_output = self.sparse_output, min_frequency = self.min_frequency, max_categories = self.max_categories) #TODO make this more consistent with sklearn baseimputer/baseencoder if isinstance(X, pd.DataFrame): self.enc.set_output(transform="pandas") for col in X.columns: # check if the column name is not a string if not isinstance(col, str): # if it's not a string, rename the column with "X" prefix X.rename(columns={col: f"X{col}"}, inplace=True) if len(self.columns_) == X.shape[1]: X_sel = self.enc.fit(X) else: X_sel, X_not_sel = _X_selected(X, self.columns_) X_sel = self.enc.fit(X_sel) return self def transform(self, X): """Transform X using one-hot encoding. Parameters ---------- X : array-like or sparse matrix, shape=(n_samples, n_features) Dense array or sparse matrix. Returns ------- X_out : sparse matrix if sparse=True else a 2-d array, dtype=int Transformed input. """ if len(self.columns_) == 0: return X #TODO make this more consistent with sklearn baseimputer/baseencoder if isinstance(X, pd.DataFrame): for col in X.columns: # check if the column name is not a string if not isinstance(col, str): # if it's not a string, rename the column with "X" prefix X.rename(columns={col: f"X{col}"}, inplace=True) if len(self.columns_) == X.shape[1]: return self.enc.transform(X) else: X_sel, X_not_sel= _X_selected(X, self.columns_) X_sel = self.enc.transform(X_sel) #If X is dataframe if isinstance(X, pd.DataFrame): X_sel = pd.DataFrame(X_sel, columns=self.enc.get_feature_names_out()) return pd.concat([X_not_sel.reset_index(drop=True), X_sel.reset_index(drop=True)], axis=1) else: return np.hstack((X_not_sel, X_sel)) class ColumnOrdinalEncoder(TransformerMixin, BaseEstimator ): def __init__(self, columns='auto', handle_unknown='error', unknown_value = -1, encoded_missing_value = np.nan, min_frequency=None,max_categories=None): ''' Parameters ---------- columns : str, list, default='auto' Determines which columns to onehot encode with sklearn.preprocessing.OneHotEncoder. - 'auto' : Automatically select categorical features based on columns with less than 10 unique values - 'categorical' : Automatically select categorical features - 'numeric' : Automatically select numeric features - 'all' : Select all features - list : A list of columns to select drop, handle_unknown, sparse_output, min_frequency, max_categories : see sklearn.preprocessing.OneHotEncoder ''' self.columns = columns self.handle_unknown = handle_unknown self.unknown_value = unknown_value self.encoded_missing_value = encoded_missing_value self.min_frequency = min_frequency self.max_categories = max_categories def fit(self, X, y=None): """Fit OneHotEncoder to X, then transform X. Equivalent to self.fit(X).transform(X), but more convenient and more efficient. See fit for the parameters, transform for the return value. Parameters ---------- X : array-like or sparse matrix, shape=(n_samples, n_features) Dense array or sparse matrix. y: array-like {n_samples,} (Optional, ignored) Feature labels """ if (self.columns == "categorical" or self.columns == "numeric") and not isinstance(X, pd.DataFrame): raise ValueError(f"Invalid value for columns: {self.columns}. " "Only 'all' or is supported for np arrays") if self.columns == "categorical": self.columns_ = list(X.select_dtypes(exclude='number').columns) elif self.columns == "numeric": self.columns_ = [col for col in X.columns if is_numeric_dtype(X[col])] elif self.columns == "auto": self.columns_ = auto_select_categorical_features(X) elif self.columns == "all": if isinstance(X, pd.DataFrame): self.columns_ = X.columns else: self.columns_ = list(range(X.shape[1])) elif isinstance(self.columns, list): self.columns_ = self.columns else: raise ValueError(f"Invalid value for columns: {self.columns}") if len(self.columns_) == 0: return self self.enc = sklearn.preprocessing.OrdinalEncoder(categories='auto', handle_unknown = self.handle_unknown, unknown_value = self.unknown_value, encoded_missing_value = self.encoded_missing_value, min_frequency = self.min_frequency, max_categories = self.max_categories) #TODO make this more consistent with sklearn baseimputer/baseencoder ''' if isinstance(X, pd.DataFrame): self.enc.set_output(transform="pandas") for col in X.columns: # check if the column name is not a string if not isinstance(col, str): # if it's not a string, rename the column with "X" prefix X.rename(columns={col: f"X{col}"}, inplace=True) ''' if len(self.columns_) == X.shape[1]: X_sel = self.enc.fit(X) else: X_sel, X_not_sel = _X_selected(X, self.columns_) X_sel = self.enc.fit(X_sel) return self def transform(self, X): """Transform X using one-hot encoding. Parameters ---------- X : array-like or sparse matrix, shape=(n_samples, n_features) Dense array or sparse matrix. Returns ------- X_out : sparse matrix if sparse=True else a 2-d array, dtype=int Transformed input. """ if len(self.columns_) == 0: return X #TODO make this more consistent with sklearn baseimputer/baseencoder ''' if isinstance(X, pd.DataFrame): for col in X.columns: # check if the column name is not a string if not isinstance(col, str): # if it's not a string, rename the column with "X" prefix X.rename(columns={col: f"X{col}"}, inplace=True) ''' if len(self.columns_) == X.shape[1]: return self.enc.transform(X) else: X_sel, X_not_sel= _X_selected(X, self.columns_) X_sel = self.enc.transform(X_sel) #If X is dataframe if isinstance(X, pd.DataFrame): X_sel = pd.DataFrame(X_sel, columns=self.enc.get_feature_names_out()) return pd.concat([X_not_sel.reset_index(drop=True), X_sel.reset_index(drop=True)], axis=1) else: return np.hstack((X_not_sel, X_sel)) ================================================ FILE: tpot/builtin_modules/estimatortransformer.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from numpy import ndarray from sklearn.base import TransformerMixin, BaseEstimator from sklearn.model_selection import cross_val_predict from sklearn.utils.validation import check_is_fitted from sklearn.utils.metaestimators import available_if import numpy as np from sklearn.utils.validation import check_is_fitted class EstimatorTransformer(TransformerMixin, BaseEstimator ): def __init__(self, estimator, method='auto', passthrough=False, cross_val_predict_cv=None): """ A class for using a sklearn estimator as a transformer. When calling fit_transform, this class returns the out put of cross_val_predict and trains the estimator on the full dataset. When calling transform, this class uses the estimator fit on the full dataset to transform the data. Parameters ---------- estimator : sklear.base. BaseEstimator The estimator to use as a transformer. method : str, default='auto' The method to use for the transformation. If 'auto', will try to use predict_proba, decision_function, or predict in that order. - predict_proba: use the predict_proba method of the estimator. - decision_function: use the decision_function method of the estimator. - predict: use the predict method of the estimator. passthrough : bool, default=False Whether to pass the original input through. cross_val_predict_cv : int, default=0 Number of folds to use for the cross_val_predict function for inner classifiers and regressors. Estimators will still be fit on the full dataset, but the following node will get the outputs from cross_val_predict. - 0-1 : When set to 0 or 1, the cross_val_predict function will not be used. The next layer will get the outputs from fitting and transforming the full dataset. - >=2 : When fitting pipelines with inner classifiers or regressors, they will still be fit on the full dataset. However, the output to the next node will come from cross_val_predict with the specified number of folds. """ self.estimator = estimator self.method = method self.passthrough = passthrough self.cross_val_predict_cv = cross_val_predict_cv def fit(self, X, y=None): self.estimator.fit(X, y) return self def transform(self, X, y=None): #Does not do cross val predict, just uses the estimator to transform the data. This is used for the actual transformation in practice, so the real transformation without fitting is needed if self.method == 'auto': if hasattr(self.estimator, 'predict_proba'): method = 'predict_proba' elif hasattr(self.estimator, 'decision_function'): method = 'decision_function' elif hasattr(self.estimator, 'predict'): method = 'predict' else: raise ValueError('Estimator has no valid method') else: method = self.method output = getattr(self.estimator, method)(X) output=np.array(output) if len(output.shape) == 1: output = output.reshape(-1,1) if self.passthrough: return np.hstack((output, X)) else: return output def fit_transform(self, X, y=None): #Does use cross_val_predict if cross_val_predict_cv is greater than 0. this function is only used in training the model. self.estimator.fit(X,y) if self.method == 'auto': if hasattr(self.estimator, 'predict_proba'): method = 'predict_proba' elif hasattr(self.estimator, 'decision_function'): method = 'decision_function' elif hasattr(self.estimator, 'predict'): method = 'predict' else: raise ValueError('Estimator has no valid method') else: method = self.method if self.cross_val_predict_cv is not None: output = cross_val_predict(self.estimator, X, y=y, cv=self.cross_val_predict_cv) else: output = getattr(self.estimator, method)(X) #reshape if needed if len(output.shape) == 1: output = output.reshape(-1,1) output=np.array(output) if self.passthrough: return np.hstack((output, X)) else: return output def _estimator_has(attr): '''Check if we can delegate a method to the underlying estimator. First, we check the first fitted final estimator if available, otherwise we check the unfitted final estimator. ''' return lambda self: (self.estimator is not None and hasattr(self.estimator, attr) ) @available_if(_estimator_has('predict')) def predict(self, X, **predict_params): check_is_fitted(self.estimator) #X = check_array(X) preds = self.estimator.predict(X,**predict_params) return preds @available_if(_estimator_has('predict_proba')) def predict_proba(self, X, **predict_params): check_is_fitted(self.estimator) #X = check_array(X) return self.estimator.predict_proba(X,**predict_params) @available_if(_estimator_has('decision_function')) def decision_function(self, X, **predict_params): check_is_fitted(self.estimator) #X = check_array(X) return self.estimator.decision_function(X,**predict_params) def __sklearn_is_fitted__(self): """ Check fitted status and return a Boolean value. """ return check_is_fitted(self.estimator) # @property # def _estimator_type(self): # return self.estimator._estimator_type @property def classes_(self): """The classes labels. Only exist if the last step is a classifier.""" return self.estimator._classes ================================================ FILE: tpot/builtin_modules/feature_encoding_frequency_selector.py ================================================ """ From https://github.com/EpistasisLab/autoqtl """ import numpy as np from sklearn.base import BaseEstimator from sklearn.feature_selection._base import SelectorMixin class FeatureEncodingFrequencySelector(SelectorMixin, BaseEstimator): """Feature selector based on Encoding Frequency. Encoding frequency is the frequency of each unique element(0/1/2/3) present in a feature set. Features are selected on the basis of a threshold assigned for encoding frequency. If frequency of any unique element is less than or equal to threshold, the feature is removed. """ @property def __name__(self): """Instance name is the same as the class name. """ return self.__class__.__name__ def __init__(self, threshold): """Create a FeatureEncodingFrequencySelector object. Parameters ---------- threshold : float, required Threshold value for allele frequency. If frequency of A or frequency of a is less than the threshold value then the feature is dropped. Returns ------- None """ self.threshold = threshold """def fit(self, X, y=None): Fit FeatureAlleleFrequencySelector for feature selection Parameters ---------- X : numpy ndarray, {n_samples, n_features} The training input samples. y : numpy array {n_samples,} The training target values. Returns ------- self : object Returns a copy of the estimator self.selected_feature_indexes = [] self.no_of_features = X.shape[1] # Finding the no of alleles in each feature column for i in range(0, X.shape[1]): no_of_AA_featurewise = np.count_nonzero(X[:,i]==0) no_of_Aa_featurewise = np.count_nonzero(X[:,i]==1) no_of_aa_featurewise = np.count_nonzero(X[:,i]==2) frequency_A_featurewise = (2*no_of_AA_featurewise + no_of_Aa_featurewise) / (2*no_of_AA_featurewise + 2*no_of_Aa_featurewise + 2*no_of_aa_featurewise) frequency_a_featurewise = 1 - frequency_A_featurewise if(not(frequency_A_featurewise <= self.threshold) and not(frequency_a_featurewise <= self.threshold)): self.selected_feature_indexes.append(i) return self""" """def transform(self, X): Make subset after fit Parameters ---------- X : numpy ndarray, {n_samples, n_features} New data, where n_samples is the number of samples and n_features is the number of features. Returns ------- X_transformed : numpy ndarray, {n_samples, n_features} The transformed feature set. X_transformed = X[:, self.selected_feature_indexes] return X_transformed""" def fit(self, X, y=None) : """Fit FeatureEncodingFrequencySelector for feature selection. This function gets the appropriate features. """ self.selected_feature_indexes = [] self.no_of_original_features = X.shape[1] # Finding the frequency of all the unique elements present featurewise in the input variable X for i in range(0, X.shape[1]): unique, counts = np.unique(X[:,i], return_counts=True) element_count_dict_featurewise = dict(zip(unique, counts)) element_frequency_dict_featurewise = {} feature_column_selected = True for x in unique: x_frequency_featurewise = element_count_dict_featurewise[x] / sum(counts) element_frequency_dict_featurewise[x] = x_frequency_featurewise for frequency in element_frequency_dict_featurewise.values(): if frequency <= self.threshold : feature_column_selected = False break if feature_column_selected == True : self.selected_feature_indexes.append(i) if not len(self.selected_feature_indexes): """msg = "No feature in X meets the encoding frequency threshold {0:.5f}" raise ValueError(msg.format(self.threshold))""" for i in range(0, X.shape[1]): self.selected_feature_indexes.append(i) return self def transform(self, X): """ Make subset after fit. This function returns a transformed version of X. """ X_transformed = X[:, self.selected_feature_indexes] return X_transformed def _get_support_mask(self): """ Get the boolean mask indicating which features are selected It is the abstractmethod Returns ------- support : boolean array of shape [# input features] An element is True iff its corresponding feature is selected for retention. """ n_features = self.no_of_original_features mask = np.zeros(n_features, dtype=bool) mask[np.asarray(self.selected_feature_indexes)] = True return mask ================================================ FILE: tpot/builtin_modules/feature_set_selector.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ #TODO handle sparse input? import numpy as np import pandas as pd import os, os.path from sklearn.base import BaseEstimator from sklearn.feature_selection._base import SelectorMixin #TODO clean this up and make sure it works class FeatureSetSelector(SelectorMixin, BaseEstimator): """ Select predefined feature subsets. """ def __init__(self, sel_subset=None, name=None): """Create a FeatureSetSelector object. Parameters ---------- sel_subset: list or int If X is a dataframe, items in sel_subset list must correspond to column names If X is a numpy array, items in sel_subset list must correspond to column indexes int: index of a single column Returns ------- None """ self.name = name self.sel_subset = sel_subset def fit(self, X, y=None): """Fit FeatureSetSelector for feature selection Parameters ---------- X: array-like of shape (n_samples, n_features) The training input samples. y: array-like, shape (n_samples,) The target values (integers that correspond to classes in classification, real numbers in regression). Returns ------- self: object Returns a copy of the estimator """ if isinstance(self.sel_subset, int) or isinstance(self.sel_subset, str): self.sel_subset = [self.sel_subset] #generate self.feat_list_idx if isinstance(X, pd.DataFrame): self.feature_names_in_ = X.columns.tolist() self.feat_list_idx = sorted([self.feature_names_in_.index(feat) for feat in self.sel_subset]) elif isinstance(X, np.ndarray): self.feature_names_in_ = None#list(range(X.shape[1])) self.feat_list_idx = sorted(self.sel_subset) n_features = X.shape[1] self.mask = np.zeros(n_features, dtype=bool) self.mask[np.asarray(self.feat_list_idx)] = True return self #TODO keep returned as dataframe if input is dataframe? may not be consistent with sklearn # def transform(self, X): def __sklearn_tags__(self): tags = super().__sklearn_tags__() tags.input_tags.allow_nan = True tags.target_tags.required = False # formally requires_y return tags def _get_support_mask(self): """ Get the boolean mask indicating which features are selected Returns ------- support : boolean array of shape [# input features] An element is True iff its corresponding feature is selected for retention. """ return self.mask ================================================ FILE: tpot/builtin_modules/feature_transformers.py ================================================ # -*- coding: utf-8 -*- """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np from sklearn.base import TransformerMixin, BaseEstimator from sklearn.utils import check_array from sklearn.decomposition import PCA from .one_hot_encoder import OneHotEncoder, auto_select_categorical_features, _X_selected class CategoricalSelector(TransformerMixin, BaseEstimator ): """Meta-transformer for selecting categorical features and transform them using OneHotEncoder. Parameters ---------- threshold : int, default=10 Maximum number of unique values per feature to consider the feature to be categorical. minimum_fraction: float, default=None Minimum fraction of unique values in a feature to consider the feature to be categorical. """ def __init__(self, threshold=10, minimum_fraction=None): """Create a CategoricalSelector object.""" self.threshold = threshold self.minimum_fraction = minimum_fraction def fit(self, X, y=None): """Do nothing and return the estimator unchanged This method is just there to implement the usual API and hence work in pipelines. Parameters ---------- X : array-like """ X = check_array(X, accept_sparse='csr') return self def transform(self, X): """Select categorical features and transform them using OneHotEncoder. Parameters ---------- X: numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples and n_components is the number of components. Returns ------- array-like, {n_samples, n_components} """ selected = auto_select_categorical_features(X, threshold=self.threshold) X_sel, _, n_selected, _ = _X_selected(X, selected) if n_selected == 0: # No features selected. raise ValueError('No categorical feature was found!') else: ohe = OneHotEncoder(categorical_features='all', sparse=False, minimum_fraction=self.minimum_fraction) return ohe.fit_transform(X_sel) class ContinuousSelector(TransformerMixin, BaseEstimator ): """Meta-transformer for selecting continuous features and transform them using PCA. Parameters ---------- threshold : int, default=10 Maximum number of unique values per feature to consider the feature to be categorical. svd_solver : string {'auto', 'full', 'arpack', 'randomized'} auto : the solver is selected by a default policy based on `X.shape` and `n_components`: if the input data is larger than 500x500 and the number of components to extract is lower than 80% of the smallest dimension of the data, then the more efficient 'randomized' method is enabled. Otherwise the exact full SVD is computed and optionally truncated afterwards. full : run exact full SVD calling the standard LAPACK solver via `scipy.linalg.svd` and select the components by postprocessing arpack : run SVD truncated to n_components calling ARPACK solver via `scipy.sparse.linalg.svds`. It requires strictly 0 < n_components < X.shape[1] randomized : run randomized SVD by the method of Halko et al. iterated_power : int >= 0, or 'auto', (default 'auto') Number of iterations for the power method computed by svd_solver == 'randomized'. """ def __init__(self, threshold=10, svd_solver='randomized' ,iterated_power='auto', random_state=42): """Create a ContinuousSelector object.""" self.threshold = threshold self.svd_solver = svd_solver self.iterated_power = iterated_power self.random_state = random_state def fit(self, X, y=None): """Do nothing and return the estimator unchanged This method is just there to implement the usual API and hence work in pipelines. Parameters ---------- X : array-like """ X = check_array(X) return self def transform(self, X): """Select continuous features and transform them using PCA. Parameters ---------- X: numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples and n_components is the number of components. Returns ------- array-like, {n_samples, n_components} """ selected = auto_select_categorical_features(X, threshold=self.threshold) _, X_sel, n_selected, _ = _X_selected(X, selected) if n_selected == 0: # No features selected. raise ValueError('No continuous feature was found!') else: pca = PCA(svd_solver=self.svd_solver, iterated_power=self.iterated_power, random_state=self.random_state) return pca.fit_transform(X_sel) ================================================ FILE: tpot/builtin_modules/genetic_encoders.py ================================================ """ Code from https://github.com/EpistasisLab/autoqtl This file contains the class definition for all the genetic encoders. All the genetic encoder classes inherit the Scikit learn BaseEstimator and TransformerMixin classes to follow the Scikit-learn paradigm. """ import numpy as np from sklearn.base import TransformerMixin, BaseEstimator from sklearn.utils import check_array class DominantEncoder(TransformerMixin, BaseEstimator ): """This class contains the function definition for encoding the input features as a Dominant genetic model. The encoding used is AA(0)->1, Aa(1)->1, aa(2)->0. """ def fit(self, X, y=None): """Do nothing and return the estimator unchanged. Dummy function to fit in with the sklearn API and hence work in pipelines. Parameters ---------- X : array-like """ return self def transform(self, X, y=None): """Transform the data by applying the Dominant encoding. Parameters ---------- X : numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples (number of individuals) and n_components is the number of components (number of features). y : None Unused Returns ------- X_transformed: numpy ndarray, {n_samples, n_components} The encoded feature set """ X = check_array(X) map = {0: 1, 1: 1, 2: 0} mapping_function = np.vectorize(lambda i: map[i] if i in map else i) X_transformed = mapping_function(X) return X_transformed class RecessiveEncoder(TransformerMixin, BaseEstimator ): """This class contains the function definition for encoding the input features as a Recessive genetic model. The encoding used is AA(0)->0, Aa(1)->1, aa(2)->1. """ def fit(self, X, y=None): """Do nothing and return the estimator unchanged. Dummy function to fit in with the sklearn API and hence work in pipelines. Parameters ---------- X : array-like """ return self def transform(self, X, y=None): """Transform the data by applying the Recessive encoding. Parameters ---------- X : numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples (number of individuals) and n_components is the number of components (number of features). y : None Unused Returns ------- X_transformed: numpy ndarray, {n_samples, n_components} The encoded feature set """ X = check_array(X) map = {0: 0, 1: 1, 2: 1} mapping_function = np.vectorize(lambda i: map[i] if i in map else i) X_transformed = mapping_function(X) return X_transformed class HeterosisEncoder(TransformerMixin, BaseEstimator ): """This class contains the function definition for encoding the input features as a Heterozygote Advantage genetic model. The encoding used is AA(0)->0, Aa(1)->1, aa(2)->0. """ def fit(self, X, y=None): """Do nothing and return the estimator unchanged. Dummy function to fit in with the sklearn API and hence work in pipelines. Parameters ---------- X : array-like """ return self def transform(self, X, y=None): """Transform the data by applying the Heterosis encoding. Parameters ---------- X : numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples (number of individuals) and n_components is the number of components (number of features). y : None Unused Returns ------- X_transformed: numpy ndarray, {n_samples, n_components} The encoded feature set """ X = check_array(X) map = {0: 0, 1: 1, 2: 0} mapping_function = np.vectorize(lambda i: map[i] if i in map else i) X_transformed = mapping_function(X) return X_transformed class UnderDominanceEncoder(TransformerMixin, BaseEstimator ): """This class contains the function definition for encoding the input features as a Under Dominance genetic model. The encoding used is AA(0)->2, Aa(1)->0, aa(2)->1. """ def fit(self, X, y=None): """Do nothing and return the estimator unchanged. Dummy function to fit in with the sklearn API and hence work in pipelines. Parameters ---------- X : array-like """ return self def transform(self, X, y=None): """Transform the data by applying the Heterosis encoding. Parameters ---------- X : numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples (number of individuals) and n_components is the number of components (number of features). y : None Unused Returns ------- X_transformed: numpy ndarray, {n_samples, n_components} The encoded feature set """ X = check_array(X) map = {0: 2, 1: 0, 2: 1} mapping_function = np.vectorize(lambda i: map[i] if i in map else i) X_transformed = mapping_function(X) return X_transformed class OverDominanceEncoder(TransformerMixin, BaseEstimator ): """This class contains the function definition for encoding the input features as a Over Dominance genetic model. The encoding used is AA(0)->1, Aa(1)->2, aa(2)->0. """ def fit(self, X, y=None): """Do nothing and return the estimator unchanged. Dummy function to fit in with the sklearn API and hence work in pipelines. Parameters ---------- X : array-like """ return self def transform(self, X, y=None): """Transform the data by applying the Heterosis encoding. Parameters ---------- X : numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples (number of individuals) and n_components is the number of components (number of features). y : None Unused Returns ------- X_transformed: numpy ndarray, {n_samples, n_components} The encoded feature set """ X = check_array(X) map = {0: 1, 1: 2, 2: 0} mapping_function = np.vectorize(lambda i: map[i] if i in map else i) X_transformed = mapping_function(X) return X_transformed ================================================ FILE: tpot/builtin_modules/imputer.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ #TODO support np arrays import numpy as np from scipy import sparse from sklearn.base import TransformerMixin, BaseEstimator from sklearn.utils import check_array from sklearn.preprocessing import OneHotEncoder import sklearn import sklearn.impute import pandas as pd from pandas.api.types import is_numeric_dtype import sklearn.compose class ColumnSimpleImputer(TransformerMixin, BaseEstimator ): def __init__(self, columns="all", missing_values=np.nan, strategy="mean", fill_value=None, copy=True, add_indicator=False, keep_empty_features=False,): """" A wrapper for SimpleImputer that allows for imputation of specific columns in a DataFrame or np array. Passes through columns that are not imputed. Parameters ---------- columns : str, list, default='all' Determines which columns to impute with sklearn.impute.SimpleImputer. - 'categorical' : Automatically select categorical features - 'numeric' : Automatically select numeric features - 'all' : Select all features - list : A list of columns to select # See documentation from sklearn.impute.SimpleImputer for the following parameters missing_values, strategy, fill_value, copy, add_indicator, keep_empty_features """ self.columns = columns self.missing_values = missing_values self.strategy = strategy self.fill_value = fill_value self.copy = copy self.add_indicator = add_indicator self.keep_empty_features = keep_empty_features def fit(self, X, y=None): if (self.columns == "categorical" or self.columns == "numeric") and not isinstance(X, pd.DataFrame): raise ValueError(f"Invalid value for columns: {self.columns}. " "Only 'all' or is supported for np arrays") if self.columns == "categorical": self.columns_ = list(X.select_dtypes(exclude='number').columns) elif self.columns == "numeric": self.columns_ = [col for col in X.columns if is_numeric_dtype(X[col])] elif self.columns == "all": if isinstance(X, pd.DataFrame): self.columns_ = X.columns else: self.columns_ = list(range(X.shape[1])) elif isinstance(self.columns, list): self.columns_ = self.columns else: raise ValueError(f"Invalid value for columns: {self.columns}") if len(self.columns_) == 0: return self self.imputer = sklearn.impute.SimpleImputer(missing_values=self.missing_values, strategy=self.strategy, fill_value=self.fill_value, copy=self.copy, add_indicator=self.add_indicator, keep_empty_features=self.keep_empty_features) if isinstance(X, pd.DataFrame): self.imputer.set_output(transform="pandas") if isinstance(X, pd.DataFrame): self.imputer.fit(X[self.columns_], y) else: self.imputer.fit(X[:, self.columns_], y) return self def transform(self, X): if len(self.columns_) == 0: return X if isinstance(X, pd.DataFrame): X = X.copy() X[self.columns_] = self.imputer.transform(X[self.columns_]) return X else: X = np.copy(X) X[:, self.columns_] = self.imputer.transform(X[:, self.columns_]) return X ================================================ FILE: tpot/builtin_modules/nn.py ================================================ # -*- coding: utf-8 -*- """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ # Note: There are quite a few pylint messages disabled in this file. In # general, this usually should be avoided. However, in some cases it is # necessary: e.g., we use `X` and `y` to refer to data and labels in compliance # with the scikit-learn API, but pylint doesn't like short variable names. # pylint: disable=redefined-outer-name # pylint: disable=not-callable from abc import abstractmethod import numpy as np from sklearn.base import BaseEstimator, ClassifierMixin from sklearn.utils.validation import check_X_y, assert_all_finite, check_array, check_is_fitted from sklearn.utils.multiclass import type_of_target try: import torch from torch import nn from torch.autograd import Variable from torch.optim import Adam from torch.utils.data import TensorDataset, DataLoader except ModuleNotFoundError: raise def _pytorch_model_is_fully_initialized(clf: BaseEstimator): if all([ hasattr(clf, 'network'), hasattr(clf, 'loss_function'), hasattr(clf, 'optimizer'), hasattr(clf, 'data_loader'), hasattr(clf, 'train_dset_len'), hasattr(clf, 'device') ]): return True else: return False def _get_cuda_device_if_available(): if torch.cuda.is_available(): return torch.device('cuda') else: return torch.device('cpu') class PytorchEstimator(BaseEstimator): """Base class for Pytorch-based estimators (currently only classifiers) for use in TPOT. In the future, these will be merged into TPOT's main code base. """ @abstractmethod def fit(self, X, y): # pragma: no cover pass @abstractmethod def transform(self, X): # pragma: no cover pass def predict(self, X): return self.transform(X) def fit_transform(self, X, y): self.fit(X, y) return self.transform(X) def set_params(self, **parameters): for parameter, value in parameters.items(): setattr(self, parameter, value) return self class PytorchClassifier(ClassifierMixin, PytorchEstimator): @abstractmethod def _init_model(self, X, y): # pragma: no cover pass def fit(self, X, y): """Generalizable method for fitting a PyTorch estimator to a training set. Parameters ---------- X : array-like of shape (n_samples, n_features) Training vector, where n_samples is the number of samples and n_features is the number of features. y : array-like of shape (n_samples,) Target vector relative to X. Returns ------- self Fitted estimator. """ self._init_model(X, y) assert _pytorch_model_is_fully_initialized(self) for epoch in range(self.num_epochs): for i, (samples, labels) in enumerate(self.data_loader): samples = samples.to(self.device) labels = labels.to(self.device) self.optimizer.zero_grad() outputs = self.network(samples) loss = self.loss_function(outputs, labels) loss.backward() self.optimizer.step() if self.verbose and ((i + 1) % 100 == 0): print( "Epoch: [%d/%d], Step: [%d/%d], Loss: %.4f" % ( epoch + 1, self.num_epochs, i + 1, self.train_dset_len // self.batch_size, loss.item(), ) ) # pylint: disable=attribute-defined-outside-init self.is_fitted_ = True return self def validate_inputs(self, X, y): # Things we don't want to allow until we've tested them: # - Sparse inputs # - Multiclass outputs (e.g., more than 2 classes in `y`) # - Non-finite inputs # - Complex inputs X, y = check_X_y(X, y, accept_sparse=False, allow_nd=False) # Throw a ValueError if X or y contains NaN or infinity. assert_all_finite(X) assert_all_finite(y) if type_of_target(y) != 'binary': raise ValueError("Non-binary targets not supported") if np.any(np.iscomplex(X)) or np.any(np.iscomplex(y)): raise ValueError("Complex data not supported") if np.issubdtype(X.dtype, np.object_) or np.issubdtype(y.dtype, np.object_): try: X = X.astype(float) y = y.astype(int) except (TypeError, ValueError): raise ValueError("argument must be a string.* number") return (X, y) def predict(self, X): X = check_array(X, accept_sparse=True) check_is_fitted(self, 'is_fitted_') X = torch.tensor(X, dtype=torch.float32).to(self.device) predictions = np.empty(len(X), dtype=int) for i, rows in enumerate(X): rows = Variable(rows.view(-1, self.input_size)) outputs = self.network(rows) _, predicted = torch.max(outputs.data, 1) predictions[i] = int(predicted) return predictions.reshape(-1, 1) def transform(self, X): return self.predict(X) class _LR(nn.Module): # pylint: disable=arguments-differ def __init__(self, input_size, num_classes): super(_LR, self).__init__() self.linear = nn.Linear(input_size, num_classes) def forward(self, x): out = self.linear(x) return out class _MLP(nn.Module): # pylint: disable=arguments-differ def __init__(self, input_size, num_classes): super(_MLP, self).__init__() self.hidden_size = round((input_size+num_classes)/2) self.fc1 = nn.Linear(input_size, self.hidden_size) self.relu = nn.Tanh() self.fc2 = nn.Linear(self.hidden_size, num_classes) def forward(self, x): hidden = self.fc1(x) r1 = self.relu(hidden) out = self.fc2(r1) return out class PytorchLRClassifier(PytorchClassifier): """Logistic Regression classifier, implemented in PyTorch, for use with TPOT. For examples on standalone use (i.e., non-TPOT) refer to: https://github.com/trang1618/tpot-nn/blob/master/tpot_nn/estimator_sandbox.py """ def __init__( self, num_epochs=10, batch_size=16, learning_rate=0.02, weight_decay=1e-4, verbose=False ): self.num_epochs = num_epochs self.batch_size = batch_size self.learning_rate = learning_rate self.weight_decay = weight_decay self.verbose = verbose self.input_size = None self.num_classes = None self.network = None self.loss_function = None self.optimizer = None self.data_loader = None self.train_dset_len = None self.device = None def _init_model(self, X, y): device = _get_cuda_device_if_available() X, y = self.validate_inputs(X, y) self.input_size = X.shape[-1] self.num_classes = len(set(y)) X = torch.tensor(X, dtype=torch.float32) y = torch.tensor(y, dtype=torch.long) train_dset = TensorDataset(X, y) # Set parameters of the network self.network = _LR(self.input_size, self.num_classes).to(device) self.loss_function = nn.CrossEntropyLoss() self.optimizer = Adam(self.network.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay) self.data_loader = DataLoader( train_dset, batch_size=self.batch_size, shuffle=True, num_workers=2 ) self.train_dset_len = len(train_dset) self.device = device def __sklearn_tags__(self): tags = super().__sklearn_tags__() tags.non_deterministic = True tags.target_tags.single_output = True return tags class PytorchMLPClassifier(PytorchClassifier): """Multilayer Perceptron, implemented in PyTorch, for use with TPOT. """ def __init__( self, num_epochs=10, batch_size=8, learning_rate=0.01, weight_decay=0, verbose=False ): self.num_epochs = num_epochs self.batch_size = batch_size self.learning_rate = learning_rate self.weight_decay = weight_decay self.verbose = verbose self.input_size = None self.num_classes = None self.network = None self.loss_function = None self.optimizer = None self.data_loader = None self.train_dset_len = None self.device = None def _init_model(self, X, y): device = _get_cuda_device_if_available() X, y = self.validate_inputs(X, y) self.input_size = X.shape[-1] self.num_classes = len(set(y)) X = torch.tensor(X, dtype=torch.float32) y = torch.tensor(y, dtype=torch.long) train_dset = TensorDataset(X, y) # Set parameters of the network self.network = _MLP(self.input_size, self.num_classes).to(device) self.loss_function = nn.CrossEntropyLoss() self.optimizer = Adam(self.network.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay) self.data_loader = DataLoader( train_dset, batch_size=self.batch_size, shuffle=True, num_workers=2 ) self.train_dset_len = len(train_dset) self.device = device def __sklearn_tags__(self): tags = super().__sklearn_tags__() tags.non_deterministic = True tags.target_tags.single_output = True return tags ================================================ FILE: tpot/builtin_modules/passkbinsdiscretizer.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import pandas as pd from sklearn.base import TransformerMixin, BaseEstimator from sklearn.compose import ColumnTransformer from sklearn.preprocessing import KBinsDiscretizer import numpy as np def select_features(X, min_unique=10,): """ Given a DataFrame or numpy array, return a list of column indices that have more than min_unique unique values. Parameters ---------- X: DataFrame or numpy array Data to select features from min_unique: int, default=10 Minimum number of unique values a column must have to be selected Returns ------- list List of column indices that have more than min_unique unique values """ if isinstance(X, pd.DataFrame): return [col for col in X.columns if len(X[col].unique()) > min_unique] else: return [i for i in range(X.shape[1]) if len(np.unique(X[:, i])) > min_unique] class PassKBinsDiscretizer(TransformerMixin, BaseEstimator ): def __init__(self, n_bins=5, encode='onehot-dense', strategy='quantile', subsample=None, random_state=None): self.n_bins = n_bins self.encode = encode self.strategy = strategy self.subsample = subsample self.random_state = random_state """ Same as sklearn.preprocessing.KBinsDiscretizer, but passes through columns that are not discretized due to having fewer than n_bins unique values instead of ignoring them. See sklearn.preprocessing.KBinsDiscretizer for more information. """ def fit(self, X, y=None): # Identify columns with more than n unique values # Create a ColumnTransformer to select and discretize the chosen columns self.selected_columns_ = select_features(X, min_unique=10) if isinstance(X, pd.DataFrame): self.not_selected_columns_ = [col for col in X.columns if col not in self.selected_columns_] else: self.not_selected_columns_ = [i for i in range(X.shape[1]) if i not in self.selected_columns_] enc = KBinsDiscretizer(n_bins=self.n_bins, encode=self.encode, strategy=self.strategy, subsample=self.subsample, random_state=self.random_state) self.transformer = ColumnTransformer([ ('discretizer', enc, self.selected_columns_), ('passthrough', 'passthrough', self.not_selected_columns_) ]) self.transformer.fit(X) return self def transform(self, X): return self.transformer.transform(X) ================================================ FILE: tpot/builtin_modules/passthrough.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from sklearn.base import TransformerMixin, BaseEstimator import numpy as np class Passthrough(TransformerMixin,BaseEstimator): """ A transformer that does nothing. It just passes the input array as is. """ def fit(self, X=None, y=None): """ Nothing to fit, just returns self. """ return self def transform(self, X): """ returns the input array as is. """ return X class SkipTransformer(TransformerMixin,BaseEstimator): """ A transformer returns an empty array. When combined with FeatureUnion, it can be used to skip a branch. """ def fit(self, X=None, y=None): """ Nothing to fit, just returns self. """ return self def transform(self, X): """ returns an empty array. """ return np.array([]).reshape(X.shape[0],0) ================================================ FILE: tpot/builtin_modules/tests/feature_set_selector_tests.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np import pandas as pd from tpot.config.custom_modules import FeatureSetSelector from nose.tools import assert_raises test_data = pd.read_csv("tests/tests.csv") test_X = test_data.drop("class", axis=1) def test_FeatureSetSelector_1(): """Assert that the StackingEstimator returns transformed X based on test feature list 1.""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset="test_subset_1") ds.fit(test_X, y=None) transformed_X = ds.transform(test_X) assert transformed_X.shape[0] == test_X.shape[0] assert transformed_X.shape[1] != test_X.shape[1] assert transformed_X.shape[1] == 5 assert np.array_equal(transformed_X, test_X[ds.feat_list].values) def test_FeatureSetSelector_2(): """Assert that the StackingEstimator returns transformed X based on test feature list 2.""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset="test_subset_2") ds.fit(test_X, y=None) transformed_X = ds.transform(test_X) assert transformed_X.shape[0] == test_X.shape[0] assert transformed_X.shape[1] != test_X.shape[1] assert transformed_X.shape[1] == 6 assert np.array_equal(transformed_X, test_X[ds.feat_list].values) def test_FeatureSetSelector_3(): """Assert that the StackingEstimator returns transformed X based on 2 subsets' names""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset=["test_subset_1", "test_subset_2"]) ds.fit(test_X, y=None) transformed_X = ds.transform(test_X) assert transformed_X.shape[0] == test_X.shape[0] assert transformed_X.shape[1] != test_X.shape[1] assert transformed_X.shape[1] == 7 assert np.array_equal(transformed_X, test_X[ds.feat_list].values) def test_FeatureSetSelector_4(): """Assert that the StackingEstimator returns transformed X based on 2 subsets' indexs""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset=[0, 1]) ds.fit(test_X, y=None) transformed_X = ds.transform(test_X) assert transformed_X.shape[0] == test_X.shape[0] assert transformed_X.shape[1] != test_X.shape[1] assert transformed_X.shape[1] == 7 assert np.array_equal(transformed_X, test_X[ds.feat_list].values) def test_FeatureSetSelector_5(): """Assert that the StackingEstimator returns transformed X seleced based on test feature list 1's index.""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset=0) ds.fit(test_X, y=None) transformed_X = ds.transform(test_X) assert transformed_X.shape[0] == test_X.shape[0] assert transformed_X.shape[1] != test_X.shape[1] assert transformed_X.shape[1] == 5 assert np.array_equal(transformed_X, test_X[ds.feat_list].values) def test_FeatureSetSelector_6(): """Assert that the _get_support_mask function returns correct mask.""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset="test_subset_1") ds.fit(test_X, y=None) mask = ds._get_support_mask() get_mask = ds.get_support() assert mask.shape[0] == 30 assert np.count_nonzero(mask) == 5 assert np.array_equal(get_mask, mask) def test_FeatureSetSelector_7(): """Assert that the StackingEstimator works as expected when input X is np.array.""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset="test_subset_1") ds.fit(test_X.values, y=None) transformed_X = ds.transform(test_X.values) str_feat_list = [str(i+2) for i in ds.feat_list_idx] assert transformed_X.shape[0] == test_X.shape[0] assert transformed_X.shape[1] != test_X.shape[1] assert transformed_X.shape[1] == 5 assert np.array_equal(transformed_X, test_X.values[:, ds.feat_list_idx]) assert np.array_equal(transformed_X, test_X[str_feat_list].values) def test_FeatureSetSelector_8(): """Assert that the StackingEstimator rasies ValueError when features are not available.""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset="test_subset_4") assert_raises(ValueError, ds.fit, test_X) def test_FeatureSetSelector_9(): """Assert that the StackingEstimator __name__ returns correct class name.""" ds = FeatureSetSelector(subset_list="tests/subset_test.csv", sel_subset="test_subset_4") assert ds.__name__ == 'FeatureSetSelector' ================================================ FILE: tpot/builtin_modules/zero_count.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np from sklearn.base import TransformerMixin, BaseEstimator from sklearn.utils import check_array class ZeroCount(TransformerMixin, BaseEstimator ): """Adds the count of zeros and count of non-zeros per sample as features.""" def fit(self, X, y=None): """Dummy function to fit in with the sklearn API.""" return self def transform(self, X, y=None): """Transform data by adding two virtual features. Parameters ---------- X: numpy ndarray, {n_samples, n_components} New data, where n_samples is the number of samples and n_components is the number of components. y: None Unused Returns ------- X_transformed: array-like, shape (n_samples, n_features) The transformed feature set """ X = check_array(X) n_features = X.shape[1] X_transformed = np.copy(X) non_zero_vector = np.count_nonzero(X_transformed, axis=1) non_zero = np.reshape(non_zero_vector, (-1, 1)) zero_col = np.reshape(n_features - non_zero_vector, (-1, 1)) X_transformed = np.hstack((non_zero, X_transformed)) X_transformed = np.hstack((zero_col, X_transformed)) return X_transformed ================================================ FILE: tpot/config/__init__.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from .get_configspace import get_search_space ================================================ FILE: tpot/config/autoqtl_builtins.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from tpot.builtin_modules import genetic_encoders from tpot.builtin_modules import feature_encoding_frequency_selector import sklearn import numpy as np from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal FeatureEncodingFrequencySelector_ConfigurationSpace = ConfigurationSpace( space = { 'threshold': Float("threshold", bounds=(0, .35)) } ) # genetic_encoders.DominantEncoder : {}, # genetic_encoders.RecessiveEncoder : {}, # genetic_encoders.HeterosisEncoder : {}, # genetic_encoders.UnderDominanceEncoder : {}, # genetic_encoders.OverDominanceEncoder : {}, ================================================ FILE: tpot/config/classifiers.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal from ConfigSpace import EqualsCondition, OrConjunction, NotEqualsCondition, InCondition import numpy as np import sklearn def get_LogisticRegression_ConfigurationSpace(random_state, n_jobs=1): dual = False space = {"solver":"saga", "max_iter":1000, "n_jobs":n_jobs, "dual":dual, } penalty = Categorical('penalty', ['l1', 'l2',"elasticnet"], default='l2') C = Float('C', (0.01, 1e5), log=True) l1_ratio = Float('l1_ratio', (0.0, 1.0)) class_weight = Categorical('class_weight', [None, 'balanced']) l1_ratio_condition = EqualsCondition(l1_ratio, penalty, 'elasticnet') if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace(space) cs.add([penalty, C, l1_ratio, class_weight]) cs.add([l1_ratio_condition]) return cs def get_KNeighborsClassifier_ConfigurationSpace(n_samples, n_jobs=1): return ConfigurationSpace( space = { 'n_neighbors': Integer("n_neighbors", bounds=(1, min(100,n_samples)), log=True), 'weights': Categorical("weights", ['uniform', 'distance']), 'p': Integer("p", bounds=(1, 3)), 'n_jobs': n_jobs, } ) def get_BaggingClassifier_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': Integer("n_estimators", bounds=(3, 100)), 'max_samples': Float("max_samples", bounds=(0.1, 1.0)), 'max_features': Float("max_features", bounds=(0.1, 1.0)), 'bootstrap_features': Categorical("bootstrap_features", [True, False]), 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state bootstrap = Categorical("bootstrap", [True, False]) oob_score = Categorical("oob_score", [True, False]) oob_condition = EqualsCondition(oob_score, bootstrap, True) cs = ConfigurationSpace( space = space ) cs.add([bootstrap, oob_score]) cs.add([oob_condition]) return cs def get_DecisionTreeClassifier_ConfigurationSpace(n_featues, random_state): space = { 'criterion': Categorical("criterion", ['gini', 'entropy']), 'max_depth': Integer("max_depth", bounds=(1, min(20,2*n_featues))), #max of 20? log scale? 'min_samples_split': Integer("min_samples_split", bounds=(2, 20)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 20)), 'max_features': Categorical("max_features", [None, 'sqrt', 'log2']), 'min_weight_fraction_leaf': 0.0, 'class_weight' : Categorical('class_weight', [None, 'balanced']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) #TODO Does not support predict_proba def get_LinearSVC_ConfigurationSpace(random_state): space = {"dual":"auto"} penalty = Categorical('penalty', ['l1', 'l2']) C = Float('C', (0.01, 1e5), log=True) loss = Categorical('loss', ['hinge', 'squared_hinge']) loss_condition = EqualsCondition(loss, penalty, 'l2') if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace(space) cs.add([penalty, C, loss]) cs.add([loss_condition]) return cs def get_SVC_ConfigurationSpace(random_state): space = { 'max_iter': 3000, 'probability':True} kernel = Categorical("kernel", ['poly', 'rbf', 'sigmoid', 'linear']) C = Float('C', (0.01, 1e5), log=True) degree = Integer("degree", bounds=(1, 5)) gamma = Float("gamma", bounds=(1e-5, 8), log=True) shrinking = Categorical("shrinking", [True, False]) coef0 = Float("coef0", bounds=(-1, 1)) class_weight = Categorical('class_weight', [None, 'balanced']) degree_condition = EqualsCondition(degree, kernel, 'poly') gamma_condition = InCondition(gamma, kernel, ['rbf', 'poly']) coef0_condition = InCondition(coef0, kernel, ['poly', 'sigmoid']) if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace(space) cs.add([kernel, C, coef0, degree, gamma, shrinking, class_weight]) cs.add([degree_condition, gamma_condition, coef0_condition]) return cs def get_RandomForestClassifier_ConfigurationSpace( random_state, n_jobs=1): space = { 'n_estimators': 128, #as recommended by Oshiro et al. (2012 'max_features': Float("max_features", bounds=(0.01,1), log=True), #log scale like autosklearn? 'criterion': Categorical("criterion", ['gini', 'entropy']), 'min_samples_split': Integer("min_samples_split", bounds=(2, 20)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 20)), 'bootstrap': Categorical("bootstrap", [True, False]), 'class_weight': Categorical("class_weight", [None, 'balanced']), 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_XGBClassifier_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': 100, 'learning_rate': Float("learning_rate", bounds=(1e-3, 1), log=True), 'subsample': Float("subsample", bounds=(0.5, 1.0)), 'min_child_weight': Integer("min_child_weight", bounds=(1, 21)), 'gamma': Float("gamma", bounds=(1e-4, 20), log=True), 'max_depth': Integer("max_depth", bounds=(3, 18)), 'reg_alpha': Float("reg_alpha", bounds=(1e-4, 100), log=True), 'reg_lambda': Float("reg_lambda", bounds=(1e-4, 1), log=True), 'n_jobs': n_jobs, 'nthread': 1, 'verbosity': 0, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_LGBMClassifier_ConfigurationSpace(random_state, n_jobs=1): space = { 'boosting_type': Categorical("boosting_type", ['gbdt', 'dart', 'goss']), 'num_leaves': Integer("num_leaves", bounds=(2, 256)), 'max_depth': Integer("max_depth", bounds=(1, 10)), 'n_estimators': Integer("n_estimators", bounds=(10, 100)), 'class_weight': Categorical("class_weight", [None, 'balanced']), 'verbose':-1, 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space=space ) def get_ExtraTreesClassifier_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': 100, 'criterion': Categorical("criterion", ["gini", "entropy"]), 'max_features': Float("max_features", bounds=(0.01, 1.00)), 'min_samples_split': Integer("min_samples_split", bounds=(2, 20)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 20)), 'bootstrap': Categorical("bootstrap", [True, False]), 'class_weight': Categorical("class_weight", [None, 'balanced']), 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_SGDClassifier_ConfigurationSpace(random_state, n_jobs=1): space = { 'loss': Categorical("loss", ['modified_huber']), #don't include hinge because we have LinearSVC, don't include log because we have LogisticRegression. TODO 'squared_hinge'? doesn't support predict proba 'penalty': 'elasticnet', 'alpha': Float("alpha", bounds=(1e-5, 0.01), log=True), 'l1_ratio': Float("l1_ratio", bounds=(0.0, 1.0)), 'eta0': Float("eta0", bounds=(0.01, 1.0)), 'n_jobs': n_jobs, 'fit_intercept': Categorical("fit_intercept", [True]), 'class_weight': Categorical("class_weight", [None, 'balanced']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state power_t = Float("power_t", bounds=(1e-5, 100.0), log=True) learning_rate = Categorical("learning_rate", ['invscaling', 'constant', "optimal"]) powertcond = EqualsCondition(power_t, learning_rate, 'invscaling') cs = ConfigurationSpace( space = space ) cs.add([power_t, learning_rate]) cs.add([powertcond]) return cs GaussianNB_ConfigurationSpace = {} def get_BernoulliNB_ConfigurationSpace(): return ConfigurationSpace( space = { 'alpha': Float("alpha", bounds=(1e-2, 100), log=True), 'fit_prior': Categorical("fit_prior", [True, False]), } ) def get_MultinomialNB_ConfigurationSpace(): return ConfigurationSpace( space = { 'alpha': Float("alpha", bounds=(1e-3, 100), log=True), 'fit_prior': Categorical("fit_prior", [True, False]), } ) def get_AdaBoostClassifier_ConfigurationSpace(random_state): space = { 'n_estimators': Integer("n_estimators", bounds=(50, 500)), 'learning_rate': Float("learning_rate", bounds=(0.01, 2), log=True), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_QuadraticDiscriminantAnalysis_ConfigurationSpace(): return ConfigurationSpace( space = { 'reg_param': Float("reg_param", bounds=(0, 1)), } ) def get_PassiveAggressiveClassifier_ConfigurationSpace(random_state): space = { 'C': Float("C", bounds=(1e-5, 10), log=True), 'loss': Categorical("loss", ['hinge', 'squared_hinge']), 'average': Categorical("average", [True, False]), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) #TODO support auto shrinkage when solver is svd. may require custom node def get_LinearDiscriminantAnalysis_ConfigurationSpace(): solver = Categorical("solver", ['svd', 'lsqr', 'eigen']) shrinkage = Float("shrinkage", bounds=(0, 1)) shrinkcond = NotEqualsCondition(shrinkage, solver, 'svd') cs = ConfigurationSpace() cs.add([solver, shrinkage]) cs.add([shrinkcond]) return cs #### Gradient Boosting Classifiers def get_GradientBoostingClassifier_ConfigurationSpace(n_classes, random_state): early_stop = Categorical("early_stop", ["off", "valid", "train"]) n_iter_no_change = Integer("n_iter_no_change",bounds=(1,20)) validation_fraction = Float("validation_fraction", bounds=(0.01, 0.4)) n_iter_no_change_cond = InCondition(n_iter_no_change, early_stop, ["valid", "train"] ) validation_fraction_cond = EqualsCondition(validation_fraction, early_stop, "valid") space = { 'learning_rate': Float("learning_rate", bounds=(1e-3, 1), log=True), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 200)), 'min_samples_split': Integer("min_samples_split", bounds=(2, 20)), 'subsample': Float("subsample", bounds=(0.1, 1.0)), 'max_features': Float("max_features", bounds=(0.01, 1.00)), 'max_leaf_nodes': Integer("max_leaf_nodes", bounds=(3, 2047)), 'max_depth':None, # 'max_depth': Integer("max_depth", bounds=(1, 2*n_features)), 'tol': 1e-4, } if n_classes == 2: space['loss']= Categorical("loss", ['log_loss', 'exponential']) else: space['loss'] = "log_loss" if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace( space = space ) cs.add([n_iter_no_change, validation_fraction, early_stop ]) cs.add([validation_fraction_cond, n_iter_no_change_cond]) return cs def GradientBoostingClassifier_hyperparameter_parser(params): final_params = { 'loss': params['loss'], 'learning_rate': params['learning_rate'], 'min_samples_leaf': params['min_samples_leaf'], 'min_samples_split': params['min_samples_split'], 'max_features': params['max_features'], 'max_leaf_nodes': params['max_leaf_nodes'], 'max_depth': params['max_depth'], 'tol': params['tol'], 'subsample': params['subsample'] } if 'random_state' in params: final_params['random_state'] = params['random_state'] if params['early_stop'] == 'off': final_params['n_iter_no_change'] = None final_params['validation_fraction'] = None elif params['early_stop'] == 'valid': #this is required because in crossover, its possible that n_iter_no_change is not in the params if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 else: final_params['n_iter_no_change'] = params['n_iter_no_change'] if 'validation_fraction' not in params: final_params['validation_fraction'] = 0.1 else: final_params['validation_fraction'] = params['validation_fraction'] elif params['early_stop'] == 'train': if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 final_params['validation_fraction'] = None return final_params #only difference is l2_regularization def get_HistGradientBoostingClassifier_ConfigurationSpace(random_state): early_stop = Categorical("early_stop", ["off", "valid", "train"]) n_iter_no_change = Integer("n_iter_no_change",bounds=(1,20)) validation_fraction = Float("validation_fraction", bounds=(0.01, 0.4)) n_iter_no_change_cond = InCondition(n_iter_no_change, early_stop, ["valid", "train"] ) validation_fraction_cond = EqualsCondition(validation_fraction, early_stop, "valid") space = { 'learning_rate': Float("learning_rate", bounds=(1e-3, 1), log=True), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 200)), 'max_features': Float("max_features", bounds=(0.1,1.0)), 'max_leaf_nodes': Integer("max_leaf_nodes", bounds=(3, 2047)), 'max_depth':None, # 'max_depth': Integer("max_depth", bounds=(1, 2*n_features)), 'l2_regularization': Float("l2_regularization", bounds=(1e-10, 1), log=True), 'tol': 1e-4, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace( space = space ) cs.add([n_iter_no_change, validation_fraction, early_stop ]) cs.add([validation_fraction_cond, n_iter_no_change_cond]) return cs def HistGradientBoostingClassifier_hyperparameter_parser(params): final_params = { 'learning_rate': params['learning_rate'], 'min_samples_leaf': params['min_samples_leaf'], 'max_features': params['max_features'], 'max_leaf_nodes': params['max_leaf_nodes'], 'max_depth': params['max_depth'], 'tol': params['tol'], 'l2_regularization': params['l2_regularization'] } if 'random_state' in params: final_params['random_state'] = params['random_state'] if params['early_stop'] == 'off': # final_params['n_iter_no_change'] = 0 final_params['validation_fraction'] = None final_params['early_stopping'] = False elif params['early_stop'] == 'valid': #this is required because in crossover, its possible that n_iter_no_change is not in the params if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 else: final_params['n_iter_no_change'] = params['n_iter_no_change'] if 'validation_fraction' not in params: final_params['validation_fraction'] = 0.1 else: final_params['validation_fraction'] = params['validation_fraction'] final_params['early_stopping'] = True elif params['early_stop'] == 'train': if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 else: final_params['n_iter_no_change'] = params['n_iter_no_change'] final_params['validation_fraction'] = None final_params['early_stopping'] = True return final_params ### def get_MLPClassifier_ConfigurationSpace(random_state): space = {"n_iter_no_change":32} if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace( space = space ) n_hidden_layers = Integer("n_hidden_layers", bounds=(1, 3)) n_nodes_per_layer = Integer("n_nodes_per_layer", bounds=(16, 512)) activation = Categorical("activation", ["identity", "logistic",'tanh', 'relu']) alpha = Float("alpha", bounds=(1e-4, 1e-1), log=True) early_stopping = Categorical("early_stopping", [True,False]) learning_rate_init = Float("learning_rate_init", bounds=(1e-4, 1e-1), log=True) learning_rate = Categorical("learning_rate", ['constant', 'invscaling', 'adaptive']) cs.add([n_hidden_layers, n_nodes_per_layer, activation, alpha, learning_rate, early_stopping, learning_rate_init]) return cs def MLPClassifier_hyperparameter_parser(params): hyperparameters = { 'n_iter_no_change': params['n_iter_no_change'], 'hidden_layer_sizes' : [params['n_nodes_per_layer']]*params['n_hidden_layers'], 'activation': params['activation'], 'alpha': params['alpha'], 'early_stopping': params['early_stopping'], 'learning_rate_init': params['learning_rate_init'], 'learning_rate': params['learning_rate'], } if 'random_state' in params: hyperparameters['random_state'] = params['random_state'] return hyperparameters ### ### def get_GaussianProcessClassifier_ConfigurationSpace(n_features, random_state): space = { 'n_features': n_features, 'alpha': Float("alpha", bounds=(1e-10, 1.0), log=True), 'thetaL': Float("thetaL", bounds=(1e-10, 1e-3), log=True), 'thetaU': Float("thetaU", bounds=(1.0, 100000), log=True), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def GaussianProcessClassifier_hyperparameter_parser(params): kernel = sklearn.gaussian_process.kernels.RBF( length_scale = [1.0]*params['n_features'], length_scale_bounds=[(params['thetaL'], params['thetaU'])] * params['n_features'], ) final_params = {"kernel": kernel, "n_restarts_optimizer": 10, "optimizer": "fmin_l_bfgs_b", "copy_X_train": True, } if "random_state" in params: final_params['random_state'] = params['random_state'] return final_params ================================================ FILE: tpot/config/classifiers_sklearnex.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal def get_RandomForestClassifier_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': 100, #TODO make this a higher number? learned? 'bootstrap': Categorical("bootstrap", [True, False]), 'min_samples_split': Integer("min_samples_split", bounds=(2, 20)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 20)), 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_KNeighborsClassifier_ConfigurationSpace(n_samples): return ConfigurationSpace( space = { 'n_neighbors': Integer("n_neighbors", bounds=(1, max(n_samples, 100)), log=True), 'weights': Categorical("weights", ['uniform', 'distance']), } ) #TODO add conditionals def get_LogisticRegression_ConfigurationSpace(random_state): space = { 'solver': Categorical("solver", ['liblinear', 'sag', 'saga']), 'penalty': Categorical("penalty", ['l1', 'l2']), 'dual': Categorical("dual", [True, False]), 'C': Float("C", bounds=(1e-4, 1e4), log=True), 'max_iter': 1000, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_SVC_ConfigurationSpace(random_state): space = { 'kernel': Categorical("kernel", ['poly', 'rbf', 'linear', 'sigmoid']), 'C': Float("C", bounds=(1e-4, 25), log=True), 'degree': Integer("degree", bounds=(1, 4)), 'max_iter': 3000, 'tol': 0.001, 'probability': Categorical("probability", [True]), # configspace doesn't allow bools as a default value? but does allow them as a value inside a Categorical } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_NuSVC_ConfigurationSpace(random_state): space = { 'nu': Float("nu", bounds=(0.05, 1.0)), 'kernel': Categorical("kernel", ['poly', 'rbf', 'linear', 'sigmoid']), #'C': Float("C", bounds=(1e-4, 25), log=True), 'degree': Integer("degree", bounds=(1, 4)), 'class_weight': Categorical("class_weight", [None, 'balanced']), 'max_iter': 3000, 'tol': 0.005, 'probability': Categorical("probability", [True]), # configspace doesn't allow bools as a default value? but does allow them as a value inside a Categorical } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) ================================================ FILE: tpot/config/get_configspace.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import importlib.util import sys import numpy as np import warnings import importlib.util from ..search_spaces.nodes import EstimatorNode from ..search_spaces.pipelines import ChoicePipeline, WrapperPipeline from . import classifiers from . import transformers from . import selectors from . import regressors from . import autoqtl_builtins from . import imputers from . import mdr_configs from . import special_configs from . import classifiers_sklearnex from . import regressors_sklearnex from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal #autoqtl_builtins from tpot.builtin_modules import genetic_encoders, feature_encoding_frequency_selector from tpot.builtin_modules import AddTransformer, mul_neg_1_Transformer, MulTransformer, SafeReciprocalTransformer, EQTransformer, NETransformer, GETransformer, GTTransformer, LETransformer, LTTransformer, MinTransformer, MaxTransformer, ZeroTransformer, OneTransformer, NTransformer from tpot.builtin_modules.genetic_encoders import DominantEncoder, RecessiveEncoder, HeterosisEncoder, UnderDominanceEncoder, OverDominanceEncoder from tpot.builtin_modules import ZeroCount, ColumnOneHotEncoder, ColumnOrdinalEncoder, PassKBinsDiscretizer from tpot.builtin_modules import Passthrough, SkipTransformer from sklearn.linear_model import SGDClassifier, LogisticRegression, SGDRegressor, Ridge, Lasso, ElasticNet, Lars, LassoLars, LassoLarsCV, RidgeCV, ElasticNetCV, PassiveAggressiveClassifier, ARDRegression from sklearn.ensemble import BaggingClassifier, RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier, ExtraTreesRegressor, ExtraTreesClassifier, AdaBoostRegressor, AdaBoostClassifier, GradientBoostingRegressor,RandomForestRegressor, BaggingRegressor, ExtraTreesRegressor, HistGradientBoostingClassifier, HistGradientBoostingRegressor from sklearn.neural_network import MLPClassifier, MLPRegressor from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor from xgboost import XGBClassifier, XGBRegressor from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor from sklearn.svm import SVC, SVR, LinearSVR, LinearSVC from lightgbm import LGBMClassifier, LGBMRegressor import sklearn import sklearn.calibration as calibration from sklearn.naive_bayes import GaussianNB, BernoulliNB, MultinomialNB from sklearn.decomposition import FastICA, PCA from sklearn.cluster import FeatureAgglomeration from sklearn.kernel_approximation import Nystroem, RBFSampler from sklearn.preprocessing import StandardScaler, PowerTransformer, QuantileTransformer, RobustScaler, PolynomialFeatures, Normalizer, MinMaxScaler, MaxAbsScaler, Binarizer, KBinsDiscretizer from sklearn.feature_selection import SelectFwe, SelectPercentile, VarianceThreshold, RFE, SelectFromModel from sklearn.feature_selection import f_classif, f_regression #TODO create a selectomixin using these? from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis from sklearn.gaussian_process import GaussianProcessRegressor, GaussianProcessClassifier from sklearn.experimental import enable_iterative_imputer from sklearn.impute import SimpleImputer, IterativeImputer, KNNImputer import sklearn.calibration import importlib.util # Check if skrebate is installed is_skrebate_installed = importlib.util.find_spec("skrebate") is not None # Check if sklearnx is installed is_sklearnx_installed = importlib.util.find_spec("sklearnx") is not None all_methods = [SGDClassifier, RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier, MLPClassifier, DecisionTreeClassifier, XGBClassifier, KNeighborsClassifier, SVC, LogisticRegression, LGBMClassifier, LinearSVC, GaussianNB, BernoulliNB, MultinomialNB, ExtraTreesRegressor, RandomForestRegressor, GradientBoostingRegressor, BaggingRegressor, DecisionTreeRegressor, KNeighborsRegressor, XGBRegressor, ZeroCount, ColumnOneHotEncoder, ColumnOrdinalEncoder, Binarizer, FastICA, FeatureAgglomeration, MaxAbsScaler, MinMaxScaler, Normalizer, Nystroem, PCA, PolynomialFeatures, RBFSampler, RobustScaler, StandardScaler, SelectFwe, SelectPercentile, VarianceThreshold, SGDRegressor, Ridge, Lasso, ElasticNet, Lars, LassoLars, LassoLarsCV, RidgeCV, SVR, LinearSVR, AdaBoostRegressor, GradientBoostingRegressor, RandomForestRegressor, BaggingRegressor, ExtraTreesRegressor, DecisionTreeRegressor, KNeighborsRegressor, ElasticNetCV, AdaBoostClassifier,MLPRegressor, GaussianProcessRegressor, HistGradientBoostingClassifier, HistGradientBoostingRegressor, AddTransformer, mul_neg_1_Transformer, MulTransformer, SafeReciprocalTransformer, EQTransformer, NETransformer, GETransformer, GTTransformer, LETransformer, LTTransformer, MinTransformer, MaxTransformer, ZeroTransformer, OneTransformer, NTransformer, PowerTransformer, QuantileTransformer,ARDRegression, QuadraticDiscriminantAnalysis, PassiveAggressiveClassifier, LinearDiscriminantAnalysis, DominantEncoder, RecessiveEncoder, HeterosisEncoder, UnderDominanceEncoder, OverDominanceEncoder, GaussianProcessClassifier, BaggingClassifier,LGBMRegressor, Passthrough,SkipTransformer, PassKBinsDiscretizer, SimpleImputer, IterativeImputer, KNNImputer, KBinsDiscretizer, ] #if mdr is installed if importlib.util.find_spec('mdr') is not None: from mdr import MDR, ContinuousMDR all_methods.append(MDR) all_methods.append(ContinuousMDR) if importlib.util.find_spec('skrebate') is not None: from skrebate import ReliefF, SURF, SURFstar, MultiSURF all_methods.append(ReliefF) all_methods.append(SURF) all_methods.append(SURFstar) all_methods.append(MultiSURF) STRING_TO_CLASS = { t.__name__: t for t in all_methods } if importlib.util.find_spec('sklearnex') is not None: import sklearnex import sklearnex.linear_model import sklearnex.svm import sklearnex.ensemble import sklearnex.neighbors sklearnex_methods = [] sklearnex_methods.append(sklearnex.linear_model.LinearRegression) sklearnex_methods.append(sklearnex.linear_model.Ridge) sklearnex_methods.append(sklearnex.linear_model.Lasso) sklearnex_methods.append(sklearnex.linear_model.ElasticNet) sklearnex_methods.append(sklearnex.svm.SVR) sklearnex_methods.append(sklearnex.svm.NuSVR) sklearnex_methods.append(sklearnex.ensemble.RandomForestRegressor) sklearnex_methods.append(sklearnex.neighbors.KNeighborsRegressor) sklearnex_methods.append(sklearnex.ensemble.RandomForestClassifier) sklearnex_methods.append(sklearnex.neighbors.KNeighborsClassifier) sklearnex_methods.append(sklearnex.svm.SVC) sklearnex_methods.append(sklearnex.svm.NuSVC) sklearnex_methods.append(sklearnex.linear_model.LogisticRegression) STRING_TO_CLASS.update({f"{t.__name__}_sklearnex": t for t in sklearnex_methods}) # not including "PassiveAggressiveClassifier" in classifiers since it is mainly for larger than memory datasets/online use cases # TODO need to subclass "GaussianProcessClassifier" and 'GaussianProcessRegressor'. These require n_features as a parameter for the kernel, but n_features may be different depending on selection functions or transformations previously in the pipeline. GROUPNAMES = { "selectors": ["SelectFwe", "SelectPercentile", "VarianceThreshold",], "selectors_classification": ["SelectFwe", "SelectPercentile", "VarianceThreshold", "RFE_classification", "SelectFromModel_classification"], "selectors_regression": ["SelectFwe", "SelectPercentile", "VarianceThreshold", "RFE_regression", "SelectFromModel_regression"], "classifiers" : ["LGBMClassifier", "BaggingClassifier", 'AdaBoostClassifier', 'BernoulliNB', 'DecisionTreeClassifier', 'ExtraTreesClassifier', 'GaussianNB', 'HistGradientBoostingClassifier', 'KNeighborsClassifier','LinearDiscriminantAnalysis', 'LogisticRegression', 'MLPClassifier', 'MultinomialNB', "QuadraticDiscriminantAnalysis", 'RandomForestClassifier', 'SGDClassifier', 'XGBClassifier'], "regressors" : ["LGBMRegressor", 'AdaBoostRegressor', "ARDRegression", 'DecisionTreeRegressor', 'ExtraTreesRegressor', 'HistGradientBoostingRegressor', 'KNeighborsRegressor', 'LinearSVR', "MLPRegressor", 'RandomForestRegressor', 'SGDRegressor', 'XGBRegressor'], "transformers": ["KBinsDiscretizer", "Binarizer", "PCA", "ZeroCount", "FastICA", "FeatureAgglomeration", "Nystroem", "RBFSampler", "QuantileTransformer", "PowerTransformer", "ColumnOneHotEncoder", "ColumnOrdinalEncoder"], "scalers": ["MinMaxScaler", "RobustScaler", "StandardScaler", "MaxAbsScaler", "Normalizer", ], "all_transformers" : ["transformers", "scalers"], "arithmatic": ["AddTransformer", "mul_neg_1_Transformer", "MulTransformer", "SafeReciprocalTransformer", "EQTransformer", "NETransformer", "GETransformer", "GTTransformer", "LETransformer", "LTTransformer", "MinTransformer", "MaxTransformer"], "imputers": ["SimpleImputer", "IterativeImputer", "KNNImputer"], "genetic_encoders": ["DominantEncoder", "RecessiveEncoder", "HeterosisEncoder", "UnderDominanceEncoder", "OverDominanceEncoder"], "genetic encoders" : ["DominantEncoder", "RecessiveEncoder", "HeterosisEncoder", "UnderDominanceEncoder", "OverDominanceEncoder"] } # Add skrebate-related entries if skrebate is installed if is_skrebate_installed: GROUPNAMES["skrebate"] = ["ReliefF", "SURF", "SURFstar", "MultiSURF"] # Add sklearnx-related entries if sklearnx is installed if is_sklearnx_installed: GROUPNAMES["classifiers_sklearnex"] = ["RandomForestClassifier_sklearnex", "LogisticRegression_sklearnex", "KNeighborsClassifier_sklearnex", "SVC_sklearnex","NuSVC_sklearnex"], GROUPNAMES["regressors_sklearnex"] = ["LinearRegression_sklearnex", "Ridge_sklearnex", "Lasso_sklearnex", "ElasticNet_sklearnex", "SVR_sklearnex", "NuSVR_sklearnex", "RandomForestRegressor_sklearnex", "KNeighborsRegressor_sklearnex"], def get_configspace(name, n_classes=3, n_samples=1000, n_features=100, random_state=None, n_jobs=1): """ This function returns the ConfigSpace.ConfigurationSpace with the hyperparameter ranges for the given scikit-learn method. It also uses the n_classes, n_samples, n_features, and random_state to set the hyperparameters that depend on these values. Parameters ---------- name : str The str name of the scikit-learn method for which to create the ConfigurationSpace. (e.g. 'RandomForestClassifier' for sklearn.ensemble.RandomForestClassifier) n_classes : int The number of classes in the target variable. Default is 3. n_samples : int The number of samples in the dataset. Default is 1000. n_features : int The number of features in the dataset. Default is 100. random_state : int The random_state to use in the ConfigurationSpace. Default is None. If None, the random_state hyperparameter is not included in the ConfigurationSpace. Use this to set the random state for the individual methods if you want to ensure reproducibility. n_jobs : int (default=1) Sets the n_jobs parameter for estimators that have it. Default is 1. """ match name: #autoqtl_builtins.py case "FeatureEncodingFrequencySelector": return autoqtl_builtins.FeatureEncodingFrequencySelector_ConfigurationSpace case "DominantEncoder": return {} case "RecessiveEncoder": return {} case "HeterosisEncoder": return {} case "UnderDominanceEncoder": return {} case "OverDominanceEncoder": return {} case "Passthrough": return {} case "SkipTransformer": return {} #classifiers.py case "LinearDiscriminantAnalysis": return classifiers.get_LinearDiscriminantAnalysis_ConfigurationSpace() case "AdaBoostClassifier": return classifiers.get_AdaBoostClassifier_ConfigurationSpace(random_state=random_state) case "LogisticRegression": return classifiers.get_LogisticRegression_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "KNeighborsClassifier": return classifiers.get_KNeighborsClassifier_ConfigurationSpace(n_samples=n_samples, n_jobs=n_jobs) case "DecisionTreeClassifier": return classifiers.get_DecisionTreeClassifier_ConfigurationSpace(n_featues=n_features, random_state=random_state) case "SVC": return classifiers.get_SVC_ConfigurationSpace(random_state=random_state) case "LinearSVC": return classifiers.get_LinearSVC_ConfigurationSpace(random_state=random_state) case "RandomForestClassifier": return classifiers.get_RandomForestClassifier_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "GradientBoostingClassifier": return classifiers.get_GradientBoostingClassifier_ConfigurationSpace(n_classes=n_classes, random_state=random_state) case "HistGradientBoostingClassifier": return classifiers.get_HistGradientBoostingClassifier_ConfigurationSpace(random_state=random_state) case "XGBClassifier": return classifiers.get_XGBClassifier_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "LGBMClassifier": return classifiers.get_LGBMClassifier_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "ExtraTreesClassifier": return classifiers.get_ExtraTreesClassifier_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "SGDClassifier": return classifiers.get_SGDClassifier_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "MLPClassifier": return classifiers.get_MLPClassifier_ConfigurationSpace(random_state=random_state) case "BernoulliNB": return classifiers.get_BernoulliNB_ConfigurationSpace() case "MultinomialNB": return classifiers.get_MultinomialNB_ConfigurationSpace() case "GaussianNB": return {} case "LassoLarsCV": return {} case "ElasticNetCV": return regressors.ElasticNetCV_configspace case "RidgeCV": return {} case "PassiveAggressiveClassifier": return classifiers.get_PassiveAggressiveClassifier_ConfigurationSpace(random_state=random_state) case "QuadraticDiscriminantAnalysis": return classifiers.get_QuadraticDiscriminantAnalysis_ConfigurationSpace() case "GaussianProcessClassifier": return classifiers.get_GaussianProcessClassifier_ConfigurationSpace(n_features=n_features, random_state=random_state) case "BaggingClassifier": return classifiers.get_BaggingClassifier_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) #regressors.py case "RandomForestRegressor": return regressors.get_RandomForestRegressor_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "SGDRegressor": return regressors.get_SGDRegressor_ConfigurationSpace(random_state=random_state) case "Ridge": return regressors.get_Ridge_ConfigurationSpace(random_state=random_state) case "Lasso": return regressors.get_Lasso_ConfigurationSpace(random_state=random_state) case "ElasticNet": return regressors.get_ElasticNet_ConfigurationSpace(random_state=random_state) case "Lars": return regressors.get_Lars_ConfigurationSpace(random_state=random_state) case "OthogonalMatchingPursuit": return regressors.get_OthogonalMatchingPursuit_ConfigurationSpace() case "BayesianRidge": return regressors.get_BayesianRidge_ConfigurationSpace() case "LassoLars": return regressors.get_LassoLars_ConfigurationSpace(random_state=random_state) case "BaggingRegressor": return regressors.get_BaggingRegressor_ConfigurationSpace(random_state=random_state) case "ARDRegression": return regressors.get_ARDRegression_ConfigurationSpace() case "TheilSenRegressor": return regressors.get_TheilSenRegressor_ConfigurationSpace(random_state=random_state) case "Perceptron": return regressors.get_Perceptron_ConfigurationSpace(random_state=random_state) case "DecisionTreeRegressor": return regressors.get_DecisionTreeRegressor_ConfigurationSpace(random_state=random_state) case "LinearSVR": return regressors.get_LinearSVR_ConfigurationSpace(random_state=random_state) case "SVR": return regressors.get_SVR_ConfigurationSpace() case "XGBRegressor": return regressors.get_XGBRegressor_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "AdaBoostRegressor": return regressors.get_AdaBoostRegressor_ConfigurationSpace(random_state=random_state) case "ExtraTreesRegressor": return regressors.get_ExtraTreesRegressor_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "GradientBoostingRegressor": return regressors.get_GradientBoostingRegressor_ConfigurationSpace(random_state=random_state) case "HistGradientBoostingRegressor": return regressors.get_HistGradientBoostingRegressor_ConfigurationSpace(random_state=random_state) case "MLPRegressor": return regressors.get_MLPRegressor_ConfigurationSpace(random_state=random_state) case "KNeighborsRegressor": return regressors.get_KNeighborsRegressor_ConfigurationSpace(n_samples=n_samples, n_jobs=n_jobs) case "GaussianProcessRegressor": return regressors.get_GaussianProcessRegressor_ConfigurationSpace(n_features=n_features, random_state=random_state) case "LGBMRegressor": return regressors.get_LGBMRegressor_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "BaggingRegressor": return regressors.get_BaggingRegressor_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) #transformers.py case "Binarizer": return transformers.Binarizer_configspace case "Normalizer": return transformers.Normalizer_configspace case "PCA": return transformers.PCA_configspace case "ZeroCount": return transformers.ZeroCount_configspace case "FastICA": return transformers.get_FastICA_configspace(n_features=n_features, random_state=random_state) case "FeatureAgglomeration": return transformers.get_FeatureAgglomeration_configspace(n_features=n_features) case "Nystroem": return transformers.get_Nystroem_configspace(n_features=n_features, random_state=random_state) case "RBFSampler": return transformers.get_RBFSampler_configspace(n_features=n_features, random_state=random_state) case "MinMaxScaler": return {} case "PowerTransformer": return {} case "QuantileTransformer": return transformers.get_QuantileTransformer_configspace(n_samples=n_samples, random_state=random_state) case "RobustScaler": return transformers.RobustScaler_configspace case "MaxAbsScaler": return {} case "PolynomialFeatures": return transformers.PolynomialFeatures_configspace case "StandardScaler": return {} case "PassKBinsDiscretizer": return transformers.get_passkbinsdiscretizer_configspace(random_state=random_state) case "KBinsDiscretizer": return transformers.get_passkbinsdiscretizer_configspace(random_state=random_state) case "ColumnOneHotEncoder": return {} case "ColumnOrdinalEncoder": return {} #selectors.py case "SelectFwe": return selectors.SelectFwe_configspace case "SelectPercentile": return selectors.SelectPercentile_configspace case "VarianceThreshold": return selectors.VarianceThreshold_configspace case "RFE": return selectors.RFE_configspace_part case "SelectFromModel": return selectors.SelectFromModel_configspace_part #special_configs.py case "AddTransformer": return {} case "mul_neg_1_Transformer": return {} case "MulTransformer": return {} case "SafeReciprocalTransformer": return {} case "EQTransformer": return {} case "NETransformer": return {} case "GETransformer": return {} case "GTTransformer": return {} case "LETransformer": return {} case "LTTransformer": return {} case "MinTransformer": return {} case "MaxTransformer": return {} case "ZeroTransformer": return {} case "OneTransformer": return {} case "NTransformer": return ConfigurationSpace( space = { 'n': Float("n", bounds=(-1e2, 1e2)), } ) #imputers.py case "SimpleImputer": return imputers.simple_imputer_cs case "IterativeImputer": return imputers.get_IterativeImputer_config_space(n_features=n_features, random_state=random_state) case "IterativeImputer_no_estimator": return imputers.get_IterativeImputer_config_space_no_estimator(n_features=n_features, random_state=random_state) case "KNNImputer": return imputers.get_KNNImputer_config_space(n_samples=n_samples) #mdr_configs.py case "MDR": return mdr_configs.MDR_configspace case "ContinuousMDR": return mdr_configs.MDR_configspace case "ReliefF": return mdr_configs.get_skrebate_ReliefF_config_space(n_features=n_features) case "SURF": return mdr_configs.get_skrebate_SURF_config_space(n_features=n_features) case "SURFstar": return mdr_configs.get_skrebate_SURFstar_config_space(n_features=n_features) case "MultiSURF": return mdr_configs.get_skrebate_MultiSURF_config_space(n_features=n_features) #classifiers_sklearnex.py case "RandomForestClassifier_sklearnex": return classifiers_sklearnex.get_RandomForestClassifier_ConfigurationSpace(random_state=random_state, n_jobs=n_jobs) case "LogisticRegression_sklearnex": return classifiers_sklearnex.get_LogisticRegression_ConfigurationSpace(random_state=random_state) case "KNeighborsClassifier_sklearnex": return classifiers_sklearnex.get_KNeighborsClassifier_ConfigurationSpace(n_samples=n_samples) case "SVC_sklearnex": return classifiers_sklearnex.get_SVC_ConfigurationSpace(random_state=random_state) case "NuSVC_sklearnex": return classifiers_sklearnex.get_NuSVC_ConfigurationSpace(random_state=random_state) #regressors_sklearnex.py case "LinearRegression_sklearnex": return {} case "Ridge_sklearnex": return regressors_sklearnex.get_Ridge_ConfigurationSpace(random_state=random_state) case "Lasso_sklearnex": return regressors_sklearnex.get_Lasso_ConfigurationSpace(random_state=random_state) case "ElasticNet_sklearnex": return regressors_sklearnex.get_ElasticNet_ConfigurationSpace(random_state=random_state) case "SVR_sklearnex": return regressors_sklearnex.get_SVR_ConfigurationSpace(random_state=random_state) case "NuSVR_sklearnex": return regressors_sklearnex.get_NuSVR_ConfigurationSpace(random_state=random_state) case "RandomForestRegressor_sklearnex": return regressors_sklearnex.get_RandomForestRegressor_ConfigurationSpace(random_state=random_state) case "KNeighborsRegressor_sklearnex": return regressors_sklearnex.get_KNeighborsRegressor_ConfigurationSpace(n_samples=n_samples) #raise error raise ValueError(f"Could not find configspace for {name}") def flatten_group_names(name): #if string if isinstance(name, str): if name in GROUPNAMES: return flatten_group_names(GROUPNAMES[name]) else: return name flattened_list = [] for key in name: if key in GROUPNAMES: flattened_list.extend(flatten_group_names(GROUPNAMES[key])) else: flattened_list.append(key) return flattened_list def get_search_space(name, n_classes=3, n_samples=1000, n_features=100, random_state=None, return_choice_pipeline=True, base_node=EstimatorNode, n_jobs=1): """ Returns a TPOT search space for a given scikit-learn method or group of methods. Parameters ---------- name : str or list The name of the scikit-learn method or group of methods for which to create the search space. - str: The name of the scikit-learn method. (e.g. 'RandomForestClassifier' for sklearn.ensemble.RandomForestClassifier) Alternatively, the name of a group of methods. (e.g. 'classifiers' for all classifiers). - list: A list of scikit-learn method names. (e.g. ['RandomForestClassifier', 'ExtraTreesClassifier']) n_classes : int (default=3) The number of classes in the target variable. n_samples : int (default=1000) The number of samples in the dataset. n_features : int (default=100) The number of features in the dataset. random_state : int (default=None) A fixed random_state to pass through to all methods that have a random_state hyperparameter. return_choice_pipeline : bool (default=True) If False, returns a list of TPOT.search_spaces.nodes.EstimatorNode objects. If True, returns a single TPOT.search_spaces.pipelines.ChoicePipeline that includes and samples from all EstimatorNodes. base_node: TPOT.search_spaces.base.SearchSpace (default=TPOT.search_spaces.nodes.EstimatorNode) The SearchSpace to pass the configuration space to. If you want to experiment with custom mutation/crossover operators, you can pass a custom SearchSpace node here. n_jobs : int (default=1) Sets the n_jobs parameter for estimators that have it. Default is 1. Returns ------- Returns an SearchSpace object that can be optimized by TPOT. - TPOT.search_spaces.nodes.EstimatorNode (or base_node) if there is only one search space. - List of TPOT.search_spaces.nodes.EstimatorNode (or base_node) objects if there are multiple search spaces. - TPOT.search_spaces.pipelines.ChoicePipeline object if return_choice_pipeline is True. Note: for some special cases with methods using wrapped estimators, the returned search space is a TPOT.search_spaces.pipelines.WrapperPipeline object. """ name = flatten_group_names(name) #if list of names, return a list of EstimatorNodes if isinstance(name, list) or isinstance(name, np.ndarray): search_spaces = [get_search_space(n, n_classes=n_classes, n_samples=n_samples, n_features=n_features, random_state=random_state, return_choice_pipeline=False, base_node=base_node, n_jobs=n_jobs) for n in name] #remove Nones search_spaces = [s for s in search_spaces if s is not None] if return_choice_pipeline: return ChoicePipeline(search_spaces=np.hstack(search_spaces)) else: return np.hstack(search_spaces) # if name in GROUPNAMES: # name_list = GROUPNAMES[name] # return get_search_space(name_list, n_classes=n_classes, n_samples=n_samples, n_features=n_features, random_state=random_state, return_choice_pipeline=return_choice_pipeline, base_node=base_node) return get_node(name, n_classes=n_classes, n_samples=n_samples, n_features=n_features, random_state=random_state, base_node=base_node, n_jobs=n_jobs) def get_node(name, n_classes=3, n_samples=100, n_features=100, random_state=None, base_node=EstimatorNode, n_jobs=1): """ Helper function for get_search_space. Returns a single EstimatorNode for the given scikit-learn method. Also includes special cases for nodes that require custom parsing of the hyperparameters or methods that wrap other methods. Parameters ---------- name : str or list The name of the scikit-learn method or group of methods for which to create the search space. - str: The name of the scikit-learn method. (e.g. 'RandomForestClassifier' for sklearn.ensemble.RandomForestClassifier) Alternatively, the name of a group of methods. (e.g. 'classifiers' for all classifiers). - list: A list of scikit-learn method names. (e.g. ['RandomForestClassifier', 'ExtraTreesClassifier']) n_classes : int (default=3) The number of classes in the target variable. n_samples : int (default=1000) The number of samples in the dataset. n_features : int (default=100) The number of features in the dataset. random_state : int (default=None) A fixed random_state to pass through to all methods that have a random_state hyperparameter. return_choice_pipeline : bool (default=True) If False, returns a list of TPOT.search_spaces.nodes.EstimatorNode objects. If True, returns a single TPOT.search_spaces.pipelines.ChoicePipeline that includes and samples from all EstimatorNodes. base_node: TPOT.search_spaces.base.SearchSpace (default=TPOT.search_spaces.nodes.EstimatorNode) The SearchSpace to pass the configuration space to. If you want to experiment with custom mutation/crossover operators, you can pass a custom SearchSpace node here. n_jobs : int (default=1) Sets the n_jobs parameter for estimators that have it. Default is 1. Returns ------- Returns an SearchSpace object that can be optimized by TPOT. - TPOT.search_spaces.nodes.EstimatorNode (or base_node). - TPOT.search_spaces.pipelines.WrapperPipeline object if the method requires a wrapped estimator. """ if name == "LinearSVC_wrapped": ext = get_node("LinearSVC", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return WrapperPipeline(estimator_search_space=ext, method=sklearn.calibration.CalibratedClassifierCV, space={}) if name == "RFE_classification": rfe_sp = get_configspace(name="RFE", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) ext = get_node("ExtraTreesClassifier", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return WrapperPipeline(estimator_search_space=ext, method=RFE, space=rfe_sp) if name == "RFE_regression": rfe_sp = get_configspace(name="RFE", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) ext = get_node("ExtraTreesRegressor", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return WrapperPipeline(estimator_search_space=ext, method=RFE, space=rfe_sp) if name == "SelectFromModel_classification": sfm_sp = get_configspace(name="SelectFromModel", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) ext = get_node("ExtraTreesClassifier", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return WrapperPipeline(estimator_search_space=ext, method=SelectFromModel, space=sfm_sp) if name == "SelectFromModel_regression": sfm_sp = get_configspace(name="SelectFromModel", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) ext = get_node("ExtraTreesRegressor", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return WrapperPipeline(estimator_search_space=ext, method=SelectFromModel, space=sfm_sp) # TODO Add IterativeImputer with more estimator methods if name == "IterativeImputer_learned_estimators": iteative_sp = get_configspace(name="IterativeImputer_no_estimator", n_features=n_features, random_state=random_state, n_jobs=n_jobs) regressor_searchspace = get_node("ExtraTreesRegressor", n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return WrapperPipeline(estimator_search_space=regressor_searchspace, method=IterativeImputer, space=iteative_sp) #these are nodes that have special search spaces which require custom parsing of the hyperparameters if name == "IterativeImputer": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return EstimatorNode(STRING_TO_CLASS[name], configspace, hyperparameter_parser=imputers.IterativeImputer_hyperparameter_parser) if name == "RobustScaler": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=transformers.robust_scaler_hyperparameter_parser) if name == "GradientBoostingClassifier": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=classifiers.GradientBoostingClassifier_hyperparameter_parser) if name == "HistGradientBoostingClassifier": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=classifiers.HistGradientBoostingClassifier_hyperparameter_parser) if name == "GradientBoostingRegressor": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=regressors.GradientBoostingRegressor_hyperparameter_parser) if name == "HistGradientBoostingRegressor": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=regressors.HistGradientBoostingRegressor_hyperparameter_parser) if name == "MLPClassifier": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=classifiers.MLPClassifier_hyperparameter_parser) if name == "MLPRegressor": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=regressors.MLPRegressor_hyperparameter_parser) if name == "GaussianProcessRegressor": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=regressors.GaussianProcessRegressor_hyperparameter_parser) if name == "GaussianProcessClassifier": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=classifiers.GaussianProcessClassifier_hyperparameter_parser) if name == "FeatureAgglomeration": configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, random_state=random_state, n_jobs=n_jobs) return base_node(STRING_TO_CLASS[name], configspace, hyperparameter_parser=transformers.FeatureAgglomeration_hyperparameter_parser) configspace = get_configspace(name, n_classes=n_classes, n_samples=n_samples, n_features=n_features, random_state=random_state, n_jobs=n_jobs) if configspace is None: #raise warning warnings.warn(f"Could not find configspace for {name}") return None return base_node(STRING_TO_CLASS[name], configspace) ================================================ FILE: tpot/config/imputers.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import sklearn import sklearn.ensemble import sklearn.linear_model import sklearn.neighbors from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal from ConfigSpace import EqualsCondition simple_imputer_cs = ConfigurationSpace( space = { 'strategy' : Categorical('strategy', ['mean','median', 'most_frequent', 'constant'] ), #'add_indicator' : Categorical('add_indicator', [True, False]), #Removed add_indicator, it appends a mask next to the rest of the data # and can cause errors. gk } ) #test def get_IterativeImputer_config_space(n_features, random_state): space = { 'initial_strategy' : Categorical('initial_strategy', ['mean', 'median', 'most_frequent', 'constant']), 'n_nearest_features' : Integer('n_nearest_features', bounds=(1, n_features)), 'imputation_order' : Categorical('imputation_order', ['ascending', 'descending', 'roman', 'arabic', 'random']), } estimator = Categorical('estimator', ['Bayesian', 'RFR', 'Ridge', 'KNN']) sample_posterior = Categorical('sample_posterior', [True, False]) sampling_condition = EqualsCondition(sample_posterior, estimator, 'Bayesian') if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace(space=space) cs.add([estimator, sample_posterior]) cs.add([sampling_condition]) return cs def get_IterativeImputer_config_space_no_estimator(n_features, random_state): space = { 'initial_strategy' : Categorical('initial_strategy', ['mean', 'median', 'most_frequent', 'constant']), 'n_nearest_features' : Integer('n_nearest_features', bounds=(1, n_features)), 'imputation_order' : Categorical('imputation_order', ['ascending', 'descending', 'roman', 'arabic', 'random']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace(space=space) return cs def get_KNNImputer_config_space(n_samples): space = { 'n_neighbors': Integer('n_neighbors', bounds=(1, max(n_samples,100))), 'weights': Categorical('weights', ['uniform', 'distance']) } return ConfigurationSpace( space=space ) def IterativeImputer_hyperparameter_parser(params): est = params['estimator'] match est: case 'Bayesian': estimator = sklearn.linear_model.BayesianRidge() case 'RFR': estimator = sklearn.ensemble.RandomForestRegressor() case 'Ridge': estimator = sklearn.linear_model.Ridge() case 'KNN': estimator = sklearn.neighbors.KNeighborsRegressor() final_params = { 'estimator' : estimator, 'initial_strategy' : params['initial_strategy'], 'n_nearest_features' : params['n_nearest_features'], 'imputation_order' : params['imputation_order'], } if 'sample_posterior' in params: final_params['sample_posterior'] = params['sample_posterior'] if 'random_state' in params: final_params['random_state'] = params['random_state'] return final_params ================================================ FILE: tpot/config/mdr_configs.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal #MDR MDR_configspace = ConfigurationSpace( space = { 'tie_break': Categorical('tie_break', [0,1]), 'default_label': Categorical('default_label', [0,1]), } ) def get_skrebate_ReliefF_config_space(n_features): return ConfigurationSpace( space = { 'n_features_to_select': Integer('n_features_to_select', bounds=(1, n_features), log=True), 'n_neighbors': Integer('n_neighbors', bounds=(2,500), log=True), } ) def get_skrebate_SURF_config_space(n_features): return ConfigurationSpace( space = { 'n_features_to_select': Integer('n_features_to_select', bounds=(1, n_features), log=True), } ) def get_skrebate_SURFstar_config_space(n_features): return ConfigurationSpace( space = { 'n_features_to_select': Integer('n_features_to_select', bounds=(1, n_features), log=True), } ) def get_skrebate_MultiSURF_config_space(n_features): return ConfigurationSpace( space = { 'n_features_to_select': Integer('n_features_to_select', bounds=(1, n_features), log=True), } ) ================================================ FILE: tpot/config/regressors.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import sklearn from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal from ConfigSpace import EqualsCondition, OrConjunction, NotEqualsCondition, InCondition import numpy as np #TODO: fill in remaining #TODO check for places were we could use log scaling ElasticNetCV_configspace = { "l1_ratio" : np.arange(0.0, 1.01, 0.05), } def get_RandomForestRegressor_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': 100, 'criterion': Categorical("criterion", ['friedman_mse', 'poisson', 'absolute_error', 'squared_error']), 'max_features': Float("max_features", bounds=(0.05, 1.0)), 'bootstrap': Categorical("bootstrap", [True, False]), 'min_samples_split': Integer("min_samples_split", bounds=(2, 21)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 21)), 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_SGDRegressor_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(1e-7, 1e-1), log=True), 'average': Categorical("average", [True, False]), 'fit_intercept': Categorical("fit_intercept", [True]), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace( space = space ) l1_ratio = Float("l1_ratio", bounds=(1e-7, 1.0), log=True) penalty = Categorical("penalty", ["l1", "l2", "elasticnet"]) epsilon = Float("epsilon", bounds=(1e-5, 1e-1), log=True) loss = Categorical("loss", ['epsilon_insensitive', 'squared_epsilon_insensitive', 'huber', 'squared_error']) eta0 = Float("eta0", bounds=(1e-7, 1e-1), log=True) learning_rate = Categorical("learning_rate", ['optimal', 'invscaling', 'constant']) power_t = Float("power_t", bounds=(1e-5, 1.0), log=True) elasticnet = EqualsCondition(l1_ratio, penalty, "elasticnet") epsilon_condition = InCondition( epsilon, loss, ["huber", "epsilon_insensitive", "squared_epsilon_insensitive"], ) eta0_in_inv_con = InCondition(eta0, learning_rate, ["invscaling", "constant"]) power_t_condition = EqualsCondition(power_t, learning_rate, "invscaling") cs.add( [l1_ratio, penalty, epsilon, loss, eta0, learning_rate, power_t] ) cs.add( [elasticnet, epsilon_condition, power_t_condition, eta0_in_inv_con] ) return cs def get_Ridge_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(0.0, 1.0)), 'fit_intercept': Categorical("fit_intercept", [True]), 'tol': Float("tol", bounds=(1e-5, 1e-1), log=True), 'solver': Categorical("solver", ['auto', 'svd', 'cholesky', 'lsqr', 'sparse_cg', 'sag', 'saga']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_Lasso_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(0.0, 1.0)), 'fit_intercept': Categorical("fit_intercept", [True]), 'tol': 0.0001, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_ElasticNet_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(0.0, 1.0)), 'l1_ratio': Float("l1_ratio", bounds=(0.0, 1.0)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_Lars_ConfigurationSpace(random_state): space = { } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_OthogonalMatchingPursuit_ConfigurationSpace(): return ConfigurationSpace( space = { } ) def get_BayesianRidge_ConfigurationSpace(): return ConfigurationSpace( space = { 'tol': 0.0001, 'alpha_1': Float("alpha_1", bounds=(1e-6, 1e-1), log=True), 'alpha_2': Float("alpha_2", bounds=(1e-6, 1e-1), log=True), 'lambda_1': Float("lambda_1", bounds=(1e-6, 1e-1), log=True), 'lambda_2': Float("lambda_2", bounds=(1e-6, 1e-1), log=True), } ) def get_LassoLars_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(0.0, 1.0)), 'eps': Float("eps", bounds=(1e-5, 1e-1), log=True), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_BaggingRegressor_ConfigurationSpace(random_state): space = { 'max_samples': Float("max_samples", bounds=(0.05, 1.00)), 'max_features': Float("max_features", bounds=(0.05, 1.00)), 'bootstrap': Categorical("bootstrap", [True, False]), 'bootstrap_features': Categorical("bootstrap_features", [True, False]), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_ARDRegression_ConfigurationSpace(): return ConfigurationSpace( space = { 'alpha_1': Float("alpha_1", bounds=(1e-10, 1e-3), log=True), 'alpha_2': Float("alpha_2", bounds=(1e-10, 1e-3), log=True), 'lambda_1': Float("lambda_1", bounds=(1e-10, 1e-3), log=True), 'lambda_2': Float("lambda_2", bounds=(1e-10, 1e-3), log=True), 'threshold_lambda': Integer("threshold_lambda", bounds=(1e3, 1e5)), } ) def get_TheilSenRegressor_ConfigurationSpace(random_state): space = { 'n_subsamples': Integer("n_subsamples", bounds=(10, 10000)), 'max_subpopulation': Integer("max_subpopulation", bounds=(10, 1000)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_Perceptron_ConfigurationSpace(random_state): space = { 'penalty': Categorical("penalty", [None, 'l2', 'l1', 'elasticnet']), 'alpha': Float("alpha", bounds=(1e-5, 1e-1), log=True), 'l1_ratio': Float("l1_ratio", bounds=(0.0, 1.0)), 'learning_rate': Categorical("learning_rate", ['constant', 'optimal', 'invscaling']), 'validation_fraction': Float("validation_fraction", bounds=(0.05, 1.00)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_DecisionTreeRegressor_ConfigurationSpace(random_state): space = { 'criterion': Categorical("criterion", ['friedman_mse', 'poisson', 'absolute_error', 'squared_error']), # 'max_depth': Integer("max_depth", bounds=(1, n_features*2)), 'min_samples_split': Integer("min_samples_split", bounds=(2, 21)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 21)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_KNeighborsRegressor_ConfigurationSpace(n_samples, n_jobs=1): return ConfigurationSpace( space = { 'n_neighbors': Integer("n_neighbors", bounds=(1, min(100,n_samples))), 'weights': Categorical("weights", ['uniform', 'distance']), 'p': Integer("p", bounds=(1, 3)), 'n_jobs': n_jobs, } ) def get_LinearSVR_ConfigurationSpace(random_state): space = { 'epsilon': Float("epsilon", bounds=(1e-4, 1.0), log=True), 'C': Float('C', (0.01, 1e5), log=True), 'dual': "auto", 'loss': Categorical("loss", ['epsilon_insensitive', 'squared_epsilon_insensitive']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) #add coef0? def get_SVR_ConfigurationSpace(): space = { 'epislon': Float("epsilon", bounds=(1e-4, 1.0), log=True), 'shrinking': Categorical("shrinking", [True, False]), 'C': Float('C', (0.01, 1e5), log=True), 'max_iter': 3000, 'tol': 0.005, } cs = ConfigurationSpace( space = space ) kernel = Categorical("kernel", ['poly', 'rbf', 'linear', 'sigmoid']) degree = Integer("degree", bounds=(1, 5)) gamma = Float("gamma", bounds=(1e-5, 10.0), log=True) coef0 = Float("coef0", bounds=(-1, 1)) degree_condition = EqualsCondition(degree, kernel, 'poly') gamma_condition = InCondition(gamma, kernel, ['poly', 'rbf',]) coef0_condition = InCondition(coef0, kernel, ['poly', 'sigmoid']) cs.add([kernel, degree, gamma, coef0]) cs.add([degree_condition,gamma_condition]) return cs def get_XGBRegressor_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': 100, 'learning_rate': Float("learning_rate", bounds=(1e-3, 1), log=True), 'subsample': Float("subsample", bounds=(0.5, 1.0)), 'min_child_weight': Integer("min_child_weight", bounds=(1, 21)), 'gamma': Float("gamma", bounds=(1e-4, 20), log=True), 'max_depth': Integer("max_depth", bounds=(3, 18)), 'reg_alpha': Float("reg_alpha", bounds=(1e-4, 100), log=True), 'reg_lambda': Float("reg_lambda", bounds=(1e-4, 1), log=True), 'n_jobs': n_jobs, 'nthread': 1, 'verbosity': 0, 'objective': 'reg:squarederror', } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_AdaBoostRegressor_ConfigurationSpace(random_state): space = { 'n_estimators': Integer("n_estimators", bounds=(50, 500)), 'learning_rate': Float("learning_rate", bounds=(1e-3, 2.0), log=True), 'loss': Categorical("loss", ['linear', 'square', 'exponential']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_ExtraTreesRegressor_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': 100, 'criterion': Categorical("criterion", ['friedman_mse', 'poisson', 'absolute_error', 'squared_error']), 'max_features': Float("max_features", bounds=(0.05, 1.0)), 'min_samples_split': Integer("min_samples_split", bounds=(2, 21)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 21)), 'bootstrap': Categorical("bootstrap", [True, False]), 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) ### def get_GaussianProcessRegressor_ConfigurationSpace(n_features, random_state): space = { 'n_features': n_features, 'alpha': Float("alpha", bounds=(1e-10, 1.0), log=True), 'thetaL': Float("thetaL", bounds=(1e-10, 1e-3), log=True), 'thetaU': Float("thetaU", bounds=(1.0, 100000), log=True), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def GaussianProcessRegressor_hyperparameter_parser(params): kernel = sklearn.gaussian_process.kernels.RBF( length_scale = [1.0]*params['n_features'], length_scale_bounds=[(params['thetaL'], params['thetaU'])] * params['n_features'], ) final_params = {"kernel": kernel, "alpha": params['alpha'], "n_restarts_optimizer": 10, "optimizer": "fmin_l_bfgs_b", "normalize_y": True, "copy_X_train": True, } if "random_state" in params: final_params['random_state'] = params['random_state'] return final_params ### def get_GradientBoostingRegressor_ConfigurationSpace(random_state): early_stop = Categorical("early_stop", ["off", "valid", "train"]) n_iter_no_change = Integer("n_iter_no_change",bounds=(1,20)) validation_fraction = Float("validation_fraction", bounds=(0.01, 0.4)) n_iter_no_change_cond = InCondition(n_iter_no_change, early_stop, ["valid", "train"] ) validation_fraction_cond = EqualsCondition(validation_fraction, early_stop, "valid") space = { 'loss': Categorical("loss", ['log_loss', 'exponential']), 'learning_rate': Float("learning_rate", bounds=(1e-3, 1), log=True), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 200)), 'min_samples_split': Integer("min_samples_split", bounds=(2, 20)), 'subsample': Float("subsample", bounds=(0.1, 1.0)), 'max_features': Float("max_features", bounds=(0.01, 1.00)), 'max_leaf_nodes': Integer("max_leaf_nodes", bounds=(3, 2047)), 'max_depth':None, #'max_depth': Integer("max_depth", bounds=(1, 2*n_features)), 'tol': 1e-4, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace( space = space ) cs.add([n_iter_no_change, validation_fraction, early_stop ]) cs.add([validation_fraction_cond, n_iter_no_change_cond]) return cs def GradientBoostingRegressor_hyperparameter_parser(params): final_params = { 'loss': params['loss'], 'learning_rate': params['learning_rate'], 'min_samples_leaf': params['min_samples_leaf'], 'min_samples_split': params['min_samples_split'], 'max_features': params['max_features'], 'max_leaf_nodes': params['max_leaf_nodes'], 'max_depth': params['max_depth'], 'tol': params['tol'], 'subsample': params['subsample'] } if 'random_state' in params: final_params['random_state'] = params['random_state'] if params['early_stop'] == 'off': final_params['n_iter_no_change'] = None final_params['validation_fraction'] = None elif params['early_stop'] == 'valid': #this is required because in crossover, its possible that n_iter_no_change is not in the params if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 else: final_params['n_iter_no_change'] = params['n_iter_no_change'] if 'validation_fraction' not in params: final_params['validation_fraction'] = 0.1 else: final_params['validation_fraction'] = params['validation_fraction'] elif params['early_stop'] == 'train': if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 else: final_params['n_iter_no_change'] = params['n_iter_no_change'] final_params['validation_fraction'] = None return final_params #only difference is l2_regularization def get_HistGradientBoostingRegressor_ConfigurationSpace(random_state): early_stop = Categorical("early_stop", ["off", "valid", "train"]) n_iter_no_change = Integer("n_iter_no_change",bounds=(1,20)) validation_fraction = Float("validation_fraction", bounds=(0.01, 0.4)) n_iter_no_change_cond = InCondition(n_iter_no_change, early_stop, ["valid", "train"] ) validation_fraction_cond = EqualsCondition(validation_fraction, early_stop, "valid") space = { 'loss': Categorical("loss", ['log_loss', 'exponential']), 'learning_rate': Float("learning_rate", bounds=(1e-3, 1), log=True), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 200)), 'max_features': Float("max_features", bounds=(0.1,1.0)), 'max_leaf_nodes': Integer("max_leaf_nodes", bounds=(3, 2047)), 'max_depth':None, #'max_depth': Integer("max_depth", bounds=(1, 2*n_features)), 'l2_regularization': Float("l2_regularization", bounds=(1e-10, 1), log=True), 'tol': 1e-4, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace( space = space ) cs.add([n_iter_no_change, validation_fraction, early_stop ]) cs.add([validation_fraction_cond, n_iter_no_change_cond]) return cs def HistGradientBoostingRegressor_hyperparameter_parser(params): final_params = { 'learning_rate': params['learning_rate'], 'min_samples_leaf': params['min_samples_leaf'], 'max_features': params['max_features'], 'max_leaf_nodes': params['max_leaf_nodes'], 'max_depth': params['max_depth'], 'tol': params['tol'], 'l2_regularization': params['l2_regularization'] } if 'random_state' in params: final_params['random_state'] = params['random_state'] if params['early_stop'] == 'off': # final_params['n_iter_no_change'] = 0 # final_params['validation_fraction'] = None final_params['early_stopping'] = False elif params['early_stop'] == 'valid': if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 else: final_params['n_iter_no_change'] = params['n_iter_no_change'] if 'validation_fraction' not in params: final_params['validation_fraction'] = 0.1 else: final_params['validation_fraction'] = params['validation_fraction'] final_params['early_stopping'] = True elif params['early_stop'] == 'train': if 'n_iter_no_change' not in params: final_params['n_iter_no_change'] = 10 else: final_params['n_iter_no_change'] = params['n_iter_no_change'] final_params['validation_fraction'] = None final_params['early_stopping'] = True return final_params ### def get_MLPRegressor_ConfigurationSpace(random_state): space = {"n_iter_no_change":32} if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state cs = ConfigurationSpace( space = space ) n_hidden_layers = Integer("n_hidden_layers", bounds=(1, 3)) n_nodes_per_layer = Integer("n_nodes_per_layer", bounds=(16, 512)) activation = Categorical("activation", ['tanh', 'relu']) alpha = Float("alpha", bounds=(1e-7, 1e-1), log=True) learning_rate = Float("learning_rate", bounds=(1e-4, 1e-1), log=True) early_stopping = Categorical("early_stopping", [True,False]) learning_rate_init = Float("learning_rate_init", bounds=(1e-4, 1e-1), log=True) learning_rate = Categorical("learning_rate", ['constant', 'invscaling', 'adaptive']) cs.add([n_hidden_layers, n_nodes_per_layer, activation, alpha, learning_rate, early_stopping, learning_rate_init]) return cs def MLPRegressor_hyperparameter_parser(params): hyperparameters = { 'n_iter_no_change': params['n_iter_no_change'], 'hidden_layer_sizes' : [params['n_nodes_per_layer']]*params['n_hidden_layers'], 'activation': params['activation'], 'alpha': params['alpha'], 'early_stopping': params['early_stopping'], 'learning_rate_init': params['learning_rate_init'], 'learning_rate': params['learning_rate'], } if 'random_state' in params: hyperparameters['random_state'] = params['random_state'] return hyperparameters def get_BaggingRegressor_ConfigurationSpace(random_state, n_jobs=1): space = { 'n_estimators': Integer("n_estimators", bounds=(3, 100)), 'max_samples': Float("max_samples", bounds=(0.1, 1.0)), 'max_features': Float("max_features", bounds=(0.1, 1.0)), 'bootstrap_features': Categorical("bootstrap_features", [True, False]), 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state bootstrap = Categorical("bootstrap", [True, False]) oob_score = Categorical("oob_score", [True, False]) oob_condition = EqualsCondition(oob_score, bootstrap, True) cs = ConfigurationSpace( space = space ) cs.add([bootstrap, oob_score]) cs.add([oob_condition]) return cs def get_LGBMRegressor_ConfigurationSpace(random_state, n_jobs=1): space = { 'boosting_type': Categorical("boosting_type", ['gbdt', 'dart', 'goss']), 'num_leaves': Integer("num_leaves", bounds=(2, 256)), 'max_depth': Integer("max_depth", bounds=(1, 10)), 'n_estimators': Integer("n_estimators", bounds=(10, 100)), 'verbose':-1, 'n_jobs': n_jobs, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space=space ) ================================================ FILE: tpot/config/regressors_sklearnex.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal def get_RandomForestRegressor_ConfigurationSpace(random_state): space = { 'n_estimators': 100, 'max_features': Float("max_features", bounds=(0.05, 1.0)), 'bootstrap': Categorical("bootstrap", [True, False]), 'min_samples_split': Integer("min_samples_split", bounds=(2, 21)), 'min_samples_leaf': Integer("min_samples_leaf", bounds=(1, 21)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_KNeighborsRegressor_ConfigurationSpace(n_samples): return ConfigurationSpace( space = { 'n_neighbors': Integer("n_neighbors", bounds=(1, max(n_samples, 100))), 'weights': Categorical("weights", ['uniform', 'distance']), } ) def get_Ridge_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(0.0, 1.0)), 'fit_intercept': Categorical("fit_intercept", [True]), 'tol': Float("tol", bounds=(1e-5, 1e-1)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_Lasso_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(0.0, 1.0)), 'fit_intercept': Categorical("fit_intercept", [True]), 'precompute': Categorical("precompute", [True, False, 'auto']), 'tol': 0.001, 'positive': Categorical("positive", [True, False]), 'selection': Categorical("selection", ['cyclic', 'random']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_ElasticNet_ConfigurationSpace(random_state): space = { 'alpha': Float("alpha", bounds=(0.0, 1.0)), 'l1_ratio': Float("l1_ratio", bounds=(0.0, 1.0)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_SVR_ConfigurationSpace(random_state): space = { 'kernel': Categorical("kernel", ['poly', 'rbf', 'linear', 'sigmoid']), 'C': Float("C", bounds=(1e-4, 25), log=True), 'degree': Integer("degree", bounds=(1, 4)), 'max_iter': 3000, 'tol': 0.001, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_NuSVR_ConfigurationSpace(random_state): space = { 'nu': Float("nu", bounds=(0.05, 1.0)), 'kernel': Categorical("kernel", ['poly', 'rbf', 'linear', 'sigmoid']), 'C': Float("C", bounds=(1e-4, 25), log=True), 'degree': Integer("degree", bounds=(1, 4)), 'max_iter': 3000, 'tol': 0.005, } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) ================================================ FILE: tpot/config/selectors.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ #TODO: how to best support transformers/selectors that take other transformers with their own hyperparameters? import numpy as np import sklearn from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal SelectFwe_configspace = ConfigurationSpace( space = { 'alpha': Float('alpha', bounds=(1e-4, 0.05), log=True), } ) SelectPercentile_configspace = ConfigurationSpace( space = { 'percentile': Float('percentile', bounds=(1, 100.0)), } ) VarianceThreshold_configspace = ConfigurationSpace( space = { 'threshold': Float('threshold', bounds=(1e-4, .2), log=True), } ) # Note the RFE_configspace_part and SelectFromModel_configspace_part are not complete, they both require the estimator to be set. # These are indended to be used with the Wrapped search space. RFE_configspace_part = ConfigurationSpace( space = { 'step': Float('step', bounds=(1e-4, 1.0)), } ) SelectFromModel_configspace_part = ConfigurationSpace( space = { 'threshold': Float('threshold', bounds=(1e-4, 1.0), log=True), } ) ================================================ FILE: tpot/config/special_configs.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from tpot.builtin_modules import ArithmeticTransformer, FeatureSetSelector from functools import partial import pandas as pd import numpy as np from tpot.builtin_modules import AddTransformer, mul_neg_1_Transformer, MulTransformer, SafeReciprocalTransformer, EQTransformer, NETransformer, GETransformer, GTTransformer, LETransformer, LTTransformer, MinTransformer, MaxTransformer, ZeroTransformer, OneTransformer, NTransformer from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal def get_ArithmeticTransformer_ConfigurationSpace(): return ConfigurationSpace( space = { 'function': Categorical("function", ["add", "mul_neg_1", "mul", "safe_reciprocal", "eq","ne","ge","gt","le","lt", "min","max","0","1"]), } ) # AddTransformer: {} # mul_neg_1_Transformer: {} # MulTransformer: {} # SafeReciprocalTransformer: {} # EQTransformer: {} # NETransformer: {} # GETransformer: {} # GTTransformer: {} # LETransformer: {} # LTTransformer: {} # MinTransformer: {} # MaxTransformer: {} ================================================ FILE: tpot/config/template_search_spaces.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot from tpot.search_spaces.pipelines import * from tpot.search_spaces.nodes import * from .get_configspace import get_search_space import sklearn.model_selection import sklearn def get_linear_search_space(classification=True, inner_predictors=True, cross_val_predict_cv=0, **get_search_space_params ): if classification: selectors = get_search_space(["selectors","selectors_classification", "Passthrough"], **get_search_space_params) estimators = get_search_space(["classifiers"], **get_search_space_params) else: selectors = get_search_space(["selectors","selectors_regression", "Passthrough"], **get_search_space_params) estimators = get_search_space(["regressors"], **get_search_space_params) # this allows us to wrap the classifiers in the EstimatorTransformer # this is necessary so that classifiers can be used inside of sklearn pipelines wrapped_estimators = WrapperPipeline(tpot.builtin_modules.EstimatorTransformer, {'cross_val_predict_cv':cross_val_predict_cv}, estimators) scalers = get_search_space(["scalers","Passthrough"], **get_search_space_params) transformers_layer =UnionPipeline([ ChoicePipeline([ DynamicUnionPipeline(get_search_space(["transformers"],**get_search_space_params)), get_search_space("SkipTransformer", **get_search_space_params), ]), get_search_space("Passthrough", **get_search_space_params) ] ) inner_estimators_layer = UnionPipeline([ ChoicePipeline([ DynamicUnionPipeline(wrapped_estimators), get_search_space("SkipTransformer", **get_search_space_params), ]), get_search_space("Passthrough", **get_search_space_params)] ) if inner_predictors: search_space = SequentialPipeline(search_spaces=[ scalers, selectors, transformers_layer, inner_estimators_layer, estimators, ]) else: search_space = SequentialPipeline(search_spaces=[ scalers, selectors, transformers_layer, estimators, ]) return search_space def get_graph_search_space(classification=True, inner_predictors=True, cross_val_predict_cv=0, **get_search_space_params ): if classification: root_search_space = get_search_space(["classifiers"], **get_search_space_params) inner_search_space = tpot.config.get_search_space(["transformers","scalers","selectors_classification"],**get_search_space_params) else: root_search_space = get_search_space(["regressors"], **get_search_space_params) if classification: if inner_predictors: inner_search_space = tpot.config.get_search_space(["classifiers","transformers","scalers","selectors_classification"],**get_search_space_params) else: inner_search_space = tpot.config.get_search_space(["transformers","scalers","selectors_classification"],**get_search_space_params) else: if inner_predictors: inner_search_space = tpot.config.get_search_space(["regressors", "transformers","scalers","selectors_regression"],**get_search_space_params) else: inner_search_space = tpot.config.get_search_space(["transformers","scalers","selectors_regression"],**get_search_space_params) search_space = tpot.search_spaces.pipelines.GraphSearchPipeline( root_search_space= root_search_space, leaf_search_space = None, inner_search_space = inner_search_space, cross_val_predict_cv=cross_val_predict_cv, max_size=15, ) return search_space def get_graph_search_space_light(classification=True, inner_predictors=True, cross_val_predict_cv=0, **get_search_space_params ): if classification: root_search_space = get_search_space(['BernoulliNB', 'DecisionTreeClassifier', 'GaussianNB', 'KNeighborsClassifier', 'LogisticRegression', 'MultinomialNB'], **get_search_space_params) else: root_search_space = get_search_space(["RidgeCV", "LinearSVR", "LassoLarsCV", "KNeighborsRegressor", "DecisionTreeRegressor", "ElasticNetCV"], **get_search_space_params) if classification: if inner_predictors: inner_search_space = tpot.config.get_search_space(['BernoulliNB', 'DecisionTreeClassifier', 'GaussianNB', 'KNeighborsClassifier', 'LogisticRegression', 'MultinomialNB',"transformers","scalers","SelectFwe", "SelectPercentile", "VarianceThreshold"],**get_search_space_params) else: inner_search_space = tpot.config.get_search_space(["transformers","scalers","SelectFwe", "SelectPercentile", "VarianceThreshold"],**get_search_space_params) else: if inner_predictors: inner_search_space = tpot.config.get_search_space(["RidgeCV", "LinearSVR", "LassoLarsCV", "KNeighborsRegressor", "DecisionTreeRegressor", "ElasticNetCV", "transformers","scalers", "SelectFwe", "SelectPercentile", "VarianceThreshold"],**get_search_space_params) else: inner_search_space = tpot.config.get_search_space(["transformers", "scalers", "SelectFwe", "SelectPercentile", "VarianceThreshold"],**get_search_space_params) search_space = tpot.search_spaces.pipelines.GraphSearchPipeline( root_search_space= root_search_space, leaf_search_space = None, inner_search_space = inner_search_space, cross_val_predict_cv=cross_val_predict_cv, max_size=15, ) return search_space def get_light_search_space(classification=True, inner_predictors=False, cross_val_predict_cv=0, **get_search_space_params ): selectors = get_search_space(["SelectFwe", "SelectPercentile", "VarianceThreshold","Passthrough"], **get_search_space_params) if classification: estimators = get_search_space(['BernoulliNB', 'DecisionTreeClassifier', 'GaussianNB', 'KNeighborsClassifier', 'LogisticRegression', 'MultinomialNB'], **get_search_space_params) else: estimators = get_search_space(["RidgeCV", "LinearSVR", "LassoLarsCV", "KNeighborsRegressor", "DecisionTreeRegressor", "ElasticNetCV"], **get_search_space_params) # this allows us to wrap the classifiers in the EstimatorTransformer # this is necessary so that classifiers can be used inside of sklearn pipelines wrapped_estimators = WrapperPipeline(tpot.builtin_modules.EstimatorTransformer, {'cross_val_predict_cv':cross_val_predict_cv}, estimators) scalers = get_search_space(["scalers","Passthrough"], **get_search_space_params) transformers_layer =UnionPipeline([ ChoicePipeline([ DynamicUnionPipeline(get_search_space(["transformers"],**get_search_space_params)), get_search_space("SkipTransformer", **get_search_space_params), ]), get_search_space("Passthrough", **get_search_space_params) ] ) inner_estimators_layer = UnionPipeline([ ChoicePipeline([ DynamicUnionPipeline(wrapped_estimators), get_search_space("SkipTransformer", **get_search_space_params), ]), get_search_space("Passthrough", **get_search_space_params)] ) if inner_predictors: search_space = SequentialPipeline(search_spaces=[ scalers, selectors, transformers_layer, inner_estimators_layer, estimators, ]) else: search_space = SequentialPipeline(search_spaces=[ scalers, selectors, transformers_layer, estimators, ]) return search_space def get_mdr_search_space(classification=True, **get_search_space_params ): if classification: mdr_sp = DynamicLinearPipeline(get_search_space(["ReliefF", "SURF", "SURFstar", "MultiSURF", "MDR"], **get_search_space_params), max_length=10) estimators = get_search_space(['LogisticRegression'], **get_search_space_params) else: mdr_sp = DynamicLinearPipeline(get_search_space(["ReliefF", "SURF", "SURFstar", "MultiSURF", "ContinuousMDR"], **get_search_space_params), max_length=10) estimators = get_search_space(["ElasticNetCV"], **get_search_space_params) search_space = SequentialPipeline(search_spaces=[ mdr_sp, estimators, ]) return search_space def get_template_search_spaces(search_space, classification=True, inner_predictors=None, cross_val_predict_cv=None, **get_search_space_params): """ Returns a search space which can be optimized by TPOT. Parameters ---------- search_space: str or SearchSpace The default search space to use. If a string, it should be one of the following: - 'linear': A search space for linear pipelines - 'linear-light': A search space for linear pipelines with a smaller, faster search space - 'graph': A search space for graph pipelines - 'graph-light': A search space for graph pipelines with a smaller, faster search space - 'mdr': A search space for MDR pipelines If a SearchSpace object, it should be a valid search space object for TPOT. classification: bool, default=True Whether the problem is a classification problem or a regression problem. inner_predictors: bool, default=None Whether to include additional classifiers/regressors before the final classifier/regressor (allowing for ensembles). Defaults to False for 'linear-light' and 'graph-light' search spaces, and True otherwise. (Not used for 'mdr' search space) cross_val_predict_cv: int, default=None The number of folds to use for cross_val_predict. Defaults to 0 for 'linear-light' and 'graph-light' search spaces, and 5 otherwise. (Not used for 'mdr' search space) get_search_space_params: dict Additional parameters to pass to the get_search_space function. """ if inner_predictors is None: if search_space == "light" or search_space == "graph_light": inner_predictors = False else: inner_predictors = True if cross_val_predict_cv is None: if search_space == "light" or search_space == "graph_light": cross_val_predict_cv = 0 else: if classification: cross_val_predict_cv = sklearn.model_selection.StratifiedKFold(n_splits=5, shuffle=True, random_state=42) else: cross_val_predict_cv = sklearn.model_selection.KFold(n_splits=5, shuffle=True, random_state=42) if isinstance(search_space, str): if search_space == "linear": return get_linear_search_space(classification, inner_predictors, cross_val_predict_cv=cross_val_predict_cv, **get_search_space_params) elif search_space == "graph": return get_graph_search_space(classification, inner_predictors, cross_val_predict_cv=cross_val_predict_cv, **get_search_space_params) elif search_space == "graph-light": return get_graph_search_space_light(classification, inner_predictors, cross_val_predict_cv=cross_val_predict_cv, **get_search_space_params) elif search_space == "linear-light": return get_light_search_space(classification, inner_predictors, cross_val_predict_cv=cross_val_predict_cv, **get_search_space_params) elif search_space == "mdr": return get_mdr_search_space(classification, **get_search_space_params) else: raise ValueError("Invalid search space") else: return search_space ================================================ FILE: tpot/config/tests/__init__.py ================================================ ================================================ FILE: tpot/config/tests/test_get_configspace.py ================================================ import pytest import tpot import sys from sklearn.datasets import load_iris import random import sklearn import warnings import tpot.config from ..get_configspace import STRING_TO_CLASS, GROUPNAMES import importlib.util def test_loop_through_all_hyperparameters(): n_classes=3 n_samples=100 n_features=100 random_state=None for class_name, _ in STRING_TO_CLASS.items(): print(class_name) estnode_gen = tpot.config.get_search_space(class_name, n_classes=n_classes, n_samples=n_samples, n_features=n_features, random_state=random_state) #generate 100 random hyperparameters and make sure they are all valid for i in range(25): estnode = estnode_gen.generate() est = estnode.export_pipeline() @pytest.mark.skipif(sys.platform == 'darwin', reason="sklearnex dependency not available on macOS") def test_loop_through_groupnames(): n_classes=3 n_samples=100 n_features=100 random_state=None # Check if skrebate is installed is_skrebate_installed = importlib.util.find_spec("skrebate") is not None # Check if sklearnx is installed is_sklearnx_installed = importlib.util.find_spec("sklearnx") is not None if is_skrebate_installed: warnings.warn("skrebate not installed, skipping those estimators") if is_sklearnx_installed: warnings.warn("sklearnx not installed, skipping those estimators") for groupname, group in GROUPNAMES.items(): for class_name in group: print(class_name) estnode_gen = tpot.config.get_search_space(class_name, n_classes=n_classes, n_samples=n_samples, n_features=n_features, random_state=random_state) #generate 10 random hyperparameters and make sure they are all valid for i in range(25): estnode = estnode_gen.generate() est = estnode.export_pipeline() ================================================ FILE: tpot/config/transformers.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal from ConfigSpace import EqualsCondition, OrConjunction, NotEqualsCondition, InCondition import numpy as np Binarizer_configspace = ConfigurationSpace( space = { 'threshold': Float('threshold', bounds=(0.0, 1.0)), } ) Normalizer_configspace = ConfigurationSpace( space={'norm': Categorical('norm', ['l1', 'l2', 'max'])} ) PCA_configspace = ConfigurationSpace( space={'n_components': Float('n_components', bounds=(0.5, 0.999))} ) ZeroCount_configspace = {} PolynomialFeatures_configspace = ConfigurationSpace( space = { 'degree': Integer('degree', bounds=(2, 3)), 'interaction_only': Categorical('interaction_only', [True, False]), } ) OneHotEncoder_configspace = {} #TODO include the parameter for max unique values OrdinalEncoder_configspace = {} #TODO include the parameter for max unique values def get_FastICA_configspace(n_features=100, random_state=None): space = { 'n_components': Integer('n_components', bounds=(1, n_features)), 'algorithm': Categorical('algorithm', ['parallel', 'deflation']), 'whiten':'unit-variance', } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_FeatureAgglomeration_configspace(n_features): linkage = Categorical('linkage', ['ward', 'complete', 'average']) metric = Categorical('metric', ['euclidean', 'l1', 'l2', 'manhattan', 'cosine']) n_clusters = Integer('n_clusters', bounds=(2, min(n_features,400))) pooling_func = Categorical('pooling_func', ['mean', 'median', 'max']) metric_condition = NotEqualsCondition(metric, linkage, 'ward') cs = ConfigurationSpace() cs.add([linkage, metric, n_clusters, pooling_func]) cs.add(metric_condition) return cs def FeatureAgglomeration_hyperparameter_parser(params): new_params = params.copy() if "pooling_func" in new_params: if new_params["pooling_func"] == "mean": new_params["pooling_func"] = np.mean elif new_params["pooling_func"] == "median": new_params["pooling_func"] = np.median elif new_params["pooling_func"] == "max": new_params["pooling_func"] = np.max elif new_params["pooling_func"] == "min": new_params["pooling_func"] = np.min return new_params def get_Nystroem_configspace(n_features=100, random_state=None,): space = { 'gamma': Float('gamma', bounds=(0.0, 1.0)), 'kernel': Categorical('kernel', ['rbf', 'cosine', 'chi2', 'laplacian', 'polynomial', 'poly', 'linear', 'additive_chi2', 'sigmoid']), 'n_components': Integer('n_components', bounds=(1, n_features)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_RBFSampler_configspace(n_features=100, random_state=None): space = { 'gamma': Float('gamma', bounds=(0.0, 1.0)), 'n_components': Integer('n_components', bounds=(1, n_features)), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_QuantileTransformer_configspace(random_state=None, n_samples=1000): space = { 'n_quantiles': Integer('n_quantiles', bounds=(10, n_samples)), 'output_distribution': Categorical('output_distribution', ['uniform', 'normal']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) def get_passkbinsdiscretizer_configspace(random_state=None): space = { 'n_bins': Integer('n_bins', bounds=(3, 100)), 'encode': 'onehot-dense', 'strategy': Categorical('strategy', ['uniform', 'quantile', 'kmeans']), # 'subsample': Categorical('subsample', ['auto', 'warn', 'ignore']), } if random_state is not None: #This is required because configspace doesn't allow None as a value space['random_state'] = random_state return ConfigurationSpace( space = space ) ### ROBUST SCALER RobustScaler_configspace = ConfigurationSpace({ "q_min": Float("q_min", bounds=(0.001, 0.3)), "q_max": Float("q_max", bounds=(0.7, 0.999)), }) def robust_scaler_hyperparameter_parser(params): return {"quantile_range": (params["q_min"], params["q_max"])} ================================================ FILE: tpot/evolvers/__init__.py ================================================ from .base_evolver import * from .steady_state_evolver import * ================================================ FILE: tpot/evolvers/base_evolver.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ #All abstract methods in the Evolutionary_Optimization module from abc import abstractmethod import tpot import typing import tqdm from tpot import BaseIndividual import time import numpy as np import copy import scipy import os import pickle import statistics from tqdm.dask import TqdmCallback import distributed from dask.distributed import Client from dask.distributed import LocalCluster from tpot.selectors import survival_select_NSGA2, tournament_selection_dominated import math from tpot.utils.utils import get_thresholds, beta_interpolation, remove_items, equalize_list import gc # Evolvers allow you to pass in custom mutation and crossover functions. By default, # the evolver will just use these functions to call ind.mutate or ind.crossover def ind_mutate(ind, rng): """ Calls the ind.mutate method on the individual Parameters ---------- ind : tpot.BaseIndividual The individual to mutate rng : int or numpy.random.Generator A numpy random generator to use for reproducibility """ rng = np.random.default_rng(rng) return ind.mutate(rng=rng) def ind_crossover(ind1, ind2, rng): """ Calls the ind1.crossover(ind2, rng=rng) Parameters ---------- ind1 : tpot.BaseIndividual ind2 : tpot.BaseIndividual rng : int or numpy.random.Generator A numpy random generator to use for reproducibility """ rng = np.random.default_rng(rng) return ind1.crossover(ind2, rng=rng) class BaseEvolver(): def __init__( self, individual_generator , objective_functions, objective_function_weights, objective_names = None, objective_kwargs = None, bigger_is_better = True, population_size = 50, initial_population_size = None, population_scaling = .5, generations_until_end_population = 1, generations = 50, early_stop = None, early_stop_tol = 0.001, max_time_mins=float("inf"), max_eval_time_mins=5, n_jobs=1, memory_limit="4GB", client=None, survival_percentage = 1, crossover_probability=.2, mutate_probability=.7, mutate_then_crossover_probability=.05, crossover_then_mutate_probability=.05, mutation_functions = [ind_mutate], crossover_functions = [ind_crossover], mutation_function_weights = None, crossover_function_weights = None, n_parents=2, survival_selector = survival_select_NSGA2, parent_selector = tournament_selection_dominated, budget_range = None, budget_scaling = .5, generations_until_end_budget = 1, stepwise_steps = 5, threshold_evaluation_pruning = None, threshold_evaluation_scaling = .5, min_history_threshold = 20, selection_evaluation_pruning = None, selection_evaluation_scaling = .5, evaluation_early_stop_steps = None, final_score_strategy = "mean", verbose = 0, periodic_checkpoint_folder = None, callback = None, rng=None, ) -> None: """ Uses mutation, crossover, and optimization functions to evolve a population of individuals towards the given objective functions. Parameters ---------- individual_generator : generator Generator that yields new base individuals. Used to generate initial population. objective_functions : list of callables list of functions that get applied to the individual and return a float or list of floats If an objective function returns multiple values, they are all concatenated in order with respect to objective_function_weights and early_stop_tol. objective_function_weights : list of floats list of weights for each objective function. Sign flips whether bigger is better or not objective_names : list of strings, default=None Names of the objectives. If None, objective0, objective1, etc. will be used objective_kwargs : dict, default=None Dictionary of keyword arguments to pass to the objective function bigger_is_better : bool, default=True If True, the objective function is maximized. If False, the objective function is minimized. Use negative weights to reverse the direction. population_size : int, default=50 Size of the population initial_population_size : int, default=None Size of the initial population. If None, population_size will be used. population_scaling : int, default=0.5 Scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. generations_until_end_population : int, default=1 Number of generations until the population size reaches population_size generations : int, default=50 Number of generations to run early_stop : int, default=None Number of generations without improvement before early stopping. All objectives must have converged within the tolerance for this to be triggered. In general a value of around 5-20 is good. early_stop_tol : float, list of floats, or None, default=0.001 -list of floats list of tolerances for each objective function. If the difference between the best score and the current score is less than the tolerance, the individual is considered to have converged If an index of the list is None, that item will not be used for early stopping -int If an int is given, it will be used as the tolerance for all objectives max_time_mins : float, default=float("inf") Maximum time to run the optimization. If none or inf, will run until the end of the generations. max_eval_time_mins : float, default=10 Maximum time to evaluate a single individual. If none or inf, there will be no time limit per evaluation. n_jobs : int, default=1 Number of processes to run in parallel. memory_limit : str, default=None Memory limit for each job. See Dask [LocalCluster documentation](https://distributed.dask.org/en/stable/api.html#distributed.Client) for more information. client : dask.distributed.Client, default=None A dask client to use for parallelization. If not None, this will override the n_jobs and memory_limit parameters. If None, will create a new client with num_workers=n_jobs and memory_limit=memory_limit. survival_percentage : float, default=1 Percentage of the population size to utilize for mutation and crossover at the beginning of the generation. The rest are discarded. Individuals are selected with the selector passed into survival_selector. The value of this parameter must be between 0 and 1, inclusive. For example, if the population size is 100 and the survival percentage is .5, 50 individuals will be selected with NSGA2 from the existing population. These will be used for mutation and crossover to generate the next 100 individuals for the next generation. The remainder are discarded from the live population. In the next generation, there will now be the 50 parents + the 100 individuals for a total of 150. Surivival percentage is based of the population size parameter and not the existing population size (current population size when using successive halving). Therefore, in the next generation we will still select 50 individuals from the currently existing 150. crossover_probability : float, default=.2 Probability of generating a new individual by crossover between two individuals. mutate_probability : float, default=.7 Probability of generating a new individual by crossover between one individuals. mutate_then_crossover_probability : float, default=.05 Probability of generating a new individual by mutating two individuals followed by crossover. crossover_then_mutate_probability : float, default=.05 Probability of generating a new individual by crossover between two individuals followed by a mutation of the resulting individual. n_parents : int, default=2 Number of parents to use for crossover. Must be greater than 1. survival_selector : function, default=survival_select_NSGA2 Function to use to select individuals for survival. Must take a matrix of scores and return selected indexes. Used to selected population_size * survival_percentage individuals at the start of each generation to use for mutation and crossover. parent_selector : function, default=parent_select_NSGA2 Function to use to select pairs parents for crossover and individuals for mutation. Must take a matrix of scores and return selected indexes. budget_range : list [start, end], default=None This parameter is used for the successive halving algorithm. A starting and ending budget to use for the budget scaling. The evolver will interpolate between these values over the generations_until_end_budget. Use is dependent on the objective functions. (In TPOTEstimator this corresponds to the percentage of the data to sample.) budget_scaling float : [0,1], default=0.5 A scaling factor to use when determining how fast we move the budget from the start to end budget. generations_until_end_budget : int, default=1 The number of generations to run before reaching the max budget. stepwise_steps : int, default=1 The number of staircase steps to take when interpolating the budget and population size. threshold_evaluation_pruning : list [start, end], default=None Starting and ending percentile to use as a threshold for the evaluation early stopping. The evolver will interpolate between these values over the evaluation_early_stop_steps. Values between 0 and 100. At each step of the evaluation, a threshold is calculated based on the previous evaluations. All individuals that are below the performance threshold are not evaluated for further steps. For example, if the threshold is set to the 90th percentile of the previous evaluations, all individuals that are below the 90th percentile are not evaluated further. This can save computation by not evaluating all individuals for all steps of cross validation. threshold_evaluation_scaling : float [0,inf), default=0.5 A scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. Must be greater than zero. Higher numbers will move the threshold to the end faster. min_history_threshold : int, default=0 The minimum number of previous scores needed before using threshold early stopping. selection_evaluation_pruning : list, default=None A lower and upper percent of the population size to select each round of CV. Values between 0 and 1. Selects a percentage of the population to evaluate at each step of the evaluation. For example, one strategy is to evaluate different steps of cross validation one at a time, and only select the best N individuals for subsequent steps. This can save computation by not evaluating all individuals for all steps of cross validation. By default this selection is done with the NSGA2 selector. selection_evaluation_scaling : float, default=0.5 A scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. Must be greater than zero. Higher numbers will move the threshold to the end faster. evaluation_early_stop_steps : int, default=1 The number of steps that will be taken from the objective function. (e.g., the number of CV folds to evaluate) final_score_strategy : str, default="mean" The strategy to use when determining the final score for an individual. "mean": The mean of all objective scores "last": The score returned by the last call. Currently each objective is evaluated with a clone of the individual. verbose : int, default=0 How much information to print during the optimization process. Higher values include the information from lower values. 0. nothing 1. progress bar 2. evaluations progress bar 3. best individual 4. warnings >=5. full warnings trace periodic_checkpoint_folder : str, default=None Folder to save the population to periodically. If None, no periodic saving will be done. If provided, training will resume from this checkpoint. callback : tpot.CallBackInterface, default=None Callback object. Not implemented rng : Numpy.Random.Generator, None, default=None An object for reproducability of experiments. This value will be passed to numpy.random.default_rng() to create an instnce of the genrator to pass to other classes - Numpy.Random.Generator Will be used to create and lock in Generator instance with 'numpy.random.default_rng()'. Note this will be the same Generator passed in. - None Will be used to create Generator for 'numpy.random.default_rng()' where a fresh, unpredictable entropy will be pulled from the OS Attributes ---------- population : tpot.Population The population of individuals. Use population.population to access the individuals in the current population. Use population.evaluated_individuals to access a data frame of all individuals that have been explored. """ self.rng = np.random.default_rng(rng) if threshold_evaluation_pruning is not None or selection_evaluation_pruning is not None: if evaluation_early_stop_steps is None: raise ValueError("evaluation_early_stop_steps must be set when using threshold_evaluation_pruning or selection_evaluation_pruning") self.individual_generator = individual_generator self.population_size = population_size self.objective_functions = objective_functions self.objective_function_weights = np.array(objective_function_weights) self.bigger_is_better = bigger_is_better if not bigger_is_better: self.objective_function_weights = np.array(self.objective_function_weights)*-1 self.initial_population_size = initial_population_size if self.initial_population_size is None: self.cur_population_size = population_size else: self.cur_population_size = initial_population_size self.population_scaling = population_scaling self.generations_until_end_population = generations_until_end_population self.population_size_list = None self.periodic_checkpoint_folder = periodic_checkpoint_folder self.verbose = verbose self.callback = callback self.generations = generations self.n_jobs = n_jobs if max_time_mins is None: self.max_time_mins = float("inf") else: self.max_time_mins = max_time_mins self.max_eval_time_mins = max_eval_time_mins self.generation = 0 self.threshold_evaluation_pruning =threshold_evaluation_pruning self.threshold_evaluation_scaling = max(0.00001,threshold_evaluation_scaling ) self.min_history_threshold = min_history_threshold self.selection_evaluation_pruning = selection_evaluation_pruning self.selection_evaluation_scaling = max(0.00001,selection_evaluation_scaling ) self.evaluation_early_stop_steps = evaluation_early_stop_steps self.final_score_strategy = final_score_strategy self.budget_range = budget_range self.budget_scaling = budget_scaling self.generations_until_end_budget = generations_until_end_budget self.stepwise_steps = stepwise_steps self.memory_limit = memory_limit self.client = client self.survival_selector=survival_selector self.parent_selector=parent_selector self.survival_percentage = survival_percentage total_var_p = crossover_probability + mutate_probability + mutate_then_crossover_probability + crossover_then_mutate_probability self.crossover_probability = crossover_probability / total_var_p self.mutate_probability = mutate_probability / total_var_p self.mutate_then_crossover_probability= mutate_then_crossover_probability / total_var_p self.crossover_then_mutate_probability= crossover_then_mutate_probability / total_var_p self.mutation_functions = mutation_functions self.crossover_functions = crossover_functions if mutation_function_weights is None: self.mutation_function_weights = [1 for _ in range(len(mutation_functions))] else: self.mutation_function_weights = mutation_function_weights if mutation_function_weights is None: self.crossover_function_weights = [1 for _ in range(len(mutation_functions))] else: self.crossover_function_weights = crossover_function_weights self.n_parents = n_parents if objective_kwargs is None: self.objective_kwargs = {} else: self.objective_kwargs = objective_kwargs # if objective_kwargs is None: # self.objective_kwargs = [{}] * len(self.objective_functions) # elif isinstance(objective_kwargs, dict): # self.objective_kwargs = [objective_kwargs] * len(self.objective_functions) # else: # self.objective_kwargs = objective_kwargs ########### if self.initial_population_size != self.population_size: self.population_size_list = beta_interpolation(start=self.cur_population_size, end=self.population_size, scale=self.population_scaling, n=generations_until_end_population, n_steps=self.stepwise_steps) self.population_size_list = np.round(self.population_size_list).astype(int) if self.budget_range is None: self.budget_list = None else: self.budget_list = beta_interpolation(start=self.budget_range[0], end=self.budget_range[1], n=self.generations_until_end_budget, scale=self.budget_scaling, n_steps=self.stepwise_steps) if objective_names is None: self.objective_names = ["objective"+str(i) for i in range(len(objective_function_weights))] else: self.objective_names = objective_names if self.budget_list is not None: if len(self.budget_list) <= self.generation: self.budget = self.budget_list[-1] else: self.budget = self.budget_list[self.generation] else: self.budget = None self.early_stop_tol = early_stop_tol self.early_stop = early_stop if isinstance(self.early_stop_tol, float): self.early_stop_tol = [self.early_stop_tol for _ in range(len(self.objective_names))] self.early_stop_tol = [np.inf if tol is None else tol for tol in self.early_stop_tol] self.population = None self.population_file = None if self.periodic_checkpoint_folder is not None: self.population_file = os.path.join(self.periodic_checkpoint_folder, "population.pkl") if not os.path.exists(self.periodic_checkpoint_folder): os.makedirs(self.periodic_checkpoint_folder) if os.path.exists(self.population_file): self.population = pickle.load(open(self.population_file, "rb")) if len(self.population.evaluated_individuals)>0 and "Generation" in self.population.evaluated_individuals.columns: self.generation = self.population.evaluated_individuals['Generation'].max() + 1 #TODO check if this is empty? init_names = self.objective_names if self.budget_range is not None: init_names = init_names + ["Budget"] if self.population is None: self.population = tpot.Population(column_names=init_names) initial_population = [next(self.individual_generator) for _ in range(self.cur_population_size)] self.population.add_to_population(initial_population, self.rng) self.population.update_column(self.population.population, column_names="Generation", data=self.generation) def optimize(self, generations=None): """ Creates an initial population and runs the evolutionary algorithm for the given number of generations. If generations is None, will use self.generations. Parameters ---------- generations : int, default=None Number of generations to run. If None, will use self.generations. """ if self.client is not None: #If user passed in a client manually self._client = self.client else: if self.verbose >= 4: silence_logs = 30 elif self.verbose >=5: silence_logs = 40 else: silence_logs = 50 self._cluster = LocalCluster(n_workers=self.n_jobs, #if no client is passed in and no global client exists, create our own threads_per_worker=1, silence_logs=silence_logs, processes=True, memory_limit=self.memory_limit) self._client = Client(self._cluster) if generations is None: generations = self.generations start_time = time.time() generations_without_improvement = np.array([0 for _ in range(len(self.objective_function_weights))]) best_scores = [-np.inf for _ in range(len(self.objective_function_weights))] self.scheduled_timeout_time = time.time() + self.max_time_mins*60 try: #for gen in tnrange(generations,desc="Generation", disable=self.verbose<1): done = False gen = 0 if self.verbose >= 1: if generations is None or np.isinf(generations): pbar = tqdm.tqdm(total=0) else: pbar = tqdm.tqdm(total=generations) pbar.set_description("Generation") while not done: # Generation 0 is the initial population if self.generation == 0: if self.population_file is not None: pickle.dump(self.population, open(self.population_file, "wb")) self.evaluate_population() if self.population_file is not None: pickle.dump(self.population, open(self.population_file, "wb")) attempts = 2 while len(self.population.population) == 0 and attempts > 0: new_initial_population = [next(self.individual_generator) for _ in range(self.cur_population_size)] self.population.add_to_population(new_initial_population, rng=self.rng) attempts -= 1 self.evaluate_population() if len(self.population.population) == 0: raise Exception("No individuals could be evaluated in the initial population. This may indicate a bug in the configuration, included models, or objective functions. Set verbose>=4 to see the errors that caused individuals to fail.") self.generation += 1 # Generation 1 is the first generation after the initial population else: if time.time() - start_time > self.max_time_mins*60: if self.population.evaluated_individuals[self.objective_names].isnull().all().iloc[0]: raise Exception("No individuals could be evaluated in the initial population as the max_eval_mins time limit was reached before any individuals could be evaluated.") break self.step() if self.verbose >= 3: sign = np.sign(self.objective_function_weights) valid_df = self.population.evaluated_individuals[~self.population.evaluated_individuals[self.objective_names].isin(["TIMEOUT","INVALID"]).any(axis=1)][self.objective_names]*sign cur_best_scores = valid_df.max(axis=0)*sign cur_best_scores = cur_best_scores.to_numpy() print("Generation: ", self.generation) for i, obj in enumerate(self.objective_names): print(f"Best {obj} score: {cur_best_scores[i]}") if self.early_stop: if self.budget is None or self.budget>=self.budget_range[-1]: #self.budget>=1: #get sign of objective_function_weights sign = np.sign(self.objective_function_weights) #get best score for each objective valid_df = self.population.evaluated_individuals[~self.population.evaluated_individuals[self.objective_names].isin(["TIMEOUT","INVALID"]).any(axis=1)][self.objective_names]*sign cur_best_scores = valid_df.max(axis=0) cur_best_scores = cur_best_scores.to_numpy() #cur_best_scores = self.population.get_column(self.population.population, column_names=self.objective_names).max(axis=0)*sign #TODO this assumes the current population is the best improved = ( np.array(cur_best_scores) - np.array(best_scores) >= np.array(self.early_stop_tol) ) not_improved = np.logical_not(improved) generations_without_improvement = generations_without_improvement * not_improved + not_improved #set to zero if not improved, else increment pass #update best score best_scores = [max(best_scores[i], cur_best_scores[i]) for i in range(len(self.objective_names))] if all(generations_without_improvement>self.early_stop): if self.verbose >= 3: print("Early stop") break #save population if self.population_file is not None: # and time.time() - last_save_time > 60*10: pickle.dump(self.population, open(self.population_file, "wb")) gen += 1 if self.verbose >= 1: pbar.update(1) if generations is not None and gen >= generations: done = True except KeyboardInterrupt: if self.verbose >= 3: print("KeyboardInterrupt") self.population.remove_invalid_from_population(column_names=self.objective_names, invalid_value="INVALID") self.population.remove_invalid_from_population(column_names=self.objective_names, invalid_value="TIMEOUT") self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="INVALID") self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="TIMEOUT") if self.population_file is not None: pickle.dump(self.population, open(self.population_file, "wb")) if self.client is None: #If we created our own client, close it self._client.close() self._cluster.close() tpot.utils.get_pareto_frontier(self.population.evaluated_individuals, column_names=self.objective_names, weights=self.objective_function_weights) def step(self,): """ Runs a single generation of the evolutionary algorithm. This includes selecting individuals for survival, generating offspring, and evaluating the offspring. """ if self.population_size_list is not None: if self.generation < len(self.population_size_list): self.cur_population_size = self.population_size_list[self.generation] else: self.cur_population_size = self.population_size if self.budget_list is not None: if len(self.budget_list) <= self.generation: self.budget = self.budget_range[-1] else: self.budget = self.budget_list[self.generation] else: self.budget = None if self.survival_selector is not None: n_survivors = max(1,int(self.cur_population_size*self.survival_percentage)) #always keep at least one individual self.population.survival_select( selector=self.survival_selector, weights=self.objective_function_weights, columns_names=self.objective_names, n_survivors=n_survivors, inplace=True, rng=self.rng,) self.generate_offspring() self.evaluate_population() self.generation += 1 def generate_offspring(self, ): #your EA Algorithm goes here """ Create population_size new individuals from the current population. This includes selecting parents, applying mutation and crossover, and adding the new individuals to the population. """ parents = self.population.parent_select(selector=self.parent_selector, weights=self.objective_function_weights, columns_names=self.objective_names, k=self.cur_population_size, n_parents=2, rng=self.rng) p = np.array([self.crossover_probability, self.mutate_then_crossover_probability, self.crossover_then_mutate_probability, self.mutate_probability]) p = p / p.sum() var_op_list = self.rng.choice(["crossover", "mutate_then_crossover", "crossover_then_mutate", "mutate"], size=self.cur_population_size, p=p) for i, op in enumerate(var_op_list): if op == "mutate": parents[i] = parents[i][0] #mutations take a single individual offspring = self.population.create_offspring2(parents, var_op_list, self.mutation_functions, self.mutation_function_weights, self.crossover_functions, self.crossover_function_weights, add_to_population=True, keep_repeats=False, mutate_until_unique=True, rng=self.rng) self.population.update_column(offspring, column_names="Generation", data=self.generation, ) # Gets a list of unevaluated individuals in the livepopulation, evaluates them, and removes failed attempts # TODO This could probably be an independent function? def evaluate_population(self,): """ Evaluates the individuals in the population that have not been evaluated yet. """ #Update the sliding scales and thresholds # Save population, TODO remove some of these if self.population_file is not None: # and time.time() - last_save_time > 60*10: pickle.dump(self.population, open(self.population_file, "wb")) last_save_time = time.time() #Get the current thresholds per step self.thresholds = None if self.threshold_evaluation_pruning is not None: old_data = self.population.evaluated_individuals[self.objective_names] old_data = old_data[old_data[self.objective_names].notnull().all(axis=1)] if len(old_data) >= self.min_history_threshold: self.thresholds = np.array([get_thresholds(old_data[obj_name], start=self.threshold_evaluation_pruning[0], end=self.threshold_evaluation_pruning[1], scale=self.threshold_evaluation_scaling, n=self.evaluation_early_stop_steps) for obj_name in self.objective_names]).T #Get the selectors survival rates per step if self.selection_evaluation_pruning is not None: lower = self.cur_population_size*self.selection_evaluation_pruning[0] upper = self.cur_population_size*self.selection_evaluation_pruning[1] #survival_counts = self.cur_population_size*(scipy.special.betainc(1,self.selection_evaluation_scaling,np.linspace(0,1,self.evaluation_early_stop_steps))*(upper-lower)+lower) survival_counts = np.array(beta_interpolation(start=lower, end=upper, scale=self.selection_evaluation_scaling, n=self.evaluation_early_stop_steps, n_steps=self.evaluation_early_stop_steps)) self.survival_counts = survival_counts.astype(int) else: self.survival_counts = None if self.evaluation_early_stop_steps is not None: if self.survival_counts is None: #TODO if we are not using selection method for each step, we can create single threads that run all steps for an individual. No need to come back each step. self.evaluate_population_selection_early_stop(survival_counts=self.survival_counts, thresholds=self.thresholds, budget=self.budget) else: #parallelize one step at a time. After each step, come together and select the next individuals to run the next step on. self.evaluate_population_selection_early_stop(survival_counts=self.survival_counts, thresholds=self.thresholds, budget=self.budget) else: self.evaluate_population_full(budget=self.budget) # Save population, TODO remove some of these if self.population_file is not None: # and time.time() - last_save_time > 60*10: pickle.dump(self.population, open(self.population_file, "wb")) last_save_time = time.time() def evaluate_population_full(self, budget=None): """ Evaluates all individuals in the population that have not been evaluated yet. This is the normal/default strategy for evaluating individuals without any early stopping of individual evaluation functions. (e.g., no threshold or selection early stopping). Early stopping by generation is still possible. """ individuals_to_evaluate = self.get_unevaluated_individuals(self.objective_names, budget=budget,) #print("evaluating this many individuals: ", len(individuals_to_evaluate)) if len(individuals_to_evaluate) == 0: if self.verbose > 3: print("No new individuals to evaluate") return if self.max_eval_time_mins is not None: theoretical_timeout = self.max_eval_time_mins * math.ceil(len(individuals_to_evaluate) / self.n_jobs) theoretical_timeout = theoretical_timeout*2 else: theoretical_timeout = np.inf scheduled_timeout_time_left = self.scheduled_timeout_time - time.time() parallel_timeout = min(theoretical_timeout, scheduled_timeout_time_left) if parallel_timeout < 0: parallel_timeout = 10 scores, start_times, end_times, eval_errors = tpot.utils.eval_utils.parallel_eval_objective_list(individuals_to_evaluate, self.objective_functions, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, budget=budget, n_expected_columns=len(self.objective_names), client=self._client, scheduled_timeout_time=self.scheduled_timeout_time, **self.objective_kwargs) self.population.update_column(individuals_to_evaluate, column_names=self.objective_names, data=scores) if budget is not None: self.population.update_column(individuals_to_evaluate, column_names="Budget", data=budget) self.population.update_column(individuals_to_evaluate, column_names="Submitted Timestamp", data=start_times) self.population.update_column(individuals_to_evaluate, column_names="Completed Timestamp", data=end_times) self.population.update_column(individuals_to_evaluate, column_names="Eval Error", data=eval_errors) self.population.remove_invalid_from_population(column_names="Eval Error") self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="TIMEOUT") def get_unevaluated_individuals(self, column_names, budget=None, individual_list=None): """ This function is used to get a list of individuals in the current population that have not been evaluated yet. Parameters ---------- column_names : list of strings Names of the columns to check for unevaluated individuals (generally objective functions). budget : float, default=None Budget to use when checking for unevaluated individuals. If None, will not check the budget column. Finds individuals who have not been evaluated with the given budget on column names. individual_list : list of individuals, default=None List of individuals to check for unevaluated individuals. If None, will use the current population. """ if individual_list is not None: cur_pop = np.array(individual_list) else: cur_pop = np.array(self.population.population) if all([name_step in self.population.evaluated_individuals.columns for name_step in column_names]): if budget is not None: offspring_scores = self.population.get_column(cur_pop, column_names=column_names+["Budget"], to_numpy=False) #Individuals are unevaluated if we have a higher budget OR if any of the objectives are nan unevaluated_filter = lambda i: any(offspring_scores.loc[offspring_scores.index[i]][column_names].isna()) or (offspring_scores.loc[offspring_scores.index[i]]["Budget"] < budget) else: offspring_scores = self.population.get_column(cur_pop, column_names=column_names, to_numpy=False) unevaluated_filter = lambda i: any(offspring_scores.loc[offspring_scores.index[i]][column_names].isna()) unevaluated_individuals_this_step = [i for i in range(len(cur_pop)) if unevaluated_filter(i)] return cur_pop[unevaluated_individuals_this_step] else: #if column names are not in the evaluated_individuals, then we have not evaluated any individuals yet for name_step in column_names: self.population.evaluated_individuals[name_step] = np.nan return cur_pop def evaluate_population_selection_early_stop(self,survival_counts, thresholds=None, budget=None): """ This function tries to save computation by partially evaluating the individuals and then selecting which individuals to evaluate further based on the results of the partial evaluation. Two strategies are implemented: 1. Selection early stopping: Selects a percentage of the population to evaluate at each step of the evaluation. for example, one strategy is to evaluate different steps of cross validation one at a time, and only select the best N individuals for subsequent steps. This can save computation by not evaluating all individuals for all steps of cross validation. By default this selection is done with the NSGA2 selector. 2. Threshold early stopping: At each step of the evaluation, a threshold is calculated based on the previous evaluations. All individuals that are below the performance threshold are not evaluated for further steps. For example, if the threshold is set to the 90th percentile of the previous evaluations, all individuals that are below the 90th percentile are not evaluated further. This can save computation by not evaluating all individuals for all steps of cross validation. Both of these strategies can be used simultaneously. Individuals must pass both the selection and threshold criteria to be evaluated further. Parameters ---------- survival_counts : list of ints, default=None Number of individuals to select for survival at each step of the evaluation. If None, will not use selection early stopping. For example: [10, 5, 2] would select 10 individuals for the first step, 5 for the second, and 2 for the third. thresholds : list of floats, default=None Thresholds to use for early stopping at each step of the evaluation. If None, will not use threshold early stopping. budget : float, default=None Budget to use when evaluating individuals. Use is dependent on the objective functions. (In TPOTEstimator this corresponds to the percentage of the data to sample.) """ survival_selector = tpot.selectors.survival_select_NSGA2 ################ objective_function_signs = np.sign(self.objective_function_weights) cur_individuals = self.population.population.copy() all_step_names = [] for step in range(self.evaluation_early_stop_steps): if budget is None: this_step_names = [f"{n}_step_{step}" for n in self.objective_names] else: this_step_names = [f"{n}_budget_{budget}_step_{step}" for n in self.objective_names] all_step_names.append(this_step_names) unevaluated_individuals_this_step = self.get_unevaluated_individuals(this_step_names, budget=None, individual_list=cur_individuals) if len(unevaluated_individuals_this_step) == 0: if self.verbose > 3: print("No new individuals to evaluate") continue if self.max_eval_time_mins is not None: theoretical_timeout = self.max_eval_time_mins * math.ceil(len(unevaluated_individuals_this_step) / self.n_jobs)*60 theoretical_timeout = theoretical_timeout*2 else: theoretical_timeout = np.inf scheduled_timeout_time_left = self.scheduled_timeout_time - time.time() parallel_timeout = min(theoretical_timeout, scheduled_timeout_time_left) if parallel_timeout < 0: parallel_timeout = 10 scores, start_times, end_times, eval_errors = tpot.utils.eval_utils.parallel_eval_objective_list(individual_list=unevaluated_individuals_this_step, objective_list=self.objective_functions, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, step=step, budget = self.budget, generation = self.generation, n_expected_columns=len(self.objective_names), client=self._client, scheduled_timeout_time=self.scheduled_timeout_time, **self.objective_kwargs, ) self.population.update_column(unevaluated_individuals_this_step, column_names=this_step_names, data=scores) self.population.update_column(unevaluated_individuals_this_step, column_names="Submitted Timestamp", data=start_times) self.population.update_column(unevaluated_individuals_this_step, column_names="Completed Timestamp", data=end_times) self.population.update_column(unevaluated_individuals_this_step, column_names="Eval Error", data=eval_errors) self.population.remove_invalid_from_population(column_names="Eval Error") self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="TIMEOUT") #remove invalids: invalids = [] #find indeces of invalids for j in range(len(scores)): if any([s=="INVALID" for s in scores[j]]): invalids.append(j) for j in range(len(scores)): if any([s=="TIMEOUT" for s in scores[j]]): invalids.append(j) #already evaluated already_evaluated = list(set(cur_individuals) - set(unevaluated_individuals_this_step)) #evaluated and valid valid_evaluations_this_step = remove_items(unevaluated_individuals_this_step,invalids) #update cur_individuals with current individuals with valid scores cur_individuals = np.concatenate([already_evaluated, valid_evaluations_this_step]) #Get average scores #array of shape (steps, individuals, objectives) offspring_scores = [self.population.get_column(cur_individuals, column_names=step_names) for step_names in all_step_names] offspring_scores = np.array(offspring_scores) if self.final_score_strategy == 'mean': offspring_scores = offspring_scores.mean(axis=0) elif self.final_score_strategy == 'last': offspring_scores = offspring_scores[-1] #remove individuals with nan scores invalids = [] for i in range(len(offspring_scores)): if any(np.isnan(offspring_scores[i])): invalids.append(i) cur_individuals = remove_items(cur_individuals,invalids) offspring_scores = remove_items(offspring_scores,invalids) #if last step, add the final metrics if step == self.evaluation_early_stop_steps-1: self.population.update_column(cur_individuals, column_names=self.objective_names, data=offspring_scores) if budget is not None: self.population.update_column(cur_individuals, column_names="Budget", data=budget) return #If we have more threads than remaining individuals, we may as well evaluate the extras too if self.n_jobs < len(cur_individuals): #Remove based on thresholds if thresholds is not None: threshold = thresholds[step] invalids = [] for i in range(len(offspring_scores)): if all([s*w>t*w for s,t,w in zip(offspring_scores[i],threshold,objective_function_signs) ]): invalids.append(i) if len(invalids) > 0: max_to_remove = min(len(cur_individuals) - self.n_jobs, len(invalids)) if max_to_remove < len(invalids): # invalids = np.random.choice(invalids, max_to_remove, replace=False) invalids = self.rng.choice(invalids, max_to_remove, replace=False) cur_individuals = remove_items(cur_individuals,invalids) offspring_scores = remove_items(offspring_scores,invalids) # Remove based on selection if survival_counts is not None: if step < self.evaluation_early_stop_steps - 1 and survival_counts[step]>1: #don't do selection for the last loop since they are completed k = survival_counts[step] + len(invalids) #TODO can remove the min if the selections method can ignore k>population size if len(cur_individuals)> 1 and k > self.n_jobs and k < len(cur_individuals): weighted_scores = np.array([s * self.objective_function_weights for s in offspring_scores ]) new_population_index = survival_selector(weighted_scores, k=k) cur_individuals = np.array(cur_individuals)[new_population_index] offspring_scores = offspring_scores[new_population_index] ================================================ FILE: tpot/evolvers/steady_state_evolver.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ #All abstract methods in the Evolutionary_Optimization module import tpot import typing import tqdm import time import numpy as np import os import pickle from tqdm.dask import TqdmCallback import distributed from dask.distributed import Client from dask.distributed import LocalCluster from tpot.selectors import survival_select_NSGA2, tournament_selection_dominated import math from tpot.utils.utils import get_thresholds, beta_interpolation, remove_items, equalize_list import dask import warnings import gc # Evolvers allow you to pass in custom mutation and crossover functions. By default, # the evolver will just use these functions to call ind.mutate or ind.crossover def ind_mutate(ind, rng): """ Calls the ind.mutate method on the individual Parameters ---------- ind : tpot.BaseIndividual The individual to mutate rng : int or numpy.random.Generator A numpy random generator to use for reproducibility """ rng = np.random.default_rng(rng) return ind.mutate(rng=rng) def ind_crossover(ind1, ind2, rng): """ Calls the ind1.crossover(ind2, rng=rng) Parameters ---------- ind1 : tpot.BaseIndividual ind2 : tpot.BaseIndividual rng : int or numpy.random.Generator A numpy random generator to use for reproducibility """ rng = np.random.default_rng(rng) return ind1.crossover(ind2, rng=rng) class SteadyStateEvolver(): def __init__( self, individual_generator , objective_functions, objective_function_weights, objective_names = None, objective_kwargs = None, bigger_is_better = True, initial_population_size = 50, population_size = 300, max_evaluated_individuals = None, early_stop = None, early_stop_mins = None, early_stop_tol = 0.001, max_time_mins=float("inf"), max_eval_time_mins=10, n_jobs=1, memory_limit="4GB", client=None, crossover_probability=.2, mutate_probability=.7, mutate_then_crossover_probability=.05, crossover_then_mutate_probability=.05, n_parents=2, survival_selector = survival_select_NSGA2, parent_selector = tournament_selection_dominated, budget_range = None, budget_scaling = .5, individuals_until_end_budget = 1, stepwise_steps = 5, verbose = 0, periodic_checkpoint_folder = None, callback = None, rng=None ) -> None: """ Whereas the base_evolver uses a generational approach, the steady state evolver continuously generates individuals as resources become available. This evolver will simultaneously evaluated n_jobs individuals. As soon as one individual is evaluated, the current population is updated with survival_selector, a new individual is generated from parents selected with parent_selector, and the new individual is immediately submitted for evaluation. In contrast, the base_evolver batches evaluations in generations, and only updates the population and creates new individuals after all individuals in the current generation are evaluated. In practice, this means that steady state evolver is more likely to use all cores at all times, allowing for flexibility is duration of evaluations and number of evaluations. However, it may also generate less diverse populations as a result. Parameters ---------- individual_generator : generator Generator that yields new base individuals. Used to generate initial population. objective_functions : list of callables list of functions that get applied to the individual and return a float or list of floats If an objective function returns multiple values, they are all concatenated in order with respect to objective_function_weights and early_stop_tol. objective_function_weights : list of floats list of weights for each objective function. Sign flips whether bigger is better or not objective_names : list of strings, default=None Names of the objectives. If None, objective0, objective1, etc. will be used objective_kwargs : dict, default=None Dictionary of keyword arguments to pass to the objective function bigger_is_better : bool, default=True If True, the objective function is maximized. If False, the objective function is minimized. Use negative weights to reverse the direction. initial_population_size : int, default=50 Number of random individuals to generate in the initial population. These will all be randomly sampled, all other subsequent individuals will be generated from the population. population_size : int, default=50 Note: This is different from the base_evolver. In steady_state_evolver, the population_size is the number of individuals to keep in the live population. This is the total number of best individuals (as determined by survival_selector) to keep in the population. New individuals are generated from this population size. In base evolver, this is also the number of individuals to generate in each generation, however, here, we generate individuals as resources become available so there is no concept of a generation. It is recommended to use a higher population_size to ensure diversity in the population. max_evaluated_individuals : int, default=None Maximum number of individuals to evaluate after which training is terminated. If None, will evaluate until time limit is reached. early_stop : int, default=None If the best individual has not improved in this many evaluations, stop training. Note: Also different from base_evolver. In base evolver, this is the number of generations without improvement. Here, it is the number of individuals evaluated without improvement. Naturally, a higher value is recommended. early_stop_mins : int, default=None If the best individual has not improved in this many minutes, stop training. early_stop_tol : float, list of floats, or None, default=0.001 -list of floats list of tolerances for each objective function. If the difference between the best score and the current score is less than the tolerance, the individual is considered to have converged If an index of the list is None, that item will not be used for early stopping -int If an int is given, it will be used as the tolerance for all objectives max_time_mins : float, default=float("inf") Maximum time to run the optimization. If none or inf, will run until the end of the generations. max_eval_time_mins : float, default=10 Maximum time to evaluate a single individual. If none or inf, there will be no time limit per evaluation. n_jobs : int, default=1 Number of processes to run in parallel. memory_limit : str, default=None Memory limit for each job. See Dask [LocalCluster documentation](https://distributed.dask.org/en/stable/api.html#distributed.Client) for more information. client : dask.distributed.Client, default=None A dask client to use for parallelization. If not None, this will override the n_jobs and memory_limit parameters. If None, will create a new client with num_workers=n_jobs and memory_limit=memory_limit. crossover_probability : float, default=.2 Probability of generating a new individual by crossover between two individuals. mutate_probability : float, default=.7 Probability of generating a new individual by crossover between one individuals. mutate_then_crossover_probability : float, default=.05 Probability of generating a new individual by mutating two individuals followed by crossover. crossover_then_mutate_probability : float, default=.05 Probability of generating a new individual by crossover between two individuals followed by a mutation of the resulting individual. n_parents : int, default=2 Number of parents to use for crossover. Must be greater than 1. survival_selector : function, default=survival_select_NSGA2 Function to use to select individuals for survival. Must take a matrix of scores and return selected indexes. Used to selected population_size * survival_percentage individuals at the start of each generation to use for mutation and crossover. parent_selector : function, default=parent_select_NSGA2 Function to use to select pairs parents for crossover and individuals for mutation. Must take a matrix of scores and return selected indexes. budget_range : list [start, end], default=None This parameter is used for the successive halving algorithm. A starting and ending budget to use for the budget scaling. The evolver will interpolate between these values over the generations_until_end_budget. Use is dependent on the objective functions. (In TPOTEstimator this corresponds to the percentage of the data to sample.) budget_scaling float : [0,1], default=0.5 A scaling factor to use when determining how fast we move the budget from the start to end budget. evaluations_until_end_budget : int, default=1 The number of evaluations to run before reaching the max budget. stepwise_steps : int, default=1 The number of staircase steps to take when interpolating the budget. verbose : int, default=0 How much information to print during the optimization process. Higher values include the information from lower values. 0. nothing 1. progress bar 2. evaluations progress bar 3. best individual 4. warnings >=5. full warnings trace periodic_checkpoint_folder : str, default=None Folder to save the population to periodically. If None, no periodic saving will be done. If provided, training will resume from this checkpoint. callback : tpot.CallBackInterface, default=None Callback object. Not implemented rng : Numpy.Random.Generator, None, default=None An object for reproducability of experiments. This value will be passed to numpy.random.default_rng() to create an instnce of the genrator to pass to other classes - Numpy.Random.Generator Will be used to create and lock in Generator instance with 'numpy.random.default_rng()'. Note this will be the same Generator passed in. - None Will be used to create Generator for 'numpy.random.default_rng()' where a fresh, unpredictable entropy will be pulled from the OS Attributes ---------- population : tpot.Population The population of individuals. Use population.population to access the individuals in the current population. Use population.evaluated_individuals to access a data frame of all individuals that have been explored. """ self.rng = np.random.default_rng(rng) self.max_evaluated_individuals = max_evaluated_individuals self.individuals_until_end_budget = individuals_until_end_budget self.individual_generator = individual_generator self.population_size = population_size self.objective_functions = objective_functions self.objective_function_weights = np.array(objective_function_weights) self.bigger_is_better = bigger_is_better if not bigger_is_better: self.objective_function_weights = np.array(self.objective_function_weights)*-1 self.population_size_list = None self.periodic_checkpoint_folder = periodic_checkpoint_folder self.verbose = verbose self.callback = callback self.n_jobs = n_jobs if max_time_mins is None: self.max_time_mins = float("inf") else: self.max_time_mins = max_time_mins #functools requires none for infinite time, doesn't support inf if max_eval_time_mins is not None and math.isinf(max_eval_time_mins ): self.max_eval_time_mins = None else: self.max_eval_time_mins = max_eval_time_mins self.initial_population_size = initial_population_size self.budget_range = budget_range self.budget_scaling = budget_scaling self.stepwise_steps = stepwise_steps self.memory_limit = memory_limit self.client = client self.survival_selector=survival_selector self.parent_selector=parent_selector total_var_p = crossover_probability + mutate_probability + mutate_then_crossover_probability + crossover_then_mutate_probability self.crossover_probability = crossover_probability / total_var_p self.mutate_probability = mutate_probability / total_var_p self.mutate_then_crossover_probability= mutate_then_crossover_probability / total_var_p self.crossover_then_mutate_probability= crossover_then_mutate_probability / total_var_p self.n_parents = n_parents if objective_kwargs is None: self.objective_kwargs = {} else: self.objective_kwargs = objective_kwargs ########### if self.budget_range is None: self.budget_list = None else: self.budget_list = beta_interpolation(start=self.budget_range[0], end=self.budget_range[1], n=self.generations_until_end_budget, scale=self.budget_scaling, n_steps=self.stepwise_steps) if objective_names is None: self.objective_names = ["objective"+str(i) for i in range(len(objective_function_weights))] else: self.objective_names = objective_names if self.budget_list is not None: if len(self.budget_list) <= self.generation: self.budget = self.budget_list[-1] else: self.budget = self.budget_list[self.generation] else: self.budget = None self.early_stop_tol = early_stop_tol self.early_stop_mins = early_stop_mins self.early_stop = early_stop if isinstance(self.early_stop_tol, float): self.early_stop_tol = [self.early_stop_tol for _ in range(len(self.objective_names))] self.early_stop_tol = [np.inf if tol is None else tol for tol in self.early_stop_tol] self.population = None self.population_file = None if self.periodic_checkpoint_folder is not None: self.population_file = os.path.join(self.periodic_checkpoint_folder, "population.pkl") if not os.path.exists(self.periodic_checkpoint_folder): os.makedirs(self.periodic_checkpoint_folder) if os.path.exists(self.population_file): self.population = pickle.load(open(self.population_file, "rb")) init_names = self.objective_names if self.budget_range is not None: init_names = init_names + ["Budget"] if self.population is None: self.population = tpot.Population(column_names=init_names) initial_population = [next(self.individual_generator) for _ in range(self.initial_population_size)] self.population.add_to_population(initial_population, rng=self.rng) def optimize(self): """ Creates an initial population and runs the evolutionary algorithm for the given number of generations. If generations is None, will use self.generations. """ #intialize the client if self.client is not None: #If user passed in a client manually self._client = self.client else: if self.verbose >= 4: silence_logs = 30 elif self.verbose >=5: silence_logs = 40 else: silence_logs = 50 self._cluster = LocalCluster(n_workers=self.n_jobs, #if no client is passed in and no global client exists, create our own threads_per_worker=1, silence_logs=silence_logs, processes=False, memory_limit=self.memory_limit) self._client = Client(self._cluster) self.max_queue_size = len(self._client.cluster.workers) #set up logging params evaluated_count = 0 generations_without_improvement = np.array([0 for _ in range(len(self.objective_function_weights))]) timestamp_of_last_improvement = np.array([time.time() for _ in range(len(self.objective_function_weights))]) best_scores = [-np.inf for _ in range(len(self.objective_function_weights))] scheduled_timeout_time = time.time() + self.max_time_mins*60 budget = None submitted_futures = {} submitted_inds = set() start_time = time.time() try: if self.verbose >= 1: if self.max_evaluated_individuals is not None: pbar = tqdm.tqdm(total=self.max_evaluated_individuals, miniters=1) else: pbar = tqdm.tqdm(total=0, miniters=1) pbar.set_description("Evaluations") #submit initial population individuals_to_evaluate = self.get_unevaluated_individuals(self.objective_names, budget=budget,) for individual in individuals_to_evaluate: if len(submitted_futures) >= self.max_queue_size: break future = self._client.submit(tpot.utils.eval_utils.eval_objective_list, individual, self.objective_functions, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, **self.objective_kwargs) submitted_futures[future] = {"individual": individual, "time": time.time(), "budget": budget,} submitted_inds.add(individual.unique_id()) self.population.update_column(individual, column_names="Submitted Timestamp", data=time.time()) done = False start_time = time.time() enough_parents_evaluated=False while not done: ############################### # Step 1: Check for finished futures ############################### #wait for at least one future to finish or timeout try: if self.max_eval_time_mins is None or math.isinf(self.max_eval_time_mins): next(distributed.as_completed(submitted_futures, timeout=5*60)) else: next(distributed.as_completed(submitted_futures, timeout=self.max_eval_time_mins*60)) except dask.distributed.TimeoutError: pass except dask.distributed.CancelledError: pass #Loop through all futures, collect completed and timeout futures. for completed_future in list(submitted_futures.keys()): eval_error = None #get scores and update if completed_future.done(): #if future is done #If the future is done but threw and error, record the error if completed_future.exception() or completed_future.status == "error": #if the future is done and threw an error print("Exception in future") print(completed_future.exception()) scores = [np.nan for _ in range(len(self.objective_names))] eval_error = "INVALID" elif completed_future.cancelled(): #if the future is done and was cancelled print("Cancelled future (likely memory related)") scores = [np.nan for _ in range(len(self.objective_names))] eval_error = "INVALID" self._client.run(gc.collect) else: #if the future is done and did not throw an error, get the scores try: scores = completed_future.result() #check if scores contain "INVALID" or "TIMEOUT" if "INVALID" in scores: eval_error = "INVALID" scores = [np.nan] elif "TIMEOUT" in scores: eval_error = "TIMEOUT" scores = [np.nan] except Exception as e: print("Exception in future, but not caught by dask") print(e) print(completed_future.exception()) print(completed_future) print("status", completed_future.status) print("done", completed_future.done()) print("cancelld ", completed_future.cancelled()) scores = [np.nan for _ in range(len(self.objective_names))] eval_error = "INVALID" completed_future.release() #release the future else: #if future is not done if (self.max_eval_time_mins is not None) and (not math.isinf(self.max_eval_time_mins)): #if max_eval_time_mins is set to a value #check if the future has been running for too long, cancel the future if time.time() - submitted_futures[completed_future]["time"] > self.max_eval_time_mins*1.25*60: completed_future.cancel() completed_future.release() #release the future if self.verbose >= 4: print(f'WARNING AN INDIVIDUAL TIMED OUT (Fallback): \n {submitted_futures[completed_future]} \n') scores = [np.nan for _ in range(len(self.objective_names))] eval_error = "TIMEOUT" else: continue #otherwise, continue to next future else: #this future is not done and we don't have a time limit so let it keep goooooiiiinnnggggg #there must be another future that did complete continue #update population this_individual = submitted_futures[completed_future]["individual"] this_budget = submitted_futures[completed_future]["budget"] this_time = submitted_futures[completed_future]["time"] if len(scores) < len(self.objective_names): scores = [scores[0] for _ in range(len(self.objective_names))] self.population.update_column(this_individual, column_names=self.objective_names, data=scores) self.population.update_column(this_individual, column_names="Completed Timestamp", data=time.time()) self.population.update_column(this_individual, column_names="Eval Error", data=eval_error) if budget is not None: self.population.update_column(this_individual, column_names="Budget", data=this_budget) submitted_futures.pop(completed_future) submitted_inds.add(this_individual.unique_id()) if self.verbose >= 1: pbar.update(1) #now we have a list of completed futures self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="INVALID") self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="TIMEOUT") #I am not entirely sure if this is necessary. I believe that calling release on the futures should be enough to free up memory. If memory issues persist, this may be a good place to start. #client.run(gc.collect) #run garbage collection to free up memory ############################### # Step 2: Early Stopping ############################### if self.verbose >= 3: sign = np.sign(self.objective_function_weights) valid_df = self.population.evaluated_individuals[~self.population.evaluated_individuals[["Eval Error"]].isin(["TIMEOUT","INVALID"]).any(axis=1)][self.objective_names]*sign cur_best_scores = valid_df.max(axis=0)*sign cur_best_scores = cur_best_scores.to_numpy() for i, obj in enumerate(self.objective_names): print(f"Best {obj} score: {cur_best_scores[i]}") if self.early_stop or self.early_stop_mins: if self.budget is None or self.budget>=self.budget_range[-1]: #self.budget>=1: #get sign of objective_function_weights sign = np.sign(self.objective_function_weights) #get best score for each objective valid_df = self.population.evaluated_individuals[~self.population.evaluated_individuals[["Eval Error"]].isin(["TIMEOUT","INVALID"]).any(axis=1)][self.objective_names]*sign cur_best_scores = valid_df.max(axis=0) cur_best_scores = cur_best_scores.to_numpy() #cur_best_scores = self.population.get_column(self.population.population, column_names=self.objective_names).max(axis=0)*sign #TODO this assumes the current population is the best improved = ( np.array(cur_best_scores) - np.array(best_scores) >= np.array(self.early_stop_tol) ) not_improved = np.logical_not(improved) generations_without_improvement = generations_without_improvement * not_improved + not_improved #set to zero if not improved, else increment timestamp_of_last_improvement = timestamp_of_last_improvement * not_improved + time.time()*improved #set to current time if improved pass #update best score best_scores = [max(best_scores[i], cur_best_scores[i]) for i in range(len(self.objective_names))] if self.early_stop: if all(generations_without_improvement>self.early_stop): if self.verbose >= 3: print(f"Early stop ({self.early_stop} individuals evaluated without improvement)") break if self.early_stop_mins: if any(time.time() - timestamp_of_last_improvement > self.early_stop_mins*60): if self.verbose >= 3: print(f"Early stop ({self.early_stop_mins} seconds passed without improvement)") break #if we evaluated enough individuals or time is up, stop if self.max_time_mins is not None and time.time() - start_time > self.max_time_mins*60: if self.verbose >= 3: print("Time limit reached") done = True break if self.max_evaluated_individuals is not None and len(self.population.evaluated_individuals.dropna(subset=self.objective_names)) >= self.max_evaluated_individuals: print("Evaluated enough individuals") done = True break ############################### # Step 3: Submit unevaluated individuals from the initial population ############################### individuals_to_evaluate = self.get_unevaluated_individuals(self.objective_names, budget=budget,) individuals_to_evaluate = [ind for ind in individuals_to_evaluate if ind.unique_id() not in submitted_inds] for individual in individuals_to_evaluate: if self.max_queue_size > len(submitted_futures): future = self._client.submit(tpot.utils.eval_utils.eval_objective_list, individual, self.objective_functions, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins,**self.objective_kwargs) submitted_futures[future] = {"individual": individual, "time": time.time(), "budget": budget,} submitted_inds.add(individual.unique_id()) self.population.update_column(individual, column_names="Submitted Timestamp", data=time.time()) ############################### # Step 4: Survival Selection ############################### if self.survival_selector is not None: parents_df = self.population.get_column(self.population.population, column_names=self.objective_names + ["Individual"], to_numpy=False) evaluated = parents_df[~parents_df[self.objective_names].isna().any(axis=1)] if len(evaluated) > self.population_size: unevaluated = parents_df[parents_df[self.objective_names].isna().any(axis=1)] cur_evaluated_population = parents_df["Individual"].to_numpy() if len(cur_evaluated_population) > self.population_size: scores = evaluated[self.objective_names].to_numpy() weighted_scores = scores * self.objective_function_weights new_population_index = np.ravel(self.survival_selector(weighted_scores, k=self.population_size, rng=self.rng)) #TODO make it clear that we are concatenating scores... #set new population try: cur_evaluated_population = np.array(cur_evaluated_population)[new_population_index] cur_evaluated_population = np.concatenate([cur_evaluated_population, unevaluated["Individual"].to_numpy()]) self.population.set_population(cur_evaluated_population, rng=self.rng) except Exception as e: print("Exception in survival selection") print(e) print("new_population_index", new_population_index) print("cur_evaluated_population", cur_evaluated_population) print("unevaluated", unevaluated) print("evaluated", evaluated) print("scores", scores) print("weighted_scores", weighted_scores) print("self.objective_function_weights", self.objective_function_weights) print("self.population_size", self.population_size) print("parents_df", parents_df) ############################### # Step 5: Parent Selection and Variation ############################### n_individuals_to_submit = self.max_queue_size - len(submitted_futures) if n_individuals_to_submit > 0: #count non-nan values in the objective columns if not enough_parents_evaluated: parents_df = self.population.get_column(self.population.population, column_names=self.objective_names, to_numpy=False) scores = parents_df[self.objective_names[0]].to_numpy() #count non-nan values in the objective columns n_evaluated = np.count_nonzero(~np.isnan(scores)) if n_evaluated >0 : enough_parents_evaluated=True # parents_df = self.population.get_column(self.population.population, column_names=self.objective_names+ ["Individual"], to_numpy=False) # parents_df = parents_df[~parents_df[self.objective_names].isin(["TIMEOUT","INVALID"]).any(axis=1)] # parents_df = parents_df[~parents_df[self.objective_names].isna().any(axis=1)] # cur_evaluated_population = parents_df["Individual"].to_numpy() # if len(cur_evaluated_population) > 0: # scores = parents_df[self.objective_names].to_numpy() # weighted_scores = scores * self.objective_function_weights # #number of crossover pairs and mutation only parent to generate # if len(parents_df) < 2: # var_ops = ["mutate" for _ in range(n_individuals_to_submit)] # else: # var_ops = [self.rng.choice(["crossover","mutate_then_crossover","crossover_then_mutate",'mutate'],p=[self.crossover_probability,self.mutate_then_crossover_probability, self.crossover_then_mutate_probability,self.mutate_probability]) for _ in range(n_individuals_to_submit)] # parents = [] # for op in var_ops: # if op == "mutate": # parents.extend(np.array(cur_evaluated_population)[self.parent_selector(weighted_scores, k=1, n_parents=1, rng=self.rng)]) # else: # parents.extend(np.array(cur_evaluated_population)[self.parent_selector(weighted_scores, k=1, n_parents=2, rng=self.rng)]) # #_offspring = self.population.create_offspring2(parents, var_ops, rng=self.rng, add_to_population=True) # offspring = self.population.create_offspring2(parents, var_ops, [ind_mutate], None, [ind_crossover], None, add_to_population=True, keep_repeats=False, mutate_until_unique=True, rng=self.rng) if enough_parents_evaluated: parents = self.population.parent_select(selector=self.parent_selector, weights=self.objective_function_weights, columns_names=self.objective_names, k=n_individuals_to_submit, n_parents=2, rng=self.rng) p = np.array([self.crossover_probability, self.mutate_then_crossover_probability, self.crossover_then_mutate_probability, self.mutate_probability]) p = p / p.sum() var_op_list = self.rng.choice(["crossover", "mutate_then_crossover", "crossover_then_mutate", "mutate"], size=n_individuals_to_submit, p=p) for i, op in enumerate(var_op_list): if op == "mutate": parents[i] = parents[i][0] #mutations take a single individual offspring = self.population.create_offspring2(parents, var_op_list, [ind_mutate], None, [ind_crossover], None, add_to_population=True, keep_repeats=False, mutate_until_unique=True, rng=self.rng) # If we don't have enough evaluated individuals to use as parents for variation, we create new individuals randomly # This can happen if the individuals in the initial population are invalid elif len(submitted_futures) < self.max_queue_size: initial_population = self.population.evaluated_individuals.iloc[:self.initial_population_size*3] invalid_initial_population = initial_population[initial_population[["Eval Error"]].isin(["TIMEOUT","INVALID"]).any(axis=1)] if len(invalid_initial_population) >= self.initial_population_size*3: #if all individuals in the 3*initial population are invalid raise Exception("No individuals could be evaluated in the initial population. This may indicate a bug in the configuration, included models, or objective functions. Set verbose>=4 to see the errors that caused individuals to fail.") n_individuals_to_create = self.max_queue_size - len(submitted_futures) initial_population = [next(self.individual_generator) for _ in range(n_individuals_to_create)] self.population.add_to_population(initial_population, rng=self.rng) ############################### # Step 6: Add Unevaluated Individuals Generated by Variation ############################### individuals_to_evaluate = self.get_unevaluated_individuals(self.objective_names, budget=budget,) individuals_to_evaluate = [ind for ind in individuals_to_evaluate if ind.unique_id() not in submitted_inds] for individual in individuals_to_evaluate: if self.max_queue_size > len(submitted_futures): future = self._client.submit(tpot.utils.eval_utils.eval_objective_list, individual, self.objective_functions, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins,**self.objective_kwargs) submitted_futures[future] = {"individual": individual, "time": time.time(), "budget": budget,} submitted_inds.add(individual.unique_id()) self.population.update_column(individual, column_names="Submitted Timestamp", data=time.time()) #Checkpointing if self.population_file is not None: # and time.time() - last_save_time > 60*10: pickle.dump(self.population, open(self.population_file, "wb")) except KeyboardInterrupt: if self.verbose >= 3: print("KeyboardInterrupt") ############################### # Step 7: Cleanup ############################### self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="INVALID") self.population.remove_invalid_from_population(column_names="Eval Error", invalid_value="TIMEOUT") #done, cleanup futures for future in submitted_futures.keys(): future.cancel() future.release() #release the future #I am not entirely sure if this is necessary. I believe that calling release on the futures should be enough to free up memory. If memory issues persist, this may be a good place to start. #client.run(gc.collect) #run garbage collection to free up memory #checkpoint if self.population_file is not None: pickle.dump(self.population, open(self.population_file, "wb")) if self.client is None: #If we created our own client, close it self._client.close() self._cluster.close() tpot.utils.get_pareto_frontier(self.population.evaluated_individuals, column_names=self.objective_names, weights=self.objective_function_weights) def get_unevaluated_individuals(self, column_names, budget=None, individual_list=None): """ This function is used to get a list of individuals in the current population that have not been evaluated yet. Parameters ---------- column_names : list of strings Names of the columns to check for unevaluated individuals (generally objective functions). budget : float, default=None Budget to use when checking for unevaluated individuals. If None, will not check the budget column. Finds individuals who have not been evaluated with the given budget on column names. individual_list : list of individuals, default=None List of individuals to check for unevaluated individuals. If None, will use the current population. """ if individual_list is not None: cur_pop = np.array(individual_list) else: cur_pop = np.array(self.population.population) if all([name_step in self.population.evaluated_individuals.columns for name_step in column_names]): if budget is not None: offspring_scores = self.population.get_column(cur_pop, column_names=column_names+["Budget"], to_numpy=False) #Individuals are unevaluated if we have a higher budget OR if any of the objectives are nan unevaluated_filter = lambda i: any(offspring_scores.loc[offspring_scores.index[i]][column_names].isna()) or (offspring_scores.loc[offspring_scores.index[i]]["Budget"] < budget) else: offspring_scores = self.population.get_column(cur_pop, column_names=column_names, to_numpy=False) unevaluated_filter = lambda i: any(offspring_scores.loc[offspring_scores.index[i]][column_names].isna()) unevaluated_individuals_this_step = [i for i in range(len(cur_pop)) if unevaluated_filter(i)] return cur_pop[unevaluated_individuals_this_step] else: #if column names are not in the evaluated_individuals, then we have not evaluated any individuals yet for name_step in column_names: self.population.evaluated_individuals[name_step] = np.nan return cur_pop ================================================ FILE: tpot/graphsklearn.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from functools import partial import numpy as np import networkx as nx import matplotlib.pyplot as plt import sklearn from sklearn.utils.metaestimators import available_if import pandas as pd from sklearn.utils.metaestimators import _BaseComposition from sklearn.utils.validation import check_memory from sklearn.preprocessing import LabelEncoder from sklearn.base import is_classifier, is_regressor from sklearn.utils._tags import get_tags import copy #labels - str #attributes - "instance" -> instance of the type def plot(graph: nx.DiGraph): G = graph.reverse() try: pos = nx.planar_layout(G) # positions for all nodes except: pos = nx.shell_layout(G) # nodes options = {'edgecolors': 'tab:gray', 'node_size': 800, 'alpha': 0.9} nx.draw_networkx_nodes(G, pos, nodelist=list(G.nodes), node_color='tab:red', **options) # edges nx.draw_networkx_edges(G, pos, width=3.0, arrows=True) # some math labels labels = {} for i, n in enumerate(G.nodes): labels[n] = n#.__class__.__name__ nx.draw_networkx_labels(G, pos, labels, font_size=7, font_color='black') plt.tight_layout() plt.axis('off') plt.show() #copied from https://github.com/scikit-learn/scikit-learn/blob/36958fb240fbe435673a9e3c52e769f01f36bec0/sklearn/ensemble/_stacking.py#L121 def _method_name(name, estimator, method): if estimator == 'drop': return None if method == 'auto': if hasattr(estimator, 'predict_proba'): return 'predict_proba' elif hasattr(estimator, 'decision_function'): return 'decision_function' else: return 'predict' else: if not hasattr(estimator, method): raise ValueError( 'Underlying estimator {} does not implement the method {}.'.format( name, method ) ) return method def estimator_fit_transform_override_cross_val_predict(estimator, X, y, cv=5, method='auto', **fit_params): method = _method_name(name=estimator.__class__.__name__, estimator=estimator, method=method) if (isinstance(cv, int) and cv>1) or (not isinstance(cv, int) and cv is not None): preds = sklearn.model_selection.cross_val_predict(estimator=estimator, X=X, y=y, cv=cv, method=method, **fit_params) estimator.fit(X,y, **fit_params) else: estimator.fit(X,y, **fit_params) func = getattr(estimator,method) preds = func(X) return preds, estimator # https://github.com/scikit-learn/scikit-learn/blob/7db5b6a98/sklearn/pipeline.py#L883 def _fit_transform_one(model, X, y, fit_transform=True, **fit_params): """Fit and transform one step in a pipeline.""" if fit_transform and hasattr(model, "fit_transform"): res = model.fit_transform(X, y, **fit_params) else: res = model.fit(X, y, **fit_params).transform(X) #return model return res, model #TODO: make sure predict proba doesn't return p and 1-p for nclasses=2 def fit_sklearn_digraph(graph: nx.DiGraph, X, y, method='auto', cross_val_predict_cv = 0, #func(est,X,y) -> transformed_X memory = None, topo_sort = None, ): memory = check_memory(memory) fit_transform_one_cached = memory.cache(_fit_transform_one) estimator_fit_transform_override_cross_val_predict_cached = memory.cache(estimator_fit_transform_override_cross_val_predict) if topo_sort is None: topo_sort = list(nx.topological_sort(graph)) topo_sort.reverse() transformed_steps = {} for i in range(len(topo_sort)): node = topo_sort[i] instance = graph.nodes[node]["instance"] if len(list(get_ordered_successors(graph, node))) == 0: #If this node had no inputs use X this_X = X else: #in node has inputs, get those this_X = np.hstack([transformed_steps[child] for child in get_ordered_successors(graph, node)]) # Removed so that the cache is the same for all models. Not including transform would index it seperately #if i == len(topo_sort)-1: #last method doesn't need transformed. # instance.fit(this_X, y) if is_classifier(instance) or is_regressor(instance): transformed, instance = estimator_fit_transform_override_cross_val_predict_cached(instance, this_X, y, cv=cross_val_predict_cv, method=method) else: transformed, instance = fit_transform_one_cached(instance, this_X, y)#instance.fit_transform(this_X,y) graph.nodes[node]["instance"] = instance if len(transformed.shape) == 1: transformed = transformed.reshape(-1, 1) transformed_steps[node] = transformed #TODO add attribute to decide 'method' for each node #TODO make more memory efficient. Free memory when a transformation is no longer needed #TODO better handle multiple roots def transform_sklearn_digraph(graph: nx.DiGraph, X, method = 'auto', output_nodes = None, topo_sort = None,): if graph.number_of_nodes() == 1: #TODO make this better... return X if topo_sort is None: topo_sort = list(nx.topological_sort(graph)) topo_sort.reverse() transformed_steps = {} for i in range(len(topo_sort)): node = topo_sort[i] instance = graph.nodes[node]["instance"] if len(list(get_ordered_successors(graph, node))) == 0: this_X = X else: this_X = np.hstack([transformed_steps[child] for child in get_ordered_successors(graph, node)]) if is_classifier(instance) or is_regressor(instance): this_method = _method_name(instance.__class__.__name__, instance, method) transformed = getattr(instance, this_method)(this_X) else: transformed = instance.transform(this_X) if len(transformed.shape) == 1: transformed = transformed.reshape(-1, 1) transformed_steps[node] = transformed if output_nodes is None: return transformed_steps else: return {n: transformed_steps[n] for n in output_nodes} def get_inputs_to_node(graph: nx.DiGraph, X, node, method = 'auto', topo_sort = None, ): if len(list(get_ordered_successors(graph, node))) == 0: this_X = X else: transformed_steps = transform_sklearn_digraph(graph, X, method, topo_sort = topo_sort, ) this_X = np.hstack([transformed_steps[child] for child in get_ordered_successors(graph, node)]) return this_X def _estimator_has(attr): '''Check if we can delegate a method to the underlying estimator. First, we check the first fitted final estimator if available, otherwise we check the unfitted final estimator. ''' def check(self): return hasattr(self.graph.nodes[self.root]["instance"], attr) return check def setup_ordered_successors(graph: nx.DiGraph): for node in graph.nodes: graph.nodes[node]["successors"] = sorted(list(graph.successors(node))) def get_ordered_successors(graph: nx.DiGraph, node): return graph.nodes[node]["successors"] #TODO make sure it meets all requirements for basecomposition class GraphPipeline(_BaseComposition): def __init__( self, graph, cross_val_predict_cv=0, #signature function(estimator, X, y=none) method='auto', memory=None, use_label_encoder=False, **kwargs, ): super().__init__(**kwargs) ''' An sklearn baseestimator that uses genetic programming to optimize a pipeline. Parameters ---------- graph: networkx.DiGraph A directed graph where the nodes are sklearn estimators and the edges are the inputs to those estimators. cross_val_predict_cv: int, cross-validation generator or an iterable, optional Determines the cross-validation splitting strategy used in inner classifiers or regressors method: str, optional The prediction method to use for the inner classifiers or regressors. If 'auto', it will try to use predict_proba, decision_function, or predict in that order. memory: str or object with the joblib.Memory interface, optional Used to cache the input and outputs of nodes to prevent refitting or computationally heavy transformations. By default, no caching is performed. If a string is given, it is the path to the caching directory. use_label_encoder: bool, optional If True, the label encoder is used to encode the labels to be 0 to N. If False, the label encoder is not used. Mainly useful for classifiers (XGBoost) that require labels to be ints from 0 to N. Can also be a sklearn.preprocessing.LabelEncoder object. If so, that label encoder is used. ''' self.graph = graph self.cross_val_predict_cv = cross_val_predict_cv self.method = method self.memory = memory self.use_label_encoder = use_label_encoder setup_ordered_successors(graph) self.topo_sorted_nodes = list(nx.topological_sort(self.graph)) self.topo_sorted_nodes.reverse() self.root = self.topo_sorted_nodes[-1] if self.use_label_encoder: if type(self.use_label_encoder) == LabelEncoder: self.label_encoder = self.use_label_encoder else: self.label_encoder = LabelEncoder() #TODO clean this up try: nx.find_cycle(self.G) raise BaseException except: pass def __str__(self): if len(self.graph.edges) > 0: return str(self.graph.edges) else: return str(self.graph.nodes) def fit(self, X, y): if self.use_label_encoder: if type(self.use_label_encoder) == LabelEncoder: y = self.label_encoder.transform(y) else: y = self.label_encoder.fit_transform(y) fit_sklearn_digraph( graph=self.graph, X=X, y=y, method=self.method, cross_val_predict_cv = self.cross_val_predict_cv, memory = self.memory, topo_sort = self.topo_sorted_nodes, ) return self def plot(self, ): plot(graph = self.graph) def __sklearn_is_fitted__(self): '''Indicate whether pipeline has been fit.''' try: # check if the last step of the pipeline is fitted # we only check the last step since if the last step is fit, it # means the previous steps should also be fit. This is faster than # checking if every step of the pipeline is fit. sklearn.utils.validation.check_is_fitted(self.graph.nodes[self.root]["instance"]) return True except sklearn.exceptions.NotFittedError: return False @available_if(_estimator_has('predict')) def predict(self, X, **predict_params): this_X = get_inputs_to_node(self.graph, X, self.root, method = self.method, topo_sort = self.topo_sorted_nodes, ) preds = self.graph.nodes[self.root]["instance"].predict(this_X, **predict_params) if self.use_label_encoder: preds = self.label_encoder.inverse_transform(preds) return preds @available_if(_estimator_has('predict_proba')) def predict_proba(self, X, **predict_params): this_X = get_inputs_to_node(self.graph, X, self.root, method = self.method, topo_sort = self.topo_sorted_nodes, ) return self.graph.nodes[self.root]["instance"].predict_proba(this_X, **predict_params) @available_if(_estimator_has('decision_function')) def decision_function(self, X, **predict_params): this_X = get_inputs_to_node(self.graph, X, self.root, method = self.method, topo_sort = self.topo_sorted_nodes, ) return self.graph.nodes[self.root]["instance"].decision_function(this_X, **predict_params) @available_if(_estimator_has('transform')) def transform(self, X, **predict_params): this_X = get_inputs_to_node(self.graph, X, self.root, method = self.method, topo_sort = self.topo_sorted_nodes, ) return self.graph.nodes[self.root]["instance"].transform(this_X, **predict_params) @property def classes_(self): """The classes labels. Only exist if the last step is a classifier.""" if self.use_label_encoder: return self.label_encoder.classes_ else: return self.graph.nodes[self.root]["instance"].classes_ @property def _estimator_type(self): return self.graph.nodes[self.root]["instance"]._estimator_type def __sklearn_tags__(self): tags = super().__sklearn_tags__() final_step = self.graph.nodes[self.root]["instance"] try: last_step_tags = final_step.__sklearn_tags__() except: last_step_tags = get_tags(final_step) tags.estimator_type = last_step_tags.estimator_type tags.target_tags.multi_output = last_step_tags.target_tags.multi_output tags.classifier_tags = copy.deepcopy(last_step_tags.classifier_tags) tags.regressor_tags = copy.deepcopy(last_step_tags.regressor_tags) tags.transformer_tags = copy.deepcopy(last_step_tags.transformer_tags) tags.input_tags.sparse = all( self.graph.nodes[step]['instance'].__sklearn_tags__().input_tags.sparse for step in self.topo_sorted_nodes ) tags.input_tags.pairwise = last_step_tags.input_tags.pairwise return tags ================================================ FILE: tpot/individual.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from abc import abstractmethod import types import numpy as np import copy import copy import typing class BaseIndividual: def __init__(self) -> None: self.mutation_list = [] self.crossover_list = [] def mutate(self, rng=None): rng = np.random.default_rng(rng) mutation_list_copy = self.mutation_list.copy() rng.shuffle(mutation_list_copy) for func in mutation_list_copy: if func(): return True return False def crossover(self, ind2, rng=None): rng = np.random.default_rng(rng) crossover_list_copy = self.crossover_list.copy() rng.shuffle(crossover_list_copy) for func in crossover_list_copy: if func(ind2): return True return False # a guided change of an individual when given an objective function def optimize(self, objective_function, rng=None , steps=5): rng = np.random.default_rng(rng) for _ in range(steps): self.mutate(rng=rng) #Return a hashable unique to this individual setup #For use when evaluating whether or not an individual is 'the same' and another individual def unique_id(self): return self #TODO https://www.pythontutorial.net/python-oop/python-__hash__/ #python hashing and __eq__ functions look into #whether or not this would be a better way of doing things # #TODO: use this instead of unique_id()? # #unique_id() and __repr__ could have different levels of specificity. # def __repr__(self) -> str: # pass # def __hash__(self) -> int: # pass # def __eq__(self, other): # self.unique_id() == other.unique_id() ================================================ FILE: tpot/logbook.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ class CallBackInterface(): def __init__(self) -> None: pass def step_callback(self, population): pass def population_mutate_callback(self, offspring, parent=None): pass def population_crossover_callback(self, offspring, parent=None): pass def evolutionary_algorithm_step_callback(self, population): pass class Logbook(): pass ================================================ FILE: tpot/objectives/__init__.py ================================================ from .average_path_length import average_path_length_objective from .number_of_nodes import number_of_nodes_objective from .number_of_leaves import number_of_leaves_scorer, number_of_leaves_objective from .complexity import complexity_scorer #these scorers are calculated per fold of CV on the fitted pipeline for that fold SCORERS = { "complexity_scorer": complexity_scorer } #these objectives are calculated once on unfitted models as secondary objectives OBJECTIVES = { "average_path_length_objective": average_path_length_objective, "number_of_nodes_objective": number_of_nodes_objective, "number_of_leaves_objective": number_of_leaves_objective } ================================================ FILE: tpot/objectives/average_path_length.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import networkx as nx import numpy as np def average_path_length_objective(graph_pipeline): """ Computes the average shortest path from all nodes to the root/final estimator (only supported for GraphPipeline) Parameters ---------- graph_pipeline: GraphPipeline The pipeline to compute the average path length for """ path_lengths = nx.shortest_path_length(graph_pipeline.graph, source=graph_pipeline.root) return np.mean(np.array(list(path_lengths.values())))+1 ================================================ FILE: tpot/objectives/complexity.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from tpot import GraphPipeline import numpy as np import sklearn import warnings from functools import reduce # Valid in Python 2.6+, required in Python 3 import operator from sklearn.multiclass import OneVsOneClassifier, OneVsRestClassifier from sklearn.linear_model import SGDClassifier, LogisticRegression, SGDRegressor, Ridge, Lasso, ElasticNet, Lars, LassoLars, LassoLarsCV, RidgeCV, ElasticNetCV, PassiveAggressiveClassifier, ARDRegression from sklearn.ensemble import BaggingClassifier, RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier, ExtraTreesRegressor, ExtraTreesClassifier, AdaBoostRegressor, AdaBoostClassifier, GradientBoostingRegressor,RandomForestRegressor, BaggingRegressor, ExtraTreesRegressor, HistGradientBoostingClassifier, HistGradientBoostingRegressor from sklearn.neural_network import MLPClassifier, MLPRegressor from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor from xgboost import XGBClassifier, XGBRegressor from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor from sklearn.svm import SVC, SVR, LinearSVR, LinearSVC from lightgbm import LGBMClassifier, LGBMRegressor from sklearn.naive_bayes import GaussianNB, BernoulliNB, MultinomialNB from sklearn.ensemble import StackingClassifier, StackingRegressor, VotingClassifier, VotingRegressor from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis from sklearn.gaussian_process import GaussianProcessRegressor, GaussianProcessClassifier # MultinomialNB: params_MultinomialNB, from sklearn.base import is_classifier, is_regressor #https://scikit-learn.org/stable/auto_examples/applications/plot_model_complexity_influence.html def _count_nonzero_coefficients_and_intercept(est): n_coef = np.count_nonzero(est.coef_) if hasattr(est, 'intercept_'): n_coef += np.count_nonzero(est.intercept_) return n_coef #https://stackoverflow.com/questions/51139875/sklearn-randomforestregressor-number-of-trainable-parameters def tree_complexity(tree): return tree.tree_.node_count * 5 #each node has 5 parameters #https://stackoverflow.com/questions/51139875/sklearn-randomforestregressor-number-of-trainable-parameters def forest_complexity(forest): all_trees = np.array(forest.estimators_) if len(all_trees.shape)>1: all_trees = all_trees.ravel() return sum(tree_complexity(tree) for tree in all_trees) def histgradientboosting_complexity(forest): all_trees = np.array(forest._predictors) if len(all_trees.shape)>1: all_trees = all_trees.ravel() return sum(len(tree.nodes)*5 for tree in all_trees) def knn_complexity(knn): return knn.n_neighbors def support_vector_machine_complexity(svm): count = 0 count += sum(svm.n_support_) if svm.kernel == 'linear': count += np.count_nonzero(svm.coef_) return count def sklearn_MLP_complexity(mlp): n_layers = len(mlp.coefs_) n_params = 0 for i in range(n_layers): n_params += len(mlp.coefs_[i]) + len(mlp.intercepts_[i]) return n_params def calculate_xgb_model_complexity(est): df = est.get_booster().trees_to_dataframe() cols_to_remove = ['Tree','Node', 'ID', 'count', 'Gain', 'Cover'] #keeps ['Feature', 'Split', 'Yes', 'No', 'Missing', 'Category'] #category is the specific category for a given feature. takes the place of split for categorical features for col in cols_to_remove: if col in df.columns: df = df.drop(col, axis=1) df = ~df.isna() return df.sum().sum() def BernoulliNB_Complexity(model): num_coefficients = len(model.class_log_prior_) + len(model.feature_log_prob_) return num_coefficients def GaussianNB_Complexity(model): num_coefficients = len(model.class_prior_) + len(model.theta_) + len(model.var_) return num_coefficients def MultinomialNB_Complexity(model): num_coefficients = len(model.class_log_prior_) + len(model.feature_log_prob_) return num_coefficients def BaggingComplexity(est): return sum([calculate_model_complexity(bagged) for bagged in est.estimators_]) def lightgbm_complexity(est): df = est.booster_.trees_to_dataframe() #remove tree_index and node_depth cols_to_remove = ['node_index','tree_index', 'node_depth', 'count', 'parent_index'] for col in cols_to_remove: if col in df.columns: df = df.drop(col, axis=1) s = df.shape return s[0] * s[1] def QuadraticDiscriminantAnalysis_complexity(est): count = reduce(operator.mul,np.array(est.rotations_).shape) + reduce(operator.mul,np.array(est.scalings_).shape) + reduce(operator.mul,np.array(est.means_).shape) + reduce(operator.mul,np.array(est.priors_).shape) return count #TODO consider the complexity of the kernel? def gaussian_process_classifier_complexity(est): if isinstance(est.base_estimator_, OneVsOneClassifier) or isinstance(est.base_estimator_, OneVsRestClassifier): count = 0 for clf in est.base_estimator_.estimators_: count += len(clf.pi_) return count return len(est.base_estimator_.pi_) #TODO consider the complexity of the kernel? def gaussian_process_regressor_complexity(est): return len(est.alpha_) def adaboost_complexity(est): return len(est.estimator_weights_) + sum(calculate_model_complexity(bagged) for bagged in est.estimators_) def ensemble_complexity(est): return sum(calculate_model_complexity(bagged) for bagged in est.estimators_) complexity_objective_per_estimator = { LogisticRegression: _count_nonzero_coefficients_and_intercept, SGDClassifier: _count_nonzero_coefficients_and_intercept, LinearSVC : _count_nonzero_coefficients_and_intercept, LinearSVR : _count_nonzero_coefficients_and_intercept, ARDRegression: _count_nonzero_coefficients_and_intercept, #When predicting mean, only coef and intercept used. Though there are more params for the variance/covariance matrix LinearDiscriminantAnalysis: _count_nonzero_coefficients_and_intercept, QuadraticDiscriminantAnalysis: QuadraticDiscriminantAnalysis_complexity, SGDRegressor: _count_nonzero_coefficients_and_intercept, Ridge: _count_nonzero_coefficients_and_intercept, Lasso: _count_nonzero_coefficients_and_intercept, ElasticNet: _count_nonzero_coefficients_and_intercept, Lars: _count_nonzero_coefficients_and_intercept, LassoLars: _count_nonzero_coefficients_and_intercept, LassoLarsCV: _count_nonzero_coefficients_and_intercept, RidgeCV: _count_nonzero_coefficients_and_intercept, ElasticNetCV: _count_nonzero_coefficients_and_intercept, PassiveAggressiveClassifier: _count_nonzero_coefficients_and_intercept, KNeighborsClassifier: knn_complexity, KNeighborsRegressor: knn_complexity, DecisionTreeClassifier: tree_complexity, DecisionTreeRegressor: tree_complexity, GradientBoostingRegressor: forest_complexity, GradientBoostingClassifier: forest_complexity, RandomForestClassifier : forest_complexity, RandomForestRegressor: forest_complexity, HistGradientBoostingClassifier: histgradientboosting_complexity, HistGradientBoostingRegressor: histgradientboosting_complexity, ExtraTreesRegressor: forest_complexity, ExtraTreesClassifier: forest_complexity, XGBClassifier: calculate_xgb_model_complexity, XGBRegressor: calculate_xgb_model_complexity, SVC : support_vector_machine_complexity, SVR : support_vector_machine_complexity, MLPClassifier: sklearn_MLP_complexity, MLPRegressor: sklearn_MLP_complexity, BaggingRegressor: BaggingComplexity, BaggingClassifier: BaggingComplexity, BernoulliNB: BernoulliNB_Complexity, GaussianNB: GaussianNB_Complexity, MultinomialNB: MultinomialNB_Complexity, LGBMClassifier: lightgbm_complexity, LGBMRegressor: lightgbm_complexity, GaussianProcessClassifier: gaussian_process_classifier_complexity, GaussianProcessRegressor: gaussian_process_regressor_complexity, AdaBoostClassifier: adaboost_complexity, AdaBoostRegressor: adaboost_complexity, # StackingClassifier: ensemble_complexity, # StackingRegressor: ensemble_complexity, # VotingClassifier: ensemble_complexity, # VotingRegressor: ensemble_complexity } def calculate_model_complexity(est): if isinstance(est, sklearn.pipeline.Pipeline): return sum(calculate_model_complexity(estimator) for _,estimator in est.steps) if isinstance(est, sklearn.pipeline.FeatureUnion): return sum(calculate_model_complexity(estimator) for _,estimator in est.transformer_list) if isinstance(est, GraphPipeline): return sum(calculate_model_complexity(est.graph.nodes[node]['instance']) for node in est.graph.nodes) model_type = type(est) if is_classifier(est) or is_regressor(est): if model_type not in complexity_objective_per_estimator: warnings.warn(f"Complexity objective not defined for this classifier/regressor: {model_type}") if model_type in complexity_objective_per_estimator: return complexity_objective_per_estimator[model_type](est) #else, if is subclass of sklearn selector elif issubclass(model_type, sklearn.feature_selection.SelectorMixin): return 0 else: return 1 def complexity_scorer(est, X=None, y=None): """ Estimates the number of learned parameters across all classifiers and regressors in the pipelines. Additionally, currently transformers add 1 point and selectors add 0 points (since they don't affect the complexity of the "final" predictive pipeline. Parameters ---------- est: sklearn.base.BaseEstimator The estimator or pipeline to compute the complexity for X: array-like The input samples (unused) y: array-like The target values (unused) """ return calculate_model_complexity(est) ================================================ FILE: tpot/objectives/number_of_leaves.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ def number_of_leaves_scorer(est,X=None, y=None): return len([v for v, d in est.graph.out_degree() if d == 0]) def number_of_leaves_objective(est): """ Calculates the number of leaves (input nodes) in a GraphPipeline Parameters ---------- est: GraphPipeline The pipeline to compute the number of leaves for """ return len([v for v, d in est.graph.out_degree() if d == 0]) ================================================ FILE: tpot/objectives/number_of_nodes.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from ..graphsklearn import GraphPipeline from sklearn.pipeline import Pipeline import sklearn def number_of_nodes_objective(est): """ Calculates the number of leaves (input nodes) in an sklearn pipeline Parameters ---------- est: GraphPipeline | Pipeline | FeatureUnion | BaseEstimator The pipeline to compute the number of nodes from. """ if isinstance(est, GraphPipeline): return sum(number_of_nodes_objective(est.graph.nodes[node]["instance"]) for node in est.graph.nodes) if isinstance(est, Pipeline): return sum(number_of_nodes_objective(estimator) for _,estimator in est.steps) if isinstance(est, sklearn.pipeline.FeatureUnion): return sum(number_of_nodes_objective(estimator) for _,estimator in est.transformer_list) return 1 ================================================ FILE: tpot/objectives/tests/test_complexity_objective.py ================================================ ================================================ FILE: tpot/objectives/tests/test_number_of_nodes.py ================================================ import pytest import tpot from sklearn.datasets import load_iris import random import sklearn from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline import networkx as nx import tpot from tpot import GraphPipeline import sklearn.metrics def test_number_of_nodes_objective_Graphpipeline(): g = nx.DiGraph() g.add_node("scaler", instance=StandardScaler()) g.add_node("svc", instance=SVC()) g.add_node("LogisticRegression", instance=LogisticRegression()) g.add_node("LogisticRegression2", instance=LogisticRegression()) g.add_edge("svc","scaler") g.add_edge("LogisticRegression", "scaler") g.add_edge("LogisticRegression2", "LogisticRegression") g.add_edge("LogisticRegression2", "svc") est = GraphPipeline(g) assert tpot.objectives.number_of_nodes.number_of_nodes_objective(est) == 4 def test_number_of_nodes_objective_Pipeline(): pipe = Pipeline([("scaler", StandardScaler()), ("svc", SVC())]) assert tpot.objectives.number_of_nodes.number_of_nodes_objective(pipe) == 2 def test_number_of_nodes_objective_not_pipeline_or_graphpipeline(): assert tpot.objectives.number_of_nodes.number_of_nodes_objective(SVC()) == 1 assert tpot.objectives.number_of_nodes.number_of_nodes_objective(StandardScaler()) == 1 assert tpot.objectives.number_of_nodes.number_of_nodes_objective(LogisticRegression()) == 1 def test_number_of_nodes_objective_pipeline_in_graphpipeline(): g = nx.DiGraph() g.add_node("scaler", instance=StandardScaler()) g.add_node("pipe", instance=Pipeline([("scaler", StandardScaler()), ("svc", SVC())])) g.add_edge("pipe","scaler") est = GraphPipeline(g) assert tpot.objectives.number_of_nodes.number_of_nodes_objective(est) == 3 def test_number_of_nodes_objective_graphpipeline_in_pipeline(): pipe = Pipeline([("scaler", StandardScaler()), ("svc", SVC())]) g = nx.DiGraph() g.add_node("scaler", instance=StandardScaler()) g.add_node("svc", instance=SVC()) g.add_node("LogisticRegression", instance=LogisticRegression()) g.add_node("LogisticRegression2", instance=LogisticRegression()) g.add_edge("svc","scaler") g.add_edge("LogisticRegression", "scaler") g.add_edge("LogisticRegression2", "LogisticRegression") g.add_edge("LogisticRegression2", "svc") est = GraphPipeline(g) pipe.steps.append(("graphpipe", est)) assert tpot.objectives.number_of_nodes.number_of_nodes_objective(pipe) == 6 def test_number_of_nodes_objective_graphpipeline_in_graphpipeline(): g = nx.DiGraph() g.add_node("scaler", instance=StandardScaler()) g.add_node("svc", instance=SVC()) g.add_node("LogisticRegression", instance=LogisticRegression()) g.add_node("LogisticRegression2", instance=LogisticRegression()) g.add_edge("svc","scaler") g.add_edge("LogisticRegression", "scaler") g.add_edge("LogisticRegression2", "LogisticRegression") g.add_edge("LogisticRegression2", "svc") est = GraphPipeline(g) g2 = nx.DiGraph() g2.add_node("g1", instance=est) g2.add_node("svc", instance=SVC()) g2.add_node("LogisticRegression", instance=LogisticRegression()) g2.add_node("LogisticRegression2", instance=LogisticRegression()) g2.add_edge("svc","g1") g2.add_edge("LogisticRegression", "g1") g2.add_edge("LogisticRegression2", "LogisticRegression") g2.add_edge("LogisticRegression2", "svc") est2 = GraphPipeline(g2) assert tpot.objectives.number_of_nodes.number_of_nodes_objective(est2) == 7 def test_number_of_nodes_objective_pipeline_in_pipeline(): pipe = Pipeline([("scaler", StandardScaler()), ("svc", SVC())]) pipe2 = Pipeline([("pipe", pipe), ("svc", SVC())]) assert tpot.objectives.number_of_nodes.number_of_nodes_objective(pipe2) == 3 ================================================ FILE: tpot/old_config_utils/__init__.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from .old_config_utils import convert_config_dict_to_list, convert_config_dict_to_choicepipeline, convert_config_dict_to_graphpipeline, convert_config_dict_to_linearpipeline ================================================ FILE: tpot/old_config_utils/old_config_utils.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal from ConfigSpace import EqualsCondition, OrConjunction, NotEqualsCondition, InCondition from ..search_spaces.nodes import EstimatorNode from ..search_spaces.pipelines import WrapperPipeline, ChoicePipeline, GraphSearchPipeline, SequentialPipeline, DynamicLinearPipeline import ConfigSpace import sklearn from functools import partial import inspect import numpy as np def load_get_module_from_string(module_string): """ Takes a string in the form of 'module.submodule.class' and returns the class. Parameters ---------- module_string : str The string representation of the module and class to load. Returns ------- class The class that was loaded from the module string. """ module_name, class_name = module_string.rsplit('.', 1) module = __import__(module_name, fromlist=[class_name]) return getattr(module, class_name) def hyperparameter_parser(hdict, function_params_conversion_dict): d = hdict.copy() d.update(function_params_conversion_dict) return d def get_node_space(module_string, params): """ Create the search space for a single node in the TPOT config. Parameters ---------- module_string : str The string representation of the module and class to load. E.g. 'sklearn.ensemble.RandomForestClassifier' params : dict The dictionary representation of the hyperparameter search space for the module_string. Returns ------- EstimatorNode or WrapperPipeline """ method = load_get_module_from_string(module_string) config_space = ConfigurationSpace() sub_space = None sub_space_name = None function_params_conversion_dict = {} if params is None: return EstimatorNode(method=method, space=config_space) for param_name, param in params.items(): if param is None: config_space.add(Categorical(param_name, [None])) if isinstance(param, range): param = list(param) if isinstance(param, list) or isinstance(param, np.ndarray): if len(param) == 1: p = param[0] config_space.add(ConfigSpace.hyperparameters.Constant(param_name, p)) else: config_space.add(Categorical(param_name, param)) # if all(isinstance(i, int) for i in param): # config_space.add_hyperparameter(Integer(param_name, (min(param), max(param)))) # elif all(isinstance(i, float) for i in param): # config_space.add_hyperparameter(Float(param_name, (min(param), max(param)))) # else: # config_space.add_hyperparameter(Categorical(param_name, param)) elif isinstance(param, dict): #TPOT1 config dicts have dictionaries for values of hyperparameters that are either a function or an estimator if len(param) > 1: raise ValueError(f"Multiple items in dictionary entry for {param_name}") key = list(param.keys())[0] innermethod = load_get_module_from_string(key) if inspect.isclass(innermethod) and issubclass(innermethod, sklearn.base.BaseEstimator): #is an estimator if sub_space is None: sub_space_name = param_name sub_space = get_node_space(key, param[key]) else: raise ValueError("Only multiple hyperparameters are estimators. Only one parameter ") else: #assume the key is a function and ignore the value function_params_conversion_dict[param_name] = innermethod else: # config_space.add_hyperparameter(Categorical(param_name, param)) config_space.add(ConfigSpace.hyperparameters.Constant(param_name, param)) parser=None if len(function_params_conversion_dict) > 0: parser = partial(hyperparameter_parser, function_params_conversion_dict) if sub_space is None: if parser is not None: return EstimatorNode(method=method, space=config_space, hyperparameter_parser=parser) else: return EstimatorNode(method=method, space=config_space) else: if parser is not None: return WrapperPipeline(method=method, space=config_space, estimator_search_space=sub_space, wrapped_param_name=sub_space_name, hyperparameter_parser=parser) else: return WrapperPipeline(method=method, space=config_space, estimator_search_space=sub_space, wrapped_param_name=sub_space_name) ### Below are the functions that convert the old config dicts to the new search spaces to be used by users. def convert_config_dict_to_list(config_dict): """ Takes in a TPOT config dictionary and returns a list of search spaces (EstimatorNode, WrapperPipeline) Parameters ---------- config_dict : dict The dictionary representation of the TPOT config. Returns ------- list A list of search spaces (EstimatorNode, WrapperPipeline) that represent the config_dict. """ search_spaces = [] for key, value in config_dict.items(): search_spaces.append(get_node_space(key, value)) return search_spaces def convert_config_dict_to_choicepipeline(config_dict): """ Takes in a TPOT config dictionary and returns a ChoicePipeline search space that represents the config_dict. This space will sample from all included modules in the config_dict. Parameters ---------- config_dict : dict The dictionary representation of the TPOT config. Returns ------- ChoicePipeline A ChoicePipeline search space that represents the config_dict. """ search_spaces = [] for key, value in config_dict.items(): search_spaces.append(get_node_space(key, value)) return ChoicePipeline(search_spaces) #Note doesn't convert estimators so they passthrough inputs like in TPOT1 def convert_config_dict_to_graphpipeline(config_dict): """ Takes in a TPOT config dictionary and returns a GraphSearchPipeline search space that represents the config_dict. This space will sample from all included modules in the config_dict. It will also identify classifiers/regressors to set the search space for the root node. Note doesn't convert estimators so they passthrough inputs like in TPOT1 Parameters ---------- config_dict : dict The dictionary representation of the TPOT config. Returns ------- GraphSearchPipeline A GraphSearchPipeline search space that represents the config_dict. """ root_search_spaces = [] inner_search_spaces = [] for key, value in config_dict.items(): #if root if issubclass(load_get_module_from_string(key), sklearn.base.ClassifierMixin) or issubclass(load_get_module_from_string(key), sklearn.base.RegressorMixin): root_search_spaces.append(get_node_space(key, value)) else: inner_search_spaces.append(get_node_space(key, value)) if len(root_search_spaces) == 0: Warning("No classifiers or regressors found, allowing any estimator to be the root node") root_search_spaces = inner_search_spaces #merge inner and root search spaces inner_space = np.concatenate([root_search_spaces,inner_search_spaces]) root_space = ChoicePipeline(root_search_spaces) inner_space = ChoicePipeline(inner_search_spaces) final_space = GraphSearchPipeline(root_search_space=root_space, inner_search_space=inner_space) return final_space #Note doesn't convert estimators so they passthrough inputs like in TPOT1 def convert_config_dict_to_linearpipeline(config_dict): """ Takes in a TPOT config dictionary and returns a GraphSearchPipeline search space that represents the config_dict. This space will sample from all included modules in the config_dict. It will also identify classifiers/regressors to set the search space for the root node. Note doesn't convert estimators so they passthrough inputs like in TPOT1 Parameters ---------- config_dict : dict The dictionary representation of the TPOT config. Returns ------- GraphSearchPipeline A GraphSearchPipeline search space that represents the config_dict. """ root_search_spaces = [] inner_search_spaces = [] for key, value in config_dict.items(): #if root if issubclass(load_get_module_from_string(key), sklearn.base.ClassifierMixin) or issubclass(load_get_module_from_string(key), sklearn.base.RegressorMixin): root_search_spaces.append(get_node_space(key, value)) else: inner_search_spaces.append(get_node_space(key, value)) if len(root_search_spaces) == 0: Warning("No classifiers or regressors found, allowing any estimator to be the root node") root_search_spaces = inner_search_spaces #merge inner and root search spaces inner_space = np.concatenate([root_search_spaces,inner_search_spaces]) root_space = ChoicePipeline(root_search_spaces) inner_space = ChoicePipeline(inner_search_spaces) final_space = SequentialPipeline([ DynamicLinearPipeline(inner_space, 10), root_space ]) return final_space ================================================ FILE: tpot/population.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np import copy import copy import typing import tpot from tpot import BaseIndividual from traitlets import Bool import collections import pandas as pd from joblib import Parallel, delayed import copy import pickle import dask def mutate(individual, rng): rng = np.random.default_rng(rng) if isinstance(individual, collections.abc.Iterable): for ind in individual: ind.mutate(rng=rng) else: individual.mutate(rng=rng) return individual def crossover(parents, rng): rng = np.random.default_rng(rng) parents[0].crossover(parents[1], rng=rng) return parents[0] def mutate_and_crossover(parents, rng): rng = np.random.default_rng(rng) parents[0].crossover(parents[1], rng=rng) parents[0].mutate(rng=rng) parents[1].mutate(rng=rng) return parents def crossover_and_mutate(parents, rng): rng = np.random.default_rng(rng) for p in parents: p.mutate(rng=rng) parents[0].crossover(parents[1], rng=rng) return parents[0] built_in_var_ops_dict = {"mutate":mutate, "crossover":crossover, "mutate_then_crossover":mutate_and_crossover, "crossover_then_mutate":crossover_and_mutate} class Population(): ''' Primary usage is to keep track of evaluated individuals Parameters ---------- initial_population : {list of BaseIndividuals}, default=None Initial population to start with. If None, start with an empty population. use_unique_id : {Bool}, default=True If True, individuals are treated as unique if they have the same unique_id(). If False, all new individuals are treated as unique. callback : {function}, default=None NOT YET IMPLEMENTED A function to call after each generation. The function should take a Population object as its only argument. Attributes ---------- population : {list of BaseIndividuals} The current population of individuals. Contains the live instances of BaseIndividuals. evaluated_individuals : {dict} A dictionary of dictionaries. The keys are the unique_id() or self of each BaseIndividual. Can be thought of as a table with the unique_id() as the row index and the inner dictionary keys as the columns. ''' def __init__( self, column_names: typing.List[str] = None, n_jobs: int = 1, callback=None, ) -> None: if column_names is not None: column_names = column_names+["Parents", "Variation_Function"] else: column_names = ["Parents", "Variation_Function"] self.evaluated_individuals = pd.DataFrame(columns=column_names) self.evaluated_individuals["Parents"] = self.evaluated_individuals["Parents"].astype('object') self.use_unique_id = True #Todo clean this up. perhaps pull unique_id() out of baseestimator and have it be supplied as a function self.n_jobs = n_jobs self.callback=callback self.population = [] def survival_select(self, selector, weights, columns_names, n_survivors, rng, inplace=True): rng = np.random.default_rng(rng) weighted_scores = self.get_column(self.population, column_names=columns_names) * weights new_population_index = np.ravel(selector(weighted_scores, k=n_survivors, rng=rng)) #TODO make it clear that we are concatenating scores... new_population = np.array(self.population)[new_population_index] if inplace: self.set_population(new_population, rng=rng) return new_population def parent_select(self, selector, weights, columns_names, k, n_parents, rng): rng = np.random.default_rng(rng) weighted_scores = self.get_column(self.population, column_names=columns_names) * weights parents_index = selector(weighted_scores, k=k, n_parents=n_parents, rng=rng) parents = np.array(self.population)[parents_index] return parents #remove individuals that either do not have a column_name value or a nan in that value #TODO take into account when the value is not a list/tuple? #TODO make invalid a global variable? def remove_invalid_from_population(self, column_names, invalid_value = "INVALID"): ''' Remove individuals from the live population if either do not have a value in the column_name column or if the value contains np.nan. Parameters ---------- column_name : {str} The name of the column to check for np.nan values. Returns ------- None ''' if isinstance(column_names, str): #TODO check this column_names = [column_names] is_valid = lambda ind: ind.unique_id() not in self.evaluated_individuals.index or invalid_value not in self.evaluated_individuals.loc[ind.unique_id(),column_names].to_list() self.population = [ind for ind in self.population if is_valid(ind)] # takes the list of individuals and adds it to the live population list. # if keep_repeats is False, repeated individuals are not added to the population # returns a list of individuals added to the live population #TODO make keep repeats allow for previously evaluated individuals, #but make sure that the live population only includes one of each, no repeats def add_to_population(self, individuals: typing.List[BaseIndividual], rng, keep_repeats=False, mutate_until_unique=True): ''' Add individuals to the live population. Add individuals to the evaluated_individuals if they are not already there. Parameters: ----------- individuals : {list of BaseIndividuals} The individuals to add to the live population. keep_repeats : {bool}, default=False If True, allow the population to have repeated individuals. If False, only add individuals that have not yet been added to geneology. ''' rng = np.random.default_rng(rng) if not isinstance(individuals, collections.abc.Iterable): individuals = [individuals] new_individuals = [] #TODO check for proper inputs for individual in individuals: key = individual.unique_id() if key not in self.evaluated_individuals.index: #If its new, we always add it self.evaluated_individuals.loc[key] = np.nan self.evaluated_individuals.loc[key,"Individual"] = copy.deepcopy(individual) self.population.append(individual) new_individuals.append(individual) else:#If its old if keep_repeats: #If we want to keep repeats, we add it self.population.append(individual) new_individuals.append(individual) elif mutate_until_unique: #If its old and we don't want repeats, we can optionally mutate it until it is unique for _ in range(20): individual = copy.deepcopy(individual) individual.mutate(rng=rng) key = individual.unique_id() if key not in self.evaluated_individuals.index: self.evaluated_individuals.loc[key] = np.nan self.evaluated_individuals.loc[key,"Individual"] = copy.deepcopy(individual) self.population.append(individual) new_individuals.append(individual) break return new_individuals def update_column(self, individual, column_names, data): ''' Update the column_name column in the evaluated_individuals with the data. If the data is a list, it must be the same length as the evaluated_individuals. If the data is a single value, it will be applied to all individuals in the evaluated_individuals. ''' if isinstance(individual, collections.abc.Iterable): if self.use_unique_id: key = [ind.unique_id() for ind in individual] else: key = individual else: if self.use_unique_id: key = individual.unique_id() else: key = individual self.evaluated_individuals.loc[key,column_names] = data def get_column(self, individual, column_names=None, to_numpy=True): ''' Update the column_name column in the evaluated_individuals with the data. If the data is a list, it must be the same length as the evaluated_individuals. If the data is a single value, it will be applied to all individuals in the evaluated_individuals. ''' if isinstance(individual, collections.abc.Iterable): if self.use_unique_id: key = [ind.unique_id() for ind in individual] else: key = individual else: if self.use_unique_id: key = individual.unique_id() else: key = individual if column_names is not None: slice = self.evaluated_individuals.loc[key,column_names] else: slice = self.evaluated_individuals.loc[key] if to_numpy: slice.reset_index(drop=True, inplace=True) return slice.to_numpy() else: return slice #returns the individuals without a 'column' as a key in geneology #TODO make sure not to get repeats in this list even if repeats are in the "live" population def get_unevaluated_individuals(self, column_names, individual_list=None): if individual_list is None: individual_list = self.population if self.use_unique_id: unevaluated_filter = lambda individual: individual.unique_id() not in self.evaluated_individuals.index or any(self.evaluated_individuals.loc[individual.unique_id(), column_names].isna()) else: unevaluated_filter = lambda individual: individual not in self.evaluated_individuals.index or any(self.evaluated_individuals.loc[individual.unique_id(), column_names].isna()) return [individual for individual in individual_list if unevaluated_filter(individual)] # def get_valid_evaluated_individuals_df(self, column_names_to_check, invalid_values=["TIMEOUT","INVALID"]): # ''' # Returns a dataframe of the evaluated individuals that do no have invalid_values in column_names_to_check. # ''' # return self.evaluated_individuals[~self.evaluated_individuals[column_names_to_check].isin(invalid_values).any(axis=1)] #the live population empied and is set to new_population def set_population(self, new_population, rng, keep_repeats=True): ''' sets population to new population for selection? ''' rng = np.random.default_rng(rng) self.population = [] self.add_to_population(new_population, rng=rng, keep_repeats=keep_repeats) #TODO should we just generate one offspring per crossover? def create_offspring(self, parents_list, var_op_list, rng, add_to_population=True, keep_repeats=False, mutate_until_unique=True, n_jobs=1): ''' parents_list: a list of lists of parents. var_op_list: a list of var_ops to apply to each list of parents. Should be the same length as parents_list. for example: parents_list = [[parent1, parent2], [parent3]] var_op_list = ["crossover", "mutate"] This will apply crossover to parent1 and parent2 and mutate to parent3. Creates offspring from parents using the var_op_list. If string, will use a built in method - "crossover" : crossover - "mutate" : mutate - "mutate_and_crossover" : mutate_and_crossover - "cross_and_mutate" : cross_and_mutate ''' rng = np.random.default_rng(rng) new_offspring = [] all_offspring = parallel_create_offspring(parents_list, var_op_list, rng=rng, n_jobs=n_jobs) for parents, offspring, var_op in zip(parents_list, all_offspring, var_op_list): # if var_op in built_in_var_ops_dict: # var_op = built_in_var_ops_dict[var_op] # offspring = copy.deepcopy(parents) # offspring = var_op(offspring) # if isinstance(offspring, collections.abc.Iterable): # offspring = offspring[0] if add_to_population: added = self.add_to_population(offspring, rng=rng, keep_repeats=keep_repeats, mutate_until_unique=mutate_until_unique) if len(added) > 0: for new_child in added: parent_keys = [parent.unique_id() for parent in parents] if not pd.api.types.is_object_dtype(self.evaluated_individuals["Parents"]): #TODO Is there a cleaner way of doing this? Not required for some python environments? self.evaluated_individuals["Parents"] = self.evaluated_individuals["Parents"].astype('object') if not pd.api.types.is_object_dtype(self.evaluated_individuals["Variation_Function"]):#TODO Is there a cleaner way of doing this? Not required for some python environments? self.evaluated_individuals["Variation_Function"] = self.evaluated_individuals["Variation_Function"].astype('object') self.evaluated_individuals.at[new_child.unique_id(),"Parents"] = tuple(parent_keys) #if var_op is a function if hasattr(var_op, '__call__'): self.evaluated_individuals.at[new_child.unique_id(),"Variation_Function"] = var_op.__name__ else: self.evaluated_individuals.at[new_child.unique_id(),"Variation_Function"] = str(var_op) new_offspring.append(new_child) else: new_offspring.append(offspring) return new_offspring #TODO should we just generate one offspring per crossover? def create_offspring2(self, parents_list, var_op_list, mutation_functions,mutation_function_weights, crossover_functions,crossover_function_weights, rng, add_to_population=True, keep_repeats=False, mutate_until_unique=True): rng = np.random.default_rng(rng) new_offspring = [] all_offspring = [] chosen_ops = [] for parents, var_op in zip(parents_list,var_op_list): #TODO put this loop in population class if var_op == "mutate": mutation_op = rng.choice(mutation_functions, p=mutation_function_weights) all_offspring.append(copy_and_mutate(parents[0], mutation_op, rng=rng)) chosen_ops.append(mutation_op.__name__) elif var_op == "crossover": crossover_op = rng.choice(crossover_functions, p=crossover_function_weights) all_offspring.append(copy_and_crossover(parents, crossover_op, rng=rng)) chosen_ops.append(crossover_op.__name__) elif var_op == "mutate_then_crossover": mutation_op1 = rng.choice(mutation_functions, p=mutation_function_weights) mutation_op2 = rng.choice(mutation_functions, p=mutation_function_weights) crossover_op = rng.choice(crossover_functions, p=crossover_function_weights) p1 = copy_and_mutate(parents[0], mutation_op1, rng=rng) p2 = copy_and_mutate(parents[1], mutation_op2, rng=rng) crossover_op(p1,p2,rng=rng) all_offspring.append(p1) chosen_ops.append(f"{mutation_op1.__name__} , {mutation_op2.__name__} , {crossover_op.__name__}") elif var_op == "crossover_then_mutate": crossover_op = rng.choice(crossover_functions, p=crossover_function_weights) child = copy_and_crossover(parents, crossover_op, rng=rng) mutation_op = rng.choice(mutation_functions, p=mutation_function_weights) mutation_op(child, rng=rng) all_offspring.append(child) chosen_ops.append(f"{crossover_op.__name__} , {mutation_op.__name__}") for parents, offspring, var_op in zip(parents_list, all_offspring, chosen_ops): # if var_op in built_in_var_ops_dict: # var_op = built_in_var_ops_dict[var_op] # offspring = copy.deepcopy(parents) # offspring = var_op(offspring) # if isinstance(offspring, collections.abc.Iterable): # offspring = offspring[0] if add_to_population: added = self.add_to_population(offspring, rng=rng, keep_repeats=keep_repeats, mutate_until_unique=mutate_until_unique) if len(added) > 0: for new_child in added: parent_keys = [parent.unique_id() for parent in parents] if not pd.api.types.is_object_dtype(self.evaluated_individuals["Parents"]): #TODO Is there a cleaner way of doing this? Not required for some python environments? self.evaluated_individuals["Parents"] = self.evaluated_individuals["Parents"].astype('object') self.evaluated_individuals.at[new_child.unique_id(),"Parents"] = tuple(parent_keys) #check if Variation_Function variable is an object type if not pd.api.types.is_object_dtype(self.evaluated_individuals["Variation_Function"]): #TODO Is there a cleaner way of doing this? Not required for some python environments? self.evaluated_individuals["Variation_Function"] = self.evaluated_individuals["Variation_Function"].astype('object') #if var_op is a function if hasattr(var_op, '__call__'): self.evaluated_individuals.at[new_child.unique_id(),"Variation_Function"] = var_op.__name__ else: self.evaluated_individuals.at[new_child.unique_id(),"Variation_Function"] = str(var_op) new_offspring.append(new_child) else: new_offspring.append(offspring) return new_offspring def get_id(individual): return individual.unique_id() def parallel_create_offspring(parents_list, var_op_list, rng, n_jobs=1): rng = np.random.default_rng(rng) if n_jobs == 1: return nonparallel_create_offpring(parents_list, var_op_list, rng=rng) else: delayed_offspring = [] for parents, var_op in zip(parents_list,var_op_list): #TODO put this loop in population class if var_op in built_in_var_ops_dict: var_op = built_in_var_ops_dict[var_op] delayed_offspring.append(dask.delayed(copy_and_change)(parents, var_op, rng=rng)) offspring = dask.compute(*delayed_offspring, num_workers=n_jobs, threads_per_worker=1) return offspring def nonparallel_create_offpring(parents_list, var_op_list, rng, n_jobs=1): rng = np.random.default_rng(rng) offspring = [] for parents, var_op in zip(parents_list,var_op_list): #TODO put this loop in population class if var_op in built_in_var_ops_dict: var_op = built_in_var_ops_dict[var_op] offspring.append(copy_and_change(parents, var_op, rng=rng)) return offspring def copy_and_change(parents, var_op, rng): rng = np.random.default_rng(rng) offspring = copy.deepcopy(parents) offspring = var_op(offspring, rng=rng) if isinstance(offspring, collections.abc.Iterable): offspring = offspring[0] return offspring def copy_and_mutate(parents, var_op, rng): rng = np.random.default_rng(rng) offspring = copy.deepcopy(parents) var_op(offspring, rng=rng) if isinstance(offspring, collections.abc.Iterable): offspring = offspring[0] return offspring def copy_and_crossover(parents, var_op, rng): rng = np.random.default_rng(rng) offspring = copy.deepcopy(parents) var_op(offspring[0],offspring[1], rng=rng) return offspring[0] def parallel_get_id(n_jobs, individual_list): id_list = Parallel(n_jobs=n_jobs)(delayed(get_id)(ind) for ind in individual_list) return id_list ================================================ FILE: tpot/search_spaces/__init__.py ================================================ from .base import * from . import nodes from . import pipelines ================================================ FILE: tpot/search_spaces/base.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import sklearn from sklearn.base import BaseEstimator import sklearn import networkx as nx from . import graph_utils from typing import final class SklearnIndividual(tpot.BaseIndividual): def __init_subclass__(cls): cls.crossover = cls.validate_same_type(cls.crossover) def __init__(self,) -> None: super().__init__() def mutate(self, rng=None): return def crossover(self, other, rng=None, **kwargs): return @final def validate_same_type(func): def wrapper(self, other, rng=None, **kwargs): if not isinstance(other, type(self)): return False return func(self, other, rng=rng, **kwargs) return wrapper def export_pipeline(self, **kwargs) -> BaseEstimator: return def unique_id(self): """ Returns a unique identifier for the individual. Used for preventing duplicate individuals from being evaluated. """ return self #TODO currently TPOT population class manually uses the unique_id to generate the index for the population data frame. #alternatively, the index could be the individual itself, with the __eq__ and __hash__ methods implemented. # Though this breaks the graphpipeline. When a mutation is called, it changes the __eq__ and __hash__ outputs. # Since networkx uses the hash and eq to determine if a node is already in the graph, this causes the graph thing that # This is a new node not in the graph. But this could be changed if when the graphpipeline mutates nodes, # it "replaces" the existing node with the mutated node. This would require a change in the graphpipeline class. # def __eq__(self, other): # return self.unique_id() == other.unique_id() # def __hash__(self): # return hash(self.unique_id()) #number of components in the pipeline def get_size(self): return 1 @final def export_flattened_graphpipeline(self, **graphpipeline_kwargs) -> tpot.GraphPipeline: return flatten_to_graphpipeline(self.export_pipeline(), **graphpipeline_kwargs) class SearchSpace(): def __init__(self,): pass def generate(self, rng=None) -> SklearnIndividual: pass def flatten_graphpipeline(est): flattened_full_graph = est.graph.copy() #put ests into the node label from the attributes flattened_full_graph = nx.relabel_nodes(flattened_full_graph, {n: flattened_full_graph.nodes[n]['instance'] for n in flattened_full_graph.nodes}) remove_list = [] for node in flattened_full_graph.nodes: if isinstance(node, nx.DiGraph): flattened = flatten_any(node) roots = graph_utils.get_roots(flattened) leaves = graph_utils.get_leaves(flattened) n1_s = flattened_full_graph.successors(node) n1_p = flattened_full_graph.predecessors(node) remove_list.append(node) flattened_full_graph = nx.compose(flattened_full_graph, flattened) flattened_full_graph.add_edges_from([ (n2, n) for n in n1_s for n2 in leaves]) flattened_full_graph.add_edges_from([ (n, n2) for n in n1_p for n2 in roots]) for node in remove_list: flattened_full_graph.remove_node(node) return flattened_full_graph def flatten_pipeline(est): graph = nx.DiGraph() steps = [flatten_any(s[1]) for s in est.steps] #add steps to graph and connect them for s in steps: graph = nx.compose(graph, s) #connect leaves of each step to the roots of the next step for i in range(len(steps)-1): roots = graph_utils.get_roots(steps[i]) leaves = graph_utils.get_leaves(steps[i+1]) graph.add_edges_from([ (l,r) for l in leaves for r in roots]) return graph def flatten_estimator(est): graph = nx.DiGraph() graph.add_node(est) return graph def flatten_any(est): if isinstance(est, tpot.GraphPipeline): return flatten_graphpipeline(est) elif isinstance(est, sklearn.pipeline.Pipeline): return flatten_pipeline(est) else: return flatten_estimator(est) def flatten_to_graphpipeline(est, **graphpipeline_kwargs): #rename nodes to string representation of the instance and put the instance in the node attributes flattened_full_graph = flatten_any(est) instance_to_label = {} label_to_instance = {} for node in flattened_full_graph.nodes: found_unique_label = False i=1 while not found_unique_label: new_label = f"{node.__class__.__name__}_{i}" if new_label not in label_to_instance: found_unique_label = True i+=1 label_to_instance[new_label] = node instance_to_label[node] = new_label flattened_full_graph = nx.relabel_nodes(flattened_full_graph, instance_to_label) for label, instance in label_to_instance.items(): flattened_full_graph.nodes[label]["instance"] = instance return tpot.GraphPipeline(flattened_full_graph, **graphpipeline_kwargs) ================================================ FILE: tpot/search_spaces/graph_utils.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import networkx as nx import numpy as np def remove_and_stitch(graph, node): successors = graph.successors(node) predecessors = graph.predecessors(node) graph.remove_node(node) for s in successors: for p in predecessors: graph.add_edge(p,s) def remove_nodes_disconnected_from_node(graph, node): descendants = nx.descendants(graph, node) for n in list(graph.nodes): if n not in descendants and n is not node: graph.remove_node(n) #graph.remove_nodes_from([n for n in graph.nodes if n not in nx.descendants(graph, node) and n is not node]) def get_roots(graph): return [v for v, d in graph.in_degree() if d == 0] def get_leaves(graph): return [v for v, d in graph.out_degree() if d == 0] def get_max_path_through_node(graph, root, node): if len(list(graph.successors(node)))==0: return get_max_path_size(graph, root, node) else: leaves = [n for n in nx.descendants(graph,node) if len(list(graph.successors(n)))==0] return max([get_max_path_size(graph, root, l) for l in leaves]) def get_max_path_size(graph, fromnode1,tonode2, return_path=False): if fromnode1 is tonode2: if return_path: return [fromnode1] return 1 else: max_length_path = max(nx.all_simple_paths(graph, fromnode1, tonode2), key=lambda x: len(x)) if return_path: return max_length_path return len(max_length_path) #gets the max path and finds the length of that path def invert_dictionary(d): inv_map = {} for k, v in d.items(): inv_map.setdefault(v, set()).add(k) return inv_map def select_nodes_same_depth(g1, node1, g2, node2, rng=None): rng = np.random.default_rng(rng) g1_nodes = nx.shortest_path_length(g1, source=node1) g2_nodes = nx.shortest_path_length(g2, source=node2) max_depth = max(list(g1_nodes.values()) + list(g2_nodes.values())) g1_nodes = invert_dictionary(g1_nodes) g2_nodes = invert_dictionary(g2_nodes) # depth_number_of_nodes = [] # for i in range(max_depth+1): # n = 0 # if i in g1_nodes and i in g2_nodes: # depth_number_of_nodes.append(len(g1_nodes[i])+len(g1_nodes[i])) # else: # break possible_pairs = [] for i in range(max_depth+1): if i in g1_nodes and i in g2_nodes: for n1 in g1_nodes[i]: for n2 in g2_nodes[i]: possible_pairs.append( (n1,n2) ) rng.shuffle(possible_pairs) for p in possible_pairs: yield p[0], p[1] def select_nodes_randomly(g1, g2, rng=None): rng = np.random.default_rng(rng) sorted_self_nodes_list = list(g1.nodes) rng.shuffle(sorted_self_nodes_list) sorted_other_nodes_list = list(g2.nodes) rng.shuffle(sorted_other_nodes_list) for node1 in sorted_self_nodes_list: for node2 in sorted_other_nodes_list: if node1 is node2: continue yield node1, node2 ================================================ FILE: tpot/search_spaces/nodes/__init__.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from .estimator_node import * from .genetic_feature_selection import * from .fss_node import * ================================================ FILE: tpot/search_spaces/nodes/estimator_node.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ # try https://automl.github.io/ConfigSpace/main/api/hyperparameters.html import numpy as np from ..base import SklearnIndividual, SearchSpace from ConfigSpace import ConfigurationSpace from typing import final def default_hyperparameter_parser(params:dict) -> dict: return params class EstimatorNodeIndividual(SklearnIndividual): """ Note that ConfigurationSpace does not support None as a parameter. Instead, use the special string "". TPOT will automatically replace instances of this string with the Python None. Parameters ---------- method : type The class of the estimator to be used space : ConfigurationSpace|dict The hyperparameter space to be used. If a dict is passed, hyperparameters are fixed and not learned. """ def __init__(self, method: type, space: ConfigurationSpace|dict, #TODO If a dict is passed, hyperparameters are fixed and not learned. Is this confusing? Should we make a second node type? hyperparameter_parser: callable = None, rng=None) -> None: super().__init__() self.method = method self.space = space if hyperparameter_parser is None: self.hyperparameter_parser = default_hyperparameter_parser else: self.hyperparameter_parser = hyperparameter_parser if isinstance(space, dict): self.hyperparameters = space else: rng = np.random.default_rng(rng) self.space.seed(rng.integers(0, 2**32)) self.hyperparameters = dict(self.space.sample_configuration()) def mutate(self, rng=None): if isinstance(self.space, dict): return False rng = np.random.default_rng(rng) self.space.seed(rng.integers(0, 2**32)) self.hyperparameters = dict(self.space.sample_configuration()) return True def crossover(self, other, rng=None): if isinstance(self.space, dict): return False rng = np.random.default_rng(rng) if self.method != other.method: return False #loop through hyperparameters, randomly swap items in self.hyperparameters with items in other.hyperparameters for hyperparameter in self.space: if rng.choice([True, False]): if hyperparameter in other.hyperparameters: self.hyperparameters[hyperparameter] = other.hyperparameters[hyperparameter] return True @final #this method should not be overridden, instead override hyperparameter_parser def export_pipeline(self, **kwargs): return self.method(**self.hyperparameter_parser(self.hyperparameters)) def unique_id(self): #return a dictionary of the method and the hyperparameters method_str = self.method.__name__ params = list(self.hyperparameters.keys()) params = sorted(params) id_str = f"{method_str}({', '.join([f'{param}={self.hyperparameters[param]}' for param in params])})" return id_str class EstimatorNode(SearchSpace): def __init__(self, method, space, hyperparameter_parser=default_hyperparameter_parser): self.method = method self.space = space self.hyperparameter_parser = hyperparameter_parser def generate(self, rng=None): return EstimatorNodeIndividual(self.method, self.space, hyperparameter_parser=self.hyperparameter_parser, rng=rng) ================================================ FILE: tpot/search_spaces/nodes/estimator_node_gradual.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ # try https://automl.github.io/ConfigSpace/main/api/hyperparameters.html import numpy as np from tpot.search_spaces.base import SklearnIndividual, SearchSpace from ConfigSpace import ConfigurationSpace from typing import final import ConfigSpace def default_hyperparameter_parser(params:dict) -> dict: return params # NOTE: This is not the default, currently experimental class EstimatorNodeIndividual_gradual(SklearnIndividual): """ Note that ConfigurationSpace does not support None as a parameter. Instead, use the special string "". TPOT will automatically replace instances of this string with the Python None. Parameters ---------- method : type The class of the estimator to be used space : ConfigurationSpace|dict The hyperparameter space to be used. If a dict is passed, hyperparameters are fixed and not learned. """ def __init__(self, method: type, space: ConfigurationSpace|dict, #TODO If a dict is passed, hyperparameters are fixed and not learned. Is this confusing? Should we make a second node type? hyperparameter_parser: callable = None, rng=None) -> None: super().__init__() self.method = method self.space = space if hyperparameter_parser is None: self.hyperparameter_parser = default_hyperparameter_parser else: self.hyperparameter_parser = hyperparameter_parser if isinstance(space, dict): self.hyperparameters = space else: rng = np.random.default_rng(rng) self.space.seed(rng.integers(0, 2**32)) self.hyperparameters = dict(self.space.sample_configuration()) def mutate(self, rng=None): if isinstance(self.space, dict): return False self.hyperparameters = gradual_hyperparameter_update(params=self.hyperparameters, configspace=self.space, rng=rng) return True def crossover(self, other, rng=None): if isinstance(self.space, dict): return False rng = np.random.default_rng(rng) if self.method != other.method: return False #loop through hyperparameters, randomly swap items in self.hyperparameters with items in other.hyperparameters for hyperparameter in self.space: if rng.choice([True, False]): if hyperparameter in other.hyperparameters: self.hyperparameters[hyperparameter] = other.hyperparameters[hyperparameter] return True @final #this method should not be overridden, instead override hyperparameter_parser def export_pipeline(self, **kwargs): return self.method(**self.hyperparameter_parser(self.hyperparameters)) def unique_id(self): #return a dictionary of the method and the hyperparameters method_str = self.method.__name__ params = list(self.hyperparameters.keys()) params = sorted(params) id_str = f"{method_str}({', '.join([f'{param}={self.hyperparameters[param]}' for param in params])})" return id_str def gradual_hyperparameter_update(params:dict, configspace:ConfigurationSpace, rng=None): rng = np.random.default_rng(rng) configspace.seed(rng.integers(0, 2**32)) new_params = dict(configspace.sample_configuration()) for param in list(new_params.keys()): #if parameter is float, multiply by normal distribution if param not in params: continue try: if issubclass(type(configspace[param]), ConfigSpace.hyperparameters.hyperparameter.FloatHyperparameter): if configspace[param].log: new_params[param] = params[param] * rng.lognormal(0, 1) else: new_params[param] = params[param] + rng.normal(0, .1)* (configspace[param].upper-configspace[param].lower) # if check if above or below min and cap if new_params[param] < configspace[param].lower: new_params[param] = configspace[param].lower elif new_params[param] > configspace[param].upper: new_params[param] = configspace[param].upper #if parameter is integer, add normal distribution elif issubclass(type(configspace[param]), ConfigSpace.hyperparameters.hyperparameter.IntegerHyperparameter): new_params[param] = params[param] * rng.normal(0, 1) # if check if above or below min and cap if new_params[param] < configspace[param].lower: new_params[param] = configspace[param].lower elif new_params[param] > configspace[param].upper: new_params[param] = configspace[param].upper new_params[param] = int(new_params[param]) # TODO : add support for categorical hyperparameters else: new_params[param] = params[param] except: pass return new_params class EstimatorNode_gradual(SearchSpace): def __init__(self, method, space, hyperparameter_parser=default_hyperparameter_parser): self.method = method self.space = space self.hyperparameter_parser = hyperparameter_parser def generate(self, rng=None): return EstimatorNodeIndividual_gradual(self.method, self.space, hyperparameter_parser=self.hyperparameter_parser, rng=rng) ================================================ FILE: tpot/search_spaces/nodes/fss_node.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from numpy import iterable import tpot import numpy as np import sklearn import sklearn.datasets import numpy as np import pandas as pd import os, os.path from sklearn.base import BaseEstimator from sklearn.feature_selection._base import SelectorMixin from ..base import SklearnIndividual, SearchSpace from ...builtin_modules.feature_set_selector import FeatureSetSelector class FSSIndividual(SklearnIndividual): def __init__( self, subsets, rng=None, ): """ An individual for representing a specific FeatureSetSelector. The FeatureSetSelector selects a feature list of list of predefined feature subsets. This instance will select one set initially. Mutation and crossover can swap the selected subset with another. Parameters ---------- subsets : str or list, default=None Sets the subsets that the FeatureSetSeletor will select from if set as an option in one of the configuration dictionaries. Features are defined by column names if using a Pandas data frame, or ints corresponding to indexes if using numpy arrays. - str : If a string, it is assumed to be a path to a csv file with the subsets. The first column is assumed to be the name of the subset and the remaining columns are the features in the subset. - list or np.ndarray : If a list or np.ndarray, it is assumed to be a list of subsets (i.e a list of lists). - dict : A dictionary where keys are the names of the subsets and the values are the list of features. - int : If an int, it is assumed to be the number of subsets to generate. Each subset will contain one feature. - None : If None, each column will be treated as a subset. One column will be selected per subset. rng : int, np.random.Generator, optional The random number generator. The default is None. Only used to select the first subset. Returns ------- None """ subsets = subsets rng = np.random.default_rng(rng) if isinstance(subsets, str): df = pd.read_csv(subsets,header=None,index_col=0) df['features'] = df.apply(lambda x: list([x[c] for c in df.columns]),axis=1) self.subset_dict = {} for row in df.index: self.subset_dict[row] = df.loc[row]['features'] elif isinstance(subsets, dict): self.subset_dict = subsets elif isinstance(subsets, list) or isinstance(subsets, np.ndarray): self.subset_dict = {str(i):subsets[i] for i in range(len(subsets))} elif isinstance(subsets, int): self.subset_dict = {"{0}".format(i):i for i in range(subsets)} else: raise ValueError("Subsets must be a string, dictionary, list, int, or numpy array") self.names_list = list(self.subset_dict.keys()) self.selected_subset_name = rng.choice(self.names_list) self.sel_subset = self.subset_dict[self.selected_subset_name] def mutate(self, rng=None): rng = np.random.default_rng(rng) #get list of names not including the current one names = [name for name in self.names_list if name != self.selected_subset_name] self.selected_subset_name = rng.choice(names) self.sel_subset = self.subset_dict[self.selected_subset_name] def crossover(self, other, rng=None): self.selected_subset_name = other.selected_subset_name self.sel_subset = other.sel_subset def export_pipeline(self, **kwargs): return FeatureSetSelector(sel_subset=self.sel_subset, name=self.selected_subset_name) def unique_id(self): id_str = "FeatureSetSelector({0})".format(self.selected_subset_name) return id_str class FSSNode(SearchSpace): def __init__(self, subsets, ): """ A search space for a FeatureSetSelector. The FeatureSetSelector selects a feature list of list of predefined feature subsets. Parameters ---------- subsets : str or list, default=None Sets the subsets that the FeatureSetSeletor will select from if set as an option in one of the configuration dictionaries. Features are defined by column names if using a Pandas data frame, or ints corresponding to indexes if using numpy arrays. - str : If a string, it is assumed to be a path to a csv file with the subsets. The first column is assumed to be the name of the subset and the remaining columns are the features in the subset. - list or np.ndarray : If a list or np.ndarray, it is assumed to be a list of subsets (i.e a list of lists). - dict : A dictionary where keys are the names of the subsets and the values are the list of features. - int : If an int, it is assumed to be the number of subsets to generate. Each subset will contain one feature. - None : If None, each column will be treated as a subset. One column will be selected per subset. Returns ------- None """ self.subsets = subsets def generate(self, rng=None) -> SklearnIndividual: return FSSIndividual( subsets=self.subsets, rng=rng, ) ================================================ FILE: tpot/search_spaces/nodes/genetic_feature_selection.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from numpy import iterable import tpot import numpy as np import sklearn import sklearn.datasets import numpy as np import pandas as pd import os, os.path from sklearn.base import BaseEstimator from sklearn.feature_selection._base import SelectorMixin from ..base import SklearnIndividual, SearchSpace class MaskSelector(SelectorMixin, BaseEstimator): """Select predefined feature subsets.""" def __init__(self, mask, set_output_transform=None): self.mask = mask self.set_output_transform = set_output_transform if set_output_transform is not None: self.set_output(transform=set_output_transform) def fit(self, X, y=None): self.n_features_in_ = X.shape[1] if isinstance(X, pd.DataFrame): self.feature_names_in_ = X.columns # self.set_output(transform="pandas") self.is_fitted_ = True #so sklearn knows it's fitted return self def __sklearn_tags__(self): tags = super().__sklearn_tags__() tags.input_tags.allow_nan = True tags.target_tags.required = False # formally requires_y return tags def _get_support_mask(self): return np.array(self.mask) def get_feature_names_out(self, input_features=None): return self.feature_names_in_[self.get_support()] class GeneticFeatureSelectorIndividual(SklearnIndividual): def __init__( self, mask, start_p=0.2, mutation_rate = 0.5, crossover_rate = 0.5, mutation_rate_rate = 0, crossover_rate_rate = 0, rng=None, ): self.start_p = start_p self.mutation_rate = mutation_rate self.crossover_rate = crossover_rate self.mutation_rate_rate = mutation_rate_rate self.crossover_rate_rate = crossover_rate_rate rng = np.random.default_rng(rng) if isinstance(mask, int): #list of random bollean values self.mask = rng.choice([True, False], size=mask, p=[self.start_p,1-self.start_p]) else: self.mask = mask # check if there are no features selected, if so select one if sum(self.mask) == 0: index = rng.choice(len(self.mask)) self.mask[index] = True self.mutation_list = [self._mutate_add, self._mutate_remove] self.crossover_list = [self._crossover_swap] def mutate(self, rng=None): rng = np.random.default_rng(rng) if rng.uniform() < self.mutation_rate_rate: self.mutation_rate = self.mutation_rate * rng.uniform(0.5, 2) self.mutation_rate = min(self.mutation_rate, 2) self.mutation_rate = max(self.mutation_rate, 1/len(self.mask)) return rng.choice(self.mutation_list)(rng) def crossover(self, other, rng=None): rng = np.random.default_rng(rng) if rng.uniform() < self.crossover_rate_rate: self.crossover_rate = self.crossover_rate * rng.uniform(0.5, 2) self.crossover_rate = min(self.crossover_rate, .6) self.crossover_rate = max(self.crossover_rate, 1/len(self.mask)) return rng.choice(self.crossover_list)(other, rng) # def _mutate_add(self, rng=None): # rng = np.random.default_rng(rng) # add_mask = rng.choice([True, False], size=self.mask.shape, p=[self.mutation_rate,1-self.mutation_rate]) # self.mask = np.logical_or(self.mask, add_mask) # return True # def _mutate_remove(self, rng=None): # rng = np.random.default_rng(rng) # add_mask = rng.choice([False, True], size=self.mask.shape, p=[self.mutation_rate,1-self.mutation_rate]) # self.mask = np.logical_and(self.mask, add_mask) # return True def _mutate_add(self, rng=None): rng = np.random.default_rng(rng) num_pos = np.sum(self.mask) num_neg = len(self.mask) - num_pos if num_neg == 0: return False to_add = int(self.mutation_rate * num_pos) to_add = max(to_add, 1) p = to_add / num_neg p = min(p, 1) add_mask = rng.choice([True, False], size=self.mask.shape, p=[p,1-p]) if sum(np.logical_or(self.mask, add_mask)) == 0: pass self.mask = np.logical_or(self.mask, add_mask) return True def _mutate_remove(self, rng=None): rng = np.random.default_rng(rng) num_pos = np.sum(self.mask) if num_pos == 1: return False num_neg = len(self.mask) - num_pos to_remove = int(self.mutation_rate * num_pos) to_remove = max(to_remove, 1) p = to_remove / num_pos p = min(p, .5) remove_mask = rng.choice([True, False], size=self.mask.shape, p=[p,1-p]) self.mask = np.logical_and(self.mask, remove_mask) if sum(self.mask) == 0: index = rng.choice(len(self.mask)) self.mask[index] = True return True def _crossover_swap(self, ss2, rng=None): rng = np.random.default_rng(rng) mask = rng.choice([True, False], size=self.mask.shape, p=[self.crossover_rate,1-self.crossover_rate]) self.mask = np.where(mask, self.mask, ss2.mask) def export_pipeline(self, **kwargs): return MaskSelector(mask=self.mask) def unique_id(self): mask_idexes = np.where(self.mask)[0] id_str = ','.join([str(i) for i in mask_idexes]) return id_str class GeneticFeatureSelectorNode(SearchSpace): def __init__(self, n_features, start_p=0.2, mutation_rate = 0.1, crossover_rate = 0.1, mutation_rate_rate = 0, # These are still experimental but seem to help. Theory is that it takes slower steps as it gets closer to the optimal solution. crossover_rate_rate = 0,# Otherwise is mutation_rate is too small, it takes forever, and if its too large, it never converges. ): """ A node that generates a GeneticFeatureSelectorIndividual. Uses genetic algorithm to select novel subsets of features. Parameters ---------- n_features : int Number of features in the dataset. start_p : float Probability of selecting a given feature for the initial subset of features. mutation_rate : float Probability of adding/removing a feature from the subset of features. crossover_rate : float Probability of swapping a feature between two subsets of features. mutation_rate_rate : float Probability of changing the mutation rate. (experimental) crossover_rate_rate : float Probability of changing the crossover rate. (experimental) """ self.n_features = n_features self.start_p = start_p self.mutation_rate = mutation_rate self.crossover_rate = crossover_rate self.mutation_rate_rate = mutation_rate_rate self.crossover_rate_rate = crossover_rate_rate def generate(self, rng=None) -> SklearnIndividual: return GeneticFeatureSelectorIndividual( mask=self.n_features, start_p=self.start_p, mutation_rate=self.mutation_rate, crossover_rate=self.crossover_rate, mutation_rate_rate=self.mutation_rate_rate, crossover_rate_rate=self.crossover_rate_rate, rng=rng ) ================================================ FILE: tpot/search_spaces/pipelines/__init__.py ================================================ from .choice import * from .dynamic_linear import * from .sequential import * from .graph import * from .tree import * from .wrapper import * from .dynamicunion import * from .union import * ================================================ FILE: tpot/search_spaces/pipelines/choice.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np import pandas as pd import sklearn from tpot import config from typing import Generator, List, Tuple, Union import random from ..base import SklearnIndividual, SearchSpace class ChoicePipelineIndividual(SklearnIndividual): def __init__(self, search_spaces : List[SearchSpace], rng=None) -> None: super().__init__() rng = np.random.default_rng(rng) self.search_spaces = search_spaces self.node = rng.choice(self.search_spaces).generate(rng=rng) def mutate(self, rng=None): rng = np.random.default_rng(rng) if rng.choice([True, False]): return self._mutate_select_new_node(rng) else: return self._mutate_node(rng) def _mutate_select_new_node(self, rng=None): rng = np.random.default_rng(rng) self.node = rng.choice(self.search_spaces).generate(rng=rng) return True def _mutate_node(self, rng=None): return self.node.mutate(rng) def crossover(self, other, rng=None): return self.node.crossover(other.node, rng) def export_pipeline(self, **kwargs): return self.node.export_pipeline(**kwargs) def unique_id(self): return self.node.unique_id() class ChoicePipeline(SearchSpace): def __init__(self, search_spaces : List[SearchSpace] ) -> None: self.search_spaces = search_spaces """ Takes in a list of search spaces. Will select one node from the search space. """ def generate(self, rng=None): rng = np.random.default_rng(rng) return ChoicePipelineIndividual(self.search_spaces, rng=rng) ================================================ FILE: tpot/search_spaces/pipelines/dynamic_linear.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np import pandas as pd import sklearn from tpot import config from typing import Generator, List, Tuple, Union import random from ..base import SklearnIndividual, SearchSpace import copy from ..tuple_index import TupleIndex class DynamicLinearPipelineIndividual(SklearnIndividual): # takes in a single search space. # will produce a pipeline of variable length. Each step in the pipeline will be pulled from the search space provided. def __init__(self, search_space : SearchSpace, max_length: int , rng=None) -> None: super().__init__() rng = np.random.default_rng(rng) self.search_space = search_space self.min_length = 1 self.max_length = max_length self.pipeline = self._generate_pipeline(rng) def _generate_pipeline(self, rng=None): rng = np.random.default_rng(rng) pipeline = [] length = rng.integers(self.min_length, self.max_length) length = min(length, 3) for _ in range(length): pipeline.append(self.search_space.generate(rng)) return pipeline def mutate(self, rng=None): rng = np.random.default_rng(rng) options = [] if len(self.pipeline) > self.min_length: options.append(self._mutate_remove_node) if len(self.pipeline) < self.max_length: options.append(self._mutate_add_node) options.append(self._mutate_step) return rng.choice(options)(rng) def _mutate_add_node(self, rng=None): rng = np.random.default_rng(rng) new_node = self.search_space.generate(rng) idx = rng.integers(len(self.pipeline)) self.pipeline.insert(idx, new_node) def _mutate_remove_node(self, rng=None): rng = np.random.default_rng(rng) idx = rng.integers(len(self.pipeline)) self.pipeline.pop(idx) def _mutate_step(self, rng=None): #choose a random step in the pipeline and mutate it rng = np.random.default_rng(rng) step = rng.choice(self.pipeline) return step.mutate(rng) def crossover(self, other, rng=None): #swap a random step in the pipeline with the corresponding step in the other pipeline rng = np.random.default_rng(rng) cx_funcs = [self._crossover_swap_multiple_nodes, self._crossover_node] rng.shuffle(cx_funcs) for cx_func in cx_funcs: if cx_func(other, rng): return True return False def _crossover_swap_multiple_nodes(self, other, rng): rng = np.random.default_rng(rng) max_steps = int(min(len(self.pipeline), len(other.pipeline))/2) max_steps = max(max_steps, 1) if max_steps == 1: n_steps_to_swap = 1 else: n_steps_to_swap = rng.integers(1, max_steps) other_indexes_to_take = rng.choice(len(other.pipeline), n_steps_to_swap, replace=False) self_indexes_to_replace = rng.choice(len(self.pipeline), n_steps_to_swap, replace=False) # self.pipeline[self_indexes_to_replace], other.pipeline[other_indexes_to_take] = other.pipeline[other_indexes_to_take], self.pipeline[self_indexes_to_replace] for self_idx, other_idx in zip(self_indexes_to_replace, other_indexes_to_take): self.pipeline[self_idx], other.pipeline[other_idx] = other.pipeline[other_idx], self.pipeline[self_idx] return True def _crossover_swap_node(self, other, rng): if len(self.pipeline) != len(other.pipeline): return False if len(self.pipeline) < 2: return False rng = np.random.default_rng(rng) idx = rng.integers(1,len(self.pipeline)) self.pipeline[idx], other.pipeline[idx] = other.pipeline[idx], self.pipeline[idx] return True def _crossover_node(self, other, rng): rng = np.random.default_rng(rng) pipeline1_indexes= list(range(len(self.pipeline))) pipeline2_indexes= list(range(len(other.pipeline))) rng.shuffle(pipeline1_indexes) rng.shuffle(pipeline2_indexes) crossover_success = False for idx1, idx2 in zip(pipeline1_indexes, pipeline2_indexes): if self.pipeline[idx1].crossover(other.pipeline[idx2], rng): crossover_success = True return crossover_success def export_pipeline(self, memory=None, **kwargs): return sklearn.pipeline.make_pipeline(*[step.export_pipeline(memory=memory, **kwargs) for step in self.pipeline], memory=memory) def unique_id(self): l = [step.unique_id() for step in self.pipeline] l = ["DynamicLinearPipeline"] + l return TupleIndex(tuple(l)) class DynamicLinearPipeline(SearchSpace): def __init__(self, search_space : SearchSpace, max_length: int ) -> None: self.search_space = search_space self.max_length = max_length """ Takes in a single search space. Will produce a linear pipeline of variable length. Each step in the pipeline will be pulled from the search space provided. """ def generate(self, rng=None): rng = np.random.default_rng(rng) return DynamicLinearPipelineIndividual(self.search_space, self.max_length, rng=rng) ================================================ FILE: tpot/search_spaces/pipelines/dynamicunion.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np import pandas as pd import sklearn from tpot import config from typing import Generator, List, Tuple, Union import random from ..base import SklearnIndividual, SearchSpace from ..tuple_index import TupleIndex class DynamicUnionPipelineIndividual(SklearnIndividual): """ Takes in one search space. Will produce a FeatureUnion of up to max_estimators number of steps. The output of the FeatureUnion will the all of the steps concatenated together. """ def __init__(self, search_space : SearchSpace, max_estimators=None, allow_repeats=False, rng=None) -> None: super().__init__() self.search_space = search_space if max_estimators is None: self.max_estimators = np.inf else: self.max_estimators = max_estimators self.allow_repeats = allow_repeats self.union_dict = {} if self.max_estimators == np.inf: init_max = 3 else: init_max = self.max_estimators rng = np.random.default_rng(rng) for _ in range(rng.integers(1, init_max)): self._mutate_add_step(rng) def mutate(self, rng=None): rng = np.random.default_rng(rng) mutation_funcs = [self._mutate_add_step, self._mutate_remove_step, self._mutate_replace_step, self._mutate_note] rng.shuffle(mutation_funcs) for mutation_func in mutation_funcs: if mutation_func(rng): return True def _mutate_add_step(self, rng): rng = np.random.default_rng(rng) max_attempts = 10 if len(self.union_dict) < self.max_estimators: for _ in range(max_attempts): new_step = self.search_space.generate(rng) if new_step.unique_id() not in self.union_dict: self.union_dict[new_step.unique_id()] = new_step return True return False def _mutate_remove_step(self, rng): rng = np.random.default_rng(rng) if len(self.union_dict) > 1: self.union_dict.pop( rng.choice(list(self.union_dict.keys()))) return True return False def _mutate_replace_step(self, rng): rng = np.random.default_rng(rng) changed = self._mutate_remove_step(rng) or self._mutate_add_step(rng) return changed #TODO mutate one step or multiple? def _mutate_note(self, rng): rng = np.random.default_rng(rng) changed = False values = list(self.union_dict.values()) for step in values: if rng.random() < 0.5: changed = step.mutate(rng) or changed self.union_dict = {step.unique_id(): step for step in values} return changed def crossover(self, other, rng=None): rng = np.random.default_rng(rng) cx_funcs = [self._crossover_swap_multiple_nodes, self._crossover_node] rng.shuffle(cx_funcs) for cx_func in cx_funcs: if cx_func(other, rng): return True return False def _crossover_swap_multiple_nodes(self, other, rng): rng = np.random.default_rng(rng) self_values = list(self.union_dict.values()) other_values = list(other.union_dict.values()) rng.shuffle(self_values) rng.shuffle(other_values) self_idx = rng.integers(0,len(self_values)) other_idx = rng.integers(0,len(other_values)) #Note that this is not one-point-crossover since the sequence doesn't matter. this is just a quick way to swap multiple random items self_values[:self_idx], other_values[:other_idx] = other_values[:other_idx], self_values[:self_idx] self.union_dict = {step.unique_id(): step for step in self_values} other.union_dict = {step.unique_id(): step for step in other_values} return True def _crossover_node(self, other, rng): rng = np.random.default_rng(rng) changed = False self_values = list(self.union_dict.values()) other_values = list(other.union_dict.values()) rng.shuffle(self_values) rng.shuffle(other_values) for self_step, other_step in zip(self_values, other_values): if rng.random() < 0.5: changed = self_step.crossover(other_step, rng) or changed self.union_dict = {step.unique_id(): step for step in self_values} other.union_dict = {step.unique_id(): step for step in other_values} return changed def export_pipeline(self, **kwargs): values = list(self.union_dict.values()) return sklearn.pipeline.make_union(*[step.export_pipeline(**kwargs) for step in values]) def unique_id(self): values = list(self.union_dict.values()) l = [step.unique_id() for step in values] # if all items are strings, then sort them if all([isinstance(x, str) for x in l]): l.sort() l = ["FeatureUnion"] + l return TupleIndex(frozenset(l)) class DynamicUnionPipeline(SearchSpace): def __init__(self, search_space : SearchSpace, max_estimators=None, allow_repeats=False ) -> None: """ Takes in a list of search spaces. will produce a pipeline of Sequential length. Each step in the pipeline will correspond to the the search space provided in the same index. """ self.search_space = search_space self.max_estimators = max_estimators self.allow_repeats = allow_repeats def generate(self, rng=None): rng = np.random.default_rng(rng) return DynamicUnionPipelineIndividual(self.search_space, max_estimators=self.max_estimators, allow_repeats=self.allow_repeats, rng=rng) ================================================ FILE: tpot/search_spaces/pipelines/graph.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np from typing import Generator, List, Tuple, Union from ..base import SklearnIndividual, SearchSpace import networkx as nx import copy import matplotlib.pyplot as plt import itertools from ..graph_utils import * from ..nodes.estimator_node import EstimatorNodeIndividual from typing import Union, Callable import sklearn from functools import partial import random class GraphPipelineIndividual(SklearnIndividual): """ Defines a search space of pipelines in the shape of a Directed Acyclic Graphs. The search spaces for root, leaf, and inner nodes can be defined separately if desired. Each graph will have a single root serving as the final estimator which is drawn from the `root_search_space`. If the `leaf_search_space` is defined, all leaves in the pipeline will be drawn from that search space. If the `leaf_search_space` is not defined, all leaves will be drawn from the `inner_search_space`. Nodes that are not leaves or roots will be drawn from the `inner_search_space`. If the `inner_search_space` is not defined, there will be no inner nodes. `cross_val_predict_cv`, `method`, `memory`, and `use_label_encoder` are passed to the GraphPipeline object when the pipeline is exported and not directly used in the search space. Exports to a GraphPipeline object. Parameters ---------- root_search_space: SearchSpace The search space for the root node of the graph. This node will be the final estimator in the pipeline. inner_search_space: SearchSpace, optional The search space for the inner nodes of the graph. If not defined, there will be no inner nodes. leaf_search_space: SearchSpace, optional The search space for the leaf nodes of the graph. If not defined, the leaf nodes will be drawn from the inner_search_space. crossover_same_depth: bool, optional If True, crossover will only occur between nodes at the same depth in the graph. If False, crossover will occur between nodes at any depth. cross_val_predict_cv: int, cross-validation generator or an iterable, optional Determines the cross-validation splitting strategy used in inner classifiers or regressors method: str, optional The prediction method to use for the inner classifiers or regressors. If 'auto', it will try to use predict_proba, decision_function, or predict in that order. memory: str or object with the joblib.Memory interface, optional Used to cache the input and outputs of nodes to prevent refitting or computationally heavy transformations. By default, no caching is performed. If a string is given, it is the path to the caching directory. use_label_encoder: bool, optional If True, the label encoder is used to encode the labels to be 0 to N. If False, the label encoder is not used. Mainly useful for classifiers (XGBoost) that require labels to be ints from 0 to N. Can also be a sklearn.preprocessing.LabelEncoder object. If so, that label encoder is used. rng: int, RandomState instance or None, optional Seed for sampling the first graph instance. """ def __init__( self, root_search_space: SearchSpace, leaf_search_space: SearchSpace = None, inner_search_space: SearchSpace = None, max_size: int = np.inf, crossover_same_depth: bool = False, cross_val_predict_cv: Union[int, Callable] = 0, #signature function(estimator, X, y=none) method: str = 'auto', use_label_encoder: bool = False, rng=None): super().__init__() self.__debug = False rng = np.random.default_rng(rng) self.root_search_space = root_search_space self.leaf_search_space = leaf_search_space self.inner_search_space = inner_search_space self.max_size = max_size self.crossover_same_depth = crossover_same_depth self.cross_val_predict_cv = cross_val_predict_cv self.method = method self.use_label_encoder = use_label_encoder self.root = self.root_search_space.generate(rng) self.graph = nx.DiGraph() self.graph.add_node(self.root) if self.leaf_search_space is not None: self.leaf = self.leaf_search_space.generate(rng) self.graph.add_node(self.leaf) self.graph.add_edge(self.root, self.leaf) if self.inner_search_space is None and self.leaf_search_space is None: self.mutate_methods_list = [self._mutate_node] self.crossover_methods_list = [self._crossover_swap_branch,]#[self._crossover_swap_branch, self._crossover_swap_node, self._crossover_take_branch] #TODO self._crossover_nodes, else: self.mutate_methods_list = [self._mutate_insert_leaf, self._mutate_insert_inner_node, self._mutate_remove_node, self._mutate_node, self._mutate_insert_bypass_node] self.crossover_methods_list = [self._crossover_swap_branch, self._crossover_nodes, self._crossover_take_branch ]#[self._crossover_swap_branch, self._crossover_swap_node, self._crossover_take_branch] #TODO self._crossover_nodes, self.merge_duplicated_nodes_toggle = True self.graphkey = None def mutate(self, rng=None): rng = np.random.default_rng(rng) rng.shuffle(self.mutate_methods_list) for mutate_method in self.mutate_methods_list: if mutate_method(rng=rng): if self.merge_duplicated_nodes_toggle: self._merge_duplicated_nodes() if self.__debug: print(mutate_method) if self.root not in self.graph.nodes: print('lost root something went wrong with ', mutate_method) if len(self.graph.predecessors(self.root)) > 0: print('root has parents ', mutate_method) if any([n in nx.ancestors(self.graph,n) for n in self.graph.nodes]): print('a node is connecting to itself...') if self.__debug: try: nx.find_cycle(self.graph) print('something went wrong with ', mutate_method) except: pass self.graphkey = None return False def _mutate_insert_leaf(self, rng=None): rng = np.random.default_rng(rng) if self.max_size > self.graph.number_of_nodes(): sorted_nodes_list = list(self.graph.nodes) rng.shuffle(sorted_nodes_list) #TODO: sort by number of children and/or parents? bias model one way or another for node in sorted_nodes_list: #if leafs are protected, check if node is a leaf #if node is a leaf, skip because we don't want to add node on top of node if (self.leaf_search_space is not None #if leafs are protected and len(list(self.graph.successors(node))) == 0 #if node is leaf and len(list(self.graph.predecessors(node))) > 0 #except if node is root, in which case we want to add a leaf even if it happens to be a leaf too ): continue #If node *is* the root or is not a leaf, add leaf node. (dont want to add leaf on top of leaf) if self.leaf_search_space is not None: new_node = self.leaf_search_space.generate(rng) else: new_node = self.inner_search_space.generate(rng) self.graph.add_node(new_node) self.graph.add_edge(node, new_node) return True return False def _mutate_insert_inner_node(self, rng=None): """ Finds an edge in the graph and inserts a new node between the two nodes. Removes the edge between the two nodes. """ rng = np.random.default_rng(rng) if self.max_size > self.graph.number_of_nodes(): sorted_nodes_list = list(self.graph.nodes) sorted_nodes_list2 = list(self.graph.nodes) rng.shuffle(sorted_nodes_list) #TODO: sort by number of children and/or parents? bias model one way or another rng.shuffle(sorted_nodes_list2) for node in sorted_nodes_list: #loop through children of node for child_node in list(self.graph.successors(node)): if child_node is not node and child_node not in nx.ancestors(self.graph, node): if self.leaf_search_space is not None: #If if we are protecting leafs, dont add connection into a leaf if len(list(nx.descendants(self.graph,node))) ==0 : continue new_node = self.inner_search_space.generate(rng) self.graph.add_node(new_node) self.graph.add_edges_from([(node, new_node), (new_node, child_node)]) self.graph.remove_edge(node, child_node) return True return False def _mutate_remove_node(self, rng=None): ''' Removes a randomly chosen node and connects its parents to its children. If the node is the only leaf for an inner node and 'leaf_search_space' is not none, we do not remove it. ''' rng = np.random.default_rng(rng) nodes_list = list(self.graph.nodes) nodes_list.remove(self.root) leaves = get_leaves(self.graph) while len(nodes_list) > 0: node = rng.choice(nodes_list) nodes_list.remove(node) if self.leaf_search_space is not None and len(list(nx.descendants(self.graph,node))) == 0 : #if the node is a leaf if len(leaves) <= 1: continue #dont remove the last leaf leaf_parents = self.graph.predecessors(node) # if any of the parents of the node has one one child, continue if any([len(list(self.graph.successors(lp))) < 2 for lp in leaf_parents]): #dont remove a leaf if it is the only input into another node. continue remove_and_stitch(self.graph, node) remove_nodes_disconnected_from_node(self.graph, self.root) return True else: remove_and_stitch(self.graph, node) remove_nodes_disconnected_from_node(self.graph, self.root) return True return False def _mutate_node(self, rng=None): ''' Mutates the hyperparameters for a randomly chosen node in the graph. ''' rng = np.random.default_rng(rng) sorted_nodes_list = list(self.graph.nodes) rng.shuffle(sorted_nodes_list) completed_one = False for node in sorted_nodes_list: if node.mutate(rng): return True return False def _mutate_remove_edge(self, rng=None): ''' Deletes an edge as long as deleting that edge does not make the graph disconnected. ''' rng = np.random.default_rng(rng) sorted_nodes_list = list(self.graph.nodes) rng.shuffle(sorted_nodes_list) for child_node in sorted_nodes_list: parents = list(self.graph.predecessors(child_node)) if len(parents) > 1: # if it has more than one parent, you can remove an edge (if this is the only child of a node, it will become a leaf) for parent_node in parents: # if removing the egde will make the parent_node a leaf node, skip if self.leaf_search_space is not None and len(list(self.graph.successors(parent_node))) < 2: continue self.graph.remove_edge(parent_node, child_node) return True return False def _mutate_add_edge(self, rng=None): ''' Randomly add an edge from a node to another node that is not an ancestor of the first node. ''' rng = np.random.default_rng(rng) sorted_nodes_list = list(self.graph.nodes) rng.shuffle(sorted_nodes_list) for child_node in sorted_nodes_list: for parent_node in sorted_nodes_list: if self.leaf_search_space is not None: if len(list(self.graph.successors(parent_node))) == 0: continue # skip if # - parent and child are the same node # - edge already exists # - child is an ancestor of parent if (child_node is not parent_node) and not self.graph.has_edge(parent_node,child_node) and (child_node not in nx.ancestors(self.graph, parent_node)): self.graph.add_edge(parent_node,child_node) return True return False def _mutate_insert_bypass_node(self, rng=None): """ Pick two nodes (doesn't necessarily need to be connected). Create a new node. connect one node to the new node and the new node to the other node. Does not remove any edges. """ rng = np.random.default_rng(rng) if self.max_size > self.graph.number_of_nodes(): sorted_nodes_list = list(self.graph.nodes) sorted_nodes_list2 = list(self.graph.nodes) rng.shuffle(sorted_nodes_list) #TODO: sort by number of children and/or parents? bias model one way or another rng.shuffle(sorted_nodes_list2) for node in sorted_nodes_list: for child_node in sorted_nodes_list2: if child_node is not node and child_node not in nx.ancestors(self.graph, node): if self.leaf_search_space is not None: #If if we are protecting leafs, dont add connection into a leaf if len(list(nx.descendants(self.graph,node))) ==0 : continue new_node = self.inner_search_space.generate(rng) self.graph.add_node(new_node) self.graph.add_edges_from([(node, new_node), (new_node, child_node)]) return True return False def crossover(self, ind2, rng=None): ''' self is the first individual, ind2 is the second individual If crossover_same_depth, it will select graphindividuals at the same recursive depth. Otherwise, it will select graphindividuals randomly from the entire graph and its subgraphs. This does not impact graphs without subgraphs. And it does not impacts nodes that are not graphindividuals. Cros ''' rng = np.random.default_rng(rng) rng.shuffle(self.crossover_methods_list) finished = False for crossover_method in self.crossover_methods_list: if crossover_method(ind2, rng=rng): self._merge_duplicated_nodes() finished = True break if self.__debug: try: nx.find_cycle(self.graph) print('something went wrong with ', crossover_method) except: pass if finished: self.graphkey = None return finished def _crossover_swap_branch(self, G2, rng=None): ''' swaps a branch from parent1 with a branch from parent2. does not modify parent2 ''' rng = np.random.default_rng(rng) if self.crossover_same_depth: pair_gen = select_nodes_same_depth(self.graph, self.root, G2.graph, G2.root, rng=rng) else: pair_gen = select_nodes_randomly(self.graph, G2.graph, rng=rng) for node1, node2 in pair_gen: #TODO: if root is in inner_search_space, then do use it? if node1 is self.root or node2 is G2.root: #dont want to add root as inner node continue #check if node1 is a leaf and leafs are protected, don't add an input to the leave if self.leaf_search_space is not None: #if we are protecting leaves, node1_is_leaf = len(list(self.graph.successors(node1))) == 0 node2_is_leaf = len(list(G2.graph.successors(node2))) == 0 #if not ((node1_is_leaf and node1_is_leaf) or (not node1_is_leaf and not node2_is_leaf)): #if node1 is a leaf #if (node1_is_leaf and (not node2_is_leaf)) or ( (not node1_is_leaf) and node2_is_leaf): if not node1_is_leaf: #only continue if node1 and node2 are both leaves or both not leaves continue temp_graph_1 = self.graph.copy() temp_graph_1.remove_node(node1) remove_nodes_disconnected_from_node(temp_graph_1, self.root) #isolating the branch branch2 = G2.graph.copy() n2_descendants = nx.descendants(branch2,node2) for n in list(branch2.nodes): if n not in n2_descendants and n is not node2: #removes all nodes not in the branch branch2.remove_node(n) branch2 = copy.deepcopy(branch2) branch2_root = get_roots(branch2)[0] temp_graph_1.add_edges_from(branch2.edges) for p in list(self.graph.predecessors(node1)): temp_graph_1.add_edge(p,branch2_root) if temp_graph_1.number_of_nodes() > self.max_size: continue self.graph = temp_graph_1 return True return False def _crossover_take_branch(self, G2, rng=None): ''' Takes a subgraph from Parent2 and add it to a randomly chosen node in Parent1. ''' rng = np.random.default_rng(rng) if self.crossover_same_depth: pair_gen = select_nodes_same_depth(self.graph, self.root, G2.graph, G2.root, rng=rng) else: pair_gen = select_nodes_randomly(self.graph, G2.graph, rng=rng) for node1, node2 in pair_gen: #TODO: if root is in inner_search_space, then do use it? if node2 is G2.root: #dont want to add root as inner node continue #check if node1 is a leaf and leafs are protected, don't add an input to the leave if self.leaf_search_space is not None and len(list(self.graph.successors(node1))) == 0: continue #icheck if node2 is graph individual # if isinstance(node2,GraphIndividual): # if not ((isinstance(node2,GraphIndividual) and ("Recursive" in self.inner_search_space or "Recursive" in self.leaf_search_space))): # continue #isolating the branch branch2 = G2.graph.copy() n2_descendants = nx.descendants(branch2,node2) for n in list(branch2.nodes): if n not in n2_descendants and n is not node2: #removes all nodes not in the branch branch2.remove_node(n) #if node1 plus node2 branch has more than max_children, skip if branch2.number_of_nodes() + self.graph.number_of_nodes() > self.max_size: continue branch2 = copy.deepcopy(branch2) branch2_root = get_roots(branch2)[0] self.graph.add_edges_from(branch2.edges) self.graph.add_edge(node1,branch2_root) return True return False def _crossover_nodes(self, G2, rng=None): ''' Swaps the hyperparamters of one randomly chosen node in Parent1 with the hyperparameters of randomly chosen node in Parent2. ''' rng = np.random.default_rng(rng) if self.crossover_same_depth: pair_gen = select_nodes_same_depth(self.graph, self.root, G2.graph, G2.root, rng=rng) else: pair_gen = select_nodes_randomly(self.graph, G2.graph, rng=rng) for node1, node2 in pair_gen: #if both nodes are leaves if len(list(self.graph.successors(node1)))==0 and len(list(G2.graph.successors(node2)))==0: if node1.crossover(node2): return True #if both nodes are inner nodes if len(list(self.graph.successors(node1)))>0 and len(list(G2.graph.successors(node2)))>0: if len(list(self.graph.predecessors(node1)))>0 and len(list(G2.graph.predecessors(node2)))>0: if node1.crossover(node2): return True #if both nodes are root nodes if node1 is self.root and node2 is G2.root: if node1.crossover(node2): return True return False #not including the nodes, just their children #Finds leaves attached to nodes and swaps them def _crossover_swap_leaf_at_node(self, G2, rng=None): rng = np.random.default_rng(rng) if self.crossover_same_depth: pair_gen = select_nodes_same_depth(self.graph, self.root, G2.graph, G2.root, rng=rng) else: pair_gen = select_nodes_randomly(self.graph, G2.graph, rng=rng) success = False for node1, node2 in pair_gen: # if leaves are protected node1 and node2 must both be leaves or both be inner nodes if self.leaf_search_space is not None and not (len(list(self.graph.successors(node1)))==0 ^ len(list(G2.graph.successors(node2)))==0): continue #self_leafs = [c for c in nx.descendants(self.graph,node1) if len(list(self.graph.successors(c)))==0 and c is not node1] node_leafs = [c for c in nx.descendants(G2.graph,node2) if len(list(G2.graph.successors(c)))==0 and c is not node2] # if len(self_leafs) >0: # for c in self_leafs: # if random.choice([True,False]): # self.graph.remove_node(c) # G2.graph.add_edge(node2, c) # success = True if len(node_leafs) >0: for c in node_leafs: if rng.choice([True,False]): G2.graph.remove_node(c) self.graph.add_edge(node1, c) success = True return success #TODO edit so that G2 is not modified def _crossover_swap_node(self, G2, rng=None): ''' Swaps randomly chosen node from Parent1 with a randomly chosen node from Parent2. ''' rng = np.random.default_rng(rng) if self.crossover_same_depth: pair_gen = select_nodes_same_depth(self.graph, self.root, G2.graph, G2.root, rng=rng) else: pair_gen = select_nodes_randomly(self.graph, G2.graph, rng=rng) for node1, node2 in pair_gen: if node1 is self.root or node2 is G2.root: #TODO: allow root continue #if leaves are protected if self.leaf_search_space is not None: #if one node is a leaf, the other must be a leaf if not((len(list(self.graph.successors(node1)))==0) ^ (len(list(G2.graph.successors(node2)))==0)): continue #only continue if both are leaves, or both are not leaves n1_s = self.graph.successors(node1) n1_p = self.graph.predecessors(node1) n2_s = G2.graph.successors(node2) n2_p = G2.graph.predecessors(node2) self.graph.remove_node(node1) G2.graph.remove_node(node2) self.graph.add_node(node2) self.graph.add_edges_from([ (node2, n) for n in n1_s]) G2.graph.add_edges_from([ (node1, n) for n in n2_s]) self.graph.add_edges_from([ (n, node2) for n in n1_p]) G2.graph.add_edges_from([ (n, node1) for n in n2_p]) return True return False def _merge_duplicated_nodes(self): graph_changed = False merged = False while(not merged): node_list = list(self.graph.nodes) merged = True for node, other_node in itertools.product(node_list, node_list): if node is other_node: continue #If nodes are same class/hyperparameters if node.unique_id() == other_node.unique_id(): node_children = set(self.graph.successors(node)) other_node_children = set(self.graph.successors(other_node)) #if nodes have identical children, they can be merged if node_children == other_node_children: for other_node_parent in list(self.graph.predecessors(other_node)): if other_node_parent not in self.graph.predecessors(node): self.graph.add_edge(other_node_parent,node) self.graph.remove_node(other_node) merged=False graph_changed = True break return graph_changed def export_pipeline(self, memory=None, **kwargs): estimator_graph = self.graph.copy() #mapping = {node:node.method_class(**node.hyperparameters) for node in estimator_graph} label_remapping = {} label_to_instance = {} for node in estimator_graph: this_pipeline_node = node.export_pipeline(memory=memory, **kwargs) found_unique_label = False i=1 while not found_unique_label: label = "{0}_{1}".format(this_pipeline_node.__class__.__name__, i) if label not in label_to_instance: found_unique_label = True else: i+=1 label_remapping[node] = label label_to_instance[label] = this_pipeline_node estimator_graph = nx.relabel_nodes(estimator_graph, label_remapping) for label, instance in label_to_instance.items(): estimator_graph.nodes[label]["instance"] = instance return tpot.GraphPipeline(graph=estimator_graph, memory=memory, use_label_encoder=self.use_label_encoder, method=self.method, cross_val_predict_cv=self.cross_val_predict_cv) def plot(self): G = self.graph.reverse() #TODO clean this up try: pos = nx.planar_layout(G) # positions for all nodes except: pos = nx.shell_layout(G) # nodes options = {'edgecolors': 'tab:gray', 'node_size': 800, 'alpha': 0.9} nodelist = list(G.nodes) node_color = [plt.cm.Set1(G.nodes[n]['recursive depth']) for n in G] fig, ax = plt.subplots() nx.draw(G, pos, nodelist=nodelist, node_color=node_color, ax=ax, **options) '''edgelist = [] for n in n1.node_set: for child in n.children: edgelist.append((n,child))''' # edges #nx.draw_networkx_edges(G, pos, width=3.0, arrows=True) '''nx.draw_networkx_edges( G, pos, edgelist=[edgelist], width=8, alpha=0.5, edge_color='tab:red', )''' # some math labels labels = {} for i, n in enumerate(G.nodes): labels[n] = n.method_class.__name__ + "\n" + str(n.hyperparameters) nx.draw_networkx_labels(G, pos, labels,ax=ax, font_size=7, font_color='black') plt.tight_layout() plt.axis('off') plt.show() def unique_id(self): if self.graphkey is None: #copy self.graph new_graph = self.graph.copy() for n in new_graph.nodes: new_graph.nodes[n]['label'] = n.unique_id() new_graph = nx.convert_node_labels_to_integers(new_graph) self.graphkey = GraphKey(new_graph) return self.graphkey class GraphSearchPipeline(SearchSpace): def __init__(self, root_search_space: SearchSpace, leaf_search_space: SearchSpace = None, inner_search_space: SearchSpace = None, max_size: int = np.inf, crossover_same_depth: bool = False, cross_val_predict_cv: Union[int, Callable] = 0, #signature function(estimator, X, y=none) method: str = 'auto', use_label_encoder: bool = False): """ Defines a search space of pipelines in the shape of a Directed Acyclic Graphs. The search spaces for root, leaf, and inner nodes can be defined separately if desired. Each graph will have a single root serving as the final estimator which is drawn from the `root_search_space`. If the `leaf_search_space` is defined, all leaves in the pipeline will be drawn from that search space. If the `leaf_search_space` is not defined, all leaves will be drawn from the `inner_search_space`. Nodes that are not leaves or roots will be drawn from the `inner_search_space`. If the `inner_search_space` is not defined, there will be no inner nodes. `cross_val_predict_cv`, `method`, `memory`, and `use_label_encoder` are passed to the GraphPipeline object when the pipeline is exported and not directly used in the search space. Exports to a GraphPipeline object. Parameters ---------- root_search_space: SearchSpace The search space for the root node of the graph. This node will be the final estimator in the pipeline. inner_search_space: SearchSpace, optional The search space for the inner nodes of the graph. If not defined, there will be no inner nodes. leaf_search_space: SearchSpace, optional The search space for the leaf nodes of the graph. If not defined, the leaf nodes will be drawn from the inner_search_space. crossover_same_depth: bool, optional If True, crossover will only occur between nodes at the same depth in the graph. If False, crossover will occur between nodes at any depth. cross_val_predict_cv : int, default=0 Number of folds to use for the cross_val_predict function for inner classifiers and regressors. Estimators will still be fit on the full dataset, but the following node will get the outputs from cross_val_predict. - 0-1 : When set to 0 or 1, the cross_val_predict function will not be used. The next layer will get the outputs from fitting and transforming the full dataset. - >=2 : When fitting pipelines with inner classifiers or regressors, they will still be fit on the full dataset. However, the output to the next node will come from cross_val_predict with the specified number of folds. method: str, optional The prediction method to use for the inner classifiers or regressors. If 'auto', it will try to use predict_proba, decision_function, or predict in that order. memory: str or object with the joblib.Memory interface, optional Used to cache the input and outputs of nodes to prevent refitting or computationally heavy transformations. By default, no caching is performed. If a string is given, it is the path to the caching directory. use_label_encoder: bool, optional If True, the label encoder is used to encode the labels to be 0 to N. If False, the label encoder is not used. Mainly useful for classifiers (XGBoost) that require labels to be ints from 0 to N. Can also be a sklearn.preprocessing.LabelEncoder object. If so, that label encoder is used. """ self.root_search_space = root_search_space self.leaf_search_space = leaf_search_space self.inner_search_space = inner_search_space self.max_size = max_size self.crossover_same_depth = crossover_same_depth self.cross_val_predict_cv = cross_val_predict_cv self.method = method self.use_label_encoder = use_label_encoder def generate(self, rng=None): rng = np.random.default_rng(rng) ind = GraphPipelineIndividual(self.root_search_space, self.leaf_search_space, self.inner_search_space, self.max_size, self.crossover_same_depth, self.cross_val_predict_cv, self.method, self.use_label_encoder, rng=rng) # if user specified limit, grab a random number between that limit if self.max_size is None or self.max_size == np.inf: n_nodes = rng.integers(1, 5) else: n_nodes = min(rng.integers(1, self.max_size), 5) starting_ops = [] if self.inner_search_space is not None: starting_ops.append(ind._mutate_insert_inner_node) if self.leaf_search_space is not None or self.inner_search_space is not None: starting_ops.append(ind._mutate_insert_leaf) n_nodes -= 1 if len(starting_ops) > 0: for _ in range(n_nodes-1): func = rng.choice(starting_ops) func(rng=rng) ind._merge_duplicated_nodes() return ind class GraphKey(): ''' A class that can be used as a key for a graph. Parameters ---------- graph : (nx.Graph) The graph to use as a key. Node Attributes are used for the hash. matched_label : (str) The node attribute to consider for the hash. ''' def __init__(self, graph, matched_label='label') -> None:#['hyperparameters', 'method_class']) -> None: self.graph = graph self.matched_label = matched_label self.node_match = partial(node_match, matched_labels=[matched_label]) self.key = int(nx.weisfeiler_lehman_graph_hash(self.graph, node_attr=self.matched_label),16) #hash(tuple(sorted([val for (node, val) in self.graph.degree()]))) #If hash is different, node is definitely different # https://arxiv.org/pdf/2002.06653.pdf def __hash__(self) -> int: return self.key #If hash is same, use __eq__ to know if they are actually different def __eq__(self, other): return nx.is_isomorphic(self.graph, other.graph, node_match=self.node_match) def node_match(n1,n2, matched_labels): return all( [ n1[m] == n2[m] for m in matched_labels]) ================================================ FILE: tpot/search_spaces/pipelines/sequential.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np import pandas as pd import sklearn from tpot import config from typing import Generator, List, Tuple, Union import random from ..base import SklearnIndividual, SearchSpace from ..tuple_index import TupleIndex class SequentialPipelineIndividual(SklearnIndividual): # takes in a list of search spaces. each space is a list of SearchSpaces. # will produce a pipeline of Sequential length. Each step in the pipeline will correspond to the the search space provided in the same index. def __init__(self, search_spaces : List[SearchSpace], rng=None) -> None: super().__init__() self.search_spaces = search_spaces self.pipeline = [] for space in self.search_spaces: self.pipeline.append(space.generate(rng)) self.pipeline = np.array(self.pipeline) #TODO, mutate all steps or just one? def mutate(self, rng=None): # mutated = False # for step in self.pipeline: # if rng.random() < 0.5: # if step.mutate(rng): # mutated = True # return mutated rng = np.random.default_rng(rng) step = rng.choice(self.pipeline) return step.mutate(rng) def crossover(self, other, rng=None): #swap a random step in the pipeline with the corresponding step in the other pipeline if len(self.pipeline) != len(other.pipeline): return False rng = np.random.default_rng(rng) cx_funcs = [self._crossover_swap_multiple_nodes, self._crossover_swap_segment, self._crossover_node] rng.shuffle(cx_funcs) for cx_func in cx_funcs: if cx_func(other, rng): return True return False def _crossover_swap_node(self, other, rng): if len(self.pipeline) != len(other.pipeline): return False rng = np.random.default_rng(rng) idx = rng.integers(1,len(self.pipeline)) self.pipeline[idx], other.pipeline[idx] = other.pipeline[idx], self.pipeline[idx] return True def _crossover_swap_multiple_nodes(self, other, rng): if len(self.pipeline) != len(other.pipeline): return False if len(self.pipeline) < 2: return False rng = np.random.default_rng(rng) max_steps = int(min(len(self.pipeline), len(other.pipeline))/2) max_steps = max(max_steps, 1) if max_steps == 1: n_steps_to_swap = 1 else: n_steps_to_swap = rng.integers(1, max_steps) indexes_to_swap = rng.choice(len(other.pipeline), n_steps_to_swap, replace=False) for idx in indexes_to_swap: self.pipeline[idx], other.pipeline[idx] = other.pipeline[idx], self.pipeline[idx] return True def _crossover_swap_segment(self, other, rng): if len(self.pipeline) != len(other.pipeline): return False if len(self.pipeline) < 2: return False rng = np.random.default_rng(rng) idx = rng.integers(1,len(self.pipeline)) left = rng.choice([True, False]) if left: self.pipeline[:idx], other.pipeline[:idx] = other.pipeline[:idx], self.pipeline[:idx] else: self.pipeline[idx:], other.pipeline[idx:] = other.pipeline[idx:], self.pipeline[idx:] return True def _crossover_node(self, other, rng): rng = np.random.default_rng(rng) # crossover_success = False # for idx in range(len(self.pipeline)): # if rng.random() < 0.5: # if self.pipeline[idx].crossover(other.pipeline[idx], rng): # crossover_success = True # return crossover_success crossover_success = False for idx in range(len(self.pipeline)): if rng.random() < 0.5: if self.pipeline[idx].crossover(other.pipeline[idx], rng): crossover_success = True return crossover_success def export_pipeline(self, memory=None, **kwargs): return sklearn.pipeline.make_pipeline(*[step.export_pipeline(memory=memory, **kwargs) for step in self.pipeline], memory=memory) def unique_id(self): l = [step.unique_id() for step in self.pipeline] l = ["SequentialPipeline"] + l return TupleIndex(tuple(l)) class SequentialPipeline(SearchSpace): def __init__(self, search_spaces : List[SearchSpace] ) -> None: """ Takes in a list of search spaces. will produce a pipeline of Sequential length. Each step in the pipeline will correspond to the the search space provided in the same index. """ self.search_spaces = search_spaces def generate(self, rng=None): rng = np.random.default_rng(rng) return SequentialPipelineIndividual(self.search_spaces, rng=rng) ================================================ FILE: tpot/search_spaces/pipelines/tests/test_graphspace.py ================================================ # Test all nodes have all dictionaries import pytest import tpot import tpot from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal from sklearn.neighbors import KNeighborsClassifier from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.preprocessing import StandardScaler def test_merge_duplicate_nodes(): knn_configspace = {} standard_scaler_configspace = {} knn_node = tpot.search_spaces.nodes.EstimatorNode( method = KNeighborsClassifier, space = knn_configspace, ) scaler_node = tpot.search_spaces.nodes.EstimatorNode( method = StandardScaler, space = standard_scaler_configspace, ) graph_search_space = tpot.search_spaces.pipelines.GraphSearchPipeline( root_search_space= knn_node, leaf_search_space = scaler_node, inner_search_space = None, max_size = 10, ) ind = graph_search_space.generate() # all of these leaves should be identical ind._mutate_insert_leaf() ind._mutate_insert_leaf() ind._mutate_insert_leaf() ind._mutate_insert_leaf() ind._merge_duplicated_nodes() assert len(ind.graph.nodes) == 2 ================================================ FILE: tpot/search_spaces/pipelines/tree.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np import pandas as pd import sklearn from tpot import config from typing import Generator, List, Tuple, Union import random from ..base import SklearnIndividual, SearchSpace import networkx as nx import copy import matplotlib.pyplot as plt from .graph import GraphPipelineIndividual from ..graph_utils import * class TreePipelineIndividual(GraphPipelineIndividual): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.crossover_methods_list = [self._crossover_swap_branch, self._crossover_swap_node, self._crossover_nodes] self.mutate_methods_list = [self._mutate_insert_leaf, self._mutate_insert_inner_node, self._mutate_remove_node, self._mutate_node] self.merge_duplicated_nodes_toggle = False class TreePipeline(SearchSpace): def __init__(self, root_search_space : SearchSpace, leaf_search_space : SearchSpace = None, inner_search_space : SearchSpace =None, min_size: int = 2, max_size: int = 10, crossover_same_depth=False) -> None: """ Generates a pipeline of variable length. Pipeline will have a tree structure similar to TPOT1. """ self.search_space = root_search_space self.leaf_search_space = leaf_search_space self.inner_search_space = inner_search_space self.min_size = min_size self.max_size = max_size self.crossover_same_depth = crossover_same_depth def generate(self, rng=None): rng = np.random.default_rng(rng) return TreePipelineIndividual(self.search_space, self.leaf_search_space, self.inner_search_space, self.min_size, self.max_size, self.crossover_same_depth, rng=rng) ================================================ FILE: tpot/search_spaces/pipelines/union.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np import pandas as pd import sklearn from tpot import config from typing import Generator, List, Tuple, Union import random from ..base import SklearnIndividual, SearchSpace from ..tuple_index import TupleIndex class UnionPipelineIndividual(SklearnIndividual): """ Takes in a list of search spaces. each space is a list of SearchSpaces. Will produce a FeatureUnion pipeline. Each step in the pipeline will correspond to the the search space provided in the same index. The resulting pipeline will be a FeatureUnion of the steps in the pipeline. """ def __init__(self, search_spaces : List[SearchSpace], rng=None) -> None: super().__init__() self.search_spaces = search_spaces self.pipeline = [] for space in self.search_spaces: self.pipeline.append(space.generate(rng)) def mutate(self, rng=None): rng = np.random.default_rng(rng) step = rng.choice(self.pipeline) return step.mutate(rng) def crossover(self, other, rng=None): #swap a random step in the pipeline with the corresponding step in the other pipeline rng = np.random.default_rng(rng) cx_funcs = [self._crossover_node, self._crossover_swap_node] rng.shuffle(cx_funcs) for cx_func in cx_funcs: if cx_func(other, rng): return True return False def _crossover_swap_node(self, other, rng): rng = np.random.default_rng(rng) idx = rng.integers(1,len(self.pipeline)) self.pipeline[idx], other.pipeline[idx] = other.pipeline[idx], self.pipeline[idx] return True def _crossover_node(self, other, rng): rng = np.random.default_rng(rng) crossover_success = False for idx in range(len(self.pipeline)): if rng.random() < 0.5: if self.pipeline[idx].crossover(other.pipeline[idx], rng): crossover_success = True return crossover_success def export_pipeline(self, **kwargs): return sklearn.pipeline.make_union(*[step.export_pipeline(**kwargs) for step in self.pipeline]) def unique_id(self): l = [step.unique_id() for step in self.pipeline] l = ["FeatureUnion"] + l return TupleIndex(tuple(l)) class UnionPipeline(SearchSpace): def __init__(self, search_spaces : List[SearchSpace] ) -> None: """ Takes in a list of search spaces. will produce a pipeline of Sequential length. Each step in the pipeline will correspond to the the search space provided in the same index. """ self.search_spaces = search_spaces def generate(self, rng=None): rng = np.random.default_rng(rng) return UnionPipelineIndividual(self.search_spaces, rng=rng) ================================================ FILE: tpot/search_spaces/pipelines/wrapper.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np import pandas as pd import sklearn from tpot import config from typing import Generator, List, Tuple, Union import random from ..base import SklearnIndividual, SearchSpace from ConfigSpace import ConfigurationSpace from ..tuple_index import TupleIndex class WrapperPipelineIndividual(SklearnIndividual): def __init__( self, method: type, space: ConfigurationSpace, estimator_search_space: SearchSpace, hyperparameter_parser: callable = None, wrapped_param_name: str = None, rng=None) -> None: super().__init__() self.method = method self.space = space self.estimator_search_space = estimator_search_space self.hyperparameters_parser = hyperparameter_parser self.wrapped_param_name = wrapped_param_name rng = np.random.default_rng(rng) self.node = self.estimator_search_space.generate(rng) if isinstance(space, dict): self.hyperparameters = space else: rng = np.random.default_rng(rng) self.space.seed(rng.integers(0, 2**32)) self.hyperparameters = dict(self.space.sample_configuration()) def mutate(self, rng=None): rng = np.random.default_rng(rng) if rng.choice([True, False]): return self._mutate_hyperparameters(rng) else: return self._mutate_node(rng) def _mutate_hyperparameters(self, rng=None): if isinstance(self.space, dict): return False rng = np.random.default_rng(rng) self.space.seed(rng.integers(0, 2**32)) self.hyperparameters = dict(self.space.sample_configuration()) return True def _mutate_node(self, rng=None): return self.node.mutate(rng) def crossover(self, other, rng=None): rng = np.random.default_rng(rng) if rng.choice([True, False]): return self._crossover_hyperparameters(other, rng) else: self.node.crossover(other.estimator_search_space, rng) def _crossover_hyperparameters(self, other, rng=None): if isinstance(self.space, dict): return False rng = np.random.default_rng(rng) if self.method != other.method: return False #loop through hyperparameters, randomly swap items in self.hyperparameters with items in other.hyperparameters for hyperparameter in self.space: if rng.choice([True, False]): if hyperparameter in other.hyperparameters: self.hyperparameters[hyperparameter] = other.hyperparameters[hyperparameter] return True def export_pipeline(self, **kwargs): if self.hyperparameters_parser is not None: final_params = self.hyperparameters_parser(self.hyperparameters) else: final_params = self.hyperparameters est = self.node.export_pipeline(**kwargs) wrapped_est = self.method(est, **final_params) return wrapped_est def unique_id(self): #return a dictionary of the method and the hyperparameters method_str = self.method.__name__ params = list(self.hyperparameters.keys()) params = sorted(params) id_str = f"{method_str}({', '.join([f'{param}={self.hyperparameters[param]}' for param in params])})" return TupleIndex(("WrapperPipeline", id_str, self.node.unique_id())) class WrapperPipeline(SearchSpace): def __init__( self, method: type, space: ConfigurationSpace, estimator_search_space: SearchSpace, hyperparameter_parser: callable = None, wrapped_param_name: str = None ) -> None: """ This search space is for wrapping a sklearn estimator with a method that takes another estimator and hyperparameters as arguments. For example, this can be used with sklearn.ensemble.BaggingClassifier or sklearn.ensemble.AdaBoostClassifier. """ self.estimator_search_space = estimator_search_space self.method = method self.space = space self.hyperparameter_parser=hyperparameter_parser self.wrapped_param_name = wrapped_param_name def generate(self, rng=None): rng = np.random.default_rng(rng) return WrapperPipelineIndividual(method=self.method, space=self.space, estimator_search_space=self.estimator_search_space, hyperparameter_parser=self.hyperparameter_parser, wrapped_param_name=self.wrapped_param_name, rng=rng) ================================================ FILE: tpot/search_spaces/tests/test_search_spaces.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ # Test all nodes have all dictionaries import pytest import tpot import tpot from ConfigSpace import ConfigurationSpace from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal from sklearn.neighbors import KNeighborsClassifier from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.preprocessing import StandardScaler def test_EstimatorNodeCrossover(): knn_configspace = {} standard_scaler_configspace = {} knn_node = tpot.search_spaces.nodes.EstimatorNode( method = KNeighborsClassifier, space = knn_configspace, ) knnind1 = knn_node.generate() knnind2 = knn_node.generate() for i in range(0,10): knnind1.mutate() knnind2.mutate() knnind1.crossover(knnind2) def test_ValueError_different_types(): knn_node = tpot.config.get_search_space(["KNeighborsClassifier"]) sfm_wrapper_node = tpot.config.get_search_space(["SelectFromModel_classification"]) for i in range(10): ind1 = knn_node.generate() ind2 = sfm_wrapper_node.generate() assert not ind1.crossover(ind2) assert not ind2.crossover(ind1) if __name__ == "__main__": test_EstimatorNodeCrossover() test_ValueError_different_types() ================================================ FILE: tpot/search_spaces/tuple_index.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np class TupleIndex(): """ TPOT uses tuples to create a unique id for some pipeline search spaces. However, tuples sometimes don't interact correctly with pandas indexes. This class is a wrapper around a tuple that allows it to be used as a key in a dictionary, without it being an itereable. An alternative could be to make unique id return a string, but this would not work with graphpipelines, which require a special object. This class allows linear pipelines to contain graph pipelines while still being able to be used as a key in a dictionary. """ def __init__(self, tup): self.tup = tup def __eq__(self,other) -> bool: return self.tup == other def __hash__(self) -> int: return self.tup.__hash__() def __str__(self) -> str: return self.tup.__str__() def __repr__(self) -> str: return self.tup.__repr__() ================================================ FILE: tpot/selectors/__init__.py ================================================ from .lexicase_selection import lexicase_selection from .max_weighted_average_selector import max_weighted_average_selector from .random_selector import random_selector from .tournament_selection import tournament_selection from .tournament_selection_dominated import tournament_selection_dominated from .nsgaii import nondominated_sorting, crowding_distance, dominates, survival_select_NSGA2 from .map_elites_selection import map_elites_survival_selector, map_elites_parent_selector SELECTORS = {"lexicase":lexicase_selection, "max_weighted_average":max_weighted_average_selector, "random":random_selector, "tournament":tournament_selection, "tournament_dominated":tournament_selection_dominated, "nsgaii":survival_select_NSGA2, "map_elites_survival":map_elites_survival_selector, "map_elites_parent":map_elites_parent_selector, } ================================================ FILE: tpot/selectors/lexicase_selection.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np def lexicase_selection(scores, k, n_parents=1, rng=None): """ Select the best individual according to Lexicase Selection, *k* times. The returned list contains the indices of the chosen *individuals*. Parameters ---------- scores : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. k : int The number of individuals to select. n_parents : int, optional The number of parents to select per individual. The default is 1. rng : int, np.random.Generator, optional The random number generator. The default is None. Returns ------- A array of indices of selected individuals of shape (k, n_parents). """ rng = np.random.default_rng(rng) chosen =[] for i in range(k*n_parents): candidates = list(range(len(scores))) cases = list(range(len(scores[0]))) rng.shuffle(cases) while len(cases) > 0 and len(candidates) > 1: best_val_for_case = max(scores[candidates,cases[0]]) candidates = [x for x in candidates if scores[x, cases[0]] == best_val_for_case] cases.pop(0) chosen.append(rng.choice(candidates)) return np.reshape(chosen, (k, n_parents)) ================================================ FILE: tpot/selectors/map_elites_selection.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np #TODO make these functions take in a predetermined set of bins rather than calculating a new set each time def create_nd_matrix(matrix, grid_steps=None, bins=None): """ Create an n-dimensional matrix with the highest score for each cell Parameters ---------- matrix : np.ndarray The score matrix, where the first column is the score and the rest are the features for the map-elites algorithm. grid_steps : int, optional The number of steps to use for each feature to automatically create the bin thresholds. The default is None. bins : list, optional A list of lists containing the bin edges for each feature (other than the score). The default is None. Returns ------- np.ndarray An n-dimensional matrix with the highest score for each cell and the index of the individual with that score. The value in the cell is a dictionary with the keys "score" and "idx" containing the score and index of the individual respectively. """ if grid_steps is not None and bins is not None: raise ValueError("Either grid_steps or bins must be provided but not both") # Extract scores and features scores = matrix[:, 0] features = matrix[:, 1:] # Determine the min and max of each feature min_vals = np.min(features, axis=0) max_vals = np.max(features, axis=0) # Create bins for each feature if bins is None: bins = [np.linspace(min_vals[i], max_vals[i], grid_steps) for i in range(len(min_vals))] # Initialize n-dimensional matrix with negative infinity nd_matrix = np.full([len(b)+1 for b in bins], {"score": -np.inf, "idx": None}) # Fill in each cell with the highest score for that cell for idx, (score, feature) in enumerate(zip(scores, features)): indices = [np.digitize(f, bin) for f, bin in zip(feature, bins)] cur_score = nd_matrix[tuple(indices)]["score"] if score > cur_score: nd_matrix[tuple(indices)] = {"score": score, "idx": idx} return nd_matrix def manhattan(a, b): """ Calculate the Manhattan distance between two points. Parameters ---------- a : np.ndarray The first point. b : np.ndarray The second point. Returns ------- float The Manhattan distance between the two points. """ return sum(abs(val1-val2) for val1, val2 in zip(a,b)) def map_elites_survival_selector(scores, k=None, rng=None, grid_steps= 10, bins=None): """ Takes a matrix of scores and returns the indexes of the individuals that are in the best cells of the map-elites grid. Can either take a grid_steps parameter to automatically create the bins or a bins parameter to specify the bins manually. Parameters ---------- scores : np.ndarray The score matrix, where the first column is the score and the rest are the features for the map-elites algorithm. k : int, optional The number of individuals to select. The default is None. rng : int, np.random.Generator, optional The random number generator. The default is None. grid_steps : int, optional The number of steps to use for each feature to automatically create the bin thresholds. The default is None. bins : list, optional A list of lists containing the bin edges for each feature (other than the score). The default is None. Returns ------- np.ndarray An array of indexes of the individuals in the best cells of the map-elites grid (without repeats). """ if grid_steps is not None and bins is not None: raise ValueError("Either grid_steps or bins must be provided but not both") rng = np.random.default_rng(rng) scores = np.array(scores) #create grid matrix = create_nd_matrix(scores, grid_steps=grid_steps, bins=bins) matrix = matrix.flatten() indexes = [cell["idx"] for cell in matrix if cell["idx"] is not None] return np.unique(indexes) def map_elites_parent_selector(scores, k, n_parents=1, rng=None, manhattan_distance = 2, grid_steps= 10, bins=None): """ A parent selection algorithm for the map-elites algorithm. First creates a grid of the best individuals per cell and then selects parents based on the Manhattan distance between the cells of the best individuals. Parameters ---------- scores : np.ndarray The score matrix, where the first column is the score and the rest are the features for the map-elites algorithm. k : int The number of individuals to select. n_parents : int, optional The number of parents to select per individual. The default is 1. rng : int, np.random.Generator, optional The random number generator. The default is None. manhattan_distance : int, optional The maximum Manhattan distance between parents. The default is 2. If no parents are found within this distance, the distance is increased by 1 until at least one other parent is found. grid_steps : int, optional The number of steps to use for each feature to automatically create the bin thresholds. The default is None. bins : list, optional A list of lists containing the bin edges for each feature (other than the score). The default is None. Returns ------- np.ndarray An array of indexes of the parents selected for each individual """ if grid_steps is not None and bins is not None: raise ValueError("Either grid_steps or bins must be provided but not both") rng = np.random.default_rng(rng) scores = np.array(scores) #create grid matrix = create_nd_matrix(scores, grid_steps=grid_steps, bins=bins) #return true if cell is not empty f = np.vectorize(lambda x: x["idx"] is not None) valid_coordinates = np.array(np.where(f(matrix))).T idx_to_coordinates = {matrix[tuple(coordinates)]["idx"]: coordinates for coordinates in valid_coordinates} idxes = [idx for idx in idx_to_coordinates.keys()] #all the indexes of best score per cell distance_matrix = np.zeros((len(idxes), len(idxes))) for i, idx1 in enumerate(idxes): for j, idx2 in enumerate(idxes): distance_matrix[i][j] = manhattan(idx_to_coordinates[idx1], idx_to_coordinates[idx2]) parents = [] for i in range(k): #randomly select a cell idx = rng.choice(idxes) #select random parent #get the distance from this parent to all other parents dm_idx = idxes.index(idx) row = distance_matrix[dm_idx] #get all second parents that are within manhattan distance. if none are found increase the distance candidates = [] while len(candidates) == 0: candidates = np.where(row <= manhattan_distance)[0] #remove self from candidates candidates = candidates[candidates != dm_idx] manhattan_distance += 1 if manhattan_distance > np.max(distance_matrix): break if len(candidates) == 0: parents.append([idx, idx]) #if no other parents are found, select the same parent twice. weird to crossover with itself though else: this_parents = [idx] for p in range(n_parents-1): idx2_cords = rng.choice(candidates) this_parents.append(idxes[idx2_cords]) parents.append(this_parents) return np.array(parents) def get_bins_quantiles(arr, k=None, q=None): """ Takes a matrix and returns the bin thresholds based on quantiles. Parameters ---------- arr : np.ndarray The matrix to calculate the bins for. k : int, optional The number of bins to create. This parameter creates k equally spaced quantiles. For example, k=3 will create quantiles at array([0.25, 0.5 , 0.75]). q : np.ndarray, optional Custom quantiles to use for the bins. This parameter creates bins based on the quantiles of the data. The default is None. """ bins = [] if q is not None and k is not None: raise ValueError("Only one of k or q can be specified") if q is not None: final_q = q elif k is not None: final_q = np.linspace(0, 1, k+2)[1:-1] for i in range(arr.shape[1]): bins.append(np.quantile(arr[:,i], final_q)) return bins def get_bins(arr, k): """ Get equally spaced bin thresholds between the min and max values for the array of scores. Parameters ---------- arr : np.ndarray The list of values to calculate the bins for. k : int The number of bins to create. Returns ------- list A list of bin thresholds calculated to be k equally spaced bins between the min and max of the array. """ min_vals = np.min(arr, axis=0) max_vals = np.max(arr, axis=0) [np.linspace(min_vals[i], max_vals[i], k) for i in range(len(min_vals))] ================================================ FILE: tpot/selectors/max_weighted_average_selector.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np def max_weighted_average_selector(scores,k, n_parents=1, rng=None): """ Select the best individual according to Max Weighted Average Selection, *k* times. Parameters ---------- scores : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. k : int The number of individuals to select. n_parents : int, optional The number of parents to select per individual. The default is 1. rng : int, np.random.Generator, optional The random number generator. The default is None. Returns ------- A array of indices of selected individuals of shape (k, n_parents). """ ave_scores = [np.nanmean(s ) for s in scores ] #TODO make this more efficient chosen = np.argsort(ave_scores)[::-1][0:k] #TODO check this behavior with nans return np.reshape(chosen, (k, n_parents)) ================================================ FILE: tpot/selectors/nsgaii.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np # Deb, Pratab, Agarwal, and Meyarivan, “A fast elitist non-dominated sorting genetic algorithm for multi-objective optimization: NSGA-II”, 2002. # chatgpt def nondominated_sorting(matrix): """ Returns the indices of the non-dominated rows in the scores matrix. Rows are considered samples, and columns are considered objectives. Parameters ---------- matrix : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. Returns ------- list A list of lists of indices of the non-dominated rows in the scores matrix. """ # Initialize the front list and the rank list # Initialize the current front fronts = {0:set()} # Initialize the list of dominated points dominated = [set() for _ in range(len(matrix))] #si the set of solutions which solution i dominates # Initialize the list of points that dominate the current point dominating = [0 for _ in range(len(matrix))] #ni the number of solutions that denominate solution i # Iterate over all points for p, p_scores in enumerate(matrix): # Iterate over all other points for q, q_scores in enumerate(matrix): # If the current point dominates the other point, increment the count of points dominated by the current point if dominates(p_scores, q_scores): dominated[p].add(q) # If the current point is dominated by the other point, add it to the list of dominated points elif dominates(q_scores, p_scores): dominating[p] += 1 if dominating[p] == 0: fronts[0].add(p) i=0 # Iterate until all points have been added to a front while len(fronts[i]) > 0: H = set() for p in fronts[i]: for q in dominated[p]: dominating[q] -= 1 if dominating[q] == 0: H.add(q) i += 1 fronts[i] = H return [fronts[j] for j in range(i)] def dominates(list1, list2): """ returns true is all values in list1 are not strictly worse than list2 AND at least one item in list1 is better than list2 Parameters ---------- list1 : list The first list of values to compare. list2 : list The second list of values to compare. Returns ------- bool True if all values in list1 are not strictly worse than list2 AND at least one item in list1 is better than list2, False otherwise. """ return all(list1[i] >= list2[i] for i in range(len(list1))) and any(list1[i] > list2[i] for i in range(len(list1))) #adapted from deap + gtp #bigger is better def crowding_distance(matrix): """ Takes a matrix of scores and returns the crowding distance for each point. Parameters ---------- matrix : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. Returns ------- list A list of the crowding distances for each point in the score matrix. """ matrix = np.array(matrix) # Initialize the crowding distance for each point to zero crowding_distances = [0 for _ in range(len(matrix))] # Iterate over each objective for objective_i in range(matrix.shape[1]): # Sort the points according to the current objective sorted_i = matrix[:, objective_i].argsort() # Set the crowding distance of the first and last points to infinity crowding_distances[sorted_i[0]] = float("inf") crowding_distances[sorted_i[-1]] = float("inf") if matrix[sorted_i[0]][objective_i] == matrix[sorted_i[-1]][objective_i]: # https://github.com/DEAP/deap/blob/f2a570567fa3dce156d7cfb0c50bc72f133258a1/deap/tools/emo.py#L135 continue norm = matrix.shape[1] * float(matrix[sorted_i[0]][objective_i] - matrix[sorted_i[-1]][objective_i]) for prev, cur, following in zip(sorted_i[:-2], sorted_i[1:-1], sorted_i[2:]): crowding_distances[cur] += (matrix[following][objective_i] - matrix[prev][objective_i]) / norm return crowding_distances def survival_select_NSGA2(scores, k, rng=None): """ Select the top k individuals from the scores matrix using the NSGA-II algorithm. Parameters ---------- scores : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. k : int The number of individuals to select. rng : int, np.random.Generator, optional The random number generator. The default is None. Returns ------- list A list of indices of the selected individuals (without repeats). """ pareto_fronts = nondominated_sorting(scores) # chosen = list(itertools.chain.from_iterable(fronts)) # if len(chosen) >= k: # return chosen[0:k] chosen = [] current_front_number = 0 while len(chosen) < k and current_front_number < len(pareto_fronts): current_front = np.array(list(pareto_fronts[current_front_number])) front_scores = [scores[i] for i in current_front] crowding_distances = crowding_distance(front_scores) sorted_indeces = current_front[np.argsort(crowding_distances)[::-1]] chosen.extend(sorted_indeces[0:(k-len(chosen))]) current_front_number += 1 return chosen ================================================ FILE: tpot/selectors/random_selector.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np def random_selector(scores, k, n_parents=1, rng=None, ): """ Randomly selects indeces of individuals from the scores matrix. Parameters ---------- scores : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. k : int The number of individuals to select. n_parents : int, optional The number of parents to select per individual. The default is 1. rng : int, np.random.Generator, optional The random number generator. The default is None. Returns ------- A array of indices of randomly selected individuals (with replacement) of shape (k, n_parents). """ rng = np.random.default_rng(rng) chosen = rng.choice(list(range(0,len(scores))), size=k*n_parents) return np.reshape(chosen, (k, n_parents)) ================================================ FILE: tpot/selectors/tournament_selection.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np def tournament_selection(scores, k, n_parents=1, rng=None, tournament_size=2, score_index=0): """ Select the best individual among *tournsize* randomly chosen individuals, *k* times. The returned list contains the indices of the chosen *individuals*. Parameters ---------- scores : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. k : int The number of individuals to select. n_parents : int, optional The number of parents to select per individual. The default is 1. rng : int, np.random.Generator, optional The random number generator. The default is None. tournament_size : int, optional The number of individuals participating in each tournament. score_index : int, str, optional The index of the score to use for selection. If "average" is passed, the average score is used. The default is 0 (only the first score is used). Returns ------- A array of indices of selected individuals of shape (k, n_parents). """ rng = np.random.default_rng(rng) if isinstance(score_index,int): key=lambda x:x[1][score_index] elif score_index == "average": key=lambda x:np.mean(x[1]) chosen = [] for i in range(k*n_parents): aspirants_idx =[rng.choice(len(scores)) for i in range(tournament_size)] aspirants = list(zip(aspirants_idx, scores[aspirants_idx])) # Zip indices and elements together chosen.append(max(aspirants, key=key)[0]) # Retrun the index of the maximum element return np.reshape(chosen, (k, n_parents)) ================================================ FILE: tpot/selectors/tournament_selection_dominated.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np from.nsgaii import nondominated_sorting, crowding_distance, dominates #based on deap def tournament_selection_dominated(scores, k, n_parents=2, rng=None): """ Select the best individual among 2 randomly chosen individuals, *k* times. Selection is first attempted by checking if one individual dominates the other. Otherwise one with the highest crowding distance is selected. The returned list contains the indices of the chosen *individuals*. Parameters ---------- scores : np.ndarray The score matrix, where rows the individuals and the columns are the corresponds to scores on different objectives. k : int The number of individuals to select. n_parents : int, optional The number of parents to select per individual. The default is 2. rng : int, np.random.Generator, optional The random number generator. The default is None. Returns ------- A array of indices of selected individuals of shape (k, n_parents). """ rng = np.random.default_rng(rng) pareto_fronts = nondominated_sorting(scores) # chosen = list(itertools.chain.from_iterable(fronts)) # if len(chosen) >= k: # return chosen[0:k] crowding_dict = {} chosen = [] current_front_number = 0 while current_front_number < len(pareto_fronts): current_front = np.array(list(pareto_fronts[current_front_number])) front_scores = [scores[i] for i in current_front] crowding_distances = crowding_distance(front_scores) for i, crowding in zip(current_front,crowding_distances): crowding_dict[i] = crowding current_front_number += 1 chosen = [] for i in range(k*n_parents): asp1 = rng.choice(len(scores)) asp2 = rng.choice(len(scores)) if dominates(scores[asp1], scores[asp2]): chosen.append(asp1) elif dominates(scores[asp2], scores[asp1]): chosen.append(asp2) elif crowding_dict[asp1] > crowding_dict[asp2]: chosen.append(asp1) elif crowding_dict[asp1] < crowding_dict[asp2]: chosen.append(asp2) else: chosen.append(rng.choice([asp1,asp2])) return np.reshape(chosen, (k, n_parents)) ================================================ FILE: tpot/tests/__init__.py ================================================ ================================================ FILE: tpot/tests/conftest.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import pytest import sys @pytest.fixture def capture_stdout(monkeypatch): buffer = {"stdout": "", "write_calls": 0} def fake_write(s): buffer["stdout"] += s buffer["write_calls"] += 1 monkeypatch.setattr(sys.stdout, "write", fake_write) return buffer ================================================ FILE: tpot/tests/test_estimators.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import pytest import tpot from sklearn.datasets import load_iris import random import sklearn @pytest.fixture def sample_dataset(): X_train, y_train = load_iris(return_X_y=True) return X_train, y_train #standard test @pytest.fixture def tpot_estimator(): n_classes=3 n_samples=100 n_features=100 search_space = tpot.search_spaces.pipelines.GraphSearchPipeline( root_search_space= tpot.config.get_search_space("classifiers", n_samples=n_samples, n_features=n_features, n_classes=n_classes), leaf_search_space = None, inner_search_space = tpot.config.get_search_space(["selectors","transformers"],n_samples=n_samples, n_features=n_features, n_classes=n_classes), max_size = 10, ) return tpot.TPOTEstimator( search_space=search_space, population_size=10, generations=2, scorers=['roc_auc_ovr'], scorers_weights=[1], classification=True, n_jobs=4, early_stop=5, other_objective_functions= [], other_objective_functions_weights=[], max_time_mins=20/60, verbose=3) @pytest.fixture def tpot_classifier(): return tpot.tpot_estimator.templates.TPOTClassifier(max_time_mins=60/60,verbose=0) @pytest.fixture def tpot_regressor(): return tpot.tpot_estimator.templates.TPOTRegressor(max_time_mins=60/60,verbose=0) @pytest.fixture def tpot_estimator_with_pipeline(tpot_estimator,sample_dataset): tpot_estimator.fit(sample_dataset[0], sample_dataset[1]) return tpot_estimator def test_tpot_estimator_predict(tpot_estimator_with_pipeline,sample_dataset): #X_test = [[1, 2, 3], [4, 5, 6]] X_test = sample_dataset[0] y_pred = tpot_estimator_with_pipeline.predict(X_test) assert len(y_pred) == len(X_test) assert tpot_estimator_with_pipeline.fitted_pipeline_ is not None def test_tpot_estimator_generations_type(): with pytest.raises(TypeError): tpot.TPOTEstimator(generations="two", population_size=10, verbosity=2) def test_tpot_estimator_population_size_type(): with pytest.raises(TypeError): tpot.TPOTEstimator(generations=2, population_size='ten', verbosity=2) def test_tpot_estimator_verbosity_type(): with pytest.raises(TypeError): tpot.TPOTEstimator(generations=2, population_size=10, verbosity='high') def test_tpot_estimator_scoring_type(): with pytest.raises(TypeError): tpot.TPOTEstimator(generations=2, population_size=10, verbosity=2, scoring=0.5) def test_tpot_estimator_cv_type(): with pytest.raises(TypeError): tpot.TPOTEstimator(generations=2, population_size=10, verbosity=2, cv='kfold') def test_tpot_estimator_n_jobs_type(): with pytest.raises(TypeError): tpot.TPOTEstimator(generations=2, population_size=10, verbosity=2, n_jobs='all') def test_tpot_estimator_config_dict_type(): with pytest.raises(TypeError): tpot.TPOTEstimator(generations=2, population_size=10, verbosity=2, config_dict='config') def test_tpot_classifier_fit(tpot_classifier,sample_dataset): #load iris dataset X_train = sample_dataset[0] y_train = sample_dataset[1] tpot_classifier.fit(X_train, y_train) assert tpot_classifier.fitted_pipeline_ is not None def test_tpot_regressor_fit(tpot_regressor): scorer = sklearn.metrics.get_scorer('neg_mean_squared_error') X, y = sklearn.datasets.load_diabetes(return_X_y=True) X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.05, test_size=0.95) tpot_regressor.fit(X_train, y_train) assert tpot_regressor.fitted_pipeline_ is not None ================================================ FILE: tpot/tests/test_hello_world.py ================================================ """ Test hello world. Notes: parameterizing the test_input and expected values allows tests continue running even if one fails. xfail marks a test as expected to fail. This is useful for tests that are not yet implemented. fixtures are used to setup and teardown tests. They are useful for tests that require a lot of setup. We can implement fixtures if we need them. """ import pytest @pytest.mark.parametrize("test_input,expected", [ ("Hello World", "Hello World"), ]) def test_hello_world(test_input, expected): assert test_input is expected def test_print(capture_stdout): print("Hello World") assert capture_stdout["stdout"] == "Hello World\n" ================================================ FILE: tpot/tpot_estimator/__init__.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from .estimator import TPOTEstimator from .steady_state_estimator import TPOTEstimatorSteadyState from .templates import TPOTClassifier, TPOTRegressor ================================================ FILE: tpot/tpot_estimator/cross_val_utils.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import time import sklearn.metrics from collections.abc import Iterable import pandas as pd import sklearn import numpy as np def cross_val_score_objective(estimator, X, y, scorers, cv, fold=None): """ Compute the cross validated scores for a estimator. Only fits the estimator once per fold, and loops over the scorers to evaluate the estimator. Parameters ---------- estimator: sklearn.base.BaseEstimator The estimator to fit and score. X: np.ndarray or pd.DataFrame The feature matrix. y: np.ndarray or pd.Series The target vector. scorers: list or scorer The scorers to use. If a list, will loop over the scorers and return a list of scorers. If a single scorer, will return a single score. cv: sklearn cross-validator The cross-validator to use. For example, sklearn.model_selection.KFold or sklearn.model_selection.StratifiedKFold. fold: int, optional The fold to return the scores for. If None, will return the mean of all the scores (per scorer). Default is None. Returns ------- scores: np.ndarray or float The scores for the estimator per scorer. If fold is None, will return the mean of all the scores (per scorer). Returns a list if multiple scorers are used, otherwise returns a float for the single scorer. """ #check if scores is not iterable if not isinstance(scorers, Iterable): scorers = [scorers] scores = [] if fold is None: for train_index, test_index in cv.split(X, y): this_fold_estimator = sklearn.base.clone(estimator) if isinstance(X, pd.DataFrame) or isinstance(X, pd.Series): X_train, X_test = X.iloc[train_index], X.iloc[test_index] else: X_train, X_test = X[train_index], X[test_index] if isinstance(y, pd.DataFrame) or isinstance(y, pd.Series): y_train, y_test = y.iloc[train_index], y.iloc[test_index] else: y_train, y_test = y[train_index], y[test_index] start = time.time() this_fold_estimator.fit(X_train,y_train) duration = time.time() - start this_fold_scores = [sklearn.metrics.get_scorer(scorer)(this_fold_estimator, X_test, y_test) for scorer in scorers] scores.append(this_fold_scores) del this_fold_estimator del X_train del X_test del y_train del y_test return np.mean(scores,0) else: this_fold_estimator = sklearn.base.clone(estimator) train_index, test_index = list(cv.split(X, y))[fold] if isinstance(X, pd.DataFrame) or isinstance(X, pd.Series): X_train, X_test = X.iloc[train_index], X.iloc[test_index] else: X_train, X_test = X[train_index], X[test_index] if isinstance(y, pd.DataFrame) or isinstance(y, pd.Series): y_train, y_test = y.iloc[train_index], y.iloc[test_index] else: y_train, y_test = y[train_index], y[test_index] start = time.time() this_fold_estimator.fit(X_train,y_train) duration = time.time() - start this_fold_scores = [sklearn.metrics.get_scorer(scorer)(this_fold_estimator, X_test, y_test) for scorer in scorers] return this_fold_scores ================================================ FILE: tpot/tpot_estimator/estimator.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from sklearn.base import BaseEstimator from sklearn.utils.metaestimators import available_if import numpy as np import sklearn.metrics import tpot.config from sklearn.utils.validation import check_is_fitted from tpot.selectors import survival_select_NSGA2, tournament_selection_dominated from sklearn.preprocessing import LabelEncoder import pandas as pd from sklearn.model_selection import train_test_split import tpot from dask.distributed import Client from dask.distributed import LocalCluster from sklearn.preprocessing import LabelEncoder import warnings import math from .estimator_utils import * from dask import config as cfg from sklearn.experimental import enable_iterative_imputer from ..config.template_search_spaces import get_template_search_spaces import warnings from sklearn.utils._tags import get_tags import copy def set_dask_settings(): cfg.set({'distributed.scheduler.worker-ttl': None}) cfg.set({'distributed.scheduler.allowed-failures':1}) #TODO inherit from _BaseComposition? class TPOTEstimator(BaseEstimator): def __init__(self, search_space, scorers, scorers_weights, classification, cv = 10, other_objective_functions=[], other_objective_functions_weights = [], objective_function_names = None, bigger_is_better = True, export_graphpipeline = False, memory = None, categorical_features = None, preprocessing = False, population_size = 50, initial_population_size = None, population_scaling = .5, generations_until_end_population = 1, generations = None, max_time_mins=60, max_eval_time_mins=10, validation_strategy = "none", validation_fraction = .2, disable_label_encoder = False, #early stopping parameters early_stop = None, scorers_early_stop_tol = 0.001, other_objectives_early_stop_tol =None, threshold_evaluation_pruning = None, threshold_evaluation_scaling = .5, selection_evaluation_pruning = None, selection_evaluation_scaling = .5, min_history_threshold = 20, #evolver parameters survival_percentage = 1, crossover_probability=.2, mutate_probability=.7, mutate_then_crossover_probability=.05, crossover_then_mutate_probability=.05, survival_selector = survival_select_NSGA2, parent_selector = tournament_selection_dominated, #budget parameters budget_range = None, budget_scaling = .5, generations_until_end_budget = 1, stepwise_steps = 5, #dask parameters n_jobs=1, memory_limit = None, client = None, processes = True, #debugging and logging parameters warm_start = False, periodic_checkpoint_folder = None, callback = None, verbose = 0, scatter = True, # random seed for random number generator (rng) random_state = None, ): ''' An sklearn baseestimator that uses genetic programming to optimize a pipeline. Parameters ---------- search_space : (String, tpot.search_spaces.SearchSpace) - String : The default search space to use for the optimization. | String | Description | | :--- | :----: | | linear | A linear pipeline with the structure of "Selector->(transformers+Passthrough)->(classifiers/regressors+Passthrough)->final classifier/regressor." For both the transformer and inner estimator layers, TPOT may choose one or more transformers/classifiers, or it may choose none. The inner classifier/regressor layer is optional. | | linear-light | Same search space as linear, but without the inner classifier/regressor layer and with a reduced set of faster running estimators. | | graph | TPOT will optimize a pipeline in the shape of a directed acyclic graph. The nodes of the graph can include selectors, scalers, transformers, or classifiers/regressors (inner classifiers/regressors can optionally be not included). This will return a custom GraphPipeline rather than an sklearn Pipeline. More details in Tutorial 6. | | graph-light | Same as graph search space, but without the inner classifier/regressors and with a reduced set of faster running estimators. | | mdr |TPOT will search over a series of feature selectors and Multifactor Dimensionality Reduction models to find a series of operators that maximize prediction accuracy. The TPOT MDR configuration is specialized for genome-wide association studies (GWAS), and is described in detail online here. Note that TPOT MDR may be slow to run because the feature selection routines are computationally expensive, especially on large datasets. | - SearchSpace : The search space to use for the optimization. This should be an instance of a SearchSpace. The search space to use for the optimization. This should be an instance of a SearchSpace. TPOT has groups of search spaces found in the following folders, tpot.search_spaces.nodes for the nodes in the pipeline and tpot.search_spaces.pipelines for the pipeline structure. scorers : (list, scorer) A scorer or list of scorers to be used in the cross-validation process. see https://scikit-learn.org/stable/modules/model_evaluation.html scorers_weights : list A list of weights to be applied to the scorers during the optimization process. classification : bool If True, the problem is treated as a classification problem. If False, the problem is treated as a regression problem. Used to determine the CV strategy. cv : int, cross-validator - (int): Number of folds to use in the cross-validation process. By uses the sklearn.model_selection.KFold cross-validator for regression and StratifiedKFold for classification. In both cases, shuffled is set to True. - (sklearn.model_selection.BaseCrossValidator): A cross-validator to use in the cross-validation process. - max_depth (int): The maximum depth from any node to the root of the pipelines to be generated. other_objective_functions : list, default=[] A list of other objective functions to apply to the pipeline. The function takes a single parameter for the graphpipeline estimator and returns either a single score or a list of scores. other_objective_functions_weights : list, default=[] A list of weights to be applied to the other objective functions. objective_function_names : list, default=None A list of names to be applied to the objective functions. If None, will use the names of the objective functions. bigger_is_better : bool, default=True If True, the objective function is maximized. If False, the objective function is minimized. Use negative weights to reverse the direction. memory: Memory object or string, default=None If supplied, pipeline will cache each transformer after calling fit with joblib.Memory. This feature is used to avoid computing the fit transformers within a pipeline if the parameters and input data are identical with another fitted pipeline during optimization process. - String 'auto': TPOT uses memory caching with a temporary directory and cleans it up upon shutdown. - String path of a caching directory TPOT uses memory caching with the provided directory and TPOT does NOT clean the caching directory up upon shutdown. If the directory does not exist, TPOT will create it. - Memory object: TPOT uses the instance of joblib.Memory for memory caching, and TPOT does NOT clean the caching directory up upon shutdown. - None: TPOT does not use memory caching. categorical_features: list or None Categorical columns to inpute and/or one hot encode during the preprocessing step. Used only if preprocessing is not False. - None : If None, TPOT will automatically use object columns in pandas dataframes as objects for one hot encoding in preprocessing. - List of categorical features. If X is a dataframe, this should be a list of column names. If X is a numpy array, this should be a list of column indices preprocessing : bool or BaseEstimator/Pipeline, EXPERIMENTAL - will be changed in future versions A pipeline that will be used to preprocess the data before CV. Note that the parameters for these steps are not optimized. Add them to the search space to be optimized. - bool : If True, will use a default preprocessing pipeline which includes imputation followed by one hot encoding. - Pipeline : If an instance of a pipeline is given, will use that pipeline as the preprocessing pipeline. population_size : int, default=50 Size of the population initial_population_size : int, default=None Size of the initial population. If None, population_size will be used. population_scaling : int, default=0.5 Scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. generations_until_end_population : int, default=1 Number of generations until the population size reaches population_size generations : int, default=None Number of generations to run max_time_mins : float, default=60 Maximum time to run the optimization. If none or inf, will run until the end of the generations. max_eval_time_mins : float, default=10 Maximum time to evaluate a single individual. If none or inf, there will be no time limit per evaluation. validation_strategy : str, default='none' EXPERIMENTAL The validation strategy to use for selecting the final pipeline from the population. TPOT may overfit the cross validation score. A second validation set can be used to select the final pipeline. - 'auto' : Automatically determine the validation strategy based on the dataset shape. - 'reshuffled' : Use the same data for cross validation and final validation, but with different splits for the folds. This is the default for small datasets. - 'split' : Use a separate validation set for final validation. Data will be split according to validation_fraction. This is the default for medium datasets. - 'none' : Do not use a separate validation set for final validation. Select based on the original cross-validation score. This is the default for large datasets. validation_fraction : float, default=0.2 EXPERIMENTAL The fraction of the dataset to use for the validation set when validation_strategy is 'split'. Must be between 0 and 1. disable_label_encoder : bool, default=False If True, TPOT will check if the target needs to be relabeled to be sequential ints from 0 to N. This is necessary for XGBoost compatibility. If the labels need to be encoded, TPOT will use sklearn.preprocessing.LabelEncoder to encode the labels. The encoder can be accessed via the self.label_encoder_ attribute. If False, no additional label encoders will be used. early_stop : int, default=None Number of generations without improvement before early stopping. All objectives must have converged within the tolerance for this to be triggered. In general a value of around 5-20 is good. scorers_early_stop_tol : -list of floats list of tolerances for each scorer. If the difference between the best score and the current score is less than the tolerance, the individual is considered to have converged If an index of the list is None, that item will not be used for early stopping -int If an int is given, it will be used as the tolerance for all objectives other_objectives_early_stop_tol : -list of floats list of tolerances for each of the other objective function. If the difference between the best score and the current score is less than the tolerance, the individual is considered to have converged If an index of the list is None, that item will not be used for early stopping -int If an int is given, it will be used as the tolerance for all objectives threshold_evaluation_pruning : list [start, end], default=None starting and ending percentile to use as a threshold for the evaluation early stopping. Values between 0 and 100. threshold_evaluation_scaling : float [0,inf), default=0.5 A scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. Must be greater than zero. Higher numbers will move the threshold to the end faster. selection_evaluation_pruning : list, default=None A lower and upper percent of the population size to select each round of CV. Values between 0 and 1. selection_evaluation_scaling : float, default=0.5 A scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. Must be greater than zero. Higher numbers will move the threshold to the end faster. min_history_threshold : int, default=0 The minimum number of previous scores needed before using threshold early stopping. survival_percentage : float, default=1 Percentage of the population size to utilize for mutation and crossover at the beginning of the generation. The rest are discarded. Individuals are selected with the selector passed into survival_selector. The value of this parameter must be between 0 and 1, inclusive. For example, if the population size is 100 and the survival percentage is .5, 50 individuals will be selected with NSGA2 from the existing population. These will be used for mutation and crossover to generate the next 100 individuals for the next generation. The remainder are discarded from the live population. In the next generation, there will now be the 50 parents + the 100 individuals for a total of 150. Surivival percentage is based of the population size parameter and not the existing population size (current population size when using successive halving). Therefore, in the next generation we will still select 50 individuals from the currently existing 150. crossover_probability : float, default=.2 Probability of generating a new individual by crossover between two individuals. mutate_probability : float, default=.7 Probability of generating a new individual by crossover between one individuals. mutate_then_crossover_probability : float, default=.05 Probability of generating a new individual by mutating two individuals followed by crossover. crossover_then_mutate_probability : float, default=.05 Probability of generating a new individual by crossover between two individuals followed by a mutation of the resulting individual. survival_selector : function, default=survival_select_NSGA2 Function to use to select individuals for survival. Must take a matrix of scores and return selected indexes. Used to selected population_size * survival_percentage individuals at the start of each generation to use for mutation and crossover. parent_selector : function, default=parent_select_NSGA2 Function to use to select pairs parents for crossover and individuals for mutation. Must take a matrix of scores and return selected indexes. budget_range : list [start, end], default=None A starting and ending budget to use for the budget scaling. budget_scaling float : [0,1], default=0.5 A scaling factor to use when determining how fast we move the budget from the start to end budget. generations_until_end_budget : int, default=1 The number of generations to run before reaching the max budget. stepwise_steps : int, default=1 The number of staircase steps to take when scaling the budget and population size. n_jobs : int, default=1 Number of processes to run in parallel. memory_limit : str, default=None Memory limit for each job. See Dask [LocalCluster documentation](https://distributed.dask.org/en/stable/api.html#distributed.Client) for more information. client : dask.distributed.Client, default=None A dask client to use for parallelization. If not None, this will override the n_jobs and memory_limit parameters. If None, will create a new client with num_workers=n_jobs and memory_limit=memory_limit. processes : bool, default=True If True, will use multiprocessing to parallelize the optimization process. If False, will use threading. True seems to perform better. However, False is required for interactive debugging. warm_start : bool, default=False If True, will use the continue the evolutionary algorithm from the last generation of the previous run. periodic_checkpoint_folder : str, default=None Folder to save the population to periodically. If None, no periodic saving will be done. If provided, training will resume from this checkpoint. callback : tpot.CallBackInterface, default=None Callback object. Not implemented verbose : int, default=1 How much information to print during the optimization process. Higher values include the information from lower values. 0. nothing 1. progress bar 3. best individual 4. warnings >=5. full warnings trace 6. evaluations progress bar. (Temporary: This used to be 2. Currently, using evaluation progress bar may prevent some instances were we terminate a generation early due to it reaching max_time_mins in the middle of a generation OR a pipeline failed to be terminated normally and we need to manually terminate it.) scatter : bool, default=True If True, will scatter the data to the dask workers. If False, will not scatter the data. This can be useful for debugging. random_state : int, None, default=None A seed for reproducability of experiments. This value will be passed to numpy.random.default_rng() to create an instnce of the genrator to pass to other classes - int Will be used to create and lock in Generator instance with 'numpy.random.default_rng()' - None Will be used to create Generator for 'numpy.random.default_rng()' where a fresh, unpredictable entropy will be pulled from the OS Attributes ---------- fitted_pipeline_ : GraphPipeline A fitted instance of the GraphPipeline that inherits from sklearn BaseEstimator. This is fitted on the full X, y passed to fit. evaluated_individuals : A pandas data frame containing data for all evaluated individuals in the run. Columns: - *objective functions : The first few columns correspond to the passed in scorers and objective functions - Parents : A tuple containing the indexes of the pipelines used to generate the pipeline of that row. If NaN, this pipeline was generated randomly in the initial population. - Variation_Function : Which variation function was used to mutate or crossover the parents. If NaN, this pipeline was generated randomly in the initial population. - Individual : The internal representation of the individual that is used during the evolutionary algorithm. This is not an sklearn BaseEstimator. - Generation : The generation the pipeline first appeared. - Pareto_Front : The nondominated front that this pipeline belongs to. 0 means that its scores is not strictly dominated by any other individual. To save on computational time, the best frontier is updated iteratively each generation. The pipelines with the 0th pareto front do represent the exact best frontier. However, the pipelines with pareto front >= 1 are only in reference to the other pipelines in the final population. All other pipelines are set to NaN. - Instance : The unfitted GraphPipeline BaseEstimator. - *validation objective functions : Objective function scores evaluated on the validation set. - Validation_Pareto_Front : The full pareto front calculated on the validation set. This is calculated for all pipelines with Pareto_Front equal to 0. Unlike the Pareto_Front which only calculates the frontier and the final population, the Validation Pareto Front is calculated for all pipelines tested on the validation set. pareto_front : The same pandas dataframe as evaluated individuals, but containing only the frontier pareto front pipelines. ''' # sklearn BaseEstimator must have a corresponding attribute for each parameter. # These should not be modified once set. self.scorers = scorers self.scorers_weights = scorers_weights self.classification = classification self.cv = cv self.other_objective_functions = other_objective_functions self.other_objective_functions_weights = other_objective_functions_weights self.objective_function_names = objective_function_names self.bigger_is_better = bigger_is_better self.search_space = search_space self.export_graphpipeline = export_graphpipeline self.memory = memory self.categorical_features = categorical_features self.preprocessing = preprocessing self.validation_strategy = validation_strategy self.validation_fraction = validation_fraction self.disable_label_encoder = disable_label_encoder self.population_size = population_size self.initial_population_size = initial_population_size self.population_scaling = population_scaling self.generations_until_end_population = generations_until_end_population self.generations = generations self.early_stop = early_stop self.scorers_early_stop_tol = scorers_early_stop_tol self.other_objectives_early_stop_tol = other_objectives_early_stop_tol self.max_time_mins = max_time_mins self.max_eval_time_mins = max_eval_time_mins self.n_jobs= n_jobs self.memory_limit = memory_limit self.client = client self.survival_percentage = survival_percentage self.crossover_probability = crossover_probability self.mutate_probability = mutate_probability self.mutate_then_crossover_probability= mutate_then_crossover_probability self.crossover_then_mutate_probability= crossover_then_mutate_probability self.survival_selector=survival_selector self.parent_selector=parent_selector self.budget_range = budget_range self.budget_scaling = budget_scaling self.generations_until_end_budget = generations_until_end_budget self.stepwise_steps = stepwise_steps self.threshold_evaluation_pruning =threshold_evaluation_pruning self.threshold_evaluation_scaling = threshold_evaluation_scaling self.min_history_threshold = min_history_threshold self.selection_evaluation_pruning = selection_evaluation_pruning self.selection_evaluation_scaling = selection_evaluation_scaling self.warm_start = warm_start self.verbose = verbose self.periodic_checkpoint_folder = periodic_checkpoint_folder self.callback = callback self.processes = processes self.scatter = scatter timer_set = self.max_time_mins != float("inf") and self.max_time_mins is not None if self.generations is not None and timer_set: warnings.warn("Both generations and max_time_mins are set. TPOT will terminate when the first condition is met.") # create random number generator based on rngseed self.rng = np.random.default_rng(random_state) # save random state passed to us for other functions that use random_state self.random_state = random_state #Initialize other used params if self.initial_population_size is None: self._initial_population_size = self.population_size else: self._initial_population_size = self.initial_population_size if isinstance(self.scorers, str): self._scorers = [self.scorers] elif callable(self.scorers): self._scorers = [self.scorers] else: self._scorers = self.scorers self._scorers = [sklearn.metrics.get_scorer(scoring) for scoring in self._scorers] self._scorers_early_stop_tol = self.scorers_early_stop_tol self._evolver = tpot.evolvers.BaseEvolver self.objective_function_weights = [*scorers_weights, *other_objective_functions_weights] if self.objective_function_names is None: obj_names = [f.__name__ for f in other_objective_functions] else: obj_names = self.objective_function_names self.objective_names = [f._score_func.__name__ if hasattr(f,"_score_func") else f.__name__ for f in self._scorers] + obj_names if not isinstance(self.other_objectives_early_stop_tol, list): self._other_objectives_early_stop_tol = [self.other_objectives_early_stop_tol for _ in range(len(self.other_objective_functions))] else: self._other_objectives_early_stop_tol = self.other_objectives_early_stop_tol if not isinstance(self._scorers_early_stop_tol, list): self._scorers_early_stop_tol = [self._scorers_early_stop_tol for _ in range(len(self._scorers))] else: self._scorers_early_stop_tol = self._scorers_early_stop_tol self.early_stop_tol = [*self._scorers_early_stop_tol, *self._other_objectives_early_stop_tol] self._evolver_instance = None self.evaluated_individuals = None self.label_encoder_ = None set_dask_settings() def fit(self, X, y): if self.client is not None: #If user passed in a client manually _client = self.client else: if self.verbose >= 4: silence_logs = 30 elif self.verbose >=5: silence_logs = 40 else: silence_logs = 50 cluster = LocalCluster(n_workers=self.n_jobs, #if no client is passed in and no global client exists, create our own threads_per_worker=1, processes=self.processes, silence_logs=silence_logs, memory_limit=self.memory_limit) _client = Client(cluster) if self.classification and not self.disable_label_encoder and not check_if_y_is_encoded(y): warnings.warn("Labels are not encoded as ints from 0 to N. For compatibility with some classifiers such as sklearn, TPOT has encoded y with the sklearn LabelEncoder. When using pipelines outside the main TPOT estimator class, you can encode the labels with est.label_encoder_") self.label_encoder_ = LabelEncoder() y = self.label_encoder_.fit_transform(y) self.evaluated_individuals = None #determine validation strategy if self.validation_strategy == 'auto': nrows = X.shape[0] ncols = X.shape[1] if nrows/ncols < 20: validation_strategy = 'reshuffled' elif nrows/ncols < 100: validation_strategy = 'split' else: validation_strategy = 'none' else: validation_strategy = self.validation_strategy if validation_strategy == 'split': if self.classification: X, X_val, y, y_val = train_test_split(X, y, test_size=self.validation_fraction, stratify=y, random_state=self.random_state) else: X, X_val, y, y_val = train_test_split(X, y, test_size=self.validation_fraction, random_state=self.random_state) X_original = X y_original = y if isinstance(self.cv, int) or isinstance(self.cv, float): n_folds = self.cv else: n_folds = self.cv.get_n_splits(X, y) if self.classification: X, y = remove_underrepresented_classes(X, y, n_folds) if self.preprocessing: #X = pd.DataFrame(X) if not isinstance(self.preprocessing, bool) and isinstance(self.preprocessing, sklearn.base.BaseEstimator): self._preprocessing_pipeline = sklearn.base.clone(self.preprocessing) #TODO: check if there are missing values in X before imputation. If not, don't include imputation in pipeline. Check if there are categorical columns. If not, don't include one hot encoding in pipeline else: #if self.preprocessing is True or not a sklearn estimator pipeline_steps = [] if self.categorical_features is not None: #if categorical features are specified, use those pipeline_steps.append(("impute_categorical", tpot.builtin_modules.ColumnSimpleImputer(self.categorical_features, strategy='most_frequent'))) pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("numeric", strategy='mean'))) pipeline_steps.append(("ColumnOneHotEncoder", tpot.builtin_modules.ColumnOneHotEncoder(self.categorical_features, min_frequency=0.0001))) # retain wrong param fix else: if isinstance(X, pd.DataFrame): categorical_columns = X.select_dtypes(include=['object']).columns if len(categorical_columns) > 0: pipeline_steps.append(("impute_categorical", tpot.builtin_modules.ColumnSimpleImputer("categorical", strategy='most_frequent'))) pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("numeric", strategy='mean'))) pipeline_steps.append(("ColumnOneHotEncoder", tpot.builtin_modules.ColumnOneHotEncoder("categorical", min_frequency=0.0001))) # retain wrong param fix else: pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("all", strategy='mean'))) else: pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("all", strategy='mean'))) self._preprocessing_pipeline = sklearn.pipeline.Pipeline(pipeline_steps) X = self._preprocessing_pipeline.fit_transform(X, y) else: self._preprocessing_pipeline = None #_, y = sklearn.utils.check_X_y(X, y, y_numeric=True) #Set up the configuation dictionaries and the search spaces #check if self.cv is a number if isinstance(self.cv, int) or isinstance(self.cv, float): if self.classification: self.cv_gen = sklearn.model_selection.StratifiedKFold(n_splits=self.cv, shuffle=True, random_state=self.random_state) else: self.cv_gen = sklearn.model_selection.KFold(n_splits=self.cv, shuffle=True, random_state=self.random_state) else: self.cv_gen = sklearn.model_selection.check_cv(self.cv, y, classifier=self.classification) n_samples= int(math.floor(X.shape[0]/n_folds)) n_features=X.shape[1] if isinstance(X, pd.DataFrame): self.feature_names = X.columns else: self.feature_names = None def objective_function(pipeline_individual, X, y, is_classification=self.classification, scorers= self._scorers, cv=self.cv_gen, other_objective_functions=self.other_objective_functions, export_graphpipeline=self.export_graphpipeline, memory=self.memory, **kwargs): return objective_function_generator( pipeline_individual, X, y, is_classification=is_classification, scorers= scorers, cv=cv, other_objective_functions=other_objective_functions, export_graphpipeline=export_graphpipeline, memory=memory, **kwargs, ) if self.threshold_evaluation_pruning is not None or self.selection_evaluation_pruning is not None: evaluation_early_stop_steps = self.cv else: evaluation_early_stop_steps = None if self.scatter: X_future = _client.scatter(X) y_future = _client.scatter(y) else: X_future = X y_future = y if self.classification: n_classes = len(np.unique(y)) else: n_classes = None get_search_space_params = {"n_classes": n_classes, "n_samples":len(y), "n_features":X.shape[1], "random_state":self.random_state} self._search_space = get_template_search_spaces(self.search_space, classification=self.classification, inner_predictors=True, **get_search_space_params) # TODO : Add check for empty values in X and if so, add imputation to the search space # make this depend on self.preprocessing # if check_empty_values(X): # from sklearn.experimental import enable_iterative_imputer # from ConfigSpace import ConfigurationSpace # from ConfigSpace import ConfigurationSpace, Integer, Float, Categorical, Normal # iterative_imputer_cs = ConfigurationSpace( # space = { # 'n_nearest_features' : Categorical('n_nearest_features', [100]), # 'initial_strategy' : Categorical('initial_strategy', ['mean','median', 'most_frequent', ]), # 'add_indicator' : Categorical('add_indicator', [True, False]), # } # ) # imputation_search = tpot.search_spaces.pipelines.ChoicePipeline([ # tpot.config.get_search_space("SimpleImputer"), # tpot.search_spaces.nodes.EstimatorNode(sklearn.impute.IterativeImputer, iterative_imputer_cs) # ]) # self.search_space_final = tpot.search_spaces.pipelines.SequentialPipeline(search_spaces=[ imputation_search, self._search_space], memory="sklearn_pipeline_memory") # else: # self.search_space_final = self._search_space self.search_space_final = self._search_space def ind_generator(rng): rng = np.random.default_rng(rng) while True: yield self.search_space_final.generate(rng) #If warm start and we have an evolver instance, use the existing one if not(self.warm_start and self._evolver_instance is not None): self._evolver_instance = self._evolver( individual_generator=ind_generator(self.rng), objective_functions= [objective_function], objective_function_weights = self.objective_function_weights, objective_names=self.objective_names, bigger_is_better = self.bigger_is_better, population_size= self.population_size, generations=self.generations, initial_population_size = self._initial_population_size, n_jobs=self.n_jobs, verbose = self.verbose, max_time_mins = self.max_time_mins , max_eval_time_mins = self.max_eval_time_mins, periodic_checkpoint_folder = self.periodic_checkpoint_folder, threshold_evaluation_pruning = self.threshold_evaluation_pruning, threshold_evaluation_scaling = self.threshold_evaluation_scaling, min_history_threshold = self.min_history_threshold, selection_evaluation_pruning = self.selection_evaluation_pruning, selection_evaluation_scaling = self.selection_evaluation_scaling, evaluation_early_stop_steps = evaluation_early_stop_steps, early_stop_tol = self.early_stop_tol, early_stop= self.early_stop, budget_range = self.budget_range, budget_scaling = self.budget_scaling, generations_until_end_budget = self.generations_until_end_budget, population_scaling = self.population_scaling, generations_until_end_population = self.generations_until_end_population, stepwise_steps = self.stepwise_steps, client = _client, objective_kwargs = {"X": X_future, "y": y_future}, survival_selector=self.survival_selector, parent_selector=self.parent_selector, survival_percentage = self.survival_percentage, crossover_probability = self.crossover_probability, mutate_probability = self.mutate_probability, mutate_then_crossover_probability= self.mutate_then_crossover_probability, crossover_then_mutate_probability= self.crossover_then_mutate_probability, rng=self.rng, ) self._evolver_instance.optimize() #self._evolver_instance.population.update_pareto_fronts(self.objective_names, self.objective_function_weights) self.make_evaluated_individuals() tpot.utils.get_pareto_frontier(self.evaluated_individuals, column_names=self.objective_names, weights=self.objective_function_weights) if validation_strategy == 'reshuffled': best_pareto_front_idx = list(self.pareto_front.index) best_pareto_front = list(self.pareto_front.loc[best_pareto_front_idx]['Individual']) #reshuffle rows X, y = sklearn.utils.shuffle(X, y, random_state=self.random_state) if self.scatter: X_future = _client.scatter(X) y_future = _client.scatter(y) else: X_future = X y_future = y val_objective_function_list = [lambda ind, X, y, is_classification=self.classification, scorers= self._scorers, cv=self.cv_gen, other_objective_functions=self.other_objective_functions, export_graphpipeline=self.export_graphpipeline, memory=self.memory, **kwargs: objective_function_generator( ind, X, y, is_classification=is_classification, scorers= scorers, cv=cv, other_objective_functions=other_objective_functions, export_graphpipeline=export_graphpipeline, memory=memory, **kwargs, )] objective_kwargs = {"X": X_future, "y": y_future} val_scores, start_times, end_times, eval_errors = tpot.utils.eval_utils.parallel_eval_objective_list(best_pareto_front, val_objective_function_list, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, n_expected_columns=len(self.objective_names), client=_client, **objective_kwargs) val_objective_names = ['validation_'+name for name in self.objective_names] self.objective_names_for_selection = val_objective_names self.evaluated_individuals.loc[best_pareto_front_idx,val_objective_names] = val_scores self.evaluated_individuals.loc[best_pareto_front_idx,'validation_start_times'] = start_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_end_times'] = end_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_eval_errors'] = eval_errors self.evaluated_individuals["Validation_Pareto_Front"] = tpot.utils.get_pareto_frontier(self.evaluated_individuals, column_names=val_objective_names, weights=self.objective_function_weights) elif validation_strategy == 'split': if self.scatter: X_future = _client.scatter(X) y_future = _client.scatter(y) X_val_future = _client.scatter(X_val) y_val_future = _client.scatter(y_val) else: X_future = X y_future = y X_val_future = X_val y_val_future = y_val objective_kwargs = {"X": X_future, "y": y_future, "X_val" : X_val_future, "y_val":y_val_future } best_pareto_front_idx = list(self.pareto_front.index) best_pareto_front = list(self.pareto_front.loc[best_pareto_front_idx]['Individual']) val_objective_function_list = [lambda ind, X, y, X_val, y_val, scorers= self._scorers, other_objective_functions=self.other_objective_functions, export_graphpipeline=self.export_graphpipeline, memory=self.memory, **kwargs: val_objective_function_generator( ind, X, y, X_val, y_val, scorers= scorers, other_objective_functions=other_objective_functions, export_graphpipeline=export_graphpipeline, memory=memory, **kwargs, )] val_scores, start_times, end_times, eval_errors = tpot.utils.eval_utils.parallel_eval_objective_list(best_pareto_front, val_objective_function_list, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, n_expected_columns=len(self.objective_names), client=_client, **objective_kwargs) val_objective_names = ['validation_'+name for name in self.objective_names] self.objective_names_for_selection = val_objective_names self.evaluated_individuals.loc[best_pareto_front_idx,val_objective_names] = val_scores self.evaluated_individuals.loc[best_pareto_front_idx,'validation_start_times'] = start_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_end_times'] = end_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_eval_errors'] = eval_errors self.evaluated_individuals["Validation_Pareto_Front"] = tpot.utils.get_pareto_frontier(self.evaluated_individuals, column_names=val_objective_names, weights=self.objective_function_weights) else: self.objective_names_for_selection = self.objective_names val_scores = self.evaluated_individuals[self.evaluated_individuals[self.objective_names_for_selection].isna().all(1).ne(True)][self.objective_names_for_selection] weighted_scores = val_scores*self.objective_function_weights if self.bigger_is_better: best_indices = list(weighted_scores.sort_values(by=self.objective_names_for_selection, ascending=False).index) else: best_indices = list(weighted_scores.sort_values(by=self.objective_names_for_selection, ascending=True).index) for best_idx in best_indices: best_individual = self.evaluated_individuals.loc[best_idx]['Individual'] self.selected_best_score = self.evaluated_individuals.loc[best_idx] #TODO #best_individual_pipeline = best_individual.export_pipeline(memory=self.memory, cross_val_predict_cv=self.cross_val_predict_cv) if self.export_graphpipeline: best_individual_pipeline = best_individual.export_flattened_graphpipeline(memory=self.memory) else: best_individual_pipeline = best_individual.export_pipeline(memory=self.memory) if self.preprocessing: self.fitted_pipeline_ = sklearn.pipeline.make_pipeline(sklearn.base.clone(self._preprocessing_pipeline), best_individual_pipeline ) else: self.fitted_pipeline_ = best_individual_pipeline try: self.fitted_pipeline_.fit(X_original,y_original) #TODO use y_original as well? break except Exception as e: if self.verbose >= 4: warnings.warn("Final pipeline failed to fit. Rarely, the pipeline might work on the objective function but fail on the full dataset. Generally due to interactions with different features being selected or transformations having different properties. Trying next pipeline") print(e) continue if self.client is None: #no client was passed in #close cluster and client # _client.close() # cluster.close() try: _client.shutdown() cluster.close() #catch exception except Exception as e: print("Error shutting down client and cluster") Warning(e) return self def _estimator_has(attr): '''Check if we can delegate a method to the underlying estimator. First, we check the first fitted final estimator if available, otherwise we check the unfitted final estimator. ''' return lambda self: (self.fitted_pipeline_ is not None and hasattr(self.fitted_pipeline_, attr) ) @available_if(_estimator_has('predict')) def predict(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) preds = self.fitted_pipeline_.predict(X,**predict_params) if self.classification and self.label_encoder_: preds = self.label_encoder_.inverse_transform(preds) return preds @available_if(_estimator_has('predict_proba')) def predict_proba(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) return self.fitted_pipeline_.predict_proba(X,**predict_params) @available_if(_estimator_has('decision_function')) def decision_function(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) return self.fitted_pipeline_.decision_function(X,**predict_params) @available_if(_estimator_has('transform')) def transform(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) return self.fitted_pipeline_.transform(X,**predict_params) @property def classes_(self): """The classes labels. Only exist if the last step is a classifier.""" if self.label_encoder_: return self.label_encoder_.classes_ else: return self.fitted_pipeline_.classes_ @property def _estimator_type(self): return self.fitted_pipeline_._estimator_type def __sklearn_tags__(self): if hasattr(self, 'fitted_pipeline_'): #if fitted try: tags = copy.deepcopy(self.fitted_pipeline_.__sklearn_tags__()) except: tags = copy.deepcopy(get_tags(self.fitted_pipeline_)) else: #if not fitted tags = super().__sklearn_tags__() if self.random_state is None: tags.non_deterministic = False if self.classification: if tags.classifier_tags is None: tags.classifier_tags = sklearn.utils.ClassifierTags() tags.classifier_tags.multi_class = True tags.classifier_tags.multi_label = True return tags def make_evaluated_individuals(self): #check if _evolver_instance exists if self.evaluated_individuals is None: self.evaluated_individuals = self._evolver_instance.population.evaluated_individuals.copy() objects = list(self.evaluated_individuals.index) object_to_int = dict(zip(objects, range(len(objects)))) self.evaluated_individuals = self.evaluated_individuals.set_index(self.evaluated_individuals.index.map(object_to_int)) self.evaluated_individuals['Parents'] = self.evaluated_individuals['Parents'].apply(lambda row: convert_parents_tuples_to_integers(row, object_to_int)) self.evaluated_individuals["Instance"] = self.evaluated_individuals["Individual"].apply(lambda ind: apply_make_pipeline(ind, preprocessing_pipeline=self._preprocessing_pipeline, export_graphpipeline=self.export_graphpipeline, memory=self.memory)) return self.evaluated_individuals @property def pareto_front(self): #check if _evolver_instance exists if self.evaluated_individuals is None: return None else: if "Pareto_Front" not in self.evaluated_individuals: return self.evaluated_individuals else: return self.evaluated_individuals[self.evaluated_individuals["Pareto_Front"]==1] def check_empty_values(data): """ Checks for empty values in a dataset. Args: data (numpy.ndarray or pandas.DataFrame): The dataset to check. Returns: bool: True if the dataset contains empty values, False otherwise. """ if isinstance(data, pd.DataFrame): return data.isnull().values.any() elif isinstance(data, np.ndarray): return np.isnan(data).any() else: raise ValueError("Unsupported data type") ================================================ FILE: tpot/tpot_estimator/estimator_utils.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np import sklearn import sklearn.base import tpot import pandas as pd from .cross_val_utils import cross_val_score_objective def convert_parents_tuples_to_integers(row, object_to_int): """ Helper function to convert the parent rows into integers representing the index of the parent in the population. Original pandas dataframe using a custom index for the parents. This function converts the custom index to an integer index for easier manipulation by end users. Parameters ---------- row: list, np.ndarray, tuple The row to convert. object_to_int: dict A dictionary mapping the object to an integer index. Returns ------- tuple The row with the custom index converted to an integer index. """ if type(row) == list or type(row) == np.ndarray or type(row) == tuple: return tuple(object_to_int[obj] for obj in row) else: return np.nan #TODO add kwargs def apply_make_pipeline(ind, preprocessing_pipeline=None, export_graphpipeline=False, **pipeline_kwargs): """ Helper function to create a column of sklearn pipelines from the tpot individual class. Parameters ---------- ind: tpot.SklearnIndividual The individual to convert to a pipeline. preprocessing_pipeline: sklearn.pipeline.Pipeline, optional The preprocessing pipeline to include before the individual's pipeline. export_graphpipeline: bool, default=False Force the pipeline to be exported as a graph pipeline. Flattens all nested pipelines, FeatureUnions, and GraphPipelines into a single GraphPipeline. pipeline_kwargs: dict Keyword arguments to pass to the export_pipeline or export_flattened_graphpipeline method. Returns ------- sklearn estimator """ try: if export_graphpipeline: est = ind.export_flattened_graphpipeline(**pipeline_kwargs) else: est = ind.export_pipeline(**pipeline_kwargs) if preprocessing_pipeline is None: return est else: return sklearn.pipeline.make_pipeline(sklearn.base.clone(preprocessing_pipeline), est) except: return None def objective_function_generator(pipeline, x,y, scorers, cv, other_objective_functions, step=None, budget=None, is_classification=True, export_graphpipeline=False, **pipeline_kwargs): """ Uses cross validation to evaluate the pipeline using the scorers, and concatenates results with scores from standalone other objective functions. Parameters ---------- pipeline: tpot.SklearnIndividual The individual to evaluate. x: np.ndarray The feature matrix. y: np.ndarray The target vector. scorers: list The scorers to use for cross validation. cv: int, float, or sklearn cross-validator The cross-validator to use. For example, sklearn.model_selection.KFold or sklearn.model_selection.StratifiedKFold. If an int, will use sklearn.model_selection.KFold with n_splits=cv. other_objective_functions: list A list of standalone objective functions to evaluate the pipeline. With signature obj(pipeline) -> float. or obj(pipeline) -> np.ndarray These functions take in the unfitted estimator. step: int, optional The fold to return the scores for. If None, will return the mean of all the scores (per scorer). Default is None. budget: float, optional The budget to subsample the data. If None, will use the full dataset. Default is None. Will subsample budget*len(x) samples. is_classification: bool, default=True If True, will stratify the subsampling. Default is True. export_graphpipeline: bool, default=False Force the pipeline to be exported as a graph pipeline. Flattens all nested sklearn pipelines, FeatureUnions, and GraphPipelines into a single GraphPipeline. pipeline_kwargs: dict Keyword arguments to pass to the export_pipeline or export_flattened_graphpipeline method. Returns ------- np.ndarray The concatenated scores for the pipeline. The first len(scorers) elements are the cross validation scores, and the remaining elements are the standalone objective functions. """ if export_graphpipeline: pipeline = pipeline.export_flattened_graphpipeline(**pipeline_kwargs) else: pipeline = pipeline.export_pipeline(**pipeline_kwargs) if budget is not None and budget < 1: if is_classification: x,y = sklearn.utils.resample(x,y, stratify=y, n_samples=int(budget*len(x)), replace=False, random_state=1) else: x,y = sklearn.utils.resample(x,y, n_samples=int(budget*len(x)), replace=False, random_state=1) if isinstance(cv, int) or isinstance(cv, float): n_splits = cv else: n_splits = cv.n_splits if len(scorers) > 0: cv_obj_scores = cross_val_score_objective(sklearn.base.clone(pipeline),x,y,scorers=scorers, cv=cv , fold=step) else: cv_obj_scores = [] if other_objective_functions is not None and len(other_objective_functions) >0: other_scores = [obj(sklearn.base.clone(pipeline)) for obj in other_objective_functions] #flatten other_scores = np.array(other_scores).flatten().tolist() else: other_scores = [] return np.concatenate([cv_obj_scores,other_scores]) def val_objective_function_generator(pipeline, X_train, y_train, X_test, y_test, scorers, other_objective_functions, export_graphpipeline=False, **pipeline_kwargs): """ Trains a pipeline on a training set and evaluates it on a test set using the scorers and other objective functions. Parameters ---------- pipeline: tpot.SklearnIndividual The individual to evaluate. X_train: np.ndarray The feature matrix of the training set. y_train: np.ndarray The target vector of the training set. X_test: np.ndarray The feature matrix of the test set. y_test: np.ndarray The target vector of the test set. scorers: list The scorers to use for cross validation. other_objective_functions: list A list of standalone objective functions to evaluate the pipeline. With signature obj(pipeline) -> float. or obj(pipeline) -> np.ndarray These functions take in the unfitted estimator. export_graphpipeline: bool, default=False Force the pipeline to be exported as a graph pipeline. Flattens all nested sklearn pipelines, FeatureUnions, and GraphPipelines into a single GraphPipeline. pipeline_kwargs: dict Keyword arguments to pass to the export_pipeline or export_flattened_graphpipeline method. Returns ------- np.ndarray The concatenated scores for the pipeline. The first len(scorers) elements are the cross validation scores, and the remaining elements are the standalone objective functions. """ #subsample the data if export_graphpipeline: pipeline = pipeline.export_flattened_graphpipeline(**pipeline_kwargs) else: pipeline = pipeline.export_pipeline(**pipeline_kwargs) fitted_pipeline = sklearn.base.clone(pipeline) fitted_pipeline.fit(X_train, y_train) if len(scorers) > 0: scores =[sklearn.metrics.get_scorer(scorer)(fitted_pipeline, X_test, y_test) for scorer in scorers] other_scores = [] if other_objective_functions is not None and len(other_objective_functions) >0: other_scores = [obj(sklearn.base.clone(pipeline)) for obj in other_objective_functions] return np.concatenate([scores,other_scores]) def remove_underrepresented_classes(x, y, min_count): """ Helper function to remove classes with less than min_count samples from the dataset. Parameters ---------- x: np.ndarray or pd.DataFrame The feature matrix. y: np.ndarray or pd.Series The target vector. min_count: int The minimum number of samples to keep a class. Returns ------- np.ndarray, np.ndarray The feature matrix and target vector with rows from classes with less than min_count samples removed. """ if isinstance(y, (np.ndarray, pd.Series)): unique, counts = np.unique(y, return_counts=True) if min(counts) >= min_count: return x, y keep_classes = unique[counts >= min_count] mask = np.isin(y, keep_classes) x = x[mask] y = y[mask] elif isinstance(y, pd.DataFrame): counts = y.apply(pd.Series.value_counts) if min(counts) >= min_count: return x, y keep_classes = counts.index[counts >= min_count].tolist() mask = y.isin(keep_classes).all(axis=1) x = x[mask] y = y[mask] else: raise TypeError("y must be a numpy array or a pandas Series/DataFrame") return x, y def convert_to_float(x): try: return float(x) except ValueError: return x def check_if_y_is_encoded(y): ''' Checks if the target y is composed of sequential ints from 0 to N. XGBoost requires the target to be encoded in this way. Parameters ---------- y: np.ndarray The target vector. Returns ------- bool True if the target is encoded as sequential ints from 0 to N, False otherwise ''' y = sorted(set(y)) return all(i == j for i, j in enumerate(y)) ================================================ FILE: tpot/tpot_estimator/steady_state_estimator.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from sklearn.base import BaseEstimator from sklearn.utils.metaestimators import available_if import numpy as np import sklearn.metrics import tpot.config from sklearn.utils.validation import check_is_fitted from tpot.selectors import survival_select_NSGA2, tournament_selection_dominated from sklearn.preprocessing import LabelEncoder from sklearn.utils.multiclass import unique_labels import pandas as pd from sklearn.model_selection import train_test_split import tpot from dask.distributed import Client from dask.distributed import LocalCluster import math from dask import config as cfg from .estimator_utils import * import warnings from sklearn.utils._tags import get_tags import copy from ..config.template_search_spaces import get_template_search_spaces def set_dask_settings(): cfg.set({'distributed.scheduler.worker-ttl': None}) cfg.set({'distributed.scheduler.allowed-failures':1}) #TODO inherit from _BaseComposition? class TPOTEstimatorSteadyState(BaseEstimator): def __init__(self, search_space, scorers= [], scorers_weights = [], classification = False, cv = 10, other_objective_functions=[], #tpot.objectives.estimator_objective_functions.number_of_nodes_objective], other_objective_functions_weights = [], objective_function_names = None, bigger_is_better = True, export_graphpipeline = False, memory = None, categorical_features = None, subsets = None, preprocessing = False, validation_strategy = "none", validation_fraction = .2, disable_label_encoder = False, initial_population_size = 50, population_size = 50, max_evaluated_individuals = None, early_stop = None, early_stop_mins = None, scorers_early_stop_tol = 0.001, other_objectives_early_stop_tol = None, max_time_mins=None, max_eval_time_mins=10, n_jobs=1, memory_limit = None, client = None, crossover_probability=.2, mutate_probability=.7, mutate_then_crossover_probability=.05, crossover_then_mutate_probability=.05, survival_selector = survival_select_NSGA2, parent_selector = tournament_selection_dominated, budget_range = None, budget_scaling = .5, individuals_until_end_budget = 1, stepwise_steps = 5, warm_start = False, verbose = 0, periodic_checkpoint_folder = None, callback = None, processes = True, scatter = True, # random seed for random number generator (rng) random_state = None, optuna_optimize_pareto_front = False, optuna_optimize_pareto_front_trials = 100, optuna_optimize_pareto_front_timeout = 60*10, optuna_storage = "sqlite:///optuna.db", ): ''' An sklearn baseestimator that uses genetic programming to optimize a pipeline. Parameters ---------- scorers : (list, scorer) A scorer or list of scorers to be used in the cross-validation process. see https://scikit-learn.org/stable/modules/model_evaluation.html scorers_weights : list A list of weights to be applied to the scorers during the optimization process. classification : bool If True, the problem is treated as a classification problem. If False, the problem is treated as a regression problem. Used to determine the CV strategy. cv : int, cross-validator - (int): Number of folds to use in the cross-validation process. By uses the sklearn.model_selection.KFold cross-validator for regression and StratifiedKFold for classification. In both cases, shuffled is set to True. - (sklearn.model_selection.BaseCrossValidator): A cross-validator to use in the cross-validation process. other_objective_functions : list, default=[] A list of other objective functions to apply to the pipeline. The function takes a single parameter for the graphpipeline estimator and returns either a single score or a list of scores. other_objective_functions_weights : list, default=[] A list of weights to be applied to the other objective functions. objective_function_names : list, default=None A list of names to be applied to the objective functions. If None, will use the names of the objective functions. bigger_is_better : bool, default=True If True, the objective function is maximized. If False, the objective function is minimized. Use negative weights to reverse the direction. max_size : int, default=np.inf The maximum number of nodes of the pipelines to be generated. linear_pipeline : bool, default=False If True, the pipelines generated will be linear. If False, the pipelines generated will be directed acyclic graphs. root_config_dict : dict, default='auto' The configuration dictionary to use for the root node of the model. If 'auto', will use "classifiers" if classification=True, else "regressors". - 'selectors' : A selection of sklearn Selector methods. - 'classifiers' : A selection of sklearn Classifier methods. - 'regressors' : A selection of sklearn Regressor methods. - 'transformers' : A selection of sklearn Transformer methods. - 'arithmetic_transformer' : A selection of sklearn Arithmetic Transformer methods that replicate symbolic classification/regression operators. - 'passthrough' : A node that just passes though the input. Useful for passing through raw inputs into inner nodes. - 'feature_set_selector' : A selector that pulls out specific subsets of columns from the data. Only well defined as a leaf. Subsets are set with the subsets parameter. - 'skrebate' : Includes ReliefF, SURF, SURFstar, MultiSURF. - 'MDR' : Includes MDR. - 'ContinuousMDR' : Includes ContinuousMDR. - 'genetic encoders' : Includes Genetic Encoder methods as used in AutoQTL. - 'FeatureEncodingFrequencySelector': Includes FeatureEncodingFrequencySelector method as used in AutoQTL. - list : a list of strings out of the above options to include the corresponding methods in the configuration dictionary. inner_config_dict : dict, default=["selectors", "transformers"] The configuration dictionary to use for the inner nodes of the model generation. Default ["selectors", "transformers"] - 'selectors' : A selection of sklearn Selector methods. - 'classifiers' : A selection of sklearn Classifier methods. - 'regressors' : A selection of sklearn Regressor methods. - 'transformers' : A selection of sklearn Transformer methods. - 'arithmetic_transformer' : A selection of sklearn Arithmetic Transformer methods that replicate symbolic classification/regression operators. - 'passthrough' : A node that just passes though the input. Useful for passing through raw inputs into inner nodes. - 'feature_set_selector' : A selector that pulls out specific subsets of columns from the data. Only well defined as a leaf. Subsets are set with the subsets parameter. - 'skrebate' : Includes ReliefF, SURF, SURFstar, MultiSURF. - 'MDR' : Includes MDR. - 'ContinuousMDR' : Includes ContinuousMDR. - 'genetic encoders' : Includes Genetic Encoder methods as used in AutoQTL. - 'FeatureEncodingFrequencySelector': Includes FeatureEncodingFrequencySelector method as used in AutoQTL. - list : a list of strings out of the above options to include the corresponding methods in the configuration dictionary. - None : If None and max_depth>1, the root_config_dict will be used for the inner nodes as well. leaf_config_dict : dict, default=None The configuration dictionary to use for the leaf node of the model. If set, leaf nodes must be from this dictionary. Otherwise leaf nodes will be generated from the root_config_dict. Default None - 'selectors' : A selection of sklearn Selector methods. - 'classifiers' : A selection of sklearn Classifier methods. - 'regressors' : A selection of sklearn Regressor methods. - 'transformers' : A selection of sklearn Transformer methods. - 'arithmetic_transformer' : A selection of sklearn Arithmetic Transformer methods that replicate symbolic classification/regression operators. - 'passthrough' : A node that just passes though the input. Useful for passing through raw inputs into inner nodes. - 'feature_set_selector' : A selector that pulls out specific subsets of columns from the data. Only well defined as a leaf. Subsets are set with the subsets parameter. - 'skrebate' : Includes ReliefF, SURF, SURFstar, MultiSURF. - 'MDR' : Includes MDR. - 'ContinuousMDR' : Includes ContinuousMDR. - 'genetic encoders' : Includes Genetic Encoder methods as used in AutoQTL. - 'FeatureEncodingFrequencySelector': Includes FeatureEncodingFrequencySelector method as used in AutoQTL. - list : a list of strings out of the above options to include the corresponding methods in the configuration dictionary. - None : If None, a leaf will not be required (i.e. the pipeline can be a single root node). Leaf nodes will be generated from the inner_config_dict. categorical_features: list or None Categorical columns to inpute and/or one hot encode during the preprocessing step. Used only if preprocessing is not False. - None : If None, TPOT will automatically use object columns in pandas dataframes as objects for one hot encoding in preprocessing. - List of categorical features. If X is a dataframe, this should be a list of column names. If X is a numpy array, this should be a list of column indices memory: Memory object or string, default=None If supplied, pipeline will cache each transformer after calling fit with joblib.Memory. This feature is used to avoid computing the fit transformers within a pipeline if the parameters and input data are identical with another fitted pipeline during optimization process. - String 'auto': TPOT uses memory caching with a temporary directory and cleans it up upon shutdown. - String path of a caching directory TPOT uses memory caching with the provided directory and TPOT does NOT clean the caching directory up upon shutdown. If the directory does not exist, TPOT will create it. - Memory object: TPOT uses the instance of joblib.Memory for memory caching, and TPOT does NOT clean the caching directory up upon shutdown. - None: TPOT does not use memory caching. preprocessing : bool or BaseEstimator/Pipeline, EXPERIMENTAL A pipeline that will be used to preprocess the data before CV. - bool : If True, will use a default preprocessing pipeline. - Pipeline : If an instance of a pipeline is given, will use that pipeline as the preprocessing pipeline. validation_strategy : str, default='none' EXPERIMENTAL The validation strategy to use for selecting the final pipeline from the population. TPOT may overfit the cross validation score. A second validation set can be used to select the final pipeline. - 'auto' : Automatically determine the validation strategy based on the dataset shape. - 'reshuffled' : Use the same data for cross validation and final validation, but with different splits for the folds. This is the default for small datasets. - 'split' : Use a separate validation set for final validation. Data will be split according to validation_fraction. This is the default for medium datasets. - 'none' : Do not use a separate validation set for final validation. Select based on the original cross-validation score. This is the default for large datasets. validation_fraction : float, default=0.2 EXPERIMENTAL The fraction of the dataset to use for the validation set when validation_strategy is 'split'. Must be between 0 and 1. disable_label_encoder : bool, default=False If True, TPOT will check if the target needs to be relabeled to be sequential ints from 0 to N. This is necessary for XGBoost compatibility. If the labels need to be encoded, TPOT will use sklearn.preprocessing.LabelEncoder to encode the labels. The encoder can be accessed via the self.label_encoder_ attribute. If False, no additional label encoders will be used. population_size : int, default=50 Size of the population initial_population_size : int, default=50 Size of the initial population. If None, population_size will be used. population_scaling : int, default=0.5 Scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. generations_until_end_population : int, default=1 Number of generations until the population size reaches population_size generations : int, default=50 Number of generations to run early_stop : int, default=None Number of evaluated individuals without improvement before early stopping. Counted across all objectives independently. Triggered when all objectives have not improved by the given number of individuals. early_stop_mins : float, default=None Number of seconds without improvement before early stopping. All objectives must not have improved for the given number of seconds for this to be triggered. scorers_early_stop_tol : -list of floats list of tolerances for each scorer. If the difference between the best score and the current score is less than the tolerance, the individual is considered to have converged If an index of the list is None, that item will not be used for early stopping -int If an int is given, it will be used as the tolerance for all objectives other_objectives_early_stop_tol : -list of floats list of tolerances for each of the other objective function. If the difference between the best score and the current score is less than the tolerance, the individual is considered to have converged If an index of the list is None, that item will not be used for early stopping -int If an int is given, it will be used as the tolerance for all objectives max_time_mins : float, default=float("inf") Maximum time to run the optimization. If none or inf, will run until the end of the generations. max_eval_time_mins : float, default=10 Maximum time to evaluate a single individual. If none or inf, there will be no time limit per evaluation. n_jobs : int, default=1 Number of processes to run in parallel. memory_limit : str, default=None Memory limit for each job. See Dask [LocalCluster documentation](https://distributed.dask.org/en/stable/api.html#distributed.Client) for more information. client : dask.distributed.Client, default=None A dask client to use for parallelization. If not None, this will override the n_jobs and memory_limit parameters. If None, will create a new client with num_workers=n_jobs and memory_limit=memory_limit. crossover_probability : float, default=.2 Probability of generating a new individual by crossover between two individuals. mutate_probability : float, default=.7 Probability of generating a new individual by crossover between one individuals. mutate_then_crossover_probability : float, default=.05 Probability of generating a new individual by mutating two individuals followed by crossover. crossover_then_mutate_probability : float, default=.05 Probability of generating a new individual by crossover between two individuals followed by a mutation of the resulting individual. survival_selector : function, default=survival_select_NSGA2 Function to use to select individuals for survival. Must take a matrix of scores and return selected indexes. Used to selected population_size individuals at the start of each generation to use for mutation and crossover. parent_selector : function, default=parent_select_NSGA2 Function to use to select pairs parents for crossover and individuals for mutation. Must take a matrix of scores and return selected indexes. budget_range : list [start, end], default=None A starting and ending budget to use for the budget scaling. budget_scaling float : [0,1], default=0.5 A scaling factor to use when determining how fast we move the budget from the start to end budget. individuals_until_end_budget : int, default=1 The number of generations to run before reaching the max budget. stepwise_steps : int, default=1 The number of staircase steps to take when scaling the budget and population size. threshold_evaluation_pruning : list [start, end], default=None starting and ending percentile to use as a threshold for the evaluation early stopping. Values between 0 and 100. threshold_evaluation_scaling : float [0,inf), default=0.5 A scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. Must be greater than zero. Higher numbers will move the threshold to the end faster. min_history_threshold : int, default=0 The minimum number of previous scores needed before using threshold early stopping. selection_evaluation_pruning : list, default=None A lower and upper percent of the population size to select each round of CV. Values between 0 and 1. selection_evaluation_scaling : float, default=0.5 A scaling factor to use when determining how fast we move the threshold moves from the start to end percentile. Must be greater than zero. Higher numbers will move the threshold to the end faster. n_initial_optimizations : int, default=0 Number of individuals to optimize before starting the evolution. optimization_cv : int Number of folds to use for the optuna optimization's internal cross-validation. max_optimize_time_seconds : float, default=60*5 Maximum time to run an optimization optimization_steps : int, default=10 Number of steps per optimization warm_start : bool, default=False If True, will use the continue the evolutionary algorithm from the last generation of the previous run. verbose : int, default=1 How much information to print during the optimization process. Higher values include the information from lower values. 0. nothing 1. progress bar 3. best individual 4. warnings >=5. full warnings trace random_state : int, None, default=None A seed for reproducability of experiments. This value will be passed to numpy.random.default_rng() to create an instnce of the genrator to pass to other classes - int Will be used to create and lock in Generator instance with 'numpy.random.default_rng()' - None Will be used to create Generator for 'numpy.random.default_rng()' where a fresh, unpredictable entropy will be pulled from the OS periodic_checkpoint_folder : str, default=None Folder to save the population to periodically. If None, no periodic saving will be done. If provided, training will resume from this checkpoint. callback : tpot.CallBackInterface, default=None Callback object. Not implemented processes : bool, default=True If True, will use multiprocessing to parallelize the optimization process. If False, will use threading. True seems to perform better. However, False is required for interactive debugging. Attributes ---------- fitted_pipeline_ : GraphPipeline A fitted instance of the GraphPipeline that inherits from sklearn BaseEstimator. This is fitted on the full X, y passed to fit. evaluated_individuals : A pandas data frame containing data for all evaluated individuals in the run. Columns: - *objective functions : The first few columns correspond to the passed in scorers and objective functions - Parents : A tuple containing the indexes of the pipelines used to generate the pipeline of that row. If NaN, this pipeline was generated randomly in the initial population. - Variation_Function : Which variation function was used to mutate or crossover the parents. If NaN, this pipeline was generated randomly in the initial population. - Individual : The internal representation of the individual that is used during the evolutionary algorithm. This is not an sklearn BaseEstimator. - Generation : The generation the pipeline first appeared. - Pareto_Front : The nondominated front that this pipeline belongs to. 0 means that its scores is not strictly dominated by any other individual. To save on computational time, the best frontier is updated iteratively each generation. The pipelines with the 0th pareto front do represent the exact best frontier. However, the pipelines with pareto front >= 1 are only in reference to the other pipelines in the final population. All other pipelines are set to NaN. - Instance : The unfitted GraphPipeline BaseEstimator. - *validation objective functions : Objective function scores evaluated on the validation set. - Validation_Pareto_Front : The full pareto front calculated on the validation set. This is calculated for all pipelines with Pareto_Front equal to 0. Unlike the Pareto_Front which only calculates the frontier and the final population, the Validation Pareto Front is calculated for all pipelines tested on the validation set. pareto_front : The same pandas dataframe as evaluated individuals, but containing only the frontier pareto front pipelines. ''' # sklearn BaseEstimator must have a corresponding attribute for each parameter. # These should not be modified once set. self.search_space = search_space self.scorers = scorers self.scorers_weights = scorers_weights self.classification = classification self.cv = cv self.other_objective_functions = other_objective_functions self.other_objective_functions_weights = other_objective_functions_weights self.objective_function_names = objective_function_names self.bigger_is_better = bigger_is_better self.export_graphpipeline = export_graphpipeline self.memory = memory self.categorical_features = categorical_features self.preprocessing = preprocessing self.validation_strategy = validation_strategy self.validation_fraction = validation_fraction self.disable_label_encoder = disable_label_encoder self.population_size = population_size self.initial_population_size = initial_population_size self.early_stop = early_stop self.early_stop_mins = early_stop_mins self.scorers_early_stop_tol = scorers_early_stop_tol self.other_objectives_early_stop_tol = other_objectives_early_stop_tol self.max_time_mins = max_time_mins self.max_eval_time_mins = max_eval_time_mins self.n_jobs= n_jobs self.memory_limit = memory_limit self.client = client self.crossover_probability = crossover_probability self.mutate_probability = mutate_probability self.mutate_then_crossover_probability= mutate_then_crossover_probability self.crossover_then_mutate_probability= crossover_then_mutate_probability self.survival_selector=survival_selector self.parent_selector=parent_selector self.budget_range = budget_range self.budget_scaling = budget_scaling self.individuals_until_end_budget = individuals_until_end_budget self.stepwise_steps = stepwise_steps self.warm_start = warm_start self.verbose = verbose self.periodic_checkpoint_folder = periodic_checkpoint_folder self.callback = callback self.processes = processes self.scatter = scatter self.optuna_optimize_pareto_front = optuna_optimize_pareto_front self.optuna_optimize_pareto_front_trials = optuna_optimize_pareto_front_trials self.optuna_optimize_pareto_front_timeout = optuna_optimize_pareto_front_timeout self.optuna_storage = optuna_storage # create random number generator based on rngseed self.rng = np.random.default_rng(random_state) # save random state passed to us for other functions that use random_state self.random_state = random_state self.max_evaluated_individuals = max_evaluated_individuals #Initialize other used params if self.initial_population_size is None: self._initial_population_size = self.population_size else: self._initial_population_size = self.initial_population_size if isinstance(self.scorers, str): self._scorers = [self.scorers] elif callable(self.scorers): self._scorers = [self.scorers] else: self._scorers = self.scorers self._scorers = [sklearn.metrics.get_scorer(scoring) for scoring in self._scorers] self._scorers_early_stop_tol = self.scorers_early_stop_tol self._evolver = tpot.evolvers.SteadyStateEvolver self.objective_function_weights = [*scorers_weights, *other_objective_functions_weights] if self.objective_function_names is None: obj_names = [f.__name__ for f in other_objective_functions] else: obj_names = self.objective_function_names self.objective_names = [f._score_func.__name__ if hasattr(f,"_score_func") else f.__name__ for f in self._scorers] + obj_names if not isinstance(self.other_objectives_early_stop_tol, list): self._other_objectives_early_stop_tol = [self.other_objectives_early_stop_tol for _ in range(len(self.other_objective_functions))] else: self._other_objectives_early_stop_tol = self.other_objectives_early_stop_tol if not isinstance(self._scorers_early_stop_tol, list): self._scorers_early_stop_tol = [self._scorers_early_stop_tol for _ in range(len(self._scorers))] else: self._scorers_early_stop_tol = self._scorers_early_stop_tol self.early_stop_tol = [*self._scorers_early_stop_tol, *self._other_objectives_early_stop_tol] self._evolver_instance = None self.evaluated_individuals = None self.label_encoder_ = None set_dask_settings() def fit(self, X, y): if self.client is not None: #If user passed in a client manually _client = self.client else: if self.verbose >= 4: silence_logs = 30 elif self.verbose >=5: silence_logs = 40 else: silence_logs = 50 cluster = LocalCluster(n_workers=self.n_jobs, #if no client is passed in and no global client exists, create our own threads_per_worker=1, processes=self.processes, silence_logs=silence_logs, memory_limit=self.memory_limit) _client = Client(cluster) if self.classification and not self.disable_label_encoder and not check_if_y_is_encoded(y): warnings.warn("Labels are not encoded as ints from 0 to N. For compatibility with some classifiers such as sklearn, TPOT has encoded y with the sklearn LabelEncoder. When using pipelines outside the main TPOT estimator class, you can encode the labels with est.label_encoder_") self.label_encoder_ = LabelEncoder() y = self.label_encoder_.fit_transform(y) self.evaluated_individuals = None #determine validation strategy if self.validation_strategy == 'auto': nrows = X.shape[0] ncols = X.shape[1] if nrows/ncols < 20: validation_strategy = 'reshuffled' elif nrows/ncols < 100: validation_strategy = 'split' else: validation_strategy = 'none' else: validation_strategy = self.validation_strategy if validation_strategy == 'split': if self.classification: X, X_val, y, y_val = train_test_split(X, y, test_size=self.validation_fraction, stratify=y, random_state=self.random_state) else: X, X_val, y, y_val = train_test_split(X, y, test_size=self.validation_fraction, random_state=self.random_state) X_original = X y_original = y if isinstance(self.cv, int) or isinstance(self.cv, float): n_folds = self.cv else: n_folds = self.cv.get_n_splits(X, y) if self.classification: X, y = remove_underrepresented_classes(X, y, n_folds) if self.preprocessing: #X = pd.DataFrame(X) if not isinstance(self.preprocessing, bool) and isinstance(self.preprocessing, sklearn.base.BaseEstimator): self._preprocessing_pipeline = self.preprocessing #TODO: check if there are missing values in X before imputation. If not, don't include imputation in pipeline. Check if there are categorical columns. If not, don't include one hot encoding in pipeline else: #if self.preprocessing is True or not a sklearn estimator pipeline_steps = [] if self.categorical_features is not None: #if categorical features are specified, use those pipeline_steps.append(("impute_categorical", tpot.builtin_modules.ColumnSimpleImputer(self.categorical_features, strategy='most_frequent'))) pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("numeric", strategy='mean'))) pipeline_steps.append(("ColumnOneHotEncoder", tpot.builtin_modules.ColumnOneHotEncoder(self.categorical_features, strategy='most_frequent'))) else: if isinstance(X, pd.DataFrame): categorical_columns = X.select_dtypes(include=['object']).columns if len(categorical_columns) > 0: pipeline_steps.append(("impute_categorical", tpot.builtin_modules.ColumnSimpleImputer("categorical", strategy='most_frequent'))) pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("numeric", strategy='mean'))) pipeline_steps.append(("ColumnOneHotEncoder", tpot.builtin_modules.ColumnOneHotEncoder("categorical", strategy='most_frequent'))) else: pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("all", strategy='mean'))) else: pipeline_steps.append(("impute_numeric", tpot.builtin_modules.ColumnSimpleImputer("all", strategy='mean'))) self._preprocessing_pipeline = sklearn.pipeline.Pipeline(pipeline_steps) X = self._preprocessing_pipeline.fit_transform(X, y) else: self._preprocessing_pipeline = None #_, y = sklearn.utils.check_X_y(X, y, y_numeric=True) #Set up the configuation dictionaries and the search spaces #check if self.cv is a number if isinstance(self.cv, int) or isinstance(self.cv, float): if self.classification: self.cv_gen = sklearn.model_selection.StratifiedKFold(n_splits=self.cv, shuffle=True, random_state=self.random_state) else: self.cv_gen = sklearn.model_selection.KFold(n_splits=self.cv, shuffle=True, random_state=self.random_state) else: self.cv_gen = sklearn.model_selection.check_cv(self.cv, y, classifier=self.classification) n_samples= int(math.floor(X.shape[0]/n_folds)) n_features=X.shape[1] if isinstance(X, pd.DataFrame): self.feature_names = X.columns else: self.feature_names = None def objective_function(pipeline_individual, X, y, is_classification=self.classification, scorers= self._scorers, cv=self.cv_gen, other_objective_functions=self.other_objective_functions, export_graphpipeline=self.export_graphpipeline, memory=self.memory, **kwargs): return objective_function_generator( pipeline_individual, X, y, is_classification=is_classification, scorers= scorers, cv=cv, other_objective_functions=other_objective_functions, export_graphpipeline=export_graphpipeline, memory=memory, **kwargs, ) if self.classification: n_classes = len(np.unique(y)) else: n_classes = None get_search_space_params = {"n_classes": n_classes, "n_samples":len(y), "n_features":X.shape[1], "random_state":self.random_state} self._search_space = get_template_search_spaces(self.search_space, classification=self.classification, inner_predictors=True, **get_search_space_params) def ind_generator(rng): rng = np.random.default_rng(rng) while True: yield self._search_space.generate(rng) if self.scatter: X_future = _client.scatter(X) y_future = _client.scatter(y) else: X_future = X y_future = y #If warm start and we have an evolver instance, use the existing one if not(self.warm_start and self._evolver_instance is not None): self._evolver_instance = self._evolver( individual_generator=ind_generator(self.rng), objective_functions= [objective_function], objective_function_weights = self.objective_function_weights, objective_names=self.objective_names, bigger_is_better = self.bigger_is_better, population_size= self.population_size, initial_population_size = self._initial_population_size, n_jobs=self.n_jobs, verbose = self.verbose, max_time_mins = self.max_time_mins , max_eval_time_mins = self.max_eval_time_mins, periodic_checkpoint_folder = self.periodic_checkpoint_folder, early_stop_tol = self.early_stop_tol, early_stop= self.early_stop, early_stop_mins = self.early_stop_mins, budget_range = self.budget_range, budget_scaling = self.budget_scaling, individuals_until_end_budget = self.individuals_until_end_budget, stepwise_steps = self.stepwise_steps, client = _client, objective_kwargs = {"X": X_future, "y": y_future}, survival_selector=self.survival_selector, parent_selector=self.parent_selector, crossover_probability = self.crossover_probability, mutate_probability = self.mutate_probability, mutate_then_crossover_probability= self.mutate_then_crossover_probability, crossover_then_mutate_probability= self.crossover_then_mutate_probability, max_evaluated_individuals = self.max_evaluated_individuals, rng=self.rng, ) self._evolver_instance.optimize() #self._evolver_instance.population.update_pareto_fronts(self.objective_names, self.objective_function_weights) self.make_evaluated_individuals() if self.optuna_optimize_pareto_front: pareto_front_inds = self.pareto_front['Individual'].values all_graphs, all_scores = tpot.individual_representations.graph_pipeline_individual.simple_parallel_optuna(pareto_front_inds, objective_function, self.objective_function_weights, _client, storage=self.optuna_storage, steps=self.optuna_optimize_pareto_front_trials, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, max_time_mins=self.optuna_optimize_pareto_front_timeout, **{"X": X, "y": y}) all_scores = tpot.utils.eval_utils.process_scores(all_scores, len(self.objective_function_weights)) if len(all_graphs) > 0: df = pd.DataFrame(np.column_stack((all_graphs, all_scores,np.repeat("Optuna",len(all_graphs)))), columns=["Individual"] + self.objective_names +["Parents"]) for obj in self.objective_names: df[obj] = df[obj].apply(convert_to_float) self.evaluated_individuals = pd.concat([self.evaluated_individuals, df], ignore_index=True) else: print("WARNING NO OPTUNA TRIALS COMPLETED") tpot.utils.get_pareto_frontier(self.evaluated_individuals, column_names=self.objective_names, weights=self.objective_function_weights) if validation_strategy == 'reshuffled': best_pareto_front_idx = list(self.pareto_front.index) best_pareto_front = list(self.pareto_front.loc[best_pareto_front_idx]['Individual']) #reshuffle rows X, y = sklearn.utils.shuffle(X, y, random_state=self.random_state) if self.scatter: X_future = _client.scatter(X) y_future = _client.scatter(y) else: X_future = X y_future = y val_objective_function_list = [lambda ind, X, y, is_classification=self.classification, scorers= self._scorers, cv=self.cv_gen, other_objective_functions=self.other_objective_functions, export_graphpipeline=self.export_graphpipeline, memory=self.memory, **kwargs: objective_function_generator( ind, X, y, is_classification=is_classification, scorers= scorers, cv=cv, other_objective_functions=other_objective_functions, export_graphpipeline=export_graphpipeline, memory=memory, **kwargs, )] objective_kwargs = {"X": X_future, "y": y_future} val_scores, start_times, end_times, eval_errors = tpot.utils.eval_utils.parallel_eval_objective_list(best_pareto_front, val_objective_function_list, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, n_expected_columns=len(self.objective_names), client=_client, **objective_kwargs) val_objective_names = ['validation_'+name for name in self.objective_names] self.objective_names_for_selection = val_objective_names self.evaluated_individuals.loc[best_pareto_front_idx,val_objective_names] = val_scores self.evaluated_individuals.loc[best_pareto_front_idx,'validation_start_times'] = start_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_end_times'] = end_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_eval_errors'] = eval_errors self.evaluated_individuals["Validation_Pareto_Front"] = tpot.utils.get_pareto_frontier(self.evaluated_individuals, column_names=val_objective_names, weights=self.objective_function_weights) elif validation_strategy == 'split': if self.scatter: X_future = _client.scatter(X) y_future = _client.scatter(y) X_val_future = _client.scatter(X_val) y_val_future = _client.scatter(y_val) else: X_future = X y_future = y X_val_future = X_val y_val_future = y_val objective_kwargs = {"X": X_future, "y": y_future, "X_val" : X_val_future, "y_val":y_val_future } best_pareto_front_idx = list(self.pareto_front.index) best_pareto_front = list(self.pareto_front.loc[best_pareto_front_idx]['Individual']) val_objective_function_list = [lambda ind, X, y, X_val, y_val, scorers= self._scorers, other_objective_functions=self.other_objective_functions, export_graphpipeline=self.export_graphpipeline, memory=self.memory, **kwargs: val_objective_function_generator( ind, X, y, X_val, y_val, scorers= scorers, other_objective_functions=other_objective_functions, export_graphpipeline=export_graphpipeline, memory=memory, **kwargs, )] val_scores, start_times, end_times, eval_errors = tpot.utils.eval_utils.parallel_eval_objective_list(best_pareto_front, val_objective_function_list, verbose=self.verbose, max_eval_time_mins=self.max_eval_time_mins, n_expected_columns=len(self.objective_names), client=_client, **objective_kwargs) val_objective_names = ['validation_'+name for name in self.objective_names] self.objective_names_for_selection = val_objective_names self.evaluated_individuals.loc[best_pareto_front_idx,val_objective_names] = val_scores self.evaluated_individuals.loc[best_pareto_front_idx,'validation_start_times'] = start_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_end_times'] = end_times self.evaluated_individuals.loc[best_pareto_front_idx,'validation_eval_errors'] = eval_errors self.evaluated_individuals["Validation_Pareto_Front"] = tpot.utils.get_pareto_frontier(self.evaluated_individuals, column_names=val_objective_names, weights=self.objective_function_weights) else: self.objective_names_for_selection = self.objective_names val_scores = self.evaluated_individuals[self.evaluated_individuals[self.objective_names_for_selection].isin(["TIMEOUT","INVALID"]).any(axis=1).ne(True)][self.objective_names_for_selection].astype(float) weighted_scores = val_scores*self.objective_function_weights if self.bigger_is_better: best_indices = list(weighted_scores.sort_values(by=self.objective_names_for_selection, ascending=False).index) else: best_indices = list(weighted_scores.sort_values(by=self.objective_names_for_selection, ascending=True).index) for best_idx in best_indices: best_individual = self.evaluated_individuals.loc[best_idx]['Individual'] self.selected_best_score = self.evaluated_individuals.loc[best_idx] #TODO #best_individual_pipeline = best_individual.export_pipeline(memory=self.memory, cross_val_predict_cv=self.cross_val_predict_cv) if self.export_graphpipeline: best_individual_pipeline = best_individual.export_flattened_graphpipeline(memory=self.memory) else: best_individual_pipeline = best_individual.export_pipeline(memory=self.memory) if self.preprocessing: self.fitted_pipeline_ = sklearn.pipeline.make_pipeline(sklearn.base.clone(self._preprocessing_pipeline), best_individual_pipeline ) else: self.fitted_pipeline_ = best_individual_pipeline try: self.fitted_pipeline_.fit(X_original,y_original) #TODO use y_original as well? break except Exception as e: if self.verbose >= 4: warnings.warn("Final pipeline failed to fit. Rarely, the pipeline might work on the objective function but fail on the full dataset. Generally due to interactions with different features being selected or transformations having different properties. Trying next pipeline") print(e) continue if self.client is None: #no client was passed in #close cluster and client # _client.close() # cluster.close() try: _client.shutdown() cluster.close() #catch exception except Exception as e: print("Error shutting down client and cluster") Warning(e) return self def _estimator_has(attr): '''Check if we can delegate a method to the underlying estimator. First, we check the first fitted final estimator if available, otherwise we check the unfitted final estimator. ''' return lambda self: (self.fitted_pipeline_ is not None and hasattr(self.fitted_pipeline_, attr) ) @available_if(_estimator_has('predict')) def predict(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) preds = self.fitted_pipeline_.predict(X,**predict_params) if self.classification and self.label_encoder_: preds = self.label_encoder_.inverse_transform(preds) return preds @available_if(_estimator_has('predict_proba')) def predict_proba(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) return self.fitted_pipeline_.predict_proba(X,**predict_params) @available_if(_estimator_has('decision_function')) def decision_function(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) return self.fitted_pipeline_.decision_function(X,**predict_params) @available_if(_estimator_has('transform')) def transform(self, X, **predict_params): check_is_fitted(self) #X = check_array(X) return self.fitted_pipeline_.transform(X,**predict_params) @property def classes_(self): """The classes labels. Only exist if the last step is a classifier.""" if self.label_encoder_: return self.label_encoder_.classes_ else: return self.fitted_pipeline_.classes_ @property def _estimator_type(self): return self.fitted_pipeline_._estimator_type def __sklearn_tags__(self): if hasattr(self, 'fitted_pipeline_'): #if fitted try: tags = copy.deepcopy(self.fitted_pipeline_.__sklearn_tags__()) except: tags = copy.deepcopy(get_tags(self.fitted_pipeline_)) else: #if not fitted tags = super().__sklearn_tags__() if self.random_state is None: tags.non_deterministic = False if self.classification: if tags.classifier_tags is None: tags.classifier_tags = sklearn.utils.ClassifierTags() tags.classifier_tags.multi_class = True tags.classifier_tags.multi_label = True return tags def make_evaluated_individuals(self): #check if _evolver_instance exists if self.evaluated_individuals is None: self.evaluated_individuals = self._evolver_instance.population.evaluated_individuals.copy() objects = list(self.evaluated_individuals.index) object_to_int = dict(zip(objects, range(len(objects)))) self.evaluated_individuals = self.evaluated_individuals.set_index(self.evaluated_individuals.index.map(object_to_int)) self.evaluated_individuals['Parents'] = self.evaluated_individuals['Parents'].apply(lambda row: convert_parents_tuples_to_integers(row, object_to_int)) self.evaluated_individuals["Instance"] = self.evaluated_individuals["Individual"].apply(lambda ind: apply_make_pipeline(ind, preprocessing_pipeline=self._preprocessing_pipeline, export_graphpipeline=self.export_graphpipeline, memory=self.memory)) return self.evaluated_individuals @property def pareto_front(self): #check if _evolver_instance exists if self.evaluated_individuals is None: return None else: if "Pareto_Front" not in self.evaluated_individuals: return self.evaluated_individuals else: return self.evaluated_individuals[self.evaluated_individuals["Pareto_Front"]==1] ================================================ FILE: tpot/tpot_estimator/templates/__init__.py ================================================ from .tpottemplates import * ================================================ FILE: tpot/tpot_estimator/templates/tpot_autoimputer.py ================================================ ================================================ FILE: tpot/tpot_estimator/templates/tpottemplates.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import tpot import numpy as np import pandas as pd from ..estimator import TPOTEstimator from sklearn.utils.validation import check_X_y, check_array, check_is_fitted from tpot.selectors import survival_select_NSGA2, tournament_selection_dominated #TODO These do not follow sklearn conventions of __init__ from ...config.template_search_spaces import get_template_search_spaces class TPOTRegressor(TPOTEstimator): def __init__( self, search_space = "linear", scorers=['neg_mean_squared_error'], scorers_weights=[1], cv = 10, #remove this and use a value based on dataset size? other_objective_functions=[], #tpot.objectives.estimator_objective_functions.number_of_nodes_objective], other_objective_functions_weights = [], objective_function_names = None, bigger_is_better = True, categorical_features = None, memory = None, preprocessing = False, max_time_mins=60, max_eval_time_mins=10, n_jobs = 1, validation_strategy = "none", validation_fraction = .2, early_stop = None, warm_start = False, periodic_checkpoint_folder = None, verbose = 2, memory_limit = None, client = None, random_state=None, allow_inner_regressors=None, **tpotestimator_kwargs, ): ''' An sklearn baseestimator that uses genetic programming to optimize a regression pipeline. For more parameters, see the TPOTEstimator class. Parameters ---------- search_space : (String, tpot.search_spaces.SearchSpace) - String : The default search space to use for the optimization. | String | Description | | :--- | :----: | | linear | A linear pipeline with the structure of "Selector->(transformers+Passthrough)->(classifiers/regressors+Passthrough)->final classifier/regressor." For both the transformer and inner estimator layers, TPOT may choose one or more transformers/classifiers, or it may choose none. The inner classifier/regressor layer is optional. | | linear-light | Same search space as linear, but without the inner classifier/regressor layer and with a reduced set of faster running estimators. | | graph | TPOT will optimize a pipeline in the shape of a directed acyclic graph. The nodes of the graph can include selectors, scalers, transformers, or classifiers/regressors (inner classifiers/regressors can optionally be not included). This will return a custom GraphPipeline rather than an sklearn Pipeline. More details in Tutorial 6. | | graph-light | Same as graph search space, but without the inner classifier/regressors and with a reduced set of faster running estimators. | | mdr |TPOT will search over a series of feature selectors and Multifactor Dimensionality Reduction models to find a series of operators that maximize prediction accuracy. The TPOT MDR configuration is specialized for genome-wide association studies (GWAS), and is described in detail online here. Note that TPOT MDR may be slow to run because the feature selection routines are computationally expensive, especially on large datasets. | - SearchSpace : The search space to use for the optimization. This should be an instance of a SearchSpace. The search space to use for the optimization. This should be an instance of a SearchSpace. TPOT has groups of search spaces found in the following folders, tpot.search_spaces.nodes for the nodes in the pipeline and tpot.search_spaces.pipelines for the pipeline structure. scorers : (list, scorer) A scorer or list of scorers to be used in the cross-validation process. see https://scikit-learn.org/stable/modules/model_evaluation.html scorers_weights : list A list of weights to be applied to the scorers during the optimization process. classification : bool If True, the problem is treated as a classification problem. If False, the problem is treated as a regression problem. Used to determine the CV strategy. cv : int, cross-validator - (int): Number of folds to use in the cross-validation process. By uses the sklearn.model_selection.KFold cross-validator for regression and StratifiedKFold for classification. In both cases, shuffled is set to True. - (sklearn.model_selection.BaseCrossValidator): A cross-validator to use in the cross-validation process. - max_depth (int): The maximum depth from any node to the root of the pipelines to be generated. other_objective_functions : list, default=[] A list of other objective functions to apply to the pipeline. The function takes a single parameter for the graphpipeline estimator and returns either a single score or a list of scores. other_objective_functions_weights : list, default=[] A list of weights to be applied to the other objective functions. objective_function_names : list, default=None A list of names to be applied to the objective functions. If None, will use the names of the objective functions. bigger_is_better : bool, default=True If True, the objective function is maximized. If False, the objective function is minimized. Use negative weights to reverse the direction. categorical_features : list or None Categorical columns to inpute and/or one hot encode during the preprocessing step. Used only if preprocessing is not False. categorical_features: list or None Categorical columns to inpute and/or one hot encode during the preprocessing step. Used only if preprocessing is not False. - None : If None, TPOT will automatically use object columns in pandas dataframes as objects for one hot encoding in preprocessing. - List of categorical features. If X is a dataframe, this should be a list of column names. If X is a numpy array, this should be a list of column indices memory: Memory object or string, default=None If supplied, pipeline will cache each transformer after calling fit with joblib.Memory. This feature is used to avoid computing the fit transformers within a pipeline if the parameters and input data are identical with another fitted pipeline during optimization process. - String 'auto': TPOT uses memory caching with a temporary directory and cleans it up upon shutdown. - String path of a caching directory TPOT uses memory caching with the provided directory and TPOT does NOT clean the caching directory up upon shutdown. If the directory does not exist, TPOT will create it. - Memory object: TPOT uses the instance of joblib.Memory for memory caching, and TPOT does NOT clean the caching directory up upon shutdown. - None: TPOT does not use memory caching. preprocessing : bool or BaseEstimator/Pipeline, EXPERIMENTAL A pipeline that will be used to preprocess the data before CV. Note that the parameters for these steps are not optimized. Add them to the search space to be optimized. - bool : If True, will use a default preprocessing pipeline which includes imputation followed by one hot encoding. - Pipeline : If an instance of a pipeline is given, will use that pipeline as the preprocessing pipeline. max_time_mins : float, default=float("inf") Maximum time to run the optimization. If none or inf, will run until the end of the generations. max_eval_time_mins : float, default=60*5 Maximum time to evaluate a single individual. If none or inf, there will be no time limit per evaluation. n_jobs : int, default=1 Number of processes to run in parallel. validation_strategy : str, default='none' EXPERIMENTAL The validation strategy to use for selecting the final pipeline from the population. TPOT may overfit the cross validation score. A second validation set can be used to select the final pipeline. - 'auto' : Automatically determine the validation strategy based on the dataset shape. - 'reshuffled' : Use the same data for cross validation and final validation, but with different splits for the folds. This is the default for small datasets. - 'split' : Use a separate validation set for final validation. Data will be split according to validation_fraction. This is the default for medium datasets. - 'none' : Do not use a separate validation set for final validation. Select based on the original cross-validation score. This is the default for large datasets. validation_fraction : float, default=0.2 EXPERIMENTAL The fraction of the dataset to use for the validation set when validation_strategy is 'split'. Must be between 0 and 1. early_stop : int, default=None Number of generations without improvement before early stopping. All objectives must have converged within the tolerance for this to be triggered. In general a value of around 5-20 is good. warm_start : bool, default=False If True, will use the continue the evolutionary algorithm from the last generation of the previous run. periodic_checkpoint_folder : str, default=None Folder to save the population to periodically. If None, no periodic saving will be done. If provided, training will resume from this checkpoint. verbose : int, default=1 How much information to print during the optimization process. Higher values include the information from lower values. 0. nothing 1. progress bar 3. best individual 4. warnings >=5. full warnings trace 6. evaluations progress bar. (Temporary: This used to be 2. Currently, using evaluation progress bar may prevent some instances were we terminate a generation early due to it reaching max_time_mins in the middle of a generation OR a pipeline failed to be terminated normally and we need to manually terminate it.) memory_limit : str, default=None Memory limit for each job. See Dask [LocalCluster documentation](https://distributed.dask.org/en/stable/api.html#distributed.Client) for more information. client : dask.distributed.Client, default=None A dask client to use for parallelization. If not None, this will override the n_jobs and memory_limit parameters. If None, will create a new client with num_workers=n_jobs and memory_limit=memory_limit. random_state : int, None, default=None A seed for reproducability of experiments. This value will be passed to numpy.random.default_rng() to create an instnce of the genrator to pass to other classes - int Will be used to create and lock in Generator instance with 'numpy.random.default_rng()' - None Will be used to create Generator for 'numpy.random.default_rng()' where a fresh, unpredictable entropy will be pulled from the OS allow_inner_regressors : bool, default=True If True, the search space will include ensembled regressors. Attributes ---------- fitted_pipeline_ : GraphPipeline A fitted instance of the GraphPipeline that inherits from sklearn BaseEstimator. This is fitted on the full X, y passed to fit. evaluated_individuals : A pandas data frame containing data for all evaluated individuals in the run. Columns: - *objective functions : The first few columns correspond to the passed in scorers and objective functions - Parents : A tuple containing the indexes of the pipelines used to generate the pipeline of that row. If NaN, this pipeline was generated randomly in the initial population. - Variation_Function : Which variation function was used to mutate or crossover the parents. If NaN, this pipeline was generated randomly in the initial population. - Individual : The internal representation of the individual that is used during the evolutionary algorithm. This is not an sklearn BaseEstimator. - Generation : The generation the pipeline first appeared. - Pareto_Front : The nondominated front that this pipeline belongs to. 0 means that its scores is not strictly dominated by any other individual. To save on computational time, the best frontier is updated iteratively each generation. The pipelines with the 0th pareto front do represent the exact best frontier. However, the pipelines with pareto front >= 1 are only in reference to the other pipelines in the final population. All other pipelines are set to NaN. - Instance : The unfitted GraphPipeline BaseEstimator. - *validation objective functions : Objective function scores evaluated on the validation set. - Validation_Pareto_Front : The full pareto front calculated on the validation set. This is calculated for all pipelines with Pareto_Front equal to 0. Unlike the Pareto_Front which only calculates the frontier and the final population, the Validation Pareto Front is calculated for all pipelines tested on the validation set. pareto_front : The same pandas dataframe as evaluated individuals, but containing only the frontier pareto front pipelines. ''' self.search_space = search_space self.scorers = scorers self.scorers_weights = scorers_weights self.cv = cv self.other_objective_functions = other_objective_functions self.other_objective_functions_weights = other_objective_functions_weights self.objective_function_names = objective_function_names self.bigger_is_better = bigger_is_better self.categorical_features = categorical_features self.memory = memory self.preprocessing = preprocessing self.max_time_mins = max_time_mins self.max_eval_time_mins = max_eval_time_mins self.n_jobs = n_jobs self.validation_strategy = validation_strategy self.validation_fraction = validation_fraction self.early_stop = early_stop self.warm_start = warm_start self.periodic_checkpoint_folder = periodic_checkpoint_folder self.verbose = verbose self.memory_limit = memory_limit self.client = client self.random_state = random_state self.allow_inner_regressors = allow_inner_regressors self.tpotestimator_kwargs = tpotestimator_kwargs self.initialized = False def fit(self, X, y): if not self.initialized: get_search_space_params = {"n_classes": None, "n_samples":len(y), "n_features":X.shape[1], "random_state":self.random_state} search_space = get_template_search_spaces(self.search_space, classification=False, inner_predictors=self.allow_inner_regressors, **get_search_space_params) super(TPOTRegressor,self).__init__( search_space=search_space, scorers=self.scorers, scorers_weights=self.scorers_weights, cv=self.cv, other_objective_functions=self.other_objective_functions, #tpot.objectives.estimator_objective_functions.number_of_nodes_objective], other_objective_functions_weights = self.other_objective_functions_weights, objective_function_names = self.objective_function_names, bigger_is_better = self.bigger_is_better, categorical_features = self.categorical_features, memory = self.memory, preprocessing = self.preprocessing, max_time_mins=self.max_time_mins, max_eval_time_mins=self.max_eval_time_mins, n_jobs=self.n_jobs, validation_strategy = self.validation_strategy, validation_fraction = self.validation_fraction, early_stop = self.early_stop, warm_start = self.warm_start, periodic_checkpoint_folder = self.periodic_checkpoint_folder, verbose = self.verbose, classification=False, memory_limit = self.memory_limit, client = self.client, random_state=self.random_state, **self.tpotestimator_kwargs) self.initialized = True return super().fit(X,y) class TPOTClassifier(TPOTEstimator): def __init__( self, search_space = "linear", scorers=['roc_auc_ovr'], scorers_weights=[1], cv = 10, other_objective_functions=[], #tpot.objectives.estimator_objective_functions.number_of_nodes_objective], other_objective_functions_weights = [], objective_function_names = None, bigger_is_better = True, categorical_features = None, memory = None, preprocessing = False, max_time_mins=60, max_eval_time_mins=10, n_jobs = 1, validation_strategy = "none", validation_fraction = .2, early_stop = None, warm_start = False, periodic_checkpoint_folder = None, verbose = 2, memory_limit = None, client = None, random_state=None, allow_inner_classifiers=None, **tpotestimator_kwargs, ): """ An sklearn baseestimator that uses genetic programming to optimize a classification pipeline. For more parameters, see the TPOTEstimator class. Parameters ---------- search_space : (String, tpot.search_spaces.SearchSpace) - String : The default search space to use for the optimization. | String | Description | | :--- | :----: | | linear | A linear pipeline with the structure of "Selector->(transformers+Passthrough)->(classifiers/regressors+Passthrough)->final classifier/regressor." For both the transformer and inner estimator layers, TPOT may choose one or more transformers/classifiers, or it may choose none. The inner classifier/regressor layer is optional. | | linear-light | Same search space as linear, but without the inner classifier/regressor layer and with a reduced set of faster running estimators. | | graph | TPOT will optimize a pipeline in the shape of a directed acyclic graph. The nodes of the graph can include selectors, scalers, transformers, or classifiers/regressors (inner classifiers/regressors can optionally be not included). This will return a custom GraphPipeline rather than an sklearn Pipeline. More details in Tutorial 6. | | graph-light | Same as graph search space, but without the inner classifier/regressors and with a reduced set of faster running estimators. | | mdr |TPOT will search over a series of feature selectors and Multifactor Dimensionality Reduction models to find a series of operators that maximize prediction accuracy. The TPOT MDR configuration is specialized for genome-wide association studies (GWAS), and is described in detail online here. Note that TPOT MDR may be slow to run because the feature selection routines are computationally expensive, especially on large datasets. | - SearchSpace : The search space to use for the optimization. This should be an instance of a SearchSpace. The search space to use for the optimization. This should be an instance of a SearchSpace. TPOT has groups of search spaces found in the following folders, tpot.search_spaces.nodes for the nodes in the pipeline and tpot.search_spaces.pipelines for the pipeline structure. scorers : (list, scorer) A scorer or list of scorers to be used in the cross-validation process. see https://scikit-learn.org/stable/modules/model_evaluation.html scorers_weights : list A list of weights to be applied to the scorers during the optimization process. classification : bool If True, the problem is treated as a classification problem. If False, the problem is treated as a regression problem. Used to determine the CV strategy. cv : int, cross-validator - (int): Number of folds to use in the cross-validation process. By uses the sklearn.model_selection.KFold cross-validator for regression and StratifiedKFold for classification. In both cases, shuffled is set to True. - (sklearn.model_selection.BaseCrossValidator): A cross-validator to use in the cross-validation process. - max_depth (int): The maximum depth from any node to the root of the pipelines to be generated. other_objective_functions : list, default=[] A list of other objective functions to apply to the pipeline. The function takes a single parameter for the graphpipeline estimator and returns either a single score or a list of scores. other_objective_functions_weights : list, default=[] A list of weights to be applied to the other objective functions. objective_function_names : list, default=None A list of names to be applied to the objective functions. If None, will use the names of the objective functions. bigger_is_better : bool, default=True If True, the objective function is maximized. If False, the objective function is minimized. Use negative weights to reverse the direction. categorical_features : list or None Categorical columns to inpute and/or one hot encode during the preprocessing step. Used only if preprocessing is not False. categorical_features: list or None Categorical columns to inpute and/or one hot encode during the preprocessing step. Used only if preprocessing is not False. - None : If None, TPOT will automatically use object columns in pandas dataframes as objects for one hot encoding in preprocessing. - List of categorical features. If X is a dataframe, this should be a list of column names. If X is a numpy array, this should be a list of column indices memory: Memory object or string, default=None If supplied, pipeline will cache each transformer after calling fit with joblib.Memory. This feature is used to avoid computing the fit transformers within a pipeline if the parameters and input data are identical with another fitted pipeline during optimization process. - String 'auto': TPOT uses memory caching with a temporary directory and cleans it up upon shutdown. - String path of a caching directory TPOT uses memory caching with the provided directory and TPOT does NOT clean the caching directory up upon shutdown. If the directory does not exist, TPOT will create it. - Memory object: TPOT uses the instance of joblib.Memory for memory caching, and TPOT does NOT clean the caching directory up upon shutdown. - None: TPOT does not use memory caching. preprocessing : bool or BaseEstimator/Pipeline, EXPERIMENTAL A pipeline that will be used to preprocess the data before CV. Note that the parameters for these steps are not optimized. Add them to the search space to be optimized. - bool : If True, will use a default preprocessing pipeline which includes imputation followed by one hot encoding. - Pipeline : If an instance of a pipeline is given, will use that pipeline as the preprocessing pipeline. max_time_mins : float, default=float("inf") Maximum time to run the optimization. If none or inf, will run until the end of the generations. max_eval_time_mins : float, default=60*5 Maximum time to evaluate a single individual. If none or inf, there will be no time limit per evaluation. n_jobs : int, default=1 Number of processes to run in parallel. validation_strategy : str, default='none' EXPERIMENTAL The validation strategy to use for selecting the final pipeline from the population. TPOT may overfit the cross validation score. A second validation set can be used to select the final pipeline. - 'auto' : Automatically determine the validation strategy based on the dataset shape. - 'reshuffled' : Use the same data for cross validation and final validation, but with different splits for the folds. This is the default for small datasets. - 'split' : Use a separate validation set for final validation. Data will be split according to validation_fraction. This is the default for medium datasets. - 'none' : Do not use a separate validation set for final validation. Select based on the original cross-validation score. This is the default for large datasets. validation_fraction : float, default=0.2 EXPERIMENTAL The fraction of the dataset to use for the validation set when validation_strategy is 'split'. Must be between 0 and 1. early_stop : int, default=None Number of generations without improvement before early stopping. All objectives must have converged within the tolerance for this to be triggered. In general a value of around 5-20 is good. warm_start : bool, default=False If True, will use the continue the evolutionary algorithm from the last generation of the previous run. periodic_checkpoint_folder : str, default=None Folder to save the population to periodically. If None, no periodic saving will be done. If provided, training will resume from this checkpoint. verbose : int, default=1 How much information to print during the optimization process. Higher values include the information from lower values. 0. nothing 1. progress bar 3. best individual 4. warnings >=5. full warnings trace 6. evaluations progress bar. (Temporary: This used to be 2. Currently, using evaluation progress bar may prevent some instances were we terminate a generation early due to it reaching max_time_mins in the middle of a generation OR a pipeline failed to be terminated normally and we need to manually terminate it.) memory_limit : str, default=None Memory limit for each job. See Dask [LocalCluster documentation](https://distributed.dask.org/en/stable/api.html#distributed.Client) for more information. client : dask.distributed.Client, default=None A dask client to use for parallelization. If not None, this will override the n_jobs and memory_limit parameters. If None, will create a new client with num_workers=n_jobs and memory_limit=memory_limit. random_state : int, None, default=None A seed for reproducability of experiments. This value will be passed to numpy.random.default_rng() to create an instnce of the genrator to pass to other classes - int Will be used to create and lock in Generator instance with 'numpy.random.default_rng()' - None Will be used to create Generator for 'numpy.random.default_rng()' where a fresh, unpredictable entropy will be pulled from the OS allow_inner_classifiers : bool, default=True If True, the search space will include ensembled classifiers. Attributes ---------- fitted_pipeline_ : GraphPipeline A fitted instance of the GraphPipeline that inherits from sklearn BaseEstimator. This is fitted on the full X, y passed to fit. evaluated_individuals : A pandas data frame containing data for all evaluated individuals in the run. Columns: - *objective functions : The first few columns correspond to the passed in scorers and objective functions - Parents : A tuple containing the indexes of the pipelines used to generate the pipeline of that row. If NaN, this pipeline was generated randomly in the initial population. - Variation_Function : Which variation function was used to mutate or crossover the parents. If NaN, this pipeline was generated randomly in the initial population. - Individual : The internal representation of the individual that is used during the evolutionary algorithm. This is not an sklearn BaseEstimator. - Generation : The generation the pipeline first appeared. - Pareto_Front : The nondominated front that this pipeline belongs to. 0 means that its scores is not strictly dominated by any other individual. To save on computational time, the best frontier is updated iteratively each generation. The pipelines with the 0th pareto front do represent the exact best frontier. However, the pipelines with pareto front >= 1 are only in reference to the other pipelines in the final population. All other pipelines are set to NaN. - Instance : The unfitted GraphPipeline BaseEstimator. - *validation objective functions : Objective function scores evaluated on the validation set. - Validation_Pareto_Front : The full pareto front calculated on the validation set. This is calculated for all pipelines with Pareto_Front equal to 0. Unlike the Pareto_Front which only calculates the frontier and the final population, the Validation Pareto Front is calculated for all pipelines tested on the validation set. pareto_front : The same pandas dataframe as evaluated individuals, but containing only the frontier pareto front pipelines. """ self.search_space = search_space self.scorers = scorers self.scorers_weights = scorers_weights self.cv = cv self.other_objective_functions = other_objective_functions self.other_objective_functions_weights = other_objective_functions_weights self.objective_function_names = objective_function_names self.bigger_is_better = bigger_is_better self.categorical_features = categorical_features self.memory = memory self.preprocessing = preprocessing self.max_time_mins = max_time_mins self.max_eval_time_mins = max_eval_time_mins self.n_jobs = n_jobs self.validation_strategy = validation_strategy self.validation_fraction = validation_fraction self.early_stop = early_stop self.warm_start = warm_start self.periodic_checkpoint_folder = periodic_checkpoint_folder self.verbose = verbose self.memory_limit = memory_limit self.client = client self.random_state = random_state self.tpotestimator_kwargs = tpotestimator_kwargs self.allow_inner_classifiers = allow_inner_classifiers self.initialized = False def fit(self, X, y): if not self.initialized: get_search_space_params = {"n_classes": len(np.unique(y)), "n_samples":len(y), "n_features":X.shape[1], "random_state":self.random_state} search_space = get_template_search_spaces(self.search_space, classification=True, inner_predictors=self.allow_inner_classifiers, **get_search_space_params) super(TPOTClassifier,self).__init__( search_space=search_space, scorers=self.scorers, scorers_weights=self.scorers_weights, cv = self.cv, other_objective_functions=self.other_objective_functions, #tpot.objectives.estimator_objective_functions.number_of_nodes_objective], other_objective_functions_weights = self.other_objective_functions_weights, objective_function_names = self.objective_function_names, bigger_is_better = self.bigger_is_better, categorical_features = self.categorical_features, memory = self.memory, preprocessing = self.preprocessing, max_time_mins=self.max_time_mins, max_eval_time_mins=self.max_eval_time_mins, n_jobs=self.n_jobs, validation_strategy = self.validation_strategy, validation_fraction = self.validation_fraction, early_stop = self.early_stop, warm_start = self.warm_start, periodic_checkpoint_folder = self.periodic_checkpoint_folder, verbose = self.verbose, classification=True, memory_limit = self.memory_limit, client = self.client, random_state=self.random_state, **self.tpotestimator_kwargs) self.initialized = True return super().fit(X,y) def predict(self, X, **predict_params): check_is_fitted(self) #X=check_array(X) return self.fitted_pipeline_.predict(X,**predict_params) ================================================ FILE: tpot/tpot_estimator/tests/__init__.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ ================================================ FILE: tpot/tpot_estimator/tests/test_estimator_utils.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import pytest import numpy as np import pandas as pd from ..estimator_utils import * def test_remove_underrepresented_classes(): x = np.array([[1, 2], [3, 4], [5, 6], [7, 8]]) y = np.array([0, 1, 0, 2]) min_count = 2 x_result, y_result = remove_underrepresented_classes(x, y, min_count) np.testing.assert_array_equal(x_result, np.array([[1, 2], [5, 6]])) np.testing.assert_array_equal(y_result, np.array([0, 0])) x = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6], 'd': [7, 8]}).T y = pd.Series([0, 1, 0, 2]) min_count = 2 x_result, y_result = remove_underrepresented_classes(x, y, min_count) pd.testing.assert_frame_equal(x_result, pd.DataFrame({'a': [1, 2], 'c': [5, 6]}).T) pd.testing.assert_series_equal(y_result, pd.Series([0, 1, 0, 2])[[0,2]]) x = np.array([[1, 2], [3, 4], [5, 6], [7, 8]]) y = np.array([0, 1, 0, 1]) min_count = 2 x_result, y_result = remove_underrepresented_classes(x, y, min_count) np.testing.assert_array_equal(x_result, np.array([[1, 2], [3, 4], [5, 6], [7, 8]])) np.testing.assert_array_equal(y_result, np.array([0, 1, 0, 1])) x = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6], 'd': [7, 8]}).T y = pd.Series([0, 1, 0, 1]) min_count = 2 x_result, y_result = remove_underrepresented_classes(x, y, min_count) pd.testing.assert_frame_equal(x_result, pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6], 'd': [7, 8]}).T) pd.testing.assert_series_equal(y_result, pd.Series([0, 1, 0, 1])) def test_check_if_y_is_encoded(): assert check_if_y_is_encoded([0, 1, 2, 3]) == True assert check_if_y_is_encoded([0, 1, 3, 4]) == False assert check_if_y_is_encoded([0, 2, 3]) == False assert check_if_y_is_encoded([0]) == True assert check_if_y_is_encoded([0,0,0,0,1,1,1,1]) == True assert check_if_y_is_encoded([0,0,0,0,1,1,1,1,3]) == False assert check_if_y_is_encoded([1,1,1,1,2,2,2,2]) == False ================================================ FILE: tpot/utils/__init__.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from . import eval_utils from .utils import * # If amltk is installed, import the parser try: from .amltk_parser import tpot_parser except ImportError: # Handle the case when amltk is not installed pass # print("amltk is not installed. Please install it to use tpot_parser.") # Optional: raise an exception or provide alternative functionality ================================================ FILE: tpot/utils/amltk_parser.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ from amltk.pipeline import Choice, Component, Sequential, Node, Fixed, Split, Join, Searchable from tpot.search_spaces.pipelines import SequentialPipeline, ChoicePipeline, UnionPipeline from tpot.search_spaces.nodes import EstimatorNode from ConfigSpace import ConfigurationSpace def component_to_estimatornode(component: Component) -> EstimatorNode: method = component.item space_dict = {} if component.space is not None: space_dict.update(component.space) if component.config is not None: space_dict.update(component.config) space = ConfigurationSpace(component.space) tpot_sp = EstimatorNode(method=method, space=space) return tpot_sp def fixed_to_estimatornode(node: Fixed) -> EstimatorNode: method = node.item #check if method is a class or an object if not isinstance(method, type): method = type(method) #if baseestimator, get params if hasattr(node.item, 'get_params'): space_dict = node.item.get_params(deep=False) else: space_dict = {} if node.space is not None: space_dict.update(node.space) if node.config is not None: space_dict.update(node.config) tpot_sp = EstimatorNode(method=method, space=space_dict) return tpot_sp def sequential_to_sequentialpipeline(sequential: Sequential) -> SequentialPipeline: nodes = [tpot_parser(node) for node in sequential.nodes] tpot_sp = SequentialPipeline(search_spaces=nodes) return tpot_sp def choice_to_choicepipeline(choice: Choice) -> ChoicePipeline: nodes = [tpot_parser(node) for node in choice.nodes] tpot_sp = ChoicePipeline(search_spaces=nodes) return tpot_sp def split_to_unionpipeline(split: Split) -> UnionPipeline: nodes = [tpot_parser(node) for node in split.nodes] tpot_sp = UnionPipeline(search_spaces=nodes) return tpot_sp def tpot_parser( node: Node, ): """ Convert amltk pipeline search space into a tpot pipeline search space. Parameters ---------- node: amltk.pipeline.Node The node to convert. Returns ------- tpot.search_spaces.base.SearchSpace The equivalent TPOT search space which can be optimized by TPOT. """ if isinstance(node, Component): return component_to_estimatornode(node) elif isinstance(node, Sequential): return sequential_to_sequentialpipeline(node) elif isinstance(node, Choice): return choice_to_choicepipeline(node) elif isinstance(node, Fixed): return fixed_to_estimatornode(node) elif isinstance(node, Split): return split_to_unionpipeline(node) else: raise ValueError(f"Node type {type(node)} not supported") ================================================ FILE: tpot/utils/eval_utils.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import types from abc import abstractmethod import numpy as np from joblib import Parallel, delayed import traceback from collections.abc import Iterable import warnings from stopit import threading_timeoutable, TimeoutException from tpot.selectors import survival_select_NSGA2 import time import dask import stopit from dask.diagnostics import ProgressBar from tqdm.dask import TqdmCallback from dask.distributed import progress import distributed import func_timeout import gc import math def process_scores(scores, n): ''' Purpose: This function processes a list of scores to ensure that each score list has the same length, n. If a score list is shorter than n, the function fills the list with either "TIMEOUT" or "INVALID" values. Parameters: scores: A list of score lists. Each score list represents a set of scores for a particular player or team. The score lists may have different lengths. n: An integer representing the desired length for each score list. Returns: The scores list, after processing. ''' for i in range(len(scores)): if len(scores[i]) < n: if "TIMEOUT" in scores[i]: scores[i] = ["TIMEOUT" for j in range(n)] else: scores[i] = ["INVALID" for j in range(n)] return scores def objective_nan_wrapper( individual, objective_function, verbose=0, max_eval_time_mins=None, **objective_kwargs): with warnings.catch_warnings(record=True) as w: #catches all warnings in w so it can be supressed by verbose try: if max_eval_time_mins is None or math.isinf(max_eval_time_mins): value = objective_function(individual, **objective_kwargs) else: value = func_timeout.func_timeout(max_eval_time_mins*60, objective_function, args=[individual], kwargs=objective_kwargs) if not isinstance(value, Iterable): value = [value] if len(w) and verbose>=4: warnings.warn(w[0].message) return value except func_timeout.exceptions.FunctionTimedOut: if verbose >= 4: print(f'WARNING AN INDIVIDUAL TIMED OUT: \n {individual} \n') return ["TIMEOUT"] except Exception as e: if verbose == 4: print(f'WARNING THIS INDIVIDUAL CAUSED AND EXCEPTION \n {individual} \n {e} \n') if verbose >= 5: trace = traceback.format_exc() print(f'WARNING THIS INDIVIDUAL CAUSED AND EXCEPTION \n {individual} \n {e} \n {trace}') return ["INVALID"] def eval_objective_list(ind, objective_list, verbose=0,**objective_kwargs): scores = np.concatenate([objective_nan_wrapper(ind, obj, verbose,**objective_kwargs) for obj in objective_list ]) return scores def parallel_eval_objective_list(individual_list, objective_list, verbose=0, max_eval_time_mins=None, n_expected_columns=None, client=None, scheduled_timeout_time=None, **objective_kwargs): individual_stack = list(individual_list) max_queue_size = len(client.cluster.workers) submitted_futures = {} scores_dict = {} submitted_inds = set() global_timeout_triggered = False while len(submitted_futures) < max_queue_size and len(individual_stack)>0: individual = individual_stack.pop() future = client.submit(eval_objective_list, individual, objective_list, verbose=verbose, max_eval_time_mins=max_eval_time_mins,**objective_kwargs) submitted_futures[future] = {"individual": individual, "time": time.time(),} submitted_inds.add(individual.unique_id()) while len(individual_stack)>0 or len(submitted_futures)>0: #wait for at least one future to finish or timeout try: if max_eval_time_mins is None or math.isinf(max_eval_time_mins): next(distributed.as_completed(submitted_futures)) else: next(distributed.as_completed(submitted_futures, timeout=max_eval_time_mins*60)) except dask.distributed.TimeoutError: pass except dask.distributed.CancelledError: pass global_timeout_triggered = scheduled_timeout_time is not None and time.time() > scheduled_timeout_time #Loop through all futures, collect completed and timeout futures. for completed_future in list(submitted_futures.keys()): #get scores and update if completed_future.done(): #if future is done #If the future is done but threw and error, record the error if completed_future.exception() or completed_future.status == "error": #if the future is done and threw an error print("Exception in future") print(completed_future.exception()) scores = [np.nan for _ in range(n_expected_columns)] eval_error = "INVALID" elif completed_future.cancelled(): #if the future is done and was cancelled print("Cancelled future (likely memory related)") scores = [np.nan for _ in range(n_expected_columns)] eval_error = "INVALID" client.run(gc.collect) else: #if the future is done and did not throw an error, get the scores try: scores = completed_future.result() #check if scores contain "INVALID" or "TIMEOUT" if "INVALID" in scores: eval_error = "INVALID" scores = [np.nan for _ in range(n_expected_columns)] elif "TIMEOUT" in scores: eval_error = "TIMEOUT" scores = [np.nan for _ in range(n_expected_columns)] else: eval_error = None except Exception as e: print("Exception in future, but not caught by dask") print(e) print(completed_future.exception()) print(completed_future) print("status", completed_future.status) print("done", completed_future.done()) print("cancelld ", completed_future.cancelled()) scores = [np.nan for _ in range(n_expected_columns)] eval_error = "INVALID" completed_future.release() #release the future else: #if future is not done # check if the future has been running for too long, cancel the future # we multiply max_eval_time_mins by 1.25 since the objective function in the future should be able to cancel itself. This is a backup in case it doesn't. if max_eval_time_mins is not None and time.time() - submitted_futures[completed_future]["time"] > max_eval_time_mins*1.25*60: completed_future.cancel() completed_future.release() if verbose >= 4: print(f'WARNING AN INDIVIDUAL TIMED OUT (Fallback): \n {submitted_futures[completed_future]} \n') scores = [np.nan for _ in range(n_expected_columns)] eval_error = "TIMEOUT" elif global_timeout_triggered: completed_future.cancel() completed_future.release() if verbose >= 4: print(f'WARNING AN INDIVIDUAL TIMED OUT (max_time_mins): \n {submitted_futures[completed_future]} \n') scores = [np.nan for _ in range(n_expected_columns)] eval_error = None #eval error is None because these individuals were not evaluated or did not have time to reach max_eval_time_mins. this allows them to be reused if warm_start=True else: continue #otherwise, continue to next future #log scores cur_individual = submitted_futures[completed_future]["individual"] scores_dict[cur_individual] = {"scores": scores, "start_time": submitted_futures[completed_future]["time"], "end_time": time.time(), "eval_error": eval_error, } #update submitted futures submitted_futures.pop(completed_future) #I am not entirely sure if this is necessary. I believe that calling release on the futures should be enough to free up memory. If memory issues persist, this may be a good place to start. #client.run(gc.collect) #run garbage collection to free up memory #break if timeout if global_timeout_triggered: while len(individual_stack) > 0: individual = individual_stack.pop() scores_dict[individual] = {"scores": [np.nan for _ in range(n_expected_columns)], "start_time": time.time(), "end_time": time.time(), "eval_error": None, } break #submit new futures while len(submitted_futures) < max_queue_size and len(individual_stack)>0: individual = individual_stack.pop() future = client.submit(eval_objective_list, individual, objective_list, verbose=verbose, timeout=max_eval_time_mins*60,**objective_kwargs) submitted_futures[future] = {"individual": individual, "time": time.time(),} submitted_inds.add(individual.unique_id()) #I am not entirely sure if this is necessary. I believe that calling release on the futures should be enough to free up memory. If memory issues persist, this may be a good place to start. #client.run(gc.collect) #run garbage collection to free up memory #collect remaining futures final_scores = [scores_dict[individual]["scores"] for individual in individual_list] final_start_times = [scores_dict[individual]["start_time"] for individual in individual_list] final_end_times = [scores_dict[individual]["end_time"] for individual in individual_list] final_eval_errors = [scores_dict[individual]["eval_error"] for individual in individual_list] final_scores = process_scores(final_scores, n_expected_columns) return final_scores, final_start_times, final_end_times, final_eval_errors ################### # Parallel optimization ############# @threading_timeoutable(np.nan) #TODO timeout behavior def optimize_objective(ind, objective, steps=5, verbose=0): with warnings.catch_warnings(record=True) as w: #catches all warnings in w so it can be supressed by verbose try: value = ind.optimize(objective, steps=steps) if not isinstance(value, Iterable): value = [value] if len(w) and verbose>=2: warnings.warn(w[0].message) return value except Exception as e: if verbose >= 2: print('WARNING THIS INDIVIDUAL CAUSED AND EXCEPTION') print(e) print() if verbose >= 3: print(traceback.format_exc()) print() return [np.nan] def parallel_optimize_objective(individual_list, objective, n_jobs = 1, verbose=0, steps=5, timeout=None, **objective_kwargs, ): Parallel(n_jobs=n_jobs)(delayed(optimize_objective)(ind, objective, steps, verbose, timeout=timeout) for ind in individual_list ) #TODO: parallelize ================================================ FILE: tpot/utils/utils.py ================================================ """ This file is part of the TPOT library. The current version of TPOT was developed at Cedars-Sinai by: - Pedro Henrique Ribeiro (https://github.com/perib, https://www.linkedin.com/in/pedro-ribeiro/) - Anil Saini (anil.saini@cshs.org) - Jose Hernandez (jgh9094@gmail.com) - Jay Moran (jay.moran@cshs.org) - Nicholas Matsumoto (nicholas.matsumoto@cshs.org) - Hyunjun Choi (hyunjun.choi@cshs.org) - Gabriel Ketron (gabriel.ketron@cshs.org) - Miguel E. Hernandez (miguel.e.hernandez@cshs.org) - Jason Moore (moorejh28@gmail.com) The original version of TPOT was primarily developed at the University of Pennsylvania by: - Randal S. Olson (rso@randalolson.com) - Weixuan Fu (weixuanf@upenn.edu) - Daniel Angell (dpa34@drexel.edu) - Jason Moore (moorejh28@gmail.com) - and many more generous open-source contributors TPOT is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TPOT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TPOT. If not, see . """ import numpy as np import scipy import statistics import tpot import pandas as pd def get_thresholds(scores, start=0, end=1, scale=.5, n=10,): thresh = beta_interpolation(start=start, end=end, scale=scale, n=n) return [np.percentile(scores, t) for t in thresh] def equalize_list(lst, n_steps): step_size = len(lst) / n_steps new_lst = [] for i in range(n_steps): start_index = int(i * step_size) end_index = int((i+1) * step_size) if i == 0: # First segment step_lst = [lst[start_index]] * (end_index - start_index) elif i == n_steps-1: # Last segment step_lst = [lst[-1]] * (end_index - start_index) else: # Middle segment segment = lst[start_index:end_index] median_value = statistics.median(segment) step_lst = [median_value] * (end_index - start_index) new_lst.extend(step_lst) return new_lst def beta_interpolation(start=0, end=1, scale=1, n=10, n_steps=None): if n_steps is None: n_steps = n if n_steps > n: n_steps = n if scale <= 0: scale = 0.0001 if scale >= 1: scale = 0.9999 alpha = 3 * scale beta = 3 - alpha x = np.linspace(0,1,n) values = scipy.special.betainc(alpha,beta,x)*(end-start)+start if n_steps is not None: return equalize_list(values, n_steps) else: return values #thanks chat gtp def remove_items(items, indexes_to_remove): items = items.copy() #if items is a numpy array, we need to convert to a list if type(items) == np.ndarray: items = items.tolist() for index in sorted(indexes_to_remove, reverse=True): del items[index] return np.array(items) # https://stackoverflow.com/questions/32791911/fast-calculation-of-pareto-front-in-python # bigger is better def is_pareto_efficient(scores, return_mask = True): """ Find the pareto-efficient points :param scores: An (n_points, n_scores) array :param return_mask: True to return a mask :return: An array of indices of pareto-efficient points. If return_mask is True, this will be an (n_points, ) boolean array Otherwise it will be a (n_efficient_points, ) integer array of indices. """ is_efficient = np.arange(scores.shape[0]) n_points = scores.shape[0] next_point_index = 0 # Next index in the is_efficient array to search for while next_point_indexscores[next_point_index], axis=1) nondominated_point_mask[next_point_index] = True is_efficient = is_efficient[nondominated_point_mask] # Remove dominated points scores = scores[nondominated_point_mask] next_point_index = np.sum(nondominated_point_mask[:next_point_index])+1 if return_mask: is_efficient_mask = np.zeros(n_points, dtype = bool) is_efficient_mask[is_efficient] = True return is_efficient_mask else: return is_efficient def get_pareto_frontier(df, column_names, weights): # dftmp = df[~df[column_names].isin(invalid_values).any(axis=1)] dftmp = df[df[column_names].notnull().all(axis=1)] if "Budget" in dftmp.columns: #get rows with the max budget dftmp = dftmp[dftmp["Budget"]==dftmp["Budget"].max()] indexes = dftmp[~dftmp[column_names].isna().any(axis=1)].index.values weighted_scores = df.loc[indexes][column_names].to_numpy() * weights mask = is_pareto_efficient(weighted_scores, return_mask = True) df["Pareto_Front"] = np.nan #TODO this will get deprecated df.loc[indexes[mask], "Pareto_Front"] = 1 def get_pareto_front(df, column_names, weights): dftmp = df[df[column_names].notnull().all(axis=1)] if "Budget" in dftmp.columns: #get rows with the max budget dftmp = dftmp[dftmp["Budget"]==dftmp["Budget"].max()] indexes = dftmp[~dftmp[column_names].isna().any(axis=1)].index.values weighted_scores = df.loc[indexes][column_names].to_numpy() * weights pareto_fronts = tpot.selectors.nondominated_sorting(weighted_scores) df = pd.DataFrame(index=df.index,columns=["Pareto_Front"], data=[]) df["Pareto_Front"] = np.nan for i, front in enumerate(pareto_fronts): for index in front: df.loc[indexes[index], "Pareto_Front"] = i+1 return df["Pareto_Front"]