Автоматизация юридических процессов. Робот-судья: за и против

LegalTech это наше все. 

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

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

Но сейчас будет не самореклама. А немного самокритики. 

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

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

LegalTech все чаще привлекает крупных инвесторов. Например, ВТБ Капитал в 2020 году приобрел долю в 33% в Европейской юридической службе, одном из крупнейших поставщиков дистанционных юридических услуг. Аналитики оценивают эту сделку в 3-4 млрд рублей. 2020 год стал рекордным по суммам вложений в компании, разрабатывающие LegalTech-решения — более $ 2 млрд по всему миру.   

Если развивать эту идею дальше, то мы упираемся в вопрос: сможет ли искусственный интеллект применяться для автоматизации и оптимизации процессов судопроизводства? Стоит ли ожидать в ближайшее время автономного робота-судью?

В начале было слово

Указ Президента РФ от 10.10.2019 г. №490 «О развитии искусственного интеллекта в РФ» утверждает Национальную стратегию развития ИИ на период до 2030 года, в которой закреплен ряд основных понятий. Приоритетные направления развития и использования технологий ИИ определены в пункте 20 этой стратегии, и система судопроизводства в число этих направлений не входит.

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

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

Однако, исходя из понимания искусственного человека как моделируемой (искусственно воспроизводимой) интеллектуальной деятельности мышления человека (п. 3.17 ГОСТ Р 43.0.5-2009), можно предположить, что в судопроизводстве он будет внедряться в несколько этапов, включающих краткосрочную перспективу, когда программируемые алгоритмы работают ассистентом судьи-человека по ряду вопросов делопроизводства и рассмотрении дела по существу. Затем среднесрочная перспектива: искусственный интеллект становится компаньоном судьи человека, в том числе и по вопросам оценки доказательств. Долгосрочная перспектива подразумевает замену судьи-человека искусственным интеллектом по отдельным функциям.

Возможно ли это? 

Немного «за» 

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

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

Именно дела такой категории автоматизировались в качестве пилота в судах Белгородской области. Один мобильный оператор создал ПО, которое, анализируя входящие от Федеральной налоговой службы заявления о выдаче судебных приказов, готовил проекты судебных актов на основании заложенных алгоритмов. Приложение установили на трех мировых участках в области. Авторы считают, что проект показал свою эффективность — время, затрачиваемое на подготовку судебного акта, уменьшилось на 84%, а время на заполнение карточки дела на 96%. 

Затем этот же оператор представил пилотную версию юридического чат-бота LegalApe 2.8, который показал высокий уровень готовности даже к юридическому спору. Конечно, он проиграл живому юристу — на ПМЮФ-2018 состоялся показательный «батл» между человеком и ботом (впечатления юриста о споре с алгоритмом можно почитать здесь). Он строился по принципам арбитражного спора и длился полтора часа. В результате судьи решили, что человек в своих ответах точнее раскрывал суть дела, а робот зациклился на формализмах. Кроме того, последний допускал фактические ошибки и высказывания, которые привели бы к предупреждениям со стороны судьи в ходе реального разбирательства.   

Заместитель председателя Арбитражного суда Московской области Андрей Соловьев, заинтересовавшийся экспериментом в Белгородской области, в ходе панели на ПМЮФ-18 высказал предположение, что после массового внедрения подобных технологий некоторые споры смогут уйти из компетенции судов. Речь именно про те дела, где неважно судебное усмотрение. 

Существуют и другие примеры. Среди прочих — юридический бот на онлайн-платформе Pravoved.ru. Он создан на базе глубокой нейронной сети (DNN), которую обучали на практике по сотням тысячам реальных обращений. Робот-консультант дает консультации от имени Федора Нейронова из Санкт-Петербурга.

Если обобщить восторг сторонников концепции автоматизации судопроизводства, то можно сказать, что их энтузиазм строится на примерах зарубежного опыта. Это, прежде всего, примеры американской судебной системы (машины?), где алгоритмы принимают досудебные решения в отношении обвиняемых: об оставлении под арестом или безопасном освобождении до даты суда, в том числе под залог (например, алгоритмы recidivism models). Примером являются также Франция, Сингапур, Китай и другие страны, где ИИ используется в основном как вспомогательный инструмент для анализа судебных документов.

Много «против»

Впрочем, эксперты, выступающие против подобной автоматизации приводят примеры из того же зарубежного опыта, которые показывают, что перспективы для этого феномена не такие уж и радужные. Элина Сидоренко, генеральный директор платформы «ЗаБизнес.рф», выступая на ПМЮФ-22 в июле этого года отмечала, что даже в такой технологично подкованной стране, как Китай, в цифровых судах встает вопрос верификации собеседника на другом конце видеосвязи. Не одно решение было отменено в силу сомнения в том, что по видео связи выступает лицо, которое можно идентифицировать как человека, а не дипфейк. 

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

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

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

Эксперты напоминают, что процессуальное законодательство РФ (п.1 ст.17 УПК РФ,п.1 ст.67 ГПК РФ, п.1 ст.71 АПК РФ) требует от судьи при оценке доказательств руководствоваться своими внутренними убеждениями. А они представляют собой более сложные процессы, чем программные алгоритмы. В зависимости от конкретных обстоятельств дела одни и те же доказательства могут быть как отвергнуты, так и приняты судом. 

Проблемы кода или людей?

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

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

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

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

Первая волна оптимизма, связанная с новостями 2016 года о том, что ИИ научился предсказывать решения Европейского суда по правам человека с точностью в 79% (под руководством программистов Университетского колледжа Лондона), повлекла за собой кредит доверия общественности к роботам-судьям. Однако дальнейшие исследования, например, 2019 года, показали, что 79% точности в этих 79% завышены, поскольку авторы исходной работы использовали будущие решения судей для предсказания прошлых. 

Это представляет собой классический пример «утечка данных» (data leakage) из будущего в прошлое при машинном обучении, которая приводит к излишне оптимистичным результатам. При исправлении ошибки точность предсказания оказалась 58–68%. Более того, модель, которая предсказывала решение по делу, используя только фамилии судей, добилась точности в 65% — и это еще без использования модели обстоятельств дела: робот-судья принимал решения, опираясь на личность судей, поведение которых он повторяет. 

А был ли мальчик?

Элина Сидоренко на упомянутом ПМЮФ-22, высказала мысль, которую забывают многие сторонники автоматизации судопроизводства в розовых очках (и вообще внедрения ИИ где только можно): то, что мы называем сегодня искусственным интеллектом, на самом деле является даже не зачатком «интеллекта». Это просто алгоритмы (пусть и хорошие, качественные) работы с большими данными. Не более.  

Один из разработчиков Сири, Люк Джулия, в своей книге «Нет такой вещи, как искусственный интеллект» так перефразирует Декарта: «ИИ не мыслит, следовательно, он не существует». По его словам, люди неправильно представляют, что такое искусственный интеллект. Даже автоматизация процессов — тех, что люди десятилетиями выполняли вручную, сегодня переводя их в коды, чтобы их могли обрабатывать «машины» — это мечта для многих компаний. Есть история в Scientific American, которая рассказывает, как журналист издания участвовал в запуске стартапа States Title, ставивший своей целью автоматизацию страховой индустрии. Он обнаружил в недрах компании чуть ли не армию сотрудников, которые вручную вводили данные.  

Что можно доверить младенцу?

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

Это, например: 

— распознавание и перевод на русский язык в читаемый цифровой формат документов; 

— ведение цифрового протоколирования хода судебных заседаний; 

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

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

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

Далее можно будет доверить участие ИИ в правовой оценке доказательств:

— определение категории и юридических свойств сделки (форма, дата, подлинность электронной подписи); 

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

— определение пропуска срока исковой давности и срока на обращение в суд; 

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

— вычисление «глубинных подделок» с использованием ИИ (deepfake) и иных фальсификаций.

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

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

Дарим скидку 4000 рублей при первом обращении на любую услугу onlinepatent.ru

Промокод: LOVEHABR


ссылка на оригинал статьи https://habr.com/ru/company/onlinepatent/blog/704564/

Домашняя приточная вентиляция малыми средствами

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

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

Итак, в трёх тезисах:

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

  • с минимумом ручного труда в изготовлении и монтаже, со 100-% использованием готовых компонентов (в силу вышеупомянутой лени идея выпиливания фанерных ящиков мне не слишком близка);

  • с общим ценником существенно ниже 50 тысяч рублей (идея траты пары сотен тысяч рублей мне также не очень близка).

В процессе экспериментов с разными вентиляторами: Blauberg Iso-Mix (установлен) против Blauberg Iso-B (на подоконнике). Последний победил с разгромным счётом.
В процессе экспериментов с разными вентиляторами: Blauberg Iso-Mix (установлен) против Blauberg Iso-B (на подоконнике). Последний победил с разгромным счётом.

И важное. Если вы — адепт систем рекуперации, долгих инженерных расчётов и полугода проектирования, эта статья не для вас. Я — адепт золотой середины между «я сделяль» и «я задолбался».

Но зачем?

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

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

Как показали два года с «приточкой», избавление от шума и чёрной пыли — не единственные плюсы:

  • появляется возможность удобной регулировки баланса свежего воздуха и температуры в помещении — более того, выяснилось, что зимой весьма комфортно жить при 21-22 °С и < 700 ppm CO2;

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

  • в квартире пропала пыльца, крупная пыль, семена растений, а также комары и мухи — несмотря на не самый низкий этаж, всё это залетало в окна;

  • пропали неприятные холодные сквозняки из приоткрытых окон зимой (а мой рабочий стол стоит прямо у окна — и у выходного диффузора приточной вентиляции).

Какие есть варианты?

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

  • бризеры: недорого, просто в установке, но — занимает место на стене, находится внутри помещения, а значит, весь механический шум вентилятора достаётся нам. Особенно с учётом, что в силу размеров бризеров вентиляторы там достаточно скромные по размерам, а уровень шума сравним с кондиционером или превышает таковой. Жить можно, но зачем, если есть место снаружи? Неудовлетворённость.

  • готовые системы приточной вентиляции: ценники за сам наружный блок начинаются от 100 тыс. рублей, и это ещё надо поискать. Если не искать, то 200+. Жаба.

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

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

Из чего состоит приточная вентиляция в минимальной конфигурации?

  • входной раструб, прикрытый решёткой от крупного мусора;

  • противопылевой фильтр (желающие могут рассмотреть HEPA-фильтры, но я решил, что мне необходимо и достаточно хорошего противопылевого);

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

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

  • трубы разводки воздуха по жилым помещениям;

  • диффузоры в жилых помещениях.

Начнём с самого сложного.

Вентилятор

Если вы пойдёте в гугл и поищете «вентилятор канальный малошумный», то среди мощных моделей — после отбрасывания игрушек, предназначенных для вентиляции санузлов — вы обнаружите, с высокой вероятностью, Soller&Palau TD-160/100 N Silent, с чуть меньшей — Blauberg Iso-Mix 100, ну и далее по убыванию — аналогичные им модели других производителей.

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

Именно с Blauberg Iso-Mix я и начал эксперименты. И он с задачей вентиляции двухкомнатной квартиры справиться не смог.

Осевой шумоизолированный вентилятор
Осевой шумоизолированный вентилятор

Если посмотреть на базовые паспортные характеристики вентилятора, то с ними всё хорошо: воздушный поток 175-233 м³ при шуме 24-29 дБА. Ну красота же, нам же надо максимум кубов 60-80 в час, на двушку-то.

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

  • шумность измеряется на расстоянии 3 м от корпуса вентилятора в направлении, перпендикулярном его оси;

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

Как только разница давлений начинает возрастать — а в неё вносят свой вклад фильтр, трубы, диффузоры и собственно вытяжная вентиляция вашей квартиры, производительность вентилятора начинает падать:

При дельте в 100 Па вместо 175-233 м³ мы получаем уже всего лишь 75-90 м³.

Как показал эксперимент, производительности Iso-Mix 100 в принципе хватало, но — на максимальной скорости. На которой внутри квартиры становится слышен шум двигателя.

Но, позвольте, спросите вы, ведь всего 29 дБА — через метровую кирпичную стену и двойные стеклопакеты?

Как я уже отмечал, 29 дБА — что действительно очень немного — это уровень, измеренный в 3 м от вентилятора в направлении, перпендикулярном его оси. Открываем подробный Spec sheet:

Опаньки. В направлении вдоль оси шум на выходе из вентилятора (строчка «LwA to outlet») составляет уже 58 дБА — и именно он передаётся по трубам внутрь квартиры. Более того, при наружном расположении вентилятора по сути только он нас и должен интересовать: никакого другого шума мы через стену и не услышим.

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

Классический пример центробежного вентилятора — так называемая «улитка» (например, BVN BDRS 160-60, но это просто первый пункт в Яндекс.Маркете, а так-то тысячи их), принцип работы которой очевиден уже по первому фото. Как правило, в таком формате делают промышленные вентиляторы — очень мощные и очень шумные. Иногда — дешёвые, но очевидно, что если я расстроился от Iso-Mix, то «улитка» меня вряд ли устроит. Нет, экспериментов с ЛАТРом и подбором скоростей я тоже не хочу.

Центробежный вентилятор
Центробежный вентилятор

Чуть более сложный и чуть более бытовой пример — Blauberg Centro 100. Он внешне похож на короткий и толстый осевой вентилятор, но не дайте себя обмануть — внутри у него центробежная конструкция, просто корпус заворачивает поток воздуха ещё на девяносто градусов.

Но увы. Дёшево, практично и шумно. Хотя при тех же 100 Па разницы давлений Centro 100 V2 даёт около 225 м³, уровень его шума вдоль оси на выходе из вентилятора — 82 дБА. То, что будет прилетать нам в квартиру на максимальной его скорости. Можно, конечно, поставить эксперимент, кто будет шумнее при заданном расходе воздуха в реальной квартире — Centro V2 (он у меня даже есть, но купленный для другого) или Iso-Mix, но в тот момент новых экспериментов мне не хотелось, а сейчас я Iso-Mix и вовсе подарил коллеге.

Хотелось бы золотую середину: чтобы шум как у Iso-Mix, а статическое давление (и, соответственно, практический расход воздуха) как у Centro.

Центробежный шумоизолированный вентилятор
Центробежный шумоизолированный вентилятор

Встречайте: Blauberg Iso-B. Шумоизолированная «улитка». Центробежный вентилятор, убранный в стальной прямоугольный корпус, отделанный изнутри шумкой.

68 дБА вдоль оси вентилятора на его выходе — на 10 дБА больше, чем у осевого Iso-Mix, но и существенно меньше, чем у воющего Centro. 40 дБА в сторону от вентилятора — не бесшумно, конечно, но ведь он у нас всё равно за окном, а там… ну для сравнения, у наружного блока кондиционера типовые 40-50 дБА, так что мы никого не удивим и не огорчим.

Расход воздуха при разнице давлений вход-выход 100 Па — чуть выше 200 м³, более чем вдвое выше, чем у Iso-Mix на максимальной скорости.

Так может, в практических условиях, где Iso-Mix справлялся, но именно что на максимуме, Iso-B окажется тише, потому что будет работать на меньшей скорости?

Реальность даже превзошла ожидания. Iso-B оказался тише не только на крейсерской, но и на максимальной скорости. Iso-Mix издавал лёгкое, но хорошо слышимое жужжание — а у Iso-B его нет.

Итак, вопрос с вентилятором решился.

В заключение отмечу ещё несколько моментов тезисно:

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

  • Iso-B не только лучше, чем Iso-Mix, но и хуже — он более громоздкий, это тоже надо учитывать;

  • в данный момент я не вижу в продаже именно Iso-B, а из конструктивно схожих моделей конкретно Blauberg наблюдаю Box — он не шумоизолированный, но вдоль оси даёт 66 дБА шума, а расход при 100 Па составляет 200 м³, то есть в этом плане практически неотличим от Iso-B. Из плюсов — он заметно компаткнее, чем Iso-B. Существенно отличается только шум в окружающую среду, но в принципе вы можете сами попробовать оклеить его корпус шумкой, если 51 дБА будут вас напрягать. Или поискать конкурентов — на Blauberg я ссылаюсь исключительно потому, что сам имел дело с ними; есть, например, в продаже российские Навека VS-100, есть и другие модели.

  • себе я взял в итоге Blauberg Iso-B 125, то есть с диаметром канала 125 мм. Почему, не помню — может, только он в тот момент был в наличии; каналы дальше у меня идут 100-мм, но об этом мы поговорим ниже.

Регулировка скорости вентилятора

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

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

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

  • установка минимальной мощности — чтобы не задумываться на тему «дальше этого деления не крутить, двигатель остановится».

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

Фильтр

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

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

Замечу, что фильтр создаёт довольно серьёзное сопротивление воздуху — особенно когда в него набьётся пыли. Поэтому, если место позволяет, можно поставить фильтр покрупнее, например, FSL 160 или FSL 200, с переходником на 100 или 125 мм вашего вентилятора.

Второй связанный с фильтром вопрос — где его ставить, до вентилятора или после?

  • до вентилятора — не придётся чистить от пыли и прилетевшего мусора сам вентилятор, всё это останется на фильтре;

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

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

Ах да, про классы фильтров.

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

Впрочем, вот, например, карманный фильтр класса G4.
Впрочем, вот, например, карманный фильтр класса G4.

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

Глушитель

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

Ставится после вентилятора. Бывает длиной 600 мм и 1000 мм, выбирайте по имеющемуся месту. Диаметр выбирается в соответствии с используемыми воздуховодами.

Подогрев воздуха

При необходимости осуществляется канальным нагревателем — по сути, металлической трубой с ТЭНом внутри. Выпускаются на разную мощность и размер, вот, например, Shuft EHC 100-0,3/1 на воздуховоды 100 мм и мощность всего 300 Вт, а вот EHC 100-0,6/1 на 600 Вт.

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

Потребную мощность нагревателя можно прикинуть по формуле P = 0,36×V×T, где V — кубометры в час, T — дельта температуры. Для подогрева воздуха на 10 °С при расходе 100 м³/ч, соответственно, надо 360 Вт — то есть 600-ваттный нагреватель в принципе решит проблему с морозным воздухом зимой, превращая его в достаточно комфортный (впрочем, ценой сильного увеличения вашего счёта за электричество).

Для нагревателя потребуется отдельный контроллер температуры, а также необходимо будет следить за тем, чтобы нагреватель не оказался включён при выключенном вентиляторе. Бюджетный контроллер стоит более 6 тыс. рублей и позволяет поддерживать заданную температуру в помещении — что может быть неудобно с учётом наличия центрального отопления. В принципе, можно попробовать, например, добавить к такому контроллеру внешний датчик, расположить его в воздуховоде и поставить контроллер на 0 °C, чтобы он работал только зимой и лишь доводил воздух до температуры, при которой не чувствуется сквозняк.

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

Воздуховоды

Как ни странно, самая сложная часть вопроса — потому как являются единственной частью системы (ну, кроме разве что ещё диммера), находящейся внутри квартиры. Соответственно, желание сделать красивую сеть воздуховодов по всем помещениям будет бороться с нежеланием делать капитальный ремонт квартиры.

У меня оно, впрочем, проиграло не только нежеланию, но и невозможности: для прокладки воздуховодов по потолку нужно хотя бы миллиметров 70 высоты, а когда у тебя вся высота потолков — 2640, и это до ремонта с нормальным выравниванием пола, особенно не разугляешься, и так уже на голову давит. Кроме того, сеть воздуховодов предполагает распределительное устройство и расчёт сопротивления и получающегося воздушного потока по каждой из веток.

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

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

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

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

В качестве труб, как можно заметить, бюджетный ПВХ, размером 100 мм круглая и 110×55 мм прямоугольная.

Ввод в комнату и диффузоры

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

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

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

На рисунке приточного клапана Aereco EHT видно канал с акустической проставкой AEA 968.
На рисунке приточного клапана Aereco EHT видно канал с акустической проставкой AEA 968.

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

У меня это две модели. Первый — диффузор от приточного клапана Aereco EHT 780, довольно дорогое изделие с гигрорегулировкой (в моём случае ненужной; теоретически есть модель без неё, но практически ни у кого в продаже её не было; впрочем, гигрорегулировка отключается).

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

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

Второй диффузор, в маленькой комнате — Soler&Palau BDO-100, аккуратный внешне и имеющий четыре шторки, позволяющие регулировать (до полного перекрытия) поток из него на все четыре стороны. Он менее комфортен, даёт больший уровень шума при той же скорости потока, а также худшее распределение потока по комнате, так что ставить его в большую комнату, на которую приходится основной расход воздуха, я бы, пожалуй, не стал — но в моём случае требующийся расход отличается раза в два, так что в маленькой комнате он к месту.

Важный момент: BDO-100 для работы с холодным забортным воздухом требуется доработать! Необходимо снять с него декоративную крышку и наклеить на её внутреннюю поверхность слой теплоизоляции толщиной 4-5 мм. Без этого зимой крышка будет охлаждаться так сильно, что на ней с внешней стороны, не обдуваемой потоком, будет конденсироваться — и стекать вниз — влага.

Для EHT 780 такая доработка не требуется, у него уже есть теплошумоизоляция.

Разумеется, в моменте стоит посмотреть, какие диффузоры сейчас есть на рынке. Например, у Aereco появился новый симпатичный клапан серии EHT2. Единственное — ещё раз подчеркну, что дешёвые всенаправленные диффузоры я не советую.

Итого

Во-первых, прикинем общую стоимость.

Вентилятор Blauberg Iso-B 125

20 тыс. руб.

Фильтр FSL-100

2 тыс. руб.

Шумоглушитель 100/600 мм

3 тыс. руб.

Диффузор Aereco EHT 780

5 тыс. руб.

Диффузор Soler&Palau BDO-100

1,5 тыс. руб.

Диммер

2 тыс. руб.

Прочее

около 3 тыс. руб.

Итого: 36,5 тыс. рублей с дорогим вентилятором Blauberg и дорогим диффузором Aereco, не считая стоимости работ. Бризер Tion Lite стоит около 25 тысяч рублей — то есть на две комнаты он мне обошёлся бы уже существенно дороже, при худших, с моей точки зрения, пользовательских характеристиках.

И, конечно, если бы я собирал систему сегодня, я бы взял планшет и сделал рисёч на предмет вентиляторов, подобрав аналогичную Iso-B или Box по производительности модель у других производителей. В Blauberg в текущей международной обстановке расстраивает как доступность, так и цена. Как минимум, стоит посмотреть на Навека VS 100 (около 12 тыс. руб.).

Стоит ли делать систему дороже? Да, конечно, если у вас есть деньги и, главное, желание их потратить — можно купить готовый модуль приточной вентиляции, можно делать полноценную систему воздухопроводов под потолком, можно делать модуль рекуперации. Но надо понимать, что следующая ступенька — готовый модуль приточной вентиляции без рекуперации — обойдётся вам в общей сложности в 80-100 тыс. рублей по минимальной планке.

Во-вторых, ну и как оно мне?

Если коротко: отлично.

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

Проблем с эксплуатацией не возникло. Фильтры меняю примерно раз в квартал, покупая раз в год большой кусок фильтроткани. Зимой при забортной температуре -20 °С в квартире — 22-23 °С (при CO2 порядка 800 ppm), без сквозняков вблизи окон стало значительно комфортнее. В воздуховодах чисто, что при наличии фильтра на входе, вообще говоря, неудивительно. На идею автоматической регулировки потока я забил, так как пока что она выглядит более сложной, нежели иногда повернуть диммер — да и тогда надо уж делать комплексную систему регулировки климата, в которую будут включены и батареи отопления.


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

Отечественная образовательная робототехника. Часть 2: Переклеивание шильдиков или самобытные решения?

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

Сразу хотим оговорить параметры, по которым мы отнесли те или иные решения к отечественным и образовательным. Они должны быть разработаны и изготовлены (понятно, что не на 100%, но хотя бы в части компонентов) в России, у них должна быть в наличии методическая база и поддержка для образовательных учреждений, и их создатели сами позиционируют свои решения как предназначенные для образовательных учреждений. Из-за этого за бортом остались множество «самоделок», иностранных наборов, в том числе DIY из Китая. Кроме того, чтобы не быть заподозренными в рекламе конкретных решений, мы не даем ссылок на них. Также огромное спасибо тем, кто «накидал» нам ссылок на этих производителей в социальных сетях.

ЛАРТ

У компании из города Пушкино Московской области в портфолио помимо заказной разработки электроники есть линейка учебных роботов: от балансирующего робота, до руки-манипулятора. Роботы собираются на акриловых или металлических основаниях (собственной разработки и производства) с использованием «обычных» стоек для крепления печатных плат. В них применяется управляющий контроллер собственной разработки (ЛАРТ R5), в который устанавливается Arduino Nano и подключаются Arduino-совместимые датчики (часть из них, такие как датчики линии или платы со светодиодами) и моторы с сервоприводами.

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

Эвольвектор

Компания из Наро-Фоминска позиционирует себя как производитель решений как для образовательных учреждений, так и для персонального использования. Похоже, что у Эвольвектор есть отдельной серия «для самодельщиков» под маркой Geegrow, так какплаты с этой  маркировкой лежат в основе продающихся наборов. У компании есть наборы как для изучения микроэлектроники на основе Arduino и RaspberryPi, так и робототехнические наборы (от недорогих до больших, предназначенных для оборудования классов учебных заведений) и наборы для «Умного дома».

Платы и контроллеры наборов позиционируются как собственная разработка, а сами шасси роботов собираются из металлических деталей (нам они напомнили конструктор Makeblock). Есть разные вариации контроллеров и наборов, все из них снабжены хорошей методической поддержкой как в бумажном, так и в видео формате. Есть своя среда программирования (очень похоже, что она сделана на основе Blockly). Но, так как в основе лежит Arduino Leonardo, то можно программировать наборы на всем поддерживающем Arduino.

ПРОКУБИКИ

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

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

ОМЕГАБОТ

Компания из Санкт-Петербурга, позиционирующая себя как создатель робототехнических наборов исключительно для образовательных учреждений. Авторы у себя на сайте утверждают, что большая часть компонентов и сами наборы изготавливаются и собираются в России, наборы имеют Lego Technic совместимое шасси, плату расширения Omega и собраны на основе микроконтроллеров Arduino и Raspberry Pi. Программировать предлагается в «визуальной среде, С++ и Python». Судя по фотографиям, используется визуальная среда на переработанном Blockly.

Для роботов доступен типичный набор датчиков (линии, касания, освещенности), светодиоды и динамик, Bluetooth, манипулятор и камера для машинного зрения.

ЙоТИК

Следующим в очереди находится не совсем робот, а микроконтроллер для него. Встречайте  ЙоТик 32A и 32B от компании МГБот из Санкт-Петербурга. Микроконтроллер собран на основе ESP32 двухъядерного процессора Tensilica Xtensa LX6, несет на борту поддержку WiFi и Bluetooth, встроенные датчики Холла и  температуры, а по форм-фактору совместим с датчиками и платами расширения Arduino. Различия между A и B версиями заключаются в наличие в последней разъема для MicroSD карт и ИК-приемника.

На основе данного микроконтроллера компания предлагает пару робототехнических решений (например Динамика ЙоТик М1), но больше специализируется на обучающих наборах по «Умному Дому/Теплице» и IoT в целом.

Программировать микроконтроллер предлагается в Arduino IDE и Trik Studio (точнее в ее модификации IoTik Studio).

МАРСОХОД

Нам предложили рассмотреть еще проект МАРСОХОД, но похоже, что он в этом году испытывает проблемы из-за санкций. МАРСОХОД это отечественная попытка создать (причем в формате открытого аппаратного проекта) плату для разработки различных электронных устройств и роботов на основе ПЛИС. В последней версии (МАРСОХОД 3) применялась ПЛИС Altera MAX10, а разработку нужно было вести VERILOG. Опять же, так как проект открытый, и у него есть определенное (и живое) сообщество, можно надеяться на его возрождение.

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


Мы приглашаем детей от 5 до 14 лет к нам в RoboUniver на бесплатное пробное занятие. На каждом занятии мы собираем нового робота, изучаем теорию по механике, закрепляем школьные темы. И, конечно, оживляем нашего робота с помощью программирования. По промокоду HABR2022, вы можете также получить скидку в 2000 рублей при покупке абонемента на занятия!


ссылка на оригинал статьи https://habr.com/ru/company/robouniver/blog/704584/

Введение в библиотеку Transformers и платформу Hugging Face

Исходники: https://github.com/huggingface/transformers
Документация: https://huggingface.co/docs/transformers/main/en/index

Платформа Hugging Face это коллекция готовых современных предварительно обученных Deep Learning моделей. А библиотека Transformers предоставляет инструменты и интерфейсы для их простой загрузки и использования. Это позволяет вам экономить время и ресурсы, необходимые для обучения моделей с нуля.

Модели решают весьма разнообразный спектр задач:

  • NLP: classification, NER, question answering, language modeling, summarization, translation, multiple choice, text generation.

  • CV: classification, object detection,segmentation.

  • Audio: classification, automatic speech recognition.

  • Multimodal: table question answering, optical character recognition, information extraction from scanned documents, video classification, visual question answering.

  • Reinforcement Learning

  • Time Series

Одна и та же задача может решаться различными архитектурами и их список впечатляет — более 150 на текущий момент. Из наиболее известных: Vision Transformer (ViT), T5, ResNet, BERT, GPT2. На этих архитектурах обучены более 60 000 моделей.

Модели Transformers поддерживают три фреймворках: PyTorch, TensorFlow и JAX. Для На PyTorch’а доступны почти все архитектуры. А вот для остальных надо смотреть совместимость: https://huggingface.co/docs/transformers/main/en/index#supported-frameworks
Также модели можно экспортировать в форматы ONNX и TorchScript.

Transformers не является набором модулей, из которых составляется нейронная сеть, как например PyTorch. Вместо это Transformers предоставляет несколько высокоуровневых абстракций, которые позволяют работать с моделями в несколько строк кода.

Начнем с установки…

Установка

Нам понадобится сами трансформеры:

pip install transformers

И т.к. мы будем писать на торче, то:

pip install torch

Еще нам понадобится библиотека evaluate, которая предоставляет различные ML метрики:

pip install evaluate

Поиск моделей

Прежде чем приступать к коду нам нужно формализовать нашу задачу до одного из общепринятых классов и найти подходящую для нее модель на хабе Hugging Face: https://huggingface.co/models

Слева вы можете увидеть ряд фильтров:

  • Класс задачи

  • Поддерживаемый фреймворк глубокого обучения

  • На каком датасете происходило обучение

  • Язык, на котором училась модель (если это NLP задача).

Также вы сможете поискать модель по названию — часто название модели содержит ее предназначение или архитектуру. Например, NLP-модели для классификации токсичности текста могут содержать “toxic” в названии. А берто-подобные архитектуры содержат слово “bert”.

На ваш поиск может вывалится множество моделей, поскольку для одной и той же задачи имеется множество предобученных моделей на разных архитектурах. И вам нужно выбрать подходящую. Что значит “подходящую”? Тут на вкус и цвет фломастеры разные: кому-то важнее точность, кому-то универсальность, а кому-то размер модели — выбирайте 🙂

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

  • Более подробное описание модели

  • Кол-во классов, которые предсказывает модель

  • Примеры кода

  • Бенчмарки

  • И возможность поэкспериментировать

Более подробно про различные классы задач и как они решаются можете почитать здесь: https://huggingface.co/tasks

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

Использование моделей

Для доступа к моделям есть два способа:

  • Прямое использование моделей на исходном фреймворке — больше кода, но и больше гибкости.

  • Класс Pipeline — самый простой способ воспользоваться моделями из transformers. С него и начнем.

Pipeline

Использование любой модели включает в себя минимум два шага: соответствующим образом подготовить данные и непосредственное использование модели. Класс Pipeline объединяет в себе эти два шага.

Посмотрим как его задействовать на примере задачи классификации (токсичности) текста:

from transformers import pipeline  clf = pipeline(     task = 'sentiment-analysis',      model = 'SkolkovoInstitute/russian_toxicity_classifier')  text = ['У нас в есть убунты и текникал превью.',     'Как минимум два малолетних дегенерата в треде, мда.']  clf(text)  #вывод [{'label': 'neutral', 'score': 0.9872767329216003},  {'label': 'toxic', 'score': 0.985331654548645}]

Здесь мы:

  • В конструкторе pipeline указали задачу, которую хотим решить, а также название конкретной модели из хаба Hugging Face (https://huggingface.co/models).

  • Задали набор документов, в которых нужно найти токсичный текст.

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

С полным списком наименований задач, которые поддерживаются Pipeline вы можете ознакомится здесь: https://huggingface.co/docs/transformers/main/en/main_classes/pipelines

При первом обращении к какой-либо модели произойдет ее загрузка. При повторном обращении к этой модели загрузка будет производится из кэша.

Что тут можно улучшить:

  • Помимо конкретной модели в pipeline можно передать tokenizer. Токенайзер используется в NLP задачах и отвечает за предварительную обработку текста и конвертирует их в массив чисел, которые затем поступают на вход модели (об этом подробнее ниже). 

    Обычно для модели используется точно такой же tokenizer, который использовался при обучении (только так можно гарантировать корректность ее работы). Но если по каким-либо причинам вам потребовался другой, то его можно задать примерно так:

pipeline(     task = 'question-answering',      model = 'distilbert-base-cased-distilled-squad',      tokenizer = 'bert-base-cased')
  • По умолчанию классификатор возвращает наиболее вероятный класс, но вы можете вернуть и все значения:

clf(text, top_k=None)  #вывод [[{'label': 'neutral', 'score': 0.9872767329216003},   {'label': 'toxic', 'score': 0.012723307125270367}],  [{'label': 'neutral', 'score': 0.01466838177293539},   {'label': 'toxic', 'score': 0.985331654548645}]]
  • Если все данные, которые нужно обработать, не влазят в память, то можно задействовать генератор, который будет поштучно загружать данные в память и подавать их в модель:

from transformers import pipeline  clf = pipeline(     task = 'sentiment-analysis',      model = 'SkolkovoInstitute/russian_toxicity_classifier')  text = ['У нас в есть убунты и текникал превью.',         'Как минимум два малолетних дегенерата в треде, мда.']  def data(text):     for row in text:         yield row  for out in clf(data(text)):     print(out)  #вывод {'label': 'neutral', 'score': 0.9872767329216003} {'label': 'toxic', 'score': 0.985331654548645}

PyTorch

А теперь посмотрим как использовать модель на нативном Торче. Будем классифицировать котиков 🙂

import torch import requests from PIL import Image from io import BytesIO from transformers import AutoImageProcessor, AutoModelForImageClassification  response = requests.get(     'https://github.com/laxmimerit/dog-cat-full-dataset/blob/master/data/train/cats/cat.10055.jpg?raw=true') img = Image.open(BytesIO(response.content))  img_proc = AutoImageProcessor.from_pretrained(     'google/vit-base-patch16-224') model = AutoModelForImageClassification.from_pretrained(     'google/vit-base-patch16-224')  inputs = img_proc(img, return_tensors='pt')  with torch.no_grad():     logits = model(**inputs).logits  predicted_id = logits.argmax(-1).item() predicted_label = model.config.id2label[predicted_id] print(predicted_id, '-', predicted_label)  #вывод 281 - tabby, tabby cat

Тут мы:

  • Импортируем два AutoClass’а: AutoImageProcessor и AutoModelForImageClassification. 
    AutoClass (начинается с Auto) это специальный класс, который автоматически извлекает архитектуру предварительно обученной модели по ее имени или пути.

  • Загружаем картинку по URL.

  • Загружаем ImageProcessor. Это аналог токенайзера, но только для картинок — выравнивает размеры картинок, нормализует и т.д. (об ImageProcessor чуть подробнее ниже).

  • Загружаем модель. Сама модель представляет собой PyTorch nn.Module, который вы можете использовать как обычно при работе с торчом.

  • Обрабатываем картинку посредством ImageProcessor. ImageProcessor возвращает словарь, который подаем на вход модели с оператором распаковки (**).

  • Все модели Transformers возвращают логиты, которые идут перед последней функцией активации (например, softmax). Соответственно, нам самим необходимо их обработать, чтобы получить на выходе вероятность или класс.

Автоматическое определение архитектуры

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

Чтобы каждый раз не заморачиваться с определением точного названия класса в Transformers завезли так называемый AutoClass. AutoClass позволяет автоматически считывать всю метаинформацию (архитектуру и пр.) из предварительно обученной модели при ее загрузке:

img_proc = AutoImageProcessor.from_pretrained(     'google/vit-base-patch16-224') model = AutoModelForImageClassification.from_pretrained(     'google/vit-base-patch16-224')  tokenizer = AutoTokenizer.from_pretrained(     'SkolkovoInstitute/russian_toxicity_classifier') model = AutoModelForSequenceClassification.from_pretrained(     'SkolkovoInstitute/russian_toxicity_classifier')

Каждый автокласс привязан к определенной задаче. С полным списком автоклассов можете ознакомится здесь: https://huggingface.co/docs/transformers/main/en/model_doc/auto

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

Дообучение

Не часто вам потребуются модели как есть. В соревнованиях, а тем более в работе вам скорее всего придется дообучить модель на своем датасете. И тут вас есть несколько вариантов…

Trainer

Самый простой способ — воспользоваться классом Trainer. Это аналог Pipline’а. Только он предназначен для организации упрощенного процесса обучения.

import datasets import evaluate import pandas as pd import numpy as np from datasets import Dataset from sklearn.model_selection import train_test_split from transformers import (AutoTokenizer, AutoModelForSequenceClassification,                            TrainingArguments, Trainer)  # Загружаем данные df = pd.read_csv('toxic.csv') df.columns = ['text','label'] df['label'] = df['label'].astype(int)  # Конвертируем датафрейм в Dataset train, test = train_test_split(df, test_size=0.3) train = Dataset.from_pandas(train) test = Dataset.from_pandas(test)  # Выполняем предобработку текста tokenizer = AutoTokenizer.from_pretrained(     'SkolkovoInstitute/russian_toxicity_classifier')  def tokenize_function(examples): return tokenizer(examples['text'], padding='max_length', truncation=True)  tokenized_train = train.map(tokenize_function) tokenized_test = test.map(tokenize_function)  # Загружаем предобученную модель model = AutoModelForSequenceClassification.from_pretrained( 'SkolkovoInstitute/russian_toxicity_classifier', num_labels=2)  # Задаем параметры обучения training_args = TrainingArguments( output_dir = 'test_trainer_log', evaluation_strategy = 'epoch', per_device_train_batch_size = 6, per_device_eval_batch_size = 6, num_train_epochs = 5, report_to='none')  # Определяем как считать метрику metric = evaluate.load('f1') def compute_metrics(eval_pred): logits, labels = eval_pred predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels)  # Выполняем обучение trainer = Trainer( model = model, args = training_args, train_dataset = tokenized_train, eval_dataset = tokenized_test, compute_metrics = compute_metrics)  trainer.train()  # Сохраняем модель save_directory = './pt_save_pretrained' #tokenizer.save_pretrained(save_directory) model.save_pretrained(save_directory) #alternatively save the trainer #trainer.save_model('CustomModels/CustomHamSpam')

Что мы тут делаем:

  • Загружаем данные в пандас.

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

  • Применяем к каждой строке датасета (тексту) токенизатор, чтобы перевести его в массив чисел.

  • Подгружаем предварительно обученную модель.

  • Определяем экземпляр класса TrainingArguments. В нем задаются гиперпараметры, которые будут использоваться при обучении модели, а также специальные флаги, которые активируют различные варианты обучения.
    Т.к. класс универсальный и предназначен для обучения разных архитектур и задач, то параметров у него довольно много. Подробнее ознакомится с ними можете здесь: https://huggingface.co/docs/transformers/main_classes/trainer#transformers.TrainingArguments

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

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

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

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

    Список доступных метрик: https://huggingface.co/evaluate-metric
    тут же вы можете с ними поэкспериментировать.

  • Создаем объект Trainer и подгружаем в него все ранее определенные компоненты: модель, аргументы, датасеты, функцию оценки. И запускаем обучение.

  • После обучения сохраняем результат на диск.

В последующем мы можем загрузить нашу модель так:

model = AutoModelForSequenceClassification.from_pretrained( './pt_save_pretrained')

Trainer поддерживает поиск гиперпараметров посредством специализированных пакетов: optuna, sigopt, raytune и wandb. Более подрбно: https://huggingface.co/docs/transformers/hpo_train

PyTorch

Теперь задействуем классический алгоритм обучения на торче:

import torch import evaluate import pandas as pd from tqdm.auto import tqdm from datasets import Dataset from torch.optim import AdamW from torch.utils.data import DataLoader from sklearn.model_selection import train_test_split from transformers import (AutoTokenizer,                            AutoModelForSequenceClassification, get_scheduler)  # Загружаем данные df = pd.read_csv('toxic.csv') df.columns = ['text','label'] df['label'] = df['label'].astype(int)  # Конвертируем датафрейм в Dataset train, test = train_test_split(df, test_size=0.2) train = Dataset.from_pandas(train) test = Dataset.from_pandas(test)  # Выполняем предобработку текста tokenizer = AutoTokenizer.from_pretrained( 'SkolkovoInstitute/russian_toxicity_classifier')  def tokenize_function(examples): return tokenizer(examples['text'], padding='max_length', truncation=True)  def ds_preproc(ds): ds = ds.map(tokenize_function) ds = ds.remove_columns(['text', 'index_level_0']) ds = ds.rename_column('label', 'labels') ds.set_format('torch') return ds  tokenized_train = ds_preproc(train) tokenized_test = ds_preproc(test)  # Создаем даталоадер train_dataloader = DataLoader(tokenized_train, shuffle=True, batch_size=8) test_dataloader = DataLoader(tokenized_test, batch_size=8)  # Загружаем модель и указываем кол-во классов model = AutoModelForSequenceClassification.from_pretrained( 'SkolkovoInstitute/russian_toxicity_classifier', num_labels=2)  # Задаем оптимайзер и шедулер optimizer = AdamW(model.parameters(), lr=5e-6)  num_epochs = 5 num_training_steps = num_epochs * len(train_dataloader)  lr_scheduler = get_scheduler( name = 'linear', optimizer = optimizer, num_warmup_steps = 0, num_training_steps = num_training_steps)  device = 'cuda' model.to(device)  # Выполняем цикл... for epoch in tqdm(range(num_epochs)):  #... обучения model.train() for batch in tqdm(train_dataloader, leave=False):     batch = {k: v.to(device) for k, v in batch.items()}     outputs = model(**batch)     loss = outputs.loss     loss.backward()     optimizer.step()     lr_scheduler.step()     optimizer.zero_grad()  #... оценки metric = evaluate.load('f1')  model.eval() for batch in tqdm(test_dataloader, leave=False):     batch = {k: v.to(device) for k, v in batch.items()}     with torch.no_grad():         outputs = model(**batch)     logits = outputs.logits     predictions = torch.argmax(logits, dim=-1)      metric.add_batch(predictions=predictions, references=batch['labels'])  print(f'epoch {epoch} -', metric.compute())  # Сохраняем модель save_directory = './pt_save_pretrained' model.save_pretrained(save_directory)

Этот пайплайн не сильно отличается от других процессов обучения нейронных сетей:

  • Загружаем датасет в пандас, разбиваем на трейн/тест.

  • Конвертируем датафрейм в класс Dataset, который принимает на вход модель.

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

  • Формируем DataLoader для тренировочных и тестовых данных, чтобы мы могли 

  • Определяем оптимизатор и шедулер.

  • Загружаем модель и указываем кол-во классов, которые должна выучить модель.

  • Задаем оптимизатор и планировщик. Причем оптимизатор родной для торча, а планировщик из библиотеки трансформеров.

  • Итерируемся по эпохам. На каждой выполняем цикл обучения и цикл оценки.

  • Сохраняем модель на диске.

Эмбединги

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

Вытащить эмбединги можно двумя способами.

  1. Первый — ручной:

import torch import pandas as pd from transformers import AutoTokenizer, AutoModel  #Mean Pooling - Take attention mask into account for correct averaging def mean_pooling(model_output, attention_mask): token_embeddings = model_output[0] #First element of model_output contains all token embeddings input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, 1) sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9) return sum_embeddings / sum_mask  #Sentences we want sentence embeddings for sentences = ['This framework generates embeddings for each input sentence',              'Sentences are passed as a list of string.',              'The quick brown fox jumps over the lazy dog.']  #Load AutoModel from huggingface model repository tokenizer = AutoTokenizer.from_pretrained( 'sentence-transformers/all-MiniLM-L6-v2') model = AutoModel.from_pretrained( 'sentence-transformers/all-MiniLM-L6-v2')  #Tokenize sentences encoded_input = tokenizer( sentences,  padding=True,  truncation=True,  max_length=128,  return_tensors='pt')  #Compute token embeddings with torch.no_grad(): model_output = model(encoded_input)  #Perform pooling. In this case, mean pooling sentence_embeddings = mean_pooling( model_output, encoded_input['attention_mask'])  df = pd.DataFrame(sentence_embeddings).astype('float')
  1. Альтернативно можно задействовать отдельную библиотеку —  SentenceTransformers (ставится через пип):

from sentence_transformers import SentenceTransformer  model = SentenceTransformer('SkolkovoInstitute/russian_toxicity_classifier')  text = ['У нас в есть убунты и текникал превью.',     'Как минимум два малолетних дегенерата в треде, мда.']  embeddings = model.encode(text)  df = pd.DataFrame(embeddings)

Как видите, SentenceTransformers может подгружать нужные модели с хаба Hugging Face.

Предварительная обработка

Чуть поподробнее разберем в чем заключается предварительная обработка текста и картинок…

Токенайзер

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

По сути каждый токенайзер состоит из набора правил и глобально эти правила решают две задачи:

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

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

Сначала посмотрим как токенайзер разбивает предложение на токены:

from transformers import AutoTokenizer  tokenizer = AutoTokenizer.from_pretrained( 'SkolkovoInstitute/russian_toxicity_classifier')  tokenizer.tokenize('У нас в есть убунты и текникал превью.')  #вывод ['У','нас','в','есть','убу','##нты','и','тек','##ника','##л','превью','.']

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

Это один из видов токенизации. Всего в трансформерах используются три основных вида токенизации: Byte-Pair Encoding (BPE), WordPiece, SentencePiece.

Теперь посмотрим, что подается на вход модели:

text = 'У нас в есть убунты и текникал превью.' encoding = tokenizer(text) print(encoding)  #вывод 'input_ids': [101, 486, 1159, 340, 999, 63692, 10285, 322, 3100, 1352, 343, 85379, 132, 102] 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Токенайзер возвращает словарь, содержащий:

  • input_ids — массив чисел, каждое из которых соответствует одному токену.

  • attention_mask — указывает модели, какие токены следует учитывать, а какие игнорировать.

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

Данный словарь подается на вход модели с оператором распаковки (**).

Теперь посмотрим как токенайзер выравнивает длину предложений. За это отвечают три параметра:

  • padding — тензоры подаваемые в модель должны иметь одинаковую длину. Если этот параметр = True, то коротки последовательности дополняются служебными токенами до длины самой длинной последовательности.

  • truncation — очень длинные последовательности тоже плохо. Если параметр = True, то все последовательности усекаются до максимальной длины.

  • max_length — указываем до скольки токенов усекать последовательность.

text = ['У нас в есть убунты',     'Как минимум два малолетних дегенерата в треде, мда.']  encoding = tokenizer( text, padding=True, truncation=True, max_length=512)  print(encoding)  #вывод {'input_ids': [ [101, 486, 1159, 340, 999, 63692, 10285, 102, 0, 0, 0, 0, 0],  [101, 1235, 3932, 1617, 53502, 97527, 303, 340, 39685, 128, 48557, 132, 102]], 'token_type_ids': [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],  'attention_mask': [ [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}

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

По каким правилам происходит усечение и дополнение токенов читайте здесь: https://huggingface.co/docs/transformers/main/en/pad_truncation

ImageProcessor

ImageProcessor отвечает за подготовку данных CV задач. Его работа несколько проще. По сути он переводит все пиксели в числа и при необходимости выравнивает изображения до одинаковой длины/ширины.

from transformers import AutoImageProcessor from PIL import Image from io import BytesIO import requests  response = requests.get( 'https://github.com/laxmimerit/dog-cat-full-dataset/blob/master/data/train/cats/cat.10055.jpg?raw=true') img = Image.open(BytesIO(response.content))  image_processor = AutoImageProcessor.from_pretrained( 'google/vit-base-patch16-224')  inputs = image_processor(img, return_tensors='pt')  #вывод {'pixel_values': tensor([[[[ 0.4275,  0.4275,  0.4196,  ...,  0.0902,  0.1216,  0.0667],           [ 0.4431,  0.4353,  0.4118,  ...,  0.0902,  0.0588,  0.0118],           [ 0.4431,  0.4353,  0.4039,  ...,  0.1686,  0.1059,  0.0431],           ...,           [-0.1373, -0.0745, -0.0431,  ...,  0.2941,  0.2863,  0.2627],           [-0.1529, -0.1137, -0.0588,  ...,  0.2784,  0.2627,  0.2627],           [-0.1529, -0.1294, -0.0745,  ...,  0.2706,  0.2392,  0.2392]],          [[ 0.4275,  0.4431,  0.4588,  ...,  0.0275,  0.0667,  0.0588],           [ 0.4431,  0.4510,  0.4510,  ...,  0.0275,  0.0039,  0.0039],           [ 0.4431,  0.4431,  0.4431,  ...,  0.1059,  0.0510,  0.0275],           ...,            [-0.2392, -0.1765, -0.1451,  ...,  0.1922,  0.1922,  0.1765],           [-0.2549, -0.2157, -0.1608,  ...,  0.1765,  0.1765,  0.1922],           [-0.2549, -0.2314, -0.1765,  ...,  0.1686,  0.1529,  0.1765]],          [[ 0.4431,  0.4510,  0.4275,  ..., -0.0902, -0.0824, -0.0980],           [ 0.4588,  0.4588,  0.4275,  ..., -0.0980, -0.1451, -0.1529],           [ 0.4588,  0.4510,  0.4118,  ..., -0.0196, -0.0980, -0.1294],           ...,           [-0.3647, -0.3020, -0.2706,  ...,  0.0667,  0.0667,  0.0510],           [-0.3804, -0.3490, -0.2941,  ...,  0.0431,  0.0353,  0.0431],           [-0.3882, -0.3647, -0.3098,  ...,  0.0353,  0.0118,  0.0275]]]])}

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

Прочее

Шаринг

Если вы обучили/дообучили хорошую модель, то можете поделиться ею с сообществом. Подробная инструкция как это сделать: https://huggingface.co/docs/transformers/model_sharing

Также вы можете создать и загрузить в хаб Hugging Face свой кастомный Pipeline: https://huggingface.co/docs/transformers/main/en/add_new_pipeline#how-to-create-a-custom-pipeline

Датасеты

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

https://huggingface.co/datasets

Загружать примерно так:

from datasets import load_dataset  dataset = load_dataset('rotten_tomatoes')

Более подробно изучить функционал датасетов сможете здесь: https://huggingface.co/docs/datasets/index

Что дальше?

А дальше изучаем и воспроизводим кучу готовых примеров:

Отдельно стоит отметить целый курс, посвященный трансформерам: https://huggingface.co/course/chapter1/1

Код из статьи: https://github.com/slivka83/article/blob/main/transformers/Transformers.ipynb

Мой телеграм-канал


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

NestJS для разрастающейся разработки: зачем так сложно и почему всё-таки да

Привет, Хабр. Меня зовут Денис Былинин, я архитектор в компании Сравни. 

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

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

С чего всё начиналось

Несколько лет назад, когда наша команда разработки бэкенда мобильного приложения была совсем небольшой, мы решили с нуля пересобрать наш Node.js-стек. Взяли Babel и все вкусные фишки ESNext, поставили поверх Koa и Mongoose, сделали красивую документацию в JSDoc и склеили всё это набором кастомных библиотек. Так родился наш небольшой boilerplate для новых сервисов мобильной команды. На тот момент такая схема покрывала наши основные потребности, и мы спокойно занимались своим делом.

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

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

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

В итоге выбор пал на фреймворк NestJS на базе Node.js. Сразу скажу, что мы не искали «бескомпромиссно лучший со всех сторон фреймворк». Нам нужен был инструмент, который помог бы решить наметившиеся проблемы и одновременно дал бы достаточно свободы для развития. 

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

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

  • профиль пользователя (10 микросервисов + 4 сервиса интеграций)

  • управление продуктовыми сценариями (5 микросервисов)

  • мобильные виджеты (2 микросервиса)

  • последние действия пользователя

  • проведение экспериментов

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

Структура и boilerplate

Одна из главных особенностей Nest, с которой сталкиваешься даже при развертывании стандартного приложения через nest new my-app —  в нем очень много шаблонного кода. 

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

Постепенно с этим удается свыкнуться. Подобная шаблонизация помогает хорошо ориентироваться даже в незнакомом коде. А с помощью CLI или расширений на VSCode вы сможете быстро генерировать новые модули и сервисы. 

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

Метамагия и темная сторона Nest

Вся магия фреймворка построена на записи метаданных (роутинги, OpenAPI, инъекции), а также на извлечении данных о типах. 

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

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

Для кастомизации определенного поведения Nest приготовьтесь часто использовать reflect-metadata и всякие интересные поля типа «design:type», «design:paramtypes», «design:returntype», а также искать константы, которые Nest использует для записи своей служебной meta-информации.

Dependency Injection

Nest предлагает прекрасный инструментарий для управления созданием сущностей для DI. Есть несколько видов инъекций: по конструктору и по токену. В качестве токена можно взять любую строку или символ, пометить им соответствующее поле класса или параметр конструктора, для инъекции по конструктору токен будет сгенерирован из имени класса «под капотом» Nest. Также вы можете управлять процессом создания элементов модуля с помощью подмен, фабрик и даже просто подменять токен или конструктор каким-то объектом. 

Но этот же инструментарий часто служит причиной критики NestJS. 

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

 

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

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

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

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

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

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

Ошибки DI

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

Сделаем, например, циклическую зависимость, которую webpack нам соберет без проблем:

test.module.ts

test.service.ts

second.service.ts

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

[Nest] 20992  — 01.12.2022, 07:47:40   ERROR [ExceptionHandler] Nest can’t resolve dependencies of the TestService (?). Please make sure that the argument dependency at index [0] is available in the TestModule context.

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

ERROR [ExceptionHandler] Nest can’t resolve dependencies of the TestService (?). Please make sure that the argument SecondService at index [0] is available in the TestModule context.

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

Transport agnostic framework

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

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

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

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

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

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

К примеру, код из документации, который прекрасно работает для RabbitMQ, не сработает для Kafka. Потому что, в конечном итоге, код server-kafka попытается подписаться на consumer.subscribe(Object.assign({ topic: &lsquo;{&quot;cmd&quot;:&quot;sum&quot;}&rsquo; }, consumerSubscribeOptions)), где значение поля topic формируется этой замечательной функцией, a фигурные скобки не допустимы в имени топика. Конечно, можно придумать свой универсальный способ преобразования подходящий для Kafka, но придется создать свой собственный транспорт, наследуя его от server-kafka.ts.

Где не стоит использовать Nest

Во-первых, Nest скорее всего «не зайдет» для небольших проектов, где не нужно разнообразие транспортов, healthcheck-и и прочее. С другой стороны, развернуть простой HTTP-сервер с CLI при некотором опыте с Nest достаточно просто. Мы, например, разворачиваем новые сервисы уже из готовых шаблонов.

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

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

Наконец, Nest, возможно, не лучший выбор, если для вас критична максимальная производительность. За удобство, универсальность и гибкость Nest отвечают множество слоев абстракций и rxjs-магия — и всё это, увы, отрицательно сказывается на производительности. 

Что в итоге?

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

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

Спасибо за внимание, буду рад ответить на вопросы и комментарии.


ссылка на оригинал статьи https://habr.com/ru/company/sravni/blog/704594/