aGrUM  0.20.3
a C++ library for (probabilistic) graphical models
paramEstimatorML_tpl.h
Go to the documentation of this file.
1 /**
2  *
3  * Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4  * info_at_agrum_dot_org
5  *
6  * This library is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 
22 /** @file
23  * @brief the class for estimating parameters of CPTs using Maximum Likelihood
24  *
25  * @author Christophe GONZALES(@AMU) and Pierre-Henri WUILLEMIN(@LIP6)
26  */
27 #ifndef DOXYGEN_SHOULD_SKIP_THIS
28 
29 // if this define is active, the estimator will not accept to assess numbers for a
30 // distribution with no case at all (N_ij=0). if this define is not active, the
31 // estimator will use an uniform distribution for this case.
32 //# define GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
33 
34 namespace gum {
35 
36  namespace learning {
37 
38  /// default constructor
39  template < template < typename > class ALLOC >
40  INLINE ParamEstimatorML< ALLOC >::ParamEstimatorML(
42  const Apriori< ALLOC >& external_apriori,
44  const std::vector< std::pair< std::size_t, std::size_t >,
45  ALLOC< std::pair< std::size_t, std::size_t > > >& ranges,
47  const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) :
51  ranges,
53  alloc) {
55  }
56 
57 
58  /// default constructor
59  template < template < typename > class ALLOC >
62  const Apriori< ALLOC >& external_apriori,
65  const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) :
70  alloc) {
72  }
73 
74 
75  /// copy constructor with a given allocator
76  template < template < typename > class ALLOC >
78  const ParamEstimatorML< ALLOC >& from,
79  const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) :
82  }
83 
84 
85  /// copy constructor
86  template < template < typename > class ALLOC >
88  ParamEstimatorML< ALLOC >(from, this->getAllocator()) {}
89 
90 
91  /// move constructor with a given allocator
92  template < template < typename > class ALLOC >
95  const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) :
98  }
99 
100 
101  /// move constructor
102  template < template < typename > class ALLOC >
104  ParamEstimatorML< ALLOC >(std::move(from), this->getAllocator()) {}
105 
106 
107  /// virtual copy constructor with a given allocator
108  template < template < typename > class ALLOC >
110  const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) const {
113  try {
115  } catch (...) {
117  throw;
118  }
119 
120  return new_score;
121  }
122 
123 
124  /// virtual copy constructor
125  template < template < typename > class ALLOC >
127  return clone(this->getAllocator());
128  }
129 
130 
131  /// destructor
132  template < template < typename > class ALLOC >
135  }
136 
137 
138  /// copy operator
139  template < template < typename > class ALLOC >
143  return *this;
144  }
145 
146 
147  /// move operator
148  template < template < typename > class ALLOC >
152  return *this;
153  }
154 
155 
156  /// returns the CPT's parameters corresponding to a given set of nodes
157  template < template < typename > class ALLOC >
158  std::vector< double, ALLOC< double > > ParamEstimatorML< ALLOC >::parameters(
159  const NodeId target_node,
160  const std::vector< NodeId, ALLOC< NodeId > >& conditioning_nodes) {
161  // create an idset that contains all the nodes in the following order:
162  // first, the target node, then all the conditioning nodes
164 
165  // get the counts for all the nodes in the idset and add the external and
166  // score internal aprioris
167  std::vector< double, ALLOC< double > > N_ijk(this->counter_.counts(idset, true));
174 
175 
176  // now, normalize N_ijk
177 
178  // here, we distinguish nodesets with conditioning nodes from those
179  // without conditioning nodes
180  if (!conditioning_nodes.empty()) {
181  // get the counts for all the conditioning nodes, and add them the
182  // external and score internal aprioris
183  std::vector< double, ALLOC< double > > N_ij(
184  this->counter_.counts(idset.conditionalIdCondSet(), false));
189 
192 
193 # ifdef GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
194  // check that all conditioning nodes have strictly positive counts
195  for (std::size_t j = std::size_t(0); j < conditioning_domsize; ++j) {
196  if (N_ij[j] == 0) {
197  // get the domain sizes of the conditioning nodes
200 
201  const auto& node2cols = this->counter_.nodeId2Columns();
202  const auto& database = this->counter_.database();
203  if (node2cols.empty()) {
204  for (std::size_t i = std::size_t(0); i < cond_nb; ++i) {
206  }
207  } else {
208  for (std::size_t i = std::size_t(0); i < cond_nb; ++i) {
210  }
211  }
212 
213  // determine the value of each conditioning variable in N_ij[j]
215  Idx offset = 1;
216  std::size_t i;
217  for (i = std::size_t(0); i < cond_nb; ++i) {
218  offsets[i] = offset;
219  offset *= cond_domsize[i];
220  }
221  std::vector< Idx > values(cond_nb);
222  i = 0;
223  offset = j;
224  for (Idx jj = cond_nb - 1; i < cond_nb; ++i, --jj) {
225  values[jj] = offset / offsets[jj];
226  offset %= offsets[jj];
227  }
228 
229  // create the error message
231  str << "The conditioning set <";
232  bool deja = true;
233  for (i = std::size_t(0); i < cond_nb; ++i) {
234  if (deja)
235  str << ", ";
236  else
237  deja = true;
240  const DiscreteVariable& var
241  = dynamic_cast< const DiscreteVariable& >(database.variable(col));
242  str << var.name() << "=" << var.labels()[values[i]];
243  }
246  str << "> for target node " << var.name()
247  << " never appears in the database. Please consider using "
248  << "priors such as smoothing.";
249 
251  }
252  }
253 # endif // GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
254 
255  // normalize the counts
256  for (std::size_t j = std::size_t(0), k = std::size_t(0); j < conditioning_domsize; ++j) {
257  for (std::size_t i = std::size_t(0); i < target_domsize; ++i, ++k) {
258 # ifdef GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
259  N_ijk[k] /= N_ij[j];
260 # else // GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
261  if (N_ij[j] != 0) {
262  N_ijk[k] /= N_ij[j];
263  } else {
264  N_ijk[k] = 1.0 / target_domsize;
265  }
266 # endif // GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
267  }
268  }
269  } else {
270  // here, there are no conditioning nodes. Hence N_ijk is the marginal
271  // probability distribution over the target node. To normalize it, it
272  // is sufficient to divide each cell by the sum over all the cells
273  double sum = 0;
274  for (const double n_ijk: N_ijk)
275  sum += n_ijk;
276 
277  if (sum != 0) {
278  for (double& n_ijk: N_ijk)
279  n_ijk /= sum;
280  } else {
281 # ifdef GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
283 
284  const auto& node2cols = this->counter_.nodeId2Columns();
285  const auto& database = this->counter_.database();
288  str << "No data for target node " << var.name()
289  << ". It is impossible to estimate the parameters by maximum "
290  "likelihood";
292 # else // GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
293  for (double& n_ijk: N_ijk)
294  n_ijk = 1.0 / N_ijk.size();
295 # endif // GUM_PARAMESTIMATOR_ERROR_WHEN_NIJ_IS_NULL
296  }
297  }
298 
299  return N_ijk;
300  }
301 
302  } /* namespace learning */
303 
304 } /* namespace gum */
305 
306 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:643
Database(const std::string &filename, const BayesNet< GUM_SCALAR > &bn, const std::vector< std::string > &missing_symbols)