Trong những năm gần đây, từ khóa Chatbot không còn xa lạ gì với mọi người như trợ lý ảo Siri của Apple, Chatbot của Facebook… Các bạn có thể xem lại bài viết Hướng dẫn tạo Chat Bot tự động trả lời tin nhắn Facebook mà trước đây mình có viết. Hôm nay, mình sẽ hướng dẫn các bạn xây dựng Chatbot bằng NLTK và Keras.
Chatbot là gì?
Chatbot là một phần mềm thông minh có khả năng giao tiếp và thực hiện các hành động tương tự như con người. Chatbot được sử dụng rất nhiều trong tương tác với khách hàng, tiếp thị trên các trang mạng xã hội và nhắn tin tức thời cho khách hàng.
Có 2 loại mô hình chatbot cơ bản:
- Retrieval based: sử dụng các mẫu đầu vào và phản hồi được xác định trước.
- Generative based: không dựa trên một số phản hồi được xác định trước.
Xây dựng Chatbot bằng NLTK và Keras
Trong bài này, chúng ta sẽ xây dựng Chatbot bằng Deep Learning sử dụng thư viện NLTK (là thư viện phổ biến nhất để xử lý ngôn ngữ tự nhiên – NLP) và Keras. Chatbot sẽ được huấn luyện bởi tập dữ liệu chứa các danh mục (intents), mẫu (pattern) và phản hồi (response). Chúng ta sẽ sử dụng mạng nơ-ron LSTM để phân loại thông điệp của người dùng thuộc danh mục nào và sau đó sẽ đưa ra phản hồi ngẫu nhiên từ danh sách phản hồi.
Dataset
Tập dữ liệu mà chúng ta sẽ sử dụng là “intents.json”. Đây là một tệp JSON chứa các pattern chúng ta cần tìm và các phản hồi mà chúng ta muốn trả lại cho người dùng.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
{"intents": [ {"tag": "greeting", "patterns": ["Hi there", "How are you", "Is anyone there?","Hey","Hola", "Hello", "Good day"], "responses": ["Hello, thanks for asking", "Good to see you again", "Hi there, how can I help?"], "context": [""] }, {"tag": "goodbye", "patterns": ["Bye", "See you later", "Goodbye", "Nice chatting to you, bye", "Till next time"], "responses": ["See you!", "Have a nice day", "Bye! Come back again soon."], "context": [""] }, {"tag": "thanks", "patterns": ["Thanks", "Thank you", "That's helpful", "Awesome, thanks", "Thanks for helping me"], "responses": ["Happy to help!", "Any time!", "My pleasure"], "context": [""] }, {"tag": "noanswer", "patterns": [], "responses": ["Sorry, can't understand you", "Please give me more info", "Not sure I understand"], "context": [""] }, {"tag": "options", "patterns": ["How you could help me?", "What you can do?", "What help you provide?", "How you can be helpful?", "What support is offered"], "responses": ["I can guide you through Adverse drug reaction list, Blood pressure tracking, Hospitals and Pharmacies", "Offering support for Adverse drug reaction, Blood pressure, Hospitals and Pharmacies"], "context": [""] }, {"tag": "adverse_drug", "patterns": ["How to check Adverse drug reaction?", "Open adverse drugs module", "Give me a list of drugs causing adverse behavior", "List all drugs suitable for patient with adverse reaction", "Which drugs dont have adverse reaction?" ], "responses": ["Navigating to Adverse drug reaction module"], "context": [""] }, {"tag": "blood_pressure", "patterns": ["Open blood pressure module", "Task related to blood pressure", "Blood pressure data entry", "I want to log blood pressure results", "Blood pressure data management" ], "responses": ["Navigating to Blood Pressure module"], "context": [""] }, {"tag": "blood_pressure_search", "patterns": ["I want to search for blood pressure result history", "Blood pressure for patient", "Load patient blood pressure result", "Show blood pressure results for patient", "Find blood pressure results by ID" ], "responses": ["Please provide Patient ID", "Patient ID?"], "context": ["search_blood_pressure_by_patient_id"] }, {"tag": "search_blood_pressure_by_patient_id", "patterns": [], "responses": ["Loading Blood pressure result for Patient"], "context": [""] }, {"tag": "pharmacy_search", "patterns": ["Find me a pharmacy", "Find pharmacy", "List of pharmacies nearby", "Locate pharmacy", "Search pharmacy" ], "responses": ["Please provide pharmacy name"], "context": ["search_pharmacy_by_name"] }, {"tag": "search_pharmacy_by_name", "patterns": [], "responses": ["Loading pharmacy details"], "context": [""] }, {"tag": "hospital_search", "patterns": ["Lookup for hospital", "Searching for hospital to transfer patient", "I want to search hospital data", "Hospital lookup for patient", "Looking up hospital details" ], "responses": ["Please provide hospital name or location"], "context": ["search_hospital_by_params"] }, {"tag": "search_hospital_by_params", "patterns": [], "responses": ["Please provide hospital type"], "context": ["search_hospital_by_type"] }, {"tag": "search_hospital_by_type", "patterns": [], "responses": ["Loading hospital details"], "context": [""] } ] } |
Train the model
1. Import và load data file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import nltk from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer() import json import pickle import numpy as np from keras.models import Sequential from keras.layers import Dense, Activation, Dropout from keras.optimizers import SGD import random words=[] classes = [] documents = [] ignore_words = ['?', '!'] data_file = open('intents.json').read() intents = json.loads(data_file) |
2. Preprocess data
Khi làm việc với dữ liệu văn bản, chúng ta cần thực hiện nhiều quá trình tiền xử lý dữ liệu trước khi tạo ra một mô hình học máy (Machine Learning) hoặc học sâu (Deep Learning). Dựa trên các yêu cầu, chúng ta cần áp dụng các thao tác khác nhau để xử lý trước dữ liệu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
for intent in intents['intents']: for pattern in intent['patterns']: #tokenize each word w = nltk.word_tokenize(pattern) words.extend(w) #add documents in the corpus documents.append((w, intent['tag'])) # add to our classes list if intent['tag'] not in classes: classes.append(intent['tag']) # lemmatize, lower each word and remove duplicates words = [lemmatizer.lemmatize(w.lower()) for w in words if w not in ignore_words] words = sorted(list(set(words))) # sort classes classes = sorted(list(set(classes))) # documents = combination between patterns and intents print (len(documents), "documents") # classes = intents print (len(classes), "classes", classes) # words = all words, vocabulary print (len(words), "unique lemmatized words", words) pickle.dump(words,open('words.pkl','wb')) pickle.dump(classes,open('classes.pkl','wb')) |
Tokenizing là quá trình chia toàn bộ văn bản thành các phần nhỏ.
Ở đây chúng ta lặp lại qua các mẫu và mã hóa câu bằng cách sử dụng hàm nltk.word_tokenize() và nối từng từ trong danh sách từ. Chúng ta cũng tạo một danh sách các lớp cho các thẻ (tag).
Kế tiếp, chúng ta sẽ bổ sung từng từ và loại bỏ các từ trùng lặp khỏi danh sách. Lemmatizing là quá trình chuyển đổi một từ thành dạng bổ đề (lemma) của nó và sau đó tạo một tệp nhỏ để lưu trữ các đối tượng Python mà chúng ta sẽ sử dụng trong khi dự đoán (predict).
Sau đó, chúng ta lưu lại 2 file pickle là “words.pkl” và “classes.pkl”.
3. Tạo training và testing data
Input sẽ là mẫu (pattern) và output sẽ là lớp (class) tương ứng với mẫu đầu vào.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
training = [] output_empty = [0] * len(classes) # training set, bag of words for each sentence for doc in documents: # initialize our bag of words bag = [] # list of tokenized words for the pattern pattern_words = doc[0] # lemmatize each word - create base word, in attempt to represent related words pattern_words = [lemmatizer.lemmatize(word.lower()) for word in pattern_words] # create our bag of words array with 1, if word match found in current pattern for 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])] = 1 training.append([bag, output_row]) random.shuffle(training) training = np.array(training) train_x = list(training[:,0]) train_y = list(training[:,1]) print("Training data created") |
4. Build the model
Bây giờ, chúng ta sẽ xây dựng một mạng nơ-ron có 3 layer sử dụng Keras. Sau khi huấn luyện mô hình trong 200 epochs, chúng ta lưu lại model với tên “chatbot_model.h5”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
model = 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')) sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy']) hist = 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") |
Xây dựng giao diện chương trình Chatbot
Chúng ta sẽ load model đã được đào tạo và sau đó sử dụng giao diện dự đoán phản hồi từ bot. Mô hình sẽ chỉ cho chúng ta biết lớp mà nó thuộc về, vì vậy chúng ta sẽ triển khai một số hàm sẽ xác định lớp và sau đó lấy cho chúng ta một phản hồi ngẫu nhiên từ danh sách các phản hồi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import nltk from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer() import pickle import numpy as np from keras.models import load_model model = load_model('chatbot_model.h5') import json import random intents = 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 array sentence_words = nltk.word_tokenize(sentence) # stem each word - create short form for word sentence_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 sentence def bow(sentence, words, show_details=True): # tokenize the pattern sentence_words = clean_up_sentence(sentence) # bag of words - matrix of N words, vocabulary matrix bag = [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 position bag[i] = 1 if show_details: print ("found in bag: %s" % w) return(np.array(bag)) def predict_class(sentence, model): # filter out predictions below a threshold p = bow(sentence, words,show_details=False) res = model.predict(np.array([p]))[0] ERROR_THRESHOLD = 0.25 results = [[i,r] for i,r in enumerate(res) if r>ERROR_THRESHOLD] # sort by strength of probability results.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 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']) break return result def chatbot_response(text): ints = predict_class(text, model) res = getResponse(ints, intents) return res |
Xây dựng giao diện sử dụng Tkinter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#Creating GUI with tkinter import tkinter from 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("Chatbot!") base.geometry("400x500") base.resizable(width=FALSE, height=FALSE) #Create Chat window ChatLog = Text(base, bd=0, bg="white", height="8", width="50", font="Arial",) ChatLog.config(state=DISABLED) #Bind scrollbar to Chat window scrollbar = Scrollbar(base, command=ChatLog.yview, cursor="heart") ChatLog['yscrollcommand'] = scrollbar.set #Create Button to send message SendButton = 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 message EntryBox = Text(base, bd=0, bg="white",width="29", height="5", font="Arial") #EntryBox.bind("<Return>", send) #Place all components on the screen scrollbar.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() |
Chạy thôi
1 |
$python chatbot_app.py |
Leave a Reply