Мой второй ответ является анализ кода, и указывая на то, где я думаю, что вы сделали какие-то ошибки или перепутаны. Я предполагаю, что вы хотите воспроизвести график, аналогичный тому, который показан на стр. 864 вашего связанного PDF.
ROC-график, подобный этому на p864, представляет собой график, показывающий доступные компромиссы в вашей предсказательной модели между ложноположительными и истинными положительными показателями. Чтобы увидеть все возможные компромиссы, вам нужно посетить все точки данных, где порог будет иметь значение, и построить их ложные положительные и истинные положительные ставки.
Ваша первая точка смятения, похоже, заключается в том, что у вас есть «платная оценка голосов пользователей» вместо категории «истина/ложь». В примере в PDF есть p/n случаи, которые уже определены для построения ROC.
# results from a classifier
# index 0: users voting
# index 1: estimate from the system
results = [[5.0,4.8],[4.6,4.2],[4.3,2.2],[3.1,4.9],[1.3,2.6],[3.9,4.3],[1.9,2.4],[2.6,2.3]]
Так что я думаю, вы бы лучше с
results = [[true,4.8],[true,4.2],[true,2.2],[true,4.9],[false,2.6],[true,4.3],[false,2.4],[true,2.3]]
, прежде чем начать построить РПЦ.Было бы хорошо сделать это преобразование inline, но вам нужно разделить проблемы, связанные с тем, как вы генерируете тестовые данные, из вашего графика ROC - например, тот факт, что оценки пользователей и оценки оценки компьютеров находятся в одном масштабе, не имеет значения.
Что приводит к переменной threshold
. Вы можете использовать, например. 2.5
, чтобы преобразовать ваши данные пользователя, но это не влияет на ваш участок ROC. Фактически, чтобы получить полный график ROC, вам нужно проверить несколько значений порога, как они влияют на истинные и ложные положительные ставки.
# over a score of 2.5 an item is a positive one
threshold = 2.5
Это сортирует значения в обратном порядке, с деталями, набравших наибольшее количество баллов в первую очередь. Вы могли бы сделать это так или иначе, но для меня это означает, что вы хотите, чтобы начать на высоком пороге (где все ваши баллы предсказать false
), а в положении [0.0,0.0]
на графике
# sort by index 1, the estimate
l_sorted = results.sort { |a,b| b[1] <=> a[1] }
Следующий код выглядит достаточно точной, но на самом деле это просто суммирование теста позитивов и негативов, поэтому не следует возиться с понятиями порога:
# count the real positives and negatives
positives, negatives = 0, 0
positives, negatives = 0, 0
l_sorted.each do |item|
if item[0] >= threshold
positives += 1
else
negatives += 1
end
end
приятнее рубином способ положить ту же логику, если вы замените оценки пользователей с истинным/fasle значения в другом месте могут быть
positives = l_sorted.select { |item| item[0] }.count
negatives = l_sorted.count - positives
Это выглядит нормально, вы действительно начать в [0.0,0.0] с
fp, tp = 0, 0
# the array that holds the points
r = []
Однако, это выглядит как исходного порог
f_prev = -Float::INFINITY
так будет логично быть положительным Float::Infinity
, на мой взгляд, так что все ваши прогнозы изначально false
(hen ce fp
и tp
логически должны быть 0
, потому что нет p
разрешено вообще). Это не имеет значения, поскольку вы не используете значение.
Внутри цикла, что происходит это код отслеживания, что общее количество ложных срабатываний и истинные позитивы если бы порог был установлен чуть выше текущего элемента. Когда вы опускаете этот бар мимо групп предметов с одинаковой оценкой, они будут прогнозировать положительные значения (нет необходимости проверять это по сравнению с переменной threshold
, что вас сбивало с толку). Все, что вам нужно сделать, это отсортировать эти положительные значения в tp
или fp
. Проверка по сравнению с f_prev
просто помогает группировать похожие предметы, вы накладываете только одну точку, если 3 прогноза имеют одинаковую оценку.
# iterate over all items
l_sorted.each do |item|
if item[1]!=f_prev
# Plot a point, assuming all predictions with a score equal or lower than current
# item are thresholded out as negative.
r.push [fp/negatives.to_f,tp/positives.to_f]
f_prev = item[1]
end
# Assume the current prediction is now positive, and calculate how that affects the curve
# if the current test item is a real positive
# add to true positives, otherwise, it has become a false positve
if item[0]
tp += 1
else
fp += 1
end
end
# push the last point (1,1) to the array
r.push [fp/negatives.to_f,tp/positives.to_f]
Как и изменения теста, я удалил неточный комментарий («оценщик тоже правильно») - мы не осуждаем в этом коде ли оценка является «правильной» или нет для одного значения, мы просто видим, насколько хорошо он оценивает fp
против tp
в конкретной точке отсечки. Однопроходный процесс в отсортированном списке основан на том, что это будет небольшое поэтапное изменение от последней точки, построенной на основе изменений в fp
и tp
.
Это должно теперь перейти от [0.0,0.0]
к [1.0,1.0]
r.each do |point|
puts "(#{point[0].round(3)},#{point[1].round(3)})"
end
Я привык к классификаторам, являющихся 0 или 1, почему ваш индекс 0 балла вместо этого? Вы уверены, что вам нужна ROC для вашей проблемы, она больше похожа на регресс? Изменить: у меня только упрощенный код для области под ROC, а не для самой кривой. Это очень просто, но, вероятно, не то, что вам нужно. –
Я запустил код, который вы написали, без изменений, и получил коэффициент отзыва в 1.0 (индекс 1 результирующего массива). Вы имели в виду fp rate over 1.0? – galymzhan
thx для ваших комментариев! Нет, индекс 0 вызывает (по оси X), а индекс 1 - точность (по оси Y). Или я ошибаюсь? – 23tux