Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.LibSVM/3.12/LibSVM-3.12/SVM.cs

Last change on this file was 11371, checked in by mkommend, 10 years ago

#2233: Merged r11036 into stable.

File size: 83.0 KB
Line 
1/*
2 * Copyright (c) 2000-2012 Chih-Chung Chang and Chih-Jen Lin
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * 3. Neither name of copyright holders nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 * C# port from the original java sources by Gabriel Kronberger (Sept. 2012)
34 */
35
36using System.Globalization;
37using System.IO;
38using System.Linq;
39using System.Threading;
40
41namespace LibSVM {
42  //
43  // Kernel Cache
44  //
45  // l is the number of total data items
46  // size is the cache size limit in bytes
47  //
48
49  using System;
50
51  class Cache {
52    private readonly int l;
53    private long size;
54    private sealed class head_t {
55      public head_t prev, next; // a cicular list
56      public float[] data;
57      public int len;   // data[0,len) is cached in this entry
58    }
59    private readonly head_t[] head;
60    private head_t lru_head;
61
62    public Cache(int l_, long size_) {
63      l = l_;
64      size = size_;
65      head = new head_t[l];
66      for (int i = 0; i < l; i++) head[i] = new head_t();
67      size /= 4;
68      size -= l * (16 / 4); // sizeof(head_t) == 16
69      size = Math.Max(size, 2 * (long)l);  // cache must be large enough for two columns
70      lru_head = new head_t();
71      lru_head.next = lru_head.prev = lru_head;
72    }
73
74    private void lru_delete(head_t h) {
75      // delete from current location
76      h.prev.next = h.next;
77      h.next.prev = h.prev;
78    }
79
80    private void lru_insert(head_t h) {
81      // insert to last position
82      h.next = lru_head;
83      h.prev = lru_head.prev;
84      h.prev.next = h;
85      h.next.prev = h;
86    }
87
88    // request data [0,len)
89    // return some position p where [p,len) need to be filled
90    // (p >= len if nothing needs to be filled)
91    // java: simulate pointer using single-element array
92    public int get_data(int index, float[][] data, int len) {
93      head_t h = head[index];
94      if (h.len > 0) lru_delete(h);
95      int more = len - h.len;
96
97      if (more > 0) {
98        // free old space
99        while (size < more) {
100          head_t old = lru_head.next;
101          lru_delete(old);
102          size += old.len;
103          old.data = null;
104          old.len = 0;
105        }
106
107        // allocate new space
108        float[] new_data = new float[len];
109        if (h.data != null) {
110          Array.Copy(h.data, 0, new_data, 0, h.len);
111        }
112        h.data = new_data;
113        size -= more; { int _ = h.len; h.len = len; len = _; }
114      }
115
116      lru_insert(h);
117      data[0] = h.data;
118      return len;
119    }
120
121    public void swap_index(int i, int j) {
122      if (i == j) return;
123
124      if (head[i].len > 0) lru_delete(head[i]);
125      if (head[j].len > 0) lru_delete(head[j]); { float[] _ = head[i].data; head[i].data = head[j].data; head[j].data = _; }
126      { int _ = head[i].len; head[i].len = head[j].len; head[j].len = _; }
127      if (head[i].len > 0) lru_insert(head[i]);
128      if (head[j].len > 0) lru_insert(head[j]);
129
130      if (i > j) { int _ = i; i = j; j = _; }
131      for (head_t h = lru_head.next; h != lru_head; h = h.next) {
132        if (h.len > i) {
133          if (h.len > j) { float _ = h.data[i]; h.data[i] = h.data[j]; h.data[j] = _; } else {
134            // give up
135            lru_delete(h);
136            size += h.len;
137            h.data = null;
138            h.len = 0;
139          }
140        }
141      }
142    }
143  }
144
145  //
146  // Kernel evaluation
147  //
148  // the static method k_function is for doing single kernel evaluation
149  // the constructor of Kernel prepares to calculate the l*l kernel matrix
150  // the member function get_Q is for getting one column from the Q Matrix
151  //
152  abstract class QMatrix {
153    public abstract float[] get_Q(int column, int len);
154    public abstract double[] get_QD();
155    public abstract void swap_index(int i, int j);
156  };
157
158  abstract class Kernel : QMatrix {
159    private svm_node[][] x;
160    private readonly double[] x_square;
161
162    // svm_parameter
163    private readonly int kernel_type;
164    private readonly int degree;
165    private readonly double gamma;
166    private readonly double coef0;
167
168    public override abstract float[] get_Q(int column, int len);
169    public override abstract double[] get_QD();
170
171    public override void swap_index(int i, int j) {
172      { svm_node[] _ = x[i]; x[i] = x[j]; x[j] = _; }
173      if (x_square != null) { double _ = x_square[i]; x_square[i] = x_square[j]; x_square[j] = _; }
174    }
175
176    private static double powi(double @base, int times) {
177      double tmp = @base, ret = 1.0;
178
179      for (int t = times; t > 0; t /= 2) {
180        if (t % 2 == 1) ret *= tmp;
181        tmp = tmp * tmp;
182      }
183      return ret;
184    }
185
186    protected virtual double kernel_function(int i, int j) {
187      switch (kernel_type) {
188        case svm_parameter.LINEAR:
189          return dot(x[i], x[j]);
190        case svm_parameter.POLY:
191          return powi(gamma * dot(x[i], x[j]) + coef0, degree);
192        case svm_parameter.RBF:
193          return Math.Exp(-gamma * (x_square[i] + x_square[j] - 2 * dot(x[i], x[j])));
194        case svm_parameter.SIGMOID:
195          return Math.Tanh(gamma * dot(x[i], x[j]) + coef0);
196        case svm_parameter.PRECOMPUTED:
197          return x[i][(int)(x[j][0].value)].value;
198        default:
199          return 0; // java
200      }
201    }
202
203    public Kernel(int l, svm_node[][] x_, svm_parameter param) {
204      this.kernel_type = param.kernel_type;
205      this.degree = param.degree;
206      this.gamma = param.gamma;
207      this.coef0 = param.coef0;
208
209      x = (svm_node[][])x_.Clone();
210
211      if (kernel_type == svm_parameter.RBF) {
212        x_square = new double[l];
213        for (int i = 0; i < l; i++)
214          x_square[i] = dot(x[i], x[i]);
215      } else x_square = null;
216    }
217
218    static double dot(svm_node[] x, svm_node[] y) {
219      double sum = 0;
220      int xlen = x.Length;
221      int ylen = y.Length;
222      int i = 0;
223      int j = 0;
224      while (i < xlen && j < ylen) {
225        if (x[i].index == y[j].index)
226          sum += x[i++].value * y[j++].value;
227        else {
228          if (x[i].index > y[j].index)
229            ++j;
230          else
231            ++i;
232        }
233      }
234      return sum;
235    }
236
237    public static double k_function(svm_node[] x, svm_node[] y,
238            svm_parameter param) {
239      switch (param.kernel_type) {
240        case svm_parameter.LINEAR:
241          return dot(x, y);
242        case svm_parameter.POLY:
243          return powi(param.gamma * dot(x, y) + param.coef0, param.degree);
244        case svm_parameter.RBF: {
245            double sum = 0;
246            int xlen = x.Length;
247            int ylen = y.Length;
248            int i = 0;
249            int j = 0;
250            while (i < xlen && j < ylen) {
251              if (x[i].index == y[j].index) {
252                double d = x[i++].value - y[j++].value;
253                sum += d * d;
254              } else if (x[i].index > y[j].index) {
255                sum += y[j].value * y[j].value;
256                ++j;
257              } else {
258                sum += x[i].value * x[i].value;
259                ++i;
260              }
261            }
262
263            while (i < xlen) {
264              sum += x[i].value * x[i].value;
265              ++i;
266            }
267
268            while (j < ylen) {
269              sum += y[j].value * y[j].value;
270              ++j;
271            }
272
273            return Math.Exp(-param.gamma * sum);
274          }
275        case svm_parameter.SIGMOID:
276          return Math.Tanh(param.gamma * dot(x, y) + param.coef0);
277        case svm_parameter.PRECOMPUTED:
278          return x[(int)(y[0].value)].value;
279        default:
280          return 0; // java
281      }
282    }
283  }
284
285  // An SMO algorithm in Fan et al., JMLR 6(2005), p. 1889--1918
286  // Solves:
287  //
288  //  min 0.5(\alpha^T Q \alpha) + p^T \alpha
289  //
290  //    y^T \alpha = \delta
291  //    y_i = +1 or -1
292  //    0 <= alpha_i <= Cp for y_i = 1
293  //    0 <= alpha_i <= Cn for y_i = -1
294  //
295  // Given:
296  //
297  //  Q, p, y, Cp, Cn, and an initial feasible point \alpha
298  //  l is the size of vectors and matrices
299  //  eps is the stopping tolerance
300  //
301  // solution will be put in \alpha, objective value will be put in obj
302  //
303  class Solver {
304    protected int active_size;
305    protected short[] y;
306    protected double[] G;   // gradient of objective function
307    protected const byte LOWER_BOUND = 0;
308    protected const byte UPPER_BOUND = 1;
309    protected const byte FREE = 2;
310    protected byte[] alpha_status;  // LOWER_BOUND, UPPER_BOUND, FREE
311    protected double[] alpha;
312    protected QMatrix Q;
313    protected double[] QD;
314    protected double eps;
315    protected double Cp, Cn;
316    protected double[] p;
317    protected int[] active_set;
318    protected double[] G_bar;   // gradient, if we treat free variables as 0
319    protected int l;
320    protected bool unshrink;  // XXX
321
322    protected const double INF = double.PositiveInfinity;
323
324    protected virtual double get_C(int i) {
325      return (y[i] > 0) ? Cp : Cn;
326    }
327    protected virtual void update_alpha_status(int i) {
328      if (alpha[i] >= get_C(i))
329        alpha_status[i] = UPPER_BOUND;
330      else if (alpha[i] <= 0)
331        alpha_status[i] = LOWER_BOUND;
332      else alpha_status[i] = FREE;
333    }
334    protected virtual bool is_upper_bound(int i) { return alpha_status[i] == UPPER_BOUND; }
335    protected virtual bool is_lower_bound(int i) { return alpha_status[i] == LOWER_BOUND; }
336    protected virtual bool is_free(int i) { return alpha_status[i] == FREE; }
337
338    // java: information about solution except alpha,
339    // because we cannot return multiple values otherwise...
340    public class SolutionInfo {
341      public double obj;
342      public double rho;
343      public double upper_bound_p;
344      public double upper_bound_n;
345      public double r;  // for Solver_NU
346    }
347
348    protected virtual void swap_index(int i, int j) {
349      Q.swap_index(i, j); { short _ = y[i]; y[i] = y[j]; y[j] = _; }
350      { double _ = G[i]; G[i] = G[j]; G[j] = _; }
351      { byte _ = alpha_status[i]; alpha_status[i] = alpha_status[j]; alpha_status[j] = _; }
352      { double _ = alpha[i]; alpha[i] = alpha[j]; alpha[j] = _; }
353      { double _ = p[i]; p[i] = p[j]; p[j] = _; }
354      { int _ = active_set[i]; active_set[i] = active_set[j]; active_set[j] = _; }
355      { double _ = G_bar[i]; G_bar[i] = G_bar[j]; G_bar[j] = _; }
356    }
357
358    protected virtual void reconstruct_gradient() {
359      // reconstruct inactive elements of G from G_bar and free variables
360
361      if (active_size == l) return;
362
363      int i, j;
364      int nr_free = 0;
365
366      for (j = active_size; j < l; j++)
367        G[j] = G_bar[j] + p[j];
368
369      for (j = 0; j < active_size; j++)
370        if (is_free(j))
371          nr_free++;
372
373      if (2 * nr_free < active_size)
374        svm.info("WARNING: using -h 0 may be faster" + Environment.NewLine + Environment.NewLine);
375
376      if (nr_free * l > 2 * active_size * (l - active_size)) {
377        for (i = active_size; i < l; i++) {
378          float[] Q_i = Q.get_Q(i, active_size);
379          for (j = 0; j < active_size; j++)
380            if (is_free(j))
381              G[i] += alpha[j] * Q_i[j];
382        }
383      } else {
384        for (i = 0; i < active_size; i++)
385          if (is_free(i)) {
386            float[] Q_i = Q.get_Q(i, l);
387            double alpha_i = alpha[i];
388            for (j = active_size; j < l; j++)
389              G[j] += alpha_i * Q_i[j];
390          }
391      }
392    }
393
394    public virtual void Solve(int l, QMatrix Q, double[] p_, short[] y_,
395         double[] alpha_, double Cp, double Cn, double eps, SolutionInfo si, int shrinking) {
396      this.l = l;
397      this.Q = Q;
398      QD = Q.get_QD();
399      p = (double[])p_.Clone();
400      y = (short[])y_.Clone();
401      alpha = (double[])alpha_.Clone();
402      this.Cp = Cp;
403      this.Cn = Cn;
404      this.eps = eps;
405      this.unshrink = false;
406
407      // initialize alpha_status
408      {
409        alpha_status = new byte[l];
410        for (int i = 0; i < l; i++)
411          update_alpha_status(i);
412      }
413
414      // initialize active set (for shrinking)
415      {
416        active_set = new int[l];
417        for (int i = 0; i < l; i++)
418          active_set[i] = i;
419        active_size = l;
420      }
421
422      // initialize gradient
423      {
424        G = new double[l];
425        G_bar = new double[l];
426        int i;
427        for (i = 0; i < l; i++) {
428          G[i] = p[i];
429          G_bar[i] = 0;
430        }
431        for (i = 0; i < l; i++)
432          if (!is_lower_bound(i)) {
433            float[] Q_i = Q.get_Q(i, l);
434            double alpha_i = alpha[i];
435            int j;
436            for (j = 0; j < l; j++)
437              G[j] += alpha_i * Q_i[j];
438            if (is_upper_bound(i))
439              for (j = 0; j < l; j++)
440                G_bar[j] += get_C(i) * Q_i[j];
441          }
442      }
443
444      // optimization step
445
446      int iter = 0;
447      int max_iter = Math.Max(10000000, l > int.MaxValue / 100 ? int.MaxValue : 100 * l);
448      int counter = Math.Min(l, 1000) + 1;
449      int[] working_set = new int[2];
450
451      while (iter < max_iter) {
452        // show progress and do shrinking
453
454        if (--counter == 0) {
455          counter = Math.Min(l, 1000);
456          if (shrinking != 0) do_shrinking();
457          svm.info(".");
458        }
459
460        if (select_working_set(working_set) != 0) {
461          // reconstruct the whole gradient
462          reconstruct_gradient();
463          // reset active set size and check
464          active_size = l;
465          svm.info("*");
466          if (select_working_set(working_set) != 0)
467            break;
468          else
469            counter = 1;  // do shrinking next iteration
470        }
471
472        int i = working_set[0];
473        int j = working_set[1];
474
475        ++iter;
476
477        // update alpha[i] and alpha[j], handle bounds carefully
478
479        float[] Q_i = Q.get_Q(i, active_size);
480        float[] Q_j = Q.get_Q(j, active_size);
481
482        double C_i = get_C(i);
483        double C_j = get_C(j);
484
485        double old_alpha_i = alpha[i];
486        double old_alpha_j = alpha[j];
487
488        if (y[i] != y[j]) {
489          double quad_coef = QD[i] + QD[j] + 2 * Q_i[j];
490          if (quad_coef <= 0)
491            quad_coef = 1e-12;
492          double delta = (-G[i] - G[j]) / quad_coef;
493          double diff = alpha[i] - alpha[j];
494          alpha[i] += delta;
495          alpha[j] += delta;
496
497          if (diff > 0) {
498            if (alpha[j] < 0) {
499              alpha[j] = 0;
500              alpha[i] = diff;
501            }
502          } else {
503            if (alpha[i] < 0) {
504              alpha[i] = 0;
505              alpha[j] = -diff;
506            }
507          }
508          if (diff > C_i - C_j) {
509            if (alpha[i] > C_i) {
510              alpha[i] = C_i;
511              alpha[j] = C_i - diff;
512            }
513          } else {
514            if (alpha[j] > C_j) {
515              alpha[j] = C_j;
516              alpha[i] = C_j + diff;
517            }
518          }
519        } else {
520          double quad_coef = QD[i] + QD[j] - 2 * Q_i[j];
521          if (quad_coef <= 0)
522            quad_coef = 1e-12;
523          double delta = (G[i] - G[j]) / quad_coef;
524          double sum = alpha[i] + alpha[j];
525          alpha[i] -= delta;
526          alpha[j] += delta;
527
528          if (sum > C_i) {
529            if (alpha[i] > C_i) {
530              alpha[i] = C_i;
531              alpha[j] = sum - C_i;
532            }
533          } else {
534            if (alpha[j] < 0) {
535              alpha[j] = 0;
536              alpha[i] = sum;
537            }
538          }
539          if (sum > C_j) {
540            if (alpha[j] > C_j) {
541              alpha[j] = C_j;
542              alpha[i] = sum - C_j;
543            }
544          } else {
545            if (alpha[i] < 0) {
546              alpha[i] = 0;
547              alpha[j] = sum;
548            }
549          }
550        }
551
552        // update G
553
554        double delta_alpha_i = alpha[i] - old_alpha_i;
555        double delta_alpha_j = alpha[j] - old_alpha_j;
556
557        for (int k = 0; k < active_size; k++) {
558          G[k] += Q_i[k] * delta_alpha_i + Q_j[k] * delta_alpha_j;
559        }
560
561        // update alpha_status and G_bar
562
563        {
564          bool ui = is_upper_bound(i);
565          bool uj = is_upper_bound(j);
566          update_alpha_status(i);
567          update_alpha_status(j);
568          int k;
569          if (ui != is_upper_bound(i)) {
570            Q_i = Q.get_Q(i, l);
571            if (ui)
572              for (k = 0; k < l; k++)
573                G_bar[k] -= C_i * Q_i[k];
574            else
575              for (k = 0; k < l; k++)
576                G_bar[k] += C_i * Q_i[k];
577          }
578
579          if (uj != is_upper_bound(j)) {
580            Q_j = Q.get_Q(j, l);
581            if (uj)
582              for (k = 0; k < l; k++)
583                G_bar[k] -= C_j * Q_j[k];
584            else
585              for (k = 0; k < l; k++)
586                G_bar[k] += C_j * Q_j[k];
587          }
588        }
589
590      }
591
592      if (iter >= max_iter) {
593        if (active_size < l) {
594          // reconstruct the whole gradient to calculate objective value
595          reconstruct_gradient();
596          active_size = l;
597          svm.info("*");
598        }
599        svm.info("WARNING: reaching max number of iterations" + Environment.NewLine);
600      }
601
602      // calculate rho
603
604      si.rho = calculate_rho();
605
606      // calculate objective value
607      {
608        double v = 0;
609        int i;
610        for (i = 0; i < l; i++)
611          v += alpha[i] * (G[i] + p[i]);
612
613        si.obj = v / 2;
614      }
615
616      // put back the solution
617      {
618        for (int i = 0; i < l; i++)
619          alpha_[active_set[i]] = alpha[i];
620      }
621
622      si.upper_bound_p = Cp;
623      si.upper_bound_n = Cn;
624
625      svm.info("optimization finished, #iter = " + iter + Environment.NewLine);
626    }
627
628    // return 1 if already optimal, return 0 otherwise
629    protected virtual int select_working_set(int[] working_set) {
630      // return i,j such that
631      // i: maximizes -y_i * grad(f)_i, i in I_up(\alpha)
632      // j: mimimizes the decrease of obj value
633      //    (if quadratic coefficeint <= 0, replace it with tau)
634      //    -y_j*grad(f)_j < -y_i*grad(f)_i, j in I_low(\alpha)
635
636      double Gmax = -INF;
637      double Gmax2 = -INF;
638      int Gmax_idx = -1;
639      int Gmin_idx = -1;
640      double obj_diff_min = INF;
641
642      for (int t = 0; t < active_size; t++)
643        if (y[t] == +1) {
644          if (!is_upper_bound(t))
645            if (-G[t] >= Gmax) {
646              Gmax = -G[t];
647              Gmax_idx = t;
648            }
649        } else {
650          if (!is_lower_bound(t))
651            if (G[t] >= Gmax) {
652              Gmax = G[t];
653              Gmax_idx = t;
654            }
655        }
656
657      int i = Gmax_idx;
658      float[] Q_i = null;
659      if (i != -1) // null Q_i not accessed: Gmax=-INF if i=-1
660        Q_i = Q.get_Q(i, active_size);
661
662      for (int j = 0; j < active_size; j++) {
663        if (y[j] == +1) {
664          if (!is_lower_bound(j)) {
665            double grad_diff = Gmax + G[j];
666            if (G[j] >= Gmax2)
667              Gmax2 = G[j];
668            if (grad_diff > 0) {
669              double obj_diff;
670              double quad_coef = QD[i] + QD[j] - 2.0 * y[i] * Q_i[j];
671              if (quad_coef > 0)
672                obj_diff = -(grad_diff * grad_diff) / quad_coef;
673              else
674                obj_diff = -(grad_diff * grad_diff) / 1e-12;
675
676              if (obj_diff <= obj_diff_min) {
677                Gmin_idx = j;
678                obj_diff_min = obj_diff;
679              }
680            }
681          }
682        } else {
683          if (!is_upper_bound(j)) {
684            double grad_diff = Gmax - G[j];
685            if (-G[j] >= Gmax2)
686              Gmax2 = -G[j];
687            if (grad_diff > 0) {
688              double obj_diff;
689              double quad_coef = QD[i] + QD[j] + 2.0 * y[i] * Q_i[j];
690              if (quad_coef > 0)
691                obj_diff = -(grad_diff * grad_diff) / quad_coef;
692              else
693                obj_diff = -(grad_diff * grad_diff) / 1e-12;
694
695              if (obj_diff <= obj_diff_min) {
696                Gmin_idx = j;
697                obj_diff_min = obj_diff;
698              }
699            }
700          }
701        }
702      }
703
704      if (Gmax + Gmax2 < eps)
705        return 1;
706
707      working_set[0] = Gmax_idx;
708      working_set[1] = Gmin_idx;
709      return 0;
710    }
711
712    private bool be_shrunk(int i, double Gmax1, double Gmax2) {
713      if (is_upper_bound(i)) {
714        if (y[i] == +1)
715          return (-G[i] > Gmax1);
716        else
717          return (-G[i] > Gmax2);
718      } else if (is_lower_bound(i)) {
719        if (y[i] == +1)
720          return (G[i] > Gmax2);
721        else
722          return (G[i] > Gmax1);
723      } else
724        return (false);
725    }
726
727    protected virtual void do_shrinking() {
728      int i;
729      double Gmax1 = -INF;    // max { -y_i * grad(f)_i | i in I_up(\alpha) }
730      double Gmax2 = -INF;    // max { y_i * grad(f)_i | i in I_low(\alpha) }
731
732      // find maximal violating pair first
733      for (i = 0; i < active_size; i++) {
734        if (y[i] == +1) {
735          if (!is_upper_bound(i)) {
736            if (-G[i] >= Gmax1)
737              Gmax1 = -G[i];
738          }
739          if (!is_lower_bound(i)) {
740            if (G[i] >= Gmax2)
741              Gmax2 = G[i];
742          }
743        } else {
744          if (!is_upper_bound(i)) {
745            if (-G[i] >= Gmax2)
746              Gmax2 = -G[i];
747          }
748          if (!is_lower_bound(i)) {
749            if (G[i] >= Gmax1)
750              Gmax1 = G[i];
751          }
752        }
753      }
754
755      if (unshrink == false && Gmax1 + Gmax2 <= eps * 10) {
756        unshrink = true;
757        reconstruct_gradient();
758        active_size = l;
759      }
760
761      for (i = 0; i < active_size; i++)
762        if (be_shrunk(i, Gmax1, Gmax2)) {
763          active_size--;
764          while (active_size > i) {
765            if (!be_shrunk(active_size, Gmax1, Gmax2)) {
766              swap_index(i, active_size);
767              break;
768            }
769            active_size--;
770          }
771        }
772    }
773
774    protected virtual double calculate_rho() {
775      double r;
776      int nr_free = 0;
777      double ub = INF, lb = -INF, sum_free = 0;
778      for (int i = 0; i < active_size; i++) {
779        double yG = y[i] * G[i];
780
781        if (is_lower_bound(i)) {
782          if (y[i] > 0)
783            ub = Math.Min(ub, yG);
784          else
785            lb = Math.Max(lb, yG);
786        } else if (is_upper_bound(i)) {
787          if (y[i] < 0)
788            ub = Math.Min(ub, yG);
789          else
790            lb = Math.Max(lb, yG);
791        } else {
792          ++nr_free;
793          sum_free += yG;
794        }
795      }
796
797      if (nr_free > 0)
798        r = sum_free / nr_free;
799      else
800        r = (ub + lb) / 2;
801
802      return r;
803    }
804
805  }
806
807  //
808  // Solver for nu-svm classification and regression
809  //
810  // additional constraint: e^T \alpha = constant
811  //
812  internal sealed class Solver_NU : Solver {
813    private SolutionInfo si;
814
815    public override void Solve(int l, QMatrix Q, double[] p, short[] y,
816         double[] alpha, double Cp, double Cn, double eps,
817         SolutionInfo si, int shrinking) {
818      this.si = si;
819      base.Solve(l, Q, p, y, alpha, Cp, Cn, eps, si, shrinking);
820    }
821
822    // return 1 if already optimal, return 0 otherwise
823    protected override int select_working_set(int[] working_set) {
824      // return i,j such that y_i = y_j and
825      // i: maximizes -y_i * grad(f)_i, i in I_up(\alpha)
826      // j: minimizes the decrease of obj value
827      //    (if quadratic coefficeint <= 0, replace it with tau)
828      //    -y_j*grad(f)_j < -y_i*grad(f)_i, j in I_low(\alpha)
829
830      double Gmaxp = -INF;
831      double Gmaxp2 = -INF;
832      int Gmaxp_idx = -1;
833
834      double Gmaxn = -INF;
835      double Gmaxn2 = -INF;
836      int Gmaxn_idx = -1;
837
838      int Gmin_idx = -1;
839      double obj_diff_min = INF;
840
841      for (int t = 0; t < active_size; t++)
842        if (y[t] == +1) {
843          if (!is_upper_bound(t))
844            if (-G[t] >= Gmaxp) {
845              Gmaxp = -G[t];
846              Gmaxp_idx = t;
847            }
848        } else {
849          if (!is_lower_bound(t))
850            if (G[t] >= Gmaxn) {
851              Gmaxn = G[t];
852              Gmaxn_idx = t;
853            }
854        }
855
856      int ip = Gmaxp_idx;
857      int @in = Gmaxn_idx;
858      float[] Q_ip = null;
859      float[] Q_in = null;
860      if (ip != -1) // null Q_ip not accessed: Gmaxp=-INF if ip=-1
861        Q_ip = Q.get_Q(ip, active_size);
862      if (@in != -1)
863        Q_in = Q.get_Q(@in, active_size);
864
865      for (int j = 0; j < active_size; j++) {
866        if (y[j] == +1) {
867          if (!is_lower_bound(j)) {
868            double grad_diff = Gmaxp + G[j];
869            if (G[j] >= Gmaxp2)
870              Gmaxp2 = G[j];
871            if (grad_diff > 0) {
872              double obj_diff;
873              double quad_coef = QD[ip] + QD[j] - 2 * Q_ip[j];
874              if (quad_coef > 0)
875                obj_diff = -(grad_diff * grad_diff) / quad_coef;
876              else
877                obj_diff = -(grad_diff * grad_diff) / 1e-12;
878
879              if (obj_diff <= obj_diff_min) {
880                Gmin_idx = j;
881                obj_diff_min = obj_diff;
882              }
883            }
884          }
885        } else {
886          if (!is_upper_bound(j)) {
887            double grad_diff = Gmaxn - G[j];
888            if (-G[j] >= Gmaxn2)
889              Gmaxn2 = -G[j];
890            if (grad_diff > 0) {
891              double obj_diff;
892              double quad_coef = QD[@in] + QD[j] - 2 * Q_in[j];
893              if (quad_coef > 0)
894                obj_diff = -(grad_diff * grad_diff) / quad_coef;
895              else
896                obj_diff = -(grad_diff * grad_diff) / 1e-12;
897
898              if (obj_diff <= obj_diff_min) {
899                Gmin_idx = j;
900                obj_diff_min = obj_diff;
901              }
902            }
903          }
904        }
905      }
906
907      if (Math.Max(Gmaxp + Gmaxp2, Gmaxn + Gmaxn2) < eps)
908        return 1;
909
910      if (y[Gmin_idx] == +1)
911        working_set[0] = Gmaxp_idx;
912      else
913        working_set[0] = Gmaxn_idx;
914      working_set[1] = Gmin_idx;
915
916      return 0;
917    }
918
919    private bool be_shrunk(int i, double Gmax1, double Gmax2, double Gmax3, double Gmax4) {
920      if (is_upper_bound(i)) {
921        if (y[i] == +1)
922          return (-G[i] > Gmax1);
923        else
924          return (-G[i] > Gmax4);
925      } else if (is_lower_bound(i)) {
926        if (y[i] == +1)
927          return (G[i] > Gmax2);
928        else
929          return (G[i] > Gmax3);
930      } else
931        return (false);
932    }
933
934    protected override void do_shrinking() {
935      double Gmax1 = -INF;  // max { -y_i * grad(f)_i | y_i = +1, i in I_up(\alpha) }
936      double Gmax2 = -INF;  // max { y_i * grad(f)_i | y_i = +1, i in I_low(\alpha) }
937      double Gmax3 = -INF;  // max { -y_i * grad(f)_i | y_i = -1, i in I_up(\alpha) }
938      double Gmax4 = -INF;  // max { y_i * grad(f)_i | y_i = -1, i in I_low(\alpha) }
939
940      // find maximal violating pair first
941      int i;
942      for (i = 0; i < active_size; i++) {
943        if (!is_upper_bound(i)) {
944          if (y[i] == +1) {
945            if (-G[i] > Gmax1) Gmax1 = -G[i];
946          } else if (-G[i] > Gmax4) Gmax4 = -G[i];
947        }
948        if (!is_lower_bound(i)) {
949          if (y[i] == +1) {
950            if (G[i] > Gmax2) Gmax2 = G[i];
951          } else if (G[i] > Gmax3) Gmax3 = G[i];
952        }
953      }
954
955      if (unshrink == false && Math.Max(Gmax1 + Gmax2, Gmax3 + Gmax4) <= eps * 10) {
956        unshrink = true;
957        reconstruct_gradient();
958        active_size = l;
959      }
960
961      for (i = 0; i < active_size; i++)
962        if (be_shrunk(i, Gmax1, Gmax2, Gmax3, Gmax4)) {
963          active_size--;
964          while (active_size > i) {
965            if (!be_shrunk(active_size, Gmax1, Gmax2, Gmax3, Gmax4)) {
966              swap_index(i, active_size);
967              break;
968            }
969            active_size--;
970          }
971        }
972    }
973
974    protected override double calculate_rho() {
975      int nr_free1 = 0, nr_free2 = 0;
976      double ub1 = INF, ub2 = INF;
977      double lb1 = -INF, lb2 = -INF;
978      double sum_free1 = 0, sum_free2 = 0;
979
980      for (int i = 0; i < active_size; i++) {
981        if (y[i] == +1) {
982          if (is_lower_bound(i))
983            ub1 = Math.Min(ub1, G[i]);
984          else if (is_upper_bound(i))
985            lb1 = Math.Max(lb1, G[i]);
986          else {
987            ++nr_free1;
988            sum_free1 += G[i];
989          }
990        } else {
991          if (is_lower_bound(i))
992            ub2 = Math.Min(ub2, G[i]);
993          else if (is_upper_bound(i))
994            lb2 = Math.Max(lb2, G[i]);
995          else {
996            ++nr_free2;
997            sum_free2 += G[i];
998          }
999        }
1000      }
1001
1002      double r1, r2;
1003      if (nr_free1 > 0)
1004        r1 = sum_free1 / nr_free1;
1005      else
1006        r1 = (ub1 + lb1) / 2;
1007
1008      if (nr_free2 > 0)
1009        r2 = sum_free2 / nr_free2;
1010      else
1011        r2 = (ub2 + lb2) / 2;
1012
1013      si.r = (r1 + r2) / 2;
1014      return (r1 - r2) / 2;
1015    }
1016  }
1017
1018  //
1019  // Q matrices for various formulations
1020  //
1021  class SVC_Q : Kernel {
1022    private readonly short[] y;
1023    private readonly Cache cache;
1024    private readonly double[] QD;
1025
1026    public SVC_Q(svm_problem prob, svm_parameter param, short[] y_)
1027      : base(prob.l, prob.x, param) {
1028      y = (short[])y_.Clone();
1029      cache = new Cache(prob.l, (long)(param.cache_size * (1 << 20)));
1030      QD = new double[prob.l];
1031      for (int i = 0; i < prob.l; i++)
1032        QD[i] = kernel_function(i, i);
1033    }
1034
1035    public override float[] get_Q(int i, int len) {
1036      float[][] data = new float[1][];
1037      int start, j;
1038      if ((start = cache.get_data(i, data, len)) < len) {
1039        for (j = start; j < len; j++)
1040          data[0][j] = (float)(y[i] * y[j] * kernel_function(i, j));
1041      }
1042      return data[0];
1043    }
1044
1045    public override double[] get_QD() {
1046      return QD;
1047    }
1048
1049    public override void swap_index(int i, int j) {
1050      cache.swap_index(i, j);
1051      base.swap_index(i, j); { short _ = y[i]; y[i] = y[j]; y[j] = _; }
1052    { double _ = QD[i]; QD[i] = QD[j]; QD[j] = _; }
1053    }
1054  }
1055
1056  class ONE_CLASS_Q : Kernel {
1057    private readonly Cache cache;
1058    private readonly double[] QD;
1059
1060    public ONE_CLASS_Q(svm_problem prob, svm_parameter param)
1061      : base(prob.l, prob.x, param) {
1062      cache = new Cache(prob.l, (long)(param.cache_size * (1 << 20)));
1063      QD = new double[prob.l];
1064      for (int i = 0; i < prob.l; i++)
1065        QD[i] = kernel_function(i, i);
1066    }
1067
1068    public override float[] get_Q(int i, int len) {
1069      float[][] data = new float[1][];
1070      int start, j;
1071      if ((start = cache.get_data(i, data, len)) < len) {
1072        for (j = start; j < len; j++)
1073          data[0][j] = (float)kernel_function(i, j);
1074      }
1075      return data[0];
1076    }
1077
1078    public override double[] get_QD() {
1079      return QD;
1080    }
1081
1082    public override void swap_index(int i, int j) {
1083      cache.swap_index(i, j);
1084      base.swap_index(i, j); { double _ = QD[i]; QD[i] = QD[j]; QD[j] = _; }
1085    }
1086  }
1087
1088  class SVR_Q : Kernel {
1089    private int l;
1090    private Cache cache;
1091    private short[] sign;
1092    private int[] index;
1093    private int next_buffer;
1094    private float[][] buffer;
1095    private readonly double[] QD;
1096
1097    public SVR_Q(svm_problem prob, svm_parameter param)
1098      : base(prob.l, prob.x, param) {
1099      l = prob.l;
1100      cache = new Cache(l, (long)(param.cache_size * (1 << 20)));
1101      QD = new double[2 * l];
1102      sign = new short[2 * l];
1103      index = new int[2 * l];
1104      for (int k = 0; k < l; k++) {
1105        sign[k] = 1;
1106        sign[k + l] = -1;
1107        index[k] = k;
1108        index[k + l] = k;
1109        QD[k] = kernel_function(k, k);
1110        QD[k + l] = QD[k];
1111      }
1112      buffer = new float[2][];
1113      buffer[0] = new float[2 * l];
1114      buffer[1] = new float[2 * l];
1115      next_buffer = 0;
1116    }
1117
1118    public override void swap_index(int i, int j) {
1119      { short _ = sign[i]; sign[i] = sign[j]; sign[j] = _; }
1120      { int _ = index[i]; index[i] = index[j]; index[j] = _; }
1121      { double _ = QD[i]; QD[i] = QD[j]; QD[j] = _; }
1122    }
1123
1124    public override float[] get_Q(int i, int len) {
1125      float[][] data = new float[1][];
1126      int j, real_i = index[i];
1127      if (cache.get_data(real_i, data, l) < l) {
1128        for (j = 0; j < l; j++)
1129          data[0][j] = (float)kernel_function(real_i, j);
1130      }
1131
1132      // reorder and copy
1133      float[] buf = buffer[next_buffer];
1134      next_buffer = 1 - next_buffer;
1135      short si = sign[i];
1136      for (j = 0; j < len; j++)
1137        buf[j] = (float)si * sign[j] * data[0][index[j]];
1138      return buf;
1139    }
1140
1141    public override double[] get_QD() {
1142      return QD;
1143    }
1144  }
1145
1146  public class svm {
1147    //
1148    // construct and solve various formulations
1149    //
1150    public static readonly int LIBSVM_VERSION = 312;
1151    public static readonly Random rand = new Random();
1152
1153    private static Action<string> svm_print_string = (s) => {
1154      Console.Out.Write(s);
1155      Console.Out.Flush();
1156    };
1157
1158    public static void info(String s) {
1159      svm_print_string(s);
1160    }
1161
1162    private static void solve_c_svc(svm_problem prob, svm_parameter param,
1163                                    double[] alpha, Solver.SolutionInfo si,
1164                                    double Cp, double Cn) {
1165      int l = prob.l;
1166      double[] minus_ones = new double[l];
1167      short[] y = new short[l];
1168
1169      int i;
1170
1171      for (i = 0; i < l; i++) {
1172        alpha[i] = 0;
1173        minus_ones[i] = -1;
1174        if (prob.y[i] > 0) y[i] = +1;
1175        else y[i] = -1;
1176      }
1177
1178      Solver s = new Solver();
1179      s.Solve(l, new SVC_Q(prob, param, y), minus_ones, y,
1180              alpha, Cp, Cn, param.eps, si, param.shrinking);
1181
1182      double sum_alpha = 0;
1183      for (i = 0; i < l; i++)
1184        sum_alpha += alpha[i];
1185
1186      if (Cp == Cn)
1187        svm.info("nu = " + sum_alpha / (Cp * prob.l) + Environment.NewLine);
1188
1189      for (i = 0; i < l; i++)
1190        alpha[i] *= y[i];
1191    }
1192
1193    private static void solve_nu_svc(svm_problem prob, svm_parameter param,
1194                                     double[] alpha, Solver.SolutionInfo si) {
1195      int i;
1196      int l = prob.l;
1197      double nu = param.nu;
1198
1199      short[] y = new short[l];
1200
1201      for (i = 0; i < l; i++)
1202        if (prob.y[i] > 0)
1203          y[i] = +1;
1204        else
1205          y[i] = -1;
1206
1207      double sum_pos = nu * l / 2;
1208      double sum_neg = nu * l / 2;
1209
1210      for (i = 0; i < l; i++)
1211        if (y[i] == +1) {
1212          alpha[i] = Math.Min(1.0, sum_pos);
1213          sum_pos -= alpha[i];
1214        } else {
1215          alpha[i] = Math.Min(1.0, sum_neg);
1216          sum_neg -= alpha[i];
1217        }
1218
1219      double[] zeros = new double[l];
1220
1221      for (i = 0; i < l; i++)
1222        zeros[i] = 0;
1223
1224      Solver_NU s = new Solver_NU();
1225      s.Solve(l, new SVC_Q(prob, param, y), zeros, y,
1226              alpha, 1.0, 1.0, param.eps, si, param.shrinking);
1227      double r = si.r;
1228
1229      svm.info("C = " + 1 / r + Environment.NewLine);
1230
1231      for (i = 0; i < l; i++)
1232        alpha[i] *= y[i] / r;
1233
1234      si.rho /= r;
1235      si.obj /= (r * r);
1236      si.upper_bound_p = 1 / r;
1237      si.upper_bound_n = 1 / r;
1238    }
1239
1240    private static void solve_one_class(svm_problem prob, svm_parameter param,
1241                                        double[] alpha, Solver.SolutionInfo si) {
1242      int l = prob.l;
1243      double[] zeros = new double[l];
1244      short[] ones = new short[l];
1245      int i;
1246
1247      int n = (int)(param.nu * prob.l); // # of alpha's at upper bound
1248
1249      for (i = 0; i < n; i++)
1250        alpha[i] = 1;
1251      if (n < prob.l)
1252        alpha[n] = param.nu * prob.l - n;
1253      for (i = n + 1; i < l; i++)
1254        alpha[i] = 0;
1255
1256      for (i = 0; i < l; i++) {
1257        zeros[i] = 0;
1258        ones[i] = 1;
1259      }
1260
1261      Solver s = new Solver();
1262      s.Solve(l, new ONE_CLASS_Q(prob, param), zeros, ones,
1263              alpha, 1.0, 1.0, param.eps, si, param.shrinking);
1264    }
1265
1266    private static void solve_epsilon_svr(svm_problem prob, svm_parameter param,
1267                                          double[] alpha, Solver.SolutionInfo si) {
1268      int l = prob.l;
1269      double[] alpha2 = new double[2 * l];
1270      double[] linear_term = new double[2 * l];
1271      short[] y = new short[2 * l];
1272      int i;
1273
1274      for (i = 0; i < l; i++) {
1275        alpha2[i] = 0;
1276        linear_term[i] = param.p - prob.y[i];
1277        y[i] = 1;
1278
1279        alpha2[i + l] = 0;
1280        linear_term[i + l] = param.p + prob.y[i];
1281        y[i + l] = -1;
1282      }
1283
1284      Solver s = new Solver();
1285      s.Solve(2 * l, new SVR_Q(prob, param), linear_term, y,
1286              alpha2, param.C, param.C, param.eps, si, param.shrinking);
1287
1288      double sum_alpha = 0;
1289      for (i = 0; i < l; i++) {
1290        alpha[i] = alpha2[i] - alpha2[i + l];
1291        sum_alpha += Math.Abs(alpha[i]);
1292      }
1293      svm.info("nu = " + sum_alpha / (param.C * l) + Environment.NewLine);
1294    }
1295
1296    private static void solve_nu_svr(svm_problem prob, svm_parameter param,
1297                                     double[] alpha, Solver.SolutionInfo si) {
1298      int l = prob.l;
1299      double C = param.C;
1300      double[] alpha2 = new double[2 * l];
1301      double[] linear_term = new double[2 * l];
1302      short[] y = new short[2 * l];
1303      int i;
1304
1305      double sum = C * param.nu * l / 2;
1306      for (i = 0; i < l; i++) {
1307        alpha2[i] = alpha2[i + l] = Math.Min(sum, C);
1308        sum -= alpha2[i];
1309
1310        linear_term[i] = -prob.y[i];
1311        y[i] = 1;
1312
1313        linear_term[i + l] = prob.y[i];
1314        y[i + l] = -1;
1315      }
1316
1317      Solver_NU s = new Solver_NU();
1318      s.Solve(2 * l, new SVR_Q(prob, param), linear_term, y,
1319              alpha2, C, C, param.eps, si, param.shrinking);
1320
1321      svm.info("epsilon = " + (-si.r) + Environment.NewLine);
1322
1323      for (i = 0; i < l; i++)
1324        alpha[i] = alpha2[i] - alpha2[i + l];
1325    }
1326
1327    //
1328    // decision_function
1329    //
1330    private sealed class decision_function {
1331      public double[] alpha;
1332      public double rho;
1333    };
1334
1335    private static decision_function svm_train_one(
1336      svm_problem prob, svm_parameter param,
1337      double Cp, double Cn) {
1338      double[] alpha = new double[prob.l];
1339      Solver.SolutionInfo si = new Solver.SolutionInfo();
1340      switch (param.svm_type) {
1341        case svm_parameter.C_SVC:
1342          solve_c_svc(prob, param, alpha, si, Cp, Cn);
1343          break;
1344        case svm_parameter.NU_SVC:
1345          solve_nu_svc(prob, param, alpha, si);
1346          break;
1347        case svm_parameter.ONE_CLASS:
1348          solve_one_class(prob, param, alpha, si);
1349          break;
1350        case svm_parameter.EPSILON_SVR:
1351          solve_epsilon_svr(prob, param, alpha, si);
1352          break;
1353        case svm_parameter.NU_SVR:
1354          solve_nu_svr(prob, param, alpha, si);
1355          break;
1356      }
1357
1358      svm.info("obj = " + si.obj + ", rho = " + si.rho + Environment.NewLine);
1359
1360      // output SVs
1361
1362      int nSV = 0;
1363      int nBSV = 0;
1364      for (int i = 0; i < prob.l; i++) {
1365        if (Math.Abs(alpha[i]) > 0) {
1366          ++nSV;
1367          if (prob.y[i] > 0) {
1368            if (Math.Abs(alpha[i]) >= si.upper_bound_p)
1369              ++nBSV;
1370          } else {
1371            if (Math.Abs(alpha[i]) >= si.upper_bound_n)
1372              ++nBSV;
1373          }
1374        }
1375      }
1376
1377      svm.info("nSV = " + nSV + ", nBSV = " + nBSV + Environment.NewLine);
1378
1379      decision_function f = new decision_function();
1380      f.alpha = alpha;
1381      f.rho = si.rho;
1382      return f;
1383    }
1384
1385    // Platt's binary SVM Probablistic Output: an improvement from Lin et al.
1386    private static void sigmoid_train(int l, double[] dec_values, double[] labels,
1387                                      double[] probAB) {
1388      double A, B;
1389      double prior1 = 0, prior0 = 0;
1390      int i;
1391
1392      for (i = 0; i < l; i++)
1393        if (labels[i] > 0) prior1 += 1;
1394        else prior0 += 1;
1395
1396      int max_iter = 100; // Maximal number of iterations
1397      double min_step = 1e-10; // Minimal step taken in line search
1398      double sigma = 1e-12; // For numerically strict PD of Hessian
1399      double eps = 1e-5;
1400      double hiTarget = (prior1 + 1.0) / (prior1 + 2.0);
1401      double loTarget = 1 / (prior0 + 2.0);
1402      double[] t = new double[l];
1403      double fApB, p, q, h11, h22, h21, g1, g2, det, dA, dB, gd, stepsize;
1404      double newA, newB, newf, d1, d2;
1405      int iter;
1406
1407      // Initial Point and Initial Fun Value
1408      A = 0.0;
1409      B = Math.Log((prior0 + 1.0) / (prior1 + 1.0));
1410      double fval = 0.0;
1411
1412      for (i = 0; i < l; i++) {
1413        if (labels[i] > 0) t[i] = hiTarget;
1414        else t[i] = loTarget;
1415        fApB = dec_values[i] * A + B;
1416        if (fApB >= 0)
1417          fval += t[i] * fApB + Math.Log(1 + Math.Exp(-fApB));
1418        else
1419          fval += (t[i] - 1) * fApB + Math.Log(1 + Math.Exp(fApB));
1420      }
1421      for (iter = 0; iter < max_iter; iter++) {
1422        // Update Gradient and Hessian (use H' = H + sigma I)
1423        h11 = sigma; // numerically ensures strict PD
1424        h22 = sigma;
1425        h21 = 0.0;
1426        g1 = 0.0;
1427        g2 = 0.0;
1428        for (i = 0; i < l; i++) {
1429          fApB = dec_values[i] * A + B;
1430          if (fApB >= 0) {
1431            p = Math.Exp(-fApB) / (1.0 + Math.Exp(-fApB));
1432            q = 1.0 / (1.0 + Math.Exp(-fApB));
1433          } else {
1434            p = 1.0 / (1.0 + Math.Exp(fApB));
1435            q = Math.Exp(fApB) / (1.0 + Math.Exp(fApB));
1436          }
1437          d2 = p * q;
1438          h11 += dec_values[i] * dec_values[i] * d2;
1439          h22 += d2;
1440          h21 += dec_values[i] * d2;
1441          d1 = t[i] - p;
1442          g1 += dec_values[i] * d1;
1443          g2 += d1;
1444        }
1445
1446        // Stopping Criteria
1447        if (Math.Abs(g1) < eps && Math.Abs(g2) < eps)
1448          break;
1449
1450        // Finding Newton direction: -inv(H') * g
1451        det = h11 * h22 - h21 * h21;
1452        dA = -(h22 * g1 - h21 * g2) / det;
1453        dB = -(-h21 * g1 + h11 * g2) / det;
1454        gd = g1 * dA + g2 * dB;
1455
1456
1457        stepsize = 1; // Line Search
1458        while (stepsize >= min_step) {
1459          newA = A + stepsize * dA;
1460          newB = B + stepsize * dB;
1461
1462          // New function value
1463          newf = 0.0;
1464          for (i = 0; i < l; i++) {
1465            fApB = dec_values[i] * newA + newB;
1466            if (fApB >= 0)
1467              newf += t[i] * fApB + Math.Log(1 + Math.Exp(-fApB));
1468            else
1469              newf += (t[i] - 1) * fApB + Math.Log(1 + Math.Exp(fApB));
1470          }
1471          // Check sufficient decrease
1472          if (newf < fval + 0.0001 * stepsize * gd) {
1473            A = newA;
1474            B = newB;
1475            fval = newf;
1476            break;
1477          } else
1478            stepsize = stepsize / 2.0;
1479        }
1480
1481        if (stepsize < min_step) {
1482          svm.info("Line search fails in two-class probability estimates" + Environment.NewLine);
1483          break;
1484        }
1485      }
1486
1487      if (iter >= max_iter)
1488        svm.info("Reaching maximal iterations in two-class probability estimates" + Environment.NewLine);
1489      probAB[0] = A;
1490      probAB[1] = B;
1491    }
1492
1493    private static double sigmoid_predict(double decision_value, double A, double B) {
1494      double fApB = decision_value * A + B;
1495      if (fApB >= 0)
1496        return Math.Exp(-fApB) / (1.0 + Math.Exp(-fApB));
1497      else
1498        return 1.0 / (1 + Math.Exp(fApB));
1499    }
1500
1501    // Method 2 from the multiclass_prob paper by Wu, Lin, and Weng
1502    private static void multiclass_probability(int k, double[][] r, double[] p) {
1503      int t, j;
1504      int iter = 0, max_iter = Math.Max(100, k);
1505      double[][] Q = new double[k][];
1506      double[] Qp = new double[k];
1507      double pQp, eps = 0.005 / k;
1508
1509      for (t = 0; t < k; t++) {
1510        Q[t] = new double[k];
1511        p[t] = 1.0 / k; // Valid if k = 1
1512        Q[t][t] = 0;
1513        for (j = 0; j < t; j++) {
1514          Q[t][t] += r[j][t] * r[j][t];
1515          Q[t][j] = Q[j][t];
1516        }
1517        for (j = t + 1; j < k; j++) {
1518          Q[t][t] += r[j][t] * r[j][t];
1519          Q[t][j] = -r[j][t] * r[t][j];
1520        }
1521      }
1522      for (iter = 0; iter < max_iter; iter++) {
1523        // stopping condition, recalculate QP,pQP for numerical accuracy
1524        pQp = 0;
1525        for (t = 0; t < k; t++) {
1526          Qp[t] = 0;
1527          for (j = 0; j < k; j++)
1528            Qp[t] += Q[t][j] * p[j];
1529          pQp += p[t] * Qp[t];
1530        }
1531        double max_error = 0;
1532        for (t = 0; t < k; t++) {
1533          double error = Math.Abs(Qp[t] - pQp);
1534          if (error > max_error)
1535            max_error = error;
1536        }
1537        if (max_error < eps) break;
1538
1539        for (t = 0; t < k; t++) {
1540          double diff = (-Qp[t] + pQp) / Q[t][t];
1541          p[t] += diff;
1542          pQp = (pQp + diff * (diff * Q[t][t] + 2 * Qp[t])) / (1 + diff) / (1 + diff);
1543          for (j = 0; j < k; j++) {
1544            Qp[j] = (Qp[j] + diff * Q[t][j]) / (1 + diff);
1545            p[j] /= (1 + diff);
1546          }
1547        }
1548      }
1549      if (iter >= max_iter)
1550        svm.info("Exceeds max_iter in multiclass_prob" + Environment.NewLine);
1551    }
1552
1553    // Cross-validation decision values for probability estimates
1554    private static void svm_binary_svc_probability(svm_problem prob, svm_parameter param, double Cp, double Cn,
1555                                                   double[] probAB) {
1556      int i;
1557      int nr_fold = 5;
1558      int[] perm = new int[prob.l];
1559      double[] dec_values = new double[prob.l];
1560
1561      // random shuffle
1562      for (i = 0; i < prob.l; i++) perm[i] = i;
1563      for (i = 0; i < prob.l; i++) {
1564        int j = i + rand.Next(prob.l - i);
1565        {
1566          int _ = perm[i];
1567          perm[i] = perm[j];
1568          perm[j] = _;
1569        }
1570      }
1571      for (i = 0; i < nr_fold; i++) {
1572        int begin = i * prob.l / nr_fold;
1573        int end = (i + 1) * prob.l / nr_fold;
1574        int j, k;
1575        svm_problem subprob = new svm_problem();
1576
1577        subprob.l = prob.l - (end - begin);
1578        subprob.x = new svm_node[subprob.l][];
1579        subprob.y = new double[subprob.l];
1580
1581        k = 0;
1582        for (j = 0; j < begin; j++) {
1583          subprob.x[k] = prob.x[perm[j]];
1584          subprob.y[k] = prob.y[perm[j]];
1585          ++k;
1586        }
1587        for (j = end; j < prob.l; j++) {
1588          subprob.x[k] = prob.x[perm[j]];
1589          subprob.y[k] = prob.y[perm[j]];
1590          ++k;
1591        }
1592        int p_count = 0, n_count = 0;
1593        for (j = 0; j < k; j++)
1594          if (subprob.y[j] > 0)
1595            p_count++;
1596          else
1597            n_count++;
1598
1599        if (p_count == 0 && n_count == 0)
1600          for (j = begin; j < end; j++)
1601            dec_values[perm[j]] = 0;
1602        else if (p_count > 0 && n_count == 0)
1603          for (j = begin; j < end; j++)
1604            dec_values[perm[j]] = 1;
1605        else if (p_count == 0 && n_count > 0)
1606          for (j = begin; j < end; j++)
1607            dec_values[perm[j]] = -1;
1608        else {
1609          svm_parameter subparam = (svm_parameter)param.Clone();
1610          subparam.probability = 0;
1611          subparam.C = 1.0;
1612          subparam.nr_weight = 2;
1613          subparam.weight_label = new int[2];
1614          subparam.weight = new double[2];
1615          subparam.weight_label[0] = +1;
1616          subparam.weight_label[1] = -1;
1617          subparam.weight[0] = Cp;
1618          subparam.weight[1] = Cn;
1619          svm_model submodel = svm_train(subprob, subparam);
1620          for (j = begin; j < end; j++) {
1621            double[] dec_value = new double[1];
1622            svm_predict_values(submodel, prob.x[perm[j]], dec_value);
1623            dec_values[perm[j]] = dec_value[0];
1624            // ensure +1 -1 order; reason not using CV subroutine
1625            dec_values[perm[j]] *= submodel.label[0];
1626          }
1627        }
1628      }
1629      sigmoid_train(prob.l, dec_values, prob.y, probAB);
1630    }
1631
1632    // Return parameter of a Laplace distribution
1633    private static double svm_svr_probability(svm_problem prob, svm_parameter param) {
1634      int i;
1635      int nr_fold = 5;
1636      double[] ymv = new double[prob.l];
1637      double mae = 0;
1638
1639      svm_parameter newparam = (svm_parameter)param.Clone();
1640      newparam.probability = 0;
1641      svm_cross_validation(prob, newparam, nr_fold, ymv);
1642      for (i = 0; i < prob.l; i++) {
1643        ymv[i] = prob.y[i] - ymv[i];
1644        mae += Math.Abs(ymv[i]);
1645      }
1646      mae /= prob.l;
1647      double std = Math.Sqrt(2 * mae * mae);
1648      int count = 0;
1649      mae = 0;
1650      for (i = 0; i < prob.l; i++)
1651        if (Math.Abs(ymv[i]) > 5 * std)
1652          count = count + 1;
1653        else
1654          mae += Math.Abs(ymv[i]);
1655      mae /= (prob.l - count);
1656      svm.info("Prob. model for test data: target value = predicted value + z, " + Environment.NewLine
1657               + "z: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma=" + mae + Environment.NewLine);
1658      return mae;
1659    }
1660
1661    // label: label name, start: begin of each class, count: #data of classes, perm: indices to the original data
1662    // perm, length l, must be allocated before calling this subroutine
1663    private static void svm_group_classes(svm_problem prob, int[] nr_class_ret, int[][] label_ret, int[][] start_ret,
1664                                          int[][] count_ret, int[] perm) {
1665      int l = prob.l;
1666      int max_nr_class = 16;
1667      int nr_class = 0;
1668      int[] label = new int[max_nr_class];
1669      int[] count = new int[max_nr_class];
1670      int[] data_label = new int[l];
1671      int i;
1672
1673      for (i = 0; i < l; i++) {
1674        int this_label = (int)(prob.y[i]);
1675        int j;
1676        for (j = 0; j < nr_class; j++) {
1677          if (this_label == label[j]) {
1678            ++count[j];
1679            break;
1680          }
1681        }
1682        data_label[i] = j;
1683        if (j == nr_class) {
1684          if (nr_class == max_nr_class) {
1685            max_nr_class *= 2;
1686            int[] new_data = new int[max_nr_class];
1687            Array.Copy(label, 0, new_data, 0, label.Length);
1688            label = new_data;
1689            new_data = new int[max_nr_class];
1690            Array.Copy(count, 0, new_data, 0, count.Length);
1691            count = new_data;
1692          }
1693          label[nr_class] = this_label;
1694          count[nr_class] = 1;
1695          ++nr_class;
1696        }
1697      }
1698
1699      int[] start = new int[nr_class];
1700      start[0] = 0;
1701      for (i = 1; i < nr_class; i++)
1702        start[i] = start[i - 1] + count[i - 1];
1703      for (i = 0; i < l; i++) {
1704        perm[start[data_label[i]]] = i;
1705        ++start[data_label[i]];
1706      }
1707      start[0] = 0;
1708      for (i = 1; i < nr_class; i++)
1709        start[i] = start[i - 1] + count[i - 1];
1710
1711      nr_class_ret[0] = nr_class;
1712      label_ret[0] = label;
1713      start_ret[0] = start;
1714      count_ret[0] = count;
1715    }
1716
1717    //
1718    // Interface functions
1719    //
1720    public static svm_model svm_train(svm_problem prob, svm_parameter param) {
1721      svm_model model = new svm_model();
1722      model.param = param;
1723
1724      if (param.svm_type == svm_parameter.ONE_CLASS ||
1725          param.svm_type == svm_parameter.EPSILON_SVR ||
1726          param.svm_type == svm_parameter.NU_SVR) {
1727        // regression or one-class-svm
1728        model.nr_class = 2;
1729        model.label = null;
1730        model.nSV = null;
1731        model.probA = null;
1732        model.probB = null;
1733        model.sv_coef = new double[1][];
1734
1735        if (param.probability == 1 &&
1736            (param.svm_type == svm_parameter.EPSILON_SVR ||
1737             param.svm_type == svm_parameter.NU_SVR)) {
1738          model.probA = new double[1];
1739          model.probA[0] = svm_svr_probability(prob, param);
1740        }
1741
1742        decision_function f = svm_train_one(prob, param, 0, 0);
1743        model.rho = new double[1];
1744        model.rho[0] = f.rho;
1745
1746        int nSV = 0;
1747        int i;
1748        for (i = 0; i < prob.l; i++)
1749          if (Math.Abs(f.alpha[i]) > 0) ++nSV;
1750        model.l = nSV;
1751        model.SV = new svm_node[nSV][];
1752        model.sv_coef[0] = new double[nSV];
1753        int j = 0;
1754        for (i = 0; i < prob.l; i++)
1755          if (Math.Abs(f.alpha[i]) > 0) {
1756            model.SV[j] = prob.x[i];
1757            model.sv_coef[0][j] = f.alpha[i];
1758            ++j;
1759          }
1760      } else {
1761        // classification
1762        int l = prob.l;
1763        int[] tmp_nr_class = new int[1];
1764        int[][] tmp_label = new int[1][];
1765        int[][] tmp_start = new int[1][];
1766        int[][] tmp_count = new int[1][];
1767        int[] perm = new int[l];
1768
1769        // group training data of the same class
1770        svm_group_classes(prob, tmp_nr_class, tmp_label, tmp_start, tmp_count, perm);
1771        int nr_class = tmp_nr_class[0];
1772        int[] label = tmp_label[0];
1773        int[] start = tmp_start[0];
1774        int[] count = tmp_count[0];
1775
1776        if (nr_class == 1)
1777          svm.info("WARNING: training data in only one class. See README for details." + Environment.NewLine);
1778
1779        svm_node[][] x = new svm_node[l][];
1780        int i;
1781        for (i = 0; i < l; i++)
1782          x[i] = prob.x[perm[i]];
1783
1784        // calculate weighted C
1785
1786        double[] weighted_C = new double[nr_class];
1787        for (i = 0; i < nr_class; i++)
1788          weighted_C[i] = param.C;
1789        for (i = 0; i < param.nr_weight; i++) {
1790          int j;
1791          for (j = 0; j < nr_class; j++)
1792            if (param.weight_label[i] == label[j])
1793              break;
1794          if (j == nr_class)
1795            Console.Error.WriteLine("WARNING: class label " + param.weight_label[i] +
1796                                    " specified in weight is not found");
1797          else
1798            weighted_C[j] *= param.weight[i];
1799        }
1800
1801        // train k*(k-1)/2 models
1802
1803        bool[] nonzero = new bool[l];
1804        for (i = 0; i < l; i++)
1805          nonzero[i] = false;
1806        decision_function[] f = new decision_function[nr_class * (nr_class - 1) / 2];
1807
1808        double[] probA = null, probB = null;
1809        if (param.probability == 1) {
1810          probA = new double[nr_class * (nr_class - 1) / 2];
1811          probB = new double[nr_class * (nr_class - 1) / 2];
1812        }
1813
1814        int p = 0;
1815        for (i = 0; i < nr_class; i++)
1816          for (int j = i + 1; j < nr_class; j++) {
1817            svm_problem sub_prob = new svm_problem();
1818            int si = start[i], sj = start[j];
1819            int ci = count[i], cj = count[j];
1820            sub_prob.l = ci + cj;
1821            sub_prob.x = new svm_node[sub_prob.l][];
1822            sub_prob.y = new double[sub_prob.l];
1823            int k;
1824            for (k = 0; k < ci; k++) {
1825              sub_prob.x[k] = x[si + k];
1826              sub_prob.y[k] = +1;
1827            }
1828            for (k = 0; k < cj; k++) {
1829              sub_prob.x[ci + k] = x[sj + k];
1830              sub_prob.y[ci + k] = -1;
1831            }
1832
1833            if (param.probability == 1) {
1834              double[] probAB = new double[2];
1835              svm_binary_svc_probability(sub_prob, param, weighted_C[i], weighted_C[j], probAB);
1836              probA[p] = probAB[0];
1837              probB[p] = probAB[1];
1838            }
1839
1840            f[p] = svm_train_one(sub_prob, param, weighted_C[i], weighted_C[j]);
1841            for (k = 0; k < ci; k++)
1842              if (!nonzero[si + k] && Math.Abs(f[p].alpha[k]) > 0)
1843                nonzero[si + k] = true;
1844            for (k = 0; k < cj; k++)
1845              if (!nonzero[sj + k] && Math.Abs(f[p].alpha[ci + k]) > 0)
1846                nonzero[sj + k] = true;
1847            ++p;
1848          }
1849
1850        // build output
1851
1852        model.nr_class = nr_class;
1853
1854        model.label = new int[nr_class];
1855        for (i = 0; i < nr_class; i++)
1856          model.label[i] = label[i];
1857
1858        model.rho = new double[nr_class * (nr_class - 1) / 2];
1859        for (i = 0; i < nr_class * (nr_class - 1) / 2; i++)
1860          model.rho[i] = f[i].rho;
1861
1862        if (param.probability == 1) {
1863          model.probA = new double[nr_class * (nr_class - 1) / 2];
1864          model.probB = new double[nr_class * (nr_class - 1) / 2];
1865          for (i = 0; i < nr_class * (nr_class - 1) / 2; i++) {
1866            model.probA[i] = probA[i];
1867            model.probB[i] = probB[i];
1868          }
1869        } else {
1870          model.probA = null;
1871          model.probB = null;
1872        }
1873
1874        int nnz = 0;
1875        int[] nz_count = new int[nr_class];
1876        model.nSV = new int[nr_class];
1877        for (i = 0; i < nr_class; i++) {
1878          int nSV = 0;
1879          for (int j = 0; j < count[i]; j++)
1880            if (nonzero[start[i] + j]) {
1881              ++nSV;
1882              ++nnz;
1883            }
1884          model.nSV[i] = nSV;
1885          nz_count[i] = nSV;
1886        }
1887
1888        svm.info("Total nSV = " + nnz + Environment.NewLine);
1889
1890        model.l = nnz;
1891        model.SV = new svm_node[nnz][];
1892        p = 0;
1893        for (i = 0; i < l; i++)
1894          if (nonzero[i]) model.SV[p++] = x[i];
1895
1896        int[] nz_start = new int[nr_class];
1897        nz_start[0] = 0;
1898        for (i = 1; i < nr_class; i++)
1899          nz_start[i] = nz_start[i - 1] + nz_count[i - 1];
1900
1901        model.sv_coef = new double[nr_class - 1][];
1902        for (i = 0; i < nr_class - 1; i++)
1903          model.sv_coef[i] = new double[nnz];
1904
1905        p = 0;
1906        for (i = 0; i < nr_class; i++)
1907          for (int j = i + 1; j < nr_class; j++) {
1908            // classifier (i,j): coefficients with
1909            // i are in sv_coef[j-1][nz_start[i]...],
1910            // j are in sv_coef[i][nz_start[j]...]
1911
1912            int si = start[i];
1913            int sj = start[j];
1914            int ci = count[i];
1915            int cj = count[j];
1916
1917            int q = nz_start[i];
1918            int k;
1919            for (k = 0; k < ci; k++)
1920              if (nonzero[si + k])
1921                model.sv_coef[j - 1][q++] = f[p].alpha[k];
1922            q = nz_start[j];
1923            for (k = 0; k < cj; k++)
1924              if (nonzero[sj + k])
1925                model.sv_coef[i][q++] = f[p].alpha[ci + k];
1926            ++p;
1927          }
1928      }
1929      return model;
1930    }
1931
1932    // Stratified cross validation
1933    public static void svm_cross_validation(svm_problem prob, svm_parameter param, int nr_fold, double[] target) {
1934      int i;
1935      int[] fold_start = new int[nr_fold + 1];
1936      int l = prob.l;
1937      int[] perm = new int[l];
1938
1939      // stratified cv may not give leave-one-out rate
1940      // Each class to l folds -> some folds may have zero elements
1941      if ((param.svm_type == svm_parameter.C_SVC ||
1942           param.svm_type == svm_parameter.NU_SVC) && nr_fold < l) {
1943        int[] tmp_nr_class = new int[1];
1944        int[][] tmp_label = new int[1][];
1945        int[][] tmp_start = new int[1][];
1946        int[][] tmp_count = new int[1][];
1947
1948        svm_group_classes(prob, tmp_nr_class, tmp_label, tmp_start, tmp_count, perm);
1949
1950        int nr_class = tmp_nr_class[0];
1951        int[] start = tmp_start[0];
1952        int[] count = tmp_count[0];
1953
1954        // random shuffle and then data grouped by fold using the array perm
1955        int[] fold_count = new int[nr_fold];
1956        int c;
1957        int[] index = new int[l];
1958        for (i = 0; i < l; i++)
1959          index[i] = perm[i];
1960        for (c = 0; c < nr_class; c++)
1961          for (i = 0; i < count[c]; i++) {
1962            int j = i + rand.Next(count[c] - i);
1963            {
1964              int _ = index[start[c] + j];
1965              index[start[c] + j] = index[start[c] + i];
1966              index[start[c] + i] = _;
1967            }
1968          }
1969        for (i = 0; i < nr_fold; i++) {
1970          fold_count[i] = 0;
1971          for (c = 0; c < nr_class; c++)
1972            fold_count[i] += (i + 1) * count[c] / nr_fold - i * count[c] / nr_fold;
1973        }
1974        fold_start[0] = 0;
1975        for (i = 1; i <= nr_fold; i++)
1976          fold_start[i] = fold_start[i - 1] + fold_count[i - 1];
1977        for (c = 0; c < nr_class; c++)
1978          for (i = 0; i < nr_fold; i++) {
1979            int begin = start[c] + i * count[c] / nr_fold;
1980            int end = start[c] + (i + 1) * count[c] / nr_fold;
1981            for (int j = begin; j < end; j++) {
1982              perm[fold_start[i]] = index[j];
1983              fold_start[i]++;
1984            }
1985          }
1986        fold_start[0] = 0;
1987        for (i = 1; i <= nr_fold; i++)
1988          fold_start[i] = fold_start[i - 1] + fold_count[i - 1];
1989      } else {
1990        for (i = 0; i < l; i++) perm[i] = i;
1991        for (i = 0; i < l; i++) {
1992          int j = i + rand.Next(l - i);
1993          {
1994            int _ = perm[i];
1995            perm[i] = perm[j];
1996            perm[j] = _;
1997          }
1998        }
1999        for (i = 0; i <= nr_fold; i++)
2000          fold_start[i] = i * l / nr_fold;
2001      }
2002
2003      for (i = 0; i < nr_fold; i++) {
2004        int begin = fold_start[i];
2005        int end = fold_start[i + 1];
2006        int j, k;
2007        svm_problem subprob = new svm_problem();
2008
2009        subprob.l = l - (end - begin);
2010        subprob.x = new svm_node[subprob.l][];
2011        subprob.y = new double[subprob.l];
2012
2013        k = 0;
2014        for (j = 0; j < begin; j++) {
2015          subprob.x[k] = prob.x[perm[j]];
2016          subprob.y[k] = prob.y[perm[j]];
2017          ++k;
2018        }
2019        for (j = end; j < l; j++) {
2020          subprob.x[k] = prob.x[perm[j]];
2021          subprob.y[k] = prob.y[perm[j]];
2022          ++k;
2023        }
2024        svm_model submodel = svm_train(subprob, param);
2025        if (param.probability == 1 &&
2026            (param.svm_type == svm_parameter.C_SVC ||
2027             param.svm_type == svm_parameter.NU_SVC)) {
2028          double[] prob_estimates = new double[svm_get_nr_class(submodel)];
2029          for (j = begin; j < end; j++)
2030            target[perm[j]] = svm_predict_probability(submodel, prob.x[perm[j]], prob_estimates);
2031        } else
2032          for (j = begin; j < end; j++)
2033            target[perm[j]] = svm_predict(submodel, prob.x[perm[j]]);
2034      }
2035    }
2036
2037    public static int svm_get_svm_type(svm_model model) {
2038      return model.param.svm_type;
2039    }
2040
2041    public static int svm_get_nr_class(svm_model model) {
2042      return model.nr_class;
2043    }
2044
2045    public static void svm_get_labels(svm_model model, int[] label) {
2046      if (model.label != null)
2047        for (int i = 0; i < model.nr_class; i++)
2048          label[i] = model.label[i];
2049    }
2050
2051    public static double svm_get_svr_probability(svm_model model) {
2052      if ((model.param.svm_type == svm_parameter.EPSILON_SVR || model.param.svm_type == svm_parameter.NU_SVR) &&
2053          model.probA != null)
2054        return model.probA[0];
2055      else {
2056        Console.Error.WriteLine("Model doesn't contain information for SVR probability inference");
2057        return 0;
2058      }
2059    }
2060
2061    public static double svm_predict_values(svm_model model, svm_node[] x, double[] dec_values) {
2062      int i;
2063      if (model.param.svm_type == svm_parameter.ONE_CLASS ||
2064          model.param.svm_type == svm_parameter.EPSILON_SVR ||
2065          model.param.svm_type == svm_parameter.NU_SVR) {
2066        double[] sv_coef = model.sv_coef[0];
2067        double sum = 0;
2068        for (i = 0; i < model.l; i++)
2069          sum += sv_coef[i] * Kernel.k_function(x, model.SV[i], model.param);
2070        sum -= model.rho[0];
2071        dec_values[0] = sum;
2072
2073        if (model.param.svm_type == svm_parameter.ONE_CLASS)
2074          return (sum > 0) ? 1 : -1;
2075        else
2076          return sum;
2077      } else {
2078        int nr_class = model.nr_class;
2079        int l = model.l;
2080
2081        double[] kvalue = new double[l];
2082        for (i = 0; i < l; i++)
2083          kvalue[i] = Kernel.k_function(x, model.SV[i], model.param);
2084
2085        int[] start = new int[nr_class];
2086        start[0] = 0;
2087        for (i = 1; i < nr_class; i++)
2088          start[i] = start[i - 1] + model.nSV[i - 1];
2089
2090        int[] vote = new int[nr_class];
2091        for (i = 0; i < nr_class; i++)
2092          vote[i] = 0;
2093
2094        int p = 0;
2095        for (i = 0; i < nr_class; i++)
2096          for (int j = i + 1; j < nr_class; j++) {
2097            double sum = 0;
2098            int si = start[i];
2099            int sj = start[j];
2100            int ci = model.nSV[i];
2101            int cj = model.nSV[j];
2102
2103            int k;
2104            double[] coef1 = model.sv_coef[j - 1];
2105            double[] coef2 = model.sv_coef[i];
2106            for (k = 0; k < ci; k++)
2107              sum += coef1[si + k] * kvalue[si + k];
2108            for (k = 0; k < cj; k++)
2109              sum += coef2[sj + k] * kvalue[sj + k];
2110            sum -= model.rho[p];
2111            dec_values[p] = sum;
2112
2113            if (dec_values[p] > 0)
2114              ++vote[i];
2115            else
2116              ++vote[j];
2117            p++;
2118          }
2119
2120        int vote_max_idx = 0;
2121        for (i = 1; i < nr_class; i++)
2122          if (vote[i] > vote[vote_max_idx])
2123            vote_max_idx = i;
2124
2125        return model.label[vote_max_idx];
2126      }
2127    }
2128
2129    public static double svm_predict(svm_model model, svm_node[] x) {
2130      int nr_class = model.nr_class;
2131      double[] dec_values;
2132      if (model.param.svm_type == svm_parameter.ONE_CLASS ||
2133          model.param.svm_type == svm_parameter.EPSILON_SVR ||
2134          model.param.svm_type == svm_parameter.NU_SVR)
2135        dec_values = new double[1];
2136      else
2137        dec_values = new double[nr_class * (nr_class - 1) / 2];
2138      double pred_result = svm_predict_values(model, x, dec_values);
2139      return pred_result;
2140    }
2141
2142    public static double svm_predict_probability(svm_model model, svm_node[] x, double[] prob_estimates) {
2143      if ((model.param.svm_type == svm_parameter.C_SVC || model.param.svm_type == svm_parameter.NU_SVC) &&
2144          model.probA != null && model.probB != null) {
2145        int i;
2146        int nr_class = model.nr_class;
2147        double[] dec_values = new double[nr_class * (nr_class - 1) / 2];
2148        svm_predict_values(model, x, dec_values);
2149
2150        double min_prob = 1e-7;
2151        double[][] pairwise_prob = new double[nr_class][];
2152
2153        int k = 0;
2154        for (i = 0; i < nr_class; i++)
2155          pairwise_prob[i] = new double[nr_class];
2156        for (int j = i + 1; j < nr_class; j++) {
2157          pairwise_prob[i][j] =
2158            Math.Min(Math.Max(sigmoid_predict(dec_values[k], model.probA[k], model.probB[k]), min_prob), 1 - min_prob);
2159          pairwise_prob[j][i] = 1 - pairwise_prob[i][j];
2160          k++;
2161        }
2162        multiclass_probability(nr_class, pairwise_prob, prob_estimates);
2163
2164        int prob_max_idx = 0;
2165        for (i = 1; i < nr_class; i++)
2166          if (prob_estimates[i] > prob_estimates[prob_max_idx])
2167            prob_max_idx = i;
2168        return model.label[prob_max_idx];
2169      } else
2170        return svm_predict(model, x);
2171    }
2172
2173    private static readonly string[] svm_type_table = new string[]
2174                                                        {
2175                                                          "c_svc", "nu_svc", "one_class", "epsilon_svr", "nu_svr",
2176                                                        };
2177
2178    private static readonly string[] kernel_type_table = new string[]
2179                                                           {
2180                                                             "linear", "polynomial", "rbf", "sigmoid", "precomputed"
2181                                                           };
2182
2183
2184    public static void svm_save_model(string model_file_name, svm_model model) {
2185      //DataOutputStream fp = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(model_file_name)));
2186      var writer = new StreamWriter(model_file_name);
2187      svm_save_model(writer, model);
2188    }
2189
2190
2191    public static void svm_save_model(StreamWriter writer, svm_model model) {
2192
2193      var savedCulture = Thread.CurrentThread.CurrentCulture;
2194      Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
2195      svm_parameter param = model.param;
2196
2197      writer.Write("svm_type " + svm_type_table[param.svm_type] + Environment.NewLine);
2198      writer.Write("kernel_type " + kernel_type_table[param.kernel_type] + Environment.NewLine);
2199
2200      if (param.kernel_type == svm_parameter.POLY)
2201        writer.Write("degree " + param.degree + Environment.NewLine);
2202
2203      if (param.kernel_type == svm_parameter.POLY ||
2204         param.kernel_type == svm_parameter.RBF ||
2205         param.kernel_type == svm_parameter.SIGMOID)
2206        writer.Write("gamma " + param.gamma.ToString("r") + Environment.NewLine);
2207
2208      if (param.kernel_type == svm_parameter.POLY ||
2209         param.kernel_type == svm_parameter.SIGMOID)
2210        writer.Write("coef0 " + param.coef0.ToString("r") + Environment.NewLine);
2211
2212      int nr_class = model.nr_class;
2213      int l = model.l;
2214      writer.Write("nr_class " + nr_class + Environment.NewLine);
2215      writer.Write("total_sv " + l + Environment.NewLine);
2216
2217      {
2218        writer.Write("rho");
2219        for (int i = 0; i < nr_class * (nr_class - 1) / 2; i++)
2220          writer.Write(" " + model.rho[i].ToString("r"));
2221        writer.Write(Environment.NewLine);
2222      }
2223
2224      if (model.label != null) {
2225        writer.Write("label");
2226        for (int i = 0; i < nr_class; i++)
2227          writer.Write(" " + model.label[i]);
2228        writer.Write(Environment.NewLine);
2229      }
2230
2231      if (model.probA != null) // regression has probA only
2232      {
2233        writer.Write("probA");
2234        for (int i = 0; i < nr_class * (nr_class - 1) / 2; i++)
2235          writer.Write(" " + model.probA[i].ToString("r"));
2236        writer.Write(Environment.NewLine);
2237      }
2238      if (model.probB != null) {
2239        writer.Write("probB");
2240        for (int i = 0; i < nr_class * (nr_class - 1) / 2; i++)
2241          writer.Write(" " + model.probB[i].ToString("r"));
2242        writer.Write(Environment.NewLine);
2243      }
2244
2245      if (model.nSV != null) {
2246        writer.Write("nr_sv");
2247        for (int i = 0; i < nr_class; i++)
2248          writer.Write(" " + model.nSV[i]);
2249        writer.Write(Environment.NewLine);
2250      }
2251
2252      writer.WriteLine("SV");
2253      double[][] sv_coef = model.sv_coef;
2254      svm_node[][] SV = model.SV;
2255
2256      for (int i = 0; i < l; i++) {
2257        for (int j = 0; j < nr_class - 1; j++)
2258          writer.Write(sv_coef[j][i].ToString("r") + " ");
2259
2260        svm_node[] p = SV[i];
2261        if (param.kernel_type == svm_parameter.PRECOMPUTED)
2262          writer.Write("0:" + (int)(p[0].value));
2263        else
2264          for (int j = 0; j < p.Length; j++)
2265            writer.Write(p[j].index + ":" + p[j].value.ToString("r") + " ");
2266        writer.Write(Environment.NewLine);
2267      }
2268
2269      writer.Flush();
2270      Thread.CurrentThread.CurrentCulture = savedCulture;
2271    }
2272
2273    private static double atof(String s) {
2274      return double.Parse(s);
2275    }
2276
2277    private static int atoi(String s) {
2278      return int.Parse(s);
2279    }
2280
2281
2282    public static svm_model svm_load_model(String model_file_name) {
2283      return svm_load_model(new StreamReader(model_file_name));
2284    }
2285
2286    public static svm_model svm_load_model(StreamReader reader) {
2287      var savedCulture = Thread.CurrentThread.CurrentCulture;
2288      Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
2289
2290      // read parameters
2291
2292      svm_model model = new svm_model();
2293      svm_parameter param = new svm_parameter();
2294      model.param = param;
2295      model.rho = null;
2296      model.probA = null;
2297      model.probB = null;
2298      model.label = null;
2299      model.nSV = null;
2300
2301      while (true) {
2302        String cmd = reader.ReadLine();
2303        String arg = cmd.Substring(cmd.IndexOf(' ') + 1);
2304
2305        if (cmd.StartsWith("svm_type")) {
2306          int i;
2307          for (i = 0; i < svm_type_table.Length; i++) {
2308            if (arg.IndexOf(svm_type_table[i], StringComparison.InvariantCultureIgnoreCase) != -1) {
2309              param.svm_type = i;
2310              break;
2311            }
2312          }
2313          if (i == svm_type_table.Length) {
2314            Console.Error.WriteLine("unknown svm type.");
2315            return null;
2316          }
2317        } else if (cmd.StartsWith("kernel_type")) {
2318          int i;
2319          for (i = 0; i < kernel_type_table.Length; i++) {
2320            if (arg.IndexOf(kernel_type_table[i], StringComparison.InvariantCultureIgnoreCase) != -1) {
2321              param.kernel_type = i;
2322              break;
2323            }
2324          }
2325          if (i == kernel_type_table.Length) {
2326            Console.Error.WriteLine("unknown kernel function.");
2327            return null;
2328          }
2329        } else if (cmd.StartsWith("degree"))
2330          param.degree = atoi(arg);
2331        else if (cmd.StartsWith("gamma"))
2332          param.gamma = atof(arg);
2333        else if (cmd.StartsWith("coef0"))
2334          param.coef0 = atof(arg);
2335        else if (cmd.StartsWith("nr_class"))
2336          model.nr_class = atoi(arg);
2337        else if (cmd.StartsWith("total_sv"))
2338          model.l = atoi(arg);
2339        else if (cmd.StartsWith("rho")) {
2340          int n = model.nr_class * (model.nr_class - 1) / 2;
2341          model.rho = new double[n];
2342          var st = arg.Split(' ', '\t', '\n', '\r', '\f');
2343          for (int i = 0; i < n; i++)
2344            model.rho[i] = atof(st[i]);
2345        } else if (cmd.StartsWith("label")) {
2346          int n = model.nr_class;
2347          model.label = new int[n];
2348          var st = arg.Split(' ', '\t', '\n', '\r', '\f');
2349          for (int i = 0; i < n; i++)
2350            model.label[i] = atoi(st[i]);
2351        } else if (cmd.StartsWith("probA")) {
2352          int n = model.nr_class * (model.nr_class - 1) / 2;
2353          model.probA = new double[n];
2354          var st = arg.Split(' ', '\t', '\n', '\r', '\f');
2355          for (int i = 0; i < n; i++)
2356            model.probA[i] = atof(st[i]);
2357        } else if (cmd.StartsWith("probB")) {
2358          int n = model.nr_class * (model.nr_class - 1) / 2;
2359          model.probB = new double[n];
2360          var st = arg.Split(' ', '\t', '\n', '\r', '\f');
2361          for (int i = 0; i < n; i++)
2362            model.probB[i] = atof(st[i]);
2363        } else if (cmd.StartsWith("nr_sv")) {
2364          int n = model.nr_class;
2365          model.nSV = new int[n];
2366          var st = arg.Split(' ', '\t', '\n', '\r', '\f');
2367          for (int i = 0; i < n; i++)
2368            model.nSV[i] = atoi(st[i]);
2369        } else if (cmd.StartsWith("SV")) {
2370          break;
2371        } else {
2372          Console.Error.WriteLine("unknown text in model file: [" + cmd + "]");
2373          return null;
2374        }
2375      }
2376
2377      // read sv_coef and SV
2378
2379      int m = model.nr_class - 1;
2380      int l = model.l;
2381      model.sv_coef = new double[m][];
2382      for (int k = 0; k < m; k++)
2383        model.sv_coef[k] = new double[l];
2384
2385      model.SV = new svm_node[l][];
2386
2387
2388      for (int i = 0; i < l; i++) {
2389        String line = reader.ReadLine();
2390        var st = line.Split(' ', '\t', '\n', '\r', '\f', ':');
2391
2392        for (int k = 0; k < m; k++) {
2393          model.sv_coef[k][i] = atof(st[k]);
2394        }
2395        // skip y value
2396        st = st.Skip(1).ToArray();
2397
2398        int n = st.Length / 2;
2399        model.SV[i] = new svm_node[n];
2400        for (int j = 0; j < n; j++) {
2401          model.SV[i][j] = new svm_node();
2402          model.SV[i][j].index = atoi(st[2 * j]);
2403          model.SV[i][j].value = atof(st[2 * j + 1]);
2404        }
2405      }
2406
2407      Thread.CurrentThread.CurrentCulture = savedCulture;
2408      return model;
2409    }
2410
2411    public static string svm_check_parameter(svm_problem prob, svm_parameter param) {
2412      // svm_type
2413
2414      int svm_type = param.svm_type;
2415      if (svm_type != svm_parameter.C_SVC &&
2416         svm_type != svm_parameter.NU_SVC &&
2417         svm_type != svm_parameter.ONE_CLASS &&
2418         svm_type != svm_parameter.EPSILON_SVR &&
2419         svm_type != svm_parameter.NU_SVR)
2420        return "unknown svm type";
2421
2422      // kernel_type, degree
2423
2424      int kernel_type = param.kernel_type;
2425      if (kernel_type != svm_parameter.LINEAR &&
2426         kernel_type != svm_parameter.POLY &&
2427         kernel_type != svm_parameter.RBF &&
2428         kernel_type != svm_parameter.SIGMOID &&
2429         kernel_type != svm_parameter.PRECOMPUTED)
2430        return "unknown kernel type";
2431
2432      if (param.gamma < 0)
2433        return "gamma < 0";
2434
2435      if (param.degree < 0)
2436        return "degree of polynomial kernel < 0";
2437
2438      // cache_size,eps,C,nu,p,shrinking
2439
2440      if (param.cache_size <= 0)
2441        return "cache_size <= 0";
2442
2443      if (param.eps <= 0)
2444        return "eps <= 0";
2445
2446      if (svm_type == svm_parameter.C_SVC ||
2447         svm_type == svm_parameter.EPSILON_SVR ||
2448         svm_type == svm_parameter.NU_SVR)
2449        if (param.C <= 0)
2450          return "C <= 0";
2451
2452      if (svm_type == svm_parameter.NU_SVC ||
2453         svm_type == svm_parameter.ONE_CLASS ||
2454         svm_type == svm_parameter.NU_SVR)
2455        if (param.nu <= 0 || param.nu > 1)
2456          return "nu <= 0 or nu > 1";
2457
2458      if (svm_type == svm_parameter.EPSILON_SVR)
2459        if (param.p < 0)
2460          return "p < 0";
2461
2462      if (param.shrinking != 0 &&
2463         param.shrinking != 1)
2464        return "shrinking != 0 and shrinking != 1";
2465
2466      if (param.probability != 0 &&
2467         param.probability != 1)
2468        return "probability != 0 and probability != 1";
2469
2470      if (param.probability == 1 &&
2471         svm_type == svm_parameter.ONE_CLASS)
2472        return "one-class SVM probability output not supported yet";
2473
2474      // check whether nu-svc is feasible
2475
2476      if (svm_type == svm_parameter.NU_SVC) {
2477        int l = prob.l;
2478        int max_nr_class = 16;
2479        int nr_class = 0;
2480        int[] label = new int[max_nr_class];
2481        int[] count = new int[max_nr_class];
2482
2483        int i;
2484        for (i = 0; i < l; i++) {
2485          int this_label = (int)prob.y[i];
2486          int j;
2487          for (j = 0; j < nr_class; j++)
2488            if (this_label == label[j]) {
2489              ++count[j];
2490              break;
2491            }
2492
2493          if (j == nr_class) {
2494            if (nr_class == max_nr_class) {
2495              max_nr_class *= 2;
2496              int[] new_data = new int[max_nr_class];
2497              Array.Copy(label, 0, new_data, 0, label.Length);
2498              label = new_data;
2499
2500              new_data = new int[max_nr_class];
2501              Array.Copy(count, 0, new_data, 0, count.Length);
2502              count = new_data;
2503            }
2504            label[nr_class] = this_label;
2505            count[nr_class] = 1;
2506            ++nr_class;
2507          }
2508        }
2509
2510        for (i = 0; i < nr_class; i++) {
2511          int n1 = count[i];
2512          for (int j = i + 1; j < nr_class; j++) {
2513            int n2 = count[j];
2514            if (param.nu * (n1 + n2) / 2 > Math.Min(n1, n2))
2515              return "specified nu is infeasible";
2516          }
2517        }
2518      }
2519
2520      return null;
2521    }
2522
2523    public static int svm_check_probability_model(svm_model model) {
2524      if (((model.param.svm_type == svm_parameter.C_SVC || model.param.svm_type == svm_parameter.NU_SVC) &&
2525      model.probA != null && model.probB != null) ||
2526      ((model.param.svm_type == svm_parameter.EPSILON_SVR || model.param.svm_type == svm_parameter.NU_SVR) &&
2527       model.probA != null))
2528        return 1;
2529      else
2530        return 0;
2531    }
2532
2533    public static void svm_set_print_string_function(Action<string> print_func) {
2534      /*if (print_func == null)
2535        svm_print_string = svm_print_stdout;
2536      else
2537        svm_print_string = print_func;
2538       */
2539      if (print_func != null) svm_print_string = print_func;
2540    }
2541  }
2542
2543  public class svm_node : ICloneable {
2544    public int index;
2545    public double value;
2546    public object Clone() {
2547      var clone = new svm_node();
2548      clone.index = index;
2549      clone.value = value;
2550      return clone;
2551    }
2552  }
2553
2554  public class svm_model {
2555    public svm_parameter param; // parameter
2556    public int nr_class;    // number of classes, = 2 in regression/one class svm
2557    public int l;     // total #SV
2558    public svm_node[][] SV; // SVs (SV[l])
2559    public double[][] sv_coef;  // coefficients for SVs in decision functions (sv_coef[k-1][l])
2560    public double[] rho;    // constants in decision functions (rho[k*(k-1)/2])
2561    public double[] probA;         // pariwise probability information
2562    public double[] probB;
2563
2564    // for classification only
2565
2566    public int[] label;   // label of each class (label[k])
2567    public int[] nSV;   // number of SVs for each class (nSV[k])
2568    // nSV[0] + nSV[1] + ... + nSV[k-1] = l
2569  };
2570
2571  public class svm_problem {
2572    public int l;
2573    public double[] y;
2574    public svm_node[][] x;
2575  }
2576
2577  public interface svm_print_interface {
2578    void print(String s);
2579  }
2580
2581  public class svm_parameter : ICloneable {
2582    /* svm_type */
2583    public const int C_SVC = 0;
2584    public const int NU_SVC = 1;
2585    public const int ONE_CLASS = 2;
2586    public const int EPSILON_SVR = 3;
2587    public const int NU_SVR = 4;
2588
2589    /* kernel_type */
2590    public const int LINEAR = 0;
2591    public const int POLY = 1;
2592    public const int RBF = 2;
2593    public const int SIGMOID = 3;
2594    public const int PRECOMPUTED = 4;
2595
2596    public int svm_type;
2597    public int kernel_type;
2598    public int degree;  // for poly
2599    public double gamma;  // for poly/rbf/sigmoid
2600    public double coef0;  // for poly/sigmoid
2601
2602    // these are for training only
2603    public double cache_size; // in MB
2604    public double eps;  // stopping criteria
2605    public double C;  // for C_SVC, EPSILON_SVR and NU_SVR
2606    public int nr_weight;   // for C_SVC
2607    public int[] weight_label;  // for C_SVC
2608    public double[] weight;   // for C_SVC
2609    public double nu; // for NU_SVC, ONE_CLASS, and NU_SVR
2610    public double p;  // for EPSILON_SVR
2611    public int shrinking; // use the shrinking heuristics
2612    public int probability; // do probability estimates
2613
2614    public virtual object Clone() {
2615      var clone = new svm_parameter();
2616      clone.svm_type = svm_type;
2617      clone.kernel_type = kernel_type;
2618      clone.degree = degree;
2619      clone.gamma = gamma;
2620      clone.coef0 = coef0;
2621      clone.cache_size = cache_size;
2622      clone.eps = eps;
2623      clone.C = C;
2624      clone.nr_weight = nr_weight;
2625      if (weight_label != null) {
2626        clone.weight_label = new int[weight_label.Length];
2627        Array.Copy(weight_label, clone.weight_label, weight_label.Length);
2628      }
2629      if (weight != null) {
2630        clone.weight = new double[weight.Length];
2631        Array.Copy(weight, clone.weight, weight.Length);
2632      }
2633      clone.nu = nu;
2634      clone.p = p;
2635      clone.shrinking = shrinking;
2636      clone.probability = probability;
2637      return clone;
2638    }
2639  }
2640}
Note: See TracBrowser for help on using the repository browser.