Moved activation function to after weight application

This commit is contained in:
Pascal Serrarens 2026-01-08 14:09:31 +01:00
parent b9b942760b
commit 600ecd5406
13 changed files with 94 additions and 1436 deletions

View File

@ -48,7 +48,7 @@ public class Neuroid : Nucleus {
public float exponent = 1.0f;
public Neuroid(NanoBrainObj brain, string name) : base(name) {
public Neuroid(NanoBrain brain, string name) : base(name) {
this.brain = brain;
if (this.brain != null) {
this.brain.nuclei.Add(this);
@ -75,20 +75,40 @@ public class Neuroid : Nucleus {
}
public override void UpdateState() {
Vector3 result = Vector3.zero;
Vector3 sum = Vector3.zero;
int n = 0;
//Applying the weight factgors
foreach (Synapse synapse in this.synapses) {
Vector3 outputValue = synapse.nucleus.outputValue;
// As this is the activation function, it should be evaluated in the synapse.nucleus...
float activatedValue = curve.Evaluate(outputValue.magnitude);
float magnitude = synapse.weight * activatedValue;
float magnitude = synapse.weight * outputValue.magnitude;
result += magnitude * outputValue.normalized;
sum += magnitude * outputValue.normalized;
n++;
}
if (average)
result /= n;
sum /= n;
// Activation function
Vector3 result;
switch (this.curvePreset) {
case CurvePresets.Linear:
result = sum;
break;
case CurvePresets.Sqrt:
result = sum.normalized * System.MathF.Sqrt(sum.magnitude);
break;
case CurvePresets.Power:
result = sum.normalized * System.MathF.Pow(sum.magnitude, 2);
break;
case CurvePresets.Reciprocal:
result = sum.normalized * (1 / sum.magnitude);
break;
default:
float activatedValue = this.curve.Evaluate(sum.magnitude);
result = sum.normalized * activatedValue;
break;
}
UpdateResult(result);
}

View File

@ -26,7 +26,7 @@ public class Nucleus {
[SerializeField]
protected string nucleusType;
public virtual void Rebuild(NanoBrainObj brain) {
public virtual void Rebuild(NanoBrain brain) {
if (this.synapses != null) {
foreach (Synapse synapse in synapses)
synapse.Rebuild(brain);
@ -43,7 +43,7 @@ public class Nucleus {
}
}
public static Nucleus RebuildType(NanoBrainObj brain, Nucleus nucleus) {
public static Nucleus RebuildType(NanoBrain brain, Nucleus nucleus) {
if (string.IsNullOrEmpty(nucleus.nucleusType) == false) {
Type nucleusType = Type.GetType(nucleus.nucleusType);
if (nucleusType != null) {
@ -62,7 +62,7 @@ public class Nucleus {
#region Runtime state (not serialized)
public NanoBrainObj brain { get; set; }
public NanoBrain brain { get; set; }
private Vector3 _outputValue;
public Vector3 outputValue
@ -154,11 +154,11 @@ public class Nucleus {
public virtual void UpdateState() { }
public void UpdateResult(Vector3 result) {
// float d = Vector3.Distance(result, this.outputValue);
// if (d < 0.1f) {
// //Debug.Log($"insignificant update: {d}");
// return;
// }
float d = Vector3.Distance(result, this.outputValue);
if (d < 0.5f) {
//Debug.Log($"insignificant update: {d}");
return;
}
this.outputValue = result;
foreach (Receiver receiver in this.receivers)
@ -181,8 +181,8 @@ public class Synapse {
Reciprocal,
Custom
}
public CurvePresets curvePreset;
public AnimationCurve curve;
// public CurvePresets curvePreset;
// public AnimationCurve curve;
public float curveMax = 1.0f;
public Synapse(Nucleus nucleus, float weight) {
@ -191,7 +191,7 @@ public class Synapse {
this.weight = weight;
}
public void Rebuild(NanoBrainObj brain) {
public void Rebuild(NanoBrain brain) {
if (brain == null) {
return;
}
@ -211,30 +211,30 @@ public class Synapse {
Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}");
}
public AnimationCurve GenerateCurve() {
switch (this.curvePreset) {
case CurvePresets.Linear:
this.curveMax = this.weight;
return Presets.Linear(this.weight);
case CurvePresets.Power:
this.curveMax = this.weight;
return Presets.Power(2.0f, this.weight);
case CurvePresets.Sqrt:
this.curveMax = this.weight;
return Presets.Power(0.5f, this.weight);
case CurvePresets.Reciprocal:
this.curveMax = 1 / 0.01f * this.weight;
return Presets.Reciprocal(this.weight);
default:
this.curveMax = weight;
return AnimationCurve.Constant(0, 1, weight);
}
}
// public AnimationCurve GenerateCurve() {
// switch (this.curvePreset) {
// case CurvePresets.Linear:
// this.curveMax = this.weight;
// return Presets.Linear(this.weight);
// case CurvePresets.Power:
// this.curveMax = this.weight;
// return Presets.Power(2.0f, this.weight);
// case CurvePresets.Sqrt:
// this.curveMax = this.weight;
// return Presets.Power(0.5f, this.weight);
// case CurvePresets.Reciprocal:
// this.curveMax = 1 / 0.01f * this.weight;
// return Presets.Reciprocal(this.weight);
// default:
// this.curveMax = weight;
// return AnimationCurve.Constant(0, 1, weight);
// }
// }
public static class Presets {
private const int samples = 32;
public static AnimationCurve Linear(float weight) {
return AnimationCurve.Linear(0f, 0f, 1f, weight);
return AnimationCurve.Linear(0f, 0f, 1000f, weight * 1000);
}
public static AnimationCurve Power(float exponent, float weight) {
// build keyframes
@ -287,7 +287,7 @@ public class Receiver {
this.nucleusId = nucleus.id;
}
public bool Rebuild(NanoBrainObj brain) {
public bool Rebuild(NanoBrain brain) {
if (brain == null) {
return false;
}

View File

@ -14,7 +14,7 @@ public class Perceptoid : Neuroid {
public int thingType;
public int thingId;
public override void Rebuild(NanoBrainObj brain) {
public override void Rebuild(NanoBrain brain) {
base.Rebuild(brain);
this.receptor = Receptor.GetReceptor(brain, thingType);
this.receptor.perceptei.Add(this);
@ -48,7 +48,7 @@ public class Perceptoid : Neuroid {
#endregion Serialization
public Perceptoid(NanoBrainObj brain, int thingType, string name = "sensor") : base(name) {
public Perceptoid(NanoBrain brain, int thingType, string name = "sensor") : base(name) {
this.brain = brain;
if (this.brain != null) {
this.brain.perceptei.Add(this);
@ -80,7 +80,7 @@ public class Perceptoid : Neuroid {
}
public static Perceptoid GetPerception(NanoBrainObj brain, int thingType = 0) {
public static Perceptoid GetPerception(NanoBrain brain, int thingType = 0) {
foreach (Nucleus nucleus in brain.nuclei) {
if (nucleus is Perceptoid perceptoid && (thingType == 0 || perceptoid.receptor.thingType == thingType))
return perceptoid;

View File

@ -22,13 +22,13 @@ public class Receptor {
public float distanceResolution = 0.1f;
public float directionResolution = 5;
public Receptor(NanoBrainObj brain, int thingType) {
public Receptor(NanoBrain brain, int thingType) {
this.thingType = thingType;
//this.perceptei.Add(perceptoid);
brain.receptors.Add(this);
}
public static Receptor GetReceptor(NanoBrainObj brain, int thingType) {
public static Receptor GetReceptor(NanoBrain brain, int thingType) {
foreach (Receptor receptor in brain.receptors) {
if (thingType == 0 || receptor.thingType == thingType)
return receptor;
@ -38,9 +38,6 @@ public class Receptor {
}
public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector) {
//Spherical newLocalPosition = Spherical.FromVector3(newLocalPositionVector);
Vector3 previousLocalPosition = this.localPosition;
this.localPosition = newLocalPositionVector;
Perceptoid selectedPerceptoid = null;
@ -50,16 +47,6 @@ public class Receptor {
selectedPerceptoid = perceptoid;
// Do not look any further
// // This does not do a lot....
// float deltaDistance = newLocalPosition.distance - previousLocalPosition.distance;
// // See if the change is significant
// AngleFloat deltaDirection = Direction.UnsignedAngle(newLocalPosition.direction, previousLocalPosition.direction);
// if (deltaDistance < this.distanceResolution && deltaDirection.inDegrees < directionResolution) {
// // The difference is not significant we don't process this data.
// this.localPosition = previousLocalPosition;
// return;
// }
// This is now also handled by the UpdateState Spherical.Distance
break;
}
else if (perceptoid.isSleeping) {

View File

@ -20,7 +20,7 @@ public class NanoBrainComponent_Editor : Editor {
public override VisualElement CreateInspectorGUI() {
//NanoBrainComponent component = target as NanoBrainComponent;
NanoBrainObj brain = Application.isPlaying ? component.brain : component.defaultBrain;
NanoBrain brain = Application.isPlaying ? component.brain : component.defaultBrain;
if (Application.isPlaying == false)
serializedObject.Update();

View File

@ -11,7 +11,7 @@ public class NucleusLayer {
}
public class NanoBrainEditor : EditorWindow {
public NanoBrainObj brain;
public NanoBrain brain;
public static VisualElement inspectorContainer;
@ -20,7 +20,7 @@ public class NanoBrainEditor : EditorWindow {
GetWindow<NanoBrainEditor>("NanoBrain Editor");
}
public static void Open(NanoBrainObj asset) {
public static void Open(NanoBrain asset) {
NanoBrainEditor editor = GetWindow<NanoBrainEditor>("NanoBrain Editor");
editor.brain = asset;
editor.Show();
@ -69,7 +69,7 @@ public class NanoBrainEditor : EditorWindow {
}
public class GraphBoardView : VisualElement {
NanoBrainObj brain;
NanoBrain brain;
SerializedObject serializedBrain;
Nucleus currentNucleus;
private List<NeuroidLayer> layers = new();
@ -99,7 +99,7 @@ public class GraphBoardView : VisualElement {
RegisterCallback<MouseUpEvent>(OnMouseUp);
}
public void SetGraph(NanoBrainObj brain, Nucleus nucleus) {
public void SetGraph(NanoBrain brain, Nucleus nucleus) {
this.brain = brain;
this.serializedBrain = new SerializedObject(brain);
this.currentNucleus = nucleus;
@ -473,9 +473,9 @@ public class GraphNodeWrapper : ScriptableObject {
public string title;
public Vector2 position;
Nucleus node;
NanoBrainObj graph; // needed to write back and mark dirty
NanoBrain graph; // needed to write back and mark dirty
public GraphNodeWrapper Init(Nucleus node, NanoBrainObj graphAsset) {
public GraphNodeWrapper Init(Nucleus node, NanoBrain graphAsset) {
this.node = node;
this.graph = graphAsset;
this.title = " A " + node.name;
@ -499,7 +499,7 @@ public static class OpenAssetHandler {
// Called when an asset is double-clicked or opened.
[OnOpenAsset]
public static bool OpenMyScriptableObject(int instanceID, int line) {
NanoBrainObj obj = EditorUtility.EntityIdToObject(instanceID) as NanoBrainObj;
NanoBrain obj = EditorUtility.EntityIdToObject(instanceID) as NanoBrain;
if (obj != null) {
NanoBrainEditor.Open(obj);
return true; // handled

View File

@ -5,7 +5,7 @@ using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
[CustomEditor(typeof(NanoBrainObj))]
[CustomEditor(typeof(NanoBrain))]
public class NanoBrainInspector : Editor {
protected static VisualElement mainContainer;
protected static VisualElement inspectorContainer;
@ -15,7 +15,7 @@ public class NanoBrainInspector : Editor {
#region Start
public override VisualElement CreateInspectorGUI() {
NanoBrainObj brain = target as NanoBrainObj;
NanoBrain brain = target as NanoBrain;
serializedObject.Update();
@ -72,7 +72,7 @@ public class NanoBrainInspector : Editor {
}
public class GraphView : VisualElement {
NanoBrainObj brain;
NanoBrain brain;
SerializedObject serializedBrain;
Nucleus currentNucleus;
GameObject gameObject;
@ -103,7 +103,7 @@ public class NanoBrainInspector : Editor {
RegisterCallback<MouseUpEvent>(OnMouseUp);
}
public void SetGraph(GameObject gameObject, NanoBrainObj brain, Nucleus nucleus, VisualElement inspectorContainer) {
public void SetGraph(GameObject gameObject, NanoBrain brain, Nucleus nucleus, VisualElement inspectorContainer) {
this.gameObject = gameObject;
this.brain = brain;
if (Application.isPlaying == false)

View File

@ -1,15 +1,15 @@
using UnityEngine;
public class NanoBrainComponent : MonoBehaviour {
public NanoBrainObj defaultBrain;
private NanoBrainObj brainInstance;
public NanoBrain defaultBrain;
private NanoBrain brainInstance;
public Nucleus root {
get {
return brainInstance.root;
}
}
public NanoBrainObj brain {
public NanoBrain brain {
get {
if (brainInstance == null && defaultBrain != null) {
brainInstance = Instantiate(defaultBrain);

View File

@ -3,7 +3,7 @@ using UnityEngine;
using LinearAlgebra;
[CreateAssetMenu(menuName = "Passer/NanoBrain")]
public class NanoBrainObj : ScriptableObject, ISerializationCallbackReceiver {
public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver {
public string title;
public int count;
@ -21,7 +21,7 @@ public class NanoBrainObj : ScriptableObject, ISerializationCallbackReceiver {
// public Perception perception;
public NanoBrainObj() {
public NanoBrain() {
this.root = new Neuroid(this, "Root");
// this.perception = new Perception(this);
}

View File

@ -375,10 +375,10 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::SwarmControl
speed: 1
inertia: 0.7
alignmentForce: 2
cohesionForce: 4
alignmentForce: 0.5
cohesionForce: 2.2
separationForce: -5
avoidanceForce: 5
avoidanceForce: 1
separationDistance: 0.3
perceptionDistance: 2
boundaryForce: 5

View File

@ -10,9 +10,9 @@ public class SwarmControl_Editor : Editor {
if (EditorGUI.EndChangeCheck()) {
SwarmControl swarmControl = (SwarmControl)target;
NanoBrainObj[] nanoBrains = FindObjectsByType<NanoBrainObj>(FindObjectsSortMode.None);
NanoBrain[] nanoBrains = FindObjectsByType<NanoBrain>(FindObjectsSortMode.None);
foreach (NanoBrainObj brain in nanoBrains) {
foreach (NanoBrain brain in nanoBrains) {
UpdateWeight(brain, "Avoidance", swarmControl.avoidanceForce);
UpdateWeight(brain, "Cohesion", swarmControl.cohesionForce);
UpdateWeight(brain, "Separation", swarmControl.separationForce);
@ -22,7 +22,7 @@ public class SwarmControl_Editor : Editor {
}
}
protected void UpdateWeight(NanoBrainObj brain, string name, float weight) {
protected void UpdateWeight(NanoBrain brain, string name, float weight) {
Nucleus root = brain.root;
foreach (Synapse synapse in root.synapses) {
if (synapse.nucleus.name == name) {

File diff suppressed because it is too large Load Diff

Binary file not shown.