ai4ruby 1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. data/README.rdoc +47 -0
  2. data/examples/classifiers/id3_data.csv +121 -0
  3. data/examples/classifiers/id3_example.rb +29 -0
  4. data/examples/classifiers/naive_bayes_data.csv +11 -0
  5. data/examples/classifiers/naive_bayes_example.rb +16 -0
  6. data/examples/classifiers/results.txt +31 -0
  7. data/examples/genetic_algorithm/genetic_algorithm_example.rb +37 -0
  8. data/examples/genetic_algorithm/travel_cost.csv +16 -0
  9. data/examples/neural_network/backpropagation_example.rb +67 -0
  10. data/examples/neural_network/patterns_with_base_noise.rb +68 -0
  11. data/examples/neural_network/patterns_with_noise.rb +66 -0
  12. data/examples/neural_network/training_patterns.rb +68 -0
  13. data/examples/neural_network/xor_example.rb +35 -0
  14. data/examples/som/som_data.rb +156 -0
  15. data/examples/som/som_multi_node_example.rb +22 -0
  16. data/examples/som/som_single_example.rb +24 -0
  17. data/lib/ai4r.rb +33 -0
  18. data/lib/ai4r/classifiers/classifier.rb +62 -0
  19. data/lib/ai4r/classifiers/hyperpipes.rb +118 -0
  20. data/lib/ai4r/classifiers/ib1.rb +121 -0
  21. data/lib/ai4r/classifiers/id3.rb +326 -0
  22. data/lib/ai4r/classifiers/multilayer_perceptron.rb +135 -0
  23. data/lib/ai4r/classifiers/naive_bayes.rb +259 -0
  24. data/lib/ai4r/classifiers/one_r.rb +110 -0
  25. data/lib/ai4r/classifiers/prism.rb +197 -0
  26. data/lib/ai4r/classifiers/zero_r.rb +73 -0
  27. data/lib/ai4r/clusterers/average_linkage.rb +59 -0
  28. data/lib/ai4r/clusterers/bisecting_k_means.rb +93 -0
  29. data/lib/ai4r/clusterers/centroid_linkage.rb +66 -0
  30. data/lib/ai4r/clusterers/clusterer.rb +61 -0
  31. data/lib/ai4r/clusterers/complete_linkage.rb +67 -0
  32. data/lib/ai4r/clusterers/diana.rb +139 -0
  33. data/lib/ai4r/clusterers/k_means.rb +126 -0
  34. data/lib/ai4r/clusterers/median_linkage.rb +61 -0
  35. data/lib/ai4r/clusterers/single_linkage.rb +194 -0
  36. data/lib/ai4r/clusterers/ward_linkage.rb +64 -0
  37. data/lib/ai4r/clusterers/ward_linkage_hierarchical.rb +31 -0
  38. data/lib/ai4r/clusterers/weighted_average_linkage.rb +61 -0
  39. data/lib/ai4r/data/data_set.rb +266 -0
  40. data/lib/ai4r/data/parameterizable.rb +64 -0
  41. data/lib/ai4r/data/proximity.rb +100 -0
  42. data/lib/ai4r/data/statistics.rb +77 -0
  43. data/lib/ai4r/experiment/classifier_evaluator.rb +95 -0
  44. data/lib/ai4r/genetic_algorithm/genetic_algorithm.rb +270 -0
  45. data/lib/ai4r/neural_network/backpropagation.rb +326 -0
  46. data/lib/ai4r/neural_network/hopfield.rb +149 -0
  47. data/lib/ai4r/som/layer.rb +68 -0
  48. data/lib/ai4r/som/node.rb +96 -0
  49. data/lib/ai4r/som/som.rb +155 -0
  50. data/lib/ai4r/som/two_phase_layer.rb +90 -0
  51. data/test/classifiers/hyperpipes_test.rb +84 -0
  52. data/test/classifiers/ib1_test.rb +78 -0
  53. data/test/classifiers/id3_test.rb +208 -0
  54. data/test/classifiers/multilayer_perceptron_test.rb +79 -0
  55. data/test/classifiers/naive_bayes_test.rb +43 -0
  56. data/test/classifiers/one_r_test.rb +62 -0
  57. data/test/classifiers/prism_test.rb +85 -0
  58. data/test/classifiers/zero_r_test.rb +49 -0
  59. data/test/clusterers/average_linkage_test.rb +51 -0
  60. data/test/clusterers/bisecting_k_means_test.rb +66 -0
  61. data/test/clusterers/centroid_linkage_test.rb +53 -0
  62. data/test/clusterers/complete_linkage_test.rb +57 -0
  63. data/test/clusterers/diana_test.rb +69 -0
  64. data/test/clusterers/k_means_test.rb +100 -0
  65. data/test/clusterers/median_linkage_test.rb +53 -0
  66. data/test/clusterers/single_linkage_test.rb +122 -0
  67. data/test/clusterers/ward_linkage_hierarchical_test.rb +61 -0
  68. data/test/clusterers/ward_linkage_test.rb +53 -0
  69. data/test/clusterers/weighted_average_linkage_test.rb +53 -0
  70. data/test/data/data_set_test.rb +96 -0
  71. data/test/data/proximity_test.rb +81 -0
  72. data/test/data/statistics_test.rb +65 -0
  73. data/test/experiment/classifier_evaluator_test.rb +76 -0
  74. data/test/genetic_algorithm/chromosome_test.rb +58 -0
  75. data/test/genetic_algorithm/genetic_algorithm_test.rb +81 -0
  76. data/test/neural_network/backpropagation_test.rb +82 -0
  77. data/test/neural_network/hopfield_test.rb +72 -0
  78. data/test/som/som_test.rb +97 -0
  79. metadata +168 -0
@@ -0,0 +1,135 @@
1
+ # Author:: Sergio Fierens (Implementation only)
2
+ # License:: MPL 1.1
3
+ # Project:: ai4r
4
+ # Url:: http://ai4r.rubyforge.org/
5
+ #
6
+ # You can redistribute it and/or modify it under the terms of
7
+ # the Mozilla Public License version 1.1 as published by the
8
+ # Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
9
+
10
+ require File.dirname(__FILE__) + '/../data/data_set.rb'
11
+ require File.dirname(__FILE__) + '/../classifiers/classifier'
12
+ require File.dirname(__FILE__) + '/../neural_network/backpropagation'
13
+
14
+ module Ai4r
15
+ module Classifiers
16
+
17
+ # = Introduction
18
+ #
19
+ # The idea behind the MultilayerPerceptron classifier is to
20
+ # train a Multilayer Perceptron neural network with the provided examples,
21
+ # and predict the class for new data items.
22
+ #
23
+ # = Parameters
24
+ #
25
+ # Use class method get_parameters_info to obtain details on the algorithm
26
+ # parameters. Use set_parameters to set values for this parameters.
27
+ # See Parameterizable module documentation.
28
+ #
29
+ # * :network_class => Neural network implementation class.
30
+ # By default: Ai4r::NeuralNetwork::Backpropagation.
31
+ # * :network_parameters => Parameters to be forwarded to the back end
32
+ # neural ntework.
33
+ # * :hidden_layers => Hidden layer structure. E.g. [8, 6] will generate
34
+ # 2 hidden layers with 8 and 6 neurons each. By default []
35
+ # * :training_iterations => How many times the training should be repeated.
36
+ # By default: 1000.
37
+ # :active_node_value => Default: 1
38
+ # :inactive_node_value => Default: 1
39
+ class MultilayerPerceptron < Classifier
40
+
41
+ attr_reader :data_set, :class_value, :network, :domains
42
+
43
+ parameters_info :network_class => "Neural network implementation class."+
44
+ "By default: Ai4r::NeuralNetwork::Backpropagation.",
45
+ :network_parameters => "parameters to be forwarded to the back end " +
46
+ "neural network.",
47
+ :hidden_layers => "Hidden layer structure. E.g. [8, 6] will generate " +
48
+ "2 hidden layers with 8 and 6 neurons each. By default []",
49
+ :training_iterations => "How many times the training should be " +
50
+ "repeated. By default: 1000",
51
+ :active_node_value => "Default: 1",
52
+ :inactive_node_value => "Default: 0"
53
+
54
+ def initialize
55
+ @network_class = Ai4r::NeuralNetwork::Backpropagation
56
+ @hidden_layers = []
57
+ @training_iterations = 500
58
+ @network_parameters = {}
59
+ @active_node_value = 1
60
+ @inactive_node_value = 0
61
+ end
62
+
63
+ # Build a new MultilayerPerceptron classifier. You must provide a DataSet
64
+ # instance as parameter. The last attribute of each item is considered as
65
+ # the item class.
66
+ def build(data_set)
67
+ data_set.check_not_empty
68
+ @data_set = data_set
69
+ @domains = @data_set.build_domains.collect {|domain| domain.to_a}
70
+ @outputs = @domains.last.length
71
+ @inputs = 0
72
+ @domains[0...-1].each {|domain| @inputs += domain.length}
73
+ @structure = [@inputs] + @hidden_layers + [@outputs]
74
+ @network = @network_class.new @structure
75
+ @training_iterations.times do
76
+ data_set.data_items.each do |data_item|
77
+ input_values = data_to_input(data_item[0...-1])
78
+ output_values = data_to_output(data_item.last)
79
+ @network.train(input_values, output_values)
80
+ end
81
+ end
82
+ return self
83
+ end
84
+
85
+ # You can evaluate new data, predicting its class.
86
+ # e.g.
87
+ # classifier.eval(['New York', '<30', 'F']) # => 'Y'
88
+ def eval(data)
89
+ input_values = data_to_input(data)
90
+ output_values = @network.eval(input_values)
91
+ return @domains.last[get_max_index(output_values)]
92
+ end
93
+
94
+ # Multilayer Perceptron Classifiers cannot generate
95
+ # human-readable rules.
96
+ def get_rules
97
+ return "raise 'Neural networks classifiers do not generate human-readable rules.'"
98
+ end
99
+
100
+ protected
101
+
102
+ def data_to_input(data_item)
103
+ input_values = Array.new(@inputs, @inactive_node_value)
104
+ accum_index = 0
105
+ data_item.each_index do |att_index|
106
+ att_value = data_item[att_index]
107
+ domain_index = @domains[att_index].index(att_value)
108
+ input_values[domain_index + accum_index] = @active_node_value
109
+ accum_index = @domains[att_index].length
110
+ end
111
+ return input_values
112
+ end
113
+
114
+ def data_to_output(data_item)
115
+ output_values = Array.new(@outputs, @inactive_node_value)
116
+ output_values[@domains.last.index(data_item)] = @active_node_value
117
+ return output_values
118
+ end
119
+
120
+ def get_max_index(output_values)
121
+ max_value = @inactive_node_value
122
+ max_index = 0
123
+ output_values.each_index do |output_index|
124
+ if max_value < output_values[output_index]
125
+ max_value = output_values[output_index]
126
+ max_index = output_index
127
+ end
128
+ end
129
+ return max_index
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,259 @@
1
+ # Author:: Thomas Kern
2
+ # License:: MPL 1.1
3
+ # Project:: ai4r
4
+ # Url:: http://ai4r.rubyforge.org/
5
+ #
6
+ # You can redistribute it and/or modify it under the terms of
7
+ # the Mozilla Public License version 1.1 as published by the
8
+ # Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
9
+
10
+ require File.dirname(__FILE__) + '/../data/data_set'
11
+ require File.dirname(__FILE__) + '/classifier'
12
+
13
+ module Ai4r
14
+ module Classifiers
15
+
16
+
17
+ # = Introduction
18
+ #
19
+ # This is an implementation of a Naive Bayesian Classifier without any
20
+ # specialisation (ie. for text classification)
21
+ # Probabilities P(a_i | v_j) are estimated using m-estimates, hence the
22
+ # m parameter as second parameter when isntantiating the class.
23
+ # The estimation looks like this:
24
+ #(n_c + mp) / (n + m)
25
+ #
26
+ # the variables are:
27
+ # n = the number of training examples for which v = v_j
28
+ # n_c = number of examples for which v = v_j and a = a_i
29
+ # p = a priori estimate for P(a_i | v_j)
30
+ # m = the equivalent sample size
31
+ #
32
+ # stores the conditional probabilities in an array named @pcp and in this form:
33
+ # @pcp[attributes][values][classes]
34
+ #
35
+ # This kind of estimator is useful when the training data set is relatively small.
36
+ # If the data set is big enough, set it to 0, which is also the default value
37
+ #
38
+ #
39
+ # For further details regarding Bayes and Naive Bayes Classifier have a look at those websites:
40
+ # http://en.wikipedia.org/wiki/Naive_Bayesian_classification
41
+ # http://en.wikipedia.org/wiki/Bayes%27_theorem
42
+ #
43
+ #
44
+ # = Parameters
45
+ #
46
+ # * :m => Optional. Default value is set to 0. It may be set to a value greater than 0 when
47
+ # the size of the dataset is relatively small
48
+ #
49
+ # = How to use it
50
+ #
51
+ # data = DataSet.new.load_csv_with_labels "bayes_data.csv"
52
+ # b = NaiveBayes.new.
53
+ # set_parameters({:m=>3}).
54
+ # build data
55
+ # b.eval(["Red", "SUV", "Domestic"])
56
+ #
57
+ class NaiveBayes < Classifier
58
+
59
+ parameters_info :m => "Default value is set to 0. It may be set to a value greater than " +
60
+ "0 when the size of the dataset is relatively small"
61
+
62
+ def initialize
63
+ @m = 0
64
+ @class_counts = []
65
+ @class_prob = [] # stores the probability of the classes
66
+ @pcc = [] # stores the number of instances divided into attribute/value/class
67
+ @pcp = [] # stores the conditional probabilities of the values of an attribute
68
+ @klass_index = {} # hashmap for quick lookup of all the used klasses and their indice
69
+ @values = {} # hashmap for quick lookup of all the values
70
+ end
71
+
72
+ # You can evaluate new data, predicting its category.
73
+ # e.g.
74
+ # b.eval(["Red", "SUV", "Domestic"])
75
+ # => 'No'
76
+ def eval(data)
77
+ prob = @class_prob.map {|cp| cp}
78
+ prob = calculate_class_probabilities_for_entry(data, prob)
79
+ index_to_klass(prob.index(prob.max))
80
+ end
81
+
82
+ # Calculates the probabilities for the data entry Data.
83
+ # data has to be an array of the same dimension as the training data minus the
84
+ # class column.
85
+ # Returns a map containint all classes as keys:
86
+ # {Class_1 => probability, Class_2 => probability2 ... }
87
+ # Probability is <= 1 and of type Float.
88
+ # e.g.
89
+ # b.get_probability_map(["Red", "SUV", "Domestic"])
90
+ # => {"Yes"=>0.4166666666666667, "No"=>0.5833333333333334}
91
+ def get_probability_map(data)
92
+ prob = @class_prob.map {|cp| cp}
93
+ prob = calculate_class_probabilities_for_entry(data, prob)
94
+ prob = normalize_class_probability prob
95
+ probability_map = {}
96
+ prob.each_with_index { |p, i| probability_map[index_to_klass(i)] = p }
97
+ return probability_map
98
+ end
99
+
100
+ # counts values of the attribute instances and calculates the probability of the classes
101
+ # and the conditional probabilities
102
+ # Parameter data has to be an instance of CsvDataSet
103
+ def build(data)
104
+ raise "Error instance must be passed" unless data.is_a?(DataSet)
105
+ raise "Data should not be empty" if data.data_items.length == 0
106
+
107
+ initialize_domain_data(data)
108
+ initialize_klass_index
109
+ initialize_pc
110
+ calculate_probabilities
111
+
112
+ return self
113
+ end
114
+
115
+ private
116
+
117
+ def initialize_domain_data(data)
118
+ @domains = data.build_domains
119
+ @data_items = data.data_items.map { |item| DataEntry.new(item[0...-1], item.last) }
120
+ @data_labels = data.data_labels[0...-1]
121
+ @klasses = @domains.last.to_a
122
+ end
123
+
124
+
125
+ # calculates the klass probability of a data entry
126
+ # as usual, the probability of the value is multiplied with every conditional
127
+ # probability of every attribute in condition to a specific class
128
+ # this is repeated for every class
129
+ def calculate_class_probabilities_for_entry(data, prob)
130
+ prob.each_with_index do |prob_entry, prob_index|
131
+ data.each_with_index do |att, index|
132
+ next if value_index(att, index).nil?
133
+ prob[prob_index] *= @pcp[index][value_index(att, index)][prob_index]
134
+ end
135
+ end
136
+ end
137
+
138
+ # normalises the array of probabilities so the sum of the array equals 1
139
+ def normalize_class_probability(prob)
140
+ prob_sum = sum(prob)
141
+ prob_sum > 0 ?
142
+ prob.map {|prob_entry| prob_entry / prob_sum } :
143
+ prob
144
+ end
145
+
146
+ # sums an array up; returns a number of type Float
147
+ def sum(array)
148
+ array.inject(0.0){|b, i| b+i}
149
+ end
150
+
151
+ # returns the name of the class when the index is found
152
+ def index_to_klass(index)
153
+ @klass_index.has_value?(index) ? @klass_index.index(index) : nil
154
+ end
155
+
156
+ # initializes @values and @klass_index; maps a certain value to a uniq index
157
+ def initialize_klass_index
158
+ @klasses.each_with_index do |dl, index|
159
+ @klass_index[dl] = index
160
+ end
161
+
162
+ @data_labels.each_with_index do |dl, index|
163
+ @values[index] = {}
164
+ @domains[index].each_with_index do |d, d_index|
165
+ @values[index][d] = d_index
166
+ end
167
+ end
168
+ end
169
+
170
+ # returns the index of a class
171
+ def klass_index(klass)
172
+ @klass_index[klass]
173
+ end
174
+
175
+ # returns the index of a value, depending on the attribute index
176
+ def value_index(value, dl_index)
177
+ @values[dl_index][value]
178
+ end
179
+
180
+ # builds an array of the form:
181
+ # array[attributes][values][classes]
182
+ def build_array(dl, index)
183
+ domains = Array.new(@domains[index].length)
184
+ domains.map do |p1|
185
+ pl = Array.new @klasses.length, 0
186
+ end
187
+ end
188
+
189
+ # initializes the two array for storing the count and conditional probabilities of
190
+ # the attributes
191
+ def initialize_pc
192
+ @data_labels.each_with_index do |dl, index|
193
+ @pcc << build_array(dl, index)
194
+ @pcp << build_array(dl, index)
195
+ end
196
+ end
197
+
198
+ # calculates the occurrences of a class and the instances of a certain value of a
199
+ # certain attribute and the assigned class.
200
+ # In addition to that, it also calculates the conditional probabilities and values
201
+ def calculate_probabilities
202
+ @klasses.each {|dl| @class_counts[klass_index(dl)] = 0}
203
+
204
+ calculate_class_probabilities
205
+ count_instances
206
+ calculate_conditional_probabilities
207
+ end
208
+
209
+ def calculate_class_probabilities
210
+ @data_items.each do |entry|
211
+ @class_counts[klass_index(entry.klass)] += 1
212
+ end
213
+
214
+ @class_counts.each_with_index do |k, index|
215
+ @class_prob[index] = k.to_f / @data_items.length
216
+ end
217
+ end
218
+
219
+ # counts the instances of a certain value of a certain attribute and the assigned class
220
+ def count_instances
221
+ @data_items.each do |item|
222
+ @data_labels.each_with_index do |dl, dl_index|
223
+ @pcc[dl_index][value_index(item[dl_index], dl_index)][klass_index(item.klass)] += 1
224
+ end
225
+ end
226
+ end
227
+
228
+ # calculates the conditional probability and stores it in the @pcp-array
229
+ def calculate_conditional_probabilities
230
+ @pcc.each_with_index do |attributes, a_index|
231
+ attributes.each_with_index do |values, v_index|
232
+ values.each_with_index do |klass, k_index|
233
+ @pcp[a_index][v_index][k_index] = (klass.to_f + @m * @class_prob[k_index]) / (@class_counts[k_index] + @m).to_f
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ #DataEntry stores the instance of the data entry
240
+ #the data is accessible via entries
241
+ #stores the class-column in the attribute klass and
242
+ #removes the column for the class-entry
243
+ class DataEntry
244
+ attr_accessor :klass, :entries
245
+
246
+ def initialize(attributes, klass)
247
+ @klass = klass
248
+ @entries = attributes
249
+ end
250
+
251
+ # wrapper method for the access to @entries
252
+ def [](index)
253
+ @entries[index]
254
+ end
255
+ end
256
+
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,110 @@
1
+ # Author:: Sergio Fierens (Implementation only)
2
+ # License:: MPL 1.1
3
+ # Project:: ai4r
4
+ # Url:: http://ai4r.rubyforge.org/
5
+ #
6
+ # You can redistribute it and/or modify it under the terms of
7
+ # the Mozilla Public License version 1.1 as published by the
8
+ # Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
9
+
10
+ require 'set'
11
+ require File.dirname(__FILE__) + '/../data/data_set'
12
+ require File.dirname(__FILE__) + '/../classifiers/classifier'
13
+
14
+ module Ai4r
15
+ module Classifiers
16
+
17
+ # = Introduction
18
+ #
19
+ # The idea of the OneR algorithm is identify the single
20
+ # attribute to use to classify data that makes
21
+ # fewest prediction errors.
22
+ # It generates rules based on a single attribute.
23
+ class OneR < Classifier
24
+
25
+ attr_reader :data_set, :rule
26
+
27
+ # Build a new OneR classifier. You must provide a DataSet instance
28
+ # as parameter. The last attribute of each item is considered as
29
+ # the item class.
30
+ def build(data_set)
31
+ data_set.check_not_empty
32
+ @data_set = data_set
33
+ if (data_set.num_attributes == 1)
34
+ @zero_r = ZeroR.new.build(data_set)
35
+ return self;
36
+ else
37
+ @zero_r = nil;
38
+ end
39
+ domains = @data_set.build_domains
40
+ @rule = nil
41
+ domains[1...-1].each_index do |attr_index|
42
+ rule = build_rule(@data_set.data_items, attr_index, domains)
43
+ @rule = rule if !@rule || rule[:correct] > @rule[:correct]
44
+ end
45
+ return self
46
+ end
47
+
48
+ # You can evaluate new data, predicting its class.
49
+ # e.g.
50
+ # classifier.eval(['New York', '<30', 'F']) # => 'Y'
51
+ def eval(data)
52
+ return @zero_r.eval(data) if @zero_r
53
+ attr_value = data[@rule[:attr_index]]
54
+ return @rule[:rule][attr_value]
55
+ end
56
+
57
+ # This method returns the generated rules in ruby code.
58
+ # e.g.
59
+ #
60
+ # classifier.get_rules
61
+ # # => if age_range == '<30' then marketing_target = 'Y'
62
+ # elsif age_range == '[30-50)' then marketing_target = 'N'
63
+ # elsif age_range == '[50-80]' then marketing_target = 'N'
64
+ # end
65
+ #
66
+ # It is a nice way to inspect induction results, and also to execute them:
67
+ # marketing_target = nil
68
+ # eval classifier.get_rules
69
+ # puts marketing_target
70
+ # # => 'Y'
71
+ def get_rules
72
+ return @zero_r.get_rules if @zero_r
73
+ sentences = []
74
+ attr_label = @data_set.data_labels[@rule[:attr_index]]
75
+ class_label = @data_set.data_labels.last
76
+ @rule[:rule].each_pair do |attr_value, class_value|
77
+ sentences << "#{attr_label} == '#{attr_value}' then #{class_label} = '#{class_value}'"
78
+ end
79
+ return "if " + sentences.join("\nelsif ") + "\nend"
80
+ end
81
+
82
+ protected
83
+
84
+ def build_rule(data_examples, attr_index, domains)
85
+ domain = domains[attr_index]
86
+ value_freq = Hash.new
87
+ domain.each do |attr_value|
88
+ value_freq[attr_value] = Hash.new { |hash, key| hash[key] = 0 }
89
+ end
90
+ data_examples.each do |data|
91
+ value_freq[data[attr_index]][data.last] = value_freq[data[attr_index]][data.last] + 1
92
+ end
93
+ rule = {}
94
+ correct_instances = 0
95
+ value_freq.each_pair do |attr, class_freq_hash|
96
+ max_freq = 0
97
+ class_freq_hash.each_pair do |class_value, freq|
98
+ if max_freq < freq
99
+ rule[attr] = class_value
100
+ max_freq = freq
101
+ end
102
+ end
103
+ correct_instances += max_freq
104
+ end
105
+ return {:attr_index => attr_index, :rule => rule, :correct => correct_instances}
106
+ end
107
+
108
+ end
109
+ end
110
+ end