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.
- checksums.yaml +4 -4
- data/README.md +69 -115
- data/lib/liblinear/array/double.rb +26 -0
- data/lib/liblinear/array/integer.rb +26 -0
- data/lib/liblinear/array.rb +15 -0
- data/lib/liblinear/error.rb +1 -1
- data/lib/liblinear/example.rb +29 -0
- data/lib/liblinear/feature_node.rb +40 -0
- data/lib/liblinear/feature_node_matrix.rb +23 -0
- data/lib/liblinear/model.rb +48 -83
- data/lib/liblinear/parameter.rb +72 -31
- data/lib/liblinear/problem.rb +40 -35
- data/lib/liblinear/version.rb +2 -2
- data/lib/liblinear.rb +98 -93
- data/spec/liblinear/array/double_spec.rb +21 -0
- data/spec/liblinear/example_spec.rb +17 -0
- data/spec/liblinear/feature_node_matrix_spec.rb +14 -0
- data/spec/liblinear/feature_node_spec.rb +14 -0
- data/spec/liblinear/model_spec.rb +23 -66
- data/spec/liblinear/parameter_spec.rb +46 -36
- data/spec/liblinear/problem_spec.rb +30 -8
- data/spec/liblinear_spec.rb +36 -76
- metadata +16 -3
- data/lib/liblinear/cross_validator.rb +0 -58
data/lib/liblinear/parameter.rb
CHANGED
@@ -1,42 +1,83 @@
|
|
1
|
-
|
1
|
+
class Liblinear
|
2
2
|
class Parameter
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
23
|
-
def
|
24
|
-
|
25
|
-
|
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
|
-
# @
|
29
|
-
def
|
30
|
-
|
31
|
-
@param.weight = new_double_array(weight)
|
48
|
+
# @return [Liblinearswig::Parameter]
|
49
|
+
def swig
|
50
|
+
@parameter
|
32
51
|
end
|
33
52
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
data/lib/liblinear/problem.rb
CHANGED
@@ -1,45 +1,50 @@
|
|
1
|
-
|
1
|
+
class Liblinear
|
2
2
|
class Problem
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
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
|
21
|
-
|
22
|
-
@
|
23
|
-
@
|
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
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
data/lib/liblinear/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = '0.0
|
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/
|
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
|
-
|
12
|
-
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
|
19
|
+
L2R_L2LOSS_SVC = Liblinearswig::L2R_L2LOSS_SVC
|
15
20
|
L2R_L1LOSS_SVC_DUAL = Liblinearswig::L2R_L1LOSS_SVC_DUAL
|
16
|
-
MCSVM_CS
|
17
|
-
L1R_L2LOSS_SVC
|
18
|
-
L1R_LR
|
19
|
-
L2R_LR_DUAL
|
20
|
-
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Liblinearswig.
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|