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