using System;
using UnityEngine;
using Unity.Mathematics;
namespace NanoBrain {
///
/// A Synapse connects the ouput of a Neuron to another Neuron
///
[Serializable]
public class Synapse {
///
/// The neuron from which input is received
///
[SerializeReference]
[HideInInspector]
public Neuron neuron;
///
/// The weight value to apply to the Neuron input
///
public float weight;
///
/// Indicator whether the weight can be trained
///
public bool trainable = false;
///
/// Create a new Synapse
///
/// The neuron from which input is received
/// The weight value to apply to the Neuron input
public Synapse(Neuron nucleus, float weight = 1.0f) {
this.neuron = nucleus;
this.weight = weight;
}
public virtual void BackPropagation(Neuron receiver, float derivative, float learningRate) {
switch (receiver.activator) {
case Neuron.ActivationType.Linear:
derivative *= 1;
break;
case Neuron.ActivationType.Power:
// untested
derivative *= 2 * math.length(this.neuron.combination);
break;
case Neuron.ActivationType.Reciprocal:
// untested
derivative *= -1 / Mathf.Pow(math.length(this.neuron.combination), 2);
break;
default:
Debug.Log("other activator");
break;
}
this.neuron.BackPropagation1D(derivative * this.weight, learningRate);
derivative *= math.length(this.neuron.activation);
if (this.trainable) {
float deltaWeight = learningRate * derivative;
this.weight += deltaWeight;
}
}
public virtual void BackPropagation3D(Neuron receiver, Vector3 derivative, float 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
if (Synapse.AreOpposed(derivative, this.neuron.activation))
BackPropagation(receiver, -derivative.magnitude, learningRate);
else
BackPropagation(receiver, derivative.magnitude, learningRate);
// switch (receiver.activator) {
// case Neuron.ActivationType.Linear:
// derivative *= 1;
// break;
// case Neuron.ActivationType.Power:
// // untested
// derivative *= 2 * math.length(this.neuron.combination);
// break;
// case Neuron.ActivationType.Reciprocal:
// // untested
// derivative *= -1 / Mathf.Pow(math.length(this.neuron.combination), 2);
// break;
// default:
// Debug.Log("other activator");
// break;
// }
// this.neuron.BackPropagation3D(derivative * this.weight, learningRate);
// derivative *= math.length(this.neuron.activation);
// if (this.trainable) {
// float deltaWeight = learningRate * derivative.magnitude;
// // Compared to the 1D solution, this does not decrease the weight because magnitude is always positive
// // derivative.direction and derivative.sign are different....
// if (AreOpposed(derivative, this.neuron.activation))
// this.weight -= deltaWeight;
// else
// this.weight += deltaWeight;
// }
}
public static bool AreOpposed(Vector3 a, Vector3 b) {
// 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]
public class SynapseData {
public string clusterName;
public string neuronName;
public float weight;
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.name;
this.neuronName = synapse.neuron.name;
this.weight = synapse.weight;
this.trainable = synapse.trainable;
}
}
}