Skip to content

Commit

Permalink
✨ Add Basic GUI and Basic Chatbot
Browse files Browse the repository at this point in the history
  • Loading branch information
EssamWisam committed Jul 2, 2023
1 parent 1c75541 commit 58bdd77
Show file tree
Hide file tree
Showing 18 changed files with 639 additions and 97 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ model.pt
botive.egg-info/
dist/
build/
botive.egg-info
1 change: 1 addition & 0 deletions botiverse/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Entry point for the botiverse package."""
from botiverse.basic_chatbot.basic_chatbot import basic_chatbot
from botiverse.TODS.TODS import TODS
from botiverse.gui.gui import chat_gui
#from botiverse.TODS.DNN_DST.DNN_DST import DNNDST
#from botiverse.TODS.DNN_TODS import DNNTODS
231 changes: 211 additions & 20 deletions botiverse/basic_chatbot/basic_chatbot.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,196 @@
import numpy as np
import json
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader, dataloader
from gensim.utils import tokenize
from nltk.stem.porter import PorterStemmer
stemmer = PorterStemmer()
import numpy as np

from botiverse.models import SVM
from botiverse.models import NeuralNet

import gdown
import os


class basic_chatbot:
"""
Instantiate a basic chat bot model that uses a classic feedforward neural network.
Data can be then used to train the chatbot model.
:param name: The chatbot's name.
:type name: string
"""

def __init__(self, name):
self.name = name
self.model = lambda x: f"My name is {self.name} and I don't know how to respond to that."

def __init__(self, machine='NN', repr='tf-idf'):
"""
Instantiate a basic chat bot model that uses a classic feedforward neural network.
Data can be then used to train the chatbot model.
:param name: The chatbot's name.
:type name: string
"""
self.model = None
self.machine = machine
self.repr = repr
self.glove_dict = None
if repr == 'glove' or 'tf-idf-glove': self.load_glove_vectors()
self.tf = None
self.idf = None
self.classes = None


# Load GLOVE word vectors
def load_glove_vectors(self, force_download=False):
curr_dir = os.path.dirname(os.path.abspath(__file__))
path = curr_dir + '/glove.6B.50d.txt'
if not os.path.exists(path) or force_download:
print("GLoVE embeddings not found. Downloading now...")
f_id = '1BOSO0rR3ZzjWlv5WYzCux6ZluBP_vNDv'
gdown.download(f'https://drive.google.com/uc?export=download&confirm=pbef&id={f_id}', curr_dir + '/glove.6B.50d.txt', quiet=False)
print("Done.")

glove_dict = {} # dictionary mapping words to their GloVe vector representation
with open(path, 'r', encoding='utf-8') as f:
for line in f:
values = line.split()
# In each line, the first value is the word, the rest are the values of the vector
word = values[0]
vector = np.asarray(values[1:], dtype='float32')
glove_dict[word] = vector
self.glove_dict = glove_dict


def setup_glove_vectors(self, sentence_list):
# make a numpy array of the sentence vectors
X = np.zeros((len(sentence_list), 50))
for i, sentence in enumerate(sentence_list):
if self.repr == 'glove':
X[i] = self.get_glove_vector(sentence)
elif self.repr == 'tf-idf-glove':
X[i] = self.get_tf_idf_glove_vector(sentence)

return X

# Calculate average vector
def get_tf_idf_glove_vector(self, sentence):

tokens = list(tokenize(sentence, to_lower=True))
tokens_s = [stemmer.stem(word.lower()) for word in tokens if word not in ['?', '!', '.', ',']]

# get the idf of each word in the sentence
weights = np.zeros((len(tokens)))
for i, word in enumerate(tokens_s):
# if word in self.all_words:
if word in self.all_words and tokens[i] in self.glove_dict:
weights[i] = self.idf[self.all_words.index(word)]

# normalize the weights
weights = weights / np.sum(weights)

# get the weighted average of the glove vectors
avg_vector = np.zeros(50)
for i, word in enumerate(tokens):
if word in self.glove_dict:
avg_vector += weights[i] * self.glove_dict[word]

avg_vector = avg_vector[np.newaxis, :]
return avg_vector

# Calculate average vector
def get_glove_vector(self, sentence):
tokens = list(tokenize(sentence, to_lower=True))
vector_sum = np.zeros(50)
num_glove_tokens = 0 # num of words that occur in glove vocab
for token in tokens:
if token in self.glove_dict:
vector_sum += self.glove_dict[token]
num_glove_tokens += 1

def train(self, data):
avg_vector = np.zeros_like(self.glove_dict['a']) if num_glove_tokens == 0 else vector_sum / num_glove_tokens
avg_vector = avg_vector[np.newaxis, :]
return avg_vector


def setup_tf_idf(self, sentence_list, all_words):
'''
Given a list of tokenized sentences, return a table of tf-idf vectors (one for each sentence)
'''
# Compute the normalized frequency of each word in the document
tf_table = np.zeros((len(sentence_list), len(all_words)), dtype=np.float64)
for i, sentence in enumerate(sentence_list):
sentence = list(tokenize(sentence, to_lower=True))
sentence = [stemmer.stem(word.lower()) for word in sentence if word not in ['?', '!', '.', ',']]
sentence_length = len(sentence)
for word in sentence:
word_index = all_words.index(word)
tf_table[i, word_index] += 1 / sentence_length

# Get the number of documents in which each word appears
df = np.sum(tf_table > 0, axis=0)
N = len(sentence_list)
idf_col = np.log(N/(df+1))

# compute the tf-table by the idf column (gets broadcasted)
self.tf, self.idf = tf_table, idf_col
tfidf_table = tf_table * idf_col

return tfidf_table

def get_tf_idf(self, sentence):
'''
Given a sentence, return its tf-idf vector.
'''
sentence = list(tokenize(sentence, to_lower=True))
sentence = [stemmer.stem(word.lower()) for word in sentence if word not in ['?', '!', '.', ',']]
# compute the tf-idf vector for the prompt
tf_idf = np.zeros((1, len(self.all_words)), dtype=np.float64)
for word in sentence:
# get its tf
if word not in self.all_words: continue
word_index = self.all_words.index(word)
tf_idf[0, word_index] += 1 / len(sentence)
tf_idf *= self.idf
return tf_idf

def setup_data(self):
"""
Given JSON data, set up the data for training by converting it to a list of sentences and their corresponding classes.
"""
all_words = []
classes = []
sentence_list = [] # sentence_table[i] is a tuple (list of words, class)
y = []
for intent in self.raw_data['FAQ']: #this is a list of dictionaries. each has a tag (class), list of patterns and list of responses.
tag = intent['tag']
classes.append(tag)
for pattern in intent['patterns']:
if self.repr == 'tf-idf' or self.repr == 'tf-idf-glove':
all_words += list(tokenize(pattern, to_lower=True))
sentence_list.append(pattern)
y.append(tag)

# stem and lower each word
all_words = [stemmer.stem(word.lower()) for word in all_words if word not in ['?', '!', '.', ',']]

# remove duplicates and sort alphabetically
all_words = sorted(set(all_words))
classes = sorted(set(classes))

self.all_words = all_words
self.classes = classes

if self.repr == 'tf-idf':
X = self.setup_tf_idf(sentence_list, all_words)
elif self.repr == 'glove':
X = self.setup_glove_vectors(sentence_list)
elif self.repr == 'tf-idf-glove':
_ = self.setup_tf_idf(sentence_list, all_words)
X = self.setup_glove_vectors(sentence_list)


# convert each class to its index
for i, tag in enumerate(y):
y[i] = classes.index(tag)
y = np.array(y)
return X, y

def train(self, path):
"""
Train the chatbot model with the given JSON data.
Expand All @@ -24,9 +200,19 @@ def train(self, data):
:return: None
:rtype: NoneType
"""
rubbish = data

def infer(self, prompt):
with open(path, 'r') as f:
self.raw_data = json.load(f)

X, y = self.setup_data()
if self.machine == 'NN':
self.model = NeuralNet(structure=[X.shape[1], 12, len(self.classes)], activation='sigmoid')
self.model.fit(X, y, batch_size=1, epochs=1000, λ = 0.04, eval_train=True)
elif self.machine == 'SVM':
self.model = SVM(kernel='linear', C=700)
self.model.fit(X, y, eval_train=True)
print("meh")

def infer(self, prompt, confidence=None):
"""
Infer a suitable response to the given prompt.
Expand All @@ -36,8 +222,13 @@ def infer(self, prompt):
:return: The chatbot's response
:rtype: string
"""
response = self.model(prompt)
return response



if confidence is None: confidence = 1.5/len(self.classes)
vector = self.get_tf_idf(prompt) if self.repr == 'tf-idf' else self.get_glove_vector(prompt) if self.repr == 'glove' else self.get_tf_idf_glove_vector(prompt)
# predict the class of the prompt
tag_idx, tag_prob = self.model.predict(vector)
tag_idx, tag_prob = tag_idx[0], tag_prob[0]
tag = self.classes[tag_idx]
if tag_prob < confidence: return "Could you rephrase that?"
for intent in self.raw_data['FAQ']:
if tag == intent["tag"]:
return np.random.choice(intent['responses'])
File renamed without changes.
108 changes: 108 additions & 0 deletions botiverse/gui/gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import tkinter as tk
from tkinter import scrolledtext, messagebox

class chat_gui:
def __init__(self, chatbot_function, speak_function=None):
self.chatbot_function = chatbot_function
self.speak_function = speak_function

# Create the main window
self.root = tk.Tk()
self.root.title("Botiverse Chat")
self.root.geometry("600x500")
self.root.configure(bg='#222222')
# disable vertical resizing
self.root.resizable(True, False)

# Create the chat display area
self.chat_display = scrolledtext.ScrolledText(self.root, width=40, height=30, state='disabled', bg='#1d1d1d', fg='#1d1d1d')
self.chat_display.configure(state='disabled', borderwidth=0)
self.chat_display.configure(highlightthickness=0, relief='flat')


# Create the input area
self.input_entry = tk.Entry(self.root, width=30, bg='#505050', fg='#ffffff')
self.input_entry.bind('<Return>', self.process_input)
# remove border form input
self.input_entry.configure(highlightthickness=0, relief='flat')
# increase height of input area
self.input_entry.configure(font=("Avenir", 15))

# Create the send button
self.send_button = tk.Button(self.root, text="Send", command=self.process_input, width=10)
# make button grey
self.send_button.configure(bg='#505050', fg='#1d1d1d', activebackground='#1d1d1d', activeforeground='#1d1d1d', background='#1d1d1d', foreground='#1d1d1d')
# remove border from button
#self.send_button.configure(highlightthickness=0, relief='flat')
# set font
self.send_button.configure(font=("Avenir", 18))
# remove send button
self.send_button.pack_forget()

# Position the widgets in the window
self.chat_display.pack(fill="both", padx=10, pady=10)
self.input_entry.pack(fill="x", padx=10, pady=5)
self.send_button.pack(fill="x", pady=5)

def process_input(self, event=None):
# Get the user input
user_input = self.input_entry.get()
self.input_entry.delete(0, 'end')

# Display user input in the chat area
self.display_message(user_input, user=True)

# Pass the user input to the chatbot function and get the response
bot_response = self.chatbot_function(user_input)

# Display bot response in the chat area
self.display_message(bot_response, user=False)


def display_message(self, message, user=True):
# Enable the chat display area to insert text
self.chat_display.configure(state='normal')

# Insert the message into the chat display area with a chat bubble
if user:
self.chat_display.insert('end', message + '\n', 'user')
else:
self.chat_display.insert('end', message + '\n', 'bot')
if self.speak_function is not None:
self.speak_function(message)

# Disable the chat display area to prevent editing
self.chat_display.configure(state='disabled')

# Scroll to the bottom of the chat display area
self.chat_display.see('end')



def run(self):
# Configure the chat bubble styles
self.chat_display.tag_configure('user', foreground='white', justify='left', font=('Avenir', 15, 'bold'),
background='#505050', borderwidth=2, relief='solid',
wrap='word', spacing3=10, spacing1=10, lmargin1=10, spacing2=5)

self.chat_display.tag_configure('bot', foreground='white', justify='right', font=('Avenir', 15, 'bold'),
background='#303030', borderwidth=2, relief='solid',
wrap='word', spacing3=10, spacing1=10, rmargin=10, spacing2=5)



# Start the main GUI loop
self.root.mainloop()


# Example usage:
'''
def chatbot_function(input):
# Your chatbot logic goes here
# Process the input and generate a response
response = "Hello! You said: " + input
return response
chat_gui = ChatGUI(chatbot_function)
chat_gui.run()
'''
2 changes: 1 addition & 1 deletion botiverse/models/FastSpeech1/FastSpeech.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ def __init__(self, force_download_wg=False, force_download_fs=False):
self.model.eval()
self.WaveGlow = waveglow.load.load_model(download=force_download_wg)

def speak(self, text, play=False, save=False):
def speak(self, text, play=True, save=False):
'''
Pass given text through the FastSpeech 1.0 model and the WaveGlow model to generate speech.
text: text to be spoken with at most 300 characters
Expand Down
Loading

0 comments on commit 58bdd77

Please sign in to comment.