Automation And A.I. Systems will be the nexus for next generation Applications.

Join us on our journey of exploring the Techniques of these fields

Photo by Bram Janssens/Hemera / Getty Images

 

 

Coding A.I. Techniques in Elixir: The Generate and Test Algorithm

Coding A.I. Techniques in Elixir: The Generate and Test Algorithm

Lately, I've been working on a side A.I. project that I expect will take some considerable time to complete. The goal was to build 100% of my current A.I. project in Elixir, but before I could make that decision, I needed to see if I could apply some of the most popular A.I. problem solving approaches using Elixir.  During these days of working on this side project I've been teaching myself some of the most effective techniques A.I. researchers use to solve problems programmatically.

In this post I will do a brief explanation of the A.I. technique called the Generate And Test algorithm(aka Trial and Error), and then I will use a variation of this technique to solve a simple problem utilizing the Elixir programming language.

Within A.I. there are many fields. The A.I. fields we most commonly hear about today are machine learning, natural language processing, along with robotics. However, for me my favorite discipline in A.I. is known as Automated Reasoning. The program that I will demonstrate will fall into this field of A.I.

THE PROBLEM

Suppose you were tasked with writing a program that needs to get the sum of a group of very large numbers in a stated question and then determine if the result of the answer to that question is even. The problem sounds simple enough, but if you took the normal sequential approach, you'd end up writing way more code than you'd need to. It would be much better to take an automated reasoning approach. 

THE SOLUTION

The Generate and Test Algorithm is best suited for simple problems like the one above. The way this algorithm works is quite simple. You can think of it as having 3 main steps

  1. Generate a possible solution
  2. Test to see if the expected solution is reached
  3. If the solution is found stop, otherwise repeat steps 1 through 3

With these concepts in mind let us create a smart program that can answer the question "Is the result of x + y even?".  In this exercise we will only care about the even numbers, so when the system finds that the result is even we will respond accordingly affirming it has found an even result. Normally, when we get to a true answer using this A.I. technique we would stop all processing. However, in our case we want to only stop when all the questions given to the system have been answered. Finally, for results that yield an odd result, we will just answer No.

THE CODE

While writing the solution for this, I noticed that the code you are about to see encompasses a majority of Elixir's core language features. So, if you've never used Elixir before these code examples will give you practical insight on how the language works.

First step is to create a module named SumAndTest to get everything started.

- Elixir organizes code via modules. NO CLASSES THANK GOODNESS!!! - @moduledoc is a comment macro that allows you to give a brief description of what it is the given module is meant to do.

- Elixir organizes code via modules. NO CLASSES THANK GOODNESS!!!

- @moduledoc is a comment macro that allows you to give a brief description of what it is the given module is meant to do.

We need to answer the question "Is the result of x + y even?". So lets go ahead and create a function that holds that question.

-  we define a private function called addition question with two arguments - String Interpolation syntax in Elixir works the same way it does in Ruby 

-  we define a private function called addition question with two arguments

- String Interpolation syntax in Elixir works the same way it does in Ruby 

Now we have our question. The next step is to define the two possible responses our system will give to this question. We will use Elixir's ability to pattern match via the function arguments as an alternative to if statements. 

-When true we say yes, when false we simply say no - Pattern matching is a key feature that separates Elixir from most other programming languages.  Superb for AI programming.

-When true we say yes, when false we simply say no

- Pattern matching is a key feature that separates Elixir from most other programming languages.  Superb for AI programming.

We have built the ability to answer yes and no. But what will trigger this answer? We need to determine if the result of the sum of 2 numbers is even. Lets handle that function next.

- Elixir has function guards that  serve as input argument validators to functions - In Elixir, you can have inline if statements too. -rem() is a core elixir function belonging to the Kernel module. It determines the remainder of a integer division.

- Elixir has function guards that  serve as input argument validators to functions

- In Elixir, you can have inline if statements too.

-rem() is a core elixir function belonging to the Kernel module. It determines the remainder of a integer division.

The basic building blocks are now in, but we are striving for automated reasoning. We want the system itself to automatically come up with the question so that we can test that our system truly answers this question correctly. Lets build a generate function that is responsible for this. 

- Confused? Don't be. In Elixir there are no such things as loops. Functions are your only tool for getting things done in the language, as it should be. If you want to repeatedly do actions in a loop style fashion we use recursion. - With pattern matching we can now take in a number of questions and pass that to a 2 argument version of the generate function. The first argument is set to 0 because that is where the accumulator will start. -The second generate function is only called when it is given a accumulator and the amount of questions is greater than or equal to 1. We then call the addition_question function we wrote earlier giving it 2 parameters. - The Enum.random  function takes  a range given and returns a random integer within the upper and lower bound of that range. This is perfect for our system as we do not know what numbers will be given. This will test our systems accuracy perfectly! - Once these questions are generated we need a way to store them so that we can have them fed to the system. This is where build_list comes in. We will write that soon. -Finally at the end of the function we call the generate function again, this time increasing the  accumulator by one, and taking away from the total amount of questions given.  - After the recursion completes our question count must be 0 and we present the amount of questions generated to the user so that they know how many questions were created.

- Confused? Don't be. In Elixir there are no such things as loops. Functions are your only tool for getting things done in the language, as it should be. If you want to repeatedly do actions in a loop style fashion we use recursion.

- With pattern matching we can now take in a number of questions and pass that to a 2 argument version of the generate function. The first argument is set to 0 because that is where the accumulator will start.

-The second generate function is only called when it is given a accumulator and the amount of questions is greater than or equal to 1. We then call the addition_question function we wrote earlier giving it 2 parameters.

- The Enum.random  function takes  a range given and returns a random integer within the upper and lower bound of that range. This is perfect for our system as we do not know what numbers will be given. This will test our systems accuracy perfectly!

- Once these questions are generated we need a way to store them so that we can have them fed to the system. This is where build_list comes in. We will write that soon.

-Finally at the end of the function we call the generate function again, this time increasing the  accumulator by one, and taking away from the total amount of questions given. 

- After the recursion completes our question count must be 0 and we present the amount of questions generated to the user so that they know how many questions were created.

We are pretty close now. But there is something important to implement. In the generate/2 function we called a function known as build_list. Why did we need this?

Elixir has immutable data structures. That means state is not maintained outside of the current function. This is fantastic for AI programming because things can get complicated very quickly if you're building systems with a lot of mutability. In a field where you could possibly be dealing with an astronomical amount of un-known possibilities, you need to have some consistency. That consistency should be the data.

We generated those questions, but we need a way to hold on to them so that the system doesn't lose them after we exit the function. That's where Elixir Agents come in. Elixir Agents allow for a program to hold state. It does the tracking of the data structures changes over time on its own. The usage for agents are quite simple, and they might be the main reason why Joe Armstrong (The creator of Erlang) says with Erlang and Elixir "You don't need a database!". Lets create an Agent that starts as an empty list. This list will hold our questions.

- creating an agent with two arguments. The first  is a function that yields an empty list. The second arg is a keyword list that names the function by the current module name -(NOTE) Always give names to your Agents!

- creating an agent with two arguments. The first  is a function that yields an empty list. The second arg is a keyword list that names the function by the current module name

-(NOTE) Always give names to your Agents!

Great! Now that our agent has the ability to start and can hold state, lets go ahead and implement the build_list function we saw earlier.

- Here we take a question, and we find that build_list actually wraps the Agent.update function which takes two arguments. The first argument is the agent name. The second argument is a function that takes the current list and appends the current question to a new version of the list.

- Here we take a question, and we find that build_list actually wraps the Agent.update function which takes two arguments. The first argument is the agent name. The second argument is a function that takes the current list and appends the current question to a new version of the list.

To see all the questions the system will answer we need to get the current state of the agent. We can simply use the Agent.get/2 function. Let's name that function questions.

- Agent.get takes two  params. Current module name,  and a short hand version that stands for current value.

- Agent.get takes two  params. Current module name,  and a short hand version that stands for current value.

Now we need to tie all this logic together so that all the processing can happen in one big pipeline. Our pipeline needs to do the following...

  1. Get all the digits from the question string
  2. Format the digits in the question string
  3. Turn the digit strings into integers so that they can be added
  4. Add the numbers
  5. Check if they are even
  6. Answer Yes or No.

This is where you would use Elixir's PipeLine Operator. This operator is the muscle of this programming language and it makes AI processing algorithms much clearer than they otherwise would be in LISP.

- The pipeline operator takes the result of the function above it and inserts its' result as the first argument of the next function.  

- The pipeline operator takes the result of the function above it and inserts its' result as the first argument of the next function.  

Now in order for users of the system to see what it's doing lets' build a display function that shows the question as well as its answer.

- IO is the main module for dealing with standard Input/Output for devices

- IO is the main module for dealing with standard Input/Output for devices

Finally! We are at the last part of the system. We need a way to trigger this whole process. We need a start function that does the following...

  1. Call our start_list to start the agent
  2. Then generate a certain amount of questions. Let's start off with 20.
  3. For each individual question we need to show the answer to the question.

Here is how that function should look.

-Since the Agent is started we can tell the system to generate 20 questions. For each of those questions we want to display the answer for each individual question.

-Since the Agent is started we can tell the system to generate 20 questions. For each of those questions we want to display the answer for each individual question.

We're done! What happens when we call SumAndTest.start? The system answers 20 random questions with the appropriate response. See the output below!!

- BANG!!

- BANG!!

CONCLUSION

This is probably the simplest of all AI algorithms. Its' simplicity is why I chose to write about this algorithm on my first post here on Automating the Future.  I'd consider reaching for it if I were dealing with a trial and error problem, and there was only a small amount of possibilities within the problem space.

If you're interested in future posts don't forget to subscribe by clicking the top left icon!

  

 Finding the needle in the Haystack: Breadth First Search... The Elixir Way

Finding the needle in the Haystack: Breadth First Search... The Elixir Way