Pull to refresh
542.21
Сбер
Больше чем банк

GigaCode и все-все-все. Сравниваем различные ИИ-ассистенты между собой

Level of difficultyHard
Reading time19 min
Views5.6K

Привет, Хабр! Мы представляем команду GigaCode. В декабре 2023 года наш продукт стал доступен широкой аудитории. До этого GigaCode использовался только внутри компании, и нас часто спрашивали о том, как GigaCode выглядит на фоне других ИИ-ассистентов, как вы сравниваете себя с остальными? Отвечая на эти вопросы, мы начали с простой задачи, которая оказалась не такой уж и простой и вылилась в увлекательное исследование со всем тем, что мы так любим: множеством измерений, математической статистикой и, конечно же, новыми горизонтами. Интересно? Добро пожаловать под кат.

С чего начать?

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

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

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

Пример однострочной подсказки.
Пример однострочной подсказки.

Согласно нашим исследованиям и исследованиям компании Meta (признана в России экстремистской организацией) [1] большая доля выбранных подсказок и объёма принятого кода приходится на однострочные подсказки. Поэтому наше исследование будет связано именно с ними.  

Выбираем метрику качества помощи ИИ

Как же измерить качество однострочной подсказки? Самый простой способ — проверить условие полного совпадения подсказки, предложенной ассистентом, с так называемой истинной подсказкой.Для оценки можно взять фрагмент кода, убрать из него последнюю строчку и попросить ассистента её предсказать. Тогда эта удалённая строка будет считаться истинной строкой (или true line), а то, что предскажет ассистент, — предсказанной строкой (или predicted line). Если ассистент подскажет более одной строки, мы будем учитывать лишь первую. Таким образом оценивается его способность давать подсказки на основании верхнего контекста, поскольку всегда предсказывается последняя строка в коде, а снизу контекст отсутствует.

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

Расстояние Левенштейна

На помощь приходит дистанция редактирования — понятие, введённое в научный обиход нашим соотечественником Владимиром Левенштейном [2]. Дистанция редактирования, или расстояние Левенштейна, показывает, какое минимальное количество односимвольных операций (вставки, удаления, замены) необходимо для превращения одной последовательности символов в другую. Это то, что нам нужно, ведь почти правильная подсказка будет иметь небольшое расстояние от true line (а соответственно и значение метрики), в то время как сильно отличающаяся — наоборот.

Коэффициент помощи ИИ

Теперь попробуем оценить, насколько ассистент был полезен в каждом конкретном случае принятия подсказки. Нам осталось ввести нормировку (чтобы метрика получилась в процентах) и учесть случай, когда расстояние от подсказки до true line больше, чем сама истинная строка кода: 

c_{help}(s) = \max\big{[}0, len(s_{true}) - ld(s_{true},s_{pred})\big{]} \ /\ len(s_{true}),

где s = (s_{true}, s_{pred}) означает пару true line и predicted line соответственно, len(\cdot) возвращает длину строки в символах, а ld(\cdot,\cdot) вычисляет расстояние Левенштейна между строками. Если расстояние больше true line, то мы считаем, что ИИ не помог — c_{help}(s) = 0, за это отвечает условие \max

Коэффициент имеет простую интерпретацию: c_{help}(s) равен доле символов в строке s_{true}, которая была написана ассистентом. Действительно, вместо написания s_{true} с нуля, разработчик, приняв подсказку s_{pred}, совершил лишь ld(s_{true}, s_{pred}) редактирований символов, а оставшуюся работу по набору строки len(s_{true}) - ld(s_{true}, s_{pred}) = len(s_{true}) \  c_{help}(s) можно зачесть ассистенту. 

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

true line

predicted line

ld1

ld2

chelp1

chelp2

assertEquals(expected, FormattingUtils.msToMinSec(4494000));

assertEquals(expected,FormattingUtils.msToMinSec(4494999));

3

6

0,95

0,9

synchronized Optional<Controller> get(String name) {

synchronized void remove(Controller controller){

26

37

0,5

0,288

@BeforeAll

static MockedStatic<ContextManager>contextManagerMocker;

53

59

0

0

CloudApplication existingApp =context.getVariable(Variables.EXISTING_APP);

return Optional.empty();

63

73

0,16

0,027

while( !(nextCommand = readString()).equals(END_KERN_DATA ) )

while (!END_KERN_DATA.equals(nextCommand = readString()))

47

47

0,242

0,242

Здесь видно, что оригинальное расстояние Левенштейна в среднем немного завышает коэффициент помощи ИИ, а в некоторых случаях разница становится просто драматической. Поэтому в дальнейших расчётах мы решили использовать именно модифицированное расстояние ld_2. Проблема синтаксически разных, но одинаковых по смыслу фрагментов кода в случае однострочных подсказок не стоит так остро. Это связано с тем, что мы измеряем точность подсказок в рамках одной строки, поэтому количество таких случаев крайне ограничено. Чтобы обнаружить приведённый выше пример, нам пришлось перебрать около сотни подсказок. Поэтому, для упрощения расчётов, мы решили пренебречь этим фактом.

Метрика AI code help

Теперь перед нами стоит задача оценки качества выдаваемых подсказок на большом объёме протестированного кода. Добавим формализмов: \begin{itemize}

  • Имеется набор из N подсказок S = \{s^i\}_{i = 0}^{N}, s^i = (s_{true}^i,s_{pred}^i).

  • Каждая подсказка s однозначно соответствует строке s_{true}. Будем говорить, что подсказка s является подсказкой по строке s_{true}.

Проще говоря, метрика является функцией от набора подсказок, на которых она рассчитана. С точки зрения методологии исследования, чтобы получить набор подсказок S, необходимо сначала выбрать набор строк \{s_{true}^i\}_{i=0}^{N} из интересующих файлов с кодом. Затем для каждой такой строки нужно вставить верхний контекст (весь текст из файла, находящийся выше строки, включая комментарии к коду) в редактор кода IDE и дождаться s_{pred}, которая придёт в подсказке. Проще говоря, мы измеряем строки кода в файлах, подразумевая, что в действительности мы измеряем качество, с которым ассистент предсказал true line. О том, как именно мы выбирали файлы с кодом и строки из них, речь пойдёт в следующих главах. 

Мы стремимся посчитать метрику H(S) (H от слова help), которая отражала бы то, насколько подсказки ассистента релевантны и насколько они помогли разработчику в написании кода из исследуемого набора S. Самый простой способ — набрать статистику подсказок, посчитать коэффициент помощи c_{help} по всем подсказкам и затем усреднить. Что ж, теперь набираем 1000 подсказок для каждого ассистента, считаем 1000 коэффициентов AI help, усредняем, получаем результаты и расходимся? Так было бы в идеальном мире. 

В реальности возникает целый ряд проблем. Во-первых, значительная доля этих подсказок может состоять из единственного символа скобки или чего-нибудь столь же простого и короткого. Было бы не совсем корректно учитывать эти подсказки в общей статистике наравне с более длинными подсказками. Так возникает идея усреднять коэффициенты помощи ИИ пропорционально длине true line. А именно, сложим все длины s_{true}, умноженные на соответствующие коэффициенты помощи c_{help}(s). Для каждой строки len(s_{true})\  c_{help}(s) будет интерпретироваться как количество кода в символах, написанное ИИ-ассистентом в строке s_{true}. Просуммировав по всем строкам, получим общее количество кода (в символах), верно написанное ИИ. Остаётся поделить на общий объём протестированного кода, чтобы получить оценку доли, которую ИИ-ассистент в среднем пишет вместо разработчика. В точной форме полученная метрика H выражается следующим образом:

H(S) = \frac{\underset{s \in S}{\sum}len(s_{true})\  c_{help}(s)}{\underset{s \in S}{\sum}len(s_{true})}

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

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

Введённая нами метрика H даёт оценку доли кода, написанного ИИ, но при условии, что разработчик принимает любую подсказку, для которой коэффициент помощи c_{help} выше нуля. Однако такое поведение разработчика неоправданно идеализировано и наблюдаться могло бы лишь в случае, если бы вместо разработчиков код писали машины, целью которых является минимизация количества нажатий на клавиши клавиатуры. Наши наблюдения показывают, что в действительности разработчики принимают только те подсказки, чьё качество выше определённого порога. При редакционном расстоянии, близком к длине истинной строки (то есть при малых c_{help}), реальный разработчик скорее отвергнет подсказку и напечатает строку с нуля, чем будет редактировать predicted line, превращая её в true line. 

Чтобы отразить это поведение, мы ввели порог минимального качества подсказки AI help threshold и теперь не засчитываем те подсказки, по которым коэффициент помощи c_{help} оказался ниже этого порога. Формально, обозначив буквой t порог качества, введём в нашу метрику AI code help зависимость от t:

H(S| t) = \frac{\underset{s \in S}{\sum}len(s_{true})\  c_{help}(s)\  \mathbb{I} \{ c_{help}(s) \geq t \} }{\underset{s \in S}{\sum}len(s_{true})},

где I — индикаторная функция, равная 1, если условие в скобках выполнено, и 0 в противном случае.

Итак, мы смогли учесть поведение разработчика и вновь прийти к аккуратному формальному выражению. При нулевом пороге метрика H(t=0) совпадает просто с H, а при единичном пороге метрика учитывает лишь идеально точные подсказки, что эквивалентно полному совпадению (exact match) истинной строки (true line) с предсказанной (predicted line).

Далее мы будем строить графики AI code help в зависимости от порога t. Такие графики могут наглядно показать, какая доля AI code help приходится на подсказки различного качества. Однако, хоть графики несут больше информации и просто выглядят классно, всё-таки хочется уйти от произвола в выборе порога t и оценивать помощь ИИ одним-единственным числом. Как это сделать? 

Прибегнем к  простой эвристике: хочется учесть все возможные пороги t, но при этом низкие пороги учитывать с меньшим весом. Тут мы предполагаем, что разработчик будет пользоваться разными подсказками, но более точные предпочитать менее точным. Для этого достаточно просто проинтегрировать H(S | t)*t по всем t. Домножение на t как раз и служит тому, что величина AI code help при малых t учитывается с меньшим весом, чем при больших. Для нормировки итоговую метрику следует умножить на два:

\mathcal{H}(S)=2\int_{0}^{1}H(S|t)tdt=2\int_{0}^{1}\frac{\underset{s \in S}{\sum}len(s_{true})\  c_{help}(s)\  \mathbb{I} \{ c_{help}(s) \geq t \} }{\underset{s \in S}{\sum}len(s_{true})}tdt \\=\frac{\underset{s \in S}{\sum}2\int_{0}^{1}len(s_{true})\  c_{help}(s)\  \mathbb{I} \{t \leq c_{help}(s)\}tdt }{\underset{s \in S}{\sum}len(s_{true})}=\frac{\underset{s \in S}{\sum}2\int_{0}^{c_{help}(s)}len(s_{true})\  c_{help}(s)tdt }{\underset{s \in S}{\sum}len(s_{true})}\\=\frac{\underset{s \in S}{\sum}len(s_{true})\  c_{help}(s)^3}{\underset{s \in S}{\sum}len(s_{true})}

Итак, число символов, которые напечатал ИИ в одной строке, равно, как мы помним, len(s_{true})\ * c_{help}(s). Теперь мы эту величину дополнительно умножаем на c_{help}(s)^2, что легко  интерпретируется как умножение на вероятность принятия подсказки разработчиком. Таким образом, в этой модели вероятность принятия подсказки пропорциональна качеству этой подсказки в квадрате. Более удачные подсказки принимаются чаще, идеальные (c_{help} = 1) принимаются с вероятностью 1. Полученную метрику \mathcal{H} назовём интегральным AI code help или интегральной долей верно предсказанного кода.

Определяем спарринг-партнёров

Итак, метрика у нас есть. Теперь нам осталось понять, к чему же мы будем прикладывать эту линейку. Для этого мы воспользовались результатами опроса StackOverlfow, которые показывают степень востребованности различных ИИ-ассистентов среди разработчиков. И для сравнения с GigaCode мы выбрали лидеров рейтинга — Copilot и Codeium, а также одного из пионеров индустрии — Tabnine.

Рейтинг популярных ИИ-ассистентов разработчика.
Рейтинг популярных ИИ-ассистентов разработчика.

Начинаем измерять

Вооружённые готовой метрикой мы принялись оценивать ассистентов в бою. Изначальная идея была проста: возьмём набор файлов с кодом, написанным на одном языке, и будем имитировать их написание «с нуля»: вставим в редактор кода с подключённым плагином ИИ-ассистента первую строку кода из первого файла, поставим курсор в начало новой строки и зафиксируем пришедшую от плагина подсказку predicted line. Запишем пару (s^1_{true}, s^1_{predicted}). Затем вставим уже первые две строки и зафиксируем предсказание третьей строки, и так далее. В конце в файле вставим подряд n-1 строк, кроме последней n-ной, и запишем  (s^n_{true}, s^n_{predicted}). Разумеется, вставка контекста в редактор кода и запись подсказок были автоматизированы с помощью написанного нами скрипта. Повторим процедуру для нескольких файлов и вычислим на полученных парах true line и predicted line метрику AI code help. Подчеркнём, что мы измеряем исключительно строки кода, а не комментарии к нему, то есть true line — это всегда строка кода.

Нам уже не терпелось получить первые результаты. Взяв 11 случайных .java-файлов из открытых GitHub-репозиториев, мы принялись измерять. Нас ждала следующая картина:

Общая длина файлов составляет 2806 строк кода, что может показаться достаточно большим количеством измеренных подсказок, чтобы получить надёжную оценку метрики. Вопрос: в чём тут подвох? Посмотрим на метрики AI code help, вычисленные на отдельных файлах из нашей первоначальной выборки.

Такой разброс не мог возникнуть, если бы не одна особенность наших измерений: они не являются независимыми дрyг от друга. Коэффициенты помощи c_{help} коррелируют для строк из одного файла. Это очень важный нюанс, с которым нам предстояло разобраться, так как мы хотели получить адекватную оценку метрики и вычислить погрешность этой оценки. Чтобы лучше понять наши дальнейшие рассуждения, устроим читателю краткий экскурс в мир математической статистики.

От 11 до 17 тысяч файлов: как измерять подсказки независимо, или борьба с корреляцией

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

Эффект корреляции возникает как следствие двух особенностей наших данных:

  • Коэффициенты c_{help} для соседних строк в одном файле не являются независимыми величинами. Если ассистент хорошо предсказал n-ную строку в файле, следует ожидать, что он неплохо справится и с предсказанием n+1-ой, так как оба предсказания даются на основании общего контекста. И наоборот, если ассистент не очень силён в предсказании строк на основании данного ему контекста, то что n-ю, что n+1-ю строку он будет склонен предсказывать плохо, потому что к контексту добавилась лишь одна строка в конце. То же самое рассуждение будет верно и для строк, стоящих одна от другой на 2, 3, 4, ... m позиций. Очевидно, с ростом m эффект корреляции должен угасать. Такая корреляция одних измерений с соседними измерениями в общей последовательности всех измерений называется автокорреляцией.

  • Есть более общий эффект, заключающийся в том, что строки кода в одном файле и даже строки кода из разных файлов одного репозитория связаны общей тематикой. Другими словами, фрагменты кода в рамках одного файла или в рамках одного репозитория склонны быть похожими друг на друга. Например, в них могут встречаться одни и те же функции, модули, библиотеки. Это приводит к тому, что c_{help} для строк кода внутри одного файла и внутри одного репозитория коррелируют между собой. Например, если для набора строк из одного файла (репозитория) ассистент выдал подсказки, чьё качество выше среднего, то стоит ожидать, что на очередной строке кода из этого же файла (репозитория) он вновь выдаст подсказку более высокого качества, и наоборот. Так возникает эффект внутриклассовой корреляции. Другими словами, строки в рамках одного класса (множества строк из одного файла или репозитория с кодом) схожи друг с другом, а потому подсказки по таким строкам коррелируют. Эффект корреляции внутри файла ожидаемо должен быть ниже корреляции внутри репозитория, однако и там, и там он присутствует. 

Чтобы понять эффект лучше, можно воспользоваться простой аналогией. Допустим, перед вами стоит задача измерить средний рост людей на Земле. Вместо того, чтобы измерить рост у сотни абсолютно случайных людей, вы решили измерить рост у 50 голландцев и 50 бушменов, из которых 2 являются близнецами и имеют одинаковый рост. Но рост людей внутри каждой национальной группы коррелирует: голландцы в основном имеют рост выше среднего, а бушмены ниже среднего. Рост близнецов и вовсе коррелирует с коэффициентом, близким к 100 %. Измерить двух близнецов с точки зрения полезной информации — это практически то же самое, что измерить всего одного из них. Очевидно, что первая выборка, состоящая из 100 случайных индивидов, даёт более точную оценку среднего роста всех людей и несёт больше информации о людях в целом. 

Чтобы проверить наши предположения, мы измерили качество подсказок GigaCode, взяв случайным образом 152 файла из 119 репозиториев общей длиной 31 914 строк кода. Начали мы с того, что вычислили коэффициент корреляции между c_{help} для двух строк из файла, отстающих одна от другой на m позиций: corr\_coef \big{(}c_{help}(s), c_{help}(s^{+m})\big{)}. Зависимость этого коэффициента корреляции от расстояния между строками представлена ниже.

Что это за ломаная линия, спросите вы? Многочисленные пики являются шумом, случайной ошибкой, возникающей из-за того, что коэффициент корреляции вычислен на ограниченном количестве строк кода. Общий тренд, однако, налицо: корреляция действительно уменьшается с ростом расстояния между строками. Более того, она не исчезает полностью, а стремится к ненулевому значению. В этом и проявляется вышеупомянутый эффект кластеризации данных. Даже для удалённых друг от друга строк положительная корреляция между c_{help} присутствует просто в силу того, что они находятся в одном файле.

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

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

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

При увеличении общего числа измерений в  раз ошибка уменьшается в  раз.
При увеличении общего числа измерений в N раз ошибка уменьшается в \sqrt{N} раз.

В итоге мы решили провести измерения на одном из наших датасетов качественного кода на Java. Для этого датасета мы отобрали файлы из GitHub-репозиториев с определённым количеством форков и звёзд, с разработанными модульными тестами. Общий объем набора данных — 33456 .java-файлов, собранных из 2289 репозиториев.

Последовательно перебирая все строки во всех файлах, мы с вероятностью 1 % брали текущую строку и проводили измерение на ней для всех ассистентов.  В итоге мы измерили подсказки всех исследуемых ассистентов по 41 944 строкам из 17 717 файлов из 2059 репозиториев.

Вы можете спросить: раз строки внутри одного файла в любом случае дают коррелирующие измерения, то почему не брать просто по одной строке из каждого файла или вообще из каждого репозитория? Корреляция ведь упадёт в ноль. Гениальная идея, срочно отказываемся от предыдущей стратегии! Шутка. Проблема тут в том, что ассистенты дают более точные подсказки, если в их распоряжении имеется больше контекста. Однако в файлах с малым объёмом кода контекста попросту меньше, чем в длинных файлах. Это приводит к тому, что чем больше файл, тем, в среднем, большие коэффициенты c_{help} получаются при измерении строк в нём. Теперь представим, что мы директивно берём из каждого файла ровно одну строку, ни больше, ни меньше. Тогда пропорция строк, полученных из больших файлов, уменьшится, а из коротких — возрастёт. Это приведёт к неоправданному смещению оценки метрики AI code help в меньшую сторону. Например, если раньше файл длиной в 20 000 строк имел в сто раз больший вес в общей статистике, чем файл длиной в 20 строк, то теперь эта пропорция превращается в 1 к 1. Другими словами, распределение размеров файлов, из которых были взяты строки, смещается, и короткие файлы получают неоправданно больший вес. Предложенная нами стратегия позволяет избежать этого и получить несмещенную оценку AI code help. 

Бутстрэп: математические чит-коды

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

Начнём по порядку. Во-первых, метрика AI code help не сводится к простому среднему коэффициентов c_{help} по всем измеренным строчкам, поэтому классическая формула для стандартной ошибки среднего тут бы не сработала. Во-вторых, при выводе подобных аналитических формул обычно предполагается, что все измерения независимы. Тому, что в наших измерениях это условие нарушается, мы посвятили всю предыдущую главу, так что выходит неловкая ситуация. Но это только с первого взгляда! На помощь приходит бутстрэп — эффективный способ оценки доверительных интервалов в обход параметрических методов математической статистики. 

Ещё раз зададимся вопросом: почему возникает ошибка в оценке метрики? Да потому, что всякий раз мы её измеряем на ограниченных данных. Допустим, было измерено n_1 строк из n_2 файлов из n_3 репозиториев. Обозначим этот набор строк и подсказок по ним как S_1. Мы получили значение метрики H(S_1 | t) (далее для краткости будем опускать t, предполагая его фиксированным). Теперь допустим, что мы вновь случайным образом выбрали n_1 строк из других n_2 файлов из других n_3 репозиториев. Обозначим это как набор S_2. В общем случае очевидно, что H(S_1) \neq H(S_2). От случая к случаю мы будем получать несколько разный результат. Из-за случайности в выборе S у H(S) будет ненулевая дисперсия D (разброс): D\ H(S) \neq 0

Чтобы оценить этот разброс и существует метод бутстрэпа: на основании уже полученной выборки подсказок S мы будем случайным образом брать новые подсказки и складывать их в выборку \tilde{S}. При этом, в силу случайности, одна и та же подсказка может войти в новую выборку несколько раз. Таким образом, повторно выбирая подсказки из S, мы будем всякий раз получать разные наборы \tilde{S}, и H(\tilde{S}) тоже будет иметь ненулевую дисперсию. Ключевая идея бутстрэпа заключается в том, что дисперсия D \big{[} H(\tilde{S})\big{]} даёт хорошую оценку для дисперсии D \big{[} H(S)\big{]}. При этом оценить D \big{[} H(\tilde{S})\big{]} крайне просто: выбираем N разных наборов \{\tilde{S}_i\}_{i=1}^N, вычисляем N соответствующих метрик H(\tilde{S}_i) и считаем выборочную дисперсию. N можно взять порядка 1000.

Вопрос: каким образом мы будем собирать новые наборы \tilde{S} из уже имеющегося на руках оригинального набора подсказок S? Наши данные имеют трёхуровневую иерархическую структуру: строки, файлы, репозитории. В статье [3] показано, что в случае трёхуровневой иерархии данных имеет смысл делать выборку только на верхнем уровне. То есть, если в наборе S были данные из n репозиториев, то выбирая репозитории с повтором, мы получим второй набор из n репозиториев (где некоторые репозитории могут повторяться). Ключевое значение имеет возможность повторно выбирать то же количество репозиториев, которое было изначально. Из всех строк этих репозиториев составим новый набор \tilde{S}. Если какой-то репозиторий встречается в наборе k раз, то и все строки этого репозитория повторяются k раз.

Итак, с помощью бутстрэпа мы можем оценить дисперсию оценки нашей метрики. Более того, мы можем оценить и дисперсию оценки разницы AI code help для двух разных ассистентов. Для этого будем вычислять для каждого \tilde{S_i} соответствующую метрику H_1(\tilde{S_i}) для первого ассистента и H_2(\tilde{S}_i) для второго ассистента и считать их разницу. На основании полученной оценки и её дисперсии мы рассчитаем статистическую значимость отличия метрики AI code help для двух ассистентов.

Теперь мы умеем оценивать дисперсию и это, несомненно, прекрасно, но как оценить доверительные интервалы? Дело в том, что оценка метрики AI code help имеет нормальное распределение. Его дисперсию мы оцениваем с помощью бутстрэпа, а значит, мы можем оценить и ширину 95 % доверительного интервала оценки. Для нормального распределения эта ширина равна четырём стандартным отклонениям (стандартное отклонение равно корню из дисперсии).

Финальные результаты и их статистическая значимость

Ура, мы поняли, как снизить ошибку оценки метрики AI code help и научились численно эту ошибку оценивать. Представляем наш итоговый результат с 95 % доверительными интервалами вокруг оценок. Этот интервал показывает ту область значений, в которую попадает истинный AI code help c вероятностью в 95 %. При этом предполагается, что наш датасет GitHub-кода действительно репрезентативен и даёт несмещённую оценку AI code help.

Java
Java

В ходе нашего исследования мы обнаружили, что не все ассистенты стабильно выдают подсказки по каждой строке и порой «отмалчиваются», оставляя разработчика наедине с кодом. Особенно к такому поведению склонны GitHub Copilot и Tabnine, которые не выдавали подсказку в 16 % и 14,5 % случаев соответственно. Для сравнения, Codeium не выдал подсказку в 5,4 % случаев, GigaCode — 1,3 %. Пытаясь выяснить причины этого поведения, мы провели ещё одно небольшое исследование, повторно «прогнав» Tabnine и Copilot по 270 случайным строкам из 180 .js-файлов. Мы решили проверить, будут ли ассистенты пропускать подсказки повторно на тех же самых строках, что и в первый раз, или же этот эффект абсолютно случаен. Результаты довольно занимательны: Tabnine вновь пропустил подсказку для 92,9 % строк из тех, на которых он пропустил её в первый раз. При этом в обоих случаях он пропустил в общей сложности 5,2 % подсказок, что можно назвать довольно стабильным результатом. Copilot же повторно пропустил подсказку для 94,9 % строк из тех, для которых он пропустил её и в первый раз. Однако при этом он пропустил подсказки и для многих новых строк, так что общая доля пропусков подскочила с 14,4 % до 21,9 %. Наша гипотеза состоит в том, что ассистенты могут пропускать подсказки для контекста, в котором они меньше уверены, либо на основании каких-то ещё неизвестных нам внутренних правил. При этом количество пропусков может зависеть от загруженности серверов или быть продиктовано другими техническими особенностями работы инфраструктуры.

Если не учитывать пустые подсказки, то метрика AI code help, само собой, окажется выше. Она может рассматриваться как показатель среднего качества подсказок ассистента вне зависимости от того, как часто он их выдаёт. Однако она уже не отражает истинной помощи ассистента в разработке, так как закрывает глаза на то, что, пропуская подсказки, ассистент не вносит никакого полезного вклада. 



Java
Java

Отдельно мы измерили подсказки на языках JavaScript и TypeScript. В силу их синтаксической схожести мы собрали по ним общую статистику, состоящую из 4906 файлов из 188 GitHub-репозиториев. Получившаяся картина, в целом, схожа с результатами, полученными для Java-кода.

JavaScript, TypeScript
JavaScript, TypeScript

Приведём таблицу со статистической значимостью отличий интегральной AI code help между ассистентами, а также доли идеально подсказанного кода (AI code help при единичном пороге принятия подсказки). P-value, приводимое ниже, равно вероятности случайно обнаружить зафиксированное в результатах эксперимента отличие AI code help, если бы в действительности отличия между ассистентами не было. Чем эта вероятность ближе к нулю, тем более статистически значимо отличие. P-value округлены до сотых.

Интегральная метрика AI code help, %:

Язык

GigaCode

Copilot

Codeium

Tabnine

Java

48,8

46,8

45,8

42,3

JavaScript, TypeScript

44,1

42,9

43,8

37,3

Двустороннее p-value для интегрального AI code help (Java):

GigaCode

Copilot

Codeium

Tabnine

GigaCode

1

0

0

0

Copilot

0

1

0,09

0

Codeium

0

0,09

1

Tabnine

0

0

0

1

Двустороннее p-value для интегрального AI code help (JavaScript, TypeScript):

GigaCode

Copilot

Codeium

Tabnine

GigaCode

1

0,24

0,73

0

Copilot

0,24

1

0,1

0

Codeium

0,73

0,1

1

Tabnine

0

0

0

1

Доля идеально подсказанного кода, %:

Язык

GigaCode

Copilot

Codeium

Tabnine

Java

37,7

37,7

36,5

32,4

JavaScript, TypeScript

35,5

36,3

36,4

30

Двустороннее p-value для различия доли идеально подсказанного кода (Java):

GigaCode

Copilot

Codeium

Tabnine

GigaCode

1

0,997

0

0

Copilot

0,997

1

0,09

0

Codeium

0

0,09

1

Tabnine

0

0

0

1

Двустороннее p-value для различия доли идеально подсказанного кода (JavaScript, TypeScript):

GigaCode

Copilot

Codeium

Tabnine

GigaCode

1

0,44

0,42

0

Copilot

0,44

1

0,91

0

Codeium

0,42

0,91

1

Tabnine

0

0

0

1

Как видим, объёма измеренного Java-кода оказалось достаточно, чтобы с уверенностью отличить почти всех ассистентов. В случае языков JavaScript и TypeScript три ассистента — GigaCode, Codeium и Copilot — не обнаруживают статистически значимых отличий c точки зрения интегральной доли верно предсказанного кода. Это может быть связано как с меньшим объёмом измеренного кода, так и с отсутствием значимого отличия в реальности. Отметим, что в обоих случаях Tabnine статистически значимо отличается от конкурентов, причём отличие не в его пользу.

Выводы

Попробуем проанализировать полученные результаты.

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

Это позволяет нам сделать вывод о том, что по исследуемой задаче предсказания полной следующей строки кода по левому контексту GigaCode, Copilot и Codeium показывают паритетные результаты в области генерации практически точных подсказок.

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

Если оценивать всю помощь интегрально, учитывая различные пороги принятия подсказок в совокупности, то тут GigaCode опережает Copilot на 2 %, Codeium на 3 % и TabNine на 6,5  %.

В случае JavaScript и TypeScript ситуация схожа, с той разницей, что Codeium показывает более высокий результат. Действительно, кривые AI code help трех ассистентов статистически неразличимы при средних и высоких порогах принятия подсказки. При низких порогах Copilot по-прежнему статистически хуже GigaCode (p-value = 0,027 при нулевом пороге), однако начиная с порога в 0,2 статистически это различие уже теряется. 

В то же время, статистически значимое отличие для всех порогов сохраняется у Tabnine, но это отличие не в его пользу. С точки зрения интегральной метрики его отставание от GigaCode составляет 6,8 %. 

Также мы выяснили, что GigaCode подсказывает чаще, выдавая подсказку в 98,7 % случаев против 84 % для Github Copilot, 94,6 % для Codeium и 85,5 % для Tabnine.

Заключение

Надеемся, вам было интересно и увлекательно пройти этот путь вместе с нашей командой. В дальнейшем мы планируем продолжить сравнительные исследования ИИ-ассистентов и на других задачах, таких как, например, оценка качества подсказки с учётом контекста снизу, контекста из других файлов, оценка качества multi-line подсказок и многое другое, ведь GigaCode и другие ассистенты не стоят на месте!  

Источники

Авторы: 

  • Гавриляк Р.Я.

  • Шелепин С.Л

  • Балыбердин-Панкратов Ф.А.

Tags:
Hubs:
Total votes 13: ↑13 and ↓0+18
Comments17

Information

Website
www.sber.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия