The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

AI::NeuralNet::BackProp - A simple back-prop neural net that uses Delta's and Hebbs' rule.

SYNOPSIS

use AI::NeuralNet::BackProp;

        # Create a new network with 1 layer, 5 inputs, and 5 outputs.
        my $net = new AI::NeuralNet::BackProp(1,5,5);
        
        # Add a small amount of randomness to the network
        $net->random(0.001);

        # Demonstrate a simple learn() call
        my @inputs = ( 0,0,1,1,1 );
        my @ouputs = ( 1,0,1,0,1 );
        
        print $net->learn(\@inputs, \@outputs),"\n";

        # Create a data set to learn
        my @set = (
                [ 2,2,3,4,1 ], [ 1,1,1,1,1 ],
                [ 1,1,1,1,1 ], [ 0,0,0,0,0 ],
                [ 1,1,1,0,0 ], [ 0,0,0,1,1 ]    
        );
        
        # Demo learn_set()
        my $f = $net->learn_set(\@set);
        print "Forgetfulness: $f unit\n";
        
        # Crunch a bunch of strings and return array refs
        my $phrase1 = $net->crunch("I love neural networks!");
        my $phrase2 = $net->crunch("Jay Lenno is wierd.");
        my $phrase3 = $net->crunch("The rain in spain...");
        my $phrase4 = $net->crunch("Tired of word crunching yet?");

        # Make a data set from the array refs
        my @phrases = (
                $phrase1, $phrase2,
                $phrase3, $phrase4
        );

        # Learn the data set    
        $net->learn_set(\@phrases);
        
        # Run a test phrase through the network
        my $test_phrase = $net->crunch("I love neural networking!");
        my $result = $net->run($test_phrase);
        
        # Get this, it prints "Jay Leno is  networking!" ...  LOL!
        print $net->uncrunch($result),"\n";

UPDATES

This is version 0.77, a complete internal upgrade from version 0.42. A new feature is the introduction of a randomness factor in the network, optional to disable. The restriction on 0s are removed, so you can run any network you like. See NOTES on using 0s with randomness disabled, below. Included is an improved learn() function, and a much more accurate internal fixed-point system for learning. Also included is automated learning of input sets. See learn_set() and learn_rand_set()

DESCRIPTION

AI::NeuralNet::BackProp is the flagship package for this file. It implements a nerual network similar to a feed-foward, back-propagtion network; learning via a mix of a generalization of the Delta rule and a disection of Hebbs rule. The actual neruons of the network are implemented via the AI::NeuralNet::BackProp::neuron package.

You constuct a new network via the new constructor:

        my $net = new AI::NeuralNet::BackProp(2,3,1);
                

The new() constructor accepts two arguments and one optional argument, $layers, $size, and $outputs is optional (in this example, $layers is 2, $size is 3, and $outputs is 1).

$layers specifies the number of layers, including the input and the output layer, to use in each neural grouping. A new neural grouping is created for each pattern learned. Layers is typically set to 2. Each layer has $size neurons in it. Each neuron's output is connected to one input of every neuron in the layer below it.

This diagram illustrates a simple network, created with a call to "new AI::NeuralNet::BackProp(2,2)" (2 layers, 2 neurons/layer).

     input
     /  \
    O    O
    |\  /|
    | \/ |
    | /\ |
    |/  \|
    O    O
     \  /
    mapper

In this diagram, each neuron is connected to one input of every neuron in the layer below it, but there are not connections between neurons in the same layer. Weights of the connection are controlled by the neuron it is connected to, not the connecting neuron. (E.g. the connecting neuron has no idea how much weight its output has when it sends it, it just sends its output and the weighting is taken care of by the receiving neuron.) This is the method used to connect cells in every network built by this package.

Input is fed into the network via a call like this:

        use AI;
        my $net = new AI::NeuralNet::BackProp(2,2);
        
        my @map = (0,1);
        
        my $result = $net->run(\@map);
        

Now, this call would probably not give what you want, because the network hasn't "learned" any patterns yet. But this illustrates the call. Run expects an array refrence, and run gets mad if you don't give it what it wants. So be nice.

Run returns a refrence with $size elements (Remember $size? $size is what you passed as the second argument to the network constructor.) This array contains the results of the mapping. If you ran the example exactly as shown above, $result would contain (1,1) as its elements.

To make the network learn a new pattern, you simply call the learn method with a sample input and the desired result, both array refrences of $size length. Example:

        use AI;
        my $net = new AI::NeuralNet::BackProp(2,2);
        
        my @map = (0,1);
        my @res = (1,0);
        
        $net->learn(\@map,\@res);
        
        my $result = $net->run(\@map);

Now $result will conain (1,0), effectivly flipping the input pattern around. Obviously, the larger $size is, the longer it will take to learn a pattern. Learn() returns a string in the form of

        Learning took X loops and X wallclock seconds (X.XXX usr + X.XXX sys = X.XXX CPU).

With the X's replaced by time or loop values for that loop call. So, to view the learning stats for every learn call, you can just:

        print $net->learn(\@map,\@res);
        

If you call "$net->debug(4)" with $net being the refrence returned by the new() constructor, you will get benchmarking information for the learn function, as well as plenty of other information output. See notes on debug() , in METHODS, below.

If you do call $net->debug(1), it is a good idea to point STDIO of your script to a file, as a lot of information is output. I often use this command line:

        $ perl some_script.pl > .out

Then I can simply go and use emacs or any other text editor and read the output at my leisure, rather than have to wait or use some 'more' as it comes by on the screen.

This system was originally created to be a type of content-addressable-memory system. As such, it implements "groups" for storing patterns and maps. After the network has learned the patterns you want, then you can call run with a pattern it has never seen before, and it will decide which of the stored patterns best fit the new pattern, returning the results the same as the above examples (as an array ref from $net->run()).

METHODS

new AI::NeuralNet::BackProp($layers, $size [, $outputs])

Returns a newly created neural network from an AI::NeuralNet::BackProp object. Each group of this network will have $layers number layers in it and each layer will have $size number of neurons in that layer.

There is an optional parameter of $outputs, which specifies the number of output neurons to provide. If $outputs is not specified, $outputs defaults to equal $size. $outputs may not exceed $size. If $outputs exceeds $size, the new() constructor will return undef.

Before you can really do anything useful with your new neural network object, you need to teach it some patterns. See the learn() method, below.

$net->learn($input_map_ref, $desired_result_ref [, options ]);

This will 'teach' a network to associate an new input map with a desired resuly. It will return a string containg benchmarking information. You can retrieve the pattern index that the network stored the new input map in after learn() is complete with the pattern() method, below.

The first two arguments must be array refs, and they may be of different lengths.

Options should be written on hash form. There are three options:

         inc    =>      $learning_gradient
         max    =>      $maximum_iterations
         error  =>      $maximum_allowable_percentage_of_error
         

$learning_gradient is an optional value used to adjust the weights of the internal connections. If $learning_gradient is ommitted, it defaults to 0.20.

$maximum_iterations is the maximum numbers of iteration the loop should do. It defaults to 1024. Set it to 0 if you never want the loop to quit before the pattern is perfectly learned.

$maximum_allowable_percentage_of_error is the maximum allowable error to have. If this is set, then learn() will return when the perecentage difference between the actual results and desired results falls below $maximum_allowable_percentage_of_error. If you do not include 'error', or $maximum_allowable_percentage_of_error is set to -1, then learn() will not return until it gets an exact match for the desired result OR it reaches $maximum_iterations.

$net->learn_set(\@set, [ options ]);

This takes the same options as learn() and allows you to specify a set to learn, rather than individual patterns. learn_set() will return an integer specifying the amount of forgetfulness when all the patterns are learned. If the learn_set()-specific option 'p' is set true, as in 'p => 1' in the hash of options, then it will return a percentage represting the amount of forgetfullness, rather than an integer.

NOTE: I have disabled percentage returns, so it will always return a integer, for now. I disabled it because I found there is a problem with the array refs and percentages when I was writing the new synopsis, so for now it returns intergers, no matter what the 'p' option is.

Example:

        # Data from 1989 (as far as I know..this is taken from example data on BrainMaker)
        my @data = ( 
                #       Mo  CPI  CPI-1 CPI-3    Oil  Oil-1 Oil-3    Dow   Dow-1 Dow-3   Dow Ave (output)
                [       1,      229, 220,  146,         20.0, 21.9, 19.5,       2645, 2652, 2597],      [       2647  ],
                [       2,      235, 226,  155,         19.8, 20.0, 18.3,       2633, 2645, 2585],      [       2637  ],
                [       3,      244, 235,  164,         19.6, 19.8, 18.1,       2627, 2633, 2579],      [       2630  ],
                [       4,      261, 244,  181,         19.6, 19.6, 18.1,       2611, 2627, 2563],      [       2620  ],
                [       5,      276, 261,  196,         19.5, 19.6, 18.0,       2630, 2611, 2582],      [       2638  ],
                [       6,      287, 276,  207,         19.5, 19.5, 18.0,       2637, 2630, 2589],      [       2635  ],
                [       7,      296, 287,  212,         19.3, 19.5, 17.8,       2640, 2637, 2592],      [       2641  ]                 
        );
        
        # Learn the set
        my $f = learn_set(\@data, 
                                          inc   =>      0.1,    
                                          max   =>      500,
                                          p             =>      1
                                         );
                        
        # Print it 
        print "Forgetfullness: $f%";

    

This is a snippet from the example script examples/finance.pl, which demonstrates DOW average prediction for the next month. A more simple set defenition would be as such:

        my @data = (
                [ 0,1 ], [ 1 ],
                [ 1,0 ], [ 0 ]
        );
        
        $net->learn_set(\@data);
        

Same effect as above, but not the same data (obviously).

$net->learn_set_rand(\@set, [ options ]);

This takes the same options as learn() and allows you to specify a set to learn, rather than individual patterns.

learn_set_rand() differs from learn_set() in that it learns the patterns in a random order, each pattern once, rather than in the order that they are in the array. This returns a true value (1) instead of a forgetfullnes factor.

Example:

        my @data = (
                [ 0,1 ], [ 1 ],
                [ 1,0 ], [ 0 ]
        );
        
        $net->learn_set_rand(\@data);
        
$net->run($input_map_ref);

This method will apply the given array ref at the input layer of the neural network.

It will return undef on an error. An error is caused by one of two events.

The first is the possibility that the argument passed is not an array ref. If it is not an array ref, it returns silently a value of undef.

UPDATED: You can now run maps with a 0 value. Beware though, it may not learn() a 0 value in the input map if you have randomness disabled. See NOTES on using a 0 value with randomness disabled.

$net->benchmarked();

This returns a benchmark info string for the last learn() or the last run() call, whichever occured later. It is easily printed as a string, as following:

        print $net->benchmarked() . "\n";
$net->debug($level)

Toggles debugging off if called with $level = 0 or no arguments. There are four levels of debugging.

Level 0 ($level = 0) : Default, no debugging information printed, except for the 'Cannot run 0 value.' error message. Other than that one message, all printing is left to calling script.

Level 1 ($level = 1) : This causes ALL debugging information for the network to be dumped as the network runs. In this mode, it is a good idea to pipe your STDIO to a file, especially for large programs.

Level 2 ($level = 2) : A slightly-less verbose form of debugging, not as many internal data dumps.

Level 3 ($level = 3) : JUST prints weight mapping as weights change.

Level 4 ($level = 4) : JUST prints the benchmark info for EACH learn loop iteteration, not just learning as a whole. Also prints the percentage difference for each loop between current network results and desired results, as well as learning gradient ('incremenet').

Level 4 is useful for seeing if you need to give a smaller learning incrememnt to learn() . I used level 4 debugging quite often in creating the letters.pl example script and the small_1.pl example script.

Toggles debuging off when called with no arguments.

$net->save($filename);

This will save the complete state of the network to disk, including all weights and any words crunched with crunch() .

$net->load($filename);

This will load from disk any network saved by save() and completly restore the internal state at the point it was save() was called at.

$net->join_cols($array_ref,$row_length_in_elements,$high_state_character,$low_state_character);

This is more of a utility function than any real necessary function of the package. Instead of joining all the elements of the array together in one long string, like join() , it prints the elements of $array_ref to STDIO, adding a newline (\n) after every $row_length_in_elements number of elements has passed. Additionally, if you include a $high_state_character and a $low_state_character, it will print the $high_state_character (can be more than one character) for every element that has a true value, and the $low_state_character for every element that has a false value. If you do not supply a $high_state_character, or the $high_state_character is a null or empty or undefined string, it join_cols() will just print the numerical value of each element seperated by a null character (\0). join_cols() defaults to the latter behaviour.

$net->pdiff($array_ref_A, $array_ref_B);

This function is used VERY heavily internally to calculate the difference in percent between elements of the two array refs passed. It returns a %.02f (sprintf-format) percent sting.

$net->p($a,$b);

Returns a floating point number which represents $a as a percentage of $b.

$net->intr($float);

Rounds a floating-point number rounded to an integer using sprintf() and int() , Provides better rounding than just calling int() on the float. Also used very heavily internally.

$net->high($array_ref);

Returns the index of the element in array REF passed with the highest comparative value.

$net->low($array_ref);

Returns the index of the element in array REF passed with the lowest comparative value.

$net->show();

This will dump a simple listing of all the weights of all the connections of every neuron in the network to STDIO.

$net->crunch($string);

UPDATE: Now you can use a variabled instead of using qw(). Strings will be split internally. Do not use qw() to pass strings to crunch.

This splits a string passed with /[\s\t]/ into an array ref containing unique indexes to the words. The words are stored in an intenal array and preserved across load() and save() calls. This is designed to be used to generate unique maps sutible for passing to learn() and run() directly. It returns an array ref.

The words are not duplicated internally. For example:

        $net->crunch("How are you?");

Will probably return an array ref containing 1,2,3. A subsequent call of:

    $net->crunch("How is Jane?");

Will probably return an array ref containing 1,4,5. Notice, the first element stayed the same. That is because it already stored the word "How". So, each word is stored only once internally and the returned array ref reflects that.

$net->uncrunch($array_ref);

Uncrunches a map (array ref) into an array of words (not an array ref) and returns array. This is ment to be used as a counterpart to the crunch() method, above, possibly to uncrunch() the output of a run() call. Consider the below code (also in ./examples/ex1.pl):

        use AI::NeuralNet::BackProp;
        my $net = AI::NeuralNet::BackProp->new(2,3);
        
        for (0..3) {
                $net->learn($net->crunch("I love chips."),  $net->crunch(qw(That's Junk Food!"));
                $net->learn($net->crunch("I love apples."), $net->crunch("Good, Healthy Food."));
                $net->learn($net->crunch("I love pop."),    $net->crunch("That's Junk Food!"));
                $net->learn($net->crunch("I love oranges."),$net->crunch("Good, Healthy Food."));
        }
        
        my $response = $net->run($net->crunch("I love corn."));
        
        print join(' ',$net->uncrunch($response));

On my system, this responds with, "Good, Healthy Food." If you try to run crunch() with "I love pop.", though, you will probably get "Food! apples. apples." (At least it returns that on my system.) As you can see, the associations are not yet perfect, but it can make for some interesting demos!

$net->crunched($word);

This will return undef if the word is not in the internal crunch list, or it will return the index of the word if it exists in the crunch list.

$net->col_width($width);

This is useful for formating the debugging output of Level 4 if you are learning simple bitmaps. This will set the debugger to automatically insert a line break after that many elements in the map output when dumping the currently run map during a learn loop.

It will return the current width when called with a 0 or undef value.

$net->random($rand);

This will set the randomness factor from the network. Default is 0.001. When called with no arguments, or an undef value, it will return current randomness value. When called with a 0 value, it will disable randomness in the network. See NOTES on learning a 0 value in the input map with randomness disabled.

$net->load_pcx($filename);

Oh heres a treat... this routine will load a PCX-format file (yah, I know ... ancient format ... but it is the only one I could find specs for to write it in Perl. If anyone can get specs for any other formats, or could write a loader for them, I would be very grateful!) Anyways, a PCX-format file that is exactly 320x200 with 8 bits per pixel, with pure Perl. It returns a blessed refrence to a AI::NeuralNet::BackProp::PCX object, which supports the following routinges/members. See example files pcx.pl and pcx2.pl in the ./examples/ directory.

$pcx->{image}

This is an array refrence to the entire image. The array containes exactly 64000 elements, each element contains a number corresponding into an index of the palette array, details below.

$pcx->{palette}

This is an array ref to an AoH (array of hashes). Each element has the following three keys:

        $pcx->{palette}->[0]->{red};
        $pcx->{palette}->[0]->{green};
        $pcx->{palette}->[0]->{blue};

Each is in the range of 0..63, corresponding to their named color component.

$pcx->get_block($array_ref);

Returns a rectangular block defined by an array ref in the form of:

        [$left,$top,$right,$bottom]

These must be in the range of 0..319 for $left and $right, and the range of 0..199 for $top and $bottom. The block is returned as an array ref with horizontal lines in sequental order. I.e. to get a pixel from [2,5] in the block, and $left-$right was 20, then the element in the array ref containing the contents of coordinates [2,5] would be found by [5*20+2] ($y*$width+$x).

        print (@{$pcx->get_block(0,0,20,50)})[5*20+2];

This would print the contents of the element at block coords [2,5].

$pcx->get($x,$y);

Returns the value of pixel at image coordinates $x,$y. $x must be in the range of 0..319 and $y must be in the range of 0..199.

$pcx->rgb($index);

Returns a 3-element array (not array ref) with each element corresponding to the red, green, or blue color components, respecitvely.

$pcx->avg($index);

Returns the mean value of the red, green, and blue values at the palette index in $index.

NOTES

Learning 0s With Randomness Disabled

You can now use 0 values in any input maps. This is a good improvement over versions 0.40 and 0.42, where no 0s were allowed because the learning would never finish learning completly with a 0 in the input.

Yet with the allowance of 0s, it requires one of two factors to learn correctly. Either you must enable randomness with $net->random(0.0001) (Other valuse work, see random() ), or you must set an error-minum with the 'error => 5' option (you can use some other error value as well).

When randomness is enabled (that is, when you call random() with a value other than 0), it interjects a bit of randomness into the output of every neuron in the network, except for the input and output neurons. The randomness is interjected with rand()*$rand, where $rand is the value that was passed to random() call. This assures the network that it will never have a pure 0 internally. It is bad to have a pure 0 internally because the weights cannot change a 0 when multiplied by a 0, the product stays a 0. Yet when a weight is multiplied by 0.00001, eventually with enough weight, it will be able to learn. With a 0 value instead of 0.00001 or whatever, then it would never be able to add enough weight to get anything other than a 0.

The second option to allow for 0s is to enable a maximum error with the 'error' option in learn() , learn_set() , and learn_set_rand() . This allows the network to not worry about learning an output perfectly.

For accuracy reasons, it is recomended that you work with 0s using the random() method.

If anyone has any thoughts/arguments/suggestions for using 0s in the network, let me know at jdb@wcoil.com.

OTHER INCLUDED PACKAGES

AI::NeuralNet::BackProp::File

AI::NeuralNet::BackProp::File implements a simple 'relational'-style database system. It is used internally by AI::NeuralNet::BackProp for storage and retrival of network states. It can also be used independently of AI::NeuralNet::BackProp. PODs are not yet included for this package, I hope to include documentation for this package in future releases.

AI::NeuralNet::BackProp::File depends on Storable, version 0.611 for low- level disk access. This dependency is noted in Makefile.PL, and should be handled automatically when you installe this AI::NeuralNet::BackProp.

AI::NeuralNet::BackProp::neuron

AI::NeuralNet::BackProp::neuron is the worker package for AI::NeuralNet::BackProp. It implements the actual neurons of the nerual network. AI::NeuralNet::BackProp::neuron is not designed to be created directly, as it is used internally by AI::NeuralNet::BackProp.

AI::NeuralNet::BackProp::_run
AI::NeuralNet::BackProp::_map

These two packages, _run and _map are used to insert data into the network and used to get data from the network. The _run and _map packages are connected to the neurons so that the neurons think that the IO packages are just another neuron, sending data on. But the IO packs. are special packages designed with the same methods as neurons, just meant for specific IO purposes. You will never need to call any of the IO packs. directly. Instead, they are called whenever you use the run() or learn() methods of your network.

BUGS

This is the beta release of AI::NeuralNet::BackProp, and that holding true, I am sure there are probably bugs in here which I just have not found yet. If you find bugs in this module, I would appreciate it greatly if you could report them to me at <jdb@wcoil.com>, or, even better, try to patch them yourself and figure out why the bug is being buggy, and send me the patched code, again at <jdb@wcoil.com>.

AUTHOR

Josiah Bryan <jdb@wcoil.com>

Copyright (c) 2000 Josiah Bryan. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

The AI::NeuralNet::BackProp and related modules are free software. THEY COME WITHOUT WARRANTY OF ANY KIND.

THANX

Below is a list of people that have helped, made suggestions, patches, etc. No particular order.

                Tobias Bronx, F<E<lt>tobiasb@odin.funcom.comE<gt>> 
                Pat Trainor, F<E<lt>ptrainor@title14.comE<gt>>
                Steve Purkis, F<E<lt>spurkis@epn.nuE<gt>>
                Rodin Porrata, F<E<lt>rodin@ursa.llnl.govE<gt>>

Tobias was a great help with the initial releases, and helped with learning options and a great many helpful suggestions. Rodin has gave me some great ideas for the new internals. Steve is the author of AI::Perceptron, and gave some good suggestions for weighting the neurons. Pat has been a great help for running the module through the works. Pat is the author of the new Inter game, a in-depth strategy game. He is using a group of neural networks internally which provides a good test bed for coming up with new ideas for the network. Thankyou for all of your help, everybody.

DOWNLOAD

You can always download the latest copy of AI::NeuralNet::BackProp from http://www.josiah.countystart.com/modules/AI/cgi-bin/rec.pl

5 POD Errors

The following errors were encountered while parsing the POD:

Around line 2652:

You forgot a '=back' before '=head1'

Around line 2654:

'=item' outside of any '=over'

Around line 2685:

You forgot a '=back' before '=head1'

Around line 2687:

'=item' outside of any '=over'

Around line 2722:

You forgot a '=back' before '=head1'