Итак, как часто вы узнаете погоду или время у Siri, Алисы или Google? Сейчас на рынке существует несколько видов ботов. Некоторые из них более сложные, способные поддерживать непрерывный диалог, а другие просто выполняют различные предварительно запрограммированные действия.
В этой статье мы расскажем, как создать бота, который выполняет определенные действия. С его помощью вы сможете проверять наличие товаров, запрашивать время и погоду и даже извлекать данные с веб-страниц. Если вас заинтересовала такая технология, рекомендуем начать с этого простого бота.
Итак, создаем набор данных, но сначала определим некоторые понятия.
- Функции — это взаимодействие между ботом и человеком. Оно состоит из шаблонов, тегов и ответов.
- Шаблоны — это фразы или ключевые слова, которые может задавать человек для запуска и формирования процесса.
- Теги — это название процесса, который необходимо выполнить.
- Ответы — это может быть один или несколько ответов, которые должен выдавать получившийся бот.
Например, функция приветствия:
intents:
- tag: greeting
patterns:
- Hi (Привет)
- How are you (Как вы)
- Is anyone there? (Тут кто-нибудь есть?)
- Hey (Эй)
- Hola (Привет)
- Hello (Здравствуй)
responses:
- Hi (Привет)
- Holi (Привет)
- Hello, thanks for asking (Привет, спасибо за вопрос)
- Good to see you again (Рад снова вас видеть)
- Hi there, how can I help? (Привет, чем я могу помочь?)
- 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. (Пока! Приходите еще)
Итак, нам потребуется:
- yml-файл со всеми функциями, которые должен выполнять готовый бот. Если вы не знаете, что такое yml-файл, то пройдите по этой ссылке.
- Python 3 либо версии выше.
- Библиотеки TensorFlow, pickle и nltk.
Разделим все на 3 главные части или класса: считыватель функций бота, модель бота и загрузчик бота. Вот какие данные необходимо будет импортировать:
import os
import re
import yaml
import nltk
import random
import json
import numpy as np
import tensorflow as tf
import pickle
from tensorflow.keras.models import load_model
from tensorflow.keras.layers import (
Dense,
Activation,
Dropout
)
from nltk.stem import WordNetLemmatizer
from tensorflow.keras.optimizers import SGD
Считыватель функций бота
Этот класс предназначен для чтения yml-файла с функциями. Для данной модели бота понадобится 4 выхода: лемматизатор, слова, классы и функции. Бот, которого мы создаем, — простой классификатор. В нем каждый тег является классом, а токенизированные и лемматизированные слова — вводимыми данными.
Наконец, необходимо узнать, какую из всех команд или действий должен выполнить бот. Итак, классы — это теги. Затем нужны токенизированные и приведенные к начальной форме слова. Для этого понадобится лемматизатор и токенизатор слов — они есть в библиотеке nltk. Сначала создаем имя класса BotIntentsReader
.
class BotIntentsReader():
def __init__(self, path, tokenize_func=nltk.word_tokenize, lemmatizer=WordNetLemmatizer().lemmatize, logging=False):
nltk.download('punkt')
nltk.download('wordnet')
stream = open(path, 'rb')
self.lemmatizer = lemmatizer
docs = yaml.safe_load(stream)
self.words=[]
self.classes = []
self.documents = []
ignore_words = ['?', '!']
self.intents = docs['intents']
for intent in self.intents:
for pattern in intent['patterns']:
token_pattern = tokenize_func(pattern)
self.words.extend(token_pattern)
self.documents.append((token_pattern, intent['tag']))
if intent['tag'] not in self.classes:
self.classes.append(intent['tag'])
self.words = [lemmatizer(w.lower()) for w in self.words if w not in ignore_words]
self.words = sorted(list(set(self.words)))
self.classes = sorted(list(set(self.classes)))
if logging:
print (len(self.documents), "documents")
print (len(self.classes), "classes", self.classes)
print (len(self.words), "unique lemmatized words", self.words)
Мы добавили дополнительный вывод под названием documents
, который представляет собой только объединение токенизированных слов (до лемматизации) и тегов. Эта переменная будет повторяться для обучения модели.
Модель бота
Вводимыми данными для модели бота будет список токенизированных слов для шаблона. Мы лемматизируем каждое слово, чтобы привести его к начальной форме и составить список связанных с ним слов. Затем создадим массив из слов с обозначением 1, если в текущем шаблоне найдено либо не найдено совпадение между словами. Для вывода создадим двоичную матрицу для репрезентации каждого тега. Все это сделаем с помощью алгоритма order_data
(внутри класса BotModelCreator
), который будет выполняться в рамках инициализации (при создании класса).
Вторая и самая интересная часть — это построение модели. На входе модели бота будет список слов. В случае приветствия функция будет равна 23 (всего разных слов). Затем — вывод возможных тегов (в данном случае 2), которые были заданы. Для этой модели мы будем использовать простую нейронную сеть, но вы можете попробовать более продвинутую или сеть с другой архитектурой по вашему выбору. Архитектура представляет собой два плотных слоя 32 и 16 с функцией активации RELU и выдачей 0,5.
class BotModelCreator():
def __init__(self, bot_data, verbose=0):
self.verbose = verbose
self.classes = bot_data.classes
self.words = bot_data.words
self.documents = bot_data.documents
self.lemmatizer = bot_data.lemmatizer
self.intents = bot_data.intents
self.order_data()
self.creat_model()
def order_data(self):
training = []
output_empty = [0] * len(self.classes)
for doc in self.documents:
bag = []
pattern_words = [self.lemmatizer(word.lower()) for word in doc[0]]
for w in self.words:
bag.append(1) if w in pattern_words else bag.append(0)
output_row = list(output_empty)
output_row[self.classes.index(doc[1])] = 1
training.append([bag, output_row])
random.shuffle(training)
training = np.array(training, dtype="object")
self.train_x = list(training[:,0])
self.train_y = list(training[:,1])
if self.verbose != 0:
print("Training data created", training.shape)
def creat_model(self):
self.model = Sequential()
self.model.add(Dense(32, input_shape=(len(self.train_x[0]),), activation='relu'))
self.model.add(Dropout(0.5))
self.model.add(Dense(16, activation='relu'))
self.model.add(Dropout(0.5))
self.model.add(Dense(len(self.train_y[0]), activation='softmax'))
sgd = SGD(lr=0.001, decay=1e-6, momentum=0.9, nesterov=True)
self.model.compile(
loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy']
)
if self.verbose != 0:
print("model created")
def train_model(self):
hist = self.model.fit(
np.array(self.train_x), np.array(self.train_y),
epochs=500,
batch_size=5,
verbose=self.verbose
)
return hist
def save_models(self, out_path=""):
pickle.dump(self.words, open(
os.path.join(out_path, 'words.pkl'),
'wb'
)
)
pickle.dump(self.classes, open(
os.path.join(out_path, 'classes.pkl'),
'wb'
)
)
pickle.dump(self.lemmatizer, open(
os.path.join(out_path, 'lemmatizer.pkl'),
'wb'
)
)
self.model.save(
os.path.join(out_path, 'chatbot_model.h5'),
self.model
)
json.dump(self.intents, open(os.path.join(out_path, 'intents.json'), 'w'))
Теперь, когда у нас есть все — от последовательности ввода до алгоритмов обучения, остается только натренировать бота. Сначала нужно загрузить все модели и создать данные для бота, а затем и саму модель. Можно оставить сообщение по умолчанию равным 0 (если вы хотите регистрировать в логе информацию об обучении, измените его на 1 или 2).
Теперь нам нужно сохранить все файлы модели, которые необходимы для использования бота.
bot_data = BotIntentsReader("intents.yml")
bot_model = BotModelCreator(
bot_data=bot_data,
)
bot_model.train_model()
bot_model.save_models()
Наконец у нас есть файлы, модель обучена, но что теперь? Как взаимодействовать с получившимся ботом? Для этого нужно создать новый класс, который может загружаться из файла или передавать объект bot_model
для прогнозирования тега. Также нужен метод, который использует для ввода текст и возвращает ответ от бота. Можно добавить и защиту от незнакомых команд: если пользователь отправит предложение, в котором нет нужного слова из созданного выше списка слов, бот ответит: «Я вас не понимаю, пожалуйста, повторите».
class BotLoader():
def __init__(self, bot_model, threshold=0.25):
self.threshold = threshold
if type(bot_model)==dict:
self.lemmatizer = pickle.load(open(bot_model["lemmatizer"], 'rb'))
self.model = load_model(bot_model['model'])
self.intents = json.loads(open(bot_model['intents']).read())
self.words = pickle.load(open(bot_model['words'],'rb'))
self.classes = pickle.load(open(bot_model['classes'],'rb'))
else:
self.model = bot_model.model
self.intents = bot_model.intents
self.words = bot_model.words
self.classes = bot_model.classes
self.lemmatizer = bot_model.lemmatizer
def clean_up_sentence(self, sentence):
sentence_words = nltk.word_tokenize(sentence)
sentence_words = [self.lemmatizer(word.lower()) for word in sentence_words]
return sentence_words
def bow(self, sentence):
sentence_words = self.clean_up_sentence(sentence)
bag = [0]*len(self.words)
for s in sentence_words:
for i, w in enumerate(self.words):
if w == s:
bag[i] = 1
return (np.array(bag))
def predict_class(self, sentence):
tokens = self.bow(sentence)
if tokens.sum() > 0:
predictions = self.model.predict(np.array([tokens]))[0]
results = [[i, r] for i, r in enumerate(predictions) if r > self.threshold]
results.sort(key=lambda x: x[1], reverse=True)
return_list = []
for result in results:
return_list.append(
{"intent": self.classes[result[0]],
"probability": str(result[1])}
)
else:
return_list = None
return return_list
def getResponse(self, ints):
tag = ints[0]['intent']
for i in self.intents:
if(i['tag']== tag):
result = random.choice(i['responses'])
break
return result
def chatbot_response(self, msg):
ints = self.predict_class(msg)
if ints is not None:
res = self.getResponse(ints)
else:
res = "I don't understand you, can you repeat"
return res
Для тестирования нужно только создать объект загрузчика бота и сообщить ему некоторый текст. Теперь единственное, что остается сделать, — это добавить больше функций с тегами, шаблонами и ответами, чтобы сделать бота «умнее».
Читайте также:
- 22 сниппета на Python для повседневных задач
- Как скрывают секретные ключи и пароли в Python
- Создаем собственную Alexa в 20 строках Python
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Sebastian Correa: Make a Simple Comand BOT Python