Direct Graphical Models  v.1.7.0
TrainNodeGMM.cpp
1 #include "TrainNodeGMM.h"
2 #include "macroses.h"
3 
4 namespace DirectGraphicalModels
5 {
6  // Constants
7  const size_t CTrainNodeGMM::MIN_SAMPLES = 16;
8  const long double CTrainNodeGMM::MAX_COEFFICIENT = 1.0;
9 
10  // Constructor
11  CTrainNodeGMM::CTrainNodeGMM(byte nStates, word nFeatures, TrainNodeGMMParams params)
12  : CBaseRandomModel(nStates)
13  , CTrainNode(nStates, nFeatures)
14  , m_params(params)
15  {
16  m_vGaussianMixtures.resize(nStates);
17  for (auto &gaussianMixture : m_vGaussianMixtures)
18  gaussianMixture.reserve(m_params.maxGausses);
20  }
21 
22  // Constructor
23  CTrainNodeGMM::CTrainNodeGMM(byte nStates, word nFeatures, byte maxGausses)
24  : CBaseRandomModel(nStates)
25  , CTrainNode(nStates, nFeatures)
27  {
28  m_params.maxGausses = maxGausses;
29  m_vGaussianMixtures.resize(nStates);
30  for (auto &gaussianMixture : m_vGaussianMixtures)
31  gaussianMixture.reserve(m_params.maxGausses);
32  }
33 
34  // Destructor
36  { }
37 
39  {
40  m_vGaussianMixtures.clear();
41  m_minAlpha = 1;
42  }
43 
44  namespace {
45  // Calculates distance from all Gaussians in a mixture to the point <x>
46  // If when using Mahalanobis distance, a Gaussian is not full, the scaled Euclidian for this Gaussian is returned
47  inline std::vector<double> getDistance(const Mat &x, const GaussianMixture &gaussianMixture, size_t samplesTreshold, double dist_Etreshold, double dist_Mtreshold)
48  {
49  std::vector<double> res(gaussianMixture.size());
50  for (size_t i = 0; i < res.size(); i++)
51  if (dist_Mtreshold) // Euclidian distance
52  res[i] = gaussianMixture[i].getEuclidianDistance(x);
53  else // Mahalanobis distance
54  res[i] = gaussianMixture[i].getNumPoints() >= samplesTreshold ? gaussianMixture[i].getMahalanobisDistance(x)
55  : gaussianMixture[i].getEuclidianDistance(x) * dist_Mtreshold / dist_Etreshold;
56  return res;
57  }
58 
59  // Calculates divergence from Gaussian <x> to all other Gaussians in the mixture (including itself)
60  // If a Gaussian from mixture is not full, the returned divergence is infinity
61  // The caller should care about argument <x>
62  inline std::vector<double> getDivergence(const CKDGauss &x, const GaussianMixture &gaussianMixture, size_t samplesTreshold)
63  {
64  std::vector<double> res(gaussianMixture.size());
65  for (size_t i = 0; i < res.size(); i++)
66  res[i] = gaussianMixture[i].getNumPoints() >= samplesTreshold ? x.getKullbackLeiberDivergence(gaussianMixture[i]) : DBL_MAX;
67  return res;
68  }
69  }
70 
71  void CTrainNodeGMM::addFeatureVec(const Mat &featureVector, byte gt) {
72  // Assertions
73  DGM_ASSERT_MSG(gt < m_nStates, "The groundtruth value %u is out of range [0; %u)", gt, m_nStates);
74 
75  Mat point;
76  featureVector.convertTo(point, CV_64FC1);
77 
78  GaussianMixture &gaussianMixture = m_vGaussianMixtures[gt]; // GMM of current state
79 
80  if (gaussianMixture.empty())
81  gaussianMixture.emplace_back(point); // NEW GAUSS
82  else {
83  std::vector<double> dist = getDistance(point, gaussianMixture, m_params.minSamples, m_params.dist_Etreshold, m_params.dist_Mtreshold); // Calculate distances all existing Gaussians in the mixture to the point
84 
85  // Find the smallest distance
86  auto it = std::min_element(dist.begin(), dist.end());
87  double minDist = *it;
88 
89  double dist_treshold = (m_params.dist_Mtreshold < 0) ? m_params.dist_Etreshold : m_params.dist_Mtreshold;
90 
91  // Add to existing Gaussian or crete a new one
92  if ((minDist > dist_treshold) && (gaussianMixture.size() < m_params.maxGausses))
93  gaussianMixture.emplace_back(point); // NEW GAUSS
94  else {
95  size_t updIdx = std::distance(dist.begin(), it);
96  CKDGauss &updGauss = gaussianMixture[updIdx]; // the nearest Gaussian
97  updGauss += point; // update the nearest Gauss
98 
99  // Chech the updated Gauss function if after update it became too close to another Gauss function
100  if ((m_params.div_KLtreshold > 0) && (updGauss.getNumPoints() >= m_params.minSamples)) {
101  // Calculate divergences between updGauss and all other gausses
102  std::vector<double> div = getDivergence(updGauss, gaussianMixture, m_params.minSamples);
103  div[updIdx] = DBL_MAX; // divergence to itself
104 
105  // Find the smallest divergence
106  auto it = std::min_element(div.begin(), div.end());
107 
108  // Merge together if they are too close
109  if ((it != div.end()) && (*it < m_params.div_KLtreshold)) {
110  size_t idx = std::distance(div.begin(), it);
111  gaussianMixture[idx] += updGauss;
112  gaussianMixture.erase(gaussianMixture.begin() + updIdx);
113  }
114  }
115  }
116  }
117  }
118 
119  namespace {
120  template<typename T>
121  void printMat(const std::string &name, const Mat &m) {
122  if (!name.empty()) printf("%s:\n", name.c_str());
123  for (int y = 0; y < m.rows; y++) {
124  for (int x = 0; x < m.cols; x++)
125  printf("%.1f\t", m.at<T>(y, x));
126  printf("\n");
127  }
128  }
129 
130  void printStatus(std::vector<GaussianMixture> &vGaussianMixtures, long double minCoefficient) {
131 #ifdef DEBUG_PRINT_INFO
132  printf("\nCTrainNodeGMM::Status\n");
133  printf("---------------------------\n");
134  printf("( minCoefficient = %Le )\n", minCoefficient);
135 
136  for (size_t s = 0; s < vGaussianMixtures.size(); s++) { // states
137  printf("Class %zu (%zu gausses):\n", s, vGaussianMixtures[s].size());
138 
139  word g = 0;
140  for (const CKDGauss &gauss : vGaussianMixtures[s]) {
141  printf("\tG[%u]: %zupts; ", g++, gauss.getNumPoints());
142  printf("alpha: %Le;\n", gauss.getAlpha());
143  //printf("aK: %e;\n", gauss.getAlpha() / m_minCoefficient);
144 
145  //printMat<double>("MU:", gauss.getMu());
146  //printMat<double>("SIGMA:", gauss.getSigma());
147  } // gausses
148  printf("\n");
149  } // s
150 #endif
151  }
152  }
153 
155  {
156  // merge gausses with too small number of samples
157  for (GaussianMixture &gaussianMixture : m_vGaussianMixtures) { // state
158  for (auto it = gaussianMixture.begin(); it != gaussianMixture.end(); it++) {
159  size_t nPoints = it->getNumPoints();
160  if (nPoints < m_params.minSamples) { // if Gaussian is not full
161  if (nPoints >= MIN_SAMPLES) {
162  size_t g = std::distance(gaussianMixture.begin(), it);
163  std::vector<double> div = getDivergence(*it, gaussianMixture, m_params.minSamples);
164  div[g] = DBL_MAX; // distance to itself (redundant here)
165 
166  // Finding the smallest divergence
167  auto itm = std::min_element(div.begin(), div.end());
168  if (itm != div.end()) {
169  size_t gaussIdx = std::distance(div.begin(), itm);
170  gaussianMixture[gaussIdx] += (*it);
171  }
172  } // if Gaussian has less then MIN_SAMPLES points, consider it as a noise and delete
173  gaussianMixture.erase(it);
174  it--;
175  } // if Gaussian full
176  } // gausses
177  } // gaussianMixture
178 
179  // getting the coefficients
180  for (GaussianMixture &gaussianMixture : m_vGaussianMixtures) { // state
181  for (auto itGauss = gaussianMixture.begin(); itGauss != gaussianMixture.end(); itGauss++) {
182  long double alpha = itGauss->getAlpha();
183  if (alpha > MAX_COEFFICIENT) { // i.e. if (Coefficient = \infinitiy) delete Gaussian
184  gaussianMixture.erase(itGauss);
185  itGauss--;
186  continue;
187  }
188  if (m_minAlpha > alpha)
189  m_minAlpha = alpha;
190  } // gausses
191  } // gaussianMixture
192 
193  printStatus(m_vGaussianMixtures, m_minAlpha);
194  }
195 
196  void CTrainNodeGMM::saveFile(FILE *pFile) const
197  {
198  // m_params
199  fwrite(&m_params.maxGausses, sizeof(word), 1, pFile);
200  fwrite(&m_params.minSamples, sizeof(size_t), 1, pFile);
201  fwrite(&m_params.dist_Etreshold, sizeof(double), 1, pFile);
202  fwrite(&m_params.dist_Mtreshold, sizeof(double), 1, pFile);
203  fwrite(&m_params.div_KLtreshold, sizeof(double), 1, pFile);
204 
205  // m_pvpGausses;
206  for (const GaussianMixture &gaussianMixture : m_vGaussianMixtures) { // state
207  word nGausses = static_cast<word>(gaussianMixture.size());
208  fwrite(&nGausses, sizeof(word), 1, pFile);
209  for (const CKDGauss &gauss : gaussianMixture) {
210  size_t nPoints = gauss.getNumPoints();
211  Mat mu = gauss.getMu();
212  Mat sigma = gauss.getSigma();
213 
214  fwrite(&nPoints, sizeof(long), 1, pFile);
215  for (word y = 0; y < getNumFeatures(); y++)
216  fwrite(&mu.at<double>(y, 0), sizeof(double), 1, pFile);
217  for (word y = 0; y < getNumFeatures(); y++)
218  for (word x = 0; x < getNumFeatures(); x++)
219  fwrite(&sigma.at<double>(y, x), sizeof(double), 1, pFile);
220  mu.release();
221  sigma.release();
222  } // gauss
223  } // gaussianMixture
224 
225  fwrite(&m_minAlpha, sizeof(long double), 1, pFile);
226  }
227 
228  void CTrainNodeGMM::loadFile(FILE *pFile)
229  {
230  // m_params
231  fread(&m_params.maxGausses, sizeof(word), 1, pFile);
232  fread(&m_params.minSamples, sizeof(size_t), 1, pFile);
233  fread(&m_params.dist_Etreshold, sizeof(double), 1, pFile);
234  fread(&m_params.dist_Mtreshold, sizeof(double), 1, pFile);
235  fread(&m_params.div_KLtreshold, sizeof(double), 1, pFile);
236 
237  // m_pvpGausses;
239  for (GaussianMixture &gaussianMixture : m_vGaussianMixtures) { // state
240  word nGausses;
241  fread(&nGausses, sizeof(word), 1, pFile);
242  gaussianMixture.assign(nGausses, CKDGauss(getNumFeatures()));
243  for (CKDGauss &gauss : gaussianMixture) {
244  long nPoints;
245  Mat mu(getNumFeatures(), 1, CV_64FC1);
246  Mat sigma(getNumFeatures(), getNumFeatures(), CV_64FC1);
247 
248  fread(&nPoints, sizeof(long), 1, pFile);
249  for (word y = 0; y < getNumFeatures(); y++)
250  fread(&mu.at<double>(y, 0), sizeof(double), 1, pFile);
251  for (word y = 0; y < getNumFeatures(); y++)
252  for (word x = 0; x < getNumFeatures(); x++)
253  fread(&sigma.at<double>(y, x), sizeof(double), 1, pFile);
254 
255  gauss.setMu(mu);
256  gauss.setSigma(sigma);
257  gauss.setNumPoints(nPoints);
258 
259  mu.release();
260  sigma.release();
261  } // gausses
262  } // gaussianMixture
263 
264  fread(&m_minAlpha, sizeof(long double), 1, pFile);
265  }
266 
267  void CTrainNodeGMM::calculateNodePotentials(const Mat &featureVector, Mat &potential, Mat &mask) const
268  {
269  Mat fv;
270  Mat aux1, aux2, aux3;
271 
272  featureVector.convertTo(fv, CV_64FC1);
273 
274  for (byte s = 0; s < m_nStates; s++) { // state
275  const GaussianMixture &gaussianMixture = m_vGaussianMixtures[s];
276 
277  if (gaussianMixture.empty()) mask.at<byte>(s, 0) = 0;
278  else {
279  size_t nAllPoints = 0; // number of points were used for approximating the density for current state
280  for (const CKDGauss &gauss : gaussianMixture)
281  nAllPoints += gauss.getNumPoints();
282 
283  for (const CKDGauss &gauss : gaussianMixture) {
284  double k = static_cast<double>(gauss.getNumPoints()) / nAllPoints;
285  double value = gauss.getValue(fv, aux1, aux2, aux3);
286  long double aK = gauss.getAlpha() / m_minAlpha; // scaled Gaussian coefficient
287  potential.at<float>(s, 0) += static_cast<float>(k * aK * value);
288  } // gausses
289  }
290  } // s
291  }
292 }
void saveFile(FILE *pFile) const
Saves the random model into the file.
double dist_Etreshold
Minimum Euclidean distance between Gauss functions.
Definition: TrainNodeGMM.h:15
double div_KLtreshold
Minimum Kullback-Leiber divergence between Gauss functions. If this parameter is negative, the merging of Gaussians in addFeatureVec() function will be disabled.
Definition: TrainNodeGMM.h:17
word getNumFeatures(void) const
Returns number of features.
Definition: ITrain.h:37
void reset(void)
Resets class variables.
void train(bool doClean=false)
Random model training.
Gaussian Mixture Model parameters.
Definition: TrainNodeGMM.h:12
CTrainNodeGMM(byte nStates, word nFeatures, TrainNodeGMMParams params=TRAIN_NODE_GMM_PARAMS_DEFAULT)
Constructor.
static const long double MAX_COEFFICIENT
Definition: TrainNodeGMM.h:82
Base abstract class for random model training.
word maxGausses
The maximal number of Gauss functions for approximation.
Definition: TrainNodeGMM.h:13
void calculateNodePotentials(const Mat &featureVector, Mat &potential, Mat &mask) const
Calculates the node potential, based on the feature vector.
std::vector< CKDGauss > GaussianMixture
Definition: KDGauss.h:230
const TrainNodeGMMParams TRAIN_NODE_GMM_PARAMS_DEFAULT
Definition: TrainNodeGMM.h:23
Base abstract class for node potentials training.
Definition: TrainNode.h:47
double dist_Mtreshold
Minimum Mahalanobis distance between Gauss functions. If this parameter is negative, the Euclidean distance is used.
Definition: TrainNodeGMM.h:16
std::vector< GaussianMixture > m_vGaussianMixtures
Definition: TrainNodeGMM.h:87
Multivariate Gaussian distribution class.
Definition: KDGauss.h:28
size_t minSamples
Minimum number of sapmles to approximate a Gauss function.
Definition: TrainNodeGMM.h:14
void addFeatureVec(const Mat &featureVector, byte gt)
Adds new feature vector.
byte m_nStates
The number of states (classes)
void loadFile(FILE *pFile)
Loads the random model from the file.