├── decompose ├── __init__.py ├── cv │ ├── __init__.py │ └── cv.py ├── models │ ├── __init__.py │ └── tests │ │ └── __init__.py ├── postU │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_postU.py │ └── postU.py ├── tests │ ├── __init__.py │ ├── fixtures.py │ ├── test_sklearn.py │ ├── test_sklearn_tensor.py │ ├── test_sklearn_cv.py │ ├── test_sklearn_all_het_noise.py │ ├── test_sklearn_het_noise.py │ ├── test_sklearn_cv_tensor.py │ └── test_all_priors.py ├── likelihoods │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_normal2dLikelihood.py │ ├── likelihood.py │ ├── normal2dLikelihood.py │ ├── specificNormal2dLikelihood.py │ ├── allSpecificNormal2dLikelihood.py │ ├── normalNdLikelihood.py │ ├── cvNormal2dLikelihood.py │ └── cvNormalNdLikelihood.py ├── stopCriterions │ ├── __init__.py │ ├── nIterations.py │ ├── stopCriterion.py │ ├── llhImprovementThreshold.py │ └── llhStall.py ├── distributions │ ├── tests │ │ ├── __init__.py │ │ ├── test_cenNormalAlgorithms.py │ │ ├── test_cenLaplaceAlgorithms.py │ │ ├── test_cenNnNormalAlgorithms.py │ │ ├── test_cenTAlgorithms.py │ │ ├── test_cenNnFullyElasticNetAlgorithms.py │ │ ├── test_exponentialAlgorithms.py │ │ ├── test_laplaceAlgorithms.py │ │ ├── test_normalAlgorithms.py │ │ └── test_nnNormalAlgorithms.py │ ├── __init__.py │ ├── properties.py │ ├── cenNormalRankOneHeuristicAlgorithms.py │ ├── algorithms.py │ ├── uniformAlgorithms.py │ ├── normalNormal.py │ ├── cenNnElasticNetAlgorithms.py │ ├── nnUniformAlgorithms.py │ ├── normalUniform.py │ ├── normalExponential.py │ ├── normalNnUniform.py │ ├── cenNormal.py │ ├── cenNnNormal.py │ ├── cenLaplace.py │ ├── cenT.py │ ├── normalLaplace.py │ ├── normalNnNormal.py │ ├── normalAlgorithms.py │ ├── product.py │ ├── laplaceAlgorithms.py │ ├── cenNnNormalAlgorithms.py │ ├── cenNormalAlgorithms.py │ ├── cenLaplaceAlgorithms.py │ ├── normalCenNnFullyElasticNetCond.py │ ├── uniform.py │ ├── exponential.py │ ├── nnUniform.py │ ├── cenNnTAlgorithms.py │ ├── cenDoubleLomaxAlgorithms.py │ ├── exponentialAlgorithms.py │ ├── normal.py │ ├── nnNormal.py │ ├── laplace.py │ ├── cenNormalRankOne.py │ ├── jumpNormal.py │ ├── cenTAlgorithms.py │ ├── lomax.py │ ├── cenDoubleLomax.py │ ├── cenNnT.py │ ├── productDistLookup.py │ ├── cenNnFullyElasticNetCond.py │ ├── tAlgorithms.py │ ├── t.py │ ├── tfppf.py │ ├── cenNormalRankOneAlgorithms.py │ ├── lomaxAlgorithms.py │ ├── cenNnFullyElasticNet.py │ ├── nnNormalAlgorithms.py │ └── jumpNormalAlgorithms.py ├── version.py └── data │ ├── __init__.py │ └── random.py ├── setup.cfg ├── .travis.yml ├── docs ├── source │ ├── _templates │ │ └── github.html │ ├── index.rst │ └── quick-start.rst └── Makefile ├── LICENSE ├── README.md ├── .gitignore └── setup.py /decompose/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/cv/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/postU/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/likelihoods/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/postU/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/models/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/stopCriterions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/distributions/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /decompose/likelihoods/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /decompose/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.2-dev' 2 | -------------------------------------------------------------------------------- /decompose/data/__init__.py: -------------------------------------------------------------------------------- 1 | """Provide example data.""" 2 | -------------------------------------------------------------------------------- /decompose/distributions/__init__.py: -------------------------------------------------------------------------------- 1 | """Provide distributions.""" 2 | -------------------------------------------------------------------------------- /decompose/distributions/properties.py: -------------------------------------------------------------------------------- 1 | from decompose.distributions.distribution import DrawType, UpdateType 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | install: 5 | - pip install . 6 | - pip install .[test] 7 | script: 8 | - pytest -m "not slow" 9 | -------------------------------------------------------------------------------- /docs/source/_templates/github.html: -------------------------------------------------------------------------------- 1 |
2 | 4 |
5 | -------------------------------------------------------------------------------- /decompose/tests/fixtures.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import tensorflow as tf 3 | 4 | 5 | @pytest.fixture(scope="module", 6 | params=["/cpu:0"]) 7 | def device(request): 8 | device = request.param 9 | with tf.device(device): 10 | yield device 11 | 12 | 13 | @pytest.fixture(scope="module", 14 | params=[tf.float32, tf.float64]) 15 | def dtype(request): 16 | dtype = request.param 17 | return(dtype) 18 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Decompose documentation master file, created by 2 | sphinx-quickstart on Sun Apr 8 01:26:41 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Decompose 7 | ========= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | quick-start 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = Decompose 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /decompose/distributions/cenNormalRankOneHeuristicAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.cenNormalRankOneAlgorithms import CenNormalRankOneAlgorithms 7 | 8 | 9 | class CenNormalRankOneHeuristicAlgorithms(CenNormalRankOneAlgorithms): 10 | 11 | @classmethod 12 | def fit(cls, parameters: Dict[str, Tensor], 13 | data: tf.Tensor) -> Dict[str, Tensor]: 14 | """Non-optimal update Using an heuristic.""" 15 | 16 | var0 = tf.sqrt(tf.reduce_mean(data**2, axis=1)) 17 | var1 = tf.sqrt(tf.reduce_mean(data**2, axis=0)) 18 | c = tf.reduce_mean(data**2)/tf.reduce_mean(tf.matmul(var0[..., None], 19 | var1[None, ...])) 20 | tau0 = 1./(var0*tf.sqrt(c)) 21 | tau1 = 1./(var1*tf.sqrt(c)) 22 | 23 | updatedParameters = {"tau0": tau0, "tau1": tau1} 24 | return(updatedParameters) 25 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_cenNormalAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.cenNormalAlgorithms import CenNormalAlgorithms 8 | 9 | 10 | @pytest.mark.slow 11 | def test_cenNormal_fit(): 12 | """Test if the fitted parameters match the true parameters.""" 13 | tau = np.array([0.5, 1., 2.]) 14 | nSamples = 100000 15 | 16 | nParameters = tau.shape[0] 17 | norm = sp.stats.norm(scale=1./np.sqrt(tau)) 18 | data = norm.rvs(size=(nSamples, nParameters)) 19 | parameters = {"tau": tf.constant(np.ones(nParameters))} 20 | tfData = tf.constant(data) 21 | parameters = CenNormalAlgorithms.fit(parameters=parameters, 22 | data=tfData) 23 | 24 | with tf.Session() as sess: 25 | parameters = sess.run(parameters) 26 | 27 | tauHat = parameters["tau"] 28 | assert(tauHat.shape == tau.shape) 29 | assert(np.allclose(tauHat, tau, atol=1e-1)) 30 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_cenLaplaceAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.cenLaplaceAlgorithms import CenLaplaceAlgorithms 8 | 9 | 10 | @pytest.mark.slow 11 | def test_cenLaplace_fit(): 12 | """Test if the fitted parameters match the true parameters.""" 13 | beta = np.array([0.5, 1., 2.]) 14 | nSamples = 100000 15 | 16 | nParameters = beta.shape[0] 17 | norm = sp.stats.laplace(scale=beta) 18 | data = norm.rvs(size=(nSamples, nParameters)) 19 | parameters = {"beta": tf.constant(np.ones(nParameters))} 20 | tfData = tf.constant(data) 21 | parameters = CenLaplaceAlgorithms.fit(parameters=parameters, 22 | data=tfData) 23 | 24 | with tf.Session() as sess: 25 | parameters = sess.run(parameters) 26 | 27 | betaHat = parameters["beta"] 28 | assert(betaHat.shape == beta.shape) 29 | assert(np.allclose(betaHat, beta, atol=1e-1)) 30 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_cenNnNormalAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.cenNnNormalAlgorithms import CenNnNormalAlgorithms 8 | 9 | 10 | @pytest.mark.slow 11 | def test_cenNnNormal_fit(): 12 | """Test if the fitted parameters match the true parameters.""" 13 | tau = np.array([0.5, 1., 2.]) 14 | nSamples = 100000 15 | 16 | nParameters = tau.shape[0] 17 | norm = sp.stats.norm(scale=1./np.sqrt(tau)) 18 | data = np.abs(norm.rvs(size=(nSamples, nParameters))) 19 | parameters = {"tau": tf.constant(np.ones(nParameters))} 20 | tfData = tf.constant(data) 21 | parameters = CenNnNormalAlgorithms.fit(parameters=parameters, 22 | data=tfData) 23 | 24 | with tf.Session() as sess: 25 | parameters = sess.run(parameters) 26 | 27 | tauHat = parameters["tau"] 28 | assert(tauHat.shape == tau.shape) 29 | assert(np.allclose(tauHat, tau, atol=1e-1)) 30 | -------------------------------------------------------------------------------- /decompose/distributions/algorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | 6 | class Algorithms(object): 7 | 8 | def __init__(self): 9 | return 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | # here the sample axis is postpended unlike tf where it is postpend 14 | ... 15 | 16 | @classmethod 17 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 18 | ... 19 | 20 | @classmethod 21 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 22 | ... 23 | 24 | @classmethod 25 | def fit(cls, parameters: Dict[str, Tensor], 26 | data: Tensor) -> Dict[str, Tensor]: 27 | ... 28 | 29 | @classmethod 30 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> float: 31 | ... 32 | 33 | @classmethod 34 | def fitLatents(cls, parameters: Dict[str, Tensor], 35 | data: Tensor) -> Dict[str, Tensor]: 36 | ... 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bethge Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /decompose/distributions/uniformAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.algorithms import Algorithms 6 | 7 | 8 | class UniformAlgorithms(Algorithms): 9 | 10 | @classmethod 11 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 12 | raise ValueError 13 | 14 | @classmethod 15 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 16 | raise ValueError 17 | 18 | @classmethod 19 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 20 | raise ValueError 21 | 22 | @classmethod 23 | def fit(cls, parameters: Dict[str, Tensor], 24 | data: tf.Tensor) -> Dict[str, Tensor]: 25 | updatedParameters = {} # Dict[str, Tensor] 26 | return(updatedParameters) 27 | 28 | @classmethod 29 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> float: 30 | return(tf.zeros_like(data)) 31 | 32 | @classmethod 33 | def fitLatents(cls, parameters: Dict[str, Tensor], 34 | data: Tensor) -> Dict[str, Tensor]: 35 | return({}) 36 | -------------------------------------------------------------------------------- /decompose/distributions/normalNormal.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | import numpy as np 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.normal import Normal 7 | from decompose.distributions.product import Product 8 | 9 | 10 | class NormalNormal(Product): 11 | 12 | def fromUnordered(self, d0: Distribution, 13 | d1: Distribution) -> Normal: 14 | 15 | if isinstance(d0, Normal) and isinstance(d1, Normal): 16 | return(self.product(d0, d1)) 17 | else: 18 | raise ValueError("Expecting Normal and Normal") 19 | 20 | def product(self, n0: Normal, n1: Normal) -> Normal: 21 | mu = self.mu(n0, n1) 22 | tau = self.tau(n0, n1) 23 | otherParams = self.productParams(n0, n1) 24 | pd = Normal(mu=mu, tau=tau, **otherParams) 25 | return(pd) 26 | 27 | def mu(self, n0, n1) -> Tensor: 28 | mu0, tau0 = n0.mu, n0.tau 29 | mu1, tau1 = n1.mu, n1.tau 30 | tau = self.tau(n0, n1) 31 | mu = (mu0*tau0 + mu1*tau1)/tau 32 | return(mu) 33 | 34 | def tau(self, n0, n1) -> Tensor: 35 | tau = n0.tau + n1.tau 36 | return(tau) 37 | -------------------------------------------------------------------------------- /decompose/distributions/cenNnElasticNetAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.nnNormalAlgorithms import NnNormalAlgorithms 6 | 7 | 8 | class CenNnElasticNetAlgorithms(NnNormalAlgorithms): 9 | 10 | @classmethod 11 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 12 | mode = tf.zeros_like(parameters["mu"]) 13 | return(mode) 14 | 15 | @classmethod 16 | def fit(cls, parameters: Dict[str, Tensor], 17 | data: tf.Tensor) -> Dict[str, Tensor]: 18 | # get non-negative normal fit 19 | nnNormalParameters = NnNormalAlgorithms.fit(parameters=parameters, 20 | data=data) 21 | mu, tau = nnNormalParameters["mu"], nnNormalParameters["tau"] 22 | 23 | # project mus on the non-positive interval and update taus accordingly 24 | projectMuToZero = tf.greater(mu, 0) 25 | mu = tf.where(projectMuToZero, tf.zeros_like(mu), mu) 26 | tauGivenMuIsZero = 1./tf.reduce_mean(data**2, axis=0) 27 | tau = tf.where(projectMuToZero, tauGivenMuIsZero, tau) 28 | updatedParameters = {"mu": mu, "tau": tau} 29 | 30 | return(updatedParameters) 31 | -------------------------------------------------------------------------------- /decompose/stopCriterions/nIterations.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from tensorflow import Tensor 3 | 4 | from decompose.stopCriterions.stopCriterion import StopCriterion 5 | 6 | 7 | class NIterations(StopCriterion): 8 | def __init__(self, nIterations=100): 9 | self.__nIterations = nIterations 10 | 11 | def init(self, ns: str = "stopCriterion") -> None: 12 | self.__ns = ns 13 | with tf.variable_scope(self.__ns): 14 | iterationNumberVar = tf.get_variable("iterationNumber", 15 | dtype=tf.int32, 16 | initializer=0) 17 | self.iterationNumberVar = iterationNumberVar 18 | stopVar = tf.get_variable("stop", 19 | dtype=tf.bool, 20 | initializer=False) 21 | self.__stopVar = stopVar 22 | 23 | def update(self, model, X: Tensor): 24 | u0 = tf.assign(self.iterationNumberVar, self.iterationNumberVar + 1) 25 | u1 = tf.assign(self.stopVar, tf.greater_equal(self.iterationNumberVar, 26 | self.__nIterations)) 27 | return([u0, u1]) 28 | 29 | @property 30 | def stopVar(self): 31 | return(self.__stopVar) 32 | -------------------------------------------------------------------------------- /decompose/stopCriterions/stopCriterion.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | 6 | class StopCriterion(ABC): 7 | 8 | @abstractmethod 9 | def init(self, ns: str) -> None: 10 | ... 11 | 12 | 13 | class NoStop(StopCriterion): 14 | def __init__(self): 15 | pass 16 | 17 | def init(self, ns="stopCriterion"): 18 | self.__ns = ns 19 | with tf.variable_scope(self.__ns): 20 | stopVar = tf.get_variable("stop", 21 | dtype=tf.bool, 22 | initializer=False) 23 | self.__stopVar = stopVar 24 | 25 | def update(self, model, X: Tensor): 26 | return([]) 27 | 28 | @property 29 | def stopVar(self) -> tf.Variable: 30 | return(self.__stopVar) 31 | 32 | 33 | class StopHook(tf.train.SessionRunHook): 34 | def after_run(self, run_context, run_values): 35 | with tf.variable_scope("", reuse=tf.AUTO_REUSE): 36 | stopVar = tf.get_variable("stopCriterion/stop", 37 | dtype=tf.bool, 38 | shape=()) 39 | condition = run_context.session.run(stopVar) 40 | if condition: 41 | run_context.request_stop() 42 | -------------------------------------------------------------------------------- /decompose/distributions/nnUniformAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | 8 | 9 | class NnUniformAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | raise ValueError 14 | 15 | @classmethod 16 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 17 | raise ValueError 18 | 19 | @classmethod 20 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 21 | raise ValueError 22 | 23 | @classmethod 24 | def fit(cls, parameters: Dict[str, Tensor], 25 | data: tf.Tensor) -> Dict[str, Tensor]: 26 | updatedParameters = {} # Dict[str, Tensor] 27 | return(updatedParameters) 28 | 29 | @classmethod 30 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> float: 31 | llh = tf.where(tf.less(data, 0.), 32 | -tf.ones_like(data)*np.inf, 33 | tf.zeros_like(data)) 34 | return(llh) 35 | 36 | @classmethod 37 | def fitLatents(cls, parameters: Dict[str, Tensor], 38 | data: Tensor) -> Dict[str, Tensor]: 39 | return({}) 40 | -------------------------------------------------------------------------------- /decompose/distributions/normalUniform.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | import tensorflow as tf 3 | import numpy as np 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.normal import Normal 8 | from decompose.distributions.uniform import Uniform 9 | from decompose.distributions.product import Product 10 | 11 | 12 | class NormalUniform(Product): 13 | 14 | def fromUnordered(self, d0: Distribution, 15 | d1: Distribution) -> Normal: 16 | if isinstance(d0, Normal) and isinstance(d1, Uniform): 17 | return(self.product(d0, d1)) 18 | elif isinstance(d1, Normal) and isinstance(d0, Uniform): 19 | return(self.product(d1, d0)) 20 | else: 21 | raise ValueError("Expecting Normal and Uniform") 22 | 23 | def product(self, n: Normal, u: Uniform) -> Normal: 24 | mu = self.mu(n, u) 25 | tau = self.tau(n, u) 26 | otherParams = self.productParams(n, u) 27 | return(Normal(mu=mu, tau=tau, **otherParams)) 28 | 29 | def mu(self, n, u) -> Tensor: 30 | ones = tf.ones(u.shape, dtype=n.mu.dtype) 31 | return(n.mu*ones) 32 | 33 | def tau(self, n, u) -> Tensor: 34 | ones = tf.ones(u.shape, dtype=n.mu.dtype) 35 | return(n.tau*ones) 36 | -------------------------------------------------------------------------------- /decompose/distributions/normalExponential.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.normal import Normal 7 | from decompose.distributions.nnNormal import NnNormal 8 | from decompose.distributions.exponential import Exponential 9 | from decompose.distributions.product import Product 10 | 11 | 12 | class NormalExponential(Product): 13 | 14 | def fromUnordered(self, d0: Distribution, 15 | d1: Distribution) -> NnNormal: 16 | if isinstance(d0, Normal) and isinstance(d1, Exponential): 17 | return(self.product(d0, d1)) 18 | elif isinstance(d1, Normal) and isinstance(d0, Exponential): 19 | return(self.product(d1, d0)) 20 | else: 21 | raise ValueError("Expecting Normal and Exponential") 22 | 23 | def product(self, n: Normal, e: Exponential) -> NnNormal: 24 | mu = self.mu(n, e) 25 | tau = self.tau(n, e) 26 | otherParams = self.productParams(n, e) 27 | return(NnNormal(mu=mu, tau=tau, **otherParams)) 28 | 29 | def mu(self, n: Normal, e: Exponential) -> Tensor: 30 | mu = n.mu - 1. / (n.tau * e.beta) 31 | return(mu) 32 | 33 | def tau(self, n: Normal, e: Exponential) -> Tensor: 34 | tau = n.tau * tf.ones_like(e.beta) 35 | return(tau) 36 | -------------------------------------------------------------------------------- /decompose/distributions/normalNnUniform.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | import tensorflow as tf 3 | import numpy as np 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.normal import Normal 8 | from decompose.distributions.nnNormal import NnNormal 9 | from decompose.distributions.nnUniform import NnUniform 10 | from decompose.distributions.product import Product 11 | 12 | 13 | class NormalNnUniform(Product): 14 | 15 | def fromUnordered(self, d0: Distribution, 16 | d1: Distribution) -> NnNormal: 17 | if isinstance(d0, Normal) and isinstance(d1, NnUniform): 18 | return(self.product(d0, d1)) 19 | elif isinstance(d1, Normal) and isinstance(d0, NnUniform): 20 | return(self.product(d1, d0)) 21 | else: 22 | raise ValueError("Expecting Normal and NnUniform") 23 | 24 | def product(self, n: Normal, u: NnUniform) -> NnNormal: 25 | mu = self.mu(n, u) 26 | tau = self.tau(n, u) 27 | otherParams = self.productParams(n, u) 28 | return(NnNormal(mu=mu, tau=tau, **otherParams)) 29 | 30 | def mu(self, n, u) -> Tensor: 31 | ones = tf.ones(u.shape, dtype=n.mu.dtype) 32 | return(n.mu*ones) 33 | 34 | def tau(self, n, u) -> Tensor: 35 | ones = tf.ones(u.shape, dtype=n.mu.dtype) 36 | return(n.tau*ones) 37 | -------------------------------------------------------------------------------- /docs/source/quick-start.rst: -------------------------------------------------------------------------------- 1 | Quick start 2 | =========== 3 | 4 | Installation 5 | ------------ 6 | 7 | Decompose demands python 3.6 and tensorflow 1.7. The newest gibhub code of decompose can be installed using pip: 8 | 9 | .. code-block:: bash 10 | 11 | pip3 install git+https://github.com/bethgelab/decompose 12 | 13 | 14 | Example 15 | ------- 16 | 17 | Decompose provides an interface that is similar to the interface of scikit-learn: 18 | 19 | .. code-block:: python 20 | 21 | import numpy as np 22 | from sklearn.datasets import make_low_rank_matrix 23 | 24 | from decompose.sklearn import DECOMPOSE 25 | from decompose.distributions.cenNormal import CenNormal 26 | 27 | 28 | # create a numpy array containing a synthetic low rank dataset 29 | X = make_low_rank_matrix(n_samples=1000, n_features=1000, 30 | effective_rank=3, tail_strength=0.5) 31 | 32 | # create an instance of a decompose model 33 | model = DECOMPOSE(modelDirectory="/tmp/myNewModel", 34 | priors=[CenNormal(), CenNormal()], 35 | n_components=3) 36 | 37 | # train the model and transform the training data 38 | U0 = model.fit_transform(X) 39 | 40 | # learned filter bank 41 | U1 = model.components_ 42 | 43 | # variance ratio of the sources 44 | varianceRatio = model.variance_ratio_ 45 | 46 | # reconstruction of the data 47 | XHat = np.dot(U0.T, U1) 48 | -------------------------------------------------------------------------------- /decompose/distributions/cenNormal.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Any, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.normal import Normal 8 | from decompose.distributions.algorithms import Algorithms 9 | from decompose.distributions.cenNormalAlgorithms import CenNormalAlgorithms 10 | from decompose.distributions.distribution import Properties 11 | 12 | 13 | class CenNormal(Normal): 14 | def __init__(self, 15 | algorithms: Type[Algorithms] = CenNormalAlgorithms, 16 | tau: Tensor = None, 17 | properties: Properties = None) -> None: 18 | parameters = {"tau": tau} 19 | Distribution.__init__(self, 20 | algorithms=algorithms, 21 | parameters=parameters, 22 | properties=properties) 23 | 24 | def parameterInfo(self, 25 | shape: Tuple[int, ...] = (1,), 26 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 27 | initializers = { 28 | "tau": (shape, True) 29 | } # type: Dict[str, Tensor] 30 | return(initializers) 31 | 32 | @property 33 | def mu(self) -> Tensor: 34 | mu = tf.zeros_like(self.tau) 35 | return(mu) 36 | -------------------------------------------------------------------------------- /decompose/distributions/cenNnNormal.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Any, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.nnNormal import NnNormal 8 | from decompose.distributions.algorithms import Algorithms 9 | from decompose.distributions.cenNnNormalAlgorithms import CenNnNormalAlgorithms 10 | from decompose.distributions.distribution import Properties 11 | 12 | 13 | class CenNnNormal(NnNormal): 14 | def __init__(self, 15 | algorithms: Type[Algorithms] = CenNnNormalAlgorithms, 16 | tau: Tensor = None, 17 | properties: Properties = None) -> None: 18 | parameters = {"tau": tau} 19 | Distribution.__init__(self, 20 | algorithms=algorithms, 21 | parameters=parameters, 22 | properties=properties) 23 | 24 | def parameterInfo(self, 25 | shape: Tuple[int, ...] = (1,), 26 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 27 | initializers = { 28 | "tau": (shape, True) 29 | } # type: Dict[str, Tensor] 30 | return(initializers) 31 | 32 | @property 33 | def mu(self) -> Tensor: 34 | mu = tf.zeros_like(self.tau) 35 | return(mu) 36 | -------------------------------------------------------------------------------- /decompose/data/random.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Callable 2 | import numpy as np 3 | from numpy import ndarray 4 | import tensorflow as tf 5 | 6 | 7 | class Random(object): 8 | """Create random data set useful for testing huge input data. 9 | 10 | Arguments: 11 | M: `Tuple[int, int]`, shape of the data. 12 | dtype: `type`, type of the training and test data. 13 | """ 14 | def __init__(self, 15 | M: Tuple[int, int] = (1000, 2000), 16 | dtype: type = np.float32) -> None: 17 | 18 | self.__M = M 19 | self.__dtype = tf.as_dtype(dtype) 20 | 21 | @property 22 | def input_fn(self) -> Callable: 23 | """Training data as a tensorflow `input_fn` function. 24 | 25 | The whole training matrix will be provided as a minibatch 26 | and repeated infinitely many times. 27 | 28 | Returns: 29 | `Callable` that can be passed to the `train` function of 30 | an `Estimator` function as `input_fn` argument to serve 31 | the training data. 32 | """ 33 | def f(): 34 | dataset = tf.data.Dataset.from_tensors( 35 | {"data": tf.random_uniform(self.__M, maxval=100, 36 | dtype=self.__dtype)}) 37 | dataset = dataset.repeat() 38 | return(dataset) 39 | return(f) 40 | 41 | @property 42 | def M(self): 43 | return(self.__M) 44 | -------------------------------------------------------------------------------- /decompose/distributions/cenLaplace.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Any, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.laplace import Laplace 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.cenLaplaceAlgorithms import CenLaplaceAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class CenLaplace(Laplace): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = CenLaplaceAlgorithms, 17 | beta: Tensor = None, 18 | properties: Properties = None) -> None: 19 | parameters = {"beta": beta} 20 | Distribution.__init__(self, 21 | algorithms=algorithms, 22 | parameters=parameters, 23 | properties=properties) 24 | 25 | def parameterInfo(self, 26 | shape: Tuple[int, ...] = (1,), 27 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 28 | initializers = { 29 | "beta": (shape, True) 30 | } # type: Dict[str, Tensor] 31 | return(initializers) 32 | 33 | @property 34 | def mu(self) -> Tensor: 35 | mu = tf.zeros_like(self.beta) 36 | return(mu) 37 | -------------------------------------------------------------------------------- /decompose/stopCriterions/llhImprovementThreshold.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.stopCriterions.stopCriterion import StopCriterion 6 | 7 | 8 | class LlhImprovementThreshold(StopCriterion): 9 | def __init__(self, lhImprovementThreshold: float = 1.) -> None: 10 | self.__lhImprovementThreshold = np.float64(lhImprovementThreshold) 11 | 12 | def init(self, ns: str = "stopCriterion") -> None: 13 | self.__ns = ns 14 | negInf = tf.constant(-np.inf, dtype=tf.float64) 15 | with tf.variable_scope(self.__ns): 16 | llhVar = tf.get_variable("llh", 17 | dtype=tf.float64, 18 | initializer=negInf) 19 | self.llhVar = llhVar 20 | stopVar = tf.get_variable("stop", 21 | dtype=tf.bool, 22 | initializer=False) 23 | self.__stopVar = stopVar 24 | 25 | def update(self, model, X: Tensor): 26 | llh = tf.cast(model.llh(X), tf.float64) 27 | llhOld = self.llhVar 28 | cond = tf.greater(self.llhImprovementThreshold, llh - llhOld) 29 | u0 = tf.assign(self.stopVar, cond) 30 | with tf.control_dependencies([u0]): 31 | u1 = tf.assign(self.llhVar, llh) 32 | return([u0, u1]) 33 | 34 | @property 35 | def stopVar(self) -> tf.Variable: 36 | return(self.__stopVar) 37 | 38 | @property 39 | def llhImprovementThreshold(self) -> float: 40 | return(self.__lhImprovementThreshold) 41 | -------------------------------------------------------------------------------- /decompose/distributions/cenT.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.t import T 9 | from decompose.distributions.distribution import parameterProperty 10 | from decompose.distributions.algorithms import Algorithms 11 | from decompose.distributions.cenTAlgorithms import CenTAlgorithms 12 | from decompose.distributions.distribution import Properties 13 | 14 | 15 | class CenT(T): 16 | def __init__(self, 17 | algorithms: Type[Algorithms] = CenTAlgorithms, 18 | Psi: Tensor = None, 19 | nu: Tensor = None, 20 | tau: Tensor = None, 21 | properties: Properties = None) -> None: 22 | parameters = {"Psi": Psi, "nu": nu, "tau": tau} 23 | Distribution.__init__(self, 24 | algorithms=algorithms, 25 | parameters=parameters, 26 | properties=properties) 27 | 28 | def parameterInfo(self, 29 | shape: Tuple[int, ...] = (1,), 30 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 31 | initializers = { 32 | "Psi": (shape, True), 33 | "nu": (shape, True), 34 | "tau": (latentShape + shape, True) 35 | } # type: Dict[str, Tensor] 36 | return(initializers) 37 | 38 | @parameterProperty 39 | def mu(self) -> Tensor: 40 | return(tf.zeros_like(self.Psi)) 41 | -------------------------------------------------------------------------------- /decompose/stopCriterions/llhStall.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.stopCriterions.stopCriterion import StopCriterion 6 | 7 | 8 | class LlhStall(StopCriterion): 9 | def __init__(self, nStalledIterationsThreshold: int = 100) -> None: 10 | self.__nStalledIterationsThrehold = nStalledIterationsThreshold 11 | 12 | def init(self, ns: str = "stopCriterion") -> None: 13 | self.__ns = ns 14 | llhsInit = -np.inf*tf.ones(self.__nStalledIterationsThrehold, 15 | dtype=tf.float64) 16 | with tf.variable_scope(self.__ns): 17 | llhsVar = tf.get_variable("llhs", 18 | dtype=tf.float64, 19 | initializer=llhsInit) 20 | self.llhsVar = llhsVar 21 | stopVar = tf.get_variable("stop", 22 | dtype=tf.bool, 23 | initializer=False) 24 | self.__stopVar = stopVar 25 | 26 | def update(self, model, X: Tensor): 27 | llh = tf.cast(model.llh(X), tf.float64) 28 | llhsVar = self.llhsVar 29 | llhsUpdated = tf.concat((llh[None], llhsVar[1:]), axis=0) 30 | llhsUpdated = tf.manip.roll(llhsUpdated, shift=1, axis=0) 31 | cond = tf.reduce_all(tf.greater_equal(llhsUpdated[0], llhsUpdated[1:])) 32 | u0 = tf.assign(self.stopVar, cond) 33 | with tf.control_dependencies([u0]): 34 | u1 = tf.assign(self.llhsVar, llhsUpdated) 35 | return([u0, u1]) 36 | 37 | @property 38 | def stopVar(self) -> tf.Variable: 39 | return(self.__stopVar) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decompose 2 | Blind source separation based on the probabilistic tensor factorisation framework 3 | 4 | ## Installation 5 | Decompose demands python 3.6 and tensorflow 1.7. The newest github code of decompose can be installed using pip: 6 | ```bash 7 | pip3 install git+https://github.com/bethgelab/decompose 8 | ``` 9 | 10 | ## Quick start 11 | Decompose provides an interface that is similar to the interface of scikit-learn: 12 | 13 | ```python 14 | import numpy as np 15 | from sklearn.datasets import make_low_rank_matrix 16 | 17 | from decompose.sklearn import DECOMPOSE 18 | from decompose.distributions.cenNormal import CenNormal 19 | 20 | 21 | # create a numpy array containing a synthetic low rank dataset 22 | X = make_low_rank_matrix(n_samples=1000, n_features=1000, 23 | effective_rank=3, tail_strength=0.5) 24 | 25 | # create an instance of a decompose model 26 | model = DECOMPOSE(modelDirectory="/tmp/myNewModel", 27 | priors=[CenNormal(), CenNormal()], 28 | n_components=3) 29 | 30 | # train the model and transform the training data 31 | U0 = model.fit_transform(X) 32 | 33 | # learned filter bank 34 | U1 = model.components_ 35 | 36 | # variance ratio of the sources 37 | varianceRatio = model.variance_ratio_ 38 | 39 | # reconstruction of the data 40 | XHat = np.dot(U0.T, U1) 41 | ``` 42 | 43 | ## Publication 44 | The publication linked to this repository is available on arXiv: 45 | [[1803.08882] Trace your sources in large-scale data: one ring to find them all](http://arxiv.org/abs/1803.08882) 46 | 47 | ## Version 48 | The repository is still in alpha stage and does not yet contain all the code used in the publication above. In the next days documentation and features will be added. 49 | -------------------------------------------------------------------------------- /decompose/distributions/normalLaplace.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.normal import Normal 8 | from decompose.distributions.laplace import Laplace 9 | from decompose.distributions.jumpNormal import JumpNormal 10 | from decompose.distributions.product import Product 11 | 12 | 13 | class NormalLaplace(Product): 14 | 15 | def fromUnordered(self, d0: Distribution, 16 | d1: Distribution) -> Distribution: 17 | if isinstance(d0, Normal) and isinstance(d1, Laplace): 18 | return(self.product(d0, d1)) 19 | elif isinstance(d1, Normal) and isinstance(d0, Laplace): 20 | return(self.product(d1, d0)) 21 | else: 22 | raise ValueError("Expecting Normal and Exponential") 23 | 24 | def product(self, n: Normal, l: Laplace) -> JumpNormal: 25 | mu = self.mu(n, l) 26 | tau = self.tau(n, l) 27 | nu = self.nu(n, l) 28 | beta = self.beta(n, l) 29 | 30 | otherParams = self.productParams(n, l) 31 | return(JumpNormal(mu=mu, tau=tau, nu=nu, beta=beta, 32 | **otherParams)) 33 | 34 | def mu(self, n: Normal, l: Laplace) -> Tensor: 35 | mu = n.mu * tf.ones_like(l.mu) 36 | return(mu) 37 | 38 | def tau(self, n: Normal, l: Laplace) -> Tensor: 39 | tau = n.tau * tf.ones_like(l.mu) 40 | return(tau) 41 | 42 | def nu(self, n: Normal, l: Laplace) -> Tensor: 43 | nu = l.mu * tf.ones_like(n.mu) 44 | return(nu) 45 | 46 | def beta(self, n: Normal, l: Laplace) -> Tensor: 47 | beta = l.beta * tf.ones_like(n.mu) 48 | return(beta) 49 | -------------------------------------------------------------------------------- /decompose/distributions/normalNnNormal.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | 4 | from decompose.distributions.distribution import Distribution 5 | from decompose.distributions.normal import Normal 6 | from decompose.distributions.nnNormal import NnNormal 7 | from decompose.distributions.product import Product 8 | 9 | 10 | class NormalNnNormal(Product): 11 | 12 | def fromUnordered(self, d0: Distribution, 13 | d1: Distribution) -> NnNormal: 14 | if isinstance(d0, Normal) and isinstance(d1, NnNormal): 15 | return(self.product(d0, d1)) 16 | elif isinstance(d1, Normal) and isinstance(d0, NnNormal): 17 | return(self.product(d1, d0)) 18 | else: 19 | raise ValueError("Expecting Normal and NnNormal") 20 | 21 | def product(self, n: Normal, nnn: NnNormal) -> NnNormal: 22 | mu = self.mu(n, nnn) 23 | tau = self.tau(n, nnn) 24 | otherParams = self.productParams(n, nnn) 25 | return(NnNormal(mu=mu, tau=tau, **otherParams)) 26 | 27 | def mu(self, n: Normal, nnn: NnNormal) -> np.ndarray: 28 | muN, tauN = n.mu, n.tau 29 | muNnn, tauNnn = nnn.mu, nnn.tau 30 | tau = tauN + tauNnn 31 | mu = (muN*tauN + muNnn*tauNnn)/tau 32 | return(mu) 33 | 34 | def tau(self, n: Normal, nnn: NnNormal) -> np.ndarray: 35 | tauN = n.tau 36 | tauNnn = nnn.tau 37 | tau = tauN + tauNnn 38 | 39 | assertTauIs0 = tf.Assert(tf.reduce_all(tf.logical_not(tf.equal(tau, 0.))), [tau], name='norNnNortauIs0') 40 | assertTauNotPos = tf.Assert(tf.reduce_all(tf.greater(tau, 0.)), [tau], name='norNnNorCenNotPositive') 41 | with tf.control_dependencies([assertTauIs0, assertTauNotPos]): 42 | tau = tau + 0. 43 | return(tau) 44 | -------------------------------------------------------------------------------- /decompose/distributions/normalAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | 8 | 9 | class NormalAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | mu, tau = parameters["mu"], parameters["tau"] 14 | norm = tf.distributions.Normal(loc=mu, scale=1./tf.sqrt(tau)) 15 | r = norm.sample(sample_shape=(nSamples,)) 16 | return(r) 17 | 18 | @classmethod 19 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 20 | mu = parameters["mu"] 21 | return(mu) 22 | 23 | @classmethod 24 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 25 | mu, tau = parameters["mu"], parameters["tau"] 26 | norm = tf.distributions.Normal(loc=mu, scale=tf.sqrt(1./tau)) 27 | pdf = norm.prob(value=data) 28 | return(pdf) 29 | 30 | @classmethod 31 | def fit(cls, parameters: Dict[str, Tensor], 32 | data: tf.Tensor) -> Dict[str, Tensor]: 33 | mu = tf.reduce_mean(data, axis=0) 34 | var = tf.reduce_mean((data-mu)**2, axis=0) 35 | tau = 1./var 36 | updatedParameters = {"mu": mu, "tau": tau} 37 | return(updatedParameters) 38 | 39 | @classmethod 40 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> float: 41 | mu, tau = parameters["mu"], parameters["tau"] 42 | norm = tf.distributions.Normal(loc=mu, scale=tf.sqrt(1./tau)) 43 | llh = norm.log_prob(value=data) 44 | return(llh) 45 | 46 | @classmethod 47 | def fitLatents(cls, parameters: Dict[str, Tensor], 48 | data: Tensor) -> Dict[str, Tensor]: 49 | return({}) 50 | -------------------------------------------------------------------------------- /decompose/distributions/product.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | import numpy as np 3 | import tensorflow as tf 4 | 5 | from decompose.distributions.distribution import DrawType, UpdateType 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import Properties 8 | 9 | 10 | class Product(metaclass=ABCMeta): 11 | 12 | @abstractmethod 13 | def fromUnordered(self, d0: Distribution, 14 | d1: Distribution) -> Distribution: 15 | ... 16 | 17 | def productParams(self, d0: Distribution, 18 | d1: Distribution): 19 | name = self.name(d0, d1) 20 | drawType = self.drawType(d0, d1) 21 | updateType = self.updateType(d0, d1) 22 | persistent = False 23 | properties = Properties(name=name, 24 | drawType=drawType, 25 | updateType=updateType, 26 | persistent=persistent) 27 | params = {"properties": properties} 28 | return(params) 29 | 30 | def name(self, d0: Distribution, d1: Distribution) -> str: 31 | return(d0.name + "_" + d1.name) 32 | 33 | def homogenous(self, d0: Distribution, d1: Distribution) -> bool: 34 | return(np.logical_and(d0.homogenous, d1.homogenous)) 35 | 36 | def drawType(self, d0: Distribution, d1: Distribution) -> DrawType: 37 | if d0.drawType == d1.drawType: 38 | return(d0.drawType) 39 | else: 40 | raise ValueError("DrawType does not match") 41 | 42 | def updateType(self, d0: Distribution, 43 | d1: Distribution) -> UpdateType: 44 | if d0.updateType == d1.updateType: 45 | return(d0.updateType) 46 | else: 47 | raise ValueError("UpdateType does not match") 48 | -------------------------------------------------------------------------------- /decompose/tests/test_sklearn.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import tensorflow as tf 4 | from decompose.distributions.cenNormal import CenNormal 5 | from decompose.sklearn import DECOMPOSE 6 | from decompose.data.lowRank import LowRank 7 | 8 | 9 | tf.logging.set_verbosity(tf.logging.INFO) 10 | 11 | 12 | @pytest.mark.system 13 | @pytest.mark.slow 14 | def test_sklearn(tmpdir): 15 | """Tests the sk-learn interface of the tensor factorisation estimator. 16 | 17 | The test creates a `DECOMPOSE` object and applies its `fit_transform` 18 | method to some low rank training data. The learned filter banks have 19 | to reconstruct the data very well. Then unseen test data is transformed 20 | into the learned basis. The test data has to be recoverd from the 21 | transformed representation. 22 | """ 23 | # create temporary directory where the model and its checkpoints are stored 24 | modelDirectory = str(tmpdir.mkdir("model")) 25 | 26 | # create a synthetic low rank dataset 27 | K, M_train, M_test = 3, [5000, 1000], [5000, 1000] 28 | lrData = LowRank(rank=K, M_train=M_train, M_test=M_test) 29 | 30 | # instantiate a model 31 | priors, K, dtype = [CenNormal(), CenNormal()], K, np.float32 32 | model = DECOMPOSE(modelDirectory, priors=priors, n_components=K, 33 | dtype=dtype) 34 | 35 | # train the model 36 | U0 = model.fit_transform(lrData.training) 37 | 38 | # check whether variance explained is between 0.95 and 1. 39 | U1 = model.components_ 40 | assert(0.95 <= lrData.var_expl_training((U0, U1)) <= 1.) 41 | 42 | # transform test data 43 | transformModelDirectory = str(tmpdir.mkdir("transformModel")) 44 | U0test = model.transform(transformModelDirectory=transformModelDirectory, 45 | X=lrData.test) 46 | assert(0.95 <= lrData.var_expl_test((U0test, U1)) <= 1.) 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | .incremental_checker_cache.json 103 | .cache 104 | .runtest_log.json 105 | dmypy.json 106 | 107 | # pytest cache 108 | .pytest_cache/ 109 | 110 | # emacs 111 | *~ 112 | \#*\# 113 | .\#* 114 | -------------------------------------------------------------------------------- /decompose/distributions/laplaceAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | 8 | 9 | class LaplaceAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | mu, beta = parameters["mu"], parameters["beta"] 14 | norm = tf.distributions.Laplace(loc=mu, scale=beta) 15 | r = norm.sample(sample_shape=(nSamples,)) 16 | print("r in sample", r) 17 | return(r) 18 | 19 | @classmethod 20 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 21 | mu = parameters["mu"] 22 | return(mu) 23 | 24 | @classmethod 25 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 26 | mu, beta = parameters["mu"], parameters["beta"] 27 | norm = tf.distributions.Laplace(loc=mu, scale=beta) 28 | pdf = norm.prob(value=data) 29 | return(pdf) 30 | 31 | @classmethod 32 | def fit(cls, parameters: Dict[str, Tensor], 33 | data: tf.Tensor) -> Dict[str, Tensor]: 34 | M = data.get_shape().as_list()[0] 35 | mu = tf.contrib.nn.nth_element(tf.transpose(data), M//2) 36 | beta = tf.reduce_mean(tf.abs(data-mu), axis=0) 37 | updatedParameters = {"mu": mu, "beta": beta} 38 | return(updatedParameters) 39 | 40 | @classmethod 41 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 42 | mu, beta = parameters["mu"], parameters["beta"] 43 | norm = tf.distributions.Laplace(loc=mu, scale=beta) 44 | llh = norm.log_prob(value=data) 45 | return(llh) 46 | 47 | @classmethod 48 | def fitLatents(cls, parameters: Dict[str, Tensor], 49 | data: Tensor) -> Dict[str, Tensor]: 50 | return({}) 51 | -------------------------------------------------------------------------------- /decompose/tests/test_sklearn_tensor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import tensorflow as tf 4 | from decompose.distributions.cenNormal import CenNormal 5 | from decompose.sklearn import DECOMPOSE 6 | from decompose.data.lowRank import LowRank 7 | 8 | 9 | tf.logging.set_verbosity(tf.logging.INFO) 10 | 11 | 12 | @pytest.mark.system 13 | @pytest.mark.slow 14 | def test_sklearn_tensor(tmpdir): 15 | """Tests the sk-learn interface of the tensor factorisation estimator. 16 | 17 | The test creates a `DECOMPOSE` object and applies its `fit_transform` 18 | method to some low rank training data. The learned filter banks have 19 | to reconstruct the data very well. Then unseen test data is transformed 20 | into the learned basis. The test data has to be recoverd from the 21 | transformed representation. 22 | """ 23 | # create temporary directory where the model and its checkpoints are stored 24 | modelDirectory = str(tmpdir.mkdir("model")) 25 | 26 | # create a synthetic low rank dataset 27 | K, M_train, M_test = 3, [500, 100, 50], [500, 100, 50] 28 | lrData = LowRank(rank=K, M_train=M_train, M_test=M_test) 29 | 30 | # instantiate a model 31 | priors, K, dtype = [CenNormal(), CenNormal(), CenNormal()], K, np.float32 32 | model = DECOMPOSE(modelDirectory, priors=priors, n_components=K, 33 | dtype=dtype) 34 | 35 | # train the model 36 | U0 = model.fit_transform(lrData.training) 37 | 38 | # check whether variance explained is between 0.95 and 1. 39 | U1, U2 = model.components_ 40 | assert(0.95 <= lrData.var_expl_training((U0, U1, U2)) <= 1.) 41 | 42 | # transform test data 43 | transformModelDirectory = str(tmpdir.mkdir("transformModel")) 44 | U0test = model.transform(transformModelDirectory=transformModelDirectory, 45 | X=lrData.test) 46 | assert(0.95 <= lrData.var_expl_test((U0test, U1, U2)) <= 1.) 47 | -------------------------------------------------------------------------------- /decompose/distributions/cenNnNormalAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.nnNormalAlgorithms import NnNormalAlgorithms 6 | 7 | 8 | class CenNnNormalAlgorithms(NnNormalAlgorithms): 9 | 10 | @classmethod 11 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 12 | tau = parameters["tau"] 13 | mu = tf.zeros_like(tau) 14 | samples = NnNormalAlgorithms.sample(parameters={"mu": mu, "tau": tau}, 15 | nSamples=nSamples) 16 | return(samples) 17 | 18 | @classmethod 19 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 20 | tau = parameters["tau"] 21 | mode = tf.zeros_like(tau) 22 | return(mode) 23 | 24 | @classmethod 25 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 26 | tau = parameters["tau"] 27 | mu = tf.zeros_like(tau) 28 | pdf = NnNormalAlgorithms.pdf(parameters={"mu": mu, "tau": tau}, 29 | data=data) 30 | return(pdf) 31 | 32 | @classmethod 33 | def fit(cls, parameters: Dict[str, Tensor], 34 | data: tf.Tensor) -> Dict[str, Tensor]: 35 | var = tf.reduce_mean(data**2, axis=0) 36 | tau = 1./var 37 | updatedParameters = {"tau": tau} 38 | return(updatedParameters) 39 | 40 | @classmethod 41 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> float: 42 | tau = parameters["tau"] 43 | mu = tf.zeros_like(tau) 44 | llh = NnNormalAlgorithms.llh(parameters={"mu": mu, "tau": tau}, 45 | data=data) 46 | return(llh) 47 | 48 | @classmethod 49 | def fitLatents(cls, parameters: Dict[str, Tensor], 50 | data: Tensor) -> Dict[str, Tensor]: 51 | return({}) 52 | -------------------------------------------------------------------------------- /decompose/distributions/cenNormalAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.algorithms import Algorithms 6 | from decompose.distributions.normalAlgorithms import NormalAlgorithms 7 | 8 | 9 | class CenNormalAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | tau = parameters["tau"] 14 | mu = tf.zeros_like(tau) 15 | samples = NormalAlgorithms.sample(parameters={"mu": mu, "tau": tau}, 16 | nSamples=nSamples) 17 | return(samples) 18 | 19 | @classmethod 20 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 21 | tau = parameters["tau"] 22 | mode = tf.zeros_like(tau) 23 | return(mode) 24 | 25 | @classmethod 26 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 27 | tau = parameters["tau"] 28 | mu = tf.zeros_like(tau) 29 | pdf = NormalAlgorithms.pdf(parameters={"mu": mu, "tau": tau}, 30 | data=data) 31 | return(pdf) 32 | 33 | @classmethod 34 | def fit(cls, parameters: Dict[str, Tensor], 35 | data: tf.Tensor) -> Dict[str, Tensor]: 36 | var = tf.reduce_mean(data**2, axis=0) 37 | tau = 1./var 38 | updatedParameters = {"tau": tau} 39 | return(updatedParameters) 40 | 41 | @classmethod 42 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> float: 43 | tau = parameters["tau"] 44 | mu = tf.zeros_like(tau) 45 | llh = NormalAlgorithms.llh(parameters={"mu": mu, "tau": tau}, 46 | data=data) 47 | return(llh) 48 | 49 | @classmethod 50 | def fitLatents(cls, parameters: Dict[str, Tensor], 51 | data: Tensor) -> Dict[str, Tensor]: 52 | return({}) 53 | -------------------------------------------------------------------------------- /decompose/distributions/cenLaplaceAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.algorithms import Algorithms 6 | from decompose.distributions.laplaceAlgorithms import LaplaceAlgorithms 7 | 8 | 9 | class CenLaplaceAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | beta = parameters["beta"] 14 | mu = tf.zeros_like(beta) 15 | samples = LaplaceAlgorithms.sample(parameters={"mu": mu, "beta": beta}, 16 | nSamples=nSamples) 17 | return(samples) 18 | 19 | @classmethod 20 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 21 | beta = parameters["beta"] 22 | mode = tf.zeros_like(beta) 23 | return(mode) 24 | 25 | @classmethod 26 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 27 | beta = parameters["beta"] 28 | mu = tf.zeros_like(beta) 29 | pdf = LaplaceAlgorithms.pdf(parameters={"mu": mu, "beta": beta}, 30 | data=data) 31 | return(pdf) 32 | 33 | @classmethod 34 | def fit(cls, parameters: Dict[str, Tensor], 35 | data: tf.Tensor) -> Dict[str, Tensor]: 36 | beta = tf.reduce_mean(tf.abs(data), axis=0) 37 | updatedParameters = {"beta": beta} 38 | return(updatedParameters) 39 | 40 | @classmethod 41 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 42 | beta = parameters["beta"] 43 | mu = tf.zeros_like(beta) 44 | llh = LaplaceAlgorithms.llh(parameters={"mu": mu, "beta": beta}, 45 | data=data) 46 | return(llh) 47 | 48 | @classmethod 49 | def fitLatents(cls, parameters: Dict[str, Tensor], 50 | data: Tensor) -> Dict[str, Tensor]: 51 | return({}) 52 | -------------------------------------------------------------------------------- /decompose/distributions/normalCenNnFullyElasticNetCond.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | import numpy as np 3 | import tensorflow as tf 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.normal import Normal 7 | from decompose.distributions.nnNormal import NnNormal 8 | from decompose.distributions.exponential import Exponential 9 | from decompose.distributions.cenNnFullyElasticNetCond import CenNnFullyElasticNetCond 10 | from decompose.distributions.product import Product 11 | 12 | 13 | class NormalCenNnFullyElasticNetCond(Product): 14 | 15 | def fromUnordered(self, d0: Distribution, 16 | d1: Distribution) -> Normal: 17 | 18 | if isinstance(d0, Normal) and isinstance(d1, CenNnFullyElasticNetCond): 19 | return(self.product(d0, d1)) 20 | else: 21 | raise ValueError("Expecting Normal and CenNnFullyElasticNet") 22 | 23 | def product(self, n0: Normal, n1: CenNnFullyElasticNetCond) -> NnNormal: 24 | otherParams = self.productParams(n0, n1) 25 | 26 | lomax = Exponential(beta=n1.beta, **otherParams) 27 | normalLomax = n0*lomax 28 | 29 | exponential = Exponential(beta=n1.betaExponential, **otherParams) 30 | normalExponential = n0*exponential 31 | 32 | nnNormal = NnNormal(mu=n1.mu, tau=n1.tau, **otherParams) 33 | normalNnNormal = n0*nnNormal 34 | 35 | b = n1.b 36 | mu = tf.where(tf.equal(b, 0.), 37 | normalNnNormal.mu, 38 | tf.where(tf.equal(b, 1.), 39 | normalExponential.mu, 40 | normalLomax.mu)) 41 | tau = tf.where(tf.equal(b, 0.), 42 | normalNnNormal.tau, 43 | tf.where(tf.equal(b, 1.), 44 | normalExponential.tau, 45 | normalLomax.tau)) 46 | 47 | pd = NnNormal(mu=mu, tau=tau, **otherParams) 48 | return(pd) 49 | -------------------------------------------------------------------------------- /decompose/tests/test_sklearn_cv.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import tensorflow as tf 4 | from decompose.distributions.cenNormal import CenNormal 5 | from decompose.sklearn import DECOMPOSE 6 | from decompose.data.lowRank import LowRank 7 | from decompose.cv.cv import Block 8 | 9 | 10 | tf.logging.set_verbosity(tf.logging.INFO) 11 | 12 | 13 | @pytest.mark.system 14 | @pytest.mark.slow 15 | def test_sklearn_cv(tmpdir): 16 | """Tests the sk-learn interface of the tensor factorisation estimator. 17 | 18 | The test creates a `DECOMPOSE` object and applies its `fit_transform` 19 | method to some low rank training data. The learned filter banks have 20 | to reconstruct the data very well. Then unseen test data is transformed 21 | into the learned basis. The test data has to be recoverd from the 22 | transformed representation. 23 | """ 24 | # create temporary directory where the model and its checkpoints are stored 25 | modelDirectory = str(tmpdir.mkdir("model")) 26 | 27 | # create a synthetic low rank dataset 28 | K, M_train, M_test = 3, [500, 100], [200, 100] 29 | lrData = LowRank(rank=K, M_train=M_train, M_test=M_test) 30 | 31 | # instantiate a model 32 | priors, K, dtype = [CenNormal(), CenNormal()], K, np.float32 33 | model = DECOMPOSE(modelDirectory, priors=priors, n_components=K, 34 | cv=Block(nFolds=(3, 3), foldNumber=3), dtype=dtype) 35 | 36 | # train the model 37 | U0 = model.fit_transform(lrData.training) 38 | 39 | # get mask marking the training set 40 | testMask = model.testMask 41 | 42 | # check whether variance explained is between 0.95 and 1. 43 | U1 = model.components_ 44 | testIndexes = testMask.flatten() 45 | testResiduals = (np.dot(U0.T, U1) - lrData.training).flatten()[testIndexes] 46 | testData = lrData.training.flatten()[testIndexes] 47 | testVarExpl = 1. - np.var(testResiduals)/np.var(testData) 48 | print("testVarExpl", testVarExpl) 49 | assert(0.95 <= lrData.var_expl_training((U0, U1)) <= 1.) 50 | -------------------------------------------------------------------------------- /decompose/tests/test_sklearn_all_het_noise.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import tensorflow as tf 4 | from decompose.distributions.cenNormal import CenNormal 5 | from decompose.sklearn import DECOMPOSE 6 | from decompose.data.lowRank import LowRank 7 | from decompose.models.tensorFactorisation import NoiseUniformity 8 | 9 | 10 | tf.logging.set_verbosity(tf.logging.INFO) 11 | HETEROGENOUS = NoiseUniformity.HETEROGENEOUS 12 | 13 | 14 | @pytest.mark.system 15 | @pytest.mark.slow 16 | def test_sklearn(tmpdir): 17 | """Tests the sk-learn interface of the tensor factorisation estimator. 18 | 19 | The test creates a `DECOMPOSE` object and applies its `fit_transform` 20 | method to some low rank training data. The learned filter banks have 21 | to reconstruct the data very well. Then unseen test data is transformed 22 | into the learned basis. The test data has to be recoverd from the 23 | transformed representation. 24 | """ 25 | # create temporary directory where the model and its checkpoints are stored 26 | modelDirectory = str(tmpdir.mkdir("model")) 27 | 28 | # create a synthetic low rank dataset 29 | K, M_train, M_test = 3, [5000, 1000], [5000, 1000] 30 | lrData = LowRank(rank=K, M_train=M_train, M_test=M_test) 31 | 32 | # instantiate a model 33 | priors, K, dtype = [CenNormal(), CenNormal()], K, np.float32 34 | model = DECOMPOSE(modelDirectory, priors=priors, n_components=K, 35 | dtype=dtype, noiseUniformity=HETEROGENOUS) 36 | 37 | # train the model 38 | U0 = model.fit_transform(lrData.training) 39 | 40 | # check whether variance explained is between 0.95 and 1. 41 | U1 = model.components_ 42 | assert(0.95 <= lrData.var_expl_training((U0, U1)) <= 1.) 43 | 44 | # transform test data 45 | transformModelDirectory = str(tmpdir.mkdir("transformModel")) 46 | U0test = model.transform(transformModelDirectory=transformModelDirectory, 47 | X=lrData.test) 48 | assert(0.95 <= lrData.var_expl_test((U0test, U1)) <= 1.) 49 | -------------------------------------------------------------------------------- /decompose/distributions/uniform.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.distribution import parameterProperty 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.uniformAlgorithms import UniformAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class Uniform(Distribution): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = UniformAlgorithms, 17 | dummy: Tensor = None, 18 | properties: Properties = None) -> None: 19 | parameters = {"dummy": dummy} 20 | Distribution.__init__(self, 21 | algorithms=algorithms, 22 | parameters=parameters, 23 | properties=properties) 24 | 25 | def parameterInfo(self, 26 | shape: Tuple[int, ...] = (1,), 27 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 28 | initializers = { 29 | "dummy": (shape, False) 30 | } # type: Dict[str, Tensor] 31 | return(initializers) 32 | 33 | @property 34 | def shape(self) -> Tuple[int, ...]: 35 | return(tuple(self.dummy.get_shape().as_list())) 36 | 37 | @property 38 | def latentShape(self) -> Tuple[int, ...]: 39 | return(()) 40 | 41 | @parameterProperty 42 | def dummy(self) -> Tensor: 43 | return(self.__dummy) 44 | 45 | @dummy.setter(name="dummy") 46 | def dummy(self, dummy: Tensor): 47 | self.__dummy = dummy 48 | 49 | @property 50 | def nonNegative(self) -> bool: 51 | return(False) 52 | 53 | @property 54 | def homogenous(self) -> bool: 55 | return(True) 56 | 57 | def cond(self) -> Distribution: 58 | return(self) 59 | -------------------------------------------------------------------------------- /decompose/distributions/exponential.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Any, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.distribution import parameterProperty 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.exponentialAlgorithms import ExponentialAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class Exponential(Distribution): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = ExponentialAlgorithms, 17 | beta: Tensor = None, 18 | properties: Properties = None) -> None: 19 | parameters = {"beta": beta} 20 | Distribution.__init__(self, 21 | algorithms=algorithms, 22 | parameters=parameters, 23 | properties=properties) 24 | 25 | def parameterInfo(self, 26 | shape: Tuple[int, ...] = (1,), 27 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 28 | initializers = { 29 | "beta": (shape, True) 30 | } # type: Dict[str, Tensor] 31 | return(initializers) 32 | 33 | @parameterProperty 34 | def beta(self) -> Tensor: 35 | return(self.__beta) 36 | 37 | @beta.setter("beta") 38 | def beta(self, beta: Tensor): 39 | self.__beta = beta 40 | 41 | @property 42 | def nonNegative(self) -> bool: 43 | return(True) 44 | 45 | @property 46 | def homogenous(self) -> bool: 47 | return(False) 48 | 49 | def cond(self) -> Distribution: 50 | return(self) 51 | 52 | @property 53 | def shape(self) -> Tuple[int, ...]: 54 | return(tuple(self.beta.get_shape().as_list())) 55 | 56 | @property 57 | def latentShape(self) -> Tuple[int, ...]: 58 | return(()) 59 | -------------------------------------------------------------------------------- /decompose/distributions/nnUniform.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.distribution import parameterProperty 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.nnUniformAlgorithms import NnUniformAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class NnUniform(Distribution): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = NnUniformAlgorithms, 17 | dummy: Tensor = None, 18 | properties: Properties = None) -> None: 19 | parameters = {"dummy": dummy} 20 | Distribution.__init__(self, 21 | algorithms=algorithms, 22 | parameters=parameters, 23 | properties=properties) 24 | 25 | def parameterInfo(self, 26 | shape: Tuple[int, ...] = (1,), 27 | latentShape: Tuple[int, ...] = ())-> ParameterInfo: 28 | initializers = { 29 | "dummy": (shape, False) 30 | } # type: Dict[str, Tensor] 31 | return(initializers) 32 | 33 | @property 34 | def shape(self) -> Tuple[int, ...]: 35 | return(tuple(self.dummy.get_shape().as_list())) 36 | 37 | @property 38 | def latentShape(self) -> Tuple[int, ...]: 39 | return(()) 40 | 41 | @parameterProperty 42 | def dummy(self) -> Tensor: 43 | return(self.__dummy) 44 | 45 | @dummy.setter(name="dummy") 46 | def dummy(self, dummy: Tensor): 47 | self.__dummy = dummy 48 | 49 | @property 50 | def nonNegative(self) -> bool: 51 | return(True) 52 | 53 | @property 54 | def homogenous(self) -> bool: 55 | return(True) 56 | 57 | def cond(self) -> Distribution: 58 | return(self) 59 | -------------------------------------------------------------------------------- /decompose/distributions/cenNnTAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | from decompose.distributions.cenTAlgorithms import CenTAlgorithms 8 | 9 | 10 | class CenNnTAlgorithms(Algorithms): 11 | 12 | @classmethod 13 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 14 | Psi, nu = parameters["Psi"], parameters["nu"] 15 | mu = tf.zeros_like(Psi) 16 | t = tf.distributions.StudentT(df=nu, loc=mu, scale=tf.sqrt(Psi)) 17 | r = t.sample(sample_shape=(nSamples,)) 18 | return(r) 19 | 20 | @classmethod 21 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 22 | mode = tf.zeros_like(parameters["Psi"]) 23 | return(mode) 24 | 25 | @classmethod 26 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 27 | Psi, nu = parameters["Psi"], parameters["nu"] 28 | mu = tf.zeros_like(Psi) 29 | t = tf.distributions.StudentT(df=nu, loc=mu, scale=tf.sqrt(Psi)) 30 | pdf = 2*t.prob(value=data) 31 | pdf = tf.where(tf.less(data, 0.), tf.zeros_like(pdf), pdf) 32 | return(pdf) 33 | 34 | @classmethod 35 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 36 | Psi, nu = parameters["Psi"], parameters["nu"] 37 | mu = tf.zeros_like(Psi) 38 | t = tf.distributions.StudentT(df=nu, loc=mu, scale=tf.sqrt(Psi)) 39 | llh = t.log_prob(value=data) 40 | llh = tf.where(tf.less(data, 0.), -np.inf*tf.ones_like(llh), llh) 41 | return(llh) 42 | 43 | @classmethod 44 | def fit(cls, parameters: Dict[str, Tensor], 45 | data: tf.Tensor) -> Dict[str, Tensor]: 46 | return(CenTAlgorithms.fit(parameters, data)) 47 | 48 | @classmethod 49 | def fitLatents(cls, parameters: Dict[str, Tensor], 50 | data: Tensor) -> Dict[str, Tensor]: 51 | return(CenTAlgorithms.fitLatents(parameters, data)) 52 | -------------------------------------------------------------------------------- /decompose/tests/test_sklearn_het_noise.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import tensorflow as tf 4 | from decompose.distributions.cenNormal import CenNormal 5 | from decompose.sklearn import DECOMPOSE 6 | from decompose.data.lowRank import LowRank 7 | from decompose.models.tensorFactorisation import NoiseUniformity 8 | 9 | 10 | tf.logging.set_verbosity(tf.logging.INFO) 11 | LAST_FACTOR_HETEROGENOUS = NoiseUniformity.LAST_FACTOR_HETEROGENOUS 12 | 13 | 14 | @pytest.mark.system 15 | @pytest.mark.slow 16 | def test_sklearn(tmpdir): 17 | """Tests the sk-learn interface of the tensor factorisation estimator. 18 | 19 | The test creates a `DECOMPOSE` object and applies its `fit_transform` 20 | method to some low rank training data. The learned filter banks have 21 | to reconstruct the data very well. Then unseen test data is transformed 22 | into the learned basis. The test data has to be recoverd from the 23 | transformed representation. 24 | """ 25 | # create temporary directory where the model and its checkpoints are stored 26 | modelDirectory = str(tmpdir.mkdir("model")) 27 | 28 | # create a synthetic low rank dataset 29 | K, M_train, M_test = 3, [5000, 1000], [5000, 1000] 30 | lrData = LowRank(rank=K, M_train=M_train, M_test=M_test) 31 | 32 | # instantiate a model 33 | priors, K, dtype = [CenNormal(), CenNormal()], K, np.float32 34 | model = DECOMPOSE(modelDirectory, priors=priors, n_components=K, 35 | dtype=dtype, noiseUniformity=LAST_FACTOR_HETEROGENOUS) 36 | 37 | # train the model 38 | U0 = model.fit_transform(lrData.training) 39 | 40 | # check whether variance explained is between 0.95 and 1. 41 | U1 = model.components_ 42 | assert(0.95 <= lrData.var_expl_training((U0, U1)) <= 1.) 43 | 44 | # transform test data 45 | transformModelDirectory = str(tmpdir.mkdir("transformModel")) 46 | U0test = model.transform(transformModelDirectory=transformModelDirectory, 47 | X=lrData.test) 48 | assert(0.95 <= lrData.var_expl_test((U0test, U1)) <= 1.) 49 | -------------------------------------------------------------------------------- /decompose/distributions/cenDoubleLomaxAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | from decompose.distributions.lomaxAlgorithms import LomaxAlgorithms 8 | 9 | 10 | class CenDoubleLomaxAlgorithms(Algorithms): 11 | 12 | @classmethod 13 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 14 | alpha, beta = parameters["alpha"], parameters["beta"] 15 | gamma = tf.distributions.Gamma(concentration=alpha, rate=beta) 16 | tau = gamma.sample(sample_shape=(nSamples,)) 17 | lap = tf.distributions.Laplace(scale=1./tau, 18 | loc=tf.zeros_like(alpha)) 19 | s = lap.sample() 20 | return(s) 21 | 22 | @classmethod 23 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 24 | mode = tf.zeros_like(parameters["alpha"]) 25 | return(mode) 26 | 27 | @classmethod 28 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 29 | pdf = LomaxAlgorithms.pdf(parameters=parameters, data=tf.abs(data))/2. 30 | return(pdf) 31 | 32 | @classmethod 33 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 34 | llh = LomaxAlgorithms.llh(parameters=parameters, 35 | data=tf.abs(data)) - np.log(2.) 36 | return(llh) 37 | 38 | @classmethod 39 | def fit(cls, parameters: Dict[str, Tensor], 40 | data: tf.Tensor) -> Dict[str, Tensor]: 41 | parameters = LomaxAlgorithms.fit(parameters=parameters, 42 | data=tf.abs(data)) 43 | return(parameters) 44 | 45 | @classmethod 46 | def fitLatents(cls, parameters: Dict[str, Tensor], 47 | data: Tensor) -> Dict[str, Tensor]: 48 | parameters = LomaxAlgorithms.fitLatents(parameters=parameters, 49 | data=tf.abs(data)) 50 | return(parameters) 51 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_cenTAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.cenTAlgorithms import CenTAlgorithms 8 | 9 | 10 | @pytest.mark.slow 11 | def test_t_fit(): 12 | """Test if the fitted parameters match the true parameters.""" 13 | nu = np.array([0.5, 1., 1.5]) 14 | Psi = np.array([0.5, 1., 2.]) 15 | nParameters = nu.shape[0] 16 | nSamples = 1000000 17 | shape = (nSamples, nParameters) 18 | nIterations = 100 19 | 20 | # sample from the distribution using the true parameters 21 | data = sp.stats.t(df=nu, scale=np.sqrt(Psi)).rvs(shape) 22 | tfData = tf.constant(data) 23 | 24 | # sample from the distribution using the true parameters 25 | parameters = {"nu": tf.constant(np.ones(nParameters)), 26 | "Psi": tf.constant(np.ones(nParameters)), 27 | "tau": tf.constant(np.ones((nSamples, nParameters)))} 28 | variables = {key: tf.get_variable(key, initializer=value) 29 | for key, value in parameters.items()} 30 | 31 | # estimate the parameters from the random sample 32 | parameterUpdate = CenTAlgorithms.fit(parameters=variables, 33 | data=tfData) 34 | varUpdates = {} 35 | for key, var in variables.items(): 36 | varUpdates[key] = tf.assign(var, parameterUpdate[key]) 37 | with tf.Session() as sess: 38 | # initialize variables 39 | for key, var in variables.items(): 40 | sess.run(var.initializer) 41 | # update the variables 42 | for i in range(nIterations): 43 | sess.run(varUpdates) 44 | # get estimated parameters 45 | parameters = sess.run(variables) 46 | 47 | # check the estimations 48 | nuHat = parameters["nu"] 49 | assert(nuHat.shape == nu.shape) 50 | assert(np.allclose(nuHat, nu, atol=1e-1)) 51 | PsiHat = parameters["Psi"] 52 | assert(PsiHat.shape == Psi.shape) 53 | assert(np.allclose(PsiHat, Psi, atol=1e-1)) 54 | tf.reset_default_graph() 55 | -------------------------------------------------------------------------------- /decompose/postU/tests/test_postU.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | import pytest 3 | import numpy as np 4 | import scipy as sp 5 | import scipy.stats 6 | import tensorflow as tf 7 | 8 | from decompose.likelihoods.normal2dLikelihood import Normal2dLikelihood 9 | from decompose.tests.fixtures import device, dtype 10 | from decompose.distributions.distribution import UpdateType, Properties 11 | from decompose.distributions.uniform import Uniform 12 | from decompose.postU.postU import PostU 13 | 14 | 15 | @pytest.fixture(scope="module", 16 | params=[0, 1]) 17 | def f(request): 18 | f = request.param 19 | return(f) 20 | 21 | 22 | @pytest.fixture(scope="module", 23 | params=[UpdateType.ALL, UpdateType.ONLYLATENTS]) 24 | def updateType(request): 25 | updateType = request.param 26 | return(updateType) 27 | 28 | 29 | def test_update(device, f, dtype): 30 | npdtype = dtype.as_numpy_dtype 31 | M, K, tau, F = (20, 30), 3, 0.1, 2 32 | npU = (np.random.normal(size=(K, M[0])).astype(npdtype), 33 | np.random.normal(size=(K, M[1])).astype(npdtype)) 34 | U = [tf.constant(npU[0]), tf.constant(npU[1])] 35 | npnoise = np.random.normal(size=M).astype(npdtype) 36 | npdata = np.dot(npU[0].T, npU[1]) + npnoise 37 | data = tf.constant(npdata, dtype=dtype) 38 | 39 | lh = Normal2dLikelihood(M=M, K=K, tau=tau, dtype=dtype) 40 | lh.init(data=data) 41 | 42 | properties = Properties(persistent=True, 43 | dtype=dtype) 44 | prior = Uniform(dummy=tf.constant(np.random.random(K).astype(npdtype), 45 | dtype=dtype), 46 | properties=properties) 47 | 48 | postUf = PostU(lh, prior, f) 49 | 50 | Ufupdated = postUf.update(U, data, transform=False) 51 | 52 | for g in range(F): 53 | assert(Ufupdated.dtype == dtype) 54 | 55 | with tf.Session() as sess: 56 | sess.run(tf.global_variables_initializer()) 57 | npUfupdated = sess.run(Ufupdated) 58 | 59 | assert(not np.allclose(npU[f], npUfupdated)) 60 | 61 | tf.reset_default_graph() 62 | -------------------------------------------------------------------------------- /decompose/distributions/exponentialAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import tensorflow as tf 3 | import numpy as np 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | 8 | 9 | class ExponentialAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | beta = parameters["beta"] 14 | exp = tf.distributions.Exponential(rate=1./beta) 15 | r = exp.sample(sample_shape=(nSamples,)) 16 | return(r) 17 | 18 | @classmethod 19 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 20 | mode = tf.zeros_like(parameters["beta"]) 21 | return(mode) 22 | 23 | @classmethod 24 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 25 | beta = parameters["beta"] 26 | # TODO: once tensorflow issue #20737 is resolved use the 27 | # commented out code to calculate the pdf 28 | # exp = tf.distributions.Exponential(rate=1./beta) 29 | # pdf = exp.prob(value=data) 30 | pdf = 1./beta*tf.exp(-data/beta) 31 | pdf = tf.where(tf.less(data, 0.), tf.zeros_like(pdf), pdf) 32 | return(pdf) 33 | 34 | @classmethod 35 | def fit(cls, parameters: Dict[str, Tensor], 36 | data: tf.Tensor) -> Dict[str, Tensor]: 37 | beta = tf.reduce_mean(tf.abs(data), axis=0) 38 | updatedParameters = {"beta": beta} 39 | return(updatedParameters) 40 | 41 | @classmethod 42 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 43 | beta = parameters["beta"] 44 | # TODO: once tensorflow issue #20737 is resolved use the 45 | # commented out code to calculate the llh 46 | # exp = tf.distributions.Exponential(rate=1./beta) 47 | # llh = exp.log_prob(value=data) 48 | llh = -tf.log(beta) - data/beta 49 | llh = tf.where(data < 0., -tf.ones_like(llh)*np.inf, llh) 50 | return(llh) 51 | 52 | @classmethod 53 | def fitLatents(cls, parameters: Dict[str, Tensor], 54 | data: Tensor) -> Dict[str, Tensor]: 55 | return({}) 56 | -------------------------------------------------------------------------------- /decompose/cv/cv.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from abc import ABCMeta, abstractmethod 3 | import string 4 | import numpy as np 5 | import tensorflow as tf 6 | from tensorflow import Tensor 7 | 8 | 9 | class CV(metaclass=ABCMeta): 10 | @abstractmethod 11 | def isLowrank(self) -> bool: 12 | ... 13 | 14 | @abstractmethod 15 | def lowrankMask(self, X: Tensor) -> Tuple[Tensor, ...]: 16 | ... 17 | 18 | @abstractmethod 19 | def mask(self, X: Tensor) -> Tensor: 20 | ... 21 | 22 | 23 | class Block(CV): 24 | 25 | def __init__(self, nFolds: Tuple[int, ...], foldNumber: int) -> None: 26 | self.__nFolds = nFolds 27 | self.__foldNumber = foldNumber 28 | 29 | @property 30 | def nFolds(self) -> Tuple[int, ...]: 31 | return(self.__nFolds) 32 | 33 | @property 34 | def foldNumber(self) -> int: 35 | return(self.__foldNumber) 36 | 37 | def isLowrank(self) -> bool: 38 | return(True) 39 | 40 | def lowrankMask(self, X: Tensor): 41 | nFolds = np.array(self.nFolds) 42 | foldNumber = self.foldNumber 43 | 44 | M = np.array(X.get_shape().as_list()) 45 | F = len(M) 46 | nValues = M//nFolds 47 | 48 | folds = np.zeros(np.product(M)).flatten() 49 | folds[foldNumber] = 1. 50 | folds = folds.reshape(M) 51 | foldNumbers = np.array(np.where(folds == 1.)).flatten() 52 | 53 | U = [] 54 | for f in range(F): 55 | Uf = self.testMask(M[f], foldNumbers[f], nFolds[f], nValues[f]) 56 | U.append(tf.constant(Uf)) 57 | return(U) 58 | 59 | def mask(self, X: Tensor): 60 | U = self.lowrankMask(X) 61 | F = len(U) 62 | axis = string.ascii_lowercase[:F] 63 | subscripts = ','.join(axis) + "->" + axis 64 | mask = tf.cast(tf.einsum(subscripts, *U), dtype=tf.bool) 65 | return(mask) 66 | 67 | def testMask(self, Mf, foldNumber, nFolds, nValues): 68 | Uf = np.zeros(Mf) 69 | if foldNumber == nFolds-1: 70 | Uf[(nFolds-1)*nValues:] = 1. 71 | else: 72 | Uf[foldNumber*nValues:(foldNumber+1)*nValues] = 1. 73 | return(Uf) 74 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from setuptools import setup, find_packages 4 | from os import path 5 | from decompose.version import __version__ 6 | 7 | if sys.version_info < (3, 6, 0): 8 | sys.stderr.write("ERROR: You need Python 3.6 or later to use decompose.\n") 9 | exit(1) 10 | 11 | description = 'Blind source separation based on the probabilistic tensor ' \ 12 | 'factorisation framework' 13 | long_description = ''' 14 | Decompose -- Blind source separation framework 15 | ============================================== 16 | 17 | DECOMPOSE is a probabilistic blind source separation framework that 18 | can be flexibly adjusted to the data, is extensible and easy to use, 19 | adapts to individual sources and handles large-scale data through 20 | algorithmic efficiency. DECOMPOSE encompasses and generalises many 21 | traditional BSS algorithms such as PCA, ICA and NMF. 22 | '''.lstrip() 23 | 24 | here = path.abspath(path.dirname(__file__)) 25 | 26 | setup( 27 | name='decompose', 28 | version=__version__, 29 | description=description, 30 | long_description=long_description, 31 | url='https://github.com/bethgelab/decompose', 32 | author='Alexander Boettcher', 33 | author_email='alexander.boettcher@bethgelab.org', 34 | classifiers=[ 35 | 'Development Status :: 3 - Alpha', 36 | 'Intended Audience :: Developers', 37 | 'Intended Audience :: Education', 38 | 'Intended Audience :: Science/Research', 39 | 'Topic :: Scientific/Engineering :: Information Analysis', 40 | 'License :: OSI Approved :: MIT License', 41 | 'Programming Language :: Python', 42 | 'Programming Language :: Python :: 3', 43 | 'Programming Language :: Python :: 3.6', 44 | ], 45 | keywords='data-analysis machine-learning blind-source-separation', 46 | packages=find_packages(), 47 | install_requires=['tensorflow>=1.7', 48 | 'numpy', 49 | 'mypy'], 50 | extras_require={ 51 | 'test': ['pytest', 52 | 'scipy'], 53 | }, 54 | 55 | project_urls={ 56 | 'Bug Reports': 'https://github.com/bethgelab/decompose/issues', 57 | 'Source': 'https://github.com/bethgelab/decompose/', 58 | }, 59 | ) 60 | -------------------------------------------------------------------------------- /decompose/distributions/normal.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.distribution import parameterProperty 8 | from decompose.distributions.algorithms import Algorithms 9 | from decompose.distributions.normalAlgorithms import NormalAlgorithms 10 | from decompose.distributions.distribution import Properties 11 | 12 | 13 | class Normal(Distribution): 14 | def __init__(self, 15 | algorithms: Type[Algorithms] = NormalAlgorithms, 16 | mu: Tensor = None, 17 | tau: Tensor = None, 18 | properties: Properties = None) -> None: 19 | parameters = {"mu": mu, "tau": tau} 20 | Distribution.__init__(self, 21 | algorithms=algorithms, 22 | parameters=parameters, 23 | properties=properties) 24 | 25 | def parameterInfo(self, 26 | shape: Tuple[int, ...] = (1,), 27 | latentShape: Tuple[int, ...] = ())-> ParameterInfo: 28 | initializers = { 29 | "mu": (shape, False), 30 | "tau": (shape, True) 31 | } # type: Dict[str, Tensor] 32 | return(initializers) 33 | 34 | @parameterProperty 35 | def mu(self) -> Tensor: 36 | return(self.__mu) 37 | 38 | @mu.setter(name="mu") 39 | def mu(self, mu: Tensor): 40 | self.__mu = mu 41 | 42 | @parameterProperty 43 | def tau(self) -> Tensor: 44 | return(self.__tau) 45 | 46 | @tau.setter(name="tau") 47 | def tau(self, tau: Tensor): 48 | self.__tau = tau 49 | 50 | @property 51 | def nonNegative(self) -> bool: 52 | return(False) 53 | 54 | @property 55 | def homogenous(self) -> bool: 56 | return(False) 57 | 58 | def cond(self) -> Distribution: 59 | return(self) 60 | 61 | @property 62 | def shape(self) -> Tuple[int, ...]: 63 | return(tuple(self.mu.get_shape().as_list())) 64 | 65 | @property 66 | def latentShape(self) -> Tuple[int, ...]: 67 | return(()) 68 | -------------------------------------------------------------------------------- /decompose/distributions/nnNormal.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.distribution import parameterProperty 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.nnNormalAlgorithms import NnNormalAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class NnNormal(Distribution): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = NnNormalAlgorithms, 17 | mu: Tensor = None, 18 | tau: Tensor = None, 19 | properties: Properties = None) -> None: 20 | parameters = {"mu": mu, "tau": tau} 21 | Distribution.__init__(self, 22 | algorithms=algorithms, 23 | parameters=parameters, 24 | properties=properties) 25 | 26 | def parameterInfo(self, 27 | shape: Tuple[int, ...] = (1,), 28 | latentShape: Tuple[int, ...] = ())-> ParameterInfo: 29 | initializers = { 30 | "mu": (shape, False), 31 | "tau": (shape, True) 32 | } # type: Dict[str, Tensor] 33 | return(initializers) 34 | 35 | @parameterProperty 36 | def mu(self) -> Tensor: 37 | return(self.__mu) 38 | 39 | @mu.setter(name="mu") 40 | def mu(self, mu: Tensor): 41 | self.__mu = mu 42 | 43 | @parameterProperty 44 | def tau(self) -> Tensor: 45 | return(self.__tau) 46 | 47 | @tau.setter(name="tau") 48 | def tau(self, tau: Tensor): 49 | self.__tau = tau 50 | 51 | @property 52 | def nonNegative(self) -> bool: 53 | return(True) 54 | 55 | @property 56 | def homogenous(self) -> bool: 57 | return(False) 58 | 59 | def cond(self) -> Distribution: 60 | return(self) 61 | 62 | @property 63 | def shape(self) -> Tuple[int, ...]: 64 | return(tuple(self.mu.get_shape().as_list())) 65 | 66 | @property 67 | def latentShape(self) -> Tuple[int, ...]: 68 | return(()) 69 | -------------------------------------------------------------------------------- /decompose/tests/test_sklearn_cv_tensor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import tensorflow as tf 4 | from decompose.distributions.cenNormal import CenNormal 5 | from decompose.sklearn import DECOMPOSE 6 | from decompose.data.lowRank import LowRank 7 | from decompose.cv.cv import Block 8 | 9 | 10 | tf.logging.set_verbosity(tf.logging.INFO) 11 | 12 | 13 | @pytest.mark.system 14 | @pytest.mark.slow 15 | def test_sklearn_cv(tmpdir): 16 | """Tests the sk-learn interface of the tensor factorisation estimator. 17 | 18 | The test creates a `DECOMPOSE` object and applies its `fit_transform` 19 | method to some low rank training data. The learned filter banks have 20 | to reconstruct the data very well. Then unseen test data is transformed 21 | into the learned basis. The test data has to be recoverd from the 22 | transformed representation. 23 | """ 24 | # create temporary directory where the model and its checkpoints are stored 25 | modelDirectory = str(tmpdir.mkdir("model")) 26 | 27 | # create a synthetic low rank dataset 28 | K, M_train, M_test = 3, [30, 100, 150], [200, 100, 150] 29 | lrData = LowRank(rank=K, M_train=M_train, M_test=M_test) 30 | 31 | # instantiate a model 32 | priors, K, dtype = [CenNormal(), CenNormal(), CenNormal()], K, np.float32 33 | model = DECOMPOSE(modelDirectory, priors=priors, n_components=K, 34 | isFullyObserved=False, 35 | cv=Block(nFolds=(2, 3, 3), foldNumber=3), dtype=dtype) 36 | 37 | # mark 20% of the elments as unobserved 38 | data = lrData.training.copy() 39 | r = np.random.random(data.shape) > 0.8 40 | data[r] = np.nan 41 | 42 | # train the model 43 | U0 = model.fit_transform(data) 44 | 45 | # get mask marking the test set 46 | testMask = model.testMask 47 | 48 | # # check whether variance explained is between 0.95 and 1. 49 | U1, U2 = model.components_ 50 | testIndexes = testMask.flatten() 51 | recons = np.einsum("ka,kb,kc->abc", U0, U1, U2) 52 | testResiduals = (recons - lrData.training).flatten()[testIndexes] 53 | testData = lrData.training.flatten()[testIndexes] 54 | testVarExpl = 1. - np.var(testResiduals)/np.var(testData) 55 | assert(0.95 <= testVarExpl <= 1.) 56 | assert(0.95 <= lrData.var_expl_training((U0, U1, U2)) <= 1.) 57 | -------------------------------------------------------------------------------- /decompose/distributions/laplace.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.distribution import parameterProperty 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.laplaceAlgorithms import LaplaceAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class Laplace(Distribution): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = LaplaceAlgorithms, 17 | mu: Tensor = None, 18 | beta: Tensor = None, 19 | properties: Properties = None) -> None: 20 | parameters = {"mu": mu, "beta": beta} 21 | Distribution.__init__(self, 22 | algorithms=algorithms, 23 | parameters=parameters, 24 | properties=properties) 25 | 26 | def parameterInfo(self, 27 | shape: Tuple[int, ...] = (1,), 28 | latentShape: Tuple[int, ...] = ())-> ParameterInfo: 29 | initializers = { 30 | "mu": (shape, False), 31 | "beta": (shape, True) 32 | } # type: Dict[str, Tensor] 33 | return(initializers) 34 | 35 | @parameterProperty 36 | def mu(self) -> Tensor: 37 | return(self.__mu) 38 | 39 | @mu.setter(name="mu") 40 | def mu(self, mu: Tensor) -> None: 41 | self.__mu = mu 42 | 43 | @parameterProperty 44 | def beta(self) -> Tensor: 45 | return(self.__beta) 46 | 47 | @beta.setter(name="beta") 48 | def beta(self, beta: Tensor) -> None: 49 | self.__beta = beta 50 | 51 | @property 52 | def nonNegative(self) -> bool: 53 | return(False) 54 | 55 | @property 56 | def homogenous(self) -> bool: 57 | return(False) 58 | 59 | def cond(self) -> Distribution: 60 | return(self) 61 | 62 | @property 63 | def shape(self) -> Tuple[int, ...]: 64 | return(tuple(self.mu.get_shape().as_list())) 65 | 66 | @property 67 | def latentShape(self) -> Tuple[int, ...]: 68 | return(()) 69 | -------------------------------------------------------------------------------- /decompose/likelihoods/likelihood.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | from typing import Tuple, List 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.normal import Normal 7 | from decompose.distributions.distribution import Distribution, Properties 8 | 9 | 10 | class Likelihood(metaclass=ABCMeta): 11 | 12 | def __init__(self, M: Tuple[int, ...], K: int) -> None: 13 | self.__K = K 14 | self.__M = M 15 | self.__F = len(M) 16 | 17 | @abstractmethod 18 | def prepVars(self, f: int, U: List[Tensor], 19 | X: Tensor) -> Tuple[Tensor, Tensor, Tensor]: 20 | ... 21 | 22 | def lhUfk(self, Uf: Tensor, prepVars: Tuple[Tensor, ...], 23 | f: int, k: Tensor) -> Distribution: 24 | XVT, VVT, alpha = prepVars 25 | XvT = XVT[:, k] 26 | VvT = VVT[..., k] 27 | vvT = VVT[..., k, k] 28 | Ufk = Uf[k] 29 | 30 | UVvT = tf.reduce_sum(tf.transpose(Uf)*VvT, axis=-1) 31 | uvvT = Ufk*vvT 32 | Xtildev = XvT - UVvT + uvvT 33 | 34 | mu = Xtildev/vvT 35 | tau = vvT*alpha 36 | 37 | properties = Properties(name=f"lhU{f}k", 38 | drawType=self.noiseDistribution.drawType, 39 | updateType=self.noiseDistribution.updateType, 40 | persistent=False) 41 | lhUfk = Normal(mu=mu, tau=tau, properties=properties) 42 | return(lhUfk) 43 | 44 | @abstractmethod 45 | def update(self, U: Tuple[Tensor, ...], X: Tensor) -> None: 46 | ... 47 | 48 | @property 49 | @abstractmethod 50 | def noiseDistribution(self) -> Distribution: 51 | ... 52 | 53 | @abstractmethod 54 | def init(self, data: Tensor) -> None: 55 | ... 56 | 57 | @abstractmethod 58 | def llh(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 59 | ... 60 | 61 | @abstractmethod 62 | def loss(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 63 | ... 64 | 65 | @property 66 | def M(self) -> Tuple[int, ...]: 67 | return(self.__M) 68 | 69 | @property 70 | def F(self) -> int: 71 | return(self.__F) 72 | 73 | @property 74 | def K(self) -> int: 75 | return(self.__K) 76 | 77 | @property 78 | def id(self) -> str: 79 | likelihoodName = self.__class__.__name__ 80 | K = str(self.K) 81 | id = likelihoodName + K 82 | return(id) 83 | -------------------------------------------------------------------------------- /decompose/distributions/cenNormalRankOne.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Any, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.distribution import parameterProperty 8 | from decompose.distributions.normal import Normal 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.cenNormalRankOneAlgorithms import CenNormalRankOneAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class CenNormalRankOne(Normal): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = CenNormalRankOneAlgorithms, 17 | tau0: Tensor = None, 18 | tau1: Tensor = None, 19 | properties: Properties = None) -> None: 20 | parameters = {"tau0": tau0, "tau1": tau1} 21 | Distribution.__init__(self, 22 | algorithms=algorithms, 23 | parameters=parameters, 24 | properties=properties) 25 | 26 | def parameterInfo(self, 27 | shape: Tuple[int, int] = (5, 5), 28 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 29 | initializers = { 30 | "tau0": (shape[0], True), 31 | "tau1": (shape[1], True) 32 | } # type: Dict[str, Tensor] 33 | return(initializers) 34 | 35 | @property 36 | def mu(self) -> Tuple[Tensor, Tensor]: 37 | mu0 = tf.zeros_like(self.tau0) 38 | mu1 = tf.zeros_like(self.tau1) 39 | return((mu0, mu1)) 40 | 41 | @parameterProperty 42 | def tau0(self) -> Tensor: 43 | return(self.__tau0) 44 | 45 | @tau0.setter(name="tau0") 46 | def tau0(self, tau0: Tensor): 47 | self.__tau0 = tau0 48 | 49 | @parameterProperty 50 | def tau1(self) -> Tensor: 51 | return(self.__tau1) 52 | 53 | @tau1.setter(name="tau1") 54 | def tau1(self, tau1: Tensor): 55 | self.__tau1 = tau1 56 | 57 | @property 58 | def nonNegative(self) -> bool: 59 | return(False) 60 | 61 | @property 62 | def homogenous(self) -> bool: 63 | return(False) 64 | 65 | def cond(self) -> Distribution: 66 | return(self) 67 | 68 | @property 69 | def shape(self) -> Tuple[int, ...]: 70 | return(tuple(self.tau0.get_shape().as_list()) 71 | + tuple(self.tau1.get_shape().as_list())) 72 | 73 | @property 74 | def latentShape(self) -> Tuple[int, ...]: 75 | return(()) 76 | -------------------------------------------------------------------------------- /decompose/postU/postU.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, List 2 | from tensorflow import Tensor 3 | import tensorflow as tf 4 | from copy import copy 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.likelihoods.likelihood import Likelihood 8 | 9 | 10 | class PostU(object): 11 | 12 | def __init__(self, likelihood: Likelihood, prior: Distribution, 13 | f: int) -> None: 14 | self.__likelihood = likelihood 15 | self.__prior = prior 16 | self.__f = f 17 | self.__K = likelihood.K 18 | 19 | def f(self) -> int: 20 | return(self.__f) 21 | 22 | @property 23 | def prior(self): 24 | return(self.__prior) 25 | 26 | def updateUf(self, Uf, Ufk, k): 27 | UfUpdated = tf.concat((Uf[:k], Ufk, Uf[k+1:]), 0) 28 | return(UfUpdated) 29 | 30 | def update(self, U: List[Tensor], X: Tensor, 31 | transform: bool) -> Tuple[Tensor]: 32 | f, K = self.__f, self.__K 33 | U = copy(U) # copy the list since we change it below 34 | 35 | # update hyper parameters 36 | if not transform: 37 | self.prior.update(data=tf.transpose(U[f])) 38 | else: 39 | self.prior.fitLatents(data=tf.transpose(U[f])) 40 | 41 | # prepare update of the f-th factor 42 | prepVars = self.__likelihood.prepVars(f=f, U=U, X=X) 43 | 44 | # update the filters of the f-th factor 45 | def cond(k, U): 46 | return(tf.less(k, K)) 47 | 48 | def body(k, U): 49 | U = self.updateK(k, prepVars, U) 50 | return(k+1, U) 51 | 52 | k = tf.constant(0) 53 | loop_vars = [k, U] 54 | _, U = tf.while_loop(cond, body, loop_vars) 55 | return(U[f]) 56 | 57 | def updateK(self, k, prepVars, U): 58 | f = self.__f 59 | UfShape = U[f].get_shape() 60 | 61 | lhUfk = self.__likelihood.lhUfk(U[f], prepVars, f, k) 62 | postfk = lhUfk*self.prior[k].cond() 63 | Ufk = postfk.draw() 64 | Ufk = tf.expand_dims(Ufk, 0) 65 | 66 | normUfk = tf.norm(Ufk) 67 | notNanNorm = tf.logical_not(tf.is_nan(normUfk)) 68 | finiteNorm = tf.is_finite(normUfk) 69 | positiveNorm = normUfk > 0. 70 | isValid = tf.logical_and(notNanNorm, 71 | tf.logical_and(finiteNorm, 72 | positiveNorm)) 73 | Uf = tf.cond(isValid, lambda: self.updateUf(U[f], Ufk, k), 74 | lambda: U[f]) 75 | 76 | # TODO: if valid -> self.__likelihood.lhU()[f].updateUfk(U[f][k], k) 77 | Uf.set_shape(UfShape) 78 | U[f] = Uf 79 | return(U) 80 | -------------------------------------------------------------------------------- /decompose/distributions/jumpNormal.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.distribution import parameterProperty 9 | from decompose.distributions.algorithms import Algorithms 10 | from decompose.distributions.jumpNormalAlgorithms import JumpNormalAlgorithms 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class JumpNormal(Distribution): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = JumpNormalAlgorithms, 17 | mu: Tensor = None, 18 | tau: Tensor = None, 19 | nu: Tensor = None, 20 | beta: Tensor = None, 21 | properties: Properties = None) -> None: 22 | parameters = {"mu": mu, "tau": tau, "nu": nu, "beta": beta} 23 | Distribution.__init__(self, 24 | algorithms=algorithms, 25 | parameters=parameters, 26 | properties=properties) 27 | 28 | def parameterInfo(self, 29 | shape: Tuple[int, ...] = (1,), 30 | latentShape: Tuple[int, ...] = ())-> ParameterInfo: 31 | initializers = { 32 | "mu": (shape, False), 33 | "tau": (shape, True), 34 | "nu": (shape, False), 35 | "beta": (shape, True), 36 | } # type: Dict[str, Tensor] 37 | return(initializers) 38 | 39 | @parameterProperty 40 | def mu(self) -> Tensor: 41 | return(self.__mu) 42 | 43 | @mu.setter(name="mu") 44 | def mu(self, mu: Tensor) -> None: 45 | self.__mu = mu 46 | 47 | @parameterProperty 48 | def tau(self) -> Tensor: 49 | return(self.__tau) 50 | 51 | @tau.setter(name="tau") 52 | def tau(self, tau: Tensor) -> None: 53 | self.__tau = tau 54 | 55 | @parameterProperty 56 | def nu(self) -> Tensor: 57 | return(self.__nu) 58 | 59 | @nu.setter(name="nu") 60 | def nu(self, nu: Tensor) -> None: 61 | self.__nu = nu 62 | 63 | @parameterProperty 64 | def beta(self) -> Tensor: 65 | return(self.__beta) 66 | 67 | @beta.setter(name="beta") 68 | def beta(self, beta: Tensor) -> None: 69 | self.__beta = beta 70 | 71 | @property 72 | def nonNegative(self) -> bool: 73 | return(False) 74 | 75 | @property 76 | def homogenous(self) -> bool: 77 | return(False) 78 | 79 | def cond(self) -> Distribution: 80 | return(self) 81 | 82 | @property 83 | def shape(self) -> Tuple[int, ...]: 84 | return(tuple(self.mu.get_shape().as_list())) 85 | 86 | @property 87 | def latentShape(self) -> Tuple[int, ...]: 88 | return(()) 89 | -------------------------------------------------------------------------------- /decompose/distributions/cenTAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.tAlgorithms import TAlgorithms 7 | 8 | 9 | class CenTAlgorithms(TAlgorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | Psi, nu = parameters["Psi"], parameters["nu"] 14 | mu = tf.zeros_like(Psi) 15 | parameters = {"nu": nu, "mu": mu, "Psi": Psi} 16 | samples = TAlgorithms.sample(parameters=parameters, 17 | nSamples=nSamples) 18 | return(samples) 19 | 20 | @classmethod 21 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 22 | Psi = parameters["Psi"] 23 | mode = tf.zeros_like(Psi) 24 | return(mode) 25 | 26 | @classmethod 27 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 28 | Psi, nu = parameters["Psi"], parameters["nu"] 29 | mu = tf.zeros_like(Psi) 30 | parameters = {"nu": nu, "mu": mu, "Psi": Psi} 31 | pdf = TAlgorithms.pdf(parameters=parameters, data=data) 32 | return(pdf) 33 | 34 | @classmethod 35 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 36 | Psi, nu = parameters["Psi"], parameters["nu"] 37 | mu = tf.zeros_like(Psi) 38 | parameters = {"nu": nu, "mu": mu, "Psi": Psi} 39 | llh = TAlgorithms.llh(parameters=parameters, data=data) 40 | return(llh) 41 | 42 | @classmethod 43 | def fit(cls, parameters: Dict[str, Tensor], 44 | data: tf.Tensor) -> Dict[str, Tensor]: 45 | Psi, nu = parameters["Psi"], parameters["nu"] 46 | 47 | p = 1. 48 | 49 | n = data.get_shape()[0].value 50 | Y = data 51 | for i in range(5): 52 | delta = Y**2/Psi 53 | w = (nu + p)/(nu + delta) 54 | 55 | PsiNew = tf.reduce_mean(w*Y**2, axis=0) 56 | cond = tf.logical_and(tf.is_finite(PsiNew), 57 | tf.greater(PsiNew, 1e-6)) 58 | Psi = tf.where(cond, PsiNew, Psi*tf.ones_like(PsiNew)) 59 | 60 | delta = Y**2/Psi 61 | nuNew = cls.nuStep(nu, n, delta) 62 | cond = tf.logical_and(tf.is_finite(nuNew), 63 | tf.greater(nuNew, 0.)) 64 | nu = tf.where(cond, nuNew, nu*tf.ones_like(nuNew)) 65 | 66 | tau = w 67 | updatedParameters = {"nu": nu, "Psi": Psi, "tau": tau} 68 | return(updatedParameters) 69 | 70 | @classmethod 71 | def fitLatents(cls, parameters: Dict[str, Tensor], 72 | data: Tensor) -> Dict[str, Tensor]: 73 | Psi, nu = parameters["Psi"], parameters["nu"] 74 | p = 1. 75 | Y = data 76 | delta = Y**2/Psi 77 | w = (nu + p)/(nu + delta) 78 | return({"tau": w}) 79 | -------------------------------------------------------------------------------- /decompose/likelihoods/normal2dLikelihood.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, List 2 | import tensorflow as tf 3 | from tensorflow import Tensor, DType 4 | 5 | from decompose.distributions.distribution import Properties 6 | from decompose.distributions.distribution import DrawType, UpdateType 7 | from decompose.distributions.cenNormal import CenNormal 8 | from decompose.likelihoods.likelihood import Likelihood 9 | 10 | 11 | class Normal2dLikelihood(Likelihood): 12 | 13 | def __init__(self, M: Tuple[int, ...], K: int=1, tau: float = 1./1e10, 14 | drawType: DrawType = DrawType.SAMPLE, 15 | updateType: UpdateType = UpdateType.ALL, 16 | dtype: DType = tf.float32) -> None: 17 | Likelihood.__init__(self, M, K) 18 | self.__tauInit = tau 19 | self.__dtype = dtype 20 | self.__properties = Properties(name='likelihood', 21 | drawType=drawType, 22 | dtype=dtype, 23 | updateType=updateType, 24 | persistent=True) 25 | 26 | def init(self, data: Tensor) -> None: 27 | tau = self.__tauInit 28 | dtype = self.__dtype 29 | properties = self.__properties 30 | noiseDistribution = CenNormal(tau=tf.constant([tau], dtype=dtype), 31 | properties=properties) 32 | self.__noiseDistribution = noiseDistribution 33 | 34 | @property 35 | def noiseDistribution(self) -> CenNormal: 36 | return(self.__noiseDistribution) 37 | 38 | def residuals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 39 | assert(len(U) == 2) 40 | U0, U1 = U 41 | Xhat = tf.matmul(tf.transpose(U0), U1) 42 | residuals = tf.reshape(X-Xhat, (-1,)) 43 | return(residuals) 44 | 45 | def llh(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 46 | r = self.residuals(U, X) 47 | llh = tf.reduce_sum(self.noiseDistribution.llh(r)) 48 | return(llh) 49 | 50 | def loss(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 51 | loss = tf.reduce_sum(self.residuals(U, X)**2) 52 | return(loss) 53 | 54 | def update(self, U: Tuple[Tensor, ...], X: Tensor) -> None: 55 | if self.noiseDistribution.updateType == UpdateType.ALL: 56 | residuals = self.residuals(U, X) 57 | flattenedResiduals = tf.reshape(residuals, (-1,))[..., None] 58 | self.noiseDistribution.update(flattenedResiduals) 59 | 60 | def prepVars(self, f: int, U: List[Tensor], 61 | X: Tensor) -> Tuple[Tensor, Tensor, Tensor]: 62 | if f == 0: 63 | U1 = U[1] 64 | else: 65 | U1 = U[0] 66 | X = tf.transpose(X) 67 | 68 | U1T = tf.transpose(U1) 69 | A = tf.matmul(X, U1T) 70 | B = tf.matmul(U1, U1T) 71 | alpha = self.noiseDistribution.tau 72 | 73 | return(A, B, alpha) 74 | -------------------------------------------------------------------------------- /decompose/likelihoods/specificNormal2dLikelihood.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple, List 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.distribution import Properties 7 | from decompose.distributions.distribution import DrawType, UpdateType 8 | from decompose.distributions.cenNormal import CenNormal 9 | from decompose.likelihoods.likelihood import Likelihood 10 | 11 | 12 | class SpecificNormal2dLikelihood(Likelihood): 13 | 14 | def __init__(self, M: Tuple[int, ...], K: int=1, tau: float = 1./1e10, 15 | drawType: DrawType = DrawType.SAMPLE, 16 | updateType: UpdateType = UpdateType.ALL, 17 | dtype=tf.float32) -> None: 18 | Likelihood.__init__(self, M, K) 19 | self.__tauInit = tau 20 | self.__dtype = dtype 21 | self.__properties = Properties(name='likelihood', 22 | drawType=drawType, 23 | dtype=dtype, 24 | updateType=updateType, 25 | persistent=True) 26 | 27 | def init(self, data: Tensor) -> None: 28 | tau = self.__tauInit 29 | properties = self.__properties 30 | tau = tf.ones_like(data[0])*tau # TODO is using ones really useful 31 | noiseDistribution = CenNormal(tau=tau, 32 | properties=properties) 33 | self.__noiseDistribution = noiseDistribution 34 | 35 | @property 36 | def noiseDistribution(self) -> CenNormal: 37 | return(self.__noiseDistribution) 38 | 39 | def residuals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 40 | assert(len(U) == 2) 41 | U0, U1 = U 42 | Xhat = tf.matmul(tf.transpose(U0), U1) 43 | residuals = X-Xhat 44 | return(residuals) 45 | 46 | def llh(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 47 | r = self.residuals(U, X) 48 | llh = tf.reduce_sum(self.noiseDistribution.llh(r)) 49 | return(llh) 50 | 51 | def loss(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 52 | loss = tf.reduce_sum(self.residuals(U, X)**2) 53 | return(loss) 54 | 55 | def update(self, U: Tuple[Tensor, ...], X: Tensor) -> None: 56 | if self.noiseDistribution.updateType == UpdateType.ALL: 57 | residuals = self.residuals(U, X) 58 | self.noiseDistribution.update(residuals) 59 | 60 | def prepVars(self, f: int, U: List[Tensor], 61 | X: Tensor) -> Tuple[Tensor, Tensor, Tensor]: 62 | if f == 0: 63 | U1 = U[1] 64 | alpha1 = self.noiseDistribution.tau 65 | alpha = tf.ones_like(X[:, 0]) 66 | elif f == 1: 67 | U1 = U[0] 68 | alpha1 = tf.ones_like(X[:, 0]) 69 | alpha = self.noiseDistribution.tau 70 | X = tf.transpose(X) 71 | 72 | U1T = tf.transpose(U1) 73 | A = tf.matmul(X, U1T*alpha1[..., None]) 74 | B = tf.matmul(U1*alpha1, U1T) 75 | return(A, B, alpha) 76 | -------------------------------------------------------------------------------- /decompose/likelihoods/allSpecificNormal2dLikelihood.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple, List 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.distribution import Properties 7 | from decompose.distributions.distribution import DrawType, UpdateType 8 | from decompose.distributions.cenNormalRankOne import CenNormalRankOne 9 | from decompose.likelihoods.likelihood import Likelihood 10 | 11 | 12 | class AllSpecificNormal2dLikelihood(Likelihood): 13 | 14 | def __init__(self, M: Tuple[int, ...], K: int=1, tau: float = 1./1e2, 15 | drawType: DrawType = DrawType.SAMPLE, 16 | updateType: UpdateType = UpdateType.ALL, 17 | dtype=tf.float32) -> None: 18 | Likelihood.__init__(self, M, K) 19 | self.__tauInit = tau 20 | self.__dtype = dtype 21 | self.__properties = Properties(name='likelihood', 22 | drawType=drawType, 23 | dtype=dtype, 24 | updateType=updateType, 25 | persistent=True) 26 | 27 | def init(self, data: Tensor) -> None: 28 | M, N = data.get_shape().as_list() 29 | tau0Init = tf.random_uniform(shape=(M,)) 30 | tau1Init = tf.random_uniform(shape=(N,)) 31 | properties = self.__properties 32 | noiseDistribution = CenNormalRankOne(tau0=tau0Init, 33 | tau1=tau1Init, 34 | properties=properties) 35 | self.__noiseDistribution = noiseDistribution 36 | 37 | @property 38 | def noiseDistribution(self) -> CenNormalRankOne: 39 | return(self.__noiseDistribution) 40 | 41 | def residuals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 42 | assert(len(U) == 2) 43 | U0, U1 = U 44 | Xhat = tf.matmul(tf.transpose(U0), U1) 45 | residuals = X-Xhat 46 | return(residuals) 47 | 48 | def llh(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 49 | r = self.residuals(U, X) 50 | llh = tf.reduce_sum(self.noiseDistribution.llh(r)) 51 | return(llh) 52 | 53 | def loss(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 54 | loss = tf.reduce_sum(self.residuals(U, X)**2) 55 | return(loss) 56 | 57 | def update(self, U: Tuple[Tensor, ...], X: Tensor) -> None: 58 | if self.noiseDistribution.updateType == UpdateType.ALL: 59 | residuals = self.residuals(U, X) 60 | self.noiseDistribution.update(residuals) 61 | 62 | def prepVars(self, f: int, U: List[Tensor], 63 | X: Tensor) -> Tuple[Tensor, Tensor, Tensor]: 64 | if f == 0: 65 | U1 = U[1] 66 | alpha1 = self.noiseDistribution.tau1 67 | alpha = self.noiseDistribution.tau0 68 | elif f == 1: 69 | U1 = U[0] 70 | alpha1 = self.noiseDistribution.tau0 71 | alpha = self.noiseDistribution.tau1 72 | X = tf.transpose(X) 73 | 74 | U1T = tf.transpose(U1) 75 | A = tf.matmul(X, U1T*alpha1[..., None]) 76 | B = tf.matmul(U1*alpha1, U1T) 77 | return(A, B, alpha) 78 | -------------------------------------------------------------------------------- /decompose/tests/test_all_priors.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow.python import pywrap_tensorflow 5 | from decompose.models.tensorFactorisation import TensorFactorisation 6 | from decompose.stopCriterions.stopCriterion import StopHook 7 | from decompose.distributions.cenNormal import CenNormal 8 | from decompose.distributions.normal import Normal 9 | from decompose.distributions.exponential import Exponential 10 | from decompose.distributions.laplace import Laplace 11 | from decompose.distributions.t import T 12 | from decompose.distributions.lomax import Lomax 13 | from decompose.distributions.cenNnNormal import CenNnNormal 14 | from decompose.distributions.cenT import CenT 15 | from decompose.distributions.cenNnT import CenNnT 16 | from decompose.distributions.uniform import Uniform 17 | from decompose.distributions.nnUniform import NnUniform 18 | from decompose.distributions.nnNormal import NnNormal 19 | from decompose.distributions.cenDoubleLomax import CenDoubleLomax 20 | from decompose.distributions.cenLaplace import CenLaplace 21 | from decompose.distributions.cenNnFullyElasticNet import CenNnFullyElasticNet 22 | from decompose.data.random import Random 23 | from decompose.stopCriterions.nIterations import NIterations 24 | 25 | 26 | tf.logging.set_verbosity(tf.logging.INFO) 27 | 28 | 29 | @pytest.fixture(params=[Laplace, CenNnFullyElasticNet, 30 | NnNormal, CenDoubleLomax, CenLaplace, 31 | CenNnNormal, CenNnT, CenNormal, CenT, 32 | Exponential, Laplace, Lomax, 33 | Uniform, NnUniform, Normal, T]) 34 | def PriorDistribution(request): 35 | """A fixture that provides a prior distribution at a time.""" 36 | prior = request.param 37 | return(prior) 38 | 39 | 40 | @pytest.mark.system 41 | @pytest.mark.slow 42 | def test_all_priors(tmpdir, PriorDistribution): 43 | """Tests distributions in a tensor factorisation model. 44 | 45 | The test is useful to check for obious error such as shape 46 | mismatches or data type mismatches. The test does not check 47 | whether the priors are correctly implemented. The test fits a 48 | model with the specified priors to random data. 49 | """ 50 | # create temporary directory where the model and its checkpoints are stored 51 | modelDirectory = str(tmpdir.mkdir("model")) 52 | 53 | # create a synthetic low rank dataset 54 | randomData = Random(M=(100, 200)) 55 | 56 | # instantiate a model 57 | priors, K, dtype = [PriorDistribution(), PriorDistribution()], 3, np.float32 58 | tefa = TensorFactorisation.getEstimator(priors=priors, 59 | K=K, 60 | stopCriterionInit=NIterations(10), 61 | stopCriterionEM=NIterations(10), 62 | stopCriterionBCD=NIterations(10), 63 | dtype=tf.as_dtype(dtype), 64 | path=modelDirectory, 65 | device="/cpu:0") 66 | 67 | # create input_fna 68 | input_fn = randomData.input_fn 69 | 70 | # train the model 71 | tefa.train(input_fn=input_fn, 72 | hooks=[StopHook()]) 73 | -------------------------------------------------------------------------------- /decompose/distributions/lomax.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.distribution import Distribution 8 | from decompose.distributions.exponential import Exponential 9 | from decompose.distributions.distribution import parameterProperty 10 | from decompose.distributions.algorithms import Algorithms 11 | from decompose.distributions.lomaxAlgorithms import LomaxAlgorithms 12 | from decompose.distributions.distribution import Properties 13 | 14 | 15 | class Lomax(Distribution): 16 | def __init__(self, 17 | algorithms: Type[Algorithms] = LomaxAlgorithms, 18 | alpha: Tensor = None, 19 | beta: Tensor = None, 20 | tau: Tensor = None, 21 | properties: Properties = None) -> None: 22 | parameters = {"alpha": alpha, "beta": beta, "tau": tau} 23 | Distribution.__init__(self, 24 | algorithms=algorithms, 25 | parameters=parameters, 26 | properties=properties) 27 | 28 | def parameterInfo(self, 29 | shape: Tuple[int, ...] = (1,), 30 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 31 | initializers = { 32 | "alpha": (shape, True), 33 | "beta": (shape, True), 34 | "tau": (latentShape + shape, True) 35 | } # type: Dict[str, Tensor] 36 | return(initializers) 37 | 38 | @parameterProperty 39 | def mu(self) -> Tensor: 40 | mu = tf.zeros_like(self.alpha) 41 | return(mu) 42 | 43 | @parameterProperty 44 | def alpha(self) -> tf.Tensor: 45 | return(self.__alpha) 46 | 47 | @alpha.setter(name="alpha") 48 | def alpha(self, alpha: tf.Tensor) -> None: 49 | self.__alpha = alpha 50 | 51 | @parameterProperty 52 | def beta(self) -> tf.Tensor: 53 | return(self.__beta) 54 | 55 | @beta.setter(name="beta") 56 | def beta(self, beta: tf.Tensor) -> None: 57 | self.__beta = beta 58 | 59 | @parameterProperty 60 | def tau(self) -> Tensor: 61 | return(self.__tau) 62 | 63 | @tau.setter(name="tau") 64 | def tau(self, tau: Tensor) -> None: 65 | self.__tau = tau 66 | 67 | @property 68 | def nonNegative(self) -> bool: 69 | return(True) 70 | 71 | @property 72 | def homogenous(self) -> bool: 73 | return(False) 74 | 75 | def cond(self) -> Exponential: 76 | tau = self.tau 77 | name = self.name + "Cond" 78 | properties = Properties(name=name, 79 | drawType=self.drawType, 80 | updateType=self.updateType, 81 | persistent=False) 82 | cond = Exponential(beta=1./tau, 83 | properties=properties) 84 | return(cond) 85 | 86 | @property 87 | def shape(self) -> Tuple[int, ...]: 88 | return(tuple(self.mu.get_shape().as_list())) 89 | 90 | @property 91 | def latentShape(self) -> Tuple[int, ...]: 92 | ndims = len(self.tau.get_shape().as_list()) - len(self.shape) 93 | return(tuple(self.tau.get_shape().as_list()[:ndims])) 94 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_cenNnFullyElasticNetAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.cenNnFullyElasticNetAlgorithms import CenNnFullyElasticNetAlgorithms 8 | 9 | 10 | @pytest.mark.slow 11 | def test_cenNnFullyElasticNet_sample(): 12 | """Test whether the parameters can be recovered from many samples.""" 13 | b = np.array([0., 0., 0., 1., 1., 1., 2., 2., 2.]) 14 | 15 | mu = np.array([-2., 1., 0., 1., 1., 1., 2., 2., 2.]) 16 | tau = np.array([.5, 1., 2., 1., 1., 1., 2., 2., 2.]) 17 | 18 | betaExponential = np.array([1., 1., 1., .5, 1., 2., 2., 2., 2.]) 19 | 20 | alpha = np.array([1., 1., 1., 1., 1., 1., 1., 1.5, 2.]) 21 | beta = np.array([1., 1., 1., 1., 1., 1., 1., 2., 3.]) 22 | tauLomax = np.array([1., 1., 1., 1., 1., 1., 1., 2., 3.]) 23 | 24 | nParameters = alpha.shape[0] 25 | nSamples = 1000000 26 | nIterations = 2 27 | 28 | # sample from the distribution using the true parameters 29 | trueParameters = {"b": tf.constant(b), 30 | "mu": tf.constant(mu), 31 | "tau": tf.constant(tau), 32 | "betaExponential": tf.constant(betaExponential), 33 | "alpha": tf.constant(alpha), 34 | "beta": tf.constant(beta), 35 | "tauLomax": tf.constant(tauLomax)} 36 | tfData = CenNnFullyElasticNetAlgorithms.sample(parameters=trueParameters, 37 | nSamples=nSamples) 38 | tfData = tf.Print(tfData, [tfData], "tfData") 39 | # random initialize parameter estimates 40 | parameters = {"b": tf.constant(np.ones(nParameters)), 41 | "mu": tf.constant(np.ones(nParameters)), 42 | "tau": tf.constant(np.ones(nParameters)), 43 | "betaExponential": tf.constant(np.ones(nParameters)), 44 | "alpha": tf.constant(np.ones(nParameters)), 45 | "beta": tf.constant(np.ones(nParameters)), 46 | "tauLomax": tf.constant(np.ones((nSamples, nParameters)))} 47 | variables = {key: tf.get_variable(key, initializer=value) 48 | for key, value in parameters.items()} 49 | 50 | # estimate the parameters from the random sample 51 | parameterUpdate = CenNnFullyElasticNetAlgorithms.fit(parameters=variables, 52 | data=tfData) 53 | varUpdates = {} 54 | for key, var in variables.items(): 55 | varUpdates[key] = tf.assign(var, parameterUpdate[key]) 56 | with tf.Session() as sess: 57 | # initialize variables 58 | for key, var in variables.items(): 59 | sess.run(var.initializer) 60 | # update the variables 61 | for i in range(nIterations): 62 | sess.run(varUpdates) 63 | # get estimated parameters 64 | parameters = sess.run(variables) 65 | 66 | # check the estimations 67 | print("b", parameters['b']) 68 | alphaHat = parameters["alpha"] 69 | assert(alphaHat.shape == alpha.shape) 70 | assert(np.allclose(alphaHat, alpha, atol=1e-1)) 71 | betaHat = parameters["beta"] 72 | assert(betaHat.shape == beta.shape) 73 | assert(np.allclose(betaHat, beta, atol=1e-1)) 74 | tf.reset_default_graph() 75 | -------------------------------------------------------------------------------- /decompose/distributions/cenDoubleLomax.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.distribution import Distribution 8 | from decompose.distributions.cenLaplace import CenLaplace 9 | from decompose.distributions.distribution import parameterProperty 10 | from decompose.distributions.algorithms import Algorithms 11 | from decompose.distributions.cenDoubleLomaxAlgorithms import CenDoubleLomaxAlgorithms 12 | from decompose.distributions.distribution import Properties 13 | 14 | 15 | class CenDoubleLomax(Distribution): 16 | def __init__(self, 17 | algorithms: Type[Algorithms] = CenDoubleLomaxAlgorithms, 18 | alpha: Tensor = None, 19 | beta: Tensor = None, 20 | tau: Tensor = None, 21 | properties: Properties = None) -> None: 22 | parameters = {"alpha": alpha, "beta": beta, "tau": tau} 23 | Distribution.__init__(self, 24 | algorithms=algorithms, 25 | parameters=parameters, 26 | properties=properties) 27 | 28 | def parameterInfo(self, 29 | shape: Tuple[int, ...] = (1,), 30 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 31 | initializers = { 32 | "alpha": (shape, True), 33 | "beta": (shape, True), 34 | "tau": (latentShape + shape, True) 35 | } # type: Dict[str, Tensor] 36 | return(initializers) 37 | 38 | @parameterProperty 39 | def mu(self) -> Tensor: 40 | mu = tf.zeros_like(self.alpha) 41 | return(mu) 42 | 43 | @parameterProperty 44 | def alpha(self) -> tf.Tensor: 45 | return(self.__alpha) 46 | 47 | @alpha.setter(name="alpha") 48 | def alpha(self, alpha: tf.Tensor) -> None: 49 | self.__alpha = alpha 50 | 51 | @parameterProperty 52 | def beta(self) -> tf.Tensor: 53 | return(self.__beta) 54 | 55 | @beta.setter(name="beta") 56 | def beta(self, beta: tf.Tensor) -> None: 57 | self.__beta = beta 58 | 59 | @parameterProperty 60 | def tau(self) -> Tensor: 61 | return(self.__tau) 62 | 63 | @tau.setter(name="tau") 64 | def tau(self, tau: Tensor) -> None: 65 | self.__tau = tau 66 | 67 | @property 68 | def nonNegative(self) -> bool: 69 | return(False) 70 | 71 | @property 72 | def homogenous(self) -> bool: 73 | return(False) 74 | 75 | def cond(self) -> CenLaplace: 76 | tau = self.tau 77 | name = self.name + "Cond" 78 | properties = Properties(name=name, 79 | drawType=self.drawType, 80 | updateType=self.updateType, 81 | persistent=False) 82 | cond = CenLaplace(beta=1./tau, 83 | properties=properties) 84 | return(cond) 85 | 86 | @property 87 | def shape(self) -> Tuple[int, ...]: 88 | return(tuple(self.mu.get_shape().as_list())) 89 | 90 | @property 91 | def latentShape(self) -> Tuple[int, ...]: 92 | ndims = len(self.tau.get_shape().as_list()) - len(self.shape) 93 | return(tuple(self.tau.get_shape().as_list()[:ndims])) 94 | -------------------------------------------------------------------------------- /decompose/likelihoods/normalNdLikelihood.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple, List 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | import string 6 | 7 | from decompose.distributions.distribution import DrawType, UpdateType 8 | from decompose.distributions.cenNormal import CenNormal 9 | from decompose.likelihoods.likelihood import Likelihood 10 | from decompose.distributions.distribution import Properties 11 | 12 | 13 | class NormalNdLikelihood(Likelihood): 14 | 15 | def __init__(self, M: Tuple[int, ...], K: int=1, tau: float = 1./1e10, 16 | drawType: DrawType = DrawType.SAMPLE, 17 | updateType: UpdateType = UpdateType.ALL, 18 | dtype=tf.float32) -> None: 19 | Likelihood.__init__(self, M, K) 20 | self.__tauInit = tau 21 | self.__dtype = dtype 22 | self.__properties = Properties(name='likelihood', 23 | drawType=drawType, 24 | updateType=updateType, 25 | persistent=True) 26 | 27 | def init(self, data: Tensor) -> None: 28 | tau = self.__tauInit 29 | dtype = self.__dtype 30 | properties = self.__properties 31 | noiseDistribution = CenNormal(tau=tf.constant([tau], dtype=dtype), 32 | properties=properties) 33 | self.__noiseDistribution = noiseDistribution 34 | 35 | @property 36 | def noiseDistribution(self) -> CenNormal: 37 | return(self.__noiseDistribution) 38 | 39 | def residuals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 40 | F = len(U) 41 | axisIds = string.ascii_lowercase[:F] 42 | subscripts = f'k{",k".join(axisIds)}->{axisIds}' 43 | Xhat = tf.einsum(subscripts, *U) 44 | residuals = X-Xhat 45 | return(residuals) 46 | 47 | def llh(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 48 | r = self.residuals(U, X) 49 | llh = tf.reduce_sum(self.noiseDistribution.llh(r)) 50 | return(llh) 51 | 52 | def loss(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 53 | loss = tf.reduce_sum(self.residuals(U, X)**2) 54 | return(loss) 55 | 56 | def update(self, U: Tuple[Tensor, ...], X: Tensor) -> None: 57 | if self.noiseDistribution.updateType == UpdateType.ALL: 58 | residuals = self.residuals(U, X) 59 | flattenedResiduals = tf.reshape(residuals, (-1,))[..., None] 60 | self.noiseDistribution.update(flattenedResiduals) 61 | 62 | def outterTensorProduct(self, Us): 63 | F = len(Us) 64 | axisIds = string.ascii_lowercase[:F] 65 | subscripts = f'k{",k".join(axisIds)}->{axisIds}k' 66 | Xhat = tf.einsum(subscripts, *Us) 67 | return(Xhat) 68 | 69 | def prepVars(self, f: int, U: List[Tensor], 70 | X: Tensor) -> Tuple[Tensor, Tensor, Tensor]: 71 | F = self.F 72 | Umf = [U[g] for g in range(F) if g != f] 73 | UmfOutter = self.outterTensorProduct(Umf) 74 | 75 | rangeFm1 = list(range(F-1)) 76 | A = tf.tensordot(X, UmfOutter, 77 | axes=([g for g in range(F) if g != f], rangeFm1)) 78 | B = tf.tensordot(UmfOutter, UmfOutter, 79 | axes=(rangeFm1, rangeFm1)) 80 | alpha = self.noiseDistribution.tau 81 | return(A, B, alpha) 82 | -------------------------------------------------------------------------------- /decompose/distributions/cenNnT.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import Distribution 7 | from decompose.distributions.distribution import ParameterInfo 8 | from decompose.distributions.nnNormal import NnNormal 9 | from decompose.distributions.distribution import parameterProperty 10 | from decompose.distributions.algorithms import Algorithms 11 | from decompose.distributions.cenNnTAlgorithms import CenNnTAlgorithms 12 | from decompose.distributions.distribution import Properties 13 | 14 | 15 | class CenNnT(Distribution): 16 | def __init__(self, 17 | algorithms: Type[Algorithms] = CenNnTAlgorithms, 18 | Psi: Tensor = None, 19 | nu: Tensor = None, 20 | tau: Tensor = None, 21 | properties: Properties = None) -> None: 22 | parameters = {"Psi": Psi, "nu": nu, "tau": tau} 23 | Distribution.__init__(self, 24 | algorithms=algorithms, 25 | parameters=parameters, 26 | properties=properties) 27 | 28 | def parameterInfo(self, 29 | shape: Tuple[int, ...] = (1,), 30 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 31 | initializers = { 32 | "Psi": (shape, True), 33 | "nu": (shape, True), 34 | "tau": (latentShape + shape, True) 35 | } # type: Dict[str, Tensor] 36 | return(initializers) 37 | 38 | @parameterProperty 39 | def mu(self) -> Tensor: 40 | mu = tf.zeros_like(self.Psi) 41 | return(mu) 42 | 43 | @parameterProperty 44 | def Psi(self) -> tf.Tensor: 45 | return(self.__Psi) 46 | 47 | @Psi.setter(name="Psi") 48 | def Psi(self, Psi: tf.Tensor) -> None: 49 | self.__Psi = Psi 50 | 51 | @parameterProperty 52 | def nu(self) -> tf.Tensor: 53 | return(self.__nu) 54 | 55 | @nu.setter(name="nu") 56 | def nu(self, nu: tf.Tensor) -> None: 57 | self.__nu = nu 58 | 59 | @parameterProperty 60 | def tau(self) -> Tensor: 61 | return(self.__tau) 62 | 63 | @tau.setter(name="tau") 64 | def tau(self, tau: Tensor) -> None: 65 | self.__tau = tau 66 | 67 | @property 68 | def nonNegative(self) -> bool: 69 | return(True) 70 | 71 | @property 72 | def homogenous(self) -> bool: 73 | return(False) 74 | 75 | def cond(self) -> NnNormal: 76 | mu = self.mu 77 | Psi = self.Psi 78 | tau = self.tau 79 | mu = tf.ones_like(tau)*mu 80 | Psi = tf.ones_like(tau)*Psi 81 | name = self.name + "Cond" 82 | properties = Properties(name=name, 83 | drawType=self.drawType, 84 | updateType=self.updateType, 85 | persistent=False) 86 | cond = NnNormal(mu=mu, 87 | tau=tau/Psi, 88 | properties=properties) 89 | return(cond) 90 | 91 | @property 92 | def shape(self) -> Tuple[int, ...]: 93 | return(tuple(self.mu.get_shape().as_list())) 94 | 95 | @property 96 | def latentShape(self) -> Tuple[int, ...]: 97 | ndims = len(self.tau.get_shape().as_list()) - len(self.shape) 98 | return(tuple(self.tau.get_shape().as_list()[:ndims])) 99 | -------------------------------------------------------------------------------- /decompose/distributions/productDistLookup.py: -------------------------------------------------------------------------------- 1 | class ProductDict(object): 2 | class __ProductDict(object): 3 | def __init__(self): 4 | self.data = {} 5 | 6 | # Normal times Normal 7 | from decompose.distributions.normal import Normal 8 | from decompose.distributions.normalNormal import NormalNormal 9 | self.data[frozenset((Normal, Normal))] = NormalNormal() 10 | 11 | # Normal times CenNormal 12 | from decompose.distributions.cenNormal import CenNormal 13 | self.data[frozenset((Normal, CenNormal))] = NormalNormal() 14 | 15 | # Normal times NnNormal 16 | from decompose.distributions.nnNormal import NnNormal 17 | from decompose.distributions.normalNnNormal import NormalNnNormal 18 | self.data[frozenset((Normal, NnNormal))] = NormalNnNormal() 19 | 20 | # Normal times CenNnNormal 21 | from decompose.distributions.cenNnNormal import CenNnNormal 22 | self.data[frozenset((Normal, CenNnNormal))] = NormalNnNormal() 23 | 24 | # Normal times Uniform 25 | from decompose.distributions.uniform import Uniform 26 | from decompose.distributions.normalUniform import NormalUniform 27 | self.data[frozenset((Normal, Uniform))] = NormalUniform() 28 | 29 | # Normal times NnUniform 30 | from decompose.distributions.nnUniform import NnUniform 31 | from decompose.distributions.normalNnUniform import NormalNnUniform 32 | self.data[frozenset((Normal, NnUniform))] = NormalNnUniform() 33 | 34 | # Normal times Exponential 35 | from decompose.distributions.exponential import Exponential 36 | from decompose.distributions.normalExponential import NormalExponential 37 | self.data[frozenset((Normal, Exponential))] = NormalExponential() 38 | 39 | from decompose.distributions.cenLaplace import CenLaplace 40 | from decompose.distributions.normalLaplace import NormalLaplace 41 | self.data[frozenset((Normal, CenLaplace))] = NormalLaplace() 42 | 43 | from decompose.distributions.laplace import Laplace 44 | self.data[frozenset((Normal, Laplace))] = NormalLaplace() 45 | 46 | # Normal times CenNnFullyElasticNet 47 | from decompose.distributions.cenNnFullyElasticNetCond import CenNnFullyElasticNetCond 48 | from decompose.distributions.normalCenNnFullyElasticNetCond import NormalCenNnFullyElasticNetCond 49 | self.data[frozenset((Normal, CenNnFullyElasticNetCond))] = NormalCenNnFullyElasticNetCond() 50 | 51 | 52 | def lookup(self, d0, d1): 53 | from decompose.distributions.distribution import Distribution 54 | if not isinstance(d0, Distribution): 55 | raise ValueError("Both factors must be distributions") 56 | if not isinstance(d1, Distribution): 57 | raise ValueError("Both factors must be distributions") 58 | 59 | pdClass = self.data[frozenset((type(d0), type(d1)))] 60 | pd = pdClass.fromUnordered(d0, d1) 61 | return(pd) 62 | 63 | instance = None # type: "ProductDict" 64 | 65 | def __init__(self): 66 | if not ProductDict.instance: 67 | ProductDict.instance = ProductDict.__ProductDict() 68 | 69 | def __getattr__(self, name): 70 | return getattr(self.instance, name) 71 | -------------------------------------------------------------------------------- /decompose/distributions/cenNnFullyElasticNetCond.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.distribution import parameterProperty 8 | from decompose.distributions.algorithms import Algorithms 9 | from decompose.distributions.cenNnFullyElasticNetAlgorithms import CenNnFullyElasticNetAlgorithms 10 | from decompose.distributions.distribution import Properties 11 | 12 | 13 | class CenNnFullyElasticNetCond(Distribution): 14 | def __init__(self, 15 | algorithms: Type[Algorithms] = CenNnFullyElasticNetAlgorithms, 16 | b: Tensor = None, 17 | mu: Tensor = None, 18 | tau: Tensor = None, 19 | betaExponential: Tensor = None, 20 | beta: Tensor = None, 21 | properties: Properties = None) -> None: 22 | parameters = {"b": b, 23 | "mu": mu, "tau": tau, 24 | "betaExponential": betaExponential, 25 | "beta": beta} 26 | Distribution.__init__(self, 27 | algorithms=algorithms, 28 | parameters=parameters, 29 | properties=properties) 30 | 31 | def parameterInfo(self, 32 | shape: Tuple[int, ...] = (1,), 33 | latentShape: Tuple[int, ...] = ())-> ParameterInfo: 34 | initializers = { 35 | "b": (shape, False), 36 | "mu": (shape, False), 37 | "tau": (shape, True), 38 | "betaExponential": (shape, True), 39 | "beta": (shape, True), 40 | } # type: Dict[str, Tensor] 41 | return(initializers) 42 | 43 | @parameterProperty 44 | def b(self) -> Tensor: 45 | return(self.__b) 46 | 47 | @b.setter(name="b") 48 | def b(self, b: Tensor): 49 | self.__b = b 50 | 51 | @parameterProperty 52 | def mu(self) -> Tensor: 53 | return(self.__mu) 54 | 55 | @mu.setter(name="mu") 56 | def mu(self, mu: Tensor): 57 | self.__mu = mu 58 | 59 | @parameterProperty 60 | def tau(self) -> Tensor: 61 | return(self.__tau) 62 | 63 | @tau.setter(name="tau") 64 | def tau(self, tau: Tensor): 65 | self.__tau = tau 66 | 67 | @parameterProperty 68 | def betaExponential(self) -> Tensor: 69 | return(self.__betaExponential) 70 | 71 | @betaExponential.setter(name="betaExponential") 72 | def betaExponential(self, betaExponential: Tensor): 73 | self.__betaExponential = betaExponential 74 | 75 | @parameterProperty 76 | def beta(self) -> tf.Tensor: 77 | return(self.__beta) 78 | 79 | @beta.setter(name="beta") 80 | def beta(self, beta: tf.Tensor) -> None: 81 | self.__beta = beta 82 | 83 | @property 84 | def nonNegative(self) -> bool: 85 | return(True) 86 | 87 | @property 88 | def homogenous(self) -> bool: 89 | return(False) 90 | 91 | def cond(self) -> Distribution: 92 | return(self) 93 | 94 | @property 95 | def shape(self) -> Tuple[int, ...]: 96 | return(tuple(self.mu.get_shape().as_list())) 97 | 98 | @property 99 | def latentShape(self) -> Tuple[int, ...]: 100 | return(()) 101 | -------------------------------------------------------------------------------- /decompose/distributions/tAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | 8 | 9 | class TAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | mu, Psi, nu = parameters["mu"], parameters["Psi"], parameters["nu"] 14 | t = tf.distributions.StudentT(df=nu, loc=mu, scale=tf.sqrt(Psi)) 15 | r = t.sample(sample_shape=(nSamples,)) 16 | return(r) 17 | 18 | @classmethod 19 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 20 | mu = parameters["mu"] 21 | return(mu) 22 | 23 | @classmethod 24 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 25 | mu, Psi, nu = parameters["mu"], parameters["Psi"], parameters["nu"] 26 | t = tf.distributions.StudentT(df=nu, loc=mu, scale=tf.sqrt(Psi)) 27 | pdf = t.prob(value=data) 28 | return(pdf) 29 | 30 | @classmethod 31 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 32 | mu, Psi, nu = parameters["mu"], parameters["Psi"], parameters["nu"] 33 | t = tf.distributions.StudentT(df=nu, loc=mu, scale=tf.sqrt(Psi)) 34 | llh = t.log_prob(value=data) 35 | return(llh) 36 | 37 | @classmethod 38 | def nuStep(cls, nu, n, delta, p=1.): 39 | three = tf.constant(3., dtype=nu.dtype) 40 | for i in range(2): 41 | w = (nu+p)/(nu+delta) 42 | fp = (-tf.digamma(nu/2) + tf.log(nu/2) 43 | + 1./n*tf.reduce_sum(tf.log((nu+p)/(nu+delta)) - w, 44 | axis=0) 45 | + 1 46 | + tf.digamma((p+nu)/2) - tf.log((p+nu)/2)) 47 | fpp = (tf.polygamma(three, nu/2)/2. + 1./nu 48 | + tf.polygamma(three, (p+nu)/2)/2. - 1./(nu+p) 49 | + 1./n*tf.reduce_sum((delta-p)/(nu+delta)**2*(w-1), 50 | axis=0)) 51 | nu = nu + fp/fpp 52 | return(nu) 53 | 54 | @classmethod 55 | def fit(cls, parameters: Dict[str, Tensor], 56 | data: tf.Tensor) -> Dict[str, Tensor]: 57 | mu, Psi, nu = parameters["mu"], parameters["Psi"], parameters["nu"] 58 | 59 | p = 1. 60 | 61 | n = data.get_shape()[0].value 62 | Y = data 63 | for i in range(5): 64 | delta = (Y - mu)**2/Psi 65 | w = (nu + p)/(nu + delta) 66 | 67 | mu = tf.reduce_mean(w*Y, axis=0) 68 | PsiNew = tf.reduce_mean(w*(Y-mu)**2, axis=0) 69 | cond = tf.logical_and(tf.is_finite(PsiNew), 70 | tf.greater(PsiNew, 1e-6)) 71 | Psi = tf.where(cond, PsiNew, Psi*tf.ones_like(PsiNew)) 72 | 73 | delta = (Y - mu)**2/Psi 74 | nuNew = cls.nuStep(nu, n, delta) 75 | cond = tf.logical_and(tf.is_finite(nuNew), 76 | tf.greater(nuNew, 0.)) 77 | nu = tf.where(cond, nuNew, nu*tf.ones_like(nuNew)) 78 | 79 | tau = w 80 | updatedParameters = {"mu": mu, "nu": nu, "Psi": Psi, "tau": tau} 81 | return(updatedParameters) 82 | 83 | @classmethod 84 | def fitLatents(cls, parameters: Dict[str, Tensor], 85 | data: Tensor) -> Dict[str, Tensor]: 86 | mu, Psi, nu = parameters["mu"], parameters["Psi"], parameters["nu"] 87 | p = 1. 88 | Y = data 89 | delta = (Y - mu)**2/Psi 90 | w = (nu + p)/(nu + delta) 91 | return({"tau": w}) 92 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_exponentialAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.exponentialAlgorithms import ExponentialAlgorithms 8 | 9 | 10 | @pytest.mark.slow 11 | def test_exponential_sample(): 12 | """Test if the mean of the samples equals the scale `beta`.""" 13 | beta = np.array([0.5, 1., 2.]) 14 | nSamples = 1000000 15 | 16 | nBetas = beta.shape[0] 17 | parameters = {"beta": tf.constant(beta)} 18 | tfNSamples = tf.constant(nSamples) 19 | 20 | r = ExponentialAlgorithms.sample(parameters=parameters, 21 | nSamples=tfNSamples) 22 | 23 | with tf.Session() as sess: 24 | r = sess.run(r) 25 | 26 | assert(r.shape == (nSamples, nBetas)) 27 | betaHat = np.mean(r, axis=0) 28 | assert(np.allclose(betaHat, beta, atol=1e-1)) 29 | 30 | 31 | def test_exponential_mode(): 32 | """Test if the mode is 0 for all elements.""" 33 | beta = np.array([0.5, 1., 2.]) 34 | 35 | nBetas = beta.shape[0] 36 | parameters = {"beta": tf.constant(beta)} 37 | 38 | mode = ExponentialAlgorithms.mode(parameters=parameters) 39 | 40 | with tf.Session() as sess: 41 | mode = sess.run(mode) 42 | 43 | assert(mode.shape == (nBetas,)) 44 | assert(np.all(mode == 0.)) 45 | 46 | 47 | def test_exponential_pdf(): 48 | """Test if the pdf is the same as reported by scipy.""" 49 | beta = np.array([0.5, 1., 2.]) 50 | nSamples = 1000 51 | 52 | nBetas = beta.shape[0] 53 | data = np.random.random((nSamples, nBetas)) 54 | 55 | parameters = {"beta": tf.constant(beta)} 56 | tfData = tf.constant(data) 57 | probs = ExponentialAlgorithms.pdf(parameters=parameters, 58 | data=tfData) 59 | 60 | with tf.Session() as sess: 61 | probs = sess.run(probs) 62 | 63 | assert(probs.shape == (nSamples, nBetas)) 64 | spProbs = sp.stats.expon(scale=beta).pdf(data) 65 | assert(np.allclose(probs, spProbs)) 66 | 67 | 68 | def test_exponential_llh(): 69 | """Test if the llh is the same as reported by scipy.""" 70 | beta = np.array([0.5, 1., 2.]) 71 | nSamples = 1000 72 | 73 | nBetas = beta.shape[0] 74 | data = np.random.random((nSamples, nBetas)) 75 | 76 | parameters = {"beta": tf.constant(beta)} 77 | tfData = tf.constant(data) 78 | llh = ExponentialAlgorithms.llh(parameters=parameters, 79 | data=tfData) 80 | 81 | with tf.Session() as sess: 82 | llh = sess.run(llh) 83 | 84 | assert(llh.shape == (nSamples, nBetas)) 85 | spLlh = sp.stats.expon(scale=beta).logpdf(data) 86 | assert(np.allclose(llh, spLlh)) 87 | 88 | 89 | @pytest.mark.slow 90 | def test_exponential_fit(): 91 | """Test if the fitted parameters match the true parameters.""" 92 | beta = np.array([0.5, 1., 2.]) 93 | nSamples = 100000 94 | nBetas = beta.shape[0] 95 | data = np.random.random((nSamples, nBetas)) 96 | data = sp.stats.expon(scale=beta).rvs(size=(nSamples, nBetas)) 97 | 98 | parameters = {"beta": tf.constant(np.ones(nBetas))} 99 | tfData = tf.constant(data) 100 | parameters = ExponentialAlgorithms.fit(parameters=parameters, 101 | data=tfData) 102 | 103 | with tf.Session() as sess: 104 | parameters = sess.run(parameters) 105 | 106 | betaHat = parameters["beta"] 107 | 108 | assert(betaHat.shape == beta.shape) 109 | assert(np.allclose(betaHat, beta, atol=1e-1)) 110 | -------------------------------------------------------------------------------- /decompose/distributions/t.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | import numpy as np 5 | 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.distribution import Distribution 8 | from decompose.distributions.normal import Normal 9 | from decompose.distributions.distribution import parameterProperty 10 | from decompose.distributions.algorithms import Algorithms 11 | from decompose.distributions.tAlgorithms import TAlgorithms 12 | from decompose.distributions.distribution import Properties 13 | 14 | 15 | class T(Distribution): 16 | def __init__(self, 17 | algorithms: Type[Algorithms] = TAlgorithms, 18 | mu: Tensor = None, 19 | Psi: Tensor = None, 20 | nu: Tensor = None, 21 | tau: Tensor = None, 22 | properties: Properties = None) -> None: 23 | parameters = {"mu": mu, "Psi": Psi, "nu": nu, "tau": tau} 24 | Distribution.__init__(self, 25 | algorithms=algorithms, 26 | parameters=parameters, 27 | properties=properties) 28 | 29 | def parameterInfo(self, 30 | shape: Tuple[int, ...] = (1,), 31 | latentShape: Tuple[int, ...] = ()) -> ParameterInfo: 32 | initializers = { 33 | "mu": (shape, False), 34 | "Psi": (shape, True), 35 | "nu": (shape, True), 36 | "tau": (latentShape + shape, True) 37 | } # type: Dict[str, Tensor] 38 | return(initializers) 39 | 40 | @parameterProperty 41 | def mu(self) -> Tensor: 42 | return(self.__mu) 43 | 44 | @mu.setter(name="mu") 45 | def mu(self, mu: Tensor) -> None: 46 | self.__mu = mu 47 | 48 | @parameterProperty 49 | def Psi(self) -> tf.Tensor: 50 | return(self.__Psi) 51 | 52 | @Psi.setter(name="Psi") 53 | def Psi(self, Psi: tf.Tensor) -> None: 54 | self.__Psi = Psi 55 | 56 | @parameterProperty 57 | def nu(self) -> tf.Tensor: 58 | return(self.__nu) 59 | 60 | @nu.setter(name="nu") 61 | def nu(self, nu: tf.Tensor) -> None: 62 | self.__nu = nu 63 | 64 | @parameterProperty 65 | def tau(self) -> Tensor: 66 | return(self.__tau) 67 | 68 | @tau.setter(name="tau") 69 | def tau(self, tau: Tensor) -> None: 70 | self.__tau = tau 71 | 72 | @property 73 | def nonNegative(self) -> bool: 74 | return(False) 75 | 76 | @property 77 | def homogenous(self) -> bool: 78 | return(False) 79 | 80 | def cond(self) -> Normal: 81 | mu = self.mu 82 | Psi = self.Psi 83 | tau = self.tau 84 | mu = tf.ones_like(tau)*mu 85 | Psi = tf.ones_like(tau)*Psi 86 | name = self.name + "Cond" 87 | properties = Properties(name=name, 88 | drawType=self.drawType, 89 | updateType=self.updateType, 90 | persistent=False) 91 | cond = Normal(mu=mu, 92 | tau=tau/Psi, 93 | properties=properties) 94 | return(cond) 95 | 96 | @property 97 | def shape(self) -> Tuple[int, ...]: 98 | return(tuple(self.mu.get_shape().as_list())) 99 | 100 | @property 101 | def latentShape(self) -> Tuple[int, ...]: 102 | ndims = len(self.tau.get_shape().as_list()) - len(self.shape) 103 | return(tuple(self.tau.get_shape().as_list()[:ndims])) 104 | -------------------------------------------------------------------------------- /decompose/distributions/tfppf.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | 4 | 5 | s2pi = 2.50662827463100050242E0 6 | 7 | P0 = [ 8 | -5.99633501014107895267E1, 9 | 9.80010754185999661536E1, 10 | -5.66762857469070293439E1, 11 | 1.39312609387279679503E1, 12 | -1.23916583867381258016E0, 13 | ] 14 | 15 | Q0 = [ 16 | 1, 17 | 1.95448858338141759834E0, 18 | 4.67627912898881538453E0, 19 | 8.63602421390890590575E1, 20 | -2.25462687854119370527E2, 21 | 2.00260212380060660359E2, 22 | -8.20372256168333339912E1, 23 | 1.59056225126211695515E1, 24 | -1.18331621121330003142E0, 25 | ] 26 | 27 | P1 = [ 28 | 4.05544892305962419923E0, 29 | 3.15251094599893866154E1, 30 | 5.71628192246421288162E1, 31 | 4.40805073893200834700E1, 32 | 1.46849561928858024014E1, 33 | 2.18663306850790267539E0, 34 | -1.40256079171354495875E-1, 35 | -3.50424626827848203418E-2, 36 | -8.57456785154685413611E-4, 37 | ] 38 | 39 | Q1 = [ 40 | 1, 41 | 1.57799883256466749731E1, 42 | 4.53907635128879210584E1, 43 | 4.13172038254672030440E1, 44 | 1.50425385692907503408E1, 45 | 2.50464946208309415979E0, 46 | -1.42182922854787788574E-1, 47 | -3.80806407691578277194E-2, 48 | -9.33259480895457427372E-4, 49 | ] 50 | 51 | P2 = [ 52 | 3.23774891776946035970E0, 53 | 6.91522889068984211695E0, 54 | 3.93881025292474443415E0, 55 | 1.33303460815807542389E0, 56 | 2.01485389549179081538E-1, 57 | 1.23716634817820021358E-2, 58 | 3.01581553508235416007E-4, 59 | 2.65806974686737550832E-6, 60 | 6.23974539184983293730E-9, 61 | ] 62 | 63 | Q2 = [ 64 | 1, 65 | 6.02427039364742014255E0, 66 | 3.67983563856160859403E0, 67 | 1.37702099489081330271E0, 68 | 2.16236993594496635890E-1, 69 | 1.34204006088543189037E-2, 70 | 3.28014464682127739104E-4, 71 | 2.89247864745380683936E-6, 72 | 6.79019408009981274425E-9, 73 | ] 74 | 75 | 76 | def tfppf(y0): 77 | dtype = y0.dtype 78 | tfy0 = y0 79 | if len(tfy0.get_shape().as_list()) == 0: 80 | tfy0 = tf.expand_dims(tfy0, -1) 81 | 82 | tfy = tfy0 83 | c = tf.constant(0.13533528323661269189, dtype=dtype) 84 | one = tf.constant(1.0, dtype=dtype) 85 | ones = tf.ones(tf.shape(tfy0), dtype=dtype) 86 | tfy = tf.where(tf.greater(tfy, one - c), one - tfy, tfy) 87 | 88 | tfnegate = tf.where( 89 | tf.greater(tfy0, (one - c)*ones), 90 | tf.zeros(tf.shape(tfy0), dtype=dtype), 91 | tf.ones(tf.shape(tfy0), dtype=dtype)) 92 | 93 | tfy1 = tfy - 0.5 94 | tfy2 = tfy1**2 95 | tfx = tfy1 + tfy1 * (tfy2 * polevl(tfy2, P0) / polevl(tfy2, Q0)) 96 | tfx = tfx * s2pi 97 | 98 | tfx = tf.where(tf.less_equal(tfy, c), 99 | tf.sqrt(-2.0 * tf.log(tfy)), 100 | tfx) 101 | tfx0 = tfx - tf.log(tfx) / tfx 102 | 103 | tfz = 1./tfx 104 | 105 | tfx1 = tfz * polevl(tfz, P1) / polevl(tfz, Q1) 106 | tfx1 = tf.where(tf.greater_equal(tfx, 8.0), 107 | tfz * polevl(tfz, P2) / polevl(tfz, Q2), 108 | tfx1) 109 | 110 | tfx = tf.where(tf.less_equal(tfy, c), 111 | tfx0 - tfx1, 112 | tfx) 113 | tfx = tf.where( 114 | tf.logical_and(tf.less_equal(tfy, c), tf.equal(tfnegate, one)), 115 | -tfx, 116 | tfx) 117 | tfx = tf.where(tf.equal(tfy0, 0.), -np.inf*ones, tfx) 118 | tfx = tf.where(tf.equal(tfy0, 1.), np.inf*ones, tfx) 119 | 120 | return(tfx) 121 | 122 | 123 | def polevl(x, coef): 124 | accum = 0 125 | for c in coef: 126 | accum = x * accum + c 127 | return(accum) 128 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_laplaceAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.laplaceAlgorithms import LaplaceAlgorithms 8 | 9 | 10 | @pytest.mark.slow 11 | def test_laplace_sample(): 12 | """Test whether the mean and the variance of the samples are correct.""" 13 | mu = np.array([-1, 0., 1.]) 14 | beta = np.array([0.5, 1., 2.]) 15 | nSamples = 1000000 16 | 17 | nParameters = mu.shape[0] 18 | parameters = {"mu": tf.constant(mu), 19 | "beta": tf.constant(beta)} 20 | tfNSamples = tf.constant(nSamples) 21 | r = LaplaceAlgorithms.sample(parameters=parameters, 22 | nSamples=tfNSamples) 23 | 24 | with tf.Session() as sess: 25 | r = sess.run(r) 26 | 27 | assert(r.shape == (nSamples, nParameters)) 28 | muHat = np.median(r, axis=0) 29 | assert(np.allclose(muHat, mu, atol=1e-1)) 30 | betaHat = np.sqrt(np.var(r, axis=0)/2.) 31 | assert(np.allclose(betaHat, beta, atol=1e-1)) 32 | 33 | 34 | def test_laplace_mode(): 35 | """Test if the mode is equal to `mu`.""" 36 | mu = np.array([-1, 0., 1.]) 37 | beta = np.array([0.5, 1., 2.]) 38 | 39 | nParameters = mu.shape[0] 40 | parameters = {"mu": tf.constant(mu), 41 | "beta": tf.constant(beta)} 42 | mode = LaplaceAlgorithms.mode(parameters=parameters) 43 | 44 | with tf.Session() as sess: 45 | mode = sess.run(mode) 46 | 47 | assert(mode.shape == (nParameters,)) 48 | assert(np.all(mode == mu)) 49 | 50 | 51 | def test_laplace_pdf(): 52 | """Test if the pdf is the same as reported by scipy.""" 53 | mu = np.array([-1, 0., 1.]) 54 | beta = np.array([0.5, 1., 2.]) 55 | nSamples = 1000 56 | 57 | nParameters = mu.shape[0] 58 | data = np.random.random((nSamples, nParameters)) 59 | parameters = {"mu": tf.constant(mu), 60 | "beta": tf.constant(beta)} 61 | tfData = tf.constant(data) 62 | probs = LaplaceAlgorithms.pdf(parameters=parameters, 63 | data=tfData) 64 | 65 | with tf.Session() as sess: 66 | probs = sess.run(probs) 67 | 68 | assert(probs.shape == (nSamples, nParameters)) 69 | spProbs = sp.stats.laplace(loc=mu, scale=beta).pdf(data) 70 | assert(np.allclose(probs, spProbs)) 71 | 72 | 73 | def test_laplace_llh(): 74 | """Test if the llh is the same as reported by scipy.""" 75 | mu = np.array([-1, 0., 1.]) 76 | beta = np.array([0.5, 1., 2.]) 77 | nSamples = 1000 78 | 79 | nParameters = mu.shape[0] 80 | data = np.random.random((nSamples, nParameters)) 81 | parameters = {"mu": tf.constant(mu), 82 | "beta": tf.constant(beta)} 83 | tfData = tf.constant(data) 84 | llh = LaplaceAlgorithms.llh(parameters=parameters, 85 | data=tfData) 86 | 87 | with tf.Session() as sess: 88 | llh = sess.run(llh) 89 | 90 | assert(llh.shape == (nSamples, nParameters)) 91 | spLlh = sp.stats.laplace(loc=mu, scale=beta).logpdf(data) 92 | assert(np.allclose(llh, spLlh)) 93 | 94 | 95 | @pytest.mark.slow 96 | def test_laplace_fit(): 97 | """Test if the fitted parameters match the true parameters.""" 98 | mu = np.array([-1, 0., 1.]) 99 | beta = np.array([0.5, 1., 2.]) 100 | nSamples = 100000 101 | 102 | nParameters = mu.shape[0] 103 | data = np.random.random((nSamples, nParameters)) 104 | norm = sp.stats.laplace(loc=mu, scale=beta) 105 | data = norm.rvs(size=(nSamples, nParameters)) 106 | parameters = {"mu": tf.constant(np.ones(nParameters)), 107 | "beta": tf.constant(np.ones(nParameters))} 108 | tfData = tf.constant(data) 109 | parameters = LaplaceAlgorithms.fit(parameters=parameters, 110 | data=tfData) 111 | 112 | with tf.Session() as sess: 113 | parameters = sess.run(parameters) 114 | 115 | muHat = parameters["mu"] 116 | assert(muHat.shape == mu.shape) 117 | assert(np.allclose(muHat, mu, atol=1e-1)) 118 | 119 | betaHat = parameters["beta"] 120 | assert(betaHat.shape == beta.shape) 121 | assert(np.allclose(betaHat, beta, atol=1e-1)) 122 | -------------------------------------------------------------------------------- /decompose/distributions/cenNormalRankOneAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | 8 | 9 | class CenNormalRankOneAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | tau0, tau1 = parameters["tau0"], parameters["tau"] 14 | tau = tf.matmul(tau0[..., None], tau1[None, ...]) 15 | norm = tf.distributions.Normal(loc=tf.zeros_like(tau), 16 | scale=1./tf.sqrt(tau1)) 17 | r = norm.sample(sample_shape=(nSamples,)) 18 | return(r) 19 | 20 | @classmethod 21 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 22 | tau0, tau1 = parameters["tau0"], parameters["tau1"] 23 | M = tau0.get_shape().as_list()[0] 24 | N = tau1.get_shape().as_list()[0] 25 | mode = tf.zeros(shape=(M, N), dtype=tau0.dtype) 26 | return(mode) 27 | 28 | @classmethod 29 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 30 | tau0, tau1 = parameters["tau0"], parameters["tau1"] 31 | tau = tf.matmul(tau0[..., None], tau1[None, ...]) 32 | norm = tf.distributions.Normal(loc=tf.zeros_like(tau), 33 | scale=tf.sqrt(1./tau)) 34 | pdf = norm.prob(value=data) 35 | return(pdf) 36 | 37 | @classmethod 38 | def fitGamma(cls, tau): 39 | alpha = 0.5/(tf.log(tf.reduce_mean(tau)) 40 | + 1e-6 # added due to numerical instability 41 | - tf.reduce_mean(tf.log(tau))) 42 | for i in range(20): 43 | alpha = (1. / (1./alpha 44 | + (tf.reduce_mean(tf.log(tau)) 45 | - tf.log(tf.reduce_mean(tau)) 46 | + tf.log(alpha) 47 | - tf.digamma(alpha)) 48 | / (alpha**2*(1./alpha 49 | - tf.polygamma(tf.ones_like(alpha), 50 | alpha))))) 51 | 52 | beta = alpha/tf.reduce_mean(tau) 53 | return(alpha, beta) 54 | 55 | @classmethod 56 | def fit(cls, parameters: Dict[str, Tensor], 57 | data: tf.Tensor) -> Dict[str, Tensor]: 58 | """Optimal ML update using the EM algorithm.""" 59 | 60 | # regularized multiplicative 61 | M, N = data.get_shape().as_list() 62 | tau0, tau1 = parameters["tau0"], parameters["tau1"] 63 | 64 | # hyperparameter optimization 65 | alpha0, beta0 = cls.fitGamma(tau0) 66 | alpha1, beta1 = cls.fitGamma(tau1) 67 | 68 | # sampling taus 69 | alphaPost0 = alpha0 + N/2 70 | betaPost0 = beta0 + tf.matmul(data**2, tau1[..., None])[..., 0]/2 71 | tau0 = tf.distributions.Gamma(concentration=alphaPost0, 72 | rate=betaPost0).sample(1)[0] 73 | tau0 = tf.where(tau0 < 1e-6, tf.ones_like(tau0)*1e-6, tau0) 74 | 75 | alphaPost1 = alpha1 + M/2 76 | betaPost1 = beta1 + tf.matmul(tau0[None, ...], data**2)[0, ...]/2 77 | tau1 = tf.distributions.Gamma(concentration=alphaPost1, 78 | rate=betaPost1).sample(1)[0] 79 | tau1 = tf.where(tau1 < 1e-6, tf.ones_like(tau1)*1e-6, tau1) 80 | 81 | # rescaling taus 82 | normTau0 = tf.norm(tau0) 83 | normTau1 = tf.norm(tau1) 84 | normPerFactor = tf.sqrt(normTau0*normTau1) 85 | tau0 = tau0/normTau0*normPerFactor 86 | tau1 = tau1/normTau1*normPerFactor 87 | 88 | updatedParameters = {"tau0": tau0, "tau1": tau1} 89 | return(updatedParameters) 90 | 91 | @classmethod 92 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> float: 93 | tau0, tau1 = parameters["tau0"], parameters["tau1"] 94 | tau = tf.matmul(tau0[..., None], tau1[None, ...]) 95 | norm = tf.distributions.Normal(loc=tf.zeros_like(tau), 96 | scale=tf.sqrt(1./tau)) 97 | llh = norm.log_prob(value=data) 98 | return(llh) 99 | 100 | @classmethod 101 | def fitLatents(cls, parameters: Dict[str, Tensor], 102 | data: Tensor) -> Dict[str, Tensor]: 103 | return({}) 104 | -------------------------------------------------------------------------------- /decompose/distributions/lomaxAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | 8 | 9 | class LomaxAlgorithms(Algorithms): 10 | 11 | @classmethod 12 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 13 | alpha, beta = parameters["alpha"], parameters["beta"] 14 | gamma = tf.distributions.Gamma(concentration=alpha, rate=beta) 15 | tau = gamma.sample(sample_shape=(nSamples,)) 16 | exp = tf.distributions.Exponential(rate=tau) 17 | s = exp.sample() 18 | return(s) 19 | 20 | @classmethod 21 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 22 | mode = tf.zeros_like(parameters["alpha"]) 23 | return(mode) 24 | 25 | @classmethod 26 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 27 | return(tf.exp(cls.llh(parameters=parameters, data=data))) 28 | 29 | @classmethod 30 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 31 | alpha, beta = parameters["alpha"], parameters["beta"] 32 | llh = tf.log(alpha) - tf.log(beta) - (alpha+1)*tf.log(1.+data/beta) 33 | llh = tf.where(tf.less(data, 0.), -np.inf*tf.ones_like(llh), llh) 34 | return(llh) 35 | 36 | @classmethod 37 | def f(cls, y, beta, tn): 38 | tal = tn/tf.reduce_sum(tf.log((beta+y)/beta), axis=-2, keepdims=True) 39 | tfu = tn*(tf.log(tal)-tf.log(beta) - (1.+1./tal)) 40 | return(tfu) 41 | 42 | @classmethod 43 | def fit(cls, parameters: Dict[str, Tensor], 44 | data: tf.Tensor) -> Dict[str, Tensor]: 45 | alphaOld, betaOld = parameters["alpha"], parameters["beta"] 46 | 47 | y = data 48 | tn = data.get_shape()[0].value 49 | alpha = alphaOld 50 | beta = betaOld 51 | 52 | for i in range(1): 53 | swidths = tf.constant(np.array([0., 1e-8, 1e-7, 1e-6, 1e-4, 1e-3, 54 | 1e-2, 1e-1, 1e0, 1e1, 1e2, 55 | 1e3]), dtype=alpha.dtype) 56 | grads = tf.gradients(cls.f(data, beta, tn), [beta])[0] 57 | normedDirection = tf.sign(grads) 58 | testBetas = beta+swidths[..., None, None]*normedDirection 59 | testUtilities = cls.f(data, testBetas, tn) 60 | argmax = tf.cast(tf.argmax(testUtilities, axis=0)[0], 61 | dtype=tf.int32) 62 | maxWidths = tf.gather(swidths, argmax) 63 | beta = beta+maxWidths*normedDirection 64 | 65 | beta = tf.where(tf.greater(beta, 1e-9), beta, 66 | 1e-9*tf.ones_like(beta)) 67 | beta = tf.where(tf.less(beta, 1e9), beta, 68 | 1e9*tf.ones_like(beta)) 69 | 70 | alpha = tn/tf.reduce_sum(tf.log(beta+y) - tf.log(beta), axis=-2) 71 | alpha = tf.where(tf.greater(alpha, 1e-9), alpha, 72 | 1e-9*tf.ones_like(alpha)) 73 | alpha = tf.where(tf.less(alpha, 1e9), alpha, 74 | 1e9*tf.ones_like(alpha)) 75 | 76 | llh = tf.reduce_mean(cls.llh(parameters={"alpha": alpha, "beta": beta}, 77 | data=data), axis=0) 78 | llhOld = tf.reduce_mean(cls.llh(parameters={"alpha": alphaOld, "beta": betaOld}, 79 | data=data), axis=0) 80 | alpha = tf.where(llh > llhOld, 81 | alpha, 82 | alphaOld) 83 | beta = tf.where(llh > llhOld, 84 | beta, 85 | betaOld) 86 | w = (alpha + 1)/(beta + y) 87 | w = tf.where(tf.less(w, 1e9), w, 88 | 1e9*tf.ones_like(w)) 89 | w = tf.where(tf.greater(w, 1e-9), w, 90 | 1e-9*tf.ones_like(w)) 91 | return({"alpha": alpha, 92 | "beta": beta, 93 | "tau": w}) 94 | 95 | @classmethod 96 | def fitLatents(cls, parameters: Dict[str, Tensor], 97 | data: Tensor) -> Dict[str, Tensor]: 98 | alpha, beta = parameters["alpha"], parameters["beta"] 99 | tau = (alpha + 1)/(beta + data) 100 | tau = tf.where(tf.less(tau, 1e9), tau, 101 | 1e9*tf.ones_like(tau)) 102 | tau = tf.where(tf.greater(tau, 1e-9), tau, 103 | 1e-9*tf.ones_like(tau)) 104 | return({"tau": tau}) 105 | -------------------------------------------------------------------------------- /decompose/likelihoods/cvNormal2dLikelihood.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple, List 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.distribution import DrawType, UpdateType 7 | from decompose.distributions.cenNormal import CenNormal 8 | from decompose.likelihoods.likelihood import Likelihood 9 | from decompose.distributions.distribution import Properties 10 | from decompose.cv.cv import CV 11 | 12 | 13 | class CVNormal2dLikelihood(Likelihood): 14 | 15 | def __init__(self, M: Tuple[int, ...], K: int=1, tau: float = 1./1e10, 16 | cv: CV = None, 17 | drawType: DrawType = DrawType.SAMPLE, 18 | updateType: UpdateType = UpdateType.ALL, 19 | dtype=tf.float32) -> None: 20 | Likelihood.__init__(self, M, K) 21 | self.__cv = cv 22 | self.__tauInit = tau 23 | self.__dtype = dtype 24 | self.__properties = Properties(name='likelihood', 25 | drawType=drawType, 26 | updateType=updateType, 27 | persistent=True) 28 | 29 | def init(self, data: Tensor) -> None: 30 | tau = self.__tauInit 31 | dtype = self.__dtype 32 | properties = self.__properties 33 | noiseDistribution = CenNormal(tau=tf.constant([tau], dtype=dtype), 34 | properties=properties) 35 | self.__noiseDistribution = noiseDistribution 36 | observedMask = tf.logical_not(tf.is_nan(data)) 37 | trainMask = tf.logical_not(self.cv.mask(X=data)) 38 | trainMask = tf.get_variable("trainMask", 39 | dtype=trainMask.dtype, 40 | initializer=trainMask) 41 | trainMask = tf.logical_and(trainMask, observedMask) 42 | testMask = tf.logical_and(observedMask, 43 | tf.logical_not(trainMask)) 44 | self.__observedMask = observedMask 45 | self.__trainMask = trainMask 46 | self.__testMask = testMask 47 | 48 | @property 49 | def cv(self) -> CV: 50 | return(self.__cv) 51 | 52 | @property 53 | def observedMask(self) -> Tensor: 54 | return(self.__observedMask) 55 | 56 | @property 57 | def trainMask(self) -> Tensor: 58 | return(self.__trainMask) 59 | 60 | @property 61 | def testMask(self) -> Tensor: 62 | return(self.__testMask) 63 | 64 | @property 65 | def noiseDistribution(self) -> CenNormal: 66 | return(self.__noiseDistribution) 67 | 68 | def residuals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 69 | return(self.testResiduals(U, X)) 70 | 71 | def testResiduals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 72 | assert(len(U) == 2) 73 | U0, U1 = U 74 | Xhat = tf.matmul(tf.transpose(U0), U1) 75 | residuals = tf.reshape(X-Xhat, (-1,)) 76 | indices = tf.cast(tf.where(tf.reshape(self.testMask, (-1,))), 77 | dtype=tf.int32) 78 | testResiduals = tf.gather_nd(residuals, indices) 79 | return(testResiduals) 80 | 81 | def trainResiduals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 82 | assert(len(U) == 2) 83 | U0, U1 = U 84 | Xhat = tf.matmul(tf.transpose(U0), U1) 85 | residuals = tf.reshape(X-Xhat, (-1,)) 86 | indices = tf.cast(tf.where(tf.reshape(self.trainMask, (-1,))), 87 | dtype=tf.int32) 88 | trainResiduals = tf.gather_nd(residuals, indices) 89 | return(trainResiduals) 90 | 91 | def llh(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 92 | r = self.testResiduals(U, X) 93 | llh = tf.reduce_sum(self.noiseDistribution.llh(r)) 94 | return(llh) 95 | 96 | def loss(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 97 | loss = tf.reduce_sum(self.testResiduals(U, X)**2) 98 | return(loss) 99 | 100 | def update(self, U: Tuple[Tensor, ...], X: Tensor) -> None: 101 | if self.noiseDistribution.updateType == UpdateType.ALL: 102 | residuals = self.trainResiduals(U, X) 103 | flattenedResiduals = residuals[..., None] 104 | self.noiseDistribution.update(flattenedResiduals) 105 | 106 | def prepVars(self, f: int, U: List[Tensor], 107 | X: Tensor) -> Tuple[Tensor, Tensor, Tensor]: 108 | trainMask = tf.cast(self.__trainMask, dtype=U[0].dtype) 109 | if f == 0: 110 | U1 = U[1] 111 | else: 112 | U1 = U[0] 113 | X = tf.transpose(X) 114 | trainMask = tf.transpose(trainMask) 115 | U1T = tf.transpose(U1) 116 | 117 | A = tf.matmul(X*trainMask, U1T) 118 | B = tf.einsum("mn,in,jn->mij", trainMask, U1, U1) 119 | alpha = self.noiseDistribution.tau 120 | return(A, B, alpha) 121 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_normalAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.normalAlgorithms import NormalAlgorithms 8 | from decompose.tests.fixtures import device, dtype 9 | 10 | 11 | @pytest.mark.slow 12 | def test_normal_sample(device, dtype): 13 | """Test whether the mean and the variance of the samples are correct.""" 14 | npdtype = dtype.as_numpy_dtype 15 | mu = np.array([-1, 0., 1.]).astype(npdtype) 16 | tau = np.array([0.5, 1., 2.]).astype(npdtype) 17 | nSamples = 1000000 18 | 19 | nParameters = mu.shape[0] 20 | parameters = {"mu": tf.constant(mu, dtype=dtype), 21 | "tau": tf.constant(tau, dtype=dtype)} 22 | tfNSamples = tf.constant(nSamples) 23 | r = NormalAlgorithms.sample(parameters=parameters, 24 | nSamples=tfNSamples) 25 | 26 | assert(r.dtype == dtype) 27 | 28 | with tf.Session() as sess: 29 | r = sess.run(r) 30 | 31 | assert(r.shape == (nSamples, nParameters)) 32 | muHat = np.mean(r, axis=0) 33 | assert(np.allclose(muHat, mu, atol=1e-1)) 34 | tauHat = 1./np.var(r, axis=0) 35 | assert(np.allclose(tauHat, tau, atol=1e-1)) 36 | 37 | 38 | def test_normal_mode(device, dtype): 39 | """Test if the mode is equal to `mu`.""" 40 | npdtype = dtype.as_numpy_dtype 41 | mu = np.array([-1, 0., 1.]).astype(npdtype) 42 | tau = np.array([0.5, 1., 2.]).astype(npdtype) 43 | 44 | nParameters = mu.shape[0] 45 | parameters = {"mu": tf.constant(mu, dtype=dtype), 46 | "tau": tf.constant(tau, dtype=dtype)} 47 | mode = NormalAlgorithms.mode(parameters=parameters) 48 | 49 | assert(mode.dtype == dtype) 50 | 51 | with tf.Session() as sess: 52 | mode = sess.run(mode) 53 | 54 | assert(mode.shape == (nParameters,)) 55 | assert(np.all(mode == mu)) 56 | 57 | 58 | def test_normal_pdf(device, dtype): 59 | """Test if the pdf is the same as reported by scipy.""" 60 | npdtype = dtype.as_numpy_dtype 61 | mu = np.array([-1, 0., 1.]).astype(npdtype) 62 | tau = np.array([0.5, 1., 2.]).astype(npdtype) 63 | nSamples = 1000 64 | 65 | nParameters = mu.shape[0] 66 | data = np.random.random((nSamples, nParameters)).astype(npdtype) 67 | parameters = {"mu": tf.constant(mu, dtype=dtype), 68 | "tau": tf.constant(tau, dtype=dtype)} 69 | tfData = tf.constant(data) 70 | probs = NormalAlgorithms.pdf(parameters=parameters, 71 | data=tfData) 72 | 73 | assert(probs.dtype == dtype) 74 | 75 | with tf.Session() as sess: 76 | probs = sess.run(probs) 77 | 78 | assert(probs.shape == (nSamples, nParameters)) 79 | spProbs = sp.stats.norm(loc=mu, scale=1./np.sqrt(tau)).pdf(data) 80 | assert(np.allclose(probs, spProbs)) 81 | 82 | 83 | def test_normal_llh(device, dtype): 84 | """Test if the llh is the same as reported by scipy.""" 85 | npdtype = dtype.as_numpy_dtype 86 | mu = np.array([-1, 0., 1.]).astype(npdtype) 87 | tau = np.array([0.5, 1., 2.]).astype(npdtype) 88 | nSamples = 1000 89 | 90 | nParameters = mu.shape[0] 91 | data = np.random.random((nSamples, nParameters)).astype(npdtype) 92 | parameters = {"mu": tf.constant(mu, dtype=dtype), 93 | "tau": tf.constant(tau, dtype=dtype)} 94 | tfData = tf.constant(data) 95 | llh = NormalAlgorithms.llh(parameters=parameters, 96 | data=tfData) 97 | 98 | assert(llh.dtype == dtype) 99 | 100 | with tf.Session() as sess: 101 | llh = sess.run(llh) 102 | 103 | assert(llh.shape == (nSamples, nParameters)) 104 | spLlh = sp.stats.norm(loc=mu, scale=1./np.sqrt(tau)).logpdf(data) 105 | assert(np.allclose(llh, spLlh)) 106 | 107 | 108 | @pytest.mark.slow 109 | def test_normal_fit(device, dtype): 110 | """Test if the fitted parameters match the true parameters.""" 111 | npdtype = dtype.as_numpy_dtype 112 | mu = np.array([-1, 0., 1.]).astype(npdtype) 113 | tau = np.array([0.5, 1., 2.]).astype(npdtype) 114 | nSamples = 100000 115 | 116 | nParameters = mu.shape[0] 117 | norm = sp.stats.norm(loc=mu, scale=1./np.sqrt(tau)) 118 | data = norm.rvs(size=(nSamples, nParameters)).astype(npdtype) 119 | parameters = {"mu": tf.constant(np.ones(nParameters), dtype=dtype), 120 | "tau": tf.constant(np.ones(nParameters), dtype=dtype)} 121 | tfData = tf.constant(data) 122 | parameters = NormalAlgorithms.fit(parameters=parameters, 123 | data=tfData) 124 | 125 | assert(parameters['mu'].dtype == dtype) 126 | assert(parameters['tau'].dtype == dtype) 127 | 128 | 129 | with tf.Session() as sess: 130 | parameters = sess.run(parameters) 131 | 132 | muHat = parameters["mu"] 133 | assert(muHat.shape == mu.shape) 134 | assert(np.allclose(muHat, mu, atol=1e-1)) 135 | 136 | tauHat = parameters["tau"] 137 | assert(tauHat.shape == tau.shape) 138 | assert(np.allclose(tauHat, tau, atol=1e-1)) 139 | -------------------------------------------------------------------------------- /decompose/distributions/cenNnFullyElasticNet.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict, Type 2 | import tensorflow as tf 3 | from tensorflow import Tensor 4 | 5 | from decompose.distributions.distribution import Distribution 6 | from decompose.distributions.distribution import ParameterInfo 7 | from decompose.distributions.distribution import parameterProperty 8 | from decompose.distributions.algorithms import Algorithms 9 | from decompose.distributions.cenNnFullyElasticNetAlgorithms import CenNnFullyElasticNetAlgorithms 10 | from decompose.distributions.cenNnFullyElasticNetCond import CenNnFullyElasticNetCond 11 | from decompose.distributions.distribution import Properties 12 | 13 | 14 | class CenNnFullyElasticNet(Distribution): 15 | def __init__(self, 16 | algorithms: Type[Algorithms] = CenNnFullyElasticNetAlgorithms, 17 | b: Tensor = None, 18 | mu: Tensor = None, 19 | betaExponential: Tensor = None, 20 | tau: Tensor = None, 21 | alpha: Tensor = None, 22 | beta: Tensor = None, 23 | tauLomax: Tensor = None, 24 | properties: Properties = None) -> None: 25 | parameters = {"b": b, 26 | "mu": mu, "tau": tau, 27 | "betaExponential": betaExponential, 28 | "alpha": alpha, "beta": beta, "tauLomax": tauLomax} 29 | Distribution.__init__(self, 30 | algorithms=algorithms, 31 | parameters=parameters, 32 | properties=properties) 33 | 34 | def parameterInfo(self, 35 | shape: Tuple[int, ...] = (1,), 36 | latentShape: Tuple[int, ...] = ())-> ParameterInfo: 37 | initializers = { 38 | "b": (shape, False), 39 | "mu": (shape, False), 40 | "tau": (shape, True), 41 | "betaExponential": (shape, True), 42 | "alpha": (shape, True), 43 | "beta": (shape, True), 44 | "tauLomax": (latentShape + shape, True) 45 | 46 | } # type: Dict[str, Tensor] 47 | return(initializers) 48 | 49 | @parameterProperty 50 | def b(self) -> Tensor: 51 | return(self.__b) 52 | 53 | @b.setter(name="b") 54 | def b(self, b: Tensor): 55 | self.__b = b 56 | 57 | @parameterProperty 58 | def mu(self) -> Tensor: 59 | return(self.__mu) 60 | 61 | @mu.setter(name="mu") 62 | def mu(self, mu: Tensor): 63 | self.__mu = mu 64 | 65 | @parameterProperty 66 | def tau(self) -> Tensor: 67 | return(self.__tau) 68 | 69 | @tau.setter(name="tau") 70 | def tau(self, tau: Tensor): 71 | self.__tau = tau 72 | 73 | @parameterProperty 74 | def betaExponential(self) -> Tensor: 75 | return(self.__betaExponential) 76 | 77 | @betaExponential.setter(name="betaExponential") 78 | def betaExponential(self, betaExponential: Tensor): 79 | self.__betaExponential = betaExponential 80 | 81 | @parameterProperty 82 | def muLomax(self) -> Tensor: 83 | muLomax = tf.zeros_like(self.alpha) 84 | return(muLomax) 85 | 86 | @parameterProperty 87 | def alpha(self) -> tf.Tensor: 88 | return(self.__alpha) 89 | 90 | @alpha.setter(name="alpha") 91 | def alpha(self, alpha: tf.Tensor) -> None: 92 | self.__alpha = alpha 93 | 94 | @parameterProperty 95 | def beta(self) -> tf.Tensor: 96 | return(self.__beta) 97 | 98 | @beta.setter(name="beta") 99 | def beta(self, beta: tf.Tensor) -> None: 100 | self.__beta = beta 101 | 102 | @parameterProperty 103 | def tau(self) -> Tensor: 104 | return(self.__tau) 105 | 106 | @tau.setter(name="tau") 107 | def tau(self, tau: Tensor) -> None: 108 | self.__tau = tau 109 | 110 | @property 111 | def nonNegative(self) -> bool: 112 | return(True) 113 | 114 | @property 115 | def homogenous(self) -> bool: 116 | return(False) 117 | 118 | def cond(self) -> CenNnFullyElasticNetCond: 119 | b = self.b 120 | mu = self.mu 121 | tau = self.tau 122 | betaExponential = self.betaExponential 123 | tauLomax = self.tauLomax 124 | b = tf.ones_like(tauLomax)*b 125 | mu = tf.ones_like(tauLomax)*mu 126 | tau = tf.ones_like(tauLomax)*tau 127 | betaExponential = tf.ones_like(tauLomax)*betaExponential 128 | name = self.name + "Cond" 129 | properties = Properties(name=name, 130 | drawType=self.drawType, 131 | updateType=self.updateType, 132 | persistent=False) 133 | cond = CenNnFullyElasticNetCond(b=b, mu=mu, tau=tau, 134 | betaExponential=betaExponential, 135 | beta=1./tauLomax, 136 | properties=properties) 137 | return(cond) 138 | 139 | @property 140 | def shape(self) -> Tuple[int, ...]: 141 | return(tuple(self.mu.get_shape().as_list())) 142 | 143 | @property 144 | def latentShape(self) -> Tuple[int, ...]: 145 | 146 | return(()) 147 | -------------------------------------------------------------------------------- /decompose/distributions/nnNormalAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | from decompose.distributions.chopin2011 import rtnorm 8 | 9 | 10 | class NnNormalAlgorithms(Algorithms): 11 | 12 | @classmethod 13 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 14 | mu, tau = parameters["mu"], parameters["tau"] 15 | sigma = tf.sqrt(1./tau) 16 | a = tf.zeros_like(mu) 17 | b = tf.ones_like(a)*np.inf 18 | r = rtnorm(a=a, b=b, mu=mu, sigma=sigma, nSamples=nSamples) 19 | return(r) 20 | 21 | @classmethod 22 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 23 | mu = parameters["mu"] 24 | mode = tf.where(tf.greater(mu, 0.), mu, tf.zeros_like(mu)) 25 | return(mode) 26 | 27 | @classmethod 28 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 29 | mu, tau = parameters["mu"], parameters["tau"] 30 | dtype = mu.dtype 31 | 32 | norm = tf.distributions.Normal(loc=tf.constant(0., dtype=dtype), 33 | scale=tf.constant(1., dtype=dtype)) 34 | sigma = 1./tf.sqrt(tau) 35 | pdf = (1./sigma 36 | * norm.prob(value=((data-mu)/sigma)) 37 | / norm.cdf(value=(mu/sigma))) 38 | pdf = tf.where(tf.less(data, 0.), tf.zeros_like(pdf), pdf) 39 | return(pdf) 40 | 41 | @classmethod 42 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 43 | mu, tau = parameters["mu"], parameters["tau"] 44 | dtype = mu.dtype 45 | 46 | norm = tf.distributions.Normal(loc=tf.constant(0., dtype=dtype), 47 | scale=tf.constant(1., dtype=dtype)) 48 | sigma = 1./tf.sqrt(tau) 49 | llh = (- tf.log(sigma) 50 | + norm.log_prob(value=((data-mu)/sigma)) 51 | - norm.log_cdf(value=(mu/sigma))) 52 | data = data*tf.ones_like(llh) 53 | llh = tf.where(tf.less(data, 0.), -tf.ones_like(llh)*np.inf, llh) 54 | return(llh) 55 | 56 | @classmethod 57 | def gradStep(cls, data, mu, tau, v, e): 58 | swidths = tf.constant(np.array([0., 1e-9, 1e-8, 1e-7, 1e-6, 1e-4, 1e-3, 59 | 1e-2, 1e-1, 1e0, 1e1, 1e2, 60 | 1e3])[..., None, None], 61 | dtype=data.dtype) 62 | for i in range(5): 63 | llhs = cls.llh(parameters={"mu": mu, 64 | "tau": 1./(v+e**2-e*mu)}, 65 | data=data) 66 | llhs = tf.reduce_mean(llhs, axis=0) 67 | signGradMu = tf.sign(tf.gradients(llhs, [mu])[0]) 68 | mus = mu+signGradMu*swidths 69 | taus = 1./(v+e**2-e*mus) 70 | tauIsNonPositive = tf.less_equal(taus, 0.) 71 | mus = tf.where(tauIsNonPositive, tf.zeros_like(mus), mus) 72 | taus = tf.where(tauIsNonPositive, 1./tf.reduce_mean(data, axis=0)*tf.ones_like(taus), taus) 73 | newLlhs = cls.llh(parameters={"mu": mus, 74 | "tau": taus}, 75 | data=data[None]) 76 | newLlhs = tf.reduce_mean(newLlhs, axis=-2, keepdims=True) 77 | argmax = tf.cast(tf.argmax(newLlhs, axis=0)[0], dtype=tf.int32) 78 | mu = tf.gather(mus[:, 0], argmax) 79 | mu = tf.diag_part(mu) 80 | tau = tf.gather(taus[:, 0], argmax) 81 | tau = tf.diag_part(tau) 82 | return(mu, tau) 83 | 84 | @classmethod 85 | def fit(cls, parameters: Dict[str, Tensor], 86 | data: tf.Tensor) -> Dict[str, Tensor]: 87 | 88 | muOld, tauOld = parameters["mu"], parameters["tau"] 89 | 90 | e = tf.reduce_mean(data, axis=0) 91 | v = tf.reduce_mean((data-e)**2, axis=0) 92 | s = tf.reduce_mean(((data-e)/tf.sqrt(v))**3, axis=0) 93 | 94 | muOld, tauOld = cls.gradStep(data, muOld, tauOld, v, e) 95 | 96 | mu = (s*v**1.5 + e*v - e**3)/(v-e**2) 97 | tau = 1./(v+e**2-e*mu) 98 | mu, tau = cls.gradStep(data, mu, tau, v, e) 99 | 100 | llhOld = cls.llh(parameters={"mu": muOld, "tau": tauOld}, data=data) 101 | llhOld = tf.reduce_mean(llhOld, axis=0) 102 | 103 | llh = cls.llh(parameters={"mu": mu, "tau": tau}, data=data) 104 | llh = tf.reduce_mean(llh, axis=0) 105 | 106 | mu = tf.where(tf.greater(llhOld, llh), muOld, mu) 107 | tau = tf.where(tf.greater(llhOld, llh), tauOld, tau) 108 | 109 | assertTauIs0 = tf.Assert(tf.reduce_all(tf.logical_not(tf.equal(tau, 0.))), [tau], name='nnNormalAlgnorNnNortauIs0') 110 | assertTauNotPos = tf.Assert(tf.reduce_all(tf.greater(tau, 0.)), [tau], name='nnNormalAlgnorNnNorCenNotPositive') 111 | with tf.control_dependencies([assertTauIs0, assertTauNotPos]): 112 | tau = tau + 0. 113 | 114 | updatedParameters = {"mu": mu, "tau": tau} 115 | return(updatedParameters) 116 | 117 | @classmethod 118 | def fitLatents(cls, parameters: Dict[str, Tensor], 119 | data: Tensor) -> Dict[str, Tensor]: 120 | return({}) 121 | -------------------------------------------------------------------------------- /decompose/distributions/jumpNormalAlgorithms.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Dict 2 | import numpy as np 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | 6 | from decompose.distributions.algorithms import Algorithms 7 | from decompose.distributions.chopin2011 import rtnorm 8 | 9 | 10 | class JumpNormalAlgorithms(Algorithms): 11 | 12 | @classmethod 13 | def alpha(cls, parameters: Dict[str, Tensor]) -> Tensor: 14 | mu = parameters["mu"] 15 | tau = parameters["tau"] 16 | nu = parameters["nu"] 17 | beta = parameters["beta"] 18 | 19 | sigma = 1./tf.sqrt(tau) 20 | lam = 1./beta 21 | 22 | muStd = tf.constant(0., dtype=mu.dtype) 23 | sigmaStd = tf.constant(1., dtype=mu.dtype) 24 | stdNorm = tf.contrib.distributions.Normal(loc=muStd, scale=sigmaStd) 25 | 26 | c0 = lam*(mu-nu) + stdNorm.log_cdf((nu-(mu+sigma**2*lam))/sigma) 27 | c1 = -lam*(mu-nu) + stdNorm.log_cdf(-(nu-(mu-sigma**2*lam))/sigma) 28 | c = tf.reduce_logsumexp([c0, c1], axis=0) 29 | f = (mu-nu)*lam 30 | 31 | norm = tf.distributions.Normal(loc=mu+sigma**2*lam, scale=sigma) 32 | 33 | alpha = tf.exp(f + norm.log_cdf(nu) - c) 34 | return(alpha) 35 | 36 | @classmethod 37 | def mode(cls, parameters: Dict[str, Tensor]) -> Tensor: 38 | mu = parameters["mu"] 39 | tau = parameters["tau"] 40 | nu = parameters["nu"] 41 | beta = parameters["beta"] 42 | 43 | lam = 1./beta 44 | mode = tf.zeros_like(mu) * tf.zeros_like(mu) 45 | mode = tf.where(tf.logical_and(tf.greater(nu, mu), 46 | tf.less(mu+lam/tau, nu)), 47 | mu+lam/tau, 48 | mode) 49 | mode = tf.where(tf.logical_and(tf.greater(nu, mu), 50 | tf.greater_equal(mu+lam/tau, nu)), 51 | nu, 52 | mode) 53 | mode = tf.where(tf.logical_and(tf.less_equal(nu, mu), 54 | tf.greater(mu-lam/tau, nu)), 55 | mu-lam/tau, 56 | mode) 57 | mode = tf.where(tf.logical_and(tf.less_equal(nu, mu), 58 | tf.less_equal(mu-lam/tau, nu)), 59 | nu, 60 | mode) 61 | return(mode) 62 | 63 | @classmethod 64 | def sample(cls, parameters: Dict[str, Tensor], nSamples: Tensor) -> Tensor: 65 | mu = parameters["mu"] 66 | tau = parameters["tau"] 67 | nu = parameters["nu"] 68 | beta = parameters["beta"] 69 | 70 | shape = tf.concat(((nSamples,), tf.shape(mu)), 0) 71 | ones = tf.ones(shape=shape, dtype=mu.dtype) 72 | mu = tf.reshape(mu*ones, (-1,)) 73 | tau = tf.reshape(tau*ones, (-1,)) 74 | nu = tf.reshape(nu*ones, (-1,)) 75 | beta = tf.reshape(beta*ones, (-1,)) 76 | inf = tf.reshape(tf.ones_like(mu)*tf.constant(np.inf, dtype=mu.dtype), (-1,)) 77 | 78 | # sample from which side of the distribution to sample from 79 | alpha = cls.alpha(parameters) 80 | rUni = tf.random_uniform(shape=tf.shape(mu), dtype=mu.dtype) 81 | isRight = tf.greater(rUni, alpha) 82 | 83 | # sample from the left side 84 | rl = rtnorm(a=-inf, 85 | b=nu, 86 | mu=mu+1./(tau*beta), 87 | sigma=1./tf.sqrt(tau)) 88 | rl = tf.reshape(rl, (-1,)) 89 | 90 | # sample from the right side 91 | rr = rtnorm(a=nu, 92 | b=inf, 93 | mu=mu-1./(tau*beta), 94 | sigma=1./tf.sqrt(tau)) 95 | rr = tf.reshape(rr, (-1,)) 96 | 97 | # pick the samples randomly 98 | r = tf.where(isRight, rr, rl) 99 | 100 | # reshaping the samples 101 | r = tf.reshape(r, shape) 102 | return(r) 103 | 104 | @classmethod 105 | def logConstant(cls, parameters: Dict[str, Tensor]) -> Tensor: 106 | mu = parameters["mu"] 107 | tau = parameters["tau"] 108 | nu = parameters["nu"] 109 | beta = parameters["beta"] 110 | 111 | sigma = 1./tf.sqrt(tau) 112 | zero = tf.constant(0., dtype=mu.dtype) 113 | one = tf.constant(1., dtype=mu.dtype) 114 | stdNormal = tf.distributions.Normal(loc=zero, scale=one) 115 | l0 = (mu-nu)/beta + stdNormal.log_cdf((nu-(mu+1./(beta*tau)))/sigma) 116 | l1 = -(mu-nu)/beta + stdNormal.log_cdf(-(nu-(mu-1./(beta*tau)))/sigma) 117 | log_c = tf.reduce_logsumexp((l0, l1)) 118 | return(log_c) 119 | 120 | @classmethod 121 | def pdf(cls, parameters: Dict[str, Tensor], data: Tensor) -> Tensor: 122 | logProb = cls.llh(parameters, data) 123 | prob = tf.exp(logProb) 124 | return(prob) 125 | 126 | @classmethod 127 | def llh(cls, parameters: Dict[str, Tensor], data: tf.Tensor) -> Tensor: 128 | mu = parameters["mu"] 129 | tau = parameters["tau"] 130 | nu = parameters["nu"] 131 | beta = parameters["beta"] 132 | 133 | logConstant = cls.logConstant(parameters) 134 | sign = tf.sign(data-nu) 135 | loc = mu-1./(tau*beta)*sign 136 | scale = 1./tf.sqrt(tau) 137 | normal = tf.distributions.Normal(loc=loc, scale=scale) 138 | logProb = normal.log_prob(value=data) - (mu-nu)*sign/beta - logConstant 139 | return(logProb) 140 | 141 | @classmethod 142 | def fit(cls, parameters: Dict[str, Tensor], 143 | data: tf.Tensor) -> Dict[str, Tensor]: 144 | raise NotImplemented 145 | 146 | @classmethod 147 | def fitLatents(cls, parameters: Dict[str, Tensor], 148 | data: Tensor) -> Dict[str, Tensor]: 149 | return({}) 150 | -------------------------------------------------------------------------------- /decompose/likelihoods/tests/test_normal2dLikelihood.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | import pytest 3 | import numpy as np 4 | import scipy as sp 5 | import scipy.stats 6 | import tensorflow as tf 7 | 8 | from decompose.likelihoods.normal2dLikelihood import Normal2dLikelihood 9 | from decompose.tests.fixtures import device, dtype 10 | from decompose.distributions.distribution import UpdateType 11 | 12 | 13 | @pytest.fixture(scope="module", 14 | params=[0, 1]) 15 | def f(request): 16 | f = request.param 17 | return(f) 18 | 19 | 20 | @pytest.fixture(scope="module", 21 | params=[UpdateType.ALL, UpdateType.ONLYLATENTS]) 22 | def updateType(request): 23 | updateType = request.param 24 | return(updateType) 25 | 26 | 27 | 28 | def test_residuals(device, dtype): 29 | npdtype = dtype.as_numpy_dtype 30 | M, K, tau = (20, 30), 3, 0.1 31 | U = (tf.constant(np.random.normal(size=(K, M[0])).astype(npdtype)), 32 | tf.constant(np.random.normal(size=(K, M[1])).astype(npdtype))) 33 | noise = np.random.normal(size=M).astype(npdtype) 34 | data = tf.matmul(tf.transpose(U[0]), U[1]) + tf.constant(noise) 35 | 36 | lh = Normal2dLikelihood(M=M, K=K, tau=tau, dtype=dtype) 37 | lh.init(data=data) 38 | 39 | r = lh.residuals(U, data) 40 | 41 | assert(r.dtype == dtype) 42 | 43 | with tf.Session() as sess: 44 | sess.run(tf.global_variables_initializer()) 45 | npr = sess.run(r) 46 | 47 | assert(np.allclose(noise.flatten(), npr, atol=1e-5, rtol=1e-5)) 48 | tf.reset_default_graph() 49 | 50 | 51 | def test_loss(device, dtype): 52 | npdtype = dtype.as_numpy_dtype 53 | M, K, tau = (20, 30), 3, 0.1 54 | U = (tf.constant(np.random.normal(size=(K, M[0])).astype(npdtype)), 55 | tf.constant(np.random.normal(size=(K, M[1])).astype(npdtype))) 56 | noise = np.random.normal(size=M).astype(npdtype) 57 | data = tf.matmul(tf.transpose(U[0]), U[1]) + tf.constant(noise) 58 | 59 | lh = Normal2dLikelihood(M=M, K=K, tau=tau, dtype=dtype) 60 | lh.init(data=data) 61 | 62 | loss = lh.loss(U, data) 63 | 64 | assert(loss.dtype == dtype) 65 | 66 | with tf.Session() as sess: 67 | sess.run(tf.global_variables_initializer()) 68 | nploss = sess.run(loss) 69 | 70 | assert(np.allclose(np.sum(noise**2), nploss, atol=1e-5, rtol=1e-5)) 71 | tf.reset_default_graph() 72 | 73 | 74 | def test_llh(device, dtype): 75 | npdtype = dtype.as_numpy_dtype 76 | M, K, tau = (20, 30), 3, 0.1 77 | U = (tf.constant(np.random.normal(size=(K, M[0])).astype(npdtype)), 78 | tf.constant(np.random.normal(size=(K, M[1])).astype(npdtype))) 79 | noise = np.random.normal(size=M).astype(npdtype) 80 | data = tf.matmul(tf.transpose(U[0]), U[1]) + tf.constant(noise) 81 | 82 | lh = Normal2dLikelihood(M=M, K=K, tau=tau, dtype=dtype) 83 | lh.init(data=data) 84 | 85 | llh = lh.llh(U, data) 86 | 87 | assert(llh.dtype == dtype) 88 | 89 | with tf.Session() as sess: 90 | sess.run(tf.global_variables_initializer()) 91 | npllh = sess.run(llh) 92 | 93 | llhgt = np.sum(sp.stats.norm(loc=0., scale=1./np.sqrt(tau)).logpdf(noise)) 94 | assert(np.allclose(llhgt, npllh, atol=1e-5, rtol=1e-5)) 95 | tf.reset_default_graph() 96 | 97 | 98 | def test_prepVars(device, f, dtype): 99 | npdtype = dtype.as_numpy_dtype 100 | M, K, tau = (20, 30), 3, 0.1 101 | npU = (np.random.normal(size=(K, M[0])).astype(npdtype), 102 | np.random.normal(size=(K, M[1])).astype(npdtype)) 103 | U = (tf.constant(npU[0]), tf.constant(npU[1])) 104 | npnoise = np.random.normal(size=M).astype(npdtype) 105 | npdata = np.dot(npU[0].T, npU[1]) + npnoise 106 | data = tf.constant(npdata, dtype=dtype) 107 | 108 | lh = Normal2dLikelihood(M=M, K=K, tau=tau, dtype=dtype) 109 | lh.init(data=data) 110 | 111 | A, B, alpha = lh.prepVars(f, U, data) 112 | 113 | assert(A.dtype == dtype) 114 | assert(B.dtype == dtype) 115 | assert(alpha.dtype.base_dtype == dtype) 116 | 117 | with tf.Session() as sess: 118 | sess.run(tf.global_variables_initializer()) 119 | npA, npB, npalpha = sess.run([A, B, alpha]) 120 | 121 | if f == 0: 122 | Agt = np.dot(npdata, npU[1].T) 123 | Bgt = np.dot(npU[1], npU[1].T) 124 | assert(np.allclose(Agt, npA, atol=1e-5, rtol=1e-5)) 125 | assert(np.allclose(Bgt, npB, atol=1e-5, rtol=1e-5)) 126 | assert(np.allclose(tau, npalpha, atol=1e-5, rtol=1e-5)) 127 | if f == 1: 128 | Agt = np.dot(npdata.T, npU[0].T) 129 | Bgt = np.dot(npU[0], npU[0].T) 130 | assert(np.allclose(Agt, npA, atol=1e-5, rtol=1e-5)) 131 | assert(np.allclose(Bgt, npB, atol=1e-5, rtol=1e-5)) 132 | assert(np.allclose(tau, npalpha, atol=1e-5, rtol=1e-5)) 133 | tf.reset_default_graph() 134 | 135 | 136 | def test_update(device, f, updateType, dtype): 137 | npdtype = dtype.as_numpy_dtype 138 | M, K, tau = (20, 30), 3, 0.1 139 | npU = (np.random.normal(size=(K, M[0])).astype(npdtype), 140 | np.random.normal(size=(K, M[1])).astype(npdtype)) 141 | U = (tf.constant(npU[0]), tf.constant(npU[1])) 142 | npnoise = np.random.normal(size=M).astype(npdtype) 143 | npdata = np.dot(npU[0].T, npU[1]) + npnoise 144 | data = tf.constant(npdata, dtype=dtype) 145 | 146 | lh = Normal2dLikelihood(M=M, K=K, tau=tau, updateType=updateType) 147 | lh.init(data=data) 148 | lh.noiseDistribution.update = MagicMock() 149 | residuals = tf.ones_like(data) 150 | lh.residuals = MagicMock(return_value=residuals) 151 | 152 | lh.update(U, data) 153 | 154 | if updateType == UpdateType.ALL: 155 | lh.residuals.assert_called_once() 156 | lh.noiseDistribution.update.assert_called_once() 157 | else: 158 | lh.residuals.assert_not_called() 159 | lh.noiseDistribution.update.assert_not_called() 160 | tf.reset_default_graph() 161 | -------------------------------------------------------------------------------- /decompose/likelihoods/cvNormalNdLikelihood.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple, List 3 | import tensorflow as tf 4 | from tensorflow import Tensor 5 | import string 6 | 7 | from decompose.distributions.distribution import DrawType, UpdateType 8 | from decompose.distributions.cenNormal import CenNormal 9 | from decompose.likelihoods.likelihood import Likelihood 10 | from decompose.distributions.distribution import Properties 11 | from decompose.cv.cv import CV 12 | 13 | 14 | class CVNormalNdLikelihood(Likelihood): 15 | 16 | def __init__(self, M: Tuple[int, ...], K: int=1, tau: float = 1./1e10, 17 | cv: CV = None, 18 | drawType: DrawType = DrawType.SAMPLE, 19 | updateType: UpdateType = UpdateType.ALL, 20 | dtype=tf.float32) -> None: 21 | Likelihood.__init__(self, M, K) 22 | self.__cv = cv 23 | self.__tauInit = tau 24 | self.__dtype = dtype 25 | self.__properties = Properties(name='likelihood', 26 | drawType=drawType, 27 | updateType=updateType, 28 | persistent=True) 29 | 30 | def init(self, data: Tensor) -> None: 31 | tau = self.__tauInit 32 | dtype = self.__dtype 33 | properties = self.__properties 34 | noiseDistribution = CenNormal(tau=tf.constant([tau], dtype=dtype), 35 | properties=properties) 36 | self.__noiseDistribution = noiseDistribution 37 | observedMask = tf.logical_not(tf.is_nan(data)) 38 | trainMask = tf.logical_not(self.cv.mask(X=data)) 39 | trainMask = tf.get_variable("trainMask", 40 | dtype=trainMask.dtype, 41 | initializer=trainMask) 42 | trainMask = tf.logical_and(trainMask, observedMask) 43 | testMask = tf.logical_and(observedMask, 44 | tf.logical_not(trainMask)) 45 | self.__observedMask = observedMask 46 | self.__trainMask = trainMask 47 | self.__testMask = testMask 48 | 49 | @property 50 | def cv(self) -> CV: 51 | return(self.__cv) 52 | 53 | @property 54 | def observedMask(self) -> Tensor: 55 | return(self.__observedMask) 56 | 57 | @property 58 | def trainMask(self) -> Tensor: 59 | return(self.__trainMask) 60 | 61 | @property 62 | def testMask(self) -> Tensor: 63 | return(self.__testMask) 64 | 65 | @property 66 | def noiseDistribution(self) -> CenNormal: 67 | return(self.__noiseDistribution) 68 | 69 | def residuals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 70 | return(self.testResiduals(U, X)) 71 | 72 | def testResiduals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 73 | F = len(U) 74 | axisIds = string.ascii_lowercase[:F] 75 | subscripts = f'k{",k".join(axisIds)}->{axisIds}' 76 | Xhat = tf.einsum(subscripts, *U) 77 | residuals = tf.reshape(X-Xhat, (-1,)) 78 | indices = tf.cast(tf.where(tf.reshape(self.testMask, (-1,))), 79 | dtype=tf.int32) 80 | testResiduals = tf.gather_nd(residuals, indices) 81 | return(testResiduals) 82 | 83 | def trainResiduals(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 84 | F = len(U) 85 | axisIds = string.ascii_lowercase[:F] 86 | subscripts = f'k{",k".join(axisIds)}->{axisIds}' 87 | Xhat = tf.einsum(subscripts, *U) 88 | residuals = tf.reshape(X-Xhat, (-1,)) 89 | indices = tf.cast(tf.where(tf.reshape(self.trainMask, (-1,))), 90 | dtype=tf.int32) 91 | trainResiduals = tf.gather_nd(residuals, indices) 92 | return(trainResiduals) 93 | 94 | def llh(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 95 | r = self.testResiduals(U, X) 96 | llh = tf.reduce_sum(self.noiseDistribution.llh(r)) 97 | return(llh) 98 | 99 | def loss(self, U: Tuple[Tensor, ...], X: Tensor) -> Tensor: 100 | loss = tf.reduce_sum(self.testResiduals(U, X)**2) 101 | return(loss) 102 | 103 | def update(self, U: Tuple[Tensor, ...], X: Tensor) -> None: 104 | if self.noiseDistribution.updateType == UpdateType.ALL: 105 | residuals = self.trainResiduals(U, X) 106 | flattenedResiduals = residuals[..., None] 107 | self.noiseDistribution.update(flattenedResiduals) 108 | 109 | def outterTensorProduct(self, Us): 110 | F = len(Us) 111 | axisIds = string.ascii_lowercase[:F] 112 | subscripts = f'k{",k".join(axisIds)}->{axisIds}k' 113 | Xhat = tf.einsum(subscripts, *Us) 114 | return(Xhat) 115 | 116 | def calcB(self, mask, UmfOutter, f, F): 117 | axisIds0 = (string.ascii_lowercase[:f] 118 | + "x" 119 | + string.ascii_lowercase[f:F-1]) 120 | axisIds1 = string.ascii_lowercase[:F-1] + "y" 121 | axisIds2 = string.ascii_lowercase[:F-1] + "z" 122 | subscripts = (axisIds0 + "," 123 | + axisIds1 + "," 124 | + axisIds2 + "->" 125 | + "xyz") 126 | B = tf.einsum(subscripts, mask, UmfOutter, UmfOutter) 127 | return(B) 128 | 129 | def prepVars(self, f: int, U: List[Tensor], 130 | X: Tensor) -> Tuple[Tensor, Tensor, Tensor]: 131 | mask = tf.cast(self.trainMask, dtype=U[0].dtype) 132 | F = self.F 133 | 134 | Umf = [U[g] for g in range(F) if g != f] 135 | UmfOutter = self.outterTensorProduct(Umf) 136 | 137 | rangeFm1 = list(range(F-1)) 138 | A = tf.tensordot(X*mask, UmfOutter, 139 | axes=([g for g in range(F) if g != f], rangeFm1)) 140 | 141 | B = self.calcB(mask, UmfOutter, f, F) 142 | alpha = self.noiseDistribution.tau 143 | return(A, B, alpha) 144 | -------------------------------------------------------------------------------- /decompose/distributions/tests/test_nnNormalAlgorithms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy as sp 4 | import scipy.stats 5 | import tensorflow as tf 6 | 7 | from decompose.distributions.nnNormalAlgorithms import NnNormalAlgorithms 8 | from decompose.tests.fixtures import device, dtype 9 | 10 | 11 | # @pytest.mark.slow 12 | # def test_nnNormal_sample(dtype): 13 | # """Test whether the mean and the variance of the samples are correct.""" 14 | # npdtype = dtype.as_numpy_dtype 15 | # mu = np.array([-1, 0., 1.]).astype(npdtype) 16 | # tau = np.array([0.5, 1., 2.]).astype(npdtype) 17 | # nSamples = 1000000 18 | 19 | # nParameters = mu.shape[0] 20 | # parameters = {"mu": tf.constant(mu, dtype=dtype), 21 | # "tau": tf.constant(tau, dtype=dtype)} 22 | # tfNSamples = nSamples 23 | # r = NnNormalAlgorithms.sample(parameters=parameters, 24 | # nSamples=tfNSamples) 25 | 26 | # assert(r.dtype == dtype) 27 | 28 | # with tf.Session() as sess: 29 | # r = sess.run(r) 30 | 31 | # assert(r.shape == (nSamples, nParameters)) 32 | 33 | # sigma = 1./np.sqrt(tau) 34 | # alpha = -mu/sigma 35 | # normal = sp.stats.norm() 36 | # meanGt = mu + sigma*normal.pdf(alpha)/normal.cdf(-alpha) 37 | # mean = np.mean(r, axis=0) 38 | # assert(np.allclose(meanGt, mean, atol=1e-1)) 39 | 40 | # varGt = sigma**2*(1 + alpha*normal.pdf(alpha)/normal.cdf(-alpha) 41 | # - (normal.pdf(alpha)/normal.cdf(-alpha))**2) 42 | # var = np.var(r, axis=0) 43 | # assert(np.allclose(varGt, var, atol=1e-1)) 44 | 45 | 46 | # def test_nnNormal_mode(dtype): 47 | # """Test if the mode is equal to `mu`.""" 48 | # npdtype = dtype.as_numpy_dtype 49 | # mu = np.array([-1, 0., 1.]).astype(npdtype) 50 | # tau = np.array([0.5, 1., 2.]).astype(npdtype) 51 | 52 | # nParameters = mu.shape[0] 53 | # parameters = {"mu": tf.constant(mu, dtype=dtype), 54 | # "tau": tf.constant(tau, dtype=dtype)} 55 | # mode = NnNormalAlgorithms.mode(parameters=parameters) 56 | 57 | # assert(mode.dtype == dtype) 58 | 59 | # with tf.Session() as sess: 60 | # mode = sess.run(mode) 61 | 62 | # modeGt = mu 63 | # modeGt[modeGt < 0.] = 0. 64 | 65 | # assert(mode.shape == (nParameters,)) 66 | # assert(np.all(mode == modeGt)) 67 | 68 | 69 | # def test_nnNormal_pdf(dtype): 70 | # """Test if the pdf is the same as reported by scipy.""" 71 | # npdtype = dtype.as_numpy_dtype 72 | # mu = np.array([-1, 0., 1.]).astype(npdtype) 73 | # tau = np.array([0.5, 1., 2.]).astype(npdtype) 74 | # nSamples = 1000 75 | 76 | # nParameters = mu.shape[0] 77 | # data = np.random.random((nSamples, nParameters)).astype(npdtype) 78 | # parameters = {"mu": tf.constant(mu, dtype=dtype), 79 | # "tau": tf.constant(tau, dtype=dtype)} 80 | # tfData = tf.constant(data) 81 | # probs = NnNormalAlgorithms.pdf(parameters=parameters, 82 | # data=tfData) 83 | 84 | # assert(probs.dtype == dtype) 85 | 86 | # with tf.Session() as sess: 87 | # probs = sess.run(probs) 88 | 89 | # assert(probs.shape == (nSamples, nParameters)) 90 | # sigma = 1./np.sqrt(tau) 91 | # a, b = -mu/sigma, np.ones_like(mu)*np.inf 92 | # print(a.shape, b.shape, mu.shape, sigma.shape, data.shape) 93 | # for i in range(mu.shape[0]): 94 | # spProbsi = sp.stats.truncnorm.pdf(x=data[..., i], a=a[i], b=b[i], 95 | # loc=mu[i], scale=sigma[i]) 96 | # assert(np.allclose(probs[..., i], spProbsi)) 97 | 98 | 99 | # def test_nnNormal_llh(dtype): 100 | # """Test if the llh is the same as reported by scipy.""" 101 | # npdtype = dtype.as_numpy_dtype 102 | # mu = np.array([-1, 0., 1.]).astype(npdtype) 103 | # tau = np.array([0.5, 1., 2.]).astype(npdtype) 104 | # nSamples = 1000 105 | 106 | # nParameters = mu.shape[0] 107 | # data = np.random.random((nSamples, nParameters)).astype(npdtype) 108 | 109 | # parameters = {"mu": tf.constant(mu, dtype=dtype), 110 | # "tau": tf.constant(tau, dtype=dtype)} 111 | # tfData = tf.constant(data) 112 | # llh = NnNormalAlgorithms.llh(parameters=parameters, 113 | # data=tfData) 114 | 115 | # assert(llh.dtype == dtype) 116 | 117 | # with tf.Session() as sess: 118 | # llh = sess.run(llh) 119 | 120 | # assert(llh.shape == (nSamples, nParameters)) 121 | # sigma = 1./np.sqrt(tau) 122 | # a, b = -mu/sigma, np.ones_like(mu)*np.inf 123 | # print(a.shape, b.shape, mu.shape, sigma.shape, data.shape) 124 | # for i in range(mu.shape[0]): 125 | # spLlhi = sp.stats.truncnorm.logpdf(x=data[..., i], a=a[i], b=b[i], 126 | # loc=mu[i], scale=sigma[i]) 127 | # assert(np.allclose(llh[..., i], spLlhi)) 128 | 129 | 130 | @pytest.mark.slow 131 | def test_nnNormal_fit(dtype): 132 | """Test if the fitted parameters match the true parameters.""" 133 | npdtype = dtype.as_numpy_dtype 134 | mu = np.array([-1, 0., 1.]).astype(npdtype) 135 | tau = np.array([0.5, 1., 2.]).astype(npdtype) 136 | sigma = 1./np.sqrt(tau) 137 | nSamples = 100000 138 | 139 | nParameters = mu.shape[0] 140 | a, b = -mu/sigma, np.ones_like(mu)*np.inf 141 | norm = sp.stats.truncnorm(a=a, b=b, loc=mu, scale=sigma) 142 | data = norm.rvs(size=(nSamples, nParameters)).astype(npdtype) 143 | parameters = {"mu": tf.constant(np.ones(nParameters), dtype=dtype), 144 | "tau": tf.constant(np.ones(nParameters), dtype=dtype)} 145 | tfData = tf.constant(data) 146 | parameters = NnNormalAlgorithms.fit(parameters=parameters, 147 | data=tfData) 148 | 149 | assert(parameters['mu'].dtype == dtype) 150 | assert(parameters['tau'].dtype == dtype) 151 | 152 | with tf.Session() as sess: 153 | parameters = sess.run(parameters) 154 | 155 | muHat = parameters["mu"] 156 | assert(muHat.shape == mu.shape) 157 | assert(np.allclose(muHat, mu, atol=1e-1)) 158 | 159 | tauHat = parameters["tau"] 160 | assert(tauHat.shape == tau.shape) 161 | assert(np.allclose(tauHat, tau, atol=1e-1)) 162 | 163 | print(muHat) 164 | print(tauHat) 165 | --------------------------------------------------------------------------------