Архив за месяц: Декабрь 2019

Пример запуска Scrapy отдельным скриптом, без создания проекта

Scrapy представяет собой серъёзный фреймворк для обхода сайтов и извлечения из них структурированной информации. Отличается большим диапазоном решаемых задач (сбор данных, мониторинг, автоматическое тестирование, итд) а также отличной скоростью (благодаря асинхронным вызовам). Использование scrapy подразумевает создание отдельного проекта, со своей заданной структурой и настройкой логики/методов обхода в отдельных python классах scrapy.Spider .
Но что если мы хотим использовать все плюшки большого фремворка, не создавая непосредственно проект? Скажем надо сделать маленькую подзадачу в другом проекте: залогиниться, перейти на заданную страницу и вытащить что-то из html кода?
Столкнувшись с данной задачей мне не удалось быстро найти решение на просторах интернета, поэтому опишу здесь что у меня получилось:

# Устанавливаем библиотеку
pip3 install scrapy

Сценарий следующий:
1. Заходим на стартовую страницу сайта (SCRAPY_START_URL), на которой находится форма входа
2. После успешного залогинивания переходим на вторую страницу с данными SCRAPY_SECOND_URL
3. В функции action из html кода получаем необходимые данные, путём указания пути через аналоги css-селекторов
* Для вызова всего кода используем функцию get_data_by_id, которая по data_id конкретизирует вторую страницу.
* Во время исполнения будет создаваться временный файл items.json, который будет перезаписываться после каждого вызова (он сейчас специально удаляется, если не удалять, то новые результаты будут записываться в конец файла). items.json создается в Scrapy по умолчанию, и как-то без него скорее всего нельзя, но можно задать формат, если вдруг json вам не по душе.
* пример ниже:

import json
import os
import scrapy
from scrapy.crawler import CrawlerProcess, CrawlerRunner
from twisted.internet import reactor
from multiprocessing import Process, Queue


SCRAPY_START_URL = 'https://example.com/'
SCRAPY_USER = 'user_name'
SCRAPY_PASS = 'some_pass'
SCRAPY_SECOND_URL = 'https://example.com/data/'


class CPSpider(scrapy.Spider):
    name = "crawler_name_here"
    start_urls = [SCRAPY_START_URL]
    second_url = None

    def parse(self, response):
        yield scrapy.FormRequest.from_response(
            response,
            formxpath='//form[@id="loginForm"]',
            formdata={
                'auth_signin[username]': SCRAPY_USER,
                'auth_signin[password]': SCRAPY_PASS,
                'Action': '1',
            },
            callback=self.after_login)

    # запускается после успешного логина
    # переходим на странцу, откуда надо стянуть данные
    def after_login(self, response):
        yield scrapy.Request(
            url=CPSpider.second_url,
            callback=self.action)

    # вытаскиваем данные из html кода с помощью xpath
    def action(self, response):
        final_result_txt = response.xpath('//span[@class="data_display"]/b/text()').extract_first(default='some_default')
        print(final_result_txt)

        # создадим словарь и запишем полученный резальтат
        item = dict()
        item['final_result'] = final_result_txt
        return item


def get_data_by_id(data_id):
    if os.path.isfile('items.json'):
        os.remove('items.json')

    CPSpider.reseller_url = SCRAPY_SECOND_URL + str(data_id)

    # а вот здесь хитрая обертка
    # благодаря таким фокусам можно не запускать основное приложение
    def run_spider(spider):
        def f(q):
            try:
                runner = CrawlerRunner(settings={
                    # установим уровень ошибок выше дефолта
                    # чтобы не сыпалось много отладочного вывода
                    # если надо отдебажть, то ставим DEBUG
                    'LOG_LEVEL': 'ERROR',
                    'FEED_FORMAT': 'json',
                    'FEED_URI': 'items.json'
                })
                deferred = runner.crawl(spider)
                deferred.addBoth(lambda _: reactor.stop())
                reactor.run()
                q.put(None)
            except Exception as e:
                q.put(e)

        q = Queue()
        p = Process(target=f, args=(q,))
        p.start()
        result = q.get()
        p.join()

        if result is not None:
            raise result

    try:
        run_spider(CPSpider)
        # считываем результат, полученнный после работы scrapy
        with open('items.json') as json_file:
            res = json.load(json_file)
        if len(res):
            return res[0]['final_result']
    except BaseException as e:
        return 'error'

Сравнение 3ёх фреймворков для машинного обучения: Keras, TensorFlow и PyTorch

Keras, TensorFlow и PyTorch входят в тройку наиболее используемых DL фреймворков. Эти библиотеки используют и как продвинутые дата-инженеры, так и новички в области глубокого обучения. Чтобы облегчить Вам выбор библиотеки, здесь приведем сравнение между троицей Keras, TensorFlow и PyTorch.

Keras — это открытая библиотека для построения нейронных сетей. Написана на Python. По сути это обертка поверх TensorFlow. Идеалогически построена для быстрого прототипирования глубоких нейронных сетей.

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

Pytorch — основное отличие заключается в том, что разработана была в Facebook AI, поэтому имеет несколько другой интерфейс. Однако многим разработчикам нравится именно библиотека, хоть она и слабее распространена

Параметры сравнения библиотек
* простота использования
* скорость работы
* архитектура
* легкость отладки
* наличие подготовленных наборов данных
* популярность

1.простота использования
Keras — обладает наиболее высокоуровневым API, позволяет работать поверх TensorFlow, CNTK и Theano. Завоевал популярность благодаря синтаксической простоте, способствуя быстрой разработке.
TensorFlow — является одноверменно и высокоуровневой библиотекой так и позволяет вносить изменения в нейронную сейть на весьма низком уровне.
Pytorch — предоставляет низкоуровневый API, ориентированный непосредственно на работу с массивами матриц. Он приобрел огромный интерес в академической среде и в научных исследованиях по глубокому обучению.

2. Скорость/производительность
Производительность в Keras немного ниже чем в Tensorflow и PyTorch, которые обеспечивают
примерно одинаковую скорость, подходящую для серъёзных нагруженных проектов.

3. архитектура
В Keras реализована относительно простая архитектуру, является более читабельной и краткой. С этой стороны, Tensorflow более сложен, хоть Keras и базируется на нем. PyTorch же имеет сложную архитектуру и читаемость кода на нем меньше в сравнении с Keras

4. легкость отладки
В Keras обычно очень редко возникает необходимость отладки простых сетей. Большой набор примеров и армия разработчиков на Keras позволит быстро локализовать ошибку.
В Tensorflow отладка наверняка займет больше времени.
Pytorch обладает хорошим набором инструментов для отладки по сравнению с двумя другими.

5. Работа с датасетами
Keras обычно используется для небольших наборов данных, поскольку он несколько медленнее. А вот TensorFlow и PyTorch используются для высокопроизводительных моделей и большие датасеты не должны вызвать затруднения.

6. популярность
В связи с растущим спросом на технологию глубокого машинного обучения эти три фреймворка приобрели огромную популярность среди python разработчиков. Согласно гугл-трендам, Keras возглавляет список и становится самым популярным, за ним следуют TensorFlow и PyTorch с некоторым отставанием.

Подведем итоги
Для этого рассмотрим ситуации, при которых предпочтительнее будет выбрать ту или другую библиотеку.

Keras наиболее подходит когда для Вас важны:
* Быстрое прототипирование
* При малом наборе данных
* Поддержка сообщества

TensorFlow наиболее подходит когда для Вас важны:
* Работа с большими наборами данных
* Высокая производительность
* функциональность

PyTorch наиболее подходит когда для Вас важны:
* гибкость
* малая продолжительность обучения библиотеки
* быстрая отладка

Пример нахождения косинусного расстония между текстами в sklearn

Косинусное расстояние — это распространенная метрика поиска схожих объектов, часто в разряженном пространстве. Поэтому косинусное расстоние часто применяют для сравнения текстов. Ниже приведу код для поиска наиболее похожего текста с помощью встроенных функций sklearn.
Пусть у нас есть большое количество текстов, предобработаем тексты с помощью tf-idf, после этого возьмем новый текст и найдем 3 самых похожих.

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel

# зададим массив текстов
some_texts = [
   'текст номер один',
   'текст следующий под номером два',
   'третий набор слов',
   'что-то ещё для полноценного дата-сета',
   'пример текстового документа',
]
df = pd.DataFrame({'texts': some_texts})

# А к данному тексту будем искать наиболее похожий из заданного выше набора
find_nearest_to = "текст номер три"

# формирование весов tf-idf
tfidf = TfidfVectorizer()
mx_tf = tfidf.fit_transform(some_texts)
new_entry = tfidf.transform([find_nearest_to])

# расчет косинусного расстояния
cosine_similarities = linear_kernel(new_entry, mx_tf).flatten()

# запишем все попарные результаты сравнений
df['cos_similarities'] = cosine_similarities
# и отсортируем по убыванию (т.к. cos(0)=1)
df = df.sort_values(by=['cos_similarities'], ascending=[0])

# Выведем 3 самых близких результата
for index, row in df[0:3].iterrows():
    print(row['texts'], row['cos_similarities'])
# output:
# текст номер один 0.7891589913464455
# текст следующий под номером два 0.23490553492076713
# третий набор слов 0.0

Заметим, что в данном примере мы не использовали cosine_similarity, а почему-то взяли linear_kernel. Дело в том, что когда вектора нормализованы (а они нормализованы после TfidfVectorizer) результат будет одинаковый, однако linear_kernel требует меньше вычислительной мощности и вычислится быстрее. Особенно это будет хорошо заметно на больших данных. Если же вектора не нормализованы, то правильно использовать sklearn.metrics.pairwise.cosine_similarity .

Пример также можно рассматривать как рекомендательную систему на большом корпусе каких-то объектов, работает быстро и хорошо подходит для бейзлайна. В случае с объектами не текстовыми, рекомендуется также попробовать использовать CountVectorizer, вместо TfidfVectorizer.

Пример классификации текстов на python с помощью fasttext

Fasttext это библиотека от фейсбука для создания векторного представления слов а также для классификации небольших текстов, а точнее — предложений. Основное отличие от самого известного векторного представления word2vec заключается в том, что каждое слово дополнительно дробится на символьные n-граммы, что позволяет решить проблему редких слов. Достоинством данного эмбеддинга также является высокая скорость работы и оптимизация под небольшие ресурсы (специально была оптимизирована под мобильные устройства).

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

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

pip3 install fasttext

Непосредственно пример:

import pandas as pd
import fasttext


# зададим набор текстов и меток классов
some_texts = [
   'текст номер один',
   'текст следующий под номером два',
   'третий набор слов',
   'что-то ещё для полноценного дата-сета',
   'пример текстового документа',
]
some_labels = [1, 1, 0, 0, 1]

# А теперь одно из разачарований имплементации именно этой библиотеки:
# Для обучения придется сделать файл, где целевой класс должен начинаться с __label__
df_train = pd.DataFrame({'txt': some_texts, 'target': some_labels})
df_train['target'] = df_train['target'].apply(lambda x: '__label__' + str(x))
df_train[['target', 'txt']].to_csv('train_data.txt', header=False, index=False, sep="\t")

# обучаем на 20 эпохах, полный набор гиперпараметров можно взглянуть на официальном сайте fasttext.cc
model = fasttext.train_supervised(input='train_data.txt', epoch=20)

# указываем чтобы отдавало вероятности обоих классов, по умолчанию k=1
p = model.predict('текст номер четыре', k=2)

print(p)
# (('__label__1', '__label__0'), array([0.5010367 , 0.49898329]))

Второе разачарование заключается в том, что в итоговом предсказании выдаётся сначала самый вероятный класс, и если требуется получить вероятность по конкретному классу, то нужно сначала узнать индекс нужного класса и только после этого по индексу взять вероятность. Для 2ух классов можно сделать примерно так:

    if p[0][0] == '__label__0':
        real_probability = p[1][1]
    else:
        real_probability = p[1][0]

В целом данный пример предлагается использовать для быстрого получения результата в задаче классификации текстов. Я попробовал код выше на соревновании mlbootcamp . ru по классификации экспертных вопросов и он показал результат из коробки лучше чем tf-idf: 0.73 fasttext против 0.68 tf-idf + lgbm