Compare commits

..

No commits in common. "main" and "Training" have entirely different histories.

14 changed files with 146 additions and 806 deletions

View File

@ -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;

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2
guid: 879b585246de16598959c21b724cf105
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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 ??= "";

View File

@ -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()) {

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2
guid: ecce20d60829feced84788f7f9dcfe08
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8ea9c456ab9da37daaa610edaadc38bb

View File

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

View File

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

View File

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

View File

@ -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() { }
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: a820ab88a63bed6e881dbb82eae0a824
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8ea9c456ab9da37daaa610edaadc38bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: