20 Commits

Author SHA1 Message Date
Alexander Engelsberger
0649d5bb45 build: bump version 0.7.3 → 0.7.4 2022-05-17 11:57:32 +02:00
Alexander Engelsberger
339316aa7e fix: use epsilon in cbc competition 2022-05-17 11:56:43 +02:00
Alexander Engelsberger
2a85c94b55 chore: minor changes and version updates 2022-05-17 11:56:18 +02:00
Alexander Engelsberger
6714cb7915 ci: add python 3.10 as supported python version 2022-04-27 09:56:06 +02:00
Alexander Engelsberger
a501ab6c3b build: bump version 0.7.2 → 0.7.3 2022-04-27 09:49:50 +02:00
Alexander Engelsberger
37add944b1 chore: merge dev into master 2022-04-27 09:48:58 +02:00
Jensun Ravichandran
0d10fc7e25 fix: correct typo 2022-04-04 21:50:22 +02:00
Jensun Ravichandran
71a2e74eff feat: add RandomLinearTransformInitializer 2022-04-04 20:55:03 +02:00
Jensun Ravichandran
85f75bb28c feat: add repr for LinearTransform 2022-04-01 10:13:25 +02:00
Alexander Engelsberger
46ff1c4eb1 fix: forward of LinearTransform uses undetached weights now 2022-03-29 17:07:17 +02:00
Jensun Ravichandran
ed5b9b6c62 feat: warn user when component counts do not match 2022-03-29 14:39:41 +02:00
Jensun Ravichandran
08b3f9bbb9 feat: add LiteralLinearTransformInitializer 2022-03-21 14:38:00 +01:00
Jensun Ravichandran
784a963527 chore: housekeeping 2022-03-10 14:46:56 +01:00
Jensun Ravichandran
236cbbc4d2 feat: add color utils 2022-03-10 14:45:55 +01:00
Jensun Ravichandran
695559fd4a fix: incorrect variable names in GLVQLoss.forward 2022-03-09 13:20:00 +01:00
Jensun Ravichandran
a54acdef22 feat: update GLVQLoss to include a regularization term 2022-02-15 17:16:44 +01:00
Jensun Ravichandran
bebd13868f fix: typo fix 2022-02-03 23:29:47 +01:00
Jensun Ravichandran
62df3c0457 feat: raise initializer error on unavailable data 2022-01-31 12:27:48 +01:00
Alexander Engelsberger
07a2d6caaa feat: Add new mesh util 2021-10-15 13:08:19 +02:00
Alexander Engelsberger
3d3d27fbab chore: Absolute imports 2021-10-15 13:07:08 +02:00
17 changed files with 197 additions and 88 deletions

View File

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

View File

@@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: 3.9
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
@@ -27,13 +27,15 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9"]
python-version: ["3.7", "3.8", "3.9", "3.10"]
os: [ubuntu-latest, windows-latest]
exclude:
- os: windows-latest
python-version: "3.7"
- os: windows-latest
python-version: "3.8"
- os: windows-latest
python-version: "3.9"
runs-on: ${{ matrix.os }}
steps:
@@ -55,10 +57,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: "3.9"
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip

View File

@@ -1,6 +1,7 @@
MIT License
Copyright (c) 2020 si-cim
Copyright (c) 2020 Saxon Institute for Computational Intelligence and Machine
Learning (SICIM)
Permission is hereby granted, free of charge, to any person obtaining a copy
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
#
release = "0.7.2"
release = "0.7.4"
# -- General configuration ---------------------------------------------------
@@ -120,7 +120,7 @@ html_css_files = [
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "protoflowdoc"
htmlhelp_basename = "prototorchdoc"
# -- Options for LaTeX output ---------------------------------------------

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,12 @@ class LiteralCompInitializer(AbstractComponentsInitializer):
def generate(self, num_components: int = 0):
"""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):
wmsg = f"Converting components to {torch.Tensor}..."
warnings.warn(wmsg)
@@ -231,6 +237,8 @@ class AbstractStratifiedCompInitializer(AbstractClassAwareCompInitializer):
components = torch.tensor([])
for k, v in distribution.items():
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(
stratified_data,
noise=self.noise,
@@ -457,7 +465,15 @@ class OnesLinearTransformInitializer(AbstractLinearTransformInitializer):
return self.generate_end_hook(weights)
class EyeTransformInitializer(AbstractLinearTransformInitializer):
class RandomLinearTransformInitializer(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."""
def generate(self, in_dim: int, out_dim: int):
@@ -496,6 +512,13 @@ class PCALinearTransformInitializer(AbstractDataAwareLTInitializer):
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
CACI = ClassAwareCompInitializer
DACI = DataAwareCompInitializer
@@ -524,7 +547,9 @@ RRI = RandomReasoningsInitializer
ZRI = ZerosReasoningsInitializer
# Aliases - Transforms
Eye = EyeTransformInitializer
ELTI = Eye = EyeLinearTransformInitializer
OLTI = OnesLinearTransformInitializer
RLTI = RandomLinearTransformInitializer
ZLTI = ZerosLinearTransformInitializer
PCALTI = PCALinearTransformInitializer
LLTI = LiteralLinearTransformInitializer

View File

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

View File

@@ -5,7 +5,7 @@ from torch.nn.parameter import Parameter
from .initializers import (
AbstractLinearTransformInitializer,
EyeTransformInitializer,
EyeLinearTransformInitializer,
)
@@ -16,7 +16,7 @@ class LinearTransform(torch.nn.Module):
in_dim: int,
out_dim: int,
initializer:
AbstractLinearTransformInitializer = EyeTransformInitializer()):
AbstractLinearTransformInitializer = EyeLinearTransformInitializer()):
super().__init__()
self.set_weights(in_dim, out_dim, initializer)
@@ -32,12 +32,15 @@ class LinearTransform(torch.nn.Module):
in_dim: int,
out_dim: int,
initializer:
AbstractLinearTransformInitializer = EyeTransformInitializer()):
AbstractLinearTransformInitializer = EyeLinearTransformInitializer()):
weights = initializer.generate(in_dim, out_dim)
self._register_weights(weights)
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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,13 @@
"""ProtoFlow color utilities"""
"""ProtoTorch 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):
@@ -13,3 +22,39 @@ def rgb_to_hex(rgb_values):
for v in rgb_values:
c = "%02x%02x%02x" % tuple(v)
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,4 +1,4 @@
"""ProtoFlow utilities"""
"""ProtoTorch utilities"""
import warnings
from typing import (

View File

@@ -20,9 +20,10 @@ with open("README.md", "r") as fh:
INSTALL_REQUIRES = [
"torch>=1.3.1",
"torchvision>=0.7.2",
"torchvision>=0.7.4",
"numpy>=1.9.1",
"sklearn",
"matplotlib",
]
DATASETS = [
"requests",
@@ -40,7 +41,6 @@ DOCS = [
"sphinx-autodoc-typehints",
]
EXAMPLES = [
"matplotlib",
"torchinfo",
]
TESTS = [
@@ -51,7 +51,7 @@ ALL = DATASETS + DEV + DOCS + EXAMPLES + TESTS
setup(
name="prototorch",
version="0.7.2",
version="0.7.4",
description="Highly extensible, GPU-supported "
"Learning Vector Quantization (LVQ) toolbox "
"built using PyTorch and its nn API.",
@@ -62,7 +62,7 @@ setup(
url=PROJECT_URL,
download_url=DOWNLOAD_URL,
license="MIT",
python_requires=">=3.7,<3.10",
python_requires=">=3.7",
install_requires=INSTALL_REQUIRES,
extras_require={
"datasets": DATASETS,
@@ -88,6 +88,7 @@ setup(
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
packages=find_packages(),
zip_safe=False,

View File

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