Artificial Neural Network through Memblocks: ANNtM
(say "Auntie Em", like from the Wizard of Oz
)
Wasn't quite sure where to post this, but the snippets forum felt most appropriate. It's a sort of Work In Progress, but it's not intended to be a full program, but rather something to use in a full program (have I defined a snippet yet?). Either way...
ANNtM is a Memblock based Artificial Neural Network. It is designed not to do anything so specific as Artificial Intelligence (on it's own), but is intended more as a tool for AI codes. Programming the base logic for AI is fairly straight forward. ANNtM handles the more complex part of the AI, mainly the learning.
ANNtM combines a genetic evolution code with a linked neural network, and uses Memblocks (about as efficiently as I can get them) to store all that data. The genetic evolution code means that it learns. The neural network means that multiple inputs can impact multiple outputs. For example:
An AI that gets hungry, then goes and eats, isn't very realistic. What if the AI is at work, sleeping, or being chased by another hungry AI? The AI should not stop what it's doing and eat, as these other factors need to be accounted for. A Neural Network handles that and weighs in all the factors before an output is generated.
The neurons are laid out in clusters of 32 neurons (0-31) and each cluster can be linked to another cluster. For those that are a bit familiar with ANNs, this means that creating input, processing, and output layers is relatively easy.
Attached is the code and a tutorial (in the Word document) and a small Excel file that shows the neuron layout. The demo is only really there to show how the neuron clusters affect each other and to test that all the functions are working properly. But it's not all that exciting.
The code in the code bracket below is the predecessor of ANNtM and gives a little more demonstration of how neurons work. This AI learns and uses only 2 neurons. This will give an idea of how poweful several clusters of 32 neurons can potentially be.
`ANN (Artificial Neural Network) Tutorial
`By RiiDii
`Dec, 2005
`Just to make things a little easier to read
#Constant LeftEye 1
#Constant RightEye 2
#Constant Monster 1
#Constant Egg 10
`Standard set up
Sync On: Sync Rate 60: Autocam Off
Null=Make Vector3(1)
Randomize Timer()
`The Curve type has been added as it is clearly something we will want to "evolve".
Type Neurons
Value as byte
Curve as byte
Weight as byte
Threshold as byte
Endtype
Type Genes
Curve as Integer
Weight as Integer
Threshold as Integer
Endtype
Dim Neuron(2) as Neurons
`Gene is the monster's "genetic code". Typically, the genetic code would record and consider the population.
`In this case though, we only have one monster and one player, and that type of evolution would take a really long time.
`So we are going to take the population of posibilities and use those instead. Since a neuron can hold a value of
`0 to 15, we will track how well each setting does and keep track over many Epochs.
`We are tracking 3 neurons ( Array Count(Neurons()) ), 2 nibbles (0-1), and 16 possible settings (0-15)
`So our Dim is:
Dim Gene(Array Count(Neuron()),1,15) as Genes
Dim BestGenes(Array Count(Neuron()),1) as Genes
`We are adding a bit of a game here.
`Instead of worrying about rules for the player, we are adding rules for the monster.
Global Monster_Score
`Added and average to help keep the number manageble.
`This will also now allow us to use the sigmoidal curve to curve-grade the scores.
Global Monster_Average
`We will also need a timer function to keep track of when to do the evolution Epochs.
Global Epoch_Timer
Global Epoch_Counter
`Make the monster with two eyes
Make Object Sphere Monster,.5,6,6
Make Mesh From Object Monster,1
Delete Object Monster
Make Object Cube Monster,2
Add Limb Monster,LeftEye,1
Offset Limb Monster,LeftEye,-1,1,1
Color Limb Monster,LeftEye,Rgb(255,0,0)
Add Limb Monster,RightEye,1
Offset Limb Monster,RightEye,1,1,1
Color Limb Monster,RightEye,Rgb(255,0,0)
Position Object Monster,0,0,15
Turn Object Right Monster,90
`Here is a golden egg. We will make the monster want to defend the golden egg.
`Our goal will be to trick the monster away from the egg so we can get it.
Make Object Sphere Egg,1
Position Object Egg,5,0,20
Color Object Egg,Rgb(255,255,0)
Set Object Emissive Egg,Rgb(100,100,0)
Initialize_AI()
`Main Loop
Do
Control Camera Using Arrowkeys 0,1.5,3
Set Cursor 0,0
Input_Data()
Process_Data()
Output()
Game()
Epoch()
Print "Monster Score: ";Monster_Score
Print "Monster Avg: ";Monster_Average
`Just to play, the player's score will be opposite of the monster's.
Print "Player Score: ";-Monster_Score
Print
Print "Epoch: ";Epoch_Counter
Print "FPS: ";Screen FPS()
Sync
Loop
`This function is added to set, or reset, our AI randomly.
Function Initialize_AI()
For i = 0 to Array Count(Neuron())
Neuron(i).Curve=Set_Left_Nibble(0,rnd(14)+1)
Neuron(i).Curve=Set_Right_Nibble(Neuron(i).Curve,rnd(14)+1)
Neuron(i).Weight=Set_Left_Nibble(0,rnd(14)+1)
Neuron(i).Weight=Set_Right_Nibble(Neuron(i).Weight,rnd(14)+1)
Neuron(i).Threshold=Set_Left_Nibble(0,rnd(15))
Neuron(i).Threshold=Set_Right_Nibble(Neuron(i).Threshold,rnd(15))
Next i
Endfunction
`We need a "game" that our monster can learn to play.
Function Game()
Set Vector3 1,(Camera Position X()-Object Position X(Egg)),0,(Camera Position Z()-Object Position Z(Egg))
pd#=Length Vector3(1):`Player Distance to the Egg
`Our monster gains point for keeping the player away from the egg, and loses points if the player gets too close
if pd#<25
Monster_Score=Monster_Score-(4-int(pd#/10)):`The closer the player gets, the more points lost.
Else
Monster_Score=Monster_Score+1
Endif
Set Vector3 1,(Camera Position X()-Object Position X(Monster)),0,(Camera Position Z()-Object Position Z(Monster))
pd#=Length Vector3(1):`Player Distance to the monster
`Our monster gets bonus points for "touching" the player.
If pd#<=2
Monster_Score=Monster_Score+50:` A significant reward for catching the player.
Endif
Endfunction
`Each Epoch (100 frames), the mosnter will update it's learning.
Function Epoch()
Inc Epoch_Timer
If Epoch_Timer<100 Then ExitFunction
`Every 100 frames, the monster will evaluate its performance and start over. This will eventually be the genetic evolution
`Here's where we score our neuron settings and store them for later:
For i = 0 to Array Count(Neuron())
For j = 0 to 1
n=Get_nibble(neuron(i).curve,j)
Gene(i,j,n).curve=Gene(i,j,n).curve+Int(Sigmoidal(Monster_Score-Monster_Average,500)*10)
n=Get_nibble(neuron(i).weight,j)
Gene(i,j,n).weight=Gene(i,j,n).weight+Int(Sigmoidal(Monster_Score-Monster_Average,500)*10)
n=Get_nibble(neuron(i).threshold,j)
Gene(i,j,n).threshold=Gene(i,j,n).threshold+Int(Sigmoidal(Monster_Score-Monster_Average,500)*10)
Next j
Next i
`Initialize the AI with some random values.
Initialize_AI()
`Now populate the AI with some best scores. The more Epochs that go by, the less mutation occurs.
`Note: this is sort of an inverse mutation function. The gene starts off by default as mutated.
` Then if the random selection comes up, the mutation is replaced by a successful gene.
` The more Epochs that go by, the more likely the mutations will be replaced, but never 100%.
Get_Best_Genes()
For i = 0 to Array Count(Neuron())
If Rnd(20+Epoch_Counter)<Epoch_Counter
Neuron(i).curve=Set_Left_Nibble(0,BestGenes(i,0).curve)
Neuron(i).curve=Set_Right_Nibble(Neuron(i).curve,BestGenes(i,0).curve)
Endif
If Rnd(20+Epoch_Counter)<Epoch_Counter
Neuron(i).weight=Set_Left_Nibble(0,BestGenes(i,0).weight)
Neuron(i).weight=Set_Right_Nibble(Neuron(i).weight,BestGenes(i,0).weight)
Endif
If Rnd(20+Epoch_Counter)<Epoch_Counter
Neuron(i).threshold=Set_Left_Nibble(0,BestGenes(i,0).threshold)
Neuron(i).threshold=Set_Right_Nibble(Neuron(i).threshold,BestGenes(i,0).threshold)
Endif
Next i
`Calculate the new average score
Inc Epoch_Counter
`Weighted average so the current performance is based on past performance.
Monster_Average=((Monster_Average * (Epoch_Counter-1))+Monster_Score)/Epoch_Counter
Monster_Score=0
Epoch_Timer=0
Initialize_AI()
Endfunction
`This function chooses the "best" genes, the genes with the highest scores.
`A feature has been added to typically select 1 of the best 3 (usually) to avoid getting stuck in a niche.
Function Get_Best_Genes()
For i = 1 to Array Count(Neuron())
For j=0 to 1
best_curve=-9999
best_weight=-9999
best_threshold=-9999
For k=0 to 15
If best_curve<Gene(i,j,k).curve and rnd(3)=1 :`Typically will choose one of the best 3, but not always.
best_curve=Gene(i,j,k).curve
BC=k
Endif
If best_weight<Gene(i,j,k).weight and rnd(3)=1
best_weight=Gene(i,j,k).weight
BW=k
Endif
If best_threshold<Gene(i,j,k).threshold and rnd(3)=1
best_threshold=Gene(i,j,k).threshold
BT=k
Endif
Next k
BestGenes(i,j).curve=BC
BestGenes(i,j).weight=BW
BestGenes(i,j).threshold=BT
Next j
Next i
Endfunction
Function Input_Data()
if Get_Left_Nibble(Neuron(2).value)-8>0 :
`If the neuron fires, then the monster chases the player.
Look_at_Player()
Else
`If the neuron doesn't fire, then the monster gaurds the egg.
Look_at_Egg()
Endif
Set Vector3 1,(Camera Position X()-Object Position X(Monster)),0,(Camera Position Z()-Object Position Z(Monster))
pd#=Length Vector3(1):`Player Distance
Set Vector3 1,(Object Position X(Egg)-Object Position X(Monster)),0,(Object Position Z(Egg)-Object Position Z(Monster))
ed#=Length Vector3(1):`Egg Distance
`Here we set the player vs. monster range Neuron
curve=Get_Left_Nibble(Neuron(1).Curve)
`Notice the curve variable has been added to our Sigmoidal function. This will be part of how our AI learns.
range#=Sigmoidal((ed#-pd#),Curve)*15.0
Neuron(1).value=Set_Left_Nibble(0,range#)
`Here we set the monster vs. egg range Neuron
curve=Get_Right_Nibble(Neuron(1).Curve)
range#=Sigmoidal((ed#-20.0),curve)*15.0
Neuron(1).value=Set_Right_Nibble(Neuron(1).value,range#)
Endfunction
Function Process_Data()
n1=Get_Left_Nibble(Neuron(1).value)
n2=Get_Right_Nibble(Neuron(1).value)
curve=Get_Left_Nibble(Neuron(2).Curve)
factor#=Sigmoidal((n1-n2),curve)*15.0
Neuron(2).value=Set_Left_Nibble(0,factor#)
Endfunction
Function Output()
turn#=((Get_Left_Nibble(Neuron(0).value)-8.0)/7.0)*4.0
Neuron(0).value=0
Turn Object Right 1,turn#
Move Object 1,.1
Endfunction
Function Look_at_Player()
Set Vector3 1,(Camera Position X()-Limb Position X(Monster,LeftEye)),0,(Camera Position Z()-Limb Position Z(Monster,LeftEye))
ld#=Length Vector3(1)
Set Vector3 1,(Camera Position X()-Limb Position X(Monster,RightEye)),0,(Camera Position Z()-Limb Position Z(Monster,RightEye))
rd#=Length Vector3(1)
curve#=Get_Left_Nibble(Neuron(0).Curve)
Turn#=Sigmoidal((ld#-rd#)*2.0,curve#/10.0)*15.0
Neuron(0).value=Set_Left_Nibble(Neuron(0).value,turn#)
EndFunction
Function Look_at_Egg()
Set Vector3 1,(Object Position X(Egg)-Limb Position X(Monster,LeftEye)),0,(Object Position Z(Egg)-Limb Position Z(Monster,LeftEye))
ld#=Length Vector3(1)
Set Vector3 1,(Object Position X(Egg)-Limb Position X(Monster,RightEye)),0,(Object Position Z(Egg)-Limb Position Z(Monster,RightEye))
rd#=Length Vector3(1)
curve#=Get_Left_Nibble(Neuron(0).Curve)
Turn#=Sigmoidal((ld#-rd#)*2.0,curve#/10.0)*15.0
Neuron(0).value=Set_Left_Nibble(Neuron(0).value,turn#)
Endfunction
Function Sigmoidal(value#,curve#)
`the sigmoidal formula. Graph this on a spreadsheet to see how it works.
result#=1.0/(1.0+2.7183^-(value#/curve#))
Endfunction result#
`Simple Nibble Functions.
Function Set_Nibble(bt as byte, value as byte, nibble as byte)
if nibble=0 then bt = Set_Right_Nibble(bt,value)
if nibble=1 then bt = Set_Left_Nibble(bt,value)
Endfunction bt
Function Set_Left_Nibble(bt as byte,value as byte)
value = value << 4
bt = bt || value
Endfunction bt
Function Set_Right_Nibble(bt as byte,value as byte)
value = value << 4
value = value >> 4
bt = bt || value
Endfunction bt
Function Get_Nibble(bt as byte, nibble as byte)
if nibble=0 then bt=Get_Right_Nibble(bt)
if nibble=1 then bt=Get_Left_Nibble(bt)
Endfunction bt
Function Get_Left_Nibble(bt as byte)
bt = bt >> 4
Endfunction bt
Function Get_Right_Nibble(bt as byte)
bt = bt << 4
bt = bt >> 4
Endfunction bt
Edit: All code is DBPro 5.8
Open MMORPG: It's your game!