text: from __future__ import division, print_function
  import numpy as np
  import progressbar
  
  # Import helper functions
  from mlfromscratch.utils import train_test_split, standardize, to_categorical
  from mlfromscratch.utils import mean_squared_error, accuracy_score
  from mlfromscratch.deep_learning.loss_functions import SquareLoss, CrossEntropy
  from mlfromscratch.supervised_learning.decision_tree import RegressionTree
  from mlfromscratch.utils.misc import bar_widgets
  
  
  class GradientBoosting(object):
      """Super class of GradientBoostingClassifier and GradientBoostinRegressor. 
      Uses a collection of regression trees that trains on predicting the gradient
      of the loss function. 
  
      Parameters:
      -----------
      n_estimators: int
          The number of classification trees that are used.
      learning_rate: float
          The step length that will be taken when following the negative gradient during
          training.
      min_samples_split: int
          The minimum number of samples needed to make a split when building a tree.
      min_impurity: float
          The minimum impurity required to split the tree further. 
      max_depth: int
          The maximum depth of a tree.
      regression: boolean
          True or false depending on if we're doing regression or classification.
      """
      def __init__(self, n_estimators, learning_rate, min_samples_split,
                   min_impurity, max_depth, regression):
          self.n_estimators = n_estimators
          self.learning_rate = learning_rate
          self.min_samples_split = min_samples_split
          self.min_impurity = min_impurity
          self.max_depth = max_depth
          self.regression = regression
          self.bar = progressbar.ProgressBar(widgets=bar_widgets)
          self.initial_prediction = None
          
          # Square loss for regression
          # Log loss for classification
          self.loss = SquareLoss()
          if not self.regression:
              self.loss = CrossEntropy()
  
          # Initialize regression trees
          self.trees = []
          for _ in range(n_estimators):
              tree = RegressionTree(
                      min_samples_split=self.min_samples_split,
                      min_impurity=min_impurity,
                      max_depth=self.max_depth)
              self.trees.append(tree)
  
  
      def fit(self, X, y):
          self.initial_prediction = np.mean(y, axis=0)
          y_pred = np.full(np.shape(y), np.mean(y, axis=0))
          for i in self.bar(range(self.n_estimators)):
              gradient = self.loss.gradient(y, y_pred)
              self.trees[i].fit(X, gradient)
              update = self.trees[i].predict(X)
              # Update y prediction
              y_pred -= np.multiply(self.learning_rate, update)
  
  
      def predict(self, X):
          y_pred = np.array([])
          # Make predictions
          for tree in self.trees:
              update = tree.predict(X)
              update = np.multiply(self.learning_rate, update)
              y_pred = self.initial_prediction - update if not y_pred.any() else y_pred - update
  
          if not self.regression:
              # Turn into probability distribution
              y_pred = np.exp(y_pred) / np.expand_dims(np.sum(np.exp(y_pred), axis=1), axis=1)
              # Set label to the value that maximizes probability
              y_pred = np.argmax(y_pred, axis=1)
          return y_pred
  
  
  class GradientBoostingRegressor(GradientBoosting):
      def __init__(self, n_estimators=200, learning_rate=0.5, min_samples_split=2,
                   min_var_red=1e-7, max_depth=4, debug=False):
          super(GradientBoostingRegressor, self).__init__(n_estimators=n_estimators, 
              learning_rate=learning_rate, 
              min_samples_split=min_samples_split, 
              min_impurity=min_var_red,
              max_depth=max_depth,
              regression=True)
  
  class GradientBoostingClassifier(GradientBoosting):
      def __init__(self, n_estimators=200, learning_rate=.5, min_samples_split=2,
                   min_info_gain=1e-7, max_depth=2, debug=False):
          super(GradientBoostingClassifier, self).__init__(n_estimators=n_estimators, 
              learning_rate=learning_rate, 
              min_samples_split=min_samples_split, 
              min_impurity=min_info_gain,
              max_depth=max_depth,
              regression=False)
  
      def fit(self, X, y):
          y = to_categorical(y)
          super(GradientBoostingClassifier, self).fit(X, y)
  
  