• HOME
  • VIDEOS
  • ARTICLES
  • HOW-TO
  • INTERVIEWS
  • ABOUT
instant | How to program a chatbot
 How-to

How to program a chatbot

John Doxaras

2018-09-10 11:25:54.616537

 

Let's code a chatbot!

In [1]:
from IPython.display import Image
Image("/Users/vlad/Desktop/ChatBot/Image_2_rasa.png", width = 1000, height = 1000)
Out[1]:
 

Chatbots are increasingly transforming the way we interact with software. They provide a great business opportunity for both, small and large companies. Their purpose is to support and scale business teams in their relations with customers. Doing this helps businesses save a lot of money which is why many business owners are adopting this technology.

But first, what is a chatbot? Chatbots can be definied as software agents that converse through a chat interface. What that means, is that they are software programs that are able to have a human-like conversation, which provides some kind of value to the end user. The user can interact with the chatbot by typing in a question, a request or simply by using their voice, depending on the chatbot's providor. Virtual assistants, like Apple's Siri or Amazon's Alexa, are two examples of widly adopted chatbots that interact by voice rather than by text. Typically, the chatbot will greet the user and then invite him to to ask some kind of question. When the user replies, the chatbot will parse the input and figure out what is the intention of the user's request. The chatbot then, will respond in some kind of consequential or logical manner, either by providing, relevant to the user's question, information or by asking the user for further details before ultimately answering his question. Great chatbots can keep up with this conversational workflow back and forth in a natural way, within the scope of what the chatbot is designed to do.

 

According to the way they are designed, chatbots can be devided into two categories: the "smart" bots and the "dumb" ones. The “dumb” bots behave according to a set of rules. They are preprogrammed to act in a certain way and can’t “think outside the box”. They scan the user's input for keywords or commands and can’t respond to anything else.

 

The second type of chatbots is built using more advanced technologies, such as Artificial Intelligence, Machine Learning (ML), and Named Entity Recognition (NER). “Smart” bots learn from conversations with humans and become more and more advanced as time goes by. They can not only respond to a list of predetermined commands but also understand organic human speech.

 

Such bots can serve as personal assistants or even digital friends. They shine in operations that require extensive search, complex calculations or picking similar objects among a great mass of things.

 

Building a conversational chatbot with RASA

In [24]:
from IPython.display import Image
Image("/Users/vlad/Desktop/ChatBot/Image_1_rasa.png", width = 1000, height = 1000)
Out[24]:
 

In this blog post, you will learn how to build a simple weather chatbot that relies on natural language processing and machine learning without any special coding skills or machine learning knowledge. We will use a pair of open source, highly scalable, python libraries for building conversational software, Rasa NLU and Rasa Core. Both libraries hold to a high standard of professionalism, and this extends to implementations of machine learning algorithms. They aim to bridge the gap between research and application, bringing recent advances in machine learning to non-experts who want to implement conversational AI systems.

One core advantage of Rasa NLU and Rasa Core over other similar systems, is that it allows you to build a working conversational system with a minimum amount of training data.

 

Rasa Architecture

 

The RASA platform is based on two fundamental components:

  • Rasa NLU: Natural Language Understanding: An open source stack for intent classification and entity extraction. Every time a message is received, the system passes the message to RASA NLU to extract intents, entities, and any other structured information. The intents, entities and contexts are passed to the dialogue engine.
 
  • Rasa Core: Dialogue Engine: This is the flow control engine of the RASA platform. Fundamentally, RASA core processes intents, entities or any context generated by RASA NLU and decides what actions to take. Those actions are not based on prescriptive instructions coded in flow chart but on probabilistic models that allow the conversations to evolve organically without requiring constant modifications. Developers can enrich dialogs by creating custom interprets, policies or other highly sophisticated elements included in the RASA Core stack.
 

Install dependencies

 

To make everything work, we have first to install three packages: RASA NLU, RASA CORE and Apixu package for weather prediction:

  • pip install rasa_nlu
  • pip install rasa-core
  • pip install git+https://github.com/apixu/apixu-python.git (Unfortunately, apixu it's not hosted on PyPI.)
In [13]:
#for some basic configurations
%matplotlib inline

import logging, io, json, warnings
logging.basicConfig(level="INFO")
warnings.filterwarnings('ignore')
 

Set up the Domain

 

The Domain is a yml file that defines the universe in which our bot operates.

In [82]:
weather_domain = """

entities:
 - location

slots:
  location:
    type: text

intents:
 - greet
 - inform
 - deny

templates:
  utter_greet:
    - 'Hello! How can I help you?'
    - 'Hi there! How can I help you?'
  utter_goodbye:
    - 'Talk later then!'
    - 'Bye Bye then!'
    - 'Cu next time then:)'
    - 'Okay then, bye bye!'
  utter_ask_location:
    - 'What location would you like to check the weather?'
  utter_ask_for_anything_else:
    - "What else can I do for you?"

actions:
 - utter_greet
 - utter_goodbye
 - utter_ask_location
 - utter_ask_for_anything_else
 - __main__.WeatherAction
 
""" 

%store weather_domain > weather_domain.yml
 
Writing 'weather_domain' (str) to file 'weather_domain.yml'.
 

The domain contains the following RASA components:

  • Intents: (usually) are the supported actions of the bot
  • Entities: are the tokens we want the bot to extract from each text message
  • Slots: the entities we want our bot to track through the dialogue
  • Templates: define the actual text responses used by the dialogue engine. The engine will pick one random response
  • Actions: define the responses of our bot. Simple text response names that start with utter_. It can contain custom actions that are definied as classes which run arbitrary code. Such a custom action in our code is the WeatherAction class with makes a connection to apixu api and returns us the weather in a specific location that we asked our bot from
 

Create stories for the dialogue engine

 

Next, we create a markdown file which contains some basic stories for our chatbot. For more information about Rasa stories check out the official RASA documenation: http://rasa.com/docs/core/stories/.

In [83]:
weather_stories = """

## story_01
* greet
    - utter_greet
* inform
    - utter_ask_location
* inform{"location": "Athens"}
    - slot{"location": "Athens"}
    - action_weather
    - utter_ask_for_anything_else
* deny
    - utter_goodbye

## story_02
* greet
    - utter_greet
* inform
    - utter_ask_location
* inform{"location": "Crete"}
    - slot{"location": "Crete"}
    - action_weather
    - utter_ask_for_anything_else
* deny
    - utter_goodbye
    
## story_03
* greet
    - utter_greet
* inform
    - utter_ask_location
* inform{"location": "Thessaloniki"}
    - slot{"location": "Thessaloniki"}
    - action_weather
    - utter_ask_for_anything_else
    - utter_ask_for_anything_else
* deny
    - utter_goodbye
    
## story_04
* greet
    - utter_greet
* inform
    - utter_ask_location
* inform{"location": "Crete"}
    - slot{"location": "Crete"}
    - action_weather
    - utter_ask_for_anything_else
* inform{"location": "Bucharest"}
    - slot{"location": "Bucharest"}
    - action_weather
    - utter_ask_for_anything_else
* deny
    - utter_goodbye

"""

%store weather_stories > weather_stories.md
 
Writing 'weather_stories' (str) to file 'weather_stories.md'.
 

Create NLU Data For Intent Recognition

 

Both Rasa NLU and Core work with human-readable training data formats. Rasa NLU requires a list of utterances annotated with intents and entities. These can be specified either in a json structure or in a markdown format.

In our blog post, we use a markdown format which is compact and quite simple to work with. We could also use a JSON structure instead, which is slightly more cumbersome to read, but not whitespace sensitive and more suitable for transmission of training data between applications and servers.

For more information regarding the training data format you can check the documentation on: http://rasa.com/docs/nlu/master/dataformat/.

In [84]:
weather_nlu = """

## intent:greet
- hey
- hello
- hi
- good morning
- good evening
- hey there

## intent: inform
- I would like to know the weather
- I want to know the weather
- What's the weather?
- I would like to know the weather in [Athens](location)
- What's the weather in [Athens](location)
- What is the weather in [Athens](location)
- I want to know the weather in [Athens](location)
- I would like to know the weather in [Thessaloniki](location)
- I would like to know the weather in [Crete](location)
- I would like to know the weather in [Rome](location)
- I would like to know the weather in [Bucharest](location)

## intent: deny
- Nothing
- Nothing for this time
- I am okay, thanks
- Nothing, thanks

"""
%store weather_nlu > weather_nlu.md
 
Writing 'weather_nlu' (str) to file 'weather_nlu.md'.
 

Connect to the Apixu API

 

In the next step we will make a connection to the Apixu API and get the weather in Athens, just to check if everything works fine.

In [5]:
from apixu.client import ApixuClient, ApixuException

api_key = 'ac778d7ea3f7492e99290419180309'
client = ApixuClient(api_key)
current = client.getCurrentWeather(q='Athens')
 

We get the following JSON file wich contains all the needed information.

In [ ]:
#create a function to print JSON formats in a eye-appealing format
def pprint(o):
    # small helper to make dict dumps a bit prettier
    print(json.dumps(o, indent=2))
In [15]:
pprint(current)
 
{
  "location": {
    "name": "Athens",
    "region": "Attica",
    "country": "Greece",
    "lat": 37.98,
    "lon": 23.72,
    "tz_id": "Europe/Athens",
    "localtime_epoch": 1536088351,
    "localtime": "2018-09-04 22:12"
  },
  "current": {
    "last_updated_epoch": 1536087610,
    "last_updated": "2018-09-04 22:00",
    "temp_c": 27.0,
    "temp_f": 80.6,
    "is_day": 0,
    "condition": {
      "text": "Clear",
      "icon": "//cdn.apixu.com/weather/64x64/night/113.png",
      "code": 1000
    },
    "wind_mph": 6.9,
    "wind_kph": 11.2,
    "wind_degree": 80,
    "wind_dir": "E",
    "pressure_mb": 1009.0,
    "pressure_in": 30.3,
    "precip_mm": 0.0,
    "precip_in": 0.0,
    "humidity": 62,
    "cloud": 0,
    "feelslike_c": 28.5,
    "feelslike_f": 83.3,
    "vis_km": 10.0,
    "vis_miles": 6.0
  }
}
 

Let's check if the connection works by printing the weather in Athens today!

In [21]:
#get information from JSON file
country = current['location']['country']
city = current['location']['name']
weather_condition = current['current']['condition']['text']
temp_feelslike_celsius = current['current']['feelslike_c']
temp_celsius = current['current']['temp_c']
wind_kph = current['current']['wind_kph']
humidity = current['current']['humidity']

#quick check
print('It is {0} in {1} today. The temperature is {2} degrees celsius.'.format(weather_condition,city,temp_celsius))
 
It is Clear in Athens today. The temperature is 27.0 degrees celsius
 

Define the custom action

 

Now, we are ready to define our custom action class. Note that we also define the WeatherAction class in our domain file, inside the "actions" compartment. In case you forget to put it there, your chatbot will not work.

In [85]:
#import libraries
from  __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

from rasa_core.actions.action import Action
from rasa_core.events import SlotSet
In [86]:
#define the custom action 
class WeatherAction(Action):

    def name(self):
        return 'action_weather'
    
    def run(self,dispatcher, tracker, domain):

        from apixu.client import ApixuClient
        api_key = "ac778d7ea3f7492e99290419180309"
        client = ApixuClient(api_key)

        location = tracker.get_slot('location')
        
        
        current = client.getCurrentWeather(q=location)
        
        country = current['location']['country']
        city = current['location']['name']
        weather_condition = current['current']['condition']['text']
        temp_feelslike_celsius = current['current']['feelslike_c']
        temp_celsius = current['current']['temp_c']
        wind_kph = current['current']['wind_kph']
        humidity = current['current']['humidity']
        
        response = 'It is {0} in {1} today.The temperature is {2} degrees celsius and it feels like {3} degrees. The humidity is {4}% and the wind is {5} km/h '.format(weather_condition,
                                                                                                                                                                        city,
                                                                                                                                                                        temp_celsius,
                                                                                                                                                                        temp_feelslike_celsius,                                                                                                                                                               humidity,
                                                                                                                                                                        wind_kph)
        dispatcher.utter_message(response)
        return [SlotSet('location',location)]
 

Define the Configuration file

 

As we previously mentioned Rasa NLU is the natural language understanding module. It comprises loosely coupled modules, combining a number of natural language processing and machine learning libraries in a consistent API. The developers aimed for a balance between customisability and ease of use. To this end, there are pre-defined pipelines with sensible defaults which work well for most use cases. One of these libraries is the the spaCy NLP library.

In our example, we used the "nlp_spacy" pipeline to load the spaCy language model, then the "tokenizer_spacy" pipeline to split each sentence (user message) into individial words and the "ner_crf" component to train a conditional random field to recognise the entities in the training data, using the tokens and POS tags as base features. Next, we used the "intent_featurizer_spacy" to represent each sentence into a multidimentional euclidean space, and finally the "intent_classifier_sklearn" for our text classification task.

In [87]:
weathermodel_config = """
language: "en"

pipeline:
- name: "nlp_spacy"                   # loads the spacy language model
- name: "tokenizer_spacy"             # splits the sentence into tokens
- name: "ner_crf"                     # uses the pretrained spacy NER model
- name: "intent_featurizer_spacy"     # transform the sentence into a vector representation
- name: "intent_classifier_sklearn"   # uses the vector representation to classify using SVM
""" 

%store weathermodel_config > weathermodel_config.yml
 
Writing 'weathermodel_config' (str) to file 'weathermodel_config.yml'.
 

Train the model

 

Now, we are finally ready to train our model!

In [88]:
from rasa_nlu.training_data import load_data
from rasa_nlu.config import RasaNLUModelConfig
from rasa_nlu.model import Trainer
from rasa_nlu import config

# loading the nlu training samples
training_data = load_data("weather_nlu.md")
In [89]:
# trainer to educate our pipeline
trainer = Trainer(config.load("weathermodel_config.yml"))
# train the model!
interpreter = trainer.train(training_data)
# store it for future use
model_directory = trainer.persist("./models/nlu", fixed_model_name="current")
 
Fitting 2 folds for each of 6 candidates, totalling 12 fits
 
/Users/vlad/anaconda/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
/Users/vlad/anaconda/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
/Users/vlad/anaconda/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
/Users/vlad/anaconda/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
/Users/vlad/anaconda/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
/Users/vlad/anaconda/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
[Parallel(n_jobs=1)]: Done  12 out of  12 | elapsed:    0.0s finished
 

Evaluate the model on some inputs

 

Once we trained our model, we can play around with by shooting in some messages.

In [115]:
pprint(interpreter.parse("I would like to know the weather"))
 
{
  "intent": {
    "name": "inform",
    "confidence": 0.8378837991420259
  },
  "entities": [],
  "intent_ranking": [
    {
      "name": "inform",
      "confidence": 0.8378837991420259
    },
    {
      "name": "deny",
      "confidence": 0.09604358581164986
    },
    {
      "name": "greet",
      "confidence": 0.06607261504632436
    }
  ],
  "text": "I would like to know the weather"
}
In [116]:
pprint(interpreter.parse("I would like to see the weather in Bucharest"))
 
{
  "intent": {
    "name": "inform",
    "confidence": 0.8421851028978105
  },
  "entities": [
    {
      "start": 35,
      "end": 44,
      "value": "bucharest",
      "entity": "location",
      "confidence": 0.8569844821691686,
      "extractor": "ner_crf"
    }
  ],
  "intent_ranking": [
    {
      "name": "inform",
      "confidence": 0.8421851028978105
    },
    {
      "name": "deny",
      "confidence": 0.09869753774469882
    },
    {
      "name": "greet",
      "confidence": 0.05911735935749066
    }
  ],
  "text": "I would like to see the weather in Bucharest"
}
 

Train model cont.

In [ ]:
from rasa_core.policies import FallbackPolicy, KerasPolicy, MemoizationPolicy
from rasa_core.agent import Agent

agent = Agent('weather_domain.yml', policies=[MemoizationPolicy(), KerasPolicy()])

# loading our neatly defined training dialogues
training_data = agent.load_data('weather_stories.md')

agent.train(
    training_data,
    validation_split=0.1,
    epochs=2000,
    augmentation_factor = 50
)

agent.persist('models/dialogue')
 

Now it’s time to enjoy the fruit of our hard work by having a conversation with our bot! Below you can see a simple example of a possible discussion with our bot.

In [91]:
#ignore some warnings
import warnings
import ruamel.yaml 
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)

from rasa_core.agent import Agent
agent = Agent.load('models/dialogue', interpreter = model_directory)

print("Your bot is ready to talk! Type your messages here or send 'stop'")
while True:
    a = input()
    if a == 'stop':
        break
    responses = agent.handle_message(a)
    for response in responses:
        print(response["text"])
 
Your bot is ready to talk! Type your messages here or send 'stop'
hello
Hello! How can I help you?
I would like to know the weather
What location would you like to check the weather?
I would like to check the weather in Athens
It is Sunny in Athens today.The temperature is 35.0 degrees celsius and it feels like 34.2 degrees. The humidity is 25% and the wind is 15.1 km/h 
What else can I do for you?
I would like to know the weather in Bucharest
It is Sunny in Bucharest today.The temperature is 31.0 degrees celsius and it feels like 29.0 degrees. The humidity is 22% and the wind is 19.1 km/h 
What else can I do for you?
I am okay for now, nothing
Cu next time then:)
stop
 

Visualize the stories

 

Rasa Core also has the capability to visualise a graph of training dialogues. A story graph is a directed graph with actions as nodes. Edges are labeled with the user utterances that occur in between the execution of two actions. If there is no user interaction between two consecutive actions, the edge label is omitted. Each graph has an initial node called START and a terminal node called END. Note that the graph does not capture the full dialogue state and not all possible walks along the edges necessarily occur in the training set. To simplify the visualization a heuristic is used to merge similar nodes.

You can see the story graph of our training dialogues in the image below.

In [92]:
from IPython.display import Image
from rasa_core.agent import Agent

agent = Agent('weather_domain.yml')
agent.visualize("weather_stories.md", "weather_stories.png", max_history = 2)
Image(filename="weather_stories.png")
 
Processed Story Blocks:   0%|          | 0/4 [00:00<?, ?it/s]
Processed Story Blocks:   0%|          | 0/4 [00:00<?, ?it/s, # trackers=1]
Processed Story Blocks:   0%|          | 0/4 [00:00<?, ?it/s, # trackers=1]
Processed Story Blocks:   0%|          | 0/4 [00:00<?, ?it/s, # trackers=1]
Processed Story Blocks:   0%|          | 0/4 [00:00<?, ?it/s, # trackers=1]
Processed Story Blocks: 100%|██████████| 4/4 [00:00<00:00, 179.86it/s, # trackers=1]
Out[92]:
 

Model Evaluation

 

We can also evaluate the accuarcy of our model by generating the classification report and by plotting the relevant confusion matrix.

In [ ]:
from rasa_core.evaluate import run_story_evaluation

run_story_evaluation("weather_stories.md", "models/dialogue", 
                     nlu_model_path=None, 
                     max_stories=None, 
                     out_file_plot="stories_evaluation.pdf")
 

Machine Teaching

 

In addition to supervised learning, Rasa Core supports a machine teaching approach where developers correct actions made by the system. It is a practical approach for generating training data, and exploring the space of plausible conversations efficiently.

In order to learn how this functionality works, you can run the cell and play with it.

In [ ]:
from rasa_core import utils
from rasa_core.channels.console import ConsoleInputChannel
from rasa_core.interpreter import RegexInterpreter

logger = logging.getLogger(__name__)

def run_concertbot_online(input_channel, interpreter,
                          domain_file="weather_domain.yml",
                          training_data_file='weather_stories.md'):
    agent = Agent(domain_file,
                  policies=[MemoizationPolicy(max_history=2), KerasPolicy()],
                  interpreter=interpreter)

    training_data = agent.load_data(training_data_file)
    
    agent.train_online(training_data,
                       input_channel=input_channel,
                      # batch_size=50,
                       epochs=2000)
                       #max_training_samples=30)

    return agent


if __name__ == '__main__':
    utils.configure_colored_logging(loglevel="INFO")
    run_concertbot_online(ConsoleInputChannel(), RegexInterpreter())
 

Conclusions

 

Chatbots are gaining more popularity than ever and they are totally bringing new ways of how businesses run marketing. They have become the latest addition to every marketer’s bag of strategies, as being an early adopter can give you major advantage from customer support to lead generation.

Despite the hype, the chatbot development faces many constrains and drawbacks. With all the big companies of the world betting so heavily on AI, you would expect chatbots to be more intelligent by now. The truth is-they aren't. Building a bot that truly understands the depth and context of a conversation, and one that is capable enough to hold its ground in a long conversation has proved to be a challenging task. You may ask a bot 100 different questions and it will answer. But that is not a long conversation, is it? That is you trying to have 100 micro-conversations with a bot. Having 100 micro-conversations is not the same as having one long, meaningful conversation! For chatbots to go truly mainstream, we need to start having bots that can give the same kind of experience we get while talking to a friend. Unless we are able to enrichen the “conversational” aspect of the bot, the engagement will always be on the low side. And if the engagement is on the low side, a chatbot will become just a marketing dissemination tool, and not a growth driver — which it can potentially be.

A simple chatbot is not a challenging task as compared to complex chatbots. If you are planning to build a complex chatbot, you should seriously consider stability, scalability and flexibility aspects. If you don’t pay enough attention to the intricacies of the human language, a conversation can quickly go off the rails. You may be either required to build your own solution from scratch or use a combination of a tools for solving general NLP problems (i.e RASA) plus custom server side logic for more powerful features.

Just like building any other type of software product, building a chatbot is a continuous process. Developing a good chatbot is a lot of work. It requires everyone undertaking the project to challenge some of the misconception people commonly have around the chatbot technology. It also poses some development challenges that are unique to ML-enabled applications. Finally, developing a chatbot will require a great deal of planning. This planning involves everything from chatbot and auxiliary tools architecture to its integration with existing processes to strategies for making users accustomed to the chatbot faster.

Despite all its limitations and drawbacks, its definitelly worth trying to build and add an enterprise chatbots platform to your business.

Related Articles
 How-to
CLV the right way using probabilistic programming

John Doxaras

2019-05-31 12:52:41.441911

 How-to
Goal Setting Plan for Application Pre-launch Checklist

Neoklis Athanasiou

2019-01-21 12:49:52.633478

 How-to
All mobile data is credit data: the unbanked problem

John Doxaras

2018-10-19 08:22:01.419442

 How-to
Minimum Viable Data Product Development

John Doxaras

2018-09-03 13:50:05.468625

VIDEOS
ARTICLES
HOW TO
ABOUT

SEE ALL OUR NEWSLETTER

© 2018 Instant Network. All rights reserved. Powered by Warply

Terms of Service