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
00039
00040
00043
#include <plearn/math/plapack.h>
00044
#include "PLS.h"
00045
#include <plearn/vmat/ShiftAndRescaleVMatrix.h>
00046
#include <plearn/vmat/SubVMatrix.h>
00047
#include <plearn/math/TMat_maths_impl.h>
00048
#include <plearn/vmat/VMat_maths.h>
00049
00050
namespace PLearn {
00051
using namespace std;
00052
00053 PLS::PLS()
00054 : m(-1),
00055 p(-1),
00056
k(1),
00057 method("kernel"),
00058 precision(1e-6),
00059 output_the_score(0),
00060 output_the_target(1)
00061 {}
00062
00063
PLEARN_IMPLEMENT_OBJECT(
PLS,
00064
"Partial Least Squares Regression (PLSR).",
00065
"You can use this learner to perform regression, and / or dimensionality\n"
00066
"reduction.\n"
00067
"PLS regression assumes the target Y and the data X are linked through:\n"
00068
" Y = T.Q' + E\n"
00069
" X = T.P' + F\n"
00070
"The underlying coefficients T (the 'scores') and the loading matrices\n"
00071
"Q and P are seeked. It is then possible to compute the prediction y for\n"
00072
"a new input x, as well as its score vector t (its representation in\n"
00073
"lower-dimensional coordinates).\n"
00074
"The available algorithms to perform PLS (chosen by the 'method' option) are:\n"
00075
"\n"
00076
" ==== PLS1 ====\n"
00077
"The classical PLS algorithm, suitable only for a 1-dimensional target. The\n"
00078
"following algorithm is taken from 'Factor Analysis in Chemistry', with an\n"
00079
"additional loop that (I believe) was missing:\n"
00080
" (1) Let X (n x p) = the centered and normalized input data\n"
00081
" Let y (n x 1) = the centered and normalized target data\n"
00082
" Let k be the number of components extracted\n"
00083
" (2) s = y\n"
00084
" (3) lx' = s' X, s = X lx (normalized)\n"
00085
" (4) If s has changed by more than 'precision', loop to (3)\n"
00086
" (5) ly = s' y\n"
00087
" (6) lx' = s' X\n"
00088
" (7) Store s, lx and ly in the columns of respectively T, P and Q\n"
00089
" (8) X = X - s lx', y = y - s ly, loop to (2) k times\n"
00090
" (9) Set W = (T P')^(+) T, where the ^(+) is the right pseudoinverse\n"
00091
"\n"
00092
" ==== Kernel ====\n"
00093
"The code implements a NIPALS-PLS-like algorithm, which is a so-called\n"
00094
"'kernel' algorithm (faster than more classical implementations).\n"
00095
"The algorithm, inspired from 'Factor Analysis in Chemistry' and above all\n"
00096
"www.statsoftinc.com/textbook/stpls.html, is the following:\n"
00097
" (1) Let X (n x p) = the centered and normalized input data\n"
00098
" Let Y (n x m) = the centered and normalized target data\n"
00099
" Let k be the number of components extracted\n"
00100
" (2) Initialize A_0 = X'Y, M_0 = X'X, C_0 = Identity(p), and h = 0\n"
00101
" (3) q_h = largest eigenvector of B_h = A_h' A_h, found by the NIPALS method:\n"
00102
" (3.a) q_h = a (normalized) randomn column of B_h\n"
00103
" (3.b) q_h = B_h q_h\n"
00104
" (3.c) normalize q_h\n"
00105
" (3.d) if q_h has changed by more than 'precision', go to (b)\n"
00106
" (4) w_h = C_h A_h q_h, normalize w_h and store it in a column of W (p x k)\n"
00107
" (5) p_h = M_h w_h, c_h = w_h' p_h, p_h = p_h / c_h and store it in a column\n"
00108
" of P (p x k)\n"
00109
" (6) q_h = A_h' w_h / c_h, and store it in a column of Q (m x k)\n"
00110
" (7) A_h+1 = A_h - c_h p_h q_h'\n"
00111
" M_h+1 = M_h - c_h p_h p_h',\n"
00112
" C_h+1 = C_h - w_h p_h\n"
00113
" (8) h = h+1, and if h < k, go to (3)\n"
00114
"\n"
00115
"The result is then given by:\n"
00116
" - Y = X B, with B (p x m) = W Q'\n"
00117
" - T = X W, where T is the score (reduced coordinates)\n"
00118
"\n"
00119
"You can choose to have the score (T) and / or the target (Y) in the output\n"
00120
"of the learner (default is target only, i.e. regression)."
00121 );
00122
00124
00126 void PLS::declareOptions(
OptionList& ol)
00127 {
00128
00129
00130
declareOption(ol,
"k", &PLS::k, OptionBase::buildoption,
00131
"The number of components (factors) computed.");
00132
00133
declareOption(ol,
"method", &PLS::method, OptionBase::buildoption,
00134
"The PLS algorithm used ('pls1' or 'kernel', see help for more details).\n");
00135
00136
declareOption(ol,
"output_the_score", &PLS::output_the_score, OptionBase::buildoption,
00137
"If set to 1, then the score (the low-dimensional representation of the input)\n"
00138
"will be included in the output (before the target).");
00139
00140
declareOption(ol,
"output_the_target", &PLS::output_the_target, OptionBase::buildoption,
00141
"If set to 1, then (the prediction of) the target will be included in the\n"
00142
"output (after the score).");
00143
00144
00145
00146
declareOption(ol,
"B", &PLS::B, OptionBase::learntoption,
00147
"The regression matrix in Y = X.B + E.");
00148
00149
declareOption(ol,
"m", &PLS::m, OptionBase::learntoption,
00150
"Used to store the target size.");
00151
00152
declareOption(ol,
"mean_input", &PLS::mean_input, OptionBase::learntoption,
00153
"The mean of the input data X.");
00154
00155
declareOption(ol,
"mean_target", &PLS::mean_target, OptionBase::learntoption,
00156
"The mean of the target data Y.");
00157
00158
declareOption(ol,
"p", &PLS::p, OptionBase::learntoption,
00159
"Used to store the input size.");
00160
00161
declareOption(ol,
"precision", &PLS::precision, OptionBase::buildoption,
00162
"The precision to which we compute the eigenvectors.");
00163
00164
declareOption(ol,
"stddev_input", &PLS::stddev_input, OptionBase::learntoption,
00165
"The standard deviation of the input data X.");
00166
00167
declareOption(ol,
"stddev_target", &PLS::stddev_target, OptionBase::learntoption,
00168
"The standard deviation of the target data Y.");
00169
00170
declareOption(ol,
"W", &PLS::W, OptionBase::learntoption,
00171
"The regression matrix in T = X.W.");
00172
00173
00174 inherited::declareOptions(ol);
00175 }
00176
00178
00180 void PLS::build()
00181 {
00182 inherited::build();
00183
build_();
00184 }
00185
00187
00189 void PLS::build_()
00190 {
00191
if (train_set) {
00192 this->
m = train_set->targetsize();
00193 this->
p = train_set->inputsize();
00194
mean_input.
resize(
p);
00195
stddev_input.
resize(
p);
00196
mean_target.
resize(
m);
00197
stddev_target.
resize(
m);
00198
if (train_set->weightsize() > 0) {
00199
PLWARNING(
"In PLS::build_ - The train set has weights, but the optimization algorithm won't use them");
00200 }
00201
00202
if (
method ==
"pls1") {
00203
00204
if (
m != 1) {
00205
PLERROR(
"In PLS::build_ - With the 'pls1' method, target should be 1-dimensional");
00206 }
00207 }
else if (
method ==
"kernel") {
00208
00209 }
else {
00210
PLERROR(
"In PLS::build_ - Unknown value for option 'method'");
00211 }
00212 }
00213
if (!
output_the_score && !
output_the_target) {
00214
00215
PLWARNING(
"In PLS::build_ - There will be no output");
00216 }
00217 }
00218
00220
00222 void PLS::computeCostsFromOutputs(
const Vec& input,
const Vec& output,
00223
const Vec& target,
Vec& costs)
const
00224
{
00225
00226 }
00227
00229
00231 void PLS::computeOutput(
const Vec& input,
Vec& output)
const
00232
{
00233
static Vec input_copy;
00234
if (
W.
width()==0)
00235
PLERROR(
"PLS::computeOutput but model was not trained!");
00236
00237
int nout =
outputsize();
00238 output.
resize(nout);
00239
00240 input_copy.
resize(this->p);
00241 input_copy << input;
00242 input_copy -=
mean_input;
00243 input_copy /=
stddev_input;
00244
int target_start = 0;
00245
if (
output_the_score) {
00246
transposeProduct(output.
subVec(0, this->k),
W, input_copy);
00247 target_start = this->
k;
00248 }
00249
if (
output_the_target) {
00250
if (this->
m > 0) {
00251
Vec target = output.
subVec(target_start, this->m);
00252
transposeProduct(target,
B, input_copy);
00253 target *=
stddev_target;
00254 target +=
mean_target;
00255 }
else {
00256
00257
PLWARNING(
"In PLS::computeOutput - You ask to output the target but the target size is <= 0");
00258 }
00259 }
00260 }
00261
00263
00265 void PLS::forget()
00266 {
00267 stage = 0;
00268
00269
B =
Mat();
00270
W =
Mat();
00271 }
00272
00274
00276 TVec<string> PLS::getTestCostNames()
const
00277
{
00278
00279
TVec<string> t;
00280
return t;
00281 }
00282
00284
00286 TVec<string> PLS::getTrainCostNames()
const
00287
{
00288
00289
TVec<string> t;
00290
return t;
00291 }
00292
00294
00296 void PLS::makeDeepCopyFromShallowCopy(map<const void*, void*>& copies)
00297 {
00298 inherited::makeDeepCopyFromShallowCopy(copies);
00299
00300
00301
00302
00303
00304
deepCopyField(
B, copies);
00305
deepCopyField(
mean_input, copies);
00306
deepCopyField(
mean_target, copies);
00307
deepCopyField(
stddev_input, copies);
00308
deepCopyField(
stddev_target, copies);
00309
deepCopyField(
W, copies);
00310 }
00311
00313
00315 void PLS::NIPALSEigenvector(
const Mat& m,
Vec& v,
real precision) {
00316
int n = v.
length();
00317
Vec w(n);
00318 v << m.
column(0);
00319
normalize(v, 2.0);
00320
bool ok =
false;
00321
while (!ok) {
00322 w << v;
00323
product(v, m, w);
00324
normalize(v, 2.0);
00325 ok =
true;
00326
for (
int i = 0; i < n && ok; i++) {
00327
if (fabs(v[i] - w[i]) > precision) {
00328 ok =
false;
00329 }
00330 }
00331 }
00332 }
00333
00335
00337 int PLS::outputsize()
const
00338
{
00339
int os = 0;
00340
if (
output_the_score) {
00341 os += this->
k;
00342 }
00343
if (
output_the_target &&
m >= 0) {
00344
00345
00346 os += this->m;
00347 }
00348
return os;
00349 }
00350
00352
00354 void PLS::train()
00355 {
00356
if (stage == 1) {
00357
00358
if (verbosity >= 1) {
00359 cout <<
"Skipping PLS training" <<
endl;
00360 }
00361
return;
00362 }
00363
if (verbosity >= 1) {
00364 cout <<
"PLS training started" <<
endl;
00365 }
00366
00367
00368
00369
if (verbosity >= 2) {
00370 cout <<
" Normalization of the data" <<
endl;
00371 }
00372
VMat input_part =
new SubVMatrix(
00373 train_set, 0, 0, train_set->
length(), train_set->inputsize());
00374
VMat target_part =
new SubVMatrix(
00375 train_set, 0, train_set->inputsize(), train_set->
length(), train_set->targetsize());
00376
PP<ShiftAndRescaleVMatrix> X_vmat =
new ShiftAndRescaleVMatrix();
00377 X_vmat->vm = input_part;
00378 X_vmat->build();
00379
mean_input << X_vmat->shift;
00380
stddev_input << X_vmat->scale;
00381
negateElements(
mean_input);
00382
invertElements(
stddev_input);
00383
PP<ShiftAndRescaleVMatrix> Y_vmat =
new ShiftAndRescaleVMatrix();
00384 Y_vmat->vm = target_part;
00385 Y_vmat->build();
00386
mean_target << Y_vmat->shift;
00387
stddev_target << Y_vmat->scale;
00388
negateElements(
mean_target);
00389
invertElements(
stddev_target);
00390
00391
00392
W.
resize(
p,
k);
00393
Mat P(
p,
k);
00394
Mat Q(
m,
k);
00395
int n = X_vmat->length();
00396
VMat X_vmatrix = static_cast<ShiftAndRescaleVMatrix*>(X_vmat);
00397
VMat Y_vmatrix = static_cast<ShiftAndRescaleVMatrix*>(Y_vmat);
00398
00399
if (
method ==
"kernel") {
00400
00401
if (verbosity >= 2) {
00402 cout <<
" Initialization of the coefficients" <<
endl;
00403 }
00404
Vec ph(
p);
00405
Vec qh(
m);
00406
Vec wh(
p);
00407
Vec tmp(
p);
00408
real ch;
00409
Mat Ah =
transposeProduct(X_vmatrix, Y_vmatrix);
00410
Mat Mh =
transposeProduct(X_vmatrix, X_vmatrix);
00411
Mat Ch(
p,
p);
00412
Mat Ah_t_Ah;
00413
Mat update_Ah(
p,
m);
00414
Mat update_Mh(
p,
p);
00415
Mat update_Ch(
p,
p);
00416
for (
int i = 0; i <
p; i++) {
00417
for (
int j = i+1; j < p; j++) {
00418 Ch(i,j) = Ch(j,i) = 0;
00419 }
00420 Ch(i,i) = 1;
00421 }
00422
00423
00424
ProgressBar* pb = 0;
00425
if(report_progress) {
00426 pb =
new ProgressBar(
"Computing the components",
k);
00427 }
00428
for (
int h = 0; h < this->
k; h++) {
00429 Ah_t_Ah =
transposeProduct(Ah,Ah);
00430
if (
m == 1) {
00431
00432 qh[0] = 1;
00433 }
else {
00434
NIPALSEigenvector(Ah_t_Ah, qh,
precision);
00435 }
00436
product(tmp, Ah, qh);
00437
product(wh, Ch, tmp);
00438
normalize(wh, 2.0);
00439
W.
column(h) << wh;
00440
product(ph, Mh, wh);
00441 ch =
dot(wh, ph);
00442 ph /= ch;
00443
P.
column(h) << ph;
00444
transposeProduct(qh, Ah, wh);
00445 qh /= ch;
00446 Q.
column(h) << qh;
00447
Mat ph_mat(p, 1, ph);
00448
Mat qh_mat(
m, 1, qh);
00449
Mat wh_mat(p, 1, wh);
00450 update_Ah =
productTranspose(ph_mat, qh_mat);
00451 update_Ah *= ch;
00452 Ah -= update_Ah;
00453 update_Mh =
productTranspose(ph_mat, ph_mat);
00454 update_Mh *= ch;
00455 Mh -= update_Mh;
00456 update_Ch =
productTranspose(wh_mat, ph_mat);
00457 Ch -= update_Ch;
00458
if (pb)
00459 pb->
update(h + 1);
00460 }
00461
if (pb)
00462
delete pb;
00463 }
else if (
method ==
"pls1") {
00464
Vec s(n);
00465
Vec old_s(n);
00466
Vec y(n);
00467
Vec lx(
p);
00468
Vec ly(1);
00469
Mat T(n,
k);
00470
Mat X = X_vmatrix->
toMat();
00471 y << Y_vmatrix->
toMat();
00472
ProgressBar* pb = 0;
00473
if(report_progress) {
00474 pb =
new ProgressBar(
"Computing the components",
k);
00475 }
00476
for (
int h = 0; h <
k; h++) {
00477 s << y;
00478
normalize(s, 2.0);
00479
bool finished =
false;
00480
while (!finished) {
00481 old_s << s;
00482
transposeProduct(lx, X, s);
00483
product(s, X, lx);
00484
normalize(s, 2.0);
00485
if (
dist(old_s, s, 2) <
precision) {
00486 finished =
true;
00487 }
00488 }
00489 ly[0] =
dot(s, y);
00490
transposeProduct(lx, X, s);
00491 T.
column(h) << s;
00492
P.
column(h) << lx;
00493 Q.
column(h) << ly;
00494
00495
00496
for (
int i = 0; i < n; i++) {
00497
for (
int j = 0; j <
p; j++) {
00498 X(i,j) -= s[i] * lx[j];
00499 }
00500 y[i] -= s[i] * ly[0];
00501 }
00502
if (report_progress)
00503 pb->
update(h);
00504 }
00505
if (pb)
00506
delete pb;
00507
if (verbosity >= 2) {
00508 cout <<
" Computation of the corresponding coefficients" <<
endl;
00509 }
00510
Mat tmp(n,
p);
00511
productTranspose(tmp, T,
P);
00512
Mat U, Vt;
00513
Vec D;
00514
real safeguard = 1.1;
00515
SVD(tmp, U, D, Vt,
'A', safeguard);
00516
for (
int i = 0; i < D.
length(); i++) {
00517
if (
abs(D[i]) <
precision) {
00518 D[i] = 0;
00519 }
else {
00520 D[i] = 1.0 / D[i];
00521 }
00522 }
00523
Mat tmp2(n,
p);
00524 tmp2.
fill(0);
00525
for (
int i = 0; i < D.
length(); i++) {
00526
if (D[i] != 0) {
00527 tmp2(i) << D[i] * Vt(i);
00528 }
00529 }
00530
product(tmp, U, tmp2);
00531
transposeProduct(
W, tmp, T);
00532 }
00533
B.
resize(
p,
m);
00534
productTranspose(
B,
W, Q);
00535
if (verbosity >= 1) {
00536 cout <<
"PLS training ended" <<
endl;
00537 }
00538 stage = 1;
00539 }
00540
00541 }