22 min read

In this article written by Alexander T. Combs, author of the book Python Machine Learning Blueprints, we are going to learn how to construct a chatbot from scratch. Along the way, we’ll learn more about the history of the field and its future prospects.

We’ll cover the following topics in this article: The

  • Turing test
  • The history of chatbots
  • The design of chatbots
  • Building a chatbot

(For more resources related to this topic, see here.)

The Turing test

In 1950, Alan Turing posed the question, “Can machines think?” in one of his more famous papers. Being the practical genius that he was, he decided not to tackle this question head on but to pose it in the framework of the “problem of other minds” instead. This problem poses the question, “How do we truly know that other people have minds like our own?”.

As we can only observe their behavior and not the inner workings of their mind, we must take it on faith that they are like us. Turing proposed that if a machine behaved as if it was intelligent, then in fact, we should view it as such. This, in a nutshell, is the Turing test. Passing this test means convincing humans that a machine is a fellow human.

You may wonder at this point whether a program ever passed the Turing test successfully. The answer is that there really is no official Turing test. The closest thing to an official event is the Loebner Prize. This is a contest held annually that awards modest prizes to chatbots that appear to be the most convincingly human to a panel of judges. If this sounds rigorous and academic, it has been described as anything but. Loebner’s sponsorship for the contest is said to have been a product of his total disdain for labor of any kind.

Hopefully, by this point, you realized that any claims about “passing the Turing test” are more spectacle than actual science. However, despite this, Turing’s original concept is a useful construct.

The history of chatbots

I still remember the day my father sat me down in front of his IBM XT with its black and green flickering monitor and asked me to talk to “it”. The conversation went something like the following:

>Hello? How are you feeling today?

>Hi. Who is this?

>Hi there… how are you today?

>I'm fine. You didn't tell me who you are.

>We should be discussing you, not me.

>We should? Why?

>Let's change focus a bit… Tell me about your family.

>You are impossible to have a conversation with.

>Can you elaborate on that?
>You are killing me.

>Perhaps you would like me to be killing you.
>Ok, you are freaking me out.

>Perhaps you're really talking about yourself?

>What?? That makes no sense.

>How would an answer to that help you?

>END

>Very Interesting.

>QUIT

>Thank you for talking with me.

>#$###$#@@….

The “it” that I talked to was a program called ELIZA. Written in the mid-1960s at MIT’s Artificial Intelligence Lab by Joseph Weizenbaum, ELIZA was created to mimic the responses of a Rogerian psychotherapist. Though nearly comical when examined in any depth, the program was capable of convincing some users that they were chatting with an actual human. This was a remarkable feat considering it was a scant 200 lines of code that used randomization and regular expressions to parrot back responses. Even today, this simple program remains a staple of popular culture. If you ask Siri who ELIZA is, she will tell you she is a friend and brilliant psychiatrist.

If ELIZA was an early example of chatbots, what have we seen after this? In recent years, there has been an explosion of new chatbots; most notable of these is Cleverbot.

Cleverbot was released to the world via the web in 1997. Since then, this bot has racked up hundreds of millions of conversions. Unlike early chatbots, Cleverbot (as the name suggests) appears to become more intelligent with each conversion. Though the exact details of the workings of the algorithm are difficult to find, it is said to work by recording all conversations in a database and finding the most appropriate response by identifying the most similar questions and responses in the database.

I made up a nonsensical question in the following screenshot, and you can see that it found something similar to the object of my question in terms of a string match.

I persisted:

Again I got something…similar?

You’ll also notice that topics can persist across the conversation. In response to my answer, I was asked to go into more detail and justify my answer. This is one of the things that appears to make Cleverbot, well, clever.

While chatbots that learn from humans can be quite amusing, they can also have a darker side.

Just this past year, Microsoft released a chatbot named Tay on Twitter. People were invited to ask questions of Tay, and Tay would respond in accordance with her “personality”. Microsoft had apparently programmed the bot to appear to be 19-year-old American girl. She was intended to be your virtual “bestie”; the only problem was she started sounding like she would rather hang with the Nazi youth than you.

As a result of these unbelievably inflammatory tweets, Microsoft was forced to pull Tay off Twitter and issue an apology:

“As many of you know by now, on Wednesday we launched a chatbot called Tay. We are deeply sorry for the unintended offensive and hurtful tweets from Tay, which do not represent who we are or what we stand for, nor how we designed Tay. Tay is now offline and we’ll look to bring Tay back only when we are confident we can better anticipate malicious intent that conflicts with our principles and values.”

-March 25, 2016 Official Microsoft Blog

Clearly, brands that want to release chatbots into the wild in the future should take a lesson from this debacle.

There is no doubt that brands are embracing chatbots. Everyone from Facebook to Taco Bell is getting in on the game.

Witness the TacoBot:

Yes, this is a real thing, and despite the stumbles such as Tay, there is a good chance the future of UI looks a lot like TacoBot. One last example might even help explain why.

Quartz recently launched an app that turns news into a conversation. Rather than lay out the day’s stories as a flat list, you are engaged in a chat as if you were getting news from a friend.

David Gasca, a PM at Twitter, describes his experience using the app in a post on Medium. He describes how the conversational nature invoked feelings that were normally only triggered in human relationships. This is his take on how he felt when he encountered an ad in the app:

“Unlike a simple display ad, in a conversational relationship with my app, I feel like I owe something to it: I want to click. At the most subconscious level, I feel the need to reciprocate and not let the app down: The app has given me this content. It’s been very nice so far and I enjoyed the GIFs. I should probably click since it’s asking nicely.”

If this experience is universal—and I expect that it is—this could be the next big thing in advertising, and have no doubt that advertising profits will drive UI design:

“The more the bot acts like a human, the more it will be treated like a human.”

-Mat Webb, technologist and co-author of Mind Hacks

At this point, you are probably dying to know how these things work, so let’s get on with it!

The design of chatbots

The original ELIZA application was two-hundred odd lines of code. The Python NLTK implementation is similarly short. An excerpt can be seen at the following link from NLTK’s website (http://www.nltk.org/_modules/nltk/chat/eliza.html). I have also reproduced an except below:

# Natural Language Toolkit: Eliza
#
# Copyright (C) 2001-2016 NLTK Project
# Authors: Steven Bird <[email protected]>
# Edward Loper <[email protected]>
# URL: <http://nltk.org/>
# For license information, see LICENSE.TXT
# Based on an Eliza implementation by Joe Strout
<[email protected]>,
# Jeff Epler <[email protected]> and Jez Higgins
<mailto:[email protected]>.
# a translation table used to convert things you say into
things the
# computer says back, e.g. "I am" --> "you are"
from   future 
import print_function
# a table of response pairs, where each pair consists of a
# regular expression, and a list of possible responses,
# with group-macros labelled as %1, %2.
pairs = ((r'I need (.*)',("Why do you need %1?", "Would it
really help you to get %1?","Are you sure you need
%1?")),(r'Why don't you (.*)',
("Do you really think I don't %1?","Perhaps eventually
I will %1.","Do you really want me to %1?")),
[snip](r'(.*)?',("Why do you ask that?", "Please consider
whether you can answer your own question.",
"Perhaps the answer lies within yourself?",
"Why don't you tell me?")),
(r'quit',("Thank you for talking with me.","Good-bye.", "Thank you, that will be $150.	Have a good day!")), (r'(.*)',("Please tell me more.","Let's change focus a bit...
Tell me about your family.","Can you elaborate on that?","Why do you say that %1?","I see.",
"Very interesting.","%1.","I see.	And what does that tell you?","How does that make you feel?",
"How do you feel when you say that?"))
)
eliza_chatbot = Chat(pairs, reflections)
def eliza_chat():
print("Therapistn---------")
print("Talk to the program by typing in plain English,
using normal upper-")
print('and lower-case letters and punctuation. Enter "quit"
when done.')
print('='*72)
print("Hello.	How are you feeling today?")
eliza_chatbot.converse()
def demo():
eliza_chat()
if   name 	
demo()
== "  main ":

As you can see from this code, input text was parsed and then matched against a series of regular expressions. Once the input was matched, a randomized response (that sometimes echoed back a portion of the input) was returned. So, something such as I need a taco would trigger a response of Would it really help you to get a taco? Obviously, the answer is yes, and fortunately, we have advanced to the point that technology can provide one to you (bless you, TacoBot), but this was still in the early days. Shockingly, some people did actually believe ELIZA was a real human.

However, what about more advanced bots? How are they constructed?

Surprisingly, most of the chatbots that you’re likely to encounter don’t even use machine learning; they use what’s known as retrieval-based models. This means responses are predefined according to the question and the context. The most common architecture for these bots is something called Artificial Intelligence Markup Language (AIML). AIML is

an XML-based schema to represent how the bot should interact to the user’s input. It’s really just a more advanced version of how ELIZA works.

Let’s take a look at how responses are generated using AIML. First, all inputs are preprocessed to normalize them. This means when you input “Waaazzup???”, it is mapped to “WHAT IS UP”. This preprocessing step funnels down the myriad ways of saying the same thing into one input that can run against a single rule. Punctuation and other extraneous inputs are removed as well at this point. Once this is complete, the input is

matched against the appropriate rule. The following is a sample template:

<category>
<pattern>WHAT  IS  UP</pattern>
<template>The  sky,  duh.  Pfft.  Humans...</template>
</category>

This is the basic setup, but you can also layer in wildcards, randomization, and prioritization schemes. For example, the following pattern uses wildcard matching:

<category>
<pattern>*  FOR  ME<pattern>
<template>I'm  a  bot.  I  don't  <star/>.  Ever.</template>
</category>

Here, the *wildcard matches one or more words before FORMEand then repeats these back in the output template. If the user were to type in Danceforme!, the response would be, I’m a bot. I don’t dance. Ever.

As you can see, these rules don’t make for anything that approximates any type of real intelligence, but there are a few tricks that strengthen the illusion. One of the better ones is the ability to generate responses conditioned to a topic.

For example, here is a rule that invokes a topic:

<category>
<pattern>I  LIKE  TURTLES</pattern>
<template>I  feel  like  this  whole  <set  name="topic">turle</set>  thing could	be  a  problem.  What  do  you  like  about  them?  </template>
</category>

Once the topic is set, then the rules specific to that context can be matched:

<topic  name="turtles">

<category>
<pattern>*  SHELL  IS  *</pattern>
<template>I  dislike  turtles  primarily  because  of  their  shells.  What other	creepy  things  do  you  like  about  turtles?  </template>
</category>

<category>
<pattern>*  HIDE  *</pattern>
<template>I  wish  like  a  turtle  that  I  could  hide  from  this
conversation.</template>
</category>
</topic>

Let’s see what this interaction looks like:

>I like turtles!

>I feel like this whole turtle thing could be a problem. What do you like about them?

>I like how they hide in their shell

>I wish like a turtle I could hide from this conversation.

You can see that the continuity across the conversation adds a measure of realism.

You probably think that this can’t be state-of-the-art in this age of deep learning, and you’re right. While most bots are rule-based, the next generation of chatbots are emerging, and they are based on neural networks.

In 2015, Oriol Vinyas and Quoc Le of Google published a paper (http://arxiv.org/pdf/1506.05869v1.pdf), which described the construction of a neural network, based on

sequence-to-sequence models. This type of model maps an input sequence, such as “ABC”, to an output sequence, such as “XYZ”. These inputs and outputs can be translations from one language to another for example. However, in the case of their work here, the training data was not language translation, but rather tech support transcripts and movie dialog. While the results from both models are both interesting, it was the interactions that were based on movie model that stole the headlines.

The following are sample interactions taken from the paper:

None of this was explicitly encoded by humans or present in a training set as asked, and yet, looking at this is, it is frighteningly like speaking with a human. However, let’s see more…

Note that the model responds with what appears to be knowledge of gender (he, she), of place (England), and career (player). Even questions of meaning, ethics, and morality are fair game:

The conversation continues:

If this transcript doesn’t give you a slight chill of fear for the future, there’s a chance you may already be some sort of AI.

I wholeheartedly recommend reading the entire paper. It isn’t overly technical, and it will definitely give you a glimpse of where this technology is headed.

We talked a lot about the history, types, and design of chatbots, but let’s now move on to building our own!

Building a chatbot

Now, having seen what is possible in terms of chatbots, you most likely want to build the best, most state-of-the-art, Google-level bot out there, right? Well, just put that out of your mind right now because we will do just the opposite! We will build the best, most awful bot ever!

Let me tell you why. Building a chatbot comparable to what Google built takes some serious hardware and time. You aren’t going to whip up a model on your MacBook Pro that takes anything less than a month or two to run with any type of real training set. This means that you will have to rent some time on an AWS box, and not just any box. This box will need to have some heavy-duty specs and preferably be GPU-enabled. You are more than welcome to attempt such a thing. However, if your goal is just to build something very cool and engaging, I have you covered here.

I should also warn you in advance, although Cleverbot is no Tay, the conversations can get a bit salty. If you are easily offended, you may want to find a different training set.

Ok, let’s get started!

First, as always, we need training data. Again, as always, this is the most challenging step in the process. Fortunately, I have come across an amazing repository of conversational data. The notsocleverbot.com site has people submit the most absurd conversations they have with Cleverbot. How can you ask for a better training set?

Let’s take a look at a sample conversation between Cleverbot and a user from the site:

So, this is where we’ll begin. We’ll need to download the transcripts from the site to get started:

You’ll just need to paste the link into the form on the page. The format will be like the following: http://www.notsocleverbot.com/index.php?page=1.

Once this is submitted, the site will process the request and return a page back that looks like the following:

From here, if everything looks right, click on the pink Done button near the top right.

The site will process the page and then bring you to the following page:

Next, click on the Show URL Generator button in the middle:

Next, you can set the range of numbers that you’d like to download from. For example, 1-20, by 1 step. Obviously, the more pages you capture, the better this model will be. However, remember that you are taxing the server, so please be considerate.

Once this is done, click on Add to list and hit Return in the text box, and you should be able to click on Save. It will begin running, and when it is complete, you will be able to download the data as a CSV file.

Next, we’ll use our Jupyter notebook to examine and process the data. We’ll first import pandasand the Python regular expressions library, re. We will also set the option in pandasto widen our column width so that we can see the data better:

import pandas as pd import re pd.set_option('display.max_colwidth',200)

Now, we’ll load in our data:

df = pd.read_csv('/Users/alexcombs/Downloads/nscb.csv')
df

The preceding code will result in the following output:

As we’re only interested in the first column, the conversation data, we’ll parse this out:

convo = df.iloc[:,0]
convo

The preceding code will result in the following output:

You should be able to make out that we have interactions between User and Cleverbot, and that either can initiate the conversation. To get the data in the format that we need, we’ll have to parse it into question and response pairs. We aren’t necessarily concerned with who says what, but we are concerned with matching up each response to each question. You’ll see why in a bit. Let’s now perform a bit of regular expression magic on the text:

clist = []
def qa_pairs(x):
cpairs = re.findall(": (.*?)(?:$|n)", x)
clist.extend(list(zip(cpairs, cpairs[1:])))
convo.map(qa_pairs);
convo_frame = pd.Series(dict(clist)).to_frame().reset_index()
convo_frame.columns = ['q', 'a']

The preceding code results in the following output:

Okay, there’s a lot of code there. What just happened? We first created a list to hold our question and response tuples. We then passed our conversations through a function to split them into these pairs using regular expressions.

Finally, we set it all into a pandas DataFramewith columns labelled qand a.

We will now apply a bit of algorithm magic to match up the closest question to the one a user inputs:

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity vectorizer = TfidfVectorizer(ngram_range=(1,3))
vec = vectorizer.fit_transform(convo_frame['q'])

What we did in the preceding code was to import our TfidfVectorizationlibrary and the cosine similarity library. We then used our training data to create a tf-idf matrix. We can now use this to transform our own new questions and measure the similarity to existing questions in our training set.

We covered cosine similarity and tf-idf algorithms in detail, so flip back there if you want to understand how these work under the hood.

Let’s now get our similarity scores:

my_q = vectorizer.transform(['Hi. My name is Alex.'])
cs = cosine_similarity(my_q, vec)
rs = pd.Series(cs[0]).sort_values(ascending=0)
top5 = rs.iloc[0:5]
top5

The preceding code results in the following output:

What are we looking at here? This is the cosine similarity between the question I asked and the top five closest questions. To the left is the index and on the right is the cosine similarity. Let’s take a look at these:

convo_frame.iloc[top5.index]['q']

This results in the following output:

As you can see, nothing is exactly the same, but there are definitely some similarities.

Let’s now take a look at the response:

rsi = rs.index[0] rsi convo_frame.iloc[rsi]['a']

The preceding code results in the following output:

Okay, so our bot seems to have an attitude already. Let’s push further.

We’ll create a handy function so that we can test a number of statements easily:

def get_response(q):
my_q = vectorizer.transform([q])
cs = cosine_similarity(my_q, vec)
rs = pd.Series(cs[0]).sort_values(ascending=0)
rsi = rs.index[0]
return convo_frame.iloc[rsi]['a']
get_response('Yes, I am clearly more clever than you will ever be!')

This results in the following output:

We have clearly created a monster, so we’ll continue:

get_response('You are a stupid machine. Why must I prove anything to you?')

This results in the following output:

I’m enjoying this. Let’s keep rolling with it:

get_response('My spirit animal is a menacing cat. What is yours?')

To which I responded:

get_response('I mean I didn't actually name it.')

This results in the following output:

Continuing:

get_response('Do you have a name suggestion?')

This results in the following output:

To which I respond:

get_response('I think it might be a bit aggressive for a kitten')

This results in the following output:

I attempt to calm the situation:

get_response('No need to involve the police.')

This results in the following output:

And finally,

get_response('And I you, Cleverbot')

This results in the following output:

Remarkably, this may be one of the best conversations I’ve had in a while: bot or no bot.

Now that we have created this cake-based intelligence, let’s set it up so that we can actually chat with it via text message.

We’ll need a few things to make this work. The first is a twilio account. They will give you a free account that lets you send and receive text messages.

Go to http://ww.twilio.com and click to sign up for a free developer API key. You’ll set up some login credentials and they will text your phone to confirm your number. Once this is set up, you’ll be able to find the details in their Quickstart documentation. Make sure that you select Python from the drop-down menu in the upper left-hand corner.

Sending messages from Python code is a breeze, but you will need to request a twilio number. This is the number that you will use to send a receive messages in your code. The receiving bit is a little more complicated because it requires that you to have a webserver running. The documentation is succinct, so you shouldn’t have that hard a time getting it set up. You will need to paste a public-facing flask server’s URL in under the area where you manage your twilio numbers. Just click on the number and it will bring you to the spot to paste in your URL:

Once this is all set up, you will just need to make sure that you have your Flask web server up and running. I have condensed all the code here for you to use on your Flask app:

from flask import Flask, request, redirect import twilio.twiml
import pandas as pd import re
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity
app = Flask(  name  )
PATH_TO_CSV = 'your/path/here.csv'
df = pd.read_csv(PATH_TO_CSV)
convo = df.iloc[:,0]
clist = []
def qa_pairs(x):
cpairs = re.findall(": (.*?)(?:$|n)", x)
clist.extend(list(zip(cpairs, cpairs[1:])))
convo.map(qa_pairs);
convo_frame = pd.Series(dict(clist)).to_frame().reset_index()
convo_frame.columns = ['q', 'a']
vectorizer = TfidfVectorizer(ngram_range=(1,3))
vec = vectorizer.fit_transform(convo_frame['q'])
@app.route("/", methods=['GET', 'POST'])
def get_response():
input_str = request.values.get('Body')
def get_response(q):
my_q = vectorizer.transform([input_str])
cs = cosine_similarity(my_q, vec)
rs = pd.Series(cs[0]).sort_values(ascending=0)
rsi = rs.index[0]
return convo_frame.iloc[rsi]['a']
resp = twilio.twiml.Response()
if input_str:
resp.message(get_response(input_str))
return str(resp)
else:
resp.message('Something bad happened here.')
return str(resp)

It looks like there is a lot going on, but essentially we use the same code that we used before, only now we grab the POST data that twilio sends—the text body specifically—rather than the data we hand-entered before into our get_requestfunction.

If all goes as planned, you should have your very own weirdo bestie that you can text anytime, and what could be better than that!

Summary

In this article, we had a full tour of the chatbot landscape. It is clear that we are just on the cusp of an explosion of these sorts of applications. The Conversational UI revolution is just about to begin. Hopefully, this article has inspired you to create your own bot, but if not,

at least perhaps you have a much richer understanding of how these applications work and how they will shape our future.

I’ll let the app say the final words:

get_response("Say goodbye, Clevercake")

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here