Русский
Русский
English
Статистика
Реклама

Питон

Перевод Масштабируемая классификация данных для безопасности и конфиденциальности

23.09.2020 18:06:49 | Автор: admin


Классификация данных на основе контента это открытая задача. Традиционные системы предотвращения потери данных (DLP) решают эту проблему путем снятия отпечатков с соответствующих данных и мониторинга конечных точек для снятия отпечатков. Учитывая большое количество постоянно меняющихся ресурсов данных в Facebook, этот подход не только не масштабируется, но и неэффективен для определения того, где находятся данные. Эта статья посвящена сквозной системе, построенной для обнаружения чувствительных семантических типов в Facebook в масштабе и автоматического обеспечения хранения данных и контроля доступа.

Описанный здесь подход это наша первая сквозная система конфиденциальности, которая пытается решить эту проблему путем включения сигналов данных, машинного обучения и традиционных методов снятия отпечатков для отображения и классификации всех данных в Facebook. Описанная система эксплуатируется в производственной среде, достигая среднего балла F2 0,9+ по различным классам конфиденциальности при обработке большого количества ресурсов данных в десятках хранилищ. Представляем перевод публикации Facebook на ArXiv о масштабируемой классификации данных для обеспечения безопасности и конфиденциальности на основе машинного обучения.

Введение


Сегодня организации собирают и хранят большие объемы данных в различных форматах и местах [1], затем данные потребляются во многих местах, иногда копируются или кэшируются несколько раз, в результате чего ценная и конфиденциальная деловая информация рассеивается по многим корпоративным хранилищам данных. Когда от организации требуется выполнить определенные правовые или нормативные требования, например, соблюдать нормативные акты в ходе гражданского судопроизводства, возникает необходимость сбора данных о местоположении нужных данных. Когда в постановлении о конфиденциальности говорится, что организация должна маскировать все номера социального страхования (SSN) при передаче личной информации неавторизованным субъектам, естественным первым шагом является поиск всех SSN в хранилищах данных всей организации. При таких обстоятельствах классификации данных приобретает решающее значение [1]. Система классификации позволит организациям автоматически обеспечить соблюдение конфиденциальности и политики безопасности, такие как включение политики управления доступом, сохранение данных. Facebook представляет систему, построенную нами в Facebook, которая использует множество сигналов данных, масштабируемую системную архитектуру и машинное обучение для обнаружения чувствительных семантических типов данных.

Обнаружение и классификация данных это их поиск и маркировка таким образом, чтобы при необходимости можно было быстро и эффективно извлекать соответствующую информацию. Нынешний процесс носит скорее ручной характер и заключается в изучении соответствующих законов или нормативных актов, определении того, какие типы информации следует считать чувствительными и каковы различные уровни чувствительности, а затем в соответствующем построении классов и политики классификации [1]. После системы защиты от потери данных (DLP) снимают отпечатки с данных и отслеживают конечные точки вниз по потоку для получения отпечатков. При работе с хранилищем с большим количеством активов и петабайтами данных такой подход просто не масштабируется.

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


Рисунок 1. Потоки онлайн и офлайн-прогнозирования

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

В этой статье описывается, как мы справлялись с проблемами выше, и представляется быстрая и масштабируемая система классификации, которая классифицирует элементы данных всех типов, форматов и источников на основе общего набора признаков. Мы расширили системную архитектуру и создали специальную модель машинного обучения для быстрой классификации офлайн и онлайн-данных. Эта статья организована следующим образом: в разделе 2 представляется общий дизайн системы. В разделе 3 обсуждаются части системы машинного обучения. В разделах 4 и 5 рассказывается о связанной работе, намечается будущее направление работы.

Архитектура


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

Устойчивые данные


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

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

Каждое задание это скомпилированный двоичный файл, который выполняет выборку Бернулли по последним данным, доступным для каждого актива. Актив разбивается на отдельные столбцы, где результат классификации каждого столбца обрабатывается независимо. Кроме того, система сканирует любые насыщенные данные внутри столбцов. JSON, массивы, кодированные структуры, URL-адреса, сериализованные данные base 64 и многое другое всё это сканируется. Это может значительно увеличить время выполнения сканирования, так как одна таблица может содержать тысячи вложенных столбцов в большом двоичном объекте json.

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

Для чего нужны признаки?


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

  1. Конфиденциальность прежде всего: самое главное, понятие признаков позволяет нам хранить в памяти только те образцы, которые мы извлекаем. Это гарантирует, что мы храним образцы для единственной цели и никогда не логируем их нашими собственными усилиями. Это особенно важно для неустойчивых данных, поскольку сервис должен поддерживать некоторое состояние классификации, прежде чем предоставлять прогноз.
  2. Память: некоторые сэмплы могут иметь длину в тысячи символов. Хранение таких данных и передача их частям системы без необходимости потребляет много дополнительных байтов. Два фактора могут объединиться с течением времени, учитывая, что существует много ресурсов данных с тысячами столбцов.
  3. Агрегирование признаков: с помощью признаков через их набор четко представляются результаты каждого сканирования, что позволяет системе объединять результаты предыдущих сканирований одного и того же ресурса данных удобным способом. Это может быть полезно для агрегирования результатов сканирования одного ресурса данных в нескольких запусках.

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

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

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

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

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

Неустойчивые данные


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

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

Оптимизация


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

Для чрезвычайно больших таблиц (50 + петабайт), несмотря на все оптимизации и эффективность памяти, система работает над сканированием и вычислением всего, прежде чем закончится память. В конце концов, сканирование полностью вычисляется в памяти и не сохраняется в течение сканирования. Если большие таблицы содержат тысячи столбцов с неструктурированными сгустками данных, задание может завершиться неудачей из-за нехватки ресурсов памяти при выполнении прогнозов для всей таблицы. Это приведет к уменьшению покрытия. Чтобы бороться с этим, мы оптимизировали систему, чтобы использовать скорость сканирования в качестве посредника в том, насколько хорошо система справляется с текущей нагрузкой. Мы используем скорость как прогностический механизм, тчобы видеть проблемы с памятью и при упреждающем расчете карты объектов. При этом мы используем меньше данных, чем обычно.

Сигналы данных


Система классификации хороша настолько, насколько хороши сигналы от данных. Здесь мы рассмотрим все сигналы, используемые системой классификации.

  • На основе содержимого: конечно, первый и важнейший сигнал это содержимое. Выполняется выборка Бернулли по каждому активу данных, который мы сканируем и извлекаем признаки по содержанию данных. Многие признаки происходят из содержимого. Возможно любое количество плавающих объектов, которые представляют рассчеты того, сколько раз был замечен определенный тип образца. Например, у нас могут быть ротзнаки количества электронных писем, увиденных в выборке, или признаки того, сколько смайликов замечено в выборке. Эти расчеты признаков можно нормализовать и агрегировать по различным сканированиям.
  • Происхождения данных: важный сигнал, который может помочь, когда содержимое изменилось из родительской таблицы. Распространенный пример хэшированные данные. Когда данные в дочерней таблице хэшируются, они часто поступают из родительской таблицы, где остаются в открытом виде. Данные о происхождении помогают классифицировать определенные типы данных, когда они не читаются четко или преобразованы из таблицы вверх по потоку.
  • Аннотации: еще один высококачественный сигнал, помогающий в идентификации неструктурированных данных. Фактически аннотации и данные происхождения могут работать вместе для распространения атрибутов между различными активами данных. Аннотации помогают идентифицировать источник неструктурированных данных, в то время как данные о происхождении могут помочь отслеживать поток этих данных по всему хранилищу.
  • Инъекция данных это метод, когда намеренно вводятся специальные, нечитаемые символы в известные источники с известными типами данных. Затем, всякий раз, когда мы сканируем содержимое с одной и той же нечитаемой последовательностью символов, можно сделать вывод, что содержимое исходит из этого известного типа данных. Это еще один качественный сигнал данных, подобный аннотациям. За исключением того, что обнаружение на основе контента помогает обнаружить введенные данные.


Измерение метрик


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

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

Сбор достоверных данных


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

  • Конфигурации платформы логирования: определенные поля в таблицах улья заполняются данными, которые относятся к определенному типу. Использование и распространение этих данных служит надежным источником достоверных данных.
  • Ручная маркировка: разработчики, поддерживающие систему, а также внешние маркировщики обучены маркировать столбцы. Это обычно хорошо работает для всех типов данных в хранилище, и может быть основным источником достоверности для некоторых неструктурированных данных, таких как данные сообщений или пользовательский контент.
  • Столбцы из родительских таблиц могут помечаться или аннотироваться как содержащие определенные данные, и мы можем отслеживать эти данные в нижестоящих таблицах.
  • Выборка потоков выполнения: потоки выполнения в Facebook несут данные определенного типа. Используя наш сканер в качестве сервисной архитектуры, мы можем делать выборку потоков, имеющих известные типы данных, и отправлять их через систему. Система обещает не хранить эти данные.
  • Таблицы выборки: большие таблицы улья, которые, как известно, содержат весь корпус данных, также могут использоваться в качестве обучающих данных и передаваться через сканер как сервис. Это отлично подходит для таблиц с полным диапазоном типов данных, так что выборка столбца случайным образом эквивалентна выборке всего множества этого типа данных.
  • Синтетические данные: мы можем даже использовать библиотеки, которые генерируют данные на лету. Это хорошо работает для простых, общедоступных типов данных, таких как адрес или GPS.
  • Стюарды данных: программы конфиденциальности, как правило, используют стюардов данных для ручного закрепления политик за частями данных. Это служит высокоточным источником достоверности.

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

Непрерывная интеграция


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

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

Так мы сравниваем результаты классификации релиз-кандидата и производственной модели в режиме реального времени.

Пока наборы данных сравнивают признаки RC и PROD, логируется множество вариаций движка классификации ML сервиса прогнозирования. Самая последняя построенная модель машинного обучения, текущая модель в производстве и любые экспериментальные модели. Тот же самый подход позволяет нам разрезать различные версии модели (агностик наших классификаторов правил) и сравнивать метрики в режиме реального времени. Так легко определить, когда эксперимент с ML готов к внедрению в производство.

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

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

Некоторые результаты


Маркируется более 100 различных типов данных с высокой точностью. Хорошо структурированные типы, такие как электронные письма и телефонные номера, классифицируются с оценкой f2 более 0,95. Свободные типы данных, такие как пользовательский контент и имя, также работают очень хорошо, с F2-баллами более 0,85.

Ежедневно классифицируется большое количество отдельных столбцов устойчивых и неустойчивых данных во всех хранилищах. Более 500 терабайт сканируются ежедневно в более чем 10 хранилищах данных. Охват большинства из этих хранилищ составляет более 98%.

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


Рис. 2. Диаграмма, описывающая непрерывный поток интеграции, чтобы понимать, как RC-объекты генерируются и отправляются в модель.


Рисунок 3. Высокоуровневая диаграмма компонента машинного обучения.

Компонент системы машинного обучения


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

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

Реализованная модель изучает векторные представления [3] над плотными и разреженными объектами отдельно. Затем они объединяются, чтобы сформировать вектор, который проходит через серию этапов пакетной нормализации [4] и нелинейности для получения конечного результата. Конечный результат число с плавающей точкой между [0-1] для каждой метки, указывающий вероятность того, что пример принадлежит данному типу чувствительности. Использование PyTorch для модели позволило нам двигаться быстрее, дав возможность разработчикам вне команды быстро вносить и тестировать изменения.

При проектировании архитектуры было важно моделировать разреженные (например, текстовые) и плотные (например, числовые) объекты отдельно из-за их внутреннего различия. Для окончательной архитектуры также было важно выполнить развертку параметров, чтобы найти оптимальное значение скорости обучения, размера пакета и других гиперпараметров. Выбор оптимизатора также был важным гиперпараметром. Мы обнаружили, что популярный оптимизатор Adamчасто приводит к переобучению, тогда как модель с SGD стабильнее. Были дополнительные нюансы, которые мы должны были включить непосредственно в модель. Например, статические правила, которые гарантировали, что модель делает детерминированный прогноз, когда признак имеет определенное значение. Эти статические правила определены нашими клиентами. Мы обнаружили, что включение их непосредственно в модель привело к созданию более самодостаточной и надежной архитектуры, в отличие от реализации этапа постобработки для обработки этих специальных граничных случаев. Также обратите внимание, что во время тренировки эти правила отключены, чтобы не мешать тренировочному процессу градиентного спуска.

Проблемы


Одной из проблем был сбор высококачественных достоверных данных. Модель нуждается в достоверности для каждого класса, чтобы она могла изучать ассоциации между объектами и метками. В предыдущем разделе мы обсуждали методы сбора данных как для измерения системы, так и для обучения моделей. Анализ показал, что такие классы данных, как номера кредитных карт и банковских счетов не очень распространены в нашем хранилище. Это затрудняет сбор больших объемов достоверных данных для обучения моделей. Чтобы решить эту проблему, мы разработали процессы получения синтетических достоверных данных для этих классов. Мы генерируем такие данные для чувствительных типов, включая SSN, номера кредитных карт и IBAN-номера, для которых модель не могла прогнозировать ранее. Этот подход позволяет обрабатывать конфиденциальные типы данных без риска конфиденциальности, связанного с укрывательством реальных конфиденциальных данных.

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

Важность признака


Когда в модель вводится новый признак, мы хотим знать его общее влияние на модель. Мы также хотим убедиться, что прогнозы интерпретируемы человеком, чтобы можно было точно понять, какие признаки используются для каждого типа данных. Для этого мы разработали и ввели поклассовую важность признаков для модели PyTorch. Обратите внимание, что это отличается от общей важности признака, которая обычно поддерживается, потому что она не говорит нам, какие признаки важны для определенного класса. Мы измеряем важность объекта, вычисляя увеличение ошибки прогноза после перестановки объекта. Признак является важным, когда перестановка значений увеличивает ошибку модели, поскольку в этом случае модель полагалась на признак в прогнозировании. Признак неважен, когда перетасовка его значений оставляет ошибку модели неизменной, поскольку в этом случае модель игнорировала его [5].

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

Оценка


Важно определить единую метрику успеха. Мы выбрали F2 баланс между отзывом и точностью (смещение отзыва немного больше). Отзыв важнее для случая использования конфиденциальности, чем точность, потому что для команды крайне важно не пропустить никаких конфиденциальных данных (обеспечивая при этом разумную точность). Фактические данные оценки производительности F2 нашей модели выходят за рамки данной статьи. Тем не менее, при тщательной настройке мы можем достичь высокого (0,9+) балла F2 для наиболее важных чувствительных классов.

Связанная работа


Существует множество алгоритмов автоматической классификации неструктурированных документов с использованием различных методов, таких как сопоставление шаблонов, поиск сходства документов и различные методы машинного обучения (байесовские, деревья решений, k-ближайших соседей и многие другие) [6]. Любой из них может использоваться как часть классификации. Однако проблема в масштабируемости. Подход к классификации в этой статье смещен в сторону гибкости и производительности. Это позволяет нам поддерживать новые классы в будущем и поддерживать низкую задержку.

Существует также масса работ по снятию отпечатков с данных. Например, авторы в [7] описали решение, которое фокусируется на проблеме улавливания утечек конфиденциальных данных. Основное предположение заключается в возможности отпечатка с данных, чтобы сопоставить его с набором известных конфиденциальных данных. Авторы в [8] описывают аналогичную проблему утечки конфиденциальности, но их решение основано на конкретной архитектуре Android и классифицируется только в том случае, когда действия пользователя привели к отправке личной информации или если в базовом приложении утечка пользовательских данных. Ситуация здесь несколько отличается, поскольку пользовательские данные также могут быть сильно неструктурированными. Поэтому нам нужна более сложная техника, чем снятие отпечатков.

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

Заключение


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

Есть много направлений будущей работы. Она может включать обеспечение поддержки несхематизированных данных (файлов), классификацию не только типа данных, но и уровня чувствительности, а также использование самоконтролируемого обучения непосредственно во время обучения путем генерации точных синтетических примеров. Которые, в свою очередь помогут модели уменьшить потери на наибольшую величину. Будущая работа может также сосредоточиться на рабочем процессе расследования, где мы выходим за рамки обнаружения и предоставляем анализ первопричин различных нарушений конфиденциальности. Это поможет в таких случаях, как анализ чувствительности (т.е. является ли чувствительность конфиденциальности типа данных высокой (например, IP пользователя) или низкой (например, внутренний IP Facebook)).

Библиография
  1. David Ben-David, Tamar Domany, and Abigail Tarem. Enterprise data classification using semantic web technolo- gies. In Peter F. Patel-Schneider, Yue Pan, Pascal Hitzler, Peter Mika, Lei Zhang, Jeff Z. Pan, Ian Horrocks, and Birte Glimm, editors, The Semantic Web ISWC 2010, pages 6681, Berlin, Heidelberg, 2010. Springer Berlin Heidelberg.
  2. Subramanian Muralidhar, Wyatt Lloyd, Sabyasachi Roy, Cory Hill, Ernest Lin, Weiwen Liu, Satadru Pan, Shiva Shankar, Viswanath Sivakumar, Linpeng Tang, and Sanjeev Kumar. f4: Facebooks warm BLOB storage system. In 11th USENIX Symposium on Operating Systems Design and Implementation (OSDI 14), pages 383398, Broomfield, CO, October 2014. USENIX Association.
  3. Tomas Mikolov, Ilya Sutskever, Kai Chen, Greg S Corrado, and Jeff Dean. Distributed representations of words and phrases and their compositionality. In C. J. C. Burges, L. Bottou, M. Welling, Z. Ghahramani, and K. Q. Weinberger, editors, Advances in Neural Information Processing Systems 26, pages 31113119. Curran Associates, Inc., 2013.
  4. Sergey Ioffe and Christian Szegedy. Batch normalization: Accelerating deep network training by reducing internal covariate shift. In Francis Bach and David Blei, editors, Proceedings of the 32nd International Conference on Machine Learning, volume 37 of Proceedings of Machine Learning Research, pages 448456, Lille, France, 0709 Jul 2015. PMLR.
  5. Leo Breiman. Random forests. Mach. Learn., 45(1):532, October 2001.
  6. Thair Nu Phyu. Survey of classification techniques in data mining.
  7. X. Shu, D. Yao, and E. Bertino. Privacy-preserving detection of sensitive data exposure. IEEE Transactions on Information Forensics and Security, 10(5):10921103, 2015.
  8. Zhemin Yang, Min Yang, Yuan Zhang, Guofei Gu, Peng Ning, and Xiaoyang Wang. Appintent: Analyzing sensitive data transmission in android for privacy leakage detection. pages 10431054, 11 2013.
  9. Qizhe Xie, Zihang Dai, Eduard H. Hovy, Minh-Thang Luong, and Quoc V. Le. Unsupervised data augmentation.

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя онлайн-курсы SkillFactory:



Подробнее..

Перевод Вот ваш мозг на Питоне исследователи расшифровали нейро-механику программирования

17.12.2020 20:07:58 | Автор: admin


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

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

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

Люди учатся программированию по-разному: от чтения самоучителей до формальных курсов. Удивительно, что мы обнаруживаем такую последовательную модель активации мозга у людей, которые программируют, сказала автор исследования Юнь-Фей Лю, аспирантка лаборатории нейропластичности университета. Это особенно удивительно, потому что мы знаем, что решающий период для овладения языком обычно заканчивается в раннем подростковом возрасте. А многие люди учатся программировать уже взрослыми.

Результаты исследования опубликованы в журнале eLife.

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

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

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

Чтобы разобраться в этом, Лю собрала 15 опытных программистов, в совершенстве владеющих языком Python. Каждый из подопытных лег в сканер фМРТ, чтобы измерить активность мозга в процессе программирования.

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

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

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



Подробнее..

Из песочницы Файловый менеджер на питоне в 430 строк для начинающих и чайников

01.11.2020 04:06:50 | Автор: admin
Всем привет!

Я захотел обобщить свои знания питона и решил написать файловый менеджер для пк.



Внимание!
Это всего лишь игрушка и не более, это не реальная ОС!

Импорт библиотек:

import tkinterimport osimport subprocessfrom tkinter import messageboxfrom tkinter import simpledialog

Главное меню:

class MainContextMenu(tkinter.Menu):''' Контекстное меню для внутренней области директории'''def __init__(self, main_window, parent):super(MainContextMenu, self).__init__(parent, tearoff = 0)self.main_window = main_windowself.add_command(label="Создать директорию", command = self.create_dir)self.add_command(label="Создать файл", command = self.create_file)def popup_menu(self, event):''' функция для активации контекстного меню'''#если активны другие меню - отменяем ихif self.main_window.file_context_menu:self.main_window.file_context_menu.unpost()if self.main_window.dir_context_menu:self.main_window.dir_context_menu.unpost()self.post(event.x_root, event.y_root)def create_dir(self):''' функция для создания новой директории в текущей'''dir_name = simpledialog.askstring("Новая директория", "Введите название новой директории")if dir_name:command = "mkdir {0}".format(dir_name).split(' ')#выполняем команду отдельным процессомprocess = subprocess.Popen(command, cwd=self.main_window.path_text.get(), stdout = subprocess.PIPE, stderr = subprocess.PIPE)out, err = process.communicate()#при возникновении ошибки выводим сообщениеif err:messagebox.showwarning("Операция невозможна!","Отказано в доступе.")self.main_window.refresh_window()def create_file(self):''' функция для создания нового файла в текущей директории'''dir_name = simpledialog.askstring("Новый файл", "Введите название нового файла")if dir_name:command = "touch {0}".format(dir_name).split(' ')#выполняем команду отдельным процессомprocess = subprocess.Popen(command, cwd=self.main_window.path_text.get(), stdout = subprocess.PIPE, stderr = subprocess.PIPE)out, err = process.communicate()#при возникновении ошибки выводим сообщениеif err:messagebox.showwarning("Операция невозможна!","Отказано в доступе.")self.main_window.refresh_window()def insert_to_dir(self):''' функция для копирования файла или директории в текущую директорию'''copy_obj = self.main_window.buffto_dir = self.main_window.path_text.get()if os.path.isdir(self.main_window.buff):#выполняем команду отдельным процессомprocess = subprocess.Popen(['cp', '-r', copy_obj, to_dir], stdout = subprocess.PIPE, stderr = subprocess.PIPE)out, err = process.communicate()if err:messagebox.showwarning("Операция невозможна!", err.decode("utf-8"))else:#выполняем команду отдельным процессомprocess = subprocess.Popen(['cp', '-n', copy_obj, to_dir], stdout = subprocess.PIPE, stderr = subprocess.PIPE)out, err = process.communicate()#при возникновении ошибки выводим сообщениеif err:messagebox.showwarning("Операция невозможна!",err.decode("utf-8"))self.main_window.refresh_window()

При нажатии на файл должно выводиться контекстное меню:

image

class FileContextMenu(tkinter.Menu):def __init__(self, main_window, parent):super(FileContextMenu, self).__init__(parent, tearoff = 0)self.main_window = main_windowself.add_command(label="Открыть файл", command = self.open_file)self.add_separator()self.add_command(label="Копировать", command = self.copy_file)self.add_command(label="Переименовать", command = self.rename_file)self.add_separator()self.add_command(label="Удалить", command = self.delete_file)def open_file(self):''' функция для открытия файла сторонними программами'''ext = self.main_window.take_extention_file(self.main_window.selected_file)full_path = self.main_window.path_text.get() + self.main_window.selected_fileif ext in ['txt', 'py', 'html', 'css', 'js']:if 'mousepad' in self.main_window.all_program:subprocess.Popen(["mousepad", full_path], start_new_session = True)else:self.problem_message()elif ext == 'pdf':if 'evince' in self.main_window.all_program:subprocess.run(["evince", full_path], start_new_session = True)else:self.problem_message()elif ext in ['png', 'jpeg', 'jpg', 'gif']:if 'ristretto' in self.main_window.all_program:subprocess.run(["ristretto", full_path], start_new_session = True)else:self.problem_message()else:self.problem_message()def problem_message(self):messagebox.showwarning("Проблема при открытии файла", 'Прости, но я не могу открыть этот файл')def copy_file(self):''' функция для копирования файла'''#заносим полный путь к файлу в буфферself.main_window.buff = self.main_window.path_text.get() + self.main_window.selected_fileself.main_window.refresh_window()def delete_file(self):''' функция для удаления выбранного файла'''full_path = self.main_window.path_text.get() + self.main_window.selected_file#выполняем команду отдельным процессомprocess = subprocess.Popen(['rm', full_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)output, err = process.communicate()#при возникновении ошибки выводим сообщениеif err:messagebox.showwarning("Проблема при удалении файла", 'У Вас нет прав для удаления данного файла')self.main_window.refresh_window()def rename_file(self):''' функция для переименования выбранного файла'''new_name = simpledialog.askstring("Переименование файла", "Введите новое название файла")if new_name:old_file = self.main_window.path_text.get() + self.main_window.selected_filenew_file = self.main_window.path_text.get() + new_name#выполняем команду отдельным процессомprocess = subprocess.Popen(['mv', old_file, new_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)output, err = process.communicate()#при возникновении ошибки выводим сообщениеif err:messagebox.showwarning("Проблема при переименовании файла", 'У Вас нет прав для переименования данного файла')self.main_window.refresh_window()def popup_menu(self, event):''' функция для активации контекстного меню'''self.post(event.x_root, event.y_root)#если активны другие меню - отменяем ихif self.main_window.main_context_menu:self.main_window.main_context_menu.unpost()if self.main_window.dir_context_menu:self.main_window.dir_context_menu.unpost()self.main_window.selected_file = event.widget["text"]

То же самое для директории:

class DirContextMenu(tkinter.Menu):def __init__(self, main_window, parent):super(DirContextMenu, self).__init__(parent, tearoff = 0)self.main_window = main_windowself.add_command(label="Переименовать", command = self.rename_dir)self.add_command(label="Копировать", command = self.copy_dir)self.add_separator()self.add_command(label="Удалить", command = self.delete_dir)def copy_dir(self):''' функция для копирования директории'''self.main_window.buff = self.main_window.path_text.get() + self.main_window.selected_fileself.main_window.refresh_window()def delete_dir(self):''' функция для удаления выбранной директории'''full_path = self.main_window.path_text.get() + self.main_window.selected_fileif os.path.isdir(full_path):#выполняем команду отдельным процессомprocess = subprocess.Popen(['rm', '-rf', full_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)output, err = process.communicate()#при возникновении ошибки выводим сообщениеif err:messagebox.showwarning("Проблема при удалении директории", 'У Вас нет прав для удаления данной директории')self.main_window.refresh_window()def rename_dir(self):''' функция для переименования выбранной директории'''new_name = simpledialog.askstring("Переименование директории", "Введите новое название директории")if new_name:old_dir = self.main_window.path_text.get() + self.main_window.selected_filenew_dir = self.main_window.path_text.get() + new_name#выполняем команду отдельным процессомprocess = subprocess.Popen(['mv', old_dir, new_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE)output, err = process.communicate()#при возникновении ошибки выводим сообщениеif err:messagebox.showwarning("Проблема при переименовании директории", 'У Вас нет прав для переименования данной директории')self.main_window.refresh_window()def popup_menu(self, event):''' функция для активации контекстного меню'''self.post(event.x_root, event.y_root)#если активны другие меню - отменяем ихif self.main_window.main_context_menu:self.main_window.main_context_menu.unpost()if self.main_window.file_context_menu:self.main_window.file_context_menu.unpost()self.main_window.selected_file = event.widget["text"]

Класс основного окна:

class MainWindow():''' Класс основного окна'''def __init__(self):self.root = tkinter.Tk()self.root.title("FileManager")self.root.resizable(width = False, height = False)self.root.geometry('450x300')self.hidden_dir = tkinter.IntVar()self.buff = Noneself.all_program = os.listdir('C:/')self.root.bind('<Button-1>', self.root_click)self.root.bind('<FocusOut>', self.root_click)#top frameself.title_frame = tkinter.Frame(self.root)self.title_frame.pack(fill = 'both', expand = True)#back buttonself.back_button = tkinter.Button(self.title_frame, text = "..", command = self.parent_dir, width = 1, height = 1)self.back_button.pack(side = 'left')#path entryself.path_text = tkinter.StringVar()self.path_text.set('/')self.current_path = tkinter.Entry(self.title_frame, textvariable = self.path_text, width = 40, state='readonly')self.current_path.pack(side = 'left')#button show/hidde hidden dir/fileself.check_button = tkinter.Checkbutton(self.title_frame, text = "Hidden", font = ("Helvetica", 10), padx = 1, pady = 1, variable = self.hidden_dir, command = self.refresh_window)self.check_button.pack(side = 'left')#main frameself.main_frame = tkinter.Frame(self.root)self.main_frame.pack()# scroll barself.scrollbar_vert = tkinter.Scrollbar(self.main_frame, orient="vertical")self.scrollbar_vert.pack(side = 'right', fill = 'y')self.scrollbar_hor = tkinter.Scrollbar(self.main_frame, orient="horizontal")self.scrollbar_hor.pack(side = 'bottom', fill = 'x')#canvasself.canvas = tkinter.Canvas(self.main_frame, borderwidth=0,  bg = 'white')self.inner_frame = tkinter.Frame(self.canvas,  bg = 'white')#команды для прокруткиself.scrollbar_vert["command"] = self.canvas.yviewself.scrollbar_hor["command"] = self.canvas.xview#настройки для canvasself.canvas.configure(yscrollcommand=self.scrollbar_vert.set, xscrollcommand = self.scrollbar_hor.set, width=400, heigh=250)self.canvas.pack(side='left', fill='both', expand=True)self.canvas.create_window((0,0), window=self.inner_frame, anchor="nw")#отрисовываем содержимое лиректорииself.dir_content()def root_click(self, event):''' функция для обработки события клика в root'''#если есть контекстные меню - отменяемif self.file_context_menu:self.file_context_menu.unpost()if self.main_context_menu:self.main_context_menu.unpost()if self.dir_context_menu:self.dir_context_menu.unpost()def dir_content(self):''' функция для определения содержимого текущей директории'''#содержимое в текущей директорииdir_list = os.listdir(self.path_text.get())path = self.path_text.get()if not dir_list:#общее контекстное менюself.main_context_menu = MainContextMenu(self, self.canvas)self.canvas.bind('<Button-3>', self.main_context_menu.popup_menu)if self.buff:self.main_context_menu.add_command(label="Вставить", command = self.main_context_menu.insert_to_dir)self.inner_frame.bind('<Button-3>', self.main_context_menu.popup_menu)#контекстное меню для файловself.file_context_menu = None#контекстное меню для директорииself.dir_context_menu = Nonereturn None#общее контекстное менюself.main_context_menu = MainContextMenu(self, self.canvas)self.canvas.bind('<Button-3>', self.main_context_menu.popup_menu)if self.buff:self.main_context_menu.add_command(label="Вставить", command = self.main_context_menu.insert_to_dir)#контекстное меню для файловself.file_context_menu = FileContextMenu(self, self.inner_frame)#контекстное меню для директорииself.dir_context_menu = DirContextMenu(self, self.inner_frame)i = 0for item in dir_list:if os.path.isdir(str(path) + item):#обрабатываем директорииif os.access(str(path) + item, os.R_OK):if (not self.hidden_dir.get() and  not item.startswith('.')) or self.hidden_dir.get():photo = tkinter.PhotoImage(file ="img/folder.png")icon = tkinter.Label(self.inner_frame, image=photo,  bg = 'white')icon.image = photoicon.grid(row=i+1, column=0)folder_name = tkinter.Label(self.inner_frame, text=item,  bg = 'white', cursor = 'hand1')folder_name.bind("<Button-1>", self.move_to_dir)folder_name.bind("<Button-3>", self.dir_context_menu.popup_menu)folder_name.grid(row=i+1, column=1, sticky='w')else:if (not self.hidden_dir.get() and not item.startswith('.')) or self.hidden_dir.get():photo = tkinter.PhotoImage(file ="img/folder_access.png")icon = tkinter.Label(self.inner_frame, image=photo,  bg = 'white')icon.image = photoicon.grid(row=i+1, column=0)folder_name = tkinter.Label(self.inner_frame, text=item,  bg = 'white')folder_name.bind("<Button-1>", self.move_to_dir)folder_name.grid(row=i+1, column=1, sticky='w')else:#обрабатываем файлыif (not self.hidden_dir.get() and not item.startswith('.')) or self.hidden_dir.get():ext = self.take_extention_file(item)#фото, картинкиif ext in ['jpeg', 'jpg', 'png', 'gif']:photo = tkinter.PhotoImage(file ="img/photo.png")icon = tkinter.Label(self.inner_frame, image=photo,  bg = 'white')icon.image = photoicon.grid(row=i+1, column=0)file_name = tkinter.Label(self.inner_frame, text=item,  bg = 'white')file_name.grid(row=i+1, column=1, sticky='w')file_name.bind("<Button-3>", self.file_context_menu.popup_menu)else:#другие файлыif os.access(str(path) + item, os.R_OK):photo = tkinter.PhotoImage(file ="img/file.png")icon = tkinter.Label(self.inner_frame, image=photo,  bg = 'white')icon.image = photoicon.grid(row=i+1, column=0)folder_name = tkinter.Label(self.inner_frame, text=item,  bg = 'white')folder_name.grid(row=i+1, column=1, sticky='w')folder_name.bind("<Button-3>", self.file_context_menu.popup_menu)else:photo = tkinter.PhotoImage(file ="img/file_access.png")icon = tkinter.Label(self.inner_frame, image=photo,  bg = 'white')icon.image = photoicon.grid(row=i+1, column=0)folder_name = tkinter.Label(self.inner_frame, text=item,  bg = 'white')folder_name.grid(row=i+1, column=1, sticky='w')i += 1#обновляем inner_frame и устанавливаем прокрутку для нового содержимогоself.inner_frame.update()self.canvas.configure(scrollregion=self.canvas.bbox("all"))def move_to_dir(self, event):''' функция для перехода в выбранную директорию'''elem = event.widgetdir_name = elem["text"]fool_path = self.path_text.get() + dir_nameif os.path.isdir(fool_path) and os.access(fool_path, os.R_OK):old_path = self.path_text.get()self.path_text.set(old_path + dir_name + '/')self.root_click('<Button-1>')self.refresh_window()def parent_dir(self):''' функция для перемещения в родительскую директорию'''old_path = [i for i in self.path_text.get().split('/') if i]new_path = '/'+'/'.join(old_path[:-1])if not new_path:new_path = '/'if os.path.isdir(new_path):if new_path == '/':self.path_text.set(new_path)else:self.path_text.set(new_path + '/')self.refresh_window()def take_extention_file(self, file_name):''' функция для получения расширения файла'''ls = file_name.split('.')if len(ls)>1:return ls[-1]else:return Nonedef refresh_window(self):''' функция для обновления текущего отображения директорий/файлов'''for widget in self.inner_frame.winfo_children():widget.destroy()self.dir_content()self.canvas.yview_moveto(0)

И наконец, создание окна и запаковка виджетов:

win = MainWindow()win.root.mainloop()

Файлы, ассеты, бинарники здесь

Буду рад, если вы поделитесь со мной улучшенной версией этой программы!
Пишите: ki1killer@yandex.ru
Подробнее..

Как совместить парадигму Объектно-ориентированного программирования и Python в голове новичка?

13.11.2020 20:09:06 | Автор: admin

Я всегда с благодарностью относился к учебным текстам по программированию наподобиеЭТОГО:

На мой взгляд, хороший учебный материал и там всё прекрасно объяснено про Объектно-орииентированное программирование. Даны все понятия, определения, в общем - бери и используй!

Но как часто бывает у новичков, на самом деле у меня была только иллюзия ясности. Когда же сталкиваешься с изучением Python на практике, понимаешь, насколько всё сложно. И сперва такого типа код может внушать даже ужас, ну или там, чувство бессилия, мысли "Я непроходимо тупой", и т.д. - у каждого по-своему:

def createSetsMultiClasses(wordIndexes,xLen, step):  nClasses = len(wordIndexes)  xTest10Classes01 = []  xTest10Classes = []  for wI in wordIndexes:    sample = (getSetFromIndexes(wI, xLen, step))    xTest10Classes.append(np.array(sample))    xTest10Classes01.append(tokenizer.sequences_to_matrix(sample))  xTest10Classes01 = np.array(xTest10Classes01)  return xTest10Classes01, xTest10Classes

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

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

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

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

Выставляю на суд сообщества 2 диаграммы, родившиеся в результате такого понимания. Не претендую на уникальность, истину и полноту охвата. Надеюсь, что данная работа пригодится новичкам для осмысления Python и парадигмы объектно-ориентированного программирования, что называется в бою.

Данная диаграмма иллюстрирует понятия ОБЪЕКТ, КЛАСС, МЕТОД, ФУНКЦИЯ в их взаимосвязи. Я так же попытался осмыслить понятие ПЕРЕМЕННАЯ и то, как она используется в разных парадигмах программирования - Объектно-ориентированной и Функциональной (Процедурной).

Здесь изображена "мозгодробительная", на мой взгляд, вещь: как объектно-ориентированная модель работает совместно с функциональным программированием. И как объекты в Питоне могут использоваться как элементы функций.

Всем удачи в учёбе и осмыслении действительности!

Подробнее..

Как перестать беспокоиться и начать жить

14.02.2021 14:06:25 | Автор: admin

Мониторинг работы организации с помощью докера и телеграм-бота

Многим хотелось бы знать, если вдруг что-то случится с их сайтом, магазином и т.п., особенно, если это не требует ни денег, ни времени, ни усилий. А ведь это довольно легко сделать: потратив совсем немного свободного времени, создадим докер контейнер, который будет периодически делать запросы в БД, расчитывать на их основании некие метрики, сравнивать их с пороговыми значениями и в случае превышения этих порогов, оповещать заинтересованных лиц.

Первым делом надо завести бота. Как это сделать можно нагуглить за 5 минут. Например, тут недавно была статья. Также нашему боту нужно разрешение на добавление его в группы, если получать сообщения планирует не один человек, а несколько. Для этого при создании бота надо написать BotFather команду /setjoingroups. После этого, собственно надо создать группу, добавить в нее нашего бота и всех заинтересованных в получении сообщений.

Далее воспользуемся библиотекой pytelegrambotapi и закончим подготовительный этап тем, что узнаем CHAT_ID Его можно узнать, например, включив логирование и, воспользовавшись командой из той же статьи, включить своего бота на ожидание команды /start. Затем ввести эту команду в созданной нами группе. Команда придет к боту, он ответит, логах будет выведена масса разной информации, в том числе и нужный нам CHAT_ID группы:

import loggingimport telebotbot = telebot.TeleBot(TOKEN)chat_id = CHAT_IDlogger = telebot.loggertelebot.logger.setLevel(logging.DEBUG)@bot.message_handler(commands=['start'])def start_message(message):    bot.send_message(message.chat.id, 'Привет, ты написал мне /start')bot.polling()

Теперь мы можем посылать сообщения ботом в нашу группу:

bot.send_message(chat_id=CHAT_ID, text=TEXT)

Чтобы было что посылать рассчитаем метрику, по данным полученным из БД. Допустим, у нас каждый бизнес процесс подтверждается СМС клиенту. Соответственно, если давно не посылались СМС, что-то идет не так.

Создадим класс, который будет отвечать за подключение к БД и расчет метрик. Я использовал для доступа в БД библиотеку pymysql. Она легкая и понятная:

import datetimeimport osfrom contextlib import closingimport pymysqlfrom pymysql.cursors import DictCursorfrom constants import *class Monitor:    def __init__(self):        self.starttime = datetime.datetime.today()        self.port = 3306        self.host = os.environ.get('MYSQL_HOST')        self.user = os.environ.get('USER')        self.password = os.environ.get('MYSQL_PWD')        self.db_name = 'backend'def sms_log(self):    with closing(pymysql.connect(host=self.host, port=self.port, user=self.user, password=self.password,db=self.db_name, charset='utf8', cursorclass=DictCursor)) as connection:        end_time = datetime.datetime.today()        start_time = end_time - datetime.timedelta(minutes=TIME_PERIOD)        st_time = start_time.strftime('%Y-%m-%d %H:%M:%S')                end_time = end_time.strftime('%Y-%m-%d %H:%M:%S')        with connection.cursor() as cursor:            query = f"SELECT COUNT(*) FROM sms_log WHERE created_at BETWEEN '{st_time}' AND '{end_time}'"            cursor.execute(query)            for row in cursor:                result = row['COUNT(*)']                if result < SMS_COUNT_ALERT:                    return f"За {TIME_PERIOD} минут с \            f"{start_time.strftime('%H:%M')} " \                 f"было отправлено {result} СМС, предел: {SMS_COUNT_ALERT}"

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

def main():    bot = telebot.TeleBot(TOKEN)    chat_id = CHAT_ID    logger = telebot.logger    telebot.logger.setLevel(logging.DEBUG)      monitor = Monitor()    while True:        """         Выполняем запросы в БД каждые 15 (TIME_PERIOD) минут.         """      result = monitor.sms_log()      if result:            bot.send_message(            chat_id=chat_id,             text=result,            disable_notification=not monitor.is_day()        )      . . .      sleep(TIME_PERIOD*60)

Для того, чтобы сообщения ночью приходили бесшумно, используем следующую конструкцию:

disable_notification=not monitor.is_day()

где

@staticmethoddef is_day():    if 9 <= (datetime.datetime.today()).hour <= 23:        return True    else:        return False

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

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

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

FROM python:3.6-alpineCOPY requirements.txt requirements.txtRUN python -m venv venvRUN venv/bin/pip install -r requirements.txtRUN apk add bashCOPY src srcCOPY .gitignore .gitignoreCOPY boot.sh boot.shCMD ["bash", "./boot.sh"]

где requirements.txt:

PyMySQL==1.0.2pyTelegramBotAPI==3.7.6

и boot.sh:

#!/bin/shsource venv/bin/activateexec python ./src/bot.py

Можно не ставить bash и заменить его на /bin/sh , мне с ним привычнее при отладке, но на боевых условиях это лишнее. Образ также можно выбрать поновее, но и этот отлично справится.

Теперь его надо сбилдить:

docker build -t bot:latest .

И запустить, передав переменные окружения для подключения к БД в команде:

docker run --name bot -d -e USER=ххх -e MYSQL_HOST=ххх -e \MYSQL_PWD=ххх bot:latest

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

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

Подробнее..

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

01.09.2020 22:17:56 | Автор: admin
  • Общее введение
  • ФП
    • Введение в ФП
    • Главные свойства функционального программирования
    • Основные термины
    • Встроенное ФП поведение в Python
    • Библиотека Xoltar Toolkit
    • Библиотека returns
    • Литература
  • Генераторы
    • Введение в итераторы
    • Введение в генераторы
    • Генераторы vs итераторы
    • Генераторы как пайплайн
    • Концепт yield from
    • Маршрутизация данных на генераторах (мультиплексирование, броадкастинг)
    • Пример трейсинга генератора
    • Стандартные инструменты генераторы
    • Выводы
      • Плюсы
      • Минусы
    • Литература
  • Итоги

Общее введение


Говоря о Python, обычно используется процедурный и ООП стиль программирования, однако это не значит, что другие стили невозможны. В презентации ниже мы рассмотрим ещё пару вариантов Функциональное программирование и программирование с помощью генераторов. Последние, в том числе, привели к появлению сопрограмм, которые позднее помогли создать асинхронность в Python. Сопрограммы и асинхронность выходят за рамки текущего доклада, поэтому, если интересно, можете ознакомиться об этом самостоятельно. Лично я рекомендую книгу "Fluent Python", в которой разговор начинается от итераторов, плавно переходит в темы о генераторах, сопрограммах и асинхронности.


ФП


Введение в ФП


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


Выделяют две крупные парадигмы программирования: императивная и декларативная.


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


Декларативное программирование предполагает ответ на вопрос Что?. Здесь вы описываете задачу, даете спецификацию, говорите, что вы хотите получить в результате выполнения программы, но не определяете, как этот ответ будет получен. Каждая из этих парадигм включает в себя более специфические модели.


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


В рамках процедурного подхода к программированию основное внимание сосредоточено на декомпозиции разбиении программы / задачи на отдельные блоки / подзадачи. Разработка ведётся пошагово, методом сверху вниз. Наиболее распространенным языком, который предполагает использование процедурного подхода к программирования является язык C, в нем, основными строительными блоками являются функции.


В рамках объектно-ориентированного (ООП) подхода программа представляется в виде совокупности объектов, каждый из которых является экземпляром определенного класса, классы образуют иерархию наследования. ООП базируется на следующих принципах: инкапсуляция, наследование, полиморфизм, абстракция. Примерами языков, которые позволяют вести разработку в этой парадигме являются C#, Java.


В рамках функционального программирования выполнение программы процесс вычисления, который трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании). Языки, которые реализуют эту парадигму Haskell, Lisp.


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


Главные свойства функционального программирования


  • Функции являются объектами первого класса (First Class Object).
    Это означает, что с функциями вы можете работать, также как и с данными передавать их в качестве аргументов другим функциям, присваивать переменным и т.п.
  • Использование рекурсии в качестве основной структуры контроля потока управления. В некоторых языках не существует иной конструкции цикла, кроме рекурсии.
  • Акцент на обработке списков (lists, отсюда название Lisp LISt Processing). Списки с рекурсивным обходом подсписков часто используются в качестве замены циклов.
  • Используются функции высшего порядка (High Order Functions). Функции высшего порядка функции, которые могут в качестве аргументов принимать другие функции.
    Функции высшего порядка принимают в качестве аргументов другие функции. В стандартную библиотеку Python входит достаточно много таких функций, в качестве примера приведем функцию map. Она принимает функцию и Iterable объект, применяет функцию к каждому элементу Iterable объекта и возвращает Iterator объект, который итеративно возвращает все модифицированные после функции элементы.
  • Функции являются чистыми (Pure Functions) т.е. не имеют побочных эффектов (иногда говорят: не имеют сайд-эффектов).
    В Python это не выполняется. Необходимо самостоятельно следить за тем, чтобы функция была чистой.
  • Акцент на том, что должно быть вычислено, а не на том, как вычислять.

Основные термины


Не все термины ниже необходимы для понимания доклада, но необходимы для понимания ФП (спасибо одному другу за их подборку)


  • Ссылочная прозрачность.


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


  • Функции


    • Детерминированные.


      Детерминированная функция возвращает тот же результат для одних и тех же входных данных.


    • Чистые.


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


    • Тотальные.


      Тотальная функция возвращает вывод для каждого ввода.
      Тотальные функции всегда завершаются и никогда не вызывают исключений.



  • Композиция функций применение одной функции к результату другой


  • Сайд эффект.


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


  • Полиморфизм.


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


    • Параметрический полиморфизм.


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


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



  • Замыкание (closure)


    Замыкание процедура вместе с привязанной к ней совокупностью данных. Steve Majewski
    Замыкание функция, которая ссылается на свободные переменные в своей области видимости.



Встроенное ФП поведение в Python


Базовые элементы ФП в Python функции map(), reduce(), filter() и оператор lambda. В Python 1.x введена также функция apply(), удобная для прямого применения функции к списку, возвращаемому другой. Python 2.0 предоставляет для этого улучшенный синтаксис. Начиная с Python 2.3 считается устаревшей, удалена в Python 3.0


Несколько неожиданно, но этих функций и всего нескольких базовых операторов почти достаточно для написания любой программы на Python; в частности, все управляющие утверждения (if, elif, else, assert, try, except, finally, for, break, continue, while, def) можно представить в функциональном стиле, используя исключительно функции и операторы. Несмотря на то, что задача реального удаления всех команд управления потоком, возможно, полезна только для представления на конкурс "невразумительный Python" (с кодом, выглядящим как программа на Lisp'е), стоит уяснить, как ФП выражает управляющие структуры через вызовы функций и рекурсию.


В соответствии с вышесказанным, попробуем сделать несколько хаков, чтобы наш код был более ФПшный. Избавимся от if/elif/else в Python


# Normal statement-based flow controlif <cond1>:     func1() elif <cond2>:     func2() else:     func3()     # Equivalent "short circuit" expression(<cond1> and func1()) or (<cond2> and func2()) or (func3()) 

Используем lambda для присваивания таких условных выражений


pr = lambda s:s namenum = lambda x: (x==1 and pr("one")) or (x==2 and pr("two")) or (pr("other"))assert namenum(1) == 'one' assert namenum(2) == 'two' assert namenum(3) == 'other'

Замена циклов на выражения так же проста, как и замена условных блоков. for может быть переписана с помощью map().


for e in lst:      func(e)      # statement-based loopmap(func,lst)    # map-based loop

То же самое мы можем сделать и с функциями.


do_it = lambda f: f()# let f1, f2, f3 (etc) be functions that perform actionsmap(do_it, [f1,f2,f3])

Перевести while впрямую немного сложнее, но вполне получается.


# statement-based while loopwhile <cond>:     <pre-suite>     if <break_condition>:         break    else:         <suite> # FP-style recursive while loopdef while_block():     <pre-suite>     if <break_condition>:         return 1     else:         <suite>         return 0 while_FP = lambda: (<cond> and while_block()) or while_FP() while_FP()

ФП вариант while все еще требует функцию while_block(), которая сама по себе может содержать не только выражения, но и утверждения (statements). Но мы могли бы продолжить дальнейшее исключение утверждений в этой функции (как, например, замену блока if/else в вышеописанном шаблоне).


К тому же, обычная проверка на месте (наподобие while myvar == 7) вряд ли окажется полезной, поскольку тело цикла (в представленном виде) не может изменить какие-либо переменные (хотя глобальные переменные могут быть изменены в while_block()). Один из способов применить более полезное условие заставить while_block() возвращать более осмысленное значение и сравнивать его с условием завершения.

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


# imperative version of "echo()"def echo_IMP():    while 1:         x = input("IMP -- ")         if x == 'quit':             break        else:            print(x) echo_IMP() # utility function for "identity with side-effect"def monadic_print(x):    print(x)     return x     # FP version of "echo()" echo_FP = lambda: monadic_print(input("FP -- ")) == 'quit' or echo_FP() echo_FP()

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


Мы все еще используем служебную функцию monadic_print(), но эта функция совершенно общая и может использоваться в любых функциональных выражениях, которые мы создадим позже. Заметим, что любое выражение, содержащее monadic_print(x) вычисляется так же, как если бы оно содержало просто x.


После всей проделанной работы по избавлению от совершенно осмысленных конструкций и замене их на невразумительные вложенные выражения, возникает естественный вопрос "Зачем?!". Перечитывая описания характеристик ФП, мы можем видеть, что все они достигнуты в Python. Но важнейшая (и, скорее всего, в наибольшей степени реально используемая) характеристика исключение побочных эффектов или, по крайней мере, ограничение их применения специальными областями наподобие монад. Огромный процент программных ошибок и главная проблема, требующая применения отладчиков, случается из-за того, что переменные получают неверные значения в процессе выполнения программы.


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


# Nested loop procedural style for finding big products xs = (1,2,3,4) ys = (10,15,3,22) bigmuls = [] # ...more stuff...for x in xs:     for y in ys:         # ...more stuff...        if x*y > 25:             bigmuls.append((x,y))         # ...more stuff...    # ...more stuff...    print(bigmuls)

Секции, комментированные как #...more stuff... места, где побочные эффекты с наибольшей вероятностью могут привести к ошибкам.


В любой из этих точек переменные xs, ys, bigmuls, x, y могут приобрести неожиданные значения в гипотетическом коде. Далее, после завершения этого куска кода все переменные могут иметь значения, которые могут ожидаться, а могут и не ожидаться посдедующим кодом.


Очевидно, что инкапсуляция в функциях/объектах и тщательное управление областью видимости могут использоваться, чтобы защититься от этого рода проблем. Вы также можете всегда удалять (del) ваши переменные после использования.


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


bigmuls = lambda xs,ys: filter(lambda (x,y):x*y > 25, combine(xs,ys))combine = lambda xs,ys: map(None, xs*len(ys), dupelms(ys,len(xs)))dupelms = lambda lst,n: reduce(lambda s,t:s+t, map(lambda l,n=n: [l]*n, lst))print(bigmuls((1,2,3,4),(10,15,3,22)))

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


print([(x,y) for x in (1,2,3,4) for y in (10,15,3,22) if x*y > 25])

Да, всё верно, list, tuple, set, dict comprehensions и generator expressions изначально ФП техники, которые, как и многое другое, перекочевало в другие не-ФП языки


Библиотека Xoltar Toolkit


Сразу оговоримся, что библиотека достаточно старая и подходит лишь для Python 2, однако для ознакомления её достаточно. Библиотека Xoltar Toolkit Брина Келлера (Bryn Keller) покажет нам больше возможностей ФП.


Основные возможности ФП Келлер представил в виде небольшого эффективного модуля на чистом Python. Помимо модуля functional, в Xoltar Toolkit входит модуль lazy, поддерживающий структуры, вычисляемые "только когда это необходимо". Множество функциональных языков программирования поддерживают отложенное вычисление, поэтому эти компоненты Xoltar Toolkit предоставят вам многое из того, что вы можете найти в функциональном языке наподобие Haskell.


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


>>> car = lambda lst: lst[0] >>> cdr = lambda lst: lst[1:] >>> sum2 = lambda lst: car(lst)+car(cdr(lst)) >>> sum2(range(10))1 >>> car = lambda lst: lst[2] >>> sum2(range(10))5

К несчастью, одно и то же выражение sum2(range(10)) вычисляется к разным результатам в двух местах программы, несмотря на то, что аргументы выражении не являются изменяемыми переменными.


К счастью, модуль functional предоставляет класс Bindings, предотвращающий такое переприсваивание.


>>> from functional import * >>> let = Bindings() >>> let.car = lambda lst: lst[0] >>> let.car = lambda lst: lst[2] Traceback (innermost last):     File "<stdin>",         line 1, in ? File "d:\tools\functional.py",         line 976, in __setattr__ raise BindingError, "Binding '%s' cannot be modified." % name         functional.BindingError: Binding 'car' cannot be modified. >>> car(range(10)) 0

Разумеется, реальная программа должна перехватить и обработать исключение BindingError, однако сам факт его возбуждения позволяет избежать целого класса проблем.


Библиотека returns


Уже более современная библиотека, предлагаемая Никитой Соболевым, нашим соотечественником, также позволяет использовать возможности ФП в Python. Декоратор maybe позволяет переходить к следующей итерации только при успешном завершении предыдущей


from returns.maybe import Maybe, maybe@maybe  # decorator to convert existing Optional[int] to Maybe[int]def bad_function() -> Optional[int]:    ...    maybe_number: Maybe[float] = bad_function().map(    lambda number: number / 2,    )# => Maybe will return Some[float] only if there's a non-None value#    Otherwise, will return Nothing

Более реальный пример, из императивного стиля в декларативный можно переписать так:


# Imperative styleuser: Optional[User]discount_program: Optional['DiscountProgram'] = Noneif user is not None:     balance = user.get_balance()     if balance is not None:         credit = balance.credit_amount()         if credit is not None and credit > 0:            discount_program = choose_discount(credit)# same with returnsuser: Optional[User]# Type hint here is optional, it only helps the reader here:discount_program: Maybe['DiscountProgram'] = Maybe.from_value(    user,    ).map(  # This won't be called if `user is None`    lambda real_user: real_user.get_balance(),    ).map(  # This won't be called if `real_user.get_balance()` returns None    lambda balance: balance.credit_amount(),    ).map(  # And so on!    lambda credit: choose_discount(credit) if credit > 0 else None,    )

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


# Imperative styledef fetch_user_profile(user_id: int) -> 'UserProfile':    """Fetches UserProfile dict from foreign API."""    response = requests.get('/api/users/{0}'.format(user_id))    # What if we try to find user that does not exist?    # Or network will go down? Or the server will return 500?    # In this case the next line will fail with an exception.    # We need to handle all possible errors in this function    # and do not return corrupt data to consumers.    response.raise_for_status()    # What if we have received invalid JSON?    # Next line will raise an exception!    return response.json()

И то же самое, только с помощью returns


import requestsfrom returns.result import Result, safefrom returns.pipeline import flowfrom returns.pointfree import binddef fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:    """Fetches `UserProfile` TypedDict from foreign API."""    return flow(        user_id,        _make_request,        bind(_parse_json),    )@safedef _make_request(user_id: int) -> requests.Response:    response = requests.get('/api/users/{0}'.format(user_id))    response.raise_for_status()    return response@safedef _parse_json(response: requests.Response) -> 'UserProfile':    return response.json()

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


Вместо обычных значений возвращаются значения, заключенные в специальный контейнер, благодаря декоратору @safe. Он вернет Success [YourType] или Failure [Exception]. И никогда не бросит нам исключение!


Подробнее об этой библиотеке можно уточнить из её достаточно большой и подробной документации, указанной в литературе.


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

Литература



Генераторы


Введение в итераторы


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


>>> for x in [1,4,5,10]:... print(x, end=' ')...1 4 5 10

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


>>> items = [1, 4, 5]>>> it = iter(items)>>> it.__next__()1>>> it.__next__()4>>> it.__next__()5>>> it.__next__()

Внутри под капотом обычной итерации.


for x in obj:    # statements

Происходит примерно следующее:


_iter = iter(obj) # Get iterator objectwhile 1:    try:        x = _iter.__next__() # Get next item    except StopIteration: # No more items        break    # statements

Фактически, любой объект, который поддерживает конструкцию iter() называется итерируемым. Чтобы добавить его поддержку в своём классе, нам необходимо имплементировать методы __iter__() и __next__().


Например, посмотрим, как имплементировать такое:


>>> for x in Countdown(10):... print(x, end=' ')...10 9 8 7 6 5 4 3 2 1

Его реализация будет такова:


class Countdown(object):    def __init__(self,start):        self.start = start    def __iter__(self):        return CountdownIter(self.start)class CountdownIter(object):    def __init__(self, count):        self.count = count    def __next__(self):        if self.count <= 0:            raise StopIteration        r = self.count        self.count -= 1        return r

Введение в генераторы


Генератор функция, которая генерирует последовательность результатов вместо одного значения


def countdown(n):    while n > 0:        yield n        n -= 1

Вместо того, чтобы возвращать значение, мы создаём серию значений (с использованием оператора yield). Вызов функции генератора создает объект-генератор. Однако функция не запускается.


def countdown(n):    print("Counting down from", n)    while n > 0:        yield n        n -= 1>>> x = countdown(10)>>> x<generator object at 0x58490>>>>

Функция генератор выполняется только при вызове __next__().


>>> x = countdown(10)>>> x<generator object at 0x58490>>>> x.__next__()Counting down from 1010>>>

yield возвращает значение, но приостанавливает выполнение функции. Функция генератор возобновляется при следующем вызове __next__(). При завершении итератора возбуждается исключение StopIteration.


>>> x.__next__()9>>> x.__next__()8>>>...>>> x.__next__()1>>> x.__next__()Traceback (most recent call last):    File "<stdin>", line 1, in ?        StopIteration>>>

Небольшие выводы:


  • Функция генератор гораздо более удобный способ написания итератора
  • Вам не нужно беспокоиться об реализации протокола итератора (__next__, __iter__ и т. д.), т.к. при создании функции с yield магия Python уже добавляет в объект функции нужные методы.

>>> def x():...     return 1... >>> def y():...     yield 1... >>> [i for i in dir(y()) if i not in dir(x())]['__del__', '__iter__', '__name__', '__next__', '__qualname__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']

Помимо функции генератора возможно также использовать генераторное выражение, которое также возвращает объект генератора.


>>> a = [1,2,3,4]>>> b = (2*x for x in a)>>> b<generator object at 0x58760>>>> for i in b: print(b, end=' ')...2 4 6 8

Синтаксис генераторного выражения также прост


(expression for i in s if condition)# the same withfor i in s:    if condition:        yield expression

Генераторы vs итераторы


Функция генератор немного отличается от объекта, поддерживающего итерацию. Генератор разовая операция. Мы можем перебирать сгенерированные данные один раз, но если мы хотим сделать это снова, мы должны снова вызвать функцию генератор. Это отличается, например, от списка (который мы можем перебирать столько раз, сколько хотим)


Генераторы как пайплайн


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


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


Каждая строка в логах выглядит примерно так:


81.107.39.38 - ... "GET /ply/ply.html HTTP/1.1" 200 97238


Число байтов находится в последней колонке:


bytes_sent = line.rsplit(None,1)[1]

Это может быть число или отсутствующее значение.


81.107.39.38 - ... "GET /ply/ HTTP/1.1" 304 -


Сконвертируем полученный результат в число


if bytes_sent != '-':    bytes_sent = int(bytes_sent)

В итоге, может получиться примерно такой код


with open("access-log") as wwwlog:    total = 0    for line in wwwlog:        bytes_sent = line.rsplit(None,1)[1]        if bytes_sent != '-':            total += int(bytes_sent)    print("Total", total)

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


with open("access-log") as wwwlog:    bytecolumn = (line.rsplit(None,1)[1] for line in wwwlog)    bytes_sent = (int(x) for x in bytecolumn if x != '-')    print("Total", sum(bytes_sent))

Этот подход отличается от предыдущего, меньше строк, напоминает функциональный стиль Мы получили пайплайн


simple pipeline


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


Безусловно, этот генераторный подход имеет разновидности причудливой медленной магии. Файл из 1.3 ГБ код в старом стиле выполнил за 18.6 секунд, код на генераторах был выполнен за 16,7 секунд.


AWK с той же задачей справился гораздо медленее, за 70.5 секунд


awk '{ total += $NF } END { print total }' big-access-log

Небольшие выводы:


  • Это не только не медленный, но и на 10% быстрее
  • Меньше кода
  • Код относительно легко читать
  • И, честно говоря, мне он нравится в целом больше
  • Ни разу в нашем решении с генератором мы не создавали большие временные списки
  • Таким образом, это решение не только быстрее, но и может применяться к огромным файлам с данными
  • Подход конкурентоспособен с традиционными инструментами

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


Концепт yield from


'yield from' может использоваться для делегирования итерации


def countdown(n):    while n > 0:        yield n        n -= 1def countup(stop):    n = 1    while n < stop:        yield n        n += 1def up_and_down(n):    yield from countup(n)    yield from countdown(n)>>> for x in up_and_down(3):... print(x)...12321>>>

Также стоит упомянуть, что в более ранних версиях python (3.5 и ниже) yield from также использовался вместо await, пока await не стал новым ключевым зарезервированными словом, т.к. await по сути, просто передача контекста управления внутри другой корутины. Чтобы не возлагать много ответственности на yield from и было придумано отдельное зарезервированное слово await.


Маршрутизация данных на генераторах (мультиплексирование, броадкастинг)


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


multiplex broadcast pipeline


На данной диаграмме видно, что нам потребуется мультиплексирование (всё в одно) и броадкастинг (одно во всё). Что ж, не будем томить и напишем сразу решение.


# same with `tail -f`def follow(thefile):    thefile.seek(0, os.SEEK_END) # End-of-file    while True:        line = thefile.readline()        if not line:            time.sleep(0.1) # Sleep briefly            continue        yield linedef gen_cat(sources):    # В данном конкретном случае используется для распаковки вложенного списка в плоский    for src in sources:        yield from srcdef genfrom_queue(thequeue):    while True:        item = thequeue.get()        if item is StopIteration:            break        yield itemdef sendto_queue(source, thequeue):    for item in source:        thequeue.put(item)    thequeue.put(StopIteration)def multiplex(sources):    in_q = queue.Queue()    consumers = []    for src in sources:        thr = threading.Thread(target=sendto_queue, args=(src, in_q))        thr.start()        consumers.append(genfrom_queue(in_q))    return gen_cat(consumers)def broadcast(source, consumers):    for item in source:        for c in consumers:            c.send(item)class Consumer(object):    def send(self,item):        print(self, "got", item)if __name__ == '__main__':    c1 = Consumer()    c2 = Consumer()    c3 = Consumer()    log1 = follow(open("foo/access-log"))    log2 = follow(open("bar/access-log"))    log3 = follow(open("baz/access-log"))    lines = multiplex([log1, log2, log3])    broadcast(lines,[c1,c2,c3])

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


Пример трейсинга генератора


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


def trace(source):    for item in source:        print(item)        yield itemlines = follow(open("access-log"))log = trace(apache_log(lines))r404 = trace(r for r in log if r['status'] == 404)

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


Стандартные инструменты генераторы


Генераторы встречаются повсюду в стандартной библиотеке. Начиная с 3.0 их стараются внедрять повсеместно. Например, pathlib.Path.rglob, glob.iglob, os.walk, range, map, filter. Есть даже целиком библиотека на генераторах itertools.


Выводы


Плюсы:


  • Генераторы невероятно полезный инструмент для решения многообразных проблем
  • Сила исходит от способности настраивать пайплайны
  • Можно создавать компоненты, которые можно переиспользовать в разных пайплайнах
  • Небольшие компоненты, которые просто обрабатывают поток данных
  • Намного проще, чем это может быть сделано с помощью ООП шаблонов
  • Можно расширить идею пайплайнов во многих направлениях (сеть, потоки, корутины)

Минусы


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

Литература



Итоги


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


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


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


Если нашли ошибки, пишите в телеграмме Niccolum или на почту lastsal@mail.ru. Буду рад конструктивной критике.

Подробнее..

HMM ловим мошеннические транзакции

09.04.2021 14:13:02 | Автор: admin

Три года я проработал в Сербии iOS-евангелистом - было два профильный проекта и один Machine Learning-овый.

Если вам стало интересно - добро пожаловать в мир HMM.

Постановка задачи

Австрийский банк. У него много клиентов, у клиентов открыт счет в этом банке. В течении года клиент тратит средства со своего счета. Ходит в магазины, гасит коммунальные платежи и пр. Каждое списание денег со счета назовем транзакцией. Дана последовательность транзакций за определенное время (скажем год). Надо обучить машину, чтобы она начала проверять новые транзакции как достоверные или подозрительные. И выдавала предупреждение в последнем случае. Для решения задачи надо использовать Hidden Markov Model.

Введение в HMM

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

Представим эту последовательность из 365 символов в виде массива. h означает здоров, l - болен.

days{365} = {hhhhhhhhhhllllllllllhhhhhhhhhhhhhhhhhhhhhhhh...hhhhh}

Вопрос - Какова вероятность, что я сегодня болен?

\frac{10}{365}= 3 процента

Если вам понятен ответ, то читайте дальше, через 15 минут вы станете экспертом по HMM. Если нет - ставьте плюс и идите ужинать.

Изменим вопрос - Какова вероятность, что я завтра буду больным?

Вы резонно уточните: -А сегодня я болен или здоров?

Если болен (смотрите на последовательность - таких больных дней всего 10), то с вероятностью \frac{9}{10} = 90 процентов завтра я продолжу болеть и с вероятностью 10 процентов стану здоров.

А если я сегодня здоров? - то с вероятностью

\frac{1}{355}= 0.3 процента я завтра заболею и с вероятностью 99.7% буду здоров.

Если вы сегодня больны, то с вероятностью 10% завтра вы будете здоровы и 90% продолжите болеть.

Получили 4 числа, которые красиво вписываются в матрицу 2 на 2 - вуаля! эта матрица и есть Марковский слой. Уберем проценты, оставим чистые вероятности в диапазоне от 0 до 1, как того требует наука.

здоров

болен

здоров

0.997

0.003

болен

0.10

0.90

Еще раз взглянем на матрицу и прочитаем слева направо, что если ты сегодня здоров, завтра с вероятностью 0.997 ты останешься здоров, а с вероятностью 0.003 заболеешь и так далее.

Формализуем транзакции

Чем отличается последовательность транзакций от череды болен/здоров? Ничем.

Для доказательства этого возьмем список транзакций, который мне предоставил Сбережении по моей просьбе.

27.10.2020 00:00 GAZPROMNEFT AZS 219    2507,43 118 753,95 28.10.2020 / 298380 Автомобиль 26.10.2020 14:45 SPAR 77                319,73 121 261,38 27.10.2020 / 220146 Супермаркеты 26.10.2020 14:38 ATM 60006475           4800,00 121 581,11 26.10.2020 / 213074 Выдача наличных 25.10.2020 17:52 EUROSPAR 18            970,02 126 381,11 26.10.2020 / 259110 Супермаркеты 25.10.2020 00:00 Tinkoff Card2Card      20000,00 127 351,13 26.10.2020 / 253237 Перевод с карты 22.10.2020 14:22 SBOL перевод 4276      7000,00 147 351,13 22.10.2020 / 276951 Перевод с карты 22.10.2020 12:18 STOLOVAYA              185,00 154 351,13 23.10.2020 / 279502 Рестораны и кафе 21.10.2020 16:46 MEGAFON R9290499831    500,00 154 536,13 21.10.2020 / 224592 Комунальные платежи, связь, интернет. 21.10.2020 14:17 SPAR 77                987,03 155 036,13 22.10.2020 / 219015 Супермаркеты 21.10.2020 13:42 PYATEROCHKA 646        289,93 156 023,16 22.10.2020 / 294539 Супермаркеты 21.10.2020 00:00 MEBEL                  75,00 156 313,09 22.10.2020 / 279935 Прочие расходы 19.10.2020 14:54 SPAR 77                552,92 132 044,80 20.10.2020 / 208987 Супермаркеты 19.10.2020 00:00 MOBILE FEE             60,00 132 597,72 20.10.2020 / - Прочие операции 16.10.2020 14:19 SPAR 77                579,39 132 657,72 17.10.2020 / 229627 Супермаркеты 12.10.2020 13:33 STOLOVAYA              185,00 133 237,11 13.10.2020 / 261374 Рестораны и кафе 12.10.2020 00:00 OOO MASTERHOST         1000,00 133 422,11 13.10.2020 / 268065 Прочие расходы 11.10.2020 12:09 SPAR 77                782,87 134 422,11 12.10.2020 / 275816 Супермаркеты 10.10.2020 14:52 SBOL перевод           400,00 135 204,98 10.10.2020 / 276925 Перевод с карты 09.10.2020 13:29 SBOL перевод 5484*     1000,00 135 604,98 09.10.2020 / 229184 Перевод с карты 09.10.2020 11:55 MAGNIT MK KRYUCHYA     274,00 136 604,98 10.10.2020 / 209914 Супермаркеты

Напишем на питоне скрипт, который вытащит последовательность моих транзакций в рублях и нарисует график

def readtrans():    with open ("assets/trans.txt", "r") as file:        grades = file.read()    pattern = '(\d{2,5}),\d\d'    result = re.findall(pattern, grades)    r = list(map(int, result[0::2]))    return rdata = readtrans()t = list(range(len(data)))df = pd.DataFrame({'number':t, 'amount':data})ax1 = df.plot.bar(x='number', y='amount', rot=0, width=1.5)

Упростим картину - дешевые транзакции (менее 10$) обозначим буквой l, дорогие свыше 100$ буквой h, остальные - буквой m.

Наша последовательность стала выглядеть подозрительно знакомо

print(observations[:20])trans[] = ['m', 'm', 'm', 'l', 'm', 'm', 'h', 'm', 'l', 'l', 'm', 'l', 'l', 'l', 'l', 'l', 'l', 'm', 'l', 'l']

Теперь напишем Марковский слой для транзакций. Это будет матрица 3 на 3, поскольку у нас 3 возможных типа транзакций = {l,m,h}

[[0.5 0.3 0.2] [0.6 0.3 0.1] [0.7 0.3 0.0]]

Расшифровываем последнюю строку матрицы - если последняя транзакция была дорогая, то с вероятностью 0.7 последующая операция будет дешевой, с вероятностью 0.3 - средней и никогда снова дорогой.

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

-Так просто?! - воскликнет читатель. Да, но за построение такой простой модели банк много денег не заплатит. Надо ввести еще один Марковский слой (невидимый), и алгоритм сразу станет красивее, сложнее и наукоёмчее. И потянет на кандидатскую диссертацию.

Скрытый Марковский слой

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

То есть существует набор периодических однотипных событий, число которых мы не знаем и Марковский слой для которых невозможно построить. Как построишь, если ничего не знаешь конкретно?! Вот именно. Но мы знаем, что эти события происходят и их примерно 4-6 штук. Поход в магазин. Столовая. Еще что-то. И так далее. И что мы точно знаем, что после похода в столовую мы никогда не опустошаем свой кошелек на сумму более 300 рублей.

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

[[a1 a2 a3 a4 a5] [b1 b2 b3 b4 b5] [c1 c2 c3 c4 c5] [x1 x2 x3 x4 x5] [y1 y2 y3 y4 y5]]

Именно 20, а не 25 (отвечать будет Ягуар). Это точно такой же Марковский слой, что мы строили раньше, но основанный на 5 событиях.

И чтобы связать два слоя (видимый с платежами и невидимый с событиями) мы заведем матрицу перехода размера 5 на 3.

В чем смысл матрицы перехода? В том, что после невидимого события a (поход в столовую)

у нас произойдет с разной степенью вероятности либо l-платеж, либо m-платеж, либо h-платеж. Скорее всего после похода в столовую эта строка будет такая

[0.96 0.04 0.0]

Что означает невозможность потратить более 100 долларов в нашей технопарковской столовой. Даже с приглашенной девушкой.

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

И мы можем найти эти 20+10 неизвестных величин, потому что у нас есть история платежей!

Это прекрасно и это называется обучить систему!

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

Обучение

В питоне есть библиотека hmm, в которой существует метод обучения - ему надо подать ваши три матрицы, и серию последовательностей из транзакций. Алгоритм такой, что вы разбиваете историю ваших транзакций порциями штук по 15-20 и скармливаете ее, пока система не обучится.

Процесс сходимости хорошо виден на графике.

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

Питон против Сишарпа

Работодатель обязал меня использовать библиотек Accord и язык C#

using Accord.MachineLearning;using Accord.Statistics.Models.Markov;using Accord.Statistics.Models.Markov.Learning;using Accord.Statistics.Models.Markov.Topology;using Comtrade.FMS.Common;

Согласно контракту я не могу разглашать код, поэтому написал аналог на Питоне (я не знаю оба языка) и частично смог повторить результат. Но по скорости обучения Питон сильно проигрывает Си-шарпу. Правда, я run-ил программу на разного класса компьютерах)) В Словении это был сервер работодателя. Питон для Хабра пыхтел на 2010 года макбуке.

Приведу одну строку кода, в которой зашифрован метод обучения.

var teacher = new BaumWelchLearning(hmm)

Детали метода Баум-Уэлша вы поймете, прочитав соответствующую литературу и настроив свои мозги на стат. процессы.

Успехов и хорошей карьеры в банковских IT структурах!

Подробнее..

Декораторы Python хватит это терпеть

02.06.2021 12:04:43 | Автор: admin
Конец страданиям.Конец страданиям.

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

Я не буду рассказывать про области применения декораторов. Есть множество статей на эту тему.

Для начала, давайте вспомним: что же такое декораторы в Пайтон.

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

Давайте разбираться!

Как работают декораторы

def decorator_function(wrapped_func):    def wrapper():        print('Входим в функцию-обёртку')        print('Оборачиваемая функция: ', wrapped_func)        print('Выполняем обёрнутую функцию...')        wrapped_func()        print('Выходим из обёртки')    return wrapper

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

Теперь можно декорировать:

@decorator_functiondef hello_world():        print('Hello world!')hello_world()

Здесь декоратор получает функцию hello_world, и подменяет её своей вложенной функцией wrapper.

Вывод:

Входим в функцию-обёрткуОборачиваемая функция:  <function hello_world at 0x0201B2F8>Выполняем обёрнутую функцию...Hello world!Выходим из обёртки

Важно помнить!

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

Мы это увидим, если добавим две строчки в наш декоратор:

def decorator_function(wrapped_func):    print('Входим в декоратор')    def wrapper():        ...    print('Выходим из декоратора')    return wrapper
@decorator_functiondef hello_world():        print('Hello world!')
Входим в декораторВыходим из декоратора
hello_world()
Входим в функцию-обёрткуОборачиваемая функция:  <function hello_world at 0x0201B2F8>Выполняем обёрнутую функцию...Hello world!Выходим из обёртки

А вот и страдания: аргументы функции и аргументы декоратора

У функции, которую мы декорируем, могут быть аргументы. Принимает их вложенная функция wrapper:

def decorator_function(wrapped_func):    def wrapper(*args):        ...        wrapped_func(args)        ...    return wrapper  @decorator_functiondef hello_world(text):        print(text)    hello_world('Hello world!')

А ещё, аргументы могут быть переданы непосредственно в декоратор:

def fictive(decorator_text):    def decorator_function(wrapped_func):                def wrapper(*args):            print(decorator_text, end='')            wrapped_func(*args)        return wrapper        return decorator_function@fictive(decorator_text='Hello, ')def hello_world(text):        print(text)hello_world('world!')

Здесь аргумент decorator_text передаётся при декорировании в строке 11 и попадает в функцию fictive, строка 1. Таким образом, появился ещё один уровень вложенности только для того, чтобы принять аргументы декоратора.

Вывод:

Hello, world!

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

def fictive(_func=None, *, decorator_text=''):    def decorator_function(wrapped_func):        def wrapper(*args):            print(decorator_text, end='')            wrapped_func(*args)        return wrapper    if _func is None:        return decorator_function    else:        return decorator_function(_func)      @fictivedef hello_world(text):        print(text)hello_world('Hello, world!')@fictive(decorator_text='Hi, ')def say(text):        print(text)say('world!')

Вывод:

Hello, world!Hi, world!

Как Вам код? Вспомним, мантру Питонистов из начала статьи:

Декораторы - это удобный способ передать...

Ничего, на помощь придёт DecoratorHelper! Но, перед этим, ещё пара слов о декораторах.

Мифы декораторов

  1. Декораторы удобны. Думаю, с этим мы уже разобрались.

  2. В декораторы нужно передавать функции. Передавать можно не только функции, но и любые callable объекты. Это такие объекты, у которых определён дандер метод (магический метод) __call__. Этот метод отвечает за операции, которые будут произведены при вызове объекта (когда вы ставите скобочки после имени объекта: object()). Вместо функции может быть метод или класс.

  3. Декораторы - это функции. И опять: это может быть любой callable объект.

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

  1. Передавать можно не только функции, но и аргументы. Вместо функции может быть метод или некоторые классы.


DecoratorHelper: решение проблем

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

pip install DecoratorHelper

Импортируем и используем как декоратор:

from DecoratorHelper import DecoratorHelper@DecoratorHelperdef hello_world(text):        print(text)hello_world('Hello, world!')

Что это даёт?

  1. Вы больше не думаете над тем, будут ли аргументы у декоратора. DecoratorHelper думает об этом вместо Вас.

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

В итоге Вы получаете вместо функции объект, который имеет следующие атрибуты:

  • self.function - оборачиваемая функция

  • self.decorator_args- аргументы декоратора. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.

  • self.function_args- аргументы функции. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.

  • self.pre_function - то, что будет происходить перед выполнением функции (так можно превратить функцию в коллбэк).

  • self.post_function - то, что будет происходить после выполнения функции (так можно добавить функции в коллбэк).

Как использовать?

Перепишем приведённый ранее код декоратора, который может принимать/не принимать аргументы:

from DecoratorHelper import DecoratorHelperdef fictive(object):    object.pre_function = lambda : print(*object.decorator_args[:-1], end='')    return object    @fictive@DecoratorHelper('Hello, ')def hello_world(text):        print(text)hello_world('world!')

Как мы можем видеть, тело декоратора сократилось в 8 раз. Profit!

Ограничение! Первым аргументом нельзя передавать callable объекты, иначе всё сломается :) Думаю, для большинства задач, это не смертельно...

Что дальше?

В следующих версиях планируется:

  • Улучшенная обработка аргументов.

  • Встроенный счётчик вызовов.

  • Возможность превратить объект в синглтон.

  • Возможность превратить объект в буилдер.

  • Может быть, возможность подключить асинхронность.

    И всё это в максимально удобном формате: singleton = True.

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

Подробнее..

Сортируем файлы с помощью Python

11.06.2021 16:11:37 | Автор: admin

Штош. Скорее всего, у многих в папке загрузок собиралась куча разных инсталляторов, архивов и прочих файлов. И вот наступает момент, когда из этой кучи экскрементов нужно найти какой-то файл. Вот я и подумал, почему бы не написать скрипт сортировщика по расширениям файлов на Python?

Пишем код

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

import os

Создадим переменную для пути папки или диска, в которой мы будем сортировать файлы. В моих любимых окошечках путь к файлу записывается через backslash, который в Python является специальным символом экранирования. Поэтому мы либо пишем 2 бэкслэша, либо добавляем перед строкой букву r, тем самым подавляя экранирование.

main_path = 'd:\\down'# main_path = r'd:\down

Чтобы создать папку, используем метод os.mkdir()

os.mkdir(main_path + '\\aboba')

Создаем много папок

Напишем функцию для создания папок из списка названий. Для каждого названия проверяем существование папки с помощью метода os.path.exists().

# also creates folders from dictionary keysdef create_folders_from_list(folder_path, folder_names):    for folder in folder_names:        if not os.path.exists(f'{folder_path}\\{folder}'):            os.mkdir(f'{folder_path}\\{folder}')

Теперь давайте создадим словарь extensions. Ключи - названия папок. Значения - расширения файлов для каждой отдельной папки.

# key names will be folder names!extensions = {    'video': ['mp4', 'mov', 'avi', 'mkv', 'wmv', '3gp', '3g2', 'mpg', 'mpeg', 'm4v',               'h264', 'flv', 'rm', 'swf', 'vob'],    'data': ['sql', 'sqlite', 'sqlite3', 'csv', 'dat', 'db', 'log', 'mdb', 'sav',              'tar', 'xml'],    'audio': ['mp3', 'wav', 'ogg', 'flac', 'aif', 'mid', 'midi', 'mpa', 'wma', 'wpl',              'cda'],    'image': ['jpg', 'png', 'bmp', 'ai', 'psd', 'ico', 'jpeg', 'ps', 'svg', 'tif',               'tiff'],    'archive': ['zip', 'rar', '7z', 'z', 'gz', 'rpm', 'arj', 'pkg', 'deb'],    'text': ['pdf', 'txt', 'doc', 'docx', 'rtf', 'tex', 'wpd', 'odt'],    '3d': ['stl', 'obj', 'fbx', 'dae', '3ds', 'iges', 'step'],    'presentation': ['pptx', 'ppt', 'pps', 'key', 'odp'],    'spreadsheet': ['xlsx', 'xls', 'xlsm', 'ods'],    'font': ['otf', 'ttf', 'fon', 'fnt'],    'gif': ['gif'],    'exe': ['exe'],    'bat': ['bat'],    'apk': ['apk']}

Передаем в функцию create_folders_from_list() новоиспеченный словарь. Папки создадутся из названий ключей.

Получаем пути подпапок и файлов

Пишем функцию для получения путей подпапок. Для каждого объекта в методе os.scandir() проверяем, является ли он каталогом.

def get_subfolder_paths(folder_path) -> list:    subfolder_paths = [f.path for f in os.scandir(folder_path) if f.is_dir()]    return subfolder_paths

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

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

def get_subfolder_names(folder_path) -> list:    subfolder_paths = get_subfolder_paths(folder_path)    subfolder_names = [f.split('\\')[-1] for f in subfolder_paths]    return subfolder_names

Теперь получим пути всех файлов в папке, скопируем функцию get_subfolder_paths() и добавим в условие генератора not.

def get_file_paths(folder_path) -> list:    file_paths = [f.path for f in os.scandir(folder_path) if not f.is_dir()]    return file_paths

Для полного счастья не хватает только функции получения имен файлов.

def get_file_names(folder_path) -> list:    file_paths = [f.path for f in os.scandir(folder_path) if not f.is_dir()]    file_names = [f.split('\\')[-1] for f in file_paths]    return file_names

Сортируем файлы

Приступаем к функции сортировки. Получаем пути файлов в переменную file_paths. Создаем переменную ext_list со списком метода словаря extensions.items(). Обращение к списку по индексу возвращает нам пару ключ-значение в виде списка, первый элемент которого - это ключ или название папки в нашем проекте, а второй элемент - это значение, то есть расширения файлов для этой папки.

def sort_files(folder_path):    file_paths = get_file_paths(folder_path)    ext_list = list(extensions.items())

Теперь создадим цикл для каждого пути файла в списке. Вытащим отдельно расширение и имя файла.

for file_path in file_paths:  extension = file_path.split('.')[-1]  file_name = file_path.split('\\')[-1]

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

for dict_key_int in range(len(ext_list)):  if extension in ext_list[dict_key_int][1]:    print(f'Moving {file_name} in {ext_list[dict_key_int][0]} folder\n')    os.rename(file_path, f'{main_path}\\{ext_list[dict_key_int][0]}\\{file_name}')

Сделать это можно при помощи изменения пути файла методом os.rename("Путь файла сейчас", "Будущий путь файла")

Готовая функция сортировки файлов:

def sort_files(folder_path):    file_paths = get_file_paths(folder_path)    ext_list = list(extensions.items())    for file_path in file_paths:        extension = file_path.split('.')[-1]        file_name = file_path.split('\\')[-1]        for dict_key_int in range(len(ext_list)):            if extension in ext_list[dict_key_int][1]:                print(f'Moving {file_name} in {ext_list[dict_key_int][0]} folder\n')                os.rename(file_path, f'{main_path}\\{ext_list[dict_key_int][0]}\\{file_name}')

Удаляем пустые папки

Остался последний штрих - удаление пустых папок. Все просто. Создаем функцию. Получаем пути подпапок. Проверяем, какой список возвращает метод os.listdir("folder_path") для каждой подпапки. Если возвращается пустой список, значит удаляем папку с помощью os.rmdir("folder_path")

def remove_empty_folders(folder_path):    subfolder_paths = get_subfolder_paths(folder_path)    for p in subfolder_paths:        if not os.listdir(p):            print('Deleting empty folder:', p.split('\\')[-1], '\n')            os.rmdir(p)

Полный код программы

import osmain_path = 'd:\\down'# key names will be folder names!extensions = {    'video': ['mp4', 'mov', 'avi', 'mkv', 'wmv', '3gp', '3g2', 'mpg', 'mpeg', 'm4v', 'h264', 'flv',              'rm', 'swf', 'vob'],    'data': ['sql', 'sqlite', 'sqlite3', 'csv', 'dat', 'db', 'log', 'mdb', 'sav', 'tar', 'xml'],    'audio': ['mp3', 'wav', 'ogg', 'flac', 'aif', 'mid', 'midi', 'mpa', 'wma', 'wpl', 'cda'],    'image': ['jpg', 'png', 'bmp', 'ai', 'psd', 'ico', 'jpeg', 'ps', 'svg', 'tif', 'tiff'],    'archive': ['zip', 'rar', '7z', 'z', 'gz', 'rpm', 'arj', 'pkg', 'deb'],    'text': ['pdf', 'txt', 'doc', 'docx', 'rtf', 'tex', 'wpd', 'odt'],    '3d': ['stl', 'obj', 'fbx', 'dae', '3ds', 'iges', 'step'],    'presentation': ['pptx', 'ppt', 'pps', 'key', 'odp'],    'spreadsheet': ['xlsx', 'xls', 'xlsm', 'ods'],    'font': ['otf', 'ttf', 'fon', 'fnt'],    'gif': ['gif'],    'exe': ['exe'],    'bat': ['bat'],    'apk': ['apk']}# also creates folders from dictionary keysdef create_folders_from_list(folder_path, folder_names):    for folder in folder_names:        if not os.path.exists(f'{folder_path}\\{folder}'):            os.mkdir(f'{folder_path}\\{folder}')def get_subfolder_paths(folder_path) -> list:    subfolder_paths = [f.path for f in os.scandir(folder_path) if f.is_dir()]    return subfolder_pathsdef get_file_paths(folder_path) -> list:    file_paths = [f.path for f in os.scandir(folder_path) if not f.is_dir()]    return file_pathsdef sort_files(folder_path):    file_paths = get_file_paths(folder_path)    ext_list = list(extensions.items())    for file_path in file_paths:        extension = file_path.split('.')[-1]        file_name = file_path.split('\\')[-1]        for dict_key_int in range(len(ext_list)):            if extension in ext_list[dict_key_int][1]:                print(f'Moving {file_name} in {ext_list[dict_key_int][0]} folder\n')                os.rename(file_path, f'{main_path}\\{ext_list[dict_key_int][0]}\\{file_name}')def remove_empty_folders(folder_path):    subfolder_paths = get_subfolder_paths(folder_path)    for p in subfolder_paths:        if not os.listdir(p):            print('Deleting empty folder:', p.split('\\')[-1], '\n')            os.rmdir(p)if __name__ == "__main__":    create_folders_from_list(main_path, extensions)    sort_files(main_path)    remove_empty_folders(main_path)

Настройка программы под свои нужды

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

Приведу пример. Для каждого видео на свой YouTube канал я создаю каталог, в котором есть папки для футажей, картинок, звука, mkv файлов для последующего конвертирования в mp4 (premiere не любит mkv) и самого проекта.

Вот такой словарь.

main_folder = 'f:\\shtosh python\\new video'# key names will be folder names!extensions = {    'img': ['jpg', 'png', 'bmp', 'gif', 'ico', 'jpeg'],    'audio': ['mp3', 'wav'],    'footage': ['mp4', 'mov', 'avi'],    'mkv': ['mkv'],    'prj': []}

С помощью такого подхода я могу накидать любые файлы для монтажа в одну папку, затем запустить скрипт и легко импортировать все это дело в premiere pro.

Заключение

Штош. Код лежит на GitHub. Берите, изменяйте под себя, пользуйтесь на здоровье. Буду рад любому фидбеку.

Подробнее..

Хакатоны Зима 2020

04.01.2021 14:06:43 | Автор: admin

Это продолжение моего обзора по хакатонам 2020 года - первая часть тут

AGRO HACK

Линк на хакатон - https://agro-code.ru/

[ Короткий хакатон прошедший 11.12 13.12 ]

Недавно мы с друзьями поучаствовали в хакатоне Агрокод, прошедший в течении выходных с 11 по 13 декабря. Хакатон проводил Россельхозбанк. В хакатоне мы участвовали командой из 5 человек.

Хакатон включал в себя несколько различных заданий-треков. Среди заданий были следующие:

  • Разработка приложения для повышения урожайности космической клубники

  • Рекомендательный сервис по размещению культур на сельскохозяйственных полях

  • Определение заболоченных и переувлажненных участков сельскохозяйственных угодий

  • Нейросеть для обнаружения заболеваний листьев яблони по фотографии

  • Разработка полетного задания для группировки сельскохозяйственных дронов

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

На этот хакатон мы решили собрать команду. Основная причина в том, что кроме модели с машинным обучением надо было создать веб интерфейс. Для этого понадобилось больше специалистов. Изначально нас было всего два человека Владимир @vovaekb90 и я. Мы специалисты только в машинном обучении. Поэтому мы пригласили в команду еще двух человек Никиту @NickTheBuilder и Илью @Sopherdjin, они отвечали за реализацию веба фронт и бекенда. Критерий оценки был 80% за точность модели и 20% за веб интерфейс который использовал эту модель. На совещании команды мы решили добавить специалистов по машинному обучению и я пригласил в команду Антона @med_phisiker . Мы назвали команду Berserkers AI и приняли участие в хакатоне. Нам понравилась организация этого мероприятия. Перед началом хакатона был общий стрим в зуме с ответами на вопросы по задаче и по данным. Это сильно помогло нам на начальном этапе работы. Оказалось организаторы трека упростили задачу и нас ждала бинарная классификация определения больного или здорового дерева по фотографии листа. В первый вечер Антон предложил нам модель на Pytorch для классификации фотографий.

Для улучшения точности модели мы использовали ступенчатое уменьшение лернинг рейта в процессе обучения. Мы перепробовали 5-7 моделей пред обученных сетей и лучшую точность нам дал Efficient Net.

Изначально нам дали размеченный датасет состоящий из 2к картинок.Нам пришла идея расширить датасет. Путем добавления данных с Kaggle и других датасетов использованных в научных работах со схожей тематикой. Теоретически это должно было улучшить наш результат. Изначально это казалось идеальным решением,но при обсуждении с командой мы вспомнили что это может запросто ухудшить наш результат. Тестовый дататест был у организаторов. Нам дали только сабсет этих данных и он отражал генеральную совокупность всех данных. Был слишком большой риск что модель будет учиться на данных с другим распределением. Мы пробовали добавлять 5%,10%,15% новых данных, но это не давало нам прироста. Результат становился даже немного хуже. Поэтому мы решили больше работать с аугментацией данных.

Что касается веб интерфейса Никита и Илья писали его на React. Мы использовали модульный подход. Команде создающей веб мы дали дамп модели, что бы они не простаивали и создавали веб. Пока команда по машинному обучению тюнила модель, команда с вебом создавала веб, пусть и на бейзлайновом дампе модели. За несколько часов до финальной презентации, мы просто отдали финальную модель команде с вебом. Они просто заменили старую модель на последнюю версию.

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

Конкуренция была очень серьезная. На хакатон по нашему треку было отобрано 10 команд, которые показали отличные результаты. Вот списки команд - https://agro-code.ru/results/

При финальном оглашении результатов оказалось что две команды заняли первое место. К сожалению это был не мы. Наша команда заняла 3 место среди 9 команд дошедших до финала. Основным критерием была точность и 5 лидирующих команд выдали fscore 0.99 на тестовой выборке. Окончательный результат определили тысячные доли.

Всей нашей команде понравился этот хакатон интересной задачей и великолепной организацией. Продолжение следует

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru