File Coverage

blib/lib/AI/NeuralNet/SOM/Hexa.pm
Criterion Covered Total %
statement 74 78 94.8
branch 17 20 85.0
condition 8 10 80.0
subroutine 15 17 88.2
pod 7 8 87.5
total 121 133 90.9


line stmt bran cond sub pod time code
1             package AI::NeuralNet::SOM::Hexa;
2              
3 1     1   23981 use strict;
  1         3  
  1         43  
4 1     1   6 use warnings;
  1         2  
  1         33  
5              
6 1     1   617 use AI::NeuralNet::SOM;
  1         3  
  1         84  
7 1     1   6 use Data::Dumper;
  1         4  
  1         57  
8 1     1   6 use base qw(AI::NeuralNet::SOM);
  1         3  
  1         117  
9              
10 1     1   1476 use AI::NeuralNet::SOM::Utils;
  1         3  
  1         900  
11              
12             =pod
13              
14             =head1 NAME
15              
16             AI::NeuralNet::SOM::Hexa - Perl extension for Kohonen Maps (hexagonal topology)
17              
18             =head1 SYNOPSIS
19              
20             use AI::NeuralNet::SOM::Hexa;
21             my $nn = new AI::NeuralNet::SOM::Hexa (output_dim => 6,
22             input_dim => 3);
23             # ... see also base class AI::NeuralNet::SOM
24              
25             =head1 INTERFACE
26              
27             =head2 Constructor
28              
29             The constructor takes the following arguments (additionally to those in the base class):
30              
31             =over
32              
33             =item C : (mandatory, no default)
34              
35             A positive, non-zero number specifying the diameter of the hexagonal. C<1> creates one with a single
36             hexagon, C<2> one with 4, C<3> one with 9. The number plays the role of a diameter.
37              
38             =back
39              
40             Example:
41              
42             my $nn = new AI::NeuralNet::SOM::Hexa (output_dim => 6,
43             input_dim => 3);
44              
45             =cut
46              
47             sub new {
48 7     7 0 3851 my $class = shift;
49 7         27 my %options = @_;
50 7         37 my $self = bless { %options }, $class;
51              
52 7 50       30 if ($self->{output_dim} > 0) {
53 7         18 $self->{_D} = $self->{output_dim};
54             } else {
55 0         0 die "output dimension must be positive integer";
56             }
57 7 50       18 if ($self->{input_dim} > 0) {
58 7         13 $self->{_Z} = $self->{input_dim};
59             } else {
60 0         0 die "input dimension must be positive integer";
61             }
62              
63 7         21 $self->{_R} = $self->{_D} / 2;
64 7   66     38 $self->{_Sigma0} = $options{sigma0} || $self->{_R}; # impact distance, start value
65 7   50     32 $self->{_L0} = $options{learning_rate} || 0.1; # learning rate, start value
66              
67 7         24 return $self;
68             }
69              
70             =pod
71              
72             =head2 Methods
73              
74             =over
75              
76             =item I
77              
78             Returns the radius (half the diameter).
79              
80             =cut
81              
82             sub radius {
83 1     1 1 830 my $self = shift;
84 1         5 return $self->{_R};
85             }
86              
87             =pod
88              
89             =item I
90              
91             Returns the diameter (= dimension) of the hexagon.
92              
93             =cut
94              
95             sub diameter {
96 8     8 1 846 my $self = shift;
97 8         27 return $self->{_D};
98             }
99              
100             =pod
101              
102             =cut
103              
104             sub initialize {
105 5     5 1 27 my $self = shift;
106 5         9 my @data = @_;
107              
108 5         8 our $i = 0;
109             my $get_from_stream = sub {
110 26 100   26   56 $i = 0 if $i > $#data;
111 26         26 return [ @{ $data[$i++] } ]; # cloning !
  26         131  
112 5 100       27 } if @data;
113             $get_from_stream ||= sub {
114 4     4   11 return [ map { rand( 1 ) - 0.5 } 1..$self->{_Z} ];
  12         85  
115 5   100     26 };
116              
117 5         13 for my $x (0 .. $self->{_D}-1) {
118 12         25 for my $y (0 .. $self->{_D}-1) {
119 30         45 $self->{map}->[$x]->[$y] = &$get_from_stream;
120             }
121             }
122             }
123              
124             sub bmu {
125 202     202 1 262 my $self = shift;
126 202         218 my $sample = shift;
127              
128 202         221 my $closest; # [x,y, distance] value and co-ords of closest match
129 202         400 for my $x (0 .. $self->{_D}-1) {
130 605         1160 for my $y (0 .. $self->{_D}-1){
131 1813         5354 my $distance = AI::NeuralNet::SOM::Utils::vector_distance ($self->{map}->[$x]->[$y], $sample); # || Vi - Sample ||
132             #warn "distance to $x, $y : $distance";
133 1813 100       4074 $closest = [0, 0, $distance] unless $closest;
134 1813 50       5612 $closest = [$x, $y, $distance] if $distance < $closest->[2];
135             }
136             }
137 202         917 return @$closest;
138             }
139              
140             sub neighbors { # http://www.ai-junkie.com/ann/som/som3.html
141 203     203 1 3950 my $self = shift;
142 203         207 my $sigma = shift;
143 203         202 my $X = shift;
144 203         205 my $Y = shift;
145              
146 203         196 my @neighbors;
147 203         411 for my $x (0 .. $self->{_D}-1) {
148 618         1178 for my $y (0 .. $self->{_D}-1){
149 1908         3126 my $distance = _hexa_distance ($X, $Y, $x, $y);
150             ##warn "$X, $Y, $x, $y: distance: $distance";
151 1908 100       5039 next if $distance > $sigma;
152 1062         3126 push @neighbors, [ $x, $y, $distance ]; # we keep the distances
153             }
154             }
155 203         655 return \@neighbors;
156             }
157              
158             sub _hexa_distance {
159 1908     1908   2340 my ($x1, $y1) = (shift, shift); # one point
160 1908         2250 my ($x2, $y2) = (shift, shift); # another
161              
162 1908 100       4168 ($x1, $y1, $x2, $y2) = ($x2, $y2, $x1, $y1) # swapping
163             if ( $x1+$y1 > $x2+$y2 );
164              
165 1908         2253 my $dx = $x2 - $x1;
166 1908         1898 my $dy = $y2 - $y1;
167              
168 1908 100 100     6935 if ($dx < 0 || $dy < 0) {
169 25         47 return abs ($dx) + abs ($dy);
170             } else {
171 1883 100       4393 return $dx < $dy ? $dy : $dx;
172             }
173             }
174              
175             =pod
176              
177             =item I
178              
179             I<$m> = I<$nn>->map
180              
181             This method returns the 2-dimensional array of vectors in the grid (as a reference to an array of
182             references to arrays of vectors).
183              
184             Example:
185              
186             my $m = $nn->map;
187             for my $x (0 .. $nn->diameter -1) {
188             for my $y (0 .. $nn->diameter -1){
189             warn "vector at $x, $y: ". Dumper $m->[$x]->[$y];
190             }
191             }
192              
193             This array represents a hexagon like this (ASCII drawing is so cool):
194              
195             <0,0>
196             <0,1> <1,0>
197             <0,2> <1,1> <2,0>
198             <0,3> <1,2> <2,1> <3,0>
199             ...............................
200              
201              
202             =item I
203              
204             Not implemented.
205              
206             =cut
207              
208             ## TODO: pretty printing of this as hexagon ?
209 0     0 1   sub as_string { die "not implemented"; }
210              
211             =pod
212              
213             =item I
214              
215             Not implemented.
216              
217             =cut
218              
219 0     0 1   sub as_data { die "not implemented"; }
220              
221             =pod
222              
223             =back
224              
225             =head1 AUTHOR
226              
227             Robert Barta, Erho@devc.atE
228              
229             =head1 COPYRIGHT AND LICENSE
230              
231             Copyright (C) 200[78] by Robert Barta
232              
233             This library is free software; you can redistribute it and/or modify
234             it under the same terms as Perl itself, either Perl version 5.8.8 or,
235             at your option, any later version of Perl 5 you may have available.
236              
237             =cut
238              
239             our $VERSION = '0.02';
240              
241             1;
242              
243             __END__