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

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

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

Почему важны шаблоны проектирования?

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

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

Что такое шаблон проектирования?

По словам Кристофера Александра:

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

Кристофер Александр был американским архитектором. Однако его заявление применимо и к миру программного обеспечения.

Каждый шаблон проектирования определяется 4 основными элементами:

  • Имя: способ определения общего контекста и словаря того, как и для чего используется DP.
  • Проблема. Описывает, когда применять шаблон.
  • Решение: то, как классы, интерфейсы и объекты предназначены для решения проблемы.
  • Последствия: компромиссы, которые мы должны рассмотреть, когда примем решение о применении данного DP.

Как классифицировать шаблоны проектирования?

В зависимости от того, какое повторное использование мы хотели бы застраховать, шаблоны проектирования могут быть:

  • Поведение: как распределяется ответственность и информация распространяется через различные объекты.
  • Структурный: касается процессов взаимодействия между различными объектами и классами.
  • Creational: позволяет разделить и оптимизировать этапы создания различных объектов.

Я покажу по крайней мере пример каждого типа позже.

Каких выводов ожидать от этой статьи?

Обратите внимание, что каждая описанная здесь концепция взята из замечательной книги Gang of Four: Design Patterns Principles. Тем не менее, я пытаюсь реализовать несколько шаблонов проектирования, представляя небольшие сценарии в области разработки машинного обучения. Эти реализации сделаны на Python и могут быть воспроизведены на другом языке программирования.

Эксперимент:

Давайте продолжим с нашими вариантами использования. Для каждого шаблона проектирования мы приведем определение, данное GoF. Затем приведем наглядный пример. Мы записываем некоторые зависимости в небольшой скрипт на Python, который загружаем в начале каждой демонстрации, включая The Boston housing prices dataset.

Он доступен в пакете python scikit-learn и широко используется в литературе для тестирования алгоритмов. Он также был первоначально получен от StatLib archive.

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

1. Стратегия

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

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

Шаблон основан на интерфейсе стратегии, представляющем основные абстрактные функции алгоритма.

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

Мы определяем контекст, который «проглатывает» стратегию и сопоставляет ее атрибуты с ее ядром.

Давайте проверим, как наш Context принимает класс модели:

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

2. Посредник

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

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

Один хороший способ (конечно, не лучший здесь) сделать что-то было бы установить посредника между двумя сторонами и ограничить каждое взаимодействие внутри себя. Таким образом, каждый объект может свободно развиваться в случае его модификации, а реализации остаются полностью изолированными друг от друга. Затем посредник может обрабатывать различные способы взаимодействия наших классов модели.

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

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

Конкретная реализация Mediator настраивает 3 стороны, инициализирует их и позволяет им выполнять соответствующие действия самостоятельно или в интерактивном режиме.

Давайте тогда проверим наш mediator :

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

3. Состояние

Разрешить объекту изменять свое поведение при изменении его внутреннего состояния. Объект изменит свой класс.

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

Паттерн состояния запускает определенную модель в определенном контексте, задавая состояние. Если состояние меняется, меняется и модель.

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

Теперь создадим контекст и попробуем поэкспериментировать с разными состояниями:

Давайте приведем наш контекст в действие:

Вы можете заметить определенное сходство между шаблонами State и Strategy.

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

4. Строитель

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

Построитель действует как промежуточное программное обеспечение между классом построителя и классом продукта. Наш продукт будет содержать интересную информацию о:

  • Модель
  • Элементы тренировочного процесса (размер обучающих данных, используемые переменные и цель обучения)
  • Прогноз модели

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

Начнем с приготовления ингредиентов нашего класса продуктов:

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

Мы создаем экземпляр нашего интерфейса Builder и реализуем основные шаги и компоненты нашего процесса обучения:

Наконец, мы устанавливаем Director, который будет запускать полный процесс сборки и выводить объект ModelObj:

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

5. Прототип

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

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

Мы напишем обучающий класс, содержащий обучающие данные в качестве свойства.
Класс-прототип будет содержать три основных атрибута: модель, определенные параметры и обучаемый объект.

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

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

Теперь мы можем создавать копии прототипов и перебирать различные алгоритмы, сохраняя при этом наши процессы разрозненными.

6. Адаптер

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

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

Сначала создается интерфейс с именем AdaptivePredictor. Затем мы реализуем два разных класса; один с обычным предсказанием и один со сверхспособностями.

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

Давайте попробуем:

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

7. Декоратор

Динамически прикрепляйте дополнительные обязанности к объекту. Декораторы предоставляют гибкую альтернативу подклассам для расширения функциональности.

В дополнение к встроенной реализации, предоставляемой Python, и при определенных обстоятельствах мы хотим добавить определенные атрибуты к конкретному объекту, а не к классу. Мы могли бы справиться с этим, используя наследование.

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

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

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

Мы начнем с создания интерфейса Decorator с двумя свойствами (итерируемый и конвейер) и методом, который добавляет преобразователи в конвейер.

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

Мы тестируем наш процесс декорирования, в результате чего получается полный Pipeline, готовый для соответствия нашим данным.

Update : Если вам интересно увидеть больше сценариев с большим количеством шаблонов проектирования, не стесняйтесь проверять вторую часть.

Заключение

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

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

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

Веселиться !!