Проблема со Snake Game, алгоритмом geneti c и нейронными сетями - PullRequest
1 голос
/ 13 апреля 2020

В настоящее время я работаю над проектом, связанным с Джулией Ланг, алгоритмом geneti c, нейронными сетями и игрой в змеи, но по какой-то причине я не могу преподавать nnet с моим генети c. Я почти уверен, что это проблема с логикой c за генети c, но я не уверен.

AI.jl

#=
4x movement directions 
4x check wheter the fruit is in one of the snake head direction OK
4x check wheter the snake body is in one of the snake head direction OK
4x check wheter the snake body is in on of the snake head diagonal quadrants OK
4x check wheter the fruit is in one of the snake head diagonal quadrants    OK
4x check wheter the snake is near to the wall OK
=#
mutable struct SnakeCandidate <: Candidate
    game::SnakeGame
    nn::NeuralNetwork
    fitness::Float64
    probability::Float64
end

const sensorCount = 20

function aiGetSensorsData(game::SnakeGame)
    data = Array{Float64, 1}(undef, 0)

    #fruit is on the left side
    onLeft = game.snake.head.x > game.fruit.x
    #fruit is on the right side
    onRight = game.snake.head.x < game.fruit.x
    #fruit is on the middle
    onMiddle = game.snake.head.x == game.fruit.x

    #FRUIT DIRECTION
    #fruit is at snake's left
    push!(data, onLeft && game.snake.head.y == game.fruit.y)
    #fruit is at snake's right
    push!(data, onRight && game.snake.head.y == game.fruit.y)
    #fruit is at snake's top
    push!(data, onMiddle && game.snake.head.y > game.fruit.y)
    #fruit is at snake's down
    push!(data, onMiddle && game.snake.head.y < game.fruit.y)

    #FRUIT QUADRANT
    #fruit is at the 1st quadrant
    push!(data, onRight && game.snake.head.y > game.fruit.y)
    #fruit is at the 2nd quadrant
    push!(data, onLeft && game.snake.head.y > game.fruit.y)
    #fruit is at the 3rd quadrant
    push!(data, onLeft && game.snake.head.y < game.fruit.y)
    #fruit is at the 4th quadrant
    push!(data, onRight && game.snake.head.y < game.fruit.y)

    #SNAKE BODY
    #BODY QUADRANT
    bodySides = falses(4)
    bodyQuadrants = falses(4)
    for i in 2:length(game.snake.body)
        xEqual::Bool = game.snake.head.x == game.snake.body[i].x
        yEqual::Bool = game.snake.head.y == game.snake.body[i].y

        #a piece of the snake body is at snake's left
        bodySides[1] = bodySides[1] || (yEqual && game.snake.head.x > game.snake.body[i].x)
        #a piece of the snake body is at snake's right
        bodySides[2] = bodySides[2] || (yEqual && game.snake.head.x < game.snake.body[i].x)
        #a piece of the snake body is at snake's top
        bodySides[3] = bodySides[3] || (xEqual && game.snake.head.y > game.snake.body[i].y)
        #a piece of the snake body is at snake's bottom
        bodySides[4] = bodySides[4] || (xEqual && game.snake.head.y < game.snake.body[i].y)

        yOver = game.snake.head.y > game.snake.body[i].y
        yUnder = game.snake.head.y < game.snake.body[i].y
        #a piece of the snake is on the 1st quadrant
        bodyQuadrants[1] = bodyQuadrants[1] || (yOver && game.snake.head.x < game.snake.body[i].x)
        #a piece of the snake is on the 2nd quadrant
        bodyQuadrants[2] = bodyQuadrants[2] || (yOver && game.snake.head.x > game.snake.body[i].x)
        #a piece of the snake is on the 3rd quadrant
        bodyQuadrants[3] = bodyQuadrants[3] || (yUnder && game.snake.head.x < game.snake.body[i].x)
        #a piece of the snake is on the 4th quadrant
        bodyQuadrants[4] = bodyQuadrants[4] || (yUnder && game.snake.head.x < game.snake.body[i].x)
    end
    append!(data, bodySides)
    append!(data, bodyQuadrants)

    #NEAR TO WALL
    #top wall
    push!(data, game.snake.head.y == 1)
    #left wall
    push!(data, game.snake.head.x == 1)
    #bottom wall
    push!(data, game.snake.head.y == game.mapH)
    #right wall
    push!(data, game.snake.head.x == game.mapW)

    #DIRECTIONS
    push!(data, game.snake.direction == UP)
    push!(data, game.snake.direction == DOWN)
    push!(data, game.snake.direction == LEFT)
    push!(data, game.snake.direction == RIGHT)

    return data
end

aiCreateCandidate() = SnakeCandidate(SnakeGame(9, 9, 5, 5, UP), 
                                     Float64NeuralNetwork(24, [20], 4), 0, 0)

function aiNextMovement(game::SnakeGame, nn::NeuralNetwork)
        snakeSetDirection(game, findmax(nnetExecute(nn, aiGetSensorsData(game)))[2])
end

function aiFitnessEvaluate(cand::Candidate)
    cand.fitness = 0

    maxMovement = 100
    movCount = 0
    while !cand.game.lost && movCount < maxMovement
        aiNextMovement(cand.game, cand.nn)
        prevSize = length(cand.game.snake.body)
        snakeNextFrame(cand.game)
        movCount += 1
        movCount = (1 - (length(cand.game.snake.body) - prevSize)) * movCount
    end

     cand.fitness = 5 * (length(cand.game.snake.body) - 1) + 1

     return cand
end

function aiTrain()
    tset = genExecute(aiCreateCandidate, aiFitnessEvaluate, 2000, 50, 50, 2, 0.1)
    for cand in tset
        println(cand.fitness)
    end


    game = SnakeGame(9,9,5,5,UP)
    nn = tset[1].nn

    while !game.lost
        display(snakeGetDrawMatrix(game))
        aiNextMovement(game, nn)
        snakeNextFrame(game)
        sleep(0.3)
    end

en

NeuralNetwork.jl

abstract type Neuron end
abstract type NeuralNetwork end

mutable struct Float64Neuron <: Neuron
    weights::Array{Float64, 1}

    function Float64Neuron(inputNumber::Int)
        neuron = new()

        neuron.weights = [2 * rand() - 1 for i in 1:inputNumber] 

        return neuron
    end
end

mutable struct Float64NeuralNetwork <: NeuralNetwork
    inputNeuronsNumber::Int
    hiddenLayers::Array{Array{Float64Neuron, 1}, 1}
    outputLayer::Array{Float64Neuron, 1}

    weights::Float64
    layers::Array{Array{Float64Neuron, 1}, 1}

    function Float64NeuralNetwork(inputNumber::Int,
        hiddenLayersArchitecture::Array{Int, 1}, outputNeuronsNumber::Int)
            net = new()

            net.inputNeuronsNumber = inputNumber

            net.hiddenLayers = Array{Array{Float64Neuron, 1}, 1}(undef, 0)
            currentInput = inputNumber
            for neuronsAmmout in hiddenLayersArchitecture
                push!(net.hiddenLayers, [Float64Neuron(currentInput) for i in 1:neuronsAmmout])
                currentInput = neuronsAmmout
            end

            net.outputLayer = [Float64Neuron(currentInput) for i in 1:outputNeuronsNumber]

            net.layers = [net.hiddenLayers; [net.outputLayer]]
            return net
    end
end

neuronSigmoid(number) = 1 / (1 + exp(-number))

neuronExecute(neuron::Float64Neuron, input::Array{Float64, 1}) = neuronSigmoid(dot(neuron.weights, input))

function nnetExecuteLayer(layer::Array{Float64Neuron, 1}, input::Array{Float64, 1})
    return [neuronExecute(neuron, input) for neuron in layer]
end

function nnetExecute(net::NeuralNetwork, input)
    for layer in net.layers
        @inbounds input = nnetExecuteLayer(layer, input)
    end

    return input
end

Geneti c .jl

#note: the subtypes of candidate must have a NeuralNetwork field named as nn
# and another Float64 field named fitness
abstract type Candidate end

function genSwapArrayIndexes(arr, index1::Integer, index2::Integer)
    aux = arr[index1]
    arr[index1] = arr[index2]
    arr[index2] = aux
end

function genSelectionRoullete(cands, selectedCandidatesCount::Integer)
    #reversively sorts the array by each item fitness
    sort!(cands, by=v->v.fitness, rev=true)

    for i in 1:selectedCandidatesCount
        arr = view(cands, i:length(cands))

        #calculates fitness sum
        fitSum = 0
        for cand in arr
            fitSum += cand.fitness
        end

        #calculates each candidate probability
        lastProb = 0
        for cand in arr
            cand.probability = cand.fitness/fitSum + lastProb
            lastProb = cand.probability
        end

        #selects one item from the list by a random value of probability
        chosen = rand()
        for j in 1:length(arr)
            if arr[j].probability > chosen
                genSwapArrayIndexes(cands, i, j + i - 1)
                break
            end
        end
    end
end

function genCrossoverMixParentsWeights(father::Array{Float64, 1}, mother::Array{Float64, 1},
    crossoverPoint::Integer)
    return [father[1:crossoverPoint]; mother[(crossoverPoint+1):length(mother)]]
end

function genGetCrossoverPoint(layer, crossoverCuttingFractionDivisor::Integer)
    return convert(Int64, floor(length(layer) / crossoverCuttingFractionDivisor))
end

function genCrossover(cands, selectedCandidatesCount::Integer, 
    crossoverCuttingFractionDivisor::Integer)
    for i in (selectedCandidatesCount + 1):length(cands)
        father = cands[rand(1:selectedCandidatesCount)].nn
        mother = cands[rand(1:selectedCandidatesCount)].nn
        for j in eachindex(cands[i].nn.layers)
            crossoverPoint = genGetCrossoverPoint(cands[i].nn.layers[j], 
                                            crossoverCuttingFractionDivisor)
            for k in eachindex(cands[i].nn.layers[j])
                cands[i].nn.layers[j][k].weights = genCrossoverMixParentsWeights(
                    father.layers[j][k].weights, mother.layers[j][k].weights, crossoverPoint)
            end
        end
    end
end

function genMutateWeight(weights::Array{Float64, 1})
    weights[rand(1:length(weights))] = 2 * rand() - 1
end

function genMutatation(cands, mutationRate::Float64)
    for cand in cands
        while mutationRate > rand()
            layerindex = rand(1:length(cand.nn.layers))
            neuronindex = rand(1:length(cand.nn.layers[layerindex]))
            genMutateWeight(cand.nn.layers[layerindex][neuronindex].weights)

            mutationRate /= 2
        end
    end
end

function genExecute(createCandidate::Function, fitnessFunc::Function, populationSize::Integer, 
    generationCount::Integer, selectedCandidatesCount::Integer, crossoverCutingFractionDivisor::Integer, mutationRate::Float64)
    candidates = [fitnessFunc(createCandidate()) for i in 1:populationSize]

    for gen in 1:generationCount
        genSelectionRoullete(candidates, selectedCandidatesCount)
        genCrossover(candidates, selectedCandidatesCount, crossoverCutingFractionDivisor)
        genMutatation(candidates, mutationRate)

        for candidate in candidates
            fitnessFunc(candidate)
        end
    end

    sort!(candidates, by=x->x.fitness, rev=true)

    return candidates
end

PS: я пытался тренировать свой nnet с 30 поколениями с населением 2000 субъектов, но у меня не было положительных результатов

...