/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * BayesNet.java * Copyright (C) 2001 Remco Bouckaert * */ package weka.classifiers.bayes; import java.io.*; import java.util.*; import weka.core.*; import weka.estimators.*; import weka.classifiers.*; /** * Base class for a Bayes Network classifier. Provides datastructures (network structure, * conditional probability distributions, etc.) and facilities common to Bayes Network * learning algorithms like K2 and B. * Works with nominal variables and no missing values only. * * @author Remco Bouckaert (rrb@xm.co.nz) * @version $Revision: 1.1.1.1 $ */ public class BayesNet extends DistributionClassifier implements OptionHandler, WeightedInstancesHandler { /** * topological ordering of the network */ protected int[] m_nOrder; /** * The parent sets. */ protected ParentSet[] m_ParentSets; /** * The attribute estimators containing CPTs. */ protected Estimator[][] m_Distributions; /** * The number of classes */ protected int m_NumClasses; /** * The dataset header for the purposes of printing out a semi-intelligible * model */ public Instances m_Instances; ADNode m_ADTree; public static final Tag [] TAGS_SCORE_TYPE = { new Tag(Scoreable.BAYES, "BAYES"), new Tag(Scoreable.MDL, "MDL"), new Tag(Scoreable.ENTROPY, "ENTROPY"), new Tag(Scoreable.AIC, "AIC") }; /** * Holds the score type used to measure quality of network */ int m_nScoreType = Scoreable.BAYES; /** * Holds prior on count */ double m_fAlpha = 0.5; /** * Holds upper bound on number of parents */ int m_nMaxNrOfParents = 100000; /** * determines whether initial structure is an empty graph or a Naive Bayes network */ boolean m_bInitAsNaiveBayes = true; /** * Use the experimental ADTree datastructure for calculating contingency tables */ boolean m_bUseADTree = true; /** * Generates the classifier. * * @param instances set of instances serving as training data * @exception Exception if the classifier has not been generated * successfully */ public void buildClassifier(Instances instances) throws Exception { // Check that class is nominal if (!instances.classAttribute().isNominal()) { throw new UnsupportedClassTypeException("BayesNet: nominal class, please."); } // check that all variables are nominal and that there // are no missing values Enumeration enum = instances.enumerateAttributes(); while (enum.hasMoreElements()) { Attribute attribute = (Attribute) enum.nextElement(); if (attribute.type() != Attribute.NOMINAL) { throw new UnsupportedAttributeTypeException("BayesNet handles nominal variables only. Non-nominal variable in dataset detected."); } Enumeration enum2 = instances.enumerateInstances(); while (enum2.hasMoreElements()) { if (((Instance) enum2.nextElement()).isMissing(attribute)) { throw new NoSupportForMissingValuesException("BayesNet: no missing values, please."); } } } // Copy the instances m_Instances = new Instances(instances); // sanity check: need more than 1 variable in datat set m_NumClasses = instances.numClasses(); // initialize ADTree if (m_bUseADTree) { m_ADTree = ADNode.MakeADTree(instances); // System.out.println("Oef, done!"); } // build the network structure initStructure(); // build the network structure buildStructure(); // build the set of CPTs estimateCPTs(); // Save space // m_Instances = new Instances(m_Instances, 0); m_ADTree = null; } // buildClassifier /** * Init structure initializes the structure to an empty graph or a Naive Bayes * graph (depending on the -N flag). */ public void initStructure() throws Exception { // initialize topological ordering m_nOrder = new int[m_Instances.numAttributes()]; m_nOrder[0] = m_Instances.classIndex(); int nAttribute = 0; for (int iOrder = 1; iOrder < m_Instances.numAttributes(); iOrder++) { if (nAttribute == m_Instances.classIndex()) { nAttribute++; } m_nOrder[iOrder] = nAttribute++; } // reserce memory m_ParentSets = new ParentSet[m_Instances.numAttributes()]; for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { m_ParentSets[iAttribute] = new ParentSet(m_Instances.numAttributes()); } if (m_bInitAsNaiveBayes) { // initialize parent sets to have arrow from classifier node to // each of the other nodes for (int iOrder = 1; iOrder < m_Instances.numAttributes(); iOrder++) { int iAttribute = m_nOrder[iOrder]; m_ParentSets[iAttribute].AddParent(m_Instances.classIndex(), m_Instances); } } } /** * buildStructure determines the network structure/graph of the network. * The default behavior is creating a network where all nodes have the first * node as its parent (i.e., a BayesNet that behaves like a naive Bayes classifier). * This method can be overridden by derived classes to restrict the class * of network structures that are acceptable. */ public void buildStructure() throws Exception { // place holder for structure learing algorithms like K2, B, etc. } // buildStructure /** * estimateCPTs estimates the conditional probability tables for the Bayes * Net using the network structure. */ public void estimateCPTs() throws Exception { // Reserve space for CPTs int nMaxParentCardinality = 1; for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { if (m_ParentSets[iAttribute].GetCardinalityOfParents() > nMaxParentCardinality) { nMaxParentCardinality = m_ParentSets[iAttribute].GetCardinalityOfParents(); } } // Reserve plenty of memory m_Distributions = new Estimator[m_Instances.numAttributes()][nMaxParentCardinality]; // estimate CPTs for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetCardinalityOfParents(); iParent++) { m_Distributions[iAttribute][iParent] = new DiscreteEstimatorBayes(m_Instances.attribute(iAttribute) .numValues(), m_fAlpha); } } // Compute counts Enumeration enumInsts = m_Instances.enumerateInstances(); while (enumInsts.hasMoreElements()) { Instance instance = (Instance) enumInsts.nextElement(); updateClassifier(instance); } } // estimateCPTs /** * Updates the classifier with the given instance. * * @param instance the new training instance to include in the model * @exception Exception if the instance could not be incorporated in * the model. */ public void updateClassifier(Instance instance) throws Exception { for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { double iCPT = 0; for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetNrOfParents(); iParent++) { int nParent = m_ParentSets[iAttribute].GetParent(iParent); iCPT = iCPT * m_Instances.attribute(nParent).numValues() + instance.value(nParent); } m_Distributions[iAttribute][(int) iCPT] .addValue(instance.value(iAttribute), instance.weight()); } } // updateClassifier /** * Calculates the class membership probabilities for the given test * instance. * * @param instance the instance to be classified * @return predicted class probability distribution * @exception Exception if there is a problem generating the prediction */ public double[] distributionForInstance(Instance instance) throws Exception { double[] fProbs = new double[m_NumClasses]; for (int iClass = 0; iClass < m_NumClasses; iClass++) { fProbs[iClass] = 1.0; } for (int iClass = 0; iClass < m_NumClasses; iClass++) { double logfP = 0; for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { double iCPT = 0; for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetNrOfParents(); iParent++) { int nParent = m_ParentSets[iAttribute].GetParent(iParent); if (nParent == m_Instances.classIndex()) { iCPT = iCPT * m_NumClasses + iClass; } else { iCPT = iCPT * m_Instances.attribute(nParent).numValues() + instance.value(nParent); } } if (iAttribute == m_Instances.classIndex()) { // fP *= // m_Distributions[iAttribute][(int) iCPT].getProbability(iClass); logfP += Math.log(m_Distributions[iAttribute][(int) iCPT].getProbability(iClass)); } else { // fP *= // m_Distributions[iAttribute][(int) iCPT] // .getProbability(instance.value(iAttribute)); logfP += Math.log(m_Distributions[iAttribute][(int) iCPT] .getProbability(instance.value(iAttribute))); } } // fProbs[iClass] *= fP; fProbs[iClass] += logfP; } // Find maximum double fMax = fProbs[0]; for (int iClass = 0; iClass < m_NumClasses; iClass++) { if (fProbs[iClass] > fMax) { fMax = fProbs[iClass]; } } // transform from log-space to normal-space for (int iClass = 0; iClass < m_NumClasses; iClass++) { fProbs[iClass] = Math.exp(fProbs[iClass] - fMax); } // Display probabilities Utils.normalize(fProbs); return fProbs; } // distributionForInstance /** * Calculates the counts for Dirichlet distribution for the * class membership probabilities for the given test instance. * * @param instance the instance to be classified * @return counts for Dirichlet distribution for class probability * @exception Exception if there is a problem generating the prediction */ public double[] countsForInstance(Instance instance) throws Exception { double[] fCounts = new double[m_NumClasses]; for (int iClass = 0; iClass < m_NumClasses; iClass++) { fCounts[iClass] = 0.0; } for (int iClass = 0; iClass < m_NumClasses; iClass++) { double fCount = 0; for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { double iCPT = 0; for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetNrOfParents(); iParent++) { int nParent = m_ParentSets[iAttribute].GetParent(iParent); if (nParent == m_Instances.classIndex()) { iCPT = iCPT * m_NumClasses + iClass; } else { iCPT = iCPT * m_Instances.attribute(nParent).numValues() + instance.value(nParent); } } if (iAttribute == m_Instances.classIndex()) { fCount += ((DiscreteEstimatorBayes)m_Distributions[iAttribute][(int) iCPT]).getCount(iClass); } else { fCount += ((DiscreteEstimatorBayes)m_Distributions[iAttribute][(int) iCPT]).getCount(instance.value(iAttribute)); } } fCounts[iClass] += fCount; } return fCounts; } // countsForInstance /** * Returns an enumeration describing the available options * * @return an enumeration of all the available options */ public Enumeration listOptions() { Vector newVector = new Vector(5); newVector .addElement(new Option("\tScore type (BAYES, MDL, ENTROPY, or AIC)\n", "S", 1, "-S [BAYES|MDL|ENTROPY|AIC]")); newVector.addElement(new Option("\tInitial count (alpha)\n", "A", 1, "-A <alpha>")); newVector .addElement(new Option("\tInitial structure is empty (instead of Naive Bayes)\n", "N", 0, "-N")); newVector .addElement(new Option("\tUse ADTree data structure\n", "D", 0, "-D")); newVector.addElement(new Option("\tMaximum number of parents\n", "P", 1, "-P <nr of parents>")); return newVector.elements(); } // listOptions /** * Parses a given list of options. Valid options are:<p> * * @param options the list of options as an array of strings * @exception Exception if an option is not supported */ public void setOptions(String[] options) throws Exception { m_bInitAsNaiveBayes = !(Utils.getFlag('N', options)); m_bUseADTree = !(Utils.getFlag('D', options)); String sScore = Utils.getOption('S', options); if (sScore.compareTo("BAYES") == 0) { // m_nScoreType = Scoreable.BAYES; setScoreType(new SelectedTag(Scoreable.BAYES, TAGS_SCORE_TYPE)); } if (sScore.compareTo("MDL") == 0) { // m_nScoreType = Scoreable.MDL; setScoreType(new SelectedTag(Scoreable.MDL, TAGS_SCORE_TYPE)); } if (sScore.compareTo("ENTROPY") == 0) { // m_nScoreType = Scoreable.ENTROPY; setScoreType(new SelectedTag(Scoreable.ENTROPY, TAGS_SCORE_TYPE)); } if (sScore.compareTo("AIC") == 0) { // m_nScoreType = Scoreable.AIC; setScoreType(new SelectedTag(Scoreable.AIC, TAGS_SCORE_TYPE)); } String sAlpha = Utils.getOption('A', options); if (sAlpha.length() != 0) { m_fAlpha = (new Float(sAlpha)).floatValue(); } else { m_fAlpha = 0.5f; } String sMaxNrOfParents = Utils.getOption('P', options); if (sMaxNrOfParents.length() != 0) { setMaxNrOfParents(Integer.parseInt(sMaxNrOfParents)); } else { setMaxNrOfParents(100000); } Utils.checkForRemainingOptions(options); } // setOptions /** * Method declaration * * @param scoreType * */ public void setScoreType(SelectedTag newScoreType) { if (newScoreType.getTags() == TAGS_SCORE_TYPE) { m_nScoreType = newScoreType.getSelectedTag().getID(); } } /** * Method declaration * * @return * */ public SelectedTag getScoreType() { return new SelectedTag(m_nScoreType, TAGS_SCORE_TYPE); } /** * Method declaration * * @param fAlpha * */ public void setAlpha(double fAlpha) { m_fAlpha = fAlpha; } /** * Method declaration * * @return * */ public double getAlpha() { return m_fAlpha; } /** * Method declaration * * @param bInitAsNaiveBayes * */ public void setInitAsNaiveBayes(boolean bInitAsNaiveBayes) { m_bInitAsNaiveBayes = bInitAsNaiveBayes; } /** * Method declaration * * @return * */ public boolean getInitAsNaiveBayes() { return m_bInitAsNaiveBayes; } /** * Method declaration * * @param bUseADTree * */ public void setUseADTree(boolean bUseADTree) { m_bUseADTree = bUseADTree; } /** * Method declaration * * @return * */ public boolean getUseADTree() { return m_bUseADTree; } /** * Method declaration * * @param nMaxNrOfParents * */ public void setMaxNrOfParents(int nMaxNrOfParents) { m_nMaxNrOfParents = nMaxNrOfParents; } /** * Method declaration * * @return * */ public int getMaxNrOfParents() { return m_nMaxNrOfParents; } /** * Gets the current settings of the classifier. * * @return an array of strings suitable for passing to setOptions */ public String[] getOptions() { String[] options = new String[8]; int current = 0; options[current++] = "-S"; switch (m_nScoreType) { case (Scoreable.BAYES): options[current++] = "BAYES"; break; case (Scoreable.MDL): options[current++] = "MDL"; break; case (Scoreable.ENTROPY): options[current++] = "ENTROPY"; break; case (Scoreable.AIC): options[current++] = "AIC"; break; } options[current++] = "-A"; options[current++] = "" + m_fAlpha; if (!m_bInitAsNaiveBayes) { options[current++] = "-N"; } if (!m_bUseADTree) { options[current++] = "-D"; } if (m_nMaxNrOfParents != 10000) { options[current++] = "-P"; options[current++] = "" + m_nMaxNrOfParents; } // Fill up rest with empty strings, not nulls! while (current < options.length) { options[current++] = ""; } return options; } // getOptions /** * logScore returns the log of the quality of a network * (e.g. the posterior probability of the network, or the MDL * value). * @param nType score type (Bayes, MDL, etc) to calculate score with * @return log score. */ public double logScore(int nType) { if (nType < 0) { nType = m_nScoreType; } double fLogScore = 0.0; for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetCardinalityOfParents(); iParent++) { fLogScore += ((Scoreable) m_Distributions[iAttribute][iParent]).logScore(nType); } switch (nType) { case (Scoreable.MDL): { fLogScore -= 0.5 * m_ParentSets[iAttribute].GetCardinalityOfParents() * (m_Instances.attribute(iAttribute).numValues() - 1) * Math.log(m_Instances.numInstances()); } break; case (Scoreable.AIC): { fLogScore -= m_ParentSets[iAttribute].GetCardinalityOfParents() * (m_Instances.attribute(iAttribute).numValues() - 1); } break; } } return fLogScore; } // logScore /** * Returns a description of the classifier. * * @return a description of the classifier as a string. */ public String toString() { StringBuffer text = new StringBuffer(); text.append("Bayes Network Classifier"); text.append("\n" + (m_bUseADTree ? "Using " : "not using ") + "ADTree"); if (m_Instances == null) { text.append(": No model built yet."); } else { // TODO: flatten BayesNet down to text text.append("\n#attributes="); text.append(m_Instances.numAttributes()); text.append(" #classindex="); text.append(m_Instances.classIndex()); text.append("\nNetwork structure (nodes followed by parents)\n"); for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) { text.append(m_Instances.attribute(iAttribute).name() + "(" + m_Instances.attribute(iAttribute).numValues() + "): "); for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetNrOfParents(); iParent++) { text .append(m_Instances.attribute(m_ParentSets[iAttribute].GetParent(iParent)).name() + " "); } text.append("\n"); // Description of distributions tends to be too much detail, so it is commented out here // for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetCardinalityOfParents(); iParent++) { // text.append('(' + m_Distributions[iAttribute][iParent].toString() + ')'); // } // text.append("\n"); } text.append("LogScore Bayes: " + logScore(Scoreable.BAYES) + "\n"); text.append("LogScore MDL: " + logScore(Scoreable.MDL) + "\n"); text.append("LogScore ENTROPY: " + logScore(Scoreable.ENTROPY) + "\n"); text.append("LogScore AIC: " + logScore(Scoreable.AIC) + "\n"); String[] options = getOptions(); for (int iOption = 0; iOption < 1; iOption++) { text.append(options[iOption]); } } return text.toString(); } // toString /** * Calc Node Score With AddedParent * * @param nNode node for which the score is calculate * @param nCandidateParent candidate parent to add to the existing parent set * @return log score */ protected double CalcScoreWithExtraParent(int nNode, int nCandidateParent) { // sanity check: nCandidateParent should not be in parent set already for (int iParent = 0; iParent < m_ParentSets[nNode].GetNrOfParents(); iParent++) { if (m_ParentSets[nNode].GetParent(iParent) == nCandidateParent) { return -1e100; } } // determine cardinality of parent set & reserve space for frequency counts int nCardinality = m_ParentSets[nNode].GetCardinalityOfParents() * m_Instances.attribute(nCandidateParent).numValues(); int numValues = m_Instances.attribute(nNode).numValues(); int[][] nCounts = new int[nCardinality][numValues]; // set up candidate parent m_ParentSets[nNode].AddParent(nCandidateParent, m_Instances); // calculate the score double logScore = CalcNodeScore(nNode); // delete temporarily added parent m_ParentSets[nNode].DeleteLastParent(m_Instances); return logScore; } // CalcScore /** * Calc Node Score for given parent set * * @param nNode node for which the score is calculate * @return log score */ protected double CalcNodeScore(int nNode) { if (m_bUseADTree && m_ADTree != null) { return CalcNodeScoreADTree(nNode, m_Instances); } else { return CalcNodeScore(nNode, m_Instances); } } /** * helper function for CalcNodeScore above using the ADTree data structure * @param nNode node for which the score is calculate * @param instances used to calculate score with * @return log score */ private double CalcNodeScoreADTree(int nNode, Instances instances) { // get set of parents, insert iNode int nNrOfParents = m_ParentSets[nNode].GetNrOfParents(); int [] nNodes = new int [nNrOfParents + 1]; for (int iParent = 0; iParent < nNrOfParents; iParent++) { nNodes[iParent] = m_ParentSets[nNode].GetParent(iParent); } nNodes[nNrOfParents] = nNode; // calculate offsets int [] nOffsets = new int [nNrOfParents + 1]; int nOffset = 1; nOffsets[nNrOfParents] = 1; nOffset *= instances.attribute(nNode).numValues(); for (int iNode = nNrOfParents - 1; iNode >=0; iNode--) { nOffsets[iNode] = nOffset; nOffset *= instances.attribute(nNodes[iNode]).numValues(); } // sort nNodes & offsets for (int iNode = 1; iNode < nNodes.length; iNode++) { int iNode2 = iNode; while (iNode2 > 0 && nNodes[iNode2] < nNodes[iNode2 - 1]) { int h = nNodes[iNode2]; nNodes[iNode2] = nNodes[iNode2 - 1]; nNodes[iNode2 - 1] = h; h = nOffsets[iNode2]; nOffsets[iNode2] = nOffsets[iNode2 - 1]; nOffsets[iNode2 - 1] = h; iNode2--; } } // get counts from ADTree int nCardinality = m_ParentSets[nNode].GetCardinalityOfParents(); int numValues = instances.attribute(nNode).numValues(); int [] nCounts = new int[nCardinality * numValues]; //if (nNrOfParents > 1) { /* System.out.println("==========================="); for (int iNode = 0; iNode < nNodes.length; iNode++) { System.out.print(nNodes[iNode] + " " + nOffsets[iNode] + ": "); } System.out.println(); */ // int i = 3; //} //CalcNodeScore2(nNode, instances); m_ADTree.getCounts(nCounts, nNodes, nOffsets, 0, 0, false); // for (int iNode = 0; iNode < nCounts.length; iNode++) { // System.out.print(nCounts[iNode] + " "); // } // System.out.println(); return CalcScoreOfCounts(nCounts, nCardinality, numValues, instances); } // CalcNodeScore private double CalcNodeScore(int nNode, Instances instances) { // determine cardinality of parent set & reserve space for frequency counts int nCardinality = m_ParentSets[nNode].GetCardinalityOfParents(); int numValues = instances.attribute(nNode).numValues(); int[][] nCounts = new int[nCardinality][numValues]; // initialize (don't need this?) for (int iParent = 0; iParent < nCardinality; iParent++) { for (int iValue = 0; iValue < numValues; iValue++) { nCounts[iParent][iValue] = 0; } } // estimate distributions Enumeration enumInsts = instances.enumerateInstances(); while (enumInsts.hasMoreElements()) { Instance instance = (Instance) enumInsts.nextElement(); // updateClassifier; double iCPT = 0; for (int iParent = 0; iParent < m_ParentSets[nNode].GetNrOfParents(); iParent++) { int nParent = m_ParentSets[nNode].GetParent(iParent); iCPT = iCPT * instances.attribute(nParent).numValues() + instance.value(nParent); } nCounts[(int) iCPT][(int) instance.value(nNode)]++; } /* System.out.print("Counts:"); for (int iNode = 0; iNode < nCardinality; iNode++) { for (int iNode2 = 0; iNode2 < numValues; iNode2++) { System.out.print(nCounts[iNode][iNode2] + " "); } } System.out.println(); */ return CalcScoreOfCounts2(nCounts, nCardinality, numValues, instances); } // CalcNodeScore /** * utility function used by CalcScore and CalcNodeScore to determine the score * based on observed frequencies. * * @param nCounts array with observed frequencies * @param nCardinality ardinality of parent set * @param numValues number of values a node can take * @param instances to calc score with * @return log score */ protected double CalcScoreOfCounts(int [] nCounts, int nCardinality, int numValues, Instances instances) { // calculate scores using the distributions double fLogScore = 0.0; for (int iParent = 0; iParent < nCardinality; iParent++) { switch (m_nScoreType) { case (Scoreable.BAYES): { double nSumOfCounts = 0; for (int iSymbol = 0; iSymbol < numValues; iSymbol++) { if (m_fAlpha + nCounts[iParent * numValues + iSymbol] != 0) { fLogScore += Statistics.lnGamma(m_fAlpha + nCounts[iParent * numValues + iSymbol]); nSumOfCounts += m_fAlpha + nCounts[iParent * numValues + iSymbol]; } } if (nSumOfCounts != 0) { fLogScore -= Statistics.lnGamma(nSumOfCounts); } if (m_fAlpha != 0) { fLogScore -= numValues * Statistics.lnGamma(m_fAlpha); fLogScore += Statistics.lnGamma(numValues * m_fAlpha); } } break; case (Scoreable.MDL): case (Scoreable.AIC): case (Scoreable.ENTROPY): { double nSumOfCounts = 0; for (int iSymbol = 0; iSymbol < numValues; iSymbol++) { nSumOfCounts += nCounts[iParent * numValues + iSymbol]; } for (int iSymbol = 0; iSymbol < numValues; iSymbol++) { if (nCounts[iParent * numValues + iSymbol] > 0) { fLogScore += nCounts[iParent * numValues + iSymbol] * Math.log(nCounts[iParent * numValues + iSymbol] / nSumOfCounts); } } } break; default: {} } } switch (m_nScoreType) { case (Scoreable.MDL): { fLogScore -= 0.5 * nCardinality * (numValues - 1) * Math.log(instances.numInstances()); // it seems safe to assume that numInstances>0 here } break; case (Scoreable.AIC): { fLogScore -= nCardinality * (numValues - 1); } break; } return fLogScore; } // CalcNodeScore protected double CalcScoreOfCounts2(int[][] nCounts, int nCardinality, int numValues, Instances instances) { // calculate scores using the distributions double fLogScore = 0.0; for (int iParent = 0; iParent < nCardinality; iParent++) { switch (m_nScoreType) { case (Scoreable.BAYES): { double nSumOfCounts = 0; for (int iSymbol = 0; iSymbol < numValues; iSymbol++) { if (m_fAlpha + nCounts[iParent][iSymbol] != 0) { fLogScore += Statistics.lnGamma(m_fAlpha + nCounts[iParent][iSymbol]); nSumOfCounts += m_fAlpha + nCounts[iParent][iSymbol]; } } if (nSumOfCounts != 0) { fLogScore -= Statistics.lnGamma(nSumOfCounts); } if (m_fAlpha != 0) { fLogScore -= numValues * Statistics.lnGamma(m_fAlpha); fLogScore += Statistics.lnGamma(numValues * m_fAlpha); } } break; case (Scoreable.MDL): case (Scoreable.AIC): case (Scoreable.ENTROPY): { double nSumOfCounts = 0; for (int iSymbol = 0; iSymbol < numValues; iSymbol++) { nSumOfCounts += nCounts[iParent][iSymbol]; } for (int iSymbol = 0; iSymbol < numValues; iSymbol++) { if (nCounts[iParent][iSymbol] > 0) { fLogScore += nCounts[iParent][iSymbol] * Math.log(nCounts[iParent][iSymbol] / nSumOfCounts); } } } break; default: {} } } switch (m_nScoreType) { case (Scoreable.MDL): { fLogScore -= 0.5 * nCardinality * (numValues - 1) * Math.log(instances.numInstances()); // it seems safe to assume that numInstances>0 here } break; case (Scoreable.AIC): { fLogScore -= nCardinality * (numValues - 1); } break; } return fLogScore; } // CalcNodeScore /** * @return a string to describe the ScoreType option. */ public String scoreTypeTipText() { return "The score type determines the measure used to judge the quality of a" + " network structure. It can be one of Bayes, Minimum Description Length (MDL)," + " Akaike Information Criterion (AIC), and Entropy."; } /** * @return a string to describe the Alpha option. */ public String alphaTipText() { return "Alpha is used for estimating the probability tables and can be interpreted" + " as the initial count on each value."; } /** * @return a string to describe the InitAsNaiveBayes option. */ public String initAsNaiveBayesTipText() { return "When set to true (default), the initial network used for structure learning" + " is a Naive Bayes Network, that is, a network with an arrow from the classifier" + " node to each other node. When set to false, an empty network is used as initial"+ " network structure"; } /** * @return a string to describe the UseADTreeoption. */ public String useADTreeTipText() { return "When ADTree (the data structure for increasing speed on counts," + " not to be confused with the classifier under the same name) is used" + " learning time goes down typically. However, because ADTrees are memory" + " intensive, memory problems may occur. Switching this option off makes" + " the structure learning algorithms slower, and run with less memory." + " By default, ADTrees are used."; } /** * @return a string to describe the MaxNrOfParentsoption. */ public String maxNrOfParentsTipText() { return "Set the maximum number of parents a node in the Bayes net can have." + " When initialized as Naive Bayes, setting this parameter to 1 results in" + " a Naive Bayes classifier. When set to 2, a Tree Augmented Bayes Network (TAN)" + " is learned, and when set >2, a Bayes Net Augmented Bayes Network (BAN)" + " is learned. By setting it to a value much larger than the number of nodes" + " in the network (the default of 100000 pretty much guarantees this), no" + " restriction on the number of parents is enforced"; } /** * Main method for testing this class. * * @param argv the options */ public static void main(String[] argv) { try { System.out.println(Evaluation.evaluateModel(new BayesNet(), argv)); } catch (Exception e) { e.printStackTrace(); System.err.println(e.getMessage()); } } // main } // class BayesNet