liblinear-ruby 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,42 +1,83 @@
1
- module Liblinear
1
+ class Liblinear
2
2
  class Parameter
3
- include Liblinear
4
- include Liblinearswig
5
- attr_accessor :param
6
-
7
- # @param param [Hash]
8
- def initialize(param = {})
9
- @param = Liblinearswig::Parameter.new
10
- self.solver_type = 1
11
- self.C = 1
12
- self.eps = 0.1
13
- self.p = 0.1
14
- self.nr_weight = 0
15
- self.weight_label = []
16
- self.weight = []
17
- param.each do |k, v|
18
- self.send("#{k}=", v)
3
+ class << self
4
+ # @return [Float]
5
+ def default_epsilon(solver_type)
6
+ case solver_type
7
+ when Liblinear::L2R_LR then
8
+ 0.01
9
+ when Liblinear::L2R_L2LOSS_SVC_DUAL then
10
+ 0.1
11
+ when Liblinear::L2R_L2LOSS_SVC then
12
+ 0.01
13
+ when Liblinear::L2R_L1LOSS_SVC_DUAL then
14
+ 0.1
15
+ when Liblinear::MCSVM_CS then
16
+ 0.1
17
+ when Liblinear::L1R_L2LOSS_SVC then
18
+ 0.01
19
+ when Liblinear::L1R_LR then
20
+ 0.01
21
+ when Liblinear::L2R_LR_DUAL then
22
+ 0.1
23
+ when Liblinear::L2R_L2LOSS_SVR then
24
+ 0.001
25
+ when Liblinear::L2R_L2LOSS_SVR_DUAL then
26
+ 0.1
27
+ when Liblinear::L2R_L1LOSS_SVR_DUAL then
28
+ 0.1
29
+ end
19
30
  end
20
31
  end
21
32
 
22
- # @param weigt_label [Array <Integer>]
23
- def weight_label=(weight_label)
24
- free_int_array(@param.weight_label)
25
- @param.weight_label = new_int_array(weight_label)
33
+ # @param parameter [Hash]
34
+ def initialize(parameter = {})
35
+ parameter[:weight_labels] = [] if parameter[:weight_labels].nil?
36
+ parameter[:weights] = [] if parameter[:weights].nil?
37
+
38
+ @parameter = Liblinearswig::Parameter.new
39
+ @parameter.solver_type = parameter[:solver_type] || Liblinear::L2R_L2LOSS_SVC_DUAL
40
+ @parameter.C = parameter[:cost] || 1.0
41
+ @parameter.p = parameter[:sensitive_loss] || 0.1
42
+ @parameter.eps = parameter[:epsilon] || self.class.default_epsilon(@parameter.solver_type)
43
+ @parameter.nr_weight = parameter[:weight_labels].size
44
+ @parameter.weight_label = Liblinear::Array::Integer.new(parameter[:weight_labels]).swig
45
+ @parameter.weight = Liblinear::Array::Double.new(parameter[:weights]).swig
26
46
  end
27
47
 
28
- # @param weight [Array <Double>]
29
- def weight=(weight)
30
- free_double_array(@param.weight)
31
- @param.weight = new_double_array(weight)
48
+ # @return [Liblinearswig::Parameter]
49
+ def swig
50
+ @parameter
32
51
  end
33
52
 
34
- def method_missing(m, *args)
35
- if m.to_s.index('=')
36
- @param.send(m.to_sym, args.first)
37
- else
38
- @param.send(m.to_sym)
39
- end
53
+ # @return [Integer]
54
+ def solver_type
55
+ @parameter.solver_type
56
+ end
57
+
58
+ # @return [Float]
59
+ def cost
60
+ @parameter.C
61
+ end
62
+
63
+ # @return [Float]
64
+ def sensitive_loss
65
+ @parameter.p
66
+ end
67
+
68
+ # @return [Float]
69
+ def epsilon
70
+ @parameter.eps
71
+ end
72
+
73
+ # @return [Array <Integer>]
74
+ def weight_labels
75
+ Liblinear::Array::Integer.decode(@parameter.weight_label, @parameter.nr_weight)
76
+ end
77
+
78
+ # @return [Array <Float>]
79
+ def weights
80
+ Liblinear::Array::Double.decode(@parameter.weight, @parameter.nr_weight)
40
81
  end
41
82
  end
42
83
  end
@@ -1,45 +1,50 @@
1
- module Liblinear
1
+ class Liblinear
2
2
  class Problem
3
- include Liblinear
4
- include Liblinearswig
5
- attr_accessor :prob
6
- attr_reader :labels, :examples
7
-
8
- # @param labels [Array <Double>]
9
- # @param examples [Array <Double, Hash>]
10
- # @param bias [Double]
11
- # @raise [ArgumentError]
3
+ # @param labels [Array <Float>]
4
+ # @param examples [Array <Array <Float> or Hash>]
5
+ # @param bias [Float]
12
6
  def initialize(labels, examples, bias = -1)
13
- unless labels.size == examples.size
14
- raise ArgumentError, 'labels and examples must be same size'
15
- end
16
- @prob = Liblinearswig::Problem.new
17
- @labels = labels
18
- c_labels = new_double_array(@labels)
7
+ @labels = labels
19
8
  @examples = examples
20
- @bias = bias
21
- @max_example_index = max_index(@examples)
22
- @example_matrix = feature_node_matrix(examples.size)
23
- @c_example_array = []
9
+ @bias = bias
10
+
11
+ @problem = Liblinearswig::Problem.new
12
+ @problem.y = Liblinear::Array::Double.new(labels).swig
13
+ @problem.x = example_matrix.swig
14
+ @problem.bias = bias
15
+ @problem.l = examples.size
16
+ @problem.n = Liblinear::Example.max_feature_id(examples)
17
+ @problem.n += 1 if bias >= 0
18
+ end
19
+
20
+ # @return [Liblinearswig::Problem]
21
+ def swig
22
+ @problem
23
+ end
24
24
 
25
- set_example_matrix
25
+ # @return [Integer]
26
+ def example_size
27
+ @problem.l
28
+ end
29
+
30
+ # @return [Integer]
31
+ def max_feature_id
32
+ @problem.n
33
+ end
34
+
35
+ # @return [Array <Float>]
36
+ def labels
37
+ Liblinear::Array::Double.decode(@problem.y, @labels.size)
38
+ end
26
39
 
27
- @prob.tap do |p|
28
- p.y = c_labels
29
- p.x = @example_matrix
30
- p.bias = bias
31
- p.l = examples.size
32
- p.n = @max_example_index
33
- p.n += 1 if bias >= 0
34
- end
40
+ # @return [Liblinear::FeatureNodeMatrix]
41
+ def example_matrix
42
+ Liblinear::FeatureNodeMatrix.new(@examples, @bias)
35
43
  end
36
44
 
37
- def set_example_matrix
38
- @examples.size.times do |index|
39
- c_example = convert_to_feature_node_array(@examples[index], @max_example_index, @bias)
40
- @c_example_array << c_example
41
- feature_node_matrix_set(@example_matrix, index, c_example)
42
- end
45
+ # @return [Float]
46
+ def bias
47
+ @problem.bias
43
48
  end
44
49
  end
45
50
  end
@@ -1,3 +1,3 @@
1
- module Liblinear
2
- VERSION = '0.0.7'
1
+ class Liblinear
2
+ VERSION = '1.0.0'
3
3
  end
data/lib/liblinear.rb CHANGED
@@ -1,124 +1,129 @@
1
1
  $: << File.expand_path(File.join(__FILE__, '..', '..', 'ext'))
2
2
 
3
3
  require 'liblinearswig'
4
- require 'liblinear/cross_validator'
4
+ require 'liblinear/array'
5
+ require 'liblinear/array/integer'
6
+ require 'liblinear/array/double'
7
+ require 'liblinear/example'
8
+ require 'liblinear/feature_node'
9
+ require 'liblinear/feature_node_matrix'
5
10
  require 'liblinear/error'
6
11
  require 'liblinear/model'
7
12
  require 'liblinear/parameter'
8
13
  require 'liblinear/problem'
9
14
  require 'liblinear/version'
10
15
 
11
- module Liblinear
12
- L2R_LR = Liblinearswig::L2R_LR
16
+ class Liblinear
17
+ L2R_LR = Liblinearswig::L2R_LR
13
18
  L2R_L2LOSS_SVC_DUAL = Liblinearswig::L2R_L2LOSS_SVC_DUAL
14
- L2R_L2LOSS_SVC = Liblinearswig::L2R_L2LOSS_SVC
19
+ L2R_L2LOSS_SVC = Liblinearswig::L2R_L2LOSS_SVC
15
20
  L2R_L1LOSS_SVC_DUAL = Liblinearswig::L2R_L1LOSS_SVC_DUAL
16
- MCSVM_CS = Liblinearswig::MCSVM_CS
17
- L1R_L2LOSS_SVC = Liblinearswig::L1R_L2LOSS_SVC
18
- L1R_LR = Liblinearswig::L1R_LR
19
- L2R_LR_DUAL = Liblinearswig::L2R_LR_DUAL
20
- L2R_L2LOSS_SVR = Liblinearswig::L2R_L2LOSS_SVR
21
+ MCSVM_CS = Liblinearswig::MCSVM_CS
22
+ L1R_L2LOSS_SVC = Liblinearswig::L1R_L2LOSS_SVC
23
+ L1R_LR = Liblinearswig::L1R_LR
24
+ L2R_LR_DUAL = Liblinearswig::L2R_LR_DUAL
25
+ L2R_L2LOSS_SVR = Liblinearswig::L2R_L2LOSS_SVR
21
26
  L2R_L2LOSS_SVR_DUAL = Liblinearswig::L2R_L2LOSS_SVR_DUAL
22
27
  L2R_L1LOSS_SVR_DUAL = Liblinearswig::L2R_L1LOSS_SVR_DUAL
23
28
 
24
- # @param ruby_array [Array <Integer>]
25
- # @return [SWIG::TYPE_p_int]
26
- def new_int_array(ruby_array)
27
- c_int_array = Liblinearswig.new_int(ruby_array.size)
28
- ruby_array.size.times do |index|
29
- Liblinearswig.int_setitem(c_int_array, index, ruby_array[index])
29
+ class << self
30
+ # @param problem [Liblinear::Problem]
31
+ # @param parameter [Liblinear::Parameter]
32
+ # @return [String]
33
+ def check_parameter(problem, parameter)
34
+ Liblinearswig.check_parameter(problem.swig, parameter.swig)
30
35
  end
31
- c_int_array
32
- end
33
-
34
- # @param c_array [SWIG::TYPE_p_int]
35
- def free_int_array(c_array)
36
- delete_int(c_array) unless c_array.nil?
37
- end
38
36
 
39
- # @param ruby_array [Array <Double>]
40
- # @return [SWIG::TYPE_p_double]
41
- def new_double_array(ruby_array)
42
- c_double_array = Liblinearswig.new_double(ruby_array.size)
43
- ruby_array.size.times do |index|
44
- Liblinearswig.double_setitem(c_double_array, index, ruby_array[index])
37
+ # @param fold [Integer]
38
+ # @param parameter [Hash]
39
+ # @param labels [Array <Integer>]
40
+ # @examples [Array [Array <Float> or Hash]
41
+ # @bias [<Float>]
42
+ # @return [Array <Float>]
43
+ def cross_validation(fold, parameter, labels, examples, bias = -1)
44
+ parameter = Liblinear::Parameter.new(parameter)
45
+ problem = Liblinear::Problem.new(labels, examples, bias)
46
+ error_message = self.check_parameter(problem, parameter)
47
+ raise Liblinear::InvalidParameter, error_message if error_message
48
+ prediction_swig = Liblinearswig.new_double(labels.size)
49
+ Liblinearswig.cross_validation(problem.swig, parameter.swig, fold, prediction_swig)
50
+ prediction = Liblinear::Array::Double.decode(prediction_swig, labels.size)
51
+ Liblinear::Array::Double.delete(prediction_swig)
52
+ prediction
45
53
  end
46
- c_double_array
47
- end
48
54
 
49
- # @param c_array [SWIG::TYPE_p_double]
50
- def free_double_array(c_array)
51
- delete_double(c_array) unless c_array.nil?
52
- end
53
-
54
- # @param c_array [SWIG::TYPE_p_int]
55
- # @param size [Integer]
56
- # @return [Array<Integer>]
57
- def int_array_c_to_ruby(c_array, size)
58
- size.times.map {|index| int_getitem(c_array, index)}
59
- end
60
-
61
- # @param c_array [SWIG::TYPE_p_double]
62
- # @param size [Integer]
63
- # @return [Array <Double>]
64
- def double_array_c_to_ruby(c_array, size)
65
- size.times.map {|index| double_getitem(c_array, index)}
66
- end
55
+ # @param parameter [Liblinear::Parameter]
56
+ # @param labels [Array <Integer>]
57
+ # @examples [Array [Array <Float> or Hash]
58
+ # @bias [<Float>]
59
+ # @return [Liblinear::Model]
60
+ def train(parameter, labels, examples, bias = -1)
61
+ parameter = Liblinear::Parameter.new(parameter)
62
+ problem = Liblinear::Problem.new(labels, examples, bias)
63
+ error_message = self.check_parameter(problem, parameter)
64
+ raise Liblinear::InvalidParameter, error_message if error_message
65
+ Liblinear::Model.train(problem, parameter)
66
+ end
67
67
 
68
- # @param examples [Array <Hash, Array>]
69
- # @return [Integer]
70
- def max_index(examples)
71
- max_index = 0
72
- examples.each do |example|
73
- if example.is_a?(Hash)
74
- max_index = [max_index, example.keys.max].max if example.size > 0
75
- else
76
- max_index = [max_index, example.size].max
77
- end
68
+ # @param model [Liblinear::Model]
69
+ # @param examples [Array <Float> or Hash]
70
+ # @return [Integer]
71
+ def predict(model, example)
72
+ feature_node = Liblinear::FeatureNode.new(example, model.feature_size, model.bias)
73
+ prediction = Liblinearswig.predict(model.swig, feature_node.swig)
74
+ feature_node.delete
75
+ prediction
78
76
  end
79
- max_index
80
- end
81
77
 
82
- # @param array [Array]
83
- # @return [Hash]
84
- def array_to_hash(array)
85
- raise ArgumentError unless array.is_a?(Array)
86
- hash = {}
87
- key = 1
88
- array.each do |value|
89
- hash[key] = value
90
- key += 1
78
+ # @param model [Liblinear::Model]
79
+ # @examples [Array <Float> or Hash]
80
+ # @return [Array <Float>]
81
+ def predict_probabilities(model, example)
82
+ feature_node = Liblinear::FeatureNode.new(example, model.feature_size, model.bias)
83
+ probability_swig = Liblinearswig.new_double(model.class_size)
84
+ Liblinearswig.predict_probability(model.swig, feature_node.swig, probability_swig)
85
+ probability = Liblinear::Array::Double.decode(probability_swig, model.class_size)
86
+ Liblinear::Array::Double.delete(probability_swig)
87
+ feature_node.delete
88
+ probability
91
89
  end
92
- hash
93
- end
94
90
 
95
- # @param example [Hash, Array]
96
- # @param max_value_index [Integer]
97
- # @param bias [Double]
98
- # @return [Liblinearswig::Feature_node]
99
- def convert_to_feature_node_array(example, max_value_index, bias = -1)
100
- example = array_to_hash(example) if example.is_a?(Array)
91
+ # @param model [Liblinear::Model]
92
+ # @examples [Array <Float> or Hash]
93
+ # @return [Array <Float>]
94
+ def predict_values(model, example)
95
+ feature_node = Liblinear::FeatureNode.new(example, model.feature_size, model.bias)
96
+ values_swig = Liblinearswig.new_double(model.class_size)
97
+ Liblinearswig.predict_values(model.swig, feature_node.swig, values_swig)
98
+ values = Liblinear::Array::Double.decode(values_swig, model.class_size)
99
+ Liblinear::Array::Double.delete(values_swig)
100
+ feature_node.delete
101
+ values
102
+ end
101
103
 
102
- example_indexes = []
103
- example.each_key do |key|
104
- example_indexes << key
104
+ # @param model [Liblinear::Model]
105
+ # @param feature_id [Integer]
106
+ # @param label_index [Integer]
107
+ # @return [Float]
108
+ def decision_function_coefficient(model, feature_id, label_index)
109
+ Liblinearswig.get_decfun_coef(model.swig, feature_id, label_index)
105
110
  end
106
- example_indexes.sort!
107
111
 
108
- if bias >= 0
109
- feature_nodes = Liblinearswig.feature_node_array(example_indexes.size + 2)
110
- Liblinearswig.feature_node_array_set(feature_nodes, example_indexes.size, max_value_index + 1, bias)
111
- Liblinearswig.feature_node_array_set(feature_nodes, example_indexes.size + 1, -1, 0)
112
- else
113
- feature_nodes = Liblinearswig.feature_node_array(example_indexes.size + 1)
114
- Liblinearswig.feature_node_array_set(feature_nodes, example_indexes.size, -1, 0)
112
+ # @param model [Liblinear::Model]
113
+ # @param label_index [Integer]
114
+ # @return [Float]
115
+ def decision_function_bias(model, label_index)
116
+ Liblinearswig.get_decfun_bias(model.swig, label_index)
115
117
  end
116
118
 
117
- f_index = 0
118
- example_indexes.each do |e_index|
119
- Liblinearswig.feature_node_array_set(feature_nodes, f_index, e_index, example[e_index])
120
- f_index += 1
119
+ # @param model [Liblinear::Model]
120
+ # @return [Array <Integer>]
121
+ def labels(model)
122
+ labels_swig = Liblinearswig.new_int(model.class_size)
123
+ Liblinearswig.get_labels(model.swig, labels_swig)
124
+ labels = Liblinear::Array::Integer.decode(labels_swig, model.class_size)
125
+ Liblinear::Array::Integer.delete(labels_swig)
126
+ labels
121
127
  end
122
- feature_nodes
123
128
  end
124
129
  end
@@ -0,0 +1,21 @@
1
+ $: << File.expand_path(File.join(__FILE__, '..', '..', '..', '..', 'lib'))
2
+ require 'liblinear'
3
+
4
+ describe Liblinear::Array::Double do
5
+ before do
6
+ @array = Liblinear::Array::Double.new([1.0, 1.1, 1.2])
7
+ end
8
+
9
+ describe '#decode' do
10
+ it 'returns decoded array' do
11
+ expect(Liblinear::Array::Double.decode(@array.swig, 3)).to eq([1.0, 1.1, 1.2])
12
+ end
13
+ end
14
+
15
+ describe '#delete' do
16
+ it 'delete array' do
17
+ Liblinear::Array::Double.delete(@array.swig)
18
+ expect(Liblinear::Array::Double.decode(@array.swig, 3)).not_to eq([1.0, 1.1, 1.2])
19
+ end
20
+ end
21
+ end