Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
"""
|
2011-11-07 18:15:50 +08:00
|
|
|
Testing for the tree module (sklearn.tree).
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
"""
|
2013-07-22 21:35:31 +08:00
|
|
|
import pickle
|
2013-09-23 15:43:22 +08:00
|
|
|
from functools import partial
|
2013-07-22 21:35:31 +08:00
|
|
|
from itertools import product
|
2014-09-22 21:39:23 +08:00
|
|
|
import platform
|
2013-07-22 21:35:31 +08:00
|
|
|
|
2014-04-04 04:28:08 +08:00
|
|
|
import numpy as np
|
|
|
|
|
from scipy.sparse import csc_matrix
|
|
|
|
|
from scipy.sparse import csr_matrix
|
|
|
|
|
from scipy.sparse import coo_matrix
|
|
|
|
|
|
|
|
|
|
from sklearn.random_projection import sparse_random_matrix
|
|
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
from sklearn.metrics import accuracy_score
|
|
|
|
|
from sklearn.metrics import mean_squared_error
|
|
|
|
|
|
|
|
|
|
from sklearn.utils.testing import assert_array_equal
|
|
|
|
|
from sklearn.utils.testing import assert_array_almost_equal
|
|
|
|
|
from sklearn.utils.testing import assert_almost_equal
|
|
|
|
|
from sklearn.utils.testing import assert_equal
|
2014-02-16 00:22:16 +08:00
|
|
|
from sklearn.utils.testing import assert_in
|
2013-07-22 21:35:31 +08:00
|
|
|
from sklearn.utils.testing import assert_raises
|
|
|
|
|
from sklearn.utils.testing import assert_greater
|
2014-06-28 16:57:39 +08:00
|
|
|
from sklearn.utils.testing import assert_greater_equal
|
2013-07-22 21:35:31 +08:00
|
|
|
from sklearn.utils.testing import assert_less
|
2015-10-20 19:15:24 +08:00
|
|
|
from sklearn.utils.testing import assert_less_equal
|
2014-01-16 06:32:16 +08:00
|
|
|
from sklearn.utils.testing import assert_true
|
2015-10-09 04:49:26 +08:00
|
|
|
from sklearn.utils.testing import assert_warns
|
2014-01-08 23:30:45 +08:00
|
|
|
from sklearn.utils.testing import raises
|
2015-06-06 02:45:45 +08:00
|
|
|
from sklearn.utils.testing import ignore_warnings
|
2015-09-10 11:14:18 +08:00
|
|
|
|
2014-02-11 14:43:24 +08:00
|
|
|
from sklearn.utils.validation import check_random_state
|
2015-06-06 02:45:45 +08:00
|
|
|
|
|
|
|
|
from sklearn.exceptions import NotFittedError
|
2013-07-22 21:35:31 +08:00
|
|
|
|
|
|
|
|
from sklearn.tree import DecisionTreeClassifier
|
|
|
|
|
from sklearn.tree import DecisionTreeRegressor
|
|
|
|
|
from sklearn.tree import ExtraTreeClassifier
|
|
|
|
|
from sklearn.tree import ExtraTreeRegressor
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2011-09-15 02:36:43 +08:00
|
|
|
from sklearn import tree
|
2014-04-04 04:28:08 +08:00
|
|
|
from sklearn.tree.tree import SPARSE_SPLITTERS
|
|
|
|
|
from sklearn.tree._tree import TREE_LEAF
|
2011-09-15 02:36:43 +08:00
|
|
|
from sklearn import datasets
|
2013-07-22 21:35:31 +08:00
|
|
|
|
2014-01-06 00:54:47 +08:00
|
|
|
from sklearn.preprocessing._weights import _balance_weights
|
2013-07-29 00:45:20 +08:00
|
|
|
|
|
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
CLF_CRITERIONS = ("gini", "entropy")
|
|
|
|
|
REG_CRITERIONS = ("mse", )
|
|
|
|
|
|
|
|
|
|
CLF_TREES = {
|
|
|
|
|
"DecisionTreeClassifier": DecisionTreeClassifier,
|
2013-09-23 15:43:22 +08:00
|
|
|
"Presort-DecisionTreeClassifier": partial(DecisionTreeClassifier,
|
2015-09-11 16:39:21 +08:00
|
|
|
presort=True),
|
2013-07-22 21:35:31 +08:00
|
|
|
"ExtraTreeClassifier": ExtraTreeClassifier,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
REG_TREES = {
|
|
|
|
|
"DecisionTreeRegressor": DecisionTreeRegressor,
|
2013-09-23 15:43:22 +08:00
|
|
|
"Presort-DecisionTreeRegressor": partial(DecisionTreeRegressor,
|
2015-09-11 16:39:21 +08:00
|
|
|
presort=True),
|
2013-07-22 21:35:31 +08:00
|
|
|
"ExtraTreeRegressor": ExtraTreeRegressor,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ALL_TREES = dict()
|
|
|
|
|
ALL_TREES.update(CLF_TREES)
|
|
|
|
|
ALL_TREES.update(REG_TREES)
|
|
|
|
|
|
2015-09-11 16:39:21 +08:00
|
|
|
SPARSE_TREES = ["DecisionTreeClassifier", "DecisionTreeRegressor",
|
|
|
|
|
"ExtraTreeClassifier", "ExtraTreeRegressor"]
|
2014-04-04 04:28:08 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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]
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
|
|
|
|
# toy sample
|
|
|
|
|
X = [[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1]]
|
2011-09-26 00:59:29 +08:00
|
|
|
y = [-1, -1, -1, 1, 1, 1]
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
T = [[-1, -1], [2, 2], [3, 2]]
|
2011-08-16 04:40:55 +08:00
|
|
|
true_result = [-1, 1, 1]
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
|
|
|
|
# also load the iris dataset
|
|
|
|
|
# and randomly permute it
|
|
|
|
|
iris = datasets.load_iris()
|
2011-09-26 00:54:10 +08:00
|
|
|
rng = np.random.RandomState(1)
|
|
|
|
|
perm = rng.permutation(iris.target.size)
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
iris.data = iris.data[perm]
|
|
|
|
|
iris.target = iris.target[perm]
|
|
|
|
|
|
|
|
|
|
# also load the boston dataset
|
|
|
|
|
# and randomly permute it
|
|
|
|
|
boston = datasets.load_boston()
|
2011-09-26 00:54:10 +08:00
|
|
|
perm = rng.permutation(boston.target.size)
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
boston.data = boston.data[perm]
|
|
|
|
|
boston.target = boston.target[perm]
|
|
|
|
|
|
2014-04-04 04:28:08 +08:00
|
|
|
digits = datasets.load_digits()
|
|
|
|
|
perm = rng.permutation(digits.target.size)
|
|
|
|
|
digits.data = digits.data[perm]
|
|
|
|
|
digits.target = digits.target[perm]
|
|
|
|
|
|
|
|
|
|
random_state = check_random_state(0)
|
|
|
|
|
X_multilabel, y_multilabel = datasets.make_multilabel_classification(
|
2015-07-02 20:02:54 +08:00
|
|
|
random_state=0, n_samples=30, n_features=10)
|
2014-04-04 04:28:08 +08:00
|
|
|
|
|
|
|
|
X_sparse_pos = random_state.uniform(size=(20, 5))
|
|
|
|
|
X_sparse_pos[X_sparse_pos <= 0.8] = 0.
|
|
|
|
|
y_random = random_state.randint(0, 4, size=(20, ))
|
|
|
|
|
X_sparse_mix = sparse_random_matrix(20, 10, density=0.25, random_state=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DATASETS = {
|
|
|
|
|
"iris": {"X": iris.data, "y": iris.target},
|
|
|
|
|
"boston": {"X": boston.data, "y": boston.target},
|
|
|
|
|
"digits": {"X": digits.data, "y": digits.target},
|
|
|
|
|
"toy": {"X": X, "y": y},
|
|
|
|
|
"clf_small": {"X": X_small, "y": y_small},
|
|
|
|
|
"reg_small": {"X": X_small, "y": y_small_reg},
|
|
|
|
|
"multilabel": {"X": X_multilabel, "y": y_multilabel},
|
|
|
|
|
"sparse-pos": {"X": X_sparse_pos, "y": y_random},
|
|
|
|
|
"sparse-neg": {"X": - X_sparse_pos, "y": y_random},
|
|
|
|
|
"sparse-mix": {"X": X_sparse_mix, "y": y_random},
|
|
|
|
|
"zeros": {"X": np.zeros((20, 3)), "y": y_random}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name in DATASETS:
|
|
|
|
|
DATASETS[name]["X_sparse"] = csc_matrix(DATASETS[name]["X"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assert_tree_equal(d, s, message):
|
|
|
|
|
assert_equal(s.node_count, d.node_count,
|
|
|
|
|
"{0}: inequal number of node ({1} != {2})"
|
|
|
|
|
"".format(message, s.node_count, d.node_count))
|
|
|
|
|
|
|
|
|
|
assert_array_equal(d.children_right, s.children_right,
|
|
|
|
|
message + ": inequal children_right")
|
|
|
|
|
assert_array_equal(d.children_left, s.children_left,
|
|
|
|
|
message + ": inequal children_left")
|
|
|
|
|
|
|
|
|
|
external = d.children_right == TREE_LEAF
|
|
|
|
|
internal = np.logical_not(external)
|
|
|
|
|
|
|
|
|
|
assert_array_equal(d.feature[internal], s.feature[internal],
|
|
|
|
|
message + ": inequal features")
|
|
|
|
|
assert_array_equal(d.threshold[internal], s.threshold[internal],
|
|
|
|
|
message + ": inequal threshold")
|
|
|
|
|
assert_array_equal(d.n_node_samples.sum(), s.n_node_samples.sum(),
|
|
|
|
|
message + ": inequal sum(n_node_samples)")
|
|
|
|
|
assert_array_equal(d.n_node_samples, s.n_node_samples,
|
|
|
|
|
message + ": inequal n_node_samples")
|
|
|
|
|
|
|
|
|
|
assert_almost_equal(d.impurity, s.impurity,
|
|
|
|
|
err_msg=message + ": inequal impurity")
|
|
|
|
|
|
|
|
|
|
assert_array_almost_equal(d.value[external], s.value[external],
|
|
|
|
|
err_msg=message + ": inequal value")
|
|
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
|
|
|
|
def test_classification_toy():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check classification on a toy dataset.
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, Tree in CLF_TREES.items():
|
|
|
|
|
clf = Tree(random_state=0)
|
|
|
|
|
clf.fit(X, y)
|
|
|
|
|
assert_array_equal(clf.predict(T), true_result,
|
|
|
|
|
"Failed with {0}".format(name))
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = Tree(max_features=1, random_state=1)
|
|
|
|
|
clf.fit(X, y)
|
|
|
|
|
assert_array_equal(clf.predict(T), true_result,
|
|
|
|
|
"Failed with {0}".format(name))
|
2012-12-23 20:13:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_weighted_classification_toy():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check classification on a weighted toy dataset.
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, Tree in CLF_TREES.items():
|
|
|
|
|
clf = Tree(random_state=0)
|
2012-12-23 20:13:21 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf.fit(X, y, sample_weight=np.ones(len(X)))
|
|
|
|
|
assert_array_equal(clf.predict(T), true_result,
|
|
|
|
|
"Failed with {0}".format(name))
|
2011-11-04 20:08:01 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf.fit(X, y, sample_weight=np.ones(len(X)) * 0.5)
|
|
|
|
|
assert_array_equal(clf.predict(T), true_result,
|
|
|
|
|
"Failed with {0}".format(name))
|
2011-11-04 20:08:01 +08:00
|
|
|
|
2011-09-06 06:04:46 +08:00
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
def test_regression_toy():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check regression on a toy dataset.
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, Tree in REG_TREES.items():
|
|
|
|
|
reg = Tree(random_state=1)
|
|
|
|
|
reg.fit(X, y)
|
|
|
|
|
assert_almost_equal(reg.predict(T), true_result,
|
|
|
|
|
err_msg="Failed with {0}".format(name))
|
2011-11-04 20:08:01 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = Tree(max_features=1, random_state=1)
|
|
|
|
|
clf.fit(X, y)
|
|
|
|
|
assert_almost_equal(reg.predict(T), true_result,
|
|
|
|
|
err_msg="Failed with {0}".format(name))
|
2011-11-04 20:08:01 +08:00
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2012-10-22 21:36:36 +08:00
|
|
|
def test_xor():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check on a XOR problem
|
2012-10-22 21:36:36 +08:00
|
|
|
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()
|
|
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, Tree in CLF_TREES.items():
|
|
|
|
|
clf = Tree(random_state=0)
|
|
|
|
|
clf.fit(X, y)
|
|
|
|
|
assert_equal(clf.score(X, y), 1.0,
|
|
|
|
|
"Failed with {0}".format(name))
|
2012-10-22 21:36:36 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = Tree(random_state=0, max_features=1)
|
|
|
|
|
clf.fit(X, y)
|
|
|
|
|
assert_equal(clf.score(X, y), 1.0,
|
|
|
|
|
"Failed with {0}".format(name))
|
2012-10-22 21:36:36 +08:00
|
|
|
|
2012-10-31 18:24:25 +08:00
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
def test_iris():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check consistency on dataset iris.
|
2013-07-22 21:35:31 +08:00
|
|
|
for (name, Tree), criterion in product(CLF_TREES.items(), CLF_CRITERIONS):
|
|
|
|
|
clf = Tree(criterion=criterion, random_state=0)
|
|
|
|
|
clf.fit(iris.data, iris.target)
|
|
|
|
|
score = accuracy_score(clf.predict(iris.data), iris.target)
|
|
|
|
|
assert_greater(score, 0.9,
|
|
|
|
|
"Failed with {0}, criterion = {1} and score = {2}"
|
|
|
|
|
"".format(name, criterion, score))
|
|
|
|
|
|
|
|
|
|
clf = Tree(criterion=criterion, max_features=2, random_state=0)
|
|
|
|
|
clf.fit(iris.data, iris.target)
|
|
|
|
|
score = accuracy_score(clf.predict(iris.data), iris.target)
|
|
|
|
|
assert_greater(score, 0.5,
|
|
|
|
|
"Failed with {0}, criterion = {1} and score = {2}"
|
|
|
|
|
"".format(name, criterion, score))
|
2011-11-04 20:08:01 +08:00
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
|
|
|
|
def test_boston():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check consistency on dataset boston house prices.
|
2011-09-06 06:04:46 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for (name, Tree), criterion in product(REG_TREES.items(), REG_CRITERIONS):
|
|
|
|
|
reg = Tree(criterion=criterion, random_state=0)
|
|
|
|
|
reg.fit(boston.data, boston.target)
|
|
|
|
|
score = mean_squared_error(boston.target, reg.predict(boston.data))
|
|
|
|
|
assert_less(score, 1,
|
|
|
|
|
"Failed with {0}, criterion = {1} and score = {2}"
|
|
|
|
|
"".format(name, criterion, score))
|
2011-11-04 20:08:01 +08:00
|
|
|
|
2012-12-23 20:13:21 +08:00
|
|
|
# using fewer features reduces the learning ability of this tree,
|
2011-11-04 20:08:01 +08:00
|
|
|
# but reduces training time.
|
2013-07-22 21:35:31 +08:00
|
|
|
reg = Tree(criterion=criterion, max_features=6, random_state=0)
|
|
|
|
|
reg.fit(boston.data, boston.target)
|
|
|
|
|
score = mean_squared_error(boston.target, reg.predict(boston.data))
|
|
|
|
|
assert_less(score, 2,
|
|
|
|
|
"Failed with {0}, criterion = {1} and score = {2}"
|
|
|
|
|
"".format(name, criterion, score))
|
2011-11-04 20:08:01 +08:00
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
|
|
|
|
def test_probability():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Predict probabilities using DecisionTreeClassifier.
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, Tree in CLF_TREES.items():
|
|
|
|
|
clf = Tree(max_depth=1, max_features=1, random_state=42)
|
|
|
|
|
clf.fit(iris.data, iris.target)
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
prob_predict = clf.predict_proba(iris.data)
|
|
|
|
|
assert_array_almost_equal(np.sum(prob_predict, 1),
|
|
|
|
|
np.ones(iris.data.shape[0]),
|
|
|
|
|
err_msg="Failed with {0}".format(name))
|
|
|
|
|
assert_array_equal(np.argmax(prob_predict, 1),
|
|
|
|
|
clf.predict(iris.data),
|
|
|
|
|
err_msg="Failed with {0}".format(name))
|
|
|
|
|
assert_almost_equal(clf.predict_proba(iris.data),
|
|
|
|
|
np.exp(clf.predict_log_proba(iris.data)), 8,
|
|
|
|
|
err_msg="Failed with {0}".format(name))
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2011-09-06 06:04:46 +08:00
|
|
|
|
2011-11-17 02:59:25 +08:00
|
|
|
def test_arrayrepr():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check the array representation.
|
2011-11-17 02:59:25 +08:00
|
|
|
# Check resize
|
|
|
|
|
X = np.arange(10000)[:, np.newaxis]
|
|
|
|
|
y = np.arange(10000)
|
2013-07-22 21:35:31 +08:00
|
|
|
|
|
|
|
|
for name, Tree in REG_TREES.items():
|
|
|
|
|
reg = Tree(max_depth=None, random_state=0)
|
|
|
|
|
reg.fit(X, y)
|
2011-11-17 02:59:25 +08:00
|
|
|
|
|
|
|
|
|
2012-07-12 00:44:15 +08:00
|
|
|
def test_pure_set():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check when y is pure.
|
2012-07-12 00:44:15 +08:00
|
|
|
X = [[-2, -1], [-1, -1], [-1, -2], [1, 1], [1, 2], [2, 1]]
|
|
|
|
|
y = [1, 1, 1, 1, 1, 1]
|
|
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeClassifier in CLF_TREES.items():
|
|
|
|
|
clf = TreeClassifier(random_state=0)
|
|
|
|
|
clf.fit(X, y)
|
|
|
|
|
assert_array_equal(clf.predict(X), y,
|
|
|
|
|
err_msg="Failed with {0}".format(name))
|
2012-07-12 00:44:15 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeRegressor in REG_TREES.items():
|
|
|
|
|
reg = TreeRegressor(random_state=0)
|
|
|
|
|
reg.fit(X, y)
|
|
|
|
|
assert_almost_equal(clf.predict(X), y,
|
|
|
|
|
err_msg="Failed with {0}".format(name))
|
2012-07-12 00:44:15 +08:00
|
|
|
|
|
|
|
|
|
2011-10-09 17:18:36 +08:00
|
|
|
def test_numerical_stability():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check numerical stability.
|
2012-11-26 21:57:51 +08:00
|
|
|
X = np.array([
|
|
|
|
|
[152.08097839, 140.40744019, 129.75102234, 159.90493774],
|
2011-11-29 06:39:46 +08:00
|
|
|
[142.50700378, 135.81935120, 117.82884979, 162.75781250],
|
|
|
|
|
[127.28772736, 140.40744019, 129.75102234, 159.90493774],
|
|
|
|
|
[132.37025452, 143.71923828, 138.35694885, 157.84558105],
|
|
|
|
|
[103.10237122, 143.71928406, 138.35696411, 157.84559631],
|
|
|
|
|
[127.71276855, 143.71923828, 138.35694885, 157.84558105],
|
|
|
|
|
[120.91514587, 140.40744019, 129.75102234, 159.90493774]])
|
2011-10-09 17:18:36 +08:00
|
|
|
|
|
|
|
|
y = np.array(
|
2013-11-02 17:36:19 +08:00
|
|
|
[1., 0.70209277, 0.53896582, 0., 0.90914464, 0.48026916, 0.49622521])
|
2011-10-09 17:18:36 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
with np.errstate(all="raise"):
|
|
|
|
|
for name, Tree in REG_TREES.items():
|
|
|
|
|
reg = Tree(random_state=0)
|
|
|
|
|
reg.fit(X, y)
|
|
|
|
|
reg.fit(X, -y)
|
|
|
|
|
reg.fit(-X, y)
|
|
|
|
|
reg.fit(-X, -y)
|
2011-10-09 17:18:36 +08:00
|
|
|
|
|
|
|
|
|
2013-07-05 17:20:17 +08:00
|
|
|
def test_importances():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check variable importances.
|
2013-09-16 17:58:25 +08:00
|
|
|
X, y = datasets.make_classification(n_samples=2000,
|
2013-07-05 17:20:17 +08:00
|
|
|
n_features=10,
|
|
|
|
|
n_informative=3,
|
|
|
|
|
n_redundant=0,
|
|
|
|
|
n_repeated=0,
|
|
|
|
|
shuffle=False,
|
|
|
|
|
random_state=0)
|
2011-12-19 19:33:44 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, Tree in CLF_TREES.items():
|
|
|
|
|
clf = Tree(random_state=0)
|
2014-01-08 21:14:06 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf.fit(X, y)
|
|
|
|
|
importances = clf.feature_importances_
|
|
|
|
|
n_important = np.sum(importances > 0.1)
|
2011-12-19 19:33:44 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_equal(importances.shape[0], 10, "Failed with {0}".format(name))
|
|
|
|
|
assert_equal(n_important, 3, "Failed with {0}".format(name))
|
2011-12-19 19:33:44 +08:00
|
|
|
|
2015-10-09 04:49:26 +08:00
|
|
|
X_new = assert_warns(
|
|
|
|
|
DeprecationWarning, clf.transform, X, threshold="mean")
|
|
|
|
|
assert_less(0, X_new.shape[1], "Failed with {0}".format(name))
|
|
|
|
|
assert_less(X_new.shape[1], X.shape[1], "Failed with {0}".format(name))
|
2011-12-28 22:29:22 +08:00
|
|
|
|
2014-04-11 02:27:37 +08:00
|
|
|
# Check on iris that importances are the same for all builders
|
|
|
|
|
clf = DecisionTreeClassifier(random_state=0)
|
|
|
|
|
clf.fit(iris.data, iris.target)
|
|
|
|
|
clf2 = DecisionTreeClassifier(random_state=0,
|
|
|
|
|
max_leaf_nodes=len(iris.data))
|
|
|
|
|
clf2.fit(iris.data, iris.target)
|
|
|
|
|
|
|
|
|
|
assert_array_equal(clf.feature_importances_,
|
|
|
|
|
clf2.feature_importances_)
|
|
|
|
|
|
2011-12-19 19:33:44 +08:00
|
|
|
|
2014-01-08 23:30:45 +08:00
|
|
|
@raises(ValueError)
|
|
|
|
|
def test_importances_raises():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check if variable importance before fit raises ValueError.
|
2014-01-08 23:30:45 +08:00
|
|
|
clf = DecisionTreeClassifier()
|
|
|
|
|
clf.feature_importances_
|
|
|
|
|
|
|
|
|
|
|
2014-01-14 00:11:20 +08:00
|
|
|
def test_importances_gini_equal_mse():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check that gini is equivalent to mse for binary output variable
|
2014-01-14 00:11:20 +08:00
|
|
|
|
|
|
|
|
X, y = datasets.make_classification(n_samples=2000,
|
|
|
|
|
n_features=10,
|
|
|
|
|
n_informative=3,
|
|
|
|
|
n_redundant=0,
|
|
|
|
|
n_repeated=0,
|
|
|
|
|
shuffle=False,
|
|
|
|
|
random_state=0)
|
|
|
|
|
|
2014-04-04 16:11:26 +08:00
|
|
|
# The gini index and the mean square error (variance) might differ due
|
|
|
|
|
# to numerical instability. Since those instabilities mainly occurs at
|
|
|
|
|
# high tree depth, we restrict this maximal depth.
|
2014-06-08 23:56:20 +08:00
|
|
|
clf = DecisionTreeClassifier(criterion="gini", max_depth=5,
|
2014-04-04 16:11:26 +08:00
|
|
|
random_state=0).fit(X, y)
|
2014-06-08 23:56:20 +08:00
|
|
|
reg = DecisionTreeRegressor(criterion="mse", max_depth=5,
|
2014-04-04 16:11:26 +08:00
|
|
|
random_state=0).fit(X, y)
|
2014-01-14 00:11:20 +08:00
|
|
|
|
|
|
|
|
assert_almost_equal(clf.feature_importances_, reg.feature_importances_)
|
|
|
|
|
assert_array_equal(clf.tree_.feature, reg.tree_.feature)
|
|
|
|
|
assert_array_equal(clf.tree_.children_left, reg.tree_.children_left)
|
|
|
|
|
assert_array_equal(clf.tree_.children_right, reg.tree_.children_right)
|
|
|
|
|
assert_array_equal(clf.tree_.n_node_samples, reg.tree_.n_node_samples)
|
|
|
|
|
|
|
|
|
|
|
2013-02-25 21:41:43 +08:00
|
|
|
def test_max_features():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check max_features.
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeRegressor in REG_TREES.items():
|
|
|
|
|
reg = TreeRegressor(max_features="auto")
|
|
|
|
|
reg.fit(boston.data, boston.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(reg.max_features_, boston.data.shape[1])
|
2013-07-22 21:35:31 +08:00
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
for name, TreeClassifier in CLF_TREES.items():
|
|
|
|
|
clf = TreeClassifier(max_features="auto")
|
|
|
|
|
clf.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(clf.max_features_, 2)
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
for name, TreeEstimator in ALL_TREES.items():
|
|
|
|
|
est = TreeEstimator(max_features="sqrt")
|
|
|
|
|
est.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(est.max_features_,
|
2013-07-22 22:04:55 +08:00
|
|
|
int(np.sqrt(iris.data.shape[1])))
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
est = TreeEstimator(max_features="log2")
|
|
|
|
|
est.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(est.max_features_,
|
2013-07-22 22:04:55 +08:00
|
|
|
int(np.log2(iris.data.shape[1])))
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
est = TreeEstimator(max_features=1)
|
|
|
|
|
est.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(est.max_features_, 1)
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
est = TreeEstimator(max_features=3)
|
|
|
|
|
est.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(est.max_features_, 3)
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2014-05-28 23:03:48 +08:00
|
|
|
est = TreeEstimator(max_features=0.01)
|
|
|
|
|
est.fit(iris.data, iris.target)
|
|
|
|
|
assert_equal(est.max_features_, 1)
|
|
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
est = TreeEstimator(max_features=0.5)
|
|
|
|
|
est.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(est.max_features_,
|
2013-07-22 22:04:55 +08:00
|
|
|
int(0.5 * iris.data.shape[1]))
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
est = TreeEstimator(max_features=1.0)
|
|
|
|
|
est.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(est.max_features_, iris.data.shape[1])
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 22:04:55 +08:00
|
|
|
est = TreeEstimator(max_features=None)
|
|
|
|
|
est.fit(iris.data, iris.target)
|
2013-09-12 19:57:47 +08:00
|
|
|
assert_equal(est.max_features_, iris.data.shape[1])
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
# use values of max_features that are invalid
|
|
|
|
|
est = TreeEstimator(max_features=10)
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
est = TreeEstimator(max_features=-1)
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
est = TreeEstimator(max_features=0.0)
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
2013-02-25 21:41:43 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
est = TreeEstimator(max_features=1.5)
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
2013-02-25 22:29:50 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
est = TreeEstimator(max_features="foobar")
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
2013-02-25 21:41:43 +08:00
|
|
|
|
|
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
def test_error():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Test that it gives proper exception on deficient input.
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeEstimator in CLF_TREES.items():
|
|
|
|
|
# predict before fit
|
|
|
|
|
est = TreeEstimator()
|
2015-04-08 18:16:09 +08:00
|
|
|
assert_raises(NotFittedError, est.predict_proba, X)
|
2012-12-23 20:13:21 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
est.fit(X, y)
|
2015-08-25 07:52:40 +08:00
|
|
|
X2 = [[-2, -1, 1]] # wrong feature shape for sample
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_raises(ValueError, est.predict_proba, X2)
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeEstimator in ALL_TREES.items():
|
|
|
|
|
# Invalid values for parameters
|
|
|
|
|
assert_raises(ValueError, TreeEstimator(min_samples_leaf=-1).fit, X, y)
|
2014-06-30 08:53:14 +08:00
|
|
|
assert_raises(ValueError,
|
|
|
|
|
TreeEstimator(min_weight_fraction_leaf=-1).fit,
|
2014-05-27 18:39:54 +08:00
|
|
|
X, y)
|
2014-06-30 08:53:14 +08:00
|
|
|
assert_raises(ValueError,
|
|
|
|
|
TreeEstimator(min_weight_fraction_leaf=0.51).fit,
|
2014-05-27 18:39:54 +08:00
|
|
|
X, y)
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_raises(ValueError, TreeEstimator(min_samples_split=-1).fit,
|
|
|
|
|
X, y)
|
|
|
|
|
assert_raises(ValueError, TreeEstimator(max_depth=-1).fit, X, y)
|
|
|
|
|
assert_raises(ValueError, TreeEstimator(max_features=42).fit, X, y)
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
# Wrong dimensions
|
|
|
|
|
est = TreeEstimator()
|
|
|
|
|
y2 = y[:-1]
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y2)
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
# Test with arrays that are non-contiguous.
|
|
|
|
|
Xf = np.asfortranarray(X)
|
|
|
|
|
est = TreeEstimator()
|
|
|
|
|
est.fit(Xf, y)
|
|
|
|
|
assert_almost_equal(est.predict(T), true_result)
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
# predict before fitting
|
|
|
|
|
est = TreeEstimator()
|
2015-04-08 18:16:09 +08:00
|
|
|
assert_raises(NotFittedError, est.predict, T)
|
2012-12-23 20:13:21 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
# predict on vector with different dims
|
|
|
|
|
est.fit(X, y)
|
|
|
|
|
t = np.asarray(T)
|
|
|
|
|
assert_raises(ValueError, est.predict, t[:, 1:])
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
# wrong sample shape
|
|
|
|
|
Xt = np.array(X).T
|
2011-09-06 06:04:46 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
est = TreeEstimator()
|
|
|
|
|
est.fit(np.dot(X, Xt), y)
|
|
|
|
|
assert_raises(ValueError, est.predict, X)
|
2015-04-16 16:19:57 +08:00
|
|
|
assert_raises(ValueError, est.apply, X)
|
2011-09-06 06:04:46 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = TreeEstimator()
|
|
|
|
|
clf.fit(X, y)
|
|
|
|
|
assert_raises(ValueError, clf.predict, Xt)
|
2015-04-16 16:19:57 +08:00
|
|
|
assert_raises(ValueError, clf.apply, Xt)
|
2011-09-06 06:04:46 +08:00
|
|
|
|
2015-04-08 18:16:09 +08:00
|
|
|
# apply before fitting
|
|
|
|
|
est = TreeEstimator()
|
|
|
|
|
assert_raises(NotFittedError, est.apply, T)
|
|
|
|
|
|
Refactored decision trees and forests to support CART algorithm.
Notable changes:
1) Supports classification and regression
2) 3 classification criteria, 1 regression criterion
3) A new dataset is provided to test regression (Boston House Prices)
4) Weights are removed from the algorithm entirely. If the need for weights can be justified, I would welcome reintroducing them, but for the refactoring I left them out.
5) The subset of dimensions (F) to split on is fixed for the entire tree, not at each node. This is more in line with CART and RandomForests.
6) A max_depth parameter is offered to limit the size of the constructed trees.
7) Randomisation is fixed with python's random module, but can be seeded.
8) For classification, the number of classes must be provided when the tree is constructed. This is because the tree cannot necessarily infer the correct number of labels at the time of training if only a subset of the data is used for individual trees.
9) For classification, labels are not normalised internally. Labels must be provided to the algorithm in the range [0, ..., K)
10) For classification, the leaf nodes retain the distribution of classes. This means that it is possible to query the tree for the probability distribution of a test sample
2011-07-29 19:20:03 +08:00
|
|
|
|
2012-07-11 20:22:14 +08:00
|
|
|
def test_min_samples_leaf():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Test if leaves contain more than leaf_count training examples
|
2012-07-11 20:22:14 +08:00
|
|
|
X = np.asfortranarray(iris.data.astype(tree._tree.DTYPE))
|
|
|
|
|
y = iris.target
|
|
|
|
|
|
2014-06-05 06:49:59 +08:00
|
|
|
# test both DepthFirstTreeBuilder and BestFirstTreeBuilder
|
2014-06-30 08:53:14 +08:00
|
|
|
# by setting max_leaf_nodes
|
2014-06-05 06:49:59 +08:00
|
|
|
for max_leaf_nodes in (None, 1000):
|
|
|
|
|
for name, TreeEstimator in ALL_TREES.items():
|
|
|
|
|
est = TreeEstimator(min_samples_leaf=5,
|
|
|
|
|
max_leaf_nodes=max_leaf_nodes,
|
|
|
|
|
random_state=0)
|
|
|
|
|
est.fit(X, y)
|
|
|
|
|
out = est.tree_.apply(X)
|
|
|
|
|
node_counts = np.bincount(out)
|
|
|
|
|
# drop inner nodes
|
|
|
|
|
leaf_count = node_counts[node_counts != 0]
|
|
|
|
|
assert_greater(np.min(leaf_count), 4,
|
|
|
|
|
"Failed with {0}".format(name))
|
2012-07-10 17:55:38 +08:00
|
|
|
|
|
|
|
|
|
2014-09-30 15:44:48 +08:00
|
|
|
def check_min_weight_fraction_leaf(name, datasets, sparse=False):
|
2014-05-27 18:39:54 +08:00
|
|
|
"""Test if leaves contain at least min_weight_fraction_leaf of the
|
|
|
|
|
training set"""
|
2014-09-30 15:44:48 +08:00
|
|
|
if sparse:
|
|
|
|
|
X = DATASETS[datasets]["X_sparse"].astype(np.float32)
|
|
|
|
|
else:
|
|
|
|
|
X = DATASETS[datasets]["X"].astype(np.float32)
|
|
|
|
|
y = DATASETS[datasets]["y"]
|
|
|
|
|
|
2014-05-27 18:39:54 +08:00
|
|
|
weights = rng.rand(X.shape[0])
|
|
|
|
|
total_weight = np.sum(weights)
|
|
|
|
|
|
2014-09-30 15:44:48 +08:00
|
|
|
TreeEstimator = ALL_TREES[name]
|
|
|
|
|
|
2014-06-05 06:49:59 +08:00
|
|
|
# test both DepthFirstTreeBuilder and BestFirstTreeBuilder
|
2014-06-30 08:53:14 +08:00
|
|
|
# by setting max_leaf_nodes
|
2014-09-30 15:44:48 +08:00
|
|
|
for max_leaf_nodes, frac in product((None, 1000), np.linspace(0, 0.5, 6)):
|
2014-06-30 08:53:14 +08:00
|
|
|
est = TreeEstimator(min_weight_fraction_leaf=frac,
|
|
|
|
|
max_leaf_nodes=max_leaf_nodes,
|
|
|
|
|
random_state=0)
|
|
|
|
|
est.fit(X, y, sample_weight=weights)
|
2014-09-30 15:44:48 +08:00
|
|
|
|
|
|
|
|
if sparse:
|
|
|
|
|
out = est.tree_.apply(X.tocsr())
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
out = est.tree_.apply(X)
|
|
|
|
|
|
2014-06-30 08:53:14 +08:00
|
|
|
node_weights = np.bincount(out, weights=weights)
|
|
|
|
|
# drop inner nodes
|
|
|
|
|
leaf_weights = node_weights[node_weights != 0]
|
|
|
|
|
assert_greater_equal(
|
|
|
|
|
np.min(leaf_weights),
|
|
|
|
|
total_weight * est.min_weight_fraction_leaf,
|
|
|
|
|
"Failed with {0} "
|
|
|
|
|
"min_weight_fraction_leaf={1}".format(
|
|
|
|
|
name, est.min_weight_fraction_leaf))
|
2014-05-27 18:39:54 +08:00
|
|
|
|
|
|
|
|
|
2014-09-30 15:44:48 +08:00
|
|
|
def test_min_weight_fraction_leaf():
|
|
|
|
|
# Check on dense input
|
|
|
|
|
for name in ALL_TREES:
|
|
|
|
|
yield check_min_weight_fraction_leaf, name, "iris"
|
|
|
|
|
|
|
|
|
|
# Check on sparse input
|
|
|
|
|
for name in SPARSE_TREES:
|
|
|
|
|
yield check_min_weight_fraction_leaf, name, "multilabel", True
|
|
|
|
|
|
|
|
|
|
|
2013-07-05 04:37:20 +08:00
|
|
|
def test_pickle():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check that tree estimator are pickable
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeClassifier in CLF_TREES.items():
|
|
|
|
|
clf = TreeClassifier(random_state=0)
|
|
|
|
|
clf.fit(iris.data, iris.target)
|
|
|
|
|
score = clf.score(iris.data, iris.target)
|
|
|
|
|
|
|
|
|
|
serialized_object = pickle.dumps(clf)
|
|
|
|
|
clf2 = pickle.loads(serialized_object)
|
|
|
|
|
assert_equal(type(clf2), clf.__class__)
|
|
|
|
|
score2 = clf2.score(iris.data, iris.target)
|
|
|
|
|
assert_equal(score, score2, "Failed to generate same score "
|
|
|
|
|
"after pickling (classification) "
|
|
|
|
|
"with {0}".format(name))
|
|
|
|
|
|
|
|
|
|
for name, TreeRegressor in REG_TREES.items():
|
|
|
|
|
reg = TreeRegressor(random_state=0)
|
|
|
|
|
reg.fit(boston.data, boston.target)
|
|
|
|
|
score = reg.score(boston.data, boston.target)
|
|
|
|
|
|
|
|
|
|
serialized_object = pickle.dumps(reg)
|
|
|
|
|
reg2 = pickle.loads(serialized_object)
|
|
|
|
|
assert_equal(type(reg2), reg.__class__)
|
|
|
|
|
score2 = reg2.score(boston.data, boston.target)
|
|
|
|
|
assert_equal(score, score2, "Failed to generate same score "
|
|
|
|
|
"after pickling (regression) "
|
|
|
|
|
"with {0}".format(name))
|
2011-09-22 22:29:32 +08:00
|
|
|
|
|
|
|
|
|
2012-06-29 21:16:50 +08:00
|
|
|
def test_multioutput():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check estimators on multi-output problems.
|
2012-07-02 15:56:24 +08:00
|
|
|
X = [[-2, -1],
|
|
|
|
|
[-1, -1],
|
|
|
|
|
[-1, -2],
|
|
|
|
|
[1, 1],
|
|
|
|
|
[1, 2],
|
|
|
|
|
[2, 1],
|
|
|
|
|
[-2, 1],
|
|
|
|
|
[-1, 1],
|
|
|
|
|
[-1, 2],
|
|
|
|
|
[2, -1],
|
|
|
|
|
[1, -1],
|
|
|
|
|
[1, -2]]
|
|
|
|
|
|
|
|
|
|
y = [[-1, 0],
|
|
|
|
|
[-1, 0],
|
|
|
|
|
[-1, 0],
|
|
|
|
|
[1, 1],
|
|
|
|
|
[1, 1],
|
|
|
|
|
[1, 1],
|
|
|
|
|
[-1, 2],
|
|
|
|
|
[-1, 2],
|
|
|
|
|
[-1, 2],
|
|
|
|
|
[1, 3],
|
|
|
|
|
[1, 3],
|
|
|
|
|
[1, 3]]
|
|
|
|
|
|
|
|
|
|
T = [[-1, -1], [1, 1], [-1, 1], [1, -1]]
|
|
|
|
|
y_true = [[-1, 0], [1, 1], [-1, 2], [1, 3]]
|
2012-06-29 21:16:50 +08:00
|
|
|
|
|
|
|
|
# toy classification problem
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeClassifier in CLF_TREES.items():
|
|
|
|
|
clf = TreeClassifier(random_state=0)
|
|
|
|
|
y_hat = clf.fit(X, y).predict(T)
|
|
|
|
|
assert_array_equal(y_hat, y_true)
|
|
|
|
|
assert_equal(y_hat.shape, (4, 2))
|
|
|
|
|
|
|
|
|
|
proba = clf.predict_proba(T)
|
|
|
|
|
assert_equal(len(proba), 2)
|
|
|
|
|
assert_equal(proba[0].shape, (4, 2))
|
|
|
|
|
assert_equal(proba[1].shape, (4, 4))
|
|
|
|
|
|
|
|
|
|
log_proba = clf.predict_log_proba(T)
|
|
|
|
|
assert_equal(len(log_proba), 2)
|
|
|
|
|
assert_equal(log_proba[0].shape, (4, 2))
|
|
|
|
|
assert_equal(log_proba[1].shape, (4, 4))
|
2012-07-02 17:51:50 +08:00
|
|
|
|
2012-06-29 21:16:50 +08:00
|
|
|
# toy regression problem
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeRegressor in REG_TREES.items():
|
|
|
|
|
reg = TreeRegressor(random_state=0)
|
|
|
|
|
y_hat = reg.fit(X, y).predict(T)
|
|
|
|
|
assert_almost_equal(y_hat, y_true)
|
|
|
|
|
assert_equal(y_hat.shape, (4, 2))
|
2012-06-29 21:16:50 +08:00
|
|
|
|
|
|
|
|
|
2012-12-04 21:25:14 +08:00
|
|
|
def test_classes_shape():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Test that n_classes_ and classes_ have proper shape.
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeClassifier in CLF_TREES.items():
|
|
|
|
|
# Classification, single output
|
|
|
|
|
clf = TreeClassifier(random_state=0)
|
|
|
|
|
clf.fit(X, y)
|
2012-12-04 21:25:14 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_equal(clf.n_classes_, 2)
|
|
|
|
|
assert_array_equal(clf.classes_, [-1, 1])
|
2012-12-04 21:25:14 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
# Classification, multi-output
|
|
|
|
|
_y = np.vstack((y, np.array(y) * 2)).T
|
|
|
|
|
clf = TreeClassifier(random_state=0)
|
|
|
|
|
clf.fit(X, _y)
|
|
|
|
|
assert_equal(len(clf.n_classes_), 2)
|
|
|
|
|
assert_equal(len(clf.classes_), 2)
|
|
|
|
|
assert_array_equal(clf.n_classes_, [2, 2])
|
|
|
|
|
assert_array_equal(clf.classes_, [[-1, 1], [-2, 2]])
|
2012-12-04 21:25:14 +08:00
|
|
|
|
|
|
|
|
|
2012-12-25 21:20:59 +08:00
|
|
|
def test_unbalanced_iris():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check class rebalancing.
|
2012-12-25 21:20:59 +08:00
|
|
|
unbalanced_X = iris.data[:125]
|
|
|
|
|
unbalanced_y = iris.target[:125]
|
2014-01-06 00:54:47 +08:00
|
|
|
sample_weight = _balance_weights(unbalanced_y)
|
2012-12-25 21:20:59 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
for name, TreeClassifier in CLF_TREES.items():
|
|
|
|
|
clf = TreeClassifier(random_state=0)
|
|
|
|
|
clf.fit(unbalanced_X, unbalanced_y, sample_weight=sample_weight)
|
|
|
|
|
assert_almost_equal(clf.predict(unbalanced_X), unbalanced_y)
|
2012-12-25 21:20:59 +08:00
|
|
|
|
|
|
|
|
|
2013-07-18 20:46:22 +08:00
|
|
|
def test_memory_layout():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check that it works no matter the memory layout
|
2013-07-22 21:35:31 +08:00
|
|
|
for (name, TreeEstimator), dtype in product(ALL_TREES.items(),
|
|
|
|
|
[np.float64, np.float32]):
|
|
|
|
|
est = TreeEstimator(random_state=0)
|
|
|
|
|
|
2013-07-18 20:46:22 +08:00
|
|
|
# Nothing
|
|
|
|
|
X = np.asarray(iris.data, dtype=dtype)
|
|
|
|
|
y = iris.target
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_array_equal(est.fit(X, y).predict(X), y)
|
2013-07-18 20:46:22 +08:00
|
|
|
|
|
|
|
|
# C-order
|
|
|
|
|
X = np.asarray(iris.data, order="C", dtype=dtype)
|
|
|
|
|
y = iris.target
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_array_equal(est.fit(X, y).predict(X), y)
|
2013-07-18 20:46:22 +08:00
|
|
|
|
|
|
|
|
# F-order
|
|
|
|
|
X = np.asarray(iris.data, order="F", dtype=dtype)
|
|
|
|
|
y = iris.target
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_array_equal(est.fit(X, y).predict(X), y)
|
2013-07-18 20:46:22 +08:00
|
|
|
|
|
|
|
|
# Contiguous
|
|
|
|
|
X = np.ascontiguousarray(iris.data, dtype=dtype)
|
|
|
|
|
y = iris.target
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_array_equal(est.fit(X, y).predict(X), y)
|
2013-07-18 20:46:22 +08:00
|
|
|
|
2015-09-11 16:39:21 +08:00
|
|
|
if not est.presort:
|
2014-04-04 04:28:08 +08:00
|
|
|
# csr matrix
|
|
|
|
|
X = csr_matrix(iris.data, dtype=dtype)
|
|
|
|
|
y = iris.target
|
|
|
|
|
assert_array_equal(est.fit(X, y).predict(X), y)
|
|
|
|
|
|
|
|
|
|
# csc_matrix
|
|
|
|
|
X = csc_matrix(iris.data, dtype=dtype)
|
|
|
|
|
y = iris.target
|
|
|
|
|
assert_array_equal(est.fit(X, y).predict(X), y)
|
|
|
|
|
|
2013-07-18 20:46:22 +08:00
|
|
|
# Strided
|
|
|
|
|
X = np.asarray(iris.data[::3], dtype=dtype)
|
|
|
|
|
y = iris.target[::3]
|
2013-07-22 21:35:31 +08:00
|
|
|
assert_array_equal(est.fit(X, y).predict(X), y)
|
|
|
|
|
|
2013-07-18 20:46:22 +08:00
|
|
|
|
2012-12-25 21:20:59 +08:00
|
|
|
def test_sample_weight():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check sample weighting.
|
2012-12-25 21:20:59 +08:00
|
|
|
# Test that zero-weighted samples are not taken into account
|
|
|
|
|
X = np.arange(100)[:, np.newaxis]
|
|
|
|
|
y = np.ones(100)
|
|
|
|
|
y[:50] = 0.0
|
|
|
|
|
|
|
|
|
|
sample_weight = np.ones(100)
|
|
|
|
|
sample_weight[y == 0] = 0.0
|
|
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = DecisionTreeClassifier(random_state=0)
|
2012-12-25 21:20:59 +08:00
|
|
|
clf.fit(X, y, sample_weight=sample_weight)
|
|
|
|
|
assert_array_equal(clf.predict(X), np.ones(100))
|
|
|
|
|
|
|
|
|
|
# Test that low weighted samples are not taken into account at low depth
|
|
|
|
|
X = np.arange(200)[:, np.newaxis]
|
|
|
|
|
y = np.zeros(200)
|
|
|
|
|
y[50:100] = 1
|
|
|
|
|
y[100:200] = 2
|
|
|
|
|
X[100:200, 0] = 200
|
|
|
|
|
|
|
|
|
|
sample_weight = np.ones(200)
|
|
|
|
|
|
2013-01-17 17:32:51 +08:00
|
|
|
sample_weight[y == 2] = .51 # Samples of class '2' are still weightier
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = DecisionTreeClassifier(max_depth=1, random_state=0)
|
2012-12-25 21:20:59 +08:00
|
|
|
clf.fit(X, y, sample_weight=sample_weight)
|
|
|
|
|
assert_equal(clf.tree_.threshold[0], 149.5)
|
|
|
|
|
|
2014-04-12 16:33:04 +08:00
|
|
|
sample_weight[y == 2] = .5 # Samples of class '2' are no longer weightier
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = DecisionTreeClassifier(max_depth=1, random_state=0)
|
2012-12-25 21:20:59 +08:00
|
|
|
clf.fit(X, y, sample_weight=sample_weight)
|
2013-01-17 17:32:51 +08:00
|
|
|
assert_equal(clf.tree_.threshold[0], 49.5) # Threshold should have moved
|
2012-12-25 21:20:59 +08:00
|
|
|
|
2013-01-06 09:32:42 +08:00
|
|
|
# Test that sample weighting is the same as having duplicates
|
2012-12-25 21:45:28 +08:00
|
|
|
X = iris.data
|
2013-01-05 08:05:59 +08:00
|
|
|
y = iris.target
|
2012-12-25 21:45:28 +08:00
|
|
|
|
2015-09-12 21:18:51 +08:00
|
|
|
duplicates = rng.randint(0, X.shape[0], 100)
|
2012-12-25 21:45:28 +08:00
|
|
|
|
2013-07-22 21:35:31 +08:00
|
|
|
clf = DecisionTreeClassifier(random_state=1)
|
2013-01-09 05:43:36 +08:00
|
|
|
clf.fit(X[duplicates], y[duplicates])
|
2012-12-25 21:45:28 +08:00
|
|
|
|
2014-01-06 21:45:40 +08:00
|
|
|
sample_weight = np.bincount(duplicates, minlength=X.shape[0])
|
2013-07-22 21:35:31 +08:00
|
|
|
clf2 = DecisionTreeClassifier(random_state=1)
|
2012-12-25 21:45:28 +08:00
|
|
|
clf2.fit(X, y, sample_weight=sample_weight)
|
|
|
|
|
|
|
|
|
|
internal = clf.tree_.children_left != tree._tree.TREE_LEAF
|
2013-07-10 15:32:22 +08:00
|
|
|
assert_array_almost_equal(clf.tree_.threshold[internal],
|
|
|
|
|
clf2.tree_.threshold[internal])
|
2013-07-26 19:05:09 +08:00
|
|
|
|
|
|
|
|
|
2014-01-08 21:14:06 +08:00
|
|
|
def test_sample_weight_invalid():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Check sample weighting raises errors.
|
2014-01-08 21:14:06 +08:00
|
|
|
X = np.arange(100)[:, np.newaxis]
|
|
|
|
|
y = np.ones(100)
|
|
|
|
|
y[:50] = 0.0
|
|
|
|
|
|
|
|
|
|
clf = DecisionTreeClassifier(random_state=0)
|
|
|
|
|
|
|
|
|
|
sample_weight = np.random.rand(100, 1)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, y, sample_weight=sample_weight)
|
|
|
|
|
|
|
|
|
|
sample_weight = np.array(0)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, y, sample_weight=sample_weight)
|
|
|
|
|
|
|
|
|
|
sample_weight = np.ones(101)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, y, sample_weight=sample_weight)
|
|
|
|
|
|
|
|
|
|
sample_weight = np.ones(99)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, y, sample_weight=sample_weight)
|
|
|
|
|
|
|
|
|
|
|
2014-12-23 08:50:58 +08:00
|
|
|
def check_class_weights(name):
|
|
|
|
|
"""Check class_weights resemble sample_weights behavior."""
|
|
|
|
|
TreeClassifier = CLF_TREES[name]
|
|
|
|
|
|
2015-05-13 03:41:22 +08:00
|
|
|
# Iris is balanced, so no effect expected for using 'balanced' weights
|
2014-12-23 08:50:58 +08:00
|
|
|
clf1 = TreeClassifier(random_state=0)
|
|
|
|
|
clf1.fit(iris.data, iris.target)
|
2015-05-13 03:41:22 +08:00
|
|
|
clf2 = TreeClassifier(class_weight='balanced', random_state=0)
|
2014-12-23 08:50:58 +08:00
|
|
|
clf2.fit(iris.data, iris.target)
|
|
|
|
|
assert_almost_equal(clf1.feature_importances_, clf2.feature_importances_)
|
|
|
|
|
|
|
|
|
|
# Make a multi-output problem with three copies of Iris
|
|
|
|
|
iris_multi = np.vstack((iris.target, iris.target, iris.target)).T
|
|
|
|
|
# Create user-defined weights that should balance over the outputs
|
|
|
|
|
clf3 = TreeClassifier(class_weight=[{0: 2., 1: 2., 2: 1.},
|
|
|
|
|
{0: 2., 1: 1., 2: 2.},
|
|
|
|
|
{0: 1., 1: 2., 2: 2.}],
|
|
|
|
|
random_state=0)
|
|
|
|
|
clf3.fit(iris.data, iris_multi)
|
|
|
|
|
assert_almost_equal(clf2.feature_importances_, clf3.feature_importances_)
|
|
|
|
|
# Check against multi-output "auto" which should also have no effect
|
2015-05-13 03:41:22 +08:00
|
|
|
clf4 = TreeClassifier(class_weight='balanced', random_state=0)
|
2014-12-23 08:50:58 +08:00
|
|
|
clf4.fit(iris.data, iris_multi)
|
|
|
|
|
assert_almost_equal(clf3.feature_importances_, clf4.feature_importances_)
|
|
|
|
|
|
|
|
|
|
# Inflate importance of class 1, check against user-defined weights
|
|
|
|
|
sample_weight = np.ones(iris.target.shape)
|
|
|
|
|
sample_weight[iris.target == 1] *= 100
|
|
|
|
|
class_weight = {0: 1., 1: 100., 2: 1.}
|
|
|
|
|
clf1 = TreeClassifier(random_state=0)
|
|
|
|
|
clf1.fit(iris.data, iris.target, sample_weight)
|
|
|
|
|
clf2 = TreeClassifier(class_weight=class_weight, random_state=0)
|
|
|
|
|
clf2.fit(iris.data, iris.target)
|
|
|
|
|
assert_almost_equal(clf1.feature_importances_, clf2.feature_importances_)
|
|
|
|
|
|
|
|
|
|
# Check that sample_weight and class_weight are multiplicative
|
|
|
|
|
clf1 = TreeClassifier(random_state=0)
|
2015-05-13 03:41:22 +08:00
|
|
|
clf1.fit(iris.data, iris.target, sample_weight ** 2)
|
2014-12-23 08:50:58 +08:00
|
|
|
clf2 = TreeClassifier(class_weight=class_weight, random_state=0)
|
|
|
|
|
clf2.fit(iris.data, iris.target, sample_weight)
|
|
|
|
|
assert_almost_equal(clf1.feature_importances_, clf2.feature_importances_)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_class_weights():
|
|
|
|
|
for name in CLF_TREES:
|
|
|
|
|
yield check_class_weights, name
|
|
|
|
|
|
|
|
|
|
|
2015-01-04 05:15:42 +08:00
|
|
|
def check_class_weight_errors(name):
|
2015-03-21 13:22:08 +08:00
|
|
|
# Test if class_weight raises errors and warnings when expected.
|
2015-01-04 05:15:42 +08:00
|
|
|
TreeClassifier = CLF_TREES[name]
|
|
|
|
|
_y = np.vstack((y, np.array(y) * 2)).T
|
|
|
|
|
|
|
|
|
|
# Invalid preset string
|
|
|
|
|
clf = TreeClassifier(class_weight='the larch', random_state=0)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, y)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, _y)
|
|
|
|
|
|
|
|
|
|
# Not a list or preset for multi-output
|
|
|
|
|
clf = TreeClassifier(class_weight=1, random_state=0)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, _y)
|
|
|
|
|
|
|
|
|
|
# Incorrect length list for multi-output
|
|
|
|
|
clf = TreeClassifier(class_weight=[{-1: 0.5, 1: 1.}], random_state=0)
|
|
|
|
|
assert_raises(ValueError, clf.fit, X, _y)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_class_weight_errors():
|
|
|
|
|
for name in CLF_TREES:
|
|
|
|
|
yield check_class_weight_errors, name
|
|
|
|
|
|
|
|
|
|
|
2013-11-03 18:50:45 +08:00
|
|
|
def test_max_leaf_nodes():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Test greedy trees with max_depth + 1 leafs.
|
2013-11-02 17:36:19 +08:00
|
|
|
from sklearn.tree._tree import TREE_LEAF
|
|
|
|
|
X, y = datasets.make_hastie_10_2(n_samples=100, random_state=1)
|
|
|
|
|
k = 4
|
|
|
|
|
for name, TreeEstimator in ALL_TREES.items():
|
2013-11-03 15:40:24 +08:00
|
|
|
est = TreeEstimator(max_depth=None, max_leaf_nodes=k + 1).fit(X, y)
|
2013-11-02 17:36:19 +08:00
|
|
|
tree = est.tree_
|
2014-01-16 06:32:16 +08:00
|
|
|
assert_equal((tree.children_left == TREE_LEAF).sum(), k + 1)
|
2013-11-03 22:07:57 +08:00
|
|
|
|
|
|
|
|
# max_leaf_nodes in (0, 1) should raise ValueError
|
|
|
|
|
est = TreeEstimator(max_depth=None, max_leaf_nodes=0)
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
|
|
|
|
est = TreeEstimator(max_depth=None, max_leaf_nodes=1)
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
2014-01-08 21:14:06 +08:00
|
|
|
est = TreeEstimator(max_depth=None, max_leaf_nodes=0.1)
|
|
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
2013-12-03 03:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_max_leaf_nodes_max_depth():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Test preceedence of max_leaf_nodes over max_depth.
|
2013-12-03 03:13:57 +08:00
|
|
|
X, y = datasets.make_hastie_10_2(n_samples=100, random_state=1)
|
|
|
|
|
k = 4
|
|
|
|
|
for name, TreeEstimator in ALL_TREES.items():
|
|
|
|
|
est = TreeEstimator(max_depth=1, max_leaf_nodes=k).fit(X, y)
|
|
|
|
|
tree = est.tree_
|
|
|
|
|
assert_greater(tree.max_depth, 1)
|
2014-01-16 06:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_arrays_persist():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Ensure property arrays' memory stays alive when tree disappears
|
|
|
|
|
# non-regression for #2726
|
2014-01-16 06:32:16 +08:00
|
|
|
for attr in ['n_classes', 'value', 'children_left', 'children_right',
|
|
|
|
|
'threshold', 'impurity', 'feature', 'n_node_samples']:
|
|
|
|
|
value = getattr(DecisionTreeClassifier().fit([[0]], [0]).tree_, attr)
|
|
|
|
|
# if pointing to freed memory, contents may be arbitrary
|
|
|
|
|
assert_true(-2 <= value.flat[0] < 2,
|
|
|
|
|
'Array points to arbitrary memory')
|
2014-02-11 14:43:24 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_only_constant_features():
|
|
|
|
|
random_state = check_random_state(0)
|
|
|
|
|
X = np.zeros((10, 20))
|
|
|
|
|
y = random_state.randint(0, 2, (10, ))
|
|
|
|
|
for name, TreeEstimator in ALL_TREES.items():
|
|
|
|
|
est = TreeEstimator(random_state=0)
|
|
|
|
|
est.fit(X, y)
|
|
|
|
|
assert_equal(est.tree_.max_depth, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_with_only_one_non_constant_features():
|
|
|
|
|
X = np.hstack([np.array([[1.], [1.], [0.], [0.]]),
|
|
|
|
|
np.zeros((4, 1000))])
|
|
|
|
|
|
|
|
|
|
y = np.array([0., 1., 0., 1.0])
|
|
|
|
|
for name, TreeEstimator in CLF_TREES.items():
|
|
|
|
|
est = TreeEstimator(random_state=0, max_features=1)
|
|
|
|
|
est.fit(X, y)
|
|
|
|
|
assert_equal(est.tree_.max_depth, 1)
|
|
|
|
|
assert_array_equal(est.predict_proba(X), 0.5 * np.ones((4, 2)))
|
|
|
|
|
|
|
|
|
|
for name, TreeEstimator in REG_TREES.items():
|
|
|
|
|
est = TreeEstimator(random_state=0, max_features=1)
|
|
|
|
|
est.fit(X, y)
|
|
|
|
|
assert_equal(est.tree_.max_depth, 1)
|
|
|
|
|
assert_array_equal(est.predict(X), 0.5 * np.ones((4, )))
|
2014-02-16 00:22:16 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_big_input():
|
2015-03-21 13:22:08 +08:00
|
|
|
# Test if the warning for too large inputs is appropriate.
|
2014-02-16 00:22:16 +08:00
|
|
|
X = np.repeat(10 ** 40., 4).astype(np.float64).reshape(-1, 1)
|
|
|
|
|
clf = DecisionTreeClassifier()
|
|
|
|
|
try:
|
|
|
|
|
clf.fit(X, [0, 1, 0, 1])
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
assert_in("float32", str(e))
|
2014-04-29 05:08:48 +08:00
|
|
|
|
|
|
|
|
|
2014-09-22 20:21:30 +08:00
|
|
|
def test_realloc():
|
2015-09-09 03:07:30 +08:00
|
|
|
from sklearn.tree._utils import _realloc_test
|
2014-04-29 05:08:48 +08:00
|
|
|
assert_raises(MemoryError, _realloc_test)
|
2014-09-22 20:21:30 +08:00
|
|
|
|
|
|
|
|
|
2014-09-22 21:39:23 +08:00
|
|
|
def test_huge_allocations():
|
|
|
|
|
n_bits = int(platform.architecture()[0].rstrip('bit'))
|
|
|
|
|
|
2014-09-22 20:21:30 +08:00
|
|
|
X = np.random.randn(10, 2)
|
|
|
|
|
y = np.random.randint(0, 2, 10)
|
2014-09-22 21:39:23 +08:00
|
|
|
|
|
|
|
|
# Sanity check: we cannot request more memory than the size of the address
|
|
|
|
|
# space. Currently raises OverflowError.
|
|
|
|
|
huge = 2 ** (n_bits + 1)
|
|
|
|
|
clf = DecisionTreeClassifier(splitter='best', max_leaf_nodes=huge)
|
|
|
|
|
assert_raises(Exception, clf.fit, X, y)
|
|
|
|
|
|
|
|
|
|
# Non-regression test: MemoryError used to be dropped by Cython
|
|
|
|
|
# because of missing "except *".
|
|
|
|
|
huge = 2 ** (n_bits - 1) - 1
|
|
|
|
|
clf = DecisionTreeClassifier(splitter='best', max_leaf_nodes=huge)
|
2014-09-22 20:21:30 +08:00
|
|
|
assert_raises(MemoryError, clf.fit, X, y)
|
2014-04-04 04:28:08 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_sparse_input(tree, dataset, max_depth=None):
|
|
|
|
|
TreeEstimator = ALL_TREES[tree]
|
|
|
|
|
X = DATASETS[dataset]["X"]
|
|
|
|
|
X_sparse = DATASETS[dataset]["X_sparse"]
|
|
|
|
|
y = DATASETS[dataset]["y"]
|
|
|
|
|
|
|
|
|
|
# Gain testing time
|
|
|
|
|
if dataset in ["digits", "boston"]:
|
|
|
|
|
n_samples = X.shape[0] // 5
|
|
|
|
|
X = X[:n_samples]
|
|
|
|
|
X_sparse = X_sparse[:n_samples]
|
|
|
|
|
y = y[:n_samples]
|
|
|
|
|
|
|
|
|
|
for sparse_format in (csr_matrix, csc_matrix, coo_matrix):
|
|
|
|
|
X_sparse = sparse_format(X_sparse)
|
|
|
|
|
|
|
|
|
|
# Check the default (depth first search)
|
|
|
|
|
d = TreeEstimator(random_state=0, max_depth=max_depth).fit(X, y)
|
|
|
|
|
s = TreeEstimator(random_state=0, max_depth=max_depth).fit(X_sparse, y)
|
|
|
|
|
|
|
|
|
|
assert_tree_equal(d.tree_, s.tree_,
|
|
|
|
|
"{0} with dense and sparse format gave different "
|
|
|
|
|
"trees".format(tree))
|
|
|
|
|
|
|
|
|
|
y_pred = d.predict(X)
|
|
|
|
|
if tree in CLF_TREES:
|
|
|
|
|
y_proba = d.predict_proba(X)
|
|
|
|
|
y_log_proba = d.predict_log_proba(X)
|
|
|
|
|
|
|
|
|
|
for sparse_matrix in (csr_matrix, csc_matrix, coo_matrix):
|
|
|
|
|
X_sparse_test = sparse_matrix(X_sparse, dtype=np.float32)
|
|
|
|
|
|
|
|
|
|
assert_array_almost_equal(s.predict(X_sparse_test), y_pred)
|
|
|
|
|
|
|
|
|
|
if tree in CLF_TREES:
|
|
|
|
|
assert_array_almost_equal(s.predict_proba(X_sparse_test),
|
2014-12-23 08:50:58 +08:00
|
|
|
y_proba)
|
2014-04-04 04:28:08 +08:00
|
|
|
assert_array_almost_equal(s.predict_log_proba(X_sparse_test),
|
|
|
|
|
y_log_proba)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sparse_input():
|
|
|
|
|
for tree, dataset in product(SPARSE_TREES,
|
|
|
|
|
("clf_small", "toy", "digits", "multilabel",
|
|
|
|
|
"sparse-pos", "sparse-neg", "sparse-mix",
|
|
|
|
|
"zeros")):
|
|
|
|
|
max_depth = 3 if dataset == "digits" else None
|
|
|
|
|
yield (check_sparse_input, tree, dataset, max_depth)
|
|
|
|
|
|
|
|
|
|
# Due to numerical instability of MSE and too strict test, we limit the
|
|
|
|
|
# maximal depth
|
|
|
|
|
for tree, dataset in product(REG_TREES, ["boston", "reg_small"]):
|
|
|
|
|
if tree in SPARSE_TREES:
|
|
|
|
|
yield (check_sparse_input, tree, dataset, 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_sparse_parameters(tree, dataset):
|
|
|
|
|
TreeEstimator = ALL_TREES[tree]
|
|
|
|
|
X = DATASETS[dataset]["X"]
|
|
|
|
|
X_sparse = DATASETS[dataset]["X_sparse"]
|
|
|
|
|
y = DATASETS[dataset]["y"]
|
|
|
|
|
|
|
|
|
|
# Check max_features
|
|
|
|
|
d = TreeEstimator(random_state=0, max_features=1, max_depth=2).fit(X, y)
|
|
|
|
|
s = TreeEstimator(random_state=0, max_features=1,
|
|
|
|
|
max_depth=2).fit(X_sparse, y)
|
|
|
|
|
assert_tree_equal(d.tree_, s.tree_,
|
|
|
|
|
"{0} with dense and sparse format gave different "
|
|
|
|
|
"trees".format(tree))
|
|
|
|
|
assert_array_almost_equal(s.predict(X), d.predict(X))
|
|
|
|
|
|
|
|
|
|
# Check min_samples_split
|
|
|
|
|
d = TreeEstimator(random_state=0, max_features=1,
|
|
|
|
|
min_samples_split=10).fit(X, y)
|
|
|
|
|
s = TreeEstimator(random_state=0, max_features=1,
|
|
|
|
|
min_samples_split=10).fit(X_sparse, y)
|
|
|
|
|
assert_tree_equal(d.tree_, s.tree_,
|
|
|
|
|
"{0} with dense and sparse format gave different "
|
|
|
|
|
"trees".format(tree))
|
|
|
|
|
assert_array_almost_equal(s.predict(X), d.predict(X))
|
|
|
|
|
|
|
|
|
|
# Check min_samples_leaf
|
|
|
|
|
d = TreeEstimator(random_state=0,
|
|
|
|
|
min_samples_leaf=X_sparse.shape[0] // 2).fit(X, y)
|
|
|
|
|
s = TreeEstimator(random_state=0,
|
|
|
|
|
min_samples_leaf=X_sparse.shape[0] // 2).fit(X_sparse, y)
|
|
|
|
|
assert_tree_equal(d.tree_, s.tree_,
|
|
|
|
|
"{0} with dense and sparse format gave different "
|
|
|
|
|
"trees".format(tree))
|
|
|
|
|
assert_array_almost_equal(s.predict(X), d.predict(X))
|
|
|
|
|
|
|
|
|
|
# Check best-first search
|
|
|
|
|
d = TreeEstimator(random_state=0, max_leaf_nodes=3).fit(X, y)
|
|
|
|
|
s = TreeEstimator(random_state=0, max_leaf_nodes=3).fit(X_sparse, y)
|
|
|
|
|
assert_tree_equal(d.tree_, s.tree_,
|
|
|
|
|
"{0} with dense and sparse format gave different "
|
|
|
|
|
"trees".format(tree))
|
|
|
|
|
assert_array_almost_equal(s.predict(X), d.predict(X))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sparse_parameters():
|
|
|
|
|
for tree, dataset in product(SPARSE_TREES,
|
|
|
|
|
["sparse-pos", "sparse-neg", "sparse-mix",
|
|
|
|
|
"zeros"]):
|
|
|
|
|
yield (check_sparse_parameters, tree, dataset)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_sparse_criterion(tree, dataset):
|
|
|
|
|
TreeEstimator = ALL_TREES[tree]
|
|
|
|
|
X = DATASETS[dataset]["X"]
|
|
|
|
|
X_sparse = DATASETS[dataset]["X_sparse"]
|
|
|
|
|
y = DATASETS[dataset]["y"]
|
|
|
|
|
|
|
|
|
|
# Check various criterion
|
|
|
|
|
CRITERIONS = REG_CRITERIONS if tree in REG_TREES else CLF_CRITERIONS
|
|
|
|
|
for criterion in CRITERIONS:
|
|
|
|
|
d = TreeEstimator(random_state=0, max_depth=3,
|
|
|
|
|
criterion=criterion).fit(X, y)
|
|
|
|
|
s = TreeEstimator(random_state=0, max_depth=3,
|
|
|
|
|
criterion=criterion).fit(X_sparse, y)
|
|
|
|
|
|
|
|
|
|
assert_tree_equal(d.tree_, s.tree_,
|
|
|
|
|
"{0} with dense and sparse format gave different "
|
|
|
|
|
"trees".format(tree))
|
|
|
|
|
assert_array_almost_equal(s.predict(X), d.predict(X))
|
|
|
|
|
|
2014-12-23 08:50:58 +08:00
|
|
|
|
2014-04-04 04:28:08 +08:00
|
|
|
def test_sparse_criterion():
|
|
|
|
|
for tree, dataset in product(SPARSE_TREES,
|
|
|
|
|
["sparse-pos", "sparse-neg", "sparse-mix",
|
|
|
|
|
"zeros"]):
|
|
|
|
|
yield (check_sparse_criterion, tree, dataset)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_explicit_sparse_zeros(tree, max_depth=3,
|
|
|
|
|
n_features=10):
|
|
|
|
|
TreeEstimator = ALL_TREES[tree]
|
|
|
|
|
|
|
|
|
|
# n_samples set n_feature to ease construction of a simultaneous
|
|
|
|
|
# construction of a csr and csc matrix
|
|
|
|
|
n_samples = n_features
|
|
|
|
|
samples = np.arange(n_samples)
|
|
|
|
|
|
|
|
|
|
# Generate X, y
|
|
|
|
|
random_state = check_random_state(0)
|
|
|
|
|
indices = []
|
|
|
|
|
data = []
|
|
|
|
|
offset = 0
|
|
|
|
|
indptr = [offset]
|
|
|
|
|
for i in range(n_features):
|
|
|
|
|
n_nonzero_i = random_state.binomial(n_samples, 0.5)
|
|
|
|
|
indices_i = random_state.permutation(samples)[:n_nonzero_i]
|
|
|
|
|
indices.append(indices_i)
|
2014-12-23 08:50:58 +08:00
|
|
|
data_i = random_state.binomial(3, 0.5, size=(n_nonzero_i, )) - 1
|
2014-04-04 04:28:08 +08:00
|
|
|
data.append(data_i)
|
|
|
|
|
offset += n_nonzero_i
|
|
|
|
|
indptr.append(offset)
|
|
|
|
|
|
|
|
|
|
indices = np.concatenate(indices)
|
|
|
|
|
data = np.array(np.concatenate(data), dtype=np.float32)
|
|
|
|
|
X_sparse = csc_matrix((data, indices, indptr),
|
|
|
|
|
shape=(n_samples, n_features))
|
|
|
|
|
X = X_sparse.toarray()
|
|
|
|
|
X_sparse_test = csr_matrix((data, indices, indptr),
|
|
|
|
|
shape=(n_samples, n_features))
|
|
|
|
|
X_test = X_sparse_test.toarray()
|
|
|
|
|
y = random_state.randint(0, 3, size=(n_samples, ))
|
|
|
|
|
|
|
|
|
|
# Ensure that X_sparse_test owns its data, indices and indptr array
|
|
|
|
|
X_sparse_test = X_sparse_test.copy()
|
|
|
|
|
|
|
|
|
|
# Ensure that we have explicit zeros
|
|
|
|
|
assert_greater((X_sparse.data == 0.).sum(), 0)
|
|
|
|
|
assert_greater((X_sparse_test.data == 0.).sum(), 0)
|
|
|
|
|
|
|
|
|
|
# Perform the comparison
|
|
|
|
|
d = TreeEstimator(random_state=0, max_depth=max_depth).fit(X, y)
|
|
|
|
|
s = TreeEstimator(random_state=0, max_depth=max_depth).fit(X_sparse, y)
|
|
|
|
|
|
|
|
|
|
assert_tree_equal(d.tree_, s.tree_,
|
|
|
|
|
"{0} with dense and sparse format gave different "
|
|
|
|
|
"trees".format(tree))
|
|
|
|
|
|
|
|
|
|
Xs = (X_test, X_sparse_test)
|
|
|
|
|
for X1, X2 in product(Xs, Xs):
|
|
|
|
|
assert_array_almost_equal(s.tree_.apply(X1), d.tree_.apply(X2))
|
2015-01-08 02:03:10 +08:00
|
|
|
assert_array_almost_equal(s.apply(X1), d.apply(X2))
|
|
|
|
|
assert_array_almost_equal(s.apply(X1), s.tree_.apply(X1))
|
2015-10-20 19:15:24 +08:00
|
|
|
|
2015-10-21 16:37:55 +08:00
|
|
|
assert_array_almost_equal(s.tree_.decision_path(X1).toarray(),
|
|
|
|
|
d.tree_.decision_path(X2).toarray())
|
|
|
|
|
assert_array_almost_equal(s.decision_path(X1).toarray(),
|
|
|
|
|
d.decision_path(X2).toarray())
|
|
|
|
|
assert_array_almost_equal(s.decision_path(X1).toarray(),
|
|
|
|
|
s.tree_.decision_path(X1).toarray())
|
2015-10-20 19:15:24 +08:00
|
|
|
|
2014-04-04 04:28:08 +08:00
|
|
|
assert_array_almost_equal(s.predict(X1), d.predict(X2))
|
|
|
|
|
|
|
|
|
|
if tree in CLF_TREES:
|
|
|
|
|
assert_array_almost_equal(s.predict_proba(X1),
|
|
|
|
|
d.predict_proba(X2))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_explicit_sparse_zeros():
|
|
|
|
|
for tree in SPARSE_TREES:
|
|
|
|
|
yield (check_explicit_sparse_zeros, tree)
|
|
|
|
|
|
|
|
|
|
|
2015-08-25 07:52:40 +08:00
|
|
|
@ignore_warnings
|
2014-04-04 04:28:08 +08:00
|
|
|
def check_raise_error_on_1d_input(name):
|
|
|
|
|
TreeEstimator = ALL_TREES[name]
|
|
|
|
|
|
|
|
|
|
X = iris.data[:, 0].ravel()
|
|
|
|
|
X_2d = iris.data[:, 0].reshape((-1, 1))
|
|
|
|
|
y = iris.target
|
|
|
|
|
|
|
|
|
|
assert_raises(ValueError, TreeEstimator(random_state=0).fit, X, y)
|
|
|
|
|
|
|
|
|
|
est = TreeEstimator(random_state=0)
|
|
|
|
|
est.fit(X_2d, y)
|
2015-08-25 07:52:40 +08:00
|
|
|
assert_raises(ValueError, est.predict, [X])
|
2014-04-04 04:28:08 +08:00
|
|
|
|
|
|
|
|
|
2015-08-25 07:52:40 +08:00
|
|
|
@ignore_warnings
|
2014-04-04 04:28:08 +08:00
|
|
|
def test_1d_input():
|
|
|
|
|
for name in ALL_TREES:
|
|
|
|
|
yield check_raise_error_on_1d_input, name
|
2014-11-26 17:56:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _check_min_weight_leaf_split_level(TreeEstimator, X, y, sample_weight):
|
|
|
|
|
# Private function to keep pretty printing in nose yielded tests
|
|
|
|
|
est = TreeEstimator(random_state=0)
|
|
|
|
|
est.fit(X, y, sample_weight=sample_weight)
|
|
|
|
|
assert_equal(est.tree_.max_depth, 1)
|
|
|
|
|
|
|
|
|
|
est = TreeEstimator(random_state=0, min_weight_fraction_leaf=0.4)
|
|
|
|
|
est.fit(X, y, sample_weight=sample_weight)
|
|
|
|
|
assert_equal(est.tree_.max_depth, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_min_weight_leaf_split_level(name):
|
|
|
|
|
TreeEstimator = ALL_TREES[name]
|
|
|
|
|
|
|
|
|
|
X = np.array([[0], [0], [0], [0], [1]])
|
|
|
|
|
y = [0, 0, 0, 0, 1]
|
|
|
|
|
sample_weight = [0.2, 0.2, 0.2, 0.2, 0.2]
|
|
|
|
|
_check_min_weight_leaf_split_level(TreeEstimator, X, y, sample_weight)
|
|
|
|
|
|
2015-09-11 16:39:21 +08:00
|
|
|
if not TreeEstimator().presort:
|
2014-11-26 17:56:56 +08:00
|
|
|
_check_min_weight_leaf_split_level(TreeEstimator, csc_matrix(X), y,
|
|
|
|
|
sample_weight)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_min_weight_leaf_split_level():
|
|
|
|
|
for name in ALL_TREES:
|
|
|
|
|
yield check_min_weight_leaf_split_level, name
|
2015-01-10 12:36:15 +08:00
|
|
|
|
|
|
|
|
|
2015-04-02 17:51:00 +08:00
|
|
|
def check_public_apply(name):
|
|
|
|
|
X_small32 = X_small.astype(tree._tree.DTYPE)
|
|
|
|
|
|
|
|
|
|
est = ALL_TREES[name]()
|
|
|
|
|
est.fit(X_small, y_small)
|
|
|
|
|
assert_array_equal(est.apply(X_small),
|
|
|
|
|
est.tree_.apply(X_small32))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_public_apply_sparse(name):
|
|
|
|
|
X_small32 = csr_matrix(X_small.astype(tree._tree.DTYPE))
|
|
|
|
|
|
|
|
|
|
est = ALL_TREES[name]()
|
|
|
|
|
est.fit(X_small, y_small)
|
|
|
|
|
assert_array_equal(est.apply(X_small),
|
|
|
|
|
est.tree_.apply(X_small32))
|
|
|
|
|
|
2015-01-10 12:36:15 +08:00
|
|
|
|
|
|
|
|
def test_public_apply():
|
2015-04-02 17:51:00 +08:00
|
|
|
for name in ALL_TREES:
|
|
|
|
|
yield (check_public_apply, name)
|
|
|
|
|
|
|
|
|
|
for name in SPARSE_TREES:
|
|
|
|
|
yield (check_public_apply_sparse, name)
|
2015-09-11 16:39:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_presort_sparse(est, X, y):
|
2015-10-20 19:15:24 +08:00
|
|
|
assert_raises(ValueError, est.fit, X, y)
|
|
|
|
|
|
2015-09-11 16:39:21 +08:00
|
|
|
|
|
|
|
|
def test_presort_sparse():
|
2015-06-06 02:45:45 +08:00
|
|
|
ests = (DecisionTreeClassifier(presort=True),
|
2015-09-11 16:39:21 +08:00
|
|
|
DecisionTreeRegressor(presort=True))
|
|
|
|
|
sparse_matrices = (csr_matrix, csc_matrix, coo_matrix)
|
|
|
|
|
|
|
|
|
|
y, X = datasets.make_multilabel_classification(random_state=0,
|
|
|
|
|
n_samples=50,
|
|
|
|
|
n_features=1,
|
|
|
|
|
n_classes=20)
|
|
|
|
|
y = y[:, 0]
|
|
|
|
|
|
|
|
|
|
for est, sparse_matrix in product(ests, sparse_matrices):
|
|
|
|
|
yield check_presort_sparse, est, sparse_matrix(X), y
|
2015-10-20 19:15:24 +08:00
|
|
|
|
|
|
|
|
|
2015-10-21 16:41:55 +08:00
|
|
|
def test_decision_path_hardcoded():
|
|
|
|
|
X = iris.data
|
|
|
|
|
y = iris.target
|
|
|
|
|
est = DecisionTreeClassifier(random_state=0, max_depth=1).fit(X, y)
|
|
|
|
|
node_indicator = est.decision_path(X[:2]).toarray()
|
|
|
|
|
assert_array_equal(node_indicator, [[1, 1, 0], [1, 0, 1]])
|
|
|
|
|
|
|
|
|
|
|
2015-10-20 19:15:24 +08:00
|
|
|
def check_decision_path(name):
|
|
|
|
|
X = iris.data
|
|
|
|
|
y = iris.target
|
|
|
|
|
n_samples = X.shape[0]
|
|
|
|
|
|
|
|
|
|
TreeEstimator = ALL_TREES[name]
|
|
|
|
|
est = TreeEstimator(random_state=0, max_depth=2)
|
|
|
|
|
est.fit(X, y)
|
|
|
|
|
|
2015-10-21 16:37:55 +08:00
|
|
|
node_indicator_csr = est.decision_path(X)
|
2015-10-20 19:15:24 +08:00
|
|
|
node_indicator = node_indicator_csr.toarray()
|
|
|
|
|
assert_equal(node_indicator.shape, (n_samples, est.tree_.node_count))
|
|
|
|
|
|
|
|
|
|
# Assert that leaves index are correct
|
|
|
|
|
leaves = est.apply(X)
|
|
|
|
|
leave_indicator = [node_indicator[i, j] for i, j in enumerate(leaves)]
|
|
|
|
|
assert_array_almost_equal(leave_indicator, np.ones(shape=n_samples))
|
|
|
|
|
|
|
|
|
|
# Ensure only one leave node per sample
|
|
|
|
|
all_leaves = est.tree_.children_left == TREE_LEAF
|
|
|
|
|
assert_array_almost_equal(np.dot(node_indicator, all_leaves),
|
|
|
|
|
np.ones(shape=n_samples))
|
|
|
|
|
|
|
|
|
|
# Ensure max depth is consistent with sum of indicator
|
|
|
|
|
max_depth = node_indicator.sum(axis=1).max()
|
|
|
|
|
assert_less_equal(est.tree_.max_depth, max_depth)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_decision_path():
|
|
|
|
|
for name in ALL_TREES:
|
|
|
|
|
yield (check_decision_path, name)
|