Professional Documents
Culture Documents
with Flask
2. Show menu
Final Outcome:
Please click on Full Screen button, and change the quality to HD from
SD to see it clearly.
Flask app demo
Overview:
Creation of embedded_dataset.json:
This is just for having the project up and running, I will explain the
parts one by one deeper into the blog :)
1. Install Pre-requisites
3. Prepare dataset
Run data_embedder.py This will take the dataset.json file and convert
all the sentences to FastText Vectors.
> python data_embedder.py
feedback docs will be inserted when a user gives a feedback so that the
restaurant authority can read them and take necessary action.
Implementation:
For example,
The user sends a message: “Please show me the vegetarian items on the
menu?”
1. Building Dataset
The dataset is a JSON file with three fields: tag, patterns, response,
where we record a few possible messages with that intent, and some
possible responses. For some of the intents, the responses are left
empty, because they would require further action to determine the
response. For example, for a query, “Are there any offers going on?”
The bot would first have to check in the database if any offers are active
and then respond accordingly.
2. Normalising messages
2. Remove punctuation
'''
Since the dataset is small, using NLTK stop words stripped it off many words that were
important for this context
So I wrote a small script to get words and their frequencies in the whole document, and manually
selected
inconsequential words to make this list
'''
stop_words = ['the', 'you', 'i', 'are', 'is', 'a', 'me', 'to', 'can', 'this', 'your', 'have', 'any', 'of', 'we', 'very',
'could', 'please', 'it', 'with', 'here', 'if', 'my', 'am']
def lemmatize_sentence(tokens):
lemmatizer = WordNetLemmatizer()
lemmatized_tokens = [lemmatizer.lemmatize(word) for word in tokens]
return lemmatized_tokens
def tokenize_and_remove_punctuation(sentence):
tokenizer = RegexpTokenizer(r'\w+')
tokens = tokenizer.tokenize(sentence)
return tokens
def remove_stopwords(word_tokens):
filtered_tokens = []
for w in word_tokens:
if w not in stop_words:
filtered_tokens.append(w)
return filtered_tokens
'''
Convert to lower case,
remove punctuation
lemmatize
'''
def preprocess_main(sent):
sent = sent.lower()
tokens = tokenize_and_remove_punctuation(sent)
lemmatized_tokens = lemmatize_sentence(tokens)
orig = lemmatized_tokens
filtered_tokens = remove_stopwords(lemmatized_tokens)
if len(filtered_tokens) == 0:
# if stop word removal removes everything, don't do it
filtered_tokens = orig
normalized_sent = " ".join(filtered_tokens)
return normalized_sent
3. Sentence Embedding:
After embedding the sentences in the dataset, I wrote them back into a
json file called embedded_dataset.json and keep it for later use while
running the chatbot.
3. Intent Classification:
Intuition:
In our case, we have 18 intents that demand 18 different kinds of
responses.
I made a small dataset, with a few example messages for each of the 18
intents. Intuitively, all these messages, when converted to vectors with
a word embedding model (I have used pre-trained FastText English
model), and represented on a 2-D space should lie close to each other.
import codecs
import json
import numpy as np
import data_embedder
import sentence_normalizer
ft_model = data_embedder.load_embedding_model()
def normalize(vec):
norm = np.linalg.norm(vec)
return norm
scores = []
intent_flag = 0
tie_flag = 0
for pattern in intent['patterns']:
pattern = np.array(pattern)
similarity = cosine_similarity(pattern, input_vec)
similarity = round(similarity, 6)
scores.append(similarity)
# if exact match is found, then no need to check any further
if similarity == 1.000000:
intent_flag = 1
break_flag = 1
# no need to check any more sentences in this intent
break
elif similarity > max_sim_score:
max_sim_score = similarity
intent_flag = 1
# if a sentence in this intent has same similarity as the max and this max is from a
previous intent,
# that means there is a tie between this intent and some previous intent
elif similarity == max_sim_score and intent_flag == 0:
tie_flag = 1
'''
If tie occurs check which intent has max top 4 average
top 4 is taken because even without same intent there are often different ways of expressing
the same intent,
which are vector-wise less similar to each other.
Taking an average of all of them, reduced the score of those clusters
'''
if tie_flag == 1:
scores.sort()
top = scores[:min(4, len(scores))]
intent_score_avg = np.mean(top)
if intent_score_avg > max_score_avg:
max_score_avg = intent_score_avg
intent_flag = 1
if intent_flag == 1:
max_sim_intent = intent['tag']
# if exact match was found in this intent, then break 'cause we don't have to iterate through
anymore intents
if break_flag == 1:
break
if break_flag != 1 and ((tie_flag == 1 and intent_flag == 1 and max_score_avg < 0.06) or
(intent_flag == 1 and max_sim_score < 0.6)):
max_sim_intent = ""
return max_sim_intent
def classify(input):
input = sentence_normalizer.preprocess_main(input)
input_vec = data_embedder.embed_sentence(input, ft_model)
output_intent = detect_intent(data, input_vec)
return output_intent
if __name__ == '__main__':
input = sentence_normalizer.preprocess_main("hmm")
input_vec = data_embedder.embed_sentence(input, ft_model)
output_intent = detect_intent(data, input_vec)
print(output_intent)
1. menu has columns: item, cost, vegan, veg, about, offer -> app.py
queries into it
2. feedback has columns: feedback_string, type -> docs are inserted
into it by app.py
import json
import random
import datetime
import pymongo
import uuid
import intent_classifier
seat_count = 50
client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["restaurant"]
menu_collection = db["menu"]
feedback_collection = db["feedback"]
bookings_collection = db["bookings"]
with open("dataset.json") as file:
data = json.load(file)
def get_intent(message):
tag = intent_classifier.classify(message)
return tag
'''
Reduce seat_count variable by 1
Generate and give customer a unique booking ID if seats available
Write the booking_id and time of booking into Collection named bookings in restaurant database
'''
def book_table():
global seat_count
seat_count = seat_count - 1
booking_id = str(uuid.uuid4())
now = datetime.datetime.now()
booking_time = now.strftime("%Y-%m-%d %H:%M:%S")
booking_doc = {"booking_id": booking_id, "booking_time": booking_time}
bookings_collection.insert_one(booking_doc)
return booking_id
def vegan_menu():
query = {"vegan": "Y"}
vegan_doc = menu_collection.find(query)
if vegan_doc.count() > 0:
response = "Vegan options are: "
for x in vegan_doc:
response = response + str(x.get("item")) + " for Rs. " + str(x.get("cost")) + "; "
response = response[:-2] # to remove the last ;
else:
response = "Sorry no vegan options are available"
return response
def veg_menu():
query = {"veg": "Y"}
vegan_doc = menu_collection.find(query)
if vegan_doc.count() > 0:
response = "Vegetarian options are: "
for x in vegan_doc:
response = response + str(x.get("item")) + " for Rs. " + str(x.get("cost")) + "; "
response = response[:-2] # to remove the last ;
else:
response = "Sorry no vegetarian options are available"
return response
def offers():
all_offers = menu_collection.distinct('offer')
if len(all_offers)>0:
response = "The SPECIAL OFFERS are: "
for ofr in all_offers:
docs = menu_collection.find({"offer": ofr})
response = response + ' ' + ofr.upper() + " On: "
for x in docs:
response = response + str(x.get("item")) + " - Rs. " + str(x.get("cost")) + "; "
response = response[:-2] # to remove the last ;
else:
response = "Sorry there are no offers available now."
return response
def suggest():
day = datetime.datetime.now()
day = day.strftime("%A")
if day == "Monday":
response = "Chef recommends: Paneer Grilled Roll, Jade Chicken"
elif day == "Tuesday":
response = "Chef recommends: Tofu Cutlet, Chicken A La King"
def recipe_enquiry(message):
all_foods = menu_collection.distinct('item')
response = ""
for food in all_foods:
query = {"item": food}
food_doc = menu_collection.find(query)[0]
if food.lower() in message.lower():
response = food_doc.get("about")
break
if "" == response:
response = "Sorry please try again with exact spelling of the food item!"
return response
def get_specific_response(tag):
for intent in data['intents']:
if intent['tag'] == tag:
responses = intent['responses']
response = random.choice(responses)
return response
def show_menu():
all_items = menu_collection.distinct('item')
response = ', '.join(all_items)
return response
def generate_response(message):
global seat_count
tag = get_intent(message)
response = ""
if tag != "":
if tag == "book_table":
if seat_count > 0:
booking_id = book_table()
response = "Your table has been booked successfully. Please show this Booking ID at
the counter: " + str(
booking_id)
else:
response = "Sorry we are sold out now!"
We will be using AJAX for asynchronous transfer of data i.e you won’t
have to reload your webpage every time you send an input to the
model. The web application will respond to your inputs seamlessly.
Let’s take a look at the HTML file.
The latest Flask is threaded by default, so if different users chat at the
same time, the unique IDs will be unique across all instances, and
common variables like seat_count will be shared.
In the JavaScript section we get the input from the user, send it to the
“app.py” file where we generate response and then receive the output
back to display it on the app.
<!DOCTYPE
html>
<html>
<title>Restaurant Chatbot</title>
<head>
<link rel="icon" href="">
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<style>
body {
font-family: monospace;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
}
h2 {
background-color: white;
border: 2px solid black;
border-radius: 5px;
color: #03989E;
display: inline-block;Helvetica
margin: 5px;
padding: 5px;
}
h4{
position: center;
}
#chatbox {
margin-top: 10px;
margin-bottom: 60px;
margin-left: auto;
margin-right: auto;
width: 40%;
height: 40%
position:fixed;
}
#userInput {
margin-left: auto;
margin-right: auto;
width: 40%;
margin-top: 60px;
}
#textInput {
width: 90%;
border: none;
border-bottom: 3px solid black;
font-family: 'Helvetica';
font-size: 17px;
}
.userText {
width:fit-content; width:-webkit-fit-content; width:-moz-fit-content;
color: white;
background-color: #FF9351;
font-family: 'Helvetica';
font-size: 12px;
margin-left: auto;
margin-right: 0;
line-height: 20px;
border-radius: 5px;
text-align: left;
}
.userText span {
padding:10px;
border-radius: 5px;
}
.botText {
margin-left: 0;
margin-right: auto;
width:fit-content; width:-webkit-fit-content; width:-moz-fit-content;
color: white;
background-color: #00C2CB;
font-family: 'Helvetica';
font-size: 12px;
line-height: 20px;
text-align: left;
border-radius: 5px;
}
.botText span {
padding: 10px;
border-radius: 5px;
}
.boxed {
margin-left: auto;
margin-right: auto;
width: 100%;
border-radius: 5px;
}
input[type=text] {
bottom: 0;
width: 40%;
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
position: fixed;
border-radius: 5px;
}
</style>
</head>
<body background="{{ url_for('static', filename='images/slider.jpg') }}">
<img />
<center>
<h2>
Welcome to Aindri's Restro
</h2>
<h4>
You are chatting with our customer support bot!
</h4>
</center>
<div class="boxed">
<div>
<div id="chatbox">
</div>
</div>
<div id="userInput">
<input id="nameInput" type="text" name="msg" placeholder="Ask me
anything..." />
</div>
<script>
function getBotResponse() {
var rawText = $("#nameInput").val();
var userHtml = '<p class="userText"><span><b>' + "You : " + '</b>' +
rawText + "</span></p>";
$("#nameInput").val("");
$("#chatbox").append(userHtml);
document
.getElementById("userInput")
.scrollIntoView({ block: "start", behavior: "smooth" });
$.get("/get", { msg: rawText }).done(function(data) {
var botHtml = '<p class="botText"><span><b>' + "Restrobot : " + '</b>' +
data + "</span></p>";
$("#chatbox").append(botHtml);
document
.getElementById("userInput")
.scrollIntoView({ block: "start", behavior: "smooth" });
});
}
$("#nameInput").keypress(function(e) {
if (e.which == 13) {
getBotResponse();
}
});
</script>
</div>
</body>
</html>
from flask
import Flask,
render_template,
request, jsonify
import response_generator
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/get')
def get_bot_response():
message = request.args.get('msg')
response = ""
if message:
response =
response_generator.generate_response(message)
return str(response)
else:
return "Missing Data!"
if __name__ == "__main__":
app.run()