qdtsne
A quick and dirty t-SNE C++ library
Loading...
Searching...
No Matches
Status.hpp
Go to the documentation of this file.
1/*
2 *
3 * Copyright (c) 2014, Laurens van der Maaten (Delft University of Technology)
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the Delft University of Technology.
16 * 4. Neither the name of the Delft University of Technology nor the names of
17 * its contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY LAURENS VAN DER MAATEN ''AS IS'' AND ANY EXPRESS
21 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
23 * EVENT SHALL LAURENS VAN DER MAATEN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
28 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
29 * OF SUCH DAMAGE.
30 *
31 */
32
33#ifndef QDTSNE_STATUS_HPP
34#define QDTSNE_STATUS_HPP
35
36#include <vector>
37#include <cmath>
38#include <algorithm>
39#include <type_traits>
40#include <limits>
41
42#include "SPTree.hpp"
43#include "Options.hpp"
44#include "utils.hpp"
45
51namespace qdtsne {
52
63template<int num_dim_, typename Index_, typename Float_>
64class Status {
65public:
70 my_neighbors(std::move(neighbors)),
71 my_dY(my_neighbors.size() * num_dim_),
72 my_uY(my_neighbors.size() * num_dim_),
73 my_gains(my_neighbors.size() * num_dim_, 1.0),
74 my_pos_f(my_neighbors.size() * num_dim_),
75 my_neg_f(my_neighbors.size() * num_dim_),
76 my_tree(my_neighbors.size(), options.max_depth),
77 my_options(std::move(options))
78 {
79 if (options.num_threads > 1) {
80 my_parallel_buffer.resize(my_neighbors.size());
81 }
82 }
87private:
88 NeighborList<Index_, Float_> my_neighbors;
89 std::vector<Float_> my_dY, my_uY, my_gains, my_pos_f, my_neg_f;
90
91 internal::SPTree<num_dim_, Float_> my_tree;
92 std::vector<Float_> my_parallel_buffer; // Buffer to hold parallel-computed results prior to reduction.
93
94 Options my_options;
95 int my_iter = 0;
96
97 typename decltype(my_tree)::LeafApproxWorkspace my_leaf_workspace;
98
99public:
103 int iteration() const {
104 return my_iter;
105 }
106
111 int max_iterations() const {
112 return my_options.max_iterations;
113 }
114
118 size_t num_observations() const {
119 return my_neighbors.size();
120 }
121
122#ifndef NDEBUG
126 const auto& get_neighbors() const {
127 return my_neighbors;
128 }
132#endif
133
134public:
146 void run(Float_* Y, int limit) {
147 Float_ multiplier = (my_iter < my_options.stop_lying_iter ? my_options.exaggeration_factor : 1);
148 Float_ momentum = (my_iter < my_options.mom_switch_iter ? my_options.start_momentum : my_options.final_momentum);
149
150 for(; my_iter < limit; ++my_iter) {
151 // Stop lying about the P-values after a while, and switch momentum
152 if (my_iter == my_options.stop_lying_iter) {
153 multiplier = 1;
154 }
155 if (my_iter == my_options.mom_switch_iter) {
156 momentum = my_options.final_momentum;
157 }
158
159 iterate(Y, multiplier, momentum);
160 }
161 }
162
172 void run(Float_* Y) {
173 run(Y, my_options.max_iterations);
174 }
175
176private:
177 static Float_ sign(Float_ x) {
178 constexpr Float_ zero = 0;
179 constexpr Float_ one = 1;
180 return (x == zero ? zero : (x < zero ? -one : one));
181 }
182
183 void iterate(Float_* Y, Float_ multiplier, Float_ momentum) {
184 compute_gradient(Y, multiplier);
185
186 // Update gains
187 size_t ngains = my_gains.size();
188 for (size_t i = 0; i < ngains; ++i) {
189 Float_& g = my_gains[i];
190 constexpr Float_ lower_bound = 0.01;
191 constexpr Float_ to_add = 0.2;
192 constexpr Float_ to_mult = 0.8;
193 g = std::max(lower_bound, sign(my_dY[i]) != sign(my_uY[i]) ? (g + to_add) : (g * to_mult));
194 }
195
196 // Perform gradient update (with momentum and gains)
197 for (size_t i = 0; i < ngains; ++i) {
198 my_uY[i] = momentum * my_uY[i] - my_options.eta * my_gains[i] * my_dY[i];
199 Y[i] += my_uY[i];
200 }
201
202 // Make solution zero-mean
203 size_t N = num_observations();
204 for (int d = 0; d < num_dim_; ++d) {
205 auto start = Y + d;
206
207 // Compute means from column-major coordinates.
208 Float_ sum = 0;
209 for (size_t i = 0; i < N; ++i, start += num_dim_) {
210 sum += *start;
211 }
212 sum /= N;
213
214 start = Y + d;
215 for (size_t i = 0; i < N; ++i, start += num_dim_) {
216 *start -= sum;
217 }
218 }
219
220 return;
221 }
222
223private:
224 void compute_gradient(const Float_* Y, Float_ multiplier) {
225 my_tree.set(Y);
226 compute_edge_forces(Y, multiplier);
227
228 size_t N = num_observations();
229 std::fill(my_neg_f.begin(), my_neg_f.end(), 0);
230
231 Float_ sum_Q = compute_non_edge_forces();
232
233 // Compute final t-SNE gradient
234 size_t ntotal = N * static_cast<size_t>(num_dim_);
235 for (size_t i = 0; i < ntotal; ++i) {
236 my_dY[i] = my_pos_f[i] - (my_neg_f[i] / sum_Q);
237 }
238 }
239
240 void compute_edge_forces(const Float_* Y, Float_ multiplier) {
241 std::fill(my_pos_f.begin(), my_pos_f.end(), 0);
242 size_t N = num_observations();
243
244 parallelize(my_options.num_threads, N, [&](int, size_t start, size_t length) -> void {
245 for (size_t n = start, end = start + length; n < end; ++n) {
246 const auto& current = my_neighbors[n];
247 size_t offset = n * static_cast<size_t>(num_dim_); // cast to avoid overflow.
248 const Float_* self = Y + offset;
249 Float_* pos_out = my_pos_f.data() + offset;
250
251 for (const auto& x : current) {
252 Float_ sqdist = 0;
253 const Float_* neighbor = Y + static_cast<size_t>(x.first) * num_dim_; // cast to avoid overflow.
254 for (int d = 0; d < num_dim_; ++d) {
255 Float_ delta = self[d] - neighbor[d];
256 sqdist += delta * delta;
257 }
258
259 const Float_ mult = multiplier * x.second / (static_cast<Float_>(1) + sqdist);
260 for (int d = 0; d < num_dim_; ++d) {
261 pos_out[d] += mult * (self[d] - neighbor[d]);
262 }
263 }
264 }
265 });
266
267 return;
268 }
269
270 Float_ compute_non_edge_forces() {
271 size_t N = num_observations();
272
273 if (my_options.leaf_approximation) {
274 my_tree.compute_non_edge_forces_for_leaves(my_options.theta, my_leaf_workspace, my_options.num_threads);
275 }
276
277 if (my_options.num_threads > 1) {
278 // Don't use reduction methods, otherwise we get numeric imprecision
279 // issues (and stochastic results) based on the order of summation.
280 parallelize(my_options.num_threads, N, [&](int, size_t start, size_t length) -> void {
281 for (size_t n = start, end = start + length; n < end; ++n) {
282 auto neg_ptr = my_neg_f.data() + n * static_cast<size_t>(num_dim_); // cast to avoid overflow.
283 if (my_options.leaf_approximation) {
284 my_parallel_buffer[n] = my_tree.compute_non_edge_forces_from_leaves(n, neg_ptr, my_leaf_workspace);
285 } else {
286 my_parallel_buffer[n] = my_tree.compute_non_edge_forces(n, my_options.theta, neg_ptr);
287 }
288 }
289 });
290 return std::accumulate(my_parallel_buffer.begin(), my_parallel_buffer.end(), static_cast<Float_>(0));
291 }
292
293 Float_ sum_Q = 0;
294 for (size_t n = 0; n < N; ++n) {
295 auto neg_ptr = my_neg_f.data() + n * static_cast<size_t>(num_dim_); // cast to avoid overflow.
296 if (my_options.leaf_approximation) {
297 sum_Q += my_tree.compute_non_edge_forces_from_leaves(n, neg_ptr, my_leaf_workspace);
298 } else {
299 sum_Q += my_tree.compute_non_edge_forces(n, my_options.theta, neg_ptr);
300 }
301 }
302 return sum_Q;
303 }
304};
305
306}
307
308#endif
Options for the t-SNE algorithm.
Status of the t-SNE iterations.
Definition Status.hpp:64
int iteration() const
Definition Status.hpp:103
void run(Float_ *Y, int limit)
Definition Status.hpp:146
void run(Float_ *Y)
Definition Status.hpp:172
size_t num_observations() const
Definition Status.hpp:118
int max_iterations() const
Definition Status.hpp:111
Quick and dirty t-SNE.
void parallelize(int num_workers, Task_ num_tasks, Run_ run_task_range)
Definition utils.hpp:118
Options for initialize().
Definition Options.hpp:14
int mom_switch_iter
Definition Options.hpp:63
int max_iterations
Definition Options.hpp:44
bool leaf_approximation
Definition Options.hpp:102
double eta
Definition Options.hpp:79
double final_momentum
Definition Options.hpp:73
double exaggeration_factor
Definition Options.hpp:84
double start_momentum
Definition Options.hpp:68
int num_threads
Definition Options.hpp:109
double theta
Definition Options.hpp:39
int stop_lying_iter
Definition Options.hpp:53
Utilities for running t-SNE.