/// <summary> /// This version starts with a SOM, then potentially splits the largest node and/or gathers the smallest nodes into a single /// </summary> /// <returns></returns> public static SOMResult Train(ISOMInput[] inputs, SOMRules rules, bool isDisplay2D) { SOMResult result = SelfOrganizingMaps.TrainSOM(inputs, rules, isDisplay2D); if (result.Nodes.Length == 0) { return(result); } else if (result.Nodes.Length == 1) { #region kmeans single node if (inputs.Length < 20) { return(result); } return(SelfOrganizingMaps.TrainKMeans(inputs, 5, true)); #endregion } var categorized = GetSOM_SplitMerge_Categorize(result); List <SOMNode> nodes = new List <SOMNode>(); List <ISOMInput[]> newInputs = new List <ISOMInput[]>(); foreach (NodeCombo set in UtilityCore.Iterate(categorized.kmeans, categorized.keep)) // UtilityCore.Iterate gracefully skips nulls { nodes.Add(set.Node); newInputs.Add(set.Inputs); } if (categorized.remaining != null) { nodes.Add(new SOMNode() { Position = MathND.GetCenter(categorized.remaining.Select(o => o.Node.Position)), Weights = MathND.GetCenter(categorized.remaining.Select(o => o.Node.Weights)), }); newInputs.Add(categorized.remaining. SelectMany(o => o.Inputs). ToArray()); } return(new SOMResult(nodes.ToArray(), newInputs.ToArray(), false)); }
/// <summary> /// This overload does an initial training, then recurses on any node that has too wide of a range of values /// </summary> /// <remarks> /// This method is a bit of a failure. Sometimes it works, but other times it just runs without fixing anything /// </remarks> /// <param name="maxSpreadPercent"> /// Spread is an input's distance from the center of all inputs. The percent is a node's max distance divided by all node's max distance. /// .65 to .75 is a good value to use (smaller values will chop up into more nodes) /// </param> public static SOMResult TrainSOM(ISOMInput[] inputs, SOMRules rules, double maxSpreadPercent, bool isDisplay2D, bool returnEmptyNodes = false) { const int MININPUTSFORSPLIT = 4; // Get the initial result SOMResult result = TrainSOM(inputs, rules, isDisplay2D, returnEmptyNodes); #region Divide large nodes double totalSpread = GetTotalSpread(inputs.Select(o => o.Weights)); int infiniteLoop = 0; while (infiniteLoop < 50) // if it exceeds this, just use whatever is there { // Split up nodes that have too much variation (image's distance from average) var reduced = Enumerable.Range(0, result.Nodes.Length). AsParallel(). Select(o => SplitNode(o, result, MININPUTSFORSPLIT, maxSpreadPercent, totalSpread, rules)). ToArray(); if (reduced.All(o => !o.Item1)) { // No changes were needed this pass break; } SOMNode[] reducedNodes = reduced. SelectMany(o => o.Item2). ToArray(); // Rebuild result ISOMInput[][] imagesByNode = SelfOrganizingMaps.GetInputsByNode(reducedNodes, inputs); result = new SOMResult(reducedNodes, imagesByNode, false); result = SelfOrganizingMaps.RemoveZeroNodes(result); infiniteLoop++; } #endregion // Inject positions into the nodes InjectNodePositions2D(result.Nodes); //TODO: Look at isDisplay2D result = ArrangeNodes_LikesAttract(result); return(result); }
private static (NodeCombo[] kmeans, NodeCombo[] keep, NodeCombo[] remaining) GetSOM_SplitMerge_Categorize(SOMResult result) { NodeCombo[] nodes = Enumerable.Range(0, result.Nodes.Length). Select(o => new NodeCombo() { Node = result.Nodes[o], Inputs = result.InputsByNode[o] }). OrderByDescending(o => o.Inputs.Length). ToArray(); // First node is a potential kmeans split NodeCombo kmeans = null; int keepStart = 0; if (nodes[0].Inputs.Length.ToDouble() / nodes[1].Inputs.Length.ToDouble() > 10) { kmeans = nodes[0]; keepStart = 1; } NodeCombo[] kmeansSplit = null; if (kmeans != null) { SOMResult result2 = SelfOrganizingMaps.TrainKMeans(kmeans.Inputs, 4, true); kmeansSplit = Enumerable.Range(0, result2.Nodes.Length). Select(o => new NodeCombo() { Node = result2.Nodes[o], Inputs = result2.InputsByNode[o] }). ToArray(); } // Next nodes are the ones to leave alone var keep = new List <NodeCombo>(); int?keepStop = null; keep.Add(nodes[keepStart]); for (int cntr = keepStart + 1; cntr < nodes.Length; cntr++) { if (nodes[keepStart].Inputs.Length.ToDouble() / nodes[cntr].Inputs.Length.ToDouble() > 10) { keepStop = cntr; break; } keep.Add(nodes[cntr]); } // Everything else gets merged into a single node NodeCombo[] remaining = null; if (keepStop != null) { remaining = Enumerable.Range(keepStop.Value, result.Nodes.Length - keepStop.Value). Select(o => nodes[o]). ToArray(); } if (remaining == null && keep.Count > 0 && kmeans != null) { int sumKeep = keep.Sum(o => o.Inputs.Length); int smallestKmeans = kmeansSplit. Select(o => o.Inputs.Length). OrderBy(o => o). First(); if (smallestKmeans.ToDouble() / sumKeep.ToDouble() > 10) { remaining = keep.ToArray(); keep.Clear(); } } return(kmeansSplit, keep.ToArray(), remaining); }