coding utf8 references https lvdmaaten github iotsne based on https lvdmaaten github iotsnecodetsnepython zip a tdistributed stochastic neighbor embedding implementation parameters maxiter int default 200 perplexity float default 30 0 ncomponents int default 2 normalizer q distribution early exaggeration momentum perform gradient step computes pairwise affinities affines np zerosself nsamples self nsamples dtypenp float32 targetentropy np logself perplexity distances l2distancex for i in rangeself nsamples affinesi self binarysearchdistancesi targetentropy fill diagonal with near zero value np filldiagonalaffines 1 0e12 affines affines clipmin1e100 affines affines affines t 2 self nsamples return affines def binarysearchself dist targetentropy exclude zeros decrease precision increase precision computes student tdistribution q 1 0 1 0 d np filldiagonalq 0 0 q q clipmin1e100 return q coding utf 8 references https lvdmaaten github io tsne based on https lvdmaaten github io tsne code tsne_python zip a t distributed stochastic neighbor embedding implementation parameters max_iter int default 200 perplexity float default 30 0 n_components int default 2 normalizer q distribution early exaggeration momentum perform gradient step computes pairwise affinities fill diagonal with near zero value performs binary search to find suitable precision exclude zeros decrease precision increase precision computes student t distribution
import logging

import numpy as np

from mla.base import BaseEstimator
from mla.metrics.distance import l2_distance

np.random.seed(999)




class TSNE(BaseEstimator):
    y_required = False

    def __init__(self, n_components=2, perplexity=30.0, max_iter=200, learning_rate=500):
        
        self.max_iter = max_iter
        self.perplexity = perplexity
        self.n_components = n_components
        self.initial_momentum = 0.5
        self.final_momentum = 0.8
        self.min_gain = 0.01
        self.lr = learning_rate
        self.tol = 1e-5
        self.perplexity_tries = 50

    def fit_transform(self, X, y=None):
        self._setup_input(X, y)

        Y = np.random.randn(self.n_samples, self.n_components)
        velocity = np.zeros_like(Y)
        gains = np.ones_like(Y)

        P = self._get_pairwise_affinities(X)

        iter_num = 0
        while iter_num < self.max_iter:
            iter_num += 1

            D = l2_distance(Y)
            Q = self._q_distribution(D)

            
            Q_n = Q / np.sum(Q)

            
            pmul = 4.0 if iter_num < 100 else 1.0
            momentum = 0.5 if iter_num < 20 else 0.8

            
            grads = np.zeros(Y.shape)
            for i in range(self.n_samples):
                grad = 4 * np.dot((pmul * P[i] - Q_n[i]) * Q[i], Y[i] - Y)
                grads[i] = grad

            gains = (gains + 0.2) * ((grads > 0) != (velocity > 0)) + (gains * 0.8) * ((grads > 0) == (velocity > 0))
            gains = gains.clip(min=self.min_gain)

            velocity = momentum * velocity - self.lr * (gains * grads)
            Y += velocity
            Y = Y - np.mean(Y, 0)

            error = np.sum(P * np.log(P / Q_n))
            logging.info("Iteration %s, error %s" % (iter_num, error))
        return Y

    def _get_pairwise_affinities(self, X):
        
        affines = np.zeros((self.n_samples, self.n_samples), dtype=np.float32)
        target_entropy = np.log(self.perplexity)
        distances = l2_distance(X)

        for i in range(self.n_samples):
            affines[i, :] = self._binary_search(distances[i], target_entropy)

        
        np.fill_diagonal(affines, 1.0e-12)

        affines = affines.clip(min=1e-100)
        affines = (affines + affines.T) / (2 * self.n_samples)
        return affines

    def _binary_search(self, dist, target_entropy):
        
        precision_min = 0
        precision_max = 1.0e15
        precision = 1.0e5

        for _ in range(self.perplexity_tries):
            denom = np.sum(np.exp(-dist[dist > 0.0] / precision))
            beta = np.exp(-dist / precision) / denom

            
            g_beta = beta[beta > 0.0]
            entropy = -np.sum(g_beta * np.log2(g_beta))

            error = entropy - target_entropy

            if error > 0:
                
                precision_max = precision
                precision = (precision + precision_min) / 2.0
            else:
                
                precision_min = precision
                precision = (precision + precision_max) / 2.0

            if np.abs(error) < self.tol:
                break

        return beta

    def _q_distribution(self, D):
        
        Q = 1.0 / (1.0 + D)
        np.fill_diagonal(Q, 0.0)
        Q = Q.clip(min=1e-100)
        return Q