From c3d90f16aa65f8113d0a5b30fe4e8b8246359ae6 Mon Sep 17 00:00:00 2001 From: Hendrik Buschmeier <hbuschme@uni-bielefeld.de> Date: Mon, 2 Mar 2015 11:34:05 +0100 Subject: [PATCH] Added PRIMO ipython notebook tutorial. --- .../chapters/NaiveBayesDS.py | 169 ++++++++++++ .../chapters/belief_approx.ipynb | 173 ++++++++++++ .../chapters/belief_cont.ipynb | 129 +++++++++ .../chapters/belief_exact.ipynb | 186 +++++++++++++ .../chapters/belief_networks.ipynb | 248 ++++++++++++++++++ .../chapters/dbn.ipynb | 208 +++++++++++++++ .../chapters/decision_networks.ipynb | 127 +++++++++ .../chapters/dependency_test.ipynb | 75 ++++++ .../chapters/img/dbn.png | Bin 0 -> 12500 bytes .../chapters/img/decnet.png | Bin 0 -> 21449 bytes .../chapters/img/samiam.png | Bin 0 -> 9110 bytes .../chapters/naive_bayes_ev.ipynb | 172 ++++++++++++ doc/ipython-notebook-tutorial/index.ipynb | 71 +++++ 13 files changed, 1558 insertions(+) create mode 100644 doc/ipython-notebook-tutorial/chapters/NaiveBayesDS.py create mode 100755 doc/ipython-notebook-tutorial/chapters/belief_approx.ipynb create mode 100755 doc/ipython-notebook-tutorial/chapters/belief_cont.ipynb create mode 100755 doc/ipython-notebook-tutorial/chapters/belief_exact.ipynb create mode 100755 doc/ipython-notebook-tutorial/chapters/belief_networks.ipynb create mode 100644 doc/ipython-notebook-tutorial/chapters/dbn.ipynb create mode 100644 doc/ipython-notebook-tutorial/chapters/decision_networks.ipynb create mode 100755 doc/ipython-notebook-tutorial/chapters/dependency_test.ipynb create mode 100644 doc/ipython-notebook-tutorial/chapters/img/dbn.png create mode 100644 doc/ipython-notebook-tutorial/chapters/img/decnet.png create mode 100755 doc/ipython-notebook-tutorial/chapters/img/samiam.png create mode 100644 doc/ipython-notebook-tutorial/chapters/naive_bayes_ev.ipynb create mode 100755 doc/ipython-notebook-tutorial/index.ipynb diff --git a/doc/ipython-notebook-tutorial/chapters/NaiveBayesDS.py b/doc/ipython-notebook-tutorial/chapters/NaiveBayesDS.py new file mode 100644 index 0000000..1789dad --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/NaiveBayesDS.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +import numpy + + + +class NaiveBayesDS: + def __init__(self): + self.rootNode = None + self.featureNodes = [] + self.evidence = [] + + def calcRootDistribution(self): + """ + Berechnung der Verteilung des Wurzelknotens. + P(A | B1, ... Bn) [für alle B mit Evidenz] + + A: Wurzelknoten/self.rootNode + B: featureNode + """ + # Array für Verteilung anlegen und mit Nullen füllen + distributionOfA = numpy.zeros(len(self.rootNode[1])) + + # Der Divisor für die Formel wird inkrementell aufsummiert + divisor = 0 + + # Summe über alle Zustände von A + for j in range(len(self.rootNode[2])): + + # Produkt über alle Evidenzen gegebener Bs, multipliziert mit Aj + tmpprod = self.rootNode[2][j] + for i in range(len(self.evidence)): + tmpNode = self.evidence[i][0] + evIdx = self.evidence[i][1] + evIdx = self.getIndex(tmpNode, evIdx) + evConfidence = self.evidence[i][2] + + # Confidence der Evidenz auf CPT rechnen + tmpCpt = numpy.copy(tmpNode[2]) + ix = 0 + for row in tmpCpt: + for ridx in range(len(row)): + row[ridx] *= evConfidence + remains = 1 - numpy.sum(row) + remains /= len(row) + for ridx in range(len(row)): + row[ridx] += remains + tmpCpt[ix] = row + ix += 1 + tmpprod *= tmpCpt[j][evIdx] + + # Achtung! Hier ist die Verteilung von A noch nicht feritg (s.u.) + distributionOfA[j] = tmpprod + divisor += tmpprod + + # Abschließend die Verteilung von A durch den Divisor teilen + distributionOfA = numpy.array(distributionOfA) / divisor + return distributionOfA + + + def calcFeatureDistribution(self, featureNode): + """ + Berechnung der Verteilung eines Feature-Knoten: + P(B_i | B1, ... B_i-1, B_i+1, ... Bn) [für alle featureNode mit Evidenz] + + A: Wurzelknoten/self.rootNode + B: featureNode + """ + # Array für Verteilung anlegen und mit Nullen füllen + distributionOfB = numpy.zeros(len(featureNode[1])) + + # Die Verteilung von A kann aus Aufgabe 1 genutzt werden + distributionOfA = self.calcRootDistribution() + + # Verteilung für featureNode iterativ berechnen + for i in range(len(featureNode[1])): + tmpsum = 0 + # Multiplikationssatz: + for j in range(len(distributionOfA)): + # P(B_i|A_j) * PRODUKT über P(A_j|evidence) + tmpsum += featureNode[2][j][i] * distributionOfA[j] + distributionOfB[i] = tmpsum + + return distributionOfB + + + def hasEvidence(self, node): + """ + Prüft, ob für einen Featureknoten Evidenz gesetzt ist. + """ + for e in self.evidence: + enode = e[0] + if node == enode: + return True + return False + + + def getIndex(self, node, value): + """ + Index für gegebenen Evidenz-Wert zurückgeben. + """ + for i in range(len(node[1])): + if node[1][i] == value: + return i + return -1 + + def getFeatureEntropy(self, featureNode): + """ + Entropie für einen Featureknoten berechnen. + """ + if self.hasEvidence(featureNode): + return 0 + return self.getEntropy(self.calcFeatureDistribution(featureNode)) + + def getRootEntropy(self): + """ + Entropie für den Wurzelknoten berechnen. + """ + if self.hasEvidence(self.rootNode): + return 0 + distribution = self.calcRootDistribution() + return self.getEntropy(distribution) + + def getEntropy(self, distribution): + """ + Entropie für eine Verteilung berechnen. + + SUMME über alle Werte der Verteilung mit P(B=b) * log2( P(B=b) ) + """ + entropy = 0.0 + for d in distribution: + if (d > 0): + entropy -= d * numpy.log2(d) + return entropy + + + def getConditionalEntropy(self, featureNode): + """ + Bedingte Entropie für den Wurzelknoten bei gegebenen Featureknoten + berechnen, für welchen noch keine Evidenz vorliegt. + + SUMME über alle b aus B mit P(B=b) * H(A|B=b) + """ + entropy = 0.0 + if self.hasEvidence(featureNode): + return 0 + + idx = 0 + for value in featureNode[1]: + # P(B=b) + distB = self.calcFeatureDistribution(featureNode) + probValue = distB[idx] + + # H(A|B=b) + evEntry = [featureNode, value, 1.0] + self.evidence.append(evEntry) + rootEntropy = self.getRootEntropy() + self.evidence = self.evidence[:-1] + + # SUMME += P(B=b) * H(A|B=b) + entropy += probValue * rootEntropy + + idx += 1 + + return entropy + + + + + diff --git a/doc/ipython-notebook-tutorial/chapters/belief_approx.ipynb b/doc/ipython-notebook-tutorial/chapters/belief_approx.ipynb new file mode 100755 index 0000000..8d1e7d4 --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/belief_approx.ipynb @@ -0,0 +1,173 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:5d6d1074166d83c785ce5c1c3faa78e5f5285c404ea2117b8d56e2bc402e5ff9" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1>Belief Networks - Approximierte Inferenz</h1>\n", + "Mittels MCMC-Verfahren kann in *PRIMO* auch approximierte Inferenz berechnet werden. Hier wird das gleiche Beispiel wie bei der exakten Inferenz verwendet:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.networks import BayesianNetwork\n", + "from primo.nodes import DiscreteNode\n", + "import numpy\n", + "\n", + "# Netz initialisieren\n", + "bn = BayesianNetwork()\n", + "\n", + "# Knoten und deren m\u00f6gliche Zust\u00e4nde festlegen\n", + "burglary = DiscreteNode(\"Burglary\", [\"Intruder\",\"Safe\"])\n", + "alarm = DiscreteNode(\"Alarm\", [\"Ringing\", \"Silent\"])\n", + "earthquake = DiscreteNode(\"Earthquake\", [\"Shaking\", \"Calm\"])\n", + "john_calls = DiscreteNode(\"John calls\", [\"Calling\", \"Not Calling\"])\n", + "baum_calls = DiscreteNode(\"Baum calls\", [\"Calling\", \"Not Calling\"])\n", + "\n", + "# Knoten ins Netz hinzuf\u00fcgen\n", + "bn.add_node(burglary)\n", + "bn.add_node(alarm)\n", + "bn.add_node(earthquake)\n", + "bn.add_node(john_calls)\n", + "bn.add_node(baum_calls)\n", + "\n", + "# Kanten einf\u00fcgen\n", + "bn.add_edge(burglary,alarm)\n", + "bn.add_edge(earthquake, alarm)\n", + "bn.add_edge(alarm, john_calls)\n", + "bn.add_edge(alarm, baum_calls)\n", + "\n", + "# F\u00fcr Wurzelknoten (ohne Eltern) werden hier im Beispiel die CPTs direkt gesetzt\n", + "cpt_burglary = numpy.array([0.001,0.999])\n", + "burglary.set_probability_table(cpt_burglary,[burglary])\n", + "\n", + "cpt_earthquake = numpy.array([0.002,0.998])\n", + "earthquake.set_probability_table(cpt_earthquake,[earthquake])\n", + "\n", + "# Setzen der CPT-Werte f\u00fcr Knoten mit Eltern\n", + "alarm.set_probability(0.95,[(alarm,\"Ringing\"),(burglary,\"Intruder\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.05,[(alarm,\"Silent\"),(burglary,\"Intruder\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.29,[(alarm,\"Ringing\"),(burglary,\"Safe\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.71,[(alarm,\"Silent\"),(burglary,\"Safe\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.94,[(alarm,\"Ringing\"),(burglary,\"Intruder\"),(earthquake,\"Calm\")])\n", + "alarm.set_probability(0.06,[(alarm,\"Silent\"),(burglary,\"Intruder\"),(earthquake,\"Calm\")])\n", + "alarm.set_probability(0.001,[(alarm,\"Ringing\"),(burglary,\"Safe\"),(earthquake,\"Calm\")])\n", + "alarm.set_probability(0.999,[(alarm,\"Silent\"),(burglary,\"Safe\"),(earthquake,\"Calm\")])\n", + "\n", + "baum_calls.set_probability(0.9,[(alarm,\"Ringing\"),(baum_calls,\"Calling\")])\n", + "baum_calls.set_probability(0.1,[(alarm,\"Ringing\"),(baum_calls,\"Not Calling\")])\n", + "baum_calls.set_probability(0.05,[(alarm,\"Silent\"),(baum_calls,\"Calling\")])\n", + "baum_calls.set_probability(0.95,[(alarm,\"Silent\"),(baum_calls,\"Not Calling\")])\n", + "\n", + "john_calls.set_probability(0.7,[(alarm,\"Ringing\"),(john_calls,\"Calling\")])\n", + "john_calls.set_probability(0.3,[(alarm,\"Ringing\"),(john_calls,\"Not Calling\")])\n", + "john_calls.set_probability(0.01,[(alarm,\"Silent\"),(john_calls,\"Calling\")])\n", + "john_calls.set_probability(0.99,[(alarm,\"Silent\"),(john_calls,\"Not Calling\")])\n", + "\n", + "# Grafische Ausgabe des Netzes\n", + "bn.draw()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>Funktionen</h2>\n", + "<p>\n", + "Um MCMC zu initialisieren sind mindestens zwei Parameter notwendig: Das Bayesnetz selbst und die Anzahl der Iterationen.\n", + "</p>\n", + "<p>Optional k\u00f6nnen auch das \u00dcbergangsmodell und das Limit f\u00fcr den Konvergenztest bei der Berechnung der Markov-Kette gesetzt werden.\n", + "Metropolis-Hastings:\n", + "```\n", + "mcmc = MCMC(bn, 5000, transition_model=MetropolisHastingsTransitionModel()) # DEFAULT\n", + "```\n", + "Gibbs-Sampling:\n", + "```\n", + "mcmc = MCMC(bn, 5000, transition_model=GibbsTransitionModel())\n", + "```\n", + "\n", + "Limt f\u00fcr den Konvergenztest:\n", + "```\n", + "mcmc = MCMC(bn, 5000, convergence_test=ConvergenceTestSimpleCounting(500)) # DEFAULT\n", + "mcmc = MCMC(bn, 5000, convergence_test=ConvergenceTestSimpleCounting(100))\n", + "```\n", + "\n", + "Im Beispiel wird gezeigt, wie mit MCMC a-priori und a-posteriori Wahrscheinlichkeiten, sowie die Evidenzwahrscheinlichkeit (<i>Probability of Evidence</i>) und die MAP-Hypothese berechnet werden k\u00f6nnen." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from primo.inference.mcmc import MCMC\n", + "from primo.inference.mcmc import GibbsTransitionModel\n", + "from primo.inference.mcmc import MetropolisHastingsTransitionModel\n", + "from primo.evidence import EvidenceEqual as EvEq\n", + "from primo.densities import ProbabilityTable\n", + "from primo.inference.mcmc import ConvergenceTestSimpleCounting\n", + "\n", + "# MCMC initialisieren: Bayesnetz, Iterationen, transition model\n", + "#mcmc = MCMC(bn, 5000, transition_model=GibbsTransitionModel())\n", + "#mcmc = MCMC(bn, 5000, transition_model=MetropolisHastingsTransitionModel())\n", + "mcmc = MCMC(bn, 5000, transition_model=GibbsTransitionModel(), convergence_test=ConvergenceTestSimpleCounting(500))\n", + "\n", + "\n", + "\n", + "print \"-------PriorMarginal:-------\"\n", + "pm = mcmc.calculate_PriorMarginal([alarm],ProbabilityTable)\n", + "print \"P(Alarm)= \" + str(pm)\n", + "print \"Ground truth=[0.0025 0.9975]\\n\"\n", + "\n", + "pm = mcmc.calculate_PriorMarginal([burglary],ProbabilityTable)\n", + "print \"P(Burglary)= \" + str(pm)\n", + "print \"Ground truth=[0.001 0.999]\\n\"\n", + "\n", + "# Evidenz setzen\n", + "evidence = {burglary:EvEq(\"Intruder\")}\n", + "\n", + "\n", + "\n", + "print \"-------ProbabilityOfEvidence:-------\" \n", + "poe = mcmc.calculate_PoE(evidence)\n", + "print \"p(evidence=Intruder)=\"+str(poe)\n", + "print \"Ground truth=0.001\\n\"\n", + "\n", + "print \"-------PosteriorMarginal:-------\"\n", + "pm = mcmc.calculate_PosteriorMarginal([alarm],evidence,ProbabilityTable)\n", + "print \"P(alarm|burglary=Intruder)=\"+str(pm)\n", + "print \"Ground truth=[0.94 0.06]\\n\"\n", + "\n", + "\n", + "\n", + "print \"-------MAP:-------\"\n", + "hyp = mcmc.calculate_MAP([alarm],evidence,ProbabilityTable)\n", + "print \"MAP(alarm|burglary=intruder)=\" + str(hyp)\n", + "print \"Ground truth=\\\"Ringing\\\"\\n\"\n", + "\n", + "hyp = mcmc.calculate_MAP([burglary,alarm],{},ProbabilityTable)\n", + "print \"MAP(burglary,alarm)=\"+str(hyp)\n", + "print \"Ground truth=\\\"Safe\\\",\\\"Silent\\\"\\n\"\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/chapters/belief_cont.ipynb b/doc/ipython-notebook-tutorial/chapters/belief_cont.ipynb new file mode 100755 index 0000000..40a172e --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/belief_cont.ipynb @@ -0,0 +1,129 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:b5510da9fa62058537da539fc44d713d34ed6b88e5a9a7acab0e555001879c5a" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1>Belief Networks - Kontinuierliche Variablen</h1>\n", + "<p>MCMC (siehe <a href=\"chapters/belief_approx.ipynb\">Belief Networks - Approximierte Inferenz</a>) kann neben diskreten auch mit kontinuierlichen Variablen Inferenzen ziehen. Allerdings kann ein Bayesnetz nur aus kontinuierlichen oder nur aus diskreten Variablen bestehen, eine Berechnung auf einem Netz, wo diese Typen vermischt sind, ist aktuell noch nicht m\u00f6glich.\n", + "</p>\n", + "<p>\n", + "Sowohl bei der Parametrisierung von Knoten als auch bei der Inferenz k\u00f6nnen Verteilungen angegeben werden. Diese sind in `primo.densities` implementiert, dort bereits enthalten sind: Beta-Verteilung, Exponential-Verteilung und Gauss-Verteilung.\n", + "</p>\n", + "<p>\n", + "Im folgenden Beispiel ist ein Bayesnetz modelliert, welches das lineare Verh\u00e4ltnis zwischen Alter und H\u00f6he einer Pflanze modelliert, zuz\u00fcglich Rauschen.\n", + "</p>" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.networks import BayesianNetwork\n", + "from primo.nodes import ContinuousNodeFactory\n", + "from primo.densities import ExponentialParameters\n", + "from primo.densities import BetaParameters\n", + "from primo.densities import GaussParameters\n", + "\n", + "# Bayesnetz initialisieren\n", + "bn = BayesianNetwork()\n", + "\n", + "# Knoten erstellen und hinzuf\u00fcgen\n", + "cnf = ContinuousNodeFactory()\n", + "\n", + "age = cnf.createExponentialNode(\"Age\")\n", + "sun = cnf.createBetaNode(\"Sun\")\n", + "ground = cnf.createGaussNode(\"Ground\")\n", + "growth = cnf.createGaussNode(\"Growth\")\n", + "height = cnf.createBetaNode(\"Height\")\n", + "diameter = cnf.createExponentialNode(\"Diameter\")\n", + "children = cnf.createExponentialNode(\"Children\")\n", + "\n", + "bn.add_node(age)\n", + "bn.add_node(sun)\n", + "bn.add_node(ground)\n", + "bn.add_node(growth)\n", + "bn.add_node(height)\n", + "bn.add_node(diameter)\n", + "bn.add_node(children)\n", + "\n", + "# Kanten erstellen\n", + "bn.add_edge(age, growth)\n", + "bn.add_edge(ground, growth)\n", + "bn.add_edge(sun, growth)\n", + "bn.add_edge(growth, diameter)\n", + "bn.add_edge(growth, height)\n", + "bn.add_edge(height, children)\n", + "bn.add_edge(ground, children)\n", + "\n", + "# Parametrisierung\n", + "age.set_density_parameters(ExponentialParameters(0.1, {}))\n", + "sun.set_density_parameters(BetaParameters(2, {}, 2, {}))\n", + "ground.set_density_parameters(GaussParameters(2.0, {}, 1.5))\n", + "growth.set_density_parameters(GaussParameters(0.1, {age:5.0, ground:1.0, sun:4.0}, 2.5))\n", + "height.set_density_parameters(BetaParameters(0.1, {growth:1}, 0.5, {growth:0.5}))\n", + "diameter.set_density_parameters(ExponentialParameters(0.01, {growth:0.2}))\n", + "children.set_density_parameters(ExponentialParameters(0.1, {ground:1.0, height:1.0}))\n", + "\n", + "bn.draw()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>Funktionen</h2>\n", + "<p>\n", + "Zur Inferenz auf kontinuierlichen Netzen k\u00f6nnen die gleichen Funktionen wie auf diskreten Netzen verwendet werden. (F\u00fcr die Parameter des MCMC-Konstruktors siehe ebenfalls <a href=\"chapters/belief_approx.ipynb\">Belief Networks - Approximierte Inferenz</a>).\n", + "</p>\n", + "<p>\n", + "Bei den R\u00fcckgabewerten werden die Verteilungsparameter angegeben. Im Beispiel f\u00fcr die Gauss-Verteilung sind dies \"`mu`\" f\u00fcr den Erwartungswert $\\mu$ und \"`c`\" f\u00fcr die Standardabweichung $\\sigma$.\n", + "</p>\n" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from primo.inference.mcmc import MCMC\n", + "from primo.evidence import EvidenceEqual as EvEqual\n", + "from primo.densities import NDGauss\n", + "from primo.densities import Gauss\n", + "\n", + "# MCMC mit Standardwerten initialisieren\n", + "mcmc = MCMC(bn, 100)\n", + "\n", + "print \"PriorMarginal:\"\n", + "pm = mcmc.calculate_PriorMarginal([age], NDGauss)\n", + "print pm\n", + "pm = mcmc.calculate_PriorMarginal([height], NDGauss)\n", + "print pm\n", + "\n", + "# Evidenz setzen\n", + "evidence = {age:EvEqual(2)}\n", + "\n", + "print \"PosteriorMarginal:\"\n", + "pm = mcmc.calculate_PosteriorMarginal([age, height], evidence, NDGauss)\n", + "print pm" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/chapters/belief_exact.ipynb b/doc/ipython-notebook-tutorial/chapters/belief_exact.ipynb new file mode 100755 index 0000000..b975cf6 --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/belief_exact.ipynb @@ -0,0 +1,186 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:8d5da5c534ffd680f845f7a01f34a54839873823dcd8d641a54447577f16576a" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1 id=\"inferenz\">Belief Networks - Exakte Inferenz</h1>\n", + "<ul>\n", + "<li><a href=\"#fe\">Factor Elimination</a></li>\n", + "<li><a href=\"#ftree\">Factor Tree</a></li>\n", + "</ul>\n", + "\n", + "Im Folgenden werden verschiedene Methoden f\u00fcr die exakte Inferenz vorgestellt. Daf\u00fcr soll das Beispiel aus *A. Darwiche - Modeling and Reasoning with Bayesian Networks, S. 54* dienen:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.networks import BayesianNetwork\n", + "from primo.nodes import DiscreteNode\n", + "import numpy\n", + "\n", + "# Netz initialisieren\n", + "bn = BayesianNetwork()\n", + "\n", + "# Knoten und deren m\u00f6gliche Zust\u00e4nde festlegen\n", + "burglary = DiscreteNode(\"Burglary\", [\"Intruder\",\"Safe\"])\n", + "alarm = DiscreteNode(\"Alarm\", [\"Ringing\", \"Silent\"])\n", + "earthquake = DiscreteNode(\"Earthquake\", [\"Shaking\", \"Calm\"])\n", + "john_calls = DiscreteNode(\"John calls\", [\"Calling\", \"Not Calling\"])\n", + "baum_calls = DiscreteNode(\"Baum calls\", [\"Calling\", \"Not Calling\"])\n", + "\n", + "# Knoten ins Netz hinzuf\u00fcgen\n", + "bn.add_node(burglary)\n", + "bn.add_node(alarm)\n", + "bn.add_node(earthquake)\n", + "bn.add_node(john_calls)\n", + "bn.add_node(baum_calls)\n", + "\n", + "# Kanten einf\u00fcgen\n", + "bn.add_edge(burglary,alarm)\n", + "bn.add_edge(earthquake, alarm)\n", + "bn.add_edge(alarm, john_calls)\n", + "bn.add_edge(alarm, baum_calls)\n", + "\n", + "# F\u00fcr Wurzelknoten (ohne Eltern) werden hier im Beispiel die CPTs direkt gesetzt\n", + "cpt_burglary = numpy.array([0.001,0.999])\n", + "burglary.set_probability_table(cpt_burglary,[burglary])\n", + "\n", + "cpt_earthquake = numpy.array([0.002,0.998])\n", + "earthquake.set_probability_table(cpt_earthquake,[earthquake])\n", + "\n", + "# Setzen der CPT-Werte f\u00fcr Knoten mit Eltern\n", + "alarm.set_probability(0.95,[(alarm,\"Ringing\"),(burglary,\"Intruder\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.05,[(alarm,\"Silent\"),(burglary,\"Intruder\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.29,[(alarm,\"Ringing\"),(burglary,\"Safe\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.71,[(alarm,\"Silent\"),(burglary,\"Safe\"),(earthquake,\"Shaking\")])\n", + "alarm.set_probability(0.94,[(alarm,\"Ringing\"),(burglary,\"Intruder\"),(earthquake,\"Calm\")])\n", + "alarm.set_probability(0.06,[(alarm,\"Silent\"),(burglary,\"Intruder\"),(earthquake,\"Calm\")])\n", + "alarm.set_probability(0.001,[(alarm,\"Ringing\"),(burglary,\"Safe\"),(earthquake,\"Calm\")])\n", + "alarm.set_probability(0.999,[(alarm,\"Silent\"),(burglary,\"Safe\"),(earthquake,\"Calm\")])\n", + "\n", + "baum_calls.set_probability(0.9,[(alarm,\"Ringing\"),(baum_calls,\"Calling\")])\n", + "baum_calls.set_probability(0.1,[(alarm,\"Ringing\"),(baum_calls,\"Not Calling\")])\n", + "baum_calls.set_probability(0.05,[(alarm,\"Silent\"),(baum_calls,\"Calling\")])\n", + "baum_calls.set_probability(0.95,[(alarm,\"Silent\"),(baum_calls,\"Not Calling\")])\n", + "\n", + "john_calls.set_probability(0.7,[(alarm,\"Ringing\"),(john_calls,\"Calling\")])\n", + "john_calls.set_probability(0.3,[(alarm,\"Ringing\"),(john_calls,\"Not Calling\")])\n", + "john_calls.set_probability(0.01,[(alarm,\"Silent\"),(john_calls,\"Calling\")])\n", + "john_calls.set_probability(0.99,[(alarm,\"Silent\"),(john_calls,\"Not Calling\")])\n", + "\n", + "# Grafische Ausgabe des Netzes\n", + "bn.draw()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2 id=\"fe\">Exakte Inferenz - Factor Elimination</h2>\n", + "<i>(zuerst den Beispiel-Code aus <a href=\"#inferenz\">Exakte Inferenz</a> ausf\u00fchren)</i><br>\n", + "<i>EasiestFactorElimination</i> bildet zun\u00e4chst den Joint Probability Table (jpt), um anschlie\u00dfend auf die angefragten Variablen abzubilden:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.inference.factor import EasiestFactorElimination\n", + "\n", + "# Initialisieren und Netz setzen\n", + "fe = EasiestFactorElimination(bn)\n", + "\n", + "# a-priori\n", + "print \"Prior Marginals:\"\n", + "print \"Prior Alarm: \" + str(fe.calculate_PriorMarginal([alarm]))\n", + "print \"Prior John_Calls: \" + str(fe.calculate_PriorMarginal([john_calls]))\n", + "print \"Prior Baum_Calls: \" + str(fe.calculate_PriorMarginal([baum_calls]))\n", + "print \"Prior Burglary: \" + str(fe.calculate_PriorMarginal([burglary]))\n", + "print \"Prior Earthquake: \" + str(fe.calculate_PriorMarginal([earthquake]))\n", + "\n", + "# Probability of Evidence f\u00fcr zwei Evidenz-Beispiele\n", + "print \"PoE Earthquake: \" + str(fe.calculate_PoE([(earthquake, \"Calm\")]))\n", + "print \"PoE BaumCalls is Calling: \" + str(fe.calculate_PoE([(baum_calls, \"Calling\")]))\n", + "\n", + "# a-posteriori (mit gegebener Evidenz f\u00fcr alarm und earthquake)\n", + "print \"Posterior of burglary : \" + str(fe.calculate_PosteriorMarginal([burglary],[(alarm, \"Ringing\"),(earthquake, \"Calm\")]))\n", + "print \"Posterior of alarm: \" + str(fe.calculate_PosteriorMarginal([alarm],[(burglary, \"Intruder\")]))\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2 id=\"ftree\">Exakte Inferenz - Factor Tree</h2>\n", + "<i>(zuerst den Beispiel-Code aus <a href=\"#inferenz\">Exakte Inferenz</a> ausf\u00fchren)</i><br>\n", + "<i>FactorTree</i> ist bei gro\u00dfen Netzen und/oder vielen Anfragen effizienter als <i>EasiestFactorElimination</i>. Die erste Anfrage ist zwar aufw\u00e4ndig, daf\u00fcr werden die folgenden Anfragen viel schneller verarbeitet.\n", + "Nach ver\u00e4nderter Evidenz m\u00fcssen zun\u00e4chst alle zwischengespeicherten Werte neu berechnet werden, was wieder relativ teuer ist, anschlie\u00dfend jedoch wieder zu schnellerer Verarbeitung f\u00fchrt.\n", + "\n", + "Im Beispiel werden die Berechungen f\u00fcr Marginals, sowohl a-priori sowie a-posteriori und die Berechnung der Evidenzwahrscheinlichkeit (<i>Probability of Evidence</i>) dargestellt." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from primo.inference.factor import FactorTreeFactory\n", + "\n", + "# Initialisieren\n", + "factorTreeFactory = FactorTreeFactory()\n", + "\n", + "# Netz setzen\n", + "factorTree = factorTreeFactory.create_greedy_factortree(bn)\n", + "\n", + "# Grafische Ausgabe des FactorTrees\n", + "factorTree.draw()\n", + "\n", + "# a-priori\n", + "print \"Prior Marginals:\"\n", + "print \"AlarmFT: \" + str(factorTree.calculate_marginal([alarm]))\n", + "print \"John_CallsFT: \" + str(factorTree.calculate_marginal([john_calls]))\n", + "print \"Baum_CallsFT: \" + str(factorTree.calculate_marginal([baum_calls]))\n", + "print \"BurglaryFT: \" + str(factorTree.calculate_marginal([burglary]))\n", + "print \"EarthquakeFT: \" + str(factorTree.calculate_marginal([earthquake]))\n", + "\n", + "# Evidenz setzen\n", + "factorTree.set_evidences([(alarm, \"Ringing\"),(earthquake, \"Calm\")])\n", + "\n", + "# Probability of Evidence\n", + "print \"PoE: \" + str(factorTree.calculate_PoE())\n", + "\n", + "# a-posteriori (mit gegebener Evidenz)\n", + "print \"Posterior Marginal (alarm->ringing , earthquake->calm):\"\n", + "print \"AlarmFT: \" + str(factorTree.calculate_marginal([alarm]))\n", + "print \"John_CallsFT: \" + str(factorTree.calculate_marginal([john_calls]))\n", + "print \"Baum_CallsFT: \" + str(factorTree.calculate_marginal([baum_calls]))\n", + "print \"BurglaryFT: \" + str(factorTree.calculate_marginal([burglary]))\n", + "print \"EarthquakeFT: \" + str(factorTree.calculate_marginal([earthquake]))\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/chapters/belief_networks.ipynb b/doc/ipython-notebook-tutorial/chapters/belief_networks.ipynb new file mode 100755 index 0000000..7f857a8 --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/belief_networks.ipynb @@ -0,0 +1,248 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:65a93d4eac3d4833aa873df158fb44763b355addcdd8fd960ef8780af43c06c1" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#Belief Networks\n", + "* <a href=\"#aufbau\">Aufbau und Struktur</a><br>\n", + "* <a href=\"#cpt\">CPTs</a><br>\n", + "* <a href=\"#valid\">Validit\u00e4t</a><br>\n", + "* <a href=\"#xbif\">Aus XMLBIF-Format laden</a><br>\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2 id=\"aufbau\">Aufbau und Struktur</h2>\n", + "Im ersten Beispiel zu Belief Networks wird ein minimales Bayesnetz erstellt. Es gibt einen Knoten *A* und einen Knoten *B*, beide enthalten die Werte `true` und `false`. Es exisiert eine gerichtete Kante von *A* nach *B*.\n", + "In der grafischen Darstellung unter dem Code-Beispiel ist die Richtung der Kante am dickeren Ende erkennbar, welche auf den Kindknoten zeigt." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.networks import BayesianNetwork\n", + "from primo.nodes import DiscreteNode\n", + "\n", + "# Bayesnetz initialisieren\n", + "bn = BayesianNetwork()\n", + "\n", + "# Knoten erstellen\n", + "A = DiscreteNode(\"A\", [\"true\",\"false\"])\n", + "B = DiscreteNode(\"B\", [\"true\", \"false\"])\n", + "\n", + "# Knoten zum Bayesnetz hinzuf\u00fcgen\n", + "bn.add_node(A)\n", + "bn.add_node(B)\n", + "\n", + "# Eine Kante einf\u00fcgen\n", + "bn.add_edge(A,B)\n", + "\n", + "# Netzwerk in Textform darstellen\n", + "print \"Netzwerk in Textform:\", bn.node_lookup\n", + "\n", + "# Netzwerk grafisch darstellen\n", + "bn.draw()\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2 id=\"cpt\">CPTs</h2>\n", + "Das n\u00e4chste Beispiel mit vier Knoten zeigt zus\u00e4tzlich, wie CPTs zu den Knoten hinzugef\u00fcgt werden. Diese k\u00f6nnen entweder direkt als numpy-array gesetzt werden, alternativ werden die Werte f\u00fcr jede Variablenbelegung einzeln \u00fcbergeben." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.networks import BayesianNetwork\n", + "from primo.nodes import DiscreteNode\n", + "import numpy\n", + "\n", + "# Bayesnetz initialisieren\n", + "bn = BayesianNetwork()\n", + "\n", + "# Knoten erstellen\n", + "A = DiscreteNode(\"A\", [\"true\",\"false\"])\n", + "B = DiscreteNode(\"B\", [\"true\", \"false\"])\n", + "C = DiscreteNode(\"C\", [\"true\", \"false\"])\n", + "D = DiscreteNode(\"D\", [\"true\", \"false\"])\n", + "\n", + "# Knoten zum Bayesnetz hinzuf\u00fcgen\n", + "bn.add_node(A)\n", + "bn.add_node(B)\n", + "bn.add_node(C)\n", + "bn.add_node(D)\n", + "\n", + "# Kanten einf\u00fcgen\n", + "bn.add_edge(A,B)\n", + "bn.add_edge(A,C)\n", + "bn.add_edge(B,D)\n", + "bn.add_edge(C,D)\n", + "\n", + "\n", + "# A h\u00e4ngt von keinem weiteren Knoten ab. Ein einfaches Beispiel, wie man den CPT diret als numpy-array setzen kann.\n", + "cpt_A = numpy.array([0.001,0.999])\n", + "A.set_probability_table(cpt_A,[A])\n", + "\n", + "\n", + "# Setzen der CPT-Werte f\u00fcr B gegeben A\n", + "B.set_probability(0.9,[(A,\"true\"),(B,\"true\")])\n", + "B.set_probability(0.1,[(A,\"true\"),(B,\"false\")])\n", + "B.set_probability(0.25,[(A,\"false\"),(B,\"true\")])\n", + "B.set_probability(0.75,[(A,\"false\"),(B,\"false\")])\n", + "\n", + "\n", + "# Setzen der CPT-Werte f\u00fcr C gegeben A\n", + "C.set_probability(0.66,[(A,\"true\"),(C,\"true\")])\n", + "C.set_probability(0.34,[(A,\"true\"),(C,\"false\")])\n", + "C.set_probability(0.9,[(A,\"false\"),(C,\"true\")])\n", + "C.set_probability(0.1,[(A,\"false\"),(C,\"false\")])\n", + "\n", + "# Setzen der CPT-Werte f\u00fcr D gegeben B und C\n", + "D.set_probability(0.55,[(B,\"true\"),(C,\"true\"),(D,\"true\")])\n", + "D.set_probability(0.45,[(B,\"true\"),(C,\"true\"),(D,\"false\")])\n", + "D.set_probability(0.23,[(B,\"true\"),(C,\"false\"),(D,\"true\")])\n", + "D.set_probability(0.77,[(B,\"true\"),(C,\"false\"),(D,\"false\")])\n", + "D.set_probability(0.15,[(B,\"false\"),(C,\"true\"),(D,\"true\")])\n", + "D.set_probability(0.85,[(B,\"false\"),(C,\"true\"),(D,\"false\")])\n", + "D.set_probability(0.99,[(B,\"false\"),(C,\"false\"),(D,\"true\")])\n", + "D.set_probability(0.01,[(B,\"false\"),(C,\"false\"),(D,\"false\")])\n", + "\n", + "# Ausgabe der CPTs (hei\u00dft hier cpd...)\n", + "print \"CPT des Knoten A:\", A.cpd, \"\\n\"\n", + "print \"CPT des Knoten B:\", B.cpd, \"\\n\"\n", + "print \"CPT des Knoten C:\", C.cpd, \"\\n\"\n", + "print \"CPT des Knoten D:\", D.cpd, \"\\n\"\n", + "\n", + "# Netzwerk in Textform darstellen\n", + "print \"Netzwerk in Textform:\", bn.node_lookup\n", + "\n", + "# Netzwerk grafisch darstellen\n", + "bn.draw()\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2 id=\"valid\">Validit\u00e4t</h2>\n", + "Im n\u00e4chsten Beispiel wird ein Knoten hinzugef\u00fcgt, welcher ein Netz aus drei Knoten zu einem zyklischen Graphen macht. Der Graph l\u00e4sst sich zwar erstellen, die Abfrage `is_valid()` gibt jedoch `False` zur\u00fcck, da das Netz nicht mehr azyklisch (und damit nicht mehr valide) ist.\n", + "\n", + "Weiter werden M\u00f6glichkeiten dargestellt, warum Knoten m\u00f6glicherweise nicht valide sind. Dies ist des Fall, wenn kein CPT gesetzt ist, bzw. dieses Fehler enth\u00e4lt." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.networks import BayesianNetwork\n", + "from primo.nodes import DiscreteNode\n", + "import numpy\n", + "\n", + "# Bayesnetz initialisieren\n", + "bn = BayesianNetwork()\n", + "\n", + "# Knoten erstellen\n", + "A = DiscreteNode(\"A\", [\"true\",\"false\"])\n", + "B = DiscreteNode(\"B\", [\"true\", \"false\"])\n", + "C = DiscreteNode(\"C\", [\"true\", \"false\"])\n", + "\n", + "# Knoten zum Bayesnetz hinzuf\u00fcgen\n", + "bn.add_node(A)\n", + "bn.add_node(B)\n", + "bn.add_node(C)\n", + "\n", + "# Kanten einf\u00fcgen\n", + "bn.add_edge(A,B)\n", + "bn.add_edge(B,C)\n", + "bn.add_edge(C,A)\n", + "\n", + "# F\u00fcr A ist kein CPT gesetzt, daher ist A NICHT valide\n", + "print \"Ist A valide?\", A.is_valid()\n", + "\n", + "# FALSCHES Setzen der CPT-Werte f\u00fcr B gegeben A\n", + "# 1. Die Summe der Wahrscheinlichkeiten f\u00fcr einen Fall muss immer 1 sein (hier ist 0.9+0.2=1.1)\n", + "B.set_probability(0.9,[(A,\"true\"),(B,\"true\")])\n", + "B.set_probability(0.2,[(A,\"true\"),(B,\"false\")])\n", + "\n", + "# 2. Die Belegungen m\u00fcssen vollst\u00e4ndig definiert sein\n", + "B.set_probability(0.25,[(A,\"false\"),(B,\"true\")])\n", + "# Die Belegungen m\u00fcssen vollst\u00e4ndig definiert sein\n", + "#B.set_probability(0.75,[(A,\"false\"),(B,\"false\")])\n", + "\n", + "\n", + "print \"Ist B valide?\", B.is_valid()\n", + "\n", + "\n", + "# Setzen der CPT-Werte f\u00fcr C gegeben B\n", + "# Hier ist ALLES IN ORDNUNG\n", + "C.set_probability(0.66,[(B,\"true\"),(C,\"true\")])\n", + "C.set_probability(0.34,[(B,\"true\"),(C,\"false\")])\n", + "C.set_probability(0.9,[(B,\"false\"),(C,\"true\")])\n", + "C.set_probability(0.1,[(B,\"false\"),(C,\"false\")])\n", + "print \"Ist C valide?\", C.is_valid()\n", + "\n", + "\n", + "# Netzwerk grafisch darstellen\n", + "bn.draw()\n", + "\n", + "print \"Ist das ein valider Graph?\", bn.is_valid()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2 id=\"xbif\">Aus XMLBIF-Format laden</h2>\n", + "XMLBIF ist ein XML-Format f\u00fcr Bayesnetze. Eine detaillierte Erkl\u00e4rung ist hier zu finden: [http://www.cs.cmu.edu/~fgcozman/Research/InterchangeFormat/](http://www.cs.cmu.edu/~fgcozman/Research/InterchangeFormat/)\n", + "\n", + "Das Laden aus einer XMLBIF-Datei verk\u00fcrzt den Code im Vergleich zu den von Hand erstellten Netzen weiter oben enorm. Die Methode `XMLBIF.read()` gibt direkt ein Netz zur\u00fcck. Im folgenden Code wird das *Slippery-*Beispiel aus *A. Darwiche - Modeling and Reasoning with Bayesian Networks, S. 57* geladen:\n" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from primo.io import XMLBIF\n", + "\n", + "# Construct a DynmaicBayesianNetwork\n", + "bn = XMLBIF.read(\"xml/slippery.xbif\")\n", + "\n", + "# Netzwerk in Textform darstellen\n", + "print \"Netzwerk in Textform:\", bn.node_lookup" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/chapters/dbn.ipynb b/doc/ipython-notebook-tutorial/chapters/dbn.ipynb new file mode 100644 index 0000000..00bf0c5 --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/dbn.ipynb @@ -0,0 +1,208 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:4c4ddf6d6d8303f0a44a42263d6888fadfa3c20f03de1f40c70658f3c43c9f06" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1>Dynamische Bayesnetze (DBN)</h1>\n", + "<p>\n", + "Mit Dynamischen Bayesnetzen lassen sich Netze mit zeitlicher Abh\u00e4ngigkeit modellieren. Das Beispiel, welches hier verwendet wird, ist angelehnt an D. Barber - Bayesian Reasoning and Machine Learning\n", + ", S. 466ff(DRAFT Feb. 2014), wo es darum geht, dass ein Roboter einen Belief \u00fcber seine aktuelle Position hat. In der folgenden Abbildung des Beispiels ist x die letzte Position und x0 die aktuelle Position. Die Variable door kann interpretiert werden als Hindernis, welches zu durchqueren ist.\n", + "\n", + "<img src=\"img/dbn.png\" />\n", + "</p>\n", + "\n", + "<h2>Evidenzfunktion</h2>\n", + "<p>\n", + "Vor der eigentlichen Anwendung der *PRIMO*-Methoden f\u00fcr Dynamische Bayesnetze muss eine Evidenzfunktion definiert werden, welche als Eingabeparameter f\u00fcr das Particle Filtering ben\u00f6tigt wird. Dabei handelt es sich neben der Evidenzfunktion selbst um Unteraufrife, welche die n\u00e4chste Position und Evidenz in einzelnen Samples simulieren sollen. Die Variablen f\u00fcr die Evidenz sowie die aktuelle und die letzte Position sollen \u00fcber alle Iterationsschritte erhalten bleiben und sind daher global deklariert.\n", + "</p>" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# Die aktuelle und letzte Position sowie die Evidenz werden in jedem Schritt\n", + "# ben\u00f6tigt, deshalb werden sie global verwendet.\n", + "currentPosition = 0\n", + "lastPosition = 0\n", + "evidence = {}\n", + "\n", + "# Die Evidenzfunktion wird f\u00fcr das Particle Filtering ben\u00f6tigt\n", + "def get_evidence_function():\n", + " global currentPosition\n", + " global evidence\n", + " simulate_evidence()\n", + " simulate_next_pos()\n", + " return evidence\n", + " \n", + "# Simulation f\u00fcr die n\u00e4chste Position\n", + "def simulate_next_pos():\n", + " global currentPosition\n", + " global lastPosition\n", + " lastPosition = currentPosition\n", + " random_pos = random.random()\n", + " if random_pos >= 0.1:\n", + " currentPosition = currentPosition + 1\n", + " elif random_pos >= 0.05:\n", + " currentPosition = currentPosition\n", + " else:\n", + " currentPosition = currentPosition - 1\n", + " if currentPosition == 10:\n", + " currentPosition = 0\n", + " if currentPosition == -1:\n", + " currentPosition = 9\n", + "\n", + "# Simulation der Evidenz \n", + "def simulate_evidence():\n", + " global currentPosition\n", + " global evidence\n", + " global door\n", + " err = False\n", + " if random.random() > 0.99:\n", + " err = True\n", + " if currentPosition == 1 or currentPosition == 3 or currentPosition == 7:\n", + " if err:\n", + " evidence = {door:\"False\"}\n", + " else:\n", + " evidence = {door:\"True\"}\n", + " else:\n", + " if err:\n", + " evidence = {door:\"True\"}\n", + " else:\n", + " evidence = {door:\"False\"}" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>Netz laden und DBN initialisieren</h2>\n", + "Nachdem nun die Evidenzfunktion gegeben ist, kann auf die eigentliche Funktionalit\u00e4t eingegangen werden. Das Netz wird dazu aus einer XBIF Datei geladen." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from primo.networks import DynamicBayesianNetwork\n", + "from primo.networks import BayesianNetwork\n", + "from primo.networks import TwoTBN\n", + "from primo.io import XMLBIF\n", + "from primo.nodes import DiscreteNode\n", + "\n", + "import numpy\n", + "\n", + "\n", + "# Initialisieren\n", + "# DBN\n", + "dbn = DynamicBayesianNetwork()\n", + "# BN zum Zeitpunkt 0\n", + "B0 = BayesianNetwork()\n", + "# Zeitschritt-BN\n", + "twoTBN = TwoTBN(XMLBIF.read(\"xml/Robot_Localization.xmlbif\"))\n", + "\n", + "# Knoten in Variablen packen (door wird sp\u00e4ter f\u00fcr Evidenz ben\u00f6tigt)\n", + "x0 = twoTBN.get_node(\"x0\")\n", + "x = twoTBN.get_node(\"x\")\n", + "door = twoTBN.get_node(\"door\")\n", + "\n", + "# Initialisierung der zeitabh\u00e4ngigen Knoten im Zeitschritt-BN\n", + "twoTBN.set_initial_node(x0.name, x.name)\n", + "\n", + "# Die f\u00fcr twoTBN eingelesenen Knoten werden auf das BN f\u00fcr Zeitpunkt 0 \u00fcbertragen\n", + "x0_init = DiscreteNode(x0.name, [\"p0\", \"p1\", \"p2\", \"p3\", \"p4\", \"p5\", \"p6\", \"p7\", \"p8\", \"p9\"])\n", + "B0.add_node(x0_init)\n", + "cpt_x0_init = numpy.array([.1, .1, .1, .1, .1, .1, .1, .1, .1, .1])\n", + "x0_init.set_probability_table(cpt_x0_init, [x0_init])\n", + "\n", + "# Die Netze f\u00fcr den Startpunkt und weitere Iterationsschritte werden gesetzt.\n", + "dbn.B0 = B0\n", + "dbn.twoTBN = twoTBN" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>Berechnen mit Particle Filtering</h2>\n", + "Nachdem das Dynamische Bayesnetz nun initialisiert ist, kann darauf gearbeitet werden. Die Funktion `particle_filtering` ben\u00f6tigt vier Parameter:<ul>\n", + "<li>dbn: das Dynamische Bayesnetz selbst</li>\n", + "<li>N: Anzahl der Samples</li>\n", + "<li>T: Zeitschritte (-1 f\u00fcr unendlich)</li>\n", + "<li>GEF: die Evidenzfunktion</li>\n", + "</ul>\n", + "Das Ergebnis daraus ist eine Liste von Samples, welches im nachfolgenden Beispiel aufaddiert und normalisiert werden, um daraus Wahrscheinlichkeiten zu erhalten.\n" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from primo.inference import particlefilter as pf\n", + "\n", + "import random\n", + "\n", + "\n", + "N = 1000 # Anzahl der Samples\n", + "T = 10 # Zeitschritte\n", + "GEF = get_evidence_function # Evidenzfunktion\n", + "\n", + "# Particle Filtering\n", + "result = pf.particle_filtering_DBN(dbn, N, T, GEF)\n", + "\n", + "# Auswertung der einzelnen Samples des Ergebnisses\n", + "for samples in result:\n", + " w_hit = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\n", + " for sample in samples:\n", + " state = sample.get_state()\n", + " x_ = x\n", + " if x not in state:\n", + " x_ = x0_init\n", + " if state[x_] == \"p0\":\n", + " w_hit[0] += 1\n", + " if state[x_] == \"p1\":\n", + " w_hit[1] += 1\n", + " if state[x_] == \"p2\":\n", + " w_hit[2] += 1\n", + " if state[x_] == \"p3\":\n", + " w_hit[3] += 1\n", + " if state[x_] == \"p4\":\n", + " w_hit[4] += 1\n", + " if state[x_] == \"p5\":\n", + " w_hit[5] += 1\n", + " if state[x_] == \"p6\":\n", + " w_hit[6] += 1\n", + " if state[x_] == \"p7\":\n", + " w_hit[7] += 1\n", + " if state[x_] == \"p8\":\n", + " w_hit[8] += 1\n", + " if state[x_] == \"p9\":\n", + " w_hit[9] += 1\n", + " prob = [w / N for w in w_hit]\n", + " print \"Real position: \" + str(lastPosition) + \" (Door: \" + str(evidence[door]) + \")\"\n", + " print str(prob)" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/chapters/decision_networks.ipynb b/doc/ipython-notebook-tutorial/chapters/decision_networks.ipynb new file mode 100644 index 0000000..2385de8 --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/decision_networks.ipynb @@ -0,0 +1,127 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:5522c7bc0fd005f67da9ee2a1f19d3e74c1540436271cf6c3d2dcc50c23acdd2" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1>Decision Networks</h1>\n", + "<p>\n", + "In diesem kurzen Beispiel (aus D. Barber - Bayesian Reasoning and Machine Learning\n", + ", S. 128, DRAFT Feb.2014) wird die Entscheidung modelliert, ob es sich lohnt ein PhD-Studium aufzunehmen.\n", + "<img src=\"img/decnet.png\" />\n", + "</p>\n", + "<p>\n", + "Neben den <i>Chance Node</i> (oval) der Belief Networks kann ein <i>Decision</i> Network au\u00dferdem <i>Decision Nodes</i> (rechteckig) und <i>Utility Nodes</i> (oval) enthalten.\n", + "</p>\n", + "<p>\n", + "Das oben dargestellte Beispiel ist im folgenden Code implementiert. In der Klasse `MakeDecision` ist der MaxSum-Algorithmus implementiert (`MakeDecision.max_sum()`), welcher nach dem MEU-Prinzip die beste Entscheidung liefert.\n", + "</p>\n" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "from primo.networks import BayesianDecisionNetwork\n", + "from primo.nodes import DecisionNode\n", + "from primo.nodes import UtilityNode\n", + "from primo.nodes import DiscreteNode\n", + "from primo.inference.decision import MakeDecision\n", + "import numpy\n", + "\n", + "# Netz initialisieren\n", + "bdn = BayesianDecisionNetwork()\n", + "\n", + "# Knoten anlegen\n", + "education = DecisionNode(\"education\", [\"do Phd\", \"no Phd\"]) # E\n", + "cost = UtilityNode(\"cost\") # Uc\n", + "prize = DiscreteNode(\"prize\", [\"prize\", \"no prize\"]) # P\n", + "income = DiscreteNode(\"income\", [\"low\", \"average\", \"high\"]) # I\n", + "benefit = UtilityNode(\"benefit\") # Ub\n", + "startup = DecisionNode(\"startUp\", [\"do startUp\", \"no startUp\"]) # S\n", + "costStartup = UtilityNode(\"costStartup\") # Us\n", + "\n", + "bdn.add_node(education)\n", + "bdn.add_node(cost)\n", + "bdn.add_node(prize)\n", + "bdn.add_node(income)\n", + "bdn.add_node(benefit)\n", + "bdn.add_node(startup)\n", + "bdn.add_node(costStartup)\n", + "\n", + "# Kanten anlegen\n", + "bdn.add_edge(education, cost)\n", + "bdn.add_edge(education, prize)\n", + "bdn.add_edge(prize, startup)\n", + "bdn.add_edge(startup, income)\n", + "bdn.add_edge(startup, costStartup)\n", + "bdn.add_edge(prize, income)\n", + "bdn.add_edge(income, benefit)\n", + "\n", + "# Utilities definieren\n", + "costut=numpy.array([-50000, 0])\n", + "cost.set_utility_table(costut, [education])\n", + "\n", + "benefitut=numpy.array([100000,200000,500000])\n", + "benefit.set_utility_table(benefitut,[income])\n", + "\n", + "startuput=numpy.array([-20000,0])\n", + "costStartup.set_utility_table(startuput,[startup])\n", + "\n", + "# Setzen der CPT-Werte f\u00fcr Knoten mit Eltern\n", + "income.set_probability(0.1,[(income,\"low\"),(startup,\"do startUp\"), (prize,\"no prize\")])\n", + "income.set_probability(0.2,[(income,\"low\"),(startup,\"no startUp\"), (prize,\"no prize\")])\n", + "income.set_probability(0.005,[(income,\"low\"),(startup,\"do startUp\"), (prize,\"prize\")])\n", + "income.set_probability(0.005,[(income,\"low\"),(startup,\"no startUp\"), (prize,\"prize\")])\n", + "income.set_probability(0.5,[(income,\"average\"),(startup,\"do startUp\"), (prize,\"no prize\")])\n", + "income.set_probability(0.6,[(income,\"average\"),(startup,\"no startUp\"), (prize,\"no prize\")])\n", + "income.set_probability(0.005,[(income,\"average\"),(startup,\"do startUp\"), (prize,\"prize\")])\n", + "income.set_probability(0.015,[(income,\"average\"),(startup,\"no startUp\"), (prize,\"prize\")])\n", + "income.set_probability(0.4,[(income,\"high\"),(startup,\"do startUp\"), (prize,\"no prize\")])\n", + "income.set_probability(0.2,[(income,\"high\"),(startup,\"no startUp\"), (prize,\"no prize\")])\n", + "income.set_probability(0.99,[(income,\"high\"),(startup,\"do startUp\"), (prize,\"prize\")])\n", + "income.set_probability(0.8,[(income,\"high\"),(startup,\"no startUp\"), (prize,\"prize\")])\n", + "\n", + "prize.set_probability(0.0000001,[(prize,\"prize\"),(education,\"no Phd\")])\n", + "prize.set_probability(0.001,[(prize,\"prize\"),(education,\"do Phd\")])\n", + "prize.set_probability(0.9999999,[(prize,\"no prize\"),(education,\"no Phd\")])\n", + "prize.set_probability(0.999,[(prize,\"no prize\"),(education,\"do Phd\")])\n", + "\n", + "# Reihenfolge der Decision Nodes/Entscheidungsknoten festlegen\n", + "bdn.set_partialOrdering([education, [prize], startup, [income]])\n", + "\n", + "\n", + "\n", + "# Klasse f\u00fcr Entscheidungen initialisieren\n", + "md = MakeDecision(bdn)\n", + "\n", + "# a-priori Entscheidung \u00fcber Education\n", + "decision = md.max_sum(education)\n", + "print \"Entscheidung Education =\", decision[1]\n", + "\n", + "\n", + "# Entscheidung \u00fcber StartUp, nachdem Education auf \"no Phd gesetzt\" ist\n", + "education.set_state(decision[1])\n", + "start=md.max_sum(startup)\n", + "print \"Entscheidung StartUp =\", start[1]\n", + "\n", + "bdn.draw()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/chapters/dependency_test.ipynb b/doc/ipython-notebook-tutorial/chapters/dependency_test.ipynb new file mode 100755 index 0000000..77d47c7 --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/dependency_test.ipynb @@ -0,0 +1,75 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:3d28b5ba7900703cf14a53b485cf0514346e2a8ce0094c0df330eb3186a8a76e" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Download\n", + "Herunterladen von <a href=\"https://github.com/hbuschme/PRIMO\">https://github.com/hbuschme/PRIMO</a> oder\n", + "```\n", + "git clone https://github.com/hbuschme/PRIMO\n", + "```\n", + "im Zielverzeichnis aufrufen.\n", + "\n", + "# Installation\n", + "Um PRIMO nutzen zu k\u00f6nnen, muss entweder der Ordner *primo* aus dem Hauptordner *PRIMO* in das Projektverzeichnis kopiert werden oder der Umgebungsvariable `PYTHONPATH` muss der Pfad des Ordners, in dem der *primo*-Ordner liegt hinzugef\u00fcgt werden, z.B.:\n", + "```\n", + "export PYTHONPATH=$PYTHONPATH:/home/juser/path/to/PRIMO\n", + "```\n", + "\n", + "# Dependency-Test\n", + "Um zu \u00fcberpr\u00fcfen, ob alle Abh\u00e4ngigkeiten f\u00fcr *PRIMO* erf\u00fcllt sind, kann der nachfolgenden Code genutzt werden.\n", + "Wenn das Ausf\u00fchren ohne Fehlermeldung durchl\u00e4uft, ist alles in Ordnung. Andernfalls wird in der Fehlermeldung angezeigt, welches Modul noch fehlt." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "try:\n", + " # logging: wird nur hier zur Ausgabe ben\u00f6tigt\n", + " import logging\n", + "\n", + " # Die nachfolgenden Module werden auch von PRIMO ben\u00f6tigt\n", + " import abc\n", + " import copy\n", + " import itertools\n", + " import matplotlib.pyplot\n", + " import networkx\n", + " import numpy\n", + " import operator\n", + " import os\n", + " import random\n", + " import re\n", + " import scipy.stats\n", + " import time\n", + " import unittest\n", + " import xml.dom.minidom\n", + " print \"Es sind alle Abh\u00e4ngigkeiten f\u00fcr PRIMO erf\u00fcllt!\"\n", + "except Exception:\n", + " logging.exception(\"FEHLER: Es sind nicht alle Abh\u00e4ngigkeiten f\u00fcr PRIMO erf\u00fcllt!\")\n", + " \n", + " \n", + "try:\n", + " import primo\n", + "except Exception:\n", + " print \"primo selbst fehlt :(\"\n", + " \n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/chapters/img/dbn.png b/doc/ipython-notebook-tutorial/chapters/img/dbn.png new file mode 100644 index 0000000000000000000000000000000000000000..b068c260e53b32a1968b0e30e00038c6fb2f2b68 GIT binary patch literal 12500 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 literal 0 HcmV?d00001 diff --git a/doc/ipython-notebook-tutorial/chapters/img/decnet.png b/doc/ipython-notebook-tutorial/chapters/img/decnet.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5b7cdf21d26c0f5fb7cdb3fef1e5336353eeb0 GIT binary patch literal 21449 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 literal 0 HcmV?d00001 diff --git a/doc/ipython-notebook-tutorial/chapters/img/samiam.png b/doc/ipython-notebook-tutorial/chapters/img/samiam.png new file mode 100755 index 0000000000000000000000000000000000000000..3d450ba41a4ac63b43463d76aef7d774d08dea2d GIT binary patch literal 9110 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 literal 0 HcmV?d00001 diff --git a/doc/ipython-notebook-tutorial/chapters/naive_bayes_ev.ipynb b/doc/ipython-notebook-tutorial/chapters/naive_bayes_ev.ipynb new file mode 100644 index 0000000..a65c5b1 --- /dev/null +++ b/doc/ipython-notebook-tutorial/chapters/naive_bayes_ev.ipynb @@ -0,0 +1,172 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:90a9d74d92118f490bf4db28493687c0ede1b81b9e3ca489862c92c0251b285e" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1>Naive Bayes + Evidenztheorie</h1>\n", + "<p>\n", + "Mit dem Naive Bayes-Klassifikator, welcher um die Evidenztheorie (Dempster-Shafer) erweitert wurde, k\u00f6nnen Evidenzen mit einer Unsicherheit angegeben werden. Ist eine Evidenz nicht sicher oder soll sie als weniger wichtig interpretiert werden, kann sie mit einer `confidence` aus dem Wertebereich `[0.0, 1.0]` versehen werden.\n", + "</p>\n", + "<p>\n", + "Im folgenden Beispiel wird ein Klassifikator erstellt, welcher anhand einiger technischer Features einen Gegenstand erkennen soll. Durch die Berechnung der Entropie kann eine Annahme dar\u00fcber getroffen werden, welches Feature im Mittel die Entropie des Wurzelknoten am weitesten einschr\u00e4nkt und somit am ehesten zu einer sicheren Klassifikation f\u00fchrt.\n", + "</p>" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# -*- coding: utf-8 -*-\n", + "import numpy\n", + "import primo.NaiveBayesDS as NBDS\n", + "\n", + "# NaiveBayesDS initialisieren\n", + "nb = NBDS.NaiveBayesDS()\n", + "\n", + "# Knoten:\n", + "# Index 0: Name des Knoten\n", + "# Index 1: Werte/Zust\u00e4nde des Knoten\n", + "# Index 2 (Wurzelknoten): Verteilung \u00fcber Zust\u00e4nde\n", + "# Index 2 (Featureknoten): Vom Wurzelknoten abh\u00e4ngige Wahrscheinlichkeiten, \u00e4hnlich wie CPT bei komplexeren Bayesnetzen\n", + "\n", + "# Wurzelknoten\n", + "rootNode = (\"gegenstand\", [\"server\", \"laptop\", \"smartphone\", \"fernseher\", \"akkuschrauber\"], numpy.array([0.2, 0.2, 0.2, 0.2, 0.2]))\n", + "# Featureknoten\n", + "nodeBildschirm = (\"bildschirm\", [\"ja\", \"nein\"], numpy.array([[0.01, 0.99], [1.0, 0.0], [1.0, 0.0], [1.0, 0.0], [0.05, 0.95]]))\n", + "nodeAntrieb = (\"antrieb\", [\"stromnetz\", \"akku\", \"hybrid\"], numpy.array([[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]))\n", + "nodeInternet = (\"internet\", [\"ja\", \"nein\"], numpy.array([[1.0, 0.0], [0.9, 0.1], [0.8, 0.2], [0.5, 0.5], [0.01, 0.99]]))\n", + "nodeEinsatz = (\"einsatz\", [\"buero\", \"heim\", \"mobil\"], numpy.array([[0.98, 0.02, 0.0], [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], [0.05, 0.35, 0.6], [0.0, 1.0, 0.0], [0.02, 0.8, 0.18]]))\n", + "nodeFarbe = (\"farbe\", [\"schwarz\", \"grau\", \"blau\", \"gruen\"], numpy.array([ [0.5, 0.5, 0.0, 0.0], [0.6, 0.3, 0.05, 0.05], [0.3, 0.2, 0.3, 0.2], [0.7, 0.25, 0.05, 0.0], [0.25, 0.25, 0.25, 0.25]]))\n", + "\n", + "# Knoten dem Klassifikator hinzuf\u00fcgen\n", + "nb.rootNode = rootNode\n", + "nb.featureNodes.append(nodeBildschirm)\n", + "nb.featureNodes.append(nodeAntrieb)\n", + "nb.featureNodes.append(nodeInternet)\n", + "nb.featureNodes.append(nodeEinsatz)\n", + "nb.featureNodes.append(nodeFarbe)\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>Beispiel 1: Ohne Evidenz</h2>\n", + "Im ersten Anwendungsbeispiel ist die Evidenz noch leer. Die Ausgabe liefert Informationen \u00fcber den Wurzelknoten und \u00fcber den Featureknoten \"einsatz\". Die bedingte Entropie des Featureknoten ist zu interpretieren als die Entropie des Wurzelknoten, wenn der Featureknoten gegeben w\u00e4re." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# Keine Evidenz setzen\n", + "# Harte Evidenz bestimmen\n", + "evidence = []\n", + "nb.evidence = evidence\n", + "\n", + "print \"++++ Ohne Evidenz ++++\"\n", + "print nb.rootNode[0], \"(Root)\"\n", + "print nb.rootNode[1]\n", + "print nb.calcRootDistribution()\n", + "print \"Entropie\", nb.getRootEntropy()\n", + "print \"---- ----\"\n", + "print nodeEinsatz[0], \"(Feature)\"\n", + "print nodeEinsatz[1]\n", + "print nb.calcFeatureDistribution(nodeEinsatz)\n", + "print \"Bedingte Entropie\", nb.getConditionalEntropy(nodeEinsatz)\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>Beispiel 2: Harte Evidenz</h2>\n", + "Das n\u00e4chste Anwendungsbeispiel enth\u00e4lt <i>harte</i> Evidenz f\u00fcr zwei Featureknoten\n", + "```\n", + "(bildschirm=ja mit confidence=1.0; farbe=blau mit confidence=1.0)\n", + "```\n", + "Im Vergleich zum ersten Beispiel lassen sich Ver\u00e4nderungen in Verteilung und Entropien erkennen." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# Harte Evidenz bestimmen\n", + "evidence = []\n", + "evidence.append([nodeBildschirm, \"ja\", 1.0])\n", + "evidence.append([nodeFarbe, \"blau\", 1.0])\n", + "nb.evidence = evidence\n", + "\n", + "print \"++++ Mit Evidenz [bildschirm=ja, farbe=blau] ++++\"\n", + "print nb.rootNode[0], \"(Root)\"\n", + "print nb.rootNode[1]\n", + "print nb.calcRootDistribution()\n", + "print \"Entropie\", nb.getRootEntropy()\n", + "print \"---- ----\"\n", + "print nodeEinsatz[0], \"(Feature)\"\n", + "print nodeEinsatz[1]\n", + "print nb.calcFeatureDistribution(nodeEinsatz)\n", + "print \"Bedingte Entropie\", nb.getConditionalEntropy(nodeEinsatz)\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>Beispiel 3: Weiche Evidenz</h2>\n", + "Das dritte Anwendungsbeispiel zeigt, wie Unsicherheiten bei der Evidenz angegeben werden k\u00f6nnen:\n", + "```\n", + "(bildschirm=ja mit confidence=0.8; farbe=blau mit confidence=0.5)\n", + "```\n", + "In der Ausgabe wird ersichtlich, wie die Verteilungen gegen\u00fcber der harten Evidenz des vorherigen Beispiels gegl\u00e4ttet werden und auch die Entropie etwas gr\u00f6\u00dfer ist." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# Unsichere Evidenz bestimmen\n", + "evidence = []\n", + "evidence.append([nodeBildschirm, \"ja\", 0.8])\n", + "evidence.append([nodeFarbe, \"blau\", 0.5])\n", + "nb.evidence = evidence\n", + "\n", + "print \"++++ Mit unsicherer Evidenz [bildschirm=ja(0.8), farbe=blau(0.5)] ++++\"\n", + "print nb.rootNode[0], \"(Root)\"\n", + "print nb.rootNode[1]\n", + "print nb.calcRootDistribution()\n", + "print \"Entropie\", nb.getRootEntropy()\n", + "print \"---- ----\"\n", + "print nodeEinsatz[0], \"(Feature)\"\n", + "print nodeEinsatz[1]\n", + "print nb.calcFeatureDistribution(nodeEinsatz)\n", + "print \"Bedingte Entropie\", nb.getConditionalEntropy(nodeEinsatz)" + ], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/doc/ipython-notebook-tutorial/index.ipynb b/doc/ipython-notebook-tutorial/index.ipynb new file mode 100755 index 0000000..6bec841 --- /dev/null +++ b/doc/ipython-notebook-tutorial/index.ipynb @@ -0,0 +1,71 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:9c9890f254a26ad9bf5f2dd21bc36508e7fbe48692ea074f1f4d7f030748a31b" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1>PRIMO - [PR]obabilistic [I]nference [MO]dules</h1>\n", + "\n", + "<h2>Inhalt</h2>\n", + "In <i>PRIMO</i> werden verschiedene Varianten von Bayesnetzen implementiert. Im Folgenden werden alle Varianten an Code-Beispielen vorgestellt. Es wird jeweils gezeigt, wie sie erstellt und modifiziert werden und wie man auf ihnen Inferenzen ziehen und weitere Funktionen anwenden kann.\n", + "\n", + "<ul>\n", + "<li><a href=\"chapters/dependency_test.ipynb\">Installation und Dependency-Test:</a><br>\n", + "Eine kurze Anleitung, wie man *PRIMO* in ein Python-Projekt einbindet und ein Check, ob alle ben\u00f6tigten Python-Module installiert sind.\n", + "</li>\n", + "<br>\n", + "\n", + "<li><a href=\"chapters/belief_networks.ipynb\">Belief Networks:</a><br>\n", + "Belief Networks sind die \"herk\u00f6mmlich\" Form von Bayesnetzen. Sie modellieren die kausalen Beziehungen zwischen mehreren Variablen in einem gerichteten Graphen:<br>\n", + " <ul>\n", + " \n", + " <br><li> <a href=\"chapters/belief_exact.ipynb\">Belief Networks - Exakte Inferenz:</a><br>\n", + " *PRIMO* bietet zwei verschiedene Funktionen zur exakten Inferenz, also der Berechnung von Wahrscheinlichkeitsverteilungen, sowohl a-priori (ohne Evidenz) als auch a-posteriori (mit Evidenz). \n", + " </li>\n", + " \n", + " <br><li> <a href=\"chapters/belief_approx.ipynb\">Belief Networks - Approximierte Inferenz:</a><br>\n", + " Mit MCMC enth\u00e4lt *PRIMO* au\u00dferdem ein approximatives Sampling-Verfahren, mit dem ebenfalls Inferenzen und eine MAP-Hypothese berechnet werden k\u00f6nnen.\n", + " </li>\n", + " \n", + " <br><li> <a href=\"chapters/belief_cont.ipynb\">Belief Networks - Kontinuierliche Variablen:</a><br>\n", + " Das MCMC-Verfahren bietet au\u00dferdem die M\u00f6glichkeit, auf Bayesnetzen mit kontinuierlichen Variablen zu arbeiten. In diesem Fall liegen keine diskreten Variablen mit einer festen Menge an Zust\u00e4nden vor, stattdessen enthalten Knoten Wahrscheinlichkeitsverteilungen.\n", + " </li>\n", + " \n", + " </ul>\n", + "</li>\n", + "\n", + "<br><li> <a href=\"chapters/decision_networks.ipynb\">Decision Networks:</a><br>\n", + "Decision Networks sind eine erweiterte Form von Bayesnetzen, welche zus\u00e4tzlich <i>Utilities</i> f\u00fcr Entscheidungen enthalten. So k\u00f6nnen Entscheidungssituationen durch ein Decision Network modelliert werden, welches f\u00fcr bestimmte Variablenbelegungen (Entscheidungen) deren Utility berechnet und so die Entscheidung bestimmt werden kann, welche wahrscheinlich den gr\u00f6\u00dften Nutzen (maximale Utility) bietet.\n", + "</li>\n", + "\n", + "<br><li> <a href=\"chapters/dbn.ipynb\">Dynamische Bayesnetze:</a><br>\n", + "Dynamische Bayesnetze dienen dazu, ein modelliertes Problem zeitlich anzupassen. Dabei hat der Zustand des vorherigen Zeitschritts jeweils Einfluss auf bestimmte Variablen des aktuellen Zeitschritts (gleiches Prinzip wie HMMs).\n", + "</li>\n", + "\n", + "<br><li> <a href=\"chapters/naive_bayes_ev.ipynb\">Naive Bayes + Evidenztheorie:</a><br>\n", + "Einfache Form eines Bayesnetzes mit Erweiterungen f\u00fcr die Angabe von Unsicherheit (weiche Evidenz mittels Dempster-Shafer-Theory) und der Berechnung von Entropien.\n", + "</li>\n", + "</ul>\n", + "\n", + "<h2>Sonstiges</h2>\n", + "Definition des XBIF-Formats: <a href=\"http://www.cs.cmu.edu/~fgcozman/Research/InterchangeFormat/\">http://www.cs.cmu.edu/~fgcozman/Research/InterchangeFormat/</a>\n", + "<br>\n", + "Kurzer Guide zum Plotten mit `pyplot` aus `matplotlib`: <a href=\"doc/pyplot.pdf\">pyplot.pdf</a>\n", + "<br>\n", + "Kurzer Guide zum externen Modul `networkx`: <a href=\"doc/networkx.pdf\">networkx.pdf</a>\n", + "<br>" + ] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file -- GitLab