Tutorial on making an Artificial Intelligence Chatbot-A step by step guide on how to implement you own chatbot |
Basically chatterbot is a computer program that when you provide it with some inputs in Natural Language (English, French ...) responds with something meaningful in that same language. Which means that the strength of a chatterbot could be directly measured by the quality of the output selected by the Bot in response to the user? By the previous description, we could deduce that a very basic chatterbot can be written in a few lines of code in a given specific programming language. Lets make our first chatterbot (notice that all the codes that will be used in this tutorial will be written in C++. Also, it is assumed that the reader is familiar with the STL library) This tutorial is also available in Java: Chatbot Tutorial in Java
//
// Program Name: chatterbot1
// Description: this is a very basic example of a chatterbot program
//
// Author: Gonzales Cenelia
//
#include <iostream>
#include <string>
#include <ctime>
int main()
{
std::string Response[] = {
"I HEARD YOU!",
"SO, YOU ARE TALKING TO ME.",
"CONTINUE, I’M LISTENING.",
"VERY INTERESTING CONVERSATION.",
"TELL ME MORE..."
};
srand((unsigned) time(NULL));
std::string sInput = "";
std::string sResponse = "";
while(1) {
std::cout << ">";
std::getline(std::cin, sInput);
int nSelection = rand() % 5;
sResponse = Response[nSelection];
if(sInput == "bye") {
std::cout << "IT WAS NICE TALKING TO YOU USER, SEE YOU NEXT TIME!" << std::endl;
break;
}
std::cout << sResponse << std::endl;
}
return 0;
}
As you can see, it doesn't take a lot of code to write a very basic program that can interact with a user but it would probably be very difficult to write a program that would really be capable of truly interpreting what the user is actually saying and after that would also generate the appropriate response to it. These have been a long term goal since the beginning and even before the very first computers were created. In 1951,the British mathematician Alan Turing has came up with the question Can machines think and he has also propose a test which is now known as the Turing Test. In this test, a computer program and also a real person is set to speak to a third person (the judge) and he has to decide which of them is the real person. Nowadays, there is a competition that was named the Loebner Prize and in this competition bots that has successfully fool most of the judge for at list 5 minutes would win a prize of 100.000$. So far no computer program was able to pass this test successfully. One of the major reasons for this is that computer programs written to compute in such contest have naturally the tendency of committing a lot of typo (they are often out of the context of the conversation). Which means that generally, it isn't that difficult for a judge to decide whether he is speaking to a "computer program" or a real person.
Also, the direct ancestor of all those program that tries to mimic a conversation between real human beings is Eliza,the first version of this program was written in 1966 by Joseph Weizenbaum a professor of MIT.
Chatbots in general are considered to belong to the weak a.i field (weak artificial intelligence) as opposed to strong a.i who's goal is to create programs that are as intelligent as humans or more intelligent. But it doesn't mean that chatbots do not have any true potential. Being able to create a program that could communicate the same way humans do would be a great advance for the a.i field. Chatbot is this part of artificial intelligence which is more accessible to hobbyist (it only take some average programming skill to be a chatbot programmer). So, programmers out there who wanted to create true a.i or some kind of artificial intelligence, writing intelligent chatbots is a great place to start!
A keyword is just a sentence (not necessarily a complete one) or even a word that the program might recognize from the user's input which then makes it possible for the program to react to it (ex: by printing a sentence on the screen). For the next program, we will write a knowledge base or database, it will be composed of keywords and some responses associated to each keyword.
so, now we know what to do to improve "our first chatterbot" and make it more intelligent.
Let’s proceed on writing "our second bot", we will call it chatterbot2.
//
// Program Name: chatterbot2
// Description: this is an improved version of the previous chatterbot program "chatterbot1"
// this one will try a little bit more to understand what the user is trying to say
//
// Author: Gonzales Cenelia
//
#pragma warning(disable: 4786)
#include <iostream>
#include <string>
#include <vector>
#include <ctime>
const int MAX_RESP = 3;
typedef std::vector<std::string> vstring;
vstring find_match(std::string input);
void copy(char *array[], vstring &v);
typedef struct {
char *input;
char *responses[MAX_RESP];
}record;
record KnowledgeBase[] = {
{"WHAT IS YOUR NAME",
{"MY NAME IS CHATTERBOT2.",
"YOU CAN CALL ME CHATTERBOT2.",
"WHY DO YOU WANT TO KNOW MY NAME?"}
},
{"HI",
{"HI THERE!",
"HOW ARE YOU?",
"HI!"}
},
{"HOW ARE YOU",
{"I'M DOING FINE!",
"I'M DOING WELL AND YOU?",
"WHY DO YOU WANT TO KNOW HOW AM I DOING?"}
},
{"WHO ARE YOU",
{"I'M AN A.I PROGRAM.",
"I THINK THAT YOU KNOW WHO I'M.",
"WHY ARE YOU ASKING?"}
},
{"ARE YOU INTELLIGENT",
{"YES,OFCORSE.",
"WHAT DO YOU THINK?",
"ACTUALY,I'M VERY INTELLIGENT!"}
},
{"ARE YOU REAL",
{"DOES THAT QUESTION REALLY MATERS TO YOU?",
"WHAT DO YOU MEAN BY THAT?",
"I'M AS REAL AS I CAN BE."}
}
};
size_t nKnowledgeBaseSize = sizeof(KnowledgeBase)/sizeof(KnowledgeBase[0]);
int main() {
srand((unsigned) time(NULL));
std::string sInput = "";
std::string sResponse = "";
while(1) {
std::cout << ">";
std::getline(std::cin, sInput);
vstring responses = find_match(sInput);
if(sInput == "BYE") {
std::cout << "IT WAS NICE TALKING TO YOU USER, SEE YOU NEXTTIME!" << std::endl;
break;
}
else if(responses.size() == 0) {
std::cout << "I'M NOT SURE IF I UNDERSTAND WHAT YOU ARE TALKING ABOUT." << std::endl;
}
else {
int nSelection = rand() % MAX_RESP;
sResponse = responses[nSelection]; std::cout << sResponse << std::endl;
}
}
return 0;
}
// make a search for the user's input
// inside the database of the program
vstring find_match(std::string input) {
vstring result;
for(int i = 0; i < nKnowledgeBaseSize; ++i) {
if(std::string(KnowledgeBase[i].input) == input) {
copy(KnowledgeBase[i].responses, result);
return result;
}
}
return result;
}
void copy(char *array[], vstring &v) {
for(int i = 0; i < MAX_RESP; ++i) {
v.push_back(array[i]);
}
}
Now,the program can understand some sentences like "what is your name", "are you intelligent" etc And also he can choose an appropriate response from his list of responses for this given sentence and just display it on the screen. Unlike the previous version of the program (chatterbot1) Chatterbot2 is capable of choosing a suitable response to the given user input without choosing random responses that doesn't take into account what actualy the user trying to say.
We’ve also added a couple of new techniques to theses new program: when the program is unable to find a matching keyword the current user input, it simply answers by saying that it doesn't understand which is quiet human like.
There are quiet a few things that we can improve, the first one is that since the chatterbot tends to be very repetitive, we might create a mechanism to control these repetitions. We could simply store the previous response of that Chatbot within a string "sPrevResponse" and make some checkings when selecting the next bot response to see if it's not equal to the previous response. If it is the case, we then select a new response from the available responses.
The other thing that we could improve would be the way that the chatbot handles the users inputs, currently if you enter an input that is in lower case the Chatbot would not understand anything about it even if there would be a match inside the bot's database for that input. Also if the input contains extra spaces or punctuation characters (!;,.) this also would prevent the Chatbot from understanding the input. That's the reason why we will try to introduce some new mechanism to preprocess the user’s inputs before it can be search into the Chatbot database. We could have a function to put the users inputs in upper case since the keywords inside the database are in uppercase and another procedure to just remove all of the punctuations and extra spaces that could be found within users input. That said, we now have enough material to write our next chatterbot: "Chattebot3". View the code for Chatterbot3
The other possibility is much more complex, it use's the concept of Fuzzy String Search. To apply this method, it could be useful at first to break the inputs and the current keyword in separate words, after that we could create two different vectors, the first one could be use to store the words for the input and the other one would store the words for the current keyword. Once we have done this we could use the Levenshtein distance for measuring the distance between the two word vectors. (Notice that in order for this method to be effective we would also need an extra keyword that would represent the subject of the current keyword).
So, there you have it, two different methods for improving the chatterbot. Actually we could combine both methods and just selecting which one to use on each situation.
Finally, there are still another problem that you may have noticed with the previous chatterbot, you could repeat the same sentence over and over and the program wouldn't have any reaction to this. We need also to correct this problem.
So, we are now ready to write our fourth chatterbot, we will simply call it chatterbot4. View the code for Chatterbot4
As you probably may have seen, the code for "chatterbot4" is very similar to the one for "chatterbot3" but also there was some key changes in it. In particular, the function for searching for keywords inside the database is now a little bit more flexible. So, what next? Don’t worry; there are still a lot of things to be covered.
Before proceeding to the next part of this tutorial, you are encouraged to try compiling and running the code for "chatterbot5" so that you can understand how it works and also to verifies the changes that have been made in it. Has you may have seen, the implementation of the "current chatterbot", is now encapsulated into a class, also, there has been some new functions added to the new version of the program.
select_response()
this function selects a response from a list of responses, there is a new helper function that was added to the program shuffle, this new function shuffles a list of strings randomly after seed_random_generator() was called.
save_prev_input()
This function simply saves the current user input into a variable (m_sPrevInput) before getting some new inputs from the user.
void save_prev_response()
The function save_prev_response() saves the current response of the chatterbot before the bot have started to search responses for the current input, the current responsesis save in the varaible (m_sPrevResponse).
void save_prev_event()
This function simply saves the current event (m_sEvent) into the variable (m_sPrevEvent).
An event can be when the program has detected a null input from the user also, when the user repeats himself or even when the chatterbot makes repetitions has well etc.
void set_event(std::string str)
Sets the current event (m_sEvent)
void save_input()
Makes a backup of the current input (m_sIntput) into the variable m_sInputBackup.
void set_input(std::string str)
Sets the current input (m_sInput)
void restore_input()
Restores the value of the current input (m_sInput) that has been saved previously into the variable m_sInputBackup.
void print_response()
Prints the response that has been selected by the chat robot on the screen.
void preprocess_input()
This function does some preprocessing on the input like removing punctuations, redundant spaces charactes and also it converts the input to uppercase.
bool bot_repeat()
Verifies if the chatterbot has started to repeat himself.
bool user_repeat()
Verifies if the user has repeated his self.
bool bot_understand()
Verifies that the bot understand the current user input (m_sInput).
bool null_input()
Verifies if the current user input (m_sInput) is null.
bool null_input_repetition()
Verifies if the user has repeated some null inputs.
bool user_want_to_quit()
Check to see if the user wants to quit the current session with the chatterbot.
bool same_event()
Verifies if the current event (m_sEvent) is the same as the previous one (m_sPrevEvent).
bool no_response()
Checks to see if the program has no response for the current input.
bool same_input()
Verifies if the current input (m_sInput) is the same as the previous one (m_sPrevInput).
bool similar_input()
Checks to see if the current and previous input are similar, two inputs are considered similar if one of them is the substring of the other one
(Ex: how are you and how are you doing would be considered similar because how are you is a substring of how are you doing.
void get_input()
Gets inputs from the user.
void respond()
Handles all responses of the chat robot wheter it is for events or simply the current user input. So, basicaly, these function controls the behaviour of the program.
find_match()
Finds responses for the current input.
void handle_repetition()
Handles repetions made by the program.
handle_user_repetition()
Handles repetitions made by the user.
void handle_event(std::string str)
This function handles events in general.
You can clearly see that "chatterbot5" have much more functionalities than "chatterbot4" and also each functionalities is encapsulated into methods (functions) of the class "CBot" but still there are a lot more improvements to be made on it too.
Chattebot5 introduce the concept of "state", in these new version of the Chatterbot, we associate a different "state" to some of the events that can occure during a conversation. Ex: when the user enters a null input, the chatterbot would set itsel into the "NULL INPUT**" state, when the user repeat the same sentence, it would go into the "REPETITION T1**" state, etc.
Also these new chatterbot uses a bigger database than the previous chatbot that we have seen so far: chatterbot1, chatterbot2, chatterbot3 ... But still, this is quiet insignificant due to the fact that most chatterbots in use today (the very popular ones) have a database of at least 10000 lines or more. So, this would definitely be one of the major goal that we might try to achieve into the next versions of the chatterbot.
But however for now, we will concentrate a little problem concerning the current chatterbot.
How did we arrive at this transformation? We may have done it by using two steps:
We make sure that the chatterbot have a list of response templates that is linked to the corresponding keywords. Responses templates are a sort of skeleton to build new responses for the chatterbot. usually we used wildcards in the responses to indicate that it is a template. On the previous example, we have used the template: (SO, YOU THINK THAT*) to construct our response. During the reassembly process, we simply replace the wildcard by some part of the original input. In that same example, we have use: YOU ARE A MACHINE which is actually the complete original input from the user. After replacing the wildcard by the user's input, we have the following sentence: SO, YOU THINK THAT YOU ARE A MACHINE but we can not use these sentence as it is, before that we need to make some pronoun reversal in it.
Notice that it's not a good thing to use transposition too much during a conversation, the mechanism would become too obvious and it could create some repetition. View the code for Chatterbot9
Some Examples of sentences using "WHO ARE YOU" would be:
But a keyword such as "WHO IS" can only be found at the beginning or in the middle of a given sentence but it can not be found at end of the sentence or alone.
Examples of sentences using the keyword: "WHO IS"
How do we make sure that the chatterbot will be able to distinguish such keywords and the specific places were they are aloud to be found on a sentence? We will simply introduce some new notations for keywords:
A keyword can have different meanings depending on it's position in a given sentence.
View the code for Chatterbot10
To illustrate these concept, we are going to look at the following conversation log:
USER: What is your favorite movie?
CHATTERBOT: IT IS TERMINATOR II.
USER: Why do you like this movie? (Now how are we supposed to answer that question if we knew nothing about the previous response of the Chatbot?)
So clearly, some inputs requires the usage of "context" in order to be able to formulate a correct answer. In the previous example, it would simply be: IT IS TERMINATOR II. Now the Bot knows what it was talking about previously, it can more easily formulate a good answer to the user's input.
we can now continue the previous conversation log:
(Context: IT IS TERMINATOR II)
CHATTERBOT: BECAUSE IT IS A SCIENCE-FICTION MOVIE AND I LOVE SCIENCE-FICTION.
Context also aloud us to control improper reaction from the Chatbot. Example, if the user enters the sentence: "Why do you like these movie?" during a conversation without the Chatterbot even talking about these subject. It could simply respond by saying: WHAT ARE YOU TALKING ABOUT?
The context feature has been implemented in Chatterbot11.
View the code for Chatterbot11
Another great feature that would be very interesting to implement into a Chatterbot is the capacity to anticipate the next response of the user, these would make the Chatbot
looks even more smarter during a conversation.
Initialize_TTS_Engine()
These function as the name suggest initalised the "Text To Speech Engine" that is, we first start by initializing the "COM objects" since SAPI is build on top
of the ATL library. If the initialization was successful, we then create an instance of the "ISpVoice" object that controlled the "Text To Speech" mechanism
within the SAPI library by using the "CoCreateInstance" function. If that also was successful, it means that our "Text To Speech Engine" was initialized
properly and we are now ready for the next stage: speak out the "response string"
speak (const std::string text)
So, this is the main function that is used for implementing "Text To Speech" within the program, it basically takes the "response string" converted
to wide characters (WCHAR) and then pass it to the "Speak method" of the "ISpVoice" object which then speak out the "bot's response"
Release_TTS_Engine()
Once we are done using the "SAPI Text To Speech Engine", we just release all the resources that has been allocated during the procedure
View the code for Chatterbot12
We now have a complete architecture for the database, we just need to implement theses features into the next version of the chatbot (Chatterbot13)
View the code for Chatterbot13