/*---------------------------------------------------------------------------- ChucK Concurrent, On-the-fly Audio Programming Language Compiler and Virtual Machine Copyright (c) 2004 Ge Wang and Perry R. Cook. All rights reserved. http://chuck.stanford.edu/ http://chuck.cs.princeton.edu/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 U.S.A. -----------------------------------------------------------------------------*/ //----------------------------------------------------------------------------- // file: ulib_ai.cpp // desc: Music and AI class library; part of ChAI (ChucK for AI) // // date: Fall 2022 -- present // authors: Ge Wang (ge@ccrma.stanford.edu) // Yikai Li (yikaili@stanford.edu) // // (HISTORICAL NOTE) // Much of ChAI and its ways of thinking and doing can be foundationally // attributed to the work of Dr. Rebecca Fiebrink, her landmark Ph.D. Thesis, // /Real-time Human Interaction With Supervised Learning Algorithms for Music // Composition and Performance/ (2011) [1], the Wekinator framework [2], her // teaching at the intersections of AI, HCI, and Music [3], as well as earlier // collaborations between Rebecca and Ge [4,5] including SMIRK [6] (Small Musical // Information Retrieval toolKit; 2008; MARSYAS and the MIR work of Dr. George // Tzanetakis; early efforts in on-the-fly learning), the unit analyzer // framework [7] (2007; afford real-time audio feature extraction). All of // these have directly or indirectly contributed the creation of ChAI. // Additionally, ChAI benefitted from the teaching and design philosophy of // Dr. Perry R. Cook (Ph.D. advisor to Rebecca, George, and Ge at Princeton, // and ChucK co-author), who argued for real-time human interaction, parametric // sound synthesis, and the importance of play in the design of musical tools. // // [1] Fiebrink, Rebecca. 2011. /Real-time Human Interaction With Supervised // Learning Algorithms for Music Composition and Performance/ Ph.D. Thesis. // Princeton University. https://www.cs.princeton.edu/techreports/2010/891.pdf // // [2] http://www.wekinator.org/ // Wekinator | Software for real-time, interactive machine learning // // [3] Fiebrink, Rebecca. "Machine Learning for Musicians and Artists" MOOC // https://www.kadenze.com/courses/machine-learning-for-musicians-and-artists/info // // [4] Fiebrink, R., G. Wang, P. R. Cook. 2008. "Foundations for On-the-fly // Learning in the ChucK Programming Language." /International Computer Music // Conference/. https://mcd.stanford.edu/publish/files/2008-icmc-learning.pdf // // [5] Fiebrink, R., G. Wang, P. R. Cook. 2008. "Support for Music Information // Retrieval in the ChucK Programming Language." /International Conference // on Music Information Retrieval/ (ISMIR). // https://mcd.stanford.edu/publish/files/2008-ismir-proto.pdf // // [6] http://smirk.cs.princeton.edu/ // sMIRk | Small Music Information Retrieval toolKit // // [7] Tzanetakis, G. and P. R. Cook. 2000 "MARSYAS: A Framework for Audio // Analysis." Organised Sound. 4:(3) // // [8] Tzanetakis, G. and P. R. Cook. 2002 "Musical Genre Classification of // Audio Signals." IEEE Transaction on Speech and Audio Processing. 10(5). // // [9] Wang, G., R. Fiebrink, P. R. Cook. 2007. "Combining Analysis and // Synthesis in the ChucK Programming Language." /International Computer // Music Conference/. https://mcd.stanford.edu/publish/files/2007-icmc-uana.pdf //----------------------------------------------------------------------------- #include "ulib_ai.h" #include "chuck_type.h" #include "chuck_instr.h" #include "chuck_vm.h" #include "chuck_errmsg.h" #include "util_string.h" #include #include #include #include #include #include using namespace std; // global model type static t_CKINT g_mt_mlp = 0; static t_CKINT g_mt_knn = 1; static t_CKINT g_mt_svm = 2; static t_CKINT g_mt_lr = 3; static t_CKINT g_mt_pr = 4; static t_CKINT g_mt_dt = 5; static t_CKINT g_mt_boost = 6; static t_CKINT g_mt_naivebayes = 7; // global activation type static t_CKINT g_at_linear = 0; static t_CKINT g_at_sigmoid = 1; static t_CKINT g_at_relu = 2; static t_CKINT g_at_tanh = 3; static t_CKINT g_at_softmax = 4; // global task type static t_CKINT g_tt_regression = -1; static t_CKINT g_tt_classification = -2; // 1.4.2.0 (yikai) added SVM CK_DLL_CTOR( SVM_ctor ); CK_DLL_DTOR( SVM_dtor ); CK_DLL_MFUN( SVM_train ); CK_DLL_MFUN( SVM_predict ); // offset static t_CKUINT SVM_offset_data = 0; // 1.4.2.0 (yikai) added KNN CK_DLL_CTOR( KNN_ctor ); CK_DLL_DTOR( KNN_dtor ); CK_DLL_MFUN( KNN_train ); CK_DLL_MFUN( KNN_search ); CK_DLL_MFUN( KNN_search2 ); CK_DLL_MFUN( KNN_weigh ); // 1.4.2.1 (yikai) added KNN2 CK_DLL_CTOR( KNN2_ctor ); CK_DLL_DTOR( KNN2_dtor ); CK_DLL_MFUN( KNN2_train ); CK_DLL_MFUN( KNN2_predict ); CK_DLL_MFUN( KNN2_search ); CK_DLL_MFUN( KNN2_search2 ); CK_DLL_MFUN( KNN2_search3 ); CK_DLL_MFUN( KNN2_weigh ); // offset static t_CKUINT KNN_offset_data = 0; static t_CKUINT KNN2_offset_data = 0; // 1.4.2.0 (yikai) added HMM CK_DLL_CTOR( HMM_ctor ); CK_DLL_DTOR( HMM_dtor ); CK_DLL_MFUN( HMM_init ); CK_DLL_MFUN( HMM_train ); CK_DLL_MFUN( HMM_generate ); // offset static t_CKUINT HMM_offset_data = 0; // 1.4.2.1 (yikai) added Word2Vec CK_DLL_CTOR( Word2Vec_ctor ); CK_DLL_DTOR( Word2Vec_dtor ); CK_DLL_MFUN( Word2Vec_load ); CK_DLL_MFUN( Word2Vec_load2 ); CK_DLL_MFUN( Word2Vec_getMostSimilarByWord ); CK_DLL_MFUN( Word2Vec_getMostSimilarByVector ); CK_DLL_MFUN( Word2Vec_getVector ); CK_DLL_MFUN( Word2Vec_getDictionarySize ); CK_DLL_MFUN( Word2Vec_getDictionaryDim ); CK_DLL_MFUN( Word2Vec_getUseKDTree ); CK_DLL_MFUN( Word2Vec_getDimMinMax ); CK_DLL_MFUN( Word2Vec_contains ); // offset static t_CKUINT Word2Vec_offset_data = 0; // 1.4.2.0 (yikai) added PCA CK_DLL_CTOR( PCA_ctor ); CK_DLL_DTOR( PCA_dtor ); CK_DLL_SFUN( PCA_reduce ); // offset static t_CKUINT PCA_offset_data = 0; // 1.4.2.1 (yikai) added Wekinator CK_DLL_CTOR( Wekinator_ctor ); CK_DLL_DTOR( Wekinator_dtor ); CK_DLL_MFUN( Wekinator_set_num_inputs ); CK_DLL_MFUN( Wekinator_get_num_inputs ); CK_DLL_MFUN( Wekinator_set_num_outputs ); CK_DLL_MFUN( Wekinator_get_num_outputs ); CK_DLL_MFUN( Wekinator_set_task_type ); CK_DLL_MFUN( Wekinator_get_task_type ); CK_DLL_MFUN( Wekinator_get_task_type_name ); CK_DLL_MFUN( Wekinator_set_property ); CK_DLL_MFUN( Wekinator_set_property1 ); CK_DLL_MFUN( Wekinator_get_property_int ); CK_DLL_MFUN( Wekinator_get_property_float ); CK_DLL_MFUN( Wekinator_set_model_type ); CK_DLL_MFUN( Wekinator_get_model_type ); CK_DLL_MFUN( Wekinator_get_model_type_name ); CK_DLL_MFUN( Wekinator_set_output_property ); CK_DLL_MFUN( Wekinator_get_output_property_int ); CK_DLL_MFUN( Wekinator_set_output_property1 ); CK_DLL_MFUN( Wekinator_set_output_property2 ); CK_DLL_MFUN( Wekinator_get_output_property_int1 ); CK_DLL_MFUN( Wekinator_get_output_property_float ); CK_DLL_MFUN( Wekinator_set_output_property3 ); CK_DLL_MFUN( Wekinator_get_output_property ); CK_DLL_MFUN( Wekinator_set_inputs ); CK_DLL_MFUN( Wekinator_set_outputs ); CK_DLL_MFUN( Wekinator_randomize_outputs ); CK_DLL_MFUN( Wekinator_get_num_obs ); CK_DLL_MFUN( Wekinator_get_num_obs1 ); CK_DLL_MFUN( Wekinator_get_obs ); CK_DLL_MFUN( Wekinator_get_obs1 ); CK_DLL_MFUN( Wekinator_clear_obs ); CK_DLL_MFUN( Wekinator_clear_obs1 ); CK_DLL_MFUN( Wekinator_save ); CK_DLL_MFUN( Wekinator_load ); CK_DLL_MFUN( Wekinator_export_obs ); CK_DLL_MFUN( Wekinator_export_obs1 ); CK_DLL_MFUN( Wekinator_import_obs ); CK_DLL_MFUN( Wekinator_clear_all_obs ); CK_DLL_MFUN( Wekinator_clear_all_obs1 ); CK_DLL_MFUN( Wekinator_set_all_record_status ); CK_DLL_MFUN( Wekinator_set_output_record_status ); CK_DLL_MFUN( Wekinator_get_output_record_status ); CK_DLL_MFUN( Wekinator_set_all_run_status ); CK_DLL_MFUN( Wekinator_set_output_run_status ); CK_DLL_MFUN( Wekinator_get_output_run_status ); CK_DLL_MFUN( Wekinator_add ); CK_DLL_MFUN( Wekinator_add1 ); CK_DLL_MFUN( Wekinator_add2 ); CK_DLL_MFUN( Wekinator_next_round ); CK_DLL_MFUN( Wekinator_train ); CK_DLL_MFUN( Wekinator_predict ); CK_DLL_MFUN( Wekinator_delete_last_round ); CK_DLL_MFUN( Wekinator_clear ); CK_DLL_MFUN( Wekinator_get_round ); CK_DLL_MFUN( Wekinator_get_all_record_status ); CK_DLL_MFUN( Wekinator_get_all_run_status ); // offset static t_CKUINT Wekinator_offset_data = 0; // 1.4.2.0 (yikai) added MLP CK_DLL_CTOR( MLP_ctor ); CK_DLL_DTOR( MLP_dtor ); CK_DLL_MFUN( MLP_init ); CK_DLL_MFUN( MLP_init2 ); CK_DLL_MFUN( MLP_init3 ); CK_DLL_MFUN( MLP_train ); CK_DLL_MFUN( MLP_train2 ); CK_DLL_MFUN( MLP_predict ); CK_DLL_MFUN( MLP_get_weights ); CK_DLL_MFUN( MLP_get_biases ); CK_DLL_MFUN( MLP_get_gradients ); CK_DLL_MFUN( MLP_get_activations ); CK_DLL_MFUN( MLP_forward ); CK_DLL_MFUN( MLP_backprop ); CK_DLL_MFUN( MLP_save ); CK_DLL_MFUN( MLP_load ); CK_DLL_SFUN( MLP_shuffle ); // offset static t_CKUINT MLP_offset_data = 0; //----------------------------------------------------------------------------- // name: libai_query() // desc: add class definition to chuck internal type system //----------------------------------------------------------------------------- DLL_QUERY libai_query( Chuck_DL_Query * QUERY ) { // get environment reference Chuck_Env * env = QUERY->env(); // set module name QUERY->setname( QUERY, "AI" ); // model type // add class QUERY->begin_class( QUERY, "AI", "Object" ); // add documentatiopn QUERY->doc_class( QUERY, "AI class library." ); // add svar QUERY->add_svar( QUERY, "int", "MLP", TRUE, &g_mt_mlp ); QUERY->doc_var( QUERY, "Model type: Multi-layer perceptron" ); QUERY->add_svar( QUERY, "int", "KNN", TRUE, &g_mt_knn ); QUERY->doc_var( QUERY, "Model type: K-nearest neighbor" ); QUERY->add_svar( QUERY, "int", "SVM", TRUE, &g_mt_svm ); QUERY->doc_var( QUERY, "Model type: Support vector machine" ); QUERY->add_svar( QUERY, "int", "LR", TRUE, &g_mt_lr ); QUERY->doc_var( QUERY, "Model type: Logistic regression" ); QUERY->add_svar( QUERY, "int", "PR", TRUE, &g_mt_pr ); QUERY->doc_var( QUERY, "Model type: Polynomial regression" ); QUERY->add_svar( QUERY, "int", "DT", TRUE, &g_mt_dt ); QUERY->doc_var( QUERY, "Model type: Decision tree" ); QUERY->add_svar( QUERY, "int", "BOOST", TRUE, &g_mt_boost ); QUERY->doc_var( QUERY, "Model type: Boosting" ); QUERY->add_svar( QUERY, "int", "NAIVEBAYES", TRUE, &g_mt_naivebayes ); QUERY->doc_var( QUERY, "Model type: Naive Bayes" ); QUERY->add_svar( QUERY, "int", "ReLU", TRUE, &g_at_relu ); QUERY->doc_var( QUERY, "Activation type: ReLU" ); QUERY->add_svar( QUERY, "int", "Sigmoid", TRUE, &g_at_sigmoid ); QUERY->doc_var( QUERY, "Activation type: Sigmoid" ); QUERY->add_svar( QUERY, "int", "Tanh", TRUE, &g_at_tanh ); QUERY->doc_var( QUERY, "Activation type: Tanh" ); QUERY->add_svar( QUERY, "int", "Softmax", TRUE, &g_at_softmax ); QUERY->doc_var( QUERY, "Activation type: Softmax" ); QUERY->add_svar( QUERY, "int", "Linear", TRUE, &g_at_linear ); QUERY->doc_var( QUERY, "Activation type: Linear" ); QUERY->add_svar( QUERY, "int", "Regression", TRUE, &g_tt_regression ); QUERY->doc_var( QUERY, "Task type: Regression" ); QUERY->add_svar( QUERY, "int", "Classification", TRUE, &g_tt_classification ); QUERY->doc_var( QUERY, "Task type: Classification" ); // done QUERY->end_class( QUERY ); // function Chuck_DL_Func * func = NULL; // documentation string std::string doc; //--------------------------------------------------------------------- // init as base class: SVM // 1.4.1.2 added by Yikai Li, Fall 2022 //--------------------------------------------------------------------- // doc string doc = "a support vector machine (SVM) utility trains a model and predicts output based on new input"; // begin class definition if( !type_engine_import_class_begin( env, "SVM", "Object", env->global(), SVM_ctor, SVM_dtor, doc.c_str() ) ) return FALSE; // data offset SVM_offset_data = type_engine_import_mvar( env, "int", "@SVM_data", FALSE ); if( SVM_offset_data == CK_INVALID_OFFSET ) goto error; // train func = make_new_mfun( "int", "train", SVM_train ); func->add_arg( "float[][]", "x" ); func->add_arg( "float[][]", "y" ); func->doc = "Train the SVM model with the given samples 'x' and 'y'."; if( !type_engine_import_mfun( env, func ) ) goto error; // predict func = make_new_mfun( "int", "predict", SVM_predict ); func->add_arg( "float[]", "x" ); func->add_arg( "float[]", "y" ); func->doc = "Predict the output 'y' given the input 'x'."; if( !type_engine_import_mfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); //--------------------------------------------------------------------- // init as base class: KNN // 1.4.1.2 added by Yikai Li, Fall 2022 //--------------------------------------------------------------------- // doc string doc = "a basic k-NN utility that searches for k nearest neighbors from a set of observations / feature vectors. (Also see KNN2. The differrence between KNN and KNN2 is that KNN does not deal with labels whereas KNN2 is designed to work with labels.)"; // begin class definition if( !type_engine_import_class_begin( env, "KNN", "Object", env->global(), KNN_ctor, KNN_dtor, doc.c_str() ) ) return FALSE; // data offset KNN_offset_data = type_engine_import_mvar( env, "int", "@KNN_data", FALSE ); if( KNN_offset_data == CK_INVALID_OFFSET ) goto error; // train func = make_new_mfun( "int", "train", KNN_train ); func->add_arg( "float[][]", "x" ); func->doc = "Train the KNN model with the given observations 'x'"; if( !type_engine_import_mfun( env, func ) ) goto error; // search func = make_new_mfun( "void", "search", KNN_search ); func->add_arg( "float[]", "query" ); func->add_arg( "int", "k" ); func->add_arg( "int[]", "indices" ); func->doc = "Search for the 'k' nearest neighbors of 'query' and return their corresponding indices."; if( !type_engine_import_mfun( env, func ) ) goto error; // search func = make_new_mfun( "void", "search", KNN_search2 ); func->add_arg( "float[]", "query" ); func->add_arg( "int", "k" ); func->add_arg( "int[]", "indices" ); func->add_arg( "float[][]", "observations" ); func->doc = "Search for the 'k' nearest neighbors of 'query' and return their corresponding indices and observations"; if( !type_engine_import_mfun( env, func ) ) goto error; // weigh func = make_new_mfun( "void", "weigh", KNN_weigh ); func->add_arg( "float[]", "weights" ); func->doc = "Set the weights for each dimension in the data."; if( !type_engine_import_mfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); //--------------------------------------------------------------------- // init as base class: KNN2 // 1.4.1.2 added by Yikai Li, Fall 2022 //--------------------------------------------------------------------- // doc string doc = "a k-NN utility that predicts probabilities of class membership based on distances from a test input to its k nearest neighbors. (Also see KNN. The differrence between KNN and KNN2 is that KNN does not deal with labels whereas KNN2 is designed to work with labels.)"; // begin class definition if( !type_engine_import_class_begin( env, "KNN2", "Object", env->global(), KNN2_ctor, KNN2_dtor, doc.c_str() ) ) return FALSE; // data offset KNN_offset_data = type_engine_import_mvar( env, "int", "@KNN2_data", FALSE ); if( KNN_offset_data == CK_INVALID_OFFSET ) goto error; // train func = make_new_mfun( "int", "train", KNN2_train ); func->add_arg( "float[][]", "x" ); func->add_arg( "int[]", "labels" ); func->doc = "Train the KNN model with the given observations 'x' and corresponding labels."; if( !type_engine_import_mfun( env, func ) ) goto error; // predict func = make_new_mfun( "int", "predict", KNN2_predict ); func->add_arg( "float[]", "query" ); func->add_arg( "int", "k" ); func->add_arg( "float[]", "prob" ); func->doc = "Predict the output probabilities 'prob' given unlabeled test input 'query' based on distances to 'k' nearest neighbors."; if( !type_engine_import_mfun( env, func ) ) goto error; // search func = make_new_mfun( "void", "search", KNN2_search ); func->add_arg( "float[]", "query" ); func->add_arg( "int", "k" ); func->add_arg( "int[]", "labels" ); func->doc = "Search for the 'k' nearest neighbors of 'query' and return their labels."; if( !type_engine_import_mfun( env, func ) ) goto error; // search func = make_new_mfun( "void", "search", KNN2_search2 ); func->add_arg( "float[]", "query" ); func->add_arg( "int", "k" ); func->add_arg( "int[]", "labels" ); func->add_arg( "int[]", "indices" ); func->doc = "Search for the 'k' nearest neighbors of 'query' and return their labels and indices."; if( !type_engine_import_mfun( env, func ) ) goto error; // search func = make_new_mfun( "void", "search", KNN2_search3 ); func->add_arg( "float[]", "query" ); func->add_arg( "int", "k" ); func->add_arg( "int[]", "labels" ); func->add_arg( "int[]", "indices" ); func->add_arg( "float[][]", "observations" ); func->doc = "Search for the 'k' nearest neighbors of 'query' and return their labels, indices, and observations."; if( !type_engine_import_mfun( env, func ) ) goto error; // weigh func = make_new_mfun( "void", "weigh", KNN2_weigh ); func->add_arg( "float[]", "weights" ); func->doc = "Set the weights for each dimension in the data."; if( !type_engine_import_mfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); //--------------------------------------------------------------------- // init as base class: HMM // 1.4.1.2 added by Yikai Li, Fall 2022 //--------------------------------------------------------------------- // doc string doc = "a hidden markov model (HMM) utility that generates a sequence of observations based on the training data."; // begin class definition if( !type_engine_import_class_begin( env, "HMM", "Object", env->global(), HMM_ctor, HMM_dtor, doc.c_str() ) ) return FALSE; // data offset HMM_offset_data = type_engine_import_mvar( env, "int", "@HMM_data", FALSE ); if( HMM_offset_data == CK_INVALID_OFFSET ) goto error; // init func = make_new_mfun( "int", "load", HMM_init ); func->add_arg( "float[]", "initiailDistribution" ); func->add_arg( "float[][]", "transitionMatrix" ); func->add_arg( "float[][]", "emissionMatrix" ); func->doc = "Initialize the HMM model with the given initial state distribution, transition matrix, and emission matrix."; if( !type_engine_import_mfun( env, func ) ) goto error; // train func = make_new_mfun( "int", "train", HMM_train ); func->add_arg( "int", "numStates" ); func->add_arg( "int", "numEmissions" ); func->add_arg( "int[]", "observations" ); func->doc = "Train the HMM model with the given observations."; if( !type_engine_import_mfun( env, func ) ) goto error; // generate func = make_new_mfun( "int", "generate", HMM_generate ); func->add_arg( "int", "length" ); func->add_arg( "int[]", "output" ); func->doc = "Generate a sequence of observations of the given length."; if( !type_engine_import_mfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); //--------------------------------------------------------------------- // init as base class: Word2Vec // 1.4.2.1 added by Yikai Li, Spring 2023 //--------------------------------------------------------------------- // doc string doc = "a word embeddings utility that maps words to vectors; can load a model and perform similarity retrieval."; // begin class definition if( !type_engine_import_class_begin( env, "Word2Vec", "Object", env->global(), Word2Vec_ctor, Word2Vec_dtor, doc.c_str() ) ) return FALSE; // data offset Word2Vec_offset_data = type_engine_import_mvar( env, "int", "@Word2Vec_data", FALSE ); if( Word2Vec_offset_data == CK_INVALID_OFFSET ) goto error; // load func = make_new_mfun( "int", "load", Word2Vec_load ); func->add_arg( "string", "path" ); func->doc = "Load pre-trained word embedding model from the given path."; if( !type_engine_import_mfun( env, func ) ) goto error; // load func = make_new_mfun( "int", "load", Word2Vec_load2 ); func->add_arg( "string", "path" ); func->add_arg( "int", "useKDTreeDim" ); func->doc = "Load pre-trained word embedding model from the given path; will use KDTree for similarity searches if the data dimension is less than or equal to 'useKDTreeDim'. Set 'useKDTreeDim' to 0 to use linear (brute force) similarity search; set 'useKDTreeDim' to less than 0 to always use KDTree."; if( !type_engine_import_mfun( env, func ) ) goto error; // getMostSimilar func = make_new_mfun( "int", "getSimilar", Word2Vec_getMostSimilarByWord ); func->add_arg( "string", "word" ); func->add_arg( "int", "k" ); func->add_arg( "string[]", "output" ); func->doc = "Get the k most similar words to the given word; return false if 'word' is not in model."; if( !type_engine_import_mfun( env, func ) ) goto error; // getByVector func = make_new_mfun( "int", "getSimilar", Word2Vec_getMostSimilarByVector ); func->add_arg( "float[]", "vec" ); func->add_arg( "int", "k" ); func->add_arg( "string[]", "output" ); func->doc = "Get the k most similar words to the given vector."; if( !type_engine_import_mfun( env, func ) ) goto error; // getVector func = make_new_mfun( "int", "getVector", Word2Vec_getVector ); func->add_arg( "string", "word" ); func->add_arg( "float[]", "output" ); func->doc = "Get the vector of the given word; returns false if 'word' is not in model."; if( !type_engine_import_mfun( env, func ) ) goto error; // contains func = make_new_mfun( "int", "contains", Word2Vec_contains ); func->add_arg( "string", "word" ); func->doc = "Query if 'word' is in the current model."; if( !type_engine_import_mfun( env, func ) ) goto error; // size func = make_new_mfun( "int", "size", Word2Vec_getDictionarySize ); func->doc = "Get number of words in dictionary."; if( !type_engine_import_mfun( env, func ) ) goto error; // dim func = make_new_mfun( "int", "dim", Word2Vec_getDictionaryDim ); func->doc = "Get number of dimensions for word embedding."; if( !type_engine_import_mfun( env, func ) ) goto error; // dim func = make_new_mfun( "void", "minMax", Word2Vec_getDimMinMax ); func->add_arg( "float[]", "mins" ); func->add_arg( "float[]", "maxs" ); func->doc = "Retrieve the minimums and maximums for each dimension."; if( !type_engine_import_mfun( env, func ) ) goto error; // dim func = make_new_mfun( "int", "useKDTree", Word2Vec_getUseKDTree ); func->doc = "Get whether a KDTree is used for similarity search."; if( !type_engine_import_mfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); //--------------------------------------------------------------------- // init as base class: PCA // 1.4.1.2 added by Yikai Li, Fall 2022 //--------------------------------------------------------------------- // doc string doc = "a principle component analysis (PCA) utility, commonly used for dimensionality reduction."; // begin class definition if( !type_engine_import_class_begin( env, "PCA", "Object", env->global(), PCA_ctor, PCA_dtor, doc.c_str() ) ) return FALSE; // data offset PCA_offset_data = type_engine_import_mvar( env, "int", "@PCA_data", FALSE ); if( PCA_offset_data == CK_INVALID_OFFSET ) goto error; // transform func = make_new_sfun( "void", "reduce", PCA_reduce ); func->add_arg( "float[][]", "input" ); func->add_arg( "int", "D" ); func->add_arg( "float[][]", "output" ); func->doc = "dimension-reduce 'input' (NxM) to 'output' (NxD) as the projection of the input data onto its first 'D' principle components"; if( !type_engine_import_sfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); //--------------------------------------------------------------------- // init as base class: Wekinator // 1.4.2.1 added by Yikai Li, Winter 2023 //--------------------------------------------------------------------- // doc string doc = "a Wekinator utility that maps input vectors to output vectors, commonly used for interactive machine learning combining human-computer interaction and ML. Based on Rebecca Fiebrink's Wekinator framework."; // begin class definition if( !type_engine_import_class_begin( env, "Wekinator", "Object", env->global(), Wekinator_ctor, Wekinator_dtor, doc.c_str() ) ) return FALSE; // data offset Wekinator_offset_data = type_engine_import_mvar( env, "float", "@Wekinator_data", FALSE ); if( Wekinator_offset_data == CK_INVALID_OFFSET ) goto error; // set_num_inputs func = make_new_mfun( "int", "inputDims", Wekinator_set_num_inputs ); func->add_arg( "int", "n" ); func->doc = "Set the number of input dimensions to Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_num_inputs func = make_new_mfun( "int", "inputDims", Wekinator_get_num_inputs ); func->doc = "Get the number of input dimensions to Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_num_outputs func = make_new_mfun( "int", "outputDims", Wekinator_set_num_outputs ); func->add_arg( "int", "n" ); func->doc = "Set the number of output dimensions to Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_num_outputs func = make_new_mfun( "int", "outputDims", Wekinator_get_num_outputs ); func->doc = "Get the number of output dimensions to Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_task_type func = make_new_mfun( "int", "taskType", Wekinator_set_task_type ); func->add_arg( "int", "task_type" ); func->doc = "Set the task type of the Wekinator. Options: AI.Regression, AI.Classification"; if( !type_engine_import_mfun( env, func ) ) goto error; // get_task_type func = make_new_mfun( "int", "taskType", Wekinator_get_task_type ); func->doc = "Get the task type id of the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_task_type_name func = make_new_mfun( "string", "taskTypeName", Wekinator_get_task_type_name ); func->doc = "Get the task type name of the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_property func = make_new_mfun( "void", "setProperty", Wekinator_set_property ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->add_arg( "int", "property_value" ); func->doc = "Set the property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_property1 func = make_new_mfun( "void", "setProperty", Wekinator_set_property1 ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->add_arg( "float", "property_value" ); func->doc = "Set the property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_property_int func = make_new_mfun( "int", "getPropertyInt", Wekinator_get_property_int ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->doc = "Get the property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_property_float func = make_new_mfun( "float", "getPropertyFloat", Wekinator_get_property_float ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->doc = "Get the property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_model_type func = make_new_mfun( "int", "modelType", Wekinator_set_model_type ); func->add_arg( "int", "model_type" ); func->doc = "Set the model type of the Wekinator. Options: AI.Regression: AI.MLP, AI.LR, AI.Classification: AI.KNN, AI.SVM, AI.DT"; if( !type_engine_import_mfun( env, func ) ) goto error; // get_model_type func = make_new_mfun( "int", "modelType", Wekinator_get_model_type ); func->doc = "Get the model type id of the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_model_type_name func = make_new_mfun( "string", "modelTypeName", Wekinator_get_model_type_name ); func->doc = "Get the model type name of the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_output_property func = make_new_mfun( "void", "setOutputProperty", Wekinator_set_output_property ); func->add_arg( "int", "output_index" ); func->add_arg( "string", "property_name" ); func->add_arg( "int", "property_value" ); func->doc = "Set the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_output_property_int func = make_new_mfun( "int", "getOutputPropertyInt", Wekinator_get_output_property_int ); func->add_arg( "int", "output_index" ); func->add_arg( "string", "property_name" ); func->doc = "Get the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_output_property1 func = make_new_mfun( "void", "setOutputProperty", Wekinator_set_output_property1 ); func->add_arg( "int", "output_index" ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->add_arg( "int", "property_value" ); func->doc = "Set the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_output_property2 func = make_new_mfun( "void", "setOutputProperty", Wekinator_set_output_property2 ); func->add_arg( "int", "output_index" ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->add_arg( "float", "property_value" ); func->doc = "Set the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_output_property_int1 func = make_new_mfun( "int", "getOutputPropertyInt", Wekinator_get_output_property_int1 ); func->add_arg( "int", "output_index" ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->doc = "Get the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_output_property_float func = make_new_mfun( "float", "getOutputPropertyFloat", Wekinator_get_output_property_float ); func->add_arg( "int", "output_index" ); func->add_arg( "int", "property_type" ); func->add_arg( "string", "property_name" ); func->doc = "Get the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_output_property3 func = make_new_mfun( "void", "setOutputProperty", Wekinator_set_output_property3 ); func->add_arg( "int", "output_index" ); func->add_arg( "string", "property_name" ); func->add_arg( "int[]", "property_value" ); func->doc = "Set the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_output_property func = make_new_mfun( "void", "getOutputProperty", Wekinator_get_output_property ); func->add_arg( "int", "output_index" ); func->add_arg( "string", "property_name" ); func->add_arg( "int[]", "property_value" ); func->doc = "Get the output property of the Wekinator. See the Wekinator documentation for more information."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_inputs func = make_new_mfun( "void", "input", Wekinator_set_inputs ); func->add_arg( "float[]", "inputs" ); func->doc = "Set the inputs of the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_outputs func = make_new_mfun( "void", "output", Wekinator_set_outputs ); func->add_arg( "float[]", "outputs" ); func->doc = "Set the outputs of the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // randomize_outputs func = make_new_mfun( "void", "randomizeOutputs", Wekinator_randomize_outputs ); func->doc = "Randomize the outputs of the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_num_obs func = make_new_mfun( "int", "numObs", Wekinator_get_num_obs ); func->doc = "Get the number of observations in the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_num_obs1 func = make_new_mfun( "int", "numObs", Wekinator_get_num_obs1 ); func->add_arg( "int", "output_index" ); func->doc = "Get the number of observations for the specified output in the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_obs func = make_new_mfun( "void", "getObs", Wekinator_get_obs ); func->add_arg( "float[][]", "obs" ); func->doc = "Get the observations in the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_obs1 func = make_new_mfun( "void", "getObs", Wekinator_get_obs1 ); func->add_arg( "int", "output_index" ); func->add_arg( "float[][]", "obs" ); func->doc = "Get the observations for the specified output in the Wekinator."; if( !type_engine_import_mfun( env, func ) ) goto error; // clear_obs func = make_new_mfun( "void", "clearObs", Wekinator_clear_obs ); func->add_arg( "int", "lo" ); func->add_arg( "int", "hi" ); func->doc = "Clear the observations by id range."; if( !type_engine_import_mfun( env, func ) ) goto error; // clear_obs1 func = make_new_mfun( "void", "clearObs", Wekinator_clear_obs1 ); func->add_arg( "int", "output_index" ); func->add_arg( "int", "lo" ); func->add_arg( "int", "hi" ); func->doc = "Clear the observations by id range for the specified output."; if( !type_engine_import_mfun( env, func ) ) goto error; // save func = make_new_mfun( "void", "save", Wekinator_save ); func->add_arg( "string", "filename" ); func->doc = "Save the Wekinator to a file."; if( !type_engine_import_mfun( env, func ) ) goto error; // load func = make_new_mfun( "void", "load", Wekinator_load ); func->add_arg( "string", "filename" ); func->doc = "Load the Wekinator from a file."; if( !type_engine_import_mfun( env, func ) ) goto error; // export_obs func = make_new_mfun( "void", "exportObs", Wekinator_export_obs ); func->add_arg( "string", "filename" ); func->doc = "Export the observations to a file."; if( !type_engine_import_mfun( env, func ) ) goto error; // export_obs1 func = make_new_mfun( "void", "exportObs", Wekinator_export_obs1 ); func->add_arg( "int", "output_index" ); func->add_arg( "string", "filename" ); func->doc = "Export the observations for the specified output to a file."; if( !type_engine_import_mfun( env, func ) ) goto error; // import_obs func = make_new_mfun( "void", "importObs", Wekinator_import_obs ); func->add_arg( "string", "filename" ); func->doc = "Import the observations from a file."; if( !type_engine_import_mfun( env, func ) ) goto error; // clear_all_obs func = make_new_mfun( "void", "clearAllObs", Wekinator_clear_all_obs ); func->doc = "Clear all observations."; if( !type_engine_import_mfun( env, func ) ) goto error; // clear_all_obs1 func = make_new_mfun( "void", "clearAllObs", Wekinator_clear_all_obs1 ); func->add_arg( "int", "output_index" ); func->doc = "Clear all observations for the specified output."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_all_record_status func = make_new_mfun( "void", "setAllRecordStatus", Wekinator_set_all_record_status ); func->add_arg( "int", "status" ); func->doc = "Set the record status for all outputs."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_output_record_status func = make_new_mfun( "void", "setOutputRecordStatus", Wekinator_set_output_record_status ); func->add_arg( "int", "output_index" ); func->add_arg( "int", "status" ); func->doc = "Set the record status for the specified output."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_output_record_status func = make_new_mfun( "int", "getOutputRecordStatus", Wekinator_get_output_record_status ); func->add_arg( "int", "output_index" ); func->doc = "Get the record status for the specified output."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_all_run_status func = make_new_mfun( "void", "setAllRunStatus", Wekinator_set_all_run_status ); func->add_arg( "int", "status" ); func->doc = "Set the run status for all outputs."; if( !type_engine_import_mfun( env, func ) ) goto error; // set_output_run_status func = make_new_mfun( "void", "setOutputRunStatus", Wekinator_set_output_run_status ); func->add_arg( "int", "output_index" ); func->add_arg( "int", "status" ); func->doc = "Set the run status for the specified output."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_output_run_status func = make_new_mfun( "int", "getOutputRunStatus", Wekinator_get_output_run_status ); func->add_arg( "int", "output_index" ); func->doc = "Get the run status for the specified output."; if( !type_engine_import_mfun( env, func ) ) goto error; // add func = make_new_mfun( "void", "add", Wekinator_add ); func->doc = "Add current inputs and outputs to the observations."; if( !type_engine_import_mfun( env, func ) ) goto error; // add1 func = make_new_mfun( "void", "add", Wekinator_add1 ); func->add_arg( "float[]", "inputs" ); func->add_arg( "float[]", "outputs" ); func->doc = "Add given inputs and outputs to the observations."; if( !type_engine_import_mfun( env, func ) ) goto error; // add2 func = make_new_mfun( "void", "add", Wekinator_add2 ); func->add_arg( "int", "output_index" ); func->add_arg( "float[]", "inputs" ); func->add_arg( "float[]", "outputs" ); func->doc = "Add given inputs and outputs to the observations for the specified output."; if( !type_engine_import_mfun( env, func ) ) goto error; // next_round func = make_new_mfun( "void", "nextRound", Wekinator_next_round ); func->doc = "Bump the recording round."; if( !type_engine_import_mfun( env, func ) ) goto error; // train func = make_new_mfun( "void", "train", Wekinator_train ); func->doc = "Train models for all outputs."; if( !type_engine_import_mfun( env, func ) ) goto error; // predict func = make_new_mfun( "void", "predict", Wekinator_predict ); func->add_arg( "float[]", "inputs" ); func->add_arg( "float[]", "outputs" ); func->doc = "Predict outputs for the given inputs."; if( !type_engine_import_mfun( env, func ) ) goto error; // delete_last_round func = make_new_mfun( "void", "deleteLastRound", Wekinator_delete_last_round ); func->doc = "Delete the last round of observations."; if( !type_engine_import_mfun( env, func ) ) goto error; // clear func = make_new_mfun( "void", "clear", Wekinator_clear ); func->doc = "Clear everything except the global properties."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_round func = make_new_mfun( "int", "getRound", Wekinator_get_round ); func->doc = "Get the current recording round."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_all_record_status func = make_new_mfun( "int", "getAllRecordStatus", Wekinator_get_all_record_status ); func->doc = "Get the record status for all outputs."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_all_run_status func = make_new_mfun( "int", "getAllRunStatus", Wekinator_get_all_run_status ); func->doc = "Get the run status for all outputs."; if( !type_engine_import_mfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); //--------------------------------------------------------------------- // init as base class: MLP // 1.4.2.1 added by Yikai Li, Winter 2023 //--------------------------------------------------------------------- // doc string doc = "a multilayer perceptron (MLP)--a basic artificial neural network--that maps an input layer to an output layer across a number of fully-connected hidden layers. This implemention can be trained either 1) by using one of the comprehensive .train() functions OR 2) by iteratively calling .forward() and .backprop() for each input-output observation, and using .shuffle() for each epoch. Commonly used for regression or classification."; // begin class definition if( !type_engine_import_class_begin( env, "MLP", "Object", env->global(), MLP_ctor, MLP_dtor, doc.c_str() ) ) return FALSE; // data offset MLP_offset_data = type_engine_import_mvar( env, "float", "@MLP_data", FALSE ); if( MLP_offset_data == CK_INVALID_OFFSET ) goto error; // init func = make_new_mfun( "void", "init", MLP_init ); func->add_arg( "int[]", "nodesPerLayer" ); func->doc = "Initialize the MLP with the given number of nodes per layer."; if( !type_engine_import_mfun( env, func ) ) goto error; // init2 func = make_new_mfun( "void", "init", MLP_init2 ); func->add_arg( "int[]", "nodesPerLayer" ); func->add_arg( "int[]", "activationPerLayer" ); func->doc = "Initialize the MLP with the given number of nodes per layer and the given activation function per layer, as specified in 'activationPerLayer' (options: AI.Linear, AI.Sigmoid, AI.ReLU, AI.Tanh, or AI.Softmax)."; if( !type_engine_import_mfun( env, func ) ) goto error; // init3 func = make_new_mfun( "void", "init", MLP_init3 ); func->add_arg( "int[]", "nodesPerLayer" ); func->add_arg( "int", "activationFunction" ); func->doc = "Initialize the MLP with the given number of nodes per layer and the given activation function for all layers (options: AI.Linear, AI.Sigmoid, AI.ReLU, AI.Tanh, or AI.Softmax)."; if( !type_engine_import_mfun( env, func ) ) goto error; // train func = make_new_mfun( "void", "train", MLP_train ); func->add_arg( "float[][]", "inputs" ); func->add_arg( "float[][]", "outputs" ); func->doc = "Train the MLP with the given input and output observations with default learning rate=.01 and epochs=100. (Also see MLP.train(inputs,outputs,learningRate,epochs).)"; if( !type_engine_import_mfun( env, func ) ) goto error; // train2 func = make_new_mfun( "void", "train", MLP_train2 ); func->add_arg( "float[][]", "inputs" ); func->add_arg( "float[][]", "outputs" ); func->add_arg( "float", "learningRate" ); func->add_arg( "int", "epochs" ); func->doc = "Train the MLP with the given input and output observations, the learning rate, and number of epochs."; if( !type_engine_import_mfun( env, func ) ) goto error; // predict func = make_new_mfun( "int", "predict", MLP_predict ); func->add_arg( "float[]", "input" ); func->add_arg( "float[]", "output" ); func->doc = "Predict the output layer from an input layer."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_weights func = make_new_mfun( "void", "getWeights", MLP_get_weights ); func->add_arg( "int", "layer" ); func->add_arg( "float[][]", "weights" ); func->doc = "Get the weights of the given layer."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_biases func = make_new_mfun( "void", "getBiases", MLP_get_biases ); func->add_arg( "int", "layer" ); func->add_arg( "float[]", "biases" ); func->doc = "Get the biases of the given layer."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_gradients func = make_new_mfun( "void", "getGradients", MLP_get_gradients ); func->add_arg( "int", "layer" ); func->add_arg( "float[]", "gradients" ); func->doc = "Get the gradients of the given layer, after a manual .backprop()."; if( !type_engine_import_mfun( env, func ) ) goto error; // get_activations func = make_new_mfun( "void", "getActivations", MLP_get_activations ); func->add_arg( "int", "layer" ); func->add_arg( "float[]", "activations" ); func->doc = "Get the activations of the given layer, after a manual .forward()."; if( !type_engine_import_mfun( env, func ) ) goto error; // forward func = make_new_mfun( "void", "forward", MLP_forward ); func->add_arg( "float[]", "input" ); func->doc = "(Manually) forward-propagate the input vector through the network."; if( !type_engine_import_mfun( env, func ) ) goto error; // backprop func = make_new_mfun( "void", "backprop", MLP_backprop ); func->add_arg( "float[]", "output" ); func->add_arg( "float", "learningRate" ); func->doc = "(Manually) backpropagate from the output layer, for a single input-output observation; compute the gradient of the loss function with respect to the weights in the network, one layer at a time."; if( !type_engine_import_mfun( env, func ) ) goto error; // shuffle func = make_new_sfun( "void", "shuffle", MLP_shuffle ); func->add_arg( "float[][]", "X" ); func->add_arg( "float[][]", "Y" ); func->doc = "(Manually) shuffle the given input and output vectors."; if( !type_engine_import_sfun( env, func ) ) goto error; // save func = make_new_mfun( "int", "save", MLP_save ); func->add_arg( "string", "filename" ); func->doc = "Save the MLP model to file."; if( !type_engine_import_mfun( env, func ) ) goto error; // load func = make_new_mfun( "int", "load", MLP_load ); func->add_arg( "string", "filename" ); func->doc = "Load a MLP model from file."; if( !type_engine_import_mfun( env, func ) ) goto error; // end the class import type_engine_import_class_end( env ); return TRUE; error: // end the class import type_engine_import_class_end( env ); return FALSE; } //----------------------------------------------------------------------------- // name: chuck2chai() // desc: chuck array (assuming float[][]) conversion to new chai matrix //----------------------------------------------------------------------------- ChaiMatrixFast * chuck2chai( Chuck_Array4 & array ) { t_CKINT rows = array.size(); Chuck_Array8 * row = (Chuck_Array8 *)array.m_vector[0]; t_CKINT cols = row->size(); ChaiMatrixFast * matrix = new ChaiMatrixFast( rows, cols ); for( t_CKINT i = 0; i < rows; i++ ) { row = (Chuck_Array8 *)array.m_vector[i]; for( t_CKINT j = 0; j < cols; j++ ) { matrix->v( i, j ) = row->m_vector[j]; } } return matrix; } //----------------------------------------------------------------------------- // name: chuck2chai() // desc: chuck array (float []) conversion to new chai vector //----------------------------------------------------------------------------- ChaiVectorFast * chuck2chai( Chuck_Array8 & array ) { t_CKINT size = array.size(); ChaiVectorFast * vector = new ChaiVectorFast( size ); for( t_CKINT i = 0; i < size; i++ ) { vector->v( i ) = array.m_vector[i]; } return vector; } //----------------------------------------------------------------------------- // name: shuffle() // desc: shuffle training data //----------------------------------------------------------------------------- void shuffle( Chuck_Array4 & X, Chuck_Array4 & Y ) { t_CKUINT temp, j; for( t_CKINT i = X.size() - 1; i > 0; i-- ) { j = ck_random() % ( i + 1 ); temp = X.m_vector[i]; X.m_vector[i] = X.m_vector[j]; X.m_vector[j] = temp; temp = Y.m_vector[i]; Y.m_vector[i] = Y.m_vector[j]; Y.m_vector[j] = temp; } } //----------------------------------------------------------------------------- // name: nextline() // desc: read next non-empty or commented line //----------------------------------------------------------------------------- t_CKBOOL nextline( std::ifstream & fin, string & line, t_CKBOOL commentIsHash ) { // skip over empty lines and commented lines (starts with #) do { // get line if( !std::getline( fin, line ) ) { line = ""; return FALSE; } // ltrim leading white spaces line = ltrim( line ); } while( line == "" || ( commentIsHash && line[0] == '#' ) ); return TRUE; } //----------------------------------------------------------------------------- // name: struct SVM_Object // desc: SVM implementation | added 1.4.2.0 (yikai) //----------------------------------------------------------------------------- struct SVM_Object { public: // constructor SVM_Object() { w = NULL; } // destructor ~SVM_Object() { SAFE_DELETE( w ); } // train t_CKINT train( Chuck_Array4 & x_, Chuck_Array4 & y_ ) { ChaiMatrixFast * x = chuck2chai( x_ ); ChaiMatrixFast * y = chuck2chai( y_ ); train( *x, *y, x->xDim() ); SAFE_DELETE( x ); SAFE_DELETE( y ); return 0; } void train( ChaiMatrixFast & x, ChaiMatrixFast & y, t_CKINT n_sample ) { // init SAFE_DELETE( w ); t_CKINT x_dim = x.yDim(), y_dim = y.yDim(); w = new ChaiMatrixFast( x_dim + 1, y_dim ); // compute svm ChaiMatrixFast xtx( x_dim, x_dim ); for( int i = 0; i < x_dim; i++ ) { for( int j = 0; j < x_dim; j++ ) { xtx.v( i, j ) = 0.0; for( int k = 0; k < n_sample; k++ ) { xtx.v( i, j ) += x.v( k, i ) * x.v( k, j ); } } } // inverse matrix ChaiMatrixFast xtx_inv( x_dim, x_dim ); for( int i = 0; i < x_dim; i++ ) { for( int j = 0; j < x_dim; j++ ) { xtx_inv.v( i, j ) = 0.0; if( i == j ) { xtx_inv.v( i, j ) = 1.0; } } } for( int i = 0; i < x_dim; i++ ) { t_CKFLOAT pivot = xtx.v( i, i ); for( int j = 0; j < x_dim; j++ ) { xtx.v( i, j ) /= pivot; xtx_inv.v( i, j ) /= pivot; } } for( int i = 0; i < x_dim; i++ ) { for( int j = 0; j < x_dim; j++ ) { if( i != j ) { t_CKFLOAT factor = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; for( int k = 0; k < x_dim; k++ ) { xtx.v( j, k ) -= factor * xtx.v( i, k ); xtx_inv.v( j, k ) -= factor * xtx_inv.v( i, k ); } } } } // compute w for( int i = 0; i < x_dim; i++ ) { for( int j = 0; j < y_dim; j++ ) { w->v( i, j ) = 0.0; for( int k = 0; k < x_dim; k++ ) { for( int l = 0; l < n_sample; l++ ) { w->v( i, j ) += xtx_inv.v( i, k ) * x.v( l, k ) * y.v( l, j ); } } } } // compute b ChaiVectorFast b( y_dim ); for( int i = 0; i < y_dim; i++ ) { b.v( i ) = 0.0; for( int j = 0; j < n_sample; j++ ) { b.v( i ) += y.v( j, i ); for( int k = 0; k < x_dim; k++ ) { b.v( i ) -= w->v( k, i ) * x.v( j, k ); } } b.v( i ) /= n_sample; w->v( x_dim, i ) = b.v( i ); } } // predict t_CKBOOL predict( Chuck_Array8 & x_, Chuck_Array8 & y_ ) { // init t_CKINT x_dim = x_.size(); t_CKINT y_dim = y_.size(); // copy ChaiVectorFast x( x_dim ); for( t_CKINT i = 0; i < x_dim; i++ ) { x_.get( i, &x.v( i ) ); } // compute ChaiVectorFast y( y_dim ); for( int i = 0; i < y_dim; i++ ) { y.v( i ) = 0.0; for( int j = 0; j < x_dim; j++ ) { y.v( i ) += w->v( j, i ) * x.v( j ); } y.v( i ) += w->v( x_dim, i ); } // copy for( t_CKINT i = 0; i < y_dim; i++ ) { y_.set( i, y.v( i ) ); } return TRUE; } // predict t_CKBOOL predict( ChaiVectorFast & x_, ChaiVectorFast & y_ ) { // init t_CKINT x_dim = x_.size(); t_CKINT y_dim = y_.size(); // compute for( int i = 0; i < y_dim; i++ ) { y_.v( i ) = 0.0; for( int j = 0; j < x_dim; j++ ) { y_.v( i ) += w->v( j, i ) * x_.v( j ); } y_.v( i ) += w->v( x_dim, i ); } return TRUE; } private: ChaiMatrixFast * w; }; //----------------------------------------------------------------------------- // SVM c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( SVM_ctor ) { SVM_Object * svm = new SVM_Object(); OBJ_MEMBER_UINT( SELF, SVM_offset_data ) = (t_CKUINT)svm; } CK_DLL_DTOR( SVM_dtor ) { SVM_Object * svm = (SVM_Object *)OBJ_MEMBER_UINT( SELF, SVM_offset_data ); SAFE_DELETE( svm ); OBJ_MEMBER_UINT( SELF, SVM_offset_data ) = 0; } CK_DLL_MFUN( SVM_train ) { // get object SVM_Object * svm = (SVM_Object *)OBJ_MEMBER_UINT( SELF, SVM_offset_data ); // get args Chuck_Array4 * x = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * y = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // add svm->train( *x, *y ); } CK_DLL_MFUN( SVM_predict ) { // get object SVM_Object * svm = (SVM_Object *)OBJ_MEMBER_UINT( SELF, SVM_offset_data ); // get args Chuck_Array8 * x = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array8 * y = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // add RETURN->v_int = svm->predict( *x, *y ); } //----------------------------------------------------------------------------- // name: struct KNN_Object // desc: k-nearest neighbor implementation | added 1.4.2.0 (yikai) //----------------------------------------------------------------------------- struct KNN_Object { public: // constructor KNN_Object() { X = NULL; Y = NULL; weights = NULL; } // destructor ~KNN_Object() { clear(); } // clear void clear() { SAFE_DELETE( X ); SAFE_DELETE( Y ); SAFE_DELETE( weights ); } // train t_CKINT train( Chuck_Array4 & x_ ) { clear(); // init t_CKINT n_sample = x_.size(); t_CKUINT v; x_.get( 0, &v ); Chuck_Array8 * x_i = (Chuck_Array8 *)v; t_CKINT x_dim = x_i->size(); X = new ChaiMatrixFast( n_sample, x_dim ); weights = new ChaiVectorFast( x_dim ); // copy for( t_CKINT i = 0; i < n_sample; i++ ) { x_.get( i, &v ); x_i = (Chuck_Array8 *)v; for( t_CKINT j = 0; j < x_dim; j++ ) { x_i->get( j, &X->v( i, j ) ); } } // init weights for( t_CKINT i = 0; i < x_dim; i++ ) { weights->v( i ) = 1.0; } return 0; } // train void train( ChaiMatrixFast & x_, t_CKINT size ) { clear(); // init X = new ChaiMatrixFast( size, x_.yDim() ); weights = new ChaiVectorFast( x_.yDim() ); // copy for( t_CKINT i = 0; i < size; i++ ) { for( t_CKINT j = 0; j < x_.yDim(); j++ ) { X->v( i, j ) = x_.v( i, j ); } } // init weights for( t_CKINT i = 0; i < x_.yDim(); i++ ) { weights->v( i ) = 1.0; } return; } // train t_CKINT train( Chuck_Array4 & x_, Chuck_Array4 & y_ ) { clear(); // init t_CKINT n_sample = x_.size(); t_CKUINT v; x_.get( 0, &v ); Chuck_Array8 * x_i = (Chuck_Array8 *)v; t_CKUINT x_dim = x_i->size(); X = new ChaiMatrixFast( n_sample, x_dim ); Y = new ChaiVectorFast( n_sample ); weights = new ChaiVectorFast( x_dim ); // copy for( t_CKINT i = 0; i < n_sample; i++ ) { x_.get( i, &v ); x_i = (Chuck_Array8 *)v; for( t_CKINT j = 0; j < x_dim; j++ ) { x_i->get( j, &X->v( i, j ) ); } y_.get( i, &Y->v( i ) ); } // init weights for( t_CKINT i = 0; i < x_dim; i++ ) { weights->v( i ) = 1.0; } return 0; } void getNearestNeighbors( const vector & query, t_CKINT k, ChaiVectorFast & indices ) { // check query dimension against training dimensions if( query.size() < X->yDim() ) { // warning EM_error3( "k-NN query dimension %d less than training dimension %d; zero-padding...", query.size(), X->yDim() ); } // allocate ChaiVectorFast top_distances( k ); ChaiVectorFast top_indices( k ); // initialize for( t_CKINT i = 0; i < k; i++ ) { top_distances.v( i ) = 1e10; top_indices.v( i ) = 0; } // compute distances t_CKFLOAT distance, diff; for( t_CKINT i = 0; i < X->xDim(); i++ ) { distance = 0.0; for( t_CKINT j = 0; j < X->yDim(); j++ ) { // extra check in case query less than training dimensions diff = ( j < query.size() ? query[j] : 0.0 ) - X->v( i, j ); distance += diff * diff * weights->v( j ); } // check for( t_CKINT j = 0; j < k; j++ ) { if( distance < top_distances.v( j ) ) { for( t_CKINT l = k - 1; l > j; l-- ) { top_distances.v( l ) = top_distances.v( l - 1 ); top_indices.v( l ) = top_indices.v( l - 1 ); } top_distances.v( j ) = distance; top_indices.v( j ) = i; break; } } } // copy for( t_CKINT i = 0; i < k; i++ ) { indices.v( i ) = top_indices.v( i ); } } // search; returns indices void search0( const vector & query, t_CKINT k, Chuck_Array4 & indices_ ) { // init ChaiVectorFast indices( k ); // search getNearestNeighbors( query, k, indices ); // resize indices_.set_size( k ); // copy for( t_CKINT i = 0; i < k; i++ ) { indices_.m_vector[i] = indices[i]; } } // search; returns labels void search1( const vector & query, t_CKINT k, Chuck_Array4 & labels_ ) { // init ChaiVectorFast indices( k ); // search getNearestNeighbors( query, k, indices ); // resize labels_.set_size( k ); // copy for( t_CKINT i = 0; i < k; i++ ) { labels_.m_vector[i] = Y->v( indices.v( i ) ); } } // search; returns labels and indices void search2( const vector & query, t_CKINT k, Chuck_Array4 & labels_, Chuck_Array4 & indices_ ) { // init ChaiVectorFast indices( k ); // search getNearestNeighbors( query, k, indices ); // resize labels_.set_size( k ); indices_.set_size( k ); for( t_CKINT i = 0; i < k; i++ ) { labels_.m_vector[i] = Y->v( indices.v( i ) ); indices_.m_vector[i] = indices[i]; } } // search; returns labels, indices, and feature vectors void search3( const vector & query, t_CKINT k, Chuck_Array4 & labels_, Chuck_Array4 & indices_, Chuck_Array4 & features_ ) { // init ChaiVectorFast indices( k ); // search getNearestNeighbors( query, k, indices ); // resize labels_.set_size( k ); indices_.set_size( k ); // check dimensions if( features_.size() < k ) { Chuck_Array8 * x_i = features_.size() ? (Chuck_Array8 *)features_.m_vector[0] : NULL; EM_error3( "KNN2: insufficient 'features' matrix provided: %dx%d (expecting %dx%d)", features_.size(), x_i != NULL ? x_i->m_vector.size() : 0, k, this->X->xDim() ); return; } // copy Chuck_Array8 * x_i; for( t_CKINT i = 0; i < k; i++ ) { labels_.m_vector[i] = Y->v( indices.v( i ) ); indices_.m_vector[i] = indices[i]; x_i = (Chuck_Array8 *)features_.m_vector[i]; for( t_CKINT j = 0; j < X->yDim(); j++ ) { x_i->m_vector[j] = X->v( indices.v( i ), j ); } } } // search; returns labels, indices, and feature vectors void search3b( const vector & query, t_CKINT k, Chuck_Array4 & indices_, Chuck_Array4 & features_ ) { // init ChaiVectorFast indices( k ); // search getNearestNeighbors( query, k, indices ); // resize indices_.set_size( k ); // check dimensions if( features_.size() < k ) { Chuck_Array8 * x_i = features_.size() ? (Chuck_Array8 *)features_.m_vector[0] : NULL; EM_error3( "KNN: insufficient 'features' matrix provided: %dx%d (expecting %dx%d)", features_.size(), x_i != NULL ? x_i->m_vector.size() : 0, k, this->X->xDim() ); return; } // copy Chuck_Array8 * x_i; for( t_CKINT i = 0; i < k; i++ ) { indices_.m_vector[i] = indices[i]; x_i = (Chuck_Array8 *)features_.m_vector[i]; for( t_CKINT j = 0; j < X->yDim(); j++ ) { x_i->m_vector[j] = X->v( indices.v( i ), j ); } } } // predict t_CKBOOL predict( const vector & query, t_CKINT k, Chuck_Array8 & prob_ ) { // TODO: check if model initialized // init ChaiVectorFast indices( k ); // search getNearestNeighbors( query, k, indices ); // copy prob_.set_size( Y->getMax() + 1 ); // zero out prob_.zero( 0, prob_.size() ); for( t_CKINT i = 0; i < k; i++ ) { prob_.m_vector[Y->v( indices.v( i ) )] += 1.0; } for( t_CKINT i = 0; i < prob_.size(); i++ ) { prob_.m_vector[i] /= k; } return TRUE; } // weigh void weigh( const vector & weights_ ) { // sanity check if( X == NULL ) { EM_error3( "KNN: cannot weigh without training data" ); return; } if( weights_.size() != X->yDim() ) { EM_error3( "KNN: invalid 'weights' vector provided: %d (expecting %d)", weights_.size(), X->yDim() ); return; } // copy for( t_CKINT i = 0; i < weights_.size(); i++ ) { weights->v( i ) = weights_[i]; } } private: ChaiMatrixFast * X; ChaiVectorFast * Y; ChaiVectorFast * weights; }; //----------------------------------------------------------------------------- // KNN c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( KNN_ctor ) { KNN_Object * knn = new KNN_Object(); OBJ_MEMBER_UINT( SELF, KNN_offset_data ) = (t_CKUINT)knn; } CK_DLL_DTOR( KNN_dtor ) { KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN_offset_data ); SAFE_DELETE( knn ); OBJ_MEMBER_UINT( SELF, KNN_offset_data ) = 0; } CK_DLL_MFUN( KNN_train ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN_offset_data ); // get args Chuck_Array4 * x = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( x == NULL ) { EM_error3( "KNN.train(): NULL input encountered..." ); return; } // train knn->train( *x ); } CK_DLL_MFUN( KNN_search ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN_offset_data ); // get args Chuck_Array8 * query = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKINT k = GET_NEXT_INT( ARGS ); Chuck_Array4 * indices = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( query == NULL || indices == NULL ) { EM_error3( "KNN.search(): NULL input encountered..." ); return; } // search0 knn->search0( query->m_vector, k, *indices ); } CK_DLL_MFUN( KNN_search2 ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); // get args Chuck_Array8 * query = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKINT k = GET_NEXT_INT( ARGS ); Chuck_Array4 * indices = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * features = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( query == NULL || indices == NULL || features == NULL ) { EM_error3( "KNN2.search(): NULL input encountered..." ); return; } // search3 knn->search3b( query->m_vector, k, *indices, *features ); } CK_DLL_MFUN( KNN_weigh ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN_offset_data ); // get args Chuck_Array8 * weights = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // weigh knn->weigh( weights->m_vector ); } //----------------------------------------------------------------------------- // KNN2 c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( KNN2_ctor ) { KNN_Object * knn = new KNN_Object(); OBJ_MEMBER_UINT( SELF, KNN2_offset_data ) = (t_CKUINT)knn; } CK_DLL_DTOR( KNN2_dtor ) { KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); SAFE_DELETE( knn ); OBJ_MEMBER_UINT( SELF, KNN2_offset_data ) = 0; } CK_DLL_MFUN( KNN2_train ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); // get args Chuck_Array4 * x = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * y = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( x == NULL || y == NULL ) { EM_error3( "KNN2.train(): NULL input encountered..." ); return; } // train knn->train( *x, *y ); } CK_DLL_MFUN( KNN2_predict ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); // get args Chuck_Array8 * query = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKINT k = GET_NEXT_INT( ARGS ); Chuck_Array8 * prob = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( query == NULL || prob == NULL ) { EM_error3( "KNN2.predict(): NULL input encountered..." ); return; } // predict RETURN->v_int = knn->predict( query->m_vector, k, *prob ); } CK_DLL_MFUN( KNN2_search ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); // get args Chuck_Array8 * query = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKINT k = GET_NEXT_INT( ARGS ); Chuck_Array4 * labels = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( query == NULL || labels == NULL ) { EM_error3( "KNN2.search(): NULL input encountered..." ); return; } // search1 knn->search1( query->m_vector, k, *labels ); } CK_DLL_MFUN( KNN2_search2 ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); // get args Chuck_Array8 * query = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKINT k = GET_NEXT_INT( ARGS ); Chuck_Array4 * labels = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * indices = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( query == NULL || labels == NULL || indices == NULL ) { EM_error3( "KNN2.search(): NULL input encountered..." ); return; } // search2 knn->search2( query->m_vector, k, *labels, *indices ); } CK_DLL_MFUN( KNN2_search3 ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); // get args Chuck_Array8 * query = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKINT k = GET_NEXT_INT( ARGS ); Chuck_Array4 * labels = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * indices = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * features = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( query == NULL || labels == NULL || indices == NULL || features == NULL ) { EM_error3( "KNN2.search(): NULL input encountered..." ); return; } // search3 knn->search3( query->m_vector, k, *labels, *indices, *features ); } CK_DLL_MFUN( KNN2_weigh ) { // get object KNN_Object * knn = (KNN_Object *)OBJ_MEMBER_UINT( SELF, KNN2_offset_data ); // get args Chuck_Array8 * weights = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // weigh knn->weigh( weights->m_vector ); } //----------------------------------------------------------------------------- // name: struct HMM_Object // desc: hidden markov model implementation | added 1.4.2.0 (yikai) //----------------------------------------------------------------------------- struct HMM_Object { public: // constructor HMM_Object() : initial( NULL ), transition( NULL ), emission( NULL ) { } // destructor ~HMM_Object() { clear(); } // clear void clear() { SAFE_DELETE( initial ); SAFE_DELETE( transition ); SAFE_DELETE( emission ); } // init t_CKINT init( Chuck_Array8 & initial_, Chuck_Array4 & transition_, Chuck_Array4 & emission_ ) { clear(); initial = chuck2chai( initial_ ); transition = chuck2chai( transition_ ); emission = chuck2chai( emission_ ); return 0; } void filter( ChaiVectorFast & y, ChaiMatrixFast & phi, ChaiVectorFast & c ) { t_CKINT i, j, t, n = y.size(), N = emission->xDim(); // M = emission->yDim(); ChaiVectorFast z( N ); c.v( 0 ) = 0; for( j = 0; j < N; j++ ) { z.v( j ) = initial->v( j ) * emission->v( j, y.v( 0 ) ); c.v( 0 ) += z.v( j ); } for( j = 0; j < N; j++ ) phi.v( 0, j ) = z.v( j ) / c.v( 0 ); for( t = 1; t < n; t++ ) { c.v( t ) = 0; for( j = 0; j < N; j++ ) { z.v( j ) = 0; for( i = 0; i < N; i++ ) z.v( j ) += phi.v( t - 1, i ) * transition->v( i, j ) * emission->v( j, y.v( t ) ); c.v( t ) += z.v( j ); } for( j = 0; j < N; j++ ) phi.v( t, j ) = z.v( j ) / c.v( t ); } } void smoother( ChaiVectorFast & y, ChaiVectorFast & c, ChaiMatrixFast & beta ) { t_CKINT i, j, t, n = y.size(), N = emission->xDim(); // M = emission->yDim(); for( j = 0; j < N; j++ ) beta.v( n - 1, j ) = 1; for( t = n - 2; t >= 0; t-- ) for( i = 0; i < N; i++ ) { beta.v( t, i ) = 0; for( j = 0; j < N; j++ ) beta.v( t, i ) += transition->v( i, j ) * emission->v( j, y.v( t + 1 ) ) * beta.v( t + 1, j ); beta.v( t, i ) /= c.v( t + 1 ); } } void randomTransitionKernel() { t_CKINT i, j, N = transition->xDim(); t_CKFLOAT s; for( i = 0; i < N; i++ ) { s = 0; for( j = 0; j < N; j++ ) { transition->v( i, j ) = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; s += transition->v( i, j ); } for( j = 0; j < N; j++ ) { transition->v( i, j ) /= s; } } } void randomEmissionKernel() { t_CKINT i, j, N = emission->xDim(), M = emission->yDim(); t_CKFLOAT s; for( i = 0; i < N; i++ ) { s = 0; for( j = 0; j < M; j++ ) { emission->v( i, j ) = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; s += emission->v( i, j ); } for( j = 0; j < M; j++ ) { emission->v( i, j ) /= s; } } } // train t_CKINT train( t_CKINT n_state, t_CKINT n_emission, Chuck_Array4 & observations ) { clear(); // init initial = new ChaiVectorFast( n_state ); transition = new ChaiMatrixFast( n_state, n_state ); emission = new ChaiMatrixFast( n_state, n_emission ); // train t_CKINT maxIt = 100; t_CKFLOAT tol = 1e-4; t_CKINT i, j, it, t, n = observations.size(), N = n_state, M = n_emission; t_CKFLOAT z, l = 0, change = tol + 1; initial->v( 0 ) = 1; for( i = 1; i < N; i++ ) initial->v( i ) = 0; randomTransitionKernel(); randomEmissionKernel(); ChaiMatrixFast phi( n, N ); ChaiMatrixFast beta( n, N ); ChaiVectorFast c( n ); ChaiMatrixFast A( N, M ); ChaiVectorFast s( N ); ChaiMatrixFast B( N, N ); ChaiVectorFast s2( N ); ChaiVectorFast y( n ); for( i = 0; i < n; i++ ) { y.v( i ) = observations.m_vector[i]; } for( it = 0; ( change > tol ) && ( it < maxIt ); it++ ) { change = 0; filter( y, phi, c ); smoother( y, c, beta ); for( i = 0; i < N; i++ ) { s.v( i ) = 0; s2.v( i ) = 0; for( j = 0; j < M; j++ ) A.v( i, j ) = 0; for( j = 0; j < N; j++ ) B.v( i, j ) = 0; } for( t = 0; t < n; t++ ) for( i = 0; i < N; i++ ) { z = phi.v( t, i ) * beta.v( t, i ); A.v( i, y.v( t ) ) += z; s.v( i ) += z; } for( i = 0; i < N; i++ ) for( j = 0; j < M; j++ ) { change = ck_max( change, fabs( emission->v( i, j ) - A.v( i, j ) / s.v( i ) ) ); emission->v( i, j ) = A.v( i, j ) / s.v( i ); } for( t = 1; t < n; t++ ) for( i = 0; i < N; i++ ) for( j = 0; j < N; j++ ) { z = phi.v( t - 1, i ) * transition->v( i, j ) * emission->v( j, y.v( t ) ) * beta.v( t, j ) / c.v( t ); B.v( i, j ) += z; s2.v( i ) += z; } for( i = 0; i < N; i++ ) for( j = 0; j < N; j++ ) { change = ck_max( change, fabs( transition->v( i, j ) - B.v( i, j ) / s2.v( i ) ) ); transition->v( i, j ) = B.v( i, j ) / s2.v( i ); } } for( i = 0; i < n; i++ ) l += log( c.v( i ) ); return 0; } // generate t_CKINT generate( t_CKINT length, Chuck_Array4 & output_ ) { // compute t_CKINT state; t_CKFLOAT r; for( t_CKINT i = 0; i < length; i++ ) { // state if( i == 0 ) { r = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; for( t_CKINT j = 0; j < initial->size(); j++ ) { if( r < initial->v( j ) ) { state = j; break; } r -= initial->v( j ); } } else { r = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; for( t_CKINT j = 0; j < transition->yDim(); j++ ) { if( r < transition->v( state, j ) ) { state = j; break; } r -= transition->v( state, j ); } } // observation r = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; for( t_CKINT j = 0; j < emission->yDim(); j++ ) { if( r < emission->v( state, j ) ) { output_.set( i, j ); break; } r -= emission->v( state, j ); } } return 0; } private: ChaiVectorFast * initial; ChaiMatrixFast * transition; ChaiMatrixFast * emission; }; //----------------------------------------------------------------------------- // HMM c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( HMM_ctor ) { HMM_Object * hmm = new HMM_Object(); OBJ_MEMBER_UINT( SELF, HMM_offset_data ) = (t_CKUINT)hmm; } CK_DLL_DTOR( HMM_dtor ) { HMM_Object * hmm = (HMM_Object *)OBJ_MEMBER_UINT( SELF, HMM_offset_data ); SAFE_DELETE( hmm ); OBJ_MEMBER_UINT( SELF, HMM_offset_data ) = 0; } CK_DLL_MFUN( HMM_init ) { // get object HMM_Object * hmm = (HMM_Object *)OBJ_MEMBER_UINT( SELF, HMM_offset_data ); // get args Chuck_Array8 * initial = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * transition = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * emission = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // init hmm->init( *initial, *transition, *emission ); } CK_DLL_MFUN( HMM_train ) { // get object HMM_Object * hmm = (HMM_Object *)OBJ_MEMBER_UINT( SELF, HMM_offset_data ); // get args t_CKINT n_state = GET_NEXT_INT( ARGS ); t_CKINT n_emission = GET_NEXT_INT( ARGS ); Chuck_Array4 * observations = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // train hmm->train( n_state, n_emission, *observations ); } CK_DLL_MFUN( HMM_generate ) { // get object HMM_Object * hmm = (HMM_Object *)OBJ_MEMBER_UINT( SELF, HMM_offset_data ); // get args t_CKINT length = GET_NEXT_INT( ARGS ); Chuck_Array4 * output = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // generate hmm->generate( length, *output ); } //----------------------------------------------------------------------------- // name: struct W2V_Dictionary // desc: struct for caching Word2Vec | added 1.4.2.1 (ge) //----------------------------------------------------------------------------- struct W2V_Dictionary { t_CKINT dictionarySize, vectorLength; std::map * key_to_index; ChaiVectorFast * index_to_key; ChaiMatrixFast * word_vectors; struct kdtree * tree; // bounds for each dimension ChaiVectorFast mins; ChaiVectorFast maxs; W2V_Dictionary() : key_to_index( NULL ), index_to_key( NULL ), word_vectors( NULL ), dictionarySize( 0 ), vectorLength( 0 ), tree( NULL ) { } // clear void clear() { dictionarySize = 0; vectorLength = 0; SAFE_DELETE( key_to_index ); SAFE_DELETE( index_to_key ); SAFE_DELETE( word_vectors ); kdtree_destroy( tree ); } }; //----------------------------------------------------------------------------- // name: struct Word2Vec_Object // desc: Word2Vec implementation | added 1.4.2.1 (yikai) //----------------------------------------------------------------------------- struct Word2Vec_Object { private: // static (shared) cache | added (ge) 1.4.2.1 static map o_cache; private: // pointer to dictionary W2V_Dictionary * dictionary; // line number for parsing error reporting t_CKINT m_lineNum; public: // constructor Word2Vec_Object() : dictionary( NULL ), m_lineNum( 0 ) { } // destructor ~Word2Vec_Object() { clear(); } // clear void clear() { // just zero the pointer (no delete since we are caching) dictionary = NULL; // zero out line number m_lineNum = 0; } // get dictionary size t_CKINT getDictionarySize() const { return dictionary != NULL ? dictionary->dictionarySize : 0; } // get dictionary dimensions t_CKINT getDictionaryDim() const { return dictionary != NULL ? dictionary->vectorLength : 0; } // get dim mins and maxs void getDimMinMaxs( Chuck_Array8 * mins, Chuck_Array8 * maxs ) { // get dim t_CKINT dims = getDictionaryDim(); // allocate if( dims == 0 ) return; // if not null if( mins != NULL ) { // set size mins->set_size( dims ); // copy minimums for( int i = 0; i < dims; i++ ) { mins->set( i, dictionary->mins.v( i ) ); } } // if not null if( maxs != NULL ) { // set size maxs->set_size( dims ); // copy minimums for( int i = 0; i < dims; i++ ) { maxs->set( i, dictionary->maxs.v( i ) ); } } } // get dictionary dimensions t_CKBOOL getUseKDTree() const { return dictionary != NULL ? dictionary->tree != NULL : 0; } // read next non-empty or commented line t_CKBOOL nextline( std::ifstream & fin, string & line, t_CKBOOL commentIsHash ) { // skip over empty lines and commented lines (starts with #) do { // increment line number m_lineNum++; // get line if( !std::getline( fin, line ) ) { line = ""; return FALSE; } // ltrim leading white spaces line = ltrim( line ); } while( line == "" || ( commentIsHash && line[0] == '#' ) ); return TRUE; } // load t_CKINT load( Chuck_String & path, t_CKINT useKDTreeIfDimLEQtoThis ) { // clear clear(); // check cache if( o_cache.find( path.str() ) != o_cache.end() ) { // retrieve from cache dictionary = o_cache[path.str()]; // done return true; } // open file std::ifstream fin( path.str() ); if( !fin.is_open() ) return false; // instantiate dictionary dictionary = new W2V_Dictionary(); // string to hold the line string line; // get next non-empty, non-commented # line if( !nextline( fin, line, TRUE ) ) { EM_error3( "Word2Vec: error parsing model on line: %d", m_lineNum ); return false; } // string stream istringstream strin( line ); // read header strin >> dictionary->dictionarySize >> dictionary->vectorLength; // allocate dictionary->key_to_index = new std::map(); dictionary->index_to_key = new ChaiVectorFast( dictionary->dictionarySize ); dictionary->mins.allocate( dictionary->vectorLength ); dictionary->maxs.allocate( dictionary->vectorLength ); for( int i = 0; i < dictionary->vectorLength; i++ ) { // initialize dictionary->mins.v( i ) = 1e10; dictionary->maxs.v( i ) = -1e10; } // check if using kdtree if( useKDTreeIfDimLEQtoThis < 0 || dictionary->vectorLength <= useKDTreeIfDimLEQtoThis ) { // read vectors and build kd-tree dictionary->tree = kdtree_init( dictionary->vectorLength ); std::string key; t_CKFLOAT value; ChaiVectorFast vector( dictionary->vectorLength ); for( t_CKINT i = 0; i < dictionary->dictionarySize; i++ ) { // get next non-empty line if( !nextline( fin, line, FALSE ) ) { EM_error3( "Word2Vec: error parsing model on line: %d", m_lineNum ); return false; } // set into string stream strin.str( line ); strin.clear(); // get the word strin >> key; dictionary->index_to_key->v( i ) = key; ( *( dictionary->key_to_index ) )[key] = i; for( t_CKINT j = 0; j < dictionary->vectorLength; j++ ) { strin >> value; vector.v( j ) = value; // find min/max if( value < dictionary->mins.v( j ) ) dictionary->mins.v( j ) = value; else if( value > dictionary->maxs.v( j ) ) dictionary->maxs.v( j ) = value; } kdtree_insert( dictionary->tree, vector.m_vector ); } // rebuild kdtree kdtree_rebuild( dictionary->tree ); } else { // allocate word vectors dictionary->word_vectors = new ChaiMatrixFast( dictionary->dictionarySize, dictionary->vectorLength ); // read vectors std::string key; t_CKFLOAT value; for( t_CKINT i = 0; i < dictionary->dictionarySize; i++ ) { // get next non-empty line if( !nextline( fin, line, FALSE ) ) { EM_error3( "Word2Vec: error parsing model on line: %d", m_lineNum ); return false; } // set into string stream strin.str( line ); strin.clear(); strin >> key; dictionary->index_to_key->v( i ) = key; ( *( dictionary->key_to_index ) )[key] = i; for( t_CKINT j = 0; j < dictionary->vectorLength; j++ ) { strin >> value; dictionary->word_vectors->v( i, j ) = value; // find min/max if( value < dictionary->mins.v( j ) ) dictionary->mins.v( j ) = value; else if( value > dictionary->maxs.v( j ) ) dictionary->maxs.v( j ) = value; } } } // close file fin.close(); // add to cache o_cache[path.str()] = dictionary; return true; } // get nearest neighbors (brute force) void getNearestNeighborsBF( ChaiVectorFast & vector, t_CKINT topn, ChaiVectorFast & indices ) { // allocate ChaiVectorFast top_distances( topn ); ChaiVectorFast top_indices( topn ); // initialize for( t_CKINT i = 0; i < topn; i++ ) { top_distances.v( i ) = 1e10; top_indices.v( i ) = 0; } // compute distances t_CKFLOAT distance, diff; for( t_CKINT i = 0; i < dictionary->dictionarySize; i++ ) { distance = 0; for( t_CKINT j = 0; j < dictionary->vectorLength; j++ ) { diff = vector.v( j ) - dictionary->word_vectors->v( i, j ); distance += diff * diff; } for( t_CKINT j = 0; j < topn; j++ ) { if( distance < top_distances.v( j ) ) { for( t_CKINT k = topn - 1; k > j; k-- ) { top_distances.v( k ) = top_distances.v( k - 1 ); top_indices.v( k ) = top_indices.v( k - 1 ); } top_distances.v( j ) = distance; top_indices.v( j ) = i; break; } } } // copy for( t_CKINT i = 0; i < topn; i++ ) { indices.v( i ) = top_indices.v( i ); } } // get nearest neighbors void getNearestNeighborsKDTree( ChaiVectorFast & vector, t_CKINT topn, ChaiVectorFast & indices ) { kdtree_knn_search( dictionary->tree, vector.m_vector, topn ); struct knn_list * p = dictionary->tree->knn_list_head.next; t_CKINT i = 0; // TODO: check this -- ge: add index check to prevent out of bounds while( p != &dictionary->tree->knn_list_head && i < indices.length() ) { indices.v( i++ ) = p->node->coord_index; p = p->next; } } // get nearest neighbors void getNearestNeighbors( ChaiVectorFast & vector, t_CKINT topn, ChaiVectorFast & indices ) { // check if( dictionary == NULL ) { EM_error3( "Word2Vec: no model loaded..." ); return; } // check if using kdtree if( dictionary->tree != NULL ) return getNearestNeighborsKDTree( vector, topn, indices ); else return getNearestNeighborsBF( vector, topn, indices ); } // getMostSimilar t_CKBOOL getMostSimilarByWord( const string & word, t_CKINT topn, Chuck_Array4 & output_ ) { // error flag t_CKBOOL hasError = FALSE; // clear for( t_CKINT i = 0; i < output_.size(); i++ ) { ( (Chuck_String *)output_.m_vector[i] )->set( "" ); } // check if( dictionary == NULL ) { EM_error3( "Word2Vec: no model loaded..." ); return false; } // check if( output_.size() < topn ) { EM_error3( "Word2Vec.getSimilar() results array insufficient size %d (expecting %d)...", output_.size(), topn ); // reduce k and do our best topn = output_.size(); // but mark error to return false (since we won't fully fulfill our function) hasError = TRUE; } // see if word is there if( !contains( word ) ) { return false; } // get index t_CKINT index = ( *( dictionary->key_to_index ) )[word]; // get vector ChaiVectorFast vector( dictionary->vectorLength ); // use tree? if( dictionary->tree != NULL ) { for( t_CKINT i = 0; i < dictionary->vectorLength; i++ ) vector.v( i ) = dictionary->tree->coord_table[index][i]; // get nearest neighbors ChaiVectorFast top_indices( topn ); getNearestNeighbors( vector, topn, top_indices ); // copy for( t_CKINT i = 0; i < topn; i++ ) { ( (Chuck_String *)output_.m_vector[i] )->set( dictionary->index_to_key->v( top_indices.v( i ) ) ); } } else { for( t_CKINT i = 0; i < dictionary->vectorLength; i++ ) vector.v( i ) = dictionary->word_vectors->v( index, i ); // get nearest neighbors ChaiVectorFast top_indices( topn ); getNearestNeighbors( vector, topn, top_indices ); // copy for( t_CKINT i = 0; i < topn; i++ ) { ( (Chuck_String *)output_.m_vector[i] )->set( dictionary->index_to_key->v( top_indices.v( i ) ) ); } } return !hasError; } // getByVector t_CKBOOL getMostSimilarByVector( Chuck_Array8 & vec_, t_CKINT topn, Chuck_Array4 & output_ ) { // error flag t_CKBOOL hasError = FALSE; // clear for( t_CKINT i = 0; i < output_.size(); i++ ) { ( (Chuck_String *)output_.m_vector[i] )->set( "" ); } // check if( dictionary == NULL ) { EM_error3( "Word2Vec: no model loaded..." ); return false; } // check if( output_.size() < topn ) { EM_error3( "Word2Vec.getSimilar() results array insufficient size %d (expecting %d)...", output_.size(), topn ); // reduce k and do our best topn = output_.size(); // but mark error to return false (since we won't fully fulfill our function) hasError = TRUE; } // get vector ChaiVectorFast vector( dictionary->vectorLength ); for( t_CKINT i = 0; i < dictionary->vectorLength; i++ ) vector.v( i ) = vec_.m_vector[i]; // get nearest neighbors ChaiVectorFast top_indices( topn ); getNearestNeighbors( vector, topn, top_indices ); // copy for( t_CKINT i = 0; i < topn; i++ ) { ( (Chuck_String *)output_.m_vector[i] )->set( dictionary->index_to_key->v( top_indices.v( i ) ) ); } return !hasError; } // contains t_CKBOOL contains( const string & word ) { // see if word is there std::map & key2index = *( dictionary->key_to_index ); return key2index.find( word ) != key2index.end(); } // getVector t_CKBOOL getVector( const string & word, Chuck_Array8 & output_ ) { // check if( dictionary == NULL ) { EM_error3( "Word2Vec: no model loaded..." ); return false; } // ensure length if( output_.size() < dictionary->vectorLength ) { output_.set_size( dictionary->vectorLength ); } // zero out output_.zero( 0, output_.size() ); // see if word is there if( !contains( word ) ) { return false; } // get index std::map & key2index = *( dictionary->key_to_index ); t_CKINT index = key2index[word]; // whether we are using if( dictionary->tree != NULL ) { // copy for( t_CKINT i = 0; i < dictionary->vectorLength; i++ ) { output_.m_vector[i] = dictionary->tree->coord_table[index][i]; } } else { // copy for( t_CKINT i = 0; i < dictionary->vectorLength; i++ ) { output_.m_vector[i] = dictionary->word_vectors->v( index, i ); } } // done return true; } }; // static instantiation | added (ge) 1.4.2.1 map Word2Vec_Object::o_cache; //----------------------------------------------------------------------------- // Word2Vec c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( Word2Vec_ctor ) { Word2Vec_Object * word2vec = new Word2Vec_Object(); OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ) = (t_CKUINT)word2vec; } CK_DLL_DTOR( Word2Vec_dtor ) { Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); SAFE_DELETE( word2vec ); OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ) = 0; } CK_DLL_MFUN( Word2Vec_load ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get args Chuck_String * path = GET_NEXT_STRING( ARGS ); // load RETURN->v_int = word2vec->load( *path, 8 ); } CK_DLL_MFUN( Word2Vec_load2 ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get args Chuck_String * path = GET_NEXT_STRING( ARGS ); t_CKINT useKDTreeDim = GET_NEXT_INT( ARGS ); // load RETURN->v_int = word2vec->load( *path, useKDTreeDim ); } CK_DLL_MFUN( Word2Vec_getMostSimilarByWord ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get args Chuck_String * word = GET_NEXT_STRING( ARGS ); t_CKINT topn = GET_NEXT_INT( ARGS ); Chuck_Array4 * output = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( word == NULL || output == NULL ) { RETURN->v_int = FALSE; return; } // get by word RETURN->v_int = word2vec->getMostSimilarByWord( word->str(), topn, *output ); } CK_DLL_MFUN( Word2Vec_getMostSimilarByVector ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get args Chuck_Array8 * vec = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKINT topn = GET_NEXT_INT( ARGS ); Chuck_Array4 * output = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( vec == NULL || output == NULL ) { RETURN->v_int = FALSE; return; } // get by vector RETURN->v_int = word2vec->getMostSimilarByVector( *vec, topn, *output ); } CK_DLL_MFUN( Word2Vec_getVector ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get args Chuck_String * word = GET_NEXT_STRING( ARGS ); Chuck_Array8 * output = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // check for NULL if( word == NULL || output == NULL ) { RETURN->v_int = FALSE; return; } // getVector RETURN->v_int = word2vec->getVector( word->str(), *output ); } CK_DLL_MFUN( Word2Vec_contains ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get args Chuck_String * word = GET_NEXT_STRING( ARGS ); // check for NULL if( word == NULL ) { RETURN->v_int = FALSE; return; } // contains RETURN->v_int = word2vec->contains( word->str() ); } CK_DLL_MFUN( Word2Vec_getDictionarySize ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get size RETURN->v_int = word2vec->getDictionarySize(); } CK_DLL_MFUN( Word2Vec_getDictionaryDim ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get dim RETURN->v_int = word2vec->getDictionaryDim(); } CK_DLL_MFUN( Word2Vec_getDimMinMax ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get chuck arrays Chuck_Array8 * mins = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array8 * maxs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // get it word2vec->getDimMinMaxs( mins, maxs ); } CK_DLL_MFUN( Word2Vec_getUseKDTree ) { // get object Word2Vec_Object * word2vec = (Word2Vec_Object *)OBJ_MEMBER_UINT( SELF, Word2Vec_offset_data ); // get dim RETURN->v_int = word2vec->getUseKDTree(); } //----------------------------------------------------------------------------- // name: struct PCA_Object // desc: Principal Component Analysis Implementation | added 1.4.2.1 (yikai) //----------------------------------------------------------------------------- struct PCA_Object { public: // constructor PCA_Object() { } // destructor ~PCA_Object() { } /* Reduce a real, symmetric matrix to a symmetric, tridiag. matrix. */ static void tred2( ChaiMatrixFast & a, t_CKINT m, ChaiVectorFast & d, ChaiVectorFast & e ) /* Householder reductiom of matrix a to tridiagomal form. Algorithm: Martim et al., Num. Math. 11, 181-195, 1968. Ref: Smith et al., Matrix Eigemsystem Routimes -- EISPACK Guide Sprimger-Verlag, 1976, pp. 489-494. W H Press et al., Numerical Recipes im C, Cambridge U P, 1988, pp. 373-374. Source code adapted from F. Murtagh, Munich, 6 June 1989 http://astro.u-strasbg.fr/~fmurtagh/mda-sw/pca.c */ { t_CKINT l, k, j, i; t_CKFLOAT scale, hh, h, g, f; for( i = m - 1; i > 0; i-- ) { l = i - 1; h = scale = 0.0; if( l > 0 ) { for( k = 0; k <= l; k++ ) scale += fabs( a( i, k ) ); if( scale == 0.0 ) e[i] = a( i, l ); else { for( k = 0; k <= l; k++ ) { a( i, k ) /= scale; h += a( i, k ) * a( i, k ); } f = a( i, l ); g = f > 0 ? -sqrt( h ) : sqrt( h ); e[i] = scale * g; h -= f * g; a( i, l ) = f - g; f = 0.0; for( j = 0; j <= l; j++ ) { a( j, i ) = a( i, j ) / h; g = 0.0; for( k = 0; k <= j; k++ ) g += a( j, k ) * a( i, k ); for( k = j + 1; k <= l; k++ ) g += a( k, j ) * a( i, k ); e[j] = g / h; f += e[j] * a( i, j ); } hh = f / ( h + h ); for( j = 0; j <= l; j++ ) { f = a( i, j ); e[j] = g = e[j] - hh * f; for( k = 0; k <= j; k++ ) a( j, k ) -= ( f * e[k] + g * a( i, k ) ); } } } else e[i] = a( i, l ); d[i] = h; } d[0] = 0.0; e[0] = 0.0; for( i = 0; i < m; ++i ) { l = i - 1; if( d[i] ) { for( j = 0; j <= l; j++ ) { g = 0.0; for( k = 0; k <= l; k++ ) g += a( i, k ) * a( k, j ); for( k = 0; k <= l; k++ ) a( k, j ) -= g * a( k, i ); } } d[i] = a( i, i ); a( i, i ) = 1.0; for( j = 0; j <= l; j++ ) a( j, i ) = a( i, j ) = 0.0; } } /* Tridiagonal QL algorithm -- Implicit */ static void tqli( ChaiVectorFast & d, ChaiVectorFast & e, t_CKINT m, ChaiMatrixFast & z ) /* Source code adapted from F. Murtagh, Munich, 6 June 1989 http://astro.u-strasbg.fr/~fmurtagh/mda-sw/pca.c */ { t_CKINT n, l, i, k; t_CKFLOAT s, r, p, g, f, dd, c, b; for( i = 1; i < m; ++i ) e[i - 1] = e[i]; e[m - 1] = 0.0; for( l = 0; l < m; l++ ) { do { for( n = l; n < m - 1; n++ ) { dd = fabs( d[n] ) + fabs( d[n + 1] ); if( fabs( e[n] ) + dd == dd ) break; } if( n != l ) { g = ( d[l + 1] - d[l] ) / ( 2.0 * e[l] ); r = sqrt( ( g * g ) + 1.0 ); g = d[n] - d[l] + e[l] / ( g + ( ( g ) < 0 ? -fabs( r ) : fabs( r ) ) ); s = c = 1.0; p = 0.0; for( i = n - 1; i >= l; i-- ) { f = s * e[i]; b = c * e[i]; if( fabs( f ) >= fabs( g ) ) { c = g / f; r = sqrt( ( c * c ) + 1.0 ); e[i + 1] = f * r; c *= ( s = 1.0 / r ); } else { s = f / g; r = sqrt( ( s * s ) + 1.0 ); e[i + 1] = g * r; s *= ( c = 1.0 / r ); } g = d[i + 1] - p; r = ( d[i] - g ) * s + 2.0 * c * b; p = s * r; d[i + 1] = g + p; g = c * r - b; for( k = 0; k < m; k++ ) { f = z( k, i + 1 ); z( k, i + 1 ) = s * z( k, i ) + c * f; z( k, i ) = c * z( k, i ) - s * f; } } d[l] = d[l] - p; e[l] = g; e[n] = 0.0; } } while( n != l ); } } // transform static void transform( Chuck_Array4 & input_, t_CKINT npc, Chuck_Array4 & output_ ) { ChaiMatrixFast * input = chuck2chai( input_ ); t_CKINT t, o; t_CKINT o1, o2; t_CKINT n = input->xDim(), d = input->yDim(); ChaiMatrixFast temp_matrix( d, n ); for( t = 0; t < n; t++ ) { for( o = 0; o < d; o++ ) { temp_matrix( o, t ) = input->v( t, o ); } } ChaiVectorFast means( d ); ChaiVectorFast stds( d ); for( o = 0; o < d; o++ ) { means[o] = 0; for( t = 0; t < n; t++ ) { means[o] += temp_matrix( o, t ); } means[o] /= n; stds[o] = 0; for( t = 0; t < n; t++ ) { stds[o] += pow( temp_matrix( o, t ) - means[o], 2 ); } stds[o] = sqrt( stds[o] / n ); } // Adjust data : ( X - means(X) ) / ( sqrt(n) * stds(X) ) for( o = 0; o < d; o++ ) for( t = 0; t < n; t++ ) temp_matrix( o, t ) = ( temp_matrix( o, t ) - means[o] ) / ( sqrt( (t_CKFLOAT)n ) * stds[o] ); ChaiMatrixFast corr_matrix( d, d ); // Calculate the correlation matrix for( o1 = 0; o1 < d; o1++ ) { corr_matrix( o1, o1 ) = 1.0; for( o2 = o1; o2 < d; o2++ ) { corr_matrix( o1, o2 ) = 0.0; for( t = 0; t < n; t++ ) corr_matrix( o1, o2 ) += temp_matrix( o1, t ) * temp_matrix( o2, t ); corr_matrix( o2, o1 ) = corr_matrix( o1, o2 ); } } corr_matrix( d - 1, d - 1 ) = 1.0; ChaiVectorFast evals( d ); ChaiVectorFast interm( d ); // Triangular decomposition tred2( corr_matrix, d, evals, interm ); // Reduction of symmetric tridiagonal matrix tqli( evals, interm, d, corr_matrix ); // t_CKFLOAT percent_eig = 0.0; // t_CKFLOAT sum_eig = 0.0; // for( t_CKINT m = d - 2; m >= 0; m-- ) // sum_eig += evals[m]; // // for( t_CKINT m = d - 2; m >= 0; m-- ) // { // percent_eig += evals[m]; // std::cout << evals[m] / sum_eig << "\t"; // std::cout << percent_eig / sum_eig << std::endl; // } /* evals now contains the eigenvalues, corr_matrix now contains the associated eigenvectors. */ /* Project row data onto the top "npc_" principal components. */ Chuck_Array8 * vi; for( t = 0; t < n; t++ ) { vi = (Chuck_Array8 *)output_.m_vector[t]; for( o = 0; o < npc; o++ ) { vi->m_vector[o] = 0.0; for( o1 = 0; o1 < d; o1++ ) vi->m_vector[o] += input->v( t, o1 ) * corr_matrix( o1, d - 1 - o ); } } // clean up SAFE_DELETE( input ); } private: }; //----------------------------------------------------------------------------- // PCA c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( PCA_ctor ) { PCA_Object * pca = new PCA_Object(); OBJ_MEMBER_UINT( SELF, PCA_offset_data ) = (t_CKUINT)pca; } CK_DLL_DTOR( PCA_dtor ) { PCA_Object * pca = (PCA_Object *)OBJ_MEMBER_UINT( SELF, PCA_offset_data ); SAFE_DELETE( pca ); OBJ_MEMBER_UINT( SELF, PCA_offset_data ) = 0; } CK_DLL_SFUN( PCA_reduce ) { // get args Chuck_Array4 * input = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); t_CKINT n_components = GET_NEXT_INT( ARGS ); Chuck_Array4 * output = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // transform PCA_Object::transform( *input, n_components, *output ); } //----------------------------------------------------------------------------- // name: struct MLP_Object // desc: Multilayer Perceptron Implementation | added 1.4.2.1 (yikai) //----------------------------------------------------------------------------- struct MLP_Object { private: vector units_per_layer; vector activation_per_layer; vector *> weights; vector *> biases; vector *> activations; vector *> gradients; public: // constructor MLP_Object() { } // destructor ~MLP_Object() { clear(); } // clear void clear() { for( t_CKINT i = 0; i < weights.size(); i++ ) SAFE_DELETE( weights[i] ); for( t_CKINT i = 0; i < biases.size(); i++ ) SAFE_DELETE( biases[i] ); for( t_CKINT i = 0; i < activations.size(); i++ ) SAFE_DELETE( activations[i] ); for( t_CKINT i = 0; i < gradients.size(); i++ ) SAFE_DELETE( gradients[i] ); units_per_layer.clear(); activation_per_layer.clear(); weights.clear(); biases.clear(); activations.clear(); gradients.clear(); } // init bool init( const vector & units_per_layer_ ) { // clear clear(); // check if( units_per_layer_.size() == 0 ) { EM_error3( "MLP.init(): empty array 'unitsPerLayer'" ); return false; } // get units per layer for( t_CKINT i = 0; i < units_per_layer_.size(); i++ ) units_per_layer.push_back( units_per_layer_[i] ); // allocate for( t_CKINT i = 0; i < units_per_layer.size(); i++ ) { activations.push_back( new ChaiVectorFast( units_per_layer[i] ) ); gradients.push_back( new ChaiVectorFast( units_per_layer[i] ) ); } for( t_CKINT i = 0; i < units_per_layer.size() - 1; i++ ) { weights.push_back( new ChaiMatrixFast( units_per_layer[i + 1], units_per_layer[i] ) ); biases.push_back( new ChaiVectorFast( units_per_layer[i + 1] ) ); activation_per_layer.push_back( g_at_sigmoid ); // default to sigmoid... } // set output layer activation function to linear by default activation_per_layer.back() = g_at_linear; // random for( t_CKINT i = 0; i < weights.size(); i++ ) for( t_CKINT j = 0; j < weights[i]->xDim(); j++ ) for( t_CKINT k = 0; k < weights[i]->yDim(); k++ ) weights[i]->v( j, k ) = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; for( t_CKINT i = 0; i < biases.size(); i++ ) for( t_CKINT j = 0; j < biases[i]->size(); j++ ) biases[i]->v( j ) = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX; // done return true; } // init2 t_CKBOOL init( const vector & units_per_layer_, const vector & activation_per_layer_ ) { t_CKBOOL retval = init( units_per_layer_ ); if( retval ) { for( t_CKINT i = 0; i < activation_per_layer_.size(); i++ ) activation_per_layer.push_back( activation_per_layer_[i] ); } return retval; } // init3 t_CKBOOL init( const vector & units_per_layer_, t_CKINT activation_function_ ) { t_CKBOOL retval = init( units_per_layer_ ); if( retval ) { for( t_CKINT i = 0; i < units_per_layer_.size(); i++ ) activation_per_layer.push_back( activation_function_ ); } return retval; } // forward void forward( ChaiVectorFast & input ) { // sanity check if( units_per_layer.size() == 0 ) { EM_error3( "MLP.forward(): network not initialized" ); return; } // copy for( t_CKINT i = 0; i < input.size(); i++ ) activations[0]->v( i ) = input[i]; // forward t_CKFLOAT v, sum; for( t_CKINT i = 0; i < weights.size(); i++ ) { for( t_CKINT j = 0; j < weights[i]->xDim(); j++ ) { v = 0.0; for( t_CKINT k = 0; k < weights[i]->yDim(); k++ ) v += weights[i]->v( j, k ) * activations[i]->v( k ); v += biases[i]->v( j ); if( activation_per_layer[i] == g_at_sigmoid ) v = 1.0 / ( 1.0 + exp( -v ) ); else if( activation_per_layer[i] == g_at_tanh ) v = tanh( v ); else if( activation_per_layer[i] == g_at_relu ) v = v > 0.0 ? v : 0.0; else if( activation_per_layer[i] == g_at_softmax ) v = exp( v ); else if( activation_per_layer[i] == g_at_linear ) v = v; activations[i + 1]->v( j ) = v; } if( activation_per_layer[i] == g_at_softmax ) { sum = 0; for( t_CKINT j = 0; j < activations[i + 1]->size(); j++ ) sum += activations[i + 1]->v( j ); for( t_CKINT j = 0; j < activations[i + 1]->size(); j++ ) activations[i + 1]->v( j ) /= sum; } } } // backprop void backprop( ChaiVectorFast & output, t_CKFLOAT lr ) { // sanity check if( units_per_layer.size() == 0 ) { EM_error3( "MLP.backprop(): network not initialized" ); return; } // error for( t_CKINT i = 0; i < output.size(); i++ ) gradients.back()->v( i ) = output[i] - activations.back()->v( i ); // backprop t_CKFLOAT v; for( t_CKINT i = weights.size() - 1; i >= 0; i-- ) { // prev layer for( t_CKINT j = 0; j < weights[i]->yDim(); j++ ) { v = 0.0; for( t_CKINT k = 0; k < weights[i]->xDim(); k++ ) v += weights[i]->v( k, j ) * gradients[i + 1]->v( k ); gradients[i]->v( j ) = v; } // gradient for( t_CKINT j = 0; j < weights[i]->xDim(); j++ ) { v = activations[i + 1]->v( j ); if( activation_per_layer[i] == g_at_sigmoid ) v = v * ( 1.0 - v ); else if( activation_per_layer[i] == g_at_tanh ) v = 1.0 - v * v; else if( activation_per_layer[i] == g_at_relu ) v = v > 0.0 ? 1.0 : 0.0; else if( activation_per_layer[i] == g_at_softmax ) v = v * ( 1.0 - v ); else if( activation_per_layer[i] == g_at_linear ) v = 1.0; v = v * gradients[i + 1]->v( j ) * lr; for( t_CKINT k = 0; k < weights[i]->yDim(); k++ ) weights[i]->v( j, k ) += v * activations[i]->v( k ); biases[i]->v( j ) += v; gradients[i + 1]->v( j ) = v; } } } // train void train( ChaiMatrixFast & X, ChaiMatrixFast & Y, t_CKINT size, t_CKFLOAT lr, t_CKINT epochs ) { // sanity check if( units_per_layer.size() == 0 ) { EM_error3( "MLP.train(): network not initialized" ); return; } ChaiVectorFast x( X.yDim() ); ChaiVectorFast y( Y.yDim() ); ChaiVectorFast indices( size ); for( t_CKINT i = 0; i < size; i++ ) indices[i] = i; for( t_CKINT i = 0; i < epochs; i++ ) { indices.shuffle(); for( t_CKINT j = 0; j < size; j++ ) { for( t_CKINT k = 0; k < X.yDim(); k++ ) x[k] = X.v( indices[j], k ); forward( x ); for( t_CKINT k = 0; k < Y.yDim(); k++ ) y[k] = Y.v( indices[j], k ); backprop( y, lr ); } } } // // train // void train( Chuck_Array4 & inputs_, Chuck_Array4 & outputs_ ) // { // ChaiMatrixFast * X = chuck2chai( inputs_ ); // ChaiMatrixFast * Y = chuck2chai( outputs_ ); // train( *X, *Y, X->xDim(), 1e-3, 100 ); // SAFE_DELETE( X ); // SAFE_DELETE( Y ); // } // train2 void train( Chuck_Array4 & inputs_, Chuck_Array4 & outputs_, t_CKFLOAT learning_rate, t_CKINT max_iterations ) { ChaiMatrixFast * X = chuck2chai( inputs_ ); ChaiMatrixFast * Y = chuck2chai( outputs_ ); train( *X, *Y, X->xDim(), learning_rate, max_iterations ); SAFE_DELETE( X ); SAFE_DELETE( Y ); } // predict t_CKBOOL predict( Chuck_Array8 & input_, Chuck_Array8 & output_ ) { // sanity check if( units_per_layer.size() == 0 ) { // error message EM_error3( "MLP.predict(): network not initialized" ); // zero out output_.zero(); // done return FALSE; } if( input_.size() != units_per_layer.front() ) { // error message EM_error3( "MLP.predict(): input size mismatch" ); // zero out output_.zero(); // done return FALSE; } ChaiVectorFast * input = chuck2chai( input_ ); forward( *input ); output_.set_size( units_per_layer.back() ); for( t_CKINT i = 0; i < units_per_layer.back(); i++ ) output_.m_vector[i] = activations.back()->v( i ); SAFE_DELETE( input ); return TRUE; } // predict2 void predict( ChaiVectorFast & input_, ChaiVectorFast & output_ ) { forward( input_ ); for( t_CKINT i = 0; i < units_per_layer.back(); i++ ) output_.v( i ) = activations.back()->v( i ); } // get_weights void get_weights( t_CKINT layer, Chuck_Array4 & weights_ ) { // sanity check if( units_per_layer.size() == 0 ) { // error EM_error3( "MLP.getWeights(): network not initialized" ); // courtesy clear the array Chuck_Array8 * wi = NULL; for( t_CKINT i = 0; i < weights_.size(); i++ ) { wi = (Chuck_Array8 *)weights_.m_vector[i]; for( t_CKINT j = 0; j < wi->size(); j++ ) wi->m_vector[j] = 0.0; } return; } Chuck_Array8 * wi = NULL; for( t_CKINT i = 0; i < weights_.size(); i++ ) { wi = (Chuck_Array8 *)weights_.m_vector[i]; for( t_CKINT j = 0; j < wi->size(); j++ ) wi->m_vector[j] = weights[layer]->v( j, i ); } } // get_biases void get_biases( t_CKINT layer, Chuck_Array8 & biases_ ) { // sanity check if( units_per_layer.size() == 0 ) { // error EM_error3( "MLP.getBiases(): network not initialized" ); // clear biases_.clear(); // done return; } biases_.set_size( biases[layer]->size() ); for( t_CKINT i = 0; i < biases_.size(); i++ ) biases_.m_vector[i] = biases[layer]->v( i ); } // get_gradients void get_gradients( t_CKINT layer, Chuck_Array8 & gradients_ ) { // sanity check if( units_per_layer.size() == 0 ) { // error EM_error3( "MLP.getGradients(): network not initialized" ); // clear gradients_.clear(); // done return; } gradients_.set_size( gradients[layer]->size() ); for( t_CKINT i = 0; i < gradients_.size(); i++ ) gradients_.m_vector[i] = gradients[layer]->v( i ); } // get_activations void get_activations( t_CKINT layer, Chuck_Array8 & activations_ ) { // sanity check if( units_per_layer.size() == 0 ) { // error EM_error3( "MLP.getActivations(): network not initialized" ); // clear activations_.clear(); // done return; } activations_.set_size( activations[layer]->size() ); for( t_CKINT i = 0; i < activations_.size(); i++ ) activations_.m_vector[i] = activations[layer]->v( i ); } // forward void forward( Chuck_Array8 & input_ ) { ChaiVectorFast * input = chuck2chai( input_ ); forward( *input ); SAFE_DELETE( input ); } // backprop void backprop( Chuck_Array8 & output_, t_CKFLOAT learning_rate ) { ChaiVectorFast * output = chuck2chai( output_ ); backprop( *output, learning_rate ); SAFE_DELETE( output ); } // save t_CKBOOL save( ofstream & fout ) { fout << "# layers" << endl; // fout << units_per_layer.size() << endl; for( t_CKINT i = 0; i < units_per_layer.size(); i++ ) fout << units_per_layer[i] << " "; fout << endl; fout << "# activation functions (0=Linear, 1=Sigmoid, 2=ReLU, 3=Tanh, 4=Softmax)" << endl; for( t_CKINT i = 0; i < activation_per_layer.size(); i++ ) fout << activation_per_layer[i] << " "; fout << endl; fout << "# weights" << endl; for( t_CKINT i = 0; i < weights.size(); i++ ) { for( t_CKINT j = 0; j < weights[i]->xDim(); j++ ) { for( t_CKINT k = 0; k < weights[i]->yDim(); k++ ) fout << weights[i]->v( j, k ) << " "; fout << endl; } } fout << "# biases" << endl; for( t_CKINT i = 0; i < biases.size(); i++ ) { for( t_CKINT j = 0; j < biases[i]->size(); j++ ) fout << biases[i]->v( j ) << " "; fout << endl; } return TRUE; } // save t_CKBOOL save( const string & filename ) { // open file ofstream fout( filename.c_str() ); // check if( !fout.good() ) { EM_error3( "MLP.save(): cannot open file for output:\n |- %s", filename.c_str() ); return FALSE; } return save( fout ); } // load t_CKBOOL load( ifstream & fin ) { string line; t_CKINT num; vector units_per_layer_; nextline( fin, line, TRUE ); // get nodes per layer istringstream strin( line ); // tokenize while( strin >> num ) { units_per_layer_.push_back( num ); } t_CKINT num_layers = units_per_layer_.size(); // initialize if( !init( units_per_layer_ ) ) { EM_error3( "MLP.load(): cannot initialize model from file" ); return FALSE; } strin.clear(); nextline( fin, line, TRUE ); strin.str( line ); for( t_CKINT i = 0; i < num_layers - 1; i++ ) strin >> activation_per_layer[i]; weights.resize( num_layers - 1 ); for( t_CKINT i = 0; i < num_layers - 1; i++ ) { weights[i] = new ChaiMatrixFast( units_per_layer[i + 1], units_per_layer[i] ); for( t_CKINT j = 0; j < units_per_layer[i + 1]; j++ ) { strin.clear(); nextline( fin, line, TRUE ); strin.str( line ); for( t_CKINT k = 0; k < units_per_layer[i]; k++ ) strin >> weights[i]->v( j, k ); } } biases.resize( num_layers - 1 ); for( t_CKINT i = 0; i < num_layers - 1; i++ ) { biases[i] = new ChaiVectorFast( units_per_layer[i + 1] ); strin.clear(); nextline( fin, line, TRUE ); strin.str( line ); for( t_CKINT j = 0; j < units_per_layer[i + 1]; j++ ) strin >> biases[i]->v( j ); } return TRUE; } // load t_CKBOOL load( const string & filename ) { // fin ifstream fin( filename.c_str() ); // check if( !fin.good() ) { EM_error3( "MLP.load(): cannot open file:\n |- %s", filename.c_str() ); return FALSE; } return load( fin ); } }; //----------------------------------------------------------------------------- // MLP c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( MLP_ctor ) { MLP_Object * mlp = new MLP_Object(); OBJ_MEMBER_UINT( SELF, MLP_offset_data ) = (t_CKUINT)mlp; } CK_DLL_DTOR( MLP_dtor ) { MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); SAFE_DELETE( mlp ); OBJ_MEMBER_UINT( SELF, MLP_offset_data ) = 0; } CK_DLL_MFUN( MLP_init ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array4 * units_per_layer = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // init mlp->init( units_per_layer->m_vector ); } CK_DLL_MFUN( MLP_init2 ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array4 * units_per_layer = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * activation_per_layer = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // init mlp->init( units_per_layer->m_vector, activation_per_layer->m_vector ); } CK_DLL_MFUN( MLP_init3 ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array4 * units_per_layer = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); t_CKINT activation_function = GET_NEXT_INT( ARGS ); // init mlp->init( units_per_layer->m_vector, activation_function ); } CK_DLL_MFUN( MLP_train ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array4 * inputs = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * outputs = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // train, with defaults mlp->train( *inputs, *outputs, .01, 100 ); } CK_DLL_MFUN( MLP_train2 ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array4 * inputs = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * outputs = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); t_CKFLOAT learning_rate = GET_NEXT_FLOAT( ARGS ); t_CKINT max_iterations = GET_NEXT_INT( ARGS ); // train2 mlp->train( *inputs, *outputs, learning_rate, max_iterations ); } CK_DLL_MFUN( MLP_predict ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array8 * input = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array8 * output = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // predict RETURN->v_int = mlp->predict( *input, *output ); } CK_DLL_MFUN( MLP_get_weights ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args t_CKINT layer = GET_NEXT_INT( ARGS ); Chuck_Array4 * weights = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // get_weights mlp->get_weights( layer, *weights ); } CK_DLL_MFUN( MLP_get_biases ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args t_CKINT layer = GET_NEXT_INT( ARGS ); Chuck_Array8 * biases = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // get_biases mlp->get_biases( layer, *biases ); } CK_DLL_MFUN( MLP_get_gradients ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args t_CKINT layer = GET_NEXT_INT( ARGS ); Chuck_Array8 * gradients = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // get_gradients mlp->get_gradients( layer, *gradients ); } CK_DLL_MFUN( MLP_get_activations ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args t_CKINT layer = GET_NEXT_INT( ARGS ); Chuck_Array8 * activations = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // get_activations mlp->get_activations( layer, *activations ); } CK_DLL_MFUN( MLP_forward ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array8 * input = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // forward mlp->forward( *input ); } CK_DLL_MFUN( MLP_backprop ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_Array8 * output = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); t_CKFLOAT learning_rate = GET_NEXT_FLOAT( ARGS ); // backprop mlp->backprop( *output, learning_rate ); } CK_DLL_MFUN( MLP_save ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_String * filename = GET_NEXT_STRING( ARGS ); // save RETURN->v_int = mlp->save( filename->str() ); } CK_DLL_MFUN( MLP_load ) { // get object MLP_Object * mlp = (MLP_Object *)OBJ_MEMBER_UINT( SELF, MLP_offset_data ); // get args Chuck_String * filename = GET_NEXT_STRING( ARGS ); // load RETURN->v_int = mlp->load( filename->str() ); } CK_DLL_SFUN( MLP_shuffle ) { // get args Chuck_Array4 * X = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array4 * Y = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // shuffle shuffle( *X, *Y ); } //----------------------------------------------------------------------------- // name: struct Wekinator_Model // desc: Wekinator model implementation | added 1.4.2.1 (yikai) //----------------------------------------------------------------------------- struct Wekinator_Model { public: // data vector example_ids; // model MLP_Object * mlp; KNN_Object * knn; SVM_Object * svm; // -- properties -- t_CKINT task_type; // AI.Regression, AI.Classification t_CKINT model_type; // AI.Regression: AI.MLP, AI.LR; AI.Classification: AI.KNN, AI.SVM, AI.DT // AI.Classification t_CKINT classification_classes; // AI.Regression t_CKFLOAT regression_min; t_CKFLOAT regression_max; t_CKBOOL regression_limit; // AI.MLP t_CKINT mlp_hidden_layers; t_CKINT mlp_nodes_per_hidden_layer; t_CKFLOAT mlp_learning_rate; t_CKINT mlp_epochs; // AI.KNN t_CKINT knn_k; // system t_CKBOOL record_status; t_CKBOOL run_status; t_CKBOOL train_status; vector connected_inputs; public: Wekinator_Model() { // model mlp = NULL; knn = NULL; svm = NULL; // -- properties -- task_type = g_tt_regression; model_type = g_mt_mlp; // AI.Classification classification_classes = 0; // AI.Regression regression_min = 0.0; regression_max = 1.0; regression_limit = FALSE; // AI.MLP mlp_hidden_layers = 1; mlp_nodes_per_hidden_layer = 0; mlp_learning_rate = 0.01; mlp_epochs = 100; // AI.KNN knn_k = 1; // system record_status = TRUE; run_status = TRUE; train_status = FALSE; } Wekinator_Model( const Wekinator_Model & other ) { // model mlp = NULL; knn = NULL; svm = NULL; // -- properties -- task_type = other.task_type; model_type = other.model_type; // AI.Classification classification_classes = other.classification_classes; // AI.Regression regression_min = other.regression_min; regression_max = other.regression_max; regression_limit = other.regression_limit; // AI.MLP mlp_hidden_layers = other.mlp_hidden_layers; mlp_nodes_per_hidden_layer = other.mlp_nodes_per_hidden_layer; mlp_learning_rate = other.mlp_learning_rate; mlp_epochs = other.mlp_epochs; // AI.KNN knn_k = other.knn_k; // system record_status = other.record_status; run_status = other.run_status; train_status = FALSE; connected_inputs = other.connected_inputs; } ~Wekinator_Model() { clear(); } void clear() { // data example_ids.clear(); // model SAFE_DELETE( mlp ); SAFE_DELETE( knn ); SAFE_DELETE( svm ); // system connected_inputs.clear(); } void reset_connected_inputs( t_CKINT num_inputs ) { connected_inputs.resize( num_inputs ); for( t_CKINT i = 0; i < num_inputs; i++ ) connected_inputs[i] = i; } void clear_obs( t_CKINT lo, t_CKINT hi ) { // sanity check if( lo > hi ) { EM_error3( "Wekinator.clear_obs: invalid range: %d, %d", lo, hi ); } // erase t_CKINT start = 0, end = 0; for( t_CKINT i = 0; i < example_ids.size(); i++ ) { if( example_ids[i] < lo ) ++start; if( example_ids[i] <= hi ) ++end; } if( start < end ) example_ids.erase( example_ids.begin() + start, example_ids.begin() + end ); } // save void save( ofstream & fout ) { fout << "# obs_ids" << endl; fout << example_ids.size() << endl; for( t_CKINT i = 0; i < example_ids.size(); i++ ) fout << example_ids[i] << " "; fout << endl; fout << "# connected_inputs" << endl; fout << connected_inputs.size() << endl; for( t_CKINT i = 0; i < connected_inputs.size(); i++ ) fout << connected_inputs[i] << " "; fout << endl; fout << "# task_type" << endl; fout << task_type << endl; fout << "# model_type" << endl; fout << model_type << endl; fout << "# classification_classes" << endl; fout << classification_classes << endl; fout << "# regression_min" << endl; fout << regression_min << endl; fout << "# regression_max" << endl; fout << regression_max << endl; fout << "# regression_limit" << endl; fout << regression_limit << endl; fout << "# mlp_hidden_layers" << endl; fout << mlp_hidden_layers << endl; fout << "# mlp_nodes_per_hidden_layer" << endl; fout << mlp_nodes_per_hidden_layer << endl; fout << "# mlp_learning_rate" << endl; fout << mlp_learning_rate << endl; fout << "# mlp_epochs" << endl; fout << mlp_epochs << endl; fout << "# knn_k" << endl; fout << knn_k << endl; fout << "# record_status" << endl; fout << record_status << endl; fout << "# run_status" << endl; fout << run_status << endl; fout << "# train_status" << endl; fout << train_status << endl; if( train_status ) { if( model_type == g_mt_mlp ) { fout << "# mlp" << endl; mlp->save( fout ); } } } // load void load( ifstream & fin ) { // clear clear(); t_CKINT num; string line; istringstream strin; // obs_ids nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> num; if (num > 0) { nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); example_ids.resize( num ); for( t_CKINT i = 0; i < num; i++ ) strin >> example_ids[i]; } // connected_inputs nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> num; if (num > 0) { nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); connected_inputs.resize( num ); for( t_CKINT i = 0; i < num; i++ ) strin >> connected_inputs[i]; } // task_type nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> task_type; // model_type nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> model_type; // classification_classes nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> classification_classes; // regression_min nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> regression_min; // regression_max nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> regression_max; // regression_limit nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> regression_limit; // mlp_hidden_layers nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> mlp_hidden_layers; // mlp_nodes_per_hidden_layer nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> mlp_nodes_per_hidden_layer; // mlp_learning_rate nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> mlp_learning_rate; // mlp_epochs nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> mlp_epochs; // knn_k nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> knn_k; // record_status nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> record_status; // run_status nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> run_status; // train_status nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> train_status; if( train_status ) { if( model_type == g_mt_mlp ) { mlp = new MLP_Object(); mlp->load( fin ); } } } }; //----------------------------------------------------------------------------- // name: struct Wekinator_Object // desc: Wekinator implementation | 1.4.2.1 (yikai) added // adapted from Rebecca Fiebrink's Wekinator java/Weka implementation //----------------------------------------------------------------------------- struct Wekinator_Object { private: // input t_CKINT num_inputs; vector inputs; vector inputs_names; // output t_CKINT num_outputs; vector outputs; vector outputs_names; // model Wekinator_Model dummy_model; vector models; // data vector > examples; t_CKINT recording_round; //------------------------------------------------------------------------- // why train a separate model for each output channel? /* let's ask chatGPT (a stochastic parrot that hallucinates -M.S.) Ge: in working with multi-layer perceptrons, what is the difference between having all output dimensions in one network, versus having a separate network trained on each dimension of the output? chatGPT: In multi-layer perceptrons (MLPs), the number of output dimensions represents the number of distinct classes or values that the model can predict. Having all output dimensions in one network means that the MLP outputs vector with multiple dimensions, each representing a distinct class or value that the model can predict. This approach is often used in multi-class classification problems or in problems where the output is a vector of continuous values (e.g., regression problems). The advantage of this approach is that the model can learn complex relationships between input features and output classes/values, and it can capture dependencies between different output dimensions. On the other hand, having a separate network trained on each dimension of the output means that multiple MLPs are trained, each one responsible for predicting a single dimension of the output. This approach is often used in problems where the output dimensions are independent of each other (e.g., multi-target regression problems). The advantage of this approach is that it can be easier to train and optimize each individual MLP, and it can also be more computationally efficient than training a single MLP with multiple output dimensions. In general, whether to use a single MLP with multiple output dimensions or multiple MLPs depends on the specific problem and the nature of the output variables. It's worth noting that some architectures, such as the encoder-decoder architecture, combine both approaches by using a single etwork to encode input features and separate networks to decode the output into multiple dimensions. */ //------------------------------------------------------------------------- public: // constructor Wekinator_Object() { num_inputs = 0; num_outputs = 0; recording_round = 0; } // destructor ~Wekinator_Object() { inputs.clear(); inputs_names.clear(); outputs.clear(); outputs_names.clear(); examples.clear(); models.clear(); } // clear_all_obs void clear_all_obs() { examples.clear(); for( t_CKINT i = 0; i < models.size(); i++ ) models[i].example_ids.clear(); recording_round = 0; } // set_num_inputs void set_num_inputs( t_CKINT num_inputs_ ) { if( num_inputs_ < 0 ) { EM_error3( "Wekinator.set_num_inputs: number of inputs must be positive." ); return; } if( num_inputs_ == num_inputs ) return; num_inputs = num_inputs_; inputs.resize( num_inputs ); inputs_names.resize( num_inputs ); for( t_CKINT i = 0; i < num_inputs; i++ ) inputs_names[i] = "input-" + to_string( i ); dummy_model.reset_connected_inputs( num_inputs ); for( t_CKINT i = 0; i < models.size(); i++ ) models[i].reset_connected_inputs( num_inputs ); clear_all_obs(); } // set_num_outputs void set_num_outputs( t_CKINT num_outputs_ ) { if( num_outputs_ < 0 ) { EM_error3( "Wekinator.set_num_outputs: number of outputs must be positive." ); return; } if( num_outputs_ == num_outputs ) return; num_outputs = num_outputs_; outputs.resize( num_outputs ); outputs_names.resize( num_outputs ); for( t_CKINT i = 0; i < num_outputs; i++ ) outputs_names[i] = "output-" + to_string( i ); models.clear(); for( t_CKINT i = 0; i < num_outputs; i++ ) models.push_back( dummy_model ); clear_all_obs(); } // set_inputs void set_inputs( Chuck_Array8 & inputs_ ) { if( num_inputs == 0 ) set_num_inputs( inputs_.size() ); else if( inputs_.size() != num_inputs ) { EM_error3( "Wekinator.set_inputs: number of inputs does not match." ); return; } inputs = inputs_.m_vector; } // set_outputs void set_outputs( Chuck_Array8 & outputs_ ) { if( num_outputs == 0 ) set_num_outputs( outputs_.size() ); else if( outputs_.size() != num_outputs ) { EM_error3( "Wekinator.set_outputs: number of outputs does not match." ); return; } outputs = outputs_.m_vector; } // train void train() { ChaiMatrixFast X; ChaiMatrixFast Y; t_CKINT n, d, r; for( t_CKINT i = 0; i < num_outputs; i++ ) { // prepare data n = models[i].example_ids.size(); d = models[i].connected_inputs.size(); X.allocate( n, d ); Y.allocate( n, 1 ); for( t_CKINT j = 0; j < n; j++ ) { r = models[i].example_ids[j]; for( t_CKINT k = 0; k < d; k++ ) X( j, k ) = examples[r][3 + models[i].connected_inputs[k]]; Y( j, 0 ) = examples[r][3 + num_inputs + i]; } // prepare model if( models[i].task_type == g_tt_regression ) { if( models[i].model_type == g_mt_mlp ) { SAFE_DELETE( models[i].mlp ); models[i].mlp = new MLP_Object(); // init vector units_per_layer; units_per_layer.push_back( d ); t_CKINT nodes_per_hidden_layer = models[i].mlp_nodes_per_hidden_layer == 0 ? d : models[i].mlp_nodes_per_hidden_layer; for( t_CKINT j = 0; j < models[i].mlp_hidden_layers; j++ ) units_per_layer.push_back( nodes_per_hidden_layer ); units_per_layer.push_back( 1 ); models[i].mlp->init( units_per_layer ); // train models[i].mlp->train( X, Y, n, models[i].mlp_learning_rate, models[i].mlp_epochs ); } else { EM_error3( "Wekinator.train: unknown regression model_type." ); return; } } else if( models[i].task_type == g_tt_classification ) { EM_error3( "Wekinator.train: classification not implemented yet." ); return; } // set status models[i].train_status = TRUE; } } // set_property void set_property( t_CKINT property_type, Chuck_String & property_name, t_CKINT property_value ) { string key = tolower( property_name.str() ); if( property_type == g_tt_classification ) { if( key == "classes" ) { dummy_model.classification_classes = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].classification_classes = property_value; } else { EM_error3( "Wekinator.set_property: unknown classification property." ); return; } } else if( property_type == g_tt_regression ) { if( key == "limit" ) { if( property_value != 0 && property_value != 1 ) { EM_error3( "Wekinator.set_property: regression limit must be true or false." ); return; } dummy_model.regression_limit = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].regression_limit = property_value; } else if( key == "min" ) { dummy_model.regression_min = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].regression_min = property_value; } else if( key == "max" ) { dummy_model.regression_max = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].regression_max = property_value; } else { EM_error3( "Wekinator.set_property: unknown regression property." ); return; } } if( property_type == g_mt_knn ) { if( key == "k" ) { dummy_model.knn_k = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].knn_k = property_value; } else { EM_error3( "Wekinator.set_property: unknown kNN property." ); return; } } else if( property_type == g_mt_mlp ) { if( key == "hiddenlayers" ) { dummy_model.mlp_hidden_layers = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].mlp_hidden_layers = property_value; } else if( key == "nodesperhiddenlayer" ) { dummy_model.mlp_nodes_per_hidden_layer = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].mlp_nodes_per_hidden_layer = property_value; } else if( key == "epochs" ) { dummy_model.mlp_epochs = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].mlp_epochs = property_value; } else { EM_error3( "Wekinator.set_property: unknown MLP property." ); return; } } } // set_property void set_property( t_CKINT property_type, Chuck_String & property_name, t_CKFLOAT property_value ) { string key = tolower( property_name.str() ); if( property_type == g_tt_regression ) { if( key == "min" ) { dummy_model.regression_min = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].regression_min = property_value; } else if( key == "max" ) { dummy_model.regression_max = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].regression_max = property_value; } else { EM_error3( "Wekinator.set_property: unknown regression property." ); return; } } if( property_type == g_mt_mlp ) { if( key == "learningrate" ) { dummy_model.mlp_learning_rate = property_value; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].mlp_learning_rate = property_value; } else { EM_error3( "Wekinator.set_property: unknown MLP property." ); return; } } } // get_property_int t_CKINT get_property_int( t_CKINT property_type, Chuck_String & property_name ) { string key = tolower( property_name.str() ); if( property_type == g_tt_classification ) { if( key == "classes" ) return dummy_model.classification_classes; else { EM_error3( "Wekinator.get_property: unknown classification property." ); return 0; } } else if( property_type == g_tt_regression ) { if( key == "limit" ) return dummy_model.regression_limit; else { EM_error3( "Wekinator.get_property: unknown regression property." ); return 0; } } if( property_type == g_mt_knn ) { if( key == "k" ) return dummy_model.knn_k; else { EM_error3( "Wekinator.get_property: unknown kNN property." ); return 0; } } else if( property_type == g_mt_mlp ) { if( key == "hiddenlayers" ) return dummy_model.mlp_hidden_layers; else if( key == "nodesperhiddenlayer" ) return dummy_model.mlp_nodes_per_hidden_layer == 0 ? num_inputs : dummy_model.mlp_nodes_per_hidden_layer; else if( key == "epochs" ) return dummy_model.mlp_epochs; else { EM_error3( "Wekinator.get_property: unknown MLP property." ); return 0; } } return 0; } // get_property_float t_CKFLOAT get_property_float( t_CKINT property_type, Chuck_String & property_name ) { string key = tolower( property_name.str() ); if( property_type == g_tt_regression ) { if( key == "min" ) return dummy_model.regression_min; else if( key == "max" ) return dummy_model.regression_max; else { EM_error3( "Wekinator.get_property: unknown regression property." ); return 0; } } if( property_type == g_mt_mlp ) { if( key == "learningrate" ) return dummy_model.mlp_learning_rate; else { EM_error3( "Wekinator.get_property: unknown MLP property." ); return 0; } } return 0; } // set_output_property void set_output_property( t_CKINT output_index, Chuck_String & property_name, t_CKINT property_value ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.set_output_property: output index out of range." ); return; } string key = tolower( property_name.str() ); if( key == "tasktype" ) { if( property_value == g_tt_classification || property_value == g_tt_regression ) { models[output_index].task_type = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown output task_type." ); return; } } else if( key == "modeltype" ) { if( models[output_index].task_type == g_tt_classification ) { if( property_value == g_mt_knn ) { models[output_index].model_type = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown classification model_type." ); return; } } else if( models[output_index].task_type == g_tt_regression ) { if( property_value == g_mt_mlp ) { models[output_index].model_type = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown regression model_type." ); return; } } } else { EM_error3( "Wekinator.set_output_property: unknown output property." ); return; } } // set_output_property void set_output_property( t_CKINT output_index, t_CKINT property_type, Chuck_String & property_name, t_CKINT property_value ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.set_output_property: output index out of range." ); return; } string key = tolower( property_name.str() ); if( property_type == g_tt_classification ) { if( key == "classes" ) { models[output_index].classification_classes = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown classification property." ); return; } } else if( property_type == g_tt_regression ) { if( key == "limit" ) { if( property_value != 0 && property_value != 1 ) { EM_error3( "Wekinator.set_output_property: regression limit must be true or false." ); return; } models[output_index].regression_limit = property_value; } else if( key == "min" ) { models[output_index].regression_min = property_value; } else if( key == "max" ) { models[output_index].regression_max = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown regression property." ); return; } } if( property_type == g_mt_knn ) { if( key == "k" ) { models[output_index].knn_k = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown kNN property." ); return; } } else if( property_type == g_mt_mlp ) { if( key == "hiddenlayers" ) { models[output_index].mlp_hidden_layers = property_value; } else if( key == "nodesperhiddenlayer" ) { models[output_index].mlp_nodes_per_hidden_layer = property_value; } else if( key == "epochs" ) { models[output_index].mlp_epochs = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown MLP property." ); return; } } } // set_output_property void set_output_property( t_CKINT output_index, t_CKINT property_type, Chuck_String & property_name, t_CKFLOAT property_value ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.set_output_property: output index out of range." ); return; } string key = tolower( property_name.str() ); if( property_type == g_mt_mlp ) { if( key == "learningrate" ) { models[output_index].mlp_learning_rate = property_value; } else { EM_error3( "Wekinator.set_output_property: unknown MLP property." ); return; } } } // get_output_property_int t_CKINT get_output_property_int( t_CKINT output_index, Chuck_String & property_name ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_output_property: output index out of range." ); return 0; } string key = tolower( property_name.str() ); if( key == "tasktype" ) { return models[output_index].task_type; } else if( key == "modeltype" ) { return models[output_index].model_type; } else { EM_error3( "Wekinator.get_output_property: unknown output property." ); return 0; } } // get_output_property_float t_CKFLOAT get_output_property_float( t_CKINT output_index, Chuck_String & property_name ) { EM_error3( "Wekinator.get_output_property: unknown output property." ); return 0; } // get_output_property_int1 t_CKINT get_output_property_int( t_CKINT output_index, t_CKINT property_type, Chuck_String & property_name ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_output_property: output index out of range." ); return 0; } string key = tolower( property_name.str() ); if( property_type == g_tt_classification ) { if( key == "classes" ) { return models[output_index].classification_classes; } else { EM_error3( "Wekinator.get_output_property: unknown classification property." ); return 0; } } else if( property_type == g_tt_regression ) { if( key == "limit" ) { return models[output_index].regression_limit; } else { EM_error3( "Wekinator.get_output_property: unknown regression property." ); return 0; } } if( property_type == g_mt_knn ) { if( key == "k" ) { return models[output_index].knn_k; } else { EM_error3( "Wekinator.get_output_property: unknown kNN property." ); return 0; } } else if( property_type == g_mt_mlp ) { if( key == "hiddenlayers" ) { return models[output_index].mlp_hidden_layers; } else if( key == "nodesperhiddenlayer" ) { return models[output_index].mlp_nodes_per_hidden_layer == 0 ? num_inputs : models[output_index].mlp_nodes_per_hidden_layer; } else if( key == "epochs" ) { return models[output_index].mlp_epochs; } else { EM_error3( "Wekinator.get_output_property: unknown MLP property." ); return 0; } } return 0; } // get_output_property_float1 t_CKFLOAT get_output_property_float( t_CKINT output_index, t_CKINT property_type, Chuck_String & property_name ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_output_property: output index out of range." ); return 0; } string key = tolower( property_name.str() ); if( property_type == g_tt_regression ) { if( key == "min" ) { return models[output_index].regression_min; } else if( key == "max" ) { return models[output_index].regression_max; } else { EM_error3( "Wekinator.get_output_property: unknown regression property." ); return 0; } } if( property_type == g_mt_mlp ) { if( key == "learningrate" ) { return models[output_index].mlp_learning_rate; } else { EM_error3( "Wekinator.get_output_property: unknown MLP property." ); return 0; } } return 0; } // set_output_property3 void set_output_property( t_CKINT output_index, Chuck_String & property_name, Chuck_Array4 & property_value_ ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.set_output_property: output index out of range." ); return; } // set string key = tolower( property_name.str() ); t_CKINT value; if( key == "connectedinputs" ) { models[output_index].connected_inputs.clear(); for( t_CKINT i = 0; i < property_value_.size(); i++ ) { value = property_value_.m_vector[i]; // sanity check if( value < 0 || value >= num_inputs ) { EM_error3( "Wekinator.set_output_property: input index out of range." ); return; } models[output_index].connected_inputs.push_back( value ); } } else { EM_error3( "Wekinator.set_output_property: unknown output property." ); return; } } // get_output_property void get_output_property( t_CKINT output_index, Chuck_String & property_name, Chuck_Array4 & property_value_ ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_output_property: output index out of range." ); return; } // get string key = tolower( property_name.str() ); if( key == "connectedinputs" ) { property_value_.set_size( models[output_index].connected_inputs.size() ); for( t_CKINT i = 0; i < models[output_index].connected_inputs.size(); i++ ) { property_value_.m_vector[i] = models[output_index].connected_inputs[i]; } } else { EM_error3( "Wekinator.get_output_property: unknown output property." ); return; } } // get_num_inputs t_CKINT get_num_inputs() { return num_inputs; } // get_num_outputs t_CKINT get_num_outputs() { return num_outputs; } // push back inputs and outputs void push_back_example( const vector & inputs_, const vector & outputs_ ) { vector example( 3 + num_inputs + num_outputs ); example[0] = examples.size(); example[1] = 0; example[2] = recording_round; for( t_CKINT i = 0; i < num_inputs; i++ ) example[3 + i] = inputs_[i]; for( t_CKINT i = 0; i < num_outputs; i++ ) example[3 + num_inputs + i] = outputs_[i]; examples.push_back( example ); } // add void add() { push_back_example( inputs, outputs ); if( dummy_model.record_status ) dummy_model.example_ids.push_back( examples.size() - 1 ); for( t_CKINT i = 0; i < num_outputs; i++ ) if( models[i].record_status ) models[i].example_ids.push_back( examples.size() - 1 ); } // predict void predict( Chuck_Array8 & inputs_, Chuck_Array8 & outputs_ ) { // sanity check if( inputs_.size() != num_inputs ) { EM_error3( "Wekinator.predict: input array size mismatch." ); return; } outputs_.set_size( num_outputs ); outputs_.zero(); for( t_CKINT i = 0; i < num_outputs; i++ ) { if( !models[i].train_status ) { EM_error3( "Wekinator.run: model not trained." ); return; } if( !models[i].run_status ) continue; // prepare data ChaiVectorFast x( models[i].connected_inputs.size() ); ChaiVectorFast y( 1 ); for( t_CKINT j = 0; j < x.size(); j++ ) x[j] = inputs_.m_vector[models[i].connected_inputs[j]]; // predict if( models[i].task_type == g_tt_regression ) { if( models[i].model_type == g_mt_mlp ) { models[i].mlp->predict( x, y ); outputs_.m_vector[i] = y[0]; } else { EM_error3( "Wekinator.predict: unknown regression model_type." ); return; } if( models[i].regression_limit ) { if( outputs_.m_vector[i] < models[i].regression_min ) outputs_.m_vector[i] = models[i].regression_min; else if( outputs_.m_vector[i] > models[i].regression_max ) outputs_.m_vector[i] = models[i].regression_max; } } else if( models[i].task_type == g_tt_classification ) { EM_error3( "Wekinator.predict: classification not implemented." ); return; } } } // set_task_type void set_task_type( t_CKINT task_type ) { if( task_type != g_tt_regression && task_type != g_tt_classification ) { EM_error3( "Wekinator.set_task_type: unknown task_type." ); return; } dummy_model.task_type = task_type; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].task_type = task_type; } // get_task_type t_CKINT get_task_type() { return dummy_model.task_type; } // get_task_type_name void get_task_type_name( Chuck_String & name ) { if( dummy_model.task_type == g_tt_regression ) name.set( "AI.Regression" ); else if( dummy_model.task_type == g_tt_classification ) name.set( "AI.Classification" ); } // set_model_type void set_model_type( t_CKINT model_type ) { if( dummy_model.task_type == g_tt_classification ) { if( model_type != g_mt_knn ) { EM_error3( "Wekinator.set_model_type: unknown model_type for classification. Available: AI.KNN." ); return; } dummy_model.model_type = model_type; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].model_type = model_type; } else if( dummy_model.task_type == g_tt_regression ) { if( model_type != g_mt_mlp ) { EM_error3( "Wekinator.set_model_type: unknown model_type for regression. Available: AI.MLP" ); return; } dummy_model.model_type = model_type; for( t_CKINT i = 0; i < models.size(); i++ ) models[i].model_type = model_type; } } // get_model_type t_CKINT get_model_type() { return dummy_model.model_type; } // get_model_type_name void get_model_type_name( Chuck_String & name ) { if( dummy_model.model_type == g_mt_mlp ) name.set( "AI.MLP" ); else if( dummy_model.model_type == g_mt_svm ) name.set( "AI.SVM" ); else if( dummy_model.model_type == g_mt_knn ) name.set( "AI.KNN" ); } // randomize_outputs void randomize_outputs() { t_CKFLOAT random_min, random_max; for( t_CKINT i = 0; i < num_outputs; i++ ) { random_min = models[i].regression_min; random_max = models[i].regression_max; outputs[i] = ck_random() / (t_CKFLOAT)CK_RANDOM_MAX * ( random_max - random_min ) + random_min; } } // get_num_obs t_CKINT get_num_obs() { return dummy_model.example_ids.size(); } // get_num_obs1 t_CKINT get_num_obs1( t_CKINT output_index ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_num_obs: output_index out of range." ); return 0; } return models[output_index].example_ids.size(); } // get_obs void get_obs( Chuck_Array4 & obs_ ) { // sanity check if( obs_.size() < dummy_model.example_ids.size() ) { EM_error3( "Wekinator.get_obs: input array too small." ); return; } Chuck_Array8 * row; t_CKINT dim = 3 + num_inputs + num_outputs; // copy for( t_CKINT i = 0; i < dummy_model.example_ids.size(); i++ ) { row = (Chuck_Array8 *)obs_.m_vector[i]; row->set_size( dim ); row->m_vector = examples[dummy_model.example_ids[i]]; } } // get_obs1 void get_obs1( t_CKINT output_index, Chuck_Array4 & obs_ ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_obs1: output_index out of range." ); return; } if( obs_.size() < models[output_index].example_ids.size() ) { EM_error3( "Wekinator.get_obs1: input array too small." ); return; } Chuck_Array8 * row; t_CKINT dim = 3 + num_inputs + num_outputs; // copy for( t_CKINT i = 0; i < models[output_index].example_ids.size(); i++ ) { row = (Chuck_Array8 *)obs_.m_vector[i]; row->set_size( dim ); row->m_vector = examples[models[output_index].example_ids[i]]; } } // clear_obs void clear_obs( t_CKINT lo, t_CKINT hi ) { dummy_model.clear_obs( lo, hi ); for( t_CKINT i = 0; i < num_outputs; i++ ) models[i].clear_obs( lo, hi ); } // clear_obs1 void clear_obs1( t_CKINT output_index, t_CKINT lo, t_CKINT hi ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.clear_obs1: output_index out of range." ); return; } models[output_index].clear_obs( lo, hi ); } // save t_CKBOOL save( Chuck_String & filename ) { ofstream fout( filename.str().c_str() ); // check if( !fout.good() ) { EM_error3( "Wekinator.save: could not open file '%s' for writing.", filename.str().c_str() ); return FALSE; } // data fout << "## data" << endl; fout << "# num_inputs" << endl; fout << num_inputs << endl; fout << "# num_outputs" << endl; fout << num_outputs << endl; fout << "# num_obs" << endl; t_CKINT num_obs = dummy_model.example_ids.size(), dim = 3 + num_inputs + num_outputs; fout << num_obs << endl; fout << "# obs" << endl; for( t_CKINT i = 0; i < num_obs; i++ ) { for( t_CKINT j = 0; j < dim; j++ ) fout << examples[dummy_model.example_ids[i]][j] << " "; fout << endl; } // model fout << "## models" << endl; for( t_CKINT i = 0; i < num_outputs; i++ ) { fout << "# output-" << i << endl; models[i].save( fout ); } return TRUE; } // load t_CKBOOL load( Chuck_String & filename ) { ifstream fin( filename.str().c_str() ); // check if( !fin.good() ) { EM_error3( "Wekinator.load: could not open file '%s' for reading.", filename.str().c_str() ); return FALSE; } t_CKINT num; string line; istringstream strin; // num_inputs nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> num; set_num_inputs( num ); // num_outputs nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> num; set_num_outputs( num ); t_CKINT dim = 3 + num_inputs + num_outputs, id = 0, target; vector example( dim ); // num_obs nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); strin >> num; // obs for( t_CKINT i = 0; i < num; i++ ) { nextline( fin, line, TRUE ); strin.clear(); strin.str( line ); for( t_CKINT j = 0; j < dim; j++ ) strin >> example[j]; target = (t_CKINT)( example[0] + 0.5 ); while( id <= target ) { examples.push_back( example ); id++; } dummy_model.example_ids.push_back( target ); } // models for( t_CKINT i = 0; i < num_outputs; i++ ) { models[i].load( fin ); } return TRUE; } // save_obs_as_arff void save_obs_as_arff( const string & filename, const vector example_ids_ ) { ofstream fout( filename.c_str() ); // check if( !fout.good() ) { EM_error3( "Wekinator.export_obs: could not open file '%s' for writing.", filename.c_str() ); return; } string header = "@relation dataset" "\n" "@attribute ID numeric" "@attribute Time date 'yyyy/MM/dd HH:mm:ss:SSS'" "@attribute 'Training round' numeric"; fout << header << endl; for( t_CKINT i = 0; i < num_inputs; i++ ) { fout << "@attribute inputs-" << i + 1 << " numeric" << endl; } for( t_CKINT i = 0; i < num_outputs; i++ ) { fout << "@attribute outputs-" << i + 1 << " numeric" << endl; } t_CKINT index, dim = num_inputs + num_outputs; fout << "\n@data" << endl; for( t_CKINT i = 0; i < example_ids_.size(); i++ ) { index = example_ids_[i]; fout << index << ", 2023/01/01 00:00:00:000, " << (t_CKINT)( examples[index][2] + 0.5 ); for( t_CKINT j = 0; j < dim; j++ ) { fout << ", " << examples[index][3 + j]; } fout << endl; } } // export_obs void export_obs( Chuck_String & filename ) { save_obs_as_arff( filename.str(), dummy_model.example_ids ); } // export_obs1 void export_obs1( t_CKINT output_index, Chuck_String & filename ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.export_obs: output_index out of range." ); return; } // export save_obs_as_arff( filename.str(), models[output_index].example_ids ); } // import_obs void import_obs( Chuck_String & filename ) { // init t_CKINT x_size = 0, y_size = 0; ifstream fin( filename.str().c_str() ); string line; while( getline( fin, line ) ) { if( line.find( "@attribute" ) != string::npos ) { if( line.find( "inputs" ) != string::npos ) { x_size++; } else if( line.find( "outputs" ) != string::npos ) { y_size++; } } else if( line.find( "@data" ) != string::npos ) { break; } } set_num_inputs( x_size ); set_num_outputs( y_size ); // load stringstream ss; string token; t_CKINT index, size = 3 + x_size + y_size; vector example( size ); while( getline( fin, line ) ) { ss.clear(); ss.str( line ); index = 0; while( getline( ss, token, ',' ) ) { if( index == size ) break; else if( index == 1 ) example[1] = 0; else { example[index] = atof( token.c_str() ); } ++index; } examples.push_back( example ); dummy_model.example_ids.push_back( examples.size() - 1 ); for( t_CKINT i = 0; i < models.size(); i++ ) models[i].example_ids.push_back( examples.size() - 1 ); } } // clear_all_obs1 void clear_all_obs1( t_CKINT output_index ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.clear_all_obs1: output_index out of range." ); return; } // clear models[output_index].example_ids.clear(); } // set_all_record_status void set_all_record_status( t_CKINT status ) { if( status != 0 && status != 1 ) { EM_error3( "Wekinator.set_all_record_status: status must be true or false." ); return; } dummy_model.record_status = status; for( t_CKINT i = 0; i < num_outputs; i++ ) models[i].record_status = status; } // set_output_record_status void set_output_record_status( t_CKINT output_index, t_CKINT status ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.set_output_record_status: output_index out of range." ); return; } if( status != 0 && status != 1 ) { EM_error3( "Wekinator.set_output_record_status: status must be true or false." ); return; } // set models[output_index].record_status = status; } // get_output_record_status t_CKINT get_output_record_status( t_CKINT output_index ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_output_record_status: output_index out of range." ); return 0; } // get return models[output_index].record_status; } // set_all_run_status void set_all_run_status( t_CKINT status ) { if( status != 0 && status != 1 ) { EM_error3( "Wekinator.set_all_run_status: status must be true or false." ); return; } dummy_model.run_status = status; for( t_CKINT i = 0; i < num_outputs; i++ ) models[i].run_status = status; } // set_output_run_status void set_output_run_status( t_CKINT output_index, t_CKINT status ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.set_output_run_status: output_index out of range." ); return; } if( status != 0 && status != 1 ) { EM_error3( "Wekinator.set_output_run_status: status must be true or false." ); return; } // set models[output_index].run_status = status; } // get_output_run_status t_CKINT get_output_run_status( t_CKINT output_index ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.get_output_run_status: output_index out of range." ); return 0; } // get return models[output_index].run_status; } // add1 void add1( Chuck_Array8 & inputs_, Chuck_Array8 & outputs_ ) { // sanity check if( inputs_.size() != num_inputs ) { EM_error3( "Wekinator.add1: inputs array size does not match number of inputs." ); return; } if( outputs_.size() != num_outputs ) { EM_error3( "Wekinator.add1: outputs array size does not match number of outputs." ); return; } push_back_example( inputs_.m_vector, outputs_.m_vector ); if( dummy_model.record_status ) dummy_model.example_ids.push_back( examples.size() - 1 ); for( t_CKINT i = 0; i < num_outputs; i++ ) if( models[i].record_status ) models[i].example_ids.push_back( examples.size() - 1 ); } // add2 void add2( t_CKINT output_index, Chuck_Array8 & inputs_, Chuck_Array8 & outputs_ ) { // sanity check if( output_index < 0 || output_index >= num_outputs ) { EM_error3( "Wekinator.add2: output_index out of range." ); return; } if( inputs_.size() != num_inputs ) { EM_error3( "Wekinator.add2: inputs array size does not match number of inputs." ); return; } if( outputs_.size() != num_outputs ) { EM_error3( "Wekinator.add2: outputs array size does not match number of outputs." ); return; } push_back_example( inputs_.m_vector, outputs_.m_vector ); dummy_model.example_ids.push_back( examples.size() - 1 ); models[output_index].example_ids.push_back( examples.size() - 1 ); } // next_round void next_round() { ++recording_round; } // delete_last_round void delete_last_round() { t_CKINT start = 0, end = examples.size(); for( t_CKINT i = 0; i < end; i++ ) { if( recording_round == (t_CKINT)( examples[i][2] + 0.5 ) ) { start = i; break; } } clear_obs( start, end ); } // clear void clear() { clear_all_obs(); set_num_inputs( 0 ); set_num_outputs( 0 ); recording_round = 0; } // get_round t_CKINT get_round() { return recording_round; } // get_all_record_status t_CKINT get_all_record_status() { return dummy_model.record_status; } // get_all_run_status t_CKINT get_all_run_status() { return dummy_model.run_status; } }; //----------------------------------------------------------------------------- // Wekinator c++ hooks //----------------------------------------------------------------------------- CK_DLL_CTOR( Wekinator_ctor ) { Wekinator_Object * wekinator = new Wekinator_Object(); OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ) = (t_CKUINT)wekinator; } CK_DLL_DTOR( Wekinator_dtor ) { Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); SAFE_DELETE( wekinator ); OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ) = 0; } CK_DLL_MFUN( Wekinator_set_num_inputs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT num_inputs = GET_NEXT_INT( ARGS ); // set_num_inputs wekinator->set_num_inputs( num_inputs ); // pass through RETURN->v_int = num_inputs; } CK_DLL_MFUN( Wekinator_get_num_inputs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_num_inputs RETURN->v_int = wekinator->get_num_inputs(); } CK_DLL_MFUN( Wekinator_set_num_outputs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT num_outputs = GET_NEXT_INT( ARGS ); // set_num_outputs wekinator->set_num_outputs( num_outputs ); // pass throught RETURN->v_int = num_outputs; } CK_DLL_MFUN( Wekinator_get_num_outputs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_num_outputs RETURN->v_int = wekinator->get_num_outputs(); } CK_DLL_MFUN( Wekinator_set_task_type ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT task_type = GET_NEXT_INT( ARGS ); // set_task_type wekinator->set_task_type( task_type ); } CK_DLL_MFUN( Wekinator_get_task_type ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_task_type RETURN->v_int = wekinator->get_task_type(); } CK_DLL_MFUN( Wekinator_get_task_type_name ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // make a new chuck string Chuck_String * name = (Chuck_String *)instantiate_and_initialize_object( VM->env()->t_string, SHRED ); // get_task_type_name wekinator->get_task_type_name( *name ); // return RETURN->v_object = name; } CK_DLL_MFUN( Wekinator_set_property ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); t_CKINT property_value = GET_NEXT_INT( ARGS ); // set_property wekinator->set_property( property_type, *property_name, property_value ); } CK_DLL_MFUN( Wekinator_set_property1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); t_CKFLOAT property_value = GET_NEXT_FLOAT( ARGS ); // set_property1 wekinator->set_property( property_type, *property_name, property_value ); } CK_DLL_MFUN( Wekinator_get_property_int ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); // get_property_int RETURN->v_int = wekinator->get_property_int( property_type, *property_name ); } CK_DLL_MFUN( Wekinator_get_property_float ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); // get_property_float RETURN->v_float = wekinator->get_property_float( property_type, *property_name ); } CK_DLL_MFUN( Wekinator_set_model_type ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT model_type = GET_NEXT_INT( ARGS ); // set_model_type wekinator->set_model_type( model_type ); } CK_DLL_MFUN( Wekinator_get_model_type ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_model_type RETURN->v_int = wekinator->get_model_type(); } CK_DLL_MFUN( Wekinator_get_model_type_name ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // make a chuck string Chuck_String * name = (Chuck_String *)instantiate_and_initialize_object( VM->env()->t_string, SHRED ); // get_model_type_name wekinator->get_model_type_name( *name ); // return it RETURN->v_object = name; } CK_DLL_MFUN( Wekinator_set_output_property ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); t_CKINT property_value = GET_NEXT_INT( ARGS ); // set_output_property wekinator->set_output_property( output_index, *property_name, property_value ); } CK_DLL_MFUN( Wekinator_get_output_property_int ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); // get_output_property_int RETURN->v_int = wekinator->get_output_property_int( output_index, *property_name ); } CK_DLL_MFUN( Wekinator_set_output_property1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); t_CKINT property_value = GET_NEXT_INT( ARGS ); // set_output_property1 wekinator->set_output_property( output_index, property_type, *property_name, property_value ); } CK_DLL_MFUN( Wekinator_set_output_property2 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); t_CKFLOAT property_value = GET_NEXT_FLOAT( ARGS ); // set_output_property2 wekinator->set_output_property( output_index, property_type, *property_name, property_value ); } CK_DLL_MFUN( Wekinator_get_output_property_int1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); // get_output_property_int1 RETURN->v_int = wekinator->get_output_property_int( output_index, property_type, *property_name ); } CK_DLL_MFUN( Wekinator_get_output_property_float ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); t_CKINT property_type = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); // get_output_property_float RETURN->v_float = wekinator->get_output_property_float( output_index, property_type, *property_name ); } CK_DLL_MFUN( Wekinator_set_output_property3 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); Chuck_Array4 * property_value = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // set_output_property3 wekinator->set_output_property( output_index, *property_name, *property_value ); } CK_DLL_MFUN( Wekinator_get_output_property ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); Chuck_String * property_name = GET_NEXT_STRING( ARGS ); Chuck_Array4 * property_value = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // get_output_property wekinator->get_output_property( output_index, *property_name, *property_value ); } CK_DLL_MFUN( Wekinator_set_inputs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_Array8 * inputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // set_inputs wekinator->set_inputs( *inputs ); } CK_DLL_MFUN( Wekinator_set_outputs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_Array8 * outputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // set_outputs wekinator->set_outputs( *outputs ); } CK_DLL_MFUN( Wekinator_randomize_outputs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // randomize_outputs wekinator->randomize_outputs(); } CK_DLL_MFUN( Wekinator_get_num_obs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_num_obs RETURN->v_int = wekinator->get_num_obs(); } CK_DLL_MFUN( Wekinator_get_num_obs1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); // get_num_obs1 RETURN->v_int = wekinator->get_num_obs1( output_index ); } CK_DLL_MFUN( Wekinator_get_obs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_Array4 * obs = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // get_obs wekinator->get_obs( *obs ); } CK_DLL_MFUN( Wekinator_get_obs1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); Chuck_Array4 * obs = (Chuck_Array4 *)GET_NEXT_OBJECT( ARGS ); // get_obs1 wekinator->get_obs1( output_index, *obs ); } CK_DLL_MFUN( Wekinator_clear_obs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT lo = GET_NEXT_INT( ARGS ); t_CKINT hi = GET_NEXT_INT( ARGS ); // clear_obs wekinator->clear_obs( lo, hi ); } CK_DLL_MFUN( Wekinator_clear_obs1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); t_CKINT lo = GET_NEXT_INT( ARGS ); t_CKINT hi = GET_NEXT_INT( ARGS ); // clear_obs1 wekinator->clear_obs1( output_index, lo, hi ); } CK_DLL_MFUN( Wekinator_save ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_String * filename = GET_NEXT_STRING( ARGS ); // save wekinator->save( *filename ); } CK_DLL_MFUN( Wekinator_load ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_String * filename = GET_NEXT_STRING( ARGS ); // load wekinator->load( *filename ); } CK_DLL_MFUN( Wekinator_export_obs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_String * filename = GET_NEXT_STRING( ARGS ); // export_obs wekinator->export_obs( *filename ); } CK_DLL_MFUN( Wekinator_export_obs1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); Chuck_String * filename = GET_NEXT_STRING( ARGS ); // export_obs1 wekinator->export_obs1( output_index, *filename ); } CK_DLL_MFUN( Wekinator_import_obs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_String * filename = GET_NEXT_STRING( ARGS ); // import_obs wekinator->import_obs( *filename ); } CK_DLL_MFUN( Wekinator_clear_all_obs ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // clear_all_obs wekinator->clear_all_obs(); } CK_DLL_MFUN( Wekinator_clear_all_obs1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); // clear_all_obs1 wekinator->clear_all_obs1( output_index ); } CK_DLL_MFUN( Wekinator_set_all_record_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT status = GET_NEXT_INT( ARGS ); // set_all_record_status wekinator->set_all_record_status( status ); } CK_DLL_MFUN( Wekinator_set_output_record_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); t_CKINT status = GET_NEXT_INT( ARGS ); // set_output_record_status wekinator->set_output_record_status( output_index, status ); } CK_DLL_MFUN( Wekinator_get_output_record_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); // get_output_record_status RETURN->v_int = wekinator->get_output_record_status( output_index ); } CK_DLL_MFUN( Wekinator_set_all_run_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT status = GET_NEXT_INT( ARGS ); // set_all_run_status wekinator->set_all_run_status( status ); } CK_DLL_MFUN( Wekinator_set_output_run_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); t_CKINT status = GET_NEXT_INT( ARGS ); // set_output_run_status wekinator->set_output_run_status( output_index, status ); } CK_DLL_MFUN( Wekinator_get_output_run_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); // get_output_run_status RETURN->v_int = wekinator->get_output_run_status( output_index ); } CK_DLL_MFUN( Wekinator_add ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // add wekinator->add(); } CK_DLL_MFUN( Wekinator_add1 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_Array8 * inputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array8 * outputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // add1 wekinator->add1( *inputs, *outputs ); } CK_DLL_MFUN( Wekinator_add2 ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args t_CKINT output_index = GET_NEXT_INT( ARGS ); Chuck_Array8 * inputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array8 * outputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // add2 wekinator->add2( output_index, *inputs, *outputs ); } CK_DLL_MFUN( Wekinator_next_round ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // next_round wekinator->next_round(); } CK_DLL_MFUN( Wekinator_train ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // train wekinator->train(); } CK_DLL_MFUN( Wekinator_predict ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get args Chuck_Array8 * inputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); Chuck_Array8 * outputs = (Chuck_Array8 *)GET_NEXT_OBJECT( ARGS ); // predict wekinator->predict( *inputs, *outputs ); } CK_DLL_MFUN( Wekinator_delete_last_round ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // delete_last_round wekinator->delete_last_round(); } CK_DLL_MFUN( Wekinator_clear ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // clear wekinator->clear(); } CK_DLL_MFUN( Wekinator_get_round ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_round RETURN->v_int = wekinator->get_round(); } CK_DLL_MFUN( Wekinator_get_all_record_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_all_record_status RETURN->v_int = wekinator->get_all_record_status(); } CK_DLL_MFUN( Wekinator_get_all_run_status ) { // get object Wekinator_Object * wekinator = (Wekinator_Object *)OBJ_MEMBER_UINT( SELF, Wekinator_offset_data ); // get_all_run_status RETURN->v_int = wekinator->get_all_run_status(); } //----------------------------------------------------------------------------- // KDTree implementation //----------------------------------------------------------------------------- static inline t_CKINT is_leaf( struct kdnode * node ) { return node->left == node->right; } static inline void swap( long * a, long * b ) { long tmp = *a; *a = *b; *b = tmp; } static inline double square( double d ) { return d * d; } static inline double distance( double * c1, double * c2, t_CKINT dim ) { double distance = 0; while( dim-- > 0 ) { distance += square( *c1++ - *c2++ ); } return distance; } static inline double knn_max( struct kdtree * tree ) { return tree->knn_list_head.prev->distance; } static inline double D( struct kdtree * tree, long index, t_CKINT r ) { return tree->coord_table[index][r]; } static inline t_CKINT kdnode_passed( struct kdtree * tree, struct kdnode * node ) { return node != NULL ? tree->coord_passed[node->coord_index] : 1; } static inline t_CKINT knn_search_on( struct kdtree * tree, t_CKINT k, double value, double target ) { return tree->knn_num < k || square( target - value ) < knn_max( tree ); } static inline void coord_index_reset( struct kdtree * tree ) { long i; for( i = 0; i < tree->capacity; i++ ) { tree->coord_indexes[i] = i; } } static inline void coord_table_reset( struct kdtree * tree ) { long i; for( i = 0; i < tree->capacity; i++ ) { tree->coord_table[i] = tree->coords + i * tree->dim; } } static inline void coord_deleted_reset( struct kdtree * tree ) { memset( tree->coord_deleted, 0, tree->capacity ); } static inline void coord_passed_reset( struct kdtree * tree ) { memset( tree->coord_passed, 0, tree->capacity ); } static void coord_dump_all( struct kdtree * tree ) { long i, j; for( i = 0; i < tree->count; i++ ) { long index = tree->coord_indexes[i]; double * coord = tree->coord_table[index]; printf( "(" ); for( j = 0; j < tree->dim; j++ ) { if( j != tree->dim - 1 ) { printf( "%.2f,", coord[j] ); } else { printf( "%.2f)\n", coord[j] ); } } } } static void coord_dump_by_indexes( struct kdtree * tree, long low, long high, t_CKINT r ) { long i; printf( "r=%d:", (int)r ); for( i = 0; i <= high; i++ ) { if( i < low ) { printf( "%8s", " " ); } else { long index = tree->coord_indexes[i]; printf( "%8.2f", tree->coord_table[index][r] ); } } printf( "\n" ); } static void quicksort( struct kdtree * tree, long lo, long hi, t_CKINT r ) { long i, j, pivot, * indexes; // double * p; if( lo >= hi ) { return; } i = lo; j = hi; indexes = tree->coord_indexes; pivot = indexes[lo]; while( i < j ) { while( i < j && D( tree, indexes[j], r ) >= D( tree, pivot, r ) ) j--; /* Loop invariant: nums[j] > pivot or i == j */ indexes[i] = indexes[j]; while( i < j && D( tree, indexes[i], r ) <= D( tree, pivot, r ) ) i++; /* Loop invariant: nums[i] < pivot or i == j */ indexes[j] = indexes[i]; } /* Loop invariant: i == j */ indexes[i] = pivot; quicksort( tree, lo, i - 1, r ); quicksort( tree, i + 1, hi, r ); } static struct kdnode * kdnode_alloc( double * coord, long index, t_CKINT r ) { struct kdnode * node = (struct kdnode *)malloc( sizeof( *node ) ); if( node != NULL ) { memset( node, 0, sizeof( *node ) ); node->coord = coord; node->coord_index = index; node->r = r; } return node; } static void kdnode_free( struct kdnode * node ) { free( node ); } static t_CKINT coord_cmp( double * c1, double * c2, t_CKINT dim ) { t_CKINT i; double ret; for( i = 0; i < dim; i++ ) { ret = *c1++ - *c2++; if( fabs( ret ) >= DBL_EPSILON ) { return ret > 0 ? 1 : -1; } } if( fabs( ret ) < DBL_EPSILON ) { return 0; } else { return ret > 0 ? 1 : -1; } } static void knn_list_add( struct kdtree * tree, struct kdnode * node, double distance ) { if( node == NULL ) return; struct knn_list * head = &tree->knn_list_head; struct knn_list * p = head->prev; if( tree->knn_num == 1 ) { if( p->distance > distance ) { p = p->prev; } } else { while( p != head && p->distance > distance ) { p = p->prev; } } if( p == head || coord_cmp( p->node->coord, node->coord, tree->dim ) ) { struct knn_list * log = (struct knn_list *)malloc( sizeof( *log ) ); if( log != NULL ) { log->node = node; log->distance = distance; log->prev = p; log->next = p->next; p->next->prev = log; p->next = log; tree->knn_num++; } } } static void knn_list_adjust( struct kdtree * tree, struct kdnode * node, double distance ) { if( node == NULL ) return; struct knn_list * head = &tree->knn_list_head; struct knn_list * p = head->prev; if( tree->knn_num == 1 ) { if( p->distance > distance ) { p = p->prev; } } else { while( p != head && p->distance > distance ) { p = p->prev; } } if( p == head || coord_cmp( p->node->coord, node->coord, tree->dim ) ) { struct knn_list * log = head->prev; /* Replace the original max one */ log->node = node; log->distance = distance; /* Remove from the max position */ head->prev = log->prev; log->prev->next = head; /* insert as a new one */ log->prev = p; log->next = p->next; p->next->prev = log; p->next = log; } } static void knn_list_clear( struct kdtree * tree ) { struct knn_list * head = &tree->knn_list_head; struct knn_list * p = head->next; while( p != head ) { struct knn_list * prev = p; p = p->next; free( prev ); } tree->knn_num = 0; } static void resize( struct kdtree * tree ) { tree->capacity *= 2; tree->coords = (double *)realloc( tree->coords, tree->dim * sizeof( double ) * tree->capacity ); tree->coord_table = (double **)realloc( tree->coord_table, sizeof( double * ) * tree->capacity ); tree->coord_indexes = (long *)realloc( tree->coord_indexes, sizeof( long ) * tree->capacity ); tree->coord_deleted = (unsigned char *)realloc( tree->coord_deleted, sizeof( char ) * tree->capacity ); tree->coord_passed = (unsigned char *)realloc( tree->coord_passed, sizeof( char ) * tree->capacity ); coord_table_reset( tree ); coord_index_reset( tree ); coord_deleted_reset( tree ); coord_passed_reset( tree ); } static void kdnode_dump( struct kdnode * node, t_CKINT dim ) { t_CKINT i; if( node->coord != NULL ) { printf( "(" ); for( i = 0; i < dim; i++ ) { if( i != dim - 1 ) { printf( "%.2f,", node->coord[i] ); } else { printf( "%.2f)\n", node->coord[i] ); } } } else { printf( "(none)\n" ); } } void kdtree_insert( struct kdtree * tree, double * coord ) { if( tree->count + 1 > tree->capacity ) { resize( tree ); } memcpy( tree->coord_table[tree->count++], coord, tree->dim * sizeof( double ) ); } static void knn_pickup( struct kdtree * tree, struct kdnode * node, double * target, t_CKINT k ) { double dist = distance( node->coord, target, tree->dim ); if( tree->knn_num < k ) { knn_list_add( tree, node, dist ); } else { if( dist < knn_max( tree ) ) { knn_list_adjust( tree, node, dist ); } else if( fabs( dist - knn_max( tree ) ) < DBL_EPSILON ) { knn_list_add( tree, node, dist ); } } } static void kdtree_search_recursive( struct kdtree * tree, struct kdnode * node, double * target, t_CKINT k, t_CKINT * pickup ) { if( node == NULL || kdnode_passed( tree, node ) ) { return; } t_CKINT r = node->r; if( !knn_search_on( tree, k, node->coord[r], target[r] ) ) { return; } if( is_leaf( node ) ) { *pickup = 1; } else { if( target[r] <= node->coord[r] ) { kdtree_search_recursive( tree, node->left, target, k, pickup ); if( *pickup || node->left == NULL ) { kdtree_search_recursive( tree, node->right, target, k, pickup ); } } else { kdtree_search_recursive( tree, node->right, target, k, pickup ); if( *pickup || node->right == NULL ) { kdtree_search_recursive( tree, node->left, target, k, pickup ); } } } /* back track and pick up */ if( *pickup ) { tree->coord_passed[node->coord_index] = 1; knn_pickup( tree, node, target, k ); } } void kdtree_knn_search( struct kdtree * tree, double * target, t_CKINT k ) { if( k > 0 ) { t_CKINT pickup = 0; // HACK: reset for each new search coord_passed_reset( tree ); struct knn_list * p = tree->knn_list_head.next; while( p != &tree->knn_list_head ) { p->distance = 1e10; p = p->next; } // END HACK kdtree_search_recursive( tree, tree->root, target, k, &pickup ); } } //void kdtree_knn_search( struct kdtree * tree, double * target, t_CKINT k ) //{ // if( k > 0 ) // { // t_CKINT pickup = 0; // kdtree_search_recursive( tree, tree->root, target, k, &pickup ); // } //} void kdtree_delete( struct kdtree * tree, double * coord ) { t_CKINT r = 0; struct kdnode * node = tree->root; struct kdnode * parent = node; while( node != NULL ) { if( node->coord == NULL ) { if( parent->right->coord == NULL ) { break; } else { node = parent->right; continue; } } if( coord[r] < node->coord[r] ) { parent = node; node = node->left; } else if( coord[r] > node->coord[r] ) { parent = node; node = node->right; } else { t_CKINT ret = coord_cmp( coord, node->coord, tree->dim ); if( ret < 0 ) { parent = node; node = node->left; } else if( ret > 0 ) { parent = node; node = node->right; } else { node->coord = NULL; break; } } r = ( r + 1 ) % tree->dim; } } static void kdnode_build( struct kdtree * tree, struct kdnode ** nptr, t_CKINT r, long low, long high ) { if( low == high ) { long index = tree->coord_indexes[low]; *nptr = kdnode_alloc( tree->coord_table[index], index, r ); } else if( low < high ) { /* Sort and fetch the median to build a balanced BST */ quicksort( tree, low, high, r ); long median = low + ( high - low ) / 2; long median_index = tree->coord_indexes[median]; struct kdnode * node = *nptr = kdnode_alloc( tree->coord_table[median_index], median_index, r ); r = ( r + 1 ) % tree->dim; kdnode_build( tree, &node->left, r, low, median - 1 ); kdnode_build( tree, &node->right, r, median + 1, high ); } } static void kdtree_build( struct kdtree * tree ) { kdnode_build( tree, &tree->root, 0, 0, (long)( tree->count - 1 ) ); } void kdtree_rebuild( struct kdtree * tree ) { long i, j; size_t size_of_coord = tree->dim * sizeof( double ); for( i = 0, j = 0; j < tree->count; i++, j++ ) { while( j < tree->count && tree->coord_deleted[j] ) { j++; } if( i != j && j < tree->count ) { memcpy( tree->coord_table[i], tree->coord_table[j], size_of_coord ); tree->coord_deleted[i] = 0; } } tree->count = i; coord_index_reset( tree ); kdtree_build( tree ); } struct kdtree * kdtree_init( t_CKINT dim ) { struct kdtree * tree = (struct kdtree *)malloc( sizeof( *tree ) ); if( tree != NULL ) { tree->root = NULL; tree->dim = dim; tree->count = 0; tree->capacity = 65536; tree->knn_list_head.next = &tree->knn_list_head; tree->knn_list_head.prev = &tree->knn_list_head; tree->knn_list_head.node = NULL; tree->knn_list_head.distance = 0; tree->knn_num = 0; tree->coords = (double *)malloc( dim * sizeof( double ) * tree->capacity ); tree->coord_table = (double **)malloc( sizeof( double * ) * tree->capacity ); tree->coord_indexes = (long *)malloc( sizeof( long ) * tree->capacity ); tree->coord_deleted = (unsigned char *)malloc( sizeof( char ) * tree->capacity ); tree->coord_passed = (unsigned char *)malloc( sizeof( char ) * tree->capacity ); coord_index_reset( tree ); coord_table_reset( tree ); coord_deleted_reset( tree ); coord_passed_reset( tree ); } return tree; } static void kdnode_destroy( struct kdnode * node ) { if( node == NULL ) return; kdnode_destroy( node->left ); kdnode_destroy( node->right ); kdnode_free( node ); } void kdtree_destroy( struct kdtree * tree ) { kdnode_destroy( tree->root ); knn_list_clear( tree ); free( tree->coords ); free( tree->coord_table ); free( tree->coord_indexes ); free( tree->coord_deleted ); free( tree->coord_passed ); free( tree ); }