cabalist 0.0.3 → 0.0.4

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 (39) hide show
  1. data/.yardopts +1 -0
  2. data/cabalist.gemspec +4 -1
  3. data/lib/cabalist.rb +7 -2
  4. data/lib/cabalist/configuration.rb +17 -1
  5. data/lib/cabalist/frontend.rb +52 -5
  6. data/lib/cabalist/model_additions.rb +483 -82
  7. data/lib/cabalist/railtie.rb +11 -1
  8. data/lib/cabalist/version.rb +4 -1
  9. data/lib/cabalist/views/classifier.haml +4 -2
  10. data/lib/cabalist/views/index.haml +21 -16
  11. data/lib/generators/cabalist/classifier/classifier_generator.rb +9 -0
  12. data/lib/generators/cabalist/install/install_generator.rb +7 -1
  13. data/lib/generators/cabalist/install/templates/public/stylesheets/cabalist.css +55 -1
  14. data/lib/tasks/retrain.rake +21 -0
  15. data/spec/cabalist/model_additions_spec.rb +15 -0
  16. data/spec/cabalist/non_cabalist_model_spec.rb +95 -0
  17. data/spec/spec_helper.rb +14 -0
  18. metadata +32 -38
  19. data/doc/Cabalist.html +0 -257
  20. data/doc/Cabalist/ClassifierGenerator.html +0 -320
  21. data/doc/Cabalist/Configuration.html +0 -404
  22. data/doc/Cabalist/Frontend.html +0 -152
  23. data/doc/Cabalist/InstallGenerator.html +0 -282
  24. data/doc/Cabalist/ModelAdditions.html +0 -1583
  25. data/doc/Cabalist/Railtie.html +0 -127
  26. data/doc/_index.html +0 -158
  27. data/doc/class_list.html +0 -47
  28. data/doc/css/common.css +0 -1
  29. data/doc/css/full_list.css +0 -53
  30. data/doc/css/style.css +0 -320
  31. data/doc/file.README.html +0 -182
  32. data/doc/file_list.html +0 -49
  33. data/doc/frames.html +0 -13
  34. data/doc/index.html +0 -182
  35. data/doc/js/app.js +0 -205
  36. data/doc/js/full_list.js +0 -150
  37. data/doc/js/jquery.js +0 -16
  38. data/doc/method_list.html +0 -206
  39. data/doc/top-level-namespace.html +0 -103
@@ -0,0 +1 @@
1
+ --no-private
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["Marcin Wyszynski"]
9
9
  s.email = ["marcin.pixie@gmail.com"]
10
10
  s.homepage = "http://github.com/marcinwyszynski/cabalist"
11
+ s.licenses = ['MIT']
11
12
  s.summary = %q{Minimum setup machine learning (classification) library for Ruby on Rails applications.}
12
13
  s.description = <<-EOF
13
14
  Cabalist is conceived as a simple way of adding some smarts
@@ -15,9 +16,10 @@ Cabalist is conceived as a simple way of adding some smarts
15
16
  without having to dig deep into mind-boggling AI algorithms.
16
17
  Using it is meant to be as straightforward as adding a few
17
18
  lines to your existing code and running a Rails generator or two.
18
- EOF
19
+ EOF
19
20
 
20
21
  s.rubyforge_project = "cabalist"
22
+ s.required_ruby_version = '>= 1.9.2'
21
23
 
22
24
  s.files = `git ls-files`.split("\n")
23
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -27,6 +29,7 @@ lines to your existing code and running a Rails generator or two.
27
29
  # Gem dependencies
28
30
  s.add_dependency('ai4r')
29
31
  s.add_dependency('haml', '>= 3.0')
32
+ s.add_dependency('googlecharts', '>= 1.6.8')
30
33
  s.add_dependency('kaminari', '>= 0.13.0')
31
34
  s.add_dependency('leveldb-ruby')
32
35
  s.add_dependency('padrino-helpers')
@@ -4,10 +4,15 @@ require "cabalist/frontend"
4
4
  require "cabalist/model_additions"
5
5
  require "cabalist/railtie" if defined? Rails
6
6
 
7
+ # Minimum setup machine learning (classification) library for Ruby on Rails
8
+ # applications.
7
9
  module Cabalist
8
-
10
+
11
+ # Configure the application
12
+ #
13
+ # @return [Cabalist::Configuration] library configuration singleton
9
14
  def self.configure
10
15
  yield Cabalist::Configuration.instance
11
16
  end
12
-
17
+
13
18
  end
@@ -2,16 +2,32 @@ require 'leveldb'
2
2
  require 'singleton'
3
3
 
4
4
  module Cabalist
5
+
6
+ # Cabalist settings.
7
+ #
8
+ # This class is a singleton storing some configuration data of the Cabalist
9
+ # library as used by the application.
5
10
  class Configuration
6
11
 
7
12
  include Singleton
8
13
 
9
- attr_accessor :db_path, :frontend_classes
14
+ # Path to the local LevelDB instance
15
+ # @return [String] path to the local LevelDB instance
16
+ attr_accessor :db_path
10
17
 
18
+ # Cabalist-enabled classes that should be exposed via web GUI
19
+ # @return [Array] classes that should be exposed via web GUI
20
+ attr_accessor :frontend_classes
21
+
22
+ # Initializer of the Cabalist::Configuration object
23
+ # @return [Cabalist::Configuration] a new Cabalist::Configuration object
11
24
  def initialize
12
25
  self.frontend_classes = []
13
26
  end
14
27
 
28
+ # Get the local instance of LevelDB used for caching.
29
+ #
30
+ # @return [LevelDB::DB] instance of LevelDB used by the application
15
31
  def database
16
32
  LevelDB::DB::new(db_path)
17
33
  end
@@ -1,12 +1,59 @@
1
+ require 'googlecharts'
1
2
  require 'haml'
2
3
  require 'kaminari/sinatra'
3
4
  require 'padrino-helpers'
4
5
  require 'sinatra/base'
5
6
 
6
7
  module Cabalist
8
+
9
+ # Provides a web frontend to models where Cabalist functionality has
10
+ # been enabled.
7
11
  class Frontend < Sinatra::Base
8
12
 
9
- PER_PAGE = 25
13
+ # Specifies how many records of a given class should be visible on a web
14
+ # GUI page.
15
+ PER_PAGE = 25
16
+
17
+ # Specifies basic options for the frontend charts to use
18
+ CHART_OPTIONS = { :background => '00000000',
19
+ :colors => %w(6EB41E 608733 43750A 9AD952 ABD976) +
20
+ %w(188D4B 286A45 085C2C 4AC680 6BC693) +
21
+ %w(AFC220 879136 707E0A D0E054 D4E07A),
22
+ :format => 'image_tag',
23
+ :legend_position => 'bottom',
24
+ :size => '400x300' }
25
+
26
+ helpers do
27
+
28
+ def piechart_by_class_variable(klass)
29
+ data, labels = [], []
30
+ variable_name = klass::get_class_variable_name
31
+ klass::all.group_by(&variable_name).each do |k,v|
32
+ label = k.nil? ? 'n/a' : k.to_s
33
+ count = v.count
34
+ labels << "#{label} (#{count})"
35
+ data << count
36
+ end
37
+ Gchart::pie({ :bar_colors => CHART_OPTIONS[:colors][0...labels.size],
38
+ :data => data,
39
+ :legend => labels,
40
+ :title => "Breakdown by #{variable_name.to_s}" }.merge(CHART_OPTIONS))
41
+ end
42
+
43
+ def piechart_by_scope(klass)
44
+ data, labels = [], []
45
+ [:manually_classified, :auto_classified, :not_classified].each do |s|
46
+ count = klass::send(s).count
47
+ labels << "#{s.to_s.humanize} (#{count})"
48
+ data << count
49
+ end
50
+ Gchart::pie({ :bar_colors => CHART_OPTIONS[:colors][0...labels.size],
51
+ :data => data,
52
+ :legend => labels,
53
+ :title => "Breakdown by classification method" }.merge(CHART_OPTIONS))
54
+ end
55
+
56
+ end
10
57
 
11
58
  before do
12
59
  @classes = Cabalist::Configuration.instance.frontend_classes
@@ -26,7 +73,7 @@ module Cabalist
26
73
  get "/:class_name/all/?:page?" do
27
74
  page = params[:page].to_i < 1 ? 1 : params[:page].to_i
28
75
  klass = params[:class_name].titleize.constantize
29
- @collection = klass::page(page).per(PER_PAGE)
76
+ @collection = klass::order("id DESC").page(page).per(PER_PAGE)
30
77
  haml :classifier,
31
78
  :locals => { :klass => klass,
32
79
  :page => page,
@@ -38,7 +85,7 @@ module Cabalist
38
85
  get "/:class_name/none/?:page?" do
39
86
  klass = params[:class_name].titleize.constantize
40
87
  page = params[:page].to_i < 1 ? 1 : params[:page].to_i
41
- @collection = klass::not_classified \
88
+ @collection = klass::not_classified.order("id DESC") \
42
89
  .page(page).per(PER_PAGE)
43
90
  haml :classifier,
44
91
  :locals => { :klass => klass,
@@ -51,7 +98,7 @@ module Cabalist
51
98
  get "/:class_name/manual/?:page?" do
52
99
  klass = params[:class_name].titleize.constantize
53
100
  page = params[:page].to_i < 1 ? 1 : params[:page].to_i
54
- @collection = klass::manually_classified \
101
+ @collection = klass::manually_classified.order("id DESC") \
55
102
  .page(page).per(PER_PAGE)
56
103
  haml :classifier,
57
104
  :locals => { :klass => klass,
@@ -64,7 +111,7 @@ module Cabalist
64
111
  get "/:class_name/auto/?:page?" do
65
112
  klass = params[:class_name].titleize.constantize
66
113
  page = params[:page].to_i < 1 ? 1 : params[:page].to_i
67
- @collection = klass::auto_classified. \
114
+ @collection = klass::auto_classified.order("autoclassified_at DESC"). \
68
115
  page(page).per(PER_PAGE)
69
116
  haml :classifier,
70
117
  :locals => { :klass => klass,
@@ -1,8 +1,338 @@
1
1
  require 'ai4r'
2
2
 
3
3
  module Cabalist
4
+
5
+ # Core functionality of Cabalist.
6
+ #
7
+ # This module is a placeholder for methods that are mixed in with
8
+ # ActiveRecord::Base as well as those that are created by calling
9
+ # acts_as_cabalist method from within the class definition of an
10
+ # inheriting class.
4
11
  module ModelAdditions
12
+
13
+ _METHOD_UNAVAILABLE = "This method is only available for Cabalist classes"
14
+
15
+ # Class helper to add Cabalist functionality to a Rails model.
16
+ #
17
+ # @method acts_as_cabalist(opts={})
18
+ # @scope class
19
+ # @param [Hash] opts An options hash.
20
+ # @option opts [Array] :features
21
+ # Array of symbols representing name of the model attributes
22
+ # used as features (predictors) to train the classifier.
23
+ # @option opts [Symbol] :class_variable
24
+ # Symbol representing the name of the model attribute to be
25
+ # predicted by the classifier.
26
+ # @option opts [Symbol] :collection (:manually_classified)
27
+ # Symbol representing the class method used to pull data to be
28
+ # used to train the classifier. Anything returning either an
29
+ # ActiveRecord::Relation or an Array of objects of this given class
30
+ # will do.
31
+ # @option opts [Symbol] :algorithm (:id3)
32
+ # Symbol representing the algorithm to be used by the classifier.
33
+ # The library currently supports a number of algorithms provided
34
+ # by the ai4r gem. Accepted values are as following:
35
+ # - :hyperpipes for Hyperpipes
36
+ # - :ib1 for Simple Instance Based Learning
37
+ # - :id3 for Iterative Dichotomiser 3
38
+ # - :one_r for One Attribute Rule
39
+ # - :prism for PRISM
40
+ # - :zero_r for ZeroR
41
+ # @return [void]
42
+ define_singleton_method(
43
+ :acts_as_cabalist,
44
+ lambda { |opts|
45
+ raise NoMethodError, _METHOD_UNAVAILABLE
46
+ }
47
+ )
48
+
49
+ # Automatically classified records.
50
+ #
51
+ # Named scope which returns objects that were classified automatically.
52
+ # That means they have their class variable value set as well as non-nil
53
+ # value of autoclassified_at attribute.
54
+ #
55
+ # @scope class
56
+ # @return [ActiveRecord::Relation] automatically classified records.
57
+ define_singleton_method(
58
+ :auto_classified,
59
+ lambda {
60
+ raise NoMethodError, _METHOD_UNAVAILABLE
61
+ }
62
+ )
63
+
64
+ # Build the classification model from scratch.
65
+ #
66
+ # You should probably not use this method directly as it will go ahead and
67
+ # compute the classifier anew each and every time instead of looking at
68
+ # any cached versions.
69
+ #
70
+ # @scope class
71
+ # @return [Ai4r::Classifiers::Object] one of classifiers from the
72
+ # Ai4r::Classifiers module, depending on the value (and presence) of the
73
+ # :algorithm option in the acts_as_cabalist method call.
74
+ define_singleton_method(
75
+ :build_model,
76
+ lambda {
77
+ raise NoMethodError, _METHOD_UNAVAILABLE
78
+ }
79
+ )
80
+
81
+ # Show possible values for the classification.
82
+ #
83
+ # This method will return all unique values of the class variable that the
84
+ # classifier knows about. If any new values have been added after the
85
+ # classifier has last been retrained, they will not find their way here.
86
+ #
87
+ # @scope class
88
+ # @return [Array] an Array containing all unique class variable values.
89
+ define_singleton_method(
90
+ :class_variable_domain,
91
+ lambda {
92
+ raise NoMethodError, _METHOD_UNAVAILABLE
93
+ }
94
+ )
95
+
96
+ # Get the classification model for a given class.
97
+ #
98
+ # This method will try to find a ready model in the local cache but having
99
+ # failed that will resort to the 'train_model' to create one.
100
+ #
101
+ # @scope class
102
+ # @return [Ai4r::Classifiers::Object] one of classifiers from the
103
+ # Ai4r::Classifiers module, depending on the value (and presence) of the
104
+ # :algorithm option in the acts_as_cabalist method call.
105
+ define_singleton_method(
106
+ :classifier,
107
+ lambda {
108
+ raise NoMethodError, _METHOD_UNAVAILABLE
109
+ }
110
+ )
111
+
112
+ # Calculate the Cohen's kappa coefficient for the classifier
113
+ #
114
+ # Cohen's kappa is a measure of classifier quality. For more about this
115
+ # method of measurement, see the relevant article on
116
+ # Wikipedia[link:http://en.wikipedia.org/wiki/Cohen%27s_kappa].
117
+ # @scope class
118
+ define_singleton_method(
119
+ :cohens_kappa,
120
+ lambda {
121
+ raise NoMethodError, _METHOD_UNAVAILABLE
122
+ }
123
+ )
124
+ # Get name of the class variable.
125
+ #
126
+ # This method returns the class variable name (as symbol) as it has
127
+ # been passed to the acts_as_cabalist method call as :class_variable option.
128
+ #
129
+ # @scope class
130
+ # @return [Symbol] name of the class variable.
131
+ define_singleton_method(
132
+ :get_class_variable_name,
133
+ lambda {
134
+ raise NoMethodError, _METHOD_UNAVAILABLE
135
+ }
136
+ )
137
+
138
+ # Get names of feature attributes.
139
+ #
140
+ # This method returns the Array of attribute names (as symbols) as they have
141
+ # been passed to the acts_as_cabalist method call as :features option.
142
+ #
143
+ # @scope class
144
+ # @return [Array] an Array of symbols representing feature names
145
+ define_singleton_method(
146
+ :get_feature_names,
147
+ lambda {
148
+ raise NoMethodError, _METHOD_UNAVAILABLE
149
+ }
150
+ )
151
+
152
+ # Manually classified records.
153
+ #
154
+ # Named scope which returns objects that were classified but not
155
+ # automatically, that is they have their class variable value set
156
+ # but autoclassified_at nil.
157
+ #
158
+ # @scope class
159
+ # @return [ActiveRecord::Relation] manually classified records.
160
+ define_singleton_method(
161
+ :manually_classified,
162
+ lambda {
163
+ raise NoMethodError, _METHOD_UNAVAILABLE
164
+ }
165
+ )
166
+
167
+ # Records that have not yet been classified.
168
+ #
169
+ # Named scope which returns objects that were classified automatically.
170
+ # That means they have their class variable value set as well as non-nil
171
+ # value of autoclassified_at attribute.
172
+ #
173
+ # @scope class
174
+ # @return [ActiveRecord::Relation] records that have not yet been
175
+ # classified.
176
+ define_singleton_method(
177
+ :not_classified,
178
+ lambda {
179
+ raise NoMethodError, _METHOD_UNAVAILABLE
180
+ }
181
+ )
182
+
183
+ # Percantage agreement between classifier and human
184
+ #
185
+ # This is the number between 0 and 1 which represents how often
186
+ # the classifier and human decision-maker agree. It is one of quality
187
+ # measures of the classifier, albeit a naive one.
188
+ # @scope class
189
+ define_singleton_method(
190
+ :percentage_ageement,
191
+ lambda {
192
+ raise NoMethodError, _METHOD_UNAVAILABLE
193
+ }
194
+ )
195
+
196
+ # Percentage of random agreement between classifier and human
197
+ # labeling and the random classifier
198
+ #
199
+ # This is the number between 0 and 1 which represents how often
200
+ # the classifier and human decisions may accidentally be the same,
201
+ # due to sheer randomness rather than actual intelligence reperesented
202
+ # by the classifier.
203
+ # @scope class
204
+ define_singleton_method(
205
+ :percentage_random_agreement,
206
+ lambda {
207
+ raise NoMethodError, _METHOD_UNAVAILABLE
208
+ }
209
+ )
210
+
211
+ # Build the classification model from scratch and store it in cache.
212
+ #
213
+ # This method will use a 'build_model' call to create a model from scratch
214
+ # but once it has been created, it will also be immediately stored in the
215
+ # cache for further retrieval using the 'classifier' method.
216
+ #
217
+ # @scope class
218
+ # @return [Ai4r::Classifiers::Object] one of classifiers from the
219
+ # Ai4r::Classifiers module, depending on the value (and presence) of the
220
+ # :algorithm option in the acts_as_cabalist method call.
221
+ define_singleton_method(
222
+ :train_model,
223
+ lambda {
224
+ raise NoMethodError, _METHOD_UNAVAILABLE
225
+ }
226
+ )
227
+
228
+
229
+ # Get predicted value of the class variable
230
+ #
231
+ # This method will query the classifier of the instance's corresponding
232
+ # class for the predicted classification of this instance, given the value
233
+ # of its features.
234
+ #
235
+ # @method classify()
236
+ # @scope instance
237
+ # @return [Object] predicted value of the class variable
238
+ send(
239
+ :define_method,
240
+ :classify,
241
+ lambda {
242
+ raise NoMethodError, _METHOD_UNAVAILABLE
243
+ }
244
+ )
245
+
246
+ # Set the class variable to the value suggested by the classifier
247
+ #
248
+ # This method will query the classifier of the instance's corresponding
249
+ # class for the predicted classification of this instance, given the value
250
+ # of its features and then set the class variable to that value.
251
+ #
252
+ # @method classify!()
253
+ # @scope instance
254
+ # @return [self] the current object instance
255
+ send(
256
+ :define_method,
257
+ :classify!,
258
+ lambda {
259
+ raise NoMethodError, _METHOD_UNAVAILABLE
260
+ }
261
+ )
262
+
263
+ # Value of the class variable.
264
+ #
265
+ # This method returns the value of an attribute passed as the
266
+ # :class_variable option of the acts_as_cabalist method call.
267
+ #
268
+ # @method get_class_variable()
269
+ # @scope instance
270
+ # @return [Object] the value of the class variable
271
+ send(
272
+ :define_method,
273
+ :get_class_variable,
274
+ lambda {
275
+ raise NoMethodError, _METHOD_UNAVAILABLE
276
+ }
277
+ )
278
+
279
+ # Get an array of features.
280
+ #
281
+ # Returns an Array of values which result from methods passed as the
282
+ # :feature option of the acts_as_cabalist method call. Each of this methods
283
+ # is called upon current instance and results are returned.
284
+ #
285
+ # @method get_features()
286
+ # @scope instance
287
+ # @return [Array] array of values corresponding to attributes selected as
288
+ # features
289
+ send(
290
+ :define_method,
291
+ :get_features,
292
+ lambda {
293
+ raise NoMethodError, _METHOD_UNAVAILABLE
294
+ }
295
+ )
296
+
297
+ # Set the value of the class variable.
298
+ #
299
+ # This method sets the value of an attribute passed as the
300
+ # :class_variable option of the acts_as_cabalist method call to the new
301
+ # value.
302
+ #
303
+ # @method set_class_variable(new_class_variable)
304
+ # @scope instance
305
+ # @param [Object] new_class_variable the new value of the class variable
306
+ # @return [Object] the new value of the class variable
307
+ send(
308
+ :define_method,
309
+ :set_class_variable,
310
+ lambda { |i|
311
+ raise NoMethodError, _METHOD_UNAVAILABLE
312
+ }
313
+ )
5
314
 
315
+ # Set the value of the class variable.
316
+ #
317
+ # This method sets the value of an attribute passed as the
318
+ # :class_variable option of the acts_as_cabalist method call to the new
319
+ # value and sets the autoclassified_at to nil so that current object is
320
+ # not treated as automatically classified.
321
+ #
322
+ # @method teach(new_class_variable)
323
+ # @scope instance
324
+ # @param [Object] new_class_variable the new value of the class variable
325
+ # @return [DateTime] timestamp of the classification
326
+ send(
327
+ :define_method,
328
+ :teach,
329
+ lambda { |new_class|
330
+ raise NoMethodError, _METHOD_UNAVAILABLE
331
+ }
332
+ )
333
+
334
+
335
+ # @private
6
336
  def acts_as_cabalist(options = {})
7
337
 
8
338
  # Make sure that all required options are set
@@ -28,100 +358,171 @@ module Cabalist
28
358
  else raise 'Unknown algorithm provided'
29
359
  end
30
360
 
31
- # Create scopes
32
- scope :manually_classified,
361
+ define_singleton_method(
362
+ :manually_classified,
363
+ lambda {
33
364
  where("autoclassified_at IS NULL AND %s IS NOT NULL" %
34
365
  options[:class_variable])
35
- scope :auto_classified,
366
+ }
367
+ )
368
+
369
+ define_singleton_method(
370
+ :auto_classified,
371
+ lambda {
36
372
  where("autoclassified_at IS NOT NULL AND %s IS NOT NULL" %
37
373
  options[:class_variable])
38
- scope :not_classified,
374
+ }
375
+ )
376
+
377
+ define_singleton_method(
378
+ :not_classified,
379
+ lambda {
39
380
  where("autoclassified_at IS NULL AND %s IS NULL" %
40
381
  options[:class_variable])
382
+ }
383
+ )
41
384
 
42
- # Return object as an Array of features
43
- send(:define_method, :get_features, lambda {
44
- options[:features].map { |f| self.send(f) }
45
- })
46
-
47
- # Return the value of a class variable
48
- send(:define_method, :get_class_variable, lambda {
49
- self.send(options[:class_variable])
50
- })
51
-
52
- # Set the value of the class variable
53
- send(:define_method, :set_class_variable, lambda { |c|
54
- self.send("#{options[:class_variable]}=".to_sym, c) or self
55
- })
56
-
57
- # Return an Array of feature names (attributes/methods)
58
- send(:define_singleton_method, :get_feature_names, lambda {
59
- options[:features]
60
- })
61
-
62
- # Return the name of a class variable
63
- send(:define_singleton_method, :get_class_variable_name, lambda {
64
- options[:class_variable]
65
- })
66
-
67
- # Build a prediction model from scratch
68
- send(:define_singleton_method, :build_model, lambda {
69
- classifier::new.build(
70
- Ai4r::Data::DataSet::new({
71
- :data_items => send(collection).map do |el|
72
- el.get_features.push(el.get_class_variable)
73
- end,
74
- :data_labels => get_feature_names + [get_class_variable_name]
75
- })
76
- )
77
- })
78
-
79
- # Build a prediction model and store it in the LevelDB
80
- send(:define_singleton_method, :train_model, lambda {
81
- _model = build_model
82
- Cabalist::Configuration.instance.database.put(name,
83
- Marshal::dump(_model))
84
- return _model
85
- })
86
-
87
- # Return prediction model for the class
88
- send(:define_singleton_method, :classifier, lambda {
89
- _stored = Cabalist::Configuration.instance.database.get(self.name)
90
- return _stored ? Marshal.load(_stored) : train_model
91
- })
385
+ send(
386
+ :define_method,
387
+ :get_features,
388
+ lambda {
389
+ options[:features].map { |f| self.send(f) }
390
+ }
391
+ )
392
+
393
+ send(
394
+ :define_method,
395
+ :get_class_variable,
396
+ lambda {
397
+ self.send(options[:class_variable])
398
+ }
399
+ )
400
+
401
+ send(
402
+ :define_method,
403
+ :set_class_variable,
404
+ lambda { |c|
405
+ self.send("#{options[:class_variable]}=".to_sym, c) or self
406
+ }
407
+ )
408
+
409
+ define_singleton_method(
410
+ :get_feature_names,
411
+ lambda {
412
+ options[:features]
413
+ }
414
+ )
415
+
416
+ define_singleton_method(
417
+ :get_class_variable_name,
418
+ lambda {
419
+ options[:class_variable]
420
+ }
421
+ )
422
+
423
+ define_singleton_method(
424
+ :build_model,
425
+ lambda {
426
+ classifier::new.build(
427
+ Ai4r::Data::DataSet::new({
428
+ :data_items => send(collection).map do |el|
429
+ el.get_features.push(el.get_class_variable)
430
+ end,
431
+ :data_labels => get_feature_names + [get_class_variable_name]
432
+ })
433
+ )
434
+ }
435
+ )
436
+
437
+ define_singleton_method(
438
+ :train_model,
439
+ lambda {
440
+ _model = build_model
441
+ Cabalist::Configuration.instance.database.put(name,
442
+ Marshal::dump(_model))
443
+ return _model
444
+ }
445
+ )
446
+
447
+ define_singleton_method(
448
+ :classifier,
449
+ lambda {
450
+ _stored = Cabalist::Configuration.instance.database.get(self.name)
451
+ return _stored ? Marshal.load(_stored) : train_model
452
+ }
453
+ )
92
454
 
93
- # Show possible values for the classification.
94
455
  define_singleton_method(
95
456
  :class_variable_domain,
96
- lambda { self.classifier.data_set.build_domain(-1).to_a }
457
+ lambda {
458
+ self.classifier.data_set.build_domain(-1).to_a
459
+ }
460
+ )
461
+
462
+ define_singleton_method(
463
+ :percentage_agreement,
464
+ lambda {
465
+ consistent = manually_classified.all.count do |r|
466
+ r.get_class_variable == r.classify
467
+ end
468
+ return consistent / manually_classified.count.to_f
469
+ }
470
+ )
471
+
472
+ define_singleton_method(
473
+ :percentage_random_agreement,
474
+ lambda {
475
+ random_agreement = 0
476
+ records = manually_classified.all
477
+ count = records.size
478
+ human = records.map(&:get_class_variable).group_by { |i| i }
479
+ human.each_pair { |k,v| human[k] = v.size / count.to_f }
480
+ cabalist = records.map(&:classify).group_by { |i| i }
481
+ cabalist.each_pair { |k,v| cabalist[k] = v.size / count.to_f }
482
+ human.each_pair { |k,v| random_agreement += v * cabalist[k] }
483
+ return random_agreement
484
+ }
485
+ )
486
+
487
+ define_singleton_method(
488
+ :cohens_kappa,
489
+ lambda {
490
+ rand = percentage_random_agreement
491
+ return (percentage_agreement - rand) / (1 - rand).to_f
492
+ }
493
+ )
494
+
495
+ send(
496
+ :define_method,
497
+ :classify,
498
+ lambda {
499
+ begin
500
+ self.class::classifier.eval(get_features)
501
+ rescue
502
+ nil
503
+ end
504
+ }
505
+ )
506
+
507
+ send(
508
+ :define_method,
509
+ :classify!,
510
+ lambda {
511
+ set_class_variable(classify)
512
+ self.autoclassified_at = DateTime::now
513
+ }
514
+ )
515
+
516
+ send(
517
+ :define_method,
518
+ :teach,
519
+ lambda { |new_class|
520
+ set_class_variable(new_class)
521
+ self.autoclassified_at = nil
522
+ }
97
523
  )
98
-
99
- # Create a 'classify' method which will provide a classification
100
- # for any new object.
101
- send(:define_method, :classify, lambda {
102
- begin
103
- self.class::classifier.eval(get_features)
104
- rescue
105
- nil
106
- end
107
- })
108
-
109
- # Create a 'classify!' method which will get a classification
110
- # for any new object and apply it to the current instance.
111
- send(:define_method, :classify!, lambda {
112
- set_class_variable(classify)
113
- self.autoclassified_at = DateTime::now
114
- })
115
-
116
- # Create a 'teach' method which will manually set the classificaiton
117
- # and set the autoclassification timestamp to nil so that the new entry
118
- # can be treated as basis for learning.
119
- send(:define_method, :teach, lambda { |new_class|
120
- set_class_variable(new_class)
121
- self.autoclassified_at = nil
122
- })
123
524
 
124
525
  end
125
-
526
+
126
527
  end
127
528
  end