Compare commits
No commits in common. "main" and "Training" have entirely different histories.
@ -27,12 +27,13 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
}
|
||||
|
||||
clusterPrefab.cluster.name = clusterPrefab.name;
|
||||
clusterPrefab.cluster.Cleanup();
|
||||
|
||||
view.currentCluster = clusterPrefab.cluster;
|
||||
view.currentCluster ??= clusterPrefab.cluster;
|
||||
view.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
||||
view.selectedOutput = view.currentNucleus;
|
||||
|
||||
clusterPrefab.cluster.name = clusterPrefab.name;
|
||||
|
||||
clusterPrefab.cluster.Cleanup();
|
||||
}
|
||||
|
||||
void OnDisable() {
|
||||
@ -47,30 +48,28 @@ namespace NanoBrain.Unity {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
// Begin horizontal split
|
||||
// EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// // Left: fixed-width drawing area
|
||||
// GUILayoutOption[] leftOptions = { GUILayout.Width(drawAreaWidth) };
|
||||
// Rect drawRect = GUILayoutUtility.GetRect(drawAreaWidth, 450f, leftOptions); // height adjustable
|
||||
// Left: fixed-width drawing area
|
||||
GUILayoutOption[] leftOptions = { GUILayout.Width(drawAreaWidth) };
|
||||
Rect drawRect = GUILayoutUtility.GetRect(drawAreaWidth, 450f, leftOptions); // height adjustable
|
||||
|
||||
// // add padding inside rect
|
||||
// Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
|
||||
// drawRect.width - padding * 2, drawRect.height - padding * 2);
|
||||
// add padding inside rect
|
||||
Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
|
||||
drawRect.width - padding * 2, drawRect.height - padding * 2);
|
||||
|
||||
// view.Render(innerRect);
|
||||
view.Render(innerRect);
|
||||
|
||||
// Right: info panel (takes remaining width)
|
||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||
float prevLabelWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = 100f; // smaller labels -> larger fields
|
||||
|
||||
if (GUILayout.Button("Import"))
|
||||
ImportJson();
|
||||
InspectorHandler(serializedObject);
|
||||
|
||||
EditorGUIUtility.labelWidth = prevLabelWidth;
|
||||
EditorGUILayout.EndVertical(); // end right column
|
||||
// EditorGUILayout.EndHorizontal(); // end split
|
||||
EditorGUILayout.EndHorizontal(); // end split
|
||||
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.Update();
|
||||
@ -84,35 +83,6 @@ namespace NanoBrain.Unity {
|
||||
|
||||
}
|
||||
|
||||
protected void ImportJson() {
|
||||
string path = EditorUtility.OpenFilePanel(
|
||||
"Import JSON",
|
||||
"",
|
||||
"json"
|
||||
);
|
||||
|
||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) {
|
||||
ClusterData newClusterData = Cluster.Import(path);
|
||||
clusterPrefab.cluster = new(newClusterData) {
|
||||
name = clusterPrefab.name
|
||||
};
|
||||
clusterPrefab.cluster.Cleanup();
|
||||
|
||||
view.currentCluster = clusterPrefab.cluster;
|
||||
view.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
||||
view.selectedOutput = view.currentNucleus;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasPreviewGUI() => true;
|
||||
|
||||
public override void OnPreviewGUI(Rect r, GUIStyle background) {
|
||||
base.OnPreviewGUI(r, background);
|
||||
GUI.Label(r, "welcome");
|
||||
view.Render(r);
|
||||
}
|
||||
|
||||
#region Inspector
|
||||
|
||||
private bool showSynapses = true;
|
||||
|
||||
@ -1,11 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 879b585246de16598959c21b724cf105
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@ -316,7 +316,6 @@ namespace NanoBrain.Unity {
|
||||
foreach (Synapse synapse in receiverNeuron.synapses) {
|
||||
Nucleus nucleus = synapse.neuron;
|
||||
if (nucleus.parent != null && currentNucleus != null && nucleus.parent != currentNucleus.parent) {
|
||||
// if (nucleus.parent != null && nucleus.parent != currentCluster) {
|
||||
nucleus = nucleus.parent;
|
||||
}
|
||||
string nucleusName = nucleus.name;
|
||||
@ -508,11 +507,11 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
Vector3 labelPos = position - Vector3.down * (discRadius + 5); // below neuron
|
||||
string name = $"{nucleus.parent.instances[0].baseName}\n{nucleus.name}";
|
||||
GUIStyle style = new(EditorStyles.label) {
|
||||
alignment = TextAnchor.UpperCenter,
|
||||
normal = { textColor = Color.white },
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
GUIStyle style = new(EditorStyles.label) {
|
||||
alignment = TextAnchor.UpperCenter,
|
||||
normal = { textColor = Color.white },
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
Handles.Label(labelPos, name, style);
|
||||
expandArray = false;
|
||||
}
|
||||
@ -625,27 +624,14 @@ namespace NanoBrain.Unity {
|
||||
|
||||
if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent && nucleus.parent is Cluster parentCluster1) {
|
||||
// This neuron is part of another cluster
|
||||
if (nucleus is Cluster cluster) {
|
||||
string clusterName = cluster.prefab.name;
|
||||
|
||||
int colonPos = clusterName.IndexOf(":");
|
||||
string baseName;
|
||||
if (colonPos > 0 && colonPos < clusterName.Length - 2)
|
||||
baseName = clusterName[..colonPos] + "\n";
|
||||
else
|
||||
baseName = clusterName + "\n";
|
||||
Handles.Label(labelPos, baseName + nucleus.name, style);
|
||||
}
|
||||
else {
|
||||
parentCluster1.name ??= "";
|
||||
int colonPos = parentCluster1.name.IndexOf(":");
|
||||
string baseName;
|
||||
if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2)
|
||||
baseName = parentCluster1.name[..colonPos] + "\n";
|
||||
else
|
||||
baseName = parentCluster1.name + "\n";
|
||||
Handles.Label(labelPos, baseName + nucleus.name, style);
|
||||
}
|
||||
parentCluster1.name ??= "";
|
||||
int colonPos = parentCluster1.name.IndexOf(":");
|
||||
string baseName;
|
||||
if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2)
|
||||
baseName = parentCluster1.name[..colonPos] + "\n";
|
||||
else
|
||||
baseName = parentCluster1.name + "\n";
|
||||
Handles.Label(labelPos, baseName + nucleus.name, style);
|
||||
}
|
||||
else {
|
||||
nucleus.name ??= "";
|
||||
|
||||
@ -38,18 +38,13 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
|
||||
private void InstantiateCluster(SerializedProperty property, ClusterView clusterView) {
|
||||
if (property == null || clusterView.initialized)
|
||||
return;
|
||||
|
||||
SerializedObject serializedObject = property.serializedObject;
|
||||
if (serializedObject == null)
|
||||
return;
|
||||
|
||||
UnityEngine.Object targetObject = serializedObject.targetObject;
|
||||
if (targetObject == null)
|
||||
if (property == null || property.serializedObject == null || clusterView.initialized)
|
||||
return;
|
||||
|
||||
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
||||
UnityEngine.Object targetObject = property.serializedObject.targetObject;
|
||||
if (targetObject == null)
|
||||
return;
|
||||
|
||||
Debug.Log($"Instantiate new Cluster for {targetObject.name}");
|
||||
|
||||
@ -75,14 +70,11 @@ namespace NanoBrain.Unity {
|
||||
int indent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
//SerializedProperty jsonProp = property.FindPropertyRelative(nameof(Cluster.jsonAsset));
|
||||
|
||||
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
||||
|
||||
// Draw the object field on the top line
|
||||
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
//EditorGUI.PropertyField(fieldRect, jsonProp, label);
|
||||
EditorGUI.PropertyField(fieldRect, prefabProp, label);
|
||||
// If a new prefab has been selected
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
|
||||
@ -1,11 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecce20d60829feced84788f7f9dcfe08
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
2
Runtime/Scripts/ClusterJson.cs.meta
Normal file
2
Runtime/Scripts/ClusterJson.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ea9c456ab9da37daaa610edaadc38bb
|
||||
@ -26,7 +26,6 @@ namespace NanoBrain {
|
||||
/// Cluster should always be created from prefabs
|
||||
public ClusterPrefab prefab;
|
||||
public ClusterJson json;
|
||||
public TextAsset jsonAsset;
|
||||
|
||||
//[HideInInspector]
|
||||
|
||||
@ -70,86 +69,15 @@ namespace NanoBrain {
|
||||
/// </summary>
|
||||
/// In a multi-cluster each instance can be used for a thing.
|
||||
/// Cluster instance may also not (yet) be mapped to a thing.
|
||||
// [NonSerialized]
|
||||
// public Dictionary<int, Cluster> thingClusters = new();
|
||||
public int thingId;
|
||||
[NonSerialized]
|
||||
public Dictionary<int, Cluster> thingClusters = new();
|
||||
|
||||
/// <summary>
|
||||
/// All nuclei in this cluster
|
||||
/// </summary>
|
||||
[SerializeReference]
|
||||
[HideInInspector]
|
||||
public List<Nucleus> nuclei = new();
|
||||
|
||||
#region Timed Actions
|
||||
|
||||
public class TimedAction {
|
||||
public TimedAction(string neuronName, Action action, long timestamp) {
|
||||
this.neuronName = neuronName;
|
||||
this.timestamp = timestamp;
|
||||
this.action = action;
|
||||
}
|
||||
public string neuronName;
|
||||
public long timestamp;
|
||||
public Action action;
|
||||
|
||||
public static void AddTo(List<TimedAction> actions, string neuronName, Action action, long timestamp) {
|
||||
int ix = -1;
|
||||
int insertIx = actions.Count; // default: add at end
|
||||
|
||||
// Find existing item and the first item with timestamp > this.timestamp
|
||||
for (int i = 0; i < actions.Count; i++) {
|
||||
TimedAction item = actions[i];
|
||||
|
||||
if (ix < 0 && item.neuronName == neuronName) {
|
||||
ix = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ix >= 0) {
|
||||
// Debug.Log($"update {neuronName} {timestamp}");
|
||||
actions[ix].action = action;
|
||||
actions[ix].timestamp = timestamp;
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.Log($"new {neuronName} {timestamp}");
|
||||
// No existing item; compute insertion point (sorted by timestamp ascending)
|
||||
for (int i = 0; i < actions.Count; i++) {
|
||||
if (actions[i].timestamp > timestamp) {
|
||||
insertIx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TimedAction newAction = new(neuronName, action, timestamp);
|
||||
actions.Insert(insertIx, newAction);
|
||||
}
|
||||
public static void Check(List<TimedAction> actions) {
|
||||
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
// if (actions.Count > 0) {
|
||||
// Debug.Log($"{actions.Count} actions, {actions[0].neuronName} {actions[0].timestamp} {now} {actions[0].timestamp - now}");
|
||||
// }
|
||||
while (actions.Count > 0 && actions[0].timestamp <= now) {
|
||||
Action action = actions[0].action;
|
||||
actions.RemoveAt(0);
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
public readonly List<TimedAction> actions = new();
|
||||
public void CheckActions() {
|
||||
TimedAction.Check(this.actions);
|
||||
foreach (Nucleus nucleus in this.nuclei) {
|
||||
if (nucleus is not Cluster cluster)
|
||||
continue;
|
||||
cluster.CheckActions();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Timed Actions
|
||||
|
||||
#region Init
|
||||
|
||||
public Cluster() {
|
||||
@ -200,14 +128,8 @@ namespace NanoBrain {
|
||||
this.prefab = prefab;
|
||||
this.version = prefab.version;
|
||||
this.name = prefab.name;
|
||||
if (parent != null) {
|
||||
TextAsset jsonFile = Resources.Load<TextAsset>(parent.name);
|
||||
string json = jsonFile.text;
|
||||
ClusterData clusterData = JsonUtility.FromJson<ClusterData>(json);
|
||||
//ClusterData clusterData = Cluster.Import(jsonPath);
|
||||
parent.cluster = new(clusterData);
|
||||
if (parent != null)
|
||||
this.parent = parent.cluster;
|
||||
}
|
||||
|
||||
ClonePrefab();
|
||||
}
|
||||
@ -250,27 +172,21 @@ namespace NanoBrain {
|
||||
foreach (NeuronData neuronData in clusterData.neurons) {
|
||||
Neuron receiver = this.GetNeuron(neuronData.name);
|
||||
foreach (SynapseData synapseData in neuronData.synapses) {
|
||||
// If the synapse points to another cluster
|
||||
if (synapseData.clusterName != this.name) {
|
||||
// Add reference to external cluster
|
||||
// Do we know the external cluster already?
|
||||
ExternalClusterData externalClusterData = clusterData.GetCluster(synapseData.clusterName);
|
||||
Cluster extCluster = this.GetCluster(synapseData.clusterName);
|
||||
if (extCluster == null) {
|
||||
// if not: create new external cluster
|
||||
Debug.Log("New ext cluster");
|
||||
ClusterPrefab extPrefab = Resources.Load(externalClusterData.prefabName) as ClusterPrefab;
|
||||
extCluster = new(extPrefab, this, instanceCount) {
|
||||
name = externalClusterData.name
|
||||
};
|
||||
}
|
||||
extCluster.instanceCount = externalClusterData.instanceCount;
|
||||
// Do we have the synapse already?
|
||||
Neuron extNeuron = extCluster.GetNeuron(synapseData.neuronName);
|
||||
Synapse synapse = receiver.GetSynapse(extNeuron); //extCluster, synapseData.neuronName);
|
||||
if (synapse == null) {
|
||||
Debug.Log("new receiver for external");
|
||||
// If not: create new synapse
|
||||
ClusterPrefab extPrefab = Resources.Load(synapseData.clusterName) as ClusterPrefab;
|
||||
if (extPrefab == null)
|
||||
Debug.LogError($"Could not find cluster Resource {synapseData.clusterName}");
|
||||
else {
|
||||
uint instanceCount = 1;
|
||||
foreach (ExternalClusterData externalCluster in clusterData.clusters) {
|
||||
if (externalCluster.name == synapseData.clusterName)
|
||||
instanceCount = externalCluster.instanceCount;
|
||||
}
|
||||
if (this.nuclei.Find(nucleus => nucleus.name == synapseData.clusterName) is not Cluster extCluster)
|
||||
extCluster = new(extPrefab, this, instanceCount);
|
||||
Neuron extNeuron = extCluster.GetNeuron(synapseData.neuronName);
|
||||
Debug.Log("Add receiver for external");
|
||||
extNeuron.AddReceiver(receiver);
|
||||
}
|
||||
}
|
||||
@ -282,6 +198,7 @@ namespace NanoBrain {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clone a prefab.
|
||||
/// </summary>
|
||||
@ -372,71 +289,6 @@ namespace NanoBrain {
|
||||
}
|
||||
}
|
||||
|
||||
public Cluster Copy() {
|
||||
Cluster clone = new() {
|
||||
name = this.name,
|
||||
prefab = this.prefab,
|
||||
version = this.version,
|
||||
parent = this.parent
|
||||
};
|
||||
if (clone.parent != null) {
|
||||
// Do we need to clone the parent here too????
|
||||
}
|
||||
|
||||
// first clone the nuclei without their connections
|
||||
foreach (Nucleus nucleus in this.nuclei)
|
||||
nucleus.ShallowCloneTo(clone);
|
||||
|
||||
Nucleus[] clonedNuclei = clone.nuclei.ToArray();
|
||||
// Now clone the connections
|
||||
for (int nucleusIx = 0; nucleusIx < this.nuclei.Count; nucleusIx++) {
|
||||
Nucleus sourceNucleus = this.nuclei[nucleusIx];
|
||||
if (sourceNucleus is not Neuron sourceNeuron)
|
||||
continue;
|
||||
|
||||
Nucleus clonedNucleus = clonedNuclei[nucleusIx];
|
||||
if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
|
||||
continue;
|
||||
|
||||
foreach (Synapse sourceSynapse in sourceNeuron.synapses) {
|
||||
Neuron synapseNeuron = sourceSynapse.neuron;
|
||||
if (synapseNeuron.parent.prefab != null && synapseNeuron.parent.prefab != clone.prefab) {
|
||||
// Neuron is in another cluster, find the cloned cluster first
|
||||
Cluster sourceCluster = synapseNeuron.parent;
|
||||
//Cluster clonedCluster = clone.nuclei.Find(n => n.name == sourceCluster.name) as Cluster;
|
||||
Nucleus clonedClusterNucleus = clone.nuclei.Find(n => n.name == sourceCluster.name);
|
||||
if (clonedClusterNucleus is not Cluster clonedCluster)
|
||||
continue;
|
||||
|
||||
// Now find the neuron in that cloned cluster
|
||||
int neuronIx = GetNucleusIndex(sourceCluster.nuclei, sourceSynapse.neuron.name);
|
||||
if (neuronIx < 0)
|
||||
// Could not find the neuron in the prefab cluster
|
||||
continue;
|
||||
if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender)
|
||||
// Could not find the neuron in the cloned cluster
|
||||
continue;
|
||||
|
||||
clonedSender.AddReceiver(clonedNeuron, sourceSynapse.weight, sourceSynapse.trainable);
|
||||
//Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]");
|
||||
}
|
||||
else {
|
||||
int ix = GetNucleusIndex(clone.prefab.cluster.nuclei, sourceSynapse.neuron);
|
||||
if (ix < 0)
|
||||
continue;
|
||||
if (clonedNuclei[ix] is not Neuron clonedSender)
|
||||
continue;
|
||||
|
||||
// Copy the receivers which will also create the synapse
|
||||
clonedSender.AddReceiver(clonedNeuron, sourceSynapse.weight, sourceSynapse.trainable);
|
||||
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo
|
||||
public override Nucleus ShallowCloneTo(Cluster parent) {
|
||||
// Clusters should not be cloned, but instantiated from the prefab....
|
||||
@ -493,8 +345,8 @@ namespace NanoBrain {
|
||||
public static int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
|
||||
int i = 0;
|
||||
foreach (Nucleus nucleiElement in nuclei) {
|
||||
// if (nucleiElement == nucleus)
|
||||
if (nucleiElement.name == nucleus.name)
|
||||
//for (int i = 0; i < nuclei.Length; i++) {
|
||||
if (nucleiElement == nucleus)
|
||||
return i;
|
||||
i++;
|
||||
}
|
||||
@ -518,37 +370,6 @@ namespace NanoBrain {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes all trainable weights to random values
|
||||
/// </summary>
|
||||
public void InitializeRandom() {
|
||||
System.Random randomGenerator = new();
|
||||
foreach (Nucleus nucleus in this.nuclei) {
|
||||
if (nucleus is Neuron neuron) {
|
||||
foreach (Synapse synapse in neuron.synapses) {
|
||||
if (synapse.trainable) {
|
||||
synapse.weight = (float)randomGenerator.NextDouble() * 2.0f - 1.0f;
|
||||
// Debug.Log($"{neuron.name}-{synapse.neuron.name} weight = {synapse.weight}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GaussianAdditiveMutation(float sigma) {
|
||||
foreach (Nucleus nucleus in this.nuclei) {
|
||||
if (nucleus is Neuron neuron) {
|
||||
foreach (Synapse synapse in neuron.synapses) {
|
||||
if (synapse.trainable) {
|
||||
synapse.GaussianAdditiveMutation(sigma);
|
||||
//synapse.weight = (float)randomGenerator.NextDouble() * 2.0f - 1.0f;
|
||||
// Debug.Log($"{neuron.name}-{synapse.neuron.name} weight = {synapse.weight}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Cluster Array
|
||||
@ -622,20 +443,18 @@ namespace NanoBrain {
|
||||
/// Remove a mapping from a thing to a cluster such that it becomes available for new things
|
||||
/// </summary>
|
||||
/// <param name="cluster">The multi-cluster instance which not no longer be mapped</param>
|
||||
public void RemoveThingCluster(Cluster cluster) {
|
||||
cluster.thingId = 0;
|
||||
// int keyToRemove = -1;
|
||||
// foreach (KeyValuePair<int, Cluster> kvp in this.thingClusters) {
|
||||
// if (kvp.Value == cluster) {
|
||||
// keyToRemove = kvp.Key;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
private void RemoveThingCluster(Cluster cluster) {
|
||||
List<int> keysToRemove = new();
|
||||
foreach (KeyValuePair<int, Cluster> kvp in thingClusters) {
|
||||
if (kvp.Value == cluster)
|
||||
keysToRemove.Add(kvp.Key);
|
||||
}
|
||||
|
||||
// if (keyToRemove >= 0)
|
||||
// this.thingClusters.Remove(keyToRemove);
|
||||
foreach (int thingId in keysToRemove)
|
||||
thingClusters.Remove(thingId);
|
||||
}
|
||||
|
||||
|
||||
public List<Neuron> GetAllInstances(Neuron nucleus) {
|
||||
List<Neuron> allInstances = new();
|
||||
|
||||
@ -656,6 +475,28 @@ namespace NanoBrain {
|
||||
return allInstances;
|
||||
}
|
||||
|
||||
// public void Backpropagation(Func<Cluster, float> Observer, float target, float learningRate) {
|
||||
// foreach (Nucleus nucleus in this.instances[0].nuclei) {
|
||||
// if (nucleus is not Neuron neuron)
|
||||
// continue;
|
||||
|
||||
// foreach (Synapse synapse in neuron.synapses) {
|
||||
// List<Neuron> allSynapseNeurons = GetAllInstances(synapse.neuron);
|
||||
|
||||
// Vector3 dSSRdW = Vector3.zero;
|
||||
// for (int clusterIx = 0; clusterIx < this.instances.Length; clusterIx++) {
|
||||
// Cluster clusterInstance = this.instances[clusterIx];
|
||||
// Neuron neuronInstance = allSynapseNeurons[clusterIx];
|
||||
|
||||
// // Simple case, without receivers...
|
||||
// dSSRdW += (Vector3)(-2 * (Observer(clusterInstance) - target) * neuronInstance.activation);
|
||||
// }
|
||||
// synapse.weight += learningRate * dSSRdW.magnitude;
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
#endregion ClusterArray
|
||||
|
||||
/// <summary>
|
||||
@ -671,7 +512,7 @@ namespace NanoBrain {
|
||||
if (_computeOrders == null || _computeOrders.Count == 0) {
|
||||
_computeOrders = new();
|
||||
foreach (Nucleus nucleus in this.nuclei)
|
||||
_computeOrders[nucleus] = TopologicalSort(nucleus);
|
||||
_computeOrders[nucleus] = TopologicalSort2(nucleus);
|
||||
}
|
||||
return _computeOrders;
|
||||
}
|
||||
@ -683,7 +524,7 @@ namespace NanoBrain {
|
||||
this._computeOrders = null;
|
||||
}
|
||||
|
||||
private List<Nucleus> TopologicalSort(Nucleus startNode) {
|
||||
private List<Nucleus> TopologicalSort2(Nucleus startNode) {
|
||||
Dictionary<Nucleus, int> inDegree = new();
|
||||
//HashSet<Nucleus> visited = new();
|
||||
|
||||
@ -868,18 +709,13 @@ namespace NanoBrain {
|
||||
/// <returns>The found neuron or null when it is not found</returns>
|
||||
/// The cluster instance mapped to the thing will be neuron.parent if a neuron is found.
|
||||
public Neuron GetNeuron(int thingId, string neuronName, string thingName = null) {
|
||||
// If this is not an ClusterArray, just take the neuron
|
||||
if (this.instances == null || this.instances.Length <= 1)
|
||||
return this.GetNeuron(neuronName);
|
||||
|
||||
// See if we are already using a cluster for thingId
|
||||
// thingClusters ??= new();
|
||||
// if (thingClusters.TryGetValue(thingId, out Cluster cluster))
|
||||
// return cluster.GetNeuron(neuronName);
|
||||
foreach (Cluster sibling in this.instances) {
|
||||
if (sibling.thingId == thingId)
|
||||
return sibling.GetNeuron(neuronName);
|
||||
}
|
||||
thingClusters ??= new();
|
||||
if (thingClusters.TryGetValue(thingId, out Cluster cluster))
|
||||
return cluster.GetNeuron(neuronName);
|
||||
|
||||
// Find the cluster with the lowest value neuron
|
||||
Neuron lowestNeuron = null;
|
||||
@ -891,8 +727,7 @@ namespace NanoBrain {
|
||||
Cluster selectedCluster = lowestNeuron.parent;
|
||||
RemoveThingCluster(selectedCluster);
|
||||
selectedCluster.name = baseName + ": " + thingName;
|
||||
//thingClusters[thingId] = selectedCluster;
|
||||
selectedCluster.thingId = thingId;
|
||||
thingClusters[thingId] = selectedCluster;
|
||||
return lowestNeuron;
|
||||
}
|
||||
|
||||
@ -917,65 +752,6 @@ namespace NanoBrain {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool EqualStructure(Cluster cluster1, Cluster cluster2) {
|
||||
int n1 = cluster1.nuclei.Count;
|
||||
int n2 = cluster2.nuclei.Count;
|
||||
if (n1 != n2)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < cluster1.nuclei.Count; i++) {
|
||||
if (EqualStructure(cluster1.nuclei[i], cluster2.nuclei[i]) == false)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public bool CopyWeightsFrom(Cluster source) {
|
||||
int thisNucleiCount = this.nuclei.Count;
|
||||
int sourceNucleiCount = source.nuclei.Count;
|
||||
if (thisNucleiCount != sourceNucleiCount)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < this.nuclei.Count; i++) {
|
||||
if (this.nuclei[i] is Neuron thisNeuron &&
|
||||
source.nuclei[i] is Neuron sourceNeuron) {
|
||||
|
||||
if (thisNeuron.CopyWeightsFrom(sourceNeuron) == false)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ProcessWeightsFrom(Cluster source, Func<float, float, float> processor) {
|
||||
if (processor is null)
|
||||
throw new ArgumentNullException(nameof(processor));
|
||||
if (source is null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
int thisNucleiCount = this.nuclei.Count;
|
||||
int sourceNucleiCount = source.nuclei.Count;
|
||||
if (thisNucleiCount != sourceNucleiCount)
|
||||
throw new ArgumentException("Lists must have the same length.", nameof(source));
|
||||
|
||||
for (int i = 0; i < thisNucleiCount; i++) {
|
||||
if (this.nuclei[i] is Neuron thisNeuron &&
|
||||
source.nuclei[i] is Neuron sourceNeuron) {
|
||||
|
||||
thisNeuron.ProcessWeightsFrom(sourceNeuron, processor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessWeights(Func<float, float> processor) {
|
||||
if (processor is null)
|
||||
throw new ArgumentNullException(nameof(processor));
|
||||
|
||||
for (int i = 0; i < this.nuclei.Count; i++) {
|
||||
if (this.nuclei[i] is Neuron thisNeuron)
|
||||
thisNeuron.ProcessWeight(processor);
|
||||
}
|
||||
}
|
||||
|
||||
#region Receivers
|
||||
|
||||
/// <summary>
|
||||
@ -1040,15 +816,14 @@ namespace NanoBrain {
|
||||
|
||||
List<Nucleus> computeOrder = this.computeOrders[startNucleus];
|
||||
foreach (Nucleus nucleus in computeOrder) {
|
||||
if (nucleus is Cluster)
|
||||
continue;
|
||||
|
||||
nucleus.UpdateStateIsolated();
|
||||
if (nucleus is Neuron neuron) {
|
||||
foreach (Nucleus receiver in neuron.receivers) {
|
||||
if (receiver.parent != this) {
|
||||
//Debug.Log($" External: {receiver.parent.name}.{receiver.name}");
|
||||
receiver.parent.UpdateFromNucleus(receiver);
|
||||
if (nucleus is not Cluster) {
|
||||
nucleus.UpdateStateIsolated();
|
||||
if (nucleus is Neuron neuron) {
|
||||
foreach (Nucleus receiver in neuron.receivers) {
|
||||
if (receiver.parent != this) {
|
||||
//Debug.Log($" External: {receiver.parent.name}.{receiver.name}");
|
||||
receiver.parent.UpdateFromNucleus(receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1090,10 +865,10 @@ namespace NanoBrain {
|
||||
#region Serialization
|
||||
|
||||
public void Export(string path) {
|
||||
ClusterData data = new(this);
|
||||
ClusterData data = new(this); //this.ToJSON();
|
||||
string json = JsonUtility.ToJson(data, prettyPrint: true);
|
||||
|
||||
Debug.Log($"Exporting {this.name} to {path}");
|
||||
// Debug.Log($"Exporting json to {path}");
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
@ -1115,41 +890,30 @@ namespace NanoBrain {
|
||||
public ClusterData(Cluster cluster) {
|
||||
this.name = cluster.name;
|
||||
foreach (Nucleus nucleus in cluster.nuclei) {
|
||||
if (nucleus is not Neuron neuron)
|
||||
continue;
|
||||
|
||||
NeuronData neuronData = new(neuron);
|
||||
this.neurons.Add(neuronData);
|
||||
foreach (Synapse synapse in neuron.synapses) {
|
||||
if (synapse.neuron.parent.baseName == cluster.name)
|
||||
continue;
|
||||
|
||||
ExternalClusterData clusterData = new(synapse.neuron.parent);
|
||||
if (GetCluster(clusterData.name) == null)
|
||||
this.clusters.Add(clusterData);
|
||||
if (nucleus is Neuron neuron) {
|
||||
NeuronData neuronData = new(neuron);
|
||||
this.neurons.Add(neuronData);
|
||||
foreach (Synapse synapse in neuron.synapses) {
|
||||
if (synapse.neuron.parent.name != cluster.name) {
|
||||
ExternalClusterData clusterData = new(synapse.neuron.parent);
|
||||
if (this.clusters.Find(data => data.name == clusterData.name) == null) {
|
||||
this.clusters.Add(clusterData);
|
||||
//Debug.Log("Add cluster");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public ExternalClusterData GetCluster(string clusterName) {
|
||||
foreach (ExternalClusterData cluster in this.clusters) {
|
||||
if (cluster.name == clusterName)
|
||||
return cluster;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ExternalClusterData {
|
||||
public string name;
|
||||
public string prefabName;
|
||||
public uint instanceCount;
|
||||
|
||||
public ExternalClusterData(Cluster cluster) {
|
||||
this.name = cluster.baseName;
|
||||
this.prefabName = cluster.prefab.name;
|
||||
this.name = cluster.name;
|
||||
this.instanceCount = (uint)cluster.instanceCount;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
#if UNITY_MATHEMATICS
|
||||
using Unity.Mathematics;
|
||||
@ -37,7 +35,6 @@ namespace NanoBrain {
|
||||
this.parent.nuclei ??= new();
|
||||
this.parent.nuclei.Add(this);
|
||||
}
|
||||
this.resetStimulus = ResetStimulus;
|
||||
}
|
||||
|
||||
#region Serialization
|
||||
@ -98,12 +95,6 @@ namespace NanoBrain {
|
||||
|
||||
return this.GetSynapse(sender);
|
||||
}
|
||||
public Synapse GetSynapse(Cluster cluster, string senderNeuronName) {
|
||||
Neuron sender = cluster.GetNeuron(senderNeuronName);
|
||||
if (sender == null)
|
||||
return null;
|
||||
return this.GetSynapse(sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a synapse from a Nucleus
|
||||
@ -121,7 +112,7 @@ namespace NanoBrain {
|
||||
/// <param name="inputValue"></param>
|
||||
public virtual void SetBias(Vector3 inputValue) {
|
||||
this.bias = inputValue;
|
||||
// this.lastUpdate = Time.time;
|
||||
this.lastUpdate = Time.time;
|
||||
this.parent?.UpdateFromNucleus(this);
|
||||
}
|
||||
|
||||
@ -184,16 +175,11 @@ namespace NanoBrain {
|
||||
/// The output value of the neuron
|
||||
/// </summary>
|
||||
public virtual float3 outputValue {
|
||||
get {
|
||||
this.parent.CheckActions();
|
||||
return _outputValue;
|
||||
}
|
||||
get { return _outputValue; }
|
||||
set {
|
||||
_outputValue = value;
|
||||
if (this.isFiring)
|
||||
WhenFiring?.Invoke();
|
||||
else
|
||||
WhenNotFiring?.Invoke();
|
||||
}
|
||||
}
|
||||
public float3 activation => outputValue;
|
||||
@ -242,39 +228,37 @@ namespace NanoBrain {
|
||||
/// </summary>
|
||||
public Action WhenFiring;
|
||||
|
||||
public Action WhenNotFiring;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the value will not be reset after timeToSleep.
|
||||
/// </summary>
|
||||
//public bool persistOutput = false;
|
||||
public bool persistOutput = false;
|
||||
/// <summary>
|
||||
/// True when the neuron is not persisting and has not be updated for timeToSleep seconds
|
||||
/// </summary>
|
||||
//public virtual bool isSleeping => false; //!persistOutput && (Time.time - this.lastUpdate > timeToSleep);
|
||||
public virtual bool isSleeping => !persistOutput && (Time.time - this.lastUpdate > timeToSleep);
|
||||
/// <summary>
|
||||
/// Check if the neuron is sleeping.
|
||||
/// </summary>
|
||||
/// This will reset the output value if it is sleeping
|
||||
// public void SleepCheck() {
|
||||
// if (this.isSleeping && this.outputSqrMagnitude > 0) {
|
||||
// #if UNITY_MATHEMATICS
|
||||
// this._outputValue = new float3(0, 0, 0);
|
||||
// #else
|
||||
// this._outputValue = new Vector3(0,0,0);
|
||||
// #endif
|
||||
// }
|
||||
// }
|
||||
public void SleepCheck() {
|
||||
if (this.isSleeping && this.outputSqrMagnitude > 0) {
|
||||
#if UNITY_MATHEMATICS
|
||||
this._outputValue = new float3(0, 0, 0);
|
||||
#else
|
||||
this._outputValue = new Vector3(0,0,0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the last update has been done
|
||||
/// </summary>
|
||||
// [HideInInspector]
|
||||
// public float lastUpdate = 0;
|
||||
[HideInInspector]
|
||||
public float lastUpdate = 0;
|
||||
/// <summary>
|
||||
/// Time in seconds after the last update the neuron can go to sleep
|
||||
/// </summary>
|
||||
// public static readonly float timeToSleep = 0.5f;
|
||||
public static readonly float timeToSleep = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// When true, Unity will pause exection when this neuron is updated
|
||||
@ -298,63 +282,12 @@ namespace NanoBrain {
|
||||
protected virtual void CloneFields(Neuron clone) {
|
||||
clone.bias = this.bias;
|
||||
clone.trainableBias = this.trainableBias;
|
||||
//clone.persistOutput = this.persistOutput;
|
||||
clone.persistOutput = this.persistOutput;
|
||||
clone.combinator = this.combinator;
|
||||
clone.activator = this.activator;
|
||||
clone.breakOnUpdate = this.breakOnUpdate;
|
||||
}
|
||||
|
||||
public static bool EqualStructure(Neuron neuron1, Neuron neuron2) {
|
||||
int synapseCount1 = neuron1.synapses.Count;
|
||||
int synapseCount2 = neuron2.synapses.Count;
|
||||
if (synapseCount1 != synapseCount2)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < synapseCount1; i++) {
|
||||
if (Synapse.EqualStructure(neuron1.synapses[i], neuron2.synapses[i]) == false)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CopyWeightsFrom(Neuron source) {
|
||||
int thisSynapseCount = this.synapses.Count;
|
||||
int sourceSynapseCount = source.synapses.Count;
|
||||
if (thisSynapseCount != sourceSynapseCount)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < thisSynapseCount; i++) {
|
||||
Synapse thisSynapse = this.synapses[i];
|
||||
if (thisSynapse.trainable) {
|
||||
Synapse sourceSynapse = source.synapses[i];
|
||||
thisSynapse.weight = sourceSynapse.weight;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ProcessWeightsFrom(Neuron source, Func<float, float, float> processor) {
|
||||
int thisSynapseCount = this.synapses.Count;
|
||||
int sourceSynapseCount = source.synapses.Count;
|
||||
if (thisSynapseCount != sourceSynapseCount)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < thisSynapseCount; i++) {
|
||||
Synapse thisSynapse = this.synapses[i];
|
||||
if (thisSynapse.trainable) {
|
||||
Synapse sourceSynapse = source.synapses[i];
|
||||
thisSynapse.weight = processor(thisSynapse.weight, sourceSynapse.weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessWeight(Func<float, float> processor) {
|
||||
foreach (Synapse thisSynapse in this.synapses) {
|
||||
if (thisSynapse.trainable)
|
||||
thisSynapse.weight = processor(thisSynapse.weight);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the give neuron
|
||||
/// </summary>
|
||||
@ -408,7 +341,7 @@ namespace NanoBrain {
|
||||
}
|
||||
this.combination = Combinator(this.bias, this.synapses);
|
||||
this.outputValue = Activator(this.combination);
|
||||
// this.lastUpdate = Time.time;
|
||||
this.lastUpdate = Time.time;
|
||||
}
|
||||
|
||||
#region Combinator
|
||||
@ -443,8 +376,8 @@ namespace NanoBrain {
|
||||
public static float3 CombinatorSum(float3 bias, List<Synapse> synapses) {
|
||||
float3 sum = bias;
|
||||
foreach (Synapse synapse in synapses) {
|
||||
// synapse.neuron.SleepCheck();
|
||||
sum += synapse.weight * synapse.neuron._outputValue;
|
||||
synapse.neuron.SleepCheck();
|
||||
sum += synapse.weight * synapse.neuron.outputValue;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
@ -458,8 +391,8 @@ namespace NanoBrain {
|
||||
public static float3 CombinatorProduct(float3 bias, List<Synapse> synapses) {
|
||||
float3 product = bias;
|
||||
foreach (Synapse synapse in synapses) {
|
||||
// synapse.neuron.SleepCheck();
|
||||
product *= synapse.weight * synapse.neuron._outputValue;
|
||||
synapse.neuron.SleepCheck();
|
||||
product *= synapse.weight * synapse.neuron.outputValue;
|
||||
}
|
||||
return product;
|
||||
}
|
||||
@ -770,14 +703,14 @@ namespace NanoBrain {
|
||||
|
||||
foreach (Synapse synapse in this.synapses)
|
||||
synapse.BackPropagation3D(this, derivative, learningRate);
|
||||
// As the weight cannot change the direction of the derivative
|
||||
// we can use the simpler, 1D backpropagation here
|
||||
// But we still need to determine the sign of the derivative
|
||||
// As the weight cannot change the direction of the derivative
|
||||
// we can use the simpler, 1D backpropagation here
|
||||
// But we still need to determine the sign of the derivative
|
||||
|
||||
// if (Synapse.AreOpposed(derivative, synapse.neuron.activation))
|
||||
// synapse.BackPropagation(this, -derivativeMagnitude, learningRate);
|
||||
// else
|
||||
// synapse.BackPropagation(this, derivativeMagnitude, learningRate);
|
||||
// if (Synapse.AreOpposed(derivative, synapse.neuron.activation))
|
||||
// synapse.BackPropagation(this, -derivativeMagnitude, learningRate);
|
||||
// else
|
||||
// synapse.BackPropagation(this, derivativeMagnitude, learningRate);
|
||||
|
||||
// Bias
|
||||
if (this.trainableBias) {
|
||||
@ -796,32 +729,14 @@ namespace NanoBrain {
|
||||
|
||||
#endregion Back propagation
|
||||
|
||||
private CancellationTokenSource _cts;
|
||||
private Action resetStimulus;
|
||||
|
||||
/// <summary>
|
||||
/// Process an external stimulus
|
||||
/// </summary>
|
||||
/// <param name="inputValue">The value of the stimulus</param>
|
||||
public virtual void ProcessStimulus(Vector3 inputValue, float autoResetDelay = 0) {
|
||||
// this.lastUpdate = Time.time;
|
||||
public virtual void ProcessStimulus(Vector3 inputValue) {
|
||||
this.lastUpdate = Time.time;
|
||||
this.bias = inputValue;
|
||||
this.parent?.UpdateFromNucleus(this);
|
||||
this.resetStimulus ??= ResetStimulus;
|
||||
|
||||
if (autoResetDelay > 0) {
|
||||
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
long resetTime = now + (long)(autoResetDelay * 1000.0f);
|
||||
Cluster.TimedAction.AddTo(this.parent.actions, this.parent.name + "." + this.name, this.resetStimulus, resetTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetStimulus() {
|
||||
// Debug.Log($"reset stimulus {this.parent.name + "." +this.name}");
|
||||
this.parent.name = this.parent.baseName;
|
||||
this.bias = Vector3.zero;
|
||||
this.parent.thingId = 0;
|
||||
this.parent?.UpdateFromNucleus(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -845,11 +760,6 @@ namespace NanoBrain {
|
||||
this.activationType = neuron.activator;
|
||||
|
||||
foreach (Synapse synapse in neuron.synapses) {
|
||||
// Check whether synapse exists already first
|
||||
if (this.synapses.Find(s => s.neuronName == synapse.neuron.name) != null)
|
||||
// But then: what to do with the weights???? :-)
|
||||
continue;
|
||||
|
||||
SynapseData synapseData = new(synapse);
|
||||
this.synapses.Add(synapseData);
|
||||
}
|
||||
|
||||
@ -50,33 +50,6 @@ namespace NanoBrain {
|
||||
|
||||
#endregion Update
|
||||
|
||||
public static bool EqualStructure(Nucleus nucleus1, Nucleus nucleus2) {
|
||||
if (nucleus1.parent != null && nucleus2.parent != null) {
|
||||
if (nucleus1.parent.baseName != nucleus2.parent.baseName)
|
||||
return false;
|
||||
} else {
|
||||
// mainly to check one is null, other is not.
|
||||
if (nucleus1.parent != nucleus2.parent)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nucleus1 is Neuron neuron1) {
|
||||
if (nucleus2 is not Neuron neuron2)
|
||||
return false;
|
||||
if (neuron1.name != neuron2.name)
|
||||
return false;
|
||||
return Neuron.EqualStructure(neuron1, neuron2);
|
||||
}
|
||||
else if (nucleus1 is Cluster cluster1) {
|
||||
if (nucleus2 is not Cluster cluster2)
|
||||
return false;
|
||||
if (cluster1.baseName != cluster2.baseName)
|
||||
return false;
|
||||
return Cluster.EqualStructure(cluster1, cluster2);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,185 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NanoBrain {
|
||||
public class Population {
|
||||
|
||||
# region Members
|
||||
|
||||
public class Member {
|
||||
public Member(Cluster cluster) {
|
||||
this.brain = cluster;
|
||||
this.performance = 0;
|
||||
this.initialized = true;
|
||||
}
|
||||
public Cluster brain;
|
||||
public float performance;
|
||||
public bool initialized;
|
||||
}
|
||||
public List<Member> members = new();
|
||||
|
||||
public Member AddMember(Cluster brain) {
|
||||
Member newMember = new(brain);
|
||||
this.members.Add(newMember);
|
||||
return newMember;
|
||||
}
|
||||
|
||||
#endregion Members
|
||||
|
||||
#region Saving
|
||||
|
||||
public enum SaveMethod {
|
||||
Average,
|
||||
Mean,
|
||||
First,
|
||||
All
|
||||
}
|
||||
public void Save(string path, SaveMethod saveMethod = SaveMethod.Average) {
|
||||
Cluster cluster = null;
|
||||
switch (saveMethod) {
|
||||
case SaveMethod.First:
|
||||
Member member = this.members[0];
|
||||
if (member == null)
|
||||
return;
|
||||
|
||||
cluster = member.brain;
|
||||
break;
|
||||
case SaveMethod.Average:
|
||||
cluster = CalculateAverageCluster();
|
||||
break;
|
||||
}
|
||||
cluster?.Export(path);
|
||||
}
|
||||
|
||||
protected Cluster CalculateAverageCluster() {
|
||||
if (members.Count <= 0)
|
||||
return null;
|
||||
|
||||
Cluster result = members[0].brain.Copy();
|
||||
for (int memberIx = 1; memberIx < members.Count; memberIx++) {
|
||||
Member member = members[memberIx];
|
||||
result.ProcessWeightsFrom(member.brain, Sum);
|
||||
}
|
||||
result.ProcessWeights(w => w / members.Count);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion Saving
|
||||
|
||||
#region New Generation
|
||||
|
||||
public void NewGeneration() {
|
||||
foreach (Member member in this.members) {
|
||||
member.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Member> SelectElite(float percentage) {
|
||||
return SelectElite((int)(members.Count * percentage));
|
||||
}
|
||||
protected List<Member> SelectElite(int count) {
|
||||
List<Member> selectedMembers = new();
|
||||
|
||||
SortMembers();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Member member = this.members[i];
|
||||
if (!member.initialized) {
|
||||
Debug.Log($"Selected {i}: {member.performance}");
|
||||
member.initialized = true;
|
||||
selectedMembers.Add(member);
|
||||
// LogTrainableWeights(member.brain);
|
||||
}
|
||||
}
|
||||
|
||||
return selectedMembers;
|
||||
}
|
||||
|
||||
private void SortMembers() {
|
||||
members.Sort((a, b) => a.performance.CompareTo(b.performance));
|
||||
}
|
||||
|
||||
protected List<Member> GenerateMutants(List<Member> elite, float percentage) {
|
||||
return GenerateMutants(elite, (int)(members.Count * percentage));
|
||||
}
|
||||
protected List<Member> GenerateMutants(List<Member> elite, int count) {
|
||||
List<Member> selectedMembers = new();
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
while (n < count && i < this.members.Count) {
|
||||
Member member = this.members[i];
|
||||
if (member.initialized == false) {
|
||||
GenerateMutant(member, elite);
|
||||
member.initialized = true;
|
||||
n++;
|
||||
selectedMembers.Add(member);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return selectedMembers;
|
||||
}
|
||||
|
||||
protected void GenerateMutant(Member member, List<Member> ants) {
|
||||
System.Random randomGenerator = new();
|
||||
int n = ants.Count;
|
||||
int parent1 = randomGenerator.Next(0, n);
|
||||
int parent2 = randomGenerator.Next(0, n);
|
||||
Debug.Log($"Mutate from {members[parent1].performance} and {members[parent2].performance}");
|
||||
|
||||
member.brain.CopyWeightsFrom(ants[parent1].brain);
|
||||
member.brain.ProcessWeightsFrom(ants[parent2].brain, Average);
|
||||
// LogTrainableWeights(member.brain);
|
||||
member.brain.GaussianAdditiveMutation(1e-2f);
|
||||
// LogTrainableWeights(member.brain);
|
||||
}
|
||||
|
||||
private static float Average(float a, float b) {
|
||||
return (a + b) / 2;
|
||||
}
|
||||
|
||||
private static float Sum(float a, float b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
|
||||
protected List<Member> GenerateRandom() {
|
||||
return GenerateRandom(int.MaxValue);
|
||||
}
|
||||
|
||||
protected List<Member> GenerateRandom(int count) {
|
||||
List<Member> selectedMembers = new();
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
while (n < count && i < this.members.Count) {
|
||||
Member member = this.members[i];
|
||||
if (member.initialized == false) {
|
||||
Debug.Log($"Randomized {i}: {member.performance}");
|
||||
member.brain.InitializeRandom();
|
||||
member.initialized = true;
|
||||
selectedMembers.Add(member);
|
||||
n++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return selectedMembers;
|
||||
}
|
||||
|
||||
#endregion New Generation
|
||||
|
||||
private void LogTrainableWeights(Cluster cluster) {
|
||||
string s = "";
|
||||
foreach (Nucleus nucleus in cluster.nuclei) {
|
||||
if (nucleus is Neuron neuron) {
|
||||
foreach (Synapse synapse in neuron.synapses) {
|
||||
if (synapse.trainable) {
|
||||
s += synapse.weight + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Debug.Log(s);
|
||||
}
|
||||
|
||||
public virtual void Update() { }
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a820ab88a63bed6e881dbb82eae0a824
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -36,10 +36,6 @@ namespace NanoBrain {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public static bool EqualStructure(Synapse synapse1, Synapse synapse2) {
|
||||
return synapse1.neuron.name == synapse2.neuron.name;
|
||||
}
|
||||
|
||||
public virtual void BackPropagation(Neuron receiver, float derivative, float learningRate) {
|
||||
switch (receiver.activator) {
|
||||
case Neuron.ActivationType.Linear:
|
||||
@ -114,30 +110,6 @@ namespace NanoBrain {
|
||||
// Check if the angle between the vectors is > 90 degrees
|
||||
return Vector3.Dot(a, b) < 0f;
|
||||
}
|
||||
|
||||
public void GaussianAdditiveMutation(float sigma) {
|
||||
if (this.trainable == false)
|
||||
return;
|
||||
|
||||
float deltaWeight = NormalDistribution.Sample(sigma);
|
||||
this.weight += deltaWeight;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NormalDistribution {
|
||||
private static readonly System.Random rng = new();
|
||||
|
||||
// Returns a single sample from N(0, sigma^2)
|
||||
public static float Sample(float sigma) {
|
||||
// u1 must be > 0 to avoid log(0)
|
||||
float u1 = 1.0f - (float)rng.NextDouble(); // in (0,1]
|
||||
float u2 = (float) rng.NextDouble(); // in [0,1)
|
||||
|
||||
float stdNormal =
|
||||
Mathf.Sqrt(-2.0f * Mathf.Log(u1)) * Mathf.Cos(2.0f * Mathf.PI * u2);
|
||||
|
||||
return sigma * stdNormal;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@ -148,11 +120,7 @@ namespace NanoBrain {
|
||||
public bool trainable;
|
||||
|
||||
public SynapseData(Synapse synapse) {
|
||||
// if (synapse.neuron.parent.prefab != null)
|
||||
// this.clusterName = synapse.neuron.parent.prefab.name;
|
||||
// else
|
||||
// this.clusterName = synapse.neuron.parent.name;
|
||||
this.clusterName = synapse.neuron.parent.baseName;
|
||||
this.clusterName = synapse.neuron.parent.name;
|
||||
this.neuronName = synapse.neuron.name;
|
||||
this.weight = synapse.weight;
|
||||
this.trainable = synapse.trainable;
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ea9c456ab9da37daaa610edaadc38bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
x
Reference in New Issue
Block a user