stamina 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/CHANGELOG.md +22 -5
  2. data/LICENCE.md +2 -2
  3. data/bin/stamina +1 -7
  4. data/lib/stamina.rb +10 -19
  5. metadata +54 -333
  6. data/.gemtest +0 -0
  7. data/Gemfile +0 -2
  8. data/Gemfile.lock +0 -37
  9. data/Manifest.txt +0 -16
  10. data/README.md +0 -78
  11. data/Rakefile +0 -23
  12. data/example/adl/automaton.adl +0 -49
  13. data/example/adl/sample.adl +0 -53
  14. data/example/basic/characteristic_sample.adl +0 -32
  15. data/example/basic/target.adl +0 -9
  16. data/example/competition/31_test.adl +0 -1500
  17. data/example/competition/31_training.adl +0 -1759
  18. data/lib/stamina/abbadingo.rb +0 -2
  19. data/lib/stamina/abbadingo/random_dfa.rb +0 -48
  20. data/lib/stamina/abbadingo/random_sample.rb +0 -146
  21. data/lib/stamina/adl.rb +0 -298
  22. data/lib/stamina/automaton.rb +0 -1263
  23. data/lib/stamina/automaton/complete.rb +0 -36
  24. data/lib/stamina/automaton/equivalence.rb +0 -55
  25. data/lib/stamina/automaton/metrics.rb +0 -78
  26. data/lib/stamina/automaton/minimize.rb +0 -25
  27. data/lib/stamina/automaton/minimize/hopcroft.rb +0 -116
  28. data/lib/stamina/automaton/minimize/pitchies.rb +0 -64
  29. data/lib/stamina/automaton/strip.rb +0 -16
  30. data/lib/stamina/automaton/walking.rb +0 -363
  31. data/lib/stamina/classifier.rb +0 -52
  32. data/lib/stamina/command.rb +0 -45
  33. data/lib/stamina/command/abbadingo_dfa.rb +0 -81
  34. data/lib/stamina/command/abbadingo_samples.rb +0 -40
  35. data/lib/stamina/command/adl2dot.rb +0 -71
  36. data/lib/stamina/command/classify.rb +0 -48
  37. data/lib/stamina/command/help.rb +0 -27
  38. data/lib/stamina/command/infer.rb +0 -141
  39. data/lib/stamina/command/metrics.rb +0 -51
  40. data/lib/stamina/command/robustness.rb +0 -22
  41. data/lib/stamina/command/score.rb +0 -35
  42. data/lib/stamina/errors.rb +0 -23
  43. data/lib/stamina/ext/math.rb +0 -20
  44. data/lib/stamina/induction/blue_fringe.rb +0 -265
  45. data/lib/stamina/induction/commons.rb +0 -156
  46. data/lib/stamina/induction/rpni.rb +0 -186
  47. data/lib/stamina/induction/union_find.rb +0 -377
  48. data/lib/stamina/input_string.rb +0 -123
  49. data/lib/stamina/loader.rb +0 -1
  50. data/lib/stamina/markable.rb +0 -42
  51. data/lib/stamina/sample.rb +0 -267
  52. data/lib/stamina/scoring.rb +0 -213
  53. data/lib/stamina/utils.rb +0 -1
  54. data/lib/stamina/utils/decorate.rb +0 -81
  55. data/lib/stamina/version.rb +0 -14
  56. data/stamina.gemspec +0 -191
  57. data/stamina.noespec +0 -32
  58. data/tasks/debug_mail.rake +0 -78
  59. data/tasks/debug_mail.txt +0 -13
  60. data/tasks/gem.rake +0 -68
  61. data/tasks/spec_test.rake +0 -79
  62. data/tasks/unit_test.rake +0 -77
  63. data/tasks/yard.rake +0 -51
  64. data/test/stamina/abbadingo/random_dfa_test.rb +0 -16
  65. data/test/stamina/abbadingo/random_sample_test.rb +0 -78
  66. data/test/stamina/adl_test.rb +0 -516
  67. data/test/stamina/automaton/classifier_test.rb +0 -259
  68. data/test/stamina/automaton/complete_test.rb +0 -58
  69. data/test/stamina/automaton/equivalence_test.rb +0 -120
  70. data/test/stamina/automaton/metrics_test.rb +0 -36
  71. data/test/stamina/automaton/minimize/hopcroft_test.rb +0 -15
  72. data/test/stamina/automaton/minimize/minimize_test.rb +0 -55
  73. data/test/stamina/automaton/minimize/pitchies_test.rb +0 -15
  74. data/test/stamina/automaton/minimize/rice_edu_10.adl +0 -16
  75. data/test/stamina/automaton/minimize/rice_edu_10.min.adl +0 -13
  76. data/test/stamina/automaton/minimize/rice_edu_13.adl +0 -13
  77. data/test/stamina/automaton/minimize/rice_edu_13.min.adl +0 -7
  78. data/test/stamina/automaton/minimize/should_strip_1.adl +0 -8
  79. data/test/stamina/automaton/minimize/should_strip_1.min.adl +0 -6
  80. data/test/stamina/automaton/minimize/unknown_1.adl +0 -16
  81. data/test/stamina/automaton/minimize/unknown_1.min.adl +0 -12
  82. data/test/stamina/automaton/strip_test.rb +0 -36
  83. data/test/stamina/automaton/to_dot_test.rb +0 -64
  84. data/test/stamina/automaton/walking/dfa_delta_test.rb +0 -39
  85. data/test/stamina/automaton/walking_test.rb +0 -206
  86. data/test/stamina/automaton_additional_test.rb +0 -190
  87. data/test/stamina/automaton_test.rb +0 -1104
  88. data/test/stamina/exit.rb +0 -3
  89. data/test/stamina/induction/blue_fringe_test.rb +0 -83
  90. data/test/stamina/induction/induction_test.rb +0 -70
  91. data/test/stamina/induction/redblue_mergesamestatebug_expected.adl +0 -19
  92. data/test/stamina/induction/redblue_mergesamestatebug_pta.dot +0 -64
  93. data/test/stamina/induction/redblue_mergesamestatebug_sample.adl +0 -9
  94. data/test/stamina/induction/redblue_universal_expected.adl +0 -4
  95. data/test/stamina/induction/redblue_universal_sample.adl +0 -5
  96. data/test/stamina/induction/rpni_inria_expected.adl +0 -7
  97. data/test/stamina/induction/rpni_inria_sample.adl +0 -9
  98. data/test/stamina/induction/rpni_test.rb +0 -129
  99. data/test/stamina/induction/rpni_test_pta.dot +0 -22
  100. data/test/stamina/induction/rpni_universal_expected.adl +0 -4
  101. data/test/stamina/induction/rpni_universal_sample.adl +0 -4
  102. data/test/stamina/induction/union_find_test.rb +0 -124
  103. data/test/stamina/input_string_test.rb +0 -323
  104. data/test/stamina/markable_test.rb +0 -70
  105. data/test/stamina/randdfa.adl +0 -66
  106. data/test/stamina/sample.adl +0 -4
  107. data/test/stamina/sample_classify_test.rb +0 -149
  108. data/test/stamina/sample_test.rb +0 -290
  109. data/test/stamina/scoring_test.rb +0 -63
  110. data/test/stamina/small_dfa.dot +0 -16
  111. data/test/stamina/small_dfa.gif +0 -0
  112. data/test/stamina/small_nfa.dot +0 -18
  113. data/test/stamina/small_nfa.gif +0 -0
  114. data/test/stamina/stamina_test.rb +0 -80
  115. data/test/stamina/utils/decorate_test.rb +0 -65
  116. data/test/test_all.rb +0 -7
@@ -1,123 +0,0 @@
1
- module Stamina
2
- #
3
- # An input string is a sequence of input symbols (symbols being letters appearing
4
- # on automaton edges) labeled as positive, negative or unlabeled (provided for test
5
- # samples and query strings).
6
- #
7
- # This class include the Enumerable module, that allows reasoning about
8
- # ordered symbols.
9
- #
10
- # == Detailed API
11
- class InputString
12
- include Enumerable
13
-
14
- #
15
- # Creates an input string from symbols and positive or negative labeling.
16
- #
17
- # Arguments:
18
- # - symbols: When an array is provided, it is duplicated by default to be kept
19
- # internally. Set dup to false to avoid duplicating it (in both cases, the
20
- # internal array will be freezed). When a String is provided, symbols array
21
- # is created using <tt>symbols.split(' ')</tt> and then freezed. _dup_ is
22
- # ignored in the case.
23
- # - The positive argument may be true (positive string), false (negative one)
24
- # or nil (unlabeled).
25
- #
26
- # Raises:
27
- # - ArgumentError if symbols is not an Array nor a String.
28
- #
29
- def initialize(symbols, positive, dup=true)
30
- raise(ArgumentError,
31
- "Input string expects an Array or a String: #{symbols} received",
32
- caller) unless Array===symbols or String===symbols
33
- @symbols = case symbols
34
- when String
35
- symbols.split(' ').freeze
36
- when Array
37
- (dup ? symbols.dup : symbols).freeze
38
- end
39
- @positive = positive
40
- end
41
-
42
- #
43
- # Checks if this input string is empty (aka lambda, i.e. contains no symbol).
44
- #
45
- def empty?() @symbols.empty? end
46
- alias :lambda? :empty?
47
-
48
- #
49
- # Returns the string size, i.e. number of its symbols.
50
- #
51
- def size() @symbols.size end
52
-
53
- #
54
- # Returns the exact label of this string, being true (positive string)
55
- # false (negative string) or nil (unlabeled)
56
- #
57
- def label() @positive end
58
-
59
- #
60
- # Returns true if this input string is positively labeled, false otherwise.
61
- #
62
- def positive?() @positive==true end
63
-
64
- #
65
- # Returns true if this input string is negatively labeled, false otherwise.
66
- #
67
- def negative?() @positive==false end
68
-
69
- #
70
- # Returns true if this input string unlabeled.
71
- #
72
- def unlabeled?() @positive.nil? end
73
-
74
- # Copies and returns the same string, but switch the positive flag. This
75
- # method returns self if it is unlabeled.
76
- def negate
77
- return self if unlabeled?
78
- InputString.new(@symbols, !@positive, false)
79
- end
80
-
81
- #
82
- # Returns an array with symbols of this string. Returned array may not be
83
- # modified (it is freezed).
84
- #
85
- def symbols() @symbols end
86
-
87
- #
88
- # Yields the block with each string symbol, in order. Has no effect without
89
- # block.
90
- #
91
- def each() @symbols.each {|s| yield s if block_given? } end
92
-
93
- #
94
- # Checks equality with another InputString. Returns true if strings have same
95
- # sequence of symbols and same labeling, false otherwise. Returns nil if _o_
96
- # is not an InputString.
97
- #
98
- def ==(o)
99
- return nil unless InputString===o
100
- label == o.label and @symbols == o.symbols
101
- end
102
- alias :eql? :==
103
-
104
- #
105
- # Computes a hash code for this string.
106
- #
107
- def hash
108
- @symbols.hash + 37*positive?.hash
109
- end
110
-
111
- #
112
- # Prints this string in ADL.
113
- #
114
- def to_adl
115
- str = (unlabeled? ? '?' : (positive? ? '+ ' : '- '))
116
- str << @symbols.join(' ')
117
- str
118
- end
119
- alias :to_s :to_adl
120
- alias :inspect :to_adl
121
-
122
- end # class InputString
123
- end # module Stamina
@@ -1 +0,0 @@
1
- require "quickl"
@@ -1,42 +0,0 @@
1
- module Stamina
2
- #
3
- # Allows any object to be markable with user-data.
4
- #
5
- # This module is expected to be included by classes that want to implement the
6
- # Markable design pattern. Moreover, if the instances of the including class
7
- # respond to <tt>state_changed</tt>, this method is automatically invoked when
8
- # marks change. This method is used by <tt>automaton</tt> in order to make it
9
- # possible to track changes and check modified automata for consistency.
10
- #
11
- # == Detailed API
12
- module Markable
13
-
14
- #
15
- # Returns user-value associated to _key_, nil if no such key in user-data.
16
- #
17
- def [](key) @data[key] end
18
-
19
- #
20
- # Associates _value_ to _key_ in user-data. Overrides previous value if
21
- # present.
22
- #
23
- def []=(key,value)
24
- oldvalue = @data[key]
25
- @data[key] = value
26
- state_changed(:loaded_pair, [key,oldvalue,value]) if self.respond_to? :state_changed
27
- end
28
-
29
- # Removes a mark
30
- def remove_mark(key)
31
- oldvalue = @data[key]
32
- @data.delete(key)
33
- state_changed(:loaded_pair, [key,oldvalue,nil]) if self.respond_to? :state_changed
34
- end
35
-
36
- # Extracts the copy of attributes which can subsequently be modified.
37
- def data
38
- @data.nil? ? {} : @data.dup
39
- end
40
-
41
- end # module Markable
42
- end # module Stamina
@@ -1,267 +0,0 @@
1
- module Stamina
2
-
3
- #
4
- # A sample as an ordered collection of InputString labeled as positive or negative.
5
- #
6
- # == Tips and tricks
7
- # - loading samples from disk is easy thanks to ADL !
8
- #
9
- # == Detailed API
10
- class Sample
11
- include Enumerable
12
-
13
- # Number of strings in the sample
14
- attr_reader :size
15
-
16
- # Number of positive strings in the sample
17
- attr_reader :positive_count
18
-
19
- # Number of negative strings in the sample
20
- attr_reader :negative_count
21
-
22
- #
23
- # Creates an empty sample and appends it with args, by calling Sample#<< on
24
- # each of them.
25
- #
26
- def self.[](*args) Sample.new << args end
27
-
28
- #
29
- # Creates an empty sample.
30
- #
31
- def initialize(strings = nil)
32
- @strings = []
33
- @size, @positive_count, @negative_count = 0, 0, 0
34
- strings.each{|s| self << s } unless strings.nil?
35
- end
36
-
37
- #
38
- # Returns true if this sample does not contain any string,
39
- # false otherwise.
40
- #
41
- def empty?()
42
- @size==0
43
- end
44
-
45
- #
46
- # Adds a string to the sample. The _str_ argument may be an InputString instance,
47
- # a String (parsed using ADL), a Sample instance (all strings are added) or an
48
- # Array (recurses on each element).
49
- #
50
- # Raises an InconsistencyError if the same string already exists with the
51
- # opposite label. Raises an ArgumentError if the _str_ argument is not recognized.
52
- #
53
- def <<(str)
54
- case str
55
- when InputString
56
- #raise(InconsistencyError, "Inconsistent sample on #{str}", caller) if self.include?(str.negate)
57
- @size += 1
58
- str.positive? ? (@positive_count += 1) : (@negative_count += 1)
59
- @strings << str
60
- when String
61
- self << ADL::parse_string(str)
62
- when Sample
63
- str.each {|s| self << s}
64
- when Array
65
- str.each {|s| self << s}
66
- else
67
- raise(ArgumentError, "#{str} is not a valid argument.", caller)
68
- end
69
- self
70
- end
71
-
72
- #
73
- # Returns true if a given string is included in the sample, false otherwise.
74
- # This method allows same flexibility as << for the _str_ argument.
75
- #
76
- def include?(str)
77
- case str
78
- when InputString
79
- @strings.include?(str)
80
- when String
81
- include?(ADL::parse_string(str))
82
- when Array
83
- str.each {|s| return false unless include?(s)}
84
- true
85
- when Sample
86
- str.each {|s| return false unless include?(s)}
87
- true
88
- else
89
- raise(ArgumentError, "#{str} is not a valid argument.", caller)
90
- end
91
- end
92
-
93
- #
94
- # Compares with another sample _other_, which is required to be a Sample
95
- # instance. Returns true if the two samples contains the same strings (including
96
- # labels), false otherwise.
97
- #
98
- def ==(other)
99
- include?(other) and other.include?(self)
100
- end
101
- alias :eql? :==
102
-
103
- #
104
- # Computes an hash code for this sample.
105
- #
106
- def hash
107
- self.inject(37){|memo,str| memo + 17*str.hash}
108
- end
109
-
110
- #
111
- # Yields the block with each string. This method has no effect if no
112
- # block is given.
113
- #
114
- def each
115
- return unless block_given?
116
- @strings.each {|str| yield str}
117
- end
118
-
119
- #
120
- # Yields the block with each positive string. This method has no effect if no
121
- # block is given.
122
- #
123
- def each_positive
124
- return unless block_given?
125
- each {|str| yield str if str.positive?}
126
- end
127
-
128
- #
129
- # Returns an enumerator on positive strings.
130
- #
131
- def positive_enumerator
132
- if RUBY_VERSION >= "1.9"
133
- Enumerator.new(self, :each_positive)
134
- else
135
- Enumerable::Enumerator.new(self, :each_positive)
136
- end
137
- end
138
-
139
- #
140
- # Yields the block with each negative string. This method has no effect if no
141
- # block is given.
142
- #
143
- def each_negative
144
- each {|str| yield str if str.negative?}
145
- end
146
-
147
- #
148
- # Returns an enumerator on negative strings.
149
- #
150
- def negative_enumerator
151
- if RUBY_VERSION >= "1.9"
152
- Enumerator.new(self, :each_negative)
153
- else
154
- Enumerable::Enumerator.new(self, :each_negative)
155
- end
156
- end
157
-
158
- #
159
- # Checks if the sample is correctly classified by a given classifier
160
- # (expected to include the Stamina::Classfier module).
161
- # Unlabeled strings are simply ignored.
162
- #
163
- def correctly_classified_by?(classifier)
164
- classifier.correctly_classify?(self)
165
- end
166
-
167
- #
168
- # Computes and returns the binary signature of the sample. The signature
169
- # is a String having one character for each string in the sample. A '1'
170
- # is used for positive strings, '0' for negative ones and '?' for unlabeled.
171
- #
172
- def signature
173
- signature = ''
174
- each do |str|
175
- signature << (str.unlabeled? ? '?' : str.positive? ? '1' : '0')
176
- end
177
- signature
178
- end
179
-
180
- #
181
- # Takes only a given proportion of this sample and returns it as a new Sample.
182
- #
183
- def take(proportion = 0.5)
184
- taken = Stamina::Sample.new
185
- each_positive{|s| taken << s if Kernel.rand < proportion}
186
- each_negative{|s| taken << s if Kernel.rand < proportion}
187
- taken
188
- end
189
-
190
- #
191
- # Prints an ADL description of this sample on the buffer.
192
- #
193
- def to_adl(buffer="")
194
- self.inject(buffer) {|memo,str| memo << "\n" << str.to_adl}
195
- end
196
- alias :to_s :to_adl
197
- alias :inspect :to_adl
198
-
199
- #
200
- # Converts a Sample to an (augmented) prefix tree acceptor. This method ensures
201
- # that the states of the PTA are in lexical order, according to the <code><=></code>
202
- # operator defined on symbols. States reached by negative strings are tagged as
203
- # non accepting and error.
204
- #
205
- def self.to_pta(sample)
206
- thepta = Automaton.new do |pta|
207
- initial_state = add_state(:initial => true, :accepting => false)
208
-
209
- # Fill the PTA with each string
210
- sample.each do |str|
211
- # split string using the dfa
212
- parsed, reached, remaining = pta.dfa_split(str, initial_state)
213
-
214
- # remaining symbols are not empty -> build the PTA
215
- unless remaining.empty?
216
- remaining.each do |symbol|
217
- newone = pta.add_state(:initial => false, :accepting => false, :error => false)
218
- pta.connect(reached, newone, symbol)
219
- reached = newone
220
- end
221
- end
222
-
223
- # flag state
224
- str.positive? ? reached.accepting! : reached.error!
225
-
226
- # check consistency, should not arrive as Sample does not allow
227
- # inconsistencies. Should appear only if _sample_ is not a Sample
228
- # instance but some other enumerable.
229
- raise(InconsistencyError, "Inconsistent sample on #{str}", caller)\
230
- if (reached.error? and reached.accepting?)
231
- end
232
-
233
- # Reindex states by applying BFS
234
- to_index, index = [initial_state], 0
235
- until to_index.empty?
236
- state = to_index.shift
237
- state[:__index__] = index
238
- state.out_edges.sort{|e,f| e.symbol<=>f.symbol}.each{|e| to_index << e.target}
239
- index += 1
240
- end
241
- end
242
-
243
- # Now we rebuild a fresh one with states in order.
244
- # This look more efficient that reordering states of the PTA
245
- Automaton.new do |ordered|
246
- ordered.add_n_states(thepta.state_count)
247
- thepta.each_state do |pta_state|
248
- source = ordered.ith_state(pta_state[:__index__])
249
- source.initial! if pta_state.initial?
250
- source.accepting! if pta_state.accepting?
251
- source.error! if pta_state.error?
252
- pta_state.out_edges.each do |e|
253
- target = ordered.ith_state(e.target[:__index__])
254
- ordered.connect(source, target, e.symbol)
255
- end
256
- end
257
- end
258
-
259
- end
260
-
261
- # Convenient shortcut for Sample.to_pta(sample_instance)
262
- def to_pta
263
- Sample.to_pta(self)
264
- end
265
-
266
- end # class Sample
267
- end # module Stamina
@@ -1,213 +0,0 @@
1
- module Stamina
2
- #
3
- # Provides utility methods for scoring binary classifiers from signatures
4
- #
5
- module Scoring
6
-
7
- #
8
- # From the signatures of a learned model and a actual, returns an object
9
- # responding to all instance methods defined in the Scoring module.
10
- #
11
- def self.scoring(learned, actual, max_size=nil)
12
- unless learned.size==actual.size
13
- raise ArgumentError, "Signatures must be of same size (#{learned.size} vs. #{actual.size})"
14
- end
15
- max_size ||= learned.size
16
- max_size = learned.size if max_size > learned.size
17
- tp, fn, fp, tn = 0, 0, 0, 0
18
- (0...max_size).each do |i|
19
- positive, labeled_as = actual[i..i]=='1', learned[i..i]=='1'
20
- if positive==labeled_as
21
- positive ? (tp += 1) : (tn += 1)
22
- else
23
- positive ? (fn += 1) : (fp += 1)
24
- end
25
- end
26
- measures = { :true_positive => tp,
27
- :true_negative => tn,
28
- :false_positive => fp,
29
- :false_negative => fn }
30
- measures.extend(Scoring)
31
- measures
32
- end
33
-
34
- #
35
- # Returns the number of positive strings correctly labeled as positive
36
- #
37
- def true_positive
38
- self[:true_positive]
39
- end
40
-
41
- #
42
- # Returns the number of negative strings correctly labeled as negative.
43
- #
44
- def true_negative
45
- self[:true_negative]
46
- end
47
-
48
- #
49
- # Returns the number of negative strings incorrectly labeled as positive.
50
- #
51
- def false_positive
52
- self[:false_positive]
53
- end
54
-
55
- #
56
- # Returns the number of positive strings incorrectly labeled as negative.
57
- #
58
- def false_negative
59
- self[:false_negative]
60
- end
61
-
62
- #
63
- # Returns the percentage of positive predictions that are correct
64
- #
65
- def precision
66
- true_positive.to_f/(true_positive + false_positive)
67
- end
68
- alias :positive_predictive_value :precision
69
-
70
- #
71
- # Returns the percentage of true negative over all negative
72
- #
73
- def negative_predictive_value
74
- true_negative.to_f / (true_negative + false_negative)
75
- end
76
-
77
- #
78
- # Returns the percentage of positive strings that were predicted as being
79
- # positive
80
- #
81
- def recall
82
- true_positive.to_f / (true_positive + false_negative)
83
- end
84
- alias :sensitivity :recall
85
- alias :true_positive_rate :recall
86
-
87
- #
88
- # Returns the percentage of negative strings that were predicted as being
89
- # negative
90
- #
91
- def specificity
92
- true_negative.to_f / (true_negative + false_positive)
93
- end
94
- alias :true_negative_rate :specificity
95
-
96
- #
97
- # Returns the percentage of false positives
98
- #
99
- def false_positive_rate
100
- false_positive.to_f / (false_positive + true_negative)
101
- end
102
-
103
- #
104
- # Returns the percentage of false negatives
105
- #
106
- def false_negative_rate
107
- false_negative.to_f / (true_positive + false_negative)
108
- end
109
-
110
- #
111
- # Returns the likelihood that a predicted positive is an actual positive
112
- #
113
- def positive_likelihood
114
- sensitivity / (1.0 - specificity)
115
- end
116
-
117
- #
118
- # Returns the likelihood that a predicted negative is an actual negative
119
- #
120
- def negative_likelihood
121
- (1.0 - sensitivity) / specificity
122
- end
123
-
124
- #
125
- # Returns the percentage of predictions that are correct
126
- #
127
- def accuracy
128
- num = (true_positive + true_negative).to_f
129
- den = (true_positive + true_negative + false_positive + false_negative)
130
- num / den
131
- end
132
-
133
- #
134
- # Returns the error rate
135
- #
136
- def error_rate
137
- num = (false_positive + false_negative).to_f
138
- den = (true_positive + true_negative + false_positive + false_negative)
139
- num / den
140
- end
141
-
142
- #
143
- # Returns the harmonic mean between precision and recall
144
- #
145
- def f_measure
146
- 2.0 * (precision * recall) / (precision + recall)
147
- end
148
-
149
- #
150
- # Returns the balanced classification rate (arithmetic mean between
151
- # sensitivity and specificity)
152
- #
153
- def balanced_classification_rate
154
- 0.5 * (sensitivity + specificity)
155
- end
156
- alias :bcr :balanced_classification_rate
157
-
158
- #
159
- # Returns the balanced error rate (1 - bcr)
160
- #
161
- def balanced_error_rate
162
- 1.0 - balanced_classification_rate
163
- end
164
- alias :ber :balanced_error_rate
165
-
166
- #
167
- # Returns the harmonic mean between sensitivity and specificity
168
- #
169
- def harmonic_balanced_classification_rate
170
- 2.0 * (sensitivity * specificity) / (sensitivity + specificity)
171
- end
172
- alias :hbcr :harmonic_balanced_classification_rate
173
- alias :harmonic_bcr :harmonic_balanced_classification_rate
174
-
175
- MEASURES = [
176
- :false_positive, :false_negative,
177
- :true_positive, :true_negative,
178
- :accuracy, :error_rate,
179
- :precision, :recall, :f_measure,
180
- :false_positive_rate, :false_negative_rate,
181
- :true_positive_rate, :true_negative_rate,
182
- :positive_predictive_value, :negative_predictive_value,
183
- :sensitivity, :specificity,
184
- :positive_likelihood, :negative_likelihood,
185
- :balanced_classification_rate, :balanced_error_rate, :harmonic_bcr
186
- ]
187
-
188
- def to_h
189
- h = {}
190
- MEASURES.each do |m|
191
- h[m] = self.send(m.to_sym)
192
- end
193
- h
194
- end
195
-
196
- def to_s
197
- s = ""
198
- MEASURES.each do |m|
199
- vals = case val = self.send(m.to_sym)
200
- when Integer
201
- "%s" % val
202
- when Float
203
- "%.5f" % val
204
- else
205
- "%s" % val
206
- end
207
- s += "%30s: %10s\n" % [m.to_s, vals]
208
- end
209
- s
210
- end
211
-
212
- end # module Scoring
213
- end # module Stamina