Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
63e1ddb
introduce stateful API
May 26, 2021
77a7e1b
add API to free memory, use it
May 26, 2021
1be20c4
safer free_tsne API
May 26, 2021
00bc9fc
fix elapsed time reporting
May 27, 2021
86ba173
fix reviewer comments
May 27, 2021
05be25f
constify all the things
Jun 9, 2021
5bef9a1
use move and rvalue refs for TSNEState initializer
Jun 9, 2021
ae57d41
Update clang-format config to match cellranger.
adam-azarchs Jun 17, 2021
48300a0
clang-tidy google-readability-braces-around-statements
adam-azarchs Jun 17, 2021
68cd3ce
clang-tidy google-readability-casting
adam-azarchs Jun 17, 2021
30422ff
clang-tidy modernize-use-equals-delete
adam-azarchs Jun 17, 2021
ed93c6b
clang-tidy bugprone-reserved-identifier
adam-azarchs Jun 17, 2021
22a0a06
clang-tidy modernize-use-nodiscard
adam-azarchs Jun 17, 2021
6bc1ec3
clang-tidy bugprone-narrowing-conversions and deadcode.
adam-azarchs Jun 17, 2021
2f968b6
clang-tidy: Adjust types and add constructors.
adam-azarchs Jun 17, 2021
101f7b1
clang-tidy readability-isolate-declaration
adam-azarchs Jun 17, 2021
af80d6a
clang-tidy readability-simplify-boolean-expr
adam-azarchs Jun 17, 2021
8910a41
clang-tidy readability-static-definition-in-anonymous-namespace
adam-azarchs Jun 17, 2021
f44c5a4
clang-tidy readability-inconsistent-declaration-parameter-name
adam-azarchs Jun 17, 2021
b6b4f53
clang-tidy readability-redundant-access-specifiers
adam-azarchs Jun 17, 2021
89a3dc8
clang-tidy readability-implicit-bool-conversion
adam-azarchs Jun 17, 2021
91ec292
clang-tidy readability-non-const-parameter
adam-azarchs Jun 17, 2021
b70fe08
Merge #12: Mostly-automatic fixups with clang-tidy.
adam-azarchs Jun 17, 2021
70bb526
hide GCC pragmas behind ifdef __GNUC__
Aug 19, 2021
5f71f74
Merge pull request #13 from 10XDev/lh/vs-fixes
Aug 20, 2021
86beed0
Run clang-tidy --fix with clang15.
adam-azarchs Dec 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tsne/bh_sne_src/sptree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
*
*/

#include "sptree.h"

#include <array>
#include <cassert>
#include <cfloat>
#include <cmath>
#include <cstdio>

#include "sptree.h"

#pragma GCC visibility push(hidden)

using std::array;
Expand Down
163 changes: 127 additions & 36 deletions tsne/bh_sne_src/tsne.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
*
*/

#include "tsne.h"

#include <array>
#include <cassert>
#include <cfloat>
Expand All @@ -42,7 +44,6 @@
#include <vector>

#include "sptree.h"
#include "tsne.h"
#include "vptree.h"

#pragma GCC visibility push(hidden)
Expand All @@ -56,6 +57,16 @@ using std::move;
using std::sqrt;
using std::vector;

struct TSNEState {
std::vector<double> P;
std::vector<unsigned int> row_P;
std::vector<unsigned int> col_P;
std::vector<double> val_P;
std::vector<double> dY;
std::vector<double> uY;
std::vector<double> gains;
Comment thread
nlhepler marked this conversation as resolved.
Outdated
};
Comment thread
nlhepler marked this conversation as resolved.
Outdated

namespace {

template <int NDIMS>
Expand Down Expand Up @@ -576,11 +587,12 @@ double randn() {
return x;
}

// Perform t-SNE
// Initialize t-SNE
template <int NDIMS>
void run(double* X, int N, int D, double* Y, double perplexity, double theta,
int rand_seed, bool skip_random_init, double* init, bool use_init,
int max_iter, int stop_lying_iter, int mom_switch_iter) {
struct TSNE* init_tsne(double* X, int N, int D, double* Y, double perplexity,
Comment thread
adam-azarchs marked this conversation as resolved.
Outdated
double theta, int rand_seed, bool skip_random_init,
double* init, bool use_init, int max_iter,
int stop_lying_iter, int mom_switch_iter) {
// Set random seed
if (skip_random_init != true) {
if (rand_seed >= 0) {
Expand All @@ -605,11 +617,7 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta,
"Using max_iter = %d, stop_lying_iter = %d, mom_switch_iter = %d\n",
max_iter, stop_lying_iter, mom_switch_iter);

// Set learning parameters
float total_time = .0;
clock_t start, end;
double momentum = .5, final_momentum = .8;
double eta = 200.0;

// Allocate some memory
vector<double> dY(N * NDIMS);
Expand Down Expand Up @@ -706,16 +714,60 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta,
"%f)!\nLearning embedding...\n",
(float)(end - start) / CLOCKS_PER_SEC,
(double)row_P[N] / ((double)N * (double)N));
start = clock();

for (int iter = 0; iter < max_iter; iter++) {
TSNEState* state = new TSNEState;
Comment thread
nlhepler marked this conversation as resolved.
Outdated
state->P = P;
state->row_P = row_P;
state->col_P = col_P;
state->val_P = val_P;
state->dY = dY;
state->uY = uY;
state->gains = gains;
TSNE* tsne = new TSNE;
tsne->N = N;
tsne->Y = Y;
tsne->no_dims = NDIMS;
tsne->theta = theta;
tsne->max_iter = max_iter;
tsne->stop_lying_iter = stop_lying_iter;
tsne->mom_switch_iter = mom_switch_iter;
tsne->state = state;
tsne->iter = 0;
tsne->total_time = 0;
tsne->residual_time = 0;
Comment thread
adam-azarchs marked this conversation as resolved.
Outdated

return tsne;
}

// Optimize t-SNE
template <int NDIMS>
bool run_n(int n, struct TSNE* tsne) {
Comment thread
adam-azarchs marked this conversation as resolved.
Outdated
// Set learning parameters
double momentum = .5, final_momentum = .8;
double eta = 200.0;

// Extract state
int N = tsne->N;
double* Y = tsne->Y;
double theta = tsne->theta;
bool exact = (theta == .0) ? true : false;
TSNEState* state = tsne->state;
vector<double>& dY = state->dY;
vector<double>& uY = state->uY;
Comment thread
nlhepler marked this conversation as resolved.
Outdated
clock_t start = clock(), end;
float elapsed;
int max_iter = std::min(tsne->iter + n, tsne->max_iter);

for (int& iter = tsne->iter; iter < max_iter; iter++) {
// Compute (approximate) gradient
if (exact)
computeExactGradient<NDIMS>(P, Y, N, dY);
computeExactGradient<NDIMS>(state->P, Y, N, dY);
else
computeGradient<NDIMS>(row_P, col_P, val_P, Y, N, dY, theta);
computeGradient<NDIMS>(state->row_P, state->col_P, state->val_P, Y, N, dY,
theta);

// Update gains
vector<double>& gains = state->gains;
for (int i = 0; i < N * NDIMS; i++)
gains[i] =
(sign(dY[i]) != sign(uY[i])) ? (gains[i] + .2) : (gains[i] * .8);
Expand All @@ -733,68 +785,107 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta,
zeroMean<NDIMS>(Y, N);

// Stop lying about the P-values after a while, and switch momentum
if (iter == stop_lying_iter) {
if (iter == tsne->stop_lying_iter) {
if (exact) {
for (int i = 0; i < N * N; i++)
P[i] /= 12.0;
state->P[i] /= 12.0;
} else {
for (int i = 0; i < row_P[N]; i++)
val_P[i] /= 12.0;
for (int i = 0; i < state->row_P[N]; i++)
state->val_P[i] /= 12.0;
}
}
if (iter == mom_switch_iter)
if (iter == tsne->mom_switch_iter)
momentum = final_momentum;

// Print out progress
if (iter > 0 && (iter % 50 == 0 || iter == max_iter - 1)) {
if (iter > 0 && (iter % 50 == 0 || iter == tsne->max_iter - 1)) {
end = clock();
double C = .0;
if (exact) {
C = evaluateError<NDIMS>(P, Y, N);
C = evaluateError<NDIMS>(state->P, Y, N);
} else {
// doing approximate computation here!
C = evaluateError<NDIMS>(row_P, col_P, val_P, Y, N, theta);
C = evaluateError<NDIMS>(state->row_P, state->col_P, state->val_P, Y, N,
theta);
}
if (iter == 0)
fprintf(stderr, "Iteration %d: error is %f\n", iter + 1, C);
else {
total_time += (float)(end - start) / CLOCKS_PER_SEC;
elapsed = (float)(end - start) / CLOCKS_PER_SEC;
Comment thread
nlhepler marked this conversation as resolved.
Outdated
tsne->total_time += elapsed;
tsne->residual_time += elapsed;
fprintf(stderr,
"Iteration %d: error is %f (50 iterations in %4.2f seconds)\n",
iter, C, (float)(end - start) / CLOCKS_PER_SEC);
iter, C, tsne->residual_time);
tsne->residual_time = 0;
}
start = clock();
}
}
end = clock();
total_time += (float)(end - start) / CLOCKS_PER_SEC;
elapsed = (float)(end - start) / CLOCKS_PER_SEC;
tsne->total_time += elapsed;
tsne->residual_time += elapsed;

fprintf(stderr, "Fitting performed in %4.2f seconds.\n", total_time);
if (tsne->iter >= tsne->max_iter) {
fprintf(stderr, "Fitting performed in %4.2f seconds.\n", tsne->total_time);
return true;
}
return false;
}

} // namespace

extern "C" {

void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims,
double perplexity, double theta, int rand_seed,
bool skip_random_init, double* init, bool use_init,
int max_iter, int stop_lying_iter, int mom_switch_iter) {
struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y,
int no_dims, double perplexity, double theta,
int rand_seed, bool skip_random_init,
double* init, bool use_init, int max_iter,
int stop_lying_iter, int mom_switch_iter) {
assert(no_dims == 2 || no_dims == 3);
switch (no_dims) {
case 2:
run<2>(X, N, D, Y, perplexity, theta, rand_seed, skip_random_init, init,
use_init, max_iter, stop_lying_iter, mom_switch_iter);
break;
return init_tsne<2>(X, N, D, Y, perplexity, theta, rand_seed,
skip_random_init, init, use_init, max_iter,
stop_lying_iter, mom_switch_iter);
case 3:
run<3>(X, N, D, Y, perplexity, theta, rand_seed, skip_random_init, init,
use_init, max_iter, stop_lying_iter, mom_switch_iter);
break;
return init_tsne<3>(X, N, D, Y, perplexity, theta, rand_seed,
skip_random_init, init, use_init, max_iter,
stop_lying_iter, mom_switch_iter);
default:
throw "invalid dimension";
Comment thread
nlhepler marked this conversation as resolved.
Outdated
}
}

bool DLL_PUBLIC run_n(int n, struct TSNE* tsne) {
switch (tsne->no_dims) {
case 2:
return run_n<2>(n, tsne);
case 3:
return run_n<3>(n, tsne);
default:
throw "invalid dimension";
Comment thread
nlhepler marked this conversation as resolved.
Outdated
}
}

void DLL_PUBLIC free_tsne(struct TSNE* tsne) {
if (tsne == nullptr)
return;
delete tsne->state;
delete tsne;
}

void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims,
double perplexity, double theta, int rand_seed,
bool skip_random_init, double* init, bool use_init,
int max_iter, int stop_lying_iter, int mom_switch_iter) {
struct TSNE* tsne = init_tsne(X, N, D, Y, no_dims, perplexity, theta,
rand_seed, skip_random_init, init, use_init,
max_iter, stop_lying_iter, mom_switch_iter);
run_n(max_iter, tsne);
free_tsne(tsne);
Comment thread
nlhepler marked this conversation as resolved.
Outdated
}

} // extern "C"

#pragma GCC visibility pop
27 changes: 26 additions & 1 deletion tsne/bh_sne_src/tsne.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,37 @@
#endif

extern "C" {

// stateless t-SNE
void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims,
double perplexity, double theta, int rand_seed,
bool skip_random_init, double* init, bool use_init,
int max_iter = 1000, int stop_lying_iter = 250,
int mom_switch_iter = 250);
// stateful t-SNE
struct TSNEState;
struct TSNE {
// parameters
int N;
double* Y;
int no_dims;
double theta;
int max_iter;
int stop_lying_iter;
int mom_switch_iter;
struct TSNEState* state;
int iter;
float total_time;
float residual_time;
};
Comment thread
adam-azarchs marked this conversation as resolved.
Outdated
struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y,
int no_dims, double perplexity, double theta,
int rand_seed, bool skip_random_init,
double* init, bool use_init,
int max_iter = 1000,
int stop_lying_iter = 250,
int mom_switch_iter = 250);
bool DLL_PUBLIC run_n(int n, struct TSNE* tsne);
void DLL_PUBLIC free_tsne(struct TSNE* tsne);
}

#endif
4 changes: 2 additions & 2 deletions tsne/bh_sne_src/vptree.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ class euclidean_distance final {
euclidean_distance(const euclidean_distance&) = default;
double operator()(const unsigned int t1, const unsigned int t2) const {
double dd = .0;
const double* x1 = data+t1*_D;
const double* x2 = data+t2*_D;
const double* x1 = data + t1 * _D;
const double* x2 = data + t2 * _D;
for (int d = 0; d < _D; d++) {
double diff = (x1[d] - x2[d]);
dd += diff * diff;
Expand Down