Compare commits

..

No commits in common. "master" and "v0.7.2" have entirely different histories.

23 changed files with 152 additions and 338 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.7.6 current_version = 0.7.2
commit = True commit = True
tag = True tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)

View File

@ -6,42 +6,40 @@ name: tests
on: on:
push: push:
pull_request: pull_request:
branches: [master] branches: [ master ]
jobs: jobs:
style: style:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.11 - name: Set up Python 3.9
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: "3.11" python-version: 3.9
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install .[all] pip install .[all]
- uses: pre-commit/action@v3.0.0 - uses: pre-commit/action@v2.0.3
compatibility: compatibility:
needs: style needs: style
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"] python-version: ["3.7", "3.8", "3.9"]
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
exclude: exclude:
- os: windows-latest
python-version: "3.7"
- os: windows-latest - os: windows-latest
python-version: "3.8" python-version: "3.8"
- os: windows-latest
python-version: "3.9"
- os: windows-latest
python-version: "3.10"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
@ -56,11 +54,11 @@ jobs:
needs: compatibility needs: compatibility
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.10 - name: Set up Python 3.9
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: "3.11" python-version: "3.9"
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip

View File

@ -3,7 +3,7 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v4.1.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
@ -13,17 +13,17 @@ repos:
- id: check-case-conflict - id: check-case-conflict
- repo: https://github.com/myint/autoflake - repo: https://github.com/myint/autoflake
rev: v2.1.1 rev: v1.4
hooks: hooks:
- id: autoflake - id: autoflake
- repo: http://github.com/PyCQA/isort - repo: http://github.com/PyCQA/isort
rev: 5.12.0 rev: 5.10.1
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0 rev: v0.931
hooks: hooks:
- id: mypy - id: mypy
files: prototorch files: prototorch
@ -35,14 +35,14 @@ repos:
- id: yapf - id: yapf
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0 rev: v1.9.0
hooks: hooks:
- id: python-use-type-annotations - id: python-use-type-annotations
- id: python-no-log-warn - id: python-no-log-warn
- id: python-check-blanket-noqa - id: python-check-blanket-noqa
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.7.0 rev: v2.31.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade

View File

@ -1,7 +1,6 @@
MIT License MIT License
Copyright (c) 2020 Saxon Institute for Computational Intelligence and Machine Copyright (c) 2020 si-cim
Learning (SICIM)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -23,7 +23,7 @@ author = "Jensun Ravichandran"
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
# #
release = "0.7.6" release = "0.7.2"
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
@ -120,7 +120,7 @@ html_css_files = [
# -- Options for HTMLHelp output ------------------------------------------ # -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = "prototorchdoc" htmlhelp_basename = "protoflowdoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------

View File

@ -1,7 +1,5 @@
"""ProtoTorch CBC example using 2D Iris data.""" """ProtoTorch CBC example using 2D Iris data."""
import logging
import torch import torch
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
@ -36,7 +34,7 @@ class VisCBC2D():
self.resolution = 100 self.resolution = 100
self.cmap = "viridis" self.cmap = "viridis"
def on_train_epoch_end(self): def on_epoch_end(self):
x_train, y_train = self.x_train, self.y_train x_train, y_train = self.x_train, self.y_train
_components = self.model.components_layer._components.detach() _components = self.model.components_layer._components.detach()
ax = self.fig.gca() ax = self.fig.gca()
@ -96,5 +94,5 @@ if __name__ == "__main__":
correct += (y_pred.argmax(1) == y).float().sum(0) correct += (y_pred.argmax(1) == y).float().sum(0)
acc = 100 * correct / len(train_ds) acc = 100 * correct / len(train_ds)
logging.info(f"Epoch: {epoch} Accuracy: {acc:05.02f}%") print(f"Epoch: {epoch} Accuracy: {acc:05.02f}%")
vis.on_train_epoch_end() vis.on_epoch_end()

View File

@ -1,76 +0,0 @@
"""ProtoTorch GMLVQ example using Iris data."""
import torch
import prototorch as pt
class GMLVQ(torch.nn.Module):
"""
Implementation of Generalized Matrix Learning Vector Quantization.
"""
def __init__(self, data, **kwargs):
super().__init__(**kwargs)
self.components_layer = pt.components.LabeledComponents(
distribution=[1, 1, 1],
components_initializer=pt.initializers.SMCI(data, noise=0.1),
)
self.backbone = pt.transforms.Omega(
len(data[0][0]),
len(data[0][0]),
pt.initializers.RandomLinearTransformInitializer(),
)
def forward(self, data):
"""
Forward function that returns a tuple of dissimilarities and label information.
Feed into GLVQLoss to get a complete GMLVQ model.
"""
components, label = self.components_layer()
latent_x = self.backbone(data)
latent_components = self.backbone(components)
distance = pt.distances.squared_euclidean_distance(
latent_x, latent_components)
return distance, label
def predict(self, data):
"""
The GMLVQ has a modified prediction step, where a competition layer is applied.
"""
components, label = self.components_layer()
distance = pt.distances.squared_euclidean_distance(data, components)
winning_label = pt.competitions.wtac(distance, label)
return winning_label
if __name__ == "__main__":
train_ds = pt.datasets.Iris()
train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32)
model = GMLVQ(train_ds)
optimizer = torch.optim.Adam(model.parameters(), lr=0.05)
criterion = pt.losses.GLVQLoss()
for epoch in range(200):
correct = 0.0
for x, y in train_loader:
d, labels = model(x)
loss = criterion(d, y, labels).mean(0)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
y_pred = model.predict(x)
correct += (y_pred == y).float().sum(0)
acc = 100 * correct / len(train_ds)
print(f"Epoch: {epoch} Accuracy: {acc:05.02f}%")

View File

@ -17,7 +17,7 @@ from .core import similarities # noqa: F401
from .core import transforms # noqa: F401 from .core import transforms # noqa: F401
# Core Setup # Core Setup
__version__ = "0.7.6" __version__ = "0.7.2"
__all_core__ = [ __all_core__ = [
"competitions", "competitions",

View File

@ -38,7 +38,7 @@ def cbcc(detections: torch.Tensor, reasonings: torch.Tensor):
pk = A pk = A
nk = (1 - A) * B nk = (1 - A) * B
numerator = (detections @ (pk - nk).T) + nk.sum(1) numerator = (detections @ (pk - nk).T) + nk.sum(1)
probs = numerator / ((pk + nk).sum(1) + 1e-8) probs = numerator / (pk + nk).sum(1)
return probs return probs

View File

@ -11,7 +11,7 @@ def squared_euclidean_distance(x, y):
**Alias:** **Alias:**
``prototorch.functions.distances.sed`` ``prototorch.functions.distances.sed``
""" """
x, y = (arr.view(arr.size(0), -1) for arr in (x, y)) x, y = [arr.view(arr.size(0), -1) for arr in (x, y)]
expanded_x = x.unsqueeze(dim=1) expanded_x = x.unsqueeze(dim=1)
batchwise_difference = y - expanded_x batchwise_difference = y - expanded_x
differences_raised = torch.pow(batchwise_difference, 2) differences_raised = torch.pow(batchwise_difference, 2)
@ -27,14 +27,14 @@ def euclidean_distance(x, y):
:returns: Distance Tensor of shape :math:`X \times Y` :returns: Distance Tensor of shape :math:`X \times Y`
:rtype: `torch.tensor` :rtype: `torch.tensor`
""" """
x, y = (arr.view(arr.size(0), -1) for arr in (x, y)) x, y = [arr.view(arr.size(0), -1) for arr in (x, y)]
distances_raised = squared_euclidean_distance(x, y) distances_raised = squared_euclidean_distance(x, y)
distances = torch.sqrt(distances_raised) distances = torch.sqrt(distances_raised)
return distances return distances
def euclidean_distance_v2(x, y): def euclidean_distance_v2(x, y):
x, y = (arr.view(arr.size(0), -1) for arr in (x, y)) x, y = [arr.view(arr.size(0), -1) for arr in (x, y)]
diff = y - x.unsqueeze(1) diff = y - x.unsqueeze(1)
pairwise_distances = (diff @ diff.permute((0, 2, 1))).sqrt() pairwise_distances = (diff @ diff.permute((0, 2, 1))).sqrt()
# Passing `dim1=-2` and `dim2=-1` to `diagonal()` takes the # Passing `dim1=-2` and `dim2=-1` to `diagonal()` takes the
@ -54,7 +54,7 @@ def lpnorm_distance(x, y, p):
:param p: p parameter of the lp norm :param p: p parameter of the lp norm
""" """
x, y = (arr.view(arr.size(0), -1) for arr in (x, y)) x, y = [arr.view(arr.size(0), -1) for arr in (x, y)]
distances = torch.cdist(x, y, p=p) distances = torch.cdist(x, y, p=p)
return distances return distances
@ -66,7 +66,7 @@ def omega_distance(x, y, omega):
:param `torch.tensor` omega: Two dimensional matrix :param `torch.tensor` omega: Two dimensional matrix
""" """
x, y = (arr.view(arr.size(0), -1) for arr in (x, y)) x, y = [arr.view(arr.size(0), -1) for arr in (x, y)]
projected_x = x @ omega projected_x = x @ omega
projected_y = y @ omega projected_y = y @ omega
distances = squared_euclidean_distance(projected_x, projected_y) distances = squared_euclidean_distance(projected_x, projected_y)
@ -80,7 +80,7 @@ def lomega_distance(x, y, omegas):
:param `torch.tensor` omegas: Three dimensional matrix :param `torch.tensor` omegas: Three dimensional matrix
""" """
x, y = (arr.view(arr.size(0), -1) for arr in (x, y)) x, y = [arr.view(arr.size(0), -1) for arr in (x, y)]
projected_x = x @ omegas projected_x = x @ omegas
projected_y = torch.diagonal(y @ omegas).T projected_y = torch.diagonal(y @ omegas).T
expanded_y = torch.unsqueeze(projected_y, dim=1) expanded_y = torch.unsqueeze(projected_y, dim=1)

View File

@ -32,12 +32,6 @@ class LiteralCompInitializer(AbstractComponentsInitializer):
def generate(self, num_components: int = 0): def generate(self, num_components: int = 0):
"""Ignore `num_components` and simply return `self.components`.""" """Ignore `num_components` and simply return `self.components`."""
provided_num_components = len(self.components)
if provided_num_components != num_components:
wmsg = f"The number of components ({provided_num_components}) " \
f"provided to {self.__class__.__name__} " \
f"does not match the expected number ({num_components})."
warnings.warn(wmsg)
if not isinstance(self.components, torch.Tensor): if not isinstance(self.components, torch.Tensor):
wmsg = f"Converting components to {torch.Tensor}..." wmsg = f"Converting components to {torch.Tensor}..."
warnings.warn(wmsg) warnings.warn(wmsg)
@ -237,8 +231,6 @@ class AbstractStratifiedCompInitializer(AbstractClassAwareCompInitializer):
components = torch.tensor([]) components = torch.tensor([])
for k, v in distribution.items(): for k, v in distribution.items():
stratified_data = self.data[self.targets == k] stratified_data = self.data[self.targets == k]
if len(stratified_data) == 0:
raise ValueError(f"No data available for class {k}.")
initializer = self.subinit_type( initializer = self.subinit_type(
stratified_data, stratified_data,
noise=self.noise, noise=self.noise,
@ -465,15 +457,7 @@ class OnesLinearTransformInitializer(AbstractLinearTransformInitializer):
return self.generate_end_hook(weights) return self.generate_end_hook(weights)
class RandomLinearTransformInitializer(AbstractLinearTransformInitializer): class EyeTransformInitializer(AbstractLinearTransformInitializer):
"""Initialize a matrix with random values."""
def generate(self, in_dim: int, out_dim: int):
weights = torch.rand(in_dim, out_dim)
return self.generate_end_hook(weights)
class EyeLinearTransformInitializer(AbstractLinearTransformInitializer):
"""Initialize a matrix with the largest possible identity matrix.""" """Initialize a matrix with the largest possible identity matrix."""
def generate(self, in_dim: int, out_dim: int): def generate(self, in_dim: int, out_dim: int):
@ -512,13 +496,6 @@ class PCALinearTransformInitializer(AbstractDataAwareLTInitializer):
return self.generate_end_hook(weights) return self.generate_end_hook(weights)
class LiteralLinearTransformInitializer(AbstractDataAwareLTInitializer):
"""'Generate' the provided weights."""
def generate(self, in_dim: int, out_dim: int):
return self.generate_end_hook(self.data)
# Aliases - Components # Aliases - Components
CACI = ClassAwareCompInitializer CACI = ClassAwareCompInitializer
DACI = DataAwareCompInitializer DACI = DataAwareCompInitializer
@ -547,9 +524,7 @@ RRI = RandomReasoningsInitializer
ZRI = ZerosReasoningsInitializer ZRI = ZerosReasoningsInitializer
# Aliases - Transforms # Aliases - Transforms
ELTI = Eye = EyeLinearTransformInitializer Eye = EyeTransformInitializer
OLTI = OnesLinearTransformInitializer OLTI = OnesLinearTransformInitializer
RLTI = RandomLinearTransformInitializer
ZLTI = ZerosLinearTransformInitializer ZLTI = ZerosLinearTransformInitializer
PCALTI = PCALinearTransformInitializer PCALTI = PCALinearTransformInitializer
LLTI = LiteralLinearTransformInitializer

View File

@ -107,24 +107,14 @@ def margin_loss(y_pred, y_true, margin=0.3):
class GLVQLoss(torch.nn.Module): class GLVQLoss(torch.nn.Module):
def __init__(self, def __init__(self, margin=0.0, transfer_fn="identity", beta=10, **kwargs):
margin=0.0,
transfer_fn="identity",
beta=10,
add_dp=False,
**kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.margin = margin self.margin = margin
self.transfer_fn = get_activation(transfer_fn) self.transfer_fn = get_activation(transfer_fn)
self.beta = torch.tensor(beta) self.beta = torch.tensor(beta)
self.add_dp = add_dp
def forward(self, outputs, targets, plabels): def forward(self, outputs, targets, plabels):
# mu = glvq_loss(outputs, targets, plabels) mu = glvq_loss(outputs, targets, prototype_labels=plabels)
dp, dm = _get_dp_dm(outputs, targets, plabels)
mu = (dp - dm) / (dp + dm)
if self.add_dp:
mu = mu + dp
batch_loss = self.transfer_fn(mu + self.margin, beta=self.beta) batch_loss = self.transfer_fn(mu + self.margin, beta=self.beta)
return batch_loss.sum() return batch_loss.sum()

View File

@ -21,7 +21,7 @@ def cosine_similarity(x, y):
Expected dimension of x is 2. Expected dimension of x is 2.
Expected dimension of y is 2. Expected dimension of y is 2.
""" """
x, y = (arr.view(arr.size(0), -1) for arr in (x, y)) x, y = [arr.view(arr.size(0), -1) for arr in (x, y)]
norm_x = x.pow(2).sum(1).sqrt() norm_x = x.pow(2).sum(1).sqrt()
norm_y = y.pow(2).sum(1).sqrt() norm_y = y.pow(2).sum(1).sqrt()
norm_mat = norm_x.unsqueeze(-1) @ norm_y.unsqueeze(-1).T norm_mat = norm_x.unsqueeze(-1) @ norm_y.unsqueeze(-1).T

View File

@ -5,7 +5,7 @@ from torch.nn.parameter import Parameter
from .initializers import ( from .initializers import (
AbstractLinearTransformInitializer, AbstractLinearTransformInitializer,
EyeLinearTransformInitializer, EyeTransformInitializer,
) )
@ -16,7 +16,7 @@ class LinearTransform(torch.nn.Module):
in_dim: int, in_dim: int,
out_dim: int, out_dim: int,
initializer: initializer:
AbstractLinearTransformInitializer = EyeLinearTransformInitializer()): AbstractLinearTransformInitializer = EyeTransformInitializer()):
super().__init__() super().__init__()
self.set_weights(in_dim, out_dim, initializer) self.set_weights(in_dim, out_dim, initializer)
@ -32,15 +32,12 @@ class LinearTransform(torch.nn.Module):
in_dim: int, in_dim: int,
out_dim: int, out_dim: int,
initializer: initializer:
AbstractLinearTransformInitializer = EyeLinearTransformInitializer()): AbstractLinearTransformInitializer = EyeTransformInitializer()):
weights = initializer.generate(in_dim, out_dim) weights = initializer.generate(in_dim, out_dim)
self._register_weights(weights) self._register_weights(weights)
def forward(self, x): def forward(self, x):
return x @ self._weights return x @ self.weights
def extra_repr(self):
return f"weights: (shape: {tuple(self._weights.shape)})"
# Aliases # Aliases

View File

@ -20,7 +20,7 @@ class Dataset(torch.utils.data.Dataset):
_repr_indent = 2 _repr_indent = 2
def __init__(self, root): def __init__(self, root):
if isinstance(root, str): if isinstance(root, torch._six.string_classes):
root = os.path.expanduser(root) root = os.path.expanduser(root)
self.root = root self.root = root

View File

@ -5,10 +5,8 @@ URL:
""" """
from __future__ import annotations
import warnings import warnings
from typing import Sequence from typing import Sequence, Union
from sklearn.datasets import ( from sklearn.datasets import (
load_iris, load_iris,
@ -43,9 +41,9 @@ class Iris(NumpyDataset):
:param dims: select a subset of dimensions :param dims: select a subset of dimensions
""" """
def __init__(self, dims: Sequence[int] | None = None): def __init__(self, dims: Sequence[int] = None):
x, y = load_iris(return_X_y=True) x, y = load_iris(return_X_y=True)
if dims is not None: if dims:
x = x[:, dims] x = x[:, dims]
super().__init__(x, y) super().__init__(x, y)
@ -58,19 +56,15 @@ class Blobs(NumpyDataset):
""" """
def __init__( def __init__(self,
self,
num_samples: int = 300, num_samples: int = 300,
num_features: int = 2, num_features: int = 2,
seed: None | int = 0, seed: Union[None, int] = 0):
): x, y = make_blobs(num_samples,
x, y = make_blobs(
num_samples,
num_features, num_features,
centers=None, centers=None,
random_state=seed, random_state=seed,
shuffle=False, shuffle=False)
)
super().__init__(x, y) super().__init__(x, y)
@ -83,24 +77,21 @@ class Random(NumpyDataset):
Note: n_classes * n_clusters_per_class <= 2**n_informative must satisfy. Note: n_classes * n_clusters_per_class <= 2**n_informative must satisfy.
""" """
def __init__( def __init__(self,
self,
num_samples: int = 300, num_samples: int = 300,
num_features: int = 2, num_features: int = 2,
num_classes: int = 2, num_classes: int = 2,
num_clusters: int = 2, num_clusters: int = 2,
num_informative: None | int = None, num_informative: Union[None, int] = None,
separation: float = 1.0, separation: float = 1.0,
seed: None | int = 0, seed: Union[None, int] = 0):
):
if not num_informative: if not num_informative:
import math import math
num_informative = math.ceil(math.log2(num_classes * num_clusters)) num_informative = math.ceil(math.log2(num_classes * num_clusters))
if num_features < num_informative: if num_features < num_informative:
warnings.warn("Generating more features than requested.") warnings.warn("Generating more features than requested.")
num_features = num_informative num_features = num_informative
x, y = make_classification( x, y = make_classification(num_samples,
num_samples,
num_features, num_features,
n_informative=num_informative, n_informative=num_informative,
n_redundant=0, n_redundant=0,
@ -108,8 +99,7 @@ class Random(NumpyDataset):
n_clusters_per_class=num_clusters, n_clusters_per_class=num_clusters,
class_sep=separation, class_sep=separation,
random_state=seed, random_state=seed,
shuffle=False, shuffle=False)
)
super().__init__(x, y) super().__init__(x, y)
@ -123,20 +113,16 @@ class Circles(NumpyDataset):
""" """
def __init__( def __init__(self,
self,
num_samples: int = 300, num_samples: int = 300,
noise: float = 0.3, noise: float = 0.3,
factor: float = 0.8, factor: float = 0.8,
seed: None | int = 0, seed: Union[None, int] = 0):
): x, y = make_circles(num_samples,
x, y = make_circles(
num_samples,
noise=noise, noise=noise,
factor=factor, factor=factor,
random_state=seed, random_state=seed,
shuffle=False, shuffle=False)
)
super().__init__(x, y) super().__init__(x, y)
@ -150,16 +136,12 @@ class Moons(NumpyDataset):
""" """
def __init__( def __init__(self,
self,
num_samples: int = 300, num_samples: int = 300,
noise: float = 0.3, noise: float = 0.3,
seed: None | int = 0, seed: Union[None, int] = 0):
): x, y = make_moons(num_samples,
x, y = make_moons(
num_samples,
noise=noise, noise=noise,
random_state=seed, random_state=seed,
shuffle=False, shuffle=False)
)
super().__init__(x, y) super().__init__(x, y)

View File

@ -36,7 +36,6 @@ Description:
are determined by analytic chemistry. are determined by analytic chemistry.
""" """
import logging
import os import os
import numpy as np import numpy as np
@ -82,11 +81,13 @@ class Tecator(ProtoDataset):
if self._check_exists(): if self._check_exists():
return return
logging.debug("Making directories...") if self.verbose:
print("Making directories...")
os.makedirs(self.raw_folder, exist_ok=True) os.makedirs(self.raw_folder, exist_ok=True)
os.makedirs(self.processed_folder, exist_ok=True) os.makedirs(self.processed_folder, exist_ok=True)
logging.debug("Downloading...") if self.verbose:
print("Downloading...")
for fileid, md5 in self._resources: for fileid, md5 in self._resources:
filename = "tecator.npz" filename = "tecator.npz"
download_file_from_google_drive(fileid, download_file_from_google_drive(fileid,
@ -94,7 +95,8 @@ class Tecator(ProtoDataset):
filename=filename, filename=filename,
md5=md5) md5=md5)
logging.debug("Processing...") if self.verbose:
print("Processing...")
with np.load(os.path.join(self.raw_folder, "tecator.npz"), with np.load(os.path.join(self.raw_folder, "tecator.npz"),
allow_pickle=False) as f: allow_pickle=False) as f:
x_train, y_train = f["x_train"], f["y_train"] x_train, y_train = f["x_train"], f["y_train"]
@ -115,4 +117,5 @@ class Tecator(ProtoDataset):
"wb") as f: "wb") as f:
torch.save(test_set, f) torch.save(test_set, f)
logging.debug("Done!") if self.verbose:
print("Done!")

View File

@ -1,11 +1,6 @@
"""ProtoTorch utils module""" """ProtoFlow utils module"""
from .colors import ( from .colors import hex_to_rgb, rgb_to_hex
get_colors,
get_legend_handles,
hex_to_rgb,
rgb_to_hex,
)
from .utils import ( from .utils import (
mesh2d, mesh2d,
parse_data_arg, parse_data_arg,

View File

@ -1,13 +1,4 @@
"""ProtoTorch color utilities""" """ProtoFlow color utilities"""
import matplotlib.lines as mlines
import torch
from matplotlib import cm
from matplotlib.colors import (
Normalize,
to_hex,
to_rgb,
)
def hex_to_rgb(hex_values): def hex_to_rgb(hex_values):
@ -22,39 +13,3 @@ def rgb_to_hex(rgb_values):
for v in rgb_values: for v in rgb_values:
c = "%02x%02x%02x" % tuple(v) c = "%02x%02x%02x" % tuple(v)
yield c yield c
def get_colors(vmax, vmin=0, cmap="viridis"):
cmap = cm.get_cmap(cmap)
colornorm = Normalize(vmin=vmin, vmax=vmax)
colors = dict()
for c in range(vmin, vmax + 1):
colors[c] = to_hex(cmap(colornorm(c)))
return colors
def get_legend_handles(colors, labels, marker="dots", zero_indexed=False):
handles = list()
for color, label in zip(colors.values(), labels):
if marker == "dots":
handle = mlines.Line2D(
xdata=[],
ydata=[],
label=label,
color="white",
markerfacecolor=color,
marker="o",
markersize=10,
markeredgecolor="k",
)
else:
handle = mlines.Line2D(
xdata=[],
ydata=[],
label=label,
color=color,
marker="",
markersize=15,
)
handles.append(handle)
return handles

View File

@ -1,11 +1,10 @@
"""ProtoTorch utilities""" """ProtoFlow utilities"""
import warnings import warnings
from typing import ( from typing import (
Dict, Dict,
Iterable, Iterable,
List, List,
Optional,
Union, Union,
) )
@ -19,7 +18,7 @@ def generate_mesh(
maxima: torch.TensorType, maxima: torch.TensorType,
border: float = 1.0, border: float = 1.0,
resolution: int = 100, resolution: int = 100,
device: Optional[torch.device] = None, device: torch.device = None,
): ):
# Apply Border # Apply Border
ptp = maxima - minima ptp = maxima - minima
@ -56,15 +55,14 @@ def mesh2d(x=None, border: float = 1.0, resolution: int = 100):
def distribution_from_list(list_dist: List[int], def distribution_from_list(list_dist: List[int],
clabels: Optional[Iterable[int]] = None): clabels: Iterable[int] = None):
clabels = clabels or list(range(len(list_dist))) clabels = clabels or list(range(len(list_dist)))
distribution = dict(zip(clabels, list_dist)) distribution = dict(zip(clabels, list_dist))
return distribution return distribution
def parse_distribution( def parse_distribution(user_distribution,
user_distribution, clabels: Iterable[int] = None) -> Dict[int, int]:
clabels: Optional[Iterable[int]] = None) -> Dict[int, int]:
"""Parse user-provided distribution. """Parse user-provided distribution.
Return a dictionary with integer keys that represent the class labels and Return a dictionary with integer keys that represent the class labels and

View File

@ -15,15 +15,14 @@ from setuptools import find_packages, setup
PROJECT_URL = "https://github.com/si-cim/prototorch" PROJECT_URL = "https://github.com/si-cim/prototorch"
DOWNLOAD_URL = "https://github.com/si-cim/prototorch.git" DOWNLOAD_URL = "https://github.com/si-cim/prototorch.git"
with open("README.md", encoding="utf-8") as fh: with open("README.md", "r") as fh:
long_description = fh.read() long_description = fh.read()
INSTALL_REQUIRES = [ INSTALL_REQUIRES = [
"torch>=2.0.0", "torch>=1.3.1",
"torchvision", "torchvision>=0.7.2",
"numpy", "numpy>=1.9.1",
"scikit-learn", "sklearn",
"matplotlib",
] ]
DATASETS = [ DATASETS = [
"requests", "requests",
@ -41,6 +40,7 @@ DOCS = [
"sphinx-autodoc-typehints", "sphinx-autodoc-typehints",
] ]
EXAMPLES = [ EXAMPLES = [
"matplotlib",
"torchinfo", "torchinfo",
] ]
TESTS = [ TESTS = [
@ -51,7 +51,7 @@ ALL = DATASETS + DEV + DOCS + EXAMPLES + TESTS
setup( setup(
name="prototorch", name="prototorch",
version="0.7.6", version="0.7.2",
description="Highly extensible, GPU-supported " description="Highly extensible, GPU-supported "
"Learning Vector Quantization (LVQ) toolbox " "Learning Vector Quantization (LVQ) toolbox "
"built using PyTorch and its nn API.", "built using PyTorch and its nn API.",
@ -62,7 +62,7 @@ setup(
url=PROJECT_URL, url=PROJECT_URL,
download_url=DOWNLOAD_URL, download_url=DOWNLOAD_URL,
license="MIT", license="MIT",
python_requires=">=3.8", python_requires=">=3.7,<3.10",
install_requires=INSTALL_REQUIRES, install_requires=INSTALL_REQUIRES,
extras_require={ extras_require={
"datasets": DATASETS, "datasets": DATASETS,
@ -85,10 +85,9 @@ setup(
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
], ],
packages=find_packages(), packages=find_packages(),
zip_safe=False, zip_safe=False,

View File

@ -245,20 +245,20 @@ def test_random_reasonings_init_channels_not_first():
# Transform initializers # Transform initializers
def test_eye_transform_init_square(): def test_eye_transform_init_square():
t = pt.initializers.EyeLinearTransformInitializer() t = pt.initializers.EyeTransformInitializer()
I = t.generate(3, 3) I = t.generate(3, 3)
assert torch.allclose(I, torch.eye(3)) assert torch.allclose(I, torch.eye(3))
def test_eye_transform_init_narrow(): def test_eye_transform_init_narrow():
t = pt.initializers.EyeLinearTransformInitializer() t = pt.initializers.EyeTransformInitializer()
actual = t.generate(3, 2) actual = t.generate(3, 2)
desired = torch.Tensor([[1, 0], [0, 1], [0, 0]]) desired = torch.Tensor([[1, 0], [0, 1], [0, 0]])
assert torch.allclose(actual, desired) assert torch.allclose(actual, desired)
def test_eye_transform_init_wide(): def test_eye_transform_init_wide():
t = pt.initializers.EyeLinearTransformInitializer() t = pt.initializers.EyeTransformInitializer()
actual = t.generate(2, 3) actual = t.generate(2, 3)
desired = torch.Tensor([[1, 0, 0], [0, 1, 0]]) desired = torch.Tensor([[1, 0, 0], [0, 1, 0]])
assert torch.allclose(actual, desired) assert torch.allclose(actual, desired)

View File

@ -1,6 +1,7 @@
"""ProtoTorch datasets test suite""" """ProtoTorch datasets test suite"""
import os import os
import shutil
import unittest import unittest
import numpy as np import numpy as np