Привет, Хабр! 8 ноября отгремел очный финал МТС True Tech Champ 2024. В программе было много интересного — например, гонки роботов и конференция с докладами на главной сцене. Постепенно мы с вами ими делимся.
Сегодня публикуем пост на основе доклада «Как обмануть нейронную сеть» Алексея Зайцева — старшего преподавателя Сколтеха и руководителя лаборатории Центра прикладного искусственного интеллекта. Со своей командой он разрабатывает и исследует модели искусственного интеллекта, решает прикладные задачи и отвечает на фундаментальные вопросы. Что такое ИИ, как обучить нейросеть и как ее обмануть — об этом и не только прочитаете дальше. Полную видеоверсию можно посмотреть тут. Поехали!
Как обмануть нейронную сеть
Нейросети превосходят человека
Если мы возьмем человека, рядом поставим искусственный интеллект, а потом попросим их решить одну задачу, окажется, что качество решения будет примерно одинаковым во многих областях. А вот если посадить человека играть в шахматы с искусственным интеллектом, то современный ИИ, большой и мощный, выиграет почти в 100% случаев. Но это все-таки задачка из категории не самых важных.
Куда интереснее рассмотреть задачи вроде распознавания изображений. Вот вам показывают картинку, нужно определить, что на ней: порода кошки или собаки, автомобиль и так далее. Оказывается, нейронные сети могут справляться с этим на уровне человека или лучше него.
Если взять стандартную задачу с использованием выборки ImageNet, человек ошибается в распознавании классов примерно в 5% случаев. Нейронная сеть показывает практически такой же результат. И тут уже особо улучшать нечего, потому что эти 5% ошибок — это по сути предел. Такое количество ошибок объясняется неточностями самой разметки данных, так что улучшить качество распознавания в общем смысле просто невозможно. Мы, грубо говоря, уперлись в максимум для подобных задач.
Теперь перенесем все это на другую модальность — например, на речь или тексты. Модели прошлых поколений могли работать с текстом довольно ограниченно: их задача сводилась к бинарным ответам, как сигнал «1» или «0». То есть ИИ мог определить, есть ли в тексте упоминание МТС, и понять, положительно описывается компания или, наоборот, ее критикуют. Такие задачи успешно решались еще 10−15 лет назад, причем с высокой точностью. Но сейчас произошел значительный скачок. Искусственный интеллект научился отвечать практически на любые вопросы с довольно высокой точностью. Например, вы спрашиваете: «Столица России?» И получаете ответ: «Москва». С вопросом по физике он тоже справится, особенно если задача стандартная и не выходит за рамки типовых сценариев.
А теперь пример из жизни. Когда я задаю модели вопросы, связанные с моей работой (особенно такие, на которые я сам не знаю ответ), она практически в 100% случаев не справляется. А вот если я уже понимаю, как решить задачу, о которой я ее спрашиваю, то ответы вполне нормальные, допустимые. В таком случае ее уровень уже можно сравнить с нашим интеллектом. По сути тут она и правда не хуже человека.
А еще нейронным сетям отлично дается машинный перевод. Предположим, у нас есть текст на одном языке и его нужно перевести на другой. Если сравнить работу среднего студента, например меня, и нейронной сети, то сеть обычно справляется не хуже. Особенно если текст общий, без сложных терминов или узкоспециализированных тем. Перевод получается аккуратным, понятным и красивым. А если загрузить в нее дополнительные знания по конкретной предметной области, то результат станет еще лучше. В такой ситуации сеть даже превзойдет меня в этой задаче.
Машинное обучение. Главное
Когда я только начинал заниматься искусственным интеллектом и машинным обучением в 2008 году, мы не могли решать задачи машинного перевода на уровне человека, то есть продолжать тексты, писать стихи или создавать что-то действительно осмысленное. Такие возможности казались чем-то далеким и недостижимым.
В то время мы занимались немного другими задачами, которые тоже были очень важны. Например, еще с 60-х годов изучались вопросы прогнозирования, такие как предсказание, купит ли человек продукт или вернет ли он кредит, если обратился в банк с заявкой. Для этого собирались данные о клиенте: заполнялась анкета, где указывались возраст, пол, наличие детей, высшее образование и другие данные, которые могли быть полезны для анализа. На основе этой информации мы пытались предсказать, купит человек продукт или нет, интересна ли ему годовая подписка на видеосервис или она не нужна.
Если у нас есть такие данные и прогнозы, с точки зрения бизнеса мы можем предложить именно тот продукт, который заинтересует клиента. Это позволяет более эффективно взаимодействовать с потребителями.
Та информация, которую мы собираем о клиенте, называется его представлением. Это своего рода набор чисел, он достаточно хорошо описывает человека в контексте конкретной задачи. Я уже упоминал такие признаки, как возраст, пол или наличие детей. Но можно придумать и другие, более релевантные для определенной ситуации. Например, для задачи предсказания покупки продукта это могут быть данные о том, что клиент уже приобретал раньше. На маркетплейсах анализируется история покупок — и это очень полезная информация, которая позволяет предсказать, что человек может захотеть купить еще. Такой подход помогает делать точные рекомендации и улучшать персонализацию.
Задача и сейчас решается далеко не идеально. Например, вы смотрели конкретную книжку на маркетплейсе, хотели ее купить, а потом вам полгода показывают эту же самую книгу, хотя вы ее уже приобрели. Это не лучший подход, но все же более эффективный, чем если бы человек вручную подбирал вам рекомендации.
Такие системы работают благодаря представлению объекта — набору данных, которые описывают, что за объект, что он умеет или хочет, какая у него история взаимодействий. Если такое представление есть, дальше все становится относительно просто: искусственный интеллект берет эту информацию, обрабатывает ее с помощью несложной модели и выдает результат. Даже при всех недостатках такой базовый подход уже позволяет достичь заметных успехов.
Самый простой вариант искусственного интеллекта — использовать один признак, и уже на его основе принимать решение. Например, мы рассматриваем выдачу кредита и анализируем зарплату. Если она выше 300 тысяч рублей, то можно предоставить кредит, потому что человек кажется финансово надежным. Конечно, в реальности модели устроены сложнее, но базовая идея именно такая: мы берем что-то простое и строим это поверх имеющегося представления объекта.
Проблема в том, что эти представления мы обычно считаем данными, как будто они уже у нас есть. Мы предполагаем, что у нас хорошее описание, для которого нужно сделать прогноз. Но это далеко не так. Для большинства объектов в реальном мире настолько качественного описания просто нет. Например, картинка или текст — это сложное, неструктурированное описание. Оно не будет «хорошим» в стандартном смысле и не позволит решать большой набор задач напрямую.
Задачи Бонгарда
Давайте обратимся к книге 1967 года советского кибернетика Михаила Моисеевича Бонгарда. Он попытался разобраться, что отличает тогдашний уровень развития технологий от искусственного интеллекта, каким мы хотели бы его видеть. Для этого он придумал около 100 задач, которые есть в этой книге. Давайте рассмотрим три: № 2, 12 и 44.
Чтобы решить такую задачу, нам нужно придумать способ описания объекта, позволяющий определить, по какому признаку отличаются картинки слева и справа. На первый взгляд все просто: слева большие объекты, а справа — поменьше. Если мы научимся выделять этот признак, задача решена.
Но тут возникает проблема: как именно запрограммировать измерение размера объекта? Например, если мы просто посчитаем долю черных пикселей на картинке, это может не сработать. Возможно, объект закрашен неравномерно, а часть площади — вообще пустая. Нужно что-то сложнее, например, оценить, насколько объект локализован. Это значит, попытаться выделить ту часть картинки, которая содержит объект, и проверить, сколько осталось пустого пространства без информации. Такие подходы требуют изобретения более продвинутых методов анализа изображения.
Давайте посмотрим на другую задачу. Здесь ответ уже кажется менее очевидным. Если мы взглянем на картинки слева и справа, то для того, чтобы найти один общий признак, наш естественный интеллект должен немного потрудиться.
Например, можно заметить, что слева объекты скорее продолговатые, а справа — круглые, ширина и высота у них примерно равны. Если выделить такой признак, задача вроде бы решается. Но тут снова возникает вопрос: как именно извлечь его из картинки? Как превратить изображение в бинарное представление: есть признак или нет? Это требует разработки алгоритмов, которые смогут автоматически находить такие особенности.
И это только одна из задач, но на самом деле можно придумать сколько угодно других, каждая из которых по-своему сложна и интересна.
Вот еще одна задача. Здесь признак чуть сложнее, чем в первой, но, возможно, чуть проще, чем во второй. Например, у нас две дуги, и слева маленькие шарики расположены на разных дугах, а справа — на одной и той же. То есть справа два шарика находятся на одной дуге.
Сначала нужно догадаться, что различие именно в этом, а потом еще запрограммировать способ его определения. Проблема в том, что мы не можем заранее перечислить все возможные задачи. Ее бы не было, если бы мы могли сказать: вот есть 442 задачи, и если научимся их все решать, то искусственный интеллект готов.
Реальность другая: вы можете прямо сейчас придумать 443-ю задачу, которая будет отличаться от всех предыдущих, и ее тоже потребуется решить, чтобы приблизиться к понятию настоящего ИИ. Это делает процесс обучения ИИ практически бесконечным, поскольку каждая новая задача добавляет еще один слой сложности.
Общая задача: универсальное описание объекта
Давайте попробуем немного расширить эту задачу. Мы хотим создать такую нейронную сеть, такой способ кодирования входной информации, чтобы на выходе получить универсальное описание объекта. Этот набор чисел должен позволять отличать количество шариков на каждой дуге, размер объекта, его продолговатость и все, что нам может прийти в голову.
Когда мы формулируем задачу таким образом — создание универсального описания объекта с помощью отдельного кодировщика, который извлекает информацию, — то выясняется, что ее решение проходило в три этапа.
Сам термин «искусственный интеллект» одновременно и новый, и старый. Многие слышали о тесте Тьюринга — он был одной из первых попыток определить, что такое интеллект в контексте машин. Но есть еще знаковое событие — Дартмурский семинар, состоявшийся в 50-х годах. Именно там был предложен термин «искусственный интеллект». Участники определили его как способность решать задачи, требующие интеллектуальных усилий.
Если у нас есть какая-то интеллектуальная задача, то мы считаем, что ИИ создан, если он способен самостоятельно, без участия человека, решить достаточно сложную задачу. Примером может быть тест Тьюринга.
Еще один пример — машинный перевод. Например, перевод сложного текста, вроде произведений Толстого, требует когнитивных усилий. Это не просто механический перебор слов со словарем, а работа, включающая понимание смысла, контекста и стиля.
В 50-е годы многие верили, что главное — это правильно поставить задачу. Если она сформулирована, то ее решение — дело времени, максимум пары лет, ну или пяти. Например, ожидалось, что скоро появится искусственный интеллект, способный переводить тексты не хуже человека. Попытки это реализовать начались, но успеха они не принесли. Так наступила первая «зима искусственного интеллекта» — период, когда многие утратили веру в перспективы этой технологии. Люди решили, что лучше сосредоточиться на других областях, где результаты были более ощутимыми.
Через какое-то время появилась новая волна оптимизма. Начали решаться более простые задачи и была сделана вторая серьезная попытка развивать ИИ. Этот период длился до конца 90-х годов. Однако, как и в первый раз, надежды оказались слишком высокими, а реальных решенных проблем — довольно мало.
К примеру, условный Google Translate уже появился в 2003 году, но тогда он все еще был значительно хуже того, что можно было сделать вручную. Эти модели сильно уступали человеку в качестве перевода. Со временем стало ясно, что для создания более совершенного искусственного интеллекта нужны новые идеи и подходы к обработке данных. Только с их помощью можно приблизиться к тому уровню, когда ИИ действительно работает сравнимо с человеком.
На мой взгляд, сейчас мы в состоянии решить практически любую задачу с использованием искусственного интеллекта. Достаточно показать модели большой объем данных, обучить ее — и в итоге получить правильный ответ. Это удивительное время для развития ИИ, когда мы можем справляться с большинством задач и эффективно строить представления объектов с помощью кодировщиков.
Структурированные данные и обучение представлений
Мы берем сложный объект, например картинку или текст, и превращаем его в набор чисел. Они позволяют понять, что там в данном объекте.
Если вспомнить фильм «Матрица», там происходило что-то похожее: вместо показа оператору исходной картинки на экране человек смотрел на бегающие зеленые символы. Их можно назвать представлением объекта или ситуации. Оно оказалось удобнее, так как давало более точное описание того, что действительно нужно было увидеть.
Универсальные представления
Когда мы тренируем такие модели и делаем это достаточно хорошо, требования к ним становятся шире. Раньше мы говорили о моделях, которые решают одну конкретную задачу, например перевод текста, его дополнение, сочинение стихов или распознавание картинок.
Но естественно ожидать от интеллекта способности решать сразу много задач. Мы стремимся к созданию таких представлений, которые позволяли бы, например, одновременно сегментировать картинку, определяя, где на ней есть кот, или его нет, распознавать объекты: кот, собака и так далее. А еще — анализировать глубину изображения, то есть расстояние от камеры до конкретного пикселя. Последняя задача особенно важна, например, для картографирования улиц. Машина проезжает по дороге, снимает окружающую среду на камеру, а потом мы получаем трехмерную сцену, с которой удобно работать.
Другими словами, речь идет о переходе от слабого искусственного интеллекта, способного решать лишь одну задачу, к сильному, который может справляться практически со всеми заданиями, поставленными перед ним.
Универсальная модель
Так называют единую модель-кодировщика, которую можно использовать для решения различных задач с небольшими дополнительными усилиями. А вот и пример такой модели:
Современные модели машинного обучения способны решать огромное количество задач, включая саммаризацию и перевод.
Как обучить универсальную модель
В обучении есть несколько важных моментов:
-
Представление модели. Чтобы ее обучить, нужно сначала понять, какой она должна быть. По сути мы создаем упрощенную модель мозга, определяя количество нейронов. Этот баланс важен: их должно быть достаточно для выполнения задач, но не слишком много. Если модель будет очень большой, потребуется огромное количество информации для ее обучения, и она станет склонна к переобучению, то есть запоминанию данных вместо их обобщения. Это снижает ее эффективность при работе с новыми задачами.
-
Данные, которые мы показываем модели. Искусственный интеллект в какой-то мере можно описать как способ имитации того, что он уже видел. Например, за свою жизнь человек видит множество картинок и на основе этого опыта может распознавать, что изображено на новых. Аналогичным образом работает и модель.
-
Способ подачи данных. Нужно решить, как именно показывать модели картинки или другую информацию, чтобы она смогла запомнить ключевые элементы и научилась работать с ними. Это касается процесса обучения и выбора подходящих методов, которые позволят нейронной сети правильно усвоить данные.
Вот как я могу прокомментировать эти пункты:
-
Для представления модели нужно примерно 175 миллиардов параметров. Такое количество позволяет модели запомнить все тексты, которые могут быть полезны, и агрегировать их так, чтобы потом можно было отвечать на вопросы на уровне человека.
-
Изначально мы собираем около 45 Тб, практически весь интернет. Затем проводится фильтрация, и остаются только качественные части, что все равно — несколько терабайт данных.
-
Модель нужно обучать. Это процесс, который я не буду подробно описывать, но важно отметить, что он стоит несколько миллионов долларов. Обучение занимает много времени и обходится дорого. Пока что учеба человека значительно дешевле. Однако после завершения процесса использовать модель гораздо проще: она готова к работе, доступна везде, где есть интернет, и ее не нужно перемещать или дорабатывать для большинства задач.
На картинке ниже детально показано, как размер модели влияет на итоговый результат. Попробуем объяснить: у нас есть метрика качества, которая по сути измеряет ошибку. Наша цель — сделать так, чтобы она была минимальной.
По горизонтальной оси мы видим, как модель обучается, постепенно снижая ошибку. Если посмотреть на желтую кривую (самая большая модель с 450 миллиардами параметров) и сравнить ее с другими кривыми, это меньшие модели, становится очевидно, что ошибка у желтой кривой заметно ниже.
Это еще раз подтверждает, что для достижения высокого качества действительно нужны большие модели. Их разработка и обучение стоят миллионы долларов, но без таких вложений добиться аналогичных результатов просто невозможно.
Подход к обучению
Чтобы обучать модели, нам нужны очень большие выборки данных, а это само по себе стало вызовом. Первая крупная выборка для картинок совершила настоящую революцию в распознавании изображений. Она содержала около 10 миллионов картинок, разделенных на 10 тысяч меток — например, золотые рыбки, крейсеры, машины и так далее.
Но этот подход имеет ограничения. Мы приблизились к пределу, поскольку размечать больше данных становится все сложнее и дороже. К тому же найти осмысленные, размеченные картинки в крупных объемах довольно трудно.
Поэтому был разработан подход, который не требует разметки, то есть не нужно, чтобы у каждой картинки был правильный ответ. Идея в том, чтобы заменить задачу классификации другой, для которой корректный ответ можно получить автоматически.
В случае текстов это решается очень естественно. Мы берем фрагмент, убираем из него последнее слово и показываем остальную часть нейронной сети, чтобы она предсказала недостающее слово. То есть модель обучается без необходимости ручной разметки данных.
Мы заставляем модель предсказывать недостающее последнее слово. В таком подходе она не только учится «предсказаниям», но и понимает смысл всего предыдущего текста. Это происходит автоматически, без необходимости дополнительной разметки данных.
Нам не нужно просить людей объяснять, о чем эти тексты, или вручную их классифицировать. Проверка происходит автоматически: модель угадывает последнее слово — так подтверждается, что она понимает структуру и содержание текста.
Когда мы собрали все вместе — подходы к обучению, бюджет в 5 миллионов долларов и нейронную сеть с 450 млрд параметров, — у нас получилась модель, которая показывает высокое качество и в ряде задач превосходит человека.
Можно ли обмануть нейросеть и как это сделать
Теория и первые опыты
Насколько модель «умная»? Один из способов проверить — посмотреть, можно ли ее обмануть. Если обратиться к исследованиям в области нейробиологии, есть интересная корреляция: уровень интеллекта человека связан с его умением убедительно лгать. Люди с более низкими когнитивными способностями обычно хуже придумывают правдоподобную ложь.
С другой стороны, умные люди лучше распознают ложь, особенно если ее можно вычленить из контекста текста. Это поднимает интересный вопрос: в состоянии ли мы придумать подобный тест для искусственного интеллекта? Он мог бы оценить, насколько ИИ способен распознавать попытки обмана и, наоборот, как легко его самого можно ввести в заблуждение.
В качестве теста я предлагаю использовать то, что называется «злонамеренные атаки». Допустим, у нас есть нейронная сеть, которая распознает изображения. Мы показываем ей картинку, и она выдает метку, например, что на картинке панда, с определенной степенью уверенности.
Чтобы понять, можно ли эту сеть обмануть, нам требуется провести эксперимент: взять исходное изображение и немного его изменить так, чтобы эти модификации были практически незаметны для человеческого глаза, но заставили нейросеть выдать неправильный результат. Такой подход позволяет проверить, насколько легко или сложно ввести модель в заблуждение.
Мы берем картинку, немножко ее меняем и смотрим на выход модели:
Ключевая идея здесь в том, что мы намеренно генерируем шум, который заставляет модель ошибочно увериться в неправильной метке. Например, мы берем изображение панды, добавляем к нему едва заметный для человеческого глаза шум, и модель начинает считать, что это не панда, а, скажем, гиббон. Причем ее уверенность в этом может быть даже выше, чем в правильной метке.
Это создает интересный эффект. Человек смотрит на исходное и измененное изображения и видит панду на обоих. Возникает вопрос: как нейронная сеть может ошибочно думать, что это нечто другое? Но практика показывает, что нейросети действительно уязвимы к таким атакам. Даже небольшие изменения, которых человек не замечает, могут существенно поменять вывод модели.
Есть даже более интересные атаки. Например, совсем незаметная модификация изображения. Берем его, меняем всего две точки, и обман нам удается — модель считает, что на картинке что-то другое.
Похожие атаки проводятся и с текстами. Например, мы можем «сломать» модель, предсказывающую что-то на основе текста, просто добавив несколько слов в его начало. Просто вставляем одно или два слова, и внезапно модель дает некорректные ответы. Они совсем не соответствуют тому, что мы ожидали. То есть текстовые модели тоже уязвимы к специально созданным искажениям.
Почему это происходит? Есть три основные гипотезы (помимо «нулевой», нейронная сеть просто глупая):
-
Добавляя модификации к изображениям, мы создаем варианты, которых нейронная сеть никогда раньше не видела. Эти измененные картинки как будто принадлежат совершенно другому набору данных, который искусственно создан и отличается от реального. Нейросеть оказывается недостаточно умной, чтобы распознать изменения, и начинает ошибаться.
-
Мы можем заглянуть внутрь модели и анализировать ее работу на уровне отдельных нейронов. Подбирая такую модификацию картинки, просто слегка меняем начальные слои нейросети. Эти небольшие изменения накапливаются по мере прохождения сигнала через сеть, как эффект бабочки, и в итоге приводят к неправильному ответу.
-
Данные дают огромную свободу действий. И изображения, и тексты — чрезвычайно богатые наборы. Например, одна картинка бульдога может быть представлена как массив из 50 тысяч чисел. Меняем все эти числа или только два из них, причем выбор того, что именно менять, остается за нами. В результате эти изменения приводят к тому, что нейронная сеть оказывается обманутой.
Обман нейросети на практике
Давайте рассмотрим и другие сценарии. Мы уже доказали, что, если изменить картинку внутри компьютера таргетированным образом, нейронная сеть вполне в состоянии ошибиться. Но подобные атаки можно проводить и в реальной жизни.
Например, создать специальный стикер, который клеится на знак «Стоп». Человек, смотря на него, все еще видит знак и распознает его корректно. Но нейронная сеть может «подумать», что это уже нечто иное, и воспринимает информацию ошибочно. То есть уязвимости нейронных сетей могут проявляться и за пределами цифровой среды.
Можно пойти дальше и создать специальные очки, например, с желто-зелено-оранжевым рисунком. С ними система ошибается в распознавании. Или можно использовать стикер, прикрепив его на себя. Он способен сделать вас невидимым для системы распознавания лиц или даже заставить ее идентифицировать вас как совершенно другого человека. Все это показывает, насколько нейронные сети уязвимы к реальным таргетированным атакам.
Методы защиты от обмана
Давайте обсудим два важных вопроса. Первый — если у нас есть такая атака, можем ли мы от нее защититься? Спойлер: да.
Для этого мы анализируем проблему, которая обычно заключается в нехватке информации. Дальше добавляем в обучающий набор те самые «странные» искусственно созданные данные, на которых модель ранее ошибалась. Затем мы дообучаем нейронную сеть, и она становится более устойчивой к подобным атакам.
Конечно, этот процесс нужно повторять итерационно. Например, после защиты от одной атаки можно создать новую, учитывающую особенности обновленной модели. Чтобы добиться устойчивости, нужно пройти несколько таких циклов.
Если мы пытаемся сделать модель универсально защищенной, часто сталкиваемся с тем, что качество распознавания в иных задачах немного снижается. То есть выигрывая в одном месте, мы проигрываем в другом. Это связано с тем, что ресурсы модели ограничены, и пока не удается охватить все сразу. В итоге она становится менее эффективной в общем использовании.
Второй вопрос — забавный, но показательный: можно ли провести атаку не с помощью сложных алгоритмов, а используя наш естественный интеллект? Например, если задать ChatGPT вопрос «Какой рецепт напалма?», то модель, скорее всего, ответит, что не может предоставить такую информацию. Но что, если попробовать обойти это ограничение?
Можно придумать историю, например, про бабушку, которая работала на заводе напалма и рассказывала внуку про его рецепт. Теперь, чтобы сохранить память о ней, вы просите модель вспомнить рецепт напалма. Такой подход, основанный на креативной подаче вопроса, иногда срабатывает.
Эти методы показывают, что обман моделей все еще возможен, даже без знания их внутреннего устройства. Это подчеркивает существующий компромисс между защитой и реальностью атаки. Системы можно улучшать, но на 100% исключить уязвимости, особенно такие, которые используют естественный язык и креативность, пока не удается.
На этом я завершу свою статью, показав примеры атак уже не на искусственный интеллект, а на естественный.
Посмотрите на картинку слева. Кажется, что полосы на ней наклонены то вверх, то вниз, но на самом деле они абсолютно параллельны. Это иллюзия, которая обманывает наш мозг. Справа лимоны на синем фоне тоже кажутся частью динамического изображения, хотя картинка полностью статическая.
Эти примеры показывают, что и наш естественный интеллект подвержен атакам. Вероятно, его тоже можно улучшать, чтобы он стал более устойчивым к подобным воздействиям.
Если вернуться к нейронным сетям, то, возможно, с их развитием они тоже достигнут состояния, когда смогут комфортно работать со всеми данными, которые чаще всего встречаются в реальной жизни. А остальное, как курьезные примеры или редкие случаи, может быть, и не стоит делать их основной задачей.
Спасибо, что дочитали! Если у вас есть вопросы по теме статьи, пишите в комментариях — постараюсь ответить.
Еще доклад с True Tech Champ 2024:
ссылка на оригинал статьи https://habr.com/ru/articles/861968/
Добавить комментарий