Мы написали бота на Python, который может решить любой вопрос с вариантами ответа по картинке [с кодом]

В этом посте мы собираемся показать вам, как создать собственную систему поиска ответов с помощью Python. В принципе, эта автоматизация сможет найти ответ на практически любой вопрос с множественным выбором по картинке.

Итак, в чем суть проблемы: найти ответы в интернете во время экзамена почти невозможно, но мы можем быстро сделать фото бланка вопросов пока экзаменатор отворачивается. Это первая часть алгоритма. Затем необходимо как-то вычленить вопрос с картинки.

Казалось бы, есть много сервисов, которые могут предоставить инструменты для извлечения текста, но нам нужен какой-то API для решения этой проблемы. Наконец, Google Vision API стал именно тем инструментом, который мы ищем. Самое замечательное то, что первые 1000 вызовов API бесплатны каждый месяц, чего вполне достаточно для тестирования и использования API.

Vision AI

Сначала зайдите и создайте учетную запись Google Cloud, затем найдите Vision AI через поиск в службах. Используя Vision AI, вы можете выполнять такие действия, как назначение меток для изображения, чтобы упорядочить ваши изображения, получить рекомендуемые вершины обрезки, обнаружить известные пейзажи или места, извлечь тексты и многое другое.

Проверьте документацию, чтобы включить и настроить API. После настройки вам необходимо создать файл JSON, содержащий ваши ключи для загрузки на ваш компьютер.

Выполните следующую команду для установки клиентской библиотеки:

pip install google-cloud-vision

Затем предоставьте учетные данные для проверки подлинности кода своего приложения, установив переменную среды GOOGLE_APPLICATION_CREDENTIALS.

import os, io
from google.cloud import vision
from google.cloud.vision import types

# JSON file that contains your key
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'your_private_key.json'

# Instantiates a client
client = vision.ImageAnnotatorClient()

FILE_NAME = 'your_image_file.jpg'

# Loads the image into memory
with io.open(os.path.join(FILE_NAME), 'rb') as image_file:
    content = image_file.read()

image = vision.types.Image(content=content)

# Performs text detection on the image file
response = client.text_detection(image=image)
print(response)

# Extract description
texts = response.text_annotations[0]
print(texts.description)

Когда вы запустите код, вы увидите ответ в формате JSON, который содержит также спецификации обнаруженных текстов. Но нам нужно только чистое описание, поэтому мы извлекаем именно эту часть из ответа.

Поиск вопроса в Google

Следующим шагом является поиск вопросов в Google, чтобы получить некоторую информацию. Я использовал библиотеку регулярных выражений для извлечения вопроса из описания. Затем мы должны обрезать часть, содержащую непосредственно вопрос, чтобы именно ее и искать.

import re
import urllib

# If ending with question mark
if '?' in texts.description:
    question = re.search('([^?]+)', texts.description).group(1)

# If ending with colon
elif ':' in texts.description:
    question = re.search('([^:]+)', texts.description).group(1)
# If ending with newline
elif '\n' in texts.description:
    question = re.search('([^\n]+)', texts.description).group(1)

# Slugify the match
slugify_keyword = urllib.parse.quote_plus(question)
print(slugify_keyword)

Сканирование информации

Мы будем использовать BeautifulSoup для сканирования первых 3 результатов, чтобы получить информацию по нашему вопросу, потому что ответ, вероятно, находится в одном из них.

Кроме того, если вы хотите сканировать определенные данные из списка поиска Google, не используйте функцию «просмотреть код» для поиска атрибутов элементов, вместо этого напечатайте всю страницу, чтобы увидеть атрибуты, поскольку она отличается от той, что вы видите на экране.

Нам нужно сканировать первые 3 ссылки в результатах поиска, но эти ссылки содержат лишнюю информацию, поэтому важно получить чистые ссылки для сканирования.

/url?q=https://en.wikipedia.org/wiki/IAU_definition_of_planet&sa=U&ved=2ahUKEwiSmtrEsaTnAhXtwsQBHduCCO4QFjAAegQIBBAB&usg=AOvVaw0HzMKrBxdHZj5u1Yq1t0en                        

Как видите, фактическая ссылка находится между q= и &sa. Используя регулярку, мы можем получить конкретно эту часть или действительный URL.

result_urls = []

def crawl_result_urls():
    req = Request('https://google.com/search?q=' + slugify_keyword, headers={'User-Agent': 'Mozilla/5.0'})                       
    html = urlopen(req).read()
    bs = BeautifulSoup(html, 'html.parser')
    results = bs.find_all('div', class_='ZINbbc')
    try:
        for result in results:
            link = result.find('a')['href']
            # Checking if it is url (in case)
            if 'url' in link:
                result_urls.append(re.search('q=(.*)&sa', link).group(1))
    except (AttributeError, IndexError) as e:
        pass

Прежде чем мы просканируем содержимое этих URL, позвольте показать вам систему ответов на вопросы с Python.

Система ответов на вопросы

Это основная часть алгоритма. После сканирования информации из первых 3 результатов, программа должна определить ответ путем итерации документов. Сначала мы подумали, что лучше использовать алгоритм сходства для обнаружения документов, который наиболее похож на вопрос, но понятия не имеем, как его реализовать.

После нескольких часов исследований мы нашли статью в Medium, в которой объясняется система ответов на вопросы с Python. Это простой в использовании модуль для Python для реализации QA на ваших личных данных. Вы можете узнать подробности здесь

Давайте установим модуль:

pip install cdqa

Мы загружаем предварительно обученные модели и данные вручную, используя функции загрузки, которые включены в приведенный ниже пример кода:

import pandas as pd
from ast import literal_eval

from cdqa.utils.filters import filter_paragraphs
from cdqa.utils.download import download_model, download_bnpp_data
from cdqa.pipeline.cdqa_sklearn import QAPipeline

# Download data and models
download_bnpp_data(dir='./data/bnpp_newsroom_v1.1/')
download_model(model='bert-squad_1.1', dir='./models')

# Loading data and filtering / preprocessing the documents
df = pd.read_csv('data/bnpp_newsroom_v1.1/bnpp_newsroom-v1.1.csv', converters={'paragraphs': literal_eval})                       
df = filter_paragraphs(df)

# Loading QAPipeline with CPU version of BERT Reader pretrained on SQuAD 1.1                   
cdqa_pipeline = QAPipeline(reader='models/bert_qa.joblib')

# Fitting the retriever to the list of documents in the dataframe
cdqa_pipeline.fit_retriever(df)

# Sending a question to the pipeline and getting prediction
query = 'Since when does the Excellence Program of BNP Paribas exist?'
prediction = cdqa_pipeline.predict(query)

print('query: {}\n'.format(query))
print('answer: {}\n'.format(prediction[0]))
print('title: {}\n'.format(prediction[1]))
print('paragraph: {}\n'.format(prediction[2]))

Вывод должен выглядеть так:

Он содержит точный ответ и абзац, который включает в себя ответ.

В принципе, когда вопрос извлечен из изображения и отправлен в систему, ретривер выберет список документов из просканированных данных, которые с наибольшей вероятностью содержат ответ. Как мы уже говорили ранее, он вычисляет косинусное сходство между вопросом и каждым документом среди просканированных данных.

После выбора наиболее вероятных документов система разбивает каждый документ на параграфы и отправляет их запросом в ридер, которая в основном является предварительно обученной моделью глубокого обучения. Используемая модель была Pytorch-версией хорошо известной модели BERT NLP.

Затем ридер выводит наиболее вероятный ответ, среди тех, которые он может найти в каждом параграфе. После «Читателя» в системе есть ещё последний слой, который сравнивает ответы с использованием внутренней функции оценки и выводит наиболее вероятный ответ в соответствии с оценками.

Вот схема этого механизма.

Вы должны установить свой датафрейм в формате CSV в определенной структуре, чтобы он мог быть отправлен в конвейер cdQA.

Но на самом деле я использовал конвертер PDF для создания входного фрейма данных из каталог файлов PDF. Итак, мы собираемся сохранить все просканированные данные в PDF-файл для каждого результата. Ожидается, что у нас будет всего 3 файла PDF (может быть 1 или 2). Дополнительно, нам нужно назвать эти PDF-файлы, поэтому я просканировал заголовок каждой страницы.

def get_result_details(url):
    try:
        req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        html = urlopen(req).read()
        bs = BeautifulSoup(html, 'html.parser')
        try:
            # Crawl any heading in result to name pdf file
            title =  bs.find(re.compile('^h[1-6]$')).get_text().strip().replace('?', '').lower()
            # Naming the pdf file
            filename = "/home/coderasha/autoans/pdfs/" + title + ".pdf"
            if not os.path.exists(os.path.dirname(filename)):
                try:
                    os.makedirs(os.path.dirname(filename))
                except OSError as exc: # Guard against race condition
                    if exc.errno != errno.EEXIST:
                        raise
            with open(filename, 'w') as f:
                # Crawl first 5 paragraphs
                for line in bs.find_all('p')[:5]:
                    f.write(line.text + '\n')
        except AttributeError:
            pass
    except urllib.error.HTTPError:
        pass

def find_answer():
    df = pdf_converter(directory_path='/home/coderasha/autoans/pdfs')
    cdqa_pipeline = QAPipeline(reader='models/bert_qa.joblib')
    cdqa_pipeline.fit_retriever(df)
    query = question + '?'
    prediction = cdqa_pipeline.predict(query)

    print('query: {}\n'.format(query))
    print('answer: {}\n'.format(prediction[0]))
    print('title: {}\n'.format(prediction[1]))
    print('paragraph: {}\n'.format(prediction[2]))
    return prediction[0]

Что ж, если подвести итоги алгоритма, он извлечет вопрос из картинки, выполнит поиск в Google, просканирует первые 3 результата, создаст 3 файла PDF из просканированных данных и, наконец, найдет ответ, используя систему ответов на вопросы.

Весь код можно посмотреть ниже:

import os, io
import errno
import urllib
import urllib.request
import hashlib
import re
import requests
from time import sleep
from google.cloud import vision
from google.cloud.vision import types
from urllib.request import urlopen, Request
from bs4 import BeautifulSoup
import pandas as pd
from ast import literal_eval
from cdqa.utils.filters import filter_paragraphs
from cdqa.utils.download import download_model, download_bnpp_data
from cdqa.pipeline.cdqa_sklearn import QAPipeline
from cdqa.utils.converters import pdf_converter

result_urls = []

os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'your_private_key.json'

client = vision.ImageAnnotatorClient()

FILE_NAME = 'your_image_file.jpg'

with io.open(os.path.join(FILE_NAME), 'rb') as image_file:
    content = image_file.read()

image = vision.types.Image(content=content)

response = client.text_detection(image=image)

texts = response.text_annotations[0]
# print(texts.description)


if '?' in texts.description:
    question = re.search('([^?]+)', texts.description).group(1)

elif ':' in texts.description:
    question = re.search('([^:]+)', texts.description).group(1)

elif '\n' in texts.description:
    question = re.search('([^\n]+)', texts.description).group(1)

slugify_keyword = urllib.parse.quote_plus(question)
# print(slugify_keyword)

def crawl_result_urls():
    req = Request('https://google.com/search?q=' + slugify_keyword, headers={'User-Agent': 'Mozilla/5.0'})                                
    html = urlopen(req).read()
    bs = BeautifulSoup(html, 'html.parser')
    results = bs.find_all('div', class_='ZINbbc')
    try:
        for result in results:
            link = result.find('a')['href']
            print(link)
            if 'url' in link:
                result_urls.append(re.search('q=(.*)&sa', link).group(1))
    except (AttributeError, IndexError) as e:
        pass

def get_result_details(url):
    try:
        req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        html = urlopen(req).read()
        bs = BeautifulSoup(html, 'html.parser')
        try:
            title =  bs.find(re.compile('^h[1-6]$')).get_text().strip().replace('?', '').lower()
            # Set your path to pdf directory
            filename = "/path/to/pdf_folder/" + title + ".pdf"
            if not os.path.exists(os.path.dirname(filename)):
                try:
                    os.makedirs(os.path.dirname(filename))
                except OSError as exc:
                    if exc.errno != errno.EEXIST:
                        raise
            with open(filename, 'w') as f:
                for line in bs.find_all('p')[:5]:
                    f.write(line.text + '\n')
        except AttributeError:
            pass
    except urllib.error.HTTPError:
        pass

def find_answer():
    # Set your path to pdf directory
    df = pdf_converter(directory_path='/path/to/pdf_folder/')
    cdqa_pipeline = QAPipeline(reader='models/bert_qa.joblib')
    cdqa_pipeline.fit_retriever(df)
    query = question + '?'
    prediction = cdqa_pipeline.predict(query)

    # print('query: {}\n'.format(query))
    # print('answer: {}\n'.format(prediction[0]))
    # print('title: {}\n'.format(prediction[1]))
    # print('paragraph: {}\n'.format(prediction[2]))
    return prediction[0]

crawl_result_urls()

for url in result_urls[:3]:
    get_result_details(url)
    sleep(5)

answer = find_answer()
print('Answer: ' + answer)

Иногда система может сбить с с толку, но в целом все нормально. По крайней мере, можно сдать экзамен, набрав 60% правильных ответов 😀

Отличная работа, разработчики! Пожалуйста, напишите в комментариях, что вы думаете об этом? На самом деле лучше перебирать все вопросы сразу, тогда нам не нужно было бы делать снимки каждого вопроса.