├── .github └── workflows │ ├── ci.yml │ └── docs.yml ├── .gitignore ├── .pylintrc ├── .vscode └── settings.json ├── CITATION.cff ├── LICENCE.txt ├── README.md ├── deeplay ├── __init__.py ├── activelearning │ ├── __init__.py │ ├── criterion.py │ ├── data.py │ └── strategies │ │ ├── __init__.py │ │ ├── adversarial │ │ ├── __init__.py │ │ └── adversarial.py │ │ ├── strategy.py │ │ ├── uncertainty.py │ │ └── uniform.py ├── applications │ ├── __init__.py │ ├── application.py │ ├── autoencoders │ │ ├── __init__.py │ │ ├── vae.py │ │ └── wae.py │ ├── classification │ │ ├── __init__.py │ │ ├── binary.py │ │ ├── categorical.py │ │ ├── classifier.py │ │ └── multilabel.py │ ├── detection │ │ ├── __init__.py │ │ └── lodestar │ │ │ ├── __init__.py │ │ │ ├── dataset.py │ │ │ ├── lodestar.py │ │ │ └── transforms.py │ └── regression │ │ ├── __init__.py │ │ └── regressor.py ├── blocks │ ├── __init__.py │ ├── base.py │ ├── block.py │ ├── conv │ │ ├── __init__.py │ │ ├── conv2d.py │ │ └── conv2d.pyi │ ├── la.py │ ├── lan.py │ ├── land.py │ ├── lanu.py │ ├── linear │ │ ├── __init__.py │ │ └── linear.py │ ├── ls.py │ ├── plan.py │ ├── recurrentblock.py │ ├── residual.py │ ├── sequence │ │ ├── __init__.py │ │ └── sequence1d.py │ └── sequential.py ├── callbacks │ ├── __init__.py │ ├── history.py │ └── progress.py ├── components │ ├── __init__.py │ ├── cnn │ │ ├── __init__.py │ │ ├── cnn.py │ │ ├── cnn.pyi │ │ ├── encdec.py │ │ └── encdec.pyi │ ├── dict.py │ ├── diffusion │ │ ├── __init__.py │ │ └── attention_unet.py │ ├── gnn │ │ ├── __init__.py │ │ ├── gcn │ │ │ ├── __init__.py │ │ │ ├── gcn.py │ │ │ └── normalization.py │ │ ├── mpn │ │ │ ├── __init__.py │ │ │ ├── cla.py │ │ │ ├── ldw.py │ │ │ ├── mpn.py │ │ │ ├── propagation.py │ │ │ ├── rmpn.py │ │ │ ├── transformation.py │ │ │ └── update.py │ │ └── tpu.py │ ├── mlp.py │ ├── mlp.pyi │ ├── rnn.py │ └── transformer │ │ ├── __init__.py │ │ ├── enc.py │ │ ├── ldsn.py │ │ ├── pemb.py │ │ └── satt.py ├── decorators.py ├── external │ ├── __init__.py │ ├── external.py │ ├── layer.py │ └── optimizers │ │ ├── __init__.py │ │ ├── adam.py │ │ ├── optimizer.py │ │ ├── rmsprop.py │ │ └── sgd.py ├── initializers │ ├── __init__.py │ ├── constant.py │ ├── initializer.py │ ├── kaiming.py │ └── normal.py ├── list.py ├── meta.py ├── models │ ├── __init__.py │ ├── backbones │ │ ├── __init__.py │ │ ├── resnet18.py │ │ └── resnet18.pyi │ ├── discriminators │ │ ├── __init__.py │ │ ├── cyclegan.py │ │ ├── cyclegan.pyi │ │ ├── dcgan.py │ │ └── dcgan.pyi │ ├── generators │ │ ├── __init__.py │ │ ├── cyclegan.py │ │ ├── dcgan.py │ │ └── dcgan.pyi │ ├── gnn │ │ ├── __init__.py │ │ ├── gtoeMAGIK.py │ │ ├── gtoempm.py │ │ ├── gtogmpm.py │ │ ├── gtonmpm.py │ │ └── mpm.py │ ├── mlp │ │ ├── __init__.py │ │ ├── sized.py │ │ └── sized.pyi │ ├── recurrentmodel.py │ └── visiontransformer.py ├── module.py ├── ops │ ├── __init__.py │ ├── attention │ │ ├── __init__.py │ │ ├── cross.py │ │ └── self.py │ ├── logs.py │ ├── merge.py │ └── shape.py ├── shapes.py ├── tests │ ├── __init__.py │ ├── applications │ │ ├── __init__.py │ │ ├── base.py │ │ ├── classification │ │ │ ├── __init__.py │ │ │ ├── test_binary.py │ │ │ └── test_categorical.py │ │ └── regression │ │ │ ├── __init__.py │ │ │ └── test_regressor.py │ ├── blocks │ │ ├── __init__.py │ │ ├── conv2d │ │ │ ├── __init__.py │ │ │ └── test_conv.py │ │ ├── sequence │ │ │ └── test_sequence1d.py │ │ └── test_base.py │ ├── checkpointing │ │ ├── __init__.py │ │ └── test_pickling.py │ ├── models │ │ ├── __init__.py │ │ ├── backbones │ │ │ ├── __init__.py │ │ │ └── test_resnet18.py │ │ ├── discriminators │ │ │ ├── __init__.py │ │ │ ├── test_cyclegan.py │ │ │ └── test_dcgan.py │ │ └── generators │ │ │ ├── __init__.py │ │ │ ├── test_cyclegan.py │ │ │ └── test_dcgan.py │ ├── module │ │ ├── __init__.py │ │ └── test_module_subaccess.py │ ├── test_attention_unet.py │ ├── test_blocks.py │ ├── test_cnn.py │ ├── test_decorators.py │ ├── test_dict.py │ ├── test_embeddings.py │ ├── test_encdec.py │ ├── test_external.py │ ├── test_gnn.py │ ├── test_layer.py │ ├── test_layerlist.py │ ├── test_log_input_output.py │ ├── test_mlp.py │ ├── test_module.py │ ├── test_optimizers.py │ ├── test_rnn.py │ ├── test_selectors.py │ ├── test_trainer.py │ └── test_transformers.py └── trainer.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── stylestubgen.py └── tutorials ├── advanced-topics └── AT201_mappings.ipynb ├── developers ├── DT101_files.ipynb ├── DT111_style.ipynb ├── DT121_overview.ipynb ├── DT131_applications.ipynb ├── DT141_models.ipynb ├── DT151_components.ipynb ├── DT161_operations.ipynb ├── DT171_blocks.ipynb ├── DT181_internals.ipynb ├── dev_intro.ipynb └── guidelines │ ├── README.md │ ├── application │ ├── README.md │ └── testing.md │ └── model │ ├── README.md │ └── testing.md ├── examples └── vision │ ├── autoencoders │ ├── Ex01. MNIST Dense AutoEncoder.ipynb │ ├── Ex02. MNIST Spatial Broadcast AutoEncoder.ipynb │ └── Ex03. MNIST Variational AutoEncoder.ipynb │ ├── classification │ ├── .ipynb_checkpoints │ │ ├── Ex02. MNIST AutoEncode copy-checkpoint.ipynb │ │ ├── Ex02. MNIST AutoEncode-checkpoint.ipynb │ │ ├── Ex03.b UNet_Segmentation_no_padding-checkpoint.ipynb │ │ ├── Ex03.b UNet_Segmentation_with_padding-checkpoint.ipynb │ │ └── Ex04.d UNet_Segmentation_augmented-checkpoint.ipynb │ ├── Ex01. MNIST Classification Pure Deeplay.ipynb │ └── Ex01. MNIST Classification.ipynb │ ├── gans │ ├── Ex01. MNIST Gan.ipynb │ ├── Ex02. Class Conditioned Gan.ipynb │ └── figures │ │ ├── class_conditioned.gif │ │ ├── class_conditioned_epoch_0.png │ │ ├── class_conditioned_epoch_1.png │ │ ├── class_conditioned_epoch_10.png │ │ ├── class_conditioned_epoch_11.png │ │ ├── class_conditioned_epoch_12.png │ │ ├── class_conditioned_epoch_13.png │ │ ├── class_conditioned_epoch_14.png │ │ ├── class_conditioned_epoch_15.png │ │ ├── class_conditioned_epoch_16.png │ │ ├── class_conditioned_epoch_17.png │ │ ├── class_conditioned_epoch_18.png │ │ ├── class_conditioned_epoch_19.png │ │ ├── class_conditioned_epoch_2.png │ │ ├── class_conditioned_epoch_3.png │ │ ├── class_conditioned_epoch_4.png │ │ ├── class_conditioned_epoch_5.png │ │ ├── class_conditioned_epoch_6.png │ │ ├── class_conditioned_epoch_7.png │ │ ├── class_conditioned_epoch_8.png │ │ └── class_conditioned_epoch_9.png │ └── segmentation │ ├── Ex01a FCN_Segmentation.ipynb │ ├── Ex02. UNet_Segmentation_no_padding.ipynb │ ├── Ex03. UNet_Segmentation_with_padding.ipynb │ └── Ex04. UNet_Segmentation_augmented.ipynb └── getting-started ├── GS101_core_objects.ipynb ├── GS111_first_model.ipynb ├── GS121_modules.ipynb ├── GS131_methods.ipynb ├── GS141_applications.ipynb ├── GS151_models.ipynb ├── GS161_components.ipynb ├── GS171_blocks.ipynb ├── GS181_configure.ipynb ├── GS191_styles.ipynb ├── __BAK__ ├── Ch01-Why_use_Deeplay.ipynb ├── Ch02-Installation.ipynb ├── Ch03-Quick_Start_Guide.ipynb └── Ch04-Customization.ipynb └── docs3.ipynb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: ["develop"] 9 | pull_request: 10 | branches: ["develop"] 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: ["3.9", "3.10", "3.11", "3.12"] 19 | os: [ubuntu-latest, macos-latest, windows-latest] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install flake8 31 | python -m pip install -e . 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with unittest 39 | run: | 40 | python -m unittest 41 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Update Documentation 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | workflow_dispatch: 8 | inputs: 9 | test_tag: 10 | description: "Release tag to simulate" 11 | required: true 12 | 13 | jobs: 14 | update-docs: 15 | name: Update Documentation 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | # Step 1: Check out the docs branch 20 | - name: Checkout docs branch 21 | uses: actions/checkout@v3 22 | with: 23 | ref: docs 24 | 25 | # Step 2: Set up Python 26 | - name: Set up Python 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: "3.9" 30 | 31 | # Step 3: Install dependencies 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip 35 | pip install -r doc_requirements.txt 36 | 37 | # Step 4: Pull the release code into a separate directory 38 | - name: Checkout release code 39 | uses: actions/checkout@v3 40 | with: 41 | path: release-code 42 | # Use the test tag from workflow_dispatch or the actual release tag 43 | ref: ${{ github.event.inputs.test_tag || github.event.release.tag_name }} 44 | 45 | - name: Install the package 46 | run: | 47 | cd release-code 48 | pip install -e . 49 | 50 | - name: Create the markdown files 51 | run: | 52 | python generate_doc_markdown.py deeplay --exclude=tests,test 53 | 54 | # Step 5: Set version variable 55 | - name: Set version variable 56 | run: | 57 | VERSION=${{ github.event.inputs.test_tag || github.event.release.tag_name }} 58 | echo "VERSION=$VERSION" >> $GITHUB_ENV 59 | 60 | # Step 6: Update switcher.json 61 | - name: Update switcher.json 62 | run: | 63 | SWITCHER_FILE=_static/switcher.json 64 | jq --arg version "$VERSION" \ 65 | '. |= [{"name": $version, "version": $version, "url": "https://deeptrackai.github.io/deeplay/\($version)/"}] + .' \ 66 | $SWITCHER_FILE > temp.json && mv temp.json $SWITCHER_FILE 67 | 68 | # Step 7: Build documentation using Sphinx into html 69 | - name: Build documentation 70 | env: 71 | SPHINX_APIDOC_DIR: release-code 72 | run: make html 73 | 74 | # Step 8: Copy built HTML to `docs/latest` and `docs/{version}` 75 | - name: Copy built HTML 76 | run: | 77 | mkdir -p docs/latest 78 | mkdir -p docs/$VERSION 79 | cp -r _build/html/* docs/latest/ 80 | cp -r _build/html/* docs/$VERSION/ 81 | 82 | # Step 9: Clean up `release-code` directory 83 | - name: Remove release-code directory 84 | run: rm -rf release-code 85 | 86 | # Step 10: Commit and push changes 87 | - name: Commit and push changes 88 | run: | 89 | git config user.name "github-actions[bot]" 90 | git config user.email "github-actions[bot]@users.noreply.github.com" 91 | git add docs/latest docs/$VERSION _static/switcher.json 92 | git commit -m "Update docs for release $VERSION" 93 | git push -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Trial notebooks 2 | trial*.ipynb 3 | 4 | # Byte-compiled / optimized / DLL files 5 | *.pyc 6 | 7 | # Datasets 8 | *ubyte* 9 | *_dataset/ 10 | data/ 11 | tensorflow_datasets/ 12 | 13 | # Directories generated by pytorch-lightning 14 | lightning_logs/ 15 | 16 | # Directory generated by pytest 17 | .pytest_cache/ 18 | 19 | # Distribution / packaging 20 | build/ 21 | dist/ 22 | *.egg-info 23 | 24 | # Mac 25 | *.DS_Store 26 | 27 | package.json 28 | # Tutorial files 29 | tutorials/getting-started/model.pth 30 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [TYPECHECK] 2 | 3 | # List of members which are set dynamically and missed by Pylint inference 4 | # system, and so shouldn't trigger E1101 when accessed. 5 | generated-members=numpy.*, torch.* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.black-formatter" 4 | }, 5 | "python.formatting.provider": "none", 6 | "python.analysis.typeCheckingMode": "basic", 7 | "python.testing.unittestArgs": [ 8 | "-v", 9 | "-s", 10 | "./deeplay", 11 | "-p", 12 | "test_*.py" 13 | ], 14 | "python.testing.pytestEnabled": false, 15 | "python.testing.unittestEnabled": true 16 | } 17 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with cffinit. 2 | # Visit https://bit.ly/cffinit to generate yours today! 3 | 4 | cff-version: 1.2.0 5 | title: Deeplay 6 | message: >- 7 | If you use this software, please cite it using the 8 | metadata from this file. 9 | type: software 10 | authors: 11 | - given-names: Benjamin 12 | family-names: Midtvedt 13 | orcid: 'https://orcid.org/0000-0001-9386-4753' 14 | - given-names: Jesus 15 | family-names: Pineda 16 | orcid: 'https://orcid.org/0000-0002-9197-3451' 17 | - given-names: Henrik 18 | family-names: Klein Morberg 19 | orcid: 'https://orcid.org/0000-0001-7275-6921' 20 | - given-names: Harshith 21 | family-names: Bachimanchi 22 | orcid: 'https://orcid.org/0000-0001-9497-8410' 23 | - given-names: Mirja 24 | family-names: Granfors 25 | - given-names: Alex 26 | family-names: Lech 27 | - given-names: Carlo 28 | family-names: Manzo 29 | orcid: 'https://orcid.org/0000-0002-8625-0996' 30 | - given-names: Giovanni 31 | family-names: Volpe 32 | orcid: 'https://orcid.org/0000-0001-5057-1846' 33 | repository-code: 'https://github.com/DeepTrackAI/deeplay' 34 | abstract: >- 35 | Deeplay is a deep learning library in Python that extends 36 | PyTorch with additional functionalities focused on 37 | modularity and reusability. 38 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/LICENCE.txt -------------------------------------------------------------------------------- /deeplay/__init__.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | # temporary imports until we implement our own 4 | from torch.utils.data import DataLoader 5 | 6 | # Filter out warnings from lazy torch modules 7 | warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.modules.lazy") 8 | 9 | from .trainer import Trainer 10 | from .meta import ExtendedConstructorMeta 11 | from .module import DeeplayModule 12 | from .list import LayerList, Sequential, Parallel 13 | from .external import * 14 | from .blocks import * 15 | from .components import * 16 | from .applications import * 17 | from .models import * 18 | from . import decorators, activelearning, initializers, callbacks, ops 19 | from .external import Layer 20 | from .ops import Cat # For backwards compatibility 21 | -------------------------------------------------------------------------------- /deeplay/activelearning/__init__.py: -------------------------------------------------------------------------------- 1 | from .data import ActiveLearningDataset, JointDataset 2 | from .strategies import * 3 | from .criterion import ( 4 | ActiveLearningCriterion, 5 | LeastConfidence, 6 | Margin, 7 | Entropy, 8 | L1Upper, 9 | L2Upper, 10 | SumCriterion, 11 | ProductCriterion, 12 | FractionCriterion, 13 | Constant, 14 | ) 15 | -------------------------------------------------------------------------------- /deeplay/activelearning/criterion.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | 4 | class ActiveLearningCriterion: 5 | 6 | def score(self, probabilities): 7 | raise NotImplementedError 8 | 9 | def __add__(self, other): 10 | if isinstance(other, ActiveLearningCriterion): 11 | return SumCriterion(self, other) 12 | elif isinstance(other, (float, int, bool)): 13 | return SumCriterion(self, Constant(other)) 14 | else: 15 | raise NotImplementedError 16 | 17 | def __radd__(self, other): 18 | return self.__add__(other) 19 | 20 | def __mul__(self, other): 21 | if isinstance(other, ActiveLearningCriterion): 22 | return ProductCriterion(self, other) 23 | elif isinstance(other, (float, int, bool)): 24 | return ProductCriterion(self, Constant(other)) 25 | else: 26 | raise NotImplementedError 27 | 28 | def __rmul__(self, other): 29 | return self.__mul__(other) 30 | 31 | def __sub__(self, other): 32 | if isinstance(other, ActiveLearningCriterion): 33 | return SumCriterion(self, other * -1) 34 | elif isinstance(other, (float, int, bool)): 35 | return SumCriterion(self, Constant(-other)) 36 | else: 37 | raise NotImplementedError 38 | 39 | def __div__(self, other): 40 | if isinstance(other, ActiveLearningCriterion): 41 | return FractionCriterion(self, other) 42 | elif isinstance(other, (float, int, bool)): 43 | return FractionCriterion(self, other) 44 | else: 45 | raise NotImplementedError 46 | 47 | def __rdiv__(self, other): 48 | if isinstance(other, ActiveLearningCriterion): 49 | return FractionCriterion(other, self) 50 | elif isinstance(other, (float, int, bool)): 51 | return FractionCriterion(Constant(other), self) 52 | else: 53 | raise NotImplementedError 54 | 55 | 56 | class Constant(ActiveLearningCriterion): 57 | def __init__(self, value): 58 | self.value = value 59 | 60 | def score(self, probabilities): 61 | return torch.full((probabilities.shape[0],), self.value).to( 62 | probabilities.device 63 | ) 64 | 65 | 66 | class LeastConfidence(ActiveLearningCriterion): 67 | def score(self, probabilities): 68 | return torch.max(probabilities, dim=1).values 69 | 70 | 71 | class Margin(ActiveLearningCriterion): 72 | def score(self, probabilities): 73 | sorted_probs, _ = torch.sort(probabilities, dim=1, descending=True) 74 | return sorted_probs[:, 0] - sorted_probs[:, 1] 75 | 76 | 77 | class Entropy(ActiveLearningCriterion): 78 | def score(self, probabilities): 79 | return -torch.sum(probabilities * torch.log(probabilities), dim=1) 80 | 81 | 82 | class L1Upper(ActiveLearningCriterion): 83 | def score(self, probabilities): 84 | return torch.log(probabilities).sum(dim=1) * -1 85 | 86 | 87 | class L2Upper(ActiveLearningCriterion): 88 | def score(self, probabilities): 89 | return torch.norm(torch.log(probabilities), dim=1) 90 | 91 | 92 | class SumCriterion(ActiveLearningCriterion): 93 | def __init__(self, *criterion): 94 | self.criterion = criterion 95 | 96 | def score(self, probabilities): 97 | p = self.criterion[0].score(probabilities) 98 | for criterion in self.criterion[1:]: 99 | p += criterion.score(probabilities) 100 | return p 101 | 102 | 103 | class ProductCriterion(ActiveLearningCriterion): 104 | def __init__(self, *criterion): 105 | self.criterion = criterion 106 | 107 | def score(self, probabilities): 108 | p = self.criterion[0].score(probabilities) 109 | for criterion in self.criterion[1:]: 110 | p *= criterion.score(probabilities) 111 | return p 112 | 113 | 114 | class FractionCriterion(ActiveLearningCriterion): 115 | def __init__(self, *criterion): 116 | self.criterion_1 = criterion[0] 117 | self.criterion_2 = criterion[1] 118 | 119 | def score(self, probabilities): 120 | return self.criterion_1.score(probabilities) / self.criterion_2.score( 121 | probabilities 122 | ) 123 | -------------------------------------------------------------------------------- /deeplay/activelearning/data.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import torch.utils.data 4 | 5 | 6 | class ActiveLearningDataset(torch.utils.data.Dataset): 7 | def __init__(self, dataset): 8 | self.dataset = dataset 9 | self.annotated = np.zeros(len(dataset), dtype=bool) 10 | 11 | def __getitem__(self, index): 12 | return self.dataset[index], self.dataset[index] 13 | 14 | def __len__(self): 15 | return len(self.dataset) 16 | 17 | def annotate_random(self, n) -> np.ndarray: 18 | """Annotate n random samples.""" 19 | indices = np.random.choice(len(self.dataset), n, replace=False) 20 | self.annotated[indices] = True 21 | return indices 22 | 23 | def annotate(self, indices: np.ndarray): 24 | """Annotate specific samples.""" 25 | if isinstance(indices, torch.Tensor): 26 | indices = indices.cpu().numpy() 27 | unannotated_indices = np.where(~self.annotated)[0] 28 | self.annotated[unannotated_indices[indices]] = True 29 | 30 | def get_annotated_samples(self): 31 | data = self.get_annotated_data() 32 | X = [x for x, *_ in data] 33 | return torch.stack(X) 34 | 35 | def get_annotated_labels(self): 36 | data = self.get_annotated_data() 37 | Y = [y for _, y in data] 38 | return torch.stack(Y) 39 | 40 | def get_annotated_data(self): 41 | return torch.utils.data.Subset(self.dataset, np.where(self.annotated)[0]) 42 | 43 | def get_unannotated_samples(self): 44 | data = self.get_unannotated_data() 45 | X = [x for x, *_ in data] 46 | return torch.stack(X) 47 | 48 | def get_unannotated_labels(self): 49 | data = self.get_unannotated_data() 50 | Y = [y for _, y in data] 51 | try: 52 | return torch.stack(Y) 53 | except TypeError: 54 | return torch.LongTensor(Y) 55 | 56 | def get_unannotated_data(self): 57 | return torch.utils.data.Subset(self.dataset, np.where(~self.annotated)[0]) 58 | 59 | def get_num_annotated(self): 60 | return np.sum(self.annotated) 61 | 62 | 63 | class JointDataset(torch.utils.data.Dataset): 64 | 65 | def __init__(self, X_1, Y_1, X_2, Y_2): 66 | """ 67 | :param X_1: covariate from the first distribution 68 | :param Y_1: label from the first distribution 69 | :param X_2: 70 | :param Y_2: 71 | :param transform: 72 | """ 73 | self.X1 = X_1 74 | self.Y1 = Y_1 75 | self.X2 = X_2 76 | self.Y2 = Y_2 77 | 78 | def __len__(self): 79 | 80 | # returning the minimum length of two data-sets 81 | 82 | return max(len(self.X1), len(self.X2)) 83 | 84 | def __getitem__(self, index): 85 | Len1 = len(self.Y1) 86 | Len2 = len(self.Y2) 87 | 88 | # checking the index in the range or not 89 | 90 | if index < Len1: 91 | x_1 = self.X1[index] 92 | y_1 = self.Y1[index] 93 | 94 | else: 95 | 96 | # rescaling the index to the range of Len1 97 | re_index = index % Len1 98 | 99 | x_1 = self.X1[re_index] 100 | y_1 = self.Y1[re_index] 101 | 102 | # checking second datasets 103 | if index < Len2: 104 | 105 | x_2 = self.X2[index] 106 | y_2 = self.Y2[index] 107 | 108 | else: 109 | # rescaling the index to the range of Len2 110 | re_index = index % Len2 111 | 112 | x_2 = self.X2[re_index] 113 | y_2 = self.Y2[re_index] 114 | 115 | return index, x_1, y_1, x_2, y_2 116 | -------------------------------------------------------------------------------- /deeplay/activelearning/strategies/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy import Strategy 2 | from .uniform import UniformStrategy 3 | from .uncertainty import UncertaintyStrategy 4 | from .adversarial import AdversarialStrategy 5 | -------------------------------------------------------------------------------- /deeplay/activelearning/strategies/adversarial/__init__.py: -------------------------------------------------------------------------------- 1 | from .adversarial import AdversarialStrategy 2 | -------------------------------------------------------------------------------- /deeplay/activelearning/strategies/strategy.py: -------------------------------------------------------------------------------- 1 | from typing import * 2 | from deeplay.applications import Application 3 | from deeplay.activelearning.data import ActiveLearningDataset 4 | 5 | import torch 6 | import copy 7 | 8 | 9 | class Strategy(Application): 10 | 11 | def __init__( 12 | self, 13 | train_pool: ActiveLearningDataset, 14 | val_pool: Optional[ActiveLearningDataset] = None, 15 | test_data: Optional[torch.utils.data.Dataset] = None, 16 | batch_size: int = 32, 17 | val_batch_size: Optional[int] = None, 18 | test_batch_size: Optional[int] = None, 19 | **kwargs 20 | ): 21 | super().__init__(**kwargs) 22 | self.train_pool = train_pool 23 | self.val_pool = val_pool 24 | self.test_data = test_data 25 | self.initial_model_state: Optional[Dict[str, Any]] = None 26 | self.batch_size = batch_size 27 | self.val_batch_size = ( 28 | val_batch_size if val_batch_size is not None else batch_size 29 | ) 30 | self.test_batch_size = ( 31 | test_batch_size if test_batch_size is not None else batch_size 32 | ) 33 | 34 | def on_train_start(self) -> None: 35 | # Save the initial model state before training 36 | # such that we can reset the model to its initial state 37 | # if needed. 38 | self.initial_model_state = copy.deepcopy(self.state_dict()) 39 | self.train() 40 | 41 | return super().on_train_start() 42 | 43 | def reset_model(self): 44 | """Reset the model to its initial state. 45 | 46 | This is useful if you want to train the model from scratch 47 | after querying new samples.""" 48 | assert self.initial_model_state is not None 49 | self.load_state_dict(self.initial_model_state) 50 | # self.trainer.strategy.setup_optimizers(self.trainer) 51 | 52 | def query(self, n): 53 | """Query the strategy for n samples to annotate.""" 54 | if self.val_pool is not None: 55 | val_pool_frac = len(self.val_pool) / ( 56 | len(self.train_pool) + len(self.val_pool) 57 | ) 58 | n_val = int(n * val_pool_frac) 59 | n_train = n - n_val 60 | return self.query_train(n_train), self.query_val(n_val) 61 | else: 62 | return self.query_train(n) 63 | 64 | def query_train(self, n): 65 | """Query the strategy for n samples to annotate from the training pool.""" 66 | return self.query_strategy(self.train_pool, n) 67 | 68 | def query_val(self, n): 69 | """Query the strategy for n samples to annotate from the validation pool.""" 70 | return self.query_strategy(self.val_pool, n) 71 | 72 | def query_strategy(self, pool, n): 73 | """Implement the query strategy here.""" 74 | raise NotImplementedError() 75 | 76 | def query_and_update(self, n): 77 | """Query the strategy for n samples and update the dataset.""" 78 | self.to(self.trainer.strategy.root_device) 79 | self.eval() 80 | 81 | indices = self.query(n) 82 | if isinstance(indices, tuple): 83 | train_indices, val_indices = indices 84 | self.train_pool.annotate(train_indices) 85 | self.val_pool.annotate(val_indices) 86 | else: 87 | self.train_pool.annotate(indices) 88 | 89 | def train_dataloader(self): 90 | data = self.train_pool.get_annotated_data() 91 | return torch.utils.data.DataLoader( 92 | data, batch_size=self.batch_size, shuffle=True 93 | ) 94 | 95 | # def val_dataloader(self): 96 | # if self.val_pool is None: 97 | # return [] 98 | # data = self.train_pool.get_unannotated_data() 99 | # return torch.utils.data.DataLoader( 100 | # data, batch_size=self.val_batch_size, shuffle=False 101 | # ) 102 | 103 | def test_dataloader(self): 104 | if self.test_data is None: 105 | return [] 106 | return torch.utils.data.DataLoader( 107 | self.test_data, batch_size=self.test_batch_size, shuffle=False 108 | ) 109 | 110 | def test_step(self, batch, batch_idx): 111 | x, y = batch 112 | y_hat = self(x) 113 | self.test_metrics(y_hat, y) 114 | for name, metric in self.test_metrics.items(): 115 | self.log(name, metric) 116 | # return loss 117 | 118 | def validation_step(self, batch, batch_idx): 119 | x, y = batch 120 | y_hat = self(x) 121 | self.val_metrics(y_hat, y) 122 | for name, metric in self.val_metrics.items(): 123 | self.log(name, metric) 124 | -------------------------------------------------------------------------------- /deeplay/activelearning/strategies/uncertainty.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from deeplay.activelearning.strategies.strategy import Strategy 3 | from deeplay.activelearning.data import ActiveLearningDataset 4 | from deeplay.activelearning.criterion import ActiveLearningCriterion 5 | from deeplay.module import DeeplayModule 6 | from deeplay.external.optimizers import Adam 7 | 8 | import torch 9 | import torch.nn.functional as F 10 | 11 | 12 | class UncertaintyStrategy(Strategy): 13 | 14 | def __init__( 15 | self, 16 | classifier: DeeplayModule, 17 | criterion: ActiveLearningCriterion, 18 | train_pool: ActiveLearningDataset, 19 | val_pool: Optional[ActiveLearningDataset] = None, 20 | test: torch.utils.data.Dataset = None, 21 | batch_size: int = 32, 22 | val_batch_size: int = None, 23 | test_batch_size: int = None, 24 | loss=torch.nn.CrossEntropyLoss(), 25 | optimizer=None, 26 | **kwargs 27 | ): 28 | 29 | optimizer = optimizer or Adam(lr=1e-3) 30 | super().__init__( 31 | train_pool, 32 | val_pool, 33 | test, 34 | batch_size, 35 | val_batch_size, 36 | test_batch_size, 37 | loss=loss, 38 | optimizer=optimizer, 39 | **kwargs 40 | ) 41 | self.classifier = classifier 42 | self.criterion = criterion 43 | 44 | def query_strategy(self, pool, n): 45 | """Implement the query strategy here.""" 46 | self.eval() 47 | X = pool.get_unannotated_samples() 48 | 49 | latents = self.classifier.predict(X, batch_size=self.test_batch_size) 50 | probs = F.softmax(latents, dim=1) 51 | 52 | return self.criterion.score(probs).sort()[1][:n] 53 | 54 | def training_step(self, batch, batch_idx): 55 | self.train() 56 | return super().training_step(batch, batch_idx) 57 | 58 | def forward(self, x): 59 | return self.classifier(x) 60 | -------------------------------------------------------------------------------- /deeplay/activelearning/strategies/uniform.py: -------------------------------------------------------------------------------- 1 | from deeplay.activelearning.strategies.strategy import Strategy 2 | from deeplay.activelearning.data import ActiveLearningDataset 3 | from deeplay.module import DeeplayModule 4 | from deeplay.external import Adam 5 | 6 | import numpy as np 7 | import torch 8 | import torch.nn.functional as F 9 | 10 | 11 | class UniformStrategy(Strategy): 12 | 13 | def __init__( 14 | self, 15 | classifier: DeeplayModule, 16 | train_pool: ActiveLearningDataset, 17 | val_pool: ActiveLearningDataset = None, 18 | test: torch.utils.data.Dataset = None, 19 | batch_size: int = 32, 20 | val_batch_size: int = None, 21 | test_batch_size: int = None, 22 | loss=torch.nn.CrossEntropyLoss(), 23 | optimizer=None, 24 | **kwargs, 25 | ): 26 | optimizer = optimizer or Adam(lr=1e-3) 27 | super().__init__( 28 | train_pool, 29 | val_pool, 30 | test, 31 | batch_size, 32 | val_batch_size, 33 | test_batch_size, 34 | loss=loss, 35 | optimizer=optimizer, 36 | **kwargs, 37 | ) 38 | self.classifier = classifier 39 | 40 | def query_strategy(self, pool, n): 41 | """Implement the query strategy here.""" 42 | return np.random.choice(len(pool.get_unannotated_data()), n, replace=False) 43 | 44 | def training_step(self, batch, batch_idx): 45 | self.train() 46 | return super().training_step(batch, batch_idx) 47 | 48 | def forward(self, x): 49 | return self.classifier(x) 50 | -------------------------------------------------------------------------------- /deeplay/applications/__init__.py: -------------------------------------------------------------------------------- 1 | from .application import Application 2 | from .classification import * 3 | from .regression import * 4 | from .detection import * 5 | from .autoencoders import * 6 | 7 | # from .classification import * 8 | # from .segmentation import ImageSegmentor 9 | # from .gans import * 10 | # from .regression import * 11 | -------------------------------------------------------------------------------- /deeplay/applications/autoencoders/__init__.py: -------------------------------------------------------------------------------- 1 | from .vae import VariationalAutoEncoder 2 | from .wae import WassersteinAutoEncoder 3 | -------------------------------------------------------------------------------- /deeplay/applications/autoencoders/vae.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Sequence, Callable, List 2 | 3 | from deeplay.components import ConvolutionalEncoder2d, ConvolutionalDecoder2d 4 | from deeplay.applications import Application 5 | from deeplay.external import External, Optimizer, Adam 6 | 7 | 8 | import torch 9 | import torch.nn as nn 10 | 11 | 12 | class VariationalAutoEncoder(Application): 13 | input_size: tuple 14 | channels: list 15 | latent_dim: int 16 | encoder: torch.nn.Module 17 | decoder: torch.nn.Module 18 | beta: float 19 | reconstruction_loss: torch.nn.Module 20 | metrics: list 21 | optimizer: Optimizer 22 | 23 | def __init__( 24 | self, 25 | input_size: Optional[Sequence[int]] = (28, 28), 26 | channels: Optional[List[int]] = [32, 64], 27 | encoder: Optional[nn.Module] = None, 28 | decoder: Optional[nn.Module] = None, 29 | reconstruction_loss: Optional[Callable] = nn.BCELoss(reduction="sum"), 30 | latent_dim=int, 31 | beta=1, 32 | optimizer=None, 33 | **kwargs, 34 | ): 35 | red_size = [int(dim / (2 ** len(channels))) for dim in input_size] 36 | self.encoder = encoder or self._get_default_encoder(channels) 37 | self.fc_mu = nn.Linear( 38 | channels[-1] * red_size[0] * red_size[1], 39 | latent_dim, 40 | ) 41 | self.fc_var = nn.Linear( 42 | channels[-1] * red_size[0] * red_size[1], 43 | latent_dim, 44 | ) 45 | self.fc_dec = nn.Linear( 46 | latent_dim, 47 | channels[-1] * red_size[0] * red_size[1], 48 | ) 49 | self.decoder = decoder or self._get_default_decoder(channels[::-1], red_size) 50 | self.reconstruction_loss = reconstruction_loss or nn.BCELoss(reduction="sum") 51 | self.latent_dim = latent_dim 52 | self.beta = beta 53 | 54 | super().__init__(**kwargs) 55 | 56 | self.optimizer = optimizer or Adam(lr=1e-3) 57 | 58 | @self.optimizer.params 59 | def params(self): 60 | return self.parameters() 61 | 62 | def _get_default_encoder(self, channels): 63 | encoder = ConvolutionalEncoder2d( 64 | 1, 65 | channels, 66 | channels[-1], 67 | ) 68 | encoder.postprocess.configure(nn.Flatten) 69 | return encoder 70 | 71 | def _get_default_decoder(self, channels, red_size): 72 | decoder = ConvolutionalDecoder2d( 73 | channels[0], 74 | channels, 75 | 1, 76 | out_activation=nn.Sigmoid, 77 | ) 78 | # for block in decoder.blocks[:-1]: 79 | # block.upsample.configure(nn.ConvTranspose2d, kernel_size=3, stride=2, in_channels=block.in_channels, out_channels=block.out_channels) 80 | 81 | decoder.preprocess.configure( 82 | nn.Unflatten, 83 | dim=1, 84 | unflattened_size=(channels[0], red_size[0], red_size[1]), 85 | ) 86 | return decoder 87 | 88 | def encode(self, x): 89 | x = self.encoder(x) 90 | mu = self.fc_mu(x) 91 | log_var = self.fc_var(x) 92 | 93 | return mu, log_var 94 | 95 | def reparameterize(self, mu, log_var): 96 | std = torch.exp(0.5 * log_var) 97 | eps = torch.randn_like(std) 98 | return eps * std + mu 99 | 100 | def decode(self, z): 101 | x = self.fc_dec(z) 102 | x = self.decoder(x) 103 | return x 104 | 105 | def training_step(self, batch, batch_idx): 106 | x, y = self.train_preprocess(batch) 107 | y_hat, mu, log_var = self(x) 108 | rec_loss, KLD = self.compute_loss(y_hat, y, mu, log_var) 109 | tot_loss = rec_loss + self.beta * KLD 110 | loss = {"rec_loss": rec_loss, "KL": KLD, "total_loss": tot_loss} 111 | for name, v in loss.items(): 112 | self.log( 113 | f"train_{name}", 114 | v, 115 | on_step=True, 116 | on_epoch=True, 117 | prog_bar=True, 118 | logger=True, 119 | ) 120 | return tot_loss 121 | 122 | def compute_loss(self, y_hat, y, mu, log_var): 123 | rec_loss = self.reconstruction_loss(y_hat, y) 124 | KLD = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp()) 125 | return rec_loss, KLD 126 | 127 | def forward(self, x): 128 | mu, log_var = self.encode(x) 129 | z = self.reparameterize(mu, log_var) 130 | y_hat = self.decode(z) 131 | return y_hat, mu, log_var 132 | -------------------------------------------------------------------------------- /deeplay/applications/classification/__init__.py: -------------------------------------------------------------------------------- 1 | from .classifier import Classifier 2 | from .categorical import CategoricalClassifier 3 | from .binary import BinaryClassifier 4 | from .multilabel import MultiLabelClassifier 5 | -------------------------------------------------------------------------------- /deeplay/applications/classification/binary.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Sequence 2 | 3 | from deeplay.applications import Application 4 | from deeplay.external import Optimizer, Adam 5 | 6 | 7 | import torch 8 | import torch.nn.functional as F 9 | import torchmetrics as tm 10 | 11 | from .classifier import Classifier 12 | 13 | 14 | class BinaryClassifier(Application): 15 | model: torch.nn.Module 16 | loss: torch.nn.Module 17 | metrics: list 18 | optimizer: Optimizer 19 | 20 | def __init__( 21 | self, 22 | model: torch.nn.Module, 23 | loss: torch.nn.Module = torch.nn.BCELoss(), 24 | optimizer=None, 25 | **kwargs, 26 | ): 27 | if kwargs.get("metrics", None) is None: 28 | kwargs["metrics"] = [tm.Accuracy("binary")] 29 | 30 | super().__init__(loss=loss, **kwargs) 31 | 32 | self.model = model 33 | self.optimizer = optimizer or Adam(lr=1e-3) 34 | 35 | def compute_loss(self, y_hat, y): 36 | if isinstance(self.loss, (torch.nn.BCELoss, torch.nn.BCEWithLogitsLoss)): 37 | y = y.float() 38 | return super().compute_loss(y_hat, y) 39 | 40 | def metrics_preprocess(self, y_hat, y: torch.Tensor): 41 | if y.is_floating_point(): 42 | y = y > 0.5 43 | return super().metrics_preprocess(y_hat, y) 44 | 45 | def forward(self, x): 46 | return self.model(x) 47 | -------------------------------------------------------------------------------- /deeplay/applications/classification/categorical.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Sequence 2 | 3 | from deeplay.applications import Application 4 | from deeplay.external import Optimizer, Adam 5 | 6 | 7 | import torch 8 | import torch.nn.functional as F 9 | import torchmetrics as tm 10 | 11 | from .classifier import Classifier 12 | 13 | 14 | class CategoricalClassifier(Application): 15 | model: torch.nn.Module 16 | loss: torch.nn.Module 17 | metrics: list 18 | optimizer: Optimizer 19 | 20 | def __init__( 21 | self, 22 | model: torch.nn.Module, 23 | loss: torch.nn.Module = torch.nn.CrossEntropyLoss(), 24 | optimizer=None, 25 | make_targets_one_hot: bool = False, 26 | num_classes: Optional[int] = None, 27 | **kwargs, 28 | ): 29 | if num_classes is not None and kwargs.get("metrics", None) is None: 30 | kwargs["metrics"] = [tm.Accuracy("multiclass", num_classes=num_classes)] 31 | 32 | super().__init__(loss=loss, **kwargs) 33 | 34 | self.model = model 35 | self.optimizer = optimizer or Adam(lr=1e-3) 36 | self.make_targets_one_hot = make_targets_one_hot 37 | 38 | @self.optimizer.params 39 | def params(self): 40 | return self.model.parameters() 41 | 42 | def compute_loss(self, y_hat, y): 43 | if self.make_targets_one_hot: 44 | y = F.one_hot(y, num_classes=y_hat.size(1)).float() 45 | 46 | return self.loss(y_hat, y) 47 | 48 | def forward(self, x): 49 | return self.model(x) 50 | -------------------------------------------------------------------------------- /deeplay/applications/classification/classifier.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Sequence 2 | 3 | from deeplay.applications import Application 4 | from deeplay.external import External, Optimizer, Adam 5 | 6 | 7 | import torch 8 | import torch.nn.functional as F 9 | import torchmetrics as tm 10 | 11 | 12 | class Classifier(Application): 13 | 14 | model: torch.nn.Module 15 | metrics: list 16 | 17 | def __init__( 18 | self, 19 | model: torch.nn.Module, 20 | loss: torch.nn.Module = torch.nn.CrossEntropyLoss(), 21 | optimizer=None, 22 | make_targets_one_hot: bool = False, 23 | num_classes: Optional[int] = None, 24 | **kwargs, 25 | ): 26 | if num_classes is not None and kwargs.get("metrics", None) is None: 27 | kwargs["metrics"] = [tm.Accuracy("multiclass", num_classes=num_classes)] 28 | super().__init__(loss=loss, optimizer=optimizer or Adam(lr=1e-3), **kwargs) 29 | 30 | self.model = model 31 | self.make_targets_one_hot = make_targets_one_hot 32 | self.num_classes = num_classes 33 | 34 | def compute_loss(self, y_hat, y): 35 | if self.make_targets_one_hot: 36 | y = F.one_hot(y, num_classes=self.num_classes or y_hat.size(1)).float() 37 | 38 | return self.loss(y_hat, y) 39 | 40 | def forward(self, x): 41 | return self.model(x) 42 | -------------------------------------------------------------------------------- /deeplay/applications/classification/multilabel.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Sequence 2 | 3 | from deeplay.applications import Application 4 | from deeplay.external import Optimizer, Adam 5 | 6 | 7 | import torch 8 | import torch.nn.functional as F 9 | import torchmetrics as tm 10 | 11 | from .classifier import Classifier 12 | 13 | 14 | class MultiLabelClassifier(Application): 15 | model: torch.nn.Module 16 | loss: torch.nn.Module 17 | metrics: list 18 | optimizer: Optimizer 19 | 20 | def __init__( 21 | self, 22 | model: torch.nn.Module, 23 | loss: torch.nn.Module = torch.nn.BCELoss(), 24 | optimizer=None, 25 | **kwargs, 26 | ): 27 | if kwargs.get("metrics", None) is None: 28 | kwargs["metrics"] = [tm.Accuracy("binary")] 29 | 30 | super().__init__(loss=loss, **kwargs) 31 | 32 | self.model = model 33 | self.optimizer = optimizer or Adam(lr=1e-3) 34 | 35 | @self.optimizer.params 36 | def params(self): 37 | return self.model.parameters() 38 | 39 | def compute_loss(self, y_hat, y): 40 | return self.loss(y_hat, y) 41 | 42 | def forward(self, x): 43 | return self.model(x) 44 | -------------------------------------------------------------------------------- /deeplay/applications/detection/__init__.py: -------------------------------------------------------------------------------- 1 | from .lodestar import LodeSTAR 2 | -------------------------------------------------------------------------------- /deeplay/applications/detection/lodestar/__init__.py: -------------------------------------------------------------------------------- 1 | from .lodestar import LodeSTAR 2 | -------------------------------------------------------------------------------- /deeplay/applications/detection/lodestar/dataset.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/applications/detection/lodestar/dataset.py -------------------------------------------------------------------------------- /deeplay/applications/detection/lodestar/transforms.py: -------------------------------------------------------------------------------- 1 | import torchvision 2 | import torchvision.transforms.functional 3 | import functools 4 | import numpy as np 5 | import torch 6 | import kornia 7 | 8 | 9 | class Transform: 10 | def __init__(self, forward, inverse=lambda x, **kwargs: x, **kwargs): 11 | self.forward = forward 12 | self.inverse = inverse 13 | self.kwargs = kwargs 14 | 15 | def __call__(self, x): 16 | n = x.size(0) 17 | 18 | kwargs = self.kwargs.copy() 19 | 20 | for key, value in kwargs.items(): 21 | if callable(value): 22 | kwargs[key] = torch.tensor([value() for _ in range(n)]) 23 | 24 | return self.forward(x, **kwargs), functools.partial(self.inverse, **kwargs) 25 | 26 | 27 | class Transforms: 28 | def __init__(self, transforms): 29 | self.transforms = transforms 30 | 31 | def __call__(self, x): 32 | inverses = [] 33 | for transform in self.transforms: 34 | x, inverse = transform(x) 35 | inverses.append(inverse) 36 | return x, self._create_inverse(inverses) 37 | 38 | def _create_inverse(self, inverses): 39 | def inverse(x): 40 | for inverse in inverses[::-1]: 41 | x = inverse(x) 42 | return x 43 | 44 | return inverse 45 | 46 | 47 | class RandomTranslation2d(Transform): 48 | def __init__( 49 | self, 50 | dx=lambda: np.random.uniform(-2, 2), 51 | dy=lambda: np.random.uniform(-2, 2), 52 | indices=(0, 1), 53 | ): 54 | assert len(indices) == 2, "Indices must be a tuple of length 2" 55 | assert all(isinstance(i, int) for i in indices), "Indices must be integers" 56 | super().__init__(self._forward, self._backward, dx=dx, dy=dy, indices=indices) 57 | 58 | @staticmethod 59 | def _forward(x, dx, dy, indices): 60 | translation = torch.stack([dx, dy], dim=1).type_as(x).to(x.device) 61 | return kornia.geometry.transform.translate( 62 | x, translation, align_corners=True, padding_mode="reflection" 63 | ) 64 | 65 | @staticmethod 66 | def _backward(x: torch.Tensor, dx, dy, indices): 67 | sub_v = torch.zeros_like(x) 68 | sub_v[:, indices[0]] = dy 69 | sub_v[:, indices[1]] = dx 70 | return x - sub_v 71 | 72 | 73 | class RandomRotation2d(Transform): 74 | def __init__(self, angle=lambda: np.random.uniform(-np.pi, np.pi), indices=(0, 1)): 75 | super().__init__(self._forward, self._backward, angle=angle, indices=indices) 76 | 77 | @staticmethod 78 | def _forward(x, angle, indices): 79 | angle = angle.type_as(x).to(x.device) 80 | return kornia.geometry.transform.rotate( 81 | x, angle * 180 / np.pi, align_corners=True, padding_mode="reflection" 82 | ) 83 | 84 | @staticmethod 85 | def _backward(x, angle, indices): 86 | mat2d = ( 87 | torch.eye(x.size(1), device=x.device).unsqueeze(0).repeat(x.size(0), 1, 1) 88 | ) 89 | mat2d[:, indices[1], indices[1]] = torch.cos(-angle) 90 | mat2d[:, indices[1], indices[0]] = -torch.sin(-angle) 91 | mat2d[:, indices[0], indices[1]] = torch.sin(-angle) 92 | mat2d[:, indices[0], indices[0]] = torch.cos(-angle) 93 | out = torch.matmul(x.unsqueeze(1), mat2d).squeeze(1) 94 | 95 | return out 96 | -------------------------------------------------------------------------------- /deeplay/applications/regression/__init__.py: -------------------------------------------------------------------------------- 1 | from .regressor import Regressor 2 | -------------------------------------------------------------------------------- /deeplay/applications/regression/regressor.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Sequence 2 | 3 | from deeplay.applications import Application 4 | from deeplay.external import External, Optimizer, Adam 5 | 6 | 7 | import torch 8 | import torch.nn.functional as F 9 | import torchmetrics as tm 10 | 11 | 12 | class Regressor(Application): 13 | model: torch.nn.Module 14 | loss: torch.nn.Module 15 | metrics: list 16 | optimizer: Optimizer 17 | 18 | def __init__( 19 | self, 20 | model: torch.nn.Module, 21 | loss: torch.nn.Module = torch.nn.L1Loss(), 22 | optimizer=None, 23 | **kwargs, 24 | ): 25 | super().__init__(loss=loss, optimizer=optimizer or Adam(lr=1e-3), **kwargs) 26 | 27 | self.model = model 28 | 29 | def forward(self, x): 30 | return self.model(x) 31 | -------------------------------------------------------------------------------- /deeplay/blocks/__init__.py: -------------------------------------------------------------------------------- 1 | from .block import Block 2 | from .la import LayerActivation 3 | from .lan import LayerActivationNormalization 4 | from .plan import PoolLayerActivationNormalization 5 | from .land import LayerActivationNormalizationDropout 6 | from .recurrentblock import RecurrentBlock 7 | from .lanu import LayerActivationNormalizationUpsample 8 | from .residual import BaseResidual, Conv2dResidual 9 | from .conv import Conv2dBlock 10 | from .linear import LinearBlock 11 | from .ls import LayerSkip 12 | 13 | # from .conv2d import 14 | -------------------------------------------------------------------------------- /deeplay/blocks/block.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | TypeVar, 4 | overload, 5 | Optional, 6 | Union, 7 | Type, 8 | Any, 9 | ) 10 | 11 | import torch.nn as nn 12 | 13 | import warnings 14 | 15 | from deeplay import DeeplayModule 16 | from deeplay.external import Layer 17 | 18 | 19 | T = TypeVar("T") 20 | 21 | 22 | class Block(DeeplayModule): 23 | @property 24 | def configurables(self): 25 | return ( 26 | super() 27 | .configurables.union(self.kwargs.keys()) 28 | .union(self.kwargs.get("order", [])) 29 | ) 30 | 31 | def __init__(self, **kwargs: Union[DeeplayModule, Type[nn.Module]]): 32 | super().__init__() 33 | 34 | for name, val in kwargs.items(): 35 | # if val is a uninitialized module, we wrap in Layer 36 | if isinstance(val, type) and issubclass(val, nn.Module): 37 | val = Layer(val) 38 | setattr(self, name, val) 39 | 40 | def set_input_map(self, *args: str, **kwargs: str): 41 | for name in self.order: 42 | getattr(self, name).set_input_map(*args, **kwargs) 43 | 44 | def set_output_map(self, *args: str, **kwargs: int): 45 | for name in self.order: 46 | getattr(self, name).set_output_map(*args, **kwargs) 47 | 48 | @overload 49 | def configure(self, **kwargs: DeeplayModule) -> None: ... 50 | 51 | @overload 52 | def configure(self, order: List[str], **kwargs) -> None: ... 53 | 54 | @overload 55 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 56 | 57 | def configure(self, *args, order=None, **kwargs): 58 | # We do this to make sure that the order is set before the 59 | # rest of the kwargs are set. This is important because 60 | # the order is used to determine allowed kwargs. 61 | if order is not None: 62 | return super().configure(*args, order=order, **kwargs) 63 | else: 64 | return super().configure(*args, **kwargs) 65 | -------------------------------------------------------------------------------- /deeplay/blocks/conv/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["Conv2dBlock"] 2 | 3 | from .conv2d import Conv2dBlock 4 | -------------------------------------------------------------------------------- /deeplay/blocks/conv/conv2d.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | import torch.nn as nn 3 | from _typeshed import Incomplete 4 | from deeplay.blocks.base import BaseBlock as BaseBlock, DeferredConfigurableLayer as DeferredConfigurableLayer 5 | from deeplay.external import Layer as Layer 6 | from deeplay.module import DeeplayModule as DeeplayModule 7 | from deeplay.ops.logs import FromLogs as FromLogs 8 | from deeplay.ops.merge import Add as Add, MergeOp as MergeOp 9 | from deeplay.ops.shape import Permute as Permute 10 | from typing import Literal, Type 11 | from typing_extensions import Self 12 | 13 | class Conv2dBlock(BaseBlock): 14 | pool: DeferredConfigurableLayer | nn.Module 15 | in_channels: Incomplete 16 | out_channels: Incomplete 17 | kernel_size: Incomplete 18 | stride: Incomplete 19 | padding: Incomplete 20 | def __init__(self, in_channels: int | None, out_channels: int, kernel_size: int = 3, stride: int = 1, padding: int = 0, **kwargs) -> None: ... 21 | def normalized(self, normalization: Type[nn.Module] | DeeplayModule = ..., mode: str = 'append', after: Incomplete | None = None) -> Self: ... 22 | def pooled(self, pool: Layer = ..., mode: str = 'prepend', after: Incomplete | None = None) -> Self: ... 23 | def upsampled(self, upsample: Layer = ..., mode: str = 'append', after: Incomplete | None = None) -> Self: ... 24 | def transposed(self, transpose: Layer = ..., mode: str = 'prepend', after: Incomplete | None = None, remove_upsample: bool = True, remove_layer: bool = True) -> Self: ... 25 | def strided(self, stride: int | tuple[int, ...], remove_pool: bool = True) -> Self: ... 26 | def multi(self, n: int = 1) -> Self: ... 27 | def shortcut(self, merge: MergeOp = ..., shortcut: Literal['auto'] | Type[nn.Module] | DeeplayModule | None = 'auto') -> Self: ... 28 | @overload 29 | def style(self, style: Literal["residual"], order: str="lanlan|", activation: Union[Type[nn.Module], Layer]=..., normalization: Union[Type[nn.Module], Layer]=..., dropout: float=0.1) -> Self: 30 | """Make a residual block with the given order of layers. 31 | 32 | Parameters 33 | ---------- 34 | order : str 35 | The order of layers in the residual block. The shorthand is a string of 'l', 'a', 'n', 'd' and '|'. 36 | 'l' stands for layer, 'a' stands for activation, 'n' stands for normalization, 'd' stands for dropout, 37 | and '|' stands for the skip connection. The order of the characters in the string determines the order 38 | of the layers in the residual block. The characters after the '|' determine the order of the layers after 39 | the skip connection. 40 | activation : Union[Type[nn.Module], Layer] 41 | The activation function to use in the residual block. 42 | normalization : Union[Type[nn.Module], Layer] 43 | The normalization layer to use in the residual block. 44 | dropout : float 45 | The dropout rate to use in the residual block. 46 | 47 | """ 48 | @overload 49 | def style(self, style: Literal["spatial_self_attention"], to_channel_last: bool=False, normalization: Union[Layer, Type[nn.Module]]=...) -> Self: ... 50 | @overload 51 | def style(self, style: Literal["spatial_cross_attention"], to_channel_last: bool=False, normalization: Union[Layer, Type[nn.Module]]=..., condition_name: str="condition") -> Self: ... 52 | @overload 53 | def style(self, style: Literal["spatial_transformer"], to_channel_last: bool=False, normalization: Union[Layer, Type[nn.Module]]=..., condition_name: Optional[str]="condition") -> Self: ... 54 | @overload 55 | def style(self, style: Literal["resnet"], stride: int=1) -> Self: ... 56 | @overload 57 | def style(self, style: Literal["resnet18_input"], ) -> Self: ... 58 | def style(self, style: str, **kwargs) -> Self: ... 59 | 60 | def residual(block: Conv2dBlock, order: str = 'lanlan|', activation: Type[nn.Module] | Layer = ..., normalization: Type[nn.Module] | Layer = ..., dropout: float = 0.1): ... 61 | def spatial_self_attention(block: Conv2dBlock, to_channel_last: bool = False, normalization: Layer | Type[nn.Module] = ...): ... 62 | def spatial_cross_attention(block: Conv2dBlock, to_channel_last: bool = False, normalization: Layer | Type[nn.Module] = ..., condition_name: str = 'condition'): ... 63 | def spatial_transformer(block: Conv2dBlock, to_channel_last: bool = False, normalization: Layer | Type[nn.Module] = ..., condition_name: str | None = 'condition'): ... 64 | -------------------------------------------------------------------------------- /deeplay/blocks/la.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | TypeVar, 4 | overload, 5 | Literal, 6 | Optional, 7 | Any, 8 | ) 9 | 10 | import torch.nn as nn 11 | 12 | from deeplay import DeeplayModule 13 | from .sequential import SequentialBlock 14 | 15 | 16 | class LayerActivation(SequentialBlock): 17 | layer: DeeplayModule 18 | activation: DeeplayModule 19 | order: List[str] 20 | 21 | def __init__( 22 | self, 23 | layer: DeeplayModule, 24 | activation: DeeplayModule, 25 | order=["layer", "activation"], 26 | **kwargs: DeeplayModule, 27 | ): 28 | super().__init__(layer=layer, activation=activation, order=order, **kwargs) 29 | 30 | @overload 31 | def configure( 32 | self, 33 | order: Optional[List[str]] = None, 34 | layer: Optional[DeeplayModule] = None, 35 | activation: Optional[DeeplayModule] = None, 36 | **kwargs: DeeplayModule, 37 | ) -> None: ... 38 | 39 | @overload 40 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: ... 41 | 42 | @overload 43 | def configure(self, name: Literal["activation"], *args, **kwargs) -> None: ... 44 | 45 | @overload 46 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 47 | 48 | def configure(self, *args, **kwargs): # type: ignore 49 | super().configure(*args, **kwargs) 50 | -------------------------------------------------------------------------------- /deeplay/blocks/lan.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | overload, 4 | Optional, 5 | Literal, 6 | Any, 7 | ) 8 | 9 | import torch.nn as nn 10 | 11 | from deeplay import DeeplayModule 12 | from .sequential import SequentialBlock 13 | 14 | 15 | class LayerActivationNormalization(SequentialBlock): 16 | layer: nn.Module 17 | activation: nn.Module 18 | normalization: nn.Module 19 | order: List[str] 20 | 21 | def __init__( 22 | self, 23 | layer: nn.Module, 24 | activation: nn.Module, 25 | normalization: nn.Module, 26 | order: List[str] = ["layer", "activation", "normalization"], 27 | **kwargs: nn.Module, 28 | ): 29 | super().__init__( 30 | layer=layer, 31 | activation=activation, 32 | normalization=normalization, 33 | order=order, 34 | **kwargs, 35 | ) 36 | 37 | @overload 38 | def configure(self, **kwargs: nn.Module) -> None: ... 39 | 40 | @overload 41 | def configure( 42 | self, 43 | order: Optional[List[str]], 44 | layer: Optional[nn.Module], 45 | activation: Optional[nn.Module], 46 | **kwargs: nn.Module, 47 | ) -> None: ... 48 | 49 | @overload 50 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: ... 51 | 52 | @overload 53 | def configure(self, name: Literal["activation"], *args, **kwargs) -> None: ... 54 | 55 | @overload 56 | def configure(self, name: Literal["normalization"], *args, **kwargs) -> None: ... 57 | 58 | @overload 59 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 60 | 61 | def configure(self, *args, **kwargs): # type: ignore 62 | super().configure(*args, **kwargs) 63 | -------------------------------------------------------------------------------- /deeplay/blocks/land.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | overload, 4 | Optional, 5 | Literal, 6 | Any, 7 | ) 8 | 9 | import torch.nn as nn 10 | from deeplay import DeeplayModule 11 | from .sequential import SequentialBlock 12 | 13 | 14 | class LayerActivationNormalizationDropout(SequentialBlock): 15 | layer: nn.Module 16 | activation: nn.Module 17 | normalization: nn.Module 18 | dropout: nn.Module 19 | order: List[str] 20 | 21 | def __init__( 22 | self, 23 | layer: nn.Module, 24 | activation: nn.Module, 25 | normalization: nn.Module, 26 | dropout: nn.Module, 27 | order: List[str] = ["layer", "activation", "normalization", "dropout"], 28 | **kwargs: nn.Module, 29 | ): 30 | super().__init__( 31 | layer=layer, 32 | activation=activation, 33 | normalization=normalization, 34 | dropout=dropout, 35 | order=order, 36 | **kwargs, 37 | ) 38 | 39 | @overload 40 | def configure(self, **kwargs: nn.Module) -> None: ... 41 | 42 | @overload 43 | def configure( 44 | self, 45 | order: Optional[List[str]], 46 | layer: Optional[nn.Module], 47 | activation: Optional[nn.Module], 48 | **kwargs: nn.Module, 49 | ) -> None: ... 50 | 51 | @overload 52 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: ... 53 | 54 | @overload 55 | def configure(self, name: Literal["activation"], *args, **kwargs) -> None: ... 56 | 57 | @overload 58 | def configure(self, name: Literal["normalization"], *args, **kwargs) -> None: ... 59 | 60 | @overload 61 | def configure(self, name: Literal["dropout"], *args, **kwargs) -> None: ... 62 | 63 | @overload 64 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 65 | 66 | def configure(self, *args, **kwargs): # type: ignore 67 | super().configure(*args, **kwargs) 68 | -------------------------------------------------------------------------------- /deeplay/blocks/lanu.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | overload, 4 | Optional, 5 | Literal, 6 | Any, 7 | ) 8 | 9 | import torch.nn as nn 10 | 11 | from .sequential import SequentialBlock 12 | 13 | 14 | class LayerActivationNormalizationUpsample(SequentialBlock): 15 | layer: nn.Module 16 | activation: nn.Module 17 | normalization: nn.Module 18 | upsample: nn.Module 19 | order: List[str] 20 | 21 | def __init__( 22 | self, 23 | layer: nn.Module, 24 | activation: nn.Module, 25 | normalization: nn.Module, 26 | upsample: nn.Module, 27 | order: List[str] = ["layer", "activation", "normalization", "upsample"], 28 | **kwargs: nn.Module, 29 | ): 30 | super().__init__( 31 | layer=layer, 32 | activation=activation, 33 | normalization=normalization, 34 | upsample=upsample, 35 | order=order, 36 | **kwargs, 37 | ) 38 | 39 | @overload 40 | def configure(self, **kwargs: nn.Module) -> None: 41 | ... 42 | 43 | @overload 44 | def configure( 45 | self, 46 | order: Optional[List[str]], 47 | layer: Optional[nn.Module], 48 | activation: Optional[nn.Module], 49 | **kwargs: nn.Module, 50 | ) -> None: 51 | ... 52 | 53 | @overload 54 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: 55 | ... 56 | 57 | @overload 58 | def configure(self, name: Literal["activation"], *args, **kwargs) -> None: 59 | ... 60 | 61 | @overload 62 | def configure(self, name: Literal["normalization"], *args, **kwargs) -> None: 63 | ... 64 | 65 | @overload 66 | def configure(self, name: Literal["upsample"], *args, **kwargs) -> None: 67 | ... 68 | 69 | @overload 70 | def configure(self, name: str, *args, **kwargs: Any) -> None: 71 | ... 72 | 73 | def configure(self, *args, **kwargs): # type: ignore 74 | super().configure(*args, **kwargs) 75 | -------------------------------------------------------------------------------- /deeplay/blocks/linear/__init__.py: -------------------------------------------------------------------------------- 1 | from .linear import LinearBlock 2 | -------------------------------------------------------------------------------- /deeplay/blocks/linear/linear.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Optional, Type, Union 3 | from typing_extensions import Self 4 | import warnings 5 | import torch.nn as nn 6 | 7 | from deeplay.blocks.base import BaseBlock 8 | from deeplay.external import Layer 9 | from deeplay.module import DeeplayModule 10 | from deeplay.ops.merge import Add, MergeOp 11 | import torch 12 | import torch.nn as nn 13 | 14 | 15 | class LinearBlock(BaseBlock): 16 | """Convolutional block with optional normalization and activation.""" 17 | 18 | def __init__( 19 | self, 20 | in_features: Optional[int], 21 | out_features: int, 22 | bias: bool = True, 23 | **kwargs, 24 | ): 25 | 26 | self.in_features = in_features 27 | self.out_features = out_features 28 | 29 | if in_features is None: 30 | layer = Layer( 31 | nn.LazyLinear, 32 | out_features, 33 | bias=bias, 34 | ) 35 | else: 36 | layer = Layer( 37 | nn.Linear, 38 | in_features, 39 | out_features, 40 | bias=bias, 41 | ) 42 | 43 | super().__init__(layer=layer, **kwargs) 44 | 45 | def get_default_normalization(self) -> DeeplayModule: 46 | return Layer(nn.BatchNorm1d, self.out_features) 47 | 48 | def get_default_activation(self) -> DeeplayModule: 49 | return Layer(nn.ReLU) 50 | 51 | def get_default_shortcut(self) -> DeeplayModule: 52 | if self.in_features == self.out_features: 53 | return Layer(nn.Identity) 54 | else: 55 | return Layer(nn.Linear, self.in_features, self.out_features) 56 | 57 | def get_default_merge(self) -> MergeOp: 58 | return Add() 59 | 60 | def call_with_dummy_data(self): 61 | x = torch.randn(2, self.in_features) 62 | return self(x) 63 | -------------------------------------------------------------------------------- /deeplay/blocks/ls.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | overload, 3 | Literal, 4 | Optional, 5 | Any, 6 | ) 7 | 8 | from deeplay.module import DeeplayModule 9 | from .sequential import SequentialBlock 10 | 11 | 12 | class LayerSkip(SequentialBlock): 13 | layer: DeeplayModule 14 | skip: DeeplayModule 15 | 16 | def __init__( 17 | self, 18 | layer: DeeplayModule, 19 | skip: DeeplayModule, 20 | ): 21 | super().__init__(layer=layer, skip=skip) 22 | 23 | def forward(self, x): 24 | y = self.layer(x) 25 | y = self.skip(y, x) 26 | return y 27 | 28 | @overload 29 | def configure( 30 | self, 31 | layer: Optional[DeeplayModule] = None, 32 | skip: Optional[DeeplayModule] = None, 33 | ) -> None: ... 34 | 35 | @overload 36 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: ... 37 | 38 | @overload 39 | def configure(self, name: Literal["skip"], *args, **kwargs) -> None: ... 40 | 41 | @overload 42 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 43 | 44 | def configure(self, *args, **kwargs): 45 | super().configure(*args, **kwargs) 46 | -------------------------------------------------------------------------------- /deeplay/blocks/plan.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | overload, 4 | Optional, 5 | Literal, 6 | Any, 7 | ) 8 | 9 | 10 | import torch.nn as nn 11 | 12 | from .sequential import SequentialBlock 13 | from deeplay.external import Layer 14 | 15 | 16 | class PoolLayerActivationNormalization(SequentialBlock): 17 | pool: nn.Module 18 | layer: nn.Module 19 | activation: nn.Module 20 | normalization: nn.Module 21 | order: List[str] 22 | 23 | def __init__( 24 | self, 25 | pool: Layer, 26 | layer: Layer, 27 | activation: Layer, 28 | normalization: Layer, 29 | order: List[str] = ["pool", "layer", "activation", "normalization"], 30 | **kwargs: Layer, 31 | ): 32 | super().__init__( 33 | pool=pool, 34 | layer=layer, 35 | activation=activation, 36 | normalization=normalization, 37 | order=order, 38 | **kwargs, 39 | ) 40 | 41 | @overload 42 | def configure(self, **kwargs: nn.Module) -> None: ... 43 | 44 | @overload 45 | def configure( 46 | self, 47 | order: Optional[List[str]], 48 | layer: Optional[nn.Module], 49 | activation: Optional[nn.Module], 50 | **kwargs: nn.Module, 51 | ) -> None: ... 52 | 53 | @overload 54 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: ... 55 | 56 | @overload 57 | def configure(self, name: Literal["activation"], *args, **kwargs) -> None: ... 58 | 59 | @overload 60 | def configure(self, name: Literal["normalization"], *args, **kwargs) -> None: ... 61 | 62 | @overload 63 | def configure(self, name: Literal["pool"], *args, **kwargs) -> None: ... 64 | 65 | @overload 66 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 67 | 68 | def configure(self, *args, **kwargs): # type: ignore 69 | super().configure(*args, **kwargs) 70 | -------------------------------------------------------------------------------- /deeplay/blocks/recurrentblock.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | overload, 4 | Optional, 5 | Literal, 6 | Any, 7 | ) 8 | 9 | import torch.nn as nn 10 | from deeplay import DeeplayModule 11 | from .sequential import SequentialBlock 12 | from deeplay import Layer 13 | 14 | 15 | class RecurrentBlock(SequentialBlock): 16 | layer: nn.Module 17 | activation: nn.Module 18 | normalization: nn.Module 19 | dropout: nn.Module 20 | order: List[str] 21 | 22 | def __init__( 23 | self, 24 | layer: nn.Module, 25 | activation: nn.Module, 26 | normalization: nn.Module, 27 | dropout: nn.Module, 28 | order: List[str] = ["layer", "activation", "normalization", "dropout"], 29 | **kwargs: nn.Module, 30 | ): 31 | super().__init__( 32 | layer=layer, 33 | activation=activation, 34 | normalization=normalization, 35 | dropout=dropout, 36 | order=order, 37 | **kwargs, 38 | ) 39 | 40 | @overload 41 | def configure(self, **kwargs: nn.Module) -> None: ... 42 | 43 | @overload 44 | def configure( 45 | self, 46 | order: Optional[List[str]], 47 | layer: Optional[nn.Module], 48 | activation: Optional[nn.Module], 49 | **kwargs: nn.Module, 50 | ) -> None: ... 51 | 52 | @overload 53 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: ... 54 | 55 | @overload 56 | def configure(self, name: Literal["activation"], *args, **kwargs) -> None: ... 57 | 58 | @overload 59 | def configure(self, name: Literal["normalization"], *args, **kwargs) -> None: ... 60 | 61 | @overload 62 | def configure(self, name: Literal["dropout"], *args, **kwargs) -> None: ... 63 | 64 | @overload 65 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 66 | 67 | def configure(self, *args, **kwargs): # type: ignore 68 | super().configure(*args, **kwargs) 69 | -------------------------------------------------------------------------------- /deeplay/blocks/sequence/__init__.py: -------------------------------------------------------------------------------- 1 | from .sequence1d import Sequence1dBlock 2 | -------------------------------------------------------------------------------- /deeplay/blocks/sequence/sequence1d.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Literal, Type, Union 3 | from typing_extensions import Self 4 | import warnings 5 | import torch.nn as nn 6 | 7 | from deeplay.blocks.base import BaseBlock 8 | from deeplay.external import Layer 9 | from deeplay.module import DeeplayModule 10 | 11 | 12 | class SequenceDropout(nn.Module): 13 | """Dropout layer for sequences. 14 | 15 | Ensures that the dropout mask is not applied to the hidden state. 16 | Also works with packed sequences. If input data is a tensor, the dropout is applied as usual. 17 | 18 | Parameters 19 | ---------- 20 | p: float 21 | Probability of an element to be zeroed. Default: 0.0 22 | 23 | """ 24 | 25 | def __init__(self, p=0.0): 26 | super().__init__() 27 | self.p = p 28 | self.dropout = nn.Dropout(p=self.p) 29 | 30 | def forward(self, x): 31 | if not isinstance(x, tuple): 32 | if isinstance(x, nn.utils.rnn.PackedSequence): 33 | return nn.utils.rnn.PackedSequence(self.dropout(x.data), x.batch_sizes) 34 | else: 35 | return self.dropout(x) 36 | 37 | if isinstance(x[0], nn.utils.rnn.PackedSequence): 38 | return ( 39 | nn.utils.rnn.PackedSequence(self.dropout(x[0].data), x[0].batch_sizes), 40 | x[1], 41 | ) 42 | return self.dropout(x[0]), x[1] 43 | 44 | 45 | class Sequence1dBlock(BaseBlock): 46 | """Convolutional block with optional normalization and activation.""" 47 | 48 | @property 49 | def is_recurrent(self): 50 | return self.mode in ["LSTM", "GRU", "RNN"] 51 | 52 | def __init__( 53 | self, 54 | in_features: int, 55 | out_features: int, 56 | batch_first: bool = False, 57 | mode: Literal["LSTM", "GRU", "RNN", "attention"] = "LSTM", 58 | return_cell_state: bool = False, 59 | **kwargs, 60 | ): 61 | 62 | self.in_features = in_features 63 | self.out_features = out_features 64 | self.batch_first = batch_first 65 | 66 | cls = dict( 67 | LSTM=nn.LSTM, GRU=nn.GRU, RNN=nn.RNN, attention=nn.MultiheadAttention 68 | )[mode] 69 | if cls == nn.MultiheadAttention: 70 | raise NotImplementedError("Attention not implemented yet") 71 | 72 | self.mode = mode 73 | self.return_cell_state = return_cell_state 74 | 75 | layer = Layer( 76 | cls, 77 | in_features, 78 | out_features, 79 | batch_first=batch_first, 80 | ) 81 | 82 | super().__init__(layer=layer, **kwargs) 83 | 84 | def get_default_normalization(self) -> DeeplayModule: 85 | return Layer(nn.LayerNorm, self.out_features) 86 | 87 | def run_with_dummy_data(self): 88 | import torch 89 | 90 | x = torch.randn(2, 3, self.in_features) 91 | return self(x) 92 | 93 | def LSTM(self): 94 | self.configure(mode="LSTM") 95 | self.layer.configure(nn.LSTM) 96 | return self 97 | 98 | def GRU(self): 99 | self.configure(mode="GRU") 100 | self.layer.configure(nn.GRU) 101 | return self 102 | 103 | def RNN(self): 104 | self.configure(mode="RNN") 105 | self.layer.configure(nn.RNN) 106 | return self 107 | 108 | def bidirectional(self): 109 | self.layer.configure(bidirectional=True) 110 | return self 111 | 112 | def append_dropout(self, p: float, name: str | None = "dropout"): 113 | self.append(Layer(SequenceDropout, p), name=name) 114 | return self 115 | 116 | def prepend_dropout(self, p: float, name: str | None = "dropout"): 117 | self.prepend(Layer(SequenceDropout, p), name=name) 118 | return self 119 | 120 | def insert_dropout(self, p: float, after: str, name: str | None = "dropout"): 121 | self.insert(Layer(SequenceDropout, p), after=after, name=name) 122 | return self 123 | 124 | def forward(self, x): 125 | # TODO: refactor to use super class forward 126 | hx = None 127 | for name in self.order: 128 | block = getattr(self, name) 129 | if name == "layer": 130 | x = block(x) 131 | if self.is_recurrent: 132 | x, hx = x 133 | if block.bidirectional: 134 | x = x[:, :, : self.out_features] + x[:, :, self.out_features :] 135 | elif name == "shortcut_start": 136 | shortcut = block(x) 137 | elif name == "shortcut_end": 138 | x = block(x, shortcut) 139 | else: 140 | x = block(x) 141 | if self.return_cell_state: 142 | return x, hx 143 | return x 144 | -------------------------------------------------------------------------------- /deeplay/callbacks/__init__.py: -------------------------------------------------------------------------------- 1 | from .history import LogHistory 2 | from .progress import RichProgressBar, TQDMProgressBar 3 | -------------------------------------------------------------------------------- /deeplay/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .mlp import MultiLayerPerceptron 2 | from .cnn import * 3 | from .rnn import * 4 | 5 | # from .dcgan import * 6 | from .gnn import * 7 | from .dict import * 8 | from .transformer import * 9 | from .diffusion import * 10 | -------------------------------------------------------------------------------- /deeplay/components/cnn/__init__.py: -------------------------------------------------------------------------------- 1 | from .cnn import ConvolutionalNeuralNetwork 2 | from .encdec import ( 3 | ConvolutionalEncoder2d, 4 | ConvolutionalDecoder2d, 5 | UNet2d, 6 | ConvolutionalEncoderDecoder2d, 7 | ) 8 | -------------------------------------------------------------------------------- /deeplay/components/cnn/cnn.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | import torch.nn as nn 3 | from _typeshed import Incomplete 4 | from deeplay.blocks.conv.conv2d import Conv2dBlock as Conv2dBlock 5 | from deeplay.external.layer import Layer as Layer 6 | from deeplay.list import LayerList as LayerList 7 | from deeplay.module import DeeplayModule as DeeplayModule 8 | from typing import Any, List, Literal, Sequence, Type, overload 9 | 10 | class ConvolutionalNeuralNetwork(DeeplayModule): 11 | in_channels: int | None 12 | hidden_channels: Sequence[int | None] 13 | out_channels: int 14 | blocks: LayerList[Conv2dBlock] 15 | @property 16 | def input(self): ... 17 | @property 18 | def hidden(self): ... 19 | @property 20 | def output(self): ... 21 | @property 22 | def layer(self) -> LayerList[Layer]: ... 23 | @property 24 | def activation(self) -> LayerList[Layer]: ... 25 | @property 26 | def normalization(self) -> LayerList[Layer]: ... 27 | def __init__(self, in_channels: int | None, hidden_channels: Sequence[int], out_channels: int, out_activation: Type[nn.Module] | nn.Module | None = None, pool: Type[nn.Module] | nn.Module | None = None) -> None: ... 28 | def forward(self, x): ... 29 | def pooled(self, layer: Layer = ..., before_first: bool = False): ... 30 | def normalized(self, normalization: Layer = ..., after_last_layer: bool = True, mode: Literal['append', 'prepend', 'insert'] = 'append', after: Incomplete | None = None): ... 31 | def strided(self, stride: int | tuple[int, ...], apply_to_first: bool = False): ... 32 | @overload 33 | def configure(self, in_channels: int | None = None, hidden_channels: List[int] | None = None, out_channels: int | None = None, out_activation: Type[nn.Module] | nn.Module | None = None) -> None: ... 34 | @overload 35 | def configure(self, name: Literal['blocks'], order: Sequence[str] | None = None, layer: Type[nn.Module] | None = None, activation: Type[nn.Module] | None = None, normalization: Type[nn.Module] | None = None, **kwargs: Any) -> None: ... 36 | @overload 37 | def configure(self, name: Literal['blocks'], index: int | slice | List[int | slice], order: Sequence[str] | None = None, layer: Type[nn.Module] | None = None, activation: Type[nn.Module] | None = None, normalization: Type[nn.Module] | None = None, **kwargs: Any) -> None: ... 38 | @overload 39 | def style(self, style: Literal["cyclegan_resnet_bottleneck"], n_blocks=7) -> Self: ... 40 | def style(self, style: str, **kwargs) -> Self: ... 41 | configure: Incomplete 42 | -------------------------------------------------------------------------------- /deeplay/components/dict.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Union, Tuple, overload 2 | 3 | from deeplay import DeeplayModule 4 | 5 | import torch 6 | from torch_geometric.data import Data 7 | 8 | 9 | class FromDict(DeeplayModule): 10 | def __init__(self, *keys: str): 11 | super().__init__() 12 | self.keys = keys 13 | 14 | def forward(self, x: Dict[str, Any]) -> Union[Any, Tuple[Any, ...]]: 15 | return ( 16 | x[self.keys[0]] 17 | if len(self.keys) == 1 18 | else tuple(x[key] for key in self.keys) 19 | ) 20 | 21 | def extra_repr(self) -> str: 22 | return ", ".join(self.keys) 23 | 24 | 25 | class AddDict(DeeplayModule): 26 | """ 27 | Element-wise addition of two dictionaries. 28 | 29 | Parameters 30 | ---------- 31 | keys : Tuple[str] 32 | Specifies the keys to be added element-wise. 33 | 34 | Constraints 35 | ----------- 36 | - Both dictionaries 'x' (base) and 'y' (addition) must contain the same keys for the addition operation. 37 | 38 | - 'x': Dict[str, Any] or torch_geometric.data.Data. 39 | - 'y': Dict[str, Any] or torch_geometric.data.Data. 40 | """ 41 | 42 | def __init__(self, *keys: str): 43 | super().__init__() 44 | self.keys = keys 45 | 46 | def forward( 47 | self, x: Union[Dict[str, Any], Data], y: Dict[str, Any] 48 | ) -> Union[Dict[str, Any], Data]: 49 | 50 | if isinstance(x, Data): 51 | x = x.clone() 52 | else: 53 | x = x.copy() 54 | 55 | x.update({key: torch.add(x[key], y[key]) for key in self.keys}) 56 | return x 57 | -------------------------------------------------------------------------------- /deeplay/components/diffusion/__init__.py: -------------------------------------------------------------------------------- 1 | from .attention_unet import AttentionUNet 2 | -------------------------------------------------------------------------------- /deeplay/components/gnn/__init__.py: -------------------------------------------------------------------------------- 1 | from .gcn import * 2 | from .mpn import * 3 | from .tpu import * 4 | -------------------------------------------------------------------------------- /deeplay/components/gnn/gcn/__init__.py: -------------------------------------------------------------------------------- 1 | from .gcn import GraphConvolutionalNeuralNetwork 2 | from .normalization import * 3 | -------------------------------------------------------------------------------- /deeplay/components/gnn/gcn/normalization.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from deeplay import DeeplayModule 5 | 6 | 7 | class sparse_laplacian_normalization(DeeplayModule): 8 | def add_self_loops(self, A, num_nodes): 9 | """ 10 | Add self-loops to the adjacency matrix of a graph. 11 | """ 12 | loop_index = torch.arange(num_nodes, device=A.device) 13 | loop_index = loop_index.unsqueeze(0).repeat(2, 1) 14 | 15 | A = torch.cat([A, loop_index], dim=1) 16 | 17 | return A 18 | 19 | def degree(self, A, num_nodes): 20 | """ 21 | Compute the degree of each node in a graph given its edge index. 22 | """ 23 | A = torch.unique(A, dim=1) 24 | row, col = A 25 | 26 | deg = torch.zeros(num_nodes, dtype=torch.long, device=A.device) 27 | 28 | # Count occurrences of each unique edge to compute degree 29 | deg.index_add_(0, row, torch.ones_like(row)) 30 | return A, deg 31 | 32 | def normalize(self, x, A): 33 | if A.size(0) != 2: 34 | raise ValueError( 35 | f"{self.__class__.__name__} expects 'A' to be an edge index matrix of size 2 x N. " 36 | "Please ensure that 'A' follows this format for proper functioning. " 37 | "For dense adjacency matrices, consider using dense_laplacian_normalization instead,", 38 | " i.e., GNN.normalize.configure(deeplay.dense_laplacian_normalization)", 39 | ) 40 | 41 | A, deg = self.degree(A, x.size(0)) 42 | row, col = A 43 | 44 | deg_inv_sqrt = deg.pow(-0.5) 45 | deg_inv_sqrt[deg_inv_sqrt == float("inf")] = 0 46 | norm = deg_inv_sqrt[row] * deg_inv_sqrt[col] 47 | 48 | return A, norm 49 | 50 | def forward(self, x, A): 51 | A = self.add_self_loops(A, x.size(0)) 52 | # computes: D^-1/2 A' D^-1/2 53 | A, norm = self.normalize(x, A) 54 | # sparse matrix multiplication 55 | laplacian = torch.sparse_coo_tensor( 56 | A, 57 | norm, 58 | (x.size(0),) * 2, 59 | device=A.device, 60 | ) 61 | return laplacian 62 | 63 | 64 | class dense_laplacian_normalization(DeeplayModule): 65 | def degree(self, A): 66 | """ 67 | Compute the degree of each node in a graph given its adjacency matrix. 68 | """ 69 | deg = torch.sum(A, dim=1) 70 | return deg 71 | 72 | def normalize(self, x, A): 73 | if A.size(0) != A.size(1): 74 | raise ValueError( 75 | f"{self.__class__.__name__} expects 'A' to be a square adjacency matrix. " 76 | "Please ensure that 'A' follows this format for proper functioning. " 77 | "For edge index matrices, consider using sparse_laplacian_normalization instead,", 78 | " i.e., GNN.normalize.configure(deeplay.sparse_laplacian_normalization)", 79 | ) 80 | 81 | deg = self.degree(A) 82 | deg_inv_sqrt = deg.pow(-0.5) 83 | deg_inv_sqrt[deg_inv_sqrt == float("inf")] = 0 84 | norm = deg_inv_sqrt[:, None] * deg_inv_sqrt[None, :] 85 | 86 | return norm 87 | 88 | def forward(self, x, A): 89 | A = A + torch.eye(x.size(0), device=A.device) 90 | # computes: I - D^-1/2 A D^-1/2 91 | laplacian = self.normalize(x, A) * A 92 | 93 | return laplacian 94 | -------------------------------------------------------------------------------- /deeplay/components/gnn/mpn/__init__.py: -------------------------------------------------------------------------------- 1 | from .mpn import MessagePassingNeuralNetwork 2 | from .rmpn import ResidualMessagePassingNeuralNetwork 3 | 4 | from .transformation import * 5 | from .propagation import Sum, WeightedSum, Mean, Max, Min, Prod 6 | from .update import * 7 | 8 | from .cla import CombineLayerActivation 9 | from .ldw import LearnableDistancewWeighting 10 | -------------------------------------------------------------------------------- /deeplay/components/gnn/mpn/cla.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Literal, Any, overload, Union, Type, List 2 | 3 | from deeplay.blocks.sequential import SequentialBlock 4 | from deeplay import DeeplayModule 5 | 6 | import torch.nn as nn 7 | 8 | 9 | class CombineLayerActivation(DeeplayModule): 10 | combine: nn.Module 11 | layer: nn.Module 12 | activation: nn.Module 13 | 14 | def __init__( 15 | self, 16 | combine: nn.Module, 17 | layer: nn.Module, 18 | activation: nn.Module, 19 | ): 20 | super().__init__() 21 | self.combine = combine 22 | self.layer = layer 23 | self.activation = activation 24 | 25 | def forward(self, *x): 26 | x = self.get_forward_args(x) 27 | x = self.combine(*x) 28 | x = self.layer(x) 29 | x = self.activation(x) 30 | return x 31 | 32 | def get_forward_args(self, x): 33 | return x 34 | 35 | @overload 36 | def configure(self, name: Literal["combine"], *args, **kwargs) -> None: 37 | ... 38 | 39 | @overload 40 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: 41 | ... 42 | 43 | @overload 44 | def configure(self, name: Literal["activation"], *args, **kwargs) -> None: 45 | ... 46 | 47 | @overload 48 | def configure(self, name: str, *args, **kwargs: Any) -> None: 49 | ... 50 | 51 | def configure(self, *args, **kwargs): # type: ignore 52 | super().configure(*args, **kwargs) 53 | -------------------------------------------------------------------------------- /deeplay/components/gnn/mpn/ldw.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence 2 | 3 | from deeplay import DeeplayModule 4 | 5 | import torch 6 | import torch.nn as nn 7 | 8 | 9 | class LearnableDistancewWeighting(DeeplayModule): 10 | def __init__( 11 | self, 12 | init_sigma: float = 0.12, 13 | init_beta: float = 4.0, 14 | sigma_limit: Sequence[float] = [0.002, 1], 15 | beta_limit: Sequence[float] = [1, 10], 16 | ): 17 | super().__init__() 18 | 19 | self.init_sigma = init_sigma 20 | self.init_beta = init_beta 21 | self.sigma_limit = sigma_limit 22 | self.beta_limit = beta_limit 23 | 24 | self.sigma = nn.Parameter(torch.tensor(init_sigma), requires_grad=True) 25 | self.beta = nn.Parameter(torch.tensor(init_beta), requires_grad=True) 26 | 27 | def forward(self, inputs): 28 | 29 | sigma = torch.clamp(self.sigma, *self.sigma_limit) 30 | beta = torch.clamp(self.beta, *self.beta_limit) 31 | 32 | return torch.exp( 33 | -1 34 | * torch.pow( 35 | torch.square(inputs) / (2 * torch.square(sigma)), 36 | beta, 37 | ) 38 | ) 39 | 40 | def extra_repr(self) -> str: 41 | return ", ".join( 42 | [ 43 | f"init_sigma={self.init_sigma}", 44 | f"init_beta={self.init_beta}", 45 | ] 46 | ) 47 | -------------------------------------------------------------------------------- /deeplay/components/gnn/mpn/propagation.py: -------------------------------------------------------------------------------- 1 | from deeplay import DeeplayModule 2 | 3 | import torch 4 | 5 | 6 | class Sum(DeeplayModule): 7 | """Sums the edge features of each receiver node.""" 8 | 9 | def forward(self, x, edge_index, edge_attr): 10 | _sum = torch.zeros( 11 | x.size(0), edge_attr.size(1), dtype=edge_attr.dtype, device=edge_attr.device 12 | ) 13 | indices = edge_index[1].unsqueeze(1).expand_as(edge_attr) 14 | return _sum.scatter_add_(0, indices, edge_attr) 15 | 16 | 17 | class WeightedSum(DeeplayModule): 18 | """Sums the edge features of each receiver node with weights.""" 19 | 20 | def forward(self, x, edge_index, edge_attr, weights): 21 | _sum = torch.zeros( 22 | x.size(0), edge_attr.size(1), dtype=edge_attr.dtype, device=edge_attr.device 23 | ) 24 | indices = edge_index[1].unsqueeze(1).expand_as(edge_attr) 25 | return _sum.scatter_add_(0, indices, edge_attr * weights) 26 | 27 | 28 | class Mean(DeeplayModule): 29 | """Averages the edge features of each receiver node.""" 30 | 31 | Sum = Sum() 32 | 33 | def forward(self, x, edge_index, edge_attr): 34 | _sum = self.Sum(x, edge_index, edge_attr) 35 | return _sum / ( 36 | torch.bincount(edge_index[1], minlength=x.size(0)).unsqueeze(1).clamp(min=1) 37 | ) 38 | 39 | 40 | class Prod(DeeplayModule): 41 | """Product of the edge features of each receiver node.""" 42 | 43 | def forward(self, x, edge_index, edge_attr): 44 | # returns prod * mask so not connected nodes have prod = 0 45 | prod = torch.ones( 46 | x.size(0), edge_attr.size(1), dtype=edge_attr.dtype, device=edge_attr.device 47 | ) 48 | indices = edge_index[1].unsqueeze(1).expand_as(edge_attr) 49 | prod = prod.scatter_reduce_(0, indices, edge_attr, "prod") 50 | 51 | mask = ( 52 | torch.bincount(edge_index[1], minlength=x.size(0)).unsqueeze(1).clamp(max=1) 53 | ) 54 | return prod * mask 55 | 56 | 57 | class Min(DeeplayModule): 58 | """Minimum of the edge features of each receiver node.""" 59 | 60 | def forward(self, x, edge_index, edge_attr): 61 | _min = torch.ones( 62 | x.size(0), edge_attr.size(1), dtype=edge_attr.dtype, device=edge_attr.device 63 | ) * float("inf") 64 | indices = edge_index[1].unsqueeze(1).expand_as(edge_attr) 65 | _min = _min.scatter_reduce_(0, indices, edge_attr, "min") 66 | 67 | # remove inf values 68 | _min[_min == float("inf")] = 0 69 | return _min 70 | 71 | 72 | class Max(DeeplayModule): 73 | """Maximum of the edge features of each receiver node.""" 74 | 75 | def forward(self, x, edge_index, edge_attr): 76 | _max = torch.ones( 77 | x.size(0), edge_attr.size(1), dtype=edge_attr.dtype, device=edge_attr.device 78 | ) * float("-inf") 79 | indices = edge_index[1].unsqueeze(1).expand_as(edge_attr) 80 | _max = _max.scatter_reduce_(0, indices, edge_attr, "max") 81 | 82 | # remove -inf values 83 | _max[_max == float("-inf")] = 0 84 | return _max 85 | -------------------------------------------------------------------------------- /deeplay/components/gnn/mpn/transformation.py: -------------------------------------------------------------------------------- 1 | from .cla import CombineLayerActivation 2 | 3 | 4 | class Transform(CombineLayerActivation): 5 | """Transform module for MPN.""" 6 | 7 | def get_forward_args(self, x): 8 | """Get the arguments for the Transform module. 9 | An MPN Transform module takes the following arguments: 10 | - node features of sender nodes (x[A[0]]) 11 | - node features of receiver nodes (x[A[1]]) 12 | - edge features (edgefeat) 13 | A is the adjacency matrix of the graph. 14 | """ 15 | x, edge_index, edge_attr = x 16 | return x[edge_index[0]], x[edge_index[1]], edge_attr 17 | -------------------------------------------------------------------------------- /deeplay/components/gnn/mpn/update.py: -------------------------------------------------------------------------------- 1 | from .cla import CombineLayerActivation 2 | 3 | 4 | class Update(CombineLayerActivation): 5 | """Update module for MPN.""" 6 | 7 | def get_forward_args(self, x): 8 | """Get the arguments for the Update module. 9 | An MPN Update module takes the following arguments: 10 | - node features (x) 11 | - aggregated edge features (aggregates) 12 | """ 13 | x, aggregates = x 14 | return x, aggregates 15 | -------------------------------------------------------------------------------- /deeplay/components/gnn/tpu.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | TypeVar, 4 | overload, 5 | Literal, 6 | Optional, 7 | Any, 8 | ) 9 | 10 | import torch.nn as nn 11 | 12 | 13 | from deeplay import DeeplayModule 14 | from deeplay.blocks.sequential import SequentialBlock 15 | 16 | 17 | class TransformPropagateUpdate(SequentialBlock): 18 | transform: DeeplayModule 19 | propagate: DeeplayModule 20 | update: DeeplayModule 21 | order: List[str] 22 | 23 | def __init__( 24 | self, 25 | transform: DeeplayModule, 26 | propagate: DeeplayModule, 27 | update: DeeplayModule, 28 | order=["transform", "propagate", "update"], 29 | **kwargs: DeeplayModule, 30 | ): 31 | super().__init__( 32 | transform=transform, 33 | propagate=propagate, 34 | update=update, 35 | order=order, 36 | **kwargs, 37 | ) 38 | 39 | @overload 40 | def configure( 41 | self, 42 | order: Optional[List[str]] = None, 43 | transform: Optional[DeeplayModule] = None, 44 | propagate: Optional[DeeplayModule] = None, 45 | update: Optional[DeeplayModule] = None, 46 | **kwargs: DeeplayModule, 47 | ) -> None: ... 48 | 49 | @overload 50 | def configure(self, name: Literal["transform"], *args, **kwargs) -> None: ... 51 | 52 | @overload 53 | def configure(self, name: Literal["propagate"], *args, **kwargs) -> None: ... 54 | 55 | @overload 56 | def configure(self, name: Literal["update"], *args, **kwargs) -> None: ... 57 | 58 | @overload 59 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 60 | 61 | def configure(self, *args, **kwargs): # type: ignore 62 | super().configure(*args, **kwargs) 63 | -------------------------------------------------------------------------------- /deeplay/components/mlp.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | import torch.nn as nn 3 | from _typeshed import Incomplete 4 | from deeplay import DeeplayModule as DeeplayModule, Layer as Layer, LayerList as LayerList 5 | from deeplay.blocks.linear.linear import LinearBlock as LinearBlock 6 | from typing import Any, List, Literal, Sequence, Type, overload 7 | 8 | class MultiLayerPerceptron(DeeplayModule): 9 | in_features: int | None 10 | hidden_features: Sequence[int | None] 11 | out_features: int 12 | blocks: LayerList[LinearBlock] 13 | @property 14 | def input(self): ... 15 | @property 16 | def hidden(self): ... 17 | @property 18 | def output(self): ... 19 | @property 20 | def layer(self) -> LayerList[Layer]: ... 21 | @property 22 | def activation(self) -> LayerList[Layer]: ... 23 | @property 24 | def normalization(self) -> LayerList[Layer]: ... 25 | @property 26 | def dropout(self) -> LayerList[Layer]: ... 27 | flatten_input: Incomplete 28 | def __init__(self, in_features: int | None, hidden_features: Sequence[int | None], out_features: int, out_activation: Type[nn.Module] | nn.Module | None = None, flatten_input: bool = True) -> None: ... 29 | def forward(self, x): ... 30 | @overload 31 | def configure(self, in_features: int | None = None, hidden_features: List[int] | None = None, out_features: int | None = None, out_activation: Type[nn.Module] | nn.Module | None = None) -> None: ... 32 | @overload 33 | def configure(self, name: Literal['blocks'], index: int | slice | List[int | slice] | None = None, order: Sequence[str] | None = None, layer: Type[nn.Module] | None = None, activation: Type[nn.Module] | None = None, normalization: Type[nn.Module] | None = None, **kwargs: Any) -> None: ... 34 | @overload 35 | def style(self, style: Literal["normed_leaky"], ) -> Self: ... 36 | def style(self, style: str, **kwargs) -> Self: ... 37 | configure: Incomplete 38 | -------------------------------------------------------------------------------- /deeplay/components/rnn.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Literal, Any, Sequence, Type, overload, Union 2 | 3 | from deeplay.blocks.sequence.sequence1d import Sequence1dBlock 4 | 5 | from deeplay import DeeplayModule, Layer, LayerList, RecurrentBlock 6 | 7 | import torch.nn as nn 8 | 9 | 10 | class RecurrentNeuralNetwork(DeeplayModule): 11 | in_features: int 12 | hidden_features: Sequence[int] 13 | out_features: int 14 | blocks: LayerList[Sequence1dBlock] 15 | 16 | @property 17 | def input(self): 18 | """Return the input layer of the network. Equivalent to `.blocks[0]`.""" 19 | return self.blocks[0] 20 | 21 | @property 22 | def hidden(self): 23 | """Return the hidden layers of the network. Equivalent to `.blocks[:-1]`""" 24 | return self.blocks[:-1] 25 | 26 | @property 27 | def output(self): 28 | """Return the last layer of the network. Equivalent to `.blocks[-1]`.""" 29 | return self.blocks[-1] 30 | 31 | @property 32 | def layer(self) -> LayerList[Layer]: 33 | """Return the layers of the network. Equivalent to `.blocks.layer`.""" 34 | return self.blocks.layer 35 | 36 | @property 37 | def activation(self) -> LayerList[Layer]: 38 | """Return the activations of the network. Equivalent to `.blocks.activation`.""" 39 | return self.blocks.activation 40 | 41 | @property 42 | def normalization(self) -> LayerList[Layer]: 43 | """Return the normalizations of the network. Equivalent to `.blocks.normalization`.""" 44 | return self.blocks.normalization 45 | 46 | @property 47 | def dropout(self) -> LayerList[Layer]: 48 | """Return the dropout of the network. Equivalent to `.blocks.dropout`.""" 49 | return self.blocks.dropout 50 | 51 | def __init__( 52 | self, 53 | in_features: int, 54 | hidden_features: List[int], 55 | out_features: int, 56 | batch_first: bool = False, 57 | return_cell_state: bool = False, 58 | ): 59 | super().__init__() 60 | 61 | self.in_features = in_features 62 | self.hidden_features = hidden_features 63 | self.out_features = out_features 64 | self.batch_first = batch_first 65 | self.return_cell_state = return_cell_state 66 | 67 | if in_features is None: 68 | raise ValueError("in_features must be specified, got None") 69 | elif in_features <= 0: 70 | raise ValueError(f"in_channels must be positive, got {in_features}") 71 | 72 | if out_features <= 0: 73 | raise ValueError( 74 | f"Number of output features must be positive, got {out_features}" 75 | ) 76 | 77 | if any(h <= 0 for h in hidden_features): 78 | raise ValueError( 79 | f"all hidden_channels must be positive, got {hidden_features}" 80 | ) 81 | 82 | self.blocks = LayerList() 83 | for c_in, c_out in zip( 84 | [in_features, *hidden_features], [*hidden_features, out_features] 85 | ): 86 | 87 | self.blocks.append(Sequence1dBlock(c_in, c_out, batch_first=batch_first)) 88 | 89 | if return_cell_state: 90 | self.blocks[-1].configure(return_cell_state=True) 91 | 92 | def bidirectional(self) -> "RecurrentNeuralNetwork": 93 | """Make the network bidirectional.""" 94 | for block in self.blocks: 95 | block.bidirectional() 96 | return self 97 | 98 | def forward(self, x): 99 | for block in self.blocks: 100 | x = block(x) 101 | return x 102 | 103 | @overload 104 | def configure( 105 | self, 106 | /, 107 | in_features: Optional[int] = None, 108 | hidden_features: Optional[List[int]] = None, 109 | out_features: Optional[int] = None, 110 | out_activation: Union[Type[nn.Module], nn.Module, None] = None, 111 | ) -> None: ... 112 | 113 | @overload 114 | def configure( 115 | self, 116 | name: Literal["blocks"], 117 | index: Union[int, slice, List[Union[int, slice]], None] = None, 118 | order: Optional[Sequence[str]] = None, 119 | layer: Optional[Type[nn.Module]] = None, 120 | activation: Optional[Type[nn.Module]] = None, 121 | normalization: Optional[Type[nn.Module]] = None, 122 | **kwargs: Any, 123 | ) -> None: ... 124 | 125 | configure = DeeplayModule.configure 126 | -------------------------------------------------------------------------------- /deeplay/components/transformer/__init__.py: -------------------------------------------------------------------------------- 1 | from .satt import MultiheadSelfAttention 2 | from .ldsn import LayerDropoutSkipNormalization 3 | from .enc import Add, TransformerEncoderLayer 4 | from .pemb import PositionalEmbedding, IndexedPositionalEmbedding 5 | -------------------------------------------------------------------------------- /deeplay/components/transformer/ldsn.py: -------------------------------------------------------------------------------- 1 | from typing import List, overload, Optional, Literal, Any, Union, Type, Sequence 2 | 3 | from torch import Tensor 4 | import torch.nn as nn 5 | 6 | from deeplay import DeeplayModule 7 | from deeplay.blocks.sequential import SequentialBlock 8 | 9 | 10 | class LayerDropoutSkipNormalization(SequentialBlock): 11 | layer: DeeplayModule 12 | dropout: DeeplayModule 13 | skip: DeeplayModule 14 | normalization: DeeplayModule 15 | 16 | def __init__( 17 | self, 18 | layer: DeeplayModule, 19 | dropout: DeeplayModule, 20 | skip: DeeplayModule, 21 | normalization: DeeplayModule, 22 | order: List[str] = ["layer", "dropout", "skip", "normalization"], 23 | **kwargs: DeeplayModule, 24 | ): 25 | super().__init__( 26 | layer=layer, 27 | dropout=dropout, 28 | skip=skip, 29 | normalization=normalization, 30 | order=order, 31 | **kwargs, 32 | ) 33 | 34 | def forward(self, x): 35 | y = x 36 | for name in self.order: 37 | if name == "skip": 38 | y = self.skip(y, x) 39 | y = getattr(self, name)(y) 40 | return y 41 | 42 | @overload 43 | def configure(self, **kwargs: nn.Module) -> None: ... 44 | 45 | @overload 46 | def configure( 47 | self, 48 | order: Optional[List[str]], 49 | layer: Optional[nn.Module], 50 | dropout: Optional[nn.Module], 51 | skip: Optional[nn.Module], 52 | normalization: Optional[nn.Module], 53 | **kwargs: nn.Module, 54 | ) -> None: ... 55 | 56 | @overload 57 | def configure(self, name: Literal["layer"], *args, **kwargs) -> None: ... 58 | 59 | @overload 60 | def configure(self, name: Literal["dropout"], *args, **kwargs) -> None: ... 61 | 62 | @overload 63 | def configure(self, name: Literal["skip"], *args, **kwargs) -> None: ... 64 | 65 | @overload 66 | def configure(self, name: Literal["normalization"], *args, **kwargs) -> None: ... 67 | 68 | @overload 69 | def configure(self, name: str, *args, **kwargs: Any) -> None: ... 70 | 71 | def configure(self, *args, **kwargs): # type: ignore 72 | super().configure(*args, **kwargs) 73 | -------------------------------------------------------------------------------- /deeplay/components/transformer/pemb.py: -------------------------------------------------------------------------------- 1 | from deeplay import DeeplayModule, Layer 2 | 3 | import math 4 | 5 | import torch 6 | import torch.nn as nn 7 | 8 | from typing import Callable 9 | 10 | 11 | def sinusoidal_init_(tensor: torch.Tensor): 12 | """ 13 | Initialize tensor with sinusoidal positional embeddings. 14 | """ 15 | lenght, features = tensor.shape 16 | 17 | inv_freq = 1 / (10000 ** (torch.arange(0, features, 2).float() / features)) 18 | positions = torch.arange(0, lenght).unsqueeze(1).float() 19 | sinusoid_inp = positions * inv_freq.unsqueeze(0) 20 | 21 | with torch.no_grad(): 22 | tensor[:, 0::2] = torch.sin(sinusoid_inp) 23 | tensor[:, 1::2] = torch.cos(sinusoid_inp) 24 | 25 | return tensor 26 | 27 | 28 | class PositionalEmbedding(DeeplayModule): 29 | def __init__( 30 | self, 31 | features: int, 32 | max_length: int = 5000, 33 | initializer: Callable = sinusoidal_init_, 34 | learnable: bool = False, 35 | batch_first: bool = False, 36 | ): 37 | super().__init__() 38 | 39 | self.features = features 40 | self.max_length = max_length 41 | self.learnable = learnable 42 | self.batch_first = batch_first 43 | 44 | self.batched_dim = 0 if batch_first else 1 45 | init_embs = initializer(torch.empty(max_length, features)).unsqueeze( 46 | self.batched_dim 47 | ) 48 | self.embs = nn.Parameter(init_embs, requires_grad=learnable) 49 | 50 | self.dropout = Layer(nn.Dropout, 0) 51 | 52 | def forward(self, x): 53 | seq_dim = 1 - self.batched_dim 54 | x = x + torch.narrow(self.embs, dim=seq_dim, start=0, length=x.size(seq_dim)) 55 | return self.dropout(x) 56 | 57 | 58 | class IndexedPositionalEmbedding(PositionalEmbedding): 59 | def __init__( 60 | self, 61 | features, 62 | max_length=5000, 63 | initializer: Callable = sinusoidal_init_, 64 | learnable: bool = False, 65 | ): 66 | super().__init__(features, max_length, initializer, learnable) 67 | 68 | def fetch_embeddings(self, batch_index): 69 | """ 70 | This method takes an array of batch indices and returns 71 | an array of the same size where each element is replaced 72 | by its relative index within its batch. 73 | 74 | Example: 75 | batch_index = [0, 0, 1, 1, 1, 2, 2] 76 | 77 | fetch_embeddings(batch_index) -> [0, 1, 0, 1, 2, 0, 1] 78 | """ 79 | diff = torch.cat( 80 | ( 81 | torch.ones(1, dtype=torch.int64, device=batch_index.device), 82 | batch_index[1:] - batch_index[:-1], 83 | ) 84 | ) 85 | change_points = diff.nonzero().squeeze().flatten() 86 | 87 | sizes = torch.diff( 88 | torch.cat( 89 | ( 90 | change_points, 91 | torch.tensor( 92 | [len(batch_index)], dtype=torch.int64, device=batch_index.device 93 | ), 94 | ) 95 | ) 96 | ) 97 | indices = torch.arange(len(batch_index), device=batch_index.device) 98 | relative_indices = indices - torch.repeat_interleave(change_points, sizes) 99 | 100 | return self.embs[relative_indices, 0] 101 | 102 | def forward(self, x, batch_index): 103 | x = x + self.fetch_embeddings(batch_index) 104 | return self.dropout(x) 105 | -------------------------------------------------------------------------------- /deeplay/components/transformer/satt.py: -------------------------------------------------------------------------------- 1 | from deeplay import DeeplayModule, Layer 2 | 3 | import torch 4 | import torch.nn as nn 5 | 6 | 7 | class MultiheadSelfAttention(DeeplayModule): 8 | features: int 9 | num_heads: int 10 | projection: nn.Module 11 | return_attn: bool 12 | 13 | def __init__( 14 | self, 15 | features: int, 16 | num_heads: int, 17 | projection: nn.Module = nn.Identity(), 18 | return_attn: bool = False, 19 | batch_first: bool = False, 20 | ): 21 | super().__init__() 22 | self.features = features 23 | self.num_heads = num_heads 24 | self.projection = projection 25 | 26 | self.return_attn = return_attn 27 | 28 | if features <= 0: 29 | raise ValueError(f"Number of features must be positive, got {features}") 30 | 31 | self.attention = Layer( 32 | nn.MultiheadAttention, features, num_heads, batch_first=batch_first 33 | ) 34 | 35 | def forward(self, x, batch_index=None): 36 | """Apply multihead self-attention to the input tensor. 37 | Returns (y, attn) if return_attn is True, otherwise returns y. 38 | y is the output of the multihead self-attention layer, attn is the 39 | attention matrix, and x is the input to the multihead self-attention. 40 | If projection is nn.Identity, then x is the same as the input to the 41 | multihead self-attention. Otherwise, x is the output of the projection 42 | layer. 43 | """ 44 | attn_mask = None 45 | if x.ndim == 2: 46 | if batch_index is None: 47 | raise ValueError("batch_index must be provided for 2D tensor. Got None") 48 | attn_mask = self._fetch_attn_mask(batch_index) 49 | 50 | x = self.projection(x) 51 | y, attn = self.attention(x, x, x, attn_mask=attn_mask) 52 | 53 | if self.return_attn: 54 | return y, attn 55 | else: 56 | return y 57 | 58 | def _fetch_attn_mask(self, batch_index): 59 | """Fetch attention mask for 2D tensor. The mask is a square matrix with 60 | True values indicating that the corresponding element is not allowed 61 | to attend. This is used to deal with unbached sequences of different 62 | lengths. 63 | """ 64 | return ~torch.eq(batch_index.unsqueeze(1), batch_index.unsqueeze(0)) 65 | -------------------------------------------------------------------------------- /deeplay/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import time 3 | import datetime 4 | 5 | from deeplay import meta 6 | 7 | 8 | class Callback: 9 | """A deepcopy safe callback.""" 10 | 11 | instance_count = 0 12 | 13 | def __init__(self, func, *args, **kwargs): 14 | self.id = Callback.instance_count 15 | Callback.instance_count += 1 16 | self.func = func 17 | self.args = args 18 | self.kwargs = kwargs 19 | 20 | def __call__(self, instance): 21 | return self.func(instance, *self.args, **self.kwargs) 22 | 23 | def __hash__(self) -> int: 24 | return id(self) 25 | 26 | 27 | def stateful(func): 28 | """Decorator for methods modify the object state.""" 29 | 30 | @wraps(func) 31 | def wrapper(self, *args, **kwargs): 32 | self.root_module._append_to_tape(self, func.__name__, args, kwargs) 33 | 34 | with self.root_module.calling_stateful(): 35 | func(self, *args, **kwargs) 36 | return self 37 | 38 | return wrapper 39 | 40 | 41 | def before_build(func): 42 | """Decorator for methods that will be run before build instead of immediately.""" 43 | 44 | @stateful 45 | @wraps(func) 46 | def wrapper(self, *args, **kwargs): 47 | self.register_before_build_hook(Callback(func, *args, **kwargs)) 48 | return self 49 | 50 | return wrapper 51 | 52 | 53 | def after_build(func): 54 | """Decorator for methods that will be run after build instead of immediately. 55 | 56 | If the build method creates a new object, the hook will run on the new object. 57 | """ 58 | 59 | @stateful 60 | @wraps(func) 61 | def wrapper(self, *args, **kwargs): 62 | self.register_after_build_hook(Callback(func, *args, **kwargs)) 63 | return self 64 | 65 | return wrapper 66 | 67 | 68 | def after_init(func): 69 | """Decorator for methods that will be run after init _and_ immediately. 70 | 71 | If called during init, the hook will not be stored and will only run immediately. 72 | """ 73 | 74 | @stateful 75 | @wraps(func) 76 | def wrapper(self, *args, **kwargs): 77 | func(self, *args, **kwargs) 78 | 79 | if not self.is_constructing: 80 | self.register_after_init_hook(Callback(func, *args, **kwargs)) 81 | 82 | return self 83 | 84 | return wrapper 85 | -------------------------------------------------------------------------------- /deeplay/external/__init__.py: -------------------------------------------------------------------------------- 1 | from .external import External 2 | from .layer import Layer 3 | from .optimizers import * 4 | -------------------------------------------------------------------------------- /deeplay/external/optimizers/__init__.py: -------------------------------------------------------------------------------- 1 | from .optimizer import Optimizer 2 | from .adam import Adam, AdamW 3 | from .sgd import SGD 4 | from .rmsprop import RMSprop 5 | -------------------------------------------------------------------------------- /deeplay/external/optimizers/adam.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Tuple, Union, Iterable 2 | 3 | from deeplay.external import External 4 | from .optimizer import Optimizer 5 | 6 | import torch 7 | 8 | 9 | class Adam(Optimizer): 10 | def __pre_init__(self, **optimzer_kwargs): 11 | optimzer_kwargs.pop("classtype", None) 12 | super().__pre_init__(torch.optim.Adam, **optimzer_kwargs) 13 | 14 | def __init__( 15 | self, 16 | lr: float = 1e-3, 17 | betas: Tuple[float, float] = (0.9, 0.999), 18 | eps: float = 1e-8, 19 | weight_decay: float = 0, 20 | amsgrad: bool = False, 21 | **kwargs 22 | ): ... 23 | 24 | 25 | class AdamW(Optimizer): 26 | def __pre_init__(self, **optimzer_kwargs): 27 | optimzer_kwargs.pop("classtype", None) 28 | super().__pre_init__(torch.optim.AdamW, **optimzer_kwargs) 29 | 30 | def __init__( 31 | self, 32 | lr: float = 1e-3, 33 | betas: Tuple[float, float] = (0.9, 0.999), 34 | eps: float = 1e-8, 35 | weight_decay: float = 1e-2, 36 | amsgrad: bool = False, 37 | **kwargs 38 | ): ... 39 | -------------------------------------------------------------------------------- /deeplay/external/optimizers/optimizer.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Tuple, Union, Iterable, Type 2 | 3 | from deeplay.external import External 4 | from deeplay.decorators import before_build 5 | 6 | import torch 7 | 8 | 9 | class Optimizer(External): 10 | @property 11 | def kwargs(self): 12 | kwargs = super().kwargs 13 | params = kwargs.get("params", None) 14 | if callable(params): 15 | kwargs["params"] = params() 16 | 17 | return kwargs 18 | 19 | def __init__(self, classtype: Type[torch.optim.Optimizer], **optimzer_kwargs): 20 | super().__init__(classtype=classtype, **optimzer_kwargs) 21 | 22 | @before_build 23 | def params( 24 | self, 25 | func: Callable[ 26 | [torch.nn.Module], 27 | Union[ 28 | Iterable[torch.nn.Parameter], 29 | Dict[str, Iterable[torch.nn.Parameter]], 30 | List[Dict[str, Iterable[torch.nn.Parameter]]], 31 | ], 32 | ], 33 | ): 34 | try: 35 | self.configure(params=func(self.root_module)) 36 | except TypeError: 37 | import warnings 38 | 39 | # deprecation warning 40 | warnings.warn( 41 | "Providing a parameter function to the optimizer with no arguments is deprecated. Please use a function with one argument (the root model).", 42 | DeprecationWarning, 43 | ) 44 | self.configure(params=func()) 45 | return self 46 | -------------------------------------------------------------------------------- /deeplay/external/optimizers/rmsprop.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Tuple, Union, Iterable 2 | 3 | from deeplay.external import External 4 | from .optimizer import Optimizer 5 | 6 | import torch 7 | 8 | torch.optim.Adam 9 | 10 | 11 | class RMSprop(Optimizer): 12 | def __pre_init__(self, classtype=None, **optimzer_kwargs): 13 | optimzer_kwargs.pop("classtype", None) 14 | super().__pre_init__(torch.optim.RMSprop, **optimzer_kwargs) 15 | 16 | def __init__( 17 | self, 18 | lr: float = 1e-2, 19 | alpha: float = 0.99, 20 | eps: float = 1e-8, 21 | weight_decay: float = 0, 22 | momentum: float = 0, 23 | centered: bool = False, 24 | **kwargs 25 | ): ... 26 | -------------------------------------------------------------------------------- /deeplay/external/optimizers/sgd.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Tuple, Union, Iterable 2 | 3 | from deeplay.external import External 4 | from .optimizer import Optimizer 5 | 6 | import torch 7 | 8 | 9 | class SGD(Optimizer): 10 | def __pre_init__(self, **optimzer_kwargs): 11 | optimzer_kwargs.pop("classtype", None) 12 | super().__pre_init__(torch.optim.SGD, **optimzer_kwargs) 13 | 14 | def __init__( 15 | self, 16 | params=None, 17 | lr=0.1, 18 | momentum=0, 19 | dampening=0, 20 | weight_decay=0, 21 | nesterov=False, 22 | *, 23 | maximize=False, 24 | foreach=None, 25 | differentiable=False 26 | ): ... 27 | -------------------------------------------------------------------------------- /deeplay/initializers/__init__.py: -------------------------------------------------------------------------------- 1 | # from .initializer import Initializer 2 | from .kaiming import Kaiming 3 | from .normal import Normal 4 | from .constant import Constant 5 | -------------------------------------------------------------------------------- /deeplay/initializers/constant.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Type 2 | 3 | from deeplay.initializers.initializer import Initializer 4 | import torch.nn as nn 5 | 6 | _constant_default_targets = ( 7 | nn.BatchNorm1d, 8 | nn.BatchNorm2d, 9 | nn.BatchNorm3d, 10 | ) 11 | 12 | 13 | class Constant(Initializer): 14 | def __init__( 15 | self, 16 | targets: Tuple[Type[nn.Module], ...] = _constant_default_targets, 17 | weight: float = 1.0, 18 | bias: float = 0.0, 19 | ): 20 | super().__init__(targets) 21 | self.weight = weight 22 | self.bias = bias 23 | 24 | def initialize_tensor(self, tensor, name): 25 | tensor.data.fill_(self.weight) 26 | -------------------------------------------------------------------------------- /deeplay/initializers/initializer.py: -------------------------------------------------------------------------------- 1 | class Initializer: 2 | 3 | def __init__(self, targets): 4 | self.targets = targets 5 | 6 | def initialize(self, module, tensors=("weight", "bias")): 7 | if isinstance(module, self.targets): 8 | for tensor in tensors: 9 | if hasattr(module, tensor) and getattr(module, tensor) is not None: 10 | self.initialize_tensor(getattr(module, tensor), name=tensor) 11 | 12 | def initialize_tensor(self, tensor, name): 13 | raise NotImplementedError 14 | -------------------------------------------------------------------------------- /deeplay/initializers/kaiming.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Type 2 | 3 | from deeplay.initializers.initializer import Initializer 4 | import torch.nn as nn 5 | 6 | 7 | _kaiming_default_targets = ( 8 | nn.Linear, 9 | nn.Conv1d, 10 | nn.Conv2d, 11 | nn.Conv3d, 12 | nn.ConvTranspose1d, 13 | nn.ConvTranspose2d, 14 | nn.ConvTranspose3d, 15 | nn.BatchNorm1d, 16 | nn.BatchNorm2d, 17 | nn.BatchNorm3d, 18 | ) 19 | 20 | 21 | class Kaiming(Initializer): 22 | def __init__( 23 | self, 24 | targets: Tuple[Type[nn.Module], ...] = _kaiming_default_targets, 25 | mode: str = "fan_out", 26 | nonlinearity: str = "relu", 27 | fill_bias: bool = True, 28 | bias: float = 0.0, 29 | ): 30 | super().__init__(targets) 31 | self.mode = mode 32 | self.nonlinearity = nonlinearity 33 | self.fill_bias = fill_bias 34 | self.bias = bias 35 | 36 | def initialize_tensor(self, tensor, name): 37 | 38 | if name == "bias" and self.fill_bias: 39 | tensor.data.fill_(self.bias) 40 | else: 41 | nn.init.kaiming_normal_( 42 | tensor, mode=self.mode, nonlinearity=self.nonlinearity 43 | ) 44 | -------------------------------------------------------------------------------- /deeplay/initializers/normal.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Type 2 | 3 | from deeplay.initializers.initializer import Initializer 4 | import torch.nn as nn 5 | 6 | _normal_default_targets = ( 7 | nn.Linear, 8 | nn.Conv1d, 9 | nn.Conv2d, 10 | nn.Conv3d, 11 | nn.ConvTranspose1d, 12 | nn.ConvTranspose2d, 13 | nn.ConvTranspose3d, 14 | nn.BatchNorm1d, 15 | nn.BatchNorm2d, 16 | nn.BatchNorm3d, 17 | ) 18 | 19 | 20 | class Normal(Initializer): 21 | def __init__( 22 | self, 23 | targets: Tuple[Type[nn.Module], ...] = _normal_default_targets, 24 | mean: float = 0.0, 25 | std: float = 1.0, 26 | ): 27 | super().__init__(targets) 28 | self.mean = mean 29 | self.std = std 30 | 31 | def initialize_tensor(self, tensor, name): 32 | tensor.data.normal_(mean=self.mean, std=self.std) 33 | -------------------------------------------------------------------------------- /deeplay/meta.py: -------------------------------------------------------------------------------- 1 | from typing import Type, TypeVar 2 | import dill 3 | 4 | T = TypeVar("T") 5 | 6 | 7 | class ExtendedConstructorMeta(type): 8 | _module_state = { 9 | "is_top_level": True, 10 | "current_root_module": None, 11 | "constructing_module": None, 12 | } 13 | 14 | def __new__(cls, name, bases, attrs): 15 | return super().__new__(cls, name, bases, attrs) 16 | 17 | def __call__(cls: Type[T], *args, **kwargs) -> T: 18 | """Construct an instance of a class whose metaclass is Meta.""" 19 | 20 | # If the object is being constructed from a checkpoint, we instead 21 | # load the class from the pickled state and build it using the 22 | if "__from_ckpt_application" in kwargs: 23 | assert "__build_args" in kwargs, "Missing __build_args in kwargs" 24 | assert "__build_kwargs" in kwargs, "Missing __build_kwargs in kwargs" 25 | 26 | _args = kwargs.pop("__build_args") 27 | _kwargs = kwargs.pop("__build_kwargs") 28 | 29 | app = dill.loads(kwargs["__from_ckpt_application"]) 30 | app.build(*_args, **_kwargs) 31 | return app 32 | 33 | # Otherwise, we construct the object as usual 34 | obj = cls.__new__(cls, *args, **kwargs) 35 | 36 | # We store the actual arguments used to construct the object 37 | object.__setattr__( 38 | obj, 39 | "_actual_init_args", 40 | { 41 | "args": args, 42 | "kwargs": kwargs, 43 | }, 44 | ) 45 | object.__setattr__(obj, "_config_tape", []) 46 | object.__setattr__(obj, "_is_calling_stateful_method", False) 47 | # object.__setattr__(obj, "is_constructing", False) 48 | 49 | # First, we call the __pre_init__ method of the class 50 | cls.__pre_init__(obj, *args, **kwargs) 51 | 52 | object.__setattr__(obj, "_default_attribute_keys", set(obj.__dict__.keys())) 53 | 54 | # Next, we construct the class. The not_top_level context manager is used to 55 | # keep track of where in the object hierarchy we currently are. 56 | with not_top_level(cls, obj): 57 | obj.__construct__() 58 | obj.__post_init__() 59 | 60 | object.__setattr__( 61 | obj, 62 | "_user_attribute_keys", 63 | set(obj.__dict__.keys()) - obj._default_attribute_keys, 64 | ) 65 | 66 | return obj 67 | 68 | 69 | def not_top_level(cls: ExtendedConstructorMeta, obj): 70 | current_value = cls._module_state["is_top_level"] 71 | 72 | class ContextManager: 73 | """Context manager that sets the value of _module_state to False for the duration of the context. 74 | 75 | NOTE: "current_root_module" is not currently used. It was intended to be used to set the root_module 76 | of the current object to the root_module of the parent object. 77 | """ 78 | 79 | def __init__(self, obj): 80 | self.original_module_state = cls._module_state["is_top_level"] 81 | self.original_current_root_module = cls._module_state["current_root_module"] 82 | self.original_constructing_module = cls._module_state["constructing_module"] 83 | self.obj = obj 84 | 85 | def __enter__(self): 86 | if self.original_module_state: 87 | cls._module_state["is_top_level"] = False 88 | cls._module_state["current_root_module"] = self.obj 89 | cls._module_state["constructing_module"] = self.obj 90 | 91 | def __exit__(self, *args): 92 | cls._module_state["is_top_level"] = self.original_module_state 93 | cls._module_state["current_root_module"] = self.original_current_root_module 94 | cls._module_state["constructing_module"] = self.original_constructing_module 95 | 96 | return ContextManager(obj) 97 | -------------------------------------------------------------------------------- /deeplay/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .recurrentmodel import RecurrentModel 2 | from .gnn import * 3 | from .visiontransformer import ViT 4 | from .backbones import * 5 | from .mlp import * 6 | from .generators import * 7 | from .discriminators import * 8 | -------------------------------------------------------------------------------- /deeplay/models/backbones/__init__.py: -------------------------------------------------------------------------------- 1 | from .resnet18 import BackboneResnet18 2 | -------------------------------------------------------------------------------- /deeplay/models/backbones/resnet18.py: -------------------------------------------------------------------------------- 1 | import deeplay as dl 2 | import torch.nn as nn 3 | 4 | from deeplay.blocks.conv.conv2d import Conv2dBlock 5 | from deeplay.components.cnn.encdec import ConvolutionalEncoder2d 6 | 7 | import torchvision.models as models 8 | 9 | from deeplay.external.layer import Layer 10 | 11 | 12 | @Conv2dBlock.register_style 13 | def resnet(block: Conv2dBlock, stride: int = 1): 14 | """ResNet style block composed of two residual blocks. 15 | 16 | Parameters 17 | ---------- 18 | stride : int 19 | Stride of the first block, by default 1 20 | """ 21 | # 1. create two blocks 22 | block.multi(2) 23 | 24 | # 2. make the two blocks 25 | block.blocks[0].style("residual", order="lnaln|a") 26 | block.blocks[1].style("residual", order="lnaln|a") 27 | 28 | # 3. if stride > 1, stride the first block and add normalization to the shortcut 29 | if stride > 1: 30 | block.blocks[0].strided(stride) 31 | block.blocks[0].shortcut_start.normalized() 32 | 33 | # 4. remove the pooling layer if it exists. 34 | block[...].isinstance(Conv2dBlock).all.remove("pool", allow_missing=True) 35 | 36 | 37 | @Conv2dBlock.register_style 38 | def resnet18_input(block: Conv2dBlock): 39 | block.layer.configure(kernel_size=7, stride=2, padding=3, bias=False) 40 | block.normalized(mode="insert", after="layer") 41 | block.activated(Layer(nn.ReLU, inplace=True), mode="insert", after="normalization") 42 | block.pooled( 43 | Layer( 44 | nn.MaxPool2d, 45 | kernel_size=3, 46 | stride=2, 47 | padding=1, 48 | ceil_mode=False, 49 | dilation=1, 50 | ), 51 | mode="append", 52 | ) 53 | 54 | 55 | @ConvolutionalEncoder2d.register_style 56 | def resnet18(encoder: ConvolutionalEncoder2d): 57 | encoder.blocks[0].style("resnet18_input") 58 | encoder.blocks[1].style("resnet", stride=1) 59 | encoder["blocks", 2:].hasattr("style").all.style("resnet", stride=2) 60 | encoder.initialize(dl.initializers.Kaiming(targets=(nn.Conv2d,))) 61 | encoder.initialize(dl.initializers.Constant(targets=(nn.BatchNorm2d,))) 62 | encoder.pool = Layer(nn.AdaptiveAvgPool2d, (1, 1)) 63 | 64 | 65 | class BackboneResnet18(ConvolutionalEncoder2d): 66 | 67 | pool: Layer 68 | 69 | def __init__(self, in_channels: int = 3, pool_output: bool = False): 70 | super().__init__( 71 | in_channels=in_channels, 72 | hidden_channels=[64, 64, 128, 256], 73 | out_channels=512, 74 | ) 75 | self.pool_output = pool_output 76 | self.style("resnet18") 77 | 78 | def forward(self, x): 79 | x = super().forward(x) 80 | if self.pool_output: 81 | x = self.pool(x).squeeze() 82 | return x 83 | -------------------------------------------------------------------------------- /deeplay/models/backbones/resnet18.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | from _typeshed import Incomplete 3 | from deeplay.blocks.conv.conv2d import Conv2dBlock as Conv2dBlock 4 | from deeplay.components.cnn.encdec import ConvolutionalEncoder2d as ConvolutionalEncoder2d 5 | from deeplay.external.layer import Layer as Layer 6 | 7 | def resnet(block: Conv2dBlock, stride: int = 1): ... 8 | def resnet18_input(block: Conv2dBlock): ... 9 | def resnet18(encoder: ConvolutionalEncoder2d): ... 10 | 11 | class BackboneResnet18(ConvolutionalEncoder2d): 12 | pool: Layer 13 | pool_output: Incomplete 14 | def __init__(self, in_channels: int = 3, pool_output: bool = False) -> None: ... 15 | @overload 16 | def style(self, style: Literal["resnet18"], ) -> Self: ... 17 | @overload 18 | def style(self, style: Literal["cyclegan_resnet_encoder"], ) -> Self: ... 19 | @overload 20 | def style(self, style: Literal["cyclegan_discriminator"], ) -> Self: ... 21 | @overload 22 | def style(self, style: Literal["dcgan_discriminator"], ) -> Self: ... 23 | def style(self, style: str, **kwargs) -> Self: ... 24 | def forward(self, x): ... 25 | -------------------------------------------------------------------------------- /deeplay/models/discriminators/__init__.py: -------------------------------------------------------------------------------- 1 | from .cyclegan import CycleGANDiscriminator 2 | from .dcgan import DCGANDiscriminator 3 | -------------------------------------------------------------------------------- /deeplay/models/discriminators/cyclegan.py: -------------------------------------------------------------------------------- 1 | from typing import List, Any 2 | 3 | from deeplay.components import ConvolutionalEncoder2d 4 | from deeplay.external import Layer 5 | 6 | import torch.nn as nn 7 | 8 | __all__ = ["CycleGANDiscriminator"] 9 | 10 | 11 | @ConvolutionalEncoder2d.register_style 12 | def cyclegan_discriminator(encoder: ConvolutionalEncoder2d): 13 | encoder[..., "layer"].configure(kernel_size=4, padding=1) 14 | encoder["blocks", 1:-1].all.normalized( 15 | nn.InstanceNorm2d, mode="insert", after="layer" 16 | ) 17 | encoder["blocks", :].all.remove("pool", allow_missing=True) 18 | encoder["blocks", :-1].configure("activation", nn.LeakyReLU, negative_slope=0.2) 19 | encoder["blocks", :-2].configure(stride=2) 20 | 21 | 22 | class CycleGANDiscriminator(ConvolutionalEncoder2d): 23 | """ 24 | CycleGAN discriminator. 25 | 26 | Parameters 27 | ---------- 28 | in_channels : int 29 | Number of channels in the input image. 30 | 31 | Examples 32 | -------- 33 | >>> discriminator = CycleGANDiscriminator(in_channels=3) 34 | >>> discriminator.build() 35 | >>> x = torch.randn(1, 3, 256, 256) 36 | >>> y = discriminator(x) 37 | >>> y.shape 38 | 39 | Return values 40 | ------------- 41 | The forward method returns the processed tensor. 42 | 43 | """ 44 | 45 | def __init__(self, in_channels: int = 1): 46 | super().__init__( 47 | in_channels=in_channels, 48 | hidden_channels=[64, 128, 256, 512], 49 | out_channels=1, 50 | out_activation=Layer(nn.Sigmoid), 51 | ) 52 | self.style("cyclegan_discriminator") 53 | -------------------------------------------------------------------------------- /deeplay/models/discriminators/cyclegan.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | from deeplay.components import ConvolutionalEncoder2d 3 | 4 | __all__ = ['CycleGANDiscriminator'] 5 | 6 | class CycleGANDiscriminator(ConvolutionalEncoder2d): 7 | @overload 8 | def style(self, style: Literal["resnet18"], ) -> Self: ... 9 | @overload 10 | def style(self, style: Literal["cyclegan_resnet_encoder"], ) -> Self: ... 11 | @overload 12 | def style(self, style: Literal["cyclegan_discriminator"], ) -> Self: ... 13 | @overload 14 | def style(self, style: Literal["dcgan_discriminator"], ) -> Self: ... 15 | def style(self, style: str, **kwargs) -> Self: ... 16 | def __init__(self, in_channels: int = 1) -> None: ... 17 | -------------------------------------------------------------------------------- /deeplay/models/discriminators/dcgan.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | from _typeshed import Incomplete 3 | from deeplay.blocks.sequential import SequentialBlock as SequentialBlock 4 | from deeplay.components import ConvolutionalEncoder2d as ConvolutionalEncoder2d 5 | from deeplay.external.layer import Layer as Layer 6 | from deeplay.initializers.normal import Normal as Normal 7 | 8 | def dcgan_discriminator(encoder: ConvolutionalEncoder2d): ... 9 | 10 | class DCGANDiscriminator(ConvolutionalEncoder2d): 11 | input_channels: int 12 | class_conditioned_model: bool 13 | embedding_dim: int 14 | num_classes: int 15 | in_channels: Incomplete 16 | features_dim: Incomplete 17 | label_embedding: Incomplete 18 | def __init__(self, in_channels: int = 1, features_dim: int = 64, class_conditioned_model: bool = False, embedding_dim: int = 100, num_classes: int = 10, input_channels: Incomplete | None = None) -> None: ... 19 | @overload 20 | def style(self, style: Literal["resnet18"], ) -> Self: ... 21 | @overload 22 | def style(self, style: Literal["cyclegan_resnet_encoder"], ) -> Self: ... 23 | @overload 24 | def style(self, style: Literal["cyclegan_discriminator"], ) -> Self: ... 25 | @overload 26 | def style(self, style: Literal["dcgan_discriminator"], ) -> Self: ... 27 | def style(self, style: str, **kwargs) -> Self: ... 28 | def forward(self, x, y: Incomplete | None = None): ... 29 | -------------------------------------------------------------------------------- /deeplay/models/generators/__init__.py: -------------------------------------------------------------------------------- 1 | from .cyclegan import CycleGANResnetGenerator 2 | from .dcgan import DCGANGenerator 3 | -------------------------------------------------------------------------------- /deeplay/models/generators/cyclegan.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Literal, Any, Sequence, Type, overload, Union 2 | 3 | from deeplay.blocks import Conv2dBlock 4 | from deeplay.components import ( 5 | ConvolutionalEncoderDecoder2d, 6 | ConvolutionalEncoder2d, 7 | ConvolutionalDecoder2d, 8 | ConvolutionalNeuralNetwork, 9 | ) 10 | from deeplay.external import Layer 11 | from deeplay.list import LayerList 12 | from deeplay.module import DeeplayModule 13 | 14 | import torch.nn as nn 15 | 16 | __all__ = ["CycleGANResnetGenerator"] 17 | 18 | 19 | @ConvolutionalEncoder2d.register_style 20 | def cyclegan_resnet_encoder(encoder: ConvolutionalEncoder2d): 21 | encoder.strided(2) 22 | encoder.normalized(Layer(nn.InstanceNorm2d)) 23 | encoder.blocks.configure(order=["layer", "normalization", "activation"]) 24 | encoder.blocks[0].configure( 25 | "layer", kernel_size=7, stride=1, padding=3, padding_mode="reflect" 26 | ) 27 | 28 | 29 | @ConvolutionalDecoder2d.register_style 30 | def cyclegan_resnet_decoder(decoder: ConvolutionalDecoder2d): 31 | decoder["blocks", :-1].all.normalized( 32 | nn.InstanceNorm2d, mode="insert", after="layer" 33 | ) 34 | decoder.blocks.configure(order=["layer", "normalization", "activation"]) 35 | decoder.blocks[:-1].configure( 36 | "layer", nn.ConvTranspose2d, stride=2, output_padding=1 37 | ) 38 | decoder.blocks[-1].configure( 39 | "layer", kernel_size=7, stride=1, padding=3, padding_mode="reflect" 40 | ) 41 | 42 | 43 | @ConvolutionalNeuralNetwork.register_style 44 | def cyclegan_resnet_bottleneck(cnn: ConvolutionalNeuralNetwork, n_blocks=7): 45 | cnn.configure(hidden_channels=[256] * (n_blocks - 1)) 46 | cnn["blocks", :].all.style( 47 | "residual", order="lnalna|", normalization=nn.InstanceNorm2d 48 | ) 49 | 50 | 51 | class CycleGANResnetGenerator(ConvolutionalEncoderDecoder2d): 52 | """ 53 | CycleGAN generator. 54 | 55 | Parameters 56 | ---------- 57 | in_channels : int 58 | Number of channels in the input image. 59 | out_channels : int 60 | Number of channels in the output image. 61 | n_residual_blocks : int 62 | Number of residual blocks in the generator. 63 | 64 | Shorthands 65 | ---------- 66 | - input: `.blocks[0]` 67 | - hidden: `.blocks[:-1]` 68 | - output: `.blocks[-1]` 69 | - layer: `.blocks.layer` 70 | - activation: `.blocks.activation` 71 | 72 | Examples 73 | -------- 74 | >>> generator = CycleGANResnetGenerator(in_channels=1, out_channels=3) 75 | >>> generator.build() 76 | >>> x = torch.randn(1, 1, 256, 256) 77 | >>> y = generator(x) 78 | >>> y.shape 79 | 80 | Return values 81 | ------------- 82 | The forward method returns the processed tensor. 83 | 84 | """ 85 | 86 | in_channels: int 87 | out_channels: int 88 | n_residual_blocks: int 89 | blocks: LayerList[Layer] 90 | 91 | def __init__( 92 | self, 93 | in_channels: int = 1, 94 | out_channels: int = 1, 95 | n_residual_blocks: int = 9, 96 | ): 97 | super().__init__( 98 | in_channels=in_channels, 99 | encoder_channels=[64, 128, 256], 100 | bottleneck_channels=[256] * n_residual_blocks, 101 | decoder_channels=[128, 64], 102 | out_channels=out_channels, 103 | out_activation=Layer(nn.Tanh), 104 | ) 105 | 106 | # Encoder style 107 | self.encoder.style("cyclegan_resnet_encoder") 108 | self.bottleneck.style("cyclegan_resnet_bottleneck", n_residual_blocks) 109 | self.decoder.style("cyclegan_resnet_decoder") 110 | 111 | def forward(self, x): 112 | for block in self.blocks: 113 | x = block(x) 114 | return x 115 | 116 | @overload 117 | def configure( 118 | self, 119 | /, 120 | in_channels: int = 1, 121 | out_channels: int = 1, 122 | n_residual_blocks: int = 9, 123 | ) -> None: ... 124 | 125 | @overload 126 | def configure( 127 | self, 128 | name: Literal["blocks"], 129 | order: Optional[Sequence[str]] = None, 130 | layer: Optional[Type[nn.Module]] = None, 131 | activation: Optional[Type[nn.Module]] = None, 132 | normalization: Optional[Type[nn.Module]] = None, 133 | **kwargs: Any, 134 | ) -> None: ... 135 | 136 | configure = DeeplayModule.configure 137 | -------------------------------------------------------------------------------- /deeplay/models/generators/dcgan.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | from deeplay.blocks.conv.conv2d import Conv2dBlock 4 | from deeplay.components import ConvolutionalDecoder2d 5 | from deeplay.external.layer import Layer 6 | from deeplay.initializers.normal import Normal 7 | 8 | 9 | @ConvolutionalDecoder2d.register_style 10 | def dcgan_generator(generator: ConvolutionalDecoder2d): 11 | generator.normalized(after_last_layer=False) 12 | generator[...].isinstance(Conv2dBlock).hasattr("layer").all.configure( 13 | "layer", nn.ConvTranspose2d, kernel_size=4, stride=2, padding=1 14 | ).remove("upsample", allow_missing=True) 15 | generator.blocks[0].layer.configure(stride=1, padding=0) 16 | 17 | initializer = Normal( 18 | targets=( 19 | nn.ConvTranspose2d, 20 | nn.BatchNorm2d, 21 | nn.Embedding, 22 | ), 23 | mean=0, 24 | std=0.02, 25 | ) 26 | generator.initialize(initializer, tensors="weight") 27 | 28 | 29 | class DCGANGenerator(ConvolutionalDecoder2d): 30 | """ 31 | Deep Convolutional Generative Adversarial Network (DCGAN) generator. 32 | 33 | Parameters 34 | ---------- 35 | latent_dim: int 36 | Dimension of the latent space 37 | feature_dims: int 38 | Dimension of the features. The number of features in the four ConvTransposeBlocks of the Generator can be controlled by this parameter. Convolutional transpose layers = [features_dim*16, features_dim*8, features_dim*4, features_dim*2]. 39 | output_channels: int 40 | Number of output channels 41 | class_conditioned_model: bool 42 | Whether the model is class-conditional 43 | embedding_dim: int 44 | Dimension of the label embedding 45 | num_classes: int 46 | Number of classes 47 | 48 | Shorthands 49 | ---------- 50 | - input: `.blocks[0]` 51 | - hidden: `.blocks[:-1]` 52 | - output: `.blocks[-1]` 53 | - layer: `.blocks.layer` 54 | - activation: `.blocks.activation` 55 | 56 | Constraints 57 | ----------- 58 | - input shape: (batch_size, latent_dim) 59 | - output shape: (batch_size, ch_out, 64, 64) 60 | 61 | Examples 62 | -------- 63 | >>> generator = DCGANGenerator(latent_dim=100, output_channels=1, class_conditioned_model=False) 64 | >>> generator.build() 65 | >>> batch_size = 16 66 | >>> input = torch.randn([batch_size, 100, 1, 1]) 67 | >>> output = generator(x=input, y=None) 68 | 69 | Return Values 70 | ------------- 71 | The forward method returns the processed tensor. 72 | 73 | """ 74 | 75 | latent_dim: int 76 | output_channels: int 77 | class_conditioned_model: bool 78 | embedding_dim: int 79 | num_classes: int 80 | 81 | def __init__( 82 | self, 83 | latent_dim: int = 100, 84 | features_dim: int = 64, 85 | out_channels: int = 1, 86 | class_conditioned_model: bool = False, 87 | embedding_dim: int = 100, 88 | num_classes: int = 10, 89 | output_channels=None, 90 | ): 91 | if output_channels is not None: 92 | out_channels = output_channels 93 | 94 | self.latent_dim = latent_dim 95 | self.output_channels = out_channels 96 | self.class_conditioned_model = class_conditioned_model 97 | self.embedding_dim = embedding_dim 98 | self.num_classes = num_classes 99 | 100 | in_channels = latent_dim 101 | if class_conditioned_model: 102 | in_channels += embedding_dim 103 | 104 | super().__init__( 105 | in_channels=in_channels, 106 | hidden_channels=[ 107 | features_dim * 16, 108 | features_dim * 8, 109 | features_dim * 4, 110 | features_dim * 2, 111 | ], 112 | out_channels=out_channels, 113 | out_activation=Layer(nn.Tanh), 114 | ) 115 | 116 | if class_conditioned_model: 117 | self.label_embedding = Layer(nn.Embedding, num_classes, embedding_dim) 118 | else: 119 | self.label_embedding = Layer(nn.Identity) 120 | 121 | self.style("dcgan_generator") 122 | 123 | def forward(self, x, y=None): 124 | if self.class_conditioned_model: 125 | if y is None: 126 | raise ValueError( 127 | "Class label y must be provided for class-conditional generator" 128 | ) 129 | 130 | y = self.label_embedding(y) 131 | y = y.view(-1, self.embedding_dim, 1, 1) 132 | x = torch.cat([x, y], dim=1) 133 | 134 | for block in self.blocks: 135 | x = block(x) 136 | 137 | return x 138 | -------------------------------------------------------------------------------- /deeplay/models/generators/dcgan.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | from _typeshed import Incomplete 3 | from deeplay.blocks.conv.conv2d import Conv2dBlock as Conv2dBlock 4 | from deeplay.components import ConvolutionalDecoder2d as ConvolutionalDecoder2d 5 | from deeplay.external.layer import Layer as Layer 6 | from deeplay.initializers.normal import Normal as Normal 7 | 8 | def dcgan_generator(generator: ConvolutionalDecoder2d): ... 9 | 10 | class DCGANGenerator(ConvolutionalDecoder2d): 11 | latent_dim: int 12 | output_channels: int 13 | class_conditioned_model: bool 14 | embedding_dim: int 15 | num_classes: int 16 | label_embedding: Incomplete 17 | def __init__(self, latent_dim: int = 100, features_dim: int = 128, out_channels: int = 1, class_conditioned_model: bool = False, embedding_dim: int = 100, num_classes: int = 10, output_channels: Incomplete | None = None) -> None: ... 18 | @overload 19 | def style(self, style: Literal["cyclegan_resnet_decoder"], ) -> Self: ... 20 | @overload 21 | def style(self, style: Literal["dcgan_generator"], ) -> Self: ... 22 | def style(self, style: str, **kwargs) -> Self: ... 23 | def forward(self, x, y: Incomplete | None = None): ... 24 | -------------------------------------------------------------------------------- /deeplay/models/gnn/__init__.py: -------------------------------------------------------------------------------- 1 | from .mpm import MPM 2 | from .gtogmpm import GraphToGlobalMPM, GlobalMeanPool 3 | from .gtonmpm import GraphToNodeMPM 4 | from .gtoempm import GraphToEdgeMPM 5 | from .gtoeMAGIK import GraphToEdgeMAGIK 6 | -------------------------------------------------------------------------------- /deeplay/models/gnn/gtoeMAGIK.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Type, Union 2 | 3 | from . import MPM 4 | from deeplay import FromDict, LearnableDistancewWeighting, WeightedSum 5 | 6 | import torch.nn as nn 7 | 8 | 9 | class GraphToEdgeMAGIK(MPM): 10 | """Graph-to-Edge MAGIK model. 11 | 12 | Parameters 13 | ---------- 14 | hidden_features: list[int] 15 | Number of hidden units in each Message Passing Layer. 16 | out_features: int 17 | Number of output features. 18 | out_activation: template-like 19 | Specification for the output activation of the model. Default: nn.Identity. 20 | 21 | 22 | Configurables 23 | ------------- 24 | - hidden_features (list[int]): Number of hidden units in each Message Passing Layer. 25 | - out_features (int): Number of output features. 26 | - out_activation (template-like): Specification for the output activation of the model. Default: nn.Identity. 27 | - encoder (template-like): Specification for the encoder of the model. Default: dl.Parallel consisting of two MLPs to process node and edge features. 28 | - backbone (template-like): Specification for the backbone of the model. Default: dl.MessagePassingNeuralNetwork. 29 | - selector (template-like): Specification for the selector of the model. Default: dl.FromDict("x") selecting the node features. 30 | - head (template-like): Specification for the head of the model. Default: dl.MultiLayerPerceptron. 31 | 32 | Constraints 33 | ----------- 34 | - input: Dict[str, Any] or torch-geometric Data object containing the following attributes: 35 | - x: torch.Tensor of shape (num_nodes, node_in_features). 36 | - edge_index: torch.Tensor of shape (2, num_edges). 37 | - edge_attr: torch.Tensor of shape (num_edges, edge_in_features). 38 | 39 | NOTE: node_in_features and edge_in_features are inferred from the input data. 40 | 41 | - output: torch.Tensor of shape (num_edges, out_features) 42 | 43 | Examples 44 | -------- 45 | >>> model = GraphToEdgeMAGIK([64, 64], 1).create() 46 | >>> inp = {} 47 | >>> inp["x"] = torch.randn(10, 16) 48 | >>> inp["edge_index"] = torch.randint(0, 10, (2, 20)) 49 | >>> inp["edge_attr"] = torch.randn(20, 8) 50 | >>> inp["distance"] = torch.randn(20, 1) 51 | >>> model(inp).shape 52 | torch.Size([20, 1]) 53 | """ 54 | 55 | def __init__( 56 | self, 57 | hidden_features: Sequence[int], 58 | out_features: int, 59 | out_activation: Union[Type[nn.Module], nn.Module, None] = None, 60 | ): 61 | super().__init__( 62 | hidden_features=hidden_features, 63 | out_features=out_features, 64 | out_activation=out_activation, 65 | ) 66 | 67 | # Configures selector to retrieve the edge features (edge_attr) 68 | # as input to the model head. 69 | self.replace("selector", FromDict("edge_attr")) 70 | 71 | distance_embedder = LearnableDistancewWeighting() 72 | distance_embedder.set_input_map("distance") 73 | distance_embedder.set_output_map("edge_weight") 74 | 75 | self.backbone.blocks.insert(0, distance_embedder) 76 | 77 | propagate = WeightedSum() 78 | propagate.set_input_map("x", "edge_index", "edge_attr", "edge_weight") 79 | propagate.set_output_map("aggregate") 80 | 81 | for block in self.backbone.blocks[1:]: 82 | block.replace("propagate", propagate.new()) 83 | -------------------------------------------------------------------------------- /deeplay/models/gnn/gtoempm.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Type, Union 2 | 3 | from . import MPM 4 | from deeplay import FromDict 5 | 6 | import torch.nn as nn 7 | 8 | 9 | class GraphToEdgeMPM(MPM): 10 | """Graph-to-Edge Message Passing Neural Network (MPN) model. 11 | 12 | Parameters 13 | ---------- 14 | hidden_features: list[int] 15 | Number of hidden units in each Message Passing Layer. 16 | out_features: int 17 | Number of output features. 18 | out_activation: template-like 19 | Specification for the output activation of the model. Default: nn.Identity. 20 | 21 | 22 | Configurables 23 | ------------- 24 | - hidden_features (list[int]): Number of hidden units in each Message Passing Layer. 25 | - out_features (int): Number of output features. 26 | - out_activation (template-like): Specification for the output activation of the model. Default: nn.Identity. 27 | - encoder (template-like): Specification for the encoder of the model. Default: dl.Parallel consisting of two MLPs to process node and edge features. 28 | - backbone (template-like): Specification for the backbone of the model. Default: dl.MessagePassingNeuralNetwork. 29 | - selector (template-like): Specification for the selector of the model. Default: dl.FromDict("x") selecting the node features. 30 | - head (template-like): Specification for the head of the model. Default: dl.MultiLayerPerceptron. 31 | 32 | Constraints 33 | ----------- 34 | - input: Dict[str, Any] or torch-geometric Data object containing the following attributes: 35 | - x: torch.Tensor of shape (num_nodes, node_in_features). 36 | - edge_index: torch.Tensor of shape (2, num_edges). 37 | - edge_attr: torch.Tensor of shape (num_edges, edge_in_features). 38 | 39 | NOTE: node_in_features and edge_in_features are inferred from the input data. 40 | 41 | - output: torch.Tensor of shape (num_edges, out_features) 42 | 43 | Examples 44 | -------- 45 | >>> model = GraphToEdgeMPM([64, 64], 1).create() 46 | >>> inp = {} 47 | >>> inp["x"] = torch.randn(10, 16) 48 | >>> inp["edge_index"] = torch.randint(0, 10, (2, 20)) 49 | >>> inp["edge_attr"] = torch.randn(20, 8) 50 | >>> model(inp).shape 51 | torch.Size([20, 1]) 52 | """ 53 | 54 | def __init__( 55 | self, 56 | hidden_features: Sequence[int], 57 | out_features: int, 58 | out_activation: Union[Type[nn.Module], nn.Module, None] = None, 59 | ): 60 | super().__init__( 61 | hidden_features=hidden_features, 62 | out_features=out_features, 63 | out_activation=out_activation, 64 | ) 65 | 66 | # Configures selector to retrieve the edge features (edge_attr) 67 | # as input to the model head. 68 | self.replace("selector", FromDict("edge_attr")) 69 | -------------------------------------------------------------------------------- /deeplay/models/gnn/gtogmpm.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Type, Union 2 | 3 | from . import MPM 4 | from deeplay import DeeplayModule, FromDict 5 | 6 | import torch.nn as nn 7 | from torch_geometric.nn import global_mean_pool 8 | 9 | import itertools 10 | 11 | 12 | class GlobalMeanPool(DeeplayModule): 13 | """ 14 | Global mean pooling layer for Graph Neural Networks. 15 | 16 | Constraints 17 | ----------- 18 | - Inputs: 19 | - x: torch.Tensor of shape (num_nodes, num_features) 20 | - batch: torch.Tensor of shape (num_nodes,) 21 | 22 | Inputs can be passed to the forward method as a tuple or as separate arguments. 23 | 24 | - Output: torch.Tensor of shape (batch_size, num_features) 25 | 26 | Examples 27 | -------- 28 | >>> # Global mean pooling layer 29 | >>> layer = GlobalMeanPool().create() 30 | >>> # Define input as a tuple of node features and batch 31 | >>> x = torch.randn(10, 16) 32 | >>> batch = torch.Tensor([0, 0, 0, 1, 1, 1, 2, 2, 2, 2]).long() 33 | >>> out1 = layer((x, batch)) 34 | >>> # Define input as separate arguments 35 | >>> out2 = layer(x, batch) 36 | >>> torch.allclose(out1, out2) 37 | True 38 | >>> out1.shape 39 | torch.Size([3, 16]) 40 | 41 | """ 42 | 43 | def forward(self, x): 44 | x = tuple(itertools.chain(*x)) if isinstance(x[0], tuple) else x 45 | return global_mean_pool(*x) 46 | 47 | 48 | class GraphToGlobalMPM(MPM): 49 | """Graph-to-Global Message Passing Neural Network (MPN) model. 50 | 51 | Parameters 52 | ---------- 53 | hidden_features: list[int] 54 | Number of hidden units in each Message Passing Layer. 55 | out_features: int 56 | Number of output features. 57 | pool: template-like 58 | Specification for the pooling of the model. Default: nn.Identity. 59 | out_activation: template-like 60 | Specification for the output activation of the model. Default: nn.Identity. 61 | 62 | 63 | Configurables 64 | ------------- 65 | - hidden_features (list[int]): Number of hidden units in each Message Passing Layer. 66 | - out_features (int): Number of output features. 67 | - pool (template-like): Specification for the pooling of the model. Default: nn.Identity. 68 | - out_activation (template-like): Specification for the output activation of the model. Default: nn.Identity. 69 | - encoder (template-like): Specification for the encoder of the model. Default: dl.Parallel consisting of two MLPs to process node and edge features. 70 | - backbone (template-like): Specification for the backbone of the model. Default: dl.MessagePassingNeuralNetwork. 71 | - selector (template-like): Specification for the selector of the model. Default: dl.FromDict("x") selecting the node features. 72 | - head (template-like): Specification for the head of the model. Default: dl.MultiLayerPerceptron. 73 | 74 | Constraints 75 | ----------- 76 | - input: Dict[str, Any] or torch-geometric Data object containing the following attributes: 77 | - x: torch.Tensor of shape (num_nodes, node_in_features). 78 | - edge_index: torch.Tensor of shape (2, num_edges). 79 | - edge_attr: torch.Tensor of shape (num_edges, edge_in_features). 80 | - batch: torch.Tensor of shape (num_nodes,). 81 | 82 | NOTE: node_in_features and edge_in_features are inferred from the input data. 83 | 84 | - output: torch.Tensor of shape (batch_size, out_features) 85 | 86 | Examples 87 | -------- 88 | >>> model = GraphToGlobalMPM([64, 64], 1).create() 89 | >>> inp = {} 90 | >>> inp["x"] = torch.randn(10, 16) 91 | >>> inp["edge_index"] = torch.randint(0, 10, (2, 20)) 92 | >>> inp["edge_attr"] = torch.randn(20, 8) 93 | >>> inp["batch"] = torch.Tensor([0, 0, 0, 0, 1, 1, 1, 1, 1, 1]).long() 94 | >>> model(inp).shape 95 | torch.Size([2, 1]) 96 | """ 97 | 98 | def __init__( 99 | self, 100 | hidden_features: Sequence[int], 101 | out_features: int, 102 | pool: Union[Type[nn.Module], nn.Module] = GlobalMeanPool, 103 | out_activation: Union[Type[nn.Module], nn.Module, None] = None, 104 | ): 105 | super().__init__( 106 | hidden_features, 107 | out_features, 108 | pool, 109 | out_activation, 110 | ) 111 | 112 | # Configures selector to retrieve the node features (x) 113 | # and the batch tensor as inputs to the pool layer 114 | self.replace("selector", FromDict("x", "batch")) 115 | -------------------------------------------------------------------------------- /deeplay/models/gnn/gtonmpm.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Type, Union 2 | 3 | from . import MPM 4 | 5 | import torch.nn as nn 6 | 7 | 8 | class GraphToNodeMPM(MPM): 9 | """Graph-to-Node Message Passing Neural Network (MPN) model. 10 | 11 | Parameters 12 | ---------- 13 | hidden_features: list[int] 14 | Number of hidden units in each Message Passing Layer. 15 | out_features: int 16 | Number of output features. 17 | out_activation: template-like 18 | Specification for the output activation of the model. Default: nn.Identity. 19 | 20 | 21 | Configurables 22 | ------------- 23 | - hidden_features (list[int]): Number of hidden units in each Message Passing Layer. 24 | - out_features (int): Number of output features. 25 | - out_activation (template-like): Specification for the output activation of the model. Default: nn.Identity. 26 | - encoder (template-like): Specification for the encoder of the model. Default: dl.Parallel consisting of two MLPs to process node and edge features. 27 | - backbone (template-like): Specification for the backbone of the model. Default: dl.MessagePassingNeuralNetwork. 28 | - selector (template-like): Specification for the selector of the model. Default: dl.FromDict("x") selecting the node features. 29 | - head (template-like): Specification for the head of the model. Default: dl.MultiLayerPerceptron. 30 | 31 | Constraints 32 | ----------- 33 | - input: Dict[str, Any] or torch-geometric Data object containing the following attributes: 34 | - x: torch.Tensor of shape (num_nodes, node_in_features). 35 | - edge_index: torch.Tensor of shape (2, num_edges). 36 | - edge_attr: torch.Tensor of shape (num_edges, edge_in_features). 37 | 38 | NOTE: node_in_features and edge_in_features are inferred from the input data. 39 | 40 | - output: torch.Tensor of shape (num_nodes, out_features) 41 | 42 | Examples 43 | -------- 44 | >>> model = GraphToNodesMPM([64, 64], 1).create() 45 | >>> inp = {} 46 | >>> inp["x"] = torch.randn(10, 16) 47 | >>> inp["edge_index"] = torch.randint(0, 10, (2, 20)) 48 | >>> inp["edge_attr"] = torch.randn(20, 8) 49 | >>> model(inp).shape 50 | torch.Size([10, 1]) 51 | """ 52 | 53 | def __init__( 54 | self, 55 | hidden_features: Sequence[int], 56 | out_features: int, 57 | out_activation: Union[Type[nn.Module], nn.Module, None] = None, 58 | ): 59 | super().__init__( 60 | hidden_features=hidden_features, 61 | out_features=out_features, 62 | out_activation=out_activation, 63 | ) 64 | -------------------------------------------------------------------------------- /deeplay/models/mlp/__init__.py: -------------------------------------------------------------------------------- 1 | from .sized import SmallMLP, MediumMLP, LargeMLP, XLargeMLP -------------------------------------------------------------------------------- /deeplay/models/mlp/sized.py: -------------------------------------------------------------------------------- 1 | from deeplay.components.mlp import MultiLayerPerceptron 2 | import torch.nn as nn 3 | 4 | from deeplay.external.layer import Layer 5 | 6 | 7 | @MultiLayerPerceptron.register_style 8 | def normed_leaky(mlp: MultiLayerPerceptron): 9 | 10 | mlp["blocks", :-1].all.normalized(nn.BatchNorm1d).activated( 11 | Layer(nn.LeakyReLU, negative_slope=0.05) 12 | ) 13 | 14 | 15 | class SmallMLP(MultiLayerPerceptron): 16 | def __init__(self, in_features, out_features): 17 | super().__init__(in_features, [32, 32], out_features) 18 | self.style("normed_leaky") 19 | 20 | 21 | class MediumMLP(MultiLayerPerceptron): 22 | def __init__(self, in_features, out_features): 23 | super().__init__(in_features, [64, 128], out_features) 24 | self.style("normed_leaky") 25 | 26 | 27 | class LargeMLP(MultiLayerPerceptron): 28 | def __init__(self, in_features, out_features): 29 | super().__init__(in_features, [128, 128, 128], out_features) 30 | self.style("normed_leaky") 31 | 32 | 33 | class XLargeMLP(MultiLayerPerceptron): 34 | def __init__(self, in_features, out_features): 35 | super().__init__(in_features, [128, 256, 512, 512], out_features) 36 | self.style("normed_leaky") 37 | -------------------------------------------------------------------------------- /deeplay/models/mlp/sized.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, Type, Union, Optional, overload 2 | from deeplay.components.mlp import MultiLayerPerceptron as MultiLayerPerceptron 3 | from deeplay.external.layer import Layer as Layer 4 | 5 | def normed_leaky(mlp: MultiLayerPerceptron): ... 6 | 7 | class SmallMLP(MultiLayerPerceptron): 8 | def __init__(self, in_features, out_features) -> None: ... 9 | @overload 10 | def style(self, style: Literal["normed_leaky"], ) -> Self: ... 11 | def style(self, style: str, **kwargs) -> Self: ... 12 | 13 | class MediumMLP(MultiLayerPerceptron): 14 | def __init__(self, in_features, out_features) -> None: ... 15 | @overload 16 | def style(self, style: Literal["normed_leaky"], ) -> Self: ... 17 | def style(self, style: str, **kwargs) -> Self: ... 18 | 19 | class LargeMLP(MultiLayerPerceptron): 20 | def __init__(self, in_features, out_features) -> None: ... 21 | @overload 22 | def style(self, style: Literal["normed_leaky"], ) -> Self: ... 23 | def style(self, style: str, **kwargs) -> Self: ... 24 | 25 | class XLargeMLP(MultiLayerPerceptron): 26 | @overload 27 | def style(self, style: Literal["normed_leaky"], ) -> Self: ... 28 | def style(self, style: str, **kwargs) -> Self: ... 29 | def __init__(self, in_features, out_features) -> None: ... 30 | -------------------------------------------------------------------------------- /deeplay/ops/__init__.py: -------------------------------------------------------------------------------- 1 | from .shape import Flatten, View, Reshape, Squeeze, Unsqueeze, Permute 2 | from .logs import FromLogs 3 | from .attention import * 4 | from .merge import * 5 | -------------------------------------------------------------------------------- /deeplay/ops/attention/__init__.py: -------------------------------------------------------------------------------- 1 | from .cross import MultiheadCrossAttention 2 | from .self import MultiheadSelfAttention 3 | -------------------------------------------------------------------------------- /deeplay/ops/attention/cross.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any 3 | import torch 4 | import torch.nn as nn 5 | 6 | from deeplay.external.layer import Layer 7 | from deeplay.module import DeeplayModule 8 | 9 | 10 | class MultiheadCrossAttention(DeeplayModule): 11 | 12 | features: int 13 | num_heads: int 14 | return_attn: bool 15 | 16 | def __init__( 17 | self, 18 | features: int, 19 | num_heads: int, 20 | return_attn: bool = False, 21 | queries: int | str | nn.Module = 0, 22 | keys: int | str | nn.Module = 1, 23 | values: int | str | nn.Module = 2, 24 | batch_first: bool = False, 25 | dropout=0.0, 26 | bias=True, 27 | add_bias_kv=False, 28 | add_zero_attn=False, 29 | kdim=None, 30 | vdim=None, 31 | ): 32 | super().__init__() 33 | self.features = features 34 | self.num_heads = num_heads 35 | 36 | self.queries = queries 37 | self.keys = keys 38 | self.values = values 39 | 40 | self.return_attn = return_attn 41 | self.batch_first = batch_first 42 | 43 | if features <= 0: 44 | raise ValueError(f"Number of features must be positive, got {features}") 45 | 46 | self.attention = nn.MultiheadAttention( 47 | features, 48 | num_heads, 49 | batch_first=batch_first, 50 | dropout=dropout, 51 | bias=bias, 52 | add_bias_kv=add_bias_kv, 53 | add_zero_attn=add_zero_attn, 54 | kdim=kdim, 55 | vdim=vdim, 56 | ) 57 | 58 | def _parse_query_key_value(self, x): 59 | if isinstance(x, dict): 60 | return x[self.queries], x[self.keys], x[self.values] 61 | if isinstance(x, torch.Tensor): 62 | return x, x, x 63 | if len(x) == 1 and isinstance(x[0], tuple): 64 | return self._parse_query_key_value(x[0]) 65 | if len(x) == 1 and isinstance(x[0], dict): 66 | return self._parse_query_key_value(x[0]) 67 | return x[self.queries], x[self.keys], x[self.values] 68 | 69 | def forward(self, *x, batch_index=None): 70 | """Apply multihead self-attention to the input tensor. 71 | Returns (y, attn) if return_attn is True, otherwise returns y. 72 | y is the output of the multihead self-attention layer, attn is the 73 | attention matrix, and x is the input to the multihead self-attention. 74 | If projection is nn.Identity, then x is the same as the input to the 75 | multihead self-attention. Otherwise, x is the output of the projection 76 | layer. 77 | """ 78 | 79 | q, k, v = self._parse_query_key_value(x) 80 | 81 | attn_mask = None 82 | if q.ndim == 2: 83 | if batch_index is None: 84 | raise ValueError("batch_index must be provided for 2D tensor. Got None") 85 | attn_mask = self._fetch_attn_mask(batch_index) 86 | 87 | start_shape = v.shape 88 | 89 | if v.ndim > 3: 90 | if self.batch_first: 91 | q = q.view(start_shape[0], -1, start_shape[-1]) 92 | k = k.view(start_shape[0], -1, start_shape[-1]) 93 | v = v.view(start_shape[0], -1, start_shape[-1]) 94 | else: 95 | q = q.view(-1, start_shape[-2], start_shape[-1]) 96 | k = k.view(-1, start_shape[-2], start_shape[-1]) 97 | v = v.view(-1, start_shape[-2], start_shape[-1]) 98 | 99 | y, attn = self.attention(q, k, v, attn_mask=attn_mask) 100 | 101 | if len(start_shape) > 3: 102 | y = y.view(start_shape) 103 | 104 | if self.return_attn: 105 | return y, attn 106 | else: 107 | return y 108 | 109 | def _fetch_attn_mask(self, batch_index): 110 | """Fetch attention mask for 2D tensor. The mask is a square matrix with 111 | True values indicating that the corresponding element is not allowed 112 | to attend. This is used to deal with unbached sequences of different 113 | lengths. 114 | """ 115 | return ~torch.eq(batch_index.unsqueeze(1), batch_index.unsqueeze(0)) 116 | -------------------------------------------------------------------------------- /deeplay/ops/attention/self.py: -------------------------------------------------------------------------------- 1 | from .cross import MultiheadCrossAttention 2 | 3 | 4 | class MultiheadSelfAttention(MultiheadCrossAttention): 5 | def __init__( 6 | self, 7 | features: int, 8 | num_heads: int, 9 | return_attn: bool = False, 10 | batch_first: bool = False, 11 | dropout=0.0, 12 | bias=True, 13 | add_bias_kv=False, 14 | add_zero_attn=False, 15 | kdim=None, 16 | vdim=None, 17 | ): 18 | super().__init__( 19 | features, 20 | num_heads, 21 | return_attn=return_attn, 22 | batch_first=batch_first, 23 | dropout=dropout, 24 | bias=bias, 25 | add_bias_kv=add_bias_kv, 26 | add_zero_attn=add_zero_attn, 27 | kdim=kdim, 28 | vdim=vdim, 29 | ) 30 | 31 | def forward(self, x, batch_index=None): 32 | return super().forward(x, x, x, batch_index=batch_index) 33 | -------------------------------------------------------------------------------- /deeplay/ops/logs.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import torch 3 | import torch.nn as nn 4 | 5 | from deeplay.module import DeeplayModule 6 | 7 | 8 | class FromLogs(DeeplayModule): 9 | 10 | def __init__(self, key: str): 11 | super().__init__() 12 | self.key = key 13 | 14 | def forward(self, *args: Any, **kwargs: Any) -> torch.Tensor: 15 | return self.logs[self.key] 16 | -------------------------------------------------------------------------------- /deeplay/ops/merge.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | import torch.nn as nn 3 | import torch 4 | from deeplay.external.layer import Layer 5 | from deeplay.module import DeeplayModule 6 | 7 | __all__ = ["MergeOp", "Add", "Cat", "Lambda"] 8 | 9 | 10 | class MergeOp(DeeplayModule): 11 | def __init__(self): ... 12 | 13 | def forward(self, *x): 14 | raise NotImplementedError 15 | 16 | 17 | class Add(MergeOp): 18 | def forward(self, *x): 19 | return torch.stack(x).sum(dim=0) 20 | 21 | def _compute_output_shape(self, *x): 22 | return x[0] 23 | 24 | 25 | class Cat(MergeOp): 26 | def __init__(self, dim=1): 27 | self.dim = dim 28 | 29 | def forward(self, *x): 30 | return torch.cat(x, dim=self.dim) 31 | 32 | def _compute_output_shape(self, *x): 33 | x0 = list(x[0]) 34 | x0[self.dim] = sum([v[self.dim] for v in x]) 35 | return tuple(x0) 36 | 37 | 38 | class Lambda(MergeOp): 39 | def __init__(self, fn): 40 | self.fn = fn 41 | 42 | def forward(self, *x): 43 | return self.fn(x) 44 | -------------------------------------------------------------------------------- /deeplay/ops/shape.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | 6 | from typing import List, Optional, Tuple, Union, Callable 7 | 8 | from deeplay.module import DeeplayModule 9 | 10 | 11 | class ShapeOp(DeeplayModule): ... 12 | 13 | 14 | __all__ = ["Flatten", "View", "Reshape", "Squeeze", "Unsqueeze", "Permute"] 15 | 16 | 17 | class Flatten(ShapeOp): 18 | 19 | def __init__(self, start_dim=1, end_dim=-1): 20 | self.start_dim = start_dim 21 | self.end_dim = end_dim 22 | 23 | def forward(self, x): 24 | return torch.flatten(x, self.start_dim, self.end_dim) 25 | 26 | 27 | class View(ShapeOp): 28 | def __init__( 29 | self, 30 | *shape: int | Callable[[Tuple[int, ...]], Tuple[int, ...]], 31 | ): 32 | if len(shape) == 1: 33 | self.shape = shape[0] 34 | else: 35 | self.shape = shape 36 | 37 | def forward(self, x): 38 | if callable(self.shape): 39 | shape = self.shape(x.shape) 40 | else: 41 | shape = self.shape 42 | return x.view(*shape) 43 | 44 | 45 | class Reshape(View): 46 | def __init__(self, *shape: int | Callable[[Tuple[int, ...]], Tuple[int, ...]]): 47 | super().__init__(*shape) 48 | 49 | 50 | class Squeeze(ShapeOp): 51 | def __init__(self, dim: Optional[int] = None): 52 | self.dim = dim 53 | 54 | def forward(self, x): 55 | if self.dim is None: 56 | return torch.squeeze(x) 57 | else: 58 | return torch.squeeze(x, self.dim) 59 | 60 | 61 | class Unsqueeze(ShapeOp): 62 | def __init__(self, dim: int): 63 | self.dim = dim 64 | 65 | def forward(self, x): 66 | return torch.unsqueeze(x, self.dim) 67 | 68 | 69 | class Permute(ShapeOp): 70 | def __init__(self, *dims: int): 71 | self.dims = dims 72 | 73 | def forward(self, x): 74 | return x.permute(self.dims) 75 | -------------------------------------------------------------------------------- /deeplay/shapes.py: -------------------------------------------------------------------------------- 1 | from math import e 2 | from typing import Any, Callable, Tuple, Union 3 | from operator import add, sub, mul, truediv, pow, neg 4 | 5 | 6 | def two_operation(op) -> Callable[["Variable", Union[int, "Variable"]], "Variable"]: 7 | def inner(self, y: Union[int, "Variable"]): 8 | 9 | if isinstance(y, int): 10 | return Variable(lambda z: op(self(z), y)) 11 | else: 12 | return Variable(lambda z: op(self(z), y(z))) 13 | 14 | return inner 15 | 16 | 17 | def reverse_two_operation(op) -> Callable[["Variable", int], "Variable"]: 18 | def inner(self, y: int) -> "Variable": 19 | return Variable(lambda z: op(y, self(z))) 20 | 21 | return inner 22 | 23 | 24 | def unary_operation(op) -> Callable[["Variable"], "Variable"]: 25 | def inner(self) -> "Variable": 26 | return Variable(lambda z: op(self(z))) 27 | 28 | return inner 29 | 30 | 31 | class Variable: 32 | """Represents a variable integer value that can be operated on. 33 | 34 | This class is used to represent a variable integer value that can be operated on. 35 | This is used inside shape expressions to represent the shape of the tensor that 36 | is not fully known. 37 | 38 | Parameters 39 | ---------- 40 | func : Callable[[int], int], optional 41 | The function that operates on the variable, by default None. 42 | Should usually not be set. 43 | 44 | Returns 45 | ------- 46 | int 47 | The result of the operation on the variable. 48 | 49 | Examples 50 | -------- 51 | >>> x = Variable() 52 | >>> y = x + 1 53 | >>> y(1) 54 | 2 55 | >>> y(2) 56 | 3 57 | """ 58 | 59 | def __init__(self, func=None) -> None: 60 | if func is not None: 61 | self.func = func 62 | else: 63 | self.func = lambda x: x 64 | 65 | def __call__(self, x: int) -> int: 66 | return x 67 | 68 | __add__ = two_operation(add) 69 | __radd__ = reverse_two_operation(add) 70 | __sub__ = two_operation(sub) 71 | __rsub__ = reverse_two_operation(sub) 72 | __mul__ = two_operation(mul) 73 | __rmul__ = reverse_two_operation(mul) 74 | __truediv__ = two_operation(truediv) 75 | __rtruediv__ = reverse_two_operation(truediv) 76 | __pow__ = two_operation(pow) 77 | __rpow__ = reverse_two_operation(pow) 78 | __neg__ = unary_operation(neg) 79 | 80 | 81 | class Computed: 82 | 83 | def __init__(self, func: Callable[[Tuple[int, ...]], Any]) -> None: 84 | self.func = func 85 | 86 | def __call__(self, *args: Tuple[int, ...]) -> Any: 87 | return self.func(*args) 88 | -------------------------------------------------------------------------------- /deeplay/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/applications/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import BaseApplicationTest 2 | -------------------------------------------------------------------------------- /deeplay/tests/applications/classification/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/applications/classification/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/applications/classification/test_binary.py: -------------------------------------------------------------------------------- 1 | from deeplay.components.mlp import MultiLayerPerceptron 2 | from ..base import BaseApplicationTest 3 | from deeplay.applications.classification.binary import BinaryClassifier 4 | import torch 5 | import torch.nn 6 | 7 | 8 | class TestBinaryClassifier(BaseApplicationTest.BaseTest): 9 | 10 | def get_class(self): 11 | return BinaryClassifier 12 | 13 | def get_networks(self): 14 | return [ 15 | BinaryClassifier( 16 | MultiLayerPerceptron(1, [1], 1, out_activation=torch.nn.Sigmoid) 17 | ), 18 | BinaryClassifier( 19 | MultiLayerPerceptron(2, [1], 1), loss=torch.nn.BCEWithLogitsLoss() 20 | ), 21 | ] 22 | 23 | def get_training_data(self): 24 | return [ 25 | (torch.randn(10, 1), torch.randint(0, 2, (10, 1))), 26 | (torch.randn(10, 2), torch.rand(10, 1)), 27 | ] 28 | 29 | def test_forward(self): 30 | for network, (x, y) in zip(self.get_networks(), self.get_training_data()): 31 | y_pred = network.create()(x) 32 | self.assertEqual(y_pred.shape, y.shape) 33 | self.assertIsInstance(y_pred, torch.Tensor) 34 | self.assertIsInstance(y, torch.Tensor) 35 | -------------------------------------------------------------------------------- /deeplay/tests/applications/classification/test_categorical.py: -------------------------------------------------------------------------------- 1 | from deeplay.components.mlp import MultiLayerPerceptron 2 | from ..base import BaseApplicationTest 3 | from deeplay.applications.classification.categorical import CategoricalClassifier 4 | import torch 5 | import torch.nn 6 | 7 | 8 | class TestBinaryClassifier(BaseApplicationTest.BaseTest): 9 | 10 | def get_class(self): 11 | return CategoricalClassifier 12 | 13 | def get_networks(self): 14 | return [ 15 | CategoricalClassifier(MultiLayerPerceptron(1, [1], 2)), 16 | CategoricalClassifier(MultiLayerPerceptron(2, [1], 3)), 17 | ] 18 | 19 | def get_training_data(self): 20 | return [ 21 | (torch.randn(10, 1), torch.randint(0, 2, (10,)).long()), 22 | (torch.randn(10, 2), torch.randint(0, 3, (10,)).long()), 23 | ] 24 | -------------------------------------------------------------------------------- /deeplay/tests/applications/regression/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/applications/regression/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/applications/regression/test_regressor.py: -------------------------------------------------------------------------------- 1 | from deeplay.components.mlp import MultiLayerPerceptron 2 | from ..base import BaseApplicationTest 3 | from deeplay.applications.regression.regressor import Regressor 4 | import torch 5 | import torch.nn 6 | 7 | 8 | class TestRegressor(BaseApplicationTest.BaseTest): 9 | 10 | def get_class(self): 11 | return Regressor 12 | 13 | def get_networks(self): 14 | return [ 15 | Regressor(MultiLayerPerceptron(1, [1], 1)), 16 | Regressor(MultiLayerPerceptron(2, [1], 1)), 17 | Regressor(MultiLayerPerceptron(1, [1], 2)), 18 | ] 19 | 20 | def get_training_data(self): 21 | return [ 22 | (torch.randn(10, 1), torch.randn(10, 1)), 23 | (torch.randn(10, 2), torch.randn(10, 1)), 24 | (torch.randn(10, 1), torch.randn(10, 2)), 25 | ] 26 | 27 | def test_forward(self): 28 | for network, (x, y) in zip(self.get_networks(), self.get_training_data()): 29 | y_pred = network.create()(x) 30 | self.assertEqual(y_pred.shape, y.shape) 31 | self.assertIsInstance(y_pred, torch.Tensor) 32 | self.assertIsInstance(y, torch.Tensor) 33 | -------------------------------------------------------------------------------- /deeplay/tests/blocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/blocks/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/blocks/conv2d/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/blocks/conv2d/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/blocks/sequence/test_sequence1d.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from itertools import product 3 | 4 | import torch 5 | import torch.nn as nn 6 | 7 | from deeplay.blocks.sequence import Sequence1dBlock 8 | from deeplay.external.layer import Layer 9 | 10 | from deeplay.ops.attention.self import MultiheadSelfAttention 11 | from deeplay.ops.merge import Add 12 | 13 | 14 | class TestConv2dBlock(unittest.TestCase): 15 | 16 | def test_init(self): 17 | block = Sequence1dBlock(1, 1) 18 | self.assertListEqual(block.order, ["layer"]) 19 | 20 | def test_lstm_bidirectional(self): 21 | block = Sequence1dBlock(1, 2).LSTM().bidirectional().build() 22 | x = torch.randn(10, 1, 1) 23 | y = block(x) 24 | self.assertEqual(y.shape, (10, 1, 2)) 25 | 26 | def test_gru_bidirectional(self): 27 | block = Sequence1dBlock(1, 2).GRU().bidirectional().build() 28 | x = torch.randn(10, 1, 1) 29 | y = block(x) 30 | self.assertEqual(y.shape, (10, 1, 2)) 31 | 32 | def test_rnn_bidirectional(self): 33 | block = Sequence1dBlock(1, 2).RNN().bidirectional().build() 34 | x = torch.randn(10, 1, 1) 35 | y = block(x) 36 | self.assertEqual(y.shape, (10, 1, 2)) 37 | -------------------------------------------------------------------------------- /deeplay/tests/checkpointing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/checkpointing/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/models/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/models/backbones/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/models/backbones/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/models/discriminators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/models/discriminators/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/models/discriminators/test_cyclegan.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | import torch.nn as nn 5 | 6 | from deeplay.models.discriminators.cyclegan import CycleGANDiscriminator 7 | 8 | 9 | class TestCycleGANDiscriminator(unittest.TestCase): 10 | 11 | def test_discriminator_defaults(self): 12 | 13 | discriminator = CycleGANDiscriminator().build() 14 | 15 | self.assertEqual(len(discriminator.blocks), 5) 16 | self.assertTrue( 17 | all( 18 | isinstance(discriminator.blocks.normalization[i], nn.InstanceNorm2d) 19 | for i in range(1, 4) 20 | ) 21 | ) 22 | self.assertTrue( 23 | all( 24 | isinstance(discriminator.blocks.activation[i], nn.LeakyReLU) 25 | for i in range(4) 26 | ) 27 | ) 28 | self.assertTrue(isinstance(discriminator.blocks[-1].activation, nn.Sigmoid)) 29 | 30 | # Test on a batch of 2 31 | x = torch.rand(2, 1, 256, 256) 32 | output = discriminator(x) 33 | self.assertEqual(output.shape, (2, 1, 30, 30)) 34 | -------------------------------------------------------------------------------- /deeplay/tests/models/discriminators/test_dcgan.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | import torch.nn as nn 5 | 6 | from deeplay.models.discriminators.dcgan import DCGANDiscriminator 7 | 8 | 9 | class TestDCGANDiscriminator(unittest.TestCase): 10 | ... 11 | 12 | def test_discriminator_defaults(self): 13 | 14 | discriminator = DCGANDiscriminator() 15 | discriminator.build() 16 | 17 | self.assertEqual(len(discriminator.blocks), 5) 18 | self.assertEqual( 19 | [discriminator.blocks[i].layer.kernel_size for i in range(5)], [(4, 4)] * 5 20 | ) 21 | 22 | self.assertEqual( 23 | [discriminator.blocks[i].layer.stride for i in range(5)], [(2, 2)] * 5 24 | ) 25 | 26 | self.assertEqual( 27 | [discriminator.blocks[i].layer.padding for i in range(4)], [(1, 1)] * 4 28 | ) 29 | self.assertEqual(discriminator.blocks[-1].layer.padding, (0, 0)) 30 | 31 | self.assertTrue( 32 | all( 33 | isinstance(discriminator.blocks[i].activation, nn.LeakyReLU) 34 | for i in range(4) 35 | ) 36 | ) 37 | self.assertTrue(isinstance(discriminator.blocks[-1].activation, nn.Sigmoid)) 38 | 39 | self.assertTrue( 40 | all( 41 | isinstance(discriminator.blocks[1:-1].normalization[i], nn.BatchNorm2d) 42 | for i in range(3) 43 | ) 44 | ) 45 | 46 | self.assertTrue(isinstance(discriminator.label_embedding, nn.Identity)) 47 | 48 | # Test on a batch of 2 49 | x = torch.rand(2, 1, 64, 64) 50 | output = discriminator(x, y=None) 51 | self.assertEqual(output.shape, (2, 1, 1, 1)) 52 | 53 | def test_conditional_discriminator_defaults(self): 54 | 55 | discriminator = DCGANDiscriminator(class_conditioned_model=True) 56 | discriminator.build() 57 | 58 | self.assertTrue( 59 | isinstance(discriminator.label_embedding.embedding, nn.Embedding) 60 | ) 61 | self.assertTrue(isinstance(discriminator.label_embedding.layer, nn.Linear)) 62 | self.assertTrue( 63 | isinstance(discriminator.label_embedding.activation, nn.LeakyReLU) 64 | ) 65 | 66 | self.assertTrue(discriminator.label_embedding.embedding.num_embeddings, 10) 67 | self.assertTrue(discriminator.label_embedding.layer.in_features, 100) 68 | self.assertTrue(discriminator.label_embedding.layer.out_features, 64 * 64) 69 | 70 | # Test on a batch of 2 71 | x = torch.rand(2, 1, 64, 64) 72 | y = torch.randint(0, 10, (2,)) 73 | output = discriminator(x, y) 74 | self.assertEqual(output.shape, (2, 1, 1, 1)) 75 | 76 | def test_weight_initialization(self): 77 | 78 | generator = DCGANDiscriminator() 79 | generator.build() 80 | 81 | for m in generator.modules(): 82 | if isinstance(m, (nn.Conv2d, nn.BatchNorm2d)): 83 | self.assertAlmostEqual(m.weight.data.mean().item(), 0.0, places=2) 84 | self.assertAlmostEqual(m.weight.data.std().item(), 0.02, places=2) 85 | 86 | def test_weight_initialization_conditional(self): 87 | 88 | generator = DCGANDiscriminator(class_conditioned_model=True) 89 | generator.build() 90 | 91 | for m in generator.modules(): 92 | if isinstance(m, (nn.Conv2d, nn.BatchNorm2d, nn.Embedding, nn.Linear)): 93 | self.assertAlmostEqual(m.weight.data.mean().item(), 0.0, places=2) 94 | self.assertAlmostEqual(m.weight.data.std().item(), 0.02, places=2) 95 | -------------------------------------------------------------------------------- /deeplay/tests/models/generators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/models/generators/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/models/generators/test_cyclegan.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | import torch.nn as nn 5 | 6 | from deeplay.models.generators.cyclegan import CycleGANResnetGenerator 7 | 8 | 9 | class TestCycleGANResnetGenerator(unittest.TestCase): 10 | 11 | def test_init(self): 12 | generator = CycleGANResnetGenerator().build() 13 | 14 | # Encoder 15 | self.assertEqual(len(generator.encoder.blocks), 3) 16 | self.assertTrue( 17 | all( 18 | isinstance(generator.encoder.blocks.normalization[i], nn.InstanceNorm2d) 19 | for i in range(3) 20 | ) 21 | ) 22 | self.assertTrue( 23 | all( 24 | isinstance(generator.encoder.blocks.activation[i], nn.ReLU) 25 | for i in range(3) 26 | ) 27 | ) 28 | 29 | # Decoder 30 | self.assertEqual(len(generator.decoder.blocks), 3) 31 | self.assertTrue( 32 | all( 33 | isinstance( 34 | generator.decoder.blocks[:-1].normalization[i], nn.InstanceNorm2d 35 | ) 36 | for i in range(2) 37 | ) 38 | ) 39 | self.assertTrue( 40 | all( 41 | isinstance(generator.decoder.blocks.activation[i], nn.ReLU) 42 | for i in range(2) 43 | ) 44 | ) 45 | self.assertTrue(isinstance(generator.decoder.blocks[-1].activation, nn.Tanh)) 46 | 47 | data = torch.randn(1, 1, 32, 32) 48 | output = generator(data) 49 | 50 | self.assertEqual(output.shape, (1, 1, 32, 32)) 51 | 52 | def test_bottleneck_n_layers(self): 53 | generator = CycleGANResnetGenerator(n_residual_blocks=5).build() 54 | self.assertEqual(len(generator.bottleneck.blocks), 5) 55 | data = torch.randn(1, 1, 32, 32) 56 | output = generator(data) 57 | 58 | self.assertEqual(output.shape, (1, 1, 32, 32)) 59 | -------------------------------------------------------------------------------- /deeplay/tests/models/generators/test_dcgan.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | import torch.nn as nn 5 | 6 | from deeplay.models.generators.dcgan import DCGANGenerator 7 | 8 | 9 | class TestDCGANGenerator(unittest.TestCase): 10 | ... 11 | 12 | def test_generator_defaults(self): 13 | 14 | generator = DCGANGenerator() 15 | generator.build() 16 | 17 | self.assertEqual(len(generator.blocks), 5) 18 | self.assertEqual( 19 | [generator.blocks[i].layer.kernel_size for i in range(5)], [(4, 4)] * 5 20 | ) 21 | 22 | self.assertEqual(generator.blocks[0].layer.stride, (1, 1)) 23 | self.assertEqual( 24 | [generator.blocks[i].layer.stride for i in range(1, 5)], [(2, 2)] * 4 25 | ) 26 | 27 | self.assertEqual(generator.blocks[0].layer.padding, (0, 0)) 28 | self.assertEqual(generator.blocks[-1].layer.padding, (1, 1)) 29 | 30 | self.assertTrue( 31 | all(isinstance(generator.blocks.activation[i], nn.ReLU) for i in range(4)) 32 | ) 33 | self.assertTrue(isinstance(generator.blocks[-1].activation, nn.Tanh)) 34 | 35 | self.assertTrue( 36 | all( 37 | isinstance(generator.blocks[:-1].normalization[i], nn.BatchNorm2d) 38 | for i in range(4) 39 | ) 40 | ) 41 | 42 | self.assertTrue(isinstance(generator.label_embedding, nn.Identity)) 43 | 44 | # Test on a batch of 2 45 | x = torch.rand(2, 100, 1, 1) 46 | output = generator(x, y=None) 47 | self.assertEqual(output.shape, (2, 1, 64, 64)) 48 | 49 | def test_conditional_generator_defaults(self): 50 | 51 | generator = DCGANGenerator(class_conditioned_model=True) 52 | generator.build() 53 | 54 | self.assertTrue(isinstance(generator.label_embedding, nn.Embedding)) 55 | self.assertEqual(generator.label_embedding.num_embeddings, 10) 56 | self.assertEqual(generator.label_embedding.embedding_dim, 100) 57 | 58 | # Test on a batch of 2 59 | x = torch.rand(2, 100, 1, 1) 60 | y = torch.randint(0, 10, (2,)) 61 | output = generator(x, y) 62 | self.assertEqual(output.shape, (2, 1, 64, 64)) 63 | 64 | def test_weight_initialization(self): 65 | 66 | generator = DCGANGenerator() 67 | generator.build() 68 | 69 | for m in generator.modules(): 70 | if isinstance(m, (nn.ConvTranspose2d, nn.BatchNorm2d)): 71 | self.assertAlmostEqual(m.weight.data.mean().item(), 0.0, places=2) 72 | self.assertAlmostEqual(m.weight.data.std().item(), 0.02, places=2) 73 | 74 | def test_weight_initialization_conditional(self): 75 | 76 | generator = DCGANGenerator(class_conditioned_model=True) 77 | generator.build() 78 | 79 | for m in generator.modules(): 80 | if isinstance(m, (nn.ConvTranspose2d, nn.BatchNorm2d, nn.Embedding)): 81 | self.assertAlmostEqual(m.weight.data.mean().item(), 0.0, places=2) 82 | self.assertAlmostEqual(m.weight.data.std().item(), 0.02, places=2) 83 | -------------------------------------------------------------------------------- /deeplay/tests/module/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/deeplay/tests/module/__init__.py -------------------------------------------------------------------------------- /deeplay/tests/module/test_module_subaccess.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from deeplay import DeeplayModule, LayerActivation, Layer 4 | 5 | import torch.nn as nn 6 | 7 | 8 | class TestClass(DeeplayModule): 9 | def __init__(self): 10 | submodule = ChildClass() 11 | 12 | submodule.block.layer.configure(out_features=2) 13 | 14 | self.submodule = submodule 15 | 16 | 17 | class ChildClass(DeeplayModule): 18 | def __init__(self): 19 | super().__init__() 20 | 21 | self.block = LayerActivation( 22 | Layer(nn.Linear, 1, 1), 23 | Layer(nn.ReLU), 24 | ) 25 | 26 | 27 | class TestModuleSubaccess(unittest.TestCase): 28 | def test_subaccess(self): 29 | test = TestClass() 30 | test.build() 31 | self.assertEqual(test.submodule.block.layer.out_features, 2) 32 | -------------------------------------------------------------------------------- /deeplay/tests/test_dict.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from deeplay import ( 3 | FromDict, 4 | Sequential, 5 | Layer, 6 | LayerSkip, 7 | AddDict, 8 | Parallel, 9 | DeeplayModule, 10 | ) 11 | 12 | import torch 13 | import torch.nn as nn 14 | from torch_geometric.data import Data 15 | 16 | 17 | class TestComponentDict(unittest.TestCase): 18 | def test_FromDict(self): 19 | module = FromDict("a", "b") 20 | 21 | inp = {"a": 1, "b": 2} 22 | out = module(inp) 23 | 24 | self.assertEqual(out, (1, 2)) 25 | 26 | module = FromDict("a") 27 | 28 | inp = {"a": 1, "b": 2} 29 | out = module(inp) 30 | 31 | self.assertEqual(out, 1) 32 | 33 | model = Sequential(FromDict("b"), nn.Linear(2, 10)) 34 | model.build() 35 | 36 | inp = {"a": 1, "b": torch.ones(1, 2)} 37 | out = model(inp) 38 | 39 | class MultiInputModule(nn.Module): 40 | def forward(self, x): 41 | a, b = x 42 | return a * b 43 | 44 | model = Sequential(FromDict("a", "b"), MultiInputModule()) 45 | model.build() 46 | 47 | inp = {"a": 1, "b": 2} 48 | out = model(inp) 49 | 50 | self.assertEqual(out, 2) 51 | 52 | def test_add_dict(self): 53 | inp = {} 54 | inp["x"] = 2 55 | inp["y"] = 3 56 | 57 | class MulBlock(DeeplayModule): 58 | def forward(self, x, y): 59 | return x * y 60 | 61 | layer = Parallel( 62 | x=MulBlock().set_input_map("x", "y"), y=MulBlock().set_input_map("y", "x") 63 | ) 64 | block = LayerSkip(layer=layer, skip=AddDict("x", "y")).create() 65 | 66 | out = block(inp) 67 | 68 | self.assertEqual(out["x"], 8) 69 | self.assertEqual(out["y"], 9) 70 | 71 | inp = Data(x=torch.Tensor([2]), y=torch.Tensor([3])) 72 | 73 | out = block(inp) 74 | 75 | self.assertEqual(out.x, 8) 76 | self.assertEqual(out.y, 9) 77 | 78 | def test_add_with_base_dict(self): 79 | inp = Data(x=torch.Tensor([2]), y=torch.Tensor([3])) 80 | 81 | layer = Parallel( 82 | x=Layer(nn.Linear, 1, 1).set_input_map("x"), 83 | y=Layer(nn.Linear, 1, 10).set_input_map("y"), 84 | ) 85 | block = LayerSkip(layer=layer, skip=AddDict("x")).create() 86 | 87 | out = block(inp) 88 | 89 | self.assertEqual(inp.x, 2) 90 | self.assertEqual(len(out.x), 1) 91 | 92 | # Checks that the base dict is correctly passed 93 | self.assertEqual(inp.y, 3) 94 | self.assertEqual(len(out.y), 10) 95 | -------------------------------------------------------------------------------- /deeplay/tests/test_external.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import deeplay as dl 4 | import torch.nn as nn 5 | 6 | 7 | class Wrapper(dl.DeeplayModule): 8 | def __init__(self, module): 9 | super().__init__() 10 | self.module = module 11 | 12 | 13 | class TestExternal(unittest.TestCase): 14 | def test_external(self): 15 | external = dl.External(nn.Identity) 16 | built = external.build() 17 | created = external.create() 18 | self.assertIsInstance(created, nn.Identity) 19 | self.assertIsInstance(built, nn.Identity) 20 | self.assertIsNot(built, created) 21 | 22 | def test_external_arg(self): 23 | external = dl.External(nn.Linear, 10, 20) 24 | built = external.build() 25 | created = external.create() 26 | self.assertIsInstance(created, nn.Linear) 27 | self.assertIsInstance(built, nn.Linear) 28 | self.assertIsNot(built, created) 29 | 30 | self.assertEqual(built.in_features, 10) 31 | self.assertEqual(built.out_features, 20) 32 | 33 | self.assertEqual(created.in_features, 10) 34 | self.assertEqual(created.out_features, 20) 35 | 36 | def test_wrapped(self): 37 | external = dl.External(nn.Sigmoid) 38 | wrapped = Wrapper(external) 39 | 40 | created = wrapped.create() 41 | built = wrapped.build() 42 | self.assertIsInstance(created, Wrapper) 43 | self.assertIsInstance(built, Wrapper) 44 | self.assertIsNot(built, created) 45 | 46 | self.assertIsInstance(created.module, nn.Sigmoid) 47 | self.assertIsInstance(built.module, nn.Sigmoid) 48 | self.assertIsNot(built.module, created.module) 49 | -------------------------------------------------------------------------------- /deeplay/tests/test_mlp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import torch 3 | import torch.nn as nn 4 | from deeplay import MultiLayerPerceptron, Layer 5 | 6 | 7 | class TestComponentMLP(unittest.TestCase): 8 | ... 9 | 10 | def test_mlp_defaults(self): 11 | mlp = MultiLayerPerceptron(2, [4], 3) 12 | mlp.build() 13 | self.assertEqual(len(mlp.blocks), 2) 14 | 15 | self.assertEqual(mlp.blocks[0].layer.in_features, 2) 16 | self.assertEqual(mlp.blocks[0].layer.out_features, 4) 17 | 18 | self.assertEqual(mlp.output.layer.in_features, 4) 19 | self.assertEqual(mlp.output.layer.out_features, 3) 20 | 21 | # test on a batch of 2 22 | x = torch.randn(2, 2) 23 | y = mlp(x) 24 | self.assertEqual(y.shape, (2, 3)) 25 | 26 | def test_mlp_lazy_input(self): 27 | mlp = MultiLayerPerceptron(None, [4], 3).build() 28 | self.assertEqual(len(mlp.blocks), 2) 29 | 30 | self.assertEqual(mlp.blocks[0].layer.in_features, 0) 31 | self.assertEqual(mlp.blocks[0].layer.out_features, 4) 32 | self.assertEqual(mlp.output.layer.in_features, 4) 33 | self.assertEqual(mlp.output.layer.out_features, 3) 34 | 35 | # test on a batch of 2 36 | x = torch.randn(2, 2) 37 | y = mlp(x) 38 | self.assertEqual(y.shape, (2, 3)) 39 | 40 | def test_mlp_change_depth(self): 41 | mlp = MultiLayerPerceptron(2, [4], 3) 42 | mlp.configure(hidden_features=[4, 4]) 43 | mlp.build() 44 | self.assertEqual(len(mlp.blocks), 3) 45 | 46 | def test_change_activation(self): 47 | mlp = MultiLayerPerceptron(2, [4], 3) 48 | mlp.configure(out_activation=nn.Sigmoid) 49 | mlp.build() 50 | self.assertEqual(len(mlp.blocks), 2) 51 | self.assertIsInstance(mlp.output.activation, nn.Sigmoid) 52 | 53 | def test_change_out_activation_Layer(self): 54 | mlp = MultiLayerPerceptron(2, [4], 3) 55 | mlp.configure(out_activation=Layer(nn.Sigmoid)) 56 | mlp.build() 57 | self.assertEqual(len(mlp.blocks), 2) 58 | self.assertIsInstance(mlp.output.activation, nn.Sigmoid) 59 | 60 | def test_change_out_activation_instance(self): 61 | mlp = MultiLayerPerceptron(2, [4], 3) 62 | mlp.configure(out_activation=nn.Sigmoid()) 63 | mlp.build() 64 | self.assertEqual(len(mlp.blocks), 2) 65 | self.assertIsInstance(mlp.output.activation, nn.Sigmoid) 66 | 67 | def test_no_hidden_layers(self): 68 | mlp = MultiLayerPerceptron(2, [], 3) 69 | mlp.build() 70 | self.assertEqual(len(mlp.blocks), 1) 71 | self.assertEqual(mlp.blocks[0].layer.in_features, 2) 72 | self.assertEqual(mlp.blocks[0].layer.out_features, 3) 73 | 74 | def test_configure_layers(self): 75 | mlp = MultiLayerPerceptron(2, [4, 3, 5], 3) 76 | mlp.layer.configure(bias=False) 77 | mlp.build() 78 | self.assertEqual(len(mlp.blocks), 4) 79 | for idx, block in enumerate(mlp.blocks): 80 | self.assertFalse(block.layer.bias) 81 | 82 | def test_configure_activation(self): 83 | mlp = MultiLayerPerceptron(2, [4, 3, 5], 3) 84 | mlp.activation.configure(nn.Sigmoid) 85 | mlp.build() 86 | self.assertEqual(len(mlp.blocks), 4) 87 | for idx, block in enumerate(mlp.blocks): 88 | self.assertIsInstance(block.activation, nn.Sigmoid) 89 | 90 | def test_configure_activation_with_argument(self): 91 | mlp = MultiLayerPerceptron(2, [4, 3, 5], 3) 92 | mlp.activation.configure(nn.Softmax, dim=1) 93 | mlp.build() 94 | self.assertEqual(len(mlp.blocks), 4) 95 | for idx, block in enumerate(mlp.blocks): 96 | self.assertIsInstance(block.activation, nn.Softmax) 97 | self.assertEqual(block.activation.dim, 1) 98 | 99 | def test_configure_normalization(self): 100 | mlp = MultiLayerPerceptron(2, [4, 3, 5], 3) 101 | mlp["blocks", :].all.normalized(nn.BatchNorm1d) 102 | mlp.build() 103 | self.assertEqual(len(mlp.blocks), 4) 104 | for idx, block in enumerate(mlp.blocks): 105 | self.assertIsInstance(block.normalization, nn.BatchNorm1d) 106 | 107 | x = torch.randn(2, 2) 108 | y = mlp(x) 109 | self.assertEqual(y.shape, (2, 3)) 110 | 111 | def test_no_input_no_hidden_layers(self): 112 | mlp = MultiLayerPerceptron(None, [], 3) 113 | mlp.build() 114 | self.assertEqual(len(mlp.blocks), 1) 115 | self.assertEqual(mlp.blocks[0].layer.in_features, 0) 116 | self.assertEqual(mlp.blocks[0].layer.out_features, 3) 117 | -------------------------------------------------------------------------------- /deeplay/tests/test_rnn.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import torch 3 | import torch.nn as nn 4 | from deeplay import RecurrentNeuralNetwork, Layer 5 | 6 | 7 | class TestComponentRNN(unittest.TestCase): 8 | ... 9 | 10 | def test_mlp_defaults(self): 11 | rnn = RecurrentNeuralNetwork(2, [4], 3) 12 | rnn.build() 13 | self.assertEqual(len(rnn.blocks), 2) 14 | 15 | self.assertEqual(rnn.blocks[0].layer.input_size, 2) 16 | self.assertEqual(rnn.blocks[0].layer.hidden_size, 4) 17 | 18 | self.assertEqual(rnn.output.layer.input_size, 4) 19 | self.assertEqual(rnn.output.layer.hidden_size, 3) 20 | 21 | # test on a batch of 2 22 | x = torch.randn(10, 1, 2) 23 | y = rnn(x) 24 | self.assertEqual(y.shape, (10, 1, 3)) 25 | 26 | def test_mlp_change_depth(self): 27 | rnn = RecurrentNeuralNetwork(2, [4], 3) 28 | rnn.configure(hidden_features=[4, 4]) 29 | rnn.build() 30 | self.assertEqual(len(rnn.blocks), 3) 31 | 32 | def test_bidirectional(self): 33 | rnn = RecurrentNeuralNetwork(2, [4], 3) 34 | rnn.blocks.layer.configure(bidirectional=True) 35 | rnn.build() 36 | self.assertEqual(len(rnn.blocks), 2) 37 | self.assertTrue(rnn.blocks[0].layer.bidirectional) 38 | 39 | def test_no_hidden_layers(self): 40 | rnn = RecurrentNeuralNetwork(2, [], 3) 41 | rnn.build() 42 | self.assertEqual(len(rnn.blocks), 1) 43 | self.assertEqual(rnn.blocks[0].layer.input_size, 2) 44 | self.assertEqual(rnn.blocks[0].layer.hidden_size, 3) 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | torch>2.3 4 | lightning 5 | torchmetrics 6 | torchvision 7 | torch-geometric 8 | kornia 9 | scipy 10 | scikit-image 11 | rich 12 | dill 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file=README.md 3 | license_files=LICENSE.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | with open("requirements.txt", "r") as file: 7 | required = file.read().splitlines() 8 | 9 | setup( 10 | name="deeplay", 11 | version="0.1.3", 12 | license="MIT", 13 | packages=find_packages(), 14 | author=( 15 | "Benjamin Midtvedt, Jesus Pineda, Henrik Klein Moberg, " 16 | "Harshith Bachimanchi, Mirja Granfors, Alex Lech, " 17 | "Carlo Manzo, Giovanni Volpe" 18 | ), 19 | description=( 20 | "An AI-powered platform for advancing deep learning research " 21 | "and applications, developed by DeepTrackAI." 22 | ), 23 | long_description=long_description, 24 | long_description_content_type="text/markdown", 25 | url="https://github.com/DeepTrackAI/deeplay", 26 | keywords=[ 27 | "AI", 28 | "Deep Learning", 29 | "Machine Learning", 30 | "Data Science", 31 | "Research Platform", 32 | "Artificial Intelligence", 33 | "Technology", 34 | ], 35 | python_requires=">=3.9", 36 | install_requires=required, 37 | ) 38 | -------------------------------------------------------------------------------- /tutorials/developers/DT121_overview.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Knowing Deeplay Base Classes\n", 8 | "\n", 9 | "\"Open" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "The Deeplay library is built around a few base classes designed to be subclassed to create new classes that can be used in the library. The base classes are:\n", 17 | "\n", 18 | "- `DeeplayModule`\n", 19 | " \n", 20 | " The base class for all modules in the Deeplay library. This class defines the configuration logic and the selection logic for the modules.\n", 21 | " \n", 22 | "- `BaseBlock`\n", 23 | "\n", 24 | " The base class for all blocks in the Deeplay library. This class defines the structure of a block and the methods that should be implemented by all blocks.\n", 25 | "\n", 26 | "- `Application`\n", 27 | " \n", 28 | " The base class for all applications in the Deeplay library. This class defines the structure of an application and the methods that should be implemented by all applications.\n", 29 | "\n", 30 | "Components and models do not have base classes, as they are derived from the `DeeplayModule` class." 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "# Knowing Which Classes to Subclass\n", 38 | "\n", 39 | "The following table provides a sequence of questions to help you decide what type of object you should implement. Each\n", 40 | "possible answer will have its own style guide.\n", 41 | "\n", 42 | "| Question | Answer | Object type |\n", 43 | "| ---------------------------------------------------------------------------------------------------------------- | ------ | ------------- |\n", 44 | "| Does the class represent a task such as classification, without depending heavily on the exact architecture? | Yes | `Application` |\n", 45 | "| Does the class require a non-standard training procedure to make sense (standard being normal supervised learning)? | Yes | `Application` |\n", 46 | "| Does the object represent a specific architecture, such as a ResNet18? | Yes | `Model` |\n", 47 | "| Does the object represent a full architecture, not expected to be used as a part of another architecture? | Yes | `Model` |\n", 48 | "| Does the object represent a generic architecture, such as a ConvolutionalNeuralNetwork, often used as a part of another architecture? | Yes | `Component` |\n", 49 | "| Is the object a small structural object with a sequential forward pass, such as a layer activation? | Yes | `Block` |\n", 50 | "| Is the object a unit of computation, such as a convolution or a pooling operation? | Yes | `Operation` |\n", 51 | "\n", 52 | "As a general rule of thumb, for objects derived from `Component`, the number of features in each layer should be definable by the input arguments. For objects derived from `Model`, only the input and output features must be definable by the input arguments. In both cases, it is recommended to subclass an existing model or component if possible. This will make it easier to implement the required methods and attributes, and will ensure that the new model or component is compatible with the rest of the library." 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [] 59 | } 60 | ], 61 | "metadata": { 62 | "kernelspec": { 63 | "display_name": "Python 3", 64 | "language": "python", 65 | "name": "python3" 66 | }, 67 | "language_info": { 68 | "codemirror_mode": { 69 | "name": "ipython", 70 | "version": 3 71 | }, 72 | "file_extension": ".py", 73 | "mimetype": "text/x-python", 74 | "name": "python", 75 | "nbconvert_exporter": "python", 76 | "pygments_lexer": "ipython3", 77 | "version": "3.10.7" 78 | } 79 | }, 80 | "nbformat": 4, 81 | "nbformat_minor": 2 82 | } 83 | -------------------------------------------------------------------------------- /tutorials/developers/guidelines/application/README.md: -------------------------------------------------------------------------------- 1 | # Deeplay Applications 2 | 3 | Applications are broadly defined as classes that represent a task such as classification, without depending heavily on the exact architecture. They are the highest level of abstraction in the Deeplay library. Applications are designed to be easy to use and require minimal configuration to get started. They are also designed to be easily extensible, so that you can add new features without having to modify the existing code. 4 | 5 | ## What's in an application? 6 | 7 | As a general rule of thumb, try to minimize the number of models in an application. Best is if there is a single model, accessed as `app.model`. Some applications require more, 8 | such as `gan.generator` and `gan.discriminator`. This is fine, but try to keep it to a minimum. A bad example would be for a classifier to include `app.conv_backbone`, `app.conv_to_fc_connector` and `app.fc_head`. This is bad because it limits the flexibility of the application to architectures that fit this exact structure. Instead, the application should have a single model that can be easily replaced with a different model. 9 | 10 | ### Training 11 | 12 | The primary function of an application is to define how it is trained. This includes the loss function, the optimizer, and the metrics that are used to evaluate the model. Applications also define how the model is trained, including the training loop, the validation loop, and the testing loop. Applications are designed to be easy to use, so that you can get started quickly without having to worry about the details of the training process. 13 | 14 | The training step is, broadly, defined as follows: 15 | 16 | ```python 17 | x, y = self.train_preprocess(batch) 18 | y_hat = self(x) 19 | loss = self.compute_loss(y_hat, y) 20 | # logging 21 | ``` 22 | 23 | If the training can be defined in this way, then you can implement the `train_preprocess`, `compute_loss`, and `forward` methods to define the training process. If the training process is more complex, then you can override the `training_step` method to define the training process. The default behavior of `train_preprocess` is the identity function, and the default behavior of `compute_loss` is to call `self.loss(y_hat, y)`. 24 | 25 | `train_preprocess` is intended to apply any operations that cannot be simply defined as a part of the dataset. For example, in some `self-supervised` models, the target is calculated from the input data. This can be done here. It can also be used to ensure the dtype of the input matches the expected dtype of the model. Most likely, you will not need to override this method. 26 | 27 | `compute_loss` is intended to calculate the loss of the model. This can be as simple as calling `self.loss(y_hat, y)`, or it can be more complex. It is more likely that you will need to override this method. 28 | 29 | If you need to define a custom training loop, you can override the `training_step` method entirely. This method is called for each batch of data during training. It should return the loss for the batch. Note that if you override the `training_step` method, you will to handle the logging of the loss yourself. This is done by calling `self.log('train_loss', loss, ...)` where `...` is any setting you want to pass to the logger (see `lightning.LightningModule.log` for more information). 30 | -------------------------------------------------------------------------------- /tutorials/developers/guidelines/application/testing.md: -------------------------------------------------------------------------------- 1 | This section is not complete yet. We are working on it. Current applications do _not_ follow these guidelines yet. 2 | 3 | Applications can be hard to test because they often require a lot of data and time to train to fully validate. This is not feasible, since we want to retain the unit test speed to within a few minutes. Here are some guidelines to follow when testing applications: 4 | 5 | - Test both using build and create methods. 6 | - Validate that the forward pass works as expected. 7 | - Validate that the loss and metrics are correctly set. 8 | - Validate that the optimizer is correctly set. 9 | - This can be done by manually calling `app.configure_optimizer()` and checking that returned optimizer is correct the expected one. 10 | - Also, verify that the parameters of the optimizer are correctly set. This can 11 | be done by doing a forward pass, backward pass, and then checking that the optimizer's parameters have been updated. 12 | - If there are multiple optimizers with different parameters, ensure that the correct parameters are updated. 13 | - Test `app.compute_loss` on a small input and verify that the loss is correct. 14 | - Test `app.training_step` on a small input and verify that the loss is correct. Note that you might need to attach a trainer for. This can simply be done by creating a mock 15 | trainer and setting `app.trainer = mock_trainer`. We will provide a more detailed example in the future. 16 | - Test training the application on a single epoch both using `app.fit` and `trainer.fit(app)`. 17 | - It's a good idea to check that the training history contains the correct keys. 18 | - Use as small a dataset and model as possible to keep the test fast. Turn off checkpointing and logging to speed up the test. 19 | - Test that the application can be saved and loaded correctly. 20 | - Currently, we only guarantee `save_state_dict` and `load_state_dict` to work correctly. A good way to test is to save the state dict, create a new application, load the state dict, and then check that the new application has the same state as the old one. 21 | - Test any application-specific methods, such as `app.detect`, `app.classify`, etc. 22 | -------------------------------------------------------------------------------- /tutorials/developers/guidelines/model/README.md: -------------------------------------------------------------------------------- 1 | # Deeplay Models 2 | 3 | Models are broadly defined as classes that represent a specific architecture, such as a ResNet18. Unlike `components`, they are 4 | generally not as flexible from input arguments, and it should be possible to pass them directly to applications. Models are designed to be 5 | easy to use and require minimal configuration to get started. They are also designed to be easily extensible, so that you can add new 6 | features without having to modify the existing code. 7 | 8 | ## What's in a model? 9 | 10 | Generally, a model should define a `__init__` method that takes all the necessary arguments to define the model and a `forward` method that 11 | defines the forward pass of the model. 12 | 13 | Optimally, a model should have an as simple forward pass as possible. A fully sequential forward pass is optimal. 14 | This is because any hard coded structure in the forward pass limits the flexibility of the model. For example, if the forward pass is defined as 15 | `self.conv1(x) + self.conv2(x)`, then it is not possible to replace `self.conv1` and `self.conv2` with a single `self.conv` without modifying the 16 | model. 17 | 18 | Moreover, the model architecture should in almost all cases be defined purely out of components and operations. Try to limit direct calls to 19 | `torch.nn` modules and `blocks`. This is because the `torch.nn` modules are not as flexible as the components and operations in Deeplay. If 20 | components do not exist for the desired architecture, then it is a good idea to create a new component and add it to the `components` folder. 21 | 22 | ### Unknown tensor sizes 23 | 24 | Tensorflow, and by extension Keras, allows for unknown tensor sizes thanks to the graph structure. This is not possible in PyTorch. 25 | If you need to support unknown tensor sizes, you can use the `lazy` module. This module allows for unknown tensor sizes by delaying the 26 | construction of the model until the first forward pass. This is not optimal, so use it sparingly. Examples are `nn.LazyConv2d` and `nn.LazyLinear`. 27 | 28 | If a model requires unknown tensor sizes, it is heavily encouraged to define the `validate_after_build` method, which should call the forward 29 | pass with a small input to validate that the model can be built. This will instantiate the lazy modules directly, allowing for a more 30 | user-friendly experience. 31 | -------------------------------------------------------------------------------- /tutorials/developers/guidelines/model/testing.md: -------------------------------------------------------------------------------- 1 | This section is not complete yet. We are working on it. Current models do _not_ follow these guidelines yet. 2 | 3 | Models are easier to test than applications because they are usually smaller and have fewer dependencies. We do not test if models can be trained. 4 | 5 | - Test that the model, created with default arguments, can be created. 6 | - Test that the model, created with default arguments, has the correct number of parameters. 7 | - Test that the model, created with default arguments, can be saved and loaded using `save_state_dict` and `load_state_dict`. 8 | - test that the model, created with default arguments, has the expected hierarchical structure. This is mostly for forward compatibility. 9 | - Test the forward pass with a small input and verify that the output is correct. 10 | - Test that the model can be created with non-default arguments and that the arguments are correctly set. 11 | -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned.gif -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_0.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_1.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_10.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_11.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_12.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_13.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_14.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_15.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_16.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_17.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_18.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_19.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_2.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_3.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_4.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_5.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_6.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_7.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_8.png -------------------------------------------------------------------------------- /tutorials/examples/vision/gans/figures/class_conditioned_epoch_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepTrackAI/deeplay/a53d6a2e0117eba151590d764ac6a5fd9ce55554/tutorials/examples/vision/gans/figures/class_conditioned_epoch_9.png -------------------------------------------------------------------------------- /tutorials/getting-started/__BAK__/Ch02-Installation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Installation Guide\n", 8 | "\n", 9 | "**Getting Started with Deeplay**\n", 10 | "\n", 11 | "Before diving into Deeplay's powerful features, you'll need to install the package. Fortunately, we've made the installation process as simple as possible" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "#### Dependencies\n", 19 | "\n", 20 | "Deeplay requires the following libraries:\n", 21 | "\n", 22 | "- [PyTorch](https://pytorch.org/)\n", 23 | "- [PyTorch Lightning](https://www.pytorchlightning.ai/)\n", 24 | "\n", 25 | "`deeplay` will try to install these automatically, but to make sure that the version is compatible with your system, we recommend installing them manually first. \n" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "**Option 1: Install via pip**\n", 33 | "\n", 34 | "The simplest way to install Deeplay is via pip. Open a terminal and run the following command:\n", 35 | "\n", 36 | "```bash \n", 37 | "pip install deeplay\n", 38 | "```\n", 39 | "\n", 40 | "This will install the latest stable version of Deeplay along with all its dependencies." 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "**Option 2: Install from Source**\n", 48 | "\n", 49 | "For those interested in the development version, you can clone the Deeplay repository and install it from source:\n", 50 | "```bash\n", 51 | "Copy code\n", 52 | "git clone https://github.com/yourusername/deeplay.git\n", 53 | "cd deeplay\n", 54 | "pip install -e .\n", 55 | "```\n" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "**Note**: Installing from source allows you to test the latest features but may be less stable than the pip version." 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "### Testing the Installation\n", 70 | "\n", 71 | "To confirm that Deeplay has been successfully installed, try importing it in Python:" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 2, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "import deeplay as dl" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "If you don't encounter any errors, congratulations! You've successfully installed Deeplay and are ready to start creating powerful neural network architectures." 88 | ] 89 | } 90 | ], 91 | "metadata": { 92 | "kernelspec": { 93 | "display_name": "Python 3", 94 | "language": "python", 95 | "name": "python3" 96 | }, 97 | "language_info": { 98 | "codemirror_mode": { 99 | "name": "ipython", 100 | "version": 3 101 | }, 102 | "file_extension": ".py", 103 | "mimetype": "text/x-python", 104 | "name": "python", 105 | "nbconvert_exporter": "python", 106 | "pygments_lexer": "ipython3", 107 | "version": "3.10.5" 108 | }, 109 | "orig_nbformat": 4 110 | }, 111 | "nbformat": 4, 112 | "nbformat_minor": 2 113 | } 114 | --------------------------------------------------------------------------------