cabalist 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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