From fca81124920bf5cfa11c79c4a0566b4cad70b511 Mon Sep 17 00:00:00 2001 From: JesperDramsch Date: Wed, 2 Sep 2026 20:21:26 +0000 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'.github/wo?= =?UTF-8?q?rkflows/'=20with=20remote=20'workflows/code-quality/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/codeql.yml | 67 +++++------------------------------- 1 file changed, 9 insertions(+), 58 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 067a864..09f3a81 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,74 +1,25 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" +# .github/workflows/codeql.yml +name: CodeQL Analysis on: push: - branches: [ "main" ] + branches: [ main, master ] pull_request: - # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: [ main, master ] schedule: - - cron: '27 20 * * 0' + - cron: '0 0 * * 0' jobs: analyze: - name: Analyze runs-on: ubuntu-latest permissions: - actions: read - contents: read security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. + - uses: actions/checkout@v7.0.1 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v4.37.9 with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - + languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" + uses: github/codeql-action/analyze@v4.37.9 From 906c2f1608b16264bed06ce06b021326f71b90a7 Mon Sep 17 00:00:00 2001 From: JesperDramsch Date: Wed, 2 Sep 2026 20:21:26 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=94=84=20created=20local=20'.github/d?= =?UTF-8?q?ependabot.yml'=20from=20remote=20'configs/dependabot.yml'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..57535d9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" From cd2862d520794112970dcadad7f21098e957f7c7 Mon Sep 17 00:00:00 2001 From: JesperDramsch Date: Wed, 2 Sep 2026 20:21:26 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'.github/wo?= =?UTF-8?q?rkflows/'=20with=20remote=20'workflows/python/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pre-commit.yml | 18 +++++++++++++ .github/workflows/python-linting.yml | 26 +++++++++++++++++++ .github/workflows/python-publish.yml | 28 ++++++++++++++++++++ .github/workflows/python-testing.yml | 38 ++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .github/workflows/python-linting.yml create mode 100644 .github/workflows/python-publish.yml create mode 100644 .github/workflows/python-testing.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..efeb814 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,18 @@ +# .github/workflows/pre-commit.yml +name: Pre-commit Checks + +on: + pull_request: + push: + branches: [ main, master ] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7.0.0 + with: + python-version: '3.x' + cache: 'pip' + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/python-linting.yml b/.github/workflows/python-linting.yml new file mode 100644 index 0000000..3761c94 --- /dev/null +++ b/.github/workflows/python-linting.yml @@ -0,0 +1,26 @@ +# .github/workflows/python-linting.yml +name: Lint + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7.0.1 + - uses: actions/setup-python@v7.0.0 + with: + python-version: '3.x' + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff black + - name: Run Black + run: black . --check -l 120 + - name: Run Ruff + run: ruff check . diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..663c6df --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,28 @@ +# .github/workflows/python-publish.yml +name: Publish to PyPI + +on: + release: + types: [ published ] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7.0.1 + - name: Set up Python + uses: actions/setup-python@v7.0.0 + with: + python-version: '3.x' + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + - name: Build package + run: python -m build + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload dist/* diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml new file mode 100644 index 0000000..5a927a2 --- /dev/null +++ b/.github/workflows/python-testing.yml @@ -0,0 +1,38 @@ +# .github/workflows/python-testing.yml +name: Python Tests + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11'] + + steps: + - uses: actions/checkout@v7.0.1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v7.0.0 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov pytest-mock pytest-emoji + pip install -e . + - name: Run tests + run: | + pytest --cov=./ --cov-report=xml --emoji + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v7.0.0 + with: + file: ./coverage.xml + fail_ci_if_error: true + + From ae528cd20851a0643b4b1ea948b4b26173498cc6 Mon Sep 17 00:00:00 2001 From: JesperDramsch Date: Wed, 2 Sep 2026 20:21:26 +0000 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'./'=20with?= =?UTF-8?q?=20remote=20'configs/python/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 211 +++++++++++++++++++++++++++++++--------- .pre-commit-config.yaml | 56 +++++++++++ ruff.toml | 61 ++++++++++++ 3 files changed, 284 insertions(+), 44 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 ruff.toml diff --git a/.gitignore b/.gitignore index bb23d83..74d610e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,173 @@ -# Global ignore patters. They cause the files with the following names and -# extensions to be ignored in general by Git. -# -# We ignore these files because they're usually binary and/or very large, which -# greatly bloats the repository and its clone time. In this category are the -# files .h5/.hdf5/.txt/.log and files named *log_file*. Some of these logs are -# enormous. -# -# We also ignore files that can be easily reproduced one way or another by -# compilers, like .pyc/.o/.so/.a/.lib. -# -# Lastly we ignore files and directories that signal their temporary character -# with a name that contains "tmp". -# -# **************************************************************************** -# ** DO NOT put ignore patterns here that are private to your clone of this ** -# ** repository. Those belong in your clone's .git/info/exclude file. ** -# **************************************************************************** -# -**/*.h5 -**/*.hdf5 -**/*.txt -**/*log_file* -**/*.log -**/*.out -**/*.pyc -**/*.o -**/*.so -**/*.a -**/*.lib -**/*.dll -**/*.exe -**/*tmp* -**/*.yaml -**/*.npy -**/slurm-*.out -keras_complex.egg-info/ +_site +.bundle +vendor +.vscode +.DS_Store +.idea +.tmp + +# 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/ +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/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py -# Ignore virtual environment -venv +# 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/ -# IDE -.vscode -.idea +# 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/ -dist/* +# 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/ -__pycache__/* \ No newline at end of file +utils/tidy_conf/data/.tmp/ +# pixi environments +.pixi +*.egg-info diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0cf98ed --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,56 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml # Check YAML files for syntax errors + - id: debug-statements # Check for debugger imports and py37+ breakpoint() + - id: end-of-file-fixer # Ensure files end in a newline + - id: trailing-whitespace # Trailing whitespace checker + - id: check-added-large-files # Check for large files added to git + - id: check-merge-conflict # Check for files that contain merge conflict strings +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 # Use the ref you want to point at + hooks: + - id: python-use-type-annotations # Check for missing type annotations + - id: python-check-blanket-noqa # Check for # noqa: all + - id: python-no-log-warn # Check for log.warn +- repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: + - -l 120 + - --force-single-line-imports + - --profile black +- repo: https://github.com/asottile/pyupgrade # Upgrade Python syntax + rev: v3.15.2 + hooks: + - id: pyupgrade + args: + - --py310-plus +- repo: https://github.com/psf/black # Format Python code + rev: 24.4.2 + hooks: + - id: black + args: + - --line-length=120 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.4 + hooks: + - id: ruff + args: + - --line-length=120 + - --fix + - --exit-non-zero-on-fix + - --preview +- repo: https://github.com/jshwi/docsig # Check docstrings against function sig + rev: v0.53.2 + hooks: + - id: docsig + args: + - --ignore-no-params # Allow docstrings without parameters + - --check-dunders # Check dunder methods + - --check-overridden # Check overridden methods + - --check-protected # Check protected methods + - --check-class # Check class docstrings + - --disable=E113 # Disable empty docstrings diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..3e7557b --- /dev/null +++ b/ruff.toml @@ -0,0 +1,61 @@ +line-length = 120 +target-version = "py310" +preview = true + +[lint] +select = [ + "A", # flake8 builtins + "B", # flake8-bugbear + "D", # pydocstyle + "E", # pycodestyle error + "W", # pycodestyle warning + "F", # flake8 error + "UP", # Pyupgrade + "SIM", # flake8-simplify + "N", # pep8 naming + "YTT", # flake8-2020 + "S", # bandit + "COM", # Commas + "C4", # Comprehensions + "DTZ", # Datetimes + "ISC", # Implicit string concatenation + "ICN", # Import conventions + "LOG", # Logging + "PIE", # Misc lints + "PT", # Pytest + "Q", # Quotes + "RSE", # Raises + "TID", # Tidy imports + "PTH", # Use Pathlib + "PGH", # Pygrep hooks + "R", # Refactor + "FLY", # Fstrings + "PERF", # Perfomance linting + "FURB", # Modernising code + "RUF", # Ruff specific + "NPY", # Numpys + # "PL", # Pylint + # "TD", # Todos + # "FBT", # Boolean traps + # "CPY", # Copyright +] +ignore = [ + "E203", + "D100", + "D101", + "D102", + "D103", + "D104", + "D105", + "D401", + "S101", + "B028", + "B018", + "PT018", + "RET504", + +] + + +[lint.pydocstyle] +convention = "numpy" From 5ef9634f06c68ab4b25c42d83ec0f17b6be43291 Mon Sep 17 00:00:00 2001 From: JesperDramsch Date: Wed, 2 Sep 2026 20:21:26 +0000 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=94=84=20created=20local=20'.github/w?= =?UTF-8?q?orkflows/labeler.yml'=20from=20remote=20'workflows/issue-pr/lab?= =?UTF-8?q?eler.yml'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/workflows/labeler.yml diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..bf2103b --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,12 @@ +name: "Pull Request Labeler" +on: +- pull_request_target + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v7.0.0 From aba0b5a96d400d5dd57b325f1b3cb63f5dd4d411 Mon Sep 17 00:00:00 2001 From: JesperDramsch Date: Wed, 2 Sep 2026 20:21:26 +0000 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=94=84=20created=20local=20'.github/l?= =?UTF-8?q?abeler.yml'=20from=20remote=20'configs/labeler.yml'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..aa9b60f --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,63 @@ +Tests: +- changed-files: + - any-glob-to-any-file: [ "tests/**/*" ] + +Dependencies: +- changed-files: + - any-glob-to-any-file: + - "requirements.txt" + - "setup.py" + - "pyproject.toml" + - "poetry.lock" + - "pixi.toml" + - "pixi.lock" + - "Pipfile" + - "Pipfile.lock" + - "requirements/*.txt" + - "requirements/*.in" + +Build: +- changed-files: + - any-glob-to-any-file: + - "Dockerfile*" + - "docker-compose*.yml" + - "Makefile" + +Documentation: +- changed-files: + - any-glob-to-any-file: + - "docs/**/*" + - "*.md" + - "*.rst" + +Config: +- changed-files: + - any-glob-to-any-file: + - ".pre-commit-config.yaml" + - "config/**/*" + - "settings/**/*" + - "*.ini" + - "*.cfg" + - "*.conf" + +CI/CD: +- changed-files: + - any-glob-to-any-file: + - ".github/**/*" + - "tox.ini" + - ".coveragerc" + +# Add 'feature' label to any PR where the head branch name starts with `feature` or has a `feature` section in the name +feature: +- head-branch: + - '^feature' + - 'feature' + +# Add 'bugfix' label to branches with bug/fix/hotfix prefixes +bugfix: +- head-branch: [ '^bug', '^fix', '^hotfix' ] + +# Add 'breaking' label for major version bumps or breaking change commits +breaking: +- title: [ '^BREAKING CHANGE', 'BREAKING-CHANGE' ] +- body: [ 'BREAKING CHANGE:', 'BREAKING-CHANGE:' ] From e254f27893b97a3fe991aad75d842d237bad8308 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 20:21:41 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/FUNDING.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/python-testing.yml | 2 - .github/workflows/test_python.yml | 2 +- .readthedocs.yml | 2 +- README.md | 18 ++--- complexnn/__init__.py | 57 +++++++------ complexnn/bn.py | 116 +++++++++++++-------------- complexnn/conv.py | 57 ++++++------- complexnn/dense.py | 21 ++--- complexnn/fft.py | 16 ++-- complexnn/init.py | 8 +- complexnn/norm.py | 22 ++--- complexnn/pool.py | 11 ++- complexnn/utils.py | 14 ++-- docs/requirements.txt | 2 +- docs/source/bib.bib | 14 ++-- docs/source/cite.md | 14 ++-- docs/source/contrib.md | 2 +- docs/source/intro.rst | 2 +- docs/source/license.rst | 2 +- docs/source/math.rst | 6 +- pyproject.toml | 2 +- requirements.txt | 2 +- tests/test_conv.py | 8 +- tests/test_dense.py | 4 - tests/test_readme.py | 6 +- tests/test_train.py | 13 +-- 28 files changed, 208 insertions(+), 219 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a44c1b8..4f04489 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: https://dramsch.net \ No newline at end of file +custom: https://dramsch.net diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b656d5e..5ffc8f5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,4 +15,4 @@ jobs: - name: Publish package uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 5a927a2..5e323db 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -34,5 +34,3 @@ jobs: with: file: ./coverage.xml fail_ci_if_error: true - - diff --git a/.github/workflows/test_python.yml b/.github/workflows/test_python.yml index 2523f07..9d6aadb 100644 --- a/.github/workflows/test_python.yml +++ b/.github/workflows/test_python.yml @@ -29,4 +29,4 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest \ No newline at end of file + pytest diff --git a/.readthedocs.yml b/.readthedocs.yml index b71a10c..ad4f353 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -25,4 +25,4 @@ formats: python: install: - - requirements: docs/requirements.txt \ No newline at end of file + - requirements: docs/requirements.txt diff --git a/README.md b/README.md index ece19be..e2f5109 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ pip install keras-complex Usage ----- -Build your neural networks with the help of keras. +Build your neural networks with the help of keras. -``` +``` import complexnn import keras @@ -76,17 +76,17 @@ Citation Find the [CITATION file](/CITATION.cff) or cite this software version as: ``` -@misc{dramsch2019complex, - title = {Complex-Valued Neural Networks in Keras with Tensorflow}, - url = {https://figshare.com/articles/Complex-Valued_Neural_Networks_in_Keras_with_Tensorflow/9783773/1}, - DOI = {10.6084/m9.figshare.9783773}, - publisher = {figshare}, - author = {Dramsch, Jesper S{\"o}ren and Contributors}, +@misc{dramsch2019complex, + title = {Complex-Valued Neural Networks in Keras with Tensorflow}, + url = {https://figshare.com/articles/Complex-Valued_Neural_Networks_in_Keras_with_Tensorflow/9783773/1}, + DOI = {10.6084/m9.figshare.9783773}, + publisher = {figshare}, + author = {Dramsch, Jesper S{\"o}ren and Contributors}, year = {2019} } ``` -Please cite the original work as: +Please cite the original work as: ``` @ARTICLE {Trabelsi2017, diff --git a/complexnn/__init__.py b/complexnn/__init__.py index c337296..2342910 100644 --- a/complexnn/__init__.py +++ b/complexnn/__init__.py @@ -1,34 +1,33 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- - -from . import bn, conv, dense, init, norm, pool - -# from . import fft +from . import bn as bn +from . import conv as conv +from . import dense as dense +from . import init as init +from . import norm as norm +from . import pool as pool from .bn import ComplexBatchNormalization as ComplexBN -from .conv import ( - ComplexConv, - ComplexConv1D, - ComplexConv2D, - ComplexConv3D, - WeightNorm_Conv, -) -from .dense import ComplexDense +from .conv import ComplexConv as ComplexConv +from .conv import ComplexConv1D as ComplexConv1D +from .conv import ComplexConv2D as ComplexConv2D +from .conv import ComplexConv3D as ComplexConv3D +from .conv import WeightNorm_Conv as WeightNorm_Conv +from .dense import ComplexDense as ComplexDense # from .fft import (fft, ifft, fft2, ifft2, FFT, IFFT, FFT2, IFFT2) -from .init import ( - ComplexIndependentFilters, - IndependentFilters, - ComplexInit, - SqrtInit, -) -from .norm import LayerNormalization, ComplexLayerNorm -from .pool import SpectralPooling1D, SpectralPooling2D -from .utils import ( - get_realpart, - get_imagpart, - getpart_output_shape, - GetImag, - GetReal, - GetAbs, -) +from .init import ComplexIndependentFilters as ComplexIndependentFilters +from .init import ComplexInit as ComplexInit +from .init import IndependentFilters as IndependentFilters +from .init import SqrtInit as SqrtInit +from .norm import ComplexLayerNorm as ComplexLayerNorm +from .norm import LayerNormalization as LayerNormalization +from .pool import SpectralPooling1D as SpectralPooling1D +from .pool import SpectralPooling2D as SpectralPooling2D +from .utils import GetAbs as GetAbs +from .utils import GetImag as GetImag +from .utils import GetReal as GetReal +from .utils import get_imagpart as get_imagpart +from .utils import get_realpart as get_realpart +from .utils import getpart_output_shape as getpart_output_shape + +# from . import fft diff --git a/complexnn/bn.py b/complexnn/bn.py index 090ee45..c3b9f5b 100644 --- a/complexnn/bn.py +++ b/complexnn/bn.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # Note: The implementation of complex Batchnorm is based on # the Keras implementation of batch Normalization @@ -7,9 +6,12 @@ # https://github.com/fchollet/keras/blob/master/keras/layers/normalization.py import numpy as np -from tensorflow.keras.layers import Layer, InputSpec -from tensorflow.keras import initializers, regularizers, constraints import tensorflow.keras.backend as K +from tensorflow.keras import constraints +from tensorflow.keras import initializers +from tensorflow.keras import regularizers +from tensorflow.keras.layers import InputSpec +from tensorflow.keras.layers import Layer def sqrt_init(shape, dtype=None): @@ -18,17 +20,15 @@ def sqrt_init(shape, dtype=None): def sanitizedInitGet(init): - if init in ["sqrt_init"]: + if init == "sqrt_init": return sqrt_init - else: - return initializers.get(init) + return initializers.get(init) def sanitizedInitSer(init): - if init in [sqrt_init]: + if init == sqrt_init: return "sqrt_init" - else: - return initializers.serialize(init) + return initializers.serialize(init) def complex_standardization(input_centred, Vrr, Vii, Vri, layernorm=False, axis=-1): @@ -44,13 +44,14 @@ def complex_standardization(input_centred, Vrr, Vii, Vri, layernorm=False, axis= layernorm {bool} -- Normalization (default: {False}) axis {int} -- Axis for Standardization (default: {-1}) - Raises: + Raises + ------ ValueError: Mismatched dimensoins - Returns: + Returns + ------- Complex standardized input """ - ndim = K.ndim(input_centred) input_dim = K.shape(input_centred)[axis] // 2 variances_broadcast = [1] * ndim @@ -124,7 +125,7 @@ def complex_standardization(input_centred, Vrr, Vii, Vri, layernorm=False, axis= raise ValueError( "Incorrect Batchnorm combination of axis and dimensions. axis " "should be either 1 or -1. " - "axis: " + str(axis) + "; ndim: " + str(ndim) + "." + "axis: " + str(axis) + "; ndim: " + str(ndim) + ".", ) rolled_input = K.concatenate([centred_imag, centred_real], axis=axis) @@ -139,7 +140,7 @@ def complex_standardization(input_centred, Vrr, Vii, Vri, layernorm=False, axis= def ComplexBN( - input_centred, Vrr, Vii, Vri, beta, gamma_rr, gamma_ri, gamma_ii, scale=True, center=True, layernorm=False, axis=-1 + input_centred, Vrr, Vii, Vri, beta, gamma_rr, gamma_ri, gamma_ii, scale=True, center=True, layernorm=False, axis=-1, ): """Complex Batch Normalization @@ -159,13 +160,14 @@ def ComplexBN( layernorm {bool} -- Normalization (default: {False}) axis {int} -- Axis for Standardization (default: {-1}) - Raises: + Raises + ------ ValueError: Dimonsional mismatch - Returns: + Returns + ------- Batch-Normalized Input """ - ndim = K.ndim(input_centred) input_dim = K.shape(input_centred)[axis] // 2 if scale: @@ -212,7 +214,7 @@ def ComplexBN( raise ValueError( "Incorrect Batchnorm combination of axis and dimensions. axis" " should be either 1 or -1. " - "axis: " + str(axis) + "; ndim: " + str(ndim) + "." + "axis: " + str(axis) + "; ndim: " + str(ndim) + ".", ) rolled_standardized_output = K.concatenate([centred_imag, centred_real], axis=axis) if center: @@ -220,14 +222,11 @@ def ComplexBN( return ( cat_gamma_4_real * standardized_output + cat_gamma_4_imag * rolled_standardized_output + broadcast_beta ) - else: - return cat_gamma_4_real * standardized_output + cat_gamma_4_imag * rolled_standardized_output - else: - if center: - broadcast_beta = K.reshape(beta, broadcast_beta_shape) - return input_centred + broadcast_beta - else: - return input_centred + return cat_gamma_4_real * standardized_output + cat_gamma_4_imag * rolled_standardized_output + if center: + broadcast_beta = K.reshape(beta, broadcast_beta_shape) + return input_centred + broadcast_beta + return input_centred class ComplexBatchNormalization(Layer): @@ -294,7 +293,7 @@ def __init__( beta_constraint=None, gamma_diag_constraint=None, gamma_off_constraint=None, - **kwargs + **kwargs, ): super(ComplexBatchNormalization, self).__init__(**kwargs) self.supports_masking = True @@ -325,7 +324,7 @@ def build(self, input_shape): raise ValueError( "Axis " + str(self.axis) + " of " "input tensor should have a defined dimension " - "but the layer received an input with shape " + str(input_shape) + "." + "but the layer received an input with shape " + str(input_shape) + ".", ) self.input_spec = InputSpec(ndim=len(input_shape), axes={self.axis: dim}) @@ -354,13 +353,13 @@ def build(self, input_shape): constraint=self.gamma_off_constraint, ) self.moving_Vrr = self.add_weight( - shape=param_shape, initializer=self.moving_variance_initializer, name="moving_Vrr", trainable=False + shape=param_shape, initializer=self.moving_variance_initializer, name="moving_Vrr", trainable=False, ) self.moving_Vii = self.add_weight( - shape=param_shape, initializer=self.moving_variance_initializer, name="moving_Vii", trainable=False + shape=param_shape, initializer=self.moving_variance_initializer, name="moving_Vii", trainable=False, ) self.moving_Vri = self.add_weight( - shape=param_shape, initializer=self.moving_covariance_initializer, name="moving_Vri", trainable=False + shape=param_shape, initializer=self.moving_covariance_initializer, name="moving_Vri", trainable=False, ) else: self.gamma_rr = None @@ -428,7 +427,7 @@ def call(self, inputs, training=None): else: raise ValueError( "Incorrect Batchnorm combination of axis and dimensions. axis should be either 1 or -1. " - "axis: " + str(self.axis) + "; ndim: " + str(ndim) + "." + "axis: " + str(self.axis) + "; ndim: " + str(ndim) + ".", ) if self.scale: Vrr = K.mean(centred_squared_real, axis=reduction_axes) + self.epsilon @@ -460,34 +459,33 @@ def call(self, inputs, training=None): ) if training in {0, False}: return input_bn - else: - update_list = [] + update_list = [] + if self.center: + update_list.append(K.moving_average_update(self.moving_mean, mu, self.momentum)) + if self.scale: + update_list.append(K.moving_average_update(self.moving_Vrr, Vrr, self.momentum)) + update_list.append(K.moving_average_update(self.moving_Vii, Vii, self.momentum)) + update_list.append(K.moving_average_update(self.moving_Vri, Vri, self.momentum)) + self.add_update(update_list) + + def normalize_inference(): if self.center: - update_list.append(K.moving_average_update(self.moving_mean, mu, self.momentum)) - if self.scale: - update_list.append(K.moving_average_update(self.moving_Vrr, Vrr, self.momentum)) - update_list.append(K.moving_average_update(self.moving_Vii, Vii, self.momentum)) - update_list.append(K.moving_average_update(self.moving_Vri, Vri, self.momentum)) - self.add_update(update_list) - - def normalize_inference(): - if self.center: - inference_centred = inputs - K.reshape(self.moving_mean, broadcast_mu_shape) - else: - inference_centred = inputs - return ComplexBN( - inference_centred, - self.moving_Vrr, - self.moving_Vii, - self.moving_Vri, - self.beta, - self.gamma_rr, - self.gamma_ri, - self.gamma_ii, - self.scale, - self.center, - axis=self.axis, - ) + inference_centred = inputs - K.reshape(self.moving_mean, broadcast_mu_shape) + else: + inference_centred = inputs + return ComplexBN( + inference_centred, + self.moving_Vrr, + self.moving_Vii, + self.moving_Vri, + self.beta, + self.gamma_rr, + self.gamma_ri, + self.gamma_ii, + self.scale, + self.center, + axis=self.axis, + ) # Pick the normalized form corresponding to the training phase. return K.in_train_phase(input_bn, normalize_inference, training=training) diff --git a/complexnn/conv.py b/complexnn/conv.py index 11bf21f..89cee08 100644 --- a/complexnn/conv.py +++ b/complexnn/conv.py @@ -1,20 +1,23 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- +import numpy as np import tensorflow as tf +from tensorflow.keras import activations from tensorflow.keras import backend as K -from tensorflow.keras import activations, initializers, regularizers, constraints -from tensorflow.keras.layers import ( - Layer, - InputSpec, -) +from tensorflow.keras import constraints +from tensorflow.keras import initializers +from tensorflow.keras import regularizers +from tensorflow.keras.layers import InputSpec +from tensorflow.keras.layers import Layer from tensorflow.python.keras.layers.convolutional import Conv from tensorflow.python.keras.utils import conv_utils -import numpy as np -from .fft import fft, ifft, fft2, ifft2 + from .bn import ComplexBN as complex_normalization from .bn import sqrt_init -from .init import ComplexInit, ComplexIndependentFilters +from .fft import ifft +from .fft import ifft2 +from .init import ComplexIndependentFilters +from .init import ComplexInit def conv1d_transpose( @@ -30,7 +33,8 @@ def conv1d_transpose( """Compatibility layer for nn.conv1d_transpose Take a filter defined for forward convolution and adjusts it for a - transposed convolution.""" + transposed convolution. + """ if isinstance(kernel_size, tuple): kernel_size = kernel_size[0] input_shape = inputs.shape @@ -82,7 +86,8 @@ def conv2d_transpose( """Compatibility layer for K.conv2d_transpose Take a filter defined for forward convolution and adjusts it for a - transposed convolution.""" + transposed convolution. + """ input_shape = inputs.shape batch_size = input_shape[0] if data_format == "channels_first": @@ -132,7 +137,7 @@ def ifft2(f): def conv_transpose_output_length(input_length, filter_size, padding, stride, dilation=1, output_padding=None): """Rearrange arguments for compatibility with conv_output_length.""" if dilation != 1: - msg = f"Dilation must be 1 for transposed convolution. " + msg = "Dilation must be 1 for transposed convolution. " msg += f"Got dilation = {dilation}" raise ValueError(msg) # return conv_utils.deconv_length( @@ -153,25 +158,23 @@ def conv_transpose_output_length(input_length, filter_size, padding, stride, dil def sanitizedInitGet(init): - """sanitizedInitGet""" - if init in ["sqrt_init"]: + """SanitizedInitGet""" + if init == "sqrt_init": return sqrt_init - elif init in ["complex", "complex_independent", "glorot_complex", "he_complex"]: + if init in ["complex", "complex_independent", "glorot_complex", "he_complex"]: return init - else: - return initializers.get(init) + return initializers.get(init) def sanitizedInitSer(init): - """sanitizedInitSer""" - if init in [sqrt_init]: + """SanitizedInitSer""" + if init == sqrt_init: return "sqrt_init" - elif init == "complex" or isinstance(init, ComplexInit): + if init == "complex" or isinstance(init, ComplexInit): return "complex" - elif init == "complex_independent" or isinstance(init, ComplexIndependentFilters): + if init == "complex_independent" or isinstance(init, ComplexIndependentFilters): return "complex_independent" - else: - return initializers.serialize(init) + return initializers.serialize(init) class ComplexConv(Layer): @@ -315,13 +318,13 @@ def __init__( self.bias = None def build(self, input_shape): - """build""" + """Build""" if self.data_format == "channels_first": channel_axis = 1 else: channel_axis = -1 if input_shape[channel_axis] is None: - raise ValueError("The channel dimension of the inputs " "should be defined. Found `None`.") + raise ValueError("The channel dimension of the inputs should be defined. Found `None`.") # Divide by 2 for real and complex input. input_dim = input_shape[channel_axis] // 2 if False and self.transposed: @@ -1083,7 +1086,7 @@ def build(self, input_shape): else: channel_axis = -1 if input_shape[channel_axis] is None: - raise ValueError("The channel dimension of the inputs " "should be defined. Found `None`.") + raise ValueError("The channel dimension of the inputs should be defined. Found `None`.") input_dim = input_shape[channel_axis] gamma_shape = (input_dim * self.filters,) self.gamma = self.add_weight( @@ -1101,7 +1104,7 @@ def call(self, inputs): else: channel_axis = -1 if input_shape[channel_axis] is None: - raise ValueError("The channel dimension of the inputs " "should be defined. Found `None`.") + raise ValueError("The channel dimension of the inputs should be defined. Found `None`.") input_dim = input_shape[channel_axis] ker_shape = self.kernel_size + (input_dim, self.filters) nb_kernels = ker_shape[-2] * ker_shape[-1] diff --git a/complexnn/dense.py b/complexnn/dense.py index 9b31dc0..dc24849 100644 --- a/complexnn/dense.py +++ b/complexnn/dense.py @@ -1,12 +1,15 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from tensorflow.keras import backend as K -from tensorflow.keras import backend as K -from tensorflow.keras import activations, initializers, regularizers, constraints -from tensorflow.keras.layers import Layer, InputSpec import numpy as np from numpy.random import RandomState +from tensorflow.keras import activations +from tensorflow.keras import backend as K +from tensorflow.keras import constraints +from tensorflow.keras import initializers +from tensorflow.keras import regularizers +from tensorflow.keras.layers import InputSpec +from tensorflow.keras.layers import Layer + from .utils import _compute_fans @@ -69,7 +72,7 @@ def __init__( kernel_constraint=None, bias_constraint=None, seed=None, - **kwargs + **kwargs, ): if "input_shape" not in kwargs and "input_dim" in kwargs: kwargs["input_shape"] = (kwargs.pop("input_dim"),) @@ -78,7 +81,7 @@ def __init__( self.activation = activations.get(activation) self.use_bias = use_bias self.init_criterion = init_criterion - if kernel_initializer in {"complex"}: + if kernel_initializer == "complex": self.kernel_initializer = kernel_initializer else: self.kernel_initializer = initializers.get(kernel_initializer) @@ -127,7 +130,7 @@ def init_w_real(shape, dtype=None): def init_w_imag(shape, dtype=None): return rng.normal(size=kernel_shape, loc=0, scale=s) # .astype(dtype) - if self.kernel_initializer in {"complex"}: + if self.kernel_initializer == "complex": real_init = init_w_real imag_init = init_w_imag else: @@ -190,7 +193,7 @@ def compute_output_shape(self, input_shape): return tuple(output_shape) def get_config(self): - if self.kernel_initializer in {"complex"}: + if self.kernel_initializer == "complex": ki = self.kernel_initializer else: ki = initializers.serialize(self.kernel_initializer) diff --git a/complexnn/fft.py b/complexnn/fft.py index 867b7d8..b57002f 100644 --- a/complexnn/fft.py +++ b/complexnn/fft.py @@ -1,13 +1,11 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +import numpy as np +import tensorflow as tf # import tensorflow.keras.engine as KE import tensorflow.keras.backend as KB import tensorflow.keras.layers as KL -import tensorflow.keras.optimizers as KO -import tensorflow as tf -import numpy as np - # # FFT functions: @@ -26,10 +24,10 @@ def fft(z): Zr, Zi = tf.signal.rfft(z[:B]), tf.signal.rfft(z[B:]) isOdd = tf.equal(L % 2, 1) Zr = tf.cond( - isOdd, tf.concat([Zr, C * Zr[:, 1:][:, ::-1]], axis=1), tf.concat([Zr, C * Zr[:, 1:-1][:, ::-1]], axis=1) + isOdd, tf.concat([Zr, C * Zr[:, 1:][:, ::-1]], axis=1), tf.concat([Zr, C * Zr[:, 1:-1][:, ::-1]], axis=1), ) Zi = tf.cond( - isOdd, tf.concat([Zi, C * Zi[:, 1:][:, ::-1]], axis=1), tf.concat([Zi, C * Zi[:, 1:-1][:, ::-1]], axis=1) + isOdd, tf.concat([Zi, C * Zi[:, 1:][:, ::-1]], axis=1), tf.concat([Zi, C * Zi[:, 1:-1][:, ::-1]], axis=1), ) Zi = (C * Zi)[:, :, ::-1] # Zi * i Z = Zr + Zi @@ -43,10 +41,10 @@ def ifft(z): Zr, Zi = tf.signal.rfft(z[:B]), tf.signal.rfft(z[B:] * -1) isOdd = tf.equal(L % 2, 1) Zr = tf.cond( - isOdd, tf.concat([Zr, C * Zr[:, 1:][:, ::-1]], axis=1), tf.concat([Zr, C * Zr[:, 1:-1][:, ::-1]], axis=1) + isOdd, tf.concat([Zr, C * Zr[:, 1:][:, ::-1]], axis=1), tf.concat([Zr, C * Zr[:, 1:-1][:, ::-1]], axis=1), ) Zi = tf.cond( - isOdd, tf.concat([Zi, C * Zi[:, 1:][:, ::-1]], axis=1), tf.concat([Zi, C * Zi[:, 1:-1][:, ::-1]], axis=1) + isOdd, tf.concat([Zi, C * Zi[:, 1:][:, ::-1]], axis=1), tf.concat([Zi, C * Zi[:, 1:-1][:, ::-1]], axis=1), ) Zi = (C * Zi)[:, :, ::-1] # Zi * i Z = Zr + Zi diff --git a/complexnn/init.py b/complexnn/init.py index 480f41a..bb62ce0 100644 --- a/complexnn/init.py +++ b/complexnn/init.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import numpy as np -from numpy.random import RandomState import tensorflow.keras.backend as K +from numpy.random import RandomState from tensorflow.keras.initializers import Initializer -from tensorflow.python.keras.utils.generic_utils import serialize_keras_object, deserialize_keras_object + from .utils import _compute_fans @@ -67,7 +66,6 @@ def __call__(self, shape, dtype=None): multip_constant = np.sqrt(desired_var / np.var(independent_filters)) scaled_indep = multip_constant * independent_filters - if self.weight_dim == 2 and self.nb_filters is None: weight = scaled_indep else: @@ -226,7 +224,7 @@ def __call__(self, shape, dtype=None): fan_in, fan_out = _compute_fans( # tuple(self.kernel_size) + (self.input_dim, self.nb_filters) - kernel_shape + kernel_shape, ) # fix for ValueError: The initial value's shape (...) is not compatible with the explicitly supplied `shape` argument diff --git a/complexnn/norm.py b/complexnn/norm.py index 2806083..651b3f6 100644 --- a/complexnn/norm.py +++ b/complexnn/norm.py @@ -1,13 +1,15 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # Implementation of Layer Normalization and Complex Layer Normalization -import numpy as np -from tensorflow.keras.layers import Layer, InputSpec -from tensorflow.keras import initializers, regularizers, constraints import tensorflow.keras.backend as K +from tensorflow.keras import constraints +from tensorflow.keras import initializers +from tensorflow.keras import regularizers +from tensorflow.keras.layers import InputSpec +from tensorflow.keras.layers import Layer + from .bn import ComplexBN as complex_normalization from .bn import sqrt_init @@ -53,7 +55,7 @@ def __init__( gamma_init="ones", gamma_regularizer=None, beta_regularizer=None, - **kwargs + **kwargs, ): self.supports_masking = True @@ -71,10 +73,10 @@ def build(self, input_shape): shape = (input_shape[self.axis],) self.gamma = self.add_weight( - shape, initializer=self.gamma_init, regularizer=self.gamma_regularizer, name="{}_gamma".format(self.name) + shape, initializer=self.gamma_init, regularizer=self.gamma_regularizer, name=f"{self.name}_gamma", ) self.beta = self.add_weight( - shape, initializer=self.beta_init, regularizer=self.beta_regularizer, name="{}_beta".format(self.name) + shape, initializer=self.beta_init, regularizer=self.beta_regularizer, name=f"{self.name}_beta", ) self.built = True @@ -110,7 +112,7 @@ def __init__( beta_constraint=None, gamma_diag_constraint=None, gamma_off_constraint=None, - **kwargs + **kwargs, ): self.supports_masking = True @@ -137,7 +139,7 @@ def build(self, input_shape): raise ValueError( "Axis " + str(self.axis) + " of " "input tensor should have a defined dimension " - "but the layer received an input with shape " + str(input_shape) + "." + "but the layer received an input with shape " + str(input_shape) + ".", ) self.input_spec = InputSpec(ndim=len(input_shape), axes={self.axis: dim}) @@ -222,7 +224,7 @@ def call(self, inputs, **kwargs): else: raise ValueError( "Incorrect Layernorm combination of axis and dimensions. axis should be either 1 or -1. " - "axis: " + str(self.axis) + "; ndim: " + str(ndim) + "." + "axis: " + str(self.axis) + "; ndim: " + str(ndim) + ".", ) if self.scale: Vrr = K.mean(centred_squared_real, axis=reduction_axes) + self.epsilon diff --git a/complexnn/pool.py b/complexnn/pool.py index e07405f..dbbc97a 100644 --- a/complexnn/pool.py +++ b/complexnn/pool.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -import tensorflow.keras.backend as KB +import numpy as np import tensorflow.keras as KE +import tensorflow.keras.backend as KB import tensorflow.keras.layers as KL -import tensorflow.keras.optimizers as KO -import numpy as np - # # Spectral Pooling Layer @@ -106,8 +103,10 @@ def call(self, x, mask=None): if __name__ == "__main__": - import cv2, sys + import sys + import __main__ as SP + import cv2 import fft as CF # Build Model diff --git a/complexnn/utils.py b/complexnn/utils.py index 55290c7..bd348b3 100644 --- a/complexnn/utils.py +++ b/complexnn/utils.py @@ -1,10 +1,8 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -import tensorflow.keras.backend as K -from tensorflow.keras.layers import Layer, Lambda import numpy as np - +import tensorflow.keras.backend as K +from tensorflow.keras.layers import Layer # # GetReal/GetImag Lambda layer Implementation @@ -23,9 +21,9 @@ def get_realpart(x): input_dim = input_shape[-1] // 2 if ndim == 3: return x[:, :, :input_dim] - elif ndim == 4: + if ndim == 4: return x[:, :, :, :input_dim] - elif ndim == 5: + if ndim == 5: return x[:, :, :, :, :input_dim] @@ -41,9 +39,9 @@ def get_imagpart(x): input_dim = input_shape[-1] // 2 if ndim == 3: return x[:, :, input_dim:] - elif ndim == 4: + if ndim == 4: return x[:, :, :, input_dim:] - elif ndim == 5: + if ndim == 5: return x[:, :, :, :, input_dim:] diff --git a/docs/requirements.txt b/docs/requirements.txt index 17c7bcb..fab01b4 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ tensorflow sphinx>=1.8 sphinxcontrib-apidoc -sphinxcontrib-bibtex \ No newline at end of file +sphinxcontrib-bibtex diff --git a/docs/source/bib.bib b/docs/source/bib.bib index 4530410..a94bcec 100644 --- a/docs/source/bib.bib +++ b/docs/source/bib.bib @@ -5,12 +5,12 @@ @article{trabelsi2017deep year={2017} } -@misc{dramsch2019complexsoftware, - title = {Complex-Valued Neural Networks in Keras with Tensorflow}, - url = {https://figshare.com/articles/Complex-Valued_Neural_Networks_in_Keras_with_Tensorflow/9783773/1}, - DOI = {10.6084/m9.figshare.9783773}, - publisher = {figshare}, - author = {Dramsch, Jesper Soeren and Contributors}, +@misc{dramsch2019complexsoftware, + title = {Complex-Valued Neural Networks in Keras with Tensorflow}, + url = {https://figshare.com/articles/Complex-Valued_Neural_Networks_in_Keras_with_Tensorflow/9783773/1}, + DOI = {10.6084/m9.figshare.9783773}, + publisher = {figshare}, + author = {Dramsch, Jesper Soeren and Contributors}, year = {2019} } @@ -43,4 +43,4 @@ @article{Hirose2012 author={Hirose, Akira and Yoshida, Shotaro}, title = {Generalization Characteristics of Complex-Valued Feedforward Neural Networks in Relation to Signal Coherence}, journal = {IEEE Transactions on Neural Networks and Learning Systems} -} \ No newline at end of file +} diff --git a/docs/source/cite.md b/docs/source/cite.md index a4bd4d7..6043144 100644 --- a/docs/source/cite.md +++ b/docs/source/cite.md @@ -4,17 +4,17 @@ Citation Find the CITATION file called CITATION.cff on Github or cite this software version as: ``` -@misc{dramsch2019complex, - title = {Complex-Valued Neural Networks in Keras with Tensorflow}, - url = {https://figshare.com/articles/Complex-Valued_Neural_Networks_in_Keras_with_Tensorflow/9783773/1}, - DOI = {10.6084/m9.figshare.9783773}, - publisher = {figshare}, - author = {Dramsch, Jesper S{\"o}ren and Contributors}, +@misc{dramsch2019complex, + title = {Complex-Valued Neural Networks in Keras with Tensorflow}, + url = {https://figshare.com/articles/Complex-Valued_Neural_Networks_in_Keras_with_Tensorflow/9783773/1}, + DOI = {10.6084/m9.figshare.9783773}, + publisher = {figshare}, + author = {Dramsch, Jesper S{\"o}ren and Contributors}, year = {2019} } ``` -Please cite the original work as: +Please cite the original work as: ``` @ARTICLE {Trabelsi2017, diff --git a/docs/source/contrib.md b/docs/source/contrib.md index fcd0cde..73dea1e 100644 --- a/docs/source/contrib.md +++ b/docs/source/contrib.md @@ -13,4 +13,4 @@ These tests should run with `pytest`. Documentation ------------- -New features should be documented in the docs/ folder, which will be automatically generated on readthedocs.org. \ No newline at end of file +New features should be documented in the docs/ folder, which will be automatically generated on readthedocs.org. diff --git a/docs/source/intro.rst b/docs/source/intro.rst index 723faa0..a76a9cd 100644 --- a/docs/source/intro.rst +++ b/docs/source/intro.rst @@ -1,4 +1,4 @@ Introduction ============ -Complex-valued convolutions could provide some interesting results in signal processing-based deep learning. A simple(-ish) idea is including explicit phase information of time series in neural networks. This code enables complex-valued convolution in convolutional neural networks in keras with the TensorFlow backend. This makes the network modular and interoperable with standard keras layers and operations. \ No newline at end of file +Complex-valued convolutions could provide some interesting results in signal processing-based deep learning. A simple(-ish) idea is including explicit phase information of time series in neural networks. This code enables complex-valued convolution in convolutional neural networks in keras with the TensorFlow backend. This makes the network modular and interoperable with standard keras layers and operations. diff --git a/docs/source/license.rst b/docs/source/license.rst index bebfb67..c8571f0 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -1,4 +1,4 @@ License ======= -.. include:: ../../LICENSE.md \ No newline at end of file +.. include:: ../../LICENSE.md diff --git a/docs/source/math.rst b/docs/source/math.rst index c4c8ed6..265d1ea 100644 --- a/docs/source/math.rst +++ b/docs/source/math.rst @@ -1,6 +1,6 @@ Implementation and Math ========================= -Complex convolutional networks provide the benefit of explicitly modelling the phase space of physical systems :cite:`trabelsi2017deep`. +Complex convolutional networks provide the benefit of explicitly modelling the phase space of physical systems :cite:`trabelsi2017deep`. The complex convolution introduced can be explicitly implemented as convolutions of the real and complex components of both kernels and the data. A complex-valued data matrix in cartesian notation is defined as :math:`\textbf{M} = M_\Re + i M_\Im` and equally, the complex-valued convolutional kernel is defined as :math:`\textbf{K} = K_\Re + i K_\Im`. The individual coefficients :math:`(M_\Re, M_\Im, K_\Re, K_\Im)` are real-valued matrices, considering vectors are special cases of matrices with one of two dimensions being one. @@ -45,7 +45,7 @@ where K is the Kernel and M is a data vector. Considerations ----------------- -Complex convolutional neural networks learn by back-propagation. +Complex convolutional neural networks learn by back-propagation. :cite:`Sarroff2015` state that the activation functions, as well as the loss function must be complex differentiable (holomorphic). :cite:`trabelsi2017deep` suggest that employing complex losses and activation functions is valid for speed, however, refers that :cite:`Hirose2012` show that complex-valued networks can be optimized individually with real-valued loss functions and contain piecewise real-valued activations. We reimplement the code :cite:`trabelsi2017deep` provides in keras with tensorflow , which provides convenience functions implementing a multitude of real-valued loss functions and activations. @@ -53,4 +53,4 @@ We reimplement the code :cite:`trabelsi2017deep` provides in keras with tensorfl [CC-BY :cite:`dramsch2019complex`] .. bibliography:: bib.bib - :cited: \ No newline at end of file + :cited: diff --git a/pyproject.toml b/pyproject.toml index cc62436..84daf9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,4 +46,4 @@ changelog = "https://github.com/JesperDramsch/keras-complex/releases" bugtracker = "https://github.com/JesperDramsch/keras-complex/issues" [tool.setuptools.packages.find] -exclude = ["tests*", "docs*"] +exclude = ["tests*", "docs*"] diff --git a/requirements.txt b/requirements.txt index 31a6c3b..d75d4e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ numpy scikit-learn scipy -tensorflow \ No newline at end of file +tensorflow diff --git a/tests/test_conv.py b/tests/test_conv.py index fd012f1..f8180f5 100644 --- a/tests/test_conv.py +++ b/tests/test_conv.py @@ -1,10 +1,8 @@ import unittest -from tensorflow.keras.layers import Input, MaxPooling2D, Dense -from tensorflow.keras.models import Model, Sequential -import tensorflow as tf +from tensorflow.keras.layers import Input +from tensorflow.keras.models import Model -import numpy as np import complexnn as conn @@ -40,7 +38,7 @@ def test_conv2Dtranspose(self): """Test shape of model output, transposed""" inputs = Input(shape=(64, 64, 20)) # = 10 CDN filters outputs = conn.ComplexConv2D( - filters=2, kernel_size=3, strides=2, padding="same", transposed=True # = 4 Keras filters + filters=2, kernel_size=3, strides=2, padding="same", transposed=True, # = 4 Keras filters )(inputs) model = Model(inputs=inputs, outputs=outputs) true = (None, 128, 128, 4) diff --git a/tests/test_dense.py b/tests/test_dense.py index 4f8364c..f1986c9 100644 --- a/tests/test_dense.py +++ b/tests/test_dense.py @@ -1,10 +1,6 @@ import unittest -from tensorflow.keras.layers import Input, MaxPooling2D, Dense -from tensorflow.keras.models import Model, Sequential -import tensorflow as tf -import numpy as np import complexnn as conn diff --git a/tests/test_readme.py b/tests/test_readme.py index 795e274..6fdcc11 100644 --- a/tests/test_readme.py +++ b/tests/test_readme.py @@ -1,10 +1,8 @@ import unittest -from tensorflow.keras.layers import Input, MaxPooling2D, Dense -from tensorflow.keras.models import Model, Sequential import tensorflow as tf +from tensorflow.keras.layers import MaxPooling2D -import numpy as np import complexnn as conn @@ -18,4 +16,4 @@ def test_github_example(self): model.add(conn.bn.ComplexBatchNormalization()) model.add(MaxPooling2D((2, 2), padding="same")) model.compile(optimizer=tf.keras.optimizers.Adam(), loss="mse") - model.summary() \ No newline at end of file + model.summary() diff --git a/tests/test_train.py b/tests/test_train.py index 2bb5940..93d4aba 100644 --- a/tests/test_train.py +++ b/tests/test_train.py @@ -1,10 +1,11 @@ import unittest -from tensorflow.keras.layers import Input, MaxPooling2D, Dense -from tensorflow.keras.models import Model, Sequential -import tensorflow as tf - import numpy as np +from tensorflow.keras.layers import Dense +from tensorflow.keras.layers import Input +from tensorflow.keras.models import Model +from tensorflow.keras.models import Sequential + import complexnn as conn @@ -20,10 +21,10 @@ def test_train_transpose(self): Y = X inputs = Input(shape=(64, 64, 2)) conv1 = conn.ComplexConv2D( - filters=2, kernel_size=3, strides=2, padding="same", transposed=False # = 4 Keras filters + filters=2, kernel_size=3, strides=2, padding="same", transposed=False, # = 4 Keras filters )(inputs) outputs = conn.ComplexConv2D( - filters=1, kernel_size=3, strides=2, padding="same", transposed=True # = 2 Keras filters => 1 complex layer + filters=1, kernel_size=3, strides=2, padding="same", transposed=True, # = 2 Keras filters => 1 complex layer )(conv1) model = Model(inputs=inputs, outputs=outputs) model.compile(optimizer="adam", loss="mean_squared_error", metrics=["accuracy"])