aGrUM  0.20.3
a C++ library for (probabilistic) graphical models
loopyBeliefPropagation_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 /**
23  * @file
24  * @brief Implementation of Loopy Belief Propagation in Bayesian networks.
25  */
26 #ifndef DOXYGEN_SHOULD_SKIP_THIS
27 
28 # include <algorithm>
29 # include <sstream>
30 # include <string>
31 
32 # define LBP_DEFAULT_MAXITER 100
33 # define LBP_DEFAULT_EPSILON 1e-8
34 # define LBP_DEFAULT_MIN_EPSILON_RATE 1e-10
35 # define LBP_DEFAULT_PERIOD_SIZE 1
36 # define LBP_DEFAULT_VERBOSITY false
37 
38 
39 // to ease parsing for IDE
40 # include <agrum/BN/inference/loopyBeliefPropagation.h>
41 
42 
43 namespace gum {
44 
45  /// default constructor
46  template < typename GUM_SCALAR >
47  LoopyBeliefPropagation< GUM_SCALAR >::LoopyBeliefPropagation(const IBayesNet< GUM_SCALAR >* bn) :
48  ApproximateInference< GUM_SCALAR >(bn) {
49  // for debugging purposes
50  GUM_CONSTRUCTOR(LoopyBeliefPropagation);
51 
52  this->setEpsilon(LBP_DEFAULT_EPSILON);
53  this->setMinEpsilonRate(LBP_DEFAULT_MIN_EPSILON_RATE);
54  this->setMaxIter(LBP_DEFAULT_MAXITER);
55  this->setVerbosity(LBP_DEFAULT_VERBOSITY);
56  this->setPeriodSize(LBP_DEFAULT_PERIOD_SIZE);
57 
58  _init_messages_();
59  }
60 
61  /// destructor
62  template < typename GUM_SCALAR >
63  INLINE LoopyBeliefPropagation< GUM_SCALAR >::~LoopyBeliefPropagation() {
64  GUM_DESTRUCTOR(LoopyBeliefPropagation);
65  }
66 
67 
68  template < typename GUM_SCALAR >
69  void LoopyBeliefPropagation< GUM_SCALAR >::_init_messages_() {
70  _messages_.clear();
71  for (const auto& tail: this->BN().nodes()) {
72  Potential< GUM_SCALAR > p;
73  p.add(this->BN().variable(tail));
74  p.fill(static_cast< GUM_SCALAR >(1));
75 
76  for (const auto& head: this->BN().children(tail)) {
77  _messages_.insert(Arc(head, tail), p);
78  _messages_.insert(Arc(tail, head), p);
79  }
80  }
81  }
82 
83  template < typename GUM_SCALAR >
84  void LoopyBeliefPropagation< GUM_SCALAR >::updateOutdatedStructure_() {
85  _init_messages_();
86  }
87 
88 
89  template < typename GUM_SCALAR >
90  Potential< GUM_SCALAR > LoopyBeliefPropagation< GUM_SCALAR >::_computeProdPi_(NodeId X) {
91  const auto& varX = this->BN().variable(X);
92 
93  auto piX = this->BN().cpt(X);
94  for (const auto& U: this->BN().parents(X)) {
95  piX *= _messages_[Arc(U, X)];
96  }
97  piX = piX.margSumIn({&varX});
98 
99  return piX;
100  }
101 
102  template < typename GUM_SCALAR >
103  Potential< GUM_SCALAR > LoopyBeliefPropagation< GUM_SCALAR >::_computeProdPi_(NodeId X,
104  NodeId except) {
105  const auto& varX = this->BN().variable(X);
106  const auto& varExcept = this->BN().variable(except);
107  auto piXexcept = this->BN().cpt(X);
108  for (const auto& U: this->BN().parents(X)) {
109  if (U != except) { piXexcept *= _messages_[Arc(U, X)]; }
110  }
111  piXexcept = piXexcept.margSumIn({&varX, &varExcept});
112  return piXexcept;
113  }
114 
115 
116  template < typename GUM_SCALAR >
117  Potential< GUM_SCALAR > LoopyBeliefPropagation< GUM_SCALAR >::_computeProdLambda_(NodeId X) {
118  Potential< GUM_SCALAR > lamX;
119  if (this->hasEvidence(X)) {
120  lamX = *(this->evidence()[X]);
121  } else {
122  lamX.add(this->BN().variable(X));
123  lamX.fill(1);
124  }
125  for (const auto& Y: this->BN().children(X)) {
126  lamX *= _messages_[Arc(Y, X)];
127  }
128 
129  return lamX;
130  }
131 
132  template < typename GUM_SCALAR >
133  Potential< GUM_SCALAR > LoopyBeliefPropagation< GUM_SCALAR >::_computeProdLambda_(NodeId X,
134  NodeId except) {
135  Potential< GUM_SCALAR > lamXexcept;
136  if (this->hasEvidence(X)) { //
137  lamXexcept = *this->evidence()[X];
138  } else {
139  lamXexcept.add(this->BN().variable(X));
140  lamXexcept.fill(1);
141  }
142  for (const auto& Y: this->BN().children(X)) {
143  if (Y != except) { lamXexcept *= _messages_[Arc(Y, X)]; }
144  }
145 
146  return lamXexcept;
147  }
148 
149 
150  template < typename GUM_SCALAR >
151  GUM_SCALAR LoopyBeliefPropagation< GUM_SCALAR >::_updateNodeMessage_(NodeId X) {
152  auto piX = _computeProdPi_(X);
153  auto lamX = _computeProdLambda_(X);
154 
155  GUM_SCALAR KL = 0;
156  Arc argKL(0, 0);
157 
158  // update lambda_par (for arc U->x)
159  for (const auto& U: this->BN().parents(X)) {
160  auto newLambda = (_computeProdPi_(X, U) * lamX).margSumIn({&this->BN().variable(U)});
161  newLambda.normalize();
162  auto ekl = static_cast< GUM_SCALAR >(0);
163  try {
164  ekl = _messages_[Arc(X, U)].KL(newLambda);
165  } catch (InvalidArgument&) {
166  GUM_ERROR(InvalidArgument, "Not compatible pi during computation")
167  } catch (FatalError&) { // 0 misplaced
168  ekl = std::numeric_limits< GUM_SCALAR >::infinity();
169  }
170  if (ekl > KL) {
171  KL = ekl;
172  argKL = Arc(X, U);
173  }
174  _messages_.set(Arc(X, U), newLambda);
175  }
176 
177  // update pi_child (for arc x->child)
178  for (const auto& Y: this->BN().children(X)) {
179  auto newPi = (piX * _computeProdLambda_(X, Y));
180  newPi.normalize();
181  GUM_SCALAR ekl = KL;
182  try {
183  ekl = _messages_[Arc(X, Y)].KL(newPi);
184  } catch (InvalidArgument&) {
185  GUM_ERROR(InvalidArgument, "Not compatible pi during computation")
186  } catch (FatalError&) { // 0 misplaced
187  ekl = std::numeric_limits< GUM_SCALAR >::infinity();
188  }
189  if (ekl > KL) {
190  KL = ekl;
191  argKL = Arc(X, Y);
192  }
193  _messages_.set(Arc(X, Y), newPi);
194  }
195 
196  return KL;
197  }
198 
199  template < typename GUM_SCALAR >
200  INLINE void LoopyBeliefPropagation< GUM_SCALAR >::_initStats_() {
201  _init_messages_();
202  for (const auto& node: this->BN().topologicalOrder()) {
203  _updateNodeMessage_(node);
204  }
205  }
206 
207 
208  /// Returns the probability of the variables.
209  template < typename GUM_SCALAR >
210  void LoopyBeliefPropagation< GUM_SCALAR >::makeInference_() {
211  _initStats_();
212  this->initApproximationScheme();
213 
214  std::vector< NodeId > shuffleIds;
215  for (const auto& node: this->BN().nodes())
216  shuffleIds.push_back(node);
217 
218  auto engine = std::default_random_engine{};
219 
220  GUM_SCALAR error = 0.0;
221  do {
222  std::shuffle(std::begin(shuffleIds), std::end(shuffleIds), engine);
223  this->updateApproximationScheme();
224  for (const auto& node: shuffleIds) {
225  GUM_SCALAR e = _updateNodeMessage_(node);
226  if (e > error) error = e;
227  }
228  } while (this->continueApproximationScheme(error));
229  }
230 
231 
232  /// Returns the probability of the variable.
233  template < typename GUM_SCALAR >
234  INLINE const Potential< GUM_SCALAR >&
235  LoopyBeliefPropagation< GUM_SCALAR >::posterior_(NodeId id) {
236  auto p = _computeProdPi_(id) * _computeProdLambda_(id);
237  p.normalize();
238  _posteriors_.set(id, p);
239 
240  return _posteriors_[id];
241  }
242 } /* namespace gum */
243 
244 #endif // DOXYGEN_SHOULD_SKIP_THIS