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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbd4a8fc87f4ae430183e396e9977337f31b0f3abe565045f7216cd49c30857d
4
- data.tar.gz: 03b97573731e979cda4d3e4d71b6647a6e16b3133fb9227be366292b80135986
3
+ metadata.gz: 5c3697071b67bf2898d6c75e461c4b40cfc1c665b06d36b007e2572487805afb
4
+ data.tar.gz: 595a2214e41b9654422cad1bf4604b1cb84df40f1430b5b7756e23bbb1f4ee95
5
5
  SHA512:
6
- metadata.gz: 139cacb7349ebf5bee31b2292aa1de97eefbdc565affd44390359bf25a91e806d402c7d4104015fd1d6eba9b030b9fda5a7307f00923eaf259e90737559f1391
7
- data.tar.gz: cc50ab17ba43f8d59f83c97daa50d0da5750473fd0b65913b51223b253157b058464b17c7b499e240496c2065eb2f7ef12cb161575bfbce53eb2ceb117967b22
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,4 +1,4 @@
1
- Copyright (c) 2019-2022 Atsushi Tatsuma
1
+ Copyright (c) 2019-2025 Atsushi Tatsuma
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) 2019-2022 Atsushi Tatsuma
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) 2019-2022 Atsushi Tatsuma
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
 
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2007-2022 The LIBLINEAR Project.
2
+ Copyright (c) 2007-2023 The LIBLINEAR Project.
3
3
  All rights reserved.
4
4
 
5
5
  Redistribution and use in source and binary forms, with or without
@@ -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
- struct heap {
2159
- enum HEAP_TYPE { MIN, MAX };
2160
- int _size;
2161
- HEAP_TYPE _type;
2162
- feature_node* a;
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
- heap(int max_size, HEAP_TYPE type)
2165
- {
2166
- _size = 0;
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
- a[_size] = node;
2188
- _size++;
2189
- int i = _size-1;
2190
- while(i)
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
- void pop()
2203
- {
2204
- _size--;
2205
- a[0] = a[_size];
2206
- int i = 0;
2207
- while(i*2+1 < _size)
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
- int l = i*2+1;
2210
- int r = i*2+2;
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
- feature_node top()
2223
- {
2224
- return a[0];
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 | alpha_i < 1 }
2266
- double negGmin; // min { -grad(f)_i | alpha_i > 0 }
2267
-
2268
- int *most_violating_i = new int[l];
2269
- int *most_violating_j = new int[l];
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
- struct heap min_heap = heap(max_inner_iter, heap::MIN);
2332
- struct heap max_heap = heap(max_inner_iter, heap::MAX);
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
- if (min_heap.size() < max_inner_iter)
2343
- min_heap.push(node);
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
- if (max_heap.size() < max_inner_iter)
2354
- max_heap.push(node);
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(min_heap.size(), max_heap.size());
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
- for (s=max_inner_iter-1; s>=0; s--)
2369
- {
2370
- most_violating_i[s] = min_heap.top().index;
2371
- most_violating_j[s] = max_heap.top().index;
2372
- min_heap.pop();
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 = most_violating_i[s];
2379
- j = most_violating_j[s];
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 [] most_violating_i;
2495
- delete [] most_violating_j;
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
- if(model_ptr->w != NULL)
3682
- free(model_ptr->w);
3683
- if(model_ptr->label != NULL)
3684
- free(model_ptr->label);
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
- if(param->weight_label != NULL)
3700
- free(param->weight_label);
3701
- if(param->weight != NULL)
3702
- free(param->weight);
3703
- if(param->init_sol != NULL)
3704
- free(param->init_sol);
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 245
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
@@ -3,6 +3,6 @@
3
3
  module Numo
4
4
  module Liblinear
5
5
  # The version of Numo::Liblienar you are using.
6
- VERSION = '2.2.1'
6
+ VERSION = '2.4.0'
7
7
  end
8
8
  end
@@ -36,6 +36,7 @@ module Numo
36
36
  weight: Numo::DFloat?,
37
37
  p: Float?,
38
38
  nu: Float?,
39
+ w_recalc: bool?,
39
40
  verbose: bool?,
40
41
  random_seed: Integer?
41
42
  }
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.2.1
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: 2022-11-27 00:00:00.000000000 Z
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.3.26
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.