1 /** 2 * 3 * Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN(_at_LIP6) & Christophe GONZALES(_at_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(_at_AMU) and Pierre-Henri WUILLEMIN(_at_LIP6) 26 */ 27 #ifndef DOXYGEN_SHOULD_SKIP_THIS 28 29 namespace gum { 30 31 namespace learning { 32 33 /// default constructor 34 template < template < typename > class ALLOC > ParamEstimatorML(const DBRowGeneratorParser<ALLOC> & parser,const Apriori<ALLOC> & external_apriori,const Apriori<ALLOC> & score_internal_apriori,const std::vector<std::pair<std::size_t,std::size_t>,ALLOC<std::pair<std::size_t,std::size_t>>> & ranges,const Bijection<NodeId,std::size_t,ALLOC<std::size_t>> & nodeId2columns,const typename ParamEstimatorML<ALLOC>::allocator_type & alloc)35 INLINE ParamEstimatorML< ALLOC >::ParamEstimatorML( 36 const DBRowGeneratorParser< ALLOC >& parser, 37 const Apriori< ALLOC >& external_apriori, 38 const Apriori< ALLOC >& score_internal_apriori, 39 const std::vector< std::pair< std::size_t, std::size_t >, 40 ALLOC< std::pair< std::size_t, std::size_t > > >& ranges, 41 const Bijection< NodeId, std::size_t, ALLOC< std::size_t > >& nodeId2columns, 42 const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) : 43 ParamEstimator< ALLOC >(parser, 44 external_apriori, 45 score_internal_apriori, 46 ranges, 47 nodeId2columns, 48 alloc) { 49 GUM_CONSTRUCTOR(ParamEstimatorML); 50 } 51 52 53 /// default constructor 54 template < template < typename > class ALLOC > ParamEstimatorML(const DBRowGeneratorParser<ALLOC> & parser,const Apriori<ALLOC> & external_apriori,const Apriori<ALLOC> & score_internal_apriori,const Bijection<NodeId,std::size_t,ALLOC<std::size_t>> & nodeId2columns,const typename ParamEstimatorML<ALLOC>::allocator_type & alloc)55 INLINE ParamEstimatorML< ALLOC >::ParamEstimatorML( 56 const DBRowGeneratorParser< ALLOC >& parser, 57 const Apriori< ALLOC >& external_apriori, 58 const Apriori< ALLOC >& score_internal_apriori, 59 const Bijection< NodeId, std::size_t, ALLOC< std::size_t > >& nodeId2columns, 60 const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) : 61 ParamEstimator< ALLOC >(parser, 62 external_apriori, 63 score_internal_apriori, 64 nodeId2columns, 65 alloc) { 66 GUM_CONSTRUCTOR(ParamEstimatorML); 67 } 68 69 70 /// copy constructor with a given allocator 71 template < template < typename > class ALLOC > ParamEstimatorML(const ParamEstimatorML<ALLOC> & from,const typename ParamEstimatorML<ALLOC>::allocator_type & alloc)72 INLINE ParamEstimatorML< ALLOC >::ParamEstimatorML( 73 const ParamEstimatorML< ALLOC >& from, 74 const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) : 75 ParamEstimator< ALLOC >(from, alloc) { 76 GUM_CONS_CPY(ParamEstimatorML); 77 } 78 79 80 /// copy constructor 81 template < template < typename > class ALLOC > ParamEstimatorML(const ParamEstimatorML<ALLOC> & from)82 INLINE ParamEstimatorML< ALLOC >::ParamEstimatorML(const ParamEstimatorML< ALLOC >& from) : 83 ParamEstimatorML< ALLOC >(from, this->getAllocator()) {} 84 85 86 /// move constructor with a given allocator 87 template < template < typename > class ALLOC > ParamEstimatorML(ParamEstimatorML<ALLOC> && from,const typename ParamEstimatorML<ALLOC>::allocator_type & alloc)88 INLINE ParamEstimatorML< ALLOC >::ParamEstimatorML( 89 ParamEstimatorML< ALLOC >&& from, 90 const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) : 91 ParamEstimator< ALLOC >(std::move(from), alloc) { 92 GUM_CONS_MOV(ParamEstimatorML); 93 } 94 95 96 /// move constructor 97 template < template < typename > class ALLOC > ParamEstimatorML(ParamEstimatorML<ALLOC> && from)98 INLINE ParamEstimatorML< ALLOC >::ParamEstimatorML(ParamEstimatorML< ALLOC >&& from) : 99 ParamEstimatorML< ALLOC >(std::move(from), this->getAllocator()) {} 100 101 102 /// virtual copy constructor with a given allocator 103 template < template < typename > class ALLOC > clone(const typename ParamEstimatorML<ALLOC>::allocator_type & alloc)104 ParamEstimatorML< ALLOC >* ParamEstimatorML< ALLOC >::clone( 105 const typename ParamEstimatorML< ALLOC >::allocator_type& alloc) const { 106 ALLOC< ParamEstimatorML< ALLOC > > allocator(alloc); 107 ParamEstimatorML< ALLOC >* new_score = allocator.allocate(1); 108 try { 109 allocator.construct(new_score, *this, alloc); 110 } catch (...) { 111 allocator.deallocate(new_score, 1); 112 throw; 113 } 114 115 return new_score; 116 } 117 118 119 /// virtual copy constructor 120 template < template < typename > class ALLOC > clone()121 ParamEstimatorML< ALLOC >* ParamEstimatorML< ALLOC >::clone() const { 122 return clone(this->getAllocator()); 123 } 124 125 126 /// destructor 127 template < template < typename > class ALLOC > ~ParamEstimatorML()128 ParamEstimatorML< ALLOC >::~ParamEstimatorML() { 129 GUM_DESTRUCTOR(ParamEstimatorML); 130 } 131 132 133 /// copy operator 134 template < template < typename > class ALLOC > 135 ParamEstimatorML< ALLOC >& 136 ParamEstimatorML< ALLOC >::operator=(const ParamEstimatorML< ALLOC >& from) { 137 ParamEstimator< ALLOC >::operator=(from); 138 return *this; 139 } 140 141 142 /// move operator 143 template < template < typename > class ALLOC > 144 ParamEstimatorML< ALLOC >& 145 ParamEstimatorML< ALLOC >::operator=(ParamEstimatorML< ALLOC >&& from) { 146 ParamEstimator< ALLOC >::operator=(std::move(from)); 147 return *this; 148 } 149 150 151 /// returns the CPT's parameters corresponding to a given set of nodes 152 template < template < typename > class ALLOC > parameters(const NodeId target_node,const std::vector<NodeId,ALLOC<NodeId>> & conditioning_nodes)153 std::vector< double, ALLOC< double > > ParamEstimatorML< ALLOC >::parameters( 154 const NodeId target_node, 155 const std::vector< NodeId, ALLOC< NodeId > >& conditioning_nodes) { 156 // create an idset that contains all the nodes in the following order: 157 // first, the target node, then all the conditioning nodes 158 IdCondSet< ALLOC > idset(target_node, conditioning_nodes, true); 159 160 // get the counts for all the nodes in the idset and add the external and 161 // score internal aprioris 162 std::vector< double, ALLOC< double > > N_ijk(this->counter_.counts(idset, true)); 163 const bool informative_external_apriori = this->external_apriori_->isInformative(); 164 const bool informative_score_internal_apriori 165 = this->score_internal_apriori_->isInformative(); 166 if (informative_external_apriori) this->external_apriori_->addAllApriori(idset, N_ijk); 167 if (informative_score_internal_apriori) 168 this->score_internal_apriori_->addAllApriori(idset, N_ijk); 169 170 171 // now, normalize N_ijk 172 173 // here, we distinguish nodesets with conditioning nodes from those 174 // without conditioning nodes 175 if (!conditioning_nodes.empty()) { 176 // get the counts for all the conditioning nodes, and add them the 177 // external and score internal aprioris 178 std::vector< double, ALLOC< double > > N_ij( 179 this->counter_.counts(idset.conditionalIdCondSet(), false)); 180 if (informative_external_apriori) 181 this->external_apriori_->addConditioningApriori(idset, N_ij); 182 if (informative_score_internal_apriori) 183 this->score_internal_apriori_->addConditioningApriori(idset, N_ij); 184 185 const std::size_t conditioning_domsize = N_ij.size(); 186 const std::size_t target_domsize = N_ijk.size() / conditioning_domsize; 187 188 // check that all conditioning nodes have strictly positive counts 189 for (std::size_t j = std::size_t(0); j < conditioning_domsize; ++j) { 190 if (N_ij[j] == 0) { 191 // get the domain sizes of the conditioning nodes 192 const std::size_t cond_nb = conditioning_nodes.size(); 193 std::vector< Idx > cond_domsize(cond_nb); 194 195 const auto& node2cols = this->counter_.nodeId2Columns(); 196 const auto& database = this->counter_.database(); 197 if (node2cols.empty()) { 198 for (std::size_t i = std::size_t(0); i < cond_nb; ++i) { 199 cond_domsize[i] = database.domainSize(conditioning_nodes[i]); 200 } 201 } else { 202 for (std::size_t i = std::size_t(0); i < cond_nb; ++i) { 203 cond_domsize[i] = database.domainSize(node2cols.second(conditioning_nodes[i])); 204 } 205 } 206 207 // determine the value of each conditioning variable in N_ij[j] 208 std::vector< Idx > offsets(cond_nb); 209 Idx offset = 1; 210 std::size_t i; 211 for (i = std::size_t(0); i < cond_nb; ++i) { 212 offsets[i] = offset; 213 offset *= cond_domsize[i]; 214 } 215 std::vector< Idx > values(cond_nb); 216 i = 0; 217 offset = j; 218 for (Idx jj = cond_nb - 1; i < cond_nb; ++i, --jj) { 219 values[jj] = offset / offsets[jj]; 220 offset %= offsets[jj]; 221 } 222 223 // create the error message 224 std::stringstream str; 225 str << "The conditioning set <"; 226 bool deja = false; 227 for (i = std::size_t(0); i < cond_nb; ++i) { 228 if (deja) 229 str << ", "; 230 else 231 deja = true; 232 std::size_t col = node2cols.empty() ? conditioning_nodes[i] 233 : node2cols.second(conditioning_nodes[i]); 234 const DiscreteVariable& var 235 = dynamic_cast< const DiscreteVariable& >(database.variable(col)); 236 str << var.name() << "=" << var.labels()[values[i]]; 237 } 238 auto target_col = node2cols.empty() ? target_node : node2cols.second(target_node); 239 const Variable& var = database.variable(target_col); 240 str << "> for target node " << var.name() 241 << " never appears in the database. Please consider using " 242 << "priors such as smoothing."; 243 244 GUM_ERROR(DatabaseError, str.str()) 245 } 246 } 247 248 // normalize the counts 249 for (std::size_t j = std::size_t(0), k = std::size_t(0); j < conditioning_domsize; ++j) { 250 for (std::size_t i = std::size_t(0); i < target_domsize; ++i, ++k) { 251 N_ijk[k] /= N_ij[j]; 252 } 253 } 254 } else { 255 // here, there are no conditioning nodes. Hence N_ijk is the marginal 256 // probability distribution over the target node. To normalize it, it 257 // is sufficient to divide each cell by the sum over all the cells 258 double sum = 0; 259 for (const double n_ijk: N_ijk) 260 sum += n_ijk; 261 262 if (sum != 0) { 263 for (double& n_ijk: N_ijk) 264 n_ijk /= sum; 265 } else { 266 std::stringstream str; 267 268 const auto& node2cols = this->counter_.nodeId2Columns(); 269 const auto& database = this->counter_.database(); 270 auto target_col = node2cols.empty() ? target_node : node2cols.second(target_node); 271 const Variable& var = database.variable(target_col); 272 str << "No data for target node " << var.name() 273 << ". It is impossible to estimate the parameters by maximum " 274 "likelihood"; 275 GUM_ERROR(DatabaseError, str.str()) 276 } 277 } 278 279 return N_ijk; 280 } 281 282 } /* namespace learning */ 283 284 } /* namespace gum */ 285 286 #endif /* DOXYGEN_SHOULD_SKIP_THIS */ 287