Exemple #1
0
def _triu(m, k=0):
    """ returns the elements on and above the k-th diagonal of m.  k=0 is the
        main diagonal, k > 0 is above and k < 0 is below the main diagonal."""
    N = m.shape[0]
    M = m.shape[1]
    x = numx.greater_equal(numx.subtract.outer(numx.arange(N), numx.arange(M)), 1 - k)
    out = (1 - x) * m
    return out
Exemple #2
0
def _triu(m, k=0):
    """ returns the elements on and above the k-th diagonal of m.  k=0 is the
        main diagonal, k > 0 is above and k < 0 is below the main diagonal."""
    N = m.shape[0]
    M = m.shape[1]
    x = numx.greater_equal(numx.subtract.outer(numx.arange(N),
                                               numx.arange(M)),1-k)
    out = (1-x)*m
    return out
Exemple #3
0
    def _execute(self, x):
        #----------------------------------------------------
        # similar algorithm to that within self.stop_training()
        #  refer there for notes & comments on code
        #----------------------------------------------------
        N = self.data.shape[0]
        Nx = x.shape[0]
        W = numx.zeros((Nx, N), dtype=self.dtype)

        k, r = self.k, self.r
        d_out = self.output_dim
        Q_diag_idx = numx.arange(k)

        for row in range(Nx):
            #find nearest neighbors of x in M
            M_xi = self.data-x[row]
            nbrs = numx.argsort( (M_xi**2).sum(1) )[:k]
            M_xi = M_xi[nbrs]

            #find corrected covariance matrix Q
            Q = mult(M_xi, M_xi.T)
            if r is None and k > d_out:
                sig2 = (svd(M_xi, compute_uv=0))**2
                r = numx.sum(sig2[d_out:])
                Q[Q_diag_idx, Q_diag_idx] += r
            if r is not None:
                Q[Q_diag_idx, Q_diag_idx] += r

            #solve for weights
            w = self._refcast(numx_linalg.solve(Q , numx.ones(k)))
            w /= w.sum()
            W[row, nbrs] = w

        #multiply weights by result of SVD from training
        return numx.dot(W, self.training_projection)
Exemple #4
0
    def _execute(self, x):
        #----------------------------------------------------
        # similar algorithm to that within self.stop_training()
        #  refer there for notes & comments on code
        #----------------------------------------------------
        N = self.data.shape[0]
        Nx = x.shape[0]
        W = numx.zeros((Nx, N), dtype=self.dtype)

        k, r = self.k, self.r
        d_out = self.output_dim
        Q_diag_idx = numx.arange(k)

        for row in range(Nx):
            #find nearest neighbors of x in M
            M_xi = self.data - x[row]
            nbrs = numx.argsort((M_xi**2).sum(1))[:k]
            M_xi = M_xi[nbrs]

            #find corrected covariance matrix Q
            Q = mult(M_xi, M_xi.T)
            if r is None and k > d_out:
                sig2 = (svd(M_xi, compute_uv=0))**2
                r = numx.sum(sig2[d_out:])
                Q[Q_diag_idx, Q_diag_idx] += r
            if r is not None:
                Q[Q_diag_idx, Q_diag_idx] += r

            #solve for weights
            w = self._refcast(numx_linalg.solve(Q, numx.ones(k)))
            w /= w.sum()
            W[row, nbrs] = w

        #multiply weights by result of SVD from training
        return numx.dot(W, self.training_projection)
Exemple #5
0
def _triu(m, k=0):
    """Reduces a matrix to a triangular part.

    :param m: A Matrix.
    :param k: Index of diagonal.
    :type k: int
    
    :return: Elements on and above the k-th diagonal of m.  k=0 is the
        main diagonal, k > 0 is above and k < 0 is below the main diagonal.
    """

    N = m.shape[0]
    M = m.shape[1]
    x = numx.greater_equal(numx.subtract.outer(numx.arange(N),
                                               numx.arange(M)),1-k)
    out = (1-x)*m
    return out
 def _train(self, x):
     hit = self.hit
     old_tlen = self.tlen
     if hit is None:
         hit = [OneDimensionalHitParade(self.n, self.d, self.dtype,
                                        self.itype)
                for c in range(self.input_dim)]
     tlen = old_tlen + x.shape[0]
     indices = numx.arange(old_tlen, tlen)
     for c in range(self.input_dim):
         hit[c].update((x[:, c], indices))
     self.hit = hit
     self.tlen = tlen
Exemple #7
0
 def _train(self, x):
     hit = self.hit
     old_tlen = self.tlen
     if hit is None:
         hit = [OneDimensionalHitParade(self.n, self.d, self.dtype,
                                        self.itype)
                for c in range(self.input_dim)]
     tlen = old_tlen + x.shape[0]
     indices = numx.arange(old_tlen, tlen)
     for c in range(self.input_dim):
         hit[c].update((x[:, c], indices))
     self.hit = hit
     self.tlen = tlen
Exemple #8
0
def switching_signals(f1, f2, T, n_switches, n_samples=1):
    samples = []
    # seconds per simulation timestep
    t = numx.arange(T)
    proto_1 = numx.atleast_2d(numx.sin(2 * numx.pi * t * f1)).T
    proto_2 = numx.atleast_2d(numx.sin(2 * numx.pi * t * f2)).T

    for _ in range(n_samples):
        n_periods1 = numx.random.randint(4, 8, size=(n_switches))
        n_periods2 = numx.random.randint(4, 8, size=(n_switches))

        #n_periods1, n_periods2 = [1], [0]

        switch = []
        signal = []
        for p1, p2 in zip(n_periods1, n_periods2):
            switch.extend([numx.ones_like(proto_1)] * p1)
            switch.extend([-1 * numx.ones_like(proto_2)] * p2)
            signal.extend([proto_1] * p1)
            signal.extend([proto_2] * p2)

        samples.append([numx.concatenate((numx.concatenate(switch), numx.concatenate(signal)), 1)])
    return samples
Exemple #9
0
def switching_signals(f1, f2, T, n_switches, n_samples=1):
    samples = []
    # seconds per simulation timestep
    t = numx.arange(T)
    proto_1 = numx.atleast_2d(numx.sin(2 * numx.pi * t * f1)).T
    proto_2 = numx.atleast_2d(numx.sin(2 * numx.pi * t * f2)).T

    for _ in range(n_samples):
        n_periods1 = numx.random.randint(4, 8, size=(n_switches))
        n_periods2 = numx.random.randint(4, 8, size=(n_switches))

        # n_periods1, n_periods2 = [1], [0]

        switch = []
        signal = []
        for p1, p2 in zip(n_periods1, n_periods2):
            switch.extend([numx.ones_like(proto_1)] * p1)
            switch.extend([-1 * numx.ones_like(proto_2)] * p2)
            signal.extend([proto_1] * p1)
            signal.extend([proto_2] * p2)

        samples.append([numx.concatenate((numx.concatenate(switch), numx.concatenate(signal)), 1)])
    return samples
Exemple #10
0
def _find_blank_data_idx(B, rank_threshold):
    """
    Helper for some of the rank_deficit solvers.
    Some numerical decompositions, e.g. eig, svd, ldl appear to
    yield numerically unstable results, if the input matrix contains
    blank lines and columns (assuming symmetry).
    It is relevant to guard this case, because it corresponds to constants
    in the data. Think of a constantly white corner in training images or
    slight black stripe atop of a training video due to insufficient cropping
    or think of a logo or timestamp in a video. There are plenty of examples
    that cause constants in real-world data. So by checking for this kind of
    issue we release some burden of inconvenient preprocessing from the user.
    """
    zero_idx = (abs(B[0]) < rank_threshold).nonzero()[0]
    # For efficiency we just check the first line for zeros and fail fast.
    if len(zero_idx) > 0:
        # If near-zero entries are in first line we check the whole columns:
        #nonzerolines = (abs(numx.sum(B, 0)) > rank_threshold).nonzero()[0]
        zero_idx = (numx.mean(abs(B[zero_idx]), 0) < \
                rank_threshold).nonzero()[0]
        if len(zero_idx) > 0:
            nonzero_idx = numx.arange(len(B))
            nonzero_idx[zero_idx] = -1
            return nonzero_idx[(nonzero_idx != -1).nonzero()[0]]
def _find_blank_data_idx(B, rank_threshold):
    """
    Helper for some of the rank_deficit solvers.
    Some numerical decompositions, e.g. eig, svd, ldl appear to
    yield numerically unstable results, if the input matrix contains
    blank lines and columns (assuming symmetry).
    It is relevant to guard this case, because it corresponds to constants
    in the data. Think of a constantly white corner in training images or
    slight black stripe atop of a training video due to insufficient cropping
    or think of a logo or timestamp in a video. There are plenty of examples
    that cause constants in real-world data. So by checking for this kind of
    issue we release some burden of inconvenient preprocessing from the user.
    """
    zero_idx = (abs(B[0]) < rank_threshold).nonzero()[0]
    # For efficiency we just check the first line for zeros and fail fast.
    if len(zero_idx) > 0:
        # If near-zero entries are in first line we check the whole columns:
        #nonzerolines = (abs(numx.sum(B, 0)) > rank_threshold).nonzero()[0]
        zero_idx = (numx.mean(abs(B[zero_idx]), 0) < \
                rank_threshold).nonzero()[0]
        if len(zero_idx) > 0:
            nonzero_idx = numx.arange(len(B))
            nonzero_idx[zero_idx] = -1
            return nonzero_idx[(nonzero_idx != -1).nonzero()[0]]
Exemple #12
0
    def _stop_training(self):
        Cumulator._stop_training(self)

        k = self.k
        M = self.data
        N = M.shape[0]

        if k > N:
            err = ('k=%i must be less than'
                   ' or equal to number of training points N=%i' % (k, N))
            raise TrainingException(err)

        if self.verbose:
            print 'performing HLLE on %i points in %i dimensions...' % M.shape

        # determines number of output dimensions: if desired_variance
        # is specified, we need to learn it from the data. Otherwise,
        # it's easy
        learn_outdim = False
        if self.output_dim is None:
            if self.desired_variance is None:
                self.output_dim = self.input_dim
            else:
                learn_outdim = True

        # determine number of output dims, precalculate useful stuff
        if learn_outdim:
            Qs, sig2s, nbrss = self._adjust_output_dim()

        d_out = self.output_dim

        #dp = d_out + (d_out-1) + (d_out-2) + ...
        dp = d_out*(d_out+1)/2

        if min(k, N) <= d_out:
            err = ('k=%i and n=%i (number of input data points) must be'
                   ' larger than output_dim=%i' % (k, N, d_out))
            raise TrainingException(err)

        if k < 1+d_out+dp:
            wrn = ('The number of neighbours, k=%i, is smaller than'
                   ' 1 + output_dim + output_dim*(output_dim+1)/2 = %i,'
                   ' which might result in unstable results.'
                   % (k, 1+d_out+dp))
            _warnings.warn(wrn, MDPWarning)

        #build the weight matrix
        #XXX   for faster implementation, W should be a sparse matrix
        W = numx.zeros((N, dp*N), dtype=self.dtype)

        if self.verbose:
            print ' - constructing [%i x %i] weight matrix...' % W.shape

        for row in range(N):
            if learn_outdim:
                nbrs = nbrss[row, :]
            else:
                # -----------------------------------------------
                #  find k nearest neighbors
                # -----------------------------------------------
                M_Mi = M-M[row]
                nbrs = numx.argsort((M_Mi**2).sum(1))[1:k+1]

            #-----------------------------------------------
            #  center the neighborhood using the mean
            #-----------------------------------------------
            nbrhd = M[nbrs] # this makes a copy
            nbrhd -= nbrhd.mean(0)

            #-----------------------------------------------
            #  compute local coordinates
            #   using a singular value decomposition
            #-----------------------------------------------
            U, sig, VT = svd(nbrhd)
            nbrhd = U.T[:d_out]
            del VT

            #-----------------------------------------------
            #  build Hessian estimator
            #-----------------------------------------------
            Yi = numx.zeros((dp, k), dtype=self.dtype)
            ct = 0
            for i in range(d_out):
                Yi[ct:ct+d_out-i, :] = nbrhd[i] * nbrhd[i:, :]
                ct += d_out-i
            Yi = numx.concatenate([numx.ones((1, k), dtype=self.dtype),
                                   nbrhd, Yi], 0)

            #-----------------------------------------------
            #  orthogonalize linear and quadratic forms
            #   with QR factorization
            #  and make the weights sum to 1
            #-----------------------------------------------
            if k >= 1+d_out+dp:
                Q, R = numx_linalg.qr(Yi.T)
                w = Q[:, d_out+1:d_out+1+dp]
            else:
                q, r = _mgs(Yi.T)
                w = q[:, -dp:]

            S = w.sum(0) #sum along columns
            #if S[i] is too small, set it equal to 1.0
            # this prevents weights from blowing up
            S[numx.where(numx.absolute(S)<1E-4)] = 1.0
            #print w.shape, S.shape, (w/S).shape
            #print W[nbrs, row*dp:(row+1)*dp].shape
            W[nbrs, row*dp:(row+1)*dp] = w / S

        #-----------------------------------------------
        # To find the null space, we want the
        #  first d+1 eigenvectors of W.T*W
        # Compute this using an svd of W
        #-----------------------------------------------

        if self.verbose:
            msg = (' - finding [%i x %i] '
                   'null space of weight matrix...' % (d_out, N))
            print msg

        #XXX future work:
        #XXX  use of upcoming ARPACK interface for bottom few eigenvectors
        #XXX   of a sparse matrix will significantly increase the speed
        #XXX   of the next step

        if self.svd:
            sig, U = nongeneral_svd(W.T, range=(2, d_out+1))
            Y = U*numx.sqrt(N)
        else:
            WW = mult(W, W.T)
            # regularizes the eigenvalues, does not change the eigenvectors:
            W_diag_idx = numx.arange(N)
            WW[W_diag_idx, W_diag_idx] += 0.01
            sig, U = symeig(WW, range=(2, self.output_dim+1), overwrite=True)
            Y = U*numx.sqrt(N)
            del WW
        del W

        #-----------------------------------------------
        # Normalize Y
        #
        # Alternative way to do it:
        #  we need R = (Y.T*Y)^(-1/2)
        #   do this with an SVD of Y            del VT

        #      Y = U*sig*V.T
        #      Y.T*Y = (V*sig.T*U.T) * (U*sig*V.T)
        #            = V * (sig*sig.T) * V.T
        #            = V * sig^2 V.T
        #   so
        #      R = V * sig^-1 * V.T
        # The code is:
        #    U, sig, VT = svd(Y)
        #    del U
        #    S = numx.diag(sig**-1)
        #    self.training_projection = mult(Y, mult(VT.T, mult(S, VT)))
        #-----------------------------------------------
        if self.verbose:
            print ' - normalizing null space...'

        C = sqrtm(mult(Y.T, Y))
        self.training_projection = mult(Y, C)
Exemple #13
0
    def _stop_training(self):
        Cumulator._stop_training(self)

        if self.verbose:
            msg = ('training LLE on %i points'
                   ' in %i dimensions...' %
                   (self.data.shape[0], self.data.shape[1]))
            print msg

        # some useful quantities
        M = self.data
        N = M.shape[0]
        k = self.k
        r = self.r

        # indices of diagonal elements
        W_diag_idx = numx.arange(N)
        Q_diag_idx = numx.arange(k)

        if k > N:
            err = ('k=%i must be less than or '
                   'equal to number of training points N=%i' % (k, N))
            raise TrainingException(err)

        # determines number of output dimensions: if desired_variance
        # is specified, we need to learn it from the data. Otherwise,
        # it's easy
        learn_outdim = False
        if self.output_dim is None:
            if self.desired_variance is None:
                self.output_dim = self.input_dim
            else:
                learn_outdim = True

        # do we need to automatically determine the regularization term?
        auto_reg = r is None

        # determine number of output dims, precalculate useful stuff
        if learn_outdim:
            Qs, sig2s, nbrss = self._adjust_output_dim()

        # build the weight matrix
        #XXX future work:
        #XXX   for faster implementation, W should be a sparse matrix
        W = numx.zeros((N, N), dtype=self.dtype)

        if self.verbose:
            print ' - constructing [%i x %i] weight matrix...' % W.shape

        for row in range(N):
            if learn_outdim:
                Q = Qs[row, :, :]
                nbrs = nbrss[row, :]
            else:
                # -----------------------------------------------
                #  find k nearest neighbors
                # -----------------------------------------------
                M_Mi = M - M[row]
                nbrs = numx.argsort((M_Mi**2).sum(1))[1:k + 1]
                M_Mi = M_Mi[nbrs]
                # compute covariance matrix of distances
                Q = mult(M_Mi, M_Mi.T)

            # -----------------------------------------------
            #  compute weight vector based on neighbors
            # -----------------------------------------------

            #Covariance matrix may be nearly singular:
            # add a diagonal correction to prevent numerical errors
            if auto_reg:
                # automatic mode: correction is equal to the sum of
                # the (d_in-d_out) unused variances (as in deRidder &
                # Duin)
                if learn_outdim:
                    sig2 = sig2s[row, :]
                else:
                    sig2 = svd(M_Mi, compute_uv=0)**2
                r = numx.sum(sig2[self.output_dim:])
                Q[Q_diag_idx, Q_diag_idx] += r
            else:
                # Roweis et al instead use "a correction that
                #   is small compared to the trace" e.g.:
                # r = 0.001 * float(Q.trace())
                # this is equivalent to assuming 0.1% of the variance is unused
                Q[Q_diag_idx, Q_diag_idx] += r * Q.trace()

            #solve for weight
            # weight is w such that sum(Q_ij * w_j) = 1 for all i
            # XXX refcast is due to numpy bug: floats become double
            w = self._refcast(numx_linalg.solve(Q, numx.ones(k)))
            w /= w.sum()

            #update row of the weight matrix
            W[nbrs, row] = w

        if self.verbose:
            msg = (' - finding [%i x %i] null space of weight matrix\n'
                   '     (may take a while)...' % (self.output_dim, N))
            print msg

        self.W = W.copy()
        #to find the null space, we need the bottom d+1
        #  eigenvectors of (W-I).T*(W-I)
        #Compute this using the svd of (W-I):
        W[W_diag_idx, W_diag_idx] -= 1.

        #XXX future work:
        #XXX  use of upcoming ARPACK interface for bottom few eigenvectors
        #XXX   of a sparse matrix will significantly increase the speed
        #XXX   of the next step
        if self.svd:
            sig, U = nongeneral_svd(W.T, range=(2, self.output_dim + 1))
        else:
            # the following code does the same computation, but uses
            # symeig, which computes only the required eigenvectors, and
            # is much faster. However, it could also be more unstable...
            WW = mult(W, W.T)
            # regularizes the eigenvalues, does not change the eigenvectors:
            WW[W_diag_idx, W_diag_idx] += 0.1
            sig, U = symeig(WW, range=(2, self.output_dim + 1), overwrite=True)

        self.training_projection = U
Exemple #14
0
    def _stop_training(self):
        Cumulator._stop_training(self)

        k = self.k
        M = self.data
        N = M.shape[0]

        if k > N:
            err = ('k=%i must be less than'
                   ' or equal to number of training points N=%i' % (k, N))
            raise TrainingException(err)

        if self.verbose:
            print 'performing HLLE on %i points in %i dimensions...' % M.shape

        # determines number of output dimensions: if desired_variance
        # is specified, we need to learn it from the data. Otherwise,
        # it's easy
        learn_outdim = False
        if self.output_dim is None:
            if self.desired_variance is None:
                self.output_dim = self.input_dim
            else:
                learn_outdim = True

        # determine number of output dims, precalculate useful stuff
        if learn_outdim:
            Qs, sig2s, nbrss = self._adjust_output_dim()

        d_out = self.output_dim

        #dp = d_out + (d_out-1) + (d_out-2) + ...
        dp = d_out * (d_out + 1) / 2

        if min(k, N) <= d_out:
            err = ('k=%i and n=%i (number of input data points) must be'
                   ' larger than output_dim=%i' % (k, N, d_out))
            raise TrainingException(err)

        if k < 1 + d_out + dp:
            wrn = ('The number of neighbours, k=%i, is smaller than'
                   ' 1 + output_dim + output_dim*(output_dim+1)/2 = %i,'
                   ' which might result in unstable results.' %
                   (k, 1 + d_out + dp))
            _warnings.warn(wrn, MDPWarning)

        #build the weight matrix
        #XXX   for faster implementation, W should be a sparse matrix
        W = numx.zeros((N, dp * N), dtype=self.dtype)

        if self.verbose:
            print ' - constructing [%i x %i] weight matrix...' % W.shape

        for row in range(N):
            if learn_outdim:
                nbrs = nbrss[row, :]
            else:
                # -----------------------------------------------
                #  find k nearest neighbors
                # -----------------------------------------------
                M_Mi = M - M[row]
                nbrs = numx.argsort((M_Mi**2).sum(1))[1:k + 1]

            #-----------------------------------------------
            #  center the neighborhood using the mean
            #-----------------------------------------------
            nbrhd = M[nbrs]  # this makes a copy
            nbrhd -= nbrhd.mean(0)

            #-----------------------------------------------
            #  compute local coordinates
            #   using a singular value decomposition
            #-----------------------------------------------
            U, sig, VT = svd(nbrhd)
            nbrhd = U.T[:d_out]
            del VT

            #-----------------------------------------------
            #  build Hessian estimator
            #-----------------------------------------------
            Yi = numx.zeros((dp, k), dtype=self.dtype)
            ct = 0
            for i in range(d_out):
                Yi[ct:ct + d_out - i, :] = nbrhd[i] * nbrhd[i:, :]
                ct += d_out - i
            Yi = numx.concatenate(
                [numx.ones((1, k), dtype=self.dtype), nbrhd, Yi], 0)

            #-----------------------------------------------
            #  orthogonalize linear and quadratic forms
            #   with QR factorization
            #  and make the weights sum to 1
            #-----------------------------------------------
            if k >= 1 + d_out + dp:
                Q, R = numx_linalg.qr(Yi.T)
                w = Q[:, d_out + 1:d_out + 1 + dp]
            else:
                q, r = _mgs(Yi.T)
                w = q[:, -dp:]

            S = w.sum(0)  #sum along columns
            #if S[i] is too small, set it equal to 1.0
            # this prevents weights from blowing up
            S[numx.where(numx.absolute(S) < 1E-4)] = 1.0
            #print w.shape, S.shape, (w/S).shape
            #print W[nbrs, row*dp:(row+1)*dp].shape
            W[nbrs, row * dp:(row + 1) * dp] = w / S

        #-----------------------------------------------
        # To find the null space, we want the
        #  first d+1 eigenvectors of W.T*W
        # Compute this using an svd of W
        #-----------------------------------------------

        if self.verbose:
            msg = (' - finding [%i x %i] '
                   'null space of weight matrix...' % (d_out, N))
            print msg

        #XXX future work:
        #XXX  use of upcoming ARPACK interface for bottom few eigenvectors
        #XXX   of a sparse matrix will significantly increase the speed
        #XXX   of the next step

        if self.svd:
            sig, U = nongeneral_svd(W.T, range=(2, d_out + 1))
            Y = U * numx.sqrt(N)
        else:
            WW = mult(W, W.T)
            # regularizes the eigenvalues, does not change the eigenvectors:
            W_diag_idx = numx.arange(N)
            WW[W_diag_idx, W_diag_idx] += 0.01
            sig, U = symeig(WW, range=(2, self.output_dim + 1), overwrite=True)
            Y = U * numx.sqrt(N)
            del WW
        del W

        #-----------------------------------------------
        # Normalize Y
        #
        # Alternative way to do it:
        #  we need R = (Y.T*Y)^(-1/2)
        #   do this with an SVD of Y            del VT

        #      Y = U*sig*V.T
        #      Y.T*Y = (V*sig.T*U.T) * (U*sig*V.T)
        #            = V * (sig*sig.T) * V.T
        #            = V * sig^2 V.T
        #   so
        #      R = V * sig^-1 * V.T
        # The code is:
        #    U, sig, VT = svd(Y)
        #    del U
        #    S = numx.diag(sig**-1)
        #    self.training_projection = mult(Y, mult(VT.T, mult(S, VT)))
        #-----------------------------------------------
        if self.verbose:
            print ' - normalizing null space...'

        C = sqrtm(mult(Y.T, Y))
        self.training_projection = mult(Y, C)
Exemple #15
0
    # create a reservoir node and enable leak rate
    reservoir = Oger.nodes.LeakyReservoirNode(output_dim=N, input_scaling=0.1)
    reservoir.leak_rate = 0.1

    # create a ridge regression node and enable washout during training (disregarding the first timesteps)
    readout = Oger.nodes.RidgeRegressionNode()
    Oger.utils.enable_washout(Oger.nodes.RidgeRegressionNode, 100)

    # Create the freerun flow, set the first dimension of the input to be external (i.e. not self-generated or fed-back)
    flow = Oger.nodes.FreerunFlow([reservoir, readout], external_input_range=[0], freerun_steps=freerun_steps)

    reservoir.reset_states = False

    # optimize the ridge parameter
    gridsearch_parameters = {readout: {"ridge_param": 10 ** numx.arange(-6, 0, 0.5)}}

    # Instantiate an optimizer
    loss_function = Oger.utils.timeslice(range(-freerun_steps, 0), Oger.utils.nrmse)
    opt = Oger.evaluation.Optimizer(gridsearch_parameters, loss_function)

    # Do the grid search
    opt.grid_search([[], train_signals], flow, cross_validate_function=Oger.evaluation.leave_one_out)

    # get the optimal flow
    opt_flow = opt.get_optimal_flow(verbose=True)

    # Train the optimized flow to do one-step ahead prediction using the teacher-forced signal
    # An additional input giving the frequency of the desired sine wave is also given
    opt_flow.train([[], train_signals])
def symeig_semidefinite_reg(
        A, B = None, eigenvectors=True, turbo="on", range=None,
        type=1, overwrite=False, rank_threshold=1e-12, dfc_out=None):
    """
    Regularization-based routine to solve generalized symmetric positive
    semidefinite eigenvalue problems.
    This can be used in case the normal symeig() call in _stop_training()
    throws SymeigException ('Covariance matrices may be singular').

    This solver applies a moderate regularization to B before applying
    eigh/symeig. Afterwards it properly detects the rank deficit and
    filters out malformed features.
    For full range, this procedure is (approximately) as efficient as the
    ordinary eigh implementation, because all additional steps are
    computationally cheap.
    For shorter range, the LDL method should be preferred.


    The signature of this function equals that of mdp.utils.symeig, but
    has two additional parameters:
    
    rank_threshold: A threshold to determine if an eigenvalue counts as zero.
    
    dfc_out: If dfc_out is not None dfc_out.rank_deficit will be set to an
             integer indicating how many zero-eigenvalues were detected.


    Note:
    For efficiency reasons it actually modifies the matrix B
    (even if overwrite=False), but the changes are negligible.
    """
    if type != 1:
        raise ValueError('Only type=1 is supported.')

    # apply some regularization...
    # The following is equivalent to B += 1e-12*np.eye(B.shape[0]),
    # but works more in place, i.e. saves memory consumption of np.eye().
    Bflat = B.reshape(B.shape[0]*B.shape[1])
    idx = numx.arange(0, len(Bflat), B.shape[0]+1)
    diag_tmp = Bflat[idx]
    Bflat[idx] += rank_threshold

    eg, ev = mdp.utils.symeig(A, B, True, turbo, None, type, overwrite)

    Bflat[idx] = diag_tmp
    m = numx.absolute(numx.sqrt(numx.absolute(
            numx.sum(ev * mdp.utils.mult(B, ev), 0)))-1)
    off = 0
    # In theory all values in m should be close to one or close to zero.
    # So we use the mean of these values as threshold to distinguish cases:
    while m[off] > 0.5:
        off += 1
    m_off_sum = numx.sum(m[off:])
    if m_off_sum < 0.5:
        if off > 0:
            if not dfc_out is None:
                dfc_out.rank_deficit = off
            eg = eg[off:]
            ev = ev[:, off:]
    else:
        # Sometimes (unlikely though) the values in m are not sorted
        # In this case we search all indices:
        m_idx = (m < 0.5).nonzero()[0]
        eg = eg[m_idx]
        ev = ev[:, m_idx]
    if range is None:
        return eg, ev
    else:
        return eg[range[0]-1:range[1]], ev[:, range[0]-1:range[1]]
def symeig_semidefinite_ldl(
        A, B = None, eigenvectors=True, turbo="on", rng=None,
        type=1, overwrite=False, rank_threshold=1e-12, dfc_out=None):
    """
    LDL-based routine to solve generalized symmetric positive semidefinite
    eigenvalue problems.
    This can be used in case the normal symeig() call in _stop_training()
    throws SymeigException ('Covariance matrices may be singular').

    This solver uses SciPy's raw LAPACK interface to access LDL decomposition.
    www.netlib.org/lapack/lug/node54.html describes how to solve a
    generalized eigenvalue problem with positive definite B using Cholesky/LL
    decomposition. We extend this method to solve for positive semidefinite B
    using LDL decomposition, which is a variant of Cholesky/LL decomposition
    for indefinite Matrices.
    Accessing raw LAPACK's LDL decomposition (sytrf) is challenging. This code
    is partly based on code for SciPy 1.1:
    github.com/scipy/scipy/pull/7941/files#diff-9bf9b4b2f0f40415bc0e72143584c889
    We optimized and shortened that code for the real-valued positive
    semidefinite case.

    This procedure is almost as efficient as the ordinary eigh implementation.
    This is because implementations for symmetric generalized eigenvalue
    problems usually perform the Cholesky approach mentioned above. The more
    general LDL decomposition is only slightly more expensive than Cholesky,
    due to pivotization.


    The signature of this function equals that of mdp.utils.symeig, but
    has two additional parameters:
    
    rank_threshold: A threshold to determine if an eigenvalue counts as zero.
    
    dfc_out: If dfc_out is not None dfc_out.rank_deficit will be set to an
             integer indicating how many zero-eigenvalues were detected.


    Note:
    This method requires SciPy >= 1.0.
    """
    if type != 1:
        raise ValueError('Only type=1 is supported.')

    # LDL-based method appears to be particularly unstable if blank lines
    # and columns exist in B. So we circumvent this case:
    nonzero_idx = _find_blank_data_idx(B, rank_threshold)
    if not nonzero_idx is None:
        orig_shape = B.shape
        B = B[nonzero_idx, :][:, nonzero_idx]
        A = A[nonzero_idx, :][:, nonzero_idx]

    # This method has special requirements, which is why we import here
    # rather than module wide.
    from scipy.linalg.lapack import get_lapack_funcs, _compute_lwork
    from scipy.linalg.blas import get_blas_funcs
    try:
        inv_tri, solver, solver_lwork = get_lapack_funcs(
                ('trtri', 'sytrf', 'sytrf_lwork'), (B,))
        mult_tri, = get_blas_funcs(('trmm',), (B,))
    except ValueError:
        err_msg = ("ldl method for solving symeig with rank deficit B "
                   "requires at least SciPy 1.0.")
        raise SymeigException(err_msg)

    n = B.shape[0]
    arng = numx.arange(n)
    lwork = _compute_lwork(solver_lwork, n, lower=1)
    lu, piv, _ = solver(B, lwork=lwork, lower=1, overwrite_a=overwrite)

    # using piv properly requires some postprocessing:
    swap_ = numx.arange(n)
    pivs = numx.zeros(swap_.shape, dtype=int)
    skip_2x2 = False
    for ind in range(n):
        # If previous spin belonged already to a 2x2 block
        if skip_2x2:
            skip_2x2 = False
            continue

        cur_val = piv[ind]
        # do we have a 1x1 block or not?
        if cur_val > 0:
            if cur_val != ind+1:
                # Index value != array value --> permutation required
                swap_[ind] = swap_[cur_val-1]
            pivs[ind] = 1
        # Not.
        elif cur_val < 0 and cur_val == piv[ind+1]:
            # first neg entry of 2x2 block identifier
            if -cur_val != ind+2:
                # Index value != array value --> permutation required
                swap_[ind+1] = swap_[-cur_val-1]
            pivs[ind] = 2
            skip_2x2 = True

    full_perm = numx.arange(n)
    for ind in range(n-1, -1, -1):
        s_ind = swap_[ind]
        if s_ind != ind:
            col_s = ind if pivs[ind] else ind-1 # 2x2 block
            lu[[s_ind, ind], col_s:] = lu[[ind, s_ind], col_s:]
            full_perm[[s_ind, ind]] = full_perm[[ind, s_ind]]
    # usually only a few indices actually permute, so we reduce perm:
    perm = (full_perm-arng).nonzero()[0]
    perm_idx = full_perm[perm]
    # end of ldl postprocessing
    # perm_idx and perm now describe a permutation as dest and source indexes

    lu[perm_idx, :] = lu[perm, :]

    dgd = abs(numx.diag(lu))
    dnz = (dgd > rank_threshold).nonzero()[0]
    dgd_sqrt_I = numx.sqrt(1.0/dgd[dnz])
    rank_deficit = len(dgd) - len(dnz) # later used

    # c, lower, unitdiag, overwrite_c
    LI, _ = inv_tri(lu, 1, 1, 1) # invert triangular
    # we mainly apply tril here, because we need to make a
    # copy of LI anyway, because original result from
    # dtrtri seems to be read-only regarding some operations
    LI = numx.tril(LI, -1)
    LI[arng, arng] = 1
    LI[dnz, :] *= dgd_sqrt_I.reshape((dgd_sqrt_I.shape[0], 1))

    A2 = A if overwrite else A.copy()
    A2[perm_idx, :] = A2[perm, :]
    A2[:, perm_idx] = A2[:, perm]
    # alpha, a, b, side 0=left 1=right, lower, trans_a, diag 1=unitdiag,
    # overwrite_b
    A2 = mult_tri(1.0, LI, A2, 1, 1, 1, 0, 1) # A2 = mult(A2, LI.T)
    A2 = mult_tri(1.0, LI, A2, 0, 1, 0, 0, 1) # A2 = mult(LI, A2)
    A2 = A2[dnz, :]
    A2 = A2[:, dnz]

    # overwrite=True is okay here, because at this point A2 is a copy anyway
    eg, ev = mdp.utils.symeig(A2, None, True, turbo, rng, overwrite=True)
    ev = mdp.utils.mult(LI[dnz].T, ev) if rank_deficit \
        else mult_tri(1.0, LI, ev, 0, 1, 1, 0, 1)
    ev[perm] = ev[perm_idx]

    if not nonzero_idx is None:
        # restore ev to original size
        rank_deficit += orig_shape[0]-len(nonzero_idx)
        ev_tmp = ev
        ev = numx.zeros((orig_shape[0], ev.shape[1]))
        ev[nonzero_idx, :] = ev_tmp

    if not dfc_out is None:
        dfc_out.rank_deficit = rank_deficit
    return eg, ev
Exemple #18
0
    # create a reservoir node and enable leak rate
    reservoir = Oger.nodes.LeakyReservoirNode(output_dim=N, input_scaling=.1)
    reservoir.leak_rate = .1

    # create a ridge regression node and enable washout during training (disregarding the first timesteps)
    readout = Oger.nodes.RidgeRegressionNode()
    Oger.utils.enable_washout(Oger.nodes.RidgeRegressionNode, 100)

    # Create the freerun flow, set the first dimension of the input to be external (i.e. not self-generated or fed-back)
    flow = Oger.nodes.FreerunFlow([reservoir, readout], external_input_range=[0], freerun_steps=freerun_steps)

    reservoir.reset_states = False

    # optimize the ridge parameter
    gridsearch_parameters = {readout:{'ridge_param': 10 ** numx.arange(-6, 0, .5)}}

    # Instantiate an optimizer
    loss_function = Oger.utils.timeslice(range(-freerun_steps, 0), Oger.utils.nrmse)
    opt = Oger.evaluation.Optimizer(gridsearch_parameters, loss_function)

    # Do the grid search
    opt.grid_search([[], train_signals], flow,
                    cross_validate_function=Oger.evaluation.leave_one_out)

    # get the optimal flow
    opt_flow = opt.get_optimal_flow(verbose=True)

    # Train the optimized flow to do one-step ahead prediction using the teacher-forced signal
    # An additional input giving the frequency of the desired sine wave is also given   
    opt_flow.train([[] , train_signals])
Exemple #19
0
def symeig_semidefinite_ldl(A,
                            B=None,
                            eigenvectors=True,
                            turbo="on",
                            rng=None,
                            type=1,
                            overwrite=False,
                            rank_threshold=1e-12,
                            dfc_out=None):
    """LDL-based routine to solve generalized symmetric positive semidefinite
    eigenvalue problems.

    This can be used if the normal ``symeig()`` call in ``_stop_training()``
    throws ``SymeigException('Covariance matrices may be singular')``.

    This solver uses SciPy's raw LAPACK interface to access LDL decomposition.
    http://www.netlib.org/lapack/lug/node54.html describes how to solve a
    generalized eigenvalue problem with positive definite B using Cholesky/LL
    decomposition. We extend this method to solve for positive semidefinite B
    using LDL decomposition, which is a variant of Cholesky/LL decomposition
    for indefinite Matrices.
    Accessing raw LAPACK's LDL decomposition (sytrf) is challenging. This code
    is partly based on code for SciPy 1.1:
    http://github.com/scipy/scipy/pull/7941/files#diff-9bf9b4b2f0f40415bc0e72143584c889
    We optimized and shortened that code for the real-valued positive
    semidefinite case.

    This procedure is almost as efficient as the ordinary eigh implementation.
    This is because implementations for symmetric generalized eigenvalue
    problems usually perform the Cholesky approach mentioned above. The more
    general LDL decomposition is only slightly more expensive than Cholesky,
    due to pivotization.

    .. note:: This method requires SciPy >= 1.0.
    
    The signature of this function equals that of ``mdp.utils.symeig``,
    but has two additional parameters:
    
    :param rank_threshold:
        A threshold to determine if an eigenvalue counts as zero.
    :type rank_threshold: float
    
    :param dfc_out:
        If ``dfc_out`` is not ``None``, ``dfc_out.rank_deficit`` will be set
        to an integer indicating how many zero-eigenvalues were detected.
    """
    if type != 1:
        raise ValueError('Only type=1 is supported.')

    # LDL-based method appears to be particularly unstable if blank lines
    # and columns exist in B. So we circumvent this case:
    nonzero_idx = _find_blank_data_idx(B, rank_threshold)
    if not nonzero_idx is None:
        orig_shape = B.shape
        B = B[nonzero_idx, :][:, nonzero_idx]
        A = A[nonzero_idx, :][:, nonzero_idx]

    # This method has special requirements, which is why we import here
    # rather than module wide.
    from scipy.linalg.lapack import get_lapack_funcs, _compute_lwork
    from scipy.linalg.blas import get_blas_funcs
    try:
        inv_tri, solver, solver_lwork = get_lapack_funcs(
            ('trtri', 'sytrf', 'sytrf_lwork'), (B, ))
        mult_tri, = get_blas_funcs(('trmm', ), (B, ))
    except ValueError:
        err_msg = ("ldl method for solving symeig with rank deficit B "
                   "requires at least SciPy 1.0.")
        raise SymeigException(err_msg)

    n = B.shape[0]
    arng = numx.arange(n)
    lwork = _compute_lwork(solver_lwork, n, lower=1)
    lu, piv, _ = solver(B, lwork=lwork, lower=1, overwrite_a=overwrite)

    # using piv properly requires some postprocessing:
    swap_ = numx.arange(n)
    pivs = numx.zeros(swap_.shape, dtype=int)
    skip_2x2 = False
    for ind in range(n):
        # If previous spin belonged already to a 2x2 block
        if skip_2x2:
            skip_2x2 = False
            continue

        cur_val = piv[ind]
        # do we have a 1x1 block or not?
        if cur_val > 0:
            if cur_val != ind + 1:
                # Index value != array value --> permutation required
                swap_[ind] = swap_[cur_val - 1]
            pivs[ind] = 1
        # Not.
        elif cur_val < 0 and cur_val == piv[ind + 1]:
            # first neg entry of 2x2 block identifier
            if -cur_val != ind + 2:
                # Index value != array value --> permutation required
                swap_[ind + 1] = swap_[-cur_val - 1]
            pivs[ind] = 2
            skip_2x2 = True

    full_perm = numx.arange(n)
    for ind in range(n - 1, -1, -1):
        s_ind = swap_[ind]
        if s_ind != ind:
            col_s = ind if pivs[ind] else ind - 1  # 2x2 block
            lu[[s_ind, ind], col_s:] = lu[[ind, s_ind], col_s:]
            full_perm[[s_ind, ind]] = full_perm[[ind, s_ind]]
    # usually only a few indices actually permute, so we reduce perm:
    perm = (full_perm - arng).nonzero()[0]
    perm_idx = full_perm[perm]
    # end of ldl postprocessing
    # perm_idx and perm now describe a permutation as dest and source indexes

    lu[perm_idx, :] = lu[perm, :]

    dgd = abs(numx.diag(lu))
    dnz = (dgd > rank_threshold).nonzero()[0]
    dgd_sqrt_I = numx.sqrt(1.0 / dgd[dnz])
    rank_deficit = len(dgd) - len(dnz)  # later used

    # c, lower, unitdiag, overwrite_c
    LI, _ = inv_tri(lu, 1, 1, 1)  # invert triangular
    # we mainly apply tril here, because we need to make a
    # copy of LI anyway, because original result from
    # dtrtri seems to be read-only regarding some operations
    LI = numx.tril(LI, -1)
    LI[arng, arng] = 1
    LI[dnz, :] *= dgd_sqrt_I.reshape((dgd_sqrt_I.shape[0], 1))

    A2 = A if overwrite else A.copy()
    A2[perm_idx, :] = A2[perm, :]
    A2[:, perm_idx] = A2[:, perm]
    # alpha, a, b, side 0=left 1=right, lower, trans_a, diag 1=unitdiag,
    # overwrite_b
    A2 = mult_tri(1.0, LI, A2, 1, 1, 1, 0, 1)  # A2 = mult(A2, LI.T)
    A2 = mult_tri(1.0, LI, A2, 0, 1, 0, 0, 1)  # A2 = mult(LI, A2)
    A2 = A2[dnz, :]
    A2 = A2[:, dnz]

    # overwrite=True is okay here, because at this point A2 is a copy anyway
    eg, ev = mdp.utils.symeig(A2, None, True, turbo, rng, overwrite=True)
    ev = mdp.utils.mult(LI[dnz].T, ev) if rank_deficit \
        else mult_tri(1.0, LI, ev, 0, 1, 1, 0, 1)
    ev[perm] = ev[perm_idx]

    if not nonzero_idx is None:
        # restore ev to original size
        rank_deficit += orig_shape[0] - len(nonzero_idx)
        ev_tmp = ev
        ev = numx.zeros((orig_shape[0], ev.shape[1]))
        ev[nonzero_idx, :] = ev_tmp

    if not dfc_out is None:
        dfc_out.rank_deficit = rank_deficit
    return eg, ev
Exemple #20
0
def symeig_semidefinite_reg(A,
                            B=None,
                            eigenvectors=True,
                            turbo="on",
                            range=None,
                            type=1,
                            overwrite=False,
                            rank_threshold=1e-12,
                            dfc_out=None):
    """Regularization-based routine to solve generalized symmetric positive
    semidefinite eigenvalue problems.

    This can be used if the normal ``symeig()`` call in ``_stop_training()``
    throws ``SymeigException('Covariance matrices may be singular')``.

    This solver applies a moderate regularization to B before applying
    ``eigh``/``symeig``. Afterwards it properly detects the rank deficit and
    filters out malformed features.
    For full range, this procedure is (approximately) as efficient as the
    ordinary ``eigh`` implementation, because all additional steps are
    computationally cheap.
    For shorter range, the LDL method should be preferred.

    .. note::
        For efficiency reasons it actually modifies the matrix B
        (even if ``overwrite=False``), but the changes are negligible.

    The signature of this function equals that of ``mdp.utils.symeig``,
    but has two additional parameters:
    
    :param rank_threshold:
        A threshold to determine if an eigenvalue counts as zero.
    :type rank_threshold: float
    
    :param dfc_out:
        If ``dfc_out`` is not ``None``, ``dfc_out.rank_deficit`` will be set
        to an integer indicating how many zero-eigenvalues were detected.
    """
    if type != 1:
        raise ValueError('Only type=1 is supported.')

    # apply some regularization...
    # The following is equivalent to B += 1e-12*np.eye(B.shape[0]),
    # but works more in place, i.e. saves memory consumption of np.eye().
    Bflat = B.reshape(B.shape[0] * B.shape[1])
    idx = numx.arange(0, len(Bflat), B.shape[0] + 1)
    diag_tmp = Bflat[idx]
    Bflat[idx] += rank_threshold

    eg, ev = mdp.utils.symeig(A, B, True, turbo, None, type, overwrite)

    Bflat[idx] = diag_tmp
    m = numx.absolute(
        numx.sqrt(numx.absolute(numx.sum(ev * mdp.utils.mult(B, ev), 0))) - 1)
    off = 0
    # In theory all values in m should be close to one or close to zero.
    # So we use the mean of these values as threshold to distinguish cases:
    while m[off] > 0.5:
        off += 1
    m_off_sum = numx.sum(m[off:])
    if m_off_sum < 0.5:
        if off > 0:
            if not dfc_out is None:
                dfc_out.rank_deficit = off
            eg = eg[off:]
            ev = ev[:, off:]
    else:
        # Sometimes (unlikely though) the values in m are not sorted
        # In this case we search all indices:
        m_idx = (m < 0.5).nonzero()[0]
        eg = eg[m_idx]
        ev = ev[:, m_idx]
    if range is None:
        return eg, ev
    else:
        return eg[range[0] - 1:range[1]], ev[:, range[0] - 1:range[1]]
Exemple #21
0
    def _stop_training(self):
        Cumulator._stop_training(self)

        if self.verbose:
            msg = ('training LLE on %i points'
                   ' in %i dimensions...' % (self.data.shape[0],
                                             self.data.shape[1]))
            print msg

        # some useful quantities
        M = self.data
        N = M.shape[0]
        k = self.k
        r = self.r

        # indices of diagonal elements
        W_diag_idx = numx.arange(N)
        Q_diag_idx = numx.arange(k)

        if k > N:
            err = ('k=%i must be less than or '
                   'equal to number of training points N=%i' % (k, N))
            raise TrainingException(err)

        # determines number of output dimensions: if desired_variance
        # is specified, we need to learn it from the data. Otherwise,
        # it's easy
        learn_outdim = False
        if self.output_dim is None:
            if self.desired_variance is None:
                self.output_dim = self.input_dim
            else:
                learn_outdim = True

        # do we need to automatically determine the regularization term?
        auto_reg = r is None

        # determine number of output dims, precalculate useful stuff
        if learn_outdim:
            Qs, sig2s, nbrss = self._adjust_output_dim()

        # build the weight matrix
        #XXX future work:
        #XXX   for faster implementation, W should be a sparse matrix
        W = numx.zeros((N, N), dtype=self.dtype)

        if self.verbose:
            print ' - constructing [%i x %i] weight matrix...' % W.shape

        for row in range(N):
            if learn_outdim:
                Q = Qs[row, :, :]
                nbrs = nbrss[row, :]
            else:
                # -----------------------------------------------
                #  find k nearest neighbors
                # -----------------------------------------------
                M_Mi = M-M[row]
                nbrs = numx.argsort((M_Mi**2).sum(1))[1:k+1]
                M_Mi = M_Mi[nbrs]
                # compute covariance matrix of distances
                Q = mult(M_Mi, M_Mi.T)

            # -----------------------------------------------
            #  compute weight vector based on neighbors
            # -----------------------------------------------

            #Covariance matrix may be nearly singular:
            # add a diagonal correction to prevent numerical errors
            if auto_reg:
                # automatic mode: correction is equal to the sum of
                # the (d_in-d_out) unused variances (as in deRidder &
                # Duin)
                if learn_outdim:
                    sig2 = sig2s[row, :]
                else:
                    sig2 = svd(M_Mi, compute_uv=0)**2
                r = numx.sum(sig2[self.output_dim:])
                Q[Q_diag_idx, Q_diag_idx] += r
            else:
                # Roweis et al instead use "a correction that
                #   is small compared to the trace" e.g.:
                # r = 0.001 * float(Q.trace())
                # this is equivalent to assuming 0.1% of the variance is unused
                Q[Q_diag_idx, Q_diag_idx] += r*Q.trace()

            #solve for weight
            # weight is w such that sum(Q_ij * w_j) = 1 for all i
            # XXX refcast is due to numpy bug: floats become double
            w = self._refcast(numx_linalg.solve(Q, numx.ones(k)))
            w /= w.sum()

            #update row of the weight matrix
            W[nbrs, row] = w

        if self.verbose:
            msg = (' - finding [%i x %i] null space of weight matrix\n'
                   '     (may take a while)...' % (self.output_dim, N))
            print msg

        self.W = W.copy()
        #to find the null space, we need the bottom d+1
        #  eigenvectors of (W-I).T*(W-I)
        #Compute this using the svd of (W-I):
        W[W_diag_idx, W_diag_idx] -= 1.

        #XXX future work:
        #XXX  use of upcoming ARPACK interface for bottom few eigenvectors
        #XXX   of a sparse matrix will significantly increase the speed
        #XXX   of the next step
        if self.svd:
            sig, U = nongeneral_svd(W.T, range=(2, self.output_dim+1))
        else:
            # the following code does the same computation, but uses
            # symeig, which computes only the required eigenvectors, and
            # is much faster. However, it could also be more unstable...
            WW = mult(W, W.T)
            # regularizes the eigenvalues, does not change the eigenvectors:
            WW[W_diag_idx, W_diag_idx] += 0.1
            sig, U = symeig(WW, range=(2, self.output_dim+1), overwrite=True)

        self.training_projection = U