File Coverage

blib/lib/AI/NNFlex.pm
Criterion Covered Total %
statement 261 351 74.3
branch 38 78 48.7
condition 1 3 33.3
subroutine 18 21 85.7
pod 5 12 41.6
total 323 465 69.4


line stmt bran cond sub pod time code
1 5     5   27 use strict;
  5         11  
  5         407  
2 5     5   27 use vars qw ($VERSION);
  5         10  
  5         578  
3             #use warnings;
4             ###############################################################################
5             # NNFlex - Neural Network (flexible) - a heavily custom NN simulator
6             #
7             # Sept 2004 - CW Colbourn
8             #
9             # This was developed from the abortive nnseq package originally intended
10             # for real time neural networks.
11             # The basis of the approach for this version is a very flexible, modular
12             # set of packages. This package constitutes the base, allowing the modeller
13             # to create meshes, apply input, and read output ONLY!
14             #
15             # Separate modules are to be written to perform feedback adjustments,
16             # various activation functions, text/gui front ends etc
17             #
18             ###############################################################################
19             # Version Control
20             # ===============
21             #
22             # 0.1 20040905 CColbourn New module
23             # added NNFlex::datasets
24             #
25             # 0.11 20050113 CColbourn Added NNFlex::lesion
26             # Improved Draw
27             # added NNFlex::datasets
28             #
29             # 0.12 20050116 CColbourn Fixed reinforce.pm bug
30             # Added call into datasets
31             # in ::run to offer alternative
32             # syntax
33             #
34             # 0.13 20050121 CColbourn Created momentum learning module
35             #
36             # 0.14 20050201 CColbourn Abstracted derivatiive of activation
37             # function into a separate function call
38             # instead of hardcoded 1-y*y in backprop
39             # tanh, linear & momentum
40             #
41             # 0.15 20050206 CColbourn Fixed a bug in feedforward.pm. Stopped
42             # calling dbug unless scalar debug > 0
43             # in a lot of calls
44             #
45             # 0.16 20050218 CColbourn Changed from a hash of weights to an
46             # array of weights, to make it easier
47             # to adapt the code to PDL
48             #
49             # 0.17 20050302 CColbourn Changed input params to ::output to
50             # be param=>parameter not anon hash
51             # Included round parameter in output
52             #
53             # 0.20 20050307 CColbourn Modified for inheritance to simplify
54             # future network types
55             #
56             # 0.21 20050316 CColbourn Rewrote perldocs, implemented fahlman
57             # constant, chopped out old legacy stuff
58             # put math functions in mathlib, etc etc
59             #
60             # 0.22 20050317 CColbourn Implemented ::connect method
61             #
62             # 0.23 20050424 CColbourn Included Hopfield module in dist.
63             #
64             # 0.24 20050620 CColbourn Corrected a bug in the bias weight
65             # calculation
66             #
67             #
68             ###############################################################################
69             # ToDo
70             # ====
71             #
72             # Modify init to allow recurrent layer/node connections
73             # write cmd & gui frontends
74             # Speed the bugger up!
75             #
76             # Odd thought - careful coding of a network would allow grafting of
77             # two different network types or learning algorithms, like an effectve
78             # single network with 2 layers unsupervised and 2 layers supervised
79             #
80             # Clean up the perldocs
81             #
82             ###############################################################################
83             $VERSION = "0.24";
84              
85              
86             ###############################################################################
87             my @DEBUG; # a single, solitary, shameful global variable. Couldn't
88             #avoid it really. It allows correct control of debug
89             #information before the $network object is created
90             # (in ::layer->new & ::node->new for example).
91              
92              
93             ###############################################################################
94             ###############################################################################
95             # package NNFlex
96             ###############################################################################
97             ###############################################################################
98             package AI::NNFlex;
99 5     5   3167 use AI::NNFlex::Mathlib;
  5         11  
  5         162  
100 5     5   31 use base qw(AI::NNFlex::Mathlib);
  5         8  
  5         33064  
101              
102              
103              
104              
105              
106             ###############################################################################
107             # AI::NNFlex::new
108             ###############################################################################
109             sub new
110             {
111 5     5 1 183 my $class = shift;
112 5         13 my $network={};
113 5         17 bless $network,$class;
114              
115             # intercept the new style 'empty network' constructor call
116             # Maybe I should deprecate the old one, but its convenient, provided you
117             # can follow the mess of hashes
118            
119 5 50       101 if (!grep /HASH/,@_)
120             {
121 5         38 my %config = @_;
122 5         25 foreach (keys %config)
123             {
124 23         88 $network->{$_} = $config{$_};
125             }
126              
127 5         37 return $network;
128             }
129              
130             # Otherwise, continue assuming that the whole network is defined in
131             # a pair of anonymous hashes
132              
133              
134              
135 0         0 my $params = shift;
136 0         0 my $netParams = shift;
137 0         0 my @layers;
138 0         0 dbug ($netParams,"Entered AI::NNFlex::new with params $params $netParams",2);
139              
140              
141             # clean up case & spaces in layer defs from pre 0.14 constructor calls:
142 0         0 my $cleanParams;
143 0         0 foreach my $layer(@{$params})
  0         0  
144             {
145 0         0 my %cleanLayer;
146 0         0 foreach (keys %$layer)
147             {
148 0         0 my $key = lc($_);
149 0         0 $key =~ s/\s//g;
150 0         0 $cleanLayer{$key} = $$layer{$_};
151             }
152 0         0 push @$cleanParams,\%cleanLayer;
153             }
154              
155              
156              
157             # Network wide parameters (e.g. random weights)
158 0         0 foreach (keys %$netParams)
159             {
160 0         0 my $key = lc($_);
161 0         0 $key =~ s/\s//g; # up to 0.14 we had params with spaces in, now deprecated
162 0         0 $network->{$key} = ${$netParams}{$_};
  0         0  
163             }
164              
165 0 0       0 if( $network->{'debug'})
166             {
167 0         0 @DEBUG = @{$network->{'debug'}};
  0         0  
168             }
169              
170             # build the network
171 0         0 foreach (@$cleanParams)
172             {
173 0 0       0 if (!($$_{'nodes'})){next}
  0         0  
174 0         0 my %layer = %{$_};
  0         0  
175 0         0 push @layers,AI::NNFlex::layer->new(\%layer);
176             }
177 0         0 $$network{'layers'} = \@layers;
178              
179              
180              
181              
182              
183 0         0 $network->init;
184 0         0 return $network;
185             }
186              
187              
188              
189              
190             ###############################################################################
191             # AI::NNFlex::add_layer
192             ###############################################################################
193             #
194             # Adds a layer of given node definitions to the $network object
195             #
196             # syntax
197             #
198             # $network->add_layer(nodes=>4,activationfunction=>tanh);
199             #
200             # returns bool success or failure
201             #
202             ###############################################################################
203              
204             sub add_layer
205             {
206 8     8 1 2419 my $network = shift;
207              
208 8         66 my %config = @_;
209              
210 8         65 my $layer = AI::NNFlex::layer->new(\%config);
211              
212 8 50       28 if ($layer)
213             {
214 8         13 push @{$network->{'layers'}},$layer;
  8         36  
215 8         36 return 1;
216             }
217             else
218             {
219 0         0 return 0;
220             }
221             }
222              
223              
224             ###############################################################################
225             # AI::NNFlex::output
226             ###############################################################################
227             sub output
228             {
229 35     35 0 64 my $network = shift;
230 35         50 my %params = @_;
231              
232 35         36 my $finalLayer = ${$$network{'layers'}}[-1];
  35         64  
233              
234 35         35 my $outputLayer;
235              
236 35 50       70 if (defined $params{'layer'})
237             {
238 0         0 $outputLayer = ${$$network{'layers'}}[$params{'layer'}]
  0         0  
239             }
240             else
241             {
242 35         41 $outputLayer = $finalLayer
243             }
244              
245 35         64 my $output = AI::NNFlex::layer::layer_output($outputLayer);
246              
247              
248             # Round outputs if required
249 35 50       83 if ($network->{'round'})
250             {
251 0         0 foreach (@$output)
252             {
253 0 0       0 if ($_ > 0.5)
    0          
254             {
255 0         0 $_ = 1;
256             }
257             elsif ($_ < -0.5)
258             {
259 0         0 $_=-1;
260             }
261             else
262             {
263 0         0 $_=0;
264             }
265             }
266             }
267              
268 35         129 return $output;
269             }
270              
271             ################################################################################
272             # sub init
273             ################################################################################
274             sub init
275             {
276              
277             #Revised version of init for NNFlex
278              
279 4     4 1 511 my $network = shift;
280 4         8 my @layers = @{$network->{'layers'}};
  4         15  
281              
282             # if network debug state not set, set it to null
283 4 50       32 if (!$network->{'debug'})
284             {
285 0         0 $network->{'debug'} = [];
286             }
287 4         10 my @debug = @{$network->{'debug'}};
  4         11  
288            
289              
290             # implement the bias node
291 4 50       21 if ($network->{'bias'})
292             {
293 4         38 my $biasNode = AI::NNFlex::node->new({'activation function'=>'linear'});
294 4         19 $$network{'biasnode'} = $biasNode;
295 4         10 $$network{'biasnode'}->{'activation'} = 1;
296 4         14 $$network{'biasnode'}->{'nodeid'} = "bias";
297             }
298              
299 4         8 my $nodeid = 1;
300 4         8 my $currentLayer=0;
301             # foreach layer, we need to examine each node
302 4         12 foreach my $layer (@layers)
303             {
304             # Foreach node we need to make connections east and west
305 6         9 foreach my $node (@{$layer->{'nodes'}})
  6         16  
306             {
307 12         28 $node->{'nodeid'} = $nodeid;
308             # only initialise to the west if layer > 0
309 12 100       33 if ($currentLayer > 0 )
310             {
311 4         11 foreach my $westNodes (@{$network->{'layers'}->[$currentLayer -1]->{'nodes'}})
  4         12  
312             {
313 8         10 foreach my $connectionFromWest (@{$westNodes->{'connectedNodesEast'}->{'nodes'}})
  8         17  
314             {
315 16 100       47 if ($connectionFromWest eq $node)
316             {
317 8         14 my $weight = $network->calcweight;
318            
319 8         8 push @{$node->{'connectedNodesWest'}->{'nodes'}},$westNodes;
  8         25  
320 8         9 push @{$node->{'connectedNodesWest'}->{'weights'}},$weight;
  8         13  
321 8 50       31 if (scalar @debug > 0)
  0         0  
322             {$network->dbug ("West to east Connection - ".$westNodes->{'nodeid'}." to ".$node->{'nodeid'},2);}
323             }
324             }
325             }
326             }
327              
328             # Now initialise connections to the east (if not last layer)
329 12 100       39 if ($currentLayer < (scalar @layers)-1)
330             {
331 4         6 foreach my $eastNodes (@{$network->{'layers'}->[$currentLayer+1]->{'nodes'}})
  4         15  
332             {
333 8 50 33     27 if (!$network->{'randomconnections'} || $network->{'randomconnections'} > rand(1))
334             {
335 8         30 my $weight = $network->calcweight;
336 8         11 push @{$node->{'connectedNodesEast'}->{'nodes'}},$eastNodes;
  8         21  
337 8         8 push @{$node->{'connectedNodesEast'}->{'weights'}}, $weight;
  8         17  
338 8 50       26 if (scalar @debug > 0)
  0         0  
339             {$network->dbug ("East to west Connection ".$node->{'nodeid'}." to ".$eastNodes->{'nodeid'},2);}
340             }
341             }
342             }
343 12         589 $nodeid++;
344             }
345 6         15 $currentLayer++;
346             }
347              
348              
349             # add bias node to westerly connections
350 4 50       19 if ($network->{'bias'})
351             {
352 4         8 foreach my $layer (@{$network->{'layers'}})
  4         14  
353             {
354 6         10 foreach my $node (@{$layer->{'nodes'}})
  6         21  
355             {
356 12         543 push @{$node->{'connectedNodesWest'}->{'nodes'}},$network->{'biasnode'};
  12         37  
357 12         46 my $weight = $network->calcweight;
358              
359 12         16 push @{$node->{'connectedNodesWest'}->{'weights'}},$weight;
  12         608  
360 12 50       49 if (scalar @debug > 0)
  0         0  
361             {$network->dbug ("West to east Connection - bias to ".$node->{'nodeid'}." weight = $weight",2);}
362             }
363             }
364             }
365              
366              
367              
368 4         17 return 1; # return success if we get to here
369              
370              
371             }
372              
373             ###############################################################################
374             # sub $network->dbug
375             ###############################################################################
376             sub dbug
377             {
378 70     70 0 86 my $network = shift;
379 70         67 my $message = shift;
380 70         75 my $level = shift;
381              
382              
383 70         97 my @DEBUGLEVELS;
384             # cover for debug calls before the network is created
385 70 100       135 if (!$network->{'debug'})
386             {
387 28         50 @DEBUGLEVELS=@DEBUG;
388             }
389             else
390             {
391 42         37 @DEBUGLEVELS = @{$network->{'debug'}};
  42         74  
392             }
393              
394              
395             # 0 is error so ALWAYS display
396 70 50       152 if (!(grep /0/,@DEBUGLEVELS)){push @DEBUGLEVELS,0}
  70         91  
397              
398 70         98 foreach (@DEBUGLEVELS)
399             {
400            
401 70 50       308 if ($level == $_)
402             {
403 0         0 print "$message\n";
404             }
405             }
406             }
407              
408              
409             ###############################################################################
410             # AI::NNFlex::dump_state
411             ###############################################################################
412             sub dump_state
413             {
414 1     1 0 67 my $network = shift;
415 1         4 my %params =@_;
416              
417 1         1 my $filename = $params{'filename'};
418 1         2 my $activations = $params{'activations'};
419              
420            
421 1 50       125 open (OFILE,">$filename") or return "Can't create weights file $filename";
422              
423              
424 1         3 foreach my $layer (@{$network->{'layers'}})
  1         3  
425             {
426 2         2 foreach my $node (@{$layer->{'nodes'}})
  2         5  
427             {
428 4 50       7 if ($activations)
429             {
430 4         32 print OFILE $node->{'nodeid'}." activation = ".$node->{'activation'}."\n";
431             }
432 4         5 my $connectedNodeCounter=0;
433 4         4 foreach my $connectedNode (@{$node->{'connectedNodesEast'}->{'nodes'}})
  4         8  
434             {
435 9         9 my $weight = ${$node->{'connectedNodesEast'}->{'weights'}}[$connectedNodeCounter];
  9         13  
436 9         79 print OFILE $node->{'nodeid'}." <- ".$connectedNode->{'nodeid'}." = ".$weight."\n";
437 9         15 $connectedNodeCounter++;
438             }
439              
440 4 50       11 if ($node->{'connectedNodesWest'})
441             {
442 4         3 my $connectedNodeCounter=0;
443 4         5 foreach my $connectedNode (@{$node->{'connectedNodesWest'}->{'nodes'}})
  4         8  
444             {
445             #FIXME - a more easily read format would be connectedNode first in the file
446 13         9 my $weight = ${$node->{'connectedNodesWest'}->{'weights'}}[$connectedNodeCounter];
  13         21  
447 13         47 print OFILE $node->{'nodeid'}." -> ".$connectedNode->{'nodeid'}." = ".$weight."\n";
448             }
449             }
450             }
451             }
452              
453              
454              
455              
456 1         58 close OFILE;
457             }
458              
459             ###############################################################################
460             # sub load_state
461             ###############################################################################
462             sub load_state
463             {
464 1     1 0 55 my $network = shift;
465              
466 1         3 my %config = @_;
467              
468 1         2 my $filename = $config{'filename'};
469              
470 1 50       26 open (IFILE,$filename) or return "Error: unable to open $filename because $!";
471              
472             # we have to build a map of nodeids to objects
473 1         2 my %nodeMap;
474 1         1 foreach my $layer (@{$network->{'layers'}})
  1         3  
475             {
476 2         2 foreach my $node (@{$layer->{'nodes'}})
  2         4  
477             {
478 4         10 $nodeMap{$node->{'nodeid'}} = $node;
479             }
480             }
481              
482             # Add the bias node into the map
483 1 50       3 if ($network->{'bias'})
484             {
485 1         2 $nodeMap{'bias'} = $network->{'biasnode'};
486             }
487              
488              
489 1         1 my %stateFromFile;
490              
491 1         22 while ()
492             {
493 26         27 chomp $_;
494 26         21 my ($activation,$nodeid,$destNode,$weight);
495              
496 26 100       128 if ($_ =~ /(.*) activation = (.*)/)
    100          
    50          
497             {
498 4         7 $nodeid = $1;
499 4         5 $activation = $2;
500 4         9 $stateFromFile{$nodeid}->{'activation'} = $activation;
501 4         11 $network->dbug("Loading $nodeid = $activation",2);
502             }
503             elsif ($_ =~ /(.*) -> (.*) = (.*)/)
504             {
505 13         16 $nodeid = $1;
506 13         14 $destNode = $2;
507 13         17 $weight = $3;
508 13         34 $network->dbug("Loading $nodeid -> $destNode = $weight",2);
509 13         16 push @{$stateFromFile{$nodeid}->{'connectedNodesWest'}->{'weights'}},$weight;
  13         32  
510 13         13 push @{$stateFromFile{$nodeid}->{'connectedNodesWest'}->{'nodes'}},$nodeMap{$destNode};
  13         61  
511             }
512             elsif ($_ =~ /(.*) <- (.*) = (.*)/)
513             {
514 9         13 $nodeid = $1;
515 9         9 $destNode = $2;
516 9         15 $weight = $3;
517 9         5 push @{$stateFromFile{$nodeid}->{'connectedNodesEast'}->{'weights'}},$weight;
  9         23  
518 9         8 push @{$stateFromFile{$nodeid}->{'connectedNodesEast'}->{'nodes'}},$nodeMap{$destNode};
  9         23  
519 9         28 $network->dbug("Loading $nodeid <- $destNode = $weight",2);
520             }
521             }
522              
523 1         9 close IFILE;
524              
525              
526              
527              
528 1         1 my $nodeCounter=1;
529              
530 1         2 foreach my $layer (@{$network->{'layers'}})
  1         2  
531             {
532 2         2 foreach my $node (@{$layer->{'nodes'}})
  2         3  
533             {
534 4         8 $node->{'activation'} = $stateFromFile{$nodeCounter}->{'activation'};
535 4         5 $node->{'connectedNodesEast'} = $stateFromFile{$nodeCounter}->{'connectedNodesEast'};
536 4         9 $node->{'connectedNodesWest'} = $stateFromFile{$nodeCounter}->{'connectedNodesWest'};
537 4         11 $nodeCounter++;
538             }
539             }
540 1         6 return 1;
541             }
542              
543             ##############################################################################
544             # sub lesion
545             ##############################################################################
546             sub lesion
547             {
548            
549 0     0 1 0 my $network = shift;
550              
551 0         0 my %params = @_;
552 0         0 my $return;
553 0         0 $network->dbug("Entered AI::NNFlex::lesion with %params",2);
554              
555 0         0 my $nodeLesion = $params{'nodes'};
556 0         0 my $connectionLesion = $params{'connections'};
557              
558             # go through the layers & node inactivating random nodes according
559             # to probability
560            
561 0         0 foreach my $layer (@{$network->{'layers'}})
  0         0  
562             {
563 0         0 $return = $layer->lesion(%params);
564             }
565              
566 0         0 return $return;
567              
568             }
569              
570             ########################################################################
571             # AI::NNFlex::connect
572             ########################################################################
573             #
574             # Joins layers or nodes together.
575             #
576             # takes fromlayer=>INDEX, tolayer=>INDEX or
577             # fromnode=>[LAYER,NODE],tonode=>[LAYER,NODE]
578             #
579             # returns success or failure
580             #
581             #
582             #########################################################################
583             sub connect
584             {
585 4     4 1 748 my $network = shift;
586 4         14 my %params = @_;
587 4         6 my $result = 0;
588              
589 4 100       18 if ($params{'fromnode'})
    50          
590             {
591 2         18 $result = $network->connectnodes(%params);
592             }
593             elsif ($params{'fromlayer'})
594             {
595 2         22 $result = $network->connectlayers(%params);
596             }
597 4         13 return $result;
598              
599             }
600              
601             ########################################################################
602             # AI::NNFlex::connectlayers
603             ########################################################################
604             sub connectlayers
605             {
606 2     2 0 4 my $network=shift;
607 2         6 my %params = @_;
608              
609 2         5 my $fromlayerindex = $params{'fromlayer'};
610 2         3 my $tolayerindex = $params{'tolayer'};
611              
612 2         4 foreach my $node (@{$network->{'layers'}->[$tolayerindex]->{'nodes'}})
  2         7  
613             {
614 4         3 foreach my $connectedNode ( @{$network->{'layers'}->[$fromlayerindex]->{'nodes'}})
  4         11  
615             {
616 8         16 my $weight1 = $network->calcweight;
617 8         15 my $weight2 = $network->calcweight;
618 8         10 push @{$node->{'connectedNodesWest'}->{'nodes'}},$connectedNode;
  8         17  
619 8         9 push @{$connectedNode->{'connectedNodesEast'}->{'nodes'}},$node;
  8         16  
620 8         11 push @{$node->{'connectedNodesWest'}->{'weights'}},$weight1;
  8         15  
621 8         8 push @{$connectedNode->{'connectedNodesEast'}->{'weights'}},$weight2;
  8         25  
622             }
623             }
624              
625 2         6 return 1;
626             }
627              
628             ##############################################################
629             # sub AI::NNFlex::connectnodes
630             ##############################################################
631             sub connectnodes
632             {
633 2     2 0 4 my $network = shift;
634 2         5 my %params = @_;
635              
636 2         8 $params{'tonode'} =~ s/\'//g;
637 2         6 $params{'fromnode'} =~ s/\'//g;
638 2         11 my @tonodeindex = split /,/,$params{'tonode'};
639 2         8 my @fromnodeindex = split /,/,$params{'fromnode'};
640              
641             #make the connections
642 2         8 my $node = $network->{'layers'}->[$tonodeindex[0]]->{'nodes'}->[$tonodeindex[1]];
643 2         19 my $connectedNode = $network->{'layers'}->[$fromnodeindex[0]]->{'nodes'}->[$fromnodeindex[1]];
644              
645 2         6 my $weight1 = $network->calcweight;
646 2         7 my $weight2 = $network->calcweight;
647              
648 2         32 push @{$node->{'connectedNodesWest'}->{'nodes'}},$connectedNode;
  2         9  
649 2         3 push @{$connectedNode->{'connectedNodesEast'}->{'nodes'}},$node;
  2         5  
650 2         4 push @{$node->{'connectedNodesWest'}->{'weights'}},$weight1;
  2         5  
651 2         3 push @{$connectedNode->{'connectedNodesEast'}->{'weights'}},$weight2;
  2         5  
652              
653              
654 2         7 return 1;
655             }
656              
657              
658              
659             ##############################################################
660             # AI::NNFlex::calcweight
661             ##############################################################
662             #
663             # calculate an initial weight appropriate for the network
664             # settings.
665             # takes no parameters, returns weight
666             ##############################################################
667             sub calcweight
668             {
669 64     64 0 83 my $network= shift;
670 64         68 my $weight;
671 64 50       171 if ($network->{'fixedweights'})
    100          
672             {
673 0         0 $weight = $network->{'fixedweights'};
674             }
675             elsif ($network->{'randomweights'})
676             {
677 48         307 $weight = (rand(2*$network->{'randomweights'}))-$network->{'randomweights'};
678             }
679             else
680             {
681 16         83 $weight = (rand(2))-1;
682             }
683            
684              
685 64         124 return $weight;
686             }
687              
688              
689              
690              
691              
692             ###############################################################################
693             ###############################################################################
694             # Package AI::NNFlex::layer
695             ###############################################################################
696             ###############################################################################
697             package AI::NNFlex::layer;
698              
699              
700             ###############################################################################
701             # AI::NNFlex::layer::new
702             ###############################################################################
703             sub new
704             {
705 8     8   14 my $class = shift;
706 8         15 my $params = shift;
707 8         19 my $layer ={};
708              
709 8         20 foreach (keys %{$params})
  8         37  
710             {
711 44         121 $$layer{$_} = $$params{$_}
712             }
713 8         30 bless $layer,$class;
714            
715 8         109 my $numNodes = $$params{'nodes'};
716            
717 8         13 my @nodes;
718              
719 8         47 for (1..$numNodes)
720             {
721 16         69 push @nodes, AI::NNFlex::node->new($params);
722             }
723              
724 8         39 $$layer{'nodes'} = \@nodes;
725              
726 8         38 AI::NNFlex::dbug($params,"Created layer $layer",2);
727 8         23 return $layer;
728             }
729              
730             ###############################################################################
731             # AI::NNFlex::layer::layer_output
732             ##############################################################################
733             sub layer_output
734             {
735 35     35   47 my $layer = shift;
736 35         38 my $params = shift;
737              
738              
739 35         32 my @outputs;
740 35         36 foreach my $node (@{$$layer{'nodes'}})
  35         71  
741             {
742 70         155 push @outputs,$$node{'activation'};
743             }
744              
745 35         85 return \@outputs;
746             }
747              
748              
749              
750             ##############################################################################
751             # sub lesion
752             ##############################################################################
753             sub lesion
754             {
755            
756 0     0   0 my $layer = shift;
757              
758 0         0 my %params = @_;
759 0         0 my $return;
760              
761              
762 0         0 my $nodeLesion = $params{'nodes'};
763 0         0 my $connectionLesion = $params{'connections'};
764              
765             # go through the layers & node inactivating random nodes according
766             # to probability
767            
768 0         0 foreach my $node (@{$layer->{'nodes'}})
  0         0  
769             {
770 0         0 $return = $node->lesion(%params);
771             }
772              
773 0         0 return $return;
774              
775             }
776              
777              
778              
779             ###############################################################################
780             ###############################################################################
781             # package AI::NNFlex::node
782             ###############################################################################
783             ###############################################################################
784             package AI::NNFlex::node;
785              
786              
787             ###############################################################################
788             # AI::NNFlex::node::new
789             ###############################################################################
790             sub new
791             {
792 20     20   32 my $class = shift;
793 20         27 my $params = shift;
794 20         32 my $node = {};
795              
796 20         30 foreach (keys %{$params})
  20         59  
797             {
798 92         208 $$node{$_} = $$params{$_}
799             }
800              
801 20 50       78 if ($$params{'randomactivation'})
802             {
803 0         0 $$node{'activation'} =
804             rand($$params{'random'});
805 0         0 AI::NNFlex::dbug($params,"Randomly activated at ".$$node{'activation'},2);
806             }
807             else
808             {
809 20         59 $$node{'activation'} = 0;
810             }
811 20         61 $$node{'active'} = 1;
812            
813 20         50 $$node{'error'} = 0;
814            
815 20         57 bless $node,$class;
816 20         131 AI::NNFlex::dbug($params,"Created node $node",2);
817 20         63 return $node;
818             }
819              
820             ##############################################################################
821             # sub lesion
822             ##############################################################################
823             sub lesion
824             {
825              
826 0     0     my $node = shift;
827              
828 0           my %params = @_;
829              
830              
831 0           my $nodeLesion = $params{'nodes'};
832 0           my $connectionLesion = $params{'connections'};
833              
834             # go through the layers & node inactivating random nodes according
835             # to probability
836            
837 0 0         if ($nodeLesion)
838             {
839 0           my $probability = rand(1);
840 0 0         if ($probability < $nodeLesion)
841             {
842 0           $node->{'active'} = 0;
843             }
844             }
845              
846 0 0         if ($connectionLesion)
847             {
848             # init works from west to east, so we should here too
849 0           my $nodeCounter=0;
850 0           foreach my $connectedNode (@{$node->{'connectedNodesEast'}->{'nodes'}})
  0            
851             {
852 0           my $probability = rand(1);
853 0 0         if ($probability < $connectionLesion)
854             {
855 0           my $reverseNodeCounter=0; # maybe should have done this differntly in init, but 2 late now!
856 0           ${$node->{'connectedNodesEast'}->{'nodes'}}[$nodeCounter] = undef;
  0            
857 0           foreach my $reverseConnection (@{$connectedNode->{'connectedNodesWest'}->{'nodes'}})
  0            
858             {
859 0 0         if ($reverseConnection == $node)
860             {
861 0           ${$connectedNode->{'connectedNodesEast'}->{'nodes'}}[$reverseNodeCounter] = undef;
  0            
862             }
863 0           $reverseNodeCounter++;
864             }
865              
866             }
867            
868 0           $nodeCounter++;
869             }
870              
871              
872             }
873            
874              
875 0           return 1;
876             }
877              
878             1;
879              
880             =pod
881              
882             =head1 NAME
883              
884             AI::NNFlex - A base class for implementing neural networks
885              
886             =head1 SYNOPSIS
887              
888             use AI::NNFlex;
889              
890             my $network = AI::NNFlex->new(config parameter=>value);
891              
892             $network->add_layer( nodes=>x,
893             activationfunction=>'function');
894              
895             $network->init();
896              
897             $network->lesion( nodes=>PROBABILITY,
898             connections=>PROBABILITY);
899              
900             $network->dump_state (filename=>'badgers.wts');
901              
902             $network->load_state (filename=>'badgers.wts');
903              
904             my $outputsRef = $network->output(layer=>2,round=>1);
905              
906              
907             =head1 DESCRIPTION
908              
909             AI::NNFlex is a base class for constructing your own neural network modules. To implement a neural network, start with the documentation for AI::NNFlex::Backprop, included in this distribution
910              
911             =head1 CONSTRUCTOR
912              
913             =head2 AI::NNFlex->new ( parameter => value );
914            
915              
916             randomweights=>MAXIMUM VALUE FOR INITIAL WEIGHT
917              
918             fixedweights=>WEIGHT TO USE FOR ALL CONNECTIONS
919              
920             debug=>[LIST OF CODES FOR MODULES TO DEBUG]
921              
922             round=>0 or 1, a true value sets the network to round output values to nearest of 1, -1 or 0
923              
924              
925             The constructor implements a fairly generalised network object with a number of parameters.
926              
927              
928             The following parameters are optional:
929             randomweights
930             fixedweights
931             debug
932             round
933              
934              
935             (Note, if randomweights is not specified the network will default to a random value from 0 to 1.
936              
937             =head1 METHODS
938              
939             This is a short list of the main methods implemented in AI::NNFlex.
940              
941             =head2 AI::NNFlex
942              
943             =head3 add_layer
944              
945             Syntax:
946              
947             $network->add_layer( nodes=>NUMBER OF NODES IN LAYER,
948             persistentactivation=>RETAIN ACTIVATION BETWEEN PASSES,
949             decay=>RATE OF ACTIVATION DECAY PER PASS,
950             randomactivation=>MAXIMUM STARTING ACTIVATION,
951             threshold=>NYI,
952             activationfunction=>"ACTIVATION FUNCTION",
953             randomweights=>MAX VALUE OF STARTING WEIGHTS);
954              
955             Add layer adds whatever parameters you specify as attributes of the layer, so if you want to implement additional parameters simply use them in your calling code.
956              
957             Add layer returns success or failure, and if successful adds a layer object to the $network->{'layers'} array. This layer object contains an attribute $layer->{'nodes'}, which is an array of nodes in the layer.
958              
959             =head3 init
960              
961             Syntax:
962              
963             $network->init();
964              
965             Initialises connections between nodes, sets initial weights. The base AI::NNFlex init method implementes connections backwards and forwards from each node in each layer to each node in the preceeding and following layers.
966              
967             init adds the following attributes to each node:
968              
969             =over
970              
971             =item *
972             {'connectedNodesWest'}->{'nodes'} - an array of node objects connected to this node on the west/left
973              
974             =item *
975             {'connectedNodesWest'}->{'weights'} - an array of scalar numeric weights for the connections to these nodes
976              
977              
978             =item *
979             {'connectedNodesEast'}->{'nodes'} - an array of node objects connected to this node on the east/right
980              
981             =item *
982             {'connectedNodesEast'}->{'weights'} - an array of scalar numeric weights for the connections to these nodes
983              
984             =back
985              
986             The connections to easterly nodes are not used in feedforward networks.
987             Init also implements the Bias node if specified in the network config.
988              
989             =head3 connect
990              
991             Syntax:
992             $network->connect(fromlayer=>1,tolayer=>0);
993             $network->connect(fromnode=>'1,1',tonode=>'0,0');
994              
995             Connect allows you to manually create connections between layers or nodes, including recurrent connections back to the same layer/node. Node indices must be LAYER,NODE, numbered from 0.
996              
997             Weight assignments for the connection are calculated based on the network wide weight policy (see INIT).
998              
999             =head3 lesion
1000              
1001             $network->lesion (nodes=>PROBABILITY,connections=>PROBABILITY)
1002              
1003             Damages the network.
1004              
1005             B
1006              
1007             A value between 0 and 1, denoting the probability of a given node or connection being damaged.
1008              
1009             Note: this method may be called on a per network, per node or per layer basis using the appropriate object.
1010              
1011             =head1 EXAMPLES
1012              
1013             See the code in ./examples. For any given version of NNFlex, xor.pl will contain the latest functionality.
1014              
1015              
1016             =head1 PREREQs
1017              
1018             None. NNFlex should run OK on any version of Perl 5 >.
1019              
1020              
1021             =head1 ACKNOWLEDGEMENTS
1022              
1023             Phil Brierley, for his excellent free java code, that solved my backprop problem
1024              
1025             Dr Martin Le Voi, for help with concepts of NN in the early stages
1026              
1027             Dr David Plaut, for help with the project that this code was originally intended for.
1028              
1029             Graciliano M.Passos for suggestions & improved code (see SEE ALSO).
1030              
1031             Dr Scott Fahlman, whose very readable paper 'An empirical study of learning speed in backpropagation networks' (1988) has driven many of the improvements made so far.
1032              
1033             =head1 SEE ALSO
1034              
1035             AI::NNFlex::Backprop
1036             AI::NNFlex::Feedforward
1037             AI::NNFlex::Mathlib
1038             AI::NNFlex::Dataset
1039              
1040             AI::NNEasy - Developed by Graciliano M.Passos
1041             (Shares some common code with NNFlex)
1042            
1043              
1044             =head1 TODO
1045              
1046             Lots of things:
1047              
1048             clean up the perldocs some more
1049             write gamma modules
1050             write BPTT modules
1051             write a perceptron learning module
1052             speed it up
1053             write a tk gui
1054              
1055             =head1 CHANGES
1056              
1057             v0.11 introduces the lesion method, png support in the draw module and datasets.
1058              
1059             v0.12 fixes a bug in reinforce.pm & adds a reflector in feedforward->run to make $network->run($dataset) work.
1060              
1061             v0.13 introduces the momentum learning algorithm and fixes a bug that allowed training to proceed even if the node activation function module can't be loaded
1062              
1063             v0.14 fixes momentum and backprop so they are no longer nailed to tanh hidden units only.
1064              
1065             v0.15 fixes a bug in feedforward, and reduces the debug overhead
1066              
1067             v0.16 changes some underlying addressing of weights, to simplify and speed
1068              
1069             v0.17 is a bugfix release, plus some cleaning of UI
1070              
1071             v0.20 changes AI::NNFlex to be a base class, and ships three different network types (i.e. training algorithms). Backprop & momentum are both networks of the feedforward class, and inherit their 'run' method from feedforward.pm. 0.20 also fixes a whole raft of bugs and 'not nices'.
1072              
1073             v0.21 cleans up the perldocs more, and makes nnflex more distinctly a base module. There are quite a number of changes in Backprop in the v0.21 distribution.
1074              
1075             v0.22 introduces the ::connect method, to allow creation of recurrent connections, and manual control over connections between nodes/layers.
1076              
1077             v0.23 includes a Hopfield module in the distribution.
1078              
1079             v0.24 fixes a bug in the bias weight calculations
1080              
1081             =head1 COPYRIGHT
1082              
1083             Copyright (c) 2004-2005 Charles Colbourn. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
1084              
1085             =head1 CONTACT
1086              
1087             charlesc@nnflex.g0n.net
1088              
1089             =cut