GopherCon проводится ежегодно в США с 2014 года. Go явно набирает обороты, поэтому в этом году мы провели две аналогичные конференции в Европе, вдохновленные оригинальным GopherCon: GopherCon Iceland в июне и GopherCon Russia в марте.

Франсеск, наш вице-президент по связям с разработчиками, проводит семинар в Исландии, а Вадим, руководитель команды ML, говорил в России.

Ниже представлена ​​отредактированная версия стенограммы выступления Вадима. Также доступны слайды.

Измерение тональности кода в репозитории git

Привет, меня зовут Вадим, я работаю над источником {d} и решаю проблемы машинного обучения исходного кода (MLonCode). Недавно я снова наткнулся на этот известный вопрос по StackOverflow:

Я все время смеюсь, когда читаю ответы, очень жаль, что этот вопрос закрыт. Поэтому я подумал, что могу использовать проекты с открытым исходным кодом, которые мы разработали в источнике {d}, для поиска таких комментариев в репозиториях с открытым исходным кодом. Большинство приведенных примеров саркастичны и могут быть квалифицированы как черный юмор. Я решил использовать анализ настроений, относительно хорошо изученную часть НЛП, для которой существуют большие наборы данных, которые можно бесплатно загрузить. Вот что мы собираемся сделать:

  • Просмотрите все коммиты в репозитории Git.
  • Для каждого измененного файла найдите новые комментарии.
  • Классифицируйте каждый комментарий как положительный, нейтральный или отрицательный.
  • Сюжетные настроения на протяжении всего проекта.
  • Посмотрите на забавные открытия.

Мы собираемся использовать следующие технологии:

  • Src-d / go-git для чтения репозиториев Git.
  • Src-d / hercules для анализа дерева коммитов.
  • Babelfish для извлечения комментариев.
  • Vmarkovtsev / BiDiSentiment для анализа тональности (работает на Tensorflow).
  • Python для обучения модели машинного обучения и Go для ее применения.

Сейчас я подробно опишу каждую.

идиот

Мы начали его еще в 2015 году как проект с внутренним открытым исходным кодом для выполнения наших задач по поиску и анализу исходного кода. Это пример типичного рабочего процесса проекта программного обеспечения в исходном коде {d} сегодня: мы открываем исходный код с первого дня и смотрим, что будет дальше. На этот раз go-git стал огромным успехом, набрав более 2400 звезд, и несколько компаний уже использовали его в производстве. Хотя ему не хватает некоторых функций старших братьев, он отличается высокой производительностью и расширяемостью. Наконец, специалисты по сопровождению - опытные маньяки кода, и go-git - хороший пример чистого API и расширяемой архитектуры.

Геркулес

Геркулес появился в декабре 2016 года как попытка ускорить создание Гит-оф-Тесея Эрика Бернхардссона путем повторной реализации его в Go и go-git. Действительно, сейчас это на порядок быстрее.

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

… Матрица перезаписи строк, которая показывает, сколько кода, написанного каждым разработчиком, было изменено другими…

… И даже структурная связь - насколько тесно логически связаны функции или классы.

Под крышкой Hercules находится механизм разрешения DAG, который освобождает пользователей от ручного проектирования конвейера анализа. Он также использует pkg/plugin, чтобы разрешить внешние расширения.

Вавилонская рыба

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

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

Вот почему мы выбрали альтернативный путь с Babelfish. Мы берем существующие парсеры (у каждого языка должен быть хотя бы один), упаковываем их среды в контейнеры, которыми мы управляем напрямую с помощью libcontainer, и подключаемся к RPC-серверу. Проблема здесь в том, что эти «родные» артефакты синтаксического анализа, абстрактные синтаксические деревья, находятся в разных форматах, которые нам нужно нормализовать, чтобы мы могли работать с ними, используя один и тот же код.

Мы поддерживаем драйверы - компактные проекты для преобразования собственного AST в обобщенный AST, который мы назвали универсальным абстрактным синтаксическим деревом (UAST). Таким образом, мы не можем полностью избежать бремени технического обслуживания. Согласитесь, что писать преобразователи деревьев намного проще, чем писать парсеры с нуля. RPC позволяет нам масштабироваться по горизонтали в широких пределах, но хуже справляется с небольшими задачами.

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

import (
	"gopkg.in/bblfsh/client-go.v2"
	"gopkg.in/bblfsh/client-go.v2/tools"
)
client, _ := bblfsh.NewClient("0.0.0.0:9432")
resp, _ := client.NewParseRequest().Content("...").Do()
nodes, err := tools.Filter(resp.UAST, "//*[@roleComment]")
// nodes[0].Token

Как видите, всего 3 строки кода, исключая обработку ошибок, позволяют извлечь все комментарии из строки исходного кода. Самое интересное, что UAST притворяется XML и выполняет на нем запросы XPath.

Tensorflow

Мне нужно сказать несколько слов о Tensorflow, прежде чем объяснять модель классификации комментариев.

Ядром Tensorflow является вычислительный граф. Он содержит два типа узлов: тензоры и операции. «Тензор» звучит немного научно; на самом деле это просто массив с числами. Таким образом, буферы указаны как входные, они могут представлять состояние графа, которое может изменяться, и есть выходные, которые мы читаем в конце. Операции преобразуют тензоры в соответствии с правилами потока управления

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

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

  1. Отлаживайте модель локально: убедитесь, что она сходится и нет ошибок реализации. Мы выполняем отладку на электронных графических процессорах в источнике {d}.
  2. Обучите модель на том количестве данных, которое у вас есть на кластере графического процессора.
  3. При необходимости примените алгоритм гиперпараметрической оптимизации для достижения наилучших показателей в (2).
  4. Экспортируйте обученный граф в формат «GraphDef» (буферы протокола).
  5. Распространяйте его встроенным или подключаемым способом, то есть внутри двоичного файла приложения или отдельно.
  6. Примените его («сделайте вывод») с любым языком программирования.

В нашем случае я использовал Python API для Tensorflow на выделенной машине с 4 графическими процессорами, выполняющими два эксперимента параллельно для обучения, а также Go API и простой ЦП для вывода. Тренировать модели Tensorflow с использованием текущих привязок Go невозможно, но даже если бы это было возможно, я бы все равно не стал этого делать.

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

Вы слышали о neugram, языке сценариев поверх среды выполнения Go? Он был создан Дэвидом Кроушоу, автором pkg/plugin. Neugram все еще находится в раннем возрасте и имеет проблемы с молодежью, но мне он очень нравится. Кто знает, может быть, однажды мы будем обучать модели Tensorflow с помощью neugram - я предвижу это.

Следующий код Go запускает граф Tensorflow:

Мы встраиваем модель с помощью go-bindata, импортируем граф и обнаруживаем входные и выходные тензоры. Запускается новый сеанс, и мы выполняем разрешенное дерево зависимостей, чтобы получить результат с учетом входных данных. Сеансы необходимы для распределения аппаратных ресурсов и управления средой. Технически они являются мостами к базовой среде выполнения Tensorflow, написанной на C ++ как отдельная библиотека, в случае с Go этот мост реализован с помощью CGo.

BiDiSentiment

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

Модель далеко не самая современная, но работает неплохо. Он состоит из двух уложенных друг на друга повторяющихся ветвей, каждая из которых имеет два уровня LSTM. Оба читают текст побайтно, первый слева направо, а второй - в противоположном направлении. После того, как весь текст прочитан, их выходные данные объединяются, и поверх него помещается плотный слой. Результат - два числа: предполагаемая вероятность отрицательного и положительного настроения. Очевидно, их сумма равна 1.

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

Самый большой набор помеченных данных, который я нашел, состоял из 1,5 миллиона твитов, поэтому я тренировался на нем. В качестве бонуса размеры текста привязаны к 140 (теперь 280) символам, что идеально подходит для максимальной глубины рекуррентной памяти. Вот график точности (количество правильно предсказанных настроений, деленное на общий размер) во времени.

Мы разбиваем данные на две части:

  1. Часть, на которой мы обучаем модель, 80% набора данных.
  2. Часть, в которой мы оцениваем точность модели, также известную как набор для проверки. Он содержит оставшиеся 20%.

Мы оцениваем по (2) вместо (1), чтобы избежать переобучения - то есть, когда модель слишком сильно адаптируется к обучающим примерам, она теряет способность к обобщению. Здесь показана точность проверки. Мы видим, что наша модель переоснащается через 5 эпох. Эпоха считается завершенной после того, как модель однажды увидела весь обучающий набор данных. Это типичная ситуация в рекуррентных нейронных сетях. Я должен был использовать методы уменьшения переобучения, например выпадение, но я не успел. Итак, мы выбираем состояние графика в эпоху 5 и экспортируем его.

Давайте посмотрим, как BiDiSentiment отвечает на некоторые ответы на эти вопросы StackOverflow.

go get -v gopkg.in/vmarkovtsev/BiDiSentiment.v1/...
# source: https://stackoverflow.com/a/316233
echo "When I wrote this, only God and I understood \
what I was doing. Now, God only knows" | $GOPATH/bin/sentiment

Выход (вероятность отрицательная): 0,8803515 - настроение явно отрицательное.

# source: https://stackoverflow.com/a/185803
echo "sometimes I believe compiler ignores \
all my comments" | $GOPATH/bin/sentiment

Выход: 0.88705057. Опять отрицательный.

# source: https://stackoverflow.com/a/185181
echo "drunk, fix later" | $GOPATH/bin/sentiment

Выход: 0,08034721. Положительный.

Анализ настроения комментариев

Подведем итоги и опишем, как мы проводим анализ тональности комментариев в исходном коде:

  1. Установите и запустите bblfshd.
  2. Установите Геркулес.
  3. Установите libtensorflow.

Затем мы анализируем любой репозиторий Git следующим образом:

hercules --sentiment https://github.com/golang/go

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

ProjectPositiveNegativeEffective pygame / pygame 93.498.2–4.8 поддоны / фляга 23.631.9–8.3 django / django 462.1536.1–74.0 golang / go 402.1507.3–105.2 kubernetes / kubernetes 186.191.694.5 keras-team / keras »104.871.133.7

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

Можно ли из этого сделать что-нибудь полезное? К сожалению, нет, четких тенденций или периодов нет. Слишком много разных комментариев. Возможно, нам повезет больше с репозиториями с одним основным участником. А пока интереснее просматривать самые отрицательные и положительные примеры комментариев к исходному коду Go. Отрицательный:

// It is low-level, old, and unused by Go's current HTTP stack
// not clear why this shouldn't work
// overflow causes something awful
// quiet expected TLS handshake error remote error: bad certificate

Источник: 1, 2, 3, 4.

Положительный:

// Special case, useful in debugging
// Implementation: Parallel summing of adjacent bits. See Hackers Delight, Chap. 5: Counting Bits.
// TODO(austin): This could be a really awesome string method
// TODO(bradfitz): this might be 0, once escape analysis is better

Источник: 1, 2, 3, 4.

Последний на самом деле написан Брэдом, который выступал на Gophercon прямо передо мной, хе-хе. В этом случае модель должна уловить слово «лучше».

Наконец, вот положительные отзывы о Keras, фреймворке глубокого обучения, который я использовал для обучения модели BiDiSentiment:

# With TensorFlow, we can infer the output shape directly
# Make sure there is exactly one 1 in a row
# get sorted list of layer depths
# Theano has a built-in optimization for logsumexp so we can just write the expression directly

Я посмотрел на другие результаты классификации и могу сделать следующие выводы. Сначала модель обучалась на данных одного типа (твиты) и применялась к данным разного характера (комментарии). Таким образом, это часто преувеличивает настроение комментариев. Во-вторых, он ведет себя по-разному в двух типах проектов. Первый тип - это проекты, которые содержат много деталей, много условий, много проверок и обрабатывают множество крайних случаев. Например, языковые компиляторы (Go) или веб-серверы (Django). BiDiSentiment классифицирует их как отрицательные: в тексте рассказывается, как не потерпеть неудачу. Второй тип - это проекты, которые объясняют, что происходит, какие функции среды они используют для улучшения работы или объясняют большую часть терминологии предметной области. Например, Kubernetes имеет множество комментариев DevOps, а Керас описывает рецепты глубокого обучения. BiDiSentiment относится к ним положительно: они учат или объясняют, почему они крутые. Я должен сказать, что глубокое обучение кажется современной алхимией (дискуссией), поэтому у Кераса большое положительное мнение, хе-хе.

Заключение

  • С помощью Go легко анализировать репозитории Git.
  • Go удобен для «вывода из коробки» с Tensorflow.
  • Нам нужен специальный набор данных для настроения комментариев. В то же время пометить 1 миллион комментариев вручную как минимум сложно. Можем ли мы использовать реакцию на комментарии о проблемах GitHub?
  • Общее настроение комментария, возвращаемое нашей наивной моделью Twitter, зависит от типа проекта.

Вот список докладов конференции Mining Software Repositories, которые углубляются в тему:

Это все. Если вы пишете об этом в Твиттере, используйте хэштег #MLonCode в Твиттере. Спасибо.