Module: Cabalist::ModelAdditions

Defined in:
lib/cabalist/model_additions.rb

Instance Method Summary collapse

Instance Method Details

#acts_as_cabalist(options = {}) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/cabalist/model_additions.rb', line 6

def acts_as_cabalist(options = {})
  
  # Make sure that all required options are set
  raise 'No features specified' \
      unless options.has_key?(:features)
  raise 'Expecting an Array of features' \
      unless options[:features].instance_of?(Array)
  raise 'No class variable specified' \
      unless options.has_key?(:class_variable)

  # Set some sensible defaults for other options, if required
  collection = options[:collection] || :manually_classified
  algorithm  = options[:algorithm]  || :id3
  
  # Select an algorithm for the classifier
  classifier = case algorithm
               when :hyperpipes then ::Ai4r::Classifiers::Hyperpipes
               when :ib1        then ::Ai4r::Classifiers::IB1
               when :id3        then ::Ai4r::Classifiers::ID3
               when :one_r      then ::Ai4r::Classifiers::OneR
               when :prism      then ::Ai4r::Classifiers::Prism
               when :zero_r     then ::Ai4r::Classifiers::ZeroR
               else raise 'Unknown algorithm provided'
               end
  
  # Create scopes
  scope :manually_classified,
      where("autoclassified_at IS NULL AND %s IS NOT NULL" %
      options[:class_variable])
  scope :auto_classified,
      where("autoclassified_at IS NOT NULL AND %s IS NOT NULL" %
      options[:class_variable])
  scope :not_classified,
      where("autoclassified_at IS NULL AND %s IS NULL" %
      options[:class_variable])

  # Return object as an Array of features
  send(:define_method, :get_features, lambda {
    options[:features].map { |f| self.send(f) }
  })
  
  # Return the value of a class variable
  send(:define_method, :get_class_variable, lambda {
    self.send(options[:class_variable])
  })
  
  # Set the value of the class variable
  send(:define_method, :set_class_variable, lambda { |c|
    self.send("#{options[:class_variable]}=".to_sym, c) or self
  })
  
  # Return an Array of feature names (attributes/methods)
  send(:define_singleton_method, :get_feature_names, lambda {
    options[:features]
  })
  
  # Return the name of a class variable
  send(:define_singleton_method, :get_class_variable_name, lambda {
    options[:class_variable]
  })
  
  # Build a prediction model from scratch
  send(:define_singleton_method, :build_model, lambda {
    classifier::new.build(
      Ai4r::Data::DataSet::new({
        :data_items  => send(collection).map do |el| 
          el.get_features.push(el.get_class_variable)
        end,
        :data_labels => get_feature_names + [get_class_variable_name]
      })
    )
  })
  
  # Build a prediction model and store it in the LevelDB
  send(:define_singleton_method, :train_model, lambda {
    _model = build_model
    Cabalist::Configuration.instance.database.put(name,
        Marshal::dump(_model))
    return _model
  })
  
  # Return prediction model for the class
  send(:define_singleton_method, :classifier, lambda {
    _stored = Cabalist::Configuration.instance.database.get(self.name)
    return _stored ? Marshal.load(_stored) : train_model
  })

  # Show possible values for the classification.
  define_singleton_method(
    :class_variable_domain,
    lambda { self.classifier.data_set.build_domain(-1).to_a }
  )

  # Create a 'classify' method which will provide a classification
  # for any new object.
  send(:define_method, :classify, lambda {
    begin
      self.class::classifier.eval(get_features)
    rescue
      nil
    end
  })
  
  # Create a 'classify!' method which will get a classification
  # for any new object and apply it to the current instance.
  send(:define_method, :classify!, lambda {
    set_class_variable(classify)
    self.autoclassified_at = DateTime::now
  })
  
  # Create a 'teach' method which will manually set the classificaiton
  # and set the autoclassification timestamp to nil so that the new entry
  # can be treated as basis for learning.
  send(:define_method, :teach, lambda { |new_class|
    set_class_variable(new_class)
    self.autoclassified_at = nil
  })

end