numo-liblinear 2.2.1 → 2.4.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/CHANGELOG.md +8 -0
- data/LICENSE.txt +1 -1
- data/ext/numo/liblinear/liblinearext.cpp +1 -1
- data/ext/numo/liblinear/liblinearext.hpp +4 -1
- data/ext/numo/liblinear/src/COPYRIGHT +1 -1
- data/ext/numo/liblinear/src/linear.cpp +104 -113
- data/ext/numo/liblinear/src/linear.h +3 -1
- data/lib/numo/liblinear/version.rb +1 -1
- data/sig/numo/liblinear.rbs +1 -0
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c3697071b67bf2898d6c75e461c4b40cfc1c665b06d36b007e2572487805afb
|
4
|
+
data.tar.gz: 595a2214e41b9654422cad1bf4604b1cb84df40f1430b5b7756e23bbb1f4ee95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cffb328201a5ad1be57613933dd28bf03e47c2cd439c5e601cf7b5c284d8cf0b2abcc0ad8aeab57d3e63651d43854a418e366b44d96d29a3b53e984f72b66407
|
7
|
+
data.tar.gz: c547f4c52f80c049e9ccb9eb033321367821bb3fc3fc474e99bf7acca9c7421435a30acc267aa6f4a65bff968abb47f4a2b0c5d5abb3426de12d6c1a55807af0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# [[2.4.0](https://github.com/yoshoku/numo-liblinear/compare/v2.3.0...v2.4.0)] - 2025-05-31
|
2
|
+
|
3
|
+
- Update bundled LIBLINEAR to 2.49
|
4
|
+
- Add `w_recalc` parameter.
|
5
|
+
|
6
|
+
# 2.3.0
|
7
|
+
- Update bundled LIBLINEAR to 2.46
|
8
|
+
|
1
9
|
# 2.2.1
|
2
10
|
- Fix build failure with Xcode 14 and Ruby 3.1.x.
|
3
11
|
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* Copyright (c) 2019-
|
2
|
+
* Copyright (c) 2019-2024 Atsushi Tatsuma
|
3
3
|
* All rights reserved.
|
4
4
|
*
|
5
5
|
* Redistribution and use in source and binary forms, with or without
|
@@ -234,6 +234,8 @@ LibLinearParameter* convertHashToLibLinearParameter(VALUE param_hash) {
|
|
234
234
|
el = rb_hash_aref(param_hash, ID2SYM(rb_intern("init_sol")));
|
235
235
|
param->init_sol = !NIL_P(el) ? convertNArrayToVectorXd(el) : NULL;
|
236
236
|
param->regularize_bias = 1;
|
237
|
+
el = rb_hash_aref(param_hash, ID2SYM(rb_intern("w_recalc")));
|
238
|
+
param->w_recalc = !NIL_P(el) ? (RTEST(el) ? true : false) : false;
|
237
239
|
return param;
|
238
240
|
}
|
239
241
|
|
@@ -250,6 +252,7 @@ VALUE convertLibLinearParameterToHash(const LibLinearParameter* const param) {
|
|
250
252
|
rb_hash_aset(param_hash, ID2SYM(rb_intern("p")), DBL2NUM(param->p));
|
251
253
|
rb_hash_aset(param_hash, ID2SYM(rb_intern("nu")), DBL2NUM(param->nu));
|
252
254
|
rb_hash_aset(param_hash, ID2SYM(rb_intern("init_sol")), Qnil);
|
255
|
+
rb_hash_aset(param_hash, ID2SYM(rb_intern("w_recalc")), param->w_recalc ? Qtrue : Qfalse);
|
253
256
|
return param_hash;
|
254
257
|
}
|
255
258
|
|
@@ -1064,6 +1064,21 @@ static int solve_l2r_l1l2_svc(const problem *prob, const parameter *param, doubl
|
|
1064
1064
|
info("Objective value = %lf\n",v/2);
|
1065
1065
|
info("nSV = %d\n",nSV);
|
1066
1066
|
|
1067
|
+
// Reconstruct w from the primal-dual relationship w=sum(\alpha_i y_i x_i)
|
1068
|
+
// This may reduce the weight density. Some zero weights become non-zeros
|
1069
|
+
// due to the numerical update w <- w + (alpha[i] - alpha_old) y_i x_i.
|
1070
|
+
if (param->w_recalc)
|
1071
|
+
{
|
1072
|
+
for(i=0; i<w_size; i++)
|
1073
|
+
w[i] = 0;
|
1074
|
+
for(i=0; i<l; i++)
|
1075
|
+
{
|
1076
|
+
feature_node * const xi = prob->x[i];
|
1077
|
+
if(alpha[i] > 0)
|
1078
|
+
sparse_operator::axpy(y[i]*alpha[i], xi, w);
|
1079
|
+
}
|
1080
|
+
}
|
1081
|
+
|
1067
1082
|
delete [] QD;
|
1068
1083
|
delete [] alpha;
|
1069
1084
|
delete [] y;
|
@@ -2155,75 +2170,62 @@ static int solve_l1r_lr(const problem *prob_col, const parameter *param, double
|
|
2155
2170
|
return newton_iter;
|
2156
2171
|
}
|
2157
2172
|
|
2158
|
-
|
2159
|
-
|
2160
|
-
|
2161
|
-
|
2162
|
-
feature_node*
|
2173
|
+
static int compare_feature_node(const void *a, const void *b)
|
2174
|
+
{
|
2175
|
+
double a_value = (*(feature_node *)a).value;
|
2176
|
+
double b_value = (*(feature_node *)b).value;
|
2177
|
+
int a_index = (*(feature_node *)a).index;
|
2178
|
+
int b_index = (*(feature_node *)b).index;
|
2163
2179
|
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
a = new feature_node[max_size];
|
2168
|
-
_type = type;
|
2169
|
-
}
|
2170
|
-
~heap()
|
2171
|
-
{
|
2172
|
-
delete [] a;
|
2173
|
-
}
|
2174
|
-
bool cmp(const feature_node& left, const feature_node& right)
|
2175
|
-
{
|
2176
|
-
if(_type == MIN)
|
2177
|
-
return left.value > right.value;
|
2178
|
-
else
|
2179
|
-
return left.value < right.value;
|
2180
|
-
}
|
2181
|
-
int size()
|
2182
|
-
{
|
2183
|
-
return _size;
|
2184
|
-
}
|
2185
|
-
void push(feature_node node)
|
2180
|
+
if(a_value < b_value)
|
2181
|
+
return -1;
|
2182
|
+
else if(a_value == b_value)
|
2186
2183
|
{
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
{
|
2192
|
-
int p = (i-1)/2;
|
2193
|
-
if(cmp(a[p], a[i]))
|
2194
|
-
{
|
2195
|
-
swap(a[i], a[p]);
|
2196
|
-
i = p;
|
2197
|
-
}
|
2198
|
-
else
|
2199
|
-
break;
|
2200
|
-
}
|
2184
|
+
if(a_index < b_index)
|
2185
|
+
return -1;
|
2186
|
+
else if(a_index == b_index)
|
2187
|
+
return 0;
|
2201
2188
|
}
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2189
|
+
return 1;
|
2190
|
+
}
|
2191
|
+
|
2192
|
+
// elements before the returned index are < pivot, while those after are >= pivot
|
2193
|
+
static int partition(feature_node *nodes, int low, int high)
|
2194
|
+
{
|
2195
|
+
int i;
|
2196
|
+
int index;
|
2197
|
+
|
2198
|
+
swap(nodes[low + rand()%(high-low+1)], nodes[high]); // select and move pivot to the end
|
2199
|
+
|
2200
|
+
index = low;
|
2201
|
+
for(i = low; i < high; i++)
|
2202
|
+
if (compare_feature_node(&nodes[i], &nodes[high]) == -1)
|
2208
2203
|
{
|
2209
|
-
|
2210
|
-
|
2211
|
-
if(r < _size && cmp(a[l], a[r]))
|
2212
|
-
l = r;
|
2213
|
-
if(cmp(a[i], a[l]))
|
2214
|
-
{
|
2215
|
-
swap(a[i], a[l]);
|
2216
|
-
i = l;
|
2217
|
-
}
|
2218
|
-
else
|
2219
|
-
break;
|
2204
|
+
swap(nodes[index], nodes[i]);
|
2205
|
+
index++;
|
2220
2206
|
}
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2207
|
+
|
2208
|
+
swap(nodes[high], nodes[index]);
|
2209
|
+
return index;
|
2210
|
+
}
|
2211
|
+
|
2212
|
+
// rearrange nodes so that
|
2213
|
+
// nodes[i] <= nodes[k] for all i < k
|
2214
|
+
// nodes[k] <= nodes[j] for all j > k
|
2215
|
+
// low and high are the bounds of the index range during the rearranging process
|
2216
|
+
static void quick_select_min_k(feature_node *nodes, int low, int high, int k)
|
2217
|
+
{
|
2218
|
+
int pivot;
|
2219
|
+
if(low == high || high < k)
|
2220
|
+
return;
|
2221
|
+
pivot = partition(nodes, low, high);
|
2222
|
+
if(pivot == k)
|
2223
|
+
return;
|
2224
|
+
else if(k-1 < pivot)
|
2225
|
+
return quick_select_min_k(nodes, low, pivot-1, k);
|
2226
|
+
else
|
2227
|
+
return quick_select_min_k(nodes, pivot+1, high, k);
|
2228
|
+
}
|
2227
2229
|
|
2228
2230
|
// A two-level coordinate descent algorithm for
|
2229
2231
|
// a scaled one-class SVM dual problem
|
@@ -2262,11 +2264,12 @@ static int solve_oneclass_svm(const problem *prob, const parameter *param, doubl
|
|
2262
2264
|
int max_iter = 1000;
|
2263
2265
|
int active_size = l;
|
2264
2266
|
|
2265
|
-
double negGmax; // max { -grad(f)_i |
|
2266
|
-
double negGmin; // min { -grad(f)_i |
|
2267
|
-
|
2268
|
-
|
2269
|
-
|
2267
|
+
double negGmax; // max { -grad(f)_i | i in Iup }
|
2268
|
+
double negGmin; // min { -grad(f)_i | i in Ilow }
|
2269
|
+
// Iup = { i | alpha_i < 1 }, Ilow = { i | alpha_i > 0 }
|
2270
|
+
feature_node *max_negG_of_Iup = new feature_node[l];
|
2271
|
+
feature_node *min_negG_of_Ilow = new feature_node[l];
|
2272
|
+
feature_node node;
|
2270
2273
|
|
2271
2274
|
int n = (int)(nu*l); // # of alpha's at upper bound
|
2272
2275
|
for(i=0; i<n; i++)
|
@@ -2328,9 +2331,8 @@ static int solve_oneclass_svm(const problem *prob, const parameter *param, doubl
|
|
2328
2331
|
}
|
2329
2332
|
|
2330
2333
|
max_inner_iter = max(active_size/10, 1);
|
2331
|
-
|
2332
|
-
|
2333
|
-
struct feature_node node;
|
2334
|
+
int len_Iup = 0;
|
2335
|
+
int len_Ilow = 0;
|
2334
2336
|
for(s=0; s<active_size; s++)
|
2335
2337
|
{
|
2336
2338
|
i = index[s];
|
@@ -2339,44 +2341,28 @@ static int solve_oneclass_svm(const problem *prob, const parameter *param, doubl
|
|
2339
2341
|
|
2340
2342
|
if (alpha[i] < 1)
|
2341
2343
|
{
|
2342
|
-
|
2343
|
-
|
2344
|
-
else if (min_heap.top().value < node.value)
|
2345
|
-
{
|
2346
|
-
min_heap.pop();
|
2347
|
-
min_heap.push(node);
|
2348
|
-
}
|
2344
|
+
max_negG_of_Iup[len_Iup] = node;
|
2345
|
+
len_Iup++;
|
2349
2346
|
}
|
2350
2347
|
|
2351
2348
|
if (alpha[i] > 0)
|
2352
2349
|
{
|
2353
|
-
|
2354
|
-
|
2355
|
-
else if (max_heap.top().value > node.value)
|
2356
|
-
{
|
2357
|
-
max_heap.pop();
|
2358
|
-
max_heap.push(node);
|
2359
|
-
}
|
2350
|
+
min_negG_of_Ilow[len_Ilow] = node;
|
2351
|
+
len_Ilow++;
|
2360
2352
|
}
|
2361
2353
|
}
|
2362
|
-
max_inner_iter = min(
|
2363
|
-
while (max_heap.size() > max_inner_iter)
|
2364
|
-
max_heap.pop();
|
2365
|
-
while (min_heap.size() > max_inner_iter)
|
2366
|
-
min_heap.pop();
|
2354
|
+
max_inner_iter = min(max_inner_iter, min(len_Iup, len_Ilow));
|
2367
2355
|
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
max_heap.pop();
|
2374
|
-
}
|
2356
|
+
quick_select_min_k(max_negG_of_Iup, 0, len_Iup-1, len_Iup-max_inner_iter);
|
2357
|
+
qsort(&(max_negG_of_Iup[len_Iup-max_inner_iter]), max_inner_iter, sizeof(struct feature_node), compare_feature_node);
|
2358
|
+
|
2359
|
+
quick_select_min_k(min_negG_of_Ilow, 0, len_Ilow-1, max_inner_iter);
|
2360
|
+
qsort(min_negG_of_Ilow, max_inner_iter, sizeof(struct feature_node), compare_feature_node);
|
2375
2361
|
|
2376
2362
|
for (s=0; s<max_inner_iter; s++)
|
2377
2363
|
{
|
2378
|
-
i =
|
2379
|
-
j =
|
2364
|
+
i = max_negG_of_Iup[len_Iup-s-1].index;
|
2365
|
+
j = min_negG_of_Ilow[s].index;
|
2380
2366
|
|
2381
2367
|
if ((alpha[i] == 0 && alpha[j] == 0) ||
|
2382
2368
|
(alpha[i] == 1 && alpha[j] == 1))
|
@@ -2484,15 +2470,14 @@ static int solve_oneclass_svm(const problem *prob, const parameter *param, doubl
|
|
2484
2470
|
*rho = sum_free/nr_free;
|
2485
2471
|
else
|
2486
2472
|
*rho = (ub + lb)/2;
|
2487
|
-
|
2488
2473
|
info("rho = %lf\n", *rho);
|
2489
2474
|
|
2490
2475
|
delete [] QD;
|
2491
2476
|
delete [] G;
|
2492
2477
|
delete [] index;
|
2493
2478
|
delete [] alpha;
|
2494
|
-
delete []
|
2495
|
-
delete []
|
2479
|
+
delete [] max_negG_of_Iup;
|
2480
|
+
delete [] min_negG_of_Ilow;
|
2496
2481
|
|
2497
2482
|
return iter;
|
2498
2483
|
}
|
@@ -3678,10 +3663,10 @@ double get_decfun_rho(const struct model *model_)
|
|
3678
3663
|
|
3679
3664
|
void free_model_content(struct model *model_ptr)
|
3680
3665
|
{
|
3681
|
-
|
3682
|
-
|
3683
|
-
|
3684
|
-
|
3666
|
+
free(model_ptr->w);
|
3667
|
+
model_ptr->w = NULL;
|
3668
|
+
free(model_ptr->label);
|
3669
|
+
model_ptr->label = NULL;
|
3685
3670
|
}
|
3686
3671
|
|
3687
3672
|
void free_and_destroy_model(struct model **model_ptr_ptr)
|
@@ -3691,17 +3676,18 @@ void free_and_destroy_model(struct model **model_ptr_ptr)
|
|
3691
3676
|
{
|
3692
3677
|
free_model_content(model_ptr);
|
3693
3678
|
free(model_ptr);
|
3679
|
+
*model_ptr_ptr = NULL;
|
3694
3680
|
}
|
3695
3681
|
}
|
3696
3682
|
|
3697
3683
|
void destroy_param(parameter* param)
|
3698
3684
|
{
|
3699
|
-
|
3700
|
-
|
3701
|
-
|
3702
|
-
|
3703
|
-
|
3704
|
-
|
3685
|
+
free(param->weight_label);
|
3686
|
+
param->weight_label = NULL;
|
3687
|
+
free(param->weight);
|
3688
|
+
param->weight = NULL;
|
3689
|
+
free(param->init_sol);
|
3690
|
+
param->init_sol = NULL;
|
3705
3691
|
}
|
3706
3692
|
|
3707
3693
|
const char *check_parameter(const problem *prob, const parameter *param)
|
@@ -3750,6 +3736,11 @@ const char *check_parameter(const problem *prob, const parameter *param)
|
|
3750
3736
|
&& param->solver_type != L2R_L2LOSS_SVR)
|
3751
3737
|
return "Initial-solution specification supported only for solvers L2R_LR, L2R_L2LOSS_SVC, and L2R_L2LOSS_SVR";
|
3752
3738
|
|
3739
|
+
if(param->w_recalc == true
|
3740
|
+
&& param->solver_type != L2R_L2LOSS_SVC_DUAL
|
3741
|
+
&& param->solver_type != L2R_L1LOSS_SVC_DUAL)
|
3742
|
+
return "Recalculating w in the end is only for dual solvers for L2-regularized L1/L2-loss SVM";
|
3743
|
+
|
3753
3744
|
return NULL;
|
3754
3745
|
}
|
3755
3746
|
|
@@ -1,7 +1,8 @@
|
|
1
|
+
#include <stdbool.h>
|
1
2
|
#ifndef _LIBLINEAR_H
|
2
3
|
#define _LIBLINEAR_H
|
3
4
|
|
4
|
-
#define LIBLINEAR_VERSION
|
5
|
+
#define LIBLINEAR_VERSION 249
|
5
6
|
|
6
7
|
#ifdef __cplusplus
|
7
8
|
extern "C" {
|
@@ -39,6 +40,7 @@ struct parameter
|
|
39
40
|
double nu;
|
40
41
|
double *init_sol;
|
41
42
|
int regularize_bias;
|
43
|
+
bool w_recalc; /* for -s 1, 3; may be extended to -s 12, 13, 21 */
|
42
44
|
};
|
43
45
|
|
44
46
|
struct model
|
data/sig/numo/liblinear.rbs
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: numo-liblinear
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yoshoku
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: numo-narray
|
@@ -63,7 +62,6 @@ metadata:
|
|
63
62
|
source_code_uri: https://github.com/yoshoku/numo-liblinear
|
64
63
|
documentation_uri: https://yoshoku.github.io/numo-liblinear/doc/
|
65
64
|
rubygems_mfa_required: 'true'
|
66
|
-
post_install_message:
|
67
65
|
rdoc_options: []
|
68
66
|
require_paths:
|
69
67
|
- lib
|
@@ -78,8 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
76
|
- !ruby/object:Gem::Version
|
79
77
|
version: '0'
|
80
78
|
requirements: []
|
81
|
-
rubygems_version: 3.
|
82
|
-
signing_key:
|
79
|
+
rubygems_version: 3.6.7
|
83
80
|
specification_version: 4
|
84
81
|
summary: Numo::Liblinear is a Ruby gem binding to the LIBLINEAR library. Numo::Liblinear
|
85
82
|
makes to use the LIBLINEAR functions with dataset represented by Numo::NArray.
|