Анизотропия времени

Двуликий Янус — очень странный бог из пантеона древнеримских богов. В отличии от собратьев по клубу, которые, обычно, олицетворяют некие идеалы человеческих качеств, он бог дверей и переходов. Спрашивается, для чего нужен отдельный бог для дверей? И как вообще можно додуматься до такого бога? А ведь Янус очень могущественный бог, настолько могущественный что в честь него назван первый месяц календаря и до сих пор его имя самое популярное среди населения планеты индоевропейского происхождения. Все вариации его имени Ян, Иван, Янис, Джон, Хуан, как и месяц Январь, дословно означают одно — Первый.

Большинство других богов — это вполне понятные боги: Венера — богиня любви, Юпитер — главный начальников начальник, Арес — бог войны и тп, и тд. Эти боги олицетворяют идеалы качеств людей и ведут себя как люди. Устраивают пиры и склоки, соблазняют чужих жен, сплетничают, в общем занимаются обычной жизнью селебрити, полной интриг и пиара. В отличие от всей этой богемы, Двуликий Ваня никогда не принимал участия в светской жизни, хотя и был главным богом. Он был первым кто сформировался из хаоса, создал время и нанизал на него все события, которые были есть и будут. Поэтому он знает все, что было, есть и будет. Двуликий, он так как смотрит и в прошлое, и в настоящее одновременно. В какой-то момент времени ему видимо надоело смотреть на своих глупых родственничков или он осознал, что придет время, и даже бессмертные боги исчезнут, поэтому он удалился на пенсию, уступив престол карьеристу Юпитеру. И стал богом дверей и переходов. Ведь каждый раз, открывая дверь, мы совершаем переход из прошлого в неизвестное будущее. И только Двуликий создатель времени знает, что нас ждет впереди.

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

Время — самый непонятный атрибут нашей реальности и самый важный, ведь все во вселенной, включая саму вселенную, эволюционирует во времени. И также самый абстрактный атрибут для нас. У нас есть органы чувств, позволяющие определять пространство, вещество и энергию. Но у нас нет ничего чтобы определять время. Мы способны, посмотрев на объект, определить расстояние до него; способны пощупать и понять, что перед нами материя, способны определить, насколько оно теплое и тяжелое, но определить время мы не можем никак. У нас нет никаких органов чувств, чтобы измерять время. Да, мы научились отсчитывать время с помощью часов, но определять его как то же пространство мы не умеем. Что согласитесь очень странно. Ещё более странно что невозможно представить как такой орган чувств или датчик должен работать (спойлер, у нас есть одна особенность, позволяющая осознавать время, но о ней попозже).

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

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

Во времена Ньютона время и пространство представлялись неизменными абсолютами. Пространство создавало сетку — основу вселенной, называемую вакуумом. Объекты перемещались в пространстве, а время текло равномерно и не зависело от точки наблюдения. Потом обнаружилось странное свойство реальности: скорость света оказалась неизменной. Что откровенно поставило всех в тупик. Но пришел Эйнштейн, задал парочку логичных вопросов и заявил, что время относительно и его течение зависит от того, где находится наблюдатель.

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

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

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

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

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

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

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

Если мы хотим остановить время или двинуться назад, нам нужно будет преодолеть инерцию всей вселенной и выпасть из нее. Ведь в прошлом ее уже нет. Очевидно, такое сложно реализуемо, но вполне возможно. Примером таких “выпадений” являются черные дыры. Своей массой они пробивают ткань физики нашей вселенной, ведь на ее горизонте событий время останавливается. (Это не призыв к безмерному обжорству).

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

Представьте вы идете по темной узкой улочке и навстречу вам человек, который говорит -не ходи туда, там яма. Вы идете дальше и видите яму. Кто этот человек? Ведь он знал ваше будущее, кстати, как и вы знаете его будущее, ведь вы прошли путь, по которому ему еще предстоит пройти. Он пришел в настоящее (точка, где вы встретились) из своего прошлого, которое ваше будущее и уйдет в свое будущее, которое ваше прошлое. А обменявшись в настоящем своими знаниями вы вдвоем стали аналогом двуликого, ведь вы знаете и прошлое, и будущее.  (Лучше с ним не здороваться за руку, скорее всего он состоит из антиматерии).

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

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

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

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

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

Наш мозг пластичен, но с возрастом пластичность падает.  Детский мозг открыт для любых новых знаний. Но чем сложнее задача, тем больше знаний нужно запихнуть в мозг, тем дольше это занимает. К тому времени как мы получаем нужный объем знаний, чтобы разобраться в стоящей перед нами задачей, мозг уже прошел этап пластичности и не способен генерировать новые идеи. Он структурирован выученными знаниями. Не зря Ландау говорил, что если к 33 годам ты не сделал открытия достойного Нобелевской премии, то потом уже ничего нового не придумаешь  30-35 лет — это примерно тот возраст, когда мозг уже полностью сформирован и структурирован и уже практически не способен воспринимать новые знания (особенно те которые противоречат усвоенным ранее).

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

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/737446/

MajorDom v1.0 — От голосового помощника к умному дому

Статья на английском / read in english

В 2019 году я впервые узнал про возможность распознавания и синтеза речи на языке python. Гугл ассистент, сири, кортана и другие ассистенты тогда были еще более ограниченными и беспомощными, чем сейчас. О добавлении своих команд речи не шло от слова совсем. Тогда я и загорелся идеей создать своего голосового помощника, который не будет уступать даже Джарвису Тони Старка.

В процессе работы над ядром, начал задумываться, где этого ассистента хостить. Держать ноут постоянно включенным не вариант, а других компьютеров у меня не было. На помощь пришли одноплатные компьютеры raspberry pi. Я хотел, чтобы мой голосовой ассистент мог включать и выключать свет, управлять светодиодной лентой и шторами. С такими задачами отлично справляется ардуино. Оставалось только найти способ передавать команды с распбери. Использовать wifi и bluetooth не хотел с самого начала. Нашел в интернете информацию про модули nrf24l01, попробовал, понравилось.

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

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

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

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

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

Я хотел получить возможность использовать свое мобильное приложение для доступа к ассистенту на расстоянии. Надо было всего лишь придумать способ отправить запрос на локальный джанго сервер, не находясь при этом в локальной сети. Я был готов открывать и пробрасывать порты на роутере, но провайдер не дал мне белый ip. Тогда я попробовал ngrok. В первое время работало хорошо, но в бесплатной версии сервер периодически падал и менял адрес. Вариант с впн-туннелем я отбросил почти сразу. Стоимость vps была равна стоимости подписки на ngrok, но реализация была в разы сложнее.

Тогда я вспомнил, что у меня есть бесплатный хостинг для php сайтов на beget и переизобрел Long Polling и очереди. Реализация была максимально простой: приложение отправляло запрос на хостинг. Там php код добавлял тело (json) запроса в конец массива и записывал в локальный файл. Малина дома каждую секунду отправляла запрос на чтение этого файла, после чтения массив чистился. Таким образом мне удалось отправлять команды домой из любой точки планеты страны! Аналогичным образом я сделал получение ответа от ассистента: продублировал реализацию и поменял роли. Два файла и четыре эндпоинта на бесплатном хостинге на пыхе дали мне стабильную двустороннюю связь с моим домашним помощником. Чуть позже научил ассистента самостоятельно отправлять мне сообщения, например, с номером аудитории следующей пары в начале каждой перемены. Не успел всем похвастаться в колледже, как кто-то стал спамить мне домой. Пришлось добавить авторизацию: логин и пароль задавались хардкодом в приложении, а на сервере была проверка в стиле.

if ($login == 'markparker' && $password == 'MyVeryStrongP@ssw0rd!') {};

Репозитории приложения были приватные, а сервер был вообще без репы (зачем репа на один файл до 100 строк?), так что такого уровня безопасности мне более чем хватало.

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

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

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/737724/

Управление интеграцией проекта: как наладить рабочие процессы

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

  1. Инициация. 

  2. Планирование.

  3. Выполнение.

  4. Контроль за выполнением. 

  5. Завершение. 

Эти 5 этапов называют еще группами процессов. Подробнее о них вы можете узнать в общей статье[ссылка удалена модератором]. Чтобы проект двигался дальше, на каждой стадии выполняются конкретные процессы. Но руководитель не придумывает их самостоятельно. Процессов всего 49, они зафиксированы в PMBoK и разделены на 10 областей знаний. 

Каждый этап состоит из процессов, которые принадлежат одной или нескольким областям знаний. Представим связь 3 терминов в виде таблицы:

Этап №1 

Этап №2 

Этап №3 

Область знаний № 1

Конкретный процесс 1.1.

Конкретный процесс 1.2.

Конкретный процесс 1.3.

Область знаний № 2

Конкретный процесс 2.1.

Конкретный процесс 2.2.

Конкретный процесс 2.3.

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

Что такое управление интеграцией 

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

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

Если руководитель уделит должное внимание этой области знаний и ее процессам, тогда остальные 9 областей будут действовать вместе и приносить пользу, а не наносить вред. Рассмотрим каждый процесс отдельно.

Разработка устава 

Устав проекта — это документ, в котором зафиксирована информация о проекте: концепция, масштаб, состав команды и обязанности ее членов. 

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

Устав проекта: 

  • показывает цель и результаты;

  • законно утверждает начало проекта;

  • определяет временные рамки;

  • выявляет риски и определяет способы их предотвратить;

  • распределяет ресурсы;

  • упрощает взаимодействие заинтересованных сторон. 

Этапы создания устава

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

  1. Сформировать представление о проекте . На этой стадии определяем цели и назначение. Важно сформулировать не менее 5 целей по системе SMART[ссылка удалена модератором] и указать их результаты. Пример: построить загородный дом для сдачи в аренду.

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

  3. Раздать обязанности . Создайте схему, в которой укажите ответственных по проекту, их роли и обязанности. Например, Дмитрий — электрик, ему нужно провести электричество по всему дому, установить розетки и подключить счетчик. 

  4. Выделить основных этапов и обозначение сроков . Например, закладка фундамента, возведение стен и кровельные работы.

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

  6. Рассчитать бюджет. Составьте список расходов: доставка материалов на участок, работа строителей, создание 3D модели дома. 

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

План управления проектом

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

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

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

  • наладить взаимодействие с заинтересованными сторонами;

  • скоординировать работу команды; 

  • решить потенциальные проблемы до начала проекта;

  • определить сроки;

  • подобрать правильные программы, техники, инструменты и команду.

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

Виды плана управления 

План управления состоит из базовых планов и рабочих

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

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

Руководство и управление исполнением 

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

  1. Подбор, подготовку членов команды и управление ими. Пример: для постройки дома выбираем строителей с опытом работы не менее 5 лет и хорошим портфолио. 

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

  3. Управление поставщиками. Команда проекта следит за регулярными поставками древесины и качеством материалов. 

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

Мониторинг и управление работами 

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

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

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

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

  3. Формирование прогнозов, чтобы обновить текущие сведения о затратах и расписании проекта. Проект дома затянется на один месяц и потребуется еще 70 000₽.

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

Осуществление общего управления изменениями

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

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

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

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

Завершение проекта

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

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

Вывод

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


ссылка на оригинал статьи https://habr.com/ru/articles/737726/

[Паттерны API] Частичные обновления. Деградация и предсказуемость

Это главы 24 и 25 моей книги «API». v2 будет содержать три новых раздела: «Паттерны API», «HTTP API и REST», «SDK и UI‑библиотеки». Раздел «Паттерны API» на этом завершён. Если эта работа была для вас полезна, пожалуйста, оцените книгу на GitHub, Amazon или GoodReads. English version on Substack.

Глава 24. Частичные обновления 

Описанный в предыдущей главе пример со списком операций, который может быть выполнен частично, естественным образом подводит нас к следующей проблеме дизайна API. Что, если изменение не является атомарной идемпотентной операцией (как изменение статуса заказа), а представляет собой низкоуровневую перезапись нескольких полей объекта? Рассмотрим следующий пример.

// Создаёт заказ из двух напитков POST /v1/orders/ X-Idempotency-Token: <токен> {   "delivery_address",   "items": [{     "recipe": "lungo"   }, {     "recipe": "latte",     "milk_type": "oat"   }] } → { "order_id" } 
// Частично перезаписывает заказ, // обновляет объём второго напитка PATCH /v1/orders/{id} {   "items": [     // `null` показывает, что     // параметры первого напитка     // менять не надо     null,      // список изменений свойств     // второго напитка     {"volume": "800ml"}   ] } → { /* изменения приняты */ } 

Эта сигнатура плоха сама по себе, поскольку её читабельность сомнительна. Что обозначает пустой первый элемент массива — это удаление элемента или указание на отсутствие изменений? Что произойдёт с полями, которые не указаны в операции обновления (delivery_address, milk_type) — они будут сброшены в значения по умолчанию или останутся неизменными?

Самое неприятное здесь — какой бы вариант вы ни выбрали, это только начало проблем. Допустим, мы договорились, что конструкция {"items":[null, {…}]} означает, что с первым элементом массива ничего не происходит, он не меняется. А как тогда всё-таки его удалить? Придумать ещё одно «зануляемое» значение специально для удаления? Аналогично, если значения неуказанных полей остаются без изменений — как сбросить их в значения по умолчанию?

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

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

  • повышенные размеры запросов и, как следствие, расход трафика;

  • необходимость вычислять, какие конкретно поля изменились — в частности для того, чтобы правильно сгенерировать сигналы (события) для подписчиков на изменения;

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

Во избежание перечисленных проблем разработчики, как правило, реализуют некоторое наивное решение:

  • клиент передаёт только те поля, которые изменились;

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

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

// Частично перезаписывает заказ: //   * сбрасывает адрес доставки //     в значение по умолчанию //   * не изменяет первый напиток //   * удаляет второй напиток PATCH /v1/orders/{id} {   // Специальное значение №1:   // обнулить поле   "delivery_address": null   "items": [     // Специальное значение №2:     // не выполнять никаких     // операций с объектом     {},      // Специальное значение №3:     // удалить объект     false   ] } 

Предполагается, что:

  • повышенного расхода трафика можно избежать, передавая только изменившиеся поля и заменяя пропускаемые элементы специальными значениями ({} в нашем случае);

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

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

Все эти соображения, однако, на поверку оказываются мнимыми:

  • причины увеличенного расхода трафика (слишком частый поллинг, отсутствие пагинации и/или ограничений на размеры полей) мы разбирали в главе «Описание конечных интерфейсов», и передача лишних полей к ним не относится (а если и относится, то это повод декомпозировать эндпойнт);

  • концепция передачи только изменившихся полей по факту перекладывает ответственность определения, какие поля изменились, на клиент;

    • это не только не снижает сложность имплементации этого кода, но и чревато его фрагментацией на несколько независимых клиентских реализаций;

    • существование клиентского алгоритма построения diff-ов не отменяет обязанность сервера уметь делать то же самое — поскольку клиентские разработчики могли ошибиться или просто полениться правильно вычислить изменившиеся поля;

  • наконец, подобная наивная концепция организации совместного доступа работает ровно до того момента, пока изменения транзитивны, т.е. результат не зависит от порядка выполнения операций (в нашим примере это уже не так — операции удаления первого элемента и редактирования первого элемента нетранзитивны);

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

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

// Создаёт заказ из двух напитков POST /v1/orders/ {   "parameters": {     "delivery_address"   },   "items": [{     "recipe": "lungo"   }, {     "recipe": "latte",     "milk_type": "oats"   }] } → {   "order_id",    "created_at",   "parameters": {     "delivery_address"   },   "items": [     { "item_id", "status"},      { "item_id", "status"}   ] } 
// Изменяет параметры, // относящиеся ко всему заказу PUT /v1/orders/{id}/parameters { "delivery_address" } → { "delivery_address" } 
// Частично перезаписывает заказ // обновляет объём одного напитка PUT /v1/orders/{id}/items/{item_id} {    // Все поля передаются, даже если   // изменилось только какое-то одно   "recipe", "volume", "milk_type"  } → { "recipe", "volume", "milk_type" } 
// Удаляет один из напитков в заказе DELETE /v1/orders/{id}/items/{item_id} 

Теперь для удаления volume достаточно не передавать его в PUT items/{item_id}. Кроме того, обратите внимание, что операции удаления одного напитка и модификации другого теперь стали транзитивными.

Этот подход также позволяет отделить неизменяемые и вычисляемые поля (created_at и status) от изменяемых, не создавая двусмысленных ситуаций (что произойдёт, если клиент попытается изменить created_at?).

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

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

Разрешение конфликтов совместного редактирования

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

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

  • иметь максимально возможную гранулярность (т.е. одна операция соответствует одному действию клиента);

  • реализовать политику разрешения конфликтов.

В нашем случае мы можем пойти, например, вот таким путём:

POST /v1/order/changes X-Idempotency-Token: <токен> {   // Какую ревизию ресурса   // видел пользователь, когда   // выполнял изменения   "known_revision",   "changes": [{     "type": "set",     "field": "delivery_address",     "value": <новое значение>   }, {     "type": "unset_item_field",     "item_id",     "field": "volume"   }],   … } 

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

NB: один из подходов к этой задаче — разработка такой номенклатуры операций над данными (например, CRDT), в которой любые действия транзитивны (т.е. конечное состояние системы не зависит от того, в каком порядке они были применены). Мы, однако, склонны считать такой подход применимым только к весьма ограниченным предметным областям — поскольку в реальной жизни нетранзитивные действия находятся почти всегда. Если один пользователь ввёл в документ новый текст, а другой пользователь удалил документ — никакого разумного (т.е. удовлетворительного с точки зрения обоих акторов) способа автоматического разрешения конфликта здесь нет, необходимо явно спросить пользователей, что бы они хотели сделать с возникшим конфликтом.

Глава 25. Деградация и предсказуемость 

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

Зададим себе, однако, вопрос: а что значит «более предсказуемая» система? Для нас как для вендора API это достаточно просто: процент ошибок (в разбивке по типам) достаточно стабилен, и им можно пользоваться как индикатором возникающих технических проблем (если он растёт) и как KPI для технических улучшений и рефакторингов (если он падает).

Но вот для разработчиков-партнёров понятие «предсказуемость поведения API» имеет совершенно другой смысл: насколько хорошо и полно они в своём коде могут покрыть различные сценарии использования API и потенциальные проблемы — или, иными словами, насколько явно из документации и номенклатуры методов и ошибок API становится ясно, какие типовые ошибки могут возникнуть и как с ними работать.

Чем, например, оптимистичное управление параллелизмом (см. главу «Стратегии синхронизации») лучше блокировок с точки зрения партнёра? Тем, что, получив ошибку несовпадения ревизий, разработчик понимает, какой код он должен написать: обновить состояние и попробовать ещё раз (в простейшем случае — показав новое состояние пользователю и предложив ему решить, что делать дальше). Если же разработчик пытается захватить lock и не может сделать этого в течение разумного времени, то… а что он может полезного сделать? Попробовать ещё раз — но результат ведь, скорее всего, не изменится. Показать пользователю… что? Бесконечный спиннер? Попросить пользователя принять какое решение — сдаться или ещё подождать?

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

Несколько общих советов, которые могут вам пригодиться:

  • если вы можете включить в саму ошибку рекомендации, как с ней бороться — сделайте это не раздумывая (имейте в виду, что таких рекомендаций должно быть две — для пользователя, который увидит ошибку в приложении, и для разработчика, который будет разбирать логи);

  • если ошибки в каком-то эндпойнте некритичны для основной функциональности интеграции, очень желательно описать этот факт в документации (потому что разработчик может просто не догадаться обернуть соответствующий вызов в try-catch), а лучше — привести примеры, каким значением/поведением по умолчанию следует воспользоваться в случае получения ошибки;

  • не забывайте, что, какую бы стройную и всеобъемлющую систему ошибок вы ни выстроили, почти в любой среде разработчик может получить ошибку транспортного уровня или таймаут выполнения, а, значит, оказаться в ситуации, когда восстанавливать состояние надо, а «подсказки» от бэкенда недоступны; должна существовать какая-то достаточно очевидная последовательность действий «по умолчанию» для восстановления работы интеграции из любой точки;

  • наконец, при введении новых ошибок не забывайте о старых клиентах, которые про эти новые типы ошибок не знают; «реакция по умолчанию» на неизвестные ошибки должна в том числе покрывать и эти новые сценарии.

В идеальном мире для «правильной деградации» клиентов желательно иметь мета-API, позволяющее определить статус доступности для эндпойнтов основного API — тогда партнёры смогут, например, автоматически включать fallback-и, если какая-то функциональность не работает. (В реальном мире, увы, если на уровне сервиса наблюдаются масштабные проблемы, то обычно и API статуса доступности оказывается ими затронуто.)


ссылка на оригинал статьи https://habr.com/ru/articles/737728/

Пишем первый ML-пайплайн на Airflow: подробный туториал

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

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

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

Данный пайплайн специально адаптирован под задачи машинного обучения. В этом примере мы будем загружать новости из открытого источника и использовать NLP-модель для их классификации (zero-shot classification).

План:

  1. Примеры применения Airflow в проектах с машинным обучением.

  2. Знакомство с Airflow: основные понятия и инструменты.

  3. Написание тасок для загрузки данных и получения предсказания модели.

  4. Запуск Airflow локально через Docker Compose.

  5. Знакомство с веб-интерфейсом Airflow.

Код доступен на GitHub.

Как Airflow используется в проектах с машинным обучением

Airflow хорошо подходит для задач, которые запускаются в указанное время или каждый заданный интервал времени.

Apache Airflow — популярный инструмент в проектах с машинным обучением. Он позволяет создавать эффективные и масштабируемые пайплайны, связанные с обработкой данных, обучением и развертыванием моделей.

Примеры задач, которые решает Airflow:

  • Регулярное переобучение моделей машинного обучения на новых данных.

  • Получение предсказаний модели в пакетном режиме. Например, раз в час или раз в день.

  • ETL пайплайны, которые загружают данные из разных источников и преобразуют их.

  • Автоматическая генерация отчетов.

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

  1. Feature Engineering: данные от пользователей накапливаются и обрабатываются единоразово в заданное время.

  2. Training Pipeline: на основе актуальных данных модель переобучается, например, раз в день.

  3. Batch Prediction Pipeline: последняя версия модели используется, чтобы рассчитать новые рекомендации по всем пользователям и сохранить в базу.

Архитектура рекомендательной системы на Airflow

Архитектура рекомендательной системы на Airflow

Шаг 3 в этом пайплайне используется получение предсказания модели в пакетном формате. Когда это имеет смысл?

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

Недостаток: предсказания имеют запаздывание. Например, рекомендации не будут учитывать real-time фидбек от пользователя.

Если для вашей задачи критична работа модели в real-time формате подойдут другие инструменты. В прошлом туториале мы разбирали, как написать ML веб-сервис на FastAPI, который работает с моделью в формате запрос-ответ.

Знакомство с Airflow: основные понятия и инструменты

Прежде чем начать практическую часть, познакомимся с основными понятиями Apache Airflow.

Airflow DAG - пример

Airflow DAG — пример
  1. DAG (Directed Acyclic Graph) — удобный способ организации и визуализации пайплайна. Это структура, где каждая задача представлена узлом, а зависимости между задачами представлены направленными стрелками, указывающими порядок выполнения задач.
    Граф является направленным, потому что задачи выполняются последовательно в определенном порядке, определяемом зависимостями.

  2. Task: «кирпичи», из которых состоит DAG. Задачи представляют собой конкретные шаги или операции, которые должны быть выполнены в вашем пайплайне.

  3. Operator: Каждая задача выполняется некоторым кодом или скриптом, который вы определяете. Операторы (Operators) представляют собой классы или функции, которые определяют, как будет выполнена каждая задача в пайплайне.
    Например, есть операторы, отвечающие за выполнение кода на питоне, bash-команд и SQL-запросов.

  4. Metadata Database: в этой базе хранятся метаданные о структуре пайплайнов, зависимостях между задачами, расписании выполнения и других параметрах.

  5. Webserver: удобный пользовательский интерфейс (UI), позволяющий запускать, мониторить и отлаживать пайплайны.
    Через веб-интерфейс можно просматривать статус выполнения задач, проверять логи, а также управлять пайплайнами, вносить изменения и контролировать их работу.

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

0. Описание задачи и проектирование пайплайна

Главная цель этого туториала — знакомство с Airflow и самостоятельный запуск пайплайна. То есть, по итогу туториала вы научитесь локально запускать Airflow.

Но, для использования Airflow в продакшене необходимы другие инструменты, например, Kubernetes.

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

Наш пайплайн

Наш пайплайн

Пайплайн будет состоять из 3 задач:

Task 1: Подготовка данных — загрузка данных, препроцессинг и сохранение в локальную директорию.

Task 2: Предсказание модели — загрузка модели, инференс модели на сохраненных данных.

Task 3: Подготовка отчета на основе предсказаний.

Task 1 и Task 2 мы хотим запустить в отдельных Docker-контейнерах. Для этого будем использовать DockerOperator.

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

Это дает стандартные преимущества контейнеризации: изоляция, гибкость и тд. Например, применительно к ML, код расчета фичей и код предсказания модели могли использовать разные версии scikit-learn.

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

Task 3 содержит простой код агрегации, поэтому будем использовать PythonOperator.

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

  • Для обмена данными между задачами мы использовали локальную директорию data. В реальных проектах часто используются специализированные хранилища данных — Amazon S3, DVC и другие.

  • В качестве источника данных выбран открытый источник — финансовые новости с cnbc.com. В реальных задачах это будут данные из внутренних баз данных или тот же S3.

  • Модель загружали из открытого реестра моделей — Hugging Face Hub. Также сознательно выбрали модель zero-shot classification, которая способна работать с заранее заданным списком классов, не требуя дополнительного дообучения. В реальных проектах это будет специально обученная модель, хранящаяся в реестре моделей, например, MLflow.

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

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

Структура проекта

Структура проекта
  • data: вспомогательная директория, будет использоваться для обмена данными между тасками и сохранения результатов.

  • ml_pipeline: содержит код для загрузки и подготовки данных data_loader и код предсказания модели model_prediction. Как уже обсуждалось ранее, каждая таска будет запускаться в отдельном Docker-контейнере, поэтому у обеих таск есть свой Dockerfile.

  • dags: в этой директории находятся файлы, описывающие DAG. Airflow будет использовать эти файлы для планирования и запуска задач в определенном порядке.

  • docker-compose.yml: определяет конфигурацию контейнеров, необходимых для запуска и работы Airflow.

1. Загрузка данных, используем Docker

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

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

import feedparser import pandas as pd  NEWS_FEED_URL = "https://www.cnbc.com/id/19746125/device/rss/rss.xml"   def data_load(data_path: str) -> None:     news_feed = feedparser.parse(NEWS_FEED_URL)     df = pd.DataFrame(news_feed.entries)     df.to_csv(data_path, sep="\t", index=False)

Помимо этого в финальную версию data_load.py добавим:

  • Использование Click и опции командной строки — для возможности гибко задавать параметры при запуске скрипта.

  • Логирование — для возможности отслеживать работу пайплайнов и обнаружения возможных проблем. Позже эти сообщения мы увидим в интерфейсе Airflow.

  • Дополнительную обработку данных.

Финальная версия data_load.py выглядит так:

import html import logging  import click import feedparser import pandas as pd  NEWS_FEED_URL = "https://www.cnbc.com/id/19746125/device/rss/rss.xml" COLUMNS_TO_SAVE = ["id", "published", "title", "summary"]  logging.basicConfig(level=logging.INFO)   @click.command() @click.option("--data_path", help="Path to the input data CSV file") def data_load(data_path: str) -> None:     logging.info("Fetching financial news from the RSS feed...")     news_feed = feedparser.parse(NEWS_FEED_URL)     logging.info("News fetched successfully.")      df = pd.DataFrame(news_feed.entries)[COLUMNS_TO_SAVE]     df["published"] = pd.to_datetime(df["published"])     df["title"] = df["title"].map(html.unescape)     df["summary"] = df["summary"].map(html.unescape)      logging.info(f"Saving the processed data to '{data_path}'...")     df.to_csv(data_path, sep="\t", index=False)     logging.info("Data saved successfully.")   if __name__ == "__main__":     data_load()

Для подготовки Docker-образа осталось создать:

  • список зависимостей — requirements.txt:

feedparser==6.0.10 click==8.1.3 pandas==2.0.1
  • Dockerfile:

FROM python:3.11  COPY requirements.txt data_load.py /workdir/ WORKDIR /workdir  RUN pip install -r requirements.txt

Таким образом, у нас готовы все файлы для создания Docker-образа. Мы соберем образ и создадим контейнер позже — в разделе про Docker Compose.

2. Предсказание модели, zero-shot classification

В этом примере будем использовать NLP-модель для задачи zero-shot classification.

Что такое zero-shot classification?

Zero-shot classification (классификация без обучения) — это метод машинного обучения, при котором модель классифицирует объекты на классы, которых не было в процессе обучения модели.

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

Для начала определим список классов — тем, на которые мы будем разделять финансовые новости:

LABELS = [     "Crypto",     "SEC",     "Dividend",     "Economics",     "Oil or Gas",     "IPO",     "Politics",     "Buffet",     "Stock",     "Other", ]

Для получения предсказаний загрузим модель valhalla/distilbart-mnli-12-1 из Hugging Face Hub. device=-1 означает, что модель запускается на CPU.

from transformers import pipeline  model_hf = pipeline(model="valhalla/distilbart-mnli-12-1", device=-1)

Далее загрузим csv файл с новостями, который мы подготовили в предыдущем пункте. Для предсказания будем использовать объединение текста из заголовка новости (title) и ее краткого описания (summary).

import pandas as pd  df = pd.read_csv(data_path, sep="\t") texts_for_pred = (df.title + ". " + df.summary).tolist()

Для получения предсказаний передадим модели список текстов texts_for_pred и классы LABELS:

pred = model_hf(texts_for_pred, LABELS, multi_label=False)

Флаг multi_label определяет, может ли объект быть отнесен к одному или более классам. Когда multi_label=True, модель может присваивать объектам несколько классов одновременно.

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

Благодаря библиотеки transformers код занял всего несколько строк.

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

df["label"] = [x["labels"][0] for x in pred] df.T.to_json(pred_path)

На этом код предсказания готов.

Добавим логирование и использование Click по аналогии с кодом загрузки данных. Тогда финальная версия model_predict.py будет выглядеть следующим образом:

import logging  import click import pandas as pd from transformers import pipeline  LABELS = [     "Crypto",     "SEC",     "Dividend",     "Economics",     "Oil or Gas",     "IPO",     "Politics",     "Buffet",     "Stock",     "Other", ]  logging.basicConfig(level=logging.INFO)   @click.command() @click.option("--data_path", help="Path to the input data CSV file") @click.option("--pred_path", help="Path to save the output JSON file") def model_predict(data_path: str, pred_path: str) -> None:     logging.info("Loading the model...")     model_hf = pipeline(model="valhalla/distilbart-mnli-12-1", device=-1)     logging.info("Model loaded successfully.")      logging.info(f"Reading data from '{data_path}'...")     df = pd.read_csv(data_path, sep="\t")     logging.info("Data read successfully.")          texts_for_pred = (df.title + ". " + df.summary).tolist()      logging.info("Performing model prediction...")     pred = model_hf(texts_for_pred, LABELS, multi_label=False)     logging.info("Prediction completed successfully.")      df["label"] = [x["labels"][0] for x in pred]      logging.info(f"Saving the predictions to '{pred_path}'...")     df.T.to_json(pred_path)     logging.info("Predictions saved successfully.")   if __name__ == "__main__":     model_predict() 

Код предсказания модели будет также запускаться в отдельном Docker-контейнере. Поэтому аналогично предыдущему пункту мы добавили свои requirements.txt и Dockerfile.

Итак, мы подготовили код для 2 компонент нашего пайплайна: загрузки данных и получения предсказания модели. Каждый будет выполняться в отдельном Docker-контейнере. Теперь все готово, чтобы мы перешли к написанию DAG на Airflow.

3. Пишем DAG

Вспомним основные компоненты нашего пайплайна:

  1. 3 последовательные таски, первые 2 из которых должны запускаться в отдельных Docker-контейнерах.

  2. Локальная директория data, которую мы будем использовать для сохранения результатов и обмена данными между тасками.

Разберем основные шаги при написании нашего DAG:

  • Локальную директорию data нужно примонтировать к /opt/airflow/data/ — это путь к данным внутри контейнера Airflow.

from docker.types import Mount  dockerops_kwargs = {     "mount_tmp_dir": False,     "mounts": [         Mount(             source="<path_to_your_airflow-ml_repo>/data",             target="/opt/airflow/data/",             type="bind",         )     ],     ... }
  • Определим пути для трех типов файлов: исходные данные, предсказания и файл с результатом. Они используют специальный синтаксис Airflow {{ ds }}, который будет заменен на дату выполнения при запуске DAG.

raw_data_path = "/opt/airflow/data/raw/data__{{ ds }}.csv" pred_data_path = "/opt/airflow/data/predict/labels__{{ ds }}.json" result_data_path = "/opt/airflow/data/predict/result__{{ ds }}.json"
  • Создадим DAG. Декоратор dag создает DAG с названием financial_news с начальной датой сегодня (days_ago(0)) и ежедневным запуском. Функция taskflow представляет собой сам DAG и содержит задачи, формирующие пайплайн.

from airflow.decorators import dag from airflow.utils.dates import days_ago  # Create DAG @dag("financial_news", start_date=days_ago(0), schedule="@daily", catchup=False) def taskflow():   ...
  • Создадим две таски для запуска в Docker-контейнерах. Для это будем использовать DockerOperator. Здесь мы указываем имя образа (этот образ будет описан в docker-compose.yml) и команду для запуска питоновского скрипта внутри контейнера.

    # Task 1     news_load = DockerOperator(         task_id="news_load",         container_name="task__news_load",         image="data-loader:latest",         command=f"python data_load.py --data_path {raw_data_path}",         **dockerops_kwargs,     )      # Task 2     news_label = DockerOperator(         task_id="news_label",         container_name="task__news_label",         image="model-prediction:latest",         command=f"python model_predict.py --data_path {raw_data_path} --pred_path {pred_data_path}",         **dockerops_kwargs,     )
  • Создадим последнюю таску, она преобразует полученные предсказания питоновским кодом. Мы будем использовать PythonOperator, который выполнит несложный скрипт группировки предсказаний.

    # Task 3     news_by_topic = PythonOperator(         task_id="news_by_topic",         python_callable=aggregate_predictions,         op_kwargs={             "pred_data_path": pred_data_path,             "result_data_path": result_data_path,         },     )
  • Установим зависимости между задачами. В нашем случае они выполняются последовательно:

news_load >> news_label >> news_by_topic
  • Наконец, создадим и настроим объект DAG в соответствии с заданными параметрами:

taskflow()

Файл с описанием DAG имеет расширение .py и лежит в директории dags. При запуске Airflow, он сканирует эту директорию (или другую настроенную директорию) в поисках файлов с определением DAG. Когда Airflow обнаруживает файл с определением DAG, он регистрирует его и делает доступным для выполнения по расписанию.

Мы закончили писать наш пайплайн, теперь перейдем к настройке и запуску Airflow.

4. Запуск Airflow с помощью Docker Compose

Для локального запуска Airflow мы будем использовать Docker Compose. Он помогает запустить Apache Airflow с минимальными усилиями, предоставляя унифицированный и изолированный способ запуска всех компонентов Airflow.

У Airflow есть отличная инструкция по запуску с помощью Docker Compose. Там же есть загрузка готового файла docker-compose.yml. Инструкция позволяет запустить Airflow в пару строк.

Файл docker-compose.yml имеет раздел services, где определены различные сервисы, которые являются частями кластера Airflow. Каждый сервис имеет свою секцию с настройками, где указывается образ Docker, команда для запуска сервиса, порты, зависимости от других сервисов и другие параметры. В частности здесь описаны сервисы для Metadata Database, Webserver и Scheduler, которые мы упомянали в разделе Знакомство с Airflow.

Модификация docker-compose.yml для запуска тасок в отдельных Docker-контейнерах

Поскольку мы усложнили настройку Airflow, когда решили запускать таски в отдельных контейнерах, будем использовать свой модифицированный docker-compose.yml. Его можно посмотреть тут.

Основные моменты, которые мы изменили для поддержки запуска тасок в Docker-контейнерах:

  • Установили пакет для поддержки работы с Docker:

_PIP_ADDITIONAL_REQUIREMENTS: apache-airflow-providers-docker==3.6.0
  • В список volumes добавили монтирование директории с данными и сокета Docker:

  volumes:     - ${AIRFLOW_PROJ_DIR:-.}/data/:/opt/airflow/data/     - /var/run/docker.sock:/var/run/docker.sock
  • Ранее в директориях ml_pipeline/data_loader и ml_pipeline/model_prediction мы написали инструкции по созданию Docker-образов, которые импользуются для запуска тасок с помощью DockerOperator. Здесь мы также определяем эти сервисы:

  data-loader:     build:       context: ml_pipeline/data_loader     image: data-loader     restart: "no"    model-prediction:     build:       context: ml_pipeline/model_prediction     image: model-prediction     restart: "no"
  • Добавим сервис docker-socket-proxy:

  # Required because of DockerOperator. For secure access and handling permissions.   docker-socket-proxy:     image: tecnativa/docker-socket-proxy:0.1.1     environment:       CONTAINERS: 1       IMAGES: 1       AUTH: 1       POST: 1     privileged: true     volumes:       - /var/run/docker.sock:/var/run/docker.sock:ro     restart: always

Запуск Airflow

Возможно, в вашем случае запуск потребует выделения больше памяти для Docker Engine.

Перед первым запуском Airflow нужно подготовить окружение.

Если вы работаете на Linux перед запуском нужно указать AIRFLOW_UID:

echo -e "AIRFLOW_UID=$(id -u)" > .env

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

docker compose up airflow-init

Созданная учетная запись имеет логин airflow и пароль airflow.

Запуск и остановка Airflow

Для создания и запуска всех необходимых контейнеров, определенных в файле docker-compose.yml, используется команда:

docker compose up

В нашем случае также необходимо предварительно собрать образы data-loader и model-prediction, которые также указаны в файле docker-compose.yml. Поэтому модифицируем команду:

docker compose up --build

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

docker compose down --volumes --rmi all

После запуска Airflow продолжим работу в веб-интерфейсе.

5. Веб-интерфейс Airflow, запуск пайплайна

Пользовательский интерфейс Airflow упрощает мониторинг и запуск пайплайнов. Он доступен по адресу http://localhost:8080. На странице входа нужно ввести логин и пароль от учетной записи, в нашем случае airflow и airflow.

Страница авторизации

Страница авторизации

Запуск и мониторинг пайплайна

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

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

Графическое представление нашего пайплайна

Графическое представление нашего пайплайна

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

Логи таски 1

Логи таски 1

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

После того, как выполнение пайплайна успешно завершилось, посмотрим на результаты.

Смотрим результат пайплайна. Как классифицировались новости?

Результаты выполнения пайплайна сохранились в data/predict/result__<date>.json. Изначально мы ставили задачу написать пайплайн, которые будет автоматически загружать актуальные новости из финансового мира и группировать их по заданным нами темам. Посмотрим, что у нас получилось.

Результат работы пайплайна

Результат работы пайплайна

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

Заключение

  • Мы подробно рассмотрели, как создать с нуля и запустить локально свой первый ML-пайплайн на Airflow.

  • В этом примере мы написали таски для загрузки новостей из открытого источника и их классификации NLP-моделью (zero-shot classification). Каждая таска выполняется в отдельном Docker-контейнере.

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


Если формат туториалов по инструментам MLOps окажется полезным, буду планировать темы для следующих статей. А пока подписывайтесь на мой телеграм-канал. Там будут анонсы новых статей, а также советы для работы и более короткие мысли по DS/ML/AI.


ссылка на оригинал статьи https://habr.com/ru/articles/737046/