00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00040
#include <plearn/vmat/CenteredVMatrix.h>
00041
#include <plearn/vmat/GetInputVMatrix.h>
00042
#include "PCA.h"
00043
#include <plearn/math/plapack.h>
00044
#include <plearn/math/random.h>
00045
#include <plearn/vmat/VMat_maths.h>
00046
00047
namespace PLearn {
00048
using namespace std;
00049
00050 PCA::PCA()
00051 : algo("classical"),
00052 ncomponents(2),
00053 sigmasq(0),
00054
normalize(true)
00055 {
00056 }
00057
00058
PLEARN_IMPLEMENT_OBJECT(
PCA,
00059
"Performs a Principal Component Analysis preprocessing (projecting on the principal directions).",
00060
"This learner finds the empirical covariance matrix of the input part of\n"
00061
"the training data, and learns to project its input vectors along the\n"
00062
"principal eigenvectors of that matrix, optionally scaling by the inverse\n"
00063
"of the square root of the eigenvalues (to obtained 'sphered', i.e.\n"
00064
"Normal(0,I) data).\n"
00065
"Alternative EM algorithms are provided, that may be useful when there is\n"
00066
"a lot of data or the dimension is very high.\n"
00067 );
00068
00069 void PCA::declareOptions(
OptionList& ol)
00070 {
00071
declareOption(ol,
"ncomponents", &PCA::ncomponents, OptionBase::buildoption,
00072
"The number of principal components to keep (that's also the outputsize).");
00073
00074
declareOption(ol,
"sigmasq", &PCA::sigmasq, OptionBase::buildoption,
00075
"This gets added to the diagonal of the covariance matrix prior to eigen-decomposition.");
00076
00077
declareOption(ol,
"normalize", &PCA::normalize, OptionBase::buildoption,
00078
"If true, we divide by sqrt(eigenval) after projecting on the eigenvec.");
00079
00080
declareOption(ol,
"algo", &PCA::algo, OptionBase::buildoption,
00081
"The algorithm used to perform the Principal Component Analysis:\n"
00082
" - 'classical' : compute the eigenvectors of the covariance matrix\n"
00083
" - 'em' : EM algorithm from \"EM algorithms for PCA and SPCA\" by S. Roweis\n"
00084
" - 'em_orth' : a variant of 'em', where orthogonal components are directly computed"
00085 );
00086
00087
00088
declareOption(ol,
"mu", &PCA::mu, OptionBase::learntoption,
00089
"The (weighted) mean of the samples");
00090
declareOption(ol,
"eigenvals", &PCA::eigenvals, OptionBase::learntoption,
00091
"The ncomponents eigenvalues corresponding to the principal directions kept");
00092
declareOption(ol,
"eigenvecs", &PCA::eigenvecs, OptionBase::learntoption,
00093
"A ncomponents x inputsize matrix containing the principal eigenvectors");
00094
00095
00096 inherited::declareOptions(ol);
00097 }
00098
00100
00102 void PCA::build()
00103 {
00104 inherited::build();
00105
build_();
00106 }
00107
00109
00111 void PCA::build_()
00112 {
00113 }
00114
00116
00118 void PCA::computeCostsFromOutputs(
const Vec& input,
const Vec& output,
00119
const Vec& target,
Vec& costs)
const
00120
{
00121
static Vec reconstructed_input;
00122
reconstruct(output, reconstructed_input);
00123 costs.
resize(1);
00124 costs[0] =
powdistance(input, reconstructed_input);
00125 }
00126
00128
00130 void PCA::computeOutput(
const Vec& input,
Vec& output)
const
00131
{
00132
static Vec x;
00133
x.resize(input.
length());
00134
x << input;
00135
x -=
mu;
00136 output.
resize(
ncomponents);
00137
00138
if(
normalize)
00139 {
00140
for(
int i=0; i<
ncomponents; i++)
00141 output[i] =
dot(
x,
eigenvecs(i)) /
sqrt(
eigenvals[i]);
00142 }
00143
else
00144 {
00145
for(
int i=0; i<
ncomponents; i++)
00146 output[i] =
dot(
x,
eigenvecs(i));
00147 }
00148 }
00149
00151
00153 void PCA::forget()
00154 {
00155 stage = 0;
00156 }
00157
00159
00161 TVec<string> PCA::getTestCostNames()
const
00162
{
00163
return TVec<string>(1,
"squared_reconstruction_error");
00164 }
00165
00167
00169 TVec<string> PCA::getTrainCostNames()
const
00170
{
00171
return TVec<string>();
00172 }
00173
00175
00177 void PCA::makeDeepCopyFromShallowCopy(map<const void*, void*>& copies)
00178 {
00179 inherited::makeDeepCopyFromShallowCopy(copies);
00180
deepCopyField(
mu, copies);
00181
deepCopyField(
eigenvals, copies);
00182
deepCopyField(
eigenvecs, copies);
00183 }
00184
00185
00187
00189 int PCA::outputsize()
const
00190
{
00191
return ncomponents;
00192 }
00193
00195
00197 void PCA::train()
00198 {
00199
Mat covarmat;
00200
if(stage<1)
00201 {
00202
ProgressBar* pb = 0;
00203
if (
algo ==
"classical") {
00204
if (
ncomponents > train_set->inputsize())
00205
PLERROR(
"In PCA::train - You asked for %d components, but the training set inputsize is only %d",
ncomponents, train_set->inputsize());
00206
if (report_progress) {
00207 pb =
new ProgressBar(
"Training PCA", 2);
00208 }
00209
computeInputMeanAndCovar(train_set,
mu, covarmat);
00210
if (pb)
00211 pb->
update(1);
00212
eigenVecOfSymmMat(covarmat,
ncomponents,
eigenvals,
eigenvecs);
00213
if (pb)
00214 pb->
update(2);
00215 stage = 1;
00216 }
else if (
algo ==
"em") {
00217
int n = train_set->
length();
00218
int p = train_set->inputsize();
00219
int k =
ncomponents;
00220
00221
Mat C(
k,p);
00222
fill_random_normal(C);
00223
00224
VMat centered_data =
new CenteredVMatrix(
new GetInputVMatrix(train_set));
00225
Vec sample_mean = static_cast<CenteredVMatrix*>((
VMatrix*) centered_data)->getMu();
00226
mu.
resize(sample_mean.
length());
00227
mu << sample_mean;
00228
Mat Y = centered_data.
toMat();
00229
Mat X(n,
k);
00230
Mat tmp_k_k(
k,
k);
00231
Mat tmp_k_k_2(
k,
k);
00232
Mat tmp_p_k(p,
k);
00233
Mat tmp_k_n(
k,n);
00234
00235
if (report_progress)
00236 pb =
new ProgressBar(
"Training EM PCA", nstages - stage);
00237
int init_stage = stage;
00238
while (stage < nstages) {
00239
00240
productTranspose(tmp_k_k, C, C);
00241
matInvert(tmp_k_k, tmp_k_k_2);
00242
transposeProduct(tmp_p_k, C, tmp_k_k_2);
00243
product(X, Y, tmp_p_k);
00244
00245
transposeProduct(tmp_k_k, X, X);
00246
matInvert(tmp_k_k, tmp_k_k_2);
00247
productTranspose(tmp_k_n, tmp_k_k_2, X);
00248
product(C, tmp_k_n, Y);
00249 stage++;
00250
if (report_progress)
00251 pb->
update(stage - init_stage);
00252 }
00253
00254
int n_base =
GramSchmidtOrthogonalization(C);
00255
if (n_base !=
k) {
00256
PLWARNING(
"In PCA::train - The rows of C are not linearly independent");
00257 }
00258
00259
productTranspose(X, Y, C);
00260
00261
PCA true_pca;
00262
VMat proj_data(X);
00263 true_pca.
ncomponents =
k;
00264 true_pca.
normalize = 0;
00265 true_pca.
setTrainingSet(proj_data);
00266 true_pca.
train();
00267
00268
eigenvecs.
resize(
k, p);
00269
product(
eigenvecs, true_pca.
eigenvecs, C);
00270
eigenvals.
resize(
k);
00271
eigenvals << true_pca.
eigenvals;
00272 }
else if (
algo ==
"em_orth") {
00273
int n = train_set->
length();
00274
int p = train_set->inputsize();
00275
int k =
ncomponents;
00276
00277
Mat C(
k,p);
00278
fill_random_normal(C);
00279
00280
GramSchmidtOrthogonalization(C);
00281
00282
VMat centered_data =
new CenteredVMatrix(
new GetInputVMatrix(train_set));
00283
Vec sample_mean = static_cast<CenteredVMatrix*>((
VMatrix*) centered_data)->getMu();
00284
mu.
resize(sample_mean.
length());
00285
mu << sample_mean;
00286
Mat Y = centered_data.
toMat();
00287
Mat Y_copy(n,p);
00288
Mat X(n,
k);
00289
Mat tmp_k_k(
k,
k);
00290
Mat tmp_k_k_2(
k,
k);
00291
Mat tmp_p_k(p,
k);
00292
Mat tmp_k_n(
k,n);
00293
Mat tmp_n_1(n,1);
00294
Mat tmp_n_p(n,p);
00295
Mat X_j, C_j;
00296
Mat x_j_prime_x_j(1,1);
00297
00298
if (report_progress)
00299 pb =
new ProgressBar(
"Training EM PCA", nstages - stage);
00300
int init_stage = stage;
00301 Y_copy << Y;
00302
while (stage < nstages) {
00303 Y << Y_copy;
00304
for (
int j = 0; j <
k; j++) {
00305 C_j = C.
subMatRows(j, 1);
00306 X_j = X.
subMatColumns(j,1);
00307
00308
productTranspose(X_j, Y, C_j);
00309
00310
transposeProduct(x_j_prime_x_j, X_j, X_j);
00311
transposeProduct(C_j, X_j, Y);
00312 C_j /= x_j_prime_x_j(0,0);
00313
00314
PLearn::normalize(C_j, 2.0);
00315
00316
00317
productTranspose(tmp_n_1, Y, C_j);
00318
negateElements(Y);
00319
productAcc(Y, tmp_n_1, C_j);
00320
negateElements(Y);
00321 }
00322 stage++;
00323
if (report_progress)
00324 pb->
update(stage - init_stage);
00325 }
00326
00327
for (
int i = 0; i <
k; i++) {
00328
for (
int j = i; j <
k; j++) {
00329
real dot_i_j =
dot(C(i), C(j));
00330
if (i != j) {
00331
if (
abs(dot_i_j) > 1e-6) {
00332
PLWARNING(
"In PCA::train - It looks like some vectors are not orthogonal");
00333 }
00334 }
else {
00335
if (
abs(dot_i_j - 1) > 1e-6) {
00336
PLWARNING(
"In PCA::train - It looks like a vector is not normalized");
00337 }
00338 }
00339 }
00340 }
00341
00342 Y << Y_copy;
00343
productTranspose(X, Y, C);
00344
00345
00346
VMat X_vm(X);
00347
Vec mean_proj, var_proj;
00348
computeMeanAndVariance(X_vm, mean_proj, var_proj);
00349
eigenvals.
resize(
k);
00350
eigenvals << var_proj;
00351
00352
eigenvecs.
resize(
k, p);
00353
eigenvecs << C;
00354 }
else {
00355
PLERROR(
"In PCA::train - Unknown value for 'algo'");
00356 }
00357
if (pb)
00358
delete pb;
00359 }
else {
00360
PLWARNING(
"In PCA::train - The learner has already been train, skipping training");
00361 }
00362 }
00363
00364
00366
00368 void PCA::reconstruct(
const Vec& output,
Vec& input)
const
00369
{
00370 input.
resize(
mu.
length());
00371 input <<
mu;
00372
00373
int n = output.
length();
00374
if(
normalize)
00375 {
00376
for(
int i=0; i<n; i++)
00377
multiplyAcc(input,
eigenvecs(i), output[i]*
sqrt(
eigenvals[i]));
00378 }
00379
else
00380 {
00381
for(
int i=0; i<n; i++)
00382
multiplyAcc(input,
eigenvecs(i), output[i]);
00383 }
00384 }
00385
00386 }