Exemple #1
0
def test_conv():
    """Test multichannel convolution with bypass"""
    n_components = 1
    seed = 42
    L = 20
    channels = 3
    bypass = 1
    svi = False

    n_inputs = channels * L**2 + bypass

    nn = NeuralNet(n_components=n_components,
                   n_filters=[8, 8],
                   n_hiddens=[10],
                   n_inputs=n_inputs,
                   input_shape=(channels, L, L),
                   n_bypass=1,
                   n_outputs=1,
                   seed=seed,
                   svi=svi)

    eval_lprobs = theano.function([nn.params, nn.stats], nn.lprobs)

    conv_sizes = [
        lasagne.layers.get_output_shape(nn.layer['conv_' + str(i + 1)])
        for i in range(2)
    ]

    res = eval_lprobs(np.array([[1.], [2.]], dtype=dtype),
                      np.random.normal(size=(2, n_inputs)).astype(dtype))

    mog = nn.get_mog(np.random.normal(size=(1, n_inputs)).astype(dtype))
 def reinit_network(self):
     """Reinitializes the network instance (re-setting the weights!)
     """
     self.network = NeuralNet(**self.kwargs)
     self.svi = self.network.svi if 'svi' in dir(self.network) else False
     """update self.kwargs['seed'] so that reinitializing the network gives a
     different result each time unless we reseed the inference method"""
     self.kwargs['seed'] = self.gen_newseed()
     self.norm_init()
Exemple #3
0
def test_trainer_updates():
    n_components = 1
    n_params = 2
    seed = 42
    svi = True

    m = Gauss(dim=n_params)
    p = dd.Gaussian(m=np.zeros((n_params, )), S=np.eye(n_params))
    s = ds.Identity()
    g = dg.Default(model=m, prior=p, summary=s)

    nn = NeuralNet(
        n_components=n_components,
        n_hiddens=[10],
        n_inputs=n_params,
        n_outputs=n_params,
        seed=seed,
        svi=svi)
    loss = -tt.mean(nn.lprobs)

    trn_inputs = [nn.params, nn.stats]
    trn_data = g.gen(100)  # params, stats
    trn_data = tuple(x.astype(dtype) for x in trn_data)

    t = Trainer(network=nn, loss=loss, trn_data=trn_data, trn_inputs=trn_inputs)

    # single update
    outputs = t.make_update(*trn_data)

    # training
    outputs = t.train(100, 50)
Exemple #4
0
def test_lprobs():
    n_components = 2
    seed = 42
    svi = False

    nn = NeuralNet(n_components=n_components,
                   n_hiddens=[10],
                   n_inputs=1,
                   n_outputs=1,
                   seed=seed,
                   svi=svi)

    eval_lprobs = theano.function([nn.params, nn.stats], nn.lprobs)

    res = eval_lprobs(np.array([[1.], [2.]], dtype=dtype),
                      np.array([[1.], [2.]], dtype=dtype))

    mog = nn.get_mog(np.array([[1.]], dtype=dtype))
Exemple #5
0
def test_diag_precision_bounds():
    n_components = 2
    seed = 42
    svi = False
    min_precisions = np.array([0.1, 2.0, 15.0])

    nn = NeuralNet(n_components=n_components,
                   n_hiddens=[10],
                   n_inputs=3,
                   n_outputs=3,
                   seed=seed,
                   svi=svi,
                   min_precisions=None)

    nn_bounded = NeuralNet(n_components=n_components,
                           n_hiddens=[10],
                           n_inputs=3,
                           n_outputs=3,
                           seed=seed,
                           svi=svi,
                           min_precisions=min_precisions)
    mog_bounded = nn_bounded.get_mog(np.array(np.ones((1, 3)), dtype=dtype))
    for x in mog_bounded.xs:
        assert np.allclose(0.0, np.maximum(0.0, min_precisions - np.diag(x.P)))
Exemple #6
0
def test_conv():
    n_components = 1
    seed = 42
    svi = False

    nn = NeuralNet(n_components=n_components,
                   n_filters=[8, 8],
                   n_hiddens=[10],
                   n_inputs=(1, 20, 20),
                   n_outputs=1,
                   seed=seed,
                   svi=svi)

    eval_lprobs = theano.function([nn.params, nn.stats], nn.lprobs)

    conv_sizes = [
        lasagne.layers.get_output_shape(nn.layer['conv_' + str(i + 1)])
        for i in range(2)
    ]

    res = eval_lprobs(np.array([[1.], [2.]], dtype=dtype),
                      np.random.normal(size=(2, 1, 20, 20)).astype(dtype))

    mog = nn.get_mog(np.ones((1, 1, 20, 20), dtype=dtype))
Exemple #7
0
    def split_components(self, standardize=False):
        """Split MoG components

        Replicates, perturbes and (optionally) standardizes MoG
        components across rounds.

        Replica MoG components serve as part of initialization for
        MDN in the next round.

        Parameters
        ----------
        standardize : boolean
            whether to standardize the replicated components
        """
        # define magnitute of (symmetry-breaking) perturbation noise on weights
        eps = 1.0e-2 if standardize else 1.0e-6

        if self.kwargs['n_components'] > self.network.n_components:

            # get parameters of current network
            old_params = self.network.params_dict.copy()
            old_n_components = self.network.n_components

            # create new network
            self.network = NeuralNet(**self.kwargs)
            new_params = self.network.params_dict

            # set weights of new network
            comps_new = [
                s for s in new_params if 'means' in s or 'precisions' in s
            ]

            # weights of additional components are duplicates
            for p in np.sort(comps_new):
                i = int(float(p[-1])) % old_n_components
                # WARNING: this assumes there is less <10 old components!
                old_params[p] = old_params[p[:-1] + str(i)].copy()
                old_params[p] += eps * self.rng.randn(*new_params[p].shape)

            # assert mixture coefficients are alpha_k = 1/n_components)
            old_params['weights.mW'] = 0. * new_params['weights.mW']
            old_params['weights.mb'] = 0. * new_params['weights.mb']

            self.network.params_dict = old_params

            if standardize:
                self.standardize_components()
Exemple #8
0
    def run(self, n_train=100, n_rounds=2, epochs=100, minibatch=50,
            monitor=None, **kwargs):
        """Run algorithm

        Parameters
        ----------
        n_train : int or list of ints
            Number of data points drawn per round. If a list is passed, the
            nth list element specifies the number of training examples in the
            nth round. If there are fewer list elements than rounds, the last
            list element is used.
        n_rounds : int
            Number of rounds
        epochs: int
            Number of epochs used for neural network training
        minibatch: int
            Size of the minibatches used for neural network training
        monitor : list of str
            Names of variables to record during training along with the value
            of the loss function. The observables attribute contains all
            possible variables that can be monitored
        kwargs : additional keyword arguments
            Additional arguments for the Trainer instance

        Returns
        -------
        logs : list of dicts
            Dictionaries contain information logged while training the networks
        trn_datasets : list of (params, stats)
            training datasets, z-transformed
        posteriors : list of posteriors
            posterior after each round
        """
        logs = []
        trn_datasets = []
        posteriors = []

        for r in range(n_rounds):  # start at 1
            self.round += 1

            # if round > 1, set new proposal distribution before sampling
            if self.round > 1:
                # posterior becomes new proposal prior
                posterior = self.predict(self.obs)
                self.generator.proposal = posterior.project_to_gaussian()
            # number of training examples for this round
            if type(n_train) == list:
                try:
                    n_train_round = n_train[self.round - 1]
                except:
                    n_train_round = n_train[-1]
            else:
                n_train_round = n_train

            # draw training data (z-transformed params and stats)
            verbose = '(round {}) '.format(r) if self.verbose else False
            trn_data = self.gen(n_train_round, verbose=verbose)

            # algorithm 2 of Papamakarios and Murray
            if r + 1 == n_rounds and self.n_components > 1:
                # get parameters of current network
                old_params = self.network.params_dict.copy()

                # create new network
                network_spec = self.network.spec_dict.copy()
                network_spec.update({'n_components': self.n_components})
                self.network = NeuralNet(**network_spec)
                new_params = self.network.params_dict

                """In order to go from 1 component in previous rounds to
                self.n_components in the current round we will duplicate
                component 1 self.n_components times, with small random
                perturbations to the parameters affecting component means
                and precisions, and the SVI s.d.s of those parameters. Set the
                mixture coefficients to all be equal"""
                mp_param_names = [s for s in new_params if 'means' in s or \
                    'precisions' in s]  # list of dict keys
                for param_name in mp_param_names:
                    """for each param_name, get the corresponding old parameter
                    name/value for what was previously the only mixture
                    component"""
                    source_param_name = param_name[:-1] + '0'
                    source_param_val = old_params[source_param_name]
                    # copy it to the new component, add noise to break symmetry
                    old_params[param_name] = source_param_val.copy() + \
                        1.0e-6 * self.rng.randn(*source_param_val.shape)

                # initialize with equal mixture coefficients for all data
                old_params['weights.mW'] = 0. * new_params['weights.mW']
                old_params['weights.mb'] = 0. * new_params['weights.mb']

                self.network.params_dict = old_params

            trn_inputs = [self.network.params, self.network.stats]

            t = Trainer(self.network, self.loss(N=n_train_round),
                        trn_data=trn_data, trn_inputs=trn_inputs,
                        monitor=self.monitor_dict_from_names(monitor),
                        seed=self.gen_newseed(), **kwargs)
            logs.append(t.train(epochs=epochs, minibatch=minibatch,
                                verbose=verbose))

            trn_datasets.append(trn_data)
            try:
                posteriors.append(self.predict(self.obs))
            except:
                posteriors.append(None)
                print('analytic correction for proposal seemingly failed!')
                break

        return logs, trn_datasets, posteriors
Exemple #9
0
    def __init__(self, generator, prior_norm=True, pilot_samples=100,
                 seed=None, verbose=True, **kwargs):
        """Abstract base class for inference algorithms

        Inference algorithms must at least implement abstract methods of this
        class.

        Parameters
        ----------
        generator : generator instance
            Generator instance
        prior_norm : bool
            If set to True, will z-transform params based on mean/std of prior
        pilot_samples : None or int
            If an integer is provided, a pilot run with the given number of
            samples is run. The mean and std of the summary statistics of the
            pilot samples will be subsequently used to z-transform summary
            statistics.
        seed : int or None
            If provided, random number generator will be seeded
        kwargs : additional keyword arguments
            Additional arguments used when creating the NeuralNet instance

        Attributes
        ----------
        observables : dict
            Dictionary containing theano variables that can be monitored while
            training the neural network.
        """
        self.seed = seed
        if seed is not None:
            self.rng = np.random.RandomState(seed=seed)
        else:
            self.rng = np.random.RandomState()
        self.verbose = verbose

        # bind generator, reset proposal attribute
        self.generator = generator
        self.generator.proposal = None

        # generate a sample to get input and output dimensions
        params, stats = generator.gen(1, skip_feedback=True, verbose=False)
        kwargs.update({'n_inputs': stats.shape[1:],
                       'n_outputs': params.shape[1],
                       'seed': self.gen_newseed()})

        self.network = NeuralNet(**kwargs)
        self.svi = self.network.svi

        # parameters for z-transform of params
        if prior_norm:
            # z-transform for params based on prior
            self.params_mean = self.generator.prior.mean
            self.params_std = self.generator.prior.std
        else:
            # parameters are set such that z-transform has no effect
            self.params_mean = np.zeros((params.shape[1],))
            self.params_std = np.ones((params.shape[1],))

        # parameters for z-transform for stats
        if pilot_samples is not None and pilot_samples != 0:
            # determine via pilot run
            self.pilot_run(pilot_samples)
        else:
            # parameters are set such that z-transform has no effect
            self.stats_mean = np.zeros((stats.shape[1],))
            self.stats_std = np.ones((stats.shape[1],))

        # observables contains vars that can be monitored during training
        self.compile_observables()
Exemple #10
0
class BaseInference(metaclass=ABCMetaDoc):
    def __init__(self, generator, prior_norm=True, pilot_samples=100,
                 seed=None, verbose=True, **kwargs):
        """Abstract base class for inference algorithms

        Inference algorithms must at least implement abstract methods of this
        class.

        Parameters
        ----------
        generator : generator instance
            Generator instance
        prior_norm : bool
            If set to True, will z-transform params based on mean/std of prior
        pilot_samples : None or int
            If an integer is provided, a pilot run with the given number of
            samples is run. The mean and std of the summary statistics of the
            pilot samples will be subsequently used to z-transform summary
            statistics.
        seed : int or None
            If provided, random number generator will be seeded
        kwargs : additional keyword arguments
            Additional arguments used when creating the NeuralNet instance

        Attributes
        ----------
        observables : dict
            Dictionary containing theano variables that can be monitored while
            training the neural network.
        """
        self.seed = seed
        if seed is not None:
            self.rng = np.random.RandomState(seed=seed)
        else:
            self.rng = np.random.RandomState()
        self.verbose = verbose

        # bind generator, reset proposal attribute
        self.generator = generator
        self.generator.proposal = None

        # generate a sample to get input and output dimensions
        params, stats = generator.gen(1, skip_feedback=True, verbose=False)
        kwargs.update({'n_inputs': stats.shape[1:],
                       'n_outputs': params.shape[1],
                       'seed': self.gen_newseed()})

        self.network = NeuralNet(**kwargs)
        self.svi = self.network.svi

        # parameters for z-transform of params
        if prior_norm:
            # z-transform for params based on prior
            self.params_mean = self.generator.prior.mean
            self.params_std = self.generator.prior.std
        else:
            # parameters are set such that z-transform has no effect
            self.params_mean = np.zeros((params.shape[1],))
            self.params_std = np.ones((params.shape[1],))

        # parameters for z-transform for stats
        if pilot_samples is not None and pilot_samples != 0:
            # determine via pilot run
            self.pilot_run(pilot_samples)
        else:
            # parameters are set such that z-transform has no effect
            self.stats_mean = np.zeros((stats.shape[1],))
            self.stats_std = np.ones((stats.shape[1],))

        # observables contains vars that can be monitored during training
        self.compile_observables()

    @abc.abstractmethod
    def loss(self):
        pass

    @abc.abstractmethod
    def run(self):
        pass

    def gen(self, n_samples, n_reps=1, prior_mixin=0, verbose=None):
        """Generate from generator and z-transform

        Parameters
        ----------
        n_samples : int
            Number of samples to generate
        n_reps : int
            Number of repeats per parameter
        verbose : None or bool or str
            If None is passed, will default to self.verbose
        """
        verbose = self.verbose if verbose is None else verbose
        params, stats = self.generator.gen(n_samples, prior_mixin=prior_mixin, verbose=verbose)

        # z-transform params and stats
        params = (params - self.params_mean) / self.params_std
        stats = (stats - self.stats_mean) / self.stats_std

        return params, stats

    def gen_newseed(self):
        """Generates a new random seed"""
        if self.seed is None:
            return None
        else:
            return self.rng.randint(0, 2**31)

    def pilot_run(self, n_samples):
        """Pilot run in order to find parameters for z-scoring stats
        """
        verbose = '(pilot run) ' if self.verbose else False
        params, stats = self.generator.gen(n_samples, verbose=verbose)
        self.stats_mean = np.nanmean(stats, axis=0)
        self.stats_std = np.nanstd(stats, axis=0)

    def predict(self, x, deterministic=True):
        """Predict posterior given x

        Parameters
        ----------
        x : array
            Stats for which to compute the posterior
        deterministic : bool
            if True, mean weights are used for Bayesian network
        """
        x_zt = (x - self.stats_mean) / self.stats_std
        posterior = self.network.get_mog(x_zt, deterministic=deterministic)
        return posterior.ztrans_inv(self.params_mean, self.params_std)

    def compile_observables(self):
        """Creates observables dict"""
        self.observables = {}
        self.observables['loss.lprobs'] = self.network.lprobs
        for p in self.network.aps:
            self.observables[str(p)] = p

    def monitor_dict_from_names(self, monitor=None):
        """Generate monitor dict from list of variable names"""
        if monitor is not None:
            observe = {}
            if isinstance(monitor, str):
                monitor = [monitor]
            for m in monitor:
                if m in self.observables:
                    observe[m] = self.observables[m]
        else:
            observe = None
        return observe
class BaseInference(metaclass=ABCMetaDoc):
    def __init__(self, generator,
                 prior_norm=True, init_norm=False,
                 pilot_samples=100,
                 seed=None, verbose=True, **kwargs):
        """Abstract base class for inference algorithms

        Inference algorithms must at least implement abstract methods of this
        class.

        Parameters
        ----------
        generator : generator instance
            Generator instance
        prior_norm : bool
            If set to True, will z-transform params based on mean/std of prior
        pilot_samples : None or int or tuple
            If an integer is provided, a pilot run with the given number of
            samples is run. The mean and std of the summary statistics of the
            pilot samples will be subsequently used to z-transform summary
            statistics.
            If an tuple of the form (params, stats) is provided, these will be
            used directly as samples from the prior.
        seed : int or None
            If provided, random number generator will be seeded
        kwargs : additional keyword arguments
            Additional arguments used when creating the NeuralNet instance

        Attributes
        ----------
        observables : dict
            Dictionary containing theano variables that can be monitored while
            training the neural network.
        """
        self.seed = seed
        if seed is not None:
            self.rng = np.random.RandomState(seed=seed)
        else:
            self.rng = np.random.RandomState()
        self.verbose = verbose

        # bind generator, reset proposal attribute
        self.generator = generator
        self.generator.proposal = None

        # get input and output dimensions
        if type(pilot_samples) is tuple:
            params, stats = pilot_samples[0], pilot_samples[1]
        else:
            params, stats = generator.gen(1, skip_feedback=True, verbose=False)
        assert stats.ndim == 2, "invalid summary stats"
        kwargs.update({'n_inputs': stats.shape[1],
                       'n_outputs': params.shape[1],
                       'seed': self.gen_newseed()})

        self.kwargs = kwargs

        # optional: z-transform output for obs (also re-centres x onto obs!)
        self.init_norm = init_norm
        if 'n_components' in kwargs.keys() and kwargs['n_components'] > 1:
            self.init_fcv = 0.8
        else:
            self.init_fcv = 0.0

        # parameters for z-transform of params
        if prior_norm:
            # z-transform for params based on prior
            self.params_mean = self.generator.prior.mean
            self.params_std = self.generator.prior.std
            assert not np.any(np.isnan(self.params_mean)) and \
                not np.any(np.isnan(self.params_std)) and \
                self.params_mean is not None and \
                self.params_std is not None
        else:
            # parameters are set such that z-transform has no effect
            self.params_mean = np.zeros((params.shape[1],))
            self.params_std = np.ones((params.shape[1],))

        self.pilot_run(pilot_samples, stats.shape[1])

        self.reinit_network()  # init network, then update self.kwargs['seed']

        # observables contains vars that can be monitored during training
        self.compile_observables()
        self.round = 0

    @abc.abstractmethod
    def loss(self):
        pass

    @abc.abstractmethod
    def run(self):
        pass

    def run_repeated(self, n_repeats=10, n_NN_inits_per_repeat=1,
                     callback=None, **kwargs):
        """Repeatedly run the method and collect results. Optionally, carry out
        several runs with the same initial generator RNG state but different
        neural network initializations.

        parameters
        ----------
        n_repeats : int
            Number of times to run the algorithm
        n_NN_inits : int
            Number of times to
        callback: function
            callback function that will be called after each run. It should
            take 4 inputs: callback(log, train_data, posterior, self)
        kwargs : additional keyword arguments
            Additional arguments that will be passed to the run() method
        """
        posteriors, outputs, repeat_index = [], [], []
        for r in range(n_repeats):

            if n_NN_inits_per_repeat > 1:
                generator_seed = self.gen_newseed()

            for i in range(n_NN_inits_per_repeat):

                self.reset()
                if n_NN_inits_per_repeat > 1:
                    self.generator.reseed(generator_seed)

                log, train_data, posterior = self.run(**kwargs)

                if callback is not None:
                    outputs.append(callback(log, train_data, posterior, self))
                else:
                    outputs.append(None)
                posteriors.append(posterior)
                repeat_index.append(r)

        return posteriors, outputs, repeat_index

    def reinit_network(self):
        """Reinitializes the network instance (re-setting the weights!)
        """
        self.network = NeuralNet(**self.kwargs)
        self.svi = self.network.svi if 'svi' in dir(self.network) else False
        """update self.kwargs['seed'] so that reinitializing the network gives a
        different result each time unless we reseed the inference method"""
        self.kwargs['seed'] = self.gen_newseed()
        self.norm_init()

    def centre_on_obs(self):
        """ Centres first-layer input onto observed summary statistics

        Ensures x' = x - xo, i.e. first-layer input x' = 0 for x = xo.
        """

        self.stats_mean = self.obs.copy()

    def remove_hidden_biases(self):
        """ Resets all bias weights in hidden layers to zero.

        """
        def idx_hiddens(x):
            return x.name[0] == 'h'

        for b in filter(idx_hiddens, self.network.mps_bp):
            b.set_value(np.zeros_like(b.get_value()))

    def conditional_norm(self, fcv=0.8, tmu=None, tSig=None, h=None):
        """ Normalizes current network output at observed summary statistics


        Parameters
        ----------
        fcv : float
            Fraction of total that comes from uncertainty over components, i.e.
            Var[th] = E[Var[th|z]] + Var[E[th|z]]
                    =  (1-fcv)     +     fcv       = 1
        tmu: array
            Target mean.
        tSig: array
            Target covariance.

        """

        # avoid CDELFI.predict() attempt to analytically correct for proposal
        print('obs', self.obs.shape)
        print('mean', self.stats_mean.shape)
        print('std', self.stats_std.shape)
        obz = (self.obs - self.stats_mean) / self.stats_std
        posterior = self.network.get_mog(obz.reshape(self.obs.shape),
                                         deterministic=True)
        mog = posterior.ztrans_inv(self.params_mean, self.params_std)

        assert np.all(np.diff(mog.a)==0.) # assumes uniform alpha

        n_dim = self.kwargs['n_outputs']
        triu_mask = np.triu(np.ones([n_dim, n_dim], dtype=dtype), 1)
        diag_mask = np.eye(n_dim, dtype=dtype)

        # compute MoG mean mu, Sig = E[Var[th|z]] and C = Var[E[th|z]]
        mu, Sig = np.zeros_like(mog.xs[0].m), np.zeros_like(mog.xs[0].S)
        for i in range(self.network.n_components):
            Sig += mog.a[i] * mog.xs[i].S
            mu  += mog.a[i] * mog.xs[i].m
        C = np.zeros_like(Sig)
        for i in range(self.network.n_components):
            dmu = mog.xs[i].m - mu if self.network.n_components > 1 \
                else mog.xs[i].m
            C += mog.a[i] * np.outer(dmu, dmu)

        # if not provided, target zero-mean unit variance (as for prior_norm=True)
        tmu = np.zeros_like(mog.xs[0].m) if tmu is None else tmu
        tSig = np.eye(mog.xs[0].m.size) if tSig is None else tSig

        # compute normalizers (we only z-score, don't whiten!)
        Z1inv = np.sqrt((1.-fcv) / np.diag(Sig) * np.diag(tSig)).reshape(-1)
        Z2inv = np.sqrt(  fcv    / np.diag( C ) * np.diag(tSig)).reshape(-1)

        # first we need the center of means
        def idx_MoG(x):
            return x.name[:5]=='means'
        mu_ = np.zeros_like(mog.xs[0].m)
        for w, b in zip(filter(idx_MoG, self.network.mps_wp),
                        filter(idx_MoG, self.network.mps_bp)):
            h = np.zeros(w.get_value().shape[0]) if h is None else h
            mu_ += h.dot(w.get_value()) + b.get_value()
        mu_ /= self.network.n_components

        # center and normalize means
        # mu =  Z2inv * (Wh + b - mu_) + tmu
        #    = Wh + (Z2inv * (b - mu_ + Wh) - Wh + tum)
        for w, b in zip(filter(idx_MoG, self.network.mps_wp),
                        filter(idx_MoG, self.network.mps_bp)):
            Wh = h.dot(w.get_value())
            b.set_value(Z2inv * (Wh + b.get_value() - mu_) - Wh + tmu)

        # normalize covariances
        def idx_MoG(x):
            return x.name[:10]=='precisions'
        # Sig^-0.5 = diag_mask * (exp(Wh+b)/exp(log(Z1)) + triu_mask * (Wh+b)*Z1
        #          = diag_mask *  exp(Wh+ (b-log(Z1))    + triu_mask * (Wh+((b+Wh)*Z1-Wh))
        for w, b in zip(filter(idx_MoG, self.network.mps_wp),
                        filter(idx_MoG, self.network.mps_bp)):
            Wh = h.dot(w.get_value()).reshape(n_dim,n_dim)
            b_ = b.get_value().copy().reshape(n_dim,n_dim)

            val = diag_mask * (b_ - np.diag(np.log(Z1inv))) + triu_mask * ((b_+Wh).dot(np.diag(1./Z1inv))- Wh )

            b.set_value(val.flatten())


    def norm_init(self):
        if self.init_norm and self.network.density == 'mog':
            print('standardizing network initialization')
            if self.network.n_components > 1:
                self.standardize_init(fcv = self.init_fcv)
            else:
                self.standardize_init(fcv = 0.)

    def standardize_init(self, fcv = 0.8):
        """ Standardizes the network initialization on obs

        Ensures output distributions for xo have mean zero and unit variance.
        Alters hidden layers to propagates x=xo as zero to the last layer, and
        alters the MoG layers to produce the desired output distribution.
        """
        assert isinstance(self.network, NeuralNet)

        # ensure x' = x - xo
        self.centre_on_obs()

        # ensure x' = 0 stays zero up to MoG layer (setting biases to zero)
        self.remove_hidden_biases()

        # ensure MoG returns standardized output on x' = 0
        self.conditional_norm(fcv)

    def gen(self, n_samples, n_reps=1, prior_mixin=0, verbose=None):
        """Generate from generator and z-transform

        Parameters
        ----------
        n_samples : int
            Number of samples to generate
        n_reps : int
            Number of repeats per parameter
        verbose : None or bool or str
            If None is passed, will default to self.verbose
        """
        assert n_reps == 1, 'n_reps > 1 is not yet supported'
        verbose = self.verbose if verbose is None else verbose

        n_pilot = np.minimum(n_samples, len(self.unused_pilot_samples[0]))
        if n_pilot > 0 and self.generator.proposal is self.generator.prior:  # reuse pilot samples
            params = self.unused_pilot_samples[0][:n_pilot, :]
            stats = self.unused_pilot_samples[1][:n_pilot, :]
            self.unused_pilot_samples = \
                (self.unused_pilot_samples[0][n_pilot:, :],
                 self.unused_pilot_samples[1][n_pilot:, :])
            n_samples -= n_pilot

            if n_samples > 0:
                params_rem, stats_rem = self.generator.gen(n_samples,
                                                           prior_mixin=prior_mixin,
                                                           verbose=verbose)
                params = np.concatenate((params, params_rem), axis=0)
                stats = np.concatenate((stats, stats_rem), axis=0)
        else:
            params, stats = self.generator.gen(n_samples,
                                               prior_mixin=prior_mixin,
                                               verbose=verbose)

        # z-transform params and stats
        params = (params - self.params_mean) / self.params_std
        stats = (stats - self.stats_mean) / self.stats_std

        return params, stats

    def reset(self, seed=None):
        """Resets inference method to a naive state, before it has seen any
        real or simulated data. The following happens, in order:
        1) The generator's proposal is set to None, and self.round is set to 0
        2) The inference method is reseeded if a seed is provided
        3) The network is reinitialized
        4) Any additional resetting of state specific to each inference method
        """
        self.generator.proposal = None
        self.round = 0
        if seed is not None:
            self.reseed(seed)
        self.reinit_network()

    def reseed(self, seed):
        """reseed inference method's RNG, then generator, then network"""
        self.rng.seed(seed=seed)
        self.seed = seed
        self.kwargs['seed'] = self.gen_newseed()   # for consistent NN init
        self.generator.reseed(self.gen_newseed())  # also reseeds prior + model
        if isinstance(self.network, NeuralNet):
            self.network.reseed(self.gen_newseed())  # for reproducible samples
        # unfortunately, MAFs cannot currently be (re)seeded

    def gen_newseed(self):
        """Generates a new random seed"""
        if self.seed is None:
            return None
        else:
            return self.rng.randint(0, 2**31)

    def pilot_run(self, pilot_samples, n_stats, min_std=1e-4):
        """Pilot run in order to find parameters for z-scoring stats"""
        if pilot_samples is None or \
                (isint(pilot_samples) and pilot_samples == 0):
            self.unused_pilot_samples = ([], [])
            self.stats_mean = np.zeros(n_stats)
            self.stats_std = np.ones(n_stats)
            return

        if isint(pilot_samples):  # determine via pilot run
            assert pilot_samples > 0
            if self.seed is not None:  # reseed generator for consistent inits
                self.generator.reseed(self.gen_newseed())
            verbose = '(pilot run) ' if self.verbose else False
            params, stats = self.generator.gen(pilot_samples, verbose=verbose)
        else:  # samples were provided as an input
            params, stats = pilot_samples

        self.stats_mean = np.nanmean(stats, axis=0)
        self.stats_std = np.nanstd(stats, axis=0)
        assert not np.isnan(self.stats_mean).any(), "pilot run failed"
        assert not np.isnan(self.stats_std).any(), "pilot run failed"
        self.stats_std[self.stats_std == 0.0] = 1.0
        self.stats_std = np.maximum(self.stats_std, min_std)
        assert (self.stats_std > 0).all(), "pilot run failed"
        ok_sims = np.logical_not(np.logical_or(np.isnan(stats).any(axis=1),
                                               np.isnan(params).any(axis=1)))
        self.unused_pilot_samples = (params[ok_sims, :], stats[ok_sims, :])

    def predict(self, x, deterministic=True):
        """Predict posterior given x

        Parameters
        ----------
        x : array
            Stats for which to compute the posterior
        deterministic : bool
            if True, mean weights are used for Bayesian network
        """
        assert isinstance(self.network, NeuralNet)
        # z-transform inputs
        x_zt = (x - self.stats_mean) / self.stats_std

        posterior = self.network.get_density(x_zt, deterministic=deterministic)

        # z-transform outputs
        if self.network.density == 'mog':
            posterior = posterior.ztrans_inv(self.params_mean, self.params_std)
        elif self.network.density == 'maf':
            posterior.set_scale_and_offset(scale=self.params_std,
                                           offset=self.params_mean)
        else:
            assert np.all(self.params_std == 1.0) and \
                   np.all(self.params_mean == 0.0)

        return posterior

    def compile_observables(self):
        """Creates observables dict"""
        self.observables = {}
        self.observables['loss.lprobs'] = self.network.lprobs
        for p in self.network.aps:
            self.observables[str(p)] = p

    def monitor_dict_from_names(self, monitor=None):
        """Generate monitor dict from list of variable names"""
        if monitor is not None:
            observe = {}
            if isinstance(monitor, str):
                monitor = [monitor]
            for m in monitor:
                if m in self.observables:
                    observe[m] = self.observables[m]
        else:
            observe = None
        return observe
Exemple #12
0
    def run(self,
            n_train=100,
            n_rounds=2,
            epochs=100,
            minibatch=50,
            monitor=None,
            **kwargs):
        """Run algorithm

        Parameters
        ----------
        n_train : int or list of ints
            Number of data points drawn per round. If a list is passed, the
            nth list element specifies the number of training examples in the
            nth round. If there are fewer list elements than rounds, the last
            list element is used.
        n_rounds : int
            Number of rounds
        epochs: int
            Number of epochs used for neural network training
        minibatch: int
            Size of the minibatches used for neural network training
        monitor : list of str
            Names of variables to record during training along with the value
            of the loss function. The observables attribute contains all
            possible variables that can be monitored
        kwargs : additional keyword arguments
            Additional arguments for the Trainer instance

        Returns
        -------
        logs : list of dicts
            Dictionaries contain information logged while training the networks
        trn_datasets : list of (params, stats)
            training datasets, z-transformed
        posteriors : list of posteriors
            posterior after each round
        """
        logs = []
        trn_datasets = []
        posteriors = []

        for r in range(1, n_rounds + 1):  # start at 1
            # if round > 1, set new proposal distribution before sampling
            if r > 1:
                # posterior becomes new proposal prior
                posterior = self.predict(self.obs)
                self.generator.proposal = posterior.project_to_gaussian()

            # number of training examples for this round
            if type(n_train) == list:
                try:
                    n_train_round = n_train[r - 1]
                except:
                    n_train_round = n_train[-1]
            else:
                n_train_round = n_train

            # draw training data (z-transformed params and stats)
            verbose = '(round {}) '.format(r) if self.verbose else False
            trn_data = self.gen(n_train_round, verbose=verbose)

            # algorithm 2 of Papamakarios and Murray
            if r == n_rounds and self.n_components > 1:
                # get parameters of current network
                old_params = self.network.params_dict.copy()

                # create new network
                network_spec = self.network.spec_dict.copy()
                network_spec.update({'n_components': self.n_components})
                self.network = NeuralNet(**network_spec)
                new_params = self.network.params_dict

                # set weights of new network
                # weights of additional components are duplicates
                for p in [
                        s for s in new_params
                        if 'means' in s or 'precisions' in s
                ]:
                    new_params[p] = old_params[p[:-1] + '0']
                    new_params[p] += 1.0e-6 * self.rng.randn(
                        *new_params[p].shape)

                self.network.params_dict = new_params

            trn_inputs = [self.network.params, self.network.stats]

            t = Trainer(self.network,
                        self.loss(N=n_train_round),
                        trn_data=trn_data,
                        trn_inputs=trn_inputs,
                        monitor=self.monitor_dict_from_names(monitor),
                        seed=self.gen_newseed(),
                        **kwargs)
            logs.append(
                t.train(epochs=epochs, minibatch=minibatch, verbose=verbose))
            trn_datasets.append(trn_data)

            posteriors.append(self.predict(self.obs))

        return logs, trn_datasets, posteriors