You might notice that our C program isn't yet complete -- its main() function has a number of gaps, filled by comments indicating that we still have to add routines for allocating/deallocating memory, loading and preparing the data sets, and saving the results. Furthermore, it only performs a single calculation with the network, with an optional learning step. If we want to do something useful with our program, we might consider adding some interactive component like a command prompt -- which will require significantly more overhead.
The sales pitch here is that Yazoo can perform all of these housekeeping functions with fairly minimal scripting overhead. So instead of chugging through the rest of the program in C, we'll stop and plug what we have so far into Yazoo's scripting language.
The first order of business is to obtain the C version of Yazoo. (The main difference between the C and C++ versions is just in the extensions of the source files.) None of the source files need concern us now except userfn.c. In the body of userfn.c, we include our new NN.c source file (given in the last section). This is probably poor programming style but it works. We can leave the Debug() function in place if we want, but we won't be using it.
...
// #include any user-defined header files here
#include "NN.c"
...
The second step is to adapt our main() function in NN.c to let it communicate with Yazoo. The declaration of a Yazoo-embedded function is the same as that of the main() function, so for the most part we can keep what we wrote without any changes. We do, however, have to give our main() a new name, so as not to conflict with Yazoo's own main(). Let's call it run_network() instead.
int run_network(int argc, char **argv)
{ ...
We should now also add
extern int run_network(int, char **);
to NN.h.
The main channel through which Yazoo communicates with the C routine is the argv variable. argv points to a list of pointers to variables and arrays that are shared between the Yazoo script and the C code. (Structure-variables such as myNN are passed as several parameters, one for each element of the structure.) Importantly, since argv points to pointers, our C program can use it to both read data from, and send data back to, a Yazoo script.
To synchronize our C variables with our Yazoo variables, let's replace the "set up data types" comment line in NN.c with the following code. Note that ArgInfo is an extra parameter that Yazoo passes, which gives information about the other arguments.
arg_info *ArgInfo;
ArgInfo = (arg_info *) *(argv+argc);
myNN.neurons_num = (ArgInfo+1)->arg_indices;
inputs_num = (ArgInfo+2)->arg_indices;
myNN.weights = (double *) *argv;
myNN.activity = (double *) *(argv+1);
inputs = (double *) *(argv+2);
step_size = *(double *) *(argv+3);
if (argc == 6) {
outputs_num = (ArgInfo+4)->arg_indices;
target_outputs = (double *) *(argv+4);
learning_rate = *(double *) *(argv+5); }
Notice that we assigned some of our variables, namely the weights and activity fields of myNN, by reference rather than by value. One reason for this is that they are arrays and copying them would be time-consuming. The other reason is that the job of our routine is to modify activity and, if we are training our network, the weights array as well, so we need to write into some space that is shared with the script. Note that if we accidentally write past this shared space, we in all likelihood blow a few of Yazoo's fuses and cause it to start misbehaving; this can be hard to catch so we have to be careful.
Since the results of our calculations are stored in Yazoo variables, there is no need to manually export data. We can simply delete the "save results" comment line in NN.c.
One small aside: when we declared the ArgInfo variable we used a data type that is defined by Yazoo in userfn.h; so we need to add
#include "userfn.h"
at the beginning of NN.c.
The final step is to make Yazoo aware of our new routine. For this we have to go back to Yazoo's `userfn.c' source file. The only line we need to adapt is the definition of UserFunctionSet, which catalogs every C or C++ routine that Yazoo can access. To add a function of our own we need to give both the address of the function and a name (a string) that our scripts can know it by.
UserFunction UserFunctionSet[] = { { "Debug", &Debug }, { "Yazoo", &run_yazoo },
{ "RunNetwork", &run_network } };
And with that we are done. That is, the C code is fully integrated, and we should be able to recompile Yazoo and run our neural network from a script. Admittedly, there are things that we could have done better: in particular we should probably have included NN.h in userfn.c, and updated the makefile to include our source and header files. But what we have written should at least work.
To recompile: first make sure all source and header files, including NN.c and NN.h, are in the same directory as `Makefile'; then go to that directory from the command prompt and type "make yazoo" (case sensitive). (The `make' tool has to be installed for this to work.) With luck, we'll end up with an executable. To run it, type either `yazoo' or `./yazoo', depending on the system. We should see:
>
Once inside Yazoo, we will be able to execute our neural network routine by typing:
call("RunNetwork", param1, param2, ...)
Which begs the question of how to define and work with the parameters. The promise is that this will be quicker to do in Yazoo than in C. Our next order of business will be to write a neural-network class whose `run' function will take care of all that overhead. Doing so will make the scripts more eloquent (no ugly call() statement), and will allow the user to leverage the flexibility of ordinary Yazoo function calls.
Last update: July 28, 2013