12/08/2018, 16:45

Áp dụng Machine learning, xây dựng ứng dụng chatbot của riêng bạn.

Cuộc đại chiến giữa các ChatBot và các trợ lý ảo chưa bao giờ có hồi kết. Trong khi Apple, Microsoft đang rất tự hào với những cô nàng trợ lý ảo dễ thương Siri, Cortana, thì các ông lớn khác như Facebook, Amazon cũng trình làng những trợ lý riêng của họ. Khái niệm chatbot cũng nhờ đó mà trở lên ...

Cuộc đại chiến giữa các ChatBot và các trợ lý ảo chưa bao giờ có hồi kết.

Trong khi Apple, Microsoft đang rất tự hào với những cô nàng trợ lý ảo dễ thương Siri, Cortana, thì các ông lớn khác như Facebook, Amazon cũng trình làng những trợ lý riêng của họ. Khái niệm chatbot cũng nhờ đó mà trở lên quen thuộc với con người.

Một người anh trong team của tôi, dùng Amazon Alexa để biến ngôi nhà của anh ấy thành Smart Home, một người bạn khác của tôi lại dùng ChatBot Messager để hỗ trợ bán hàng. Có vẻ như việc sử dụng chatbot giúp lượng khách hàng của cô ấy tăng lên một cách đáng kể và đem lại lợi nhuận đáng mong ước của những người kinh doanh online. Nhưng đó là khi bạn sử dụng một Chatbot một ông lớn nào đó, Và đôi khi cũng phải trả phí khá cao để có thể sử dụng được lâu dài. Vậy tại sao bạn không xây dựng một ứng dụng chatbot của riêng mình nhỉ. Điều này có thể giúp bạn tiết kiệm được chi phí và thậm chí có thể phát triển lên thành các ứng dụng khác tuyệt vời hơn, cạnh tranh với chính các ông lớn đó (yaoming)

Bài viết hôm nay, tôi sẽ hướng dẫn bạn xây dựng một ứng dụng ChatBot đơn giản. Việc làm đầu tiên và khá là quan trọng đó là bạn cần phải xác định được scope của ứng dụng. Bạn cần phải biết mục đích xây dựng chatbot của bạn là gì, và hãy bắt đầu bằng những chủ đề cụ thể và có thể thực hiện. Ví dụ, trong bài viết này tôi sẽ xây dựng ứng dụng ChatBot hỗ trợ order thức ăn trong một nhà hàng. Nào ta cùng bắt đầu.

ChatBot trong lĩnh vực học máy

Trong lĩnh vực học máy, ChatBot hay các cô nàng trợ lý ảo đều được quy chung về một loại đó là Question and Answering system. Đối với các hệ chuyên gia như vậy, công việc bạn cần làm bao gồm

  • Phân loại câu hỏi
  • Mapping câu trả lời ( Trích chọn tài liệu liên quan)
  • Trích xuất câu trả lời.

Việc phân loại câu hỏi là bước khó nhất trong hệ thống hỏi đáp, Tuy nhiên, như chính cái tên của nó, bạn hoàn toàn có thể chuyển bài toán này về các bài toán phân lớp đã biết. Đến đây, có lẽ các bạn đã hình dung ra được các bước xây dựng chatBot không hề khó như những gì ta nghĩ phải không?

Chuẩn bị dữ liệu

Một bộ dữ liệu training đủ nhiều và chuẩn chỉnh có thể nâng cao độ chính xác lên rất nhiều. Mặc dù trong bài viết này dữ liệu tôi sử dụng là rất ít nhưng một phần nào đó có thể giúp các bạn hình dung cấu trúc của dữ liệu training mà bạn cần có. Bạn có thể định nghĩa cấu trúc dữ liệu của bạn theo dạng json dưới đây:

{"intents": [
        {"tag": "greeting",
         "patterns": ["Hi", "How are you", "Is anyone there?", "Hello", "Good day"],
         "responses": ["Hello, thanks for visiting", "Good to see you again", "Hi there, how can I help?"],
         "context_set": ""
        },
        {"tag": "goodbye",
         "patterns": ["Bye", "See you later", "Goodbye"],
         "responses": ["See you later, thanks for visiting", "Have a nice day", "Bye! Come back again soon."]
        },
        {"tag": "thanks",
         "patterns": ["Thanks", "Thank you", "That's helpful"],
         "responses": ["Happy to help!", "Any time!", "My pleasure"]
        },
        {"tag": "hours",
         "patterns": ["What hours are you open?", "What are your hours?", "When are you open?" ],
         "responses": ["We're open every day 9am-9pm", "Our hours are 9am-9pm every day"]
        },
        {"tag": "menu",
         "patterns": ["I want to order", "What kinds of food are there?", "What is your delicous food?" ],
         "responses": ["Are you ready to order? Our restaurant have pizza, vegetable pasta, beefsteack. These is special of our restaurant", "Can I help you? This is menu. Please ask me if you have any question."]
        },
        ...........

Như mình đã nói bên trên, việc quan trọng trong hệ thống chatbot là xác định được câu hỏi hay nội dung mà người dùng nhập.

  • Bạn cần một tag (nhãn lớp cho nội dung nhập của người dùng)
  • patterns - mẫu câu đầu vào được training để phân lớp
  • responses - các câu trả lời (bot) được mapping để hồi đáp những request trước đó.

Training model

Trong hướng dẫn này, chúng ta sẽ sử dụng TFlearn - High level API của Tensorflow, và dĩ nhiên ngôn ngữ sử dụng là Python. Đầu tiên, hãy import các thư viện cần thiết, đặc biệt là các thư viện được xử dụng trong NLP như NLTK, tensorflow...

import nltk
from nltk.stem.lancaster import LancasterStemmer
stemmer = LancasterStemmer()

import numpy as np
import tflearn
import tensorflow as tf
import random

Loading data training. Vì dữ liệu traning của chúng ta đang ở dạng json nên đừng quên import json nhé.

import json
with open('intents.json') as json_data:
    intents = json.load(json_data)

Với intents.json vừa được đưa vào bộ training, bạn cần tổ chức lại nó, Xác định cho công cụ của bạn biết đâu là documents dùng để training, các từ, các classes để phân lớp. Bạn nên sử dụng thêm bộ công cụ của xử lý ngôn ngữ tự nhiên nltk để tiền xử lý dữ liệu. Bộ công cụ này cho phép bạn thực hiện các quá trình tokenizer, POS stagging, word segmentation, remove stopword....

words = []
classes = []
documents = []
stop_words = ['?', 'a', 'an', 'the']

for intent in intents['intents']:
    for pattern in intent['patterns']:

        w = nltk.word_tokenize(pattern)
        words.extend(w)
        documents.append((w, intent['tag']))
        if intent['tag'] not in classes:
            classes.append(intent['tag'])

words = [stemmer.stem(w.lower()) for w in words if w not in stop_words]
words = sorted(list(set(words)))

classes = sorted(list(set(classes)))

Bổ sung thêm stopwords vào stop_words array hoặc tạo một file text để list danh sách các stopwords nhé. Nói nhiều như vậy mình cũng quên mất không biết là các bạn có hiểu stopwords là gì không? Nhưng đại khái thì nó là danh sách các từ xuất hiện nhiều trong văn bản nhưng lại không có giá trị trong việc phân lớp. Vì vậy trước khi training, chúng ta cần làm sạch văn bản, loại bỏ những từ ngữ không có ý nghĩa này để tránh overfiting ảnh hưởng đến kết quả training.

Thật không may, nếu chúng ta sử dụng dự liệu dạng word như vậy thì sẽ không thể hoạt động được với tensorflow, vì vậy việc cần thiết bây giờ là chuyển dữ liệu này sang dạng tensor number

# create our training data
training = []
output = []
output_empty = [0] * len(classes)

# training set, bag of words for each sentence
for doc in documents:
    bag = []
    pattern_words = doc[0]
    pattern_words = [stemmer.stem(word.lower()) for word in pattern_words]

    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
    output_row = list(output_empty)
    output_row[classes.index(doc[1])] = 1

    training.append([bag, output_row])
random.shuffle(training)
training = np.array(training)

# create train and test lists
train_x = list(training[:,0])
train_y = list(training[:,1])

Lưu ý rằng, dữ liệu của chúng ta bị xáo trộn. Tensorflow sẽ lấy một số dữ liệu trong tập intents.json để làm dữ liệu thử nghiệm để đo độ chính xác cho mô hình. Sau khi chuyển word -> number, bạn có thể thấy các "bag-of-words" array dạng như sau:

train_x example: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
train_y example: [1, 0, 0, 0, 0, 0, 0, 0]

Với dữ liệu như trên, tensorflow đã có thể hiểu được nội dung bạn muốn training và bây giờ cần phải build neural network để training model ngay thôi. Ở đây tôi dùng hàm activation là softmax và optimizer function là Adam. Bạn có thể thay đổi 2 hàm này để có thể đưa ra kết quả training cao nhất và phù hợp nhất.

tf.reset_default_graph()
# Build neural network
net = tflearn.input_data(shape=[None, len(train_x[0])])
net = tflearn.fully_connected(net, 8)
net = tflearn.fully_connected(net, 8)
net = tflearn.fully_connected(net, len(train_y[0]), activation='softmax')
net = tflearn.regression(net, optimizer='adam', loss='categorical_crossentropy')

# Define model and setup tensorboard
model = tflearn.DNN(net, tensorboard_dir='tflearn_logs')
# Start training
model.fit(train_x, train_y, n_epoch=1000, batch_size=8, show_metric=True)
model.save('model.tflearn')

Với cấu trúc như trên, chúng ta đang xây dựng mạng neural 2 lớp để traning, bạn có thể xem thêm trong tài liệu của TFlearn Nếu muốn tặng thêm số lớp ẩn. Để hoàn thành quá trình này, bạn cần lưu lại (pickle) model và document để có thể sử dụng lại nó trong quá trình predict ở bước tiếp theo.

import pickle
pickle.dump( {'words':words, 'classes':classes, 'train_x':train_x, 'train_y':train_y}, open( "training_data", "wb" ) )

Predict (Xây dựng ChatBot)

Ở bước này chúng ta sẽ xây dựng hệ thống phản hồi của Chatbot, sử dụng intents model đã được training ở bước trên. Sau khi import các thư viện giống như bước training ở trên, Bạn cần un-pickle model và documents, cũng như cần phải load lại intents.json (cái này để lấy các response đã được định nghĩa trước đó thôi mờ )

# restore our data structures
import pickle
data = pickle.load( open( "training_data", "rb" ) )
words = data['words']
classes = data['classes']
train_x = data['train_x']
train_y = data['train_y']

# import intents file
import json
with open('intents.json') as json_data:
    intents = json.load(json_data)

Load tensorflow model

model.load('./model.tflearn')

Cũng giống như khi training dữ liệu, với các ý định người dùng nhập vào bạn cũng cần phải tiền xử lý, tokenizer, hay chuyển sang bag-of-words để hệ thống có thể hiểu và phân loại về đúng lớp của nó

def clean_up_sentence(sentence):
    sentence_words = nltk.word_tokenize(sentence)
    sentence_words = [stemmer.stem(word.lower()) for word in sentence_words]
    return sentence_words

# bag of words
def bow(sentence, words, show_details=False):
    sentence_words = clean_up_sentence(sentence)
    # bag of words
    bag = [0]*len(words)  
    for s in sentence_words:
        for i,w in enumerate(words):
            if w == s: 
                bag[i] = 1
                if show_details:
                    print ("found in bag: %s" % w)

    return(np.array(bag))

hãy kiểm tra đoạn code trên với dữ liệu người dùng có thể nhập vào lúc order

bow('I want to special food', words)
>>> array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
       0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
       0, 1, 0, 0, 0, 0, 0, 0, 0, 0])

OK. Vậy là dữ liệu đầu vào đã xử lý xong. Và bây giờ là bộ xử lý phản hồi của chatbot

# data structure to hold user context
context = {}

ERROR_THRESHOLD = 0.25
def classify(sentence):
    results = model.predict([bow(sentence, words)])[0]
    results = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]
    results.sort(key=lambda x: x[1], reverse=True)
    return_list = []
    for r in results:
        return_list.append((classes[r[0]], r[1]))
    return return_list

def response(sentence, userID='1', show_details=False):
    results = classify(sentence)
    if results:
        while results:
            for i in intents['intents']:
                if i['tag'] == results[0][0]:
                    if 'context_set' in i:
                        if show_details: print ('context:', i['context_set'])
                        context[userID] = i['context_set']
                    if not 'context_filter' in i or 
                        (userID in context and 'context_filter' in i and i['context_filter'] == context[userID]):
                        if show_details: print ('tag:', i['tag'])
                        return print(random.choice(i['responses']))

            results.pop(0)

Với mỗi câu hỏi (hay request) người dùng nhập vào, tôi sẽ sử dụng hàm model.predict() để xác định request đó thuộc loại nào. Sau đó sẽ đưa ra các phản hồi tiềm ẩn, có khả năng và phù hợp nhất với các request trước đó. Và kết quả ....

classify('Hi, have any waiter')
>>> [('greeting', 0.8708275)]

response('Hi, have any waiter')
>>> Hi there, how can I help?

response('is your shop open today?')
>>> We're open every day from 9am-9pm

response('I want to special food')
>>> Are you ready to order? Our restaurant have pizza, vegetable pasta, beefsteack. These is special of our restaurant

response('thankyou, I want beefsteack and 2 glass of water')
>>> Thankyou for ordering, Please wait me some minutes.

response('do you take cash?')
>>> We accept most major credit cards

response('Goodbye, see you later')
>>> See you later, thanks for visiting

Hiện tại con ChatBot của mình đang hơi ít dữ liệu nên nó hơi ngu si xíu, Bạn có thể tham khảo cách làm của mình, để xây dựng ChatBot của riêng bạn nhé. Nếu dữ liệu nhiều, thi thoảng lôi ra chém gió với nó cũng vui lắm đấy =)) Và đây là github cho ứng dụng ChatBot của mình bạn có thể tham khảo nhé.

0