| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474 |
- #include <stdio.h>
- #include "squirrel.h"
- #include <string.h>
- #include <inttypes.h>
- #include <math.h>
- #include <stdlib.h>
- #include <sys/time.h>
- //#include <pthread.h>
- SQ_OPT_STRING_STRLEN();
- extern "C" {
- #include "nn.h"
- void *ann_malloc(size_t sz)
- {
- return sq_malloc(sz);
- }
- void ann_free(void *p)
- {
- sq_free(p, 0);
- }
- }
- #define NR_FLAG_NONE 0
- #define NR_FLAG_TRAINING (1<<0) /* NN is training in a thread. */
- #define NR_FLAG_REGRESSOR (1<<1) /* NN will be used for regression. */
- #define NR_FLAG_CLASSIFIER (1<<2) /* NN will be used for classification.*/
- #define NR_FLAG_NORMALIZE (1<<3) /* Perform input/output normalization.*/
- #define NR_FLAG_AUTO_STOP (1<<4) /* Auto stop on training. */
- #define NR_FLAG_OF_DETECTED (1<<5) /* Auto stopped on overfitting. */
- #define NR_FLAG_BACKTRACK (1<<6) /* Auto stop with backtracking. */
- /* Flags to persist when saving the NN. */
- #define NR_FLAG_TO_PERSIST (NR_FLAG_REGRESSOR| \
- NR_FLAG_CLASSIFIER| \
- NR_FLAG_NORMALIZE| \
- NR_FLAG_OF_DETECTED)
- /* Flags to transfer after training. */
- #define NR_FLAG_TO_TRANSFER (NR_FLAG_OF_DETECTED)
- #define NR_MAX_LAYERS 32
- #define NR_RDB_ENC_VER 2
- typedef struct {
- uint32_t len, maxlen;
- float *inputs, *outputs;
- } NRDataset;
- typedef struct {
- uint64_t id; /* Neural network unique ID. */
- uint64_t training_total_steps; /* How many steps of trainig the network
- received. A step is a single input/output
- pattern presented to the net (counting
- the same pattern multiple times) */
- uint64_t training_total_ms; /* Total milliseconds time of training. */
- uint64_t training_max_cycles; /* Max cycles of a single training. */
- uint64_t training_max_ms; /* Max time of a single training. */
- uint32_t flags; /* NR_FLAG_... */
- uint32_t epochs; /* Number of training epochs so far. */
- AnnRprop *nn; /* Neural network structure. */
- NRDataset dataset; /* Training dataset. */
- NRDataset test; /* Testing dataset. */
- float dataset_error; /* Average error in the training dataset. */
- float test_error; /* Average error in the test dataset. */
- float test_class_error; /* Percentage of wrong classifications in test
- dataset. Only applicable to nets flagged with
- NR_FLAG_CLASSIFIER. */
- /* For normalized (NR_FLAG_NORMALIZE) networks. */
- float *inorm; /* Inputs normalization factors. */
- float *onorm; /* Outputs normalization factors. */
- } NRTypeObject;
- #if 0
- typedef struct {
- //RedisModuleString *key; /* Key name of the NN we are training.
- // Set to NULL for unused slots. */
- int db_id; /* DB ID where the key is. */
- pthread_t tid; /* Thread ID of the trainer. */
- int in_progress; /* 0 if training terminated. */
- NRTypeObject *nr; /* A copy of the NN we are training. */
- float dataset_error; /* Dataset error in the last cycle. */
- float test_error; /* Test error in the last cycle. */
- float class_error; /* Percentage of wrong classifications. */
- int curcycle; /* Current cycle. */
- } NRPendingTraining;
- #endif
- /* We take an array with NNs currently training in other threads.
- * Every time an NN command is called, we try to see if there are
- * finished trainings, in order to udpate weights of the original
- * NN stored into the key (we work on a copy on the other thread).*/
- #define NR_PENDING_TRAINING_MAX_LEN 32
- #if 0
- #define REDISMODULE_ERR -1
- #define REDISMODULE_OK 0
- #define RedisModuleCtx void
- #define RedisModule_Log(ctx, log_level, msg)
- #define UNUSED(V) ((void) V)
- typedef SQString RedisModuleString;
- static uint64_t NRNextId = 1; /* Next neural network unique ID. */
- #define RedisModule_Alloc(x) sq_malloc(x)
- static void *RedisModule_Calloc(size_t nelm, size_t sz)
- {
- size_t malloc_size = nelm * sz;
- void *ptr = sq_malloc(malloc_size);
- if(ptr) memset(ptr, 0, malloc_size);
- return ptr;
- }
- static void *RedisModule_Realloc(void *oldPtr, size_t sz)
- {
- void *ptr = sq_realloc(oldPtr, 0, sz);
- return ptr;
- }
- #define RedisModule_Free(x) sq_free(x, 0)
- static pthread_mutex_t NRPendingTrainingMutex = PTHREAD_MUTEX_INITIALIZER;
- /* All the followings must be accessed after acquiring the mutex. */
- static NRPendingTraining NRTrainings[NR_PENDING_TRAINING_MAX_LEN];
- static int NRPendingTrainingCount = 0; /* Number of pending trainings. */
- /* ========================== Low level object API ========================== */
- long long NRMilliseconds(void) {
- struct timeval tv;
- long long ust;
- gettimeofday(&tv, NULL);
- ust = ((long long)tv.tv_sec)*1000000;
- ust += tv.tv_usec;
- return ust/1000;
- }
- /* Create a network with the specified parameters. Note that the layers
- * must be specified from the output layer[0] to the input
- * layer[N]. Each element in the integer array 'layer' specify how many
- * units there are in the corresponding layer. */
- static NRTypeObject *createNRTypeObject(int flags, int *layers, int numlayers, int dset_len, int test_len) {
- NRTypeObject *o;
- o = (NRTypeObject*)RedisModule_Calloc(1,sizeof(*o));
- o->id = NRNextId++;
- o->flags = flags;
- o->nn = AnnCreateNet(numlayers,layers);
- o->dataset.maxlen = dset_len;
- o->test.maxlen = test_len;
- int ilen = ANN_INPUT_UNITS(o->nn);
- int olen = ANN_OUTPUT_UNITS(o->nn);
- o->inorm = (float*)RedisModule_Calloc(1,sizeof(float)*ilen);
- o->onorm = (float*)RedisModule_Calloc(1,sizeof(float)*olen);
- for (int j = 0; j < ilen; j++) o->inorm[j] = 1;
- for (int j = 0; j < olen; j++) o->onorm[j] = 1;
- return o;
- }
- /* Insert data (observations needed to train and test the NN) into the
- * NN object. While the learning and testing datasets are yet not full
- * the observed pattern is inserted evenly in one or the other side in
- * order to make sure the two datasets are populated evenly. When both
- * are already full, a random elmenet from one or the other (doing
- * a random weighted choice depending on the length) is substituted with
- * the new item. */
- #define NR_INSERT_NO_TARGET 0 /* Auto select where to insert. */
- #define NR_INSERT_TRAIN 1 /* Insert in training dataset. */
- #define NR_INSERT_TEST 2 /* Insert in testing dataset. */
- static void NRTypeInsertData(NRTypeObject *o, float *inputs, float *outputs,
- int target_ds) {
- NRDataset *target = NULL;
- /* Check if there is no dataset at all. This may be a valid setup
- * with online learning, sample by sample. */
- if (o->dataset.maxlen == 0 && o->test.maxlen == 0) return;
- /* If the user specified a target, select it. */
- if (target_ds == NR_INSERT_TRAIN) target = &o->dataset;
- else if (target_ds == NR_INSERT_TEST) target = &o->test;
- /* If no target is specified, but there is only one possible
- * target, select it ASAP. */
- if (o->dataset.maxlen == 0) {
- target = &o->test;
- } else if (o->test.maxlen == 0) {
- target = &o->dataset;
- }
- /* Otherwise choose as the target to populate the one with less data
- * relatively to its size. */
- if (target == NULL) {
- /* If one of the two datasets are still not full, pick
- * based on fill percentage. Otherwise pick a random
- * target relatively to their size. */
- if (o->dataset.len != o->dataset.maxlen ||
- o->test.len != o->dataset.len)
- {
- float fill_a = (float)o->dataset.len / o->dataset.maxlen;
- float fill_b = (float)o->test.len / o->test.maxlen;
- target = (fill_a <= fill_b) ? &o->dataset : &o->test;
- } else {
- double r = rand()/RAND_MAX;
- double sumlen = o->dataset.maxlen + o->test.maxlen;
- if (r < (double)o->dataset.maxlen/sumlen) {
- target = &o->dataset;
- } else {
- target = &o->test;
- }
- }
- }
- /* Append if there is room or substitute with a random entry. */
- size_t idx;
- int j, numin = ANN_INPUT_UNITS(o->nn),
- numout = ANN_OUTPUT_UNITS(o->nn);
- if (target->maxlen == target->len) {
- idx = rand() % target->maxlen;
- } else {
- idx = target->len;
- target->len++;
- target->inputs = (float*)RedisModule_Realloc(target->inputs,
- sizeof(float)*numin*target->len);
- target->outputs = (float*)RedisModule_Realloc(target->outputs,
- sizeof(float)*numout*target->len);
- }
- /* Finally store the values at position. */
- for (j = 0; j < numin; j++)
- target->inputs[idx*numin+j] = inputs[j];
- for (j = 0; j < numout; j++)
- target->outputs[idx*numout+j] = outputs[j];
- }
- /* Free the specified dataset. */
- void NRDatasetFree(NRDataset *dset) {
- RedisModule_Free(dset->inputs);
- RedisModule_Free(dset->outputs);
- }
- /* Free a whole NN object. */
- void NRTypeReleaseObject(NRTypeObject *o) {
- AnnFree(o->nn);
- NRDatasetFree(&o->dataset);
- NRDatasetFree(&o->test);
- RedisModule_Free(o->inorm);
- RedisModule_Free(o->onorm);
- RedisModule_Free(o);
- }
- /* ================================ Training =============================== */
- /* Clone a neural network object, including the training and test dataset.
- * We use cloning in order to train in a different thread, and later
- * copy the weights back into the original NN.
- *
- * Note when 'newid' is 0, the copied object NN unique ID is the same as the
- * original as normally this is what we want, in order to later match the
- * trained network with the object stored at the specified key
- * in the pending traning structure.
- *
- * However if the copy is performed with other goals, 'newid' should
- * be set to non-zero in order to create a net with a different ID. */
- NRTypeObject *NRClone(NRTypeObject *o, int newid) {
- NRTypeObject *copy;
- copy = (NRTypeObject*)RedisModule_Calloc(1,sizeof(*o));
- *copy = *o;
- if (newid) copy->id = NRNextId++;
- copy->nn = AnnClone(o->nn);
- copy->dataset = o->dataset;
- copy->test = o->test;
- int ilen = ANN_INPUT_UNITS(o->nn);
- int olen = ANN_OUTPUT_UNITS(o->nn);
- copy->dataset.inputs = (float*)RedisModule_Alloc(sizeof(float)*ilen*o->dataset.len);
- copy->dataset.outputs = (float*)RedisModule_Alloc(sizeof(float)*olen*o->dataset.len);
- copy->test.inputs = (float*)RedisModule_Alloc(sizeof(float)*ilen*o->test.len);
- copy->test.outputs = (float*)RedisModule_Alloc(sizeof(float)*olen*o->test.len);
- memcpy(copy->dataset.inputs,o->dataset.inputs,sizeof(float)*ilen*o->dataset.len);
- memcpy(copy->dataset.outputs,o->dataset.outputs,sizeof(float)*olen*o->dataset.len);
- memcpy(copy->test.inputs,o->test.inputs,sizeof(float)*ilen*o->test.len);
- memcpy(copy->test.outputs,o->test.outputs,sizeof(float)*olen*o->test.len);
- copy->inorm = (float*)RedisModule_Alloc(sizeof(float)*ilen);
- copy->onorm = (float*)RedisModule_Alloc(sizeof(float)*olen);
- memcpy(copy->inorm,o->inorm,sizeof(float)*ilen);
- memcpy(copy->onorm,o->onorm,sizeof(float)*olen);
- return copy;
- }
- /* Transfer the weights from the source to the destination NN.
- * This is used after the learning process finished in a different
- * thread in order to transfer the learning back to the orignal
- * NN. */
- static void NRTransferWeights(RedisModuleCtx *ctx, NRTypeObject *dst, NRTypeObject *src) {
- if (dst->id != src->id) {
- RedisModule_Log(ctx,"warning",
- "NSTransferWeight(): source and destination neural network IDs "
- "don't match. This is unexpected, probably a bug inside the "
- "module. Weights not transferred back to the origina NN.");
- return;
- }
- /* It would be faster to memcpy just the weight array for each layer,
- * however this way we access the NN in a more abstract way, and should
- * be fast enough in most cases. We can always optimized it later. */
- AnnFree(dst->nn);
- dst->nn = AnnClone(src->nn);
- dst->training_total_steps = src->training_total_steps;
- dst->training_total_ms = src->training_total_ms;
- dst->dataset_error = src->dataset_error;
- dst->test_error = src->test_error;
- dst->test_class_error = src->test_class_error;
- dst->flags |= src->flags & NR_FLAG_TO_TRANSFER;
- int ilen = ANN_INPUT_UNITS(src->nn);
- int olen = ANN_OUTPUT_UNITS(src->nn);
- memcpy(dst->inorm,src->inorm,sizeof(float)*ilen);
- memcpy(dst->onorm,src->onorm,sizeof(float)*olen);
- }
- /* Threaded training entry point.
- *
- * To get some clue about overfitting algorithm behavior:
- * #define NR_TRAINING_DEBUG 1
- */
- void *NRTrainingThreadMain(void *arg) {
- NRPendingTraining *pt = (NRPendingTraining*)arg;
- NRTypeObject *nr = pt->nr;
- int training_iterations = 1;
- float train_error = 0;
- float test_error = 0;
- float class_error = 0;
- float past_train_error = 1.0/0.0;
- float past_test_error = 1.0/0.0;
- int auto_stop = nr->flags & NR_FLAG_AUTO_STOP;
- int backtrack = nr->flags & NR_FLAG_BACKTRACK;
- uint64_t cycles = 0;
- long long start = NRMilliseconds();
- long long cycle_time;
- int overfitting_count = 0;
- int overfitting_limit = 5;
- float best_test_error = 1.0/0.0;
- nr->flags &= ~NR_FLAG_TO_TRANSFER;
- /* If the network is auto normalized, we need to trasnform the inputs
- * in a way that's acceptable for the NN. We just find the maximum
- * absolute value, and divide for it, to get a -1,1 range. There
- * are more advanced transformations that are usually performed that
- * could be implemented in the future.
- *
- * Note that we compute the normalization vectors for all the inputs
- * and outputs, however if the network is a classifier, flagged with
- * (NR_FLAG_CLASSIFIER), no output normalization will be done since
- * the data is already in 0/1 format. */
- if ((nr->flags & NR_FLAG_NORMALIZE) && nr->dataset.len) {
- int ilen = ANN_INPUT_UNITS(nr->nn);
- int olen = ANN_OUTPUT_UNITS(nr->nn);
- float *imax = nr->inorm;
- float *omax = nr->onorm;
- float *inputs = nr->dataset.inputs;
- float *outputs = nr->dataset.outputs;
- for (int i = 0; i < ilen; i++) imax[i] = 1;
- for (int i = 0; i < olen; i++) omax[i] = 1;
- /* Compute the max values vectors. */
- for (uint32_t j = 0; j < nr->dataset.len; j++) {
- for (int i = 0; i < ilen; i++)
- if (fabs(inputs[i]) > imax[i]) imax[i] = fabs(inputs[i]);
- for (int i = 0; i < olen; i++)
- if (fabs(outputs[i]) > omax[i]) omax[i] = fabs(outputs[i]);
- inputs += ilen;
- outputs += olen;
- }
- /* Likely we are not seeing what will really be the true input/output
- * maximum value, so we multiply the maximum values found by a constant.
- * However if the max is exactly "1" we assume it's a classification
- * input and don't alter it. */
- for (int i = 0; i < ilen; i++) if (imax[i] != 1) imax[i] *= 1.2;
- for (int i = 0; i < olen; i++) if (omax[i] != 1) omax[i] *= 1.2;
- /* We can normalize the dataset directly: after the training it will
- * be discarded anyway. */
- inputs = nr->dataset.inputs;
- outputs = nr->dataset.outputs;
- for (uint32_t j = 0; j < nr->dataset.len; j++) {
- for (int i = 0; i < ilen; i++) inputs[i] /= nr->inorm[i];
- if (!(nr->flags & NR_FLAG_CLASSIFIER))
- for (int i = 0; i < olen; i++) outputs[i] /= nr->onorm[i];
- inputs += ilen;
- outputs += olen;
- }
- inputs = nr->test.inputs;
- outputs = nr->test.outputs;
- for (uint32_t j = 0; j < nr->test.len; j++) {
- for (int i = 0; i < ilen; i++) inputs[i] /= nr->inorm[i];
- if (!(nr->flags & NR_FLAG_CLASSIFIER))
- for (int i = 0; i < olen; i++) outputs[i] /= nr->onorm[i];
- inputs += ilen;
- outputs += olen;
- }
- }
- AnnRprop *saved = NULL; /* Saved to recover on overfitting. */
- float saved_error; /* The test error of the saved NN. */
- float saved_train_error; /* The training dataset error of the saved NN */
- float saved_class_error; /* The % of classification errors of saved NN */
- while(1) {
- long long cycle_start = NRMilliseconds();
- train_error = AnnTrain(nr->nn,
- nr->dataset.inputs,
- nr->dataset.outputs,
- 0,
- training_iterations,
- nr->dataset.len,
- ANN_ALGO_BPROP);
- cycle_time = NRMilliseconds() - cycle_start;
- nr->training_total_steps += nr->dataset.len*training_iterations;
- /* Evaluate the error in the case of auto training, stop it
- * once we see that the error in the traning set is decreasing
- * while the one in the test set is not. */
- if (auto_stop) {
- AnnTestError(nr->nn,
- nr->test.inputs,
- nr->test.outputs,
- nr->test.len, &test_error, &class_error);
- if (train_error < past_train_error &&
- test_error > past_test_error)
- {
- overfitting_count++;
- #ifdef NR_TRAINING_DEBUG
- printf("+YCLE %lld: [%d] %f VS %f\n", (long long)cycles,
- overfitting_count, train_error, test_error);
- #endif
- if (overfitting_count == overfitting_limit) {
- nr->flags |= NR_FLAG_OF_DETECTED;
- break;
- }
- } else if (overfitting_count > 0) {
- #ifdef NR_TRAINING_DEBUG
- printf("-YCLE %lld: [%d] %f VS %f\n", (long long)cycles,
- overfitting_count, train_error, test_error);
- #endif
- overfitting_count--;
- }
- /* Save all the networks with a score better than the currently
- * saved network. This can be a bit costly, but is safe: one
- * cycle of training more and overfitting can ruin it all. */
- if (backtrack && (saved == NULL || test_error < saved_error)) {
- #ifdef NR_TRAINING_DEBUG
- printf("SAVED! %f < %f\n", test_error, saved_error);
- #endif
- saved_error = test_error;
- saved_train_error = train_error;
- saved_class_error = class_error;
- if (saved) AnnFree(saved);
- saved = AnnClone(nr->nn);
- }
- /* Best network found? Reset the overfitting hints counter. */
- if (test_error < best_test_error) {
- overfitting_count = 0;
- best_test_error = test_error;
- #ifdef NR_TRAINING_DEBUG
- printf("BEST! %lld: <%d> %f VS %f\n", (long long)cycles,
- overfitting_limit,train_error, test_error);
- #endif
- }
- /* Also stop if the loss is zero in both datasets. */
- if (train_error < 0.000000000000001 &&
- test_error < 0.000000000000001) break;
- }
- cycles++;
- long long total_time = NRMilliseconds()-start;
- /* Cycles and milliseconds stop conditions. */
- if (nr->training_max_cycles && cycles == nr->training_max_cycles)
- break;
- if (nr->training_max_ms && total_time > (long long)nr->training_max_ms)
- break;
- /* If this is a long training, to do just a single training iteration
- * for each cycle is not optimal: tune the number of iterations to
- * at least take 100 milliseconds. */
- if (total_time > 10000 && cycle_time < 100) training_iterations++;
- past_train_error = train_error;
- past_test_error = test_error;
- /* Update stats for NR.THREADS to show progresses. */
- pthread_mutex_lock(&NRPendingTrainingMutex);
- pt->dataset_error = train_error;
- pt->test_error = test_error;
- if (nr->flags & NR_FLAG_CLASSIFIER) pt->class_error = class_error;
- pt->curcycle = cycles;
- pthread_mutex_unlock(&NRPendingTrainingMutex);
- }
- /* If auto stop is disabled, we still need to compute the test error
- * in order to return this information to the main thread. */
- if (!auto_stop) {
- AnnTestError(nr->nn,
- nr->test.inputs,
- nr->test.outputs,
- nr->test.len, &test_error, &class_error);
- }
- /* If both autostop and backtracking are enabled, we may have
- * a better network saved! */
- if (auto_stop && backtrack) {
- if (saved && saved_error < test_error) {
- #ifdef NR_TRAINING_DEBUG
- printf("BACKTRACK: Saved network used!\n");
- #endif
- AnnFree(nr->nn);
- nr->nn = saved;
- test_error = saved_error;
- train_error = saved_train_error;
- class_error = saved_class_error;
- } else if (saved) {
- AnnFree(saved);
- }
- }
- if (nr->flags & NR_FLAG_CLASSIFIER) nr->test_class_error = class_error;
- nr->dataset_error = train_error;
- nr->test_error = test_error;
- nr->training_total_ms += NRMilliseconds()-start;
- /* Signal that the training process has finished, it's up to the main
- * thread to cleanup this training slot, copying the weights to the
- * original neural network and reclaiming memory for the copy we
- * used to work. */
- pthread_mutex_lock(&NRPendingTrainingMutex);
- pt->in_progress = 0;
- pthread_mutex_unlock(&NRPendingTrainingMutex);
- return NULL;
- }
- /* Start a background training in another thread. Return REDISMODULE_ERR if
- * there is no free slot for training, as we already reached the maximum of
- * networks we can train in parallel.
- *
- * The 'flags' argument specifies the additional NN flags to pass to the
- * training ruotine:
- *
- * NR_FLAG_AUTO_STOP -- Automatically stop training on overtraining.
- * NR_FLAG_BACKTRACK -- Save current NN state when overfitting is likely.
- */
- int NRStartTraining(RedisModuleCtx *ctx, RedisModuleString *key, int dbid, NRTypeObject *nr) {
- pthread_mutex_lock(&NRPendingTrainingMutex);
- if (NRPendingTrainingCount == NR_PENDING_TRAINING_MAX_LEN) {
- pthread_mutex_unlock(&NRPendingTrainingMutex);
- return REDISMODULE_ERR;
- }
- /* Setup our trainig data. */
- NRPendingTraining *pt = &NRTrainings[NRPendingTrainingCount];
- //pt->key = RedisModule_CreateStringFromString(ctx,key);
- //RedisModule_RetainString(ctx,pt->key);
- pt->db_id = dbid;
- pt->in_progress = 1;
- pt->nr = NRClone(nr,0);
- pt->dataset_error = 0;
- pt->test_error = 0;
- pt->class_error = 0;
- pt->curcycle = 0;
- if (pthread_create(&pt->tid,NULL,NRTrainingThreadMain,pt) != 0) {
- RedisModule_Log(ctx,"warning","Unable to create a new pthread in NRStartTraining()");
- //RedisModule_FreeString(ctx,pt->key);
- pt->key = NULL;
- NRTypeReleaseObject(pt->nr);
- pthread_mutex_unlock(&NRPendingTrainingMutex);
- return REDISMODULE_ERR;
- }
- NRPendingTrainingCount++;
- nr->flags |= NR_FLAG_TRAINING;
- nr->flags &= ~NR_FLAG_TO_TRANSFER;
- pthread_mutex_unlock(&NRPendingTrainingMutex);
- return REDISMODULE_OK;
- }
- /* Check if there are threads that terminated the NN training, and
- * collect the info they computed (that is the new NN). */
- int NRCollectThreads(RedisModuleCtx *ctx) {
- int collected = 0;
- pthread_mutex_lock(&NRPendingTrainingMutex);
- for (int j = 0; j < NRPendingTrainingCount; j++) {
- NRPendingTraining *pt = &NRTrainings[j];
- if (pt->in_progress == 0) {
- /* Training terminated. Let's see if the key
- * is still there and NN ID matches. */
- int orig_id = RedisModule_GetSelectedDb(ctx);
- if (orig_id != pt->db_id) RedisModule_SelectDb(ctx,pt->db_id);
- RedisModuleKey *key = RedisModule_OpenKey(ctx,pt->key,
- REDISMODULE_READ|REDISMODULE_WRITE);
- if (RedisModule_ModuleTypeGetType(key) == NRType) {
- NRTypeObject *nr = RedisModule_ModuleTypeGetValue(key);
- if (nr->id == pt->nr->id) {
- NRTransferWeights(ctx,nr,pt->nr);
- nr->flags &= ~NR_FLAG_TRAINING;
- }
- RedisModule_FreeString(ctx,pt->key);
- pt->key = NULL;
- NRTypeReleaseObject(pt->nr);
- NRPendingTrainingCount--;
- memcpy(&NRTrainings[j],&NRTrainings[j+1],
- (NRPendingTrainingCount-j)*sizeof(NRTrainings[0]));
- }
- if (orig_id != pt->db_id) RedisModule_SelectDb(ctx,orig_id);
- collected++;
- }
- }
- pthread_mutex_unlock(&NRPendingTrainingMutex);
- return collected;
- }
- #endif // 0
- #define RedisModule_Free(x) sq_free(x, 0)
- static void *RedisModule_Calloc(size_t nelm, size_t sz)
- {
- size_t malloc_size = nelm * sz;
- void *ptr = sq_malloc(malloc_size);
- if(ptr) memset(ptr, 0, malloc_size);
- return ptr;
- }
- static void *RedisModule_Realloc(void *oldPtr, size_t sz)
- {
- void *ptr = sq_realloc(oldPtr, 0, sz);
- return ptr;
- }
- static uint64_t NRNextId = 1; /* Next neural network unique ID. */
- long long NRMilliseconds(void) {
- struct timeval tv;
- long long ust;
- gettimeofday(&tv, NULL);
- ust = ((long long)tv.tv_sec)*1000000;
- ust += tv.tv_usec;
- return ust/1000;
- }
- /* Create a network with the specified parameters. Note that the layers
- * must be specified from the output layer[0] to the input
- * layer[N]. Each element in the integer array 'layer' specify how many
- * units there are in the corresponding layer. */
- static NRTypeObject *createNRTypeObject(int flags, int *layers, int numlayers, int dset_len, int test_len) {
- NRTypeObject *o;
- o = (NRTypeObject*)RedisModule_Calloc(1,sizeof(*o));
- o->id = NRNextId++;
- o->flags = flags;
- o->nn = AnnCreateNet(numlayers,layers);
- o->dataset.maxlen = dset_len;
- o->test.maxlen = test_len;
- int ilen = ANN_INPUT_UNITS(o->nn);
- int olen = ANN_OUTPUT_UNITS(o->nn);
- o->inorm = (float*)RedisModule_Calloc(1,sizeof(float)*ilen);
- o->onorm = (float*)RedisModule_Calloc(1,sizeof(float)*olen);
- for (int j = 0; j < ilen; j++) o->inorm[j] = 1;
- for (int j = 0; j < olen; j++) o->onorm[j] = 1;
- return o;
- }
- /* Insert data (observations needed to train and test the NN) into the
- * NN object. While the learning and testing datasets are yet not full
- * the observed pattern is inserted evenly in one or the other side in
- * order to make sure the two datasets are populated evenly. When both
- * are already full, a random elmenet from one or the other (doing
- * a random weighted choice depending on the length) is substituted with
- * the new item. */
- #define NR_INSERT_NO_TARGET 0 /* Auto select where to insert. */
- #define NR_INSERT_TRAIN 1 /* Insert in training dataset. */
- #define NR_INSERT_TEST 2 /* Insert in testing dataset. */
- static void NRTypeInsertData(NRTypeObject *o, float *inputs, float *outputs,
- int target_ds) {
- NRDataset *target = NULL;
- /* Check if there is no dataset at all. This may be a valid setup
- * with online learning, sample by sample. */
- if (o->dataset.maxlen == 0 && o->test.maxlen == 0) return;
- /* If the user specified a target, select it. */
- if (target_ds == NR_INSERT_TRAIN) target = &o->dataset;
- else if (target_ds == NR_INSERT_TEST) target = &o->test;
- /* If no target is specified, but there is only one possible
- * target, select it ASAP. */
- if (o->dataset.maxlen == 0) {
- target = &o->test;
- } else if (o->test.maxlen == 0) {
- target = &o->dataset;
- }
- /* Otherwise choose as the target to populate the one with less data
- * relatively to its size. */
- if (target == NULL) {
- /* If one of the two datasets are still not full, pick
- * based on fill percentage. Otherwise pick a random
- * target relatively to their size. */
- if (o->dataset.len != o->dataset.maxlen ||
- o->test.len != o->dataset.len)
- {
- float fill_a = (float)o->dataset.len / o->dataset.maxlen;
- float fill_b = (float)o->test.len / o->test.maxlen;
- target = (fill_a <= fill_b) ? &o->dataset : &o->test;
- } else {
- double r = rand()/RAND_MAX;
- double sumlen = o->dataset.maxlen + o->test.maxlen;
- if (r < (double)o->dataset.maxlen/sumlen) {
- target = &o->dataset;
- } else {
- target = &o->test;
- }
- }
- }
- /* Append if there is room or substitute with a random entry. */
- size_t idx;
- int j, numin = ANN_INPUT_UNITS(o->nn),
- numout = ANN_OUTPUT_UNITS(o->nn);
- if (target->maxlen == target->len) {
- idx = rand() % target->maxlen;
- } else {
- idx = target->len;
- target->len++;
- target->inputs = (float*)RedisModule_Realloc(target->inputs,
- sizeof(float)*numin*target->len);
- target->outputs = (float*)RedisModule_Realloc(target->outputs,
- sizeof(float)*numout*target->len);
- }
- /* Finally store the values at position. */
- for (j = 0; j < numin; j++)
- target->inputs[idx*numin+j] = inputs[j];
- for (j = 0; j < numout; j++)
- target->outputs[idx*numout+j] = outputs[j];
- }
- /* Free the specified dataset. */
- void NRDatasetFree(NRDataset *dset) {
- RedisModule_Free(dset->inputs);
- RedisModule_Free(dset->outputs);
- }
- /* Free a whole NN object. */
- void NRTypeReleaseObject(NRTypeObject *o) {
- AnnFree(o->nn);
- NRDatasetFree(&o->dataset);
- NRDatasetFree(&o->test);
- RedisModule_Free(o->inorm);
- RedisModule_Free(o->onorm);
- RedisModule_Free(o);
- }
- static const SQChar sq_nn_TAG[] = _SC("AnnRprop");
- static SQRESULT sq_nn_release_hook(SQUserPointer p, SQInteger size, void */*ep*/) {
- NRTypeObject *self = (NRTypeObject *)p;
- if(self) NRTypeReleaseObject(self);
- return 0;
- }
- /*
- ** Creates a new AnnRprop.
- */
- static SQRESULT sq_nn_constructor (HSQUIRRELVM v) {
- SQ_FUNC_VARS(v);
- SQ_GET_INTEGER(v, 2, flags);
- SQ_GET_INTEGER(v, 3, ninputs);
- const SQInteger nhidden_pos = 4;
- SQ_GET_INTEGER(v, 5, noutputs);
- SQ_OPT_INTEGER(v, 6, ndata, 0);
- SQ_OPT_INTEGER(v, 7, ntest, 0);
- if(!(
- ((flags & NR_FLAG_CLASSIFIER) && !(flags & NR_FLAG_REGRESSOR))
- || (!(flags & NR_FLAG_CLASSIFIER) && (flags & NR_FLAG_REGRESSOR))
- )
- )
- return sq_throwerror(v, _SC("invalid neural network type. Must be "
- "CLASSIFIER or REGRESSOR"));
- int layers[NR_MAX_LAYERS], num_layers=0;
- layers[num_layers++] = noutputs;
- /* Our NN library takes the definition of layers in the opposite
- * order, swap the layers array. */
- SQInteger asize = sq_getsize(v, nhidden_pos);
- for(int i=asize-1; i >= 0; --i)
- {
- sq_pushinteger(v, i);
- sq_get(v, nhidden_pos);
- SQInteger nhidden;
- SQRESULT rc = sq_getinteger(v, -1, &nhidden);
- if(rc != SQ_OK) return sq_throwerror(v, _SC("only integers expected on hidden layers array"));
- layers[num_layers++] = nhidden;
- sq_poptop(v);
- }
- layers[num_layers++] = ninputs;
- //for(int i=0; i < num_layers; ++i) printf("layers %d : %d\n", i, layers[i]);
- NRTypeObject *self = createNRTypeObject(flags, layers, num_layers, ndata, ntest);
- if(self){
- self->flags = flags;
- sq_setinstanceup(v, 1, self);
- sq_setreleasehook(v, 1, sq_nn_release_hook);
- return 1;
- }
- delete self;
- return sq_throwerror(v, _SC("failed to create AnnRprop"));
- }
- #define SQ_GET_NN_INSTANCE(v, at) SQ_GET_INSTANCE_VAR(v, at, NRTypeObject, self, sq_nn_TAG)
- static SQRESULT sq_nn_observe(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS(v);
- SQ_GET_NN_INSTANCE(v, 1);
- SQ_OPT_INTEGER(v, 4, target, NR_INSERT_NO_TARGET);
- SQInteger ilen = ANN_INPUT_UNITS(self->nn);
- SQInteger olen = ANN_OUTPUT_UNITS(self->nn);
- SQInteger oargs = (self->flags & NR_FLAG_CLASSIFIER) ? 1 : olen;
- const SQInteger inputs_pos = 2;
- const SQInteger outputs_pos = 3;
- SQInteger asize_inputs = sq_getsize(v, inputs_pos);
- SQInteger asize_outputs = sq_getsize(v, outputs_pos);
- if((ilen != asize_inputs) || (oargs != asize_outputs))
- return sq_throwerror(v, _SC( "number of arguments does not "
- "match the number of " _PRINT_INT_FMT " inputs and " _PRINT_INT_FMT " outputs in the neural network"),
- ilen, oargs);
- const SQInteger inputs_alloc_size = sizeof(float)*ilen;
- const SQInteger outputs_alloc_size = sizeof(float)*olen;
- float *inputs = (float*)sq_malloc(inputs_alloc_size);
- for(SQInteger i=0; i < ilen; ++i)
- {
- sq_pushinteger(v, i);
- sq_get(v, inputs_pos);
- SQFloat fnum;
- SQRESULT rc = sq_getfloat(v, -1, &fnum);
- if(rc != SQ_OK)
- {
- sq_free(inputs, inputs_alloc_size);
- return sq_throwerror(v, _SC("only numbers expected on input array"));
- }
- inputs[i] = fnum;
- sq_poptop(v);
- }
- float *outputs = (float*)sq_malloc(outputs_alloc_size);
- for(SQInteger i=0; i < oargs; ++i)
- {
- sq_pushinteger(v, i);
- sq_get(v, outputs_pos);
- SQFloat fnum;
- SQRESULT rc = sq_getfloat(v, -1, &fnum);
- if(rc != SQ_OK)
- {
- sq_free(inputs, inputs_alloc_size);
- sq_free(outputs, outputs_alloc_size);
- return sq_throwerror(v, _SC("only numbers expected on output array"));
- }
- if (self->flags & NR_FLAG_CLASSIFIER) {
- int classid = fnum;
- if (classid != fnum || fnum >= olen || fnum < 0) {
- sq_free(inputs, inputs_alloc_size);
- sq_free(outputs, outputs_alloc_size);
- return sq_throwerror(v, _SC("classifier network output must be an integer "
- "in the range from 0 to outputs-1."));
- }
- memset(outputs,0, outputs_alloc_size);
- outputs[classid] = 1;
- } else {
- outputs[i] = fnum;
- }
- sq_poptop(v);
- }
- NRTypeInsertData(self,inputs,outputs,target);
- sq_free(inputs, inputs_alloc_size);
- sq_free(outputs, outputs_alloc_size);
- return 0;
- }
- static SQRESULT sq_nn_train(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS(v);
- SQ_GET_NN_INSTANCE(v, 1);
- SQ_OPT_INTEGER(v, 2, opt_max_cycles, 0);
- SQ_OPT_INTEGER(v, 3, opt_max_ms, 10000);
- SQ_OPT_INTEGER(v, 4, opt_flags, 0);
- NRTypeObject *nr = self;
- nr->training_max_cycles = opt_max_cycles;
- nr->training_max_ms = opt_max_ms;
- if(opt_flags & NR_FLAG_AUTO_STOP) nr->flags |= NR_FLAG_AUTO_STOP;
- if(opt_flags & NR_FLAG_BACKTRACK) nr->flags |= NR_FLAG_BACKTRACK;
- /* Overfitting detection compares error rate in testing/training data,
- * so does not work without entries in the testing dataset. */
- if (nr->flags & NR_FLAG_AUTO_STOP && nr->test.len == 0) {
- return sq_throwerror(v, _SC("Can't start training with AUTOSTOP option: "
- "overfitting detection requires a non zero length testing dataset"));
- }
- int training_iterations = 1;
- float train_error = 0;
- float test_error = 0;
- float class_error = 0;
- float past_train_error = 1.0/0.0;
- float past_test_error = 1.0/0.0;
- int auto_stop = nr->flags & NR_FLAG_AUTO_STOP;
- int backtrack = nr->flags & NR_FLAG_BACKTRACK;
- uint64_t cycles = 0;
- long long start = NRMilliseconds();
- long long cycle_time;
- int overfitting_count = 0;
- int overfitting_limit = 5;
- float best_test_error = 1.0/0.0;
- nr->flags &= ~NR_FLAG_TO_TRANSFER;
- /* If the network is auto normalized, we need to trasnform the inputs
- * in a way that's acceptable for the NN. We just find the maximum
- * absolute value, and divide for it, to get a -1,1 range. There
- * are more advanced transformations that are usually performed that
- * could be implemented in the future.
- *
- * Note that we compute the normalization vectors for all the inputs
- * and outputs, however if the network is a classifier, flagged with
- * (NR_FLAG_CLASSIFIER), no output normalization will be done since
- * the data is already in 0/1 format. */
- if ((nr->flags & NR_FLAG_NORMALIZE) && nr->dataset.len) {
- int ilen = ANN_INPUT_UNITS(nr->nn);
- int olen = ANN_OUTPUT_UNITS(nr->nn);
- float *imax = nr->inorm;
- float *omax = nr->onorm;
- float *inputs = nr->dataset.inputs;
- float *outputs = nr->dataset.outputs;
- for (int i = 0; i < ilen; i++) imax[i] = 1;
- for (int i = 0; i < olen; i++) omax[i] = 1;
- /* Compute the max values vectors. */
- for (uint32_t j = 0; j < nr->dataset.len; j++) {
- for (int i = 0; i < ilen; i++)
- if (fabs(inputs[i]) > imax[i]) imax[i] = fabs(inputs[i]);
- for (int i = 0; i < olen; i++)
- if (fabs(outputs[i]) > omax[i]) omax[i] = fabs(outputs[i]);
- inputs += ilen;
- outputs += olen;
- }
- /* Likely we are not seeing what will really be the true input/output
- * maximum value, so we multiply the maximum values found by a constant.
- * However if the max is exactly "1" we assume it's a classification
- * input and don't alter it. */
- for (int i = 0; i < ilen; i++) if (imax[i] != 1) imax[i] *= 1.2;
- for (int i = 0; i < olen; i++) if (omax[i] != 1) omax[i] *= 1.2;
- /* We can normalize the dataset directly: after the training it will
- * be discarded anyway. */
- inputs = nr->dataset.inputs;
- outputs = nr->dataset.outputs;
- for (uint32_t j = 0; j < nr->dataset.len; j++) {
- for (int i = 0; i < ilen; i++) inputs[i] /= nr->inorm[i];
- if (!(nr->flags & NR_FLAG_CLASSIFIER))
- for (int i = 0; i < olen; i++) outputs[i] /= nr->onorm[i];
- inputs += ilen;
- outputs += olen;
- }
- inputs = nr->test.inputs;
- outputs = nr->test.outputs;
- for (uint32_t j = 0; j < nr->test.len; j++) {
- for (int i = 0; i < ilen; i++) inputs[i] /= nr->inorm[i];
- if (!(nr->flags & NR_FLAG_CLASSIFIER))
- for (int i = 0; i < olen; i++) outputs[i] /= nr->onorm[i];
- inputs += ilen;
- outputs += olen;
- }
- }
- AnnRprop *saved = NULL; /* Saved to recover on overfitting. */
- float saved_error; /* The test error of the saved NN. */
- float saved_train_error; /* The training dataset error of the saved NN */
- float saved_class_error; /* The % of classification errors of saved NN */
- while(1) {
- long long cycle_start = NRMilliseconds();
- train_error = AnnTrain(nr->nn,
- nr->dataset.inputs,
- nr->dataset.outputs,
- 0,
- training_iterations,
- nr->dataset.len,
- ANN_ALGO_BPROP);
- cycle_time = NRMilliseconds() - cycle_start;
- nr->training_total_steps += nr->dataset.len*training_iterations;
- /* Evaluate the error in the case of auto training, stop it
- * once we see that the error in the traning set is decreasing
- * while the one in the test set is not. */
- if (auto_stop) {
- AnnTestError(nr->nn,
- nr->test.inputs,
- nr->test.outputs,
- nr->test.len, &test_error, &class_error);
- if (train_error < past_train_error &&
- test_error > past_test_error)
- {
- overfitting_count++;
- #ifdef NR_TRAINING_DEBUG
- printf("+YCLE %lld: [%d] %f VS %f\n", (long long)cycles,
- overfitting_count, train_error, test_error);
- #endif
- if (overfitting_count == overfitting_limit) {
- nr->flags |= NR_FLAG_OF_DETECTED;
- break;
- }
- } else if (overfitting_count > 0) {
- #ifdef NR_TRAINING_DEBUG
- printf("-YCLE %lld: [%d] %f VS %f\n", (long long)cycles,
- overfitting_count, train_error, test_error);
- #endif
- overfitting_count--;
- }
- /* Save all the networks with a score better than the currently
- * saved network. This can be a bit costly, but is safe: one
- * cycle of training more and overfitting can ruin it all. */
- if (backtrack && (saved == NULL || test_error < saved_error)) {
- #ifdef NR_TRAINING_DEBUG
- printf("SAVED! %f < %f\n", test_error, saved_error);
- #endif
- saved_error = test_error;
- saved_train_error = train_error;
- saved_class_error = class_error;
- if (saved) AnnFree(saved);
- saved = AnnClone(nr->nn);
- }
- /* Best network found? Reset the overfitting hints counter. */
- if (test_error < best_test_error) {
- overfitting_count = 0;
- best_test_error = test_error;
- #ifdef NR_TRAINING_DEBUG
- printf("BEST! %lld: <%d> %f VS %f\n", (long long)cycles,
- overfitting_limit,train_error, test_error);
- #endif
- }
- /* Also stop if the loss is zero in both datasets. */
- if (train_error < 0.000000000000001 &&
- test_error < 0.000000000000001) break;
- }
- cycles++;
- long long total_time = NRMilliseconds()-start;
- /* Cycles and milliseconds stop conditions. */
- if (nr->training_max_cycles && cycles == nr->training_max_cycles)
- break;
- if (nr->training_max_ms && total_time > (long long)nr->training_max_ms)
- break;
- /* If this is a long training, to do just a single training iteration
- * for each cycle is not optimal: tune the number of iterations to
- * at least take 100 milliseconds. */
- if (total_time > 10000 && cycle_time < 100) training_iterations++;
- past_train_error = train_error;
- past_test_error = test_error;
- }
- /* If auto stop is disabled, we still need to compute the test error
- * in order to return this information to the main thread. */
- if (!auto_stop) {
- AnnTestError(nr->nn,
- nr->test.inputs,
- nr->test.outputs,
- nr->test.len, &test_error, &class_error);
- }
- /* If both autostop and backtracking are enabled, we may have
- * a better network saved! */
- if (auto_stop && backtrack) {
- if (saved && saved_error < test_error) {
- #ifdef NR_TRAINING_DEBUG
- printf("BACKTRACK: Saved network used!\n");
- #endif
- AnnFree(nr->nn);
- nr->nn = saved;
- test_error = saved_error;
- train_error = saved_train_error;
- class_error = saved_class_error;
- } else if (saved) {
- AnnFree(saved);
- }
- }
- if (nr->flags & NR_FLAG_CLASSIFIER) nr->test_class_error = class_error;
- nr->dataset_error = train_error;
- nr->test_error = test_error;
- nr->training_total_ms += NRMilliseconds()-start;
- return 0;
- }
- static SQRESULT sq_nn_run(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- SQInteger asize_inputs = sq_getsize(v, 2);
- SQInteger ilen = ANN_INPUT_UNITS(self->nn);
- if(ilen != asize_inputs)
- return sq_throwerror(v, _SC("wrong number of inputs " _PRINT_INT_FMT " for expected " _PRINT_INT_FMT), asize_inputs, ilen);
- for(SQInteger i=0; i < ilen; ++i)
- {
- sq_pushinteger(v, i);
- sq_get(v, 2);
- SQFloat fnum;
- SQRESULT rc = sq_getfloat(v, -1, &fnum);
- if(rc != SQ_OK)
- {
- return sq_throwerror(v, _SC("only numbers expected on input array"));
- }
- if (self->flags & NR_FLAG_NORMALIZE) fnum /= self->inorm[i];
- ANN_INPUT_NODE(self->nn,i) = fnum;
- sq_poptop(v);
- }
- AnnSimulate(self->nn);
- /* Output the raw net output or the class ID if the network
- * is a classifier and the command invoked was NR.CLASS. */
- int olen = ANN_OUTPUT_UNITS(self->nn);
- sq_newarray(v, olen);
- for(int j = 0; j < olen; j++) {
- float output = ANN_OUTPUT_NODE(self->nn,j);
- if (!(self->flags & NR_FLAG_CLASSIFIER) &&
- (self->flags & NR_FLAG_NORMALIZE))
- {
- output *= self->onorm[j];
- }
- sq_pushfloat(v, output);
- sq_arrayset(v, -2, j);
- }
- return 1;
- }
- static SQRESULT sq_nn_classify(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- if (!(self->flags & NR_FLAG_CLASSIFIER))
- return sq_throwerror(v, _SC("you can't call classify with a regressor network."));
- SQInteger asize_inputs = sq_getsize(v, 2);
- SQInteger ilen = ANN_INPUT_UNITS(self->nn);
- if(ilen != asize_inputs)
- return sq_throwerror(v, _SC("wrong number of inputs %d for expected %d"), (int)asize_inputs, (int)ilen);
- for(SQInteger i=0; i < ilen; ++i)
- {
- sq_pushinteger(v, i);
- sq_get(v, 2);
- SQFloat fnum;
- SQRESULT rc = sq_getfloat(v, -1, &fnum);
- if(rc != SQ_OK)
- {
- return sq_throwerror(v, _SC("only numbers expected on input array"));
- }
- if (self->flags & NR_FLAG_NORMALIZE) fnum /= self->inorm[i];
- ANN_INPUT_NODE(self->nn,i) = fnum;
- sq_poptop(v);
- }
- AnnSimulate(self->nn);
- /* Output the raw net output or the class ID if the network
- * is a classifier and the command invoked was NR.CLASS. */
- int olen = ANN_OUTPUT_UNITS(self->nn);
- float fmax = ANN_OUTPUT_NODE(self->nn,0);
- int max_class = 0;
- for(int j = 1; j < olen; j++) {
- float output = ANN_OUTPUT_NODE(self->nn,j);
- if (output > fmax) {
- fmax = output;
- max_class = j;
- }
- }
- sq_pushinteger(v, max_class);
- return 1;
- }
- #define ADD_T_TABLE_STR(sk, sv) \
- sq_pushstring(v, sk, -1); \
- sq_pushstring(v, sv, -1); \
- sq_rawset(v, -3);
- #define ADD_T_TABLE_INT(sk, sv) \
- sq_pushstring(v, sk, -1); \
- sq_pushinteger(v, sv); \
- sq_rawset(v, -3);
- #define ADD_T_TABLE_FLOAT(sk, sv) \
- sq_pushstring(v, sk, -1); \
- sq_pushfloat(v, sv); \
- sq_rawset(v, -3);
- static SQRESULT sq_nn_info(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- sq_newtable(v);
- ADD_T_TABLE_INT("id", self->id);
- ADD_T_TABLE_STR("type", (self->flags & NR_FLAG_CLASSIFIER) ? "classifier" : "regressor");
- ADD_T_TABLE_INT("auto-normalization", !!(self->flags & NR_FLAG_NORMALIZE));
- ADD_T_TABLE_INT("training", !!(self->flags & NR_FLAG_TRAINING));
- sq_pushliteral(v, _SC("layout"));
- sq_newarray(v, ANN_LAYERS(self->nn));
- for (int ai=0, i = ANN_LAYERS(self->nn)-1; i >= 0; i--, ++ai) {
- int units = ANN_UNITS(self->nn,i);
- if (i != 0) units--; /* Don't count the bias unit. */
- sq_pushinteger(v, units);
- sq_arrayset(v, -2, ai);
- }
- sq_rawset(v, -3);
- ADD_T_TABLE_INT("training-dataset-maxlen", self->dataset.maxlen);
- ADD_T_TABLE_INT("training-dataset-len", self->dataset.len);
- ADD_T_TABLE_INT("test-dataset-maxlen", self->test.maxlen);
- ADD_T_TABLE_INT("test-dataset-len", self->test.len);
- ADD_T_TABLE_INT("training-total-steps", self->training_total_steps);
- ADD_T_TABLE_INT("training-total-cycles", self->dataset.len ?
- (self->training_total_steps / self->dataset.len) : 0);
- float tms = (float)self->training_total_ms/1000;
- ADD_T_TABLE_FLOAT("training-total-seconds", tms);
- ADD_T_TABLE_FLOAT("dataset-error", self->dataset_error);
- ADD_T_TABLE_FLOAT("test-error", self->test_error);
- if (self->flags & NR_FLAG_CLASSIFIER) {
- ADD_T_TABLE_FLOAT("classification-errors-perc", self->test_class_error);
- }
- ADD_T_TABLE_STR("overfitting-detected", (self->flags & NR_FLAG_OF_DETECTED) ? "yes" : "no");
- return 1;
- }
- static SQRESULT sq_nn_clone(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- AnnRprop *clone = AnnClone(self->nn);
- if(clone)
- {
- sq_pushstring(v, sq_nn_TAG, -1);
- if(sq_getonregistrytable(v) == SQ_ERROR) return SQ_ERROR;
- sq_createinstance(v, -1);
- sq_setinstanceup(v, -1, clone);
- sq_setreleasehook(v, -1, sq_nn_release_hook);
- }
- else sq_pushnull(v);
- return 1;
- }
- #define SQ_NN_GET_SET_FLOAT(func_name) \
- static SQRESULT sq_nn_##func_name(HSQUIRRELVM v)\
- {\
- SQ_FUNC_VARS(v);\
- SQ_GET_NN_INSTANCE(v, 1);\
- if(_top_ == 1)\
- {\
- sq_pushfloat(v, self->nn->func_name);\
- return 1;\
- }\
- SQ_GET_FLOAT(v, 2, func_name);\
- self->nn->func_name = func_name;\
- return 0;\
- }
- SQ_NN_GET_SET_FLOAT(learn_rate);
- SQ_NN_GET_SET_FLOAT(rprop_nminus);
- SQ_NN_GET_SET_FLOAT(rprop_nplus);
- SQ_NN_GET_SET_FLOAT(rprop_maxupdate);
- SQ_NN_GET_SET_FLOAT(rprop_minupdate);
- static SQRESULT sq_nn_flags(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS(v);
- SQ_GET_NN_INSTANCE(v, 1);
- if(_top_ == 1)
- {
- sq_pushinteger(v, self->nn->flags);
- return 1;
- }
- SQ_GET_INTEGER(v, 2, flags);
- self->nn->flags = flags;
- return 0;
- }
- static SQRESULT sq_nn_weights(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- sq_pushfloat(v, AnnCountWeights(self->nn));
- return 1;
- }
- static SQRESULT sq_nn_weight(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS(v);
- SQ_GET_NN_INSTANCE(v, 1);
- SQ_GET_INTEGER(v, 2, layer);
- SQ_GET_INTEGER(v, 3, i);
- SQ_GET_INTEGER(v, 4, j);
- if(layer < 0 && layer >= self->nn->layers) return sq_throwerror(v, _SC("layer out of range"));
- //if(i < 0 && i >= self->layer[layer]) return sq_throwerror(v, _("layer out of range"));
- float *weight = &ANN_WEIGHT(self->nn, layer, i, j);
- if(_top_ == 4)
- {
- sq_pushfloat(v, *weight);
- return 1;
- }
- SQ_GET_FLOAT(v, 5, new_weight);
- *weight = new_weight;
- return 0;
- }
- static SQRESULT sq_nn_Ann2Tcl(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- Ann2Tcl(self->nn);
- return 0;
- }
- static SQRESULT sq_nn_Ann2Js(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- Ann2Js(self->nn);
- return 0;
- }
- static SQRESULT sq_nn_AnnPrint(HSQUIRRELVM v)
- {
- SQ_FUNC_VARS_NO_TOP(v);
- SQ_GET_NN_INSTANCE(v, 1);
- AnnPrint(self->nn);
- return 0;
- }
- #define _DECL_FUNC(name,nparams,tycheck) {_SC(#name),sq_nn_##name,nparams,tycheck}
- static SQRegFunction sq_nn_methods[] =
- {
- _DECL_FUNC(constructor, -5,_SC("xiiaiii")),
- _DECL_FUNC(clone, 1,_SC("x")),
- _DECL_FUNC(Ann2Tcl, 1,_SC("x")),
- _DECL_FUNC(Ann2Js, 1,_SC("x")),
- _DECL_FUNC(AnnPrint, 1,_SC("x")),
- _DECL_FUNC(flags, -1,_SC("xi")),
- _DECL_FUNC(learn_rate, -1,_SC("xf")),
- _DECL_FUNC(rprop_nminus, -1,_SC("xf")),
- _DECL_FUNC(rprop_nplus, -1,_SC("xf")),
- _DECL_FUNC(rprop_maxupdate, -1,_SC("xf")),
- _DECL_FUNC(rprop_minupdate, -1,_SC("xf")),
- _DECL_FUNC(weights, 1,_SC("x")),
- _DECL_FUNC(weight, -4,_SC("xiiif")),
- _DECL_FUNC(observe, -3,_SC("xaai")),
- _DECL_FUNC(train, -1,_SC("xiii")),
- _DECL_FUNC(run, 2,_SC("xa")),
- _DECL_FUNC(classify, 2,_SC("xa")),
- _DECL_FUNC(info, 1,_SC("x")),
- {0,0}
- };
- #undef _DECL_FUNC
- typedef struct {
- const SQChar *Str;
- SQInteger Val;
- } KeyIntType, * KeyIntPtrType;
- static KeyIntType sqpcre2_constants[] = {
- #define MK_CONST(c) {_SC(#c), NR_##c}
- #define MK_CONST_FLAG(c) {_SC(#c), NR_FLAG_##c}
- MK_CONST_FLAG(NONE),
- MK_CONST_FLAG(TRAINING),
- MK_CONST_FLAG(REGRESSOR),
- MK_CONST_FLAG(CLASSIFIER),
- MK_CONST_FLAG(NORMALIZE),
- MK_CONST_FLAG(AUTO_STOP),
- MK_CONST_FLAG(OF_DETECTED),
- MK_CONST_FLAG(BACKTRACK),
- MK_CONST_FLAG(TO_PERSIST),
- MK_CONST_FLAG(TO_TRANSFER),
- MK_CONST(MAX_LAYERS),
- MK_CONST(RDB_ENC_VER),
- MK_CONST(INSERT_TRAIN),
- MK_CONST(INSERT_TEST),
- {0,0}
- };
- #ifdef __cplusplus
- extern "C" {
- #endif
- /* This defines a function that opens up your library. */
- SQRESULT sqext_register_nn (HSQUIRRELVM v) {
- sq_pushstring(v,sq_nn_TAG,-1);
- sq_newclass(v,SQFalse);
- sq_settypetag(v,-1,(void*)sq_nn_TAG);
- sq_insert_reg_funcs(v, sq_nn_methods);
- //add constants
- KeyIntPtrType KeyIntPtr;
- for (KeyIntPtr = sqpcre2_constants; KeyIntPtr->Str; KeyIntPtr++) {
- sq_pushstring(v, KeyIntPtr->Str, -1); //first the key
- sq_pushinteger(v, KeyIntPtr->Val); //then the value
- sq_newslot(v, -3, SQFalse); //store then
- }
- sq_newslot(v,-3,SQTrue);
- return SQ_OK;
- }
- #ifdef __cplusplus
- }
- #endif
|