Русский
Русский
English
Статистика
Реклама

Baseline

Статический анализ baseline файлы vs diff

25.06.2020 12:16:27 | Автор: admin

В статических анализаторах рано или поздно приходится решать задачу упрощения интеграции в существующие проекты, где поправить все предупреждения на legacy коде невозможно.


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



baseline или так называемый suppress profile


Этот метод имеет несколько названий: baseline файл в Psalm и Android Lint, suppress база (или профиль) в PVS-Studio, code smell baseline в detekt.


Данный файл генерируется линтером при запуске на проекте:


superlinter --create-baseline baseline.xml ./project

Внутри себя он содержит все предупреждения, которые были выданы на этапе создания.


При запуске статического анализатора с baseline.xml, все предупреждения, которые содержатся в этом файле, будут проигнорированы:


superlinter --baseline baseline.xml ./project

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


Обычно, мы хотим достичь следующего:


  • На новый код выдаются все предупреждения
  • На старый код предупреждения выдаются только если его редактировали
  • (опционально) Переносы файлы не должны выдавать предупреждения на весь файл

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


Вот примерный список того, что вообще может являться полем сигнатуры:


  • Название или код диагностики
  • Текст предупреждения
  • Название файла
  • Строка исходного кода, на которую сработало предупреждение

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


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


Ещё один менее очевидный признак имя функции или метода, в которой выдавалось предупреждение. Это позволяет снизить количество коллизий, но провоцирует шквал предупреждений на всю функцию, если вы её переименуете.


Эмпирически проверено, что с этим признаком можно ослабить поле имени файла и хранить только базовое имя вместо полного пути. Это позволяет перемещать файлы между директориями без инвалидации сигнатуры. В языках, типа C#, PHP или Java, где название файла чаще всего отражает название класса, такие переносы имеют смысл.


Хорошо подобранный набор признаков увеличивает эффективность baseline подхода.


Коллизии в методе baseline


Допустим, диагностика W104 находит вызовы die в коде.


В проверяемом проекте есть файл foo.php:


function legacy() {  die('test');}

Предположим, используются признаки {имя файла, код диагностики, строка исходного кода}.


Наш анализатор при создании baseline добавляет вызов die('test') в свою базу исключений:


{  "filename": "foo.php",  "diag": "W104",  "line": "die('test');"}

Теперь добавим немного нового кода:


+ function newfunc() {+   die('test');+ }  function legacy() {    die('test');  }

По всем используемым признакам новый вызов die('test') будет распознаваться как игнорируемый код. Совпадение сигнатур для потенциально разных кусков кода мы и называем коллизией.


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


Что делать, если вызов die('test') был добавлен в ту же функцию? Этот вызов может иметь одинаковые соседние строки в обоих случаях, поэтому добавление предыдущей и следующей строки в сигнатуре не поможет.


Здесь нам на помощь приходит счётчик сигнатур с коллизиями. Таким образом мы можем определить, что внутри функции ожидалось одно срабатывание, а получили два или более все, кроме первого, нужно сообщать.


При этом мы теряем некоторую точность: нельзя определить, какая из строк новая, а какая старая. Мы будем ругаться на ту, что будет идти после проигнорированных.


Метод, основанный на diff возможностях VCS


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


Утилита revgrep принимает на stdin поток предупреждений, анализирует git diff и выдаёт на выход только те предупреждения, которые исходят от новых строк.


golangci-lint использует форк revgrep как библиотеку, так что в основе его вычисления diff'а лежат те же алгоритмы.

Если выбран этот путь, придётся искать ответ на следующие вопросы:


  • Как выбрать окно коммитов для вычисления diff?
  • Как будем обрабатывать коммиты, пришедшие из основной ветки (merge/rebase)?

Вдобавок нужно понимать, что иногда мы всё же хотим выдавать предупреждения, выходящие за рамки diff. Пример: вы удалили класс директора по мемам, MemeDirector. Если этот класс упоминался в каких-либо doc-комментариях, желательно, чтобы линтер сообщил об этом.


Нам нужно не только получить правильный набор затронутых строк, но и расширить его так, чтобы найти побочные эффекты изменений во всём проекте.


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


diff режим в NoVerify


NoVerify имеет два режима работы: diff и full diff.


Обычный diff может найти предупреждения по файлам, которые были затронуты изменениями в рамках рассматриваемого диапазона коммитов. Это работает быстро, но хорошего анализа зависимостей изменений не происходит, поэтому новые предупреждения из незатронутых файлов мы найти не можем.


Full diff запускает анализатор дважды: один раз на старом коде, затем на новом коде, а потом фильтрует результаты. Это можно сравнить с генерацией baseline файла на лету с помощью того, что мы можем получить предыдущую версию кода через git. Ожидаемо, этот режим увеличивает время выполнения почти вдвое.


Изначальная схема работы предполагалась такая: на pre-push хуках запускается более быстрый анализ, в режиме обычного diff'а, чтобы люди получали обратную связь как можно быстрее. На CI агентах полный diff. В результате время от времени люди спрашивают, почему на агентах проблемы были найдены, а локально всё чисто. Удобнее иметь одинаковые процессы проверки, чтобы при прохождении pre-push хука была гарантия прохождения на CI фазы линтера.


full diff за один проход


Мы можем делать приближенный к full diff аналог, который не требует двойного анализа кода.


Допустим, в diff попала такая строка:


- class Foo {

Если мы попробуем классифицировать эту строку, то определим её как "Foo class deletion".


Каждая диагностика, которая может как-то зависеть от наличия класса, должна выдавать предупреждение, если имеется факт удаления этого класса.


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


Переименования не требуют дополнительной обработки. Мы считаем, что символ со старым именем был удалён, а с новым добавлен:


- class MemeManager {+ class SeniorMemeManager {

Основной сложностью будет правильно классифицировать строки изменений и выявить все их зависимости, не замедлив алгоритм до уровня full diff с двойным обходом кодовой базы.


Выводы


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


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


baseline diff
+ легко сделать эффективным + не требует хранить файлов
+ простота реализации и конфигурации + проще отличать новый код от старого
- нужно решать коллизии - сложно правильно приготовить

Могут существовать гибридные подходы: сначала берёшь baseline файл, а потом разрешаешь коллизии и вычисляешь смещения строк кода через git.


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

Подробнее..

Event2Mind для русского языка. Как мы обучили модель читать между строк и понимать намерения собеседника

18.06.2020 18:21:58 | Автор: admin
Умение модели распознавать намерения собеседника, то есть понимать зачем человек совершил то или иное действие, применимо в большом числе прикладных NLP-задач. К примеру, чат-ботам, голосовым помощникам и другим диалоговые системам это позволит эмоционально реагировать на высказывания собеседника, проявлять понимание, сочувствие и другие эмоции. Кроме того, задача распознавания намерения это еще один шаг на пути к пониманию человеческой речи (human understanding).



Уже было предпринято несколько попыток решить данную задачу в той или иной форме. Например, на NLP-progress публикуются последние достижения в области commonsense reasoning. Слабость большинства существующих моделей заключается в том, что в их основе лежит supervised подход, то есть им требуются большие размеченные датасеты для обучения. А в силу специфичности задачи разметка часто бывает весьма нестандартной и достаточно сложной.

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

В этом посте мы расскажем, как мы создали датасет для задачи Common Sense Reasoning в одной из ее возможных формулировок, предложенной в статье event2mind, а также адаптировали английскую модель event2mind от AllenNLP для русского языка. Для начала немного расскажем, что же из себя представляет задача Common Sense Reasoning.

На самом деле, правильнее было бы рассматривать это как целое направление задач, направленных на распознавание намерений и эмоций действующего лица. Единой формулировки у нее нет, и в данном посте мы возьмем за основу вот такой ее вариант, предложенный авторами event2mind: по короткому тексту в свободной форме, содержащему некоторое действие или событие (например, PersonX eats breakfast in the morning), определить намерения субъекта (X wants to satisfy hunger), его эмоции/реакции (X feels satiated, full) и возможные эмоции/реакции других участников события, если таковые присутствуют. Рисунок 1 это наглядно иллюстрирует.

Рисунок 1. Задача Commonsense Reasoning по короткому тексту-событию определить намерения, эмоции/реакции субъекта и эмоции/реакции окружающих


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

Данные, данные и еще раз данные


Итак, нашей задачей было собрать корпус размеченных текстов в свободной форме в формате, пригодном для обучения event2mind. Для простоты дальше мы будем называть такие короткие тексты просто событиями. При этом мы старались максимально расширить common sense reasoning кругозор модели, собрав события на самые разные темы из повседневной жизни.

Часть первая. Crowdsourced corpus


На первом этапе нам предстояло собрать достаточное количество сырых событий для последующей разметки. За основу мы взяли три источника данных:
  1. Короткие посерийные описания сериалов и мыльных опер. Мы вручную отобрали 50 сериалов с сюжетами из повседневной жизни, такие как Друзья, Секс в большом городе, Санта-Барбара, Универ, Кухня и другие. При этом мы старались выбирать сериалы о повседневной жизни с сюжетами на общие темы. Фантастика или профессиональные сериалы нам не подходили, так как они содержат очень много специальной лексики, и события там из своей специфичной области. Вы представляете, что может выучить модель, обученная на Докторе Хаусе или Звездном пути? Еще начнет подозревать у всех волчанку и сыпать рассказами о сражениях с инопланетянами.
  2. Краткие содержания книг. Суммарно нам удалось набрать краткие содержания 1512 книг.
  3. Тексты из SynTagRus, который является частью Русского Национального корпуса и содержит художественные тексты вместе с новостями.


На следующем шаге мы извлекли события из собранных текстов. События для тренировки event2mind можно определить как комбинацию глагольного предиката с входящими в составляющую глагола аргументами. Для поиска подобных паттернов был использован синтаксический парсер UdPipe, с помощью которого мы выделяли в текстах паттерны вида глагол + зависимые слова в синтаксическом дереве, как например на рисунке 2, которые удовлетворяли одному из следующих правил:

  • nsubj + root + obj
  • nsubj + root + iobj
  • nsubj + advmod + root
  • nsubj + root + case + obl
  • etc.


Рисунок 2. Синтаксические паттерны, использованные для извлечения событий из текстов



Отобранные события обезличиваются. По аналогии с оригинальным event2mind все действующие лица и именованные сущности были заменены на единообразные PersonX, а также PersonY и PersonZ, если в предложении упоминается более одного действующего лица. Для распознавания именованных сущностей (Named Entity Recognition) и дальнейшей замены мы вновь воспользовались UdPipe: в событиях, которые отвечают паттернам выше мы деперсонилизировали токены, помеченные тегами PROPN или PRONOUN. В завершении мы исключили события, которые не содержат одушевленных субъектов. Например, по этому критерию было отсеяно предложение Идет дождь. В итоге в корпус вошли только события с одушевленными именованными сущностями (person named entities).

После деперсонализации и фильтрации мы воспользовались частотным анализом и расстоянием Левенштейна для отобора наиболее распространенных событий и фильтрации нестандартных примеров, которые встретились лишь единожды. Во-первых, мы взяли все события, которые встретились в первоначальной выборке больше одного раза. Для оставшейся части данных мы посчитали попарные расстояния Левенштейна $L(phrase_1,phrase_2)$, ), отобрали пары, для которых оно не превосходило 5 и из каждой пары взяли более короткое предложение. При таком методе мы руководствовались следующим соображением: если для пары событий их расстояние Левенштейна мало (в данном случае порог 5), то эти предложения отличаются весьма незначительно, например, в роде глагола или прилагательного. Фактически это вариации одного и того же события. А более короткое предложения из пары мы выбирали потому, что оно скорее будет содержать начальные формы слов (они чаще короче, хотя и не всегда).

После сбора данных события предстояло разметить, выделив в них намерения PersonX, его эмоции/реакции, а также эмоции/реакции PersonY и PersonZ, если таковые присутствуют. Для этого мы создали задание в Яндекс.Толоке. Пример из него вы можете видеть на рисунке 3. Для каждого события мы спрашивали разметчиков:

  • содержит ли оно осмысленное событие;
  • можно ли по тексту события понять намерения действующего лица;
  • можно ли по событию понять эмоции/реакцию действующего лица;
  • может ли это событие вызвать реакцию окружающий.


Рисунок 3. Пример задания из Яндекс.Толоки



Для облегчения труда толокеров мы подготовили для них подсказки ответы пробной модели event2mind, обученной на переведенном английском корпусе event2mind. Так что им оставалось только верифицировать ответы черновой модели и по возможности предложить свои.

Тут стоит оговориться, что поскольку переведенные данные получились достаточно сырыми, то обученная на них модель оказалась слабой. До полноценной модели она не дотягивала, но в некоторых случаях ей достаточно точно удавалось угадывать эмоции и намерения субъектов.

В итоге нам удалось собрать 6756 событий на различные повседневные темы.

Часть 2. Translated English corpus


Помимо того, что мы использовали размеченный русский датасет, мы частично перевели английский корпус с помощью переводчика Google, а затем отфильтровали и откорректировали результат. Почему это было необходимо? Понятно же, что автоматический перевод несколько хуже исходно русских данных, размеченных вручную.

Дело в том, что разметка подобного датасета трудоемкое дело, требующее большого количества ресурсов, денег и средств. У нас просто не было возможности разметить корпус, по размерам сопоставимый с английским, который состоит из 46 тысяч примеров. Поскольку собранный на русском датасет оказался меньшего размера, мы решили оценить, хватит ли такого объема данных для обучения. Для этого мы обучили английскую модель на частях оригинального корпуса и измерили, как меняется качество в зависимости от размера обучающего датасета. Результаты приведены в таблице. Качество для намерений (intent) и эмоций/реакций (react) оценивалось, по аналогии с оригинальной статьей, по метрике recall@10 на валидации. recall@10 отражает долю случаев, когда истинный ответ golden standard попадает в топ-10 предсказаний модели. Метрика меняется от 0 до 1, чем больше, тем лучше.

Таблица 1. Зависимость качества английской модели от размера корпуса для обучения



Сразу можно сказать, что 5000 примеров недостаточно для полноценного обучения модели. Однако уже при 30000 примеров, loss и recall практически не отличаются от результатов на полном объеме данных. Получается, что размеченных нами 7000 примеров не хватает для обучения модели и необходимо каким-то способом увеличить размер обучающей выборки.

Для этого мы подготовили дополнительный корпус, полученный из английского с помощью автоматического перевода Google Переводчиком. Как уже отмечалось выше, при автоматическом переводе всего корпуса некоторые переводы оказывались некорректными или полностью теряли смысл. Поэтому мы отобрали ту часть английских данных, которая переводилась наиболее адекватно. Изначально английский корпус собран из нескольких источников: ROC Story training set, the GoogleSyntactic N-grams, the Spinn3r corpus и idioms. При этом предложения из некоторых источников оказались проще для перевода, чем из других. Например, адекватный перевод идиом без ручной правки оказался не под силу компьютеру. Поэтому мы взяли только примеры из ROC-story. По результатам оригинальной статьи (см. таблицу 2), у этого источника коэффициент согласованности аннотаторов (Cohen's kappa coefficient), равный 0.57. А это, скорее всего, свидетельствует о том, что события оттуда проще для понимания и разметки, а значит меньше подвержены ошибкам при переводе.

Таблица 2 Данные и Cohen's kappa coefficient для разных источников в английском корпусе


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

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

В итоге нам удалось набрать 23409 переведенных событий, а объем объединенного корпуса с учетом размеченной русской части составил 30165. Этого, как мы выяснили во время экспериментов, должно было хватить для обучения русской модели.

А теперь к экспериментам!


Итак, данные собраны, пора переходить к обучению модели и экспериментам. Модель event2mind представляет собой нейросетевую архитектуру вида encoder-decoder с одним энкодером и тремя декодарами для каждого вида предсказаний (см. рисунок 4): намерение субъекта, его эмоции/реакции и эмоции/реакции других участников события, если таковые имеются (subjects intent, subjectss reaction и others events participants reactions). Исходные предложения изначально векторизуются с помощью одного из методов векторных представлений слов (например, word2vec или fasttext) и кодируются с помощью энкодера в вектор $h^E\in \mathbb{R}^H$. А затем с помощью трех RNN декодеров генерируются предсказания. Благодаря этому модель может генерировать ответы даже для намерений и реакций, которые она до этого не видела.

Рисунок 4. Архитектура модели event2mind


Для экспериментов мы использовали объединенный корпус для русского языка, размеченную и переведенную части. А чтобы сделать распределение русских и переведенных примеров более равномерным, мы дополнительно перемешали данные. Отметим, что мы также попробовали обучить модель только на размеченных данных, но из-за маленького объема датасета, она показала очень плохие результаты. Мы протестировали различные слои в энкодере LSTM и GRU, а также попробовали различные векторные представления fasttext и word2vec с RusVectores. Результаты приведены в таблице 3, результаты по intentам и reactам, как и ранее считались по метрике recall@10.

Таблица 3. результаты моделей для русского языка, intent и react оценивались по recall@10


Итак, какие выводы можно сделать из результатов экспериментов? Во-первых, word2vec embeddings оказались немного лучше, чем fasttext. При этом fasttext embeddings, обученные на ruscorpora показали себя лучше обученных на araneum. Во-вторых, можно отметить, что при использовании word2vec, GRU в энкодере оказывается лучше LSTM. И наконец, лучшая модель (areneum word2vec + GRU) практически повторяет результаты для английского языка.

И напоследок посмотрим на реальные примеры!





Весьма неплохо! Намерения выглядят весьма правдоподобно и действительно отражают какие-то общечеловеческие понятия и знания. А вот с эмоциями/реакциями модель справилась несколько хуже, они получились более примитивными и однообразными. Возможно, это связано с тем, что эмоции зависят от большего объема текста и короткого текста-события недостаточно, чтобы правильно его определить.

Вместо заключения


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



Такой большой проект стал возможен, только благодаря совместным усилиям нашей команды. В адаптации event2mind для русского языка также принимали участие alenusch и onetwotrickster.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru