Еще одно НЛП?
Исходный код AI недавно стал популярной темой; больше, чем когда-либо прежде, компании тратят усилия в этом направлении, пытаясь использовать его для своих нужд. Мотивы вполне ясны; Во-первых, поскольку приложения с искусственным интеллектом извлекают выгоду из предметной области, что может быть лучше, чем когда вся организация, занимающаяся исследованиями и разработками, является экспертом в интересующей области! Во-вторых, учитывая многие недавние прорывы НЛП, кажется, что такие методы могут быть легко применены к любой текстовой области, которая существует (включая исходный код). Подводя итог, можно сказать, что применение методов НЛП глубокого обучения к исходному коду кажется супер простым делом; Просто возьмите одну из множества предварительно обученных Языковых моделей Трансформеров и используйте ее для интересующей задачи. Что может пойти не так? Но реальность, как обычно, сложнее. До успешного применения глубокого обучения к исходному коду еще далеко. Основная причина заключается в множестве уникальных особенностей этой области, что делает такой подход менее актуальным. Давайте более подробно рассмотрим несколько наиболее значительных проблем, связанных с областью исходного кода.
Уникальный словарь
Хотя евангелисты Python любят заявлять, что «чтение хорошей программы на Python почти похоже на чтение на английском языке», правда в том, что Python (и в основном любой другой язык исходного кода) отличается токенами, из которых он построен; наличие двух основных типов токенов — определяемых пользователем (например, имена переменных, имена функций и т. д.) и встроенных в язык (например, слов def, len или символа =). Оба они не будут правильно распознаны обычными моделями глубокого обучения НЛП (обычно обучаются на обычном корпусе английского языка), что приводит ко многим сценариям вне словарного запаса, которые, как известно, сильно влияют на производительность таких моделей. Решением может быть использование вместо этого языковых моделей, которые специализированы (специально обучены) для области исходного кода (например, code2vec), а затем настраиваются на интересующую проблему. Следующей проблемой будет то, как сделать его многоязычным.
Уникальный словарь для каждого языка
В то время как в общем мире НЛП использование модели на основе английского языка может быть достаточным для многих типов приложений (таких как анализ настроений, обобщение и т. д., учитывая преобладание английского языка), в исходном коде мы обычно предпочитаем иметь многоязычные модели. , для решения одних и тех же проблем (таких как обнаружение дефектов) на широком спектре языков одновременно (не только Python, но и для одновременной поддержки других языков, таких как JavaScript и Java). В этом основное различие между академическим и коммерческим миром; в то время как при публикации статьи мотивация может заключаться в проверке новой концепции (и, следовательно, применения одного языка исходного кода может быть достаточно), в производственной среде мы хотели бы предложить нашим клиентам решение с минимальным набором ограничений, чтобы сделать так, чтобы он поддерживал как можно более широкий спектр языков. CodeBERT от Microsoft и CodeT5 от SalesForce являются примерами в этом направлении, преднамеренно обучающими многоязычными языковыми моделями (поддержка ~6 языков). Первая проблема с такими решениями заключается в том, что их подмодели для конкретных языков всегда лучше, чем общие (просто попробуйте обобщить фрагмент Python, используя общую модель CodeT5 и оптимизированную для Python). Другая, более существенная проблема заключается в том, что такая ограниченная (~ 6 языков) поддержка — это всего лишь капля в море. Достаточно взглянуть на Список поддерживаемых языков от лингвиста на GitHub, чтобы понять, что этого недостаточно. И даже если мы оптимистично предположим, что такие модели могут быть беспрепятственно применены к подобным языкам (таким как C и C++, учитывая, что CodeBert поддерживает язык Go, который считается очень похожим), как насчет таких языков, как Yaml, XML и Clojure, чей синтаксис настолько отличается, справедливо предположить, что такой переход не должен иметь места. Решением может быть попытка охватить менее общие языковые модели, но более оптимизированные для решения интересующей проблемы. Следующей проблемой будет то, как выполнить требуемую область контекста прогнозирования.
Разреженный контекст
В отличие от обычных текстов, которые предполагается читать от начала до конца, код более динамичен, больше похож на башню из кирпичиков лего, которую предполагается компилировать и оценивать в каждом конкретном случае. Рассмотрим, например, простую объектно-ориентированную программу со структурой Наследование базового абстрактного класса (человек) с интерфейсом (выведите название должности человека), набором реализаций (разный вывод для сотрудников и менеджеров) и функцией которые применяют интерфейс (печатное название) для входного списка базовых (персональных) объектов. Предположим, мы хотим обучить модель суммировать такие функции. Как наша модель должна понимать программу, которую мы только что описали? Требуемый контекст (реализация печатного заголовка), скорее всего, будет в другом месте — далеко в этом классе или, может быть, даже в другом файле или проекте. И даже если мы рассмотрим огромный трансформер, который видит много контекста; поскольку соответствующий контекст может находиться в совершенно разных местах, локального контекста может быть недостаточно для проблемы, которую мы пытаемся решить (более того, учитывая лучшие практики, такие как Инкапсуляция и повторное использование кода, которые увеличат вероятность локального контекста). разреженность). Решением может быть попытка скомпилировать и объединить все соответствующие фрагменты кода до применения модели. Следующей проблемой будет то, как учитывать различные динамические состояния кода.
Состояние программы
В отличие от текстов, которые всегда имеют один и тот же результат, независимо от того, как мы его читаем (вероятно, за исключением квестов DnD), в коде результат зависит от конкретного ввода, который мы предоставляем. Если, например, мы хотим идентифицировать сценарии Null Pointer; они могут быть статическими (происходят всегда, независимо от ввода) из-за плохого кодирования и, следовательно, должны быть идентифицированы, просто читая код (PS, вот почему инструменты статического анализа кода довольно хорошо находят такие случаи), но они также может быть динамическим из-за плохих входных условий и отсутствия соответствующих проверок и, следовательно, должен быть менее идентифицируемым при простом чтении кода. Недостающий компонент — это граф потока данных; Понимая, как данные распространяются через программу, модель может определить, когда части кода вместе с конкретными условиями данных могут быть проблематичными. Такое представление можно получить с помощью таких инструментов, как Github’s CodeQL, который анализирует поток данных программы. Проблема в том, что это непростая задача, особенно если учесть потребность в многоязычности (например, CodeQL поддерживает только ~7 языков исходного кода). В то же время это может быть секретным соусом к волшебно работающему решению. Классический вопрос компромисса.
Подводя итог, область исходного кода кажется довольно сложной. В то же время мы должны принять во внимание гипотезу естественности Allamanis at el, которая утверждает, что Программное обеспечение — это форма человеческого общения; корпусы программного обеспечения обладают сходными статистическими свойствами с корпусами естественного языка; и эти свойства можно использовать для создания лучших инструментов разработки программного обеспечения. Алгоритмы НЛП должны быть способны обрабатывать задачи исходного кода. Мы должны убедиться, что они правильно увидят задачи, что позволит им правильно с ними справиться. Как успешно применять методы глубокого обучения НЛП в этих областях?
Понимание проблемы
Первое распространенное решение (которое в целом важно) состоит в том, чтобы правильно понять проблемную область, прежде чем пытаться ее решить; Чего мы пытаемся достичь? Каковы главные требования бизнеса? Каковы технические ограничения? Если многоязычие не является обязательным (например, при специальном нацеливании на приложения Android или Javascript), то, сняв это ограничение, разработка станет намного проще, что позволит нам использовать предварительно обученные модели для конкретного языка или даже обучать простые модели на нашем собственный. Концентрация на конкретном языке может позволить использовать обычные методы НЛП, преднамеренно подгоняя их к целевому языку исходного кода. Правильно понимая проблемную область, мы можем использовать упрощения, которые позволят использовать общие лучшие практики для решения наших задач. Переход от сверхсложных кросслингвистических задач к более общим задачам НЛП. Этого само по себе может быть достаточно, чтобы облегчить циклы разработки.
Область ввода
Правильное понимание области, которую мы пытаемся решить, также важно, чтобы убедиться, что мы выбираем правильный вход. Если мы пытаемся обобщить отдельную функцию, тела функции может быть достаточно. Это может быть то же самое, если функция вызывает внешние функции с самообъясняемым соглашением об именах. Если это не так, мы можем рассмотреть возможность предоставления в качестве входных данных всего класса или, по крайней мере, соответствующей реализации функций. Если наша цель более ориентирована на поток, например, для выявления областей, подверженных риску SQL Injection, то имеет смысл не только смотреть на конкретные реализации ORM (например, код Hibernate, который взаимодействует с базой данных) но рассмотреть возможность просмотра фрагментов, ведущих к этому пути. Основная проблема заключается в том, что этот подход перенаправляет усложнение модели, которую мы пытаемся обучить, на модули поддержки, отвечающие за сбор всех соответствующих частей области. Это означает, что мы так же хороши, как и эти модули поддержки. Добавление избыточного требования в нашу экосистему.
Моделирование данных
Внимательные читатели, возможно, заметили тот факт, что основные проблемы, которые мы представили, касались данных, а не самих моделей. Алгоритмы просто отличные. Данные, которые они получают, являются главной проблемой. Как и раньше, правильно поняв проблему, можно выбрать представление данных, которое лучше соответствует их потребностям. АСТ исходного кода обычно используются для обеспечения видимости взаимодействия функций более высокого уровня. Графики потока данных обычно используются для отслеживания того, как данные распространяются через программу (важно для обнаружения таких сценариев, как нулевые указатели или SQL-инъекция). Глядя на системные вызовы или код операции вместо простого кода, можно неявно обучать многоязычные модели (используя общий интерфейс, который можно использовать на разных языках). Встраивания кода, такие как code2vec, могут обеспечить понимание фрагмента на высоком уровне. Более того, некоторые из этих моделей встраивания уже являются многоязычными, что в то же время отвечает этой потребности. В некоторых случаях мы можем самостоятельно обучить многоязычную модель. Затем важно убедиться, что мы представляем все соответствующие подгруппы населения (учтите, что выборка набора данных кода и, в частности, использование для этого Github не тривиальны. Наивные подходы могут легко привести к серьезным скрытым смещениям населения). Обработка кода как простого текста может работать для более простых задач. Одним из его основных строительных блоков является определение уровня ввода; это могут быть слова, подслова или даже уровень символов. От самых специфичных для языка (слов) до самых общих (символы). Спираль — это пример открытого исходного кода, который пытается сделать токенизацию на основе слов более общей, нормализуя стили кодирования разных языков (например, именование Camel Case). Подслова — это компромисс между символами (когда модели нужно научиться идентифицировать словарные слова) и токенизацией слов (когда слова уже являются входными данными) для аналогичной мотивации; пытаясь сгенерировать конкретный корпус, следя за тем, чтобы не было слишком много сценариев, выходящих за рамки словарного запаса (подходящими примерами реализации являются BPE и часть слова). В некоторых случаях мы можем оставить только те части ввода кода, которые более важны для нашего случая (например, оставить только имена функций или игнорировать исходный код, встроенный в токены). Проблема в том, что для удовлетворения этой потребности требуется понимание Lexer, которое снова добавляет избыточный, подверженный риску компонент в нашу экосистему.
Что посмотреть дальше
Наиболее очевидным направлением, в котором пойдет мир программного обеспечения, является переход к моделям исходного кода, не зависящим от языка. Это делается путем создания соответствующих моделей, более специфичных для проблемной области, использования функций, уникальных для исходного кода (таких как программа и поток данных), и путем использования менее общих реализаций преобразователей, которые не рассматривают область исходного кода как еще одну. НЛП, но более специализированным образом, принимая во внимание конкретные характеристики предметной области (интересно, что это похоже на то, как передовые методы глубокого обучения изображений и НЛП применяются к звуковой области, не как есть, а с учетом уникальных концепций аудио, настройка этих архитектур, чтобы они лучше соответствовали звуковой области). В то же время, поскольку на приложениях домена исходного кода скорость может быть критической (например, для того, чтобы быть частью систем типа CI-CD, имеющих ограничения по ресурсам и скорости), мы, скорее всего, увидим все больше и больше облегченных реализаций, будь то облегченные преобразователи или более общие архитектуры НЛП. Наконец, поскольку домен исходного кода требует собственной маркировки (общие NLP менее актуальны), и с пониманием того, что просто полагаться на выборку Github недостаточно, вполне вероятно, что мы будем сталкиваться со все большими и большими усилиями по созданию помеченных наборов данных исходного кода. . Впереди захватывающие дни.