The easiest way to start with Machine Learning in Java!
Explore our Website»
This Framework can be used to implement a Machine Learning Algorithm.
It uses Neural Networks which are beeing trained by a Genetic Algorithm
The motivation for this Framework was that many universities and schools teach as a first language Java to their students. Sadly for Java there aren't many Machine Learning Libraries and Frameworks out there, and if there are you most likely don't want to spend months trying to learn how to use it just to try out some stuff.
Hence, Easy ML for Java:
It's easy to use and designed to be played around with. You can choose the settings of the training process and see how this affects the outcome.
There is no wrong way to use Easy ML for Java!
To create your own AI you first need to have at least a basic understanding of the below listet classes. To implement an AI for your own personal problem you will have to follow these steps:
- Design the structure of the NeuralNet using the NeuralNetBuilder
- implement a NeuralNetFitnessFunction
- Choose and configure the values of Selector, Recombiner and Mutator
- Configure the parameters of the Genetic Algorithm
- combine everything in the GeneticAlgorithm.Builder class
Finding the correct configurations is a very challenging task and you should always use Loggers to help you find the correct ones!
An abstract example could look like this:
Selector<NeuralNetIndividual> SELECTOR = new EliteSelector<>( 0.1 );
Recombiner<NeuralNetIndividual> RECOMBINER = new NNUniformCrossoverRecombiner( 2 );
Mutator<NeuralNetIndividual> MUTATOR = new NNRandomMutator( 0.9, 0.4, new Randomizer( -0.01, 0.01 ), 0.01 );
int GENS = 50;
NeuralNetFitnessFunction FITNESS_FUNCTION = (nn) -> //calculate Fitness;
NeuralNetSupplier neuralNetSupplier = ( ) -> new NeuralNet.Builder( NEURALNET_INPUT_SIZE, NEURALNET_OUTPUT_SIZE )
.addLayer( HIDDEN_LAYER_SIZE )
.withActivationFunction( x -> x )
.build( );
NeuralNetPopulationSupplier supplier = new NeuralNetPopulationSupplier( neuralNetSupplier, FITNESS_FUNCTION, POP_SIZE );
GeneticAlgorithm<NeuralNetIndividual> geneticAlgorithm =
new GeneticAlgorithm.Builder<>( supplier, GENS, SELECTOR )
.withRecombiner( RECOMBINER )
.withMutator( MUTATOR )
.build( );
geneticAlgorithm.solve();
This example will show how to use the Framework for a number prediction based on the simple mathematic equation: f(x) = 2x
EXAMPLE
In this example the framework is used to predict if someone has diabetes or not based on given trainingsdata.
DIABETES EXAMPLE
You can find a full tutorial for this example on our Website
This one includes a basic implementation of the game "Snake" and will show the use of a more complex scenario of the Framework.
SNAKE EXAMPLE
This is the "brain" of the Aritfical Intelligence. For further information about the way this Neural Network works read the Wikipedia-Article about it.
To create a NeuralNet you need to use Builder of this class.
It is necessary to at least provide the input and the output size of this Network. Its recommended though to also provide at least 1 hidden layer for more complex problems.
A Example for a Neural Network with the input size 100 output size 2 and a hidden layer with 50 nodes could look like this:
NeuralNet neuralNet = new NeuralNet.Builder( 100, 12 )
.addLayer( 50 )
.build()
with the method
withActivationFunction(...)
you can provide your own Activation Function. The default one is
The most simple one would be:
withActicationFunction(x -> x)
A Indivdual displays one Element in the Population of the Genetic Algorithm. The implementation details may differ from problem to problem and have to be therefor implemented by the user. To support the programmer with this task the Framework provides a basic structure to implement a Individual.
This is a simplified Version of the Individual interface
public interface Individual<T extends Individual<T>>{
void calcFitness();
double getFitness();
T copy();
In your implementation of Invdividual T should always be the implementing Class itself
e.g:
public class IndividualImplementation extends Individual<IndividualImplementation>{...}
-
void calcFitness()
This method will be used to calculate a Fitness value. Basic Rule here is: The higher the Fitness value the better the Individual! -
double getFitness()
Should return the fitness value calculated in the calcFitness Method. Due to Performance issues the calculation of the Fitness Value should always be outsourced to thecalcFitness()
method. -
T copy()
The copy method should be self explanatory: provides a copy of the Individual without sharing any rerences of to the Individual itself
The Population Supplier is used to supply the Genetic Algorithm with a Population of Individuals. A example of a PopulationSupplier using Java 8 Lambda expression could looks like this:
() -> new Population(//List of Individuals);
The Framework already provides a Implementation of a PopulationSupplier to read a Population from a File:
PopulationByFileSupplier
The task of the Selector is to throw out the worst Individuals of the population based on their fitness values. There are many ways to achieve such a selection using differnt mathematical approaches.
This Framework provides 3 of them:
It is also possible to provide your own implementation though. To do this you will have to Implement the Selector-Interface. Before starting to write your own Selector you may want to look into the implementation of the already given Selectors first.
A possible EliteSelector could look like this:
new EliteSelector<>( 0.1 );
The Recombiner may be used to fill up the Popluation again after the selection process.
Again there are many different approaches.
This framework provides 2 of them:
- FillUpRecombiner (just fills up the Population with copies of the remaining Individuals)
- NNUniformCrossoverRecombiner (Wikipedia)
It's obviously also possible to provide your own Recombiner by Implementing the Recombiner-Interface
A possible NNUniformCrossOverRecombiner could look like:
new NNUniformCrossoverRecombiner( 2 );
After the Selection and Recombination process its possible to provide a third processing step called Mutator. Some Individuals may randomly get changed to increase the probability of a positive mutation.
This framework provides one of them:
As always it's possible to provide your own Mutator by implementing the Mutator-Interface.
A possible NNRandomMutator could look like:
new NNRandomMutator( 0.2, 0.4, new Randomizer( -0.01, 0.01 ), 0.01 );
This Interface is just used to log the huge amount of metadata that is beeing generated in the evolution process. There are 3 Implementations of this Interface already given:
- ConsoleLogger (Prints Metadata in console)
- IntervalConsoleLogger (Prints Metadata in a interval in the console)
- GraphPlotLogger (Creates a .xls file with a chart of the Fitness Values)
takes as arguements in the Constructor
- Plotting Interval as int - the interval in which the file gets created not in which the data is beeing logged
- Filename as String - Name of the file without file-ending
- Chart Title as String (Optional) - Title of the resulting chart, default: "Plot for Population size: {size}"
- Line Generators as LineGenerator[] - The parser for metadata into plottable double values
LineGenerator is a abstract class that is used to parse a Population into a single double value so its plottable. You may implement your own LineGenerators but the Framework provides 4 of them:
- AvgFitnessLine - parses the Population to its average Fitness value
- MaxFitnessLine - parses the Population to its maximum Fitness value
- MinFitnessLine - parses the Population to its minimum Fitness value
- NQuantilFitnessLine - parses the Population into its Fitness Value of its n-quantil
A possible GraphPlotLogger could look like this:
int plottingInterval = 100;
double quantilOf20Percent = 0.2
double quantilOf80Percent = 0.8
new GraphPlotLogger(plottingInterval, "plot",
new AvgFitnessLine(),
new MaxFitnessLine(),
new WorstFitnessLine(),
new NQuantilFitnessLine(quantilOf20Percent),
new NQuantilFitnessLine(quantilOf80Pecent))
a resulting graph may look like this:
To create your own Genetic Algorithm you will need to build one using the GeneticAlgorithm.Builder Class.This class can be given the many optional parameters, the most essential ones will be listed below:
- PopulationSupplier - provides the first Population
- Generations - Amount of generations that should be calculated
- Selector - Is used for the Selection process
- Recombiner - Is used for the recombination process (Optional)
- Mutator - Is used for the mutation process (Optional)
A abstract Example for a GeneticAlgorithm could look like this:
new GeneticAlgorithm.Builder<>( POPULATION_SUPPLIER, GENS_AMOUNT, SELECTOR )
.withRecombiner( RECOMBINER )
.withMutator( MUTATOR )
.build( );
With the .withMutliThreaded(//amount of Threads);
method it's also possible to processes the evolution in a parallel matter.
Combination of Genetic Algorithm and Neural Network
This Interface provides one Method
double calculateFitness( NeuralNet neuralNet );
which is used to calculate the Fitness Values of a NeuralNetIndividual.
See Individual.calcFitness()
This is the combination between a NeuralNet and a Individual. The constructor of this class therefor takes a NeuralNet and a NeuralNetFitnessFunction to create a NeuralNetIndividual.
This class provides a Population of NeuralNetIndviduals, using
- a Supplier of a NeuralNet
- a NeuralNetFitnessFunction
- the size of the population as int
e.g:
NeuralNetPopulationSupplier supplier = new NeuralNetPopulationSupplier( () -> new NeuralNet.Builder..., (nn) -> /*calculate Fitness*/, POP_SIZE );
© Tom Lamprecht, David Kupper - FHWS Fakultät Informatik