Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add comments to clarify elements of the code #4

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 49 additions & 55 deletions luaneural.lua
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
-- Author: "Soulkiller" (http://www.forums.evilmana.com/psp-lua-codebase/lua-neural-networks/)
-- Gists of code: https://gist.github.com/cassiozen/de0dff87eb7ed599b5d0


ACTIVATION_RESPONSE = 1


NeuralNetwork = {
transfer = function( x) return 1 / (1 + math.exp(-x / ACTIVATION_RESPONSE)) end --This is the Transfer function (in this case a sigmoid)
--This is the Transfer function (sigmoid function to squish inputs)
transfer = function(x) return 1 / (1 + math.exp(-x))
end
}



function NeuralNetwork.create( _numInputs, _numOutputs, _numHiddenLayers, _neuronsPerLayer, _learningRate)
--instantiate the neural network with Lua metamethods - similar to x = self.x in Python
_numInputs = _numInputs or 1
_numOutputs = _numOutputs or 1
_numHiddenLayers = _numHiddenLayers or math.ceil(_numInputs/2)
_neuronsPerLayer = _neuronsPerLayer or math.ceil(_numInputs*.66666+_numOutputs)
_learningRate = _learningRate or .5
--order goes network[layer][neuron][wieght]
--order goes network[layer][neuron][weight] for later instantiation

local network = setmetatable({
learningRate = _learningRate
Expand All @@ -26,58 +23,56 @@ function NeuralNetwork.create( _numInputs, _numOutputs, _numHiddenLayers, _neuro
network[1] = {} --Input Layer

for i = 1,_numInputs do
network[1][i] = {}
network[1][i] = {} --we want the first input layer and iterate through the neurons
end

for i = 2,_numHiddenLayers+2 do --plus 2 represents the output layer (also need to skip input layer)
network[i] = {}
for i = 2,_numHiddenLayers+2 do -- +2 represents the output layer (also need to skip input layer)
network[i] = {} --iterate through the layers
local neuronsInLayer = _neuronsPerLayer

if i == _numHiddenLayers+2 then
neuronsInLayer = _numOutputs
end

for j = 1,neuronsInLayer do
network[i][j] = {bias = math.random()*2-1}
local numNeuronInputs = table.getn(network[i-1])
network[i][j] = {bias = math.random()*2-1} --return random number between -1 and 1
local numNeuronInputs = #network[i-1]

for k = 1,numNeuronInputs do
network[i][j][k] = math.random()*2-1 --return random number between -1 and 1
network[i][j][k] = math.random()*2-1
end
end
end

return network
end



function NeuralNetwork:forewardPropagate(a, b, c)
function NeuralNetwork:forwardPropagate(a, b, c)
local arg = {a, b, c}
if table.getn(arg) ~= table.getn(self[1]) and type(arg[1]) ~= "table" then
error("Neural Network received "..table.getn(arg).." input[s] (expected "..table.getn(self[1]).." input[s])",2)
elseif type(arg[1]) == "table" and table.getn(arg[1]) ~= table.getn(self[1]) then
error("Neural Network received "..table.getn(arg[1]).." input[s] (expected "..table.getn(self[1]).." input[s])",2)
if #(arg) ~= #self[1] and type(arg[1]) ~= "table" then
error("Neural Network received "..#(arg).." input[s] (expected "..#(self[1]).." input[s])",2)
elseif type(arg[1]) == "table" and #(arg[1]) ~= #(self[1]) then
error("Neural Network received "..#(arg[1]).." input[s] (expected "..#(self[1]).." input[s])",2)
end

local outputs = {}

for i = 1,table.getn(self) do
for j = 1,table.getn(self[i]) do
for i = 1,#(self) do
for j = 1,#(self[i]) do
if i == 1 then
if type(arg[1]) == "table" then
self[i][j].result = arg[1][j]
self[i][j].result = arg[1][j] --iterate through the jth component, the neurons per layer
else
self[i][j].result = arg[j]
self[i][j].result = arg[j] --iterate through the ith component, the layer
end
else
self[i][j].result = self[i][j].bias
for k = 1,table.getn(self[i][j]) do
for k = 1,#(self[i][j]) do
self[i][j].result = self[i][j].result + (self[i][j][k]*self[i-1][k].result)
end

self[i][j].result = NeuralNetwork.transfer(self[i][j].result)
if i == table.getn(self) then
self[i][j].result = NeuralNetwork.transfer(self[i][j].result) --transfer all the layers to the neural network
if i == #(self) then
table.insert(outputs,self[i][j].result)
end
end
Expand All @@ -87,36 +82,35 @@ function NeuralNetwork:forewardPropagate(a, b, c)
return outputs
end



function NeuralNetwork:backwardPropagate(inputs,desiredOutputs)
if table.getn(inputs) ~= table.getn(self[1]) then
error("Neural Network received "..table.getn(inputs).." input[s] (expected "..table.getn(self[1]).." input[s])",2)
elseif table.getn(desiredOutputs) ~= table.getn(self[table.getn(self)]) then
error("Neural Network received "..table.getn(desiredOutputs).." desired output[s] (expected "..table.getn(self[table.getn(self)]).." desired output[s])",2)
if #(inputs) ~= #(self[1]) then
error("Neural Network received "..#(inputs).." input[s] (expected "..#(self[1]).." input[s])",2)
elseif #(desiredOutputs) ~= #(self[#(self)]) then
error("Neural Network received "..#(desiredOutputs).." desired output[s] (expected "..#(self[#(self)]).." desired output[s])",2)
end

self:forewardPropagate(inputs) --update the internal inputs and outputs
self:forwardPropagate(inputs) --update the internal inputs and outputs

for i = table.getn(self),2,-1 do --iterate backwards (nothing to calculate for input layer)
for i = #(self),2,-1 do --iterate backwards (nothing to calculate for input layer)
local tempResults = {}
for j = 1,table.getn(self[i]) do
if i == table.getn(self) then --special calculations for output layer
for j = 1,#(self[i]) do
if i == #(self) then --special calculations for output layer
--the neural network calculates the error (i.e. desired output - network output) for back-propagation to work
self[i][j].delta = (desiredOutputs[j] - self[i][j].result) * self[i][j].result * (1 - self[i][j].result)
else
local weightDelta = 0
for k = 1,table.getn(self[i+1]) do
weightDelta = weightDelta + self[i+1][k][j]*self[i+1][k].delta
for k = 1,#(self[i+1]) do
weightDelta = weightDelta + self[i+1][k][j]*self[i+1][k].delta --index i+1 to skip the initial layer
end
self[i][j].delta = self[i][j].result * (1 - self[i][j].result) * weightDelta
end
end
end

for i = 2,table.getn(self) do
for j = 1,table.getn(self[i]) do
self[i][j].bias = self[i][j].delta * self.learningRate
for k = 1,table.getn(self[i][j]) do
for i = 2,#(self) do
for j = 1,#(self[i]) do
self[i][j].bias = self[i][j].delta * self.learningRate --this bias is another factor to consider with the weights
for k = 1,#(self[i][j]) do
self[i][j][k] = self[i][j][k] + self[i][j].delta * self.learningRate * self[i-1][k].result
end
end
Expand Down Expand Up @@ -144,13 +138,13 @@ function NeuralNetwork:save()

]]--

local data = "|INFO|FF BP NN|I|"..tostring(table.getn(self[1])).."|O|"..tostring(table.getn(self[table.getn(self)])).."|HL|"..tostring(table.getn(self)-2).."|NHL|"..tostring(table.getn(self[2])).."|LR|"..tostring(self.learningRate).."|BW|"
for i = 2,table.getn(self) do -- nothing to save for input layer
for j = 1,table.getn(self[i]) do
local data = "|INFO|FF BP NN|I|"..tostring(#(self[1])).."|O|"..tostring(#(self[#(self)])).."|HL|"..tostring(#(self)-2).."|NHL|"..tostring(table.getn(self[2])).."|LR|"..tostring(self.learningRate).."|BW|"
for i = 2,#(self) do -- nothing to save for input layer
for j = 1,#(self[i]) do
local neuronData = tostring(self[i][j].bias).."{"

for k = 1,table.getn(self[i][j]) do
neuronData = neuronData..tostring(self[i][j][k])
for k = 1,#(self[i][j]) do
neuronData = neuronData..tostring(self[i][j][k]) --convert the neuron data from num to string
neuronData = neuronData..","
end
data = data..neuronData.."}"
Expand All @@ -162,7 +156,7 @@ function NeuralNetwork:save()
return data
end

function NeuralNetwork.load( data)
function NeuralNetwork.load( data) --go through each of the local data points and set the in the neural network
local dataPos = string.find(data,"|")+1
local currentChunk = string.sub( data, dataPos, string.find(data,"|",dataPos)-1)
local dataPos = string.find(data,"|",dataPos)+1
Expand Down Expand Up @@ -205,10 +199,10 @@ function NeuralNetwork.load( data)
local subChunk

for i = 1,_hiddenLayers+1 do
biasWeights[i] = {}
biasWeights[i] = {} --initialize the weights for each neuron
local neuronsInLayer = _neuronsPerLayer
if i == _hiddenLayers+1 then
neuronsInLayer = _outputs
neuronsInLayer = _outputs --initialize variable with local _outputs
end

for j = 1,neuronsInLayer do
Expand Down Expand Up @@ -250,16 +244,16 @@ function NeuralNetwork.load( data)
network[1][i] = {}
end

for i = 2,_hiddenLayers+2 do --plus 2 represents the output layer (also need to skip input layer)
for i = 2,_hiddenLayers+2 do -- +2 represents the output layer (also need to skip input layer)
network[i] = {}
local neuronsInLayer = _neuronsPerLayer
if i == _hiddenLayers+2 then
neuronsInLayer = _outputs
end

for j = 1,neuronsInLayer do
network[i][j] = {bias = biasWeights[i-1][j].bias}
local numNeuronInputs = table.getn(network[i-1])
network[i][j] = {bias = biasWeights[i-1][j].bias} --use the previous layer's bias for this layer's network instantiation
local numNeuronInputs = #(network[i-1])
for k = 1,numNeuronInputs do
network[i][j][k] = biasWeights[i-1][j][k]
end
Expand Down
32 changes: 18 additions & 14 deletions main.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
math.randomseed(os.time())

dofile("luaneural.lua")
dofile("luaneural.lua") --dofile runs the .lua file, almost like parent/child classes in other languages
Filename = "SMB1-1.state"

--set up joypad input for BizHawk
controller = {}
buttons = {}
function setUpButtons()
Expand All @@ -18,6 +19,7 @@ end

function clearJoypad()
controller = {}
--set each of the key values in the dictionary as false; only when button pressed is value true
for k, v in pairs(buttons) do
controller["P1 " .. k] = false
end
Expand All @@ -35,7 +37,7 @@ function setUpJoypad(position, genome)
for k, v in pairs(buttons) do
if v > 0 then
controller["P1 " .. k] = true
local d = v - 1
local d = v - 1 --subtract v (0) by 1 if controller pressed, so genome.action = -1 <= 0
buttons[k] = d
end
end
Expand All @@ -54,13 +56,14 @@ function updateMarioPosition(position)
position.current.y = currentY
end

function restart()
function restart() --restart the emulator, we can't wait for the neural network to learn to escape
savestate.load(Filename)
end

function isStuck(l_numberOfStuckFrame, position)
if position.previous.x == position.current.x then
numberOfStuckFrame = l_numberOfStuckFrame + 1
--if Mario is not advancing, his x-position stays the same, and the network needs to fix it
if buttons[dominantsNeurons[2]] == 0 then
buttons[dominantsNeurons[2]] = 40
end
Expand Down Expand Up @@ -103,7 +106,7 @@ function getFitness(position)
return position.current.x
end

function updateFitness(position, fitness)
function updateFitness(position, fitness) --this is how the genetic algorithm improves its performance
fitness.current = getFitness(position)
if fitness.maximum < fitness.current then
fitness.maximum = fitness.current
Expand All @@ -128,6 +131,7 @@ function findDominantsNeurons(buttons)
clearJoypad()

while true do
--update Mario's x and y positions to see which genetic attempt is best
updateMarioPosition(position)
maxYCurrent = position.current.y
controller["P1 " .. k] = true
Expand Down Expand Up @@ -166,14 +170,14 @@ function generateAGenome(lengthOfStep, position)
if genome[i-lengthOfStep] ~= nil then
local p = genome[i-lengthOfStep].pressingForce / 5.625
local value = nil
if p <= 5.625 then value = network:forewardPropagate(0,0,0)[1]
elseif p <= 11.25 then value = network:forewardPropagate(0,0,1)[1]
elseif p <= 16.875 then value = network:forewardPropagate(0,1,0)[1]
elseif p <= 22.5 then value = network:forewardPropagate(0,1,1)[1]
elseif p <= 28.125 then value = network:forewardPropagate(1,0,0)[1]
elseif p <= 33.75 then value = network:forewardPropagate(1,0,1)[1]
elseif p <= 39.375 then value = network:forewardPropagate(1,1,0)[1]
else value = network:forewardPropagate(1,1,1)[1]
if p <= 5.625 then value = network:forwardPropagate(0,0,0)[1]
elseif p <= 11.25 then value = network:forwardPropagate(0,0,1)[1]
elseif p <= 16.875 then value = network:forwardPropagate(0,1,0)[1]
elseif p <= 22.5 then value = network:forwardPropagate(0,1,1)[1]
elseif p <= 28.125 then value = network:forwardPropagate(1,0,0)[1]
elseif p <= 33.75 then value = network:forwardPropagate(1,0,1)[1]
elseif p <= 39.375 then value = network:forwardPropagate(1,1,0)[1]
else value = network:forwardPropagate(1,1,1)[1]
end
genome[i].pressingForce = math.floor(genome[i].pressingForce * value)
end
Expand Down Expand Up @@ -366,7 +370,7 @@ hideBanner = forms.checkbox(form, "Hide", 5, 25)

bestFitnessForGeneration = 0

while true do
while true do --continually run the algorithm until Mario clears the level
result = {}
trainNeuralNetwork()
bestFitnessForGeneration = 0
Expand All @@ -393,4 +397,4 @@ while true do
nextPopulation = createChildren(genomes, numberOfChildren)
mutatedPopulation = mutatePopulation(nextPopulation, chance_of_mutation)
nextGeneration(generation, mutatedPopulation)
end
end