We split the predictor space (the set of possible values \( x_1,x_2,\dots, x_p \)) into \( J \) distinct and non-non-overlapping regions, \( R_1,R_2,\dots,R_J \).
For every observation that falls into the region \( R_j \) , we make the same prediction, which is simply the mean of the response values for the training observations in \( R_j \).
@@ -176,15 +169,14 @@ How do we construct the regions \( R_1,\dots,R_J \)?
In theory, the regions could have any shape. However, we
choose to divide the predictor space into high-dimensional rectangles,
or boxes, for simplicity and for ease of interpretation of the
-resulting predic- tive model. The goal is to find boxes \( R_1,\dots,R_J \)
+resulting predictive model. The goal is to find boxes \( R_1,\dots,R_J \)
that minimize the MSE, given by
$$
\sum_{j=1}^J\sum_{i\in R_j}(y_i-\overline{y}_{R_j})^2,
$$
where \( \overline{y}_{R_j} \) is the mean response for the training observations
-within the $j$th
-box.
+within box \( j \).
If our targets are the outcome of a classification process that takes for example
\( k=1,2,\dots,K \) values, the only thing we need to think of is to set up the splitting criteria for each node.
@@ -222,7 +222,7 @@ $$
defentropy(target_col):
- """
- Calculate the entropy of a dataset.
- The only parameter of this function is the target_col parameter which specifies the target column
- """
- elements,counts = np.unique(target_col,return_counts =True)
- entropy = np.sum([(-counts[i]/np.sum(counts))*np.log2(counts[i]/np.sum(counts)) for i inrange(len(elements))])
- return entropy
+
importmatplotlib.pyplotasplt
+importnumpyasnp
+fromsklearn.model_selectionimport train_test_split
+fromsklearn.datasetsimport load_breast_cancer
+fromsklearn.svmimport SVC
+fromsklearn.linear_modelimport LogisticRegression
+fromsklearn.treeimport DecisionTreeClassifier
-defInfoGain(data,split_attribute_name,target_name="class"):
- """
- Calculate the information gain of a dataset. This function takes three parameters:
- 1. data = The dataset for whose feature the IG should be calculated
- 2. split_attribute_name = the name of the feature for which the information gain should be calculated
- 3. target_name = the name of the target feature. The default for this example is "class"
- """
- #Calculate the entropy of the total dataset
- total_entropy = entropy(data[target_name])
-
- ##Calculate the entropy of the dataset
-
- #Calculate the values and the corresponding counts for the split attribute
- vals,counts= np.unique(data[split_attribute_name],return_counts=True)
-
- #Calculate the weighted entropy
- Weighted_Entropy = np.sum([(counts[i]/np.sum(counts))*entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]) for i inrange(len(vals))])
-
- #Calculate the information gain
- Information_Gain = total_entropy - Weighted_Entropy
- return Information_Gain
-
+# Load the data
+cancer = load_breast_cancer()
-defID3(data,originaldata,features,target_attribute_name="class",parent_node_class =None):
- #Define the stopping criteria --> If one of this is satisfied, we want to return a leaf node#
-
- #If all target_values have the same value, return this value
- iflen(np.unique(data[target_attribute_name])) <=1:
- return np.unique(data[target_attribute_name])[0]
-
- #If the dataset is empty, return the mode target feature value in the original dataset
- eliflen(data)==0:
- return np.unique(originaldata[target_attribute_name])[np.argmax(np.unique(originaldata[target_attribute_name],return_counts=True)[1])]
-
- #If the feature space is empty, return the mode target feature value of the direct parent node --> Note that
- #the direct parent node is that node which has called the current run of the ID3 algorithm and hence
- #the mode target feature value is stored in the parent_node_class variable.
-
- eliflen(features) ==0:
- return parent_node_class
-
- #If none of the above holds true, grow the tree!
-
- else:
- #Set the default value for this node --> The mode target feature value of the current node
- parent_node_class = np.unique(data[target_attribute_name])[np.argmax(np.unique(data[target_attribute_name],return_counts=True)[1])]
-
- #Select the feature which best splits the dataset
- item_values = [InfoGain(data,feature,target_attribute_name) for feature in features] #Return the information gain values for the features in the dataset
- best_feature_index = np.argmax(item_values)
- best_feature = features[best_feature_index]
-
- #Create the tree structure. The root gets the name of the feature (best_feature) with the maximum information
- #gain in the first run
- tree = {best_feature:{}}
-
-
- #Remove the feature with the best inforamtion gain from the feature space
- features = [i for i in features if i != best_feature]
-
- #Grow a branch under the root node for each possible value of the root node feature
-
- for value in np.unique(data[best_feature]):
- value = value
- #Split the dataset along the value of the feature with the largest information gain and therwith create sub_datasets
- sub_data = data.where(data[best_feature] == value).dropna()
-
- #Call the ID3 algorithm for each of those sub_datasets with the new parameters --> Here the recursion comes in!
- subtree = ID3(sub_data,dataset,features,target_attribute_name,parent_node_class)
-
- #Add the sub tree, grown from the sub_dataset to the tree under the root node
- tree[best_feature][value] = subtree
-
- return(tree)
+X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)
+print(X_train.shape)
+print(X_test.shape)
+# Logistic Regression
+logreg = LogisticRegression(solver='lbfgs')
+logreg.fit(X_train, y_train)
+print("Test set accuracy with Logistic Regression: {:.2f}".format(logreg.score(X_test,y_test)))
+# Support vector machine
+svm = SVC(gamma='auto', C=100)
+svm.fit(X_train, y_train)
+print("Test set accuracy with SVM: {:.2f}".format(svm.score(X_test,y_test)))
+# Decision Trees
+deep_tree_clf = DecisionTreeClassifier(max_depth=None)
+deep_tree_clf.fit(X_train, y_train)
+print("Test set accuracy with Decision Trees: {:.2f}".format(deep_tree_clf.score(X_test,y_test)))
+#now scale the data
+fromsklearn.preprocessingimport StandardScaler
+scaler = StandardScaler()
+scaler.fit(X_train)
+X_train_scaled = scaler.transform(X_train)
+X_test_scaled = scaler.transform(X_test)
+# Logistic Regression
+logreg.fit(X_train_scaled, y_train)
+print("Test set accuracy Logistic Regression with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Support Vector Machine
+svm.fit(X_train_scaled, y_train)
+print("Test set accuracy SVM with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Decision Trees
+deep_tree_clf.fit(X_train_scaled, y_train)
+print("Test set accuracy with Decision Trees and scaled data: {:.2f}".format(deep_tree_clf.score(X_test_scaled,y_test)))
White box, easy to interpret model. Some people believe that decision trees more closely mirror human decision-making than do the regression and classification approaches discussed earlier (think of support vector machines)
+
Trees are very easy to explain to people. In fact, they are even easier to explain than linear regression!
+
No feature normalization needed
+
Tree models can handle both continuous and categorical data (Classification and Regression Trees)
+
Can model nonlinear relationships
+
Can model interactions between the different descriptive features
+
Trees can be displayed graphically, and are easily interpreted even by a non-expert (especially if they are small)
+
-#Import the dataset
-dataset = pd.read_csv('data/zoo.csv')
-#We drop the animal names since this is not a good feature to split the data on
-#dataset=dataset.drop('animal_name',axis=1)
-#Split the data into a training and a testing set
-train_features = dataset.iloc[:80,:-1]
-test_features = dataset.iloc[80:,:-1]
-train_targets = dataset.iloc[:80,-1]
-test_targets = dataset.iloc[80:,-1]
-#Train the model
-tree = DecisionTreeClassifier(criterion ='entropy').fit(train_features,train_targets)
-#Predict the classes of new, unseen data
-prediction = tree.predict(test_features)
-#Check the accuracy
-print("The prediction accuracy is: ",tree.score(test_features,test_targets)*100,"%")
-
White box, easy to interpret model. Some people believe that decision trees more closely mirror human decision-making than do the regression and classification approaches discussed earlier (think of support vector machines)
-
Trees are very easy to explain to people. In fact, they are even easier to explain than linear regression!
-
No feature normalization needed
-
Tree models can handle both continuous and categorical data (Classification and Regression Trees)
-
Can model nonlinear relationships
-
Can model interactions between the different descriptive features
-
Trees can be displayed graphically, and are easily interpreted even by a non-expert (especially if they are small)
+
Unfortunately, trees generally do not have the same level of predictive accuracy as some of the other regression and classification approaches
+
If continuous features are used the tree may become quite large and hence less interpretable
+
Decision trees are prone to overfit the training data and hence do not well generalize the data if no stopping criteria or improvements like pruning, boosting or bagging are implemented
+
Small changes in the data may lead to a completely different tree. This issue can be addressed by using ensemble methods like bagging, boosting or random forests
+
Unbalanced datasets where some target feature values occur much more frequently than others may lead to biased trees since the frequently occurring feature values are preferred over the less frequently occurring ones.
+
If the number of features is relatively large (high dimensional) and the number of instances is relatively low, the tree might overfit the data
+
Features with many levels may be preferred over features with less levels since for them it is more easy to split the dataset such that the sub datasets only contain pure target feature values. This issue can be addressed by preferring for instance the information gain ratio as splitting criteria over information gain
+However, by aggregating many decision trees, using methods like bagging, random forests, and boosting, the predictive performance of trees can be substantially improved.
+
+
Unfortunately, trees generally do not have the same level of predictive accuracy as some of the other regression and classification approaches
-
If continuous features are used the tree may become quite large and hence less interpretable
-
Decision trees are prone to overfit the training data and hence do not well generalize the data if no stopping criteria or improvements like pruning, boosting or bagging are implemented
-
Small changes in the data may lead to a completely different tree. This issue can be addressed by using ensemble methods like bagging, boosting or random forests
-
Unbalanced datasets where some target feature values occur much more frequently than others may lead to biased trees since the frequently occurring feature values are preferred over the less frequently occurring ones.
-
If the number of features is relatively large (high dimensional) and the number of instances is relatively low, the tree might overfit the data
-
Features with many levels may be preferred over features with less levels since for them it is more easy to split the dataset such that the sub datasets only contain pure target feature values. This issue can be addressed by preferring for instance the information gain ratio as splitting criteria over information gain
-
+
+The plain decision trees suffer from high
+variance. This means that if we split the training data into two parts
+at random, and fit a decision tree to both halves, the results that we
+get could be quite different. In contrast, a procedure with low
+variance will yield similar results if applied repeatedly to distinct
+data sets; linear regression tends to have low variance, if the ratio
+of \( n \) to \( p \) is moderately large.
-However, by aggregating many decision trees, using methods like bagging, random forests, and boosting, the predictive performance of trees can be substantially improved.
+
+Bootstrap aggregation, or just bagging, is a
+general-purpose procedure for reducing the variance of a statistical
+learning method.
+
+
+Bagging typically results in improved accuracy
+over prediction using a single tree. Unfortunately, however, it can be
+difficult to interpret the resulting model. Recall that one of the
+advantages of decision trees is the attractive and easily interpreted
+diagram that results.
+
+
+However, when we bag a large number of trees, it is no longer
+possible to represent the resulting statistical learning procedure
+using a single tree, and it is no longer clear which variables are
+most important to the procedure. Thus, bagging improves prediction
+accuracy at the expense of interpretability. Although the collection
+of bagged trees is much more difficult to interpret than a single
+tree, one can obtain an overall summary of the importance of each
+predictor using the MSE (for bagging regression trees) or the Gini
+index (for bagging classification trees). In the case of bagging
+regression trees, we can record the total amount that the MSE is
+decreased due to splits over a given predictor, averaged over all \( B \) possible
+trees. A large value indicates an important predictor. Similarly, in
+the context of bagging classification trees, we can add up the total
+amount that the Gini index is decreased by splits over a given
+predictor, averaged over all \( B \) trees.
@@ -194,7 +218,6 @@ However, by aggregating many decision trees, using methods like bagging, random
-The plain decision trees suffer from high
-variance. This means that if we split the training data into two parts
-at random, and fit a decision tree to both halves, the results that we
-get could be quite different. In contrast, a procedure with low
-variance will yield similar results if applied repeatedly to distinct
-data sets; linear regression tends to have low variance, if the ratio
-of \( n \) to \( p \) is moderately large.
-
-
-Bootstrap aggregation, or just bagging, is a
-general-purpose procedure for reducing the variance of a statistical
-learning method.
-
-
-Bagging typically results in improved accuracy
-over prediction using a single tree. Unfortunately, however, it can be
-difficult to interpret the resulting model. Recall that one of the
-advantages of decision trees is the attractive and easily interpreted
-diagram that results.
-
-
-However, when we bag a large number of trees, it is no longer
-possible to represent the resulting statistical learning procedure
-using a single tree, and it is no longer clear which variables are
-most important to the procedure. Thus, bagging improves prediction
-accuracy at the expense of interpretability. Although the collection
-of bagged trees is much more difficult to interpret than a single
-tree, one can obtain an overall summary of the importance of each
-predictor using the MSE (for bagging regression trees) or the Gini
-index (for bagging classification trees). In the case of bagging
-regression trees, we can record the total amount that the MSE is
-decreased due to splits over a given predictor, averaged over all \( B \) possible
-trees. A large value indicates an important predictor. Similarly, in
-the context of bagging classification trees, we can add up the total
-amount that the Gini index is decreased by splits over a given
-predictor, averaged over all \( B \) trees.
+
+
+Random forests provide an improvement over bagged trees by way of a
+small tweak that decorrelates the trees.
+
+
+As in bagging, we build a
+number of decision trees on bootstrapped training samples. But when
+building these decision trees, each time a split in a tree is
+considered, a random sample of \( m \) predictors is chosen as split
+candidates from the full set of \( p \) predictors. The split is allowed to
+use only one of those \( m \) predictors.
+
+
+A fresh sample of \( m \) predictors is
+taken at each split, and typically we choose
+$$
+m\approx \sqrt{p}.
+$$
+
+In building a random forest, at
+each split in the tree, the algorithm is not even allowed to consider
+a majority of the available predictors.
+
+
+The reason for this is rather clever. Suppose that there is one very
+strong predictor in the data set, along with a number of other
+moderately strong predictors. Then in the collection of bagged
+variable importance random forest trees, most or all of the trees will
+use this strong predictor in the top split. Consequently, all of the
+bagged trees will look quite similar to each other. Hence the
+predictions from the bagged trees will be highly correlated.
+Unfortunately, averaging many highly correlated quantities does not lead
+to as large of a reduction in variance as averaging many uncorrelated
+quanti- ties. In particular, this means that bagging will not lead to
+a substantial reduction in variance over a single tree in this
+setting.
-
-
-Random forests provide an improvement over bagged trees by way of a
-small tweak that decorrelates the trees.
-
-
-As in bagging, we build a
-number of decision trees on bootstrapped training samples. But when
-building these decision trees, each time a split in a tree is
-considered, a random sample of \( m \) predictors is chosen as split
-candidates from the full set of \( p \) predictors. The split is allowed to
-use only one of those \( m \) predictors.
-
-
-A fresh sample of \( m \) predictors is
-taken at each split, and typically we choose
-$$
-m\approx \sqrt{p}.
-$$
-
-In building a random forest, at
-each split in the tree, the algorithm is not even allowed to consider
-a majority of the available predictors.
-
-
-The reason for this is rather clever. Suppose that there is one very
-strong predictor in the data set, along with a number of other
-moderately strong predictors. Then in the collection of bagged
-variable importance random forest trees, most or all of the trees will
-use this strong predictor in the top split. Consequently, all of the
-bagged trees will look quite similar to each other. Hence the
-predictions from the bagged trees will be highly correlated.
-Unfortunately, averaging many highly correlated quantities does not lead
-to as large of a reduction in variance as averaging many uncorrelated
-quanti- ties. In particular, this means that bagging will not lead to
-a substantial reduction in variance over a single tree in this
-setting.
+
+
fromsklearn.ensembleimport RandomForestClassifier
+fromsklearn.preprocessingimport LabelEncoder
+fromsklearn.model_selectionimport cross_validate
+# Data set not specificied
+X = dataset.XXX
+Y = dataset.YYY
+#Instantiate the model with 100 trees and entropy as splitting criteria
+Random_Forest_model = RandomForestClassifier(n_estimators=100,criterion="entropy")
+#Cross validation
+accuracy = cross_validate(Random_Forest_model,X,Y,cv=10)['test_score']
+
fromsklearn.ensembleimport RandomForestClassifier
-fromsklearn.preprocessingimport LabelEncoder
-fromsklearn.model_selectionimport cross_validate
-# Data set not specificied
-X = dataset.XXX
-Y = dataset.YYY
-#Instantiate the model with 100 trees and entropy as splitting criteria
-Random_Forest_model = RandomForestClassifier(n_estimators=100,criterion="entropy")
-#Cross validation
-accuracy = cross_validate(Random_Forest_model,X,Y,cv=10)['test_score']
+
[2] Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University
-
Oct 24, 2019
+
Oct 25, 2019
@@ -315,13 +315,8 @@ plt.show()
There are mainly two steps
-
We split the predictor space (the set of possible values \( x_1,x_2,\dots, x_p \)) into \( J \)
-
-
+
We split the predictor space (the set of possible values \( x_1,x_2,\dots, x_p \)) into \( J \) distinct and non-non-overlapping regions, \( R_1,R_2,\dots,R_J \).
-distinct and non-non-overlapping regions, \( R_1,R_2,\dots,R_J \).
-
-
For every observation that falls into the region \( R_j \) , we make the same prediction, which is simply the mean of the response values for the training observations in \( R_j \).
@@ -330,7 +325,7 @@ How do we construct the regions \( R_1,\dots,R_J \)?
In theory, the regions could have any shape. However, we
choose to divide the predictor space into high-dimensional rectangles,
or boxes, for simplicity and for ease of interpretation of the
-resulting predic- tive model. The goal is to find boxes \( R_1,\dots,R_J \)
+resulting predictive model. The goal is to find boxes \( R_1,\dots,R_J \)
that minimize the MSE, given by
$$
@@ -339,8 +334,7 @@ $$
where \( \overline{y}_{R_j} \) is the mean response for the training observations
-within the $j$th
-box.
+within box \( j \).
@@ -469,7 +463,7 @@ subtree corresponding to \( \alpha \).
-
A classification tree is very similar to a regression tree, except
@@ -538,6 +532,8 @@ than is the classification error rate.
Classification tree, how to split nodes
+
+
If our targets are the outcome of a classification process that takes for example
\( k=1,2,\dots,K \) values, the only thing we need to think of is to set up the splitting criteria for each node.
@@ -587,105 +583,62 @@ $$
Entropy and the ID3 algorithm
-More text to come here.
+More text and code to come here.
-
Writing your own code for a classification tree
-
+
Cancer Data again now with Decision Trees
-
defentropy(target_col):
- """
- Calculate the entropy of a dataset.
- The only parameter of this function is the target_col parameter which specifies the target column
- """
- elements,counts = np.unique(target_col,return_counts = True)
- entropy = np.sum([(-counts[i]/np.sum(counts))*np.log2(counts[i]/np.sum(counts)) for i inrange(len(elements))])
- return entropy
+
importmatplotlib.pyplotasplt
+importnumpyasnp
+fromsklearn.model_selectionimport train_test_split
+fromsklearn.datasetsimport load_breast_cancer
+fromsklearn.svmimport SVC
+fromsklearn.linear_modelimport LogisticRegression
+fromsklearn.treeimport DecisionTreeClassifier
-defInfoGain(data,split_attribute_name,target_name="class"):
- """
- Calculate the information gain of a dataset. This function takes three parameters:
- 1. data = The dataset for whose feature the IG should be calculated
- 2. split_attribute_name = the name of the feature for which the information gain should be calculated
- 3. target_name = the name of the target feature. The default for this example is "class"
- """
- #Calculate the entropy of the total dataset
- total_entropy = entropy(data[target_name])
-
- ##Calculate the entropy of the dataset
-
- #Calculate the values and the corresponding counts for the split attribute
- vals,counts= np.unique(data[split_attribute_name],return_counts=True)
-
- #Calculate the weighted entropy
- Weighted_Entropy = np.sum([(counts[i]/np.sum(counts))*entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]) for i inrange(len(vals))])
-
- #Calculate the information gain
- Information_Gain = total_entropy - Weighted_Entropy
- return Information_Gain
-
+# Load the data
+cancer = load_breast_cancer()
-defID3(data,originaldata,features,target_attribute_name="class",parent_node_class = None):
- #Define the stopping criteria --> If one of this is satisfied, we want to return a leaf node#
-
- #If all target_values have the same value, return this value
- iflen(np.unique(data[target_attribute_name])) <= 1:
- return np.unique(data[target_attribute_name])[0]
-
- #If the dataset is empty, return the mode target feature value in the original dataset
- eliflen(data)==0:
- return np.unique(originaldata[target_attribute_name])[np.argmax(np.unique(originaldata[target_attribute_name],return_counts=True)[1])]
-
- #If the feature space is empty, return the mode target feature value of the direct parent node --> Note that
- #the direct parent node is that node which has called the current run of the ID3 algorithm and hence
- #the mode target feature value is stored in the parent_node_class variable.
-
- eliflen(features) ==0:
- return parent_node_class
-
- #If none of the above holds true, grow the tree!
-
- else:
- #Set the default value for this node --> The mode target feature value of the current node
- parent_node_class = np.unique(data[target_attribute_name])[np.argmax(np.unique(data[target_attribute_name],return_counts=True)[1])]
-
- #Select the feature which best splits the dataset
- item_values = [InfoGain(data,feature,target_attribute_name) for feature in features] #Return the information gain values for the features in the dataset
- best_feature_index = np.argmax(item_values)
- best_feature = features[best_feature_index]
-
- #Create the tree structure. The root gets the name of the feature (best_feature) with the maximum information
- #gain in the first run
- tree = {best_feature:{}}
-
-
- #Remove the feature with the best inforamtion gain from the feature space
- features = [i for i in features if i != best_feature]
-
- #Grow a branch under the root node for each possible value of the root node feature
-
- for value in np.unique(data[best_feature]):
- value = value
- #Split the dataset along the value of the feature with the largest information gain and therwith create sub_datasets
- sub_data = data.where(data[best_feature] == value).dropna()
-
- #Call the ID3 algorithm for each of those sub_datasets with the new parameters --> Here the recursion comes in!
- subtree = ID3(sub_data,dataset,features,target_attribute_name,parent_node_class)
-
- #Add the sub tree, grown from the sub_dataset to the tree under the root node
- tree[best_feature][value] = subtree
-
- return(tree)
+X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)
+print(X_train.shape)
+print(X_test.shape)
+# Logistic Regression
+logreg = LogisticRegression(solver='lbfgs')
+logreg.fit(X_train, y_train)
+print("Test set accuracy with Logistic Regression: {:.2f}".format(logreg.score(X_test,y_test)))
+# Support vector machine
+svm = SVC(gamma='auto', C=100)
+svm.fit(X_train, y_train)
+print("Test set accuracy with SVM: {:.2f}".format(svm.score(X_test,y_test)))
+# Decision Trees
+deep_tree_clf = DecisionTreeClassifier(max_depth=None)
+deep_tree_clf.fit(X_train, y_train)
+print("Test set accuracy with Decision Trees: {:.2f}".format(deep_tree_clf.score(X_test,y_test)))
+#now scale the data
+fromsklearn.preprocessingimport StandardScaler
+scaler = StandardScaler()
+scaler.fit(X_train)
+X_train_scaled = scaler.transform(X_train)
+X_test_scaled = scaler.transform(X_test)
+# Logistic Regression
+logreg.fit(X_train_scaled, y_train)
+print("Test set accuracy Logistic Regression with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Support Vector Machine
+svm.fit(X_train_scaled, y_train)
+print("Test set accuracy SVM with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Decision Trees
+deep_tree_clf.fit(X_train_scaled, y_train)
+print("Test set accuracy with Decision Trees and scaled data: {:.2f}".format(deep_tree_clf.score(X_test_scaled,y_test)))
-
Back to moons again
+
Another example, the moons again
@@ -889,36 +842,7 @@ plt.show()
-
Classification again: The zoo data
-
-
-
-
importpandasaspd
-importnumpyasnp
-frompprintimport pprint
-fromsklearn.treeimport DecisionTreeClassifier
-
-#Import the dataset
-dataset = pd.read_csv('data/zoo.csv')
-#We drop the animal names since this is not a good feature to split the data on
-#dataset=dataset.drop('animal_name',axis=1)
-#Split the data into a training and a testing set
-train_features = dataset.iloc[:80,:-1]
-test_features = dataset.iloc[80:,:-1]
-train_targets = dataset.iloc[:80,-1]
-test_targets = dataset.iloc[80:,-1]
-#Train the model
-tree = DecisionTreeClassifier(criterion = 'entropy').fit(train_features,train_targets)
-#Predict the classes of new, unseen data
-prediction = tree.predict(test_features)
-#Check the accuracy
-print("The prediction accuracy is: ",tree.score(test_features,test_targets)*100,"%")
-
-
-
-
-
-
Pros and cons of trees, pros
+
Pros and cons of trees, pros
White box, easy to interpret model. Some people believe that decision trees more closely mirror human decision-making than do the regression and classification approaches discussed earlier (think of support vector machines)
We split the predictor space (the set of possible values \( x_1,x_2,\dots, x_p \)) into \( J \) distinct and non-non-overlapping regions, \( R_1,R_2,\dots,R_J \).
For every observation that falls into the region \( R_j \) , we make the same prediction, which is simply the mean of the response values for the training observations in \( R_j \).
@@ -309,15 +303,14 @@ How do we construct the regions \( R_1,\dots,R_J \)?
In theory, the regions could have any shape. However, we
choose to divide the predictor space into high-dimensional rectangles,
or boxes, for simplicity and for ease of interpretation of the
-resulting predic- tive model. The goal is to find boxes \( R_1,\dots,R_J \)
+resulting predictive model. The goal is to find boxes \( R_1,\dots,R_J \)
that minimize the MSE, given by
$$
\sum_{j=1}^J\sum_{i\in R_j}(y_i-\overline{y}_{R_j})^2,
$$
where \( \overline{y}_{R_j} \) is the mean response for the training observations
-within the $j$th
-box.
+within box \( j \).
@@ -438,7 +431,7 @@ subtree corresponding to \( \alpha \).
-
A schematic procedure
+
Schematic Regression Procedure
@@ -464,7 +457,7 @@ subtree corresponding to \( \alpha \).
-
A classification tree
+
A Classification Tree
A classification tree is very similar to a regression tree, except
@@ -508,6 +501,8 @@ than is the classification error rate.
Classification tree, how to split nodes
+
+
If our targets are the outcome of a classification process that takes for example
\( k=1,2,\dots,K \) values, the only thing we need to think of is to set up the splitting criteria for each node.
@@ -552,104 +547,61 @@ $$
Entropy and the ID3 algorithm
-More text to come here.
+More text and code to come here.
-
Writing your own code for a classification tree
-
+
Cancer Data again now with Decision Trees
-
defentropy(target_col):
- """
- Calculate the entropy of a dataset.
- The only parameter of this function is the target_col parameter which specifies the target column
- """
- elements,counts = np.unique(target_col,return_counts = True)
- entropy = np.sum([(-counts[i]/np.sum(counts))*np.log2(counts[i]/np.sum(counts)) for i inrange(len(elements))])
- return entropy
+
importmatplotlib.pyplotasplt
+importnumpyasnp
+fromsklearn.model_selectionimport train_test_split
+fromsklearn.datasetsimport load_breast_cancer
+fromsklearn.svmimport SVC
+fromsklearn.linear_modelimport LogisticRegression
+fromsklearn.treeimport DecisionTreeClassifier
-defInfoGain(data,split_attribute_name,target_name="class"):
- """
- Calculate the information gain of a dataset. This function takes three parameters:
- 1. data = The dataset for whose feature the IG should be calculated
- 2. split_attribute_name = the name of the feature for which the information gain should be calculated
- 3. target_name = the name of the target feature. The default for this example is "class"
- """
- #Calculate the entropy of the total dataset
- total_entropy = entropy(data[target_name])
-
- ##Calculate the entropy of the dataset
-
- #Calculate the values and the corresponding counts for the split attribute
- vals,counts= np.unique(data[split_attribute_name],return_counts=True)
-
- #Calculate the weighted entropy
- Weighted_Entropy = np.sum([(counts[i]/np.sum(counts))*entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]) for i inrange(len(vals))])
-
- #Calculate the information gain
- Information_Gain = total_entropy - Weighted_Entropy
- return Information_Gain
-
+# Load the data
+cancer = load_breast_cancer()
-defID3(data,originaldata,features,target_attribute_name="class",parent_node_class = None):
- #Define the stopping criteria --> If one of this is satisfied, we want to return a leaf node#
-
- #If all target_values have the same value, return this value
- iflen(np.unique(data[target_attribute_name])) <= 1:
- return np.unique(data[target_attribute_name])[0]
-
- #If the dataset is empty, return the mode target feature value in the original dataset
- eliflen(data)==0:
- return np.unique(originaldata[target_attribute_name])[np.argmax(np.unique(originaldata[target_attribute_name],return_counts=True)[1])]
-
- #If the feature space is empty, return the mode target feature value of the direct parent node --> Note that
- #the direct parent node is that node which has called the current run of the ID3 algorithm and hence
- #the mode target feature value is stored in the parent_node_class variable.
-
- eliflen(features) ==0:
- return parent_node_class
-
- #If none of the above holds true, grow the tree!
-
- else:
- #Set the default value for this node --> The mode target feature value of the current node
- parent_node_class = np.unique(data[target_attribute_name])[np.argmax(np.unique(data[target_attribute_name],return_counts=True)[1])]
-
- #Select the feature which best splits the dataset
- item_values = [InfoGain(data,feature,target_attribute_name) for feature in features] #Return the information gain values for the features in the dataset
- best_feature_index = np.argmax(item_values)
- best_feature = features[best_feature_index]
-
- #Create the tree structure. The root gets the name of the feature (best_feature) with the maximum information
- #gain in the first run
- tree = {best_feature:{}}
-
-
- #Remove the feature with the best inforamtion gain from the feature space
- features = [i for i in features if i != best_feature]
-
- #Grow a branch under the root node for each possible value of the root node feature
-
- for value in np.unique(data[best_feature]):
- value = value
- #Split the dataset along the value of the feature with the largest information gain and therwith create sub_datasets
- sub_data = data.where(data[best_feature] == value).dropna()
-
- #Call the ID3 algorithm for each of those sub_datasets with the new parameters --> Here the recursion comes in!
- subtree = ID3(sub_data,dataset,features,target_attribute_name,parent_node_class)
-
- #Add the sub tree, grown from the sub_dataset to the tree under the root node
- tree[best_feature][value] = subtree
-
- return(tree)
+X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)
+print(X_train.shape)
+print(X_test.shape)
+# Logistic Regression
+logreg = LogisticRegression(solver='lbfgs')
+logreg.fit(X_train, y_train)
+print("Test set accuracy with Logistic Regression: {:.2f}".format(logreg.score(X_test,y_test)))
+# Support vector machine
+svm = SVC(gamma='auto', C=100)
+svm.fit(X_train, y_train)
+print("Test set accuracy with SVM: {:.2f}".format(svm.score(X_test,y_test)))
+# Decision Trees
+deep_tree_clf = DecisionTreeClassifier(max_depth=None)
+deep_tree_clf.fit(X_train, y_train)
+print("Test set accuracy with Decision Trees: {:.2f}".format(deep_tree_clf.score(X_test,y_test)))
+#now scale the data
+fromsklearn.preprocessingimport StandardScaler
+scaler = StandardScaler()
+scaler.fit(X_train)
+X_train_scaled = scaler.transform(X_train)
+X_test_scaled = scaler.transform(X_test)
+# Logistic Regression
+logreg.fit(X_train_scaled, y_train)
+print("Test set accuracy Logistic Regression with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Support Vector Machine
+svm.fit(X_train_scaled, y_train)
+print("Test set accuracy SVM with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Decision Trees
+deep_tree_clf.fit(X_train_scaled, y_train)
+print("Test set accuracy with Decision Trees and scaled data: {:.2f}".format(deep_tree_clf.score(X_test_scaled,y_test)))
-
Back to moons again
+
Another example, the moons again
@@ -849,35 +801,7 @@ plt.show()
-
Classification again: The zoo data
-
-
-
-
importpandasaspd
-importnumpyasnp
-frompprintimport pprint
-fromsklearn.treeimport DecisionTreeClassifier
-
-#Import the dataset
-dataset = pd.read_csv('data/zoo.csv')
-#We drop the animal names since this is not a good feature to split the data on
-#dataset=dataset.drop('animal_name',axis=1)
-#Split the data into a training and a testing set
-train_features = dataset.iloc[:80,:-1]
-test_features = dataset.iloc[80:,:-1]
-train_targets = dataset.iloc[:80,-1]
-test_targets = dataset.iloc[80:,-1]
-#Train the model
-tree = DecisionTreeClassifier(criterion = 'entropy').fit(train_features,train_targets)
-#Predict the classes of new, unseen data
-prediction = tree.predict(test_features)
-#Check the accuracy
-print("The prediction accuracy is: ",tree.score(test_features,test_targets)*100,"%")
-
-
-
-
-
Pros and cons of trees, pros
+
Pros and cons of trees, pros
White box, easy to interpret model. Some people believe that decision trees more closely mirror human decision-making than do the regression and classification approaches discussed earlier (think of support vector machines)
We split the predictor space (the set of possible values \( x_1,x_2,\dots, x_p \)) into \( J \) distinct and non-non-overlapping regions, \( R_1,R_2,\dots,R_J \).
For every observation that falls into the region \( R_j \) , we make the same prediction, which is simply the mean of the response values for the training observations in \( R_j \).
@@ -314,15 +308,14 @@ How do we construct the regions \( R_1,\dots,R_J \)?
In theory, the regions could have any shape. However, we
choose to divide the predictor space into high-dimensional rectangles,
or boxes, for simplicity and for ease of interpretation of the
-resulting predic- tive model. The goal is to find boxes \( R_1,\dots,R_J \)
+resulting predictive model. The goal is to find boxes \( R_1,\dots,R_J \)
that minimize the MSE, given by
$$
\sum_{j=1}^J\sum_{i\in R_j}(y_i-\overline{y}_{R_j})^2,
$$
where \( \overline{y}_{R_j} \) is the mean response for the training observations
-within the $j$th
-box.
+within box \( j \).
@@ -443,7 +436,7 @@ subtree corresponding to \( \alpha \).
-
A schematic procedure
+
Schematic Regression Procedure
@@ -469,7 +462,7 @@ subtree corresponding to \( \alpha \).
-
A classification tree
+
A Classification Tree
A classification tree is very similar to a regression tree, except
@@ -513,6 +506,8 @@ than is the classification error rate.
Classification tree, how to split nodes
+
+
If our targets are the outcome of a classification process that takes for example
\( k=1,2,\dots,K \) values, the only thing we need to think of is to set up the splitting criteria for each node.
@@ -557,104 +552,61 @@ $$
Entropy and the ID3 algorithm
-More text to come here.
+More text and code to come here.
-
Writing your own code for a classification tree
-
+
Cancer Data again now with Decision Trees
-
defentropy(target_col):
- """
- Calculate the entropy of a dataset.
- The only parameter of this function is the target_col parameter which specifies the target column
- """
- elements,counts = np.unique(target_col,return_counts =True)
- entropy = np.sum([(-counts[i]/np.sum(counts))*np.log2(counts[i]/np.sum(counts)) for i inrange(len(elements))])
- return entropy
+
importmatplotlib.pyplotasplt
+importnumpyasnp
+fromsklearn.model_selectionimport train_test_split
+fromsklearn.datasetsimport load_breast_cancer
+fromsklearn.svmimport SVC
+fromsklearn.linear_modelimport LogisticRegression
+fromsklearn.treeimport DecisionTreeClassifier
-defInfoGain(data,split_attribute_name,target_name="class"):
- """
- Calculate the information gain of a dataset. This function takes three parameters:
- 1. data = The dataset for whose feature the IG should be calculated
- 2. split_attribute_name = the name of the feature for which the information gain should be calculated
- 3. target_name = the name of the target feature. The default for this example is "class"
- """
- #Calculate the entropy of the total dataset
- total_entropy = entropy(data[target_name])
-
- ##Calculate the entropy of the dataset
-
- #Calculate the values and the corresponding counts for the split attribute
- vals,counts= np.unique(data[split_attribute_name],return_counts=True)
-
- #Calculate the weighted entropy
- Weighted_Entropy = np.sum([(counts[i]/np.sum(counts))*entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]) for i inrange(len(vals))])
-
- #Calculate the information gain
- Information_Gain = total_entropy - Weighted_Entropy
- return Information_Gain
-
+# Load the data
+cancer = load_breast_cancer()
-defID3(data,originaldata,features,target_attribute_name="class",parent_node_class =None):
- #Define the stopping criteria --> If one of this is satisfied, we want to return a leaf node#
-
- #If all target_values have the same value, return this value
- iflen(np.unique(data[target_attribute_name])) <=1:
- return np.unique(data[target_attribute_name])[0]
-
- #If the dataset is empty, return the mode target feature value in the original dataset
- eliflen(data)==0:
- return np.unique(originaldata[target_attribute_name])[np.argmax(np.unique(originaldata[target_attribute_name],return_counts=True)[1])]
-
- #If the feature space is empty, return the mode target feature value of the direct parent node --> Note that
- #the direct parent node is that node which has called the current run of the ID3 algorithm and hence
- #the mode target feature value is stored in the parent_node_class variable.
-
- eliflen(features) ==0:
- return parent_node_class
-
- #If none of the above holds true, grow the tree!
-
- else:
- #Set the default value for this node --> The mode target feature value of the current node
- parent_node_class = np.unique(data[target_attribute_name])[np.argmax(np.unique(data[target_attribute_name],return_counts=True)[1])]
-
- #Select the feature which best splits the dataset
- item_values = [InfoGain(data,feature,target_attribute_name) for feature in features] #Return the information gain values for the features in the dataset
- best_feature_index = np.argmax(item_values)
- best_feature = features[best_feature_index]
-
- #Create the tree structure. The root gets the name of the feature (best_feature) with the maximum information
- #gain in the first run
- tree = {best_feature:{}}
-
-
- #Remove the feature with the best inforamtion gain from the feature space
- features = [i for i in features if i != best_feature]
-
- #Grow a branch under the root node for each possible value of the root node feature
-
- for value in np.unique(data[best_feature]):
- value = value
- #Split the dataset along the value of the feature with the largest information gain and therwith create sub_datasets
- sub_data = data.where(data[best_feature] == value).dropna()
-
- #Call the ID3 algorithm for each of those sub_datasets with the new parameters --> Here the recursion comes in!
- subtree = ID3(sub_data,dataset,features,target_attribute_name,parent_node_class)
-
- #Add the sub tree, grown from the sub_dataset to the tree under the root node
- tree[best_feature][value] = subtree
-
- return(tree)
+X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)
+print(X_train.shape)
+print(X_test.shape)
+# Logistic Regression
+logreg = LogisticRegression(solver='lbfgs')
+logreg.fit(X_train, y_train)
+print("Test set accuracy with Logistic Regression: {:.2f}".format(logreg.score(X_test,y_test)))
+# Support vector machine
+svm = SVC(gamma='auto', C=100)
+svm.fit(X_train, y_train)
+print("Test set accuracy with SVM: {:.2f}".format(svm.score(X_test,y_test)))
+# Decision Trees
+deep_tree_clf = DecisionTreeClassifier(max_depth=None)
+deep_tree_clf.fit(X_train, y_train)
+print("Test set accuracy with Decision Trees: {:.2f}".format(deep_tree_clf.score(X_test,y_test)))
+#now scale the data
+fromsklearn.preprocessingimport StandardScaler
+scaler = StandardScaler()
+scaler.fit(X_train)
+X_train_scaled = scaler.transform(X_train)
+X_test_scaled = scaler.transform(X_test)
+# Logistic Regression
+logreg.fit(X_train_scaled, y_train)
+print("Test set accuracy Logistic Regression with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Support Vector Machine
+svm.fit(X_train_scaled, y_train)
+print("Test set accuracy SVM with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Decision Trees
+deep_tree_clf.fit(X_train_scaled, y_train)
+print("Test set accuracy with Decision Trees and scaled data: {:.2f}".format(deep_tree_clf.score(X_test_scaled,y_test)))
-
Back to moons again
+
Another example, the moons again
@@ -854,35 +806,7 @@ plt.show()
-
Classification again: The zoo data
-
-
-
-
importpandasaspd
-importnumpyasnp
-frompprintimport pprint
-fromsklearn.treeimport DecisionTreeClassifier
-
-#Import the dataset
-dataset = pd.read_csv('data/zoo.csv')
-#We drop the animal names since this is not a good feature to split the data on
-#dataset=dataset.drop('animal_name',axis=1)
-#Split the data into a training and a testing set
-train_features = dataset.iloc[:80,:-1]
-test_features = dataset.iloc[80:,:-1]
-train_targets = dataset.iloc[:80,-1]
-test_targets = dataset.iloc[80:,-1]
-#Train the model
-tree = DecisionTreeClassifier(criterion ='entropy').fit(train_features,train_targets)
-#Predict the classes of new, unseen data
-prediction = tree.predict(test_features)
-#Check the accuracy
-print("The prediction accuracy is: ",tree.score(test_features,test_targets)*100,"%")
-
-
-
-
-
Pros and cons of trees, pros
+
Pros and cons of trees, pros
White box, easy to interpret model. Some people believe that decision trees more closely mirror human decision-making than do the regression and classification approaches discussed earlier (think of support vector machines)
diff --git a/doc/pub/DecisionTrees/ipynb/DecisionTrees.ipynb b/doc/pub/DecisionTrees/ipynb/DecisionTrees.ipynb
index fcf32553c..2f31deb81 100644
--- a/doc/pub/DecisionTrees/ipynb/DecisionTrees.ipynb
+++ b/doc/pub/DecisionTrees/ipynb/DecisionTrees.ipynb
@@ -10,7 +10,7 @@
" \n",
"**Morten Hjorth-Jensen**, Department of Physics, University of Oslo and Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University\n",
"\n",
- "Date: **Oct 24, 2019**\n",
+ "Date: **Oct 25, 2019**\n",
"\n",
"Copyright 1999-2019, Morten Hjorth-Jensen. Released under CC Attribution-NonCommercial 4.0 license\n",
"\n",
@@ -73,39 +73,10 @@
{
"cell_type": "code",
"execution_count": 1,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "2nd degree coefficients:\n",
- "zero power: 0.13863194714341454\n",
- "first power: 0.12283305178044136\n",
- "second power: -0.00026977980034777324\n"
- ]
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
"source": [
"%matplotlib inline\n",
"\n",
@@ -206,16 +177,15 @@
"## Building a tree, regression\n",
"\n",
"There are mainly two steps\n",
- "1. We split the predictor space (the set of possible values $x_1,x_2,\\dots, x_p$) into $J$\n",
+ "1. We split the predictor space (the set of possible values $x_1,x_2,\\dots, x_p$) into $J$ distinct and non-non-overlapping regions, $R_1,R_2,\\dots,R_J$. \n",
"\n",
- "distinct and non-non-overlapping regions, $R_1,R_2,\\dots,R_J$. \n",
- "1. For every observation that falls into the region $R_j$ , we make the same prediction, which is simply the mean of the response values for the training observations in $R_j$.\n",
+ "2. For every observation that falls into the region $R_j$ , we make the same prediction, which is simply the mean of the response values for the training observations in $R_j$.\n",
"\n",
"How do we construct the regions $R_1,\\dots,R_J$? \n",
"In theory, the regions could have any shape. However, we\n",
"choose to divide the predictor space into high-dimensional rectangles,\n",
"or boxes, for simplicity and for ease of interpretation of the\n",
- "resulting predic- tive model. The goal is to find boxes $R_1,\\dots,R_J$ \n",
+ "resulting predictive model. The goal is to find boxes $R_1,\\dots,R_J$ \n",
"that minimize the MSE, given by"
]
},
@@ -233,8 +203,7 @@
"metadata": {},
"source": [
"where $\\overline{y}_{R_j}$ is the mean response for the training observations \n",
- "within the $j$th\n",
- "box. \n",
+ "within box $j$. \n",
"\n",
"## A top-down approach, recursive binary splitting\n",
"\n",
@@ -377,7 +346,7 @@
"subtree corresponding to $\\alpha$. \n",
"\n",
"\n",
- "## A schematic procedure\n",
+ "## Schematic Regression Procedure\n",
"\n",
"**Building a Regression Tree.**\n",
"\n",
@@ -399,7 +368,7 @@
"\n",
"\n",
"\n",
- "## A classification tree\n",
+ "## A Classification Tree\n",
"\n",
"A classification tree is very similar to a regression tree, except\n",
"that it is used to predict a qualitative response rather than a\n",
@@ -435,6 +404,7 @@
"\n",
"\n",
"## Classification tree, how to split nodes\n",
+ "\n",
"If our targets are the outcome of a classification process that takes for example \n",
"$k=1,2,\\dots,K$ values, the only thing we need to think of is to set up the splitting criteria for each node.\n",
"\n",
@@ -505,113 +475,75 @@
"source": [
"## Entropy and the ID3 algorithm\n",
"\n",
- "More text to come here.\n",
+ "More text and code to come here.\n",
"\n",
- "## Writing your own code for a classification tree"
+ "## Cancer Data again now with Decision Trees"
]
},
{
"cell_type": "code",
"execution_count": 2,
- "metadata": {},
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
- "def entropy(target_col):\n",
- " \"\"\"\n",
- " Calculate the entropy of a dataset.\n",
- " The only parameter of this function is the target_col parameter which specifies the target column\n",
- " \"\"\"\n",
- " elements,counts = np.unique(target_col,return_counts = True)\n",
- " entropy = np.sum([(-counts[i]/np.sum(counts))*np.log2(counts[i]/np.sum(counts)) for i in range(len(elements))])\n",
- " return entropy\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "from sklearn.model_selection import train_test_split \n",
+ "from sklearn.datasets import load_breast_cancer\n",
+ "from sklearn.svm import SVC\n",
+ "from sklearn.linear_model import LogisticRegression\n",
+ "from sklearn.tree import DecisionTreeClassifier\n",
"\n",
- "def InfoGain(data,split_attribute_name,target_name=\"class\"):\n",
- " \"\"\"\n",
- " Calculate the information gain of a dataset. This function takes three parameters:\n",
- " 1. data = The dataset for whose feature the IG should be calculated\n",
- " 2. split_attribute_name = the name of the feature for which the information gain should be calculated\n",
- " 3. target_name = the name of the target feature. The default for this example is \"class\"\n",
- " \"\"\" \n",
- " #Calculate the entropy of the total dataset\n",
- " total_entropy = entropy(data[target_name])\n",
- " \n",
- " ##Calculate the entropy of the dataset\n",
- " \n",
- " #Calculate the values and the corresponding counts for the split attribute \n",
- " vals,counts= np.unique(data[split_attribute_name],return_counts=True)\n",
- " \n",
- " #Calculate the weighted entropy\n",
- " Weighted_Entropy = np.sum([(counts[i]/np.sum(counts))*entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]) for i in range(len(vals))])\n",
- " \n",
- " #Calculate the information gain\n",
- " Information_Gain = total_entropy - Weighted_Entropy\n",
- " return Information_Gain\n",
- " \n",
+ "# Load the data\n",
+ "cancer = load_breast_cancer()\n",
"\n",
- "def ID3(data,originaldata,features,target_attribute_name=\"class\",parent_node_class = None):\n",
- " #Define the stopping criteria --> If one of this is satisfied, we want to return a leaf node#\n",
- " \n",
- " #If all target_values have the same value, return this value\n",
- " if len(np.unique(data[target_attribute_name])) <= 1:\n",
- " return np.unique(data[target_attribute_name])[0]\n",
- " \n",
- " #If the dataset is empty, return the mode target feature value in the original dataset\n",
- " elif len(data)==0:\n",
- " return np.unique(originaldata[target_attribute_name])[np.argmax(np.unique(originaldata[target_attribute_name],return_counts=True)[1])]\n",
- " \n",
- " #If the feature space is empty, return the mode target feature value of the direct parent node --> Note that\n",
- " #the direct parent node is that node which has called the current run of the ID3 algorithm and hence\n",
- " #the mode target feature value is stored in the parent_node_class variable.\n",
- " \n",
- " elif len(features) ==0:\n",
- " return parent_node_class\n",
- " \n",
- " #If none of the above holds true, grow the tree!\n",
- " \n",
- " else:\n",
- " #Set the default value for this node --> The mode target feature value of the current node\n",
- " parent_node_class = np.unique(data[target_attribute_name])[np.argmax(np.unique(data[target_attribute_name],return_counts=True)[1])]\n",
- " \n",
- " #Select the feature which best splits the dataset\n",
- " item_values = [InfoGain(data,feature,target_attribute_name) for feature in features] #Return the information gain values for the features in the dataset\n",
- " best_feature_index = np.argmax(item_values)\n",
- " best_feature = features[best_feature_index]\n",
- " \n",
- " #Create the tree structure. The root gets the name of the feature (best_feature) with the maximum information\n",
- " #gain in the first run\n",
- " tree = {best_feature:{}}\n",
- " \n",
- " \n",
- " #Remove the feature with the best inforamtion gain from the feature space\n",
- " features = [i for i in features if i != best_feature]\n",
- " \n",
- " #Grow a branch under the root node for each possible value of the root node feature\n",
- " \n",
- " for value in np.unique(data[best_feature]):\n",
- " value = value\n",
- " #Split the dataset along the value of the feature with the largest information gain and therwith create sub_datasets\n",
- " sub_data = data.where(data[best_feature] == value).dropna()\n",
- " \n",
- " #Call the ID3 algorithm for each of those sub_datasets with the new parameters --> Here the recursion comes in!\n",
- " subtree = ID3(sub_data,dataset,features,target_attribute_name,parent_node_class)\n",
- " \n",
- " #Add the sub tree, grown from the sub_dataset to the tree under the root node\n",
- " tree[best_feature][value] = subtree\n",
- " \n",
- " return(tree)"
+ "X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)\n",
+ "print(X_train.shape)\n",
+ "print(X_test.shape)\n",
+ "# Logistic Regression\n",
+ "logreg = LogisticRegression(solver='lbfgs')\n",
+ "logreg.fit(X_train, y_train)\n",
+ "print(\"Test set accuracy with Logistic Regression: {:.2f}\".format(logreg.score(X_test,y_test)))\n",
+ "# Support vector machine\n",
+ "svm = SVC(gamma='auto', C=100)\n",
+ "svm.fit(X_train, y_train)\n",
+ "print(\"Test set accuracy with SVM: {:.2f}\".format(svm.score(X_test,y_test)))\n",
+ "# Decision Trees\n",
+ "deep_tree_clf = DecisionTreeClassifier(max_depth=None)\n",
+ "deep_tree_clf.fit(X_train, y_train)\n",
+ "print(\"Test set accuracy with Decision Trees: {:.2f}\".format(deep_tree_clf.score(X_test,y_test)))\n",
+ "#now scale the data\n",
+ "from sklearn.preprocessing import StandardScaler\n",
+ "scaler = StandardScaler()\n",
+ "scaler.fit(X_train)\n",
+ "X_train_scaled = scaler.transform(X_train)\n",
+ "X_test_scaled = scaler.transform(X_test)\n",
+ "# Logistic Regression\n",
+ "logreg.fit(X_train_scaled, y_train)\n",
+ "print(\"Test set accuracy Logistic Regression with scaled data: {:.2f}\".format(logreg.score(X_test_scaled,y_test)))\n",
+ "# Support Vector Machine\n",
+ "svm.fit(X_train_scaled, y_train)\n",
+ "print(\"Test set accuracy SVM with scaled data: {:.2f}\".format(logreg.score(X_test_scaled,y_test)))\n",
+ "# Decision Trees\n",
+ "deep_tree_clf.fit(X_train_scaled, y_train)\n",
+ "print(\"Test set accuracy with Decision Trees and scaled data: {:.2f}\".format(deep_tree_clf.score(X_test_scaled,y_test)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Back to moons again"
+ "## Another example, the moons again"
]
},
{
"cell_type": "code",
"execution_count": 3,
- "metadata": {},
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from __future__ import division, print_function, unicode_literals\n",
@@ -690,7 +622,9 @@
{
"cell_type": "code",
"execution_count": 4,
- "metadata": {},
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"np.random.seed(6)\n",
@@ -725,7 +659,9 @@
{
"cell_type": "code",
"execution_count": 5,
- "metadata": {},
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"# Quadratic training set + noise\n",
@@ -739,7 +675,9 @@
{
"cell_type": "code",
"execution_count": 6,
- "metadata": {},
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.tree import DecisionTreeRegressor\n",
@@ -758,7 +696,9 @@
{
"cell_type": "code",
"execution_count": 7,
- "metadata": {},
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.tree import DecisionTreeRegressor\n",
@@ -804,7 +744,9 @@
{
"cell_type": "code",
"execution_count": 8,
- "metadata": {},
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"tree_reg1 = DecisionTreeRegressor(random_state=42)\n",
@@ -837,41 +779,6 @@
"plt.show()"
]
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Classification again: The zoo data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd\n",
- "import numpy as np\n",
- "from pprint import pprint\n",
- "from sklearn.tree import DecisionTreeClassifier\n",
- "\n",
- "#Import the dataset \n",
- "dataset = pd.read_csv('data/zoo.csv')\n",
- "#We drop the animal names since this is not a good feature to split the data on\n",
- "#dataset=dataset.drop('animal_name',axis=1)\n",
- "#Split the data into a training and a testing set\n",
- "train_features = dataset.iloc[:80,:-1]\n",
- "test_features = dataset.iloc[80:,:-1]\n",
- "train_targets = dataset.iloc[:80,-1]\n",
- "test_targets = dataset.iloc[80:,-1]\n",
- "#Train the model\n",
- "tree = DecisionTreeClassifier(criterion = 'entropy').fit(train_features,train_targets)\n",
- "#Predict the classes of new, unseen data\n",
- "prediction = tree.predict(test_features)\n",
- "#Check the accuracy\n",
- "print(\"The prediction accuracy is: \",tree.score(test_features,test_targets)*100,\"%\")"
- ]
- },
{
"cell_type": "markdown",
"metadata": {},
@@ -952,8 +859,10 @@
},
{
"cell_type": "code",
- "execution_count": 10,
- "metadata": {},
+ "execution_count": 9,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"heads_proba = 0.51\n",
@@ -1025,8 +934,10 @@
},
{
"cell_type": "code",
- "execution_count": 11,
- "metadata": {},
+ "execution_count": 10,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.ensemble import RandomForestClassifier\n",
@@ -1050,8 +961,10 @@
},
{
"cell_type": "code",
- "execution_count": 12,
- "metadata": {},
+ "execution_count": 11,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.model_selection import train_test_split\n",
@@ -1076,8 +989,10 @@
},
{
"cell_type": "code",
- "execution_count": 13,
- "metadata": {},
+ "execution_count": 12,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.metrics import accuracy_score\n",
@@ -1090,8 +1005,10 @@
},
{
"cell_type": "code",
- "execution_count": 14,
- "metadata": {},
+ "execution_count": 13,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"log_clf = LogisticRegression(random_state=42)\n",
@@ -1106,8 +1023,10 @@
},
{
"cell_type": "code",
- "execution_count": 15,
- "metadata": {},
+ "execution_count": 14,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.metrics import accuracy_score\n",
@@ -1127,8 +1046,10 @@
},
{
"cell_type": "code",
- "execution_count": 16,
- "metadata": {},
+ "execution_count": 15,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.ensemble import BaggingClassifier\n",
@@ -1143,8 +1064,10 @@
},
{
"cell_type": "code",
- "execution_count": 17,
- "metadata": {},
+ "execution_count": 16,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from sklearn.metrics import accuracy_score\n",
@@ -1153,8 +1076,10 @@
},
{
"cell_type": "code",
- "execution_count": 18,
- "metadata": {},
+ "execution_count": 17,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"tree_clf = DecisionTreeClassifier(random_state=42)\n",
@@ -1165,8 +1090,10 @@
},
{
"cell_type": "code",
- "execution_count": 19,
- "metadata": {},
+ "execution_count": 18,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"from matplotlib.colors import ListedColormap\n",
@@ -1206,8 +1133,10 @@
},
{
"cell_type": "code",
- "execution_count": 20,
- "metadata": {},
+ "execution_count": 19,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"bag_clf = BaggingClassifier(\n",
@@ -1217,8 +1146,10 @@
},
{
"cell_type": "code",
- "execution_count": 21,
- "metadata": {},
+ "execution_count": 20,
+ "metadata": {
+ "collapsed": false
+ },
"outputs": [],
"source": [
"bag_clf.fit(X_train, y_train)\n",
@@ -1240,25 +1171,7 @@
]
}
],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.4"
- }
- },
+ "metadata": {},
"nbformat": 4,
"nbformat_minor": 2
}
diff --git a/doc/pub/DecisionTrees/ipynb/ipynb-DecisionTrees-src.tar.gz b/doc/pub/DecisionTrees/ipynb/ipynb-DecisionTrees-src.tar.gz
index 3759fecb2..a59095945 100644
Binary files a/doc/pub/DecisionTrees/ipynb/ipynb-DecisionTrees-src.tar.gz and b/doc/pub/DecisionTrees/ipynb/ipynb-DecisionTrees-src.tar.gz differ
diff --git a/doc/pub/DecisionTrees/pdf/DecisionTrees-minted.pdf b/doc/pub/DecisionTrees/pdf/DecisionTrees-minted.pdf
index 2b4b245a4..7c7c78e72 100644
Binary files a/doc/pub/DecisionTrees/pdf/DecisionTrees-minted.pdf and b/doc/pub/DecisionTrees/pdf/DecisionTrees-minted.pdf differ
diff --git a/doc/src/DecisionTrees/DecisionTrees.do.txt b/doc/src/DecisionTrees/DecisionTrees.do.txt
index f1ef0071e..44c4fda5e 100644
--- a/doc/src/DecisionTrees/DecisionTrees.do.txt
+++ b/doc/src/DecisionTrees/DecisionTrees.do.txt
@@ -155,15 +155,14 @@ plt.show()
===== Building a tree, regression =====
There are mainly two steps
-o We split the predictor space (the set of possible values $x_1,x_2,\dots, x_p$) into $J$
-distinct and non-non-overlapping regions, $R_1,R_2,\dots,R_J$.
+o We split the predictor space (the set of possible values $x_1,x_2,\dots, x_p$) into $J$ distinct and non-non-overlapping regions, $R_1,R_2,\dots,R_J$.
o For every observation that falls into the region $R_j$ , we make the same prediction, which is simply the mean of the response values for the training observations in $R_j$.
How do we construct the regions $R_1,\dots,R_J$?
In theory, the regions could have any shape. However, we
choose to divide the predictor space into high-dimensional rectangles,
or boxes, for simplicity and for ease of interpretation of the
-resulting predic- tive model. The goal is to find boxes $R_1,\dots,R_J$
+resulting predictive model. The goal is to find boxes $R_1,\dots,R_J$
that minimize the MSE, given by
!bt
\[
@@ -171,8 +170,7 @@ that minimize the MSE, given by
\]
!et
where $\overline{y}_{R_j}$ is the mean response for the training observations
-within the $j$th
-box.
+within box $j$.
!split
===== A top-down approach, recursive binary splitting =====
@@ -279,7 +277,7 @@ subtree corresponding to $\alpha$.
!split
-===== A schematic procedure =====
+===== Schematic Regression Procedure =====
!bblock Building a Regression Tree
o Use recursive binary splitting to grow a large tree on the training data, stopping only when each terminal node has fewer than some minimum number of observations.
@@ -293,7 +291,7 @@ o Return the subtree from Step 2 that corresponds to the chosen value of $\alpha
!split
-===== A classification tree =====
+===== A Classification Tree =====
A classification tree is very similar to a regression tree, except
that it is used to predict a qualitative response rather than a
@@ -331,6 +329,7 @@ than is the classification error rate.
!split
===== Classification tree, how to split nodes =====
+
If our targets are the outcome of a classification process that takes for example
$k=1,2,\dots,K$ values, the only thing we need to think of is to set up the splitting criteria for each node.
@@ -365,100 +364,58 @@ s = -\sum_{k=1}^K p_{mk}\log{p_{mk}}.
!split
===== Entropy and the ID3 algorithm =====
-More text to come here.
+More text and code to come here.
!split
-===== Writing your own code for a classification tree =====
-
+===== Cancer Data again now with Decision Trees =====
!bc pycod
-def entropy(target_col):
- """
- Calculate the entropy of a dataset.
- The only parameter of this function is the target_col parameter which specifies the target column
- """
- elements,counts = np.unique(target_col,return_counts = True)
- entropy = np.sum([(-counts[i]/np.sum(counts))*np.log2(counts[i]/np.sum(counts)) for i in range(len(elements))])
- return entropy
+import matplotlib.pyplot as plt
+import numpy as np
+from sklearn.model_selection import train_test_split
+from sklearn.datasets import load_breast_cancer
+from sklearn.svm import SVC
+from sklearn.linear_model import LogisticRegression
+from sklearn.tree import DecisionTreeClassifier
-def InfoGain(data,split_attribute_name,target_name="class"):
- """
- Calculate the information gain of a dataset. This function takes three parameters:
- 1. data = The dataset for whose feature the IG should be calculated
- 2. split_attribute_name = the name of the feature for which the information gain should be calculated
- 3. target_name = the name of the target feature. The default for this example is "class"
- """
- #Calculate the entropy of the total dataset
- total_entropy = entropy(data[target_name])
-
- ##Calculate the entropy of the dataset
-
- #Calculate the values and the corresponding counts for the split attribute
- vals,counts= np.unique(data[split_attribute_name],return_counts=True)
-
- #Calculate the weighted entropy
- Weighted_Entropy = np.sum([(counts[i]/np.sum(counts))*entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]) for i in range(len(vals))])
-
- #Calculate the information gain
- Information_Gain = total_entropy - Weighted_Entropy
- return Information_Gain
-
+# Load the data
+cancer = load_breast_cancer()
-def ID3(data,originaldata,features,target_attribute_name="class",parent_node_class = None):
- #Define the stopping criteria --> If one of this is satisfied, we want to return a leaf node#
-
- #If all target_values have the same value, return this value
- if len(np.unique(data[target_attribute_name])) <= 1:
- return np.unique(data[target_attribute_name])[0]
-
- #If the dataset is empty, return the mode target feature value in the original dataset
- elif len(data)==0:
- return np.unique(originaldata[target_attribute_name])[np.argmax(np.unique(originaldata[target_attribute_name],return_counts=True)[1])]
-
- #If the feature space is empty, return the mode target feature value of the direct parent node --> Note that
- #the direct parent node is that node which has called the current run of the ID3 algorithm and hence
- #the mode target feature value is stored in the parent_node_class variable.
-
- elif len(features) ==0:
- return parent_node_class
-
- #If none of the above holds true, grow the tree!
-
- else:
- #Set the default value for this node --> The mode target feature value of the current node
- parent_node_class = np.unique(data[target_attribute_name])[np.argmax(np.unique(data[target_attribute_name],return_counts=True)[1])]
-
- #Select the feature which best splits the dataset
- item_values = [InfoGain(data,feature,target_attribute_name) for feature in features] #Return the information gain values for the features in the dataset
- best_feature_index = np.argmax(item_values)
- best_feature = features[best_feature_index]
-
- #Create the tree structure. The root gets the name of the feature (best_feature) with the maximum information
- #gain in the first run
- tree = {best_feature:{}}
-
-
- #Remove the feature with the best inforamtion gain from the feature space
- features = [i for i in features if i != best_feature]
-
- #Grow a branch under the root node for each possible value of the root node feature
-
- for value in np.unique(data[best_feature]):
- value = value
- #Split the dataset along the value of the feature with the largest information gain and therwith create sub_datasets
- sub_data = data.where(data[best_feature] == value).dropna()
-
- #Call the ID3 algorithm for each of those sub_datasets with the new parameters --> Here the recursion comes in!
- subtree = ID3(sub_data,dataset,features,target_attribute_name,parent_node_class)
-
- #Add the sub tree, grown from the sub_dataset to the tree under the root node
- tree[best_feature][value] = subtree
-
- return(tree)
+X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)
+print(X_train.shape)
+print(X_test.shape)
+# Logistic Regression
+logreg = LogisticRegression(solver='lbfgs')
+logreg.fit(X_train, y_train)
+print("Test set accuracy with Logistic Regression: {:.2f}".format(logreg.score(X_test,y_test)))
+# Support vector machine
+svm = SVC(gamma='auto', C=100)
+svm.fit(X_train, y_train)
+print("Test set accuracy with SVM: {:.2f}".format(svm.score(X_test,y_test)))
+# Decision Trees
+deep_tree_clf = DecisionTreeClassifier(max_depth=None)
+deep_tree_clf.fit(X_train, y_train)
+print("Test set accuracy with Decision Trees: {:.2f}".format(deep_tree_clf.score(X_test,y_test)))
+#now scale the data
+from sklearn.preprocessing import StandardScaler
+scaler = StandardScaler()
+scaler.fit(X_train)
+X_train_scaled = scaler.transform(X_train)
+X_test_scaled = scaler.transform(X_test)
+# Logistic Regression
+logreg.fit(X_train_scaled, y_train)
+print("Test set accuracy Logistic Regression with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Support Vector Machine
+svm.fit(X_train_scaled, y_train)
+print("Test set accuracy SVM with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Decision Trees
+deep_tree_clf.fit(X_train_scaled, y_train)
+print("Test set accuracy with Decision Trees and scaled data: {:.2f}".format(deep_tree_clf.score(X_test_scaled,y_test)))
!ec
+
!split
-===== Back to moons again =====
+===== Another example, the moons again =====
!bc pycod
from __future__ import division, print_function, unicode_literals
@@ -645,31 +602,6 @@ plt.show()
!ec
-!split
-===== Classification again: The zoo data =====
-!bc pycod
-import pandas as pd
-import numpy as np
-from pprint import pprint
-from sklearn.tree import DecisionTreeClassifier
-
-#Import the dataset
-dataset = pd.read_csv('data/zoo.csv')
-#We drop the animal names since this is not a good feature to split the data on
-#dataset=dataset.drop('animal_name',axis=1)
-#Split the data into a training and a testing set
-train_features = dataset.iloc[:80,:-1]
-test_features = dataset.iloc[80:,:-1]
-train_targets = dataset.iloc[:80,-1]
-test_targets = dataset.iloc[80:,-1]
-#Train the model
-tree = DecisionTreeClassifier(criterion = 'entropy').fit(train_features,train_targets)
-#Predict the classes of new, unseen data
-prediction = tree.predict(test_features)
-#Check the accuracy
-print("The prediction accuracy is: ",tree.score(test_features,test_targets)*100,"%")
-
-!ec
!split
===== Pros and cons of trees, pros =====
diff --git a/doc/src/DecisionTrees/cancer.py b/doc/src/DecisionTrees/cancer.py
new file mode 100644
index 000000000..7cc3a8984
--- /dev/null
+++ b/doc/src/DecisionTrees/cancer.py
@@ -0,0 +1,41 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from sklearn.model_selection import train_test_split
+from sklearn.datasets import load_breast_cancer
+from sklearn.svm import SVC
+from sklearn.linear_model import LogisticRegression
+from sklearn.tree import DecisionTreeClassifier
+
+# Load the data
+cancer = load_breast_cancer()
+
+X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)
+print(X_train.shape)
+print(X_test.shape)
+# Logistic Regression
+logreg = LogisticRegression(solver='lbfgs')
+logreg.fit(X_train, y_train)
+print("Test set accuracy with Logistic Regression: {:.2f}".format(logreg.score(X_test,y_test)))
+# Support vector machine
+svm = SVC(gamma='auto', C=100)
+svm.fit(X_train, y_train)
+print("Test set accuracy with SVM: {:.2f}".format(svm.score(X_test,y_test)))
+# Decision Trees
+deep_tree_clf = DecisionTreeClassifier(max_depth=None)
+deep_tree_clf.fit(X_train, y_train)
+print("Test set accuracy with Decision Trees: {:.2f}".format(deep_tree_clf.score(X_test,y_test)))
+#now scale the data
+from sklearn.preprocessing import StandardScaler
+scaler = StandardScaler()
+scaler.fit(X_train)
+X_train_scaled = scaler.transform(X_train)
+X_test_scaled = scaler.transform(X_test)
+# Logistic Regression
+logreg.fit(X_train_scaled, y_train)
+print("Test set accuracy Logistic Regression with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Support Vector Machine
+svm.fit(X_train_scaled, y_train)
+print("Test set accuracy SVM with scaled data: {:.2f}".format(logreg.score(X_test_scaled,y_test)))
+# Decision Trees
+deep_tree_clf.fit(X_train_scaled, y_train)
+print("Test set accuracy with Decision Trees and scaled data: {:.2f}".format(deep_tree_clf.score(X_test_scaled,y_test)))