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.
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 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
- Generate a possible solution
- Test to see if the expected solution is reached
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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...
- Get all the digits from the question string
- Format the digits in the question string
- Turn the digit strings into integers so that they can be added
- Add the numbers
- Check if they are even
- 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.
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.
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...
- Call our start_list to start the agent
- Then generate a certain amount of questions. Let's start off with 20.
- For each individual question we need to show the answer to the question.
Here is how that function should look.
We're done! What happens when we call SumAndTest.start? The system answers 20 random questions with the appropriate response. See the output below!!
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!