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

Под катом вольный краткий перевод с комментариями

Для исследования были выбраны 7 моделей

Обзор  LLM, использованных в исследовании. CW обозначает Context Window.
Обзор LLM, использованных в исследовании. CW обозначает Context Window.

Бенчмарк для оценки моделей включал все образцы из HumanEval+ и MBPP+, а также 600 образцов из APPS+, в общей сложности 1000 образцов. Кроме того, для оценки эффективности генерации кода LLM в реальных проектах ученые создали реальный эталонный тест RWPB из репозиториев GitHub, созданных в 2024 году.

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

Основные выводы такие:

  1. Код, сгенерированный моделями, обычно имеет меньше строк, но немного более высокую сложность, в то время как количество API аналогично количеству канонических решений. Примечательно, что код, сгенерированный Claude-3, имеет тенденцию включать меньше строк кода и более низкую сложность при использовании большего количества API, что, вероятно, связано с тем, что Claude-3 использует API для замены частей сложной реализации кода.

    Различия в характеристиках кода между кодом, сгенерированным моделями, и каноническими решениями на HumanEval+. CC обозначает цикломатическую сложность.
    Различия в характеристиках кода между кодом, сгенерированным моделями, и каноническими решениями на HumanEval+. CC обозначает цикломатическую сложность.
  2. Неверный код содержит больше комментариев, чем верный код.

    Для HumanEval+ неправильный код, особенно от Claude-3 и Deepseek‑Coder, имеет тенденцию иметь больше разнообразных комментариев, чем правильный код. Аналогично, в наборе данных APPS+ дополнительные модели имеют тенденцию включать комментарии как в правильный, так и в неправильный код, с тенденцией к получению более высокого медианного числа комментариев в неправильном коде. Это явление можно объяснить двумя потенциальными факторами. Во‑первых, сложные проблемы, присутствующие в корпусах кода, используемых для обучения этих LLM, часто содержат больше комментариев. Следовательно, LLM могут научиться воспроизводить эту модель, что приведет к генерации большего количества комментариев при решении сложных задач. Во‑вторых, LLM могут генерировать дополнительные комментарии как форму контекстного или пояснительного контента, когда они не уверены в правильности сгенерированного кода. Однако эти комментарии не значительно повышают точность LLM, поскольку уровень корректности остается низким.

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

  4. Неправильное использование API составляет наибольшую долю runtime ошибок в большинстве бенчмарков. Существует три основных ошибки, связанных с неправильным использованием API: AttributeError (20,9%), TypeError (50%) и ValueError (26,9%). AttributeError возникает, когда делается недопустимая ссылка на отсутствующий атрибут API. TypeError возникает, когда API применяется к объекту неправильного типа, в то время как ValueError возникает, когда предоставляется аргумент правильного типа, но неподходящее значение.

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

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

Разница в количестве комментариев между правильным и неправильным кодом, сгенерированным LLM. SC2 обозначает StarCoder-2, DC обозначает DeepSeekCoder, LL3 обозначает Llama-3, а CL3 обозначает Claude-3.
Разница в количестве комментариев между правильным и неправильным кодом, сгенерированным LLM. SC2 обозначает StarCoder-2, DC обозначает DeepSeekCoder, LL3 обозначает Llama-3, а CL3 обозначает Claude-3.

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

Модальности трех распространенных тестов и производительность семи популярных LLM в генерации кода на этих тестах. CC обозначает цикломатическую сложность. Все значения указаны в %.
Модальности трех распространенных тестов и производительность семи популярных LLM в генерации кода на этих тестах. CC обозначает цикломатическую сложность. Все значения указаны в %.

Чтобы изучить основные причины неправильного кода, сгенерированного LLM, ученые провели всестороннее исследование таксономии ошибок в сгенерированном коде. В частности, этот анализ включал два ключевых шага:

  1. На первом этапе скрипт собирал выходные данные интерпретатора Python во время выполнения кода и использовал регулярные выражения для первичной аннотации ошибок. Синтаксические ошибки идентифицируются путем сопоставления «Syntax Invalid» или «Syntax Error» в сообщениях об ошибках. Ошибки времени выполнения обнаруживаются путем сопоставления поля «Traceback». Функциональные ошибки распознаются фразой «AssertionError».

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

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

Таксономия ошибок, возникших в коде, сгенерированном LLM.
Таксономия ошибок, возникших в коде, сгенерированном LLM.

Среди трех основных типов ошибок синтаксические ошибки составляют наименьшую долю, тогда как функциональные ошибки являются наиболее распространенными. По мере увеличения сложности набора данных частота функциональных ошибок также увеличивается. Большинство LLM имеют долю синтаксических ошибок менее 10%, тогда как DeepseekCoder, LLama-3, Phi-3 и GPT-3.5 имеют долю функциональных ошибок, превышающую 50% на APPS+.

Распространение непонимания и логических ошибок в коде LLM
Распространение непонимания и логических ошибок в коде LLM
Распределение типов ошибок в зависимости от LLM
Распределение типов ошибок в зависимости от LLM
Описание типов ошибок:

Тип A: Синтаксическая ошибка

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

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

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

A.3 Ошибка импорта библиотеки. Импорт библиотеки позволяет программам Python использовать внешний код, избегая избыточной разработки. К распространенным ошибкам импорта относятся отсутствующие операторы импорта и неверные уровни импорта. В следующем примере сгенерированный код неправильно импортирует все публичные функции из библиотеки heapq в теле функции. Однако эта операция разрешена только за пределами тела функции.

Тип B: Ошибка во время выполнения

Ошибки времени выполнения относятся к ошибкам, которые возникают, когда код не соответствует спецификации времени выполнения, которая обнаруживается во время выполнения. Согласно таксономии, существует пять вторичных ошибок времени выполнения: API Misuse , Definition Missing , Incorrect Boundary Condition Check , Incorrect Argument и Minors .

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

B.2 Отсутствует определение. Python требует, чтобы переменные и функции были определены до их использования в программе. Однако иногда опускайте определение часто используемых переменных или простых функций. Как показано в коде ниже, LLM игнорирует определяющую переменную MOD , которая обычно используется в алгоритмических задачах.

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

B.4 Неправильный аргумент. LLM иногда игнорируют указанный формат ввода в описаниях задач, что приводит к несоответствиям в количестве или типе аргументов в сгенерированном коде. Как показано в коде ниже, задача включает два ввода: первый указывает количество элементов, а второй представляет элементы для обработки. Однако сгенерированный код устанавливает только один параметр для получения элементов.

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

Тип C: Функциональная ошибка

Функциональные ошибки относятся к ошибкам в программе, которые заставляют ее вести себя неправильно или не так, как предполагалось в соответствии с ее функциональными требованиями ( т. е. код выполняется успешно, но не проходит все модульные тесты). Согласно таксономии, существует четыре вторичных функциональных ошибки: Непонимание и логическая ошибка , Галлюцинация , Ошибка формата ввода/вывода и Незначительные .

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

C.2 Галлюцинация. Галлюцинация относится к случаям, когда LLM генерирует код, который синтаксически правдоподобен, но фактически неверен или семантически бессмыслен. Как показано в коде ниже, код, сгенерированный LLM, вообще не соответствует требованиям задачи.

C.3 Ошибка формата ввода/вывода. В отличие от ошибки типа B.4 ( т.е. неверный аргумент), ошибка формата ввода/вывода относится к неправильному порядку входов и выходов, а также к неправильной точности выходных данных. Как показано в коде ниже, LLM неправильно преобразует вывод с плавающей точкой в ​​целое число.

C.4 Незначительные ошибки. Незначительные ошибки в функциональных ошибках включают неверную инициализацию , неоптимальный код и бесконечный цикл . Неправильная инициализация указывает на то, что логика кода верна, но неверные значения инициализации для некоторых переменных не позволяют коду проходить модульные тесты. Как показано в коде ниже, переменная max_sum неправильно инициализируется значением 0. Неоптимальный код относится к случаям, когда LLM генерирует код с использованием неоптимальных алгоритмов ( например, жадных алгоритмов) для решения проблемы, в результате чего код может пройти только некоторые модульные тесты, но не все. Бесконечный цикл относится к коду, который не соответствует условиям выхода из цикла при определенных входных данных, что приводит к бесконечному выполнению кода.

Как защититься от ошибок?

Авторы исследования предложили свой шаблон промпта для исправления ошибок

Промпт для исправления ошибок
Промпт для исправления ошибок

DOC_STRING и INCORRECT_CODE обозначают исходную проблему программирования и неверный код, сгенерированный LLM. BUG_CATEGORY содержит категорию и тип ошибок. COMPILER_FEEDBACK — результат выполнения компилятора. Если результат относится к типу A (т.е. синтаксическая ошибка) и типу B (т.е. ошибка времени выполнения), предоставляем журнал ошибок; если компилятор не возвращает ошибку, но код не проходит модульные тесты (т.е. функциональная ошибка), предоставляем сообщение «Функциональность кода неверна»; и если выполнение кода превышает ограничение по времени, предлагаем сообщение «Выполнение кода превысило ограничение по времени». CAUSE_AND_CODE обозначает основные причины и местоположения ошибок, а также повторно исправленный код, сгенерированный LLM.

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

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

Результаты исправления ошибок GPT-4 с использованием  подхода, основанного на самокритике.
Результаты исправления ошибок GPT-4 с использованием подхода, основанного на самокритике.

В начальной итерации GPT-4 исправляет 24,1% (29 из 120) неправильного самогенерируемого кода с помощью предложенного метода. Еще 6,6% (6 из 91) кодов исправляются во второй итерации. Синтаксические ошибки полностью исправляются в первой итерации. Для runtime ошибок 23% кодов ошибок исправляются в первой итерации и 16% во второй итерации. Для функциональных ошибок 23% исправляются в первой итерации и 3% во второй итерации.

Оригинал

Если вам интересны новости про генеративный ИИ, LLM, мультиагентов, я рассказываю об этом в своем Телеграм-канале.

Комментарии (17)


  1. Mike_666
    09.07.2024 20:34
    +4

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

    Без как минимум DeepSeek-Coder-V2, Llama3:70b и Qwen2:72b обзор открытых решений выглядит очень однобоким.


    1. ksenia-plesovskikh Автор
      09.07.2024 20:34
      +6

      Один из выводов исследования - большие платные модели делали функциональные ошибки так же часто, как и бесплатные маленькие


  1. 0xC0CAC01A
    09.07.2024 20:34
    +3

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


    1. Dair_Targ
      09.07.2024 20:34
      +6

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


      1. Nansch
        09.07.2024 20:34

        Даже если начнёт? В корректировочном промпте подправите ход решения как и с любыми ошибками.


        1. Kergan88
          09.07.2024 20:34
          +7

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


    1. Pol1mus
      09.07.2024 20:34
      +8

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

      Я своего телеграм бота научил сохранять и запускать файлы, так что он теперь пишет код, запускает его, смотрит вывод, переписывает и снова запускает. В первых версия он сталкивался с ошибками передачи текста в функцию, то есть писал нормальные программы но после их сохранения там появлялись лишние символы типа \\n вместо \n, и он пытался бороться с этим разными способами, в итоге переписал программу в однострочник и получил нужный результат. В рамках одного запроса(напиши программу такую то) он написал нормальную программу, сохранил-запустил-увидел ошибки синтаксиса в выводе, переписал по другому - снова ошибки, переписал по другому (использовал вариант с exec и кодированием строк), тоже какие то ошибки получил и в итоге попробовал удачный вариант с однострочником, больше 5 попыток за 1 запрос сделал сам.


      1. vikarti
        09.07.2024 20:34
        +1

        А можно статью подробную на эту тему?


      1. MRTNDN
        09.07.2024 20:34

        Здравствуйте, можете расписать подробнее об боте или дать гит\ссылку на бота?



  1. event1
    09.07.2024 20:34

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

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

    А если серьёзно, вот интересно, можно ли дотренировать модель на какой-нибудь конкретный проект и пусть она там таски из жиры реализует. Была бы хоть польза какая.


    1. meettya
      09.07.2024 20:34

      Очень вряд ли. А вот черновик тестов болван пишет неплохо, по моему опыту ChatGPT очень хорош. А вот с локальной Qwin договориться не получилось, хотя может из-за того что у меня карта слабая, на 8Гб - но выдает какую-то ерунду на отвали.


  1. meettya
    09.07.2024 20:34

    Основная проблема моделей - они оперируют неактуальными данными о языке и его "ландшафте". Используют старые подходы и либы. Я пытался привлечь gpt-болвана для пета на Go - ну оно работает, но кажется за такое в приличной компании забили бы трубочками от коктейля. А еще он постоянно врет, особенно если ты пытаешься получить несуществующий результат. У меня он легко соглашался писать конфиги для nginx с выдуманными директивами :)


  1. akakoychenko
    09.07.2024 20:34

    Интересно, есть ли подходы с генерацией кода через рекурсивный вызов LLM с переходом от общего к частному?

    Условно, первый раз модель не пишет код, а пишет шаги алгоритма крупными мазками + сигнатуры методов на каждый шаг

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

    На третьем - уже пишется код под ТЗ второго. То есть, чтобы в итоге получить код, шел не один вызов к модели, а столько, сколько надо шагов. Если алгоритм нужен очень объемный, то еще и шаги над шагами.

    То есть, чтобы каждый конкретный вызов работал с предельно малым контекстом


    1. Kojimeister
      09.07.2024 20:34

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


      1. akakoychenko
        09.07.2024 20:34

        Вообще, интересно, нельзя ли синтаксис языка жёстко закрепить в процесс генерации токенов сетью?

        Чтобы сеть не генерировала следующий токен из полного многообразия языка, а получала на каждом слове short-list возможных токенов, рассчитаный по грамматике языка программирования?


  1. NotebookKiller
    09.07.2024 20:34

    Данный текст написан и переведён нейросетью для оправдания нейросетей