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);
}
}
}