Расширяем существующий scikit-learn классификатор

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

Кратко одну из последних задач можно описать следующим образом: мы поставляем «в коробке» классификатор с пятью стандартными классами документов, а клиент хочет иметь возможность разделять поток документов на шесть классов: пять наших и один свой.

В этой статье я покажу один из возможных вариантов решения этой задачи на примере классического набора данных 20 newsgroup dataset:

  1. Создадам новый классификатор новостей из категорий «rec.sport.baseball», «talk.politics.guns» и «comp.sys.ibm.pc.hardware» (далее, модель_1)
  2. Расширю этот классификатор таким образом, чтобы он мог определять новости по теме «sci.electronics» (далее, модель_2).

Считаем, что модель_1 поставляется «в коробке», а клиенту требуется помимо новостей про бейсбол, оружие и PC иметь возможность классифицировать новости про электронику.

Есть несколько вариантов решения этой проблемы.

Клиент передаёт нам статьи про электронику

Сейчас это работает именно так, но это не масштабируется – отвлекаться на каждого клиента нет ни времени, ни желания.

Мы передаём клиенту статьи про бейсбол, оружие и PC

Считаем, что эти статьи конфицециальные и передавать их кому-либо в открытом виде нам запрещает NDA (с реальными документами так и есть).

Шифровать статьи про бейсбол, оружие и PC и передавать их клиенту

Считаем, что у нас есть быстрый алгоритм шифрования (хэширования) текстов. Здесь возможны два варианта.

Расшифровать тексты в процессе работы приложения и обучить на расшифрованных данных и данных клиента классификатор

Здесь основная пробема в том, что в памяти эти статьи будут в незашифрованном виде и в теории их можно получить в виде простого текста – не годится.

Зашифровать статьи клиента тем же алгоритмом, как наш набор данных и обучать классификатор на зашифрованных данных

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

Обучать классификатор на сгенерированных на существующем классификаторе текстах

Здесь так же возможны два варианта с использованием векторизатора (в моём случае TfidfVectorizer) и классификатора (SGDClassifier).

Генерация текстов для каждого класса в существующей модели и добавление к ним новых текстов для нового класса

Упрощённый алгоритм выглядит следующим образом:

train_samples = []

for document_class in classifier:
words := get_specific_words_from_vectorizer_and_classifier()
samples := generate_N_samples_for_class()
train_samples.append(samples)
train_samples.append(new_samples)

model.fit(train_samples)


Проверил этот вариант и получил F1-меру ниже, чем при обучении на «реальных» данных. Но в этом случае нет необходимости передавать чужие данные клиентам. В принципе, этот вариант вполне неплох.

Генерация текстов из данных модели и обучить бинарный классификатор: существующие тексты vs новые тексты

В этом случае потребуется создать копию существующего классификатора и переписать некоторые свойства:

train_samples := []
words := get_all_words_from_vectorizer_and_model
samples := generate_samples_for_existing_classes_and_save_them_as_an_other_class()
train_samples.append(samples)
train_samples.append(new_samples_with_new_class_name)
new_model.fit(train_samples)
clone_model := copy(model)
clone_model.update_params_from_model(new_model)


Реализацию этого подхода можно посмотреть здесь.

TIL: При работе в git клиенте регулярно всплывает форма логина GitHub

Некоторое время назад возникла проблема с визуальным git клиентом: регулярно с некоторой периодичностью всплывала форма логина Github и требовала ввести логин и пароль. Ввод реквизитов никак не решал проблему – через какое-то время форма опять появлялась.

Гуглинг вывел на тему в поддержке Github. Судя по всему, эта проблема может быть связана с тем, что добавлен remote через клонирование по HTTPS адресу.

«Но ведь у меня не должно быть проектов с github на компе О_о» думал я открывая git клиент. И действительно их нет. Но оказалось, что когда-то давно к текущему проекту был добавлен remote указывающий на github:

> git remote -v
github  https://github.com/feeeper/keras-unet-signature-extractor.git (fetch)
github  https://github.com/feeeper/keras-unet-signature-extractor.git (push)
origin  https://localgit/another_project (fetch)
origin  https://localgit/another_project (push)

Github предлагает закешировать пароль, но я сделал проще – удалил remote за ненадобностью:

> git remote remove github
> git remote -v
origin  https://localgit/another_project (fetch)
origin  https://localgit/another_project (push)

С тех пор этот диалог больше не тревожил меня.

Ротация логов в Python

Недавно возникла задача сделать ротацию логов в веб-сервисе на Tornado. До начала работы казалось, что вопрос должен быть давно изучен и решение будет очевидным.

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

Продолжить чтение «Ротация логов в Python»

Разработка рекомендательной системы на Python

Думаю, никого сегодня не удивить рекомендательными системами. Их можно встретить повсюду: на сайте с книгами (ozon.ru), блогах (habr.ru), интернет-магазинах (практически любой), стриминговых музыкальных (spotify, Яндекс.Музыка) и видео сервисах (kinopoisk, ivi, amediateka). Самый продуктивный способ разобраться как работают подобные алгоритмы — написать самому с самого начала. Этому и будет посвящена текущая статья.

В рекомендательных системах есть два основных подхода:

  1. Content-based рекомендации основанные на описании объектов, которые требуется рекомендовать (рекомендуем фильмы, похожие на те, которые понравились пользователю);
  2. Коллаборативная фильтрация основанная на оценках пользователя и похожести его на других пользователей.

Первый подход более трудоёмкий и сильно привязан к конкретной предметной области. Второй — значительно проще, но имеет недостатки. Например, проблема “холодного старта”: что рекомендовать новым пользователям, которые не оценили ни одного фильма или кому рекомендовать фильм, у которого ещё нет оценок?

В этой статье определим рекомендации на основе коллаборативной фильтрации.

Продолжение на makesomecode.me

 

[Перевод] Прощай, Объектно-Ориентированное программирование

Перепост статьи с makesomecode.me

Оригинал

Уже десятилетия я программирую с на объектно-ориентированных языках программтрования. Первым объектно-ориентированным языком, который я использовал, был C++. После этого был Smalltalk и, в конце, .NET и Java.

Я фанатично использовал преимущество Наследования, Инкапсуляции и Полиморфизма. Три столпа Объектно-ориентированного программирования.

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

Я не мог сдержать волнения при мысли, что можно сопоставить объекты реального мира с моими классами и ожидал, что всё встанет на свои места.

Ещё никогда я так не ошибался.

Наследование. Первый столп падёт.

На первый взгляд, Наследование кажется самым главным преимуществом ООП. Все упрощённые примеры с иерархией фигур выглядят логичными.

goodbye-oop

И Переиспользование — это слово дня. Нет… пусть будет словом года и даже больше.

Я повёлся на это и ринулся в мир с новыми знаниями.

Проблема банана, обезьяны, джунглей

С верой в сердце и задачами, требующими решения, я начал выстраивать иерархии классов и писать код. И всё в мире было отлично.

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

Появился новый проект и я вспомнил про Класс, который я так любил на предыдущем проекте.

Без проблем. Переиспользование — маё спасение. Всё что мне требуется — всего лишь перенести класс из старого проекта в новый.

Ну… На самом деле… не только этот Класс. Нам потребуется ещё родительский класс. Но… И это ещё не всё.

Тьфу… Погодите… Похоже, нам ещё потребуется родительский класс родительского класса… И ещё… Нам потребуются ВСЕ родительские классы. Хорошо… Я справляюсь с этим. Не проблема.

Великолепно. Теперь это не компилируется. Почему?? Ох, вижу… Этот объект содержит другой объект. Таким образом мне требуется и другой класс. Не проблема.

Погодите… Мне нужен не только другой объект. Мне нужен ещё родитель другогокласса и родительский класс родительского класса и так далее и так далее для каждого объекта который используется в используемом классе и ВСЕ родительские классы родителей, родители родителей и так далее.

Тьфу.

Вот великолепная цитата создателя Erlang Джо Армстронга:

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

Решение проблемы банана, обезьяны, джунглей

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

Именно.

Так что же делать бедному ООП-программисту?

Включать и Делегировать. Больше об этом далее.

Ромбовидное наследование (Diamond problem)

Рано или поздно эта проблема покажет свою ужасную и, в зависимости от языка программирования, неразрешимую голову.

goodbye-oop2

Многие объекто-ориентированные языки программирования не поддерживают эту возможность, даже не смотря на то, что она выглядит логично. В чём проблема поддержки этой фичи в объектно-ориентированном языке?

Окей, представьте следующий псевдокод:

Class PoweredDevice {
}

Class Scanner inherits from PoweredDevice {
  function start() {
  }
}

Class Printer inherits from PoweredDevice {
  function start() {
  }
}

Class Copier inherits from Scanner, Printer {
}

Заметьте, что оба класса Scanner и Printer реализуют метод start.

Так какой метод start класс Copier наследует? Scanner? Или Printer? Не может быть, что оба одновременно.

Решение проблемы ромбовидного наследования

Решение простое — не делать подобного.

Да, это так. Многие ОО языки программирования не позволяют вам такого сделать.

Но, но… если я должен сделать подобное? Мне необходимо моё Переиспользование!

Тогда вам надо Включать и Делегировать:

Class PoweredDevice {
}

Class Scanner inherits from PoweredDevice {
  function start() {
  }
}

Class Printer inherits from PoweredDevice {
  function start() {
  }
}

Class Copier {
  Scanner scanner
  Printer printer
  function start() {
    printer.start()
  }
}

Заметьте, что теперь класс Copier содержит ссылки на объекты классов Printer и Scanner. Класс Copier делегирует функцию start реализации из класса Printer. Так же просто можно делегировать её имплементацию классу Scanner.

Эта проблема — ещё одна трещина в столпе Наследования.

Проблема хрупкого базового класса

Итак, моя иерархия классов небольшая и я не допускаю циклических наследований. Бриллианты* не для меня.

И всё было отлично в мире. До тех пор пока…

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

Возможно, это баг… Но подождите… Что-то изменилось…

И это был не мой код. Ломающее изменение оказалось в базовом классе.

Каким образом изменение базового класса могло поломать мой код?

А вот так…

Представьте, что базовый класс ниже (написан на Java, но дожен быть понятен и тем, кто не знаком с этим языком):

import java.util.ArrayList;
 
public class Array
{
  private ArrayList<Object> a = new ArrayList<Object>();
 
  public void add(Object element)
  {
    a.add(element);
  }
 
  public void addAll(Object elements[])
  {
    for (int i = 0; i < elements.length; ++i)
      a.add(elements[i]); // эта строчка изменится.
  }
}

Обратите внимание на строку с комментарием. Она изменится и приведёт к тому, что мой код перестанет работать.

Класс Array содержит два метода add() и addAll(). Метод add() добавляет в коллекцию один элемент, а метод addAll() добавляет несколько элементов вызывая метод add().

И класс-наследник:

public class ArrayCount extends Array
{
  private int count = 0;
 
  @Override
  public void add(Object element)
  {
    super.add(element);
    ++count;
  }
 
  @Override
  public void addAll(Object elements[])
  {
    super.addAll(elements);
    count += elements.length;
  }
}

Класс ArrayCount является специализацией общего класса Array. С однойразницей, что ArrayCount хранит количество элементов массива в поле count.

Давайте внимательно посмотрим на оба класса.

Метод add() класса Array добавляет элемент в локальный ArrayList. Метод addAll() класса Array вызывает метод add класса ArrayList для каждого элемента.

Метод add() класса ArrayCount вызывает родительский метод add и затем увеличивает счётчик count. Метод addAll() класса ArrayCount вызывает родительский addAll() и затем увеличивает счётчик count на количество элементов в переданном массиве.

Всё работает отлично.

Теперь ломающее изменение. Строка с комментарием в базовом классе изменилась:

public void addAll(Object elements[])
{
    for (int i = 0; i < elements.length; ++i)
        add(elements[i]); // строчка изменилась
}

До тех пор пока создатель базового класса следит за тем, чтобы всё было нормально — всё будет работать как надо. И все автотесты будут проходить.

Но автору безразличен наследуемый класс. И автор класса-наследника получает проблемы.

Теперь ArrayCount.addAll() вызывает базовый addAll() который внутри вызывает переопределённый метод add() из класса-наследника.

Это приводит к тому, что count увеличивается каждый раз когда вызывается метод add() класса-наследника и увеличивается ещё раз, на количество элеметов в массиве, когда вызывается метод addAll() класса-наследника.

ЭЛЕМЕНЫ ПОДСЧИТЫВАЮТСЯ ДВАЖДЫ.

Если это могло произойти, то это произойдёт, автор класса-наследника обязан знать внутреннюю реализацию базового класса. И владельцев классов-наследников необходимо информировать обо всех изменениях в базовом классе так как это может непредсказуемо повлиять на класс-наследник.

Тьфу! Эта огромная трещина будет всегда угрожать стабильности прекрасного столпа Наследования.

Решение проблемы хрупкого базового класса

И снова Включение и Делегирование — путь к спасению.

Используя Включение и Делегирование мы переходим от разработки Белового Ящика (White Box programming) к разработке Чёрного Ящика (Black Box programming).

При разработке Белого Ящика мы должны знать то, как реализован базовый класс.

В случае Чёрного Ящика мы полностью ингорируем реализацию базового класса поскольку не имеем возможности внедрить код в базовый класс переопределяя его функции. Мы должны придерживаемся лишь некоего интерфейса.

Звучит тревожно…

Наследование должно было быть большим вкладом в Переиспользование кода.

Объектно-ориентированные языки программирования не были спроектированы для того, чтобы можно было просто Внедрять и Делегировать. Они были созданы для простого Наследования.

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

Проблема Иерархий

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

Должна ли быть создана папка “Документы” в которой будет папка с названием “Компания”?

Может быть, я должен создать папку “Компания” и в ней создать папку “Документы”?

Оба варианта подходят. Но какой из них — правильный? Который лучше?

Идея категориальных иерархий была в том, что базовый класс (родитель) является более общим, а классы-наследники (потомки) — более специализированными версиями базового класса. И становятся всё более узко-специализированными по мере спускания по иерархии вниз (посмотрите структуру классов выше).

Но если родитель и потомок могут произвольно меняться местами, то, очевидно, с этой моделью что-то не так.

Решение проблемы Иерархий.

Проблема в том, что…

Категориальные Иерерхии не работают

Тогда для чего иерархии подходят?

Включение

Если вы присмотритесь к реальному миру вокруг, то повсюду заметите Включаемые Иерерхии (или Иерархии Эксклюзивного Владения).

Вы не увидете Категориальных Иерархий. Отличный пример Включаемой Иерархии — это ваши носки. Они находятся в ящике для носков, который является одним из ящиков вашего комода, который находится в вашей спальне, которая находится в вашем доме, и так далее.

Директории на вашем жестком диске — другой пример Включаемой Иерархии. Они содержат папки.

Как мы можем их категоризировать?

Итак, если вы подумали о Документах Компании, то вообще не имеет значения куда я их положу. Я могу положить их в папку Документы или в папку Разное.

Для категоризации я решил использовать теги. И присвоил файлу теги:

Document
Company
Handbook

У тегов нет порядка в иерархии. (Это так же решает проблему ромбовидного наследования.)

Теги похожи на интерфейсы, которых может быть несколько для каждого типа.

Но с таким количеством трещин Наследование, похоже, разрушается.

Прощай, Наследование.

Инкапсуляция. Второй столп падёт.

На первый взгляд, Инкапсуляция — вторая большая плюшка объектно-ориентированного программирования.

Состояние объекта скрыто от внешнего доступа, т.е. оно инкапсулировано в объекте.

Больше нет необходимости думать о глобальных переменных, к которым имеют доступ непонятно кто.

Инкапсуляция предоставляет безопасность вашим переменным.

Инкапсуляция ВЕЛИКОЛЕПНА!!!

Долгой жизни инкапсуляции.

До тех пор пока…

Проблема ссылок

Ради эффективности, объекты передаются по ссылке, а не по значению.

Это означает, что в функцию передаётся не значение объекта, а ссылка или указатель на объект.

Если Объект передаётся в конструктор Объекта, то конструктор может сохранить ссылку на Объект в приватную переменную, которая защищена Инкапсуляцией.

Но переданный Объект не в безопасности!

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

Решение проблемы ссылок

Конструктор должен создавать копию переданного Объекта. При этом должно осуществляться глубокое копирование, т.е. создаваться копии всех объектов, являющиеся членами переданного Объекта и копии всех объектов этих объектов и так далее.

Столько всего ради эффективности.

И в этом проблема. Не все объекты можно клонировать. Зависимость некоторых из них от ресурсов операционной системы делает клонирование бесполезным в лучшем случае и невозможным — в худшем.

И ВСЕ популярные объектно-ориентированные языки программирования имеют эту проблему.

Прощай, Инкапсуляция.

Полиморфизм. Третий столп падёт

Полиморфизм был рыжим приёмным ребёнком Объектно Ориентированной Троицы.

Он как Ларри Файн в этой группе.

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

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

Интерфейсы предоставляют вам это. И без всего багажа Объектно-Ориентированности.

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

Таким образом, без лишнего шума, попрощаемся с Объектно-Ориентированным Полиморфизмом и скажем “Привет” Полиморфизму, основаному на интерфейсах.

Нарушенные обещания

Ну, Объектно-ориентированное программирование пообещало многое. И эти обещания даются наивным программистам в учебных классах, читающих блоги и проходящих онлайн-курсы.

Мне потребовались годы, чтобы понять как ООП меня обманывало. Я тоже был доверчивым.

И меня сожгли.

И что дальше?

Привет, Функциональное программирование. Было очень приятно работать с тобой последние несколько лет.

Просто чтобы ты знал, я НЕ принимаю ни одно из твоих обещаний за чистую монету. Я должен буду проверить их, чтобы поверить.

Однажды обжёгсись на молоке, будешь дуть и на воду.

Ты понимаешь.

Вы можете присоединиться к сообществу веб-разработчиков изучающих и помогающих друг другу разрабатывать веб-приложения с использование функционального программирования на языке Elm, взгляните на группу в Facebook “Learn Elm Programming”

*Отсылка к оригинальному названию ромбовидных наследований

Как я Telegram бота на Go писал. Часть пятая. Dockerизируй это!

На днях наконец-то решил добраться до такой хайповой темы как Docker:

Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в среде виртуализации на уровне операционной системы. Позволяет «упаковать» приложение со всем его окружением и зависимостями в контейнер, который может быть перенесён на любую Linux-систему с поддержкой cgroups в ядре, а также предоставляет среду по управлению контейнерами. Изначально использовал возможности LXC, с 2015 года применял собственную библиотеку, абстрагирующую виртуализационные возможности ядра Linux — libcontainer. С появлением ​Open Container Initiative начался переход от монолитной к модульной архитектуре. (Википедия)

И, в качестве эксперимента, решил развернуть одного из своих ботов на VDS внутри контейнера для простоты переноса и развёртывания. Бот написан на Go и, соответсвенно, это делает развёртывание немного нетривиальным (на самом деле, если читать документацию и уметь пользоваться поисковыми системами, то всё довольно просто).

Продолжить чтение «Как я Telegram бота на Go писал. Часть пятая. Dockerизируй это!»