using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Linq; namespace BotSharp.Algorithm.Bayesian { /// /// Class NaiveClassifier. This class cannot be inherited. /// /// Assumes that all token occurrences are statistically independent. /// /// public sealed class NaiveClassifier : IClassifier { /// /// The training sets /// private readonly ITrainingSetAccessor _trainingSets; /// /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied. /// private double _smoothingAlpha = 0.01D; /// /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied. /// [DefaultValue(0.01D)] public double SmoothingAlpha { get { return _smoothingAlpha; } set { if (value <= 0) throw new ArgumentOutOfRangeException("value", value, "Value must be greater than zero."); _smoothingAlpha = value; } } /// /// Initializes a new instance of the class. /// /// The training sets. /// trainingSets public NaiveClassifier(ITrainingSetAccessor trainingSets) { if (ReferenceEquals(trainingSets, null)) throw new ArgumentNullException("trainingSets"); _trainingSets = trainingSets; } /// /// Calculates the probability of having the /// given the occurrence of the . /// /// The class under test. /// The token. /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied. /// System.Double. public double CalculateProbability(IClass classUnderTest, IToken token, double? alpha = null) { var smoothingAlpha = alpha ?? _smoothingAlpha; ICollection remainingSets; var setForClassUnderTest = SplitDataSets(classUnderTest, out remainingSets); // calculate the token's probability in the class under test var percentageInClassUnderTest = setForClassUnderTest.GetPercentage(token, smoothingAlpha); var probabilityInClassUnderTest = percentageInClassUnderTest * classUnderTest.Probability; // calculate the token's probabilities for the remaining classes double sumOfRemainingProbabilites; CalculateTokenProbabilityGivenClass(token, remainingSets, out sumOfRemainingProbabilites, smoothingAlpha).Run(); // calculate total probability var totalProbability = probabilityInClassUnderTest + sumOfRemainingProbabilites; // calculate the class' probability given the token var probabilityForClass = probabilityInClassUnderTest/totalProbability; // correct for rare words return probabilityForClass; } /// /// Calculates the probability of having the /// /// given the occurrence of the /// . /// /// The token. /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied. /// System.Double. public IEnumerable CalculateProbabilities(IToken token, double? alpha = null) { var smoothingAlpha = alpha ?? _smoothingAlpha; // calculate the token's probabilities for all classes double totalProbability; var probabilities = CalculateTokenProbabilityGivenClass(token, _trainingSets, out totalProbability, smoothingAlpha); // apply Bayes theorem var inverseOfTotalProbability = 1.0D/totalProbability; return from cp in probabilities let conditionalProbability = cp.Probability * inverseOfTotalProbability select new ConditionalProbability(cp.Class, cp.Token, conditionalProbability, cp.Occurrence); } /// /// Calculates the probability of having the /// /// given the occurrence of the /// . /// /// The tokens. /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied. /// System.Double. public IEnumerable CalculateProbabilities(ICollection tokens, double? alpha = null) { var smoothingAlpha = alpha ?? _smoothingAlpha; var cpgs = tokens .SelectMany(token => CalculateProbabilities(token, smoothingAlpha)) .GroupBy(cp => cp.Class) .ToCollection(); return from @group in cpgs let cps = @group.ToCollection() let eta = cps.Select(cp => cp.Probability) .Sum(p => Math.Log(1 - p) - Math.Log(p)) let probability = 1/(1 + Math.Exp(eta)) select new CombinedConditionalProbability(@group.Key, probability, cps); } /// /// Calculates the token probabilities given a class. /// /// The token. /// The sets. /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied. /// IEnumerable<ConditionalProbability<IClass, IToken>>. private IEnumerable CalculateTokenProbabilityGivenClass(IToken token, IEnumerable sets, double alpha) { return from set in sets let @class = set.Class let classProbability = @class.Probability let percentageInClass = set.GetPercentage(token, alpha) let countInClass = set.GetCount(token) let probabilityInClass = percentageInClass*classProbability select new ConditionalProbability(@class, token, probabilityInClass, countInClass); } /// /// Calculates the token probabilities given a class. /// /// The token. /// The sets. /// Additive smoothing parameter. If set to zero, no Laplace smoothing will be applied. /// The total probability for the given classes. /// IEnumerable<ConditionalProbability<IClass, IToken>>. private IEnumerable CalculateTokenProbabilityGivenClass(IToken token, IEnumerable sets, out double totalProbability, double alpha) { var probabilities = CalculateTokenProbabilityGivenClass(token, sets, alpha).ToCollection(); totalProbability = probabilities.Sum(p => p.Probability); return probabilities; } /// /// Splits the data sets. /// /// The class under test. /// The remaining sets. /// IDataSet<IClass, IToken>. private IDataSetAccessor SplitDataSets(IClass classUnderTest, out ICollection remainingSets) { IDataSet setForClassUnderTest = null; remainingSets = new Collection(); // split data sets by selected class and other classes foreach (var trainingSet in _trainingSets) { // select the set for the class under test if (trainingSet.Class.Equals(classUnderTest)) { Debug.Assert(setForClassUnderTest == null, "The class under test must not have multiple sets registered in the DataSet"); setForClassUnderTest = trainingSet; continue; } // select remaining sets remainingSets.Add(trainingSet); } // return the found set or an empty set return setForClassUnderTest ?? new EmptyDataSet(classUnderTest); } } }