Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sparse Fascicle Model #460

Merged
merged 71 commits into from Dec 18, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
78e0ab7
NF: Starting to implement the SFM.
arokem Nov 11, 2014
caf6e56
RF: Make sklearn an optional package.
arokem Nov 11, 2014
80a2058
NF: First pass at fitting the thing.
arokem Nov 11, 2014
a0f8116
Starting to work on signal prediction.
arokem Nov 11, 2014
98d1c55
Abstract out the design matrix construction. Predict.
arokem Nov 14, 2014
2dd5e27
BF: Make it work with different in/out situations.
arokem Nov 15, 2014
344bbe1
TST + BF: More testing, more fixes.
arokem Nov 15, 2014
3f040f9
TST: More tests.
arokem Nov 15, 2014
833406a
Handle errors in the ODF estimation.
arokem Nov 15, 2014
c0943d4
When signal is zero, set params to all-zeros, not nans.
arokem Nov 15, 2014
0c1be53
TST: update the tests.
arokem Nov 15, 2014
bb940e9
NF: Calculate the ODF from the parameters.
arokem Nov 16, 2014
36353ba
RF: Cache for speed.
arokem Nov 16, 2014
0d044e1
DOC: Small fixes in documentation.
arokem Nov 16, 2014
4a861df
DOC: Reference
arokem Nov 16, 2014
04b655d
DOC: example of SFM reconstruction + tracking.
arokem Nov 16, 2014
0508e02
TST: Implemented tests for new interface to linear solvers.
arokem Nov 19, 2014
4263173
NF: Uniform sklearn-like interface to linear solvers.
arokem Nov 19, 2014
2f81db6
TST + BF: Test and improve the NNLS dipy.core.optimize interface.
arokem Nov 19, 2014
d181d3b
RF: Testing the SKLearnLinearSolver interface.
arokem Nov 19, 2014
5691af9
Try using isinstance.
arokem Nov 20, 2014
e0f683e
BF: Need to initialize these puppies.
arokem Nov 20, 2014
29d01f3
BF: This has to come from `npt` namespace.
arokem Nov 22, 2014
fc4efec
Addressing comments from review.
arokem Nov 24, 2014
6e5bb74
BF: Apparently `predict` should not be an abstractmethod.
arokem Nov 24, 2014
807e46b
BF: Assign these into variables, so that doctesting doesn't freak out.
arokem Nov 24, 2014
fae6afb
BF: This should be the mean of the non-S0 samples.
arokem Nov 25, 2014
ccb27f9
RF: Reshape once upfront and then again at the bottom.
arokem Nov 25, 2014
01910e1
DOC: Enhanced the documentation for the sfm_design_matrix function.
arokem Nov 26, 2014
8ef004c
BF: For some (older?) versions of python, take care of cases in which…
arokem Nov 26, 2014
6cea1a9
RF: Reality check.
arokem Nov 26, 2014
791de59
BF: Changed the kwarg to the full 'signal'.
arokem Nov 26, 2014
d262cb8
TST: Need to make sure that this works for stick response function.
arokem Nov 26, 2014
2908326
TST: Add another Travis-bot to test with sklearn.
arokem Nov 26, 2014
809d23c
TST: We don't really need two of these tests
arokem Nov 26, 2014
14b3dce
TST: Travising
arokem Nov 26, 2014
bd3a145
I wonder if whitespace would help.
arokem Nov 26, 2014
bb4c06d
Get me some valid yaml
arokem Nov 27, 2014
851757c
TST: This is what sklearn is called here.
arokem Nov 27, 2014
59fdc53
RF: Enable using a 'stick' function as the response function for SFM.
arokem Dec 2, 2014
e2ae7d0
TST: Test using NNLS, so that it runs on Travis.
arokem Dec 2, 2014
8ef225c
DOC: Small addition to the explanation of the kernel.
arokem Dec 2, 2014
c80e8ae
DOC: Greek letters
arokem Dec 2, 2014
eb324e3
PEP8
arokem Dec 12, 2014
b1ea909
DOC: Example tweaks.
arokem Dec 13, 2014
4f371fb
PEP8, and addressing comments in example.
arokem Dec 13, 2014
248474c
PEP8, etc.
arokem Dec 13, 2014
4ef15c2
PEP8: Whitespace.
arokem Dec 13, 2014
2fa5ffc
PEP8 + pyflakes
arokem Dec 14, 2014
03fbdfa
PEP8 and pyflakes
arokem Dec 14, 2014
d92274e
RF: More informative variable name.
arokem Dec 14, 2014
3472d9b
PEP8 + pyflakes
arokem Dec 14, 2014
36d8b37
PEP8
arokem Dec 14, 2014
c3f99f6
DOC: No need for this call to 'show'
arokem Dec 14, 2014
8cdde64
RF: Sometimes pyflakes can be rather misleading.
arokem Dec 14, 2014
9741a19
PEP8: No caps in function names
arokem Dec 14, 2014
0979ca4
DOC: Include SFM example in docs.
arokem Dec 14, 2014
7945229
DOC: Small typo in SNR example.
arokem Dec 14, 2014
2c1152f
DOC: Small typo in simulation doc
arokem Dec 14, 2014
c979ed4
DOC: More work on SFM examples.
arokem Dec 16, 2014
cc55cf3
DOC: Make this example go.
arokem Dec 16, 2014
3319a60
DOC: Put the SFM examples in valid_examples and examples_index.
arokem Dec 16, 2014
c149a48
DOC: PEP8 + pyflakes
arokem Dec 16, 2014
fcc6e8e
DOC: PEP8 + pyflakes.
arokem Dec 16, 2014
8f15f36
DOC: Move streamlines to T1.
arokem Dec 16, 2014
7b66242
DOC: Small pyflake.
arokem Dec 16, 2014
9647bc8
DOC: typo and whitespace.
arokem Dec 16, 2014
0508a7e
DOC: SFM tracking
arokem Dec 16, 2014
f9209cd
DOC: Make it run parallel.
arokem Dec 16, 2014
d1b03bb
DOC: Subselect streamlines
arokem Dec 17, 2014
558ebf7
DOC: Need to provide the number of output streamlines.
arokem Dec 17, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Expand Up @@ -23,6 +23,9 @@ matrix:
env:
- COVERAGE=1
- DEPENDS="cython==0.18 numpy==1.6.0 scipy==0.9.0 nibabel==1.2.0"
- python: 2.7
env:
- DEPENDS="cython numpy scipy matplotlib h5py nibabel cvxopt scikit_learn"
before_install:
- virtualenv --python=python venv
- source venv/bin/activate
Expand Down
86 changes: 59 additions & 27 deletions dipy/core/optimize.py
Expand Up @@ -3,11 +3,13 @@
Only L-BFGS-B and Powell is supported in this class for versions of
Scipy < 0.12. All optimizers are available for scipy >= 0.12.
"""

import abc
from distutils.version import LooseVersion
import numpy as np
import scipy
import scipy.sparse as sps
import scipy.optimize as opt
from dipy.utils.six import with_metaclass

SCIPY_LESS_0_12 = LooseVersion(scipy.__version__) < '0.12'

Expand Down Expand Up @@ -218,7 +220,6 @@ def __init__(self, fun, x0, args=(), method='L-BFGS-B', jac=None,

def history_of_x(kx):
self._evol_kx.append(kx)

res = minimize(fun, x0, args, method, jac, hess, hessp, bounds,
constraints, tol, callback=history_of_x,
options=options)
Expand Down Expand Up @@ -293,24 +294,6 @@ def spdot(A, B):
return np.dot(A, B)


def rsq(ss_residuals, ss_residuals_to_mean):
"""
Calculate: $R^2 = \frac{1-SSE}{\sigma^2}$

Parameters
----------
ss_residuals : array
Model fit errors relative to the data
ss_residuals_to_mean : array
Residuals of the data relative to the mean of the data (variance)

Returns
-------
rsq : the variance explained.
"""
return 100 * (1 - ss_residuals/ss_residuals_to_mean)


def sparse_nnls(y, X,
momentum=1,
step_size=0.01,
Expand Down Expand Up @@ -357,17 +340,14 @@ def sparse_nnls(y, X,
h_best : The best estimate of the parameters.

"""
num_data = y.shape[0]
num_regressors = X.shape[1]
# Initialize the parameters at the origin:
h = np.zeros(num_regressors)
# If nothing good happens, we'll return that:
h_best = h
gradient = np.zeros(num_regressors)
iteration = 1
count = 1
ss_residuals_min = np.inf # This will keep track of the best solution
ss_residuals_to_mean = np.sum((y - np.mean(y)) ** 2) # The variance of y
sse_best = np.inf # This will keep track of the best performance so far
count_bad = 0 # Number of times estimation error has gone up.
error_checks = 0 # How many error checks have we done so far
Expand Down Expand Up @@ -396,21 +376,73 @@ def sparse_nnls(y, X,
if sse < ss_residuals_min:
# Update your expectations about the minimum error:
ss_residuals_min = sse
n_iterations = iteration # This holds the number of iterations
# for the best solution so far.
h_best = h # This holds the best params we have so far

# Are we generally (over iterations) converging on
# sufficient improvement in r-squared?
if sse < converge_on_sse * sse_best:
sse_best = sse
count_bad = 0
else:
count_bad +=1
count_bad += 1
else:
count_bad += 1

if count_bad >= max_error_checks:
return h_best
error_checks += 1
iteration += 1


class SKLearnLinearSolver(with_metaclass(abc.ABCMeta, object)):
"""
Provide a sklearn-like uniform interface to algorithms that solve problems
of the form: $y = Ax$ for $x$

Sub-classes of SKLearnLinearSolver should provide a 'fit' method that have
the following signature: `SKLearnLinearSolver.fit(X, y)`, which would set
an attribute `SKLearnLinearSolver.coef_`, with the shape (X.shape[1],),
such that an estimate of y can be calculated as:
`y_hat = np.dot(X, SKLearnLinearSolver.coef_.T)`
"""
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs

@abc.abstractmethod
def fit(self, X, y):
"""Implement for all derived classes """

def predict(self, X):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not also make this an abstract method?

"""
Predict using the result of the model

Parameters
----------
X : array-like (n_samples, n_features)
Samples.

Returns
-------
C : array, shape = (n_samples,)
Predicted values.
"""
X = np.asarray(X)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the custom implementation, instead of using the scikit-learn solver's predict method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It requires scikit-learn, and we're trying to deal with situations where that's not installed. Though, why wouldn't it?

return np.dot(X, self.coef_.T)


class NonNegativeLeastSquares(SKLearnLinearSolver):
"""
A sklearn-like interface to scipy.optimize.nnls

"""
def fit(self, X, y):
"""
Fit the NonNegativeLeastSquares linear model to data

Parameters
----------

"""
coef, rnorm = opt.nnls(X, y)
self.coef_ = coef
return self
98 changes: 58 additions & 40 deletions dipy/core/tests/test_optimize.py
@@ -1,50 +1,43 @@
import numpy as np
import scipy.sparse as sps
from numpy.testing import (assert_equal,
assert_almost_equal,
assert_array_almost_equal,
assert_array_equal,
run_module_suite)

import numpy.testing as npt
from dipy.core.optimize import Optimizer, SCIPY_LESS_0_12, sparse_nnls, spdot
import dipy.core.optimize as opt


def func(x):

return x[0]**2 + x[1]**2 + x[2]**2


def func2(x):

return x[0]**2 + 0.5 * x[1]**2 + 0.2 * x[2]**2 + 0.2 * x[3]**2


@npt.dec.skipif(SCIPY_LESS_0_12)
def test_optimize_new_scipy():

opt = Optimizer(fun=func, x0=np.array([1., 1., 1.]), method='Powell')

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
assert_almost_equal(opt.fopt, 0)
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
npt.assert_almost_equal(opt.fopt, 0)

opt = Optimizer(fun=func, x0=np.array([1., 1., 1.]), method='L-BFGS-B',
options={'maxcor': 10, 'ftol': 1e-7,
'gtol': 1e-5, 'eps': 1e-8})

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
assert_almost_equal(opt.fopt, 0)
assert_equal(opt.evolution, None)
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
npt.assert_almost_equal(opt.fopt, 0)
npt.assert_equal(opt.evolution, None)

assert_equal(opt.evolution, None)
npt.assert_equal(opt.evolution, None)

opt = Optimizer(fun=func, x0=np.array([1., 1., 1.]), method='L-BFGS-B',
options={'maxcor': 10, 'ftol': 1e-7,
'gtol': 1e-5, 'eps': 1e-8},
evolution=False)

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
assert_almost_equal(opt.fopt, 0)
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
npt.assert_almost_equal(opt.fopt, 0)

opt.print_summary()

Expand All @@ -54,14 +47,14 @@ def test_optimize_new_scipy():
'gtol': 1e-5, 'eps': 1e-8},
evolution=True)

assert_equal(opt.evolution.shape, (opt.nit, 4))
npt.assert_equal(opt.evolution.shape, (opt.nit, 4))

opt = Optimizer(fun=func2, x0=np.array([1., 1., 1., 5.]),
method='Powell',
options={'xtol': 1e-6, 'ftol': 1e-6, 'maxiter': 1e6},
evolution=True)

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))


@npt.dec.skipif(not SCIPY_LESS_0_12)
Expand All @@ -72,80 +65,105 @@ def test_optimize_old_scipy():
options={'maxcor': 10, 'ftol': 1e-7,
'gtol': 1e-5, 'eps': 1e-8})

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
assert_almost_equal(opt.fopt, 0)
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
npt.assert_almost_equal(opt.fopt, 0)

opt = Optimizer(fun=func2, x0=np.array([1., 1., 1., 5.]),
method='Powell',
options={'xtol': 1e-6, 'ftol': 1e-6, 'maxiter': 1e6},
evolution=True)

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))

opt = Optimizer(fun=func, x0=np.array([1., 1., 1.]),
method='L-BFGS-B',
options={'maxcor': 10, 'eps': 1e-8})

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
assert_almost_equal(opt.fopt, 0)
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
npt.assert_almost_equal(opt.fopt, 0)

opt = Optimizer(fun=func, x0=np.array([1., 1., 1.]),
method='L-BFGS-B',
options=None)

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
assert_almost_equal(opt.fopt, 0)
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0]))
npt.assert_almost_equal(opt.fopt, 0)

opt = Optimizer(fun=func2, x0=np.array([1., 1., 1., 5.]),
method='L-BFGS-B',
options={'gtol': 1e-7, 'ftol': 1e-7, 'maxiter': 10000})

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]), 4)
assert_almost_equal(opt.fopt, 0)
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]), 4)
npt.assert_almost_equal(opt.fopt, 0)

opt = Optimizer(fun=func2, x0=np.array([1., 1., 1., 5.]),
method='Powell',
options={'maxiter': 1e6},
evolution=True)

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))

opt = Optimizer(fun=func2, x0=np.array([1., 1., 1., 5.]),
method='Powell',
options={'maxiter': 1e6},
evolution=True)

assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))
npt.assert_array_almost_equal(opt.xopt, np.array([0, 0, 0, 0.]))


def test_sklearn_linear_solver():
class SillySolver(opt.SKLearnLinearSolver):
def fit(self, X, y):
self.coef_ = np.ones(X.shape[-1])

MySillySolver = SillySolver()
n_samples = 100
n_features = 20
y = np.random.rand(n_samples)
X = np.ones((n_samples, n_features))
MySillySolver.fit(X, y)
npt.assert_equal(MySillySolver.coef_, np.ones(n_features))
npt.assert_equal(MySillySolver.predict(X), np.ones(n_samples) * 20)


def test_nonnegativeleastsquares():
n = 100
X = np.eye(n)
beta = np.random.rand(n)
y = np.dot(X, beta)
my_nnls = opt.NonNegativeLeastSquares()
my_nnls.fit(X, y)
npt.assert_equal(my_nnls.coef_, beta)
npt.assert_equal(my_nnls.predict(X), y)


def test_spdot():
n = 100
m = 20
k = 10
A = np.random.randn(n,m)
B = np.random.randn(m,k)
A = np.random.randn(n, m)
B = np.random.randn(m, k)
A_sparse = sps.csr_matrix(A)
B_sparse = sps.csr_matrix(B)

dense_dot = np.dot(A, B)
# Try all the different variations:
assert_array_almost_equal(dense_dot, spdot(A_sparse, B_sparse).todense())
assert_array_almost_equal(dense_dot, spdot(A, B_sparse))
assert_array_almost_equal(dense_dot, spdot(A_sparse, B))
npt.assert_array_almost_equal(dense_dot,
spdot(A_sparse, B_sparse).todense())
npt.assert_array_almost_equal(dense_dot, spdot(A, B_sparse))
npt.assert_array_almost_equal(dense_dot, spdot(A_sparse, B))


def test_nnls():
def test_sparse_nnls():
# Set up the regression:
beta = np.random.rand(10)
X = np.random.randn(1000, 10)
y = np.dot(X, beta)
beta_hat = sparse_nnls(y, X)
beta_hat_sparse = sparse_nnls(y, sps.csr_matrix(X))
# We should be able to get back the right answer for this simple case
assert_array_almost_equal(beta, beta_hat, decimal=1)
assert_array_almost_equal(beta, beta_hat_sparse, decimal=1)
npt.assert_array_almost_equal(beta, beta_hat, decimal=1)
npt.assert_array_almost_equal(beta, beta_hat_sparse, decimal=1)


if __name__ == '__main__':

run_module_suite()
npt.run_module_suite()