AI-ANN

 view release on metacpan or  search on metacpan

lib/AI/ANN.pm  view on Meta::CPAN

#!/usr/bin/perl
package AI::ANN;
BEGIN {
  $AI::ANN::VERSION = '0.008';
}
use strict;
use warnings;

# ABSTRACT: an artificial neural network simulator

use Moose;

use AI::ANN::Neuron;
use Storable qw(dclone);


has 'input_count' => (is => 'ro', isa => 'Int', required => 1);
has 'outputneurons' => (is => 'ro', isa => 'ArrayRef[Int]', required => 1);
has 'network' => (is => 'ro', isa => 'ArrayRef[HashRef]', required => 1);
# network is an arrayref of hashrefs. Each hashref is:
# object => AI::ANN::Neuron
# and has several other elements
has 'inputs' => (is => 'ro', isa => 'ArrayRef[Int]');
has 'rawpotentials' => (is => 'ro', isa => 'ArrayRef[Int]');
has 'minvalue' => (is => 'rw', isa => 'Int', default => 0);
has 'maxvalue' => (is => 'rw', isa => 'Int', default => 1);
has 'afunc' => (is => 'rw', isa => 'CodeRef', default => sub {sub {shift}});
has 'dafunc' => (is => 'rw', isa => 'CodeRef', default => sub {sub {1}});
has 'backprop_eta' => (is => 'rw', isa => 'Num', default => 0.1);

around BUILDARGS => sub {
	my $orig = shift;
	my $class = shift;
	my %data;
	if ( @_ == 1 && ref $_[0] eq 'HASH' ) {
		%data = %{$_[0]};
	} else {
		%data = @_;
	}
	if (exists $data{'inputs'} && not exists $data{'input_count'}) {
		$data{'input_count'} = $data{'inputs'};
		delete $data{'inputs'}; # inputs is used later for the actual 
								# values of the inputs.
	} 
	my $neuronlist = $data{'data'};
	$data{'outputneurons'} = [];
	$data{'network'} = [];
	for (my $i = 0; $i <= $#{$neuronlist} ; $i++) {
		push @{$data{'outputneurons'}}, $i
			if $neuronlist->[$i]->{'iamanoutput'};
		my @pass = (
				$i, 
				$neuronlist->[$i]->{'inputs'}, 
				$neuronlist->[$i]->{'neurons'} );
		push @pass, $neuronlist->[$i]->{'eta_inputs'}, 
			$neuronlist->[$i]->{'eta_neurons'}
			if defined $neuronlist->[$i]->{'eta_neurons'};
		$data{'network'}->[$i]->{'object'} = 
			new AI::ANN::Neuron( @pass );
	}
	delete $data{'data'};
	return $class->$orig(%data);
};


sub execute {
	my $self = shift;
	my $inputs = $self->{'inputs'} = shift;
	# Don't bother dereferencing $inputs only to rereference a lot
	my $net = $self->{'network'}; # For less typing
	my $lastneuron = $#{$net};
	my @neurons = ();
	foreach my $i (0..$lastneuron) {
		$neurons[$i] = 0;
	}
	foreach my $i (0..$lastneuron) {
		delete $net->[$i]->{'done'};
		delete $net->[$i]->{'state'};
	}
	my $progress = 0;
	do {
		$progress = 0;
		foreach my $i (0..$lastneuron) {
			if ($net->[$i]->{'done'}) {next}
			if ($net->[$i]->{'object'}->ready($inputs, \@neurons)) {
				my $potential = $net->[$i]->{'object'}->execute($inputs, \@neurons);
                $self->{'rawpotentials'}->[$i] = $potential;
				$potential = $self->{'maxvalue'} if $potential > $self->{'maxvalue'};
				$potential = $self->{'minvalue'} if $potential < $self->{'minvalue'};
				$potential = &{$self->{'afunc'}}($potential);
				$neurons[$i] = $net->[$i]->{'state'} = $potential;
				$net->[$i]->{'done'} = 1;
				$progress++;
			}
		}
	} while ($progress); # If the network is feed-forward, we are now finished.
	
	my @notdone = grep {not (defined $net->[$_]->{'done'} &&
							 $net->[$_]->{'done'} == 1)} 0..$lastneuron;
	my @neuronstemp = ();
	if ($#notdone > 0) { #This is the part where we deal with loops and bad things
		my $maxerror = 0;
		my $loopcounter = 1;
		while (1) {
			foreach my $i (@notdone) { # Only bother iterating over the
									   # ones we couldn't solve exactly
				# We don't care if it's ready now, we're just going to interate
				# until it stabilizes.
				if (not defined $neurons[$i] && $i <= $lastneuron) {
					# Fixes warnings about uninitialized values, but we make 
					# sure $i is valid first.
					$neurons[$i] = 0;
				}
				my $potential = $net->[$i]->{'object'}->execute($inputs, \@neurons);
                $self->{'rawpotentials'}->[$i] = $potential;
				$potential = &{$self->{'afunc'}}($potential);
				$potential = $self->{'maxvalue'} if $potential > $self->{'maxvalue'};
				$potential = $self->{'minvalue'} if $potential < $self->{'minvalue'};
				$neuronstemp[$i] = $net->[$i]->{'state'} = $potential;
				# We want to know the absolute change
				if (abs($neurons[$i]-$neuronstemp[$i])>$maxerror) {
					$maxerror = abs($neurons[$i]-$neuronstemp[$i]);
				}
			}
			foreach my $i (0..$lastneuron) { 
				# Update $neurons, since that is what gets passed to execute
				$neurons[$i] = $neuronstemp[$i];
			}
			if (($maxerror < 0.0001 && $loopcounter >= 5) || $loopcounter > 250) {last}
			$loopcounter++;
			$maxerror=0;
		}
	}

	# Ok, hopefully all the neurons have happy values by now.
	# Get the output values for neurons corresponding to outputneurons
	my @output = map {$neurons[$_]} @{$self->{'outputneurons'}};
	return \@output;
}


sub get_state {
	my $self = shift;
	my $net = $self->{'network'}; # For less typing
	my @neurons = map {$net->[$_]->{'state'}} 0..$#{$self->{'network'}};
	my @output = map {$net->[$_]->{'state'}} @{$self->{'outputneurons'}};

	return $self->{'inputs'}, \@neurons, \@output;
}


sub get_internals {
	my $self = shift;
	my $net = $self->{'network'}; # For less typing
	my $retval = [];
	for (my $i = 0; $i <= $#{$self->{'network'}}; $i++) {
		$retval->[$i] = { iamanoutput => 0,
						  inputs => $net->[$i]->{'object'}->inputs(),
						  neurons => $net->[$i]->{'object'}->neurons(),
						  eta_inputs => $net->[$i]->{'object'}->eta_inputs(),
						  eta_neurons => $net->[$i]->{'object'}->eta_neurons()
						  };
	}
	foreach my $i (@{$self->{'outputneurons'}}) {
		$retval->[$i]->{'iamanoutput'} = 1;
	}
	return dclone($retval); # Dclone for safety.
}


sub readable {
	my $self = shift;
	my $retval = "This network has ". $self->{'inputcount'} ." inputs and ".
					scalar(@{$self->{'network'}}) ." neurons.\n";
	for (my $i = 0; $i <= $#{$self->{'network'}}; $i++) {
		$retval .= "Neuron $i\n";
		while (my ($k, $v) = each %{$self->{'network'}->[$i]->{'object'}->inputs()}) {
			$retval .= "\tInput from input $k, weight is $v\n";
		}
		while (my ($k, $v) = each %{$self->{'network'}->[$i]->{'object'}->neurons()}) {
			$retval .= "\tInput from neuron $k, weight is $v\n";
		}
		if (map {$_ == $i} $self->{'outputneurons'}) {
			$retval .= "\tThis neuron is a network output\n";
		}
	}
	return $retval;
}


sub backprop {
    my $self = shift;
    my $inputs = shift;
    my $desired = shift;
    my $actual = $self->execute($inputs);
    my $net = $self->{'network'};
    my $lastneuron = $#{$net};
    my $deltas = [];
    my $i = 0;
    foreach my $neuron (@{$self->outputneurons()}) {
        $deltas->[$neuron] = $desired->[$i] - $actual->[$i];
        $i++;
    }
    my $progress = 0;
    foreach my $neuron (reverse 0..$lastneuron) {
        foreach my $i (reverse $neuron..$lastneuron) {
            my $weight = $net->[$i]->{'object'}->neurons()->[$neuron];
            if (defined $weight && $weight != 0 && $deltas->[$i]) {
                $deltas->[$neuron] += $weight * $deltas->[$i];
            }
        }
    } # Finished generating deltas
    foreach my $neuron (0..$lastneuron) {
        my $inputinputs = $net->[$neuron]->{'object'}->inputs();
        my $neuroninputs = $net->[$neuron]->{'object'}->neurons();
        my $dafunc = &{$self->{'dafunc'}}($self->{'rawpotentials'}->[$neuron]);
        my $delta = $deltas->[$neuron] || 0;
        foreach my $i (0..$#{$inputinputs}) {
            $inputinputs->[$i] += $inputs->[$i]*$self->{'backprop_eta'}*$delta*$dafunc;
        }
        foreach my $i (0..$#{$neuroninputs}) {
            $neuroninputs->[$i] += $net->[$i]->{'state'}*$self->{'backprop_eta'}*$delta*$dafunc;
        }
        $net->[$neuron]->{'object'}->inputs($inputinputs);
        $net->[$neuron]->{'object'}->neurons($neuroninputs);
    } # Finished changing weights.
}

__PACKAGE__->meta->make_immutable;

1;

__END__
=pod

=head1 NAME

AI::ANN - an artificial neural network simulator

=head1 VERSION

version 0.008

=head1 SYNOPSIS

AI::ANN is an artificial neural network simulator. It differs from existing 
solutions in that it fully exposes the internal variables and allows - and 
forces - the user to fully customize the topology and specifics of the 
produced neural network. If you want a simple solution, you do not want this 
module. This module was specifically written to be used for a simulation of 
evolution in neural networks, not training. The traditional 'backprop' and 



( run in 0.778 second using v1.01-cache-2.11-cpan-39bf76dae61 )