How to Build CHATBOT Project using PYTHON?
- What is this project all about?
In this project with source code, I built a ChatBot using Deep Learning techniques. This ChatBot was trained on a dataset which contains categories (intents), pattern and responses. You can use a special recurrent neural network (LSTM) to classify which category the user’s message belongs to and then give a random response from the list of responses. - My DataSet
I used the ‘intents.json’ dataset. This is a JSON file that contains the patterns I needed to find and the responses I wanted to return to the user. You can also download the dataset: Chatbot Project dataset
STEP NO. 01Importing and Loading the Data File
Firstly, I made a file named as train_chatbot.py. I imported the necessary packages for my ChatBot and initialized the variables that I will use later in the project.
The data file is in JSON format so I used the JSON package to parse the JSON file into Python.
import nltkfrom nltk.stem import WordNetLemmatizerlemmatizer = WordNetLemmatizer()import jsonimport pickleimport numpy as npfrom keras.models import Sequentialfrom keras.layers import Dense, Activation, Dropoutfrom keras.optimizers import SGDimport randomwords=[]classes = []documents = []ignore_words = ['?', '!']data_file = open('intents.json').read()intents = json.loads(data_file)
Here’s a snap of the intent.json file.
.
STEP NO. 02Pre-processing of Data
When working with text data, I performed various preprocessing on the data before I made a Machine Learning or a Deep Learning model. Tokenizing is the most basic and first thing you can do on text data. Tokenizing is the process of breaking the whole text into small parts like words.
.
So I iterated through the patterns and tokenized the sentence using nltk.word_tokenize() function and append each word in the words list. I also created a list of classes for my tags.
for intent in intents['intents']:for pattern in intent['patterns']:#tokenize each wordw = nltk.word_tokenize(pattern)words.extend(w)#add documents in the corpusdocuments.append((w, intent['tag']))# add to our classes listif intent['tag'] not in classes:classes.append(intent['tag'])
.
After this, I lemmatized each word and remove duplicate words from the list. Lemmatizing is the process of converting a word into its lemma form and then creating a pickle file to store the Python objects which I used while predicting.
# lemmatize, lower each word and remove duplicateswords = [lemmatizer.lemmatize(w.lower()) for w in words if w not in ignore_words]words = sorted(list(set(words)))# sort classesclasses = sorted(list(set(classes)))# documents = combination between patterns and intentsprint (len(documents), "documents")# classes = intentsprint (len(classes), "classes", classes)# words = all words, vocabularyprint (len(words), "unique lemmatized words", words)pickle.dump(words,open('words.pkl','wb'))pickle.dump(classes,open('classes.pkl','wb'))
So, it was time to create the training data in which I provided the input and the output. My input will be the pattern and output will be the class my input pattern belongs to. But the computer doesn’t understand the text so I converted text into numbers.
# create our training datatraining = []# create an empty array for our outputoutput_empty = [0] * len(classes)# training set, bag of words for each sentencefor doc in documents:# initialize our bag of wordsbag = []# list of tokenized words for the patternpattern_words = doc[0]# lemmatize each word - create base word, in attempt to represent related wordspattern_words = [lemmatizer.lemmatize(word.lower()) for word in pattern_words]# create our bag of words array with 1, if word match found in current patternfor w in words:bag.append(1) if w in pattern_words else bag.append(0)# output is a '0' for each tag and '1' for current tag (for each pattern)output_row = list(output_empty)output_row[classes.index(doc[1])] = 1training.append([bag, output_row])# shuffle our features and turn into np.arrayrandom.shuffle(training)training = np.array(training)# create train and test lists. X - patterns, Y - intentstrain_x = list(training[:,0])train_y = list(training[:,1])print("Training data created")# Create model - 3 layers. First layer 128 neurons, second layer 64 neurons and 3rd output layer contains number of neurons# equal to number of intents to predict output intent with softmaxmodel = Sequential()model.add(Dense(128, input_shape=(len(train_x[0]),), activation='relu'))model.add(Dropout(0.5))model.add(Dense(64, activation='relu'))model.add(Dropout(0.5))model.add(Dense(len(train_y[0]), activation='softmax'))# Compile model. Stochastic gradient descent with Nesterov accelerated gradient gives good results for this modelsgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])#fitting and saving the modelhist = model.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1)model.save('chatbot_model.h5', hist)print("model created")import nltkfrom nltk.stem import WordNetLemmatizerlemmatizer = WordNetLemmatizer()import pickleimport numpy as npfrom keras.models import load_modelmodel = load_model('chatbot_model.h5')import jsonimport randomintents = json.loads(open('intents.json').read())words = pickle.load(open('words.pkl','rb'))classes = pickle.load(open('classes.pkl','rb'))def clean_up_sentence(sentence):# tokenize the pattern - split words into arraysentence_words = nltk.word_tokenize(sentence)# stem each word - create short form for wordsentence_words = [lemmatizer.lemmatize(word.lower()) for word in sentence_words]return sentence_words# return bag of words array: 0 or 1 for each word in the bag that exists in the sentencedef bow(sentence, words, show_details=True):# tokenize the pattern# bag of words - matrix of N words, vocabulary matrixbag = [0]*len(words)for s in sentence_words:for i,w in enumerate(words):if w == s:# assign 1 if current word is in the vocabulary positionbag[i] = 1if show_details:print ("found in bag: %s" % w)return(np.array(bag))def predict_class(sentence, model):# filter out predictions below a thresholdp = bow(sentence, words,show_details=False)res = model.predict(np.array([p]))[0]ERROR_THRESHOLD = 0.25results = [[i,r] for i,r in enumerate(res) if r>ERROR_THRESHOLD]# sort by strength of probabilityresults.sort(key=lambda x: x[1], reverse=True)return_list = []for r in results:return_list.append({"intent": classes[r[0]], "probability": str(r[1])})return return_list
STEP NO. 04Building the Model
So my data is ready, then I built a Deep Neural Network that had 3 layers. I used the Keras sequential API for this. After training the model for 200 epochs, I achieved 100% accuracy on my model.
This is how I saved the model as ‘chatbot_model.h5’.
.
STEP NO. 05Predicting the Response
I loaded the trained model and then used a Graphical User Interface (GUI) that predicted the response from the bot. The model will only tell me the class it belongs to, so I implemented some functions which will identify the class and then retrieve me a random response from the list of responses.
Now I imported the necessary packages and loaded the ‘words.pkl’ and ‘classes.pkl’ pickle files which I created when I trained my model.
.
To predict the class, I provided input in the same way as I did while training. So I created some functions that will perform text preprocessing and then predict the class.
After predicting the class, I got a random response from the list of intents.
def getResponse(ints, intents_json):tag = ints[0]['intent']list_of_intents = intents_json['intents']for i in list_of_intents:if(i['tag']== tag):result = random.choice(i['responses'])breakreturn resultdef chatbot_response(text):ints = predict_class(text, model)res = getResponse(ints, intents)return res
Finally, I coded a GUI. For this, I used the Tkinter library which already comes in Python. I took the input message from the user and then used the helper functions I created to get the response from the bot and display it on the GUI. You will also get the same response provided no error code is been run.
Here’s the response.
#Creating GUI with tkinterimport tkinterfrom tkinter import *def send():msg = EntryBox.get("1.0",'end-1c').strip()EntryBox.delete("0.0",END)if msg != '':ChatLog.config(state=NORMAL)ChatLog.insert(END, "You: " + msg + '\n\n')ChatLog.config(foreground="#442265", font=("Verdana", 12 ))res = chatbot_response(msg)ChatLog.insert(END, "Bot: " + res + '\n\n')ChatLog.config(state=DISABLED)ChatLog.yview(END)base = Tk()base.title("Hello")base.geometry("400x500")base.resizable(width=FALSE, height=FALSE)#Create Chat windowChatLog = Text(base, bd=0, bg="white", height="8", width="50", font="Arial",)ChatLog.config(state=DISABLED)#Bind scrollbar to Chat windowscrollbar = Scrollbar(base, command=ChatLog.yview, cursor="heart")ChatLog['yscrollcommand'] = scrollbar.set#Create Button to send messageSendButton = Button(base, font=("Verdana",12,'bold'), text="Send", width="12", height=5,bd=0, bg="#32de97", activebackground="#3c9d9b",fg='#ffffff',command= send )#Create the box to enter messageEntryBox = Text(base, bd=0, bg="white",width="29", height="5", font="Arial")#EntryBox.bind("<Return>", send)#Place all components on the screenscrollbar.place(x=376,y=6, height=386)ChatLog.place(x=6,y=6, height=386, width=370)EntryBox.place(x=128, y=401, height=90, width=265)SendButton.place(x=6, y=401, height=90)base.mainloop()
STEP NO. 06Finally running the ChatBot
First, I trained the model using the command in the terminal:
python train_chatbot.py
.
Fortunately, I didn’t get any error. Same will be the result for you if you follow me exactly. So to run the app, I run the second file.
python chatgui.py
.
The program opened up a GUI window within a few seconds. With the GUI I easily chatted with the bot.
.
SCREENSHOTS
Thanks for giving your valuable time. If you find my content useful, then please follow this blog.
Any kind of suggestion for further improvement will be highly appreciated:)
Comments
Post a Comment