private PartitionScore SplitOnAxis(ContinuousAxis axis, List <Episode> episodes, double[] totalWeights) { List <EvaluatedEpisode> line = episodes .AsParallel() .Select(episode => EvaluateEpisode(episode, axis)) .OrderBy(x => x.Value) .ToList(); int lastIndex = 0; double[] tacticWeightsLeft = new double[NumTactics]; int bestSplitPosition = -1; double bestEntropy = double.MaxValue; for (int i = 1; i < line.Count; ++i) { bool sameAsPrevious = i > 0 && line[i - 1].Value == line[i].Value; if (sameAsPrevious) { // Cannot create a split part way through the same number continue; } while (lastIndex < i) { Episode episode = line[lastIndex++].Episode; tacticWeightsLeft[(int)episode.Tactic] += episode.Weight; } double[] tacticWeightsRight = TacticEntropy.Subtract(totalWeights, tacticWeightsLeft); double entropy = TacticEntropy.Entropy(tacticWeightsLeft, tacticWeightsRight); if (entropy < bestEntropy) { bestEntropy = entropy; bestSplitPosition = i; } } if (bestSplitPosition == -1) { // Unable to find split - all values must be the same return(null); } return(new PartitionScore { Partitioner = new ContinuousPartitioner(axis, line[bestSplitPosition].Value), LeftEpisodes = line.Take(bestSplitPosition).Select(x => x.Episode).ToList(), RightEpisodes = line.Skip(bestSplitPosition).Select(x => x.Episode).ToList(), Entropy = bestEntropy, }); }
private DecisionNodeAccuracy OptimalLeaf(List <Episode> episodes, double[] tacticWeights) { TacticWeight bestTacticOverall = tacticWeights .Select((weight, i) => new TacticWeight { Tactic = (Tactic)i, Weight = weight }) .MaxByOrDefault(x => x.Weight); if (!IsSpell(bestTacticOverall.Tactic)) { return(new DecisionNodeAccuracy { DecisionNode = new DecisionLeaf(bestTacticOverall.Tactic), CorrectWeight = bestTacticOverall.Weight, }); } Dictionary <HeroType, Tactic> bestTacticPerHero = new Dictionary <HeroType, Tactic>(); double totalCorrectWeight = 0.0; foreach (var group in episodes.GroupBy(ep => ep.Hero.HeroType)) { TacticWeight bestTacticForHero = TacticEntropy.TacticFrequency(group) .Select((weight, i) => new TacticWeight { Tactic = (Tactic)i, Weight = weight }) .MaxByOrDefault(x => x.Weight); bestTacticPerHero[group.Key] = bestTacticForHero.Tactic; totalCorrectWeight += bestTacticForHero.Weight; } foreach (HeroType heroType in EnumUtils.GetEnumValues <HeroType>()) { if (bestTacticPerHero.GetOrDefault(heroType) == bestTacticOverall.Tactic) { bestTacticPerHero.Remove(heroType); } } return(new DecisionNodeAccuracy { DecisionNode = new DecisionLeaf(bestTacticOverall.Tactic, bestTacticPerHero), CorrectWeight = totalCorrectWeight, }); }
private static AdditionalCategory EvaluateAdditionalCategory(Enum category, IEnumerable <Episode> additionalEpisodes, double[] totalWeights, double[] rightWeights) { double[] additionalWeights = TacticEntropy.TacticFrequency(additionalEpisodes); double[] newRightWeights = new double[TacticEntropy.NumTactics]; double[] newLeftWeights = new double[TacticEntropy.NumTactics]; for (int i = 0; i < additionalWeights.Length; ++i) { newRightWeights[i] = rightWeights[i] + additionalWeights[i]; newLeftWeights[i] = totalWeights[i] - newRightWeights[i]; } return(new AdditionalCategory { Category = category, AdditionalEpisodes = additionalEpisodes, NewRightWeights = newRightWeights, Entropy = TacticEntropy.Entropy(newLeftWeights, newRightWeights), }); }
private async Task <DecisionNodeAccuracy> OptimalDecisionNode(List <Episode> episodes, double grandTotalWeight) { double[] tacticWeights = TacticEntropy.TacticFrequency(episodes); double totalWeight = tacticWeights.Sum(); double leafEntropy = TacticEntropy.Entropy(tacticWeights); DecisionNodeAccuracy bestLeaf = OptimalLeaf(episodes, tacticWeights); if (bestLeaf.CorrectWeight + double.Epsilon >= totalWeight) { // We already know the perfect answer return(bestLeaf); } double maximumCorrectWeight = totalWeight; double maximumIncrease = maximumCorrectWeight - bestLeaf.CorrectWeight; double requiredIncrease = RequiredIncreaseToSplit * grandTotalWeight; if (maximumIncrease < requiredIncrease) { // Cannot make a big enough difference to the big picture return(bestLeaf); } PartitionScore partitionScore = _continuousLearner.Optimizers(episodes, tacticWeights) .Concat(_categoricalLearner.Optimizers(episodes, tacticWeights)) .AsParallel() .Select(partitionEvaluator => partitionEvaluator()) .Where(x => x != null && x.LeftEpisodes.Count > 0 && x.RightEpisodes.Count > 0) .MinByOrDefault(x => x.Entropy); if (partitionScore == null || partitionScore.Entropy >= leafEntropy) { if (partitionScore?.Entropy > leafEntropy + double.Epsilon) { Console.WriteLine("Entropy should never increase with a split"); } // No split found return(bestLeaf); } DecisionNodeAccuracy left = await OptimalDecisionNode(partitionScore.LeftEpisodes, grandTotalWeight); DecisionNodeAccuracy right = await OptimalDecisionNode(partitionScore.RightEpisodes, grandTotalWeight); DecisionNode splitter = new DecisionNode() { Partitioner = partitionScore.Partitioner, Left = left.DecisionNode, Right = right.DecisionNode, }; double bestSplitCorrectWeight = left.CorrectWeight + right.CorrectWeight; double correctWeightIncrease = bestSplitCorrectWeight - bestLeaf.CorrectWeight; if (correctWeightIncrease < requiredIncrease) { return(bestLeaf); } else { return(new DecisionNodeAccuracy { DecisionNode = splitter, CorrectWeight = bestSplitCorrectWeight, }); } }