Repository: biolab/orange3

Issue:
Orange.projection loads slowly after introduction of fastTSNE
After f5eeaa6cf24b7ab62a8a24847b43ec8489647048 (tSNE: Implement Orange wrapper around Pavlin's implementation of tSNE) loading of Orange.projection became even slower.

On my computer, it used to take 0.9 second, now it takes 2.3 second.

Should we import fastTSNE only when actually needed?


Discussion:
@pavlin-policar, @markotoplak, is this still an issue and will we (ehm, you :)) do something about it?
Yes, it is still an issue. Now it takes ~2.6 seconds. It can be fixed by not using numba (@pavlin-policar 
, did anyone try to work on this?) or importing some things more lazily. 
I don't see the t-SNE library not-depending on numba any time soon. I've been looking for alternatives, but have found none that would make sense. Eventually, I might write my own implementation of the library depending on numba, but that will require a fair bit of work.

In the meantime, I'll look into how we could load things lazily.

Resolving patch:
diff --git a/Orange/projection/manifold.py b/Orange/projection/manifold.py
--- a/Orange/projection/manifold.py
+++ b/Orange/projection/manifold.py
@@ -8,10 +8,6 @@
 from scipy.sparse.linalg import eigsh as arpack_eigh
 import sklearn.manifold as skl_manifold
 
-import openTSNE
-import openTSNE.affinity
-import openTSNE.initialization
-
 import Orange
 from Orange.data import Table, Domain, ContinuousVariable
 from Orange.distance import Distance, DistanceModel, Euclidean
@@ -21,10 +17,6 @@
 __all__ = ["MDS", "Isomap", "LocallyLinearEmbedding", "SpectralEmbedding",
            "TSNE"]
 
-# Disable t-SNE user warnings
-openTSNE.tsne.log.setLevel(logging.ERROR)
-openTSNE.affinity.log.setLevel(logging.ERROR)
-
 
 def torgerson(distances, n_components=2, eigen_solver="auto"):
     """
@@ -190,6 +182,23 @@ def __init__(self, n_components=2, affinity='nearest_neighbors', gamma=None,
         self.params = vars()
 
 
+class lazy_openTSNE:
+    """openTSNE uses numba, which is slow to load, so load it lazily."""
+    def __getattr__(self, item):
+        import sys
+        import openTSNE
+        import openTSNE.affinity
+        import openTSNE.initialization
+        if "openTSNE" in sys.modules:
+            # Disable t-SNE user warnings
+            openTSNE.tsne.log.setLevel(logging.ERROR)
+            openTSNE.affinity.log.setLevel(logging.ERROR)
+        return getattr(openTSNE, item)
+
+
+openTSNE = lazy_openTSNE()
+
+
 class TSNEModel(Projection):
     """A t-SNE embedding object. Supports further optimization as well as
     adding new data into the existing embedding.
@@ -204,8 +213,8 @@ class TSNEModel(Projection):
     pre_domain : Domain
         Original data domain
     """
-    def __init__(self, embedding: openTSNE.TSNEEmbedding, table: Table,
-                 pre_domain: Domain):
+    def __init__(self, embedding, table, pre_domain):
+        # type: (openTSNE.TSNEEmbedding, Table, Domain) -> None
         transformer = TransformDomain(self)
 
         def proj_variable(i):
@@ -221,7 +230,8 @@ def proj_variable(i):
             class_vars=table.domain.class_vars,
             metas=table.domain.metas)
 
-    def transform(self, X: np.ndarray, learning_rate=1, **kwargs) -> openTSNE.PartialTSNEEmbedding:
+    def transform(self, X, learning_rate=1, **kwargs):
+        # type: (np.ndarray, int, ...) -> openTSNE.PartialTSNEEmbedding
         if sp.issparse(X):
             raise TypeError(
                 "A sparse matrix was passed, but dense data is required. Use "
@@ -481,7 +491,8 @@ def prepare_embedding(self, affinities, initialization):
             callbacks_every_iters=self.callbacks_every_iters,
         )
 
-    def fit(self, X: np.ndarray, Y: np.ndarray = None) -> openTSNE.TSNEEmbedding:
+    def fit(self, X, Y=None):
+        # type: (np.ndarray, Optional[np.ndarray]) -> openTSNE.TSNEEmbedding
         # Compute affinities and initial positions and prepare the embedding object
         affinities = self.compute_affinities(X)
         initialization = self.compute_initialization(X)


Test patch: