AI::NeuralNet::BackProp - A simple back-prop neural net that uses Delta's and Hebbs' rule.
use AI::NeuralNet::BackProp;
# Create a new neural net with 2 layers and 3 neurons per layer my $net = new AI::NeuralNet::BackProp(2,3);
# Associate first pattern and print benchmark print "Associating (1,2,3) with (4,5,6)...\n"; print $net->learn([1,2,3],[4,5,6]);
# Associate second pattern and print benchmark print "Associating (4,5,6) with (1,2,3)...\n"; print $net->learn([4,5,6],[1,2,3]);
# Run a test pattern print "\nFirst run output: (".join(',',@{$net->run([1,3,2])}).")\n\n";
# Declare patterns to learn my @pattern = ( 15, 3, 5 ); my @result = ( 16, 10, 11 );
# Display patterns to associate using sub interpolation into a string. print "Associating (@{[join(',',@pattern)]}) with (@{[join(',',@result)]})...\n";
# Run learning loop and print benchmarking info. print $net->learn(\@pattern,\@result);
# Run final test my @test = ( 14, 9, 3 ); my $array_ref = $net->run(\@test);
# Display test output print "\nSecond run output: (".join(',',@{$array_ref}).")\n";
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);
The new()
constructor accepts two arguments, $layers and $size (in this example, $layers
is 2 and $size is 3).
$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 [, $inc]);
my $result = $net->run(\@map);
$inc is an optinal learning speed increment. Good values are around 0.20 and 0.30. You can experiement with $inc to achieve faster learning speeds. Some values of $inc work better for different maps. If $inc is ommitted, it will default to 0.30 for $inc internally.
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()).
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.
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.
learn()
is complete
with the pattern()
method, below.
The first two arguments must be array refs, and must be of the same length.
$learning_gradient is an optional value used to adjust the weights of the internal connections. If $learning_gradient is ommitted, it defaults to 0.30.
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.
The other condition that could cause an error is the fact that your map contained an
element with an undefined value. We don't allow this because it has been in testing that
an undefined value can never be weighted. We all know that 12958710924 times 0 is still
0, right? The network can't handle that, though. It will still try to apply as much weight
as it can to a 0 value, but the weighting will always come back 0, and therefore, never be able
to match the desired result output, thereby creating an infinite learn()
loop cycle. Soooo...
to prevent the infinite looping, we simply don't allow 0 values to be run. You can always
shift all values of your map up one number to account for this if need be, and then subtract
one number from every element of the output to shift it down again. Let me know if anyone
comes up with a better way to do this.
run()
will store the pattern index of the group as created by learn(), so it can be
retrieved with the pattern()
method, below.
See notes on comparison between run()
and slow_run()
in NOTES section, below.
This slow_run()
is different from run(), above, in that, the run()
above
compares the input map with the learn()ed input map of each group, and the
group who's comparission comes out as the lowest percentage difference is
then used to run the input map.
This slow_run()
runs the input map through every neuron group and then compares
the result map()
with the learn()ed result map, and the result map that has the
lowest comparrison percentage is returned as the output map. Some may argue
that this could be more accurate. I don't know. I plan to run some more tests
on the two methods, but right now I don't have the time. If anyone does
come up with any results, or even a better way to sort the outputs, let me know
please (jdb@wcoil.com)
slow_run()
will store the pattern index of the group as created by learn(), so it can be
retrieved with the pattern()
method, below.
See notes on comparison between run()
and slow_run()
in NOTES section, below.
This is useful if you don't care about the mapping output, but just what it mapped. For example, in the letters.pl example under the ./examples/ directory in the installation tree that you should have gotten when you downloaded this pacakge, this method is used to determine which letter was matched, rather than what the output is. See letters.pl for example usage.
learn()
or the last run()
call,
whichever occured later. It is easily printed as a string,
as following:
print $net->benchmarked() . "\n";
debug($level)
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.
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.
sprintf()
and int()
, Provides
better rounding than just calling int()
on the float. Also used very heavily internally.
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.
run()
or learn()
methods of your network.
run()
and slow_run()
comparedAuthors thoughts...
Hmm.. this could be something. I just realized:
With slow_run()
, it compares the _outputs_
(result of each group running input map) with
the desired result (output) map of each group.
run()
only compares the input map with the learn()
ed
input map.
With run()
, that means that the first group that matches
closest with learn()
ed maps will be run, even if you
have learned the same map with different desired results.
With slow_run()
, you could conceivably learn one input
map with multiple desired results, and then slow_run()
will
match the result from the input map against all desired results,
and return the one that matches closest.
Intersting idea. For now, I don't see much of a need for multiple desired asociations with same input map.
Please let me know if anyone sees any other pros or cons to this issue, and what you think should be done.
load()
and save()
These are two methods I have not documented, as they don't work (correctly) yet. They rely on the Storable package, not included, and the AI::NeuralNet::BackProp::File pacakge, included here.
The AI::NeuralNet::BackProp::File package works fine, the
problem lies in the load()
and save()
routines themselves.
It seems the freeze()
and thaw()
functions aren't handling
the refrences very well.
I included these functions in this beta release in case anyone felt dareing enough to try to get them working themselves. If you do, please send me a copy of the code! :-)
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>.
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 COMES WITHOUT WARRANTY OF ANY KIND.
You can always download the latest copy of AI::NeuralNet::BackProp from http://www.josiah.countystart.com/modules/AI/cgi-bin/rec.pl