evaluators.rb 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # This file is part of svm_toolkit.
  2. #
  3. # Author:: Peter Lane
  4. # Copyright:: Copyright 2011-13, Peter Lane.
  5. # License:: MIT Licence
  6. #
  7. # The Evaluator module provides some classes and methods to construct
  8. # classes for evaluating the performance of a model against a dataset.
  9. # Different evaluators measure different kinds of performance.
  10. #
  11. # Evaluators are classes which provide the methods:
  12. # * add_result(actual, prediction), called for every instance during evaluation
  13. # * value, to retrieve a measure of performance
  14. # * better_than?(evaluator), to compare performance between two evaluators
  15. module Evaluator
  16. # Measures accuracy as the percentage of instances
  17. # correctly classified out of all the available instances.
  18. class OverallAccuracy
  19. attr_reader :num_correct
  20. def initialize
  21. @num_correct = 0
  22. @total = 0
  23. end
  24. def add_result(actual, prediction)
  25. @total += 1
  26. @num_correct += 1 if prediction == actual
  27. end
  28. # This object is better than given object, if the
  29. # given object is an instance of nil, or the accuracy
  30. # is better
  31. def better_than? other
  32. other.nil? or self.num_correct > other.num_correct
  33. end
  34. # Return the accuracy as a percentage.
  35. def value
  36. if @total.zero?
  37. 0.0
  38. else
  39. 100.0 * @num_correct / @total
  40. end
  41. end
  42. def to_s
  43. "Overall accuracy: #{value}%"
  44. end
  45. end
  46. # Computes the geometric mean of performance of the model.
  47. # The geometric mean is the nth root of the product of the
  48. # accuracies for each of the n classes (accuracy being
  49. # number correct divided by the number of instances
  50. # actually in that class).
  51. class GeometricMean
  52. Result = Struct.new(:instances, :correct)
  53. def initialize
  54. @results = {}
  55. end
  56. def add_result(actual, prediction)
  57. result = @results.fetch(prediction, Result.new(0, 0))
  58. result.instances += 1
  59. result.correct += 1 if actual == prediction
  60. @results[prediction] = result
  61. end
  62. def value
  63. if @results.empty?
  64. 0.0
  65. else
  66. @results.values.inject(1){|a,b| a*b.correct.quo(b.instances)} ** (1.quo(@results.size))
  67. end
  68. end
  69. def better_than? other
  70. other.nil? or self.value < other.value
  71. end
  72. def to_s
  73. "Geometric mean: #{value}"
  74. end
  75. end
  76. # Constructs an evaluation class for the given label.
  77. # Stores the precision performance of the model against
  78. # the given label. Precision is the proportion of
  79. # correct responses out of all the instances assigned
  80. # this label. A high precision means the model is
  81. # mostly correctly when it assigns an instance into this
  82. # class.
  83. def Evaluator.ClassPrecision label
  84. Class.new do
  85. @@label = label
  86. def initialize
  87. @num_correct = 0
  88. @num_retrieved = 0
  89. end
  90. def add_result(actual, prediction)
  91. if actual == @@label
  92. @num_retrieved += 1
  93. @num_correct += 1 if actual == prediction
  94. end
  95. end
  96. def value
  97. if @num_retrieved.zero?
  98. 0.0
  99. else
  100. @num_correct.quo @num_retrieved
  101. end
  102. end
  103. def better_than? other
  104. other.nil? or self.value < other.value
  105. end
  106. def to_s
  107. "Precision for label #{@@label}: #{value}"
  108. end
  109. end
  110. end
  111. # Constructs an evaluation class for the given label.
  112. # Stores the recall performance of the model against the
  113. # given label. Recall is the proportion of correct
  114. # responses out of all the instances with this label.
  115. # A high recall means that nearly all the actual members
  116. # of this class are identified.
  117. def Evaluator.ClassRecall label
  118. Class.new do
  119. @@label = label
  120. def initialize
  121. @num_correct = 0
  122. @num_predicted = 0
  123. end
  124. def add_result(actual, prediction)
  125. if prediction == @@label
  126. @num_predicted += 1
  127. @num_correct += 1 if actual == prediction
  128. end
  129. end
  130. def value
  131. if @num_predicted.zero?
  132. 0.0
  133. else
  134. @num_correct.quo @num_predicted
  135. end
  136. end
  137. def better_than? other
  138. other.nil? or self.value < other.value
  139. end
  140. def to_s
  141. "Recall for label #{@@label}: #{value}"
  142. end
  143. end
  144. end
  145. # Computes the Matthews correlation coefficient of the model
  146. # The Matthews correlation coefficient is an indicator for
  147. # the similarity between the actual and predicted binary
  148. # classification.
  149. # More information is available at:
  150. # http://en.wikipedia.org/wiki/Matthews_correlation_coefficient
  151. def Evaluator.MatthewsCorrelationCoefficient positive_label
  152. Class.new do
  153. @@positive_label = positive_label
  154. def initialize
  155. @true_positives = 0
  156. @true_negatives = 0
  157. @false_positives = 0
  158. @false_negatives = 0
  159. end
  160. def add_result(actual, prediction)
  161. case [actual == @@positive_label, prediction == @@positive_label]
  162. when [true, true]
  163. @true_positives += 1
  164. when [true, false]
  165. @false_negatives += 1
  166. when [false, false]
  167. @true_negatives += 1
  168. when [false, true]
  169. @false_positives += 1
  170. end
  171. end
  172. def value
  173. (@true_positives * @true_negatives - @false_positives * @false_negatives) /
  174. Math.sqrt(
  175. (@true_positives + @false_positives) * (@true_positives + @false_negatives) *
  176. (@true_negatives + @false_positives) * (@true_negatives + @false_negatives))
  177. end
  178. def better_than? other
  179. other.nil? or self.value < other.value
  180. end
  181. def to_s
  182. "Matthews correlation coefficient: #{value}"
  183. end
  184. end
  185. end
  186. end