openGauss-server/src/gausskernel/dbmind/db4ai/commands/create_model.cpp

752 lines
29 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.
*---------------------------------------------------------------------------------------
*
* command.h
*
* IDENTIFICATION
* src/gausskernel/dbmind/db4ai/commands/create_model.cpp
*
* ---------------------------------------------------------------------------------------
*/
#include "db4ai/create_model.h"
#include "postgres.h"
#include "knl/knl_variable.h"
#include "db4ai/model_warehouse.h"
#include "db4ai/hyperparameter_validation.h"
#include "catalog/indexing.h"
#include "executor/executor.h"
#include "executor/nodeKMeans.h"
#include "nodes/value.h"
#include "parser/analyze.h"
#include "rewrite/rewriteHandler.h"
#include "utils/snapmgr.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "workload/workload.h"
#include "executor/nodeGD.h"
#include "db4ai/aifuncs.h"
#include "utils/builtins.h"
extern void exec_simple_plan(PlannedStmt *plan); // defined in postgres.cpp
bool verify_pgarray(ArrayType const * pg_array, int32_t n); // defined in kmeans.cpp
/*
* Common setup needed by both normal execution and EXPLAIN ANALYZE.
* This setup is adapted from SetupForCreateTableAs
*/
static Query *setup_for_create_model(Query *query, /* IntoClause *into, */ const char *queryString,
ParamListInfo params /* , DestReceiver *dest */)
{
List *rewritten = NIL;
Assert(query->commandType == CMD_SELECT);
/*
* Parse analysis was done already, but we still have to run the rule
* rewriter. We do not do AcquireRewriteLocks: we assume the query either
* came straight from the parser, or suitable locks were acquired by
* plancache.c.
*
* Because the rewriter and planner tend to scribble on the input, we make
* a preliminary copy of the source querytree. This prevents problems in
* the case that CTAS is in a portal or plpgsql function and is executed
* repeatedly. (See also the same hack in EXPLAIN and PREPARE.)
*/
rewritten = QueryRewrite((Query *)copyObject(query));
/* SELECT should never rewrite to more or less than one SELECT query */
if (list_length(rewritten) != 1) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INTERNAL_ERROR),
errmsg("Unexpected rewrite result for CREATE MODEL statement")));
}
query = (Query *)linitial(rewritten);
return query;
}
// Create an GradientDescent execution node with a given configuration
static GradientDescent *create_gd_node(AlgorithmML algorithm, List *hyperparameters, DestReceiverTrainModel *dest)
{
GradientDescent *gd_node = makeNode(GradientDescent);
gd_node->algorithm = algorithm;
configure_hyperparameters(algorithm, hyperparameters, dest->model, gd_node);
if (gd_node->seed == 0) {
gd_node->seed = time(NULL); // it is not set to zero again (zero is the epoch in the past)
update_model_hyperparameter(dest->model, "seed", INT4OID, Int32GetDatum(gd_node->seed));
}
gd_node->plan.type = T_GradientDescent;
gd_node->plan.targetlist = makeGradientDescentExpr(algorithm, nullptr, 1);
dest->targetlist = gd_node->plan.targetlist;
return gd_node;
}
// Add a GradientDescent operator at the root of the plan
static PlannedStmt *add_GradientDescent_to_plan(PlannedStmt *plan, AlgorithmML algorithm, List *hyperparameters,
DestReceiverTrainModel *dest)
{
GradientDescent *gd_node = create_gd_node(algorithm, hyperparameters, dest);
gd_node->plan.lefttree = plan->planTree;
plan->planTree = &gd_node->plan;
return plan;
}
static DistanceFunction get_kmeans_distance(const char *distance_func)
{
DistanceFunction distance = KMEANS_L2_SQUARED;
if (strcmp(distance_func, "L1") == 0)
distance = KMEANS_L1;
else if (strcmp(distance_func, "L2") == 0)
distance = KMEANS_L2;
else if (strcmp(distance_func, "L2_Squared") == 0)
distance = KMEANS_L2_SQUARED;
else if (strcmp(distance_func, "Linf") == 0)
distance = KMEANS_LINF;
else {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("No known distance function chosen. Current candidates are: "
"L1, L2, L2_Squared (default), Linf")));
}
return distance;
}
static SeedingFunction get_kmeans_seeding(const char *seeding_func)
{
SeedingFunction seeding = KMEANS_RANDOM_SEED;
if (strcmp(seeding_func, "Random++") == 0)
seeding = KMEANS_RANDOM_SEED;
else if (strcmp(seeding_func, "KMeans||") == 0)
seeding = KMEANS_BB;
else {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("No known seeding function chosen. Current candidates are: Random++ (default), KMeans||")));
}
return seeding;
}
static KMeans *create_kmeans_node(AlgorithmML const algorithm, List *hyperparameters, DestReceiverTrainModel *dest)
{
KMeans *kmeans_node = makeNode(KMeans);
char *distance_func = nullptr;
char *seeding_func = nullptr;
double tolerance = 0.;
int32_t num_iterations = 0;
int32_t num_centroids = 0;
int32_t const max_num_centroids = 1000000;
int32_t batch_size = 0;
int32_t num_features = 0;
int32_t external_seed = 0;
int32_t verbosity = 0;
auto kmeans_model = reinterpret_cast<ModelKMeans *>(dest->model);
HyperparameterValidation validation;
memset_s(&validation, sizeof(HyperparameterValidation), 0, sizeof(HyperparameterValidation));
kmeans_node->algorithm = algorithm;
kmeans_node->plan.type = T_KMeans;
kmeans_model->model.return_type = INT4OID;
set_hyperparameter<int32_t>("max_iterations", &num_iterations, hyperparameters, 10, dest->model, &validation);
if (unlikely(num_iterations <= 0)) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("max_iterations must be in [1, %d]", INT_MAX)));
} else {
kmeans_node->parameters.num_iterations = num_iterations;
}
set_hyperparameter<int32_t>("num_centroids", &num_centroids, hyperparameters, 10, dest->model, &validation);
if (unlikely((num_centroids <= 0) || (num_centroids > max_num_centroids))) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("num_centroids must be in [1, %d]", max_num_centroids)));
} else {
kmeans_node->parameters.num_centroids = num_centroids;
}
set_hyperparameter<double>("tolerance", &tolerance, hyperparameters, 0.00001, dest->model, &validation);
if (unlikely((tolerance <= 0.) || (tolerance > 1.))) {
ereport(ERROR,
(errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("tolerance must be in (0, 1.0]")));
} else {
kmeans_node->parameters.tolerance = tolerance;
}
set_hyperparameter<int32_t>("batch_size", &batch_size, hyperparameters, 10, dest->model, &validation);
if (unlikely((batch_size <= 0) || (batch_size > max_num_centroids))) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("batch_size must be in [1, %d]", max_num_centroids)));
} else {
kmeans_node->description.batch_size = batch_size;
}
set_hyperparameter<int32_t>("num_features", &num_features, hyperparameters, 2, dest->model, &validation);
if (unlikely((num_features <= 0) || (num_features > max_num_centroids))) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("num_features must be in [1, %d]", max_num_centroids)));
} else {
kmeans_node->description.n_features = num_features;
}
set_hyperparameter<char *>("distance_function", &distance_func, hyperparameters, "L2_Squared", dest->model,
&validation);
kmeans_node->description.distance = get_kmeans_distance(distance_func);
set_hyperparameter<char *>("seeding_function", &seeding_func, hyperparameters, "Random++", dest->model,
&validation);
kmeans_node->description.seeding = get_kmeans_seeding(seeding_func);
set_hyperparameter<int32_t>("verbose", &verbosity, hyperparameters, false, dest->model, &validation);
if (verbosity < 0 || verbosity > 2)
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Verbosity level must be between 0 (no output), 1 (less output), or 2 (full output)")));
else
kmeans_node->description.verbosity = static_cast<Verbosity>(verbosity);
/*
* unfortunately the system parses an int64_t as T_Float whenever the it does not fit into a int32_t
* thus, an int64_t might be internally parsed as a T_Integer or a T_Float depending on whether
* the precision fits into an int32_t. thus, we accept a small seed (int32_t) that is xor'ed with
* a random long internal seed.
*/
set_hyperparameter<int32_t>("seed", &external_seed, hyperparameters, 0, dest->model, &validation);
/*
* the seed used for the algorithm is the xor of the seed provided by the user with
* a random (but fixed) internal seed. as long as the internal seed is kept unchanged
* results will be reproducible (see nodeKMeans.cpp)
*/
if (external_seed < 0)
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("seed must be in [0, %d]", INT_MAX)));
else
kmeans_node->parameters.external_seed = static_cast<uint64_t>(external_seed);
/*
* these fields are propagated all the way to store_model and used for prediction
* the value of fields of ModelKMeans not set here change during execution
* and thus are set in the very end, when the model is about to be stored
*/
kmeans_model->dimension = kmeans_node->description.n_features;
kmeans_model->original_num_centroids = kmeans_node->parameters.num_centroids;
kmeans_model->distance_function_id = kmeans_node->description.distance;
pfree(distance_func);
pfree(seeding_func);
return kmeans_node;
}
// Add a k-means operator at the root of the plan
static PlannedStmt *add_kmeans_to_plan(PlannedStmt *plan, AlgorithmML algorithm, List *hyperparameters,
DestReceiverTrainModel *dest)
{
if (unlikely(algorithm != KMEANS)) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Algorithm is not the expected %u (k-means). Provided %u", KMEANS, algorithm)));
}
KMeans *kmeans_node = create_kmeans_node(algorithm, hyperparameters, dest);
kmeans_node->plan.lefttree = plan->planTree;
plan->planTree = &kmeans_node->plan;
return plan;
}
// Add the ML algorithm at the root of the plan according to the CreateModelStmt
static PlannedStmt *add_create_model_to_plan(CreateModelStmt *stmt, PlannedStmt *plan, DestReceiverTrainModel *dest)
{
PlannedStmt *result = NULL;
switch (stmt->algorithm) {
case LOGISTIC_REGRESSION:
case SVM_CLASSIFICATION:
case LINEAR_REGRESSION: {
result = add_GradientDescent_to_plan(plan, stmt->algorithm, stmt->hyperparameters, dest);
break;
}
case KMEANS: {
result = add_kmeans_to_plan(plan, stmt->algorithm, stmt->hyperparameters, dest);
break;
}
case INVALID_ALGORITHM_ML:
default: {
char *s = "logistic_regression, svm_classification, linear_regression, kmeans";
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Architecture %s is not supported. Supported architectures: %s", stmt->architecture, s)));
}
}
return result;
}
// Create the query plan with the appropriate machine learning model
PlannedStmt *plan_create_model(CreateModelStmt *stmt, const char *query_string, ParamListInfo params,
DestReceiver *dest)
{
Query *query = (Query *)stmt->select_query;
PlannedStmt *plan = NULL;
query = setup_for_create_model(query, query_string, params);
/* plan the query */
plan = pg_plan_query(query, 0, params);
// Inject the GradientDescent node at the root of the plan
plan = add_create_model_to_plan(stmt, plan, (DestReceiverTrainModel *)dest);
return plan;
}
// Prepare the DestReceiver for training
void configure_dest_receiver_train_model(DestReceiverTrainModel *dest, AlgorithmML algorithm, const char *model_name,
const char *sql)
{
switch (algorithm) {
case LOGISTIC_REGRESSION:
case LINEAR_REGRESSION:
case SVM_CLASSIFICATION: {
dest->model = (Model *)palloc0(sizeof(ModelGradientDescent));
break;
}
case KMEANS: {
dest->model = (Model *)palloc0(sizeof(ModelKMeans));
break;
}
default: {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Unsupported model type in model warehouse %d", algorithm)));
}
}
dest->model->algorithm = algorithm;
dest->model->model_name = pstrdup(model_name);
dest->model->sql = sql;
dest->targetlist = nullptr;
}
// /*
// * ExecCreateTableAs -- execute a CREATE TABLE AS command
// */
void exec_create_model(CreateModelStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag)
{
#ifdef ENABLE_MULTIPLE_NODES
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("No support for distributed scenarios yet.")));
#endif
DestReceiverTrainModel *dest = NULL;
PlannedStmt *plan = NULL;
QueryDesc *queryDesc = NULL;
ScanDirection dir;
/*
* Create the tuple receiver object and insert hyperp it will need
*/
dest = (DestReceiverTrainModel *)CreateDestReceiver(DestTrainModel);
configure_dest_receiver_train_model(dest, (AlgorithmML)stmt->algorithm, stmt->model, queryString);
plan = plan_create_model(stmt, queryString, params, (DestReceiver *)dest);
/*
* Use a snapshot with an updated command ID to ensure this query sees
* results of any previously executed queries. (This could only matter if
* the planner executed an allegedly-stable function that changed the
* database contents, but let's do it anyway to be parallel to the EXPLAIN
* code path.)
*/
PushCopiedSnapshot(GetActiveSnapshot());
UpdateActiveSnapshotCommandId();
/* Create a QueryDesc, redirecting output to our tuple receiver */
queryDesc = CreateQueryDesc(plan, queryString, GetActiveSnapshot(), InvalidSnapshot, &dest->dest, params, 0);
#ifdef ENABLE_MULTIPLE_NODES
if (ENABLE_WORKLOAD_CONTROL && (IS_PGXC_COORDINATOR)) {
#else
if (ENABLE_WORKLOAD_CONTROL) {
#endif
/* Check if need track resource */
u_sess->exec_cxt.need_track_resource = WLMNeedTrackResource(queryDesc);
}
/* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, 0);
/* workload client manager */
if (ENABLE_WORKLOAD_CONTROL) {
WLMInitQueryPlan(queryDesc);
dywlm_client_manager(queryDesc);
}
dir = ForwardScanDirection;
/* run the plan */
ExecutorRun(queryDesc, dir, 0L);
/* save the rowcount if we're given a completionTag to fill */
if (completionTag != NULL) {
errno_t rc;
rc = snprintf_s(completionTag, COMPLETION_TAG_BUFSIZE, COMPLETION_TAG_BUFSIZE - 1,
"MODEL CREATED. PROCESSED %lu", queryDesc->estate->es_processed);
securec_check_ss(rc, "\0", "\0");
}
/* and clean up */
ExecutorFinish(queryDesc);
ExecutorEnd(queryDesc);
FreeQueryDesc(queryDesc);
PopActiveSnapshot();
}
static void store_gd_expr_in_model(Datum dt, Oid type, int col, GradientDescentExprField field, Model *model,
ModelGradientDescent *model_gd, TupleDesc tupdesc)
{
switch (field) {
case GD_EXPR_ALGORITHM:
Assert(type == INT4OID);
model_gd->model.algorithm = (AlgorithmML)DatumGetInt32(dt);
break;
case GD_EXPR_OPTIMIZER:
break; // Ignore field
case GD_EXPR_RESULT_TYPE:
Assert(type == OIDOID);
model->return_type = DatumGetUInt32(dt);
break;
case GD_EXPR_NUM_ITERATIONS:
Assert(type == INT4OID);
model->num_actual_iterations = DatumGetInt32(dt);
break;
case GD_EXPR_EXEC_TIME_MSECS:
Assert(type == FLOAT4OID);
model->exec_time_secs = DatumGetFloat4(dt) / 1000.0;
break;
case GD_EXPR_PROCESSED_TUPLES:
Assert(type == INT4OID);
model->processed_tuples = DatumGetInt32(dt);
break;
case GD_EXPR_DISCARDED_TUPLES:
Assert(type == INT4OID);
model->discarded_tuples = DatumGetInt32(dt);
break;
case GD_EXPR_WEIGHTS:
Assert(type == FLOAT4ARRAYOID);
model_gd->weights = datumCopy(dt, tupdesc->attrs[col]->attbyval, tupdesc->attrs[col]->attlen);
break;
case GD_EXPR_CATEGORIES: {
ArrayType *arr = (ArrayType *)DatumGetPointer(dt);
model_gd->ncategories = ARR_DIMS(arr)[0];
model_gd->categories =
datumCopy(dt, tupdesc->attrs[col]->attbyval, tupdesc->attrs[col]->attlen);
} break;
default:
(void)type;
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Model warehouse for GradientDescent field %d not implemented", field)));
break;
}
}
static void store_tuple_gd_in_model_warehouse(TupleTableSlot *slot, DestReceiverTrainModel *dest)
{
Assert(dest->targetlist != nullptr);
TupleDesc tupdesc = slot->tts_tupleDescriptor;
Model *model = dest->model;
model->pre_time_secs = 0.0;
ModelGradientDescent *model_gd = (ModelGradientDescent *)model;
model_gd->ncategories = 0;
model_gd->model.algorithm = INVALID_ALGORITHM_ML; // undefined
int col = 0;
ListCell *lc;
foreach (lc, dest->targetlist) {
TargetEntry *target = lfirst_node(TargetEntry, lc);
GradientDescentExpr *expr = (GradientDescentExpr *)target->expr;
if (!slot->tts_isnull[col]) {
Datum dt = slot->tts_values[col];
Oid type = tupdesc->attrs[col]->atttypid;
if ((expr->field & GD_EXPR_SCORE) != 0) {
Assert(type == FLOAT4OID);
TrainingScore *score = (TrainingScore *)palloc0(sizeof(TrainingScore));
score->name = pstrdup(target->resname);
score->value = DatumGetFloat4(dt);
model->scores = lappend(model->scores, score);
} else {
store_gd_expr_in_model(dt, type, col, expr->field, model, model_gd, tupdesc);
}
}
col++;
}
}
static void store_kmeans_data_in_model(uint32_t natts, TupleDesc tupdesc, Datum *values, bool *nulls,
Model *model, ModelKMeans *model_kmeans)
{
ArrayType *centroid_ids = nullptr;
ArrayType *centroid_coordinates = nullptr;
ArrayType *objective_functions = nullptr;
ArrayType *avg_distances = nullptr;
ArrayType *min_distances = nullptr;
ArrayType *max_distances = nullptr;
ArrayType *std_dev_distances = nullptr;
ArrayType *cluster_sizes = nullptr;
/* these are the inner-facing arrays */
int32_t *centroid_ids_data = nullptr;
double *centroid_coordiates_data = nullptr;
double *objective_functions_data = nullptr;
double *avg_distances_data = nullptr;
double *min_distances_data = nullptr;
double *max_distances_data = nullptr;
double *std_dev_distances_data = nullptr;
int64_t *cluster_sizes_data = nullptr;
Oid oid = 0;
Datum attr = 0;
/*
* for tuple at a time we only use one centroid at a time
*/
for (uint32_t a = 0; a < natts; ++a) {
if (unlikely(nulls[a])) {
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("Encountered null attribute %u when serializing k-means model when it should not", a)));
}
oid = tupdesc->attrs[a]->atttypid;
attr = values[a];
/*
* this switch has to match exactly the schema of the row we return (see nodeKMeans.cpp)
* there is a single row (quite big in general) thus the switch executes only once
*/
switch (a) {
case 0:
// centroids ids of type INT4ARRAYOID
Assert(oid == INT4ARRAYOID);
centroid_ids = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
centroid_ids_data = reinterpret_cast<int32_t *>(ARR_DATA_PTR(centroid_ids));
break;
case 1:
// centroids coordinates of type FLOAT8ARRAYOID
Assert(oid == FLOAT8ARRAYOID);
/*
* in tuple at a time, this memory reference is valid until we store the centroid
* in the model warehouse
*/
centroid_coordinates = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
centroid_coordiates_data = reinterpret_cast<double *>(ARR_DATA_PTR(centroid_coordinates));
break;
case 2:
// value of the objective functions (per cluster) of type FLOAT8ARRAYOID
Assert(oid == FLOAT8ARRAYOID);
objective_functions = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
objective_functions_data = reinterpret_cast<double *>(ARR_DATA_PTR(objective_functions));
break;
case 3:
// avg distance of the clusters of type FLOAT8ARRAYOID
Assert(oid == FLOAT8ARRAYOID);
avg_distances = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
avg_distances_data = reinterpret_cast<double *>(ARR_DATA_PTR(avg_distances));
break;
case 4:
// min distance of the clusters of type FLOAT8ARRAYOID
Assert(oid == FLOAT8ARRAYOID);
min_distances = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
min_distances_data = reinterpret_cast<double *>(ARR_DATA_PTR(min_distances));
break;
case 5:
// max distance of the clusters of type FLOAT8ARRAYOID
Assert(oid == FLOAT8ARRAYOID);
max_distances = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
max_distances_data = reinterpret_cast<double *>(ARR_DATA_PTR(max_distances));
break;
case 6:
// standard deviation of clusters of type FLOAT8ARRAYOID
Assert(oid == FLOAT8ARRAYOID);
std_dev_distances = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
std_dev_distances_data = reinterpret_cast<double *>(ARR_DATA_PTR(std_dev_distances));
break;
case 7:
// cluster sizes of type INT8ARRAYOID
Assert(oid == INT8ARRAYOID);
cluster_sizes = reinterpret_cast<ArrayType *>(DatumGetPointer(attr));
cluster_sizes_data = reinterpret_cast<int64_t *>(ARR_DATA_PTR(cluster_sizes));
break;
case 8:
// num good points of type INT8OID
Assert(oid == INT8OID);
model->processed_tuples = DatumGetInt64(attr);
break;
case 9:
// num bad point of type INT8OID
Assert(oid == INT8OID);
model->discarded_tuples = DatumGetInt64(attr);
break;
case 10:
// seedings time (secs) of type FLOAT8OID
Assert(oid == FLOAT8OID);
model->pre_time_secs = DatumGetFloat8(attr);
break;
case 11:
// execution time (secs) of type FLOAT8OID
Assert(oid == FLOAT8OID);
model->exec_time_secs = DatumGetFloat8(attr);
break;
case 12:
// actual number of iterations INT4OID
Assert(oid == INT4OID);
model->num_actual_iterations = DatumGetInt32(attr);
break;
case 13:
// actual number of centroids INT4OID
Assert(oid == INT4OID);
model_kmeans->actual_num_centroids = DatumGetInt32(attr);
break;
case 14:
// seed used for computations
Assert(oid == INT8OID);
model_kmeans->seed = DatumGetInt64(attr);
break;
default:
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Unknown attribute %u when serializing k-means model", a)));
}
}
uint32_t const actual_num_centroids = model_kmeans->actual_num_centroids;
uint32_t const dimension = model_kmeans->dimension;
uint32_t centroid_coordinates_offset = 0;
WHCentroid *current_centroid = nullptr;
/*
* at this point we have extracted all the attributes and the memory representation
* of the model can be constructed so that it can be stored in the model warehouse
*/
model_kmeans->centroids = reinterpret_cast<WHCentroid *>(palloc0(sizeof(WHCentroid) * actual_num_centroids));
/*
* we fill in the information of every centroid
*/
for (uint32_t current_centroid_idx = 0; current_centroid_idx < actual_num_centroids; ++current_centroid_idx) {
current_centroid = model_kmeans->centroids + current_centroid_idx;
current_centroid->id = centroid_ids_data[current_centroid_idx];
current_centroid->objective_function = objective_functions_data[current_centroid_idx];
current_centroid->avg_distance_to_centroid = avg_distances_data[current_centroid_idx];
current_centroid->min_distance_to_centroid = min_distances_data[current_centroid_idx];
current_centroid->max_distance_to_centroid = max_distances_data[current_centroid_idx];
current_centroid->std_dev_distance_to_centroid = std_dev_distances_data[current_centroid_idx];
current_centroid->cluster_size = cluster_sizes_data[current_centroid_idx];
current_centroid->coordinates = centroid_coordiates_data + centroid_coordinates_offset;
centroid_coordinates_offset += dimension;
}
}
static void store_tuple_kmeans_in_model_warehouse(TupleTableSlot *slot, DestReceiverTrainModel *dest)
{
/*
* sanity checks
*/
Assert(slot != NULL);
Assert(!slot->tts_isempty);
Assert(slot->tts_nvalid == NUM_ATTR_OUTPUT);
Assert(slot->tts_tupleDescriptor != NULL);
Assert(!TTS_HAS_PHYSICAL_TUPLE(slot));
TupleDesc tupdesc = slot->tts_tupleDescriptor;
auto model_kmeans = reinterpret_cast<ModelKMeans *>(dest->model);
Model *model = &model_kmeans->model;
if (unlikely(slot->tts_isempty))
return;
uint32_t const natts = slot->tts_nvalid;
/*
* the slot contains a virtual tuple and thus we can access its attributs directly
*/
Datum *values = slot->tts_values;
bool *nulls = slot->tts_isnull;
if (unlikely(!values && !nulls)) {
ereport(ERROR,
(errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Empty arrays values and nulls")));
}
store_kmeans_data_in_model(natts, tupdesc, values, nulls, model, model_kmeans);
}
static void store_tuple_in_model_warehouse(TupleTableSlot *slot, DestReceiver *self)
{
DestReceiverTrainModel *dest = (DestReceiverTrainModel *)self;
Model *model = dest->model;
switch (model->algorithm) {
case LOGISTIC_REGRESSION:
case SVM_CLASSIFICATION:
case LINEAR_REGRESSION:
store_tuple_gd_in_model_warehouse(slot, dest);
break;
case KMEANS:
store_tuple_kmeans_in_model_warehouse(slot, dest);
break;
case INVALID_ALGORITHM_ML:
default:
ereport(ERROR, (errmodule(MOD_DB4AI), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Unsupported model type %d", static_cast<int>(model->algorithm))));
break;
}
store_model(model);
}
static void do_nothing_startup(DestReceiver *self, int operation, TupleDesc typehyperp)
{
/* do nothing */
}
static void do_nothing_cleanup(DestReceiver *self)
{
/* this is used for both shutdown and destroy methods */
}
DestReceiver *CreateTrainModelDestReceiver()
{
DestReceiverTrainModel *dr = (DestReceiverTrainModel *)palloc0(sizeof(DestReceiverTrainModel));
DestReceiver *result = &dr->dest;
result->rStartup = do_nothing_startup;
result->receiveSlot = store_tuple_in_model_warehouse;
result->rShutdown = do_nothing_cleanup;
result->rDestroy = do_nothing_cleanup;
result->mydest = DestTrainModel;
return result;
}