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.
- data/.yardopts +1 -0
- data/cabalist.gemspec +4 -1
- data/lib/cabalist.rb +7 -2
- data/lib/cabalist/configuration.rb +17 -1
- data/lib/cabalist/frontend.rb +52 -5
- data/lib/cabalist/model_additions.rb +483 -82
- data/lib/cabalist/railtie.rb +11 -1
- data/lib/cabalist/version.rb +4 -1
- data/lib/cabalist/views/classifier.haml +4 -2
- data/lib/cabalist/views/index.haml +21 -16
- data/lib/generators/cabalist/classifier/classifier_generator.rb +9 -0
- data/lib/generators/cabalist/install/install_generator.rb +7 -1
- data/lib/generators/cabalist/install/templates/public/stylesheets/cabalist.css +55 -1
- data/lib/tasks/retrain.rake +21 -0
- data/spec/cabalist/model_additions_spec.rb +15 -0
- data/spec/cabalist/non_cabalist_model_spec.rb +95 -0
- data/spec/spec_helper.rb +14 -0
- metadata +32 -38
- data/doc/Cabalist.html +0 -257
- data/doc/Cabalist/ClassifierGenerator.html +0 -320
- data/doc/Cabalist/Configuration.html +0 -404
- data/doc/Cabalist/Frontend.html +0 -152
- data/doc/Cabalist/InstallGenerator.html +0 -282
- data/doc/Cabalist/ModelAdditions.html +0 -1583
- data/doc/Cabalist/Railtie.html +0 -127
- data/doc/_index.html +0 -158
- data/doc/class_list.html +0 -47
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -53
- data/doc/css/style.css +0 -320
- data/doc/file.README.html +0 -182
- data/doc/file_list.html +0 -49
- data/doc/frames.html +0 -13
- data/doc/index.html +0 -182
- data/doc/js/app.js +0 -205
- data/doc/js/full_list.js +0 -150
- data/doc/js/jquery.js +0 -16
- data/doc/method_list.html +0 -206
- data/doc/top-level-namespace.html +0 -103
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private
|
data/cabalist.gemspec
CHANGED
@@ -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
|
-
|
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')
|
data/lib/cabalist.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/cabalist/frontend.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
send(
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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 {
|
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
|