Free cookie consent management tool by TermsFeed Policy Generator

source: branches/TSNE/HeuristicLab.Algorithms.DataAnalysis/3.4/TSNE/TSNEStatic.cs @ 14782

Last change on this file since 14782 was 14782, checked in by gkronber, 7 years ago

#2700 renamed files

File size: 26.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21//Code is based on an implementation from Laurens van der Maaten
22
23/*
24*
25* Copyright (c) 2014, Laurens van der Maaten (Delft University of Technology)
26* All rights reserved.
27*
28* Redistribution and use in source and binary forms, with or without
29* modification, are permitted provided that the following conditions are met:
30* 1. Redistributions of source code must retain the above copyright
31*    notice, this list of conditions and the following disclaimer.
32* 2. Redistributions in binary form must reproduce the above copyright
33*    notice, this list of conditions and the following disclaimer in the
34*    documentation and/or other materials provided with the distribution.
35* 3. All advertising materials mentioning features or use of this software
36*    must display the following acknowledgement:
37*    This product includes software developed by the Delft University of Technology.
38* 4. Neither the name of the Delft University of Technology nor the names of
39*    its contributors may be used to endorse or promote products derived from
40*    this software without specific prior written permission.
41*
42* THIS SOFTWARE IS PROVIDED BY LAURENS VAN DER MAATEN ''AS IS'' AND ANY EXPRESS
43* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
44* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
45* EVENT SHALL LAURENS VAN DER MAATEN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
47* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
48* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
49* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
50* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
51* OF SUCH DAMAGE.
52*
53*/
54#endregion
55
56using System;
57using System.Collections.Generic;
58using System.Linq;
59using HeuristicLab.Analysis;
60using HeuristicLab.Common;
61using HeuristicLab.Core;
62using HeuristicLab.Data;
63using HeuristicLab.Optimization;
64using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
65using HeuristicLab.Random;
66
67namespace HeuristicLab.Algorithms.DataAnalysis {
68  [StorableClass]
69  public class TSNE<T> : DeepCloneable where T : class, IDeepCloneable {
70
71    private const string IterationResultName = "Iteration";
72    private const string ErrorResultName = "Error";
73    private const string ErrorPlotResultName = "ErrorPlot";
74    private const string ScatterPlotResultName = "Scatterplot";
75    private const string DataResultName = "Projected Data";
76
77    #region Properties
78    [Storable]
79    private IDistance<T> distance;
80    [Storable]
81    private int maxIter;
82    [Storable]
83    private int stopLyingIter;
84    [Storable]
85    private int momSwitchIter;
86    [Storable]
87    double momentum;
88    [Storable]
89    private double finalMomentum;
90    [Storable]
91    private double eta;
92    [Storable]
93    private IRandom random;
94    [Storable]
95    private ResultCollection results;
96    [Storable]
97    private Dictionary<string, List<int>> dataRowLookup;
98    [Storable]
99    private Dictionary<string, ScatterPlotDataRow> dataRows;
100    #endregion
101
102    #region Stopping
103    public volatile bool Running;
104    #endregion
105
106    #region HLConstructors & Cloning
107    [StorableConstructor]
108    protected TSNE(bool deserializing) { }
109    protected TSNE(TSNE<T> original, Cloner cloner) : base(original, cloner) {
110      distance = cloner.Clone(original.distance);
111      maxIter = original.maxIter;
112      stopLyingIter = original.stopLyingIter;
113      momSwitchIter = original.momSwitchIter;
114      momentum = original.momentum;
115      finalMomentum = original.finalMomentum;
116      eta = original.eta;
117      random = cloner.Clone(random);
118      results = cloner.Clone(results);
119      dataRowLookup = original.dataRowLookup.ToDictionary(entry => entry.Key, entry => entry.Value.Select(x => x).ToList());
120      dataRows = original.dataRows.ToDictionary(entry => entry.Key, entry => cloner.Clone(entry.Value));
121    }
122    public override IDeepCloneable Clone(Cloner cloner) { return new TSNE<T>(this, cloner); }
123    public TSNE(IDistance<T> distance, IRandom random, ResultCollection results = null, int maxIter = 1000, int stopLyingIter = 250, int momSwitchIter = 250, double momentum = .5, double finalMomentum = .8, double eta = 200.0, Dictionary<string, List<int>> dataRowLookup = null, Dictionary<string, ScatterPlotDataRow> dataRows = null) {
124      this.distance = distance;
125      this.maxIter = maxIter;
126      this.stopLyingIter = stopLyingIter;
127      this.momSwitchIter = momSwitchIter;
128      this.momentum = momentum;
129      this.finalMomentum = finalMomentum;
130      this.eta = eta;
131      this.random = random;
132      this.results = results;
133      this.dataRowLookup = dataRowLookup;
134      if (dataRows != null) this.dataRows = dataRows;
135      else { this.dataRows = new Dictionary<string, ScatterPlotDataRow>(); }
136    }
137    #endregion
138
139    public double[,] Run(T[] data, int newDimensions, double perplexity, double theta) {
140      var currentMomentum = momentum;
141      var noDatapoints = data.Length;
142      if (noDatapoints - 1 < 3 * perplexity) throw new ArgumentException("Perplexity too large for the number of data points!");
143      SetUpResults(data);
144      Running = true;
145      var exact = Math.Abs(theta) < double.Epsilon;
146      var newData = new double[noDatapoints, newDimensions];
147      var dY = new double[noDatapoints, newDimensions];
148      var uY = new double[noDatapoints, newDimensions];
149      var gains = new double[noDatapoints, newDimensions];
150      for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < newDimensions; j++) gains[i, j] = 1.0;
151      double[,] p = null;
152      int[] rowP = null;
153      int[] colP = null;
154      double[] valP = null;
155      var rand = new NormalDistributedRandom(random, 0, 1);
156
157      //Calculate Similarities
158      if (exact) p = CalculateExactSimilarites(data, perplexity);
159      else CalculateApproximateSimilarities(data, perplexity, out rowP, out colP, out valP);
160
161      // Lie about the P-values
162      if (exact) for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < noDatapoints; j++) p[i, j] *= 12.0;
163      else for (var i = 0; i < rowP[noDatapoints]; i++) valP[i] *= 12.0;
164
165      // Initialize solution (randomly)
166      for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < newDimensions; j++) newData[i, j] = rand.NextDouble() * .0001;
167
168      // Perform main training loop
169      for (var iter = 0; iter < maxIter && Running; iter++) {
170        if (exact) ComputeExactGradient(p, newData, noDatapoints, newDimensions, dY);
171        else ComputeGradient(rowP, colP, valP, newData, noDatapoints, newDimensions, dY, theta);
172        // Update gains
173        for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < newDimensions; j++) gains[i, j] = Math.Sign(dY[i, j]) != Math.Sign(uY[i, j]) ? gains[i, j] + .2 : gains[i, j] * .8;
174        for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < newDimensions; j++) if (gains[i, j] < .01) gains[i, j] = .01;
175        // Perform gradient update (with momentum and gains)
176        for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < newDimensions; j++) uY[i, j] = currentMomentum * uY[i, j] - eta * gains[i, j] * dY[i, j];
177        for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < newDimensions; j++) newData[i, j] = newData[i, j] + uY[i, j];
178        // Make solution zero-mean
179        ZeroMean(newData);
180        // Stop lying about the P-values after a while, and switch momentum
181        if (iter == stopLyingIter) {
182          if (exact) for (var i = 0; i < noDatapoints; i++) for (var j = 0; j < noDatapoints; j++) p[i, j] /= 12.0;
183          else for (var i = 0; i < rowP[noDatapoints]; i++) valP[i] /= 12.0;
184        }
185        if (iter == momSwitchIter) currentMomentum = finalMomentum;
186
187        Analyze(exact, iter, p, rowP, colP, valP, newData, noDatapoints, newDimensions, theta);
188      }
189      return newData;
190    }
191    public static double[,] Run<TR>(TR[] data, int newDimensions, double perplexity, double theta, IDistance<TR> distance, IRandom random) where TR : class, IDeepCloneable {
192      return new TSNE<TR>(distance, random).Run(data, newDimensions, perplexity, theta);
193    }
194
195    #region helpers
196
197    private void SetUpResults(IReadOnlyCollection<T> data) {
198      if (dataRowLookup == null) dataRowLookup = new Dictionary<string, List<int>> { { "Data", Enumerable.Range(0, data.Count).ToList() } };
199      if (results == null) return;
200
201      if (!results.ContainsKey(IterationResultName)) results.Add(new Result(IterationResultName, new IntValue(0)));
202      else ((IntValue)results[IterationResultName].Value).Value = 0;
203
204      if (!results.ContainsKey(ErrorResultName)) results.Add(new Result(ErrorResultName, new DoubleValue(0)));
205      else ((DoubleValue)results[ErrorResultName].Value).Value = 0;
206
207      if (!results.ContainsKey(ErrorPlotResultName)) results.Add(new Result(ErrorPlotResultName, new DataTable(ErrorPlotResultName, "Development of errors during Gradiant descent")));
208      else results[ErrorPlotResultName].Value = new DataTable(ErrorPlotResultName, "Development of errors during Gradiant descent");
209
210      var plot = results[ErrorPlotResultName].Value as DataTable;
211      if (plot == null) throw new ArgumentException("could not create/access Error-DataTable in Results-Collection");
212
213      if (!plot.Rows.ContainsKey("errors")) plot.Rows.Add(new DataRow("errors"));
214      plot.Rows["errors"].Values.Clear();
215
216      results.Add(new Result(ScatterPlotResultName, "Plot of the projected data", new ScatterPlot(DataResultName, "")));
217      results.Add(new Result(DataResultName, "Projected Data", new DoubleMatrix()));
218
219    }
220    private void Analyze(bool exact, int iter, double[,] p, int[] rowP, int[] colP, double[] valP, double[,] newData, int noDatapoints, int newDimensions, double theta) {
221      if (results == null) return;
222      var plot = results[ErrorPlotResultName].Value as DataTable;
223      if (plot == null) throw new ArgumentException("Could not create/access error data table in results collection. Was it removed by some effect?");
224      var errors = plot.Rows["errors"].Values;
225      var c = exact
226        ? EvaluateError(p, newData, noDatapoints, newDimensions)
227        : EvaluateError(rowP, colP, valP, newData, theta);
228      errors.Add(c);
229      ((IntValue)results[IterationResultName].Value).Value = iter + 1;
230      ((DoubleValue)results[ErrorResultName].Value).Value = errors.Last();
231
232      var ndata = Normalize(newData);
233      results[DataResultName].Value = new DoubleMatrix(ndata);
234      var splot = results[ScatterPlotResultName].Value as ScatterPlot;
235      FillScatterPlot(ndata, splot);
236
237
238    }
239    private void FillScatterPlot(double[,] lowDimData, ScatterPlot plot) {
240      foreach (var rowName in dataRowLookup.Keys) {
241        if (!plot.Rows.ContainsKey(rowName))
242          plot.Rows.Add(dataRows.ContainsKey(rowName) ? dataRows[rowName] : new ScatterPlotDataRow(rowName, "", new List<Point2D<double>>()));
243        plot.Rows[rowName].Points.Replace(dataRowLookup[rowName].Select(i => new Point2D<double>(lowDimData[i, 0], lowDimData[i, 1])));
244      }
245    }
246    private static double[,] Normalize(double[,] data) {
247      var max = new double[data.GetLength(1)];
248      var min = new double[data.GetLength(1)];
249      var res = new double[data.GetLength(0), data.GetLength(1)];
250      for (var i = 0; i < max.Length; i++) max[i] = min[i] = data[0, i];
251      for (var i = 0; i < data.GetLength(0); i++)
252        for (var j = 0; j < data.GetLength(1); j++) {
253          var v = data[i, j];
254          max[j] = Math.Max(max[j], v);
255          min[j] = Math.Min(min[j], v);
256        }
257      for (var i = 0; i < data.GetLength(0); i++) {
258        for (var j = 0; j < data.GetLength(1); j++) {
259          res[i, j] = (data[i, j] - (max[j] + min[j]) / 2) / (max[j] - min[j]);
260        }
261      }
262      return res;
263    }
264    private void CalculateApproximateSimilarities(T[] data, double perplexity, out int[] rowP, out int[] colP, out double[] valP) {
265      // Compute asymmetric pairwise input similarities
266      ComputeGaussianPerplexity(data, data.Length, out rowP, out colP, out valP, perplexity, (int)(3 * perplexity));
267      // Symmetrize input similarities
268      int[] sRowP, symColP;
269      double[] sValP;
270      SymmetrizeMatrix(rowP, colP, valP, out sRowP, out symColP, out sValP);
271      rowP = sRowP;
272      colP = symColP;
273      valP = sValP;
274      var sumP = .0;
275      for (var i = 0; i < rowP[data.Length]; i++) sumP += valP[i];
276      for (var i = 0; i < rowP[data.Length]; i++) valP[i] /= sumP;
277    }
278    private double[,] CalculateExactSimilarites(T[] data, double perplexity) {
279      // Compute similarities
280      var p = new double[data.Length, data.Length];
281      ComputeGaussianPerplexity(data, data.Length, p, perplexity);
282      // Symmetrize input similarities
283      for (var n = 0; n < data.Length; n++) {
284        for (var m = n + 1; m < data.Length; m++) {
285          p[n, m] += p[m, n];
286          p[m, n] = p[n, m];
287        }
288      }
289      var sumP = .0;
290      for (var i = 0; i < data.Length; i++) for (var j = 0; j < data.Length; j++) sumP += p[i, j];
291      for (var i = 0; i < data.Length; i++) for (var j = 0; j < data.Length; j++) p[i, j] /= sumP;
292      return p;
293    }
294
295    private void ComputeGaussianPerplexity(IReadOnlyList<T> x, int n, out int[] rowP, out int[] colP, out double[] valP, double perplexity, int k) {
296      if (perplexity > k) throw new ArgumentException("Perplexity should be lower than K!");
297
298      // Allocate the memory we need
299      rowP = new int[n + 1];
300      colP = new int[n * k];
301      valP = new double[n * k];
302      var curP = new double[n - 1];
303      rowP[0] = 0;
304      for (var i = 0; i < n; i++) rowP[i + 1] = rowP[i] + k;
305
306      // Build ball tree on data set
307      var tree = new VPTree<IDataPoint<T>>(new DataPointDistance<T>(distance));
308      var objX = new List<IDataPoint<T>>();
309      for (var i = 0; i < n; i++) objX.Add(new DataPoint<T>(i, x[i]));
310      tree.Create(objX);
311
312      // Loop over all points to find nearest neighbors
313      var indices = new List<IDataPoint<T>>();
314      var distances = new List<double>();
315      for (var i = 0; i < n; i++) {
316
317        // Find nearest neighbors
318        indices.Clear();
319        distances.Clear();
320        tree.Search(objX[i], k + 1, out indices, out distances);
321
322        // Initialize some variables for binary search
323        var found = false;
324        var beta = 1.0;
325        var minBeta = -double.MaxValue;
326        var maxBeta = double.MaxValue;
327        const double tol = 1e-5;
328
329        // Iterate until we found a good perplexity
330        var iter = 0; double sumP = 0;
331        while (!found && iter < 200) {
332
333          // Compute Gaussian kernel row
334          for (var m = 0; m < k; m++) curP[m] = Math.Exp(-beta * distances[m + 1]);
335
336          // Compute entropy of current row
337          sumP = double.Epsilon;
338          for (var m = 0; m < k; m++) sumP += curP[m];
339          var h = .0;
340          for (var m = 0; m < k; m++) h += beta * (distances[m + 1] * curP[m]);
341          h = h / sumP + Math.Log(sumP);
342
343          // Evaluate whether the entropy is within the tolerance level
344          var hdiff = h - Math.Log(perplexity);
345          if (hdiff < tol && -hdiff < tol) {
346            found = true;
347          } else {
348            if (hdiff > 0) {
349              minBeta = beta;
350              if (maxBeta.IsAlmost(double.MaxValue) || maxBeta.IsAlmost(double.MinValue))
351                beta *= 2.0;
352              else
353                beta = (beta + maxBeta) / 2.0;
354            } else {
355              maxBeta = beta;
356              if (minBeta.IsAlmost(double.MinValue) || minBeta.IsAlmost(double.MaxValue))
357                beta /= 2.0;
358              else
359                beta = (beta + minBeta) / 2.0;
360            }
361          }
362
363          // Update iteration counter
364          iter++;
365        }
366
367        // Row-normalize current row of P and store in matrix
368        for (var m = 0; m < k; m++) curP[m] /= sumP;
369        for (var m = 0; m < k; m++) {
370          colP[rowP[i] + m] = indices[m + 1].Index;
371          valP[rowP[i] + m] = curP[m];
372        }
373      }
374    }
375    private void ComputeGaussianPerplexity(T[] x, int n, double[,] p, double perplexity) {
376      // Compute the squared Euclidean distance matrix
377      var dd = ComputeDistances(x);
378      // Compute the Gaussian kernel row by row
379
380      for (var i = 0; i < n; i++) {
381        // Initialize some variables
382        var found = false;
383        var beta = 1.0;
384        var minBeta = -double.MaxValue;
385        var maxBeta = double.MaxValue;
386        const double tol = 1e-5;
387        double sumP = 0;
388
389        // Iterate until we found a good perplexity
390        var iter = 0;
391        while (!found && iter < 200) {
392
393          // Compute Gaussian kernel row
394          for (var m = 0; m < n; m++) p[i, m] = Math.Exp(-beta * dd[i][m]);
395          p[i, i] = double.Epsilon;
396
397          // Compute entropy of current row
398          sumP = double.Epsilon;
399          for (var m = 0; m < n; m++) sumP += p[i, m];
400          var h = 0.0;
401          for (var m = 0; m < n; m++) h += beta * (dd[i][m] * p[i, m]);
402          h = h / sumP + Math.Log(sumP);
403
404          // Evaluate whether the entropy is within the tolerance level
405          var hdiff = h - Math.Log(perplexity);
406          if (hdiff < tol && -hdiff < tol) {
407            found = true;
408          } else {
409            if (hdiff > 0) {
410              minBeta = beta;
411              if (maxBeta.IsAlmost(double.MaxValue) || maxBeta.IsAlmost(double.MinValue))
412                beta *= 2.0;
413              else
414                beta = (beta + maxBeta) / 2.0;
415            } else {
416              maxBeta = beta;
417              if (minBeta.IsAlmost(double.MinValue) || minBeta.IsAlmost(double.MaxValue))
418                beta /= 2.0;
419              else
420                beta = (beta + minBeta) / 2.0;
421            }
422          }
423
424          // Update iteration counter
425          iter++;
426        }
427
428        // Row normalize P
429        for (var m = 0; m < n; m++) p[i, m] /= sumP;
430      }
431    }
432    private double[][] ComputeDistances(T[] x) {
433      return x.Select(m => x.Select(n => distance.Get(m, n)).ToArray()).ToArray();
434    }
435    private static void ComputeExactGradient(double[,] p, double[,] y, int n, int d, double[,] dC) {
436
437      // Make sure the current gradient contains zeros
438      for (var i = 0; i < n; i++) for (var j = 0; j < d; j++) dC[i, j] = 0.0;
439
440      // Compute the squared Euclidean distance matrix
441      var dd = new double[n, n];
442      ComputeSquaredEuclideanDistance(y, n, d, dd);
443
444      // Compute Q-matrix and normalization sum
445      var q = new double[n, n];
446      var sumQ = .0;
447      for (var n1 = 0; n1 < n; n1++) {
448        for (var m = 0; m < n; m++) {
449          if (n1 == m) continue;
450          q[n1, m] = 1 / (1 + dd[n1, m]);
451          sumQ += q[n1, m];
452        }
453      }
454
455      // Perform the computation of the gradient
456      for (var n1 = 0; n1 < n; n1++) {
457        for (var m = 0; m < n; m++) {
458          if (n1 == m) continue;
459          var mult = (p[n1, m] - q[n1, m] / sumQ) * q[n1, m];
460          for (var d1 = 0; d1 < d; d1++) {
461            dC[n1, d1] += (y[n1, d1] - y[m, d1]) * mult;
462          }
463        }
464      }
465    }
466    private static void ComputeSquaredEuclideanDistance(double[,] x, int n, int d, double[,] dd) {
467      var dataSums = new double[n];
468      for (var i = 0; i < n; i++) {
469        for (var j = 0; j < d; j++) {
470          dataSums[i] += x[i, j] * x[i, j];
471        }
472      }
473      for (var i = 0; i < n; i++) {
474        for (var m = 0; m < n; m++) {
475          dd[i, m] = dataSums[i] + dataSums[m];
476        }
477      }
478      for (var i = 0; i < n; i++) {
479        dd[i, i] = 0.0;
480        for (var m = i + 1; m < n; m++) {
481          dd[i, m] = 0.0;
482          for (var j = 0; j < d; j++) {
483            dd[i, m] += (x[i, j] - x[m, j]) * (x[i, j] - x[m, j]);
484          }
485          dd[m, i] = dd[i, m];
486        }
487      }
488    }
489    private static void ComputeGradient(int[] rowP, int[] colP, double[] valP, double[,] y, int n, int d, double[,] dC, double theta) {
490      var tree = new SPTree(y);
491      double[] sumQ = { 0 };
492      var posF = new double[n, d];
493      var negF = new double[n, d];
494      tree.ComputeEdgeForces(rowP, colP, valP, n, posF);
495      var row = new double[d];
496      for (var n1 = 0; n1 < n; n1++) {
497        Buffer.BlockCopy(negF, (sizeof(double) * n1 * d), row, 0, d);
498        tree.ComputeNonEdgeForces(n1, theta, row, sumQ);
499      }
500
501      // Compute final t-SNE gradient
502      for (var i = 0; i < n; i++)
503        for (var j = 0; j < d; j++) {
504          dC[i, j] = posF[i, j] - negF[i, j] / sumQ[0];
505        }
506    }
507    private static double EvaluateError(double[,] p, double[,] y, int n, int d) {
508      // Compute the squared Euclidean distance matrix
509      var dd = new double[n, n];
510      var q = new double[n, n];
511      ComputeSquaredEuclideanDistance(y, n, d, dd);
512
513      // Compute Q-matrix and normalization sum
514      var sumQ = double.Epsilon;
515      for (var n1 = 0; n1 < n; n1++) {
516        for (var m = 0; m < n; m++) {
517          if (n1 != m) {
518            q[n1, m] = 1 / (1 + dd[n1, m]);
519            sumQ += q[n1, m];
520          } else q[n1, m] = double.Epsilon;
521        }
522      }
523      for (var i = 0; i < n; i++) for (var j = 0; j < n; j++) q[i, j] /= sumQ;
524
525      // Sum t-SNE error
526      var c = .0;
527      for (var i = 0; i < n; i++)
528        for (var j = 0; j < n; j++) {
529          c += p[i, j] * Math.Log((p[i, j] + float.Epsilon) / (q[i, j] + float.Epsilon));
530        }
531      return c;
532    }
533    private static double EvaluateError(IReadOnlyList<int> rowP, IReadOnlyList<int> colP, IReadOnlyList<double> valP, double[,] y, double theta) {
534      // Get estimate of normalization term
535      var n = y.GetLength(0);
536      var d = y.GetLength(1);
537      var tree = new SPTree(y);
538      var buff = new double[d];
539      double[] sumQ = { 0 };
540      for (var i = 0; i < n; i++) tree.ComputeNonEdgeForces(i, theta, buff, sumQ);
541
542      // Loop over all edges to compute t-SNE error
543      var c = .0;
544      for (var k = 0; k < n; k++) {
545        for (var i = rowP[k]; i < rowP[k + 1]; i++) {
546          var q = .0;
547          for (var j = 0; j < d; j++) buff[j] = y[k, j];
548          for (var j = 0; j < d; j++) buff[j] -= y[colP[i], j];
549          for (var j = 0; j < d; j++) q += buff[j] * buff[j];
550          q = 1.0 / (1.0 + q) / sumQ[0];
551          c += valP[i] * Math.Log((valP[i] + float.Epsilon) / (q + float.Epsilon));
552        }
553      }
554      return c;
555    }
556    private static void SymmetrizeMatrix(IReadOnlyList<int> rowP, IReadOnlyList<int> colP, IReadOnlyList<double> valP, out int[] symRowP, out int[] symColP, out double[] symValP) {
557
558      // Count number of elements and row counts of symmetric matrix
559      var n = rowP.Count - 1;
560      var rowCounts = new int[n];
561      for (var j = 0; j < n; j++) {
562        for (var i = rowP[j]; i < rowP[j + 1]; i++) {
563
564          // Check whether element (col_P[i], n) is present
565          var present = false;
566          for (var m = rowP[colP[i]]; m < rowP[colP[i] + 1]; m++) {
567            if (colP[m] == j) present = true;
568          }
569          if (present) rowCounts[j]++;
570          else {
571            rowCounts[j]++;
572            rowCounts[colP[i]]++;
573          }
574        }
575      }
576      var noElem = 0;
577      for (var i = 0; i < n; i++) noElem += rowCounts[i];
578
579      // Allocate memory for symmetrized matrix
580      symRowP = new int[n + 1];
581      symColP = new int[noElem];
582      symValP = new double[noElem];
583
584      // Construct new row indices for symmetric matrix
585      symRowP[0] = 0;
586      for (var i = 0; i < n; i++) symRowP[i + 1] = symRowP[i] + rowCounts[i];
587
588      // Fill the result matrix
589      var offset = new int[n];
590      for (var j = 0; j < n; j++) {
591        for (var i = rowP[j]; i < rowP[j + 1]; i++) {                                  // considering element(n, colP[i])
592
593          // Check whether element (col_P[i], n) is present
594          var present = false;
595          for (var m = rowP[colP[i]]; m < rowP[colP[i] + 1]; m++) {
596            if (colP[m] != j) continue;
597            present = true;
598            if (j > colP[i]) continue; // make sure we do not add elements twice
599            symColP[symRowP[j] + offset[j]] = colP[i];
600            symColP[symRowP[colP[i]] + offset[colP[i]]] = j;
601            symValP[symRowP[j] + offset[j]] = valP[i] + valP[m];
602            symValP[symRowP[colP[i]] + offset[colP[i]]] = valP[i] + valP[m];
603          }
604
605          // If (colP[i], n) is not present, there is no addition involved
606          if (!present) {
607            symColP[symRowP[j] + offset[j]] = colP[i];
608            symColP[symRowP[colP[i]] + offset[colP[i]]] = j;
609            symValP[symRowP[j] + offset[j]] = valP[i];
610            symValP[symRowP[colP[i]] + offset[colP[i]]] = valP[i];
611          }
612
613          // Update offsets
614          if (present && (j > colP[i])) continue;
615          offset[j]++;
616          if (colP[i] != j) offset[colP[i]]++;
617        }
618      }
619
620      // Divide the result by two
621      for (var i = 0; i < noElem; i++) symValP[i] /= 2.0;
622    }
623    private static void ZeroMean(double[,] x) {
624      // Compute data mean
625      var n = x.GetLength(0);
626      var d = x.GetLength(1);
627      var mean = new double[d];
628      for (var i = 0; i < n; i++) {
629        for (var j = 0; j < d; j++) {
630          mean[j] += x[i, j];
631        }
632      }
633      for (var i = 0; i < d; i++) {
634        mean[i] /= n;
635      }
636      // Subtract data mean
637      for (var i = 0; i < n; i++) {
638        for (var j = 0; j < d; j++) {
639          x[i, j] -= mean[j];
640        }
641      }
642    }
643    #endregion
644  }
645}
Note: See TracBrowser for help on using the repository browser.