From 3781af87c622b5f58a8eb127fa30e7beff416faa Mon Sep 17 00:00:00 2001 From: Jorgedavyd Date: Tue, 28 May 2024 19:32:50 -0400 Subject: [PATCH] setting up ci-cd --- .github/workflows/default.yml | 46 ++++++++++++++ .github/workflows/pypi.yml | 46 ++++++++++++++ docs/RELEASES.md | 2 + lightorch/_version.py | 3 +- lightorch/nn/criterions.py | 1 + lightorch/nn/fourier.py | 37 ++++++++--- lightorch/nn/transformer/transformer.py | 4 +- lightorch/training/__init__.py | 2 - requirements.sh | 4 ++ setup.py | 4 +- tests/README.md | 7 +++ tests/htuning.py | 0 tests/nn/criterions.py | 8 --- tests/nn/functional.py | 11 ---- tests/nn/modules.py | 0 tests/nn/transformer.py | 7 --- .../ci.yaml => tests/test_adversarial.py | 0 tests/{default_models.py => test_models.py} | 0 tests/test_nn.py | 63 +++++++++++++++++++ tests/test_supervised.py | 40 ++++++++++++ tests/training.py | 0 tests/utils.py | 47 +++++++++++++- 22 files changed, 288 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/default.yml create mode 100644 .github/workflows/pypi.yml create mode 100644 docs/RELEASES.md create mode 100644 requirements.sh create mode 100644 tests/README.md delete mode 100644 tests/htuning.py delete mode 100644 tests/nn/criterions.py delete mode 100644 tests/nn/functional.py delete mode 100644 tests/nn/modules.py delete mode 100644 tests/nn/transformer.py rename .github/workflows/ci.yaml => tests/test_adversarial.py (100%) rename tests/{default_models.py => test_models.py} (100%) create mode 100644 tests/test_nn.py create mode 100644 tests/test_supervised.py delete mode 100644 tests/training.py diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml new file mode 100644 index 0000000..be79c46 --- /dev/null +++ b/.github/workflows/default.yml @@ -0,0 +1,46 @@ +name: Default + +on: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.7, 3.8, 3.9, 3.11, 3.12] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Set up the dependencies + run: | + python -m pip install --upgrade pip + pip install pipreqs pip-tools + chmod +x requirements.sh + sh ./requirements.sh + + - name: Install dependencies + run: | + pip install pytest black + pip install -r requirements.txt + + - name: Run black + id: black + run: | + black . + git config --global user.name 'Jorgedavyd' + git config --global user.email 'jorged.encyso@gmail.com' + git diff --exit-code || (git add . && git commit -m "Automatically formatted with black" && git push) + + - name: Run tests + run: | + pytest tests/ diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000..3cb12fa --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,46 @@ +name: To PyPI + +on: + push: + tags: + - '*.*.*' # Trigger on all tag pushes + +jobs: + build-and-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install wheel setuptools + + - name: Extract tag name + id: extract_tag + run: echo "TAG_NAME=$(echo $GITHUB_REF | cut -d/ -f3)" >> $GITHUB_ENV + + - name: Update version in setup.py and lightorch/_version.py + run: | + sed -i "s/{{VERSION_PLACEHOLDER}}/${{ env.TAG_NAME }}/g" setup.py + sed -i "s/{{VERSION_PLACEHOLDER}}/${{ env.TAG_NAME }}/g" lightorch/_version.py + + - name: Build the package + run: | + python setup.py sdist bdist_wheel + + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@v1.8.14 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + + - name: Clean up + run: rm -rf build dist *.egg-info \ No newline at end of file diff --git a/docs/RELEASES.md b/docs/RELEASES.md new file mode 100644 index 0000000..d48df1b --- /dev/null +++ b/docs/RELEASES.md @@ -0,0 +1,2 @@ +# 0.0.1 +First Release \ No newline at end of file diff --git a/lightorch/_version.py b/lightorch/_version.py index c9ca121..89ac199 100644 --- a/lightorch/_version.py +++ b/lightorch/_version.py @@ -1,2 +1 @@ -__version_info__ = ("0", "0", "1") -__version__ = ".".join(__version_info__) +__version__ = '{{VERSION_PLACEHOLDER}}' diff --git a/lightorch/nn/criterions.py b/lightorch/nn/criterions.py index a2c8052..f3df6ee 100644 --- a/lightorch/nn/criterions.py +++ b/lightorch/nn/criterions.py @@ -205,4 +205,5 @@ def forward(self, out: Tensor, target: Tensor) -> Tensor: "PeakNoiseSignalRatio", "StyleLoss", "PerceptualLoss", + "Loss" ] diff --git a/lightorch/nn/fourier.py b/lightorch/nn/fourier.py index 24dd476..84873ba 100644 --- a/lightorch/nn/fourier.py +++ b/lightorch/nn/fourier.py @@ -5,6 +5,7 @@ from torch.nn import init from math import sqrt import torch.nn.functional as f +from typing import Tuple class _FourierConvNd(nn.Module): def __init__( @@ -12,6 +13,7 @@ def __init__( in_channels: int, out_channels: int, *kernel_size, + padding: Tuple[int], bias: bool = True, eps: float = 1e-5, pre_fft: bool = True, @@ -22,7 +24,7 @@ def __init__( super().__init__() self.factory_kwargs = {"device": device, "dtype": dtype} - + self.padding = padding if pre_fft: self.fft = lambda x: fftn(x, dim=(-i for i in range(1, len(kernel_size)))) else: @@ -73,7 +75,12 @@ def __init__(self, *args, **kwargs) -> None: def forward(self, input: Tensor) -> Tensor: if self.fft: input = self.fft(input) - out = F.fourierconv1d(input, self.one, self.weight, self.bias) + if self.padding is not None: + out = F.fourierconv1d(input, self.one, self.weight, self.bias) + else: + out = F.fourierconv1d(f.pad( + input, self.padding, mode = 'constant', value = 0 + ), self.one, self.weight, self.bias) if self.ifft: return self.ifft(out) return out @@ -86,7 +93,11 @@ def __init__(self, *args, **kwargs) -> None: def forward(self, input: Tensor) -> Tensor: if self.fft: input = self.fft(input) - out = F.fourierconv2d(input, self.one, self.weight, self.bias) + if self.padding is not None: + out = F.fourierconv2d(input, self.one, self.weight, self.bias) + else: + out = F.fourierconv2d(f.pad(input, self.padding, 'constant', value = 0), self.one, self.weight, self.bias) + if self.ifft: return self.ifft(out) return out @@ -99,7 +110,10 @@ def __init__(self, *args, **kwargs) -> None: def forward(self, input: Tensor) -> Tensor: if self.fft: input = self.fft(input) - out = F.fourierconv3d(input, self.one, self.weight, self.bias) + if self.padding is not None: + out = F.fourierconv3d(input, self.one, self.weight, self.bias) + else: + out = F.fourierconv3d(f.pad(input, self.padding, 'constant', value = 0), self.one, self.weight, self.bias) if self.ifft: return self.ifft(out) return out @@ -111,7 +125,10 @@ def __init__(self, *args, **kwargs) -> None: def forward(self, input: Tensor) -> Tensor: if self.fft: input = self.fft(input) - out = F.fourierdeconv1d(input, self.one, self.weight, self.bias, self.eps) + if self.padding is not None: + out = F.fourierdeconv1d(input, self.one, self.weight, self.bias) + else: + out = F.fourierdeconv1d(f.pad(input, self.padding, 'constant', value = 0), self.one, self.weight, self.bias) if self.ifft: return self.ifft(out) return out @@ -124,7 +141,10 @@ def __init__(self, *args, **kwargs) -> None: def forward(self, input: Tensor) -> Tensor: if self.fft: input = self.fft(input) - out = F.fourierdeconv2d(input, self.one, self.weight, self.bias, self.eps) + if self.padding is not None: + out = F.fourierdeconv2d(input, self.one, self.weight, self.bias) + else: + out = F.fourierdeconv2d(f.pad(input, self.padding, 'constant', value = 0), self.one, self.weight, self.bias) if self.ifft: return self.ifft(out) return out @@ -137,7 +157,10 @@ def __init__(self, *args, **kwargs) -> None: def forward(self, input: Tensor) -> Tensor: if self.fft: input = self.fft(input) - out = F.fourierdeconv3d(input, self.one, self.weight, self.bias, self.eps) + if self.padding is not None: + out = F.fourierdeconv3d(input, self.one, self.weight, self.bias) + else: + out = F.fourierdeconv3d(f.pad(input, self.padding, 'constant', value = 0), self.one, self.weight, self.bias) if self.ifft: return self.ifft(out) return out diff --git a/lightorch/nn/transformer/transformer.py b/lightorch/nn/transformer/transformer.py index cbac249..545dda0 100644 --- a/lightorch/nn/transformer/transformer.py +++ b/lightorch/nn/transformer/transformer.py @@ -81,7 +81,7 @@ def forward(self, **kwargs) -> Tensor: hist.append(out) for cross, decoder in zip(hist, self.decoder): - out = decoder(**kwargs, cross) + out = decoder(**kwargs, cross = cross) out = self.fc(out) @@ -102,7 +102,7 @@ def __init__(self, *cells, n_layers: int, fc: nn.Module) -> None: self.fc = fc self.n_layers = n_layers - def _single_forward(self, cells: Sequence[TransformerCell], layer: int, first_args: Sequence, second_args: Sequence) -> Tensor: + def _single_forward(self, cells: Sequence[TransformerCell], first_args: Sequence, second_args: Sequence) -> Tensor: out0 = cells[0].self_attention(*first_args) out1 = cells[1].self_attention(*second_args) diff --git a/lightorch/training/__init__.py b/lightorch/training/__init__.py index e7a9e7d..e69de29 100644 --- a/lightorch/training/__init__.py +++ b/lightorch/training/__init__.py @@ -1,2 +0,0 @@ -from .adversarial import * -from .supervised import * diff --git a/requirements.sh b/requirements.sh new file mode 100644 index 0000000..94c2720 --- /dev/null +++ b/requirements.sh @@ -0,0 +1,4 @@ +rm requirements.txt +pipreqs lightorch/ --savepath=requirements.in +pip-compile +rm requirements.in diff --git a/setup.py b/setup.py index 2bc0e47..af7dc14 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ from setuptools import setup, find_packages -from lightorch import __version__, __author__, __email__ +from lightorch import __author__, __email__ from pathlib import Path this_directory = Path(__file__).parent @@ -8,7 +8,7 @@ if __name__ == "__main__": setup( name="lightorch", - version=__version__, + version='{{VERSION_PLACEHOLDER}}', packages=find_packages(), author=__author__, long_description=long_description, diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..df0a497 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,7 @@ +# Tests +1. Unit Tests: +- All functions/methods covered. +- Edge cases considered. +2. Integration Tests: +- Interactions with other modules tested. +- API calls verified. \ No newline at end of file diff --git a/tests/htuning.py b/tests/htuning.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/nn/criterions.py b/tests/nn/criterions.py deleted file mode 100644 index f014ae3..0000000 --- a/tests/nn/criterions.py +++ /dev/null @@ -1,8 +0,0 @@ -import pytest -from torch import Tensor -import torch - - - -## Test automation for criterions - diff --git a/tests/nn/functional.py b/tests/nn/functional.py deleted file mode 100644 index c8a3ac4..0000000 --- a/tests/nn/functional.py +++ /dev/null @@ -1,11 +0,0 @@ -import pytest -from torch import Tensor -import torch -from ...lightorch.nn.functional import ( - residual_connection, - -) -from ..utils import create_inputs - - - diff --git a/tests/nn/modules.py b/tests/nn/modules.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/nn/transformer.py b/tests/nn/transformer.py deleted file mode 100644 index ac2a2f9..0000000 --- a/tests/nn/transformer.py +++ /dev/null @@ -1,7 +0,0 @@ -## Attention - -## Patch Embedding - -## Actual transformer - -## \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/tests/test_adversarial.py similarity index 100% rename from .github/workflows/ci.yaml rename to tests/test_adversarial.py diff --git a/tests/default_models.py b/tests/test_models.py similarity index 100% rename from tests/default_models.py rename to tests/test_models.py diff --git a/tests/test_nn.py b/tests/test_nn.py new file mode 100644 index 0000000..f75e7bd --- /dev/null +++ b/tests/test_nn.py @@ -0,0 +1,63 @@ +from ..lightorch.nn.criterions import * +from torch import Tensor, nn +from .utils import * +import random + +## Test automation for criterions + +# Unit tests +input: Tensor = create_inputs(1, 3, 256, 256) +target: Tensor = create_inputs(1, 3, 256, 256) + +mu: Tensor = create_inputs(1, 32) +logvar: Tensor = create_inputs(1, 32) + +model: nn.Module = Model(32) +randint: int = random.randint(-100, 100) + + +def test_tv() -> None: + loss = TV(randint) + loss(input = input) + + +def test_style() -> None: + loss = StyleLoss(model, input, randint) + loss(input = input, target = target, feature_extractor = False) + + +def test_perc() -> None: + loss = PerceptualLoss(model, input, randint) + loss(input = input, target = target, feature_extractor = False) + +def test_psnr() -> None: + loss = PeakNoiseSignalRatio(1, randint) + loss(input = input, target = target) + +# Integration tests +def test_lagrange() -> None: + loss = LagrangianFunctional( + nn.MSELoss(), nn.MSELoss(), nn.MSELoss(), lambd = Tensor([randint for _ in range(2)]) + ) + loss(input=input, target=target) + +def test_loss() -> None: + loss = Loss( + TV(randint), PeakNoiseSignalRatio(1, randint) + ) + loss(input = input, target = target) + +def test_elbo() -> None: + loss = ELBO( + randint, PeakNoiseSignalRatio(1, randint) + ) + loss(input = input, target = target, mu = mu, logvar = logvar) + +def test_fourier() -> None: + input: Tensor = create_inputs(1, 3, 256, 256) + +def test_partial() -> None: + raise NotImplementedError + +def test_kan() -> None: + raise NotImplementedError diff --git a/tests/test_supervised.py b/tests/test_supervised.py new file mode 100644 index 0000000..c16222c --- /dev/null +++ b/tests/test_supervised.py @@ -0,0 +1,40 @@ +from ..lightorch.training.supervised import Module +from ..lightorch.nn.criterions import PeakNoiseSignalRatio +from ..lightorch.htuning.optuna import htuning +from .utils import Model, create_inputs, DataModule + +import random +from torch import Tensor, nn +in_size: int = 32 +input: Tensor = create_inputs(1, in_size) +randint: int = random.randint(-100, 100) +#Integrated test + +class SupModel(Module): + def __init__(self, **hparams) -> None: + super().__init__(**hparams) + self.criterion = PeakNoiseSignalRatio(1, randint) + self.model = Model(in_size) + + def forward(self, input: Tensor) -> Tensor: + return self.model(input) + +def objective(): + pass + +def test_supervised() -> None: + htuning( + model_class = SupModel, + hparam_objective=objective, + datamodule = DataModule, + valid_metrics = 'MSE', + datamodule_kwargs= dict( + pin_memory = False, + num_workers = 1, + batch_size = 1 + ), + directions = 'minimize', + precision = 'high', + n_trials = 10, + trianer_kwargs = dict(fast_dev_run = True) + ) diff --git a/tests/training.py b/tests/training.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/utils.py b/tests/utils.py index b2de2bb..ffee27e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,5 +1,46 @@ +from lightning.pytorch.utilities.types import TRAIN_DATALOADERS import torch -from torch import Tensor - +from torch import Tensor, nn +from lightning.pytorch import LightningDataModule +from torch.utils.data import Dataset, DataLoader def create_inputs(*size) -> Tensor: - return torch.randn(*size) \ No newline at end of file + return torch.randn(*size) + +class Model(nn.Sequential): + def __init__(self, in_channels) -> None: + super().__init__( + nn.Linear(in_channels, 10), + nn.Linear(10, 1) + ) + def forward(self, input): + return super().forward(input) + +# Add feature extractor +class Data(Dataset): + def __init__(self,) -> None: + pass + +class DataModule(LightningDataModule): + def __init__(self, batch_size: int, pin_memory: bool = False, num_workers: int = 1): + self.batch_size = batch_size + self.pin_memory = pin_memory + self.num_workers = num_workers + def setup(self) -> None: + self.train_ds + def train_dataloader(self) -> DataLoader: + return DataLoader( + Data(), + self.batch_size, + False, + num_workers=self.num_workers, + pin_memory=self.pin_memory + ) + + def val_dataloader(self) -> DataLoader: + return DataLoader( + Data(), + self.batch_size * 2, + False, + num_workers=self.num_workers, + pin_memory=self.pin_memory + ) \ No newline at end of file