From dbe8462ddb7bc6d94f34fbfe3bd2db2ebef2cdf1 Mon Sep 17 00:00:00 2001 From: Gene Kogan Date: Sun, 31 Jan 2016 20:39:33 +0530 Subject: [PATCH] added option to run iterations manually --- README.md | 1 + .../example-images.xcscmblueprint | 30 +++++ .../UserInterfaceState.xcuserstate | Bin 16063 -> 18528 bytes example-images/src/ofApp.h | 4 +- .../UserInterfaceState.xcuserstate | Bin 20038 -> 21138 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 5 + example/src/ofApp.cpp | 38 ++++-- example/src/ofApp.h | 3 + src/bhtsne/tsne.cpp | 124 +++++++++++------- src/bhtsne/tsne.h | 18 ++- src/ofxTSNE.cpp | 55 ++++++-- src/ofxTSNE.h | 19 ++- 12 files changed, 221 insertions(+), 76 deletions(-) create mode 100644 example-images/example-images.xcodeproj/project.xcworkspace/xcshareddata/example-images.xcscmblueprint create mode 100644 example/example.xcodeproj/xcuserdata/gene.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist diff --git a/README.md b/README.md index 12fb9b9..c5c7d08 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ ofxTSNE is very simple to run, containing only one function. The harder part is `example` demonstrates how to use ofxTSNE by constructing a toy 100-dim dataset. It contains comments explaining what the parameters do and how to set them. +**clever hack**: try setting D=3 and instead of making points clustered around 10 centers, make the points random 3d points and map the point's color linearly from its 3d position. #### clustering images diff --git a/example-images/example-images.xcodeproj/project.xcworkspace/xcshareddata/example-images.xcscmblueprint b/example-images/example-images.xcodeproj/project.xcworkspace/xcshareddata/example-images.xcscmblueprint new file mode 100644 index 0000000..1b6c554 --- /dev/null +++ b/example-images/example-images.xcodeproj/project.xcworkspace/xcshareddata/example-images.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "E0DC71BFE67F75D80C24B658C9671C34FDB45F42", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "CE6DB5CAD37933655326CE4949F968A4A0799398" : 0, + "E0DC71BFE67F75D80C24B658C9671C34FDB45F42" : 0 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "66619A9E-9261-40DB-A457-E6E7339B3EAA", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "CE6DB5CAD37933655326CE4949F968A4A0799398" : "ofxCcv\/", + "E0DC71BFE67F75D80C24B658C9671C34FDB45F42" : "ofxTSNE\/" + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "example-images", + "DVTSourceControlWorkspaceBlueprintVersion" : 204, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "example-images\/example-images.xcodeproj", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/kylemcdonald\/ofxCcv.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "CE6DB5CAD37933655326CE4949F968A4A0799398" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/genekogan\/ofxTSNE.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "E0DC71BFE67F75D80C24B658C9671C34FDB45F42" + } + ] +} \ No newline at end of file diff --git a/example-images/example-images.xcodeproj/project.xcworkspace/xcuserdata/gene.xcuserdatad/UserInterfaceState.xcuserstate b/example-images/example-images.xcodeproj/project.xcworkspace/xcuserdata/gene.xcuserdatad/UserInterfaceState.xcuserstate index b50d009672bbaef10d0750911dd6e56bad184ca9..71269610d6f36a5b7e34c4431488e34dc542743c 100644 GIT binary patch literal 18528 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 C*smb~ diff --git a/example-images/src/ofApp.h b/example-images/src/ofApp.h index 16dfa58..f718b0c 100644 --- a/example-images/src/ofApp.h +++ b/example-images/src/ofApp.h @@ -28,10 +28,12 @@ class ofApp : public ofBaseApp{ ofxTSNE tsne; vector images; - vector > imagePoints; + vector > imagePoints; vector > encodings; ofxPanel gui; ofParameter scale; ofParameter imageSize; + + int t; }; diff --git a/example/example.xcodeproj/project.xcworkspace/xcuserdata/gene.xcuserdatad/UserInterfaceState.xcuserstate b/example/example.xcodeproj/project.xcworkspace/xcuserdata/gene.xcuserdatad/UserInterfaceState.xcuserstate index bf6081a7d28b2d9314d10e79721d01717acd08d4..4ae9e4eb337e4f14426a78dce5f5a34cb999b7ba 100644 GIT binary patch delta 12537 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 GT-1;@Y%@H@Bw{secxU2qRP1W&;;@Ep7Z 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 oN~d;99gsRGb#UsC)Lp5^Q-4eSy{ + + diff --git a/example/src/ofApp.cpp b/example/src/ofApp.cpp index 17a0546..b2f0732 100644 --- a/example/src/ofApp.cpp +++ b/example/src/ofApp.cpp @@ -3,6 +3,12 @@ //-------------------------------------------------------------- void ofApp::setup(){ + // the runManually flag lets us step through each iteration of t-SNE manually, + // letting us watch the process take place. If set to false, the whole + // process will take place internally when you run ofxTSNE::run + + runManually = true; + // first let's construct our toy dataset. // we will create N samples of dimension D, which will be distributed // into a number of classes, where a point belonging to a particular @@ -13,8 +19,10 @@ void ofApp::setup(){ // transforming them from high-dimensional to low-dimensional space, so // in this example, the classes are just for us to see this clearer. + // pick initial parameters - int N = 2000; // number of points in our dataset + + int N = 1500; // number of points in our dataset int D = 100; // number of dimensions in our data int numClasses = 10; // how many classes to create @@ -100,28 +108,36 @@ void ofApp::setup(){ // normalize = this will automatically remap all tsne points to range {0, 1} // if false, you'll get the original points. - int dims = 2; - float perplexity = 30; - float theta = 0.5; + float perplexity = 40; + float theta = 0.2; bool normalize = true; // finally let's run ofxTSNE! this may take a while depending on your // data, and it will return a set of embedded points, structured as // a vector > where the inner vector contains (dims) elements. // We will unpack these points and assign them back to our testPoints dataset. - - vector > tsnePoints = tsne.run(data, dims, perplexity, theta, normalize); - // unpack the embedded points back into our testPoints - for (int i=0; i testPoints; + vector > tsnePoints; + + bool runManually; }; diff --git a/src/bhtsne/tsne.cpp b/src/bhtsne/tsne.cpp index 11fa0a4..c1a87c2 100755 --- a/src/bhtsne/tsne.cpp +++ b/src/bhtsne/tsne.cpp @@ -46,24 +46,33 @@ using namespace std; // Perform t-SNE -void TSNE::run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta) { +void TSNE::run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, bool runManually) { + this->X = X; + this->N = N; + this->D = D; + this->Y = Y; + this->no_dims = no_dims; + this->perplexity = perplexity; + this->theta = theta; // Determine whether we are using an exact algorithm if(N - 1 < 3 * perplexity) { printf("Perplexity too large for the number of data points!\n"); exit(1); } printf("Using no_dims = %d, perplexity = %f, and theta = %f\n", no_dims, perplexity, theta); - bool exact = (theta == .0) ? true : false; + exact = (theta == .0) ? true : false; // Set learning parameters - float total_time = .0; - clock_t start, end; - int max_iter = 1000, stop_lying_iter = 250, mom_switch_iter = 250; - double momentum = .5, final_momentum = .8; - double eta = 200.0; + total_time = .0; + max_iter = 1000; + stop_lying_iter = 250; + mom_switch_iter = 250; + momentum = .5; + final_momentum = .8; + eta = 200.0; // Allocate some memory - double* dY = (double*) malloc(N * no_dims * sizeof(double)); - double* uY = (double*) malloc(N * no_dims * sizeof(double)); - double* gains = (double*) malloc(N * no_dims * sizeof(double)); + dY = (double*) malloc(N * no_dims * sizeof(double)); + uY = (double*) malloc(N * no_dims * sizeof(double)); + gains = (double*) malloc(N * no_dims * sizeof(double)); if(dY == NULL || uY == NULL || gains == NULL) { printf("Memory allocation failed!\n"); exit(1); } for(int i = 0; i < N * no_dims; i++) uY[i] = .0; for(int i = 0; i < N * no_dims; i++) gains[i] = 1.0; @@ -79,7 +88,6 @@ void TSNE::run(double* X, int N, int D, double* Y, int no_dims, double perplexit for(int i = 0; i < N * D; i++) X[i] /= max_X; // Compute input similarities for exact t-SNE - double* P; unsigned int* row_P; unsigned int* col_P; double* val_P; if(exact) { // Compute similarities @@ -130,45 +138,16 @@ void TSNE::run(double* X, int N, int D, double* Y, int no_dims, double perplexit if(exact) printf("Input similarities computed in %4.2f seconds!\nLearning embedding...\n", (float) (end - start) / CLOCKS_PER_SEC); else printf("Input similarities computed in %4.2f seconds (sparsity = %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++) { - - // Compute (approximate) gradient - if(exact) computeExactGradient(P, Y, N, no_dims, dY); - else computeGradient(P, row_P, col_P, val_P, Y, N, no_dims, dY, theta); - - // Update gains - for(int i = 0; i < N * no_dims; i++) gains[i] = (sign(dY[i]) != sign(uY[i])) ? (gains[i] + .2) : (gains[i] * .8); - for(int i = 0; i < N * no_dims; i++) if(gains[i] < .01) gains[i] = .01; - - // Perform gradient update (with momentum and gains) - for(int i = 0; i < N * no_dims; i++) uY[i] = momentum * uY[i] - eta * gains[i] * dY[i]; - for(int i = 0; i < N * no_dims; i++) Y[i] = Y[i] + uY[i]; - - // Make solution zero-mean - zeroMean(Y, N, no_dims); - - // Stop lying about the P-values after a while, and switch momentum - if(iter == stop_lying_iter) { - if(exact) { for(int i = 0; i < N * N; i++) P[i] /= 12.0; } - else { for(int i = 0; i < row_P[N]; i++) val_P[i] /= 12.0; } - } - if(iter == mom_switch_iter) momentum = final_momentum; - - // Print out progress - if(iter > 0 && (iter % 50 == 0 || iter == max_iter - 1)) { - end = clock(); - double C = .0; - if(exact) C = evaluateError(P, Y, N, no_dims); - else C = evaluateError(row_P, col_P, val_P, Y, N, no_dims, theta); // doing approximate computation here! - if(iter == 0) - printf("Iteration %d: error is %f\n", iter + 1, C); - else { - total_time += (float) (end - start) / CLOCKS_PER_SEC; - printf("Iteration %d: error is %f (50 iterations in %4.2f seconds)\n", iter, C, (float) (end - start) / CLOCKS_PER_SEC); - } - start = clock(); + + iter = 0; + if (!runManually) { + while(iter < max_iter) { + runIteration(); } } +} + +void TSNE::finish() { end = clock(); total_time += (float) (end - start) / CLOCKS_PER_SEC; // Clean up memory @@ -184,6 +163,55 @@ void TSNE::run(double* X, int N, int D, double* Y, int no_dims, double perplexit printf("Fitting performed in %4.2f seconds.\n", total_time); } +void TSNE::runIteration() { + + if (iter >= max_iter) { + return; + } + + // Compute (approximate) gradient + if(exact) computeExactGradient(P, Y, N, no_dims, dY); + else computeGradient(P, row_P, col_P, val_P, Y, N, no_dims, dY, theta); + + // Update gains + for(int i = 0; i < N * no_dims; i++) gains[i] = (sign(dY[i]) != sign(uY[i])) ? (gains[i] + .2) : (gains[i] * .8); + for(int i = 0; i < N * no_dims; i++) if(gains[i] < .01) gains[i] = .01; + + // Perform gradient update (with momentum and gains) + for(int i = 0; i < N * no_dims; i++) uY[i] = momentum * uY[i] - eta * gains[i] * dY[i]; + for(int i = 0; i < N * no_dims; i++) Y[i] = Y[i] + uY[i]; + + // Make solution zero-mean + zeroMean(Y, N, no_dims); + + // Stop lying about the P-values after a while, and switch momentum + if(iter == stop_lying_iter) { + if(exact) { for(int i = 0; i < N * N; i++) P[i] /= 12.0; } + else { for(int i = 0; i < row_P[N]; i++) val_P[i] /= 12.0; } + } + if(iter == mom_switch_iter) momentum = final_momentum; + + // Print out progress + if(iter > 0 && (iter % 50 == 0 || iter == max_iter - 1)) { + end = clock(); + double C = .0; + if(exact) C = evaluateError(P, Y, N, no_dims); + else C = evaluateError(row_P, col_P, val_P, Y, N, no_dims, theta); // doing approximate computation here! + if(iter == 0) + printf("Iteration %d: error is %f\n", iter + 1, C); + else { + total_time += (float) (end - start) / CLOCKS_PER_SEC; + printf("Iteration %d: error is %f (50 iterations in %4.2f seconds)\n", iter, C, (float) (end - start) / CLOCKS_PER_SEC); + } + start = clock(); + } + + iter++; + + if (iter == max_iter) { + finish(); + } +} // Compute gradient of the t-SNE cost function (using Barnes-Hut algorithm) void TSNE::computeGradient(double* P, unsigned int* inp_row_P, unsigned int* inp_col_P, double* inp_val_P, double* Y, int N, int D, double* dC, double theta) diff --git a/src/bhtsne/tsne.h b/src/bhtsne/tsne.h index 4e6a800..7051c31 100755 --- a/src/bhtsne/tsne.h +++ b/src/bhtsne/tsne.h @@ -41,13 +41,15 @@ static inline double sign(double x) { return (x == .0 ? .0 : (x < .0 ? -1.0 : 1. class TSNE { public: - void run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta); + void run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, bool runManually=false); + void runIteration(); bool load_data(double** data, int* n, int* d, int* no_dims, double* theta, double* perplexity, int* rand_seed); void save_data(double* data, int* landmarks, double* costs, int n, int d); void symmetrizeMatrix(unsigned int** row_P, unsigned int** col_P, double** val_P, int N); // should be static! private: + void finish(); void computeGradient(double* P, unsigned int* inp_row_P, unsigned int* inp_col_P, double* inp_val_P, double* Y, int N, int D, double* dC, double theta); void computeExactGradient(double* P, double* Y, int N, int D, double* dC); double evaluateError(double* P, double* Y, int N, int D); @@ -57,6 +59,20 @@ class TSNE void computeGaussianPerplexity(double* X, int N, int D, unsigned int** _row_P, unsigned int** _col_P, double** _val_P, double perplexity, int K); void computeSquaredEuclideanDistance(double* X, int N, int D, double* DD); double randn(); + + bool exact; + float total_time; + clock_t start, end; + int iter, max_iter, stop_lying_iter, mom_switch_iter; + double momentum, final_momentum; + double eta; + double* dY, *uY, *gains; + double* P, *val_P; + unsigned int *row_P, *col_P; + + double *X, *Y; + int N, D, no_dims; + double perplexity, theta; }; #endif diff --git a/src/ofxTSNE.cpp b/src/ofxTSNE.cpp index d307fa5..53c7c98 100644 --- a/src/ofxTSNE.cpp +++ b/src/ofxTSNE.cpp @@ -1,9 +1,16 @@ #include "ofxTSNE.h" -vector > ofxTSNE::run(vector > & data, int dims, double perplexity, double theta, bool normalize) { - int N, D; - double *X, *Y; +vector > ofxTSNE::run(vector > & data, int dims, double perplexity, double theta, bool normalize, bool runManually) { + this->data = data; + this->dims = dims; + this->perplexity = perplexity; + this->theta = theta; + this->normalize = normalize; + this->runManually = runManually; + + max_iter = 1000; + iter = 0; N = data.size(); D = data[0].size(); @@ -25,22 +32,37 @@ vector > ofxTSNE::run(vector > & data, int dims, dou } // t-SNE - tsne.run(X, N, D, Y, dims, perplexity, theta); + tsne.run(X, N, D, Y, dims, perplexity, theta, runManually); + + if (runManually) { + return tsnePoints; + } + return iterate(); +} + +vector > ofxTSNE::iterate(){ + if (iter > max_iter) { + return tsnePoints; + } + + if (runManually) { + tsne.runIteration(); + } // keep track of min for normalization - vector min_, max_; + vector min_, max_; min_.resize(dims); max_.resize(dims); for (int i=0; i::max(); - max_[i] = numeric_limits::min(); + min_[i] = numeric_limits::max(); + max_[i] = numeric_limits::min(); } // unpack Y into tsnePoints tsnePoints.clear(); int idxY = 0; for (int i=0; i tsnePoint; + vector tsnePoint; tsnePoint.resize(dims); for (int j=0; j > ofxTSNE::run(vector > & data, int dims, dou } tsnePoints.push_back(tsnePoint); } - + // normalize if requested if (normalize) { for (int i=0; i > run(vector > & data, int dims=2, double perplexity=30, double theta=0.5, bool normalize=true); + vector > run(vector > & data, int dims=2, double perplexity=30, double theta=0.5, bool normalize=true, bool runManually=false); + vector > iterate(); private: + void finish(); + TSNE tsne; - vector > tsnePoints; + vector > tsnePoints; + + vector > data; + int dims; + double perplexity; + double theta; + bool normalize; + bool runManually; + + int N, D; + double *X, *Y; + + int iter, max_iter; };