forked from huawei/openGauss-server
856 lines
32 KiB
C++
856 lines
32 KiB
C++
/*
|
|
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
*
|
|
* openGauss is licensed under Mulan PSL v2.
|
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
* You may obtain a copy of Mulan PSL v2 at:
|
|
*
|
|
* http://license.coscl.org.cn/MulanPSL2
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
* See the Mulan PSL v2 for more details.
|
|
*---------------------------------------------------------------------------------------
|
|
*
|
|
* xgboost.cpp
|
|
* Main file implemented for the xgboost algorithm
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/dbmind/db4ai/executor/xgboost/xgboost.cpp
|
|
*
|
|
* ---------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "nodes/execnodes.h"
|
|
#include "nodes/pg_list.h"
|
|
#include "postgres_ext.h"
|
|
#include "utils/builtins.h"
|
|
#include "funcapi.h"
|
|
#include <dlfcn.h>
|
|
|
|
#include "db4ai/xgboost.h"
|
|
#include "db4ai/aifuncs.h"
|
|
#include "db4ai/model_warehouse.h"
|
|
#include "db4ai/predict_by.h"
|
|
#include "db4ai/db4ai_common.h"
|
|
|
|
#include "xgboost/c_api.h"
|
|
|
|
double total_exec_time = 0.0;
|
|
struct timespec exec_start_time, exec_end_time;
|
|
|
|
#define XGBOOST_LIB_NAME "libxgboost.so"
|
|
|
|
typedef const int (*XGBoosterSetParam_Sym)(BoosterHandle handle, const char *name, const char *value);
|
|
typedef const int (*XGDMatrixCreateFromMat_Sym)(const float *data, bst_ulong nrow, bst_ulong ncol,
|
|
float missing, DMatrixHandle *out);
|
|
typedef const int (*XGDMatrixSetFloatInfo_Sym)(DMatrixHandle handle, const char *field, const float *array, bst_ulong len);
|
|
typedef const int (*XGBoosterCreate_Sym)(const DMatrixHandle dmats[], bst_ulong len, BoosterHandle *out);
|
|
typedef const int (*XGBoosterUnserializeFromBuffer_Sym)(BoosterHandle handle, const void *buf, bst_ulong len);
|
|
typedef const int (*XGBoosterUpdateOneIter_Sym)(BoosterHandle handle, int iter, DMatrixHandle dtrain);
|
|
typedef const int (*XGBoosterEvalOneIter_Sym)(BoosterHandle handle, int iter, DMatrixHandle dmats[],
|
|
const char *evnames[], bst_ulong len, const char **out_result);
|
|
typedef const int (*XGBoosterSerializeToBuffer_Sym)(BoosterHandle handle, bst_ulong *out_len, const char **out_dptr);
|
|
typedef const int (*XGDMatrixFree_Sym)(DMatrixHandle handle);
|
|
typedef const int (*XGBoosterFree_Sym)(BoosterHandle handle);
|
|
typedef const int (*XGBoosterPredict_Sym)(BoosterHandle handle, DMatrixHandle dmat, int option_mask, unsigned ntree_limit,
|
|
int training, bst_ulong *out_len, const float **out_result);
|
|
typedef const char* (*XGBGetLastError_Sym)(void);
|
|
|
|
typedef struct {
|
|
XGBoosterSetParam_Sym XGBoosterSetParam;
|
|
XGDMatrixCreateFromMat_Sym XGDMatrixCreateFromMat;
|
|
XGDMatrixSetFloatInfo_Sym XGDMatrixSetFloatInfo;
|
|
XGBoosterCreate_Sym XGBoosterCreate;
|
|
XGBoosterUnserializeFromBuffer_Sym XGBoosterUnserializeFromBuffer;
|
|
XGBoosterUpdateOneIter_Sym XGBoosterUpdateOneIter;
|
|
XGBoosterEvalOneIter_Sym XGBoosterEvalOneIter;
|
|
XGBoosterSerializeToBuffer_Sym XGBoosterSerializeToBuffer;
|
|
XGDMatrixFree_Sym XGDMatrixFree;
|
|
XGBoosterFree_Sym XGBoosterFree;
|
|
XGBoosterPredict_Sym XGBoosterPredict;
|
|
XGBGetLastError_Sym XGBGetLastError;
|
|
} xgboostApi; //xgboost API function
|
|
|
|
static void *g_xgboost_handle = NULL; //dynamic library handler
|
|
static xgboostApi *g_xgboostApi = NULL; // function symbols of xgboost library
|
|
static MemoryContext g_xgboostMcxt = NULL;
|
|
|
|
#define safe_xgboost(call) { \
|
|
int err = (call); \
|
|
if (err != 0) { \
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
|
|
errmsg("%s:%d: error in %s: %s\n", __FILE__, __LINE__, #call, g_xgboostApi->XGBGetLastError()))); \
|
|
} \
|
|
}
|
|
|
|
double parseDoubleFromErrMetric(const char *str)
|
|
{
|
|
// skip all the way to `:'
|
|
while (*str != ':')
|
|
++str;
|
|
// skip `:' and parse the number
|
|
return strtod(++str, NULL);
|
|
}
|
|
|
|
struct xg_data_t {
|
|
float* labels{nullptr};
|
|
float* features{nullptr};
|
|
char* raw_model{nullptr};
|
|
int ft_rows{0};
|
|
int ft_cols{0};
|
|
int lb_rows{0};
|
|
double validation_score{0};
|
|
uint64_t raw_model_size{0};
|
|
|
|
inline void set_raw_model(char *raw_model_, uint64_t len_)
|
|
{
|
|
if (raw_model_ == 0 || len_ <= 0)
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
|
|
errmsg("Xgboost failed loading raw model.")));
|
|
|
|
/* check if we need to release the old buffer */
|
|
if (raw_model)
|
|
pfree((void *)raw_model);
|
|
|
|
raw_model_size = len_;
|
|
raw_model = (char *)palloc0(sizeof(char) * len_);
|
|
errno_t rc = memcpy_s(raw_model, len_, raw_model_, len_);
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
};
|
|
|
|
typedef struct XgboostModelV01 {
|
|
int ft_cols;
|
|
} XgboostModelV01;
|
|
|
|
/*
|
|
* Auxiliary method for printing tuples, useful for debugging
|
|
*/
|
|
void printXGData(const xg_data_t &chunk, const int n_tuples)
|
|
{
|
|
StringInfoData buf;
|
|
initStringInfo(&buf);
|
|
|
|
for (int i = 0; i < n_tuples; ++i) {
|
|
appendStringInfo(&buf, "%4.2f\t", chunk.labels[i]);
|
|
for (int j = 0; j < chunk.ft_cols; ++j) {
|
|
appendStringInfo(&buf, "%4.2f", *(chunk.features + i * chunk.ft_cols + j));
|
|
appendStringInfoChar(&buf, '\t');
|
|
}
|
|
appendStringInfoChar(&buf, '\n');
|
|
elog(NOTICE, "%s", buf.data);
|
|
resetStringInfo(&buf);
|
|
}
|
|
pfree(buf.data);
|
|
}
|
|
|
|
typedef struct HyperparamsXGBoost {
|
|
ModelHyperparameters mhp; /* place-holder */
|
|
/* hyperparameters */
|
|
uint32_t n_iterations;
|
|
uint32_t batch_size;
|
|
uint32_t max_depth;
|
|
uint32_t min_child_weight;
|
|
uint32_t nthread;
|
|
uint32_t seed;
|
|
uint32_t verbosity;
|
|
double eta;
|
|
double gamma;
|
|
const char* booster{nullptr};
|
|
const char* tree_method{nullptr};
|
|
const char* eval_metric{nullptr};
|
|
} HyperparamsXGBoost;
|
|
|
|
/*
|
|
* XGBoost state
|
|
*/
|
|
typedef struct XGBoostState {
|
|
TrainModelState tms;
|
|
// tuple description
|
|
Oid *oids;
|
|
bool done = false;
|
|
uint32_t processed_tuples = 0U;
|
|
double execution_time = 0.;
|
|
} XGBoostState;
|
|
|
|
typedef struct SerializedModelXgboost {
|
|
int ft_cols;
|
|
BoosterHandle booster = nullptr;
|
|
}SerializedModelXgboost;
|
|
|
|
extern XGBoost xg_reg_logistic;
|
|
extern XGBoost xg_bin_logistic;
|
|
extern XGBoost xg_reg_sqe;
|
|
extern XGBoost xg_reg_gamma;
|
|
|
|
XGBoost *xgboost_get_algorithm(AlgorithmML algorithm)
|
|
{
|
|
XGBoost *xgboost_algorithm = nullptr;
|
|
switch (algorithm) {
|
|
case XG_REG_LOGISTIC:
|
|
xgboost_algorithm = &xg_reg_logistic;
|
|
break;
|
|
case XG_BIN_LOGISTIC:
|
|
xgboost_algorithm = &xg_bin_logistic;
|
|
break;
|
|
case XG_REG_SQE:
|
|
xgboost_algorithm = &xg_reg_sqe;
|
|
break;
|
|
case XG_REG_GAMMA:
|
|
xgboost_algorithm = &xg_reg_gamma;
|
|
break;
|
|
default:
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Invalid algorithm %d", algorithm)));
|
|
break;
|
|
}
|
|
return xgboost_algorithm;
|
|
}
|
|
|
|
// Hyperparameter and algorithm definitions
|
|
MetricML *xgboost_metrics_accuracy(AlgorithmAPI *self, int *num_metrics)
|
|
{
|
|
Assert(num_metrics != nullptr);
|
|
static MetricML metrics[] = {
|
|
METRIC_ML_AUC, METRIC_ML_AUC_PR, METRIC_ML_MAP, METRIC_ML_RMSE, METRIC_ML_RMSLE, METRIC_ML_MAE
|
|
};
|
|
*num_metrics = sizeof(metrics) / sizeof(MetricML);
|
|
return metrics;
|
|
}
|
|
|
|
#define BOOST_GBLINEAR_IDX 1
|
|
const char *xgboost_boost_str[] = {"gbtree", "gblinear", "dart"};
|
|
const char *xgboost_tree_method_str[] = {"auto", "exact", "approx", "hist", "gpu_hist"};
|
|
const char *xgboost_eval_metric_str[] = {"rmse", "rmsle", "map", "mae", "auc", "aucpr" };
|
|
static HyperparameterDefinition xgboost_hyperparameter_definitions[] = {
|
|
HYPERPARAMETER_INT4("n_iter", 10, 1, true, ITER_MAX, true, HyperparamsXGBoost, n_iterations, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_INT4("batch_size", 10000, 1, true, MAX_BATCH_SIZE, true, HyperparamsXGBoost, batch_size,
|
|
HP_NO_AUTOML()),
|
|
HYPERPARAMETER_INT4("max_depth", 5, 0, true, INT32_MAX, true, HyperparamsXGBoost, max_depth, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_INT4("min_child_weight", 1, 0, true, INT32_MAX, true, HyperparamsXGBoost, min_child_weight,
|
|
HP_NO_AUTOML()),
|
|
HYPERPARAMETER_FLOAT8("gamma", 0.0, 0.0, true, DBL_MAX, true, HyperparamsXGBoost, gamma, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_FLOAT8("eta", 0.3, 0.0, true, 1, true, HyperparamsXGBoost, eta, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_INT4("nthread", 1, 0, true, 100, true, HyperparamsXGBoost, nthread, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_INT4("verbosity", 1, 0, true, 3, true, HyperparamsXGBoost, verbosity, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_INT4("seed", 0, 0, true, INT32_MAX, true, HyperparamsXGBoost, seed,
|
|
HP_AUTOML_INT(1, INT32_MAX, 1, ProbabilityDistribution::UNIFORM_RANGE)),
|
|
HYPERPARAMETER_STRING("booster", "gbtree", xgboost_boost_str, ARRAY_LENGTH(xgboost_boost_str), HyperparamsXGBoost,
|
|
booster, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_STRING("tree_method", "auto", xgboost_tree_method_str, ARRAY_LENGTH(xgboost_tree_method_str),
|
|
HyperparamsXGBoost, tree_method, HP_NO_AUTOML()),
|
|
HYPERPARAMETER_STRING("eval_metric", "rmse", xgboost_eval_metric_str, ARRAY_LENGTH(xgboost_eval_metric_str),
|
|
HyperparamsXGBoost, eval_metric, HP_NO_AUTOML()),
|
|
};
|
|
|
|
static const HyperparameterDefinition* xgboost_get_hyperparameters(AlgorithmAPI *self, int *definitions_size)
|
|
{
|
|
Assert(definitions_size != nullptr);
|
|
*definitions_size = sizeof(xgboost_hyperparameter_definitions) / sizeof(HyperparameterDefinition);
|
|
return xgboost_hyperparameter_definitions;
|
|
}
|
|
|
|
static ModelHyperparameters *xgboost_make_hyperparameters(AlgorithmAPI *self)
|
|
{
|
|
auto xgboost_hyperp = reinterpret_cast<HyperparamsXGBoost *>(palloc0(sizeof(HyperparamsXGBoost)));
|
|
|
|
return &xgboost_hyperp->mhp;
|
|
}
|
|
|
|
void load_xgboost_library(void)
|
|
{
|
|
if (g_xgboost_handle != NULL) {
|
|
return; // have load it
|
|
}
|
|
|
|
// get the library path from GAUSSHOME/lib directory
|
|
char* gausshome = getGaussHome();
|
|
StringInfo libPath = makeStringInfo();
|
|
appendStringInfo(libPath, "%s/lib/%s", gausshome, XGBOOST_LIB_NAME);
|
|
|
|
(void)LWLockAcquire(XGBoostLibLock, LW_EXCLUSIVE);
|
|
if (g_xgboost_handle != NULL) { //check again to avoid double dlopen
|
|
LWLockRelease(XGBoostLibLock);
|
|
return; // have load it
|
|
}
|
|
|
|
g_xgboost_handle = dlopen(libPath->data, RTLD_NOW | RTLD_GLOBAL);
|
|
if (g_xgboost_handle == NULL) {
|
|
LWLockRelease(XGBoostLibLock);
|
|
ereport(ERROR, (errcode(ERRCODE_FILE_READ_FAILED),
|
|
errmsg("Call dlopen to load library file %s failed. error: %s", libPath->data, dlerror())));
|
|
}
|
|
|
|
g_xgboostMcxt = AllocSetContextCreate(g_instance.instance_context,
|
|
"xgboostApiMemoryContext",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE,
|
|
SHARED_CONTEXT);
|
|
g_xgboostApi = (xgboostApi *)MemoryContextAlloc(g_xgboostMcxt, sizeof(xgboostApi));
|
|
|
|
g_xgboostApi->XGBoosterSetParam = (XGBoosterSetParam_Sym)dlsym(g_xgboost_handle, "XGBoosterSetParam");
|
|
g_xgboostApi->XGDMatrixCreateFromMat = (XGDMatrixCreateFromMat_Sym)dlsym(g_xgboost_handle, "XGDMatrixCreateFromMat");
|
|
g_xgboostApi->XGDMatrixSetFloatInfo = (XGDMatrixSetFloatInfo_Sym)dlsym(g_xgboost_handle, "XGDMatrixSetFloatInfo");
|
|
g_xgboostApi->XGBoosterCreate = (XGBoosterCreate_Sym)dlsym(g_xgboost_handle, "XGBoosterCreate");
|
|
g_xgboostApi->XGBoosterUnserializeFromBuffer = (XGBoosterUnserializeFromBuffer_Sym)dlsym(g_xgboost_handle, "XGBoosterUnserializeFromBuffer");
|
|
g_xgboostApi->XGBoosterUpdateOneIter = (XGBoosterUpdateOneIter_Sym)dlsym(g_xgboost_handle, "XGBoosterUpdateOneIter");
|
|
g_xgboostApi->XGBoosterEvalOneIter = (XGBoosterEvalOneIter_Sym)dlsym(g_xgboost_handle, "XGBoosterEvalOneIter");
|
|
g_xgboostApi->XGBoosterSerializeToBuffer = (XGBoosterSerializeToBuffer_Sym)dlsym(g_xgboost_handle, "XGBoosterSerializeToBuffer");
|
|
g_xgboostApi->XGDMatrixFree = (XGDMatrixFree_Sym)dlsym(g_xgboost_handle, "XGDMatrixFree");
|
|
g_xgboostApi->XGBoosterFree = (XGBoosterFree_Sym)dlsym(g_xgboost_handle, "XGBoosterFree");
|
|
g_xgboostApi->XGBoosterPredict = (XGBoosterPredict_Sym)dlsym(g_xgboost_handle, "XGBoosterPredict");
|
|
g_xgboostApi->XGBGetLastError = (XGBGetLastError_Sym)dlsym(g_xgboost_handle, "XGBGetLastError");
|
|
|
|
if (g_xgboostApi->XGBoosterSetParam == NULL || g_xgboostApi->XGDMatrixCreateFromMat == NULL ||
|
|
g_xgboostApi->XGDMatrixSetFloatInfo == NULL || g_xgboostApi->XGBoosterCreate == NULL ||
|
|
g_xgboostApi->XGBoosterUnserializeFromBuffer == NULL || g_xgboostApi->XGBoosterUpdateOneIter == NULL ||
|
|
g_xgboostApi->XGBoosterEvalOneIter == NULL || g_xgboostApi->XGBoosterSerializeToBuffer == NULL ||
|
|
g_xgboostApi->XGDMatrixFree == NULL || g_xgboostApi->XGBoosterFree == NULL ||
|
|
g_xgboostApi->XGBoosterPredict == NULL || g_xgboostApi->XGBGetLastError == NULL) {
|
|
(void)dlclose(g_xgboost_handle);
|
|
g_xgboost_handle = NULL;
|
|
LWLockRelease(XGBoostLibLock);
|
|
ereport(ERROR, (errcode(ERRCODE_FILE_READ_FAILED),
|
|
errmsg("Call dlsym to the symbol of load library file %s failed. error: %s", libPath->data, dlerror())));
|
|
}
|
|
|
|
LWLockRelease(XGBoostLibLock);
|
|
|
|
/* clear any existing error */
|
|
(void)dlerror();
|
|
|
|
pfree(libPath->data);
|
|
}
|
|
|
|
/*
|
|
* This method is reposnsible for setting hyperparams for the XGBoost algorithm.
|
|
* For hyperparams which are not set, XGBoost takes default values.
|
|
*/
|
|
void set_hyperparams(AlgorithmAPI *alg, const HyperparamsXGBoost *xg_hyp, void *booster)
|
|
{
|
|
StringInfoData buf;
|
|
initStringInfo(&buf);
|
|
|
|
switch (alg->algorithm) {
|
|
case XG_BIN_LOGISTIC:
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "objective", "binary:logistic"));
|
|
break;
|
|
case XG_REG_LOGISTIC:
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "objective", "reg:logistic"));
|
|
break;
|
|
case XG_REG_SQE:
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "objective", "reg:squarederror"));
|
|
break;
|
|
case XG_REG_GAMMA:
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "objective", "reg:gamma"));
|
|
break;
|
|
default:
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Unknown XG Algorithm %d!", alg->algorithm)));
|
|
break;
|
|
}
|
|
|
|
if (xg_hyp->booster) {
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "booster", xg_hyp->booster));
|
|
}
|
|
if (xg_hyp->tree_method) {
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "tree_method", xg_hyp->tree_method));
|
|
}
|
|
if (strcmp(xg_hyp->eval_metric, "") != 0) {
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "eval_metric", xg_hyp->eval_metric));
|
|
}
|
|
if (xg_hyp->seed != 0) {
|
|
appendStringInfo(&buf, "%d", xg_hyp->seed);
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "seed", buf.data));
|
|
resetStringInfo(&buf);
|
|
}
|
|
if (xg_hyp->verbosity != 1) {
|
|
appendStringInfo(&buf, "%d", xg_hyp->verbosity);
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "verbosity", buf.data));
|
|
resetStringInfo(&buf);
|
|
}
|
|
|
|
appendStringInfo(&buf, "%d", xg_hyp->nthread);
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "nthread", buf.data));
|
|
resetStringInfo(&buf);
|
|
|
|
appendStringInfo(&buf, "%d", xg_hyp->max_depth);
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "max_depth", buf.data));
|
|
resetStringInfo(&buf);
|
|
|
|
appendStringInfo(&buf, "%f", xg_hyp->gamma);
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "gamma", buf.data));
|
|
resetStringInfo(&buf);
|
|
|
|
appendStringInfo(&buf, "%f", xg_hyp->eta);
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "eta", buf.data));
|
|
resetStringInfo(&buf);
|
|
|
|
appendStringInfo(&buf, "%d", xg_hyp->min_child_weight);
|
|
safe_xgboost(g_xgboostApi->XGBoosterSetParam(booster, "min_child_wight", buf.data));
|
|
resetStringInfo(&buf);
|
|
|
|
pfree(buf.data);
|
|
}
|
|
|
|
template <bool alloc = false>
|
|
void setup_xg_chunk(xg_data_t &xg_data)
|
|
{
|
|
check_hyper_bounds(sizeof(float), xg_data.lb_rows, "batch_size");
|
|
|
|
/* allocate the labels array */
|
|
uint lb_size = sizeof(float) * xg_data.lb_rows;
|
|
if (alloc) {
|
|
xg_data.labels = (float*)palloc0(lb_size);
|
|
} else {
|
|
/* reset labels for the next iteration */
|
|
errno_t rc = memset_s(xg_data.labels, lb_size, 0, lb_size);
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
|
|
check_hyper_bounds(xg_data.ft_rows, xg_data.ft_cols, "batch_size");
|
|
check_hyper_bounds(sizeof(float), xg_data.ft_rows * xg_data.ft_cols, "batch_size");
|
|
|
|
uint ft_size = sizeof(float) * xg_data.ft_rows * xg_data.ft_cols;
|
|
if (alloc) {
|
|
xg_data.features = (float*)palloc0(ft_size);
|
|
} else {
|
|
/* reset features for next iteration */
|
|
errno_t rc = memset_s(xg_data.features, ft_size, 0, ft_size);
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* this function initializes the algorithm
|
|
*/
|
|
static TrainModelState *xgboost_create(AlgorithmAPI *self, const TrainModel *pnode)
|
|
{
|
|
if (pnode->configurations != 1)
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("multiple hyper-parameter configurations for xgboost are not yet supported")));
|
|
|
|
auto xg_state = reinterpret_cast<XGBoostState *>(makeNodeWithSize(TrainModelState, sizeof(XGBoostState)));
|
|
xg_state->done = false;
|
|
return &xg_state->tms;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* XBoost train operation.
|
|
*
|
|
* Node that the allocated chunk size may differ from the number of
|
|
* actual tuples, i.e., number of tuples can be less in the last
|
|
* chunk.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void trainXG(AlgorithmAPI *alg, const HyperparamsXGBoost *xg_hyp, xg_data_t *chunk, const int n_tuples,
|
|
bool first_call = true)
|
|
{
|
|
/* XGoost DMatrix handle */
|
|
DMatrixHandle dtrain, dtest;
|
|
|
|
/* load DTrain matrix */
|
|
safe_xgboost(g_xgboostApi->XGDMatrixCreateFromMat((float *)chunk->features, // input data
|
|
n_tuples, // # rows
|
|
chunk->ft_cols, // # columns in the input
|
|
-1, // filler for missing values
|
|
&dtrain)); // handle of the DMatrix
|
|
|
|
/* load DTest matrix */
|
|
safe_xgboost(g_xgboostApi->XGDMatrixCreateFromMat((float *)chunk->features, n_tuples, chunk->ft_cols, -1, &dtest));
|
|
|
|
/* load the labels */
|
|
safe_xgboost(g_xgboostApi->XGDMatrixSetFloatInfo(dtrain, "label", chunk->labels, n_tuples));
|
|
safe_xgboost(g_xgboostApi->XGDMatrixSetFloatInfo(dtest, "label", chunk->labels, n_tuples));
|
|
|
|
DMatrixHandle eval_dmats[2] = {dtrain, dtest};
|
|
|
|
/* create the booster and load the desired parameters */
|
|
BoosterHandle booster;
|
|
safe_xgboost(g_xgboostApi->XGBoosterCreate(eval_dmats, 2, &booster));
|
|
|
|
if (!first_call) {
|
|
safe_xgboost(g_xgboostApi->XGBoosterUnserializeFromBuffer(booster, chunk->raw_model, chunk->raw_model_size));
|
|
} else {
|
|
set_hyperparams(alg, xg_hyp, booster);
|
|
}
|
|
|
|
/* evaluation structures */
|
|
const char* eval_names[2] = {"train", "test"};
|
|
const char* eval_result = nullptr;
|
|
|
|
for (uint32_t iter = 0; iter < xg_hyp->n_iterations; ++iter) {
|
|
safe_xgboost(g_xgboostApi->XGBoosterUpdateOneIter(booster, iter, dtrain));
|
|
safe_xgboost(g_xgboostApi->XGBoosterEvalOneIter(booster, iter, eval_dmats, eval_names, 2, &eval_result));
|
|
}
|
|
|
|
/* get evaluation results */
|
|
chunk->validation_score = parseDoubleFromErrMetric(eval_result);
|
|
uint64_t raw_model_len;
|
|
char *raw_model;
|
|
safe_xgboost(g_xgboostApi->XGBoosterSerializeToBuffer(booster, &raw_model_len, (const char **)&raw_model));
|
|
chunk->set_raw_model(raw_model, raw_model_len);
|
|
/* free xgboost structures */
|
|
safe_xgboost(g_xgboostApi->XGDMatrixFree(dtrain));
|
|
safe_xgboost(g_xgboostApi->XGDMatrixFree(dtest));
|
|
safe_xgboost(g_xgboostApi->XGBoosterFree(booster));
|
|
}
|
|
|
|
static void check_label(AlgorithmAPI *self, HyperparamsXGBoost *xg_hyperp, float label)
|
|
{
|
|
if ((self->algorithm == XG_BIN_LOGISTIC || self->algorithm == XG_REG_LOGISTIC) ||
|
|
(strcmp(xg_hyperp->eval_metric, "auc") == 0 || strcmp(xg_hyperp->eval_metric, "aucpr") == 0)) {
|
|
if (label == 0 || label == 1) {
|
|
return;
|
|
}
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Label must be 0 or 1 for logistic regression.")));
|
|
} else if (self->algorithm == XG_REG_GAMMA && label < 0) {
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Label must be nonnegative for gamma.")));
|
|
}
|
|
}
|
|
|
|
static void check_data_cnt(uint32_t tuple_count, uint32_t pos_cnt, HyperparamsXGBoost *xg_hyperp)
|
|
{
|
|
// only have positive numbers or negative numbers
|
|
if ((strcmp(xg_hyperp->eval_metric, "auc") == 0 || strcmp(xg_hyperp->eval_metric, "aucpr") == 0) &&
|
|
(pos_cnt == 0 || pos_cnt == tuple_count)) {
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("The dataset only contains pos or neg samples for auc or aucpr")));
|
|
}
|
|
}
|
|
|
|
void xgboost_serialize(SerializedModel *data, xg_data_t *chunk_ptr)
|
|
{
|
|
data->size = chunk_ptr->raw_model_size + sizeof(XgboostModelV01);
|
|
XgboostModelV01 *mdata = (XgboostModelV01 *)palloc0(data->size);
|
|
|
|
data->raw_data = mdata;
|
|
|
|
mdata->ft_cols = chunk_ptr->ft_cols;
|
|
int8_t *ptr = (int8_t *)(mdata + 1);
|
|
int avail = data->size - sizeof(XgboostModelV01);
|
|
|
|
int rc = memcpy_s(ptr, avail, chunk_ptr->raw_model, chunk_ptr->raw_model_size);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
}
|
|
static void xgboost_run(AlgorithmAPI *self, TrainModelState *pstate, Model **models)
|
|
{
|
|
Assert(pstate->finished == 0);
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &exec_start_time);
|
|
auto xg_state = reinterpret_cast<XGBoostState*>(pstate);
|
|
auto xg_hyperp = const_cast<HyperparamsXGBoost *>(
|
|
reinterpret_cast<HyperparamsXGBoost const *>(pstate->config->hyperparameters[0]));
|
|
|
|
ModelTuple const *outer_tuple_slot = nullptr;
|
|
|
|
// Check max_depth parameter
|
|
if (xg_hyperp->max_depth == 0 && (0 != strcmp(xgboost_boost_str[BOOST_GBLINEAR_IDX], xg_hyperp->booster))) {
|
|
ereport(ERROR, (errmodule(MOD_DB4AI),
|
|
errmsg("Max_depth must be larger than 0 when booster is non_linear value.")));
|
|
}
|
|
|
|
load_xgboost_library();
|
|
|
|
// data holder for in-between (chunk-wise) invocation of XGBoost training algorithm
|
|
xg_data_t chunk;
|
|
chunk.lb_rows = chunk.ft_rows = xg_hyperp->batch_size;
|
|
chunk.ft_cols = pstate->tuple.ncolumns - 1; // minus 1 for labels!
|
|
setup_xg_chunk<true>(chunk);
|
|
|
|
uint32_t pos_cnt = 0; // number of positive numbers
|
|
uint32_t tuple_count = 0, batch_count = 1;
|
|
while (true) {
|
|
/* retrieve tuples from the outer plan until there are no more */
|
|
outer_tuple_slot = pstate->fetch(pstate->callback_data, &pstate->tuple)
|
|
? &pstate->tuple : nullptr;
|
|
/* if no more tuples in the pipeline exit the loop */
|
|
if (outer_tuple_slot == nullptr) {
|
|
break;
|
|
}
|
|
|
|
/* skip null rows */
|
|
if (outer_tuple_slot->isnull[0]) {
|
|
continue;
|
|
}
|
|
|
|
/* first attribute `0' always corresponds to the label (i.e. target) */
|
|
float label =
|
|
datum_get_float8(outer_tuple_slot->typid[XG_TARGET_COLUMN], outer_tuple_slot->values[XG_TARGET_COLUMN]);
|
|
chunk.labels[tuple_count] = label;
|
|
check_label(self, xg_hyperp, label);
|
|
|
|
bool col_is_null = false;
|
|
for (int j = 1; j < pstate->tuple.ncolumns; ++j) {
|
|
if (outer_tuple_slot->isnull[j]) {
|
|
col_is_null = true;
|
|
break;
|
|
}
|
|
*(chunk.features + tuple_count * chunk.ft_cols + j - 1) =
|
|
datum_get_float8(outer_tuple_slot->typid[j], outer_tuple_slot->values[j]);
|
|
}
|
|
if (col_is_null) {
|
|
continue;
|
|
}
|
|
if (label > 0) {
|
|
pos_cnt += 1;
|
|
}
|
|
++tuple_count;
|
|
++xg_state->processed_tuples;
|
|
|
|
/* train this batch */
|
|
if (tuple_count == xg_hyperp->batch_size) {
|
|
check_data_cnt(tuple_count, pos_cnt, xg_hyperp);
|
|
trainXG(self, xg_hyperp, &chunk, tuple_count, (batch_count == 1));
|
|
setup_xg_chunk(chunk);
|
|
tuple_count = 0;
|
|
pos_cnt = 0;
|
|
++batch_count;
|
|
}
|
|
}
|
|
|
|
/* process any remaining tuples */
|
|
if (tuple_count > 0) {
|
|
check_data_cnt(tuple_count, pos_cnt, xg_hyperp);
|
|
trainXG(self, xg_hyperp, &chunk, tuple_count, (batch_count == 1));
|
|
}
|
|
|
|
/* record the execution time of this method */
|
|
clock_gettime(CLOCK_MONOTONIC, &exec_end_time);
|
|
|
|
/* processing stats */
|
|
xg_state->done = true;
|
|
xg_state->execution_time = interval_to_sec(time_diff(&exec_end_time, &exec_start_time));
|
|
if (xg_state->processed_tuples == 0)
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_NO_DATA_FOUND),
|
|
errmsg("Training data is empty, please check the input data.")));
|
|
|
|
// number of configurations already run
|
|
++pstate->finished;
|
|
// store the model
|
|
Model* model = models[0];
|
|
MemoryContext oldcxt = MemoryContextSwitchTo(model->memory_context);
|
|
|
|
model->data.version = DB4AI_MODEL_V01;
|
|
|
|
xgboost_serialize(&model->data, &chunk);
|
|
|
|
model->exec_time_secs = xg_state->execution_time;
|
|
model->processed_tuples = xg_state->processed_tuples;
|
|
model->num_actual_iterations = xg_hyperp->n_iterations;
|
|
model->return_type = FLOAT8OID;
|
|
// store the score
|
|
TrainingScore* pscore = (TrainingScore*)palloc0(sizeof(TrainingScore));
|
|
pscore->name = xg_hyperp->eval_metric;
|
|
pscore->value = chunk.validation_score;
|
|
model->scores = lappend(model->scores, pscore);
|
|
model->status = ERRCODE_SUCCESSFUL_COMPLETION;
|
|
MemoryContextSwitchTo(oldcxt);
|
|
}
|
|
|
|
static void xgboost_end(AlgorithmAPI *self, TrainModelState *pstate)
|
|
{
|
|
auto xg_state_node = reinterpret_cast<XGBoostState*>(pstate);
|
|
if (xg_state_node->oids != nullptr)
|
|
pfree(xg_state_node->oids);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------
|
|
// Prediction part
|
|
// ---------------------------------------------------------------------------------------------------
|
|
void print1DFeature(float *arr, const int cols)
|
|
{
|
|
StringInfoData buf;
|
|
initStringInfo(&buf);
|
|
|
|
for (int i = 0; i < cols; ++i) {
|
|
appendStringInfo(&buf, "%4.2f\t", arr[i]);
|
|
}
|
|
|
|
appendStringInfoChar(&buf, '\n');
|
|
elog(NOTICE, "%s", buf.data);
|
|
|
|
pfree(buf.data);
|
|
}
|
|
|
|
void xgboost_deserialize(SerializedModel *xg_model, SerializedModelXgboost *xgboostm)
|
|
{
|
|
int avail = xg_model->size;
|
|
if (xg_model->version == DB4AI_MODEL_V01) {
|
|
if (avail < (int)sizeof(XgboostModelV01))
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_STATUS),
|
|
errmsg("Model data corrupted reading header")));
|
|
XgboostModelV01 *mdata = (XgboostModelV01 *)xg_model->raw_data;
|
|
xgboostm->ft_cols = mdata->ft_cols;
|
|
avail -= (int)sizeof(XgboostModelV01);
|
|
|
|
void *placeholder = palloc0(avail);
|
|
uint8_t *ptr = (uint8_t *)(mdata + 1);
|
|
int rc = memcpy_s(placeholder, avail, ptr, avail);
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
xg_model->raw_data = placeholder;
|
|
xg_model->size = avail;
|
|
}
|
|
}
|
|
ModelPredictor xgboost_predict_prepare(AlgorithmAPI *, SerializedModel const *model, Oid return_type)
|
|
{
|
|
if (unlikely(!model))
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Xgboost predict prepare: model cannot be null")));
|
|
|
|
load_xgboost_library();
|
|
|
|
auto xg_model = const_cast<SerializedModel *>(model);
|
|
SerializedModelXgboost *xgboostm = (SerializedModelXgboost *)palloc0(sizeof(SerializedModelXgboost));
|
|
xgboost_deserialize(xg_model, xgboostm);
|
|
/* init XGBoost predictor */
|
|
safe_xgboost(g_xgboostApi->XGBoosterCreate(nullptr, 0, &xgboostm->booster));
|
|
/* load the decoded model */
|
|
safe_xgboost(g_xgboostApi->XGBoosterUnserializeFromBuffer(xgboostm->booster, xg_model->raw_data, xg_model->size));
|
|
|
|
return reinterpret_cast<ModelPredictor>(xgboostm);
|
|
}
|
|
|
|
Datum xgboost_predict(AlgorithmAPI *, ModelPredictor model, Datum *values, bool *isnull, Oid *types, int ncolumns)
|
|
{
|
|
SerializedModelXgboost *xgboostm = (SerializedModelXgboost *)model;
|
|
/* sanity checks */
|
|
Assert(xgboostm->booster != nullptr);
|
|
if (ncolumns != xgboostm->ft_cols)
|
|
ereport(ERROR, (errmodule(MOD_DB4AI),
|
|
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Invalid number of features for prediction, provided %d, expected %d",
|
|
ncolumns, xgboostm->ft_cols)));
|
|
|
|
load_xgboost_library();
|
|
|
|
float features[ncolumns];
|
|
for (int col = 0; col < ncolumns; ++col)
|
|
features[col] = isnull[col] ? 0.0 : datum_get_float8(types[col], values[col]);
|
|
|
|
DMatrixHandle dmat;
|
|
/* convert to DMatrix */
|
|
safe_xgboost(g_xgboostApi->XGDMatrixCreateFromMat((float *) features, 1, ncolumns, -1, &dmat));
|
|
|
|
bst_ulong out_len;
|
|
const float *out_result;
|
|
safe_xgboost(g_xgboostApi->XGBoosterPredict(xgboostm->booster, dmat, 0, 0, 0, &out_len, &out_result));
|
|
|
|
/* currently predict tuple-at-the-time */
|
|
double prediction = out_result[0];
|
|
|
|
/* release memory of xgboost dmatrix structure */
|
|
safe_xgboost(g_xgboostApi->XGDMatrixFree(dmat));
|
|
|
|
return Float8GetDatum(prediction);
|
|
}
|
|
|
|
/*
|
|
* used in EXPLAIN MODEL
|
|
*/
|
|
List *xgboost_explain(AlgorithmAPI *self, SerializedModel const *model, Oid return_type)
|
|
{
|
|
if (unlikely(!model))
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("xgboost explain: model cannot be null")));
|
|
|
|
if (unlikely(model->version != DB4AI_MODEL_V01))
|
|
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("xgboost explain: currently only model V01 is supported")));
|
|
|
|
List *model_info = nullptr;
|
|
|
|
auto xg_model = const_cast<SerializedModel*>(model);
|
|
|
|
TrainingInfo *info = (TrainingInfo *)palloc0(sizeof(TrainingInfo));
|
|
info->value = Int32GetDatum(xg_model->size);
|
|
info->type = INT4OID;
|
|
info->name = "model size";
|
|
model_info = lappend(model_info, info);
|
|
|
|
return model_info;
|
|
}
|
|
|
|
XGBoost xg_reg_logistic = {
|
|
// AlgorithmAPI
|
|
{
|
|
XG_REG_LOGISTIC,
|
|
"xgboost_regression_logistic",
|
|
ALGORITHM_ML_DEFAULT,
|
|
xgboost_metrics_accuracy,
|
|
xgboost_get_hyperparameters,
|
|
xgboost_make_hyperparameters,
|
|
nullptr,
|
|
xgboost_create,
|
|
xgboost_run,
|
|
xgboost_end,
|
|
xgboost_predict_prepare,
|
|
xgboost_predict,
|
|
xgboost_explain
|
|
},
|
|
};
|
|
|
|
XGBoost xg_bin_logistic = {
|
|
// AlgorithmAPI
|
|
{
|
|
XG_BIN_LOGISTIC,
|
|
"xgboost_binary_logistic",
|
|
ALGORITHM_ML_DEFAULT,
|
|
xgboost_metrics_accuracy,
|
|
xgboost_get_hyperparameters,
|
|
xgboost_make_hyperparameters,
|
|
nullptr,
|
|
xgboost_create,
|
|
xgboost_run,
|
|
xgboost_end,
|
|
xgboost_predict_prepare,
|
|
xgboost_predict,
|
|
xgboost_explain
|
|
},
|
|
};
|
|
|
|
XGBoost xg_reg_sqe = {
|
|
// AlgorithmAPI
|
|
{
|
|
XG_REG_SQE,
|
|
"xgboost_regression_squarederror",
|
|
ALGORITHM_ML_DEFAULT,
|
|
xgboost_metrics_accuracy,
|
|
xgboost_get_hyperparameters,
|
|
xgboost_make_hyperparameters,
|
|
nullptr,
|
|
xgboost_create,
|
|
xgboost_run,
|
|
xgboost_end,
|
|
xgboost_predict_prepare,
|
|
xgboost_predict,
|
|
xgboost_explain
|
|
},
|
|
};
|
|
|
|
XGBoost xg_reg_gamma = {
|
|
// AlgorithmAPI
|
|
{
|
|
XG_REG_GAMMA,
|
|
"xgboost_regression_gamma",
|
|
ALGORITHM_ML_DEFAULT,
|
|
xgboost_metrics_accuracy,
|
|
xgboost_get_hyperparameters,
|
|
xgboost_make_hyperparameters,
|
|
nullptr,
|
|
xgboost_create,
|
|
xgboost_run,
|
|
xgboost_end,
|
|
xgboost_predict_prepare,
|
|
xgboost_predict,
|
|
xgboost_explain
|
|
},
|
|
};
|