tomz-liblinear-ruby-swig 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+