Building intelligence from the ground up. Part one.

Building intelligence from the ground up. Part one.
Photo by Juliet Sarmiento / Unsplash

Assuming you know very little about machine learning (like me) and you're a self-starter (also like me), then you'll appreciate this gradual ground-up approach to understanding and building intelligence.

Let's start out with a simple world. A maze. It is represented as a 2D array of enum values I've dubbed "cells" that can either be a wall, a path, or a reward. Here's what my initial world looks like:

 w a w w w w // w == wall, p == path, r == reward, a == actor
 w p w w w w
 w p w w w w
 w p w w w w
 w p p w w w
 w w p r w w

The program runs until our actor reaches the end of the program. It starts out at 0, 1 and knows the cells to the left, right, and front of it. It uses this information to decide how to move.

Well, let's not use any neural networks, but let's think about what the actor needs to know. In order to successfully solve the maze, it needs to be able to recognize its surroundings (sensors) and modify the world somehow to move itself (actions). Depending on what it senses, it should take a different action.

So, as a very rudimentary approach, let's give the actor all it needs to know to solve this maze. A list of the sensory information it can receive, as well as the action that will propel it towards its goal:

let actorPossibleMoves: [ActorSense: Action] = [
  .init(left: .w, forward: .p, right: .w) : .moveForward,
  .init(left: .p, forward: .w, right: .w) : .turnLeft,
  .init(left: .p, forward: .p, right: .w) : .moveForward,
  .init(left: .w, forward: .w, right: .p) : .turnRight,
  .init(left: .w, forward: .p, right: .p) : .moveForward,
  .init(left: .r, forward: .w, right: .w) : .turnLeft,
  .init(left: .p, forward: .r, right: .w) : .moveForward
]

All I did was run the program until the actor got stuck (which happened immediately the first time I ran it), copied the actor's senses, and then pasted it into the possible moves, paired with the action I would take. It was a manual process but got the job done.

Then, to solve the maze, we simply iterate through the possible moves, find the one that matches our current sensory state, and then perform the action:

func solveMaze() {
    
    var world = World()
    
    while true {
        let sense = leftForwardRight(world: world)
        guard let action = world.actorPossibleMoves[sense] else {
            print("I'm stuck!")
            break
        }
        
        switch action {
        case .moveForward:
            moveForward(world: &world)
        case .turnLeft:
            turnLeft(world: &world)
        case .turnRight:
            turnRight(world: &world)
        }
        
        print(world)
        
        if world.actorX == 3 && world.actorY == 5 {
            break
        }
    }
    
    print("Done!")
}

In this case, the moveForward(world: inout World) action, as well as turnLeft, and turnRight, simply modify the shared world state, which contains the actor's position and direction.

All in all, it's a pretty simple program and it reveals an interesting point - one part of intelligence is knowing what to do in a given scenario, and asking for help when stuck.

The next part will focus on making our little actor more independent. Can it build the possible moves array on its own? Can it experiment with a goal in mind and learn viable behaviors to reach the end? Read it here!