liblinear-ruby 0.0.7 → 1.0.0

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.
@@ -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