CEBL  2.1
QDA.cpp
Go to the documentation of this file.
1 #include "QDA.hpp"
2 
3 using namespace cppR;
4 using namespace ublas;
5 using namespace std;
6 
7 namespace CEBL {
8 
10  void QDA::train(const EEGTrainingData& data) {
11 
12  bool debug = false;
13 
14  //X is nsamples by nfeatures
15  ublas::matrix<double> X = t(data.collapse());
16  if(debug)
17  cout << "Starting QDA Training\n"
18  << "X = matrix[" << X.size1() << "," << X.size2() << "]\n";
19 
20  ublas::vector<int> Y = data.getTargets();
21 
22  classes = unique(Y);
23  int nClasses = using_classes;
24  int classesSize = classes.size();
25 
26  if(using_classes<classesSize){
27  cout << "More than the requested number of classes was collected. Only the first " << nClasses << " will be trained."<<endl;
28  }
29  else if(using_classes > classesSize){
30  cerr << "Data was only collected for " << classes.size() << " classes. Cannot train " << using_classes << " classes." <<endl;
31  trained = false;
32  return;
33  }
34 
35  int nSamples = nrow(X);
36  int nFeatures = ncol(X);
37  covariances.resize(nClasses);
38  covariancesInv.resize(nClasses);
39  for (int k = 0; k < nClasses; k++) {
40  //allow for inturruption here
41  this->inturruptionPoint();
42 
43  covariances[k] = createMatrix(0,nFeatures,nFeatures);
44  covariancesInv[k] = createMatrix(0,nFeatures,nFeatures);
45  }
46  covariancesDet = createVector(0,nClasses);
47 
48  priors = rep(0, nClasses);
49 
50  means = createMatrix(0, nClasses, nFeatures);
51  if(debug)
52  cout << "Classes: " << classes << endl << flush;
53 
54  for(int k=0; k< nClasses; k++) {
55  //allow for inturruption here
56  this->inturruptionPoint();
57 
58  // Select samples (X rows) corresponding to targets
59  // (Y) of particular class
60  std::vector<bool> mask = createMask(classes[k], Y);
61  ublas::matrix<double> Z(count(k,classes), nFeatures);
62  Z = rowMask(X, mask);
63  // Does this work instead? ublas::matrix<double> Z = rowMask(X, mask);
64 
65  int nSamplesThisClass = nrow(Z);
66 
67  // Priors are proportion of sample from each class
68  priors[k] = double(nSamplesThisClass) / nSamples;
69 
70  // Class means
71  row(means,k) = colMeans(Z);
72 
73  //R Code: x.c[mask,] <- x[mask,] - matrix( clsf$means[k,], N.k, p, byrow=TRUE);
74  matrix<double> temp = createMatrix(ublas::vector<double>(row(means,k)),
75  nSamplesThisClass, nFeatures, true);
76  matrix<double> Zc = Z - temp;
77 
78  //allow for inturruption here
79  this->inturruptionPoint();
80 
81  covariances[k] = prod(t(Zc), Zc) / nSamplesThisClass;
82  covariancesInv[k] = solve(covariances[k]);
83  covariancesDet[k] = det(covariances[k]);
84 
85  //CHECK RANK
86  // if(rank(c) < nrow(c)) {
87  // cout << "Rank is too small. Inflating covariance matrix\n";
88  // c = c * .9 + diag(.1, nrow(c));
89  // }
90 
91  }
92 
93  //trained_lags = nFeatures/19-1; //the 19 here is from having 19 channels activated
94  // when this was written
95  //change when/if channels are recorded in classifier
96  trained_classes = nClasses;
97  trained = true;
98  }
99 
101  ublas::vector<int> QDA::use(const ublas::matrix<double> & data)
102  {
103  if(data.size1() == 0)
104  {
105  cerr << "QDA use error: 0 size data matrix given.\n";
106  ublas::vector<int> ret;
107  return ret;
108  }
109 
110  ublas::matrix<double> X = data;
111  X = t(X);
112 
113  //ublas::vector<int> Y = data.getMatrixClassVector();
114  int nClasses = using_classes;
115  int nSamples = nrow(X);
116  int nFeatures = ncol(X);
117 
118  ublas::matrix<double> disc_functions(nSamples,nClasses);
119 
120  //create discriminate function for each class
121  for(int k=0; k< nClasses; k++)
122  {
123  matrix<double> temp = createMatrix(ublas::vector<double>(row(means,k)),
124  nSamples, nFeatures, true);
125  ublas::matrix<double> Xc = X - temp; //Xc is X - mu_k
126  double scalarpart = -0.5 * log(covariancesDet[k]) + log(priors[k]);
127  ublas::matrix<double> a = prod( Xc,covariancesInv[k]);
128  ublas::matrix<double> sa = compProd(a,Xc);
129  ublas::vector<double> vectorpart = -0.5 * rowSums(sa);
130  for(unsigned int vi=0; vi<vectorpart.size(); vi++)
131  disc_functions(vi,k) = vectorpart[vi] + scalarpart;
132 
133  }
134  //for each column of deltas find max
135  ublas::vector<int> predicted_classes;
136  predicted_classes.resize(nSamples);
137 
138  for(int j=0; j<nrow(disc_functions); j++)
139  {
140  ublas::vector<double> disc_functionsRow = row(disc_functions,j);
141  predicted_classes[j] = classes[whichMax(disc_functionsRow)];
142  }
143 
144  //----------------------------------------------------------------------
145  //compute probabilities for each class
146  if(compute_probs)
147  {
148  // get vector of maximum for each row in disc functions
149  ublas::vector<double> max_disc =
150  cppR::rowApply<double>(disc_functions,&cppR::max<double>);
151 
152  // create a matrix of the same size as disc functions whose
153  // columns are max_disc
154  matrix<double> max_discs =
155  createMatrix(max_disc,disc_functions.size1(),disc_functions.size2());
156 
157  //take the exponent of the disc functions subtracted from their max
158  matrix<double> probabilities =
159  apply(matrix<double>(disc_functions - max_discs), exp);
160 
161  //get rowsums from probabilities
162  ublas::vector<double> sum_p = cppR::rowSums(probabilities);
163 
164  //divide probabilities by rowsums
165  matrix<double> sum_p_rep =
166  createMatrix(sum_p,probabilities.size1(),probabilities.size2());
167  probabilities = compDiv(probabilities, sum_p_rep);
168 
169  //copy to probabilities member variable
170  this->probabilities.resize(probabilities.size1());
171  for(unsigned i=0;i<probabilities.size1();i++)
172  this->probabilities[i] =
173  asStdVector(ublas::vector<double>(row(probabilities,i)));
174 
175  }
176 
177  //----------------------------------------------------------------------
178  //return predicted classes
179  return predicted_classes;
180  }
181 
182 
183 
185  map<string, SerializedObject> QDA::save() const
186  {
187  map<string, SerializedObject> ret;
188  ret["trained"] = serialize(trained);
189  ret["priors"] = serialize(priors);
190  ret["means"] = serialize(means);
191  ret["covariances"] = serialize(covariances);
192  ret["covariancesInv"] = serialize(covariancesInv);
193  ret["covariancesDet"] = serialize(covariancesDet);
194  ret["classes"] = serialize(classes);
195  ret["trained_classes"] = serialize(trained_classes);
196  ret["using_classes"] = serialize(using_classes);
197  ret["using_lags"] = serialize(using_lags);
198  ret["trained_lags"] = serialize(trained_lags);
199  return ret;
200  }
201 
203  void QDA::load(map<string, SerializedObject> objects)
204  {
205  deserialize(objects["trained"],trained);
206  deserialize(objects["priors"],priors);
207  deserialize(objects["means"],means);
208  deserialize(objects["covariances"],covariances);
209  deserialize(objects["covariancesInv"],covariancesInv);
210  deserialize(objects["covariancesDet"],covariancesDet);
211  deserialize(objects["classes"],classes);
212  deserialize(objects["trained_classes"],trained_classes);
213  deserialize(objects["using_classes"],using_classes);
214  deserialize(objects["using_lags"],using_lags);
215  deserialize(objects["trained_lags"],trained_lags);
216  }
217 
218 
219 
221  std::map<std::string, CEBL::Param> QDA::getParamsList()
222  {
223  std::map<std::string, CEBL::Param> params;
224  CEBL::Param probs("Compute Probabilities",
225  "Should QDA compute probabilities when you use the classifier?",
226  compute_probs);
227  params["probs"] = probs;
228 
229  return params;
230  }
231 
233  void QDA::setParamsList( std::map<std::string, CEBL::Param> &p)
234  {
235  compute_probs = p["probs"].getBool();
236  }
237 }
238 
239 
240 
241 /*************************************************************/
242 //DYNAMIC LOADING
243 
245 {
246  return new CEBL::QDA;
247 }
248 
249 extern "C" void ObjectDestroy(CEBL::Classifier* p)
250 {
251  delete p;
252 }