Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Tests

on:
push:
branches: [master, main]
pull_request:
branches: [master, main]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Set up Python 3.12
run: uv python install 3.12

- name: Install dependencies
run: uv sync --extra dev

- name: Run ruff linting
run: uv run ruff check .

- name: Run ruff format check
run: uv run ruff format --check .

- name: Run tests
run: uv run pytest test/ -v
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
default_stages: [pre-push]
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff-format
- id: ruff
23 changes: 0 additions & 23 deletions .travis.yml

This file was deleted.

8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ climin
------

climin is a Python package for optimization, heavily biased to machine learning
scenarios distributed under the BSD 3-clause license. It works on top of numpy
and (partially) gnumpy.
scenarios distributed under the BSD 3-clause license. It works on top of numpy.

The project was started in winter 2011 by Christian Osendorfer and Justin Bayer.
Since then, Sarah Diot-Girard, Thomas Rueckstiess and Sebastian Urban have
Expand All @@ -24,8 +23,7 @@ Important links
Dependencies
------------

The software is tested under Python 2.7 with numpy 1.10.4, scipy 0.17. The tests
are run with nosetests.
Requires Python 3.12+ with numpy 1.26+ and scipy 1.11+.


Installation
Expand All @@ -38,4 +36,4 @@ in the clone to intall in your local user space.
Testing
-------

From the download directory run ``nosetests tests/``.
From the downloaded directory run ``pytest``.
17 changes: 8 additions & 9 deletions climin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
from __future__ import absolute_import

# Control breaking does not work on Windows after e.g. scipy.stats is
# imported because certain Fortran libraries register their own signal handler
# See:
# http://stackoverflow.com/questions/15457786/ctrl-c-crashes-python-after-importing-scipy-stats
# and https://github.com/numpy/numpy/issues/6923
import sys
import os
import imp
import ctypes

if sys.platform == 'win32':
if sys.platform == "win32":
# For setups where Intel Fortran compiler version >= 16.0 (This is the case
# for Anaconda version 4.1.5 which comes with numpy version 1.10.4) is used,
# the following flag allows to disable the additionally introduced signal
# handler, older versios make no use of this environment variable
env = 'FOR_DISABLE_CONSOLE_CTRL_HANDLER'
env = "FOR_DISABLE_CONSOLE_CTRL_HANDLER"
if env not in os.environ:
os.environ[env] = '1'
os.environ[env] = "1"
# In setups with an older version, ensuring that the respective dlls are
# loaded from the numpy core and not somewhere else (e.g. the Windows System
# folder) helps
basepath = imp.find_module('numpy')[1]
import numpy

basepath = os.path.dirname(numpy.__file__)
# dll loading fails when Intel Fortran compiler version >= 16.0, therefore
# use try/catch
try:
ctypes.CDLL(os.path.join(basepath, 'core', 'libmmd.dll'))
ctypes.CDLL(os.path.join(basepath, 'core', 'libifcoremd.dll'))
ctypes.CDLL(os.path.join(basepath, "core", "libmmd.dll"))
ctypes.CDLL(os.path.join(basepath, "core", "libifcoremd.dll"))
except Exception:
pass

Expand Down
21 changes: 9 additions & 12 deletions climin/adadelta.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

"""This module provides an implementation of adadelta."""

from __future__ import absolute_import

from .base import Minimizer
from .mathadapt import sqrt, ones_like, clip


class Adadelta(Minimizer):
"""Adadelta optimizer.
r"""Adadelta optimizer.

Adadelta [zeiler2013adadelta]_ is a method that uses the magnitude of recent
gradients and steps to obtain an adaptive step rate. An exponential moving
Expand Down Expand Up @@ -53,10 +51,9 @@ class Adadelta(Minimizer):
arXiv preprint arXiv:1212.5701 (2012).
"""

state_fields = 'n_iter gms sms step step_rate decay offset momentum'.split()
state_fields = "n_iter gms sms step step_rate decay offset momentum".split()

def __init__(self, wrt, fprime, step_rate=1, decay=0.9, momentum=0,
offset=1e-4, args=None):
def __init__(self, wrt, fprime, step_rate=1, decay=0.9, momentum=0, offset=1e-4, args=None):
"""Create an Adadelta object.

Parameters
Expand Down Expand Up @@ -113,18 +110,18 @@ def _iterate(self):

gradient = self.fprime(self.wrt, *args, **kwargs)

self.gms = (d * self.gms) + (1 - d) * gradient ** 2
self.gms = (d * self.gms) + (1 - d) * gradient**2
step2 = sqrt(self.sms + o) / sqrt(self.gms + o) * gradient * self.step_rate
self.wrt -= step2

self.step = step1 + step2
self.sms = (d * self.sms) + (1 - d) * self.step ** 2
self.sms = (d * self.sms) + (1 - d) * self.step**2

self.n_iter += 1

yield {
'n_iter': self.n_iter,
'gradient': gradient,
'args': args,
'kwargs': kwargs,
"n_iter": self.n_iter,
"gradient": gradient,
"args": args,
"kwargs": kwargs,
}
61 changes: 35 additions & 26 deletions climin/adam.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

"""This module provides an implementation of Adam."""

from __future__ import absolute_import

import warnings

from .base import Minimizer


class Adam(Minimizer):
"""Adaptive moment estimation optimizer. (Adam).
r"""Adaptive moment estimation optimizer. (Adam).

Adam is a method for the optimization of stochastic objective functions.

Expand Down Expand Up @@ -90,14 +88,22 @@ class Adam(Minimizer):
Stanford University, Tech. Rep (2015).
"""

state_fields = 'n_iter step_rate decay_mom1 decay_mom2 step offset est_mom1_b est_mom2_b'.split()

def __init__(self, wrt, fprime, step_rate=.0002,
decay=None,
decay_mom1=0.1,
decay_mom2=0.001,
momentum=0,
offset=1e-8, args=None):
state_fields = (
"n_iter step_rate decay_mom1 decay_mom2 step offset est_mom1_b est_mom2_b".split()
)

def __init__(
self,
wrt,
fprime,
step_rate=0.0002,
decay=None,
decay_mom1=0.1,
decay_mom2=0.001,
momentum=0,
offset=1e-8,
args=None,
):
"""Create an Adam object.

Parameters
Expand Down Expand Up @@ -136,16 +142,20 @@ def __init__(self, wrt, fprime, step_rate=.0002,
Iterator over arguments which ``fprime`` will be called with.
"""
if not 0 < decay_mom1 <= 1:
raise ValueError('decay_mom1 has to lie in (0, 1]')
raise ValueError("decay_mom1 has to lie in (0, 1]")
if not 0 < decay_mom2 <= 1:
raise ValueError('decay_mom2 has to lie in (0, 1]')
raise ValueError("decay_mom2 has to lie in (0, 1]")
if not (1 - decay_mom1 * 2) / (1 - decay_mom2) ** 0.5 < 1:
warnings.warn("constraint from convergence analysis for adam not "
"satisfied; check original paper to see if you "
"really want to do this.")
warnings.warn(
"constraint from convergence analysis for adam not "
"satisfied; check original paper to see if you "
"really want to do this."
)
if decay is not None:
warnings.warn('decay parameter was used in a previous verion of '
'Adam and no longer has any effect.')
warnings.warn(
"decay parameter was used in a previous verion of "
"Adam and no longer has any effect."
)

super(Adam, self).__init__(wrt, args=args)

Expand Down Expand Up @@ -176,20 +186,19 @@ def _iterate(self):

gradient = self.fprime(self.wrt, *args, **kwargs)
self.est_mom1_b = dm1 * gradient + (1 - dm1) * est_mom1_b_m1
self.est_mom2_b = dm2 * gradient ** 2 + (1 - dm2) * est_mom2_b_m1
self.est_mom2_b = dm2 * gradient**2 + (1 - dm2) * est_mom2_b_m1

step_t = self.step_rate * (1 - (1 - dm2) ** t) ** 0.5 / \
(1 - (1 - dm1) ** t)
step2 = step_t * self.est_mom1_b / (self.est_mom2_b ** 0.5 + o)
step_t = self.step_rate * (1 - (1 - dm2) ** t) ** 0.5 / (1 - (1 - dm1) ** t)
step2 = step_t * self.est_mom1_b / (self.est_mom2_b**0.5 + o)

self.wrt -= step2
self.step = step1 + step2

self.n_iter += 1

yield {
'n_iter': self.n_iter,
'gradient': gradient,
'args': args,
'kwargs': kwargs,
"n_iter": self.n_iter,
"gradient": gradient,
"args": args,
"kwargs": kwargs,
}
28 changes: 12 additions & 16 deletions climin/asgd.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
# -*- coding: utf-8 -*-

# TODO: document module
# TODO: check if gnumpy compatible

from __future__ import absolute_import

from .base import Minimizer


class Asgd(Minimizer):
# TODO: document class

def __init__(self, wrt, fprime, eta0=1e-5, lmbd=1e-4, alpha=0.75, t0=1e8,
args=None):
def __init__(self, wrt, fprime, eta0=1e-5, lmbd=1e-4, alpha=0.75, t0=1e8, args=None):
# TODO document method
# TODO rename parameters to sth sensible, not greek letters
# TODO give reference
Expand All @@ -27,10 +23,10 @@ def __init__(self, wrt, fprime, eta0=1e-5, lmbd=1e-4, alpha=0.75, t0=1e8,
self.eta_t = eta0

def set_from_info(self, info):
raise NotImplemented('nobody has found the time to implement this yet')
raise NotImplementedError("nobody has found the time to implement this yet")

def extended_info(self, **kwargs):
raise NotImplemented('nobody has found the time to implement this yet')
raise NotImplementedError("nobody has found the time to implement this yet")

def __iter__(self):
# do 'determineEta0' (see Bottou) here?
Expand All @@ -43,7 +39,7 @@ def __iter__(self):
gradient = self.fprime(self.w, *args, **kwargs)

# decay w
self.w *= (1 - self.lmbd * self.eta_t)
self.w *= 1 - self.lmbd * self.eta_t
# and update (descent direction)
self.w -= self.eta_t * gradient

Expand All @@ -55,15 +51,15 @@ def __iter__(self):
self.wrt *= 0
self.wrt += self.w

self.mu_t = 1. / max(1, (i + 1) - self.t0)
self.mu_t = 1.0 / max(1, (i + 1) - self.t0)
self.eta_t = self.eta0 / ((1 + self.lmbd * self.eta0 * (i + 1)) ** self.alpha)

yield {
'n_iter': i,
'gradient': gradient,
'mu_t': self.mu_t,
'eta_t': self.eta_t,
'step': step,
'args': args,
'kwargs': kwargs
"n_iter": i,
"gradient": gradient,
"mu_t": self.mu_t,
"eta_t": self.eta_t,
"step": step,
"args": args,
"kwargs": kwargs,
}
7 changes: 3 additions & 4 deletions climin/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

import itertools
import collections
import collections.abc

import numpy as np

Expand All @@ -14,7 +14,6 @@ def repeat_or_iter(obj):


class Minimizer(object):

def __init__(self, wrt, args=None):
self.wrt = wrt
if args is None:
Expand Down Expand Up @@ -67,10 +66,10 @@ def minimize_until(self, criterions):
an optimizer, returns a boolean indicating whether to stop. False means
to continue, True means to stop."""
if not criterions:
raise ValueError('need to supply at least one criterion')
raise ValueError("need to supply at least one criterion")

# if criterions is a single criterion, wrap it in iterable list
if not isinstance(criterions, collections.Iterable):
if not isinstance(criterions, collections.abc.Iterable):
criterions = [criterions]

info = {}
Expand Down
Loading