From b1bcb7183bd7582a094ad1da31ca610c9108c6fd Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Mon, 6 Jul 2015 11:02:19 -0700 Subject: [PATCH 001/126] Adding some details on nthread parameter I got this information about nthread='real cpu count' from https://github.com/dmlc/xgboost/blob/7cb449c4a75c2a16f6dfea5244ce959d998344b1/java/xgboost4j-demo/src/main/java/org/dmlc/xgboost4j/demo/ExternalMemory.java#L50 Please confirm if this note is still valid before merging this change! --- demo/binary_classification/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/demo/binary_classification/README.md b/demo/binary_classification/README.md index 8d1e5e2a5899..482666ec4f87 100644 --- a/demo/binary_classification/README.md +++ b/demo/binary_classification/README.md @@ -162,7 +162,11 @@ If you want to continue boosting from existing model, say 0002.model, use ``` xgboost will load from 0002.model continue boosting for 2 rounds, and save output to continue.model. However, beware that the training and evaluation data specified in mushroom.conf should not change when you use this function. #### Use Multi-Threading -When you are working with a large dataset, you may want to take advantage of parallelism. If your compiler supports OpenMP, xgboost is naturally multi-threaded, to set number of parallel running threads to 10, add ```nthread=10``` to your configuration. +When you are working with a large dataset, you may want to take advantage of parallelism. If your compiler supports OpenMP, xgboost is naturally multi-threaded, to set number of parallel running add ```nthread``` parameter to you configuration. +Eg. ```nthread=10``` + +Set nthread to be the number of your real cpu (On Unix, this can be found using ```lscpu```) +Some systems will have ```Thread(s) per core = 2```, for example, a 4 core cpu with 8 threads, in such case set ```nthread=4``` and not 8. #### Additional Notes * What are ```agaricus.txt.test.buffer``` and ```agaricus.txt.train.buffer``` generated during runexp.sh? From 761ab7c83435db0be62ba25ceabe4b71505e18f2 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Mon, 6 Jul 2015 14:52:38 -0700 Subject: [PATCH 002/126] Adding workaround for install the R-package I was facing this issue and this workaround worked for me. Maybe this should be moved to know issues section. --- R-package/README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/R-package/README.md b/R-package/README.md index e974e3554ac0..81dabb31c4d8 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -1,6 +1,8 @@ -# R package for xgboost. +R package for xgboost +===================== -## Installation +Installation +------------ For up-to-date version (which is recommended), please install from github. Windows user will need to install [RTools](http://cran.r-project.org/bin/windows/Rtools/) first. @@ -8,8 +10,26 @@ For up-to-date version (which is recommended), please install from github. Windo devtools::install_github('dmlc/xgboost',subdir='R-package') ``` - -## Examples +Examples +-------- * Please visit [walk through example](demo). * See also the [example scripts](../demo/kaggle-higgs) for Kaggle Higgs Challenge, including [speedtest script](../demo/kaggle-higgs/speedtest.R) on this dataset and the one related to [Otto challenge](../demo/kaggle-otto), including a [RMarkdown documentation](../demo/kaggle-otto/understandingXGBoostModel.Rmd). + +Notes +----- + +If you face an issue installing the package using ```devtools::install_github```, something like this (even after updating libxml and RCurl as lot of forums say) - + +``` +devtools::install_github('dmlc/xgboost',subdir='R-package') +Downloading github repo dmlc/xgboost@master +Error in function (type, msg, asError = TRUE) : + Peer certificate cannot be authenticated with given CA certificates +``` +To get around this you can build the package locally as mentioned [here](https://github.com/dmlc/xgboost/issues/347) - +``` +1. Clone the current repository and set your workspace to xgboost/R-package/ +2. Run R CMD INSTALL --build . in terminal to get the tarball. +3. Run install.packages('path_to_the_tarball',repo=NULL) in R to install. +``` From 364abdd6d1a196316390d1cfc8af09a32c903d17 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Mon, 6 Jul 2015 16:45:30 -0700 Subject: [PATCH 003/126] Adding examples on xgb.importance, xgb.plot.importance and xgb.plot tree --- R-package/vignettes/xgboostPresentation.Rmd | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/R-package/vignettes/xgboostPresentation.Rmd b/R-package/vignettes/xgboostPresentation.Rmd index b7648340d8a2..39ab819f7037 100644 --- a/R-package/vignettes/xgboostPresentation.Rmd +++ b/R-package/vignettes/xgboostPresentation.Rmd @@ -337,6 +337,17 @@ err <- as.numeric(sum(as.integer(pred > 0.5) != label))/length(label) print(paste("test-error=", err)) ``` +View feature importance/influence from the learnt model +------------------------------------------------------- + +Feature importance is similar to R gbm package's relative influence (rel.inf). + +``` +importance_matrix <- xgb.importance(model = bst) +print(importance_matrix) +xgb.plot.importance(importance_matrix) +``` + View the trees from a model --------------------------- @@ -346,6 +357,12 @@ You can dump the tree you learned using `xgb.dump` into a text file. xgb.dump(bst, with.stats = T) ``` +You can plot the trees from your model using ```xgb.plot.tree`` + +``` +xgb.plot.tree(model = bst) +``` + > if you provide a path to `fname` parameter you can save the trees to your hard drive. Save and load models From 46342d4633552ec19181c834c23586985d45c309 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Mon, 6 Jul 2015 20:07:04 -0700 Subject: [PATCH 004/126] checkin --- src/utils/thread.h | 2 +- wrapper/xgboost_wrapper.cpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/utils/thread.h b/src/utils/thread.h index 78b488cff7de..a6e8e7fdc7fe 100644 --- a/src/utils/thread.h +++ b/src/utils/thread.h @@ -11,7 +11,7 @@ #ifdef _MSC_VER #include #include -#include "../xgboost/utils.h" +#include "./utils.h" namespace xgboost { namespace utils { /*! \brief simple semaphore used for synchronization */ diff --git a/wrapper/xgboost_wrapper.cpp b/wrapper/xgboost_wrapper.cpp index 18c1eae4923c..fb33d0392351 100644 --- a/wrapper/xgboost_wrapper.cpp +++ b/wrapper/xgboost_wrapper.cpp @@ -134,9 +134,11 @@ using namespace xgboost::wrapper; * \brief every function starts with API_BEGIN(); and finishes with API_END(); * \param Finalize optionally put in a finalizer */ -#define API_END(Finalize) } catch(std::exception &e) { \ +#define API_END_FINALIZE(Finalize) } catch(std::exception &e) { \ Finalize; return XGBHandleException(e); \ } return 0; +/*! \brief API End with no finalization */ +#define API_END() API_END_FINALIZE(;) // do not use threadlocal on OSX since it is not always available #ifndef DISABLE_THREAD_LOCAL @@ -217,7 +219,7 @@ int XGDMatrixCreateFromCSR(const bst_ulong *indptr, } mat.info.info.num_row = nindptr - 1; *out = p_mat; - API_END(delete p_mat); + API_END_FINALIZE(delete p_mat); } int XGDMatrixCreateFromCSC(const bst_ulong *col_ptr, @@ -258,7 +260,7 @@ int XGDMatrixCreateFromCSC(const bst_ulong *col_ptr, mat.info.info.num_row = mat.row_ptr_.size() - 1; mat.info.info.num_col = static_cast(ncol); *out = p_mat; - API_END(delete p_mat); + API_END_FINALIZE(delete p_mat); } int XGDMatrixCreateFromMat(const float *data, @@ -289,7 +291,7 @@ int XGDMatrixCreateFromMat(const float *data, mat.row_ptr_.push_back(mat.row_ptr_.back() + nelem); } *out = p_mat; - API_END(delete p_mat); + API_END_FINALIZE(delete p_mat); } int XGDMatrixSliceDMatrix(DMatrixHandle handle, @@ -340,7 +342,7 @@ int XGDMatrixSliceDMatrix(DMatrixHandle handle, } } *out = p_ret; - API_END(delete p_ret); + API_END_FINALIZE(delete p_ret); } int XGDMatrixFree(DMatrixHandle handle) { From 9ec4c43dd2f6377c0e3875b109bed6f64a166792 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Mon, 6 Jul 2015 22:44:59 -0700 Subject: [PATCH 005/126] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cdd4c02f7304..9b3e6f57af90 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ What's New - Checkout the winning solution at [Highlight links](doc/README.md#highlight-links) * [External Memory Version](doc/external_memory.md) +Contributing to XGBoost +========= +XGBoost has been developed and used by a group of active community. Everyone is more than welcomed to is a great way to make the project better and more accessible to more users. +* Checkout [Feature Wish List](https://github.com/dmlc/xgboost/labels/Wish-List) to see what can be improved, or open an issue if you want something. +* Contribute to the [documents and examples](https://github.com/dmlc/xgboost/blob/master/doc/) to share your experience to other users. +* Features ======== * Easily accessible in python, R, Julia, CLI From 28f8267563880fc99fa1c73990d3643f8c7a79b4 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Mon, 6 Jul 2015 22:45:27 -0700 Subject: [PATCH 006/126] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b3e6f57af90..22652689180c 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ Contributing to XGBoost ========= XGBoost has been developed and used by a group of active community. Everyone is more than welcomed to is a great way to make the project better and more accessible to more users. * Checkout [Feature Wish List](https://github.com/dmlc/xgboost/labels/Wish-List) to see what can be improved, or open an issue if you want something. -* Contribute to the [documents and examples](https://github.com/dmlc/xgboost/blob/master/doc/) to share your experience to other users. -* +* Contribute to the [documents and examples](https://github.com/dmlc/xgboost/blob/master/doc/) to share your experience with other users. + Features ======== * Easily accessible in python, R, Julia, CLI From c489ce62b243b357e0fc58366b55d273925e2993 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Tue, 7 Jul 2015 16:36:45 -0700 Subject: [PATCH 007/126] refs and formatting changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22652689180c..d7cf31a08bc4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ XGBoost: eXtreme Gradient Boosting [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. -It implements machine learning algorithm under gradient boosting framework, including generalized linear model and gradient boosted regression tree (GBDT). XGBoost can also be distributed and scale to Terascale data +It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data Contributors: https://github.com/dmlc/xgboost/graphs/contributors From 57e4f4d426390c192d2904f0d2585e05612d9a9b Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Tue, 7 Jul 2015 17:36:18 -0700 Subject: [PATCH 008/126] need to load vcd if it was freshly installed --- R-package/demo/create_sparse_matrix.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R index e3a536cfe7d2..11de17a910e8 100644 --- a/R-package/demo/create_sparse_matrix.R +++ b/R-package/demo/create_sparse_matrix.R @@ -1,8 +1,10 @@ require(xgboost) require(Matrix) require(data.table) -if (!require(vcd)) install.packages('vcd') #Available in Cran. Used for its dataset with categorical values. - +if (!require(vcd)) { + install.packages('vcd') #Available in Cran. Used for its dataset with categorical values. + require(vcd) +} # According to its documentation, Xgboost works only on numbers. # Sometimes the dataset we have to work on have categorical data. # A categorical variable is one which have a fixed number of values. By exemple, if for each observation a variable called "Colour" can have only "red", "blue" or "green" as value, it is a categorical variable. From 0f5f9c03850073ce756f01cd67b0b86aa0934ac7 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Wed, 20 May 2015 14:17:03 -0500 Subject: [PATCH 009/126] ENH: Allow early stopping in sklearn API. --- wrapper/xgboost.py | 118 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 6 deletions(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index 96f6c25735d2..35c24a1f244d 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -772,7 +772,6 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, ------- booster : a trained booster model """ - evals = list(evals) bst = Booster(params, [dtrain] + [d[0] for d in evals]) @@ -1074,6 +1073,8 @@ def get_params(self, deep=False): params = super(XGBModel, self).get_params(deep=deep) if params['missing'] is np.nan: params['missing'] = None # sklearn doesn't handle nan. see #4725 + if not params.get('eval_metric', True): + del params['eval_metric'] # don't give as None param to Booster return params def get_xgb_params(self): @@ -1086,10 +1087,62 @@ def get_xgb_params(self): xgb_params.pop('nthread', None) return xgb_params - def fit(self, data, y): + def fit(self, X, y, eval_set=None, eval_metric=None, + early_stopping_rounds=None, feval=None): # pylint: disable=missing-docstring,invalid-name - train_dmatrix = DMatrix(data, label=y, missing=self.missing) - self._Booster = train(self.get_xgb_params(), train_dmatrix, self.n_estimators) + """ + Fit the gradient boosting model + + Parameters + ---------- + X : array_like + Feature matrix + y : array_like + Labels + eval_set : list, optional + A list of (X, y) tuple pairs to use as a validation set for + early-stopping + eval_metric : str, optional + Built-in evaluation metric to use. + early_stopping_rounds : int + Activates early stopping. Validation error needs to decrease at + least every round(s) to continue training. + Requires at least one item in evals. If there's more than one, + will use the last. Returns the model from the last iteration + (not the best one). If early stopping occurs, the model will + have two additional fields: bst.best_score and bst.best_iteration. + feval : function, optional + Custom evaluation metric to use. The call signature is + feval(y_predicted, y_true) where y_true will be a DMatrix object + such that you may need to call the get_label method. This objective + if always assumed to be minimized, so use -feval when appropriate. + """ + trainDmatrix = DMatrix(X, label=y, missing=self.missing) + + eval_results = {} + if eval_set is not None: + evals = list(DMatrix(x[0], label=x[1]) for x in eval_set) + evals = list(zip(evals, + ["validation_{}" for i in range(len(evals))])) + else: + evals = () + + params = self.get_xgb_params() + + if eval_metric is not None: + params.update({'eval_metric': eval_metric}) + + self._Booster = train(params, trainDmatrix, + self.n_estimators, evals=evals, + early_stopping_rounds=early_stopping_rounds, + evals_result=eval_results, feval=None) + if eval_results: + eval_results = {k: np.array(v, dtype=float) + for k, v in eval_results.items()} + eval_results = {k: np.array(v) for k, v in eval_results.items()} + self.eval_results_ = eval_results + self.best_score_ = self._Booster.best_score + self.best_iteration_ = self._Booster.best_iteration return self def predict(self, data): @@ -1117,8 +1170,39 @@ def __init__(self, max_depth=3, learning_rate=0.1, colsample_bytree, base_score, seed, missing) - def fit(self, X, y, sample_weight=None): + def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, + early_stopping_rounds=None, feval=None): # pylint: disable = attribute-defined-outside-init,arguments-differ + """ + Fit gradient boosting classifier + + Parameters + ---------- + X : array_like + Feature matrix + y : array_like + Labels + sample_weight : array_like + Weight for each instance + eval_set : list, optional + A list of (X, y) pairs to use as a validation set for + early-stopping + eval_metric : str + Built-in evaluation metric to use. + early_stopping_rounds : int, optional + Activates early stopping. Validation error needs to decrease at + least every round(s) to continue training. + Requires at least one item in evals. If there's more than one, + will use the last. Returns the model from the last iteration + (not the best one). If early stopping occurs, the model will + have two additional fields: bst.best_score and bst.best_iteration. + feval : function, optional + Custom evaluation metric to use. The call signature is + feval(y_predicted, y_true) where y_true will be a DMatrix object + such that you may need to call the get_label method. This objective + if always assumed to be minimized, so use -feval when appropriate. + """ + eval_results = {} self.classes_ = list(np.unique(y)) self.n_classes_ = len(self.classes_) if self.n_classes_ > 2: @@ -1129,6 +1213,18 @@ def fit(self, X, y, sample_weight=None): else: xgb_options = self.get_xgb_params() + if eval_metric is not None: + xgb_options.update({"eval_metric": eval_metric}) + + if eval_set is not None: + # TODO: use sample_weight if given? + evals = list(DMatrix(x[0], label=x[1]) for x in eval_set) + nevals = len(evals) + eval_names = ["validation_{}".format(i) for i in range(nevals)] + evals = list(zip(evals, eval_names)) + else: + evals = () + self._le = LabelEncoder().fit(y) training_labels = self._le.transform(y) @@ -1139,7 +1235,17 @@ def fit(self, X, y, sample_weight=None): train_dmatrix = DMatrix(X, label=training_labels, missing=self.missing) - self._Booster = train(xgb_options, train_dmatrix, self.n_estimators) + self._Booster = train(xgb_options, train_dmatrix, self.n_estimators, + evals=evals, + early_stopping_rounds=early_stopping_rounds, + evals_result=eval_results, feval=feval) + + if eval_results: + eval_results = {k: np.array(v, dtype=float) + for k, v in eval_results.items()} + self.eval_results_ = eval_results + self.best_score_ = self._Booster.best_score + self.best_iteration_ = self._Booster.best_iteration return self From 3952b525b82d2d2a2019429e3a97fe0f1f331f0c Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Wed, 20 May 2015 14:17:30 -0500 Subject: [PATCH 010/126] ENH: Allow possibly negative evaluation metrics. --- wrapper/xgboost.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index 35c24a1f244d..bc52da633b02 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -795,7 +795,7 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, sys.stderr.write(msg + '\n') if evals_result is not None: - res = re.findall(":([0-9.]+).", msg) + res = re.findall(":-?([0-9.]+).", msg) for key, val in zip(evals_name, res): evals_result[key].append(val) return bst @@ -842,7 +842,7 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, sys.stderr.write(msg + '\n') if evals_result is not None: - res = re.findall(":([0-9.]+).", msg) + res = re.findall(":-([0-9.]+).", msg) for key, val in zip(evals_name, res): evals_result[key].append(val) From cf89ae64e2c198c9e5acde076e8be40b1aab2e92 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Wed, 20 May 2015 14:27:22 -0500 Subject: [PATCH 011/126] ENH: Allow for silent evaluation --- wrapper/xgboost.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index bc52da633b02..a4acd5a7f459 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -738,7 +738,7 @@ def get_fscore(self, fmap=''): def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, - early_stopping_rounds=None, evals_result=None): + early_stopping_rounds=None, evals_result=None, verbose_eval=True): # pylint: disable=too-many-statements,too-many-branches, attribute-defined-outside-init """Train a booster with given parameters. @@ -793,7 +793,8 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, else: msg = bst_eval_set.decode() - sys.stderr.write(msg + '\n') + if verbose_eval: + sys.stderr.write(msg + '\n') if evals_result is not None: res = re.findall(":-?([0-9.]+).", msg) for key, val in zip(evals_name, res): @@ -839,7 +840,8 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, else: msg = bst_eval_set.decode() - sys.stderr.write(msg + '\n') + if verbose_eval: + sys.stderr.write(msg + '\n') if evals_result is not None: res = re.findall(":-([0-9.]+).", msg) @@ -1088,7 +1090,7 @@ def get_xgb_params(self): return xgb_params def fit(self, X, y, eval_set=None, eval_metric=None, - early_stopping_rounds=None, feval=None): + early_stopping_rounds=None, feval=None, verbose=True): # pylint: disable=missing-docstring,invalid-name """ Fit the gradient boosting model @@ -1116,6 +1118,9 @@ def fit(self, X, y, eval_set=None, eval_metric=None, feval(y_predicted, y_true) where y_true will be a DMatrix object such that you may need to call the get_label method. This objective if always assumed to be minimized, so use -feval when appropriate. + verbose : bool + If `verbose` and an evaluation set is used, writes the evaluation + metric measured on the validation set to stderr. """ trainDmatrix = DMatrix(X, label=y, missing=self.missing) @@ -1135,7 +1140,8 @@ def fit(self, X, y, eval_set=None, eval_metric=None, self._Booster = train(params, trainDmatrix, self.n_estimators, evals=evals, early_stopping_rounds=early_stopping_rounds, - evals_result=eval_results, feval=None) + evals_result=eval_results, feval=None, + verbose_eval=verbose) if eval_results: eval_results = {k: np.array(v, dtype=float) for k, v in eval_results.items()} @@ -1171,7 +1177,7 @@ def __init__(self, max_depth=3, learning_rate=0.1, base_score, seed, missing) def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, - early_stopping_rounds=None, feval=None): + early_stopping_rounds=None, feval=None, versbose=True): # pylint: disable = attribute-defined-outside-init,arguments-differ """ Fit gradient boosting classifier @@ -1201,6 +1207,9 @@ def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, feval(y_predicted, y_true) where y_true will be a DMatrix object such that you may need to call the get_label method. This objective if always assumed to be minimized, so use -feval when appropriate. + verbose : bool + If `verbose` and an evaluation set is used, writes the evaluation + metric measured on the validation set to stderr. """ eval_results = {} self.classes_ = list(np.unique(y)) @@ -1238,7 +1247,8 @@ def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, self._Booster = train(xgb_options, train_dmatrix, self.n_estimators, evals=evals, early_stopping_rounds=early_stopping_rounds, - evals_result=eval_results, feval=feval) + evals_result=eval_results, feval=feval, + verbose_eval=verbose) if eval_results: eval_results = {k: np.array(v, dtype=float) From 46e9520a28b4aca9281c938a919620a8754cb4d9 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Wed, 20 May 2015 14:38:45 -0500 Subject: [PATCH 012/126] DOC: Document verbose_eval --- wrapper/xgboost.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index a4acd5a7f459..a4ad84bf5599 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -767,6 +767,9 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, bst.best_score and bst.best_iteration. evals_result: dict This dictionary stores the evaluation results of all the items in watchlist + verbose_eval : bool + If `verbose_eval` then the evaluation metric on the validation set, if + given, is printed at each boosting stage. Returns ------- From 113285e1dc3fdc0c709e72a2cb985b3025360897 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Wed, 20 May 2015 14:39:48 -0500 Subject: [PATCH 013/126] DOC: Point to parameter.md for eval_metric --- wrapper/xgboost.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index a4ad84bf5599..adb21a00b1e9 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -1108,7 +1108,7 @@ def fit(self, X, y, eval_set=None, eval_metric=None, A list of (X, y) tuple pairs to use as a validation set for early-stopping eval_metric : str, optional - Built-in evaluation metric to use. + Built-in evaluation metric to use. See doc/parameter.md. early_stopping_rounds : int Activates early stopping. Validation error needs to decrease at least every round(s) to continue training. @@ -1197,7 +1197,7 @@ def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, A list of (X, y) pairs to use as a validation set for early-stopping eval_metric : str - Built-in evaluation metric to use. + Built-in evaluation metric to use. See doc/parameter.md. early_stopping_rounds : int, optional Activates early stopping. Validation error needs to decrease at least every round(s) to continue training. From b0f7ddaa2ee3411b33f95b42be46a3325b0ac23b Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Tue, 30 Jun 2015 11:42:14 -0500 Subject: [PATCH 014/126] REF: Combine eval_metric and feval to one parameter --- wrapper/xgboost.py | 48 ++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index adb21a00b1e9..95e0bf6ff0a7 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -1093,7 +1093,7 @@ def get_xgb_params(self): return xgb_params def fit(self, X, y, eval_set=None, eval_metric=None, - early_stopping_rounds=None, feval=None, verbose=True): + early_stopping_rounds=None, verbose=True): # pylint: disable=missing-docstring,invalid-name """ Fit the gradient boosting model @@ -1107,8 +1107,14 @@ def fit(self, X, y, eval_set=None, eval_metric=None, eval_set : list, optional A list of (X, y) tuple pairs to use as a validation set for early-stopping - eval_metric : str, optional - Built-in evaluation metric to use. See doc/parameter.md. + eval_metric : str, callable, optional + If a str, should be a built-in evaluation metric to use. See + doc/parameter.md. If callable, a custom evaluation metric. The call + signature is func(y_predicted, y_true) where y_true will be a + DMatrix object such that you may need to call the get_label + method. It must return a str, value pair where the str is a name + for the evaluation and value is the value of the evaluation + function. This objective is always minimized. early_stopping_rounds : int Activates early stopping. Validation error needs to decrease at least every round(s) to continue training. @@ -1116,11 +1122,6 @@ def fit(self, X, y, eval_set=None, eval_metric=None, will use the last. Returns the model from the last iteration (not the best one). If early stopping occurs, the model will have two additional fields: bst.best_score and bst.best_iteration. - feval : function, optional - Custom evaluation metric to use. The call signature is - feval(y_predicted, y_true) where y_true will be a DMatrix object - such that you may need to call the get_label method. This objective - if always assumed to be minimized, so use -feval when appropriate. verbose : bool If `verbose` and an evaluation set is used, writes the evaluation metric measured on the validation set to stderr. @@ -1137,13 +1138,17 @@ def fit(self, X, y, eval_set=None, eval_metric=None, params = self.get_xgb_params() + feval = eval_metric if callable(eval_metric) else None if eval_metric is not None: - params.update({'eval_metric': eval_metric}) + if callable(eval_metric): + eval_metric = None + else: + params.update({'eval_metric': eval_metric}) self._Booster = train(params, trainDmatrix, self.n_estimators, evals=evals, early_stopping_rounds=early_stopping_rounds, - evals_result=eval_results, feval=None, + evals_result=eval_results, feval=feval, verbose_eval=verbose) if eval_results: eval_results = {k: np.array(v, dtype=float) @@ -1180,7 +1185,7 @@ def __init__(self, max_depth=3, learning_rate=0.1, base_score, seed, missing) def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, - early_stopping_rounds=None, feval=None, versbose=True): + early_stopping_rounds=None, verbose=True): # pylint: disable = attribute-defined-outside-init,arguments-differ """ Fit gradient boosting classifier @@ -1196,8 +1201,14 @@ def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, eval_set : list, optional A list of (X, y) pairs to use as a validation set for early-stopping - eval_metric : str - Built-in evaluation metric to use. See doc/parameter.md. + eval_metric : str, callable, optional + If a str, should be a built-in evaluation metric to use. See + doc/parameter.md. If callable, a custom evaluation metric. The call + signature is func(y_predicted, y_true) where y_true will be a + DMatrix object such that you may need to call the get_label + method. It must return a str, value pair where the str is a name + for the evaluation and value is the value of the evaluation + function. This objective is always minimized. early_stopping_rounds : int, optional Activates early stopping. Validation error needs to decrease at least every round(s) to continue training. @@ -1205,11 +1216,6 @@ def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, will use the last. Returns the model from the last iteration (not the best one). If early stopping occurs, the model will have two additional fields: bst.best_score and bst.best_iteration. - feval : function, optional - Custom evaluation metric to use. The call signature is - feval(y_predicted, y_true) where y_true will be a DMatrix object - such that you may need to call the get_label method. This objective - if always assumed to be minimized, so use -feval when appropriate. verbose : bool If `verbose` and an evaluation set is used, writes the evaluation metric measured on the validation set to stderr. @@ -1225,8 +1231,12 @@ def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, else: xgb_options = self.get_xgb_params() + feval = eval_metric if callable(eval_metric) else None if eval_metric is not None: - xgb_options.update({"eval_metric": eval_metric}) + if callable(eval_metric): + eval_metric = None + else: + xgb_options.update({"eval_metric": eval_metric}) if eval_set is not None: # TODO: use sample_weight if given? From 4a37b852a03b1320d1c41f948a4ec212a981ad1d Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Tue, 30 Jun 2015 11:42:28 -0500 Subject: [PATCH 015/126] DOC: Add early stopping example --- demo/guide-python/sklearn_examples.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/demo/guide-python/sklearn_examples.py b/demo/guide-python/sklearn_examples.py index ce8c8d01e9b6..56fed1dd2bbe 100755 --- a/demo/guide-python/sklearn_examples.py +++ b/demo/guide-python/sklearn_examples.py @@ -8,7 +8,7 @@ import xgboost as xgb import numpy as np -from sklearn.cross_validation import KFold +from sklearn.cross_validation import KFold, train_test_split from sklearn.metrics import confusion_matrix, mean_squared_error from sklearn.grid_search import GridSearchCV from sklearn.datasets import load_iris, load_digits, load_boston @@ -65,3 +65,23 @@ pickle.dump(clf, open("best_boston.pkl", "wb")) clf2 = pickle.load(open("best_boston.pkl", "rb")) print(np.allclose(clf.predict(X), clf2.predict(X))) + +# Early-stopping + +X = digits['data'] +y = digits['target'] +X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0) +clf = xgb.XGBClassifier() +clf.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="auc", + eval_set=[(X_test, y_test)]) + +# Custom evaluation function +from sklearn.metrics import log_loss + + +def log_loss_eval(y_pred, y_true): + return "log-loss", log_loss(y_true.get_label(), y_pred) + + +clf.fit(X_train, y_train, early_stopping_rounds=10, eval_metric=log_loss_eval, + eval_set=[(X_test, y_test)]) From b76db01c6605a19e852172e3f08d9a4613bf6361 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Wed, 8 Jul 2015 14:29:52 -0500 Subject: [PATCH 016/126] STY: Fix lint errors --- wrapper/xgboost.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index 95e0bf6ff0a7..27041376be5b 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -6,7 +6,7 @@ Authors: Tianqi Chen, Bing Xu Early stopping by Zygmunt Zając """ -# pylint: disable=too-many-arguments, too-many-locals, too-many-lines, invalid-name +# pylint: disable=too-many-arguments, too-many-locals, too-many-lines, invalid-name, fixme from __future__ import absolute_import import os @@ -784,7 +784,7 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, else: evals_name = [d[1] for d in evals] evals_result.clear() - evals_result.update({key:[] for key in evals_name}) + evals_result.update({key: [] for key in evals_name}) if not early_stopping_rounds: for i in range(num_boost_round): @@ -1094,7 +1094,7 @@ def get_xgb_params(self): def fit(self, X, y, eval_set=None, eval_metric=None, early_stopping_rounds=None, verbose=True): - # pylint: disable=missing-docstring,invalid-name + # pylint: disable=missing-docstring,invalid-name,attribute-defined-outside-init """ Fit the gradient boosting model @@ -1131,8 +1131,8 @@ def fit(self, X, y, eval_set=None, eval_metric=None, eval_results = {} if eval_set is not None: evals = list(DMatrix(x[0], label=x[1]) for x in eval_set) - evals = list(zip(evals, - ["validation_{}" for i in range(len(evals))])) + evals = list(zip(evals, ["validation_{}".format(i) for i in + range(len(evals))])) else: evals = () From dabb36c0066517082a0b4c6ecec1806f03789b10 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Fri, 10 Jul 2015 20:41:00 -0700 Subject: [PATCH 017/126] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7cf31a08bc4..e69ef19302b1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -XGBoost: eXtreme Gradient Boosting +DMLC/XGBoost: eXtreme Gradient Boosting ================================== [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) @@ -8,7 +8,7 @@ It implements machine learning algorithms under the [Gradient Boosting](https:// Contributors: https://github.com/dmlc/xgboost/graphs/contributors -Documentations: [Documentation of xgboost](doc/README.md) +Documentations: [Documentation of dmlc/xgboost](doc/README.md) Issues Tracker: [https://github.com/dmlc/xgboost/issues](https://github.com/dmlc/xgboost/issues?q=is%3Aissue+label%3Aquestion) From e402d20876b8c19ee75990508b7c784f94b4af8e Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Fri, 10 Jul 2015 20:41:20 -0700 Subject: [PATCH 018/126] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e69ef19302b1..58283ed14ca5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -DMLC/XGBoost: eXtreme Gradient Boosting +DMLC/XGBoost ================================== [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) From 35638f614663a0b47e1abae0bc65b636f39b139c Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Sun, 12 Jul 2015 10:27:58 -0700 Subject: [PATCH 019/126] Update README.md --- doc/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index 371e18f21d0d..e8df7d57de12 100644 --- a/doc/README.md +++ b/doc/README.md @@ -20,7 +20,8 @@ How to get started Highlight Links ==== This section is about blogposts, presentation and videos discussing how to use xgboost to solve your interesting problem. If you think something belongs to here, send a pull request. -* [Kaggle Malware Prediction winning solution](https://github.com/xiaozhouwang/kaggle_Microsoft_Malware) +* [Kaggle CrowdFlower winner's solution by Chenglong Chen](https://github.com/ChenglongChen/Kaggle_CrowdFlower) +* [Kaggle Malware Prediction winner's solution](https://github.com/xiaozhouwang/kaggle_Microsoft_Malware) * [Kaggle Tradeshift winning solution by daxiongshu](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) * [Feature Importance Analysis with XGBoost in Tax audit](http://fr.slideshare.net/MichaelBENESTY/feature-importance-analysis-with-xgboost-in-tax-audit) * Video tutorial: [Better Optimization with Repeated Cross Validation and the XGBoost model](https://www.youtube.com/watch?v=Og7CGAfSr_Y) From 44f839b896c883f9db574d54581da1f0565a9c37 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Sun, 12 Jul 2015 10:31:55 -0700 Subject: [PATCH 020/126] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 58283ed14ca5..e6f5d69d1394 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,10 @@ XGBoost is part of [Distributed Machine Learning Common](http://dmlc.github.io/) What's New ========== +* XGBoost helps Chenglong Chen to win [Kaggle CrowdFlower Competition](https://www.kaggle.com/c/crowdflower-search-relevance) + - Checkout the winning solution at [Highlight links](doc/README.md#highlight-links) * XGBoost-0.4 release, see [CHANGES.md](CHANGES.md#xgboost-04) -* XGBoost wins [WWW2015 Microsoft Malware Classification Challenge (BIG 2015)](http://www.kaggle.com/c/malware-classification/forums/t/13490/say-no-to-overfitting-approaches-sharing) +* XGBoost helps three champion teams to win [WWW2015 Microsoft Malware Classification Challenge (BIG 2015)](http://www.kaggle.com/c/malware-classification/forums/t/13490/say-no-to-overfitting-approaches-sharing) - Checkout the winning solution at [Highlight links](doc/README.md#highlight-links) * [External Memory Version](doc/external_memory.md) From 4a746be43a30dd5dc0a72151b340421e740c8aa4 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Sun, 12 Jul 2015 10:36:16 -0700 Subject: [PATCH 021/126] Update build.md --- doc/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build.md b/doc/build.md index f9a626603d61..7b8ee96aaadd 100644 --- a/doc/build.md +++ b/doc/build.md @@ -43,7 +43,7 @@ Here is the complete solution to use OpenMp-enabled compilers to install XGBoost export CXX = clang-omp++ ``` - Remember to change `header` if using clang-omp. + Remember to change `header` (mentioned in step 2) if using clang-omp. Then `cd xgboost` then `bash build.sh` to compile XGBoost. And go to `wrapper` sub-folder to install python version. From b7f355fdd2e3867e634eae8724b178a9c57ca8ab Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Sun, 12 Jul 2015 11:00:52 -0700 Subject: [PATCH 022/126] Update travis_after_failure.sh --- scripts/travis_after_failure.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/travis_after_failure.sh b/scripts/travis_after_failure.sh index 230f3348c5d0..15b74d87f243 100755 --- a/scripts/travis_after_failure.sh +++ b/scripts/travis_after_failure.sh @@ -1,5 +1,5 @@ #!/bin/bash if [ ${TASK} == "R-package" ]; then - cat R-package/xgboost.Rcheck/00install.out -fi \ No newline at end of file + cat R-package/xgboost.Rcheck/*.log +fi From be95c80aa29233f7089f54befaa8727607c7d9b4 Mon Sep 17 00:00:00 2001 From: Joosep Date: Tue, 14 Jul 2015 11:38:38 +0200 Subject: [PATCH 023/126] fix wrapper dict --- wrapper/xgboost.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index 96f6c25735d2..7a601424c6d2 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -777,7 +777,7 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, bst = Booster(params, [dtrain] + [d[0] for d in evals]) if evals_result is not None: - if isinstance(evals_result, dict): + if not isinstance(evals_result, dict): raise TypeError('evals_result has to be a dictionary') else: evals_name = [d[1] for d in evals] From d18492e751f6c1b71d37db4b5ca649aed4e90e49 Mon Sep 17 00:00:00 2001 From: tqchen Date: Mon, 20 Jul 2015 22:48:45 -0700 Subject: [PATCH 024/126] add list of contributors --- CONTRIBUTORS.md | 19 +++++++++++++++++++ README.md | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000000..894fdc4dd956 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,19 @@ +Contributors of DMLC/XGBoost +======= +XGBoost has been developed and used by a group of active community. Everyone is more than welcomed to is a great way to make the project better and more accessible to more users. + +Comitters +======= +Committers are group of people who have made substantial contribution to the project and granted write access to the project. +* [Tianqi Chen](https://github.com/tqchen), University of Washington + - Tianqi is a PhD working on large-scale machine learning, he is the creator of the project. +* [Tong He](https://github.com/hetong007), Simon Fraser University + - Tong is a master student working on data mining, he is the maintainer of xgboost R package. +* [Michael Benesty](https://github.com/pommedeterresautee) + - Micheal is a lawyer, data scientist in France, he is the creator of xgboost interactive analysis module in R. + +List of Contributors +======= +* [Full List of Contributors](https://github.com/dmlc/xgboost/graphs/contributors) + - To contributors: please add your name to the list when you submit a patch to the project:) +* Kailong Chen Microsoft diff --git a/README.md b/README.md index e6f5d69d1394..6334d21f777a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DMLC/XGBoost An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data -Contributors: https://github.com/dmlc/xgboost/graphs/contributors +Checkout the [Comitters and Contributors](https://github.com/dmlc/xgboost/CONTRIBUTORS.md) Documentations: [Documentation of dmlc/xgboost](doc/README.md) @@ -38,6 +38,7 @@ Contributing to XGBoost XGBoost has been developed and used by a group of active community. Everyone is more than welcomed to is a great way to make the project better and more accessible to more users. * Checkout [Feature Wish List](https://github.com/dmlc/xgboost/labels/Wish-List) to see what can be improved, or open an issue if you want something. * Contribute to the [documents and examples](https://github.com/dmlc/xgboost/blob/master/doc/) to share your experience with other users. +* Please add your name to [CONTRIBUTORS.md](CONTRIBUTORS.md) after your patch has been merged. Features ======== From b18c7f9466265955985201be4c815d9b7cea3b4a Mon Sep 17 00:00:00 2001 From: tqchen Date: Mon, 20 Jul 2015 22:50:59 -0700 Subject: [PATCH 025/126] ok --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6334d21f777a..4a5e7bf6eb35 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DMLC/XGBoost An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data -Checkout the [Comitters and Contributors](https://github.com/dmlc/xgboost/CONTRIBUTORS.md) +Checkout our [Comitters and Contributors](CONTRIBUTORS.md) who keep make xgboost better. Documentations: [Documentation of dmlc/xgboost](doc/README.md) From 41f30c288ef63df53d4f9fb13fd0340884c5b0c0 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Mon, 20 Jul 2015 22:56:29 -0700 Subject: [PATCH 026/126] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 894fdc4dd956..bfd674b1b0a4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -9,6 +9,8 @@ Committers are group of people who have made substantial contribution to the pro - Tianqi is a PhD working on large-scale machine learning, he is the creator of the project. * [Tong He](https://github.com/hetong007), Simon Fraser University - Tong is a master student working on data mining, he is the maintainer of xgboost R package. +* [Bing Xu](https://github.com/antinucleon) + - Bing is the original creator of xgboost python package and currently the maintainer of [XGBoost.jl](https://github.com/antinucleon/XGBoost.jl). * [Michael Benesty](https://github.com/pommedeterresautee) - Micheal is a lawyer, data scientist in France, he is the creator of xgboost interactive analysis module in R. From 4cf116ceb60b847ce48842e0a58da2c322eb775a Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Mon, 20 Jul 2015 22:58:10 -0700 Subject: [PATCH 027/126] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index bfd674b1b0a4..974689e520b2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,7 +4,7 @@ XGBoost has been developed and used by a group of active community. Everyone is Comitters ======= -Committers are group of people who have made substantial contribution to the project and granted write access to the project. +Committers are people who have made substantial contribution to the project and granted write access to the project. * [Tianqi Chen](https://github.com/tqchen), University of Washington - Tianqi is a PhD working on large-scale machine learning, he is the creator of the project. * [Tong He](https://github.com/hetong007), Simon Fraser University From 9203d26a2f9f99f7a25214c7d16f87f9e409fc5c Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Tue, 21 Jul 2015 08:13:07 -0700 Subject: [PATCH 028/126] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 974689e520b2..7b326c523424 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -18,4 +18,11 @@ List of Contributors ======= * [Full List of Contributors](https://github.com/dmlc/xgboost/graphs/contributors) - To contributors: please add your name to the list when you submit a patch to the project:) -* Kailong Chen Microsoft +* [Kailong Chen](https://github.com/kalenhaha) +* [Skipper Seabold](https://github.com/jseabold) +* [Zygmunt Zając](https://github.com/zygmuntz) +* [Ajinkya Kale](https://github.com/ajkl) +* [Boliang Chen](https://github.com/cblsjtu) +* [Vadim Khotilovich](https://github.com/khotilov) +* [Yangqing Men](https://github.com/yanqingmen) +* [Engpeng Yao](https://github.com/yepyao) From 80b6ec447864c263d49d98fb231c4cfa1f81276c Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Tue, 21 Jul 2015 21:31:39 -0700 Subject: [PATCH 029/126] update more contributor names --- CONTRIBUTORS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7b326c523424..5ed3d266c8e2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -26,3 +26,6 @@ List of Contributors * [Vadim Khotilovich](https://github.com/khotilov) * [Yangqing Men](https://github.com/yanqingmen) * [Engpeng Yao](https://github.com/yepyao) +* [Giulio](https://github.com/giuliohome) +* [Jamie Hall](https://github.com/nerdcha) +* [Yen-Ying Lee](https://github.com/white1033) From d8fc16538ea83e624c19b4ad0e2839c6f9f3d581 Mon Sep 17 00:00:00 2001 From: orenov Date: Wed, 22 Jul 2015 12:03:01 +0300 Subject: [PATCH 030/126] issue #368, data.table problems --- R-package/R/xgb.model.dt.tree.R | 37 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/R-package/R/xgb.model.dt.tree.R b/R-package/R/xgb.model.dt.tree.R index 7eea3dfcd62d..d083566a56bd 100644 --- a/R-package/R/xgb.model.dt.tree.R +++ b/R-package/R/xgb.model.dt.tree.R @@ -133,34 +133,33 @@ xgb.model.dt.tree <- function(feature_names = NULL, filename_dump = NULL, model allTrees <- rbindlist(list(allTrees, dt), use.names = T, fill = F) } - yes <- allTrees[!is.na(Yes),Yes] - - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + yes <- allTrees[!is.na(Yes), Yes] + + set(allTrees, i = which(allTrees[, Feature] != "Leaf"), j = "Yes.Feature", - value = allTrees[ID == yes,Feature]) - - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + value = allTrees[ID %in% yes, Feature]) + + set(allTrees, i = which(allTrees[, Feature] != "Leaf"), j = "Yes.Cover", - value = allTrees[ID == yes,Cover]) - - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), - j = "Yes.Quality", - value = allTrees[ID == yes,Quality]) + value = allTrees[ID %in% yes, Cover]) - no <- allTrees[!is.na(No),No] + set(allTrees, i = which(allTrees[, Feature] != "Leaf"), + j = "Yes.Quality", + value = allTrees[ID %in% yes, Quality]) + no <- allTrees[!is.na(No), No] - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + set(allTrees, i = which(allTrees[, Feature] != "Leaf"), j = "No.Feature", - value = allTrees[ID == no,Feature]) + value = allTrees[ID %in% no, Feature]) - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + set(allTrees, i = which(allTrees[, Feature] != "Leaf"), j = "No.Cover", - value = allTrees[ID == no,Cover]) + value = allTrees[ID %in% no, Cover]) - set(allTrees, i = which(allTrees[,Feature]!= "Leaf"), + set(allTrees, i = which(allTrees[, Feature] != "Leaf"), j = "No.Quality", - value = allTrees[ID == no,Quality]) - + value = allTrees[ID %in% no, Quality]) + allTrees } From d120167725fd7d56ab84bf7feb479389b8866eab Mon Sep 17 00:00:00 2001 From: Will Stanton Date: Wed, 22 Jul 2015 09:19:22 -0600 Subject: [PATCH 031/126] Fixed a few typos in README --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4a5e7bf6eb35..21d15ce5633c 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ DMLC/XGBoost An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data -Checkout our [Comitters and Contributors](CONTRIBUTORS.md) who keep make xgboost better. +Checkout our [Committers and Contributors](CONTRIBUTORS.md) who help make xgboost better. -Documentations: [Documentation of dmlc/xgboost](doc/README.md) +Documentation: [Documentation of dmlc/xgboost](doc/README.md) -Issues Tracker: [https://github.com/dmlc/xgboost/issues](https://github.com/dmlc/xgboost/issues?q=is%3Aissue+label%3Aquestion) +Issue Tracker: [https://github.com/dmlc/xgboost/issues](https://github.com/dmlc/xgboost/issues?q=is%3Aissue+label%3Aquestion) Please join [XGBoost User Group](https://groups.google.com/forum/#!forum/xgboost-user/) to ask questions and share your experience on xgboost. - Use issue tracker for bug reports, feature requests etc. @@ -30,13 +30,13 @@ What's New - Checkout the winning solution at [Highlight links](doc/README.md#highlight-links) * XGBoost-0.4 release, see [CHANGES.md](CHANGES.md#xgboost-04) * XGBoost helps three champion teams to win [WWW2015 Microsoft Malware Classification Challenge (BIG 2015)](http://www.kaggle.com/c/malware-classification/forums/t/13490/say-no-to-overfitting-approaches-sharing) - - Checkout the winning solution at [Highlight links](doc/README.md#highlight-links) + - Check out the winning solution at [Highlight links](doc/README.md#highlight-links) * [External Memory Version](doc/external_memory.md) Contributing to XGBoost ========= -XGBoost has been developed and used by a group of active community. Everyone is more than welcomed to is a great way to make the project better and more accessible to more users. -* Checkout [Feature Wish List](https://github.com/dmlc/xgboost/labels/Wish-List) to see what can be improved, or open an issue if you want something. +XGBoost has been developed and used by a group of active community members. Everyone is more than welcome to contribute. It is a way to make the project better and more accessible to more users. +* Check out [Feature Wish List](https://github.com/dmlc/xgboost/labels/Wish-List) to see what can be improved, or open an issue if you want something. * Contribute to the [documents and examples](https://github.com/dmlc/xgboost/blob/master/doc/) to share your experience with other users. * Please add your name to [CONTRIBUTORS.md](CONTRIBUTORS.md) after your patch has been merged. @@ -66,5 +66,5 @@ Version XGBoost in Graphlab Create ========================== -* XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the Graphlab Create in http://graphlab.com/products/create/quick-start-guide.html -* Nice blogpost by Jay Gu using GLC boosted tree to solve kaggle bike sharing challenge: http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand +* XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to do data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the Graphlab Create in http://graphlab.com/products/create/quick-start-guide.html +* Nice blogpost by Jay Gu about using GLC boosted tree to solve kaggle bike sharing challenge: http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand From ba63b2886f32894c15caab87012f0862fbdd9242 Mon Sep 17 00:00:00 2001 From: Will Stanton Date: Wed, 22 Jul 2015 10:37:49 -0600 Subject: [PATCH 032/126] Check out vs. checkout Made it consistent across the README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 21d15ce5633c..97e348b43b7c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DMLC/XGBoost An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data -Checkout our [Committers and Contributors](CONTRIBUTORS.md) who help make xgboost better. +Check out our [Committers and Contributors](CONTRIBUTORS.md) who help make xgboost better. Documentation: [Documentation of dmlc/xgboost](doc/README.md) @@ -27,7 +27,7 @@ XGBoost is part of [Distributed Machine Learning Common](http://dmlc.github.io/) What's New ========== * XGBoost helps Chenglong Chen to win [Kaggle CrowdFlower Competition](https://www.kaggle.com/c/crowdflower-search-relevance) - - Checkout the winning solution at [Highlight links](doc/README.md#highlight-links) + - Check out the winning solution at [Highlight links](doc/README.md#highlight-links) * XGBoost-0.4 release, see [CHANGES.md](CHANGES.md#xgboost-04) * XGBoost helps three champion teams to win [WWW2015 Microsoft Malware Classification Challenge (BIG 2015)](http://www.kaggle.com/c/malware-classification/forums/t/13490/say-no-to-overfitting-approaches-sharing) - Check out the winning solution at [Highlight links](doc/README.md#highlight-links) From 9eca9bccf47d780add609fb8530b20d0f23a379c Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Wed, 22 Jul 2015 23:18:34 -0700 Subject: [PATCH 033/126] moving gitter chat up --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 97e348b43b7c..15775d67bc87 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ DMLC/XGBoost ================================== -[![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) +[![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data @@ -16,8 +16,6 @@ Please join [XGBoost User Group](https://groups.google.com/forum/#!forum/xgboost - Use issue tracker for bug reports, feature requests etc. - Use the user group to post your experience, ask questions about general usages. -Gitter for developers [![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - Distributed Version: [Distributed XGBoost](multi-node) Highlights of Usecases: [Highlight Links](doc/README.md#highlight-links) From 0ea5b14bd8aa1d6d89826f7e9ce56f5656ef7ec4 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Thu, 23 Jul 2015 01:12:33 -0700 Subject: [PATCH 034/126] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 97e348b43b7c..ff302175af0d 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ Version - Change log in [CHANGES.md](CHANGES.md) - This version is compatible with 0.3x versions +License +======= +© Contributors, 2015. Licensed under an [Apache-2](https://github.com/dmlc/xgboost/blob/master/LICENSE) license. + XGBoost in Graphlab Create ========================== * XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to do data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the Graphlab Create in http://graphlab.com/products/create/quick-start-guide.html From 141f9ebf4b2f1c2ebbbf10ed2008f53af906b2e1 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Fri, 24 Jul 2015 08:51:05 -0700 Subject: [PATCH 035/126] Update CHANGES.md --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 90fd77ebb7f8..0be001744862 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,3 +34,10 @@ xgboost-0.4 - xgboost python model is now pickable * sklearn wrapper is supported in python module * Experimental External memory version + +on going version +===== +* Python module now throw exception instead of crash terminal when a parameter error happens. +* Java api is ready for use +* Added more test cases and continuous integration to make each build more robust +* Improvements in sklearn compatible module From 198c5bb55e11a48dd0be351eed8dc9785e973022 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 24 Jul 2015 11:58:02 -0700 Subject: [PATCH 036/126] fix namespace and desc --- R-package/.Rbuildignore | 1 + R-package/DESCRIPTION | 2 +- R-package/R/utils.R | 4 ++-- R-package/R/xgb.cv.R | 2 +- R-package/R/xgb.plot.importance.R | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/R-package/.Rbuildignore b/R-package/.Rbuildignore index 6b3c4084e9f4..b37d627ba487 100644 --- a/R-package/.Rbuildignore +++ b/R-package/.Rbuildignore @@ -3,3 +3,4 @@ \.dll$ ^.*\.Rproj$ ^\.Rproj\.user$ +README.md diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index c6975af5e75a..6f784fbb309f 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -5,7 +5,7 @@ Version: 0.4-0 Date: 2015-05-11 Author: Tianqi Chen , Tong He , Michael Benesty Maintainer: Tong He -Description: Xgboost is short for eXtreme Gradient Boosting, which is an +Description: eXtreme Gradient Boosting, which is an efficient and scalable implementation of gradient boosting framework. This package is an R wrapper of xgboost. The package includes efficient linear model solver and tree learning algorithms. The package can automatically diff --git a/R-package/R/utils.R b/R-package/R/utils.R index f7f6b919221a..e58601df8b61 100644 --- a/R-package/R/utils.R +++ b/R-package/R/utils.R @@ -288,7 +288,7 @@ xgb.cv.aggcv <- function(res, showsd = TRUE) { } ret <- paste(ret, sprintf("%f", mean(stats)), sep="") if (showsd) { - ret <- paste(ret, sprintf("+%f", sd(stats)), sep="") + ret <- paste(ret, sprintf("+%f", stats::sd(stats)), sep="") } } return (ret) @@ -313,7 +313,7 @@ xgb.createFolds <- function(y, k = 10) if(cuts < 2) cuts <- 2 if(cuts > 5) cuts <- 5 y <- cut(y, - unique(quantile(y, probs = seq(0, 1, length = cuts))), + unique(stats::quantile(y, probs = seq(0, 1, length = cuts))), include.lowest = TRUE) } diff --git a/R-package/R/xgb.cv.R b/R-package/R/xgb.cv.R index 793d904cd195..a5364db52b8d 100644 --- a/R-package/R/xgb.cv.R +++ b/R-package/R/xgb.cv.R @@ -240,7 +240,7 @@ xgb.cv <- function(params=list(), data, nrounds, nfold, label = NULL, missing = else colnames <- colnamesMean type <- rep(x = "numeric", times = length(colnames)) - dt <- read.table(text = "", colClasses = type, col.names = colnames) %>% as.data.table + dt <- utils::read.table(text = "", colClasses = type, col.names = colnames) %>% as.data.table split <- str_split(string = history, pattern = "\t") for(line in split) dt <- line[2:length(line)] %>% str_extract_all(pattern = "\\d*\\.+\\d*") %>% unlist %>% as.numeric %>% as.list %>% {rbindlist(list(dt, .), use.names = F, fill = F)} diff --git a/R-package/R/xgb.plot.importance.R b/R-package/R/xgb.plot.importance.R index eb0f8e346c34..b86d143236a9 100644 --- a/R-package/R/xgb.plot.importance.R +++ b/R-package/R/xgb.plot.importance.R @@ -33,7 +33,7 @@ xgb.plot.importance <- function(importance_matrix = NULL, numberOfClusters = c(1 if (!"data.table" %in% class(importance_matrix)) { stop("importance_matrix: Should be a data.table.") } - if (!require(ggplot2, quietly = TRUE)) { + if (!requireNamespace(ggplot2, quietly = TRUE)) { stop("ggplot2 package is required for plotting the importance", call. = FALSE) } if (!requireNamespace("Ckmeans.1d.dp", quietly = TRUE)) { @@ -46,7 +46,7 @@ xgb.plot.importance <- function(importance_matrix = NULL, numberOfClusters = c(1 clusters <- suppressWarnings(Ckmeans.1d.dp::Ckmeans.1d.dp(importance_matrix[,Gain], numberOfClusters)) importance_matrix[,"Cluster":=clusters$cluster %>% as.character] - plot <- ggplot(importance_matrix, aes(x=reorder(Feature, Gain), y = Gain, width= 0.05), environment = environment())+ geom_bar(aes(fill=Cluster), stat="identity", position="identity") + coord_flip() + xlab("Features") + ylab("Gain") + ggtitle("Feature importance") + theme(plot.title = element_text(lineheight=.9, face="bold"), panel.grid.major.y = element_blank() ) + plot <- ggplot(importance_matrix, aes(x=stats::reorder(Feature, Gain), y = Gain, width= 0.05), environment = environment())+ geom_bar(aes(fill=Cluster), stat="identity", position="identity") + coord_flip() + xlab("Features") + ylab("Gain") + ggtitle("Feature importance") + theme(plot.title = element_text(lineheight=.9, face="bold"), panel.grid.major.y = element_blank() ) return(plot) } From a1c7104d7f7a797794ed6ecc73f76c9582dd562b Mon Sep 17 00:00:00 2001 From: hetong007 Date: Fri, 24 Jul 2015 19:11:08 +0000 Subject: [PATCH 037/126] fix crash --- R-package/R/xgb.plot.importance.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R-package/R/xgb.plot.importance.R b/R-package/R/xgb.plot.importance.R index b86d143236a9..f126dfe464ae 100644 --- a/R-package/R/xgb.plot.importance.R +++ b/R-package/R/xgb.plot.importance.R @@ -33,7 +33,7 @@ xgb.plot.importance <- function(importance_matrix = NULL, numberOfClusters = c(1 if (!"data.table" %in% class(importance_matrix)) { stop("importance_matrix: Should be a data.table.") } - if (!requireNamespace(ggplot2, quietly = TRUE)) { + if (!requireNamespace("ggplot2", quietly = TRUE)) { stop("ggplot2 package is required for plotting the importance", call. = FALSE) } if (!requireNamespace("Ckmeans.1d.dp", quietly = TRUE)) { @@ -46,7 +46,7 @@ xgb.plot.importance <- function(importance_matrix = NULL, numberOfClusters = c(1 clusters <- suppressWarnings(Ckmeans.1d.dp::Ckmeans.1d.dp(importance_matrix[,Gain], numberOfClusters)) importance_matrix[,"Cluster":=clusters$cluster %>% as.character] - plot <- ggplot(importance_matrix, aes(x=stats::reorder(Feature, Gain), y = Gain, width= 0.05), environment = environment())+ geom_bar(aes(fill=Cluster), stat="identity", position="identity") + coord_flip() + xlab("Features") + ylab("Gain") + ggtitle("Feature importance") + theme(plot.title = element_text(lineheight=.9, face="bold"), panel.grid.major.y = element_blank() ) + plot <- ggplot2::ggplot(importance_matrix, ggplot2::aes(x=stats::reorder(Feature, Gain), y = Gain, width= 0.05), environment = environment())+ ggplot2::geom_bar(ggplot2::aes(fill=Cluster), stat="identity", position="identity") + ggplot2::coord_flip() + ggplot2::xlab("Features") + ggplot2::ylab("Gain") + ggplot2::ggtitle("Feature importance") + ggplot2::theme(plot.title = ggplot2::element_text(lineheight=.9, face="bold"), panel.grid.major.y = ggplot2::element_blank() ) return(plot) } From e353a2e51cd0269a31bbc1dac4001fa5193d312a Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Fri, 24 Jul 2015 17:00:02 -0700 Subject: [PATCH 038/126] restructuring the README with an index --- README.md | 87 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4fabb7362caa..7a4cfa4c81fd 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,32 @@ -DMLC/XGBoost -================================== +XGBoost +======= [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. -It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data - -Check out our [Committers and Contributors](CONTRIBUTORS.md) who help make xgboost better. - -Documentation: [Documentation of dmlc/xgboost](doc/README.md) - -Issue Tracker: [https://github.com/dmlc/xgboost/issues](https://github.com/dmlc/xgboost/issues?q=is%3Aissue+label%3Aquestion) +An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. -Please join [XGBoost User Group](https://groups.google.com/forum/#!forum/xgboost-user/) to ask questions and share your experience on xgboost. - - Use issue tracker for bug reports, feature requests etc. - - Use the user group to post your experience, ask questions about general usages. - -Distributed Version: [Distributed XGBoost](multi-node) - -Highlights of Usecases: [Highlight Links](doc/README.md#highlight-links) +It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data XGBoost is part of [Distributed Machine Learning Common](http://dmlc.github.io/) projects +Contents +-------- +* [What's New](#whats-new) +* [Version](#version) +* [Documentation](doc/README.md) +* [Build Instruction](doc/build.md) +* [Features](#features) +* [Distributed XGBoost](multi-node) +* [Usecases](doc/README.md#highlight-links) +* [Bug Reporting](#bug-reporting) +* [Contributing to XGBoost](#contributing-to-xgboost) +* [Committers and Contributors](CONTRIBUTORS.md) +* [License](#license) +* [XGBoost in Graphlab Create](#xgboost-in-graphlab-create) + What's New -========== +---------- + * XGBoost helps Chenglong Chen to win [Kaggle CrowdFlower Competition](https://www.kaggle.com/c/crowdflower-search-relevance) - Check out the winning solution at [Highlight links](doc/README.md#highlight-links) * XGBoost-0.4 release, see [CHANGES.md](CHANGES.md#xgboost-04) @@ -31,42 +34,46 @@ What's New - Check out the winning solution at [Highlight links](doc/README.md#highlight-links) * [External Memory Version](doc/external_memory.md) -Contributing to XGBoost -========= -XGBoost has been developed and used by a group of active community members. Everyone is more than welcome to contribute. It is a way to make the project better and more accessible to more users. -* Check out [Feature Wish List](https://github.com/dmlc/xgboost/labels/Wish-List) to see what can be improved, or open an issue if you want something. -* Contribute to the [documents and examples](https://github.com/dmlc/xgboost/blob/master/doc/) to share your experience with other users. -* Please add your name to [CONTRIBUTORS.md](CONTRIBUTORS.md) after your patch has been merged. +Version +------- + +* Current version xgboost-0.4, a lot improvment has been made since 0.3 + - Change log in [CHANGES.md](CHANGES.md) + - This version is compatible with 0.3x versions Features -======== -* Easily accessible in python, R, Julia, CLI -* Fast speed and memory efficient - - Can be more than 10 times faster than GBM in sklearn and R +-------- + +* Easily accessible through python, R, Julia, CLI +* Fast and memory efficient + - Can be more than 10 times faster than GBM in sklearn and R. [benchm-ml numbers](https://github.com/szilard/benchm-ml) - Handles sparse matrices, support external memory * Accurate prediction, and used extensively by data scientists and kagglers - See [highlight links](https://github.com/dmlc/xgboost/blob/master/doc/README.md#highlight-links) * Distributed and Portable - The distributed version runs on Hadoop (YARN), MPI, SGE etc. - Scales to billions of examples and beyond + +Bug Reporting +------------- -Build -======= -* Run ```bash build.sh``` (you can also type make) - - Normally it gives what you want - - See [Build Instruction](doc/build.md) for more information +* For reporting bugs please use the [xgboost/issues](https://github.com/dmlc/xgboost/issues) page. +* For generic questions or to share your experience using xgboost please use the [XGBoost User Group](https://groups.google.com/forum/#!forum/xgboost-user/) -Version -======= -* Current version xgboost-0.4, a lot improvment has been made since 0.3 - - Change log in [CHANGES.md](CHANGES.md) - - This version is compatible with 0.3x versions + +Contributing to XGBoost +----------------------- + +XGBoost has been developed and used by a group of active community members. Everyone is more than welcome to contribute. It is a way to make the project better and more accessible to more users. +* Check out [Feature Wish List](https://github.com/dmlc/xgboost/labels/Wish-List) to see what can be improved, or open an issue if you want something. +* Contribute to the [documents and examples](https://github.com/dmlc/xgboost/blob/master/doc/) to share your experience with other users. +* Please add your name to [CONTRIBUTORS.md](CONTRIBUTORS.md) after your patch has been merged. License -======= +------- © Contributors, 2015. Licensed under an [Apache-2](https://github.com/dmlc/xgboost/blob/master/LICENSE) license. XGBoost in Graphlab Create -========================== +-------------------------- * XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to do data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the Graphlab Create in http://graphlab.com/products/create/quick-start-guide.html * Nice blogpost by Jay Gu about using GLC boosted tree to solve kaggle bike sharing challenge: http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand From cbdcbfc49c63c8c0201b429839e8b64c6a81ef52 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Sat, 25 Jul 2015 12:46:28 -0700 Subject: [PATCH 039/126] some more changes to remove redundant information --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7a4cfa4c81fd..18c5b77c120e 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,17 @@ What's New ---------- * XGBoost helps Chenglong Chen to win [Kaggle CrowdFlower Competition](https://www.kaggle.com/c/crowdflower-search-relevance) - - Check out the winning solution at [Highlight links](doc/README.md#highlight-links) + Check out the [winning solution](doc/README.md#highlight-links) * XGBoost-0.4 release, see [CHANGES.md](CHANGES.md#xgboost-04) * XGBoost helps three champion teams to win [WWW2015 Microsoft Malware Classification Challenge (BIG 2015)](http://www.kaggle.com/c/malware-classification/forums/t/13490/say-no-to-overfitting-approaches-sharing) - - Check out the winning solution at [Highlight links](doc/README.md#highlight-links) + Check out the [winning solution](doc/README.md#highlight-links) * [External Memory Version](doc/external_memory.md) Version ------- -* Current version xgboost-0.4, a lot improvment has been made since 0.3 - - Change log in [CHANGES.md](CHANGES.md) +* Current version xgboost-0.4 + - [Change log](CHANGES.md) - This version is compatible with 0.3x versions Features @@ -48,8 +48,7 @@ Features * Fast and memory efficient - Can be more than 10 times faster than GBM in sklearn and R. [benchm-ml numbers](https://github.com/szilard/benchm-ml) - Handles sparse matrices, support external memory -* Accurate prediction, and used extensively by data scientists and kagglers - - See [highlight links](https://github.com/dmlc/xgboost/blob/master/doc/README.md#highlight-links) +* Accurate prediction, and used extensively by data scientists and kagglers - [highlight links](https://github.com/dmlc/xgboost/blob/master/doc/README.md#highlight-links) * Distributed and Portable - The distributed version runs on Hadoop (YARN), MPI, SGE etc. - Scales to billions of examples and beyond @@ -75,5 +74,5 @@ License XGBoost in Graphlab Create -------------------------- -* XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to do data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the Graphlab Create in http://graphlab.com/products/create/quick-start-guide.html +* XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to do data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the [Graphlab Create](http://graphlab.com/products/create/quick-start-guide.html) * Nice blogpost by Jay Gu about using GLC boosted tree to solve kaggle bike sharing challenge: http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand From af042f6a248afa01b882c6f41ad068d28036e84c Mon Sep 17 00:00:00 2001 From: tqchen Date: Sat, 25 Jul 2015 21:14:50 -0700 Subject: [PATCH 040/126] make things cxx98 compatible --- src/utils/thread_buffer.h | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/utils/thread_buffer.h b/src/utils/thread_buffer.h index 2119f53ab670..c4dc1185d23a 100644 --- a/src/utils/thread_buffer.h +++ b/src/utils/thread_buffer.h @@ -11,9 +11,14 @@ #include #include #include "./utils.h" +// threading util could not run on solaris +#ifndef XGBOOST_STRICT_CXX98_ #include "./thread.h" +#endif + namespace xgboost { namespace utils { +#if !defined(XGBOOST_STRICT_CXX98_) /*! * \brief buffered loading iterator that uses multithread * this template method will assume the following paramters @@ -201,6 +206,52 @@ class ThreadBuffer { loading_need.Post(); } }; +#else +// a dummy single threaded ThreadBuffer +// use this to resolve R's solaris compatibility for now +template +class ThreadBuffer { + public: + ThreadBuffer() : init_end_(false) {} + ~ThreadBuffer() { + if (init_end_) { + factory_.FreeSpace(data_); + factory_.Destroy(); + } + } + inline void SetParam(const char *name, const char *val) { + } + inline bool Init(void) { + if (!factory_.Init()) return false; + data_ = factory_.Create(); + return (init_end_ = true); + } + inline void BeforeFirst(void) { + factory_.BeforeFirst(); + } + inline bool Next(Elem &elem) { // NOLINT(*) + if (factory_.LoadNext(data_)) { + elem = data_; return true; + } else { + return false; + } + } + inline ElemFactory &get_factory() { + return factory_; + } + inline const ElemFactory &get_factory() const { + return factory_; + } + + private: + // initialized + bool init_end_; + // current data + Elem data_; + // factory object used to load configures + ElemFactory factory_; +}; +#endif // !defined(XGBOOST_STRICT_CXX98_) } // namespace utils } // namespace xgboost #endif // XGBOOST_UTILS_THREAD_BUFFER_H_ From f6c82d52ec498f8f73261d58b7811748f6480799 Mon Sep 17 00:00:00 2001 From: tqchen Date: Sat, 25 Jul 2015 21:17:28 -0700 Subject: [PATCH 041/126] make solaris happy --- wrapper/xgboost_wrapper.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/wrapper/xgboost_wrapper.cpp b/wrapper/xgboost_wrapper.cpp index fb33d0392351..b27132d50c56 100644 --- a/wrapper/xgboost_wrapper.cpp +++ b/wrapper/xgboost_wrapper.cpp @@ -98,7 +98,14 @@ class Booster: public learner::BoostLearner { private: bool init_model; }; +} // namespace wrapper +} // namespace xgboost + +using namespace xgboost::wrapper; +#ifndef XGBOOST_STRICT_CXX98_ +namespace xgboost { +namespace wrapper { // helper to support threadlocal struct ThreadLocalStore { std::vector data; @@ -126,8 +133,6 @@ static ThreadLocalStore thread_local_store; } // namespace wrapper } // namespace xgboost -using namespace xgboost::wrapper; - /*! \brief macro to guard beginning and end section of all functions */ #define API_BEGIN() try { /*! @@ -173,6 +178,17 @@ const char *XGBSetGetLastError_(const char *str_set) { } return last_error->c_str(); } +#else +// crippled implementation for solaris case +// exception handling is not needed for R, so it is OK. +#define API_BEGIN() +#define API_END_FINALIZE(Finalize) return 0 +#define API_END() return 0 + +const char *XGBSetGetLastError_(const char *str_set) { + return NULL; +} +#endif /*! \brief return str message of the last error */ const char *XGBGetLastError() { From 0dbac3d11ec59f9c8322866d645e3717f68566d0 Mon Sep 17 00:00:00 2001 From: tqchen Date: Sat, 25 Jul 2015 21:23:40 -0700 Subject: [PATCH 042/126] fix travis --- scripts/travis_after_failure.sh | 2 +- src/utils/thread_buffer.h | 4 ++-- wrapper/xgboost_wrapper.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/travis_after_failure.sh b/scripts/travis_after_failure.sh index 15b74d87f243..921e14953e4b 100755 --- a/scripts/travis_after_failure.sh +++ b/scripts/travis_after_failure.sh @@ -1,5 +1,5 @@ #!/bin/bash if [ ${TASK} == "R-package" ]; then - cat R-package/xgboost.Rcheck/*.log + cat xgboost/xgboost.Rcheck/*.log fi diff --git a/src/utils/thread_buffer.h b/src/utils/thread_buffer.h index c4dc1185d23a..bc4fb9f5e0d9 100644 --- a/src/utils/thread_buffer.h +++ b/src/utils/thread_buffer.h @@ -219,7 +219,7 @@ class ThreadBuffer { factory_.Destroy(); } } - inline void SetParam(const char *name, const char *val) { + inline void SetParam(const char *name, const char *val) { } inline bool Init(void) { if (!factory_.Init()) return false; @@ -242,7 +242,7 @@ class ThreadBuffer { inline const ElemFactory &get_factory() const { return factory_; } - + private: // initialized bool init_end_; diff --git a/wrapper/xgboost_wrapper.cpp b/wrapper/xgboost_wrapper.cpp index b27132d50c56..6956b567d27f 100644 --- a/wrapper/xgboost_wrapper.cpp +++ b/wrapper/xgboost_wrapper.cpp @@ -181,14 +181,14 @@ const char *XGBSetGetLastError_(const char *str_set) { #else // crippled implementation for solaris case // exception handling is not needed for R, so it is OK. -#define API_BEGIN() +#define API_BEGIN() #define API_END_FINALIZE(Finalize) return 0 #define API_END() return 0 const char *XGBSetGetLastError_(const char *str_set) { return NULL; } -#endif +#endif // XGBOOST_STRICT_CXX98_ /*! \brief return str message of the last error */ const char *XGBGetLastError() { From b1dec917c7fe17f9d95f26ce1b47735c225dafd5 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Sat, 25 Jul 2015 21:29:46 -0700 Subject: [PATCH 043/126] Update page_fmatrix-inl.hpp --- src/io/page_fmatrix-inl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/page_fmatrix-inl.hpp b/src/io/page_fmatrix-inl.hpp index 2aaec5b19e44..2fa5c83bd950 100644 --- a/src/io/page_fmatrix-inl.hpp +++ b/src/io/page_fmatrix-inl.hpp @@ -319,7 +319,7 @@ class FMatrixPage : public IFMatrix { bytes_write += spage; double tnow = rabit::utils::GetTime(); double tdiff = tnow - tstart; - utils::Printf("Writting to %s in %g MB/s, %lu MB written current speed:%g MB/s\n", + utils::Printf("Writting to %s in %g MB/s, %lu MB written\n", col_data_name_.c_str(), (bytes_write >> 20UL) / tdiff, (bytes_write >> 20UL)); From 9a936721d84873c5d97fda04f9c96760e82500e5 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Sun, 26 Jul 2015 20:12:51 -0700 Subject: [PATCH 044/126] dropping raw graphlab url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18c5b77c120e..0a09c516895b 100644 --- a/README.md +++ b/README.md @@ -75,4 +75,4 @@ License XGBoost in Graphlab Create -------------------------- * XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to do data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the [Graphlab Create](http://graphlab.com/products/create/quick-start-guide.html) -* Nice blogpost by Jay Gu about using GLC boosted tree to solve kaggle bike sharing challenge: http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand +* Nice [blogpost](http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand) by Jay Gu about using GLC boosted tree to solve kaggle bike sharing challenge: From f2eb55683cc20f8e7885add55cca50916bb7ad5f Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Sun, 26 Jul 2015 20:30:59 -0700 Subject: [PATCH 045/126] some more links and restructuring --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0a09c516895b..51ed94633969 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,13 @@ Version Features -------- -* Easily accessible through python, R, Julia, CLI -* Fast and memory efficient - - Can be more than 10 times faster than GBM in sklearn and R. [benchm-ml numbers](https://github.com/szilard/benchm-ml) - - Handles sparse matrices, support external memory +* Easily accessible through CLI, [python](guide-python/basic_walkthrough.py), + [R](../R-package/demo/basic_walkthrough.R), + [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/basic_walkthrough.jl) +* Its fast! Benchmark numbers comparing xgboost, H20, Spark, R - [benchm-ml numbers](https://github.com/szilard/benchm-ml) +* Memory efficient - Handles sparse matrices, supports external memory * Accurate prediction, and used extensively by data scientists and kagglers - [highlight links](https://github.com/dmlc/xgboost/blob/master/doc/README.md#highlight-links) -* Distributed and Portable - - The distributed version runs on Hadoop (YARN), MPI, SGE etc. - - Scales to billions of examples and beyond +* Distributed version runs on Hadoop (YARN), MPI, SGE etc., scales to billions of examples. Bug Reporting ------------- From fc27e2f32d79632261d9a905e3a34f4df42061e7 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Sun, 26 Jul 2015 20:31:51 -0700 Subject: [PATCH 046/126] adding DMLC back to the title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51ed94633969..df53f5e46081 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -XGBoost +DMLC/XGBoost ======= [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From 74055cc15e3fde12847d483b7b36b2746f759d8f Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Sun, 26 Jul 2015 21:22:35 -0700 Subject: [PATCH 047/126] fixing broken basic_walkthrough links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df53f5e46081..0f6ffc7faa3b 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ Version Features -------- -* Easily accessible through CLI, [python](guide-python/basic_walkthrough.py), - [R](../R-package/demo/basic_walkthrough.R), +* Easily accessible through CLI, [python](https://github.com/dmlc/xgboost/blob/master/demo/guide-python/basic_walkthrough.py), + [R](https://github.com/dmlc/xgboost/blob/master/R-package/demo/basic_walkthrough.R), [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/basic_walkthrough.jl) * Its fast! Benchmark numbers comparing xgboost, H20, Spark, R - [benchm-ml numbers](https://github.com/szilard/benchm-ml) * Memory efficient - Handles sparse matrices, supports external memory From 0c8c23194928d3afe3e5a2c1119fe01bb43ea0de Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Wed, 29 Jul 2015 14:28:34 -0700 Subject: [PATCH 048/126] Fixing duplicate params in demo Issue in "demo(package="xgboost", custom_objective)" > bst <- xgb.train(param, dtrain, num_round, watchlist, + objective=logregobj, eval_metric=evalerror) Error in xgb.train(param, dtrain, num_round, watchlist, objective = logregobj, : Duplicated term in parameters. Please check your list of params. --- R-package/demo/custom_objective.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R-package/demo/custom_objective.R b/R-package/demo/custom_objective.R index 201f23d98d20..7234ead869a3 100644 --- a/R-package/demo/custom_objective.R +++ b/R-package/demo/custom_objective.R @@ -33,7 +33,7 @@ evalerror <- function(preds, dtrain) { return(list(metric = "error", value = err)) } -param <- list(max.depth=2,eta=1,nthread = 2, silent=1, +param <- list(max.depth=2, eta=1, nthread = 2, silent=1, objective=logregobj, eval_metric=evalerror) print ('start training with user customized objective') # training with customized objective, we can also do step by step training @@ -57,9 +57,9 @@ logregobjattr <- function(preds, dtrain) { hess <- preds * (1 - preds) return(list(grad = grad, hess = hess)) } - +param <- list(max.depth=2, eta=1, nthread = 2, silent=1, + objective=logregobjattr, eval_metric=evalerror) print ('start training with user customized objective, with additional attributes in DMatrix') # training with customized objective, we can also do step by step training # simply look at xgboost.py's implementation of train -bst <- xgb.train(param, dtrain, num_round, watchlist, - objective=logregobj, eval_metric=evalerror) +bst <- xgb.train(param, dtrain, num_round, watchlist) From cca955fc9465f627899023a492b74d1b62c22e92 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Wed, 29 Jul 2015 16:20:55 -0700 Subject: [PATCH 049/126] add setuptools info --- wrapper/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/wrapper/README.md b/wrapper/README.md index ab013faf601e..c5368bd7d392 100644 --- a/wrapper/README.md +++ b/wrapper/README.md @@ -5,6 +5,7 @@ This folder provides wrapper of xgboost to other languages Python ===== * To make the python module, type ```./build.sh``` in the root directory of project +* Make sure you have [setuptools](https://pypi.python.org/pypi/setuptools) * Install with `python setup.py install` from this directory. * Refer also to the walk through example in [demo folder](../demo/guide-python) * **NOTE**: if you want to run XGBoost process in parallel using the fork backend for joblib/multiprocessing, you must build XGBoost without support for OpenMP by `make no_omp=1`. Otherwise, use the forkserver (in Python 3.4) or spawn backend. See the sklearn_parallel.py demo. From 5f9f42292c82afea411a3939e58544ef4cc723d2 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 17:49:55 -0700 Subject: [PATCH 050/126] fix sklearn best score --- wrapper/xgboost.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/wrapper/xgboost.py b/wrapper/xgboost.py index 77f5bedb8f54..32f9a52b4aa4 100644 --- a/wrapper/xgboost.py +++ b/wrapper/xgboost.py @@ -866,6 +866,7 @@ def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, bst.best_iteration = best_score_i return bst + class CVPack(object): """"Auxiliary datastruct to hold one fold of CV.""" def __init__(self, dtrain, dtest, param): @@ -1154,9 +1155,11 @@ def fit(self, X, y, eval_set=None, eval_metric=None, eval_results = {k: np.array(v, dtype=float) for k, v in eval_results.items()} eval_results = {k: np.array(v) for k, v in eval_results.items()} - self.eval_results_ = eval_results - self.best_score_ = self._Booster.best_score - self.best_iteration_ = self._Booster.best_iteration + self.eval_results = eval_results + + if early_stopping_rounds is not None: + self.best_score = self._Booster.best_score + self.best_iteration = self._Booster.best_iteration return self def predict(self, data): @@ -1266,9 +1269,11 @@ def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, if eval_results: eval_results = {k: np.array(v, dtype=float) for k, v in eval_results.items()} - self.eval_results_ = eval_results - self.best_score_ = self._Booster.best_score - self.best_iteration_ = self._Booster.best_iteration + self.eval_results = eval_results + + if early_stopping_rounds is not None: + self.best_score = self._Booster.best_score + self.best_iteration = self._Booster.best_iteration return self From efde0eb1719f84c6ac74dadbff134379b8889d4c Mon Sep 17 00:00:00 2001 From: Tong He Date: Wed, 29 Jul 2015 18:16:59 -0700 Subject: [PATCH 051/126] enable travis on os x --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index ac4f58154f29..8d754c33324e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ sudo: true +# Enabling test on Linux and OS X +os: + - linux + - osx + # Use Build Matrix to do lint and build seperately env: matrix: From 75c8bdf962f38a8eeb521b1f966779a1883c4721 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 18:24:19 -0700 Subject: [PATCH 052/126] add osx matrix --- .travis.yml | 6 ++++++ scripts/travis_script.sh | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ac4f58154f29..4940738506d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ env: - TASK=build CXX=g++ - TASK=build-with-dmlc CXX=g++ +os: + - linux + - osx + # dependent apt packages addons: apt: @@ -25,6 +29,7 @@ addons: - python-nose before_install: + - scripts/travis_osx_install.sh - git clone https://github.com/dmlc/dmlc-core - export TRAVIS=dmlc-core/scripts/travis/ - export PYTHONPATH=${PYTHONPATH}:${PWD}/wrapper @@ -33,6 +38,7 @@ before_install: install: - pip install cpplint pylint --user `whoami` + script: scripts/travis_script.sh diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index 5702d35cd6b5..d382a1a2ece6 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -2,7 +2,9 @@ # main script of travis if [ ${TASK} == "lint" ]; then - make lint || exit -1 + if [ ${TRAVIS_OS_NAME} != "osx" ]; then + make lint || exit -1 + fi fi if [ ${TASK} == "build" ]; then From f44511e94df2b0bc6620c87b000facac8897c4f0 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 18:29:06 -0700 Subject: [PATCH 053/126] fix mac build --- scripts/travis_osx_install.sh | 11 +++++++++++ scripts/travis_script.sh | 4 ++++ 2 files changed, 15 insertions(+) create mode 100755 scripts/travis_osx_install.sh diff --git a/scripts/travis_osx_install.sh b/scripts/travis_osx_install.sh new file mode 100755 index 000000000000..d82dfe63d0e8 --- /dev/null +++ b/scripts/travis_osx_install.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ ${TRAVIS_OS_NAME} != "osx" ]; then + exit 0 +fi + +brew update +brew install unzip +brew install python-numpy +brew install python-scipy +brew install python-nose diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index d382a1a2ece6..3717c2ed2b56 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -7,6 +7,10 @@ if [ ${TASK} == "lint" ]; then fi fi +if [ ${TRAVIS_OS_NAME} != "osx" ]; then + export no_omp=1 +fi + if [ ${TASK} == "build" ]; then make all CXX=${CXX} || exit -1 fi From 2ab6907fe287df32bfd5602f15aef832e2d02986 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 18:45:42 -0700 Subject: [PATCH 054/126] add os lrt --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index a24bea327534..aa9bf632f857 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,12 @@ ifeq ($(OS), Windows_NT) export CC = gcc -m64 endif +UNAME= $(shell uname) + +ifeq ($(UNAME), Linux) + LDFLAGS += -lrt +endif + ifeq ($(no_omp),1) CFLAGS += -DDISABLE_OPENMP else From 24a188588a6d3dae7a8d7be803cc57336f14bebb Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 20:10:29 -0700 Subject: [PATCH 055/126] ok --- scripts/travis_osx_install.sh | 9 +++++---- scripts/travis_script.sh | 6 +++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/travis_osx_install.sh b/scripts/travis_osx_install.sh index d82dfe63d0e8..4b4c714b8057 100755 --- a/scripts/travis_osx_install.sh +++ b/scripts/travis_osx_install.sh @@ -5,7 +5,8 @@ if [ ${TRAVIS_OS_NAME} != "osx" ]; then fi brew update -brew install unzip -brew install python-numpy -brew install python-scipy -brew install python-nose + +if [ ${TASK} == "python-package" ]; then + brew install python git + easy_install pip scipy numpy +fi diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index 3717c2ed2b56..85bfab47f91c 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -18,7 +18,11 @@ fi if [ ${TASK} == "build-with-dmlc" ]; then cd dmlc-core cp make/config.mk . - echo "USE_S3=1" >> config.mk + if [ ${TRAVIS_OS_NAME} != "osx" ]; then + echo "USE_S3=1" >> config.mk + else + echo "USE_S3=0" >> config.mk + fi make all CXX=${CXX}|| exit -1 cd .. make dmlc=dmlc-core CXX=${CXX} || exit -1 From 6062f4dd587ebcc413e5d838702c74cdd39792a1 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 20:18:54 -0700 Subject: [PATCH 056/126] update --- scripts/travis_osx_install.sh | 3 ++- scripts/travis_script.sh | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/travis_osx_install.sh b/scripts/travis_osx_install.sh index 4b4c714b8057..95e5838a895d 100755 --- a/scripts/travis_osx_install.sh +++ b/scripts/travis_osx_install.sh @@ -8,5 +8,6 @@ brew update if [ ${TASK} == "python-package" ]; then brew install python git - easy_install pip scipy numpy + easy_install pip + pip install numpy scipy fi diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index 85bfab47f91c..0d4cf8049524 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -7,7 +7,7 @@ if [ ${TASK} == "lint" ]; then fi fi -if [ ${TRAVIS_OS_NAME} != "osx" ]; then +if [ ${TRAVIS_OS_NAME} == "osx" ]; then export no_omp=1 fi @@ -37,7 +37,10 @@ if [ ${TASK} == "python-package" ]; then nosetests tests/python || exit -1 fi +# only test java under linux for now if [ ${TASK} == "java-package" ]; then - make java CXX=${CXX} || exit -1 - scripts/travis_java_script.sh || exit -1 + if [ ${TRAVIS_OS_NAME} != "osx" ]; then + make java CXX=${CXX} || exit -1 + scripts/travis_java_script.sh || exit -1 + fi fi From d9599f816fab5cd777a8f5ab8bfafc98a5736316 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:01:53 -0700 Subject: [PATCH 057/126] add appvegor --- appvegor.yml | 22 ++++++++++++++++++++++ scripts/travis_osx_install.sh | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 appvegor.yml diff --git a/appvegor.yml b/appvegor.yml new file mode 100644 index 000000000000..7adff2a2d6a1 --- /dev/null +++ b/appvegor.yml @@ -0,0 +1,22 @@ +platform: + - x64 + - x86 + +configuration: + - Release + +clone_folder: c:\dmlc\xgboost + + +install: + - cmd: git clone https://github.com/ogrisel/python-appveyor-demo + - ps: if (-not(Test-Path($env:PYTHON))) { & python-appvegor-demo\appveyor\install.ps1 } + - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + - "python --version" + - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + - "pip instal nose numpy scipy" + +build: + parallel: true + project: windows\xgboost.sln + diff --git a/scripts/travis_osx_install.sh b/scripts/travis_osx_install.sh index 95e5838a895d..8121afd6b88a 100755 --- a/scripts/travis_osx_install.sh +++ b/scripts/travis_osx_install.sh @@ -9,5 +9,5 @@ brew update if [ ${TASK} == "python-package" ]; then brew install python git easy_install pip - pip install numpy scipy + pip install numpy scipy nose fi From 15286523cf98b6d4fe28d9c4a5bbb8f3b6ae41aa Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:06:29 -0700 Subject: [PATCH 058/126] ok --- appvegor.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/appvegor.yml b/appvegor.yml index 7adff2a2d6a1..bb9bfe8c9ba7 100644 --- a/appvegor.yml +++ b/appvegor.yml @@ -5,9 +5,6 @@ platform: configuration: - Release -clone_folder: c:\dmlc\xgboost - - install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - ps: if (-not(Test-Path($env:PYTHON))) { & python-appvegor-demo\appveyor\install.ps1 } @@ -17,6 +14,6 @@ install: - "pip instal nose numpy scipy" build: - parallel: true - project: windows\xgboost.sln + - "msbuild windows\xgboost.sln" + From 8f6e5e197b6be3bdcc0d32520616a1e3b142206e Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:07:18 -0700 Subject: [PATCH 059/126] ok --- appvegor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appvegor.yml b/appvegor.yml index bb9bfe8c9ba7..2d7f233feb71 100644 --- a/appvegor.yml +++ b/appvegor.yml @@ -12,8 +12,8 @@ install: - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - "pip instal nose numpy scipy" - -build: - "msbuild windows\xgboost.sln" +build: false + From fa41fe3f13b03d041f6f01ade3d51ce5a5eaf8e7 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:09:42 -0700 Subject: [PATCH 060/126] rename --- appvegor.yml => appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename appvegor.yml => appveyor.yml (89%) diff --git a/appvegor.yml b/appveyor.yml similarity index 89% rename from appvegor.yml rename to appveyor.yml index 2d7f233feb71..ead70d5efbca 100644 --- a/appvegor.yml +++ b/appveyor.yml @@ -12,8 +12,8 @@ install: - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - "pip instal nose numpy scipy" - - "msbuild windows\xgboost.sln" -build: false +build: + project: windows\xgboost.sln From c870c08b7ebd46f189e6d94cc98b51817ba2703f Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:11:44 -0700 Subject: [PATCH 061/126] disable openmp in dmlc --- scripts/travis_script.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index 0d4cf8049524..402cb69927d2 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -9,6 +9,7 @@ fi if [ ${TRAVIS_OS_NAME} == "osx" ]; then export no_omp=1 + export NO_OPENMP=1 fi if [ ${TASK} == "build" ]; then From 2bf0eeb82dbe60512151feef6f33e24a6f7fc344 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:15:25 -0700 Subject: [PATCH 062/126] update appvegor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index ead70d5efbca..96018b1d01c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ configuration: install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - - ps: if (-not(Test-Path($env:PYTHON))) { & python-appvegor-demo\appveyor\install.ps1 } + - ps: python-appvegor-demo\appveyor\install.ps1 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" From 899bfbfbaed6c05225c9fae530426daead64e3d1 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:19:49 -0700 Subject: [PATCH 063/126] rest --- appveyor.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 96018b1d01c2..fbad6f51a371 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,8 @@ configuration: install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - - ps: python-appvegor-demo\appveyor\install.ps1 + - cmd: copy-item python-appvegor-demo\appvegor\install.ps1 install.ps1 + - ps: .\\install.ps1 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" @@ -15,5 +16,3 @@ install: build: project: windows\xgboost.sln - - From 0d5741bc7469ebba3e480716619a6c1d2279d528 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:21:15 -0700 Subject: [PATCH 064/126] rest --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index fbad6f51a371..a7f7e2de0211 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ configuration: install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - - cmd: copy-item python-appvegor-demo\appvegor\install.ps1 install.ps1 + - cmd: copy python-appvegor-demo\appvegor\install.ps1 install.ps1 - ps: .\\install.ps1 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" From 033a0c139e80fbbbd6863169c0a83cf315b8f3ef Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:21:58 -0700 Subject: [PATCH 065/126] ok --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index a7f7e2de0211..b6feac2a541c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,7 +8,7 @@ configuration: install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - cmd: copy python-appvegor-demo\appvegor\install.ps1 install.ps1 - - ps: .\\install.ps1 + - ps: install.ps1 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" From bb13c2cd1587d5fcf9b1f0e99b488f291b6394bd Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:25:52 -0700 Subject: [PATCH 066/126] ok --- appveyor.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b6feac2a541c..5c7dd813fa3f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,8 +7,9 @@ configuration: install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - - cmd: copy python-appvegor-demo\appvegor\install.ps1 install.ps1 - - ps: install.ps1 + - cmd: mkdir appveyor + - cmd: copy python-appveyor-demo\appvegor\install.ps1 appveyor\install.ps1 + - ps: appvegor\install.ps1 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" From 1a91b15a6ea9cbbaaf749f3f3b3dfbbff3e48a07 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:27:40 -0700 Subject: [PATCH 067/126] ok --- appveyor.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5c7dd813fa3f..2309917772a2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,9 +7,7 @@ configuration: install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - - cmd: mkdir appveyor - - cmd: copy python-appveyor-demo\appvegor\install.ps1 appveyor\install.ps1 - - ps: appvegor\install.ps1 + - ps: python-appveyor-demo\appveyor\install.ps1 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" From c2c5ad2d47fccaf76e0ea8d6dd0d903ad7c9763d Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:35:15 -0700 Subject: [PATCH 068/126] finl --- appveyor.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 2309917772a2..0211f15c4f38 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,7 @@ +environment: + global: + CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\python-appveyor-demo\\appveyor\\run_with_env.cmd" + platform: - x64 - x86 @@ -7,11 +11,17 @@ configuration: install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo - - ps: python-appveyor-demo\appveyor\install.ps1 + - ECHO "Filesystem root:" + - ps: "ls \"C:/\"" + + - ECHO "Installed SDKs:" + - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" + + - ps: if (-not(Test-Path($env:PYTHON))) { & python-appveyor-demo\appveyor\install.ps1 } - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - "pip instal nose numpy scipy" + - ""%CMD_IN_ENV% pip install numpy scipy nose" build: project: windows\xgboost.sln From 7e166066185773e66a9afc3145e4116937af4da1 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:36:28 -0700 Subject: [PATCH 069/126] ok --- appveyor.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0211f15c4f38..388acccf3b56 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,7 +21,10 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - ""%CMD_IN_ENV% pip install numpy scipy nose" + - "%CMD_IN_ENV% pip install numpy scipy nose" build: project: windows\xgboost.sln + +test_script: + - "%CMD_IN_ENV% nosetests test\\python" \ No newline at end of file From 6f4148faab6f9034ab6bb5f622dd7513facfb789 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:37:16 -0700 Subject: [PATCH 070/126] ok --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 388acccf3b56..0d0a2b72f2ee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,7 +17,7 @@ install: - ECHO "Installed SDKs:" - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" - - ps: if (-not(Test-Path($env:PYTHON))) { & python-appveyor-demo\appveyor\install.ps1 } + - ps: python-appveyor-demo\appveyor\install.ps1 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" From e30c724bd4b5f156bbe80903415937fd46e9a48d Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:39:34 -0700 Subject: [PATCH 071/126] ok --- appveyor.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0d0a2b72f2ee..401a150c3ec9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,9 +2,17 @@ environment: global: CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\python-appveyor-demo\\appveyor\\run_with_env.cmd" + matrix: + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" # currently 2.7.9 + PYTHON_ARCH: "64" + + - PYTHON: "C:\\Python33-x64" + PYTHON_VERSION: "3.3.x" # currently 3.3.5 + PYTHON_ARCH: "64" + platform: - x64 - - x86 configuration: - Release From 259dea0777f3d7e9cacdca76660fcc9cccf02f02 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 21:46:41 -0700 Subject: [PATCH 072/126] incomplete appveyor --- appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 401a150c3ec9..67c310a007de 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,10 +29,7 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - "%CMD_IN_ENV% pip install numpy scipy nose" build: project: windows\xgboost.sln -test_script: - - "%CMD_IN_ENV% nosetests test\\python" \ No newline at end of file From 5dab410537e098f212863d4e8824dd7ec261dcde Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 22:00:38 -0700 Subject: [PATCH 073/126] ok --- appveyor.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 67c310a007de..c9948ba5f89c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,5 +31,4 @@ install: - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" build: - project: windows\xgboost.sln - + - cmd: msbuild windows\xgboost.sln /openmp- From 67d332e0f55c9618e05fa5eb6cc2eb1977751200 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 22:01:42 -0700 Subject: [PATCH 074/126] ok --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c9948ba5f89c..6a64fb15f8b5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,6 +29,6 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - -build: - cmd: msbuild windows\xgboost.sln /openmp- + +build: off From 6f01fa50ce6f5e0ff177ea5b59e4e4ae7f6eac2a Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 22:14:38 -0700 Subject: [PATCH 075/126] try disable omp --- appveyor.yml | 1 + src/utils/omp.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6a64fb15f8b5..3f5011824d9f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,7 @@ environment: global: CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\python-appveyor-demo\\appveyor\\run_with_env.cmd" + DISABLE_OPENMP: 1 matrix: - PYTHON: "C:\\Python27-x64" diff --git a/src/utils/omp.h b/src/utils/omp.h index ddd3467d9e48..e3c61110c01e 100644 --- a/src/utils/omp.h +++ b/src/utils/omp.h @@ -7,10 +7,10 @@ #ifndef XGBOOST_UTILS_OMP_H_ #define XGBOOST_UTILS_OMP_H_ -#if defined(_OPENMP) +#if defined(_OPENMP) && !defined(DISABLE_OPENMP) #include #else -#ifndef DISABLE_OPENMP +#if !defined(DISABLE_OPENMP) // use pragma message instead of warning #pragma message("Warning: OpenMP is not available,"\ "xgboost will be compiled into single-thread code."\ From 0a9c8acd6db158a10616de64799af1343d80c0bc Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 22:17:25 -0700 Subject: [PATCH 076/126] final --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3f5011824d9f..30c009c2adbd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,6 +30,6 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - cmd: msbuild windows\xgboost.sln /openmp- -build: off +build: + project: windows\xgboost.sln From 73ec467dd3911319edb36714589e8ac27a2417e2 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 22:22:43 -0700 Subject: [PATCH 077/126] final --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 30c009c2adbd..ab1040434724 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,6 +30,6 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + - cmd: msbuild windows\xgboost.sln /DDISABLE_OPENMP=1 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" -build: - project: windows\xgboost.sln +build: off \ No newline at end of file From ebefb78fd418b4fc542c4652207d692728388b1f Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 22:26:21 -0700 Subject: [PATCH 078/126] use debug --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ab1040434724..dfc291922f65 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ platform: - x64 configuration: - - Release + - Debug install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo @@ -30,6 +30,6 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - cmd: msbuild windows\xgboost.sln /DDISABLE_OPENMP=1 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" -build: off \ No newline at end of file +build: + project: windows\xgboost.sln \ No newline at end of file From 4a6f4eaac95474da59a3b6107b327bec456c18d5 Mon Sep 17 00:00:00 2001 From: tqchen Date: Wed, 29 Jul 2015 22:31:35 -0700 Subject: [PATCH 079/126] giveup for now, appveyor do not support openmp for msvc yet allow openmp to switch on --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index dfc291922f65..1fb594cb2bfe 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ platform: - x64 configuration: - - Debug + - Release install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo From 11f27beccd2a64d8800d00e44371e5fc2a324c51 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Wed, 29 Jul 2015 22:41:06 -0700 Subject: [PATCH 080/126] checkin debug --- appveyor.yml | 2 +- subtree/rabit/windows/rabit/rabit.vcxproj | 2 +- windows/xgboost.sln | 8 ++++---- windows/xgboost/xgboost.vcxproj | 1 + windows/xgboost_wrapper/xgboost_wrapper.vcxproj | 1 + 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 1fb594cb2bfe..dfc291922f65 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ platform: - x64 configuration: - - Release + - Debug install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo diff --git a/subtree/rabit/windows/rabit/rabit.vcxproj b/subtree/rabit/windows/rabit/rabit.vcxproj index 5948e4c17332..c9594b182530 100644 --- a/subtree/rabit/windows/rabit/rabit.vcxproj +++ b/subtree/rabit/windows/rabit/rabit.vcxproj @@ -29,7 +29,7 @@ MultiByte - Application + StaticLibrary true MultiByte diff --git a/windows/xgboost.sln b/windows/xgboost.sln index 7bd8db5b2c5c..b1371a26685e 100644 --- a/windows/xgboost.sln +++ b/windows/xgboost.sln @@ -22,15 +22,16 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|Win32.ActiveCfg = Debug|Win32 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|Win32.Build.0 = Debug|Win32 - {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|x64.ActiveCfg = Release|x64 - {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|x64.Build.0 = Release|x64 + {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|x64.ActiveCfg = Debug|x64 + {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|x64.Build.0 = Debug|x64 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Release|Win32.ActiveCfg = Release|Win32 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Release|Win32.Build.0 = Release|Win32 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Release|x64.ActiveCfg = Release|x64 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Release|x64.Build.0 = Release|x64 {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Debug|Win32.ActiveCfg = Debug|Win32 {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Debug|Win32.Build.0 = Debug|Win32 - {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Debug|x64.ActiveCfg = Debug|Win32 + {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Debug|x64.ActiveCfg = Debug|x64 + {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Debug|x64.Build.0 = Debug|x64 {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Release|Win32.ActiveCfg = Release|Win32 {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Release|Win32.Build.0 = Release|Win32 {B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}.Release|x64.ActiveCfg = Release|x64 @@ -46,7 +47,6 @@ Global {20A0E4D7-20C7-4EC1-BDF6-0D469CE239AA}.Debug|Win32.ActiveCfg = Debug|Win32 {20A0E4D7-20C7-4EC1-BDF6-0D469CE239AA}.Debug|Win32.Build.0 = Debug|Win32 {20A0E4D7-20C7-4EC1-BDF6-0D469CE239AA}.Debug|x64.ActiveCfg = Debug|x64 - {20A0E4D7-20C7-4EC1-BDF6-0D469CE239AA}.Debug|x64.Build.0 = Debug|x64 {20A0E4D7-20C7-4EC1-BDF6-0D469CE239AA}.Release|Win32.ActiveCfg = Release|Win32 {20A0E4D7-20C7-4EC1-BDF6-0D469CE239AA}.Release|Win32.Build.0 = Release|Win32 {20A0E4D7-20C7-4EC1-BDF6-0D469CE239AA}.Release|x64.ActiveCfg = Release|x64 diff --git a/windows/xgboost/xgboost.vcxproj b/windows/xgboost/xgboost.vcxproj index c14d84645f73..00846f36abcd 100644 --- a/windows/xgboost/xgboost.vcxproj +++ b/windows/xgboost/xgboost.vcxproj @@ -85,6 +85,7 @@ true + $(OutDir)\rabit.lib;%(AdditionalDependencies) diff --git a/windows/xgboost_wrapper/xgboost_wrapper.vcxproj b/windows/xgboost_wrapper/xgboost_wrapper.vcxproj index 62f7d0fd3315..cff3cde6559a 100644 --- a/windows/xgboost_wrapper/xgboost_wrapper.vcxproj +++ b/windows/xgboost_wrapper/xgboost_wrapper.vcxproj @@ -86,6 +86,7 @@ true + $(OutDir)\rabit.lib;%(AdditionalDependencies) From f9c02aa40f616e2035f2180282b098c09626adbc Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Wed, 29 Jul 2015 22:45:28 -0700 Subject: [PATCH 081/126] final attempt --- src/utils/omp.h | 2 +- windows/xgboost.sln | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/omp.h b/src/utils/omp.h index e3c61110c01e..c7a04dc3207b 100644 --- a/src/utils/omp.h +++ b/src/utils/omp.h @@ -10,7 +10,7 @@ #if defined(_OPENMP) && !defined(DISABLE_OPENMP) #include #else -#if !defined(DISABLE_OPENMP) +#if !defined(DISABLE_OPENMP) && !defined(_MSC_VER) // use pragma message instead of warning #pragma message("Warning: OpenMP is not available,"\ "xgboost will be compiled into single-thread code."\ diff --git a/windows/xgboost.sln b/windows/xgboost.sln index b1371a26685e..a3c861f6c1b5 100644 --- a/windows/xgboost.sln +++ b/windows/xgboost.sln @@ -23,7 +23,6 @@ Global {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|Win32.ActiveCfg = Debug|Win32 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|Win32.Build.0 = Debug|Win32 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|x64.ActiveCfg = Debug|x64 - {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Debug|x64.Build.0 = Debug|x64 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Release|Win32.ActiveCfg = Release|Win32 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Release|Win32.Build.0 = Release|Win32 {19766C3F-7508-49D0-BAAC-0988FCC9970C}.Release|x64.ActiveCfg = Release|x64 From 264c636adf4fce3a93f6f6c4a1389ddf5079de6d Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Wed, 29 Jul 2015 22:50:23 -0700 Subject: [PATCH 082/126] add dep --- windows/xgboost.sln | 3 +++ 1 file changed, 3 insertions(+) diff --git a/windows/xgboost.sln b/windows/xgboost.sln index a3c861f6c1b5..d94c14932231 100644 --- a/windows/xgboost.sln +++ b/windows/xgboost.sln @@ -7,6 +7,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xgboost", "xgboost\xgboost. EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xgboost_wrapper", "xgboost_wrapper\xgboost_wrapper.vcxproj", "{B0E22ADD-7849-4D3A-BDC6-0932C5F11ED5}" + ProjectSection(ProjectDependencies) = postProject + {D7B77D06-4F5F-4BD7-B81E-7CC8EBBE684F} = {D7B77D06-4F5F-4BD7-B81E-7CC8EBBE684F} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rabit", "..\subtree\rabit\windows\rabit\rabit.vcxproj", "{D7B77D06-4F5F-4BD7-B81E-7CC8EBBE684F}" EndProject From 53107995bfed1f7ab3de8e6bb4fc2ea29fcb8889 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Wed, 29 Jul 2015 22:54:21 -0700 Subject: [PATCH 083/126] give up for now --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index dfc291922f65..c966886fb47c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,5 +31,5 @@ install: - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" -build: - project: windows\xgboost.sln \ No newline at end of file +build: off + #project: windows\xgboost.sln \ No newline at end of file From 7560518eec848ed380a9da0a31f76fd33b5c659e Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Wed, 29 Jul 2015 23:23:40 -0700 Subject: [PATCH 084/126] sleep --- appveyor.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c966886fb47c..d7d0c58b143b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,8 @@ environment: global: CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\python-appveyor-demo\\appveyor\\run_with_env.cmd" DISABLE_OPENMP: 1 - + VisualStudioVersion: 12.0 + matrix: - PYTHON: "C:\\Python27-x64" PYTHON_VERSION: "2.7.x" # currently 2.7.9 @@ -16,7 +17,7 @@ platform: - x64 configuration: - - Debug + - Release install: - cmd: git clone https://github.com/ogrisel/python-appveyor-demo @@ -31,5 +32,5 @@ install: - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" -build: off - #project: windows\xgboost.sln \ No newline at end of file +build: + project: windows\xgboost.sln \ No newline at end of file From f6fed76e7ee22a6e1dbfe253c039ca33dabaea48 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Wed, 29 Jul 2015 23:24:54 -0700 Subject: [PATCH 085/126] not working --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index d7d0c58b143b..c1367d52e877 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,5 +32,5 @@ install: - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" -build: - project: windows\xgboost.sln \ No newline at end of file +build: off + #project: windows\xgboost.sln \ No newline at end of file From c2fec29bfa91d0fad27fa8d0338eac283aac4996 Mon Sep 17 00:00:00 2001 From: tqchen Date: Thu, 30 Jul 2015 22:04:45 -0700 Subject: [PATCH 086/126] python package refactor into python-package --- Makefile | 2 +- demo/.gitignore | 3 +- demo/README.md | 18 +- demo/guide-python/sklearn_examples.py | 10 - doc/python.md | 2 +- python-package/.gitignore | 3 + python-package/README.md | 7 + python-package/setup.py | 21 + .../xgboost/core.py | 630 +----------------- windows/README.md | 2 +- wrapper/README.md | 29 +- wrapper/__init__.py | 0 wrapper/setup.py | 39 -- 13 files changed, 88 insertions(+), 678 deletions(-) create mode 100644 python-package/.gitignore create mode 100644 python-package/README.md create mode 100644 python-package/setup.py rename wrapper/xgboost.py => python-package/xgboost/core.py (50%) delete mode 100644 wrapper/__init__.py delete mode 100644 wrapper/setup.py diff --git a/Makefile b/Makefile index aa9bf632f857..c9e35e80c545 100644 --- a/Makefile +++ b/Makefile @@ -169,7 +169,7 @@ Rcheck: # lint requires dmlc to be in current folder lint: - dmlc-core/scripts/lint.py xgboost $(LINT_LANG) src wrapper R-package + dmlc-core/scripts/lint.py xgboost $(LINT_LANG) src wrapper R-package python-package clean: $(RM) -rf $(OBJ) $(BIN) $(MPIBIN) $(MPIOBJ) $(SLIB) *.o */*.o */*/*.o *~ */*~ */*/*~ diff --git a/demo/.gitignore b/demo/.gitignore index e52797d1512f..ee79c704b268 100644 --- a/demo/.gitignore +++ b/demo/.gitignore @@ -1 +1,2 @@ -*.libsvm \ No newline at end of file +*.libsvm +*.pkl diff --git a/demo/README.md b/demo/README.md index 49e9e52b88e7..fcfaa8434e64 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,14 +1,14 @@ XGBoost Examples ==== -This folder contains all the code examples using xgboost. +This folder contains all the code examples using xgboost. * Contribution of examples, benchmarks is more than welcome! * If you like to share how you use xgboost to solve your problem, send a pull request:) - + Features Walkthrough ==== -This is a list of short codes introducing different functionalities of xgboost and its wrapper. -* Basic walkthrough of wrappers +This is a list of short codes introducing different functionalities of xgboost packages. +* Basic walkthrough of packages [python](guide-python/basic_walkthrough.py) [R](../R-package/demo/basic_walkthrough.R) [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/basic_walkthrough.jl) @@ -20,18 +20,18 @@ This is a list of short codes introducing different functionalities of xgboost a [python](guide-python/boost_from_prediction.py) [R](../R-package/demo/boost_from_prediction.R) [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/boost_from_prediction.jl) -* Predicting using first n trees +* Predicting using first n trees [python](guide-python/predict_first_ntree.py) [R](../R-package/demo/boost_from_prediction.R) - [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/boost_from_prediction.jl) + [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/boost_from_prediction.jl) * Generalized Linear Model [python](guide-python/generalized_linear_model.py) [R](../R-package/demo/generalized_linear_model.R) - [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/generalized_linear_model.jl) + [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/generalized_linear_model.jl) * Cross validation [python](guide-python/cross_validation.py) [R](../R-package/demo/cross_validation.R) - [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/cross_validation.jl) + [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/cross_validation.jl) * Predicting leaf indices [python](guide-python/predict_leaf_indices.py) [R](../R-package/demo/predict_leaf_indices.R) @@ -48,5 +48,5 @@ However, the parameter settings can be applied to all versions Benchmarks ==== * [Starter script for Kaggle Higgs Boson](kaggle-higgs) -* [Kaggle Tradeshift winning solution by daxiongshu](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) +* [Kaggle Tradeshift winning solution by daxiongshu](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) diff --git a/demo/guide-python/sklearn_examples.py b/demo/guide-python/sklearn_examples.py index 56fed1dd2bbe..7ce95b491dd9 100755 --- a/demo/guide-python/sklearn_examples.py +++ b/demo/guide-python/sklearn_examples.py @@ -75,13 +75,3 @@ clf.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="auc", eval_set=[(X_test, y_test)]) -# Custom evaluation function -from sklearn.metrics import log_loss - - -def log_loss_eval(y_pred, y_true): - return "log-loss", log_loss(y_true.get_label(), y_pred) - - -clf.fit(X_train, y_train, early_stopping_rounds=10, eval_metric=log_loss_eval, - eval_set=[(X_test, y_test)]) diff --git a/doc/python.md b/doc/python.md index dfe886fe9f99..93b5c43d4bac 100644 --- a/doc/python.md +++ b/doc/python.md @@ -14,7 +14,7 @@ A [walk through python example](https://github.com/tqchen/xgboost/blob/master/de = #### Install -To install XGBoost, you need to run `make` in the root directory of the project and then in the `wrappers` directory run +To install XGBoost, you need to run `make` in the root directory of the project and then in the `python-package` directory run ```shell python setup.py install diff --git a/python-package/.gitignore b/python-package/.gitignore new file mode 100644 index 000000000000..d765c67c773e --- /dev/null +++ b/python-package/.gitignore @@ -0,0 +1,3 @@ +build +dist +*.egg* \ No newline at end of file diff --git a/python-package/README.md b/python-package/README.md new file mode 100644 index 000000000000..a4ac71d4d233 --- /dev/null +++ b/python-package/README.md @@ -0,0 +1,7 @@ +XGBoost Python Package +====================== +* To make the python module, type ```./build.sh``` in the root directory of project +* Make sure you have [setuptools](https://pypi.python.org/pypi/setuptools) +* Install with `python setup.py install` from this directory. +* Refer also to the walk through example in [demo folder](../demo/guide-python) +* **NOTE**: if you want to run XGBoost process in parallel using the fork backend for joblib/multiprocessing, you must build XGBoost without support for OpenMP by `make no_omp=1`. Otherwise, use the forkserver (in Python 3.4) or spawn backend. See the sklearn_parallel.py demo. diff --git a/python-package/setup.py b/python-package/setup.py new file mode 100644 index 000000000000..42e39f3bad5b --- /dev/null +++ b/python-package/setup.py @@ -0,0 +1,21 @@ +# pylint: disable=invalid-name +"""Setup xgboost package.""" +from __future__ import absolute_import +import sys +from setuptools import setup +sys.path.insert(0, '.') +import xgboost + +LIB_PATH = xgboost.core.find_lib_path() + +setup(name='xgboost', + version=xgboost.__version__, + description=xgboost.__doc__, + install_requires=[ + 'numpy', + 'scipy', + ], + zip_safe=False, + packages=['xgboost'], + data_files=[('xgboost', [LIB_PATH[0]])], + url='https://github.com/dmlc/xgboost') diff --git a/wrapper/xgboost.py b/python-package/xgboost/core.py similarity index 50% rename from wrapper/xgboost.py rename to python-package/xgboost/core.py index 32f9a52b4aa4..85017cb82709 100644 --- a/wrapper/xgboost.py +++ b/python-package/xgboost/core.py @@ -1,17 +1,10 @@ # coding: utf-8 -""" -xgboost: eXtreme Gradient Boosting library - -Version: 0.40 -Authors: Tianqi Chen, Bing Xu -Early stopping by Zygmunt Zając -""" -# pylint: disable=too-many-arguments, too-many-locals, too-many-lines, invalid-name, fixme +# pylint: disable=too-many-arguments +"""Core XGBoost Library.""" from __future__ import absolute_import import os import sys -import re import ctypes import platform import collections @@ -19,13 +12,6 @@ import numpy as np import scipy.sparse -try: - from sklearn.base import BaseEstimator - from sklearn.base import RegressorMixin, ClassifierMixin - from sklearn.preprocessing import LabelEncoder - SKLEARN_INSTALLED = True -except ImportError: - SKLEARN_INSTALLED = False class XGBoostLibraryNotFound(Exception): """Error throwed by when xgboost is not found""" @@ -35,7 +21,6 @@ class XGBoostError(Exception): """Error throwed by xgboost trainer.""" pass -__all__ = ['DMatrix', 'CVPack', 'Booster', 'aggcv', 'cv', 'mknfold', 'train'] if sys.version_info[0] == 3: # pylint: disable=invalid-name @@ -44,30 +29,43 @@ class XGBoostError(Exception): # pylint: disable=invalid-name STRING_TYPES = basestring, -def load_xglib(): - """Load the xgboost library.""" + +def find_lib_path(): + """Load find the path to xgboost dynamic library files. + + Returns + ------- + lib_path: list(string) + List of all found library path to xgboost + """ curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) - dll_path = [curr_path] + dll_path = [curr_path, os.path.join(curr_path, '../../wrapper/')] if os.name == 'nt': if platform.architecture()[0] == '64bit': - dll_path.append(os.path.join(curr_path, '../windows/x64/Release/')) + dll_path.append(os.path.join(curr_path, '../../windows/x64/Release/')) else: - dll_path.append(os.path.join(curr_path, '../windows/Release/')) + dll_path.append(os.path.join(curr_path, '../../windows/Release/')) if os.name == 'nt': dll_path = [os.path.join(p, 'xgboost_wrapper.dll') for p in dll_path] else: dll_path = [os.path.join(p, 'libxgboostwrapper.so') for p in dll_path] lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)] - if len(dll_path) == 0: + if len(lib_path) == 0: raise XGBoostLibraryNotFound( - 'cannot find find the files in the candicate path ' + str(dll_path)) + 'Cannot find XGBoost Libarary in the candicate path %s,' + + 'Did you run build.sh in root oath?' % str(dll_path)) + return lib_path + +def _load_lib(): + """Load xgboost Library.""" + lib_path = find_lib_path() lib = ctypes.cdll.LoadLibrary(lib_path[0]) lib.XGBGetLastError.restype = ctypes.c_char_p return lib # load the XGBoost library globally -_LIB = load_xglib() +_LIB = _load_lib() def _check_call(ret): """Check the return value of C API call @@ -117,7 +115,11 @@ def c_array(ctype, values): class DMatrix(object): - """Data Matrix used in XGBoost.""" + """Data Matrix used in XGBoost. + + DMatrix is a internal data structure that used by XGBoost + which is optimized for both memory efficiency and training speed. + """ def __init__(self, data, label=None, missing=0.0, weight=None, silent=False): """ Data matrix used in XGBoost. @@ -400,11 +402,14 @@ def slice(self, rindex): class Booster(object): - """"A Booster of of XGBoost.""" + """"A Booster of of XGBoost. + + Booster is the model of xgboost, that contains low level routines for + training, prediction and evaluation. + """ def __init__(self, params=None, cache=(), model_file=None): # pylint: disable=invalid-name - """ - Learner class. + """Initialize the Booster. Parameters ---------- @@ -735,570 +740,3 @@ def get_fscore(self, fmap=''): else: fmap[fid] += 1 return fmap - - -def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, - early_stopping_rounds=None, evals_result=None, verbose_eval=True): - # pylint: disable=too-many-statements,too-many-branches, attribute-defined-outside-init - """Train a booster with given parameters. - - Parameters - ---------- - params : dict - Booster params. - dtrain : DMatrix - Data to be trained. - num_boost_round: int - Number of boosting iterations. - watchlist (evals): list of pairs (DMatrix, string) - List of items to be evaluated during training, this allows user to watch - performance on the validation set. - obj : function - Customized objective function. - feval : function - Customized evaluation function. - early_stopping_rounds: int - Activates early stopping. Validation error needs to decrease at least - every round(s) to continue training. - Requires at least one item in evals. - If there's more than one, will use the last. - Returns the model from the last iteration (not the best one). - If early stopping occurs, the model will have two additional fields: - bst.best_score and bst.best_iteration. - evals_result: dict - This dictionary stores the evaluation results of all the items in watchlist - verbose_eval : bool - If `verbose_eval` then the evaluation metric on the validation set, if - given, is printed at each boosting stage. - - Returns - ------- - booster : a trained booster model - """ - evals = list(evals) - bst = Booster(params, [dtrain] + [d[0] for d in evals]) - - if evals_result is not None: - if not isinstance(evals_result, dict): - raise TypeError('evals_result has to be a dictionary') - else: - evals_name = [d[1] for d in evals] - evals_result.clear() - evals_result.update({key: [] for key in evals_name}) - - if not early_stopping_rounds: - for i in range(num_boost_round): - bst.update(dtrain, i, obj) - if len(evals) != 0: - bst_eval_set = bst.eval_set(evals, i, feval) - if isinstance(bst_eval_set, STRING_TYPES): - msg = bst_eval_set - else: - msg = bst_eval_set.decode() - - if verbose_eval: - sys.stderr.write(msg + '\n') - if evals_result is not None: - res = re.findall(":-?([0-9.]+).", msg) - for key, val in zip(evals_name, res): - evals_result[key].append(val) - return bst - - else: - # early stopping - if len(evals) < 1: - raise ValueError('For early stopping you need at least one set in evals.') - - sys.stderr.write("Will train until {} error hasn't decreased in {} rounds.\n".format(\ - evals[-1][1], early_stopping_rounds)) - - # is params a list of tuples? are we using multiple eval metrics? - if isinstance(params, list): - if len(params) != len(dict(params).items()): - raise ValueError('Check your params.'\ - 'Early stopping works with single eval metric only.') - params = dict(params) - - # either minimize loss or maximize AUC/MAP/NDCG - maximize_score = False - if 'eval_metric' in params: - maximize_metrics = ('auc', 'map', 'ndcg') - if any(params['eval_metric'].startswith(x) for x in maximize_metrics): - maximize_score = True - - if maximize_score: - best_score = 0.0 - else: - best_score = float('inf') - - best_msg = '' - best_score_i = 0 - - for i in range(num_boost_round): - bst.update(dtrain, i, obj) - bst_eval_set = bst.eval_set(evals, i, feval) - - if isinstance(bst_eval_set, STRING_TYPES): - msg = bst_eval_set - else: - msg = bst_eval_set.decode() - - if verbose_eval: - sys.stderr.write(msg + '\n') - - if evals_result is not None: - res = re.findall(":-([0-9.]+).", msg) - for key, val in zip(evals_name, res): - evals_result[key].append(val) - - score = float(msg.rsplit(':', 1)[1]) - if (maximize_score and score > best_score) or \ - (not maximize_score and score < best_score): - best_score = score - best_score_i = i - best_msg = msg - elif i - best_score_i >= early_stopping_rounds: - sys.stderr.write("Stopping. Best iteration:\n{}\n\n".format(best_msg)) - bst.best_score = best_score - bst.best_iteration = best_score_i - break - bst.best_score = best_score - bst.best_iteration = best_score_i - return bst - - -class CVPack(object): - """"Auxiliary datastruct to hold one fold of CV.""" - def __init__(self, dtrain, dtest, param): - """"Initialize the CVPack""" - self.dtrain = dtrain - self.dtest = dtest - self.watchlist = [(dtrain, 'train'), (dtest, 'test')] - self.bst = Booster(param, [dtrain, dtest]) - - def update(self, iteration, fobj): - """"Update the boosters for one iteration""" - self.bst.update(self.dtrain, iteration, fobj) - - def eval(self, iteration, feval): - """"Evaluate the CVPack for one iteration.""" - return self.bst.eval_set(self.watchlist, iteration, feval) - - -def mknfold(dall, nfold, param, seed, evals=(), fpreproc=None): - """ - Make an n-fold list of CVPack from random indices. - """ - evals = list(evals) - np.random.seed(seed) - randidx = np.random.permutation(dall.num_row()) - kstep = len(randidx) / nfold - idset = [randidx[(i * kstep): min(len(randidx), (i + 1) * kstep)] for i in range(nfold)] - ret = [] - for k in range(nfold): - dtrain = dall.slice(np.concatenate([idset[i] for i in range(nfold) if k != i])) - dtest = dall.slice(idset[k]) - # run preprocessing on the data set if needed - if fpreproc is not None: - dtrain, dtest, tparam = fpreproc(dtrain, dtest, param.copy()) - else: - tparam = param - plst = list(tparam.items()) + [('eval_metric', itm) for itm in evals] - ret.append(CVPack(dtrain, dtest, plst)) - return ret - - -def aggcv(rlist, show_stdv=True): - # pylint: disable=invalid-name - """ - Aggregate cross-validation results. - """ - cvmap = {} - ret = rlist[0].split()[0] - for line in rlist: - arr = line.split() - assert ret == arr[0] - for it in arr[1:]: - if not isinstance(it, STRING_TYPES): - it = it.decode() - k, v = it.split(':') - if k not in cvmap: - cvmap[k] = [] - cvmap[k].append(float(v)) - for k, v in sorted(cvmap.items(), key=lambda x: x[0]): - v = np.array(v) - if not isinstance(ret, STRING_TYPES): - ret = ret.decode() - if show_stdv: - ret += '\tcv-%s:%f+%f' % (k, np.mean(v), np.std(v)) - else: - ret += '\tcv-%s:%f' % (k, np.mean(v)) - return ret - - -def cv(params, dtrain, num_boost_round=10, nfold=3, metrics=(), - obj=None, feval=None, fpreproc=None, show_stdv=True, seed=0): - # pylint: disable = invalid-name - """Cross-validation with given paramaters. - - Parameters - ---------- - params : dict - Booster params. - dtrain : DMatrix - Data to be trained. - num_boost_round : int - Number of boosting iterations. - nfold : int - Number of folds in CV. - metrics : list of strings - Evaluation metrics to be watched in CV. - obj : function - Custom objective function. - feval : function - Custom evaluation function. - fpreproc : function - Preprocessing function that takes (dtrain, dtest, param) and returns - transformed versions of those. - show_stdv : bool - Whether to display the standard deviation. - seed : int - Seed used to generate the folds (passed to numpy.random.seed). - - Returns - ------- - evaluation history : list(string) - """ - results = [] - cvfolds = mknfold(dtrain, nfold, params, seed, metrics, fpreproc) - for i in range(num_boost_round): - for fold in cvfolds: - fold.update(i, obj) - res = aggcv([f.eval(i, feval) for f in cvfolds], show_stdv) - sys.stderr.write(res + '\n') - results.append(res) - return results - - -# used for compatiblity without sklearn -XGBModelBase = object -XGBClassifierBase = object -XGBRegressorBase = object -if SKLEARN_INSTALLED: - XGBModelBase = BaseEstimator - XGBRegressorBase = RegressorMixin - XGBClassifierBase = ClassifierMixin - -class XGBModel(XGBModelBase): - # pylint: disable=too-many-arguments, too-many-instance-attributes, invalid-name - """Implementation of the Scikit-Learn API for XGBoost. - - Parameters - ---------- - max_depth : int - Maximum tree depth for base learners. - learning_rate : float - Boosting learning rate (xgb's "eta") - n_estimators : int - Number of boosted trees to fit. - silent : boolean - Whether to print messages while running boosting. - objective : string - Specify the learning task and the corresponding learning objective. - - nthread : int - Number of parallel threads used to run xgboost. - gamma : float - Minimum loss reduction required to make a further partition on a leaf node of the tree. - min_child_weight : int - Minimum sum of instance weight(hessian) needed in a child. - max_delta_step : int - Maximum delta step we allow each tree's weight estimation to be. - subsample : float - Subsample ratio of the training instance. - colsample_bytree : float - Subsample ratio of columns when constructing each tree. - - base_score: - The initial prediction score of all instances, global bias. - seed : int - Random number seed. - missing : float, optional - Value in the data which needs to be present as a missing value. If - None, defaults to np.nan. - """ - def __init__(self, max_depth=3, learning_rate=0.1, n_estimators=100, - silent=True, objective="reg:linear", - nthread=-1, gamma=0, min_child_weight=1, max_delta_step=0, - subsample=1, colsample_bytree=1, - base_score=0.5, seed=0, missing=None): - if not SKLEARN_INSTALLED: - raise XGBoostError('sklearn needs to be installed in order to use this module') - self.max_depth = max_depth - self.learning_rate = learning_rate - self.n_estimators = n_estimators - self.silent = silent - self.objective = objective - - self.nthread = nthread - self.gamma = gamma - self.min_child_weight = min_child_weight - self.max_delta_step = max_delta_step - self.subsample = subsample - self.colsample_bytree = colsample_bytree - - self.base_score = base_score - self.seed = seed - self.missing = missing if missing is not None else np.nan - self._Booster = None - - def __setstate__(self, state): - # backward compatiblity code - # load booster from raw if it is raw - # the booster now support pickle - bst = state["_Booster"] - if bst is not None and not isinstance(bst, Booster): - state["_Booster"] = Booster(model_file=bst) - self.__dict__.update(state) - - def booster(self): - """Get the underlying xgboost Booster of this model. - - This will raise an exception when fit was not called - - Returns - ------- - booster : a xgboost booster of underlying model - """ - if self._Booster is None: - raise XGBoostError('need to call fit beforehand') - return self._Booster - - def get_params(self, deep=False): - """Get parameter.s""" - params = super(XGBModel, self).get_params(deep=deep) - if params['missing'] is np.nan: - params['missing'] = None # sklearn doesn't handle nan. see #4725 - if not params.get('eval_metric', True): - del params['eval_metric'] # don't give as None param to Booster - return params - - def get_xgb_params(self): - """Get xgboost type parameters.""" - xgb_params = self.get_params() - - xgb_params['silent'] = 1 if self.silent else 0 - - if self.nthread <= 0: - xgb_params.pop('nthread', None) - return xgb_params - - def fit(self, X, y, eval_set=None, eval_metric=None, - early_stopping_rounds=None, verbose=True): - # pylint: disable=missing-docstring,invalid-name,attribute-defined-outside-init - """ - Fit the gradient boosting model - - Parameters - ---------- - X : array_like - Feature matrix - y : array_like - Labels - eval_set : list, optional - A list of (X, y) tuple pairs to use as a validation set for - early-stopping - eval_metric : str, callable, optional - If a str, should be a built-in evaluation metric to use. See - doc/parameter.md. If callable, a custom evaluation metric. The call - signature is func(y_predicted, y_true) where y_true will be a - DMatrix object such that you may need to call the get_label - method. It must return a str, value pair where the str is a name - for the evaluation and value is the value of the evaluation - function. This objective is always minimized. - early_stopping_rounds : int - Activates early stopping. Validation error needs to decrease at - least every round(s) to continue training. - Requires at least one item in evals. If there's more than one, - will use the last. Returns the model from the last iteration - (not the best one). If early stopping occurs, the model will - have two additional fields: bst.best_score and bst.best_iteration. - verbose : bool - If `verbose` and an evaluation set is used, writes the evaluation - metric measured on the validation set to stderr. - """ - trainDmatrix = DMatrix(X, label=y, missing=self.missing) - - eval_results = {} - if eval_set is not None: - evals = list(DMatrix(x[0], label=x[1]) for x in eval_set) - evals = list(zip(evals, ["validation_{}".format(i) for i in - range(len(evals))])) - else: - evals = () - - params = self.get_xgb_params() - - feval = eval_metric if callable(eval_metric) else None - if eval_metric is not None: - if callable(eval_metric): - eval_metric = None - else: - params.update({'eval_metric': eval_metric}) - - self._Booster = train(params, trainDmatrix, - self.n_estimators, evals=evals, - early_stopping_rounds=early_stopping_rounds, - evals_result=eval_results, feval=feval, - verbose_eval=verbose) - if eval_results: - eval_results = {k: np.array(v, dtype=float) - for k, v in eval_results.items()} - eval_results = {k: np.array(v) for k, v in eval_results.items()} - self.eval_results = eval_results - - if early_stopping_rounds is not None: - self.best_score = self._Booster.best_score - self.best_iteration = self._Booster.best_iteration - return self - - def predict(self, data): - # pylint: disable=missing-docstring,invalid-name - test_dmatrix = DMatrix(data, missing=self.missing) - return self.booster().predict(test_dmatrix) - - -class XGBClassifier(XGBModel, XGBClassifierBase): - # pylint: disable=missing-docstring,too-many-arguments,invalid-name - __doc__ = """ - Implementation of the scikit-learn API for XGBoost classification - """ + "\n".join(XGBModel.__doc__.split('\n')[2:]) - - def __init__(self, max_depth=3, learning_rate=0.1, - n_estimators=100, silent=True, - objective="binary:logistic", - nthread=-1, gamma=0, min_child_weight=1, - max_delta_step=0, subsample=1, colsample_bytree=1, - base_score=0.5, seed=0, missing=None): - super(XGBClassifier, self).__init__(max_depth, learning_rate, - n_estimators, silent, objective, - nthread, gamma, min_child_weight, - max_delta_step, subsample, - colsample_bytree, - base_score, seed, missing) - - def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, - early_stopping_rounds=None, verbose=True): - # pylint: disable = attribute-defined-outside-init,arguments-differ - """ - Fit gradient boosting classifier - - Parameters - ---------- - X : array_like - Feature matrix - y : array_like - Labels - sample_weight : array_like - Weight for each instance - eval_set : list, optional - A list of (X, y) pairs to use as a validation set for - early-stopping - eval_metric : str, callable, optional - If a str, should be a built-in evaluation metric to use. See - doc/parameter.md. If callable, a custom evaluation metric. The call - signature is func(y_predicted, y_true) where y_true will be a - DMatrix object such that you may need to call the get_label - method. It must return a str, value pair where the str is a name - for the evaluation and value is the value of the evaluation - function. This objective is always minimized. - early_stopping_rounds : int, optional - Activates early stopping. Validation error needs to decrease at - least every round(s) to continue training. - Requires at least one item in evals. If there's more than one, - will use the last. Returns the model from the last iteration - (not the best one). If early stopping occurs, the model will - have two additional fields: bst.best_score and bst.best_iteration. - verbose : bool - If `verbose` and an evaluation set is used, writes the evaluation - metric measured on the validation set to stderr. - """ - eval_results = {} - self.classes_ = list(np.unique(y)) - self.n_classes_ = len(self.classes_) - if self.n_classes_ > 2: - # Switch to using a multiclass objective in the underlying XGB instance - self.objective = "multi:softprob" - xgb_options = self.get_xgb_params() - xgb_options['num_class'] = self.n_classes_ - else: - xgb_options = self.get_xgb_params() - - feval = eval_metric if callable(eval_metric) else None - if eval_metric is not None: - if callable(eval_metric): - eval_metric = None - else: - xgb_options.update({"eval_metric": eval_metric}) - - if eval_set is not None: - # TODO: use sample_weight if given? - evals = list(DMatrix(x[0], label=x[1]) for x in eval_set) - nevals = len(evals) - eval_names = ["validation_{}".format(i) for i in range(nevals)] - evals = list(zip(evals, eval_names)) - else: - evals = () - - self._le = LabelEncoder().fit(y) - training_labels = self._le.transform(y) - - if sample_weight is not None: - train_dmatrix = DMatrix(X, label=training_labels, weight=sample_weight, - missing=self.missing) - else: - train_dmatrix = DMatrix(X, label=training_labels, - missing=self.missing) - - self._Booster = train(xgb_options, train_dmatrix, self.n_estimators, - evals=evals, - early_stopping_rounds=early_stopping_rounds, - evals_result=eval_results, feval=feval, - verbose_eval=verbose) - - if eval_results: - eval_results = {k: np.array(v, dtype=float) - for k, v in eval_results.items()} - self.eval_results = eval_results - - if early_stopping_rounds is not None: - self.best_score = self._Booster.best_score - self.best_iteration = self._Booster.best_iteration - - return self - - def predict(self, data): - test_dmatrix = DMatrix(data, missing=self.missing) - class_probs = self.booster().predict(test_dmatrix) - if len(class_probs.shape) > 1: - column_indexes = np.argmax(class_probs, axis=1) - else: - column_indexes = np.repeat(0, data.shape[0]) - column_indexes[class_probs > 0.5] = 1 - return self._le.inverse_transform(column_indexes) - - def predict_proba(self, data): - test_dmatrix = DMatrix(data, missing=self.missing) - class_probs = self.booster().predict(test_dmatrix) - if self.objective == "multi:softprob": - return class_probs - else: - classone_probs = class_probs - classzero_probs = 1.0 - classone_probs - return np.vstack((classzero_probs, classone_probs)).transpose() - -class XGBRegressor(XGBModel, XGBRegressorBase): - # pylint: disable=missing-docstring - __doc__ = """ - Implementation of the scikit-learn API for XGBoost regression - """ + "\n".join(XGBModel.__doc__.split('\n')[2:]) diff --git a/windows/README.md b/windows/README.md index cb1cc9dd9eb4..564c97d25337 100644 --- a/windows/README.md +++ b/windows/README.md @@ -11,7 +11,7 @@ This should give you xgboost.exe for CLI version and xgboost_wrapper.dll for pyt Use Python Module ===== -* After you build the dll, you can install the Python package from the [../wrapper](../wrapper) folder +* After you build the dll, you can install the Python package from the [../python-package](../python-package) folder ``` python setup.py install diff --git a/wrapper/README.md b/wrapper/README.md index c5368bd7d392..77316e15c117 100644 --- a/wrapper/README.md +++ b/wrapper/README.md @@ -1,20 +1,9 @@ -Wrapper of XGBoost -===== -This folder provides wrapper of xgboost to other languages - -Python -===== -* To make the python module, type ```./build.sh``` in the root directory of project -* Make sure you have [setuptools](https://pypi.python.org/pypi/setuptools) -* Install with `python setup.py install` from this directory. -* Refer also to the walk through example in [demo folder](../demo/guide-python) -* **NOTE**: if you want to run XGBoost process in parallel using the fork backend for joblib/multiprocessing, you must build XGBoost without support for OpenMP by `make no_omp=1`. Otherwise, use the forkserver (in Python 3.4) or spawn backend. See the sklearn_parallel.py demo. - - -R -===== -* See [R-package](../R-package) - -Julia -===== -* See [XGBoost.jl](https://github.com/antinucleon/XGBoost.jl) +XGBoost Wrappers +================ +This folder provides wrapper to create xgboost packages to other languages. + +***Supported Language Packages*** +* [Python package](../python-package) +* [R-package](../R-package) +* [Java Package](../java) +* [Julia Package](https://github.com/antinucleon/XGBoost.jl) diff --git a/wrapper/__init__.py b/wrapper/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/wrapper/setup.py b/wrapper/setup.py deleted file mode 100644 index 5365d61b0b0a..000000000000 --- a/wrapper/setup.py +++ /dev/null @@ -1,39 +0,0 @@ -# pylint: disable=invalid-name -"""Setup xgboost package.""" -import os -import platform -from setuptools import setup - - -class XGBoostLibraryNotFound(Exception): - """Exception to raise when xgboost library cannot be found.""" - pass - - -curr_dir = os.path.dirname(os.path.abspath(__file__)) -dll_path = [curr_dir] - -if os.name == 'nt': - if platform.architecture()[0] == '64bit': - dll_path.append(os.path.join(curr_dir, '../windows/x64/Release/')) - else: - dll_path.append(os.path.join(curr_dir, '../windows/Release/')) - - -if os.name == 'nt': - dll_path = [os.path.join(p, 'xgboost_wrapper.dll') for p in dll_path] -else: - dll_path = [os.path.join(p, 'libxgboostwrapper.so') for p in dll_path] - -lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)] - -if len(lib_path) == 0: - raise XGBoostLibraryNotFound("XGBoost library not found. Did you run " - "../make?") -setup(name="xgboost", - version="0.40", - description="Python wrappers for XGBoost: eXtreme Gradient Boosting", - zip_safe=False, - py_modules=['xgboost'], - data_files=[('.', [lib_path[0]])], - url="https://github.com/dmlc/xgboost") From 60217a2c02dc80376f1bc6084b10d003b2aeef1f Mon Sep 17 00:00:00 2001 From: tqchen Date: Thu, 30 Jul 2015 22:08:48 -0700 Subject: [PATCH 087/126] checkin all python --- .gitignore | 7 +- python-package/xgboost/__init__.py | 12 + python-package/xgboost/sklearn.py | 341 +++++++++++++++++++++++++++++ python-package/xgboost/training.py | 252 +++++++++++++++++++++ 4 files changed, 608 insertions(+), 4 deletions(-) create mode 100644 python-package/xgboost/__init__.py create mode 100644 python-package/xgboost/sklearn.py create mode 100644 python-package/xgboost/training.py diff --git a/.gitignore b/.gitignore index 73ae6748eff1..048803abd7de 100644 --- a/.gitignore +++ b/.gitignore @@ -48,10 +48,9 @@ Debug *.cpage.col *.cpage *.Rproj -xgboost -xgboost.mpi -xgboost.mock -train* +./xgboost +./xgboost.mpi +./xgboost.mock rabit #.Rbuildignore R-package.Rproj diff --git a/python-package/xgboost/__init__.py b/python-package/xgboost/__init__.py new file mode 100644 index 000000000000..6f967b8378dc --- /dev/null +++ b/python-package/xgboost/__init__.py @@ -0,0 +1,12 @@ +# coding: utf-8 +"""XGBoost: eXtreme Gradient Boosting library. + +Contributors: https://github.com/dmlc/xgboost/blob/master/CONTRIBUTORS.md +""" + +from __future__ import absolute_import +from .core import DMatrix, Booster +from .training import train, cv +from .sklearn import XGBModel, XGBClassifier, XGBRegressor + +__version__ = '0.4' diff --git a/python-package/xgboost/sklearn.py b/python-package/xgboost/sklearn.py new file mode 100644 index 000000000000..4a5771724b03 --- /dev/null +++ b/python-package/xgboost/sklearn.py @@ -0,0 +1,341 @@ +# coding: utf-8 +# pylint: disable=too-many-arguments, too-many-locals, invalid-name, fixme +"""Scikit-Learn Wrapper interface for XGBoost.""" +from __future__ import absolute_import + +import numpy as np +from .core import Booster, DMatrix, XGBoostError +from .training import train + +try: + from sklearn.base import BaseEstimator + from sklearn.base import RegressorMixin, ClassifierMixin + from sklearn.preprocessing import LabelEncoder + SKLEARN_INSTALLED = True +except ImportError: + SKLEARN_INSTALLED = False + +# used for compatiblity without sklearn +XGBModelBase = object +XGBClassifierBase = object +XGBRegressorBase = object + +if SKLEARN_INSTALLED: + XGBModelBase = BaseEstimator + XGBRegressorBase = RegressorMixin + XGBClassifierBase = ClassifierMixin + +class XGBModel(XGBModelBase): + # pylint: disable=too-many-arguments, too-many-instance-attributes, invalid-name + """Implementation of the Scikit-Learn API for XGBoost. + + Parameters + ---------- + max_depth : int + Maximum tree depth for base learners. + learning_rate : float + Boosting learning rate (xgb's "eta") + n_estimators : int + Number of boosted trees to fit. + silent : boolean + Whether to print messages while running boosting. + objective : string + Specify the learning task and the corresponding learning objective. + + nthread : int + Number of parallel threads used to run xgboost. + gamma : float + Minimum loss reduction required to make a further partition on a leaf node of the tree. + min_child_weight : int + Minimum sum of instance weight(hessian) needed in a child. + max_delta_step : int + Maximum delta step we allow each tree's weight estimation to be. + subsample : float + Subsample ratio of the training instance. + colsample_bytree : float + Subsample ratio of columns when constructing each tree. + + base_score: + The initial prediction score of all instances, global bias. + seed : int + Random number seed. + missing : float, optional + Value in the data which needs to be present as a missing value. If + None, defaults to np.nan. + """ + def __init__(self, max_depth=3, learning_rate=0.1, n_estimators=100, + silent=True, objective="reg:linear", + nthread=-1, gamma=0, min_child_weight=1, max_delta_step=0, + subsample=1, colsample_bytree=1, + base_score=0.5, seed=0, missing=None): + if not SKLEARN_INSTALLED: + raise XGBoostError('sklearn needs to be installed in order to use this module') + self.max_depth = max_depth + self.learning_rate = learning_rate + self.n_estimators = n_estimators + self.silent = silent + self.objective = objective + + self.nthread = nthread + self.gamma = gamma + self.min_child_weight = min_child_weight + self.max_delta_step = max_delta_step + self.subsample = subsample + self.colsample_bytree = colsample_bytree + + self.base_score = base_score + self.seed = seed + self.missing = missing if missing is not None else np.nan + self._Booster = None + + def __setstate__(self, state): + # backward compatiblity code + # load booster from raw if it is raw + # the booster now support pickle + bst = state["_Booster"] + if bst is not None and not isinstance(bst, Booster): + state["_Booster"] = Booster(model_file=bst) + self.__dict__.update(state) + + def booster(self): + """Get the underlying xgboost Booster of this model. + + This will raise an exception when fit was not called + + Returns + ------- + booster : a xgboost booster of underlying model + """ + if self._Booster is None: + raise XGBoostError('need to call fit beforehand') + return self._Booster + + def get_params(self, deep=False): + """Get parameter.s""" + params = super(XGBModel, self).get_params(deep=deep) + if params['missing'] is np.nan: + params['missing'] = None # sklearn doesn't handle nan. see #4725 + if not params.get('eval_metric', True): + del params['eval_metric'] # don't give as None param to Booster + return params + + def get_xgb_params(self): + """Get xgboost type parameters.""" + xgb_params = self.get_params() + + xgb_params['silent'] = 1 if self.silent else 0 + + if self.nthread <= 0: + xgb_params.pop('nthread', None) + return xgb_params + + def fit(self, X, y, eval_set=None, eval_metric=None, + early_stopping_rounds=None, verbose=True): + # pylint: disable=missing-docstring,invalid-name,attribute-defined-outside-init + """ + Fit the gradient boosting model + + Parameters + ---------- + X : array_like + Feature matrix + y : array_like + Labels + eval_set : list, optional + A list of (X, y) tuple pairs to use as a validation set for + early-stopping + eval_metric : str, callable, optional + If a str, should be a built-in evaluation metric to use. See + doc/parameter.md. If callable, a custom evaluation metric. The call + signature is func(y_predicted, y_true) where y_true will be a + DMatrix object such that you may need to call the get_label + method. It must return a str, value pair where the str is a name + for the evaluation and value is the value of the evaluation + function. This objective is always minimized. + early_stopping_rounds : int + Activates early stopping. Validation error needs to decrease at + least every round(s) to continue training. + Requires at least one item in evals. If there's more than one, + will use the last. Returns the model from the last iteration + (not the best one). If early stopping occurs, the model will + have two additional fields: bst.best_score and bst.best_iteration. + verbose : bool + If `verbose` and an evaluation set is used, writes the evaluation + metric measured on the validation set to stderr. + """ + trainDmatrix = DMatrix(X, label=y, missing=self.missing) + + eval_results = {} + if eval_set is not None: + evals = list(DMatrix(x[0], label=x[1]) for x in eval_set) + evals = list(zip(evals, ["validation_{}".format(i) for i in + range(len(evals))])) + else: + evals = () + + params = self.get_xgb_params() + + feval = eval_metric if callable(eval_metric) else None + if eval_metric is not None: + if callable(eval_metric): + eval_metric = None + else: + params.update({'eval_metric': eval_metric}) + + self._Booster = train(params, trainDmatrix, + self.n_estimators, evals=evals, + early_stopping_rounds=early_stopping_rounds, + evals_result=eval_results, feval=feval, + verbose_eval=verbose) + if eval_results: + eval_results = {k: np.array(v, dtype=float) + for k, v in eval_results.items()} + eval_results = {k: np.array(v) for k, v in eval_results.items()} + self.eval_results = eval_results + + if early_stopping_rounds is not None: + self.best_score = self._Booster.best_score + self.best_iteration = self._Booster.best_iteration + return self + + def predict(self, data): + # pylint: disable=missing-docstring,invalid-name + test_dmatrix = DMatrix(data, missing=self.missing) + return self.booster().predict(test_dmatrix) + + +class XGBClassifier(XGBModel, XGBClassifierBase): + # pylint: disable=missing-docstring,too-many-arguments,invalid-name + __doc__ = """ + Implementation of the scikit-learn API for XGBoost classification + """ + "\n".join(XGBModel.__doc__.split('\n')[2:]) + + def __init__(self, max_depth=3, learning_rate=0.1, + n_estimators=100, silent=True, + objective="binary:logistic", + nthread=-1, gamma=0, min_child_weight=1, + max_delta_step=0, subsample=1, colsample_bytree=1, + base_score=0.5, seed=0, missing=None): + super(XGBClassifier, self).__init__(max_depth, learning_rate, + n_estimators, silent, objective, + nthread, gamma, min_child_weight, + max_delta_step, subsample, + colsample_bytree, + base_score, seed, missing) + + def fit(self, X, y, sample_weight=None, eval_set=None, eval_metric=None, + early_stopping_rounds=None, verbose=True): + # pylint: disable = attribute-defined-outside-init,arguments-differ + """ + Fit gradient boosting classifier + + Parameters + ---------- + X : array_like + Feature matrix + y : array_like + Labels + sample_weight : array_like + Weight for each instance + eval_set : list, optional + A list of (X, y) pairs to use as a validation set for + early-stopping + eval_metric : str, callable, optional + If a str, should be a built-in evaluation metric to use. See + doc/parameter.md. If callable, a custom evaluation metric. The call + signature is func(y_predicted, y_true) where y_true will be a + DMatrix object such that you may need to call the get_label + method. It must return a str, value pair where the str is a name + for the evaluation and value is the value of the evaluation + function. This objective is always minimized. + early_stopping_rounds : int, optional + Activates early stopping. Validation error needs to decrease at + least every round(s) to continue training. + Requires at least one item in evals. If there's more than one, + will use the last. Returns the model from the last iteration + (not the best one). If early stopping occurs, the model will + have two additional fields: bst.best_score and bst.best_iteration. + verbose : bool + If `verbose` and an evaluation set is used, writes the evaluation + metric measured on the validation set to stderr. + """ + eval_results = {} + self.classes_ = list(np.unique(y)) + self.n_classes_ = len(self.classes_) + if self.n_classes_ > 2: + # Switch to using a multiclass objective in the underlying XGB instance + self.objective = "multi:softprob" + xgb_options = self.get_xgb_params() + xgb_options['num_class'] = self.n_classes_ + else: + xgb_options = self.get_xgb_params() + + feval = eval_metric if callable(eval_metric) else None + if eval_metric is not None: + if callable(eval_metric): + eval_metric = None + else: + xgb_options.update({"eval_metric": eval_metric}) + + if eval_set is not None: + # TODO: use sample_weight if given? + evals = list(DMatrix(x[0], label=x[1]) for x in eval_set) + nevals = len(evals) + eval_names = ["validation_{}".format(i) for i in range(nevals)] + evals = list(zip(evals, eval_names)) + else: + evals = () + + self._le = LabelEncoder().fit(y) + training_labels = self._le.transform(y) + + if sample_weight is not None: + train_dmatrix = DMatrix(X, label=training_labels, weight=sample_weight, + missing=self.missing) + else: + train_dmatrix = DMatrix(X, label=training_labels, + missing=self.missing) + + self._Booster = train(xgb_options, train_dmatrix, self.n_estimators, + evals=evals, + early_stopping_rounds=early_stopping_rounds, + evals_result=eval_results, feval=feval, + verbose_eval=verbose) + + if eval_results: + eval_results = {k: np.array(v, dtype=float) + for k, v in eval_results.items()} + self.eval_results = eval_results + + if early_stopping_rounds is not None: + self.best_score = self._Booster.best_score + self.best_iteration = self._Booster.best_iteration + + return self + + def predict(self, data): + test_dmatrix = DMatrix(data, missing=self.missing) + class_probs = self.booster().predict(test_dmatrix) + if len(class_probs.shape) > 1: + column_indexes = np.argmax(class_probs, axis=1) + else: + column_indexes = np.repeat(0, data.shape[0]) + column_indexes[class_probs > 0.5] = 1 + return self._le.inverse_transform(column_indexes) + + def predict_proba(self, data): + test_dmatrix = DMatrix(data, missing=self.missing) + class_probs = self.booster().predict(test_dmatrix) + if self.objective == "multi:softprob": + return class_probs + else: + classone_probs = class_probs + classzero_probs = 1.0 - classone_probs + return np.vstack((classzero_probs, classone_probs)).transpose() + +class XGBRegressor(XGBModel, XGBRegressorBase): + # pylint: disable=missing-docstring + __doc__ = """ + Implementation of the scikit-learn API for XGBoost regression + """ + "\n".join(XGBModel.__doc__.split('\n')[2:]) + diff --git a/python-package/xgboost/training.py b/python-package/xgboost/training.py new file mode 100644 index 000000000000..1f2d722aca0f --- /dev/null +++ b/python-package/xgboost/training.py @@ -0,0 +1,252 @@ +# coding: utf-8 +# pylint: disable=too-many-locals, too-many-arguments, invalid-name +"""Training Library containing training routines.""" +from __future__ import absolute_import + +import sys +import re +import numpy as np +from .core import Booster, STRING_TYPES + +def train(params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, + early_stopping_rounds=None, evals_result=None, verbose_eval=True): + # pylint: disable=too-many-statements,too-many-branches, attribute-defined-outside-init + """Train a booster with given parameters. + + Parameters + ---------- + params : dict + Booster params. + dtrain : DMatrix + Data to be trained. + num_boost_round: int + Number of boosting iterations. + watchlist (evals): list of pairs (DMatrix, string) + List of items to be evaluated during training, this allows user to watch + performance on the validation set. + obj : function + Customized objective function. + feval : function + Customized evaluation function. + early_stopping_rounds: int + Activates early stopping. Validation error needs to decrease at least + every round(s) to continue training. + Requires at least one item in evals. + If there's more than one, will use the last. + Returns the model from the last iteration (not the best one). + If early stopping occurs, the model will have two additional fields: + bst.best_score and bst.best_iteration. + evals_result: dict + This dictionary stores the evaluation results of all the items in watchlist + verbose_eval : bool + If `verbose_eval` then the evaluation metric on the validation set, if + given, is printed at each boosting stage. + + Returns + ------- + booster : a trained booster model + """ + evals = list(evals) + bst = Booster(params, [dtrain] + [d[0] for d in evals]) + + if evals_result is not None: + if not isinstance(evals_result, dict): + raise TypeError('evals_result has to be a dictionary') + else: + evals_name = [d[1] for d in evals] + evals_result.clear() + evals_result.update({key: [] for key in evals_name}) + + if not early_stopping_rounds: + for i in range(num_boost_round): + bst.update(dtrain, i, obj) + if len(evals) != 0: + bst_eval_set = bst.eval_set(evals, i, feval) + if isinstance(bst_eval_set, STRING_TYPES): + msg = bst_eval_set + else: + msg = bst_eval_set.decode() + + if verbose_eval: + sys.stderr.write(msg + '\n') + if evals_result is not None: + res = re.findall(":-?([0-9.]+).", msg) + for key, val in zip(evals_name, res): + evals_result[key].append(val) + return bst + + else: + # early stopping + if len(evals) < 1: + raise ValueError('For early stopping you need at least one set in evals.') + + sys.stderr.write("Will train until {} error hasn't decreased in {} rounds.\n".format(\ + evals[-1][1], early_stopping_rounds)) + + # is params a list of tuples? are we using multiple eval metrics? + if isinstance(params, list): + if len(params) != len(dict(params).items()): + raise ValueError('Check your params.'\ + 'Early stopping works with single eval metric only.') + params = dict(params) + + # either minimize loss or maximize AUC/MAP/NDCG + maximize_score = False + if 'eval_metric' in params: + maximize_metrics = ('auc', 'map', 'ndcg') + if any(params['eval_metric'].startswith(x) for x in maximize_metrics): + maximize_score = True + + if maximize_score: + best_score = 0.0 + else: + best_score = float('inf') + + best_msg = '' + best_score_i = 0 + + for i in range(num_boost_round): + bst.update(dtrain, i, obj) + bst_eval_set = bst.eval_set(evals, i, feval) + + if isinstance(bst_eval_set, STRING_TYPES): + msg = bst_eval_set + else: + msg = bst_eval_set.decode() + + if verbose_eval: + sys.stderr.write(msg + '\n') + + if evals_result is not None: + res = re.findall(":-([0-9.]+).", msg) + for key, val in zip(evals_name, res): + evals_result[key].append(val) + + score = float(msg.rsplit(':', 1)[1]) + if (maximize_score and score > best_score) or \ + (not maximize_score and score < best_score): + best_score = score + best_score_i = i + best_msg = msg + elif i - best_score_i >= early_stopping_rounds: + sys.stderr.write("Stopping. Best iteration:\n{}\n\n".format(best_msg)) + bst.best_score = best_score + bst.best_iteration = best_score_i + break + bst.best_score = best_score + bst.best_iteration = best_score_i + return bst + + +class CVPack(object): + """"Auxiliary datastruct to hold one fold of CV.""" + def __init__(self, dtrain, dtest, param): + """"Initialize the CVPack""" + self.dtrain = dtrain + self.dtest = dtest + self.watchlist = [(dtrain, 'train'), (dtest, 'test')] + self.bst = Booster(param, [dtrain, dtest]) + + def update(self, iteration, fobj): + """"Update the boosters for one iteration""" + self.bst.update(self.dtrain, iteration, fobj) + + def eval(self, iteration, feval): + """"Evaluate the CVPack for one iteration.""" + return self.bst.eval_set(self.watchlist, iteration, feval) + + +def mknfold(dall, nfold, param, seed, evals=(), fpreproc=None): + """ + Make an n-fold list of CVPack from random indices. + """ + evals = list(evals) + np.random.seed(seed) + randidx = np.random.permutation(dall.num_row()) + kstep = len(randidx) / nfold + idset = [randidx[(i * kstep): min(len(randidx), (i + 1) * kstep)] for i in range(nfold)] + ret = [] + for k in range(nfold): + dtrain = dall.slice(np.concatenate([idset[i] for i in range(nfold) if k != i])) + dtest = dall.slice(idset[k]) + # run preprocessing on the data set if needed + if fpreproc is not None: + dtrain, dtest, tparam = fpreproc(dtrain, dtest, param.copy()) + else: + tparam = param + plst = list(tparam.items()) + [('eval_metric', itm) for itm in evals] + ret.append(CVPack(dtrain, dtest, plst)) + return ret + + +def aggcv(rlist, show_stdv=True): + # pylint: disable=invalid-name + """ + Aggregate cross-validation results. + """ + cvmap = {} + ret = rlist[0].split()[0] + for line in rlist: + arr = line.split() + assert ret == arr[0] + for it in arr[1:]: + if not isinstance(it, STRING_TYPES): + it = it.decode() + k, v = it.split(':') + if k not in cvmap: + cvmap[k] = [] + cvmap[k].append(float(v)) + for k, v in sorted(cvmap.items(), key=lambda x: x[0]): + v = np.array(v) + if not isinstance(ret, STRING_TYPES): + ret = ret.decode() + if show_stdv: + ret += '\tcv-%s:%f+%f' % (k, np.mean(v), np.std(v)) + else: + ret += '\tcv-%s:%f' % (k, np.mean(v)) + return ret + + +def cv(params, dtrain, num_boost_round=10, nfold=3, metrics=(), + obj=None, feval=None, fpreproc=None, show_stdv=True, seed=0): + # pylint: disable = invalid-name + """Cross-validation with given paramaters. + + Parameters + ---------- + params : dict + Booster params. + dtrain : DMatrix + Data to be trained. + num_boost_round : int + Number of boosting iterations. + nfold : int + Number of folds in CV. + metrics : list of strings + Evaluation metrics to be watched in CV. + obj : function + Custom objective function. + feval : function + Custom evaluation function. + fpreproc : function + Preprocessing function that takes (dtrain, dtest, param) and returns + transformed versions of those. + show_stdv : bool + Whether to display the standard deviation. + seed : int + Seed used to generate the folds (passed to numpy.random.seed). + + Returns + ------- + evaluation history : list(string) + """ + results = [] + cvfolds = mknfold(dtrain, nfold, params, seed, metrics, fpreproc) + for i in range(num_boost_round): + for fold in cvfolds: + fold.update(i, obj) + res = aggcv([f.eval(i, feval) for f in cvfolds], show_stdv) + sys.stderr.write(res + '\n') + results.append(res) + return results + From 362fe4e4fa2981191e6797be5dcfd6a22be956cd Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Thu, 30 Jul 2015 22:11:27 -0700 Subject: [PATCH 088/126] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6f2cba90ffa6..10f2170386e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ before_install: - scripts/travis_osx_install.sh - git clone https://github.com/dmlc/dmlc-core - export TRAVIS=dmlc-core/scripts/travis/ - - export PYTHONPATH=${PYTHONPATH}:${PWD}/wrapper + - export PYTHONPATH=${PYTHONPATH}:${PWD}/python-package - source ${TRAVIS}/travis_setup_env.sh install: From 2a01c5c86527070f8f235823166ef5faf2ce0cc3 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Thu, 30 Jul 2015 22:26:10 -0700 Subject: [PATCH 089/126] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5ed3d266c8e2..71f48a1666df 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -19,13 +19,19 @@ List of Contributors * [Full List of Contributors](https://github.com/dmlc/xgboost/graphs/contributors) - To contributors: please add your name to the list when you submit a patch to the project:) * [Kailong Chen](https://github.com/kalenhaha) + - Kailong is an early contributor of xgboost, he is creator of ranking objectives in xgboost. * [Skipper Seabold](https://github.com/jseabold) + - Skipper is the major contributor to the scikit-learn module of xgboost. * [Zygmunt Zając](https://github.com/zygmuntz) + - Zygmunt is the master behind the early stopping feature frequently used by kagglers. * [Ajinkya Kale](https://github.com/ajkl) * [Boliang Chen](https://github.com/cblsjtu) * [Vadim Khotilovich](https://github.com/khotilov) * [Yangqing Men](https://github.com/yanqingmen) + - Yangqing is the creator of xgboost java package. * [Engpeng Yao](https://github.com/yepyao) * [Giulio](https://github.com/giuliohome) + - Giulio is the creator of windows project of xgboost * [Jamie Hall](https://github.com/nerdcha) + - Jamie is the initial creator of xgboost sklearn modue. * [Yen-Ying Lee](https://github.com/white1033) From 3a091fa30265e51f4e1b6e72960b78bbe7d5e597 Mon Sep 17 00:00:00 2001 From: hetong007 Date: Fri, 31 Jul 2015 21:33:54 +0000 Subject: [PATCH 090/126] modify desc --- R-package/DESCRIPTION | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 6f784fbb309f..4560971e2589 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -1,16 +1,16 @@ Package: xgboost Type: Package -Title: eXtreme Gradient Boosting -Version: 0.4-0 +Title: Extreme Gradient Boosting +Version: 0.4-1 Date: 2015-05-11 Author: Tianqi Chen , Tong He , Michael Benesty Maintainer: Tong He -Description: eXtreme Gradient Boosting, which is an - efficient and scalable implementation of gradient boosting framework. - This package is an R wrapper of xgboost. The package includes efficient +Description: Extreme Gradient Boosting, which is an + efficient implementation of gradient boosting framework. + This package is its R interface. The package includes efficient linear model solver and tree learning algorithms. The package can automatically - do parallel computation with OpenMP, and it can be more than 10 times faster - than existing gradient boosting packages such as gbm. It supports various + do parallel computation on a single machine which could be more than 10 times faster + than existing gradient boosting packages. It supports various objective functions, including regression, classification and ranking. The package is made to be extensible, so that users are also allowed to define their own objectives easily. From 8083c30e7b7dadf4e19387d18573abe3b70ffe12 Mon Sep 17 00:00:00 2001 From: tqchen Date: Sat, 1 Aug 2015 09:18:34 -0700 Subject: [PATCH 091/126] quick fix of solaris problem in cranc check --- CHANGES.md | 18 ++++++++++-------- R-package/README.md | 4 ++-- R-package/src/xgboost_R.cpp | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0be001744862..d9c8786c0c2c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,18 +1,18 @@ Change Log -===== +========== xgboost-0.1 -===== +=========== * Initial release xgboost-0.2x -===== +============ * Python module * Weighted samples instances * Initial version of pairwise rank xgboost-0.3 -===== +=========== * Faster tree construction module - Allows subsample columns during tree construction via ```bst:col_samplebytree=ratio``` * Support for boosting from initial predictions @@ -22,7 +22,7 @@ xgboost-0.3 * Add R module xgboost-0.4 -===== +=========== * Distributed version of xgboost that runs on YARN, scales to billions of examples * Direct save/load data and model from/to S3 and HDFS * Feature importance visualization in R module, by Michael Benesty @@ -35,9 +35,11 @@ xgboost-0.4 * sklearn wrapper is supported in python module * Experimental External memory version -on going version -===== +on going at master +================== +* Fix List + - Fixed possible problem of poisson regression for R. * Python module now throw exception instead of crash terminal when a parameter error happens. * Java api is ready for use * Added more test cases and continuous integration to make each build more robust -* Improvements in sklearn compatible module +* Improvements in sklearn compatible module \ No newline at end of file diff --git a/R-package/README.md b/R-package/README.md index 81dabb31c4d8..96113c39162c 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -24,10 +24,10 @@ If you face an issue installing the package using ```devtools::install_github`` ``` devtools::install_github('dmlc/xgboost',subdir='R-package') Downloading github repo dmlc/xgboost@master -Error in function (type, msg, asError = TRUE) : +Error in function (type, msg, asError = TRUE) : Peer certificate cannot be authenticated with given CA certificates ``` -To get around this you can build the package locally as mentioned [here](https://github.com/dmlc/xgboost/issues/347) - +To get around this you can build the package locally as mentioned [here](https://github.com/dmlc/xgboost/issues/347) - ``` 1. Clone the current repository and set your workspace to xgboost/R-package/ 2. Run R CMD INSTALL --build . in terminal to get the tarball. diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index a8084b20675a..37a30c797a43 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -32,7 +32,7 @@ extern "C" { bool CheckNAN(double v) { return ISNAN(v); } -bool LogGamma(double v) { +double LogGamma(double v) { return lgammafn(v); } } // namespace utils From c43fee541da8a15c3871c1963a2927ab4dca3e07 Mon Sep 17 00:00:00 2001 From: tqchen Date: Sat, 1 Aug 2015 11:27:13 -0700 Subject: [PATCH 092/126] enable basic sphinx doc --- README.md | 13 +- doc/.gitignore | 7 + doc/Makefile | 192 ++++++++++++++++++++++ doc/conf.py | 158 ++++++++++++++++++ doc/external_memory.md | 10 +- doc/{README.md => index.md} | 26 ++- doc/input_format.md | 9 +- doc/parameter.md | 2 +- doc/python/python_api.rst | 36 ++++ doc/{python.md => python/python_intro.md} | 3 +- doc/sphinx_util.py | 50 ++++++ python-package/xgboost/__init__.py | 4 + python-package/xgboost/core.py | 63 ++++--- python-package/xgboost/sklearn.py | 12 +- 14 files changed, 529 insertions(+), 56 deletions(-) create mode 100644 doc/.gitignore create mode 100644 doc/Makefile create mode 100644 doc/conf.py rename doc/{README.md => index.md} (87%) create mode 100644 doc/python/python_api.rst rename doc/{python.md => python/python_intro.md} (98%) create mode 100644 doc/sphinx_util.py diff --git a/README.md b/README.md index 0f6ffc7faa3b..be93e99fda76 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ DMLC/XGBoost [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. +An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data @@ -13,7 +13,7 @@ Contents -------- * [What's New](#whats-new) * [Version](#version) -* [Documentation](doc/README.md) +* [Documentation](doc/index.md) * [Build Instruction](doc/build.md) * [Features](#features) * [Distributed XGBoost](multi-node) @@ -43,15 +43,14 @@ Version Features -------- - -* Easily accessible through CLI, [python](https://github.com/dmlc/xgboost/blob/master/demo/guide-python/basic_walkthrough.py), - [R](https://github.com/dmlc/xgboost/blob/master/R-package/demo/basic_walkthrough.R), +* Easily accessible through CLI, [python](https://github.com/dmlc/xgboost/blob/master/demo/guide-python/basic_walkthrough.py), + [R](https://github.com/dmlc/xgboost/blob/master/R-package/demo/basic_walkthrough.R), [Julia](https://github.com/antinucleon/XGBoost.jl/blob/master/demo/basic_walkthrough.jl) * Its fast! Benchmark numbers comparing xgboost, H20, Spark, R - [benchm-ml numbers](https://github.com/szilard/benchm-ml) * Memory efficient - Handles sparse matrices, supports external memory * Accurate prediction, and used extensively by data scientists and kagglers - [highlight links](https://github.com/dmlc/xgboost/blob/master/doc/README.md#highlight-links) * Distributed version runs on Hadoop (YARN), MPI, SGE etc., scales to billions of examples. - + Bug Reporting ------------- @@ -74,4 +73,4 @@ License XGBoost in Graphlab Create -------------------------- * XGBoost is adopted as part of boosted tree toolkit in Graphlab Create (GLC). Graphlab Create is a powerful python toolkit that allows you to do data manipulation, graph processing, hyper-parameter search, and visualization of TeraBytes scale data in one framework. Try the [Graphlab Create](http://graphlab.com/products/create/quick-start-guide.html) -* Nice [blogpost](http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand) by Jay Gu about using GLC boosted tree to solve kaggle bike sharing challenge: +* Nice [blogpost](http://blog.graphlab.com/using-gradient-boosted-trees-to-predict-bike-sharing-demand) by Jay Gu about using GLC boosted tree to solve kaggle bike sharing challenge: diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 000000000000..382c3419ff43 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,7 @@ +html +latex +*.sh +_* +doxygen +parser.py +*.pyc diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 000000000000..40bba2a280db --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rabit.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rabit.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/rabit" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rabit" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 000000000000..b08f495f58ae --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# +# documentation build configuration file, created by +# sphinx-quickstart on Thu Jul 23 19:40:08 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. +import sys +import os, subprocess +import shlex +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +libpath = os.path.join(curr_path, '../python-package/') +sys.path.insert(0, libpath) +sys.path.insert(0, curr_path) + +from sphinx_util import MarkdownParser + +# -- General configuration ------------------------------------------------ + +# General information about the project. +project = u'xgboost' +author = u'%s developers' % project +copyright = u'2015, %s' % author +github_doc_root = 'https://github.com/dmlc/xgboost/tree/master/doc/' + +# add markdown parser +MarkdownParser.github_doc_root = github_doc_root +source_parsers = { + '.md': MarkdownParser, +} +os.environ['XGBOOST_BUILD_DOC'] = '1' +# Version information. +import xgboost +version = xgboost.__version__ +release = xgboost.__version__ + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.mathjax', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = ['.rst', '.md'] + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = project + 'doc' + +# -- Options for LaTeX output --------------------------------------------- +latex_elements = { +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, '%s.tex' % project, project, + author, 'manual'), +] + +# hook for doxygen +def run_doxygen(folder): + """Run the doxygen make command in the designated folder.""" + try: + retcode = subprocess.call("cd %s; make doxygen" % folder, shell=True) + if retcode < 0: + sys.stderr.write("doxygen terminated by signal %s" % (-retcode)) + except OSError as e: + sys.stderr.write("doxygen execution failed: %s" % e) + +def generate_doxygen_xml(app): + """Run the doxygen make commands if we're on the ReadTheDocs server""" + read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' + if read_the_docs_build: + run_doxygen('..') + +def setup(app): + # Add hook for building doxygen xml when needed + # no c++ API for now + # app.connect("builder-inited", generate_doxygen_xml) + pass diff --git a/doc/external_memory.md b/doc/external_memory.md index f8eec83fc8d1..e50c02e570fb 100644 --- a/doc/external_memory.md +++ b/doc/external_memory.md @@ -1,5 +1,5 @@ Using XGBoost External Memory Version(beta) -==== +=========================================== There is no big difference between using external memory version and in-memory version. The only difference is the filename format. @@ -19,13 +19,13 @@ You can find that there is additional ```#dtrain.cache``` following the libsvm f For CLI version, simply use ```"../data/agaricus.txt.train#dtrain.cache"``` in filename. Performance Note -==== +---------------- * the parameter ```nthread``` should be set to number of ***real*** cores - Most modern CPU offer hyperthreading, which means you can have a 4 core cpu with 8 threads - Set nthread to be 4 for maximum performance in such case Distributed Version -==== +------------------- The external memory mode naturally works on distributed version, you can simply set path like ``` data = "hdfs:///path-to-data/#dtrain.cache" @@ -34,8 +34,8 @@ xgboost will cache the data to the local position. When you run on YARN, the cur so that you can directly use ```dtrain.cache``` to cache to current folder. -Usage Note: -==== +Usage Note +---------- * This is a experimental version - If you like to try and test it, report results to https://github.com/dmlc/xgboost/issues/244 * Currently only importing from libsvm format is supported diff --git a/doc/README.md b/doc/index.md similarity index 87% rename from doc/README.md rename to doc/index.md index e8df7d57de12..5d8d5b26f647 100644 --- a/doc/README.md +++ b/doc/index.md @@ -1,6 +1,9 @@ -List of Documentations -==== -* [Using XGBoost in Python](python.md) +XGBoost Documentation +===================== + + + +* [Using XGBoost in Python](python/python_intro.md) * [Using XGBoost in R](../R-package/vignettes/xgboostPresentation.Rmd) * [Learning to use xgboost by example](../demo) * [External Memory Version](external_memory.md) @@ -11,24 +14,29 @@ List of Documentations - [Notes on Parameter Tunning](param_tuning.md) * Learning about the model: [Introduction to Boosted Trees](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) -How to get started -==== + +How to Get Started +------------------ * Try to read the [binary classification example](../demo/binary_classification) for getting started example * Find the guide specific language guide above for the language you like to use * [Learning to use xgboost by example](../demo) contains lots of useful examples -Highlight Links -==== +Example Highlight Links +----------------------- This section is about blogposts, presentation and videos discussing how to use xgboost to solve your interesting problem. If you think something belongs to here, send a pull request. * [Kaggle CrowdFlower winner's solution by Chenglong Chen](https://github.com/ChenglongChen/Kaggle_CrowdFlower) * [Kaggle Malware Prediction winner's solution](https://github.com/xiaozhouwang/kaggle_Microsoft_Malware) * [Kaggle Tradeshift winning solution by daxiongshu](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) * [Feature Importance Analysis with XGBoost in Tax audit](http://fr.slideshare.net/MichaelBENESTY/feature-importance-analysis-with-xgboost-in-tax-audit) * Video tutorial: [Better Optimization with Repeated Cross Validation and the XGBoost model](https://www.youtube.com/watch?v=Og7CGAfSr_Y) -* [Winning solution of Kaggle Higgs competition: what a single model can do](http://no2147483647.wordpress.com/2014/09/17/winning-solution-of-kaggle-higgs-competition-what-a-single-model-can-do/) +* [Winning solution of Kaggle Higgs competition: what a single model can do](http://no2147483647.wordpress.com/2014/09/17/winning-solution-of-kaggle-higgs-competition-what-a-single-model-can-do/) + +API Reference +------------- + * [Python API Reference](python/python_api.rst) Contribution -==== +------------ Contribution of documents and use-cases are welcomed! * This package use Google C++ style * Check tool of codestyle diff --git a/doc/input_format.md b/doc/input_format.md index 557b875121f0..3986d07fb182 100644 --- a/doc/input_format.md +++ b/doc/input_format.md @@ -1,12 +1,13 @@ -Input Format -==== +Text Input Format of DMatrix +============================ + ## Basic Input Format As we have mentioned, XGBoost takes LibSVM format. For training or predicting, XGBoost takes an instance file with the format as below: train.txt ``` 1 101:1.2 102:0.03 -0 1:2.1 10001:300 10002:400 +0 1:2.1 10001:300 10002:400 0 0:1.3 1:0.3 1 0:0.01 1:0.3 0 0:0.2 1:0.3 @@ -37,7 +38,7 @@ train.txt.weight 0.5 ``` It means that XGBoost will emphasize more on the first and fourth instance, that is to say positive instances while training. -The configuration is similar to configuring the group information. If the instance file name is "xxx", XGBoost will check whether there is a file named "xxx.weight" in the same directory and if there is, will use the weights while training models. Weights will be included into an "xxx.buffer" file that is created by XGBoost automatically. If you want to update the weights, you need to delete the "xxx.buffer" file prior to launching XGBoost. +The configuration is similar to configuring the group information. If the instance file name is "xxx", XGBoost will check whether there is a file named "xxx.weight" in the same directory and if there is, will use the weights while training models. Weights will be included into an "xxx.buffer" file that is created by XGBoost automatically. If you want to update the weights, you need to delete the "xxx.buffer" file prior to launching XGBoost. ## Initial Margin file XGBoost supports providing each instance an initial margin prediction. For example, if we have a initial prediction using logistic regression for "train.txt" file, we can create the following file: diff --git a/doc/parameter.md b/doc/parameter.md index 13eefa0fec6a..53cdd806f2b9 100644 --- a/doc/parameter.md +++ b/doc/parameter.md @@ -1,5 +1,5 @@ XGBoost Parameters -==== +================== Before running XGboost, we must set three types of parameters, general parameters, booster parameters and task parameters: - General parameters relates to which booster we are using to do boosting, commonly tree or linear model - Booster parameters depends on which booster you have chosen diff --git a/doc/python/python_api.rst b/doc/python/python_api.rst new file mode 100644 index 000000000000..e665efe84a2d --- /dev/null +++ b/doc/python/python_api.rst @@ -0,0 +1,36 @@ +Python API Reference +==================== +This page gives the Python API reference of xgboost. + +Core Data Structure +------------------- +.. automodule:: xgboost.core + +.. autoclass:: xgboost.DMatrix + :members: + :show-inheritance: + +.. autoclass:: xgboost.Booster + :members: + :show-inheritance: + + +Learning API +------------ +.. automodule:: xgboost.training + +.. autofunction:: xgboost.train + +.. autofunction:: xgboost.cv + + +Scikit-Learn API +---------------- +.. automodule:: xgboost.sklearn +.. autoclass:: xgboost.XGBRegressor + :members: + :show-inheritance: +.. autoclass:: xgboost.XGBClassifier + :members: + :show-inheritance: + diff --git a/doc/python.md b/doc/python/python_intro.md similarity index 98% rename from doc/python.md rename to doc/python/python_intro.md index 93b5c43d4bac..2acb73b3c340 100644 --- a/doc/python.md +++ b/doc/python/python_intro.md @@ -1,5 +1,5 @@ XGBoost Python Module -==== +===================== This page will introduce XGBoost Python module, including: * [Building and Import](#building-and-import) @@ -8,6 +8,7 @@ This page will introduce XGBoost Python module, including: * [Train Model](#training-model) * [Early Stopping](#early-stopping) * [Prediction](#prediction) +* [API Reference](python_api.md) A [walk through python example](https://github.com/tqchen/xgboost/blob/master/demo/guide-python) for UCI Mushroom dataset is provided. diff --git a/doc/sphinx_util.py b/doc/sphinx_util.py new file mode 100644 index 000000000000..33c98d3815bc --- /dev/null +++ b/doc/sphinx_util.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +"""Helper hacking utilty function for customization.""" +import sys +import os +import subprocess + +# TODO: make less hacky way than this one +if os.environ.get('READTHEDOCS', None) == 'True': + subprocess.call('cd ..; rm -rf recommonmark;' + + 'git clone https://github.com/tqchen/recommonmark;' + + 'cp recommonmark/recommonmark/parser.py doc/parser', shell=True) + +sys.path.insert(0, os.path.abspath('..')) +import parser + +class MarkdownParser(parser.CommonMarkParser): + github_doc_root = None + doc_suffix = set(['md', 'rst']) + + @staticmethod + def remap_url(url): + if MarkdownParser.github_doc_root is None or url is None: + return url + if url.startswith('#'): + return url + arr = url.split('#', 1) + ssuffix = arr[0].rsplit('.', 1) + + if len(ssuffix) == 2 and (ssuffix[-1] in MarkdownParser.doc_suffix + and arr[0].find('://') == -1): + arr[0] = ssuffix[0] + '.html' + return '#'.join(arr) + else: + if arr[0].find('://') == -1: + return MarkdownParser.github_doc_root + url + else: + return url + + def reference(self, block): + block.destination = remap_url(block.destination) + return super(MarkdownParser, self).reference(block) + +# inplace modify the function in recommonmark module to allow link remap +old_ref = parser.reference + +def reference(block): + block.destination = MarkdownParser.remap_url(block.destination) + return old_ref(block) + +parser.reference = reference diff --git a/python-package/xgboost/__init__.py b/python-package/xgboost/__init__.py index 6f967b8378dc..b284c27e0d90 100644 --- a/python-package/xgboost/__init__.py +++ b/python-package/xgboost/__init__.py @@ -10,3 +10,7 @@ from .sklearn import XGBModel, XGBClassifier, XGBRegressor __version__ = '0.4' + +__all__ = ['DMatrix', 'Booster', + 'train', 'cv', + 'XGBModel', 'XGBClassifier', 'XGBRegressor'] diff --git a/python-package/xgboost/core.py b/python-package/xgboost/core.py index 85017cb82709..0849d276cf9e 100644 --- a/python-package/xgboost/core.py +++ b/python-package/xgboost/core.py @@ -50,20 +50,24 @@ def find_lib_path(): else: dll_path = [os.path.join(p, 'libxgboostwrapper.so') for p in dll_path] lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)] - if len(lib_path) == 0: + if len(lib_path) == 0 and not os.environ.get('XGBOOST_BUILD_DOC', False): raise XGBoostLibraryNotFound( - 'Cannot find XGBoost Libarary in the candicate path %s,' + - 'Did you run build.sh in root oath?' % str(dll_path)) + 'Cannot find XGBoost Libarary in the candicate path, ' + + 'did you run build.sh in root path?\n' + 'List of candidates:\n' + ('\n'.join(dll_path))) return lib_path + def _load_lib(): """Load xgboost Library.""" lib_path = find_lib_path() + if len(lib_path) == 0: + return None lib = ctypes.cdll.LoadLibrary(lib_path[0]) lib.XGBGetLastError.restype = ctypes.c_char_p - return lib + # load the XGBoost library globally _LIB = _load_lib() @@ -119,6 +123,7 @@ class DMatrix(object): DMatrix is a internal data structure that used by XGBoost which is optimized for both memory efficiency and training speed. + You can construct DMatrix from numpy.arrays """ def __init__(self, data, label=None, missing=0.0, weight=None, silent=False): """ @@ -127,15 +132,16 @@ def __init__(self, data, label=None, missing=0.0, weight=None, silent=False): Parameters ---------- data : string/numpy array/scipy.sparse - Data source, string type is the path of svmlight format txt file, - xgb buffer or path to cache_file - label : list or numpy 1-D array (optional) + Data source of DMatrix. + When data is string type, it represents the path libsvm format txt file, + or binary file that xgboost can read from. + label : list or numpy 1-D array, optional Label of the training data. - missing : float + missing : float, optional Value in the data which needs to be present as a missing value. - weight : list or numpy 1-D array (optional) + weight : list or numpy 1-D array , optional Weight for each instance. - silent: boolean + silent : boolean, optional Whether print messages during construction """ # force into void_p, mac need to pass things in as void_p @@ -469,13 +475,22 @@ def copy(self): """Copy the booster object. Returns - -------- - a copied booster model + ------- + booster: `Booster` + a copied booster model """ return self.__copy__() def set_param(self, params, value=None): - """Set parameters into the DMatrix.""" + """Set parameters into the Booster. + + Parameters + ---------- + params: dict/list/str + list of key,value paris, dict of key to value or simply str key + value: optional + value of the specified parameter, when params is str key + """ if isinstance(params, collections.Mapping): params = params.items() elif isinstance(params, STRING_TYPES) and value is not None: @@ -485,7 +500,7 @@ def set_param(self, params, value=None): def update(self, dtrain, iteration, fobj=None): """ - Update (one iteration). + Update for one iteration, with objective function calculated internally. Parameters ---------- @@ -507,7 +522,7 @@ def update(self, dtrain, iteration, fobj=None): def boost(self, dtrain, grad, hess): """ - Update. + Boost the booster for one iteration, with customized gradient statistics. Parameters ---------- @@ -542,7 +557,8 @@ def eval_set(self, evals, iteration=0, feval=None): Returns ------- - evaluation result + result: str + Evaluation result string. """ if feval is None: for d in evals: @@ -567,18 +583,21 @@ def eval_set(self, evals, iteration=0, feval=None): def eval(self, data, name='eval', iteration=0): """Evaluate the model on mat. - Parameters - --------- + ---------- data : DMatrix The dmatrix storing the input. - name : str (default = 'eval') - The name of the dataset + name : str, optional + The name of the dataset. + iteration : int, optional + The current iteration number. - iteration : int (default = 0) - The current iteration number + Returns + ------- + result: str + Evaluation result string. """ return self.eval_set([(data, name)], iteration) diff --git a/python-package/xgboost/sklearn.py b/python-package/xgboost/sklearn.py index 4a5771724b03..6f176972aced 100644 --- a/python-package/xgboost/sklearn.py +++ b/python-package/xgboost/sklearn.py @@ -206,9 +206,9 @@ def predict(self, data): class XGBClassifier(XGBModel, XGBClassifierBase): # pylint: disable=missing-docstring,too-many-arguments,invalid-name - __doc__ = """ - Implementation of the scikit-learn API for XGBoost classification - """ + "\n".join(XGBModel.__doc__.split('\n')[2:]) + __doc__ = """Implementation of the scikit-learn API for XGBoost classification. + + """ + '\n'.join(XGBModel.__doc__.split('\n')[2:]) def __init__(self, max_depth=3, learning_rate=0.1, n_estimators=100, silent=True, @@ -335,7 +335,5 @@ def predict_proba(self, data): class XGBRegressor(XGBModel, XGBRegressorBase): # pylint: disable=missing-docstring - __doc__ = """ - Implementation of the scikit-learn API for XGBoost regression - """ + "\n".join(XGBModel.__doc__.split('\n')[2:]) - + __doc__ = """Implementation of the scikit-learn API for XGBoost regression. + """ + '\n'.join(XGBModel.__doc__.split('\n')[2:]) From e8de5da3a56661b19e237bf0e60302b69731c35d Mon Sep 17 00:00:00 2001 From: tqchen Date: Sat, 1 Aug 2015 13:47:41 -0700 Subject: [PATCH 093/126] Document refactor change badge --- CONTRIBUTORS.md | 15 +++- R-package/DESCRIPTION | 4 +- R-package/README.md | 8 +- R-package/vignettes/xgboostPresentation.Rmd | 46 +++++------ README.md | 9 ++- demo/README.md | 10 +-- demo/guide-python/README.md | 4 +- .../kaggle-otto/understandingXGBoostModel.Rmd | 14 ++-- doc/README | 5 ++ doc/build.md | 26 +++--- doc/conf.py | 13 ++- doc/dev-guide/contribute.md | 13 +++ doc/faq.md | 61 ++++++++++++++ doc/index.md | 80 +++++++++++++------ doc/param_tuning.md | 12 +-- doc/parameter.md | 25 +++--- doc/python-requirements.txt | 2 + doc/python/python_api.rst | 5 +- doc/python/python_intro.md | 73 ++++++++--------- doc/sphinx_util.py | 47 ++--------- 20 files changed, 287 insertions(+), 185 deletions(-) create mode 100644 doc/README create mode 100644 doc/dev-guide/contribute.md create mode 100644 doc/faq.md create mode 100644 doc/python-requirements.txt diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 71f48a1666df..36ccc9d5daaf 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,9 +1,9 @@ Contributors of DMLC/XGBoost -======= +============================ XGBoost has been developed and used by a group of active community. Everyone is more than welcomed to is a great way to make the project better and more accessible to more users. Comitters -======= +--------- Committers are people who have made substantial contribution to the project and granted write access to the project. * [Tianqi Chen](https://github.com/tqchen), University of Washington - Tianqi is a PhD working on large-scale machine learning, he is the creator of the project. @@ -14,8 +14,17 @@ Committers are people who have made substantial contribution to the project and * [Michael Benesty](https://github.com/pommedeterresautee) - Micheal is a lawyer, data scientist in France, he is the creator of xgboost interactive analysis module in R. +Become a Comitter +----------------- +XGBoost is a opensource project and we are actively looking for new comitters who are willing to help maintaining and lead the project. +Committers comes from contributors who: +* Made substantial contribution to the project. +* Willing to spent time on maintaining and lead the project. + +New committers will be proposed by current comitter memembers, with support from more than two of current comitters. + List of Contributors -======= +-------------------- * [Full List of Contributors](https://github.com/dmlc/xgboost/graphs/contributors) - To contributors: please add your name to the list when you submit a patch to the project:) * [Kailong Chen](https://github.com/kalenhaha) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 4560971e2589..19410d65a44a 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -1,8 +1,8 @@ Package: xgboost Type: Package Title: Extreme Gradient Boosting -Version: 0.4-1 -Date: 2015-05-11 +Version: 0.4-2 +Date: 2015-08-01 Author: Tianqi Chen , Tong He , Michael Benesty Maintainer: Tong He Description: Extreme Gradient Boosting, which is an diff --git a/R-package/README.md b/R-package/README.md index 96113c39162c..294691416349 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -4,7 +4,13 @@ R package for xgboost Installation ------------ -For up-to-date version (which is recommended), please install from github. Windows user will need to install [RTools](http://cran.r-project.org/bin/windows/Rtools/) first. +We are [on CRAN](https://cran.r-project.org/web/packages/xgboost/index.html) now. For stable/pre-compiled(for Windows and OS X) version, please install from CRAN: + +```r +install.packages('xgboost') +``` + +For up-to-date version, please install from github. Windows user will need to install [RTools](http://cran.r-project.org/bin/windows/Rtools/) first. ```r devtools::install_github('dmlc/xgboost',subdir='R-package') diff --git a/R-package/vignettes/xgboostPresentation.Rmd b/R-package/vignettes/xgboostPresentation.Rmd index 39ab819f7037..89d27fb45dc2 100644 --- a/R-package/vignettes/xgboostPresentation.Rmd +++ b/R-package/vignettes/xgboostPresentation.Rmd @@ -1,6 +1,6 @@ --- title: "Xgboost presentation" -output: +output: rmarkdown::html_vignette: css: vignette.css number_sections: yes @@ -16,7 +16,7 @@ vignette: > Introduction ============ -**Xgboost** is short for e**X**treme **G**radient **Boost**ing package. +**Xgboost** is short for e**X**treme **G**radient **Boost**ing package. The purpose of this Vignette is to show you how to use **Xgboost** to build a model and make predictions. @@ -25,9 +25,9 @@ It is an efficient and scalable implementation of gradient boosting framework by - *linear* model ; - *tree learning* algorithm. -It supports various objective functions, including *regression*, *classification* and *ranking*. The package is made to be extendible, so that users are also allowed to define their own objective functions easily. +It supports various objective functions, including *regression*, *classification* and *ranking*. The package is made to be extendible, so that users are also allowed to define their own objective functions easily. -It has been [used](https://github.com/dmlc/xgboost) to win several [Kaggle](http://www.kaggle.com) competitions. +It has been [used](https://github.com/dmlc/xgboost) to win several [Kaggle](http://www.kaggle.com) competitions. It has several features: @@ -64,7 +64,7 @@ Formerly available versions can be obtained from the CRAN [archive](http://cran. Learning ======== -For the purpose of this tutorial we will load **Xgboost** package. +For the purpose of this tutorial we will load **XGBoost** package. ```{r libLoading, results='hold', message=F, warning=F} require(xgboost) @@ -73,7 +73,7 @@ require(xgboost) Dataset presentation -------------------- -In this example, we are aiming to predict whether a mushroom can be eaten or not (like in many tutorials, example data are the the same as you will use on in your every day life :-). +In this example, we are aiming to predict whether a mushroom can be eaten or not (like in many tutorials, example data are the the same as you will use on in your every day life :-). Mushroom data is cited from UCI Machine Learning Repository. @Bache+Lichman:2013. @@ -85,7 +85,7 @@ We will load the `agaricus` datasets embedded with the package and will link the The datasets are already split in: * `train`: will be used to build the model ; -* `test`: will be used to assess the quality of our model. +* `test`: will be used to assess the quality of our model. Why *split* the dataset in two parts? @@ -115,7 +115,7 @@ dim(train$data) dim(test$data) ``` -This dataset is very small to not make the **R** package too heavy, however **Xgboost** is built to manage huge dataset very efficiently. +This dataset is very small to not make the **R** package too heavy, however **XGBoost** is built to manage huge dataset very efficiently. As seen below, the `data` are stored in a `dgCMatrix` which is a *sparse* matrix and `label` vector is a `numeric` vector (`{0,1}`): @@ -124,7 +124,7 @@ class(train$data)[1] class(train$label) ``` -Basic Training using Xgboost +Basic Training using XGBoost ---------------------------- This step is the most critical part of the process for the quality of our model. @@ -160,7 +160,7 @@ bstDense <- xgboost(data = as.matrix(train$data), label = train$label, max.depth #### xgb.DMatrix -**Xgboost** offers a way to group them in a `xgb.DMatrix`. You can even add other meta data in it. It will be usefull for the most advanced features we will discover later. +**XGBoost** offers a way to group them in a `xgb.DMatrix`. You can even add other meta data in it. It will be usefull for the most advanced features we will discover later. ```{r trainingDmatrix, message=F, warning=F} dtrain <- xgb.DMatrix(data = train$data, label = train$label) @@ -169,7 +169,7 @@ bstDMatrix <- xgboost(data = dtrain, max.depth = 2, eta = 1, nthread = 2, nround #### Verbose option -**Xgboost** has severa features to help you to view how the learning progress internally. The purpose is to help you to set the best parameters, which is the key of your model quality. +**XGBoost** has severa features to help you to view how the learning progress internally. The purpose is to help you to set the best parameters, which is the key of your model quality. One of the simplest way to see the training progress is to set the `verbose` option (see below for more advanced technics). @@ -188,7 +188,7 @@ bst <- xgboost(data = dtrain, max.depth = 2, eta = 1, nthread = 2, nround = 2, o bst <- xgboost(data = dtrain, max.depth = 2, eta = 1, nthread = 2, nround = 2, objective = "binary:logistic", verbose = 2) ``` -Basic prediction using Xgboost +Basic prediction using XGBoost ============================== Perform the prediction @@ -211,7 +211,7 @@ These numbers doesn't look like *binary classification* `{0,1}`. We need to perf Transform the regression in a binary classification --------------------------------------------------- -The only thing that **Xgboost** does is a *regression*. **Xgboost** is using `label` vector to build its *regression* model. +The only thing that **XGBoost** does is a *regression*. **XGBoost** is using `label` vector to build its *regression* model. How can we use a *regression* model to perform a binary classification? @@ -240,7 +240,7 @@ Steps explanation: 2. `probabilityVectorPreviouslyComputed != test$label` computes the vector of error between true data and computed probabilities ; 3. `mean(vectorOfErrors)` computes the *average error* itself. -The most important thing to remember is that **to do a classification, you just do a regression to the** `label` **and then apply a threshold**. +The most important thing to remember is that **to do a classification, you just do a regression to the** `label` **and then apply a threshold**. *Multiclass* classification works in a similar way. @@ -269,7 +269,7 @@ Both `xgboost` (simple) and `xgb.train` (advanced) functions train models. One of the special feature of `xgb.train` is the capacity to follow the progress of the learning after each round. Because of the way boosting works, there is a time when having too many rounds lead to an overfitting. You can see this feature as a cousin of cross-validation method. The following technics will help you to avoid overfitting or optimizing the learning time in stopping it as soon as possible. -One way to measure progress in learning of a model is to provide to **Xgboost** a second dataset already classified. Therefore it can learn on the first dataset and test its model on the second one. Some metrics are measured after each round during the learning. +One way to measure progress in learning of a model is to provide to **XGBoost** a second dataset already classified. Therefore it can learn on the first dataset and test its model on the second one. Some metrics are measured after each round during the learning. > in some way it is similar to what we have done above with the average error. The main difference is that below it was after building the model, and now it is during the construction that we measure errors. @@ -281,7 +281,7 @@ watchlist <- list(train=dtrain, test=dtest) bst <- xgb.train(data=dtrain, max.depth=2, eta=1, nthread = 2, nround=2, watchlist=watchlist, objective = "binary:logistic") ``` -**Xgboost** has computed at each round the same average error metric than seen above (we set `nround` to 2, that is why we have two lines). Obviously, the `train-error` number is related to the training dataset (the one the algorithm learns from) and the `test-error` number to the test dataset. +**XGBoost** has computed at each round the same average error metric than seen above (we set `nround` to 2, that is why we have two lines). Obviously, the `train-error` number is related to the training dataset (the one the algorithm learns from) and the `test-error` number to the test dataset. Both training and test error related metrics are very similar, and in some way, it makes sense: what we have learned from the training dataset matches the observations from the test dataset. @@ -298,13 +298,13 @@ bst <- xgb.train(data=dtrain, max.depth=2, eta=1, nthread = 2, nround=2, watchli Linear boosting --------------- -Until know, all the learnings we have performed were based on boosting trees. **Xgboost** implements a second algorithm, based on linear boosting. The only difference with previous command is `booster = "gblinear"` parameter (and removing `eta` parameter). +Until know, all the learnings we have performed were based on boosting trees. **XGBoost** implements a second algorithm, based on linear boosting. The only difference with previous command is `booster = "gblinear"` parameter (and removing `eta` parameter). ```{r linearBoosting, message=F, warning=F} bst <- xgb.train(data=dtrain, booster = "gblinear", max.depth=2, nthread = 2, nround=2, watchlist=watchlist, eval.metric = "error", eval.metric = "logloss", objective = "binary:logistic") ``` -In this specific case, *linear boosting* gets sligtly better performance metrics than decision trees based algorithm. +In this specific case, *linear boosting* gets sligtly better performance metrics than decision trees based algorithm. In simple cases, it will happem because there is nothing better than a linear algorithm to catch a linear link. However, decision trees are much better to catch a non linear link between predictors and outcome. Because there is no silver bullet, we advise you to check both algorithms with your own datasets to have an idea of what to use. @@ -340,7 +340,7 @@ print(paste("test-error=", err)) View feature importance/influence from the learnt model ------------------------------------------------------- -Feature importance is similar to R gbm package's relative influence (rel.inf). +Feature importance is similar to R gbm package's relative influence (rel.inf). ``` importance_matrix <- xgb.importance(model = bst) @@ -370,7 +370,7 @@ Save and load models May be your dataset is big, and it takes time to train a model on it? May be you are not a big fan of loosing time in redoing the same task again and again? In these very rare cases, you will want to save your model and load it when required. -Hopefully for you, **Xgboost** implements such functions. +Hopefully for you, **XGBoost** implements such functions. ```{r saveModel, message=F, warning=F} # save model to binary local file @@ -397,7 +397,7 @@ file.remove("./xgboost.model") > result is `0`? We are good! -In some very specific cases, like when you want to pilot **Xgboost** from `caret` package, you will want to save the model as a *R* binary vector. See below how to do it. +In some very specific cases, like when you want to pilot **XGBoost** from `caret` package, you will want to save the model as a *R* binary vector. See below how to do it. ```{r saveLoadRBinVectorModel, message=F, warning=F} # save model to R's raw vector @@ -412,9 +412,9 @@ pred3 <- predict(bst3, test$data) # pred2 should be identical to pred print(paste("sum(abs(pred3-pred))=", sum(abs(pred2-pred)))) -``` +``` -> Again `0`? It seems that `Xgboost` works pretty well! +> Again `0`? It seems that `XGBoost` works pretty well! References ========== diff --git a/README.md b/README.md index be93e99fda76..ac29ef7eb52b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -DMLC/XGBoost -======= - -[![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + eXtreme Gradient Boosting +=========== +[![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) +[![Documentation Status](https://readthedocs.org/projects/xgboost/badge/?version=latest)](https://xgboost.readthedocs.org) +[![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. diff --git a/demo/README.md b/demo/README.md index fcfaa8434e64..d6f061484962 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,12 +1,12 @@ -XGBoost Examples -==== +XGBoost Code Examples +===================== This folder contains all the code examples using xgboost. * Contribution of examples, benchmarks is more than welcome! * If you like to share how you use xgboost to solve your problem, send a pull request:) Features Walkthrough -==== +-------------------- This is a list of short codes introducing different functionalities of xgboost packages. * Basic walkthrough of packages [python](guide-python/basic_walkthrough.py) @@ -37,7 +37,7 @@ This is a list of short codes introducing different functionalities of xgboost p [R](../R-package/demo/predict_leaf_indices.R) Basic Examples by Tasks -==== +----------------------- Most of examples in this section are based on CLI or python version. However, the parameter settings can be applied to all versions * [Binary classification](binary_classification) @@ -46,7 +46,7 @@ However, the parameter settings can be applied to all versions * [Learning to Rank](rank) Benchmarks -==== +---------- * [Starter script for Kaggle Higgs Boson](kaggle-higgs) * [Kaggle Tradeshift winning solution by daxiongshu](https://github.com/daxiongshu/kaggle-tradeshift-winning-solution) diff --git a/demo/guide-python/README.md b/demo/guide-python/README.md index 32d0290ab7e6..ff1f98ad0d9d 100644 --- a/demo/guide-python/README.md +++ b/demo/guide-python/README.md @@ -1,6 +1,6 @@ XGBoost Python Feature Walkthrough -==== -* [Basic walkthrough of wrappers](basic_walkthrough.py) +================================== +* [Basic walkthrough of wrappers](basic_walkthrough.py) * [Cutomize loss function, and evaluation metric](custom_objective.py) * [Boosting from existing prediction](boost_from_prediction.py) * [Predicting using first n trees](predict_first_ntree.py) diff --git a/demo/kaggle-otto/understandingXGBoostModel.Rmd b/demo/kaggle-otto/understandingXGBoostModel.Rmd index 6bd64401d206..e04277d4ee20 100644 --- a/demo/kaggle-otto/understandingXGBoostModel.Rmd +++ b/demo/kaggle-otto/understandingXGBoostModel.Rmd @@ -1,7 +1,7 @@ --- title: "Understanding XGBoost Model on Otto Dataset" author: "Michaël Benesty" -output: +output: rmarkdown::html_vignette: css: ../../R-package/vignettes/vignette.css number_sections: yes @@ -54,7 +54,7 @@ test[1:6,1:5, with =F] Each *column* represents a feature measured by an `integer`. Each *row* is an **Otto** product. -Obviously the first column (`ID`) doesn't contain any useful information. +Obviously the first column (`ID`) doesn't contain any useful information. To let the algorithm focus on real stuff, we will delete it. @@ -124,7 +124,7 @@ param <- list("objective" = "multi:softprob", cv.nround <- 5 cv.nfold <- 3 -bst.cv = xgb.cv(param=param, data = trainMatrix, label = y, +bst.cv = xgb.cv(param=param, data = trainMatrix, label = y, nfold = cv.nfold, nrounds = cv.nround) ``` > As we can see the error rate is low on the test dataset (for a 5mn trained model). @@ -144,7 +144,7 @@ Feature importance So far, we have built a model made of **`r nround`** trees. -To build a tree, the dataset is divided recursively several times. At the end of the process, you get groups of observations (here, these observations are properties regarding **Otto** products). +To build a tree, the dataset is divided recursively several times. At the end of the process, you get groups of observations (here, these observations are properties regarding **Otto** products). Each division operation is called a *split*. @@ -158,7 +158,7 @@ In the same way, in Boosting we try to optimize the missclassification at each r The improvement brought by each *split* can be measured, it is the *gain*. -Each *split* is done on one feature only at one value. +Each *split* is done on one feature only at one value. Let's see what the model looks like. @@ -168,7 +168,7 @@ model[1:10] ``` > For convenience, we are displaying the first 10 lines of the model only. -Clearly, it is not easy to understand what it means. +Clearly, it is not easy to understand what it means. Basically each line represents a *branch*, there is the *tree* ID, the feature ID, the point where it *splits*, and information regarding the next *branches* (left, right, when the row for this feature is N/A). @@ -217,7 +217,7 @@ xgb.plot.tree(feature_names = names, model = bst, n_first_tree = 2) We are just displaying the first two trees here. -On simple models the first two trees may be enough. Here, it might not be the case. We can see from the size of the trees that the intersaction between features is complicated. +On simple models the first two trees may be enough. Here, it might not be the case. We can see from the size of the trees that the intersaction between features is complicated. Besides, **XGBoost** generate `k` trees at each round for a `k`-classification problem. Therefore the two trees illustrated here are trying to classify data into different classes. Going deeper diff --git a/doc/README b/doc/README new file mode 100644 index 000000000000..a14ad800b1fb --- /dev/null +++ b/doc/README @@ -0,0 +1,5 @@ +The document of xgboost is generated with recommonmark and sphinx. + +You can build it locally by typing "make html" in this folder. +- You will need to rerun the recommonmark script for readthedocs in sphinx_util. +- This was a hack to get the customized parser into readthedocs, hopefully to be removed in future. diff --git a/doc/build.md b/doc/build.md index 7b8ee96aaadd..b97237bcbac3 100644 --- a/doc/build.md +++ b/doc/build.md @@ -1,5 +1,5 @@ Build XGBoost -==== +============= * Run ```bash build.sh``` (you can also type make) * If you have C++11 compiler, it is recommended to type ```make cxx11=1``` - C++11 is not used by default @@ -12,19 +12,19 @@ Build XGBoost * OS X with multi-threading support: see [next section](#openmp-for-os-x) Build XGBoost in OS X with OpenMP -==== +--------------------------------- Here is the complete solution to use OpenMp-enabled compilers to install XGBoost. 1. Obtain gcc with openmp support by `brew install gcc --without-multilib` **or** clang with openmp by `brew install clang-omp`. The clang one is recommended because the first method requires us compiling gcc inside the machine (more than an hour in mine)! (BTW, `brew` is the de facto standard of `apt-get` on OS X. So installing [HPC](http://hpc.sourceforge.net/) separately is not recommended, but it should work.) -2. **if you are planing to use clang-omp** - in step 3 and/or 4, change line 9 in `xgboost/src/utils/omp.h` to +2. **if you are planing to use clang-omp** - in step 3 and/or 4, change line 9 in `xgboost/src/utils/omp.h` to ```C++ - #include /* instead of #include */` + #include /* instead of #include */` ``` - to make it work, otherwise you might get this error - + to make it work, otherwise you might get this error + `src/tree/../utils/omp.h:9:10: error: 'omp.h' file not found...` @@ -43,11 +43,11 @@ Here is the complete solution to use OpenMp-enabled compilers to install XGBoost export CXX = clang-omp++ ``` - Remember to change `header` (mentioned in step 2) if using clang-omp. - + Remember to change `header` (mentioned in step 2) if using clang-omp. + Then `cd xgboost` then `bash build.sh` to compile XGBoost. And go to `wrapper` sub-folder to install python version. -4. Set the `Makevars` file in highest piority for R. +4. Set the `Makevars` file in highest piority for R. The point is, there are three `Makevars` : `~/.R/Makevars`, `xgboost/R-package/src/Makevars`, and `/usr/local/Cellar/r/3.2.0/R.framework/Resources/etc/Makeconf` (the last one obtained by running `file.path(R.home("etc"), "Makeconf")` in R), and `SHLIB_OPENMP_CXXFLAGS` is not set by default!! After trying, it seems that the first one has highest piority (surprise!). @@ -75,21 +75,21 @@ Here is the complete solution to use OpenMp-enabled compilers to install XGBoost Again, remember to change `header` if using clang-omp. - Then inside R, run + Then inside R, run ```R install.packages('xgboost/R-package/', repos=NULL, type='source') ``` - + Or - + ```R devtools::install_local('xgboost/', subdir = 'R-package') # you may use devtools ``` Build with HDFS and S3 Support -===== +------------------------------ * To build xgboost use with HDFS/S3 support and distributed learnig. It is recommended to build with dmlc, with the following steps - ```git clone https://github.com/dmlc/dmlc-core``` - Follow instruction in dmlc-core/make/config.mk to compile libdmlc.a diff --git a/doc/conf.py b/doc/conf.py index b08f495f58ae..05e1e91babf0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -22,7 +22,13 @@ sys.path.insert(0, libpath) sys.path.insert(0, curr_path) -from sphinx_util import MarkdownParser +from sphinx_util import MarkdownParser, AutoStructify + +# -- mock out modules +import mock +MOCK_MODULES = ['numpy', 'scipy', 'scipy.sparse', 'sklearn', 'matplotlib'] +for mod_name in MOCK_MODULES: + sys.modules[mod_name] = mock.Mock() # -- General configuration ------------------------------------------------ @@ -155,4 +161,7 @@ def setup(app): # Add hook for building doxygen xml when needed # no c++ API for now # app.connect("builder-inited", generate_doxygen_xml) - pass + app.add_config_value('recommonmark_config', { + 'url_resolver': lambda url: github_doc_root + url, + }, True) + app.add_transform(AutoStructify) diff --git a/doc/dev-guide/contribute.md b/doc/dev-guide/contribute.md new file mode 100644 index 000000000000..5d8f7c26cbee --- /dev/null +++ b/doc/dev-guide/contribute.md @@ -0,0 +1,13 @@ +Developer Guide +=============== +This page contains guide for developers of xgboost. XGBoost has been developed and used by a group of active community. +Everyone is more than welcomed to is a great way to make the project better. +The project is maintained by a committee of [committers](../../CONTRIBUTORS.md#comitters) who will review and merge pull requests from contributors. + +Contributing Code +================= +* The C++ code follows Google C++ style +* We follow numpy style to document our python module +* Tools to precheck codestyle + - clone https://github.com/dmlc/dmlc-core into root directory + - type ```make lint``` and fix possible errors. diff --git a/doc/faq.md b/doc/faq.md new file mode 100644 index 000000000000..5c985182af19 --- /dev/null +++ b/doc/faq.md @@ -0,0 +1,61 @@ +Frequent Asked Questions +======================== +This document contains the frequent asked question to xgboost. + +How to tune parameters +---------------------- +See [Parameter Tunning Guide](param_tuning.md) + + +I have a big dataset +-------------------- +XGBoost is designed to be memory efficient. Usually it could handle problems as long as the data fit into your memory +(This usually means millions of instances). +If you are running out of memory, checkout [external memory version](external_memory.md) or +[distributed version](https://github.com/dmlc/wormhole/tree/master/learn/xgboost) of xgboost. + + +Running xgboost on Platform X (Hadoop/Yarn, Mesos) +-------------------------------------------------- +The distributed version of XGBoost is designed to be portable to various environment. +Distributed XGBoost can be ported to any platform that supports [rabit](https://github.com/dmlc/rabit). +You can directly run xgboost on Yarn. In theory Mesos and other resource allocation engine can be easily supported as well. + + +Why not implement distributed xgboost on top of X (Spark, Hadoop) +----------------------------------------------------------------- +The first fact we need to know is going distributed does not necessarily solve all the problems. +Instead, it creates more problems such as more communication over head and fault tolerance. +The ultimate question will still come back into how to push the limit of each computation node +and use less resources to complete the task (thus with less communication and chance of failure). + +To achieve these, we decide to reuse the optimizations in the single node xgboost and build distributed version on top of it. +The demand of communication in machine learning is rather simple, in a sense that we can depend on a limited set of API (in our case rabit). +Such design allows us to reuse most of the code, and being portable to major platforms such as Hadoop/Yarn, MPI, SGE. +Most importantly, pushs the limit of the computation resources we can use. + + +How can I port the model to my own system +----------------------------------------- +The model and data format of XGBoost is exchangable. +Which means the model trained by one langauge can be loaded in another. +This means you can train the model using R, while running prediction using +Java or C++, which are more common in production system. +You can also train the model using distributed version, +and load them in from python to do some interactive analysis. + + +Do you support LambdaMART +------------------------- +Yes, xgboost implements LambdaMART. Checkout the objective section in [parameters](parameter.md) + + +How to deal with Missing Value +------------------------------ +xgboost support missing value by default + + +Slightly different result between runs +-------------------------------------- +This could happen, due to non-determinism in floating point summation order and multi-threading. +Though the general accurac will usually remain the same. \ No newline at end of file diff --git a/doc/index.md b/doc/index.md index 5d8d5b26f647..7c41d15e240d 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,28 +1,45 @@ XGBoost Documentation ===================== +This is document of xgboost library. +XGBoost is short for eXtreme gradient boosting. This is a library that is designed, and optimized for boosted (tree) algorithms. +The goal of this library is to push the extreme of the computation limits of machines to provide a ***scalable***, ***portable*** and ***accurate*** +for large scale tree boosting. - -* [Using XGBoost in Python](python/python_intro.md) -* [Using XGBoost in R](../R-package/vignettes/xgboostPresentation.Rmd) -* [Learning to use xgboost by example](../demo) -* [External Memory Version](external_memory.md) -* [Text input format](input_format.md) -* [Build Instruction](build.md) -* [Notes on the Code](../src) -* List of all parameters and their usage: [Parameters](parameter.md) - - [Notes on Parameter Tunning](param_tuning.md) -* Learning about the model: [Introduction to Boosted Trees](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) - +This document is hosted at http://xgboost.readthedocs.org/. You can also browse most of the documents in github directly. How to Get Started ------------------ -* Try to read the [binary classification example](../demo/binary_classification) for getting started example -* Find the guide specific language guide above for the language you like to use -* [Learning to use xgboost by example](../demo) contains lots of useful examples +The best way to get started to learn xgboost is by the examples. There are three types of examples you can find in xgboost. +* [Tutorials](#tutorials) are self-conatained tutorials on a complete data science tasks. +* [XGBoost Code Examples](../demo/) are collections of code and benchmarks of xgboost. + - There is a walkthrough section in this to walk you through specific API features. +* [Highlight Solutions](#highlight-solutions) are presentations using xgboost to solve real world problems. + - These examples are usually more advanced. You can usually find state-of-art solutions to many problems and challenges in here. + +After you gets familiar with the interface, checkout the following additional resources +* [Frequently Asked Questions](faq.md) +* [Learning what is in Behind: Introduction to Boosted Trees](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) +* [User Guide](#user-guide) contains comprehensive list of documents of xgboost. +* [Developer Guide](dev-guide/contribute.md) + +Tutorials +--------- +Tutorials are self contained materials that teaches you how to achieve a complete data science task with xgboost, these +are great resources to learn xgboost by real examples. If you think you have something that belongs to here, send a pull request. +* [Binary classification using XGBoost Command Line](../demo/binary_classification/) (CLI) + - This tutorial introduces the basic usage of CLI version of xgboost +* [Introduction of XGBoost in Python](python/python_intro.md) (python) + - This tutorial introduces the python package of xgboost +* [Introduction to XGBoost in R](../R-package/vignettes/xgboostPresentation.Rmd) (R package) + - This is a general presentation about xgboost in R. +* [Discover your data with XGBoost in R](../R-package/vignettes/discoverYourData.Rmd) (R package) + - This tutorial explaining feature analysis in xgboost. +* [Understanding XGBoost Model on Otto Dataset](../demo/kaggle-otto/understandingXGBoostModel.Rmd) (R package) + - This tutorial teaches you how to use xgboost to compete kaggle otto challenge. -Example Highlight Links ------------------------ +Highlight Solutions +------------------- This section is about blogposts, presentation and videos discussing how to use xgboost to solve your interesting problem. If you think something belongs to here, send a pull request. * [Kaggle CrowdFlower winner's solution by Chenglong Chen](https://github.com/ChenglongChen/Kaggle_CrowdFlower) * [Kaggle Malware Prediction winner's solution](https://github.com/xiaozhouwang/kaggle_Microsoft_Malware) @@ -31,14 +48,25 @@ This section is about blogposts, presentation and videos discussing how to use x * Video tutorial: [Better Optimization with Repeated Cross Validation and the XGBoost model](https://www.youtube.com/watch?v=Og7CGAfSr_Y) * [Winning solution of Kaggle Higgs competition: what a single model can do](http://no2147483647.wordpress.com/2014/09/17/winning-solution-of-kaggle-higgs-competition-what-a-single-model-can-do/) +User Guide +---------- +* [Frequently Asked Questions](faq.md) +* [Introduction to Boosted Trees](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) +* [Using XGBoost in Python](python/python_intro.md) +* [Using XGBoost in R](../R-package/vignettes/xgboostPresentation.Rmd) +* [Learning to use XGBoost by Example](../demo) +* [External Memory Version](external_memory.md) +* [Text input format](input_format.md) +* [Build Instruction](build.md) +* [Parameters](parameter.md) +* [Notes on Parameter Tunning](param_tuning.md) + + +Developer Guide +--------------- +* [Developer Guide](dev-guide/contribute.md) + API Reference ------------- - * [Python API Reference](python/python_api.rst) - -Contribution ------------- -Contribution of documents and use-cases are welcomed! -* This package use Google C++ style -* Check tool of codestyle - - clone https://github.com/dmlc/dmlc-core into root directory - - type ```make lint``` and fix possible errors. +* [Python API Reference](python/python_api.rst) + diff --git a/doc/param_tuning.md b/doc/param_tuning.md index 78263a6a859a..c5848f6024d6 100644 --- a/doc/param_tuning.md +++ b/doc/param_tuning.md @@ -1,5 +1,5 @@ Notes on Parameter Tuning -==== +========================= Parameter tuning is a dark art in machine learning, the optimal parameters of a model can depend on many scenarios. So it is impossible to create a comprehensive guide for doing so. @@ -8,7 +8,7 @@ This document tries to provide some guideline for parameters in xgboost. Understanding Bias-Variance Tradeoff -==== +------------------------------------ If you take a machine learning or statistics course, this is likely to be one of the most important concepts. When we allow the model to get more complicated (e.g. more depth), the model @@ -22,7 +22,7 @@ will make the model more conservative or not. This can be used to help you turn the knob between complicated model and simple model. Control Overfitting -==== +------------------- When you observe high training accuracy, but low tests accuracy, it is likely that you encounter overfitting problem. There are in general two ways that you can control overfitting in xgboost @@ -31,9 +31,9 @@ There are in general two ways that you can control overfitting in xgboost * The second way is to add randomness to make training robust to noise - This include ```subsample```, ```colsample_bytree``` - You can also reduce stepsize ```eta```, but needs to remember to increase ```num_round``` when you do so. - -Handle Imbalanced Dataset -=== + +Handle Imbalanced Dataset +------------------------- For common cases such as ads clickthrough log, the dataset is extremely imbalanced. This can affect the training of xgboost model, and there are two ways to improve it. * If you care only about the ranking order (AUC) of your prediction diff --git a/doc/parameter.md b/doc/parameter.md index 53cdd806f2b9..4e0f365bf3db 100644 --- a/doc/parameter.md +++ b/doc/parameter.md @@ -3,13 +3,15 @@ XGBoost Parameters Before running XGboost, we must set three types of parameters, general parameters, booster parameters and task parameters: - General parameters relates to which booster we are using to do boosting, commonly tree or linear model - Booster parameters depends on which booster you have chosen -- Task parameters that decides on the learning scenario, for example, regression tasks may use different parameters with ranking tasks. -- In addition to these parameters, there can be console parameters that relates to behavior of console version of xgboost(e.g. when to save model) +- Learning Task parameters that decides on the learning scenario, for example, regression tasks may use different parameters with ranking tasks. +- Command line parameters that relates to behavior of CLI version of xgboost. -### Parameters in R Package +Parameters in R Package +----------------------- In R-package, you can use .(dot) to replace under score in the parameters, for example, you can use max.depth as max_depth. The underscore parameters are also valid in R. -### General Parameters +General Parameters +------------------ * booster [default=gbtree] - which booster to use, can be gbtree or gblinear. gbtree uses tree based model while gblinear uses linear function. * silent [default=0] @@ -21,10 +23,8 @@ In R-package, you can use .(dot) to replace under score in the parameters, for e * num_feature [set automatically by xgboost, no need to be set by user] - feature dimension used in boosting, set to maximum dimension of the feature -### Booster Parameters -From xgboost-unity, the ```bst:``` prefix is no longer needed for booster parameters. Parameter with or without bst: prefix will be equivalent(i.e. both bst:eta and eta will be valid parameter setting) . - -#### Parameter for Tree Booster +Parameters for Tree Booster +--------------------------- * eta [default=0.3] - step size shrinkage used in update to prevents overfitting. After each boosting step, we can directly get the weights of new features. and eta actually shrinks the feature weights to make the boosting process more conservative. - range: [0,1] @@ -47,7 +47,8 @@ From xgboost-unity, the ```bst:``` prefix is no longer needed for booster parame - subsample ratio of columns when constructing each tree. - range: (0,1] -#### Parameter for Linear Booster +Parameters for Linear Booster +----------------------------- * lambda [default=0] - L2 regularization term on weights * alpha [default=0] @@ -55,7 +56,8 @@ From xgboost-unity, the ```bst:``` prefix is no longer needed for booster parame * lambda_bias - L2 regularization term on bias, default 0(no L1 reg on bias because it is not important) -### Task Parameters +Learning Task Parameters +------------------------ * objective [ default=reg:linear ] - specify the learning task and the corresponding learning objective, and the objective options are below: - "reg:linear" --linear regression @@ -87,7 +89,8 @@ training repeatively * seed [ default=0 ] - random number seed. -### Console Parameters +Command Line Parameters +----------------------- The following parameters are only used in the console version of xgboost * use_buffer [ default=1 ] - whether create binary buffer for text input, this normally will speedup loading when do diff --git a/doc/python-requirements.txt b/doc/python-requirements.txt new file mode 100644 index 000000000000..1a041d154156 --- /dev/null +++ b/doc/python-requirements.txt @@ -0,0 +1,2 @@ +commonmark + diff --git a/doc/python/python_api.rst b/doc/python/python_api.rst index e665efe84a2d..85249cbc4ead 100644 --- a/doc/python/python_api.rst +++ b/doc/python/python_api.rst @@ -1,6 +1,8 @@ Python API Reference ==================== -This page gives the Python API reference of xgboost. +This page gives the Python API reference of xgboost, please also refer to Python Package Introduction for more information about python package. + +The document in this page is automatically generated by sphinx. The content do not render at github, you can view it at http://xgboost.readthedocs.org/en/latest/python/python_api.html Core Data Structure ------------------- @@ -33,4 +35,3 @@ Scikit-Learn API .. autoclass:: xgboost.XGBClassifier :members: :show-inheritance: - diff --git a/doc/python/python_intro.md b/doc/python/python_intro.md index 2acb73b3c340..2b670a053924 100644 --- a/doc/python/python_intro.md +++ b/doc/python/python_intro.md @@ -1,32 +1,27 @@ -XGBoost Python Module -===================== +Python Package Introduction +=========================== +This document gives a basic walkthrough of xgboost python package. -This page will introduce XGBoost Python module, including: -* [Building and Import](#building-and-import) -* [Data Interface](#data-interface) -* [Setting Parameters](#setting-parameters) -* [Train Model](#training-model) -* [Early Stopping](#early-stopping) -* [Prediction](#prediction) -* [API Reference](python_api.md) +***List of other Helpful Links*** +* [Python walkthrough code collections](https://github.com/tqchen/xgboost/blob/master/demo/guide-python) +* [Python API Reference](python_api.rst) -A [walk through python example](https://github.com/tqchen/xgboost/blob/master/demo/guide-python) for UCI Mushroom dataset is provided. - -= -#### Install - -To install XGBoost, you need to run `make` in the root directory of the project and then in the `python-package` directory run +Install XGBoost +--------------- +To install XGBoost, do the following steps. +* You need to run `make` in the root directory of the project +* In the `python-package` directory run ```shell python setup.py install ``` -Then import the module in Python as usual + ```python import xgboost as xgb ``` -= -#### Data Interface +Data Interface +-------------- XGBoost python module is able to loading from libsvm txt format file, Numpy 2D array and xgboost binary buffer file. The data will be store in ```DMatrix``` object. * To load libsvm text format file and XGBoost binary file into ```DMatrix```, the usage is like @@ -42,8 +37,8 @@ dtrain = xgb.DMatrix( data, label=label) ``` * Build ```DMatrix``` from ```scipy.sparse``` ```python -csr = scipy.sparse.csr_matrix( (dat, (row,col)) ) -dtrain = xgb.DMatrix( csr ) +csr = scipy.sparse.csr_matrix((dat, (row, col))) +dtrain = xgb.DMatrix(csr) ``` * Saving ```DMatrix``` into XGBoost binary file will make loading faster in next time. The usage is like: ```python @@ -52,18 +47,17 @@ dtrain.save_binary("train.buffer") ``` * To handle missing value in ```DMatrix```, you can initialize the ```DMatrix``` like: ```python -dtrain = xgb.DMatrix( data, label=label, missing = -999.0) +dtrain = xgb.DMatrix(data, label=label, missing = -999.0) ``` * Weight can be set when needed, like ```python -w = np.random.rand(5,1) -dtrain = xgb.DMatrix( data, label=label, missing = -999.0, weight=w) +w = np.random.rand(5, 1) +dtrain = xgb.DMatrix(data, label=label, missing = -999.0, weight=w) ``` - -= -#### Setting Parameters -XGBoost use list of pair to save [parameters](parameter.md). Eg +Setting Parameters +------------------ +XGBoost use list of pair to save [parameters](../parameter.md). Eg * Booster parameters ```python param = {'bst:max_depth':2, 'bst:eta':1, 'silent':1, 'objective':'binary:logistic' } @@ -77,8 +71,9 @@ plst += [('eval_metric', 'ams@0')] evallist = [(dtest,'eval'), (dtrain,'train')] ``` -= -#### Training Model +Training +-------- + With parameter list and data, you are able to train a model. * Training ```python @@ -104,10 +99,11 @@ After you save your model, you can load model file at anytime by using bst = xgb.Booster({'nthread':4}) #init model bst.load_model("model.bin") # load data ``` -= -#### Early stopping -If you have a validation set, you can use early stopping to find the optimal number of boosting rounds. Early stopping requires at least one set in `evals`. If there's more than one, it will use the last. +Early Stopping +-------------- +If you have a validation set, you can use early stopping to find the optimal number of boosting rounds. +Early stopping requires at least one set in `evals`. If there's more than one, it will use the last. `train(..., evals=evals, early_stopping_rounds=10)` @@ -117,13 +113,14 @@ If early stopping occurs, the model will have two additional fields: `bst.best_s This works with both metrics to minimize (RMSE, log loss, etc.) and to maximize (MAP, NDCG, AUC). -= -#### Prediction +Prediction +---------- After you training/loading a model and preparing the data, you can start to do prediction. ```python -data = np.random.rand(7,10) # 7 entities, each contains 10 features -dtest = xgb.DMatrix( data, missing = -999.0 ) -ypred = bst.predict( xgmat ) +# 7 entities, each contains 10 features +data = np.random.rand(7, 10) +dtest = xgb.DMatrix(data) +ypred = bst.predict(xgmat) ``` If early stopping is enabled during training, you can predict with the best iteration. diff --git a/doc/sphinx_util.py b/doc/sphinx_util.py index 33c98d3815bc..0b51786301f6 100644 --- a/doc/sphinx_util.py +++ b/doc/sphinx_util.py @@ -1,50 +1,17 @@ # -*- coding: utf-8 -*- -"""Helper hacking utilty function for customization.""" +"""Helper utilty function for customization.""" import sys import os +import docutils import subprocess -# TODO: make less hacky way than this one if os.environ.get('READTHEDOCS', None) == 'True': - subprocess.call('cd ..; rm -rf recommonmark;' + + subprocess.call('cd ..; rm -rf recommonmark recom;' + 'git clone https://github.com/tqchen/recommonmark;' + - 'cp recommonmark/recommonmark/parser.py doc/parser', shell=True) + 'mv recommonmark/recommonmark recom', shell=True) sys.path.insert(0, os.path.abspath('..')) -import parser +from recom import parser, transform -class MarkdownParser(parser.CommonMarkParser): - github_doc_root = None - doc_suffix = set(['md', 'rst']) - - @staticmethod - def remap_url(url): - if MarkdownParser.github_doc_root is None or url is None: - return url - if url.startswith('#'): - return url - arr = url.split('#', 1) - ssuffix = arr[0].rsplit('.', 1) - - if len(ssuffix) == 2 and (ssuffix[-1] in MarkdownParser.doc_suffix - and arr[0].find('://') == -1): - arr[0] = ssuffix[0] + '.html' - return '#'.join(arr) - else: - if arr[0].find('://') == -1: - return MarkdownParser.github_doc_root + url - else: - return url - - def reference(self, block): - block.destination = remap_url(block.destination) - return super(MarkdownParser, self).reference(block) - -# inplace modify the function in recommonmark module to allow link remap -old_ref = parser.reference - -def reference(block): - block.destination = MarkdownParser.remap_url(block.destination) - return old_ref(block) - -parser.reference = reference +MarkdownParser = parser.CommonMarkParser +AutoStructify = transform.AutoStructify From 014fa02c6a78d76783435dca29c6c9aa49bd3b23 Mon Sep 17 00:00:00 2001 From: Tong He Date: Sun, 2 Aug 2015 19:03:44 -0700 Subject: [PATCH 094/126] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ac29ef7eb52b..56275a92d89a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ =========== [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Documentation Status](https://readthedocs.org/projects/xgboost/badge/?version=latest)](https://xgboost.readthedocs.org) +[![CRAN Status Badge](http://www.r-pkg.org/badges/version/xgboost)](http://cran.r-project.org/web/packages/xgboost) [![Gitter chat for developers at https://gitter.im/dmlc/xgboost](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dmlc/xgboost?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) An optimized general purpose gradient boosting library. The library is parallelized, and also provides an optimized distributed version. From f7bb8fc10fc4c381ad2f02d8f86e695d6e8e6e7a Mon Sep 17 00:00:00 2001 From: Tong He Date: Sun, 2 Aug 2015 19:04:32 -0700 Subject: [PATCH 095/126] Update README.md --- R-package/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R-package/README.md b/R-package/README.md index 294691416349..c92bc9b96229 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -1,6 +1,9 @@ R package for xgboost ===================== +[![CRAN Status Badge](http://www.r-pkg.org/badges/version/xgboost)](http://cran.r-project.org/web/packages/xgboost) +[![CRAN Downloads](http://cranlogs.r-pkg.org/badges/xgboost)](http://cran.rstudio.com/web/packages/xgboost/index.html) + Installation ------------ From bf94add99267b2fbac3ab471c19a19c23a65059f Mon Sep 17 00:00:00 2001 From: Tong He Date: Sun, 2 Aug 2015 19:09:33 -0700 Subject: [PATCH 096/126] Update faq.md --- doc/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq.md b/doc/faq.md index 5c985182af19..63f949fad3c2 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -58,4 +58,4 @@ xgboost support missing value by default Slightly different result between runs -------------------------------------- This could happen, due to non-determinism in floating point summation order and multi-threading. -Though the general accurac will usually remain the same. \ No newline at end of file +Though the general accuracy will usually remain the same. From 64dd1973b9ef8c608852b41a8383891ab6aacf6e Mon Sep 17 00:00:00 2001 From: muli Date: Mon, 3 Aug 2015 12:59:28 -0400 Subject: [PATCH 097/126] align logo with title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56275a92d89a..fb133ed49503 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - eXtreme Gradient Boosting + - eXtreme Gradient Boosting =========== [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Documentation Status](https://readthedocs.org/projects/xgboost/badge/?version=latest)](https://xgboost.readthedocs.org) From 81b1befd106475e824f195347c4795ee9df931f6 Mon Sep 17 00:00:00 2001 From: Ajinkya Kale Date: Mon, 3 Aug 2015 15:46:22 -0700 Subject: [PATCH 098/126] Adding dmlc stamp --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb133ed49503..35a977e25509 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ - - eXtreme Gradient Boosting + +eXtreme Gradient Boosting =========== [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Documentation Status](https://readthedocs.org/projects/xgboost/badge/?version=latest)](https://xgboost.readthedocs.org) @@ -9,7 +10,7 @@ An optimized general purpose gradient boosting library. The library is paralleli It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data -XGBoost is part of [Distributed Machine Learning Common](http://dmlc.github.io/) projects + XGBoost is part of [Distributed Machine Learning Common](http://dmlc.github.io/) projects Contents -------- From 7fe8b9583301542c6b0d4a29dd270f69040453ad Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Mon, 3 Aug 2015 19:36:29 -0700 Subject: [PATCH 099/126] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 35a977e25509..ef9eb9a53a77 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ - -eXtreme Gradient Boosting + eXtreme Gradient Boosting =========== [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Documentation Status](https://readthedocs.org/projects/xgboost/badge/?version=latest)](https://xgboost.readthedocs.org) @@ -10,7 +9,7 @@ An optimized general purpose gradient boosting library. The library is paralleli It implements machine learning algorithms under the [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting) framework, including [Generalized Linear Model](https://en.wikipedia.org/wiki/Generalized_linear_model) (GLM) and [Gradient Boosted Decision Trees](https://en.wikipedia.org/wiki/Gradient_boosting#Gradient_tree_boosting) (GBDT). XGBoost can also be [distributed](#features) and scale to Terascale data - XGBoost is part of [Distributed Machine Learning Common](http://dmlc.github.io/) projects +XGBoost is part of [Distributed Machine Learning Common](http://dmlc.github.io/) projects Contents -------- From 889887c2f18117b8541e586d85048f4c17e6107f Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Mon, 3 Aug 2015 19:37:33 -0700 Subject: [PATCH 100/126] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef9eb9a53a77..299524758ad6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - eXtreme Gradient Boosting + eXtreme Gradient Boosting =========== [![Build Status](https://travis-ci.org/dmlc/xgboost.svg?branch=master)](https://travis-ci.org/dmlc/xgboost) [![Documentation Status](https://readthedocs.org/projects/xgboost/badge/?version=latest)](https://xgboost.readthedocs.org) From 3d38ebbef57743083d7cf95f6de30884dae1f45e Mon Sep 17 00:00:00 2001 From: EricChanBD Date: Wed, 5 Aug 2015 06:19:54 +0800 Subject: [PATCH 101/126] fix SetCombine and SetPrune bug --- src/utils/quantile.h | 89 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/src/utils/quantile.h b/src/utils/quantile.h index ffd9142da547..677c4e12f6ed 100644 --- a/src/utils/quantile.h +++ b/src/utils/quantile.h @@ -173,14 +173,6 @@ struct WQSummary { } } } - /*! \brief used for debug purpose, print the summary */ - inline void Print(void) const { - for (size_t i = 0; i < size; ++i) { - std::cout << "x=" << data[i].value << "\t" - << "[" << data[i].rmin << "," << data[i].rmax << "]" - << " wmin=" << data[i].wmin << std::endl; - } - } /*! * \brief set current summary to be pruned summary of src * assume data field is already allocated to be at least maxsize @@ -226,6 +218,8 @@ struct WQSummary { */ inline void SetCombine(const WQSummary &sa, const WQSummary &sb) { + utils::Check(sa.Check("BeforeCombine A"), "Check Left error"); + utils::Check(sb.Check("BeforeCombine B"), "Check right error"); if (sa.size == 0) { this->CopyFrom(sb); return; } @@ -276,9 +270,74 @@ struct WQSummary { } while (b != b_end); } this->size = dst - data; + const RType tol = 10; + RType err_mingap, err_maxgap, err_wgap; + this->FixError(&err_mingap, &err_maxgap, &err_wgap); + if (err_mingap > tol || err_maxgap > tol || err_wgap > tol) { + utils::Printf("INFO: mingap=%g, maxgap=%g, wgap=%g\n", + err_mingap, err_maxgap, err_wgap); + } + + if (!this->Check("AfterCombine")) { + utils::Printf("-----Left-----\n"); + sa.Print(); + utils::Printf("-----Right-----\n"); + sb.Print(); + utils::Error("Error after combine\n"); + } utils::Assert(size <= sa.size + sb.size, "bug in combine"); } + // helper function to print the current content of sketch + inline void Print() const { + for (size_t i = 0; i < this->size; ++i) { + utils::Printf("[%lu] rmin=%g, rmax=%g, wmin=%g, v=%g\n", + i, data[i].rmin, data[i].rmax, + data[i].wmin, data[i].value); + } + } + // try to fix rounding error + // and re-establish invariance + inline void FixError(RType *err_mingap, + RType *err_maxgap, + RType *err_wgap) const { + *err_mingap = 0; + *err_maxgap = 0; + *err_wgap = 0; + RType prev_rmin = 0, prev_rmax = 0; + for (size_t i = 0; i < this->size; ++i) { + if (data[i].rmin < prev_rmin) { + data[i].rmin = prev_rmin; + *err_mingap = std::max(*err_mingap, prev_rmin - data[i].rmin); + } else { + prev_rmin = data[i].rmin; + } + if (data[i].rmax < prev_rmax) { + data[i].rmax = prev_rmax; + *err_maxgap = std::max(*err_maxgap, prev_rmax - data[i].rmax); + } + RType rmin_next = data[i].rmin_next(); + if (data[i].rmax < rmin_next) { + data[i].rmax = rmin_next; + *err_wgap = std::max(*err_wgap, data[i].rmax - rmin_next); + } + prev_rmax = data[i].rmax; + } + } + // check consistency of the summary + inline bool Check(const char *msg) const { + const float tol = 10.0f; + for (size_t i = 0; i < this->size; ++i) { + if (data[i].rmin + data[i].wmin > data[i].rmax + tol || + data[i].rmin < -1e-6f || data[i].rmax < -1e-6f) { + utils::Printf("----%s: Check not Pass------\n", msg); + this->Print(); + return false; + } + } + return true; + } }; + /*! \brief try to do efficient prunning */ template struct WXQSummary : public WQSummary { @@ -293,6 +352,7 @@ struct WXQSummary : public WQSummary { } // set prune inline void SetPrune(const WQSummary &src, size_t maxsize) { + utils::Check(src.Check("BeforePrune"), "Check src error"); if (src.size <= maxsize) { this->CopyFrom(src); return; } @@ -334,11 +394,7 @@ struct WXQSummary : public WQSummary { utils::Printf("LOG: srcsize=%lu, maxsize=%lu, range=%g, chunk=%g\n", src.size, maxsize, static_cast(range), static_cast(chunk)); - for (size_t i = 0; i < src.size; ++i) { - utils::Printf("[%lu] rmin=%g, rmax=%g, wmin=%g, v=%g, isbig=%d\n", i, - src.data[i].rmin, src.data[i].rmax, src.data[i].wmin, - src.data[i].value, CheckLarge(src.data[i], chunk)); - } + src.Print(); utils::Assert(nbig < n - 1, "quantile: too many large chunk"); } this->data[0] = src.data[0]; @@ -357,6 +413,12 @@ struct WXQSummary : public WQSummary { if (dx2 >= maxdx2) break; while (i < end && dx2 >= src.data[i + 1].rmax + src.data[i + 1].rmin) ++i; + if (i == end) { + utils::Printf("INFO: i==end reached, dx2=%g, i=%lu, end=%lu, mrange=%g, k=%lu, n=%lu, maxsize=%lu\n", + dx2, i, end, mrange, k, n, maxsize); + src.Print(); + break; + } if (dx2 < src.data[i].rmin_next() + src.data[i + 1].rmax_prev()) { if (i != lastidx) { this->data[this->size++] = src.data[i]; lastidx = i; @@ -377,6 +439,7 @@ struct WXQSummary : public WQSummary { begin += src.data[bid].rmin_next() - src.data[bid].rmax_prev(); } } + utils::Check(this->Check("AfterPrune"), "Check result error"); } }; /*! From 0f6ad749f5b1211e41db5d89ef053e2725931147 Mon Sep 17 00:00:00 2001 From: tqchen Date: Tue, 4 Aug 2015 19:40:30 -0700 Subject: [PATCH 102/126] remove debug messages fix lint --- src/utils/quantile.h | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/utils/quantile.h b/src/utils/quantile.h index 677c4e12f6ed..adcd0222de7d 100644 --- a/src/utils/quantile.h +++ b/src/utils/quantile.h @@ -218,8 +218,6 @@ struct WQSummary { */ inline void SetCombine(const WQSummary &sa, const WQSummary &sb) { - utils::Check(sa.Check("BeforeCombine A"), "Check Left error"); - utils::Check(sb.Check("BeforeCombine B"), "Check right error"); if (sa.size == 0) { this->CopyFrom(sb); return; } @@ -270,7 +268,7 @@ struct WQSummary { } while (b != b_end); } this->size = dst - data; - const RType tol = 10; + const RType tol = 10; RType err_mingap, err_maxgap, err_wgap; this->FixError(&err_mingap, &err_maxgap, &err_wgap); if (err_mingap > tol || err_maxgap > tol || err_wgap > tol) { @@ -278,13 +276,6 @@ struct WQSummary { err_mingap, err_maxgap, err_wgap); } - if (!this->Check("AfterCombine")) { - utils::Printf("-----Left-----\n"); - sa.Print(); - utils::Printf("-----Right-----\n"); - sb.Print(); - utils::Error("Error after combine\n"); - } utils::Assert(size <= sa.size + sb.size, "bug in combine"); } // helper function to print the current content of sketch @@ -335,7 +326,7 @@ struct WQSummary { } } return true; - } + } }; /*! \brief try to do efficient prunning */ @@ -352,7 +343,6 @@ struct WXQSummary : public WQSummary { } // set prune inline void SetPrune(const WQSummary &src, size_t maxsize) { - utils::Check(src.Check("BeforePrune"), "Check src error"); if (src.size <= maxsize) { this->CopyFrom(src); return; } @@ -413,12 +403,7 @@ struct WXQSummary : public WQSummary { if (dx2 >= maxdx2) break; while (i < end && dx2 >= src.data[i + 1].rmax + src.data[i + 1].rmin) ++i; - if (i == end) { - utils::Printf("INFO: i==end reached, dx2=%g, i=%lu, end=%lu, mrange=%g, k=%lu, n=%lu, maxsize=%lu\n", - dx2, i, end, mrange, k, n, maxsize); - src.Print(); - break; - } + if (i == end) break; if (dx2 < src.data[i].rmin_next() + src.data[i + 1].rmax_prev()) { if (i != lastidx) { this->data[this->size++] = src.data[i]; lastidx = i; @@ -439,7 +424,6 @@ struct WXQSummary : public WQSummary { begin += src.data[bid].rmin_next() - src.data[bid].rmax_prev(); } } - utils::Check(this->Check("AfterPrune"), "Check result error"); } }; /*! From b30aa96a8852f59d69e85b49b3b77859aa51159b Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Tue, 4 Aug 2015 20:14:58 -0700 Subject: [PATCH 103/126] Update xgboost_R.cpp --- R-package/src/xgboost_R.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index 37a30c797a43..0f40ad8484eb 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -39,7 +39,7 @@ double LogGamma(double v) { namespace random { void Seed(unsigned seed) { - warning("parameter seed is ignored, please set random seed using set.seed"); + //warning("parameter seed is ignored, please set random seed using set.seed"); } double Uniform(void) { return unif_rand(); From 752cf4c95d682a9a42508a3f7f82235a0bcc6290 Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Tue, 4 Aug 2015 22:56:16 -0700 Subject: [PATCH 104/126] Update xgboost_R.cpp --- R-package/src/xgboost_R.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R-package/src/xgboost_R.cpp b/R-package/src/xgboost_R.cpp index 0f40ad8484eb..1d426c496a0f 100644 --- a/R-package/src/xgboost_R.cpp +++ b/R-package/src/xgboost_R.cpp @@ -39,7 +39,7 @@ double LogGamma(double v) { namespace random { void Seed(unsigned seed) { - //warning("parameter seed is ignored, please set random seed using set.seed"); + // warning("parameter seed is ignored, please set random seed using set.seed"); } double Uniform(void) { return unif_rand(); From b3bffcef342bfed04910524f60a39d07499d1be1 Mon Sep 17 00:00:00 2001 From: terrytangyuan Date: Sun, 9 Aug 2015 22:15:02 -0400 Subject: [PATCH 105/126] fixed some typos in demos comments --- R-package/demo/create_sparse_matrix.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R-package/demo/create_sparse_matrix.R b/R-package/demo/create_sparse_matrix.R index 11de17a910e8..2fbf41772029 100644 --- a/R-package/demo/create_sparse_matrix.R +++ b/R-package/demo/create_sparse_matrix.R @@ -7,7 +7,7 @@ if (!require(vcd)) { } # According to its documentation, Xgboost works only on numbers. # Sometimes the dataset we have to work on have categorical data. -# A categorical variable is one which have a fixed number of values. By exemple, if for each observation a variable called "Colour" can have only "red", "blue" or "green" as value, it is a categorical variable. +# A categorical variable is one which have a fixed number of values. By example, if for each observation a variable called "Colour" can have only "red", "blue" or "green" as value, it is a categorical variable. # # In R, categorical variable is called Factor. # Type ?factor in console for more information. @@ -74,11 +74,11 @@ importance <- xgb.importance(sparse_matrix@Dimnames[[2]], 'xgb.model.dump') print(importance) # According to the matrix below, the most important feature in this dataset to predict if the treatment will work is the Age. The second most important feature is having received a placebo or not. The sex is third. Then we see our generated features (AgeDiscret). We can see that their contribution is very low (Gain column). -# Does these results make sense? +# Does these result make sense? # Let's check some Chi2 between each of these features and the outcome. print(chisq.test(df$Age, df$Y)) -# Pearson correlation between Age and illness disapearing is 35 +# Pearson correlation between Age and illness disappearing is 35 print(chisq.test(df$AgeDiscret, df$Y)) # Our first simplification of Age gives a Pearson correlation of 8. @@ -86,6 +86,6 @@ print(chisq.test(df$AgeDiscret, df$Y)) print(chisq.test(df$AgeCat, df$Y)) # The perfectly random split I did between young and old at 30 years old have a low correlation of 2. It's a result we may expect as may be in my mind > 30 years is being old (I am 32 and starting feeling old, this may explain that), but for the illness we are studying, the age to be vulnerable is not the same. Don't let your "gut" lower the quality of your model. In "data science", there is science :-) -# As you can see, in general destroying information by simplying it won't improve your model. Chi2 just demonstrates that. But in more complex cases, creating a new feature based on existing one which makes link with the outcome more obvious may help the algorithm and improve the model. The case studied here is not enough complex to show that. Check Kaggle forum for some challenging datasets. +# As you can see, in general destroying information by simplifying it won't improve your model. Chi2 just demonstrates that. But in more complex cases, creating a new feature based on existing one which makes link with the outcome more obvious may help the algorithm and improve the model. The case studied here is not enough complex to show that. Check Kaggle forum for some challenging datasets. # However it's almost always worse when you add some arbitrary rules. # Moreover, you can notice that even if we have added some not useful new features highly correlated with other features, the boosting tree algorithm have been able to choose the best one, which in this case is the Age. Linear model may not be that strong in these scenario. From 3dd40b9f37645b162b0388178928f311e3af6b67 Mon Sep 17 00:00:00 2001 From: Yuan Tang Date: Mon, 10 Aug 2015 20:35:10 -0400 Subject: [PATCH 106/126] fixed typos in basic_walkthrough demo --- R-package/demo/basic_walkthrough.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R-package/demo/basic_walkthrough.R b/R-package/demo/basic_walkthrough.R index 762a1c8e8386..532c5d873280 100644 --- a/R-package/demo/basic_walkthrough.R +++ b/R-package/demo/basic_walkthrough.R @@ -1,7 +1,7 @@ require(xgboost) require(methods) # we load in the agaricus dataset -# In this example, we are aiming to predict whether a mushroom can be eated +# In this example, we are aiming to predict whether a mushroom can be eaten data(agaricus.train, package='xgboost') data(agaricus.test, package='xgboost') train <- agaricus.train @@ -12,8 +12,8 @@ class(train$data) #-------------Basic Training using XGBoost----------------- # this is the basic usage of xgboost you can put matrix in data field -# note: we are puting in sparse matrix here, xgboost naturally handles sparse input -# use sparse matrix when your feature is sparse(e.g. when you using one-hot encoding vector) +# note: we are putting in sparse matrix here, xgboost naturally handles sparse input +# use sparse matrix when your feature is sparse(e.g. when you are using one-hot encoding vector) print("training xgboost with sparseMatrix") bst <- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nround = 2, nthread = 2, objective = "binary:logistic") @@ -22,7 +22,7 @@ print("training xgboost with Matrix") bst <- xgboost(data = as.matrix(train$data), label = train$label, max.depth = 2, eta = 1, nround = 2, nthread = 2, objective = "binary:logistic") -# you can also put in xgb.DMatrix object, stores label, data and other meta datas needed for advanced features +# you can also put in xgb.DMatrix object, which stores label, data and other meta datas needed for advanced features print("training xgboost with xgb.DMatrix") dtrain <- xgb.DMatrix(data = train$data, label = train$label) bst <- xgboost(data = dtrain, max.depth = 2, eta = 1, nround = 2, nthread = 2, @@ -72,7 +72,7 @@ print(paste("sum(abs(pred3-pred))=", sum(abs(pred2-pred)))) dtrain <- xgb.DMatrix(data = train$data, label=train$label) dtest <- xgb.DMatrix(data = test$data, label=test$label) #---------------Using watchlist---------------- -# watchlist is a list of xgb.DMatrix, each of them tagged with name +# watchlist is a list of xgb.DMatrix, each of them is tagged with name watchlist <- list(train=dtrain, test=dtest) # to train with watchlist, use xgb.train, which contains more advanced features # watchlist allows us to monitor the evaluation result on all data in the list From 7a3676851df3edd22dadd325972c17a951df02a3 Mon Sep 17 00:00:00 2001 From: John Wittenauer Date: Thu, 13 Aug 2015 20:32:47 -0400 Subject: [PATCH 107/126] Cleaned up guide-python directory. --- demo/guide-python/README.md | 3 ++- demo/guide-python/runall.sh | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/demo/guide-python/README.md b/demo/guide-python/README.md index ff1f98ad0d9d..d26b8fcf2b06 100644 --- a/demo/guide-python/README.md +++ b/demo/guide-python/README.md @@ -7,5 +7,6 @@ XGBoost Python Feature Walkthrough * [Generalized Linear Model](generalized_linear_model.py) * [Cross validation](cross_validation.py) * [Predicting leaf indices](predict_leaf_indices.py) -* [Sklearn Wrapper](sklearn_example.py) +* [Sklearn Wrapper](sklearn_examples.py) +* [Sklearn Parallel](sklearn_parallel.py) * [External Memory](external_memory.py) diff --git a/demo/guide-python/runall.sh b/demo/guide-python/runall.sh index 8f4f9832a6d4..5c8ddf93ce94 100755 --- a/demo/guide-python/runall.sh +++ b/demo/guide-python/runall.sh @@ -2,7 +2,11 @@ python basic_walkthrough.py python custom_objective.py python boost_from_prediction.py +python predict_first_ntree.py python generalized_linear_model.py python cross_validation.py python predict_leaf_indices.py +python sklearn_examples.py +python sklearn_parallel.py +python external_memory.py rm -rf *~ *.model *.buffer From d24b36adf99b3fc3a978a35649416353f66d1727 Mon Sep 17 00:00:00 2001 From: sinhrks Date: Tue, 11 Aug 2015 16:40:09 +0900 Subject: [PATCH 108/126] ENH: Add visualization to python package --- CHANGES.md | 1 + CONTRIBUTORS.md | 2 + doc/python/python_api.rst | 10 ++ doc/python/python_intro.md | 24 +++ python-package/xgboost/__init__.py | 4 +- python-package/xgboost/plotting.py | 227 +++++++++++++++++++++++++++++ scripts/travis_osx_install.sh | 2 +- scripts/travis_script.sh | 2 + tests/python/test_basic.py | 41 ++++++ 9 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 python-package/xgboost/plotting.py diff --git a/CHANGES.md b/CHANGES.md index d9c8786c0c2c..4484a321b5e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -40,6 +40,7 @@ on going at master * Fix List - Fixed possible problem of poisson regression for R. * Python module now throw exception instead of crash terminal when a parameter error happens. +* Python module now has importance plot and tree plot functions. * Java api is ready for use * Added more test cases and continuous integration to make each build more robust * Improvements in sklearn compatible module \ No newline at end of file diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 36ccc9d5daaf..ad6c01f2fd44 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -44,3 +44,5 @@ List of Contributors * [Jamie Hall](https://github.com/nerdcha) - Jamie is the initial creator of xgboost sklearn modue. * [Yen-Ying Lee](https://github.com/white1033) +* [Masaaki Horikoshi](https://github.com/sinhrks) + - Masaaki is the initial creator of xgboost python plotting module. \ No newline at end of file diff --git a/doc/python/python_api.rst b/doc/python/python_api.rst index 85249cbc4ead..1374e4bfc99a 100644 --- a/doc/python/python_api.rst +++ b/doc/python/python_api.rst @@ -35,3 +35,13 @@ Scikit-Learn API .. autoclass:: xgboost.XGBClassifier :members: :show-inheritance: + +Plotting API +------------ +.. automodule:: xgboost.plotting + +.. autofunction:: xgboost.plot_importance + +.. autofunction:: xgboost.plot_tree + +.. autofunction:: xgboost.to_graphviz diff --git a/doc/python/python_intro.md b/doc/python/python_intro.md index 2b670a053924..b46358877dd4 100644 --- a/doc/python/python_intro.md +++ b/doc/python/python_intro.md @@ -127,3 +127,27 @@ If early stopping is enabled during training, you can predict with the best iter ```python ypred = bst.predict(xgmat,ntree_limit=bst.best_iteration) ``` + +Plotting +-------- + +You can use plotting module to plot importance and output tree. + +To plot importance, use ``plot_importance``. This function requires ``matplotlib`` to be installed. + +```python +xgb.plot_importance(bst) +``` + +To output tree via ``matplotlib``, use ``plot_tree`` specifying ordinal number of the target tree. +This function requires ``graphviz`` and ``matplotlib``. + +```python +xgb.plot_tree(bst, num_trees=2) +``` + +When you use ``IPython``, you can use ``to_graphviz`` function which converts the target tree to ``graphviz`` instance. ``graphviz`` instance is automatically rendered on ``IPython``. + +```python +xgb.to_graphviz(bst, num_trees=2) +``` \ No newline at end of file diff --git a/python-package/xgboost/__init__.py b/python-package/xgboost/__init__.py index b284c27e0d90..b251b450119b 100644 --- a/python-package/xgboost/__init__.py +++ b/python-package/xgboost/__init__.py @@ -8,9 +8,11 @@ from .core import DMatrix, Booster from .training import train, cv from .sklearn import XGBModel, XGBClassifier, XGBRegressor +from .plotting import plot_importance, plot_tree, to_graphviz __version__ = '0.4' __all__ = ['DMatrix', 'Booster', 'train', 'cv', - 'XGBModel', 'XGBClassifier', 'XGBRegressor'] + 'XGBModel', 'XGBClassifier', 'XGBRegressor', + 'plot_importance', 'plot_tree', 'to_graphviz'] diff --git a/python-package/xgboost/plotting.py b/python-package/xgboost/plotting.py new file mode 100644 index 000000000000..7c34a11f17be --- /dev/null +++ b/python-package/xgboost/plotting.py @@ -0,0 +1,227 @@ +# coding: utf-8 +# pylint: disable=too-many-locals, too-many-arguments, invalid-name, +# pylint: disable=too-many-branches +"""Plotting Library.""" +from __future__ import absolute_import + +import re +import numpy as np +from .core import Booster + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + + +def plot_importance(booster, ax=None, height=0.2, + xlim=None, title='Feature importance', + xlabel='F score', ylabel='Features', + grid=True, **kwargs): + + """Plot importance based on fitted trees. + + Parameters + ---------- + booster : Booster or dict + Booster instance, or dict taken by Booster.get_fscore() + ax : matplotlib Axes, default None + Target axes instance. If None, new figure and axes will be created. + height : float, default 0.2 + Bar height, passed to ax.barh() + xlim : tuple, default None + Tuple passed to axes.xlim() + title : str, default "Feature importance" + Axes title. To disable, pass None. + xlabel : str, default "F score" + X axis title label. To disable, pass None. + ylabel : str, default "Features" + Y axis title label. To disable, pass None. + kwargs : + Other keywords passed to ax.barh() + + Returns + ------- + ax : matplotlib Axes + """ + + try: + import matplotlib.pyplot as plt + except ImportError: + raise ImportError('You must install matplotlib to plot importance') + + if isinstance(booster, Booster): + importance = booster.get_fscore() + elif isinstance(booster, dict): + importance = booster + else: + raise ValueError('tree must be Booster or dict instance') + + if len(importance) == 0: + raise ValueError('Booster.get_fscore() results in empty') + + tuples = [(k, importance[k]) for k in importance] + tuples = sorted(tuples, key=lambda x: x[1]) + labels, values = zip(*tuples) + + if ax is None: + _, ax = plt.subplots(1, 1) + + ylocs = np.arange(len(values)) + ax.barh(ylocs, values, align='center', height=height, **kwargs) + + for x, y in zip(values, ylocs): + ax.text(x + 1, y, x, va='center') + + ax.set_yticks(ylocs) + ax.set_yticklabels(labels) + + if xlim is not None: + if not isinstance(xlim, tuple) or len(xlim, 2): + raise ValueError('xlim must be a tuple of 2 elements') + else: + xlim = (0, max(values) * 1.1) + ax.set_xlim(xlim) + + if title is not None: + ax.set_title(title) + if xlabel is not None: + ax.set_xlabel(xlabel) + if ylabel is not None: + ax.set_ylabel(ylabel) + ax.grid(grid) + return ax + + +_NODEPAT = re.compile(r'(\d+):\[(.+)\]') +_LEAFPAT = re.compile(r'(\d+):(leaf=.+)') +_EDGEPAT = re.compile(r'yes=(\d+),no=(\d+),missing=(\d+)') + + +def _parse_node(graph, text): + """parse dumped node""" + match = _NODEPAT.match(text) + if match is not None: + node = match.group(1) + graph.node(node, label=match.group(2), shape='circle') + return node + match = _LEAFPAT.match(text) + if match is not None: + node = match.group(1) + graph.node(node, label=match.group(2), shape='box') + return node + raise ValueError('Unable to parse node: {0}'.format(text)) + + +def _parse_edge(graph, node, text, yes_color='#0000FF', no_color='#FF0000'): + """parse dumped edge""" + match = _EDGEPAT.match(text) + if match is not None: + yes, no, missing = match.groups() + if yes == missing: + graph.edge(node, yes, label='yes, missing', color=yes_color) + graph.edge(node, no, label='no', color=no_color) + else: + graph.edge(node, yes, label='yes', color=yes_color) + graph.edge(node, no, label='no, missing', color=no_color) + return + raise ValueError('Unable to parse edge: {0}'.format(text)) + + +def to_graphviz(booster, num_trees=0, rankdir='UT', + yes_color='#0000FF', no_color='#FF0000', **kwargs): + + """Convert specified tree to graphviz instance. IPython can automatically plot the + returned graphiz instance. Otherwise, you shoud call .render() method + of the returned graphiz instance. + + Parameters + ---------- + booster : Booster + Booster instance + num_trees : int, default 0 + Specify the ordinal number of target tree + rankdir : str, default "UT" + Passed to graphiz via graph_attr + yes_color : str, default '#0000FF' + Edge color when meets the node condigion. + no_color : str, default '#FF0000' + Edge color when doesn't meet the node condigion. + kwargs : + Other keywords passed to graphviz graph_attr + + Returns + ------- + ax : matplotlib Axes + """ + + try: + from graphviz import Digraph + except ImportError: + raise ImportError('You must install graphviz to plot tree') + + if not isinstance(booster, Booster): + raise ValueError('booster must be Booster instance') + + tree = booster.get_dump()[num_trees] + tree = tree.split() + + kwargs = kwargs.copy() + kwargs.update({'rankdir': rankdir}) + graph = Digraph(graph_attr=kwargs) + + for i, text in enumerate(tree): + if text[0].isdigit(): + node = _parse_node(graph, text) + else: + if i == 0: + # 1st string must be node + raise ValueError('Unable to parse given string as tree') + _parse_edge(graph, node, text, yes_color=yes_color, + no_color=no_color) + + return graph + + +def plot_tree(booster, num_trees=0, rankdir='UT', ax=None, **kwargs): + """Plot specified tree. + + Parameters + ---------- + booster : Booster + Booster instance + num_trees : int, default 0 + Specify the ordinal number of target tree + rankdir : str, default "UT" + Passed to graphiz via graph_attr + ax : matplotlib Axes, default None + Target axes instance. If None, new figure and axes will be created. + kwargs : + Other keywords passed to to_graphviz + + Returns + ------- + ax : matplotlib Axes + + """ + + try: + import matplotlib.pyplot as plt + import matplotlib.image as image + except ImportError: + raise ImportError('You must install matplotlib to plot tree') + + + if ax is None: + _, ax = plt.subplots(1, 1) + + g = to_graphviz(booster, num_trees=num_trees, rankdir=rankdir, **kwargs) + + s = StringIO() + s.write(g.pipe(format='png')) + s.seek(0) + img = image.imread(s) + + ax.imshow(img) + ax.axis('off') + return ax diff --git a/scripts/travis_osx_install.sh b/scripts/travis_osx_install.sh index 8121afd6b88a..9f3f6e83100a 100755 --- a/scripts/travis_osx_install.sh +++ b/scripts/travis_osx_install.sh @@ -7,7 +7,7 @@ fi brew update if [ ${TASK} == "python-package" ]; then - brew install python git + brew install python git graphviz easy_install pip pip install numpy scipy nose fi diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index 402cb69927d2..d58eaa5de895 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -34,6 +34,8 @@ if [ ${TASK} == "R-package" ]; then fi if [ ${TASK} == "python-package" ]; then + sudo apt-get install graphviz + sudo pip install matplotlib graphviz make all CXX=${CXX} || exit -1 nosetests tests/python || exit -1 fi diff --git a/tests/python/test_basic.py b/tests/python/test_basic.py index 77d19595b53f..2ed1cc4620f9 100644 --- a/tests/python/test_basic.py +++ b/tests/python/test_basic.py @@ -29,3 +29,44 @@ def test_basic(): # assert they are the same assert np.sum(np.abs(preds2-preds)) == 0 +def test_plotting(): + bst2 = xgb.Booster(model_file='xgb.model') + # plotting + + from matplotlib.axes import Axes + from graphviz import Digraph + + ax = xgb.plot_importance(bst2) + assert isinstance(ax, Axes) + assert ax.get_title() == 'Feature importance' + assert ax.get_xlabel() == 'F score' + assert ax.get_ylabel() == 'Features' + assert len(ax.patches) == 4 + + ax = xgb.plot_importance(bst2, color='r', + title='t', xlabel='x', ylabel='y') + assert isinstance(ax, Axes) + assert ax.get_title() == 't' + assert ax.get_xlabel() == 'x' + assert ax.get_ylabel() == 'y' + assert len(ax.patches) == 4 + for p in ax.patches: + assert p.get_facecolor() == (1.0, 0, 0, 1.0) # red + + + ax = xgb.plot_importance(bst2, color=['r', 'r', 'b', 'b'], + title=None, xlabel=None, ylabel=None) + assert isinstance(ax, Axes) + assert ax.get_title() == '' + assert ax.get_xlabel() == '' + assert ax.get_ylabel() == '' + assert len(ax.patches) == 4 + assert ax.patches[0].get_facecolor() == (1.0, 0, 0, 1.0) # red + assert ax.patches[1].get_facecolor() == (1.0, 0, 0, 1.0) # red + assert ax.patches[2].get_facecolor() == (0, 0, 1.0, 1.0) # blue + assert ax.patches[3].get_facecolor() == (0, 0, 1.0, 1.0) # blue + + g = xgb.to_graphviz(bst2, num_trees=0) + assert isinstance(g, Digraph) + ax = xgb.plot_tree(bst2, num_trees=0) + assert isinstance(ax, Axes) From 70e230815b8d966b6a29c37fa63c2bb8b447b5d6 Mon Sep 17 00:00:00 2001 From: phunterlau Date: Thu, 20 Aug 2015 01:26:17 -0700 Subject: [PATCH 109/126] add necessary configrations for pip installation --- Makefile | 25 ++++++++++++++++++++++++ python-package/MANIFEST.in | 7 +++++++ python-package/setup.cfg | 2 ++ python-package/setup.py | 27 +++++++++++++++++++++++--- python-package/xgboost/build-python.sh | 26 +++++++++++++++++++++++++ python-package/xgboost/core.py | 3 ++- 6 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 python-package/MANIFEST.in create mode 100644 python-package/setup.cfg create mode 100755 python-package/xgboost/build-python.sh diff --git a/Makefile b/Makefile index c9e35e80c545..c790f6b726ee 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ export CC = gcc +#build on the fly export CXX = g++ export MPICXX = mpicxx export LDFLAGS= -pthread -lm @@ -167,6 +168,30 @@ Rcheck: make Rbuild R CMD check --as-cran xgboost*.tar.gz +pythonpack: + #make clean + cd subtree/rabit;make clean;cd .. + rm -rf xgboost-deploy xgboost*.tar.gz + cp -r python-package xgboost-deploy + cp *.md xgboost-deploy/ + cp LICENSE xgboost-deploy/ + cp Makefile xgboost-deploy/xgboost + cp -r wrapper xgboost-deploy/xgboost + cp -r subtree xgboost-deploy/xgboost + cp -r multi-node xgboost-deploy/xgboost + cp -r windows xgboost-deploy/xgboost + cp -r src xgboost-deploy/xgboost + + #make python + +pythonbuild: + make pythonpack + python setup.py install + +pythoncheck: + make pythonbuild + python -c 'import xgboost;print xgboost.core.find_lib_path()' + # lint requires dmlc to be in current folder lint: dmlc-core/scripts/lint.py xgboost $(LINT_LANG) src wrapper R-package python-package diff --git a/python-package/MANIFEST.in b/python-package/MANIFEST.in new file mode 100644 index 000000000000..2d93429a9ff6 --- /dev/null +++ b/python-package/MANIFEST.in @@ -0,0 +1,7 @@ +include *.sh *.md +recursive-include xgboost * +recursive-include xgboost/wrapper * +recursive-include xgboost/windows * +recursive-include xgboost/subtree * +recursive-include xgboost/src * +recursive-include xgboost/multi-node * diff --git a/python-package/setup.cfg b/python-package/setup.cfg new file mode 100644 index 000000000000..b88034e414bc --- /dev/null +++ b/python-package/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md diff --git a/python-package/setup.py b/python-package/setup.py index 42e39f3bad5b..e33e28fdcbf1 100644 --- a/python-package/setup.py +++ b/python-package/setup.py @@ -2,20 +2,41 @@ """Setup xgboost package.""" from __future__ import absolute_import import sys -from setuptools import setup +from setuptools import setup,find_packages +import subprocess sys.path.insert(0, '.') +#build on the fly + +build_sh = subprocess.Popen(['sh','xgboost/build-python.sh']) +build_sh.wait() +output = build_sh.communicate() +print output + import xgboost LIB_PATH = xgboost.core.find_lib_path() +#print LIB_PATH +#to deploy to pip, please use +#make pythonpack +#python setup.py register sdist upload +#and be sure to test it firstly using "python setup.py register sdist upload -r pypitest" setup(name='xgboost', version=xgboost.__version__, + #version='0.4a12', description=xgboost.__doc__, install_requires=[ 'numpy', 'scipy', ], + maintainer = 'Hongliang Liu', + maintainer_email = 'phunter.lau@gmail.com', zip_safe=False, - packages=['xgboost'], - data_files=[('xgboost', [LIB_PATH[0]])], + packages = find_packages(), + #package_dir = {'':'xgboost'}, #don't need this and don't use this, give everything to MANIFEST.in + #package_data = {'': ['*.txt','*.md','*.sh'], + # } + include_package_data=True, #this will use MANIFEST.in during install where we specify additional files, this is the golden line + data_files=[('xgboost',LIB_PATH), + ], url='https://github.com/dmlc/xgboost') diff --git a/python-package/xgboost/build-python.sh b/python-package/xgboost/build-python.sh new file mode 100755 index 000000000000..398b076b819d --- /dev/null +++ b/python-package/xgboost/build-python.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# This is a simple script to make xgboost in MAC and Linux for python wrapper only +# Basically, it first try to make with OpenMP, if fails, disable OpenMP and make it again. +# This will automatically make xgboost for MAC users who don't have OpenMP support. +# In most cases, type make will give what you want. + +# See additional instruction in doc/build.md + +# note: this script is build for python package only, and it might have some filename +# conflict with build.sh which is for everything. + + +pushd xgboost +if make python; then + echo "Successfully build multi-thread xgboost" +else + echo "-----------------------------" + echo "Building multi-thread xgboost failed" + echo "Start to build single-thread xgboost" + make clean + make python no_omp=1 + echo "Successfully build single-thread xgboost" + echo "If you want multi-threaded version" + echo "See additional instructions in doc/build.md" +fi +popd diff --git a/python-package/xgboost/core.py b/python-package/xgboost/core.py index 0849d276cf9e..ff95b33fbfe2 100644 --- a/python-package/xgboost/core.py +++ b/python-package/xgboost/core.py @@ -39,7 +39,8 @@ def find_lib_path(): List of all found library path to xgboost """ curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) - dll_path = [curr_path, os.path.join(curr_path, '../../wrapper/')] + #make pythonpack hack: copy this directory one level upper for setup.py + dll_path = [curr_path, os.path.join(curr_path, '../../wrapper/'),os.path.join(curr_path, './wrapper/')] if os.name == 'nt': if platform.architecture()[0] == '64bit': dll_path.append(os.path.join(curr_path, '../../windows/x64/Release/')) From db444c4a08469337e7328b27fa689a947142275f Mon Sep 17 00:00:00 2001 From: phunterlau Date: Thu, 20 Aug 2015 10:10:34 -0700 Subject: [PATCH 110/126] update with comments on PR #450, fixed styles and updated CHANGES and CONTRIBUTORS --- CHANGES.md | 3 ++- CONTRIBUTORS.md | 4 +++- python-package/setup.py | 20 +++++++++++--------- python-package/xgboost/core.py | 3 ++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4484a321b5e3..a8ddcd7ea577 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -43,4 +43,5 @@ on going at master * Python module now has importance plot and tree plot functions. * Java api is ready for use * Added more test cases and continuous integration to make each build more robust -* Improvements in sklearn compatible module \ No newline at end of file +* Improvements in sklearn compatible module +* Added pip installation functionality for python module diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ad6c01f2fd44..6ae79f795aee 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -45,4 +45,6 @@ List of Contributors - Jamie is the initial creator of xgboost sklearn modue. * [Yen-Ying Lee](https://github.com/white1033) * [Masaaki Horikoshi](https://github.com/sinhrks) - - Masaaki is the initial creator of xgboost python plotting module. \ No newline at end of file + - Masaaki is the initial creator of xgboost python plotting module. +* [Hongliang Liu](https://github.com/phunterlau) + - Hongliang is the maintainer of xgboost python PyPI package for pip installation. diff --git a/python-package/setup.py b/python-package/setup.py index e33e28fdcbf1..a446983bef1d 100644 --- a/python-package/setup.py +++ b/python-package/setup.py @@ -2,12 +2,12 @@ """Setup xgboost package.""" from __future__ import absolute_import import sys -from setuptools import setup,find_packages +from setuptools import setup, find_packages import subprocess sys.path.insert(0, '.') #build on the fly -build_sh = subprocess.Popen(['sh','xgboost/build-python.sh']) +build_sh = subprocess.Popen(['sh', 'xgboost/build-python.sh']) build_sh.wait() output = build_sh.communicate() print output @@ -29,14 +29,16 @@ 'numpy', 'scipy', ], - maintainer = 'Hongliang Liu', - maintainer_email = 'phunter.lau@gmail.com', + maintainer='Hongliang Liu', + maintainer_email='phunter.lau@gmail.com', zip_safe=False, - packages = find_packages(), - #package_dir = {'':'xgboost'}, #don't need this and don't use this, give everything to MANIFEST.in + packages=find_packages(), + #don't need this and don't use this, give everything to MANIFEST.in + #package_dir = {'':'xgboost'}, #package_data = {'': ['*.txt','*.md','*.sh'], # } - include_package_data=True, #this will use MANIFEST.in during install where we specify additional files, this is the golden line - data_files=[('xgboost',LIB_PATH), - ], + #this will use MANIFEST.in during install where we specify additional files, + #this is the golden line + include_package_data=True, + data_files=[('xgboost', LIB_PATH)], url='https://github.com/dmlc/xgboost') diff --git a/python-package/xgboost/core.py b/python-package/xgboost/core.py index ff95b33fbfe2..85b6a1818a18 100644 --- a/python-package/xgboost/core.py +++ b/python-package/xgboost/core.py @@ -40,7 +40,8 @@ def find_lib_path(): """ curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) #make pythonpack hack: copy this directory one level upper for setup.py - dll_path = [curr_path, os.path.join(curr_path, '../../wrapper/'),os.path.join(curr_path, './wrapper/')] + dll_path = [curr_path, os.path.join(curr_path, '../../wrapper/') + , os.path.join(curr_path, './wrapper/')] if os.name == 'nt': if platform.architecture()[0] == '64bit': dll_path.append(os.path.join(curr_path, '../../windows/x64/Release/')) From 5e81a210ce811bc769d208b483aa9d1e2840db71 Mon Sep 17 00:00:00 2001 From: phunterlau Date: Thu, 20 Aug 2015 12:33:28 -0700 Subject: [PATCH 111/126] polish README.md with more information for PR #450 --- python-package/README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/python-package/README.md b/python-package/README.md index a4ac71d4d233..20d60986401a 100644 --- a/python-package/README.md +++ b/python-package/README.md @@ -1,7 +1,26 @@ XGBoost Python Package ====================== +Installation +------------ +We are on [PyPI](https://pypi.python.org/pypi/xgboost) now. For stable version, please install using pip: + +* ```pip install xgboost``` +* Note for windows users: this pip installation may not work on some windows environment. Please install from github if pip doesn't work on windows. + +For up-to-date version, please install from github. + * To make the python module, type ```./build.sh``` in the root directory of project * Make sure you have [setuptools](https://pypi.python.org/pypi/setuptools) * Install with `python setup.py install` from this directory. + +Examples +------ + * Refer also to the walk through example in [demo folder](../demo/guide-python) -* **NOTE**: if you want to run XGBoost process in parallel using the fork backend for joblib/multiprocessing, you must build XGBoost without support for OpenMP by `make no_omp=1`. Otherwise, use the forkserver (in Python 3.4) or spawn backend. See the sklearn_parallel.py demo. +* See also the [example scripts](../demo/kaggle-higgs) for Kaggle Higgs Challenge, including [speedtest script](../demo/kaggle-higgs/speedtest.py) on this dataset. + +Note +----- + +* If you want to build xgboost on Mac OS X with multiprocessing support where clang in XCode by default doesn't support, please install gcc 4.9 or higher using [homebrew](http://brew.sh/) ```brew tap homebrew/versions; brew install gcc49``` +* If you want to run XGBoost process in parallel using the fork backend for joblib/multiprocessing, you must build XGBoost without support for OpenMP by `make no_omp=1`. Otherwise, use the forkserver (in Python 3.4) or spawn backend. See the [sklearn_parallel.py](../demo/guide-python/sklearn_parallel.py) demo. From 07182444d26a77597a5f6ec9001c34d4cdf6749c Mon Sep 17 00:00:00 2001 From: Tong He Date: Thu, 20 Aug 2015 13:53:20 -0700 Subject: [PATCH 112/126] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 299524758ad6..121462d3851c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ What's New ---------- * XGBoost helps Chenglong Chen to win [Kaggle CrowdFlower Competition](https://www.kaggle.com/c/crowdflower-search-relevance) - Check out the [winning solution](doc/README.md#highlight-links) + Check out the [winning solution](https://github.com/ChenglongChen/Kaggle_CrowdFlower) * XGBoost-0.4 release, see [CHANGES.md](CHANGES.md#xgboost-04) * XGBoost helps three champion teams to win [WWW2015 Microsoft Malware Classification Challenge (BIG 2015)](http://www.kaggle.com/c/malware-classification/forums/t/13490/say-no-to-overfitting-approaches-sharing) Check out the [winning solution](doc/README.md#highlight-links) From 10273a0288854184443c34d8936a6fb4742287ab Mon Sep 17 00:00:00 2001 From: VGuette Date: Sun, 23 Aug 2015 11:01:43 +0200 Subject: [PATCH 113/126] Update setup.py --- python-package/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/setup.py b/python-package/setup.py index a446983bef1d..d8c0df0ee9f5 100644 --- a/python-package/setup.py +++ b/python-package/setup.py @@ -10,7 +10,7 @@ build_sh = subprocess.Popen(['sh', 'xgboost/build-python.sh']) build_sh.wait() output = build_sh.communicate() -print output +print (output) import xgboost From 00702dc39b18ec7b74d3b7e8bf1a55b2dee75126 Mon Sep 17 00:00:00 2001 From: sinhrks Date: Sun, 23 Aug 2015 13:37:29 +0900 Subject: [PATCH 114/126] Fix for python 3 --- .travis.yml | 4 +--- python-package/xgboost/plotting.py | 8 ++------ scripts/travis_osx_install.sh | 8 +++++++- scripts/travis_script.sh | 21 ++++++++++++++++++++- tests/python/test_basic.py | 3 +++ 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10f2170386e6..5e10c3360e37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ env: - TASK=lint LINT_LANG=python - TASK=R-package CXX=g++ - TASK=python-package CXX=g++ + - TASK=python-package3 CXX=g++ - TASK=java-package CXX=g++ - TASK=build CXX=g++ - TASK=build-with-dmlc CXX=g++ @@ -29,9 +30,6 @@ addons: - wget - libcurl4-openssl-dev - unzip - - python-numpy - - python-scipy - - python-nose before_install: - scripts/travis_osx_install.sh diff --git a/python-package/xgboost/plotting.py b/python-package/xgboost/plotting.py index 7c34a11f17be..9c9b2a97d189 100644 --- a/python-package/xgboost/plotting.py +++ b/python-package/xgboost/plotting.py @@ -8,11 +8,7 @@ import numpy as np from .core import Booster -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - +from io import BytesIO def plot_importance(booster, ax=None, height=0.2, xlim=None, title='Feature importance', @@ -217,7 +213,7 @@ def plot_tree(booster, num_trees=0, rankdir='UT', ax=None, **kwargs): g = to_graphviz(booster, num_trees=num_trees, rankdir=rankdir, **kwargs) - s = StringIO() + s = BytesIO() s.write(g.pipe(format='png')) s.seek(0) img = image.imread(s) diff --git a/scripts/travis_osx_install.sh b/scripts/travis_osx_install.sh index 9f3f6e83100a..adc620a52922 100755 --- a/scripts/travis_osx_install.sh +++ b/scripts/travis_osx_install.sh @@ -9,5 +9,11 @@ brew update if [ ${TASK} == "python-package" ]; then brew install python git graphviz easy_install pip - pip install numpy scipy nose + pip install numpy scipy matplotlib nose +fi + +if [ ${TASK} == "python-package3" ]; then + brew install python3 git graphviz + sudo pip3 install --upgrade setuptools + pip3 install numpy scipy matplotlib nose graphviz fi diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index d58eaa5de895..c5708b0c8b9e 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -35,11 +35,30 @@ fi if [ ${TASK} == "python-package" ]; then sudo apt-get install graphviz - sudo pip install matplotlib graphviz + sudo apt-get install python-numpy python-scipy python-matplotlib python-nose + sudo python -m pip install graphviz make all CXX=${CXX} || exit -1 nosetests tests/python || exit -1 fi +if [ ${TASK} == "python-package3" ]; then + sudo apt-get install graphviz + # python3-matplotlib is unavailale on Ubuntu 12.04 + sudo apt-get install python3-dev + sudo apt-get install python3-numpy python3-scipy python3-nose python3-setuptools + + make all CXX=${CXX} || exit -1 + + if [ ${TRAVIS_OS_NAME} != "osx" ]; then + sudo easy_install3 pip + sudo easy_install3 -U distribute + sudo pip install graphviz matplotlib + nosetests3 tests/python || exit -1 + else + nosetests tests/python || exit -1 + fi +fi + # only test java under linux for now if [ ${TASK} == "java-package" ]; then if [ ${TRAVIS_OS_NAME} != "osx" ]; then diff --git a/tests/python/test_basic.py b/tests/python/test_basic.py index 2ed1cc4620f9..93ebaa7fdadf 100644 --- a/tests/python/test_basic.py +++ b/tests/python/test_basic.py @@ -33,6 +33,9 @@ def test_plotting(): bst2 = xgb.Booster(model_file='xgb.model') # plotting + import matplotlib + matplotlib.use('Agg') + from matplotlib.axes import Axes from graphviz import Digraph From d5d48560a7f400c5baa3ea30e0ce11ba8aa44e2e Mon Sep 17 00:00:00 2001 From: hetong007 Date: Sun, 23 Aug 2015 16:25:28 -0700 Subject: [PATCH 115/126] add model description --- doc/model.md | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 doc/model.md diff --git a/doc/model.md b/doc/model.md new file mode 100644 index 000000000000..1a67a696235b --- /dev/null +++ b/doc/model.md @@ -0,0 +1,188 @@ +Introduction to the Model of XGBoost +========================= + +## The Origin + +XGBoost is short for "Extreme Gradient Boosting", where the term "Gradient Boosting" is proposed in the paper _Greedy Function Approximation: A Gradient Boosting Machine_, Friedman. Based on this original model, we incoporated several modifications to make it faster and more robust. + +## The General Problem + +### Supervised Model + +XGBoost is used for supervised learning problems, where we use the training data ``$ x_i $`` to predict a target variable ``$ y_i $``. Our model is a mathematical structure that captures the pattern from the training data. Given the structure, we need to learn the best parameters ``$ \Theta $`` in the model. + +### Loss Function + +Based on different understanding or assumption of ``$ y_i $``, we can have different problems as regression, classification, ordering, etc. To model different problems, we use a so-called `loss function` to describe how good is our model's performance. The function usually takes two parameters: the true value ``$ y_i $`` and the prediction ``$ \hat{y}_i $``. For example, we can use Rooted Mean Squared Error (RMSE) + +`` `math +l(y_i, \hat{y}_i) = (y_i-\hat{y}_i)^2 +`` ` + +for a regression problem, and logistic loss function + +`` `math +l(y_i, \hat{y}_i) = y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i}) +`` ` + +for a classification problem. + +### Regularization + +Besides we need to control the complexity of our model. A model achieving a perfect loss function score on the training dataset is overfitting it, which means it not only captures the useful pattern, but also the outliers, noise and the specific pattern in the training data. Controlling the complexity can make the model focus on more important and general pattern rather than the unnecessary details. + +### Optimize the Objective + +Combining the loss function and the regularization, we have our objective for the supervised learning model as + +`` `math +Obj(\Theta) = L(\Theta) + \Omega(\Theta) +`` ` + +where ``$ L $`` is the loss function, and ``$ \Omega $`` is the regularization term. The first one is making our model being accurate, while the second one is preventing our model being overfitting. We want to have a balance between these two parts when optimizing the objective. The optimization algorithm depends on the structure of our model. The following content will introduce the details. + +## Boosting Trees Model + +### Classification and Regression Tree + +The boosting trees model is a set of classification and regression trees. Here's a simple example of such a model: + +![CART]() + +We classify the members in thie family into different leaves, and assign them the score on corresponding leaf. + +### Tree Ensemble + +However a single CART model is not so strong in practice. How about predict with more trees? + +![TwoCART]() + +Now we are predicting with two trees, by predict on each tree individually and then sum the scores up. Mathematically, we can write our model into the form + +`` `math +\hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in F +`` ` + +where ``$ f $`` is a function in the functional space ``$ F $``, and ``$ F $`` is the set of all possible CARTs. Therefore our objective to optimize can be written as + +`` `math +obj(\Theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) +`` ` + +### Additive Training + +It is not easy to train all the trees at once. Instead, we use the strategy to train them in a sequence so that everytime we train one CART and add it to the model. We note the prediction value at step `t` by ``$ \hat{y}_i^{(t)}$``, so we have + +`` `math +\hat{y}_i^{(0)} = 0\\ +\hat{y}_i^{(1)} = f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ +\hat{y}_i^{(2)} = f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ +\dots\\ +\hat{y}_i^{(t)} = \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) +`` ` + +Which CART do we want at each step? Of course we want to add the one that minimize our objective. + +`` `math +Obj^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ + & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + constant +`` ` + +Let's consider using RMSE as our loss function + +`` `math +Obj^{(t)} & = \sum_{i=1}^n (y_i - (\hat{y}_i^{(t-1)} + f_t(x_i)))^2 + \sum_{i=1}^t\Omega(f_i) \\ + & = \sum_{i=1}^n [2(\hat{y}_i^{(t-1)} - y_i)f_t(x_i) + f_t(x_i)^2] + \Omega(f_t) + constant +`` ` + +The form of RMSE is friendly. But other loss functions could be tricky to expand. For convenience we calculate the Taylor expansion of the loss function up to the second order + +`` `math +Obj^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + constant +`` ` + +where + +`` `math +g_i &= \partial_{\hat{y}_i^{(t)}} l(y_i, \hat{y}_i^{(t-1)})\\ +h_i &= \partial_{\hat{y}_i^{(t)}}^2 l(y_i, \hat{y}_i^{(t-1)}) +`` ` + +So we can remove all the constant at the t-th step and the specific objective is + +`` `math +\sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) +`` ` + +One of the benifit of this definition is as long as the loss function has the first and second order derivative, we can optimized every loss function within the same framework. + +### Model Complexity + +We have introduced the details in the loss function, next we talk about the regularization term. We want to control the complexity of a tree, thus we need to define it first. We define a tree ``$ f(x) $`` as + +`` `math +f_t(x) = w_{q(x)}, w\inR^T, q:R^d\rightarrow \{1,2,\cdots,T\} +`` ` + +where ``$ w $`` is the vector of scores on leaves, ``$ q $`` is a function assigning each data point to the corresponding leaf and ``$ T $`` is the number of leaves. In XGBoost, we define the complexity as + +`` `math +\Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 +`` ` + +It is possible to define other form of regularization terms, but this one works well in practice. + +### Get the best score on leaf + +Now we have the objective value with the ``$ t $``-th tree added: + +`` `math +Obj^{(t)} &\approx \sum_{i=1}^n [g_i w_q(x_i) + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ +&= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T +`` ` + +where ``$ I_j = \{i|q(x_i)=j\} $`` is the set of indices of data points assigned to the ``$ j $``-th leaf. Notice that in the second line we have change the index of the summation because all the data points on the same leaf get the same score. We could further compress the expression by defining ``$ G_j = \sum_{i\in I_j} g_i $`` and ``$ H_j = \sum_{i\in I_j} h_i $``: + +`` `math +Obj^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T +`` ` + +In this equation ``$ w_j $`` are independent to each other, the form ``$ G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2 $`` is quadratic and the best ``$ w_j $`` to minimize it can be solved deterministically: + +`` `math +w_j^\ast = -\frac{G_j}{H_j+\lambda}\\ +Obj = -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T +`` ` + +**Therefore, given the parameters, the gradients and the structure of the tree, we know how to set the score on each leaf.** + +### Learn the tree structure + +Our algorithm aims at optimizing the objective, so it also guides us to a good tree structure. We score the structure by ``$ Obj^{(t)} $`` which is mentioned just above. Since we can evaluate the tree, ideally we can enumerate all possible trees and pick the best one. In practice it is impossible, so we enumerate all the trees no deeper than a certain depth greedily. + +Specifically we try to split a leaf into two leaves, and the score it gains is + +`` `math +Gain = \frac{1}{2} [\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}] - \gamma +`` ` + +This formula can be decomposited as 1) the score on the new left leaf 2) the score on the new right leaf 3) The score on the original leaf 4) regularization on the additional leaf. + +The regularization in the end can be seen as the minimum increment from this split. In the end, we will prune out the split with a negative gain. + + + + + + + + + + + + + + + + + From 51964583059f82a442e6065ac5c9d98eca5a981b Mon Sep 17 00:00:00 2001 From: hetong007 Date: Sun, 23 Aug 2015 16:28:24 -0700 Subject: [PATCH 116/126] add plot --- doc/img/cart.png | Bin 0 -> 179620 bytes doc/img/twocart.png | Bin 0 -> 102756 bytes doc/model.md | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 doc/img/cart.png create mode 100644 doc/img/twocart.png diff --git a/doc/img/cart.png b/doc/img/cart.png new file mode 100644 index 0000000000000000000000000000000000000000..aaaa9ddbec4e35dd97444329f4f21b639e93d714 GIT binary patch literal 179620 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 literal 0 HcmV?d00001 diff --git a/doc/img/twocart.png b/doc/img/twocart.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c0dae7c0f4f7e043ba989bd8dedbae157ff56c GIT binary patch literal 102756 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

-z0wnML^{>r%`sr=R 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

=)FBYW%uBYU|2qFFY))DOyHQjS>1eZ+8w|! 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 D+`v84 literal 0 HcmV?d00001 diff --git a/doc/model.md b/doc/model.md index 1a67a696235b..aa42f727204c 100644 --- a/doc/model.md +++ b/doc/model.md @@ -47,7 +47,7 @@ where ``$ L $`` is the loss function, and ``$ \Omega $`` is the regularization t The boosting trees model is a set of classification and regression trees. Here's a simple example of such a model: -![CART]() +![CART](img/cart.png) We classify the members in thie family into different leaves, and assign them the score on corresponding leaf. @@ -55,7 +55,7 @@ We classify the members in thie family into different leaves, and assign them th However a single CART model is not so strong in practice. How about predict with more trees? -![TwoCART]() +![TwoCART](img/twocart.png) Now we are predicting with two trees, by predict on each tree individually and then sum the scores up. Mathematically, we can write our model into the form From 30c30d36960f41ab62f2e82c8faf37f2a1733dfa Mon Sep 17 00:00:00 2001 From: hetong007 Date: Sun, 23 Aug 2015 16:56:57 -0700 Subject: [PATCH 117/126] modify model doc --- doc/index.md | 1 + doc/model.md | 70 ++++++++++++++++++++++++++-------------------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/doc/index.md b/doc/index.md index 7c41d15e240d..cdf1b12a2049 100644 --- a/doc/index.md +++ b/doc/index.md @@ -52,6 +52,7 @@ User Guide ---------- * [Frequently Asked Questions](faq.md) * [Introduction to Boosted Trees](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) +* [Introduction to the Model of XGBoost](model.md) * [Using XGBoost in Python](python/python_intro.md) * [Using XGBoost in R](../R-package/vignettes/xgboostPresentation.Rmd) * [Learning to use XGBoost by Example](../demo) diff --git a/doc/model.md b/doc/model.md index aa42f727204c..d55d262c28dc 100644 --- a/doc/model.md +++ b/doc/model.md @@ -15,15 +15,15 @@ XGBoost is used for supervised learning problems, where we use the training data Based on different understanding or assumption of ``$ y_i $``, we can have different problems as regression, classification, ordering, etc. To model different problems, we use a so-called `loss function` to describe how good is our model's performance. The function usually takes two parameters: the true value ``$ y_i $`` and the prediction ``$ \hat{y}_i $``. For example, we can use Rooted Mean Squared Error (RMSE) -`` `math +```math l(y_i, \hat{y}_i) = (y_i-\hat{y}_i)^2 -`` ` +``` for a regression problem, and logistic loss function -`` `math +```math l(y_i, \hat{y}_i) = y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i}) -`` ` +``` for a classification problem. @@ -35,9 +35,9 @@ Besides we need to control the complexity of our model. A model achieving a perf Combining the loss function and the regularization, we have our objective for the supervised learning model as -`` `math +```math Obj(\Theta) = L(\Theta) + \Omega(\Theta) -`` ` +``` where ``$ L $`` is the loss function, and ``$ \Omega $`` is the regularization term. The first one is making our model being accurate, while the second one is preventing our model being overfitting. We want to have a balance between these two parts when optimizing the objective. The optimization algorithm depends on the structure of our model. The following content will introduce the details. @@ -59,60 +59,60 @@ However a single CART model is not so strong in practice. How about predict with Now we are predicting with two trees, by predict on each tree individually and then sum the scores up. Mathematically, we can write our model into the form -`` `math +```math \hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in F -`` ` +``` where ``$ f $`` is a function in the functional space ``$ F $``, and ``$ F $`` is the set of all possible CARTs. Therefore our objective to optimize can be written as -`` `math +```math obj(\Theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) -`` ` +``` ### Additive Training It is not easy to train all the trees at once. Instead, we use the strategy to train them in a sequence so that everytime we train one CART and add it to the model. We note the prediction value at step `t` by ``$ \hat{y}_i^{(t)}$``, so we have -`` `math +```math \hat{y}_i^{(0)} = 0\\ \hat{y}_i^{(1)} = f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ \hat{y}_i^{(2)} = f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ \dots\\ \hat{y}_i^{(t)} = \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) -`` ` +``` Which CART do we want at each step? Of course we want to add the one that minimize our objective. -`` `math +```math Obj^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + constant -`` ` +``` Let's consider using RMSE as our loss function -`` `math +```math Obj^{(t)} & = \sum_{i=1}^n (y_i - (\hat{y}_i^{(t-1)} + f_t(x_i)))^2 + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n [2(\hat{y}_i^{(t-1)} - y_i)f_t(x_i) + f_t(x_i)^2] + \Omega(f_t) + constant -`` ` +``` The form of RMSE is friendly. But other loss functions could be tricky to expand. For convenience we calculate the Taylor expansion of the loss function up to the second order -`` `math +```math Obj^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + constant -`` ` +``` where -`` `math +```math g_i &= \partial_{\hat{y}_i^{(t)}} l(y_i, \hat{y}_i^{(t-1)})\\ h_i &= \partial_{\hat{y}_i^{(t)}}^2 l(y_i, \hat{y}_i^{(t-1)}) -`` ` +``` So we can remove all the constant at the t-th step and the specific objective is -`` `math +```math \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) -`` ` +``` One of the benifit of this definition is as long as the loss function has the first and second order derivative, we can optimized every loss function within the same framework. @@ -120,15 +120,15 @@ One of the benifit of this definition is as long as the loss function has the fi We have introduced the details in the loss function, next we talk about the regularization term. We want to control the complexity of a tree, thus we need to define it first. We define a tree ``$ f(x) $`` as -`` `math +```math f_t(x) = w_{q(x)}, w\inR^T, q:R^d\rightarrow \{1,2,\cdots,T\} -`` ` +``` where ``$ w $`` is the vector of scores on leaves, ``$ q $`` is a function assigning each data point to the corresponding leaf and ``$ T $`` is the number of leaves. In XGBoost, we define the complexity as -`` `math +```math \Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 -`` ` +``` It is possible to define other form of regularization terms, but this one works well in practice. @@ -136,23 +136,23 @@ It is possible to define other form of regularization terms, but this one works Now we have the objective value with the ``$ t $``-th tree added: -`` `math +```math Obj^{(t)} &\approx \sum_{i=1}^n [g_i w_q(x_i) + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T -`` ` +``` where ``$ I_j = \{i|q(x_i)=j\} $`` is the set of indices of data points assigned to the ``$ j $``-th leaf. Notice that in the second line we have change the index of the summation because all the data points on the same leaf get the same score. We could further compress the expression by defining ``$ G_j = \sum_{i\in I_j} g_i $`` and ``$ H_j = \sum_{i\in I_j} h_i $``: -`` `math +```math Obj^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T -`` ` +``` In this equation ``$ w_j $`` are independent to each other, the form ``$ G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2 $`` is quadratic and the best ``$ w_j $`` to minimize it can be solved deterministically: -`` `math +```math w_j^\ast = -\frac{G_j}{H_j+\lambda}\\ Obj = -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T -`` ` +``` **Therefore, given the parameters, the gradients and the structure of the tree, we know how to set the score on each leaf.** @@ -162,11 +162,11 @@ Our algorithm aims at optimizing the objective, so it also guides us to a good t Specifically we try to split a leaf into two leaves, and the score it gains is -`` `math +```math Gain = \frac{1}{2} [\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}] - \gamma -`` ` +``` -This formula can be decomposited as 1) the score on the new left leaf 2) the score on the new right leaf 3) The score on the original leaf 4) regularization on the additional leaf. +This formula can be decomposited as 1) the score on the new left leaf, 2) the score on the new right leaf, 3) The score on the original leaf and 4) regularization on the additional leaf. The regularization in the end can be seen as the minimum increment from this split. In the end, we will prune out the split with a negative gain. From cc3c98d9b7631b5cf48450bba6e22aba6a64f57e Mon Sep 17 00:00:00 2001 From: hetong007 Date: Sun, 23 Aug 2015 16:59:29 -0700 Subject: [PATCH 118/126] fix formula --- doc/model.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/model.md b/doc/model.md index d55d262c28dc..8e1d6477d488 100644 --- a/doc/model.md +++ b/doc/model.md @@ -74,11 +74,11 @@ obj(\Theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) It is not easy to train all the trees at once. Instead, we use the strategy to train them in a sequence so that everytime we train one CART and add it to the model. We note the prediction value at step `t` by ``$ \hat{y}_i^{(t)}$``, so we have ```math -\hat{y}_i^{(0)} = 0\\ -\hat{y}_i^{(1)} = f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ -\hat{y}_i^{(2)} = f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ -\dots\\ -\hat{y}_i^{(t)} = \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) +\hat{y}_i^{(0)} &= 0\\ +\hat{y}_i^{(1)} &= f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ +\hat{y}_i^{(2)} &= f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ +\dots &\\ +\hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) ``` Which CART do we want at each step? Of course we want to add the one that minimize our objective. @@ -121,7 +121,7 @@ One of the benifit of this definition is as long as the loss function has the fi We have introduced the details in the loss function, next we talk about the regularization term. We want to control the complexity of a tree, thus we need to define it first. We define a tree ``$ f(x) $`` as ```math -f_t(x) = w_{q(x)}, w\inR^T, q:R^d\rightarrow \{1,2,\cdots,T\} +f_t(x) = w_{q(x)}, w\in R^T, q:R^d\rightarrow \{1,2,\cdots,T\} ``` where ``$ w $`` is the vector of scores on leaves, ``$ q $`` is a function assigning each data point to the corresponding leaf and ``$ T $`` is the number of leaves. In XGBoost, we define the complexity as @@ -132,7 +132,7 @@ where ``$ w $`` is the vector of scores on leaves, ``$ q $`` is a function assig It is possible to define other form of regularization terms, but this one works well in practice. -### Get the best score on leaf +### The best score on leaf Now we have the objective value with the ``$ t $``-th tree added: @@ -150,13 +150,13 @@ Obj^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T In this equation ``$ w_j $`` are independent to each other, the form ``$ G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2 $`` is quadratic and the best ``$ w_j $`` to minimize it can be solved deterministically: ```math -w_j^\ast = -\frac{G_j}{H_j+\lambda}\\ -Obj = -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T +w_j^\ast &= -\frac{G_j}{H_j+\lambda}\\ +Obj &= -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T ``` **Therefore, given the parameters, the gradients and the structure of the tree, we know how to set the score on each leaf.** -### Learn the tree structure +### Learning the tree structure Our algorithm aims at optimizing the objective, so it also guides us to a good tree structure. We score the structure by ``$ Obj^{(t)} $`` which is mentioned just above. Since we can evaluate the tree, ideally we can enumerate all possible trees and pick the best one. In practice it is impossible, so we enumerate all the trees no deeper than a certain depth greedily. From 7294ac4fc96e2d2d22e97be8de02ce0bdf736510 Mon Sep 17 00:00:00 2001 From: hetong007 Date: Sun, 23 Aug 2015 17:04:08 -0700 Subject: [PATCH 119/126] refine model doc --- doc/model.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/model.md b/doc/model.md index 8e1d6477d488..3dc59815c772 100644 --- a/doc/model.md +++ b/doc/model.md @@ -63,7 +63,7 @@ Now we are predicting with two trees, by predict on each tree individually and t \hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in F ``` -where ``$ f $`` is a function in the functional space ``$ F $``, and ``$ F $`` is the set of all possible CARTs. Therefore our objective to optimize can be written as +where ``$ K $`` is the number of trees, ``$ f $`` is a function in the functional space ``$ F $``, and ``$ F $`` is the set of all possible CARTs. Therefore our objective to optimize can be written as ```math obj(\Theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) @@ -137,7 +137,7 @@ It is possible to define other form of regularization terms, but this one works Now we have the objective value with the ``$ t $``-th tree added: ```math -Obj^{(t)} &\approx \sum_{i=1}^n [g_i w_q(x_i) + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ +Obj^{(t)} &\approx \sum_{i=1}^n [g_i w_{q(x_i)} + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T ``` From f258a68029c28236e8ec63be3ff7a102016e4051 Mon Sep 17 00:00:00 2001 From: phunterlau Date: Sun, 23 Aug 2015 20:38:26 -0700 Subject: [PATCH 120/126] add platform if statement in setup.py for pip for pull #450 issuecomment-133795287 --- python-package/README.md | 3 ++- python-package/setup.py | 17 +++++++++++------ python-package/xgboost/core.py | 4 ++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/python-package/README.md b/python-package/README.md index 20d60986401a..eb0fa8cca53f 100644 --- a/python-package/README.md +++ b/python-package/README.md @@ -5,13 +5,14 @@ Installation We are on [PyPI](https://pypi.python.org/pypi/xgboost) now. For stable version, please install using pip: * ```pip install xgboost``` -* Note for windows users: this pip installation may not work on some windows environment. Please install from github if pip doesn't work on windows. +* Note for windows users: this pip installation may not work on some windows environment, and it may cause unexpected errors. pip installation on windows is currently disabled for further invesigation, please install from github. For up-to-date version, please install from github. * To make the python module, type ```./build.sh``` in the root directory of project * Make sure you have [setuptools](https://pypi.python.org/pypi/setuptools) * Install with `python setup.py install` from this directory. +* For windows users, please use the Visual Studio project file under [windows folder](../windows/). See also the [installation tutorial](https://www.kaggle.com/c/otto-group-product-classification-challenge/forums/t/13043/run-xgboost-from-windows-and-python) from Kaggle Otto Forum. Examples ------ diff --git a/python-package/setup.py b/python-package/setup.py index a446983bef1d..4bc8c707f72d 100644 --- a/python-package/setup.py +++ b/python-package/setup.py @@ -5,12 +5,17 @@ from setuptools import setup, find_packages import subprocess sys.path.insert(0, '.') -#build on the fly -build_sh = subprocess.Popen(['sh', 'xgboost/build-python.sh']) -build_sh.wait() -output = build_sh.communicate() -print output +import os +#build on the fly if install in pip +#otherwise, use build.sh in the parent directory + +if 'pip' in __file__: + if not os.name == 'nt': #if not windows + build_sh = subprocess.Popen(['sh', 'xgboost/build-python.sh']) + build_sh.wait() + output = build_sh.communicate() + print output import xgboost @@ -23,7 +28,7 @@ #and be sure to test it firstly using "python setup.py register sdist upload -r pypitest" setup(name='xgboost', version=xgboost.__version__, - #version='0.4a12', + #version='0.4a13', description=xgboost.__doc__, install_requires=[ 'numpy', diff --git a/python-package/xgboost/core.py b/python-package/xgboost/core.py index 85b6a1818a18..41943cd61218 100644 --- a/python-package/xgboost/core.py +++ b/python-package/xgboost/core.py @@ -45,8 +45,12 @@ def find_lib_path(): if os.name == 'nt': if platform.architecture()[0] == '64bit': dll_path.append(os.path.join(curr_path, '../../windows/x64/Release/')) + #hack for pip installation when copy all parent source directory here + dll_path.append(os.path.join(curr_path, './windows/x64/Release/')) else: dll_path.append(os.path.join(curr_path, '../../windows/Release/')) + #hack for pip installation when copy all parent source directory here + dll_path.append(os.path.join(curr_path, './windows/Release/')) if os.name == 'nt': dll_path = [os.path.join(p, 'xgboost_wrapper.dll') for p in dll_path] else: From bc6e2af374e0307aca95e52d1cc61a4a5fe28db6 Mon Sep 17 00:00:00 2001 From: phunterlau Date: Sun, 23 Aug 2015 21:25:38 -0700 Subject: [PATCH 121/126] add back setup.py after conflict resolving --- python-package/setup.py | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 python-package/setup.py diff --git a/python-package/setup.py b/python-package/setup.py new file mode 100644 index 000000000000..5e0226c32800 --- /dev/null +++ b/python-package/setup.py @@ -0,0 +1,49 @@ +# pylint: disable=invalid-name +"""Setup xgboost package.""" +from __future__ import absolute_import +import sys +from setuptools import setup, find_packages +import subprocess +sys.path.insert(0, '.') + +import os +#build on the fly if install in pip +#otherwise, use build.sh in the parent directory + +if 'pip' in __file__: + if not os.name == 'nt': #if not windows + build_sh = subprocess.Popen(['sh', 'xgboost/build-python.sh']) + build_sh.wait() + output = build_sh.communicate() + print(output) + +import xgboost + +LIB_PATH = xgboost.core.find_lib_path() +#print LIB_PATH + +#to deploy to pip, please use +#make pythonpack +#python setup.py register sdist upload +#and be sure to test it firstly using "python setup.py register sdist upload -r pypitest" +setup(name='xgboost', + #version=xgboost.__version__, + version='0.4a13', + description=xgboost.__doc__, + install_requires=[ + 'numpy', + 'scipy', + ], + maintainer='Hongliang Liu', + maintainer_email='phunter.lau@gmail.com', + zip_safe=False, + packages=find_packages(), + #don't need this and don't use this, give everything to MANIFEST.in + #package_dir = {'':'xgboost'}, + #package_data = {'': ['*.txt','*.md','*.sh'], + # } + #this will use MANIFEST.in during install where we specify additional files, + #this is the golden line + include_package_data=True, + data_files=[('xgboost', LIB_PATH)], + url='https://github.com/dmlc/xgboost') From f4a5a8b6cd28fa9bcf9ed375b50a39220f06820e Mon Sep 17 00:00:00 2001 From: phunterlau Date: Sun, 23 Aug 2015 21:28:13 -0700 Subject: [PATCH 122/126] switch back to the original version info --- python-package/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python-package/setup.py b/python-package/setup.py index 5e0226c32800..3f8ad9a1d875 100644 --- a/python-package/setup.py +++ b/python-package/setup.py @@ -27,8 +27,8 @@ #python setup.py register sdist upload #and be sure to test it firstly using "python setup.py register sdist upload -r pypitest" setup(name='xgboost', - #version=xgboost.__version__, - version='0.4a13', + version=xgboost.__version__, + #version='0.4a13', description=xgboost.__doc__, install_requires=[ 'numpy', From 8c4c754a72c54d7feff390fd0c4a813f9babb59f Mon Sep 17 00:00:00 2001 From: tqchen Date: Sun, 23 Aug 2015 22:00:41 -0700 Subject: [PATCH 123/126] update --- doc/README | 6 +- doc/dev-guide/contribute.md | 2 +- doc/img/split_find.png | Bin 0 -> 45878 bytes doc/img/step_fit.png | Bin 0 -> 63274 bytes doc/img/struct_score.png | Bin 0 -> 74656 bytes doc/index.md | 7 +- doc/model.md | 259 +++++++++++++++++++++--------------- doc/python-requirements.txt | 2 - doc/sphinx_util.py | 10 +- 9 files changed, 165 insertions(+), 121 deletions(-) create mode 100644 doc/img/split_find.png create mode 100644 doc/img/step_fit.png create mode 100644 doc/img/struct_score.png delete mode 100644 doc/python-requirements.txt diff --git a/doc/README b/doc/README index a14ad800b1fb..ca391b77c020 100644 --- a/doc/README +++ b/doc/README @@ -1,5 +1,7 @@ The document of xgboost is generated with recommonmark and sphinx. You can build it locally by typing "make html" in this folder. -- You will need to rerun the recommonmark script for readthedocs in sphinx_util. -- This was a hack to get the customized parser into readthedocs, hopefully to be removed in future. +- clone https://github.com/tqchen/recommonmark to root +- type make html + +Checkout https://recommonmark.readthedocs.org for guide on how to write markdown with extensions used in this doc, such as math formulas and table of content. \ No newline at end of file diff --git a/doc/dev-guide/contribute.md b/doc/dev-guide/contribute.md index 5d8f7c26cbee..03060ab599bc 100644 --- a/doc/dev-guide/contribute.md +++ b/doc/dev-guide/contribute.md @@ -5,7 +5,7 @@ Everyone is more than welcomed to is a great way to make the project better. The project is maintained by a committee of [committers](../../CONTRIBUTORS.md#comitters) who will review and merge pull requests from contributors. Contributing Code -================= +----------------- * The C++ code follows Google C++ style * We follow numpy style to document our python module * Tools to precheck codestyle diff --git a/doc/img/split_find.png b/doc/img/split_find.png new file mode 100644 index 0000000000000000000000000000000000000000..f6116073f6d1c138cb7ed73c02f96a13ccda2aa7 GIT binary patch literal 45878 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�X0+@W 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 WdlVew?cgimPfA=~tW-qb=YIhQdby?m literal 0 HcmV?d00001 diff --git a/doc/img/step_fit.png b/doc/img/step_fit.png new file mode 100644 index 0000000000000000000000000000000000000000..b94b5e1962961d2c3a268935f0d1109febde94d3 GIT binary patch literal 63274 zcmeFY^;cY5vjy6O1cEjaAcWxV4k6IEYXZRo1P|`kSa1js+$}f+cZcBa?(Pna+uJ$k zeCNCOj`7BLf57{pSC8)9d$U%pT2(dYEP_7BOQ0dYK?Z?9Xi}1*iXhOl7T|^Z@)_{W z=vUh`;0?}BQ9>A0JoI)K`0(6VNKOa@Dhoro*L?weMzWSvw*!H&?Ek#rmW+sYP zDN&(M&e{hLFVtS?JPAB5KP_KjU%7i8Io-)O>{vdR!W6|uxwY9gT&P$M|(>VD}ZzoPkz{pzTkK4Hp7*wyw#a~m0x+B&xwD@w9r5#dc zzrP-`j|n1+PW>k4M$t=F^s>jI;U03go_BZs?F>3g34a?Ka`}`Uu!%j zFEtJ*Hm^j^Us6uluFRkFjEksp2@nSNR_Il}W8UaO+S7k;Au0{FV?T64v&@}9&lDre zzvRwcvN@UT)}#qs*^SZv| zS&7SgEcl&Eo!&i(;523Zc;2FKz5SC%E^sHYp$cGkG_fUx#RVeQ4Hbu<%I9x_6;taW zFl5OCZF7r`=*?_Hl}d-n%?5~D^yQv|rb%>Nro!=kfrIikl+V!}HqcO;j#vbppZLA+ zo43>Dv4IqSwlR~eS~Ro!Na3ka(|z>9VbQl6#*M3K@-}y&*T~?Bq5`)lneb5dm)SjDATf$+5W(*=PNhi|0_$@xqS9;bZ#9FNM!^Q8GDbwkU%t97axLKg(Wa!>p9 z4S0hIS2~S1Bz1X?H>ZXme$(|ac+33cL9pYiEraEjwBP_&Vsgk`r>kz1@8F?*$vm1N zdbPXT-Q}sB?U6uM^apvh*c@~FA4-RDANbtky;9|#gzitiEdPG3n+Zu-GhzHaSAGaIT;Y9EbLHgkxZGIXaI;l)z(R1*zkjcaOFA4M$8@3M-N_KOIIOOy`T^qT= z_6^lmOJe;)#@I?;d<8`$utE&(Bfq-KWBv6{?B{P=TZ|3A4L(|!k9{|G!A}sSw5l8) z(3MCU7)sgtvc0yt(B0o^vy$cUk>F8sdmyl7(a)X1-0f|$+uH+K^S0uMm>yTLd9C!t za+hvLwp94?{0q^gFt`=7fX?D-nehy8F5-eToF*;-BctO-wfn@pT2|(;=2ahidqy_i z1H(-Ux0A^D_%h#X!%=LmQA1ySwATt+{UV4Z^aTk>hun=@(L5aQ_&B6{a4TMrwo;3s zf(h#!uLCi`VXL9JE;~^3p!|CCOLsgX;MF-S2PA)`*@rD@Z|O`u6d6w5Y!fQFGux>P|!fA0L8v@ho!aMM0r67f_AB@R|Vyb8BW>>ATz8kfe+e=9ZB;1e_Hjj#&#Rlo&0J zE4@G@p@avebM=|q-KwmW-+O`J=+SlV>xMaLVQ>YD_SK+-C3ok$kB`GniqlQq5phX^ zmvmH3mZ{lzpN3323~y$=x(JNHCv`QUSgN=S?LK-MXlq?VEdsv7+bdI}9UZ#GA z4BGEp#=RRt`mLIsmzPmrkIz`Rgg7oj5GmfJ@d|8OkeH3VckZ@ug_M}hy{hQ(V;aR$ zJ1S7m&8!3V^Xjkxks^67z($>9V#0A4oiVOsy5}&T(u_#fYEsHl4!&F1&4g<=V!m0V zx>G9{OcHN8fcM^DBO;)mY^lcTIE&91?}NWtU_4Qy(<5C|t;iw`6E$b|i*gh#g~Q(^ zwf-6x*{Ini;*M+6=qZygjL~G%7ZfF>TL#8yRDUKv zRt}NWF#qz_WwX0Vm8DMKC~qcgb=xA2sH<7%%ElT}aB3Ie&|n++aRx@d_zv#+j+Mh6 zddi3JXA{SKO0k(BAuX|ZV;IwCI%?-%a&xF{%u>1I3}Ftb(U^2L2>S-U_Q%;PZKB~3 zJO$T2TTVZi^q_QMcKOm;NLIT-UTd>!Y5yE4< zw0ot%otIfqi47w~)o82meT`1&3bAk8+75%;LJ{K4%M@m@cbu|~Bg28aMXK*I%076J!S8&dYGjxX~%sl+!H`KpzZRLcRpQ{N9(vU3RH?rtNDS zDfwLQ8>?Ev9vm0Dxpj^OuESRIVJT2$+YuDO)9xvEfmd*DobP0dS%}g#~v0Scq#mmmCoKkw4 zxlDHge+HIX0j9V?|24}1!>5DV$0U5@K=f2AojK=w zL+jM_BEB(PFx~wZjA2{8=Y8$%Hb--7i3@bU(ywynnmPEpHcqQTfEd#eMbxxQ$owL> z(VVI91cqiAQ#xS&D-Msjfet=t2)`3iK^R%%b~)&^Rc^rcwA}T?1K!Y7-j6d~ertN{ zG@`u>>s?Z~G$OWzH1-cB$iev7MhFSc>d_`QRxP4>g2VKf@{5R=q1Slv1o`yQbGg+D zJzs5xzns<|Z};=R_lDmHgpr&ng+UX1$ynD{E8LLP_4}IUknfOu?+$m>{I04wR$Lsw!Lb zbf%O<+Kb#)Q_cu&l2PxU{N|f$iDYE_cxMijV4u|-LTb1XT*W?5vFRIrFxqOsQCi~W z{*c>~o$bcUz`?;z@m#>qNTTCa3@5ol8v@C$&|~l2P$7|dF^V&$=}%~Igi4Ue3|+SA z4Ii@t_>BjO1+sF)QvVoy`XTAmpkKoI*7#3zdm~}C_BK1L3NwV4tQI9&A`Pe8Jc-e|8i?Ks#$QduZh*t49k66a(%LrM5+nvbi17M)3)%D%h)+LYe zFae7@JG+k`F*T~BD-O5o)M70CGQ=i+da-OL1gG|Z9Wyf5ZhX4Y&NYv{V%?-}ix5kk z2L>JU%Gq`xLn6n{{T%#|1rKfrDkm<)2DGC8M_F=;& zIOthjs0{eU(sb_*<-UrB*@WK=qa0Z2Fkx*2)d?GS9*kGnENg2UDj4SRg>`Ut*o51n zW%UPd;W~8p`Fgr0ePs^lgxkpYxW+8(En|wbs3;XMWM+(4E(PJDwv9R?13W4IFHOT=!-*M=|;b)ZzQ4d2??0kI1h*0I*&83f{G|? zjRuVw1L_n=<+J959fh|yl-te;M~nTp?Za_*{lCJoT`U30KQWQ~_PVU*^^}|4sU>hzpgHt)gdtqhw>|%&+ zyj|v)7q!9f>|{SKsCr?2f){y-E_@xmZQ%DAkW znz17B@+-V?=ShKir4^yehuD3huf{))4z^*u2`?d~3ruNQIE`gp{)C`r!|`!;7?NPpJ#`Pf+2JkFiXx*u=J;G+Qu(dVe*(?TL5`wUHcuDz&$8%E1w2P$WN zuo?Z_21(6&%3G6o<^Y^@v(&i>HCoz&6SmLQo;uL@qhIgZa!B0SBTA_f_KG8U@?2VR zoGzyn3uM8eHgE%O;Z+17uq?HzfsmNxZ%-ew zoGkX5i%#?12tDR*9BtYU4WM!+FXTG!nWC;98r|;Uh*okQt*VGlk`l=x%D!x(iW_7E z5nn{WK4`1Ri%8XNh6#Ay?Zzo}E0E|uYtoOQTIOR3*VEsO2!BUqqb4i-DL%?)dE+Bi zJqka!>YK}NNvZzZzjbZO$Pw#!e6vFfZmrW@jcRwRGhUOEPk*#2%ajnE*@>?zyqB;> znW*$@gWG!$r>{EKXM-@#eEs^|x3ja8;yKT3XUw1uwtV4dy`LR%KFP?QU0a1!FX8VJ zl29aw?T+^k=2}y{jx8W+IIh0B)c8rI7A(asKiQ__!h=#(KfnU>YH5h^=u18g(0}gB zyJVlgBal;DT+8untWcBij`IsW27D?r09-y|^Vu^txYRU3Zresjg8RJV*)))x^2I3$flf?owe#DjQ;Z}Ym}9#n@+8&J-HdO zxo%#Zl|6uBevGR}-X7$8-npR|C|hn65!5_W{Drhx#_ixS&_vAgNLtFgr+~az%v4`BS7BV`8&&E-7h=tA!w{W+N$?u=gd@{ybLBvHeZh z@w1aqTSxT83f0sg^{5CbpYIwx5OUgDgjb3)M4rSXmSbU5k6Cj ziZV7nB8~|ahRJ*S&P#uF$D9-n`zD*@ald<0vAshG0-d-F49FJb&qqekV~xS{ zWM}wgQ$vYC-#FZ8ARL^WqlzD`9?qGCp@Mq8Kwo@-+iC?oPq#~24rj_^!il(^9ucu2?h@T+1s?%M5A)`RQc;nXmCXcW z3?7R-UY!FOpgv6}PB%+80!g|lKsNCG${#^PtQ>qq{SWmcG+SyiV zT+&!!$8!4;pe3EHpgTJ|Ukvq@&brf3cHt5{ZjW3=Anq_s5J=Gc@Nf~)7d7AHQ)~_* zo#`gKVvUheY?|;3&nX_k25wH z`(hxR32&PeD<6|0(ok-z>kJS2jo@kp{h+M)c=dFDTq5ugVi34E??f`X8Yb8Bc-9^! zaKG$HMou<@2nPb4ekrr7ufM9ch|9@P$w*f=G#YJ7E6W!Z%C2?T$ET)#=L`qRSL1=m z%E~5BkKy9*+uwFbt-=fQR+}%@IS?rFIo$j92(5O95v7O0gFtHuiHSB>fz6%*zb83G z6_z8s6@%>3el+Kx_ukC5MDUqB>00A%s=^Yev$P*UiggLQv4RXkY+^lSzU>;cUPFQhfgZKlK0A zrYGvCXlUs*OI)0soGdKgaHkN1J1=3dpMEu@PN5+oKVO_U05+d8b#`W8VzRQeMP_6k zo#zct)8Xe07V6(;;p9w-P=EivzHPX?#EOM7+ZqZ*VDuaZ_HC`de{l1QuAHo-X#cs9 zkrBtlM3|JMth{`N^8UAbl7yYzG(HdS*tk-*8VnqM(Zcv=T+g!*OI3gb*>KgP%?jn< zy_>oukir>bb3%W)dv#UPs9Hf48fqas@aq;qetdlVE6&o+PJH=yPQ(Bs85wGg3pv0u z0!yl@E=J(efvN+-$ca{znAHA-1#c+rspaV*x4%80Kek@M#~0o~dGhK0Tql)?2zUwv zF~IZT>gjWxy^Fp5tD|t-MMlQPi;Ma-<}vk-he)`=Tr|OIOxLxrJ1?8XhE~BySsZvTR`rP(`V6^4oG>FJCIT6L3nVIS1 z$??7}E~H3kw5cd?epF}x1DB)G$G`9;l4lLQlKI;g0ve~{7G=%awgnuf74vxeB5 zhGv7bjG1=RFHIXd`ht>TOSa~%db6w9q@3a5w3Zg*k_GtgZ*y~7<>i~;#|9DWk39+>k=|2Z{Rn*rHe1Z}_Q3hT_!FzmIerhnA zzdSfFuiRsIwtjjHd%8-K6L{P>+znFkDtVg~Fsy6vd^$4hc_0inwzg)f!WB9m$?XbN znQW>3^$Tg%Tt;U3=+J%t^x~rE{uy+Pt){CxXoTQ4({kD6$G{UltrD7?CZ|J^6ZN4T zI`a1|dzh*0Kq3rvPclT1j(pEFz)r%%6e0HTEp5cF5)y>Xc>xc;x z+V^jqI5{cx_h)3I4wCYdeD^cwW25~I>8d#!Oh|^oe)T?95IQ4C+S>Z`c>HuTEbxRw zh~uYHgnnixS{+>rI(?2uf`EV!zEf&9Uk&3>l&Y1q9~rMZ@q9WTHde54Ck*F#1);g0 zwra`6bVWa2Zn!FWdQ98DZbnyQP|*~@zTt2B`C@G!ad-7xN}g$^O4G4lC4XfljF?U< zCoeBCR7*yC$<5xrFgsgG6vl={oS3MgJM#LZ>Km|!8{4YZ zwyPS192^HOZtnuHu+F2RP$C+_Tl#Yr7T9EDz{t{_6~=>ML=8iA$H^sD zZbn=EhG5P3m&|}BqobzA!!F|cA}yRqT1It-$ieBVw=eOSGEPAnx-U}K&}}gb?ds{V z==92(Lca0%^r^6y_LZKV?Fb#8&hm4LDE!y&fDDbI0PT0lPhLsdXavakRLz%1N@5kmkqLVfr_|9 zqPXAH2zGgijP#Od1us56Ua(*)y}aCZev45&uH@&Y%Q!nbopc{iSoX}PE3eQ$-(9uq z8D=)tRTglV%PO(@;^rnS)e8mBo>^GP%Tt!a<)ka;{QTtXBuhRZkX2oc%lNGXI}0(R zzn50us8uYW*pfqy+_e|^y9$sf;!?X3qLivE8bYFB7OFKL@TM{F;%ah?hsi@r>c+UYXO0q3&dK45ub5gd}ErXcpjQ2u?56UNIJFA^I`f zT7Qj+E8&8Wh)XOtEGu+$^#lc7U4jV-?i%htlS14~AP|TiSwPK0m7zNLoA32C5NzFm zz&qse)K~C;_pmtOm;SDC3#;-CKzhnw?%g0+*rejnmH&SUw@gAsKYTbW8!1lXv>&!b~6L<7bxKl z7U~%}DPPbI4e7xgE&zmdcLDDHq@)y#f&oJv9IRCiN*(h1lV(nz5%yw!V@4Q*w~>>T z1z=irWg$zSh1K!T0Eq*Dx=#BM8!b90MoY{XhhdG>6a zNFZos$nP;XS}i7d0#Lemm|&=HZ%)e+)C!?x$z1{$u491tbY$J(c< zN%>R#B3Nl@kr;=QT0>_G9nP*#Pfst3E&BVkE$;S9p02_?XBL}+Rp3qxg`fbolmVlm zS#(W**3?w}+7uW(++cDGbI%mJxB?>+a$Fx?jki2H+KyXMhqoNdOFmo@mIFx`z|cSy z&B|&Pp+v(}hj(=a2r8uG#GX=$A8B!LRnkk)xn5=klnetd~(H@wdI6 zeVsb{1h*@qGl=WW<+0UQHID^g6G3C`;y1q|f)&_#cyMr1_IG18)|1NDHujUS-bR{O ztcwWcJBzldDV;N6k$jpo_wiv5Z-|A=%=7Y)kXXd5>w6C)j=?*lL<+!|K3`wtrae5| z)6vuKntYAl*(D%I0zmn-A`m%VW#k4-z9uB(C3nyi{LoTg?_z7KxZMU(;TkL~jA7n^ zmxKc_LLdeq*8^-^SO<|p@PpC^mQi*F-cams!)SjddA6HgR`w9yV>sl#-PbpQNntbH zI90tY!-R)(fnO2^KLtMqF9`T!^7z=}W_Q|xPNQJK$*+Qfd|H~_Jd?eB62WiGh`@tL zvk*~&FVIOVq{+$kbLAJmY>zOxx_%BA9kr2TY*=(PWMkLKQ2rJz2nFShj(%iB8~g*T zHz~DaW9j<(rzeZ}O8G!(@L2(&Y-vP+Q#ObvjanV-rkDeg9}E_ zutY@2<>g!D8d{j`kC z^PV1@*RLlvHG=G#{Ojwv-a|P7Dx;i@4Av4GQFVP7ri6~(`W4?C;~(U#doea9jsKc6 zKAzKLcVbFlbhKccJ+GqNq^7#)3T6cyxMhlef2yeX-6pPKNHrAYH9#Vj|LF-Vk15ak ztwmw+;zpN(S08U7$?sh5mX`^=JpLS_zmdKggaSgc91}5$OfBo~y|xCmMSj6_gko{H zlU+Jx^5u)D)acCITt%)iMv(?0FRv5MW|E(uHPR#6fB1z$B2vib<^%uOX=78X~^$e}(ex-i@;Q(0juR*7m?&~PNV~Kiy+R0&u*U!Yqs&;AY!|L z{(YjT0=QabYh9l{1m&jp;CRWxoMLDaioLe(*3>lmZ06lRBuD!mCLj-i!*-L|<)xV7 zlas{@rgF2hlhU3V`I+!SSs`ndlE27ZVBxfDxN7jXFw~W zE9*)u%s0u-Qk<>ZKj@DW{p#_K(PcXz?T5OiNO$jlt)s1?f{!^a0wkFu$Qe+!vuM1! z2K~D@Z=dmM^B4_Qh`oJdYw_u=lO%KoU^)GiQO4l^{7iIwaj_^pCp9AVM{L|nbYzw2 zsN>^(k$%v(FfUlCv?MJKRZIH_& z2WXi~5?sImf#@4jq2KJ~{s^|Qx^50$7uv_#a~+u3>do4M3!yZHXzO<3;J zrwQVgM=CpU)31T?xdLz}gFdiQ=+nJX6n0YP~$J16#oBCXwVi=a%d+i_}3jZ<4{ld+s^DwD-` zwWtP_E$nsjuU@Z@W*03isoHc=MD>IlTbMtSnXLnS0zZYIWFT>RW(L)AlZ2R*iF5qB z8^UibIDqv6vH~o44J;R&_cn1Pgz{?vP)=BZr#6tQlkL5uFV`kHxlAMpPV);38*X{p zP6PN~T1v_nMbAKmFR}tA+e5EPwYIhfw}pggH(nt(IQGviX-zNjjE=XvBQ{j4kl8~8 zDX1x*zqrnLQECIAY#mwIfgeTl8wQ49Z*eozv*_ZF;o(4PkiTYvh)hSx@8J%GDx;tb zON5a-9!FG_3*QO4c{fbqDn*I%F1=@EHMGBxH!#?nnV|s8sGTabWdwfDn~r^p-~$sh zC}U;yyuU3Nz|RL#?b4FA5Wh$HqHFjuMiT+3^nEyy3V7K}(J$olYR;|_YI9Onu*}RQ z;h2g_+F0B2$OtK&?b6J(ji-Q3J^#d1wc>c*5oHv0AAXW?)_}*7{o2tY=pPUH{{8#y zdChVbr~P_^vYS#?*$CqiCBoL7uMH9Im_R~G;l0m^bV zb~|DM&3U}|1odXaB!);a?Hc<`<$Wd9X>plcaj9(4=+TTULk5qBhpZ+@$W>`8U@|$N z?W%fEb13;eC)=6Im*btCmCdDLL7C~<*&u*Sk;m8!2i`ZG+e<7OB(?W(8UERFORhvPYm+J2Fx-%%xuyb@vBVD zTttN4D3n?U$SVbK|M0VgM585NFtqkWEQW5)w8AfcNQX)Ht}Y)5$wy=!c#?+iq{S8)2Iv`R`po$U_bJ z`inqJ+(=%Q;j;B}F5=a}-2C*^!J~O)y*7eRt6IEzTy!*PNr+03h^X_#y2LOQb?)f@nf5E(DS&uGdikc>2?kJ}<1;g@ zeL1~DHpS(UFA1q*qAeVcB5iDfMEk$^{F|&L7GU8iT6K1DZy`P!GW3AE4d`qOdr3~$ zv2^QflVLFWpgsF{4L0NO|Loyk#P^@GveN$_Zd@g-V_d|-f*z-8?+9Yctb?`MQ%7n1yGoHyeU}z8-A%57 z{{LNIW%Xt9`+Q@oV{Q%(f0ZIIF0Zk^p^~z4sF-DurmTHPHwsuUMGl$&Kg@k8)WjrP zR2KY_mmqkTc1;xV8Sgw0=`OzNy05LTxivQauZ_&k#pZ$hmYiJ4B!LvlX=rGC`t(Y} zS=0X0Cw+Juf4vtV;5_Y(u3MX#83eex*gb4)WYyJmcLoo+_P%1$A=QAE*4pLP1iaIt zLV9~V@8pP;J`PXDNy*AFL(KRs6nRcn&jBn}UvCeYj^w9}wRiadeR{XqO^gP+kdTlV zr33xG1(wp)NohZAi7!L1AiUh3pKqeIi0FEm8X7v-+h4L!t>v#K!9+)wSXaxjfy1!e zh%XAH{`z{qWMTXptJqkqbcf50{Q0M=Cg1FAW(@>_=VR@O05`GntRqJ!^E7}ZRlu`) zw>u5>;=tRB?AurjhIL~gxB%S9s!^WWn9650^!EmzY{^wjrFIo zFq=C1a&T~C<@0>Ys9NIyHVRu#P7Vp_<^4MG(>ps2EFeo@0QSWh92k1b>+ujk9QG@J zK0`U2=(jk)?gTaRL1?ccSx4D{KHrYcuJkGph|CLiQVf^{Uc{Kou?VI9Dj|Nd$f6PH zf>u-x%)=7{U>$Z=GYopjC)Jwp2zsj}I%P#+Us7WE~Nr?+LLCqjRnv+GyPsRGNe zaRpT-ElV>qrFZmK-8nkTPH(6|E91a67}?rN>nxjG5zU5&b7Wm(EC)qmkd&AAOV%Bx z*EmUP-1b$OS*D~se4Tu+rp5;$ci?2Nc>C9>)Exwn#0+d*0Rakqje0bZ9}$76PMg0> zDhLw%6iyT?-2B`fJ{N!uK0Zn#boVC7yiPK_JONobxW_ZJGKDY}R#u0bwb015z6AUX zhizNvm*vGtf7~f*Mow0S*Gv&rr@%QFT$c#*cw9wv*J|2LFsVdF!x(G1zgCi!Mb&r# zQX`fG%PcmZ>vhZLlicIu%8W@tpK1F8jc4Z;PrMDXofa^>ksOh$;a zt1xuN`QI=NKuSRT6DuUgdr2U3K8m@9iX&#dyi{CO1vJPi*y_3c6hOuKvjjlS8=a0M znwzUnNrpcpynh+hZk%|unrj$y?bP`6d-)_|d`QbU$YDdz+D{;s?JnI@o8 zgu{O38^)zP(9!R2#--WX_Q|KRJ(!&*Xx2bRTr|>>lj-iA!T+N+hT&u*zR@s52@XBz z&do5r+P9((tl~x2m-C(k5D=hS>U@O$%vNf4zk8vv^isO7y5+$mQY|j!{_U<;tb8b- z!P4vsX>{FBFDuN=BmS97;nhNZ3aA3T@hw?SzX=v2Hr16p4Q~Ri9O`shttNe@;9G3I_*I^RTT5=^1C#U=13ef>flgABanSb1S{l9@J;g%53rDzP|^w9v)Qq$tE=Fkz=N53 zMtZZ&h-sDR$mEdcf%;eyNQ@Y9%-Xk`0mQ@tWJ5^uAW#Omp9gqx@qt>N6i%2h=w*qH z(@Ub?VgRD4DV{!A^#HfGlm9a1dKHaKOWT=}(j-9<0#L%?xj&t-!^YhkexR>?mVBn` zaVKvDj_6MGjri5Vsv}p@`}6njB2UYy4@$gtdmOXHkc5PU9l`?t=Yd+9HT}J*ob_%l z=f|FW9P}MCByS>*ZH)?p})kBj>R%2uMa7zayCumqwXC-yVLt(0O_Q`eUi6DKnMLqT|C)x^v`ug5U8w ze*+3(Ni0&x%a__6dZUSxCuiRq>~B_Ku(S1jef{RthOsayCA1SZ@ft|}`S}S1 z(*sdac2v{?W4-R2_YcB{NBjr-K1)Xg2NhX}6sya14*NiFlk1<_!Y^P#r`i6d!Si8$ zn!ri!mQ%?4Bw;+)>3F8jZvGNwdpIntq>dFEiHTcI*3ojY+Twhxf{hLS+?@nCKrf}h z^G-ds$J1y?Qb2JK6x92;E%&o)FiPDwEFRs=$+tode}Wp|+JyT5aA=@gc9Rtt}ZDna9JfF(*z8 z=Zio98Eq_bKU_!UHb`ig?p+RQh7m)M;hupa{jvfUU9HzCqm+8cy}ra?6E@M|gq~dz zvPFOTjv8WG38U-Lkv0{}@=p{`h?VypKw_5wYifBa3%&vGyss>g-HaUq1mA6&1i=_R!+2tp~j&fuVhN zJdCY(3VTN|5fXC7sA+f=+1p~d3s5h{x{ZkM^v^ZV@b4fi@zsz?9+Cb(f30rE0hrnyKFg@uijLW77H zG+Xi-B**~pC}m2H#Bo0g%w~e|SclUjJnfC{Bvol6oWr~Z_Q&Vk-h{}dj#Zvi9ybWH$`ylqfpMZlKoJS3> zh+I6pg7x)xq;ye$Jlq-fx&jd5@~9?4euVtW(}+@{p`gGx7d@@8jRkb)g#q(Hi+Xx* zppr9nThfkVXJ_v*O44JV?OpG7vRXbxDndWBun6!)w_GmgWM40`jD&lHll=A}I!e=q zBtJXcH@&3b>KrV14rutJS`;8>K+?D&BtQ#*b8YS8X_#kTe%?=I zvyYmZuCi-i74@(*wA=c}l{C&|t*n?eJi=C2b#W*SsU;*T8$|(S<}_0R0Ek>hD4=g@ zFL%9kep}nal!j)K6B798jUUH{yElds-P`hUfq-&F{5&?M$Xc#vx}@RrqHut}_)8!g z6f$aNFUf&{ZeJ!QT^=68uP#B4k3zh>EnPWbA@(&;pzw{mav6um4Q{e@vl#x7(!<0* zd$f4O%s;_+Id(`u)rxQ7J-xHuo?)`2N*q@?4}9n?5I-Yrtx4o1+EP=6C_81E zo00VOJ8neV+x0aPxfd3M5fDZYk}jNg4vKQkKB`Mqjw`k1-hAHGH8Sd&+1Ud>T@l9x zfbmRGVKC2|(_FmaR7_J4XvH1KGO$ZDldp56qonVRPR~Bof?9v-eOcjnC+S->&4!H~ z#cj3lmWXPoH#7~X=jYl6%9~TaBI+J%Xv)fAon!MX)f^v~i=pgCB}Tq}vk?a1{Tqf| zxZutuj=G!0mdC@j5OKt3s0f)QxmW9+&F9*8&S22DmKX&YaBM7*>t&y~1anKdp@s%4 zV@gJW8I1;2>g+83Id6?Xy4edGOs7hkj4)0la%E3fei^8sVp|2Z!sp}^faVAETPkkL zj;^i@Ll8) zsm&k36wW+(c>Wk@1>_%uLghT9d|@o?6|K9i5qYY;^=1~VJVKS3d}<6+U41#_NE)G~kBQj+U$ipN#$?Gvad)nQ>pN*8+k zl#@%jyEHjJ-B(q`PT%*bztuvP23UY}I9H#)`s*@EC?N($u{PFuueI%ylJ6O|?F+l` z^Ldw_tYK4(YcPc0DP?sJ3_$g`kGuA^F|WuXJLBVGlj`H93>j>nkKFKboVnY=0{&mN zXU9wm3lr{sL` zokoIb!1FIYPVjF&4rms<)ff|zl-$~4RHWC|KM&?{xV`jj7BDs4^NO9OW{b9`+NA2eC8wzgCNx(VWo?2OF|6Vtu- zWFD(iMR=n4YHz$+zc36G%1HMa83$P>8vchH;=X>R4*H9?0c<}q&ztAEYN~v(A4=%; zd)G_Ln0mC!wX~uvK9y`sa`MvYn2QT*6-zzP_xhdA>!SGQf3ZV<%pKkafHy2+Gc566 zkC*>`3veU_rz@%M5)vqYU=h9H8!jmDuV4YJ9v*?+%%u6$0t`M{w6LNc5@W#pOKoWX zs=FvIK@GE)Yii642}=7UTWjgFAtwQ%s&enGfPaJq zzycU=6001NCg9Th37m%jRw^Ux6QfK=#4p{-JS(ifRFOZ^_Gk=Xcf0pz(U`asgkDqq z<8*n4a9xS_EdNV(dKF{t@o<>s_hNLc{cowFLmE6e$++>VPqSJ&E_MoFQmxM>4xjGi zz14_u-Tv+h{eu^O|AsAl#BD4%>Dt@3kF~4w^rw09SQ?2ZvqqO!&;#)+GgH;fgc6Vj zj*h&dsG0WmmaNS7#R{fYx8%5yklH&s^!}%%L^S4PaB;KG-~YhIR{A=LH$5_<`_m~i zFYhg53HZ!!U#m7Y;Uga>r;kce(z^RyG1FS|hr~ZY=-^Kf`kQprk%9gWyRs%=G*7-m z?|6IM+BN289{n!Y4C}XA)E~+n5SRW;eRtQSVjgHf4y;akH2IQcdgAwwg=ipduH?#^ zNl9p6w4X2ZNRA{YTUiu2k^VuSGSK*>_N6K=5i85L#u5!fLykariHaImWloBYo5tGo zo(mir;( ztx7_I+niq^rTHT5m_k+#GIDmivGM6>j78G8_nerBewRrC^oIapPfBWe z_lRmpDrtQ^DvoIh9Z8O?@5>V4CpEIiRhao&&;N&?-QIH1(l&*M4Ryae>S_0r;rgTySSnP z4Zt3qDZEXPj)l5_GO89+2;>3#7&QQ}>9>-$gZ_h=O4SbhqT_-S;7%*sLMcU}{P(P+ zOUu)UjNgXM8=nSI`Vr5%;2_5#Mt|7kG42Cn8;|WREhF8Iy1+HnRUp;^Ri06e)!>lj zj4q0I0~c3`4)*p4sBNfA4{D0ah~YC1e|N;R#u4i4E6QgNZSUxYX1#i5)vZ}l0cK-0t{vBDB zv4{`o9saW!aW!*|7a^JYuQd?Tl!OAWmWqr!>>mK`=&xpFKR^GeDU4i;OTvd+>UU2U z5Axjx8Ju!a%|L{tpu!y;>7%Ea18SoTWu4^Hs@&*k3zFZx3XYyATc*>0CNkb{6eX z0h5vK92`(ASHvZHE6S@}&PL@3kNoW>Wes+wF;>gMTIO}v$ANymSa{Q;iS0_qt5h(F6ooXWkpXf8X@ z@(4Qz8138h>E#}$5Fze-NWsA32%)B@NYJEM~Cx|*QW|q z^B%_>WF9^*cZCE`$TX;wZEXBJE%@yOch~@i>X+$dk_W`{|KaYdzoK~Gzg0v81O%kJ zMM^psq@_i=ySrg25s>cArIGF~=~_U#L2_BTm*$!G=lfqgKh4=Vd+wRpbI*0X>iUm+ zokv+500dD0GuWN0TyO0wAb1~p`9-F1Wo9M}APO?uXN%Sqg?$JElAo_XBr$*dm;^Lm zhUj@)BX$Xfwl&Q~}y+b+4PqILPObp~HMyWbl>XLA>X zBqw(%`#ZdtGd1;0%}O%^Fb2xyn=G9syL+1phZ|baPKy(&+(7kRe3`JYUK+?>^B-QW zlILB|XI(lx4y$#(_qz%rj~5ir@h$tey#H&N9f6iPW(bJPpk5qaSs_`RJBCfhM6Oid z3i)l)pX*scysOKfrs*wUjt^2_N9Wnh7WE)JE#9<$Ie*h+G%+X{C$CYO^ptu%^kL}eoNRAzc6S@OwrDp@ zHtsJERLV}x?^!0c1Uc;OiE#0t+mY&;EF;yjHJFUfZQ)3p6&4u%L@n|to@P*tKUoW8 zrvD1oOpPV1xti#T?T4jM_^klGl%GBifSx_(ZOreo3K9wfGaI4C{2FQ&eu=vmTQOaS zpQ{lvN(xoBhhirWMO3*)%kD&`4b3LShu2vgw1QM+R;IokH5W3H6_HTDoU>Vbr<4)< zSwgxBAQ~H8<`>O8e3$BN5L(;FaaQy8Ex^kj35EQh_thWzZmSLMgC6!{+3>@_L6_B^ z**R>cCd*Wm%MR&Oa`L|8fNWilS33U1&IANB^oN%6)vx!qp%{x~lRLY(Jl_Hk4Ny(r zL!Q^45zkL&SgD3T;j3`}r>^HF--~3x^a41^(Q$ET{GQDKLDNB(|I@MeCucMl&N_gv zb$zLmgx#BP|8(^@9s-vj!3@3ZAHbt5lRSO|uiU%I_=%Z2o4^u2BS~bUk+o;l6m|k;+7irM2}`o8v}a zKsOzdIIuLUei><AbFWCAd8VQXjOpMOtYg0ySk^9Fu5aklgJfN_V{A(fV%zOtrf!_I#cc$VFK zd}pq%-<=77#F+l-qX4l7W!@iDN?mEWpr&hRZu9OSU;?U$Kd{-=*?tM>XNdpP0m6Vs z`H(K*J#H85A7CSI+ke8K_|Fhi9S?v<3B~rd%dBHPrF1klyL*I*8Eus+EXn`(6Kit= zqa6lPvP;90HLb1bLo`@ofTpPYH7c6=T(;skm6_Sq^mHGQi8&)sllbTZ4I{8?S_bR^ zGQ5J}?CgR0#|VpRyP)XwIo|xf@Y2-e(z#AlW4&sVqLx}W$2*ni2LHG2c{QcJi3?SC zS3QQGH4iNwFmR>K1-a$%)pK*p}>YLU%mEhy&Bt- zK}R;mDSNTk{Gv4FZ3S}gEWg;hV#^ycU~@JbJI<3(Jjb=&R1ypY?@Xp^`k%8fV3xhX zwRkIEjYgN-S>HA><*e6fP4uwB&}L&>vk1uGUKCWVUv590Jl#GXcg!3{9{#$fBJtt2 z{LBmUy5Dv1FIh*Y>Io(hp3FdFe93RkdQWVudu>`&^34bG?m{$0-MC;nH=N7w4dby1 z+ubDErllfk?mCm3JSQp6CBd{xwo9Ux)u4ULSQpuS+Jn!xTVoQySo7j5#kJY*x)(dq zfbe|%`DReWa$+SYc^QL?ll`u>rGM>oDfwuhp4BT#K@ZexaI>Gj(7o2VUp}+t6V7$d zycNEjq2oPhA>6|DqUc${LF;?w+hWqXAHLd>u!F{@VWo-U)V0d&pTEE9OD*~uV_h5w z=m@+in(t~Dgt|6#$y~cy(1x3RsJi9$PYYvjP3iLmp%D%u858)+`Q=nQ3El089jGl4@HI|Ef+l$?n&TU|_L6%>&Bgy@=lKZDGum4L`?M@7CyX=H_4(cX)k8#Q~?SI78 zAL7f|j&}FO+k#Y9x`qLED?vS9-sf^QALQ<|?VQhcYUuiy+;18qtYMjK8ORz4-}6Oq zf^xQKx~#cwd8vG+1{WJ2$+83&mOlWbVE7!C(fdba$ppJ~)ihSt4L>$lUd0H1Uc6s; z3XZ>YlLF(}Q%$3{Dkdv&iujVhWdE<<`@wy|-F3AL*~jYPJs#;r;^z42th$1UA;E7u zrB5AjfcPhy$W*N`V~+M&_DJMdG?JAlLV%-}=vtO+J{k}ji2v6j{AfrA`(5l(WXzt} zZ}StdO}rgaaar}96;E_wb?!$DbD;Cj<8JxmNcli?{10FB$yqPDn*M~9d7xAgT zO@lM|E5gU`wAy#)PKjMJ7;jo!)U*IvBe$X}pxfTcphmvKTj8#*E|1mGQ26T;?=9lM z@f1#>0Z!Eh{a8C=r%=SA6K2zVEkC6*-=1W^4!Z1=-!M}%-JIFkm^3k&O`dtDd16M_ zz5jnFrJYS)o$uR?MZu{uT#Y7(ZPq*x0|qoG>Sh#c#dde?o}NC=mkMRU`*>9S1X;Lc z0-Q#{m!GE{N?g^>Wi9XjZQp!DAR*xz;Z?28+MS8SlE1MzkFFh!LrsUdRo?y{2#4qP z&0j=rnurA=0j4^?UVwaIC$s_8%-AZcsZ}kyl#^!Xz!o1$&SW> zO8l5^z%6p-CUE_XQT*>P2)|bA+lZAatyZUdTL7RO(<DIv(S z!!z$Vr-l}{EF5EK8+d%*6RgHyki`9NY>^2Z8RG^-bb zBZFmpZ>klfx4v4W6#Z_#gSo|bZFeByLCxHP7IexkdqL`PBFXf&sNH^X$RNt53d+xF zl6-xqGKKW0569KUj+n?iYAbOHgXe_xq}d3yzX^%a0W;XGENu950#NkjJbjj7E}mLz zoZgy1Ag6Xb;m5id_w|ypF`A(D%fBpMtmJmQ4|dj~ZwR_|@-7PmRtkt%csO~?auwd)t)eWc2cGjyjfV?Q^m=d-%#AV- zooQ*IgmKp^I#^cXU2aK9*cshCT+zjMM9e8!`489Ue7uUg)Qx>xQG6|_m&-pJCx?g6 z&SZ2)*wE46^KblM{=&&kWE^h}w{h^c9F8=lP1zOPQ5OMp4#L8LX}y?S*#uoVW=SiY z!XQr0tb-jY*O{Vb6ympCK@(}@=H69u=M>-FGX%#MEP8|P zQnz#`eSn_u@Y7hoiwJt|?u-FVgSgq+>0AG3Qj)M$Kk?RH@+M~Dm^Rpm#+ARapfzi} zo>0W#l6r#@Bw2*WK@n!ZLY9%GK6G6{^V^WoquJAqa-WrYgZw&#jtjx&gSv26`}1dO zWp{Mx7lY@+^V>5=bCios8lT5k7oV0ViDx{Gw9BiS9BcuT=bouMEyHfj``*gHefr5T zIwlGwq60+?tZR(7jFtX@bCd>~hB~rRyBYA7jpW+uFOblJo*e^neGRvou+3b(nGlLQ zi8+tlvi-E~0 zc}$R`*?f*hAcMt4nuiY0-BFYJVrYwApZKJgB#pwJAajzdZUJk}G0%|FRP8bae@T|U zN9S#{Pck>0G{@z+V$y_8)jb~J6C7tYDe3f9xzxgvM|N12P1Cd*7W<+3OavrcQg?6k zc|BlKL1fr1*sAC5Ha*^kArQQ%(QHX!HxsRDSjK?LVB;ySzxwkMC8P7C@Vj)1&Y+1@@;CZ#oNFBK zJGG4rSRT8*1XxRCb;}8BR0#@j`AXZL9qex}V#L}|UW<#21Zi8QWjFPFToap7REt*&uGCH2nc=o-gU^gAc`q`juqvwQuS168NF)&H5>9xc};y7@Mh?7Gc6AR;U+DmptWJ5J@> zCq9DJ7fP-@CGDE+!{+Sh;3;#=6URjAH7GaT}&=w$%+-*|^Kvi_Z9O6Lgv7RR!H$A}r0D zC`rCWu46=XbUnA2EVC*;)h)6Nnd4@_Z5W`#-2**+<0B(uE)SB?6`AK08Vh^y{%~y07g&hQmib(Vi^vcY?k8)&Eq}*c?&_L2 zBcDz`0bXD9PE*2aW)b)9Jm~~^E5iAkumE>>TRM>(#yfRBuPCK|Q~U?2J;G1|1+1aQ zbA`ICv3nfq6i*50qw}OPHOj$gB=;EWs#4avsAffX<5HW(VLz7MHf~cG>j5N#+`;~s z$Uygf5%a9W$Mm9#X13GHwE<^N-;Ca#Uq2bhKh$H~WTe|I7J{YLw6zCx&jCFOrd>cn z;}fzC*&@#JImi0f-iO%O+}|r(wQFf%x!v=6a@R`5+DVeEt7p{ZgMVOGMAPEk2_!;L z=LV)I$2Eg((a}#I)Pq-eoiwr#A|i3C;JBZMyh?~UeSz_AW}}&iG?J?5-9=Gz;>fNC z=nRRs8@nQv-b1hB&uOK%+ zXup)z{p%z7k_*5ct_i~Hvu{S2@5$xHPWo0-AY;w42p z^3<3Wa%VK2^C93UUk7 zZlihNiO$UkYCY<2+=(e%sal~|lh3<*A>pCe<_n*1d)L}> ztIT2Mzoh7EXvTgSXBrdFMIkl2$%tonz2)4(Sbw$c4h4;Ywg)3(`+kr-i`;(Z%(`3k z+`eA1tl$tS%yi*6zFwBf;Ow*VkkNIt9VCm(VtiL!xZHjaZo%q2|JCjF#F{~B@WjsS zCeC-ec&0If47S@3n-E0u9!k0?GqJ)=a$vy7*R`y+ZF|qFO+WH2nTg7yyND&KJRC+> zGcV_dQ-3a>Pw=FyuV-pOWUD-%${clf%Oa|GcKPEYydjK;h{0!S5yER61Y23S%gR|V z8%&QQxq{9K9-t!BVp3VmoEQ_Xy`%m|R>bbK={k72w{1jaGjV*Wnc=+=j6_hgiqg;$ zmuFof+{=X0#B~*^Nwb2;y@-niQDZaH8?rGzzTwVGaf+rt(+~=^(REl7L+25aQ`5q530~qcrktApV+YQgC`adnl33+hqSnP zO-ydKouqRZsD*1M*MTsUML*sdBsPBi8{v(2MWe$kh=FPp#Sk?3@2VvHc-nPD6h98d z0*gGhK|A%}dM3o%(B(r#eq=iW;)jTMEN*lsFyjQMxlb6ax7!+c5l6aMH8 z?C$lE@jK|xfRZ}M+xk+vlBNpRsN!ZBb7PNV#hn0!b80GAqB^L-3@NtSAyKhvHmN?Gb&~4)1zi;L!|zE*>wcScF_3pLJY~CC^q@Vk}89 z!toOqGyhLFgSZs&UCi^ibyFdWCzq<#_#zq<7NfZY^&u0Id`Echi(CG8NuhBGdXD;g z|M)lD=KH3&{m!mV!RSJSw{)2xi0ru|qzN^U2j#T#RTn{Krn=fF=A0knDjO%(_tEf= zA{c0+*9f_*Gnw=2Q7b=K^?fS4t z1!*oEV(}Av(SdYpboD{5=IoTnQ~sp2JxHI0S6{hVaGclJ4>Xc;3yX{jv5V8g+>_s zeDOTK-nrF!`mW_9ZiC33YAd)<@TjDFj?5eRHU6${Y?AY`fuN#B^q2yd1tYoXTB{wy zpkEjXPt1k2Q`-$64fjoB2yK1y+Y3zuWyfVjAxb@({z?Ijpe_dwuMSC1&!5dyp>=+n zB0U)4XdFp^rBm4N`{qWJ2iC#n!akq`yOx`ebHZwHXaCeh*_C`*{F4hMei?tBf!$-G zuG-pdY^)=?Gom0QFPC55mvEIwyX{|0z58|2o7(B)rMrV7if7Za;pg~A?rx2I0e(ff z(fh0T%tvIS*mlQOAa(yZcVso8d#)E+~)(x-827dy3lBGNIIc+mwyM+7YJb%EY;C z-VMa21(7zOQO#^!b|r2VaH5J;r5I%6*?AwF%e;)P5P2Bz-<7?3(a3hf&cc1vl7Y~r zXnjx5%gc)uQ+;%a^2XVuq;e!*5{cX-apg9`8FD0;&(fuR*zO9eMFn$9L6 zu(U5{8W=u>#rTthEav&SHSoYLyxjNP8U4I&F5cT)B+KlPStKQ~A96ID6r~~UE4JDN z6Gx*_;Wmiv^T3$V>0T9G!yb}0IyUf^spG86zh1#H*|5a9c((4V;RFJw z+p8h#q1kWo3VTaEVOvfJHIL7v)y}RusTa~Q= z0=#`Mhx*&#Msr6iFX@pl3ZvR2$(vF?_JvEI!2{xUL`NQ+^5BiY`O~Redjq?p)Jf=x z2zcBHe!|?XW`UKDc5VkayF-t^f$ul7P%-apXa)re|`K z?F?gkn&KmFc}UdbB(=V80UgU=$zIT@$-QSb!N0a!V`=#ba=zbT%fo(yp-G83v74*G z6=RlOl$_ihzCGvq30qfTAs$EEF%{yYF1{%@dvvVA1JE(!jWVy>>eE8Y`ujj#duTVZuXfDgy4VvU?0A*t)*+oZ-@o6f07xzdmGqQ zqz*pAoOB+f{5LD9t8&IoR>pSB^&H{do>ZX3H>t;kB6=NSn3;_7Z{Lndl9&%juivaE zuGQ;(E;MtuFA@!l5nD3@RLVv?Xo6`wDf;SsGqQ1+G96^|uW=*gu!eywus8(QkS5pJ zBl+iSfjm`|3AQ8u9V1FHubH=~A3^X5K99PaM7BoPX5z2a+0|b<1ByQCf!H|$5l?aii7-?m&eSTIeGZ|zOO-z1gE^*9n|Z?q)q_zvkL;i3$ZK)nVh5c zOH8cTzBhIR#szh2`r?_HTc+B$xgfg*?WFq@mUbh$8-V-$5jY?k9@v_d+LEi`HzQ)- zvL9`a%wSE;?>?#hno2O##C(wdcW^`LUmC}{lH*+*3s&_@qupu;08U%zP6^i)LP18p zCTp1y?gDwjeQhSp)9+FDj(5L^w~n9=-*4;;9ql_tbZpxyah^IEx&P}d+UxSm^}A85 zlCkwq`!F3X-a7Htm-!PeZfO39 z$m*C5JFbmEUeXgPF`ncP_F)6|)TAl#fRHnY$Rv1&%6pWrA&$pX(5vS(>tl+x9yqme zRsiZggKr?BL2O!cp(HS6k zF}uSml9rx<(c-(CvPzv+u_wZJ);VCGoIux++L9(kC(Oa0f*0NIiho!4RHJ|}dbLq@ zz!U?LXpx(vCS{CPe_g==l31T#>Hn?BCqI>X=Qu&J-PG}S%wH~>Vz3f)XW#7iZdHpM zy8cXA4*$yMu%+Pio{fJe>JQGgyB=-jI?{1H`(bR-p`Ww`g$gz6T!&QyeFr8AJT-uBiXP|Q8i3p;?nBnI(^JJwL(2r_!_MwZu?^NGw0Q{CRw z2D@OQI1JSuZ{jP*C&niw3bFQ${c-2253N=v#HydnTOD=0tWS(gtfwToP`1lpeQ@{4 zVUy1#i|y%ji24NZRvl>?e(x$0`bR$T0Czko^L|JSL3&^Ig#vB z%u1{@m1JaPAaOXR7C7;(YhU<0{LS~^vvpkchsD4rka!_Fzf*+WhdK7qdk|$zgj6x;eDIGf>XHbu$|yK$J9-!@Y8%zwdW*qbOnZ(zlDRbar@)Tn^7t*< znYy;?Hd=FM)?>?||1rDkIQ3thtsdX>N+hG-Ivs0q$}Cqn6ozZ!rA?fS zYf(^8Sb$m0iTagz{+h;sc0ga;lr7_U%H-ZmdHmkW>b0bjEF@XLD>Nos63UB?u%GVO zY2CcH)Ou}7J28|c(9=`BJ|&1E;z5JBqUAX;{5J1mt0O%1q`EnsLCW>ccrX@`v~84w zm)v$m{EUo|1U^}-Sr)MGEVO8qjPoae^}R)2p{0C!2U5M0~}*pV!k8kX3BcX^4rel;-GgxwaFLHG7HsDRZ<# zp(`N;N)T*g3*nF69e$2pa-$ifOVR)XRL&0k=pGt;_@8W6EPX5SvA;YraC(fR+NDK@%JwzDO z7sSF@wTWKb1i5ZYj*as&yGL#@xjEm+^qJQRQ$1f*KJDbnw!MCDZ6I0dNcqPIreD9_ z@V4hWuaS?t74eNDmb@NOj#2gW8e%8}%w{SVLgXb7N4`Jv41lf=pr?Zi46CQ2xL6ozLsV9p59sLr&I^O z^n(&v4<|PV(Lo_e^Cr;H?D#k7^+^Ul#|=@r$Ob$?K9Q_5G4 zK3g0)knH&t{BLP=KZarx6=0)k0Fr3EHrbP5-{87A1!rf*8F=#};Jek~S<$`gxwYEK zD3tC1-YX~gPsa!xG5$NskPs4%k2A_C`G;k2%<-5}`cKONVJ^+pq%RCX|MSFY`}y(H zBoN~@aDW1d$vXD;8G5#wPGX(BgW43CU;GIDzb}c3BI7mW9qDzox7R|UQ^AaYCv9r8 zO~w7Xth!p2-rNlq(#&-5>P6_DjV#vHnUW&@(`v z>9L@nBBNcblak*?a?VxOcY5EXkK*Pz|HUSG`J(Jc0g5vkV8EiTZ^!rbMOs-AP#iyr zOu>4g38`WPUGgTr1`qf-BW@2vmb_n2s8_k}cFjvVX{V6{<<{L3ql12KqSD~uEJx-? zzn;e~yEOt5S1CdYPyhjO=#XxG zR2N+6k-4~-mbY1&tnLb(#+&Yi8fxTt-}n}XhOlDWD{TsO;E=aSQa5Fhzitcu(7`En zyZ%+luEoKXeM4*ouVk@PwCeBb@3gS%$^b0835|-13PHL;fUW4tGrkRuK=tv#wO{uW zV6X9ccM}&k+1i?%O}_G_eixrST9HZj$viDA8{c8RZ~u7q7+vw&{g-!f`@W`;OA6j}}R=x`3jkXhqA>PS& z(;O3lLc8oZ@)8!0b0Yx(MVHZ0Qx!5~VL-`Zew{EPg-SOTl>GFofWR4tpiF9axB5ux z)4uzJ%J+vONw0;)1th48<-xu`JzjtQY-*{99BZU*S%}uT9E@iz zmss}uOsDAQIM1CN-)3bzFEw~GTnP6+M7Kn*5E!FLH4L>u>sMz$HrScjhgROH8EJAi zkxJUVGoDpd4OG17pqNx^)5*pJJA|YGl0E&L$$+77_e8ltA z5oG>Ou*z`pJAs&H>Sr>tta~9L0>E$xzPrRAC(puVP^hh4YpwnQWSh_=XgfQzYiAP> zCW80=76$gb5g<4ogF@TEYJh+Xn=C7E?U9^Dp^(6oOyvM|Y=naM^pp4xdAJ`jSf;1& zZsda1a&^M`@ifK+sw-&$kvd(c_4gaN{HsPx|D{7mw|8cJLgddntd{v1vfSLOzscIc zwUp`-*qde|xSh+?@{-dVE9@%8L2x zkPmY2aKTyD(lMm=Fl4(AXjAI7SxR|It#0TaCZ?9>LYWXkDD4qvm7%qDq+OXZp0n1C z?dN^aDY=Gp*}}KByyXDQ)Y+Vfzu}G85 z@#_CFtcqpRJ)#*Pawj!Q(&Je;oqQzkL6cwpLSWsFnRFsb zp@!$e-7{HxzM-7ayOvLQY;Nj4+s0V|Hi9B_e_(^uUJItDm&H}FJHB}{ps{6P$;|(G zsb@-rxy%Xv#vGi(G$OJzH8sCP#oU>LY$uULCUdFC{QKm@p~Tzx#NvoBOT=HMaeM?v z15wT^S6Q!my`VrV!`oXT ziADFdhZXp@>;(F|Dgc#bKt>Kbvpmac?0}^&2Kq-GFO3Xd`|aYt?3S55>4b+vS%d%) zKjX*W5~H4gKn;kI3#Vw|5sX;QkdkYKa>uMDp`$9;&QaBUI^5ggE0}Mf5~jZ}sj2BW(Q_Z*4aVL0h=rF4J?h{3i2#Xxgho zc>f+&K)t?jZnep!^RgVI5FDc^jeU-r!*0Z-kAQuPn(sRBD)a=A2rdn{X@8o(1TPZ`_+m4mxFw9WGgPUtE_si!)-HrAe^At zmf+a-N>lBzGILu^>J8cQdsBtb>C7xzfL^H2Y-n%4(i*lYKZI5CnQzrXUE9bZjW=4$ zF4`Vycoi`b`|K_xUPT*oL94-Mwja_bE_T^+W8fVhD}5in&s;8Fuuo~{GPx*R-q`W; zm#N_Vy2sL?SKGRt5v-$L^s3)(I35{JA05>q{U^v~^L`;r={+#JY98+bX;5DtssLW% zcdq_jhpwwUkF}rKtv)DCj@RKSJD{z&_C58ay+H8`Y(ZM*JA1h7oOFy{MmqSwTWY1_ zV055zb?z6*xZL_5nbRNLfOOa-cDbd+j5Jew7ytO`ie+od;mR;pqxxIc`hm{m7q3qz z`m29Znj5@+#Z>+NE6Iq=|`&G!Jy>oihP)qwmJAsI3T4NF47Qhx~In3S@(l?;fTo~G=TE_@Oq z@m*P2QFR%i;`x*S;539b!?Ot=%ySCzO#UU#cijG6ij9uzWi-=T?zLHbBrWCbby6De zcG8Ou8>~k6_s(fKE!suB>u3W!rlTWlMq6nD!3MwQGRe);6D0`?>^4tlht?#{sI-%9 zE?CgEFovbCziamKP%81N;N2g@fJm8{521T~-OxPKb4#y#3ZM+1I|jjxDytk$w;2;U zOwoWqV<40mcy{ZC z9Gs!^n$HYE!|m5AGLv^kUFEEq=iRm72a`xQ7&gDJR`oI__CZ74C#{NYgt{i6yJnQd zQp1oeR_)T&NPW-F2@FoDm!sAzQu|{Ky|9v7==A<+)Wwz^*ce7z|H=sgy*A*_XYfWJ zlMy$A|6Bnpal(50+CSRJ5o6!0_cYox?}Sfbq&yMjH+umT zk+tC%=M#XwO~~{8(0K0Z;$mR8A#L>9-xf!#4y3A_>TnkbJ}6LtDdSh-1grXaW)cpCH%~6+0Z}f~ zWamEG1iEOGK*#KDG#T@{1F>lsUy7N=!lKAiZ;sVh^?N8!~XgWul~bWXx(H>OKHvy{AE*X z|8?5XUUuWa#IP-7SZ4Kckc`uyAzC3{a8~nD+$Xf50gEn4%w_sqe6ZGHEc&aixcwyp z>-=<|Uqp9>LE`cnlbEQDqt5a*N-;iuUIjOLe7r1S+Duv5qik4RPmCSz+s~}I! z`!NL~k#*zB?(AE^$E?BGy#DuYA!^R(IP9v;sRfZgCk1zRaSplt?xG(=du+C^rKge3 z*GX8fwsx>l8Fhx<+QpN4M;i=AsbrY4NK@ra)%KkDT*uLiM1<3Ah_o-97;(d?i0Ad= zkMiWCy!l8lg!q!I&PrMuPTFUi)0RY!IV09lU;N7#jwRRD7QjL?prz3$DEQ9NatlcC z{6;^Y;7;{Anq72$dwzQs zpySTEz`Ah4>goPPs6&?ME8!Wn82ugfNshdkWp3|^^X`0(=w`6pEU#@?7KV?TgoE1@ z5dKt`Qy?p5a8#K|m`y_;HWop_KYEq>XV}|rp=Bu+Mj?ac;)2ma0e6B4f#WMx7uqH6N&zs2#DjT4%lo^jMN$7J)-v_aS$FJ3sTbEYHwe zA$qTLVN;lo3e-nKk{3sest+RZ*M;c*oJ@bGk11YdyNzr?>KY}1k^k1aI`kDpT0W1b z3QPqm$KbimeVVg_Vm`^xNg|7~s!O*J`aeHAlT z))bA1Vg%9td0as;okbC;hnqdGJWx#hFiR;q^J_1AB=1KkZ5lxg;f^A(T~1Oc+C090 z=tQOLwDbPv6HJBWj#^%|9nHupn66(^Nq+X@?;9jJC&Y(V68n4@B9!TvTyu|B;s?fO zP>V~Ks+c}r{3KKFSffjJHyLAwRielj-w~fqbPNADO5{g87_*pFyRfq9eWhSrGoR4( zy=+pfdS6Y#A)jWtUg{EM})4zz>9f2C_2 z_n7IG+IdjyW_p#81OJHv1rj;`Z62U@9Rw9n7FR!w z4Vg|x5Il&PH(y~1gEC#lX@roq6p#%XbiZN^5fACFew}1>DBypl6sZm90~txW_3sGB zJSNexseEH(N*{~XX>N}KP_!q_&n`4E=y>sZAl z^(<^sW4OCi`ququfyGmHaAHt0GGbAfh}Wk_)nH2xL{tiD`)#?9#JXgPw?jyZJ=qi^ zdR!XeF3?wbiGvumS0liis?ikU?j4j5M4Y;b8Iywbd|>U&B>ih3L90GRo15YskmsQH z$feS`!|m{#(pVb&QsYf<8<3BtM2$}1_eIVvNn+Gd*}fR>EsOub z3vwWH0jTYPxh#Xx2FvY|vZ|M|c6Jw!C;>ng0+o!I(nL)o{^F+`l8na2N^Xi4!rv)4 ze`i`Wek5b-v0>F(EeACMgAU8ktV{WsuNTt~$^`mLy##ZJS;ln~z?OjMu@N85(Rw{X z$94k2-H?Ma{-GbDP>TeT=8hJ1xT|wj$<)Vs};zk6HPc##YqX z9i2kwi;=~=wXMX2?A{UCzm3_e)4m3A=Dq#>o!vRFo9m58zd2Y{@a^F$ZBQ%Jn;iJ zl=QNCkQF~53YSM3m;>K!BJ`&?H#&B1vVm6vo*+uFJE9kf(5cxP0V0KCH?~~tM zBd#(2`S%RfNrk+TcJvriRHAG$cd7~|A|oayGtm9VFc=qJQ}~>>@wAqm?J;1Rp1pSf zC(F{zjd*2lT;EjeU`!mCmQ6@XEV*vx>1evqy|B2^oqHy884}PhAM*}VQNF7opHm>T z-DkZI#s3p0F3ph1z`7qbaEp_RLV$Ru#Kb&7kCjFK@yCYS$kSXoz~yx#ZA8%;^i7$G z#_LrJ`l3#U)0UH$m$y zwo)%|n~y-|zn6#q(bwg;InAFdqLj&CU41R-+Wi3e)oS8LoIrdKj`{P+XMU{hAt ztt0r)CGTDqyP@%9$2Fk`@q^&!VMWX~_`9DGXUjcl*h;vgkH(l|2`;^6B@dSzC_CAz zUELjaUb-K#?t@WKn>)?e*5TY?N?3tK&IGjbXZOl=zVp#GF4@jDd;K7f@-js|d5tIY z^Qo#6JxykOEfSh(VB*!i7wbE$EzCVjNE@5?^>_wv2LMw7(|HmS67oW#6dXVdLcx>_ z#XzJUm9XE2oTr?oCl3nDYa4EPOLhS_A{AWAr7v*)GF@fzy2+fvk~L|q%I@a3qt@=N zB{;Mjkv2DDy~pLo z!Q&D72*@#;dMG(Cltpb-Vd5~hvWrju&d9IJh$?`kyZC zp!GzL1GYO&fX$AB+eQtZSz$n$SAQ=0tQ`B?qoqbtqQV=5MSONfl-|6Hvs*rNK$X-? zXH+iZK!cu}uN0?1eJ*>Zw7U0O4HM#2LfnHnhhIw8HRSQ{oGPv?kD7pv8fBcqPviO58+cR-v>MPnoKEnhL6f)!lt2Kf?`oa-ZFH}~4dvW2hN zEiLsCfyEZURE#(0{t}}OZPthM)ZiT?Jsy;i>6_uJR|cEf&gm}S3;Qo`Fo{tkh$G$_ zs5XE2`!F8si&VvdJe)*PF!3y214_A3D?I*}F5c>5eA~<#v^B9H!J!&jL4y_Zlp81| zt)!#dYC6&HGA82xViDA!+VQ8Ng>TMu9DQ*0-XZBrm5gyZne4R}rCV%-GlCqGTp3sT znUxRgKeMKpG;3^yoYX*BP+wkH7+$5p4^<$;V4!asn>FBU(hm#CZcKTS&3kvS#lX0+ z@usj!+W6ZF&|_>784}Ct-+Ix#kxX{)viKbs7d;o0n%8*Cbcflv54@Iu!xD1lR;#Br3pTI^Q%HK8e-z5+PV}9OL|~bEHL*DJ`go% z=|!g!=n$VThyQCheg7mBUFqUfR%YBm|4cB0_)sQn+<;Ke+TOmFtA(=d)%?Kc#pSe2 z&6Q{$A82(|R*JW+*W;*$a5keCH&US4uuw?lAUEYik1GBL2E0a=hX`|eo@#jz0)1{@KWS^(l_7MVx{~={`&NEVI0wcL598;Lz1;&P-4{UCx&nmGQ z%=Hh#fCiAy-^329ZmS>er97M@4yiB6K0J|CV6!2i?awk}#x%~htrrx)_P<~^p{us)~ zWRN}|nLKY(y6_;jcSVq3dA5}fQh2;{!1A?a;l6Gy_(bPz{D#F(krU))<;KrVSpW7< zy6^bq-`jo4jG_{0Zh&kv21h*AyO6{+jv`egft?Wv)>4z_gYs7%{n9X=Yv1joviw)K z&6PW@{sF=02Nw4IOlP()SYEvl)>}N9Mkr-d_;}xDTg&^%E2NNX1Otm{q?uy*I%-51 zkt|~Uo1;yD&SIaYECIfPKA3HzUG@LSd&{7@nyzi|U;%OA{ z8xqf(H@6xRFhy4G=HlCNppE_5)~Kq*|Jbv|Hb#vCtkM!~zqln~weBp#{VdLH7+P^< zJtTDs=U3MaeRI^K6vcAqzR3EOUvTDW6Nua0Cku{1B4k0!1$TUF{~GP9?~jR5Y?BY( zwTo+4Okc*YyLv^BGWp6Qqee?3gX{iK{5ol3mK0uuf_w;&Rc(Bi5S}wVwGu8{z7%rp zxi&aZX!VfYP@B(Fndw(y5PDnFwCqV_E07^OH#g2)^)dq8_CVi#O}?vSv0)Z9n{wTF z;@ro{$^BXWxJQYglh!ppD$JvSyq=g-JybF*T`q zR@y2@U~y`Sxs-HM$u$WR>p^Jd3A}@>kncBrn-vFo7Cx=#O=jd3_*w*rTtHe$XzT$L z(Kf!GVq{sEmebEWxAzz^XR3siSPR`luYjw(=mQB-oiLm1)^Sl!z1c5lIU0*d`(_t` zouWEdCDRsu4aZCS;LKm&9xpF#N}J_aLc?|utot=nb`Ji8lG!!Q%3r&nuf84$?c|s? zJWQGQ=2yV;J#`Y@Yiv*)vTV&=BCJ+j5B-Wqx!!hcu!5}?SBmqQ9S^rF7Ymca$NKO@ z7t_K6De;|QiR-;kbxGKhy+78{j)R7I1w42s1%}l=lRNxVQc3r;;TwtzquY(Qv$31NKYR*w^4bt)G`M+h zF}iq~D-%MM(j8jU%j2**wo#xz9rC`>ed)y`P2KIITe3 zS!l2q!UvjCI=x|SD>8OP=DrblOb&%-P(VX$ms;CyO+; zhOa7G3Nj`A)R#i5nNIlyX#=yK{2gso1cEjqoiN)~wQOy1z>Oxs77?#`^)$(}(>O`2Mh2_z%O)ipP38!ovpjm48JwXTR43 zhG6n7!Je|oj5-*LR(?-KDHTl-UU>gLy~+4`eaffY52ihK>;fBR&B~&ons<4{(eY*( z>0I2MmDe6Rk)r~h%^vk6>2Ls!4(^-Cja2?ZRSU}dtz3s}Cn83}oClH7_iFo6iUGV? zi)hzW_^tXu`F&hBj}fEn!ov<^uIQo$EzNYZ{4Hl9GK9CkS4gyXc(qj0M^CA)LrI2D{lC^~Kp4Hp*etS+o!Yl{(mI?$jrdh9XEU(Bm7?*wNpOjW@ zG_M@V;oe}iiWTRxE#8}?F6iFf-L)+1+_iC0$3DTxr*7S56{s-(Rd<_4T zE)St!zo$z`Z8Y;W!%pA^TAZEAp}8kb$x0>r+?Jm_?Y~=gU+4#1#^(lLt0?JzNb~-D zmEQPW7DdZ*YKgt!o(kz|eX}Vk;Pgw<{(3K!^&$%oN?KCj4?9h?UMLjTNK{nJ-fkPt zB8U-a{VBEYso6sny*hk8*0`y78HdDCS+l)+}fk-?t54 z17j#YL(fL4{dRkb+CW;eK}}79M5CVF+@;CsK8rvs3rBp<-}qYC#Y+}tFFwZ^b9~L` zRLjAUUyL++^Am>#g`f!F)`&e;{HKADxJG&5=IY{lMjD=^vpflrgRoeKZ}OT*t) zpG65h3-q$fpC?9OyB^0TSc)&-Yd1er=|ii9a%tml&+e=>+Dr7nrHO>05Vl@Z!K?kP zWDs)_*=k9kgAEaDPC3DyN3b;~EvFzd(>so0zZuQ)&fD)4oxW@EJadl-Zq##f;`^y_ z&pR|FoH_)BBPOjIRg7>+N}^YixgAzQqrTY%-5C1uoEtiDMa9(J0*A*(1%r^7X3 zAAFnC!r6h75=a;|6XiX|u#or!Yr}%ZvEe<2QpIS0ZpISczYh>!rmKa07?I&oDV#n) zFeT!MP06zK6`Z%Z2OtFh5BsW;9Ib> z=7mXQBzK36$=v+R(hX=48|rpUc4y; zbrUydZC44~<}s4>P;)vLaZSbF9eg>zC|G$Bm5kv((1Z&ZS%XKxid@^IjT(MSpLl&%{_* z`fM{&f#=p)_DhQvU+QqxRV3C+v|PKMtH2KaqQcrxUn*Yh26cY5pM75_3 z&iThJlUcIER~)+2zwpf%ik*#4Nu^G0rOGkY?!%OTN-&34=U3J*PrS^wFyCmlD=N^D zA3eq!s`Jl@zc9Ls`?z>aQ##j%rEi3{hOx(`qcDH z;I=;B>%I~Yl2BHHt;!q)LJn66yQv@B4m7H)W!uXln_{Og(2`Y>2!fwmog1aJ*Hs{o z8_$=nxW;U)HC%n_E>yD4rzMn4Lp^DMV>C{CK^ZZvX+9V>l3-&+Im<=3{Lkb+?!3u~ z!`Bd1f|-k!FFO5Fj4A5xbKO@~)F9VRi*t_RNTIfQgYg-W3Gr~g%qQoZ|5Rd~rPkKhv+&r{k*}cCCsHv-UPk$Qv0s=yI`WTQiZpqjOZpF{dj-lohM$ zbrxL%lpvplyNxFwo-e_vn|Yq2HTL%|LmJe|Nd(NXe|Q}5&ku)w8pJ`#PA8<5QnG11 zqO>n`Idx4_@Lef1pgs5~r~I03HtCEKpEt!bsLo)kxH^)?9y| zQXy?aSw%I%rPV_1-0R`@WzfDn=|VJLIKFcruD{r2j;qQJ%P)Qd8Jq5lYC1JT=I$%N zQKWui3`x^Ix|O>+!k&G>{pE==^7VszC>JtuInHY6$Os}IYs1$Ohv%=Vrn>Ur*aUvY zkmMC#9s^x)AmYlH?dLKZKHg#7kgg}y!oNtz8&P8^DbY4E8;}`)bK-3f>)O96hzQFq zd?Bu{tEgD&TtiN&P!goI+(tDz?HC3V#I25VUyT050KItHE)#Ic9oc6WG|0-zXT*oj zluaJ#q(^P}K4;Ij$shaOANyg$+$3nKa(wr_fOq?>K(D67!^&7Ag^O^pcp3BYOV1B- z`zF{Z?gH)k-^)3R87J8DTB>rG7yTZDrS<1i)CLL~JEvW>bDG|8=XJ=CHzrg^N?P$!<*M$>+jAW}&{-SHcsA+UJT|rH@I=d%5ie5JPFPaF8 z3ypaoeeTHkYuTqNI1%!ump2pC`Br=_Mac zMnCobF(1;}G??ANV4h@0C_fQf%_?Yn8ihg7I=8Z6zO>`PiyI4IHa5i~vLCiY#&LX>xLSuwF^VXfuH4 z2&tq4CwR68Wpon{1TGW(h2a>ethgP4hZOtycLq{ z_u=6C%n`_=ZzJ}Zg(auIpsR(2J8@svV6<08X*F^C7dez>JQW}}*nc=nnB`yEL#l=$1q%b#sUb!Fi2OY-9)!o+G z*~oPr;@7?VVVp5#Z=`rDH~SQ6cfZ|gnsk@S=PzyU4y-I}JfE4HZnE4#9rtpsSXfvX zpICFVltd??n4_X-wc5|IS^=h!&-c5R_G7C?)SkCg_|>P;;Tn2!(LE1?M^rzA& zq@+2NvPp9C#dNu_20~rGN=q3Z;D9Q|MZss|ZhzgwySjYK_J|t z*8Qx^%;0#LxSt=`nKrsaL`&Vp2JI~@&U1k?D@5^kxGKj^R%(IB4M9-{1b;>$Z+G`d zL7?1qz=i4Tj7WuaDx+ZjkPT%EwTdKkygjt-D+>kc;0n9G=H({4^LC9uBb}bSS`(^gXuOycz!hk@8%IJjV z_2$)3_cv~_&3OC$gv?VMpXEsEf$lgO8Sj;8dbaX{I^L;s@=8fd$F-G=jrAd+?(Zgi z6DZeNjJ+tPO%Pad^z8QXO01HaKO2hS}B`^5gDf^>8mUtXpP zvwu&ryu6HrFa+5=#4?0j>(lkVq1jVww$XkpN!QJ2#cpSqF4N|Xp=Nz66v_IJ*6q$) zA9~;VF#gtu@qhY|{MHAYfBIktoDLG$d^;j!@+MPjg7nD<7Ya6Av7a5U&?`my7k5zP=na`;62ur!WCLH;U~(%axB0$y-WN)gX%XuttOUI9xmF(GPQ_w|s*>h+G1 zpWMYln{a`PYvG6D9uf7!PQ+{Ql*j9(2UPzzItj_=W9`??QmVz(X}`Mg@SECNm#=9q zmt(JJH)jk#@0za!fjasy7Z-!g&6Mq{Xj}`;A;DO>zU(|a7W!xnrq!&lyVL#Wp!1j3 z#iHq56oMv|?^QbGpYIe%aGN0!s0G}~>w*4R8b$AHH)9ZHh+0Bi!A*8|;q0B1Jw3pb zN~vVT@XpPKDDPqC=PavyeR;0(x}zQE7!7v2tqbA{Ia(N^AS(#J*q@dpv3u2kl_MJ zcX#o_wj>)D_Bf>k3Ig1`ez@zkl-1OMy)kT!=?@!&hR@t~d(Rqo>$){T>DNw<_cQ~o z+Am|7DP~GQoDi1WUipQ4>*YABQ_w!#k7;~r)9bkCCfMs7@MiKGz1H zoe3N_dPc-cSG%yVBblJZsBNSCoIlV3ieC!syrM0}db+;FHcfI03j=TUo|>5F@EnNE ztnzyc(6V-nG6bew(tISUtM1R>dakLl|Cs}&W@7TO-FqQCZ4fW@o8CrpP!Q)Hv_L&@ z10s?rRUCZN&c4ESQwRTIw1scM$RF}k7ZHG~2T#l(?QpAw)s>-mxqHFYgK|o!bllvath87b>N>ya6cA4>j4$E{&-o!z*#nA> zjDWbDgXqn1)wZ$K?-WE76#YuyZ-WnB&mQW6^7ZwYzN4(oa}83T&r?=bVsl^~ADF%k zHc9L!b*Ir`o9@@AB$Zj{#yi-)7fiwE%;~GcW&dimK$lZ~>clPV1KtLg zsgxKtFnW)w8o#8sBn1Hqs-MLjHkzOBu+535rM$ES5sahq3B#;fvVn>ft8~h{Xp4-c z>{v`=X5r-Hfu*IG2Bx~CT2dTQ{rb+dGD!=5ZLP`SDWHK_J~1=nB6_oY@CB2Vb)>8i zn_k-nIS5%((~j!_3nRU&MG1rY<>lN2?c=N4W7#T`(8mG8efHzyTzyYCgCs(N-iXkr zWM(F&-^GUmoi0iuR}}0KMUfS*&d%@u@F09&S|)Y5D}#T(ytb<%Ot3vwAQFs?Q9;yy zO0TLIN$^{M3>B`s`&U#HHUhJ-i+gI^nvB5^&dXkU7+ePYVtgt}x&$|bEGLU7_R*oR zW37YA>%O~zwz=tmh?|W_A}3+N#7$Vz2?JVKdmSt0PBY%T@nGe&@{jS@G|oMLGi`H( zKRo=}O}Gah+j-KeNfsja!yNVZ#niWK?DE6a0)AIN!#!GnU8I|FYI-`yS;=@ae+ZXN zt1aJXE?{4ra##?sNwg^>ABmox&?0Hxv3QFFY}1Yo4wyUHXHGywrTze_fFE~@6Ff)T zo{d8}h8Op<2WYQxF=pUg{EG6T-cgIJ(l2<=9q^I=)q&6R3)@XGPs=Qok?L=f%H2^U z2bQZ5m=GWyi~h|A>R9Cd2g+$$Bi{V3%|T!-Y6NzlGU?8>(UC^UUBi+rp3nmI?o)mP z{`_u(%90*z2>g)IA~lNcz>w+Spus@jw&PKU$Ywb`8%axlOwF26^~X6u*RB#NB4-UAIhw&8;ReQbqWLN>YSfzY~eQHD_5J-D(`;{jV!&M=nf5254J(p>tZ!&*j zUTVR)Psqln?oT@`*A|@MFc;KXp1GzdNr`g9b^BI)#a=@8+-yTW=_7)-r9H@>RfNS{ zDyr4onLn-XBy&#%+**G4HD-IH{dFohB1RxaU!9ou38%Fc3tg!jy?B(CdY_ugq+HAG zJm25ci=QjdHe64S85-J(C`HzQyR^t`ZbHzd3XmEYHKM@gdpVq$>9jogyvhjZkd)}FBOZeZat@YSk{!D>6Iy**cH za6#FUmrk|mrJ;Ofit>!0!eW@TyNk0@q={ckeBm*_c4{h#u&}}eJ0?&kA4ReMbuFvj zGO4vepsrfDEakFhFdI-uMP}FQYWUhZE7hxRz8)Bzg+ojY?o*$Hped*x8A(Zg0rv@bvyd@^8LRgiea z0&`Cl^5o&gk$;^4sN5o4-Q18)tdF(J3=JLXJUno7HyawPFlgHtwL$2?1vK05Pd^|f zM1j`VKLpju`~y zTHM=PvDw$u!^7@iu&-A9kGsEQzsD1nJ{Uhx8HDe1B>+RlOXOBu7I2$= zy>G@c33h_VskRusz7JHSZKqIwf5f`j`Tf&V5KlKAIy5Y?lU{=F*wnT0*44Tsz_j(Udg#@TqFafJ11YU=##3dZ@uUGaN;=#HBPdg%E=Hq4$5j&%LU6kWKJ)I};W*nZg2gQ9#o8XloeBrESIJ zaL?pV0yKiwq-iR3-HaVil@<<~VP~HyV1YbeVqfx=7{qUCm0tduWs$72eAJT|>+Hk; z( z@>l<+^Esx0+qAUhi3w~8SDh6a@ef2haRjKoz766L;6BKn>im4iT$mu_VrA_)uQf}x z`G^d35CRNfP>m!}pdpEOp3S?DQ*2xLGo?2pLArG@U)C0sg87r=)ud|Xk(G16xR+1Q z4{lAqtX3wBuLYOut;gv64nLa@{uEA4-^9`~V@RwiFE7U3{}1}S6WT2rL3&qLQH4WFYJ?SDRTUBYC?Klm?{AGl zIA5Y}M=-!Xrp;lwWRIN`#kc%Qfh30vgX9*aQVm@U5!Dq8!cYYaAkI<$GI@W=xUgWfM**QD zFX)=twUGxaBJ8Lvc!s!{_SoDM4K969S0Cr9U7#7nzd4y)t>`jY^slk3&c9o?_7mvN? zMdU|EYzsAR2UPM*=|}>sdG{$Of{57(8|_wqR;jA0s&t7Ces_~zUqxY8#R09HDmp!MyWSQc?vjO)LC?MjMg8)3i zZ~x!>j4?buUQw*!tUQC*E{cttqI-GWn@?REMcg(*tpp?@f6kB)-axT;;0t5mV z%)TzenX~&D{>bd>Lxe1Q`3Eua5Z|X-mFIQOqYhLI-20=6MgG@54S7d{e8Tp3YrBH3 zs(^AwW_tQ;(KM!`&qEwseuyX$k+=zn{|@~yOq7Jh;Fuj(mA2bF zzn&J~h;&bX?{nZQuU`LCkWkq)cci-s<0xj$K`xB~^aZy(8I;yFG)zygs zZ)k^plA%m@Zz~UGA7UKP9S!L1sZ-L6ai5g*64YJwA_3LQy+ zf%gF(H6_X5=D4}pyq!^TeGhM$P(;*$_=W2aLM?9ndX&yUGO{8zx7PXmp(*x2x1?%Gz}-xR`2 z|UaE};PXq&t?YXd! z|5+|3a3NvN7jnke8J5Q3;yg{pEnweLs`ja@R<7a~{1OMKMe!zNXix~?1^Nt((60`Z zwzzz=Cz_hN6@vWrdvE08f~&@~5MZX>hVTa}+^@Y^#+B!1FEF`?`q0pz5I^zfuIHYt zd>{9OP1x1vb8P@oosmj#sV5zM;s=C;j>3Ah-9sp-Gel+z6<& zsh>6|I)%feycLz&hK35igv4RsQ2{&da{pIdgP0fO`nI`PkHV;ayX3I_fSn|-sQ8$Y zP*vq$RAdw^mKz)US4oMT^!0nq>w*tYMsRTNU{QY5hr2K@0ongD$4q9np6_67;^MAN z44XDKXs$5mv9Yjv^a27!NYr7y2?SgdizrVj!18b0aH#b=$gpxbQ?%N%CK|& zADDx$Tby^EFTCEZ&R}(J!ahEF#BE3It>w`1kFzwI01`<+Z=t>{Ep;W;Yevo49iF3c zV<@}Gf4&YyOb$VYlR)v70Wit<;!-Fy-14*+%<2rHqRO<|Gv-I42krtSf54W-1B#n8 zF0P{06*Lxe&T3LWNEyrVz0-+SaoL{a>TX{V0peu8HwMX>6EAu*lMKT}ng3 zmkJGya;yskdP0;yQP6dBbyq5D1Bgd$a!$H;zNS{UEdn?q*tX*4X5Rk}Mt}>0wOkDhqzl)Pc zHZQEvl#pNkJ~VR;gaM+?fYoP00XD}CC44^5>6)5>J%OF0k9CH{p^bio>ad_EpzCNj zmzTpXoc#S-C#UI-{-RdqA&W`+GkTE6N^x5l|9P~G{GFTm(FzG^dMv!$Qop*atmOgD z$wh905WiaZOiEy_nDq~(HIV&P)I_?r&NDkJelsip^Yr>O8+1%}K~HbpOY!!-|848a zFkpZU36|#HB@aakL<;`zKimAfm>}P#3lRBYWvfF*%xstBR#H)sb+}GV|L)--X=ewk zo|Ii*4-_avE@jG2PR3xl6rd~!{0V`#01e{3s|CV_&JB$CU$0wy$c zWZAW*O$UdtpF?c7@19W7fw7Cuy1tTAl_RIi_B>6$9zSDRV9l?`B&94TrE#o25>I(; zYT6+cKh5BLEXjju&3})L!R>rl$4U5FyS3$B0}m3wmDpcD^}3VL7C4kVEN%><#*|EN zM^PtK`9xsWJMwb>j9bIk{6pI+2A{4l{KmxNhn-E-Gel5@_^mM_27h7ys<`nqA<&V@ zOrRrLT25fjgAx(iwX;$C%0#ZRMMNR&kYsq zxEntNbT2nsHJXjwYc_wWu9h4K1A%Y=Kfcvr2rPOB8MhVRa7U|ExwXw?q-8bGhi$Fo zn9VEjb@W^0qVwT9yRze#_snR=EEvt2LcNP@iFVcZ1Cp{}r#VW?7Khe(WQ+`2%z`>b ziE|E{ev`v@Z=1<@XH@sx`coKmAf-ek<;^V$74<&pgy4CRmeXx27ie*zX=`h> zln5qJ1;{xey^C_YEmbb%Yj-+5&dxTo2clpj0+YU-J70i(d|ze5ja7rq&%@$qh>T^ zw6EVs7#BC4jfSOSWHdjZFouqP-Q2`vf(E+s<~iw9*&$KSNO?#-I_bq$RsePihj}#< zLq2BSrD3Nk5{Rx;bGM>aqy;X*TGK+uX1^#mCc{-6tKR{24B@?3tQkYQ`ssXRp`n0r zqDvP2io&rpj^sePQ>p?9xiOJ%<1a?2T+aD=5Qp|tKp@}h>(zIs`Kcos@qUdXyY^T*_A=JP1l&@HztM# zpGcKzT##MeJ`WB)6f0*WB+PO;oooZHMmnDzR=Ouf?=A8A<_lD@!(m=Hft$VB1ClKi zJnAQ7T_%Emwo%30fVP=<&~)*_(962}k&Xtst6WuU_R{{iR+^F*^pf1Mxj8BOsJx8< z$47iAwWKG-Pa_R=DOI1*pJt#8sndWb3ESrRv z$Qlaj=d4mBXx;Wn$Jj&D7#udW-nu~hivQcjqoSQK&D1hDhXkNi4oWrR^}s(%j3+hI z5EEyTD=ZjN?YvE>>1}w(3afX@RgdfYk(C^5qd5r^I^!L%PeP`lAOQTE_XqzAHctF- zr%POh_Ax+DIbygBwASz7;wL9BY@^%1JUoW_WLwIjxSqAu)nUV<4#j79oQ*qX+puYB zw$`7jmT1}OMr@V(Kwbr$EiN{f^|xtO9xUA>Fw)TC_J@T6(d?|v>peNCxpa0up9Dw{VL-zqAL~qO*YgZaA_AC8+Rm0~u4*RH(C$Q>aU%k` zg>B1#8*eVBdFM3o)`~*rOZi>2&QEB#{My2sy1RS(v{9fiOGPXOKudLzexKtkxBLgu znzQ&VI;Mhuym z86BD|PpTgj5iiJ*Mx>;nxs2eKt*oN%YJe5c{w;$NxSmDqHC>!rP0L<((VoxxD`hqR z6Tc6No4dBtaTOWB9hv?p%4t3Ej}v59^lCVkt*RQ=YNp#crVWPBLWe)k=T_q%92%w_ zAC?a8u6grLSQDQ&Sq?YY{!euK^so~2y(N(ycG?Ut-B%>cN+JeGb` zl5#t}2iw&AO+UvNFG&dpb87W|vpcQ9Q|hZhPrsDLc~i;oiN9-2Ri+sl+S7L4rWT){ z9Ye=Q)uk&Py^U7goWD^yC?zG3&d%k)Ft|Tl!K49M71fzsyftwN-1sZS%{QmA^_K6< zwPw}s1BT9p!Exw&2A~c|wH}~>gH}r@(?kPL2-689BuSx3D1;OtIaFvkP{*xY0>J|9 zU|PfxE-po(KW+!IZspI}xK+l-fT1AaVT4FD-GH`6s!WQ^6HUgXysKWwig`WHmTeF|L(kGkeDY5%C z0J6qzS&)lp3{k0wgFr>}Gf^Q~S?dq}7&XkAHa6ES3|G*?}Bo|Ps9=5g}edg z-`utnYAUmsqRRNh|=ry+L;|K=u2L1yTGD zl>a|{#z2p|-_gr~u;#euZ@!0tgK~9` zpDDXIsg>}12mnsqa%pILJoLgN7X`f6f4o&DxG3%~$|{{NEDQHd^o26(;dYC5n}{+)&YSO*KyyEpp)O~lK@{2%x5 zH%KJ}^3C!3AEA3l=3kM^NE^tW6kpQuj0r8PM9q_;6VJSC)K zwEA6S*}J(uSZg0Pt{nY(7oRSz_+_C3a4F;Hb9Ok&x zmznMma(Q2M^lxm~vRSmy5jjJZ-3qM2;maCYy*8?ka#Xzo>)Hd85_!5^22d-zP#>*Z zVb3nD^VV-lhL9XH%FQ!4Lxq6%r?PVXa7KFrvjKTuw{qQDctjd(V%Qm0vl7)<8~pVT zPZP#}8Uj?#(O+#>+Hohlt+LBoJu>StOX+GlyL7n2Yx4sP4uI&W|C)M?WPV=W=27D= z4b1}~pGZR7^anTux31?7g+I--Ns31?nDm+BSZ}YVx%7+WlPBevhrGV zuqG8!27bd#GC`D8+BRk~F)+IlD3bXA;#HJFbE&TfCjfL{b5X9bA%0?p_RsF;E!WgY z4D9E-sO!qsgT;BJyfQU@w~ZrorNOg+TH-fuyZe-4x>MQe*>D#z|W+G0cv(K-sQAg? zv;WI`2?~D2#x?|aueo_Yl9bP$KXC#2;!{i}okdXa=v=KvNSd%IWj#&f(Oh9yXW=jq z#7g>VL|K0UU@`3$>)@~v3rkCJ%u-Hb5jPj@XxCQpBQD&8gY{}FLk80n_x^kQN#C16 zejXVD%V7y$=Dz6Pk1}4H-}-p7uLr&Yw_!2}a=GmKWytaopU$x=I)ro95s2UG?_B#2 zAA);e+_!QS@Q-n;tF12=+&X6{`Q#s+cQU8L!8?$US}F{WG%Hl}xjf7ln^%!(nvD+4 zJg^0mb;lRO8irZp^7Er4W3|FCUp+PI2jr!EswxZ*9XZJuq(oqYeR|*w&gUDxx^5R; zy4^0jmLF?rvXpOn^G0l)mg#-3lra&)#adPo#&5l*9p*?3OPm!>(idbS%MF9=Qx0~d z3RRbyPo!&@r^xQEVV=R&?P-y58p(K&GH>0^v*U9PM;v4RrAu*FMCDcnEP2BeCKCid z&THO^3ThJbUVQGa%?iMXOIft8RckykC!yzKkTnrELVUHdI0_CxUiEqcM0o-dgMmoCbI7%0Fv6}r+r0AbMp98>pZ!}S@DtLoE;>oA zNn>NCCkkQf9YS#zhS5AecbrU`Ct%Xdg@hc%VQckO#23b|&K6TEgN_f5ic0p%3jHBr z1}(@t<*YXP{eVeRP^Q=f<093URDVD_i%XEHthS`4!OOncU4RV{8ov^_#!dlw6CND@ z0fNfXCq_C8B&1Z%&rf~{`T2bN`+u;c__@%gJ==kt%eyQ@cH_x&gqacl6qS?;uubGA zgvpll!^_Ir62!DkN`Q6Ip$3w?0)xy?<_g`9lYf?KT6=ol3u#>d;VIA>?ttSAz_D@S zFW_E3*~p(2wAJ~w5Fq9TtYSlWF6j;EFhS|+ekISVb$;s7qBH~ID`%t?6|epWc6Q&g zNno3(%6lq^hupgU4lcDWIdQfWf-ccss>F#_cK=vjS zp6XtwMN*7NhQ9L0+BK=Q+1}>PPcnhZbRq@c8l09al~{aIil_rIvzrQ78#H{SB| zHoWsTeTU$C>{PH&MD&yMro8VlGs-!8wq2~asHTc&an8kjlM7cvt> zSU~o41AjK!k@Diy{){Jp3sS}M{X>krl!D0VEVPn@`2g!wqu}`m*y%FiSf0^ox3!vM zfjrFHcf9FlETwUJ3nb68?ct|WXFsK3hz^IW>y{qsLt3^;5bGh@zqpJ4T;*eUMk+>} zdA)bNKcITGu4m zUgZF%f9}r6YhQbd7}}8~55npx+5gEDXnN`+icd!XOtkULl^g-O^H#R5D{bf;theBB zG=ViI*C4^|ur3p{Di6@wSm-`P!qIVaf~46cgl~(?-8}-(PW?W_szSz#`n>gXyXMJa z*L{l4IRI^WMEH5ZWV}4p`0Wj_A2x5aUVp_Fjw0wsRll1*9=`>Fd`%*%$?{;;^p8{> z8mL(mUw%jQRx{P=Gu+Yvknj)G{!a)PoWoL8^4lIZl+sODmY6ED2QiJdcW~){2Q1z) zp?&wB^o$!l8goKB$5qi+l{D|a7&_b62%HAz#8U&Fqx0LMD?dTTRnd&(Fh5$uK74Jo z*NZg}-4X}#c|lBo1w!Q>{UYWbeLi!;Dfq!-6PBrJRf5wj3E;p(02dbu$AZR-=+@hG zx$*TW!k34c0ILQYc%KoCb14-xM%)(%>tCPSwJPz;EQwHJ$%KHbkS3w@yY#s}=6rv8 zY8RtaifLy3066_RA`LrRdmEtBjWcLO{}y|I>jz+Hw-ta}LG7>r+cyE^YJ|lGxY**m zLe08?yeB@eWMovfT0#TPJTN`&Q45U31xgn0xqtvo$6TP_4i2By4;^_}pqt&Nza)Cl z;H=NpL0AMI)FyS+fRsC+UEpWuE$jUM>0j^8Fhr#FhsTNcARS^lS66^QCm`#TSCa6O zAnW=f3%ZkDXG*-dYna@lGW%x1dQtBJ@e;%qzZ{0N#|sI<=*ljDWwBdDqa;k@x!&t} z8ro-(Wv!Ls;%b_Bgakbq0q!CFy%Sb7bCRtk%XXsO`jS!Sh*9T|ZVfnRu`vdJT4`?? z0)){IG~?jFf}!kJ{O#ShL9t8vY`yxP9xFEX`m9?R(A@~pey2O7`S2q<(!3f#W!ce% zan7R$Y5$PLpU|kMQ{Lru)dqR{RqyQ!N{^@>Ow!gc%QiKw_z2Cat1nD&f!5er;tA$1 zp4=xMmO>FxV1K9}e{VOUZ$SG>U_uMhSqC1x!E<@*$q%!;yW7M=N<(9}qk~92X?d|e zU8u`!Y7*)L?mGU)2elyOK2~%C?1Ov58NG2XA6AWu!vf|7!q1m7Soh!9tbTZCW0xrX z{hNlr9kSFthm~QbhdRr6v>in?Ez05`buL`ESYf+Y?2xO#)$AvQ zg_C?6F++UI#xvDAf&6p^=ZMNL^<3`8wzFLq81vEp3&}!9#$Xj@1r1oqoK{g3u0qt&Mq>uK24uKJA(rV z06xCHzkg3R`#81`B9qkUD#b}T^H9^+G-bSBq0-in2e}iKO7d1HDxLnY@E(%!6Cy1q zLe#LaxC{F;g>1NG*w!GM47g8qpAp?`qEpXK^;LLyi14h41wL}`XOVJKyyV3%3KHWA z67+Q!kV+kC;5?3-BINRR&0NPyIZV|oQ})PdMxRCr7i#aY6G6pn9ure)s7Wu(XY&54 zQzXpDAu-~H-dv)b?L|8yCCyvU0#K3uO8bjmt_ltY^RGO2Vl@KG8`ztg`1s6gn-Lfn zm=!HQ&y{7QYw#b@x@f)Wp#K$lCx+NR-hxmKll#f*$npIA4z&J_wxWWPkWWM3oXAs~ zo@%>CtRx~Ft&hXBx957fC7Kx2CMOXIgou~Vyk##BVZXm-qIJI@J>=w+Z`^cQNm&p1 zwxLa9Iy-x`wLPSy-uU>&5(uP8Go^>oJTF(_y{}SR(QHe^G(1*&rL3x_pzPW{zNu}` zj+5lQ7gY4)t>7<*txDM={KzMb@@pDqrpJ58Zi!bC-Kk-GqPHU&(du`^#AN;R-^Uyr zL(%#;3k!c?QT3*#X&)JpNYK^u6coCC{P^Q@=btw>lV0q5ZTCZm@AdXBz3p z8dXyJX5pK14hd=+q~ta!yeuDmXkK!!Qy)2P+Q4lP-w5H zFyrCKbaO%L;|f0oS_tfLId;hH)Y00Ebfg6dAGYFxn6||l64#sRY0a@SJ);$^f5Tr% z&WL?$Z|k%fkTB$YcwfZh`)GY!Ygwtw@qxC zES=m!Qk#-$@9?FquPe;i_pXMOky}zr|CIi>!LDF8-UtXL z3HJ)Peaq_pOYhIQr!w6S>`4nrCSp-aJGs zjg7r@P-CaiJ@tp~snydSg@9@GGzrY8=_*f2dUDI^wLw@#=d=|Qrn$z&&{adr#?PvMD|+N&uq(vf|JE(52d{m<6rdkE1tdN2oi;0MXbp7upn1#I!ZWRB{Ogt< zA7|u|F>&Ogv%1vst;4OyZgB-or*8wYYI@K= z0KeM2(nErwp5gXQHK{4&Dza-hTYBhDakk;%j}Od5>txub@N@iD7oYb}>-QqydV6sF zGavpMnq?AkWN@&j4_U4{!_BOO50=+|lOC$#T={suCL$?^fa~cQq3oL=8N&1-4?Q$A zxtGCQk(M&T&;a>qP$CQ=4>~;jP1h!InDmH) zKR)g-(%(DSgPFKzB7Ou{pYlpTdqa@(4!(%Uv z^yzf>&hR3M!-GA&c;YCwAkcwVATw*`bb{&Vu$2(;Qx> zf{gpQlOsc<*M%SdlPq^vBog(uwvemz=oh4izP^ET=bo=wdr?Ev6#2N2mgc7RGiQ+K zaEX&k3E4?)Y3ck<-^1ug`-9WBG;K1-wXe?BCvFGeM+aVBdZzt>9Ak%VuD`f^|I^q* z_C34yh*R{QqO!h%iiur#^(eO6^8Pzfi3iuTEVb12#D%Y4(}~55eV}Rfjw0;WWL1sS zZ(3+-8150gdQ{1y2}^n=L!C7b?u#CL6IMhz6Lo5Os0i~G61$() zNhpqtI=}DcPQkm%x15qIr=XQ$L%aPuintVe$(ovhQS_QrNheZ%~)b@ zMXr|o;~l(eC#20>LS`M5KqhtR^{(BwC=O4!x`rd4U~MhsHeN;5Kv~%UtxR$MzSY^Y z&(X>hk$O79XH1$2qlCWZ_bM+Ao_vG6oEu7S_<7aed@T58g*TeyYRe<0d0F)RbB^RJP?H#SVz>Hf8AA;%3wfDW$97#8+^1KKd^~OWr}k(Z zKK(*L)dcx;E*;h6-t#J~7y<-Mn0tCC3=Yy~-jMEOkJi7J9tIMX4mw^l{Py5jN1{*k zd(%z~`Yr1&h>KLeU?ae3D;Sma!P25a`is5 z+k%JJ=ib{C{d=~%k5$Z&9zJ^r^M#t)$7x-*;^uTbc_lM0e_~_YogW)>N8VnX*P4&Z z?)cd+9#Pn_JD8Rp@I7T-Cf?@`+VF8&2?}_rJuWXGA4wd}daLfEWlGKa0W*&R+uWklvpYyg`Ry#uctMefc1;ORK0(6Ke&42Ih)({%4R z@8+@O6Yx^GTa-cC(-xZ67Iwj$5zUL$@c5u1LYL(?p9((Of3l5SwWXZ$jp?ojZ=ADC(Sk^4* zp{=A?cjKx=eQmTp1_=jlXO0D4yNcGwb=20FaBizg_>9)a^|rKF2=S!b+n{wi^lL^< zhJ`0@VZOq`st^;Kj>nHEtLU#?C&kWj)x&+-p6JZ!VU+mc-Mf8izGL4{tdZ*LCwAG$ ztx!XQa}R?_OTu8i;^})YJ;+^x{d(#THwe5)Zbp}pG%rUE&f7kj=+`1Y%uj$*%{q?M zNs#x!lVh#iJ=+ZOtRH8?BA^ z=U~+eD?`WceX_8VzZXQ>3JugM997-E`)xwSScl$F`->}g4=Q@}5JpF8QYX?wb#&mt zBQL%-k=9(1Rz||d9KP+KBy?0L3n_B!*UP9I>%ACkdT>tjn5I86C!woKey_^uhoR(E zAECGY!9~rza-Q9!YWuxo4<4A4B3E{R=Vm7P`}P^+RcC3)2R1eh&s5SA=*@@}k+>K6 zqazo+y_h|ccVw=-MeE}f74=S@dQ47!WPQB@pW}1(?9ozIF-%D*?(Cd)EGa4~V<)#t zWhLd{i8yo9Lvl9A`(798&Bk^owwC-%i9SZwS09Ib+u@VsZo74h!efU_@&o61yRh+S z`4*673^FkkQ}7_Y>BFsBr*59R{fYdRwihHFgN;*7nVD@rImiV^*esYf4iuE;n0xAa-VX9u$xB$wTY2=*Fc(n{x0`RPvoabI0{oBgS)I~s?IulXjkU7k z*%4~u-jtc1Z};4Whsi|pY*oWI>7gd^10RzS_pWHGkBylbIVKurT*9fJ%aBR^1tAW6 z+dn+9sL#k~%FfKvRWW4dN{%H3dNe&$AZwW6d|gFD?tSTjF-=*SjftOp4{@2Dxz$Bl zD-bfC%jhw)dp~$r{xzi`-uHtztL>FHn6bsE@sqwe2g@e|*QTtD9D^H1+jx^>Nvq1X z1P5DTp)gZ-q`mr+ovVaUkjW=>r8xz0pgKZY!s4)Oer!rp*4Hn0&l&UV&Ch5eV!K)r zK6nZXgqV6Z=T}jT_RjV5F=c12%bMQPgM7GEl@>hgU(HRB+hq}9x?9#&eN7}!A}fuK zL^jDb5+ccV){S}7KCj9z@kSim>Qa)?`nZk+drKD9U?Vr=U2V=z{(9$(0qgF90@9f1 zVzZn2o40vrJSmDxX-WwVJj!EsRHLJh^o8lm4RRFX@P2C1n3-PcW#lBzX2iQYH)rPb zQ1~f8pJ{D=5GnLb=&G(V=GdCy>WtRMbXHX+=<6WQGuzF%r66zWx)I@jRW?9HfqpNC z_-oPw@+LlhY`1>HrNc)a-n?nDjYW=)UBP{NbU@6K9!5)oeGf}$7a~hgPwC}z&)m~U z-;DZ%(33}utMH>k6=`RWJo^7R z(myoiLHepL?N!$r+jx^zNBHndJWgmKjy7gcq=%$t(jNvG=?fjPLH55X5ZcS+g|FRn zpB!&k80^L+^ronPwC-~t&yG7`m6Nf*Ix0|@_g;84vgYcXNDmo4u3Q37QpWFkq#;X^ z_ohWyEpoddK3I77UH{@Aj73OE$=|kJ*4$#+5enoZK6>==lc$q=eu&JhQgQK{oLtJ{ zyEKm-e~_I$ZKYpOSjEYy;OiTQ*2j_41M-e^WJmL{UcL4pIeq=pwo!+&v4~^haOb>vr32e8DAVKa(;PI$HT{dZ5P)v8)Q8>4skHo z&06$q)!4aUQ#Ja%H{lU4EE!)#5W8b!_BbjTl3pIZ%72$=LiDtcfUF`e#(;T!QCJ9C zAJ>pe%6vJV?#S&FOEW8Bp6=#pJ(oW6GRE6C*@+3*N{E(3P8*8Y*U@e*#GUKuhStY* z=YKAWnv5y9iV!?)+?K#=ftJw7SEm-9K+D^e-;tBn^=PLg=9SXpvlCl_p$2uyn|H>KdW^w4ym%vi#toT0 z-_2#$o>%GPV=o7)vd{BhH$yf;n956sZandy{H|b%k`78}SSEe*-6CS6T;2DJDsN{~ zK6vuYOY_M5hOu4c-inL+?;H3`-a}7*oQT8&ec6*uZn5e#837QG^IsJ z@;pv#7|QZ-?N6+t>y zH^=ng>zlB0)!EqCX$LrKYnyoaRNUOBeU5lM;nSxtoLov786`tQ$cHe#svLv-2_K)b zmGvjIK8~Co#(IkeLLFT8iXiW&%HcEb-dIH!HO=tcNTVi?8JCZDO7s*Y?B0FLVeB*( za(X}>xHQ0fkI1vsw#hd%+RSRtA;TJ^fs`IJOg>ek6GnTV%0IlI8AAD$1-}ACdKkfC z<8|K~Y+9|qVP(3DBho^^6N2w*yEnbf===J>W87tB-nVRBd(gZ*q-7KhlFE& zNJD(c+QZGPcO5RTqdcD&R~hTZFPM@%+40sC@YX^|x?%iuvfA*Esit}{&+q$OI%K+y zIo#tb(uk&qa&##e=_t>0F#D?aChWGViv*joq-;GYP7u=`O4(g_F|nw$N10ggo~mve zCANNb73PR=o!qz*@wlP>!I8$V?gIS&58tL+nWb5prCFJ!>fW)~!jeEas1u*@^2JlMGR;6mpi7F8PW02;AI?Y^Gw~-!ka}*4jMOX- z+~^!%urXO4Ry+3<6!eV}TRvSB^*5gUto!o<97K?ONqKCV?1R){(JgU(nDu?w7mP{(rgY}g3P@fzo z#{D?Cc_cT$Wv7UC;n;x+vaam-y`ndrbCCCzB0ap#=|I2AxY~^Qria&W+_-jJEi0XN zl0sfy*>;vIo?euP_Y4g`2#Z|r>zj!6laLR6-~KxWhJI*$963ESj_2k4#=pF4 zD9EKCdeS7TnQ}csKIdX0Hhq&GJdrP7*(V=y;U{03#=k71qfw@oyQicFMn*<_mE`o+Eps}uZxD+`Ch4wQ=5hYyx2ONjmL3T0=^h7nCw!XRG~zm%+6oKW zONtu8&8@gZGmxboU;IIcFWP77p2px;t&=WFcDUghzoXnC?o(GI!znk;xLh~xYtHsO zA@VhDa(mF5>gIU(Xj#n+>A^#2ch;1>+m0A(TfXzPT|`1}S)}}7OEGbe)5n8f=oN-| zeiY{oafwIf8Si@_z0$^TODA!Dhr|24q!qrn_+%QZTON?AC9OS>^l-^u>g~j0+!kSE z!FQ&nW0X)I=Pbw`^>Nz%F=Dtp+);oFd7d9Ho<@t*S)Z#ad1(|UBH@zmoOYBop88^% zd|sFxIMsl}t-<2i(gPyZ+QN*Ry|ZTW4*NPfY{Ug)UXw;XAy4|Fyo~3WW8_H_8q!mZ zIk#7Qnfc^Q*Q5LWZ>XY`af3C#ENlJK}Ivf7psyM*fQE>Oy5YlIjqz9{mS`$4+ zeHm_6JNFkBVMd8{0T+ZqJxOPCkPTJGdK}u7kv2nm=xJ%T6%+8grZl;p9TRV;XC=gg z?V5Jk7~DX=t)$quXJL?KfAlh8XYQ)}!5}l5^~=&jb90-#f;RJ3ITlv=t=r^ORSmG% z=_75vlOB+*f{wBBW0&X3uitnVqfJQqb9Pa^ZKZkXon`36mSB2%7#%cMyDu!`Hf2-R zdH1B+xp$-`Gsks?^k5R-f~*1VU%Gwq(PT0$3-M;-Qn&l&$VEkb*B@tNl5m$R_SdU{bFhEP^9l9ik6WS(kp5PRsz{aMn3Z6{&0A};LKyWokj5}5qY zf*e=h2TXJlpnjAdJdky5e@T+0h`Q}qA4W*jwH^)=Cyo!N|? zIqcDW`#qwDTh^7wP3$mCntjydV3OJ~r)!2Ye8jDv9Rzo$la~6%fIU0>&8PO3_r@sf za)11hKR3zx?Atp-9pp3{*#?g|+zM@^8=Btcr6N5SrTe zQ2+6n#V!?O+g9zMWg>98ykdMsGF0|Kge$~_v;`%nhq7t{vVHU4&-3At!^9E2rVo!z zcb~08`yMXYUwlKE9@@yuS;5nSmPDJx?Dc-=2`| z?qV&(jp>+S6M%3%vOhrm8d^aOXFoY>c1*dkb^O&g$2sr_ehnq<`;Das&93o12sAwu zVvuiHsFawG&g6lWfr20hA>qvN;c-({CdBKkU!LPmd0J__p{})<0JeMDuQGMjR3qQs z{GdRzK3%r=2V+*I&I)?H(#)?(59Jm0Qc|y0t+{aF;wuYFKW-k?O`E0FZyGl=Ov`%T zNe{J&;RknJ6OniuGv0}Z=qgIOuv?kup!S!^enL`uI6avjMoZ&9@v|#GwoU896Yv8a zZtwKi_#cLjT^}jS#YKGgwDfS6@48V8Y4I)!_7N7iWglHlAP^c0GOixKv1*5wV;bdm zt0O;NghlR#abnLfG9xy@&6!v9{-;vx=t#rW<7!L-58UEwM(~8X?Bo->)NUFkBU^P` zRf>erWO_Jo7)=kM=EmF-Mim1i(>wb~qb5~Fm9`5#^-ZbA;|Y~%pN|S$yYD#G6^U(q zdRa^0lvze~5AyI8>0eHXC|!9OGxlDz-8(v&9(aVNr3XnqG(EJZMhJ*#xTSXxiKK@m z5(vmcOn>~*&dy$L9+kWItkL?o`ub)*epPq(X)E!ruAbeywe<8qqV?&To7<7k*Tj_a z3{NCI9F!%Ed+5rI;oB;A%PIx=u#itH+TM;&{82<1W!DIj9(Ep?mL7KPnoJL(3Lazm zW5D{(ORfbXo3=DEmu#v0k@o;2L1by$)2v_r@E5{Z069!uXjbk%Rl zvWrjj@i~3y)-DOH-4a@RPrPv)zwULqy*)4gqvTffkNeBhkFqK1Mvyxp2@!UVdv~h~ ziE16a@czXMtNjP=I*x7frz0eLdmok55Es`Hm$-f4*mL8Ld8E}Erdsak?elU*S7l%9 z7QHF5OH1u_NZZ)zVW8rxAlGfrWYPs{D#LxnB_4ihBrQKp*@>qGl{kd&J0~`g<{L-o zC|8xdc|p}gMe2#Ln5L-MEe+kM?(x@)sV#YS?UlrCEfLXMdv@PeeiBeWFt%DHw%(M~ z(lR8SY@On1&%*UAvwb{0RHq8FshWh4dK8I0b$4a&GH+46XG9tz8t}|uuaxcdGe$?_ zsK4uiStIF_#V2@p9^Rv|OI&l8#GL~tUYh$Ce!H3YY-6}>k50*WA4PXw z93P*W^%v5T@2{uLE#-{vD)l$Kx$#O&0C)zlr|C zo_iN>9ajy6^gs({Ob>`G4>!Xt>r7cUTZ{0u&9Zj>PI^EzgjzU>3wo&D$^7V78XQ#k z(J@5%td)RZ&}+x;u?xNQm&SVS;dYX{n;#rh;%Vl#o6AvEr*~`vgRS`T@c@s5j3zRv z_@kBQ?i~(tFUQVz7{wHZxQOiV)OuYK9FU@W+g5P9k;u`C8T%f%m@FiZM;h3c1P8^c zpEeUaRXTQ|s~W2Xv#S5=QCnGSVY>B9!4L8fj2fC#p zK}k>L%=r)GWs|xbI#WFCcd&YDzCoT$iLaga0bZL+4>~3;oH7B|7=Qh^nZ$`?5C76& z?{LL~W@4vHOGy2l#O}P0{M<1?x#StAs!mCY*Vp-MpligkIm3apX)o~i)!)1(^5rw6 zy)-%sX~}kRwBX|+U*?O9Ffw8*E*SRs0a~A~v%b!Pmov}X3$3Gw)$Wf>I0d4u+)IN4 zzP`QVdPLG}>(+2r(y1H;x=Njg6_Py6VX2oZ=>I zPwwYK!VjXqIhIM*(^MZHnGqSCSKT(gZ_-yKwO8wvTTy>oV@yO^azW$7CJ>M9PD?Ir z9$QMW9j)mZ<=tbKc|pFYO=V@_5m}|p)8aqroC}c~+M!jw&21i`uKnYizo*x#L znN>XXIwV6SnWd$*q|Ingb7N9+W#9OPVhGrtw4~y?b~JVOw>HIvr>7T^{UL0r}S1tMrKGzXqA`GJS-?Y zJdJ}xF)wd&KnYoAZB0gA9d7(H>uRX`9GM;!lV968^C6Y4hT5+g)e~3v8Nqa=rWV(C zlh)U*HPtEkq`?mTReASr+eGEHl&0s!d?8(hG*HiQuarBf!vfcrkz7>MI=Q_*(m1QLSyJ+zb!ZrG&~T=EQzUd7)w_Cip?^NmbeSii?1)^fmE1EMg}v8T8b! zp}a2N{Epuv2jniW1%ckGD%G{51!1VQB;k>~y%?YKnM;Y@aorUu!Iv&)C6S(gzTq98 zTUHZa=*~nJ&vWYa;{%2|(!;`3&)5nJdMey5$(cNK(v=q*ttsueo6q6M@klezuCXQP zTSRPozQa4uqoQ_VdjcQn)l{NSo7h(#C4DI^o^o+Uq&nK!i3_+K*zLT3SBA3#(ozuU z=WGH};>D|OtqWTMhyiO^Wm!g|?Y;AVJ19>}9 zL7#iNCE;EHQqqM*l>3JxxIwSW7i>iNY<3A*iwRnb30Mg1bh~h77>Da`Z8726p6*N< z$vffG4V6z?>NEbD3DV@JAoKBx)Qm@U>B5qxhoPZiclSt5Ei+S7-=-$YPNCM;4lC=x zJ9jPKym76sCr$m%k&C4@3MXzOZvBmX}ZN zJ@oWo>g!u5&T^iBd=riweYi9W55BwNqVN@Qd5^}iL|2v^dr<89vkx=>&<3C#v!w?_ zMOWFPWZX#Kn9{rX#;>)Z|>yOnMl_ zHdnsac)v&Nrl6p@#J;<-8ZKGnt-vFPw=IqxfAsB%Hj9dg?aoOpO!!h%(J*l)2cT)>^pNBI0jTA+mJG5p^6`q4pAzNoUwo^lsnLRu zE7{r-t=yw^qJXO_Y!q~B3cxzfii1t1^n(b8;^Z?G~41PM`KAg0u14ZkDK@^ODg9}V<4*Tm;RPi;8e^;Jf|Vk$H? zk!Mu5VD$IFgMygS@*;)@z5)e?p8uIqjH3f=a!w&LfV+C(_jX7#G<&v6M!QdrM8{^< zhF}x3tO3!-!*hzttUwqslVksImK?)7;$NF7u(!GO!nz_?Y^S$Lb?=3>=dB0M1=fvo zrPF5W9&e7ea_y6kJ5x>PZ!FOA(zn+Z!*Ow;1pNdT#`riNPiRZ@)WV;UdpQP+S>>N0 zg^bzwurGk{t(wWr=ZCAnHocf9Eroq{p?dzQ_%OjzxGneO+P#<#I#` z@&f($VXLW2PXP$>WBUtEKaIG{=QKf1?X2&27N}%}@YPPbE{TG+8}9J6%dCdn5AKl` zJmI`m)igrW)L?cT{}X2wg|LFZ(M_n^vgK~;6Jt96vYLg?b7uPkf!llB{ELf-m%ixp zx5}BjL-_QX9#oYTo)#ZLJtjt7a)^O*$Pk(joi)K4z>)gKUh`7r?;6*nrnlC5_8zq^ zttP8;u`VF~WnHs61h7pa4cruLz9dke32uuzxVN9u^5 zHi6sljv6vL)axK7<)@*(=k4mPh~JmZwD9@1Ia*U2j<+$BlA*#du7>~BWh9-rg}acs zh!Om%ww!a_&2z`L!8Z?HnsxWuOFbT;KD#jvP~g*M|MO`mUix)v3cvo;32V1AOF&z{ zoKK6iv^?FtG$5_JJAM^e*mCf5-?%1tb$6@v6Q)7@Cn6%1nHgVy-?h6FylQONy@miT zR7nA3gLCDb^Zv%!V(qy%U>xrkVh;vbx!vCXnwbDJMX8kuRs^oMwcDoUbsvF)D|*8N z3dEneH}njR@B+~D&BEG9B#3`+5h5Y`>MCExhJ();kjtH71GR#$6QvU59*b4GO^RI4 zixd8k;$M^hjsh5^$27KDey28BWPfvC`tZD9_4fP-8p->G)n^N}-E4H* z7&vDoz4>2cvkb>E86E5LX29uBj6h6L+eHl)b#B3S-TBA~PU^A-Sb3504((1ox@%2) z`}~(>-yJGoy!qL!Bg%JgMi9frChWF0o-05PGKU4#$Im}4p3OB4D<_=U^PlEU zj|nxef3r}(oIimC^Jd<)Gw1Ht|FWXpE#BzzbHT;QZVkMG*RyvG8)YEEi+K4Hf0Xb) zx3nY;hBVHA<%&#?-|jtmBqjqw&Xh#X^ng%>&o!yZOiKo%yZst}E$u{r?mLSPW*X%X zdh&N&M$S>U``V1k6%r|xrBNBgx*9iOZ)qmNamHykpLfzELTXOgUnYf%l+fj5lS{J3aoA`>tlaZ6ywBJ?0uUnA`$cjdklY4NP$$rf?g9=gf9i ztMV%dxhsxnPhfSTsiD-Tw|z+%CEWcv>(8IOzyG}+HnrlsPiLIspetYJ<>hbh`nR^r zRn!NX{c6xQv+F8I2M+Tbi}jpvBvTv!PnTe&9>9Jn${AxYkBYm?6JXn-=MK;>8GVPc8WGH zVSoBMHlQShz`~k|u|gL)#yo^}urZQiJ>QSaMFo@V?4fSZzWdXZW>5iZc*9MYP5X~W z3Y$vPE}kwWzQqRRa2b@8V_$$)wU+A+npHhWMw%^BO^R2#TgULes?XHzus}_g}lU5An9Ug1u*Rlg- zR(NlF*N~xMIYUs;z{Ovzs_E)aLFB0Lr9>D4N8mwu8=g|%e^4!JzLwo=?tfbuD#+-vc4q`YcVWjAM zS|!1KbKNZVT)vw!5N>-mzVsaxBwWQGW70-COXp@KQH>h0{f;Euv(N|0&I((IEM`;} z!AjR{iSa5DGn*1|{%j#`P=#0xe;q>8Tvd!XZtSSO)=#O)L@7g#@nY6B(>Q$4h4-;2LM3I)r z6KFFkrS5B&g?~06)&uj}N{HsipH^NnJ0gT5GYL+`L_dP8Ue#LF*g}Qf;|e z4!_`J*!&UcpStN#Z=vJkMqVLh?;nCLubDHk3$uMt&ImnM>fLDHy4MSfzouWQ{ZY08 zf9}n;1Vxa%N&_)<3YzTm6aq5NA4)~&=u5!`JuOgLR995*F>ETok&?@L-GHvLY@zWC zeu1jihB5YwHQYo|a$`#f<*5$@v_ICrRQB&UyQHfVn3vS{y=IF|>)LlK zr&rK7dE`u&sl6fQ!0&p!bN4wzI07JkBHEC*WwN}cMa^2Ma zMBP_Mb)WB13k0*}>#c5bMdeaAa3^jNFCVLQ5JiK>x8!lUbuoj-u6Ob`B+IB>T=Qm& z^64^uh=wYGb*v5LKzBOANTb72j76*PiA1ZC9G&@f+lK^;8;kXq@P_(c?-J&WJTH+H z{qELL)^j=IYa?Z({hgPe1qXW4hkY0`&KDKv#^YfTQNqLXyMg;zmr#%At(RLOu2!+v z?iORF&m#S;(@t<~#rzs~QzpXYhF{tUjE7VF`I~sobM$0&s@zYza~Q!;*Ig4lj`2HY z>o$zP*9*s;RPt882wn)!E$!ny^*P~Oq`M=1$k|g4$-TZ2)}Gex?yJ9x>z?HseZt&y z($Dp)#YF+$L^Dc++IC~@BVN==RSGfBnv82gf@mIJQ4asEO+&a3-eMyHvm#Y*prfEo z4a~>gp+YceD$Gl@Vlh>O%DHsF&Cpmw*We{S>uOQ@b*r6>4}UL>TXFwSz(_@qM>?Vg z0v8^_OP4+q*eAD>NC^WunO##(!-_82>N1we{LVGB6lEvlVWn666A_)a(@vky;p3~5 zY<&O}z1tKb9G!BB^0((o#=!&t-i!L#(?iyf9ssvK9#{CoLuJ+qu{IG#j|a4^xvwAv3HR@R?f7({EsmzHkPT^V^rI5n$BSNJs-cA4({*g4 ztF}EKyzFp1Ndw>$8hXvA7K);p2W9~e70erkutNp^Nw=M>ncKtYx-X;Os!8?5pnV}7 zC*FgA9m93IPQ{S+uRCxZ|DmyVY({^u6-Ya^+V+z`Lj1{313&-vvi{N))-ZT5|r>)mqq+`Wu5NQW@l57}uULw4^@ft3&ujrvd)LUT59cHMR5{=3@Nb2Ll?s zoxQ=;w)`1zY?G=;*8hqFFwijRdpNjFc0?ErnRNh3Ulm4VLXI<6zPJ>1Z4dxxEAc75DC()`u1Y)3?3eg;^esyYk>sg>Y6|5)q;5@mZ+8!LRX#=5zUqd1}ZIp zR)0!O9O{G?0w}*q13QQnrW^YA1-lQEQbuv#eZ6&@MqmR7dphrR)TAouaacNitlc+} zETuEM1Uca-s@uc1xVwChjrDw9LTX}y52O5U27FHZ4rJR4?&(CJpHZd-kG&2*D$yNW zSoW9uBGd-3h3K&RWuTyQ)`aV;K%UWQ6rI`Xv7pab_#+9FqFT~0xg=i1&&w2}-PECB z8r(wd`GmgOF$r;4_9pit9F(8bqOz>g*NIg=W23QGzycn0A+N5qOMrj4lv{-Cw{3S1 zl%8N5;}&el!;SSkL{8Sl{_@} zT>r@1s1%JYL{Gw4^jlT#qxfPD5Gf$vxBvj}t__(qdqXSKuUS$frGsn$*2j$EdJnnI}?}BtuDd^#j>6v22@Q?7@wnUT z%TvLWZq9JLYj-7?IwqxL42sV1pFt;*q-y%~&-ox()Ym5}^CSHKDJ_@hdFOb~0pr=K zV#bo%{<(o!^4Ghc#~n|nZHpsjW%H>EZ$+ZV$UxB3|3xozz=88-i8z<@F9pxa{u=sz ztA;YJOcEVar%Bhh7@IEK-v#CqX`F@&8~Vc-nk#>Qw^jSi4R|Ma2ONc=uwpSunmt4g zc0-NIN3vJy4TcUENW^q^^r4W2Nf!xictL%%7r673l=zE*n)<;ZZx?Cf!@^BmX}@G) zK(HgTTD^E5*Nog=GNn)V;_aumIbCiln8@48IdeFy5k=}Cp)E>#iljnC04GW%@PB|{SQG^xE6{k~3OJu;`Ho7%X zKtV&y3uM(|g@z={D2FJAL+aH3nh#y@8HZ!L@#{vHB;_9?+)nR>7#mTrrwlfzQY>JL zovGmHGpX&A25&hl#KCXqfO9BgRd4x3aSw2F3AvA>iTg76-uCpZas2j^lPWFi!Q3^T znUDMV>~l~G9P+?O51r~&4gFB8B+K75WaRC2sS7y<%}fVSza=k_54Eu zqXHk64?QJ_tdjoW{bRHH3lBgVbTfx1_5$+GPYM>pouRI!sh6Xz7QaknGvh?68&vfi z&cQXs$Cj!mND_2R9!hepPnh7JjNPLYMC1#kcE(bu(%Izf=i<2YNNkjKj{c-W1l17Q z_X?!W#aOdO+SJQ(@d~-8kFW>xxh`7iD8aK_|QD;|X5^atH)W(^~!wg!|KZlmTE-mrg14azAo70*Y!pNg-!TVQEHr6`boytNehYnNPlsxW%c7c{Wpuhp}xw)67K*oLj~> zy<=EG>8vPrbC`BBpSUatU6*9Xg5&6&mt?zo`Hy0=c1*zsnVve#ddH6>nhqp5j)oVK zD1hAAA}=i1@;CB!NSO3cH};3yZyVNn{SOa(VJ=+CM+pMjdD;R*r}fLM{@1SsToF8~ zEl+2B8s`=Zp6?H{{oIh8lwLJ$4;QdsOwC2eW|YM+Aw$feg-Yl$<4{AU_2%>)5equ_J$7J*?#jua+2HnN}5kg*rzrr^n6i7q^=edO~U(kC1`@ImB&a`Nf+U_Dj z-Y62;d(U1PVwjf-JR8?|A|Q{6Ie*E(1=^}kKa5J*_ zJukY2lTcCshJXv#=MW|IwYrva+IFn}h>;#iH!2~qlNLS7Z#(ff&8B5I4ec;SP*p^wjz=3zk zw4`TjpEq_iocdQ*i-K$NyEqgkPS8xkB<00DAz(v?U=Pp6c_qexIMg#N0`74ULxNHMwU~g_!^f$owz+0CzC8#iWBdv? z8Y-|Sr9Tu~nToPbV?C^GTflaYl7QMOUqY=okVu`y;3*=vETC^Sh@HTRdMg;&k--*A zpG4)dqOcQ@@mbB`f2oeT5q7`4<|N*)6Q({v@Ce)&mWu;(z9$$Zkrer;1pRz?+*p31K zX$c-}5y@X0cmLUXD2T{xrjIL;x*2OYbuQUSSo9>>xOO9+Rtp{`6;ly`)@^tQ#@!0CLuxF1i=cgHIP`YNNu!(NI_4--~ZCnY0TJ z@R=us-q9yj7yZkiUSDx!zP8e%p7lQ>Ywp%QT9swBmPw*%0e1#|IFn>Au5vTQ90gpI zaTwrtAslAmjR-f2Izs{~Lt~N_V-27ZhN7>u3=fs@4WU^l*cO9!(_hC;C7OWtTTFWgM#?6&q_Iy8?-2f}djG~y`uIolfdto~1ShW8!Y>(X)_7Y+ zhEb=vW}&@L*`iy@uhyE8ea&qMXq|@N>St z)>QW5iDI%WSrLb_Bzd-r05geG$dD5mP_+6is)};P2u(GwnFMY=M?uksbxIYsGt;q4wVlk~@1R^>^3TMxr_Fh#tLC1E z4WY?x1@h@PEOckr1iEiHJP(OgN0jbGp-Uo z?}9gnbFP=akxTCkWZsr~zmobLbgjkfr;vDc#w8&?OU@JHIRk~`;Y7&=I$$W|LY%r@ z>{;~*5;~@|6s)e!DhDdqfk;e@q6gz$u*q-IHq<<48JVIw@(xV6zMDcK;z`u=hZ!9cf24JO+*{bFZQc{A%@T;LKiJzNk6# z?5kNVrXM^P5O;9&tfR&RJr<= zN^Ktf!C*S03LEy~_*I5~Z8U;9I;<{Z<DgG0 z!oIZUTqrWEK5D@Bm>Nw@%oV7Bb#ov1Qs{Xs%U&83jE3kqx5_z*6YR;fr;_aDbZ$!1 z57gWTr>A?g?DM`L?uI8(;YK{Ha|)GB#a4((S=#6gr(^+=f;$Q9+QJEXl;w;a|@$OP{Z@gZTSGnDxGm}V9qp}sz>Fd?}9sF=0CDIf} zNmSQ}jlez(Gzbo>GRvydOoa+bY#oUbx$Xy7o2%M0;RU!*_;YqYEuPaJ{%iQ=(4D#M ztsYZ99|*X}LYqevc`PXGq-Cyr>JK0-7*w=^C7z;7waZ-O$?fx`rB!sOxaKr}2}ZA@ z=5nUUC8nc3>TcU>!C%USqWApii|}xLJt;ahyZJ6!&Zs>7g^OnOesjO10O1K{(L_9r zO`d~E>Z*%*P{IX>riN*VhG`>;@e*OL8d5~$3l+Qs{h9A?eIP(`dX%axE3`A9U{5qK zz7KAO2NipZr-L}tw=$nxBwAt3^19gC{9B{vkH1w z+x$JwU2*swazl3y-Ty1p~|R#uSl`7q&yrg0dBv{ikHkRTLh!jClgi-36U;STFjsR(%-`#n)O% znKHsdrm4ttySY9m6MAZr*%~|hamfs~CBfI&#*k3|rt3eH(%1HLra>~81lXI8C`BX2 zj2}IPNPLOFV44d>Lbnt)S37g$2AXe6>udeBIS*PQ!zu<OYhyeI`(TttR<48$7*os5o@}5hoe#L#n}|HRrSqO1FM50)L-4A+ft zrkw~eOF?%dG#0LO@gJERIS09_x!+(Z#d7z)D+GV46VK4sS*Hws4~jKngqPo1=hf0L zuCr+BbRY=}Q@={dr!b#aT4R)Qm^Mg4OqMreCdDiYlEj{V?>VbGWXLE|dW0#fZX*s9 zIkW8^s;1K`q1Ut{2yio8 zUgA(Ssb{|aC8!ty<0(^^*0aShcuvOb&|79&MYK1DNopAQLUBYZu?WI#eUlz`KeUJ) zQsQ20F;b+WQITR8F*zI(uJDl1EL6RLBwa^5Z8KfIHB9Mx=A3% z-=4NYfD;%v2A^SEujG)Mlc2hB7hur<{-lie#_eS0pdwFq# z71NZ~KS$=AdB?Q_^#w9w$KUIDGy(u5;^XHhNM29@;3qk`>A5xwLv*Y!l3f@=nUjJ| zFtP^A;!<)LU4`5Y7Nh30dDVV|ewtTD(c9+eRbMki#cVjkP=tGhCxYbA1PyqIQlnA3 zDF#~{hKx2cT3Kn+)j}f-_sE!FI}E*RL?&PphojYq;cdIHJKUMN4*iQrlcM0m>V*BE zUDw-!V)rM+DeFu(f2-*#$|Gs(t_AzUq-gOv=Zd?lL>W=r8C)vW|97gpHr)B^&NuLIS8-I>J4CBX1v|zop zRT>O;rz|A)B5P11r)auVWKbJ}Eiv(;rV!x5N%QUXzLun+69l03C|secjAq;xL`DWI z4AjzFMQ$Nb@rHy)fDps`1Q^%#%ELwh_C`YeNBqyP4q-+KrfpuAFoK1O_Q;8|-Oxy4 zZWgX!kw4_Vc^qua;r zk+z?cP(;T;iHDE;CisK&_Y7x@dM#27TU;m2jRZL5W1|S3ZNlqUoKx^rX+7yR+fVGK z7uee;@uOB-8ZVJJ^*(f@5tXhzwCWGy7pJt*9|2*`kgN5f9i$KKxKn=0ip5*3Tay&Z zry#|m92KPS@Bdq#kwvMK`~pe&Bg;rsAxMr{k2wy-d#8^Pe#+sk24QAYOuYIVQBYC_ z7zRT%$s&)Q8u4%kxPO@8mR?d11t*cup9nBw+}F*znZivHSNKz{M>hH7(E+J^lx#I( z351GHZ?MJ93Vf-wZ!`CI&bbiK-!U+Ecs!F^p2HNtCWu2#PaGRx_UiD+s#livD#kqB zDXIYomQ!0hHcFcW)$ZxQ&%Q~oFm^15Cpm){<4K^gVYvA6Xsxrq^fY~8w` zBL<^mv_Z>g#y1E3w1yGV11JKwT3ek1!7bv&QvSPYQN2V*H#T6oy$(P)hw9DOYq9fz)WYP>3~V z#7PpDuzyH|@Gm>>ho+PDd=lDdymv?ekNXjr!e3XKuy?bE>`}XG^4BQ=0B_~y>&t5m z0D(E=4RtRk1`nxE1Sb{&g}l&Z4%EjiWsfv{=9bskxIG9+k1kD*emJZ1Bv`0)>&k39 z-cN}Z7q4AGgQG(ll*-!78N}ALm*fKip`COMzRsAO*e-41PVfvhA07?mCoq5RzN>f4}kj!F!RquLR8n^?R zNOg0D9hDC*Q0RxB;1t7`gie`-G9HjFns%OB+*_z|FPlGdUn|*-l0vBn5dFXB0kT1zVx~{O1BGIq?ip0j|H>xJpb?mH z3*Qa}3nBJm%NpQ*Om7a`KrC$SB%soJZ1ROe)4`3YXisfpy6oA22<>^nPQ?De^lAX*sy^ zNnBbvXsWFkNj1skW;u-sjPJo})-v1b1{ZYC=!I%ZFcK!JbFTftU6!Ay)xQp>LE~Hj z@qEM|1r)V>GS=S!NbDUosRbmI!^6AM4Su@=R}bo0CGZ=dIB==z0O&oQ=(W|aD}PTZxeZ7~sXa1wI(4oO+h$`+L# zBPbmzaCMn+zRDbl!4`?x=sCIRyZ=gc|u-2E!2Q9hwu_ zbp*%Dw$oBz2ubV@JMwv@UTL@=`RsZ2nL$29ly+EGc2JTtP9SHZ2ob@f?uo~ZNd;e> zzIt?+5@4$38hbkssW#uShtm>i15puNDzQxGl(JGY@5S0xOK_hfz880Cks1yQ)NM!=~C zimt|2k!jdZ4gZYv9zK1Mtgd;^Epx}6#jAxE0N`#t)>Pa_xODyy60r^)-H_vUCwi%A z1$FK@H}IaX)e5of8#bj_PF_Eu-}gBzjR#eA<*#WdzB}G8v8mSWFd3pcI_u9n9^^Zx z+b0eDB66Q;>AjQjWcoxWYeop8dAjfa)3SFiZA86;_N542L#%73f$f9?=u5@@4&<*qjZO*y@f1 z`{E1823s+Vl(SPwb}48pALaH%UX0&X!F18)R*lF&6NQ$`PY^+O zua}rKY4Ni2cyIg*%~$*PBU6Y^iErSeU6wEw23aP@=D0i<^?5+xg54(2I_sK|(z1p-S7 z{=z-^CZ-v@ZWI@5Wh+15Lt-m#IplsfP9i>D&ZPDn-3&zUM6$0JoS&sS^0b*9p)E$C zk|VGs7C9c3ch*Ea;b#~(JHzAREH82W-nm7X;JyaL_k^~ksYcc6)E=_mv2%~$vMVKB@DjZr66*Z%G*~R<|#OS5(SfT&67=g-0b3RL13N zx9)+b;2)G*G+0=F2ZxM5f6lUreH@5jkjQjHi6RMr{MLmCj0Jo8ryXk(o8|^Jw$?Vg z+QfxcKlisuE8av*-?_El?3&HSmH74l;jR4DxV!FY#mMl|-(}tDa|n+q#n)uG_8f>= z)75Trh%HvSUUZB~wiUxXLtWD@+Gl-UJ2~D~*zC!^^?tb5{oF7E0g*K^V2vdV13`Qc z8$Y+&irf6rE_N=}dd4|`iTqp&0Wl>4ToDK`6u~Ep>CBB+K{KFFZ#7B1BwoeL2zcm) ze>6a}9VXb*cnCCpnfRMEW%a`H-pZk>bXq!1nVXMOL@PXGEe4LC^$Fz%tGtfW?o83A zVoqD*GpQS?hih8t0k(bQb8}sSmy3Cu&sN!J_)f54*k+CTc#Yi6Su~i-3_yF>o@~)y zI5lW=03k%i9H%WyR|b6Ty6047U8%d{p#2nDThsjtLu&^et*Zu-`GX(eD4NqvR}*RE zEwUT~zy<7yPdJCWz9~YY;)P4vu{Xgu8yR4$o{{Qi1atNk;M0P9qO}_ z-);jj>vR7v*B!F{L@;ym?mG}n;Urxq-?X>_hD;8*5wWl%ANa;&L9K#(-D*UiKa)G< zA$#IczmQq_8IKs;V-?}iy(-z0i-%AbiKpPo%w}X$(UCF83P@~TEK*v2ias06rl0cX ziGF(Me?27NVJ7rQ)Vb`3Z78nvT&qbm6?112 zNT*B@7tR2#zn_$aJ9uOxFf^S})6!|UPSi>1y4CD=CmmdY%LkJzKCcnY=|MvbU!S-M!rMw> zevq2OI!7B!5CAu##jP@J-#Ly1RR87_Gc``_ku8%QVjL18`lrRQuvDe8TBkNuC$T~1 z!eo4d$O;v@W%TG={Ny-}VS--ZYIfd(>q|gd3-xvy`-@UbUx}>0uR}JDg1nwjq`KR0 zxi4wbeN-SoTbs(6G!NLN02pXX#bq66a)#e|K0lCBB?p4>oxfwzu-kmQfLCV#*!H!> z07G1$E-k%vha)>4Je2?-v)3AfIh+x$;+WK~qEl3-fi&J6iSK7IDoEkCVqS)vzO$an zWW+|bUH;>i;ZE7#21xcxn_Jkw@14ecRzzK_l{Mf&X-!;Bj@Z5$0G%-^xq#M$!_R$y{u|e zRR)5v?Y^p%$KJzWc!VCG$8iu6=C#Y5m>_pSoUaJ16}`SzKRs=_J{uyw`5m9*@piE3 z1$kCis`OD*Ff)vcy>e9q9I1(&@^Rjd8gH^~#;l1HuDGz8Cl3nGhl&YX3aKgE$o+EX zc^O0iJA4Cih8)_(#c|lK!;FZ$gjgE;^}GcA+WQXh*IV}HnZUkqEd_e#)ywEyq>`pL zY@KbQqkT?^g5@Hzw*t%z889<$e~u)(eY)gIw~|uNhxav+yUU2z^}s}LUb$**n=T_e z^f^n#yFb_`-ne%-d;@F_K{_`L^GW)vOaZOp2j^+u2I%cNa5X*0dp3DDyI;EkoB|`< z;=Vl@{@E${85&sO{~Mv_?sVP_m3@HPq;?;3b>B)fc}%0pgi>BjQRhzu&1l~*z|xxV z{=b$$R~N56DV7OPu{5~^p879(~|>yM&bN?ft=I63l??u(^M~DKE54{ za1-~1%jQX1BBz-?uA*JG?M3nSs8d?!!+Sl@y(U)_jC9a zqGMf31)3F4Z2PO?<{ot&^&5_gd#oi-Oxv^S-kIGkt`&2|qsi;@($-5DsKW1F6^rDz zbL!cYw&eG=^!szvI*w3D`&-IJCl|v{zS7*0 zmeElf5G(qd^@KuuN)+=OT4wrlQYpK=K|5?TDrvzH((Jbvp=I-+tiw!L)Za7c)Z&NM zC_wGv>NP(6!B=D;Xa17WyzJssqpt1xOJ!b%U_%*n*YMFg4 zad4zx%5bdaIXp2qJh6Rw;YgTv7(ds^>y(B>5yLE~uD=6TNY(@YdI>K&wWHVSR=TKr zr9cB~ECB6Z>KVVG&H5fW!cQhZfUq6%BaYS~37Ek6{`ux?IgJe?D1cHKJK_?_0LdzBX*pET9Fs{0E|LK5q32lU8w5=UPe53L)pRgc=9nO1 z`)8nU1u*nV{?}z9m>tZ2X{a8Q0F#zGrk}H+HDDXr{+1o>VR6k@#TeL(>K5%JjFkR& zBkYA3Sdaih?=}V=w4)3_QFM-l5t$Fm=XC8|1;1nV;^zb=sUKfWJwUi?&DO)oV^`yX zJCGyrtx5C?AS;ue3b3?v#|uDiJh*=runz72mn9)@d-i`8_Hyku@wJ8zlW@O8W$u4) zIvZ1r&YvCx68!l&G5-0|sbIJ8uzjaLzRPCplq$#wKpr1+-I(34Sl|MXwLQeYkEs#s z*yMoHKi}-#W&=a10hnoU(O??2(r!e`ogYGmp%V^RZwntbinRUrpZ;k#c-zCJ zmRCuSuWaOK(TXg8zmsEle!(cX-h1&Nc^9zWlLENM{pA3ZeN_8L!D9UiirV2+@G$Hn za}n86#KV^17V?Rw)jn&T8vq>)h*A@dkmj-I2J#9DeC|)NX=!13&Ae4rRT+GqW5dJU zQ>!rlUdE+$`lT(fGFXKz0H^KSlg!GXekG_ZJ{NLATS-aWSB>dDzG=y9C~Qj^Kfb5& zXjQ^&t56=sOtolk4;uxk3nVLd{f>e*h!}F2^um%7dezE}oxvFXx5}0Bh`)dThJ}T- zwY7PB3phEk$1l?Y+|*%RpnF2qwSn*LANdgu*1g z=H^utqYm7{!mOE>RIr0#5NI7X6HGlWG@+p)XTzTsVLoRBS30FO*ks~bg`L0M0YUDQ zyZ*}El|V#kv+ezvmV|@^HFb%Cd50OZn)=d#he&2+T^;)8&rQ~wVDWpRQc{S)->GP5 zdZze5)1>>iFWJA87Z<|~|KdVKL|iE8i%m#iQ-YCo782^_dw;o}E|5`Go`()OsR$4^ z%Zx{jTJqu4luxbbZw;kGQf@ADD79N`J{_|;Mt;{O_i)xph!L-#ETrsbGI?3gM1lF< z@z1Kr&vE`?i^!9VlScQbRj;K~JiWQ)8D0(!lV|GtKHR!Q+k2Faa;W^Sm1p7mxX3@G z&R``#TalXok_g6K-|Jvaf5^qfMZ4L-VzyX`9}yHDgQmgl@v2PYua|@_Da4D(8ze#u&I992};mhk*DB6lmr)8b7~b?T@8tw7T3Pu_h-cOT^Qd>VRJW zf1aJ0IXOLDZ*fM&z`*$O#qE5pwb5?BT&)J`1d_T(*wT`YU6O=^gl6>?sJ#P&cd^TqRIIpt+ zj`1D}`6=T$@!bIQ3#tXf!qdR2b|@Uh-(p*qpIm!Y>fJL=7JXH^8XO85Us3-=#o}l;Fb5CAg3(Eq}mj{MIC}iM9zkY_Lp2T8I z;&!_Dsivm(?-@&o`rOwVjjC!dKw2lkRffZt%!u_lbY`#Uf0ra3&5<2(kyDSL<5h6x zW`w8iT$>rA*g^^x8Qt;#u}doilYlp?T5lJ(W1GPTEle|JyyATno_Lv1V{WdG!;K4p znY`M~wAQBF!htdrl8yBM7kuR?GjO(bu}UBMqBu_?LC+MFjFz^{zqnkx6^DW({-G~h zIJAQbP>#hB^ug{f%g+uk{r+&!f}U!Wovq%`p9-kt(&lENx4<>^_V&ib#(JfP1O<&G zGHg%gh%PXC%*@VuM~8%js5Tvg3*RNkM`zT?O2`%KRNQjflJA+MrskP5X{`+@J|#AG z1s_t8OU6kjQe`BU)ve3+3fk88CF;tIEiwMEUKj#KH^&#eR8Y6*0H>OpibQO3hPS08e7+9VB~%vcf_Nni6z&c6Ki%5M5neRScgN=WD3Z zk%57ZYW2X9l9IAAS|>S8%@K>f(GPZ)N3reH{VR`*JAZ~d8IEW{2_8!v$FHxt`dX@G>tfI@iKz=W9c2Xa$p+t$^? zBCy2=EYE!d&esTDC-XM$S|??&_-crYYb-&$Jk(#mKumpbO?UzD@*Qu&E;(4!e>*g@ z@FTk3pCRth5P9FfiGcuTn?(BT)#`e$r)F+zXZL(}3V)Ts>mFiQ2WHLcwLYH8QBz(0 zc~~$K8X4c~^yYZJqAVgxtHtSZAc{ma8y#q-*AlEl&!a=#sPU)`*+gZH+OtrT9B%Ky z)nkOz?+x8yP$BJ6-I2lF_1WI#!#OZ1MyQ&6Ni8X1x8GRroBnYLyJM>-RJXx<2Dq#Sb4?pEoqRAB6(t)wd-BdWLT6vhKS*8V5^@2362L1Vw77P1m6;QIf2tqHu zzda)lBqb(d#r^25hDgNt9{-&<43`awrq%PEH@=sQkBwR^U)!B>m zF3pSLc5)w4+-zv9j!U}oP?#mTMqtG<8;tn!c6du-q=nzw2DVHA@P^>rtYXnu1R8J_ z$?rDMkVg;no0~D1p;iUAe?Q*SZER0#l?cxP0C=CFVqk{(q_T(W!Q{2u*oV-xH1fmu(gMHP^ z#BQ}vf;i%$H0e4GsMh zJ?a&7SrySS4f-a`u9$pYZ^-_{CsGmh?hbBoH(z+l;J;gB$-j zfBySYX=qD=5rU{nc(pG$2@3z+f4*4aIQ)y#r~_Y4YDXdE_k-2<0t#Ss59+p?MQ~?U z4XphDaM$?-N<;=mq8<55XzS%tNp-x&R5R^?iL(3CP3PtVUo}EYS!O1PM6FK>o0YT) z$&VjDI5^7E;IQ0RhQ`K_B;JEgPEOj~pOLV>5wftL5C0MNF&hgSlWpg~!w2ayNgEo9WiGveHMn}hMz0vXXB;r`61u%-!AY%qi3TezKI_LND z(Z2d?@>E+CUu4*Qhkhkt(I(I@S5eFRyeIHBZ zcDF!B3ez2zvvwJr2Js-)$478l_i=S)<*s_M-eUP=u?A4g|6%N{qJrw$a8bHby1Ps1 z?r!PsZfTHC>2B%nZX_k7OBz8C5Co)4Vo$z*pE1rD=jM2i=vs5V@zjiCxx*A)UX%Po zmJG+v?|0{-y1t|$Xk}w_H%b=h_jre?@B-6+EfYt`<$1hEUf2DuFAg;3|K5I*a}I9NN52MJ^^)cW|w83nN!A z_StLQGTQi-k|kw{T-8wEGv?P=T5=_QgaSLaVMF=9khymNmWm1OraR~|{&4%74;NXd z(G1q5aw><%{LuL`zlXfKddj7PuxyA~yVIt4N*_LB8j|N1ht;MeDj8Cd0OAg|wrv?= zVLyD0ZfH8+>x&C0BI36uqFP$G>DO4qLcZ7zjsqdEI$emO8|5nbtc;9Zpota~kTbX2 zgPn4rf~}HbMBwihE0;rJ_}5QUU9L+DT*p>6q&%<@j2}a2nwoBCn`GklxGNLd@s~g5 z-34hf7Nh4(kmaxjF3Jx6&={Fh{4pSV(~)(djAe<{_@HH~Cak=>12PXu@@Pin zh5AjD&Jqt46cjND$)BU6S?aU5s;=LXX>6KctE#GYc6Ng7+w!ii+zI0%YPTp0@Mncp zO&gHXzoYbH(kW%9W@SZ{uA;3ypdmtR$LqH{DJd#`op-=Ti6|uCBM}!}R{SZ4zI(-RM?f>(^;RvFt$Cfx zq|Vxs>fjIA!6nuCY03SLET}}2KdagLaX4HN*IPHs?q-1g%3V-R6{EZT6bVP3=pkC6B z?7v&OT*C~E@<{?+hM~TjKHbh_+Ho0PR#ax!CKc5d#)c-fd}&Pml9U=+8T%#tu`$c8 zTM2uL?y5PH(;}&8jMnh`p*eiLI%XrcW+Rt6uM@M|hR*ni?EJLG^t8@HW?@TQ*3Qqv zs%{p}MQN0cE*C2wE9~YFS-N&dH7h_xA(UWz0%J=@-01z`tEIM~Su7(KT?{z?ys>XK z7Vrw?zO7uJ`Y;=IVSie@IazOX#}b#2n8_0o0L7nyf#FS5*j^igODi}}N8BN`8%Qp^ z#1(GHPxAi${`c|h$Qv2m%?`GxLZsmKUSEH`FYsk)!`vMQpt$%bgjs6IQVfc2 zYhUCX@A{P<ebgm_4j<=NSlR(rhk=Mhka`t^XW$y+czoE!|t&{ZwB;`kdUwgnnwo^G0CUI;gwfae?9%xO%i$0DWddzWcvag5$(BO zbtB!RJkrEE;;I}mA3rjWMmv8~#y&cQY#;Mo8~C+CGBy6}E8h z5Wrv?yKaR)K@84*+{^2Nun!}F@d^{y(5~RcWt)fr&<~!6PDsnt-p&pzZ@!>HgK?8w zEhror?HZfXjZX&|AeM^IHwUq7eb<4Il*WA*sh!KCrnd?&P{gI6n_rRn10!X`jk z8ZLbuKe4{PKJaLfVOr1#wEB~+zP^Trw+Ro<8y%RN8ptW~y!`yrSsalIyWEV7b~|6A z3ecud3y{I!{bn`FRKcVCcLIS4pTg6ptoaXaH2PS4A0_0-o0O&5(cskRV)qhq=-2cs zR;TmqD1;|9l&Ke=23*Z+167FcFMe%xRSGf!&T2$)c+nwge zS~=wto>9T1z@4h4c^@g$#cg{8{qt_)siVAJblPowYUpB9hKS9$_ZexBiN3^Ic>23= zZ@?3h#fPODU7hTyE{{K=`>#98m2=Tv9jxQtMyK1hNuwmktW665iMH!C7;z?c;8h=q zc$`e4_9@;(nW&3=hK2$j9o>tjC@Xo_1)34w$$ZyEjnj(8Sduek2{{?8Rdw9xn~3=~ z+UQY5O=cxc^sGvT8{8%Xc6f$iLt)QZ4W%fp&p3Bi#zK|nrfe?)&od#F5)Z3W%Wdz} zg`BAWny@{7_Y<9(?h!{Bir?)zYFJ`Ty~y!&+

pRLz?Oyt2HQgW`$frPz^*nj?;t591)BH5!%tZLC^T$cxX?zo zgX#UeTRSGO!TkLBQ)j0f1UlKKXMU1B3uri-=fRSp7Drr6)=_ z@!6FtlbH@2GV3;zqFC>eYD`|5TF&;Q)o)n!gES5z?|U=ZZT%kLXXtM~b`?iDex`J@ z=<3=;nJSic9aY+MP_oqBoo*6Gdi=O3yEd~elJARaI#(>x*#K5ajog$%iPGn+9xKqa%(o9X%0Hoz}jCt4D=;js+EiW|85{T z6qJ-;^R!h}UGC0)g2m=Xw--9o`9E1@WjM-zU`R4K%-h~sY#B?ymN9TpXL>~RAv5d3 zl;eBxrThMPY!1J#*>+|i975wE#rQ7+(MOT; zEbyAryNzr7FfyLkQi0N`sZIJ^+d2N;&7)dZ-FhN+35Vq6v`;~n0Fq&{MNeo;QLYD# zEK=jZKOn$9dZhy)K}Oa?+4OgJR`i8c5f%E>JU_eJ(R;VnUD{~RktQ^0HRTAiaug|I zRH@@Qnq%wAx;gAQe6G9Q>VGX&euSTiA)yX|(soTUB-XE;&?%sf(YWD4T{*B*Pa11cSYLBHihdaJ9A zelH9h94(#KB*viVIW!bhgQUzEcq)ikwDgRO+&pDs+Ax43R5egNYY#p(lJJ1%K>a9G zki$-gVo9Qe?GU$DgJPL5GcU+#=-)edSGgs|XySuELe|2=}*=cv@3 zd;^Fh0a~szuH^jq)E#l-AMY|bt;#07C;0f6_EQ`cHRFkxR*2*T2%-dtl%amwPzOxv z><2|P&CdoWu;!I$?|s+AHfsez9)=i-352$)&5nWeqRl~zfE(Mqx%m0MvFskVF+}xW`cK5b!hufq#mbJnkR_!H8#Qp*ij7h-nn4RYcd3?1 zqnlox?v!n*lZ!burfij*oH(e6t_>eIH-uX$uQxN>lqH;X9h6bHh{!=eXWpnTZHDG+ z29Q0OsqdT-C>_2IY^^Xd)!{0D#WN%Xw$b1b?g+)g?aPbM9jtt~o`YQL!h6vZ+_2hm zwpn4oLyEd|kH?}!LJ0@2K{3E{!T;R8AtaVX7BcV|ybc%O)y_BH<&fob^mT~wH+1KA zb~39~wdh8+VCUoEX)n%OdIN(5W*4L`YdlH%CJ_LD6lm_hjZtSMTPvXv+&;Uil!?_i zR!SwA)J({tU(c-8&0(s4kGDW@)92$_gz40tm>kXq0O3+Klf zmzsG0Ed~EEJug>uU|5yC2>;$z)^tQ-bSOmzku4psF^lVD3jjnux4?lYK zFq#kl8yU~?BEkHO)6iCG54N;Wet!RJ$0$nbYWj%NqGp~WTD;JV90P=0KV*g>3 zX}^JUaRpn|NxZNJQ`VzYRJ-Yh4j1rq6^hX0-$pldu&-T?Mj9Om!?{F7MOKoBv);Fc zBNmlf;%b&_e5; z=bq4+n{39EEf$}i8J>aXUu7_<7%hWR!%-dKYr&mleL7Qyv`OS@}P3D`vGcs2NTc9O?64v+RCsc+4k)V9H3f%Wq{Q-_pQxh*CR>^ZF|+* zlig|odqu8Eny}lMMPdG}?cWBusOmIjF$mQtznceLs$Nj(sCjT1=#0a5Z-I3 z5yxS~rIvmFIds_h!9({as%{dB88Zu0NH&fb-PdFeD*O^&B#P|!iLt-vWQ02TjMd-w z2L6>w8Ah945af40qM#bym1Z1`Fv=kn9|@=GM3nttW!lib+ZLEAgO*Ih=aSYoNfE;^%2GzcGuM<~*C!_7 z)Z`NArldJK)P3oKIQhVC$K`hfroeGvk`1Adl-k>?AP4Mm5SuHM^OQP|XP<>s&r@L|c6ha%OZlCHnXCaTa#&YfXi)x4z^h=@uc zFNdF7?Nu0#9GUh55QG0k>uPC1tFf`Z{WiEmF{5W<|NN}`{d?|@l7|$|H@Z&#|9kCSabspI`1o5Qru@jX zxowW;J-Q2JspV3XR~K$Fo>lQACte43Bk+HH#hKO8rY4n2FpDd{{?-$BN2SP4&i{;s zvl5hGwf~jl)BBbhRYHm;G%_)&;p7iLV~=HhwRoWsSXpBsYj}h*rDIDhN9ScfXfh1M zeui=NG(dfa#GpcDEkk`TE;SF{G$&s_SgNXA3(7l_$UjJ7oFw=3nLDDX4oL+c(T5s5ABN5 zhD*Hh+$bo3Lz?t-1KYBS2eb(n6Nk8zF#(=u^PTJp@s|KTTn-D`SkMG_*H(89K{#4Dwh+UtB8;Aq zA~`<6WM>FdoHqqyVBSC+B@16>Mg;`rI!^grehNBB9i(UTLWPqKuesh&nX6x z0SkrHgS*0_^>6@;`7rv>;*-r(iO?7K+^5g1*`JPi(w@SFa=Ix|;EF3MD(cXbE6@&Q zyMo7x2q*q>lyZnDg%}BGkg*Y;Uc)d@P=@RNCHpb|221YW_SB?G`LzR{KB@j$eh+VecB2jd)3N*6v zIHt$%_Zx{5WlUF1#T5UNp8rH4tIW&wge)E_^{G3efi&(&#nYlCR7QB%<;(1K~?41iOLlXs6 zW_o^R4t@y(lh7kNg}TS;_!Ygh5fUo8BzLlWtY!NcR1&In6J>~8wCVoH_mXgSg(mhv zsKsF^TC~&RVtReKPZro;Q$7rE`n^?%N1Wpnf1@Vr=QnK{{k!8U`sw5jwY078Bpq9+ z+-{$@@Q~dq?A`OvnWEOI-+p~E-Zopn=qR?G5e-1L&C!G~V5ZG!izqr>37)ERgk41S zZqD5-e>JRYqfm>{>MWM!p^(77pp#$OAK)4B4wU!w_^r1&N<^aNa@j8d#1o*M)m2sU z3JP1j{(vq93)BfXAwRx+H12SL)Q5z!71+EstzY^ytum*Y+G+dkcn|mCR}o4{T~Kz4 zpTpA*7p*!G!a24XsB}~0QtIcRrBO`oEFO2vJTT6iKOkmRd|Q08Q@Ps6X#kUpKXs_r z@04oU$&7=CmaJh02Fpp+Kqa~NjTHW;XCPagzO>A9^2xcjg7St3Yc{B}>0$}t29GCnYv zUoZBYl$DLY^vnl*9HK)9-;9&fw}NTW`c5X0+j8l4ARZK>4q_l|=oU8MF>VH+rPAZt z+ixr07mp;6y_}Nd|8Qz;ZRO|ZhlWRkp<`xZLY@d0T~p@4(QSEG2R<46Rv%#%=WlVi zZEH>Z+_PT-0dZq^7j&_{C-;Qbz^oP9W_IRr#2BvjiwjH4@r(lj z)M1{6wt7gY9FMc~xufT1>jWNiboBT?OV+!TlJblnUx<*arrelc&F1iwsw8$FX}$jb zfX(j!Bq)pGpTCEPh~og#eut03Ts_+Cyd@kM7|2Yq@>$sb0kEMuJMHrKQfl6WjEil4{+hY51=3>jxe&tNusSL0DfnI;S)j)lo z0^dv?m4&sQNFY8S)ew>JMBBuiK_eYfF@pk2sX+U6E~YupP)JBqZ)fvqLRL%vi2}A!CVG>7xl1VjuMl&05R80;F+dS4OJ0Cp^{ieN$28*Oly=v4~@Dy8!mq zx0Mt;ZNb<|6w=(=gSI6$(r69eZ3y3G$Ho-=44#lyL&VhZ;~lG1LNB%$D1c9@;z}=P1CD4N+`Bz{YqC)?0_?txF(7 z1mBO(*UiD5!Zc0th8tynY1EM9)YQdxXXCT1$HN6W=4zj>U%%!F`h?JsBT%xj1zzqe zfdev`#UUf=B;J)VRut#dH?CFDiqoAwtxfjscfWy9PG4+|wavPYVp}HpW5V>W`7tlq z53~e$nh6PzP@d_wuhMI&K>JZ6P_scY zDAtZj@nGx{HeqFk6QGz;qLRssj4r(?I%+PqfkpHn5c{LpS{8@zZBlmgO%qQ3=HZ79L+?-0VG_i1TGa||~( zl0TYMQ}Y82n08(xUOyk?0n(k5SL^-Obc^jmzxB!E{bi&3<>J!P__uFA&%K#hRse*! zy}b<}Xy#2m_tY_3YHA=sD=jL*3UofCPnw#XgwdRuatzEEI|UgcHHXWU^6$9=degfq zuc)igGxSrsqn))Y zd^R1vIB>5np=*T`A|DE?(qC0n^yKgqzh*l493y_+cgDgJeZLDKnD@5@8I%*;U$%H% zl%%AjH8Xk?g=c3j;G_hN!NI|qAr9`g>OL@X^W0mcwWf#RZ~AkpBThjG1#McA79 z+W_sz?fk><;@3A+B5nv77&fPkj$oEk-T)(u;K7i%8iUUHZ{KVIuauXU=XJW#*xYQ+ zzP=>Xl=H~}%k}{$bsrP=P?0+$&)cG|t~4v6wK2K25kUK`sDwo$=5T#3f}c_?*e})W z*PiErim67laqA z*WZyrv{sRC0IVG-XK83`>jL){^bqj6=h%FSZ~`s7KM3-3+X^Vxs3iQ@#QQ*@IQJRt z7oF77<|c{8G59f?rjini@l*!#axT+^9W)oyLFrf|vm z@>J3bZe})N4G}FfEywhF0gv6I%W)bdWzi~Y-CrN~EF*(PwS|Sh+)RG=oW|C?ekYON zooDikX3;8a-fia($B^AZui6==tSKWE&(A4TYv{%g3OIE9lZ!3fi2`qx%$7M1`F4h` zherc^&dI)@sL(B&UH|*iaFTSJ-N1%JR_t&YC%b^k)qKe9OaO(6&F?3ygTRB0MDn>G zGk9rZ3fK~I!S7M{ce8MX@Xw%p=(lA-~X;aN$<@^4{8!-jn8G(#2o1&c7<({ zx(-LbO>b3n)Xo}Z#VA8DM*2ZO5rw-Nh&Lf*m>Q2l>Tv1~TyP+f$U3jW8db(5aHi(4 znh-Sa`UX9QN^E89Fg*GmaMQFdGGds6&N6!3yNL_M2 z;ZVRMXf}@z?)#m033tNMS#zK)iA1#&uKHA)VU1RlLpP|9kK93gPfhnG6dMTpEy#xP zsj1jEGIg_nEo!Xc2ITtBogLS+?EyeDIj^-KR;bC!BD5WQ0o)YNg%`kvI@wCPx~oBu z&_HuJM?HJ{s?7Y+<1-wM1L^{h96e{T!UH8HINZZ;91H8}oB(V0@^9-UIwq!(nH&5T z_{e^O%4JYS!PF`fPrTag#l~9)nEk^069@+a9pQ8vDp6ChT1qOaMoyx|&bhhKpkbiy z^18p++kSEZbQut}0KwiyuRjsXH;HAuRb0Wg6jY#G7Og_0i6Zlwo?m**pCugrsU@w} zHo^`R3*7rv=^U-Nj8P1zr+wCQS0M-@CG?OCu~JP#L`~whol#t0VM=(R85AqOA*djE zXW?(-?p{)#>@p#}gJX~{#@sz(AGG5bs<@*sA&0D#ort*-YVxhyX?SkNrXv9vQA-9> z1EM#nNj1eu8_%}M3VS2;=!6Z9=bwEWl*&v{priDtXK+l6nVr_Z8Z=L#mWkl5-hh0a z`_ji7@EQa`>LWPo+tFuNB1XL%s0Q)dCTiLQcDKuyHz)c_<#mY|TXaSEC~%2%CUig| z+i#A7g3{8`5_BU3pl%k?;yHJBciuiO=;wz&^`>7%FaGhh#t^Mwtw$oeJe6f=WpI#l1k zw^iO%4LS+d3tosRT?Ks-jTE?|#i5=WpFtbg<~nF(RCSyV=7a|&so4ruD{3T&B?12( zlmaK5H(i^s2pBbCZkVX3LQDG!f&5&Vq(cRwK6s3gfg#w8es?6!&BLRV=kihI)@S7p zo<#Z5vNq0KwTos)LnkBq{w^Hx+eFXII`>J@`5!tXYpNr@5&K4$-$qg*;xXT3ms0-A ze+o{%LzhKGW00lB+9mu1V4E;P0~9I|SO!@sB(+RFMCS0R_~JLaM;{tvA2mv_6ErYl zlEqK?NpyLY0y+{CH@1b>t&fO5zdNajiVh9lKs5Y7XTFNHpqVg}+a{?gZ~N6`20mke z2%>t&xhzV~j9|;HA@*j&>CBEvWm@HRLN}0y^#;WYeVZHFAihi+35=yH3|Wytb9L5< zY*5mf)#d8y>cVT_>dKBmuIjqlWJy3k@csLWzn+v7>=-sKE}V4CK9Ivg{UYXfM^Jh; z3p#rmpcN1xnlN*_J2O>fDrnwrO-M+{%+#^5p$92NYHCpFRh5;kvvXL#*=D`S%akBT z+JF>>c`g$OiXHp|@QH{*pDqL!VHx4r`EKC=MGW~ZCiqFo@ z`t4@%w{vZnY}NH{?tscZ#O|zR9~T=tC9A3O%6;mpGi1xB&K=V#W7jqBM z+}f_vut_Kr>zb57Rz^Nb_B}3+q!FPITX=TL*GUDdeVWF{R&rDZx_O1OyUYS)G?@cJ zMfDtBF{j_6>ad480TuCCP%s%sy51C1^X_e8}#3aKMfosPK(6z8L^7;As%J@`J zTMuYKK(GfP_jEZUqM)EV16x!z#laR-Qt}Iw#UNU z?QdG)<>L!hbZM|*XJI)370ts#M})xTOaD9&xV6_rO*NVfgn)h3B&sb4ZFc|kG`@5d z!VoSC3|cl#y}fzej=h$z?;s*u&Cbxs@WZ}%vF(UjF|)Nw>pSgsF103}q~_MdcFyG0 zN~+=w{olVv_MQ`*rt_+NU*>JQhd!2%E2t!;Z17q?K-1-Pc-14RQ%!JC{z3eJ$`_Sp zTc~MUSo*CHF}VITw{^Y6`x#q`4~OBK`A07p!N9YOx}^ysZ7P2w1ypK*_{sY+avJr9)Qe<{61$1>LIULfW(EIX;3#*{qnP58&E-w1qw|QUJNIhUrIpafiKvcLK+S(#Zg=Ag7D1Ptb z<5Mghr`P7Nik@!@PYS1@CM_-9#p1BmQfV3d@&1CQg02E0-3t0Kt@~%9aydL4oGbdW zo~GvadSej=hDzLQKK%m!Vq9Q)3C%?#U=Lg9i8HgXfI$nE+!5*DSZlQh^)4|fDY$f9 zwp)e;VidtHEjc-)boF?x71_d-vy!1^cVFLvzOrNg9 z5A6>KR7kh}j`@+;1DzZ#W7|HX7A|8tZWYG}dM1B~aq6PGEfzw@TGFTuZBcmKUrU|% z8GG_i<=DF2IXS6+K={?3^n+tRG~6X6LmQQqf6EqcIv4pcxcFW;@R|5G!hsllFSA|? zLTVPD8*RE1%B+@&E9ehIA_0%%X-th9TVi^8gcWou-P=IyBU+Z5OOoys5)*?=-){@! z$ONvpF!=?l9r&)mhyaWypuGd5M#B{kxHK!Cn9t0~N!asP2P!yduPxUzhWP<*n=}nd zma$_a6drwlg)vTgVQC3yl10HEKR-biT};fReK@)ZkZOeQZs+&`x0v?tswTYd&)$jh zF=9@&%}RvtoyqJcG&%x^eK~$<-dw%ukX%#xBR^*g{iOi+S~tR za@aqo4Dsdfr>vd#>i41iJC`F@-w)YZJgeTY{v!E8ThGtdS_m_0bbdSNg!dYCqG0

D>*+xvZZsFcl#s6ak86p22EX_1rT&qGNG#Vq8ukCN^ra6&3` z^KkvgWLdO#&H^abva<=tAlqSV&?15QssUT}ClF9`a&hT%c)%L3u`~e&7y{?=>`XL8 zlJdhyEO1{w#_U7%KcUb^(5y^?@#^a*kX4Ae@fz)=$Y0xqA;U7Hj-+0@#G*k$;lOV; zTORnx^>?hE#wMRH1Vzpu$=_I%SYhqc3twH((w zM2ZaZet~LPSsKJj7Rj+v<1cwp38Wv!Z+Pi-wGD?$)QL|Vt+KPI!vcTw1^BE~N9sbd zSx3j~(DP3^MRc_`&Knu79u~thtEV!kAeCt)S6wd9Tb}fd9*N$)3~te~g`YGgvJL{M z(5Bgb<}*6(poE6T50Gy33W=$$LklDVO*N9=3KJ94%fG*UqHxuF43mYV3@ByH-K^wIB=kf;T4GxM!Lx%#0)u7A$us<03U?v~vD2})KL}u9T zUZs2ebAhgCwcrdx@G_|eYHKe8o8YTH`?H59>-pdL4O8T{{goM8psxZQjIf(RFM)xH+!u%g2CnR;q~P ztLu%2Qs2;LUaP`@uR7lY@}89q_ZXfYI*xUenCRw%C-%9Hd@qPhy=d;x91KAIRbn0l zTBN{-ov*P394)P_02c`wTw7anjo4#kWd%c~sIePFmP)?x9w}U!ba5Op-y1)#v)H(Y znER{46d+^^76mx<@6GOZAhc}@LI%jBlF}4Vd=-2P34vm46>4?>h|E(gQh8Y!)OQJZ zX6WnV@6~uNAKWqdpCM+(Yyj{8Ti)*f@w3p!n^&EyVPX%|li9jy-N=i!B8FYrw1cU&fQ{?$h0t}W4&E;XRX&I@hKmo2A z+OxTX&^l#mXV>*%SDN|U?Nf9#@QVa3M8e$tX)qo7Iy~I@F|EL#j+f+32MC1;_^sh; z=%efrfK;3;igKe(vgrKXqSGU`Mz?AGS3N7JPoJY)Ez z#e5~I1#KMKDL;mul9t)mA^i+b2Lmtf1Kj$Igv)c=8ow~P(&WSA#;XdA@qMK~JkLYE zE*DFP_A||%lZ#^!eg{&rUIk0t`X0W$ho$m|5odar5VNxwD213!Jz>UDB~wU zN5JYZ@$K8d{8krk4KNTYynstqL*n9;fb2Wnio^MbpekA3hwdO)G!p)+#Y*+%I>YXM zE1;PK%jYO=dlHb;<(b+CB4jDTm7{r+-kzb^{3}gK!AiFxn@gmS@ZRqOO6Ozn;jb^Z zk~1^MTDR1bLD?ZFFGpd~&E8X~78MtZX=>N#1edNlthb%K8k0iedU|?51-^|Yehpzy zUAtK8u_&R%!`rFn^PT1QzwUH3n^?5G_#rH#M(GMA=&XOrC?5k!+3g(>m3uA5SPItz z*PexdBMChSlTl)O%K#S##f&=v-%h*E?QGN;bghl~@K8A_XYYHAXQeha+&WQq{o>Z$ zyYIPM8L=CoOv24Md4eWhCmNt`O!;~{h3FcpOKLG_GQxf<3kw{)ym!w}|Jd2t6*5@& z)+t(>-*Ixncn)vEW1ke;+S`NeuE1@DyL7LzmOp(WgDq4jo`3^RfeJzOCzyvprHnh^ z>jXN~di{2^&pIxVXQ!LpQZcx}9Fqj+M|cpNBtjyeX>Zon^nl156BCm(3l4Uv&JfsC z5vuSoFd7_I*$oXh_kA+PVDxr2_~NUnr}vN_`26!< zkVur|!{5G_X<${qIo5S?KlwAZ?j(vjp!@WvzONs-AG|Ekt^s~}7w?XY@*C)P3#x*6 zoPU^f0&CtnBxfof`5xb)@e`i1Ce{g!yXg>;v^WCIk%IrP@{5MscE=lMeG_3#<*#-x z-29yp-wJ};(f%;7vWH94P$}|YEL~Wg@oj7iT-vLt`SfY8EA6jS z=R8*E(a>WS1Ey_#{%<8>H;09_j$7xVN2B>@c6?NqPx9bUVHyB*p}f4D$N#}yK%g6Z zM$GNsA-Fm{{@|+O($JKCuhs@N#{`+21oe&*?iQv0KIqJ_*Kc)U;j*0*r9Bz24M4~m zQk5s*g||_;{Q)MEKtLLA& zvQ`d;ccW`(=s1}9PP!-ex4-75CJyvZw&qmzAvdsP8RJF@<_MTS!j6xdsM@r^A*GF& z%;TeM*D+^UHb~+=k~zF=>9>!f5&62Dh@dO$q(?r??FcVP1=1q~@TjX_hvec6*3?ZZ z)UnY~Op+-Ef)9v%GZegEa94z$u5PXwv=KCzVbwIKJJ1mY{q9s{Wo^J{tb;Y2Fg7uv zV1~Lj2A{~r%nX=DeO1*UOEGdG5G_O^D&b?~Z$vw0oN^@j2*l?3Ppdb;gv@@21T?p_K1|V8k>b>ix)Mt)3miSmNm2&mo%0% znv1HcimIA=%Bq^ms*391O2!$-qpO(|{@9J%Gbs?+RsVhRQdf`?7GlMw$jII3+}};f zDU~z5mos&XyKbw~HPV)*{Ao4@uEm3)>`zTggN28W3=3=Vfb&!p2J*!kCUPnaMC{+i z+#3m<2+ZHVe+QJern>sjyvX2>AEd;DGmjM;VUdxcP~DyN z9;e<0{SlDbjCwXzId)ZtV+ss0O4)@1Q{o@1&R(7qF-b($2fuD;ICq5yS0?p#CI$1| zEhj#)Jq*s8bR`96L3&TgEAw!-yk-CFXve9ki(6AyUfATGo>f-!i<8E-n1PUXffoH} z*=w{5=i0LKJ`2%fPskou5}~A8lrl-H-l(>)@B{F#fj2X|GIZ2Rs`?Ci2XIEiptZRC z6h9q^BL8@E49UKHS{NTd1I$^_a44!;S|jr;P}Hr!Grm0wR^SjzF364bASfj|@^DdK zLf~Z#s)90L5UyhsVL9Gp%6Ss_jQ&^1$7Uufv^$simUO-Ea;2Aq`=2N8I%%U&)3WOp z_r#OFm$UIln-}g0e*v_UHO~$?;_nG zlw>qCG$m`qHyCH$k9X(0r6nb=uzk(G?LGY`$sKBLm=MuPF%|?HJ3FW>fxu_MbX*f) z#`5s+0Q)`Yzz~s3;xWvxK`F1VvA;mQ@)sOoIEqoDK<{(njZoU~FC=W*!IFgvY{pW^ zNA!tM(JnS8CnwPE^*dZB?vUWYaPR=|9bmV(8x9?7A$8yRoIoaGc4$RUPY*HPY(NJHdMiQ?%C5fyf zV3Zip=Dj_RftmnNsF)_Oxi`3*12T07(1)f&aKOc=>Hz!-DPV(@;`U_TPE|AmD!5j^3t4&EoGyCv>z}lB3Y!;US3WINI#?0+>Sj z_e|hx0pk<%98#W0AS)#$dm#&WZ0!L?lr8$Q$h(7Got?TmIwnzCuLc=fdJ`+Fh*FBg zy#G>k=F@mT-Z0-i1FthMz`w>sLJ66R(&WTXAji5alqrFL6ZAwub@hdI=Pfhj02q7u z;kMcFN>m7tBmsyDP!{b@b5-^9!2CSEneh-wiOlPHj8E_Ox;C&tfGQ*e9^wYh-M@bT zS$cSQc)c>{%a#dy*0yU9{DJ04~ZKL z2HQ^%M?KD4y{|a}ogqq+l3zjmh-fb0NH!n(v;eXS&Q#r3kpIx1;ic{48VH_kK^yqA zKWkQN1*rfR!=i7A{H>Djo3{!;dS(tnY(g-D^XY2i79o4iDt!cozM(V{qVg zLX_+!akuyOXqcD;d@d}Ah;sG`%L8d(piIH&4CWbW_|)Ia^;;lxLv%V4ovdHY)#(j zF6*o1DrBk$fWR^BbcB433OZz8TwYE91Ek-N`-cZJT@e{GGs>l(jo&RSEKHc%Lt5>Z zL)M@mhv%ne<&%_TWg&*S9ae%$KQeL>DgpDt=V(w8O>`Z=IR%avFfIWcl%9zxG`X|0 zQ|Dp%4I!cL)5EoHvlXPdeO>F}R^SWlU>Hp=WGswE_g+*tJ@z=5op5k)0KE*>%>}Sg zIs`*NToEqjk52OyIDP~A7#+Q!ERjIr3Ojdq&h%5H8l6U{#d|RL>2xs_6ci3?w+N>BDef%JBnJ<>}!9SXbfML3UACX-;8T8Qcb)WJ@?;;MlWb zYhS2v*^E%CF!Av}m3Bfb0~@>__@ZB23d_sk#}(w{3gvp6O$y?}xjm7vflKqt3a1w` zJT~s)#?lh50x~10n?n((L?8x+jcBJ9SUiD!-lR{b>m+DB=fmzuF#5NnBRl5m6v$Rs z52v5-kz{HH1}MUGG}P3YiqmiJ9U=br?mqLm!TG(MJB~r-2T4+%gY*@V&sst7HACjr z16ua5;JCoGI!+~6g8yv){pEp}oQ#YN^Cs2n6v&GRoPC1EAIa~JS2^+`=v}nLev%2N zUEiIXyJWCev4G99t+$YshX?OdZg4PU`aUH{*I2v<=|9UMb~9NV@Jifi$#-|&st~-a z>A2KDIskb@OG`WARrbaO4~Y{Qd%OA?N(zkL6$c@($PMlXDVdoFH<=k3k)`Ws58bG@ zp`p2`zR;_^BO0mTrG&6NHc%dpFdx#>za7Yv9Gf|E8k1wX(VyN+)%R zQPb5$^lQA<(9~@0g{&+rgj~^|7;^rwdwh&y*Xwqm%)A)}kc}XykX7_IxgYC0_2t>v z*c#l&5-CI;OLyAcPB-{eHC;f)kU%~N{sETe{6iYorw0#JYi(_9=FNMM;AuJ(@ie)( zx=ls0xC#^k3hkKxwF-MxeSHDnE1NT)gM))eOMhr)aRDM`od!{0t(!8gegBqRYe3$RG*@E`B9(sSyN`4e3h+Qrn%lyb@{Ds;58B1!|vYV(V5 zhv()niOrGyEnq#xVL*J)haE}P_E$@tZk$+tKL;o0*LkQ(aKywk`Bdgw58<r!dTGJE(wS{ zV`F2#J_xo!sdiLUAn4!AbaZwGBeh$>H7&&)1C&!t4m>pGO>>A_M$q1&--x*FE4skV zE_i_RiMv0n11L1`)xnmm)~8YG8yflpt{CVefqnZ8@$U9OsH3CffGM~CG9nOeMMQ)| zUxWR%Gani3TVjwzL`2Li6@bi!EKf~Mg~5Y0fO1i9bX;!(hdVMn+?0ktI3HV=Fpe8I zt-xobsig&MuQ(eV4xp`_Zgd*=`VGjtLjjIs`|>eCO!UBl4)qfPY~Hzz0f zR)S}yw4jjM5AXMYPAG`~rNOo^4QRL(YK{9y6(sK7>*{veF3_F^#PuTtc5}D3+7F;( z>Lf`8#>0F00}k9u39!?U3Xv!T^MijG9e2PxrgXFb;TIdbdYq%L^n~VU9MR|#J{G^r z`1POVMt7OZ6v`N3cs|!(vQCatWH0=$DEg^QfSSdCKVd~4D8aYy|AFQ{2|B9TA#N}w z(^!k=8a_e^UC{H&+QqKABhKn{SsKU3!s0D9qaJ((9_YJd=;7@!1QrMS`ACbMO8fdZjuc)Bg+aRpJ#9WnU8g;lWv!aRa9IQM{g&xB!Xj_8vl zC5EKSNj#az>EU72-t#9AOLO2z{q}7Gm(cQDr?$7<#m+Et;%du3wmSoU(9{5Pn46os zyR*}<(FR1)LD+2{?`naoE%fMhP*G_1x!gw`gK2luc_7+e1X)X%pIyKjl~3T_28`?m zSDv?$%X4$!JfN!_M1rmz%zydmcr=0R>FFsZCg#xH*WX|0?hhOu)J6(`YglG6tt2F* z-pf05%v4+Rup09Ib$!?YKkeXn>umIKZr~#309yPQK1?kNbC9(L9SvM)_CO0oegKduzdjpqxeZF96lh{g z-6ij^XchbAABFn`2>kS`D=SS8^J&3kLac{8MEHmsWLIz?NKcP2VTZD2w0`B4_KHjy zbiVndpriADnEL9VuD&l?Ksp5J?v!rnZs`(9X`~xzDe3NR>Fx$e=?;-j>27$3?{D6l zH{*XepL_1PC-z=@t+kVZo;*LF@;2@z;wg;)A3q>45QKTvm)t-Z3Gr{7-G%u!?Qfk0 z#)K3qD(Wee&R!u!=_v^Q?g7=pV2Iy0>h)}TRh`N?o`KzQ`gUM$7jB{zY||)U3MJ?B z3*5CcDZA_#Gv7e|FDNLu768g)xC<8`^VYU0f=znkMyWx-Wb?O$6A*_^nG<)Q;%&cq zYS|HY?WeMtASOP;^3ss+#&E{JmfSx=6j{GKKY?ZJ#}y$KbHtAu*n6qNpJnQc)@hcQ zmNYt%!KP)E9um5ze_uj?GqTX)HrFq~t~u8D>1#zf1-o}i`0Yk>ZAFGNIi(b6nN#Ns zUymUV(`T8_AHRWiTxkzO z#g-uqw_hO#pgLaj6UeutV9J)3@V9gTjXczf53q-3H(7<(g z@qLAdsiXe~(uJ@Bm^Y`#F!lKCrXRujN$i6;t$Ifx=?m3mTUBNY@f4esG=L5d1&xIH z5aAytAt7=0q^+u?w3cEC(ewcH+tttnYJ?y_jwrZ;e7**Mr{RK3ZviKrf9>~zX3(~E z?HwQS#P|;YI(`9_clk$9d2gnPp?m)ximE%~O`;?k92{Kq#nuo8@caP46!4ZQe0Z~_rbLU$5Qfqu0P%APNDO9XFg>jt zT08*|f}AX|xX;bpGQyHvR#5gqOy6?81?zid7M4k0r@Ng|X7#a}MN9Q%DI*v_-^j6 z&JYjVDcH%fxF3UsqLGuO|yn`--34OA=)G8y6=-xa7f#;hS!~K2(ORhJ( zTGb#cN;LI%NweU^;YQQy$SMH2OlC{!KdnMoa*Tna)rK>nd3GG4vcyr2cwE*`>c;UU z3(&(6a$@Zy8XLLMU(r>PLoxFwYByurpxqU$#9S~Lg(4HQl7Y51dLz6ifEhIV8cWUn zAa9!6TO{({5Tm@&w(WSsY4NVN2BtQ@k6M@4Nb2WPZt(>vTK8^c=d+5EQWrWD1W1Ir z7Z(?SJq{-q*Gj!*QdAUb;-9Y|S0*AP1i?2yAZxrPAI(r=_V%pNYNyx&tNDD?q2NCK z^n3o)oCE*HvvZH71yq*?rbZp_&P~|a+4CBzd|sYOA`QR0M9V8G4oq21+~)}>i3CoP z+=CcCzo4MG(iBu>Xy-bL^SjMp(6sERtDUrk`3BD~CnO>s!S;Dm*{$rzO9Nt0yQ}~Z zcijA&Mf${?XoF0(5QBe~?+LBS`onRLpHJVC(Y?hX$jm_d(&;)Jydrw=oJ#y~jHePCskXMyb9RNd z4jN&h;K=-vVgqf>JaGeY6(}S?%O=_spm{@%z#Sivll;v_*DRyR%=fQ^?qBKPgJ5%9 z79qGNWFrxHEG(%w?aNLMq~I~Qev7(un3$LxA0L}*GM*y1?Tp|>AGWQVejW$;8OlHZ zfz9g6@fd(EG|bKMxRLq6#tn@O4{tsfHzq)Q1I51mqTMGxLZ>xjVMj$i$QM{S?L8qx z9qe>$RMaS$IBH0-J_l_O;nDo~GYHr+c;She1%*nqT_e;A>4Un+$#G3hw;%?X^o9Xx z_a^dFe=Vm|&+Mx&;eLZ}!{B+auBEU~BngiL<@op+NcsV8D8X09?{OWz%UTZ-LI8Ex zm2@N6jv`ZdTHF-DWNT#G06gGRDhkW1@tnUAq!e^)woRp8yC@DQ4vhf*^UBJ~(vmv} z@y5o+%)^*Li(~5dv`VHMdq^}#b_lFPUqq_2R*r7XihCBmBv~=i-O))~P;>G{hKCiR z8~XN4*~feorSwPZ?vnELu@FaFM>gbOq!0bgB-SUzy_QFp@0K4MPjF*0AQXm06WMy~ z#?SaDC%UkE*)##e{!x=5Sy^@I8IKb=(g_ zOf!DecZ~$6e-Rp7?k_DEP`<;X?}_Ib8X6Y)oeCGIOn#RFhEr8#)H^D17J1+h3H_`DB0qWmPws5oBfQ+1Iia|ht z#KzOu0nleTX=!2oH(l2LfFQz~=z`=p_!R9DW@TVNT12b}r{xqLNFf#JJKv=$vdv?* z2@-3t9bs-9);OaL0R5*9%I`GO?FbD+Zdq*za5kaeKn;6%crkSI_D#IcLw9VUKuFomYzOZ1D%kJ%Us_#o;1 zzM17>Z|;f$4zO=1rjZ(yHwXdS4e%;KC@?&YP*%=@i_Fc< zU6t|5&hjuPhR?q!s;rdqi3C9mFSy`XVqbBy0)S$DU57&iAq6~k6}>@w7t!RSJ~FT0 zA)r)L0sd;sgxL#;fcuP2D;rBWz~BMrz-XUq$|k1$+XZ#e%Bv2AgTN`V9$;zT=fU=o zhqDyZ=J@XDA4LZ+#F=-0WLqWXz-6p9UV8>%w)ZIj7=5f=CD3c8r>9SNLX2ml0761G z<$*mb!<#7)HvzBg0#cHV8)|x9!_^i}{0)rG4tHPR)a-zcf2!=H!hwgQ&?lk9-h&rl ze(EEWh=F82%FS(y# zZ0JlppXKLOC@E{0c)*+qn{%WBuhN{H^%C{cu3ZQ1tTD*#@hqX2n^m8*v^3WhmP0h@ zR>&R5El|H60)z)*HsisCY9o1hd60;I0K}C9G3=SdI+?}YLh+1>$cwT?>x^xTR}Zpb#h7PsLC2|x0I&)E*V-n zj9h3cH6^2rsq;aAFE)iJ0xh@bRyqJJ3~e*!@I%0*KvBy22w`?Pf*MpQOGz!m8*1@s znX?f;wS$Nk-1oA59X_>2cRg6_$p4Vo-`x)4K$9FWYuZE8RtX5uxdTHQymUD!_fVp{dr1Zt^R~LH=BOC~b?P|OK zu2O(VJKgTM&^|lLp!1cvy=1=&(eTDbdFhN&7_dQCyPJj8!6vxBzp?}of!k3st5H*{ zq;DgN+Ybiq2IN<)-gu%|JrKS;NJlT3_{_QE`!O4mBO{&6!Kh^nI=O&QplERRnqP5% z5@_@(5?V424@>?hn)u@=;!hKCrKz65{q=P7LHIlj@&h(M%PIf8=1F8jH?RIAcTZY& zZrCyj3Q0DHjjt-d5(zAsr?SfEX`BsLuklsL+v9zs+71CO*ek2wC&>iT4LT;LcB^vB$g-w4s3H=kaP@e{I=8aZ^V{WvWuY z6NLSaQACrnzDj^fQ1`c;?(@(qT@c(kPw>o5s}NG%__9#LzK?`6HR-O$sAm5x4UI3Xn~kA{ek4)?4uNqwatoZ%pzAZn}a-d z@Q+Lb&yJg^`F8OSwG5i3gwK{&5`Y|2T7-d-j}Vt70(UYR?_+-;YfWBEZ1`G|D<#}> z4x4v!W?Oiq-(hzIU~+c<#ckUDdT{~;Ea*Xl9VSmNFOUN#B_;+HbP5!_R+tzVyC(K8 z!PxFbkFP>6i6=XEmP00e)7{RW6J zh$8m!lV_qx5ZsNIj{z5^E(-w4`~dB$SoSbs%h%5`v^S;R>NmTs$7yRSYLmnh^9(+W zN#r9djz|79ld5gOfPkcuR66Z87G+@m8FZuhz$Ip&-%Z_saC`u6whd_6_$V)bsz z>@oe1#->BPr^`BZF-cl~ObIw_IRh9p!?|17TWM1l29;Esdi&;vvZjZl(u!-Bxb3a* zy28SNAo6&`GSmOw-U;UU=|+EzYE7SnBnyC6ph#Q!sXBLiQ62{RlCBgiUSFSPdso*1 zS$JX&{L!&78<5Qb(h!{MJ8+%HQOm#jjB4`=YHDg5o0}qn zn2T8j9q)xMpAL%g@mm|L<-?+=sRxcfH5=!|Xjr>Isl>}t^iG7ty!KBdi}gb)KoiB| zi9zYLV-qAS?xk@4CXEv8siB5rsH`0fn)aatO54Ncu>^wqs!Ec*ODB=B<{gJ8rAi|9 z^G-}+Zvf3a6Sir-l+SY+IYqnaqW*TE9W4`Z7s|6R_Z>%e5&(EP3KkkR}3MH$5AejiggTW6pejj-qn zqzIJZjylpU60&Yzth?Hoo=cJcWAHmvp46hOeZ_5Ql~;9)XyG$zI=HYiiNvU?U|H9K z8Hm~jLke-I%Tp|?zh0L$jgtfqg;b11o}+K(iru;1fOfsnHXi|L1cY!C>qH z>5iqSG6F6Ohy?d6!P{p5W`y@12V2IHFN${U2 zU<`3w{tFwEqM?7^|HD%z>a9TCw<~?grI|+#M7r;y;lP6gZ;@5_OL5sV#3Y9ye{9?c zC6&_IfKv(Y-}#K(=`xV)sq$jCKwD@bR-u3X#kqvH(tqpCVbQcCZJ_XQ zG_vq|Qai#hWsFe9`cenNQn6%d`jl#n_F;;ftMvob2yHJEZlC}9F*Y~n=0)OJjC8qxhc-C=hG3o4x1{8~omaQ|4sl>6vlFk^ z)?f@M^#d98{~fOy4r%=z2|o!QoyR@M z&qgnM)}%Z+l+W}O+#SR?lH-xN=oEtiN~HCe{KH;ax&|_n^%mXIFzdgA`H+(%>&RVg+Lo#-fHWFikVxxEFAb|=u$Lj&;l#M4lN$kKYiuO zii&~@(m;0v89BLu-d+QX?^0a@=K;ddD6fnhFS^OE!$Po#8|+jC>iMAd_f_v`^_Y zMokBo{Rbx&qe|-b!&=K_(K@*pT(YIbRxb>h$?ehz8X{|a43Q{R5Qi}|tF--FZH=NR zBCC?zDzn*K1|`_<<2GU~G++h$LD?9AVmQnSLKLw;PfhC2va-X|^Q`hh8@bLAWQ9P0SE&GU+#V4`y6f026k}`XT zMKb0-f@B@Ts1SASoy+5W_!1C+ePA(XEI#32g!^q$T7*Ld*MDnZLgvsAIQV!&Wy~%@ zl*Qs5y~vs#?%DgpF|cu0P*Zb9$KrmoN72$Rz8yDS#`)}xoPgOnqBLxC@UJn-W}p7| zNb-CAc)XyA&T&4P1>=@~zRVQJMDrgR9^k6x??v{nt?|ewUY9@ODgUVgE~}w9=ueV3 zdv$hkLBL|*TTnp7axyigVhOA0Hu!zeM%H=yJtt=-n+c-=(7 zhbNdxx#{5+vNd`)Oyt{I;g7~i0+u+IrKHVgA61a!z|xdZI*Z|$(}A6omZ3hbt-R^n zC8)uO?QqX(Snjww;qlSld$g`-x5()`JF<+l)@uGzvFMB^og@BAczK4ktWM|CJ&E?i3uf! zMhV|2z#YZXN#%Nnj_y~!%D*-RJ};mJ1WD$7(SnDv`6c{T>B|^@`m*m=pI6PUFsVS= zF=Stj>Ne4;g?c~v2=J}MwUOYF@+}*&H5TSvkU)5 zP~U$CH_rr|H@@Z$>Pc8CX?UO0 zup_<3nwnP~$ac3beR0AMamzTiw>0vuQe&M9v~JhjE4g}TErnb*kfaAhuBF^z+cDDv z-stC;2XnnU-x(!nD?zB+21K1RK%N5Bk!)^|lDIfJiuD0aej7{>2##C<8v!6*jt&mF zljg6v8a%XU+PAY^;Vj5HUfQ&cZONRRoT8dh5{9M4#T*&)Q#U-{=Wd)-@#!_JY$ zQ(V?>MaGkCSYycSNqU4NrNp7{2E)dVmAmM`iVj-`8{ z&V9Pnk2?(8<&=zT=O~|p&L^9gu^VLfVjCWW0dyjwqqQ|(oQ~bytNbzjt^L^1DqeEPT?}?_Bq}c>@bn3#LFvOiX+$1fi^~ z3_3r^H?ka0O7yr8*_WZTi_2-bPfm`Zg*jzrUb%jj(q^mx?w>)X=goF z&nqG^eRP+{)6hL#+OFP95BW1XH*nvC)BiB7VNs!(P%MWs;IN5Le4Ox3FM}{VfszWQ zT^S8~LceBkw?Ilq+*~?4t1~Mqs!`M2E_=6hcY3j3?qW3J(i`J&_i5$8OUX|7lbx`R zc=Yh*A9BJt962N$&(ofB!xTEQOX!#TI+ijH#`D0xe$9j{k*HPohW$U0fAZnL6e^xN zv`y3dF&qqw)0boHHU<$DPEvmonOkVOgKEh$n~;0Dm1zP_)*w7pDV_nUc2HnjukTba zQ|R%!J%x-&k<80!E6vXCDuJf$%q_p%&GMn9^xUNja@+xIJ#Ax=IvHh?>4o3C@5Z{j zzbE-rfO-oDal8p|ggz~i1c0d6)gB+Ro=w0~a%yVsI9KW=XX=}Y@vCCe2PFTnqloGA z+>7m7Q%3YO8a~&h!*zeXmG)pP>d9KcnS6;EGq2lp+#)La_w?_v4>vL1p>G@hJt=2=F1Or!YuwITrt$pP7j*ZT>o`%a;ke~E#$e# zU4HX%db%OwkA3<5*WEO^c@Nn38Pv=GF(B&!EDl06G!`%m0JPhv@06xd?8)(=VA5>~ zSh90)0GD#@dl3nVKG51`GU(qeIl!pIF;O%G{hv48DWMWp=N90~aLD&|7nACx04LPrj#C}<47-~dM?BNW=X8^2Y=0|c}UW6Y&gnSuP zJJ>!DibX#WBwCHm+_(*@j72E-Z+)R?^{ynsQlq2MW*h(s%B`7*HY)c-RJ&ylxX#}` z9@#lLiObQThqyIQ0!ETj0iYA<*<1qJk)xyIWe}$Ud<{g^<&YebyM!HCnZAbu!Mlfa zt*oR^Xq%Pq2Q4~=NVypJC+3B^=Euk9nLm6W zn|G!lHsbECTPwC%If=`lZS&mUKDB;>H0S+rY-9O%(zW&2csx7lzB|rN-kEOjIw;N# z9(oZQNYh{eRTX&7_w=}&Ot87y{JQ7@`Ak&OpP%{p0S%sjDXI6|3^M(R4-w(vrw$Yv zDpFd6%toJVY$`G{^(-vt+)1EBuzjiTO{0%cy0`a_=npBLU0S>ljN&`oo#bPUmOLJ$ z_E|~D-#iIP2J}k>iT}9&+v4;p*MTH4>vAM0`+z_%;|?zNLjBiARN8b%emzz#Dei$1NX^Y{lt{zT-DiuxlQ*r$ zI^)?lukWZ2-tqZ7&W-`iSliLwUL-9nD@zD;tEs#v2UTRR&lB8;Gi#tq)Mxpp8WhsV z#Dc^nB{#P9yC){lBEDm(u(PoN8kS@PejqFf0m1NB-{>J@06fikfN#t(tLcAOiOPf( zh*!#q`4fc%{b}rOir7)GAqI7f=-c&e#l_#Wc|{^N-SS4!Z|iMC6jz+s-j`Qhf(2?c zFcYf&Rm-T)4cvA4;(Z@X_n5Vh1(`?UOuZ(nb;W>v*&+iQpDV_VF(D{yL*qaO-@Y7y|Ds5^X&V2%8g0i8*3F zas|9WFR*wmKvEDbPyw{{O5N5Vd#cxAr8pce1N;OWm_LG)`XGia{7Weou{gBl+GF{uArDu5;B)zlp zLizI|B7(g?BGXh!I@s`I$T*k3(PS9g+WH94 zJ^?Lac5JL7E^6e1=WSJC;m-VzBEa!~S(56;!o;j%f>cyckj#Pxek&RRpQSEC00*85 zJ;XFZNmvR+=~UumJ@FUBWgvS^JA2bi;`XGRico=gnQjT)hi`{@aUMb)E{Hc(SI#X2 z7yTU!tnl==!B}3#JL0pHa5II_Z0w@@bl({gCBDoR?Dg zAoL4Y;?p-Td;4&fPuU`v1Z z3R1!Ht#VJQr2ur~%Bz*tU02>z)?1c?elQnP`-%?9%i4^wp4*~Df60?nw6^Y#Vbqgw zv;^-zyvI53ElR>EIX?zD51RGQzNCNJ85kKSUCDru7BSGTLxOZKYyvP%Pltq*4Gh)* z;TW`8I&+`^MCZ#d(x3u9Tn^gWWy1op!mnY7OIxoU{KXYqoqv{=UoYZT{;Vct4pDb; zIZctgiQiRNu`D7v*bm3*=M0$f3o;vLt06y75yMMSzQl|-7yDYAjOw`V6Nx{%fes=O_@0czB5)W;1n>VEAWl6c1!Z$JC)3c0M^}GpP{j zqmG(Nz>448oKa6|cC=8;k8(7fNekUFFqPy;OdA`KH{AN+HHIAd*8AwC$6lyUO;zJ- zb@l{0*>m$RSZlRy*H+0{H2g*tjS>!~W_|(xMUP*Wo6QTRbn(tE4PiXk-kC5he}F<9 zAke2hm8T>MJ>Y22*3%A@HK^5-I0^(8=&DaAvPDF}X@@`jGGHt7wrme*q{bKp{ z+~u9cbnA+lJ;`PJ=1 z_;8ron|(C(^-K$T_#4v-MTjvQJ>&|Sp9N*9HFu5IkPZ@-T9)#{Iy?L?sq{?%6007yW%+svU2?=E+hK2QZ1)erQ9LN8^ zXiO=wyu7@R1_FW?&d(~Uh>6}b-dM835!-~MWA!7$!ng7p)r90E0_J12?KF;!shG`= z?j|;p4m#pRe=^c~R2Q21BR25P8b?M#HlOj^Y?@7*Uee%&vPu;1B(&Bo)hUx?K>h0+LA%uw}U24Af^FCKx5Fwnmg8u3>A9V+11VL%CS~-@=mE3sXtAhn(VK5~%n) zo|!=EroVF+u3AS<^0QZoEsbIKd>%(II=@gqbG0H+L9Dq#(yT*3WVeC`<6SxPRDVbG zJJgQYx#hKlDtF9gvYppz zDunCr{(=kaF&sNDJ4O93-_IXqn2#%w?6fwm8=cyJ*NH)kka$=N(LDX_hYHINI5h$9 z3h4K8V1dD6IJP^g#~W>J%DwMU`0VZSISRFyd56eu_m+@^21V#*_T?}JCympydD&_A z4?Y?5<+2T9VjBC-Blh+L&b{r?T)A>qH|ISw;}9NrCW4dJ*?o?cZ(L&Lt+C_LH$kp@ zNQF|uN^^_g@nKoTZd~Wi?kB|8r$e|yKra$^8M6(SHrVL3$~tdo^AY*%Qay9gTfMbp z#v!yjS>X3EyK-$PS-Eysm`@aHZT&rMAM-P>X46?>_FG=Xk;-L*=C#w5U0aSz^x%=V zV{=q{Vp2wUmOtolo~xc~#itnxw_tVZT{AeK;9CAK{wl1Xx<)+wjy_BB&FctGOToND zBAaT-`h^CczEu?wYBV{?`VISs*~490Rq;0J;fI7D;nj+e+Es%xvfbw`lhW%Ig|m!| zmT6~BiLn^XMrLG}$@7||bC-A|r@8Ce`SWVsJrjy8Gv*!JTJwn8{3lE7TOq9@BMO|Y z8uP4__!=}t`%vzn_3=&E*8%Klk`7)6qh?03EypntMPE7V-d?l%1eD%S|2iLrd2aGh z;_tPue^FJ>8Xy-`3~WE5TouH+ah-H(D=hqA)?0ssB$knMzYya7C-j?;@|k!3&Xe@m zK}bVbn2+hx~>A1^(&}2S`P1SpF$Q&wQ*? z@!6Z=^sETas=qY}J#nZNVrQDe^O8NQdS^$!P+A;R-=uUL8n-BDRTiu5#fm>Xf}59~ej=KVs}5L8CafhWDuA+M1N?97xN} z)Z)`)Q(7nV8vV?-G)BHP*)lO;DN2kBE3|<2Xs#YTdalIR9{n>7xpx7zTm#&zTwZ?}%3cVck`wMIe+Gr{X9vV}nCIQlQ_qO^8{+q`&`_^ENl55YZlvmS=JY%Qy# zk4M3L%eT+(mvaF1-ucJR6kUbC_%x&p9#x)0mJFw7cB8iOScSh`o9lk3bML{p3Ru(_ z$*R)vl~;6H#!s;)z+BLfQ$QoU61p2Q;$lkf&~X2azh8Wi@N*1baKfp8I*$QGpX+rX z!ah)7Wz2dr718~+^R#oWOxX9P|K&fIhHA?yU~J*;Nxy%qu|ZZU|6_!kBo~h;$6C<1 zV?X<@@TqRpWANau3d&oRxf3oe=V)`gE~{DZ+Jpy$y-pO{w`iKgqz6Rgm4c$2{NgKa z$%b~pb*9W;W?fW|GJ0o^qnKtB3wsLw_esFYxQ0b8yT;H^?knid%GGW zQxI_?#Qr{%Ue2NFo{huEOX6MJP?rZ3gl|U$@UQ^z3Glh_0$db89|2*|q=#Bue0)G5 z8!j@CZ>Tu|v`!UB1YR}vp@21LHo8)p{f-!j8-b{h5*_eY5i4OJobW+b-*u8-RaGSs z{Ox=Tp+rVrUPATY-~bT4l~GE}0)eX+0mtn4coe6{323nvHn$;7ff%Ur`E#fu#A;l6 zx|O+klVM*d;4TAe2Lo{X1HxcY|G)s8yaWMY54i(w7bqS`s=lM5qT;v$HR7^Gj|8BQ z$m_1{AHo-c$ObMmP!QfUAcUih2Z_n6l0kUjRi1jVzn_!(ityCe)Wk~W0e-$W=!jQl zmt9d2S-8?XeQ?~QqpbY>`~dKCCQ0E?@PXut#uI9<4$!9Z%3TlU7~7T!Q$9+n;xK*= zNb;x#xCfxRceJ$NGrxi0Me$>RSZm|}f^tk%U{w&DlqRNyEw5Yu6-)29T!~3)b7p3y zcQ|`m(DT-p>s5pWSg2+M6fzqloLPHC^3-jCWJH2v49v!c$%F+kW)V({1Vkl;pPTCP*{J=1upWXB z0+Mz0_E{igo%k?G0|^G&0`)r38%IE*b2{AJ+%#%%9*zW#P#jmU6J>i^vho!eAa+VD z{0jsOvWdb!RbSrd*&Ks*i{}48vD)Kh^#J5DlS%-pJY$2%rvGoYg@EQ_Z^72eDm*Ez z6%^-Fe|L9-!A5Cb$pWo~m6a9vKvK4+?)vdWPG(22<6Il3}V*Po< z;4zO&O2S!k@`pu{19!W1^}!WD0MFd#DZ-5*Lbra1z4$Jz(Zqx z1=NZtMc5-g&s&xyC%{|-Ik0niwkj>Tf;p_qq5hbh4SsML5da9OyuiTb_itIxAd)Tv77ajk z>@i8Q`rCq))hV`DiiH3EGt%W`UulPD55ON^znT}CF(gDM9@s)o$ypp0DrsbVR3O%2?VESC6ZH9Q{aR3AZ0UxUogkW zk#7Xq{c;1~t8jod*>{V4^Z~eEnGxUg8MC#ww`aL1+u5;z9|6GP;=+Pt6mbUdDAVL* zTn~Y*1fY_Wc9?C#MeD;PWXF0MTFDBYi+nP%dlsLTFMa5StQlS$)#d!qc;X@%jS* zc4R$>Y@h!P3{)_nh#p>UH=|w)7bQFZbDvoQmSS#qNHfpiAjCv8x!jusN0!@Br6Z8O zag2ey7}PFk!%#s#D`n+(%-q1QKqTVka1kg+{51$OA%VFSSKWT0LdMdLdOjdZ(!%7z z67eT}prV5D3KX^mE9A69e;w>*5hHW^&A{+*^+>z=~i) zobpvSG)NA3ySQ9)RT2Vi)Z^;Q^AG%25K3LY&Ew{6!Jn?KF7WOZTD7p1PtHIcJNY3m zHx~eKKy;w??eW&WZJCyt8EBgwX}hP*>pKDsqGgET9z7;Ne7 z?QJ)8N80DqloWw)=g^!0uKZ=`15<}+j?7pQX!4i8;)<2|GBd8mm_k?5y{t)6;T%(AU&FDaG|lidmE;dnrD%sZkFgm;x+BV`M6 zi-EtKTQj3BVht?^T8KD*rc-2U>XK_^8_*p|Y+Bg|zL71W&9!%MsIdvr*V9|sI9~+H z`IoZ+k|+K_!2ZItZh3nw+l}Z49CG@GM*)d9R5T}>8m@OaIa|u|MjIQn7PR%3x`dsPl<6JYYvudb-d{_y)+-_n;6MdfdYKuDli=Vr zTHVbg1OVg$lL)Ghqb!Y#{8xZtuu`jb=YWa>P&|ZHwN57CaKXI?fZJA8FL@sK^}x5^ zI^7wCqEx>=8u%S9tHPo;(*5S*t0T6WZmXBrT&XZYbuDX>oLOl9WR*_x*FYQoVlPf3A+FuWsR-8bIhjstOZ=kq;MU@|sNX2V1Q=^ruRZ95T~%>YJK zTwIKT&p``zbi1pA!&0>oTvwRr$(hn#|CRn~2e|^sF?`Sdu(#$j;rvs?z#Ae=s5uf1>5*(5zW)={4C!{Hg2S zSoFCcu}#MZ^;0or9Ohoo?9B}cgTFTt(O*suj)^ZFqxyF&xZywno>vTx{eQQvPo4a) z-xA!i&VUrasL+H64`1cot*WkG@bhO);P6J^&+3geAEgI4aqEWPZyG4I5e(k343r3MxvJXp*sNX5B zcbGDQ#brPnLG}n~>27_K?PJc~3n&n|Ku#);f`lZd3XZ->O6cu3z#~d+^XO@RbTYE|}g~VP;ebJkQflRjfgpmV<%oE9NSMsx#Pp3kw)a&B! z9O|Q;t2Npi?7v?ocOS%K$D0MP;>}*(w`kmOLuA{2W$y)62vwpV0X2}mH``q!_%+_= zEXw5cKzdyy^YcQ$kh2^~&xV|gY#kK5j&vcBldvCy0W~Hs3hjQYN(aa~VHQDa^SqmO z&$!&8P$*u@1qN{I0ly2eth-8OVqb40zr%4A?euqNvf6%*Fslnv8vm~6-n*z-NTt@* z2F5c=ER5-l%&9ETsT|_T-%`a4By{b|ZoAa4)zQ=l<&AooFl`u+KYo3Ptu4hFdsy|I z{_|0ATRdrpy1P>!;t@{ur-JpF>e*@^2y&I918F>*kwC_SX}sF@%|FQp@I?LWEFfRc z4xiYiSi+x{G&@m*4&BmACat+dA*(6BcSScHIdSajG40uKYRSTuVkOeyFIJTTGgWu_ zVs@K8kDLiJBMxZeuR8ig^J$GOy3>ioD0*HS?@DP3?)GbX1(A3|PMCmf0$5D(=-rlA z9Y-&%cs+~IfTf~btfs6y+4=IUEh_3KnFmS?o73*?Am#^_0`I(nq{4BlU5=JOblX(` zlbz*H`Vfa%NQqw}$C$zW98zC{^6lA?o){JX2cL6fD@rulzalM zsX+37!t3_c={Hux{H|;Ra_IBys)DG$q2L@NJT!*4O`~*Llt_6FlVK}vut z2>}5?OG^v55AO*a7#^(l0I@CzgQkJ48ynliPi|^z*k=y7dmi{2Avku{F6~sWjYyNn zidLLO73g&Hi{(c654Kpf)rlR737t0Nu79P^ASH&K(;|t~XIFH(yB)eI$fJl-4g#s7 z2x2aHQdT1vY!p?i{uH-L>4*C~lBUT?;Y!HNQtjWOLqHITo>75S20}F;d}Cxp34~BP zynTjk7YCwGe8x~_u(xSF`uh8G5BuEDF#zo62w*=QuUWIQlEH!Fj;;JNnz`SC^-9(C z(e}$NYuGaBRd(qkGw%#l2LOAJqL)k7>D&#)rNR1lTVn{mi#DhAsEH0kpRW1E-(t^W zHAtG8Yys=<5%o)XRn}$JvyFdW94A6hSKA)bbl$QrYiMJ%ud0 zq&|nTmvQl()9^;S+&3)K@X44Rp;p({&Ab6-BHc_r-d3S+4s)rwTt9S8e(z!2t1$hk zBOEn3lJ=|E@f)kVi&TU3NF7v-O|Gq<(w!1rXMOo&ZQMmGotOs!zMW4Wba6Z4J=ngN zgATx{QcOr;m9@BD4f`OqCxdXjU?n{}gGR*fOUlw0Qh08a7JGq>AZ6*z7xpa@n}3|) zP5^$nvMBauv>tP}YRLYh4C-YUuE|Gtq7Lc*p8X8@`fR4r@{zECuQdwM8bviP)(m|) zqk>kP1KFH}6dEIVMl3$D#akSfSYz#SjIWC>+kjj zvIq3`Rq_jmVLu!`b=}62MHQ?Qe3>krep~7fCVlykOR7OfDyBZ7G0xR0cG)tE%Ho8@ zqGKG&W|qyWr7%+VPkYl?+V7a>aqG-mmC(whyS0`Qxg?sPjx}-b`YTq&K+T zS3P~sMIFIew4Jr9({QLxbgn^j%tdIP3Rs>DV>pyVw=)iY=8}fBqlFxe1nkc3F|D_8j9T?seyEPu#JJ`ccDI{#>Ry)}-1znMKvUL%N~0_-&;$?IsVPvF)VlgD?AhLdRt@v#&uZH zrCQCIQ`3$T-aR>NNrfuont{ihl$_fr0DWHk=P=yx=2LYI@}VPFJ7sAJZC(jSX$$T# zC(`RG6@>S*QZz6KZ^328(Zzd)nU*5fqc~fTU9@=U8BkN+J*rXAP*EJc&9ER>pdC~R zz*n&{igu<5YgM64mw{rcyQ{$VQ{aF!LsB-Av(T;yAX4w2{oFVE**R(XV*;6f@MD}c zLMSuCmX5S2Q{u)=h^S%%W1C5Fk$|n=+QJtNIjq&yb{({T{@Vp#OzuX;7iR-yZkt}$ zFvL9?e2qYM%ZIJIH}Yf(eRz2l=m|uwObv~()tp(ja_jaDm`d6bLz{KHi@0;51FKF_e52Rba?fQRX z`x}{pc4-)h+{w0kOAih6!y3OqjZP|FigC9!^`c4pv~_Z)HTo`dG64MhTTI)B*rEch zTjV#UCV~^%#g0UV>gcVB$o#v;X6GWK*fTQz?ya$i8Ti(z;TZDWzubq^1wtnsFiY;m#e`$$j%*OUS#`P;cSVD}4%OH^Wo#p(mrb7l2 z1Drha8wzD9G1H~sH0)H`zNN|h;x6Oe?>@o8Zzi*ah(WpMV(tm+t>%@>>{!VV9nCQr*DNw-U zvw!*>Zp*Ami?;dm;D;&k%;;N^ zaN-9zm5D!6XDDAGlrpy0C9&Bqbp3*jb@_w6sFKfLMwyv{)TFxGs(ia#{CJG%jf!|* z>B}c^+T0vXpE4fNfAUd`E^$7lf77`8KoY~5BD6V9TsOq80@GX5K`h;wJ?ms<;^Rc0 z&hny8|M!-zSrwCcsJFTe-APU`{AFrKLmQOe-=*CJRb&Y%d;fF$9%58rs<0@wLb!K_ zZZx+h2K_q5J#A18cmwONrmz`MkzUkj)8mw&*qp# zT<_`pBI>v5SjK_(Yws5AzmI)S2w(HpcPLvE$7~8I&V`^Hi{N3G2uYq#j>-(lLbD{A z9kDq68?~$V8A$8m5jT(dinW2CTCahwTT@;Znm} zC4Swhx#DNFHv1xJEhM}@5{+@n32s&rSL$*uK;WGYI0}J^IOa${z1rui^Q% z|G|`Lr zp6te1_UKI&njPr{Uz*$pm4fsU6I9wr-g3?%>#+Pc>RmC^hvrP%U(4M^6h9r0LoUSy ztuE#785fvKVN`MB@a78s`&WWToTxG;{*Jq5i#_$yk)DU!QAVnyie0^H(eI@g=I4`3 z&JW{vwRd7p)R8)zmMy=Hx%w6{G-w%aZ>IV-+SJNAQni3e2=#TK+2f#sCBT1lJUoV5 zt*yP>{~4oC6;)0|lWmvXQ4sZgC%x9-sOWN^%Jq(9b6*}@@>x`U-un-qg9W+LcCFe^ ziIt5gPB_YoH!pdD^gh!Y+ICD>mrl(_^rUXWS;gR4E22C9ly0lm08K}D^G2V7Nl%QK zc4e{MECk2<(&vMy59@UMbX*D#_sEP<7M~;!tSU3)UT{OB5dIsSBFHN8l0Q=4kN-7- z6PJ8GdH*HmNrTfPHMo^3D{pbcY^QCU7K&I+S*qr=R`;v+*6tZ9Znl8Q<>;s^F(0N* zqwI$hrGmlHS%WN_Mjs^QM5CmB%%r2RBl9q6m1zlB6qY#LBlF@lQ>FvshP6kP4k`>- z5}o&J=H=@O7~Yb}`y%f^{L*?d$)>rbks-kPuP@_1b|&_)_wg{kpX`6{ebq3W%p zx_qKFUIb|Y>5@>or5hxrQ@ZO*gLH!;-Hmj2r+{>KHwe<5(s_s9Ip?l>`OoEI@xC+f z%4!M z94{8EE=#PM++;hlZo=BvWTkOzn6)c|=~DH!ssLe8k%G^ep`KYyi;7M0yO-g*WdYO@ z?+47!X_UL#Y+AG&$36snspju(E&s({z2z9Nmm#$XLJY@u|GH6K>{_8im|%7)$6u`> zVt=kD#Y*{^d&LkUs$2U3A@cVA#$97hhu{he9X{sU!K}ZFBr@9qyiWD&`@iM34 zciF!%_|aV;*HE7(I(GN=Y*N+iJjl)HiHhB*`Fn3C^)GmJA!yY&T^thN7R&B4Ja6q;{S2`p79*k59}R@# z+f>YFPW`9(=%tG~2N*qle@S-pwOR2dBRG`#O3|&O#{+e9cUkAddSHrxN}yj_c6EB# zxu`ILA~-&uf>4ZkV%O@WQ2%YROc(PhCC~G zC$7<#86U>d68gTtm+%;B9QSg8bP3NWZ!GQLNg5ds2s6eO)KylV%4*LFQL1yChswCpV+ljh=KVIPQuyPzTNY@|Fxq@J-WeMTLN2C}E35ET zu6cQj-Jv_Ps7H5mD>QAgq0G4n=_&hIVcZbJWP;M}L8QvD1vQs(*C$T(v^3d3kLEnM z=Q@Uzl|S5Rgood;DGdE-E{3}oH`PfBetREZpBt_i!Jetles;I|m_VSBfv*{hDKm!L zSDjN;`}6K%x40I;9fEa5(J_T0+WyglBhJ#i$kIRtvVdyB7IV@Y&+!&L+$*dn3JP>4Z}~B8 zDirWO-}K7m;T9+d^5BKv#43(a54j(le@gZlttq&!JQs#|pQn-i-r|Cgw|d@jpUcW# z@Y3^Q427B)e^xW`rj`$dZ0B$L-=!}|df8u6=X@*|;17$&2&NGGC;&H|2g`yad{F(P zc*846GK;-vZHuPk(!68%Lcv{A-5PQ@b+98xG@pb|s2+{25&N#aA0ey2|6>eURnhhKeC|0?AdsGK!IMpI(r-)WxFG(!6yt_shULyo1Qjx@(!X67dC zqJwma{`ZwkR1dAgpLA_K>fy)HGj!F|9(pM+r|073)FPqs>&ogk^p7H5Qm#4{#R2j-$oKU?0>}erHFET}%1{82kZbsb@~by5ld&qPhg@B{dCpLGp%BF| z7II;yN6mG;vS6gHfr?ZOOVXSeH1?-S4xUcbjy0c>yPAVNo01#omZFj{g~o35TQofu zD+TBKBHV9pcoGN$^ZZx{3SiX=;Ab*VlL;i`FDj(b%0>|ka^VbjjcHzWX>xmW z%nfOrWmQZ!8QX?fHgr$bcXco<7GHEMUXwBSrEf=?+_18c1r5zmKC$aB&MOi}+Oqsx z-$Q&-w|JYdua}0UkOpK%#F~klV=`#PQpn{}Xa*2C=Gccy-`CemYUA`~*jLs$aam?5 zxV;jHnN**r^Lo|tMJJ?V_>OED{z6>Z+%V{*O|d9uO?&W|lr`T|X(puvO&pxQdS(gN zBBAmc%_)1hb%J*?koWpCvU+DrHv5$Lf3Ku0RL}gkwgA)gw5k6&FBI?I*qnHjx6cK( zQIJRY58va-E7ZABeSh=1y@^Y6^vj}GVPBZ&yHb(}z3*>Eqha8N@mP5V5AbWKiv+kU z1qLFc*Y#2ev}4f~W6|a0k&ETeN~DmB2i_Rv!!lz$R>qL)-QTlPRcB2+JGjex2^o7q z^u2sZ5^%HRHNRi zrer82;j6~(_eoqD=esc@eZYjtkhC9LkyKaCAXJVeUOcTSyU1)JU%i_q6^EEvYj#z% z?oUBmj{C57?H007APhx3rM@jJsgLp+taPccnTr=!F@c?-glECXsm$dnCFMSk=f9qh z{x{!CU}0H45v4=jPSr24wV%ZhCeMvh5bi_asph$~t9K>Oi?(lR~li8Hb~tfuk9Vp_Gm_On>(i zW0QEpRxfp3BaKiykw`NUV^Aj4AisNDxpRPS+O0Q|8)AOB+>u2pD9FTF(NGvPkhwv2 z@2{>{)8lNrjH}mWIz*{6uUx93CAQ9lEjZ2!#vU{ufxJUr#oSKVuL+?I>VCT6IznBS zji0VrfUcR6qL7ilmXZN$C|HQO76=jda6I{0pq%}R`#Ozm)sL3XHW4o1{YyL zO>u7_qk;Q}h1Fp}@&n&59?VKVMkEn@!99`wtiui#Bi4QD-RAUF5)Axl1kv@{jKdqF zb)#L1#pT2BSD5lCva2@kB}dRM|7Z6>1b2lUnR5mddy!+rx*v_2|K>917oUrP zb9X&A;lOF{>#|4#kLX~`(zIu_SY5L6a1ThrZ|v9A^fBm zT_*D;e}~d@G^l7G2$DUVjHi%-^(Et7*j69ULyf$a%-_pD$m(JoWW2;f(#G4AMs76f zZqDMnma*8yXro7Ea*>&)z0-N(6R{sE8}#XE;*zlZuzS2Neko$4W_1fZOkBkinlxOJ z`_a}+oe5oFlyWV67~sg)0&Pg(XO|JoC^FgZLRycnaytxo4XCj?qOH%*9c> zo`*v_t$M~C3OC9Xou&ww#O$oh{YX!SK^Gk9?WQGdTt9q)t~R`R=t7@Ygk&+iHTZ}Z z9GU&A{om%V%^M2+`V@-a2a~?De<%M7cq4sDm#m3(R;c)F{w7_<*ep5e&?gd2VMG%N zx~3ItH@}oX!qbUVlEzPOeuR)`6kmKQrEcQ7I{Kr`nd6DyKziF(I0Hyyl4 zYsi$L-HBwl7c3NZLn?0nnx6uLPR0<&)A9Mba`Mm)xgfmRxOlx*WXijwsjjL012L8{ z*{RS4@lh(lk!O^(V5)G`e|2pbP;1#93_kMAd1??=|7(_aETQOKZ{6d6DTQ1Rsk*-A z*x=dxK>&eHrv2Qoc=AW_Bup_YbdtYfm~W8p&+GTZk$-G6TtFaoTNe6?7%eo`t8)G4 zUEC*kL#PCTh^=m;o+SThZ>l2k)Mvs%Zp%adMX7=#cMdkOMC<)oi+?i>+$=diz=Se} zqhrjC!l1YMf(9dS2s8SEq!thc4~mD9E=|ywPB7E>Zo1(V?-?xWUoV)dYI4pMm8bE_ znymXZ`k=ipq6y$@)xjhQ@RI66GO~2&q@8-8n}peS=&J0*E9Ht#A__F=;YbY{K`I$T z>N(G;Y(`V}HZ(7+=&BNd>&&({>a&zH*5bG9^xD84ouwm58Tf5ASF5n4yqy}4gYOra z&w23Cx_+RmL3-rBG_eh&iHa&2p|sNf@Me07qQ}(wzg8qBT^)Xl2yMKJMG5gs+CV87 zX)dvKw1{r)Z7>fZ>$TO$Huz<$CRE7DPZyGe9iWn@pCQO{{-Mej4+1pZiQnRL4I7!& zbsbeK6k-ZCMy$X1HkYpVBOX8yR>zrt*LiLfD{jon;N7-=ty+q}mM>%Hlp0oNWIJ)t zJgo4B7Df&?6-vodm|a+4+*ap`erZSlb|n;b#1Xbm9$Gily#QKwv$vYWuA|tl2RnRL zqV%4eM^<+3?$a%_NU(H@IlnthR3gF&LoYeutj^0hwmEmN-n#o%co}|^HjMH}O*D#n zwEnGpe^ok>Z8pf1%qwj^t^NbQdB)sd?TNG4ROOk(6d|P?mhjn+Yb3DAG2qk5BJP)O<>u#Nf zcJT&r)7p^Z&)mDek{rtU7+H17F>qy%LU0$z~ z$%)@;5dy7VqelZ}o1ZFncI24|_^-+XzMmB92n_!2jFb0}dWWXcwx8#|e>4fhl%Pcz zX+RZhSDu(u#^E9kF-8#h?*j1K(`#R=M0URJ5^QlVYd46!j=8Fi_oQ2L@c3V$KngZ5 zRpK)hE&>V<8sYN{goTQRG^5s#&%T>-uW8ayl>~`$-3O(Q zL8q0X{D_Fn;_rcXEHOU`bGUxw?_Q`KhWIlrnhk%L^Y41 z0wZ>jOL|hpWsEl*=9N^RFqZrsEycEzf#CQL5l zXM((oxu0Spy(JIa?ze^JOAVVuiRyQyTW4Q4-d)z)L>qEeu;#qO%raMH6KWz*f7>Yn2olzB+pgjxYfjmv?}e2gcH;+yl)v@xTKE3oHbr+enuTuT4i_6 zD(L+Vwbg|_SkQ@^j-<%9N9EJ~c`c}79Z|3+7M(!N7yAiOg&Vi>)GX*Bn7Zd*7E6Kf zraUZl+UX3}mGi_}ixKV-e$JWZMq%P2&8k>|F4L+dkBWqvXfBIj=z#J1kN>7{mlHHD zaY0NB?A^TiT0RtzGdst|@i|45wynJPY1C1)|13zIj;tVXRBwE8>=x=QqwHQ(cT#rV z`e|#U16lqx%{w$zKJ;8iTJ_t~io+tPg@0K{i6Ze&h~oGzYkP0~S-&I8-_ej;k$D+KCH~IA0_sJ+B(-rw~RUlO`A=?6s4jlap9LbvM8JxXP!&?%wXEaU$yAg zUSFR7VRCdnPNr}3$nFfy^H`-9xKZ86oSXDY+*D;sWnnn==glcEB;{?Qzcz9b5=k(* zY7Ha<*Cz5m+GJmiqk%3>uohGlrNrzoRF$#LJDtuB8>!+i9y#72YCDs9?geM&6||_P zc{QHRGYZs|vEsJsdi87Vn2n!rydR2yH77>Wu;yLg4}`u2eMO2?Vnb)?Eig@Z;u{qb<` z$NpfdyqF}mTQD~FfC{&yQ_sT9Jg8EVP}9@%0W${B&JXN3j4dpZ0pd^|tQ#i~ZVKs` zL)>)va`@(Mk5$gjtRN4_jQwS;^tIrBZxh@&+cz4sq3H*V!5_cE|`95k%{ zIGv%)iOp$aYB=dw&9GOgaOS>Y46SG-Z@mf$>QGs8QWKli!_je3G&~YiDj6HvO;6PG z*7Z)B_^AQ9UhDJ1JHjM(`OL z>VgooF9LkG3m}x(R@c;kK1*OWq<#UH@dj#l3Vg{QDQoDds_5wwG~!KN?tM=;?T|3@ z%aR6bybqUWM7DQPHf9M1TC6aB-T9eY80GKDZ0U^l=JfTJ2okq!mlFt@vJ>{v)-SkY zn(oOZ*BE{%ICf=hIdgF88_{9*F28cMv99NqUVvDHr!?E|hxL$&GZAyBx=h!qWqS{n`#P*Cq>GT5c1r7@`}vQ%JYScV&K&o9jTr#>6hM?#aVUN&Fbwoe5LIdgokmxr-VhvW0wzn<>B z4h|p7>03cN_HTB_-`XKO+PUwTPo zSf)R%va#_Z*ZDw4chD1x%gD$GNFEOl_&!K7-ri5Z`SGU=5NnzWy?UxVzPND2GPbs| zQWt~zvJ^u?J(d+UG|acR>jTGS3=9lcl^pPE zzy$*jAAijR2<4zaPde163S|uq&`re+I@W*zSaWgk^78U47&t%*foI~>h3K1`@2#zU zUjcTEf`DSJg7)9|gROek2_R$@zsO2Sfi`Z?TfYMuWJ|9*Iy!*t0k|#H)YY{BJqJ(# z0Vr{I^6UTi*K`2gIYj9YXxGo|>3|CZ@I3_GXS-gYc^Y7F07#S*fGOUG0|CW1S`CI* zP+o*U2Lc7vA_08Xb0uRHh=GYqcZsIKM?7F2P)@S~SpJ4E0TS6STfj_k(6+Y$hK?C8cxF!HVV31%5g^$5X(ZcoINm zw*8lbc;Uwm7(1d`1plph3)v1}&iHJ~fB> z_Js8EH!g%6d2|#NqXFO)Xf^=)CVdeRfK5T4`yqfUnGXN_4%yt^PKb@QbcdPC*Z^&) z)CI@$RWMAPpsDd1AaJr(x`sxwxV(XdGav~ygXMbgKO!D5y`XWsy0(`5Edt>21M50m zCf$dNJxqbdV4#`+&`QuPw_@!#cLgIuIR!}06vCT8G7nJC<*PsHfh(;CPz(X=NnbL$ z5bHYs6FoIg4mlu_MkJ=Z&@ys(fEk&t4eAITDQSCC3O%@``9^yGZ^me*hrI{CwL<&w z_|ao)IJ%?&b+yHF@$xJ@7lEYo18&K1AKvL#F{ni+(A@w2eHNH>ZfHGqpH3 zm(8H_^_L%7*JaiH`zOmm$T8mbili=yJ8^9hirhVMrmmHj04$VxOm5 zDhr<%&Ds91_{2m`;8|452-azL2~YTdF}pl)r&KJ&BK6Ee_eeGe>Un#ZvL$@r6GOLjIi*itrlO$M=QYk-BJFG#d_ zUJG5B7Cxp%#zRD1Td;OghBz#K9#@NPf0-)NV(7<;;SQIgj433qhK+ABDxnBBZup|( zeM17HT1bPP-(!UBe^G4Tj0<&FwYj+oy4WL#`FVitSA_-g_fMbR1N%*#Q7~ z#UvyUm>gjb)=@xvZ1b`NXpmO{#Qt@#gTBaoBa^GM?~-j-i?xbqBqNk?wo*Wx6*2)w zha6Ir6pR?HZzaygPL}dh0fCYY`n&K)G7JRq-!AH9Ga7vM?NG{TV#5!{VW^RXuO4-* ztr>~uvyolgI(yfxcv0;@=+)K zCWPf0^lqPZj#UGxA}lh_U;lD-(5t*i19UZjTLh>_Pyf=PGf6-Zil0{6{(x4BK<;sR zE;|RVH#Y}2R<|#6E=#Jk{CqLGrKUHGynJ>^oTcs~YZkg}DK&X=Qc)v*N3tk0CxvEI zzn0mT>H!EcYnRS;KHV3bufWb{_R3r2gk>_KnuKfo+}AK(@yoacYLFzL?h0BSTaVE`($zgrn5 zzD;WVCsBNHeb&uM9M6Se_WCKppN;bER55JeZjcZrfw$xQZ%97XP;K!h zJyWTu5D0`$&WZ(ITYsvxivo75YbsvSj*b`L`}BE|pL%kLvOWT9$P8XKm8FT^j~yu_hO$uwZGsrprIaL%Ef1mA8j|z#j1ThDJ@mmn^h}Vru7*C`Yd#j=^RIP!P+JsJd%hwL!?`|0HST`FW#iw6szmCN1q|*%XE5~>QY(uZr z4Sg9nxV)N>xr_sBj>@t*4Wao4gCp8Mmv=n9a3x`vP6(Ec!?P6$KgV)P81IV;ht!&1 zoq8+))y%=Tu8NAkS{>TfmuXj^*wXVk?$ZC*(9odJx&W+7fto~t9vHO}11{$QFCK^h z2*v!sss*QMmYjmLr!a}HA&+JqnE$PvoedQ)gvGWO#~EZnMHkow;CuK&ec1yJV0|U6 z`j47tbfED*U(jr5WJK)Muf51#s9nLA<=DL|@uV?Dbn?fHe#S`My6a%*NPpvpNG@07 zmg_A!7dPyJY5%FcrBpC|G?oSsiB{qU`eh=@>7 zLqJ9bif5vXXSGjDv6nD8ayDbDzIAn%AVAo21Y4Sm2b$Ebq9C3z@I;yT#SVP(z%B}U z_P=yfc~$|Z9Y7@}0-j@HWU~dh3|JXq2D`}W2fWlUl8eiIzHh)xpex0krs7bn?Fd_=lM>my(!L^vZzxMSZO9P zQ&ddo+e_BX-oF+gma&(VmX4~0^j~nWe+oKfV{!IHmnH0=p72(C3`PG@{mp;?|4T9Q zuy+2pe=g}@K}r3ovH>cn&D>=Jc@WT@Wp`Zx;lWw)ReW4rB_j+wCud*D$3pPw>z8zE&Cn4l3ZhN!!onIQe$ZNxiN_3G_7~WM zofWUX3l}GQivSkai@SpEr9+nd9MFoN3D0HOiW-C~;ECyd1jcm9E5pcMW$T-pR(5cY zfU*e08W0`*sFW1oE&-yBq2$+}d|&`1lX>5=7%667YVyZQ(;)}vVs42+z1))PWem|~ zI3?Vol?Z(J?ScGL($pNoOt#IUnK{HlmSRHUb#-tSMTaqAhhtIco3Wd!$(jY4618K} zWOekg`e;Ng$$Tx@@L|BOnW;V7o#>ZwGjr(*`H7#Db-{we%rd>nRmmmIR4 zfLvw7xlavJwX2|hnyGO-num#@sUH~?rO8?dJ6 z#RAmDnVA-^dz_5%WgsjrEiD}shTKr-{z_}|8$y&;lPr+{hSw2xHR zK2jH7S|YW?XcCJ}%~MQE0wf`ykO8q5OW8=-hzyez+8rGsU#pBB%Ouy>-!BIG@#0Hd ze2*wSh$gTcWC@R{Ee#+LYeOsR1_QK#d5fCX*K?amMo+B{Ygnqvnrg{9@rhsb6#XJW zT6zG^ozkd@4&^lU*7V}h} zxpsIM=8R&$GfLE4%mBo6SV&s{)9^KiaeNFEe-y(_jW_7*1tDH<9^$ZnsBFP{Gz}n) zt|fDfTKzc22|=GRgXt+;N>WE@jfgdS{WD8#Xq+*#i>{g#fdvC@+6x*IWf zgpl6cIL3cCV{B~GY)-f^pIj;D^}RnXx~n{u%TEP+1}k3```Nc31Liaq zyznCksCP4uSNC0(87jTIfEnudL_**IfT{wD4J1sQ!bx~4FA8S>kEpO%2YH(rSWywt5Nm?5LQnjujK~grh3y}7=?H@WZeb3HKO?{`k%XSfN z2^`6RG8#Bxbd?Zf$%b5^Ry!T2f?;vw0r#pPF7><)0XriHSUzC`#?y1to}*(C$XSWo z#(n<962Yg{^7#bi^|@?(HKE2HA=4ne78;t(ik3tyNeUI!mPe*AO^z#WAG@85rCm)-$Ek@#HHZrRkQikZnnDu1*=Rwq=@D3%B zD&K|@__Bg<0t2D>PaN#z><=Hp49%<{;w+rZB3TJmhT|xHc^|Cg$`U7bsCbUjnRyYhx&=;+Pm3`r`6*^AHl? z;|C}D4ErEgn=wg9NYrwPL_$9TT*ml(IUr!F)xl8M1LqwMPgu{SpW8C^yKY(UX6pn3*GxUD|p591ENC2I%+lBw`-QoH=*zjL` zv;YAd-FBkY&+m0ZITRGQXS~i-i$5eU+ezh%fYf#@5BnKg$dJWSzs6MV8cAR)4z6OJ z2$OiPTx6V0(PMQSM$<%VFIFXhhg5C;-AVG*B|b@Ic3e(^AAXLwx|ME?zbI)6K~ge7 ztOU8TFTTG;#5#RPTu?DhK4kkfCXJA;+r+JgWYx|3t6g{&7M2$yB)5MM9UUD9hkw8o zHJjdF{y+g#Bf&AI2oV2Boh6G^`W?XawDq3q&no(Xor$b%;*#4J+Pb}rL&fqxpdJKR z8o=)v!kdDjsvCf4prS1(eJ9`~ixQTIbf44{~K@LsQ zsIUfNO0)TZ0E%jFH7F=DKS@!cF9E=Gju-g)QrW)o1bfiz(%Wy|KyY^5FBzfL-d888 zyEr%<1hEiiC&Q4HAuOlLjFJ?E?wIxyCRZAfVF;pR>4g4}%1XAvV+c!fpya!2E)+I= zdbg=iO3F-gQ}za}AiMCdY7AnyzF#(7^3weLbT`(PI#RGG1$rUdhn^VW#Y!d9yM{IA z$dk2Dc&LX;P|gKVgA#dN(iFgqU|IM1Ek6Zn{s63!@)TkN49iM+Pd(h-vu%F6hg`oP zg39x88EZg7mU&0wwxXWsd-y_*T^~*74^8xeS|kOoe6DMz8dZj{QY~P2^?n21&Gq7B zt1-6MO1sJtH(Z>9B|lloOw@su6(dQMG8tV0LcK_EQUw|Uq{E5$Zq9nh!hCP;hWi)e z{w~I4Cg*-j&TU_u{s=K&tn1>XdVJi0w3sXviXwIVw1R~ARsxbF&3ChqCT20W1SvM= zD$0~IhtXP6j9sVaGuy9F-+;q2K{&X9VV;dV*0?@-P>JL|;syZ;D5(BNVmb~1a|^t( z|FrplQ~Qa1d2uoM(i<4vnuUT>zA7ZG+kH0K*B3g(Q1viT3x zcPX~KWDIJEC`+)uI0yTpiR83OETN!TJ9;ur4QVSQ6#<2sySuTMrmIsk2`g_kBYQ3j zO)e$Vin=M{SnU8?7YZ+ZVR+(vauh)|eMkkwIEG>(Oo}q9K6z>yXAp5LjCB!MoMa`_NoV@1&jx93Jei&ZJc^XL^RP0PFM-I+g~2JY%P^4+S>0f z!*F>32^Y8q#D<5DfGBROvI5)^OjHA?@yy*UCOxPaHv?lJU9&!r_V7HDBVQcB!fIT+ za=kjl*S);|)QB&3ntu=bqUpEd?3a>^!~Cm~@ODY)y~bOFFRU?D=viwVuviXQpNd3l z7>s=*iVx>s!8oT_myShtAz|(Vp6bHH@Szz!p#KK7SAQVXZLCtavv(iY!q=7it}*p1 zT82J2pIn(NCW$8a6KVE4X*Q~#F~O9g(kv!9tt*EEM#nl&Cq4l%nHffp;U0kN3W^`p zvE~*Q^@-Wudccnx$ncLI;xobQ0>TDZ;h;DqObD+5I9>u}+|%};B#(sz%jAN30KI@s zr}}q`2{2gLfI%|2XaI+Gu&Bk^Yxp>gyyO(XP6D?Hr=7TF4`msiiV6yqU@rmOS*)}d zxYFWkAHY{=@>QVz&rl$sq5e%15YiGe;Ek7IVabl^hrEud3dZjUGnUKIR1OZo_(1lI z_w}cRb_Q-=fAHPA%X<3W-yfI@KTg(sc%RGH`-@|x;=^nKTkpMc7*hh_^UBjzZrjt_ z6kb!RaD7=83Vi&@pw}Nd-uN5)A|_dh=VJ_;Q3g1e%KQ;$fy~COZGfWo1fY=so2Po< zUaVGe_m_n8zl#*IK?`uV1D)31)58u9{@$BASr0UBBOTI%^>yDw-W`xso0Wr2VPlKw zg{SlgSYsgS=!NDLKo_E zaP={I{0=PJMzC>b2B7ZDF3Xsr%1TN^vW~z( z42&GDqd#laR8^Uug{{vjJiJs-=HOF+cYWrT^Lf4wxGe&EJ119bSE>uol4@r_nJ8KD z86`o7Yab4Qzgg}FCU(0UJp$xzFKyy+2cxQo$hrZcR(Zxh-1=u1-4Rq&E(@vLr>EVz zziaM^pLm15km+Bv*D{z*jFo$(&)NkbV`3x7xaUY}xseQ!-=;63Pe9d0)zNf*4k8RWRi z(;0S1`HAanS3$I%E*S3YadVa@JN%ET=xJ=9ML>UnSzR(Ya^nxhXd3587&Qsy}y6k?`DWXeijOV2$~%k015XuH~m&#!^7v4-u|g+BZAk^)6=WP0!-iL z2Ur5AV!$xx1(%}_s^GHNB_q`SDh4HSFa%K+KZnO>v|P4CqA3{}*7b%m9DVDjVd*dW zn=Q;zQkNTi z(;8P-Nz#`E!jMZ-xaDwUsBu=jH@%=*p#JLsxYKYW`RMBEmP)RI?cDR~PyNCnas~c#&s3mn*;n=ni{f2rSBh%VqQm`XHcG<8=$uG+1;Hh z0Tc7k{^Tf!?oxyK;*hKKV;u|s?CPJVP3IC6O^aj`M^i(Is+#Y)y`PU9k+t& zBWBL7%7igSc&KkS(La9{W@c7`{S=gvT=uS@1q<`@!R0nHF)^`gqu0~ZlYBZ~2Qs)h z&O(D*=ysTd(PVylYWtq1qWsfOWjm={yVX3HmW=lm-3XG47d`)vaFjEW6QwwS%?0i>ace^nDvE zPO#oKX|U^~iIEK0YfY_v&~&n0NEnarPKpsnuY8^f{`cKZ;1^zA3(9>O1Ohk=nqRcE zO2F)aOVT-Ls80@FO#R-1>knMw8lwSu`^GRt?%pi?R9QUiQhJh4q;{=LBvnK!QPt*Ua<_{f8}QT^Dcmk#Vwk~VpYw>SEbC_~vyfYs^vvA!FtaDTp*_g8ySY}cW!=l zTf6m~>izS)BXLR>sD@U7C{N__ys2ACj#AQ(?jjoQZK*rdi$qAiKBC`N?hUymh`PBt`t6v2+OH{BY4AfG!)m3Puiq-9Zr+_j? z9TZA~fE?m5x~@-HBcxnK&R8k1Lza*dg?+JT5uPxdO0$D%qgPJ8bzlWq#m_jpQu3OjXO@VTr zDNLEX?k~vicH5i^*o6~FL3&h18u~@=!qzct>%a!%aZP8))IO_W68D*tdGs@JZ8D$X z{JZ+90p>LnSwa%^w_7I^13liogEw23WQ?ZuEzQkikL$WSq3~Z`?_gXw5hSxOqb6wR zX=PQ^sVfFC%`Oqq$#qr>Op^y1xRGq|r{!`NSPn_-mh>}4Cu(F>pFZ`(<`t1tE)E8` zc_6>&moD_KM&^=9<~7Rh48>+a;Q0^}`CqrBcXca{amr4Tc&WnFRHF1`iN!z6t60ua zd|vwP7G%{Zr6e5xt!@L8J&r{AQ@OO$<&OL+BFx8(uA`E7MuAK?YuYp;ElP&7Qi@mc z>+j&sp;L1LZN_K2ErtlR3{Auut9|)Cmkp>}3t8Js=xB1~tff~^K-{HOsU=j5J==@_ z?V+??40aD9|C})yzFWCaui)VH*f=()^5ob*95t-7B#=pQdK?1l7W~`<->pQpERN$@ zx^r}TGWHKgqREAxPO3ytDaOzQA$B7vTTEutDs@&3YHwgCJLJp%bK6tZjd~pYqo}{l z7V}hH8}*AOJ%UCzf+ig`NuWIGz3m}m#@jU3f6L$hh;f_mAy(l~5}Mp_2{zk7wtMF& z139!q6k+CPt4+XNkPp4l+)e zUmmo5?6pw6$H!eK_V_Jo1@T+H*zGi<{I+Z!G8{UoBAGWPn-qDltvW>?s5%97f-@35 z_Q_fKkBUnpQ3R3cqzwv9?$XLUCm)hP80B9XC>vXP(; zsT$hY4~!%$cGrhObA5BmCpvP--b2H|6>`B~&?_IhQ$GKaf4ukkTpIKS%V)?T=?zb> zRf9rSqQ&w)gM(4c<3BRqk`2-&vSXQZb1It@Mk@X*`V@y0AQ>w7ec)M17htQM0wr6>tQBWYgFi6*6qmWsj>CYp|J?vBELEqzz2Cw!hC zSFUhWGa|QoCs=bVSOxUOXjIGLiL(j-*xyIy_`a1FjXI(p=G$)6?OZMJ1ZwEeICMT6498U{e~v zp6$kCZ#kW0i2ODOfwuU)WG^S`(I}F4Aa((}HhrjjzuYj=8MhS0=~!}=y0%P`Ui%l* z+ky*McS%(fU8OE3*tWp@@6|If=md_nHQuECAKDI$UdHEJMvmUY{6T@xZ8eKvRyh|x z6^FL0jB8!CRNQ?@V4s{N5<-Yfl5uX4mCKoN_E=5vu$j2Q2i2r-gC3NM{dcAHP$i7* z79k~5M%A63Er%p0tn3)uf1{GS>W2LAHV^M0r;QRurb?HnyPIefRA(5B20@OIz8&rOrJ^F*TxW z2JH}kXkpFnwDaT<>piiq!1=`;Vj!XEyJvo{=jkWu`(HMH3JbdXI(SX0-&`+uVIfDR zTyLzxin`u(w;&4G)H$3-web`G>D|rX;Ud#gP7P_h@6ptY8|2|#>UoD;p!BkDj-!Is z8^kSXh+9PBfpSNZGFpn(=ax69&KGEQ=fPhuL+pJBuh&O@!(Xe?G5+K~tk|-Tq3vv5 zPn{xfEC?P=qU1JBhGyEqpi!LKD*i_mp1)UtOy_i1OHXMjEfn`>jj%$>Y?|gnv6q3n19b%hS{md z8L7wLlTJ@CjUTJ{4ILQEuhRW?kr+vOx|_HRN&T5ynTXhNOww``)0w{G=^7cI-`wn@ zV?TQMyHBpWz9168#Szuj9sb*+6m=~I`MPydF# ztZmZ1JVO&Ecm|w{MU5b@eFKM*1{WbA)Ta*A_d-|SJ5aq1Mg@dWE6A2t{C@N6y(c5% zGFl{D@=!ORRZS09*)fP;2zz+_%R`MjUuLneocQ)orE;SLf%D|;=M03zOnr~%_JURe zMsK^5M_I?qT4RdyDa_NY|IV%#51ro&MSryXAR})a-Ej7gvGq@g^bZL4-{Bn-;2ED_ zn?3V%-yQ|-=-M9hX;)--@EZ8mH_UJ8ECB;9ckAZ}ign>;Oe#Fa8E=wDJn*j&^KcFk%HwF}CcZ=tf*(&=|B`Pz9 zD>tybEoikqgva|k*B9Yqv1;!lbcL=?gi{H0tH!)Uh;&!DT?1;3Os{>s?z^Zvk>ebS4CkEmWH=|ASjH+O zXF4iR8BGa+%L{JZl~;CXc3d3I z+*;P4CVju<)VYPlh1sN#cr%atK_2j24_=d+m#gLlX~z$Fta?r%bV-ON4_DBW7wnLdb!Z~duxm`xIgco(cbC2PTm+O)>I4J%&(#5;3W zorYu8{dCg4eFa!Jr*7qhTo;xM+XgP#X8-y0Qe({pz4udk!;6;BS83(xI?2Um+B`Eg zJSdfi&<@-fGyU&L&-nR>IJ-^P?G}^7OV`DDAgOCjeSc{_0#&-wG#QZZ?sO|J^EF-b z;h{n}FLE=+dhOj#jld>9fZglTCSW4b#2qWmwymN=?E>4bRj*HI*)eAOa7a64ep71m5U&+hlh=@(|UNN9#(D;rqsht8_X>7H4S8VAFJ=) z?>P$|+*cHv=7G?)T)nDo)qUf-$5el+72t~jdEJ;bAuW87l3aeOp=!Y?Kk3x<%7iMy z#oMZ-Qtrmrw;%m@=9yF)v$%NRu%ySfy9d_Zvsc2mm27XA)ryWc7+;2W^I?)f+h(w7 zGsL>t&RVkH^j{6TsTcg0<&tHsZ!**L6)tsgt~HxnYc^}Z8h;2VMD~|!0H02C8_?-m zESx313`ppUEPET(=fM3>qoPGo3_|fqQ`NKbWwZoQ_kbE+YaGtS=v)iu&eDbi9q>tM z_9f3UTB3i$QUCfE0kW2cxg$hgK(^1^D~AD{vXyIt(#7R;sshd}Tl5N~J-t_y;D?$fKI{FZ5yrDUhf%_PU^?;M#j9^G}0 zbdFWV*|~-lNDF*Im{1Z><9l62Bs3p1NOlBanL1^`v#xVv$+4wIy$?B{$@+T_Y6qQ| zYiu6%Q0pbFfCU|86;2+0{wI+rZ{$f_T50Ld1&P~tF8ke1;P>wlK1^Yd+ zCj9v|7!nPTc+PLq`1^QaqN^|{cJzSFDff%mNyZnD=(!oWas2!?N9f=)efB(llm#5? zfH}Xu2*jDPNWSh636>O&uN7RW3U!E-qb{W*y^mjD%(@7pKb?KC{pEIzd^+I`vsc&F zaEC1$GJtn-ff$LXmvbL$ylh$MMiRCv=iHRNzS$%osSA%QXPhe+hYzB6H zN!GGY55)Kcykh0SH;$EvVb9{(5l3(kz(=p(8yK6g8t1!M{buDsr(d+o$V**b>8t47 zHq6*wY$wvENI}Wa){q>2(>)^X=#P_>#PBn!iuvav!bCx?%>2cYz&xxgC}k*z06}GFw+Ig_DClQk%00Y=zPUhB=_RMzvsbar!zwC~xQCXJ7x^8TQ) zl$g~0e4Qf}GIChf0os(m3M$j4VyHQ}{5=WT;;3i_@AK3ZKYGC|ROXUo^FTF_R!4zq zcg?CpOStAVPEBdVcHd(}USN(fh!99MmF>dTF|109s_$1swhcwsZ)}X|J0C{`e9hZQ z9DBB-9hf|o`%T2v9zRM7$^L{lpG%)B;Ab+R8bZ;?qUbUuphEhYd&<>B(F@Kgsg3s%;{u3ZfU_7dob!}m}mt`j|yvpoHt6L5v5C!8kiU~derBkSS zK?whqtBkuV>!jkS8)P_A^`byW=QTqdWiNFuf`!BD^D>{*}dBk!hi zPpS5ByTjxVy!|!rJVo=2;lWt}3^W{TgV7XiVZ?M}!L`;${LxI7n^n$O`NtWCL8BvO z0PIWt<(1Lsiaq5Z5nLPU3ZNY!qJ-2bZ}C|D;1d+M*2_7sMO>ZPSH+z!g^U5UJ2!4< z@D?T>Kf_isyKQ(xG+HwY%CNON&#}kFJR7qr$oL!Rh=m2Qz3-;w?wZEN(hPA{UZOLk zN6?*-7b<>8zX60X^Kh!_A20(IdD|Uo=K=YbAyrwSXIwp&>o>bpX}gPh5IDID3!LRB z?ZTW_(6N)F>_Vt_6?@Vds?4R=F#kbQ@b6Q66 zm;RZkHiCFoi{3VVI)SyWF(R|o+Ft>aB(mW}MCM;G{uyPm>`P?!R2I+hP{xN~{!wiO z<$v-uMbf2I4GB4yZ&STuUlT321ee%(t8nSQUZqp0c~QLsZxxA)2o(0!&i{Okc{1Z|Kin+Eug(r*MVX@+B5dh1I>U}i5h1Objde}~ErZ5Z=b zRZeXab)Mt#W%a7mx-dc~F)FnRKj&2mvJWJ@J+>y{BD1Tt4{b;Fa?hx|C`q|4d?pk& zRetS6dh)boZGVLpzuy;!B#EEq#Pi1Sx@?*s{unccJT>uD{1yn>%YPAik!Wv8@YqTP zTeGDrdx-M~HxW_I&9Wk2R6=6tEnV0Qs5n*v$P_r1-x{||$ur3LzmmV>qq&RNsQNJt z87`uZp9wOoTI}XXj!(w+s2|onEd*2W@Q}8&tM|Xi0Zm4OB%V7rZdm56K?YQNM{W;w z@7zH9dR>J_GyLPuX4dqVdlsOS4$y3I{ZRfqV(Xgt4R{v8i1pGUK&_ZPU#hbJeYZBP wDGh;VIb+=@;9ASRTDJ$panb*uF*B_tM<>_f&1#I@x=!59+2i<=qvx*u7dr6S)&Kwi literal 0 HcmV?d00001 diff --git a/doc/index.md b/doc/index.md index 7c41d15e240d..4c9abe9b98e1 100644 --- a/doc/index.md +++ b/doc/index.md @@ -19,7 +19,7 @@ The best way to get started to learn xgboost is by the examples. There are three After you gets familiar with the interface, checkout the following additional resources * [Frequently Asked Questions](faq.md) -* [Learning what is in Behind: Introduction to Boosted Trees](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) +* [Learning what is in Behind: Introduction to Boosted Trees](model.md) * [User Guide](#user-guide) contains comprehensive list of documents of xgboost. * [Developer Guide](dev-guide/contribute.md) @@ -38,6 +38,7 @@ are great resources to learn xgboost by real examples. If you think you have som * [Understanding XGBoost Model on Otto Dataset](../demo/kaggle-otto/understandingXGBoostModel.Rmd) (R package) - This tutorial teaches you how to use xgboost to compete kaggle otto challenge. + Highlight Solutions ------------------- This section is about blogposts, presentation and videos discussing how to use xgboost to solve your interesting problem. If you think something belongs to here, send a pull request. @@ -51,7 +52,7 @@ This section is about blogposts, presentation and videos discussing how to use x User Guide ---------- * [Frequently Asked Questions](faq.md) -* [Introduction to Boosted Trees](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) +* [Introduction to the model behind XGBoost](model.md) * [Using XGBoost in Python](python/python_intro.md) * [Using XGBoost in R](../R-package/vignettes/xgboostPresentation.Rmd) * [Learning to use XGBoost by Example](../demo) @@ -61,7 +62,6 @@ User Guide * [Parameters](parameter.md) * [Notes on Parameter Tunning](param_tuning.md) - Developer Guide --------------- * [Developer Guide](dev-guide/contribute.md) @@ -69,4 +69,3 @@ Developer Guide API Reference ------------- * [Python API Reference](python/python_api.rst) - diff --git a/doc/model.md b/doc/model.md index aa42f727204c..589b8c5afc8b 100644 --- a/doc/model.md +++ b/doc/model.md @@ -1,188 +1,233 @@ -Introduction to the Model of XGBoost -========================= +Introduction to Boosted Trees +============================= +XGBoost is short for "Extreme Gradient Boosting", where the term "Gradient Boosting" is proposed in the paper _Greedy Function Approximation: A Gradient Boosting Machine_, Friedman. Based on this original model. This is a tutorial on boosted trees, most of content are based on this [slide](http://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf) by the author of xgboost. -## The Origin +The GBM(boosted trees) has been around for really a while, and there are a lot of materials on the topic. This tutorial tries to explain boosted trees in a self-contained and principled way of supervised learning. We think this explaination is cleaner, more formal, and motivates the variant used in xgboost. -XGBoost is short for "Extreme Gradient Boosting", where the term "Gradient Boosting" is proposed in the paper _Greedy Function Approximation: A Gradient Boosting Machine_, Friedman. Based on this original model, we incoporated several modifications to make it faster and more robust. +Elements of Supervised Learning +------------------------------- +XGBoost is used for supervised learning problems, where we use the training data ``$ x_i $`` to predict a target variable ``$ y_i $``. +Before we get dived into trees, let us start from reviwing the basic elements in supervised learning. -## The General Problem +### Model and Parameters +The ***model*** in supervised learning usually refers to the mathematical structure on how to given the prediction ``$ y_i $`` given ``$ x_i $``. +For example, a common model is *linear model*, where the prediction is given by ``$ \hat{y}_i = \sum_j w_j x_{ij} $``, a linear combination of weighted input features. +The prediction value can have different interpretations, depending on the task. +For example, it can be logistic transformed to get the probability of postitive class in logistic regression, it can also be used as ranking score when we want to rank the outputs. -### Supervised Model +The ***parameters*** are the undermined part that we need to learn from data. In linear regression problem, the parameters are the co-efficients ``$ w $``. +Usually we will use ``$ \Theta $`` to denote the parameters. -XGBoost is used for supervised learning problems, where we use the training data ``$ x_i $`` to predict a target variable ``$ y_i $``. Our model is a mathematical structure that captures the pattern from the training data. Given the structure, we need to learn the best parameters ``$ \Theta $`` in the model. +### Object Function : Training Loss + Regularization -### Loss Function +Based on different understanding or assumption of ``$ y_i $``, we can have different problems as regression, classification, ordering, etc. +We need to find a way to find the best parameters given the training data. In order to do so, we need to define a so called ***objective function***, +to measure the performance of the model under certain set of parameters. -Based on different understanding or assumption of ``$ y_i $``, we can have different problems as regression, classification, ordering, etc. To model different problems, we use a so-called `loss function` to describe how good is our model's performance. The function usually takes two parameters: the true value ``$ y_i $`` and the prediction ``$ \hat{y}_i $``. For example, we can use Rooted Mean Squared Error (RMSE) +A very important about objective functions, is they ***must always*** contains two parts: training loss and regularization. -`` `math -l(y_i, \hat{y}_i) = (y_i-\hat{y}_i)^2 -`` ` - -for a regression problem, and logistic loss function - -`` `math -l(y_i, \hat{y}_i) = y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i}) -`` ` - -for a classification problem. +```math +Obj(\Theta) = L(\Theta) + \Omega(\Theta) +``` -### Regularization +where ``$ L $`` is the training loss function, and ``$ \Omega $`` is the regularization term. The training loss measures how *predictive* our model is on training data. +For example, a commonly used training loss is mean squared error. -Besides we need to control the complexity of our model. A model achieving a perfect loss function score on the training dataset is overfitting it, which means it not only captures the useful pattern, but also the outliers, noise and the specific pattern in the training data. Controlling the complexity can make the model focus on more important and general pattern rather than the unnecessary details. +```math +L(\Theta) = \sum_i (y_i-\hat{y}_i)^2 +``` +Another commonly used loss function is logistic loss for logistic regression -### Optimize the Objective +```math +L(\theta) = \sum_i[ y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i})] +``` -Combining the loss function and the regularization, we have our objective for the supervised learning model as +The ***regularization term*** is usually people forget to add. The regularization term controls the complexity of the model, this helps us to avoid overfitting. +This sounds a bit abstract, let us consider the following problem in the following picture. You are asked to *fit* visually a step function given the input data points +on the upper left corner of the image, which solution among the tree you think is the best fit? -`` `math -Obj(\Theta) = L(\Theta) + \Omega(\Theta) -`` ` +![Step function](img/step_fit.png) -where ``$ L $`` is the loss function, and ``$ \Omega $`` is the regularization term. The first one is making our model being accurate, while the second one is preventing our model being overfitting. We want to have a balance between these two parts when optimizing the objective. The optimization algorithm depends on the structure of our model. The following content will introduce the details. +The answer is already marked as red. Please think if it is reasonable to you visually. The general principle is we want a ***simple*** and ***predictive*** model. +The tradeoff between the two is also referred as bias-variance tradeoff in machine learning. -## Boosting Trees Model -### Classification and Regression Tree +### Why introduce the general principle +The elements introduced in above forms the basic elements of supervised learning, and they are naturally the building blocks of machine learning toolkits. +For example, you should be able to answer what is the difference and common parts between boosted trees and random forest. +Understanding the process in a formalized way also helps us to understand the objective what we are learning and getting the reason behind the heurestics such as +pruning and smoothing. -The boosting trees model is a set of classification and regression trees. Here's a simple example of such a model: +Tree Ensemble +------------- +Now we have introduce the elements of supervised learning, let us getting started with real trees. +To begin with, let us first learn what is the ***model*** of xgboost: tree ensembles. +The tree ensemble model is a set of classification and regression trees (CART). Here's a simple example of a CART +that classifies is someone will like computer games. ![CART](img/cart.png) We classify the members in thie family into different leaves, and assign them the score on corresponding leaf. +A CART is a bit different from decision trees, where the leaf only contain decision values. In CART, a real score +is associated with each of the leaves, this allows gives us richer interpretations that go beyond classification. +This also makes the unified optimization step easier, as we will see in later part of this tutorial. -### Tree Ensemble - -However a single CART model is not so strong in practice. How about predict with more trees? +Usually, a single tree is not so strong enough to be used in practice. What is actually used is the so called +tree ensemble model, that sumes the prediction of multiple trees together. ![TwoCART](img/twocart.png) -Now we are predicting with two trees, by predict on each tree individually and then sum the scores up. Mathematically, we can write our model into the form +Here is an example of tree ensemble of two trees. The prediction scores of each individual tree are summed up to get the final score. +If you look at the example, an important fact is that the two trees tries to *complement* each other. +Mathematically, we can write our model into the form -`` `math +```math \hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in F -`` ` +``` where ``$ f $`` is a function in the functional space ``$ F $``, and ``$ F $`` is the set of all possible CARTs. Therefore our objective to optimize can be written as -`` `math +```math obj(\Theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) -`` ` +``` + +Now here comes the question, what is the *model* of random forest? It is exactly tree ensembles! So random forest and boosted trees are not different in terms of model, +the difference is how we train them. This means if you write a predictive service of tree ensembles, you only need to write one of them and they should directly work +for both random forest and boosted trees. One example of elements of supervised learning rocks. + +Tree Boosting +------------- +After introducing the model, let us begin with the real training part. How should we learn the trees? +The answer is, as is always for all supervised learning models: *define an objective function, and optimize it*! + +Assume we have the following objective function (remember it always need to contain training loss, and regularization) +```math +Obj = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ +``` ### Additive Training -It is not easy to train all the trees at once. Instead, we use the strategy to train them in a sequence so that everytime we train one CART and add it to the model. We note the prediction value at step `t` by ``$ \hat{y}_i^{(t)}$``, so we have +First thing we want to ask is what are ***parameters*** of trees. You can find what we need to learn are those functions ``$f_i$``, with each contains the structure +of the tree, and the leaf score. This is much harder than traditional optimization problem where you can take the gradient and go. +It is not easy to train all the trees at once. +Instead, we use an additive strategy: fix what we have learned, add a new tree at a time. +We note the prediction value at step `t` by ``$ \hat{y}_i^{(t)}$``, so we have -`` `math +```math \hat{y}_i^{(0)} = 0\\ \hat{y}_i^{(1)} = f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ \hat{y}_i^{(2)} = f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ \dots\\ \hat{y}_i^{(t)} = \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) -`` ` +``` -Which CART do we want at each step? Of course we want to add the one that minimize our objective. +It remains to ask Which tree do we want at each step? A natural thing is to add the one that optimizes our objective. -`` `math +```math Obj^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + constant -`` ` +``` -Let's consider using RMSE as our loss function +If we consider using MSE as our loss function, it becomes the following form. -`` `math +```math Obj^{(t)} & = \sum_{i=1}^n (y_i - (\hat{y}_i^{(t-1)} + f_t(x_i)))^2 + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n [2(\hat{y}_i^{(t-1)} - y_i)f_t(x_i) + f_t(x_i)^2] + \Omega(f_t) + constant -`` ` +``` -The form of RMSE is friendly. But other loss functions could be tricky to expand. For convenience we calculate the Taylor expansion of the loss function up to the second order +The form of MSE is friendly, with a first order term (usually called residual) and a quadratic term. +For other loss of interest (for example, logistic loss), it is not so easy to get such a nice form. +So in general case, we take the Taylor expansion of the loss function up to the second order -`` `math +```math Obj^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + constant -`` ` +``` +where the ``$g_i$`` and ``$h_i$`` are defined as -where - -`` `math +```math g_i &= \partial_{\hat{y}_i^{(t)}} l(y_i, \hat{y}_i^{(t-1)})\\ h_i &= \partial_{\hat{y}_i^{(t)}}^2 l(y_i, \hat{y}_i^{(t-1)}) -`` ` - -So we can remove all the constant at the t-th step and the specific objective is +``` -`` `math +After we remove all the constants, the specific objective at t step becomes +```math \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) -`` ` +``` -One of the benifit of this definition is as long as the loss function has the first and second order derivative, we can optimized every loss function within the same framework. +This becomes our optimization goal for the new tree. One important advantage of this definition, is that +it only depends on ``$g_i$`` and ``$h_i$``, this is how xgboost allows support of customization of loss functions. +We can optimized every loss function, including logistic regression, weighted logistic regression, using the exactly +the same solver that takes ``$g_i$`` and ``$h_i$`` as input! ### Model Complexity +We have introduced the training step, but wait, there is one important thing, the ***regularization***! +We need to define the complexity of the tree ``$\Omega(f)$``. In order to do so, let us first refine the definition of the tree a tree ``$ f(x) $`` as -We have introduced the details in the loss function, next we talk about the regularization term. We want to control the complexity of a tree, thus we need to define it first. We define a tree ``$ f(x) $`` as +```math +f_t(x) = w_{q(x)}, w \in R^T, q:R^d\rightarrow \{1,2,\cdots,T\} . +``` -`` `math -f_t(x) = w_{q(x)}, w\inR^T, q:R^d\rightarrow \{1,2,\cdots,T\} -`` ` +Here ``$ w $`` is the vector of scores on leaves, ``$ q $`` is a function assigning each data point to the corresponding leaf and``$ T $`` is the number of leaves. +In XGBoost, we define the complexity as -where ``$ w $`` is the vector of scores on leaves, ``$ q $`` is a function assigning each data point to the corresponding leaf and ``$ T $`` is the number of leaves. In XGBoost, we define the complexity as - -`` `math +```math \Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 -`` ` +``` +Of course there is more than one way to define the complexity, but this specific one works well in practice. The regularization is one part most tree packages takes +less carefully, or simply ignore. This was due to the traditional treatment tree learning only emphasize improving impurity, while the complexity control part +are more lies as part of heuristics. By defining it formally, we can get a better idea of what we are learning, and yes it works well in practice. -It is possible to define other form of regularization terms, but this one works well in practice. -### Get the best score on leaf +### The Structure Score -Now we have the objective value with the ``$ t $``-th tree added: +Here is the magical part of the derivation. After reformalizing the tree model, we can write the objective value with the ``$ t $``-th tree as: -`` `math +```math Obj^{(t)} &\approx \sum_{i=1}^n [g_i w_q(x_i) + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T -`` ` +``` where ``$ I_j = \{i|q(x_i)=j\} $`` is the set of indices of data points assigned to the ``$ j $``-th leaf. Notice that in the second line we have change the index of the summation because all the data points on the same leaf get the same score. We could further compress the expression by defining ``$ G_j = \sum_{i\in I_j} g_i $`` and ``$ H_j = \sum_{i\in I_j} h_i $``: -`` `math +```math Obj^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T -`` ` +``` -In this equation ``$ w_j $`` are independent to each other, the form ``$ G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2 $`` is quadratic and the best ``$ w_j $`` to minimize it can be solved deterministically: +In this equation ``$ w_j $`` are independent to each other, the form ``$ G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2 $`` is quadratic and the best ``$ w_j $`` for a given structure ``$q(x)$`` and the best objective reduction we can get: -`` `math +```math w_j^\ast = -\frac{G_j}{H_j+\lambda}\\ -Obj = -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T -`` ` - -**Therefore, given the parameters, the gradients and the structure of the tree, we know how to set the score on each leaf.** +Obj^\ast = -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T +``` +The last equation measures ***how good*** a tree structure ``$q(x)$`` is. -### Learn the tree structure - -Our algorithm aims at optimizing the objective, so it also guides us to a good tree structure. We score the structure by ``$ Obj^{(t)} $`` which is mentioned just above. Since we can evaluate the tree, ideally we can enumerate all possible trees and pick the best one. In practice it is impossible, so we enumerate all the trees no deeper than a certain depth greedily. +![Structure Score](img/struct_score.png) -Specifically we try to split a leaf into two leaves, and the score it gains is +If all these sounds a bit complicated. Let us take a look the the picture, and see how the scores can be calculated. +Basically, for a given tree structure, we push the statistics ``$g_i$`` and ``$h_i$`` to the leaves they belong to, +sum the statistics together, and use the formula to calulate how good the tree is. +This score is like impurity measure in decision tree, except that it also takes the model complexity into account. -`` `math -Gain = \frac{1}{2} [\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}] - \gamma -`` ` +### Learn the tree structure +Now we have a way to measure how good a tree is ideally we can enumerate all possible trees and pick the best one. +In practice it is impossible, so we will try to one level of the tree at a time. +Specifically we try to split a leaf into two leaves, and the score it gains is +```math +Gain = \frac{1}{2} \left[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma +``` This formula can be decomposited as 1) the score on the new left leaf 2) the score on the new right leaf 3) The score on the original leaf 4) regularization on the additional leaf. - -The regularization in the end can be seen as the minimum increment from this split. In the end, we will prune out the split with a negative gain. - - - - - - - - - - - - - - - - - +We can find an important fact here: if the gain is smaller than ``$gamma$``, we would better not to add that branch. This is exactly the ***prunning*** techniques in tree based +models! By using the principles of supervised learning, we can naturally comes up with the reason these techniques :) + +For real valued data, we usually want to search for an optimal split. To efficiently doing so, we place all the instances in a sorted way, like the following picture. +![Best split](img/split_find.png) +Then a left to right scan is sufficient to calculate the structure score of all possible split solutions, and we can find the best split efficiently. + +Final words on XGBoost +---------------------- +Now you have understand what is a boosted tree, you may ask, where is the introduction on [XGBoost](https://github.com/dmlc/xgboost)? +XGBoost is exactly a tool motivated by the formal principle introduced in this tutorial! +More importantly, it is developed with both deep consideration in terms of ***systems optimization*** and ***principles in machine learning***. +The goal of this library is to push the extreme of the computation limits of machines to provide a ***scalable***, ***portable*** and ***accurate*** library. +Make sure you [try it out](https://github.com/dmlc/xgboost), and most importantly, contribute your piece of wisdom (code, examples, tutorials) to the community! diff --git a/doc/python-requirements.txt b/doc/python-requirements.txt deleted file mode 100644 index 1a041d154156..000000000000 --- a/doc/python-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -commonmark - diff --git a/doc/sphinx_util.py b/doc/sphinx_util.py index 0b51786301f6..a09f1e08baa5 100644 --- a/doc/sphinx_util.py +++ b/doc/sphinx_util.py @@ -6,12 +6,12 @@ import subprocess if os.environ.get('READTHEDOCS', None) == 'True': - subprocess.call('cd ..; rm -rf recommonmark recom;' + - 'git clone https://github.com/tqchen/recommonmark;' + - 'mv recommonmark/recommonmark recom', shell=True) + subprocess.call('cd ..; rm -rf recommonmark;' + + 'git clone https://github.com/tqchen/recommonmark', shell=True) -sys.path.insert(0, os.path.abspath('..')) -from recom import parser, transform +sys.path.insert(0, os.path.abspath('../recommonmark/')) + +from recommonmark import parser, transform MarkdownParser = parser.CommonMarkParser AutoStructify = transform.AutoStructify From 6bcf35f2e19bccd44da04a17e37b75025cec1c0b Mon Sep 17 00:00:00 2001 From: tqchen Date: Sun, 23 Aug 2015 22:06:38 -0700 Subject: [PATCH 124/126] minor --- doc/faq.md | 4 ++++ doc/index.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/faq.md b/doc/faq.md index 63f949fad3c2..32dc5a1b33f8 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -6,6 +6,10 @@ How to tune parameters ---------------------- See [Parameter Tunning Guide](param_tuning.md) +Description on the model +------------------------ +See [Introduction to Boosted Trees](model.md) + I have a big dataset -------------------- diff --git a/doc/index.md b/doc/index.md index 4c9abe9b98e1..40b7c519252f 100644 --- a/doc/index.md +++ b/doc/index.md @@ -52,7 +52,7 @@ This section is about blogposts, presentation and videos discussing how to use x User Guide ---------- * [Frequently Asked Questions](faq.md) -* [Introduction to the model behind XGBoost](model.md) +* [Introduction to Boosted Trees](model.md) * [Using XGBoost in Python](python/python_intro.md) * [Using XGBoost in R](../R-package/vignettes/xgboostPresentation.Rmd) * [Learning to use XGBoost by Example](../demo) From f305cdbf758fc5cde0ddc47320d00113c2e298bc Mon Sep 17 00:00:00 2001 From: Tong He Date: Sun, 23 Aug 2015 22:31:00 -0700 Subject: [PATCH 125/126] align formula --- doc/model.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/model.md b/doc/model.md index 6c39fd345742..973ec8df5a86 100644 --- a/doc/model.md +++ b/doc/model.md @@ -113,11 +113,11 @@ Instead, we use an additive strategy: fix what we have learned, add a new tree a We note the prediction value at step `t` by ``$ \hat{y}_i^{(t)}$``, so we have ```math -\hat{y}_i^{(0)} = 0\\ -\hat{y}_i^{(1)} = f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ -\hat{y}_i^{(2)} = f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ -\dots\\ -\hat{y}_i^{(t)} = \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) +\hat{y}_i^{(0)} &= 0\\ +\hat{y}_i^{(1)} &= f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ +\hat{y}_i^{(2)} &= f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ +&\dots\\ +\hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) ``` It remains to ask Which tree do we want at each step? A natural thing is to add the one that optimizes our objective. From c4fa2f6110b93b8eb78eb42acc201029fa395c1b Mon Sep 17 00:00:00 2001 From: Tianqi Chen Date: Sun, 23 Aug 2015 22:46:50 -0700 Subject: [PATCH 126/126] Update model.md --- doc/model.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/model.md b/doc/model.md index 973ec8df5a86..f4373b3fc537 100644 --- a/doc/model.md +++ b/doc/model.md @@ -82,10 +82,10 @@ If you look at the example, an important fact is that the two trees tries to *co Mathematically, we can write our model into the form ```math -\hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in F +\hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in \mathcal{F} ``` -where ``$ K $`` is the number of trees, ``$ f $`` is a function in the functional space ``$ F $``, and ``$ F $`` is the set of all possible CARTs. Therefore our objective to optimize can be written as +where ``$ K $`` is the number of trees, ``$ f $`` is a function in the functional space ``$ \mathcal{F} $``, and ``$ \mathcal{F} $`` is the set of all possible CARTs. Therefore our objective to optimize can be written as ```math obj(\Theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) @@ -110,7 +110,7 @@ First thing we want to ask is what are ***parameters*** of trees. You can find w of the tree, and the leaf score. This is much harder than traditional optimization problem where you can take the gradient and go. It is not easy to train all the trees at once. Instead, we use an additive strategy: fix what we have learned, add a new tree at a time. -We note the prediction value at step `t` by ``$ \hat{y}_i^{(t)}$``, so we have +We note the prediction value at step ``$t$`` by ``$ \hat{y}_i^{(t)}$``, so we have ```math \hat{y}_i^{(0)} &= 0\\ @@ -179,7 +179,7 @@ are more lies as part of heuristics. By defining it formally, we can get a bette ### The Structure Score -Here is the magical part of the derivation. After reformalizing the tree model, we can write the objective value with the ``$ t $``-th tree as: +Here is the magical part of the derivation. After reformalizing the tree model, we can write the objective value with the ``$ t$``-th tree as: ```math Obj^{(t)} &\approx \sum_{i=1}^n [g_i w_q(x_i) + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ @@ -216,7 +216,7 @@ Specifically we try to split a leaf into two leaves, and the score it gains is Gain = \frac{1}{2} \left[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma ``` This formula can be decomposited as 1) the score on the new left leaf 2) the score on the new right leaf 3) The score on the original leaf 4) regularization on the additional leaf. -We can find an important fact here: if the gain is smaller than ``$gamma$``, we would better not to add that branch. This is exactly the ***prunning*** techniques in tree based +We can find an important fact here: if the gain is smaller than ``$\gamma$``, we would better not to add that branch. This is exactly the ***prunning*** techniques in tree based models! By using the principles of supervised learning, we can naturally comes up with the reason these techniques :) For real valued data, we usually want to search for an optimal split. To efficiently doing so, we place all the instances in a sorted way, like the following picture.