tomz-liblinear-ruby-swig 0.1.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.
data/ext/linear.h ADDED
@@ -0,0 +1,69 @@
1
+ #ifndef _LIBLINEAR_H
2
+ #define _LIBLINEAR_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C" {
6
+ #endif
7
+
8
+ struct feature_node
9
+ {
10
+ int index;
11
+ double value;
12
+ };
13
+
14
+ struct problem
15
+ {
16
+ int l, n;
17
+ int *y;
18
+ struct feature_node **x;
19
+ double bias; /* < 0 if no bias term */
20
+ };
21
+
22
+ enum { L2_LR, L2LOSS_SVM_DUAL, L2LOSS_SVM, L1LOSS_SVM_DUAL, MCSVM_CS }; /* solver_type */
23
+
24
+ struct parameter
25
+ {
26
+ int solver_type;
27
+
28
+ /* these are for training only */
29
+ double eps; /* stopping criteria */
30
+ double C;
31
+ int nr_weight;
32
+ int *weight_label;
33
+ double* weight;
34
+ };
35
+
36
+ struct model
37
+ {
38
+ struct parameter param;
39
+ int nr_class; /* number of classes */
40
+ int nr_feature;
41
+ double *w;
42
+ int *label; /* label of each class (label[n]) */
43
+ double bias;
44
+ };
45
+
46
+ struct model* train(const struct problem *prob, const struct parameter *param);
47
+ void cross_validation(const struct problem *prob, const struct parameter *param, int nr_fold, int *target);
48
+
49
+ int predict_values(const struct model *model_, const struct feature_node *x, double* dec_values);
50
+ int predict(const struct model *model_, const struct feature_node *x);
51
+ int predict_probability(const struct model *model_, const struct feature_node *x, double* prob_estimates);
52
+
53
+ int save_model(const char *model_file_name, const struct model *model_);
54
+ struct model *load_model(const char *model_file_name);
55
+
56
+ int get_nr_feature(const struct model *model_);
57
+ int get_nr_class(const struct model *model_);
58
+ void get_labels(const struct model *model_, int* label);
59
+
60
+ void destroy_model(struct model *model_);
61
+ void destroy_param(struct parameter *param);
62
+ const char *check_parameter(const struct problem *prob, const struct parameter *param);
63
+
64
+ #ifdef __cplusplus
65
+ }
66
+ #endif
67
+
68
+ #endif /* _LIBLINEAR_H */
69
+
data/ext/tron.cpp ADDED
@@ -0,0 +1,214 @@
1
+ #include <math.h>
2
+ #include <stdio.h>
3
+ #include <string.h>
4
+ #include <stdarg.h>
5
+ #include "tron.h"
6
+
7
+ #ifndef min
8
+ template <class T> inline T min(T x,T y) { return (x<y)?x:y; }
9
+ #endif
10
+
11
+ #ifndef max
12
+ template <class T> inline T max(T x,T y) { return (x>y)?x:y; }
13
+ #endif
14
+
15
+ #ifdef __cplusplus
16
+ extern "C" {
17
+ #endif
18
+
19
+ extern double dnrm2_(int *, double *, int *);
20
+ extern double ddot_(int *, double *, int *, double *, int *);
21
+ extern int daxpy_(int *, double *, double *, int *, double *, int *);
22
+ extern int dscal_(int *, double *, double *, int *);
23
+
24
+ #ifdef __cplusplus
25
+ }
26
+ #endif
27
+
28
+
29
+ TRON::TRON(const function *fun_obj, double eps, int max_iter)
30
+ {
31
+ this->fun_obj=const_cast<function *>(fun_obj);
32
+ this->eps=eps;
33
+ this->max_iter=max_iter;
34
+ }
35
+
36
+ TRON::~TRON()
37
+ {
38
+ }
39
+
40
+ void TRON::tron(double *w)
41
+ {
42
+ // Parameters for updating the iterates.
43
+ double eta0 = 1e-4, eta1 = 0.25, eta2 = 0.75;
44
+
45
+ // Parameters for updating the trust region size delta.
46
+ double sigma1 = 0.25, sigma2 = 0.5, sigma3 = 4;
47
+
48
+ int n = fun_obj->get_nr_variable();
49
+ int i, cg_iter;
50
+ double delta, snorm, one=1.0;
51
+ double alpha, f, fnew, prered, actred, gs;
52
+ int search = 1, iter = 1, inc = 1;
53
+ double *s = new double[n];
54
+ double *r = new double[n];
55
+ double *w_new = new double[n];
56
+ double *g = new double[n];
57
+
58
+ for (i=0; i<n; i++)
59
+ w[i] = 0;
60
+
61
+ f = fun_obj->fun(w);
62
+ fun_obj->grad(w, g);
63
+ delta = dnrm2_(&n, g, &inc);
64
+ double gnorm1 = delta;
65
+ double gnorm = gnorm1;
66
+
67
+ if (gnorm <= eps*gnorm1)
68
+ search = 0;
69
+
70
+ iter = 1;
71
+
72
+ while (iter <= max_iter && search)
73
+ {
74
+ cg_iter = trcg(delta, g, s, r);
75
+
76
+ memcpy(w_new, w, sizeof(double)*n);
77
+ daxpy_(&n, &one, s, &inc, w_new, &inc);
78
+
79
+ gs = ddot_(&n, g, &inc, s, &inc);
80
+ prered = -0.5*(gs-ddot_(&n, s, &inc, r, &inc));
81
+ fnew = fun_obj->fun(w_new);
82
+
83
+ // Compute the actual reduction.
84
+ actred = f - fnew;
85
+
86
+ // On the first iteration, adjust the initial step bound.
87
+ snorm = dnrm2_(&n, s, &inc);
88
+ if (iter == 1)
89
+ delta = min(delta, snorm);
90
+
91
+ // Compute prediction alpha*snorm of the step.
92
+ if (fnew - f - gs <= 0)
93
+ alpha = sigma3;
94
+ else
95
+ alpha = max(sigma1, -0.5*(gs/(fnew - f - gs)));
96
+
97
+ // Update the trust region bound according to the ratio of actual to predicted reduction.
98
+ if (actred < eta0*prered)
99
+ delta = min(max(alpha, sigma1)*snorm, sigma2*delta);
100
+ else if (actred < eta1*prered)
101
+ delta = max(sigma1*delta, min(alpha*snorm, sigma2*delta));
102
+ else if (actred < eta2*prered)
103
+ delta = max(sigma1*delta, min(alpha*snorm, sigma3*delta));
104
+ else
105
+ delta = max(delta, min(alpha*snorm, sigma3*delta));
106
+
107
+ printf("iter %2d act %5.3e pre %5.3e delta %5.3e f %5.3e |g| %5.3e CG %3d\n", iter, actred, prered, delta, f, gnorm, cg_iter);
108
+
109
+ if (actred > eta0*prered)
110
+ {
111
+ iter++;
112
+ memcpy(w, w_new, sizeof(double)*n);
113
+ f = fnew;
114
+ fun_obj->grad(w, g);
115
+
116
+ gnorm = dnrm2_(&n, g, &inc);
117
+ if (gnorm <= eps*gnorm1)
118
+ break;
119
+ }
120
+ if (f < -1.0e+32)
121
+ {
122
+ printf("warning: f < -1.0e+32\n");
123
+ break;
124
+ }
125
+ if (fabs(actred) <= 0 && prered <= 0)
126
+ {
127
+ printf("warning: actred and prered <= 0\n");
128
+ break;
129
+ }
130
+ if (fabs(actred) <= 1.0e-12*fabs(f) &&
131
+ fabs(prered) <= 1.0e-12*fabs(f))
132
+ {
133
+ printf("warning: actred and prered too small\n");
134
+ break;
135
+ }
136
+ }
137
+
138
+ delete[] g;
139
+ delete[] r;
140
+ delete[] w_new;
141
+ delete[] s;
142
+ }
143
+
144
+ int TRON::trcg(double delta, double *g, double *s, double *r)
145
+ {
146
+ int i, inc = 1;
147
+ int n = fun_obj->get_nr_variable();
148
+ double one = 1;
149
+ double *d = new double[n];
150
+ double *Hd = new double[n];
151
+ double rTr, rnewTrnew, alpha, beta, cgtol;
152
+
153
+ for (i=0; i<n; i++)
154
+ {
155
+ s[i] = 0;
156
+ r[i] = -g[i];
157
+ d[i] = r[i];
158
+ }
159
+ cgtol = 0.1*dnrm2_(&n, g, &inc);
160
+
161
+ int cg_iter = 0;
162
+ rTr = ddot_(&n, r, &inc, r, &inc);
163
+ while (1)
164
+ {
165
+ if (dnrm2_(&n, r, &inc) <= cgtol)
166
+ break;
167
+ cg_iter++;
168
+ fun_obj->Hv(d, Hd);
169
+
170
+ alpha = rTr/ddot_(&n, d, &inc, Hd, &inc);
171
+ daxpy_(&n, &alpha, d, &inc, s, &inc);
172
+ if (dnrm2_(&n, s, &inc) > delta)
173
+ {
174
+ printf("cg reaches trust region boundary\n");
175
+ alpha = -alpha;
176
+ daxpy_(&n, &alpha, d, &inc, s, &inc);
177
+
178
+ double std = ddot_(&n, s, &inc, d, &inc);
179
+ double sts = ddot_(&n, s, &inc, s, &inc);
180
+ double dtd = ddot_(&n, d, &inc, d, &inc);
181
+ double dsq = delta*delta;
182
+ double rad = sqrt(std*std + dtd*(dsq-sts));
183
+ if (std >= 0)
184
+ alpha = (dsq - sts)/(std + rad);
185
+ else
186
+ alpha = (rad - std)/dtd;
187
+ daxpy_(&n, &alpha, d, &inc, s, &inc);
188
+ alpha = -alpha;
189
+ daxpy_(&n, &alpha, Hd, &inc, r, &inc);
190
+ break;
191
+ }
192
+ alpha = -alpha;
193
+ daxpy_(&n, &alpha, Hd, &inc, r, &inc);
194
+ rnewTrnew = ddot_(&n, r, &inc, r, &inc);
195
+ beta = rnewTrnew/rTr;
196
+ dscal_(&n, &beta, d, &inc);
197
+ daxpy_(&n, &one, r, &inc, d, &inc);
198
+ rTr = rnewTrnew;
199
+ }
200
+
201
+ delete[] d;
202
+ delete[] Hd;
203
+
204
+ return(cg_iter);
205
+ }
206
+
207
+ double TRON::norm_inf(int n, double *x)
208
+ {
209
+ double dmax = fabs(x[0]);
210
+ for (int i=1; i<n; i++)
211
+ if (fabs(x[i]) >= dmax)
212
+ dmax = fabs(x[i]);
213
+ return(dmax);
214
+ }
data/ext/tron.h ADDED
@@ -0,0 +1,32 @@
1
+ #ifndef _TRON_H
2
+ #define _TRON_H
3
+
4
+ class function
5
+ {
6
+ public:
7
+ virtual double fun(double *w) = 0 ;
8
+ virtual void grad(double *w, double *g) = 0 ;
9
+ virtual void Hv(double *s, double *Hs) = 0 ;
10
+
11
+ virtual int get_nr_variable(void) = 0 ;
12
+ virtual ~function(void){}
13
+ };
14
+
15
+ class TRON
16
+ {
17
+ public:
18
+ TRON(const function *fun_obj, double eps = 0.1, int max_iter = 1000);
19
+ ~TRON();
20
+
21
+ void tron(double *w);
22
+
23
+ private:
24
+ int trcg(double delta, double *g, double *s, double *r);
25
+ double norm_inf(int n, double *x);
26
+
27
+ double eps;
28
+ int max_iter;
29
+ function *fun_obj;
30
+ };
31
+
32
+ #endif
data/lib/linear.rb ADDED
@@ -0,0 +1,349 @@
1
+ require 'liblinear'
2
+ include Liblinear
3
+
4
+ def _int_array(seq)
5
+ size = seq.size
6
+ array = new_int(size)
7
+ i = 0
8
+ for item in seq
9
+ int_setitem(array,i,item)
10
+ i = i + 1
11
+ end
12
+ return array
13
+ end
14
+
15
+ def _double_array(seq)
16
+ size = seq.size
17
+ array = new_double(size)
18
+ i = 0
19
+ for item in seq
20
+ double_setitem(array,i,item)
21
+ i = i + 1
22
+ end
23
+ return array
24
+ end
25
+
26
+ def _free_int_array(x)
27
+ if !x.nil? # and !x.empty?
28
+ delete_int(x)
29
+ end
30
+ end
31
+
32
+ def _free_double_array(x)
33
+ if !x.nil? # and !x.empty?
34
+ delete_double(x)
35
+ end
36
+ end
37
+
38
+ def _int_array_to_list(x,n)
39
+ list = []
40
+ (0..n-1).each {|i| list << int_getitem(x,i) }
41
+ return list
42
+ end
43
+
44
+ def _double_array_to_list(x,n)
45
+ list = []
46
+ (0..n-1).each {|i| list << double_getitem(x,i) }
47
+ return list
48
+ end
49
+
50
+ class LParameter
51
+ attr_accessor :param
52
+
53
+ def initialize(*args)
54
+ @param = Parameter.new
55
+ @param.solver_type = L2_LR
56
+ @param.C = 1
57
+ @param.eps = 0.01
58
+ @param.nr_weight = 0
59
+ @param.weight_label = _int_array([])
60
+ @param.weight = _double_array([])
61
+
62
+ args[0].each {|k,v|
63
+ self.send("#{k}=",v)
64
+ } if !args[0].nil?
65
+ end
66
+
67
+ def method_missing(m, *args)
68
+ #print m.to_s
69
+ #puts args.inspect
70
+ if m.to_s == 'weight_label='
71
+ @weight_label_len = args[0].size
72
+ pargs = _int_array(args[0])
73
+ _free_int_array(@param.weight_label)
74
+ elsif m.to_s == 'weight='
75
+ @weight_len = args[0].size
76
+ pargs = _double_array(args[0])
77
+ _free_double_array(@param.weight)
78
+ else
79
+ pargs = args[0]
80
+ end
81
+
82
+ if m.to_s.index('=')
83
+ @param.send("#{m}",pargs)
84
+ else
85
+ @param.send("#{m}")
86
+ end
87
+
88
+ end
89
+
90
+ def inspect
91
+ "LParameter: solver_type=#{@param.solver_type} C=#{@param.C} eps=#{@param.eps}"
92
+ end
93
+
94
+ def destroy
95
+ _free_int_array(@param.weight_label)
96
+ _free_double_array(@param.weight)
97
+ delete_parameter(@param)
98
+ @param = nil
99
+ end
100
+ end
101
+
102
+ def _convert_to_feature_node_array(x, bias=-1)
103
+ # convert a sequence or mapping to an feature_node array
104
+
105
+ # Find non zero elements
106
+ iter_range = []
107
+ if x.class == Hash
108
+ x.each {|k, v|
109
+ # all zeros kept due to the precomputed kernel; no good solution yet
110
+ iter_range << k #if v != 0
111
+ }
112
+ elsif x.class == Array
113
+ x.each_index {|j|
114
+ iter_range << j #if x[j] != 0
115
+ }
116
+ else
117
+ raise TypeError,"data must be a hash or an array"
118
+ end
119
+
120
+ iter_range.sort!
121
+ if bias >=0
122
+ data = feature_node_array(iter_range.size+2)
123
+ #puts "bias element (#{iter_range.size},#{bias})"
124
+ feature_node_array_set(data,iter_range.size,iter_range.size,bias)
125
+ feature_node_array_set(data,iter_range.size+1,-1,0)
126
+ else
127
+ data = feature_node_array(iter_range.size+1)
128
+ feature_node_array_set(data,iter_range.size,-1,0)
129
+ end
130
+
131
+ j = 0
132
+ for k in iter_range
133
+ #puts "element #{j}= (#{k},#{x[k]})"
134
+ feature_node_array_set(data,j,k,x[k])
135
+ j = j + 1
136
+ end
137
+ return data
138
+ end
139
+
140
+
141
+ class LProblem
142
+ attr_accessor :prob, :maxlen, :size
143
+
144
+ def initialize(y,x,bias)
145
+ # assert_equal(y.size, x.size)
146
+ @prob = prob = Problem.new
147
+ @size = size = y.size
148
+
149
+ @y_array = y_array = new_int(size)
150
+ for i in (0..size-1)
151
+ int_setitem(@y_array,i,y[i])
152
+ end
153
+
154
+ @x_matrix = x_matrix = feature_node_matrix(size)
155
+ @data = []
156
+ @maxlen = 0 #max number of features
157
+ for i in (0..size-1)
158
+ data = _convert_to_feature_node_array(x[i], bias)
159
+ @data << data
160
+ feature_node_matrix_set(x_matrix,i,data)
161
+ if x[i].class == Hash
162
+ if x[i].size > 0
163
+ @maxlen = [@maxlen,x[i].keys.max].max
164
+ end
165
+ else
166
+ @maxlen = [@maxlen,x[i].size].max
167
+ end
168
+ end
169
+
170
+ prob.y = y_array
171
+ prob.x = x_matrix
172
+ prob.bias = bias
173
+ prob.l = size
174
+ prob.n = @maxlen
175
+ if bias >= 0
176
+ prob.n += 1
177
+ end
178
+ end
179
+
180
+ def inspect
181
+ "LProblem: size = #{size} n=#{prob.n} bias=#{prob.bias} maxlen=#{@maxlen}"
182
+ end
183
+
184
+ def destroy
185
+ delete_problem(@prob)
186
+ delete_int(@y_array)
187
+ for i in (0..size-1)
188
+ feature_node_array_destroy(@data[i])
189
+ end
190
+ feature_node_matrix_destroy(@x_matrix)
191
+ end
192
+ end
193
+
194
+ class LModel
195
+ attr_accessor :model, :probability
196
+
197
+ def initialize(arg1,arg2=nil)
198
+ if arg2 == nil
199
+ # create model from file
200
+ filename = arg1
201
+ @model = load_model(filename)
202
+ else
203
+ # create model from problem and parameter
204
+ prob,param = arg1,arg2
205
+ @prob = prob
206
+ msg = check_parameter(prob.prob,param.param)
207
+ raise "ValueError", msg if msg
208
+ @model = Liblinear::train(prob.prob,param.param)
209
+ end
210
+ #setup some classwide variables
211
+ @nr_class = Liblinear::get_nr_class(@model)
212
+ #create labels(classes)
213
+ intarr = new_int(@nr_class)
214
+ Liblinear::get_labels(@model,intarr)
215
+ @labels = _int_array_to_list(intarr, @nr_class)
216
+ delete_int(intarr)
217
+ end
218
+
219
+ def predict(x)
220
+ data = _convert_to_feature_node_array(x, @model.bias)
221
+ ret = Liblinear::predict(@model,data)
222
+ feature_node_array_destroy(data)
223
+ return ret
224
+ end
225
+
226
+
227
+ def get_nr_class
228
+ return @nr_class
229
+ end
230
+
231
+ def get_labels
232
+ return @labels
233
+ end
234
+
235
+ def predict_values_raw(x)
236
+ #convert x into feature_node, allocate a double array for return
237
+ n = (@nr_class*(@nr_class-1)/2).floor
238
+ data = _convert_to_feature_node_array(x, @model.bias)
239
+ dblarr = new_double(n)
240
+ Liblinear::predict_values(@model, data, dblarr)
241
+ ret = _double_array_to_list(dblarr, n)
242
+ delete_double(dblarr)
243
+ feature_node_array_destroy(data)
244
+ return ret
245
+ end
246
+
247
+ def predict_values(x)
248
+ v=predict_values_raw(x)
249
+ #puts v.inspect
250
+ if false
251
+ #if @svm_type == NU_SVR or @svm_type == EPSILON_SVR or @svm_type == ONE_CLASS
252
+ return v[0]
253
+ else #self.svm_type == C_SVC or self.svm_type == NU_SVC
254
+ count = 0
255
+
256
+ # create a width x height array
257
+ width = @labels.size
258
+ height = @labels.size
259
+ d = Array.new(width)
260
+ d.map! { Array.new(height) }
261
+
262
+ for i in (0..@labels.size-1)
263
+ for j in (i+1..@labels.size-1)
264
+ d[@labels[i]][@labels[j]] = v[count]
265
+ d[@labels[j]][@labels[i]] = -v[count]
266
+ count += 1
267
+ end
268
+ end
269
+ return d
270
+ end
271
+ end
272
+
273
+ def predict_probability(x)
274
+ # if not @probability
275
+ # raise TypeError, "model does not support probabiliy estimates"
276
+ # end
277
+
278
+ #convert x into feature_node, alloc a double array to receive probabilities
279
+ data = _convert_to_feature_node_array(x,@model.bias)
280
+ dblarr = new_double(@nr_class)
281
+ pred = Liblinear::predict_probability(@model, data, dblarr)
282
+ pv = _double_array_to_list(dblarr, @nr_class)
283
+ delete_double(dblarr)
284
+ feature_node_array_destroy(data)
285
+ p = {}
286
+ for i in (0..@labels.size-1)
287
+ p[@labels[i]] = pv[i]
288
+ end
289
+ return pred, p
290
+ end
291
+
292
+ # def get_svr_probability
293
+ # #leave the Error checking to svm.cpp code
294
+ # ret = Liblinear::get_svr_probability(@model)
295
+ # if ret == 0
296
+ # raise TypeError, "not a regression model or probability information not available"
297
+ # end
298
+ # return ret
299
+ # end
300
+
301
+ # def get_svr_pdf
302
+ # #get_svr_probability will handle error checking
303
+ # sigma = get_svr_probability()
304
+ # return Proc.new{|z| exp(-z.abs/sigma)/(2*sigma)} # TODO: verify this works
305
+ # end
306
+
307
+ def save(filename)
308
+ save_model(filename,@model)
309
+ end
310
+
311
+ def destroy
312
+ destroy_model(@model)
313
+ end
314
+ end
315
+
316
+ def cross_validation(prob, param, fold)
317
+ target = new_int(prob.size)
318
+ Liblinear::cross_validation(prob.prob, param.param, fold, target)
319
+ ret = _int_array_to_list(target, prob.size)
320
+ delete_int(target)
321
+ return ret
322
+ end
323
+
324
+ def read_file filename
325
+ labels = []
326
+ samples = []
327
+ max_index = 0
328
+
329
+ f = File.open(filename)
330
+ f.each do |line|
331
+ elems = line.split
332
+ sample = {}
333
+ for e in elems[1..-1]
334
+ points = e.split(":")
335
+ sample[points[0].to_i] = points[1].to_f
336
+ if points[0].to_i < max_index
337
+ max_index = points[0].to_i
338
+ end
339
+ end
340
+ labels << elems[0].to_i
341
+ samples << sample
342
+ #print elems[0].to_i
343
+ #print " - "
344
+ #puts sample.inspect
345
+ end
346
+ puts "#{filename}: #{samples.size} samples loaded."
347
+ return labels,samples
348
+ end
349
+