2013-04-18 6 views
1

В настоящее время я пытаюсь реализовать расчет кривой ROC в рубине. Я попытался преобразовать псевдокод из http://people.inf.elte.hu/kiss/13dwhdm/roc.pdf (см. 6-й сайт, глава 5, Алгоритм 1 «Эффективный метод для создания точек ROC») в код Ruby.Реализация Ruby для кривой ROC

Я разработал простой пример, но я всегда получаю значения за 1.0 для отзыва. Я думаю, что что-то неправильно понял или ошибся при программировании. Вот то, что я до сих пор GOR:

# 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]] 
# over a score of 2.5 an item is a positive one 
threshold = 2.5 
# 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 

fp, tp = 0, 0 
# the array that holds the points 
r = [] 
f_prev = -Float::INFINITY 

# iterate over all items 
l_sorted.each do |item| 
    # if the score of the former iteration is different, 
    # add another point to r 
    if item[1]!=f_prev 
    r.push [fp/negatives.to_f,tp/positives.to_f] 
    f_prev = item[1] 
    end 
    # if the current item is a real positive 
    # (user likes the item indeed, and estimater was also correct) 
    # add a true positive, otherwise, add a false positve 
    if item[0] >= threshold && item[1] >= threshold 
    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] 

r.each do |point| 
    puts "(#{point[0].round(3)},#{point[1].round(3)})" 
end 

На основе results массив массивов, код пытается вычислить точки. Я не уверен, что такое f_prev. Является ли в f_prev оценка хранимого классификатора или только если это true или false?

Было бы здорово, если бы кто-то мог быстро взглянуть на мой код и помочь мне найти мою ошибку. спасибо!

+0

Я привык к классификаторам, являющихся 0 или 1, почему ваш индекс 0 балла вместо этого? Вы уверены, что вам нужна ROC для вашей проблемы, она больше похожа на регресс? Изменить: у меня только упрощенный код для области под ROC, а не для самой кривой. Это очень просто, но, вероятно, не то, что вам нужно. –

+0

Я запустил код, который вы написали, без изменений, и получил коэффициент отзыва в 1.0 (индекс 1 результирующего массива). Вы имели в виду fp rate over 1.0? – galymzhan

+0

thx для ваших комментариев! Нет, индекс 0 вызывает (по оси X), а индекс 1 - точность (по оси Y). Или я ошибаюсь? – 23tux

ответ

1

Мой второй ответ является анализ кода, и указывая на то, где я думаю, что вы сделали какие-то ошибки или перепутаны. Я предполагаю, что вы хотите воспроизвести график, аналогичный тому, который показан на стр. 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

Вы абсолютно здоровы! Большое спасибо, вы мне очень помогли! – 23tux

1

Этот ответ неверен, так как он предположил из комментария OP, что для алгоритма требуется за элемент оценка ложноположительного и истинного положительного назначения. Фактически переменные tp и fp являются суммами отслеживания для всего набора данных и просто корректируются с учетом того, что текущий прогноз в цикле стал положительным. См. Мой другой ответ.


В этом блоке кода:

if item[0] >= threshold && item[1] >= threshold 
    tp += 1 
    else 
    fp += 1 
    end 

Вы, кажется, ничего, кроме «истинным положительным» считая как «ложный положительный».

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

if item[0] >= threshold && item[1] >= threshold 
    tp += 1 
    elsif item[0] < threshold && item[1] >= threshold 
    fp += 1 
    end 

или слегка DRY-эр

if item[1] >= threshold 
    if item[0] >= threshold 
     tp += 1 
    else 
     fp += 1 
    end 
    end 
+1

У меня были те же мысли. Однако не должно быть 'elsif item [0] = threshold', поскольку экземпляр должен быть отрицательным, а классификатор классифицирует его как положительный? – galymzhan

+0

@galymzhan: Да, извините, исправляя мой ответ, спасибо! –

+0

THX для вашего ответа. Некоторое время я играл с этой частью. Но что есть, если 'item [1] = threshold'? Разве это не приведет к увеличению ложных негативов, которые необходимы для вызова 'tp/(tp + fn)'? – 23tux

Смежные вопросы