Source code for ax.models.numpy.randomforest

#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from typing import List, Optional, Tuple

import numpy as np
from ax.core.types import TCandidateMetadata
from ax.models.numpy_base import NumpyModel
from ax.utils.common.docutils import copy_doc
from sklearn.ensemble import RandomForestRegressor
from sklearn.tree import DecisionTreeRegressor


[docs]class RandomForest(NumpyModel): """A Random Forest model. Uses a parametric bootstrap to handle uncertainty in Y. Can be used to fit data, make predictions, and do cross validation; however gen is not implemented and so this model cannot generate new points. Args: max_features: Maximum number of features at each split. With one-hot encoding, this should be set to None. Defaults to "sqrt", which is Breiman's version of Random Forest. num_trees: Number of trees. """ def __init__( self, max_features: Optional[str] = "sqrt", num_trees: int = 500 ) -> None: self.max_features = max_features self.num_trees = num_trees self.models: List[RandomForestRegressor] = [] # pyre-fixme[56]: While applying decorator # `ax.utils.common.docutils.copy_doc(...)`: Argument `Xs` expected.
[docs] @copy_doc(NumpyModel.fit) def fit( self, Xs: List[np.ndarray], Ys: List[np.ndarray], Yvars: List[np.ndarray], bounds: List[Tuple[float, float]], task_features: List[int], feature_names: List[str], metric_names: List[str], fidelity_features: List[int], candidate_metadata: Optional[List[List[TCandidateMetadata]]] = None, ) -> None: for i, X in enumerate(Xs): self.models.append( _get_rf( X=X, Y=Ys[i], Yvar=Yvars[i], num_trees=self.num_trees, max_features=self.max_features, ) )
# pyre-fixme[56]: While applying decorator # `ax.utils.common.docutils.copy_doc(...)`: Argument `X` expected.
[docs] @copy_doc(NumpyModel.predict) def predict(self, X: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: return _rf_predict(self.models, X)
# pyre-fixme[56]: While applying decorator # `ax.utils.common.docutils.copy_doc(...)`: Argument `X_test` expected.
[docs] @copy_doc(NumpyModel.cross_validate) def cross_validate( self, Xs_train: List[np.ndarray], Ys_train: List[np.ndarray], Yvars_train: List[np.ndarray], X_test: np.ndarray, ) -> Tuple[np.ndarray, np.ndarray]: cv_models: List[RandomForestRegressor] = [] for i, X in enumerate(Xs_train): cv_models.append( _get_rf( X=X, Y=Ys_train[i], Yvar=Yvars_train[i], num_trees=self.num_trees, max_features=self.max_features, ) ) return _rf_predict(cv_models, X_test)
def _get_rf( X: np.ndarray, Y: np.ndarray, Yvar: np.ndarray, num_trees: int, max_features: Optional[str], ) -> RandomForestRegressor: """Fit a Random Forest model. Args: X: X Y: Y Yvar: Variance for Y num_trees: Number of trees max_features: Max features specifier Returns: Fitted Random Forest. """ r = RandomForestRegressor( n_estimators=num_trees, max_features=max_features, bootstrap=True ) # pyre-fixme[16]: `RandomForestRegressor` has no attribute `estimators_`. r.estimators_ = [DecisionTreeRegressor() for i in range(r.n_estimators)] for estimator in r.estimators_: # Parametric bootstrap y = np.random.normal(loc=Y[:, 0], scale=np.sqrt(Yvar[:, 0])) estimator.fit(X, y) return r def _rf_predict( models: List[RandomForestRegressor], X: np.ndarray ) -> Tuple[np.ndarray, np.ndarray]: """Make predictions with Random Forest models. Args: models: List of models for each outcome X: X to predict Returns: mean and covariance estimates """ f = np.zeros((X.shape[0], len(models))) cov = np.zeros((X.shape[0], len(models), len(models))) for i, m in enumerate(models): preds = np.vstack([tree.predict(X) for tree in m.estimators_]) # pyre-ignore f[:, i] = preds.mean(0) cov[:, i, i] = preds.var(0) return f, cov