diff --git a/.clang-format b/.clang-format index 3d22e0a8..f6cb8ad9 100644 --- a/.clang-format +++ b/.clang-format @@ -1 +1 @@ -BasedOnStyle: Google +BasedOnStyle: Google diff --git a/.dockerignore b/.dockerignore new file mode 120000 index 00000000..3e4e48b0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..96ef7342 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +# https://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +indent_size = 4 +src_paths=torchopt,tests,examples + +[*.{yaml,yml}] +indent_size = 2 + +[*.md] +indent_size = 2 +x-soft-wrap-text = true + +[*.rst] +indent_size = 4 +x-soft-wrap-text = true + +[Makefile] +indent_style = tab + +[*.{cpp,h,cu,cuh}] +indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..86dcfbcb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,64 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: ["bug"] +assignees: Benjamin-eecs + +--- + +## Describe the bug + +A clear and concise description of what the bug is. + +## To Reproduce + +Steps to reproduce the behavior. + +Please try to provide a minimal example to reproduce the bug. Error messages and stack traces are also helpful. + +Please use the markdown code blocks for both code and stack traces. + +```python +import torchopt +``` + +```pytb +Traceback (most recent call last): + File ... +``` + +## Expected behavior + +A clear and concise description of what you expected to happen. + +## Screenshots + +If applicable, add screenshots to help explain your problem. + +## System info + +Describe the characteristic of your environment: + +- Describe how the library was installed (pip, source, ...) +- Python version +- Versions of any other relevant libraries + +```python +import torchopt, numpy, sys +print(torchopt.__version__, numpy.__version__, sys.version, sys.platform) +``` + +## Additional context + +Add any other context about the problem here. + +## Reason and Possible fixes + +If you know or suspect the reason for this bug, paste the code lines and suggest modifications. + +## Checklist + +- [ ] I have checked that there is no similar issue in the repo (**required**) +- [ ] I have read the [documentation](https://torchopt.readthedocs.io/) (**required**) +- [ ] I have provided a minimal working example to reproduce the bug (**required**) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..b61aa154 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,30 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature Request]" +labels: ["enhancement"] +assignees: Benjamin-eecs + +--- + +## Motivation + +Please outline the motivation for the proposal. +Is your feature request related to a problem? e.g., "I'm always frustrated when [...]". +If this is related to another issue, please link here too. + +## Solution + +A clear and concise description of what you want to happen. + +## Alternatives + +A clear and concise description of any alternative solutions or features you've considered. + +## Additional context + +Add any other context or screenshots about the feature request here. + +## Checklist + +- [ ] I have checked that there is no similar issue in the repo (**required**) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..4225daaf --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,41 @@ +## Description + +Describe your changes in detail. + +## Motivation and Context + +Why is this change required? What problem does it solve? +If it fixes an open issue, please link to the issue here. +You can use the syntax `close #15213` if this solves the issue #15213 + +- [ ] I have raised an issue to propose this change ([required](https://github.com/metaopt/TorchOpt/issues) for new features and bug fixes) + +## Types of changes + +What types of changes does your code introduce? Put an `x` in all the boxes that apply: + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds core functionality) +- [ ] New environment (non-breaking change which adds 3rd-party environment) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] Documentation (update in the documentation) +- [ ] Example (update in the folder of example) + +## Implemented Tasks + +- [ ] Subtask 1 +- [ ] Subtask 2 +- [ ] Subtask 3 + +## Checklist + +Go over all the following points, and put an `x` in all the boxes that apply. +If you are unsure about any of these, don't hesitate to ask. We are here to help! + +- [ ] I have read the [CONTRIBUTION](https://torchopt.readthedocs.io/en/latest/developer/contributing.html) guide (**required**) +- [ ] My change requires a change to the documentation. +- [ ] I have updated the tests accordingly (*required for a bug fix or a new feature*). +- [ ] I have updated the documentation accordingly. +- [ ] I have reformatted the code using `make format` (**required**) +- [ ] I have checked the code using `make lint` (**required**) +- [ ] I have ensured `make test` pass. (**required**) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..8b26e861 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,202 @@ +name: Build + +on: + push: + branches: + - main # allow to trigger the workflow with tag push event + pull_request: + paths: + - setup.py + - setup.cfg + - pyproject.toml + - MANIFEST.in + - CMakeLists.txt + - include/** + - src/** + - torchopt/version.py + - .github/workflow/build.yml + release: + types: + - published + # Allow to trigger the workflow manually + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + runs-on: ubuntu-18.04 + if: github.repository == 'metaopt/TorchOpt' && (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: "recursive" + fetch-depth: 1 + + - name: Set up Python 3.7 + id: py37 + uses: actions/setup-python@v4 + with: + python-version: "3.7" + update-environment: false + + - name: Set up Python 3.8 + id: py38 + uses: actions/setup-python@v4 + with: + python-version: "3.8" + update-environment: false + + - name: Set up Python 3.9 + id: py39 + uses: actions/setup-python@v4 + with: + python-version: "3.9" + update-environment: false + + - name: Set up Python 3.10 + id: py310 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + update-environment: false + + - name: Set up Python executable paths + run: | + echo "${{ steps.py37.outputs.python-path }}" > .python-paths + echo "${{ steps.py38.outputs.python-path }}" >> .python-paths + echo "${{ steps.py39.outputs.python-path }}" >> .python-paths + echo "${{ steps.py310.outputs.python-path }}" >> .python-paths + + - name: Setup CUDA Toolkit + uses: Jimver/cuda-toolkit@v0.2.7 + id: cuda-toolkit + with: + cuda: "11.6.2" + method: network + sub-packages: '["nvcc"]' + - run: | + CUDA_VERSION="${{steps.cuda-toolkit.outputs.cuda}}" + echo "CUDA_VERSION=${CUDA_VERSION}" >> "${GITHUB_ENV}" + TORCH_INDEX_URL="https://download.pytorch.org/whl/cu$(echo "${CUDA_VERSION}" | cut -d'.' -f-2 | tr -d '.')" + echo "TORCH_INDEX_URL=${TORCH_INDEX_URL}" >> "${GITHUB_ENV}" + + echo "Installed CUDA version is: ${CUDA_VERSION}" + echo "CUDA install location: ${{steps.cuda-toolkit.outputs.CUDA_PATH}}" + nvcc -V + echo "Torch index URL: ${TORCH_INDEX_URL}" + + - name: Build sdist and wheels + run: | + DEFAULT_PYTHON="$(head -n 1 .python-paths)" + + while read -r PYTHON; do + echo "Building wheel with Python: ${PYTHON} ($("${PYTHON}" --version))" + "${PYTHON}" -m pip install --upgrade pip setuptools wheel build + "${PYTHON}" -m pip install --extra-index-url "${TORCH_INDEX_URL}" \ + -r requirements.txt + if [[ "${PYTHON}" == "${DEFAULT_PYTHON}" ]]; then + "${PYTHON}" -m build + else + "${PYTHON}" -m build --wheel + fi + done < .python-paths + + - name: List built sdist and wheels + run: | + if [[ -n "$(find dist -maxdepth 0 -not -empty -print 2>/dev/null)" ]]; then + echo "Built sdist and wheels:" + ls -lh dist/ + else + echo "No sdist and wheels are built." + exit 1 + fi + + - name: Audit and repair wheels + run: | + while read -r PYTHON; do + PYVER="cp$("${PYTHON}" --version | cut -d ' ' -f2 | cut -d '.' -f-2 | tr -d '.')" + echo "Audit and repair wheel for Python: ${PYTHON} (${PYVER})" + LIBTORCH_PATH="$("${PYTHON}" -c 'import os, site; print(os.path.join(site.getsitepackages()[0], "torch", "lib"))')" + "${PYTHON}" -m pip install --upgrade git+https://github.com/XuehaiPan/auditwheel.git@torchopt + ( + export LD_LIBRARY_PATH="${LIBTORCH_PATH}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" + echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" + "${PYTHON}" -m auditwheel show dist/torchopt-*-${PYVER}-*.whl && + "${PYTHON}" -m auditwheel repair --plat manylinux2014_x86_64 --wheel-dir wheelhouse dist/torchopt-*-${PYVER}-*.whl + ) + done < .python-paths + + rm dist/torchopt-*.whl + mv wheelhouse/torchopt-*manylinux*.whl dist/ + + - name: List built sdist and wheels + run: | + if [[ -n "$(find dist -maxdepth 0 -not -empty -print 2>/dev/null)" ]]; then + echo "Built sdist and wheels:" + ls -lh dist/ + else + echo "No sdist and wheels are built." + exit 1 + fi + + - name: Test sdist and wheels + run: | + DEFAULT_PYTHON="$(head -n 1 .python-paths)" + while read -r PYTHON; do + PYVER="cp$("${PYTHON}" --version | cut -d ' ' -f2 | cut -d '.' -f-2 | tr -d '.')" + mkdir -p "temp-${PYVER}" + pushd "temp-${PYVER}" + if [[ "${PYTHON}" == "${DEFAULT_PYTHON}" ]]; then + echo "Testing sdist with Python: ${PYTHON} (${PYVER})" + "${PYTHON}" -m pip uninstall torch torchopt -y + "${PYTHON}" -m pip install --extra-index-url https://download.pytorch.org/whl/cpu \ + ../dist/torchopt-*.tar.gz + "${PYTHON}" -c 'import torchopt' + fi + echo "Testing wheel with Python: ${PYTHON} (${PYVER})" + "${PYTHON}" -m pip uninstall torch torchopt -y + "${PYTHON}" -m pip install --extra-index-url https://download.pytorch.org/whl/cpu \ + ../dist/torchopt-*-${PYVER}-*.whl + "${PYTHON}" -c 'import torchopt' + "${PYTHON}" -m pip uninstall torch torchopt -y + popd + done < .python-paths + + - name: Check consistency between the package version and release tag + if: startsWith(github.ref, 'refs/tags/') + run: | + RELEASE_TAG="${GITHUB_REF#refs/*/}" + PACKAGE_VER="v$(python setup.py --version)" + if [[ "${PACKAGE_VER}" != "${RELEASE_TAG}" ]]; then + echo "package ver. (${PACKAGE_VER}) != release tag. (${RELEASE_TAG})" + exit 1 + fi + + - name: Publish to TestPyPI + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' + uses: pypa/gh-action-pypi-publish@v1.5.0 + with: + user: __token__ + password: ${{ secrets.TESTPYPI_UPLOAD_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + verbose: true + print_hash: true + skip_existing: true + + - name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' + uses: pypa/gh-action-pypi-publish@v1.5.0 + with: + user: __token__ + password: ${{ secrets.PYPI_UPLOAD_TOKEN }} + verbose: true + print_hash: true + skip_existing: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..f2393c77 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,102 @@ +name: Lint + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: "recursive" + fetch-depth: 1 + + - name: Set up Python 3.7 # the lowest version we support + uses: actions/setup-python@v4 + with: + python-version: "3.7" + update-environment: true + + - name: Setup CUDA Toolkit + uses: Jimver/cuda-toolkit@v0.2.7 + id: cuda-toolkit + with: + cuda: "11.6.2" + method: network + sub-packages: '["nvcc"]' + - run: | + CUDA_VERSION="${{steps.cuda-toolkit.outputs.cuda}}" + echo "CUDA_VERSION=${CUDA_VERSION}" >> "${GITHUB_ENV}" + TORCH_INDEX_URL="https://download.pytorch.org/whl/cu$(echo "${CUDA_VERSION}" | cut -d'.' -f-2 | tr -d '.')" + echo "TORCH_INDEX_URL=${TORCH_INDEX_URL}" >> "${GITHUB_ENV}" + + echo "Installed CUDA version is: ${CUDA_VERSION}" + echo "CUDA install location: ${{steps.cuda-toolkit.outputs.CUDA_PATH}}" + nvcc -V + echo "Torch index URL: ${TORCH_INDEX_URL}" + + - name: Upgrade pip + run: | + python -m pip install --upgrade pip setuptools + + - name: Install dependencies + run: | + python -m pip install --extra-index-url "${TORCH_INDEX_URL}" \ + -r tests/requirements.txt -r docs/requirements.txt + + - name: Install TorchOpt + run: | + python -m pip install -e . + + - name: pre-commit + run: | + make pre-commit + + - name: flake8 + run: | + make flake8 + + - name: pylint + run: | + make pylint + + - name: isort and black + run: | + make py-format + + - name: cpplint + run: | + make cpplint + + - name: clang-format + run: | + make clang-format + + - name: addlicense + run: | + make addlicense + + - name: mypy + run: | + make mypy + + - name: docstyle + run: | + make docstyle + + - name: spelling + run: | + make spelling diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..5c62ff1b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,77 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + paths: + - setup.py + - setup.cfg + - pyproject.toml + - MANIFEST.in + - CMakeLists.txt + - include/** + - src/** + - tests/** + - torchopt/** + - .github/workflows/tests.yml + +permissions: + contents: read + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: "recursive" + fetch-depth: 1 + + - name: Set up Python 3.7 # the lowest version we support + uses: actions/setup-python@v4 + with: + python-version: "3.7" + update-environment: true + + - name: Setup CUDA Toolkit + uses: Jimver/cuda-toolkit@v0.2.7 + id: cuda-toolkit + with: + cuda: "11.6.2" + method: network + sub-packages: '["nvcc"]' + - run: | + CUDA_VERSION="${{steps.cuda-toolkit.outputs.cuda}}" + echo "CUDA_VERSION=${CUDA_VERSION}" >> "${GITHUB_ENV}" + TORCH_INDEX_URL="https://download.pytorch.org/whl/cu$(echo "${CUDA_VERSION}" | cut -d'.' -f-2 | tr -d '.')" + echo "TORCH_INDEX_URL=${TORCH_INDEX_URL}" >> "${GITHUB_ENV}" + + echo "Installed CUDA version is: ${CUDA_VERSION}" + echo "CUDA install location: ${{steps.cuda-toolkit.outputs.CUDA_PATH}}" + nvcc -V + echo "Torch index URL: ${TORCH_INDEX_URL}" + + - name: Upgrade pip + run: | + python -m pip install --upgrade pip setuptools + + - name: Install dependencies + run: | + python -m pip install --extra-index-url "${TORCH_INDEX_URL}" \ + -r tests/requirements.txt + + - name: Install TorchOpt + run: | + python -m pip install -e . + + - name: Test with pytest + run: | + make pytest diff --git a/.gitignore b/.gitignore index 14816e4b..e195bfa9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,410 @@ -.vscode -.idea -build -__pycache__ -TorchOpt/**/*.so -TorchOpt.egg-info -dist -**/.ipynb_checkpoints/* +##### Python.gitignore ##### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +wheelhouse/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docs/source/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + + +##### macOS.gitignore ##### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +##### Linux.gitignore ##### +*~ + +# Temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + + +##### Windows.gitignore ##### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +##### Archives.gitignore ##### +# It's better to unpack these files and commit the raw source because +# git has its own built in compression methods. +*.7z +*.jar +*.rar +*.zip +*.gz +*.gzip +*.tgz +*.bzip +*.bzip2 +*.bz2 +*.xz +*.lzma +*.cab +*.xar + +# Packing-only formats +*.iso +*.tar + +# Package management formats +*.dmg +*.xpi +*.gem +*.egg +*.deb +*.rpm +*.msi +*.msm +*.msp +*.txz + + +##### Xcode.gitignore ##### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## Compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## Compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Gcc Patch +/*.gcno + + +##### JetBrains.gitignore ##### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User settings +.idea/* + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + + +##### VisualStudioCode.gitignore ##### +.vscode/* +# !.vscode/settings.json +# !.vscode/tasks.json +# !.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + + +##### Vim.gitignore ##### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 111a2bef..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "third_party/pybind11"] - path = third_party/pybind11 - url = https://github.com/pybind/pybind11.git - shallow = true \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9849236f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,45 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-symlinks + - id: destroyed-symlinks + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-toml + - id: check-ast + - id: check-added-large-files + - id: check-merge-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: detect-private-key + - id: debug-statements + - id: double-quote-string-fixer + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + stages: [commit, push, manual] + - repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black + stages: [commit, push, manual] + - repo: local + hooks: + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + require_serial: true + stages: [commit, push, manual] + exclude: | + (?x)( + ^examples/| + ^tests/| + ^setup.py$ + ) diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..81c22e5d --- /dev/null +++ b/.pylintrc @@ -0,0 +1,593 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS,.vscode,.history, + examples, + tests + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. The default value ignores emacs file +# locks +ignore-patterns=^\.# + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.7 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=missing-module-docstring, + duplicate-code, + consider-using-from-import + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _, + op, + fn, + f, + g, + p, + u, + t, + lr, + mu, + nu + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=numpy.*, + torch.* + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=no + +# Signatures are removed from the similarity computation +ignore-signatures=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..88b7a202 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,32 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-20.04 + tools: + python: mambaforge-4.10 + +# Optionally declare the Python requirements required to build your docs +conda: + environment: docs/conda-recipe.yaml + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: html + configuration: docs/source/conf.py + fail_on_warning: true + +# Optionally declare the Python requirements required to build your docs +python: + install: + - method: pip + path: . diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..70cbe2e8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +------ + +## [Unreleased] + +------ + +## [0.4.2] - 2022-07-26 + +### Added + +- Read the Docs integration by [@Benjamin-eecs](https://github.com/Benjamin-eecs) and [@XuehaiPan](https://github.com/XuehaiPan) in [#34](https://github.com/metaopt/TorchOpt/pull/34). +- Update documentation and code styles by [@Benjamin-eecs](https://github.com/Benjamin-eecs) and [@XuehaiPan](https://github.com/XuehaiPan) in [#22](https://github.com/metaopt/TorchOpt/pull/22). +- Update tutorial notebooks by [@XuehaiPan](https://github.com/XuehaiPan) in [#27](https://github.com/metaopt/TorchOpt/pull/27). +- Bump PyTorch version to 1.12 by [@XuehaiPan](https://github.com/XuehaiPan) in [#25](https://github.com/metaopt/TorchOpt/pull/25). +- Support custom Python executable path in `CMakeLists.txt` by [@XuehaiPan](https://github.com/XuehaiPan) in [#18](https://github.com/metaopt/TorchOpt/pull/18). +- Add citation information by [@waterhorse1](https://github.com/waterhorse1) in [#14](https://github.com/metaopt/TorchOpt/pull/14) and [@Benjamin-eecs](https://github.com/Benjamin-eecs) in [#15](https://github.com/metaopt/TorchOpt/pull/15). +- Implement RMSProp optimizer by [@future-xy](https://github.com/future-xy) in [#8](https://github.com/metaopt/TorchOpt/pull/8). + +### Changed + +- Use `pyproject.toml` for packaging and update GitHub Action workflows by [@XuehaiPan](https://github.com/XuehaiPan) in [#31](https://github.com/metaopt/TorchOpt/pull/31). +- Rename the package from `TorchOpt` to `torchopt` by [@XuehaiPan](https://github.com/XuehaiPan) in [#20](https://github.com/metaopt/TorchOpt/pull/20). + +### Fixed + +- Fixed errors while building from the source and add `conda` environment recipe by [@XuehaiPan](https://github.com/XuehaiPan) in [#24](https://github.com/metaopt/TorchOpt/pull/24). + +------ + +## [0.4.1] - 2022-04-15 + +### Fixed + +- Fix set devices bug for multi-GPUs. + +------ + +## [0.4.0] - 2022-04-09 + +### Added + +- The first beta release of TorchOpt. +- TorchOpt with L2R, LOLA, MAML-RL, MGRL, and few-shot examples. + +------ + +[Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.4.2...HEAD +[0.4.2]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.4.1...v0.4.2 +[0.4.1]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/olivierlacan/keep-a-changelog/releases/tag/v0.4.0 diff --git a/CITATION.cff b/CITATION.cff index 5c239556..60c65cb3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -16,6 +16,10 @@ authors: email: benjaminliu.eecs@gmail.com affiliation: Peking University orcid: 'https://orcid.org/0000-0001-5426-515X' + - given-names: Xuehai + family-names: Pan + email: xuehaipan@pku.edu.cn + affiliation: Peking University - given-names: Luo family-names: Mai email: luo.mai@ed.ac.uk @@ -24,7 +28,7 @@ authors: family-names: Yang affiliation: Peking University email: yaodong.yang@pku.edu.cn -version: 0.4.1 -date-released: "2022-04-09" +version: 0.4.2 +date-released: "2022-07-26" license: Apache-2.0 repository-code: "https://github.com/metaopt/TorchOpt" diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100755 new mode 100644 index 3b0f3229..523dc849 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,25 +13,17 @@ # limitations under the License. # ============================================================================== -cmake_minimum_required(VERSION 3.1) -project(TorchOpt LANGUAGES CXX CUDA) - -find_package(CUDA REQUIRED) - -# include(FindCUDA/select_compute_arch) -# CUDA_DETECT_INSTALLED_GPUS(INSTALLED_GPU_CCS_1) -# string(STRIP "${INSTALLED_GPU_CCS_1}" INSTALLED_GPU_CCS_2) -# string(REPLACE " " ";" INSTALLED_GPU_CCS_3 "${INSTALLED_GPU_CCS_2}") -# string(REPLACE "." "" CUDA_ARCH_LIST "${INSTALLED_GPU_CCS_3}") -# message("-- nvcc generates code for arch ${CUDA_ARCH_LIST}") -# SET(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCH_LIST}) -SET(CMAKE_CUDA_ARCHITECTURES 53;60;61;70;75;80;86) +cmake_minimum_required(VERSION 3.4) +project(torchopt LANGUAGES CXX CUDA) if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) + set(CMAKE_BUILD_TYPE Release) endif() -set(CMAKE_INCLUDE_CURRENT_DIR ON) +find_package(CUDA REQUIRED) +cuda_select_nvcc_arch_flags(CUDA_ARCH_FLAGS All) +list(APPEND CUDA_NVCC_FLAGS ${CUDA_ARCH_FLAGS}) + set(CMAKE_CXX_STANDARD 14) set(CMAKE_CUDA_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -39,59 +31,126 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pthread -fPIC -fopenmp") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CUDA_FLAGS_RELEASE "${CMAKE_CUDA_FLAGS_RELEASE} -O3") +function(system) + set(options STRIP) + set(oneValueArgs OUTPUT_VARIABLE ERROR_VARIABLE WORKING_DIRECTORY) + set(multiValueArgs COMMAND) + cmake_parse_arguments(SYSTEM + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + "${ARGN}") + + if(NOT DEFINED SYSTEM_WORKING_DIRECTORY) + set(SYSTEM_WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") + endif() + + execute_process( + COMMAND ${SYSTEM_COMMAND} + OUTPUT_VARIABLE STDOUT + ERROR_VARIABLE STDERR + WORKING_DIRECTORY "${SYSTEM_WORKING_DIRECTORY}" + ) + if("${SYSTEM_STRIP}") + string(STRIP "${STDOUT}" STDOUT) + string(STRIP "${STDERR}" STDERR) + endif() + + set("${SYSTEM_OUTPUT_VARIABLE}" "${STDOUT}" PARENT_SCOPE) + + if(DEFINED SYSTEM_ERROR_VARIABLE) + set("${SYSTEM_ERROR_VARIABLE}" "${STDERR}" PARENT_SCOPE) + endif() +endfunction() + +if(NOT DEFINED PYTHON_EXECUTABLE) + set(PYTHON_EXECUTABLE python3) +endif() + +system( + STRIP OUTPUT_VARIABLE PYTHON_EXECUTABLE + COMMAND bash -c "type -P '${PYTHON_EXECUTABLE}'" +) + +system( + STRIP OUTPUT_VARIABLE PYTHON_VERSION + COMMAND "${PYTHON_EXECUTABLE}" -c "print(__import__('platform').python_version())" +) + +message(STATUS "Use Python version: ${PYTHON_VERSION}") +message(STATUS "Use Python executable: \"${PYTHON_EXECUTABLE}\"") + if(NOT DEFINED PYTHON_INCLUDE_DIR) - message("-- Auto detecting Python include directory...") - execute_process ( - COMMAND python3 -c "import sys; import os; path = sys.path[2]; splited_path = path.split('/'); splited_path[-2] = 'include'; print(os.path.join('/', *splited_path))" - OUTPUT_VARIABLE PYTHON_INCLUDE_DIR) - string(STRIP ${PYTHON_INCLUDE_DIR} PYTHON_INCLUDE_DIR) + message(STATUS "Auto detecting Python include directory...") + system( + STRIP OUTPUT_VARIABLE PYTHON_INCLUDE_DIR + COMMAND "${PYTHON_EXECUTABLE}" -c "print(__import__('sysconfig').get_path('include'))" + ) endif() if("${PYTHON_INCLUDE_DIR}" STREQUAL "") - message(FATAL_ERROR "-- Python include directory not found") + message(FATAL_ERROR "Python include directory not found") else() - message("-- Detected Python include directory: ${PYTHON_INCLUDE_DIR}") + message(STATUS "Detected Python include directory: \"${PYTHON_INCLUDE_DIR}\"") include_directories(${PYTHON_INCLUDE_DIR}) endif() +set(PYBIND11_PYTHON_VERSION "${PYTHON_VERSION}") + +if(NOT DEFINED PYBIND11_CMAKE_DIR) + message(STATUS "Auto detecting pybind11 CMake directory...") + system( + STRIP OUTPUT_VARIABLE PYBIND11_CMAKE_DIR + COMMAND "${PYTHON_EXECUTABLE}" -m pybind11 --cmakedir + ) +endif() + +if("${PYBIND11_CMAKE_DIR}" STREQUAL "") + message(FATAL_ERROR "Pybind11 CMake directory not found") +else() + message(STATUS "Detected Pybind11 CMake directory: \"${PYBIND11_CMAKE_DIR}\"") + find_package(pybind11 CONFIG PATHS "${PYBIND11_CMAKE_DIR}") +endif() + if(NOT DEFINED TORCH_INCLUDE_PATH) - message("-- Auto detecting PyTorch include directory...") - execute_process ( - COMMAND python3 -c "from torch.utils import cpp_extension; print(cpp_extension.include_paths()[0], end='')" - OUTPUT_VARIABLE TORCH_INCLUDE_PATH) - string(STRIP ${TORCH_INCLUDE_PATH} TORCH_INCLUDE_PATH) + message(STATUS "Auto detecting PyTorch include directory...") + system( + STRIP OUTPUT_VARIABLE TORCH_INCLUDE_PATH + COMMAND "${PYTHON_EXECUTABLE}" -c "print('\\\;'.join(__import__('torch.utils.cpp_extension', fromlist=[None]).include_paths()))" + ) endif() if("${TORCH_INCLUDE_PATH}" STREQUAL "") - message(FATAL_ERROR "-- Torch include directory not found") + message(FATAL_ERROR "Torch include directory not found") else() - message("-- Detected Torch include directory: ${TORCH_INCLUDE_PATH}") + message(STATUS "Detected Torch include directory: \"${TORCH_INCLUDE_PATH}\"") include_directories(${TORCH_INCLUDE_PATH}) endif() - if(NOT DEFINED TORCH_LIBRARY_PATH) - message("-- Auto detecting PyTorch library directory...") - execute_process ( - COMMAND python3 -c "from torch.utils import cpp_extension; print(cpp_extension.library_paths()[0], end='')" - OUTPUT_VARIABLE TORCH_LIBRARY_PATH) - string(STRIP ${TORCH_LIBRARY_PATH} TORCH_LIBRARY_PATH) + message(STATUS "Auto detecting PyTorch library directory...") + system( + STRIP OUTPUT_VARIABLE TORCH_LIBRARY_PATH + COMMAND "${PYTHON_EXECUTABLE}" -c "print('\\\;'.join(__import__('torch.utils.cpp_extension', fromlist=[None]).library_paths()))" + ) endif() if("${TORCH_LIBRARY_PATH}" STREQUAL "") - message(FATAL_ERROR "-- Torch library directory not found") + message(FATAL_ERROR "Torch library directory not found") else() - message("-- Detected Torch library directory: ${TORCH_LIBRARY_PATH}") + message(STATUS "Detected Torch library directory: \"${TORCH_LIBRARY_PATH}\"") endif() -add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) - -add_subdirectory("third_party/pybind11") -include_directories(include) +unset(TORCH_LIBRARIES) -foreach(TMP_PATH ${TORCH_LIBRARY_PATH}) - file(GLOB TORCH_LIBRARY ${TMP_PATH}/*.so) - set(TORCH_LIBRARIES "${TORCH_LIBRARIES};${TORCH_LIBRARY};") +foreach(VAR_PATH ${TORCH_LIBRARY_PATH}) + file(GLOB TORCH_LIBRARY "${VAR_PATH}/*.so") + list(APPEND TORCH_LIBRARIES "${TORCH_LIBRARY}") endforeach() +message(STATUS "Detected Torch libraries: \"${TORCH_LIBRARIES}\"") + +add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + +include_directories(${CMAKE_SOURCE_DIR}) add_subdirectory(src) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..9cc25a3e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,6 @@ + + + +# Contributing to TorchOpt + +Please refer to [torchopt.readthedocs.io/en/latest/developer/contributing.html](https://torchopt.readthedocs.io/en/latest/developer/contributing.html) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e38d6fa4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,87 @@ +# Dockerfile for TorchOpt +# +# $ docker build --target base --tag torchopt:latest . +# +# or +# +# $ docker build --target devel --tag torchopt-devel:latest . +# + +ARG cuda_docker_tag="11.6.2-cudnn8-devel-ubuntu20.04" +FROM nvidia/cuda:"${cuda_docker_tag}" AS builder + +ENV DEBIAN_FRONTEND=noninteractive +SHELL ["/bin/bash", "-c"] + +# Install packages +RUN apt-get update && \ + apt-get install -y sudo ca-certificates openssl \ + git ssh build-essential gcc-10 g++-10 cmake make \ + python3.9-dev python3.9-venv graphviz && \ + rm -rf /var/lib/apt/lists/* + +ENV LANG C.UTF-8 +ENV CC=gcc-10 CXX=g++-10 + +# Add a new user +RUN useradd -m -s /bin/bash torchopt && \ + echo "torchopt ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers +USER torchopt +RUN echo "export PS1='[\[\e[1;33m\]\u\[\e[0m\]:\[\e[1;35m\]\w\[\e[0m\]]\$ '" >> ~/.bashrc + +# Setup virtual environment +RUN /usr/bin/python3.9 -m venv --upgrade-deps ~/venv && rm -rf ~/.pip/cache +RUN TORCH_INDEX_URL="https://download.pytorch.org/whl/cu$(echo "${CUDA_VERSION}" | cut -d'.' -f-2 | tr -d '.')" && \ + echo "export TORCH_INDEX_URL='${TORCH_INDEX_URL}'" >> ~/venv/bin/activate && \ + echo "source /home/torchopt/venv/bin/activate" >> ~/.bashrc + +# Install dependencies +WORKDIR /home/torchopt/TorchOpt +COPY --chown=torchopt requirements.txt requirements.txt +RUN source ~/venv/bin/activate && \ + python -m pip install --extra-index-url "${TORCH_INDEX_URL}" -r requirements.txt && \ + rm -rf ~/.pip/cache ~/.cache/pip + +#################################################################################################### + +FROM builder AS devel-builder + +# Install extra dependencies +RUN sudo apt-get update && \ + sudo apt-get install -y golang-1.16 clang-format clang-tidy && \ + sudo chown -R "$(whoami):$(whoami)" /usr/lib/go-1.16 && \ + sudo rm -rf /var/lib/apt/lists/* + +# Install addlicense +ENV GOPATH="/usr/lib/go-1.16" +ENV GOBIN="${GOPATH}/bin" +ENV GOROOT="${GOPATH}" +ENV PATH="${GOBIN}:${PATH}" +RUN go install github.com/google/addlicense@latest + +# Install extra PyPI dependencies +COPY --chown=torchopt tests/requirements.txt tests/requirements.txt +COPY --chown=torchopt tutorials/requirements.txt tutorials/requirements.txt +RUN source ~/venv/bin/activate && \ + python -m pip install --extra-index-url "${TORCH_INDEX_URL}" \ + -r tests/requirements.txt -r tutorials/requirements.txt && \ + rm -rf ~/.pip/cache ~/.cache/pip + +#################################################################################################### + +FROM builder AS base + +COPY --chown=torchopt . . + +# Install TorchOpt +RUN source ~/venv/bin/activate && \ + python -m pip install -e . && \ + rm -rf .eggs *.egg-info ~/.pip/cache ~/.cache/pip + +ENTRYPOINT [ "/bin/bash", "--login" ] + +#################################################################################################### + +FROM devel-builder AS devel + +COPY --from=base /home/torchopt/TorchOpt . diff --git a/LICENSE b/LICENSE index 46474282..710ed864 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2022] [Jie Ren] + Copyright [2022] [MetaOPT Team. All Rights Reserved.] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..08cf6257 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +recursive-include torchopt *.pyi +include LICENSE + +# Include source files in sdist +include CMakeLists.txt +recursive-include src * +recursive-include include * diff --git a/Makefile b/Makefile index b4e42f22..f050cf1f 100644 --- a/Makefile +++ b/Makefile @@ -1,83 +1,166 @@ print-% : ; @echo $* = $($*) -SHELL = /bin/bash -PROJECT_NAME = TorchOpt -PYTHON_FILES = $(shell find . -type f -name "*.py") -CPP_FILES = $(shell find . -type f -name "*.h" -o -name "*.cpp") -COMMIT_HASH = $(shell git log -1 --format=%h) +PROJECT_NAME = torchopt +COPYRIGHT = "MetaOPT Team. All Rights Reserved." +PROJECT_PATH = $(PROJECT_NAME) +SHELL = /bin/bash +SOURCE_FOLDERS = $(PROJECT_PATH) examples include src tests docs +PYTHON_FILES = $(shell find $(SOURCE_FOLDERS) -type f -name "*.py" -o -name "*.pyi") +CXX_FILES = $(shell find $(SOURCE_FOLDERS) -type f -name "*.h" -o -name "*.cpp" -o -name "*.cuh" -o -name "*.cu") +COMMIT_HASH = $(shell git log -1 --format=%h) +PATH := $(HOME)/go/bin:$(PATH) +PYTHON ?= $(shell command -v python3 || command -v python) +.PHONY: default +default: install -# installation +install: + $(PYTHON) -m pip install . -check_install = python3 -c "import $(1)" || (cd && pip3 install $(1) --upgrade && cd -) -check_install_extra = python3 -c "import $(1)" || (cd && pip3 install $(2) --upgrade && cd -) +build: + $(PYTHON) -m pip install --upgrade pip + $(PYTHON) -m pip install --upgrade setuptools wheel build + $(PYTHON) -m build +# Tools Installation + +check_pip_install = $(PYTHON) -m pip show $(1) &>/dev/null || (cd && $(PYTHON) -m pip install $(1) --upgrade) +check_pip_install_extra = $(PYTHON) -m pip show $(1) &>/dev/null || (cd && $(PYTHON) -m pip install $(2) --upgrade) + +pylint-install: + $(call check_pip_install,pylint) flake8-install: - $(call check_install, flake8) - $(call check_install_extra, bugbear, flake8_bugbear) + $(call check_pip_install,flake8) + $(call check_pip_install_extra,bugbear,flake8_bugbear) py-format-install: - $(call check_install, isort) - $(call check_install, yapf) + $(call check_pip_install,isort) + $(call check_pip_install,black) mypy-install: - $(call check_install, mypy) + $(call check_pip_install,mypy) + +pre-commit-install: + $(call check_pip_install,pre-commit) + $(PYTHON) -m pre_commit install --install-hooks + +docs-install: + $(call check_pip_install,pydocstyle) + $(call check_pip_install,doc8) + $(call check_pip_install,sphinx) + $(call check_pip_install,sphinx-rtd-theme) + $(call check_pip_install,sphinx-autoapi) + $(call check_pip_install,sphinx-autobuild) + $(call check_pip_install,sphinx-copybutton) + $(call check_pip_install,sphinxcontrib-katex) + $(call check_pip_install,sphinxcontrib-bibtex) + $(call check_pip_install,sphinx-autodoc-typehints) + $(call check_pip_install,myst_nb) + $(call check_pip_install_extra,sphinxcontrib.spelling,sphinxcontrib.spelling pyenchant) + +pytest-install: + $(call check_pip_install,pytest) + $(call check_pip_install,pytest_cov) + $(call check_pip_install,pytest_xdist) cpplint-install: - $(call check_install, cpplint) + $(call check_pip_install,cpplint) clang-format-install: - command -v clang-format-11 || sudo apt-get install -y clang-format-11 + command -v clang-format || sudo apt-get install -y clang-format clang-tidy-install: command -v clang-tidy || sudo apt-get install -y clang-tidy +go-install: + # requires go >= 1.16 + command -v go || (sudo apt-get install -y golang-1.16 && sudo ln -sf /usr/lib/go-1.16/bin/go /usr/bin/go) + +addlicense-install: go-install + command -v addlicense || go install github.com/google/addlicense@latest + +# Tests -doc-install: - $(call check_install, pydocstyle) - $(call check_install, doc8) - $(call check_install, sphinx) - $(call check_install, sphinx_rtd_theme) - $(call check_install_extra, sphinxcontrib.spelling, sphinxcontrib.spelling pyenchant) +pytest: pytest-install + cd tests && $(PYTHON) -m pytest unit --cov $(PROJECT_PATH) --durations 0 -v --cov-report term-missing --color=yes -# python linter +test: pytest + +# Python linters + +pylint: pylint-install + $(PYTHON) -m pylint $(PROJECT_PATH) flake8: flake8-install - flake8 $(PYTHON_FILES) --count --show-source --statistics + $(PYTHON) -m flake8 $(PYTHON_FILES) --count --select=E9,F63,F7,F82,E225,E251 --show-source --statistics py-format: py-format-install - isort --check $(PYTHON_FILES) && yapf -r -d $(PYTHON_FILES) + $(PYTHON) -m isort --project torchopt --check $(PYTHON_FILES) && \ + $(PYTHON) -m black --check $(PYTHON_FILES) mypy: mypy-install - mypy $(PROJECT_NAME) + $(PYTHON) -m mypy $(PROJECT_PATH) -# c++ linter +pre-commit: pre-commit-install + $(PYTHON) -m pre_commit run --all-files + +# C++ linters cpplint: cpplint-install - cpplint $(CPP_FILES) + $(PYTHON) -m cpplint $(CXX_FILES) clang-format: clang-format-install - clang-format-11 --style=file -i $(CPP_FILES) -n --Werror + clang-format --style=file -i $(CXX_FILES) -n --Werror + +# Documentation + +addlicense: addlicense-install + addlicense -c $(COPYRIGHT) -l apache -y 2022 -check $(SOURCE_FOLDERS) + +docstyle: docs-install + $(PYTHON) -m pydocstyle $(PROJECT_PATH) && doc8 docs && make -C docs html SPHINXOPTS="-W" + +docs: docs-install + $(PYTHON) -m sphinx_autobuild --watch $(PROJECT_PATH) --open-browser docs/source docs/build + +spelling: docs-install + make -C docs spelling SPHINXOPTS="-W" + +clean-docs: + make -C docs clean + +# Utility functions + +lint: flake8 py-format mypy clang-format cpplint docstyle spelling -# documentation +format: py-format-install clang-format-install addlicense-install + $(PYTHON) -m isort --project torchopt $(PYTHON_FILES) + $(PYTHON) -m black $(PYTHON_FILES) + clang-format -style=file -i $(CXX_FILES) + addlicense -c $(COPYRIGHT) -l apache -y 2022 $(SOURCE_FOLDERS) -docstyle: doc-install - pydocstyle $(PROJECT_NAME) && doc8 docs && cd docs && make html SPHINXOPTS="-W" +clean-py: + find . -type f -name '*.py[co]' -delete + find . -depth -type d -name ".mypy_cache" -exec rm -r "{}" + + find . -depth -type d -name ".pytest_cache" -exec rm -r "{}" + -doc: doc-install - cd docs && make html && cd _build/html && python3 -m http.server +clean-build: + rm -rf build/ dist/ + rm -rf *.egg-info .eggs -spelling: doc-install - cd docs && make spelling SPHINXOPTS="-W" +clean: clean-py clean-build clean-docs -doc-clean: - cd docs && make clean +# Build docker images -lint: flake8 py-format clang-format cpplint mypy docstyle spelling +docker-base: + docker build --target base --tag $(PROJECT_NAME):$(COMMIT_HASH) --file Dockerfile . + @echo Successfully build docker image with tag $(PROJECT_NAME):$(COMMIT_HASH) -format: py-format-install clang-format-install - isort $(PYTHON_FILES) - yapf -ir $(PYTHON_FILES) - clang-format-11 -style=file -i $(CPP_FILES) +docker-devel: + docker build --target devel --tag $(PROJECT_NAME)-devel:$(COMMIT_HASH) --file Dockerfile . + @echo Successfully build docker image with tag $(PROJECT_NAME)-devel:$(COMMIT_HASH) +docker: docker-base docker-devel +docker-run-devel: docker-devel + docker run --network=host --gpus=all -v /:/host -h ubuntu -it $(PROJECT_NAME)-devel:$(COMMIT_HASH) diff --git a/README.md b/README.md index 4ceb9de3..c73ae163 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,28 @@ + +
- +
+![Python 3.7+](https://img.shields.io/badge/Python-3.7%2B-brightgreen.svg) +[![PyPI](https://img.shields.io/pypi/v/torchopt?label=PyPI)](https://pypi.org/project/torchopt) +![Status](https://img.shields.io/pypi/status/torchopt?label=Status) +![GitHub Workflow Status](https://img.shields.io/github/workflow/status/metaopt/TorchOpt/Tests?label=tests&logo=github) +[![Documentation Status](https://readthedocs.org/projects/torchopt/badge/?version=latest)](https://torchopt.readthedocs.io/en/latest/?badge=latest) +[![Downloads](https://static.pepy.tech/personalized-badge/torchopt?period=month&left_color=grey&right_color=blue&left_text=Downloads/month)](https://pepy.tech/project/torchopt) +[![GitHub Repo Stars](https://img.shields.io/github/stars/metaopt/torchopt?label=Stars&logo=github&color=brightgreen)](https://github.com/metaopt/torchopt/stargazers) +[![License](https://img.shields.io/github/license/metaopt/TorchOpt?label=License)](#license) + **TorchOpt** is a high-performance optimizer library built upon [PyTorch](https://pytorch.org/) for easy implementation of functional optimization and gradient-based meta-learning. It consists of two main features: -- TorchOpt provides functional optimizer which enables [JAX-like](https://github.com/google/jax) composable functional optimizer for PyTorch. With TorchOpt, one can easily conduct neural network optimization in PyTorch with functional style optimizer, similar to [Optax](https://github.com/deepmind/optax) in JAX. -- With the desgin of functional programing, TorchOpt provides efficient, flexible, and easy-to-implement differentiable optimizer for gradient-based meta-learning research. It largely reduces the efforts required to implement sophisticated meta-learning algorithms. + +- TorchOpt provides functional optimizer which enables [JAX-like](https://github.com/google/jax) composable functional optimizer for PyTorch. With TorchOpt, one can easily conduct neural network optimization in PyTorch with functional style optimizer, similar to [Optax](https://github.com/deepmind/optax) in JAX. +- With the design of functional programing, TorchOpt provides efficient, flexible, and easy-to-implement differentiable optimizer for gradient-based meta-learning research. It largely reduces the efforts required to implement sophisticated meta-learning algorithms. -------------------------------------------------------------------------------- + The README is organized as follows: + - [TorchOpt as Functional Optimizer](#torchopt-as-functional-optimizer) - [Optax-Like API](#optax-like-api) - [PyTorch-Like API](#pytorch-like-api) @@ -20,178 +34,243 @@ The README is organized as follows: - [Visualization](#visualization) - [Installation](#installation) - [Future Plan](#future-plan) +- [Changelog](#changelog) - [The Team](#the-team) +- [Citing TorchOpt](#citing-torchopt) +-------------------------------------------------------------------------------- ## TorchOpt as Functional Optimizer -The desgin of TorchOpt follows the philosophy of functional programming. Aligned with [functorch](https://github.com/pytorch/functorch), users can conduct functional style programing with models, optimizers and training in PyTorch. We use the Adam optimizer as an example in the following illustration. You can also check out the tutorial notebook [Functional Optimizer](./tutorials/1_Functional_Optimizer.ipynb) for more details. + +The design of TorchOpt follows the philosophy of functional programming. Aligned with [`functorch`](https://github.com/pytorch/functorch), users can conduct functional style programing with models, optimizers and training in PyTorch. We use the Adam optimizer as an example in the following illustration. You can also check out the tutorial notebook [Functional Optimizer](tutorials/1_Functional_Optimizer.ipynb) for more details. + ### Optax-Like API -For those users who prefer fully functional programing, we offer Optax-Like API by passing gradients and optimizers states to the optimizer function. We design base class `TorchOpt.Optimizer` that has the same interface as `torch.optim.Optimizer`. Here is an example coupled with functorch: + +For those users who prefer fully functional programing, we offer Optax-Like API by passing gradients and optimizers states to the optimizer function. We design base class `torchopt.Optimizer` that has the same interface as `torch.optim.Optimizer`. Here is an example coupled with `functorch`: + ```python -import torch -from torch import nn -from torch import data -from nn import functional as F import functorch -import TorchOpt +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.data import DataLoader + +import torchopt -class Net(nn.Module):... +class Net(nn.Module): ... -class Loader(data.DataLoader):... +class Loader(DataLoader): ... -net = Net() # init +net = Net() # init loader = Loader() -optimizer = TorchOpt.adam() -func, params = functorch.make_functional(net) # use functorch extract network parameters -opt_state = optimizer.init(params) # init optimizer -xs, ys = next(loader) # get data -pred = func(params, xs) # forward -loss = F.cross_entropy(pred, ys) # compute loss -grad = torch.autograd.grad(loss, params) # compute gradients -updates, opt_state = optimizer.update(grad, opt_state) # get updates -params = TorchOpt.apply_updates(params, updates) # update network parameters +optimizer = torchopt.adam() + +model, params = functorch.make_functional(net) # use functorch extract network parameters +opt_state = optimizer.init(params) # init optimizer + +xs, ys = next(loader) # get data +pred = model(params, xs) # forward +loss = F.cross_entropy(pred, ys) # compute loss + +grads = torch.autograd.grad(loss, params) # compute gradients +updates, opt_state = optimizer.update(grads, opt_state) # get updates +params = torchopt.apply_updates(params, updates) # update network parameters ``` + ### PyTorch-Like API -We also offer origin PyTorch APIs (e.g. `zero_grad()` or `step()`) by warpping our Optax-Like API for traditional PyTorch user: - + +We also offer origin PyTorch APIs (e.g. `zero_grad()` or `step()`) by wrapping our Optax-Like API for traditional PyTorch user: + ```python net = Net() # init loader = Loader() -optimizer = TorchOpt.Adam(net.parameters()) -xs, ys = next(loader) # get data -pred = net(xs) # forward +optimizer = torchopt.Adam(net.parameters()) + +xs, ys = next(loader) # get data +pred = net(xs) # forward loss = F.cross_entropy(pred, ys) # compute loss -optimizer.zero_grad() # zero gradients -loss.backward() # backward -optimizer.step() # step updates + +optimizer.zero_grad() # zero gradients +loss.backward() # backward +optimizer.step() # step updates ``` + ### Differentiable + On top of the same optimization function as `torch.optim`, an important benefit of functional optimizer is that one can implement differentiable optimization easily. This is particularly helpful when the algorithm requires to differentiate through optimization update (such as meta learning practices). We take as the inputs the gradients and optimizer states, use non-in-place operators to compute and output the updates. The processes can be automatically implemented, with the only need from users being to pass the argument `inplace=False` to the functions: + ```python -# get updates +# Get updates updates, opt_state = optimizer.update(grad, opt_state, inplace=False) -# update network parameters -params = TorchOpt.apply_updates(params, updates, inplace=False) +# Update network parameters +params = torchopt.apply_updates(params, updates, inplace=False) ``` + +-------------------------------------------------------------------------------- + ## TorchOpt as Differentiable Optimizer for Meta-Learning -Meta-Learning has gained enormous attention in both Supervised Learning and Reinforcement Learning. Meta-Learning algorithms often contain a bi-level optimisation process with *inner loop* updating the network parameters and *outer loop* updating meta parameters. The figure below illustrates the basic formulation for meta-optimization in Meta-Learning. The main feature is that the gradients of *outer loss* will back-propagate through all `inner.step` operations. + +Meta-Learning has gained enormous attention in both Supervised Learning and Reinforcement Learning. Meta-Learning algorithms often contain a bi-level optimization process with *inner loop* updating the network parameters and *outer loop* updating meta parameters. The figure below illustrates the basic formulation for meta-optimization in Meta-Learning. The main feature is that the gradients of *outer loss* will back-propagate through all `inner.step` operations. +
- +
-Since network parameters become a node of computation graph, a flexible Meta-Learning library should enable users manually control the gradient graph connection which means that users should have access to the network parameters and optimizer states for manually detaching or connecting the computation graph. In PyTorch designing, the network parameters or optimizer states are members of network (a.k.a. `nn.Module`) or optimizer (a.k.a. `optim.Optimizer`), this design significantly introducing difficulty for user control network parameters or optimizer states. Previous differentiable optimizer Repo [higher](https://github.com/facebookresearch/higher), [learn2learn](https://github.com/learnables/learn2learn) follows the PyTorch designing which leads to inflexible API. +Since network parameters become a node of computation graph, a flexible Meta-Learning library should enable users manually control the gradient graph connection which means that users should have access to the network parameters and optimizer states for manually detaching or connecting the computation graph. In PyTorch designing, the network parameters or optimizer states are members of network (a.k.a. `torch.nn.Module`) or optimizer (a.k.a. `torch.optim.Optimizer`), this design significantly introducing difficulty for user control network parameters or optimizer states. Previous differentiable optimizer Repo [`higher`](https://github.com/facebookresearch/higher), [`learn2learn`](https://github.com/learnables/learn2learn) follows the PyTorch designing which leads to inflexible API. In contrast to them, TorchOpt realizes differentiable optimizer with functional programing, where Meta-Learning researchers could control the network parameters or optimizer states as normal variables (a.k.a. `torch.Tensor`). This functional optimizer design of TorchOpt is beneficial for implementing complex gradient flow Meta-Learning algorithms and allow us to improve computational efficiency by using techniques like operator fusion. - - ### Meta-Learning API - -- We design a base class `TorchOpt.MetaOptimizer` for managing network updates in Meta-Learning. The constructor of `MetaOptimizer` takes as input the network rather than network parameters. `MetaOptimizer` exposed interface `step(loss)` takes as input the loss for step the network parameter. Refer to the tutorial notebook [Meta Optimizer](./tutorials/2_Meta_Optimizer.ipynb) for more details. -- We offer `TorchOpt.chain` which can apply a list of chainable update transformations. Combined with `MetaOptimizer`, it can help you conduct gradient transformation such as gradient clip before the Meta optimizer steps. Refer to the tutorial notebook [Meta Optimizer](./tutorials/2_Meta_Optimizer.ipynb) for more details. + +- We design a base class `torchopt.MetaOptimizer` for managing network updates in Meta-Learning. The constructor of `MetaOptimizer` takes as input the network rather than network parameters. `MetaOptimizer` exposed interface `step(loss)` takes as input the loss for step the network parameter. Refer to the tutorial notebook [Meta Optimizer](tutorials/3_Meta_Optimizer.ipynb) for more details. +- We offer `torchopt.chain` which can apply a list of chainable update transformations. Combined with `MetaOptimizer`, it can help you conduct gradient transformation such as gradient clip before the Meta optimizer steps. Refer to the tutorial notebook [Meta Optimizer](tutorials/3_Meta_Optimizer.ipynb) for more details. - We observe that different Meta-Learning algorithms vary in inner-loop parameter recovery. TorchOpt provides basic functions for users to extract or recover network parameters and optimizer states anytime anywhere they want. -- Some algorithms such as [MGRL](https://proceedings.neurips.cc/paper/2018/file/2715518c875999308842e3455eda2fe3-Paper.pdf) initialize the inner-loop parameters inherited from previous inner-loop process when conducting a new bi-level process. TorchOpt also provides a finer function `stop_gradient` for manipulating the gradient graph, which is helpful for this kind of algortihms. Refer to the notebook [Stop Gradient](./tutorials/4_Stop_Gradient.ipynb) for more details. +- Some algorithms such as MGRL ([arXiv:1805.09801](https://arxiv.org/abs/1805.09801)) initialize the inner-loop parameters inherited from previous inner-loop process when conducting a new bi-level process. TorchOpt also provides a finer function `stop_gradient` for manipulating the gradient graph, which is helpful for this kind of algorithms. Refer to the notebook [Stop Gradient](tutorials/4_Stop_Gradient.ipynb) for more details. -We give an example of [MAML](https://arxiv.org/abs/1703.03400) with inner-loop Adam optimizer to illustrate TorchOpt APIs: +We give an example of MAML ([arXiv:1703.03400](https://arxiv.org/abs/1703.03400)) with inner-loop Adam optimizer to illustrate TorchOpt APIs: ```python -net = Net() # init -# the constructor `MetaOptimizer` takes as input the network -inner_optim = TorchOpt.MetaAdam(net) -outer_optim = TorchOpt.Adam(net.parameters()) +net = Net() # init + +# The constructor `MetaOptimizer` takes as input the network +inner_optim = torchopt.MetaAdam(net) +outer_optim = torchopt.Adam(net.parameters()) for train_iter in range(train_iters): outer_loss = 0 for task in range(tasks): loader = Loader(tasks) - - # store states at the inital points - net_state = TorchOpt.extract_state_dict(net) # extract state - optim_state = TorchOpt.extract_state_dict(inner_optim) + + # Store states at the initial points + net_state = torchopt.extract_state_dict(net) # extract state + optim_state = torchopt.extract_state_dict(inner_optim) for inner_iter in range(inner_iters): - # compute inner loss and perform inner update + # Compute inner loss and perform inner update xs, ys = next(loader) pred = net(xs) - inner_loss = F.cross_entropy(pred, ys) + inner_loss = F.cross_entropy(pred, ys) inner_optim.step(inner_loss) - # compute outer loss and back-propagate - xs, ys = next(loader) + + # Compute outer loss and back-propagate + xs, ys = next(loader) pred = net(xs) - outer_loss += F.cross_entropy(pred, ys) - - # recover network and optimizer states at the inital point for the next task - TorchOpt.recover_state_dict(inner_optim, optim_state) - TorchOpt.recover_state_dict(net, net_state) - - outer_loss /= len(tasks) # task average + outer_loss = outer_loss + F.cross_entropy(pred, ys) + + # Recover network and optimizer states at the initial point for the next task + torchopt.recover_state_dict(inner_optim, optim_state) + torchopt.recover_state_dict(net, net_state) + + outer_loss = outer_loss / len(tasks) # task average outer_optim.zero_grad() outer_loss.backward() outer_optim.step() - # stop gradient if necessary - TorchOpt.stop_gradient(net) - TorchOpt.stop_gradient(inner_optim) + # Stop gradient if necessary + torchopt.stop_gradient(net) + torchopt.stop_gradient(inner_optim) ``` + +-------------------------------------------------------------------------------- + ## Examples -In *examples/*, we offer serveral examples of functional optimizer and 5 light-weight meta-learning examples with TorchOpt. The meta-learning examples covers 2 Supervised Learning and 3 Reinforcement Learning algorithms. -- [Model Agnostic Meta Learning (MAML)-Supervised Learning](https://arxiv.org/abs/1703.03400) (ICML2017) -- [Learning to Reweight Examples for Robust Deep Learning](https://arxiv.org/pdf/1803.09050.pdf) (ICML2018) -- [Model Agnostic Meta Learning (MAML)-Reinforcement Learning](https://arxiv.org/abs/1703.03400) (ICML2017) -- [Meta Gradient Reinforcement Learning (MGRL)](https://proceedings.neurips.cc/paper/2018/file/2715518c875999308842e3455eda2fe3-Paper.pdf) (NeurIPS 2018) + +In [`examples`](examples), we offer several examples of functional optimizer and 5 light-weight meta-learning examples with TorchOpt. The meta-learning examples covers 2 Supervised Learning and 3 Reinforcement Learning algorithms. + +- [Model Agnostic Meta Learning (MAML) - Supervised Learning](https://arxiv.org/abs/1703.03400) (ICML2017) +- [Learning to Reweight Examples for Robust Deep Learning](https://arxiv.org/abs/1803.09050) (ICML2018) +- [Model Agnostic Meta Learning (MAML) - Reinforcement Learning](https://arxiv.org/abs/1703.03400) (ICML2017) +- [Meta Gradient Reinforcement Learning (MGRL)](https://arxiv.org/abs/1805.09801) (NeurIPS 2018) - [Learning through opponent learning process (LOLA)](https://arxiv.org/abs/1709.04326) (AAMAS 2018) +-------------------------------------------------------------------------------- + ## High-Performance + One can think of the scale procedures on gradients of optimizer algorithms as a combination of several operations. For example, the implementation of the Adam algorithm often includes addition, multiplication, power and square operations, one can fuse these operations into several compound functions. The operator fusion could greatly simplify the computation graph and reduce the GPU function launching stall. In addition, one can also implement the optimizer backward function and manually reuse some intermediate tensors to improve the backward performance. Users can pass argument `use_accelerated_op=True` to `adam`, `Adam` and `MetaAdam` to enable the fused accelerated operator. The arguments are the same between the two kinds of implementations. -Here we evaluate the performance using the maml-omniglot code with the inner-loop Adam optimizer on GPU. We comparble the run time of the overall algorithm and the meta-optimization (outer-loop optimization) under different network architecture/inner-step numbers. We choose [higher](https://github.com/facebookresearch/higher) as our baseline. The figure below illustrate that our accelerated Adam can achieve at least 1/3 efficiency improvement over the baseline. +Here we evaluate the performance using the MAML-Omniglot code with the inner-loop Adam optimizer on GPU. We comparable the run time of the overall algorithm and the meta-optimization (outer-loop optimization) under different network architecture/inner-step numbers. We choose [`higher`](https://github.com/facebookresearch/higher) as our baseline. The figure below illustrate that our accelerated Adam can achieve at least $1/3$ efficiency improvement over the baseline. +
- +
Notably, the operator fusion not only increases performance but also help simplify the computation graph, which will be discussed in the next section. +-------------------------------------------------------------------------------- + ## Visualization -Complex gradient flow in meta-learning brings in a great challenge for managing the gradient flow and verifying the correctness of it. TorchOpt provides a visualization tool that draw variable (e.g. network parameters or meta parameters) names on the gradient graph for better analyzing. The visualization tool is modified from [torchviz](https://github.com/szagoruyko/pytorchviz). We provide an example using the [visualization code](./examples/visualize.py). Also refer to the notebook [Visualization](./tutorials/3_Visualization.ipynb) for more details. -The figure below show the visulization result. Compared with torchviz, TorchOpt fuses the operations within the Adam together (orange) to reduce the complexity and provide simpler visualization. +Complex gradient flow in meta-learning brings in a great challenge for managing the gradient flow and verifying the correctness of it. TorchOpt provides a visualization tool that draw variable (e.g. network parameters or meta parameters) names on the gradient graph for better analyzing. The visualization tool is modified from [`torchviz`](https://github.com/szagoruyko/pytorchviz). We provide an example using the [visualization code](examples/visualize.py). Also refer to the notebook [Visualization](tutorials/2_Visualization.ipynb) for more details. + +The figure below show the visualization result. Compared with [`torchviz`](https://github.com/szagoruyko/pytorchviz), TorchOpt fuses the operations within the `Adam` together (orange) to reduce the complexity and provide simpler visualization.
- +
+-------------------------------------------------------------------------------- + ## Installation + Requirements - - (Optional) For visualizing computation graphs - - [Graphviz](https://graphviz.org/download/) (for Linux users use `apt/yum install graphviz` or `conda install -c anaconda python-graphviz`) + +- PyTorch +- JAX +- (Optional) For visualizing computation graphs + - [Graphviz](https://graphviz.org/download/) (for Linux users use `apt/yum install graphviz` or `conda install -c anaconda python-graphviz`) + +Please follow the instructions at to install PyTorch in your Python environment first. Then run the following command to install TorchOpt from PyPI ([![PyPI](https://img.shields.io/pypi/v/torchopt?label=PyPI)](https://pypi.org/project/torchopt) / ![Status](https://img.shields.io/pypi/status/torchopt?label=Status)): + ```bash -pip install TorchOpt +pip3 install torchopt ``` You can also build shared libraries from source, use: + ```bash -git clone git@github.com:metaopt/TorchOpt.git +git clone https://github.com/metaopt/TorchOpt.git cd TorchOpt -python setup.py build_from_source +pip3 install . ``` + +We provide a [conda](https://github.com/conda/conda) environment recipe to install the build toolchain such as `cmake`, `g++`, and `nvcc`: + +```bash +git clone https://github.com/metaopt/TorchOpt.git +cd TorchOpt + +# You may need `CONDA_OVERRIDE_CUDA` if conda fails to detect the NVIDIA driver (e.g. in docker or WSL2) +CONDA_OVERRIDE_CUDA=11.7 conda env create --file conda-recipe.yaml + +conda activate torchopt +pip3 install -e . +``` + +-------------------------------------------------------------------------------- + ## Future Plan + - [ ] Support general implicit differentiation with functional programing. -- [ ] Support more optimizers such as AdamW, RMSPROP -- [ ] CPU-acclerated optimizer +- [ ] Support more optimizers such as AdamW, RMSProp +- [ ] CPU-accelerated optimizer + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md). + +-------------------------------------------------------------------------------- ## The Team -TorchOpt is a work by Jie Ren, Xidong Feng, [Bo Liu](https://github.com/Benjamin-eecs/), [Luo Mai](https://luomai.github.io/) and [Yaodong Yang](https://www.yangyaodong.com/). + +TorchOpt is a work by Jie Ren, Xidong Feng, [Bo Liu](https://github.com/Benjamin-eecs), [Xuehai Pan](https://github.com/XuehaiPan), [Luo Mai](https://luomai.github.io/) and [Yaodong Yang](https://www.yangyaodong.com/). ## Citing TorchOpt If you find TorchOpt useful, please cite it in your publications. -``` +```bibtex @software{TorchOpt, - author = {Jie Ren and Xidong Feng and Bo Liu and Luo Mai and Yaodong Yang}, + author = {Jie Ren and Xidong Feng and Bo Liu and Xuehai Pan and Luo Mai and Yaodong Yang}, title = {TorchOpt}, year = {2022}, publisher = {GitHub}, diff --git a/TorchOpt/_src/MetaOptimizer.py b/TorchOpt/_src/MetaOptimizer.py deleted file mode 100644 index fa9c541f..00000000 --- a/TorchOpt/_src/MetaOptimizer.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright 2022 MetaOPT Team. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== - -import jax -import torch -from torch import nn - -import TorchOpt -from TorchOpt._src import base -from TorchOpt._src.alias import adam, rmsprop, sgd -from TorchOpt._src.pytypes import ScalarOrSchedule - - -class MetaOptimizer(object): - """A high-level optimizer base class for meta learning.""" - def __init__(self, net: nn.Module, impl: base.GradientTransformation): - """ - Args: - net (nn.Module): a network whose parameters should be optimized. - impl (base.GradientTransformation): a low level optimizer function, it could be a - optimizer function provided by `alias.py` or a customerized `chain` provided by - `combine.py`. Note that use `MetaOptimizer(sgd(moment_requires_grad=True))` or - `MetaOptimizer(chain(sgd(moment_requires_grad=True))) is equavalent to `MetaSGD`. - """ - self.impl = impl - self.param_containers_groups = [] - self.state_groups = [] - self.add_param_group(net) - - def step(self, loss: torch.Tensor): - """Compute the gradients of the loss to the network parameters and update network parameters. - - Graph of the derivative will be constructed, allowing to compute higher order derivative products. - We use the differentiable optimizer (pass argument inplace=False) to scale the gradients and update - the network parameters without modifying tensors in-place. - - Args: - loss (torch.Tensor): the loss that is used to compute the gradients to the network parameters. - """ - # step parameter only - for idx, (state, param_containers) in enumerate( - zip(self.state_groups, self.param_containers_groups)): - flatten_params, containers_tree = jax.tree_util.tree_flatten( - param_containers) - flatten_params = tuple(flatten_params) - grad = torch.autograd.grad(loss, - flatten_params, - create_graph=True, - allow_unused=True) - updates, state = self.impl.update(grad, state, False) - self.state_groups[idx] = state - new_params = TorchOpt.apply_updates(flatten_params, - updates, - inplace=False) - unflatten_new_params = containers_tree.unflatten(new_params) - for (container, unflatten_param) in zip(param_containers, - unflatten_new_params): - container.update(unflatten_param) - - def add_param_group(self, net): - from .utils import _extract_container - net_container = _extract_container(net, with_buffer=False) - flatten_param, _ = jax.tree_util.tree_flatten(net_container) - flatten_param = tuple(flatten_param) - optim_state = self.impl.init(flatten_param) - self.state_groups.append(optim_state) - self.param_containers_groups.append(net_container) - - def state_dict(self): - """Extract the references of the optimizer states. - - Note that the states are references, so any in-place operations will - change the states inside `MetaOptimizer` at the same time. - """ - out_groups = tuple(group for group in self.state_groups) - return out_groups - - def load_state_dict(self, state_dict): - self.state_groups = list(group for group in state_dict) - - -class MetaSGD(MetaOptimizer): - """A canonical Stochastic Gradient Descent optimiser.""" - def __init__(self, - net, - lr: ScalarOrSchedule, - momentum: float = None, - nesterov: bool = False, - moment_requires_grad: bool = True): - """ - Args: - net (nn.Module): a network whose parameters should be optimized. - args: other arguments see `alias.sgd`, here we set `moment_requires_grad=True` - to make tensors like momentum be differentiable. - """ - super().__init__( - net, - sgd(lr=lr, - momentum=momentum, - nesterov=nesterov, - moment_requires_grad=moment_requires_grad)) - - -class MetaAdam(MetaOptimizer): - """The classic Adam optimiser.""" - def __init__(self, - net, - lr: ScalarOrSchedule, - b1: float = 0.9, - b2: float = 0.999, - eps: float = 1e-8, - eps_root: float = 0.0, - moment_requires_grad: bool = True, - use_accelerated_op: bool = False): - """ - Args: - net (nn.Module): a network whose parameters should be optimized. - args: other arguments see `alias.adam`, here we set `moment_requires_grad=True` - to make tensors like momentum be differentiable. - """ - super().__init__( - net, - adam(lr=lr, - b1=b1, - b2=b2, - eps=eps, - eps_root=eps_root, - moment_requires_grad=moment_requires_grad, - use_accelerated_op=use_accelerated_op)) - - -class MetaRMSProp(MetaOptimizer): - """The classic RMSProp optimiser.""" - def __init__(self, - net, - lr: ScalarOrSchedule, - decay: float = 0.9, - eps: float = 1e-8, - initial_scale: float = 0., - centered: bool = False, - momentum: float = None, - nesterov: bool = False): - """ - Args: - net (nn.Module): a network whose parameters should be optimized. - args: other arguments see `alias.adam`, here we set `moment_requires_grad=True` - to make tensors like momentum be differentiable. - """ - super().__init__( - net, - rmsprop(lr=lr, - decay=decay, - eps=eps, - initial_scale=initial_scale, - centered=centered, - momentum=momentum, - nesterov=nesterov)) diff --git a/TorchOpt/_src/Optimizer.py b/TorchOpt/_src/Optimizer.py deleted file mode 100644 index d825118f..00000000 --- a/TorchOpt/_src/Optimizer.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright 2022 MetaOPT Team. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== - -import jax -import torch - -from TorchOpt._src.alias import adam, rmsprop, sgd -from TorchOpt._src.pytypes import ScalarOrSchedule -from TorchOpt._src.update import apply_updates - - -class Optimizer(object): - """A high-level base class that has the similar with `torch.optim.Optimier`""" - def __init__(self, params, impl): - """ - Args: - params (iterable): an iterable of `torch.Tensor`s. Specifies what Tensors should be optimized. - impl (base.GradientTransformation): a low level optimizer function, it could be - a optimizer function provided by `alias.py` or a customerized `chain` provided by - `combine.py`. Note that use `MetaOptimizer(sgd())` or `MetaOptimizer(chain(sgd())) - is equavalent to `SGD`. - - """ - if not isinstance(params, list): - params = list(params) - self.impl = impl - self.param_groups = [] - self.param_tree_groups = [] - self.state_groups = [] - self.add_param_group(params) - - def zero_grad(self, set_to_none: bool = False): - """Sets the gradients of all optimized `torch.Tensor`s to zero. - - The behivour is similar to `torch.optim.Optimizer.zero_grad`. - - Args: - set_to_none (bool): instead of setting to zero, set the grads to None. - - """ - for group in self.param_groups: - if set_to_none: - - def f(p): - p.grad = None - return None - else: - - def f(p): - if p.grad is None: - return None - if p.grad.grad_fn is not None: - p.grad.detach_() - else: - p.grad.requires_grad_(False) - p.grad.zero_() - return None - - jax.tree_map(f, group) - - def state_dict(self): - return self.state_groups - - def load_state_dict(self, state_dict): - self.state_groups = state_dict - - def step(self, closure=None): - """Performs a single optimization step (parameter update). - - The behivour is similar to `torch.optim.Optimizer.step`. - - Args: - closure (callable, optional): A closure that reevaluates the model and returns the loss. - - """ - loss = None - if closure is not None: - with torch.enable_grad(): - loss = closure() - - for param, state in zip(self.param_groups, self.state_groups): - - def f(p): - return p.grad - - grad = jax.tree_map(f, param) - updates, _ = self.impl.update(grad, state) - apply_updates(param, updates) - - return loss - - def add_param_group(self, params): - params, tree = jax.tree_flatten(params) - params = tuple(params) - self.param_groups.append(params) - self.param_tree_groups.append(tree) - self.state_groups.append(self.impl.init(params)) - - -class SGD(Optimizer): - """The classic Adam optimiser.""" - def __init__(self, - params, - lr: ScalarOrSchedule, - momentum: float = None, - nesterov: bool = False): - """ - Args: - params (iterable): an iterable of `torch.Tensor`s. Specifies what Tensors should be optimized. - args: other arguments see `alias.adam`. - """ - super().__init__( - params, - sgd(lr=lr, - momentum=momentum, - nesterov=nesterov, - moment_requires_grad=False)) - - -class Adam(Optimizer): - """A canonical Stochastic Gradient Descent optimiser.""" - def __init__(self, - params, - lr: ScalarOrSchedule, - b1: float = 0.9, - b2: float = 0.999, - eps: float = 1e-8, - eps_root: float = 0.0, - use_accelerated_op: bool = False): - """ - Args: - params (iterable): an iterable of `torch.Tensor`s. Specifies what Tensors should be optimized. - args: other arguments see `alias.sgd`. - """ - super().__init__( - params, - adam(lr=lr, - b1=b1, - b2=b2, - eps=eps, - eps_root=eps_root, - moment_requires_grad=False, - use_accelerated_op=use_accelerated_op)) - - -class RMSProp(Optimizer): - """An RMSProp optimiser.""" - def __init__(self, - params, - lr: ScalarOrSchedule, - decay: float = 0.9, - eps: float = 1e-8, - initial_scale: float = 0., - centered: bool = False, - momentum: float = None, - nesterov: bool = False): - """ - Args: - params (iterable): an iterable of `torch.Tensor`s. Specifies what Tensors should be optimized. - args: other arguments see `alias.sgd`. - """ - super().__init__( - params, - rmsprop(lr=lr, - decay=decay, - eps=eps, - initial_scale=initial_scale, - centered=centered, - momentum=momentum, - nesterov=nesterov)) diff --git a/TorchOpt/_src/alias.py b/TorchOpt/_src/alias.py deleted file mode 100644 index a34ea4dc..00000000 --- a/TorchOpt/_src/alias.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright 2022 MetaOPT Team. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -# This file is modified from: -# https://github.com/deepmind/optax/blob/master/optax/_src/alias.py -# ============================================================================== -# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== - -from typing import Optional - -import jax - -from TorchOpt._src import base, combine, transform -from TorchOpt._src.pytypes import ScalarOrSchedule - - -def _scale_by_lr(lr: ScalarOrSchedule, flip_sign=True): - m = -1 if flip_sign else 1 - if callable(lr): - - def schedule_wrapper(count): - def f(scaled_lr): - return m * scaled_lr - - return jax.tree_map(f, lr(count)) - - return transform.scale_by_schedule(schedule_wrapper) - return transform.scale(m * lr) - - -def adam(lr: ScalarOrSchedule, - b1: float = 0.9, - b2: float = 0.999, - eps: float = 1e-8, - eps_root: float = 0.0, - moment_requires_grad: bool = False, - use_accelerated_op: bool = False) -> base.GradientTransformation: - """The classic Adam optimiser. - - Adam is an SGD variant with learning rate adaptation. The `lr` - used for each weight is computed from estimates of first- and second-order - moments of the gradients (using suitable exponential moving averages). - - References: - Kingma et al, 2014: https://arxiv.org/abs/1412.6980 - - Args: - lr: this is a fixed global scaling factor. - b1: the exponential decay rate to track the first moment of past gradients. - b2: the exponential decay rate to track the second moment of past gradients. - eps: a small constant applied to denominator outside of the square root - (as in the Adam paper) to avoid dividing by zero when rescaling. - eps_root: (default `0`), a small constant applied to denominator inside the - square root (as in RMSProp), to avoid dividing by zero when rescaling. - This is needed for example when computing (meta-)gradients through Adam. - moment_requires_grad: (default `False`), if True the momentums will be created with flag - `requires_grad=True`, this flag is often used in Meta Learning algorithms. - use_accelerated_op: (default `False`), if True use our implemented fused operator. - - Returns: - the corresponding `GradientTransformation`. - """ - adam_inst = transform.scale_by_accelerated_adam if use_accelerated_op else transform.scale_by_adam - return combine.chain( - adam_inst(b1=b1, - b2=b2, - eps=eps, - eps_root=eps_root, - moment_requires_grad=moment_requires_grad), - _scale_by_lr(lr), - ) - - -def sgd( - lr: ScalarOrSchedule, - momentum: Optional[float] = None, - nesterov: bool = False, - moment_requires_grad: bool = False, -) -> base.GradientTransformation: - """A canonical Stochastic Gradient Descent optimiser. - - This implements stochastic gradient descent. It also includes support for - momentum, and nesterov acceleration, as these are standard practice when - using stochastic gradient descent to train deep neural networks. - - References: - Sutskever et al, 2013: http://proceedings.mlr.press/v28/sutskever13.pdf - - Args: - lr: this is a fixed global scaling factor. - momentum: (default `None`), the `decay` rate used by the momentum term, - when it is set to `None`, then momentum is not used at all. - nesterov (default `False`): whether nesterov momentum is used. - moment_requires_grad: (default `False`), if True the momentums will be created with flag - `requires_grad=True`, this flag is often used in Meta Learning algorithms. - - Returns: - A `GradientTransformation`. - """ - return combine.chain( - (transform.trace(decay=momentum, - nesterov=nesterov, - moment_requires_grad=moment_requires_grad) - if momentum is not None else base.identity()), _scale_by_lr(lr)) - - -def rmsprop(lr: ScalarOrSchedule, - decay: float = 0.9, - eps: float = 1e-8, - initial_scale: float = 0., - centered: bool = False, - momentum: Optional[float] = None, - nesterov: bool = False) -> base.GradientTransformation: - # pylint: disable=line-too-long - """A flexible RMSProp optimiser. - RMSProp is an SGD variant with learning rate adaptation. The `learning_rate` - used for each weight is scaled by a suitable estimate of the magnitude of the - gradients on previous steps. Several variants of RMSProp can be found - in the literature. This alias provides an easy to configure RMSProp - optimiser that can be used to switch between several of these variants. - References: - Tieleman and Hinton, 2012: http://www.cs.toronto.edu/~hinton/coursera/lecture6/lec6.pdf - Graves, 2013: https://arxiv.org/abs/1308.0850 - Args: - learning_rate: this is a fixed global scaling factor. - decay: the decay used to track the magnitude of previous gradients. - eps: a small numerical constant to avoid dividing by zero when rescaling. - initial_scale: (default `0.`), initialisation of accumulators tracking the - magnitude of previous updates. PyTorch uses `0`, TF1 uses `1`. When - reproducing results from a paper, verify the value used by the authors. - centered: (default `False`), whether the second moment or the variance of - the past gradients is used to rescale the latest gradients. - momentum: (default `None`), the `decay` rate used by the momentum term, - when it is set to `None`, then momentum is not used at all. - nesterov (default `False`): whether nesterov momentum is used. - Returns: - the corresponding `GradientTransformation`. - """ - # pylint: enable=line-too-long - if centered: - return combine.chain( - transform.scale_by_stddev(decay=decay, - eps=eps, - initial_scale=initial_scale), - _scale_by_lr(lr), - (transform.trace(decay=momentum, nesterov=nesterov) - if momentum is not None else base.identity())) - return combine.chain( - transform.scale_by_rms(decay=decay, - eps=eps, - initial_scale=initial_scale), _scale_by_lr(lr), - (transform.trace(decay=momentum, nesterov=nesterov) - if momentum is not None else base.identity())) diff --git a/TorchOpt/_src/base.py b/TorchOpt/_src/base.py deleted file mode 100644 index 5b2ad532..00000000 --- a/TorchOpt/_src/base.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright 2022 MetaOPT Team. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -# This file is modified from: -# https://github.com/deepmind/optax/blob/master/optax/_src/base.py -# ============================================================================== -# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== - -from typing import Callable, NamedTuple, Tuple - -import typing_extensions - -from TorchOpt._src import pytypes - -OptState = pytypes.TensorTree # States are arbitrary nests of `torch.Tensor`. -# Parameters are arbitrary nests of `torch.Tensor`. -Params = pytypes.TensorTree -Updates = Params # Gradient updates are of the same type as parameters. - -Schedule = Callable[[pytypes.Numeric], pytypes.Numeric] - - -class EmptyState(NamedTuple): - """An empty state for the simplest stateless transformations.""" - - -class TransformInitFn(typing_extensions.Protocol): - """A callable type for the `init` step of a `GradientTransformation`. - - The `init` step takes a tree of `params` and uses these to construct an - arbitrary structured initial `state` for the gradient transformation. This - may hold statistics of the past updates or any other non static information. - """ - def __call__(self, params: Params) -> OptState: - """The `init` function. - - Args: - params: The initial value of the parameters. - - Returns: - The initial state of the gradient transformation. - """ - ... - - -class TransformUpdateFn(typing_extensions.Protocol): - """A callable type for the `update` step of a `GradientTransformation`. - - The `update` step takes a tree of candidate parameter `updates` (e.g. their - gradient with respect to some loss), an arbitrary structured `state`, and the - current `params` of the model being optimised. The `params` argument is - optional, it must however be provided when using transformations that require - access to the current values of the parameters. - """ - def __call__(self, - updates: Updates, - state: OptState, - inplace: bool = True) -> Tuple[Updates, OptState]: - """The `update` function. - - Args: - updates: A tree of candidate updates. - state: The state of the gradient transformation. - inplace: (Optionally) if true, modify updates and state using inplace operations. - - Returns: - The transformed updates, and the updated state. - """ - ... - - -class GradientTransformation(NamedTuple): - """A pair of pure functions implementing a gradient transformation. - - TorchOpt optimizers are all implemented as _gradient transformations_ like - Optax. A gradient transformation is defined to be a pair of pure functions, - which are combined together in a `NamedTuple` so that they can be referred - to by name. - - Since gradient transformations do not contain any internal state, all stateful - optimizer properties (such as the current step count when using optimizer - scheduels, or momemtum values) are passed through gradient transformations by - using the optimizer _state_ pytree. Each time a gradient transformation is - applied, the state is computed and returned, ready to be passed to the next - call to the gradient transformation. - - Attributes: - init: A pure function which, when called with an example instance of the - parameters whose gradients will be transformed, returns a pytree - containing the initial value for the optimizer state. - update: A pure function which takes as input a pytree of updates (with the - same tree structure as the original params pytree passed to init), the - previous optimizer state (which may have been initialized using the init - function), and optionally the inplace flag. The update function then - returns the computed gradient updates, and a updates optimizer state. - If the inplace flag is true, the output results are the same instance as - the input. - """ - init: TransformInitFn - update: TransformUpdateFn - - -def identity() -> GradientTransformation: - """Stateless identity transformation that leaves input gradients untouched. - - This function passes through the *gradient updates* unchanged. - - Returns: - An (init_fn, update_fn) tuple. - """ - def init_fn(_): - return EmptyState() - - def update_fn(updates, state, inplace=False): - return updates, state - - return GradientTransformation(init_fn, update_fn) diff --git a/TorchOpt/_src/pytypes.py b/TorchOpt/_src/pytypes.py deleted file mode 100644 index ca14c319..00000000 --- a/TorchOpt/_src/pytypes.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Any, Callable, Iterable, Mapping, Union - -from torch import Tensor - -Scalar = Union[float, int] -Numeric = Union[Tensor, Scalar] -TensorTree = Union[Tensor, Iterable['TensorTree'], Mapping[Any, 'TensorTree']] - -Schedule = Callable[[Numeric], Numeric] -ScalarOrSchedule = Union[float, Schedule] diff --git a/conda-recipe.yaml b/conda-recipe.yaml new file mode 100644 index 00000000..3c10a3ed --- /dev/null +++ b/conda-recipe.yaml @@ -0,0 +1,84 @@ +# Create virtual environment with command: +# +# $ CONDA_OVERRIDE_CUDA=11.7 conda env create --file conda-recipe.yaml +# + +name: torchopt + +channels: + - pytorch + - defaults + - nvidia/label/cuda-11.6.2 + - nvidia + - conda-forge + +dependencies: + - python = 3.8 + - pip + + # Learning + - pytorch::pytorch = 1.12 + - pytorch::torchvision + - pytorch::pytorch-mutex = *=*cuda* + - pip: + - functorch + - torchviz + - sphinxcontrib-katex # for documentation + - jax + - jaxlib >= 0.3=*cuda* + - optax # for tutorials + - tensorboard # for examples + - wandb + + # Device select + - nvidia::cudatoolkit = 11.6 + - cudnn + + # Build toolchain + - cmake >= 3.4 + - make + - cxx-compiler + - gxx = 10 + - nvidia/label/cuda-11.6.2::cuda-nvcc + - nvidia/label/cuda-11.6.2::cuda-cudart-dev + - patchelf >= 0.9 + - pybind11 + + # Misc + - typing-extensions + - numpy + - matplotlib-base + - seaborn + - python-graphviz + - pillow + + # Documentation + - sphinx + - sphinx_rtd_theme + - sphinx-autobuild + - sphinx-copybutton + - sphinxcontrib-spelling + - sphinxcontrib-bibtex + - sphinx-autodoc-typehints + - pyenchant + - myst-nb + - ipykernel + - pandoc + - docutils + + # Testing + - pytest + - pytest-cov + - pytest-xdist + - isort + - conda-forge::black >= 22.6.0 + - pylint + - mypy + - flake8 + - flake8-bugbear + - doc8 + - pydocstyle + - clang-format + - clang-tools # clang-tidy + - cpplint + - pre-commit diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d0c3cbf1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conda-recipe.yaml b/docs/conda-recipe.yaml new file mode 100644 index 00000000..d55c0f19 --- /dev/null +++ b/docs/conda-recipe.yaml @@ -0,0 +1,73 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +# +# Create virtual environment with command: +# +# $ CONDA_OVERRIDE_CUDA=11.7 conda env create --file docs/conda-recipe.yaml +# + +name: torchopt-docs + +channels: + - pytorch + - defaults + - conda-forge + +dependencies: + - python = 3.8 + - pip + + # Learning + - pytorch::pytorch = 1.12 + - pytorch::torchvision + - pytorch::pytorch-mutex = *=*cpu* + - pip: + - jax[cpu] >= 0.3 + - functorch + - torchviz + - sphinxcontrib-katex # for documentation + - tensorboard + - wandb + + # Build toolchain + - cmake >= 3.4 + - make + - cxx-compiler + - gxx = 10 + - nvidia/label/cuda-11.6.2::cuda-nvcc + - nvidia/label/cuda-11.6.2::cuda-cudart-dev + - pybind11 + + # Misc + - typing-extensions + - numpy + - matplotlib-base + - seaborn + - python-graphviz + - pillow + + # Documentation + - sphinx + - sphinx_rtd_theme + - sphinx-autobuild + - sphinx-copybutton + - sphinxcontrib-spelling + - sphinxcontrib-bibtex + - sphinx-autodoc-typehints + - pyenchant + - myst-nb + - ipykernel + - pandoc + - docutils diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..61b877af --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,21 @@ +--extra-index-url https://download.pytorch.org/whl/cu116 +torch == 1.12 +torchvision +functorch + +--requirement ../requirements.txt + +sphinx >= 5.0 +sphinx-autoapi +sphinx-autobuild +sphinx-copybutton +sphinx-rtd-theme +sphinxcontrib-katex +sphinxcontrib-bibtex +sphinx-autodoc-typehints +IPython +ipykernel +pandoc +myst_nb +docutils +matplotlib diff --git a/docs/source/_static/css/style.css b/docs/source/_static/css/style.css new file mode 100644 index 00000000..df73d696 --- /dev/null +++ b/docs/source/_static/css/style.css @@ -0,0 +1,170 @@ +/** + * Copyright 2022 MetaOPT Team. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +body { + font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; +} + +/* Default header fonts are ugly */ +h1, +h2, +.rst-content .toctree-wrapper p.caption, +h3, +h4, +h5, +h6, +legend, +p.caption { + font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; +} + +/* Use white for docs background */ +.wy-side-nav-search { + background-color: #fff; +} + +.wy-nav-content { + max-width: 1200px !important; +} + +.wy-nav-content-wrap, +.wy-menu li.current>a { + background-color: #fff; +} + +.wy-side-nav-search>a img.logo { + width: 100%; + margin-top: 10px; +} + +@media screen and (min-width: 1400px) { + .wy-nav-content-wrap { + background-color: #fff; + } + + .wy-nav-content { + background-color: #fff; + } +} + +/* Fixes for mobile */ +.wy-nav-top { + background-color: #fff; + background-repeat: no-repeat; + background-position: center; + padding: 0; + margin: 0.4045em 0.809em; + color: #333; +} + +.wy-nav-top>a { + display: none; +} + +@media screen and (max-width: 768px) { + .wy-side-nav-search>a img.logo { + height: 60px; + } +} + +/* This is needed to ensure that logo above search scales properly */ +.wy-side-nav-search a { + display: block; +} + +/* This ensures that multiple constructors will remain in separate lines. */ +.rst-content dl:not(.docutils) dt { + display: table; +} + +/* Use our red for literals (it's very similar to the original color) */ +.rst-content tt.literal, +.rst-content tt.literal, +.rst-content code.literal { + color: #4692BC; +} + +.rst-content tt.xref, +a .rst-content tt, +.rst-content tt.xref, +.rst-content code.xref, +a .rst-content tt, +a .rst-content code { + color: #404040; +} + +/* Change link colors (except for the menu) */ + +a { + color: #4692BC; +} + +a:hover { + color: #4692BC; +} + + +a:visited { + color: #4692BC; +} + +.wy-menu a { + color: #b3b3b3; +} + +.wy-menu a:hover { + color: #b3b3b3; +} + +/* Default footer text is quite big */ +footer { + font-size: 80%; +} + +footer .rst-footer-buttons { + font-size: 125%; + /* revert footer settings - 1/80% = 125% */ +} + +footer p { + font-size: 100%; +} + +.ethical-rtd { + display: none; +} + +.ethical-fixedfooter { + display: none; +} + +.ethical-content { + display: none; +} + +/* For hidden headers that appear in TOC tree */ +/* see http://stackoverflow.com/a/32363545/3343043 */ +.rst-content .hidden-section { + display: none; +} + +nav .hidden-section { + display: inherit; +} + +.wy-side-nav-search>div.version { + color: #000; +} diff --git a/image/logod-07.png b/docs/source/_static/images/logo-large.png similarity index 100% rename from image/logod-07.png rename to docs/source/_static/images/logo-large.png diff --git a/docs/source/_static/images/logo-torchopt.pdf b/docs/source/_static/images/logo-torchopt.pdf new file mode 100644 index 00000000..5e1cbdab Binary files /dev/null and b/docs/source/_static/images/logo-torchopt.pdf differ diff --git a/image/logod-05.png b/docs/source/_static/images/logo.png similarity index 100% rename from image/logod-05.png rename to docs/source/_static/images/logo.png diff --git a/docs/source/_static/images/maml-accs.png b/docs/source/_static/images/maml-accs.png new file mode 100644 index 00000000..a3a0f4ce Binary files /dev/null and b/docs/source/_static/images/maml-accs.png differ diff --git a/docs/source/api/api.rst b/docs/source/api/api.rst new file mode 100644 index 00000000..44da5b93 --- /dev/null +++ b/docs/source/api/api.rst @@ -0,0 +1,224 @@ +TorchOpt Optimizer +================== + +.. currentmodule:: torchopt + +.. autosummary:: + + Optimizer + MetaOptimizer + +Optimizer +~~~~~~~~~ + +.. autoclass:: Optimizer + :members: + +MetaOptimizer +~~~~~~~~~~~~~ + +.. autoclass:: MetaOptimizer + :members: + +------ + +Functional Optimizers +===================== + +.. currentmodule:: torchopt + +.. autosummary:: + + adam + sgd + rmsprop + +Functional Adam Optimizer +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: adam + +Functional SGD Optimizer +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: sgd + +Functional RMSProp Optimizer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: rmsprop + +------ + +Classic Optimizers +================== + +.. currentmodule:: torchopt + +.. autosummary:: + + Adam + SGD + RMSProp + +Classic Adam Optimizer +~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: Adam + +Classic SGD Optimizer +~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: SGD + +Classic RMSProp Optimizer +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: RMSProp + +------ + +Differentiable Meta-Optimizers +============================== + +.. currentmodule:: torchopt + +.. autosummary:: + + MetaAdam + MetaSGD + MetaRMSProp + +Differentiable Meta-Adam Optimizer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: MetaAdam + +Differentiable Meta-SGD Optimizer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: MetaSGD + +Differentiable Meta-RMSProp Optimizer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: MetaRMSProp + +------ + +Optimizer Hooks +=============== + +.. currentmodule:: torchopt._src.hook + +.. autosummary:: + + register_hook + zero_nan_hook + +Hook +~~~~ + +.. autofunction:: register_hook +.. autofunction:: zero_nan_hook + +Gradient Transformation +======================= + +.. currentmodule:: torchopt._src.clip + +.. autosummary:: + + clip_grad_norm + +Transforms +~~~~~~~~~~ + +.. autofunction:: clip_grad_norm + +Optimizer Schedules +=================== + +.. currentmodule:: torchopt._src.schedule + +.. autosummary:: + + linear_schedule + polynomial_schedule + +Schedules +~~~~~~~~~ + +.. autofunction:: linear_schedule +.. autofunction:: polynomial_schedule + + +Apply Parameter Updates +======================= + +.. currentmodule:: torchopt + +.. autosummary:: + + apply_updates + +Apply Updates +~~~~~~~~~~~~~ + +.. autofunction:: apply_updates + +Combining Optimizers +==================== + +.. currentmodule:: torchopt._src.combine + +.. autosummary:: + + chain + +Chain +~~~~~ + +.. autofunction:: chain + + +General Utilities +================= + +.. currentmodule:: torchopt + +.. autosummary:: + + extract_state_dict + recover_state_dict + stop_gradient + +Extract State Dict +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: extract_state_dict + +Recover State Dict +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: recover_state_dict + +Stop Gradient +~~~~~~~~~~~~~ + +.. autofunction:: stop_gradient + + +Visualizing Gradient Flow +========================= + +.. currentmodule:: torchopt._src.visual + +.. autosummary:: + + make_dot + +Make Dot +~~~~~~~~ + +.. autofunction:: make_dot diff --git a/docs/source/bibtex.json b/docs/source/bibtex.json new file mode 100644 index 00000000..c2aa9165 --- /dev/null +++ b/docs/source/bibtex.json @@ -0,0 +1,7 @@ +{ + "cited": { + "examples/MAML": [ + "MAML", + ] + } +} diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..25159cb0 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,209 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Configuration file for the Sphinx documentation builder.""" +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# pylint: disable=all + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import pathlib +import sys + +import sphinxcontrib.katex as katex + + +HERE = pathlib.Path(__file__).absolute().parent +PROJECT_ROOT = HERE.parent.parent + + +def get_version() -> str: + sys.path.insert(0, str(PROJECT_ROOT / 'torchopt')) + import version # noqa + + return version.__version__ + + +# -- Project information ----------------------------------------------------- + +project = 'TorchOpt' +copyright = '2022 MetaOPT Team' +author = 'TorchOpt Contributors' + +# The full version, including alpha/beta/rc tags +release = get_version() + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.napoleon', + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', + 'sphinx.ext.extlinks', + 'sphinx_copybutton', + 'sphinx_rtd_theme', + 'sphinxcontrib.bibtex', + 'sphinxcontrib.katex', + 'sphinx_autodoc_typehints', + 'myst_nb', # This is used for the .ipynb notebooks +] + +if not os.getenv('READTHEDOCS', None): + extensions.append('sphinxcontrib.spelling') + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'build', 'Thumbs.db', '.DS_Store'] +spelling_exclude_patterns = [''] +spelling_word_list_filename = ['spelling_wordlist.txt'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'default' + +# -- Options for autodoc ----------------------------------------------------- + +autodoc_default_options = { + 'member-order': 'bysource', + 'undoc-members': True, + 'special-members': True, + 'show-inheritance': True, + 'exclude-members': '__module__, __dict__, __repr__, __str__, __weakref__', +} +autoclass_content = 'both' + +# -- Options for bibtex ----------------------------------------------------- + +bibtex_bibfiles = ['references.bib'] + +# -- Options for myst ------------------------------------------------------- + +nb_execution_mode = 'force' +nb_execution_allow_errors = False + +# -- Options for katex ------------------------------------------------------ + +# See: https://sphinxcontrib-katex.readthedocs.io/en/0.4.1/macros.html +latex_macros = r""" + \def \d #1{\operatorname{#1}} +""" + +# Translate LaTeX macros to KaTeX and add to options for HTML builder +katex_macros = katex.latex_defs_to_katex_macros(latex_macros) +katex_options = 'macros: {' + katex_macros + '}' + +# Add LaTeX macros for LATEX builder +latex_elements = {'preamble': latex_macros} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = ['style.css'] +html_logo = '_static/images/logo.png' + + +def setup(app): + app.add_js_file('https://cdn.jsdelivr.net/npm/vega@5.20.2') + app.add_js_file('https://cdn.jsdelivr.net/npm/vega-lite@5.1.0') + app.add_js_file('https://cdn.jsdelivr.net/npm/vega-embed@6.17.0') + + app.add_css_file('css/style.css') + + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + +# -- Source code links ------------------------------------------------------- + +extlinks = { + 'gitcode': ('https://github.com/metaopt/TorchOpt/blob/HEAD/%s', '%s'), + 'issue': ('https://github.com/metaopt/TorchOpt/issues/%s', 'issue %s'), +} + +# -- Extension configuration ------------------------------------------------- + +# -- Options for napoleon extension ------------------------------------------ + +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/docs/source/developer/contributing.rst b/docs/source/developer/contributing.rst new file mode 100644 index 00000000..278e2900 --- /dev/null +++ b/docs/source/developer/contributing.rst @@ -0,0 +1,100 @@ +Contributing to TorchOpt +======================== + +Before contributing to TorchOpt, please follow the instructions below to setup. + +1. Fork TorchOpt (`fork `_) on GitHub and clone the repository. + +.. code-block:: bash + + git clone git@github.com:/TorchOpt.git # use the SSH protocol + cd TorchOpt + + git remote add upstream git@github.com:metaopt/TorchOpt.git + +2. Setup a development environment via `conda `_: + +.. code-block:: bash + + # You may need `CONDA_OVERRIDE_CUDA` if conda fails to detect the NVIDIA driver (e.g. in docker or WSL2) + CONDA_OVERRIDE_CUDA=11.7 conda env create --file conda-recipe.yaml + + conda activate torchopt + +3. Setup the `pre-commit `_ hooks: + +.. code-block:: bash + + pre-commit install --install-hooks + +Then you are ready to rock. Thanks for contributing to TorchOpt! + + +Install Develop Version +----------------------- + +To install TorchOpt in an "editable" mode, run + +.. code-block:: bash + + pip install -e . + +in the main directory. This installation is removable by + +.. code-block:: bash + + python setup.py develop --uninstall + + +Lint Check +---------- + +We use several tools to secure code quality, including: + + * PEP8 code style: ``black``, ``isort``, ``pylint``, ``flake8`` + * Type hint check: ``mypy`` + * C++ Google-style: ``cpplint``, ``clang-format`` + * License: ``addlicense`` + * Documentation: ``pydocstyle``, ``doc8`` + +To make things easier, we create several shortcuts as follows. + +To automatically format the code, run: + +.. code-block:: bash + + make format + +To check if everything conforms to the specification, run: + +.. code-block:: bash + + make lint + + +Test Locally +------------ + +This command will run automatic tests in the main directory + +.. code-block:: bash + + $ make test + + +Documentation +------------- + +Documentations are written under the :gitcode:`docs/source` directory as ReStructuredText (``.rst``) files. ``index.rst`` is the main page. A Tutorial on ReStructuredText can be found `here `_. + +API References are automatically generated by `Sphinx `_ according to the outlines under directory :gitcode:`docs/source/api` and should be modified when any code changes. + +To compile documentation into webpage, run + +.. code-block:: bash + + $ make docs + +The generated webpage locates under directory ``docs/build`` and will open the browser after building documentation. + +Detailed documentation is hosted online at https://torchopt.readthedocs.io. diff --git a/docs/source/developer/contributor.rst b/docs/source/developer/contributor.rst new file mode 100644 index 00000000..0f08d38a --- /dev/null +++ b/docs/source/developer/contributor.rst @@ -0,0 +1,6 @@ +Contributor +=========== + +We always welcome contributions to help make TorchOpt better. Below is an incomplete list of our contributors (find more on `this page `_). + +* Yao Fu (`future-xy `_) diff --git a/docs/source/examples/MAML.rst b/docs/source/examples/MAML.rst new file mode 100644 index 00000000..9b38feb7 --- /dev/null +++ b/docs/source/examples/MAML.rst @@ -0,0 +1,277 @@ +Model-Agnostic Meta-Learning +============================ + +Meta reinforcement learning has achieved significant successes in various applications. +**Model-Agnostic Meta-Learning** (MAML) :cite:`MAML` is the pioneer one. +In this tutorial, we will show how to train MAML on few-shot Omniglot classification with TorchOpt step by step. +The full script is at :gitcode:`examples/few-shot/maml_omniglot.py`. + +Contrary to existing differentiable optimizer libraries such as `higher `_, which follows the PyTorch designing which leads to inflexible API, TorchOpt provides an easy way of construction through the code-level. + + +Overview +-------- + +There are six steps to finish MAML training pipeline: + +1. Load Dataset: load Omniglot dataset; +2. Build the Network: build the neural network architecture of model; +3. Train: meta-train; +4. Test: meta-test; +5. Plot: plot the results; +6. Pipeline: combine step 3-5 together; + + +In the following sections, we will set up Load Dataset, build the neural network, train-test, and plot to successfully run the MAML training and evaluation pipeline. +Here is the overall procedure: + + +Load Dataset +------------ + +In your Python code, simply import torch and load the dataset, the full script is at :gitcode:`examples/few-shot/support/omniglot_loaders.py`: + +.. code-block:: python + + from .support.omniglot_loaders import OmniglotNShot + import torch + + device = torch.device('cuda:0') + db = OmniglotNShot( + '/tmp/omniglot-data', + batchsz=args.task_num, + n_way=args.n_way, + k_shot=args.k_spt, + k_query=args.k_qry, + imgsz=28, + rng=rng, + device=device, + ) + +The goal is to train a model for few-shot Omniglot classification. + +Build the Network +----------------- + +TorchOpt supports any user-defined PyTorch networks. Here is an example: + +.. code-block:: python + + import torch, numpy as np + from torch import nn + import torch.optim as optim + + net = nn.Sequential( + nn.Conv2d(1, 64, 3), + nn.BatchNorm2d(64, momentum=1., affine=True), + nn.ReLU(inplace=False), + nn.MaxPool2d(2, 2), + nn.Conv2d(64, 64, 3), + nn.BatchNorm2d(64, momentum=1., affine=True), + nn.ReLU(inplace=False), + nn.MaxPool2d(2, 2), + nn.Conv2d(64, 64, 3), + nn.BatchNorm2d(64, momentum=1., affine=True), + nn.ReLU(inplace=False), nn.MaxPool2d(2, 2), + nn.Flatten(), + nn.Linear(64, args.n_way), + ).to(device) + + # We will use Adam to (meta-)optimize the initial parameters + # to be adapted. + meta_opt = optim.Adam(net.parameters(), lr=1e-3) + +Train +----- + +Define the ``train`` function: + +.. code-block:: python + + def train(db, net, meta_opt, epoch, log): + net.train() + n_train_iter = db.x_train.shape[0] // db.batchsz + inner_opt = torchopt.MetaSGD(net, lr=1e-1) + + for batch_idx in range(n_train_iter): + start_time = time.time() + # Sample a batch of support and query images and labels. + x_spt, y_spt, x_qry, y_qry = db.next() + + task_num, setsz, c_, h, w = x_spt.size() + querysz = x_qry.size(1) + + # TODO: Maybe pull this out into a separate module so it + # doesn't have to be duplicated between `train` and `test`? + + # Initialize the inner optimizer to adapt the parameters to + # the support set. + n_inner_iter = 5 + + qry_losses = [] + qry_accs = [] + meta_opt.zero_grad() + + net_state_dict = torchopt.extract_state_dict(net) + optim_state_dict = torchopt.extract_state_dict(inner_opt) + for i in range(task_num): + # Optimize the likelihood of the support set by taking + # gradient steps w.r.t. the model's parameters. + # This adapts the model's meta-parameters to the task. + # higher is able to automatically keep copies of + # your network's parameters as they are being updated. + for _ in range(n_inner_iter): + spt_logits = net(x_spt[i]) + spt_loss = F.cross_entropy(spt_logits, y_spt[i]) + inner_opt.step(spt_loss) + + # The final set of adapted parameters will induce some + # final loss and accuracy on the query dataset. + # These will be used to update the model's meta-parameters. + qry_logits = net(x_qry[i]) + qry_loss = F.cross_entropy(qry_logits, y_qry[i]) + qry_losses.append(qry_loss.detach()) + qry_acc = (qry_logits.argmax(dim=1) == y_qry[i]).sum().item() / querysz + qry_accs.append(qry_acc) + + # Update the model's meta-parameters to optimize the query + # losses across all of the tasks sampled in this batch. + # This unrolls through the gradient steps. + qry_loss.backward() + + torchopt.recover_state_dict(net, net_state_dict) + torchopt.recover_state_dict(inner_opt, optim_state_dict) + + meta_opt.step() + qry_losses = sum(qry_losses) / task_num + qry_accs = 100. * sum(qry_accs) / task_num + i = epoch + float(batch_idx) / n_train_iter + iter_time = time.time() - start_time + + print( + f'[Epoch {i:.2f}] Train Loss: {qry_losses:.2f} | Acc: {qry_accs:.2f} | Time: {iter_time:.2f}' + ) + + log.append( + { + 'epoch': i, + 'loss': qry_losses, + 'acc': qry_accs, + 'mode': 'train', + 'time': time.time(), + } + ) + +Test +---- + +Define the ``test`` function: + +.. code-block:: python + + def test(db, net, epoch, log): + # Crucially in our testing procedure here, we do *not* fine-tune + # the model during testing for simplicity. + # Most research papers using MAML for this task do an extra + # stage of fine-tuning here that should be added if you are + # adapting this code for research. + net.train() + n_test_iter = db.x_test.shape[0] // db.batchsz + inner_opt = torchopt.MetaSGD(net, lr=1e-1) + + qry_losses = [] + qry_accs = [] + + for batch_idx in range(n_test_iter): + x_spt, y_spt, x_qry, y_qry = db.next('test') + + task_num, setsz, c_, h, w = x_spt.size() + querysz = x_qry.size(1) + + # TODO: Maybe pull this out into a separate module so it + # doesn't have to be duplicated between `train` and `test`? + n_inner_iter = 5 + + net_state_dict = torchopt.extract_state_dict(net) + optim_state_dict = torchopt.extract_state_dict(inner_opt) + for i in range(task_num): + # Optimize the likelihood of the support set by taking + # gradient steps w.r.t. the model's parameters. + # This adapts the model's meta-parameters to the task. + for _ in range(n_inner_iter): + spt_logits = net(x_spt[i]) + spt_loss = F.cross_entropy(spt_logits, y_spt[i]) + inner_opt.step(spt_loss) + + # The query loss and acc induced by these parameters. + qry_logits = net(x_qry[i]).detach() + qry_loss = F.cross_entropy(qry_logits, y_qry[i], reduction='none') + qry_losses.append(qry_loss.detach()) + qry_accs.append((qry_logits.argmax(dim=1) == y_qry[i]).detach()) + + torchopt.recover_state_dict(net, net_state_dict) + torchopt.recover_state_dict(inner_opt, optim_state_dict) + + qry_losses = torch.cat(qry_losses).mean().item() + qry_accs = 100. * torch.cat(qry_accs).float().mean().item() + print(f'[Epoch {epoch+1:.2f}] Test Loss: {qry_losses:.2f} | Acc: {qry_accs:.2f}') + log.append( + { + 'epoch': epoch + 1, + 'loss': qry_losses, + 'acc': qry_accs, + 'mode': 'test', + 'time': time.time(), + } + ) + +Plot +---- + +TorchOpt supports any user-defined PyTorch networks and optimizers. Yet, of course, the inputs and outputs must comply with TorchOpt's API. Here is an example: + +.. code-block:: python + + def plot(log): + # Generally you should pull your plotting code out of your training + # script but we are doing it here for brevity. + df = pd.DataFrame(log) + + fig, ax = plt.subplots(figsize=(6, 4)) + train_df = df[df['mode'] == 'train'] + test_df = df[df['mode'] == 'test'] + ax.plot(train_df['epoch'], train_df['acc'], label='Train') + ax.plot(test_df['epoch'], test_df['acc'], label='Test') + ax.set_xlabel('Epoch') + ax.set_ylabel('Accuracy') + ax.set_ylim(70, 100) + fig.legend(ncol=2, loc='lower right') + fig.tight_layout() + fname = 'maml-accs.png' + print(f'--- Plotting accuracy to {fname}') + fig.savefig(fname) + plt.close(fig) + + +Pipeline +-------- + +We can now combine all the components together, and plot the results. + +.. code-block:: python + + log = [] + for epoch in range(10): + train(db, net, meta_opt, epoch, log) + test(db, net, epoch, log) + plot(log) + +.. image:: /_static/images/maml-accs.png + :align: center + :height: 300 + + +.. rubric:: References + +.. bibliography:: /references.bib + :style: unsrtalpha diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..892a1090 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,101 @@ +:github_url: https://github.com/metaopt/TorchOpt/tree/HEAD/docs + +TorchOpt +-------- + +**TorchOpt** is a high-performance optimizer library built upon `PyTorch `_ for easy implementation of functional optimization and gradient-based meta-learning. It consists of two main features: + +* TorchOpt provides functional optimizer which enables `JAX-like `_ composable functional optimizer for PyTorch. With TorchOpt, one can easily conduct neural network optimization in PyTorch with functional style optimizer, similar to `Optax `_ in JAX. +* With the design of functional programming, TorchOpt provides efficient, flexible, and easy-to-implement differentiable optimizer for gradient-based meta-learning research. It largely reduces the efforts required to implement sophisticated meta-learning algorithms. + +Installation +------------ + +Requirements + +- PyTorch +- JAX +- (Optional) For visualizing computation graphs + - `Graphviz `_ (for Linux users use ``apt/yum install graphviz`` or ``conda install -c anaconda python-graphviz``) + +Please follow the instructions at https://pytorch.org to install PyTorch in your Python environment first. Then run the following command to install TorchOpt from PyPI: + +.. code-block:: bash + + pip install torchopt + +You can also build shared libraries from source, use: + +.. code-block:: bash + + git clone https://github.com/metaopt/TorchOpt.git + cd TorchOpt + pip3 install . + +We provide a `conda `_ environment recipe to install the build toolchain such as `cmake`, `g++`, and `nvcc`: + +.. code-block:: bash + + git clone https://github.com/metaopt/TorchOpt.git + cd TorchOpt + + # You may need `CONDA_OVERRIDE_CUDA` if conda fails to detect the NVIDIA driver (e.g. in docker or WSL2) + CONDA_OVERRIDE_CUDA=11.7 conda env create --file conda-recipe.yaml + + conda activate torchopt + + +.. toctree:: + :caption: Getting Started + :maxdepth: 1 + + torchopt101/torchopt-101.rst + + +.. toctree:: + :caption: Examples + :maxdepth: 1 + + examples/MAML.rst + + +.. toctree:: + :caption: Developer Documentation + :maxdepth: 1 + + developer/contributing.rst + developer/contributor.rst + +.. toctree:: + :caption: API Documentation + :maxdepth: 2 + + api/api.rst + +The Team +-------- + +TorchOpt is a work by + +* Jie Ren (`JieRen98 `_) +* Xidong Feng (`waterhorse1 `_) +* Bo Liu (`Benjamin-eecs `_) +* Xuehai Pan (`XuehaiPan `_) +* Luo Mai (`luomai `_) +* Yaodong Yang (`PKU-YYang `_). + +Support +------- + +If you are having issues, please let us know by filing an issue on our +`issue tracker `_. + +Changelog +--------- + +See :gitcode:`CHANGELOG.md`. + +License +------- + +TorchOpt is licensed under the Apache 2.0 License. diff --git a/docs/source/references.bib b/docs/source/references.bib new file mode 100644 index 00000000..9e7910f3 --- /dev/null +++ b/docs/source/references.bib @@ -0,0 +1,19 @@ +@inproceedings{MAML, + author = {Chelsea Finn and + Pieter Abbeel and + Sergey Levine}, + editor = {Doina Precup and + Yee Whye Teh}, + title = {Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks}, + booktitle = {Proceedings of the 34th International Conference on Machine Learning, + {ICML} 2017, Sydney, NSW, Australia, 6-11 August 2017}, + series = {Proceedings of Machine Learning Research}, + volume = {70}, + pages = {1126--1135}, + publisher = {{PMLR}}, + year = {2017}, + url = {http://proceedings.mlr.press/v70/finn17a.html}, + timestamp = {Thu, 21 Jan 2021 17:37:24 +0100}, + biburl = {https://dblp.org/rec/conf/icml/FinnAL17.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} diff --git a/docs/source/spelling_wordlist.txt b/docs/source/spelling_wordlist.txt new file mode 100644 index 00000000..db1e67a1 --- /dev/null +++ b/docs/source/spelling_wordlist.txt @@ -0,0 +1,71 @@ +TorchOpt +torchopt +Kingma +Sutskever +Pieter +Abbeel +Sergey +Doina +Precup +Tieleman +Yee +Whye +Teh +Jie +Ren +Xidong +Feng +Bo +Liu +Luo +Mai +Yaodong +Yang +Xuehai +Pan +Yao +Fu +Jupyter +Colaboratory +Omniglot +differentiable +Dataset +dataset +Optimizers +optimizers +lr +eps +nesterov +et +al +rescaling +rescale +composable +momentums +addlicense +webpage +Omniglot +differentiable +toolchain +init +fn +inplace +impl +params +iterable +nan +param +Graphviz +autograd +attrs +GradientTransformations +args +chainable +adam +Adam +rmsprop +RMSProp +sgd +SGD +CHANGELOG +Changelog diff --git a/docs/source/torchopt101/torchopt-101.rst b/docs/source/torchopt101/torchopt-101.rst new file mode 100644 index 00000000..87bffd4c --- /dev/null +++ b/docs/source/torchopt101/torchopt-101.rst @@ -0,0 +1,9 @@ +Get Started with Jupyter Notebook +================================= + +In this tutorial, we will use Google Colaboratory to show you the most basic usages of TorchOpt. + +- 1: `Functional Optimizer `_ +- 2: `Visualization `_ +- 3: `Meta Optimizer `_ +- 4: `Stop Gradient `_ diff --git a/examples/L2R/README.md b/examples/L2R/README.md index e2f8007e..e9317235 100644 --- a/examples/L2R/README.md +++ b/examples/L2R/README.md @@ -1,23 +1,26 @@ # Learning-to-reweight-examples -Code On Mnist reweighting example in paper [Learning to Reweight Examples for Robust Deep Learning](https://arxiv.org/abs/1803.09050)] using `TorchOpt`. The idea of L2R is to use virtual update of inner-loop neural network optimisation to meta-learn the reweighting parameters for robust deep learning. We use `MetaSGD` as the inner-loop optimiser. +Code on MNIST re-weighting example in paper [Learning to Reweight Examples for Robust Deep Learning](https://arxiv.org/abs/1803.09050)] using TorchOpt. The idea of L2R is to use virtual update of inner-loop neural network optimization to meta-learn the re-weighting parameters for robust deep learning. We use `MetaSGD` as the inner-loop optimizer. + +## Usage -# Usage We use traditional supervised training as the baseline. + ```bash ### Run both algorithms and conduct comparison -python3 train_l2r.py --algo both +python3 l2r.py --algo both -### For baseline -python3 train_l2r.py --algo baseline +### For baseline +python3 l2r.py --algo baseline ### For L2R algorithm -python3 train_l2r.py --algo l2r +python3 l2r.py --algo l2r ``` -# Results +## Results + The test accuracy comparison between baseline and L2R validate the effectiveness of algorithms. +
- +
- diff --git a/examples/L2R/helper/argument.py b/examples/L2R/helper/argument.py index a44095e0..5df9f314 100644 --- a/examples/L2R/helper/argument.py +++ b/examples/L2R/helper/argument.py @@ -1,6 +1,19 @@ -import argparse +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== -import torch +import argparse def parse_args(): @@ -10,28 +23,18 @@ def parse_args(): parser.add_argument('--epoch', type=int, default=30, help='Training Epoch') parser.add_argument('--lr', type=float, default=1e-3, help='learning rate') - parser.add_argument('--pos_ratio', - type=float, - default=0.995, - help='Ratio of positive examples in training') - parser.add_argument('--ntest', - type=int, - default=500, - help='Number of testing examples') - parser.add_argument('--ntrain', - type=int, - default=5000, - help='Number of testing examples') - parser.add_argument('--nval', - type=int, - default=10, - help='Number of valid examples') - parser.add_argument('--batch_size', - type=int, - default=100, - help='Batch size') + parser.add_argument( + '--pos_ratio', + type=float, + default=0.995, + help='Ratio of positive examples in training', + ) + parser.add_argument('--ntest', type=int, default=500, help='Number of testing examples') + parser.add_argument('--ntrain', type=int, default=5000, help='Number of testing examples') + parser.add_argument('--nval', type=int, default=10, help='Number of valid examples') + parser.add_argument('--batch_size', type=int, default=100, help='Batch size') - ### For baseline + # For baseline parser.add_argument('--algo', type=str, default='both') args = parser.parse_args() diff --git a/examples/L2R/helper/model.py b/examples/L2R/helper/model.py index 469b1c97..80fae8ac 100644 --- a/examples/L2R/helper/model.py +++ b/examples/L2R/helper/model.py @@ -28,9 +28,7 @@ # # Models for MNIST experiments. # -from __future__ import division, print_function -import numpy as np import torch import torch.nn as nn @@ -38,23 +36,30 @@ class LeNet5(nn.Module): def __init__(self, args): super(LeNet5, self).__init__() - self.model = nn.Sequential(nn.Conv2d(1, 16, 5), nn.ReLU(), - nn.MaxPool2d(2), nn.Conv2d(16, 32, 5), - nn.ReLU(), nn.MaxPool2d(2), nn.Flatten(), - nn.Linear(512, 128), nn.ReLU(), - nn.Linear(128, 1), nn.Sigmoid()) + self.model = nn.Sequential( + nn.Conv2d(1, 16, 5), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Conv2d(16, 32, 5), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Flatten(), + nn.Linear(512, 128), + nn.ReLU(), + nn.Linear(128, 1), + nn.Sigmoid(), + ) self.args = args - self.meta_weights = torch.zeros(self.args.batch_size, - requires_grad=True).to( - self.args.device) + self.meta_weights = torch.zeros(self.args.batch_size, requires_grad=True).to( + self.args.device + ) self.criterion = nn.BCELoss() def forward(self, x): return self.model(x).squeeze(dim=-1) def reset_meta(self, size): - self.meta_weights = torch.zeros(size, requires_grad=True).to( - self.args.device) + self.meta_weights = torch.zeros(size, requires_grad=True).to(self.args.device) def normalise(self): self.meta_weights = self.meta_weights.detach() @@ -66,8 +71,9 @@ def inner_loss(self, train_x, train_y): result = self.forward(train_x) # manually implement bce_loss to make the loss differentiable w.r.t self.meta_weights - loss = -(train_y * torch.log(result + 1e-10) + - (1 - train_y) * torch.log(1 - result + 1e-10)) + loss = -( + train_y * torch.log(result + 1e-10) + (1 - train_y) * torch.log(1 - result + 1e-10) + ) weighted_loss = torch.sum(self.meta_weights * loss) return weighted_loss diff --git a/examples/L2R/helper/utils.py b/examples/L2R/helper/utils.py index dece9938..954b27b2 100644 --- a/examples/L2R/helper/utils.py +++ b/examples/L2R/helper/utils.py @@ -14,23 +14,25 @@ # ============================================================================== # This file is modified from: # https://github.com/uber-research/learning-to-reweight-examples +# ============================================================================== import random import numpy as np -import seaborn as sns import torch from torch.utils.data import TensorDataset -def get_imbalance_dataset(mnist_train, - mnist_test, - pos_ratio=0.9, - ntrain=5000, - nval=10, - ntest=500, - class_0=4, - class_1=9): +def get_imbalance_dataset( + mnist_train, + mnist_test, + pos_ratio=0.9, + ntrain=5000, + nval=10, + ntest=500, + class_0=4, + class_1=9, +): ratio = 1 - pos_ratio ratio_test = 0.5 @@ -53,16 +55,14 @@ def get_imbalance_dataset(mnist_train, ntrain_small_neg = int(np.floor(ntrain * ratio)) - nval_small_neg x_val_0 = x_train_0[:nval_small_neg] # 450 4 in validation. - x_train_0 = x_train_0[nval_small_neg:nval_small_neg + - ntrain_small_neg] # 500 4 in training. + x_train_0 = x_train_0[nval_small_neg : nval_small_neg + ntrain_small_neg] # 500 4 in training. print('Number of train negative classes', ntrain_small_neg) print('Number of val negative classes', nval_small_neg) idx = np.arange(x_test_0.shape[0]) np.random.shuffle(idx) - x_test_0 = x_test_0[:int(np.floor(ntest * - ratio_test))] # 450 4 in testing. + x_test_0 = x_test_0[: int(np.floor(ntest * ratio_test))] # 450 4 in testing. x_train_1 = x_train[y_train == class_1] x_test_1 = x_test[y_test == class_1] @@ -76,33 +76,24 @@ def get_imbalance_dataset(mnist_train, ntrainsmall_pos = int(np.floor(ntrain * (1 - ratio))) - nvalsmall_pos x_val_1 = x_train_1[:nvalsmall_pos] # 50 9 in validation. - x_train_1 = x_train_1[nvalsmall_pos:nvalsmall_pos + - ntrainsmall_pos] # 4500 9 in training. + x_train_1 = x_train_1[nvalsmall_pos : nvalsmall_pos + ntrainsmall_pos] # 4500 9 in training. idx = np.arange(x_test_1.shape[0]) np.random.shuffle(idx) x_test_1 = x_test_1[idx] - x_test_1 = x_test_1[:int(np.floor(ntest * - (1 - ratio_test)))] # 500 9 in testing. + x_test_1 = x_test_1[: int(np.floor(ntest * (1 - ratio_test)))] # 500 9 in testing. print('Number of train positive classes', ntrainsmall_pos) print('Number of val positive classes', nvalsmall_pos) - y_train_subset = np.concatenate( - [np.zeros([x_train_0.shape[0]]), - np.ones([x_train_1.shape[0]])]) - y_val_subset = np.concatenate( - [np.zeros([x_val_0.shape[0]]), - np.ones([x_val_1.shape[0]])]) - y_test_subset = np.concatenate( - [np.zeros([x_test_0.shape[0]]), - np.ones([x_test_1.shape[0]])]) + y_train_subset = np.concatenate([np.zeros([x_train_0.shape[0]]), np.ones([x_train_1.shape[0]])]) + y_val_subset = np.concatenate([np.zeros([x_val_0.shape[0]]), np.ones([x_val_1.shape[0]])]) + y_test_subset = np.concatenate([np.zeros([x_test_0.shape[0]]), np.ones([x_test_1.shape[0]])]) y_train_pos_subset = np.ones([x_train_1.shape[0]]) y_train_neg_subset = np.zeros([x_train_0.shape[0]]) - x_train_subset = np.concatenate([x_train_0, x_train_1], axis=0)[:, - None, :, :] + x_train_subset = np.concatenate([x_train_0, x_train_1], axis=0)[:, None, :, :] x_val_subset = np.concatenate([x_val_0, x_val_1], axis=0)[:, None, :, :] x_test_subset = np.concatenate([x_test_0, x_test_1], axis=0)[:, None, :, :] @@ -125,15 +116,20 @@ def get_imbalance_dataset(mnist_train, x_test_subset = x_test_subset[idx].astype(np.float32) y_test_subset = y_test_subset[idx].astype(np.float32) - x_train_subset, y_train_subset, x_val_subset, y_val_subset, x_test_subset, y_test_subset = torch.tensor( - x_train_subset), torch.tensor(y_train_subset), torch.tensor( - x_val_subset), torch.tensor(y_val_subset), torch.tensor( - x_test_subset), torch.tensor(y_test_subset) - - train_set, val_set, test_set = TensorDataset( - x_train_subset, y_train_subset), TensorDataset( - x_val_subset, y_val_subset), TensorDataset(x_test_subset, - y_test_subset) + (x_train_subset, y_train_subset, x_val_subset, y_val_subset, x_test_subset, y_test_subset,) = ( + torch.tensor(x_train_subset), + torch.tensor(y_train_subset), + torch.tensor(x_val_subset), + torch.tensor(y_val_subset), + torch.tensor(x_test_subset), + torch.tensor(y_test_subset), + ) + + train_set, val_set, test_set = ( + TensorDataset(x_train_subset, y_train_subset), + TensorDataset(x_val_subset, y_val_subset), + TensorDataset(x_test_subset, y_test_subset), + ) return train_set, val_set, test_set @@ -144,15 +140,17 @@ def set_seed(seed, cudnn=True): Note that gym environments might need additional seeding (env.seed(seed)), and num_workers needs to be set to 1. """ + random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.random.manual_seed(seed) torch.cuda.manual_seed(seed) # note: the below slows down the code but makes it reproducible - torch.cuda.manual_seed_all( - seed - ) # Sets the seed for generating random numbers on all GPUs. It’s safe to call this function if CUDA is not available; in that case, it is silently ignored. + # Sets the seed for generating random numbers on all GPUs. It’s safe to + # call this function if CUDA is not available; in that case, it is + # silently ignored. + torch.cuda.manual_seed_all(seed) if cudnn: torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False @@ -162,8 +160,9 @@ def plot(baseline, l2r): import matplotlib.pyplot as plt import numpy as np import seaborn as sns + sns.set(style='darkgrid') - sns.set_theme(style="darkgrid") + sns.set_theme(style='darkgrid') plt.plot(baseline, label='baseline') plt.plot(l2r, label='l2r') plt.legend() diff --git a/examples/L2R/train_l2r.py b/examples/L2R/l2r.py similarity index 67% rename from examples/L2R/train_l2r.py rename to examples/L2R/l2r.py index 3cc2a018..cd093313 100644 --- a/examples/L2R/train_l2r.py +++ b/examples/L2R/l2r.py @@ -28,21 +28,20 @@ # import json -import os -import time import numpy as np import torch -import torch.nn as nn -from helper.argument import parse_args -from helper.model import LeNet5 -from helper.utils import get_imbalance_dataset, plot, set_seed -from torch import device from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter from torchvision.datasets import MNIST -import TorchOpt +import torchopt + + +# isort: off +from helper.argument import parse_args +from helper.model import LeNet5 +from helper.utils import get_imbalance_dataset, plot, set_seed def run_baseline(args, mnist_train, mnist_test): @@ -60,27 +59,19 @@ def run_baseline(args, mnist_train, mnist_test): with open('./result/baseline/config.json', 'w') as f: json.dump(args.__dict__, f) - args.device = torch.device( - "cuda:0" if torch.cuda.is_available() else "cpu") - - train_set, val_set, test_set = get_imbalance_dataset(mnist_train, - mnist_test, - pos_ratio=pos_ratio, - ntrain=ntrain, - nval=nval, - ntest=ntest) - train_loader = DataLoader(train_set, - batch_size=args.batch_size, - shuffle=True, - num_workers=4) - valid_loader = DataLoader(val_set, - batch_size=args.batch_size, - shuffle=True, - num_workers=1) - test_loader = DataLoader(test_set, - batch_size=args.batch_size, - shuffle=True, - num_workers=1) + args.device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') + + train_set, val_set, test_set = get_imbalance_dataset( + mnist_train, + mnist_test, + pos_ratio=pos_ratio, + ntrain=ntrain, + nval=nval, + ntest=ntest, + ) + train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, num_workers=4) + valid_loader = DataLoader(val_set, batch_size=args.batch_size, shuffle=True, num_workers=1) + test_loader = DataLoader(test_set, batch_size=args.batch_size, shuffle=True, num_workers=1) model = LeNet5(args).to(args.device) model_optimiser = torch.optim.Adam(model.parameters(), lr=args.lr) @@ -91,8 +82,7 @@ def run_baseline(args, mnist_train, mnist_test): for _epoch in range(epoch): model.train() for idx, (train_x, train_label) in enumerate(train_loader): - train_x, train_label = train_x.to(args.device), train_label.to( - args.device) + train_x, train_label = train_x.to(args.device), train_label.to(args.device) outer_loss = model.outer_loss(train_x, train_label) model_optimiser.zero_grad() @@ -104,10 +94,8 @@ def run_baseline(args, mnist_train, mnist_test): if step % 10 == 0 and step > 0: running_train_mean = np.mean(np.array(running_train_loss)) - print("EPOCH: {}, BATCH: {}, LOSS: {}".format( - _epoch, idx, running_train_mean)) - writer.add_scalar('running_train_loss', running_train_mean, - step) + print('EPOCH: {}, BATCH: {}, LOSS: {}'.format(_epoch, idx, running_train_mean)) + writer.add_scalar('running_train_loss', running_train_mean, step) running_train_loss = [] step += 1 @@ -121,8 +109,7 @@ def run_baseline(args, mnist_train, mnist_test): writer.add_scalar('train_acc', train_acc, _epoch) writer.add_scalar('test_acc', test_acc, _epoch) test_acc_result.append(test_acc) - print("EPOCH: {}, TRAIN_ACC: {}, TEST_ACC: {}".format( - _epoch, train_acc, test_acc)) + print('EPOCH: {}, TRAIN_ACC: {}, TEST_ACC: {}'.format(_epoch, train_acc, test_acc)) return test_acc_result @@ -141,29 +128,21 @@ def run_L2R(args, mnist_train, mnist_test): with open('./result/l2r/config.json', 'w') as f: json.dump(args.__dict__, f) - args.device = torch.device( - "cuda:0" if torch.cuda.is_available() else "cpu") - - train_set, val_set, test_set = get_imbalance_dataset(mnist_train, - mnist_test, - pos_ratio=pos_ratio, - ntrain=ntrain, - nval=nval, - ntest=ntest) - train_loader = DataLoader(train_set, - batch_size=args.batch_size, - shuffle=True, - num_workers=2) - valid_loader = DataLoader(val_set, - batch_size=args.batch_size, - shuffle=True, - num_workers=1) - test_loader = DataLoader(test_set, - batch_size=args.batch_size, - shuffle=True, - num_workers=1) + args.device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') + + train_set, val_set, test_set = get_imbalance_dataset( + mnist_train, + mnist_test, + pos_ratio=pos_ratio, + ntrain=ntrain, + nval=nval, + ntest=ntest, + ) + train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, num_workers=2) + valid_loader = DataLoader(val_set, batch_size=args.batch_size, shuffle=True, num_workers=1) + test_loader = DataLoader(test_set, batch_size=args.batch_size, shuffle=True, num_workers=1) model = LeNet5(args).to(args.device) - model_optimiser = TorchOpt.MetaSGD(model, lr=args.lr) + model_optimiser = torchopt.MetaSGD(model, lr=args.lr) real_model_optimiser = torch.optim.Adam(model.parameters(), lr=args.lr) step = 0 @@ -177,18 +156,21 @@ def run_L2R(args, mnist_train, mnist_test): for idx, (train_x, train_label) in enumerate(train_loader): try: valid_x, valid_label = valid.next() - except: + except BaseException: valid = iter(valid_loader) valid_x, valid_label = valid.next() - train_x, train_label, valid_x, valid_label = train_x.to( - args.device), train_label.to(args.device), valid_x.to( - args.device), valid_label.to(args.device) + train_x, train_label, valid_x, valid_label = ( + train_x.to(args.device), + train_label.to(args.device), + valid_x.to(args.device), + valid_label.to(args.device), + ) # reset meta-parameter weights model.reset_meta(size=train_x.size(0)) - net_state_dict = TorchOpt.extract_state_dict(model) - optim_state_dict = TorchOpt.extract_state_dict(model_optimiser) + net_state_dict = torchopt.extract_state_dict(model) + optim_state_dict = torchopt.extract_state_dict(model_optimiser) for _ in range(1): inner_loss = model.inner_loss(train_x, train_label) @@ -196,8 +178,7 @@ def run_L2R(args, mnist_train, mnist_test): # caclulate outer_loss, deirve meta-gradient and normalise outer_loss = model.outer_loss(valid_x, valid_label) - model.meta_weights = - \ - torch.autograd.grad(outer_loss, model.meta_weights)[0] + model.meta_weights = -torch.autograd.grad(outer_loss, model.meta_weights)[0] model.meta_weights = torch.nn.ReLU()(model.meta_weights) model.normalise() @@ -206,8 +187,8 @@ def run_L2R(args, mnist_train, mnist_test): writer.add_scalar('validation_loss', outer_loss.item(), step) # reset the model and model optimiser - TorchOpt.recover_state_dict(model, net_state_dict) - TorchOpt.recover_state_dict(model_optimiser, optim_state_dict) + torchopt.recover_state_dict(model, net_state_dict) + torchopt.recover_state_dict(model_optimiser, optim_state_dict) # reuse inner_adapt to conduct real update based on learned meta weights inner_loss = model.inner_loss(train_x, train_label) @@ -224,15 +205,14 @@ def run_L2R(args, mnist_train, mnist_test): running_valid_mean = np.mean(np.array(running_valid_loss)) running_train_mean = np.mean(np.array(running_train_loss)) print( - "EPOCH: {}, BATCH: {}, WEIGHTED_TRAIN_LOSS: {}, VALID_LOSS: {}" - .format(_epoch, idx, running_train_mean, - running_valid_mean)) + 'EPOCH: {}, BATCH: {}, WEIGHTED_TRAIN_LOSS: {}, VALID_LOSS: {}'.format( + _epoch, idx, running_train_mean, running_valid_mean + ) + ) running_valid_loss = [] running_train_loss = [] - writer.add_scalar('running_valid_loss', running_valid_mean, - step) - writer.add_scalar('running_train_loss', running_train_mean, - step) + writer.add_scalar('running_valid_loss', running_valid_mean, step) + writer.add_scalar('running_train_loss', running_train_mean, step) step += 1 @@ -245,8 +225,7 @@ def run_L2R(args, mnist_train, mnist_test): writer.add_scalar('train_acc', train_acc, _epoch) writer.add_scalar('test_acc', test_acc, _epoch) test_acc_result.append(test_acc) - print("EPOCH: {}, TRAIN_ACC: {}, TEST_ACC: {}".format( - _epoch, train_acc, test_acc)) + print('EPOCH: {}, TRAIN_ACC: {}, TEST_ACC: {}'.format(_epoch, train_acc, test_acc)) return test_acc_result diff --git a/examples/L2R/result.png b/examples/L2R/result.png old mode 100755 new mode 100644 diff --git a/examples/LOLA/README.md b/examples/LOLA/README.md old mode 100755 new mode 100644 index 8ef37723..1523851a --- a/examples/LOLA/README.md +++ b/examples/LOLA/README.md @@ -1,19 +1,21 @@ # LOLA-examples -Code On LOLA a in paper [Learning with Opponent-Learning Awareness](https://arxiv.org/abs/1709.04326)] using `TorchOpt`. The LOLA learning rule includes a term that accounts for the impact of one agent's policy on the anticipated parameter update of the other agents. We use `MetaSGD` as the inner-loop optimiser. +Code on LOLA a in paper [Learning with Opponent-Learning Awareness](https://arxiv.org/abs/1709.04326)] using TorchOpt. The LOLA learning rule includes a term that accounts for the impact of one agent's policy on the anticipated parameter update of the other agents. We use `MetaSGD` as the inner-loop optimizer. + +## Usage -# Usage ```bash ### Run LOLA python3 lola_dice.py ### After get the result.npy, run visualization code -python3 visualise.py +python3 visualize.py ``` -# Results +## Results + The figure illustrate the experimental result. +
- +
- diff --git a/examples/LOLA/helper/agent.py b/examples/LOLA/helper/agent.py old mode 100755 new mode 100644 index 1ae36688..8b30a983 --- a/examples/LOLA/helper/agent.py +++ b/examples/LOLA/helper/agent.py @@ -1,37 +1,50 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== # This file is modified from: # https://github.com/alexis-jacq/LOLA_DiCE +# ============================================================================== import torch import torch.nn as nn -import TorchOpt +import torchopt class theta_model(nn.Module): def __init__(self, theta): super().__init__() - self.theta = nn.Parameter( - torch.tensor(theta.detach(), requires_grad=True)) + self.theta = nn.Parameter(torch.tensor(theta.detach(), requires_grad=True)) -class Agent(): +class Agent: def __init__(self, args): self.args = args # init theta and its optimizer self.theta = nn.Parameter(torch.zeros(5, requires_grad=True)) - self.theta_optimizer = torch.optim.Adam((self.theta, ), lr=args.lr_out) + self.theta_optimizer = torch.optim.Adam((self.theta,), lr=args.lr_out) # init values and its optimizer self.values = nn.Parameter(torch.zeros(5, requires_grad=True)) - self.value_optimizer = torch.optim.Adam((self.values, ), lr=args.lr_v) + self.value_optimizer = torch.optim.Adam((self.values,), lr=args.lr_v) self.set_virtual() def set_virtual(self): self.virtual_theta = theta_model(self.theta) - self.virtual_optimiser = TorchOpt.MetaSGD(self.virtual_theta, - lr=self.args.lr_in) + self.virtual_optimiser = torchopt.MetaSGD(self.virtual_theta, lr=self.args.lr_in) def value_update(self, loss): self.value_optimizer.zero_grad() diff --git a/examples/LOLA/helper/argument.py b/examples/LOLA/helper/argument.py old mode 100755 new mode 100644 index acd50a52..39618134 --- a/examples/LOLA/helper/argument.py +++ b/examples/LOLA/helper/argument.py @@ -1,3 +1,18 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + import argparse @@ -5,39 +20,15 @@ def parse_args(): parser = argparse.ArgumentParser([], description='LOLA') parser.add_argument('--seed', type=int, default=6666) - parser.add_argument('--lr_in', - type=float, - default=0.3, - help='Inner Learning rate') + parser.add_argument('--lr_in', type=float, default=0.3, help='Inner Learning rate') - parser.add_argument('--lr_out', - type=float, - default=0.2, - help='Outer learning rate') - parser.add_argument('--lr_v', - type=float, - default=0.1, - help='Learning rate of value function') - parser.add_argument('--gamma', - type=float, - default=0.96, - help='Discount factor') - parser.add_argument('--n_update', - type=int, - default=100, - help='Number of updates') - parser.add_argument('--n_lookaheads', - type=int, - default=1, - help='Number of updates') - parser.add_argument('--len_rollout', - type=int, - default=150, - help='Length of IPD') - parser.add_argument('--batch_size', - type=int, - default=1024, - help='Natch size') + parser.add_argument('--lr_out', type=float, default=0.2, help='Outer learning rate') + parser.add_argument('--lr_v', type=float, default=0.1, help='Learning rate of value function') + parser.add_argument('--gamma', type=float, default=0.96, help='Discount factor') + parser.add_argument('--n_update', type=int, default=100, help='Number of updates') + parser.add_argument('--n_lookaheads', type=int, default=1, help='Number of updates') + parser.add_argument('--len_rollout', type=int, default=150, help='Length of IPD') + parser.add_argument('--batch_size', type=int, default=1024, help='Natch size') parser.add_argument('--use_baseline', action='store_false', default=True) args = parser.parse_args() diff --git a/examples/LOLA/helper/env.py b/examples/LOLA/helper/env.py old mode 100755 new mode 100644 index 8ac392c8..f1ef6e6f --- a/examples/LOLA/helper/env.py +++ b/examples/LOLA/helper/env.py @@ -1,5 +1,20 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== # This file is modified from: # https://github.com/alexis-jacq/LOLA_DiCE +# ============================================================================== import gym import numpy as np @@ -10,24 +25,27 @@ class OneHot(gym.Space): """ One-hot space. Used as the observation space. """ + def __init__(self, n): self.n = n def sample(self): - return np.random.multinomial(1, [1. / self.n] * self.n) + return np.random.multinomial(1, [1.0 / self.n] * self.n) def contains(self, x): - return isinstance(x, np.ndarray) and \ - x.shape == (self.n, ) and \ - np.all(np.logical_or(x == 0, x == 1)) and \ - np.sum(x) == 1 + return ( + isinstance(x, np.ndarray) + and x.shape == (self.n,) + and np.all(np.logical_or(x == 0, x == 1)) + and np.sum(x) == 1 + ) @property def shape(self): - return (self.n, ) + return (self.n,) def __repr__(self): - return "OneHot(%d)" % self.n + return 'OneHot(%d)' % self.n def __eq__(self, other): return self.n == other.n @@ -38,6 +56,7 @@ class IPD(gym.Env): A two-agent vectorized environment. Possible actions for each agent are (C)ooperate and (D)efect. """ + # Possible actions NUM_AGENTS = 2 NUM_ACTIONS = 2 @@ -49,13 +68,10 @@ def __init__(self, max_steps, batch_size=1): self.payout_mat = np.array([[-2, 0], [-3, -1]]) self.states = np.array([[1, 2], [3, 4]]) - self.action_space = Tuple( - [Discrete(self.NUM_ACTIONS) for _ in range(self.NUM_AGENTS)]) - self.observation_space = Tuple( - [OneHot(self.NUM_STATES) for _ in range(self.NUM_AGENTS)]) + self.action_space = Tuple([Discrete(self.NUM_ACTIONS) for _ in range(self.NUM_AGENTS)]) + self.observation_space = Tuple([OneHot(self.NUM_STATES) for _ in range(self.NUM_AGENTS)]) self.available_actions = [ - np.ones((batch_size, self.NUM_ACTIONS), dtype=int) - for _ in range(self.NUM_AGENTS) + np.ones((batch_size, self.NUM_ACTIONS), dtype=int) for _ in range(self.NUM_AGENTS) ] self.step_count = None @@ -77,6 +93,6 @@ def step(self, action): s1 = self.states[ac1, ac0] observation = [s0, s1] reward = [r0, r1] - done = (self.step_count == self.max_steps) + done = self.step_count == self.max_steps info = [{'available_actions': aa} for aa in self.available_actions] return observation, reward, done, info diff --git a/examples/LOLA/helper/utils.py b/examples/LOLA/helper/utils.py old mode 100755 new mode 100644 index 86421034..afa9e609 --- a/examples/LOLA/helper/utils.py +++ b/examples/LOLA/helper/utils.py @@ -1,5 +1,20 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== # This file is modified from: # https://github.com/alexis-jacq/LOLA_DiCE +# ============================================================================== import numpy as np import torch @@ -28,7 +43,7 @@ def magic_box(x): # replay buffer -class Memory(): +class Memory: def __init__(self, args): self.self_logprobs = [] self.other_logprobs = [] @@ -49,9 +64,9 @@ def dice_objective(self, use_baseline=True): rewards = torch.stack(self.rewards, dim=1) # apply discount: - cum_discount = torch.cumprod( - self.args.gamma * torch.ones(*rewards.size()), - dim=1) / self.args.gamma + cum_discount = ( + torch.cumprod(self.args.gamma * torch.ones(*rewards.size()), dim=1) / self.args.gamma + ) discounted_rewards = rewards * cum_discount discounted_values = values * cum_discount @@ -62,15 +77,13 @@ def dice_objective(self, use_baseline=True): stochastic_nodes = self_logprobs + other_logprobs # dice objective: - dice_objective = torch.mean( - torch.sum(magic_box(dependencies) * discounted_rewards, dim=1)) + dice_objective = torch.mean(torch.sum(magic_box(dependencies) * discounted_rewards, dim=1)) if use_baseline: # variance_reduction: baseline_term = torch.mean( - torch.sum( - (1 - magic_box(stochastic_nodes)) * discounted_values, - dim=1)) + torch.sum((1 - magic_box(stochastic_nodes)) * discounted_values, dim=1) + ) dice_objective = dice_objective + baseline_term return -dice_objective # want to minimize -objective @@ -78,7 +91,7 @@ def dice_objective(self, use_baseline=True): def value_loss(self): values = torch.stack(self.values, dim=1) rewards = torch.stack(self.rewards, dim=1) - return torch.mean((rewards - values)**2) + return torch.mean((rewards - values) ** 2) def act(batch_states, theta, values): diff --git a/examples/LOLA/lola_dice.py b/examples/LOLA/lola_dice.py old mode 100755 new mode 100644 index 1eee2ae7..61d2e22c --- a/examples/LOLA/lola_dice.py +++ b/examples/LOLA/lola_dice.py @@ -14,20 +14,17 @@ # ============================================================================== # This file is modified from: # https://github.com/alexis-jacq/LOLA_DiCE +# ============================================================================== -from copy import deepcopy - -import matplotlib.pyplot as plt import numpy as np import torch -import torch.nn as nn + + +# isort: off from helper.agent import Agent from helper.argument import parse_args from helper.env import IPD from helper.utils import sample, step -from torch.distributions import Bernoulli - -import TorchOpt def main(args): @@ -36,7 +33,7 @@ def main(args): agent1_copy, agent2_copy = Agent(args), Agent(args) n_lookaheads = args.n_lookaheads joint_scores = [] - print("start iterations with", n_lookaheads, "lookaheads:") + print('start iterations with', n_lookaheads, 'lookaheads:') for update in range(args.n_update): # reset virtual update @@ -46,23 +43,32 @@ def main(args): # agent 2 assumes that agent 1 conducts n-step lookahead for _ in range(n_lookaheads): memory1, memory2 = sample( - ipd, [agent1.virtual_theta.theta, agent2.theta], - [agent1.values, agent2.values], args) + ipd, + [agent1.virtual_theta.theta, agent2.theta], + [agent1.values, agent2.values], + args, + ) inner_loss = memory1.dice_objective(use_baseline=args.use_baseline) agent1.virtual_optimiser.step(inner_loss) # agent 1 assumes that agent 2 conducts n-step lookahead for _ in range(n_lookaheads): memory1, memory2 = sample( - ipd, [agent1.theta, agent2.virtual_theta.theta], - [agent1.values, agent2.values], args) + ipd, + [agent1.theta, agent2.virtual_theta.theta], + [agent1.values, agent2.values], + args, + ) inner_loss = memory2.dice_objective(use_baseline=args.use_baseline) agent2.virtual_optimiser.step(inner_loss) # update agent 1 - memory1, memory2 = sample(ipd, - [agent1.theta, agent2.virtual_theta.theta], - [agent1.values, agent2.values], args) + memory1, memory2 = sample( + ipd, + [agent1.theta, agent2.virtual_theta.theta], + [agent1.values, agent2.values], + args, + ) outer_loss = memory1.dice_objective(use_baseline=args.use_baseline) agent1.theta_optimizer.zero_grad() outer_loss.backward(retain_graph=True) @@ -73,9 +79,12 @@ def main(args): agent1.value_update(v_loss) # update agent 2 - memory1, memory2 = sample(ipd, - [agent1.virtual_theta.theta, agent2.theta], - [agent1.values, agent2.values], args) + memory1, memory2 = sample( + ipd, + [agent1.virtual_theta.theta, agent2.theta], + [agent1.values, agent2.values], + args, + ) outer_loss = memory2.dice_objective(use_baseline=args.use_baseline) agent2.theta_optimizer.zero_grad() outer_loss.backward(retain_graph=True) @@ -86,8 +95,7 @@ def main(args): agent2.value_update(v_loss) # evaluate progress: - score = step(ipd, agent1.theta, agent2.theta, agent1.values, - agent2.values, args) + score = step(ipd, agent1.theta, agent2.theta, agent1.values, agent2.values, args) joint_scores.append(0.5 * (score[0] + score[1])) # print @@ -95,16 +103,18 @@ def main(args): p1 = [p.item() for p in torch.sigmoid(agent1.theta)] p2 = [p.item() for p in torch.sigmoid(agent2.theta)] print( - 'update', update, 'score (%.3f,%.3f)' % (score[0], score[1]), - 'policy (agent1) = {%.3f, %.3f, %.3f, %.3f, %.3f}' % - (p1[0], p1[1], p1[2], p1[3], p1[4]), - ' (agent2) = {%.3f, %.3f, %.3f, %.3f, %.3f}' % - (p2[0], p2[1], p2[2], p2[3], p2[4])) + 'update', + update, + 'score (%.3f,%.3f)' % (score[0], score[1]), + 'policy (agent1) = {%.3f, %.3f, %.3f, %.3f, %.3f}' + % (p1[0], p1[1], p1[2], p1[3], p1[4]), + ' (agent2) = {%.3f, %.3f, %.3f, %.3f, %.3f}' % (p2[0], p2[1], p2[2], p2[3], p2[4]), + ) return joint_scores -if __name__ == "__main__": +if __name__ == '__main__': args = parse_args() joint_score = dict() for nla in range(3): diff --git a/examples/LOLA/result.png b/examples/LOLA/result.png old mode 100755 new mode 100644 diff --git a/examples/LOLA/visualise.py b/examples/LOLA/visualise.py deleted file mode 100755 index da5ea0da..00000000 --- a/examples/LOLA/visualise.py +++ /dev/null @@ -1,20 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np -import seaborn as sns - - -def plot(file): - data = np.load('result.npy', allow_pickle=True).tolist() - sns.set(style='darkgrid') - sns.set_theme(style="darkgrid") - for step in range(3): - plt.plot(data[step], label='Step ' + str(step)) - plt.legend() - plt.xlabel('Iteartions', fontsize=20) - plt.ylabel('Joint score', fontsize=20) - plt.savefig('./result.png') - - -# plot progress: -if __name__ == "__main__": - plot('result.npy') diff --git a/examples/LOLA/visualize.py b/examples/LOLA/visualize.py new file mode 100755 index 00000000..6dc54ddf --- /dev/null +++ b/examples/LOLA/visualize.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +import matplotlib.pyplot as plt +import numpy as np +import seaborn as sns + + +def plot(file): + data = np.load('result.npy', allow_pickle=True).tolist() + sns.set(style='darkgrid') + sns.set_theme(style='darkgrid') + for step in range(3): + plt.plot(data[step], label='Step ' + str(step)) + plt.legend() + plt.xlabel('Iteartions', fontsize=20) + plt.ylabel('Joint score', fontsize=20) + plt.savefig('./result.png') + + +# plot progress: +if __name__ == '__main__': + plot('result.npy') diff --git a/examples/MAML-RL/README.md b/examples/MAML-RL/README.md old mode 100755 new mode 100644 index 26a80200..be2150bd --- a/examples/MAML-RL/README.md +++ b/examples/MAML-RL/README.md @@ -1,16 +1,20 @@ # Reinforcement learning with Model-Agnostic Meta-Learning (MAML) -Code on Tabular MDP example in paper *Model-Agnostic Meta-Learning* [[MAML](https://arxiv.org/abs/1703.03400)] using `TorchOpt`. The idea of MAML is to learn the initial parameters of an agent's policy so that the agent can rapidly adapt to new environments with a limited number of policy-gradient updates. We use `MetaSGD` as the inner-loop optimiser. +Code on Tabular MDP example in paper *Model-Agnostic Meta-Learning* [[MAML](https://arxiv.org/abs/1703.03400)] using TorchOpt. The idea of MAML is to learn the initial parameters of an agent's policy so that the agent can rapidly adapt to new environments with a limited number of policy-gradient updates. We use `MetaSGD` as the inner-loop optimizer. + +## Usage -# Usage Specify the seed to train. + ```bash ### Run MAML -python run_MAML.py --seed 1 +python maml.py --seed 1 ``` -# Results +## Results + The training curve and testing curve between initial policy and adapted policy validate the effectiveness of algorithms. +
- +
diff --git a/examples/MAML-RL/helpers/__init__.py b/examples/MAML-RL/helpers/__init__.py index a83c9eee..9855e0b3 100644 --- a/examples/MAML-RL/helpers/__init__.py +++ b/examples/MAML-RL/helpers/__init__.py @@ -1,12 +1,26 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== # This file is modified from: # https://github.com/tristandeleu/pytorch-maml-rl +# ============================================================================== + from gym.envs.registration import register -register('TabularMDP-v0', - entry_point='helpers.Tabular_mdp:TabularMDPEnv', - kwargs={ - 'num_states': 10, - 'num_actions': 5, - 'max_episode_steps': 10, - 'seed': 1 - }) + +register( + 'TabularMDP-v0', + entry_point='helpers.tabular_mdp:TabularMDPEnv', + kwargs={'num_states': 10, 'num_actions': 5, 'max_episode_steps': 10, 'seed': 1}, +) diff --git a/examples/MAML-RL/helpers/policy.py b/examples/MAML-RL/helpers/policy.py index 0ef52c6a..9b32b8c8 100644 --- a/examples/MAML-RL/helpers/policy.py +++ b/examples/MAML-RL/helpers/policy.py @@ -1,5 +1,20 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== # This file is modified from: # https://github.com/tristandeleu/pytorch-maml-rl +# ============================================================================== import torch import torch.nn as nn @@ -7,10 +22,11 @@ class CategoricalMLPPolicy(nn.Module): - """Policy network based on a multi-layer perceptron (MLP), with a - `Categorical` distribution output. This policy network can be used on tasks - with discrete action spaces (eg. `TabularMDPEnv`). + """Policy network based on a multi-layer perceptron (MLP), with a + `Categorical` distribution output. This policy network can be used on tasks + with discrete action spaces (eg. `TabularMDPEnv`). """ + def __init__( self, input_size, diff --git a/examples/MAML-RL/helpers/Tabular_mdp.py b/examples/MAML-RL/helpers/tabular_mdp.py similarity index 65% rename from examples/MAML-RL/helpers/Tabular_mdp.py rename to examples/MAML-RL/helpers/tabular_mdp.py index b5786296..5f8dcc17 100644 --- a/examples/MAML-RL/helpers/Tabular_mdp.py +++ b/examples/MAML-RL/helpers/tabular_mdp.py @@ -14,55 +14,53 @@ # ============================================================================== # This file is modified from: # https://github.com/tristandeleu/pytorch-maml-rl +# ============================================================================== import gym import numpy as np from gym import spaces from gym.utils import seeding -from gym.wrappers.time_limit import TimeLimit class TabularMDPEnv(gym.Env): """Tabular MDP problems, as described in [1]. - At each time step, the agent chooses one of `num_actions` actions, say `i`, - receives a reward sampled from a Normal distribution with mean `m_i` and - variance 1 (fixed across all tasks), and reaches a new state following the - dynamics of the Markov Decision Process (MDP). The tabular MDP tasks are - generated by sampling the mean rewards from a Normal distribution with mean - 1 and variance 1, and sampling the transition probabilities from a uniform + At each time step, the agent chooses one of `num_actions` actions, say `i`, + receives a reward sampled from a Normal distribution with mean `m_i` and + variance 1 (fixed across all tasks), and reaches a new state following the + dynamics of the Markov Decision Process (MDP). The tabular MDP tasks are + generated by sampling the mean rewards from a Normal distribution with mean + 1 and variance 1, and sampling the transition probabilities from a uniform Dirichlet distribution (ie. with parameter 1). [1] Yan Duan, John Schulman, Xi Chen, Peter L. Bartlett, Ilya Sutskever, Pieter Abbeel, "RL2: Fast Reinforcement Learning via Slow Reinforcement Learning", 2016 (https://arxiv.org/abs/1611.02779) """ - def __init__(self, - num_states, - num_actions, - max_episode_steps, - seed, - task={}): + + def __init__(self, num_states, num_actions, max_episode_steps, seed, task={}): super(TabularMDPEnv, self).__init__() self.max_episode_steps = max_episode_steps self.num_states = num_states self.num_actions = num_actions self.action_space = spaces.Discrete(num_actions) - self.observation_space = spaces.Box(low=0.0, - high=1.0, - shape=(num_states, ), - dtype=np.float32) + self.observation_space = spaces.Box( + low=0.0, high=1.0, shape=(num_states,), dtype=np.float32 + ) self._task = task self._transitions = task.get( 'transitions', - np.full((num_states, num_actions, num_states), - 1.0 / num_states, - dtype=np.float32)) + np.full( + (num_states, num_actions, num_states), + 1.0 / num_states, + dtype=np.float32, + ), + ) self._rewards_mean = task.get( - 'rewards_mean', - np.zeros((num_states, num_actions), dtype=np.float32)) + 'rewards_mean', np.zeros((num_states, num_actions), dtype=np.float32) + ) self._state = 0 self._elapsed_steps = None @@ -73,18 +71,17 @@ def seed(self, seed=None): return [seed] def sample_tasks(self, num_tasks): - transitions = self.np_random.dirichlet(np.ones(self.num_states), - size=(num_tasks, - self.num_states, - self.num_actions)) - rewards_mean = self.np_random.normal(1.0, - 1.0, - size=(num_tasks, self.num_states, - self.num_actions)) - tasks = [{ - 'transitions': transition, - 'rewards_mean': reward_mean - } for (transition, reward_mean) in zip(transitions, rewards_mean)] + transitions = self.np_random.dirichlet( + np.ones(self.num_states), + size=(num_tasks, self.num_states, self.num_actions), + ) + rewards_mean = self.np_random.normal( + 1.0, 1.0, size=(num_tasks, self.num_states, self.num_actions) + ) + tasks = [ + {'transitions': transition, 'rewards_mean': reward_mean} + for (transition, reward_mean) in zip(transitions, rewards_mean) + ] return tasks def reset_task(self, task): @@ -106,9 +103,9 @@ def step(self, action): mean = self._rewards_mean[self._state, action] reward = self.np_random.normal(mean, 1.0) - self._state = self.np_random.choice(self.num_states, - p=self._transitions[self._state, - action]) + self._state = self.np_random.choice( + self.num_states, p=self._transitions[self._state, action] + ) observation = np.zeros(self.num_states, dtype=np.float32) observation[self._state] = 1.0 self._elapsed_steps += 1 diff --git a/examples/MAML-RL/run_MAML.py b/examples/MAML-RL/maml.py similarity index 72% rename from examples/MAML-RL/run_MAML.py rename to examples/MAML-RL/maml.py index 8d328f08..8734e000 100644 --- a/examples/MAML-RL/run_MAML.py +++ b/examples/MAML-RL/maml.py @@ -20,9 +20,12 @@ import numpy as np import torch import torch.optim as optim -from helpers.policy import CategoricalMLPPolicy -import TorchOpt +import torchopt + + +from helpers.policy import CategoricalMLPPolicy # isort: skip + TASK_NUM = 40 TRAJ_NUM = 20 @@ -49,8 +52,7 @@ class Traj(NamedTuple): def sample_traj(env, task, policy): env.reset_task(task) obs_buf = np.zeros(shape=(TRAJ_LEN, TRAJ_NUM, STATE_DIM), dtype=np.float32) - next_obs_buf = np.zeros(shape=(TRAJ_LEN, TRAJ_NUM, STATE_DIM), - dtype=np.float32) + next_obs_buf = np.zeros(shape=(TRAJ_LEN, TRAJ_NUM, STATE_DIM), dtype=np.float32) acs_buf = np.zeros(shape=(TRAJ_LEN, TRAJ_NUM), dtype=np.int8) rews_buf = np.zeros(shape=(TRAJ_LEN, TRAJ_NUM), dtype=np.float32) gammas_buf = np.zeros(shape=(TRAJ_LEN, TRAJ_NUM), dtype=np.float32) @@ -70,11 +72,13 @@ def sample_traj(env, task, policy): rews_buf[step][batch] = rew gammas_buf[step][batch] = done * GAMMA ob = next_ob - return Traj(obs=obs_buf, - acs=acs_buf, - next_obs=next_obs_buf, - rews=rews_buf, - gammas=gammas_buf) + return Traj( + obs=obs_buf, + acs=acs_buf, + next_obs=next_obs_buf, + rews=rews_buf, + gammas=gammas_buf, + ) def a2c_loss(traj, policy, value_coef): @@ -85,8 +89,9 @@ def a2c_loss(traj, policy, value_coef): returns = [] g = next_values[-1, :] for i in reversed(range(next_values.shape[0])): - g = traj.rews[i, :] + traj.gammas[i, :] * \ - ((1 - lambdas[i, :]) * next_values[i, :] + lambdas[i, :] * g) + g = traj.rews[i, :] + traj.gammas[i, :] * ( + (1 - lambdas[i, :]) * next_values[i, :] + lambdas[i, :] * g + ) returns.insert(0, g) lambda_returns = torch.from_numpy(np.array(returns)) pi, values = policy(torch.from_numpy(traj.obs)) @@ -102,16 +107,19 @@ def a2c_loss(traj, policy, value_coef): def evaluate(env, seed, task_num, policy): pre_reward_ls = [] post_reward_ls = [] - inner_opt = TorchOpt.MetaSGD(policy, lr=0.5) + inner_opt = torchopt.MetaSGD(policy, lr=0.5) env = gym.make( 'TabularMDP-v0', - **dict(num_states=STATE_DIM, - num_actions=ACTION_DIM, - max_episode_steps=TRAJ_LEN, - seed=args.seed)) + **dict( + num_states=STATE_DIM, + num_actions=ACTION_DIM, + max_episode_steps=TRAJ_LEN, + seed=args.seed, + ), + ) tasks = env.sample_tasks(num_tasks=task_num) - policy_state_dict = TorchOpt.extract_state_dict(policy) - optim_state_dict = TorchOpt.extract_state_dict(inner_opt) + policy_state_dict = torchopt.extract_state_dict(policy) + optim_state_dict = torchopt.extract_state_dict(inner_opt) for idx in range(task_num): for _ in range(inner_iters): pre_trajs = sample_traj(env, tasks[idx], policy) @@ -124,8 +132,8 @@ def evaluate(env, seed, task_num, policy): pre_reward_ls.append(np.sum(pre_trajs.rews, axis=0).mean()) post_reward_ls.append(np.sum(post_trajs.rews, axis=0).mean()) - TorchOpt.recover_state_dict(policy, policy_state_dict) - TorchOpt.recover_state_dict(inner_opt, optim_state_dict) + torchopt.recover_state_dict(policy, policy_state_dict) + torchopt.recover_state_dict(inner_opt, optim_state_dict) return pre_reward_ls, post_reward_ls @@ -136,13 +144,16 @@ def main(args): # Env env = gym.make( 'TabularMDP-v0', - **dict(num_states=STATE_DIM, - num_actions=ACTION_DIM, - max_episode_steps=TRAJ_LEN, - seed=args.seed)) + **dict( + num_states=STATE_DIM, + num_actions=ACTION_DIM, + max_episode_steps=TRAJ_LEN, + seed=args.seed, + ), + ) # Policy policy = CategoricalMLPPolicy(input_size=STATE_DIM, output_size=ACTION_DIM) - inner_opt = TorchOpt.MetaSGD(policy, lr=0.5) + inner_opt = torchopt.MetaSGD(policy, lr=0.5) outer_opt = optim.Adam(policy.parameters(), lr=1e-3) train_pre_reward = [] train_post_reward = [] @@ -156,8 +167,8 @@ def main(args): outer_opt.zero_grad() - policy_state_dict = TorchOpt.extract_state_dict(policy) - optim_state_dict = TorchOpt.extract_state_dict(inner_opt) + policy_state_dict = torchopt.extract_state_dict(policy) + optim_state_dict = torchopt.extract_state_dict(inner_opt) for idx in range(TASK_NUM): for _ in range(inner_iters): @@ -167,15 +178,14 @@ def main(args): post_trajs = sample_traj(env, tasks[idx], policy) outer_loss = a2c_loss(post_trajs, policy, value_coef=0.5) outer_loss.backward() - TorchOpt.recover_state_dict(policy, policy_state_dict) - TorchOpt.recover_state_dict(inner_opt, optim_state_dict) + torchopt.recover_state_dict(policy, policy_state_dict) + torchopt.recover_state_dict(inner_opt, optim_state_dict) # Logging train_pre_reward_ls.append(np.sum(pre_trajs.rews, axis=0).mean()) train_post_reward_ls.append(np.sum(post_trajs.rews, axis=0).mean()) outer_opt.step() - test_pre_reward_ls, test_post_reward_ls = evaluate( - env, args.seed, TASK_NUM, policy) + test_pre_reward_ls, test_post_reward_ls = evaluate(env, args.seed, TASK_NUM, policy) train_pre_reward.append(sum(train_pre_reward_ls) / TASK_NUM) train_post_reward.append(sum(train_post_reward_ls) / TASK_NUM) @@ -183,19 +193,16 @@ def main(args): test_post_reward.append(sum(test_post_reward_ls) / TASK_NUM) print('Train_iters', i) - print("train_pre_reward", sum(train_pre_reward_ls) / TASK_NUM) - print("train_post_reward", sum(train_post_reward_ls) / TASK_NUM) - print("test_pre_reward", sum(test_pre_reward_ls) / TASK_NUM) - print("test_post_reward", sum(test_post_reward_ls) / TASK_NUM) + print('train_pre_reward', sum(train_pre_reward_ls) / TASK_NUM) + print('train_post_reward', sum(train_post_reward_ls) / TASK_NUM) + print('test_pre_reward', sum(test_pre_reward_ls) / TASK_NUM) + print('test_post_reward', sum(test_post_reward_ls) / TASK_NUM) -if __name__ == "__main__": +if __name__ == '__main__': parser = argparse.ArgumentParser( - description='Reinforcement learning with ' - 'Model-Agnostic Meta-Learning (MAML) - Train') - parser.add_argument('--seed', - type=int, - default=1, - help='random seed (default: 1)') + description='Reinforcement learning with Model-Agnostic Meta-Learning (MAML) - Train' + ) + parser.add_argument('--seed', type=int, default=1, help='random seed (default: 1)') args = parser.parse_args() main(args) diff --git a/examples/MGRL/README.md b/examples/MGRL/README.md index 65299729..2ad228ac 100644 --- a/examples/MGRL/README.md +++ b/examples/MGRL/README.md @@ -1,8 +1,10 @@ # MGRL-examples -Code on toy example of meta-learning the discount factor in paper [Meta-Gradient Reinforcement Learning](https://arxiv.org/abs/1805.09801) using `TorchOpt`. We use `MetaSGD` as the inner-loop optimiser. +Code on toy example of meta-learning the discount factor in paper [Meta-Gradient Reinforcement Learning](https://arxiv.org/abs/1805.09801) using TorchOpt. We use `MetaSGD` as the inner-loop optimizer. + +## Usage -# Usage ```bash -### Run -python3 toy.py +### Run +python3 mgrl.py +``` diff --git a/examples/MGRL/toy.py b/examples/MGRL/mgrl.py similarity index 82% rename from examples/MGRL/toy.py rename to examples/MGRL/mgrl.py index a27d177f..152e4177 100644 --- a/examples/MGRL/toy.py +++ b/examples/MGRL/mgrl.py @@ -14,10 +14,10 @@ # ============================================================================== import torch -from torch import nn -from torch.nn import functional as F +import torch.nn as nn +import torch.nn.functional as F -import TorchOpt +import torchopt def test_gamma(): @@ -34,8 +34,7 @@ def get(): def rollout(trajectory, gamma): out = [trajectory[-1]] for i in reversed(range(9)): - out.append(trajectory[i] + - gamma[i] * out[-1].clone().detach_()) + out.append(trajectory[i] + gamma[i] * out[-1].clone().detach_()) out.reverse() return torch.hstack(out).view(10, 1) @@ -51,10 +50,10 @@ def forward(self, x): inner_iters = 1 outer_iters = 10000 net = ValueNetwork() - inner_optimizer = TorchOpt.MetaSGD(net, lr=5e-1) + inner_optimizer = torchopt.MetaSGD(net, lr=5e-1) gamma = torch.zeros(9, requires_grad=True) - meta_optimizer = TorchOpt.SGD([gamma], lr=5e-1) - net_state = TorchOpt.extract_state_dict(net) + meta_optimizer = torchopt.SGD([gamma], lr=5e-1) + net_state = torchopt.extract_state_dict(net) for i in range(outer_iters): for j in range(inner_iters): trajectory, state = Rollout.get() @@ -72,11 +71,11 @@ def forward(self, x): meta_optimizer.zero_grad() loss.backward() meta_optimizer.step() - TorchOpt.recover_state_dict(net, net_state) + torchopt.recover_state_dict(net, net_state) if i % 100 == 0: with torch.no_grad(): - print(f"epoch {i} | gamma: {torch.sigmoid(gamma)}") + print(f'epoch {i} | gamma: {torch.sigmoid(gamma)}') -if __name__ == "__main__": +if __name__ == '__main__': test_gamma() diff --git a/examples/few-shot/README.md b/examples/few-shot/README.md index d617b62d..d25eafc4 100644 --- a/examples/few-shot/README.md +++ b/examples/few-shot/README.md @@ -1,15 +1,18 @@ # MAML few-shot Omniglot classification-examples -Code On MAML few-shot Omniglot classification in paper [Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks](https://arxiv.org/abs/1703.03400) using `TorchOpt`. We use `MetaSGD` as the inner-loop optimiser. +Code on MAML few-shot Omniglot classification in paper [Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks](https://arxiv.org/abs/1703.03400) using TorchOpt. We use `MetaSGD` as the inner-loop optimizer. + +## Usage -# Usage ```bash -### Run -python3 maml-omniglot.py +### Run +python3 maml_omniglot.py ``` -# Results +## Results + The figure illustrate the experimental result. +
- +
diff --git a/examples/few-shot/maml-omniglot.py b/examples/few-shot/maml_omniglot.py similarity index 76% rename from examples/few-shot/maml-omniglot.py rename to examples/few-shot/maml_omniglot.py index b501a3f9..3f7a7f0f 100644 --- a/examples/few-shot/maml-omniglot.py +++ b/examples/few-shot/maml_omniglot.py @@ -29,7 +29,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This example shows how to use higher to do Model Agnostic Meta Learning (MAML) +This example shows how to use TorchOpt to do Model Agnostic Meta Learning (MAML) for few-shot Omniglot classification. For more details see the original MAML paper: https://arxiv.org/abs/1703.03400 @@ -47,12 +47,15 @@ import numpy as np import pandas as pd import torch +import torch.nn as nn import torch.nn.functional as F import torch.optim as optim -from support.omniglot_loaders import OmniglotNShot -from torch import nn -import TorchOpt +import torchopt + + +from support.omniglot_loaders import OmniglotNShot # isort: skip + mpl.use('Agg') plt.style.use('bmh') @@ -61,18 +64,11 @@ def main(): argparser = argparse.ArgumentParser() argparser.add_argument('--n_way', type=int, help='n way', default=5) - argparser.add_argument('--k_spt', - type=int, - help='k shot for support set', - default=5) - argparser.add_argument('--k_qry', - type=int, - help='k shot for query set', - default=15) - argparser.add_argument('--task_num', - type=int, - help='meta batch size, namely task num', - default=32) + argparser.add_argument('--k_spt', type=int, help='k shot for support set', default=5) + argparser.add_argument('--k_qry', type=int, help='k shot for query set', default=15) + argparser.add_argument( + '--task_num', type=int, help='meta batch size, namely task num', default=32 + ) argparser.add_argument('--seed', type=int, help='random seed', default=1) args = argparser.parse_args() @@ -100,16 +96,22 @@ def main(): # Before higher, models could *not* be created like this # and the parameters needed to be manually updated and copied # for the updates. - net = nn.Sequential(nn.Conv2d(1, 64, 3), - nn.BatchNorm2d(64, momentum=1., affine=True), - nn.ReLU(inplace=False), nn.MaxPool2d(2, 2), - nn.Conv2d(64, 64, 3), - nn.BatchNorm2d(64, momentum=1., affine=True), - nn.ReLU(inplace=False), nn.MaxPool2d(2, 2), - nn.Conv2d(64, 64, 3), - nn.BatchNorm2d(64, momentum=1., affine=True), - nn.ReLU(inplace=False), nn.MaxPool2d(2, 2), - nn.Flatten(), nn.Linear(64, args.n_way)).to(device) + net = nn.Sequential( + nn.Conv2d(1, 64, 3), + nn.BatchNorm2d(64, momentum=1.0, affine=True), + nn.ReLU(inplace=False), + nn.MaxPool2d(2, 2), + nn.Conv2d(64, 64, 3), + nn.BatchNorm2d(64, momentum=1.0, affine=True), + nn.ReLU(inplace=False), + nn.MaxPool2d(2, 2), + nn.Conv2d(64, 64, 3), + nn.BatchNorm2d(64, momentum=1.0, affine=True), + nn.ReLU(inplace=False), + nn.MaxPool2d(2, 2), + nn.Flatten(), + nn.Linear(64, args.n_way), + ).to(device) # We will use Adam to (meta-)optimize the initial parameters # to be adapted. @@ -125,7 +127,7 @@ def main(): def train(db, net, meta_opt, epoch, log): net.train() n_train_iter = db.x_train.shape[0] // db.batchsz - inner_opt = TorchOpt.MetaSGD(net, lr=1e-1) + inner_opt = torchopt.MetaSGD(net, lr=1e-1) for batch_idx in range(n_train_iter): start_time = time.time() @@ -146,8 +148,8 @@ def train(db, net, meta_opt, epoch, log): qry_accs = [] meta_opt.zero_grad() - net_state_dict = TorchOpt.extract_state_dict(net) - optim_state_dict = TorchOpt.extract_state_dict(inner_opt) + net_state_dict = torchopt.extract_state_dict(net) + optim_state_dict = torchopt.extract_state_dict(inner_opt) for i in range(task_num): # Optimize the likelihood of the support set by taking # gradient steps w.r.t. the model's parameters. @@ -165,8 +167,7 @@ def train(db, net, meta_opt, epoch, log): qry_logits = net(x_qry[i]) qry_loss = F.cross_entropy(qry_logits, y_qry[i]) qry_losses.append(qry_loss.detach()) - qry_acc = (qry_logits.argmax(dim=1) - == y_qry[i]).sum().item() / querysz + qry_acc = (qry_logits.argmax(dim=1) == y_qry[i]).sum().item() / querysz qry_accs.append(qry_acc) # Update the model's meta-parameters to optimize the query @@ -174,12 +175,12 @@ def train(db, net, meta_opt, epoch, log): # This unrolls through the gradient steps. qry_loss.backward() - TorchOpt.recover_state_dict(net, net_state_dict) - TorchOpt.recover_state_dict(inner_opt, optim_state_dict) + torchopt.recover_state_dict(net, net_state_dict) + torchopt.recover_state_dict(inner_opt, optim_state_dict) meta_opt.step() qry_losses = sum(qry_losses) / task_num - qry_accs = 100. * sum(qry_accs) / task_num + qry_accs = 100.0 * sum(qry_accs) / task_num i = epoch + float(batch_idx) / n_train_iter iter_time = time.time() - start_time @@ -187,13 +188,15 @@ def train(db, net, meta_opt, epoch, log): f'[Epoch {i:.2f}] Train Loss: {qry_losses:.2f} | Acc: {qry_accs:.2f} | Time: {iter_time:.2f}' ) - log.append({ - 'epoch': i, - 'loss': qry_losses, - 'acc': qry_accs, - 'mode': 'train', - 'time': time.time(), - }) + log.append( + { + 'epoch': i, + 'loss': qry_losses, + 'acc': qry_accs, + 'mode': 'train', + 'time': time.time(), + } + ) def test(db, net, epoch, log): @@ -204,7 +207,7 @@ def test(db, net, epoch, log): # adapting this code for research. net.train() n_test_iter = db.x_test.shape[0] // db.batchsz - inner_opt = TorchOpt.MetaSGD(net, lr=1e-1) + inner_opt = torchopt.MetaSGD(net, lr=1e-1) qry_losses = [] qry_accs = [] @@ -219,8 +222,8 @@ def test(db, net, epoch, log): # doesn't have to be duplicated between `train` and `test`? n_inner_iter = 5 - net_state_dict = TorchOpt.extract_state_dict(net) - optim_state_dict = TorchOpt.extract_state_dict(inner_opt) + net_state_dict = torchopt.extract_state_dict(net) + optim_state_dict = torchopt.extract_state_dict(inner_opt) for i in range(task_num): # Optimize the likelihood of the support set by taking # gradient steps w.r.t. the model's parameters. @@ -236,21 +239,21 @@ def test(db, net, epoch, log): qry_losses.append(qry_loss.detach()) qry_accs.append((qry_logits.argmax(dim=1) == y_qry[i]).detach()) - TorchOpt.recover_state_dict(net, net_state_dict) - TorchOpt.recover_state_dict(inner_opt, optim_state_dict) + torchopt.recover_state_dict(net, net_state_dict) + torchopt.recover_state_dict(inner_opt, optim_state_dict) qry_losses = torch.cat(qry_losses).mean().item() - qry_accs = 100. * torch.cat(qry_accs).float().mean().item() - print( - f'[Epoch {epoch+1:.2f}] Test Loss: {qry_losses:.2f} | Acc: {qry_accs:.2f}' + qry_accs = 100.0 * torch.cat(qry_accs).float().mean().item() + print(f'[Epoch {epoch+1:.2f}] Test Loss: {qry_losses:.2f} | Acc: {qry_accs:.2f}') + log.append( + { + 'epoch': epoch + 1, + 'loss': qry_losses, + 'acc': qry_accs, + 'mode': 'test', + 'time': time.time(), + } ) - log.append({ - 'epoch': epoch + 1, - 'loss': qry_losses, - 'acc': qry_accs, - 'mode': 'test', - 'time': time.time(), - }) def plot(log): diff --git a/examples/few-shot/support/omniglot_loaders.py b/examples/few-shot/support/omniglot_loaders.py index 95eba9ce..d857d386 100644 --- a/examples/few-shot/support/omniglot_loaders.py +++ b/examples/few-shot/support/omniglot_loaders.py @@ -11,15 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +# ============================================================================== # These Omniglot loaders are from Jackie Loong's PyTorch MAML implementation: # https://github.com/dragen1860/MAML-Pytorch # https://github.com/dragen1860/MAML-Pytorch/blob/master/omniglot.py # https://github.com/dragen1860/MAML-Pytorch/blob/master/omniglotNShot.py +# ============================================================================== import errno import os -import os.path import numpy as np import torch @@ -29,27 +29,27 @@ class Omniglot(data.Dataset): + """ + The items are ``(filename, category)``. The index of all the categories can be found in + :attr:`idx_classes`. + + Args: + root: the directory where the dataset will be stored + transform: how to transform the input + target_transform: how to transform the target + download: need to download the dataset + """ + urls = [ 'https://github.com/brendenlake/omniglot/raw/master/python/images_background.zip', - 'https://github.com/brendenlake/omniglot/raw/master/python/images_evaluation.zip' + 'https://github.com/brendenlake/omniglot/raw/master/python/images_evaluation.zip', ] raw_folder = 'raw' processed_folder = 'processed' training_file = 'training.pt' test_file = 'test.pt' - ''' - The items are (filename,category). The index of all the categories can be found in self.idx_classes - Args: - - root: the directory where the dataset will be stored - - transform: how to transform the input - - target_transform: how to transform the target - - download: need to download the dataset - ''' - def __init__(self, - root, - transform=None, - target_transform=None, - download=False): + + def __init__(self, root, transform=None, target_transform=None, download=False): self.root = root self.transform = transform self.target_transform = target_transform @@ -58,11 +58,9 @@ def __init__(self, if download: self.download() else: - raise RuntimeError('Dataset not found.' + - ' You can use download=True to download it') + raise RuntimeError('Dataset not found. You can use download=True to download it') - self.all_items = find_classes( - os.path.join(self.root, self.processed_folder)) + self.all_items = find_classes(os.path.join(self.root, self.processed_folder)) self.idx_classes = index_classes(self.all_items) def __getitem__(self, index): @@ -81,8 +79,9 @@ def __len__(self): return len(self.all_items) def _check_exists(self): - return os.path.exists(os.path.join(self.root, self.processed_folder, "images_evaluation")) and \ - os.path.exists(os.path.join(self.root, self.processed_folder, "images_background")) + return os.path.exists( + os.path.join(self.root, self.processed_folder, 'images_evaluation') + ) and os.path.exists(os.path.join(self.root, self.processed_folder, 'images_background')) def download(self): import zipfile @@ -110,22 +109,22 @@ def download(self): with open(file_path, 'wb') as f: f.write(data.read()) file_processed = os.path.join(self.root, self.processed_folder) - print("== Unzip from " + file_path + " to " + file_processed) + print('== Unzip from ' + file_path + ' to ' + file_processed) zip_ref = zipfile.ZipFile(file_path, 'r') zip_ref.extractall(file_processed) zip_ref.close() - print("Download finished.") + print('Download finished.') def find_classes(root_dir): retour = [] for (root, dirs, files) in os.walk(root_dir): for f in files: - if (f.endswith("png")): + if f.endswith('png'): r = root.split('/') lr = len(r) - retour.append((f, r[lr - 2] + "/" + r[lr - 1], root)) - print("== Found %d items " % len(retour)) + retour.append((f, r[lr - 2] + '/' + r[lr - 1], root)) + print('== Found %d items ' % len(retour)) return retour @@ -134,20 +133,12 @@ def index_classes(items): for i in items: if i[1] not in idx: idx[i[1]] = len(idx) - print("== Found %d classes" % len(idx)) + print('== Found %d classes' % len(idx)) return idx class OmniglotNShot: - def __init__(self, - root, - batchsz, - n_way, - k_shot, - k_query, - imgsz, - rng, - device=None): + def __init__(self, root, batchsz, n_way, k_shot, k_query, imgsz, rng, device=None): """ Different from mnistNShot, the :param root: @@ -166,16 +157,19 @@ def __init__(self, self.x = Omniglot( root, download=True, - transform=transforms.Compose([ - lambda x: Image.open(x).convert('L'), lambda x: x.resize( - (imgsz, imgsz)), - lambda x: np.reshape(x, (imgsz, imgsz, 1)), - lambda x: np.transpose(x, [2, 0, 1]), lambda x: x / 255. - ]), + transform=transforms.Compose( + [ + lambda x: Image.open(x).convert('L'), + lambda x: x.resize((imgsz, imgsz)), + lambda x: np.reshape(x, (imgsz, imgsz, 1)), + lambda x: np.transpose(x, [2, 0, 1]), + lambda x: x / 255.0, + ] + ), ) - temp = dict( - ) # {label:img1, img2..., 20 imgs, label2: img1, img2,... in total, 1623 label} + # {label: [img1, img2..., img20], label2: [img1, img2, ...], ... 1623 labels in total} + temp = {} for (img, label) in self.x: if label in temp.keys(): temp[label].append(img) @@ -183,13 +177,14 @@ def __init__(self, temp[label] = [img] self.x = [] - for label, imgs in temp.items( - ): # labels info deserted , each label contains 20imgs + for ( + label, + imgs, + ) in temp.items(): # labels info deserted , each label contains 20imgs self.x.append(np.array(imgs)) # as different class may have different number of imgs - self.x = np.array(self.x).astype( - np.float) # [[20 imgs],..., 1623 classes in total] + self.x = np.array(self.x).astype(np.float) # [[20 imgs],..., 1623 classes in total] # each character contains 20 imgs print('data shape:', self.x.shape) # [1623, 20, 84, 84, 1] temp = [] # Free memory @@ -215,17 +210,16 @@ def __init__(self, assert (k_shot + k_query) <= 20 # save pointer of current read batch in total cache - self.indexes = {"train": 0, "test": 0} + self.indexes = {'train': 0, 'test': 0} self.datasets = { - "train": self.x_train, - "test": self.x_test + 'train': self.x_train, + 'test': self.x_test, } # original data cached - print("DB: train", self.x_train.shape, "test", self.x_test.shape) + print('DB: train', self.x_train.shape, 'test', self.x_test.shape) self.datasets_cache = { - "train": self.load_data_cache( - self.datasets["train"]), # current epoch data cached - "test": self.load_data_cache(self.datasets["test"]) + 'train': self.load_data_cache(self.datasets['train']), # current epoch data cached + 'test': self.load_data_cache(self.datasets['test']), } def normalization(self): @@ -253,6 +247,7 @@ def load_data_cache(self, data_pack): :param data_pack: [cls_num, 20, 84, 84, 1] :return: A list with [support_set_x, support_set_y, target_x, target_y] ready to be fed to our networks """ + # take 5 way 1 shot as example: 5 * 1 setsz = self.k_shot * self.n_way querysz = self.k_query * self.n_way @@ -265,33 +260,29 @@ def load_data_cache(self, data_pack): for i in range(self.batchsz): # one batch means one set x_spt, y_spt, x_qry, y_qry = [], [], [], [] - selected_cls = self.rng.choice(data_pack.shape[0], self.n_way, - False) + selected_cls = self.rng.choice(data_pack.shape[0], self.n_way, False) for j, cur_class in enumerate(selected_cls): - selected_img = self.rng.choice(20, - self.k_shot + self.k_query, - False) + selected_img = self.rng.choice(20, self.k_shot + self.k_query, False) # meta-training and meta-test - x_spt.append( - data_pack[cur_class][selected_img[:self.k_shot]]) - x_qry.append( - data_pack[cur_class][selected_img[self.k_shot:]]) + x_spt.append(data_pack[cur_class][selected_img[: self.k_shot]]) + x_qry.append(data_pack[cur_class][selected_img[self.k_shot :]]) y_spt.append([j for _ in range(self.k_shot)]) y_qry.append([j for _ in range(self.k_query)]) # shuffle inside a batch perm = self.rng.permutation(self.n_way * self.k_shot) - x_spt = np.array(x_spt).reshape(self.n_way * self.k_shot, 1, - self.resize, self.resize)[perm] + x_spt = np.array(x_spt).reshape( + self.n_way * self.k_shot, 1, self.resize, self.resize + )[perm] y_spt = np.array(y_spt).reshape(self.n_way * self.k_shot)[perm] perm = self.rng.permutation(self.n_way * self.k_query) - x_qry = np.array(x_qry).reshape(self.n_way * self.k_query, 1, - self.resize, self.resize)[perm] - y_qry = np.array(y_qry).reshape(self.n_way * - self.k_query)[perm] + x_qry = np.array(x_qry).reshape( + self.n_way * self.k_query, 1, self.resize, self.resize + )[perm] + y_qry = np.array(y_qry).reshape(self.n_way * self.k_query)[perm] # append [sptsz, 1, 84, 84] => [b, setsz, 1, 84, 84] x_spts.append(x_spt) @@ -300,19 +291,18 @@ def load_data_cache(self, data_pack): y_qrys.append(y_qry) # [b, setsz, 1, 84, 84] - x_spts = np.array(x_spts).astype(np.float32).reshape( - self.batchsz, setsz, 1, self.resize, self.resize) - y_spts = np.array(y_spts).astype(np.int).reshape( - self.batchsz, setsz) + x_spts = np.array(x_spts, dtype=np.float32).reshape( + self.batchsz, setsz, 1, self.resize, self.resize + ) + y_spts = np.array(y_spts, dtype=np.int).reshape(self.batchsz, setsz) # [b, qrysz, 1, 84, 84] - x_qrys = np.array(x_qrys).astype(np.float32).reshape( - self.batchsz, querysz, 1, self.resize, self.resize) - y_qrys = np.array(y_qrys).astype(np.int).reshape( - self.batchsz, querysz) + x_qrys = np.array(x_qrys, dtype=np.float32).reshape( + self.batchsz, querysz, 1, self.resize, self.resize + ) + y_qrys = np.array(y_qrys, dtype=np.int).reshape(self.batchsz, querysz) x_spts, y_spts, x_qrys, y_qrys = [ - torch.from_numpy(z).to(self.device) - for z in [x_spts, y_spts, x_qrys, y_qrys] + torch.from_numpy(z).to(self.device) for z in [x_spts, y_spts, x_qrys, y_qrys] ] data_cache.append([x_spts, y_spts, x_qrys, y_qrys]) @@ -325,11 +315,11 @@ def next(self, mode='train'): :param mode: The name of the splitting (one of "train", "val", "test") :return: """ + # update cache if indexes is larger cached num if self.indexes[mode] >= len(self.datasets_cache[mode]): self.indexes[mode] = 0 - self.datasets_cache[mode] = self.load_data_cache( - self.datasets[mode]) + self.datasets_cache[mode] = self.load_data_cache(self.datasets[mode]) next_batch = self.datasets_cache[mode][self.indexes[mode]] self.indexes[mode] += 1 diff --git a/examples/requirements.txt b/examples/requirements.txt new file mode 100644 index 00000000..9e2e108e --- /dev/null +++ b/examples/requirements.txt @@ -0,0 +1,13 @@ +--extra-index-url https://download.pytorch.org/whl/cu116 +torch == 1.12 +torchvision +functorch + +--requirement ../requirements.txt + +gym >= 0.20.0, < 0.24.0a0 +matplotlib +pandas +seaborn +torchviz +pillow diff --git a/examples/visualize.py b/examples/visualize.py index 10307eda..56de2bd5 100644 --- a/examples/visualize.py +++ b/examples/visualize.py @@ -1,9 +1,24 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + import torch +import torch.nn as nn +import torch.nn.functional as F import torchviz -from torch import nn -from torch.nn import functional as F -import TorchOpt +import torchopt class Net(nn.Module): @@ -17,8 +32,8 @@ def forward(self, x, meta_param): def draw_torchviz(): net = Net(dim).cuda() - optimizer = TorchOpt.MetaAdam(net, lr=1e-3, use_accelerated_op=False) - meta_param = torch.tensor(1., requires_grad=True) + optimizer = torchopt.MetaAdam(net, lr=1e-3, use_accelerated_op=False) + meta_param = torch.tensor(1.0, requires_grad=True) xs = torch.ones(batch_size, dim).cuda() @@ -29,39 +44,34 @@ def draw_torchviz(): pred = net(xs, meta_param) loss = F.mse_loss(pred, torch.ones_like(pred)) # draw computation graph - torchviz.make_dot(loss).render("torchviz_graph", format="svg") + torchviz.make_dot(loss).render('torchviz_graph', format='svg') -def draw_TorchOpt(): +def draw_torchopt(): net = Net(dim).cuda() - optimizer = TorchOpt.MetaAdam(net, lr=1e-3, use_accelerated_op=True) - meta_param = torch.tensor(1., requires_grad=True) + optimizer = torchopt.MetaAdam(net, lr=1e-3, use_accelerated_op=True) + meta_param = torch.tensor(1.0, requires_grad=True) xs = torch.ones(batch_size, dim).cuda() pred = net(xs, meta_param) loss = F.mse_loss(pred, torch.ones_like(pred)) # set enable_visual - net_state_0 = TorchOpt.extract_state_dict(net, - enable_visual=True, - visual_prefix='step0.') + net_state_0 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step0.') optimizer.step(loss) # set enable_visual - net_state_1 = TorchOpt.extract_state_dict(net, - enable_visual=True, - visual_prefix='step1.') + net_state_1 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step1.') pred = net(xs, meta_param) loss = F.mse_loss(pred, torch.ones_like(pred)) # draw computation graph - TorchOpt.visual.make_dot( - loss, [net_state_0, net_state_1, { - meta_param: "meta_param" - }]).render("TorchOpt_graph", format="svg") + torchopt.visual.make_dot(loss, [net_state_0, net_state_1, {meta_param: 'meta_param'}]).render( + 'torchopt_graph', format='svg' + ) if __name__ == '__main__': dim = 5 batch_size = 2 draw_torchviz() - draw_TorchOpt() + draw_torchopt() diff --git a/image/TorchOpt.png b/image/TorchOpt.png index 76327240..04a90032 100644 Binary files a/image/TorchOpt.png and b/image/TorchOpt.png differ diff --git a/image/logo-large.png b/image/logo-large.png new file mode 100644 index 00000000..81c753be Binary files /dev/null and b/image/logo-large.png differ diff --git a/image/logo.png b/image/logo.png new file mode 100644 index 00000000..098b8a17 Binary files /dev/null and b/image/logo.png differ diff --git a/image/time.png b/image/time.png old mode 100755 new mode 100644 diff --git a/image/torchviz_torchopt.jpg b/image/torchviz_torchopt.jpg old mode 100755 new mode 100644 diff --git a/include/adam_op/adam_op.h b/include/adam_op/adam_op.h index 7834ed0b..38ebd0cc 100644 --- a/include/adam_op/adam_op.h +++ b/include/adam_op/adam_op.h @@ -18,11 +18,12 @@ #include -#include "common.h" +#include "include/common.h" -namespace TorchOpt { -TensorArray<3> adamForwardInplace(torch::Tensor& updates, torch::Tensor& mu, - torch::Tensor& nu, const float b1, +namespace torchopt { +TensorArray<3> adamForwardInplace(const torch::Tensor& updates, + const torch::Tensor& mu, + const torch::Tensor& nu, const float b1, const float b2, const float eps, const float eps_root, const int count); @@ -50,4 +51,4 @@ TensorArray<2> adamBackwardUpdates(const torch::Tensor& dupdates, const torch::Tensor& new_mu, const torch::Tensor& new_nu, const float b1, const float b2, const int count); -} // namespace TorchOpt +} // namespace torchopt diff --git a/include/adam_op/adam_op_impl.cuh b/include/adam_op/adam_op_impl.cuh index 8e4d8777..c9dcba85 100644 --- a/include/adam_op/adam_op_impl.cuh +++ b/include/adam_op/adam_op_impl.cuh @@ -18,11 +18,12 @@ #include -#include "common.h" +#include "include/common.h" -namespace TorchOpt { -TensorArray<3> adamForwardInplaceCUDA(torch::Tensor &updates, torch::Tensor &mu, - torch::Tensor &nu, const float b1, +namespace torchopt { +TensorArray<3> adamForwardInplaceCUDA(const torch::Tensor &updates, + const torch::Tensor &mu, + const torch::Tensor &nu, const float b1, const float b2, const float eps, const float eps_root, const int count); @@ -52,4 +53,4 @@ TensorArray<2> adamBackwardUpdatesCUDA(const torch::Tensor &dupdates, const torch::Tensor &new_nu, const float b1, const float b2, const int count); -} // namespace TorchOpt +} // namespace torchopt diff --git a/include/adam_op/adam_op_impl.h b/include/adam_op/adam_op_impl.h index 1bf99046..87562fb1 100644 --- a/include/adam_op/adam_op_impl.h +++ b/include/adam_op/adam_op_impl.h @@ -18,11 +18,12 @@ #include -#include "common.h" +#include "include/common.h" -namespace TorchOpt { -TensorArray<3> adamForwardInplaceCPU(torch::Tensor& updates, torch::Tensor& mu, - torch::Tensor& nu, const float b1, +namespace torchopt { +TensorArray<3> adamForwardInplaceCPU(const torch::Tensor& updates, + const torch::Tensor& mu, + const torch::Tensor& nu, const float b1, const float b2, const float eps, const float eps_root, const int count); @@ -51,4 +52,4 @@ TensorArray<2> adamBackwardUpdatesCPU(const torch::Tensor& dupdates, const torch::Tensor& new_nu, const float b1, const float b2, const int count); -} // namespace TorchOpt +} // namespace torchopt diff --git a/include/common.h b/include/common.h index e5c681b6..e4362013 100644 --- a/include/common.h +++ b/include/common.h @@ -18,7 +18,7 @@ #include -namespace TorchOpt { +namespace torchopt { template using TensorArray = std::array; } diff --git a/include/utils.h b/include/utils.h index ddc0a992..92f9bad0 100644 --- a/include/utils.h +++ b/include/utils.h @@ -22,7 +22,7 @@ #define __forceinline__ __inline__ __attribute__((always_inline)) #endif -namespace TorchOpt { +namespace torchopt { __forceinline__ size_t getTensorPlainSize(const torch::Tensor& tensor) { const auto dim = tensor.dim(); size_t n = 1; @@ -31,4 +31,4 @@ __forceinline__ size_t getTensorPlainSize(const torch::Tensor& tensor) { } return n; } -} // namespace TorchOpt +} // namespace torchopt diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d76dd3dc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,101 @@ +[build-system] +requires = ["setuptools", "torch == 1.12", "numpy", "pybind11"] +build-backend = "setuptools.build_meta" + +[project] +name = "torchopt" +description = "A Jax-style optimizer for PyTorch." +readme = "README.md" +requires-python = ">= 3.7" +authors = [ + {name = "TorchOpt Contributors"}, + {name = "Xuehai Pan", email = "XuehaiPan@pku.edu.cn"}, + {name = "Jie Ren", email = "jieren9806@gmail.com"}, + {name = "Xidong Feng", email = "xidong.feng.20@ucl.ac.uk"}, + {name = "Bo Liu", email = "benjaminliu.eecs@gmail.com"}, +] +license = {file = "LICENSE"} +keywords = [ + "PyTorch", + "functorch", + "JAX", + "Meta-Learning", + "Optimizer", + "Differentiable Optimizer", + "Functional Programming", +] +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: Apache Software License 2.0 (Apache-2.0)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Operating System :: POSIX :: Linux", + "Environment :: GPU", + "Environment :: GPU :: NVIDIA CUDA", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Intelligence", +] +dependencies = [ + "torch == 1.12", + "jax[cpu] >= 0.3", + "numpy", + "graphviz", + "typing-extensions", +] +dynamic = [ + "version", +] + +[project.urls] +Homepage = "https://github.com/metaopt/TorchOpt" +Repository = "https://github.com/metaopt/TorchOpt" +Documentation = "https://torchopt.readthedocs.io" +"Bug Report" = "https://github.com/metaopt/TorchOpt/issues" + +[tool.setuptools.packages.find] +include = ["torchopt", "torchopt.*"] + +[tool.black] +safe = true +line-length = 100 +skip-string-normalization = true +target-version = ["py37", "py38", "py39", "py310"] + +[tool.isort] +profile = "black" +src_paths = ["torchopt", "examples", "tests"] +indent = 4 +line_length = 100 +lines_after_imports = 2 +multi_line_output = 3 + +[tool.mypy] +allow_redefinition = true +check_untyped_defs = true +disallow_incomplete_defs = false +disallow_untyped_defs = false +ignore_missing_imports = true +no_implicit_optional = true +pretty = true +show_error_codes = true +show_error_context = true +show_traceback = true +strict_equality = true +strict_optional = true +warn_no_return = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_configs = true +warn_unused_ignores = true + +[tool.pydocstyle] +convention = "google" + +[tool.doc8] +max-line-length = 500 diff --git a/requirements.txt b/requirements.txt index cdff8c3e..21fb120c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -jax[cpu] +torch == 1.12 +jax[cpu] >= 0.3 +numpy graphviz -torch \ No newline at end of file +typing-extensions diff --git a/setup.py b/setup.py index ea627c34..169a767c 100644 --- a/setup.py +++ b/setup.py @@ -1,117 +1,88 @@ import os import pathlib +import shutil import sys from setuptools import find_packages, setup -from setuptools.command.build_ext import build_ext -from torch.utils import cpp_extension -class MyBuild(build_ext): - def run(self): - self.build_cmake() +try: + from pybind11.setup_helpers import Pybind11Extension as Extension + from pybind11.setup_helpers import build_ext +except ImportError: + from setuptools import Extension + from setuptools.command.build_ext import build_ext - def copy(self, build_temp): - from distutils.file_util import copy_file - cwd = str(pathlib.Path().absolute()) - src = os.path.join('.', build_temp, 'src') - ops = os.listdir(src) - for op in ops: - op_path = os.path.join(src, op) - if not os.path.isdir(op_path): - continue - files = os.listdir(op_path) - for file in files: - if file.split('.')[-1] == 'so': - copy_file(os.path.join(op_path, file), - os.path.join(cwd, 'TorchOpt', '_lib')) +HERE = pathlib.Path(__file__).absolute().parent - def build_cmake(self): - cwd = pathlib.Path().absolute() +sys.path.insert(0, str(HERE / 'torchopt')) +import version # noqa - build_temp = f"{pathlib.Path(self.build_temp)}" - os.makedirs(build_temp, exist_ok=True) - config = "Debug" if self.debug else "Release" +class CMakeExtension(Extension): + def __init__(self, name, source_dir='.', **kwargs): + super().__init__(name, sources=[], **kwargs) + self.source_dir = os.path.abspath(source_dir) - PYTHON_INCLUDE_DIR = "" - for path in self.include_dirs: - PYTHON_INCLUDE_DIR += path + ';' - TORCH_INCLUDE_PATH = "" - for path in cpp_extension.include_paths(): - TORCH_INCLUDE_PATH += path + ';' +class cmake_build_ext(build_ext): + def build_extension(self, ext): + if not isinstance(ext, CMakeExtension): + super().build_extension(ext) + return + + import pybind11 + from torch.utils import cpp_extension + + cmake = shutil.which('cmake') + if cmake is None: + raise RuntimeError('Cannot find CMake executable.') + + build_temp = pathlib.Path(self.build_temp) + build_temp.mkdir(parents=True, exist_ok=True) - TORCH_LIBRARY_PATH = "" - for path in cpp_extension.library_paths(): - TORCH_LIBRARY_PATH += path + ';' + config = 'Debug' if self.debug else 'Release' + + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + print(self.get_ext_fullpath(ext.name)) + + PYTHON_INCLUDE_DIR = ';'.join(self.include_dirs) + TORCH_INCLUDE_PATH = ';'.join(cpp_extension.include_paths()) + TORCH_LIBRARY_PATH = ';'.join(cpp_extension.library_paths()) cmake_args = [ - "-DPYTHON_INCLUDE_DIR=" + PYTHON_INCLUDE_DIR, - "-DTORCH_INCLUDE_PATH=" + TORCH_INCLUDE_PATH, - "-DTORCH_LIBRARY_PATH=" + TORCH_LIBRARY_PATH, - "-DCMAKE_BUILD_TYPE=" + config + f'-DCMAKE_BUILD_TYPE={config}', + f'-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{config.upper()}={extdir}', + f'-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_{config.upper()}={self.build_temp}', + f'-DPYTHON_EXECUTABLE={sys.executable}', + f'-DPYBIND11_CMAKE_DIR={pybind11.get_cmake_dir()}', + f'-DPYTHON_INCLUDE_DIR={PYTHON_INCLUDE_DIR}', + f'-DTORCH_INCLUDE_PATH={TORCH_INCLUDE_PATH}', + f'-DTORCH_LIBRARY_PATH={TORCH_LIBRARY_PATH}', ] - build_args = ["--config", config, "--", "-j4"] - - os.chdir(build_temp) - self.spawn(["cmake", f"{str(cwd)}"] + cmake_args) - if not self.dry_run: - self.spawn(["cmake", "--build", "."] + build_args) - os.chdir(str(cwd)) - self.copy(build_temp) - - -class download_shared(): - def __init__(self): - import urllib - dir_path = os.path.dirname(os.path.realpath(__file__)) - print(f"setup.py at {dir_path}") - print("downloading shared libraries") - op_urls = [] - if sys.version_info >= (3, 8) and sys.version_info < (3, 9): - op_urls.append( - "https://torchopt.oss-cn-beijing.aliyuncs.com/torch1_11/adam_op.cpython-38-x86_64-linux-gnu.so" - ) - elif sys.version_info >= (3, 9) and sys.version_info < (3, 10): - op_urls.append( - "https://torchopt.oss-cn-beijing.aliyuncs.com/torch1_11/adam_op.cpython-39-x86_64-linux-gnu.so" - ) - - if len(op_urls) == 0: - import warnings - warnings.warn("no pre-compiled libraries for you python version") - return + build_args = ['--config', config] - for url in op_urls: - data = urllib.request.urlopen(url) - filename = url.rpartition('/')[-1] - file_path = os.path.join(dir_path, 'TorchOpt', '_lib', filename) - with open(file_path, 'wb') as f: - f.write(data.read()) - print("shared libraries downloaded") + if ( + 'CMAKE_BUILD_PARALLEL_LEVEL' not in os.environ + and hasattr(self, 'parallel') + and self.parallel + ): + build_args.append(f'-j{self.parallel}') + try: + os.chdir(build_temp) + self.spawn(['cmake', ext.source_dir] + cmake_args) + if not self.dry_run: + self.spawn(['cmake', '--build', '.'] + build_args) + finally: + os.chdir(HERE) -if 'build_from_source' not in sys.argv: - download_shared() setup( - name="TorchOpt", - version="0.4.1", - author="TorchOpt Contributors", - author_email="jieren9806@gmail.com", - description="A Jax-style optimizer.", - license="Apache License Version 2.0", - keywords="meta learning", - url="https://github.com/metaopt/TorchOpt", - packages=find_packages(), - package_data={"": ["_lib/*.so"]}, + version=version.__version__, + package_data={'sharedlib': ['_lib/*.so']}, include_package_data=True, - cmdclass={'build_from_source': MyBuild}, - install_requires=[ - 'jax[cpu]', - 'torch==1.11', - 'graphviz', - ], + cmdclass={'build_ext': cmake_build_ext}, + ext_modules=[CMakeExtension('torchopt._lib.adam_op', source_dir=HERE)], ) diff --git a/src/adam_op/CMakeLists.txt b/src/adam_op/CMakeLists.txt index 88991ad0..cea0371b 100644 --- a/src/adam_op/CMakeLists.txt +++ b/src/adam_op/CMakeLists.txt @@ -47,4 +47,4 @@ pybind11_add_module(adam_op adam_op.cpp adam_op_impl.cpp adam_op_impl.cu) target_link_libraries( adam_op PRIVATE ${TORCH_LIBRARIES} - ) +) diff --git a/src/adam_op/adam_op.cpp b/src/adam_op/adam_op.cpp index b4e12ca8..a11c0116 100644 --- a/src/adam_op/adam_op.cpp +++ b/src/adam_op/adam_op.cpp @@ -13,17 +13,18 @@ // limitations under the License. // ============================================================================== -#include "adam_op/adam_op.h" +#include "include/adam_op/adam_op.h" #include #include -#include "adam_op/adam_op_impl.cuh" -#include "adam_op/adam_op_impl.h" +#include "include/adam_op/adam_op_impl.cuh" +#include "include/adam_op/adam_op_impl.h" -namespace TorchOpt { -TensorArray<3> adamForwardInplace(torch::Tensor& updates, torch::Tensor& mu, - torch::Tensor& nu, const float b1, +namespace torchopt { +TensorArray<3> adamForwardInplace(const torch::Tensor& updates, + const torch::Tensor& mu, + const torch::Tensor& nu, const float b1, const float b2, const float eps, const float eps_root, const int count) { if (updates.device().is_cuda()) { @@ -34,7 +35,7 @@ TensorArray<3> adamForwardInplace(torch::Tensor& updates, torch::Tensor& mu, } else { throw std::runtime_error("Not implemented"); } -}; +} torch::Tensor adamForwardMu(const torch::Tensor& updates, const torch::Tensor& mu, const float b1) { if (updates.device().is_cuda()) { @@ -44,7 +45,7 @@ torch::Tensor adamForwardMu(const torch::Tensor& updates, } else { throw std::runtime_error("Not implemented"); } -}; +} torch::Tensor adamForwardNu(const torch::Tensor& updates, const torch::Tensor& nu, const float b2) { @@ -55,7 +56,7 @@ torch::Tensor adamForwardNu(const torch::Tensor& updates, } else { throw std::runtime_error("Not implemented"); } -}; +} torch::Tensor adamForwardUpdates(const torch::Tensor& new_mu, const torch::Tensor& new_nu, const float b1, @@ -68,7 +69,7 @@ torch::Tensor adamForwardUpdates(const torch::Tensor& new_mu, } else { throw std::runtime_error("Not implemented"); } -}; +} TensorArray<2> adamBackwardMu(const torch::Tensor& dmu, const torch::Tensor& updates, @@ -80,7 +81,7 @@ TensorArray<2> adamBackwardMu(const torch::Tensor& dmu, } else { throw std::runtime_error("Not implemented"); } -}; +} TensorArray<2> adamBackwardNu(const torch::Tensor& dnu, const torch::Tensor& updates, @@ -92,7 +93,7 @@ TensorArray<2> adamBackwardNu(const torch::Tensor& dnu, } else { throw std::runtime_error("Not implemented"); } -}; +} TensorArray<2> adamBackwardUpdates(const torch::Tensor& dupdates, const torch::Tensor& updates, @@ -108,15 +109,15 @@ TensorArray<2> adamBackwardUpdates(const torch::Tensor& dupdates, } else { throw std::runtime_error("Not implemented"); } -}; -} // namespace TorchOpt +} +} // namespace torchopt PYBIND11_MODULE(adam_op, m) { - m.def("forward_", &TorchOpt::adamForwardInplace); - m.def("forwardMu", &TorchOpt::adamForwardMu); - m.def("forwardNu", &TorchOpt::adamForwardNu); - m.def("forwardUpdates", &TorchOpt::adamForwardUpdates); - m.def("backwardMu", &TorchOpt::adamBackwardMu); - m.def("backwardNu", &TorchOpt::adamBackwardNu); - m.def("backwardUpdates", &TorchOpt::adamBackwardUpdates); + m.def("forward_", &torchopt::adamForwardInplace); + m.def("forwardMu", &torchopt::adamForwardMu); + m.def("forwardNu", &torchopt::adamForwardNu); + m.def("forwardUpdates", &torchopt::adamForwardUpdates); + m.def("backwardMu", &torchopt::adamBackwardMu); + m.def("backwardNu", &torchopt::adamBackwardNu); + m.def("backwardUpdates", &torchopt::adamBackwardUpdates); } diff --git a/src/adam_op/adam_op_impl.cpp b/src/adam_op/adam_op_impl.cpp index fe951f16..16be5251 100644 --- a/src/adam_op/adam_op_impl.cpp +++ b/src/adam_op/adam_op_impl.cpp @@ -13,16 +13,16 @@ // limitations under the License. // ============================================================================== -#include "adam_op/adam_op_impl.h" +#include "include/adam_op/adam_op_impl.h" #include #include #include -#include "utils.h" +#include "include/utils.h" -namespace TorchOpt { +namespace torchopt { using std::size_t; namespace { template @@ -50,8 +50,9 @@ void adamForwardInplaceCPUKernel( } } // namespace -TensorArray<3> adamForwardInplaceCPU(torch::Tensor& updates, torch::Tensor& mu, - torch::Tensor& nu, const float b1, +TensorArray<3> adamForwardInplaceCPU(const torch::Tensor& updates, + const torch::Tensor& mu, + const torch::Tensor& nu, const float b1, const float b2, const float eps, const float eps_root, const int count) { using other_t = float; @@ -99,7 +100,7 @@ torch::Tensor adamForwardMuCPU(const torch::Tensor& updates, mu_out.data_ptr()); })); return mu_out; -}; +} namespace { template @@ -132,7 +133,7 @@ torch::Tensor adamForwardNuCPU(const torch::Tensor& updates, nu_out.data_ptr()); })); return nu_out; -}; +} namespace { template @@ -176,7 +177,7 @@ torch::Tensor adamForwardUpdatesCPU(const torch::Tensor& new_mu, updates_out.data_ptr()); })); return updates_out; -}; +} namespace { template @@ -210,7 +211,7 @@ TensorArray<2> adamBackwardMuCPU(const torch::Tensor& dmu, dmu_out.data_ptr()); })); return TensorArray<2>{std::move(dupdates_out), std::move(dmu_out)}; -}; +} namespace { template @@ -246,7 +247,7 @@ TensorArray<2> adamBackwardNuCPU(const torch::Tensor& dnu, dupdates_out.data_ptr(), dnu_out.data_ptr()); })); return TensorArray<2>{std::move(dupdates_out), std::move(dnu_out)}; -}; +} namespace { template @@ -305,5 +306,5 @@ TensorArray<2> adamBackwardUpdatesCPU(const torch::Tensor& dupdates, n, dmu_out.data_ptr(), dnu_out.data_ptr()); })); return TensorArray<2>{std::move(dmu_out), std::move(dnu_out)}; -}; -} // namespace TorchOpt +} +} // namespace torchopt diff --git a/src/adam_op/adam_op_impl.cu b/src/adam_op/adam_op_impl.cu index ccb189d0..b10942eb 100644 --- a/src/adam_op/adam_op_impl.cu +++ b/src/adam_op/adam_op_impl.cu @@ -17,10 +17,10 @@ #include -#include "adam_op/adam_op_impl.cuh" -#include "utils.h" +#include "include/adam_op/adam_op_impl.cuh" +#include "include/utils.h" -namespace TorchOpt { +namespace torchopt { namespace { template @@ -49,8 +49,9 @@ __global__ void adamForwardInplaceCUDAKernel( } } // namespace -TensorArray<3> adamForwardInplaceCUDA(torch::Tensor &updates, torch::Tensor &mu, - torch::Tensor &nu, const float b1, +TensorArray<3> adamForwardInplaceCUDA(const torch::Tensor &updates, + const torch::Tensor &mu, + const torch::Tensor &nu, const float b1, const float b2, const float eps, const float eps_root, const int count) { using other_t = float; @@ -103,7 +104,7 @@ torch::Tensor adamForwardMuCUDA(const torch::Tensor &updates, mu_out.data_ptr()); })); return mu_out; -}; +} namespace { template @@ -140,7 +141,7 @@ torch::Tensor adamForwardNuCUDA(const torch::Tensor &updates, nu_out.data_ptr()); })); return nu_out; -}; +} namespace { template @@ -188,7 +189,7 @@ torch::Tensor adamForwardUpdatesCUDA(const torch::Tensor &new_mu, updates_out.data_ptr()); })); return updates_out; -}; +} namespace { template @@ -226,7 +227,7 @@ TensorArray<2> adamBackwardMuCUDA(const torch::Tensor &dmu, dmu_out.data_ptr()); })); return TensorArray<2>{std::move(dupdates_out), std::move(dmu_out)}; -}; +} namespace { template @@ -266,7 +267,7 @@ TensorArray<2> adamBackwardNuCUDA(const torch::Tensor &dnu, dupdates_out.data_ptr(), dnu_out.data_ptr()); })); return TensorArray<2>{std::move(dupdates_out), std::move(dnu_out)}; -}; +} namespace { template @@ -328,5 +329,5 @@ TensorArray<2> adamBackwardUpdatesCUDA(const torch::Tensor &dupdates, n, dmu_out.data_ptr(), dnu_out.data_ptr()); })); return TensorArray<2>{std::move(dmu_out), std::move(dnu_out)}; -}; -} // namespace TorchOpt +} +} // namespace torchopt diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 00000000..6cf7a2a1 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,21 @@ +--extra-index-url https://download.pytorch.org/whl/cu116 +torch == 1.12 +torchvision +functorch + +--requirement ../requirements.txt + +pytest +pytest-cov +pytest-xdist +isort +black >= 22.6.0 +pylint +mypy +flake8 +flake8-bugbear +doc8 +pydocstyle +pyenchant +cpplint +pre-commit diff --git a/tests/unit/high_level/test_high_level_inplace.py b/tests/unit/high_level/test_high_level_inplace.py index dc55ce0c..03e206d9 100644 --- a/tests/unit/high_level/test_high_level_inplace.py +++ b/tests/unit/high_level/test_high_level_inplace.py @@ -16,12 +16,13 @@ import copy import unittest +import pytest import torch -from torch.nn import functional as F +import torch.nn.functional as F from torch.utils import data from torchvision import models -from TorchOpt import SGD, Adam, RMSProp +import torchopt class HighLevelInplace(unittest.TestCase): @@ -33,8 +34,7 @@ def setUpClass(cls): cls.model_backup = copy.deepcopy(cls.model) cls.batch_size = 2 - cls.dataset = data.TensorDataset(torch.randn(2, 3, 224, 224), - torch.randint(0, 1000, (2, ))) + cls.dataset = data.TensorDataset(torch.randn(2, 3, 224, 224), torch.randint(0, 1000, (2,))) cls.loader = data.DataLoader(cls.dataset, cls.batch_size, False) cls.lr = 1e-3 @@ -45,7 +45,7 @@ def setUp(self) -> None: self.model_ref = copy.deepcopy(self.model_backup) def test_sgd(self) -> None: - optim = SGD(self.model.parameters(), self.lr) + optim = torchopt.SGD(self.model.parameters(), self.lr) optim_ref = torch.optim.SGD(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: pred = self.model(xs) @@ -60,20 +60,17 @@ def test_sgd(self) -> None: optim_ref.step() with torch.no_grad(): - for p, p_ref in zip(self.model.parameters(), - self.model_ref.parameters()): + for p, p_ref in zip(self.model.parameters(), self.model_ref.parameters()): mse = F.mse_loss(p, p_ref) self.assertAlmostEqual(float(mse), 0) - for b, b_ref in zip(self.model.buffers(), - self.model_ref.buffers()): + for b, b_ref in zip(self.model.buffers(), self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) def test_adam(self) -> None: - optim = Adam(self.model.parameters(), self.lr) + optim = torchopt.Adam(self.model.parameters(), self.lr) optim_ref = torch.optim.Adam(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: pred = self.model(xs) @@ -88,22 +85,19 @@ def test_adam(self) -> None: optim_ref.step() with torch.no_grad(): - for p, p_ref in zip(self.model.parameters(), - self.model_ref.parameters()): + for p, p_ref in zip(self.model.parameters(), self.model_ref.parameters()): mse = F.mse_loss(p, p_ref) self.assertAlmostEqual(float(mse), 0) - for b, b_ref in zip(self.model.buffers(), - self.model_ref.buffers()): + for b, b_ref in zip(self.model.buffers(), self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) def test_accelerated_adam_cpu(self) -> None: self.model self.model_ref - optim = Adam(self.model.parameters(), self.lr, use_accelerated_op=True) + optim = torchopt.Adam(self.model.parameters(), self.lr, use_accelerated_op=True) optim_ref = torch.optim.Adam(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: xs = xs @@ -120,22 +114,20 @@ def test_accelerated_adam_cpu(self) -> None: optim_ref.step() with torch.no_grad(): - for p, p_ref in zip(self.model.parameters(), - self.model_ref.parameters()): + for p, p_ref in zip(self.model.parameters(), self.model_ref.parameters()): mse = F.mse_loss(p, p_ref) self.assertAlmostEqual(float(mse), 0) - for b, b_ref in zip(self.model.buffers(), - self.model_ref.buffers()): + for b, b_ref in zip(self.model.buffers(), self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) + @pytest.mark.skipif(not torch.cuda.is_available(), reason='No CUDA device available.') def test_accelerated_adam_cuda(self) -> None: self.model.cuda() self.model_ref.cuda() - optim = Adam(self.model.parameters(), self.lr, use_accelerated_op=True) + optim = torchopt.Adam(self.model.parameters(), self.lr, use_accelerated_op=True) optim_ref = torch.optim.Adam(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: xs = xs.cuda() @@ -152,21 +144,19 @@ def test_accelerated_adam_cuda(self) -> None: optim_ref.step() with torch.no_grad(): - for p, p_ref in zip(self.model.parameters(), - self.model_ref.parameters()): + for p, p_ref in zip(self.model.parameters(), self.model_ref.parameters()): mse = F.mse_loss(p, p_ref) self.assertAlmostEqual(float(mse), 0) - for b, b_ref in zip(self.model.buffers(), - self.model_ref.buffers()): + for b, b_ref in zip(self.model.buffers(), self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) def test_rmsprop(self) -> None: - optim = RMSProp(self.model.parameters(), self.lr, - decay=0.99) # pytorch uses 0.99 as the default value + optim = torchopt.RMSProp( + self.model.parameters(), self.lr, decay=0.99 + ) # pytorch uses 0.99 as the default value optim_ref = torch.optim.RMSprop(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: pred = self.model(xs) @@ -181,17 +171,14 @@ def test_rmsprop(self) -> None: optim_ref.step() with torch.no_grad(): - for p, p_ref in zip(self.model.parameters(), - self.model_ref.parameters()): + for p, p_ref in zip(self.model.parameters(), self.model_ref.parameters()): mse = F.mse_loss(p, p_ref) self.assertAlmostEqual( float(mse), 0, delta=1e-4 ) # Optax and pytorch have different implementation - for b, b_ref in zip(self.model.buffers(), - self.model_ref.buffers()): + for b, b_ref in zip(self.model.buffers(), self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) diff --git a/tests/unit/low_level/test_low_level_inplace.py b/tests/unit/low_level/test_low_level_inplace.py index de9d9861..09f39ec9 100644 --- a/tests/unit/low_level/test_low_level_inplace.py +++ b/tests/unit/low_level/test_low_level_inplace.py @@ -17,13 +17,13 @@ import unittest import functorch +import pytest import torch -from torch.nn import functional as F +import torch.nn.functional as F from torch.utils import data from torchvision import models -import TorchOpt -from TorchOpt import adam, rmsprop, sgd +import torchopt class LowLevelInplace(unittest.TestCase): @@ -35,8 +35,7 @@ def setUpClass(cls): cls.model_backup = copy.deepcopy(cls.model) cls.batch_size = 2 - cls.dataset = data.TensorDataset(torch.randn(2, 3, 224, 224), - torch.randint(0, 1000, (2, ))) + cls.dataset = data.TensorDataset(torch.randn(2, 3, 224, 224), torch.randint(0, 1000, (2,))) cls.loader = data.DataLoader(cls.dataset, cls.batch_size, False) cls.lr = 1e-3 @@ -47,9 +46,8 @@ def setUp(self) -> None: self.model_ref = copy.deepcopy(self.model_backup) def test_sgd(self) -> None: - fun, params, buffers = functorch.make_functional_with_buffers( - self.model) - optim = sgd(self.lr) + fun, params, buffers = functorch.make_functional_with_buffers(self.model) + optim = torchopt.sgd(self.lr) optim_state = optim.init(params) optim_ref = torch.optim.SGD(self.model_ref.parameters(), self.lr) @@ -61,7 +59,7 @@ def test_sgd(self) -> None: grad = torch.autograd.grad(loss, params) updates, optim_state = optim.update(grad, optim_state) - params = TorchOpt.apply_updates(params, updates) + params = torchopt.apply_updates(params, updates) optim_ref.zero_grad() loss_ref.backward() @@ -73,15 +71,13 @@ def test_sgd(self) -> None: self.assertAlmostEqual(float(mse), 0) for b, b_ref in zip(buffers, self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) def test_adam(self) -> None: - fun, params, buffers = functorch.make_functional_with_buffers( - self.model) - optim = adam(self.lr) + fun, params, buffers = functorch.make_functional_with_buffers(self.model) + optim = torchopt.adam(self.lr) optim_state = optim.init(params) optim_ref = torch.optim.Adam(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: @@ -92,7 +88,7 @@ def test_adam(self) -> None: grad = torch.autograd.grad(loss, params) updates, optim_state = optim.update(grad, optim_state) - params = TorchOpt.apply_updates(params, updates) + params = torchopt.apply_updates(params, updates) optim_ref.zero_grad() loss_ref.backward() @@ -103,17 +99,15 @@ def test_adam(self) -> None: self.assertAlmostEqual(float(mse), 0) for b, b_ref in zip(buffers, self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) def test_accelerated_adam_cpu(self) -> None: self.model self.model_ref - fun, params, buffers = functorch.make_functional_with_buffers( - self.model) - optim = adam(self.lr, use_accelerated_op=True) + fun, params, buffers = functorch.make_functional_with_buffers(self.model) + optim = torchopt.adam(self.lr, use_accelerated_op=True) optim_state = optim.init(params) optim_ref = torch.optim.Adam(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: @@ -126,7 +120,7 @@ def test_accelerated_adam_cpu(self) -> None: grad = torch.autograd.grad(loss, params) updates, optim_state = optim.update(grad, optim_state) - params = TorchOpt.apply_updates(params, updates) + params = torchopt.apply_updates(params, updates) optim_ref.zero_grad() loss_ref.backward() @@ -137,17 +131,16 @@ def test_accelerated_adam_cpu(self) -> None: self.assertAlmostEqual(float(mse), 0) for b, b_ref in zip(buffers, self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) + @pytest.mark.skipif(not torch.cuda.is_available(), reason='No CUDA device available.') def test_accelerated_adam_cuda(self) -> None: self.model.cuda() self.model_ref.cuda() - fun, params, buffers = functorch.make_functional_with_buffers( - self.model) - optim = adam(self.lr, use_accelerated_op=True) + fun, params, buffers = functorch.make_functional_with_buffers(self.model) + optim = torchopt.adam(self.lr, use_accelerated_op=True) optim_state = optim.init(params) optim_ref = torch.optim.Adam(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: @@ -160,7 +153,7 @@ def test_accelerated_adam_cuda(self) -> None: grad = torch.autograd.grad(loss, params) updates, optim_state = optim.update(grad, optim_state) - params = TorchOpt.apply_updates(params, updates) + params = torchopt.apply_updates(params, updates) optim_ref.zero_grad() loss_ref.backward() @@ -171,16 +164,13 @@ def test_accelerated_adam_cuda(self) -> None: self.assertAlmostEqual(float(mse), 0) for b, b_ref in zip(buffers, self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) def test_rmsprop(self) -> None: - fun, params, buffers = functorch.make_functional_with_buffers( - self.model) - optim = rmsprop(self.lr, - decay=0.99) # pytorch uses 0.99 as the default value + fun, params, buffers = functorch.make_functional_with_buffers(self.model) + optim = torchopt.rmsprop(self.lr, decay=0.99) # pytorch uses 0.99 as the default value optim_state = optim.init(params) optim_ref = torch.optim.RMSprop(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: @@ -191,7 +181,7 @@ def test_rmsprop(self) -> None: grad = torch.autograd.grad(loss, params) updates, optim_state = optim.update(grad, optim_state) - params = TorchOpt.apply_updates(params, updates) + params = torchopt.apply_updates(params, updates) optim_ref.zero_grad() loss_ref.backward() @@ -204,8 +194,7 @@ def test_rmsprop(self) -> None: ) # Optax and pytorch have different implementation for b, b_ref in zip(buffers, self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) diff --git a/tests/unit/test_clip.py b/tests/unit/test_clip.py index b66c6f9b..7907b9a9 100644 --- a/tests/unit/test_clip.py +++ b/tests/unit/test_clip.py @@ -17,13 +17,12 @@ import unittest import torch -from torch.nn import functional as F +import torch.nn.functional as F from torch.nn.utils import clip_grad_norm_ from torch.utils import data from torchvision import models -import TorchOpt -from TorchOpt import Optimizer, sgd +import torchopt class HighLevelInplace(unittest.TestCase): @@ -35,12 +34,11 @@ def setUpClass(cls): cls.model_ref = copy.deepcopy(cls.model) cls.batch_size = 2 - cls.dataset = data.TensorDataset(torch.randn(2, 3, 224, 224), - torch.randint(0, 1000, (2, ))) + cls.dataset = data.TensorDataset(torch.randn(2, 3, 224, 224), torch.randint(0, 1000, (2,))) cls.loader = data.DataLoader(cls.dataset, cls.batch_size, False) cls.lr = 1e0 - cls.max_norm = 10. + cls.max_norm = 10.0 def setUp(self) -> None: torch.manual_seed(0) @@ -48,10 +46,11 @@ def setUp(self) -> None: self.model_ref = copy.deepcopy(self.model_backup) def test_sgd(self) -> None: - chain = TorchOpt.combine.chain( - TorchOpt.clip.clip_grad_norm(max_norm=self.max_norm), - sgd(lr=self.lr)) - optim = Optimizer(self.model.parameters(), chain) + chain = torchopt.combine.chain( + torchopt.clip.clip_grad_norm(max_norm=self.max_norm), + torchopt.sgd(lr=self.lr), + ) + optim = torchopt.Optimizer(self.model.parameters(), chain) optim_ref = torch.optim.SGD(self.model_ref.parameters(), self.lr) for xs, ys in self.loader: pred = self.model(xs) @@ -63,20 +62,16 @@ def test_sgd(self) -> None: optim.step() optim_ref.zero_grad() loss_ref.backward() - clip_grad_norm_(self.model_ref.parameters(), - max_norm=self.max_norm) + clip_grad_norm_(self.model_ref.parameters(), max_norm=self.max_norm) optim_ref.step() with torch.no_grad(): - for p, p_ref in zip(self.model.parameters(), - self.model_ref.parameters()): + for p, p_ref in zip(self.model.parameters(), self.model_ref.parameters()): mse = F.mse_loss(p, p_ref) self.assertAlmostEqual(float(mse), 0) - for b, b_ref in zip(self.model.buffers(), - self.model_ref.buffers()): + for b, b_ref in zip(self.model.buffers(), self.model_ref.buffers()): b = b.float() if not b.is_floating_point() else b - b_ref = b_ref.float( - ) if not b_ref.is_floating_point() else b_ref + b_ref = b_ref.float() if not b_ref.is_floating_point() else b_ref mse = F.mse_loss(b, b_ref) self.assertAlmostEqual(float(mse), 0) diff --git a/tests/unit/test_schedule.py b/tests/unit/test_schedule.py index 0143cb7f..b1681949 100644 --- a/tests/unit/test_schedule.py +++ b/tests/unit/test_schedule.py @@ -15,14 +15,14 @@ import unittest -import TorchOpt +import torchopt class TestSchedule(unittest.TestCase): @classmethod def setUpClass(cls): - cls.init_value = 1. - cls.end_value = 0. + cls.init_value = 1.0 + cls.end_value = 0.0 cls.gap_value = cls.init_value - cls.end_value cls.transition_steps = 10 cls.transition_begin = 1 @@ -31,15 +31,18 @@ def setUp(self) -> None: pass def test_linear(self) -> None: - schedule = TorchOpt.schedule.linear_schedule( + schedule = torchopt.schedule.linear_schedule( init_value=self.init_value, end_value=self.end_value, transition_steps=self.transition_steps, - transition_begin=self.transition_begin) + transition_begin=self.transition_begin, + ) for i in range(self.transition_begin, self.transition_steps): lr = schedule(i) - lr_gt = self.init_value - self.gap_value * \ - (i - self.transition_begin) / self.transition_steps + lr_gt = ( + self.init_value + - self.gap_value * (i - self.transition_begin) / self.transition_steps + ) self.assertEqual(lr, lr_gt) diff --git a/third_party/pybind11 b/third_party/pybind11 deleted file mode 160000 index ad0de0f5..00000000 --- a/third_party/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ad0de0f5a6bebbebbeb7f8f2f15c0c1430f34268 diff --git a/torchopt/__init__.py b/torchopt/__init__.py new file mode 100644 index 00000000..3f94afeb --- /dev/null +++ b/torchopt/__init__.py @@ -0,0 +1,48 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""TorchOpt: a high-performance optimizer library built upon PyTorch.""" + +from torchopt._src import accelerated_op_available, clip, combine, hook, schedule, visual +from torchopt._src.alias import adam, rmsprop, sgd +from torchopt._src.optimizer import SGD, Adam, Optimizer, RMSProp, meta +from torchopt._src.optimizer.meta import MetaAdam, MetaOptimizer, MetaRMSProp, MetaSGD +from torchopt._src.update import apply_updates +from torchopt._src.utils import extract_state_dict, recover_state_dict, stop_gradient +from torchopt.version import __version__ + + +__all__ = [ + 'accelerated_op_available', + 'clip', + 'combine', + 'hook', + 'schedule', + 'visual', + 'adam', + 'rmsprop', + 'sgd', + 'Optimizer', + 'SGD', + 'Adam', + 'RMSProp', + 'MetaOptimizer', + 'MetaSGD', + 'MetaAdam', + 'MetaRMSProp', + 'apply_updates', + 'extract_state_dict', + 'recover_state_dict', + 'stop_gradient', +] diff --git a/TorchOpt/_lib/__init__.py b/torchopt/_lib/__init__.py similarity index 100% rename from TorchOpt/_lib/__init__.py rename to torchopt/_lib/__init__.py diff --git a/torchopt/_lib/adam_op.pyi b/torchopt/_lib/adam_op.pyi new file mode 100644 index 00000000..47f04d2b --- /dev/null +++ b/torchopt/_lib/adam_op.pyi @@ -0,0 +1,57 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ==============================================================================\ + +# isort: off + +from typing import Tuple + +import torch + +def forward_( + updates: torch.Tensor, + mu: torch.Tensor, + nu: torch.Tensor, + b1: float, + b2: float, + eps: float, + eps_root: float, + count: int, +) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: ... +def forwardMu(updates: torch.Tensor, mu: torch.Tensor, b1: float) -> torch.Tensor: ... +def forwardNu(updates: torch.Tensor, nu: torch.Tensor, b2: float) -> torch.Tensor: ... +def forwardUpdates( + new_mu: torch.Tensor, + new_nu: torch.Tensor, + b1: float, + b2: float, + eps: float, + eps_root: float, + count: int, +) -> torch.Tensor: ... +def backwardMu( + dmu: torch.Tensor, updates: torch.Tensor, mu: torch.Tensor, b1: float +) -> Tuple[torch.Tensor, torch.Tensor]: ... +def backwardNu( + dnu: torch.Tensor, updates: torch.Tensor, nu: torch.Tensor, b2: float +) -> Tuple[torch.Tensor, torch.Tensor]: ... +def backwardUpdates( + dupdates: torch.Tensor, + updates: torch.Tensor, + new_mu: torch.Tensor, + new_nu: torch.Tensor, + b1: float, + b2: float, + count: int, +) -> Tuple[torch.Tensor, torch.Tensor]: ... diff --git a/TorchOpt/_src/accelerated_op/adam_op/__init__.py b/torchopt/_src/__init__.py similarity index 91% rename from TorchOpt/_src/accelerated_op/adam_op/__init__.py rename to torchopt/_src/__init__.py index 95a47453..75b3cf8d 100644 --- a/TorchOpt/_src/accelerated_op/adam_op/__init__.py +++ b/torchopt/_src/__init__.py @@ -13,4 +13,4 @@ # limitations under the License. # ============================================================================== -from TorchOpt._src.accelerated_op.adam_op.AdamOp import AdamOp +from torchopt._src.accelerated_op import accelerated_op_available diff --git a/TorchOpt/_src/accelerated_op/__init__.py b/torchopt/_src/accelerated_op/__init__.py similarity index 56% rename from TorchOpt/_src/accelerated_op/__init__.py rename to torchopt/_src/accelerated_op/__init__.py index c7cc70c4..4c7f1cd9 100644 --- a/TorchOpt/_src/accelerated_op/__init__.py +++ b/torchopt/_src/accelerated_op/__init__.py @@ -13,20 +13,33 @@ # limitations under the License. # ============================================================================== -from TorchOpt._src.accelerated_op.adam_op import AdamOp +from typing import Iterable, Optional, Union +import torch -def accelerated_op_available(devices=None): - import torch +from torchopt._src.accelerated_op.adam_op import AdamOp + + +def accelerated_op_available( + devices: Optional[Union[str, torch.device, Iterable[Union[str, torch.device]]]] = None +) -> bool: + """Check the availability of accelerated optimizer.""" op = AdamOp() + if devices is None: - devices = [torch.device("cuda"), torch.device("cpu")] + devices = [torch.device('cuda'), torch.device('cpu')] elif isinstance(devices, torch.device): devices = [devices] + elif isinstance(devices, str): + devices = [torch.device(devices)] + try: for device in devices: - updates = torch.tensor(1., device=device) + device = torch.device(device) + if device.type == 'cuda' and not torch.cuda.is_available(): + return False + updates = torch.tensor(1.0, device=device) op(updates, updates, updates, 1) return True - except: + except BaseException: # pylint: disable=broad-except return False diff --git a/TorchOpt/_src/__init__.py b/torchopt/_src/accelerated_op/adam_op/__init__.py similarity index 91% rename from TorchOpt/_src/__init__.py rename to torchopt/_src/accelerated_op/adam_op/__init__.py index f57f9a2d..d1203e92 100644 --- a/TorchOpt/_src/__init__.py +++ b/torchopt/_src/accelerated_op/adam_op/__init__.py @@ -13,4 +13,4 @@ # limitations under the License. # ============================================================================== -from .accelerated_op import accelerated_op_available +from torchopt._src.accelerated_op.adam_op.adam_op import AdamOp diff --git a/TorchOpt/_src/accelerated_op/adam_op/AdamOp.py b/torchopt/_src/accelerated_op/adam_op/adam_op.py similarity index 54% rename from TorchOpt/_src/accelerated_op/adam_op/AdamOp.py rename to torchopt/_src/accelerated_op/adam_op/adam_op.py index 92fd92d4..a59b00e6 100644 --- a/TorchOpt/_src/accelerated_op/adam_op/AdamOp.py +++ b/torchopt/_src/accelerated_op/adam_op/adam_op.py @@ -13,21 +13,29 @@ # limitations under the License. # ============================================================================== +# pylint: disable=c-extension-no-member,invalid-name + from typing import Any import torch -from TorchOpt._lib import adam_op +from torchopt._lib import adam_op # pylint: disable=no-name-in-module + + +class AdamOp: # pylint: disable=too-few-public-methods + """Fused accelerated Adam operators.""" + class MuOp(torch.autograd.Function): # pylint: disable=abstract-method + """Bias-corrected first moment estimate.""" -class AdamOp(object): - class MuOp(torch.autograd.Function): @staticmethod def jvp(ctx: Any, *grad_inputs: Any) -> Any: - pass + # pylint: disable-next=line-too-long + """Defines a formula for differentiating the operation with forward mode automatic differentiation.""" @staticmethod - def forward(ctx, *args): + def forward(ctx: Any, *args: Any, **kwargs: Any) -> Any: + """Performs the operation.""" updates, mu, b1 = args new_mu = adam_op.forwardMu(updates, mu, b1) ctx.save_for_backward(updates, mu) @@ -35,20 +43,26 @@ def forward(ctx, *args): return new_mu @staticmethod - def backward(ctx, *args): + def backward(ctx: Any, *args: Any) -> Any: + # pylint: disable-next=line-too-long + """Defines a formula for differentiating the operation with backward mode automatic differentiation (alias to the :meth:`vjp` method).""" dmu = args[0] updates, mu = ctx.saved_tensors b1 = ctx.b1 result = adam_op.backwardMu(dmu, updates, mu, b1) return result[0], result[1], None - class NuOp(torch.autograd.Function): + class NuOp(torch.autograd.Function): # pylint: disable=abstract-method + """Bias-corrected second raw moment estimate.""" + @staticmethod def jvp(ctx: Any, *grad_inputs: Any) -> Any: - pass + # pylint: disable-next=line-too-long + """Defines a formula for differentiating the operation with forward mode automatic differentiation.""" @staticmethod - def forward(ctx, *args): + def forward(ctx: Any, *args: Any, **kwargs: Any) -> Any: + """Performs the operation.""" updates, nu, b2 = args new_nu = adam_op.forwardNu(updates, nu, b2) ctx.save_for_backward(updates, nu) @@ -56,37 +70,45 @@ def forward(ctx, *args): return new_nu @staticmethod - def backward(ctx, *args): + def backward(ctx: Any, *args: Any) -> Any: + # pylint: disable-next=line-too-long + """Defines a formula for differentiating the operation with backward mode automatic differentiation (alias to the :meth:`vjp` function).""" dnu = args[0] updates, nu = ctx.saved_tensors b2 = ctx.b2 result = adam_op.backwardNu(dnu, updates, nu, b2) return result[0], result[1], None - class UpdatesOp(torch.autograd.Function): + class UpdatesOp(torch.autograd.Function): # pylint: disable=abstract-method + """Adam updates.""" + @staticmethod def jvp(ctx: Any, *grad_inputs: Any) -> Any: - pass + # pylint: disable-next=line-too-long + """Defines a formula for differentiating the operation with forward mode automatic differentiation.""" @staticmethod - def forward(ctx, *args): + def forward(ctx: Any, *args: Any, **kwargs: Any) -> Any: + """Performs the operation.""" new_mu, new_nu, (b1, b2, eps, eps_root, count) = args - new_updates = adam_op.forwardUpdates(new_mu, new_nu, b1, b2, eps, - eps_root, count) + new_updates = adam_op.forwardUpdates(new_mu, new_nu, b1, b2, eps, eps_root, count) ctx.save_for_backward(new_updates, new_mu, new_nu) ctx.others = (b1, b2, eps, eps_root, count) return new_updates @staticmethod - def backward(ctx, *args): + def backward(ctx: Any, *args: Any) -> Any: + # pylint: disable-next=line-too-long + """Defines a formula for differentiating the operation with backward mode automatic differentiation (alias to the :meth:`vjp` function).""" dupdates = args[0] updates, new_mu, new_nu = ctx.saved_tensors - b1, b2, eps, eps_root, count = ctx.others - result = adam_op.backwardUpdates(dupdates, updates, new_mu, new_nu, - b1, b2, count) + b1, b2, _, _, count = ctx.others + result = adam_op.backwardUpdates(dupdates, updates, new_mu, new_nu, b1, b2, count) return result[0], result[1], None - def __init__(self, b1=0.9, b2=0.999, eps=1e-8, eps_root=0., inplace=True): + # pylint: disable-next=too-many-arguments + def __init__(self, b1=0.9, b2=0.999, eps=1e-8, eps_root=0.0, inplace=True): + """The :meth:`__init__` function.""" self.b1 = b1 self.b2 = b2 self.eps = eps @@ -94,6 +116,7 @@ def __init__(self, b1=0.9, b2=0.999, eps=1e-8, eps_root=0., inplace=True): self.inplace = inplace def __call__(self, mu, nu, updates, count): + """The :meth:`__call__` function.""" if updates is None: return mu, nu, None if updates.is_cuda: @@ -101,14 +124,14 @@ def __call__(self, mu, nu, updates, count): torch.cuda.set_device(updates.device) if self.inplace: new_updates, new_mu, new_nu = adam_op.forward_( - updates, mu, nu, self.b1, self.b2, self.eps, self.eps_root, - count) + updates, mu, nu, self.b1, self.b2, self.eps, self.eps_root, count + ) else: new_mu = self.MuOp.apply(updates, mu, self.b1) new_nu = self.NuOp.apply(updates, nu, self.b2) new_updates = self.UpdatesOp.apply( - new_mu, new_nu, - (self.b1, self.b2, self.eps, self.eps_root, count)) + new_mu, new_nu, (self.b1, self.b2, self.eps, self.eps_root, count) + ) if updates.is_cuda: torch.cuda.set_device(current_device) return new_mu, new_nu, new_updates diff --git a/torchopt/_src/alias.py b/torchopt/_src/alias.py new file mode 100644 index 00000000..f27f5c3a --- /dev/null +++ b/torchopt/_src/alias.py @@ -0,0 +1,214 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +# This file is modified from: +# https://github.com/deepmind/optax/blob/master/optax/_src/alias.py +# ============================================================================== +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# pylint: disable=invalid-name + +from typing import Optional + +import jax + +from torchopt._src import base, combine, transform +from torchopt._src.typing import ScalarOrSchedule + + +def _scale_by_lr(lr: ScalarOrSchedule, flip_sign=True): + sign = -1 if flip_sign else 1 + if callable(lr): + + def schedule_wrapper(count): + def f(scaled_lr): + return sign * scaled_lr + + return jax.tree_map(f, lr(count)) # type: ignore + + return transform.scale_by_schedule(schedule_wrapper) + return transform.scale(sign * lr) + + +# pylint: disable=too-many-arguments +def adam( + lr: ScalarOrSchedule, + b1: float = 0.9, + b2: float = 0.999, + eps: float = 1e-8, + eps_root: float = 0.0, + moment_requires_grad: bool = False, + use_accelerated_op: bool = False, +) -> base.GradientTransformation: + """The functional Adam optimizer. + + Adam is an SGD variant with learning rate adaptation. The *learning rate* used for each weight + is computed from estimates of first- and second-order moments of the gradients (using suitable + exponential moving averages). + + References: + - Kingma et al, 2014: https://arxiv.org/abs/1412.6980 + + Args: + lr: This is a fixed global scaling factor. + b1: The exponential decay rate to track the first moment of past gradients. + b2: The exponential decay rate to track the second moment of past gradients. + eps: + A small constant applied to denominator outside of the square root (as in the Adam + paper) to avoid dividing by zero when rescaling. + eps_root: (default: :data:`0.0`) + A small constant applied to denominator inside the square root (as in RMSProp), to avoid + dividing by zero when rescaling. This is needed for example when computing + (meta-)gradients through Adam. + moment_requires_grad: (default: :data:`False`) + If :data:`True` the momentums will be created with flag ``requires_grad=True``, this + flag is often used in Meta Learning algorithms. + use_accelerated_op: (default: :data:`False`) + If :data:`True` use our implemented fused operator. + + Returns: + The corresponding :class:`GradientTransformation` instance. + """ + adam_inst = ( + transform.scale_by_accelerated_adam if use_accelerated_op else transform.scale_by_adam + ) + return combine.chain( + adam_inst( + b1=b1, + b2=b2, + eps=eps, + eps_root=eps_root, + moment_requires_grad=moment_requires_grad, + ), + _scale_by_lr(lr), + ) + + +def sgd( + lr: ScalarOrSchedule, + momentum: Optional[float] = None, + nesterov: bool = False, + moment_requires_grad: bool = False, +) -> base.GradientTransformation: + """The functional version of the canonical Stochastic Gradient Descent optimizer. + + This implements stochastic gradient descent. It also includes support for momentum, and nesterov + acceleration, as these are standard practice when using stochastic gradient descent to train + deep neural networks. + + References: + - Sutskever et al, 2013: http://proceedings.mlr.press/v28/sutskever13.pdf + + Args: + lr: This is a fixed global scaling factor. + momentum: (default: :data:`None`) + The ``decay`` rate used by the momentum term, when it is set to :data:`None`, then + momentum is not used at all. + nesterov: (default: :data:`False`) + Whether the nesterov momentum is used. + moment_requires_grad: (default: :data:`False`) + If :data:`True` the momentums will be created with flag ``requires_grad=True``, this + flag is often used in Meta-Learning algorithms. + + Returns: + A :class:`GradientTransformation` instance. + """ + return combine.chain( + ( + transform.trace( + decay=momentum, + nesterov=nesterov, + moment_requires_grad=moment_requires_grad, + ) + if momentum is not None + else base.identity() + ), + _scale_by_lr(lr), + ) + + +# pylint: disable=too-many-arguments +def rmsprop( + lr: ScalarOrSchedule, + decay: float = 0.9, + eps: float = 1e-8, + initial_scale: float = 0.0, + centered: bool = False, + momentum: Optional[float] = None, + nesterov: bool = False, +) -> base.GradientTransformation: + """The functional version of the RMSProp optimizer. + + RMSProp is an SGD variant with learning rate adaptation. The *learning rate* used for each + weight is scaled by a suitable estimate of the magnitude of the gradients on previous steps. + Several variants of RMSProp can be found in the literature. This alias provides an easy to + configure RMSProp optimizer that can be used to switch between several of these variants. + + References: + - Tieleman and Hinton, 2012: http://www.cs.toronto.edu/~hinton/coursera/lecture6/lec6.pdf + - Graves, 2013: https://arxiv.org/abs/1308.0850 + + Args: + lr: This is a fixed global scaling factor. + decay: The decay used to track the magnitude of previous gradients. + eps: A small numerical constant to avoid dividing by zero when rescaling. + initial_scale: (default: :data:`0.0`) + Initialization of accumulators tracking the magnitude of previous updates. PyTorch uses + :data:`0.0`, TensorFlow 1.x uses :data:`1.0`. When reproducing results from a paper, + verify the value used by the authors. + centered: (default: :data:`False`) + Whether the second moment or the variance of the past gradients is used to rescale the + latest gradients. + momentum: (default: :data:`None`) + The ``decay`` rate used by the momentum term, when it is set to :data:`None`, then + momentum is not used at all. + nesterov: (default: :data:`False`) + Whether the nesterov momentum is used. + + Returns: + The corresponding :class:`GradientTransformation` instance. + """ + if centered: + return combine.chain( + transform.scale_by_stddev(decay=decay, eps=eps, initial_scale=initial_scale), + _scale_by_lr(lr), + ( + transform.trace(decay=momentum, nesterov=nesterov) + if momentum is not None + else base.identity() + ), + ) + + return combine.chain( + transform.scale_by_rms(decay=decay, eps=eps, initial_scale=initial_scale), + _scale_by_lr(lr), + ( + transform.trace(decay=momentum, nesterov=nesterov) + if momentum is not None + else base.identity() + ), + ) diff --git a/torchopt/_src/base.py b/torchopt/_src/base.py new file mode 100644 index 00000000..d725d607 --- /dev/null +++ b/torchopt/_src/base.py @@ -0,0 +1,146 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +# This file is modified from: +# https://github.com/deepmind/optax/blob/master/optax/_src/base.py +# ============================================================================== +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from abc import abstractmethod +from typing import Callable, NamedTuple, Tuple + +from typing_extensions import Protocol + +from torchopt._src.typing import Numeric, TensorTree + + +OptState = TensorTree # States are arbitrary nests of `torch.Tensor`. +# Parameters are arbitrary nests of `torch.Tensor`. +Params = TensorTree +Updates = Params # Gradient updates are of the same type as parameters. + +Schedule = Callable[[Numeric], Numeric] + + +class EmptyState(NamedTuple): + """An empty state for the simplest stateless transformations.""" + + +class TransformInitFn(Protocol): # pylint: disable=too-few-public-methods + """A callable type for the :func:`init` step of a :class:`GradientTransformation`. + + The :func:`init` step takes a tree of ``params`` and uses these to construct an arbitrary + structured initial ``state`` for the gradient transformation. This may hold statistics of the + past updates or any other non static information. + """ + + @abstractmethod + def __call__(self, params: Params) -> OptState: + """The `init` function. + + Args: + params: + The initial value of the parameters. + + Returns: + The initial state of the gradient transformation. + """ + + +class TransformUpdateFn(Protocol): # pylint: disable=too-few-public-methods + """A callable type for the :func:`update` step of a :class:`GradientTransformation`. + + The :func:`update` step takes a tree of candidate parameter ``updates`` (e.g. their gradient + with respect to some loss), an arbitrary structured ``state``, and the current ``params`` of the + model being optimized. The ``params`` argument is optional, it must however be provided when + using transformations that require access to the current values of the parameters. + """ + + @abstractmethod + def __call__( + self, updates: Updates, state: OptState, inplace: bool = True + ) -> Tuple[Updates, OptState]: + """The `update` function. + + Args: + updates: A tree of candidate updates. + state: The state of the gradient transformation. + inplace: (optional) + If :data:`True`, modify updates and state using inplace operations. + + Returns: + The transformed ``updates``, and the updated ``state``. + """ + + +class GradientTransformation(NamedTuple): + """A pair of pure functions implementing a gradient transformation. + + TorchOpt optimizers are all implemented as *gradient transformations* like Optax. A gradient + transformation is defined to be a pair of pure functions, which are combined together in a + :class:`NamedTuple` so that they can be referred to by name. + + Since gradient transformations do not contain any internal state, all stateful optimizer + properties (such as the current step count when using optimizer schedules, or momentum values) + are passed through gradient transformations by using the optimizer *state* ``pytree``. Each time + a gradient transformation is applied, the state is computed and returned, ready to be passed to + the next call to the gradient transformation. + + Attributes: + init: + A pure function which, when called with an example instance of the parameters whose + gradients will be transformed, returns a ``pytree`` containing the initial value for the + optimizer state. + update: + A pure function which takes as input a pytree of updates (with the same tree structure + as the original params ``pytree`` passed to :attr:`init`), the previous optimizer state + (which may have been initialized using the :attr:`init` function), and optionally the + ``inplace`` flag. The :attr:`update` function then returns the computed gradient + updates, and a updates optimizer state. If the ``inplace`` flag is :data:`True`, the + output results are the same instance as the input. + """ + + init: TransformInitFn + update: TransformUpdateFn + + +def identity() -> GradientTransformation: + """Stateless identity transformation that leaves input gradients untouched. + + This function passes through the *gradient updates* unchanged. + + Returns: + An ``(init_fn, update_fn)`` tuple. + """ + + def init_fn(_): + return EmptyState() + + def update_fn(updates, state, inplace=False): # pylint: disable=unused-argument + return updates, state + + return GradientTransformation(init_fn, update_fn) diff --git a/TorchOpt/_src/clip.py b/torchopt/_src/clip.py similarity index 67% rename from TorchOpt/_src/clip.py rename to torchopt/_src/clip.py index 3b50c40c..58500ee7 100644 --- a/TorchOpt/_src/clip.py +++ b/torchopt/_src/clip.py @@ -20,21 +20,24 @@ import torch from torch._six import inf -from TorchOpt._src import base +from torchopt._src import base + ClipState = base.EmptyState def clip_grad_norm( - max_norm: float, - norm_type: float = 2., - error_if_nonfinite: bool = False) -> base.GradientTransformation: + max_norm: float, norm_type: float = 2.0, error_if_nonfinite: bool = False +) -> base.GradientTransformation: """Clips gradient norm of an iterable of parameters. + Args: - max_delta: The maximum absolute value for each element in the update. + max_delta: The maximum absolute value for each element in the update. + Returns: - An (init_fn, update_fn) tuple. + An ``(init_fn, update_fn)`` tuple. """ + def init_fn(params): del params return ClipState() @@ -45,35 +48,33 @@ def update_fn(updates, state, inplace=True): if g is not None: available_updates.append(g) if len(available_updates) == 0: - return torch.tensor(0.) + return torch.tensor(0.0) device = available_updates[0].device with torch.no_grad(): if norm_type == inf: norms = [p.abs().max().to(device) for p in available_updates] - total_norm = norms[0] if len(norms) == 1 else torch.max( - torch.stack(norms)) + total_norm = norms[0] if len(norms) == 1 else torch.max(torch.stack(norms)) else: total_norm = torch.norm( - torch.stack([ - torch.norm(p, norm_type).to(device) - for p in available_updates - ]), norm_type) - if error_if_nonfinite and torch.logical_or(total_norm.isnan(), - total_norm.isinf()): + torch.stack([torch.norm(p, norm_type).to(device) for p in available_updates]), + norm_type, + ) + if error_if_nonfinite and torch.logical_or(total_norm.isnan(), total_norm.isinf()): raise RuntimeError( - f'The total norm of order {norm_type} for gradients from ' - '`parameters` is non-finite, so it cannot be clipped. To disable ' - 'this error and scale the gradients by the non-finite norm anyway, ' - 'set `error_if_nonfinite=False`') + f'The total norm of order {norm_type} for gradients from `parameters` is ' + f'non-finite, so it cannot be clipped. To disable this error and scale the ' + f'gradients by the non-finite norm anyway, set `error_if_nonfinite=False`' + ) clip_coef = max_norm / (float(total_norm) + 1e-6) - # Note: multiplying by the clamped coef is redundant when the coef is clamped to 1, but doing so - # avoids a `if clip_coef < 1:` conditional which can require a CPU <=> device synchronization - # when the gradients do not reside in CPU memory. - clip_coef_clamped = min(clip_coef, 1.) + # Note: multiplying by the clamped coef is redundant when the coef is clamped to 1, but + # doing so avoids a `if clip_coef < 1:` conditional which can require a CPU <=> device + # synchronization when the gradients do not reside in CPU memory. + clip_coef_clamped = min(clip_coef, 1.0) if inplace: def f(g): return g.mul_(clip_coef_clamped) if g is not None else None + else: def f(g): diff --git a/TorchOpt/_src/combine.py b/torchopt/_src/combine.py similarity index 77% rename from TorchOpt/_src/combine.py rename to torchopt/_src/combine.py index 6a1b241c..c7b4b237 100644 --- a/TorchOpt/_src/combine.py +++ b/torchopt/_src/combine.py @@ -30,24 +30,23 @@ # limitations under the License. # ============================================================================== -from TorchOpt._src import base +from torchopt._src import base def chain(*args: base.GradientTransformation) -> base.GradientTransformation: """Applies a list of chainable update transformations. - Given a sequence of chainable transforms, `chain` returns an `init_fn` - that constructs a `state` by concatenating the states of the individual - transforms, and returns an `update_fn` which chains the update transformations - feeding the appropriate state to each. + Given a sequence of chainable transforms, :func:`chain` returns an :func:`init_fn` that + constructs a ``state`` by concatenating the states of the individual transforms, and returns an + :func:`update_fn` which chains the update transformations feeding the appropriate state to each. - Args: - *args: a sequence of chainable (init_fn, update_fn) tuples. - - Returns: - A single (init_fn, update_fn) tuple. - """ + Args: + *args: + A sequence of chainable ``(init_fn, update_fn)`` tuples. + Returns: + A single ``(init_fn, update_fn)`` tuple. + """ init_fns, update_fns = zip(*args) def init_fn(params): @@ -56,10 +55,11 @@ def init_fn(params): def update_fn(updates, state, inplace=True): if len(update_fns) != len(state): raise ValueError( - 'The number of updates and states has to be the same in ' - 'chain! Make sure you have called init first!') + 'The number of updates and states has to be the same in chain! Make sure you have ' + 'called init first!' + ) new_state = [] - for s, fn in zip(state, update_fns): + for s, fn in zip(state, update_fns): # pylint: disable=invalid-name updates, new_s = fn(updates, s, inplace) new_state.append(new_s) return updates, tuple(new_state) diff --git a/TorchOpt/_src/hook.py b/torchopt/_src/hook.py similarity index 77% rename from TorchOpt/_src/hook.py rename to torchopt/_src/hook.py index 95c6ba63..a0081991 100644 --- a/TorchOpt/_src/hook.py +++ b/torchopt/_src/hook.py @@ -16,25 +16,27 @@ import jax import torch -from .base import EmptyState, GradientTransformation +from torchopt._src.base import EmptyState, GradientTransformation def zero_nan_hook(g: torch.Tensor) -> torch.Tensor: + """Registers a zero nan hook to replace nan with zero.""" return torch.where(torch.isnan(g), torch.zeros_like(g), g) def register_hook(hook) -> GradientTransformation: """Stateless identity transformation that leaves input gradients untouched. - This function passes through the *gradient updates* unchanged. + This function passes through the *gradient updates* unchanged. + + Returns: + An ``(init_fn, update_fn)`` tuple. + """ - Returns: - An (init_fn, update_fn) tuple. - """ def init_fn(_): return EmptyState() - def update_fn(updates, state, inplace=False): + def update_fn(updates, state, inplace=False): # pylint: disable=unused-argument def f(g): return g.register_hook(hook) if g is not None else None diff --git a/TorchOpt/__init__.py b/torchopt/_src/optimizer/__init__.py similarity index 61% rename from TorchOpt/__init__.py rename to torchopt/_src/optimizer/__init__.py index 28e783d5..3d07bcdd 100644 --- a/TorchOpt/__init__.py +++ b/torchopt/_src/optimizer/__init__.py @@ -13,12 +13,8 @@ # limitations under the License. # ============================================================================== -from ._src import (accelerated_op_available, clip, combine, hook, schedule, - visual) -from ._src.alias import adam, rmsprop, sgd -from ._src.MetaOptimizer import MetaAdam, MetaOptimizer, MetaRMSProp, MetaSGD -from ._src.Optimizer import SGD, Adam, Optimizer, RMSProp -from ._src.update import apply_updates -from ._src.utils import extract_state_dict, recover_state_dict, stop_gradient - -__version__ = "0.4.1" +from torchopt._src.optimizer import meta +from torchopt._src.optimizer.adam import Adam +from torchopt._src.optimizer.base import Optimizer +from torchopt._src.optimizer.rmsprop import RMSProp +from torchopt._src.optimizer.sgd import SGD diff --git a/torchopt/_src/optimizer/adam.py b/torchopt/_src/optimizer/adam.py new file mode 100644 index 00000000..ff861334 --- /dev/null +++ b/torchopt/_src/optimizer/adam.py @@ -0,0 +1,72 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Iterable + +import torch + +from torchopt._src.alias import adam +from torchopt._src.optimizer.base import Optimizer +from torchopt._src.typing import ScalarOrSchedule + + +class Adam(Optimizer): + """The classic Adam optimizer. + + See Also: + - The functional Adam optimizer: :func:`torchopt.adam`. + - The differentiable meta-Adam optimizer: :class:`torchopt.MetaAdam`. + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + params: Iterable[torch.Tensor], + lr: ScalarOrSchedule, + b1: float = 0.9, + b2: float = 0.999, + eps: float = 1e-8, + eps_root: float = 0.0, + use_accelerated_op: bool = False, + ): + r"""The :meth:`init` function. + + Args: + params (iterable of torch.Tensor): An iterable of :class:`torch.Tensor`\s. Specifies + what tensors should be optimized. + lr: This is a fixed global scaling factor. + b1: The exponential decay rate to track the first moment of past gradients. + b2: The exponential decay rate to track the second moment of past gradients. + eps: A small constant applied to denominator outside of the square root (as in the Adam + paper) to avoid dividing by zero when rescaling. + eps_root: (default: :data:`0.0`) + A small constant applied to denominator inside the square root (as in RMSProp), to + avoid dividing by zero when rescaling. This is needed for example when computing + (meta-)gradients through Adam. + use_accelerated_op: (default: :data:`False`) + If :data:`True` use our implemented fused operator. + """ + super().__init__( + params, + adam( + lr=lr, + b1=b1, + b2=b2, + eps=eps, + eps_root=eps_root, + moment_requires_grad=False, + use_accelerated_op=use_accelerated_op, + ), + ) diff --git a/torchopt/_src/optimizer/base.py b/torchopt/_src/optimizer/base.py new file mode 100644 index 00000000..428ba198 --- /dev/null +++ b/torchopt/_src/optimizer/base.py @@ -0,0 +1,117 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Iterable + +import jax +import torch + +from torchopt._src.base import GradientTransformation +from torchopt._src.update import apply_updates + + +class Optimizer: + """A base class for classic optimizers that similar to :class:`torch.optim.Optimizer`.""" + + def __init__(self, params: Iterable[torch.Tensor], impl: GradientTransformation): + r"""The :meth:`init` function. + + Args: + params (iterable of torch.Tensor): An iterable of :class:`torch.Tensor`\s. Specifies + what tensors should be optimized. + impl (GradientTransformation): A low level optimizer function, it could be a optimizer + function provided by ``alias.py`` or a customized ``chain`` provided by + ``combine.py``. + Note that using ``Optimizer(sgd())`` or ``Optimizer(chain(sgd()))`` is equivalent to + :class:`torchopt.SGD`. + """ + if not isinstance(params, list): + params = list(params) + self.impl = impl + self.param_groups = [] # type: ignore + self.param_tree_groups = [] # type: ignore + self.state_groups = [] # type: ignore + self.add_param_group(params) + + def zero_grad(self, set_to_none: bool = False): + r"""Sets the gradients of all optimized :class:`torch.Tensor`\s to zero. + + The behavior is similar to :meth:`torch.optim.Optimizer.zero_grad`. + + Args: + set_to_none (bool): Instead of setting to zero, set the ``grads`` to :data:`None`. + """ + for group in self.param_groups: + if set_to_none: + + def f(p): + p.grad = None + + else: + + def f(p): + if p.grad is None: + return + if p.grad.grad_fn is not None: + p.grad.detach_() + else: + p.grad.requires_grad_(False) + p.grad.zero_() + + jax.tree_map(f, group) + + def state_dict(self): + """Returns the state of the optimizer.""" + return self.state_groups + + def load_state_dict(self, state_dict): + """Loads the optimizer state. + + Args: + state_dict (dict): Optimizer state. Should be an object returned from a call to + :meth:`state_dict`. + """ + self.state_groups = state_dict + + def step(self, closure=None): + """Performs a single optimization step. + + The behavior is similar to :meth:`torch.optim.Optimizer.step`. + + Args: + closure (callable, optional): A closure that reevaluates the model and returns the loss. + """ + loss = None + if closure is not None: + with torch.enable_grad(): + loss = closure() + + def f(p): + return p.grad + + for param, state in zip(self.param_groups, self.state_groups): + grad = jax.tree_map(f, param) + updates, _ = self.impl.update(grad, state) + apply_updates(param, updates) + + return loss + + def add_param_group(self, params): + """Add a param group to the optimizer's :attr:`param_groups`.""" + params, tree = jax.tree_flatten(params) + params = tuple(params) + self.param_groups.append(params) + self.param_tree_groups.append(tree) + self.state_groups.append(self.impl.init(params)) diff --git a/torchopt/_src/optimizer/meta/__init__.py b/torchopt/_src/optimizer/meta/__init__.py new file mode 100644 index 00000000..86fcb3b3 --- /dev/null +++ b/torchopt/_src/optimizer/meta/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from torchopt._src.optimizer.meta.adam import MetaAdam +from torchopt._src.optimizer.meta.base import MetaOptimizer +from torchopt._src.optimizer.meta.rmsprop import MetaRMSProp +from torchopt._src.optimizer.meta.sgd import MetaSGD diff --git a/torchopt/_src/optimizer/meta/adam.py b/torchopt/_src/optimizer/meta/adam.py new file mode 100644 index 00000000..43d5f334 --- /dev/null +++ b/torchopt/_src/optimizer/meta/adam.py @@ -0,0 +1,74 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +import torch.nn as nn + +from torchopt._src.alias import adam +from torchopt._src.optimizer.meta.base import MetaOptimizer +from torchopt._src.typing import ScalarOrSchedule + + +class MetaAdam(MetaOptimizer): + """The differentiable Adam optimizer. + + See Also: + - The functional Adam optimizer: :func:`torchopt.adam`. + - The classic Adam optimizer: :class:`torchopt.Adam`. + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + net: nn.Module, + lr: ScalarOrSchedule, + b1: float = 0.9, + b2: float = 0.999, + eps: float = 1e-8, + eps_root: float = 0.0, + moment_requires_grad: bool = True, + use_accelerated_op: bool = False, + ): + """The :meth:`init` function. + + Args: + net (nn.Module): A network whose parameters should be optimized. + args: Other arguments see also :func:`torchopt.adam`, + lr: This is a fixed global scaling factor. + b1: The exponential decay rate to track the first moment of past gradients. + b2: The exponential decay rate to track the second moment of past gradients. + eps: A small constant applied to denominator outside of the square root (as in the Adam + paper) to avoid dividing by zero when rescaling. + eps_root: (default: :data:`0.0`) + A small constant applied to denominator inside the square root (as in RMSProp), to + avoid dividing by zero when rescaling. This is needed for example when computing + (meta-)gradients through Adam. + moment_requires_grad: (default: :data:`True`) + Here we set ``moment_requires_grad=True`` to make tensors like momentum be + differentiable. + use_accelerated_op: (default: :data:`False`) + If :data:`True` use our implemented fused operator. + """ + super().__init__( + net, + adam( + lr=lr, + b1=b1, + b2=b2, + eps=eps, + eps_root=eps_root, + moment_requires_grad=moment_requires_grad, + use_accelerated_op=use_accelerated_op, + ), + ) diff --git a/torchopt/_src/optimizer/meta/base.py b/torchopt/_src/optimizer/meta/base.py new file mode 100644 index 00000000..ac54bbf7 --- /dev/null +++ b/torchopt/_src/optimizer/meta/base.py @@ -0,0 +1,93 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +import jax +import torch +import torch.nn as nn + +from torchopt._src.base import GradientTransformation +from torchopt._src.update import apply_updates + + +class MetaOptimizer: + """The base class for high-level differentiable optimizers.""" + + def __init__(self, net: nn.Module, impl: GradientTransformation): + """The :meth:`init` function. + + Args: + net (torch.nn.Module): A network whose parameters should be optimized. + impl (GradientTransformation): A low level optimizer function, it could be a optimizer + function provided by ``alias.py`` or a customized ``chain`` provided by + ``combine.py``. + Note that using ``MetaOptimizer(sgd(moment_requires_grad=True))`` or + ``MetaOptimizer(chain(sgd(moment_requires_grad=True)))`` is equivalent to + :class:`torchopt.MetaSGD`. + """ + self.impl = impl + self.param_containers_groups = [] # type: ignore + self.state_groups = [] # type: ignore + + self.add_param_group(net) + + def step(self, loss: torch.Tensor): + """Compute the gradients of the loss to the network parameters and update network parameters. + + Graph of the derivative will be constructed, allowing to compute higher order derivative + products. We use the differentiable optimizer (pass argument ``inplace=False``) to scale the + gradients and update the network parameters without modifying tensors in-place. + + Args: + loss (torch.Tensor): The loss that is used to compute the gradients to the network + parameters. + """ # pylint: disable=line-too-long + # step parameter only + for idx, (state, param_containers) in enumerate( + zip(self.state_groups, self.param_containers_groups) + ): + flatten_params, containers_tree = jax.tree_util.tree_flatten(param_containers) + flatten_params = tuple(flatten_params) + grad = torch.autograd.grad(loss, flatten_params, create_graph=True, allow_unused=True) + updates, state = self.impl.update(grad, state, False) + self.state_groups[idx] = state + new_params = apply_updates(flatten_params, updates, inplace=False) + unflatten_new_params = containers_tree.unflatten(new_params) + for container, unflatten_param in zip(param_containers, unflatten_new_params): + container.update(unflatten_param) + + def add_param_group(self, net): + """Add a param group to the optimizer's :attr:`state_groups`.""" + # pylint: disable=import-outside-toplevel,cyclic-import + from torchopt._src.utils import _extract_container + + net_container = _extract_container(net, with_buffer=False) + flatten_param, _ = jax.tree_util.tree_flatten(net_container) + flatten_param = tuple(flatten_param) + optim_state = self.impl.init(flatten_param) + self.state_groups.append(optim_state) + self.param_containers_groups.append(net_container) + + def state_dict(self): + """Extract the references of the optimizer states. + + Note that the states are references, so any in-place operations will change the states + inside :class:`MetaOptimizer` at the same time. + """ + out_groups = tuple(group for group in self.state_groups) + return out_groups + + def load_state_dict(self, state_dict): + """Load the references of the optimizer states.""" + self.state_groups = list(group for group in state_dict) diff --git a/torchopt/_src/optimizer/meta/rmsprop.py b/torchopt/_src/optimizer/meta/rmsprop.py new file mode 100644 index 00000000..313acac1 --- /dev/null +++ b/torchopt/_src/optimizer/meta/rmsprop.py @@ -0,0 +1,76 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Optional + +import torch.nn as nn + +from torchopt._src.alias import rmsprop +from torchopt._src.optimizer.meta.base import MetaOptimizer +from torchopt._src.typing import ScalarOrSchedule + + +class MetaRMSProp(MetaOptimizer): + """The differentiable RMSProp optimizer. + + See Also: + - The functional RMSProp optimizer: :func:`torchopt.rmsprop`. + - The classic RMSProp optimizer: :class:`torchopt.RMSProp`. + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + net: nn.Module, + lr: ScalarOrSchedule, + decay: float = 0.9, + eps: float = 1e-8, + initial_scale: float = 0.0, + centered: bool = False, + momentum: Optional[float] = None, + nesterov: bool = False, + ): + """The :meth:`init` function. + + Args: + net (nn.Module): A network whose parameters should be optimized. + lr: This is a fixed global scaling factor. + decay: The decay used to track the magnitude of previous gradients. + eps: A small numerical constant to avoid dividing by zero when rescaling. + initial_scale: (default: :data:`0.0`) + Initialization of accumulators tracking the magnitude of previous updates. PyTorch + uses :data:`0.0`, TensorFlow 1.x uses :data:`1.0`. When reproducing results from a + paper, verify the value used by the authors. + centered: (default: :data:`False`) + Whether the second moment or the variance of the past gradients is + used to rescale the latest gradients. + momentum: (default: :data:`None`) + Here we set ``moment_requires_grad=True`` to make tensors like momentum be + differentiable. + nesterov: (default: :data:`False`) + Whether the nesterov momentum is used. + """ + super().__init__( + net, + rmsprop( + lr=lr, + decay=decay, + eps=eps, + initial_scale=initial_scale, + centered=centered, + momentum=momentum, + nesterov=nesterov, + ), + ) diff --git a/torchopt/_src/optimizer/meta/sgd.py b/torchopt/_src/optimizer/meta/sgd.py new file mode 100644 index 00000000..f1686fc7 --- /dev/null +++ b/torchopt/_src/optimizer/meta/sgd.py @@ -0,0 +1,61 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Optional + +import torch.nn as nn + +from torchopt._src.alias import sgd +from torchopt._src.optimizer.meta.base import MetaOptimizer +from torchopt._src.typing import ScalarOrSchedule + + +class MetaSGD(MetaOptimizer): + """The differentiable Stochastic Gradient Descent optimizer. + + See Also: + - The functional SGD optimizer: :func:`torchopt.sgd`. + - The classic SGD optimizer: :class:`torchopt.SGD`. + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + net: nn.Module, + lr: ScalarOrSchedule, + momentum: Optional[float] = None, + nesterov: bool = False, + moment_requires_grad: bool = True, + ): + """The :meth:`init` function. + + Args: + net: A network whose parameters should be optimized. + lr: This is a fixed global scaling factor. + momentum: The ``decay`` rate used by the momentum term, when it is set to :data:`None`, + then momentum is not used at all. + nesterov: Whether the nesterov momentum is used. + moment_requires_grad: Here we set ``moment_requires_grad=True`` to make tensors like + momentum be differentiable. + """ + super().__init__( + net, + sgd( + lr=lr, + momentum=momentum, + nesterov=nesterov, + moment_requires_grad=moment_requires_grad, + ), + ) diff --git a/torchopt/_src/optimizer/rmsprop.py b/torchopt/_src/optimizer/rmsprop.py new file mode 100644 index 00000000..c264ab06 --- /dev/null +++ b/torchopt/_src/optimizer/rmsprop.py @@ -0,0 +1,77 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Iterable, Optional + +import torch + +from torchopt._src.alias import rmsprop +from torchopt._src.optimizer.base import Optimizer +from torchopt._src.typing import ScalarOrSchedule + + +class RMSProp(Optimizer): + """The classic RMSProp optimizer. + + See Also: + - The functional RMSProp optimizer: :func:`torchopt.rmsprop`. + - The differentiable meta-RMSProp optimizer: :class:`torchopt.MetaRMSProp`. + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + params: Iterable[torch.Tensor], + lr: ScalarOrSchedule, + decay: float = 0.9, + eps: float = 1e-8, + initial_scale: float = 0.0, + centered: bool = False, + momentum: Optional[float] = None, + nesterov: bool = False, + ): + r"""The `init` function. + + Args: + params (iterable of torch.Tensor): An iterable of :class:`torch.Tensor`\s. Specifies + what Tensors should be optimized. + lr: This is a fixed global scaling factor. + decay: The decay used to track the magnitude of previous gradients. + eps: A small numerical constant to avoid dividing by zero when rescaling. + initial_scale: (default: :data:`0.0`) + Initialization of accumulators tracking the magnitude of previous updates. PyTorch + uses :data:`0.0`, TensorFlow 1.x uses :data:`1.0`. When reproducing results from a + paper, verify the value used by the authors. + centered: (default: :data:`False`) + Whether the second moment or the variance of the past gradients is used to rescale + the latest gradients. + momentum: (default: :data:`None`) + The ``decay`` rate used by the momentum term, when it is set to :data:`None`, then + momentum is not used at all. + nesterov: (default: :data:`False`) + Whether the nesterov momentum is used. + """ + super().__init__( + params, + rmsprop( + lr=lr, + decay=decay, + eps=eps, + initial_scale=initial_scale, + centered=centered, + momentum=momentum, + nesterov=nesterov, + ), + ) diff --git a/torchopt/_src/optimizer/sgd.py b/torchopt/_src/optimizer/sgd.py new file mode 100644 index 00000000..51cc63a6 --- /dev/null +++ b/torchopt/_src/optimizer/sgd.py @@ -0,0 +1,55 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Iterable, Optional + +import torch + +from torchopt._src.alias import sgd +from torchopt._src.optimizer.base import Optimizer +from torchopt._src.typing import ScalarOrSchedule + + +class SGD(Optimizer): + """The classic SGD optimizer. + + See Also: + - The functional SGD optimizer: :func:`torchopt.sgd`. + - The differentiable meta-SGD optimizer: :class:`torchopt.MetaSGD`. + """ + + def __init__( + self, + params: Iterable[torch.Tensor], + lr: ScalarOrSchedule, + momentum: Optional[float] = None, + nesterov: bool = False, + ): + r"""The :meth:`init` function. + + Args: + params (iterable of torch.Tensor): An iterable of :class:`torch.Tensor`\s. Specifies + what tensors should be optimized. + lr: This is a fixed global scaling factor. + momentum: (default: :data:`None`) + The ``decay`` rate used by the momentum term, when it is set to :data:`None`, then + momentum is not used at all. + nesterov: (default: :data:`False`) + Whether the nesterov momentum is used. + """ + super().__init__( + params, + sgd(lr=lr, momentum=momentum, nesterov=nesterov, moment_requires_grad=False), + ) diff --git a/TorchOpt/_src/schedule.py b/torchopt/_src/schedule.py similarity index 57% rename from TorchOpt/_src/schedule.py rename to torchopt/_src/schedule.py index 192cca3c..d20eb18e 100644 --- a/TorchOpt/_src/schedule.py +++ b/torchopt/_src/schedule.py @@ -34,40 +34,48 @@ import numpy as np from absl import logging -from TorchOpt._src import base, pytypes +from torchopt._src import base +from torchopt._src.typing import Scalar -def polynomial_schedule(init_value: pytypes.Scalar, - end_value: pytypes.Scalar, - power: pytypes.Scalar, - transition_steps: int, - transition_begin: int = 0) -> base.Schedule: +def polynomial_schedule( + init_value: Scalar, + end_value: Scalar, + power: Scalar, + transition_steps: int, + transition_begin: int = 0, +) -> base.Schedule: """Constructs a schedule with polynomial transition from init to end value. + Args: - init_value: initial value for the scalar to be annealed. - end_value: end value of the scalar to be annealed. - power: the power of the polynomial used to transition from init to end. - transition_steps: number of steps over which annealing takes place, - the scalar starts changing at `transition_begin` steps and completes - the transition by `transition_begin + transition_steps` steps. - If `transition_steps <= 0`, then the entire annealing process is disabled - and the value is held fixed at `init_value`. - transition_begin: must be positive. After how many steps to start annealing - (before this many steps the scalar value is held fixed at `init_value`). + init_value: Initial value for the scalar to be annealed. + end_value: End value of the scalar to be annealed. + power: The power of the polynomial used to transition from ``init`` to ``end``. + transition_steps: + Number of steps over which annealing takes place, the scalar starts changing at + ``transition_begin`` steps and completes the transition by + ``transition_begin + transition_steps`` steps. + If ``transition_steps <= 0``, then the entire annealing process is disabled and the + value is held fixed at ``init_value``. + transition_begin: + Must be *positive*. After how many steps to start annealing (before this many steps the + scalar value is held fixed at ``init_value``). + Returns: - schedule: A function that maps step counts to values. + schedule: + A function that maps step counts to values. """ if transition_steps <= 0: logging.info( - 'A polynomial schedule was set with a non-positive `transition_steps` ' - 'value; this results in a constant schedule with value `init_value`.' + 'A polynomial schedule was set with a non-positive `transition_steps` value; this ' + 'results in a constant schedule with value `init_value`.' ) return lambda count: init_value if transition_begin < 0: logging.info( - 'An exponential schedule was set with a negative `transition_begin` ' - 'value; this will result in `transition_begin` falling back to `0`.' + 'An exponential schedule was set with a negative `transition_begin` value; this will ' + 'result in `transition_begin` falling back to `0`.' ) transition_begin = 0 @@ -83,12 +91,17 @@ def impl(count): # Alias polynomial schedule to linear schedule for convenience. -def linear_schedule(init_value: pytypes.Scalar, - end_value: pytypes.Scalar, - transition_steps: int, - transition_begin: int = 0) -> base.Schedule: - return polynomial_schedule(init_value=init_value, - end_value=end_value, - power=1, - transition_steps=transition_steps, - transition_begin=transition_begin) +def linear_schedule( + init_value: Scalar, + end_value: Scalar, + transition_steps: int, + transition_begin: int = 0, +) -> base.Schedule: + """Alias polynomial schedule to linear schedule for convenience.""" + return polynomial_schedule( + init_value=init_value, + end_value=end_value, + power=1, + transition_steps=transition_steps, + transition_begin=transition_begin, + ) diff --git a/TorchOpt/_src/transform.py b/torchopt/_src/transform.py similarity index 61% rename from TorchOpt/_src/transform.py rename to torchopt/_src/transform.py index 6c293684..a04d49b5 100644 --- a/TorchOpt/_src/transform.py +++ b/torchopt/_src/transform.py @@ -30,18 +30,23 @@ # limitations under the License. # ============================================================================== -from typing import List, NamedTuple +# pylint: disable=invalid-name + +from typing import NamedTuple, Tuple import jax import torch -from TorchOpt._src import base -from TorchOpt._src.pytypes import ScalarOrSchedule, Schedule +from torchopt._src import base +from torchopt._src.typing import Schedule + ScaleState = base.EmptyState -def inc_count(updates, count: List[int]) -> List[int]: +def inc_count(updates, count: Tuple[int]) -> Tuple[int]: + """Increments int counter by one.""" + def f(c, g): return c + 1 if g is not None else c @@ -49,14 +54,15 @@ def f(c, g): def scale(step_size: float) -> base.GradientTransformation: - """Scale updates by some fixed scalar `step_size`. + """Scale updates by some fixed scalar ``step_size``. - Args: - step_size: a scalar corresponding to a fixed scaling factor for updates. + Args: + step_size: A scalar corresponding to a fixed scaling factor for updates. + + Returns: + An ``(init_fn, update_fn)`` tuple. + """ - Returns: - An (init_fn, update_fn) tuple. - """ def init_fn(params): del params return ScaleState() @@ -66,6 +72,7 @@ def update_fn(updates, state, inplace=True): def f(g): return g.mul_(step_size) if g is not None else None + else: def f(g): @@ -79,78 +86,70 @@ def f(g): class ScaleByScheduleState(NamedTuple): """Maintains count for scale scheduling.""" - count: List[int] + + count: Tuple[int, ...] # type: ignore def scale_by_schedule(step_size_fn: Schedule) -> base.GradientTransformation: - """Scale updates using a custom schedule for the `step_size`. + """Scale updates using a custom schedule for the ``step_size``. + + Args: + step_size_fn: + A function that takes an update count as input and proposes the ``step_size`` to + multiply the updates by. - Args: - step_size_fn: a function that takes an update count as input and proposes - the step_size to multiply the updates by. + Returns: + An ``(init_fn, update_fn)`` tuple. + """ - Returns: - An (init_fn, update_fn) tuple. - """ def init_fn(params): return ScaleByScheduleState(count=tuple(0 for _ in range(len(params)))) def update_fn(updates, state, inplace=True): step_size = step_size_fn(state.count) if inplace: - updates = jax.tree_map(lambda g, step_size: g.mul_(step_size), - updates, step_size) + updates = jax.tree_map(lambda g, step_size: g.mul_(step_size), updates, step_size) else: - updates = jax.tree_map(lambda g, step_size: g.mul(step_size), - updates, step_size) - return updates, ScaleByScheduleState( - count=inc_count(updates, state.count)) + updates = jax.tree_map(lambda g, step_size: g.mul(step_size), updates, step_size) + return updates, ScaleByScheduleState(count=inc_count(updates, state.count)) return base.GradientTransformation(init_fn, update_fn) -class ScaleByRStdDevState(NamedTuple): - """State for centered exponential moving average of squares of updates.""" - mu: base.Updates - nu: base.Updates - - def _update_moment(updates, moments, decay, order, inplace=True): - """Compute the exponential moving average of the `order`-th moment.""" + """Compute the exponential moving average of the ``order``-th moment.""" if inplace: def f(g, t): - return t.mul_(decay).add_(g**order, alpha=1 - - decay) if g is not None else t + return t.mul_(decay).add_(g**order, alpha=1 - decay) if g is not None else t + else: def f(g, t): - return t.mul(decay).add(g**order, alpha=1 - - decay) if g is not None else t + return t.mul(decay).add(g**order, alpha=1 - decay) if g is not None else t return jax.tree_map(f, updates, moments) def _update_moment_per_elem_norm(updates, moments, decay, order, inplace=True): """Compute the EMA of the `order`-th moment of the element-wise norm.""" - if inplace: def f(g, t): - return t.mul_(decay).add_(g**order, alpha=1 - - decay) if g is not None else t + return t.mul_(decay).add_(g**order, alpha=1 - decay) if g is not None else t + else: def f(g, t): - return t.mul(decay).add(g**order, alpha=1 - - decay) if g is not None else t + return t.mul(decay).add(g**order, alpha=1 - decay) if g is not None else t return jax.tree_map(f, updates, moments) class ScaleByAdamState(NamedTuple): """State for the Adam algorithm.""" - count: List[int] + + count: Tuple[int, ...] # type: ignore mu: base.Updates nu: base.Updates @@ -161,6 +160,7 @@ def _bias_correction(moment, decay, count, inplace=True): def f(t, c): return t.div_(1 - decay**c) + else: def f(t, c): @@ -178,30 +178,34 @@ def scale_by_adam( ) -> base.GradientTransformation: """Rescale updates according to the Adam algorithm. - References: - [Kingma et al, 2014](https://arxiv.org/abs/1412.6980) + References: + [Kingma et al, 2014](https://arxiv.org/abs/1412.6980) + + Args: + b1: + Decay rate for the exponentially weighted average of grads. + b2: + Decay rate for the exponentially weighted average of squared grads. + eps: + Term added to the denominator to improve numerical stability. + eps_root: + Term added to the denominator inside the square-root to improve + numerical stability when back-propagating gradients through the rescaling. + moment_requires_grad: + If true, states will be created with flag `requires_grad = True`. + + Returns: + An (init_fn, update_fn) tuple. + """ - Args: - b1: decay rate for the exponentially weighted average of grads. - b2: decay rate for the exponentially weighted average of squared grads. - eps: term added to the denominator to improve numerical stability. - eps_root: term added to the denominator inside the square-root to improve - numerical stability when backpropagating gradients through the rescaling. - moment_requires_grad: if true, states will be created with flag `requires_grad = True`. - - Returns: - An (init_fn, update_fn) tuple. - """ def init_fn(params): mu = jax.tree_map( # First moment - lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), - params) + lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), params + ) nu = jax.tree_map( # Second moment - lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), - params) - return ScaleByAdamState(count=tuple(0 for _ in range(len(mu))), - mu=tuple(mu), - nu=tuple(nu)) + lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), params + ) + return ScaleByAdamState(count=tuple(0 for _ in range(len(mu))), mu=tuple(mu), nu=tuple(nu)) def update_fn(updates, state, inplace=True): mu = _update_moment(updates, state.mu, b1, 1, inplace) @@ -212,13 +216,12 @@ def update_fn(updates, state, inplace=True): if inplace: def f(g, m, v): - return m.div_(torch.sqrt_( - v.add_(eps_root)).add_(eps)) if g is not None else None + return m.div_(torch.sqrt_(v.add_(eps_root)).add_(eps)) if g is not None else None + else: def f(g, m, v): - return m.div(torch.sqrt( - v.add(eps_root)).add(eps)) if g is not None else None + return m.div(torch.sqrt(v.add(eps_root)).add(eps)) if g is not None else None updates = jax.tree_map(f, updates, mu_hat, nu_hat) return updates, ScaleByAdamState(count=count_inc, mu=mu, nu=nu) @@ -235,34 +238,37 @@ def scale_by_accelerated_adam( ) -> base.GradientTransformation: """Rescale updates according to the Adam algorithm. - This function is acceleracted by using some fused accelerated operators. - - References: - [Kingma et al, 2014](https://arxiv.org/abs/1412.6980) - - Args: - b1: decay rate for the exponentially weighted average of grads. - b2: decay rate for the exponentially weighted average of squared grads. - eps: term added to the denominator to improve numerical stability. - eps_root: term added to the denominator inside the square-root to improve - numerical stability when backpropagating gradients through the rescaling. - moment_requires_grad: if true, states will be created with flag `requires_grad = True`. - - Returns: - An (init_fn, update_fn) tuple. - """ - from .accelerated_op import AdamOp + This function is accelerated by using some fused accelerated operators. + + References: + [Kingma et al, 2014](https://arxiv.org/abs/1412.6980) + + Args: + b1: + Decay rate for the exponentially weighted average of grads. + b2: + Decay rate for the exponentially weighted average of squared grads. + eps: + Term added to the denominator to improve numerical stability. + eps_root: + Term added to the denominator inside the square-root to improve + numerical stability when back-propagating gradients through the rescaling. + moment_requires_grad: + If true, states will be created with flag `requires_grad = True`. + + Returns: + An (init_fn, update_fn) tuple. + """ + from torchopt._src.accelerated_op import AdamOp # pylint: disable=import-outside-toplevel def init_fn(params): mu = jax.tree_map( # First moment - lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), - params) + lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), params + ) nu = jax.tree_map( # Second moment - lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), - params) - return ScaleByAdamState(count=tuple(0 for _ in range(len(params))), - mu=mu, - nu=nu) + lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), params + ) + return ScaleByAdamState(count=tuple(0 for _ in range(len(params))), mu=mu, nu=nu) def update_fn(updates, state, inplace=True): count_inc = inc_count(updates, state.count) @@ -273,15 +279,16 @@ def update_fn(updates, state, inplace=True): new_mus.append(new_mu) new_nus.append(new_nu) new_updates.append(new_update) - return tuple(new_updates), ScaleByAdamState(count=count_inc, - mu=tuple(new_mus), - nu=tuple(new_nus)) + return tuple(new_updates), ScaleByAdamState( + count=count_inc, mu=tuple(new_mus), nu=tuple(new_nus) + ) return base.GradientTransformation(init_fn, update_fn) class TraceState(NamedTuple): """Holds an aggregation of past updates.""" + trace: base.Params @@ -292,25 +299,32 @@ def trace( ) -> base.GradientTransformation: """Compute a trace of past updates. - Note: `trace` and `ema` have very similar but distinct updates; - `trace = decay * trace + t`, while `ema = decay * ema + (1-decay) * t`. - Both are frequently found in the optimisation literature. + Note: `trace` and `ema` have very similar but distinct updates; + `trace = decay * trace + t`, while `ema = decay * ema + (1-decay) * t`. + Both are frequently found in the optimization literature. + + Args: + decay: + The decay rate for the trace of past updates. + nesterov: + Whether to use Nesterov momentum. + moment_requires_grad: + If true, states will be created with flag `requires_grad = True`. - Args: - decay: the decay rate for the trace of past updates. - nesterov: whether to use Nesterov momentum. - moment_requires_grad: if true, states will be created with flag `requires_grad = True`. + Returns: + An (init_fn, update_fn) tuple. + """ - Returns: - An (init_fn, update_fn) tuple. - """ def init_fn(params): - if decay == 0.: + if decay == 0.0: return TraceState(trace=()) - else: - return TraceState(trace=jax.tree_map( - lambda t: torch.zeros_like( - t, requires_grad=moment_requires_grad), params)) + + return TraceState( + trace=jax.tree_map( + lambda t: torch.zeros_like(t, requires_grad=moment_requires_grad), + params, + ) + ) def update_fn(updates, state, inplace=True): if nesterov: @@ -355,47 +369,53 @@ def f(g, t): class ScaleByRmsState(NamedTuple): """State for exponential root mean-squared (RMS)-normalized updates.""" + nu: base.Updates -def scale_by_rms(decay: float = 0.9, - eps: float = 1e-8, - initial_scale: float = 0.) -> base.GradientTransformation: +def scale_by_rms( + decay: float = 0.9, eps: float = 1e-8, initial_scale: float = 0.0 +) -> base.GradientTransformation: """Rescale updates by the root of the exp. moving avg of the square. - References: - [Hinton](www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf) + References: + [Hinton](www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf) + + Args: + decay: + Decay rate for the exponentially weighted average of squared grads. + eps: + Term added to the denominator to improve numerical stability. + initial_scale: + Initial value for second moment - Args: - decay: decay rate for the exponentially weighted average of squared grads. - eps: term added to the denominator to improve numerical stability. - initial_scale: initial value for second moment + Returns: + An (init_fn, update_fn) tuple. + """ - Returns: - An (init_fn, update_fn) tuple. - """ def init_fn(params): - nu = jax.tree_map(lambda n: torch.full_like(n, initial_scale), - params) # second moment + nu = jax.tree_map(lambda n: torch.full_like(n, initial_scale), params) # second moment return ScaleByRmsState(nu=nu) - def update_fn(updates, state, params=None, inplace=True): - del params + def update_fn(updates, state, inplace=True): nu = _update_moment_per_elem_norm(updates, state.nu, decay, 2, inplace) if inplace: def f(g, n): return g.mul_(torch.rsqrt(n.add(eps))) + else: def f(g, n): return g.mul(torch.rsqrt(n.add(eps))) # """The followings are pytorch style""" + # # if inplace: - # def f(g, n): return g.div_(torch.sqrt_(n).add_(eps)) + # def f(g, n): return g.div_(torch.sqrt_(n).add_(eps)) # else: - # def f(g, n): return g.div(torch.sqrt(n).add(eps)) + # def f(g, n): return g.div(torch.sqrt(n).add(eps)) + # updates = jax.tree_map(f, updates, nu) return updates, ScaleByRmsState(nu=nu) @@ -404,50 +424,56 @@ def f(g, n): class ScaleByRStdDevState(NamedTuple): """State for centered exponential moving average of squares of updates.""" + mu: base.Updates nu: base.Updates -def scale_by_stddev(decay: float = 0.9, - eps: float = 1e-8, - initial_scale: float = 0.) -> base.GradientTransformation: +def scale_by_stddev( + decay: float = 0.9, eps: float = 1e-8, initial_scale: float = 0.0 +) -> base.GradientTransformation: """Rescale updates by the root of the centered exp. moving average of squares. - References: - [Hinton](www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf) + References: + [Hinton](www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf) - Args: - decay: decay rate for the exponentially weighted average of squared grads. - eps: term added to the denominator to improve numerical stability. - initial_scale: initial value for second moment + Args: + decay: + Decay rate for the exponentially weighted average of squared grads. + eps: + Term added to the denominator to improve numerical stability. + initial_scale: + Initial value for second moment + + Returns: + An (init_fn, update_fn) tuple. + """ - Returns: - An (init_fn, update_fn) tuple. - """ def init_fn(params): mu = jax.tree_map(torch.zeros_like, params) # First moment - nu = jax.tree_map(lambda n: torch.full_like(n, initial_scale), - params) # second moment + nu = jax.tree_map(lambda n: torch.full_like(n, initial_scale), params) # second moment return ScaleByRStdDevState(mu=mu, nu=nu) - def update_fn(updates, state, params=None, inplace=True): - del params + def update_fn(updates, state, inplace=True): mu = _update_moment(updates, state.mu, decay, 1, inplace) nu = _update_moment_per_elem_norm(updates, state.nu, decay, 2, inplace) if inplace: def f(g, m, n): return g.mul_(torch.rsqrt(n.sub(m**2).add(eps))) + else: def f(g, m, n): return g.mul(torch.rsqrt(n.sub(m**2).add(eps))) # """The followings are pytorch style""" + # # if inplace: - # def f(g, m, n): return g.div_(torch.sqrt_(n.sub_(m ** 2)).add(eps)) + # def f(g, m, n): return g.div_(torch.sqrt_(n.sub_(m ** 2)).add(eps)) # else: - # def f(g, m, n): return g.div(torch.sqrt(n.sub(m ** 2)).add(eps)) + # def f(g, m, n): return g.div(torch.sqrt(n.sub(m ** 2)).add(eps)) + # updates = jax.tree_map(f, updates, mu, nu) return updates, ScaleByRStdDevState(mu=mu, nu=nu) diff --git a/TorchOpt/_lib/adam_op.py b/torchopt/_src/typing.py similarity index 62% rename from TorchOpt/_lib/adam_op.py rename to torchopt/_src/typing.py index 0a72e0b1..69096c99 100644 --- a/TorchOpt/_lib/adam_op.py +++ b/torchopt/_src/typing.py @@ -13,30 +13,16 @@ # limitations under the License. # ============================================================================== +from typing import Any, Callable, Iterable, Mapping, Union -def forward_(updates, mu, nu, lr, b1, b2, eps, eps_root, count): - ... +from torch import Tensor -def forwardMu(updates, mu, b1): - ... +Scalar = Union[float, int] +Numeric = Union[Tensor, Scalar] +Schedule = Callable[[Numeric], Numeric] +ScalarOrSchedule = Union[float, Schedule] -def forwardNu(updates, nu, b2): - ... - - -def forwardUpdates(new_mu, new_nu, lr, b1, b2, eps, eps_root, count): - ... - - -def backwardMu(dmu, updates, mu, b1): - ... - - -def backwardNu(dnu, updates, nu, b2): - ... - - -def backwardUpdates(dupdates, updates, new_mu, new_nu, lr, b1, b2, count): - ... +# mypy: ignore-errors +TensorTree = Union[Tensor, Iterable['TensorTree'], Mapping[Any, 'TensorTree']] diff --git a/TorchOpt/_src/update.py b/torchopt/_src/update.py similarity index 64% rename from TorchOpt/_src/update.py rename to torchopt/_src/update.py index 885ca71a..1f05f90c 100644 --- a/TorchOpt/_src/update.py +++ b/torchopt/_src/update.py @@ -32,36 +32,37 @@ import jax -from TorchOpt._src import base +from torchopt._src import base # pylint: disable=unused-import -def apply_updates(params: base.Params, - updates: base.Updates, - inplace: bool = True) -> base.Params: +def apply_updates( + params: 'base.Params', updates: 'base.Updates', inplace: bool = True +) -> 'base.Params': """Applies an update to the corresponding parameters. - This is a utility functions that applies an update to a set of parameters, and - then returns the updated parameters to the caller. As an example, the update - may be a gradient transformed by a sequence of`GradientTransformations`. This - function is exposed for convenience, but it just adds updates and parameters; - you may also apply updates to parameters manually, using `tree_map` - (e.g. if you want to manipulate updates in custom ways before applying them). + This is a utility functions that applies an update to a set of parameters, and then returns the + updated parameters to the caller. As an example, the update may be a gradient transformed by a + sequence of :class:`GradientTransformations`. This function is exposed for convenience, but it + just adds updates and parameters; you may also apply updates to parameters manually, using + :func:`tree_map` (e.g. if you want to manipulate updates in custom ways before applying them). - Args: - params: a tree of parameters. - updates: a tree of updates, the tree structure and the shape of the leaf - nodes must match that of `params`. - inplace: if True, will update params in a inplace manner. + Args: + params: A tree of parameters. + updates: + A tree of updates, the tree structure and the shape of the leaf nodes must match that + of ``params``. + inplace: If :data:`True`, will update params in a inplace manner. - Returns: - Updated parameters, with same structure, shape and type as `params`. - """ + Returns: + Updated parameters, with same structure, shape and type as ``params``. + """ if inplace: def f(p, u): if u is not None: p.data.add_(u) return p + else: def f(p, u): diff --git a/TorchOpt/_src/utils.py b/torchopt/_src/utils.py similarity index 52% rename from TorchOpt/_src/utils.py rename to torchopt/_src/utils.py index ad30373b..5c904e45 100644 --- a/TorchOpt/_src/utils.py +++ b/torchopt/_src/utils.py @@ -17,40 +17,40 @@ import jax import torch -from torch import nn - -from TorchOpt._src.MetaOptimizer import MetaOptimizer +import torch.nn as nn class _ModuleState(NamedTuple): params: List[Dict] - visual_contents: Union[None, Dict] = None +# mypy: ignore-errors def stop_gradient(target): """Stop the gradient for the input object. - Since a tensor use `grad_fn` to connect itself with the previous computation - graph, the back-propagated gradient will flow over the tensor and continue - flow to the tensors that is connected by `grad_fn`. Some algorithms requires - manually detaching tensors from the computation graph. - - Note that the stop_gradient operation is in-place. - - Args: - target: the target that to be detached from the computation graph, it coule - be a `nn.Module`, `TorchOpt.MetaOptimizer`, state of the - `TorchOpt.MetaOptimizer`, or just a plain list of tensors. - inplace: if True, the target will be detached in-place. if False, this function - will return a detached copy of the target. The in-place operation is fast - and memory efficient but may raise back-propagation error. - """ + Since a tensor use :attr:`grad_fn` to connect itself with the previous computation graph, the + back-propagated gradient will flow over the tensor and continue flow to the tensors that is + connected by :attr:`grad_fn`. Some algorithms requires manually detaching tensors from the + computation graph. + + Note that the :func:`stop_gradient` operation is in-place. + + Args: + target: The target that to be detached from the computation graph, it could be a + :class:`nn.Module`, :class:`torchopt.MetaOptimizer`, state of the + :class:`torchopt.MetaOptimizer`, or just a plain list of tensors. + inplace: If :data:`True`, the target will be detached in-place. if :data:`Frue`, this + function will return a detached copy of the target. The in-place operation is fast and + memory efficient but may raise back-propagation error. + """ + # pylint: disable=import-outside-toplevel + from torchopt._src.optimizer.meta.base import MetaOptimizer + def f(obj): if isinstance(obj, torch.Tensor): requires_grad = obj.requires_grad obj.detach_().requires_grad_(requires_grad) - return None if isinstance(target, _ModuleState): true_target = target.params @@ -64,39 +64,40 @@ def f(obj): jax.tree_map(f, true_target) -def extract_state_dict(mod, - copy=False, - *, - with_buffer=True, - enable_visual=False, - visual_prefix=''): +# pylint: disable=too-many-branches,too-many-locals +def extract_state_dict(mod, copy=False, *, with_buffer=True, enable_visual=False, visual_prefix=''): """Extract target state. - Since a tensor use `grad_fn` to connect itself with the previous computation - graph, the back-propagated gradient will flow over the tensor and continue - flow to the tensors that is connected by `grad_fn`. Some algorithms requires - manually detaching tensors from the computation graph. - - Note that the extracted state is a reference, which means any in-place operatior - will affect the target that the state is extracted from. - - Args: - mod: it coule be a `nn.Module` or `TorchOpt.MetaOptimizer`. - with_buffer: extract buffer together with parameters, this argument is only - used if the input target is `nn.Module`. - enable_visual: add additional annoations, which could be used in computation - graph visualization. Currently, this flag only has effect on `nn.Module` but - we will support `TorchOpt.MetaOptimizer` later. - visual_prefix: prefix for the visualization annoations. - - Returns: - State extracted of the input object. - """ - if isinstance(mod, nn.Module): + Since a tensor use :attr:`grad_fn` to connect itself with the previous computation graph, the + back-propagated gradient will flow over the tensor and continue flow to the tensors that is + connected by :attr:`grad_fn`. Some algorithms requires manually detaching tensors from the + computation graph. + + Note that the extracted state is a reference, which means any in-place operator will affect the + target that the state is extracted from. + + Args: + mod: It could be a :class:`nn.Module` or :class:`torchopt.MetaOptimizer`. + with_buffer: + Extract buffer together with parameters, this argument is only used if the input target + is :class:`nn.Module`. + enable_visual: + Add additional annotations, which could be used in computation graph visualization. + Currently, this flag only has effect on :class:`nn.Module` but we will support + :class:`torchopt.MetaOptimizer` later. + visual_prefix: Prefix for the visualization annotations. + + Returns: + State extracted of the input object. + """ + # pylint: disable=import-outside-toplevel + from torchopt._src.optimizer.meta.base import MetaOptimizer + + if isinstance(mod, nn.Module): # pylint: disable=no-else-return if enable_visual: visual_contents = {} - for k, v in mod.named_parameters(): + for k, v in mod.named_parameters(): # pylint: disable=invalid-name if v.grad_fn is not None: visual_contents.update({v.grad_fn: (visual_prefix + k, v)}) else: @@ -106,17 +107,18 @@ def extract_state_dict(mod, params = [] - def get_v(v): + def get_v(v): # pylint: disable=invalid-name if copy: requires_grad = v.requires_grad return v.clone().detach_().requires_grad_(requires_grad) - else: - return v + + return v def _update(term): if len(term) != 0: params.append({k: get_v(v) for k, v in term.items()}) + # pylint: disable=protected-access _update(mod._parameters) if with_buffer: _update(mod._buffers) @@ -126,14 +128,14 @@ def _update(term): _update(module._parameters) if with_buffer: _update(module._buffers) - return _ModuleState(params=tuple(params), - visual_contents=visual_contents) + return _ModuleState(params=tuple(params), visual_contents=visual_contents) + elif isinstance(mod, MetaOptimizer): state = mod.state_dict() if copy: flatten_state, state_tree = jax.tree_flatten(state) - def get_v(v): + def get_v(v): # pylint: disable=invalid-name if not isinstance(v, torch.Tensor): return v requires_grad = v.requires_grad @@ -141,11 +143,10 @@ def get_v(v): flatten_state = jax.tree_map(get_v, flatten_state) return state_tree.unflatten(flatten_state) - else: - return state - else: - raise RuntimeError(f"Unexpected class of {mod}") + return state + + raise RuntimeError(f'Unexpected class of {mod}') def _extract_container(mod, with_buffer=True): @@ -156,6 +157,7 @@ def _update(term): if len(term) != 0: containers.append(term) + # pylint: disable=protected-access _update(mod._parameters) if with_buffer: _update(mod._buffers) @@ -166,22 +168,25 @@ def _update(term): if with_buffer: _update(module._buffers) return tuple(containers) - else: - raise RuntimeError(f"Unexpected class of {mod}") + + raise RuntimeError(f'Unexpected class of {mod}') def recover_state_dict(mod, state): """Recover state. - This function is compatiable for the `extract_state`. + This function is compatible for the ``extract_state``. + + Note that the recovering process is not in-place, so the tensors of the object will not be + modified. - Note that the recovering process is not in-place, so the tensors of the object - will not be modified. + Args: + mod: Target that need to recover. + state: The recovering state. + """ + # pylint: disable=import-outside-toplevel + from torchopt._src.optimizer.meta.base import MetaOptimizer - Args: - mod: targe that need to recover. - state: the recovering state. - """ if isinstance(mod, nn.Module): target_container = _extract_container(mod) for target, source in zip(target_container, state.params): @@ -189,4 +194,4 @@ def recover_state_dict(mod, state): elif isinstance(mod, MetaOptimizer): mod.load_state_dict(state) else: - raise RuntimeError(f"Unexpected class of {mod}") + raise RuntimeError(f'Unexpected class of {mod}') diff --git a/TorchOpt/_src/visual.py b/torchopt/_src/visual.py similarity index 61% rename from TorchOpt/_src/visual.py rename to torchopt/_src/visual.py index e71c5ebc..68d96903 100644 --- a/TorchOpt/_src/visual.py +++ b/torchopt/_src/visual.py @@ -18,89 +18,96 @@ import warnings from collections import namedtuple -from distutils.version import LooseVersion from typing import Dict, Generator import torch from graphviz import Digraph +from pkg_resources import parse_version + Node = namedtuple('Node', ('name', 'inputs', 'attr', 'op')) # Saved attrs for grad_fn (incl. saved variables) begin with `._saved_*` -SAVED_PREFIX = "_saved_" +SAVED_PREFIX = '_saved_' def get_fn_name(fn, show_attrs, max_attr_chars): + """Returns function name.""" name = str(type(fn).__name__) if not show_attrs: return name - attrs = dict() + attrs = {} for attr in dir(fn): if not attr.startswith(SAVED_PREFIX): continue val = getattr(fn, attr) - attr = attr[len(SAVED_PREFIX):] + attr = attr[len(SAVED_PREFIX) :] if torch.is_tensor(val): - attrs[attr] = "[saved tensor]" + attrs[attr] = '[saved tensor]' elif isinstance(val, tuple) and any(torch.is_tensor(t) for t in val): - attrs[attr] = "[saved tensors]" + attrs[attr] = '[saved tensors]' else: attrs[attr] = str(val) if not attrs: return name max_attr_chars = max(max_attr_chars, 3) - col1width = max(len(k) for k in attrs.keys()) + col1width = max(map(len, attrs)) col2width = min(max(len(str(v)) for v in attrs.values()), max_attr_chars) - sep = "-" * max(col1width + col2width + 2, len(name)) + sep = '-' * max(col1width + col2width + 2, len(name)) attrstr = '%-' + str(col1width) + 's: %' + str(col2width) + 's' - def truncate(s): return s[:col2width - 3] + \ -"..." if len(s) > col2width else s + def truncate(s): # pylint: disable=invalid-name + return s[: col2width - 3] + '...' if len(s) > col2width else s - params = '\n'.join(attrstr % (k, truncate(str(v))) - for (k, v) in attrs.items()) + params = '\n'.join(attrstr % (k, truncate(str(v))) for (k, v) in attrs.items()) return name + '\n' + sep + '\n' + params -def make_dot(var, - params=None, - show_attrs=False, - show_saved=False, - max_attr_chars=50): - """ Produces Graphviz representation of PyTorch autograd graph. +# mypy: ignore-errors +# pylint: disable=too-many-branches,too-many-statements,too-many-locals +def make_dot( + var: torch.Tensor, params=None, show_attrs=False, show_saved=False, max_attr_chars=50 +) -> Digraph: + """Produces Graphviz representation of PyTorch autograd graph. + + If a node represents a backward function, it is gray. Otherwise, the node represents a tensor + and is either blue, orange, or green: - If a node represents a backward function, it is gray. Otherwise, the node - represents a tensor and is either blue, orange, or green: - - Blue: reachable leaf tensors that requires grad (tensors whose `.grad` - fields will be populated during `.backward()`) - - Orange: saved tensors of custom autograd functions as well as those - saved by built-in backward nodes - - Green: tensor passed in as outputs - - Dark green: if any output is a view, we represent its base tensor with - a dark green node. + - **Blue** + Reachable leaf tensors that requires grad (tensors whose :attr:`grad` fields will be + populated during :meth:`backward`). + - **Orange** + Saved tensors of custom autograd functions as well as those saved by built-in backward + nodes. + - **Green** + Tensor passed in as outputs. + - **Dark green** + If any output is a view, we represent its base tensor with a dark green node. Args: - var: output tensor - params: [dict of (name, tensor) or state_dict] to add names to node that requires grad - show_attrs: whether to display non-tensor attributes of backward nodes + var: Output tensor. + params: ([dict of (name, tensor) or state_dict]) + Parameters to add names to node that requires grad. + show_attrs: Whether to display non-tensor attributes of backward nodes + (Requires PyTorch version >= 1.9) + show_saved: Whether to display saved tensor nodes that are not by custom autograd + functions. Saved tensor nodes for custom functions, if present, are always displayed. (Requires PyTorch version >= 1.9) - show_saved: whether to display saved tensor nodes that are not by custom - autograd functions. Saved tensor nodes for custom functions, if - present, are always displayed. (Requires PyTorch version >= 1.9) - max_attr_chars: if show_attrs is `True`, sets max number of characters - to display for any given attribute. + max_attr_chars: If ``show_attrs`` is :data:`True`, sets max number of characters to display + for any given attribute. """ - if LooseVersion(torch.__version__) < LooseVersion("1.9") and \ - (show_attrs or show_saved): + if parse_version(torch.__version__) < parse_version('1.9') and (show_attrs or show_saved): warnings.warn( - "make_dot: showing grad_fn attributes and saved variables" - " requires PyTorch version >= 1.9. (This does NOT apply to" - " saved tensors saved by custom autograd functions.)") + 'make_dot: showing grad_fn attributes and saved variables ' + 'requires PyTorch version >= 1.9. (This does NOT apply to ' + 'saved tensors saved by custom autograd functions.)' + ) param_map = {} if params is not None: - from .utils import _ModuleState + from torchopt._src.utils import _ModuleState # pylint: disable=import-outside-toplevel + if isinstance(params, _ModuleState): param_map.update(params.visual_contents) elif isinstance(params, Dict): @@ -116,30 +123,30 @@ def make_dot(var, else: param_map.update({v: k for k, v in param.items()}) - node_attr = dict(style='filled', - shape='box', - align='left', - fontsize='10', - ranksep='0.1', - height='0.2', - fontname='monospace') - dot = Digraph(node_attr=node_attr, graph_attr=dict(size="12,12")) + node_attr = dict( + style='filled', + shape='box', + align='left', + fontsize='10', + ranksep='0.1', + height='0.2', + fontname='monospace', + ) + dot = Digraph(node_attr=node_attr, graph_attr=dict(size='12,12')) seen = set() def size_to_str(size): - return '(' + (', ').join(['%d' % v for v in size]) + ')' + return '(' + (', ').join(map(str, size)) + ')' def get_var_name(var, name=None): if not name: name = param_map[var] if var in param_map else '' - return '%s\n %s' % (name, size_to_str(var.size())) + return f'{name}\n{size_to_str(var.size())}' def get_var_name_with_flag(var): if var in param_map: - return '%s\n %s' % (param_map[var][0], - size_to_str(param_map[var][1].size())) - else: - return None + return f'{param_map[var][0]}\n{size_to_str(param_map[var][1].size())}' + return None def add_nodes(fn): assert not torch.is_tensor(fn) @@ -153,20 +160,16 @@ def add_nodes(fn): continue val = getattr(fn, attr) seen.add(val) - attr = attr[len(SAVED_PREFIX):] + attr = attr[len(SAVED_PREFIX) :] if torch.is_tensor(val): - dot.edge(str(id(fn)), str(id(val)), dir="none") - dot.node(str(id(val)), - get_var_name(val, attr), - fillcolor='orange') + dot.edge(str(id(fn)), str(id(val)), dir='none') + dot.node(str(id(val)), get_var_name(val, attr), fillcolor='orange') if isinstance(val, tuple): for i, t in enumerate(val): if torch.is_tensor(t): - name = attr + '[%s]' % str(i) - dot.edge(str(id(fn)), str(id(t)), dir="none") - dot.node(str(id(t)), - get_var_name(t, name), - fillcolor='orange') + name = f'{attr}[{i}]' + dot.edge(str(id(fn)), str(id(t)), dir='none') + dot.node(str(id(t)), get_var_name(t, name), fillcolor='orange') if hasattr(fn, 'variable'): # if grad_accumulator, add the node for `.variable` @@ -179,7 +182,7 @@ def add_nodes(fn): fn_fillcolor = None var_name = get_var_name_with_flag(fn) if var_name is not None: - fn_name = '%s\n %s' % (fn_name, var_name) + fn_name = f'{fn_name}\n{var_name}' fn_fillcolor = 'lightblue' # add the node for this grad_fn @@ -205,16 +208,17 @@ def add_base_tensor(var, color='darkolivegreen1'): return seen.add(var) dot.node(str(id(var)), get_var_name(var), fillcolor=color) - if (var.grad_fn): + if var.grad_fn: add_nodes(var.grad_fn) dot.edge(str(id(var.grad_fn)), str(id(var))) + # pylint: disable=protected-access if var._is_view(): add_base_tensor(var._base, color='darkolivegreen3') - dot.edge(str(id(var._base)), str(id(var)), style="dotted") + dot.edge(str(id(var._base)), str(id(var)), style='dotted') # handle multiple outputs if isinstance(var, tuple): - for v in var: + for v in var: # pylint: disable=invalid-name add_base_tensor(v) else: add_base_tensor(var) @@ -224,13 +228,14 @@ def add_base_tensor(var, color='darkolivegreen1'): return dot -def resize_graph(dot, size_per_element=0.15, min_size=12): +def resize_graph(dot, size_per_element=0.5, min_size=12): """Resize the graph according to how much content it contains. + Modify the graph in place. """ # Get the approximate number of nodes and edges num_rows = len(dot.body) content_size = num_rows * size_per_element size = max(min_size, content_size) - size_str = str(size) + "," + str(size) + size_str = str(size) + ',' + str(size) dot.graph_attr.update(size=size_str) diff --git a/torchopt/version.py b/torchopt/version.py new file mode 100644 index 00000000..784a9a63 --- /dev/null +++ b/torchopt/version.py @@ -0,0 +1,17 @@ +# Copyright 2022 MetaOPT Team. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""TorchOpt: a high-performance optimizer library built upon PyTorch.""" + +__version__ = '0.4.2' diff --git a/tutorials/1_Functional_Optimizer.ipynb b/tutorials/1_Functional_Optimizer.ipynb old mode 100755 new mode 100644 index 868bb00d..f4194835 --- a/tutorials/1_Functional_Optimizer.ipynb +++ b/tutorials/1_Functional_Optimizer.ipynb @@ -11,57 +11,66 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this tutorial, we will introduce how TorchOpt can be treated as functional optimizer to conduct normal optimization with functional programing style. We will also illustrate how to conduct differentiable optimization with functional programing in PyTorch." + "[](https://colab.research.google.com/drive/1yfi-ETyIptlIM7WFYWF_IFhX4WF3LldP?usp=sharing)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. Basic API" + "In this tutorial, we will introduce how TorchOpt can be treated as functional optimizer to conduct normal optimization with functional programing style. We will also illustrate how to conduct differentiable optimization with functional programing in PyTorch." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In this first part, we will illustrate how TorchOpt can be used as a functional optimizer. We compare it with different api in Jax and PyTorch to help understand the similarity and dissimilarity. We use simple network, adam optimizer and MSE loss objective." + "## 1. Basic API\n", + "\n", + "In this first part, we will illustrate how TorchOpt can be used as a functional optimizer. We compare it with different API in [JAX](https://github.com/google/jax) and [PyTorch](https://pytorch.org) to help understand the similarity and dissimilarity. We use simple network, Adam optimizer and MSE loss objective." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ - "import torch\n", + "from collections import OrderedDict\n", + "\n", "import functorch\n", - "import torch.autograd\n", - "from torch import nn\n", - "import optax\n", "import jax\n", - "from jax import numpy as jnp\n", + "import jax.numpy as jnp\n", + "import optax\n", + "import torch\n", + "import torch.autograd\n", + "import torch.nn as nn\n", "\n", - "import TorchOpt\n", + "import torchopt\n", "\n", "\n", "class Net(nn.Module):\n", " def __init__(self, dim):\n", " super().__init__()\n", - " self.fc = nn.Linear(dim, 1, bias=False)\n", - " self.fc.weight.data = torch.ones_like(self.fc.weight.data)\n", + " self.fc = nn.Linear(dim, 1, bias=True)\n", + " nn.init.ones_(self.fc.weight)\n", + " nn.init.zeros_(self.fc.bias)\n", "\n", " def forward(self, x):\n", - " return self.fc(x)" + " return self.fc(x)\n", + "\n", + "\n", + "def mse(inputs, targets):\n", + " return ((inputs - targets) ** 2).mean()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "- Original JAX implementation\n", + "### 1.1 Original JAX implementation\n", "\n", - "The first example is jax implementation coupled with optax, which belongs to functional programing style." + "The first example is JAX implementation coupled with [Optax](https://github.com/deepmind/optax), which belongs to functional programing style." ] }, { @@ -71,24 +80,31 @@ "outputs": [], "source": [ "def origin_jax():\n", - " learning_rate = 1.\n", " batch_size = 1\n", " dim = 1\n", + " params = OrderedDict([('weight', jnp.ones((dim, 1))), ('bias', jnp.zeros((1,)))])\n", + "\n", + " def model(params, x):\n", + " return jnp.matmul(x, params['weight']) + params['bias']\n", + "\n", + " # Obtain the `opt_state` that contains statistics for the optimizer\n", + " learning_rate = 1.\n", " optimizer = optax.adam(learning_rate)\n", - " # Obtain the `opt_state` that contains statistics for the optimizer.\n", - " params = {'w': jnp.ones((dim, 1))}\n", " opt_state = optimizer.init(params)\n", "\n", - " def compute_loss(params, x, y): return (\n", - " (jnp.matmul(x, params['w']) - y) ** 2).sum()\n", + " def compute_loss(params, x, y):\n", + " pred = model(params, x)\n", + " return mse(pred, y)\n", "\n", " xs = 2 * jnp.ones((batch_size, dim))\n", - " ys = jnp.ones((batch_size, ))\n", + " ys = jnp.ones((batch_size, 1))\n", + "\n", " grads = jax.grad(compute_loss)(params, xs, ys)\n", " updates, opt_state = optimizer.update(grads, opt_state)\n", - " print(params)\n", + "\n", + " print('Parameters before update:', params)\n", " params = optax.apply_updates(params, updates)\n", - " print(params)" + " print('Parameters after update:', params)" ] }, { @@ -96,19 +112,18 @@ "execution_count": 3, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "{'w': DeviceArray([[1.]], dtype=float32)}\n", - "{'w': DeviceArray([[6.67572e-06]], dtype=float32)}\n" + "Parameters before update: {\n", + " 'weight': DeviceArray([[1.]], dtype=float32)),\n", + " 'bias': DeviceArray([0.], dtype=float32)\n", + "}\n", + "Parameters after update: {\n", + " 'weight': DeviceArray([[6.735325e-06]], dtype=float32),\n", + " 'bias': DeviceArray([-0.99999326], dtype=float32)\n", + "}" ] } ], @@ -120,9 +135,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- Functorch with TorchOpt\n", + "### 1.2 `functorch` with TorchOpt\n", "\n", - "The Second example is functorch coupled with TorchOpt. It basically follows the same structure with the jax example." + "The second example is [`functorch`](https://pytorch.org/functorch) coupled with TorchOpt. It basically follows the same structure with the JAX example." ] }, { @@ -135,23 +150,25 @@ " batch_size = 1\n", " dim = 1\n", " net = Net(dim)\n", - " func, params = functorch.make_functional(net)\n", - "\n", - " lr = 1.\n", - " optimizer = TorchOpt.adam(lr)\n", + " model, params = functorch.make_functional(net) # get the functional version of the model\n", "\n", + " # Obtain the `opt_state` that contains statistics for the optimizer\n", + " learning_rate = 1.\n", + " optimizer = torchopt.adam(learning_rate)\n", " opt_state = optimizer.init(params)\n", "\n", - " xs = 2 * torch.ones(batch_size, dim)\n", - " ys = torch.ones(batch_size)\n", + " xs = 2 * torch.ones((batch_size, dim))\n", + " ys = torch.ones((batch_size, 1))\n", + "\n", + " pred = model(params, xs)\n", + " loss = mse(pred, ys)\n", "\n", - " pred = func(params, xs)\n", - " loss = ((pred - ys) ** 2).sum()\n", - " grad = torch.autograd.grad(loss, params)\n", - " updates, opt_state = optimizer.update(grad, opt_state)\n", - " print(params)\n", - " params = TorchOpt.apply_updates(params, updates)\n", - " print(params)" + " grads = torch.autograd.grad(loss, params)\n", + " updates, opt_state = optimizer.update(grads, opt_state)\n", + " \n", + " print('Parameters before update:', params)\n", + " params = torchopt.apply_updates(params, updates)\n", + " print('Parameters after update:', params)" ] }, { @@ -163,10 +180,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "(Parameter containing:\n", - "tensor([[1.]], requires_grad=True),)\n", - "(Parameter containing:\n", - "tensor([[0.]], requires_grad=True),)\n" + "Parameters before update: (\n", + " Parameter containing: tensor([[1.]], requires_grad=True),\n", + " Parameter containing: tensor([0.], requires_grad=True)\n", + ")\n", + "Parameters after update: (\n", + " Parameter containing: tensor([[0.]], requires_grad=True),\n", + " Parameter containing: tensor([-1.], requires_grad=True)\n", + ")" ] } ], @@ -178,10 +199,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- Full TorchOpt\n", + "### 1.3 Full TorchOpt\n", "\n", - "The Third example is to illustrate that TorchOpt can also directly replace torch.optim with exactly the same usage. Note the API \n", - "difference happens between TorchOpt.adam() and TorchOpt.Adam(). " + "The third example is to illustrate that TorchOpt can also directly replace `torch.optim` with exactly the same usage. Note the API difference happens between `torchopt.adam()` and `torchopt.Adam()`." ] }, { @@ -195,20 +215,20 @@ " dim = 1\n", " net = Net(dim)\n", "\n", - " lr = 1.\n", - " optim = TorchOpt.Adam(net.parameters(), lr=lr)\n", + " learning_rate = 1.\n", + " optim = torchopt.Adam(net.parameters(), lr=learning_rate)\n", "\n", - " xs = 2 * torch.ones(batch_size, dim)\n", - " ys = torch.ones(batch_size)\n", + " xs = 2 * torch.ones((batch_size, dim))\n", + " ys = torch.ones((batch_size, 1))\n", "\n", " pred = net(xs)\n", - " loss = ((pred - ys) ** 2).sum()\n", + " loss = mse(pred, ys)\n", "\n", - " print(net.fc.weight)\n", + " print('Parameters before update:', dict(net.named_parameters()))\n", " optim.zero_grad()\n", " loss.backward()\n", " optim.step()\n", - " print(net.fc.weight)" + " print('Parameters after update:', dict(net.named_parameters()))" ] }, { @@ -220,10 +240,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Parameter containing:\n", - "tensor([[1.]], requires_grad=True)\n", - "Parameter containing:\n", - "tensor([[0.]], requires_grad=True)\n" + "Parameters before update: {\n", + " 'fc.weight': Parameter containing: tensor([[1.]], requires_grad=True),\n", + " 'fc.bias': Parameter containing: tensor([0.], requires_grad=True)\n", + "}\n", + "Parameters after update: {\n", + " 'fc.weight': Parameter containing: tensor([[0.]], requires_grad=True),\n", + " 'fc.bias': Parameter containing: tensor([-1.], requires_grad=True)\n", + "}" ] } ], @@ -235,9 +259,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- Original PyTorch\n", + "### 1.4 Original PyTorch\n", "\n", - "The final example is to original PyTorch example with torch.optim." + "The final example is to original PyTorch example with `torch.optim`." ] }, { @@ -251,20 +275,20 @@ " dim = 1\n", " net = Net(dim)\n", "\n", - " lr = 1.\n", - " optim = torch.optim.Adam(net.parameters(), lr=lr)\n", + " learning_rate = 1.\n", + " optim = torch.optim.Adam(net.parameters(), lr=learning_rate)\n", "\n", - " xs = 2 * torch.ones(batch_size, dim)\n", - " ys = torch.ones(batch_size)\n", + " xs = 2 * torch.ones((batch_size, dim))\n", + " ys = torch.ones((batch_size, 1))\n", "\n", " pred = net(xs)\n", - " loss = ((pred - ys) ** 2).sum()\n", + " loss = mse(pred, ys)\n", "\n", - " print(net.fc.weight)\n", + " print('Parameters before update:', dict(net.named_parameters()))\n", " optim.zero_grad()\n", " loss.backward()\n", " optim.step()\n", - " print(net.fc.weight)" + " print('Parameters after update:', dict(net.named_parameters()))" ] }, { @@ -276,10 +300,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Parameter containing:\n", - "tensor([[1.]], requires_grad=True)\n", - "Parameter containing:\n", - "tensor([[1.1921e-07]], requires_grad=True)\n" + "Parameters before update: {\n", + " 'fc.weight': Parameter containing: tensor([[1.]], requires_grad=True),\n", + " 'fc.bias': Parameter containing: tensor([0.], requires_grad=True)\n", + "}\n", + "Parameters after update: {\n", + " 'fc.weight': Parameter containing: tensor([[1.1921e-07]], requires_grad=True),\n", + " 'fc.bias': Parameter containing: tensor([-1.0000], requires_grad=True)\n", + "}" ] } ], @@ -291,15 +319,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Differentiable Optimization with functional optimizor\n", - "Coupled with functional optimizer, you can conduct differentiable optimization by setting the inplce flag as False in update and apply_updates function. (which might be helpful for meta-learning algorithm implementation with functional programing style). \n", + "## 2. Differentiable Optimization with Functional Optimizer\n", "\n", - "Note that TorchOpt.SGD, TorchOpt.Adam do not support differentiable optimization. Refer to the Meta Optimizer notebook for pytorch-like differentiable optimizers." + "Coupled with functional optimizer, you can conduct differentiable optimization by setting the `inplace` flag as `False` in update and `apply_updates` function. (which might be helpful for meta-learning algorithm implementation with functional programing style). \n", + "\n", + "Note that `torchopt.SGD` and `torchopt.Adam` do not support differentiable optimization. Refer to the Meta-Optimizer notebook for PyTorch-like differentiable optimizers." ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -307,43 +336,45 @@ " batch_size = 1\n", " dim = 1\n", " net = Net(dim)\n", - " func, params = functorch.make_functional(net)\n", + " model, params = functorch.make_functional(net) # get the functional version of the model\n", "\n", - " lr = 1.\n", - " # sgd example\n", - " optimizer = TorchOpt.sgd(lr)\n", - " meta_param = torch.tensor(1., requires_grad=True)\n", + " # Meta-parameter\n", + " meta_param = nn.Parameter(torch.ones(1))\n", "\n", + " # SGD example\n", + " learning_rate = 1.\n", + " optimizer = torchopt.sgd(learning_rate)\n", " opt_state = optimizer.init(params)\n", "\n", - " xs = torch.ones(batch_size, dim)\n", - " ys = torch.ones(batch_size)\n", + " xs = torch.ones((batch_size, dim))\n", + " ys = torch.ones((batch_size, 1))\n", "\n", - " pred = func(params, xs)\n", - " # where meta_param is used\n", + " pred = model(params, xs)\n", + " # Where meta_param is used\n", " pred = pred + meta_param\n", - " loss = ((pred - ys) ** 2).sum()\n", - " grad = torch.autograd.grad(loss, params, create_graph=True)\n", - " updates, opt_state = optimizer.update(grad, opt_state, inplace=False)\n", - " params = TorchOpt.apply_updates(params, updates, inplace=False)\n", + " loss = mse(pred, ys)\n", + "\n", + " grads = torch.autograd.grad(loss, params, create_graph=True)\n", + " updates, opt_state = optimizer.update(grads, opt_state, inplace=False)\n", + " params = torchopt.apply_updates(params, updates, inplace=False) # update parameters with single step SGD update\n", "\n", - " pred = func(params, xs)\n", - " loss = ((pred - ys) ** 2).sum()\n", + " pred = model(params, xs)\n", + " loss = mse(pred, ys)\n", " loss.backward()\n", "\n", - " print(meta_param.grad)" + " print('Gradient for the meta-parameter:', meta_param.grad)" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "tensor(8.)\n" + "Gradient for the meta-parameter: tensor([32.])\n" ] } ], @@ -355,120 +386,113 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 2.1. Track the gradient of moment\n", - "Note that most modern optimizers involve moment term in the gradient update (basically only SGD with momentum = 0 does not involve). We provide an option for user to choose whether to also track the meta-gradient through moment term. The default option is `moment_requires_grad=True`." + "### 2.1 Track the Gradient of Momentum\n", + "\n", + "Note that most modern optimizers involve momentum term in the gradient update (basically only SGD with `momentum = 0` does not involve). We provide an option for user to choose whether to also track the meta-gradient through momentum term. The default option is `moment_requires_grad=True`." ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ - "optim = TorchOpt.adam(lr=1., moment_requires_grad=False)" + "optim = torchopt.adam(lr=1., moment_requires_grad=False)" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ - "optim = TorchOpt.adam(lr=1., moment_requires_grad=True)" + "optim = torchopt.adam(lr=1., moment_requires_grad=True)" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ - "optim = TorchOpt.sgd(lr=1., momentum=0.8, moment_requires_grad=True)" + "optim = torchopt.sgd(lr=1., momentum=0.8, moment_requires_grad=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Accletated Optimizer\n", - "Users can use acclerated optimizer by seeting the `use_accelerated_op` as True. Currently we only support the Adam optimizer." + "## 3. Accelerated Optimizer\n", + "\n", + "Users can use accelerated optimizer by setting the `use_accelerated_op` as `True`. Currently we only support the Adam optimizer." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Check whether the accelerated_op is avariable:" + "Check whether the `accelerated_op` is available:" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 15, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] } ], "source": [ - "TorchOpt.accelerated_op_available(torch.device(\"cpu\"))" + "torchopt.accelerated_op_available(torch.device('cpu'))" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 16, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] } ], "source": [ - "TorchOpt.accelerated_op_available(torch.device(\"cuda\"))" + "torchopt.accelerated_op_available(torch.device('cuda'))" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "net = Net(1).cuda()\n", - "optim = TorchOpt.Adam(net.parameters(), lr=1., use_accelerated_op=True)" + "optim = torchopt.Adam(net.parameters(), lr=1., use_accelerated_op=True)" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ - "optim = TorchOpt.adam(lr=1., use_accelerated_op=True)" + "optim = torchopt.adam(lr=1., use_accelerated_op=True)" ] } ], "metadata": { - "interpreter": { - "hash": "238ad0feaa04228775e5e27229169b0e3e76c0e018d5a6d65c4906ccad5c5a9e" - }, "kernelspec": { - "display_name": "OpTorch", + "display_name": "Python 3.8.13 64-bit", "language": "python", - "name": "optorch" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -480,7 +504,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.8.13" + }, + "vscode": { + "interpreter": { + "hash": "2a8cc1ff2cbc47027bf9993941710d9ab9175f14080903d9c7c432ee63d681da" + } } }, "nbformat": 4, diff --git a/tutorials/2_Visualization.ipynb b/tutorials/2_Visualization.ipynb index f1ce0aa6..f1af008f 100644 --- a/tutorials/2_Visualization.ipynb +++ b/tutorials/2_Visualization.ipynb @@ -4,14 +4,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Visualizatoin in TorchOpt" + "# Visualization in TorchOpt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In PyTorch, if the attribute `requires_grad` a tensor is `True`, the computation graph will be created if we use the tensor to do any operations. The computation graph is implemented likes link-list -- `Tensor`s are nodes and they are linked by their attribute `gran_fn`. PyTorchViz is a Python package that uses Graphviz as a backend for plotting computation graphs. TorchOpt use PyTorchViz as the blueprint and provide more easy-to-use visualization functions on the premise of supporting all its functions." + "[](https://colab.research.google.com/drive/1Uoo2epqZKmJNQOiO0EU8DGd33AVKBlAq?usp=sharing)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In [PyTorch](https://pytorch.org), if the attribute `requires_grad` a tensor is `True`, the computation graph will be created if we use the tensor to do any operations. The computation graph is implemented likes link-list -- `Tensor`s are nodes and they are linked by their attribute `gran_fn`. [PyTorchViz](https://github.com/szagoruyko/pytorchviz) is a Python package that uses [Graphviz](https://graphviz.org) as a backend for plotting computation graphs. TorchOpt use PyTorchViz as the blueprint and provide more easy-to-use visualization functions on the premise of supporting all its functions." ] }, { @@ -23,94 +30,44 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, { "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "140558415956464\n", - "\n", - "y\n", - " ()\n", - "\n", - "\n", - "\n", - "140558415963712\n", - "\n", - "MulBackward0\n", - "\n", - "\n", - "\n", - "140558415963712->140558415956464\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140558415963664\n", - "\n", - "AccumulateGrad\n", - "\n", - "\n", - "\n", - "140558415963664->140558415963712\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140558415956064\n", - "\n", - "x\n", - " ()\n", - "\n", - "\n", - "\n", - "140558415956064->140558415963664\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n139996637621680\n\ny\n ()\n\n\n\n139993377217744\n\nMulBackward0\n\n\n\n139993377217744->139996637621680\n\n\n\n\n\n139993377217840\n\nAccumulateGrad\n\n\n\n139993377217840->139993377217744\n\n\n\n\n\n139996637619360\n\nx\n ()\n\n\n\n139996637619360->139993377217840\n\n\n\n\n\n" }, - "execution_count": 4, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ + "from IPython.display import display\n", + "\n", "import torch\n", - "import TorchOpt\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "\n", + "import torchopt\n", "\n", "\n", "x = torch.tensor(1., requires_grad=True)\n", "y = 2 * x\n", - "TorchOpt.visual.make_dot(y, params={'x': x, 'y': y})" + "display(torchopt.visual.make_dot(y, params={'x': x, 'y': y}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The figure shows `y` is connected by the multiplication edge. The gradient of `y` will flow through the multiplication backward function then accumulated on x. Note that we pass a dictionary for adding node labels." + "The figure shows `y` is connected by the multiplication edge. The gradient of `y` will flow through the multiplication backward function then accumulated on `x`. Note that we pass a dictionary for adding node labels." ] }, { @@ -122,137 +79,29 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, { "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "140562207781168\n", - "\n", - "loss\n", - " ()\n", - "\n", - "\n", - "\n", - "140558416955520\n", - "\n", - "MseLossBackward0\n", - "\n", - "\n", - "\n", - "140558416955520->140562207781168\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140558416954944\n", - "\n", - "AddmmBackward0\n", - "\n", - "\n", - "\n", - "140558416954944->140558416955520\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140552833283456\n", - "\n", - "AccumulateGrad\n", - "\n", - "\n", - "\n", - "140552833283456->140558416954944\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140562207783728\n", - "\n", - "fc.bias\n", - " (1)\n", - "\n", - "\n", - "\n", - "140562207783728->140552833283456\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140552833283792\n", - "\n", - "TBackward0\n", - "\n", - "\n", - "\n", - "140552833283792->140558416954944\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140558416606736\n", - "\n", - "AccumulateGrad\n", - "\n", - "\n", - "\n", - "140558416606736->140552833283792\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140562207782928\n", - "\n", - "fc.weight\n", - " (1, 5)\n", - "\n", - "\n", - "\n", - "140562207782928->140558416606736\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n139993376880096\n\nloss\n ()\n\n\n\n139996875678480\n\nMseLossBackward0\n\n\n\n139996875678480->139993376880096\n\n\n\n\n\n139996875677952\n\nAddmmBackward0\n\n\n\n139996875677952->139996875678480\n\n\n\n\n\n139996875678336\n\nAccumulateGrad\n\n\n\n139996875678336->139996875677952\n\n\n\n\n\n139993376879696\n\nfc.bias\n (1)\n\n\n\n139993376879696->139996875678336\n\n\n\n\n\n139996875678912\n\nTBackward0\n\n\n\n139996875678912->139996875677952\n\n\n\n\n\n139996875679152\n\nAccumulateGrad\n\n\n\n139996875679152->139996875678912\n\n\n\n\n\n139993376879616\n\nfc.weight\n (1, 5)\n\n\n\n139993376879616->139996875679152\n\n\n\n\n\n" }, - "execution_count": 5, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "from torch import nn\n", - "from torch.nn import functional as F\n", - "\n", - "\n", "class Net(nn.Module):\n", " def __init__(self, dim):\n", " super().__init__()\n", - " self.fc = nn.Linear(dim, 1)\n", + " self.fc = nn.Linear(dim, 1, bias=True)\n", "\n", " def forward(self, x):\n", " return self.fc(x)\n", @@ -261,10 +110,12 @@ "dim = 5\n", "batch_size = 2\n", "net = Net(dim)\n", - "xs = torch.ones(batch_size, dim)\n", + "xs = torch.ones((batch_size, dim))\n", + "ys = torch.ones((batch_size, 1))\n", "pred = net(xs)\n", - "loss = F.mse_loss(pred, torch.ones_like(pred))\n", - "TorchOpt.visual.make_dot(loss, params=(net.named_parameters(), {\"loss\": loss}))" + "loss = F.mse_loss(pred, ys)\n", + "\n", + "display(torchopt.visual.make_dot(loss, params=(net.named_parameters(), {'loss': loss})))" ] }, { @@ -276,89 +127,67 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] }, { "data": { - "image/png": "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Op0OSZIoKipizpw5WK1WAgICWLVqFQ6Hg7CwMHJzc7n11lsJCgpCoVA4kwfUJ+WZM2eSlJREcnIyvr6+ztKQr68vGo2G0tJSZs2axSOPPEJgYCBfffUVVVVVJCQksGHDBrccrMJd2O12Z396hUKBTqfD19fXeVy//PJLXnzxRQwGAxkZGXzxxRckJydjNBo5cuSIs2RnMplYu3Yt6enpDBs2jPT0dGpqaggLC3MmxrKyMmbNmsXUqVPx8vLCbDZjsVhIT0+nuLiY22+/HYVCwY4dO1i7di09e/YkKCgIb29vAL7++msqKysbHVe73U5hYSG1tbX07t0bLy8voqOjOXLkCOXl5W6dlBv079+f8ePH8/7779O9e3fn/gpn8w0MdibMAWPGO18Pj40DJHR6IwPHXtvksgqlkpiuycR0TW70eqduPejUrYfz38l9B15U9bVvQCBXXX8TBs/GXUOT0vqRlNa//h8ydOs/2PleaHQsodFnt7aPS+pGXFI3579T+g0CwC84xBnv0An17Q58AtznMVy7TMrbt2/H39+fqKgoevXqxerVq+nduzfV1dWsXbuWLl26cMMNN+BwOLBYLKjVajZt2oTVamXcuHFER0dTUlKCXq8/b1Wdh4cHPXv2pLi4GKVSSVRUFLt376auru6y4k5MTCQxMRGHw0FpaSnTp0/HarVy0003UVZWRnFxMVOmTAHAZrORlZVFVlYWzz77LIGBgcyZM4esrCxMJhNqtRovLy8UCgWvvfYa5eXljB8/nuHDhwP1z0HT0tKYOHGic25egJiYGGRZpqysjOeff568vDxkWeb333/no48+IjY2lq1bt7JgwYLL2sf2rLS0lDfeeAOlUondbmf06NH4+voSFBREly5dnMf1wQcfpKCggPj4eH799VeCgoK45ZZb0Ol0lJaWUlFRgdlsZt26deh0OkaNGkX37t05ceIER48epaamhvfff59bbrnFuR69Xs+IESOcz/w3btzIvHnzuO6665wJKSIigmuuuYaEhAQUCgVFRUX8/vvvfPDBB8TFxbFt2zbmzq2vOqysrEShUHDs2DEyMjKw2Ww4HI5zntvu9GhDkiQ0Gg133nknDz30EPPmzePOO+90dVhtkNTo/66UzWbl/ecebfK95D4DqKmswOOc/e6lJv+8MBcNy3UF2mVSXr16Nb169UKpVNK/f3++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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n139993376892384\n\nloss\n ()\n\n\n\n139993376862752\n\nMseLossBackward0\n\n\n\n139993376862752->139993376892384\n\n\n\n\n\n139993376862800\n\nAddBackward0\n\n\n\n139993376862800->139993376862752\n\n\n\n\n\n139993376862896\n\nAddmmBackward0\n\n\n\n139993376862896->139993376862800\n\n\n\n\n\n139993377217840\n\nAddBackward0\n step1.fc.bias\n (1)\n\n\n\n139993377217840->139993376862896\n\n\n\n\n\n139993376863136\n\nAccumulateGrad\n\n\n\n139993376863136->139993377217840\n\n\n\n\n\n139993376863664\n\nAddmmBackward0\n\n\n\n139993376863136->139993376863664\n\n\n\n\n\n139993376891904\n\nstep0.fc.bias\n (1)\n\n\n\n139993376891904->139993376863136\n\n\n\n\n\n139993376863088\n\nMulBackward0\n\n\n\n139993376863088->139993377217840\n\n\n\n\n\n139993376863184\n\nViewBackward0\n\n\n\n139993376863184->139993376863088\n\n\n\n\n\n139993376863376\n\nSumBackward1\n\n\n\n139993376863376->139993376863184\n\n\n\n\n\n139993376863472\n\nMseLossBackwardBackward0\n\n\n\n139993376863472->139993376863376\n\n\n\n\n\n139993376864000\n\nTBackward0\n\n\n\n139993376863472->139993376864000\n\n\n\n\n\n139993376863568\n\nAddBackward0\n\n\n\n139993376863568->139993376863472\n\n\n\n\n\n139993376863664->139993376863568\n\n\n\n\n\n139993376863760\n\nTBackward0\n\n\n\n139993376863760->139993376863664\n\n\n\n\n\n139993376863856\n\nAccumulateGrad\n\n\n\n139993376863856->139993376863760\n\n\n\n\n\n139993377218464\n\nAddBackward0\n step1.fc.weight\n (1, 5)\n\n\n\n139993376863856->139993377218464\n\n\n\n\n\n139993376891664\n\nstep0.fc.weight\n (1, 5)\n\n\n\n139993376891664->139993376863856\n\n\n\n\n\n139993376862848\n\nAccumulateGrad\n\n\n\n139993376862848->139993376862800\n\n\n\n\n\n139993376862848->139993376863568\n\n\n\n\n\n139996637619600\n\nmeta_param\n ()\n\n\n\n139996637619600->139993376862848\n\n\n\n\n\n139993376863040\n\nTBackward0\n\n\n\n139993376863040->139993376862896\n\n\n\n\n\n139993377218464->139993376863040\n\n\n\n\n\n139993376863424\n\nMulBackward0\n\n\n\n139993376863424->139993377218464\n\n\n\n\n\n139993376863616\n\nTBackward0\n\n\n\n139993376863616->139993376863424\n\n\n\n\n\n139993376863808\n\nTBackward0\n\n\n\n139993376863808->139993376863616\n\n\n\n\n\n139993376863904\n\nMmBackward0\n\n\n\n139993376863904->139993376863808\n\n\n\n\n\n139993376864000->139993376863904\n\n\n\n\n\n" }, + "metadata": {}, "output_type": "display_data" } ], "source": [ - "from matplotlib import pyplot as plt\n", - "from matplotlib import image as imgplt\n", - "\n", "class MetaNet(nn.Module):\n", " def __init__(self, dim):\n", " super().__init__()\n", - " self.fc = nn.Linear(dim, 1)\n", + " self.fc = nn.Linear(dim, 1, bias=True)\n", "\n", " def forward(self, x, meta_param):\n", " return self.fc(x) + meta_param\n", "\n", + "\n", "dim = 5\n", "batch_size = 2\n", - "net = MetaNet(dim).cuda()\n", - "optimizer = TorchOpt.MetaSGD(net, lr=1e-3)\n", + "net = MetaNet(dim)\n", + "\n", + "xs = torch.ones((batch_size, dim))\n", + "ys = torch.ones((batch_size, 1))\n", + "\n", + "optimizer = torchopt.MetaSGD(net, lr=1e-3)\n", "meta_param = torch.tensor(1., requires_grad=True)\n", "\n", - "xs = torch.ones(batch_size, dim).cuda()\n", + "# Set enable_visual\n", + "net_state_0 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step0.')\n", "\n", "pred = net(xs, meta_param)\n", - "loss = F.mse_loss(pred, torch.ones_like(pred))\n", - "# set enable_visual\n", - "net_state_0 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step0.')\n", + "loss = F.mse_loss(pred, ys)\n", "optimizer.step(loss)\n", - "# set enable_visual\n", - "net_state_1 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step1.')\n", + "\n", + "# Set enable_visual\n", + "net_state_1 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step1.')\n", "\n", "pred = net(xs, meta_param)\n", "loss = F.mse_loss(pred, torch.ones_like(pred))\n", - "# draw computation graph\n", - "TorchOpt.visual.make_dot(loss,\n", - " [net_state_0, net_state_1,\n", - " {\"meta_param\": meta_param, 'loss': loss}]\n", - " ).render(\"meta_graph\", format=\"png\")\n", - "plt.figure(figsize=(20,20))\n", - "plt.imshow(imgplt.imread('meta_graph.png'))" + "\n", + "# Draw computation graph\n", + "display(torchopt.visual.make_dot(loss, [net_state_0, net_state_1, {'meta_param': meta_param, 'loss': loss}]))" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { - "interpreter": { - "hash": "238ad0feaa04228775e5e27229169b0e3e76c0e018d5a6d65c4906ccad5c5a9e" - }, "kernelspec": { - "display_name": "OpTorch", + "display_name": "Python 3.8.13 ('torchopt')", "language": "python", - "name": "optorch" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -370,7 +199,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.8.13" + }, + "vscode": { + "interpreter": { + "hash": "2a8cc1ff2cbc47027bf9993941710d9ab9175f14080903d9c7c432ee63d681da" + } } }, "nbformat": 4, diff --git a/tutorials/3_Meta_Optimizer.ipynb b/tutorials/3_Meta_Optimizer.ipynb index b76114f4..aaca9e3f 100644 --- a/tutorials/3_Meta_Optimizer.ipynb +++ b/tutorials/3_Meta_Optimizer.ipynb @@ -4,42 +4,48 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# TorchOpt as MetaOptimizer" + "# TorchOpt as Meta-Optimizer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In this tutorial, we will show how to treat TorchOpt as a differentiable optimizor with traditional PyTorch optimization API. In addition, we also provide many other API for easy meta-learning algorithm implementations." + "[](https://colab.research.google.com/drive/1lo9q2gQz073urYln-4Yub5s8APUoHvQJ?usp=sharing)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. Basic API for differentiable optimizer\n", - "\n", - "`MetaOptimizer` is the main class for our differnetiabl optimzier. Combined with the functional optimizer `TorchOpt.sgd` and `TorchOpt.adam` mentioned in the tutorial 1, we can define our high-level API `TorchOpt.MetaSGD` and `TorchOpt.MetaAdam`. We will discuss how this combination happens with `TorchOpt.chain` in Section 3. Let us consider the problem below." + "In this tutorial, we will show how to treat TorchOpt as a differentiable optimizer with traditional PyTorch optimization API. In addition, we also provide many other API for easy meta-learning algorithm implementations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Assume a tensor `x` is a meta parameter and `a` is a normal parameters (such as network parameters). We have inner loss li = `a0` * x^2 and we update `a` use the gradient dl/d`a0` = x^2 and `a1` = `a0` - dl/d`a0` = `a0` - x^2. Then we compute the outer loss lo = `a1` * x^2. So the gradient of outer loss to x would be:\n", - "\n", - "dlo/dx\n", - "\n", - "= da1/dx * x^2 + a1 * d(x^2)/dx\n", - "\n", - "= d(a0 - x^2)/dx * x^2 + 2 * a1 * x\n", + "## 1. Basic API for Differentiable Optimizer\n", "\n", - "= -2 * x * x^2 + 2 * (a0 - x^2) * x\n", - "\n", - "= -2 * x^3 + 2 * a0 * x - 2 * x^3\n", + "`MetaOptimizer` is the main class for our differentiable optimizer. Combined with the functional optimizer `torchopt.sgd` and `torchopt.adam` mentioned in the tutorial 1, we can define our high-level API `torchopt.MetaSGD` and `torchopt.MetaAdam`. We will discuss how this combination happens with `torchopt.chain` in Section 3. Let us consider the problem below." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Assume a tensor $x$ is a meta parameter and $a$ is a normal parameters (such as network parameters). We have inner loss $\\mathcal{L}^{\\textrm{in}} = a_0 \\cdot x^2$ and we update $a$ use the gradient $\\frac{\\partial \\mathcal{L}^{\\textrm{in}}}{\\partial a_0} = x^2$ and $a_1 = a_0 - \\eta \\, \\frac{\\partial \\mathcal{L}^{\\textrm{in}}}{\\partial a_0} = a_0 - \\eta \\, x^2$. Then we compute the outer loss $\\mathcal{L}^{\\textrm{out}} = a_1 \\cdot x^2$. So the gradient of outer loss to $x$ would be:\n", "\n", - "= -4 * x^3 + 2 * a0 * x" + "$$\n", + "\\begin{split}\n", + " \\frac{\\partial \\mathcal{L}^{\\textrm{out}}}{\\partial x}\n", + " & = \\frac{\\partial (a_1 \\cdot x^2)}{\\partial x} \\\\\n", + " & = \\frac{\\partial a_1}{\\partial x} \\cdot x^2 + a_1 \\cdot \\frac{\\partial (x^2)}{\\partial x} \\\\\n", + " & = \\frac{\\partial (a_0 - \\eta \\, x^2)}{\\partial x} \\cdot x^2 + (a_0 - \\eta \\, x^2) \\cdot 2 x \\\\\n", + " & = (- \\eta \\cdot 2 x) \\cdot x^2 + (a_0 - \\eta \\, x^2) \\cdot 2 x \\\\\n", + " & = - 4 \\, \\eta \\, x^3 + 2 \\, a_0 \\, x\n", + "\\end{split}\n", + "$$" ] }, { @@ -55,23 +61,29 @@ "metadata": {}, "outputs": [], "source": [ + "from IPython.display import display\n", + "\n", "import torch\n", - "from torch import nn\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "\n", + "import torchopt\n", + "\n", "\n", "class Net(nn.Module):\n", " def __init__(self):\n", " super().__init__()\n", - " self.a = nn.Parameter(torch.tensor(1., requires_grad=True))\n", + " self.a = nn.Parameter(torch.tensor(1.), requires_grad=True)\n", " \n", " def forward(self, x):\n", - " return self.a * x ** 2" + " return self.a * (x ** 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Then we declear network and x. Do not forget to set flag `requires_grad=True` for x." + "Then we declare the network (parameterized by `a`) and the meta parameter `x`. Do not forget to set flag `requires_grad=True` for `x`." ] }, { @@ -81,14 +93,14 @@ "outputs": [], "source": [ "net = Net()\n", - "x = torch.tensor(2., requires_grad=True)" + "x = nn.Parameter(torch.tensor(2.), requires_grad=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next we declear meta optimizer. The meta optimizer takes as input the network and use method `step` to update the network." + "Next we declare the meta optimizer. The meta optimizer takes as input the network and use method `step` to update the network (parameterized by `a`)." ] }, { @@ -100,33 +112,39 @@ "name": "stdout", "output_type": "stream", "text": [ - "tensor(-28.)\n" + "x.grad = tensor(-28.)\n" ] } ], "source": [ - "import TorchOpt\n", + "optim = torchopt.MetaSGD(net, lr=1.)\n", "\n", - "optim = TorchOpt.MetaSGD(net, lr=1.)\n", "inner_loss = net(x)\n", "optim.step(inner_loss)\n", + "\n", "outer_loss = net(x)\n", "outer_loss.backward()\n", - "# x.grad should be:\n", - "# = -4 * x^3 + 2 * a0 * x \n", - "# = -4 * 2^3 + 2 * 1 * 2 \n", - "# = -32 + 4 \n", - "# = -28\n", - "print(x.grad)" + "# x.grad = - 4 * lr * x^3 + 2 * a_0 * x\n", + "# = - 4 * 1 * 2^3 + 2 * 1 * 2\n", + "# = -32 + 4\n", + "# = -28\n", + "print(f'x.grad = {x.grad!r}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Track the Gradient of Momentum\n", + "\n", + "Note that most modern optimizers involve moment term in the gradient update (basically only SGD with `momentum = 0` does not involve). We provide an option for user to choose whether to also track the meta-gradient through moment term. The default option is `moment_requires_grad=True`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.1 Track the gradient of moment\n", - "Note that most modern optimizers involve moment term in the gradient update (basically only SGD with momentum = 0 does not involve). We provide an option for user to choose whether to also track the meta-gradient through moment term. The default option is `moment_requires_grad=True`.\n", - "- When you do not track the meta-gradient through moment" + "- When you do not track the meta-gradient through moment (`moment_requires_grad=False`)" ] }, { @@ -135,55 +153,41 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] }, { "data": { - "image/png": "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Y3/tY5tbU35+Ptdff32rnkM4TSRCwWk8PT357LPP2nxaMqvVynfffUdBQQGPPvroee+fmprK22+/zZQpU5gwYQI6na4VojzN398fhULUYLU2kQgFp1EqlWe12LY2WZY5ePAgmZmZPPfcc8TExJz3MeLi4ujWrRsffPABy5Yt48477yQgIKAVohXaiviqEToV+6QH48ePP6u7S3NJkkSvXr14/vnnUalUPP744xw6dEgMnWzHRCIUOg2r1co333yDWq1m+vTpTRZ8Ol+SJBEYGMiDDz7ItGnTeOmll/jhhx+ora1twYiFtiISodApyLJMSkoKq1ev5u9//zt6vb5FGjs0Gg1XXHEFTz75JKtWreL111+nqqpKzDPYzohEKHQK5eXlfPrpp9xwww1ERES06LHtky28/PLLeHp68tBDD3HgwAGxJEM7IhKh0CksWbIEHx8fkpKSWqUVVpIk/P39eeCBB5g6dSpvvvkm33zzjegL2E6IVmOhQ5Nlmd27d7Nu3TrefPNNPDw8WvV8SqWSK664gtjYWN577z2OHz/OHXfcQVBQkFivxIWJEqHQYcmyTFFREZ988gl33HGHY0W61qZUKklMTOTNN99Eq9Xy5JNPsn37dtGq7MJEiVDosGw2G99//z1hYWEMHz68TUtkkiSh1+u599572bRpE++99x4FBQVMnToVrVYrSocuRpQIhQ5JlmX27t3L9u3buf3221t19Mef0el0XH755bz00kts3LiRl156iYKCAtGq7GJEIhQ6pJMnT/L+++/zt7/9jYiICKeWwBQKBV27duWFF17Az8+Pp59+muTkZPGo7EJEIhQ6HJPJxNdff01CQgJDhw51djgOvr6+zJ07lxtuuIG3336bJUuWUFdXJ0qHLkAkQqHDSU5OJi0tjZtuugm1Wu3scJrQaDSMGzeO1157jY0bN/LCCy+Ql5cnkqGTiUQodBiyLFNcXMyHH37IrbfeSmhoqMs1SthXy4uMjOS5554jIiKCJ598kv3792O1WkVCdBKRCIUOo6GhgQ8++ICRI0e2eSvx+ZIkCT8/P+666y7uuusuXn31Vb744gsxVtlJRCIUOgRZltm2bRsFBQVcc801Lp0E7SRJQqlUMnLkSJ577jnS09P517/+RX5+vigZtjGRCIUO4dSpU3z++efceuutBAQEtItEaKdQKIiPj+fZZ58l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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n140393111569088\n\nouter_loss\n ()\n\n\n\n140393111544592\n\nMseLossBackward0\n\n\n\n140393111544592->140393111569088\n\n\n\n\n\n140393111544736\n\nMulBackward0\n\n\n\n140393111544736->140393111544592\n\n\n\n\n\n140396237940576\n\nAddBackward0\n step1.a\n ()\n\n\n\n140396237940576->140393111544736\n\n\n\n\n\n140393111545216\n\nAccumulateGrad\n\n\n\n140393111545216->140396237940576\n\n\n\n\n\n140393111545984\n\nMulBackward0\n\n\n\n140393111545216->140393111545984\n\n\n\n\n\n140393111534464\n\nstep0.a\n ()\n\n\n\n140393111534464->140393111545216\n\n\n\n\n\n140393111544112\n\nMulBackward0\n\n\n\n140393111544112->140396237940576\n\n\n\n\n\n140393111545168\n\nDivBackward0\n\n\n\n140393111545168->140393111544112\n\n\n\n\n\n140393111545408\n\nDivBackward0\n\n\n\n140393111545408->140393111545168\n\n\n\n\n\n140393111545552\n\nAddBackward0\n\n\n\n140393111545552->140393111545408\n\n\n\n\n\n140393111545648\n\nPowBackward0\n\n\n\n140393111545648->140393111545552\n\n\n\n\n\n140393111545744\n\nMulBackward0\n\n\n\n140393111545744->140393111545648\n\n\n\n\n\n140393111546272\n\nPowBackward0\n\n\n\n140393111545744->140393111546272\n\n\n\n\n\n140393111545840\n\nMseLossBackwardBackward0\n\n\n\n140393111545840->140393111545744\n\n\n\n\n\n140393111545984->140393111545840\n\n\n\n\n\n140393111545792\n\nPowBackward0\n\n\n\n140393111545792->140393111545744\n\n\n\n\n\n140393111545792->140393111545984\n\n\n\n\n\n140393111546128\n\nAccumulateGrad\n\n\n\n140393111546128->140393111545792\n\n\n\n\n\n140393111545024\n\nPowBackward0\n\n\n\n140393111546128->140393111545024\n\n\n\n\n\n140393111534624\n\nx\n ()\n\n\n\n140393111534624->140393111546128\n\n\n\n\n\n140393111545360\n\nAddBackward0\n\n\n\n140393111545360->140393111545168\n\n\n\n\n\n140393111545696\n\nSqrtBackward0\n\n\n\n140393111545696->140393111545360\n\n\n\n\n\n140393111545936\n\nAddBackward0\n\n\n\n140393111545936->140393111545696\n\n\n\n\n\n140393111545888\n\nDivBackward0\n\n\n\n140393111545888->140393111545936\n\n\n\n\n\n140393111546176\n\nAddBackward0\n\n\n\n140393111546176->140393111545888\n\n\n\n\n\n140393111546272->140393111546176\n\n\n\n\n\n140393111545024->140393111544736\n\n\n\n\n\n" }, + "metadata": {}, "output_type": "display_data" } ], "source": [ - "import matplotlib.pyplot as plt\n", - "from matplotlib import image as imgplt\n", - "from torch.nn import functional as F\n", - "\n", "net = Net()\n", - "x = torch.tensor(2., requires_grad=True)\n", + "x = nn.Parameter(torch.tensor(2.), requires_grad=True)\n", "y = torch.tensor(1.)\n", "\n", - "optim = TorchOpt.MetaAdam(net, lr=1., moment_requires_grad=False)\n", + "optim = torchopt.MetaAdam(net, lr=1., moment_requires_grad=False)\n", + "\n", + "net_state_0 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step0.')\n", "inner_loss = F.mse_loss(net(x), y)\n", - "net_state_0 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step0.')\n", "optim.step(inner_loss)\n", - "net_state_1 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step1.')\n", + "net_state_1 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step1.')\n", + "\n", "outer_loss = F.mse_loss(net(x), y)\n", - "TorchOpt.visual.make_dot(outer_loss, params=[net_state_0, net_state_1,{'x': x, 'outer_loss': outer_loss}]).render(\"graph\", format=\"png\")\n", - "plt.figure(figsize=(15,15))\n", - "plt.imshow(imgplt.imread('graph.png'))" + "display(torchopt.visual.make_dot(outer_loss, params=[net_state_0, net_state_1, {'x': x, 'outer_loss': outer_loss}]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "- When you track the meta-gradient through moment" + "- When you track the meta-gradient through moment (`moment_requires_grad=True`, default for `torchopt.MetaAdam`)" ] }, { @@ -192,45 +196,34 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] }, { "data": { - "image/png": "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6EALUCgUxMbGEh0dzbx58/juu+945JFHGDZsGOPHjycyMrJLzomTZZna2lqOHz/O8uXLaWlpYcGCBfTv37/DlgexNEmSzIla+G12dnbMmTOH+++/n//+9798+eWXVFVVsWzZMptf7fiakpYkSXZcTFiLZVle9ePLZZIkBfzYygoAyn98vRgI+cmvBwOl17J/S2pqauLdd9+loKCA559/npiYGEuH1K21dR35+vqyaNEiZsyYwcqVK3njjTcICQlh9uzZ9O7d+xeV8/Py8ggJCbHKivptJb+cnJxwcXG55P81NTWxefNmtm3bhkKhYNq0aeb6jV0xWf2USFp/TK/Xk5WVRV1dHffddx+tra34+vqSn5/ffZOWdPGb8RlwVpblN37yv9YCtwEv//jvNT95fYkkSW9wcSBGFHD4avdvKbIsU1FRwRtvvIFKpeLZZ5/tsoVubZm/vz+LFi2ivLyc3bt3m+ci/fnPfyY4OBiNRkNjYyMPPfQQw4cP595778XBwcFqLviyLJOdnc1f//pXFi5cyA033ABAfX29uRvQ39+fuXPn0rdvX1xdXa0m9o4kWlp/zGg08tJLL/Hpp59y4cIFc9mruro6cnJybH66wLXcXg4HbgFOS5J04sfXnuRislomSdLtQCFwA4Asy2mSJC0D0rk48vA+Wxs5KMsy6enpvPPOO8TFxXHPPfeY66EJ1kelUhEYGMi8efOYMmUKGzZs4B//+Ac9evTguuuuIyMjg71797Jr1y60Wi2PPvqoVSxQKcsyGRkZ3HPPPezdu5fq6moGDBhAamoqu3btws7OjkWLFjFs2DDzuSfOQaGNQqEgJSWF1atXU1r6v86slpYWioqKMBqNVtmzcLmuZfTgXn79ORXA2N/4nReBF692n5bStgjeyZMnee2117juuuu4/vrrxfMrGyFJEq6ursydO5cRI0aYl4Q5cOAAVVVVyLLM22+/jbOzM3/9618tujilLMvk5OTw2GOPsX//fgwGA6dOneKhhx4iMTGRm2++mcTERJt/mH61ZFm22SH7nUWSJIYNG8aHH37I3/72N44cOYLRaESSJMrLy9FqtTY9WMx2020nMhgMrF27lpUrV/LAAw+QlJTULYdU2zpJkggKCmLOnDkolUoWL15s7mqqqanhpZdeorm5mQcffBBHR8dOT1xtlVTuvPNO9u7da+7WaW1t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ywLTokW7LkMLICwsjN/97nfMmTOHAwcOeLoc0YKpqsrRo0c5cuQINpuNffv2kZGRcU4wVVRU8MILL/Daa69RVVUFQF1dHatWrWLVqlXs3r37kq//pqoq77zzTqMfY9u/fz9ff/01AGVlZaxbt47t27fLxCXRYly3oaUoCl26dOHBBx/knXfeIScnR4Y3xBVbuXIls2fPpqioiP/85z9s2bIFp9OJqqruPwEBATz22GNERka6H1dWVsb06dOpr69n9erV7N69G1VVcTqd7j+nb8N1G3DO71x/gPM+5vT7X8pt8+bN48CBAyxbtox9+/Z5olmFuGzX3TGt0ymKwogRI0hPT+fdd9/lmWeeISgoyNNliRbIbDZTWVnJ1q1bCQsLQ6/Xc/ToUXbv3s2kSZNYsGABvXv3PmMJJhdvb2/i4uI4duwYVquVuro6XnrpJWpqakhMTGTq1KkEBASwZs0a1qxZg6qq3HvvvQBUVlYyffp02rVrx9GjRxk2bBiJiYnU19czZ84chg0bxowZMygsLCQyMpJf/OIXeHt786c//Yk2bdpgs9n44x//yNatW1m7di0mk4mYmBgqKys5ePAgr732GikpKWzYsKFZHMsT4mKu256Wi16v57777sPb25v333+furo6T5ckWiCNRkObNm3YsmWLe+dut9vdw3319fUXPP5UUlLCpk2bqKqqwsfHB4PBwE033UTHjh05cuQIKSkpVFVVsXnzZn71q1/x2muv0a1bN2pra/niiy/w8fFhwIABtG3blkOHDrFx40by8vIoKioiODiYkSNH0qVLF7Kzs9mxYweqqmKz2XjooYf4xz/+gaIobNq0iWeeeYbu3bsDUFtbi16vJzU1ldraWiorK5umIYW4Std9aAH4+fnxxz/+kbKyMj777DPq6+tlqFBcto4dO/L444+7e+uKouB0Os8IL0VRzhjGA4iPj+eRRx5h7NixbNu2jczMTNatW0ffvn1p166de8KFVqs9YyTAFZS5ublYrVY6derEzp072bRpE3v27MFkMlFbW8vixYvp3bs3Xbt2dU+yCA0NJSoqCkVRqK+vx2g04u/vT1hYGFqtFm9vb6xWK6Ghoaiqir+/fxO2pBBXrlWEFjQE1zPPPMPBgwf5+uuvZUahuGx6vZ42bdq4T1j38/OjsLCQhQsXkpmZ6b7NbrezcuVKcnNzgYbr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2ZP/aX7fiEXLYbfbWbBgAcePH+ePf/xjszuOdToJLSEuSdOFQmBoGAYvLyp+PH6lqiqlhfl4+5gJCAltsjpE6+A6jrVs2TIeffRR/P39L/4gD5LQEqKZ0ep0DBw7nrLCfPdtddXVdB84BG8fOblXNK6srCxee+01HnzwQXr06NEsj2OdTpZxEqKZURSFqLgEouIS3D+7ln8SorGoqkpFRQX/93//x7Bhwxg+fHizDyyQnpYQQrRKVquVjz/+GG9vb6ZOneqxizperpZRpRBCiEZjt9uZO3cumZmZ/Pa3v8XX17dF9LJAhgeFcHM47BTn55F74rinS2lWCnOysTvsqKqKqqotZucmzs/pdPL999+zatUqXnrpJcLCwjxd0mWR0BKChuNG3Tp2YOvWrew8JFcVOF1VZSU1NTV8++23jBkzhqioKAmuFmz37t3MmDGDp556irZt23q6nMsmlyYRgoaD0k6nUxakPY+amhoOHDjApk2bSElJITk5mXvvvZfg4GA0Go0EWAuhqioZGRlMmzaNBx98kOHDhzeL62NdyIUuTSKhJYS4KFVVsdvt5Ofns2TJEn744QeSk5MZOXIknTp1wtfX19Mlip+gqipZWVm8/PLLjB49mttvv71ZBxZIaAkhGomqqpw6dYrVq1eza9cu9Ho9t9xyCzfeeGOzXfqntauoqOD5558nKSmJBx98sFmveOEioSWEaFROp5PS0lL27t3LwoULqa6u5q677qJ///4EBwc3+2/yrYGqqpSXl/PWW28RFBTEI4880iICCyS0hBDXgGv/oaoq27ZtY+HChZSXl9OzZ09GjRpFUlKSHPPyoPr6et544w1sNhvPPfcc3t7eni7pkl0otGT2oBDiirkCSVEUBg0aRO/evTl69Chr1qxh2rRpdOvWjcmTJxMXF4dOp5MAayKqqmKz2fj0008pKyvjueeeazE9rIuRnpYQolG5ZmJWVlby7bffsn79euLi4rjpppvo2bMn/v7+El7XWF1dHZ988glHjx7llVdewc/Pr8W1uQwPCiGanKqqlJSUsHbtWjZs2IDFYuHWW29lxIgRmM0Ni/+2tJ1pc+ban3/++eds376dP//5z8TExLTINpbQEkJ4jNPppKamhrS0NGbMmEFeXh633norw4YNIyIiQoYOG4GqqlgsFmbPns3GjRt58cUXiYiIaLHtKse0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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n140393102737552\n\nouter_loss\n ()\n\n\n\n140393111544400\n\nMseLossBackward0\n\n\n\n140393111544400->140393102737552\n\n\n\n\n\n140393111544304\n\nMulBackward0\n\n\n\n140393111544304->140393111544400\n\n\n\n\n\n140396584753232\n\nAddBackward0\n step1.a\n ()\n\n\n\n140396584753232->140393111544304\n\n\n\n\n\n140393111544016\n\nAccumulateGrad\n\n\n\n140393111544016->140396584753232\n\n\n\n\n\n140393111547280\n\nMulBackward0\n\n\n\n140393111544016->140393111547280\n\n\n\n\n\n140393111570848\n\nstep0.a\n ()\n\n\n\n140393111570848->140393111544016\n\n\n\n\n\n140393111544256\n\nMulBackward0\n\n\n\n140393111544256->140396584753232\n\n\n\n\n\n140393111544160\n\nDivBackward0\n\n\n\n140393111544160->140393111544256\n\n\n\n\n\n140393111546512\n\nDivBackward0\n\n\n\n140393111546512->140393111544160\n\n\n\n\n\n140393111544112\n\nAddBackward0\n\n\n\n140393111544112->140393111546512\n\n\n\n\n\n140393111546368\n\nMulBackward0\n\n\n\n140393111546368->140393111544112\n\n\n\n\n\n140393111547040\n\nAccumulateGrad\n\n\n\n140393111547040->140393111546368\n\n\n\n\n\n140393111569408\n\n ()\n\n\n\n140393111569408->140393111547040\n\n\n\n\n\n140393111546272\n\nPowBackward0\n\n\n\n140393111546272->140393111544112\n\n\n\n\n\n140393111547088\n\nMulBackward0\n\n\n\n140393111547088->140393111546272\n\n\n\n\n\n140393111547328\n\nPowBackward0\n\n\n\n140393111547088->140393111547328\n\n\n\n\n\n140393111547184\n\nMseLossBackwardBackward0\n\n\n\n140393111547184->140393111547088\n\n\n\n\n\n140393111547280->140393111547184\n\n\n\n\n\n140393111546944\n\nPowBackward0\n\n\n\n140393111546944->140393111547088\n\n\n\n\n\n140393111546944->140393111547280\n\n\n\n\n\n140393111546320\n\nAccumulateGrad\n\n\n\n140393111546320->140393111546944\n\n\n\n\n\n140393111544208\n\nPowBackward0\n\n\n\n140393111546320->140393111544208\n\n\n\n\n\n140393111571168\n\nx\n ()\n\n\n\n140393111571168->140393111546320\n\n\n\n\n\n140393111546848\n\nAddBackward0\n\n\n\n140393111546848->140393111544160\n\n\n\n\n\n140393111547136\n\nSqrtBackward0\n\n\n\n140393111547136->140393111546848\n\n\n\n\n\n140393111547232\n\nAddBackward0\n\n\n\n140393111547232->140393111547136\n\n\n\n\n\n140393111545360\n\nDivBackward0\n\n\n\n140393111545360->140393111547232\n\n\n\n\n\n140393111547424\n\nAddBackward0\n\n\n\n140393111547424->140393111545360\n\n\n\n\n\n140393111547520\n\nMulBackward0\n\n\n\n140393111547520->140393111547424\n\n\n\n\n\n140393111547616\n\nAccumulateGrad\n\n\n\n140393111547616->140393111547520\n\n\n\n\n\n140393111570288\n\n ()\n\n\n\n140393111570288->140393111547616\n\n\n\n\n\n140393111547328->140393111547424\n\n\n\n\n\n140393111544208->140393111544304\n\n\n\n\n\n" }, + "metadata": {}, "output_type": "display_data" } ], "source": [ "net = Net()\n", - "x = torch.tensor(2., requires_grad=True)\n", + "x = nn.Parameter(torch.tensor(2.), requires_grad=True)\n", "y = torch.tensor(1.)\n", "\n", - "optim = TorchOpt.MetaAdam(net, lr=1.)\n", + "optim = torchopt.MetaAdam(net, lr=1., moment_requires_grad=True)\n", + "\n", + "net_state_0 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step0.')\n", "inner_loss = F.mse_loss(net(x), y)\n", - "net_state_0 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step0.')\n", "optim.step(inner_loss)\n", - "net_state_1 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step1.')\n", + "net_state_1 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step1.')\n", "\n", "outer_loss = F.mse_loss(net(x), y)\n", - "TorchOpt.visual.make_dot(outer_loss, params=[net_state_0, net_state_1, {'x': x, 'outer_loss': outer_loss}]).render(\"graph\", format=\"png\")\n", - "plt.figure(figsize=(15,15))\n", - "plt.imshow(imgplt.imread('graph.png'))" + "display(torchopt.visual.make_dot(outer_loss, params=[net_state_0, net_state_1, {'x': x, 'outer_loss': outer_loss}]))" ] }, { @@ -251,11 +244,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 2.1 Baisc API\n", + "### 2.1 Basic API\n", "\n", - "We observe that how to reinitialize the inner-loop parameter in a new bi-level process vary in different Meta-Learning algorithms. For instance, in algorithm like MAML, every time a new task comes, we need to reset the parameters to the initial ones. In other cases such as Meta-gradient reinforcement learning, the inner-loop network parameter just inherit previous updated parameter to continue the new bi-level process.\n", + "We observe that how to reinitialize the inner-loop parameter in a new bi-level process vary in different meta-learning algorithms. For instance, in algorithm like Model-Agnostic Meta-Learning (MAML) ([arXiv:1703.03400](https://arxiv.org/abs/1703.03400)), every time a new task comes, we need to reset the parameters to the initial ones. In other cases such as Meta-Gradient Reinforcement Learning (MGRL) ([arXiv:1805.09801](https://arxiv.org/abs/1805.09801)), the inner-loop network parameter just inherit previous updated parameter to continue the new bi-level process.\n", "\n", - "We provide the `TorchOpt.extract_state_dict` and `TorchOpt.recover_state_dict` function to extract and restore the state of network and optimizer. By default, the extracted state dictionary is a reference (this design is for accumulating gradient of multi-task batch training, MAML for example). You can also set `copy=True` to extract the copy of state dictionary." + "We provide the `torchopt.extract_state_dict` and `torchopt.recover_state_dict` functions to extract and restore the state of network and optimizer. By default, the extracted state dictionary is a reference (this design is for accumulating gradient of multi-task batch training, MAML for example). You can also set `copy=True` to extract the copy of state dictionary." ] }, { @@ -267,69 +260,70 @@ "name": "stdout", "output_type": "stream", "text": [ - "tensor(-1., grad_fn=)\n", - "tensor(-1., grad_fn=)\n" + "a = tensor(-1., grad_fn=)\n", + "a = tensor(-1., grad_fn=)\n" ] } ], "source": [ "net = Net()\n", - "x = torch.tensor(2., requires_grad=True)\n", - "optim = TorchOpt.MetaAdam(net, lr=1.)\n", - "init_net_state = TorchOpt.extract_state_dict(net)\n", - "init_optim_state = TorchOpt.extract_state_dict(optim)\n", + "x = nn.Parameter(torch.tensor(2.), requires_grad=True)\n", "\n", - "# get the copy of state dictionary\n", - "init_net_state_copy = TorchOpt.extract_state_dict(net, copy=True)\n", - "init_optim_state_copy = TorchOpt.extract_state_dict(optim, copy=True)\n", + "optim = torchopt.MetaAdam(net, lr=1.)\n", "\n", - "# Conduct 2 inner-loop optimization \n", - "inner_loss = net(x)\n", - "optim.step(inner_loss)\n", - "inner_loss = net(x)\n", - "optim.step(inner_loss)\n", - "print(net.a)\n", + "# Get the reference of state dictionary\n", + "init_net_state = torchopt.extract_state_dict(net)\n", + "init_optim_state = torchopt.extract_state_dict(optim)\n", "\n", - "# Recover and reconduct 2 inner-loop optimization \n", - "TorchOpt.recover_state_dict(net, init_net_state)\n", - "TorchOpt.recover_state_dict(optim, init_optim_state)\n", - "inner_loss = net(x)\n", - "optim.step(inner_loss)\n", - "inner_loss = net(x)\n", - "optim.step(inner_loss)\n", - "outer_loss = net(x)\n", - "outer_loss.backward()\n", - "print(net.a)\n", + "# Set `copy=True` to get the copy of state dictionary\n", + "init_net_state_copy = torchopt.extract_state_dict(net, copy=True)\n", + "init_optim_state_copy = torchopt.extract_state_dict(optim, copy=True)\n", + "\n", + "# Conduct 2 inner-loop optimization\n", + "for i in range(2):\n", + " inner_loss = net(x)\n", + " optim.step(inner_loss)\n", "\n", - "# same result" + "print(f'a = {net.a!r}')\n", + "\n", + "# Recover and reconduct 2 inner-loop optimization\n", + "torchopt.recover_state_dict(net, init_net_state)\n", + "torchopt.recover_state_dict(optim, init_optim_state)\n", + "\n", + "for i in range(2):\n", + " inner_loss = net(x)\n", + " optim.step(inner_loss)\n", + "\n", + "print(f'a = {net.a!r}') # the same result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 2.2 Multi-task example with extract_state_dict, recover_state_dict\n", + "### 2.2 Multi-task Example with `extract_state_dict` and `recover_state_dict`\n", "\n", "Let's move to another more complex setting. Meta Learning algorithms always fix network on several different tasks and accumulate outer loss of each task to the meta gradient.\n", "\n", - "Assume `x` is a meta parameter and `a` is a normal parameter. We firstly update `a` use inner loss li1 = `a0` * x^2 to `a1`. Then we use a1 to compute the outer loss lo1 = a1 * x^2 and backpropagate it. Then we use `a0` to compute the inner loss li2 = `a0` * x and update `a0` to `a2` (`a2` = `a0` - dli2/d`a0` = `a0` - x). Then we compute outer loss lo2 = `a2` * x and backpropagate it. So the accumulated meta gradient would be:\n", - "\n", - "dlo1 / dx + dlo2 / dx\n", - "\n", - "= (-4 * x^3 + 2 * a0 * x) + d(a2 * x)/dx\n", - "\n", - "= (-4 * x^3 + 2 * a0 * x) + da2/dx * x + a2\n", - "\n", - "= (-4 * x^3 + 2 * a0 * x) + d(a0 - x)/dx * x + a0 - x\n", + "Assume $x$ is a meta parameter and $a$ is a normal parameter. We firstly update $a$ use inner loss $\\mathcal{L}_1^{\\textrm{in}} = a_0 \\cdot x^2$ to $a_1$. Then we use $a_1$ to compute the outer loss $\\mathcal{L}_1^{\\textrm{out}} = a_1 \\cdot x^2$ and back-propagate it. Then we use $a_0$ to compute the inner loss $\\mathcal{L}_2^{\\textrm{in}} = a_0 \\cdot x$ and update $a_0$ to $a_2 = a_0 - \\eta \\, \\frac{\\partial \\mathcal{L}_2^{\\textrm{in}}}{\\partial a_0} = a_0 - \\eta \\, x$. Then we compute outer loss $\\mathcal{L}_2^{\\textrm{out}} = a_2 \\cdot x$ and back-propagate it. So the accumulated meta gradient would be:\n", "\n", - "= (-4 * x^3 + 2 * a0 * x) - 2 * x + a0" + "$$\n", + "\\begin{split}\n", + " \\frac{\\partial \\mathcal{L}_1^{\\textrm{out}}}{\\partial x} + \\frac{\\partial \\mathcal{L}_2^{\\textrm{out}}}{\\partial x}\n", + " & = (- 4 \\, \\eta \\, x^3 + 2 \\, a_0 \\, x) + \\frac{\\partial (a_2 \\cdot x)}{\\partial x} \\\\\n", + " & = (- 4 \\, \\eta \\, x^3 + 2 \\, a_0 \\, x) + (\\frac{\\partial a_2}{\\partial x} \\cdot x + a_2) \\\\\n", + " & = (- 4 \\, \\eta \\, x^3 + 2 \\, a_0 \\, x) + [\\frac{\\partial (a_0 - \\eta \\, x)}{\\partial x} \\cdot x + (a_0 - \\eta \\, x)] \\\\\n", + " & = (- 4 \\, \\eta \\, x^3 + 2 \\, a_0 \\, x) + [(- \\eta) \\cdot x + (a_0 - \\eta \\, x)] \\\\\n", + " & = (- 4 \\, \\eta \\, x^3 + 2 \\, a_0 \\, x) + (- 2 \\, \\eta \\, x + a_0)\n", + "\\end{split}\n", + "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's define network and variables first." + "Let's define the network and variables first." ] }, { @@ -341,7 +335,7 @@ "class Net2Tasks(nn.Module):\n", " def __init__(self):\n", " super().__init__()\n", - " self.a = nn.Parameter(torch.tensor(1., requires_grad=True))\n", + " self.a = nn.Parameter(torch.tensor(1.), requires_grad=True)\n", " \n", " def task1(self, x):\n", " return self.a * x ** 2\n", @@ -351,15 +345,16 @@ "\n", "\n", "net = Net2Tasks()\n", - "x = torch.tensor(2., requires_grad=True)\n", - "optim = TorchOpt.MetaSGD(net, lr=1.)" + "x = nn.Parameter(torch.tensor(2.), requires_grad=True)\n", + "\n", + "optim = torchopt.MetaSGD(net, lr=1.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Once we call `step` method of `MetaOptimizer`, the parameters of the network would be changed. We should use `TorchOpt.extract_state_dict` to extract state and use `TorchOpt.recover_state_dict` to recover the state. Note that if we use optimizers that have momentum buffers, we should also extract and recover them, vanilla SGD does not have momentum buffers so codes `init_optim_state = TorchOpt.extract_state_dict(optim)` and `TorchOpt.recover_state_dict(optim, init_optim_state)` have no effect." + "Once we call `step` method of `MetaOptimizer`, the parameters of the network would be changed. We should use `torchopt.extract_state_dict` to extract state and use `torchopt.recover_state_dict` to recover the state. Note that if we use optimizers that have momentum buffers, we should also extract and recover them, vanilla SGD does not have momentum buffers so code `init_optim_state = torchopt.extract_state_dict(optim)` and `torchopt.recover_state_dict(optim, init_optim_state)` have no effect." ] }, { @@ -371,67 +366,66 @@ "name": "stdout", "output_type": "stream", "text": [ - "((EmptyState(), EmptyState()),)\n", - "tensor(-28.)\n", - "tensor(-31.)\n" + "init_optim_state = ((EmptyState(), EmptyState()),)\n", + "Task 1: x.grad = tensor(-28.)\n", + "Accumulated: x.grad = tensor(-31.)\n" ] } ], "source": [ - "init_net_state = TorchOpt.extract_state_dict(net)\n", - "init_optim_state = TorchOpt.extract_state_dict(optim)\n", - "# it's SGD so state_dict is empty\n", - "print(init_optim_state)\n", + "# Get the reference of state dictionary\n", + "init_net_state = torchopt.extract_state_dict(net)\n", + "init_optim_state = torchopt.extract_state_dict(optim)\n", + "# The `state_dict` is empty for vanilla SGD optimizer\n", + "print(f'init_optim_state = {init_optim_state!r}')\n", "\n", - "li1 = net.task1(x)\n", - "optim.step(li1)\n", - "lo1 = net.task1(x)\n", - "lo1.backward()\n", - "print(x.grad)\n", + "inner_loss_1 = net.task1(x)\n", + "optim.step(inner_loss_1)\n", + "outer_loss_1 = net.task1(x)\n", + "outer_loss_1.backward()\n", + "print(f'Task 1: x.grad = {x.grad!r}')\n", "\n", - "TorchOpt.recover_state_dict(net, init_net_state)\n", - "TorchOpt.recover_state_dict(optim, init_optim_state)\n", - "li2 = net.task2(x)\n", - "optim.step(li2)\n", - "lo2 = net.task2(x)\n", - "lo2.backward()\n", + "torchopt.recover_state_dict(net, init_net_state)\n", + "torchopt.recover_state_dict(optim, init_optim_state)\n", + "inner_loss_2 = net.task2(x)\n", + "optim.step(inner_loss_2)\n", + "outer_loss_2 = net.task2(x)\n", + "outer_loss_2.backward()\n", "\n", - "# extract_state_dict extract the reference so gradient accumulate\n", - "# x.grad should be (-4 * x^3 + 2 * a0 * x) - 2 * x + a0 = -28 - 2 * 2 + 1 = -31\n", - "print(x.grad)" + "# `extract_state_dict`` extracts the reference so gradient accumulates\n", + "# x.grad = (- 4 * lr * x^3 + 2 * a_0 * x) + (- 2 * lr * x + a_0)\n", + "# = (- 4 * 1 * 2^3 + 2 * 1 * 2) + (- 2 * 1 * 2 + 1)\n", + "# = -28 - 3\n", + "# = -31\n", + "print(f'Accumulated: x.grad = {x.grad!r}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Gradient transformation in MetaOptimizer" + "## 3. Gradient Transformation in `MetaOptimizer`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can also use some gradient normalization tricks in our `MetaOptimizer`. In fact `MetaOptimizer` decendents like `MetaSGD` are specializations of `MetaOptimizer`. Specifically, `MetaSGD(net, lr=1.)` is `MetaOptimizer(net, alias.sgd(lr=1., moment_requires_grad=True))`, where flag `moment_requires_grad=True` means the momentums are created with flag `requires_grad=True` so the momentums will also be the part of the computation graph.\n", + "We can also use some gradient normalization tricks in our `MetaOptimizer`. In fact `MetaOptimizer` decedents like `MetaSGD` are specializations of `MetaOptimizer`. Specifically, `MetaSGD(net, lr=1.)` is `MetaOptimizer(net, alias.sgd(lr=1., moment_requires_grad=True))`, where flag `moment_requires_grad=True` means the momentums are created with flag `requires_grad=True` so the momentums will also be the part of the computation graph.\n", "\n", - "In the desiging of TorchOpt, we treat these functions as derivations of `combine.chain`. So we can build our own chain like `combine.chain(clip.clip_grad_norm(max_norm=1.), sgd(lr=1., requires_grad=True))` to clip the gradient and update parameters using sgd." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "dlo/dx\n", + "In the designing of TorchOpt, we treat these functions as derivations of `combine.chain`. So we can build our own chain like `combine.chain(clip.clip_grad_norm(max_norm=1.), sgd(lr=1., requires_grad=True))` to clip the gradient and update parameters using `sgd`.\n", "\n", - "= da1/dx * x^2 + a1 * d(x^2)/dx\n", - "\n", - "= d(a0 - x^2 / scale)/dx * x^2 + 2 * a1 * x\n", - "\n", - "= -2 * x / scale * x^2 + 2 * (a0 - x^2 / scale) * x\n", - "\n", - "= -2 * x^3 / scale + 2 * a0 * x - 2 * x^3 / scale\n", - "\n", - "= -4 * x^3 / scale + 2 * a0 * x" + "$$\n", + "\\begin{aligned}\n", + " \\frac{\\partial \\mathcal{L}^{\\textrm{out}}}{\\partial x}\n", + " & = \\frac{\\partial (a_1 \\cdot x^2)}{\\partial x} \\\\\n", + " & = \\frac{\\partial a_1}{\\partial x} \\cdot x^2 + a_1 \\cdot \\frac{\\partial (x^2)}{\\partial x} \\\\\n", + " & = \\frac{\\partial (a_0 - \\eta \\, g)}{\\partial x} \\cdot x^2 + (a_0 - \\eta \\, g) \\cdot 2 x & \\qquad (g \\propto \\frac{\\partial \\mathcal{L}^{\\textrm{in}}}{\\partial a_0} = x^2, \\ {\\lVert g \\rVert}_2 \\le G_{\\max}) \\\\\n", + " & = \\frac{\\partial (a_0 - \\eta \\, \\beta^{-1} \\, x^2)}{\\partial x} \\cdot x^2 + (a_0 - \\eta \\, \\beta^{-1} \\, x^2) \\cdot 2 x & \\qquad (g = \\beta^{-1} \\, x^2, \\ \\beta > 0, \\ {\\lVert g \\rVert}_2 \\le G_{\\max}) \\\\\n", + " & = (- \\beta^{-1} \\, \\eta \\cdot 2 x) \\cdot x^2 + (a_0 - \\beta^{-1} \\, \\eta \\, x^2) \\cdot 2 x \\\\\n", + " & = - 4 \\, \\beta^{-1} \\, \\eta \\, x^3 + 2 \\, a_0 \\, x\n", + "\\end{aligned}\n", + "$$" ] }, { @@ -443,24 +437,28 @@ "name": "stdout", "output_type": "stream", "text": [ - "tensor(-12.0000)\n" + "x.grad = tensor(-12.0000)\n" ] } ], "source": [ "net = Net()\n", - "x = torch.tensor(2., requires_grad=True)\n", + "x = nn.Parameter(torch.tensor(2.), requires_grad=True)\n", "\n", - "impl = TorchOpt.combine.chain(TorchOpt.clip.clip_grad_norm(max_norm=2.), TorchOpt.sgd(lr=1., moment_requires_grad=True))\n", - "optim = TorchOpt.MetaOptimizer(net, impl)\n", - "li = net(x)\n", - "optim.step(li)\n", - "lo = net(x)\n", - "lo.backward()\n", - "# p.grad is -4 * x^3 / scale + 2 * a0 * x = -4 * 2^3 / scale + 2 * 1 * 2 = 4 - 32 / scale\n", - "# since max_norm is 2 and the gradient is x^2, so the scale should be x^2 / 2 = 2^2 / 2 = 2\n", - "# finally p.grad is 4 - 32 / 2 = -12\n", - "print(x.grad)" + "optim_impl = torchopt.combine.chain(torchopt.clip.clip_grad_norm(max_norm=2.), torchopt.sgd(lr=1., moment_requires_grad=True))\n", + "optim = torchopt.MetaOptimizer(net, optim_impl)\n", + "\n", + "inner_loss = net(x)\n", + "optim.step(inner_loss)\n", + "\n", + "outer_loss = net(x)\n", + "outer_loss.backward()\n", + "# Since `max_norm` is 2 and the gradient is x^2, so the scale = x^2 / 2 = 2^2 / 2 = 2\n", + "# x.grad = - 4 * lr * x^3 / scale + 2 * a_0 * x\n", + "# = - 4 * 1 * 2^3 / 2 + 2 * 1 * 2\n", + "# = -16 + 4\n", + "# = -12\n", + "print(f'x.grad = {x.grad!r}')" ] }, { @@ -469,112 +467,92 @@ "source": [ "## 4. Accelerated Optimizer\n", "\n", - "Users can use acclerated optimizer by seeting the `use_accelerated_op` as True. Currently we only support the Adam optimizer." + "Users can use accelerated optimizer by setting the `use_accelerated_op` as `True`. Currently we only support the Adam optimizer." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Check whether the accelerated_op is avariable:" + "Check whether the `accelerated_op` is available:" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] } ], "source": [ - "TorchOpt.accelerated_op_available(torch.device(\"cpu\"))" + "torchopt.accelerated_op_available(torch.device('cpu'))" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] } ], "source": [ - "TorchOpt.accelerated_op_available(torch.device(\"cuda\"))" + "torchopt.accelerated_op_available(torch.device('cuda'))" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] }, { "data": { - "image/png": "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66236N69O5MnTyY+Pp4BAwawfft2NmyovP7KYDBgsViwWq0X1Ww2mxk8eDDZ2dl89NFHPP7440RHR1NSUsK2bdsICwtj27Ztjm2fPv30U86ePUufPn04efIkixcvviBgmkymC3YjqL5Qoby8HE9PT0pLS2/I7gFCiP+5uZva3WDVb47Vb9RCiLpNp9ORmprKihUr2LRpE6tWrSIuLs7Ri7N//35+/PFHVq1aRX5+PjExMYSEhBAZGcncuXNZtmwZu3btAnAEIqPRSEFBAfv27btgPbCgoCCsViurV6/m8OHDWCwWoqKiiIyMZNeuXRQWFrJjxw4OHjyIUooFCxawZcsWzp07R1lZGW5ubhgMBjp16sTy5ctZu3YtixcvJjExES8vLwAyMzP58ccfWbt2LceOHSMxsfJ6KovFwqFDh9ixYwerVq0iKioKs9nsqLe4uJj9+/c7hhAbNmyI2Wzm22+/JT09ncWLFzuGb93c3AgKCuL3v/89WVlZjmVEkpOTWb58Of3792fz5s00adIETdOwWq0YDAYqKio4dOgQZ886ZqIAF/f++fn5kZKSwowZM1i5ciWbN2+WYU0hbrBaFc48PT2xWq2/uVmvEKJuUEoxcOBAWrVqxdq1a/H19eX+++937HnZuHFjcnNzOXz4MMOGDSM2Nhaz2czYsWMpLS3l6NGjjB49moYNG2IymXjooYc4deoUW7dupUuXLvTs2dMRPgICAhg9ejSbNm1i7dq1lJaWEhAQwKhRo7DZbKxZswZfX1+aN28OQJcuXcjLy+Onn34iLS2Njh07opSiW7du9OvXj40bN+Lv788999zjGD6Nj4+npKSEPXv2MHToUBo1aoROp6Nbt27s3LmTJUuWEBgYyMMPP4ynpydjx44lPz+fjRs3kpqayu23345er8fX15fx48fj6+vLunXrcHd3JyAgADc3N1JSUoiOjsZsNjNhwgTsdjtlZWX06tWLXr160bx5c3r06EHnzp3R6/X069cPf39/Vq9eTUREBEOGDHGEycjISFq2bHnRHqPjx4/HbDazf/9+7rrrLkebCCFuDOXqf/GkpqZq6enpAGzatIlHH32UOXPmEBIS8huPFEK4ss8++4yPP/6Yd95555oev2bNGn766SfGjRtXK7Ya2r59O9999x3jx48nODjY2eU4xaRJk3B3d+ett95ydilC1KgdO3YwYsQI1q1bh4+PDwBJSUkFe/fuveSbV63qOYuKiqKsrAzZNUAI4ebmhtlsrjXzT6vnw9WWeoUQzlOrLgjw8PDA19eXU6dOkZyc7OxyhBBO1LZtW9q2bevsMq5YcnKyvG8JIa5IrQpnbm5uREREcPToUWeXIoRwstrWA1Xb6hVCOE+tGtY0GAwkJSWRkZFxwVVWQgghhBB1Ra3qOTMYDKSkpPDJJ59w+vRpIiMjnV2SEOI62Gw2xzpkou6r3sVACPHralU4U0oRGxtLRUUFR44cISIiQoYKhKjFMjMzmTRpkrPLEDVk27ZtdOvWzdllCOHyalU4g8p1dyIjI9m0aRNt2rRxLEYphKhd2rRpw9NPP+3sMpzi5Zdf5r777qN+/frOLqVGtW3blqZNmzq7DCFcXq1LNt7e3rRv356ZM2cyevRovL29nV2SEOIaJCUlkZSU5OwyapymaUyaNIk777yT9u3bO7scIYQLqlUXBEDl0Gb37t0pKChg9erVzi5HCCGEEOKGqnXhDCqHNvv27cvUqVMpLi52djlCCCGEEDdMrQxnAKNGjeLUqVMsXbrU2aUIIYQQQtwwtTachYeHM3LkSD777DNOnz6Nq+8RKoQQQghxJWptOAMYMWIEVquV2bNny/o5QgghhKgTam04U0rh5+fHvffey8KFC8nMzJTeMyGEEELUerU2nEHlXps9e/YkPDyct99+G5vN5uyShBBCCCGuS60OZwABAQE88sgjrF+/nrlz52K3251dkhBCCCHENav14UwpRXJyMk8++SRvvPEG6enpEtCEEEIIUWvV+nBWbfDgwfTq1Ys33niDo0ePOrscIYQQQohrUmfCmcFgYPz48ZjNZqZMmUJRUZFcICCEEEKIWqfOhDOlFBERETz++ONs3LiRTz75hIqKCmeXJYQQQghxVepMOIPKgNa8eXOee+45vvjiC2bNmkV5ebmzyxJCCCGEuGJ1KpxBZUDr2rUrzz77LO+99x7z5s3DYrE4uywhhBBCiCvi5uwCbgadTscdd9xBWVkZb731Fn5+fnTv3h29Xu/s0oQQQgghflWdDGdQuUDtoEGDKCws5IUXXgCgW7duuLnV2W9ZCCGEEHVAnRvWrKaUwmQyMXLkSMaOHctf//pXvvvuO7lIQAghhBAurc53I5nNZkaOHInJZOKf//wn5eXlDBkyBKPR6OzShBBCCCEuUufDGYDRaOSuu+7C09OT119/ncLCQkaOHIm7u7uzSxNCCCGEuMAtEc6UUhiNRgYMGIDZbOaVV14hLy+PBx54gODgYGeXJ4QQQgjhcEuEs2p6vZ7evXvj4+PDG2+8wZEjR3j88ceJjY1FKeXs8oQQQggh6u4FAZej0+no0KEDEydOpLS0lMcff5yMjAzZ6kkIIYQQLuGWC2dQOcyZmJjIa6+9RlJSEvfeey+LFi2iuLhYQpoQQgghnOqWGtY8n1KK4OBgXn75ZRo1asQbb7xBeno6o0ePJjIyUoY5hRBCCOEUt2w4q2Y0Ghk1ahRxcXFMnjyZp556iueee47GjRtLQBNC3BBffvklGRkZAGiaRkFBAR9//DGLFy8GIDQ0lPvvvx8PDw9nlimEcBG3fDiDynloHTt2JC4ujkmTJvHAAw/w7LPP0q9fP9zc3CSkCSGuy+HDh3nttdccX1ssFj799FN0Oh1KKfr06cO4ceOcWKEQwpXcknPOLkWn0xEREcGrr77Kww8/zMSJE3n11Vc5fPiwzEMTQlyXIUOGoJSivLyc8vJy7HY7FovF8fndd98te/8KIRwknJ1HKYVer2fkyJG8+eabHDp0iD//+c8sWLCAsrIyZ5cnhKil6tWrR4cOHS7ZCx8QEECPHj2cUJUQwlVJOLsEvV5Px44dee211+jVqxevvvoqL730EsePH3d2aUKIWshsNjN48OCLjiuluOOOO/Dz86v5ooQQLkvC2WUopQgPD2f06NFMnTqVzMxM7r//ftasWSObpwshroper6d169bExMRcdHzYsGEypCmEuICEs99gMBhITk7mvffeo3fv3jzxxBO8+eabHDx4EKvV6uzyhBC1gFKKuLg4OnTocMHxlJQUkpKS5KIjIcQFJJxdAaUUAQEBPP7447z++utkZGTw5JNPMm3aNAoKCuSCASHEbwoICKB169Z4eXkBle8rXbp0kf19hRAXkXB2Fdzc3EhLS+O1117jzjvv5Msvv2TMmDFs3rwZTdMkpAkhLkuv19OuXTsiIyMBCAkJoU2bNrK2mRDiIhLOrpJSiqCgIIYPH8706dOJjY3ld7/7HW+++SYnTpzAbrc7u0QhhItq1aoViYmJji3kUlJSZEhTCHERWYT2GlQvuREeHs7EiRO57bbbmDp1KqtWreL++++nS5cuBAYGypuuEDeJ3W4nLy+PwsJCZ5dy1dq1a8fSpUupX78+AEeOHHFuQVfJZDIREhKCTid/2wtxs0g4u056vZ4ePXrQuHFjFixYwAcffMDcuXO59957SUtLw2QyObtEIeqc8vJyZsyYwZw5c5xdylXLz8/HZrOxadMmxo8f7+xyrlrTpk155ZVXcHd3d3YpQtRZEs5uAKUUERERPPDAA3Tv3p05c+bwwgsv0LBhQ1544QXi4uIc27QIIa6fzWZj79691K9fn969ezu7nKv2ww8/0KNHj1r3nvDTTz+xf/9+bDabs0sRok6TcHYDGQwG4uLi+NOf/sTAgQP5z3/+w/Dhwxk0aBDDhg2jQYMGslenEDdQSEgIiYmJzi7jqmiaRlxcXK18Lzh8+DAHDx50dhlC1HkyaeAGU0qh0+mIj4/nrbfe4rXXXuPAgQM89thjTJkyhSNHjshVnULcwpRSGAyGWhfMhBA1R3rObiK9Xk/Pnj1p0aIFa9as4auvvmLevHkMGjSIu+++m4CAAHmDFsLF7Nq1i2nTpgHwwgsv4O3tfcOfo7S0lMWLF5OQkEDjxo1v+PkvZcWKFdjtdtnHU4haQHrObjKlFMHBwQwcOJDJkyfzhz/8gXnz5tG3b1/HIrY2m01604S4CSwWC4WFhVgsFgDKysooLCz81TlTDRo0YPz48WRmZl6wVZumaRQUFDg+SktLr/l1a7FY2LNnDzk5Odf0+GuRmZnJoUOHgMrvpbS0lIKCAkpKSmQJICFcjPSc1RCdTkdAQAADBw6kR48ezJ8/nw8++ID//ve/3HXXXaSlpVGvXj3pSRPiBlqxYgX/+te/eOSRR+jTpw+TJk1i1apVTJo0ibi4uEs+xmw2ExoaisFguOB4eXk5/fv3JyoqCl9fX3x8fBg7dizx8fE18a3cUCdPnuSdd97hzJkzeHp6MmrUKFq0aCHLYwjhIiSc1aDq4OXt7c3w4cNJS0tj3rx5fPvtt8yZM4eBAwdy2223ERoaKiFNiBtA0zSCg4PZu3cvzZs3p6CgALPZDEB6ejpGo5GmTZty6tQp9u7dS6dOnTAajZc9X3BwMH/961+JjY1l0qRJfPfdd/zpT3+iuLiYNWvWcOLECcLCwujSpQuenp4opcjNzWXdunWcPXuWxo0b07x58wvOabFY+OGHH6hfvz65ublEREQQFxeHUgpN0/j555+xWq0kJyezZcsW9u7di5+fHx07diQkJASlFAcPHuTQoUMUFRWRk5NDgwYN6NixIwUFBaxcuRK73U5OTg6BgYFomsZ///tfPD09efrpp/nuu++YM2cOycnJsvSPEC5C/kxykurlN8aOHcubb77JoEGD+PLLLxk1ahQffvghhYWFsiWUEDeAv78/3t7efPfddzRv3hw3t8q/SdPT09m+fTsAp06dYvny5RcMY16OpmlYLBYKCgocWy9t2LCBwsJC4uLi2LRpE0uXLkXTNPLz85kyZQrHjh0jOjqakydPcu7cOce5LBYLn332Gbt37yYgIICMjAzWrVvneB6AJUuWOMLjsWPHiI+P5/jx48ydO5fS0lIADh06xOTJk8nLyyM5OZmYmBj0ej0ffPABWVlZeHl5sW3bNjRNo6Kigv3799O+fXt8fX1p2rQphw8fxmq13rhGF0JcF+k5czI3Nzfq169PvXr16Nu3L8uXL2fKlCl8/PHHjBkzhjvuuIOgoCC5ukuIa+Th4UF0dDQfffQRL774It9+++01nysrK4s///nPGAwG/P39efDBB9E0jTZt2lBUVISmaeTm5rJ582YGDBjA3r17KS4uZvz48QQEBGCz2dDr9RQVFVFeXs4XX3yBn58fr7zyCl5eXiQnJzN37lz279/PF198wYMPPsjevXsZNmwYoaGhhIeHY7fbsdls/PDDDxQXFzsCYmJiIkOGDMHHxwelFGfOnGHLli28//77BAYGsmPHDqByeLa8vByTycSkSZOIjo6mrKxM/hAUwoVIOHMRer2egIAA7rzzTm677Tbmz5/P119/zezZs+nZsyfdu3encePGMuwgxFXS6XQ0bNiQQYMGERoaesHx6kBis9kumBSvlEIpddFE+bCwMF555RViY2OZM2cO77//Pi+99BILFy5k8+bNaJpGVlaWIzCVlZVhNpsxGo3odLoL5nSVlZURERFBbm4uP//8Mx06dCAxMZEjR45w4sQJcnJy2LVrF3a7naioKNasWcPSpUuxWq2cPXuWioqKC+oLCgrCaDQ6/oirvmDBy8sLvV7vuOrUZDJhNpspLS3lkUceYdu2bXh4eMgff0K4EAlnLsjHx4cRI0Zw2223sXr1ahYtWsTChQtp27YtQ4YMoXnz5hdNVhZCXF6DBg1o0KDBBUN3vr6+7Nu3j6KiIg4cOEBBQYHjNrPZjJeXF5mZmQQFBV0UXHQ6HR4eHuTn51NWVsbChQsZNWoUKSkpfPXVV+zcuROAqKgozpw5w65du2jWrBnHjx8nLCwMpRQ+Pj506NABLy8vPvnkE0JCQoiPjycgIICVK1fSpUsX1qxZQ6NGjdA0jXXr1hEfH8+dd97J8uXLWbBgwa9+z0FBQfj4+LB161bi4+PZuXMnKSkpGI1GGjVqxJo1a0hJSWH79u2ORXGFEDeHpmlX9QeQvBpdlFKKoKAgBg4cSFpaGvv27WPatGmMHz+e5s2bM2HCBFq2bIler3fcXwhRqfqNUKfTOV4j1apDSMeOHVm3bh1PPPEEYWFhjt6u6vvcfffdvPvuu4SGhjJu3DiioqLIzc3l2WefxdvbG39/f0aNGoWPjw/9+vXjyy+/ZPbs2QQHBzsuOoiJiWHo0KHMmjWLjz/+mA4dOjBkyBCUUuj1enQ6HS1btuTQoUPMnDmThx9+mJYtW/Lzzz/Tq1cvVq5cSY8ePXBzc6NTp07MmDGDtWvXEhkZiZeXl+N1X32+85lMJh544AE+/fRTTCYTnp6ejm3k7rzzTiZNmsSjjz5KcHAwY8aM+dULIarbVAhxbaxW60Wv0V+jXP0Fl5qaqqWnpzu7DKfTNA2r1cqBAwf47LPPWLRoEUlJSYwYMYKUlBTCwsLkL19xyygqKuLZZ5/Fx8eH4cOHX/Z+drsdu93ueG1Uv46qv7ZarWia5hhu1Ov1jsBjt9uxWq2O4KOUcqyXBlwQsKrvC1x0ruo5YpqmOe4PlUOp1WFJ0zTsdjt6vR673e64b/UbevVzVJ/nUs9R/fjq+qsvKKpe0616qFav1zuO2+12lFJXvJXU4sWLWbNmDZ9//jleXl5X+NMSQqxfv55HHnmEZcuWOaYYJCUlFezdu9f3UveX3+a1RPWWL40aNeLVV19l9OjRzJs3j48//piPP/6YXr16kZaWRmJi4m/+BSzEreKX87yqX0fVfm16gE6nu+i1dLnX1qXue7kaqp3/x1R1Lx9wwV/X59d3ufNc7rbqMHapx1QHMiFEzbBYLBfMCf0t8uqshZRSNGzYkEcffZShQ4eyceNGx3ppLVu25K677iI1NVUWlBRCCCFcQEVFxVXNFZdwVotVL8MRHR1Nz5492bt3L19++SWjR4+mSZMmjB49mrZt2+Lp6XnBcIcQQgghao70nN2CdDod/v7+tG/fnvbt23P06FE+++wz/v73v+Pp6cntt99O165diYmJcayBJIQQQoiaUVRUdFVL1kg4qyPO/4HHxMTw3HPPMXbsWNasWeNYiqNhw4Z069aNLl26EBQU5MRqhRA329Veui+EuHny8vLw9fW94ulGEs7qKJ1OR3h4OHfeeSc9evRg7969rFixgnfeeYcpU6bQv39/Bg0aRGRkJCBLcYjay9WvOHcmaRshnE/TNPLy8vDz85OeM1FJKUVAQADt27cnNTWVCRMmsGjRIqZPn87UqVNJS0vj3nvvJSEhAW9v7yu+pF4IZ1JK4eHhwSeffML06dOdXc5Vy8/Px9PTs9ZdMWmxWGjRooW8RwhxFarDmb+/v/SciQsppTAajQQEBHDvvfdy11138fPPP/P111/zl7/8BR8fH3r06EG7du1ITEyUuWnCpZnNZsaPH0///v2dXco1GT58OM8//zzJycnOLuWq+fr6OhbZFUL8trKyMvLy8khOTpZwJn6dwWAgJSXFsTr5Tz/9xKpVq1iwYAHR0dF069aN7t27ExoaKiFNuBy9Xk9sbCyxsbHOLuWqaZqG2WymadOmtG/f3tnlCCFusuLiYs6ePUtERIQMa4oro9PpiI+PJy4ujr59+3L06FFWr17NZ599xn/+8x+6d+/OXXfdRZMmTRxrtEhYE0IIIa5MQUEBZWVlhISESDgTV0en0xEQEIC/vz9NmzZl9OjRrFu3ji+++IIHHniAuLg4Bg0aROfOnQkKCsLb21sWuRVCCCF+Q15eHjqd7qqmC0k4Exeo3nvP09OTXr160b17dw4ePMiyZcv4/vvv+fLLL0lMTKRdu3a0bNmShISEq1r1WAghhLhVVF8MoNfrHXtqXgkJZ+JX6fV6EhISSEhIYMSIEezYsYMNGzYwZ84cPvjgA1q1akXv3r3p2LEjXl5eMuQphBBCVLHb7Zw6dQpvb28JZ+Lm8PPzo1OnTrRt25b77ruPQ4cO8c033/DXv/4VvV7PwIEDGTRoELGxsbi5ucmWUUIIIW5pVquVffv2ERUVJeFM3DzVS3KEhIQQHBxMmzZtKCwsZM2aNcyYMYPZs2cTGRlJ9+7d6d69O+Hh4fj7+6PX651duhBCCFGjKioqyMjIoF+/flf1e1DCmbhmSimUUvj6+tK3b1969+7N/v37WbduHevWrWPx4sWEhobSunVr2rZtS3JysqyPJIQQ4pZRVlbGyZMniYuLu6qRJAln4oZxc3OjcePGNGrUiKFDh3Lo0CF27drF2rVr+fLLL4mMjKRr16707duX6Ohox+Nk6FMIIURddODAATRNIy4u7qoeJ+FM3HBKKfz9/WnVqhUtWrRgyJAh5OTk8N133zFnzhzefvttUlNTGTx4MG3btsXPzw93d3dZmkMIIUSdsm7dOmJiYggJCbmqx0k4EzdN9bIcXl5eeHp68sgjj/DQQw+xb98+5s+fz4cffsikSZNo2LAhPXv2JCEhgdjYWHx9faU3TQghRK1mt9tZt24daWlpV935IOFM1IjqsOXm5kZycjKNGzdm/Pjx7Ny5k02bNjFnzhzy8vIIDw+nXbt2tG/fnoSEBEwmk5MrF0IIIa5eVlYWe/fu5fnnn7/qx0o4E06hlHIszdG+fXtycnI4ceIEGzduZOnSpbz//vvUr1+fnj170r9/fyIjIx0BT3rVhBBCuLp169bh5+dHcnLyVT9WwplwOr1eT3h4OGFhYbRs2ZLRo0eTnZ3N8uXLmTt3Lu+++y7x8fF0796dnj17Ehoaio+PDwaDQYKaEEIIl2O321mxYgVdu3a9pl10JJwJl1E9R02v1xMTE8ODDz7I/fffz549e1i2bBnr169n7ty5hIWF0bp1a5o3b05CQgIRERFyMYEQQgiXkZWVxZ49e3jhhReu6fESzoRL0+l0jjlqDzzwAAcOHGDHjh1s3ryZ5cuXo2kaTZo0oX379rRr146QkBDpTRNCCOFUO3fuRK/X06hRo2t6vIQzUSsopfDx8XEszzF48GDy8vLIyMjghx9+4M033+TcuXOkpKTQr18/OnfujJ+fH25ubo7FcoUQQoibraKigs2bN9O0aVO8vb2v6fePhDNR6+h0Onx8fPDx8aFevXp069aNiooK9uzZw7fffst//vMfXnnlFRITE+nTpw8pKSkEBwcTEBDgCGtCCCHEzZCdnc3mzZu5//77cXd3v6ZzSDgTtVp1r5jZbKZly5a0aNGCJ554gm3btrFp0yaWLFnCN998g7+/P02aNKFZs2Y0atSIqKgo3Nzkv78QQogbR9M0Dh8+THl5OQkJCdfcGSC/nUSdUr3XZ1paGl26dOHs2bMcOXKEQ4cOsW3bNqZMmUJeXh4xMTGkpaXRrVs3YmJi5IICIYQQ181ms7Fy5Uri4uIu2Kbwakk4E3WWUorAwEACAwNp0aIFffv2pbi4mCNHjrBs2TKmT5/OP//5TxISEkhLS6Nfv36Eh4djNBoxGo2OcwghhBBXorS0lCVLlvDYY49hNpuv+TwSzsQtQafT4e7ujtlsJjAwkFatWvHEE0+we/duli1bxurVq5k5cyZRUVE0a9aM1q1bExMTQ3h4OL6+vuj1emd/C0IIIVzcwoUL0el0pKWlXdcf9xLOxC3l/BeLXq+nadOmNG3alAkTJrB//3527NjBvn37mDFjBsXFxXh7exMfH0+LFi1o2bIl4eHhMgQqhBDiImVlZUyfPp3BgwcTFBR0XeeScCYEYDKZHEGtoqKCs2fPcvLkSfbu3cvGjRt56623KCgoIDExkU6dOtGtWzfi4+MvOIcMgQohxK1J0zQ2btzI4cOHueuuu677fBLOhPgFo9FIWFgYoaGhNG/enKFDh1JeXs7OnTtZtmwZM2fO5D//+Q8NGjSgffv29OjRg+joaLy8vPDw8ECn00lQExcoLS3FZrNdcKysrIyioiKgctjdbDZLr6wQtZTVamXu3LmOLQavl9I07QaUdfOkpqZq6enpzi5DCAe73c6BAwf46aefWLduHQcOHMBkMtGgQQNatmxJfHw8sbGxhIWFXdOeaqLu+eSTT9i6davj6xkzZtC9e3dCQkIAiIqK4o9//OM1r4kkhHCubdu28dRTT/G3v/2NNm3aXNEf6ElJSQV79+71vdRt0nMmxFXS6XQkJCSQkJDAPffcw4kTJ8jIyGDXrl2sW7eOWbNmAdC4cWNSUlJITU0lLi5OLiq4hRUVFfH2229fcGzmzJlA5dzHkSNHYjKZnFGaEOI6VVRUsGLFCurXr39da5udT3rOhLgBNE3DZrNRUFDAuXPnOHjwID/++CMbNmwgKyuLevXq0alTJ7p27UrLli0xGAzodDrHMJYMg9ZtR48eJTk52TGMeT43NzfmzZtHnz59nFCZEOJ6aJrGyZMn+f3vf88DDzzA4MGDr3h6gvScCXGTKaVwc3MjICAAf39/4uLi6NGjB5qmcerUKVavXs3y5cuZO3cuNpuNRo0a0bZtW8dm7X5+fnh5eUnvWh0VFBREv379+Prrr7Hb7RfcVq9ePdLS0pxUmRDiev3www8YDAY6dep0w+aNSjgT4gar7gWrDlr16tXjnnvu4Z577uHcuXNs27aNrVu3sm3bNpYsWYKnpydBQUEkJibSsGFDEhISiIqKciyEK2o/k8lEnz59mD179gXhTCnF8OHDZUhTiFoqLy+PadOmcd999znmkN4IEs6EqEH+/v5069aNrl27UlxczKlTpzh+/DgZGRns2LGDRYsWUVxcTP369UlJSaFTp040btz4ulaaFs6n0+lo3rw5iYmJ7Nq1i+rpJGazmd69e8tVmkLUUl9//TUAAwYMuKHTUyScCeEESim8vLxo2LAh8fHxdO7cmbKyMoqLizl69CirVq1i9erVTJ48GV9fX1JSUujQoQPdu3fHx8cHg8GAwWCQuWq1hFKKmJgY2rZty86dOx3HOnXqRIMGDZxcnRDiWhw5coRPP/2Up59+moCAgBt6bglnQjhZ9Xw1Ly8vPD09CQkJITU1FYDTp0+zbt061q5dy8cff8ykSZOoX78+jRo1IjU1laioKIKDgwkKCsJoNEpYc2G+vr60b9+e2bNnk5eX59jiJTg4WH5uQtQyFRUVTJ8+nXr16tG7d+8b/hqWcCaEC/nlCzwkJIRBgwYxaNAgCgsL2bNnD3v27CEjI4Pp06djsVgwGAxERkYSHx9PYmIijRo1Ijg4WIbKXIxSig4dOlC/fn22bdtGVFQUKSkpMt9MiFpG0zQOHDjAmjVreOihh27K/GAJZ0LUEt7e3rRp04Y2bdpgsVg4e/Ysp0+f5sSJE+zcuZN169bx2WefoWkasbGxdO7cmXbt2pGUlHRBAJBeGueJj4+ncePG/PzzzyQkJNCiRQtnlySEuEoVFRXMnTuXiIgI2rZte1P+EJZwJkQtZDAYCA0NJTQ0lOTkZLp3747FYqG0tJSMjAx+/PFHFixYwFtvvYWXlxctWrSgY8eOtG3blsDAQDw8PDCbzbi51c63ALvdTn5+PoWFhc4u5aq1a9eOefPm0aBBA8rLyzl69KizS7oqJpNJembFLau612z+/Pm8/PLLN3yuWTVZhFaIOsput5Odnc22bdvYvHkzmzdvJjc3Fy8vLxo0aECzZs1ISEggIiKC8PBwvLy8ak2vWlFREc8++ywzv52LX1Cws8u5atnHjhAcEYWulq1rV5h3jqTY+sybNxcvLy9nlyNEjSstLWXcuHGEhYXxyiuvXNe0BFmEVohbkE6nIzw8nPDwcG6//XaKi4s5cuQImZmZZGRksHnzZhYvXkxpaSkhISE0bNiQRo0a0aRJE+Li4mpFr1rXQcPo0n+ws8u4OhrsTl9PYsvW6N1qVzjbsmoFWTvkj2Vx61q4cCH79u3jpZdeuqnzRV3/3VcIcd2ql+5ITk6mcePG2O12CgoKyM/P59y5c+zatYvNmzfz/vvvk5WVhb+/P82aNaNdu3Z07tyZ8PBwdDodSinHhyvwDQgkPCbO2WVcFU3TCIqIwlALFxn2D9pFjq52BUohbgRN0zh27BhTpkxhwoQJxMbG3tTnk3AmxC1GKYVer8ff3x9/f39iYmJo3rw5w4cPdwyFrlu3jo0bN/LBBx8wceJEgoKCaNq0KampqbRq1QpfX1+8vb3x9vZGr9e7TFi7kAa4Wl0aSqlaGcyEuJWVlZXx+eefExAQwNChQ2/6e56EMyFucdU9YdUTvOvVq8fdd9/N3XffTUVFBYcPH2bHjh3s3LmTpUuXMmPGDDw8PAgKCiIpKYmYmBiio6OJiYkhKCjout+0qufBXv+b35U//kzWKbKOHSaxRSpuBoPjeHlZKYd2/UxYdH38g0PPr/Kqzv/LmiwVFRzYsY3AsHBCIutdw3muXubundg1Ow2Sm9XI8wlRV9jtdjZt2sTq1at55plnamS+pYQzIcRlGY1GEhISSEhIYMiQIRQVFXHixAlOnDjBkSNH2LVrF2vXriUvLw+TyURsbCxNmzalefPmJCcnX9NFBhs3buTgwYMMGTLkOraturrwdCLzID99/x1xjZpeEM7KiotZu/A7Ot4x4Bfh7HqCo0ZFWSnrFs2jece0Ggtn29etwma1XEc4c+2Lx4S4WUpLS3nvvfdo27YtrVu3rpErlSWcCSGuiFIKb29vkpKSSEpKwmazObacKigoYNeuXaxfv55vv/2Wt956C4PBQLNmzWjdujVt27YlMTERk8mEXq/Hzc3tknPXNE1j5cqV/Pvf/+brr7/m//7v/2jSpInj/ldRreMzu93OpmWL+fbDd3n41X8TGRd/Tb1yFWVlPHNXXzQ03D29SGjRiqHjH8Xbz/8qz+T8oday0hJmTf4Puzb+RL34RIaO/yMhUdG/0S7Or1uImma325k2bRrZ2dn885//xMPDo0aeV8KZEOKa6PV6PD098fT0JDg4mAYNGjBgwAA0TaOkpMSxMO7GjRv56quvAIiOjiYuLo6UlBSio6Px9/cnMDAQX19fdDodFouFffv2kZ2dzbx58/jpp58YP348I0eOJD4+/pr+Yi0vLWH/9i2UFBawf/tmImIboJTCarFw8vAhSgoLOHPqBHbNDoDdZuNMdhZnsk5itVRgs1mByuCodDoefvVNwqLrM+21F1n4xcfc/fATWCoqOHUkk+KCfDy8vYmoH4eboXI7rbKSYk4dPUx5SQn+IaEEhUVcUJ/dZuP4wQy8/f0pLS7G08cXH/8AlFJomsaZ7FNodjsBIWGcPnmcc7k5mMzuhEXXx92zsmcy/+wZCs6ewVJRTmlREb6BQYRF16eivIyTmQfRNI3SoiKMZhOaprHkq8/IOnKYJ/79Pku//oIFn3/EqCefw2CU3QqEqKZpGhs3buTDDz/kn//8J1FRUTU2v1bCmRDiup3/hlV9ZWi7du1o164dNpuNM2fOOJbw2LdvH//9738pKChAp9MRFBREfHw8CQkJeHp6kpmZCVS+MZ4+fZq///3vrFq1irFjx3LHHXdc1aKPmqZRUlRI1tHDdOwzgN2b1tOl/1B0Oh071q/hx7mzCAqPJPv4UTR7ZTjLOXGM+Z9+gE6nR9PsZB3NvOh7dXMz4O7phaWiHIADO7ayc8M6LOXlnD51gs59B9GyS3fKSkpY+vWXnDqaibePH75BwXS4vZ8jBNlsVjYuW8z2tT/Sc9g9bFqxlMCQMHreda/j+ZbO/ILAsAiSWqayZfVySouKyMvNoWGzVnQZMASjyczuTT8x/7MPaZDcDJO7O7GNmhAUHsGSGZ9zNGMfASGh7NuWTvOOXbBZLezblk672/oSGBZB0/ad+ebdN7FarBLOhDjP8ePH+fe//03//v3p1q1bjV74JOFMCHFT6fV6QkJCCAkJoW3btlgsFvLy8sjNzeX06dMcP36c3bt3880333D8+HH279/veKymaWiaxqpVq9i9ezcLFizgySefJCEh4YqfP3PXTpRStO/dj3efe5z8M6fxCQhk7aJ5tErrQbted7Byzjfs3LgWTbNzaNfPANz5h8c4m53Fl/+e6DjX2ZwsPnj5OYxmEzarlbHP/x1N0wiOrMdtd4/C08eH1fO/ZfOPy2jRqSvHMvayf/sWhv3hMcJj4ijKz8PdyxtrRWWP3NqFcwHF8D8+SXj9OM5knWLb2h/JPnaE9UsW0KnvYDJ376RNj9vxDQqmS78h+AQEsjt9PesWfUdq914YTZXz8jy9fLhj5GhCoqLR6XQUF+azdtFcHvr7vwmJqsfUF58BFOVlZZQWF+Pl48vcj97DPziUksJCtKqeQyFE5dWZX3/9NQC/+93vanxHDAlnQogaZTAYCA4OJjg4mKSkJDRNw2KxUF5ezpw5c3jooYcuekx1L9qsWbNYunQp48aNo6ioCLcrmO61Ydn3hEZF4+Hjg4e3D5t/XEaH2/tTeO4s9RomYjCZCIqIxOzhhd1u51zuaXyDgvH08cVmtRIQEuY4l19QCPc89jSh9WL44ZsvWfrNFzz4zIucyDzA919MIzfrJIV5Z4mIiUMD8nJP4+3nT2BYBAajEf/gEACsFRUU5edzLieHspIix1T7uOSmzPt4MqdPniB95Q9ExjWkIO8M0QlJHDuwj4VffMyxjH0UFeTj5eOL3Wpz1BYR1wC/oBDHL5GCs+ewWiyE14/FZHYnvH4cCtC7uaF3c8Nut9N7xP0c2bcHN4PBRZdDEaLmaZpGeno6c+fO5cUXXyQyMrLGXx+yOZoQwmmql/AwmUx4eHhw7tw5ioqKLnv/6iHS119/nRkzZlTNs6q47P0L886ya+Najh/K4OO//xWb1cqm5UtwMxjwDQziyN7dVJSXkXPiGKXFReh0OgJCQsk7nUNRfh55uTmcyT7lOJ9Op8PTx4eA0FCSWrXm+IEMCvPzmPXef+jcbxAvfzqLQWP+gMm9ctKwf0gohefOcvrEcSwV5ZzNzqK8tAQAH/8ABoweT/8Hx/PVW//gXE52ZYgzmdm6ajlN23Zk6+oV1E9MRqfTseK/M4moH8v/fTKTB595Ef/gELTzrqDUKd0Fc/Z9AwMxGE2cOHSA4sICThw6gAaYzO5E1I9j39Z0UIoTmQcIjY6pdVtJCXGznDhxgpdffplBgwbRuXNnp+wjKz1nQgiXUFJSwp49exyBzWar7BXS6/UEBARQr149wsPDCQ4Oxt/fnw0bNlBRUY7dbrvsOXesX0tovfo8+dZUDEYTuadO8tofHiT7+DE69xvMslkzOLxnF+dyswFQSkeD5Gbs3byJmZP+WRVY/heACs6dZd4nU/ANDKLg7Bna3dYXT28fGqW0ZeualRzctYP83NOOmqLiE0hKacPirz7F3dOL4MgoOtze39FTpZSO9r37c+zAfpbM/JyBoyfQsHkrDu/Zxagnn+ODl58lbdCdKKWISWrM7k3r+ea9f1NeWorVYvnV9nT39CJtwFC++2QKfkHBFOadrfoeFV0HDWP2lEl89o+/UVyQT+d+gzEYZGFcIQoKCnjttdcIDw/nvvvuw3De0jo1STY+F0K4hDNnzvD0009z8uRJGjRoQGxsLDExMY5N2T08PHB3d8fd3R273c5LL71EeUAEt9/zwGXPee50DuWlJYRF1wdAs9s5djCDoPAITGZ3R4+Z2cMDvd6N4IgoUIr83NOcy83BaDJjMBrxCQjE5O7Bod070Ox2lNJh9vAgOCISk7sHRfl5nD55HAAPL2/sdjth0fUrr9YsLSH35AkqysvwDQjCLzgE0DiTlYWHtzee3j4U5edTlH+O4IgoivLPUVJURFi9GE4ePoR/cAiePr6UFhdx+uQJLBXleHj5ABrBEZG4GYwU5udRXlJMQGi44698TdMoLy0h+/hR0DRM7h4YTWYCQsOw2+2cPnmcorxzmD29CImMuqKLAdYt+o59Py7mq88/lY3PRZ1jtVr54IMPmD59Op9//jn169e/qc8nG58LIVxeQEAA7733HvC/4c7L7eVZVFR0RXNAqud4VVM6HdENEx1fh8dcen88/5BQ/ENCLzoe36T5Je/v5euHl6/fJW8zu3sQ1aDhRcdDIqMcn3v7+eHtV/l4v6AQ/IIq6z7/ce6eXhfUfj5vXz+8f/H8SinMHp7EJDS66P46nY7QqGhCo6IveT4hbjV2u52VK1fy9ddf8/LLLxMd7dzXhoQzIYRLUEphlD0nr9Evd0RwxX1FhXBNmqaRmZnJO++8Q79+/WjXrp1T5pmdTy4IEEKIWu+XQUyCmRBXqri4mL///e8EBAQwcuRI3N3dnV2ShDMhhLhemqax5cdlzJr8n+s+19pF83hy8G081q8bu9M3XH9xQojLqqio4F//+heHDx/m5ZdfJiQk5LcfVANkWFMIIa6ApmmUlZaQffQwlooK/INDCQwLx2a1cvzgfg7u/Jkj+/ewd8smx/ZJoJF/Jpcz2VkopQgKj8Tbzx+b1cKpI5U7D5SVlOAbEEhQRBQ6nY4Ot/enSZsOTPvHS45lN36N3W6n8NxZzmSdxK5pBIVF4BsYJOuWCfEbysvL+eKLL1i6dCnvv/++U9YzuxwJZ0KIuu03p19d2fwszW5n/eIFbF+3iqCwcDx9/Og9/D6UTrFr008c2Lmdszmn2LR8MfFNWhBWL4asY0dYMuNzQMNqteIXGETPu0Zis1qZ/Nc/E9WgIR7ePhSePcvAMROITkiqvABCp0Nd4dBkcUE+G5d9T+7JE5QUF2EwGOn/4PhfXAwhc9CEOJ/dbmfNmjV88803PPbYYyQlJblMMAMJZ0KIuu4332+v7A3ZbrdzeO8u4ho35bbho8jPPY2bwYDRbOaOkWPwCwxh79aNjHry+ar729i+bhV6NzcGjplAeVkpsye/RdbRwwRHRGE0mWnbqw/JbTowZ+o7pK/8geiEpKv+9oxmM806dMEvMJiSokJmTvonR/bt/kU4c51fOkI4W/UFABMnTqR379706dMHNzfXikOuVY0QQrgovZsbPe68h2WzpvPOXx4jpmES/R/8/WXvb62wcCbrJD/O/YbNP/4AGpSVFNPutr6AhpevH0FhkZhMZsLrx5GxYxuapl3VX++aplFeUlK5N+j6tRTl52GxVNCsY9oN+I6FqHs0TSMnJ4dHHnmEpk2bMmHCBDw8PJxd1kUknAkhxBXQNA03g4Fhf3gc0HjzTxOITW5GateeABiMxqqV+ytwMxhxMxoJDI3g9nsfoPeIBzAYjJw7nY1vYBClxcUU5eeRm3WS0OgYTh3JJCA49DeDmabZ2bXxJ4Ij6hFar3Idpsy9uzi8ZycPT/w3Sqfn8zdegRu5uLiMiIo6QtM0srKyeO655wgICOBvf/sbnp6eLjWcWU3CmRCibrtRc840jYO7fubo/r3YLBa8/f2JjG3guD2ifhz5Z87w+RuvktQylXa33UHzDp1Z9t8ZzH7/LfR6N3wDA+l4xyAALBUVbPzhe3asX0PB2TMMHD0BgBOHDvD99Gkc2rWDivIyjmbsddxmt9n54G/P0fvuUdwxagwAvgGBGM3uLPziY4xmd0qLCq/5e7wk1/u9JcQ1KSgoYNKkSZSWlvLqq6+6bDADCWdCiLruBs050+l0pKT1oF6DhlSUl+MbEEhI1Qr7SinC6scy8olnKS7IxzcwCJQiLLo+fe8by9msLDTNTkBoGH6BQeSdycU/OIQ2PW/H288f38AggiOi0ADfwCA69BlAhz4DgMo5ZY4a9Hoe/cfbjh0ElFJENWjI3X98gsK8PNw9Pek+5G48fX65I8yVfY/SSSbqKpvNxgcffMDWrVv529/+RkxMjMsGM5BwJoQQV0QphYeXN/WTki95u5ubgXrxCb98EP5BIfgHXbx2kl6vJzQq+qKtnbx8/WiU0uayNcQ1bnrh8xqMRNRvcMn7Xy3X/VUlxLUrLy9n+vTpzJw5kzfffJOUlBSn7wDwWyScCSFEDTOZzTRu3Q4Pb29nlyJEnVZaWsrs2bP58MMPefXVV+ncubOzS7oirh0dhRCijtE0jfSVP9C0XScCQsKAyrXK1i9ZyJnsU06uToi6w2q1snjxYqZNm8YjjzxCjx49nF3SFZNwJoQQNSj72BHWLpyLf0io45jBZCLn+BF2b1qP3WZzYnVC1A12u521a9fy+uuvc8899zBo0CCXnmP2SxLOhBCihmiaxo/zZtG8YxfMHp6O4wajiejExhzYsZ2S4iInVihE7WexWFizZg2PP/44I0eOZNSoURiNRglnQgjhMm7gkl/XrKqGivIy9m3dTOOUthf8olBKERoVzemTxykrLkK7keuUCXELsVqt/Pjjj7zwwgs8+OCDjBs3DoPBUKuCGUg4E0LUda7wnlxVQ+G5s5QWFRIUHnnRXbx8/bBWVFBcWFADBUn4E3WP3W5n3bp1vPbaawwZMoSxY8diMBicXdY1kXAmhBA1xGazoaGh0+svuk3pdKDAbq2JOWeukFiFuHFsNhvp6ek8+eST3HHHHYwZMwaTyeTssq6ZhDMhhACurTfp6h7j7euP0WQm78zpi24rLSxEKYWn7y8XkBVC/BqLxcLKlSt5+OGHGT58OA899BAeHh61bijzfBLOhBACuLbepKt7jLuXF/WTGpOxfctFt50+dZyA0DDMHq67pYwQrsZisbBkyRJeffVVRo4cyUMPPYTJZKr1ryEJZ0IIUUOUUnToM5D0FUux2/83fGmzWTmWsY/ohEZ4eMnCtEJcCbvdzvLly3nttdcYOnQoY8eOxWg0OrusG0LCmRBC1KCEZi0Jr9+AQ7t3Oo4V5eVRVlJM0zYd0LtVb9zyyyFTmcQvRLXy8nIWLFjAM888wwMPPMCYMWNwd3ev9T1m1WT7JiFEHeS6W3jr9Dru/dMzFxzzDQxi4Jg//OKev6zf2d+PhEPhGgoLC5k1axYffvghTz31FHfffbfL75V5tSScCSHqoJsdZK4n/Dk7ZF2r2lq3qEtKSkr46KOPWLx4MU8++SQDBw6sc8EMJJwJIeqsm9l7dq3ndd0ePSFcmaZpWCwWXn/9dX788Ueee+45unbtWieDGUg4E0LUWa4YglyxpqtT+78DUdtomkZWVhYTJ05kx44d/Pvf/6ZFixZ1Zn7ZpUg4E0IIccVk5pmoSZqmceDAAV577TUKCgp47733aNSokbPLuumuK5wppf4EjKXy9boDeBDwAGYC9YHDwF2app2ruv9fgDGADXhE07TF1/P8QohbW/bxo+zbmu7sMuocu82KTn/xr4eThw9hs1qcUJG4Ve3Zs4fnnnuOsLAwJk6cSIMGDZxdUo245nCmlIoEHgEaa5pWqpT6GhgONAaWaZr2mlLqGeAZ4GmlVOOq25OBCOAHpVSCpmk1sVeJEKIOUUrh4eHBxtlfsG3FEmeXU+dYLFZKS0vQAHd398qNowFLRQWtWjSv08NJwvk0TcNms7Fu3Tr+/Oc/k5aWxlNPPUVgYOAt83/veoc13QB3pZSFyh6zk8BfgK5Vt38KrASeBgYCMzRNKwcylVIHgDbAT9dZgxDiFuPh4cHEiROZOHGis0upk3bu3Mkbb7xBRkYGffr0oX///iQlJdXqvQpF7aBpGsXFxcyePZsPPviAu+++m0cffRSdTnfLBDO4jnCmadoJpdQbwFGgFFiiadoSpVSopmmnqu5zSikVUvWQSGD9eac4XnVMCCGuyq30Ju0MTZo0YfLkyaxatYrvvvuOp59+mk6dOjFo0CAaNWqEm5tMVxY3R0FBAZMmTeKnn37i4YcfZsiQIej1emeXVeOuZ1jTn8resFggD/hGKTXy1x5yiWOXnFuqlBoHjAOIjo6+1hKFEEJcg+ph4969e9OuXTu2b9/OtGnT+P3vf0+vXr34wx/+QHBwsOO+QlwvTdM4ffo0f/3rXzl06BAvvvgirVu3rjPbMV2t61kgpCeQqWnaaU3TLMB/gQ5AtlIqHKDq35yq+x8H6p33+Cgqh0EvomnaVE3TUjVNS61+AxBCCFGzlFL4+fnRpUsX3nnnHV5//XV27NhBnz59mDx5MqdOncJisaBpcg2nuDbV88t+/vlnxo8fz7lz5/jss89o3779LRvM4PrC2VGgnVLKQ1X+6dQD2APMA+6vus/9wNyqz+cBw5VSJqVULNAQ2Hgdzy+EEKIGKKXw9PSkU6dOfPXVV/zlL3/hhx9+YNy4cXz00UccPHgQu93u7DJFLWSz2ViyZAnPP/88DRo0YPLkyYSFhdXZxWWv1PXMOduglJoFbAGswFZgKuAFfK2UGkNlgBtWdf9dVVd07q66/0NypaYQQtQuRqORoUOHkpaWxvLly5k7dy7z5s3j9ttvZ8iQIURGRspQp7gihYWFfPHFF3z11VcMHz6cESNG4O/v7+yyXIJy9e7o1NRULT1d1jESQghXY7PZyM3NZf369Xz44Yfk5+czZswYhg8fXrn8hlIS1MRFNE0jJyeHN954g9WrV/PKK6/QqVMnzGazs0urUUlJSQV79+71vdRtEs6EEEJcM03T0DSNwsJC5s2bx/vvv4+XlxdjxowhLS2N4ODgW36ISvyP1Wpl586dvPzyy+j1el599VUaNGhwS/4fkXAmhBCiRuTm5vLVV1+xZMkSfHx8GDBgAGlpaYSFhTm7NOFkhYWFLFq0iHfffZcuXbrw+9//nsjIW3dFLQlnQgghaozVauXw4cOsWLGCuXPnYjKZGDlyJL1798bDw8PZ5YkapmkaZ8+e5b333mPZsmWMGzeOgQMH4unp6ezSnErCmRBCiBpnsVjIzs7m22+/5auvviIgIIBnnnmG1NRUjEbjLTmUdaux2+2cOnWKxx9/nJKSEp577jlSU1NlIWN+PZzJK0MIIcRNYTAYiIyM5OGHH2bWrFm0b9+eP/3pTzz88MOsWLGCc+fOyRppdVT1PMSFCxcyYsQIQkNDmTp1Km3btr0lV/y/WtJzJoQQokZomsaOHTuYPXs2mzZtIiYmhqFDh9KhQwcZ7qxDNE0jKyuLqVOnsmrVKoYMGcKDDz4oP+Nf+LWeM+lXFEIIUSOUUjRr1oyEhAQyMjKYM2cOzz//PE2aNGH8+PG0atXqltvgui6p7uzZvn07L7zwAp6enrzwwgu0b98eg8Hg5OpqFxnWFEIIUaPMZjNNmjThqaee4ssvvyQwMJAxY8bw2GOPkZGRQWlpqQx31jKaplFUVMTnn3/Ogw8+SLNmzXj33Xfp3LkzRqNRAvdVkmFNIYQQTmW329m+fTvvv/8++/bto3PnzvTt25eWLVtiMpmcXZ74DZqmsWfPHj788EN+/vlnJkyYwODBg+WCj98gV2sKIYRweeXl5axbt465c+eyc+dO2rVrx5133kmTJk3k6j4XVV5ezvfff8+UKVOIjY3ld7/7nfy8rpDMORNCCOHyTCYTXbt2pVWrVuzfv59p06YxduxYbr/9dh566CFCQ0NlSygXoWkaubm5TJo0iUWLFvHHP/6R/v374+/vLz+fG0DCmRBCCJehlMLX15fU1FSaNWtGeno6b731FgMHDmTEiBEMHjyYiIgIx96douaVlJSwdetWXnrpJTw9Pfnggw9o1qyZLJFxA0k4E0II4XKUUphMJjp27EhKSgrLly/n008/5YcffqBnz57cdtttNG7cWAJaDbLb7Zw4cYIvv/yS+fPnM2jQIO6//36Cg4OdXVqdI+FMCCGESzObzfTp04fWrVuzevVq5s+fz3fffceAAQMYNmwYEREREtJuMrvdzsqVK3nvvfcwm828+OKLdO3aVeaW3SRyQYAQQohaw2azUVBQwPr165k8eTK5ubncd9993HvvvXh6esoVgjeY3W6nsLCQDz74gM8//5wRI0Zw//33ExoaKm19neSCACGEEHWCXq/H39+f22+/nc6dO7No0SKmTJnC3LlzeeCBB+jUqRNhYWEy/+k6aZpGaWkpP/30E++++y4lJSV89NFHtGrVSi7KqAHScyaEEKJWy87OZt68eSxcuBCj0Ujv3r3p37+/zIW6RpqmcfjwYT799FPWrVtH165dGT16tONqWXFjyDpnQggh6jSbzcaxY8dYsWIFM2bMwM3Njfvuu4++ffvi6ekpoeIKaJpGeXk5ixYtYvLkyYSFhTFhwgRatGiBu7u7s8urcyScCSGEuCVYrVYKCgqYM2cOU6dOJSgoiCeffJLU1FQ8PDxkntRlWK1WTp06xT/+8Q82bNjAhAkTGDJkCD4+PhJsbxIJZ0IIIW4pmqZx6tQpPvvsMxYtWkRsbCx33XUXbdu2JSAgQAJHFbvdTk5ODt9//z2ffPIJSUlJPProozRq1Eja6CaTcCaEEOKWpGka+/fvZ/bs2axZs4awsDCGDBlCjx49LjtUp2kamqbV+onvmqZRUlKC0WjEYDBcdLvNZuOHH37gyy+/pLCwkLvvvpv+/fvj6enphGpvPRLOhBBC3LKq51JlZmayaNEiZsyYQWJiIn/84x9p1aoVer3eEcKqtyWaO3cuXbt2pUGDBrUyoGmaRkVFBU8//TS9e/fm9ttvRylF9e/8rKws3nrrLZYtW8aQIUMYNmwYsbGxcpVrDZKlNIQQQtyylFKYzWaSkpJo2LAhd911F5MmTeL3v/89rVu35uGHHyY2NhZPT080TWPjxo088cQTdOnShcmTJxMVFeXsb+GqFRcX87e//Y2pU6eyZ88eWrVqRUhICGfPnmXZsmW8++67hISE8Nlnn9GwYcMLAqpwPglnQgghbglKKdzc3IiKimLixImMHDmSadOm8cc//pF27drRr18/kpKSeOeddygoKGD+/Pl4eXnx+uuvEx0d7ezyr1hxcTEfffQRn3zyCaWlpWzevJmZM2fSpEkTpk+fzsmTJ3nwwQcZNmwYHh4eEspckAxrCiGEuGWVl5ezfv165s+fz5YtW6hXrx4zZsygvLwcAIPBwKhRo3jppZdqRQ+azWZj1qxZ/PnPf+bYsWOO4y1btsTf359evXoxePBg4uPjZQjTyWRYUwghhLgEk8lEly5daNWqFdu2beP+++93BDMAi8XC9OnTcXd358UXXyQwMNBle5qqh2Qff/xxTp48ecFtO3bs4NFHH+Xhhx/Gy8vLSRWKKyULvgghhLilKaXw9PRkz549nDt37qLby8rKmDx5Mq+++ir5+flOqPC32Ww2Nm3axH333cepU6cuut1qtfLpp5+SlZXlhOrE1ZJwJoQQ4pZ3/PhxZs2aRV5e3iVvt9vtvP322/zrX/9yuYCmaRq7du3imWee4fDhw1xuutKZM2f4xz/+QUVFRQ1XKK6WhDMhhBC3NLvdzubNm9mxY4fj2KWuXrRarbz99tu8/fbbFBUV1XSZl6RpGtnZ2bz00kusW7cOq9XquO38ddrc3d1JTk7GYDBcNoAK1yEXBAghhKi1tm/fzs6dO6/7PKWlpZw7d44zZ85w8uRJsrKyOHXqFCdOnCAvL8+xKK1Op8PDw4Phw4fTqVMnp0+q1zSNzz//nOXLl2O327Hb7Sil8PX1JTY2ltjYWOrXr09ISAju7u6YTKYbso2V0WjkzjvvdNn5d7WBLEIrhBCiTvrrX//Kp59+Sr169W7YvplX+nvRbrej0+mcFlA0TaO0tBSTyfSr3/uNrq+goICTJ0+Sk5Mj4ew6yNWaQggh6qy+ffsybty4S25RdLNomobVasXNzc1pAcVut1NQUICXlxdubv/7dV7dy3ezbN++neeff/6mnV9IOBNCCCGumlKqRsPgpeh0Ovz8/C46Lr1ZtZ+EMyGEEOIXCgsLyc/PR6fTERYWdsOGTM9ntVopLCzE09MTo9F4Q8/9y96z6qHagoIC3NzcZHNzFydXawohhBC/sHnzZt58882LFqW9kU6dOsVbb73FoUOHbvi5f9l7Vr3p+ccff8zixYtv+POJG0t6zoQQQtRpGzduJD09nQEDBhAZGcl3333HuXPnGDRoEL6+l5yPTdeuXWnevDkPPPDABcfPnDnD1KlTsVqteHh40LJlSzp16nTDe75qQnl5OXPmzOHo0aM0bNiQPn36YDKZnF2WQHrOhBBC1HEHDhzg+++/Z+vWrVgsFr7++mvWrl1LSUkJVqsVu90OVE6wt1qtv3q1ZlFREbt37yYtLY0uXbrw3XffsXbtWqBy6NBisVBRUXHReex2u+M2m8120XNUX2BQfb/qmqpvs9lsjsdZrVYqKiqwWCxomuY4l81mw2q1XlRD9TnPr0nTNP773/+yfv162rRpw8qVK1m6dOkVX6kqbi7pORNCCFGn6XQ6QkNDyc3NZcWKFTRp0oSjR48CMHHiRFJTU+nTpw8rVqxg9erVvPDCC786qd7Dw4PY2FiCg4MJCQlxbDC+c+dOZsyYQVZWFiEhITz22GOEhoZSUVHB0qVL+f7777FarXTr1o2BAwc6zme329m2bRuzZ89m2LBhfPnllwwdOpS2bdsClft7vv/++yQmJpKUlMRXX31FRkYGfn5+jB49msaNGwPw1VdfsWnTJpRS5Obm0q9fPwYMGMCiRYtYtGgRXl5e2Gw26tevj81mY+HChYwdO5YuXbpQUFDA999/T9++fW/Wj0FcBQlnQggh6rzQ0FAAvv32W0aNGsWXX34JVAaj83uTbDbbb54rOzubefPmodfrOXHiBHfccQdQuZDt+PHjCQgI4P3332fBggWMHj2a3bt38+233/Lkk08SERHB4cOHHeey2WysW7eOjIwMRowYQbNmzdi+fTs7duwgMjKS48ePk5SUxLFjx7jtttsoKChgyJAh1KtXj2+//ZYFCxaQmJiIm5sbdrudc+fO8fLLLzvWfcvOzmbRokU8+uij+Pv78/TTTwOVPYBFRUX4+vqyYcMG3N3dZd9NFyLhTAghRJ3n4+PjWA8sIiLius5lMBjw8/PD3d0dX19fsrOz0TQNDw8PZsyYwZkzZzh69CgJCQkA7Nu3j4YNG9KwYUP0ej3NmjVznCs3N5fDhw9jNptJSEhAKUWLFi34+OOPqVevHrNnz+axxx6joKCAevXqcebMGebNm8fx48fJysrCbDZfMASakpJCdHQ0Op3OsbWTXq8nISEBg8FAo0aNgP9dMKDT6fDy8qK0tFSW4HAhMudMCCFEnafT6ejbty+vvPIKHh4ejuNGo5GysjLHgq7nBx2DwYBSivLy8gvmYgUEBNClSxcGDBhA27ZtWbduHfn5+bz22ms0a9aMhx56iK5duzoe4+XlRUFBgWNOWUVFheO2gIAAHn74YaKjo5k+fTpWq5WEhAROnjzJiRMnCA4OZu3atdSvXx+j0cjbb7+Nv78/EyZMoH///hdN4DebzReELE9PT2w2G6WlpdhsNgoLCwHw9vbGz8+PnJwcGjVqxLlz54iKirrxDS+uiYQzIYQQtwQPDw/8/PwuCC9NmjRh9erVLF++nI0bN16wbIanpycJCQnMmTOHXbt2UVZWBlSuFbZt2zbWrFlDeno6cXFxGAwGPDw8KCwsZN++fezcudMR9Nq2bUtZWRkzZ85k5cqVLFy4kNLSUqAyAPr6+jJu3Dj27dvHqlWr0Ol0REVFcezYMfr378/3339Py5Yt0TQNd3d3ysvLOXToED///LPjPJeilCIsLIzIyEj++9//smDBAg4ePOi4bdCgQcyfP58FCxawatUqmW/mQmRYUwghRJ2WnJxMdHS042tPT0/uuOMOvLy86NatG2VlZZw6dYpevXoBF64R9vvf/56VK1eSkZFBdHQ0vr6+pKWlcezYMQwGA506daJNmzZ4enryyCOPsHHjRqxWK3feeacjnAUFBfH444+zZs0ajh07RkpKCiaTCR8fH9LS0ggKCsLf3597772XoqIiNE1j2LBhaJpGixYtGDp0KC1btsRoNDJmzBhWr17N0aNH6dKlCxaLxbH5evPmzbFYLBd87x4eHjzwwAOsXbuWiooKJkyYQEREBEop+vTpg8Fg4OTJk/Tt25cuXbrI0KaLkI3PhRBC1Fp//etfyc3NrfG9NW9l1XtrHjp0SMLcdfi1jc9lWFMIIYQQwoVIOBNCCCGEcCESzoQQQgghXIiEMyGEEEIIFyLhTAghhBDChchSGkIIIWq1Xbt28c033ziWlBA31/Hjxy9YD07ceBLOhBBC1Frt2rUjLy/PsfJ9bWGxWFi2bBnx8fHEx8c7u5yr4uXlxciRI51dRp0m4UwIIUSt1atXL9LS0pxdxlUrLi4mJyeHwYMHM3jwYGeXc9VkfbObS8KZEEKIWstoNGI0Gp1dxjVxc3PDbDbj5eXl7FKEi5ELAoQQQgghXIiEMyGEEEIIFyLhTAghhBDChUg4E0IIIYRwIRLOhBBCCCFciIQzIYQQQggXIuFMCCGEEMKFSDgTQgghhHAhEs6EEEIIIVyIhDMhhBBCCBci4UwIIYQQwoVIOBNCCCGEcCESzoQQQgghXIiEMyGEEEIIFyLhTAghhBDChUg4E0IIIYRwIRLOhBBCCCFciIQzIYQQQggXIuFMCCGEEMKFSDgTQgghhHAhEs6EEEIIIVyIhDMhhBBCCBci4UwIIYQQwoW4ObsAIYQQoq6z2+1s27aNw4cPA1BWVsbx48fZsGEDer0eAG9vb1q1akVgYKATKxWuQMKZEEIIUQO2bdvGE0884QhjJSUl7Nq1i2nTpmG322nfvj1Tp051cpXCFUg4E0IIIW4ypRRt2rTBw8ODkydPOo6XlpYCYDQaSUpKIjw83FklChcic86EEEKIm0wpRWhoKGlpaZe83dfXl759+6LTya9lIeFMCCGEqBEBAQG0b98ek8l00W3h4eG0a9fOCVUJVyThTAghhKgBer2eNm3akJCQcNHxe+65B3d3dydVJlyNhDMhhBCihiQnJ5OUlIRSynHMZDIxaNCgC46JW5uEMyGEEKKGeHl5kZaWhpeXF1A5F61z585ERUU5uTLhSiScCSGEEDWoX79+eHt7O74eMmQIZrPZiRUJVyPhTAghhKhB0dHRdOrUCZ1OR4MGDUhNTZWrNMUF5H+DEEIIUYOUUowaNQqlFO3btyc6Olrmm4kLyCK0QgghXEJ+fj7jxo1zdhk1oqioCLvdzsaNG5kwYcIt0XM2aNAgRowY4ewyagUJZ0IIIVxCWVkZq1evZujQoQQFBTm7nJvKZrPh6elJcHAwISEhzi7nprLb7axatYodO3ZIOLtCEs6EEEK4DD8/P3r16kVsbKyzS7npSktLMRgMuLnV7V/FNpuN06dPO7uMWqVu/48QQghRK90Kc7A8PDycXYJwURLOhBBCiFpC0zSg5sJr9fPV5HMKuVpTCCGEqDWWLFnCxIkTa+z59u/fz7/+9S9ycnJq7DmF9JwJIYSoRfLz89m5cyfh4eHExsaSk5PD7t27adq06TVdRJCRkYHdbicxMfEmVFtpz549HD16FABvb28SEhIIDAy8pp4oi8VCaWnpjS7xsux2O2VlZWiahqZp5ObmsnfvXqxWKw0bNpSdDW4S6TkTQghRa5w4cYL333+fTz75BE3TWLVqFS+++CL79++/pvMtXryY+fPn3+AqLzR37lwWLVrE6dOnWbNmDTNmzKjRgHWjFBQU8NVXX7FmzRp27NjB1KlTycrKcnZZdZL0nAkhhKhVQkNDycnJ4ciRIxQWFjq2QrJYLCxdupSFCxfi6enJ8OHDadGiBQCZmZl89dVXHD16lNTUVEaOHIm7u/tln6OsrIwFCxawbNky/P39GTZsGM2aNUPTNLZs2cKMGTMoLy9nyJAhdO3aFZ1Ox+nTp3n33XfJysqiTZs2jBw5EqPRCEDLli0ZMWIE27dv58MPP6SkpAS9Xs+aNWv47rvvsNvtDB48mLS0NJRSlJaWsnDhQlauXImfnx+/+93viImJcdRns9lYunQpGRkZDBgwgHnz5jFy5Ej8/f0BOHDgAEuWLGHUqFEcOHCAr7/+mrNnz9KzZ0+GDBmCXq/HYrHw1ltvoWkamZmZlJeX8+c//5nIyEhmzJhBeno6ISEhjnlnOTk57N+/n0ceeYSQkBAmTpzIli1buOOOO27Gj/mWJuFMCCFEraLX6+nUqRMzZswgPDyc0NBQlFJkZmayYsUKxowZg6+vL8eOHaO0tBRN05g0aRJdunRh3LhxfPzxx8yaNYtRo0Zd8vyapvHjjz/y008/8dRTT7Fv3z5mzJhBSEgI7u7uLFiwgG7dupGamsqWLVsoLCzE19eXOXPmYDKZePnll9myZQunT58mMjISgNOnT7N3717S09MJCgrCZDJx7tw5KioqeOaZZzh9+jTTpk0jLCyMxMREFi9ezPr163n88cfRNI0jR444wllFRQULFy5k69atjBkzBoPBQGFhIVlZWZSXl2MymcjMzMRut2Oz2Th27Bjjxo3DaDQyceJEwsLC6Ny5M5qmsXfvXiIiInjuuecICgrCzc2Nr7/+muPHj/N///d/LFy4kA0bNqBpGjk5OZjNZgwGA6WlpURERHDy5Mka+7nfSiScCSGEqHVat27N999/T3JyMp6engD4+PgQHR3Nvn37CA4OJiEhAXd3d44cOcLevXtp1qwZy5cvR9M0fv75Z+x2+yXPbbfbyczMpGXLlkRFRWEymVi5ciV5eXn4+voSFxdHVlYW27dvJz4+3tFz16BBA86cOUN6evpFi8tu376d4uJiioqKqF+/PjqdDj8/P8LDw1m/fj0FBQWUl5dz+vRpEhISSE9Pp3v37sTFxTnOXW3//v3k5OQwZMgQwsPDKS0txc/Pj6ysLKZNm0Z0dDTe3t5ERUXh4eFBYmIiO3fupKSkBJ1Ox5EjR+jcuTNQua5ct27dHCGyrKyMQ4cO0alTJ8LCwkhNTeXw4cNAZW+dTqcjIyMDpRQ6ne6ybSiuj8w5E0IIUesEBATw2GOP0b59e8fE+pCQEIYNG0b9+vXJzc3lm2++ITc3F6UUBoMBPz8/vLy8aNq0KXffffdlz119vvOXkag+7uHhQf/+/WnZsiV2u53PP/+cI0eOANCpUyf69u2L2Wxm7ty5bN682fHYnj178vzzz/PII4+wY8cOMjMz2bdvH9988w3l5eWONc9sNpvjuX75/NUCAwPp27cvmzZtIjs7G7PZTHBwMNu2bcPf35+TJ0+SnZ1NREQEhYWFTJkyhby8PDw8PNDr9VitVse59Hr9RcO7l3pupRR+fn6UlZXRrFkzunTpQkFBAT4+Ppf/IYlrJuFMCCFErePm5kbr1q0JDg52HDt69Cjp6elEREQQERFBVlYWZWVlBAUF0ahRI3Q6HSkpKXh7e3PmzBnHfpYWi4WioiKKioooLi5G0zTi4+PZtm0bhw8fZsuWLY5wUlhYyMqVK/H29qZBgwZkZWVRVFQEwMKFCykrKyMhIYGSkhLOnDnjqK2iooKioiJOnjxJUVER7u7unD17FpvNRtu2bQkJCXHcXylFmzZtWL58Ofv372f//v2sWrXKca7w8HD69u1LeHg4M2bMwGazER4ezoYNG2jevDlhYWEcPXqUoKAgiouLOX36NCkpKcTFxZGdnX3J4FXNZDKRkJDAmjVrOHbsGBs3biQ7OxuA4OBgDAYDmzZtcgTM5OTkG/yTFSDDmkIIIWoRDw8P4uLiMBgMjmPx8fH4+PgQHh5ORkYGkyZNwmg0MmTIECIjI1FK8fDDDzNr1iyWL19O48aNufPOOwGIiIhg4cKFPPLIIwAYjUaeffZZOnfuTElJCe+++y7+/v6MGDGC0NBQ7HY79erV47PPPqO4uJghQ4Y4Akrbtm354osvOHbsGI0bN6ZHjx4AREdHs2zZMjZs2EBgYCD3338/0dHR+Pv7s2/fPv75z38SHx/Pbbfdhp+fH0opevXqhaZpTJkyBT8/P8aMGQNU9prFxsZiNpu5++67effdd8nMzCQmJoaWLVvSsmVLQkND0ev1BAUFYTab6du3L5MnTyYsLIw+ffo4lhzR6XTExcU5hmWhMqj16dOH0tJS/vWvfxEbG0v79u0xGo34+flx3333MXPmTEpLS7n//vtp0qTJzf+h34LU5bpNXUVqaqqWnp7u7DKEEELcZNnZ2XTr1o3XXnvNMddKXD1N01xqNX+r1crUqVPx8/Pj1VdfdXY5LiMpKalg7969vpe6TYY1hRBC1Bnndzi4eufDzeJKwUxcGwlnQggh6ozzg4mEFFFbSTgTQgghhHAhEs6EEEIIIVyIXK0phBDCpVSvbC/qBrvdfsvO/7tWEs6EEEK4BJ1OR35+Pm+//bZj1f+6StM0ysvLcXNzw82tbv8qrt5+aujQoc4updao2/8jhBBC1Bqenp48//zzzi6jRlRUVDBz5kyaNWtG8+bNnV1OjWjVqpWzS6g1JJwJIYRwCR4eHkyYMMHZZdSIoqIi1q5dS48ePRg2bJizyxEuRi4IEEIIIYRwIRLOhBBCCCFciIQzIYQQQggXIuFMCCGEEMKFSDgTQgghhHAhEs6EEEIIIVyIhDMhhBBCCBci4UwIIYQQwoVIOBNCCCGEcCESzoQQQgghXIiEMyGEEEIIFyLhTAghhBDChUg4E0IIIYRwIRLOhBBCCCFciIQzIYQQQggXIuFMCCGEEMKFSDgTQgghhHAhEs6EEEIIIVyIhDMhhBBCCBci4UwIIYQQwoVIOBNCCCGEcCESzoQQQgghXIiEMyGEEEIIF+Lm7AKEEEKIuk7TNKxWK3a7HYDy8nLsdjsWi4Xy8nIAlFK4ubmh00m/ya1OwpkQQghxk2maxqpVq1i1ahUAFRUV7Nmzh2+//ZZ9+/YBEBERwaBBgwgNDXVmqcIFSDgTQgghasC5c+f429/+hqZpjmO7d+92fH7HHXcwbNgwZ5QmXIz0nQohhBA3mVKKZs2akZycjFLqots9PT3p0qUL/v7+TqhOuBoJZ0IIIcRNppQiPDycbt26XfJ2Hx8f+vfvf8ngJm49Es6EEEKIGuDl5UW7du0ICAi44LhSiubNm5OYmOikyoSrkXAmhBBC1AClFC1atKBBgwYX3XbPPfeg1+udUJVwRRLOhBBCiBqSkJBAs2bNLghiAQEB9O7d24lVCVcj4UwIIYSoIW5ubvTv3x9fX1+gsjftzjvvxMfHx8mVCVci4UwIIYSoQZ07dyY4OBgAo9FIr169MBqNTq5KuBIJZ0IIIUQN8vPzo2/fvri5udG8eXMaN24sV2mKC8gitEIIIeqciooKTp065ewyLqtLly785z//ISkpCU3TOHr0qLNLuiQPDw9HL5+oORLOhBBC1DmZmZk8MGYU/sHuuLm53iCR1WIjKNSbHXs38sz/PeTsci6iaVBcWEG7lDReffVVZ5dzy5FwJoQQos4pKSmhoOIoA+9NJTDUw9nlXMRu00jNbINvgBmfALOzy7mIpsGqeQc5fPiws0u5JUk4E0IIUSd5+5tIbhNKWLTrXQmpaRrNOkag0yl0etebb6ZpGpm7z3DkpLMruTVJOBNCCCFqmFIKN4PrhTLhGn5zIF4p9bFSKkcptfO8YwFKqaVKqYyqf/3Pu+0vSqkDSql9Sqne5x1PUUrtqLptkpJLU4QQQoiL2G12Pnt9Ex//fSP7tuag2TVnlyRq2JXMkpwG3P6LY88AyzRNawgsq/oapVRjYDiQXPWY95RS1csgTwbGAQ2rPn55TiGEEMLFaWja/z5uCqVo0DSIYxl57E7P5mY9jXBdvxnONE1bBZz9xeGBwKdVn38KDDrv+AxN08o1TcsEDgBtlFLhgI+maT9plf+bPzvvMUIIIUQtoTibXcLE8cs4mVlwU55Bp1O06RlNTILfTTm/cH3XOucsVNO0UwCapp1SSoVUHY8E1p93v+NVxyxVn//y+CUppcZR2ctGdHT0NZYohBBC/DpLhY29m3PIP1OKb5A7Cc2DMZr1ZB8rpKTQQkySP2UlVo4fyKNevB8lxRbWzs8kc89Z1i06TESsD4ktQwgK96Si3Mb+bafJyy3Fx99Eo5RQDKbKwaP9205jqbBRmFdOaZGFpFYhhMV4X/Xis1aLnf3bTnM2uxgffzPxzYNx93RDs8Pxg3kcO5CH0aynQZMg/IPdAcjLLSNj+2ks5TZiEv2JbOAri966uBt9QcClftrarxy/JE3TpgJTAVJTU6VDVwghxE2x+rtMVszOoEHTIA7vOUun/rH0uLMhW348wdF953jg2TacPlHEt1N2MOJPLXEzVAa30mILOSeK0LvpiEkMAGDpzH1sXXWC6AR/ju4/x+mTxfS4syEAcz7YwaFdZ2jdIxqzhxvR19grtmHpURZ9voeGzSuHPVt0iaTPyCRyjhUxc9I2fAPNmD3cKCmw0P72GAAWfb6HYxnniIzz5WhGHv0fbIyXr+mGtJ+4Oa41nGUrpcKres3CgZyq48eBeufdLwo4WXU86hLHhRBCCKew2zXmfLCDO//QjPa96/Pj3IMsmb6P7kMbXvYxkXG+DB7flCP7z9H/wcZEN6y8Hq681Mo372znT292IbFVCLs3ZTPt1U10Hxpf2UulFEmtQrnn8VaYPdyuqedKs2ss+Gw3XfrH0WNYQzYsOcKcD3Zy2/BE8s+Wce50KYPHNSW8vg+Wcht6g47yUisnDuWT2CqU3iMSKSoox+QuCzW4umv9Cc0D7gdeq/p37nnHpyul3gQiqJz4v1HTNJtSqlAp1Q7YANwHvH1dlQshhBDXobzEQsHZMqIT/DGY9ITX9+FMVgkASoGGRuUFAJVB7ny/jFb5Z8rIPl7EPx5egV6vQ9M0/IM9sFTYMJrc0OkgNjkAd0/DFdWmlKOI/9VbZiU/t7SyXqOekHre5J8pQ7NrNGgSyB33NeLrd7dTVmyhU79Yug5qgLuHgaETmrHg0928MnYp9ZMCGPGnlhiM+ss+t3C+3wxnSqmvgK5AkFLqOPB/VIayr5VSY4CjwDAATdN2KaW+BnYDVuAhTdNsVaeaQOWVn+7AoqoPIYQQwinMHgZC63mzc30WofW8OfBzLhFxlQvWunsayMsto6zESs6xQs5mlzge5+amQ9OgKK8cTdNQSuEX7E5sI3/G/l87GrcOoyivnNyTRRhN//s1q7uK3jKdToeHt5G83FKsFhsGnRsmdzfCor3ZueEUsY0DyNx9luBIL3Q6RXF+BZFxvvzpzS7s+OkU86ftpkm7cEIiPbFZ7dz3dCo2m8bzwxfRvFMEbXrKfG5X9pvhTNO0EZe5qcdl7v934O+XOJ4ONLmq6oQQQoibROkUdz/Sgm/e287WVccpL7UycGwTlIL4ZkGsnHOQfz++Ck8fA+fnKi8/E4ktQ/jijc0EhnnS74HGJLYM4b6nUln0+V4WfbYHs6eBNj2jSUq5ttp0ekVym1Bmvr2Nib9fTp97k2jTK5rB45sy461t7NuSQ0WZjT4jk9AbdFRU2Fj//WEy95ylvMxGUkoIfoFmNDsc3HWGOR/spKzEQmQDX2IbBdyYBhQ3jbpp67TcIKmpqVp6erqzyxBCCFGLbN26lQnPDOK5D3r+6vZNNpuds1kllJdaMbm74R/igd5NodkhL7eU4sIKzB5u6JTCJ9DsGA4sLqggL7cUTdMICPXAw8uIzWrnXE4pZaUWDEY9/iHujp6zszklGE36q5qIb7XYOHe6lLJiC35B7nj7m7HbNM7mlFBWbMFo1uMf4oGb4X89eQXnytHpFX6BZty9KodQy0qs5OWWYrPa8fI14Rto/s05b5qm8e2UHRxZFcr06dOvuGZx5ZKSkgr27t3re6nbZFagEEKIW5ZeryM40uui40oPAaEeBFxm03RPHyOePsYLz+WmIyjC85L3Dwi58DxWi51dG7Muup/BqCMuORCzhwE3g57giAtr0+kVQeEXP4dS4HOZTdTdPQ1XPNdNuAYJZ0IIIUQNs1psrJp38KLjnt5GwqJ9MHs4I0xdbuUrUdMknAkhhBDX4HqijFKK8Bgf2t9en8i4ypGtE4fy2b72JG6GK9lZ8Wao/m5ce7rTrcBZ/wOEEEIIJ7u+EHI9fUx70rPZk55NSNT/hi0DQj3YtyWHjJ9zb96+nVdEes+cTcKZEEKIW5RzQoimwfxpu+lwR+wFvWTungaSUkJYuyATu016r25lEs6EEEKIGlRcUM7eLTk0bRd+0VWTcU2C2L0pC5tTwpkEQlch4UwIIYS4oX495OSeKsZmsRMYfvGVoIGhHhTlVVBSWHGzivsVMpzpKiScCSGEEDfUb6whZtdAgbrE/aq3jXLxJUjFTSbhTAghhKhBgeGeKJ3ibE7JRbedyynF09uIp7esS3Yrk3AmhBBC1CBvPzMJzYMvuQjtwV1nSGwVgt5Nfj3fyuSnL4QQQtQgpaDPqCTWLsjEZrU7jpeXWtmTnk2HO2LR6WX+161MwpkQQghRw5p3jCAq3o8Th/Idx7KPFVIv3o9GKSFOrEy4AtkhQAghhKhhZg8D9z2VesGx6AR/ohP8nVSRcCXScyaEEEII4UIknAkhhBBCuBAJZ0IIIcQVkwXIxM0n4UwIIYS4YnIVpbj55IIAIYQQdY5SiuMH8/nq31vx9DE5u5xaSGPP5hyifUKdXcgtScKZEEKIOicwMJC+Pe5GV65Quc6upnZqUk8jNaW1s8u4JUk4E0IIUefUq1ePKVOmOLsMIa6JzDkTQgghhHAhEs6EEEIIIVyI0jTXvixYKVUI7HN2HbVUECCzLa6etNu1k7a7dtJ210ba7dpJ2127G9F2MZqmBV/qhtow52yfpmmpv3038UtKqXRpu6sn7XbtpO2unbTdtZF2u3bSdtfuZredDGsKIYQQQrgQCWdCCCGEEC6kNoSzqc4uoBaTtrs20m7XTtru2knbXRtpt2snbXftbmrbufwFAUIIIYQQt5La0HMmhBBCCHHLcOlwppS6XSm1Tyl1QCn1jLPrcSVKqY+VUjlKqZ3nHQtQSi1VSmVU/et/3m1/qWrHfUqp3s6p2jUopeoppVYopfYopXYppR6tOi7t9yuUUmal1Eal1Paqdnup6ri02xVQSumVUluVUvOrvpZ2uwJKqcNKqR1KqW1KqfSqY9J2V0Ap5aeUmqWU2lv1ftde2u63KaUSq/6/VX8UKKUeq9G20zTNJT8APXAQiAOMwHagsbPrcpUPoAvQCth53rF/AM9Uff4M8HrV542r2s8ExFa1q97Z34MT2y4caFX1uTewv6qNpP1+vd0U4FX1uQHYALSTdrvi9nscmA7Mr/pa2u3K2u0wEPSLY9J2V9Z2nwJjqz43An7SdlfdhnogC4ipybZz5Z6zNsABTdMOaZpWAcwABjq5Jpehadoq4OwvDg+k8sVI1b+Dzjs+Q9O0ck3TMoEDVLbvLUnTtFOapm2p+rwQ2ANEIu33q7RKRVVfGqo+NKTdfpNSKgroC3x43mFpt2snbfcblFI+VP4R/xGApmkVmqblIW13tXoABzVNO0INtp0rh7NI4Nh5Xx+vOiYuL1TTtFNQGUCAkKrj0paXoZSqD7SkshdI2u83VA3NbQNygKWapkm7XZn/AE8B9vOOSbtdGQ1YopTarJQaV3VM2u63xQGngU+qhtM/VEp5Im13tYYDX1V9XmNt58rhTF3imFxaem2kLS9BKeUFzAYe0zSt4Nfueoljt2T7aZpm0zStBRAFtFFKNfmVu0u7AUqpfkCOpmmbr/Qhlzh2y7XbeTpqmtYK6AM8pJTq8iv3lbb7Hzcqp75M1jStJVBM5VDc5Ujb/YJSyggMAL75rbte4th1tZ0rh7PjQL3zvo4CTjqpltoiWykVDlD1b07VcWnLX1BKGagMZl9qmvbfqsPSfleoanhkJXA70m6/pSMwQCl1mMrpGd2VUl8g7XZFNE07WfVvDvAtlcNF0na/7ThwvKp3G2AWlWFN2u7K9QG2aJqWXfV1jbWdK4ezTUBDpVRsVXodDsxzck2ubh5wf9Xn9wNzzzs+XCllUkrFAg2BjU6ozyUopRSV8zD2aJr25nk3Sfv9CqVUsFLKr+pzd6AnsBdpt1+ladpfNE2L0jStPpXvY8s1TRuJtNtvUkp5KqW8qz8HbgN2Im33mzRNywKOKaUSqw71AHYjbXc1RvC/IU2oybZz9pUQv3GVxB1UXkl3EHjO2fW40kfVf5hTgIXK1D4GCASWARlV/wacd//nqtpxH9DH2fU7ue06Udnl/DOwrerjDmm/32y3ZsDWqnbbCbxQdVza7crbsCv/u1pT2u232yuOyqvgtgO7qn8PSNtdcfu1ANKrXrNzAH9puytuOw/gDOB73rEaazvZIUAIIYQQwoW48rCmEEIIIcQtR8KZEEIIIYQLkXAmhBBCCOFCJJwJIYQQQrgQCWdCCCGEEC5EwpkQQgghhAuRcCaEEEII4UIknAkhhBBCuJD/BzCU4DZ6PtbKAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n140393102828544\n\nouter_loss\n ()\n\n\n\n140393111546128\n\nMseLossBackward0\n\n\n\n140393111546128->140393102828544\n\n\n\n\n\n140393111546032\n\nMulBackward0\n\n\n\n140393111546032->140393111546128\n\n\n\n\n\n140396237940288\n\nAddBackward0\n step1.a\n ()\n\n\n\n140396237940288->140393111546032\n\n\n\n\n\n140393111546464\n\nAccumulateGrad\n\n\n\n140393111546464->140396237940288\n\n\n\n\n\n140393102725760\n\nMulBackward0\n\n\n\n140393111546464->140393102725760\n\n\n\n\n\n140393102827744\n\nstep0.a\n ()\n\n\n\n140393102827744->140393111546464\n\n\n\n\n\n140393102725232\n\nMulBackward0\n\n\n\n140393102725232->140396237940288\n\n\n\n\n\n140393112318976\n\nUpdatesOpBackward\n\n\n\n140393112318976->140393102725232\n\n\n\n\n\n140396647894368\n\nMuOpBackward\n\n\n\n140396647894368->140393112318976\n\n\n\n\n\n140393102725472\n\nMulBackward0\n\n\n\n140393102725472->140396647894368\n\n\n\n\n\n140393112318736\n\nNuOpBackward\n\n\n\n140393102725472->140393112318736\n\n\n\n\n\n140393102725616\n\nMseLossBackwardBackward0\n\n\n\n140393102725616->140393102725472\n\n\n\n\n\n140393102725760->140393102725616\n\n\n\n\n\n140393102725568\n\nPowBackward0\n\n\n\n140393102725568->140393102725472\n\n\n\n\n\n140393102725568->140393102725760\n\n\n\n\n\n140393102725904\n\nAccumulateGrad\n\n\n\n140393102725904->140393102725568\n\n\n\n\n\n140393111543968\n\nPowBackward0\n\n\n\n140393102725904->140393111543968\n\n\n\n\n\n140393111485872\n\nx\n ()\n\n\n\n140393111485872->140393102725904\n\n\n\n\n\n140393102725328\n\nAccumulateGrad\n\n\n\n140393102725328->140396647894368\n\n\n\n\n\n140393111534224\n\n ()\n\n\n\n140393111534224->140396647894368\n\n\n\n\n\n140393111534224->140393102725328\n\n\n\n\n\n140393111531904\n\n ()\n\n\n\n140393111531904->140396647894368\n\n\n\n\n\n140393111531904->140393112318736\n\n\n\n\n\n140393112318736->140393112318976\n\n\n\n\n\n140393102725712\n\nAccumulateGrad\n\n\n\n140393102725712->140393112318736\n\n\n\n\n\n140393102827824\n\n ()\n\n\n\n140393102827824->140393112318736\n\n\n\n\n\n140393102827824->140393102725712\n\n\n\n\n\n140393102828784\n\n ()\n\n\n\n140393102828784->140393112318976\n\n\n\n\n\n140393102828144\n\n ()\n\n\n\n140393102828144->140393112318976\n\n\n\n\n\n140393102828224\n\n ()\n\n\n\n140393102828224->140393112318976\n\n\n\n\n\n140393111543968->140393111546032\n\n\n\n\n\n" }, + "metadata": {}, "output_type": "display_data" } ], "source": [ - "net = Net().cuda()\n", - "x = torch.tensor(2., requires_grad=True, device=torch.device(\"cuda\"))\n", - "y = torch.tensor(1., device=torch.device(\"cuda\"))\n", + "net = Net().to(device='cuda')\n", + "x = nn.Parameter(torch.tensor(2., device=torch.device('cuda')), requires_grad=True)\n", + "y = torch.tensor(1., device=torch.device('cuda'))\n", "\n", - "optim = TorchOpt.MetaAdam(net, lr=1., use_accelerated_op=True)\n", + "optim = torchopt.MetaAdam(net, lr=1., moment_requires_grad=True, use_accelerated_op=True)\n", "\n", + "net_state_0 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step0.')\n", "inner_loss = F.mse_loss(net(x), y)\n", - "net_state_0 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step0.')\n", "optim.step(inner_loss)\n", - "net_state_1 = TorchOpt.extract_state_dict(\n", - " net, enable_visual=True, visual_prefix='step1.')\n", + "net_state_1 = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step1.')\n", + "\n", "outer_loss = F.mse_loss(net(x), y)\n", - "TorchOpt.visual.make_dot(outer_loss, params=[net_state_0, net_state_1,{'x': x, 'outer_loss': outer_loss}]).render(\"graph\", format=\"png\")\n", - "plt.figure(figsize=(15,15))\n", - "plt.imshow(imgplt.imread('graph.png'))" + "display(torchopt.visual.make_dot(outer_loss, params=[net_state_0, net_state_1, {'x': x, 'outer_loss': outer_loss}]))" ] } ], "metadata": { - "interpreter": { - "hash": "238ad0feaa04228775e5e27229169b0e3e76c0e018d5a6d65c4906ccad5c5a9e" - }, "kernelspec": { - "display_name": "OpTorch", + "display_name": "Python 3.8.13 ('torchopt')", "language": "python", - "name": "optorch" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -586,7 +564,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.8.13" + }, + "vscode": { + "interpreter": { + "hash": "2a8cc1ff2cbc47027bf9993941710d9ab9175f14080903d9c7c432ee63d681da" + } } }, "nbformat": 4, diff --git a/tutorials/4_Stop_Gradient.ipynb b/tutorials/4_Stop_Gradient.ipynb old mode 100755 new mode 100644 index 4c13f420..604196ca --- a/tutorials/4_Stop_Gradient.ipynb +++ b/tutorials/4_Stop_Gradient.ipynb @@ -4,37 +4,46 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# TorchOpt.stop_gradient in meta learning" + "# `torchopt.stop_gradient` in Meta-Learning" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In this tutoial, we will illustrate the usage of TorchOpt.stop_gradient with a meta-learning example. We use TorchOpt.visual to help us visualize what is going on in automatic differentiation. Firstly, we define a simple network and the objective function for inner, outer optimization." + "[](https://colab.research.google.com/drive/1jp_oPHIG6aaQMYGNxG72FSuWjABk1DHo?usp=sharing)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, we will illustrate the usage of `torchopt.stop_gradient` with a meta-learning example. We use `torchopt.visual` to help us visualize what is going on in automatic differentiation. Firstly, we define a simple network and the objective function for inner- and outer- optimization." ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ + "from IPython.display import display\n", + "\n", "import torch\n", - "from torch import nn\n", - "from torch.nn import functional as F\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "\n", + "import torchopt\n", + "\n", "\n", "class Net(nn.Module):\n", - " def __init__(self):\n", + " def __init__(self, dim):\n", " super().__init__()\n", - " self.fc = nn.Linear(1, 1)\n", + " self.fc = nn.Linear(dim, 1, bias=True)\n", " \n", " def forward(self, x):\n", " return self.fc(x)\n", "\n", - "def fn(x):\n", - " return 2 * x + 1\n", - "\n", "loss_fn = F.mse_loss" ] }, @@ -42,40 +51,39 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We define the input x and output y. y will be served as the regression target in the following code." + "We define the input `x` and output `y`. `y` will be served as the regression target in the following code." ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "x = torch.rand(5, 1)\n", - "y = fn(x)\n", - "net = Net()" + "batch_size = 64\n", + "dim = 16\n", + "\n", + "x = torch.randn((batch_size, dim))\n", + "y = torch.zeros((batch_size, 1))\n", + "net = Net(dim)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let us define the meta-parameter, MetaSGD as the inner-loop optimizer, Adam as the outer-loop optimizer. " + "Let us define the meta-parameter, we use `MetaSGD` as the inner-loop optimizer and `Adam` as the outer-loop optimizer. " ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ - "import TorchOpt\n", - "from TorchOpt import MetaSGD\n", - "from matplotlib import image as imgplt\n", - "from matplotlib import pyplot as plt\n", + "meta_parameter = nn.Parameter(torch.tensor(1.), requires_grad=True)\n", "\n", - "meta_parameter = torch.tensor([1.], requires_grad=True)\n", - "optim = MetaSGD(net, lr=1e-1)\n", + "optim = torchopt.MetaSGD(net, lr=1e-1)\n", "meta_optim = torch.optim.Adam([meta_parameter], lr=1e-1)" ] }, @@ -88,63 +96,55 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "inner loss: 4.4117\n" + "inner loss: 0.5540\n", + "\n" ] }, { "data": { - "text/plain": [ - "" - ] + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n139978828415600\n\ninner_loss\n ()\n\n\n\n139978603488640\n\nMseLossBackward0\n\n\n\n139978603488640->139978828415600\n\n\n\n\n\n139978603489744\n\nAddmmBackward0\n\n\n\n139978603489744->139978603488640\n\n\n\n\n\n139978603490800\n\nAccumulateGrad\n\n\n\n139978603490800->139978603489744\n\n\n\n\n\n139975938634512\n\nstep0.fc.bias\n (1)\n\n\n\n139975938634512->139978603490800\n\n\n\n\n\n139978603490224\n\nTBackward0\n\n\n\n139978603490224->139978603489744\n\n\n\n\n\n139978603490368\n\nAccumulateGrad\n\n\n\n139978603490368->139978603490224\n\n\n\n\n\n139975938634432\n\nstep0.fc.weight\n (1, 16)\n\n\n\n139975938634432->139978603490368\n\n\n\n\n\n" }, - "execution_count": 56, "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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vpusrCzKysr4+c9/TmRkJJdccglDhgxh7NixbNy4kY0bN/LMM88wbdo0brnlFuDk0VXjEdratWtZuHAhdXV1PP/881gsFj788EM2bdpERUUFd955J7t378bpdJKent7uK1WtX7+eiRMnct9995GdnY3FYkGn06EoClu3bmXz5s3MmTOHhx56iPLycl588UWWLl2KqqpceumlAPz973/H7XZz5ZVXMnv2bPbv3899993HxIkTO/097YvKy8tZtWoVVqtVO2gKBAJ88sknjBw5khtvvJGIiAgOHTrEI488QkpKCpdeeilRUVE4HA527NjBvffey3333cekSZPQ6/UcP36crVu3NksGfr+f6upq7rvvPlJTU/nWt75FdnY2FRUVjBw5kp07dzJixIgmFbGqquzatYsBAwYQFxdHaWkpX375JdOnT+fXv/41tbW1LFy4kC+//BKz2awdlDWy2+1s3LiRhx56iLFjxxIaGqoNGfX7/fz2t78lLi6OBx98kAEDBrB9+3Yeeugh5syZA5w8u6iqqqKhoYGsrCztNxEdHU10dLSWkHqrPpkMysrK2Lt3LzqdjuHDhxMZGcnKlSs5evQo5eXl2uX1vv76a0wmExdeeCFTp04lJCQEq9VKUlKSdsTjdrtZsmQJeXl5XHLJJQwePBiDwUBERASpqakd7mSLjY0lJiaGqKgo0tPTiY+PJyYmpslicp3dnj5v3jxtyntje/KUKVMoLi6moqJCaxvW6/VaBazT6Zqtx+NyuTh48CCFhYXcf//9JCUlodPpmDZtGm63m40bNwInE14gENDeT7vdzmuvvcbSpUsJBAJ873vf44orrmjSBJCXl8eUKVPIzc3VmhQaT/GHDx9ORkYGRqORkJAQUlJSGDt2LDt37mT//v3MnTuXmpoa9u/fz8yZM5k6dSoxMTGMGTOGsWPHSv9EGzZs2MDOnTu15smQkBBuvvlmbRiuTqfjxhtvxGg0as02gwYNYsqUKezevZuLL76YqKgoysrKWLVqFRMnTmTGjBlERESgKAoxMTHamWEjj8fDkSNHePjhh4mMjOQ73/kOKSkp6HQ6zGYz2dnZbN++HY/HwwcffMD+/fvJzMzkmmuuYd++fUyYMIGYmBhiY2O54YYbMJlM2nctNzcXu93O7t27myWDsLAwLrroIiZPntzkYKMxMe3evZtHHnmEESNGEB4ejsFgYMyYMdr31Ov14vF4tJn9f/3rX7Xfsslk0pqEe6s+mQxKSko4dOgQAwYMIDQ0lJSUFCIjI6mrq2Pfvn1aMiguLsZisZCYmKhNgW+sMBs1rqCanJxMZGSkVvnr9fpzGm1hMpkwGAwYDAYsFgshISHo9XqMRiOBQKDJJLLOUlVVxfbt2zly5Ah2ux2Px8P+/fuJiorS+hDao66ujtraWo4fP85bb73FokWLgJNHTEePHsXv9+PxeAgNDUWn0+H1evF6vZjNZq29+v3336e2trZZO35jUmypg9/pdHLo0CE2bNhAZWUlHo+H48eP4/P5tEUMG9u0IyIiiI2NRa/XY7FYtLM70bK0tDTGjh1LZmYmDQ0N7Nq1i927d2Oz2ZgxYwY6nY6ysjLWrVtHWVkZTqeThoYGCgoKSEhI0N5bl8tFSUkJ6enphIWFaWfLBoOh2Wdqt9s5fvw4RqORiooKbUJg4wHLkCFD+Pjjj3E6nZSXl7N//36cTiezZ8+mqqqKxMREIiIicLvdFBUV8c0332jfi7KyMux2e4sXcjIajaSmpjZb96ixT8TlcpGYmIjNZsNoNGK1WklISND20+v1Wh+bx+Phoosuwmq1UltbS3FxcbcvZtjZenfpWxAIBCgtLWXfvn34fD6WLFmCoihUVFTgdDrZvXs3F154IdD+6eHtWcDq9KOMRl6vF5/P1yS5NB5lNB75nn779DLpdDoCgYB2u/EL2JLGttHT9298jN/vZ/ny5RQWFmrXN2istH0+X7OLp5x+RN7S+3D68zceCen1ejIyMoiPjwdOnv0YjUYteSQkJDB69GgMBgNLlixp8bktFkuLzW1+v5+CggKWL19OZWWl9r54PJ4mM7Aby3Vm+XvryI7ukpiYyOTJk5k8eTINDQ2Eh4ezcOFCTCYTkyZNwmKxsHjxYsrKyrTvitvt1hL96d/P9i70pigK4eHhTJ48mU8++YTNmzdjsVhISEjAZDKRnp5OVVUVR48eJRAIYDabqa2t5ciRI1gsFu1I/MiRI3z22WeUl5c3+V54vd4WDwDaWveoPd+fxjMQvV5PdXU148ePx2AwsHXrVu1ApDc7azJQFCUNeA1IAFTgOVVV/6EoSjTwDjAQOAJcr6pqtXLy3fsHcCngBL6jqurmril+cx6Ph+LiYg4cOIDVauWJJ54ATlbMJpOJ3bt3ayMiEhMTOXjwIGVlZTQ0NGA2m7WK0mg0YjKZ0Ov12oikxqNqvV6vXTymcQSNwWDAaDTicDgIBAIEAgFqa2txOBzaqIv2aPxBhYSE4HA48Pl8+Hw+GhoaqKqqara/TqfDZDKh0+majKJp/BJ7PB4++ugjxowZwzXXXENOTg6KovDUU09x9OjRZs/X+NoaK9rTj3bCwsIIDw8nLS2Nn/zkJ6Snp2ujMzwej/bDTUxMJDo6mtLSUg4fPkxUVBQGg6HNkT2tJSGv18v27dtZsWIFP/rRj7jkkkvQ6XR8+umnbNq0SXu+yMhILBYLdrudqqoqzGYzHo+H8vJyuY5uO1ksFnJycli0aBEVFRXU1NQQGRnJG2+8wV133cXUqVNJSUmhrq6OF198kcLCQu2xISEhxMfHc+zYMZxOJ3q9HkVRtNFhoaGh2ucbFRVFQkICN998M+Xl5SxatIioqChtVE9CQgJGo5G1a9cSGRnJkCFDKC8vZ/Xq1QwcOJCQkBACgQCHDx/m448/5uc//znTpk3DZDKxZs0ali9fTm1tbbPX11qi0uv12ll/WVkZKSkphIeH09DQQFlZmXaQYzabiYiIIDw8nIMHD5KXl6eNhqqurmbAgAFd8Kl0n/aMT/QBP1NVNRuYCNylKEo28Atgmaqqg4Flp24DzAUGn/p3O/CvTi91GwoKCigsLCQ5OZm3336bJUuW8Nlnn/HMM89wySWXUFBQoI2UmDp1Kg0NDaxcuZJVq1ZRX19PYWEhn376KQcOHABOHg3MnTuXjRs38tVXX7Fv3z5qamo4ePAg7733Hm63G4Dw8HBsNhtr1qzBbrdz6NAhNm3axKFDhzr8GvR6Penp6WzYsIHi4mKKi4vZvXs369ata1aZ6vV6wsPDiYuLY/PmzZSXlzc5Q2g8SmpsU/V6vezYsYOlS5e2eLGbmJgY3G43J06c4Pjx49pzNQ7Dy8zMJC0tjX/+858UFxdr7bMLFy7kjTfe0Iaizp49m8LCQj788EP27duHy+Xi6NGjHb5yV2NSUhRFa4fet28fX3/9NXv27AFO/sijoqIYPnw4+/fvZ8WKFdTU1LBlyxY2btyI0+ns8GfQnzSOIHM4HKxYsYKqqirCw8OJjo7G6/USCAQICwvDZDJRW1vL1q1bWbp0aZOj77i4OKZMmcI333zDsmXLKCkpoaysjB07dvDRRx81iafT6bT5DnfddReqqrJs2TLWrl2r3Z+dnc2XX34JwPjx4xk8eDDvvfcew4cP1/o3Gg8uIiIi0Ol0HDp0iDVr1mh9V+2l1+uJiooiJyeHzz//nN27d1NcXMyWLVvYtGmTdkakKApxcXGMGzeOBQsWcPz4cQ4ePMjGjRs5cuQIF1988Xl8CsF31jMDVVWLgeJTf9cpirIHSAGuBKad2u1VYDnwwKntr6kna6O1iqJEKoqSdOp5uty2bduor68nNze3yRF5ZmYmQ4cOZe3atSxfvpzrr7+erKws7rjjDr755hteeOEFHnroIdLS0vjud7+rtRUaDAZGjBjBn/70Jz788EP+85//4HK5SE9P595779WOnLOzs1FVlX/84x9ceuml5OTkEB8ff04TdywWC7feeiu//e1v+dnPfkZUVBQDBgxg6tSp1NTUNNvfaDTyy1/+kqeffpoXXniBtLQ05syZw3e+8x3Cw8O54447WLJkCT/+8Y/x+/3k5ORw8cUXt3hN5PT0dObOncuKFSu45ZZb8Hq93HDDDdxxxx1YrVYmTZpEamoq77zzDnfccQf19fUMHDiQyZMna6MuAG666SaSkpL48ssvue+++7Db7aSkpHDttdcyceLEds+TsFqtTJ06VbvITF1dHTk5OSQnJ5Odnd1k35/85Cd88MEHfPzxxzz33HNkZmZy8cUX9/q23K709ddfs2LFCq0JJS0tjSuuuIJJkyZpY/nvv/9+3nnnHZ544glsNhtZWVnMmjWLI0eOaM8TFhZGfn4+f/3rX3n11Vf517/+pZ1p3Hbbba3Gt1gsPPDAAzzxxBMsWrSIuLg4RowYwejRozl06BBJSUmMHj2a0NBQHn/8cUaOHElISAgWi4WRI0fyP//zP/z+97/H4XAwePBgUlJSGDduXIu/k7bo9Xp+97vf8c9//pP/+7//IxAIkJaWxsyZM5t8VwcOHMgdd9zB448/zne/+11tkMr8+fOZO3dur26WVDoyIUdRlIHASiAHOKqqauSp7QpQrapqpKIoi4A/qaq66tR9y4AHVFVtNV3n5+erp2fz+vp6PvnkE2677TbefPNNhg8f3u4yVldX09DQgF6v1yr0xtdot9uprq7WLq2nKAput5v6+nrsdrs26ayxI/P0SWeNE8ZcLheBQACTyaSNYW9sh3e73ZSXl+N2u7XHN/YLxMXFUV9fT21trTbZy263YzQaiYqK4sSJE9oEncbJUiUlJbjdbm1WqcFgwO/3azNJG6mqSn19PRUVFXg8HkwmE2FhYcTExGiv226343K5tCN8k8mE1+slPDy8WTNKdXU1DodD2z8yMpLY2Ngmk86qq6upr6/H7/djMpmw2Wza0WNjmRwOBw6HA6fTqe0XFhbWZBkNVVUpLi7GaDRis9la7EB2uVw4HA5qa2u1s5zT+xcSExOBk2dBjU1zjR3XjU1mYWFh57ROvsfj4cEHH8Rms2lDJrua2+3mlltuoa6ujltvvfWchxurqsqJEyfQ6/Utvv7jx49TX1/fpJ/KZDIRHh6O1WrFbDajqiq1tbXU1NTg9Xq1gRNGoxGPx0N8fHyTz9zpdGqduTqdDovF0mRyZuPEQUVRtOaXxt+NoihER0djsViorq6mtraWmJgYbDYbbrebo0ePkpKSorXbe71e6urqqK6uxu/3a/1OjU2XycnJWuVcV1dHXV2d1ucATZuNGic8VlRU0NDQgKqqmEwm7XsWExOD0WjU+uHKy8txOBzafKGwsLCz9hm01afy29/+ltLSUh588EHmzZvX0Y+63fLz89m4cWOLhWj3IZOiKDZgAfC/qqraz3gjVUVROjTNU1GU2znZjER6enpHHtqmqKgo7cM+LRYAERERzT4ws9mM2Wxus12/cShZW5WJXq/HarW22W5os9maTBY7veJLSUlpsq/RaCQtLa3V5zqzfGc+9+nCw8M7dLW4lt7D02OZTKYmoyxa2y8sLOys7fWKorQ48uN0FosFi8VCbGxsm/sZjUZiY2PPul9/oShKs+/V6dq67/TniIyMbNd1mNvzO2npu2g2m5utDNA4dr9RSEhIs7Nso9HYbL/WnO27qCgKRqPxrCuTNvYPnssKpj39rKFd5+qKohg5mQjeVFX1g1ObSxVFSTp1fxJQdmr7ceD0Wiz11LYmVFV9TlXVfFVV8xuHenan/nJxC4nZt/SX91Zidr+zJoNTTUAvAntUVX3stLsWAt8+9fe3gY9P236bctJEoLa7+gs6oqdeUENi9p6YwdBf3luJ2f3a00x0AXArsENRlK2ntv0S+BPwrqIo3wMKgetP3fcfTg4rLeDk0NL/6cwCCyGE6HztGU20CmgtfU1vYX8VuOs8yyWEEKIb9akrnfWXNj+J2bdiBkN/eW/7S8zO0KeSQX9p85OYfStmMPSX97a/xOwMfSoZCCGEODc9fmpm47KxQgRD43IMwRAIBOT7308E6zt2uh6fDDZu3NjisglCdAe/309ZWdk5zV4+X5WVldpFg0TfduLEiQ5dJKsr9Phk8PTTT3foer9CdKbG5TfOnCHbHfbv38+uXbvk+98PeDweJkyYENQydGhtoq5y5tpEfr+fyspK9u7d2yNOn/obv9/PZ599xieffEJ1dTW///3vycjIaLIeUn8TExNDenp6t6xZHwgE2LNnD1VVVb3qwjwej4evvvqKv//970yZMoU5c+YwZsyYYBer14iIiCA9PV1bU6wrdMraRN1Jr9cTHx+vLWQlulcgECAiIgKr1cpHH33Erl27mDZtGqmpqXJx+W6g0+kYMWJEsIvRIbW1tezYsYP9+/eTn5/PjTfeyLRp09p9DWwRfD0yGYjg0ul0jB49GqvVSllZGe+//z5ZWVnMnj2btLS0c7rcp+i7Gq9p8f7777NhwwZ++tOfMnfu3HNazE0EjyQD0SKDwcDQoUN55JFHOH78OI888gher5d58+b1+yYj8V9er5fNmzfz7rvv8u6773LXXXfxwx/+UA4YeiHpmRKt0ul0REdH8+9//5sLLriAxx9/nD//+c/s27cv2EUTPcTChQv5wx/+wNKlS/nf//1ffve73zW55rfoPSQZiFY1zqQMCQnh6aef5qqrrmLjxo385Cc/Yfv27b122r04f6qq8vrrr/Ob3/wGg8HAL37xC+69995Wr2Utej5JBqJNjT/u2NhYfvCDHzB//nzq6+v55S9/yYEDB/B6vcEuouhm9fX1LFiwgL/97W+MGDGCW265hblz5wZlLoboPJIMRLvo9XqGDRvG3LlzmTVrFocPH+bFF1/kyJEjuFyuYBdPdJPKykpWr17Na6+9RlRUFFdeeSVTpkw569XqRM8nHcii3QwGA6NHjyYiIoLi4mLefPNN0tPTmTlzJgMGDJBOwz6upqaGrVu38s4777B9+3YefvhhLr300nZddlL0fJIMRIcYDAYyMzP585//THFxMb///e9xOBxcffXVZGZmymzZPsrr9bJixQr+/e9/s2LFCn77299y8803YzBIFdJXyC9XdJiiKERERPDOO+9w0UUX8eyzz/LQQw+xe/fuYBdNdJFnnnmGhx9+mIMHD/LHP/6RO++8U4YX9zGSDESHnT7K6LHH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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, "output_type": "display_data" } ], "source": [ + "init_net_state = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step0.')\n", + "\n", "# inner loss\n", - "loss = loss_fn(net(x), y)\n", - "print(f\"inner loss: {loss:.4f}\")\n", - "TorchOpt.visual.make_dot(loss).render(\"full_graph\", format=\"png\")\n", - "plt.figure(figsize=(10,10))\n", - "plt.imshow(imgplt.imread('full_graph.png'))" + "inner_loss = loss_fn(net(x), y)\n", + "\n", + "print(f'inner loss: {inner_loss:.4f}')\n", + "display(\n", + " torchopt.visual.make_dot(\n", + " inner_loss,\n", + " params=(init_net_state, {'inner_loss': inner_loss})\n", + " )\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Conduct inner-loop optimization with MetaSGD, here the meta-parameter is served as a factor controling the scale of inner-loop loss." + "Conduct inner-loop optimization with `MetaSGD`, here the meta-parameter is served as a factor controlling the scale of inner-loop loss." ] }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# inner-step optimization\n", - "loss = loss * meta_parameter\n", + "loss = inner_loss * meta_parameter\n", "optim.step(loss)" ] }, @@ -153,56 +153,47 @@ "metadata": {}, "source": [ "We compute the outer loss and draw the full computation graph of the first bi-level process. In this graph, three main parts are included.\n", + "\n", "- Inner-loop: forward process and inner-loss calculation\n", - "- Inner-loop optimization: MetaSGD optimization step given inner-loss\n", + "- Inner-loop optimization: `MetaSGD` optimization step given inner-loss\n", "- Outer-loop: forward process and outer-loss calculation" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "outer loss: 1.5181\n" + "outer loss: 0.2297\n", + "\n" ] }, { "data": { - "text/plain": [ - "" - ] + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n139975938634752\n\nouter_loss\n ()\n\n\n\n139975938188288\n\nMseLossBackward0\n\n\n\n139975938188288->139975938634752\n\n\n\n\n\n139975938188336\n\nAddmmBackward0\n\n\n\n139975938188336->139975938188288\n\n\n\n\n\n139975938188096\n\nAddBackward0\n step1.fc.bias\n (1)\n\n\n\n139975938188096->139975938188336\n\n\n\n\n\n139978603490800\n\nAccumulateGrad\n\n\n\n139978603490800->139975938188096\n\n\n\n\n\n139978603489744\n\nAddmmBackward0\n\n\n\n139978603490800->139978603489744\n\n\n\n\n\n139975938634512\n\nstep0.fc.bias\n (1)\n\n\n\n139975938634512->139978603490800\n\n\n\n\n\n139975938188480\n\nMulBackward0\n\n\n\n139975938188480->139975938188096\n\n\n\n\n\n139975938188144\n\nViewBackward0\n\n\n\n139975938188144->139975938188480\n\n\n\n\n\n139975938187664\n\nSumBackward1\n\n\n\n139975938187664->139975938188144\n\n\n\n\n\n139975938188720\n\nMseLossBackwardBackward0\n\n\n\n139975938188720->139975938187664\n\n\n\n\n\n139975938189200\n\nTBackward0\n\n\n\n139975938188720->139975938189200\n\n\n\n\n\n139975938188816\n\nMulBackward0\n\n\n\n139975938188816->139975938188720\n\n\n\n\n\n139975938188912\n\nAccumulateGrad\n\n\n\n139975938188912->139975938188816\n\n\n\n\n\n139975938635072\n\nmeta_parameter\n ()\n\n\n\n139975938635072->139975938188912\n\n\n\n\n\n139978603489744->139975938188720\n\n\n\n\n\n139978603490224\n\nTBackward0\n\n\n\n139978603490224->139978603489744\n\n\n\n\n\n139978603490368\n\nAccumulateGrad\n\n\n\n139978603490368->139978603490224\n\n\n\n\n\n139975938187808\n\nAddBackward0\n step1.fc.weight\n (1, 16)\n\n\n\n139978603490368->139975938187808\n\n\n\n\n\n139975938634432\n\nstep0.fc.weight\n (1, 16)\n\n\n\n139975938634432->139978603490368\n\n\n\n\n\n139975938188384\n\nTBackward0\n\n\n\n139975938188384->139975938188336\n\n\n\n\n\n139975938187808->139975938188384\n\n\n\n\n\n139975938188672\n\nMulBackward0\n\n\n\n139975938188672->139975938187808\n\n\n\n\n\n139975938189008\n\nTBackward0\n\n\n\n139975938189008->139975938188672\n\n\n\n\n\n139975938189104\n\nTBackward0\n\n\n\n139975938189104->139975938189008\n\n\n\n\n\n139975938188864\n\nMmBackward0\n\n\n\n139975938188864->139975938189104\n\n\n\n\n\n139975938189200->139975938188864\n\n\n\n\n\n" }, - "execution_count": 61, "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, "output_type": "display_data" } ], "source": [ - "# extract state_dict for updated network\n", - "one_step_net_state = TorchOpt.extract_state_dict(net)\n", - "one_step_optim_state = TorchOpt.extract_state_dict(optim)\n", - "# calculate outer loss\n", + "# Extract `state_dict`` for updated network\n", + "one_step_net_state = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step1.')\n", + "one_step_optim_state = torchopt.extract_state_dict(optim)\n", + "\n", + "# Calculate outer loss\n", "outer_loss = loss_fn(net(x), y)\n", - "print(f\"outer loss: {outer_loss:.4f}\")\n", - "TorchOpt.visual.make_dot(outer_loss).render(\"full_graph\", format=\"png\")\n", - "plt.figure(figsize=(10,10))\n", - "plt.imshow(imgplt.imread('full_graph.png'))" + "print(f'outer loss: {outer_loss:.4f}')\n", + "display(\n", + " torchopt.visual.make_dot(\n", + " outer_loss,\n", + " params=(init_net_state, one_step_net_state, {'meta_parameter': meta_parameter, 'outer_loss': outer_loss})\n", + " )\n", + ")" ] }, { @@ -214,40 +205,42 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "tensor([-0.0537])\n", - "tensor([1.1000], requires_grad=True)\n" + "meta_parameter.grad = tensor(-0.2464)\n", + "meta_parameter = Parameter containing: tensor(1.1000, requires_grad=True)\n" ] } ], "source": [ "meta_optim.zero_grad()\n", "outer_loss.backward()\n", - "print(meta_parameter.grad)\n", + "print(f'meta_parameter.grad = {meta_parameter.grad!r}')\n", "meta_optim.step()\n", - "print(meta_parameter)" + "print(f'meta_parameter = {meta_parameter!r}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We have already conducted one bi-level optimization and optimize our meta-parameters. When you want to conduct the second bi-level optimization, you need to be careful whether you need to use the `stop_gradient` function. For example, if your new inner-loop parameters directly inherits previous inner-loop parameters (which is a common strategy in many meta-learning algorithms like MGRL), you might need `stop_gradient` function." + "We have already conducted one bi-level optimization and optimize our meta-parameters. When you want to conduct the second bi-level optimization, you need to be careful whether you need to use the `stop_gradient` function. For example, if your new inner-loop parameters directly inherits previous inner-loop parameters (which is a common strategy in many meta-learning algorithms like Meta-Gradient Reinforcement Learning (MGRL) ([arXiv:1805.09801](https://arxiv.org/abs/1805.09801))), you might need `stop_gradient` function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In general, the back-propagation only free saved tensors (often used as auxiliary data for computing the gradient) but the computation graph remains. Once the outer iteration is finished, if you want to use any intermediate network parameters produced by the inner loop for the next bi-level iteration, you should detach them from the computation graph.\n", + "In general, the back-propagation only frees saved tensors (often used as auxiliary data for computing the gradient) but the computation graph remains. Once the outer iteration is finished, if you want to use any intermediate network parameters produced by the inner loop for the next bi-level iteration, you should detach them from the computation graph.\n", + "\n", "There are two main reasons:\n", - "- The network parameters are still connected to the previous computation graph (`.grad_fn` is not `None`). If later the gradient back-propagate to these parameters, the PyTorch backward engine will try to back-propagate through the previous computation graph. Which will raise a `RuntimeError`: Trying to backward through the graph a second time...\n", + "\n", + "- The network parameters are still connected to the previous computation graph (`.grad_fn` is not `None`). If later the gradient back-propagate to these parameters, the PyTorch backward engine will try to back-propagate through the previous computation graph. This will raise a `RuntimeError`: Trying to backward through the graph a second time...\n", "- If we do not detach the computation graph, the computation graph connected to these parameters can not be freed by GC (Garbage Collector) until these parameters are collected by GC." ] }, @@ -260,43 +253,109 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 8, "metadata": {}, "outputs": [ { - "ename": "RuntimeError", - "evalue": "Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved tensors after calling backward.", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", - "Input \u001b[0;32mIn [48]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 7\u001b[0m plt\u001b[38;5;241m.\u001b[39mimshow(imgplt\u001b[38;5;241m.\u001b[39mimread(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mfull_graph.png\u001b[39m\u001b[38;5;124m'\u001b[39m))\n\u001b[1;32m 8\u001b[0m meta_optim\u001b[38;5;241m.\u001b[39mzero_grad()\n\u001b[0;32m----> 9\u001b[0m \u001b[43mouter_loss\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 10\u001b[0m meta_optim\u001b[38;5;241m.\u001b[39mstep()\n", - "File \u001b[0;32m~/miniconda3/envs/OpTorch/lib/python3.9/site-packages/torch/_tensor.py:363\u001b[0m, in \u001b[0;36mTensor.backward\u001b[0;34m(self, gradient, retain_graph, create_graph, inputs)\u001b[0m\n\u001b[1;32m 354\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m has_torch_function_unary(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m 355\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m handle_torch_function(\n\u001b[1;32m 356\u001b[0m Tensor\u001b[38;5;241m.\u001b[39mbackward,\n\u001b[1;32m 357\u001b[0m (\u001b[38;5;28mself\u001b[39m,),\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 361\u001b[0m create_graph\u001b[38;5;241m=\u001b[39mcreate_graph,\n\u001b[1;32m 362\u001b[0m inputs\u001b[38;5;241m=\u001b[39minputs)\n\u001b[0;32m--> 363\u001b[0m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mautograd\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgradient\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minputs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/OpTorch/lib/python3.9/site-packages/torch/autograd/__init__.py:173\u001b[0m, in \u001b[0;36mbackward\u001b[0;34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[0m\n\u001b[1;32m 168\u001b[0m retain_graph \u001b[38;5;241m=\u001b[39m create_graph\n\u001b[1;32m 170\u001b[0m \u001b[38;5;66;03m# The reason we repeat same the comment below is that\u001b[39;00m\n\u001b[1;32m 171\u001b[0m \u001b[38;5;66;03m# some Python versions print out the first line of a multi-line function\u001b[39;00m\n\u001b[1;32m 172\u001b[0m \u001b[38;5;66;03m# calls in the traceback and some print out the last line\u001b[39;00m\n\u001b[0;32m--> 173\u001b[0m \u001b[43mVariable\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_execution_engine\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun_backward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Calls into the C++ engine to run the backward pass\u001b[39;49;00m\n\u001b[1;32m 174\u001b[0m \u001b[43m \u001b[49m\u001b[43mtensors\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgrad_tensors_\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 175\u001b[0m \u001b[43m \u001b[49m\u001b[43mallow_unreachable\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maccumulate_grad\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mRuntimeError\u001b[0m: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved tensors after calling backward." + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" ] }, { "data": { - "image/png": "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\n", + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n139978828415600\n\nouter_loss\n ()\n\n\n\n139975938626944\n\nMseLossBackward0\n\n\n\n139975938626944->139978828415600\n\n\n\n\n\n139975938626656\n\nAddmmBackward0\n\n\n\n139975938626656->139975938626944\n\n\n\n\n\n139975938188624\n\nAddBackward0\n\n\n\n139975938188624->139975938626656\n\n\n\n\n\n139975938188096\n\nAddBackward0\n step1.fc.bias\n (1)\n\n\n\n139975938188096->139975938188624\n\n\n\n\n\n139975938188144\n\nAddmmBackward0\n\n\n\n139975938188096->139975938188144\n\n\n\n\n\n139975938187424\n\nAccumulateGrad\n\n\n\n139975938187424->139975938188096\n\n\n\n\n\n139975938188912\n\nAddmmBackward0\n\n\n\n139975938187424->139975938188912\n\n\n\n\n\n139975938634512\n\nstep0.fc.bias\n (1)\n\n\n\n139975938634512->139975938187424\n\n\n\n\n\n139975938187856\n\nMulBackward0\n\n\n\n139975938187856->139975938188096\n\n\n\n\n\n139975938188768\n\nViewBackward0\n\n\n\n139975938188768->139975938187856\n\n\n\n\n\n139975938189200\n\nSumBackward1\n\n\n\n139975938189200->139975938188768\n\n\n\n\n\n139975938189008\n\nMseLossBackwardBackward0\n\n\n\n139975938189008->139975938189200\n\n\n\n\n\n139975938189728\n\nTBackward0\n\n\n\n139975938189008->139975938189728\n\n\n\n\n\n139975938188864\n\nMulBackward0\n\n\n\n139975938188864->139975938189008\n\n\n\n\n\n139975938187952\n\nAccumulateGrad\n\n\n\n139975938187952->139975938188864\n\n\n\n\n\n139975938187712\n\nMulBackward0\n\n\n\n139975938187952->139975938187712\n\n\n\n\n\n139975938635072\n\nmeta_parameter\n ()\n\n\n\n139975938635072->139975938187952\n\n\n\n\n\n139975938188912->139975938189008\n\n\n\n\n\n139975938188480\n\nTBackward0\n\n\n\n139975938188480->139975938188912\n\n\n\n\n\n139975938188384\n\nAccumulateGrad\n\n\n\n139975938188384->139975938188480\n\n\n\n\n\n139975938187808\n\nAddBackward0\n step1.fc.weight\n (1, 16)\n\n\n\n139975938188384->139975938187808\n\n\n\n\n\n139975938634432\n\nstep0.fc.weight\n (1, 16)\n\n\n\n139975938634432->139975938188384\n\n\n\n\n\n139975938187520\n\nMulBackward0\n\n\n\n139975938187520->139975938188624\n\n\n\n\n\n139975938189296\n\nViewBackward0\n\n\n\n139975938189296->139975938187520\n\n\n\n\n\n139975938188576\n\nSumBackward1\n\n\n\n139975938188576->139975938189296\n\n\n\n\n\n139975938188720\n\nMseLossBackwardBackward0\n\n\n\n139975938188720->139975938188576\n\n\n\n\n\n139975938189824\n\nTBackward0\n\n\n\n139975938188720->139975938189824\n\n\n\n\n\n139975938187712->139975938188720\n\n\n\n\n\n139975938188144->139975938188720\n\n\n\n\n\n139975938188816\n\nTBackward0\n\n\n\n139975938188816->139975938188144\n\n\n\n\n\n139975938187808->139975938188816\n\n\n\n\n\n139975938189104\n\nAddBackward0\n\n\n\n139975938187808->139975938189104\n\n\n\n\n\n139975938189248\n\nMulBackward0\n\n\n\n139975938189248->139975938187808\n\n\n\n\n\n139975938189344\n\nTBackward0\n\n\n\n139975938189344->139975938189248\n\n\n\n\n\n139975938189536\n\nTBackward0\n\n\n\n139975938189536->139975938189344\n\n\n\n\n\n139975938189440\n\nMmBackward0\n\n\n\n139975938189440->139975938189536\n\n\n\n\n\n139975938189728->139975938189440\n\n\n\n\n\n139975938187904\n\nTBackward0\n\n\n\n139975938187904->139975938626656\n\n\n\n\n\n139975938189104->139975938187904\n\n\n\n\n\n139975938188240\n\nMulBackward0\n\n\n\n139975938188240->139975938189104\n\n\n\n\n\n139975938188048\n\nTBackward0\n\n\n\n139975938188048->139975938188240\n\n\n\n\n\n139975938188528\n\nTBackward0\n\n\n\n139975938188528->139975938188048\n\n\n\n\n\n139975938189584\n\nMmBackward0\n\n\n\n139975938189584->139975938188528\n\n\n\n\n\n139975938189824->139975938189584\n\n\n\n\n\n" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
╭──────────────────────────── Traceback (most recent call last) ────────────────────────────╮\n",
+       " <ipython-input-8-5906690e2182>:17 in <cell line: 17>                                      \n",
+       " /home/TorchOpt/Miniconda3/envs/torchopt/lib/python3.8/site-packages/torch/_tensor.py:396  \n",
+       " in backward                                                                               \n",
+       "                                                                                           \n",
+       "    393 │   │   │   │   retain_graph=retain_graph,                                         \n",
+       "    394 │   │   │   │   create_graph=create_graph,                                         \n",
+       "    395 │   │   │   │   inputs=inputs)                                                     \n",
+       "  396 │   │   torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs \n",
+       "    397 │                                                                                  \n",
+       "    398 │   def register_hook(self, hook):                                                 \n",
+       "    399 │   │   r\"\"\"Registers a backward hook.                                             \n",
+       "                                                                                           \n",
+       " /home/TorchOpt/Miniconda3/envs/torchopt/lib/python3.8/site-packages/torch/autograd/__init \n",
+       " __.py:173 in backward                                                                     \n",
+       "                                                                                           \n",
+       "   170 │   # The reason we repeat same the comment below is that                           \n",
+       "   171 │   # some Python versions print out the first line of a multi-line function        \n",
+       "   172 │   # calls in the traceback and some print out the last line                       \n",
+       " 173 Variable._execution_engine.run_backward(  # Calls into the C++ engine to run th \n",
+       "   174 │   │   tensors, grad_tensors_, retain_graph, create_graph, inputs,                 \n",
+       "   175 │   │   allow_unreachable=True, accumulate_grad=True)  # Calls into the C++ engine  \n",
+       "   176                                                                                     \n",
+       "╰───────────────────────────────────────────────────────────────────────────────────────────╯\n",
+       "RuntimeError: Trying to backward through the graph a second time (or directly access saved \n",
+       "tensors after they have already been freed). Saved intermediate values of the graph are freed\n",
+       "when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to \n",
+       "backward through the graph a second time or if you need to access saved tensors after calling\n",
+       "backward.\n",
+       "
\n" + ], "text/plain": [ - "
" + "\u001b[91m╭─\u001b[0m\u001b[91m─────────────────────────── \u001b[0m\u001b[1;31mTraceback \u001b[0m\u001b[1;2;31m(most recent call last)\u001b[0m\u001b[91m ───────────────────────────\u001b[0m\u001b[91m─╮\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[33m\u001b[0m:\u001b[94m17\u001b[0m in \u001b[92m\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2;33m/home/PanXuehai/Miniconda3/envs/torchopt/lib/python3.8/site-packages/torch/\u001b[0m\u001b[1;33m_tensor.py\u001b[0m:\u001b[94m396\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m in \u001b[92mbackward\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m 393 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mretain_graph=retain_graph, \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m 394 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mcreate_graph=create_graph, \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m 395 \u001b[0m\u001b[2m│ │ │ │ \u001b[0minputs=inputs) \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[31m❱ \u001b[0m 396 \u001b[2m│ │ \u001b[0mtorch.autograd.backward(\u001b[96mself\u001b[0m, gradient, retain_graph, create_graph, inputs \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m 397 \u001b[0m\u001b[2m│ \u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m 398 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mdef\u001b[0m \u001b[92mregister_hook\u001b[0m(\u001b[96mself\u001b[0m, hook): \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m 399 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[33mr\u001b[0m\u001b[33m\"\"\"Registers a backward hook.\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2;33m/home/PanXuehai/Miniconda3/envs/torchopt/lib/python3.8/site-packages/torch/autograd/\u001b[0m\u001b[1;33m__ini\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[1;33mt__.py\u001b[0m:\u001b[94m173\u001b[0m in \u001b[92mbackward\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m170 \u001b[0m\u001b[2m│ \u001b[0m\u001b[2m# The reason we repeat same the comment below is that\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m171 \u001b[0m\u001b[2m│ \u001b[0m\u001b[2m# some Python versions print out the first line of a multi-line function\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m172 \u001b[0m\u001b[2m│ \u001b[0m\u001b[2m# calls in the traceback and some print out the last line\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[31m❱ \u001b[0m173 \u001b[2m│ \u001b[0mVariable._execution_engine.run_backward( \u001b[2m# Calls into the C++ engine to run th\u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m174 \u001b[0m\u001b[2m│ │ \u001b[0mtensors, grad_tensors_, retain_graph, create_graph, inputs, \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m175 \u001b[0m\u001b[2m│ │ \u001b[0mallow_unreachable=\u001b[94mTrue\u001b[0m, accumulate_grad=\u001b[94mTrue\u001b[0m) \u001b[2m# Calls into the C++ engine \u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m│\u001b[0m \u001b[2m176 \u001b[0m \u001b[91m│\u001b[0m\n", + "\u001b[91m╰───────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", + "\u001b[1;91mRuntimeError: \u001b[0mTrying to backward through the graph a second time \u001b[1m(\u001b[0mor directly access saved \n", + "tensors after they have already been freed\u001b[1m)\u001b[0m. Saved intermediate values of the graph are freed\n", + "when you call \u001b[1;35m.backward\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m or \u001b[1;35mautograd.grad\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m. Specify \u001b[33mretain_graph\u001b[0m=\u001b[3;92mTrue\u001b[0m if you need to \n", + "backward through the graph a second time or if you need to access saved tensors after calling\n", + "backward.\n" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], "source": [ + "# Inner update with attached computation graph\n", "inner_loss = loss_fn(net(x), y)\n", "loss = inner_loss * meta_parameter\n", "optim.step(loss)\n", + "\n", + "# Outer forward process\n", "outer_loss = loss_fn(net(x), y)\n", - "TorchOpt.visual.make_dot(outer_loss).render(\"full_graph\", format=\"png\")\n", - "plt.figure(figsize=(10,10))\n", - "plt.imshow(imgplt.imread('full_graph.png'))\n", + "display(\n", + " torchopt.visual.make_dot(\n", + " outer_loss,\n", + " params=(init_net_state, one_step_net_state, {'meta_parameter': meta_parameter, 'outer_loss': outer_loss})\n", + " )\n", + ")\n", + "\n", + "# Outer update\n", "meta_optim.zero_grad()\n", "outer_loss.backward()\n", "meta_optim.step()" @@ -306,20 +365,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "From the graph we can see, directly conducting the second bi-level process links the graph of first and second bi-level process together. We should manually stop gradient with `TorchOpt.stop_gradient`. `TorchOpt.stop_gradient` will detach the node of gradient graph and make it become a leaf node. It allows the input of network, optimizer, or state dictionary and the gradient operation happens in an inplace manner.\n", + "From the graph we can see, directly conducting the second bi-level process links the graph of first and second bi-level process together. We should manually stop gradient with `torchopt.stop_gradient`. `torchopt.stop_gradient` will detach the node of gradient graph and make it become a leaf node. It allows the input of network, optimizer, or state dictionary and the gradient operation happens in an in-place manner.\n", "\n", - "Let's use recover_state_dict to come back to one-step updated states." + "Let's use `recover_state_dict` to come back to one-step updated states." ] }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Reset to previous one-step updated states\n", - "TorchOpt.recover_state_dict(net, one_step_net_state)\n", - "TorchOpt.recover_state_dict(optim, one_step_optim_state)" + "torchopt.recover_state_dict(net, one_step_net_state)\n", + "torchopt.recover_state_dict(optim, one_step_optim_state)" ] }, { @@ -331,45 +390,51 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "tensor([1.1950], requires_grad=True)\n" + "meta_parameter.grad = tensor(-0.0914)\n", + "meta_parameter = Parameter containing: tensor(1.1887, requires_grad=True)\n", + "\n" ] }, { "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" + "image/svg+xml": "\n\n\n\n\n\n%3\n\n\n\n139975938621248\n\nouter_loss\n ()\n\n\n\n139975251126352\n\nMseLossBackward0\n\n\n\n139975251126352->139975938621248\n\n\n\n\n\n139975251126592\n\nAddmmBackward0\n\n\n\n139975251126592->139975251126352\n\n\n\n\n\n139975251125920\n\nAddBackward0\n\n\n\n139975251125920->139975251126592\n\n\n\n\n\n139975251126400\n\nAccumulateGrad\n\n\n\n139975251126400->139975251125920\n\n\n\n\n\n139975251127120\n\nAddmmBackward0\n\n\n\n139975251126400->139975251127120\n\n\n\n\n\n139975938636032\n\nstep1.detached.fc.bias\n (1)\n\n\n\n139975938636032->139975251126400\n\n\n\n\n\n139975251126304\n\nMulBackward0\n\n\n\n139975251126304->139975251125920\n\n\n\n\n\n139975251127072\n\nViewBackward0\n\n\n\n139975251127072->139975251126304\n\n\n\n\n\n139975251128080\n\nSumBackward1\n\n\n\n139975251128080->139975251127072\n\n\n\n\n\n139975251126448\n\nMseLossBackwardBackward0\n\n\n\n139975251126448->139975251128080\n\n\n\n\n\n139975251127456\n\nTBackward0\n\n\n\n139975251126448->139975251127456\n\n\n\n\n\n139975251127312\n\nMulBackward0\n\n\n\n139975251127312->139975251126448\n\n\n\n\n\n139975251126016\n\nAccumulateGrad\n\n\n\n139975251126016->139975251127312\n\n\n\n\n\n139975938635072\n\nmeta_parameter\n ()\n\n\n\n139975938635072->139975251126016\n\n\n\n\n\n139975251127120->139975251126448\n\n\n\n\n\n139975251126880\n\nTBackward0\n\n\n\n139975251126880->139975251127120\n\n\n\n\n\n139975251126544\n\nAccumulateGrad\n\n\n\n139975251126544->139975251126880\n\n\n\n\n\n139975251128272\n\nAddBackward0\n\n\n\n139975251126544->139975251128272\n\n\n\n\n\n139975938635552\n\nstep1.detached.fc.weight\n (1, 16)\n\n\n\n139975938635552->139975251126544\n\n\n\n\n\n139975251126256\n\nTBackward0\n\n\n\n139975251126256->139975251126592\n\n\n\n\n\n139975251128272->139975251126256\n\n\n\n\n\n139975251127744\n\nMulBackward0\n\n\n\n139975251127744->139975251128272\n\n\n\n\n\n139975251126112\n\nTBackward0\n\n\n\n139975251126112->139975251127744\n\n\n\n\n\n139975251126640\n\nTBackward0\n\n\n\n139975251126640->139975251126112\n\n\n\n\n\n139975251126976\n\nMmBackward0\n\n\n\n139975251126976->139975251126640\n\n\n\n\n\n139975251127456->139975251126976\n\n\n\n\n\n" }, + "metadata": {}, "output_type": "display_data" } ], "source": [ - "# stop gradient and make them become the leaf node\n", - "TorchOpt.stop_gradient(net)\n", - "TorchOpt.stop_gradient(optim)\n", + "# Stop gradient and make them become the leaf node\n", + "torchopt.stop_gradient(net)\n", + "torchopt.stop_gradient(optim)\n", + "one_step_net_state_detached = torchopt.extract_state_dict(net, enable_visual=True, visual_prefix='step1.detached.')\n", "\n", + "# Inner update\n", "inner_loss = loss_fn(net(x), y)\n", "loss = inner_loss * meta_parameter\n", "optim.step(loss)\n", + "\n", + "# Outer update\n", "outer_loss = loss_fn(net(x), y)\n", - "TorchOpt.visual.make_dot(outer_loss).render(\"full_graph\", format=\"png\")\n", - "plt.figure(figsize=(10,10))\n", - "plt.imshow(imgplt.imread('full_graph.png'))\n", "meta_optim.zero_grad()\n", "outer_loss.backward()\n", + "print(f'meta_parameter.grad = {meta_parameter.grad!r}')\n", "meta_optim.step()\n", - "print(meta_parameter)" + "print(f'meta_parameter = {meta_parameter!r}')\n", + "\n", + "display(\n", + " torchopt.visual.make_dot(\n", + " outer_loss,\n", + " params=(one_step_net_state_detached, {'meta_parameter': meta_parameter, 'outer_loss': outer_loss})\n", + " )\n", + ")" ] }, { @@ -381,13 +446,10 @@ } ], "metadata": { - "interpreter": { - "hash": "238ad0feaa04228775e5e27229169b0e3e76c0e018d5a6d65c4906ccad5c5a9e" - }, "kernelspec": { - "display_name": "OpTorch", + "display_name": "Python 3.8.13 ('torchopt')", "language": "python", - "name": "optorch" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -399,7 +461,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.8.13" + }, + "vscode": { + "interpreter": { + "hash": "2a8cc1ff2cbc47027bf9993941710d9ab9175f14080903d9c7c432ee63d681da" + } } }, "nbformat": 4, diff --git a/tutorials/requirements.txt b/tutorials/requirements.txt new file mode 100644 index 00000000..00cb5228 --- /dev/null +++ b/tutorials/requirements.txt @@ -0,0 +1,8 @@ +--extra-index-url https://download.pytorch.org/whl/cu116 +torch == 1.12 +torchvision +functorch + +--requirement ../requirements.txt + +ipykernel pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy