diff --git a/.gitignore b/.gitignore index b6233b9dfd..ea4467f8bc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +*.c +*.cpp + # Distribution / packaging .Python build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..736c651f8a --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +# simple makefile to simplify repetitive build env management tasks under posix + +# caution: testing won't work on windows, see README + +PYTHON ?= python +CYTHON ?= cython +PYTEST ?= pytest +CTAGS ?= ctags + +all: clean inplace test + +clean-ctags: + rm -f tags + +clean: clean-ctags + $(PYTHON) setup.py clean + rm -rf dist + # TODO: Remove in when all modules are removed. + $(PYTHON) sklearn/_build_utils/deprecated_modules.py + +in: inplace # just a shortcut +inplace: + $(PYTHON) setup.py build_ext -i + +cython: + python setup.py build_src + +ctags: + # make tags for symbol based navigation in emacs and vim + # Install with: sudo apt-get install exuberant-ctags + $(CTAGS) --python-kinds=-i -R sklearn diff --git a/proglearn/forest.py b/proglearn/forest.py index db9e7cabbc..746ed0b797 100644 --- a/proglearn/forest.py +++ b/proglearn/forest.py @@ -41,11 +41,13 @@ class LifelongClassificationForest(ClassificationProgressiveLearner): oblique : bool, default=False Specifies if an oblique tree should used for the classifier or not. - feature_combinations : float, default=1.5 + feature_combinations : float, default=2 The feature combinations to use for the oblique split. + Equal to the parameter 'L' in the sporf paper. - density : float, default=0.5 - Density estimate. + max_features : float, default=1.0 + Controls the max number of features to consider for oblique split. + The parameter 'd' in the sporf paper is equal to ceil(max_features * dimensions) Attributes ---------- @@ -61,8 +63,8 @@ def __init__( default_kappa=np.inf, default_max_depth=30, oblique=False, - default_feature_combinations=1.5, - default_density=0.5, + default_feature_combinations=2, + default_max_features=1.0, ): self.default_n_estimators = default_n_estimators self.default_tree_construction_proportion = default_tree_construction_proportion @@ -73,7 +75,7 @@ def __init__( if oblique: default_transformer_class = ObliqueTreeClassificationTransformer self.default_feature_combinations = default_feature_combinations - self.default_density = default_density + self.default_max_features = default_max_features else: default_transformer_class = TreeClassificationTransformer @@ -97,7 +99,7 @@ def add_task( kappa="default", max_depth="default", feature_combinations="default", - density="default", + max_features="default", ): """ adds a task with id task_id, max tree depth max_depth, given input data matrix X @@ -133,11 +135,13 @@ def add_task( The maximum depth of a tree in the Lifelong Classification Forest. The default is used if 'default' is provided. - feature_combinations : float, default='default' + feature_combinations : float, default=2 The feature combinations to use for the oblique split. + Equal to the parameter 'L' in the sporf paper. - density : float, default='default' - Density estimate. + max_features : int, default=None + The max number of features to consider for oblique split. + Equal to the parameter 'd' in the sporf paper. Returns ------- @@ -156,14 +160,14 @@ def add_task( if self.oblique: if feature_combinations == "default": feature_combinations = self.default_feature_combinations - if density == "default": - density = self.default_density + if max_features == "default": + max_features = self.default_max_features transformer_kwargs = { "kwargs": { "max_depth": max_depth, "feature_combinations": feature_combinations, - "density": density, + "max_features": max_features, } } diff --git a/proglearn/split.pyx b/proglearn/split.pyx new file mode 100644 index 0000000000..225056d9e1 --- /dev/null +++ b/proglearn/split.pyx @@ -0,0 +1,245 @@ +#cython: language_level=3 +#cython: boundscheck=False +#cython: wraparound=False + +cimport cython + +import numpy as np + +from libcpp.unordered_map cimport unordered_map +from cython.operator import dereference, postincrement + +from libcpp.algorithm cimport sort as stdsort + +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +from cython.parallel import prange + +# Computes the gini score for a split +# 0 < t < len(y) + +cdef class BaseObliqueSplitter: + + cdef void argsort(self, double[:] y, int[:] idx) nogil: + + cdef int length = y.shape[0] + cdef int i = 0 + cdef pair[double, int] p + cdef vector[pair[double, int]] v + + for i in range(length): + p.first = y[i] + p.second = i + v.push_back(p) + + stdsort(v.begin(), v.end()) + + for i in range(length): + idx[i] = v[i].second + + cdef (int, int) argmin(self, double[:, :] A) nogil: + cdef int N = A.shape[0] + cdef int M = A.shape[1] + cdef int i = 0 + cdef int j = 0 + cdef int min_i = 0 + cdef int min_j = 0 + cdef double minimum = A[0, 0] + + for i in range(N): + for j in range(M): + + if A[i, j] < minimum: + minimum = A[i, j] + min_i = i + min_j = j + + return (min_i, min_j) + + cdef int argmax(self, double[:] A) nogil: + cdef int N = A.shape[0] + cdef int i = 0 + cdef int max_i = 0 + cdef double maximum = A[0] + + for i in range(N): + if A[i] > maximum: + maximum = A[i] + max_i = i + + return max_i + + cdef double impurity(self, double[:] y) nogil: + cdef int length = y.shape[0] + cdef double dlength = y.shape[0] + cdef double temp = 0 + cdef double gini = 1.0 + + cdef unordered_map[double, double] counts + cdef unordered_map[double, double].iterator it = counts.begin() + + if length == 0: + return 0 + + # Count all unique elements + for i in range(0, length): + temp = y[i] + counts[temp] += 1 + + it = counts.begin() + while it != counts.end(): + temp = dereference(it).second + temp = temp / dlength + temp = temp * temp + gini -= temp + + postincrement(it) + + return gini + + cdef double score(self, double[:] y, int t) nogil: + cdef double length = y.shape[0] + cdef double left_gini = 1.0 + cdef double right_gini = 1.0 + cdef double gini = 0 + + cdef double[:] left = y[:t] + cdef double[:] right = y[t:] + + cdef double l_length = left.shape[0] + cdef double r_length = right.shape[0] + + left_gini = self.impurity(left) + right_gini = self.impurity(right) + + gini = (l_length / length) * left_gini + (r_length / length) * right_gini + return gini + + # X = proj_X, y = y_sample + cpdef best_split(self, double[:, :] X, double[:] y, int[:] sample_inds): + + cdef int n_samples = X.shape[0] + cdef int proj_dims = X.shape[1] + cdef int i = 0 + cdef int j = 0 + cdef long temp_int = 0; + cdef double node_impurity = 0; + + cdef int thresh_i = 0 + cdef int feature = 0 + cdef double best_gini = 0 + cdef double threshold = 0 + cdef double improvement = 0 + cdef double left_impurity = 0 + cdef double right_impurity = 0 + + Q = np.zeros((n_samples, proj_dims), dtype=np.float64) + cdef double[:, :] Q_view = Q + + idx = np.zeros(n_samples, dtype=np.intc) + cdef int[:] idx_view = idx + + y_sort = np.zeros(n_samples, dtype=np.float64) + cdef double[:] y_sort_view = y_sort + + feat_sort = np.zeros(n_samples, dtype=np.float64) + cdef double[:] feat_sort_view = feat_sort + + si_return = np.zeros(n_samples, dtype=np.intc) + cdef int[:] si_return_view = si_return + + # No split or invalid split --> node impurity + node_impurity = self.impurity(y) + Q_view[:, :] = node_impurity + + for j in range(0, proj_dims): + + self.argsort(X[:, j], idx_view) + for i in range(0, n_samples): + temp_int = idx_view[i] + y_sort_view[i] = y[temp_int] + feat_sort_view[i] = X[temp_int, j] + + for i in prange(1, n_samples, nogil=True): + + # Check if the split is valid! + if feat_sort_view[i-1] < feat_sort_view[i]: + Q_view[i, j] = self.score(y_sort_view, i) + + # Identify best split + (thresh_i, feature) = self.argmin(Q_view) + + best_gini = Q_view[thresh_i, feature] + # Sort samples by split feature + self.argsort(X[:, feature], idx_view) + for i in range(0, n_samples): + temp_int = idx_view[i] + + # Sort X so we can get threshold + feat_sort_view[i] = X[temp_int, feature] + + # Sort y so we can get left_y, right_y + y_sort_view[i] = y[temp_int] + + # Sort true sample inds + si_return_view[i] = sample_inds[temp_int] + + # Get threshold, split samples into left and right + if (thresh_i == 0): + threshold = node_impurity #feat_sort_view[thresh_i] + else: + threshold = 0.5 * (feat_sort_view[thresh_i] + feat_sort_view[thresh_i - 1]) + + left_idx = si_return_view[:thresh_i] + right_idx = si_return_view[thresh_i:] + + # Evaluate improvement + improvement = node_impurity - best_gini + + # Evaluate impurities for left and right children + left_impurity = self.impurity(y_sort_view[:thresh_i]) + right_impurity = self.impurity(y_sort_view[thresh_i:]) + + return feature, threshold, left_impurity, left_idx, right_impurity, right_idx, improvement + + """ + Python wrappers for cdef functions. + Only to be used for testing purposes. + """ + + def test_argsort(self, y): + idx = np.zeros(len(y), dtype=np.intc) + self.argsort(y, idx) + return idx + + def test_argmin(self, M): + return self.argmin(M) + + def test_impurity(self, y): + return self.impurity(y) + + def test_score(self, y, t): + return self.score(y, t) + + def test_best_split(self, X, y, idx): + return self.best_split(X, y, idx) + + def test(self): + + # Test score + y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1], dtype=np.float64) + s = [self.score(y, i) for i in range(10)] + print(s) + + # Test splitter + # This one worked + X = np.array([[0, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1]], dtype=np.float64) + y = np.array([0, 0, 0, 1, 1, 1, 1], dtype=np.float64) + si = np.array([0, 1, 2, 3, 4, 5, 6], dtype=np.intc) + + (f, t, li, lidx, ri, ridx, imp) = self.best_split(X, y, si) + print(f, t) + + diff --git a/proglearn/tests/tree/test_morf.py b/proglearn/tests/tree/test_morf.py new file mode 100644 index 0000000000..72eccc5750 --- /dev/null +++ b/proglearn/tests/tree/test_morf.py @@ -0,0 +1,54 @@ +# from the template test in sklearn: +# https://github.com/scikit-learn-contrib/project-template/blob/master/skltemplate/tests/test_template.py + +import math +import re + +import numpy as np +import pytest +from sklearn import datasets, metrics +from sklearn.utils.validation import check_random_state + +from proglearn.morf import Conv2DSplitter + +# toy sample +X = [[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1]] +y = [0, 0, 0, 1, 1, 1] +T = [[-1, -1], [2, 2], [3, 2]] +true_result = [0, 1, 1] + +# also load the iris dataset +# and randomly permute it +iris = datasets.load_iris() +rng = check_random_state(0) +perm = rng.permutation(iris.target.size) +iris.data = iris.data[perm] +iris.target = iris.target[perm] + +# also load the boston dataset +# and randomly permute it +boston = datasets.load_boston() +perm = rng.permutation(boston.target.size) +boston.data = boston.data[perm] +boston.target = boston.target[perm] + + +def test_convolutional_splitter(): + n = 50 + height = 40 + d = 40 + X = np.ones((n, height, d)) + y = np.ones((n,)) + y[:25] = 0 + + splitter = Conv2DSplitter( + X, + y, + max_features=1, + feature_combinations=1.5, + random_state=random_state, + image_height=height, + image_width=d, + patch_height_max=2, + patch_height_min=2, + ) diff --git a/proglearn/tests/tree/test_splitter.py b/proglearn/tests/tree/test_splitter.py new file mode 100644 index 0000000000..26b7925933 --- /dev/null +++ b/proglearn/tests/tree/test_splitter.py @@ -0,0 +1,199 @@ +import numpy as np +from numpy.testing import ( + assert_almost_equal, + assert_allclose +) +import pytest +from split import BaseObliqueSplitter as BOS + +class TestBaseSplitter: + + def test_argsort(self): + b = BOS() + + # Ascending array + y = np.array([0, 1, 2, 3, 4], dtype=np.float64) + idx = b.test_argsort(y) + assert_allclose(y, idx) + + # Descending array + y = np.array([4, 3, 2, 1, 0], dtype=np.float64) + idx = b.test_argsort(y) + assert_allclose(y, idx) + + # Array with repeated values + y = np.array([1, 1, 1, 0, 0], dtype=np.float64) + idx = b.test_argsort(y) + assert_allclose([3, 4, 0, 1, 2], idx) + + def test_argmin(self): + + b = BOS() + + X = np.ones((5, 5), dtype=np.float64) + X[3, 4] = 0 + (i, j) = b.test_argmin(X) + assert 3 == i + assert 4 == j + + def test_impurity(self): + + """ + First 2 + Taken from SPORF's fpGiniSplitTest.h + """ + + b = BOS() + + y = np.ones(6, dtype=np.float64) * 4 + imp = b.test_impurity(y) + assert 0 == imp + + y[:3] = 2 + imp = b.test_impurity(y) + assert 0.5 == imp + + y = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2], dtype=np.float64) + imp = b.test_impurity(y) + assert_almost_equal((2/3), imp) + + def test_score(self): + + b = BOS() + + y = np.ones(10, dtype=np.float64) + y[:5] = 0 + s = b.test_score(y, 5) + assert 0 == s + + y = np.ones(9, dtype=np.float64) + y[:3] = 0 + y[6:] = 2 + s = b.test_score(y, 3) + assert_almost_equal((1/3), s) + + def test_halfSplit(self): + + b = BOS() + + y = np.ones(100, dtype=np.float64) * 4 + y[:50] = 2 + + X = np.ones((100, 1), dtype=np.float64) * 10 + X[:50] = 5 + + idx = np.array([i for i in range(100)], dtype=np.intc) + + (feat, + thresh, + left_imp, + left_idx, + right_imp, + right_idx, + improvement) = b.best_split(X, y, idx) + assert thresh == 7.5 + assert left_imp == 0 + assert right_imp == 0 + assert feat == 0 + + def test_oneOffEachEnd(self): + + b = BOS() + + y = np.ones(6, dtype=np.float64) * 4 + y[0] = 1 + + X = np.ones((6, 1), dtype=np.float64) * 10 + X[0] = 5 + + idx = np.array([i for i in range(6)], dtype=np.intc) + + (feat1, + thresh1, + left_imp1, + left_idx, + right_imp1, + right_idx, + improvement) = b.best_split(X, y, idx) + + y = np.ones(6, dtype=np.float64) * 4 + y[-1] = 1 + + X = np.ones((6, 1), dtype=np.float64) * 10 + X[-1] = 5 + + (feat2, + thresh2, + left_imp2, + left_idx, + right_imp2, + right_idx, + improvement) = b.best_split(X, y, idx) + assert feat1 == feat2 + assert thresh1 == thresh2 + assert left_imp1 + right_imp1 == left_imp2 + right_imp2 + + def test_secondFeature(self): + + b = BOS() + + y = np.ones(6, dtype=np.float64) * 4 + y[:3] = 2 + X = np.array([[10, 5, 10, 5, 10, 5]], dtype=np.float64).T + idx = np.array([i for i in range(6)], dtype=np.intc) + + (feat, + thresh, + left_imp, + left_idx, + right_imp, + right_idx, + improvement) = b.best_split(X, y, idx) + + assert 7.5 == thresh + assert 0 < left_imp + assert_almost_equal(left_imp, right_imp) + + X[:] = 8 + X[:3] = 4 + + (feat, + thresh, + left_imp, + left_idx, + right_imp, + right_idx, + improvement) = b.best_split(X, y, idx) + + assert 6 == thresh + assert 0 == left_imp + assert 0 == right_imp + + def test_largeSplit(self): + + b = BOS() + + y = np.ones(100, dtype=np.float64) * 4 + y[:50] = 2 + y[20:25] = 4 + + X = np.array([[i for i in range(100)]], dtype=np.float64).T + idx = np.array([i for i in range(100)], dtype=np.intc) + + (feat, + thresh, + left_imp, + left_idx, + right_imp, + right_idx, + improvement) = b.best_split(X, y, idx) + + # Expect a split down the middle + assert 49.5 == thresh + assert 0 == right_imp + assert_almost_equal(0.18, left_imp) + + + + + diff --git a/proglearn/tests/tree/test_sporf.py b/proglearn/tests/tree/test_sporf.py new file mode 100644 index 0000000000..3a02d4943e --- /dev/null +++ b/proglearn/tests/tree/test_sporf.py @@ -0,0 +1,152 @@ + +import numpy as np +from numpy.testing import ( + assert_almost_equal, + assert_allclose, + assert_array_equal, + assert_array_almost_equal + ) + +import pytest +from proglearn.transformers import ObliqueTreeClassifier as OTC + +from sklearn import datasets +from sklearn.metrics import accuracy_score + +""" +Sklearn test_tree.py stuff +""" +X_small = np.array([ + [0, 0, 4, 0, 0, 0, 1, -14, 0, -4, 0, 0, 0, 0, ], + [0, 0, 5, 3, 0, -4, 0, 0, 1, -5, 0.2, 0, 4, 1, ], + [-1, -1, 0, 0, -4.5, 0, 0, 2.1, 1, 0, 0, -4.5, 0, 1, ], + [-1, -1, 0, -1.2, 0, 0, 0, 0, 0, 0, 0.2, 0, 0, 1, ], + [-1, -1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, ], + [-1, -2, 0, 4, -3, 10, 4, 0, -3.2, 0, 4, 3, -4, 1, ], + [2.11, 0, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0.5, 0, -3, 1, ], + [2.11, 0, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0, 0, -2, 1, ], + [2.11, 8, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0, 0, -2, 1, ], + [2.11, 8, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0.5, 0, -1, 0, ], + [2, 8, 5, 1, 0.5, -4, 10, 0, 1, -5, 3, 0, 2, 0, ], + [2, 0, 1, 1, 1, -1, 1, 0, 0, -2, 3, 0, 1, 0, ], + [2, 0, 1, 2, 3, -1, 10, 2, 0, -1, 1, 2, 2, 0, ], + [1, 1, 0, 2, 2, -1, 1, 2, 0, -5, 1, 2, 3, 0, ], + [3, 1, 0, 3, 0, -4, 10, 0, 1, -5, 3, 0, 3, 1, ], + [2.11, 8, -6, -0.5, 0, 1, 0, 0, -3.2, 6, 0.5, 0, -3, 1, ], + [2.11, 8, -6, -0.5, 0, 1, 0, 0, -3.2, 6, 1.5, 1, -1, -1, ], + [2.11, 8, -6, -0.5, 0, 10, 0, 0, -3.2, 6, 0.5, 0, -1, -1, ], + [2, 0, 5, 1, 0.5, -2, 10, 0, 1, -5, 3, 1, 0, -1, ], + [2, 0, 1, 1, 1, -2, 1, 0, 0, -2, 0, 0, 0, 1, ], + [2, 1, 1, 1, 2, -1, 10, 2, 0, -1, 0, 2, 1, 1, ], + [1, 1, 0, 0, 1, -3, 1, 2, 0, -5, 1, 2, 1, 1, ], + [3, 1, 0, 1, 0, -4, 1, 0, 1, -2, 0, 0, 1, 0, ]]) + +y_small = [1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, + 0, 0] +y_small_reg = [1.0, 2.1, 1.2, 0.05, 10, 2.4, 3.1, 1.01, 0.01, 2.98, 3.1, 1.1, + 0.0, 1.2, 2, 11, 0, 0, 4.5, 0.201, 1.06, 0.9, 0] + +# toy sample +X = [[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1]] +y = [-1, -1, -1, 1, 1, 1] +T = [[-1, -1], [2, 2], [3, 2]] +true_result = [-1, 1, 1] + +# also load the iris dataset +# and randomly permute it +iris = datasets.load_iris() +rng = np.random.RandomState(1) +perm = rng.permutation(iris.target.size) +iris.data = iris.data[perm] +iris.target = iris.target[perm] + +# also load the diabetes dataset +# and randomly permute it +diabetes = datasets.load_diabetes() +perm = rng.permutation(diabetes.target.size) +diabetes.data = diabetes.data[perm] +diabetes.target = diabetes.target[perm] + +# Ignoring digits dataset cause it takes a minute + +def test_classification_toy(): + # Check classification on a toy dataset. + clf = OTC(random_state=0) + clf.fit(X, y) + assert_array_equal(clf.predict(T), true_result) + + """ + # Ignoring because max_features implemented differently + clf = OTC(max_features=1, random_state=0) + clf.fit(X, y) + assert_array_equal(clf.predict(T), true_result) + """ + +def test_xor(): + + # Check on a XOR problem + y = np.zeros((10, 10)) + y[:5, :5] = 1 + y[5:, 5:] = 1 + + gridx, gridy = np.indices(y.shape) + + X = np.vstack([gridx.ravel(), gridy.ravel()]).T + y = y.ravel() + + # Changing feature parameters from default 1.5 to 2 makes this test pass. + clf = OTC(random_state=0, feature_combinations=2) + clf.fit(X, y) + + assert accuracy_score(clf.predict(X), y) == 1 + +def test_iris(): + + clf = OTC(random_state=0) + + clf.fit(iris.data, iris.target) + score = accuracy_score(clf.predict(iris.data), iris.target) + assert score > 0.9 + +def test_diabetes(): + + """ + Diabetes should overfit with MSE = 0 for normal trees. + idk if this applies to sporf, so this is just a placeholder + to check consistency like iris. + """ + + clf = OTC(random_state=0) + + clf.fit(diabetes.data, diabetes.target) + score = accuracy_score(clf.predict(diabetes.data), diabetes.target) + assert score > 0.9 + +def test_probability(): + + clf = OTC(random_state=0) + + clf.fit(iris.data, iris.target) + p = clf.predict_proba(iris.data) + + assert_array_almost_equal(np.sum(p, 1), + np.ones(iris.data.shape[0])) + + assert_array_equal(np.argmax(p, 1), + clf.predict(iris.data)) + + assert_almost_equal(clf.predict_proba(iris.data), + np.exp(clf.predict_log_proba(iris.data))) + +def test_pure_set(): + + clf = OTC(random_state=0) + + X = [[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1]] + y = [1, 1, 1, 1, 1, 1] + + clf.fit(X, y) + assert_array_equal(clf.predict(X), y) + + + diff --git a/proglearn/transformers.py b/proglearn/transformers.py index 64447521a2..6ea26ff6d8 100644 --- a/proglearn/transformers.py +++ b/proglearn/transformers.py @@ -4,6 +4,7 @@ """ import keras import numpy as np +import numpy.random as rng from itertools import product from joblib import Parallel, delayed from sklearn.base import BaseEstimator @@ -13,6 +14,7 @@ from .base import BaseTransformer +from split import BaseObliqueSplitter class NeuralClassificationTransformer(BaseTransformer): """ @@ -274,8 +276,8 @@ class SplitInfo: than this threshold for the feature of this split then it will go to the left child, otherwise it wil go the right child where these children are the children nodes of the node for which this split defines. - proj_mat : array of shape [n_components, n_features] - The sparse random projection matrix for this split. + proj_vec : array of shape [n_features] + The vector of the sparse random projection matrix relevant for the split. left_impurity : float This is Gini impurity of left side of the split. left_idx : array of shape [left_n_samples] @@ -299,7 +301,7 @@ def __init__( self, feature, threshold, - proj_mat, + proj_vec, left_impurity, left_idx, left_n_samples, @@ -312,7 +314,7 @@ def __init__( self.feature = feature self.threshold = threshold - self.proj_mat = proj_mat + self.proj_vec = proj_vec self.left_impurity = left_impurity self.left_idx = left_idx self.left_n_samples = left_n_samples @@ -335,13 +337,13 @@ class ObliqueSplitter: values for each of the features. y : array of shape [n_samples] The labels for each of the examples in X. - proj_dims : int - The dimensionality of the target projection space. - density : float - Ratio of non-zero component in the random projection matrix in the range '(0, 1]'. + max_features : float + controls the dimensionality of the target projection space. + feature_combinations : float + controls the density of the projection matrix random_state : int Controls the pseudo random number generator used to generate the projection matrix. - workers : int + n_jobs : int The number of cores to parallelize the calculation of Gini impurity. Supply -1 to use all cores available to the Process. @@ -362,21 +364,44 @@ class ObliqueSplitter: Determines the best possible split for the given set of samples. """ - def __init__(self, X, y, proj_dims, density, random_state, workers): + def __init__(self, X, y, max_features, feature_combinations, random_state, n_jobs): + + self.X = np.array(X, dtype=np.float64) + + # y must be 1D + self.y = np.array(y, dtype=np.float64).squeeze() - self.X = X - self.y = y + classes = np.array(np.unique(y), dtype=int) + self.n_classes = len(classes) + self.class_indices = {j:i for i, j in enumerate(classes)} - self.classes = np.array(np.unique(y), dtype=int) - self.n_classes = len(self.classes) - self.indices = np.indices(y.shape)[0] + self.indices = np.indices(self.y.shape)[0] - self.n_samples = X.shape[0] + self.n_samples = self.X.shape[0] + self.n_features = self.X.shape[1] - self.proj_dims = proj_dims - self.density = density self.random_state = random_state - self.workers = workers + rng.seed(random_state) + + # Compute root impurity + unique, count = np.unique(y, return_counts=True) + count = count / len(y) + self.root_impurity = 1 - np.sum(np.power(count, 2)) + + # proj_dims = d = mtry + self.proj_dims = max(int(max_features * self.n_features), 1) + + # feature_combinations = mtry_mult + # In processingNodeBin.h, mtryDensity = int(mtry * mtry_mult) + self.n_non_zeros = max(int(self.proj_dims * feature_combinations), 1) + + # Base oblique splitter in cython + self.BOS = BaseObliqueSplitter() + + # Temporary debugging parameter, turns off oblique splits + self.debug = False + + self.n_jobs = n_jobs def sample_proj_mat(self, sample_inds): """ @@ -394,14 +419,21 @@ def sample_proj_mat(self, sample_inds): proj_X : {ndarray, sparse matrix} of shape (n_samples, self.proj_dims) Projected input data matrix. """ - - proj_mat = SparseRandomProjection( - density=self.density, - n_components=self.proj_dims, - random_state=self.random_state, - ) - - proj_X = proj_mat.fit_transform(self.X[sample_inds, :]) + + if self.debug: + return self.X[sample_inds, :], np.eye(self.n_features) + + # This is the way its done in the C++ + proj_mat = np.zeros((self.n_features, self.proj_dims)) + rand_feat = rng.randint(self.n_features, size=self.n_non_zeros) + rand_dim = rng.randint(self.proj_dims, size=self.n_non_zeros) + weights = [1 if rng.rand() > 0.5 else -1 for i in range(self.n_non_zeros)] + proj_mat[rand_feat, rand_dim] = weights + + # Need to remove zero vectors from projmat + proj_mat = proj_mat[:, np.unique(rand_dim)] + + proj_X = self.X[sample_inds, :] @ proj_mat return proj_X, proj_mat def leaf_label_proba(self, idx): @@ -426,114 +458,18 @@ def leaf_label_proba(self, idx): samples = self.y[idx] n = len(samples) labels, count = np.unique(samples, return_counts=True) - most = np.argmax(count) + + proba = np.zeros(self.n_classes) + for i, l in enumerate(labels): + class_idx = self.class_indices[l] + proba[class_idx] = count[i] / n + most = np.argmax(count) label = labels[most] - proba = count[most] / n + #max_proba = count[most] / n return label, proba - # Returns gini impurity for split - # Expects 0 < t < n - def score(self, y_sort, t): - """ - Finds the Gini impurity for the split of interest - - Parameters - ---------- - y_sort : array of shape [n_samples] - A sorted array of labels for the examples for which the Gini impurity - is being calculated. - t : float - The threshold determining where to split y_sort. - - Returns - ------- - gini : float - The Gini impurity of the split. - """ - - left = y_sort[:t] - right = y_sort[t:] - - n_left = len(left) - n_right = len(right) - - left_unique, left_counts = np.unique(left, return_counts=True) - right_unique, right_counts = np.unique(right, return_counts=True) - - left_counts = left_counts / n_left - right_counts = right_counts / n_right - - left_gini = 1 - np.sum(np.power(left_counts, 2)) - right_gini = 1 - np.sum(np.power(right_counts, 2)) - - gini = (n_left / self.n_samples) * left_gini + ( - n_right / self.n_samples - ) * right_gini - return gini - - def _score(self, proj_X, y_sample, i, j): - """ - Handles array indexing before calculating Gini impurity - - Parameters - ---------- - proj_X : {ndarray, sparse matrix} of shape (n_samples, self.proj_dims) - Projected input data matrix. - y_sample : array of shape [n_samples] - Labels for sample of data. - i : float - The threshold determining where to split y_sort. - j : float - The projection dimension to consider. - - Returns - ------- - gini : float - The Gini impurity of the split. - i : float - The threshold determining where to split y_sort. - j : float - The projection dimension to consider. - """ - # Sort labels by the jth feature - idx = np.argsort(proj_X[:, j]) - y_sort = y_sample[idx] - - gini = self.score(y_sort, i) - - return gini, i, j - - # Returns impurity for a group of examples - # expects idx not None - def impurity(self, idx): - """ - Finds the actual impurity for a set of samples - - Parameters - ---------- - idx : array of shape [n_samples] - The indices of the nodes in the set for which the impurity is being calculated. - - Returns - ------- - impurity : float - Actual impurity of split. - """ - - samples = self.y[idx] - n = len(samples) - - if n == 0: - return 0 - - unique, count = np.unique(samples, return_counts=True) - count = count / n - gini = np.sum(np.power(count, 2)) - - return 1 - gini - # Finds the best split def split(self, sample_inds): """ @@ -549,61 +485,48 @@ def split(self, sample_inds): split_info : SplitInfo Class holding information about the split. """ + # ensure that sample indices are 1D + sample_inds = sample_inds.squeeze() + + if not self.y.squeeze().ndim == 1: + raise RuntimeError('Does not support multivariate output yet.') # Project the data proj_X, proj_mat = self.sample_proj_mat(sample_inds) y_sample = self.y[sample_inds] n_samples = len(sample_inds) - # Score matrix - # No split score is just node impurity - Q = np.zeros((n_samples, self.proj_dims)) - node_impurity = self.impurity(sample_inds) - Q[0, :] = node_impurity - Q[-1, :] = node_impurity - - # Loop through examples and projected features to calculate split scores - split_iterator = product(range(1, n_samples - 1), range(self.proj_dims)) - scores = Parallel(n_jobs=self.workers)( - delayed(self._score)(proj_X, y_sample, i, j) for i, j in split_iterator - ) - for gini, i, j in scores: - Q[i, j] = gini - - # Identify best split feature, minimum gini impurity - best_split_ind = np.argmin(Q) - thresh_i, feature = np.unravel_index(best_split_ind, Q.shape) - best_gini = Q[thresh_i, feature] - - # Sort samples by the split feature - feat_vec = proj_X[:, feature] - idx = np.argsort(feat_vec) - - feat_vec = feat_vec[idx] - sample_inds = sample_inds[idx] - - # Get the threshold, split samples into left and right - threshold = feat_vec[thresh_i] - left_idx = sample_inds[:thresh_i] - right_idx = sample_inds[thresh_i:] - + # Assign types to everything + # TODO: assign types when making the splitter class. This is silly. + proj_X = np.array(proj_X, dtype=np.float64) + y_sample = np.array(y_sample, dtype=np.float64) + sample_inds = np.array(sample_inds, dtype=np.intc) + # print(proj_X.shape, y_sample.shape, sample_inds.shape) + + # Call cython splitter + (feature, + threshold, + left_impurity, + left_idx, + right_impurity, + right_idx, + improvement) = self.BOS.best_split(proj_X, y_sample, sample_inds) + + # TODO: These are coming out as memory views. Fix this. + left_idx = np.asarray(left_idx) + right_idx = np.asarray(right_idx) + left_n_samples = len(left_idx) right_n_samples = len(right_idx) - # See if we have no split no_split = left_n_samples == 0 or right_n_samples == 0 - # Evaluate improvement - improvement = node_impurity - best_gini - - # Evaluate impurities for left and right children - left_impurity = self.impurity(left_idx) - right_impurity = self.impurity(right_idx) + proj_vec = proj_mat[:, feature] split_info = SplitInfo( feature, threshold, - proj_mat, + proj_vec, left_impurity, left_idx, left_n_samples, @@ -645,7 +568,7 @@ def __init__(self): self.impurity = None self.n_samples = None - self.proj_mat = None + self.proj_vec = None self.label = None self.proba = None @@ -724,9 +647,6 @@ def __init__( ): # Tree parameters - # self.n_samples = n_samples - # self.n_features = n_features - # self.n_classes = n_classes self.depth = 0 self.node_count = 0 self.nodes = [] @@ -748,7 +668,7 @@ def add_node( is_leaf, feature, threshold, - proj_mat, + proj_vec, label, proba, ): @@ -774,8 +694,8 @@ def add_node( to this node's left of right child. If a sample has a value less than the threshold (for the feature of this node) it will go to the left childe, otherwise it will go the right child. - proj_mat : {ndarray, sparse matrix} of shape (n_samples, n_features) - Projection matrix for this new node. + proj_vec : {ndarray, sparse matrix} of shape (n_features) + Projection vector for this new node. label : int The label a sample will be given if it is predicted to be at this node. proba : float @@ -809,7 +729,7 @@ def add_node( node.is_leaf = False node.feature = feature node.threshold = threshold - node.proj_mat = proj_mat + node.proj_vec = proj_vec self.node_count += 1 self.nodes.append(node) @@ -835,7 +755,7 @@ def build(self): 0, 1, False, - self.splitter.impurity(self.splitter.indices), + self.splitter.root_impurity, self.splitter.indices, self.splitter.n_samples, ) @@ -892,7 +812,7 @@ def build(self): is_leaf, split.feature, split.threshold, - split.proj_mat, + split.proj_vec, None, None, ) @@ -942,8 +862,10 @@ def predict(self, X): for i in range(X.shape[0]): cur = self.nodes[0] while cur is not None and not cur.is_leaf: - proj_X = cur.proj_mat.transform(X) - if proj_X[i, cur.feature] < cur.threshold: + + proj_X = X[i] @ cur.proj_vec + + if proj_X < cur.threshold: id = cur.left_child cur = self.nodes[id] else: @@ -980,9 +902,9 @@ class ObliqueTreeClassifier(BaseEstimator): Minimum Gini impurity value that must be achieved for a split to occur on the node. feature_combinations : float The feature combinations to use for the oblique split. - density : float - Density estimate. - workers : int, optional (default: -1) + max_features : float + Output dimension = max_features * dimension + n_jobs : int, optional (default: -1) The number of cores to parallelize the calculation of Gini impurity. Supply -1 to use all cores available to the Process. @@ -1003,41 +925,35 @@ class ObliqueTreeClassifier(BaseEstimator): def __init__( self, *, - # criterion="gini", - # splitter=None, max_depth=np.inf, min_samples_split=2, min_samples_leaf=1, - # min_weight_fraction_leaf=0, - # max_features="auto", - # max_leaf_nodes=None, random_state=None, min_impurity_decrease=0, min_impurity_split=0, - # class_weight=None, - # ccp_alpha=0.0, - # New args feature_combinations=1.5, - density=0.5, - workers=-1, + max_features=1, + n_jobs=1 ): - # self.criterion=criterion + # RF parameters self.max_depth = max_depth self.min_samples_split = min_samples_split self.min_samples_leaf = min_samples_leaf - # self.min_weight_fraction_leaf=min_weight_fraction_leaf - # self.max_features=max_features - # self.max_leaf_nodes=max_leaf_nodes self.random_state = random_state self.min_impurity_decrease = min_impurity_decrease self.min_impurity_split = min_impurity_split - # self.class_weight=class_weight - # self.ccp_alpha=ccp_alpha + # OF parameters + # Feature combinations = L self.feature_combinations = feature_combinations - self.density = density - self.workers = workers + + # Max features + self.max_features = max_features + + self.n_jobs=n_jobs + + self.n_classes=None def fit(self, X, y): """ @@ -1056,10 +972,10 @@ def fit(self, X, y): The fit classifier. """ - self.proj_dims = int(np.ceil(X.shape[1]) / self.feature_combinations) splitter = ObliqueSplitter( - X, y, self.proj_dims, self.density, self.random_state, self.workers + X, y, self.max_features, self.feature_combinations, self.random_state, self.n_jobs ) + self.n_classes = splitter.n_classes self.tree = ObliqueTree( splitter, @@ -1070,6 +986,7 @@ def fit(self, X, y): self.min_impurity_decrease, ) self.tree.build() + return self def apply(self, X): @@ -1105,6 +1022,8 @@ def predict(self, X): The predictions (labels) for each testing sample. """ + X = np.array(X, dtype=np.float64) + preds = np.zeros(X.shape[0]) pred_nodes = self.apply(X) for k in range(len(pred_nodes)): @@ -1128,7 +1047,9 @@ def predict_proba(self, X): The probabilities of the predictions (labels) for each testing sample. """ - preds = np.zeros(X.shape[0]) + X = np.array(X, dtype=np.float64) + + preds = np.zeros((X.shape[0], self.n_classes)) pred_nodes = self.apply(X) for k in range(len(preds)): id = pred_nodes[k] @@ -1152,7 +1073,6 @@ def predict_log_proba(self, X): """ proba = self.predict_proba(X) - for k in range(len(proba)): - proba[k] = np.log(proba[k]) + return np.log(proba) + - return proba diff --git a/proglearn/tree/morf.py b/proglearn/tree/morf.py new file mode 100644 index 0000000000..f7213fbdca --- /dev/null +++ b/proglearn/tree/morf.py @@ -0,0 +1,86 @@ +from sklearn.ensemble._forest import ForestClassifier + +from .morf_tree import Conv2DObliqueTreeClassifier + + +class Conv2DObliqueForestClassifier(ForestClassifier): + def __init__( + self, + n_estimators=100, + # criterion="gini", + max_depth=None, + min_samples_split=2, + min_samples_leaf=1, + # min_weight_fraction_leaf=0., + max_features="auto", + # max_leaf_nodes=None, + # min_impurity_decrease=0., + # min_impurity_split=None, + bootstrap=True, + oob_score=False, + n_jobs=None, + random_state=None, + verbose=0, + warm_start=False, + class_weight=None, + # ccp_alpha=0.0, + max_samples=None, + image_height=None, + image_width=None, + patch_height_max=None, + patch_height_min=1, + patch_width_max=None, + patch_width_min=1, + discontiguous_height=False, + discontiguous_width=False, + ): + super().__init__( + base_estimator=Conv2DObliqueTreeClassifier(), + n_estimators=n_estimators, + estimator_params=( + "max_depth", + "min_samples_split", + "min_samples_leaf", + # "min_weight_fraction_leaf", + "max_features", + # "max_leaf_nodes", + # "min_impurity_decrease", "min_impurity_split", + "random_state", + "image_height", + "image_width", + "patch_height_max", + "patch_height_min", + "patch_width_max", + "patch_width_max", + "discontiguous_height", + "discontiguous_width", + ), + bootstrap=bootstrap, + oob_score=oob_score, + n_jobs=n_jobs, + random_state=random_state, + verbose=verbose, + warm_start=warm_start, + class_weight=class_weight, + max_samples=max_samples, + ) + + # self.criterion = criterion + self.max_depth = max_depth + self.min_samples_split = min_samples_split + self.min_samples_leaf = min_samples_leaf + # self.min_weight_fraction_leaf = min_weight_fraction_leaf + self.max_features = max_features + # self.max_leaf_nodes = max_leaf_nodes + # self.min_impurity_decrease = min_impurity_decrease + # self.min_impurity_split = min_impurity_split + + # s-rerf params + self.discontiguous_height = discontiguous_height + self.discontiguous_width = discontiguous_width + self.image_height = image_height + self.image_width = image_width + self.patch_height_max = patch_height_max + self.patch_height_min = patch_height_min + self.patch_width_max = patch_width_max + self.patch_width_min = patch_width_min \ No newline at end of file diff --git a/proglearn/tree/morf_split.py b/proglearn/tree/morf_split.py new file mode 100644 index 0000000000..9f1af312da --- /dev/null +++ b/proglearn/tree/morf_split.py @@ -0,0 +1,225 @@ +import numpy as np +import numpy.random as rng + +from scipy.sparse import issparse +from sklearn.base import is_classifier +from sklearn.tree import _tree +from sklearn.utils import check_random_state + +from split import BaseObliqueSplitter +from proglearn.transformers import (ObliqueSplitter, ObliqueTree, + ObliqueTreeClassifier) + + +class Conv2DSplitter(ObliqueSplitter): + """Convolutional splitter. + + A class used to represent a 2D convolutional splitter, where splits + are done on a convolutional patch. + + Note: The convolution function is currently just the + summation operator. + + Parameters + ---------- + X : array of shape [n_samples, n_features] + The input data X is a matrix of the examples and their respective feature + values for each of the features. + y : array of shape [n_samples] + The labels for each of the examples in X. + max_features : float + controls the dimensionality of the target projection space. + feature_combinations : float + controls the density of the projection matrix + random_state : int + Controls the pseudo random number generator used to generate the projection matrix. + image_height : int, optional (default=None) + MORF required parameter. Image height of each observation. + image_width : int, optional (default=None) + MORF required parameter. Width of each observation. + patch_height_max : int, optional (default=max(2, floor(sqrt(image_height)))) + MORF parameter. Maximum image patch height to randomly select from. + If None, set to ``max(2, floor(sqrt(image_height)))``. + patch_height_min : int, optional (default=1) + MORF parameter. Minimum image patch height to randomly select from. + patch_width_max : int, optional (default=max(2, floor(sqrt(image_width)))) + MORF parameter. Maximum image patch width to randomly select from. + If None, set to ``max(2, floor(sqrt(image_width)))``. + patch_width_min : int (default=1) + MORF parameter. Minimum image patch height to randomly select from. + discontiguous_height : bool, optional (defaul=False) + Whether or not the rows of the patch are taken discontiguously or not. + discontiguous_width : bool, optional (default=False) + Whether or not the columns of the patch are taken discontiguously or not. + + Methods + ------- + sample_proj_mat + Will compute projection matrix, which has columns as the vectorized + convolutional patches. + + Notes + ----- + This class assumes that data is vectorized in + row-major (C-style), rather then column-major (Fotran-style) order. + """ + + def __init__( + self, + X, + y, + max_features, + feature_combinations, + random_state, + image_height=None, + image_width=None, + patch_height_max=None, + patch_height_min=1, + patch_width_max=None, + patch_width_min=1, + discontiguous_height: bool = False, + discontiguous_width: bool = False, + debug: bool = False, + ): + super(Conv2DSplitter, self).__init__( + X=X, + y=y, + max_features=max_features, + feature_combinations=feature_combinations, + random_state=random_state, + ) + # set sample dimensions + self.image_height = image_height + self.image_width = image_width + self.patch_height_max = patch_height_max + self.patch_width_max = patch_width_max + self.patch_height_min = patch_height_min + self.patch_width_min = patch_width_min + self.axis_sample_dims = [ + (patch_height_min, patch_height_max), + (patch_width_min, patch_width_max), + ] + self.structured_data_shape = [image_height, image_width] + self.discontiguous_height = discontiguous_height + self.disontiguous_width = discontiguous_width + self.debug = debug + + def _get_rand_patch_idx(self, rand_height, rand_width): + """Generates a random patch on the original data to consider as feature combination. + + This function assumes that data samples were vectorized. Thus contiguous convolutional + patches are defined based on the top left corner. If the convolutional patch + is "discontiguous", then any random point can be chosen. + + TODO: + - refactor to optimize for discontiguous and contiguous case + - currently pretty slow because being constructed and called many times + + Returns + ------- + height_width_top : tuple of (height, width, topleft point) + [description] + """ + # XXX: results in edge effect on the RHS of the structured data... + # compute the difference between the image dimension and current random + # patch dimension + delta_height = self.image_height - rand_height + 1 + delta_width = self.image_width - rand_width + 1 + + # sample the top left pixel from available pixels now + top_left_point = rng.randint(delta_width * delta_height) + + # convert the top left point to appropriate index in full image + vectorized_start_idx = int( + (top_left_point % delta_width) + + (self.image_width * np.floor(top_left_point / delta_width)) + ) + + # get the (x_1, x_2) coordinate in 2D array of sample + multi_idx = self._compute_vectorized_index_in_data(vectorized_start_idx) + + if self.debug: + print(vec_idx, multi_idx, rand_height, rand_width) + + # get random row and column indices + if self.discontiguous_height: + row_idx = np.random.choice( + self.image_height, size=rand_height, replace=False + ) + else: + row_idx = np.arange(multi_idx[0], multi_idx[0] + rand_height) + if self.disontiguous_width: + col_idx = np.random.choice(self.image_width, size=rand_width, replace=False) + else: + col_idx = np.arange(multi_idx[1], multi_idx[1] + rand_width) + + # create index arrays in the 2D image + structured_patch_idxs = np.ix_( + row_idx, + col_idx, + ) + + # get the patch vectorized indices + patch_idxs = self._compute_index_in_vectorized_data(structured_patch_idxs) + + return patch_idxs + + def _compute_index_in_vectorized_data(self, idx): + return np.ravel_multi_index( + idx, dims=self.structured_data_shape, mode="raise", order="C" + ) + + def _compute_vectorized_index_in_data(self, vec_idx): + return np.unravel_index(vec_idx, shape=self.structured_data_shape, order="C") + + def sample_proj_mat(self, sample_inds): + """ + Gets the projection matrix and it fits the transform to the samples of interest. + + Parameters + ---------- + sample_inds : array of shape [n_samples] + The data we are transforming. + + Returns + ------- + proj_mat : {ndarray, sparse matrix} of shape (self.proj_dims, n_features) + The generated sparse random matrix. + proj_X : {ndarray, sparse matrix} of shape (n_samples, self.proj_dims) + Projected input data matrix. + + Notes + ----- + See `randMatTernary` in rerf.py for SPORF. + + See `randMat + """ + # creates a projection matrix where columns are vectorized patch + # combinations + proj_mat = np.zeros((self.n_features, self.proj_dims)) + + # generate random heights and widths of the patch. Note add 1 because numpy + # needs is exclusive of the high end of interval + rand_heights = rng.randint( + self.patch_height_min, self.patch_height_max + 1, size=self.proj_dims + ) + rand_widths = rng.randint( + self.patch_width_min, self.patch_width_max + 1, size=self.proj_dims + ) + + # loop over mtry to load random patch dimensions and the + # top left position + # Note: max_features is aka mtry + for idx in range(self.proj_dims): + rand_height = rand_heights[idx] + rand_width = rand_widths[idx] + # get patch positions + patch_idxs = self._get_rand_patch_idx( + rand_height=rand_height, rand_width=rand_width + ) + + # get indices for this patch + proj_mat[patch_idxs, idx] = 1 + + proj_X = self.X[sample_inds, :] @ proj_mat + return proj_X, proj_mat diff --git a/proglearn/tree/morf_tree.py b/proglearn/tree/morf_tree.py new file mode 100644 index 0000000000..96ebbc2bbc --- /dev/null +++ b/proglearn/tree/morf_tree.py @@ -0,0 +1,473 @@ +import numpy as np +import numpy.random as rng +from scipy.sparse import issparse +from sklearn.base import is_classifier +from sklearn.tree import _tree +from sklearn.utils import check_random_state +import numbers + +from split import BaseObliqueSplitter +from proglearn.transformers import ObliqueSplitter, ObliqueTreeClassifier, ObliqueTree + +from .morf_split import Conv2DSplitter + +# ============================================================================= +# Types and constants +# ============================================================================= + +DTYPE = _tree.DTYPE +DOUBLE = _tree.DOUBLE + + +def _check_symmetric(a, rtol=1e-05, atol=1e-08): + return np.allclose(a, a.T, rtol=rtol, atol=atol) + + +class Conv2DObliqueTreeClassifier(ObliqueTreeClassifier): + """[summary] + + Parameters + ---------- + n_estimators : int, optional + [description], by default 500 + max_depth : [type], optional + [description], by default None + min_samples_split : int, optional + [description], by default 1 + min_samples_leaf : int, optional + [description], by default 1 + min_impurity_decrease : int, optional + [description], by default 0 + min_impurity_split : int, optional + [description], by default 0 + feature_combinations : float, optional + [description], by default 1.5 + max_features : int, optional + [description], by default 1 + image_height : int, optional (default=None) + MORF required parameter. Image height of each observation. + image_width : int, optional (default=None) + MORF required parameter. Width of each observation. + patch_height_max : int, optional (default=max(2, floor(sqrt(image_height)))) + MORF parameter. Maximum image patch height to randomly select from. + If None, set to ``max(2, floor(sqrt(image_height)))``. + patch_height_min : int, optional (default=1) + MORF parameter. Minimum image patch height to randomly select from. + patch_width_max : int, optional (default=max(2, floor(sqrt(image_width)))) + MORF parameter. Maximum image patch width to randomly select from. + If None, set to ``max(2, floor(sqrt(image_width)))``. + patch_width_min : int (default=1) + MORF parameter. Minimum image patch height to randomly select from. + discontiguous_height : bool, optional (defaul=False) + Whether or not the rows of the patch are taken discontiguously or not. + discontiguous_width : bool, optional (default=False) + Whether or not the columns of the patch are taken discontiguously or not. + bootstrap : bool, optional + [description], by default True + n_jobs : [type], optional + [description], by default None + random_state : [type], optional + [description], by default None + warm_start : bool, optional + [description], by default False + verbose : int, optional + [description], by default 0 + """ + + def __init__( + self, + *, + max_depth=np.inf, + min_samples_split=1, + min_samples_leaf=1, + min_impurity_decrease=0, + min_impurity_split=0, + feature_combinations=1.5, + max_features=1, + image_height=None, + image_width=None, + patch_height_max=None, + patch_height_min=1, + patch_width_max=None, + patch_width_min=1, + discontiguous_height=False, + discontiguous_width=False, + bootstrap=True, + random_state=None, + warm_start=False, + verbose=0, + ): + super(Conv2DObliqueTreeClassifier, self).__init__( + max_depth=max_depth, + min_samples_split=min_samples_split, + min_samples_leaf=min_samples_leaf, + min_impurity_decrease=min_impurity_decrease, + feature_combinations=feature_combinations, + max_features=max_features, + random_state=random_state, + ) + + # refactor these to go inside ObliqueTreeClassifier + self.bootstrap = bootstrap + self.warm_start = warm_start + self.verbose = verbose + + # s-rerf params + self.discontiguous_height = discontiguous_height + self.discontiguous_width = discontiguous_width + self.image_height = image_height + self.image_width = image_width + self.patch_height_max = patch_height_max + self.patch_height_min = patch_height_min + self.patch_width_max = patch_width_max + self.patch_width_min = patch_width_min + + def _check_patch_params(self): + # Check that image_height and image_width are divisors of + # the num_features. This is the most we can do to + # prevent an invalid value being passed in. + if (self.n_features_ % self.image_height) != 0: + raise ValueError("Incorrect image_height given:") + else: + self.image_height_ = self.image_height + if (self.n_features_ % self.image_width) != 0: + raise ValueError("Incorrect image_width given:") + else: + self.image_width_ = self.image_width + + # If patch_height_{min, max} and patch_width_{min, max} are + # not set by the user, set them to defaults. + if self.patch_height_max is None: + self.patch_height_max_ = max(2, np.floor(np.sqrt(self.image_height_))) + else: + self.patch_height_max_ = self.patch_height_max + if self.patch_width_max is None: + self.patch_width_max_ = max(2, np.floor(np.sqrt(self.image_width_))) + else: + self.patch_width_max_ = self.patch_width_max + if 1 <= self.patch_height_min <= self.patch_height_max_: + self.patch_height_min_ = self.patch_height_min + else: + raise ValueError("Incorrect patch_height_min") + if 1 <= self.patch_width_min <= self.patch_width_max_: + self.patch_width_min_ = self.patch_width_min + else: + raise ValueError("Incorrect patch_width_min") + + def fit(self, X, y, sample_weight=None, check_input=True, X_idx_sorted=None): + # check random state - sklearn + random_state = check_random_state(self.random_state) + + # check_X_params = dict(dtype=DTYPE, accept_sparse="csc") + # check_y_params = dict(ensure_2d=False, dtype=None) + # X, y = self._validate_data( + # X, y, validate_separately=(check_X_params, check_y_params) + # ) + # if issparse(X): + # X.sort_indices() + + # if X.indices.dtype != np.intc or X.indptr.dtype != np.intc: + # raise ValueError( + # "No support for np.int64 index based " "sparse matrices" + # ) + + # Determine output settings + n_samples, self.n_features_ = X.shape + self.n_features_in_ = self.n_features_ + + # check patch parameters + self._check_patch_params() + + # mark if tree is used for classification + is_classification = is_classifier(self) + + y = np.atleast_1d(y) + expanded_class_weight = None + + # Check parameters + self.max_depth = ((2 ** 31) - 1 if self.max_depth is None + else self.max_depth) + # self.max_leaf_nodes = (-1 if self.max_leaf_nodes is None + # else self.max_leaf_nodes) + + if isinstance(self.max_features, str): + if self.max_features == "auto": + if is_classification: + max_features = max(1, int(np.sqrt(self.n_features_))) + else: + max_features = self.n_features_ + elif self.max_features == "sqrt": + max_features = max(1, int(np.sqrt(self.n_features_))) + elif self.max_features == "log2": + max_features = max(1, int(np.log2(self.n_features_))) + else: + raise ValueError( + 'Invalid value for max_features. Allowed string ' + 'values are "auto", "sqrt" or "log2".') + elif self.max_features is None: + max_features = self.n_features_ + elif isinstance(self.max_features, numbers.Integral): + max_features = self.max_features + else: # float + if self.max_features > 0.0: + max_features = max(1, + int(self.max_features * self.n_features_)) + else: + max_features = 0 + self.max_features = max_features + + # note that this is done in scikit-learn, but results in a matrix + # multiplication error because y is 2D + if y.ndim == 1: + # reshape is necessary to preserve the data contiguity against vs + # [:, np.newaxis] that does not. + y = np.reshape(y, (-1, 1)) + self.n_outputs_ = y.shape[1] + + # create the splitter + splitter = Conv2DSplitter( + X, + y, + self.max_features, + self.feature_combinations, + self.random_state, + self.image_height_, + self.image_width_, + self.patch_height_max_, + self.patch_height_min_, + self.patch_width_max_, + self.patch_width_min_, + self.discontiguous_height, + self.discontiguous_width, + ) + + # create the Oblique tree + self.tree = ObliqueTree( + splitter, + self.min_samples_split, + self.min_samples_leaf, + self.max_depth, + self.min_impurity_split, + self.min_impurity_decrease, + ) + self.tree.build() + return self + + +# XXX: refactor to: convolutional splitter, graph splitter +class MorfObliqueSplitter(ObliqueSplitter): + """ + A class used to represent a manifold oblique splitter, where splits are done on + the linear combination of the features. This class of splitter + overrides the sampled projection matrix. + + The manifolds in consideration are: + + 1. multivariate data: X is of the form (X1, X2, ...), where each Xi is of + dimension d(i). + 2. graph data: X is of the form G, where G is a class of graphs. + + There are two ways of + + Parameters + ---------- + X : array of shape [n_samples, n_features] + The input data X is a matrix of the examples and their respective feature + values for each of the features. + y : array of shape [n_samples] + The labels for each of the examples in X. + max_features : float + controls the dimensionality of the target projection space. + feature_combinations : float + controls the density of the projection matrix + random_state : int + Controls the pseudo random number generator used to generate the projection matrix. + n_jobs : int + The number of cores to parallelize the calculation of Gini impurity. + Supply -1 to use all cores available to the Process. + + Methods + ------- + _compute_vectorized_index_in_data(vec_idx) + Will compute the corresponding index in the data based on + an index chosen in the vectorized data. + + Notes + ----- + This class assumes that data is vectorized in + row-major (C-style), rather then column-major (Fotran-style) order. + """ + + def __init__( + self, + X, + y, + axis_sample_dims: list, + max_features, + feature_combinations, + random_state, + n_jobs, + axis_sample_graphs: list = None, + axis_data_dims: list = None, + ): + super(ManifoldObliqueTreeClassifier, self).__init__( + X=X, + y=y, + max_features=max_features, + feature_combinations=feature_combinations, + random_state=random_state, + n_jobs=n_jobs, + ) + + if axis_sample_graphs is None and axis_data_dims is None: + raise RuntimeError( + "Either the sample graph must be instantiated, or " + "the data dimensionality must be specified. Both are not right now." + ) + + # error check sampling graphs + if axis_sample_graphs is not None: + # perform error check on the passes in sample graphs and dimensions + if len(axis_sample_graphs) != len(axis_sample_dims): + raise ValueError( + f"The number of sample graphs \ + ({len(axis_sample_graphs)}) must match \ + the number of sample dimensions ({len(axis_sample_dims)}) in MORF." + ) + if not all([x.ndim == 2 for x in axis_sample_graphs]): + raise ValueError( + f"All axis sample graphs must be \ + 2D matrices." + ) + if not all([x.shape[0] == x.shape[1] for x in axis_sample_graphs]): + raise ValueError(f"All axis sample graphs must be " "square matrices.") + + # XXX: could later generalize to remove this condition + if not all([_check_symmetric(x) for x in axis_sample_graphs]): + raise ValueError("All axis sample graphs must" "be symmetric.") + + # error check data dimensions + if axis_data_dims is not None: + # perform error check on the passes in sample graphs and dimensions + if len(axis_data_dims) != len(axis_sample_dims): + raise ValueError( + f"The number of data dimensions " + "({len(axis_data_dims)}) must match " + "the number of sample dimensions ({len(axis_sample_dims)}) in MORF." + ) + + if X.shape[1] != np.sum(axis_data_dims): + raise ValueError( + f"The specified data dimensionality " + "({np.sum(axis_data_dims)}) does not match the dimensionality " + "of the data (i.e. # columns in X: {X.shape[1]})." + ) + + self.axis_sample_dims = axis_sample_dims + self.axis_sample_graphs = axis_sample_graphs + self.structured_data_shape = [] + + if axis_sample_graphs is not None: + for graph in self.axis_sample_graphs: + self.structured_data_shape.append(graph.shape[0]) + else: + self.structured_data_shape.extend(axis_data_dims) + + self.proj_dims = int(max_features * self.n_features) + + def _compute_index_in_vectorized_data(self, idx): + return np.ravel_multi_index( + idx, dims=self.structured_data_shape, mode="raise", order="C" + ) + + def _compute_vectorized_index_in_data(self, vec_idx): + return np.unravel_index(vec_idx, shape=self.structured_data_shape, order="C") + + def _get_rand_patch_idx(self): + """Generates""" + rnd_mtry = 0 + rnd_feature = 0 + rnd_weight = 0 + + # stores vectorized_pivot_point corresponding to patches to take + patch_vectorized_indices = [] + + # stores the dimensions of corresponding patches to take about the + # vectorized feature point + patch_dims = [] + + # loop over mtry to load random patch dimensions and the + # top left position + # Note: max_features is aka mtry + for idx in range(self.max_features): + # pick random patch size for each axis of data + axis_sample_lens = [] + delta_axis_lens = [] + axis_start_idx = [] + + for idx, (axis_min_len, axis_max_len) in enumerate(self.axis_sample_dims): + # sample from [axis_patchMin, axis_patchMax] + axis_sample_len = rng.randint(low=axis_min_len, high=axis_max_len) + axis_sample_lens.append(axis_sample_len) + + # compute area that sample can occur at + delta_len = self.structured_data_shape[idx] - axis_sample_len + delta_axis_lens.append(delta_len) + + # compute random position in this axis to generate the pivot point + this_axis_pivot_idx = rng.randint(0, delta_len) + axis_start_idx.append(this_axis_pivot_idx) + + # compute the vectorized points at which these patches start + vectorized_idx = self._compute_index_in_vectorized_data(axis_start_idx) + patch_vectorized_indices.append(start_idx) + patch_dims.append(axis_sample_lens) + pass + + def sample_proj_mat(self, sample_inds): + """ + Gets the projection matrix and it fits the transform to the samples of interest. + + Parameters + ---------- + sample_inds : array of shape [n_samples] + The data we are transforming. + + Returns + ------- + proj_mat : {ndarray, sparse matrix} of shape (self.proj_dims, n_features) + The generated sparse random matrix. + proj_X : {ndarray, sparse matrix} of shape (n_samples, self.proj_dims) + Projected input data matrix. + + Notes + ----- + See `randMatTernary` in rerf.py for SPORF. + + See `randMat + """ + + if self.debug: + return self.X[sample_inds, :], np.eye(self.n_features) + + # Edge case for fully dense matrix + if self.density == 1: + proj_mat = rng.binomial(1, 0.5, (self.n_features, self.proj_dims)) * 2 - 1 + + # Sample random matrix and build it on that + else: + + proj_mat = rng.rand(self.n_features, self.proj_dims) + prob = 0.5 * self.density + + neg_inds = np.where(proj_mat <= prob) + pos_inds = np.where(proj_mat >= 1 - prob) + mid_inds = np.where((proj_mat > prob) & (proj_mat < 1 - prob)) + + proj_mat[neg_inds] = -1 + proj_mat[pos_inds] = 1 + proj_mat[mid_inds] = 0 + + proj_X = self.X[sample_inds, :] @ proj_mat + + return proj_X, proj_mat diff --git a/proglearn/tree/setup.py b/proglearn/tree/setup.py new file mode 100644 index 0000000000..c24c820501 --- /dev/null +++ b/proglearn/tree/setup.py @@ -0,0 +1,28 @@ +import os + +import numpy +from numpy.distutils.misc_util import Configuration + + +def configuration(parent_package="", top_path=None): + config = Configuration("proglearn", parent_package, top_path) + libraries = [] + if os.name == 'posix': + libraries.append('m') + config.add_extension("split", + sources=["split.pyx"], + include_dirs=[numpy.get_include()], + libraries=libraries, + extra_compile_args=["-O3"], + # extra_compile_args=["-Xpreprocessor", "-fopenmp",], + # extra_link_args=["-Xpreprocessor", "-fopenmp"], + language="c++" + ) + + # config.add_subpackage("tests") + # config.add_data_files("split.pxd") + return config + +if __name__ == "__main__": + from numpy.distutils.core import setup + setup(**configuration().todict()) diff --git a/requirements.txt b/requirements.txt index 115458d4e8..5da7f53126 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ tensorflow>=1.19.0 scikit-learn>=0.22.0 scipy==1.4.1 joblib>=0.14.1 +Cython>=0.29.21 diff --git a/setup.py b/setup.py index 6adfffd4f3..261f1f555f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,49 @@ -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Extension +from Cython.Build import cythonize import os +import sys +import platform + +if platform.python_implementation() == "PyPy": + SCIPY_MIN_VERSION = "1.1.0" + NUMPY_MIN_VERSION = "1.14.0" +else: + SCIPY_MIN_VERSION = "0.17.0" + NUMPY_MIN_VERSION = "1.11.0" + +# Optional setuptools features +# We need to import setuptools early, if we want setuptools features, +# as it monkey-patches the 'setup' function +# For some commands, use setuptools +SETUPTOOLS_COMMANDS = { + "develop", + "release", + "bdist_egg", + "bdist_rpm", + "bdist_wininst", + "install_egg_info", + "build_sphinx", + "egg_info", + "easy_install", + "upload", + "bdist_wheel", + "--single-version-externally-managed", +} +if SETUPTOOLS_COMMANDS.intersection(sys.argv): + import setuptools + + extra_setuptools_args = dict( + zip_safe=False, # the package can run out of an .egg file + include_package_data=True, + extras_require={ + "alldeps": ( + "numpy >= {}".format(NUMPY_MIN_VERSION), + "scipy >= {}".format(SCIPY_MIN_VERSION), + ), + }, + ) +else: + extra_setuptools_args = dict() # Find mgc version. PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) @@ -7,12 +51,30 @@ if line.startswith("__version__ = "): VERSION = line.strip().split()[2][1:-1] -with open("README.md", mode="r", encoding = "utf8") as f: +with open("README.md", mode="r", encoding="utf8") as f: LONG_DESCRIPTION = f.read() -with open("requirements.txt", mode="r", encoding = "utf8") as f: +with open("requirements.txt", mode="r", encoding="utf8") as f: REQUIREMENTS = f.read() +# Cythonize splitter +ext_modules = [ + Extension( + "split", + ["proglearn/split.pyx"], + extra_compile_args=[ + "-Xpreprocessor", + "-fopenmp", + ], + extra_link_args=[ + "-Xpreprocessor", + "-fopenmp" + ], + language="c++", + ) +] + + setup( name="proglearn", version=VERSION, @@ -31,9 +93,10 @@ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7" + "Programming Language :: Python :: 3.7", ], install_requires=REQUIREMENTS, packages=find_packages(exclude=["tests", "tests.*", "tests/*"]), - include_package_data=True + include_package_data=True, + ext_modules=cythonize(ext_modules), ) diff --git a/test_morf.ipynb b/test_morf.ipynb new file mode 100644 index 0000000000..4f5618cfad --- /dev/null +++ b/test_morf.ipynb @@ -0,0 +1,2150 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# MORF API Working Demo\n", + "\n", + "Here, we want to validate visually that MORF API works as intended. First, we show the 2D contiguous convolutional splitter." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 1;\n", + " var nbb_unformatted_code = \"%load_ext nb_black\\n%load_ext lab_black\\n%load_ext autoreload\\n%autoreload 2\";\n", + " var nbb_formatted_code = \"%load_ext nb_black\\n%load_ext lab_black\\n%load_ext autoreload\\n%autoreload 2\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%load_ext nb_black\n", + "%load_ext lab_black\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 2;\n", + " var nbb_unformatted_code = \"import numpy as np\\nfrom proglearn.tree.morf_split import Conv2DSplitter\\n\\nimport matplotlib as mpl\\nimport matplotlib.pyplot as plt\\nimport seaborn as sns\";\n", + " var nbb_formatted_code = \"import numpy as np\\nfrom proglearn.tree.morf_split import Conv2DSplitter\\n\\nimport matplotlib as mpl\\nimport matplotlib.pyplot as plt\\nimport seaborn as sns\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "from proglearn.tree.morf_split import Conv2DSplitter\n", + "\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Splitters: Convolutional 2D Patches (Contiguous and Discontiguous)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Contiguous 2D Convolutional Patch" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 3;\n", + " var nbb_unformatted_code = \"random_state = 123456\\n\\nn = 50\\nheight = 5\\nd = 4\\nX = np.ones((n, height * d))\\ny = np.ones((n,))\\ny[:25] = 0\";\n", + " var nbb_formatted_code = \"random_state = 123456\\n\\nn = 50\\nheight = 5\\nd = 4\\nX = np.ones((n, height * d))\\ny = np.ones((n,))\\ny[:25] = 0\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "random_state = 123456\n", + "\n", + "n = 50\n", + "height = 5\n", + "d = 4\n", + "X = np.ones((n, height * d))\n", + "y = np.ones((n,))\n", + "y[:25] = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 7;\n", + " var nbb_unformatted_code = \"splitter = Conv2DSplitter(\\n X,\\n y,\\n max_features=1,\\n feature_combinations=1.5,\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n)\";\n", + " var nbb_formatted_code = \"splitter = Conv2DSplitter(\\n X,\\n y,\\n max_features=1,\\n feature_combinations=1.5,\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "splitter = Conv2DSplitter(\n", + " X,\n", + " y,\n", + " max_features=1,\n", + " feature_combinations=1.5,\n", + " random_state=random_state,\n", + " image_height=height,\n", + " image_width=d,\n", + " patch_height_max=5,\n", + " patch_height_min=1,\n", + " patch_width_min=1,\n", + " patch_width_max=2,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "779 µs ± 41 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 8;\n", + " var nbb_unformatted_code = \"%%timeit\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"%%timeit\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%timeit\n", + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 9;\n", + " var nbb_unformatted_code = \"proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(50, 20) (20, 20) (50, 20)\n" + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 10;\n", + " var nbb_unformatted_code = \"print(proj_X.shape, proj_mat.shape, X.shape)\";\n", + " var nbb_formatted_code = \"print(proj_X.shape, proj_mat.shape, X.shape)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(proj_X.shape, proj_mat.shape, X.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Text(0.5, 1.0, 'Sampled Projection Matrix - 2D Convolutional MORF'),\n", + " Text(0.5, 28.5, 'Sampled Patches'),\n", + " Text(28.5, 0.5, 'Vectorized Projections')]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 11;\n", + " var nbb_unformatted_code = \"sns.set_context('talk', \\n# font_scale=1.5\\n )\\nfig, ax = plt.subplots(figsize=(7,7))\\nsns.heatmap(proj_mat, annot=True, ax=ax)\\nax.set(\\n title='Sampled Projection Matrix - 2D Convolutional MORF',\\n xlabel='Sampled Patches',\\n ylabel='Vectorized Projections'\\n)\";\n", + " var nbb_formatted_code = \"sns.set_context(\\n \\\"talk\\\",\\n # font_scale=1.5\\n)\\nfig, ax = plt.subplots(figsize=(7, 7))\\nsns.heatmap(proj_mat, annot=True, ax=ax)\\nax.set(\\n title=\\\"Sampled Projection Matrix - 2D Convolutional MORF\\\",\\n xlabel=\\\"Sampled Patches\\\",\\n ylabel=\\\"Vectorized Projections\\\",\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.set_context(\n", + " \"talk\",\n", + " # font_scale=1.5\n", + ")\n", + "fig, ax = plt.subplots(figsize=(7, 7))\n", + "sns.heatmap(proj_mat, annot=True, ax=ax)\n", + "ax.set(\n", + " title=\"Sampled Projection Matrix - 2D Convolutional MORF\",\n", + " xlabel=\"Sampled Patches\",\n", + " ylabel=\"Vectorized Projections\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":32: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.\n", + " fig.tight_layout(rect=[0, 0, .9, 1])\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 44;\n", + " var nbb_unformatted_code = \"empty_mat = np.zeros((height, d))\\n\\nfig, axs = plt.subplots(3, np.ceil(proj_mat.shape[1] / 3).astype(int), \\n sharex=True, sharey=True,\\n figsize=(7, 7))\\naxs = axs.flat\\ncbar_ax = fig.add_axes([.91, .3, .03, .4])\\n\\nfor idx in range(proj_mat.shape[1]):\\n proj_vec = proj_mat[:, idx]\\n \\n vec_idx = np.argwhere(proj_vec == 1)\\n patch_idx = np.unravel_index(vec_idx, shape=(height, d))\\n mat = empty_mat.copy()\\n mat[patch_idx] = 1.0\\n \\n sns.heatmap(mat, ax=axs[idx], \\n xticklabels=np.arange(d),\\n yticklabels=np.arange(height),\\n cbar=idx == 0,\\n square=True,\\n vmin=0, vmax=1,\\n cbar_ax=None if idx else cbar_ax)\\n\\n# remove unused axes\\nidx += 1\\nwhile idx < len(axs):\\n fig.delaxes(axs[idx])\\n idx += 1\\n \\nfig.suptitle('MORF 2D Convolutional Patches Sampled')\\nfig.tight_layout(rect=[0, 0, .9, 1])\";\n", + " var nbb_formatted_code = \"empty_mat = np.zeros((height, d))\\n\\nfig, axs = plt.subplots(\\n 3,\\n np.ceil(proj_mat.shape[1] / 3).astype(int),\\n sharex=True,\\n sharey=True,\\n figsize=(7, 7),\\n)\\naxs = axs.flat\\ncbar_ax = fig.add_axes([0.91, 0.3, 0.03, 0.4])\\n\\nfor idx in range(proj_mat.shape[1]):\\n proj_vec = proj_mat[:, idx]\\n\\n vec_idx = np.argwhere(proj_vec == 1)\\n patch_idx = np.unravel_index(vec_idx, shape=(height, d))\\n mat = empty_mat.copy()\\n mat[patch_idx] = 1.0\\n\\n sns.heatmap(\\n mat,\\n ax=axs[idx],\\n xticklabels=np.arange(d),\\n yticklabels=np.arange(height),\\n cbar=idx == 0,\\n square=True,\\n vmin=0,\\n vmax=1,\\n cbar_ax=None if idx else cbar_ax,\\n )\\n\\n# remove unused axes\\nidx += 1\\nwhile idx < len(axs):\\n fig.delaxes(axs[idx])\\n idx += 1\\n\\nfig.suptitle(\\\"MORF 2D Convolutional Patches Sampled\\\")\\nfig.tight_layout(rect=[0, 0, 0.9, 1])\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "empty_mat = np.zeros((height, d))\n", + "\n", + "fig, axs = plt.subplots(3, np.ceil(proj_mat.shape[1] / 3).astype(int), \n", + " sharex=True, sharey=True,\n", + " figsize=(7, 7))\n", + "axs = axs.flat\n", + "cbar_ax = fig.add_axes([.91, .3, .03, .4])\n", + "\n", + "for idx in range(proj_mat.shape[1]):\n", + " proj_vec = proj_mat[:, idx]\n", + " \n", + " vec_idx = np.argwhere(proj_vec == 1)\n", + " patch_idx = np.unravel_index(vec_idx, shape=(height, d))\n", + " mat = empty_mat.copy()\n", + " mat[patch_idx] = 1.0\n", + " \n", + " sns.heatmap(mat, ax=axs[idx], \n", + " xticklabels=np.arange(d),\n", + " yticklabels=np.arange(height),\n", + " cbar=idx == 0,\n", + " square=True,\n", + " vmin=0, vmax=1,\n", + " cbar_ax=None if idx else cbar_ax)\n", + "\n", + "# remove unused axes\n", + "idx += 1\n", + "while idx < len(axs):\n", + " fig.delaxes(axs[idx])\n", + " idx += 1\n", + " \n", + "fig.suptitle('MORF 2D Convolutional Patches Sampled')\n", + "fig.tight_layout(rect=[0, 0, .9, 1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Discontiguous Sample" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 60;\n", + " var nbb_unformatted_code = \"random_state = 123456\\n\\nn = 50\\nheight = 5\\nd = 4\\nX = np.ones((n, height * d))\\ny = np.ones((n,))\\ny[:25] = 0\";\n", + " var nbb_formatted_code = \"random_state = 123456\\n\\nn = 50\\nheight = 5\\nd = 4\\nX = np.ones((n, height * d))\\ny = np.ones((n,))\\ny[:25] = 0\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "random_state = 123456\n", + "\n", + "n = 50\n", + "height = 5\n", + "d = 4\n", + "X = np.ones((n, height * d))\n", + "y = np.ones((n,))\n", + "y[:25] = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 71;\n", + " var nbb_unformatted_code = \"splitter = Conv2DSplitter(\\n X,\\n y,\\n max_features=1,\\n feature_combinations=1.5,\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n discontiguous_height=True,\\n discontiguous_width=False,\\n)\";\n", + " var nbb_formatted_code = \"splitter = Conv2DSplitter(\\n X,\\n y,\\n max_features=1,\\n feature_combinations=1.5,\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n discontiguous_height=True,\\n discontiguous_width=False,\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "splitter = Conv2DSplitter(\n", + " X,\n", + " y,\n", + " max_features=1,\n", + " feature_combinations=1.5,\n", + " random_state=random_state,\n", + " image_height=height,\n", + " image_width=d,\n", + " patch_height_max=5,\n", + " patch_height_min=1,\n", + " patch_width_min=1,\n", + " patch_width_max=2,\n", + " discontiguous_height=True,\n", + " discontiguous_width=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 72;\n", + " var nbb_unformatted_code = \"proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(50, 20) (20, 20) (50, 20)\n" + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 73;\n", + " var nbb_unformatted_code = \"print(proj_X.shape, proj_mat.shape, X.shape)\";\n", + " var nbb_formatted_code = \"print(proj_X.shape, proj_mat.shape, X.shape)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(proj_X.shape, proj_mat.shape, X.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Text(0.5, 1.0, 'Sampled Projection Matrix - 2D Convolutional MORF'),\n", + " Text(0.5, 28.5, 'Sampled Patches'),\n", + " Text(28.5, 0.5, 'Vectorized Projections')]" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 74;\n", + " var nbb_unformatted_code = \"sns.set_context('talk', \\n# font_scale=1.5\\n )\\nfig, ax = plt.subplots(figsize=(7,7))\\nsns.heatmap(proj_mat, annot=True, ax=ax)\\nax.set(\\n title='Sampled Projection Matrix - 2D Convolutional MORF',\\n xlabel='Sampled Patches',\\n ylabel='Vectorized Projections'\\n)\";\n", + " var nbb_formatted_code = \"sns.set_context(\\n \\\"talk\\\",\\n # font_scale=1.5\\n)\\nfig, ax = plt.subplots(figsize=(7, 7))\\nsns.heatmap(proj_mat, annot=True, ax=ax)\\nax.set(\\n title=\\\"Sampled Projection Matrix - 2D Convolutional MORF\\\",\\n xlabel=\\\"Sampled Patches\\\",\\n ylabel=\\\"Vectorized Projections\\\",\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.set_context(\n", + " \"talk\",\n", + " # font_scale=1.5\n", + ")\n", + "fig, ax = plt.subplots(figsize=(7, 7))\n", + "sns.heatmap(proj_mat, annot=True, ax=ax)\n", + "ax.set(\n", + " title=\"Sampled Projection Matrix - 2D Convolutional MORF\",\n", + " xlabel=\"Sampled Patches\",\n", + " ylabel=\"Vectorized Projections\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":33: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.\n", + " fig.tight_layout(rect=[0, 0, .9, 1])\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 75;\n", + " var nbb_unformatted_code = \"empty_mat = np.zeros((height, d))\\n\\nsns.set_context('paper')\\nfig, axs = plt.subplots(3, np.ceil(proj_mat.shape[1] / 3).astype(int), \\n sharex=True, sharey=True,\\n figsize=(7, 7))\\naxs = axs.flat\\ncbar_ax = fig.add_axes([.91, .3, .03, .4])\\n\\nfor idx in range(proj_mat.shape[1]):\\n proj_vec = proj_mat[:, idx]\\n \\n vec_idx = np.argwhere(proj_vec == 1)\\n patch_idx = np.unravel_index(vec_idx, shape=(height, d))\\n mat = empty_mat.copy()\\n mat[patch_idx] = 1.0\\n \\n sns.heatmap(mat, ax=axs[idx], \\n xticklabels=np.arange(d),\\n yticklabels=np.arange(height),\\n cbar=idx == 0,\\n square=True,\\n vmin=0, vmax=1,\\n cbar_ax=None if idx else cbar_ax)\\n\\n# remove unused axes\\nidx += 1\\nwhile idx < len(axs):\\n fig.delaxes(axs[idx])\\n idx += 1\\n \\nfig.suptitle('MORF 2D Discontiguous Convolutional Patches Sampled')\\nfig.tight_layout(rect=[0, 0, .9, 1])\";\n", + " var nbb_formatted_code = \"empty_mat = np.zeros((height, d))\\n\\nsns.set_context(\\\"paper\\\")\\nfig, axs = plt.subplots(\\n 3,\\n np.ceil(proj_mat.shape[1] / 3).astype(int),\\n sharex=True,\\n sharey=True,\\n figsize=(7, 7),\\n)\\naxs = axs.flat\\ncbar_ax = fig.add_axes([0.91, 0.3, 0.03, 0.4])\\n\\nfor idx in range(proj_mat.shape[1]):\\n proj_vec = proj_mat[:, idx]\\n\\n vec_idx = np.argwhere(proj_vec == 1)\\n patch_idx = np.unravel_index(vec_idx, shape=(height, d))\\n mat = empty_mat.copy()\\n mat[patch_idx] = 1.0\\n\\n sns.heatmap(\\n mat,\\n ax=axs[idx],\\n xticklabels=np.arange(d),\\n yticklabels=np.arange(height),\\n cbar=idx == 0,\\n square=True,\\n vmin=0,\\n vmax=1,\\n cbar_ax=None if idx else cbar_ax,\\n )\\n\\n# remove unused axes\\nidx += 1\\nwhile idx < len(axs):\\n fig.delaxes(axs[idx])\\n idx += 1\\n\\nfig.suptitle(\\\"MORF 2D Discontiguous Convolutional Patches Sampled\\\")\\nfig.tight_layout(rect=[0, 0, 0.9, 1])\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "empty_mat = np.zeros((height, d))\n", + "\n", + "sns.set_context(\"paper\")\n", + "fig, axs = plt.subplots(\n", + " 3,\n", + " np.ceil(proj_mat.shape[1] / 3).astype(int),\n", + " sharex=True,\n", + " sharey=True,\n", + " figsize=(7, 7),\n", + ")\n", + "axs = axs.flat\n", + "cbar_ax = fig.add_axes([0.91, 0.3, 0.03, 0.4])\n", + "\n", + "for idx in range(proj_mat.shape[1]):\n", + " proj_vec = proj_mat[:, idx]\n", + "\n", + " vec_idx = np.argwhere(proj_vec == 1)\n", + " patch_idx = np.unravel_index(vec_idx, shape=(height, d))\n", + " mat = empty_mat.copy()\n", + " mat[patch_idx] = 1.0\n", + "\n", + " sns.heatmap(\n", + " mat,\n", + " ax=axs[idx],\n", + " xticklabels=np.arange(d),\n", + " yticklabels=np.arange(height),\n", + " cbar=idx == 0,\n", + " square=True,\n", + " vmin=0,\n", + " vmax=1,\n", + " cbar_ax=None if idx else cbar_ax,\n", + " )\n", + "\n", + "# remove unused axes\n", + "idx += 1\n", + "while idx < len(axs):\n", + " fig.delaxes(axs[idx])\n", + " idx += 1\n", + "\n", + "fig.suptitle(\"MORF 2D Discontiguous Convolutional Patches Sampled\")\n", + "fig.tight_layout(rect=[0, 0, 0.9, 1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profiling the Projection Matrix Sampling\n", + "\n", + "Let's increase the sample size and the height and the width of samples.\n", + "\n", + "Note that if ``height`` or ``d`` is increased too much... then it's pretty slow currently." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 44;\n", + " var nbb_unformatted_code = \"n = 1000\\nheight = 100\\nd = 80\\nX = np.ones((n, height * d))\\ny = np.ones((n,))\\ny[:25] = 0\";\n", + " var nbb_formatted_code = \"n = 1000\\nheight = 100\\nd = 80\\nX = np.ones((n, height * d))\\ny = np.ones((n,))\\ny[:25] = 0\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "n = 1000\n", + "height = 100\n", + "d = 80\n", + "X = np.ones((n, height * d))\n", + "y = np.ones((n,))\n", + "y[:25] = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 45;\n", + " var nbb_unformatted_code = \"splitter = Conv2DSplitter(X, y, max_features=1, feature_combinations=1.5,\\n random_state=random_state, image_height=height, image_width=d, \\n patch_height_max=5, patch_height_min=1, patch_width_min=1, patch_width_max=2)\";\n", + " var nbb_formatted_code = \"splitter = Conv2DSplitter(\\n X,\\n y,\\n max_features=1,\\n feature_combinations=1.5,\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "splitter = Conv2DSplitter(X, y, max_features=1, feature_combinations=1.5,\n", + " random_state=random_state, image_height=height, image_width=d, \n", + " patch_height_max=5, patch_height_min=1, patch_width_min=1, patch_width_max=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.53 s ± 101 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 47;\n", + " var nbb_unformatted_code = \"%%timeit\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"%%timeit\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%timeit\n", + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "165 ms ± 7.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 34;\n", + " var nbb_unformatted_code = \"%%timeit\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"%%timeit\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%timeit\n", + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 48;\n", + " var nbb_unformatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " 280026 function calls in 1.675 seconds\n", + "\n", + " Ordered by: internal time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 1.268 1.268 1.609 1.609 morf.py:194(sample_proj_mat)\n", + " 8000 0.092 0.000 0.340 0.000 morf.py:131(_get_rand_patch_idx)\n", + " 1 0.067 0.067 1.675 1.675 :1()\n", + " 24002 0.058 0.000 0.144 0.000 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 8002 0.044 0.000 0.044 0.000 {method 'randint' of 'numpy.random.mtrand.RandomState' objects}\n", + " 8000 0.035 0.000 0.086 0.000 index_tricks.py:35(ix_)\n", + " 16001 0.026 0.000 0.026 0.000 {built-in method numpy.arange}\n", + " 32000 0.014 0.000 0.021 0.000 numerictypes.py:285(issubclass_)\n", + " 16000 0.014 0.000 0.037 0.000 numerictypes.py:359(issubdtype)\n", + " 16000 0.009 0.000 0.009 0.000 {method 'reshape' of 'numpy.ndarray' objects}\n", + " 48000 0.008 0.000 0.008 0.000 {built-in method builtins.issubclass}\n", + " 8000 0.007 0.000 0.040 0.000 morf.py:191(_compute_vectorized_index_in_data)\n", + " 8000 0.006 0.000 0.033 0.000 <__array_function__ internals>:2(unravel_index)\n", + " 8000 0.006 0.000 0.041 0.000 morf.py:186(_compute_index_in_vectorized_data)\n", + " 8000 0.006 0.000 0.035 0.000 <__array_function__ internals>:2(ravel_multi_index)\n", + " 8000 0.005 0.000 0.097 0.000 <__array_function__ internals>:2(ix_)\n", + " 16000 0.002 0.000 0.002 0.000 {built-in method builtins.isinstance}\n", + " 16000 0.002 0.000 0.002 0.000 {method 'append' of 'list' objects}\n", + " 8000 0.002 0.000 0.002 0.000 multiarray.py:1001(unravel_index)\n", + " 8000 0.001 0.000 0.001 0.000 {built-in method builtins.len}\n", + " 8000 0.001 0.000 0.001 0.000 multiarray.py:940(ravel_multi_index)\n", + " 8000 0.001 0.000 0.001 0.000 index_tricks.py:31(_ix__dispatcher)\n", + " 1 0.000 0.000 1.675 1.675 {built-in method builtins.exec}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method numpy.zeros}\n", + " 2 0.000 0.000 0.000 0.000 {method 'reduce' of 'numpy.ufunc' objects}\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:70(_wrapreduction)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(prod)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2912(prod)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:71()\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 2 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2907(_prod_dispatcher)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%prun\n", + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 42;\n", + " var nbb_unformatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " 288026 function calls in 1.677 seconds\n", + "\n", + " Ordered by: internal time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 1.294 1.294 1.669 1.669 morf.py:192(sample_proj_mat)\n", + " 8000 0.082 0.000 0.131 0.000 morf.py:131(_get_rand_patch_idx)\n", + " 24002 0.065 0.000 0.159 0.000 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 8002 0.050 0.000 0.050 0.000 {method 'randint' of 'numpy.random.mtrand.RandomState' objects}\n", + " 8000 0.039 0.000 0.094 0.000 index_tricks.py:35(ix_)\n", + " 16001 0.029 0.000 0.029 0.000 {built-in method numpy.arange}\n", + " 8000 0.019 0.000 0.197 0.000 morf.py:160(_get_patch_idx)\n", + " 16000 0.015 0.000 0.039 0.000 numerictypes.py:359(issubdtype)\n", + " 32000 0.015 0.000 0.022 0.000 numerictypes.py:285(issubclass_)\n", + " 16000 0.010 0.000 0.010 0.000 {method 'reshape' of 'numpy.ndarray' objects}\n", + " 48000 0.009 0.000 0.009 0.000 {built-in method builtins.issubclass}\n", + " 8000 0.008 0.000 0.044 0.000 morf.py:189(_compute_vectorized_index_in_data)\n", + " 1 0.008 0.008 1.676 1.676 :1()\n", + " 8000 0.007 0.000 0.046 0.000 morf.py:184(_compute_index_in_vectorized_data)\n", + " 8000 0.007 0.000 0.036 0.000 <__array_function__ internals>:2(unravel_index)\n", + " 8000 0.006 0.000 0.039 0.000 <__array_function__ internals>:2(ravel_multi_index)\n", + " 8000 0.005 0.000 0.106 0.000 <__array_function__ internals>:2(ix_)\n", + " 16000 0.002 0.000 0.002 0.000 {built-in method builtins.isinstance}\n", + " 16000 0.002 0.000 0.002 0.000 {method 'append' of 'list' objects}\n", + " 8000 0.002 0.000 0.002 0.000 multiarray.py:1001(unravel_index)\n", + " 8000 0.001 0.000 0.001 0.000 {built-in method builtins.len}\n", + " 8000 0.001 0.000 0.001 0.000 multiarray.py:940(ravel_multi_index)\n", + " 8000 0.001 0.000 0.001 0.000 index_tricks.py:31(_ix__dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method numpy.zeros}\n", + " 1 0.000 0.000 1.677 1.677 {built-in method builtins.exec}\n", + " 2 0.000 0.000 0.000 0.000 {method 'reduce' of 'numpy.ufunc' objects}\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:70(_wrapreduction)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(prod)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2912(prod)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:71()\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2907(_prod_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%prun\n", + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 35;\n", + " var nbb_unformatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " 72026 function calls in 0.171 seconds\n", + "\n", + " Ordered by: internal time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.084 0.084 0.167 0.167 morf.py:192(sample_proj_mat)\n", + " 2000 0.017 0.000 0.027 0.000 morf.py:131(_get_rand_patch_idx)\n", + " 6002 0.014 0.000 0.035 0.000 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 2002 0.010 0.000 0.010 0.000 {method 'randint' of 'numpy.random.mtrand.RandomState' objects}\n", + " 2000 0.008 0.000 0.021 0.000 index_tricks.py:35(ix_)\n", + " 4001 0.006 0.000 0.006 0.000 {built-in method numpy.arange}\n", + " 2000 0.004 0.000 0.043 0.000 morf.py:160(_get_patch_idx)\n", + " 1 0.004 0.004 0.171 0.171 :1()\n", + " 8000 0.003 0.000 0.005 0.000 numerictypes.py:285(issubclass_)\n", + " 4000 0.003 0.000 0.009 0.000 numerictypes.py:359(issubdtype)\n", + " 1 0.003 0.003 0.003 0.003 {built-in method numpy.zeros}\n", + " 4000 0.002 0.000 0.002 0.000 {method 'reshape' of 'numpy.ndarray' objects}\n", + " 12000 0.002 0.000 0.002 0.000 {built-in method builtins.issubclass}\n", + " 2000 0.002 0.000 0.009 0.000 morf.py:189(_compute_vectorized_index_in_data)\n", + " 2000 0.002 0.000 0.010 0.000 morf.py:184(_compute_index_in_vectorized_data)\n", + " 2000 0.001 0.000 0.008 0.000 <__array_function__ internals>:2(unravel_index)\n", + " 2000 0.001 0.000 0.008 0.000 <__array_function__ internals>:2(ravel_multi_index)\n", + " 2000 0.001 0.000 0.023 0.000 <__array_function__ internals>:2(ix_)\n", + " 4000 0.001 0.000 0.001 0.000 {built-in method builtins.isinstance}\n", + " 4000 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}\n", + " 2000 0.000 0.000 0.000 0.000 multiarray.py:1001(unravel_index)\n", + " 2000 0.000 0.000 0.000 0.000 {built-in method builtins.len}\n", + " 2000 0.000 0.000 0.000 0.000 index_tricks.py:31(_ix__dispatcher)\n", + " 2000 0.000 0.000 0.000 0.000 multiarray.py:940(ravel_multi_index)\n", + " 1 0.000 0.000 0.171 0.171 {built-in method builtins.exec}\n", + " 2 0.000 0.000 0.000 0.000 {method 'reduce' of 'numpy.ufunc' objects}\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:70(_wrapreduction)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(prod)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2912(prod)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:71()\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2907(_prod_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 2 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%prun\n", + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 23;\n", + " var nbb_unformatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_formatted_code = \"%%prun\\nproj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " 76006 function calls in 0.170 seconds\n", + "\n", + " Ordered by: internal time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.083 0.083 0.170 0.170 morf.py:201(sample_proj_mat)\n", + " 6000 0.021 0.000 0.021 0.000 {method 'randint' of 'numpy.random.mtrand.RandomState' objects}\n", + " 6000 0.015 0.000 0.036 0.000 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 2000 0.009 0.000 0.030 0.000 morf.py:131(_get_rand_patch_idx)\n", + " 2000 0.008 0.000 0.021 0.000 index_tricks.py:35(ix_)\n", + " 4001 0.006 0.000 0.006 0.000 {built-in method numpy.arange}\n", + " 2000 0.005 0.000 0.044 0.000 morf.py:169(_get_patch_idx)\n", + " 4000 0.004 0.000 0.009 0.000 numerictypes.py:359(issubdtype)\n", + " 8000 0.003 0.000 0.005 0.000 numerictypes.py:285(issubclass_)\n", + " 1 0.003 0.003 0.003 0.003 {built-in method numpy.zeros}\n", + " 4000 0.002 0.000 0.002 0.000 {method 'reshape' of 'numpy.ndarray' objects}\n", + " 12000 0.002 0.000 0.002 0.000 {built-in method builtins.issubclass}\n", + " 2000 0.002 0.000 0.011 0.000 morf.py:193(_compute_index_in_vectorized_data)\n", + " 2000 0.002 0.000 0.009 0.000 morf.py:198(_compute_vectorized_index_in_data)\n", + " 2000 0.001 0.000 0.008 0.000 <__array_function__ internals>:2(unravel_index)\n", + " 2000 0.001 0.000 0.009 0.000 <__array_function__ internals>:2(ravel_multi_index)\n", + " 2000 0.001 0.000 0.024 0.000 <__array_function__ internals>:2(ix_)\n", + " 4000 0.001 0.000 0.001 0.000 {built-in method builtins.isinstance}\n", + " 4000 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}\n", + " 2000 0.000 0.000 0.000 0.000 multiarray.py:1001(unravel_index)\n", + " 2000 0.000 0.000 0.000 0.000 {built-in method builtins.len}\n", + " 2000 0.000 0.000 0.000 0.000 multiarray.py:940(ravel_multi_index)\n", + " 2000 0.000 0.000 0.000 0.000 index_tricks.py:31(_ix__dispatcher)\n", + " 1 0.000 0.000 0.170 0.170 {built-in method builtins.exec}\n", + " 1 0.000 0.000 0.170 0.170 :1()\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%prun\n", + "proj_X, proj_mat = splitter.sample_proj_mat(sample_inds=np.arange(n))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Classification Tree - Convolutional Tree" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 4;\n", + " var nbb_unformatted_code = \"from proglearn.tree.morf_tree import Conv2DObliqueTreeClassifier\";\n", + " var nbb_formatted_code = \"from proglearn.tree.morf_tree import Conv2DObliqueTreeClassifier\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from proglearn.tree.morf_tree import Conv2DObliqueTreeClassifier" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 5;\n", + " var nbb_unformatted_code = \"clf = Conv2DObliqueTreeClassifier(\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n discontiguous_height=True,\\n discontiguous_width=False,\\n)\";\n", + " var nbb_formatted_code = \"clf = Conv2DObliqueTreeClassifier(\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n discontiguous_height=True,\\n discontiguous_width=False,\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "clf = Conv2DObliqueTreeClassifier(\n", + " random_state=random_state,\n", + " image_height=height,\n", + " image_width=d,\n", + " patch_height_max=5,\n", + " patch_height_min=1,\n", + " patch_width_min=1,\n", + " patch_width_max=2,\n", + " discontiguous_height=True,\n", + " discontiguous_width=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Conv2DObliqueTreeClassifier(discontiguous_height=True, image_height=5,\n", + " image_width=4, patch_height_max=5,\n", + " patch_width_max=2, random_state=123456)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 6;\n", + " var nbb_unformatted_code = \"clf.fit(X, y)\";\n", + " var nbb_formatted_code = \"clf.fit(X, y)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "clf.fit(X, y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Classification Forest - Convolutional Forest" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 7;\n", + " var nbb_unformatted_code = \"from proglearn.tree.morf import Conv2DObliqueForestClassifier\";\n", + " var nbb_formatted_code = \"from proglearn.tree.morf import Conv2DObliqueForestClassifier\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from proglearn.tree.morf import Conv2DObliqueForestClassifier" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 15;\n", + " var nbb_unformatted_code = \"clf = Conv2DObliqueForestClassifier(\\n n_estimators=100,\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n discontiguous_height=True,\\n discontiguous_width=False,\\n n_jobs=-1,\\n)\";\n", + " var nbb_formatted_code = \"clf = Conv2DObliqueForestClassifier(\\n n_estimators=100,\\n random_state=random_state,\\n image_height=height,\\n image_width=d,\\n patch_height_max=5,\\n patch_height_min=1,\\n patch_width_min=1,\\n patch_width_max=2,\\n discontiguous_height=True,\\n discontiguous_width=False,\\n n_jobs=-1,\\n)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "clf = Conv2DObliqueForestClassifier(\n", + " n_estimators=100,\n", + " random_state=random_state,\n", + " image_height=height,\n", + " image_width=d,\n", + " patch_height_max=5,\n", + " patch_height_min=1,\n", + " patch_width_min=1,\n", + " patch_width_max=2,\n", + " discontiguous_height=True,\n", + " discontiguous_width=False,\n", + " n_jobs=-1,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Conv2DObliqueForestClassifier(discontiguous_height=True, image_height=5,\n", + " image_width=4, n_jobs=-1, patch_height_max=5,\n", + " patch_width_max=2, random_state=123456)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 16;\n", + " var nbb_unformatted_code = \"clf.fit(X, y)\";\n", + " var nbb_formatted_code = \"clf.fit(X, y)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "clf.fit(X, y)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6.38 s ± 196 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 17;\n", + " var nbb_unformatted_code = \"%%timeit\\nclf.fit(X, y)\";\n", + " var nbb_formatted_code = \"%%timeit\\nclf.fit(X, y)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%timeit\n", + "clf.fit(X, y)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "data": { + "application/javascript": [ + "\n", + " setTimeout(function() {\n", + " var nbb_cell_id = 18;\n", + " var nbb_unformatted_code = \"%%prun\\nclf.fit(X, y)\";\n", + " var nbb_formatted_code = \"%%prun\\nclf.fit(X, y)\";\n", + " var nbb_cells = Jupyter.notebook.get_cells();\n", + " for (var i = 0; i < nbb_cells.length; ++i) {\n", + " if (nbb_cells[i].input_prompt_number == nbb_cell_id) {\n", + " if (nbb_cells[i].get_text() == nbb_unformatted_code) {\n", + " nbb_cells[i].set_text(nbb_formatted_code);\n", + " }\n", + " break;\n", + " }\n", + " }\n", + " }, 500);\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " 199570 function calls (195767 primitive calls) in 6.434 seconds\n", + "\n", + " Ordered by: internal time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 388 6.300 0.016 6.300 0.016 {method 'acquire' of '_thread.lock' objects}\n", + " 500 0.017 0.000 0.066 0.000 inspect.py:2112(_signature_from_function)\n", + " 10000 0.017 0.000 0.035 0.000 inspect.py:2477(__init__)\n", + " 10000 0.014 0.000 0.015 0.000 enum.py:289(__call__)\n", + " 2000/100 0.009 0.000 0.051 0.001 base.py:28(clone)\n", + " 500 0.006 0.000 0.008 0.000 base.py:165()\n", + " 500 0.006 0.000 0.101 0.000 base.py:178(get_params)\n", + " 500 0.005 0.000 0.009 0.000 inspect.py:2760(__init__)\n", + " 500 0.005 0.000 0.093 0.000 base.py:151(_get_param_names)\n", + " 29500 0.004 0.000 0.004 0.000 inspect.py:2527(name)\n", + " 500 0.004 0.000 0.005 0.000 base.py:176()\n", + " 10500 0.003 0.000 0.005 0.000 inspect.py:2809()\n", + " 500 0.003 0.000 0.071 0.000 inspect.py:2206(_signature_from_callable)\n", + " 23801 0.003 0.000 0.003 0.000 {method 'get' of 'dict' objects}\n", + " 19000 0.002 0.000 0.002 0.000 inspect.py:2539(kind)\n", + " 1900 0.002 0.000 0.003 0.000 copy.py:128(deepcopy)\n", + " 2003/101 0.002 0.000 0.052 0.001 validation.py:59(inner_f)\n", + " 10000 0.002 0.000 0.002 0.000 enum.py:586(__new__)\n", + " 12151 0.002 0.000 0.002 0.000 {built-in method builtins.getattr}\n", + " 100 0.002 0.000 0.053 0.001 _base.py:43(_set_random_states)\n", + " 100 0.002 0.000 0.002 0.000 {method 'randint' of 'numpy.random.mtrand.RandomState' objects}\n", + " 10000 0.002 0.000 0.002 0.000 {method 'isidentifier' of 'str' objects}\n", + " 12355 0.002 0.000 0.002 0.000 {built-in method builtins.isinstance}\n", + " 200 0.002 0.000 0.050 0.000 base.py:202(set_params)\n", + " 10011 0.001 0.000 0.001 0.000 {method 'append' of 'list' objects}\n", + " 600 0.001 0.000 0.001 0.000 {built-in method builtins.sorted}\n", + " 8250 0.001 0.000 0.001 0.000 {built-in method builtins.hasattr}\n", + " 1 0.001 0.001 6.302 6.302 parallel.py:918(retrieve)\n", + " 500 0.001 0.000 0.001 0.000 inspect.py:493(unwrap)\n", + " 100 0.001 0.000 0.125 0.001 _base.py:144(_make_estimator)\n", + " 1 0.001 0.001 6.434 6.434 _forest.py:273(fit)\n", + " 100 0.001 0.000 0.001 0.000 getlimits.py:514(__init__)\n", + " 4294 0.001 0.000 0.001 0.000 {built-in method builtins.len}\n", + " 88 0.001 0.000 6.301 0.072 threading.py:270(wait)\n", + " 1800 0.000 0.000 0.000 0.000 {method 'endswith' of 'str' objects}\n", + " 1000 0.000 0.000 0.001 0.000 inspect.py:158(isfunction)\n", + " 111 0.000 0.000 6.301 0.057 threading.py:540(wait)\n", + " 500 0.000 0.000 0.072 0.000 inspect.py:3091(signature)\n", + " 100 0.000 0.000 0.001 0.000 morf_tree.py:77(__init__)\n", + " 100 0.000 0.000 0.001 0.000 _base.py:151()\n", + " 101 0.000 0.000 0.001 0.000 validation.py:869(check_random_state)\n", + " 2402 0.000 0.000 0.000 0.000 {built-in method builtins.id}\n", + " 500 0.000 0.000 0.000 0.000 {method 'values' of 'mappingproxy' objects}\n", + " 30 0.000 0.000 0.000 0.000 threading.py:222(__init__)\n", + " 500 0.000 0.000 0.072 0.000 inspect.py:2839(from_callable)\n", + " 1 0.000 0.000 0.126 0.126 _forest.py:377()\n", + " 1460 0.000 0.000 0.000 0.000 {built-in method builtins.setattr}\n", + " 1900 0.000 0.000 0.000 0.000 copy.py:182(_deepcopy_atomic)\n", + " 100 0.000 0.000 6.301 0.063 pool.py:764(get)\n", + " 1300 0.000 0.000 0.000 0.000 {method 'partition' of 'str' objects}\n", + " 11 0.000 0.000 0.000 0.000 {built-in method _thread.start_new_thread}\n", + " 500 0.000 0.000 0.000 0.000 inspect.py:513(_is_wrapper)\n", + " 109 0.000 0.000 0.000 0.000 {built-in method _abc._abc_instancecheck}\n", + " 11 0.000 0.000 0.001 0.000 threading.py:834(start)\n", + " 3 0.000 0.000 0.000 0.000 arraysetops.py:310(_unique1d)\n", + " 100 0.000 0.000 6.300 0.063 pool.py:761(wait)\n", + " 500 0.000 0.000 0.000 0.000 {built-in method sys.getrecursionlimit}\n", + " 1 0.000 0.000 0.000 0.000 {function SeedSequence.generate_state at 0x7fd4705d4160}\n", + " 100 0.000 0.000 0.000 0.000 transformers.py:911(__init__)\n", + " 116 0.000 0.000 0.000 0.000 {built-in method _thread.allocate_lock}\n", + " 100 0.000 0.000 0.000 0.000 getlimits.py:538(max)\n", + " 17 0.000 0.000 0.003 0.000 parallel.py:796(dispatch_one_batch)\n", + " 100 0.000 0.000 0.000 0.000 pool.py:753(ready)\n", + " 88 0.000 0.000 0.000 0.000 threading.py:258(_acquire_restore)\n", + " 146 0.000 0.000 0.000 0.000 threading.py:246(__enter__)\n", + " 146 0.000 0.000 0.000 0.000 threading.py:249(__exit__)\n", + " 503 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}\n", + " 3 0.000 0.000 0.000 0.000 {method '__enter__' of '_multiprocessing.SemLock' objects}\n", + " 500 0.000 0.000 0.000 0.000 inspect.py:2845(parameters)\n", + " 500 0.000 0.000 0.000 0.000 {built-in method builtins.callable}\n", + " 11 0.000 0.000 0.000 0.000 threading.py:761(__init__)\n", + " 15 0.000 0.000 0.000 0.000 {built-in method numpy.array}\n", + " 146 0.000 0.000 0.000 0.000 {method '__enter__' of '_thread.lock' objects}\n", + " 32 0.000 0.000 0.000 0.000 functools.py:34(update_wrapper)\n", + " 88 0.000 0.000 0.000 0.000 threading.py:255(_release_save)\n", + " 120 0.000 0.000 0.000 0.000 threading.py:261(_is_owned)\n", + " 109 0.000 0.000 0.000 0.000 abc.py:96(__instancecheck__)\n", + " 1 0.000 0.000 0.001 0.001 _parallel_backends.py:239(terminate)\n", + " 1 0.000 0.000 0.001 0.001 pool.py:311(_repopulate_pool_static)\n", + " 1 0.000 0.000 6.306 6.306 parallel.py:958(__call__)\n", + " 100 0.000 0.000 0.000 0.000 {method 'pop' of 'list' objects}\n", + " 16 0.000 0.000 0.003 0.000 parallel.py:759(_dispatch)\n", + " 8 0.000 0.000 0.000 0.000 pool.py:919(Process)\n", + " 3 0.000 0.000 0.000 0.000 {method 'reduce' of 'numpy.ufunc' objects}\n", + " 17 0.000 0.000 0.000 0.000 :389(parent)\n", + " 101 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects}\n", + " 27 0.000 0.000 0.000 0.000 threading.py:505(__init__)\n", + " 16 0.000 0.000 0.000 0.000 pool.py:744(__init__)\n", + " 11 0.000 0.000 0.000 0.000 {built-in method posix.write}\n", + " 2 0.000 0.000 0.000 0.000 validation.py:404(check_array)\n", + " 1 0.000 0.000 0.002 0.002 pool.py:183(__init__)\n", + " 2 0.000 0.000 0.000 0.000 synchronize.py:50(__init__)\n", + " 1 0.000 0.000 6.434 6.434 {built-in method builtins.exec}\n", + " 2 0.000 0.000 0.000 0.000 validation.py:83(_assert_all_finite)\n", + " 146 0.000 0.000 0.000 0.000 {method '__exit__' of '_thread.lock' objects}\n", + " 19 0.000 0.000 0.000 0.000 queue.py:153(get)\n", + " 16 0.000 0.000 0.000 0.000 pool.py:450(apply_async)\n", + " 126 0.000 0.000 0.000 0.000 threading.py:513(is_set)\n", + " 8 0.000 0.000 0.000 0.000 __init__.py:36(__init__)\n", + " 4 0.000 0.000 0.000 0.000 resource_tracker.py:153(_send)\n", + " 1 0.000 0.000 0.000 0.000 multiclass.py:186(type_of_target)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method builtins.eval}\n", + " 4 0.000 0.000 0.000 0.000 validation.py:187(_num_samples)\n", + " 1 0.000 0.000 0.000 0.000 _forest.py:558(_validate_y_class_weight)\n", + " 16 0.000 0.000 0.000 0.000 queue.py:121(put)\n", + " 11/10 0.000 0.000 0.000 0.000 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 2 0.000 0.000 0.000 0.000 version.py:217(__init__)\n", + " 32 0.000 0.000 0.000 0.000 threading.py:341(notify)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method posix.urandom}\n", + " 16 0.000 0.000 0.000 0.000 _forest.py:388()\n", + " 4 0.000 0.000 0.000 0.000 {method 'format' of 'str' objects}\n", + " 18 0.000 0.000 0.000 0.000 _parallel_backends.py:34(__init__)\n", + " 104 0.000 0.000 0.000 0.000 {method 'append' of 'collections.deque' objects}\n", + " 8 0.000 0.000 0.000 0.000 :1017(_handle_fromlist)\n", + " 91 0.000 0.000 0.000 0.000 {method 'release' of '_thread.lock' objects}\n", + " 16 0.000 0.000 0.003 0.000 _parallel_backends.py:250(apply_async)\n", + " 16 0.000 0.000 0.000 0.000 fixes.py:205(delayed)\n", + " 1 0.000 0.000 0.000 0.000 context.py:169(_cpu_count_user)\n", + " 1 0.000 0.000 0.000 0.000 parallel.py:637(__init__)\n", + " 1 0.000 0.000 0.000 0.000 queue.py:33(__init__)\n", + " 2 0.000 0.000 0.000 0.000 weakref.py:159(__setitem__)\n", + " 33 0.000 0.000 0.000 0.000 threading.py:1306(current_thread)\n", + " 8 0.000 0.000 0.001 0.000 __init__.py:43(start)\n", + " 1 0.000 0.000 0.001 0.001 pool.py:677(_terminate_pool)\n", + " 2 0.000 0.000 0.000 0.000 synchronize.py:84(_cleanup)\n", + " 14 0.000 0.000 0.000 0.000 threading.py:1095(daemon)\n", + " 16 0.000 0.000 0.000 0.000 parallel.py:245(__init__)\n", + " 16 0.000 0.000 0.000 0.000 fixes.py:215(__init__)\n", + " 3 0.000 0.000 0.001 0.000 util.py:205(__call__)\n", + " 3 0.000 0.000 0.000 0.000 queues.py:360(put)\n", + " 16 0.000 0.000 0.000 0.000 _parallel_backends.py:124(get_nested_backend)\n", + " 3 0.000 0.000 0.000 0.000 reduction.py:38(__init__)\n", + " 11 0.000 0.000 0.000 0.000 _weakrefset.py:81(add)\n", + " 3 0.000 0.000 0.000 0.000 reduction.py:48(dumps)\n", + " 3 0.000 0.000 0.000 0.000 fromnumeric.py:70(_wrapreduction)\n", + " 4 0.000 0.000 0.000 0.000 warnings.py:181(_add_filter)\n", + " 3 0.000 0.000 0.000 0.000 connection.py:181(send_bytes)\n", + " 16 0.000 0.000 0.000 0.000 random.py:250(_randbelow_with_getrandbits)\n", + " 11 0.000 0.000 0.000 0.000 threading.py:734(_newname)\n", + " 8 0.000 0.000 0.000 0.000 weakref.py:343(__init__)\n", + " 4 0.000 0.000 0.000 0.000 warnings.py:458(__enter__)\n", + " 2 0.000 0.000 0.000 0.000 __init__.py:8(_make_name)\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:72(inner)\n", + " 25 0.000 0.000 0.000 0.000 {method 'put' of '_queue.SimpleQueue' objects}\n", + " 4 0.000 0.000 0.000 0.000 {built-in method numpy.empty}\n", + " 2 0.000 0.000 0.000 0.000 _ufunc_config.py:32(seterr)\n", + " 1 0.000 0.000 0.000 0.000 {method 'cumsum' of 'numpy.ndarray' objects}\n", + " 2 0.000 0.000 0.000 0.000 {built-in method posix.close}\n", + " 2 0.000 0.000 0.000 0.000 extmath.py:663(_safe_accumulator_op)\n", + " 35 0.000 0.000 0.000 0.000 {method 'update' of 'dict' objects}\n", + " 3 0.000 0.000 0.000 0.000 connection.py:390(_send_bytes)\n", + " 16 0.000 0.000 0.000 0.000 random.py:285(choice)\n", + " 9 0.000 0.000 0.000 0.000 _weakrefset.py:38(_remove)\n", + " 2 0.000 0.000 0.000 0.000 version.py:378(_cmpkey)\n", + " 3 0.000 0.000 0.000 0.000 arraysetops.py:138(unique)\n", + " 11 0.000 0.000 0.000 0.000 threading.py:1177(_make_invoke_excepthook)\n", + " 1 0.000 0.000 0.000 0.000 base.py:369(_validate_data)\n", + " 2 0.000 0.000 0.000 0.000 {method 'search' of 're.Pattern' objects}\n", + " 1 0.000 0.000 0.000 0.000 uuid.py:132(__init__)\n", + " 3 0.000 0.000 0.000 0.000 util.py:186(__init__)\n", + " 3 0.000 0.000 0.001 0.000 threading.py:979(join)\n", + " 25 0.000 0.000 0.000 0.000 {built-in method builtins.next}\n", + " 6 0.000 0.000 0.000 0.000 {built-in method _abc._abc_subclasscheck}\n", + " 18 0.000 0.000 0.000 0.000 _config.py:14(get_config)\n", + " 1 0.000 0.000 0.000 0.000 parallel.py:76(get_active_backend)\n", + " 3 0.000 0.000 0.000 0.000 {method 'dump' of '_pickle.Pickler' objects}\n", + " 1 0.000 0.000 0.000 0.000 pool.py:940(_help_stuff_finish)\n", + " 16 0.000 0.000 0.000 0.000 fixes.py:207(delayed_function)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method posix.stat}\n", + " 4 0.000 0.000 0.000 0.000 resource_tracker.py:70(ensure_running)\n", + " 1 0.000 0.000 0.000 0.000 random.py:721(getrandbits)\n", + " 3 0.000 0.000 0.000 0.000 threading.py:944(_stop)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2111(sum)\n", + " 3 0.000 0.000 0.000 0.000 {method 'flatten' of 'numpy.ndarray' objects}\n", + " 2 0.000 0.000 0.000 0.000 {built-in method _multiprocessing.sem_unlink}\n", + " 2 0.000 0.000 0.000 0.000 context.py:65(Lock)\n", + " 16 0.000 0.000 0.000 0.000 functools.py:64(wraps)\n", + " 16 0.000 0.000 0.000 0.000 queue.py:212(_put)\n", + " 3 0.000 0.000 0.000 0.000 {method '__exit__' of '_multiprocessing.SemLock' objects}\n", + " 1 0.000 0.000 0.000 0.000 context.py:110(SimpleQueue)\n", + " 4 0.000 0.000 0.001 0.000 threading.py:1017(_wait_for_tstate_lock)\n", + " 1 0.000 0.000 0.000 0.000 multiclass.py:113(is_multilabel)\n", + " 17 0.000 0.000 0.000 0.000 {method 'rpartition' of 'str' objects}\n", + " 1 0.000 0.000 0.000 0.000 validation.py:707(check_X_y)\n", + " 2 0.000 0.000 0.000 0.000 {method 'sort' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.000 0.000 {method 'argsort' of 'numpy.ndarray' objects}\n", + " 11 0.000 0.000 0.000 0.000 threading.py:1110(daemon)\n", + " 16 0.000 0.000 0.000 0.000 queue.py:216(_get)\n", + " 4 0.000 0.000 0.000 0.000 {method 'remove' of 'list' objects}\n", + " 21 0.000 0.000 0.000 0.000 {method 'copy' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method posix.pipe}\n", + " 2 0.000 0.000 0.000 0.000 _ufunc_config.py:132(geterr)\n", + " 2 0.000 0.000 0.000 0.000 tempfile.py:144(__next__)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(sum)\n", + " 48 0.000 0.000 0.000 0.000 parallel.py:275(__len__)\n", + " 1 0.000 0.000 0.000 0.000 fixes.py:63(_joblib_parallel_args)\n", + " 19 0.000 0.000 0.000 0.000 queue.py:208(_qsize)\n", + " 1 0.000 0.000 0.000 0.000 pool.py:644(close)\n", + " 16 0.000 0.000 0.002 0.000 _parallel_backends.py:400(_get_pool)\n", + " 4 0.000 0.000 0.000 0.000 warnings.py:437(__init__)\n", + " 4 0.000 0.000 0.000 0.000 warnings.py:165(simplefilter)\n", + " 17 0.000 0.000 0.000 0.000 util.py:48(debug)\n", + " 4 0.000 0.000 0.000 0.000 warnings.py:477(__exit__)\n", + " 3 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(unique)\n", + " 2 0.000 0.000 0.000 0.000 numerictypes.py:359(issubdtype)\n", + " 16 0.000 0.000 0.000 0.000 parallel.py:345(__init__)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method posix.cpu_count}\n", + " 1 0.000 0.000 0.000 0.000 logger.py:39(short_format_time)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:52(_wrapfunc)\n", + " 20 0.000 0.000 0.000 0.000 {method 'group' of 're.Match' objects}\n", + " 1 0.000 0.000 0.000 0.000 disk.py:42(memstr_to_bytes)\n", + " 1 0.000 0.000 0.001 0.001 pool.py:651(terminate)\n", + " 1 0.000 0.000 0.000 0.000 queues.py:334(__init__)\n", + " 4 0.000 0.000 0.000 0.000 numerictypes.py:285(issubclass_)\n", + " 4 0.000 0.000 0.000 0.000 resource_tracker.py:134(_check_alive)\n", + " 1 0.000 0.000 0.000 0.000 logger.py:23(_squeeze_time)\n", + " 33 0.000 0.000 0.000 0.000 {built-in method _thread.get_ident}\n", + " 2 0.000 0.000 0.000 0.000 tempfile.py:147()\n", + " 8 0.000 0.000 0.000 0.000 weakref.py:395(__setitem__)\n", + " 18 0.000 0.000 0.000 0.000 {built-in method time.time}\n", + " 9 0.000 0.000 0.000 0.000 _asarray.py:110(asanyarray)\n", + " 3 0.000 0.000 0.000 0.000 synchronize.py:97(__exit__)\n", + " 2 0.000 0.000 0.000 0.000 weakref.py:103(remove)\n", + " 6 0.000 0.000 0.000 0.000 weakref.py:345(remove)\n", + " 2 0.000 0.000 0.000 0.000 __init__.py:126(parse_version)\n", + " 1 0.000 0.000 0.000 0.000 _parallel_backends.py:280(__init__)\n", + " 1 0.000 0.000 0.000 0.000 _base.py:123(_validate_estimator)\n", + " 16 0.000 0.000 0.000 0.000 _parallel_backends.py:590(__init__)\n", + " 20 0.000 0.000 0.000 0.000 {method 'insert' of 'list' objects}\n", + " 3 0.000 0.000 0.000 0.000 synchronize.py:94(__enter__)\n", + " 8 0.000 0.000 0.000 0.000 threading.py:1042(name)\n", + " 1 0.000 0.000 0.000 0.000 parallel.py:730(_initialize_backend)\n", + " 2 0.000 0.000 0.000 0.000 :1(__new__)\n", + " 1 0.000 0.000 0.000 0.000 {method 'astype' of 'numpy.ndarray' objects}\n", + " 16 0.000 0.000 0.000 0.000 pool.py:348(_check_running)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:24(atleast_1d)\n", + " 1 0.000 0.000 0.000 0.000 validation.py:248(check_consistent_length)\n", + " 1 0.000 0.000 0.000 0.000 uuid.py:780(uuid4)\n", + " 1 0.000 0.000 0.000 0.000 connection.py:516(Pipe)\n", + " 7 0.000 0.000 0.000 0.000 version.py:226()\n", + " 4 0.000 0.000 0.000 0.000 base.py:1192(isspmatrix)\n", + " 3 0.000 0.000 0.000 0.000 connection.py:365(_send)\n", + " 3 0.000 0.000 0.000 0.000 fromnumeric.py:71()\n", + " 2 0.000 0.000 0.000 0.000 connection.py:360(_close)\n", + " 2 0.000 0.000 0.000 0.000 util.py:171(register_after_fork)\n", + " 1 0.000 0.000 0.000 0.000 {method 'reshape' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method numpy.zeros}\n", + " 6 0.000 0.000 0.000 0.000 abc.py:100(__subclasscheck__)\n", + " 4 0.000 0.000 0.000 0.000 _asarray.py:23(asarray)\n", + " 11 0.000 0.000 0.000 0.000 {method 'add' of 'set' objects}\n", + " 1 0.000 0.000 0.000 0.000 genericpath.py:16(exists)\n", + " 1 0.000 0.000 0.000 0.000 pool.py:927(_setup_queues)\n", + " 23 0.000 0.000 0.000 0.000 {method 'getrandbits' of '_random.Random' objects}\n", + " 1 0.000 0.000 0.000 0.000 context.py:110(cpu_count)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(any)\n", + " 1 0.000 0.000 0.000 0.000 _ufunc_config.py:433(__enter__)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(concatenate)\n", + " 1 0.000 0.000 0.000 0.000 _ufunc_config.py:438(__exit__)\n", + " 3 0.000 0.000 0.000 0.000 arraysetops.py:125(_unpack_tuple)\n", + " 1 0.000 0.000 0.000 0.000 threading.py:1071(is_alive)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(atleast_1d)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2256(any)\n", + " 1 0.000 0.000 0.000 0.000 os.py:670(__getitem__)\n", + " 1 0.000 0.000 0.000 0.000 os.py:748(encode)\n", + " 5 0.000 0.000 0.000 0.000 {method 'encode' of 'str' objects}\n", + " 2 0.000 0.000 0.000 0.000 connection.py:130(__del__)\n", + " 6 0.000 0.000 0.000 0.000 {built-in method builtins.issubclass}\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(copy)\n", + " 2 0.000 0.000 0.000 0.000 resource_tracker.py:149(unregister)\n", + " 2 0.000 0.000 0.000 0.000 validation.py:397(_ensure_no_complex_data)\n", + " 8 0.000 0.000 0.000 0.000 {method 'replace' of 'str' objects}\n", + " 2 0.000 0.000 0.000 0.000 tempfile.py:133(rng)\n", + " 17 0.000 0.000 0.000 0.000 _parallel_backends.py:89(compute_batch_size)\n", + " 10 0.000 0.000 0.000 0.000 {built-in method posix.getpid}\n", + " 5 0.000 0.000 0.000 0.000 {built-in method builtins.max}\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(cumsum)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method numpy.seterrobj}\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:82(__init__)\n", + " 2 0.000 0.000 0.000 0.000 connection.py:117(__init__)\n", + " 16 0.000 0.000 0.000 0.000 {method 'popleft' of 'collections.deque' objects}\n", + " 1 0.000 0.000 0.000 0.000 _parallel_backends.py:227(effective_n_jobs)\n", + " 1 0.000 0.000 0.000 0.000 _collections_abc.py:657(get)\n", + " 2 0.000 0.000 0.000 0.000 synchronize.py:161(__init__)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:199(reshape)\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:117(__exit__)\n", + " 12 0.000 0.000 0.000 0.000 {built-in method _warnings._filters_mutated}\n", + " 9 0.000 0.000 0.000 0.000 {method 'discard' of 'set' objects}\n", + " 4 0.000 0.000 0.000 0.000 {built-in method numpy.geterrobj}\n", + " 16 0.000 0.000 0.000 0.000 {method 'bit_length' of 'int' objects}\n", + " 1 0.000 0.000 0.000 0.000 context.py:41(cpu_count)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2446(cumsum)\n", + " 1 0.000 0.000 0.001 0.001 pool.py:302(_repopulate_pool)\n", + " 3 0.000 0.000 0.000 0.000 {built-in method _struct.pack}\n", + " 8 0.000 0.000 0.000 0.000 threading.py:1031(name)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method from_bytes}\n", + " 1 0.000 0.000 0.001 0.001 parallel.py:755(_terminate_backend)\n", + " 4 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x10369c6e8}\n", + " 2 0.000 0.000 0.000 0.000 resource_tracker.py:145(register)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:2(reshape)\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:238(helper)\n", + " 1 0.000 0.000 0.000 0.000 version.py:61(_compare)\n", + " 2 0.000 0.000 0.000 0.000 weakref.py:328(__init__)\n", + " 1 0.000 0.000 0.000 0.000 validation.py:259()\n", + " 1 0.000 0.000 0.000 0.000 multiclass.py:169(check_classification_targets)\n", + " 2 0.000 0.000 0.000 0.000 weakref.py:323(__new__)\n", + " 1 0.000 0.000 0.002 0.002 pool.py:924(__init__)\n", + " 1 0.000 0.000 0.000 0.000 uuid.py:327(hex)\n", + " 1 0.000 0.000 0.000 0.000 threading.py:81(RLock)\n", + " 1 0.000 0.000 0.000 0.000 _parallel_backends.py:389(configure)\n", + " 2 0.000 0.000 0.000 0.000 synchronize.py:90(_make_methods)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method _weakref._remove_dead_weakref}\n", + " 2 0.000 0.000 0.000 0.000 context.py:233(get_context)\n", + " 1 0.000 0.000 0.000 0.000 function_base.py:715(copy)\n", + " 1 0.000 0.000 0.000 0.000 pool.py:157(__init__)\n", + " 3 0.000 0.000 0.000 0.000 {method 'getbuffer' of '_io.BytesIO' objects}\n", + " 1 0.000 0.000 0.000 0.000 base.py:335(_check_n_features)\n", + " 1 0.000 0.000 0.000 0.000 version.py:52(__ge__)\n", + " 6 0.000 0.000 0.000 0.000 version.py:333(_parse_letter_version)\n", + " 1 0.000 0.000 0.000 0.000 {method 'get' of '_queue.SimpleQueue' objects}\n", + " 1 0.000 0.000 0.000 0.000 {method 'startswith' of 'str' objects}\n", + " 3 0.000 0.000 0.000 0.000 connection.py:134(_check_closed)\n", + " 2 0.000 0.000 0.000 0.000 {method 'split' of 'str' objects}\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:108(__enter__)\n", + " 2 0.000 0.000 0.000 0.000 parallel.py:862(_print)\n", + " 1 0.000 0.000 0.000 0.000 queue.py:205(_init)\n", + " 2 0.000 0.000 0.000 0.000 {method 'join' of 'str' objects}\n", + " 3 0.000 0.000 0.000 0.000 arraysetops.py:133(_unique_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 _asarray.py:183(ascontiguousarray)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.min}\n", + " 3 0.000 0.000 0.000 0.000 connection.py:142(_check_writable)\n", + " 3 0.000 0.000 0.000 0.000 util.py:44(sub_debug)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:2106(_sum_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 _parallel_backends.py:137(retrieval_context)\n", + " 3 0.000 0.000 0.000 0.000 context.py:187(get_context)\n", + " 2 0.000 0.000 0.000 0.000 context.py:197(get_start_method)\n", + " 4 0.000 0.000 0.000 0.000 _structures.py:32(__neg__)\n", + " 1 0.000 0.000 0.000 0.000 pool.py:933(_get_sentinels)\n", + " 1 0.000 0.000 0.000 0.000 pool.py:263(__del__)\n", + " 2 0.000 0.000 0.000 0.000 version.py:367(_parse_local_version)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2442(_cumsum_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.000 0.000 0.000 0.000 {method 'count' of 'list' objects}\n", + " 3 0.000 0.000 0.000 0.000 {method 'locked' of '_thread.lock' objects}\n", + " 1 0.000 0.000 0.000 0.000 _forest.py:78(_get_n_samples_bootstrap)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2251(_any_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 function_base.py:711(_copy_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:20(_atleast_1d_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 version.py:53()\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:194(_reshape_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 _parallel_backends.py:83(stop_call)\n", + " 2 0.000 0.000 0.000 0.000 version.py:385()\n", + " 1 0.000 0.000 0.000 0.000 :1()\n", + " 1 0.000 0.000 0.000 0.000 _parallel_backends.py:80(start_call)\n", + " 1 0.000 0.000 0.000 0.000 multiarray.py:143(concatenate)\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:59(_recreate_cm)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method builtins.iter}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%prun\n", + "clf.fit(X, y)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "morf", + "language": "python", + "name": "morf" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}