StockBrain
Lightweight Java-Kotlin based library for neural networks
This a set of components developed for genetic algorithms coupled with a deep neural network. As a reference for declaration style tensorflow-keras was used
val input = InputLayer(3)
val d0 = Dense(4, Activations.ReLu, name = "d0") { input }
val d1 = Dense(4, Activations.ReLu, name = "d1") { d0 }
val d2 = Dense(4, Activations.ReLu, name = "d2") { d0 }
val concat = Concat { listOf(d1, d2) }
val builder = ModelBuilder(input, concat)
builder.build(debug = false)
Model summary of above:
Total layers: 5
InputLayer_0 : LayerShape(width=3, height=1) : children: 1
d0 : LayerShape(width=4, height=1) : children: 2
d1 : LayerShape(width=4, height=1) : children: 1
d2 : LayerShape(width=4, height=1) : children: 1
Concat_4 : LayerShape(width=8, height=1) : children: 0
Inputs and output
Models support multi-input and multi-output
val inputLayer1 = InputLayer(3, 2, name = "input1")
val inputLayer2 = InputLayer(6, 1, name = "input2")
val d0_t = Dense(4, activation = Activations.ReLu, useBias = false) { inputLayer1 }
val conv_d = ConvDelta { d0_t }
val d1 = Direct(activation = Activations.ReLu) { inputLayer2 }
val outputLayer = Concat { listOf(conv_d, d1) }
val model = ModelBuilder(mapOf("1" to inputLayer1, "2" to inputLayer2), outputLayer).build()
val array1 = floatArrayOf(0.4f, 0f, 1f, 4f, 0f, 0f).reshapeToMatrix(3, 2)
val array2 = floatArrayOf(1.4f, 2f, 0.5f, 0f, 1f, -2f).reshapeToMatrix(6, 1)
val outputMatrix = model.getOutput(mapOf("1" to array1, "2" to array2))
outputMatrix.printRed()
// [2.577, 0, 1.322, 0, 0.97, 1.3, 0.154, 0, 0.975, 1.171]
Or singular input:
val inputLayer = InputLayer(3, 2)
val d0 = Dense(4) { inputLayer }
val convDelta = ConvDelta { d0 }
val model = ModelBuilder(inputLayer, convDelta).build()
val array1 = floatArrayOf(0.4f, 0f, 1f, 4f, 0f, 0f).reshapeToMatrix(3, 2)
val outputMatrix = model.getOutput(array1)
outputMatrix.printRed()
// [-0.134, -2.884, 2.682, 1.099]
Saving and loading the model
Model is serialized into a json
val input = InputLayer(3)
val d0 = Dense(4, activation = Activations.ReLu, name = "d0", useBias = false) { input }
val d1 = Direct(activation = Activations.ReLu, name = "d2") { input }
val model = ModelBuilder(input, Concat(name = "output") { listOf(d0, d1) }).build()
val sm = ModelWriter.serialize(model)
val json = ModelWriter.toJson(sm)
Which would produce a structure as below
{
"inputs": {
"Default": "Input_0"
},
"outputs": {
"Default": "output"
},
"layers": [
{
"name": "Input_0",
"nameType": "Input",
"width": 3,
"height": 1
},
{
"name": "d0",
"nameType": "Dense",
"width": 4,
"height": 1,
"activation": "activation.relufunction",
"weights": [
{
"name": "weight",
"value": "P02cTL6i7UC/KnmGvoK8yL1z5MA/TLA8vy+uLL8q1NQ+imqIvyfIfL7qnMi/PFxc"
}
],
"parents": [
"Input_0"
],
"builderData": {
"useBias": false
}
},
{
"name": "d2",
"nameType": "Direct",
"width": 3,
"height": 1,
"activation": "activation.relufunction",
"weights": [
{
"name": "weight",
"value": "Px6BYD9gGzS/PNSs"
},
{
"name": "bias",
"value": "AAAAAAAAAAAAAAAA"
}
],
"parents": [
"Input_0"
],
"builderData": {
"useBias": true
}
},
{
"name": "output",
"nameType": "Concat",
"width": 7,
"height": 1,
"parents": [
"d0",
"d2"
]
}
]
}
Creating a model from a json should be done like this:
val model = ModelReader.modelInstance(json)
Model summary for above:
Total layers: 4 : inputs: [Default], outputs: [Default]
Input_0 : LayerShape(width=3, height=1) : children: 2
d0 : LayerShape(width=4, height=1) : children: 1
d2 : LayerShape(width=3, height=1) : children: 1
output : LayerShape(width=7, height=1) : children: 0
GA
Instance building
val settings = GASettings(
topParentCount = 5,
totalPopulationCount = 10,
scoreBoardOrder = GAScoreBoardOrder.Descending,
initialMutationPolicy = AdditiveMutationPolicy(1.0),
mutationPolicy = CyclicMutationPolicy(0.3),
)
val ga = GA(settings, model, earlyStopCallback = { i, ga ->
val top = ga.scoreBoard.getTop()?.score ?: return@GA false
if (top == 0.0) {
printRed("Stop on gen $i with $top")
return@GA true
}
return@GA false
})
topParentCount – limitation to scoreboard, top scores will be used for crossovers and mutation
totalPopulationCount – how many of new challengers will be produced
scoreBoardOrder – possible values: GAScoreBoardOrder.Descending and GAScoreBoardOrder.Ascending
GAScoreBoardOrder.Ascending – try to achieve the highest score
GAScoreBoardOrder.Descending – try to achieve the lowest score
ga.runFor(generations =100000, silent = true) { context ->
val outputArray = inputData.map { input -> context.model.getOutput(input) }
return@runFor getScoreCustomFunction(outputArray)
}
ga.scoreBoard.printScoreBoard()
val top = ga.scoreBoard.getTop() ?: throw IllegalStateException()
top.genes.applyToModel(model)
val outputMatrix = model.getOutput(inputMatrix)