Compare commits
51 Commits
v0.1.8
...
feature/ux
Author | SHA1 | Date | |
---|---|---|---|
|
5ce326ce62 | ||
|
d1985571b3 | ||
|
967953442b | ||
|
d4448f2bc9 | ||
|
a8829945f5 | ||
|
a8336ee213 | ||
|
6ffd27d12a | ||
|
859e2cae69 | ||
|
d7ea89d47e | ||
|
fa928afe2c | ||
|
7d4a041df2 | ||
|
04c51c00c6 | ||
|
62185b38cf | ||
|
7b93cd4ad5 | ||
|
d7834e2cc0 | ||
|
0af8cf36f8 | ||
|
f8ad1d83eb | ||
|
23a3683860 | ||
|
4be9fb81eb | ||
|
9d38123114 | ||
|
0f9f24e36a | ||
|
09e3ef1d0e | ||
|
7b9b767113 | ||
|
f56ec44afe | ||
|
67a20124e8 | ||
|
72af03b991 | ||
|
71602bf38a | ||
|
a1d9657b91 | ||
|
4dc11a3737 | ||
|
2649e3ac31 | ||
|
2b2e4a5f37 | ||
|
72404f7c4e | ||
|
612ee8dc6a | ||
|
d42693a441 | ||
|
7eb496110f | ||
|
4ab0a5a414 | ||
|
29063dcec4 | ||
|
a37095409b | ||
|
1b420c1f6b | ||
|
7ec5528ade | ||
|
a44219ee47 | ||
|
24ebfdc667 | ||
|
1c658cdc1b | ||
|
1911d4b33e | ||
|
6197d7d5d6 | ||
|
d2856383e2 | ||
|
4eafe88dc4 | ||
|
3afced8662 | ||
|
68034d56f6 | ||
|
97ec15b76a | ||
|
69e5ff3243 |
@@ -1,10 +1,13 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.1.8
|
current_version = 0.3.0
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||||
serialize = {major}.{minor}.{patch}
|
serialize = {major}.{minor}.{patch}
|
||||||
|
message = build: bump version {current_version} → {new_version}
|
||||||
|
|
||||||
[bumpversion:file:setup.py]
|
[bumpversion:file:setup.py]
|
||||||
|
|
||||||
[bumpversion:file:./prototorch/models/__init__.py]
|
[bumpversion:file:./prototorch/models/__init__.py]
|
||||||
|
|
||||||
|
[bumpversion:file:./docs/source/conf.py]
|
||||||
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**Steps to reproduce the behavior**
|
||||||
|
1. ...
|
||||||
|
2. Run script '...' or this snippet:
|
||||||
|
```python
|
||||||
|
import prototorch as pt
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
3. See errors
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Observed behavior**
|
||||||
|
A clear and concise description of what actually happened.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**System and version information**
|
||||||
|
- OS: [e.g. Ubuntu 20.10]
|
||||||
|
- ProtoTorch Version: [e.g. 0.4.0]
|
||||||
|
- Python Version: [e.g. 3.9.5]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
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.
|
17
.gitignore
vendored
17
.gitignore
vendored
@@ -128,14 +128,19 @@ dmypy.json
|
|||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# Datasets
|
|
||||||
datasets/
|
|
||||||
|
|
||||||
# PyTorch-Lightning
|
|
||||||
lightning_logs/
|
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# Vim
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
# Pytorch Models or Weights
|
# Pytorch Models or Weights
|
||||||
# If necessary make exceptions for single pretrained models
|
# If necessary make exceptions for single pretrained models
|
||||||
*.pt
|
*.pt
|
||||||
|
|
||||||
|
# Artifacts created by ProtoTorch Models
|
||||||
|
datasets/
|
||||||
|
lightning_logs/
|
||||||
|
examples/_*.py
|
||||||
|
examples/_*.ipynb
|
||||||
|
@@ -1,54 +1,54 @@
|
|||||||
# See https://pre-commit.com for more information
|
# See https://pre-commit.com for more information
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v4.0.1
|
|
||||||
hooks:
|
|
||||||
- id: trailing-whitespace
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
- id: check-yaml
|
|
||||||
- id: check-added-large-files
|
|
||||||
- id: check-ast
|
|
||||||
- id: check-case-conflict
|
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.0.1
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-case-conflict
|
||||||
|
|
||||||
- repo: https://github.com/myint/autoflake
|
- repo: https://github.com/myint/autoflake
|
||||||
rev: v1.4
|
rev: v1.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: autoflake
|
- id: autoflake
|
||||||
|
|
||||||
- repo: http://github.com/PyCQA/isort
|
- repo: http://github.com/PyCQA/isort
|
||||||
rev: 5.8.0
|
rev: 5.9.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: 'v0.902'
|
rev: v0.910-1
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: prototorch
|
files: prototorch
|
||||||
additional_dependencies: [types-pkg_resources]
|
additional_dependencies: [types-pkg_resources]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-yapf
|
- repo: https://github.com/pre-commit/mirrors-yapf
|
||||||
rev: 'v0.31.0' # Use the sha / tag you want to point at
|
rev: v0.31.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: yapf
|
- id: yapf
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.9.0 # Use the ref you want to point at
|
rev: v1.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-use-type-annotations
|
- id: python-use-type-annotations
|
||||||
- id: python-no-log-warn
|
- id: python-no-log-warn
|
||||||
- id: python-check-blanket-noqa
|
- id: python-check-blanket-noqa
|
||||||
|
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v2.29.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: [--py36-plus]
|
||||||
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/si-cim/gitlint
|
||||||
rev: v2.19.4
|
rev: v0.15.2-unofficial
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: gitlint
|
||||||
|
args: [--contrib=CT1, --ignore=B6, --msg-filename]
|
||||||
- repo: https://github.com/jorisroovers/gitlint
|
|
||||||
rev: "v0.15.1"
|
|
||||||
hooks:
|
|
||||||
- id: gitlint
|
|
||||||
args: [--contrib=CT1, --ignore=B6, --msg-filename]
|
|
||||||
|
37
.travis.yml
37
.travis.yml
@@ -1,7 +1,11 @@
|
|||||||
dist: bionic
|
dist: bionic
|
||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
python: 3.9
|
python:
|
||||||
|
- 3.9
|
||||||
|
- 3.8
|
||||||
|
- 3.7
|
||||||
|
- 3.6
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- "$HOME/.cache/pip"
|
- "$HOME/.cache/pip"
|
||||||
@@ -15,11 +19,26 @@ script:
|
|||||||
- ./tests/test_examples.sh examples/
|
- ./tests/test_examples.sh examples/
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
deploy:
|
|
||||||
provider: pypi
|
# Publish on PyPI
|
||||||
username: __token__
|
jobs:
|
||||||
password:
|
include:
|
||||||
secure: PDoASdYdVlt1aIROYilAsCW6XpBs/TDel0CSptDzX0CI7i4+ksEW6Jk0JyL58bQt7V4F8PeGty4A8SODzAUIk2d8sty5RI4VJjvXZFCXlUsW+JGUN3EvWNqJLnwN8TDxgu2ENao37GUh0dC6pL8b6bVDGeOLaY1E/YR1jimmTJuxxjKjBIU8ByqTNBnC3rzybMTPU3nRoOM/WMQUyReHrPoUJj685sLqrLruhAqhiYsPbotP8xY6i8+KBbhp5vgiARV2+LkbeGcYZwozCzrEqPKY7YIfVPh895cw0v4NRyFwK1P2jyyIt22Z9Ni0Uy1J5/Qp9Sv6mBPeGjm3pnpDCQyS+2bNIDaj08KUYTIo1mC/Jcu4jQgppZEF+oey9q1tgGo+/JhsTeERKV9BoPF5HDiRArU1s5aWJjFnCsHfu+W1XqX8bwN3aTYsEIaApT3/irc6XyFJIfMN82+z+lUcZ4Y1yAHT3nH1Vif+pZYZB0UOSGrHwuI/UayjKzbCzHMuHWylWB/9ehd4o4YVp6iubVHc7Sj0KQkwBgwgl6TvwNcUuFsplFabCxmX0mVcavXsWiOBc+ivPmU6574zGj0JcEk5ghVgnKH+QS96aVrKOzegwbl4O13jY8dJp+/zgXl0gJOvRKr4BhuBJKcBaMQHdSKUChVsJJtqDyt59GvWcbg=
|
- stage: build
|
||||||
on:
|
python: 3.9
|
||||||
tags: true
|
script: echo "Starting Pypi build"
|
||||||
skip_existing: true
|
deploy:
|
||||||
|
provider: pypi
|
||||||
|
username: __token__
|
||||||
|
distributions: "sdist bdist_wheel"
|
||||||
|
password:
|
||||||
|
secure: PDoASdYdVlt1aIROYilAsCW6XpBs/TDel0CSptDzX0CI7i4+ksEW6Jk0JyL58bQt7V4F8PeGty4A8SODzAUIk2d8sty5RI4VJjvXZFCXlUsW+JGUN3EvWNqJLnwN8TDxgu2ENao37GUh0dC6pL8b6bVDGeOLaY1E/YR1jimmTJuxxjKjBIU8ByqTNBnC3rzybMTPU3nRoOM/WMQUyReHrPoUJj685sLqrLruhAqhiYsPbotP8xY6i8+KBbhp5vgiARV2+LkbeGcYZwozCzrEqPKY7YIfVPh895cw0v4NRyFwK1P2jyyIt22Z9Ni0Uy1J5/Qp9Sv6mBPeGjm3pnpDCQyS+2bNIDaj08KUYTIo1mC/Jcu4jQgppZEF+oey9q1tgGo+/JhsTeERKV9BoPF5HDiRArU1s5aWJjFnCsHfu+W1XqX8bwN3aTYsEIaApT3/irc6XyFJIfMN82+z+lUcZ4Y1yAHT3nH1Vif+pZYZB0UOSGrHwuI/UayjKzbCzHMuHWylWB/9ehd4o4YVp6iubVHc7Sj0KQkwBgwgl6TvwNcUuFsplFabCxmX0mVcavXsWiOBc+ivPmU6574zGj0JcEk5ghVgnKH+QS96aVrKOzegwbl4O13jY8dJp+/zgXl0gJOvRKr4BhuBJKcBaMQHdSKUChVsJJtqDyt59GvWcbg=
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
|
skip_existing: true
|
||||||
|
|
||||||
|
# The password is encrypted with:
|
||||||
|
# `cd prototorch && travis encrypt your-pypi-api-token --add deploy.password`
|
||||||
|
# See https://docs.travis-ci.com/user/deployment/pypi and
|
||||||
|
# https://github.com/travis-ci/travis.rb#installation
|
||||||
|
# for more details
|
||||||
|
# Note: The encrypt command does not work well in ZSH.
|
||||||
|
36
README.md
36
README.md
@@ -20,23 +20,6 @@ pip install prototorch_models
|
|||||||
of** [ProtoTorch](https://github.com/si-cim/prototorch). The plugin should then
|
of** [ProtoTorch](https://github.com/si-cim/prototorch). The plugin should then
|
||||||
be available for use in your Python environment as `prototorch.models`.
|
be available for use in your Python environment as `prototorch.models`.
|
||||||
|
|
||||||
## Contribution
|
|
||||||
|
|
||||||
This repository contains definition for [git hooks](https://githooks.com).
|
|
||||||
[Pre-commit](https://pre-commit.com) is automatically installed as development
|
|
||||||
dependency with prototorch or you can install it manually with `pip install
|
|
||||||
pre-commit`.
|
|
||||||
|
|
||||||
Please install the hooks by running:
|
|
||||||
```bash
|
|
||||||
pre-commit install
|
|
||||||
pre-commit install --hook-type commit-msg
|
|
||||||
```
|
|
||||||
before creating the first commit.
|
|
||||||
|
|
||||||
The commit will fail if the commit message does not follow the specification
|
|
||||||
provided [here](https://www.conventionalcommits.org/en/v1.0.0/#specification).
|
|
||||||
|
|
||||||
## Available models
|
## Available models
|
||||||
|
|
||||||
### LVQ Family
|
### LVQ Family
|
||||||
@@ -53,6 +36,7 @@ provided [here](https://www.conventionalcommits.org/en/v1.0.0/#specification).
|
|||||||
- Soft Learning Vector Quantization (SLVQ)
|
- Soft Learning Vector Quantization (SLVQ)
|
||||||
- Robust Soft Learning Vector Quantization (RSLVQ)
|
- Robust Soft Learning Vector Quantization (RSLVQ)
|
||||||
- Probabilistic Learning Vector Quantization (PLVQ)
|
- Probabilistic Learning Vector Quantization (PLVQ)
|
||||||
|
- Median-LVQ
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
@@ -68,7 +52,6 @@ provided [here](https://www.conventionalcommits.org/en/v1.0.0/#specification).
|
|||||||
|
|
||||||
## Planned models
|
## Planned models
|
||||||
|
|
||||||
- Median-LVQ
|
|
||||||
- Generalized Tangent Learning Vector Quantization (GTLVQ)
|
- Generalized Tangent Learning Vector Quantization (GTLVQ)
|
||||||
- Self-Incremental Learning Vector Quantization (SILVQ)
|
- Self-Incremental Learning Vector Quantization (SILVQ)
|
||||||
|
|
||||||
@@ -103,6 +86,23 @@ To assist in the development process, you may also find it useful to install
|
|||||||
please avoid installing Tensorflow in this environment. It is known to cause
|
please avoid installing Tensorflow in this environment. It is known to cause
|
||||||
problems with PyTorch-Lightning.**
|
problems with PyTorch-Lightning.**
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
This repository contains definition for [git hooks](https://githooks.com).
|
||||||
|
[Pre-commit](https://pre-commit.com) is automatically installed as development
|
||||||
|
dependency with prototorch or you can install it manually with `pip install
|
||||||
|
pre-commit`.
|
||||||
|
|
||||||
|
Please install the hooks by running:
|
||||||
|
```bash
|
||||||
|
pre-commit install
|
||||||
|
pre-commit install --hook-type commit-msg
|
||||||
|
```
|
||||||
|
before creating the first commit.
|
||||||
|
|
||||||
|
The commit will fail if the commit message does not follow the specification
|
||||||
|
provided [here](https://www.conventionalcommits.org/en/v1.0.0/#specification).
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### How do I update the plugin?
|
### How do I update the plugin?
|
||||||
|
@@ -23,7 +23,7 @@ author = "Jensun Ravichandran"
|
|||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
#
|
#
|
||||||
release = "0.4.4"
|
release = "0.3.0"
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -24,21 +23,27 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Hyperparameters
|
# Hyperparameters
|
||||||
hparams = dict(
|
hparams = dict(
|
||||||
distribution=[2, 2, 2],
|
distribution=[1, 0, 3],
|
||||||
proto_lr=0.1,
|
margin=0.1,
|
||||||
|
proto_lr=0.01,
|
||||||
|
bb_lr=0.01,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.CBC(
|
model = pt.models.CBC(
|
||||||
hparams,
|
hparams,
|
||||||
prototype_initializer=pt.components.SSI(train_ds, noise=0.01),
|
components_initializer=pt.initializers.SSCI(train_ds, noise=0.01),
|
||||||
|
reasonings_iniitializer=pt.initializers.
|
||||||
|
PurePositiveReasoningsInitializer(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
vis = pt.models.VisCBC2D(data=train_ds,
|
vis = pt.models.Visualize2DVoronoiCallback(
|
||||||
title="CBC Iris Example",
|
data=train_ds,
|
||||||
resolution=100,
|
title="CBC Iris Example",
|
||||||
axis_off=True)
|
resolution=100,
|
||||||
|
axis_off=True,
|
||||||
|
)
|
||||||
|
|
||||||
# Setup trainer
|
# Setup trainer
|
||||||
trainer = pl.Trainer.from_argparse_args(
|
trainer = pl.Trainer.from_argparse_args(
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
# Examples using Lightning CLI
|
|
||||||
|
|
||||||
Examples in this folder use the experimental [Lightning CLI](https://pytorch-lightning.readthedocs.io/en/latest/common/lightning_cli.html).
|
|
||||||
|
|
||||||
To use the example run
|
|
||||||
```
|
|
||||||
python gmlvq.py --config gmlvq.yaml
|
|
||||||
```
|
|
@@ -1,20 +0,0 @@
|
|||||||
"""GMLVQ example using the MNIST dataset."""
|
|
||||||
|
|
||||||
import torch
|
|
||||||
from pytorch_lightning.utilities.cli import LightningCLI
|
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
from prototorch.models import ImageGMLVQ
|
|
||||||
from prototorch.models.abstract import PrototypeModel
|
|
||||||
from prototorch.models.data import MNISTDataModule
|
|
||||||
|
|
||||||
|
|
||||||
class ExperimentClass(ImageGMLVQ):
|
|
||||||
def __init__(self, hparams, **kwargs):
|
|
||||||
super().__init__(hparams,
|
|
||||||
optimizer=torch.optim.Adam,
|
|
||||||
prototype_initializer=pt.components.zeros(28 * 28),
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
cli = LightningCLI(ImageGMLVQ, MNISTDataModule)
|
|
@@ -1,11 +0,0 @@
|
|||||||
model:
|
|
||||||
hparams:
|
|
||||||
input_dim: 784
|
|
||||||
latent_dim: 784
|
|
||||||
distribution:
|
|
||||||
num_classes: 10
|
|
||||||
prototypes_per_class: 2
|
|
||||||
proto_lr: 0.01
|
|
||||||
bb_lr: 0.01
|
|
||||||
data:
|
|
||||||
batch_size: 32
|
|
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -37,7 +36,7 @@ if __name__ == "__main__":
|
|||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.CELVQ(
|
model = pt.models.CELVQ(
|
||||||
hparams,
|
hparams,
|
||||||
prototype_initializer=pt.components.Ones(2, scale=3),
|
prototypes_initializer=pt.initializers.FVCI(2, 3.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute intermediate input and output sizes
|
# Compute intermediate input and output sizes
|
||||||
|
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
|
import prototorch.models.clcc
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
from torch.optim.lr_scheduler import ExponentialLR
|
from torch.optim.lr_scheduler import ExponentialLR
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -24,16 +24,16 @@ if __name__ == "__main__":
|
|||||||
hparams = dict(
|
hparams = dict(
|
||||||
distribution={
|
distribution={
|
||||||
"num_classes": 3,
|
"num_classes": 3,
|
||||||
"prototypes_per_class": 4
|
"per_class": 4
|
||||||
},
|
},
|
||||||
lr=0.01,
|
lr=0.01,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.GLVQ(
|
model = prototorch.models.GLVQ(
|
||||||
hparams,
|
hparams,
|
||||||
optimizer=torch.optim.Adam,
|
optimizer=torch.optim.Adam,
|
||||||
prototype_initializer=pt.components.SMI(train_ds),
|
prototypes_initializer=pt.initializers.SMCI(train_ds),
|
||||||
lr_scheduler=ExponentialLR,
|
lr_scheduler=ExponentialLR,
|
||||||
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
||||||
)
|
)
|
||||||
@@ -42,7 +42,13 @@ if __name__ == "__main__":
|
|||||||
model.example_input_array = torch.zeros(4, 2)
|
model.example_input_array = torch.zeros(4, 2)
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
vis = pt.models.VisGLVQ2D(data=train_ds)
|
vis = pt.models.Visualize2DVoronoiCallback(
|
||||||
|
data=train_ds,
|
||||||
|
resolution=200,
|
||||||
|
title="Example: GLVQ on Iris",
|
||||||
|
x_label="sepal length",
|
||||||
|
y_label="petal length",
|
||||||
|
)
|
||||||
|
|
||||||
# Setup trainer
|
# Setup trainer
|
||||||
trainer = pl.Trainer.from_argparse_args(
|
trainer = pl.Trainer.from_argparse_args(
|
||||||
|
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -26,7 +25,6 @@ if __name__ == "__main__":
|
|||||||
distribution=(num_classes, prototypes_per_class),
|
distribution=(num_classes, prototypes_per_class),
|
||||||
transfer_function="swish_beta",
|
transfer_function="swish_beta",
|
||||||
transfer_beta=10.0,
|
transfer_beta=10.0,
|
||||||
# lr=0.1,
|
|
||||||
proto_lr=0.1,
|
proto_lr=0.1,
|
||||||
bb_lr=0.1,
|
bb_lr=0.1,
|
||||||
input_dim=2,
|
input_dim=2,
|
||||||
@@ -37,7 +35,7 @@ if __name__ == "__main__":
|
|||||||
model = pt.models.GMLVQ(
|
model = pt.models.GMLVQ(
|
||||||
hparams,
|
hparams,
|
||||||
optimizer=torch.optim.Adam,
|
optimizer=torch.optim.Adam,
|
||||||
prototype_initializer=pt.components.SSI(train_ds, noise=1e-2),
|
prototypes_initializer=pt.initializers.SSCI(train_ds, noise=1e-2),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
@@ -47,12 +45,12 @@ if __name__ == "__main__":
|
|||||||
block=False,
|
block=False,
|
||||||
)
|
)
|
||||||
pruning = pt.models.PruneLoserPrototypes(
|
pruning = pt.models.PruneLoserPrototypes(
|
||||||
threshold=0.02,
|
threshold=0.01,
|
||||||
idle_epochs=10,
|
idle_epochs=10,
|
||||||
prune_quota_per_epoch=5,
|
prune_quota_per_epoch=5,
|
||||||
frequency=2,
|
frequency=5,
|
||||||
replace=True,
|
replace=True,
|
||||||
initializer=pt.components.SSI(train_ds, noise=1e-2),
|
prototypes_initializer=pt.initializers.SSCI(train_ds, noise=1e-1),
|
||||||
verbose=True,
|
verbose=True,
|
||||||
)
|
)
|
||||||
es = pl.callbacks.EarlyStopping(
|
es = pl.callbacks.EarlyStopping(
|
||||||
@@ -68,7 +66,7 @@ if __name__ == "__main__":
|
|||||||
args,
|
args,
|
||||||
callbacks=[
|
callbacks=[
|
||||||
vis,
|
vis,
|
||||||
# es,
|
es,
|
||||||
pruning,
|
pruning,
|
||||||
],
|
],
|
||||||
terminate_on_nan=True,
|
terminate_on_nan=True,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
"""GLVQ example using the Iris dataset."""
|
"""GMLVQ example using the Iris dataset."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
@@ -22,30 +22,29 @@ if __name__ == "__main__":
|
|||||||
# Hyperparameters
|
# Hyperparameters
|
||||||
hparams = dict(
|
hparams = dict(
|
||||||
input_dim=4,
|
input_dim=4,
|
||||||
latent_dim=3,
|
latent_dim=4,
|
||||||
distribution={
|
distribution={
|
||||||
"num_classes": 3,
|
"num_classes": 3,
|
||||||
"prototypes_per_class": 2
|
"per_class": 2
|
||||||
},
|
},
|
||||||
proto_lr=0.0005,
|
proto_lr=0.01,
|
||||||
bb_lr=0.0005,
|
bb_lr=0.01,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.GMLVQ(
|
model = pt.models.GMLVQ(
|
||||||
hparams,
|
hparams,
|
||||||
optimizer=torch.optim.Adam,
|
optimizer=torch.optim.Adam,
|
||||||
prototype_initializer=pt.components.SSI(train_ds),
|
prototypes_initializer=pt.initializers.SMCI(train_ds),
|
||||||
lr_scheduler=ExponentialLR,
|
lr_scheduler=ExponentialLR,
|
||||||
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
||||||
omega_initializer=pt.components.PCA(train_ds.data)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute intermediate input and output sizes
|
# Compute intermediate input and output sizes
|
||||||
#model.example_input_array = torch.zeros(4, 2)
|
model.example_input_array = torch.zeros(4, 4)
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
vis = pt.models.VisGMLVQ2D(data=train_ds, border=0.1)
|
vis = pt.models.VisGMLVQ2D(data=train_ds)
|
||||||
|
|
||||||
# Setup trainer
|
# Setup trainer
|
||||||
trainer = pl.Trainer.from_argparse_args(
|
trainer = pl.Trainer.from_argparse_args(
|
||||||
|
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
from torchvision import transforms
|
from torchvision import transforms
|
||||||
from torchvision.datasets import MNIST
|
from torchvision.datasets import MNIST
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -56,7 +55,7 @@ if __name__ == "__main__":
|
|||||||
model = pt.models.ImageGMLVQ(
|
model = pt.models.ImageGMLVQ(
|
||||||
hparams,
|
hparams,
|
||||||
optimizer=torch.optim.Adam,
|
optimizer=torch.optim.Adam,
|
||||||
prototype_initializer=pt.components.SMI(train_ds),
|
prototypes_initializer=pt.initializers.SMCI(train_ds),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
@@ -95,7 +94,7 @@ if __name__ == "__main__":
|
|||||||
],
|
],
|
||||||
terminate_on_nan=True,
|
terminate_on_nan=True,
|
||||||
weights_summary=None,
|
weights_summary=None,
|
||||||
accelerator="ddp",
|
# accelerator="ddp",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Training loop
|
# Training loop
|
||||||
|
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -30,7 +29,7 @@ if __name__ == "__main__":
|
|||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.GrowingNeuralGas(
|
model = pt.models.GrowingNeuralGas(
|
||||||
hparams,
|
hparams,
|
||||||
prototype_initializer=pt.components.Zeros(2),
|
prototypes_initializer=pt.initializers.ZCI(2),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute intermediate input and output sizes
|
# Compute intermediate input and output sizes
|
||||||
|
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
from sklearn.datasets import load_iris
|
from sklearn.datasets import load_iris
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
@@ -15,12 +15,20 @@ if __name__ == "__main__":
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Dataset
|
# Dataset
|
||||||
x_train, y_train = load_iris(return_X_y=True)
|
X, y = load_iris(return_X_y=True)
|
||||||
x_train = x_train[:, [0, 2]]
|
X = X[:, [0, 2]]
|
||||||
train_ds = pt.datasets.NumpyDataset(x_train, y_train)
|
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X,
|
||||||
|
y,
|
||||||
|
test_size=0.5,
|
||||||
|
random_state=42)
|
||||||
|
|
||||||
|
train_ds = pt.datasets.NumpyDataset(X_train, y_train)
|
||||||
|
test_ds = pt.datasets.NumpyDataset(X_test, y_test)
|
||||||
|
|
||||||
# Dataloaders
|
# Dataloaders
|
||||||
train_loader = torch.utils.data.DataLoader(train_ds, batch_size=150)
|
train_loader = torch.utils.data.DataLoader(train_ds, batch_size=16)
|
||||||
|
test_loader = torch.utils.data.DataLoader(test_ds, batch_size=16)
|
||||||
|
|
||||||
# Hyperparameters
|
# Hyperparameters
|
||||||
hparams = dict(k=5)
|
hparams = dict(k=5)
|
||||||
@@ -36,7 +44,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
vis = pt.models.VisGLVQ2D(
|
vis = pt.models.VisGLVQ2D(
|
||||||
data=(x_train, y_train),
|
data=(X_train, y_train),
|
||||||
resolution=200,
|
resolution=200,
|
||||||
block=True,
|
block=True,
|
||||||
)
|
)
|
||||||
@@ -54,5 +62,8 @@ if __name__ == "__main__":
|
|||||||
trainer.fit(model, train_loader)
|
trainer.fit(model, train_loader)
|
||||||
|
|
||||||
# Recall
|
# Recall
|
||||||
y_pred = model.predict(torch.tensor(x_train))
|
y_pred = model.predict(torch.tensor(X_train))
|
||||||
print(y_pred)
|
print(y_pred)
|
||||||
|
|
||||||
|
# Test
|
||||||
|
trainer.test(model, dataloaders=test_loader)
|
||||||
|
@@ -2,25 +2,11 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
from matplotlib import pyplot as plt
|
from matplotlib import pyplot as plt
|
||||||
|
from prototorch.utils.colors import hex_to_rgb
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
|
|
||||||
def hex_to_rgb(hex_values):
|
|
||||||
for v in hex_values:
|
|
||||||
v = v.lstrip('#')
|
|
||||||
lv = len(v)
|
|
||||||
c = [int(v[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)]
|
|
||||||
yield c
|
|
||||||
|
|
||||||
|
|
||||||
def rgb_to_hex(rgb_values):
|
|
||||||
for v in rgb_values:
|
|
||||||
c = "%02x%02x%02x" % tuple(v)
|
|
||||||
yield c
|
|
||||||
|
|
||||||
|
|
||||||
class Vis2DColorSOM(pl.Callback):
|
class Vis2DColorSOM(pl.Callback):
|
||||||
@@ -93,7 +79,7 @@ if __name__ == "__main__":
|
|||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.KohonenSOM(
|
model = pt.models.KohonenSOM(
|
||||||
hparams,
|
hparams,
|
||||||
prototype_initializer=pt.components.Random(3),
|
prototypes_initializer=pt.initializers.RNCI(3),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute intermediate input and output sizes
|
# Compute intermediate input and output sizes
|
||||||
|
@@ -2,23 +2,22 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser = pl.Trainer.add_argparse_args(parser)
|
parser = pl.Trainer.add_argparse_args(parser)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Dataset
|
|
||||||
train_ds = pt.datasets.Moons(num_samples=300, noise=0.2, seed=42)
|
|
||||||
|
|
||||||
# Reproducibility
|
# Reproducibility
|
||||||
pl.utilities.seed.seed_everything(seed=2)
|
pl.utilities.seed.seed_everything(seed=2)
|
||||||
|
|
||||||
|
# Dataset
|
||||||
|
train_ds = pt.datasets.Moons(num_samples=300, noise=0.2, seed=42)
|
||||||
|
|
||||||
# Dataloaders
|
# Dataloaders
|
||||||
train_loader = torch.utils.data.DataLoader(train_ds,
|
train_loader = torch.utils.data.DataLoader(train_ds,
|
||||||
batch_size=256,
|
batch_size=256,
|
||||||
@@ -32,8 +31,10 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.LGMLVQ(hparams,
|
model = pt.models.LGMLVQ(
|
||||||
prototype_initializer=pt.components.SMI(train_ds))
|
hparams,
|
||||||
|
prototypes_initializer=pt.initializers.SMCI(train_ds),
|
||||||
|
)
|
||||||
|
|
||||||
# Compute intermediate input and output sizes
|
# Compute intermediate input and output sizes
|
||||||
model.example_input_array = torch.zeros(4, 2)
|
model.example_input_array = torch.zeros(4, 2)
|
||||||
|
@@ -1,90 +0,0 @@
|
|||||||
"""Limited Rank Matrix LVQ example using the Tecator dataset."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import pytorch_lightning as pl
|
|
||||||
import torch
|
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
|
|
||||||
def plot_matrix(matrix):
|
|
||||||
title = "Lambda matrix"
|
|
||||||
plt.figure(title)
|
|
||||||
plt.title(title)
|
|
||||||
plt.imshow(matrix, cmap="gray")
|
|
||||||
plt.axis("off")
|
|
||||||
plt.colorbar()
|
|
||||||
plt.show(block=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Command-line arguments
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser = pl.Trainer.add_argparse_args(parser)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Dataset
|
|
||||||
train_ds = pt.datasets.Tecator(root="~/datasets/", train=True)
|
|
||||||
test_ds = pt.datasets.Tecator(root="~/datasets/", train=False)
|
|
||||||
|
|
||||||
# Reproducibility
|
|
||||||
pl.utilities.seed.seed_everything(seed=10)
|
|
||||||
|
|
||||||
# Dataloaders
|
|
||||||
train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32)
|
|
||||||
test_loader = torch.utils.data.DataLoader(test_ds, batch_size=32)
|
|
||||||
|
|
||||||
# Hyperparameters
|
|
||||||
hparams = dict(
|
|
||||||
distribution={
|
|
||||||
"num_classes": 2,
|
|
||||||
"prototypes_per_class": 1
|
|
||||||
},
|
|
||||||
input_dim=100,
|
|
||||||
latent_dim=2,
|
|
||||||
proto_lr=0.0001,
|
|
||||||
bb_lr=0.0001,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize the model
|
|
||||||
model = pt.models.SiameseGMLVQ(
|
|
||||||
hparams,
|
|
||||||
# optimizer=torch.optim.SGD,
|
|
||||||
optimizer=torch.optim.Adam,
|
|
||||||
prototype_initializer=pt.components.SMI(train_ds),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print(model)
|
|
||||||
|
|
||||||
# Callbacks
|
|
||||||
vis = pt.models.VisSiameseGLVQ2D(train_ds, border=0.1)
|
|
||||||
es = pl.callbacks.EarlyStopping(monitor="val_loss",
|
|
||||||
min_delta=0.001,
|
|
||||||
patience=50,
|
|
||||||
verbose=False,
|
|
||||||
mode="min")
|
|
||||||
|
|
||||||
# Setup trainer
|
|
||||||
trainer = pl.Trainer.from_argparse_args(
|
|
||||||
args,
|
|
||||||
callbacks=[vis, es],
|
|
||||||
weights_summary=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Training loop
|
|
||||||
trainer.fit(model, train_loader, test_loader)
|
|
||||||
|
|
||||||
# Save the model
|
|
||||||
torch.save(model, "liramlvq_tecator.pt")
|
|
||||||
|
|
||||||
# Load a saved model
|
|
||||||
saved_model = torch.load("liramlvq_tecator.pt")
|
|
||||||
|
|
||||||
# Display the Lambda matrix
|
|
||||||
plot_matrix(saved_model.lambda_matrix)
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
trainer.test(model, test_dataloaders=test_loader)
|
|
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
|
|
||||||
class Backbone(torch.nn.Module):
|
class Backbone(torch.nn.Module):
|
||||||
def __init__(self, input_size=4, hidden_size=10, latent_size=2):
|
def __init__(self, input_size=4, hidden_size=10, latent_size=2):
|
||||||
@@ -41,7 +40,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Hyperparameters
|
# Hyperparameters
|
||||||
hparams = dict(
|
hparams = dict(
|
||||||
distribution=[1, 2, 2],
|
distribution=[3, 4, 5],
|
||||||
proto_lr=0.001,
|
proto_lr=0.001,
|
||||||
bb_lr=0.001,
|
bb_lr=0.001,
|
||||||
)
|
)
|
||||||
@@ -52,7 +51,10 @@ if __name__ == "__main__":
|
|||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.LVQMLN(
|
model = pt.models.LVQMLN(
|
||||||
hparams,
|
hparams,
|
||||||
prototype_initializer=pt.components.SSI(train_ds, transform=backbone),
|
prototypes_initializer=pt.initializers.SSCI(
|
||||||
|
train_ds,
|
||||||
|
transform=backbone,
|
||||||
|
),
|
||||||
backbone=backbone,
|
backbone=backbone,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,11 +69,21 @@ if __name__ == "__main__":
|
|||||||
resolution=500,
|
resolution=500,
|
||||||
axis_off=True,
|
axis_off=True,
|
||||||
)
|
)
|
||||||
|
pruning = pt.models.PruneLoserPrototypes(
|
||||||
|
threshold=0.01,
|
||||||
|
idle_epochs=20,
|
||||||
|
prune_quota_per_epoch=2,
|
||||||
|
frequency=10,
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
|
|
||||||
# Setup trainer
|
# Setup trainer
|
||||||
trainer = pl.Trainer.from_argparse_args(
|
trainer = pl.Trainer.from_argparse_args(
|
||||||
args,
|
args,
|
||||||
callbacks=[vis],
|
callbacks=[
|
||||||
|
vis,
|
||||||
|
pruning,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Training loop
|
# Training loop
|
||||||
|
52
examples/median_lvq_iris.py
Normal file
52
examples/median_lvq_iris.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""Median-LVQ example using the Iris dataset."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
|
import pytorch_lightning as pl
|
||||||
|
import torch
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Command-line arguments
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser = pl.Trainer.add_argparse_args(parser)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Dataset
|
||||||
|
train_ds = pt.datasets.Iris(dims=[0, 2])
|
||||||
|
|
||||||
|
# Dataloaders
|
||||||
|
train_loader = torch.utils.data.DataLoader(
|
||||||
|
train_ds,
|
||||||
|
batch_size=len(train_ds), # MedianLVQ cannot handle mini-batches
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the model
|
||||||
|
model = pt.models.MedianLVQ(
|
||||||
|
hparams=dict(distribution=(3, 2), lr=0.01),
|
||||||
|
prototypes_initializer=pt.initializers.SSCI(train_ds),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute intermediate input and output sizes
|
||||||
|
model.example_input_array = torch.zeros(4, 2)
|
||||||
|
|
||||||
|
# Callbacks
|
||||||
|
vis = pt.models.VisGLVQ2D(data=train_ds)
|
||||||
|
es = pl.callbacks.EarlyStopping(
|
||||||
|
monitor="train_acc",
|
||||||
|
min_delta=0.01,
|
||||||
|
patience=5,
|
||||||
|
mode="max",
|
||||||
|
verbose=True,
|
||||||
|
check_on_train_epoch_end=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup trainer
|
||||||
|
trainer = pl.Trainer.from_argparse_args(
|
||||||
|
args,
|
||||||
|
callbacks=[vis, es],
|
||||||
|
weights_summary="full",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Training loop
|
||||||
|
trainer.fit(model, train_loader)
|
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
from sklearn.datasets import load_iris
|
from sklearn.datasets import load_iris
|
||||||
from sklearn.preprocessing import StandardScaler
|
from sklearn.preprocessing import StandardScaler
|
||||||
from torch.optim.lr_scheduler import ExponentialLR
|
from torch.optim.lr_scheduler import ExponentialLR
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -38,7 +37,7 @@ if __name__ == "__main__":
|
|||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.NeuralGas(
|
model = pt.models.NeuralGas(
|
||||||
hparams,
|
hparams,
|
||||||
prototype_initializer=pt.components.Zeros(2),
|
prototypes_initializer=pt.core.ZCI(2),
|
||||||
lr_scheduler=ExponentialLR,
|
lr_scheduler=ExponentialLR,
|
||||||
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
||||||
)
|
)
|
||||||
|
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
from torchvision.transforms import Lambda
|
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Command-line arguments
|
# Command-line arguments
|
||||||
@@ -28,19 +26,17 @@ if __name__ == "__main__":
|
|||||||
distribution=[2, 2, 3],
|
distribution=[2, 2, 3],
|
||||||
proto_lr=0.05,
|
proto_lr=0.05,
|
||||||
lambd=0.1,
|
lambd=0.1,
|
||||||
|
variance=1.0,
|
||||||
input_dim=2,
|
input_dim=2,
|
||||||
latent_dim=2,
|
latent_dim=2,
|
||||||
bb_lr=0.01,
|
bb_lr=0.01,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.probabilistic.PLVQ(
|
model = pt.models.RSLVQ(
|
||||||
hparams,
|
hparams,
|
||||||
optimizer=torch.optim.Adam,
|
optimizer=torch.optim.Adam,
|
||||||
# prototype_initializer=pt.components.SMI(train_ds),
|
prototypes_initializer=pt.initializers.SSCI(train_ds, noise=0.2),
|
||||||
prototype_initializer=pt.components.SSI(train_ds, noise=0.2),
|
|
||||||
# prototype_initializer=pt.components.Zeros(2),
|
|
||||||
# prototype_initializer=pt.components.Ones(2, scale=2.0),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute intermediate input and output sizes
|
# Compute intermediate input and output sizes
|
||||||
@@ -50,7 +46,7 @@ if __name__ == "__main__":
|
|||||||
print(model)
|
print(model)
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
vis = pt.models.VisSiameseGLVQ2D(data=train_ds)
|
vis = pt.models.VisGLVQ2D(data=train_ds)
|
||||||
|
|
||||||
# Setup trainer
|
# Setup trainer
|
||||||
trainer = pl.Trainer.from_argparse_args(
|
trainer = pl.Trainer.from_argparse_args(
|
||||||
|
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
|
|
||||||
class Backbone(torch.nn.Module):
|
class Backbone(torch.nn.Module):
|
||||||
def __init__(self, input_size=4, hidden_size=10, latent_size=2):
|
def __init__(self, input_size=4, hidden_size=10, latent_size=2):
|
||||||
@@ -52,7 +51,7 @@ if __name__ == "__main__":
|
|||||||
# Initialize the model
|
# Initialize the model
|
||||||
model = pt.models.SiameseGLVQ(
|
model = pt.models.SiameseGLVQ(
|
||||||
hparams,
|
hparams,
|
||||||
prototype_initializer=pt.components.SMI(train_ds),
|
prototypes_initializer=pt.initializers.SMCI(train_ds),
|
||||||
backbone=backbone,
|
backbone=backbone,
|
||||||
both_path_gradients=False,
|
both_path_gradients=False,
|
||||||
)
|
)
|
||||||
|
103
examples/warm_starting.py
Normal file
103
examples/warm_starting.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"""Warm-starting GLVQ with prototypes from Growing Neural Gas."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
import prototorch as pt
|
||||||
|
import pytorch_lightning as pl
|
||||||
|
import torch
|
||||||
|
from torch.optim.lr_scheduler import ExponentialLR
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Command-line arguments
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser = pl.Trainer.add_argparse_args(parser)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Prepare the data
|
||||||
|
train_ds = pt.datasets.Iris(dims=[0, 2])
|
||||||
|
train_loader = torch.utils.data.DataLoader(train_ds, batch_size=64)
|
||||||
|
|
||||||
|
# Initialize the gng
|
||||||
|
gng = pt.models.GrowingNeuralGas(
|
||||||
|
hparams=dict(num_prototypes=5, insert_freq=2, lr=0.1),
|
||||||
|
prototypes_initializer=pt.initializers.ZCI(2),
|
||||||
|
lr_scheduler=ExponentialLR,
|
||||||
|
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Callbacks
|
||||||
|
es = pl.callbacks.EarlyStopping(
|
||||||
|
monitor="loss",
|
||||||
|
min_delta=0.001,
|
||||||
|
patience=20,
|
||||||
|
mode="min",
|
||||||
|
verbose=False,
|
||||||
|
check_on_train_epoch_end=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup trainer for GNG
|
||||||
|
trainer = pl.Trainer(
|
||||||
|
max_epochs=100,
|
||||||
|
callbacks=[es],
|
||||||
|
weights_summary=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Training loop
|
||||||
|
trainer.fit(gng, train_loader)
|
||||||
|
|
||||||
|
# Hyperparameters
|
||||||
|
hparams = dict(
|
||||||
|
distribution=[],
|
||||||
|
lr=0.01,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warm-start prototypes
|
||||||
|
knn = pt.models.KNN(dict(k=1), data=train_ds)
|
||||||
|
prototypes = gng.prototypes
|
||||||
|
plabels = knn.predict(prototypes)
|
||||||
|
|
||||||
|
# Initialize the model
|
||||||
|
model = pt.models.GLVQ(
|
||||||
|
hparams,
|
||||||
|
optimizer=torch.optim.Adam,
|
||||||
|
prototypes_initializer=pt.initializers.LCI(prototypes),
|
||||||
|
labels_initializer=pt.initializers.LLI(plabels),
|
||||||
|
lr_scheduler=ExponentialLR,
|
||||||
|
lr_scheduler_kwargs=dict(gamma=0.99, verbose=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute intermediate input and output sizes
|
||||||
|
model.example_input_array = torch.zeros(4, 2)
|
||||||
|
|
||||||
|
# Callbacks
|
||||||
|
vis = pt.models.VisGLVQ2D(data=train_ds)
|
||||||
|
pruning = pt.models.PruneLoserPrototypes(
|
||||||
|
threshold=0.02,
|
||||||
|
idle_epochs=2,
|
||||||
|
prune_quota_per_epoch=5,
|
||||||
|
frequency=1,
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
|
es = pl.callbacks.EarlyStopping(
|
||||||
|
monitor="train_loss",
|
||||||
|
min_delta=0.001,
|
||||||
|
patience=10,
|
||||||
|
mode="min",
|
||||||
|
verbose=True,
|
||||||
|
check_on_train_epoch_end=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup trainer
|
||||||
|
trainer = pl.Trainer.from_argparse_args(
|
||||||
|
args,
|
||||||
|
callbacks=[
|
||||||
|
vis,
|
||||||
|
pruning,
|
||||||
|
es,
|
||||||
|
],
|
||||||
|
weights_summary="full",
|
||||||
|
accelerator="ddp",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Training loop
|
||||||
|
trainer.fit(model, train_loader)
|
@@ -1,15 +1,24 @@
|
|||||||
"""`models` plugin for the `prototorch` package."""
|
"""`models` plugin for the `prototorch` package."""
|
||||||
|
|
||||||
from importlib.metadata import PackageNotFoundError, version
|
|
||||||
|
|
||||||
from .callbacks import PrototypeConvergence, PruneLoserPrototypes
|
from .callbacks import PrototypeConvergence, PruneLoserPrototypes
|
||||||
from .cbc import CBC, ImageCBC
|
from .cbc import CBC, ImageCBC
|
||||||
from .glvq import (GLVQ, GLVQ1, GLVQ21, GMLVQ, GRLVQ, LGMLVQ, LVQMLN,
|
from .glvq import (
|
||||||
ImageGLVQ, ImageGMLVQ, SiameseGLVQ, SiameseGMLVQ)
|
GLVQ,
|
||||||
|
GLVQ1,
|
||||||
|
GLVQ21,
|
||||||
|
GMLVQ,
|
||||||
|
GRLVQ,
|
||||||
|
LGMLVQ,
|
||||||
|
LVQMLN,
|
||||||
|
ImageGLVQ,
|
||||||
|
ImageGMLVQ,
|
||||||
|
SiameseGLVQ,
|
||||||
|
SiameseGMLVQ,
|
||||||
|
)
|
||||||
from .knn import KNN
|
from .knn import KNN
|
||||||
from .lvq import LVQ1, LVQ21, MedianLVQ
|
from .lvq import LVQ1, LVQ21, MedianLVQ
|
||||||
from .probabilistic import CELVQ, PLVQ, RSLVQ, SLVQ
|
from .probabilistic import CELVQ, PLVQ, RSLVQ, SLVQ
|
||||||
from .unsupervised import GrowingNeuralGas, HeskesSOM, KohonenSOM, NeuralGas
|
from .unsupervised import GrowingNeuralGas, HeskesSOM, KohonenSOM, NeuralGas
|
||||||
from .vis import *
|
from .vis import *
|
||||||
|
|
||||||
__version__ = "0.1.8"
|
__version__ = "0.3.0"
|
||||||
|
@@ -1,29 +1,18 @@
|
|||||||
"""Abstract classes to be inherited by prototorch models."""
|
"""Abstract classes to be inherited by prototorch models."""
|
||||||
|
|
||||||
from typing import Final, final
|
|
||||||
|
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
import torchmetrics
|
import torchmetrics
|
||||||
from prototorch.components import Components, LabeledComponents
|
from prototorch.core.competitions import WTAC
|
||||||
from prototorch.functions.distances import euclidean_distance
|
from prototorch.core.components import Components, LabeledComponents
|
||||||
from prototorch.modules import WTAC, LambdaLayer
|
from prototorch.core.distances import euclidean_distance
|
||||||
|
from prototorch.core.initializers import LabelsInitializer
|
||||||
|
from prototorch.core.pooling import stratified_min_pooling
|
||||||
class ProtoTorchMixin(object):
|
from prototorch.nn.wrappers import LambdaLayer
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ProtoTorchBolt(pl.LightningModule):
|
class ProtoTorchBolt(pl.LightningModule):
|
||||||
"""All ProtoTorch models are ProtoTorch Bolts."""
|
"""All ProtoTorch models are ProtoTorch Bolts."""
|
||||||
def __repr__(self):
|
|
||||||
surep = super().__repr__()
|
|
||||||
indented = "".join([f"\t{line}\n" for line in surep.splitlines()])
|
|
||||||
wrapped = f"ProtoTorch Bolt(\n{indented})"
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
class PrototypeModel(ProtoTorchBolt):
|
|
||||||
def __init__(self, hparams, **kwargs):
|
def __init__(self, hparams, **kwargs):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -38,6 +27,33 @@ class PrototypeModel(ProtoTorchBolt):
|
|||||||
self.lr_scheduler = kwargs.get("lr_scheduler", None)
|
self.lr_scheduler = kwargs.get("lr_scheduler", None)
|
||||||
self.lr_scheduler_kwargs = kwargs.get("lr_scheduler_kwargs", dict())
|
self.lr_scheduler_kwargs = kwargs.get("lr_scheduler_kwargs", dict())
|
||||||
|
|
||||||
|
def configure_optimizers(self):
|
||||||
|
optimizer = self.optimizer(self.parameters(), lr=self.hparams.lr)
|
||||||
|
if self.lr_scheduler is not None:
|
||||||
|
scheduler = self.lr_scheduler(optimizer,
|
||||||
|
**self.lr_scheduler_kwargs)
|
||||||
|
sch = {
|
||||||
|
"scheduler": scheduler,
|
||||||
|
"interval": "step",
|
||||||
|
} # called after each training step
|
||||||
|
return [optimizer], [sch]
|
||||||
|
else:
|
||||||
|
return optimizer
|
||||||
|
|
||||||
|
def reconfigure_optimizers(self):
|
||||||
|
self.trainer.accelerator.setup_optimizers(self.trainer)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
surep = super().__repr__()
|
||||||
|
indented = "".join([f"\t{line}\n" for line in surep.splitlines()])
|
||||||
|
wrapped = f"ProtoTorch Bolt(\n{indented})"
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
class PrototypeModel(ProtoTorchBolt):
|
||||||
|
def __init__(self, hparams, **kwargs):
|
||||||
|
super().__init__(hparams, **kwargs)
|
||||||
|
|
||||||
distance_fn = kwargs.get("distance_fn", euclidean_distance)
|
distance_fn = kwargs.get("distance_fn", euclidean_distance)
|
||||||
self.distance_layer = LambdaLayer(distance_fn)
|
self.distance_layer = LambdaLayer(distance_fn)
|
||||||
|
|
||||||
@@ -54,23 +70,6 @@ class PrototypeModel(ProtoTorchBolt):
|
|||||||
"""Only an alias for the prototypes."""
|
"""Only an alias for the prototypes."""
|
||||||
return self.prototypes
|
return self.prototypes
|
||||||
|
|
||||||
def configure_optimizers(self):
|
|
||||||
optimizer = self.optimizer(self.parameters(), lr=self.hparams.lr)
|
|
||||||
if self.lr_scheduler is not None:
|
|
||||||
scheduler = self.lr_scheduler(optimizer,
|
|
||||||
**self.lr_scheduler_kwargs)
|
|
||||||
sch = {
|
|
||||||
"scheduler": scheduler,
|
|
||||||
"interval": "step",
|
|
||||||
} # called after each training step
|
|
||||||
return [optimizer], [sch]
|
|
||||||
else:
|
|
||||||
return optimizer
|
|
||||||
|
|
||||||
@final
|
|
||||||
def reconfigure_optimizers(self):
|
|
||||||
self.trainer.accelerator_backend.setup_optimizers(self.trainer)
|
|
||||||
|
|
||||||
def add_prototypes(self, *args, **kwargs):
|
def add_prototypes(self, *args, **kwargs):
|
||||||
self.proto_layer.add_components(*args, **kwargs)
|
self.proto_layer.add_components(*args, **kwargs)
|
||||||
self.reconfigure_optimizers()
|
self.reconfigure_optimizers()
|
||||||
@@ -85,17 +84,15 @@ class UnsupervisedPrototypeModel(PrototypeModel):
|
|||||||
super().__init__(hparams, **kwargs)
|
super().__init__(hparams, **kwargs)
|
||||||
|
|
||||||
# Layers
|
# Layers
|
||||||
prototype_initializer = kwargs.get("prototype_initializer", None)
|
prototypes_initializer = kwargs.get("prototypes_initializer", None)
|
||||||
initialized_prototypes = kwargs.get("initialized_prototypes", None)
|
if prototypes_initializer is not None:
|
||||||
if prototype_initializer is not None or initialized_prototypes is not None:
|
|
||||||
self.proto_layer = Components(
|
self.proto_layer = Components(
|
||||||
self.hparams.num_prototypes,
|
self.hparams.num_prototypes,
|
||||||
initializer=prototype_initializer,
|
initializer=prototypes_initializer,
|
||||||
initialized_components=initialized_prototypes,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def compute_distances(self, x):
|
def compute_distances(self, x):
|
||||||
protos = self.proto_layer()
|
protos = self.proto_layer().type_as(x)
|
||||||
distances = self.distance_layer(x, protos)
|
distances = self.distance_layer(x, protos)
|
||||||
return distances
|
return distances
|
||||||
|
|
||||||
@@ -109,23 +106,24 @@ class SupervisedPrototypeModel(PrototypeModel):
|
|||||||
super().__init__(hparams, **kwargs)
|
super().__init__(hparams, **kwargs)
|
||||||
|
|
||||||
# Layers
|
# Layers
|
||||||
prototype_initializer = kwargs.get("prototype_initializer", None)
|
prototypes_initializer = kwargs.get("prototypes_initializer", None)
|
||||||
initialized_prototypes = kwargs.get("initialized_prototypes", None)
|
labels_initializer = kwargs.get("labels_initializer",
|
||||||
if prototype_initializer is not None or initialized_prototypes is not None:
|
LabelsInitializer())
|
||||||
|
if prototypes_initializer is not None:
|
||||||
self.proto_layer = LabeledComponents(
|
self.proto_layer = LabeledComponents(
|
||||||
distribution=self.hparams.distribution,
|
distribution=self.hparams.distribution,
|
||||||
initializer=prototype_initializer,
|
components_initializer=prototypes_initializer,
|
||||||
initialized_components=initialized_prototypes,
|
labels_initializer=labels_initializer,
|
||||||
)
|
)
|
||||||
self.competition_layer = WTAC()
|
self.competition_layer = WTAC()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prototype_labels(self):
|
def prototype_labels(self):
|
||||||
return self.proto_layer.component_labels.detach().cpu()
|
return self.proto_layer.labels.detach().cpu()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def num_classes(self):
|
def num_classes(self):
|
||||||
return len(self.proto_layer.distribution)
|
return self.proto_layer.num_classes
|
||||||
|
|
||||||
def compute_distances(self, x):
|
def compute_distances(self, x):
|
||||||
protos, _ = self.proto_layer()
|
protos, _ = self.proto_layer()
|
||||||
@@ -134,15 +132,14 @@ class SupervisedPrototypeModel(PrototypeModel):
|
|||||||
|
|
||||||
def forward(self, x):
|
def forward(self, x):
|
||||||
distances = self.compute_distances(x)
|
distances = self.compute_distances(x)
|
||||||
y_pred = self.predict_from_distances(distances)
|
_, plabels = self.proto_layer()
|
||||||
# TODO
|
winning = stratified_min_pooling(distances, plabels)
|
||||||
y_pred = torch.eye(self.num_classes, device=self.device)[
|
y_pred = torch.nn.functional.softmin(winning)
|
||||||
y_pred.long()] # depends on labels {0,...,num_classes}
|
|
||||||
return y_pred
|
return y_pred
|
||||||
|
|
||||||
def predict_from_distances(self, distances):
|
def predict_from_distances(self, distances):
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
plabels = self.proto_layer.component_labels
|
_, plabels = self.proto_layer()
|
||||||
y_pred = self.competition_layer(distances, plabels)
|
y_pred = self.competition_layer(distances, plabels)
|
||||||
return y_pred
|
return y_pred
|
||||||
|
|
||||||
@@ -164,27 +161,10 @@ class SupervisedPrototypeModel(PrototypeModel):
|
|||||||
prog_bar=True,
|
prog_bar=True,
|
||||||
logger=True)
|
logger=True)
|
||||||
|
|
||||||
|
def test_step(self, batch, batch_idx):
|
||||||
|
x, targets = batch
|
||||||
|
|
||||||
class NonGradientMixin(ProtoTorchMixin):
|
preds = self.predict(x)
|
||||||
"""Mixin for custom non-gradient optimization."""
|
accuracy = torchmetrics.functional.accuracy(preds.int(), targets.int())
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.automatic_optimization: Final = False
|
|
||||||
|
|
||||||
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
self.log("test_acc", accuracy)
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class ImagePrototypesMixin(ProtoTorchMixin):
|
|
||||||
"""Mixin for models with image prototypes."""
|
|
||||||
@final
|
|
||||||
def on_train_batch_end(self, outputs, batch, batch_idx, dataloader_idx):
|
|
||||||
"""Constrain the components to the range [0, 1] by clamping after updates."""
|
|
||||||
self.proto_layer.components.data.clamp_(0.0, 1.0)
|
|
||||||
|
|
||||||
def get_prototype_grid(self, num_columns=2, return_channels_last=True):
|
|
||||||
from torchvision.utils import make_grid
|
|
||||||
grid = make_grid(self.components, nrow=num_columns)
|
|
||||||
if return_channels_last:
|
|
||||||
grid = grid.permute((1, 2, 0))
|
|
||||||
return grid.cpu()
|
|
||||||
|
@@ -4,7 +4,8 @@ import logging
|
|||||||
|
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
import torch
|
import torch
|
||||||
from prototorch.components import Components
|
from prototorch.core.components import Components
|
||||||
|
from prototorch.core.initializers import LiteralCompInitializer
|
||||||
|
|
||||||
from .extras import ConnectionTopology
|
from .extras import ConnectionTopology
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ class PruneLoserPrototypes(pl.Callback):
|
|||||||
prune_quota_per_epoch=-1,
|
prune_quota_per_epoch=-1,
|
||||||
frequency=1,
|
frequency=1,
|
||||||
replace=False,
|
replace=False,
|
||||||
initializer=None,
|
prototypes_initializer=None,
|
||||||
verbose=False):
|
verbose=False):
|
||||||
self.threshold = threshold # minimum win ratio
|
self.threshold = threshold # minimum win ratio
|
||||||
self.idle_epochs = idle_epochs # epochs to wait before pruning
|
self.idle_epochs = idle_epochs # epochs to wait before pruning
|
||||||
@@ -24,7 +25,7 @@ class PruneLoserPrototypes(pl.Callback):
|
|||||||
self.frequency = frequency
|
self.frequency = frequency
|
||||||
self.replace = replace
|
self.replace = replace
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.initializer = initializer
|
self.prototypes_initializer = prototypes_initializer
|
||||||
|
|
||||||
def on_epoch_end(self, trainer, pl_module):
|
def on_epoch_end(self, trainer, pl_module):
|
||||||
if (trainer.current_epoch + 1) < self.idle_epochs:
|
if (trainer.current_epoch + 1) < self.idle_epochs:
|
||||||
@@ -54,9 +55,10 @@ class PruneLoserPrototypes(pl.Callback):
|
|||||||
distribution = dict(zip(labels.tolist(), counts.tolist()))
|
distribution = dict(zip(labels.tolist(), counts.tolist()))
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"Re-adding pruned prototypes...")
|
print(f"Re-adding pruned prototypes...")
|
||||||
print(f"{distribution=}")
|
print(f"distribution={distribution}")
|
||||||
pl_module.add_prototypes(distribution=distribution,
|
pl_module.add_prototypes(
|
||||||
initializer=self.initializer)
|
distribution=distribution,
|
||||||
|
components_initializer=self.prototypes_initializer)
|
||||||
new_num_protos = pl_module.num_prototypes
|
new_num_protos = pl_module.num_prototypes
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"`num_prototypes` changed from {cur_num_protos} "
|
print(f"`num_prototypes` changed from {cur_num_protos} "
|
||||||
@@ -116,7 +118,8 @@ class GNGCallback(pl.Callback):
|
|||||||
|
|
||||||
# Add component
|
# Add component
|
||||||
pl_module.proto_layer.add_components(
|
pl_module.proto_layer.add_components(
|
||||||
initialized_components=new_component.unsqueeze(0))
|
None,
|
||||||
|
initializer=LiteralCompInitializer(new_component.unsqueeze(0)))
|
||||||
|
|
||||||
# Adjust Topology
|
# Adjust Topology
|
||||||
topology.add_prototype()
|
topology.add_prototype()
|
||||||
@@ -131,4 +134,4 @@ class GNGCallback(pl.Callback):
|
|||||||
pl_module.errors[
|
pl_module.errors[
|
||||||
worst_neighbor] = errors[worst_neighbor] * self.reduction
|
worst_neighbor] = errors[worst_neighbor] * self.reduction
|
||||||
|
|
||||||
trainer.accelerator_backend.setup_optimizers(trainer)
|
trainer.accelerator.setup_optimizers(trainer)
|
||||||
|
@@ -1,49 +1,54 @@
|
|||||||
import torch
|
import torch
|
||||||
import torchmetrics
|
import torchmetrics
|
||||||
|
from prototorch.core.competitions import CBCC
|
||||||
|
from prototorch.core.components import ReasoningComponents
|
||||||
|
from prototorch.core.initializers import RandomReasoningsInitializer
|
||||||
|
from prototorch.core.losses import MarginLoss
|
||||||
|
from prototorch.core.similarities import euclidean_similarity
|
||||||
|
from prototorch.nn.wrappers import LambdaLayer
|
||||||
|
|
||||||
from .abstract import ImagePrototypesMixin
|
|
||||||
from .extras import (CosineSimilarity, MarginLoss, ReasoningLayer,
|
|
||||||
euclidean_similarity, rescaled_cosine_similarity,
|
|
||||||
shift_activation)
|
|
||||||
from .glvq import SiameseGLVQ
|
from .glvq import SiameseGLVQ
|
||||||
|
from .mixin import ImagePrototypesMixin
|
||||||
|
|
||||||
|
|
||||||
class CBC(SiameseGLVQ):
|
class CBC(SiameseGLVQ):
|
||||||
"""Classification-By-Components."""
|
"""Classification-By-Components."""
|
||||||
def __init__(self, hparams, margin=0.1, **kwargs):
|
def __init__(self, hparams, **kwargs):
|
||||||
super().__init__(hparams, **kwargs)
|
super().__init__(hparams, **kwargs)
|
||||||
self.margin = margin
|
|
||||||
self.similarity_fn = kwargs.get("similarity_fn", euclidean_similarity)
|
|
||||||
num_components = self.components.shape[0]
|
|
||||||
self.reasoning_layer = ReasoningLayer(num_components=num_components,
|
|
||||||
num_classes=self.num_classes)
|
|
||||||
self.component_layer = self.proto_layer
|
|
||||||
|
|
||||||
@property
|
similarity_fn = kwargs.get("similarity_fn", euclidean_similarity)
|
||||||
def components(self):
|
components_initializer = kwargs.get("components_initializer", None)
|
||||||
return self.prototypes
|
reasonings_initializer = kwargs.get("reasonings_initializer",
|
||||||
|
RandomReasoningsInitializer())
|
||||||
|
self.components_layer = ReasoningComponents(
|
||||||
|
self.hparams.distribution,
|
||||||
|
components_initializer=components_initializer,
|
||||||
|
reasonings_initializer=reasonings_initializer,
|
||||||
|
)
|
||||||
|
self.similarity_layer = LambdaLayer(similarity_fn)
|
||||||
|
self.competition_layer = CBCC()
|
||||||
|
|
||||||
@property
|
# Namespace hook
|
||||||
def reasonings(self):
|
self.proto_layer = self.components_layer
|
||||||
return self.reasoning_layer.reasonings.cpu()
|
|
||||||
|
self.loss = MarginLoss(self.hparams.margin)
|
||||||
|
|
||||||
def forward(self, x):
|
def forward(self, x):
|
||||||
components, _ = self.component_layer()
|
components, reasonings = self.components_layer()
|
||||||
latent_x = self.backbone(x)
|
latent_x = self.backbone(x)
|
||||||
self.backbone.requires_grad_(self.both_path_gradients)
|
self.backbone.requires_grad_(self.both_path_gradients)
|
||||||
latent_components = self.backbone(components)
|
latent_components = self.backbone(components)
|
||||||
self.backbone.requires_grad_(True)
|
self.backbone.requires_grad_(True)
|
||||||
detections = self.similarity_fn(latent_x, latent_components)
|
detections = self.similarity_layer(latent_x, latent_components)
|
||||||
probs = self.reasoning_layer(detections)
|
probs = self.competition_layer(detections, reasonings)
|
||||||
return probs
|
return probs
|
||||||
|
|
||||||
def shared_step(self, batch, batch_idx, optimizer_idx=None):
|
def shared_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
x, y = batch
|
x, y = batch
|
||||||
# x = x.view(x.size(0), -1)
|
|
||||||
y_pred = self(x)
|
y_pred = self(x)
|
||||||
num_classes = self.reasoning_layer.num_classes
|
num_classes = self.num_classes
|
||||||
y_true = torch.nn.functional.one_hot(y.long(), num_classes=num_classes)
|
y_true = torch.nn.functional.one_hot(y.long(), num_classes=num_classes)
|
||||||
loss = MarginLoss(self.margin)(y_pred, y_true).mean(dim=0)
|
loss = self.loss(y_pred, y_true).mean()
|
||||||
return y_pred, loss
|
return y_pred, loss
|
||||||
|
|
||||||
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
@@ -70,7 +75,3 @@ class ImageCBC(ImagePrototypesMixin, CBC):
|
|||||||
"""CBC model that constrains the components to the range [0, 1] by
|
"""CBC model that constrains the components to the range [0, 1] by
|
||||||
clamping after updates.
|
clamping after updates.
|
||||||
"""
|
"""
|
||||||
def __init__(self, hparams, **kwargs):
|
|
||||||
super().__init__(hparams, **kwargs)
|
|
||||||
# Namespace hook
|
|
||||||
self.proto_layer = self.component_layer
|
|
||||||
|
0
prototorch/models/clcc/__init__.py
Normal file
0
prototorch/models/clcc/__init__.py
Normal file
86
prototorch/models/clcc/clcc_glvq.py
Normal file
86
prototorch/models/clcc/clcc_glvq.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from prototorch.core.competitions import WTAC
|
||||||
|
from prototorch.core.components import LabeledComponents
|
||||||
|
from prototorch.core.distances import euclidean_distance
|
||||||
|
from prototorch.core.initializers import AbstractComponentsInitializer, LabelsInitializer
|
||||||
|
from prototorch.core.losses import GLVQLoss
|
||||||
|
from prototorch.models.clcc.clcc_scheme import CLCCScheme
|
||||||
|
from prototorch.nn.wrappers import LambdaLayer
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GLVQhparams:
|
||||||
|
distribution: dict
|
||||||
|
component_initializer: AbstractComponentsInitializer
|
||||||
|
distance_fn: Callable = euclidean_distance
|
||||||
|
lr: float = 0.01
|
||||||
|
margin: float = 0.0
|
||||||
|
# TODO: make nicer
|
||||||
|
transfer_fn: str = "identity"
|
||||||
|
transfer_beta: float = 10.0
|
||||||
|
optimizer: torch.optim.Optimizer = torch.optim.Adam
|
||||||
|
|
||||||
|
|
||||||
|
class GLVQ(CLCCScheme):
|
||||||
|
def __init__(self, hparams: GLVQhparams) -> None:
|
||||||
|
super().__init__(hparams)
|
||||||
|
self.lr = hparams.lr
|
||||||
|
self.optimizer = hparams.optimizer
|
||||||
|
|
||||||
|
# Initializers
|
||||||
|
def init_components(self, hparams):
|
||||||
|
# initialize Component Layer
|
||||||
|
self.components_layer = LabeledComponents(
|
||||||
|
distribution=hparams.distribution,
|
||||||
|
components_initializer=hparams.component_initializer,
|
||||||
|
labels_initializer=LabelsInitializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def init_comparison(self, hparams):
|
||||||
|
# initialize Distance Layer
|
||||||
|
self.comparison_layer = LambdaLayer(hparams.distance_fn)
|
||||||
|
|
||||||
|
def init_inference(self, hparams):
|
||||||
|
self.competition_layer = WTAC()
|
||||||
|
|
||||||
|
def init_loss(self, hparams):
|
||||||
|
self.loss_layer = GLVQLoss(
|
||||||
|
margin=hparams.margin,
|
||||||
|
transfer_fn=hparams.transfer_fn,
|
||||||
|
beta=hparams.transfer_beta,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Steps
|
||||||
|
def comparison(self, batch, components):
|
||||||
|
comp_tensor, _ = components
|
||||||
|
batch_tensor, _ = batch
|
||||||
|
|
||||||
|
comp_tensor = comp_tensor.unsqueeze(1)
|
||||||
|
|
||||||
|
distances = self.comparison_layer(batch_tensor, comp_tensor)
|
||||||
|
|
||||||
|
return distances
|
||||||
|
|
||||||
|
def inference(self, comparisonmeasures, components):
|
||||||
|
comp_labels = components[1]
|
||||||
|
return self.competition_layer(comparisonmeasures, comp_labels)
|
||||||
|
|
||||||
|
def loss(self, comparisonmeasures, batch, components):
|
||||||
|
target = batch[1]
|
||||||
|
comp_labels = components[1]
|
||||||
|
return self.loss_layer(comparisonmeasures, target, comp_labels)
|
||||||
|
|
||||||
|
def configure_optimizers(self):
|
||||||
|
return self.optimizer(self.parameters(), lr=self.lr)
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
@property
|
||||||
|
def prototypes(self):
|
||||||
|
return self.components_layer.components.detach().cpu()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prototype_labels(self):
|
||||||
|
return self.components_layer.labels.detach().cpu()
|
192
prototorch/models/clcc/clcc_scheme.py
Normal file
192
prototorch/models/clcc/clcc_scheme.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
"""
|
||||||
|
CLCC Scheme
|
||||||
|
|
||||||
|
CLCC is a LVQ scheme containing 4 steps
|
||||||
|
- Components
|
||||||
|
- Latent Space
|
||||||
|
- Comparison
|
||||||
|
- Competition
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Dict, Set, Type
|
||||||
|
|
||||||
|
import pytorch_lightning as pl
|
||||||
|
import torch
|
||||||
|
import torchmetrics
|
||||||
|
|
||||||
|
|
||||||
|
class CLCCScheme(pl.LightningModule):
|
||||||
|
registered_metrics: Dict[Type[torchmetrics.Metric],
|
||||||
|
torchmetrics.Metric] = {}
|
||||||
|
registered_metric_names: Dict[Type[torchmetrics.Metric], Set[str]] = {}
|
||||||
|
|
||||||
|
def __init__(self, hparams) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Common Steps
|
||||||
|
self.init_components(hparams)
|
||||||
|
self.init_latent(hparams)
|
||||||
|
self.init_comparison(hparams)
|
||||||
|
self.init_competition(hparams)
|
||||||
|
|
||||||
|
# Train Steps
|
||||||
|
self.init_loss(hparams)
|
||||||
|
|
||||||
|
# Inference Steps
|
||||||
|
self.init_inference(hparams)
|
||||||
|
|
||||||
|
# Initialize Model Metrics
|
||||||
|
self.init_model_metrics()
|
||||||
|
|
||||||
|
# internal API, called by models and callbacks
|
||||||
|
def register_torchmetric(self, name: str, metric: torchmetrics.Metric):
|
||||||
|
if metric not in self.registered_metrics:
|
||||||
|
self.registered_metrics[metric] = metric()
|
||||||
|
self.registered_metric_names[metric] = {name}
|
||||||
|
else:
|
||||||
|
self.registered_metric_names[metric].add(name)
|
||||||
|
|
||||||
|
# external API
|
||||||
|
def get_competion(self, batch, components):
|
||||||
|
latent_batch, latent_components = self.latent(batch, components)
|
||||||
|
# TODO: => Latent Hook
|
||||||
|
comparison_tensor = self.comparison(latent_batch, latent_components)
|
||||||
|
# TODO: => Comparison Hook
|
||||||
|
return comparison_tensor
|
||||||
|
|
||||||
|
def forward(self, batch):
|
||||||
|
if isinstance(batch, torch.Tensor):
|
||||||
|
batch = (batch, None)
|
||||||
|
# TODO: manage different datatypes?
|
||||||
|
components = self.components_layer()
|
||||||
|
# TODO: => Component Hook
|
||||||
|
comparison_tensor = self.get_competion(batch, components)
|
||||||
|
# TODO: => Competition Hook
|
||||||
|
return self.inference(comparison_tensor, components)
|
||||||
|
|
||||||
|
def predict(self, batch):
|
||||||
|
"""
|
||||||
|
Alias for forward
|
||||||
|
"""
|
||||||
|
return self.forward(batch)
|
||||||
|
|
||||||
|
def loss_forward(self, batch):
|
||||||
|
# TODO: manage different datatypes?
|
||||||
|
components = self.components_layer()
|
||||||
|
# TODO: => Component Hook
|
||||||
|
comparison_tensor = self.get_competion(batch, components)
|
||||||
|
# TODO: => Competition Hook
|
||||||
|
return self.loss(comparison_tensor, batch, components)
|
||||||
|
|
||||||
|
# Empty Initialization
|
||||||
|
# TODO: Type hints
|
||||||
|
# TODO: Docs
|
||||||
|
def init_components(self, hparams):
|
||||||
|
...
|
||||||
|
|
||||||
|
def init_latent(self, hparams):
|
||||||
|
...
|
||||||
|
|
||||||
|
def init_comparison(self, hparams):
|
||||||
|
...
|
||||||
|
|
||||||
|
def init_competition(self, hparams):
|
||||||
|
...
|
||||||
|
|
||||||
|
def init_loss(self, hparams):
|
||||||
|
...
|
||||||
|
|
||||||
|
def init_inference(self, hparams):
|
||||||
|
...
|
||||||
|
|
||||||
|
def init_model_metrics(self):
|
||||||
|
self.register_torchmetric('train_accuracy', torchmetrics.Accuracy)
|
||||||
|
|
||||||
|
# Empty Steps
|
||||||
|
# TODO: Type hints
|
||||||
|
def components(self):
|
||||||
|
"""
|
||||||
|
This step has no input.
|
||||||
|
|
||||||
|
It returns the components.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(
|
||||||
|
"The components step has no reasonable default.")
|
||||||
|
|
||||||
|
def latent(self, batch, components):
|
||||||
|
"""
|
||||||
|
The latent step receives the data batch and the components.
|
||||||
|
It can transform both by an arbitrary function.
|
||||||
|
|
||||||
|
It returns the transformed batch and components, each of the same length as the original input.
|
||||||
|
"""
|
||||||
|
return batch, components
|
||||||
|
|
||||||
|
def comparison(self, batch, components):
|
||||||
|
"""
|
||||||
|
Takes a batch of size N and the componentsset of size M.
|
||||||
|
|
||||||
|
It returns an NxMxD tensor containing D (usually 1) pairwise comparison measures.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(
|
||||||
|
"The comparison step has no reasonable default.")
|
||||||
|
|
||||||
|
def competition(self, comparisonmeasures, components):
|
||||||
|
"""
|
||||||
|
Takes the tensor of comparison measures.
|
||||||
|
|
||||||
|
Assigns a competition vector to each class.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(
|
||||||
|
"The competition step has no reasonable default.")
|
||||||
|
|
||||||
|
def loss(self, comparisonmeasures, batch, components):
|
||||||
|
"""
|
||||||
|
Takes the tensor of competition measures.
|
||||||
|
|
||||||
|
Calculates a single loss value
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("The loss step has no reasonable default.")
|
||||||
|
|
||||||
|
def inference(self, comparisonmeasures, components):
|
||||||
|
"""
|
||||||
|
Takes the tensor of competition measures.
|
||||||
|
|
||||||
|
Returns the inferred vector.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(
|
||||||
|
"The inference step has no reasonable default.")
|
||||||
|
|
||||||
|
def update_metrics_step(self, batch):
|
||||||
|
x, y = batch
|
||||||
|
preds = self(x)
|
||||||
|
|
||||||
|
for metric in self.registered_metrics:
|
||||||
|
instance = self.registered_metrics[metric].to(self.device)
|
||||||
|
value = instance(y, preds)
|
||||||
|
|
||||||
|
for name in self.registered_metric_names[metric]:
|
||||||
|
self.log(name, value)
|
||||||
|
|
||||||
|
def update_metrics_epoch(self):
|
||||||
|
for metric in self.registered_metrics:
|
||||||
|
instance = self.registered_metrics[metric].to(self.device)
|
||||||
|
value = instance.compute()
|
||||||
|
|
||||||
|
for name in self.registered_metric_names[metric]:
|
||||||
|
self.log(name, value)
|
||||||
|
|
||||||
|
# Lightning Hooks
|
||||||
|
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
|
self.update_metrics_step(batch)
|
||||||
|
|
||||||
|
return self.loss_forward(batch)
|
||||||
|
|
||||||
|
def train_epoch_end(self, outs) -> None:
|
||||||
|
self.update_metrics_epoch()
|
||||||
|
|
||||||
|
def validation_step(self, batch, batch_idx):
|
||||||
|
return self.loss_forward(batch)
|
||||||
|
|
||||||
|
def test_step(self, batch, batch_idx):
|
||||||
|
return self.loss_forward(batch)
|
76
prototorch/models/clcc/test_clcc.py
Normal file
76
prototorch/models/clcc/test_clcc.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import prototorch as pt
|
||||||
|
import pytorch_lightning as pl
|
||||||
|
import torch
|
||||||
|
import torchmetrics
|
||||||
|
from prototorch.core.initializers import SMCI, RandomNormalCompInitializer
|
||||||
|
from prototorch.models.clcc.clcc_glvq import GLVQ, GLVQhparams
|
||||||
|
from prototorch.models.clcc.clcc_scheme import CLCCScheme
|
||||||
|
from prototorch.models.vis import Visualize2DVoronoiCallback
|
||||||
|
|
||||||
|
# NEW STUFF
|
||||||
|
# ##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Metrics
|
||||||
|
class MetricsTestCallback(pl.Callback):
|
||||||
|
metric_name = "test_cb_acc"
|
||||||
|
|
||||||
|
def setup(self,
|
||||||
|
trainer: pl.Trainer,
|
||||||
|
pl_module: CLCCScheme,
|
||||||
|
stage: Optional[str] = None) -> None:
|
||||||
|
pl_module.register_torchmetric(self.metric_name, torchmetrics.Accuracy)
|
||||||
|
|
||||||
|
def on_epoch_end(self, trainer: pl.Trainer,
|
||||||
|
pl_module: pl.LightningModule) -> None:
|
||||||
|
metric = trainer.logged_metrics[self.metric_name]
|
||||||
|
if metric > 0.95:
|
||||||
|
trainer.should_stop = True
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Pruning
|
||||||
|
|
||||||
|
# ##############################################################################
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Dataset
|
||||||
|
train_ds = pt.datasets.Iris(dims=[0, 2])
|
||||||
|
# Dataloaders
|
||||||
|
train_loader = torch.utils.data.DataLoader(train_ds,
|
||||||
|
batch_size=64,
|
||||||
|
num_workers=8)
|
||||||
|
|
||||||
|
components_initializer = SMCI(train_ds)
|
||||||
|
|
||||||
|
hparams = GLVQhparams(
|
||||||
|
distribution=dict(
|
||||||
|
num_classes=3,
|
||||||
|
per_class=2,
|
||||||
|
),
|
||||||
|
component_initializer=components_initializer,
|
||||||
|
)
|
||||||
|
model = GLVQ(hparams)
|
||||||
|
|
||||||
|
print(model)
|
||||||
|
# Callbacks
|
||||||
|
vis = Visualize2DVoronoiCallback(
|
||||||
|
data=train_ds,
|
||||||
|
resolution=500,
|
||||||
|
)
|
||||||
|
metrics = MetricsTestCallback()
|
||||||
|
|
||||||
|
# Train
|
||||||
|
trainer = pl.Trainer(
|
||||||
|
callbacks=[
|
||||||
|
#vis,
|
||||||
|
metrics,
|
||||||
|
],
|
||||||
|
gpus=1,
|
||||||
|
max_epochs=100,
|
||||||
|
weights_summary=None,
|
||||||
|
log_every_n_steps=1,
|
||||||
|
)
|
||||||
|
trainer.fit(model, train_loader)
|
@@ -1,124 +0,0 @@
|
|||||||
"""Prototorch Data Modules
|
|
||||||
|
|
||||||
This allows to store the used dataset inside a Lightning Module.
|
|
||||||
Mainly used for PytorchLightningCLI configurations.
|
|
||||||
"""
|
|
||||||
from typing import Any, Optional, Type
|
|
||||||
|
|
||||||
import pytorch_lightning as pl
|
|
||||||
from torch.utils.data import DataLoader, Dataset, random_split
|
|
||||||
from torchvision import transforms
|
|
||||||
from torchvision.datasets import MNIST
|
|
||||||
|
|
||||||
import prototorch as pt
|
|
||||||
|
|
||||||
|
|
||||||
# MNIST
|
|
||||||
class MNISTDataModule(pl.LightningDataModule):
|
|
||||||
def __init__(self, batch_size=32):
|
|
||||||
super().__init__()
|
|
||||||
self.batch_size = batch_size
|
|
||||||
|
|
||||||
# Download mnist dataset as side-effect, only called on the first cpu
|
|
||||||
def prepare_data(self):
|
|
||||||
MNIST("~/datasets", train=True, download=True)
|
|
||||||
MNIST("~/datasets", train=False, download=True)
|
|
||||||
|
|
||||||
# called for every GPU/machine (assigning state is OK)
|
|
||||||
def setup(self, stage=None):
|
|
||||||
# Transforms
|
|
||||||
transform = transforms.Compose([
|
|
||||||
transforms.ToTensor(),
|
|
||||||
])
|
|
||||||
# Split dataset
|
|
||||||
if stage in (None, "fit"):
|
|
||||||
mnist_train = MNIST("~/datasets", train=True, transform=transform)
|
|
||||||
self.mnist_train, self.mnist_val = random_split(
|
|
||||||
mnist_train,
|
|
||||||
[55000, 5000],
|
|
||||||
)
|
|
||||||
if stage == (None, "test"):
|
|
||||||
self.mnist_test = MNIST(
|
|
||||||
"~/datasets",
|
|
||||||
train=False,
|
|
||||||
transform=transform,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Dataloaders
|
|
||||||
def train_dataloader(self):
|
|
||||||
mnist_train = DataLoader(self.mnist_train, batch_size=self.batch_size)
|
|
||||||
return mnist_train
|
|
||||||
|
|
||||||
def val_dataloader(self):
|
|
||||||
mnist_val = DataLoader(self.mnist_val, batch_size=self.batch_size)
|
|
||||||
return mnist_val
|
|
||||||
|
|
||||||
def test_dataloader(self):
|
|
||||||
mnist_test = DataLoader(self.mnist_test, batch_size=self.batch_size)
|
|
||||||
return mnist_test
|
|
||||||
|
|
||||||
|
|
||||||
# def train_on_mnist(batch_size=256) -> type:
|
|
||||||
# class DataClass(pl.LightningModule):
|
|
||||||
# datamodule = MNISTDataModule(batch_size=batch_size)
|
|
||||||
|
|
||||||
# def __init__(self, *args, **kwargs):
|
|
||||||
# prototype_initializer = kwargs.pop(
|
|
||||||
# "prototype_initializer", pt.components.Zeros((28, 28, 1)))
|
|
||||||
# super().__init__(*args,
|
|
||||||
# prototype_initializer=prototype_initializer,
|
|
||||||
# **kwargs)
|
|
||||||
|
|
||||||
# dc: Type[DataClass] = DataClass
|
|
||||||
# return dc
|
|
||||||
|
|
||||||
|
|
||||||
# ABSTRACT
|
|
||||||
class GeneralDataModule(pl.LightningDataModule):
|
|
||||||
def __init__(self, dataset: Dataset, batch_size: int = 32) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.train_dataset = dataset
|
|
||||||
self.batch_size = batch_size
|
|
||||||
|
|
||||||
def train_dataloader(self) -> DataLoader:
|
|
||||||
return DataLoader(self.train_dataset, batch_size=self.batch_size)
|
|
||||||
|
|
||||||
|
|
||||||
# def train_on_dataset(dataset: Dataset, batch_size: int = 256):
|
|
||||||
# class DataClass(pl.LightningModule):
|
|
||||||
# datamodule = GeneralDataModule(dataset, batch_size)
|
|
||||||
# datashape = dataset[0][0].shape
|
|
||||||
# example_input_array = torch.zeros_like(dataset[0][0]).unsqueeze(0)
|
|
||||||
|
|
||||||
# def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
# prototype_initializer = kwargs.pop(
|
|
||||||
# "prototype_initializer",
|
|
||||||
# pt.components.Zeros(self.datashape),
|
|
||||||
# )
|
|
||||||
# super().__init__(*args,
|
|
||||||
# prototype_initializer=prototype_initializer,
|
|
||||||
# **kwargs)
|
|
||||||
|
|
||||||
# return DataClass
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# from prototorch.models import GLVQ
|
|
||||||
|
|
||||||
# demo_dataset = pt.datasets.Iris()
|
|
||||||
|
|
||||||
# TrainingClass: Type = train_on_dataset(demo_dataset)
|
|
||||||
|
|
||||||
# class DemoGLVQ(TrainingClass, GLVQ):
|
|
||||||
# """Model Definition."""
|
|
||||||
|
|
||||||
# # Hyperparameters
|
|
||||||
# hparams = dict(
|
|
||||||
# distribution={
|
|
||||||
# "num_classes": 3,
|
|
||||||
# "prototypes_per_class": 4
|
|
||||||
# },
|
|
||||||
# lr=0.01,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# initialized = DemoGLVQ(hparams)
|
|
||||||
# print(initialized)
|
|
@@ -5,23 +5,31 @@ Modules not yet available in prototorch go here temporarily.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from prototorch.functions.distances import euclidean_distance
|
from prototorch.core.similarities import gaussian
|
||||||
from prototorch.functions.similarities import cosine_similarity
|
|
||||||
|
|
||||||
|
|
||||||
def rescaled_cosine_similarity(x, y):
|
def rank_scaled_gaussian(distances, lambd):
|
||||||
"""Cosine Similarity rescaled to [0, 1]."""
|
order = torch.argsort(distances, dim=1)
|
||||||
similarities = cosine_similarity(x, y)
|
ranks = torch.argsort(order, dim=1)
|
||||||
return (similarities + 1.0) / 2.0
|
return torch.exp(-torch.exp(-ranks / lambd) * distances)
|
||||||
|
|
||||||
|
|
||||||
def shift_activation(x):
|
class GaussianPrior(torch.nn.Module):
|
||||||
return (x + 1.0) / 2.0
|
def __init__(self, variance):
|
||||||
|
super().__init__()
|
||||||
|
self.variance = variance
|
||||||
|
|
||||||
|
def forward(self, distances):
|
||||||
|
return gaussian(distances, self.variance)
|
||||||
|
|
||||||
|
|
||||||
def euclidean_similarity(x, y, variance=1.0):
|
class RankScaledGaussianPrior(torch.nn.Module):
|
||||||
d = euclidean_distance(x, y)
|
def __init__(self, lambd):
|
||||||
return torch.exp(-(d * d) / (2 * variance))
|
super().__init__()
|
||||||
|
self.lambd = lambd
|
||||||
|
|
||||||
|
def forward(self, distances):
|
||||||
|
return rank_scaled_gaussian(distances, self.lambd)
|
||||||
|
|
||||||
|
|
||||||
class ConnectionTopology(torch.nn.Module):
|
class ConnectionTopology(torch.nn.Module):
|
||||||
@@ -79,64 +87,3 @@ class ConnectionTopology(torch.nn.Module):
|
|||||||
|
|
||||||
def extra_repr(self):
|
def extra_repr(self):
|
||||||
return f"(agelimit): ({self.agelimit})"
|
return f"(agelimit): ({self.agelimit})"
|
||||||
|
|
||||||
|
|
||||||
class CosineSimilarity(torch.nn.Module):
|
|
||||||
def __init__(self, activation=shift_activation):
|
|
||||||
super().__init__()
|
|
||||||
self.activation = activation
|
|
||||||
|
|
||||||
def forward(self, x, y):
|
|
||||||
epsilon = torch.finfo(x.dtype).eps
|
|
||||||
normed_x = (x / x.pow(2).sum(dim=tuple(range(
|
|
||||||
1, x.ndim)), keepdim=True).clamp(min=epsilon).sqrt()).flatten(
|
|
||||||
start_dim=1)
|
|
||||||
normed_y = (y / y.pow(2).sum(dim=tuple(range(
|
|
||||||
1, y.ndim)), keepdim=True).clamp(min=epsilon).sqrt()).flatten(
|
|
||||||
start_dim=1)
|
|
||||||
# normed_x = (x / torch.linalg.norm(x, dim=1))
|
|
||||||
diss = torch.inner(normed_x, normed_y)
|
|
||||||
return self.activation(diss)
|
|
||||||
|
|
||||||
|
|
||||||
class MarginLoss(torch.nn.modules.loss._Loss):
|
|
||||||
def __init__(self,
|
|
||||||
margin=0.3,
|
|
||||||
size_average=None,
|
|
||||||
reduce=None,
|
|
||||||
reduction="mean"):
|
|
||||||
super().__init__(size_average, reduce, reduction)
|
|
||||||
self.margin = margin
|
|
||||||
|
|
||||||
def forward(self, input_, target):
|
|
||||||
dp = torch.sum(target * input_, dim=-1)
|
|
||||||
dm = torch.max(input_ - target, dim=-1).values
|
|
||||||
return torch.nn.functional.relu(dm - dp + self.margin)
|
|
||||||
|
|
||||||
|
|
||||||
class ReasoningLayer(torch.nn.Module):
|
|
||||||
def __init__(self, num_components, num_classes, num_replicas=1):
|
|
||||||
super().__init__()
|
|
||||||
self.num_replicas = num_replicas
|
|
||||||
self.num_classes = num_classes
|
|
||||||
probabilities_init = torch.zeros(2, 1, num_components,
|
|
||||||
self.num_classes)
|
|
||||||
probabilities_init.uniform_(0.4, 0.6)
|
|
||||||
# TODO Use `self.register_parameter("param", Paramater(param))` instead
|
|
||||||
self.reasoning_probabilities = torch.nn.Parameter(probabilities_init)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def reasonings(self):
|
|
||||||
pk = self.reasoning_probabilities[0]
|
|
||||||
nk = (1 - pk) * self.reasoning_probabilities[1]
|
|
||||||
ik = 1 - pk - nk
|
|
||||||
img = torch.cat([pk, nk, ik], dim=0).permute(1, 0, 2)
|
|
||||||
return img.unsqueeze(1)
|
|
||||||
|
|
||||||
def forward(self, detections):
|
|
||||||
pk = self.reasoning_probabilities[0].clamp(0, 1)
|
|
||||||
nk = (1 - pk) * self.reasoning_probabilities[1].clamp(0, 1)
|
|
||||||
numerator = (detections @ (pk - nk)) + nk.sum(1)
|
|
||||||
probs = numerator / (pk + nk).sum(1)
|
|
||||||
probs = probs.squeeze(0)
|
|
||||||
return probs
|
|
||||||
|
@@ -1,17 +1,16 @@
|
|||||||
"""Models based on the GLVQ framework."""
|
"""Models based on the GLVQ framework."""
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from prototorch.functions.activations import get_activation
|
from prototorch.core.competitions import wtac
|
||||||
from prototorch.functions.competitions import wtac
|
from prototorch.core.distances import lomega_distance, omega_distance, squared_euclidean_distance
|
||||||
from prototorch.functions.distances import (lomega_distance, omega_distance,
|
from prototorch.core.initializers import EyeTransformInitializer
|
||||||
squared_euclidean_distance)
|
from prototorch.core.losses import GLVQLoss, lvq1_loss, lvq21_loss
|
||||||
from prototorch.functions.helper import get_flat
|
from prototorch.core.transforms import LinearTransform
|
||||||
from prototorch.functions.losses import glvq_loss, lvq1_loss, lvq21_loss
|
from prototorch.nn.wrappers import LambdaLayer, LossLayer
|
||||||
from prototorch.components import LinearMapping
|
|
||||||
from prototorch.modules import LambdaLayer, LossLayer
|
|
||||||
from torch.nn.parameter import Parameter
|
from torch.nn.parameter import Parameter
|
||||||
|
|
||||||
from .abstract import ImagePrototypesMixin, SupervisedPrototypeModel
|
from .abstract import SupervisedPrototypeModel
|
||||||
|
from .mixin import ImagePrototypesMixin
|
||||||
|
|
||||||
|
|
||||||
class GLVQ(SupervisedPrototypeModel):
|
class GLVQ(SupervisedPrototypeModel):
|
||||||
@@ -20,18 +19,16 @@ class GLVQ(SupervisedPrototypeModel):
|
|||||||
super().__init__(hparams, **kwargs)
|
super().__init__(hparams, **kwargs)
|
||||||
|
|
||||||
# Default hparams
|
# Default hparams
|
||||||
|
self.hparams.setdefault("margin", 0.0)
|
||||||
self.hparams.setdefault("transfer_fn", "identity")
|
self.hparams.setdefault("transfer_fn", "identity")
|
||||||
self.hparams.setdefault("transfer_beta", 10.0)
|
self.hparams.setdefault("transfer_beta", 10.0)
|
||||||
|
|
||||||
# Layers
|
|
||||||
transfer_fn = get_activation(self.hparams.transfer_fn)
|
|
||||||
self.transfer_layer = LambdaLayer(transfer_fn)
|
|
||||||
|
|
||||||
# Loss
|
# Loss
|
||||||
self.loss = LossLayer(glvq_loss)
|
self.loss = GLVQLoss(
|
||||||
|
margin=self.hparams.margin,
|
||||||
# Prototype metrics
|
transfer_fn=self.hparams.transfer_fn,
|
||||||
self.initialize_prototype_win_ratios()
|
beta=self.hparams.transfer_beta,
|
||||||
|
)
|
||||||
|
|
||||||
def initialize_prototype_win_ratios(self):
|
def initialize_prototype_win_ratios(self):
|
||||||
self.register_buffer(
|
self.register_buffer(
|
||||||
@@ -59,10 +56,8 @@ class GLVQ(SupervisedPrototypeModel):
|
|||||||
def shared_step(self, batch, batch_idx, optimizer_idx=None):
|
def shared_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
x, y = batch
|
x, y = batch
|
||||||
out = self.compute_distances(x)
|
out = self.compute_distances(x)
|
||||||
plabels = self.proto_layer.component_labels
|
_, plabels = self.proto_layer()
|
||||||
mu = self.loss(out, y, prototype_labels=plabels)
|
loss = self.loss(out, y, plabels)
|
||||||
batch_loss = self.transfer_layer(mu, beta=self.hparams.transfer_beta)
|
|
||||||
loss = batch_loss.sum(dim=0)
|
|
||||||
return out, loss
|
return out, loss
|
||||||
|
|
||||||
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
@@ -118,7 +113,8 @@ class SiameseGLVQ(GLVQ):
|
|||||||
proto_opt = self.optimizer(self.proto_layer.parameters(),
|
proto_opt = self.optimizer(self.proto_layer.parameters(),
|
||||||
lr=self.hparams.proto_lr)
|
lr=self.hparams.proto_lr)
|
||||||
# Only add a backbone optimizer if backbone has trainable parameters
|
# Only add a backbone optimizer if backbone has trainable parameters
|
||||||
if (bb_params := list(self.backbone.parameters())):
|
bb_params = list(self.backbone.parameters())
|
||||||
|
if (bb_params):
|
||||||
bb_opt = self.optimizer(bb_params, lr=self.hparams.bb_lr)
|
bb_opt = self.optimizer(bb_params, lr=self.hparams.bb_lr)
|
||||||
optimizers = [proto_opt, bb_opt]
|
optimizers = [proto_opt, bb_opt]
|
||||||
else:
|
else:
|
||||||
@@ -135,7 +131,7 @@ class SiameseGLVQ(GLVQ):
|
|||||||
|
|
||||||
def compute_distances(self, x):
|
def compute_distances(self, x):
|
||||||
protos, _ = self.proto_layer()
|
protos, _ = self.proto_layer()
|
||||||
x, protos = get_flat(x, protos)
|
x, protos = (arr.view(arr.size(0), -1) for arr in (x, protos))
|
||||||
latent_x = self.backbone(x)
|
latent_x = self.backbone(x)
|
||||||
self.backbone.requires_grad_(self.both_path_gradients)
|
self.backbone.requires_grad_(self.both_path_gradients)
|
||||||
latent_protos = self.backbone(protos)
|
latent_protos = self.backbone(protos)
|
||||||
@@ -213,18 +209,22 @@ class SiameseGMLVQ(SiameseGLVQ):
|
|||||||
super().__init__(hparams, **kwargs)
|
super().__init__(hparams, **kwargs)
|
||||||
|
|
||||||
# Override the backbone
|
# Override the backbone
|
||||||
self.backbone = torch.nn.Linear(self.hparams.input_dim,
|
omega_initializer = kwargs.get("omega_initializer",
|
||||||
self.hparams.latent_dim,
|
EyeTransformInitializer())
|
||||||
bias=False)
|
self.backbone = LinearTransform(
|
||||||
|
self.hparams.input_dim,
|
||||||
|
self.hparams.output_dim,
|
||||||
|
initializer=omega_initializer,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def omega_matrix(self):
|
def omega_matrix(self):
|
||||||
return self.backbone.weight.detach().cpu()
|
return self.backbone.weights
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lambda_matrix(self):
|
def lambda_matrix(self):
|
||||||
omega = self.backbone.weight # (latent_dim, input_dim)
|
omega = self.backbone.weight # (input_dim, latent_dim)
|
||||||
lam = omega.T @ omega
|
lam = omega @ omega.T
|
||||||
return lam.detach().cpu()
|
return lam.detach().cpu()
|
||||||
|
|
||||||
|
|
||||||
@@ -240,22 +240,24 @@ class GMLVQ(GLVQ):
|
|||||||
super().__init__(hparams, distance_fn=distance_fn, **kwargs)
|
super().__init__(hparams, distance_fn=distance_fn, **kwargs)
|
||||||
|
|
||||||
# Additional parameters
|
# Additional parameters
|
||||||
omega_initializer = kwargs.get("omega_initializer", None)
|
omega_initializer = kwargs.get("omega_initializer",
|
||||||
initialized_omega = kwargs.get("initialized_omega", None)
|
EyeTransformInitializer())
|
||||||
if omega_initializer is not None or initialized_omega is not None:
|
omega = omega_initializer.generate(self.hparams.input_dim,
|
||||||
self.omega_layer = LinearMapping(
|
self.hparams.latent_dim)
|
||||||
mapping_shape=(self.hparams.input_dim, self.hparams.latent_dim),
|
self.register_parameter("_omega", Parameter(omega))
|
||||||
initializer=omega_initializer,
|
self.backbone = LambdaLayer(lambda x: x @ self._omega,
|
||||||
initialized_linearmapping=initialized_omega,
|
name="omega matrix")
|
||||||
)
|
|
||||||
|
|
||||||
self.register_parameter("_omega", Parameter(self.omega_layer.mapping))
|
|
||||||
self.backbone = LambdaLayer(lambda x: x @ self._omega, name = "omega matrix")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def omega_matrix(self):
|
def omega_matrix(self):
|
||||||
return self._omega.detach().cpu()
|
return self._omega.detach().cpu()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lambda_matrix(self):
|
||||||
|
omega = self._omega.detach() # (input_dim, latent_dim)
|
||||||
|
lam = omega @ omega.T
|
||||||
|
return lam.detach().cpu()
|
||||||
|
|
||||||
def compute_distances(self, x):
|
def compute_distances(self, x):
|
||||||
protos, _ = self.proto_layer()
|
protos, _ = self.proto_layer()
|
||||||
distances = self.distance_layer(x, protos, self._omega)
|
distances = self.distance_layer(x, protos, self._omega)
|
||||||
@@ -264,24 +266,6 @@ class GMLVQ(GLVQ):
|
|||||||
def extra_repr(self):
|
def extra_repr(self):
|
||||||
return f"(omega): (shape: {tuple(self._omega.shape)})"
|
return f"(omega): (shape: {tuple(self._omega.shape)})"
|
||||||
|
|
||||||
def predict_latent(self, x, map_protos=True):
|
|
||||||
"""Predict `x` assuming it is already embedded in the latent space.
|
|
||||||
|
|
||||||
Only the prototypes are embedded in the latent space using the
|
|
||||||
backbone.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.eval()
|
|
||||||
with torch.no_grad():
|
|
||||||
protos, plabels = self.proto_layer()
|
|
||||||
if map_protos:
|
|
||||||
protos = self.backbone(protos)
|
|
||||||
d = squared_euclidean_distance(x, protos)
|
|
||||||
y_pred = wtac(d, plabels)
|
|
||||||
return y_pred
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LGMLVQ(GMLVQ):
|
class LGMLVQ(GMLVQ):
|
||||||
"""Localized and Generalized Matrix Learning Vector Quantization."""
|
"""Localized and Generalized Matrix Learning Vector Quantization."""
|
||||||
|
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from prototorch.components import LabeledComponents
|
from prototorch.core.competitions import KNNC
|
||||||
from prototorch.modules import KNNC
|
from prototorch.core.components import LabeledComponents
|
||||||
|
from prototorch.core.initializers import LiteralCompInitializer, LiteralLabelsInitializer
|
||||||
|
from prototorch.utils.utils import parse_data_arg
|
||||||
|
|
||||||
from .abstract import SupervisedPrototypeModel
|
from .abstract import SupervisedPrototypeModel
|
||||||
|
|
||||||
@@ -19,9 +21,13 @@ class KNN(SupervisedPrototypeModel):
|
|||||||
data = kwargs.get("data", None)
|
data = kwargs.get("data", None)
|
||||||
if data is None:
|
if data is None:
|
||||||
raise ValueError("KNN requires data, but was not provided!")
|
raise ValueError("KNN requires data, but was not provided!")
|
||||||
|
data, targets = parse_data_arg(data)
|
||||||
|
|
||||||
# Layers
|
# Layers
|
||||||
self.proto_layer = LabeledComponents(initialized_components=data)
|
self.proto_layer = LabeledComponents(
|
||||||
|
distribution=[],
|
||||||
|
components_initializer=LiteralCompInitializer(data),
|
||||||
|
labels_initializer=LiteralLabelsInitializer(targets))
|
||||||
self.competition_layer = KNNC(k=self.hparams.k)
|
self.competition_layer = KNNC(k=self.hparams.k)
|
||||||
|
|
||||||
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
"""LVQ models that are optimized using non-gradient methods."""
|
"""LVQ models that are optimized using non-gradient methods."""
|
||||||
|
|
||||||
from prototorch.functions.losses import _get_dp_dm
|
from prototorch.core.losses import _get_dp_dm
|
||||||
|
from prototorch.nn.activations import get_activation
|
||||||
|
from prototorch.nn.wrappers import LambdaLayer
|
||||||
|
|
||||||
from .abstract import NonGradientMixin
|
|
||||||
from .glvq import GLVQ
|
from .glvq import GLVQ
|
||||||
|
from .mixin import NonGradientMixin
|
||||||
|
|
||||||
|
|
||||||
class LVQ1(NonGradientMixin, GLVQ):
|
class LVQ1(NonGradientMixin, GLVQ):
|
||||||
"""Learning Vector Quantization 1."""
|
"""Learning Vector Quantization 1."""
|
||||||
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
||||||
protos = self.proto_layer.components
|
protos, plables = self.proto_layer()
|
||||||
plabels = self.proto_layer.component_labels
|
|
||||||
|
|
||||||
x, y = train_batch
|
x, y = train_batch
|
||||||
dis = self.compute_distances(x)
|
dis = self.compute_distances(x)
|
||||||
# TODO Vectorized implementation
|
# TODO Vectorized implementation
|
||||||
@@ -29,6 +29,8 @@ class LVQ1(NonGradientMixin, GLVQ):
|
|||||||
self.proto_layer.load_state_dict({"_components": updated_protos},
|
self.proto_layer.load_state_dict({"_components": updated_protos},
|
||||||
strict=False)
|
strict=False)
|
||||||
|
|
||||||
|
print(f"dis={dis}")
|
||||||
|
print(f"y={y}")
|
||||||
# Logging
|
# Logging
|
||||||
self.log_acc(dis, y, tag="train_acc")
|
self.log_acc(dis, y, tag="train_acc")
|
||||||
|
|
||||||
@@ -38,8 +40,7 @@ class LVQ1(NonGradientMixin, GLVQ):
|
|||||||
class LVQ21(NonGradientMixin, GLVQ):
|
class LVQ21(NonGradientMixin, GLVQ):
|
||||||
"""Learning Vector Quantization 2.1."""
|
"""Learning Vector Quantization 2.1."""
|
||||||
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
||||||
protos = self.proto_layer.components
|
protos, plabels = self.proto_layer()
|
||||||
plabels = self.proto_layer.component_labels
|
|
||||||
|
|
||||||
x, y = train_batch
|
x, y = train_batch
|
||||||
dis = self.compute_distances(x)
|
dis = self.compute_distances(x)
|
||||||
@@ -65,4 +66,60 @@ class LVQ21(NonGradientMixin, GLVQ):
|
|||||||
|
|
||||||
|
|
||||||
class MedianLVQ(NonGradientMixin, GLVQ):
|
class MedianLVQ(NonGradientMixin, GLVQ):
|
||||||
"""Median LVQ"""
|
"""Median LVQ
|
||||||
|
|
||||||
|
# TODO Avoid computing distances over and over
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, hparams, verbose=True, **kwargs):
|
||||||
|
self.verbose = verbose
|
||||||
|
super().__init__(hparams, **kwargs)
|
||||||
|
|
||||||
|
self.transfer_layer = LambdaLayer(
|
||||||
|
get_activation(self.hparams.transfer_fn))
|
||||||
|
|
||||||
|
def _f(self, x, y, protos, plabels):
|
||||||
|
d = self.distance_layer(x, protos)
|
||||||
|
dp, dm = _get_dp_dm(d, y, plabels)
|
||||||
|
mu = (dp - dm) / (dp + dm)
|
||||||
|
invmu = -1.0 * mu
|
||||||
|
f = self.transfer_layer(invmu, beta=self.hparams.transfer_beta) + 1.0
|
||||||
|
return f
|
||||||
|
|
||||||
|
def expectation(self, x, y, protos, plabels):
|
||||||
|
f = self._f(x, y, protos, plabels)
|
||||||
|
gamma = f / f.sum()
|
||||||
|
return gamma
|
||||||
|
|
||||||
|
def lower_bound(self, x, y, protos, plabels, gamma):
|
||||||
|
f = self._f(x, y, protos, plabels)
|
||||||
|
lower_bound = (gamma * f.log()).sum()
|
||||||
|
return lower_bound
|
||||||
|
|
||||||
|
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
||||||
|
protos, plabels = self.proto_layer()
|
||||||
|
|
||||||
|
x, y = train_batch
|
||||||
|
dis = self.compute_distances(x)
|
||||||
|
|
||||||
|
for i, _ in enumerate(protos):
|
||||||
|
# Expectation step
|
||||||
|
gamma = self.expectation(x, y, protos, plabels)
|
||||||
|
lower_bound = self.lower_bound(x, y, protos, plabels, gamma)
|
||||||
|
|
||||||
|
# Maximization step
|
||||||
|
_protos = protos + 0
|
||||||
|
for k, xk in enumerate(x):
|
||||||
|
_protos[i] = xk
|
||||||
|
_lower_bound = self.lower_bound(x, y, _protos, plabels, gamma)
|
||||||
|
if _lower_bound > lower_bound:
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Updating prototype {i} to data {k}...")
|
||||||
|
self.proto_layer.load_state_dict({"_components": _protos},
|
||||||
|
strict=False)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
self.log_acc(dis, y, tag="train_acc")
|
||||||
|
|
||||||
|
return None
|
||||||
|
27
prototorch/models/mixin.py
Normal file
27
prototorch/models/mixin.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
class ProtoTorchMixin:
|
||||||
|
"""All mixins are ProtoTorchMixins."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NonGradientMixin(ProtoTorchMixin):
|
||||||
|
"""Mixin for custom non-gradient optimization."""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.automatic_optimization = False
|
||||||
|
|
||||||
|
def training_step(self, train_batch, batch_idx, optimizer_idx=None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ImagePrototypesMixin(ProtoTorchMixin):
|
||||||
|
"""Mixin for models with image prototypes."""
|
||||||
|
def on_train_batch_end(self, outputs, batch, batch_idx, dataloader_idx):
|
||||||
|
"""Constrain the components to the range [0, 1] by clamping after updates."""
|
||||||
|
self.proto_layer.components.data.clamp_(0.0, 1.0)
|
||||||
|
|
||||||
|
def get_prototype_grid(self, num_columns=2, return_channels_last=True):
|
||||||
|
from torchvision.utils import make_grid
|
||||||
|
grid = make_grid(self.components, nrow=num_columns)
|
||||||
|
if return_channels_last:
|
||||||
|
grid = grid.permute((1, 2, 0))
|
||||||
|
return grid.cpu()
|
@@ -1,13 +1,11 @@
|
|||||||
"""Probabilistic GLVQ methods"""
|
"""Probabilistic GLVQ methods"""
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from prototorch.functions.losses import nllr_loss, rslvq_loss
|
from prototorch.core.losses import nllr_loss, rslvq_loss
|
||||||
from prototorch.functions.pooling import (stratified_min_pooling,
|
from prototorch.core.pooling import stratified_min_pooling, stratified_sum_pooling
|
||||||
stratified_sum_pooling)
|
from prototorch.nn.wrappers import LambdaLayer, LossLayer
|
||||||
from prototorch.functions.transforms import (GaussianPrior,
|
|
||||||
RankScaledGaussianPrior)
|
|
||||||
from prototorch.modules import LambdaLayer, LossLayer
|
|
||||||
|
|
||||||
|
from .extras import GaussianPrior, RankScaledGaussianPrior
|
||||||
from .glvq import GLVQ, SiameseGMLVQ
|
from .glvq import GLVQ, SiameseGMLVQ
|
||||||
|
|
||||||
|
|
||||||
@@ -22,11 +20,11 @@ class CELVQ(GLVQ):
|
|||||||
def shared_step(self, batch, batch_idx, optimizer_idx=None):
|
def shared_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
x, y = batch
|
x, y = batch
|
||||||
out = self.compute_distances(x) # [None, num_protos]
|
out = self.compute_distances(x) # [None, num_protos]
|
||||||
plabels = self.proto_layer.component_labels
|
_, plabels = self.proto_layer()
|
||||||
winning = stratified_min_pooling(out, plabels) # [None, num_classes]
|
winning = stratified_min_pooling(out, plabels) # [None, num_classes]
|
||||||
probs = -1.0 * winning
|
probs = -1.0 * winning
|
||||||
batch_loss = self.loss(probs, y.long())
|
batch_loss = self.loss(probs, y.long())
|
||||||
loss = batch_loss.sum(dim=0)
|
loss = batch_loss.sum()
|
||||||
return out, loss
|
return out, loss
|
||||||
|
|
||||||
|
|
||||||
@@ -56,9 +54,9 @@ class ProbabilisticLVQ(GLVQ):
|
|||||||
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
x, y = batch
|
x, y = batch
|
||||||
out = self.forward(x)
|
out = self.forward(x)
|
||||||
plabels = self.proto_layer.component_labels
|
_, plabels = self.proto_layer()
|
||||||
batch_loss = self.loss(out, y, plabels)
|
batch_loss = self.loss(out, y, plabels)
|
||||||
loss = batch_loss.sum(dim=0)
|
loss = batch_loss.sum()
|
||||||
return loss
|
return loss
|
||||||
|
|
||||||
|
|
||||||
@@ -89,11 +87,10 @@ class PLVQ(ProbabilisticLVQ, SiameseGMLVQ):
|
|||||||
self.hparams.lambd)
|
self.hparams.lambd)
|
||||||
self.loss = torch.nn.KLDivLoss()
|
self.loss = torch.nn.KLDivLoss()
|
||||||
|
|
||||||
def training_step(self, batch, batch_idx, optimizer_idx=None):
|
# FIXME
|
||||||
x, y = batch
|
# def training_step(self, batch, batch_idx, optimizer_idx=None):
|
||||||
out = self.forward(x)
|
# x, y = batch
|
||||||
y_dist = torch.nn.functional.one_hot(
|
# y_pred = self(x)
|
||||||
y.long(), num_classes=self.num_classes).float()
|
# batch_loss = self.loss(y_pred, y)
|
||||||
batch_loss = self.loss(out, y_dist)
|
# loss = batch_loss.sum()
|
||||||
loss = batch_loss.sum(dim=0)
|
# return loss
|
||||||
return loss
|
|
||||||
|
@@ -2,14 +2,15 @@
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
from prototorch.functions.competitions import wtac
|
from prototorch.core.competitions import wtac
|
||||||
from prototorch.functions.distances import squared_euclidean_distance
|
from prototorch.core.distances import squared_euclidean_distance
|
||||||
from prototorch.modules import LambdaLayer
|
from prototorch.core.losses import NeuralGasEnergy
|
||||||
from prototorch.modules.losses import NeuralGasEnergy
|
from prototorch.nn.wrappers import LambdaLayer
|
||||||
|
|
||||||
from .abstract import NonGradientMixin, UnsupervisedPrototypeModel
|
from .abstract import UnsupervisedPrototypeModel
|
||||||
from .callbacks import GNGCallback
|
from .callbacks import GNGCallback
|
||||||
from .extras import ConnectionTopology
|
from .extras import ConnectionTopology
|
||||||
|
from .mixin import NonGradientMixin
|
||||||
|
|
||||||
|
|
||||||
class KohonenSOM(NonGradientMixin, UnsupervisedPrototypeModel):
|
class KohonenSOM(NonGradientMixin, UnsupervisedPrototypeModel):
|
||||||
@@ -53,7 +54,7 @@ class KohonenSOM(NonGradientMixin, UnsupervisedPrototypeModel):
|
|||||||
grid = self._grid.view(-1, 2)
|
grid = self._grid.view(-1, 2)
|
||||||
gd = squared_euclidean_distance(wp, grid)
|
gd = squared_euclidean_distance(wp, grid)
|
||||||
nh = torch.exp(-gd / self._sigma**2)
|
nh = torch.exp(-gd / self._sigma**2)
|
||||||
protos = self.proto_layer.components
|
protos = self.proto_layer()
|
||||||
diff = x.unsqueeze(dim=1) - protos
|
diff = x.unsqueeze(dim=1) - protos
|
||||||
delta = self._lr * self.hparams.alpha * nh.unsqueeze(-1) * diff
|
delta = self._lr * self.hparams.alpha * nh.unsqueeze(-1) * diff
|
||||||
updated_protos = protos + delta.sum(dim=0)
|
updated_protos = protos + delta.sum(dim=0)
|
||||||
@@ -132,7 +133,7 @@ class GrowingNeuralGas(NeuralGas):
|
|||||||
mask[torch.arange(len(mask)), winner] = 1.0
|
mask[torch.arange(len(mask)), winner] = 1.0
|
||||||
dp = d * mask
|
dp = d * mask
|
||||||
|
|
||||||
self.errors += torch.sum(dp * dp, dim=0)
|
self.errors += torch.sum(dp * dp)
|
||||||
self.errors *= self.hparams.step_reduction
|
self.errors *= self.hparams.step_reduction
|
||||||
|
|
||||||
self.topology_layer(d)
|
self.topology_layer(d)
|
||||||
|
@@ -5,13 +5,18 @@ import pytorch_lightning as pl
|
|||||||
import torch
|
import torch
|
||||||
import torchvision
|
import torchvision
|
||||||
from matplotlib import pyplot as plt
|
from matplotlib import pyplot as plt
|
||||||
|
from prototorch.utils.utils import generate_mesh, mesh2d
|
||||||
from torch.utils.data import DataLoader, Dataset
|
from torch.utils.data import DataLoader, Dataset
|
||||||
|
|
||||||
|
COLOR_UNLABELED = 'w'
|
||||||
|
|
||||||
|
|
||||||
class Vis2DAbstract(pl.Callback):
|
class Vis2DAbstract(pl.Callback):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
data,
|
data,
|
||||||
title="Prototype Visualization",
|
title=None,
|
||||||
|
x_label=None,
|
||||||
|
y_label=None,
|
||||||
cmap="viridis",
|
cmap="viridis",
|
||||||
border=0.1,
|
border=0.1,
|
||||||
resolution=100,
|
resolution=100,
|
||||||
@@ -43,6 +48,8 @@ class Vis2DAbstract(pl.Callback):
|
|||||||
self.y_train = y
|
self.y_train = y
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.x_label = x_label
|
||||||
|
self.y_label = y_label
|
||||||
self.fig = plt.figure(self.title)
|
self.fig = plt.figure(self.title)
|
||||||
self.cmap = cmap
|
self.cmap = cmap
|
||||||
self.border = border
|
self.border = border
|
||||||
@@ -55,41 +62,24 @@ class Vis2DAbstract(pl.Callback):
|
|||||||
self.pause_time = pause_time
|
self.pause_time = pause_time
|
||||||
self.block = block
|
self.block = block
|
||||||
|
|
||||||
def precheck(self, trainer):
|
def show_on_current_epoch(self, trainer):
|
||||||
if self.show_last_only:
|
if self.show_last_only and trainer.current_epoch != trainer.max_epochs - 1:
|
||||||
if trainer.current_epoch != trainer.max_epochs - 1:
|
return False
|
||||||
return False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setup_ax(self, xlabel=None, ylabel=None):
|
def setup_ax(self):
|
||||||
ax = self.fig.gca()
|
ax = self.fig.gca()
|
||||||
ax.cla()
|
ax.cla()
|
||||||
ax.set_title(self.title)
|
ax.set_title(self.title)
|
||||||
if xlabel:
|
if self.x_label:
|
||||||
ax.set_xlabel("Data dimension 1")
|
ax.set_xlabel(self.x_label)
|
||||||
if ylabel:
|
if self.x_label:
|
||||||
ax.set_ylabel("Data dimension 2")
|
ax.set_ylabel(self.y_label)
|
||||||
if self.axis_off:
|
if self.axis_off:
|
||||||
ax.axis("off")
|
ax.axis("off")
|
||||||
return ax
|
return ax
|
||||||
|
|
||||||
def get_mesh_input(self, x):
|
def plot_data(self, ax, x, y):
|
||||||
x_shift = self.border * np.ptp(x[:, 0])
|
|
||||||
y_shift = self.border * np.ptp(x[:, 1])
|
|
||||||
x_min, x_max = x[:, 0].min() - x_shift, x[:, 0].max() + x_shift
|
|
||||||
y_min, y_max = x[:, 1].min() - y_shift, x[:, 1].max() + y_shift
|
|
||||||
xx, yy = np.meshgrid(np.linspace(x_min, x_max, self.resolution),
|
|
||||||
np.linspace(y_min, y_max, self.resolution))
|
|
||||||
mesh_input = np.c_[xx.ravel(), yy.ravel()]
|
|
||||||
return mesh_input, xx, yy
|
|
||||||
|
|
||||||
def perform_pca_2D(self, data):
|
|
||||||
(_, eigVal, eigVec) = torch.pca_lowrank(data, q=2)
|
|
||||||
return data @ eigVec
|
|
||||||
|
|
||||||
def plot_data(self, ax, x, y, pca=False):
|
|
||||||
if pca:
|
|
||||||
x = self.perform_pca_2D(x)
|
|
||||||
ax.scatter(
|
ax.scatter(
|
||||||
x[:, 0],
|
x[:, 0],
|
||||||
x[:, 1],
|
x[:, 1],
|
||||||
@@ -100,9 +90,7 @@ class Vis2DAbstract(pl.Callback):
|
|||||||
s=30,
|
s=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
def plot_protos(self, ax, protos, plabels, pca=False):
|
def plot_protos(self, ax, protos, plabels):
|
||||||
if pca:
|
|
||||||
protos = self.perform_pca_2D(protos)
|
|
||||||
ax.scatter(
|
ax.scatter(
|
||||||
protos[:, 0],
|
protos[:, 0],
|
||||||
protos[:, 1],
|
protos[:, 1],
|
||||||
@@ -133,25 +121,64 @@ class Vis2DAbstract(pl.Callback):
|
|||||||
plt.close()
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
class VisGLVQ2D(Vis2DAbstract):
|
class Visualize2DVoronoiCallback(Vis2DAbstract):
|
||||||
|
def __init__(self, data, **kwargs):
|
||||||
|
super().__init__(data, **kwargs)
|
||||||
|
|
||||||
|
self.data_min = torch.min(self.x_train, axis=0).values
|
||||||
|
self.data_max = torch.max(self.x_train, axis=0).values
|
||||||
|
|
||||||
|
def current_span(self, proto_values):
|
||||||
|
proto_min = torch.min(proto_values, axis=0).values
|
||||||
|
proto_max = torch.max(proto_values, axis=0).values
|
||||||
|
|
||||||
|
overall_min = torch.minimum(proto_min, self.data_min)
|
||||||
|
overall_max = torch.maximum(proto_max, self.data_max)
|
||||||
|
|
||||||
|
return overall_min, overall_max
|
||||||
|
|
||||||
|
def get_voronoi_diagram(self, min, max, model):
|
||||||
|
mesh_input, (xx, yy) = generate_mesh(
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
border=self.border,
|
||||||
|
resolution=self.resolution,
|
||||||
|
device=model.device,
|
||||||
|
)
|
||||||
|
|
||||||
|
y_pred = model.predict(mesh_input)
|
||||||
|
return xx, yy, y_pred.reshape(xx.shape)
|
||||||
|
|
||||||
def on_epoch_end(self, trainer, pl_module):
|
def on_epoch_end(self, trainer, pl_module):
|
||||||
if not self.precheck(trainer):
|
if not self.show_on_current_epoch(trainer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
protos = pl_module.prototypes
|
# Extract Prototypes
|
||||||
plabels = pl_module.prototype_labels
|
proto_values = pl_module.prototypes
|
||||||
x_train, y_train = self.x_train, self.y_train
|
if hasattr(pl_module, "prototype_labels"):
|
||||||
ax = self.setup_ax(xlabel="Data dimension 1",
|
proto_labels = pl_module.prototype_labels
|
||||||
ylabel="Data dimension 2")
|
else:
|
||||||
self.plot_data(ax, x_train, y_train)
|
proto_labels = COLOR_UNLABELED
|
||||||
self.plot_protos(ax, protos, plabels)
|
|
||||||
x = np.vstack((x_train, protos))
|
# Calculate Voronoi Diagram
|
||||||
mesh_input, xx, yy = self.get_mesh_input(x)
|
overall_min, overall_max = self.current_span(proto_values)
|
||||||
_components = pl_module.proto_layer._components
|
xx, yy, y_pred = self.get_voronoi_diagram(
|
||||||
mesh_input = torch.from_numpy(mesh_input).type_as(_components)
|
overall_min,
|
||||||
y_pred = pl_module.predict(mesh_input)
|
overall_max,
|
||||||
y_pred = y_pred.cpu().reshape(xx.shape)
|
pl_module,
|
||||||
ax.contourf(xx, yy, y_pred, cmap=self.cmap, alpha=0.35)
|
)
|
||||||
|
|
||||||
|
ax = self.setup_ax()
|
||||||
|
ax.contourf(
|
||||||
|
xx.cpu(),
|
||||||
|
yy.cpu(),
|
||||||
|
y_pred.cpu(),
|
||||||
|
cmap=self.cmap,
|
||||||
|
alpha=0.35,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.plot_data(ax, self.x_train, self.y_train)
|
||||||
|
self.plot_protos(ax, proto_values, proto_labels)
|
||||||
|
|
||||||
self.log_and_display(trainer, pl_module)
|
self.log_and_display(trainer, pl_module)
|
||||||
|
|
||||||
@@ -162,7 +189,7 @@ class VisSiameseGLVQ2D(Vis2DAbstract):
|
|||||||
self.map_protos = map_protos
|
self.map_protos = map_protos
|
||||||
|
|
||||||
def on_epoch_end(self, trainer, pl_module):
|
def on_epoch_end(self, trainer, pl_module):
|
||||||
if not self.precheck(trainer):
|
if not self.show_on_current_epoch(trainer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
protos = pl_module.prototypes
|
protos = pl_module.prototypes
|
||||||
@@ -181,9 +208,9 @@ class VisSiameseGLVQ2D(Vis2DAbstract):
|
|||||||
if self.show_protos:
|
if self.show_protos:
|
||||||
self.plot_protos(ax, protos, plabels)
|
self.plot_protos(ax, protos, plabels)
|
||||||
x = np.vstack((x_train, protos))
|
x = np.vstack((x_train, protos))
|
||||||
mesh_input, xx, yy = self.get_mesh_input(x)
|
mesh_input, xx, yy = mesh2d(x, self.border, self.resolution)
|
||||||
else:
|
else:
|
||||||
mesh_input, xx, yy = self.get_mesh_input(x_train)
|
mesh_input, xx, yy = mesh2d(x_train, self.border, self.resolution)
|
||||||
_components = pl_module.proto_layer._components
|
_components = pl_module.proto_layer._components
|
||||||
mesh_input = torch.Tensor(mesh_input).type_as(_components)
|
mesh_input = torch.Tensor(mesh_input).type_as(_components)
|
||||||
y_pred = pl_module.predict_latent(mesh_input,
|
y_pred = pl_module.predict_latent(mesh_input,
|
||||||
@@ -195,83 +222,48 @@ class VisSiameseGLVQ2D(Vis2DAbstract):
|
|||||||
|
|
||||||
|
|
||||||
class VisGMLVQ2D(Vis2DAbstract):
|
class VisGMLVQ2D(Vis2DAbstract):
|
||||||
def __init__(self, *args, map_protos=True, **kwargs):
|
def __init__(self, *args, ev_proj=True, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.map_protos = map_protos
|
self.ev_proj = ev_proj
|
||||||
|
|
||||||
def on_epoch_end(self, trainer, pl_module):
|
def on_epoch_end(self, trainer, pl_module):
|
||||||
if not self.precheck(trainer):
|
if not self.show_on_current_epoch(trainer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
protos = pl_module.prototypes
|
protos = pl_module.prototypes
|
||||||
plabels = pl_module.prototype_labels
|
plabels = pl_module.prototype_labels
|
||||||
x_train, y_train = self.x_train, self.y_train
|
x_train, y_train = self.x_train, self.y_train
|
||||||
device = pl_module.device
|
device = pl_module.device
|
||||||
|
omega = pl_module._omega.detach()
|
||||||
|
lam = omega @ omega.T
|
||||||
|
u, _, _ = torch.pca_lowrank(lam, q=2)
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
x_train = pl_module.backbone(torch.Tensor(x_train).to(device))
|
x_train = torch.Tensor(x_train).to(device)
|
||||||
|
x_train = x_train @ u
|
||||||
x_train = x_train.cpu().detach()
|
x_train = x_train.cpu().detach()
|
||||||
if self.map_protos:
|
if self.show_protos:
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
protos = pl_module.backbone(torch.Tensor(protos).to(device))
|
protos = torch.Tensor(protos).to(device)
|
||||||
|
protos = protos @ u
|
||||||
protos = protos.cpu().detach()
|
protos = protos.cpu().detach()
|
||||||
ax = self.setup_ax()
|
ax = self.setup_ax()
|
||||||
if x_train.shape[1] > 2:
|
|
||||||
self.plot_data(ax, x_train, y_train, pca=True)
|
|
||||||
else:
|
|
||||||
self.plot_data(ax, x_train, y_train, pca=False)
|
|
||||||
if self.show_protos:
|
|
||||||
if protos.shape[1] > 2:
|
|
||||||
self.plot_protos(ax, protos, plabels, pca=True)
|
|
||||||
else:
|
|
||||||
self.plot_protos(ax, protos, plabels, pca=False)
|
|
||||||
### something to work on: meshgrid with pca
|
|
||||||
# x = np.vstack((x_train, protos))
|
|
||||||
# mesh_input, xx, yy = self.get_mesh_input(x)
|
|
||||||
#else:
|
|
||||||
# mesh_input, xx, yy = self.get_mesh_input(x_train)
|
|
||||||
#_components = pl_module.proto_layer._components
|
|
||||||
#mesh_input = torch.Tensor(mesh_input).type_as(_components)
|
|
||||||
#y_pred = pl_module.predict_latent(mesh_input,
|
|
||||||
# map_protos=self.map_protos)
|
|
||||||
#y_pred = y_pred.cpu().reshape(xx.shape)
|
|
||||||
#ax.contourf(xx, yy, y_pred, cmap=self.cmap, alpha=0.35)
|
|
||||||
self.log_and_display(trainer, pl_module)
|
|
||||||
|
|
||||||
|
|
||||||
class VisCBC2D(Vis2DAbstract):
|
|
||||||
def on_epoch_end(self, trainer, pl_module):
|
|
||||||
if not self.precheck(trainer):
|
|
||||||
return True
|
|
||||||
|
|
||||||
x_train, y_train = self.x_train, self.y_train
|
|
||||||
protos = pl_module.components
|
|
||||||
ax = self.setup_ax(xlabel="Data dimension 1",
|
|
||||||
ylabel="Data dimension 2")
|
|
||||||
self.plot_data(ax, x_train, y_train)
|
self.plot_data(ax, x_train, y_train)
|
||||||
self.plot_protos(ax, protos, "w")
|
if self.show_protos:
|
||||||
x = np.vstack((x_train, protos))
|
self.plot_protos(ax, protos, plabels)
|
||||||
mesh_input, xx, yy = self.get_mesh_input(x)
|
|
||||||
_components = pl_module.component_layer._components
|
|
||||||
y_pred = pl_module.predict(
|
|
||||||
torch.Tensor(mesh_input).type_as(_components))
|
|
||||||
y_pred = y_pred.cpu().reshape(xx.shape)
|
|
||||||
|
|
||||||
ax.contourf(xx, yy, y_pred, cmap=self.cmap, alpha=0.35)
|
|
||||||
|
|
||||||
self.log_and_display(trainer, pl_module)
|
self.log_and_display(trainer, pl_module)
|
||||||
|
|
||||||
|
|
||||||
class VisNG2D(Vis2DAbstract):
|
class VisNG2D(Vis2DAbstract):
|
||||||
def on_epoch_end(self, trainer, pl_module):
|
def on_epoch_end(self, trainer, pl_module):
|
||||||
if not self.precheck(trainer):
|
if not self.show_on_current_epoch(trainer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
x_train, y_train = self.x_train, self.y_train
|
x_train, y_train = self.x_train, self.y_train
|
||||||
protos = pl_module.prototypes
|
protos = pl_module.prototypes
|
||||||
cmat = pl_module.topology_layer.cmat.cpu().numpy()
|
cmat = pl_module.topology_layer.cmat.cpu().numpy()
|
||||||
|
|
||||||
ax = self.setup_ax(xlabel="Data dimension 1",
|
ax = self.setup_ax()
|
||||||
ylabel="Data dimension 2")
|
|
||||||
self.plot_data(ax, x_train, y_train)
|
self.plot_data(ax, x_train, y_train)
|
||||||
self.plot_protos(ax, protos, "w")
|
self.plot_protos(ax, protos, "w")
|
||||||
|
|
||||||
@@ -311,8 +303,6 @@ class VisImgComp(Vis2DAbstract):
|
|||||||
size=self.embedding_data,
|
size=self.embedding_data,
|
||||||
replace=False)
|
replace=False)
|
||||||
data = self.x_train[ind]
|
data = self.x_train[ind]
|
||||||
# print(f"{data.shape=}")
|
|
||||||
# print(f"{self.y_train[ind].shape=}")
|
|
||||||
tb.add_embedding(data.view(len(ind), -1),
|
tb.add_embedding(data.view(len(ind), -1),
|
||||||
label_img=data,
|
label_img=data,
|
||||||
global_step=None,
|
global_step=None,
|
||||||
@@ -344,7 +334,7 @@ class VisImgComp(Vis2DAbstract):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_epoch_end(self, trainer, pl_module):
|
def on_epoch_end(self, trainer, pl_module):
|
||||||
if not self.precheck(trainer):
|
if not self.show_on_current_epoch(trainer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.show:
|
if self.show:
|
||||||
|
8
setup.cfg
Normal file
8
setup.cfg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[isort]
|
||||||
|
profile = hug
|
||||||
|
src_paths = isort, test
|
||||||
|
|
||||||
|
[yapf]
|
||||||
|
based_on_style = pep8
|
||||||
|
spaces_before_comment = 2
|
||||||
|
split_before_logical_operator = true
|
12
setup.py
12
setup.py
@@ -18,11 +18,11 @@ PLUGIN_NAME = "models"
|
|||||||
PROJECT_URL = "https://github.com/si-cim/prototorch_models"
|
PROJECT_URL = "https://github.com/si-cim/prototorch_models"
|
||||||
DOWNLOAD_URL = "https://github.com/si-cim/prototorch_models.git"
|
DOWNLOAD_URL = "https://github.com/si-cim/prototorch_models.git"
|
||||||
|
|
||||||
with open("README.md", "r") as fh:
|
with open("README.md") as fh:
|
||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
INSTALL_REQUIRES = [
|
INSTALL_REQUIRES = [
|
||||||
"prototorch>=0.5.0,<0.6.0",
|
"prototorch>=0.7.0",
|
||||||
"pytorch_lightning>=1.3.5",
|
"pytorch_lightning>=1.3.5",
|
||||||
"torchmetrics",
|
"torchmetrics",
|
||||||
]
|
]
|
||||||
@@ -37,6 +37,7 @@ DOCS = [
|
|||||||
"recommonmark",
|
"recommonmark",
|
||||||
"sphinx",
|
"sphinx",
|
||||||
"nbsphinx",
|
"nbsphinx",
|
||||||
|
"ipykernel",
|
||||||
"sphinx_rtd_theme",
|
"sphinx_rtd_theme",
|
||||||
"sphinxcontrib-katex",
|
"sphinxcontrib-katex",
|
||||||
"sphinxcontrib-bibtex",
|
"sphinxcontrib-bibtex",
|
||||||
@@ -53,7 +54,7 @@ ALL = CLI + DEV + DOCS + EXAMPLES + TESTS
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=safe_name("prototorch_" + PLUGIN_NAME),
|
name=safe_name("prototorch_" + PLUGIN_NAME),
|
||||||
version="0.1.8",
|
version="0.3.0",
|
||||||
description="Pre-packaged prototype-based "
|
description="Pre-packaged prototype-based "
|
||||||
"machine learning models using ProtoTorch and PyTorch-Lightning.",
|
"machine learning models using ProtoTorch and PyTorch-Lightning.",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
@@ -63,7 +64,7 @@ setup(
|
|||||||
url=PROJECT_URL,
|
url=PROJECT_URL,
|
||||||
download_url=DOWNLOAD_URL,
|
download_url=DOWNLOAD_URL,
|
||||||
license="MIT",
|
license="MIT",
|
||||||
python_requires=">=3.9",
|
python_requires=">=3.6",
|
||||||
install_requires=INSTALL_REQUIRES,
|
install_requires=INSTALL_REQUIRES,
|
||||||
extras_require={
|
extras_require={
|
||||||
"dev": DEV,
|
"dev": DEV,
|
||||||
@@ -80,6 +81,9 @@ setup(
|
|||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Natural Language :: English",
|
"Natural Language :: English",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||||
"Topic :: Software Development :: Libraries",
|
"Topic :: Software Development :: Libraries",
|
||||||
|
@@ -1,17 +1,35 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
# Read Flags
|
||||||
|
gpu=0
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
--gpu) gpu=1;;
|
||||||
|
-g) gpu=1;;
|
||||||
|
*) path=$1;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
python --version
|
||||||
|
echo "Using GPU: " $gpu
|
||||||
|
|
||||||
|
# Loop
|
||||||
failed=0
|
failed=0
|
||||||
|
|
||||||
for example in $(find $1 -maxdepth 1 -name "*.py")
|
for example in $(find $path -maxdepth 1 -name "*.py")
|
||||||
do
|
do
|
||||||
echo -n "$x" $example '... '
|
echo -n "$x" $example '... '
|
||||||
export DISPLAY= && python $example --fast_dev_run 1 &> /dev/null
|
export DISPLAY= && python $example --fast_dev_run 1 --gpus $gpu &> run_log.txt
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo "FAILED!!"
|
echo "FAILED!!"
|
||||||
|
cat run_log.txt
|
||||||
failed=1
|
failed=1
|
||||||
else
|
else
|
||||||
echo "SUCCESS!"
|
echo "SUCCESS!"
|
||||||
fi
|
fi
|
||||||
|
rm run_log.txt
|
||||||
done
|
done
|
||||||
|
|
||||||
exit $failed
|
exit $failed
|
||||||
|
Reference in New Issue
Block a user