Как несколько строк кода могут творить настоящую магию

Этот пост основан на реализации PR-AUC, опубликованной Facebook AI Research в рамках проекта Detectron. Мне потребовалось некоторое время, чтобы понять, как всего несколько строк кода могут выполнить такую ​​сложную задачу. Позвольте мне поделиться своими мыслями.

В последнем посте мы рассмотрели теоретические основы PR-AUC. В этом посте мы углубим наше понимание, разобрав эффективную реализацию PR-AUC. Если вы еще не полностью знакомы с понятиями точности, полноты, TP, FP и т. д., пожалуйста, вернитесь к предыдущему сообщению.

Мы снова будем использовать упрощенный пример, содержащий всего 10 образцов. Для каждой выборки мы вычисляем выход (прогнозируемую достоверность) нейронной сети и помещаем его вместе с соответствующей меткой истинности в таблицу, см. Табл. 1. Затем мы сортируем записи в соответствии с прогнозируемой достоверностью, как в табл. 2.

Python code:
predictions = np.array([0.65,0.1,0.15,0.43,0.97,0.24,0.82,0.7,0.32,0.84])
labels = np.array([0, 0, 1, 0, 1, 1, 0, 1, 1, 1])
# sort the entries according to the predicted confidence
id = np.argsort(predictions)[::-1]
predictions = predictions[id]
labels = labels[id]

Если мы выберем наш порог между 1-й и 2-й записью (0,84 ‹ порог ‹ 0,97), все прогнозы выше порога считаются положительными (зеленый), а все ниже порога считаются отрицательными (красный):

Используя наземные метки истинности, мы можем легко вывести TP, FP, TN и FN для выбранного порога. Чтобы получить ТП, просто посчитайте, сколько единиц появляется в зеленых строках столбца «Ярлык» (левая таблица). Чтобы заставить TN считать 0 в красных строках столбца «Метка» и т. д.

Затем мы выбираем пороговое значение между 2-й и 3-й записью (0,84 ‹ порог ‹ 0,97) и повторяем процедуру.

Мы можем выполнить эту простую процедуру для всех строк в таблице. Если пока не совсем понятно, как работает процедура, приведем еще один пример. Мы выбираем порог между 8-й и 9-й записью (0,15 ‹ порог ‹ 0,24).

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

Кроме того, обратите внимание, что сумма TP и FP в правой таблице выше идентична записи ранга в левой таблице. Это неудивительно, так как в прошлом посте мы показали, что TP+FP — это общее количество положительных прогнозов для заданного порога. Это отличная новость, так как это означает, что для расчета точности нам фактически не нужно вычислять FP (!). Следовательно, точность может быть получена из приведенных выше таблиц путем деления совокупной суммы столбца «Метка» на записи столбца «Ранг».

Аналогичным образом обратите внимание, что сумма TP и FN в правой таблице выше всегда составляет 6. Опять же, это не должно удивлять, поскольку TP+FN — это общее количество фактических положительных образцов в наборе данных. Это число равно сумме всех записей в столбце Метка. Следовательно, для расчета полноты нам не нужно вычислять ни TN, ни FN (!).

Далее применим полученный рецепт ко всем элементам приведенной выше таблицы:

Python code:
cumsum = np.cumsum(labels)
rank = np.arange(len(cumsum)) + 1
Num = cumsum[-1]
prec = cumsum / rank
rec = cumsum / Num

Когда мы строим график зависимости точности от отзыва, мы наблюдаем зигзагообразный паттерн:

Теперь следует немного более сложный раздел, в котором я объясню, откуда взялся зигзагообразный паттерн. В конце этого раздела вы сможете качественно нарисовать кривую точности-отзыва, просто взглянув на (отсортированный) столбец «Метка». Это хороший навык, который позволяет вам получить еще более интуитивное представление о кривой точности-отзыва. Просто попробуйте несколько примеров для себя. Однако, если вас интересует только эффективная реализация PR-AUC, вы можете смело пропустить этот раздел.

Зигзагообразный шаблон имеет следующую основную причину: каждый раз, когда мы встречаем один или несколько нулей в столбце «Метка», совокупная сумма в столбце «TP» имеет то же значение, что и в предыдущей строке.

Поскольку отзыв равен TP, деленному на константу, значение отзыва также остается неизменным. Однако значения в столбце «Ранг» всегда увеличиваются на единицу при переходе на одну строку вниз. Поскольку точность равна TP, деленной на ранг, точность должна уменьшаться. Это объясняет резкое падение кривой точности-отзыва.

Напротив, каждый раз, когда мы встречаем одну или несколько единиц в столбце «Ярлыки», совокупная сумма в столбце «TP» увеличивается на единицу при переходе к следующей строке. Поскольку отзыв равен TP, деленному на константу, значение отзыва также должно увеличиваться.

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

где i =Rank — это просто индекс строки. Приведенное выше выражение эквивалентно:

Обратите внимание, что мы рассматриваем только случай, когда текущая запись (строка i) в столбце «Ярлык» имеет значение единица:

or

Подставив это выражение в верхнюю формулу, получим:

И наконец:

Это утверждение всегда выполняется, что можно тривиально увидеть в таблице ниже:

Обратите внимание, что равенство в приведенной выше формуле выполняется только до тех пор, пока в столбце «Метка» не встречается ноль. Следовательно, точность также остается постоянной (со значением 1,0), пока не встречаются нули.

Сторожевые значения

Значение Sentinel делает реализацию PR-AUC очень эффективной, как мы скоро увидим.

Давайте сначала рассмотрим особенность, видимую в приведенном выше примере. Когда первая запись в столбце «Метка» равна единице, первое значение отзыва больше нуля.

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

Обратите внимание, что если бы первая метка в нашем примере выше была нулевой, мы бы получили следующий график:

На этот раз мы начинаем с нулевого значения отзыва, однако соответствующее значение точности также равно нулю. Следовательно, мы получаем точку в начале кривой точности-отзыва. Также обратите внимание, что если бы первые 2 записи столбца «Ярлыки» были нулевыми, мы получили бы две перекрывающиеся точки в начале кривой точности-отзыва и так далее.

По этой причине не будет никакого вреда, если мы добавим дозорное значение (точность = 0,0 и полнота = 0,0) в качестве дополнительной нулевой строки в наших таблицах.

Для полноты мы также добавим контрольное значение (точность = 0,0 и полнота = 1,0) в конце таблицы. Обратите внимание, что значение отзыва в последней строке для определения всегда должно быть 1,0.

Python code:
mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))

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

Python code:
# compute the precision envelope
for i in range(mpre.size - 1, 0, -1):
    mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

Чтобы вычислить площадь и кривую точности-отзыва, мы разделим график с помощью прямоугольников (обратите внимание, что ширина прямоугольников не обязательно одинакова).

В нашем примере для описания площади требуется всего 6 прямоугольников, однако у нас есть 12 точек, определяющих кривую точности-отзыва. Как найти полезные моменты? Обратите внимание, что нам нужны только те точки, которые лежат в верхних левых углах каждого прямоугольника. Все остальные оставшиеся точки, к счастью, обладают тем свойством, что за ними (следующая строка в таблице) следует точка с тем же значением отзыва, посмотрите на красные точки без маленьких стрелок на рисунке выше, чтобы получить точку. Следовательно, в наших таблицах мы должны искать строки с другими значениями отзыва, чем в строках под ними.

Python code:
i = np.where(mrec[1:] != mrec[:-1])[0]

Обратите внимание, что повторяющиеся точки в начале координат (упомянутые ранее) также фильтруются этим методом. Будет сохранена только последняя точка в начале координат.

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

Python code:
pr_auc = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])

Больше чтений

https://neptune.ai/blog/f1-score-accuracy-roc-auc-pr-auc

Ссылка

Facebook Research Detectron
mAP (средняя средняя точность) для обнаружения объектов