Программирование ботов telegram python

Пишем чат-бот на Python + PostgreSQL и Telegram

Пошаговое руководство написания чат-бота на языке Python.

  • Установим Python и библиотеки;
  • Получим вопросы и ответы из БД PostgreSQL;
  • Подключим морфологию;
  • Подключим чат-бот к каналу Telegram.

Colaboratory от Google

Изучение Python можно начать используя сервис Colaboratory от Google, или просто Colab. Сервис позволяет писать и выполнять код Python в браузере, не требуя собственного сервера.

Пример кода. Вопросы и ответы для чат-бота подгрузим с https://drive.google.com из текстового файла

# Однократно после запуска виртуальной машины устанавливаем библиотеки pymorphy2 и numpy !pip install pymorphy2 numpy # ---------------------------- # подключим библиотеки import csv import pymorphy2 import re morph = pymorphy2.MorphAnalyzer(lang='ru') # Массив вопросов questions=[] # Массив ответов answer=[] # Подключаем файл с Google диска, содержащий вопросы/ответы # Есть ли жизнь на марсе;Есть with open("/content/drive/MyDrive/robo-bot/question.txt", "r") as f_obj: reader = csv.reader(f_obj) for row in reader: s=" ".join(row) r=s.split(';') questions.append(r[0]) answer.append(r[1]) # выведем список вопросов и ответов print (questions) print (answer) 

Запуск в Production

Наигравшись с кодом в Colaboratory и освоив Python развернем систему на боевом сервере Debian

Установим Python и PIP (установщик пакетов).

Так как Debian не самый новый, устанавливается версия 3.5

aptitude install python3 python3-pip # обновим пакеты если они были установлены ранее pip3 install --upgrade setuptools pip

Установим необходимые пакеты Python

# Из за устаревшей версии Debian установить psycopg2 не удалось, поставлен скомпилированный psycopg2-binary # Библиотека psycopg2 нужна для подключения к базе данных PostgreSQL # pip3 install psycopg2 pip3 install psycopg2-binary scikit-learn numpy pymorphy2

Пишем код в файле Chat_bot.py

# Импортируем библиотеки import pymorphy2 import re import psycopg2 import sklearn import numpy as np # Подключаемся к PostgreSQL conn = psycopg2.connect(dbname='energy', user='mao', password='darin', host='localhost') cursor = conn.cursor() # Настраиваем язык для библиотеки морфологии morph = pymorphy2.MorphAnalyzer(lang='ru') # объявляем массив кодов ответов и ответов answer_id=[] answer = dict() # получаем из PostgreSQL список ответов и проиндексируем их. # Работая с PostgreSQL обращаемся к схеме app, в которой находятся таблицы с данными cursor.execute('SELECT id, answer FROM app.chats_answer;') records = cursor.fetchall() for row in records: answer[row[0]]=row[1] 

Структура таблицы ответов chats_answer, формат SQL

CREATE TABLE app.chats_answer ( id SERIAL, answer VARCHAR(512), CONSTRAINT chats_answer_pkey PRIMARY KEY(id) ) WITH (oids = false); ALTER TABLE app.chats_answer OWNER TO mao;

Да, я готов об этом поговорить

Читайте также:  Html поисковая строка примеры

Структура таблицы вопросов chats_question, формат SQL. Каждый вопрос связан с кодом ответа.

CREATE TABLE app.chats_question ( id SERIAL, question VARCHAR(512), answer_id INTEGER, CONSTRAINT chats_question_pkey PRIMARY KEY(id) ) WITH (oids = false); ALTER TABLE app.chats_question OWNER TO mao;

Продолжаем код в файле Chat_bot.py

# объявляем массив вопросов questions=[] # загрузим вопросы и коды ответов cursor.execute('SELECT question, answer_id FROM app.chats_question;') records = cursor.fetchall() # посчитаем количество вопросов transform=0 for row in records: # Если текст вопроса не пустой if row[0]>"": # Если в БД есть код ответа на вопрос if row[1]>0: phrases=row[0] # разбираем вопрос на слова words=phrases.split(' ') phrase="" for word in words: # каждое слово из вопроса приводим в нормальную словоформу word = morph.parse(word)[0].normal_form # составляем фразу из нормализованных слов phrase = phrase + word + " " # Если длинна полученной фразы больше 0 добавляем ей в массив вопросов и массив кодов ответов if (len(phrase)>0): questions.append(phrase.strip()) answer_id.append(row[1]) transform=transform+1 # выведем на экран вопросы, ответы и коды ответов print (questions) print (answer) print (answer_id) # Закроем подключение к PostgreSQL cursor.close() conn.close()

Векторизация и трансформация

# Векторизируем вопросы в огромную матрицу # Перемножив фразы на слова из которых они состоят получим числовые значения from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import TruncatedSVD vectorizer_q = TfidfVectorizer() vectorizer_q.fit(questions) matrix_big_q = vectorizer_q.transform(questions) print ("Размер матрицы: ") print (matrix_big_q.shape) # Трансформируем матрицу вопросов в меньший размер для уменьшения объема данных # Трансформировать будем в 200 мерное пространство, если вопросов больше 200 # Размерность подбирается индивидуально в зависимости от базы вопросов, которая может содержать 1 млн. или 1к вопросов и 1 # Без трансформации большая матрицу будет приводить к потерям памяти и снижению производительности if transform>200: transform=200 svd_q = TruncatedSVD(n_components=transform) svd_q.fit(matrix_big_q) # получим трансформированную матрицу matrix_small_q = svd_q.transform(matrix_big_q) print ("Коэффициент уменьшения матрицы: ") print ( svd_q.explained_variance_ratio_.sum()) 
# Тело программы поиска ответов from sklearn.neighbors import BallTree from sklearn.base import BaseEstimator def softmax(x): #создание вероятностного распределения proba = np.exp(-x) return proba / sum(proba) class NeighborSampler(BaseEstimator): def __init__(self, k=5, temperature=10.0): self.k=k self.temperature = temperature def fit(self, X, y): self.tree_ = BallTree(X) self.y_ = np.array(y) def predict(self, X, random_state=None): distances, indices = self.tree_.query(X, return_distance=True, k=self.k) result = [] for distance, index in zip(distances, indices): result.append(np.random.choice(index, p=softmax(distance * self.temperature))) return self.y_[result] from sklearn.pipeline import make_pipeline ns_q = NeighborSampler() # answer_id - код ответа в массиве, который получается при поиске ближайшего ответа ns_q.fit(matrix_small_q, answer_id) pipe_q = make_pipeline(vectorizer_q, svd_q, ns_q)
# код для проверки работы из консоли print("Пишите ваш вопрос, слова exit или выход для выхода") request="" while request not in ['exit', 'выход']: # получим текст от ввода request=input() # разберем фразу на слова words= re.split('\W',request) phrase="" for word in words: word = morph.parse(word)[0].normal_form # морфируем слово вопроса в нормальную словоформу # Нормализуем словоформу каждого слова и соберем обратно фразу phrase = phrase + word + " " # запустим функцию и получим код ответа reply_id = int(pipe_q.predict([phrase.strip()])) # выведем текст ответа print (answer[reply_id]) 

Подключим Telegram

# установим не самую последнюю версию для валидности дальнейшего кода #pip3 install PyTelegramBotAPI pip3 install PyTelegramBotAPI==3.6.7

Откроем Telegram и обратимся к боту @BOTFATHER https://t.me/botfather

Все просто, зарегистрируем нового бота и получим token.

import telebot telebot.apihelper.ENABLE_MIDDLEWARE = True # Укажем token полученный при регистрации бота bot = telebot.TeleBot("9999999999:AABBCCDDEEFFGGQWERTYUIOPASDFGHJKLLK") # Начнем обработку. Если пользователь запустил бота, ответим @bot.message_handler(commands=['start']) def start_message(message): bot.send_message(message.from_user.id, " Здравствуйте. Я виртуальный бот Mao!") # Если пользователь что-то написал, ответим @bot.message_handler(func=lambda message: True) def get_text_messages(message): request=message.text # разобьём фразу на массив слов, используя split. '\W' - любой символ кроме буквы и цифры words= re.split('\W',request) phrase="" # разберем фразу на слова, нормализуем каждое и соберем фразу for word in words: word = morph.parse(word)[0].normal_form phrase = phrase + word + " " # получим код ответа вызывая нашу функцию reply_id = int(pipe_q.predict([phrase.strip()])) # отправим ответ bot.send_message(message.from_user.id, answer[reply_id]) # продублируем ответ пользователю с bot.send_message(99999999, str(message.from_user.id) + "\n" + str(message.from_user.first_name) + " " + str(message.from_user.last_name) + " " +str(message.from_user.username) + "\n"+ str(request) + "\n"+ str(answer[reply_id])) # выведем в консоль вопрос / ответа print("Запрос:", request, " \n\tНормализованный: ", phrase, " \n\t\tОтвет :", answer[reply_id]) # Запустим обработку событий бота bot.infinity_polling(none_stop=True, interval=1) 

В целом все готово. Вопросы в базу данных добавляются автоматически от службы тех. поддержки. Остаётся маркетологу в админ панели на YII назначать ответы вопросам. Раз в сутки cron перезапускает скрипт чат-бота, новые фразы поступают в работу.

Весь код чат бота

import csv import pymorphy2 import re import psycopg2 conn = psycopg2.connect(dbname='energy', user='mao', password='daring', host='localhost') cursor = conn.cursor() morph = pymorphy2.MorphAnalyzer(lang='ru') answer_id=[] answer = dict() cursor.execute('SELECT id, answer FROM app.chats_answer;') records = cursor.fetchall() for row in records: answer[row[0]]=row[1] questions=[] cursor.execute('SELECT question, answer_id FROM app.chats_question;') records = cursor.fetchall() transform=0 for row in records: if row[0]>"": if row[1]>0: phrases=row[0] words=phrases.split(' ') phrase="" for word in words: word = morph.parse(word)[0].normal_form phrase = phrase + word + " " if (len(phrase)>0): questions.append(phrase.strip()) answer_id.append(row[1]) transform=transform+1 #print (questions) #print (answer) #print (answer_id) cursor.close() conn.close() import sklearn from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import TruncatedSVD vectorizer_q = TfidfVectorizer() vectorizer_q.fit(questions) matrix_big_q = vectorizer_q.transform(questions) print ("Размер матрицы: ") print (matrix_big_q.shape) if transform>200: transform=200 print(transform) svd_q = TruncatedSVD(n_components=transform) svd_q.fit(matrix_big_q) matrix_small_q = svd_q.transform(matrix_big_q) print ("Коэффициент уменьшения матрицы: ") print ( svd_q.explained_variance_ratio_.sum()) # тело программы k=5, temperature=10.0 можно подбирать import numpy as np from sklearn.neighbors import BallTree from sklearn.base import BaseEstimator def softmax(x): #создание вероятностного распределения proba = np.exp(-x) return proba / sum(proba) class NeighborSampler(BaseEstimator): def __init__(self, k=5, temperature=10.0): self.k=k self.temperature = temperature def fit(self, X, y): self.tree_ = BallTree(X) self.y_ = np.array(y) def predict(self, X, random_state=None): distances, indices = self.tree_.query(X, return_distance=True, k=self.k) result = [] for distance, index in zip(distances, indices): result.append(np.random.choice(index, p=softmax(distance * self.temperature))) return self.y_[result] from sklearn.pipeline import make_pipeline ns_q = NeighborSampler() ns_q.fit(matrix_small_q, answer_id) pipe_q = make_pipeline(vectorizer_q, svd_q, ns_q) import re import telebot telebot.apihelper.ENABLE_MIDDLEWARE = True bot = telebot.TeleBot("299999999:sdfgnreognrtgortgmrtgmrtgm") @bot.message_handler(commands=['start']) def start_message(message): bot.send_message(message.from_user.id, " Здравствуйте. Я виртуальный помощник Mao?") @bot.message_handler(func=lambda message: True) def get_text_messages(message): request=message.text words= re.split('\W',request) phrase="" for word in words: word = morph.parse(word)[0].normal_form phrase = phrase + word + " " reply_id = int(pipe_q.predict([phrase.strip()])) bot.send_message(message.from_user.id, answer[reply_id]) print("Запрос:", request, " \n\tНормализованный: ", phrase, " \n\t\tОтвет :", answer[reply_id]) bot.infinity_polling(none_stop=True, interval=1) print("Пишите ваш вопрос, слова exit или выход для выхода") request="" while request not in ['exit', 'выход']: request=input() words= re.split('\W',request) phrase="" for word in words: word = morph.parse(word)[0].normal_form phrase = phrase + word + " " reply_id = int(pipe_q.predict([phrase.strip()])) print (answer[reply_id]) 

В теле программы есть переменные k=5 и temperature=10.0. Их можно менять, что будет влиять на поиск, делая его более мягким или более жестким.

P.S. Умышленно привожу весь код для практики. Теорию машинного обучения с картинками можно почитать, например, в другой статье.

Источник

Оцените статью