At some point you will want a diagram of your FANN neural network.

Example Diagram

Programmatically generated diagram of XOR ANN
Programmatically generated diagram of XOR ANN
Programmatically generated XOR ANN Stats
Programmatically generated XOR ANN Stats

Reasons May Include:

  • You need artwork for your fridge or cubical and Van Gogh’s Starry Night was mysteriously unavailable!
  • You want an illustration to help potential investors understand some of the technical aspects of how your AI startup works.
  • You’re trying to convince the good people who enjoy your work to throw gobs of cash at your Patreon. 😛

But.. Your exact reasons may very! 😉

None the less, read on because I’m giving you 100% free & fully functional code and explaining how it works.

I’m not even asking for your email address!

 

Visualizing Your FANN Neural Network

Every ANN (Artificial Neural Network) you create will have an input layer, an output layer and at least one hidden layer though you can decide to include more as needed.

Every layer is comprised of “neurons” which are where computation occurs when a signal propagates through the network.

There are also special “bias” neuron included on every layer (except for the output layer) that help your bot learn to give accurate responses.

A connection represents a path or association between two neurons and a connection has a value called a “weight”.

The weight value helps control how much of an input signal passes on (propagates forward) to the connected neuron.

In “fully connected” networks every neuron is “connected” (signal propagates/feeds) to every neuron in the layer after it.

In “sparsely” connected networks each neuron is connected to a configurable percentage of the other neurons.

It is also possible to create a “shortcut” network where connections skip layers entirely, making it possible to do direct connections from the input layer to the output layer skipping all hidden layers.

Personally, I’ve yet to mess with that beautiful voodoo but maybe I’ll conjure up a reason for us to build a shortcut network in the future!

Anyway, you could always diagram one by hand with a little effort like I did with DUI Bot.

DUI Artificial Neural Network Diagram
DUI Artificial Neural Network Diagram

Beautiful right? But lets be honest…

"Ain't nobody got time for that!"

So, I wrote some code you can use to make diagramming your neural network simple!

How It Works

Using the code I provide you can load your own ANN or one of the example neural networks I provide like Pathfinder:

Programmatically generated diagram of Pathfinder ANN
Programmatically generated diagram of Pathfinder ANN

My code then uses built in FANN library functions to learn about the network:

fann_get_num_input($ann

fann_get_num_layers($ann)

fann_get_num_output($ann)

fann_get_total_neurons($ann)

fann_get_total_connections($ann)

fann_get_layer_array($ann)

fann_get_bias_array($ann)

fann_get_connection_array($ann)

Once I have this information my code takes the connections array which is an array of FANN Connection Objects and converts it into an array of keyed neurons.

Basically this consists of looping through the connections array and creating a neuron for every “from” connection and storing every “to” connection that neuron is connected to in a keyed array on the neuron called ‘connections’.

The value assigned to the “to” connection is the weight of the connection.

With that done it becomes possible to determine which layer the neuron is on and what kind/type the neuron is (‘input’, ‘output’, ‘hidden’, ‘bias’).

Once we have assigned every neuron a type and know what layer it’s on we then determine an  “X & Y” position for the neuron relative to all other neurons.

After that we compute the “complete_layer” sizes by summing the number of all neurons on each layer (including bias neurons) and find the largest layer, which is used to determine the image size in pixels necessary to fit the neural network given the  $neuron_size specified in the beginning of the script and the default is set to 50 x 50 px.

The image size calculation is as follows:

$image_width = ($neuron_size * ($largest_layer + ($tiles_between_neurons / 2))) * $tiles_between_neurons – ($neuron_size) + 1;

$image_height = ($neuron_size * $num_layers) * $tiles_between_layers – ($neuron_size * ($tiles_between_layers – 1)) + 1;

After that, I paint the background and overlay a grid or tile lines if the $draw_grid variable in the beginning of the script is set to true.

Next I loop through the list of neurons and draw the connection using the neuron X & Y positions.

Connection color is determined by the weight polarity (positive green, negative red) if  $random_connection_color variable in the beginning of the script is set to false otherwise a random color is used which can sometimes make looking at “dense” networks a little easier.

Connection line thickness is determined by this calculation: $thickness = 1 + (abs($weight) * 2);

If the $thickness value is larger than 32 that value is used instead as a maximum to prevent extremely large weights from blocking our view of the other connections.

After that we draw the neurons over top the connections and then rotate the image if the $rotate variable in the beginning of the script is set to true.

Your diagram is then saved using the name of the neural network as the image name.

Also, if the $output_stats_image variable in the beginning of the script is set to true then a “stats” image is also created and saved as well.

Here are the Stats for the Pathfinder ANN shown above:

Programmatically generated Pathfinder ANN Stats
Programmatically generated Pathfinder ANN Stats

Code

You can download the entire project with example neural networks for free on GitHub.

GitHub: https://github.com/geekgirljoy/FANN-Neural-Network-Visualizer

<?php

$neuron_size = 50; // height & width in px
$tiles_between_neurons = 2;

$draw_connection_weights = true;

// In densely connected networks it can be difficult 
// to see all the connections so randomizing the 
// connection color can improve the visability some,
// See the Pathfinder examples.
// true = random color
// false = colors: red negitive, green positive
$random_connection_color = true;

$tiles_between_layers = 3;
$draw_grid = false;
$output_stats_image = true; // Output a second image with the ANN stats?

// Rotate view?
// default is "waterfall" inputs on top outputs on bottom
// rotate set to true is inputs on the left and outputs on the right
$rotate = false;

// Path to ANN's - change to your bot
$ann_name = "xor_float.net";
//$ann_name = "pathfinder_float.net";
//$ann_name = "dui.net";
//$ann_name = "ocr_float.net";



////////////////////////////////////////////////////////////////////////
// Don't Change Below this line
////////////////////////////////////////////////////////////////////////
$ann = fann_create_from_file(dirname(__FILE__) . DIRECTORY_SEPARATOR . $ann_name); //Load ANN

$num_inputs = fann_get_num_input($ann); // int
$num_layers = fann_get_num_layers($ann); // int
$num_outputs = fann_get_num_output($ann); // int
$total_neurons = fann_get_total_neurons($ann); // int
$total_connections = fann_get_total_connections($ann); // int
$layers_array = fann_get_layer_array($ann); // array
$bias_array = fann_get_bias_array($ann); // array 
$connections_array = fann_get_connection_array($ann); // array of FANN connection objects

fann_destroy($ann);


// Array of FANN CONN OBJ's to Array of Keyed Neurons
// Use the connections to model the neurons.
// The $my_neurons array holds the neurons in a list with the key
// being the from_neuron. Additionally, this
// also creates a "connections" array on the neuron
// with a list of neurons that this neuron is connected 
// to with the value being the connection weight.
$my_neurons = array_fill(0, $total_neurons, array());
foreach($connections_array as $connection){
  $my_neurons[$connection->from_neuron]['connections'][$connection->to_neuron] = $connection->weight;
}


// Figure out which neuron belongs on which layer 
// Assign it a type: ['input', 'output', 'hidden', 'bias']
$current_neuron = 0;
foreach($layers_array as $layer=>$layer_neuron_count){  

  // Detect Input, Output & Hidden Neurons 
  for($i = $current_neuron; $i < ($current_neuron + $layer_neuron_count); $i++){

    $my_neurons[$i]['layer'] = $layer;
    if($layer == 0){
      $my_neurons[$i]['type'] = 'input';  
    }
    elseif($layer == count($layers_array )-1){
      $my_neurons[$i]['type'] = 'output';  
    }
    else{
      $my_neurons[$i]['type'] = 'hidden';
    }
    
  }
  $current_neuron = $i;
  // Detect Bias Neurons 
  for($i = $current_neuron; $i < ($current_neuron + $bias_array[$layer]); $i++){
    $my_neurons[$i]['layer'] = $layer;
    $my_neurons[$i]['type'] = 'bias';  
  }
  $current_neuron = $i;
}
$current_neuron = NULL;
unset($current_neuron);



// Determine Neuron X,Y Position
$row = 1; // y
$col = 1; // x
foreach($my_neurons as $index=>&$neuron){
  
  if($neuron['layer'] > @$my_neurons[$index-1]['layer']){
    $row += $tiles_between_layers;
    $col = 1;
  }else{
    $col += $tiles_between_neurons;
  }
  
  $neuron['x'] = ($neuron_size * $col) - ($neuron_size / 2);
  $neuron['y'] = ($neuron_size * $row) - ($neuron_size / 2);
}



// Get complete_layer count = neurons + bias neurons
foreach($layers_array as $layer=>$layer_neuron_count){
  $complete_layers[$layer] = $layer_neuron_count + $bias_array[$layer];
}
$largest_layer = max($complete_layers); // Find the largest layer width


// Create a Blank Image
$image_width = ($neuron_size * ($largest_layer + ($tiles_between_neurons / 2))) * $tiles_between_neurons - ($neuron_size) + 1;
$image_height = ($neuron_size * $num_layers) * $tiles_between_layers - ($neuron_size * ($tiles_between_layers - 1)) + 1;
$neural_network_image = imagecreatetruecolor($image_width, $image_height);

// Create Colors Array
$colors = array(
  'background'=>imagecolorallocate($neural_network_image, 153, 153, 153),
  'grid'=>imagecolorallocate($neural_network_image, 128, 128, 128),
  'neuron_stroke_color'=>imagecolorallocate($neural_network_image, 92, 92, 92),
  'input_neuron_color'=>imagecolorallocate($neural_network_image, 170, 255, 170),
  'hidden_neuron_color'=>imagecolorallocate($neural_network_image, 233, 175, 174),
  'output_neuron_color'=>imagecolorallocate($neural_network_image, 171, 204, 255),
  'bias_neuron_color'=>imagecolorallocate($neural_network_image, 255, 255, 0),
  'positive_connection_weight_color'=>imagecolorallocate($neural_network_image, 0, 255, 0),
  'negitive_connection_weight_color'=>imagecolorallocate($neural_network_image, 255, 0, 0),
  'dead_connection_weight_color'=>imagecolorallocate($neural_network_image, 0, 0, 0)
);


// Paint Background
imagefill($neural_network_image, 0, 0, $colors['background']);


// Draw Grid if $draw_grid is set to true 
if($draw_grid == true){
  $row = 0;
  $col = 0;
  foreach(range(0, $image_height - 1, 1) as $y){
    foreach(range(0, $image_width  - 1, 1) as $x){
      
      // paint grid
      if($row == $neuron_size || (($y % $neuron_size) == 0)){
        imagesetpixel($neural_network_image, $x, $y, $colors['grid']);
        $row = 0;
      }
      if($col == $neuron_size || (($x % $neuron_size) == 0)){
        imagesetpixel($neural_network_image, $x, $y, $colors['grid']);
        $col = 0; 
      }
      $col++;
    }
    $row++;
  }
}


// Draw Connections
foreach($my_neurons as $key=>&$neuron){
  
  if(array_key_exists('connections', $neuron)){
    foreach($neuron['connections'] as $connection=>$weight){
      
      // What color is the connection
      if($random_connection_color == false){
        if($weight > 0.0){
          $color = $colors['positive_connection_weight_color'];
        }
        elseif($weight < 0.0){
          $color = $colors['negitive_connection_weight_color'];
        }
        else{
          $color = $colors['dead_connection_weight_color'];
        }
      }
      else{
        $color = imagecolorallocate($neural_network_image, mt_rand(0,255), mt_rand(0,255), mt_rand(0,255));
      }
      
      
      // Set connection thickness
      if($draw_connection_weights == true){
        $thickness = 1 + (abs($weight) * 2);
        
        if($thickness > 32){$thickness = 32;}
        imagesetthickness ($neural_network_image , $thickness);

      }
      else{
        imagesetthickness ($neural_network_image , 1);
      }
      
      // Draw connection
      imageline ($neural_network_image , $neuron['x'], $neuron['y'], $my_neurons[$connection]['x'], $my_neurons[$connection]['y'], $color);
    }  
  }

}


imagesetthickness ($neural_network_image , 2); // Reset line brush thickness

// Draw Neurons
foreach($my_neurons as $key=>&$neuron){
  
  if($neuron['type'] == 'input'){
    $color = $colors['input_neuron_color'];
  }
  elseif($neuron['type'] == 'hidden'){
    $color = $colors['hidden_neuron_color'];
  }
  elseif($neuron['type'] == 'output'){
    $color = $colors['output_neuron_color'];
  }
  elseif($neuron['type'] == 'bias'){
    $color = $colors['bias_neuron_color'];
  }
  
  imagefilledellipse($neural_network_image, $neuron['x'], $neuron['y'], $neuron_size, $neuron_size, $color);
  imagearc ($neural_network_image, $neuron['x'], $neuron['y'], $neuron_size+1, $neuron_size+1, 0, 360, $colors['neuron_stroke_color']);
}

// Rotate if you insist on looking at the network wrong! 😛
if($rotate == true){
  $neural_network_image = imagerotate($neural_network_image, 90, 0);
}


// Output the image.
imagepng($neural_network_image, "$ann_name.png");
imagedestroy($neural_network_image);




if($output_stats_image == true){
  // Create the image
  $neural_network_stats_image = imagecreatetruecolor(250, 400);
  
  // Create Colors Array
  $colors = array(
    'background'=>imagecolorallocate($neural_network_stats_image, 153, 153, 153),
    'inputs_text_color'=>imagecolorallocate($neural_network_stats_image, 170, 255, 170),
    'hidden_text_color'=>imagecolorallocate($neural_network_stats_image, 233, 175, 174),
    'outputs_text_color'=>imagecolorallocate($neural_network_stats_image, 171, 204, 255),
    'bias_text_color'=>imagecolorallocate($neural_network_stats_image, 255, 255, 0),
    'layers_text_color'=>imagecolorallocate($neural_network_stats_image, 0, 128, 128),
    'connections_text_color'=>imagecolorallocate($neural_network_stats_image, 128, 64, 0),
  );

  // Paint Background
  imagefill($neural_network_stats_image, 0, 0, $colors['background']);




  $font = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Pacifico'. DIRECTORY_SEPARATOR . 'Pacifico-Regular.ttf';

  $size = 25;
  $angle = 0.00;
  $x = 25;
  $y = 50;
  $increment = $y + 10;

  imagettftext($neural_network_stats_image, $size, $angle, $x, $y, $colors['inputs_text_color'], $font, $num_inputs . ' Inputs');
  $y += $increment;
  imagettftext($neural_network_stats_image, $size, $angle, $x, $y, $colors['hidden_text_color'], $font, $total_neurons - ($num_inputs + $num_outputs + array_sum($bias_array)) . ' Hidden');
  $y += $increment;
  imagettftext($neural_network_stats_image, $size, $angle, $x, $y, $colors['outputs_text_color'], $font, $num_outputs . ' Outputs');
  $y += $increment;
  imagettftext($neural_network_stats_image, $size, $angle, $x, $y, $colors['bias_text_color'], $font, array_sum($bias_array) . ' Bias');
  $y += $increment;
  imagettftext($neural_network_stats_image, $size, $angle, $x, $y, $colors['layers_text_color'], $font, count($layers_array) . ' Layers');
  $y += $increment;
  imagettftext($neural_network_stats_image, $size, $angle, $x, $y, $colors['connections_text_color'], $font, count($connections_array) . ' Connections');
  $y += $increment;
  
  imagepng($neural_network_stats_image, "$ann_name.stats.png");
  imagedestroy($neural_network_stats_image);
}

 

Like all my prototypes this code is ready for you to build something amazing with it and make the world a better place!

It’s covered by the MIT license so feel free to make oodles of cash off your commercial projects that use my code!

Just remember to cite me as the original source… and it sure would be nice if you said thanks for all the free and open code, by supporting me over on Patreon for as little as $1 a month.

But, if all you can do is like, share, comment and subscribe… well, that’s cool too! 😉

Just know…

😛

Much Love,

~Joy